Initial commit.
5
web/public/.eslintrc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true
|
||||
}
|
||||
}
|
46
web/public/404.html
Normal file
|
@ -0,0 +1,46 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title ng-bind="Hostr">Hostr - File not found</title>
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png">
|
||||
<link href='//fonts.googleapis.com/css?family=Lato:300,400' rel='stylesheet' type='text/css'>
|
||||
<link href='//fonts.googleapis.com/css?family=Open+Sans:400,300,600' rel='stylesheet' type='text/css'>
|
||||
<link href="/styles/app.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<section class="container header clearfix">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2" style='text-align: center;'>
|
||||
<div class="logo">
|
||||
<a href="/"><img src="/images/logo-dark-r.png" height="22" width="26" alt=""></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="jumbotron error-page">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<h1>404</h1>
|
||||
<h2>We can't find the file you're looking for :(</h2>
|
||||
|
||||
<p class="lead">The owner may have removed it or it may never have existed in the first place.</p>
|
||||
|
||||
<a href="/">Try our homepage instead :)</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
var _gaq=[['_setAccount','UA-66209-2'],['_setDomainName', 'hostr.co'],['_trackPageview']];
|
||||
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
|
||||
g.src='//www.google-analytics.com/ga.js';
|
||||
s.parentNode.insertBefore(g,s)}(document,'script'));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
44
web/public/50x.html
Normal file
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title ng-bind="Hostr">Hostr - File not found</title>
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png">
|
||||
<link href='//fonts.googleapis.com/css?family=Lato:300,400' rel='stylesheet' type='text/css'>
|
||||
<link href='//fonts.googleapis.com/css?family=Open+Sans:400,300,600' rel='stylesheet' type='text/css'>
|
||||
<link href="/styles/app.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<section class="container header clearfix">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2" style='text-align: center;'>
|
||||
<div class="logo">
|
||||
<a href="/"><img src="/images/logo-dark-r.png" height="22" width="26" alt=""></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="jumbotron error-page">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<h1>Oops!</h1>
|
||||
<h2>It looks like you've hit an unexpected error :(</h2>
|
||||
|
||||
<p class="lead">Refreshing might fix the problem. If not, sit tight! We're on it!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
var _gaq=[['_setAccount','UA-66209-2'],['_setDomainName', 'hostr.co'],['_trackPageview']];
|
||||
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
|
||||
g.src='//www.google-analytics.com/ga.js';
|
||||
s.parentNode.insertBefore(g,s)}(document,'script'));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
12
web/public/browserconfig.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square70x70logo src="/mstile-70x70.png"/>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<square310x310logo src="/mstile-310x310.png"/>
|
||||
<wide310x150logo src="/mstile-310x150.png"/>
|
||||
<TileColor>#00aba9</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
78
web/public/config.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
System.config({
|
||||
"defaultJSExtensions": true,
|
||||
"transpiler": "babel",
|
||||
"babelOptions": {
|
||||
"optional": [
|
||||
"runtime"
|
||||
]
|
||||
},
|
||||
"paths": {
|
||||
"github:*": "jspm_packages/github/*",
|
||||
"npm:*": "jspm_packages/npm/*"
|
||||
}
|
||||
});
|
||||
|
||||
System.config({
|
||||
"map": {
|
||||
"angular": "npm:angular@1.4.3",
|
||||
"angular-reconnecting-websocket": "github:adieu/angular-reconnecting-websocket@0.1.1",
|
||||
"angular-strap": "npm:angular-strap@2.1.2",
|
||||
"angular/resource": "npm:angular-resource@1.4.3",
|
||||
"angular/route": "npm:angular-route@1.4.3",
|
||||
"babel": "npm:babel-core@5.8.5",
|
||||
"babel-runtime": "npm:babel-runtime@5.8.5",
|
||||
"bootstrap-sass": "npm:bootstrap-sass@3.3.5",
|
||||
"cferdinandi/smooth-scroll": "github:cferdinandi/smooth-scroll@5.3.7",
|
||||
"core-js": "npm:core-js@0.9.18",
|
||||
"dropzone": "npm:dropzone@4.0.1",
|
||||
"jquery": "npm:jquery@2.1.4",
|
||||
"zeroclipboard": "npm:zeroclipboard@2.2.0",
|
||||
"github:jspm/nodelibs-path@0.1.0": {
|
||||
"path-browserify": "npm:path-browserify@0.0.0"
|
||||
},
|
||||
"github:jspm/nodelibs-process@0.1.1": {
|
||||
"process": "npm:process@0.10.1"
|
||||
},
|
||||
"github:jspm/nodelibs-util@0.1.0": {
|
||||
"util": "npm:util@0.10.3"
|
||||
},
|
||||
"npm:angular-strap@2.1.2": {
|
||||
"fs": "github:jspm/nodelibs-fs@0.1.2",
|
||||
"path": "github:jspm/nodelibs-path@0.1.0",
|
||||
"process": "github:jspm/nodelibs-process@0.1.1",
|
||||
"systemjs-json": "github:systemjs/plugin-json@0.1.0",
|
||||
"util": "github:jspm/nodelibs-util@0.1.0"
|
||||
},
|
||||
"npm:angular@1.4.3": {
|
||||
"process": "github:jspm/nodelibs-process@0.1.1"
|
||||
},
|
||||
"npm:babel-runtime@5.8.5": {
|
||||
"process": "github:jspm/nodelibs-process@0.1.1"
|
||||
},
|
||||
"npm:core-js@0.9.18": {
|
||||
"fs": "github:jspm/nodelibs-fs@0.1.2",
|
||||
"process": "github:jspm/nodelibs-process@0.1.1",
|
||||
"systemjs-json": "github:systemjs/plugin-json@0.1.0"
|
||||
},
|
||||
"npm:dropzone@4.0.1": {
|
||||
"process": "github:jspm/nodelibs-process@0.1.1"
|
||||
},
|
||||
"npm:inherits@2.0.1": {
|
||||
"util": "github:jspm/nodelibs-util@0.1.0"
|
||||
},
|
||||
"npm:jquery@2.1.4": {
|
||||
"process": "github:jspm/nodelibs-process@0.1.1"
|
||||
},
|
||||
"npm:path-browserify@0.0.0": {
|
||||
"process": "github:jspm/nodelibs-process@0.1.1"
|
||||
},
|
||||
"npm:util@0.10.3": {
|
||||
"inherits": "npm:inherits@2.0.1",
|
||||
"process": "github:jspm/nodelibs-process@0.1.1"
|
||||
},
|
||||
"npm:zeroclipboard@2.2.0": {
|
||||
"process": "github:jspm/nodelibs-process@0.1.1"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
BIN
web/public/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
web/public/images/animation.png
Normal file
After Width: | Height: | Size: 9 KiB |
BIN
web/public/images/apple-logo-app.png
Normal file
After Width: | Height: | Size: 982 B |
BIN
web/public/images/apple-touch-icon-114x114.png
Normal file
After Width: | Height: | Size: 860 B |
BIN
web/public/images/apple-touch-icon-120x120.png
Normal file
After Width: | Height: | Size: 849 B |
BIN
web/public/images/apple-touch-icon-144x144.png
Normal file
After Width: | Height: | Size: 1,022 B |
BIN
web/public/images/apple-touch-icon-152x152.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
web/public/images/apple-touch-icon-57x57.png
Normal file
After Width: | Height: | Size: 539 B |
BIN
web/public/images/apple-touch-icon-60x60.png
Normal file
After Width: | Height: | Size: 537 B |
BIN
web/public/images/apple-touch-icon-72x72.png
Normal file
After Width: | Height: | Size: 632 B |
BIN
web/public/images/apple-touch-icon-76x76.png
Normal file
After Width: | Height: | Size: 598 B |
BIN
web/public/images/apple-touch-icon-precomposed.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
web/public/images/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
web/public/images/apple.png
Normal file
After Width: | Height: | Size: 221 B |
BIN
web/public/images/arrow_down.png
Normal file
After Width: | Height: | Size: 173 B |
BIN
web/public/images/bullet-r.png
Normal file
After Width: | Height: | Size: 217 B |
BIN
web/public/images/bullet.png
Normal file
After Width: | Height: | Size: 155 B |
BIN
web/public/images/camera.png
Normal file
After Width: | Height: | Size: 379 B |
9
web/public/images/chevron20.svg
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 10 10" enable-background="new 0 0 10 10" xml:space="preserve" height="10" width="10">
|
||||
<path fill="#c3c3d1" d="M5,5.1L1.9,1.9C1.7,1.8,1.5,1.7,1.3,1.7S0.9,1.8,0.7,1.9L0.2,2.4C0.1,2.6,0,2.8,0,3c0,0.2,0.1,0.4,0.2,0.6l4.2,4.2
|
||||
C4.6,7.9,4.8,8,5,8s0.4-0.1,0.6-0.2l4.2-4.2C9.9,3.4,10,3.2,10,3c0-0.2-0.1-0.4-0.2-0.6L9.3,1.9C9.1,1.8,8.9,1.7,8.7,1.7
|
||||
c-0.2,0-0.4,0.1-0.6,0.2L5,5.1z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 763 B |
BIN
web/public/images/clock-25.png
Normal file
After Width: | Height: | Size: 470 B |
BIN
web/public/images/clock-50.png
Normal file
After Width: | Height: | Size: 855 B |
3
web/public/images/cloud-transfer-upload-sm.svg
Executable file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" data-icon="cloud-transfer-upload" width="16" height="16" data-container-transform="scale(1 1 ) translate(0 )" viewBox="0 0 16 16">
|
||||
<path fill="#c3c3d1" d="M8 0c-2.5 0-4.506 1.794-4.906 4.094-1.8.4-3.094 2.006-3.094 3.906 0 2.2 1.8 4 4 4l4-4 4 4h1c1.7 0 3-1.3 3-3s-1.3-3-3-3v-1c0-2.8-2.2-5-5-5zm0 10l-3 3h2v2a1 1 0 1 0 2 0v-2h2l-3-3z" />
|
||||
</svg>
|
After Width: | Height: | Size: 400 B |
BIN
web/public/images/cloud_top.jpg
Normal file
After Width: | Height: | Size: 56 KiB |
19
web/public/images/cloud_upload_font_awesome.svg
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1"
|
||||
id="svg3001" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:svg="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:dc="http://purl.org/dc/elements/1.1/" inkscape:version="0.48.3.1 r9886" sodipodi:docname="cloud_upload_font_awesome.svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 16 16"
|
||||
enable-background="new 0 0 16 16" xml:space="preserve" height="16" width="16">
|
||||
<sodipodi:namedview inkscape:cx="960" inkscape:cy="896" gridtolerance="10" borderopacity="1" id="namedview3007" bordercolor="#666666" objecttolerance="10" inkscape:zoom="0.13169643" showgrid="false" guidetolerance="10" pagecolor="#ffffff" inkscape:current-layer="svg3001" inkscape:window-maximized="0" inkscape:window-y="25" inkscape:window-x="0" inkscape:window-height="16" inkscape:window-width="16" inkscape:pageopacity="0" inkscape:pageshadow="2">
|
||||
</sodipodi:namedview>
|
||||
<g id="g3003" transform="matrix(1,0,0,-1,22.779661,1428.2373)">
|
||||
<path fill="#c3c3d1" id="path3005" inkscape:connector-curvature="0" d="M-12.1,1420c0,0.1,0,0.1-0.1,0.2l-2.9,2.9c-0.1,0-0.1,0.1-0.2,0.1
|
||||
s-0.1,0-0.2-0.1l-2.9-2.9c-0.1-0.1-0.1-0.1-0.1-0.2s0-0.1,0.1-0.2c0-0.1,0.1-0.1,0.2-0.1h1.9v-2.9c0-0.1,0-0.1,0.1-0.2
|
||||
s0.1-0.1,0.2-0.1h1.6c0.1,0,0.1,0,0.2,0.1c0.1,0.1,0.1,0.1,0.1,0.2v2.9h1.9c0.1,0,0.1,0,0.2,0.1
|
||||
C-12.1,1419.9-12.1,1419.9-12.1,1420z M-6.8,1417.6c0-0.9-0.3-1.6-0.9-2.3s-1.4-0.9-2.3-0.9h-9c-1,0-1.9,0.4-2.6,1.1
|
||||
s-1.1,1.6-1.1,2.6c0,0.7,0.2,1.4,0.6,2s0.9,1.1,1.6,1.4c0,0.2,0,0.3,0,0.4c0,1.2,0.4,2.2,1.2,3s1.8,1.2,3,1.2
|
||||
c0.9,0,1.7-0.2,2.4-0.7s1.2-1.1,1.6-1.9c0.4,0.3,0.9,0.5,1.4,0.5c0.6,0,1.1-0.2,1.5-0.6s0.6-0.9,0.6-1.5c0-0.4-0.1-0.8-0.3-1.1
|
||||
c0.7-0.2,1.3-0.5,1.8-1.1C-7,1419-6.8,1418.3-6.8,1417.6z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
19
web/public/images/cloud_upload_font_awesome_red.svg
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1"
|
||||
id="svg3001" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:svg="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:dc="http://purl.org/dc/elements/1.1/" inkscape:version="0.48.3.1 r9886" sodipodi:docname="cloud_upload_font_awesome.svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 16 16"
|
||||
enable-background="new 0 0 16 16" xml:space="preserve" height="16" width="16">
|
||||
<sodipodi:namedview inkscape:cx="960" inkscape:cy="896" gridtolerance="10" borderopacity="1" id="namedview3007" bordercolor="#666666" objecttolerance="10" inkscape:zoom="0.13169643" showgrid="false" guidetolerance="10" pagecolor="#ffffff" inkscape:current-layer="svg3001" inkscape:window-maximized="0" inkscape:window-y="25" inkscape:window-x="0" inkscape:window-height="16" inkscape:window-width="16" inkscape:pageopacity="0" inkscape:pageshadow="2">
|
||||
</sodipodi:namedview>
|
||||
<g id="g3003" transform="matrix(1,0,0,-1,22.779661,1428.2373)">
|
||||
<path fill="#a94442" id="path3005" inkscape:connector-curvature="0" d="M-12.1,1420c0,0.1,0,0.1-0.1,0.2l-2.9,2.9c-0.1,0-0.1,0.1-0.2,0.1
|
||||
s-0.1,0-0.2-0.1l-2.9-2.9c-0.1-0.1-0.1-0.1-0.1-0.2s0-0.1,0.1-0.2c0-0.1,0.1-0.1,0.2-0.1h1.9v-2.9c0-0.1,0-0.1,0.1-0.2
|
||||
s0.1-0.1,0.2-0.1h1.6c0.1,0,0.1,0,0.2,0.1c0.1,0.1,0.1,0.1,0.1,0.2v2.9h1.9c0.1,0,0.1,0,0.2,0.1
|
||||
C-12.1,1419.9-12.1,1419.9-12.1,1420z M-6.8,1417.6c0-0.9-0.3-1.6-0.9-2.3s-1.4-0.9-2.3-0.9h-9c-1,0-1.9,0.4-2.6,1.1
|
||||
s-1.1,1.6-1.1,2.6c0,0.7,0.2,1.4,0.6,2s0.9,1.1,1.6,1.4c0,0.2,0,0.3,0,0.4c0,1.2,0.4,2.2,1.2,3s1.8,1.2,3,1.2
|
||||
c0.9,0,1.7-0.2,2.4-0.7s1.2-1.1,1.6-1.9c0.4,0.3,0.9,0.5,1.4,0.5c0.6,0,1.1-0.2,1.5-0.6s0.6-0.9,0.6-1.5c0-0.4-0.1-0.8-0.3-1.1
|
||||
c0.7-0.2,1.3-0.5,1.8-1.1C-7,1419-6.8,1418.3-6.8,1417.6z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
BIN
web/public/images/collection_thumb.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
web/public/images/connected.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
web/public/images/favicon-160x160.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
web/public/images/favicon-16x16.png
Normal file
After Width: | Height: | Size: 277 B |
BIN
web/public/images/favicon-196x196.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
web/public/images/favicon-32x32.png
Normal file
After Width: | Height: | Size: 441 B |
BIN
web/public/images/favicon-96x96.png
Normal file
After Width: | Height: | Size: 919 B |
BIN
web/public/images/favicon.png
Normal file
After Width: | Height: | Size: 542 B |
BIN
web/public/images/fb.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
web/public/images/file-adjusted.png
Normal file
After Width: | Height: | Size: 272 B |
BIN
web/public/images/file-cog.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
web/public/images/file.png
Normal file
After Width: | Height: | Size: 182 B |
BIN
web/public/images/file_icon.png
Normal file
After Width: | Height: | Size: 236 B |
BIN
web/public/images/file_thumb.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
web/public/images/filetype_header.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
web/public/images/folder.png
Normal file
After Width: | Height: | Size: 168 B |
BIN
web/public/images/gear.png
Normal file
After Width: | Height: | Size: 234 B |
BIN
web/public/images/hostr-logo-500.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
web/public/images/icons.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
web/public/images/logo-dark-r.png
Normal file
After Width: | Height: | Size: 884 B |
BIN
web/public/images/logo.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
web/public/images/logo_dark.png
Normal file
After Width: | Height: | Size: 335 B |
BIN
web/public/images/main-logo.png
Normal file
After Width: | Height: | Size: 816 B |
BIN
web/public/images/menu-retina.png
Normal file
After Width: | Height: | Size: 183 B |
BIN
web/public/images/menu.png
Normal file
After Width: | Height: | Size: 123 B |
BIN
web/public/images/mstile-144x144.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
web/public/images/mstile-150x150.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
web/public/images/mstile-310x150.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
web/public/images/mstile-310x310.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
web/public/images/mstile-70x70.png
Normal file
After Width: | Height: | Size: 951 B |
BIN
web/public/images/music.png
Normal file
After Width: | Height: | Size: 409 B |
BIN
web/public/images/person.png
Executable file
After Width: | Height: | Size: 745 B |
BIN
web/public/images/plus.png
Normal file
After Width: | Height: | Size: 313 B |
BIN
web/public/images/round-icons.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
web/public/images/search.png
Normal file
After Width: | Height: | Size: 294 B |
BIN
web/public/images/share_anything.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
web/public/images/share_anywhere.png
Normal file
After Width: | Height: | Size: 6 KiB |
BIN
web/public/images/stripe-128.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
web/public/images/sync_wireframe.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
web/public/images/tagline.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
web/public/images/url.png
Normal file
After Width: | Height: | Size: 351 B |
BIN
web/public/images/user.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
web/public/images/video.png
Normal file
After Width: | Height: | Size: 243 B |
BIN
web/public/images/windows-app-icon.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
web/public/images/windows-backdrop.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
web/public/images/windows-logo-app.png
Normal file
After Width: | Height: | Size: 668 B |
BIN
web/public/images/windows-white-icon.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
web/public/images/windows.png
Normal file
After Width: | Height: | Size: 252 B |
42
web/public/maintenance.html
Normal file
|
@ -0,0 +1,42 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title ng-bind="Hostr">Hostr - File not found</title>
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png">
|
||||
<link href='//fonts.googleapis.com/css?family=Lato:300,400' rel='stylesheet' type='text/css'>
|
||||
<link href='//fonts.googleapis.com/css?family=Open+Sans:400,300,600' rel='stylesheet' type='text/css'>
|
||||
<link href="/styles/app.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<section class="container header clearfix">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2" style='text-align: center;'>
|
||||
<div class="logo">
|
||||
<a href="/"><img src="/images/logo-dark-r.png" height="22" width="26" alt=""></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="jumbotron error-page">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<h1>BRB</h1>
|
||||
<h2>We're just performing some upgrades, we'll be right back!</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
var _gaq=[['_setAccount','UA-66209-2'],['_setDomainName', 'hostr.co'],['_trackPageview']];
|
||||
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
|
||||
g.src='//www.google-analytics.com/ga.js';
|
||||
s.parentNode.insertBefore(g,s)}(document,'script'));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
3
web/public/robots.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
# hostr.co/
|
||||
|
||||
User-agent: *
|
129
web/public/src/app.js
Normal file
|
@ -0,0 +1,129 @@
|
|||
import angular from 'angular';
|
||||
import ngRoute from 'angular/route';
|
||||
import ngResource from 'angular/resource';
|
||||
import ReconnectingWebSocket from 'angular-reconnecting-websocket';
|
||||
import ngDimensions from 'angular-strap/dist/modules/dimensions';
|
||||
import ngTooltip from 'angular-strap/dist/modules/tooltip';
|
||||
|
||||
import { FilesController, FileController, AccountController, ProController, BillingController } from './app/controllers';
|
||||
import { appHeader, appFooter, menuDropdown, searchShortcut, stripeSubscribe } from './app/directives';
|
||||
import dropzone from './app/directives/dropzone';
|
||||
import lazySrc from './app/directives/lazy-src';
|
||||
import { fileSize, direct } from './app/filters';
|
||||
import { FileService, UserService, EventService, TransactionService, SettingService } from './app/services';
|
||||
|
||||
// Declare app level module which depends on filters, and services
|
||||
var app = angular.module('hostr', [
|
||||
'ngRoute',
|
||||
'ngResource',
|
||||
'reconnectingWebSocket',
|
||||
'mgcrea.ngStrap.tooltip'
|
||||
]);
|
||||
|
||||
app.factory('FileService', ['$resource', '$cacheFactory', FileService.factory]);
|
||||
app.factory('UserService', ['$resource', UserService.factory]);
|
||||
app.factory('EventService', ['$rootScope', ReconnectingWebSocket, EventService.factory]);
|
||||
app.factory('TransactionService', ['$resource', '$cacheFactory', TransactionService.factory]);
|
||||
app.factory('SettingService', ['$http', SettingService.factory]);
|
||||
|
||||
app.filter('fileSize', [fileSize]);
|
||||
app.filter('direct', [direct]);
|
||||
|
||||
app.directive('appHeader', [appHeader]);
|
||||
app.directive('appFooter', [appFooter]);
|
||||
app.directive('dropzone', ['FileService', '$cacheFactory', '$window', dropzone]);
|
||||
app.directive('menuDropdown', [menuDropdown]);
|
||||
app.directive('lazySrc', ['$window', '$document', lazySrc]);
|
||||
app.directive('searchShortcut', ['$document', searchShortcut]);
|
||||
app.directive('stripeSubscribe', ['$http', stripeSubscribe]);
|
||||
|
||||
app.config(['$routeProvider', '$locationProvider', '$httpProvider', '$tooltipProvider', function($routeProvider, $locationProvider, $httpProvider, $tooltipProvider) {
|
||||
|
||||
$tooltipProvider.defaults.template = '/jspm_packages/npm/angular-strap@2.1.2/src/tooltip/tooltip.tpl.html';
|
||||
|
||||
if (typeof window.user !== 'undefined') {
|
||||
$httpProvider.defaults.headers.common.Authorization = ':' + window.user.token;
|
||||
}
|
||||
$locationProvider.html5Mode(true);
|
||||
|
||||
$httpProvider.interceptors.push(['$q', function($q) {
|
||||
return {
|
||||
responseError: function(rejection) {
|
||||
if (rejection.status === 401) {
|
||||
window.location = '/logout';
|
||||
}
|
||||
return $q.reject(rejection);
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
$routeProvider.when('/', {
|
||||
templateUrl: '/build/partials/files.html',
|
||||
controller: FilesController,
|
||||
title: ' - Files',
|
||||
resolve: {
|
||||
files: ['FileService', function(Files) {
|
||||
return Files.query();
|
||||
}]
|
||||
}
|
||||
})
|
||||
.when('/apps', {
|
||||
templateUrl: '/build/partials/apps.html',
|
||||
title: ' - Apps for Mac and Windows'
|
||||
})
|
||||
.when('/pro', {
|
||||
templateUrl: '/build/partials/pro.html',
|
||||
controller: ProController,
|
||||
title: ' - Pro'
|
||||
})
|
||||
.when('/account', {
|
||||
templateUrl: '/build/partials/account.html',
|
||||
controller: AccountController,
|
||||
title: ' - Account'
|
||||
})
|
||||
.when('/billing', {
|
||||
templateUrl: '/build/partials/billing.html',
|
||||
controller: BillingController,
|
||||
title: ' - Billing'
|
||||
})
|
||||
.when('/terms', {
|
||||
templateUrl: '/build/partials/terms.html',
|
||||
title: ' - Terms of Service'
|
||||
})
|
||||
.when('/privacy', {
|
||||
templateUrl: '/build/partials/privacy.html',
|
||||
title: ' - Privacy Policy'
|
||||
})
|
||||
.when('/:id', {
|
||||
templateUrl: '/build/partials/file.html',
|
||||
controller: FileController,
|
||||
resolve: {
|
||||
file: ['$route', 'FileService', function($route, Files) {
|
||||
return Files.get({id: $route.current.params.id});
|
||||
}]
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
||||
app.run(['$location', '$rootScope', function($location, $rootScope) {
|
||||
|
||||
$rootScope.$on('$routeChangeStart', function(e, curr) {
|
||||
if (curr.$$route && curr.$$route.resolve) {
|
||||
// Show a loading message until promises are resolved
|
||||
$rootScope.loadingView = true;
|
||||
}
|
||||
});
|
||||
$rootScope.$on('$routeChangeSuccess', function (event, current) {
|
||||
$rootScope.navError = false;
|
||||
$rootScope.pageTitle = current.$$route.title;
|
||||
});
|
||||
$rootScope.$on('$routeChangeError', function () {
|
||||
$rootScope.loadingView = false;
|
||||
$rootScope.navError = true;
|
||||
});
|
||||
$rootScope.$on('$locationChangeStart', function(event, newUrl) {
|
||||
if (window.ga) {
|
||||
window.ga('send', 'pageview', newUrl);
|
||||
}
|
||||
});
|
||||
}]);
|
105
web/public/src/app/controllers.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
export class FilesController {
|
||||
constructor($scope, UserService, files) {
|
||||
$scope.$root.user = UserService.get();
|
||||
files.$promise.then(function() {
|
||||
$scope.$root.loadingView = false;
|
||||
});
|
||||
$scope.header = 'full';
|
||||
if (!$scope.$root.files) {
|
||||
$scope.$root.files = files;
|
||||
}
|
||||
$scope.remove = function(file) {
|
||||
$scope.$root.files.some(function(existingFile, index) {
|
||||
if (file.id === existingFile.id) {
|
||||
file.$remove(function() {
|
||||
$scope.$root.showDropdown = false;
|
||||
$scope.$root.files.splice(index, 1);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
FilesController.$inject = ['$scope', 'UserService', 'files'];
|
||||
|
||||
export class FileController {
|
||||
constructor ($scope, $rootScope, $routeParams, ReconnectingWebSocket, file) {
|
||||
file.$promise.then(function() {
|
||||
$scope.$root.loadingView = false;
|
||||
$scope.header = 'small';
|
||||
$scope.file = file;
|
||||
$scope.direct = '/file/' + file.id + '/' + file.name;
|
||||
$rootScope.pageTitle = ' - ' + file.name;
|
||||
if (file.status === 'uploading') {
|
||||
file.percent = 0;
|
||||
var ws = new ReconnectingWebSocket('wss://' + window.location.hostname + window.settings.api + '/file/' + file.id);
|
||||
ws.onmessage = function (msg) {
|
||||
var evt = JSON.parse(msg.data);
|
||||
$rootScope.$broadcast(evt.type, evt.data);
|
||||
};
|
||||
ws.onopen = function() {
|
||||
ws.send(JSON.stringify({authorization: window.user.token}));
|
||||
};
|
||||
$rootScope.$on('file-progress', function(evt, data) {
|
||||
$scope.file.percent = data.complete;
|
||||
});
|
||||
$rootScope.$on('file-added', function(evt, data) {
|
||||
$scope.file = data;
|
||||
});
|
||||
$rootScope.$on('file-accepted', function(evt, data) {
|
||||
$scope.file = data;
|
||||
});
|
||||
}
|
||||
}, function() {
|
||||
$rootScope.navError = true;
|
||||
$scope.$root.loadingView = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
FileController.$inject = ['$scope', '$rootScope', '$routeParams', 'WebSocket', 'file'];
|
||||
|
||||
export class ProController {
|
||||
constructor ($scope, $http, UserService) {
|
||||
$scope.$root.loadingView = false;
|
||||
$scope.user = UserService.get();
|
||||
$scope.header = 'full';
|
||||
$scope.cancel = function() {
|
||||
$http.post('/pro/cancel').success(function() {
|
||||
window.location.reload(true);
|
||||
}).error(function(data) {
|
||||
console.log(new Error(data));
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
ProController.$inject = ['$scope', '$http', 'UserService'];
|
||||
|
||||
export class AccountController {
|
||||
constructor ($scope, UserService, SettingService) {
|
||||
$scope.$root.loadingView = false;
|
||||
$scope.$root.user = UserService.get();
|
||||
$scope.submit = function(form) {
|
||||
$scope.updated = false;
|
||||
$scope.error = false;
|
||||
SettingService.update(form).then(function() {
|
||||
$scope.updated = true;
|
||||
delete $scope.user.new_password;
|
||||
delete $scope.user.current_password;
|
||||
}, function(response) {
|
||||
$scope.error = response.data.error.message;
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
AccountController.$inject = ['$scope', 'UserService', 'SettingService'];
|
||||
|
||||
export class BillingController {
|
||||
constructor ($scope, UserService, TransactionService) {
|
||||
$scope.$root.loadingView = false;
|
||||
$scope.$root.user = UserService.get();
|
||||
$scope.transactions = TransactionService.query();
|
||||
}
|
||||
}
|
||||
BillingController.$inject = ['$scope', 'UserService', 'TransactionService'];
|
121
web/public/src/app/directives.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
import $ from 'jquery';
|
||||
|
||||
export function appHeader() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: '/build/partials/header.html',
|
||||
replace: true,
|
||||
link: function postLink(scope) {
|
||||
scope.userMD5 = window.user.md5;
|
||||
scope.email = window.user.email;
|
||||
scope.pro = (window.user.type === 'Pro');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function appFooter() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: '/build/partials/footer.html',
|
||||
replace: true,
|
||||
link: function postLink(scope) {
|
||||
scope.userMD5 = window.user.md5;
|
||||
scope.email = window.user.email;
|
||||
scope.pro = (window.user.type === 'Pro');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function menuDropdown() {
|
||||
return function($scope, element) {
|
||||
$scope.$root.overlayClick = function overlayClick() {
|
||||
$scope.$root.showDropdown = false;
|
||||
$('.dropdown').hide();
|
||||
};
|
||||
var activeDropdown = $(element).find('.dropdown');
|
||||
element.on('click', function(e) {
|
||||
if (activeDropdown.not(':visible').length > 0) {
|
||||
$('.dropdown').hide();
|
||||
$scope.$root.showDropdown = true;
|
||||
activeDropdown.show();
|
||||
} else if (e.target === element.find('img')[0]) {
|
||||
$scope.$root.showDropdown = false;
|
||||
activeDropdown.hide();
|
||||
}
|
||||
$scope.$apply();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function searchShortcut ($document) {
|
||||
return function($scope, element) {
|
||||
$document.bind('keypress', function(event) {
|
||||
if(event.which === 47) {
|
||||
if (['INPUT', 'TEXTAREA'].indexOf(document.activeElement.tagName) < 0) {
|
||||
element[0].focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function stripeSubscribe($http) {
|
||||
const handler = window.StripeCheckout.configure({
|
||||
key: window.settings.stripePublic,
|
||||
image: '/images/stripe-128.png',
|
||||
token: function(token) {
|
||||
$http.post('/pro/create', {stripeToken: token})
|
||||
.success(function(data) {
|
||||
if (data.status === 'active') {
|
||||
window.user.plan = 'Pro';
|
||||
window.location.reload(true);
|
||||
}
|
||||
})
|
||||
.error(function() {
|
||||
alert('Error upgrading your account');
|
||||
});
|
||||
}
|
||||
});
|
||||
return function(scope, element) {
|
||||
element.on('click', function() {
|
||||
// Open Checkout with further options
|
||||
handler.open({
|
||||
name: 'Hostr',
|
||||
email: window.user.email,
|
||||
description: 'Hostr Pro Monthly',
|
||||
amount: 600,
|
||||
currency: 'USD',
|
||||
panelLabel: 'Subscribe {{amount}}',
|
||||
billingAddress: false
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// angular.module('hostr').directive('clippy', ['files', function factory() {
|
||||
// return function(scope, element, attrs) {
|
||||
// element = element[0];
|
||||
// var client = new ZeroClipboard(element);
|
||||
// client.on('ready', function(readyEvent) {
|
||||
// element.addEventListener('click', function(event) {
|
||||
// event.preventDefault();
|
||||
// });
|
||||
//
|
||||
// client.on( 'aftercopy', function( event ) {
|
||||
// if (element.innerHTML == 'Copy Link') {
|
||||
// element.innerHTML = 'Copied!';
|
||||
// setTimeout(function() {
|
||||
// if (element) {
|
||||
// element.innerHTML = 'Copy Link';
|
||||
// }
|
||||
// }, 1500);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
// }]);
|
154
web/public/src/app/directives/dropzone.js
Normal file
|
@ -0,0 +1,154 @@
|
|||
import Dropzone from 'dropzone';
|
||||
|
||||
function guid() {
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
}
|
||||
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
|
||||
s4() + '-' + s4() + s4() + s4();
|
||||
}
|
||||
|
||||
export default function dropzone(FileService, $cacheFactory) {
|
||||
var dropOverlay = document.getElementById('filedrop-overlay');
|
||||
var dropzoneEl;
|
||||
var errorTimeout;
|
||||
return function($scope) {
|
||||
$scope.$on('$viewContentLoaded', function() {
|
||||
if (!dropzoneEl) {
|
||||
$scope.$root.uploadingFiles = [];
|
||||
var clickable = [].slice.call(document.querySelectorAll('.choose-file'));
|
||||
|
||||
dropzoneEl = new Dropzone(document.body, {
|
||||
url: window.settings.apiURL + '/file',
|
||||
maxFilesize: window.user.maxFileSize / 1024 / 1024,
|
||||
maxThumbnailFilesize: 5,
|
||||
thumbnailWidth: 150,
|
||||
thumbnailHeight: 98,
|
||||
parallelUploads: 1,
|
||||
uploadMultiple: false,
|
||||
clickable: clickable.length ? clickable : false,
|
||||
autoDiscover: false,
|
||||
headers: {'Authorization': ':' + window.user.token},
|
||||
previewsContainer: false
|
||||
});
|
||||
dropzoneEl.on('thumbnail', function(file, thumbnail){
|
||||
file.thumbnail = thumbnail;
|
||||
$scope.$apply();
|
||||
});
|
||||
dropzoneEl.on('addedfile', function(file){
|
||||
var id = guid();
|
||||
file.guid = id;
|
||||
$scope.$root.uploadingFiles.push(file);
|
||||
$scope.$apply();
|
||||
});
|
||||
dropzoneEl.on('sending', function(file, xhr) {
|
||||
xhr.setRequestHeader('hostr-guid', file.guid);
|
||||
});
|
||||
dropzoneEl.on('uploadprogress', function(file, progress) {
|
||||
$scope.$root.progress = {
|
||||
name: file.name,
|
||||
percent: progress,
|
||||
status: 'Uploading'
|
||||
};
|
||||
if (progress === 100) {
|
||||
$scope.$root.progress.status = 'Processing';
|
||||
}
|
||||
$scope.$apply();
|
||||
});
|
||||
dropzoneEl.on('complete', function(file){
|
||||
delete $scope.$root.progress;
|
||||
$scope.$apply();
|
||||
$scope.$root.uploadingFiles.some(function(uploadingFile, index) {
|
||||
if (uploadingFile.guid === file.guid) {
|
||||
$scope.$root.uploadingFiles.splice(index, 1);
|
||||
$scope.$apply();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
dropzoneEl.on('error', function(evt, error){
|
||||
if (error.error) {
|
||||
$scope.$root.uploadError = 'Error uploading file: ' + evt.name + '. ' + error.error.message;
|
||||
}
|
||||
else if (evt.name) {
|
||||
$scope.$root.uploadError = 'Error uploading file: ' + evt.name + '. ' + error;
|
||||
} else {
|
||||
if (error[0] !== '<') {
|
||||
$scope.$root.uploadError = 'Uknown error during upload';
|
||||
}
|
||||
}
|
||||
$scope.$apply();
|
||||
clearTimeout(errorTimeout);
|
||||
errorTimeout = setTimeout(function() {
|
||||
$scope.$root.uploadError = '';
|
||||
$scope.$apply();
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
var addFile = function(newFile) {
|
||||
if (!$scope.$root.files.some(function (file) {
|
||||
return file.id === newFile.id;
|
||||
})) {
|
||||
var cache = $cacheFactory.get('files-cache');
|
||||
cache.removeAll();
|
||||
var file = new FileService(newFile);
|
||||
$scope.$root.files.unshift(file);
|
||||
$scope.$root.user.uploads_today++;
|
||||
$scope.$apply();
|
||||
}
|
||||
};
|
||||
|
||||
dropzoneEl.on('success', function(file, response){
|
||||
addFile(response);
|
||||
});
|
||||
$scope.$on('file-added', function(event, data){
|
||||
addFile(data);
|
||||
});
|
||||
$scope.$on('file-accepted', function(event, data){
|
||||
$scope.$root.uploadingFiles.some(function(file) {
|
||||
if (file.guid === data.guid) {
|
||||
file.id = data.id;
|
||||
file.href = data.href;
|
||||
$scope.$apply();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
$scope.$on('file-deleted', function(evt, data) {
|
||||
$scope.$root.files.forEach(function(file, index) {
|
||||
if(data.id === file.id) {
|
||||
delete $scope.$root.files[index];
|
||||
$scope.$digest();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.body.addEventListener('dragenter', function(){
|
||||
dropOverlay.style.display = 'block';
|
||||
});
|
||||
|
||||
dropOverlay.addEventListener('dragleave', function(event){
|
||||
if (event.target.outerText !== 'Drop files to upload' || event.x === 0) {
|
||||
dropOverlay.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
dropOverlay.addEventListener('drop', function(){
|
||||
dropOverlay.style.display = 'none';
|
||||
});
|
||||
} else {
|
||||
var clicker = [].slice.call(document.querySelectorAll('.choose-file'));
|
||||
if (clicker) {
|
||||
clicker.forEach(function(el) {
|
||||
el.addEventListener('click', function() {
|
||||
return dropzoneEl.hiddenFileInput.click();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
222
web/public/src/app/directives/lazy-src.js
Normal file
|
@ -0,0 +1,222 @@
|
|||
import $ from 'jquery';
|
||||
|
||||
export default function lazySrc($window, $document) {
|
||||
var lazyLoader = (function() {
|
||||
var images = [];
|
||||
var renderTimer = null;
|
||||
var renderDelay = 100;
|
||||
var win = $($window);
|
||||
var doc = $($document);
|
||||
var documentHeight = doc.height();
|
||||
var documentTimer = null;
|
||||
var documentDelay = 2000;
|
||||
var isWatchingWindow = false;
|
||||
|
||||
// ---
|
||||
// PUBLIC METHODS.
|
||||
// ---
|
||||
function addImage(image) {
|
||||
images.push(image);
|
||||
if (!renderTimer) {
|
||||
startRenderTimer();
|
||||
}
|
||||
|
||||
if (!isWatchingWindow) {
|
||||
startWatchingWindow();
|
||||
}
|
||||
}
|
||||
|
||||
let removeImage = function(image) {
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
if (images[i] === image ) {
|
||||
images.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( !images.length ) {
|
||||
clearRenderTimer();
|
||||
stopWatchingWindow();
|
||||
}
|
||||
};
|
||||
|
||||
// ---
|
||||
// PRIVATE METHODS.
|
||||
// ---
|
||||
|
||||
function clearRenderTimer() {
|
||||
clearTimeout( renderTimer );
|
||||
renderTimer = null;
|
||||
}
|
||||
|
||||
function checkImages() {
|
||||
var visible = [];
|
||||
var hidden = [];
|
||||
var windowHeight = win.height();
|
||||
var scrollTop = win.scrollTop();
|
||||
var topFoldOffset = scrollTop;
|
||||
var bottomFoldOffset = ( topFoldOffset + windowHeight );
|
||||
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
var image = images[ i ];
|
||||
if ( image.isVisible( topFoldOffset, bottomFoldOffset ) ) {
|
||||
visible.push( image );
|
||||
} else {
|
||||
hidden.push( image );
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < visible.length; i++) {
|
||||
visible[ i ].render();
|
||||
}
|
||||
|
||||
images = hidden;
|
||||
|
||||
clearRenderTimer();
|
||||
|
||||
if ( !images.length ) {
|
||||
stopWatchingWindow();
|
||||
}
|
||||
}
|
||||
|
||||
function startRenderTimer() {
|
||||
renderTimer = setTimeout( checkImages, renderDelay );
|
||||
}
|
||||
|
||||
function checkDocumentHeight() {
|
||||
if ( renderTimer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentDocumentHeight = doc.height();
|
||||
if ( currentDocumentHeight === documentHeight ) {
|
||||
return;
|
||||
}
|
||||
|
||||
documentHeight = currentDocumentHeight;
|
||||
|
||||
startRenderTimer();
|
||||
}
|
||||
|
||||
function windowChanged() {
|
||||
if (!renderTimer) {
|
||||
startRenderTimer();
|
||||
}
|
||||
}
|
||||
|
||||
function startWatchingWindow() {
|
||||
|
||||
isWatchingWindow = true;
|
||||
|
||||
win.on( 'resize.lazySrc', windowChanged );
|
||||
win.on( 'scroll.lazySrc', windowChanged );
|
||||
|
||||
documentTimer = setInterval( checkDocumentHeight, documentDelay );
|
||||
}
|
||||
|
||||
function stopWatchingWindow() {
|
||||
isWatchingWindow = false;
|
||||
|
||||
win.off( 'resize.lazySrc' );
|
||||
win.off( 'scroll.lazySrc' );
|
||||
|
||||
clearInterval( documentTimer );
|
||||
}
|
||||
|
||||
return ({
|
||||
addImage: addImage,
|
||||
removeImage: removeImage
|
||||
});
|
||||
})();
|
||||
|
||||
function LazyImage( element ) {
|
||||
var source = null;
|
||||
var isRendered = false;
|
||||
var height = null;
|
||||
|
||||
element = $(element);
|
||||
|
||||
// ---
|
||||
// PUBLIC METHODS.
|
||||
// ---
|
||||
function isVisible( topFoldOffset, bottomFoldOffset ) {
|
||||
if (!element.is(':visible')) {
|
||||
//return( false );
|
||||
}
|
||||
|
||||
bottomFoldOffset = bottomFoldOffset + 50;
|
||||
|
||||
if ( height === null ) {
|
||||
height = element.height();
|
||||
}
|
||||
|
||||
var top = element.offset().top;
|
||||
var bottom = ( top + height );
|
||||
|
||||
return (
|
||||
(
|
||||
( top <= bottomFoldOffset ) &&
|
||||
( top >= topFoldOffset )
|
||||
)
|
||||
||
|
||||
(
|
||||
( bottom <= bottomFoldOffset ) &&
|
||||
( bottom >= topFoldOffset )
|
||||
)
|
||||
||
|
||||
(
|
||||
( top <= topFoldOffset ) &&
|
||||
( bottom >= bottomFoldOffset )
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
function renderSource() {
|
||||
element[ 0 ].src = source;
|
||||
element[ 0 ].classList.add('loaded');
|
||||
}
|
||||
|
||||
function render() {
|
||||
isRendered = true;
|
||||
renderSource();
|
||||
}
|
||||
|
||||
function setSource( newSource ) {
|
||||
source = newSource;
|
||||
if ( isRendered ) {
|
||||
renderSource();
|
||||
}
|
||||
}
|
||||
|
||||
return ({
|
||||
isVisible: isVisible,
|
||||
render: render,
|
||||
setSource: setSource
|
||||
});
|
||||
}
|
||||
|
||||
function link( $scope, element, attributes ) {
|
||||
var lazyImage = new LazyImage( element );
|
||||
|
||||
lazyLoader.addImage( lazyImage );
|
||||
|
||||
attributes.$observe(
|
||||
'lazySrc',
|
||||
function( newSource ) {
|
||||
lazyImage.setSource( newSource );
|
||||
}
|
||||
);
|
||||
|
||||
$scope.$on(
|
||||
'$destroy',
|
||||
function() {
|
||||
lazyLoader.removeImage( lazyImage );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return ({
|
||||
link: link,
|
||||
restrict: 'A'
|
||||
});
|
||||
}
|
33
web/public/src/app/filters.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
export function fileSize() {
|
||||
return function(input) {
|
||||
if (input >= 1073741824) {
|
||||
input = Math.round((input / 1073741824) * 10) / 10 + 'GB';
|
||||
} else {
|
||||
if (input >= 1048576) {
|
||||
input = Math.round((input / 1048576) * 10) / 10 + 'MB';
|
||||
} else {
|
||||
if (input >= 1024) {
|
||||
input = Math.round((input / 1024) * 10) / 10 + 'KB';
|
||||
} else {
|
||||
input = Math.round(input) + 'B';
|
||||
}
|
||||
}
|
||||
}
|
||||
return input;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function direct() {
|
||||
return function(file) {
|
||||
if(file.name) {
|
||||
if (file.direct && file.name.split('.').pop().toLowerCase() === 'psd') {
|
||||
return file.direct['970x'].replace('/970/', '/').slice(0, -4);
|
||||
} else if (file.direct && file.direct['970x']) {
|
||||
return file.direct['970x'].replace('/970/', '/');
|
||||
} else {
|
||||
return file.href.replace('hostr.co/', 'hostr.co/file/') + '/' + file.name;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
72
web/public/src/app/services.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
export class FileService {
|
||||
constructor($resource, $cacheFactory) {
|
||||
var cache = $cacheFactory('files-cache');
|
||||
return $resource(window.settings.apiURL + '/file/:id', {id: '@id'}, {
|
||||
query: {method: 'GET', isArray: true, cache: cache,
|
||||
params: {perpage: 0, all: true}
|
||||
},
|
||||
delete: {method: 'DELETE', isArray: true, cache: cache}
|
||||
});
|
||||
}
|
||||
|
||||
static factory($resource, $cacheFactory) {
|
||||
return new FileService($resource, $cacheFactory);
|
||||
}
|
||||
}
|
||||
|
||||
export class UserService {
|
||||
constructor($resource) {
|
||||
return $resource(window.settings.apiURL + '/user');
|
||||
}
|
||||
|
||||
static factory($resource) {
|
||||
return new UserService($resource);
|
||||
}
|
||||
}
|
||||
|
||||
export class EventService {
|
||||
constructor($rootScope, ReconnectingWebSocket) {
|
||||
if (window.user && WebSocket) {
|
||||
let ws = new ReconnectingWebSocket('wss' + window.settings.apiURL.replace('https', '').replace('http', '') + '/user');
|
||||
ws.onmessage = function (msg) {
|
||||
var evt = JSON.parse(msg.data);
|
||||
$rootScope.$broadcast(evt.type, evt.data);
|
||||
};
|
||||
ws.onopen = function() {
|
||||
ws.send(JSON.stringify({authorization: window.user.token}));
|
||||
};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static factory($rootScope, ReconnectingWebSocket) {
|
||||
return new EventService($rootScope, ReconnectingWebSocket);
|
||||
}
|
||||
}
|
||||
|
||||
export class TransactionService {
|
||||
constructor ($resource, $cacheFactory) {
|
||||
var cache = $cacheFactory('transaction-cache');
|
||||
return $resource(window.settings.apiURL + '/user/transaction/:id', {id: '@id'}, {
|
||||
query: {method: 'GET', isArray: true, cache: cache}
|
||||
});
|
||||
}
|
||||
|
||||
static factory($resource, $cacheFactory) {
|
||||
return new TransactionService($resource, $cacheFactory);
|
||||
}
|
||||
}
|
||||
|
||||
export class SettingService {
|
||||
constructor ($http) {
|
||||
var service = {};
|
||||
service.update = function(data) {
|
||||
return $http.post(window.settings.apiURL + '/user/settings', data);
|
||||
};
|
||||
return service;
|
||||
}
|
||||
|
||||
static factory($http) {
|
||||
return new SettingService($http);
|
||||
}
|
||||
}
|
224
web/public/src/lib/smoothscroll.js
Normal file
|
@ -0,0 +1,224 @@
|
|||
/* =============================================================
|
||||
|
||||
Smooth Scroll v4.5
|
||||
Animate scrolling to anchor links, by Chris Ferdinandi.
|
||||
http://gomakethings.com
|
||||
|
||||
Additional contributors:
|
||||
https://github.com/cferdinandi/smooth-scroll#contributors
|
||||
|
||||
Free to use under the MIT License.
|
||||
http://gomakethings.com/mit/
|
||||
|
||||
* ============================================================= */
|
||||
|
||||
window.smoothScroll = (function (window, document, undefined) {
|
||||
|
||||
'use strict';
|
||||
|
||||
// Default settings
|
||||
// Private {object} variable
|
||||
var _defaults = {
|
||||
speed: 500,
|
||||
easing: 'easeInOutCubic',
|
||||
offset: 0,
|
||||
updateURL: false,
|
||||
callbackBefore: function () {},
|
||||
callbackAfter: function () {}
|
||||
};
|
||||
|
||||
// Merge default settings with user options
|
||||
// Private method
|
||||
// Returns an {object}
|
||||
var _mergeObjects = function ( original, updates ) {
|
||||
for (var key in updates) {
|
||||
original[key] = updates[key];
|
||||
}
|
||||
return original;
|
||||
};
|
||||
|
||||
// Calculate the easing pattern
|
||||
// Private method
|
||||
// Returns a decimal number
|
||||
var _easingPattern = function ( type, time ) {
|
||||
if ( type == 'easeInQuad' ) return time * time; // accelerating from zero velocity
|
||||
if ( type == 'easeOutQuad' ) return time * (2 - time); // decelerating to zero velocity
|
||||
if ( type == 'easeInOutQuad' ) return time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration
|
||||
if ( type == 'easeInCubic' ) return time * time * time; // accelerating from zero velocity
|
||||
if ( type == 'easeOutCubic' ) return (--time) * time * time + 1; // decelerating to zero velocity
|
||||
if ( type == 'easeInOutCubic' ) return time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // acceleration until halfway, then deceleration
|
||||
if ( type == 'easeInQuart' ) return time * time * time * time; // accelerating from zero velocity
|
||||
if ( type == 'easeOutQuart' ) return 1 - (--time) * time * time * time; // decelerating to zero velocity
|
||||
if ( type == 'easeInOutQuart' ) return time < 0.5 ? 8 * time * time * time * time : 1 - 8 * (--time) * time * time * time; // acceleration until halfway, then deceleration
|
||||
if ( type == 'easeInQuint' ) return time * time * time * time * time; // accelerating from zero velocity
|
||||
if ( type == 'easeOutQuint' ) return 1 + (--time) * time * time * time * time; // decelerating to zero velocity
|
||||
if ( type == 'easeInOutQuint' ) return time < 0.5 ? 16 * time * time * time * time * time : 1 + 16 * (--time) * time * time * time * time; // acceleration until halfway, then deceleration
|
||||
return time; // no easing, no acceleration
|
||||
};
|
||||
|
||||
// Calculate how far to scroll
|
||||
// Private method
|
||||
// Returns an integer
|
||||
var _getEndLocation = function ( anchor, headerHeight, offset ) {
|
||||
var location = 0;
|
||||
if (anchor.offsetParent) {
|
||||
do {
|
||||
location += anchor.offsetTop;
|
||||
anchor = anchor.offsetParent;
|
||||
} while (anchor);
|
||||
}
|
||||
location = location - headerHeight - offset;
|
||||
if ( location >= 0 ) {
|
||||
return location;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Determine the document's height
|
||||
// Private method
|
||||
// Returns an integer
|
||||
var _getDocumentHeight = function () {
|
||||
return Math.max(
|
||||
document.body.scrollHeight, document.documentElement.scrollHeight,
|
||||
document.body.offsetHeight, document.documentElement.offsetHeight,
|
||||
document.body.clientHeight, document.documentElement.clientHeight
|
||||
);
|
||||
};
|
||||
|
||||
// Convert data-options attribute into an object of key/value pairs
|
||||
// Private method
|
||||
// Returns an {object}
|
||||
var _getDataOptions = function ( options ) {
|
||||
|
||||
if ( options === null || options === undefined ) {
|
||||
return {};
|
||||
} else {
|
||||
var settings = {}; // Create settings object
|
||||
options = options.split(';'); // Split into array of options
|
||||
|
||||
// Create a key/value pair for each setting
|
||||
options.forEach( function(option) {
|
||||
option = option.trim();
|
||||
if ( option !== '' ) {
|
||||
option = option.split(':');
|
||||
settings[option[0]] = option[1].trim();
|
||||
}
|
||||
});
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Update the URL
|
||||
// Private method
|
||||
// Runs functions
|
||||
var _updateURL = function ( anchor, url ) {
|
||||
if ( (url === true || url === 'true') && history.pushState ) {
|
||||
history.pushState( {pos:anchor.id}, '', anchor );
|
||||
}
|
||||
};
|
||||
|
||||
// Start/stop the scrolling animation
|
||||
// Public method
|
||||
// Runs functions
|
||||
var animateScroll = function ( toggle, anchor, options, event ) {
|
||||
|
||||
// Options and overrides
|
||||
options = _mergeObjects( _defaults, options || {} ); // Merge user options with defaults
|
||||
var overrides = _getDataOptions( toggle ? toggle.getAttribute('data-options') : null );
|
||||
var speed = parseInt(overrides.speed || options.speed, 10);
|
||||
var easing = overrides.easing || options.easing;
|
||||
var offset = parseInt(overrides.offset || options.offset, 10);
|
||||
var updateURL = overrides.updateURL || options.updateURL;
|
||||
|
||||
// Selectors and variables
|
||||
var fixedHeader = document.querySelector('[data-scroll-header]'); // Get the fixed header
|
||||
var headerHeight = fixedHeader === null ? 0 : (fixedHeader.offsetHeight + fixedHeader.offsetTop); // Get the height of a fixed header if one exists
|
||||
var startLocation = window.pageYOffset; // Current location on the page
|
||||
var endLocation = _getEndLocation( document.querySelector(anchor), headerHeight, offset ); // Scroll to location
|
||||
var animationInterval; // interval timer
|
||||
var distance = endLocation - startLocation; // distance to travel
|
||||
var documentHeight = _getDocumentHeight();
|
||||
var timeLapsed = 0;
|
||||
var percentage, position;
|
||||
|
||||
// Prevent default click event
|
||||
if ( toggle && toggle.tagName === 'A' && event ) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// Update URL
|
||||
_updateURL(anchor, updateURL);
|
||||
|
||||
// Stop the scroll animation when it reaches its target (or the bottom/top of page)
|
||||
// Private method
|
||||
// Runs functions
|
||||
var _stopAnimateScroll = function (position, endLocation, animationInterval) {
|
||||
var currentLocation = window.pageYOffset;
|
||||
if ( position == endLocation || currentLocation == endLocation || ( (window.innerHeight + currentLocation) >= documentHeight ) ) {
|
||||
clearInterval(animationInterval);
|
||||
options.callbackAfter( toggle, anchor ); // Run callbacks after animation complete
|
||||
}
|
||||
};
|
||||
|
||||
// Loop scrolling animation
|
||||
// Private method
|
||||
// Runs functions
|
||||
var _loopAnimateScroll = function () {
|
||||
timeLapsed += 16;
|
||||
percentage = ( timeLapsed / speed );
|
||||
percentage = ( percentage > 1 ) ? 1 : percentage;
|
||||
position = startLocation + ( distance * _easingPattern(easing, percentage) );
|
||||
window.scrollTo( 0, Math.floor(position) );
|
||||
_stopAnimateScroll(position, endLocation, animationInterval);
|
||||
};
|
||||
|
||||
// Set interval timer
|
||||
// Private method
|
||||
// Runs functions
|
||||
var _startAnimateScroll = function () {
|
||||
options.callbackBefore( toggle, anchor ); // Run callbacks before animating scroll
|
||||
animationInterval = setInterval(_loopAnimateScroll, 16);
|
||||
};
|
||||
|
||||
// Reset position to fix weird iOS bug
|
||||
// https://github.com/cferdinandi/smooth-scroll/issues/45
|
||||
if ( window.pageYOffset === 0 ) {
|
||||
window.scrollTo( 0, 0 );
|
||||
}
|
||||
|
||||
// Start scrolling animation
|
||||
_startAnimateScroll();
|
||||
|
||||
};
|
||||
|
||||
// Initialize Smooth Scroll
|
||||
// Public method
|
||||
// Runs functions
|
||||
var init = function ( options ) {
|
||||
|
||||
// Feature test before initializing
|
||||
if ( 'querySelector' in document && 'addEventListener' in window && Array.prototype.forEach ) {
|
||||
|
||||
// Selectors and variables
|
||||
options = _mergeObjects( _defaults, options || {} ); // Merge user options with defaults
|
||||
var toggles = document.querySelectorAll('[data-scroll]'); // Get smooth scroll toggles
|
||||
|
||||
// When a toggle is clicked, run the click handler
|
||||
Array.prototype.forEach.call(toggles, function (toggle, index) {
|
||||
toggle.addEventListener('click', animateScroll.bind( null, toggle, toggle.getAttribute('href'), options ), false);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Return public methods
|
||||
return {
|
||||
init: init,
|
||||
animateScroll: animateScroll
|
||||
};
|
||||
|
||||
})(window, document);
|
74
web/public/src/partials/account.html
Normal file
|
@ -0,0 +1,74 @@
|
|||
<app-header></app-header>
|
||||
|
||||
|
||||
<section class="container admin">
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li class="active"><a href="/account">Account</a>
|
||||
</li>
|
||||
<li><a href="/billing">Billing</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<div class="holder">
|
||||
<div ng-show="user.plan == 'Free'">
|
||||
<a href="/pro"><p class="lead bg-primary"><strong>Go Pro</strong> and get 500MB per file upload, no daily upload limits, no advertising and more!</p></a>
|
||||
|
||||
<p class="info bg-info">
|
||||
<span>Plan</span>
|
||||
<span class="type">Free — 20 MB max filesize, 15 file daily upload limit.</span>
|
||||
|
||||
<span class="type">
|
||||
<strong>{{user.uploads_today}}/15</strong> files uploaded today</span>
|
||||
</p>
|
||||
</div>
|
||||
<div ng-show="user.plan != 'Free'">
|
||||
<p class="info bg-info">
|
||||
<span>Plan</span>
|
||||
<span class="type">Pro — 500 MB max filesize, no daily upload limit.</span>
|
||||
|
||||
<span class="type">
|
||||
<strong>0/∞</strong> files uploaded today</span>
|
||||
</p>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<form role="form" ng-submit="submit(user)">
|
||||
<div class="alert alert-danger" ng-show="error">{{error}}</div>
|
||||
<div class="alert alert-success" ng-show="updated">Updated your details successfully</div>
|
||||
<div class="form-group">
|
||||
<label for="fname">Email</label>
|
||||
<input type="email" class="form-control" id="fname" value="{{user.email}}" ng-model="user.email">
|
||||
|
||||
<span>
|
||||
<strong>Required.</strong> Password resets will be sent to this address.</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="fname">New Password</label>
|
||||
<input type="password" class="form-control" id="fname" ng-model="user.new_password">
|
||||
|
||||
<span>Leave this field blank unless you want to update your password.</span>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="fname">Current Password</label>
|
||||
<input type="password" class="form-control" id="fname" ng-model="user.current_password">
|
||||
|
||||
<span><strong>Required.</strong> When updating your details we require your current password.</span>
|
||||
</div>
|
||||
|
||||
<button type="submit" href="#" class="btn btn-signup">Save Changes</button>
|
||||
<!-- <button type="button" href="#" class="btn">Cancel</button> -->
|
||||
<!-- <button type="button" class="btn btn-danger">Delete Account</button> -->
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<app-footer></app-footer>
|
23
web/public/src/partials/apps.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
<app-header></app-header>
|
||||
|
||||
<section class="container apps">
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-lg-offset-0 col-md-6 col-md-offset-3">
|
||||
<h1>Hostr for Mac and Windows</h1>
|
||||
<ul class="list-unstyled">
|
||||
<li>Drag and drop without opening your browser.</li>
|
||||
<li>Get links instantly without waiting for uploads to finish</li>
|
||||
<li>Easily capture and share screenshots.</li>
|
||||
<li>Quick access to recent files.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
<div class="downloads">
|
||||
<a href="/apps/mac/Hostr-0.8.0.zip" class="btn btn-primary" target="_self">Download for Mac</a>
|
||||
<a href="/apps/windows/Hostr-0.7.1.exe" class="btn btn-primary" target="_self">Download for Windows</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<app-footer></app-footer>
|
37
web/public/src/partials/billing.html
Normal file
|
@ -0,0 +1,37 @@
|
|||
<app-header></app-header>
|
||||
|
||||
<section class="container admin">
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li><a href="/account">Account</a>
|
||||
</li>
|
||||
<li class="active"><a href="#=/billin">Billing</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<div class="holder">
|
||||
<table class="table" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Product</th>
|
||||
<th class="amount">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr ng-repeat="transaction in transactions">
|
||||
<td>{{transaction.date | date : "d MMM yy"}}</td>
|
||||
<td>{{transaction.description}}</td>
|
||||
<td align="right">${{transaction.amount}}.00</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div ng-hide="transactions.length > 0" style="text-align:center;min-height: 300px;">
|
||||
You have no billing history. You should <a href="/pro">check out Pro</a> right now!
|
||||
<div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<app-footer></app-footer>
|
70
web/public/src/partials/collection.html
Normal file
|
@ -0,0 +1,70 @@
|
|||
<section class="container header clearfix">
|
||||
<img class="logo img-responsive" src="images/logo_dark.png" height="21" width="26" alt="">
|
||||
<div class="stream-title">
|
||||
<img src="images/streams.png" height="24" width="27" alt="">
|
||||
<h3 class="add">Hostr</h3>
|
||||
</div>
|
||||
|
||||
<div class="menu">
|
||||
<img src="images/menu.png" height="6" width="24" alt="">
|
||||
|
||||
<div class="dropdown">
|
||||
<div class="file-info">
|
||||
<a href="#">Direct Link to File</a>
|
||||
<span class="filesize">568kB</span>
|
||||
<span class="num-downloads">5 downloads</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" class="download">download (6)</a>
|
||||
</section>
|
||||
|
||||
<section class="container stream image-preview">
|
||||
|
||||
<div class="content">
|
||||
<a href="#" class="prev"></a>
|
||||
<img class="img-responsive" src="images/large_thumb.png" height="497" width="944" alt="">
|
||||
<a href="#" class="next"></a>
|
||||
</div>
|
||||
<span class="filename">kitty
|
||||
<span class="filetype">.jpg</span>
|
||||
</span>
|
||||
|
||||
<div class="pagination">
|
||||
<a href="#">
|
||||
<img src="images/gallery.png" height="24" width="24" alt="">
|
||||
</a>
|
||||
<a href="#">
|
||||
<img src="images/tiny_thumb.png" height="25" width="24" alt="">
|
||||
</a>
|
||||
<a class="active" href="#">
|
||||
<img src="images/tiny_thumb.png" height="25" width="24" alt="">
|
||||
</a>
|
||||
<a href="#">
|
||||
<img src="images/tiny_thumb.png" height="25" width="24" alt="">
|
||||
</a>
|
||||
<a href="#">
|
||||
<img src="images/tiny_thumb.png" height="25" width="24" alt="">
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="container footer navbar-fixed-bottom">
|
||||
<ul class="nav nav-pills">
|
||||
<li>
|
||||
<a href="#">
|
||||
<strong>Go Pro</strong>
|
||||
</a>
|
||||
</li>
|
||||
<li><a href="#">Terms</a>
|
||||
</li>
|
||||
<li><a href="#">Privacy</a>
|
||||
</li>
|
||||
<li><a href="#">Contact</a>
|
||||
</li>
|
||||
<li><a href="#">API</a>
|
||||
</li>
|
||||
<li><a href="#">Blog</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
53
web/public/src/partials/file.html
Normal file
|
@ -0,0 +1,53 @@
|
|||
<section class="container header clearfix" ng-if="file" ng-cloak="file">
|
||||
<div class="logo pull-left">
|
||||
<a href="/"><img src="/images/logo-dark-r.png" height="22" width="26" alt=""></a>
|
||||
</div>
|
||||
<div class="menu" menu-dropdown>
|
||||
<img src="/images/menu-retina.png" height="6" width="24" alt="" class="dots">
|
||||
|
||||
<div class="dropdown">
|
||||
<div class="file-info">
|
||||
<a href="{{::file | direct}}" ng-show="file.width" target="_blank">Original File <span class="dimensions">({{:: file.width }}x{{:: file.height }})</span></a>
|
||||
<div class="meta">
|
||||
<div class="date">{{::file.added | date : "d MMM yy 'at' h:mm a"}}</div>
|
||||
<span class="filesize">{{:: file.size | fileSize }}</span>
|
||||
<span class="num-downloads">{{ file.downloads.toLocaleString() }} <span ng-show="file.width">views</span><span ng-hide="file.width">downloads</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="filename" ng-show="file.direct['970x'] || file.status == 'uploading'">{{ ::file.name }}<span class="filetype"></span></span>
|
||||
|
||||
</section>
|
||||
|
||||
<section class="container" ng-if="file" ng-show="file.status == 'uploading'" ng-cloak="file">
|
||||
<div class="file-progress">
|
||||
<span>File has not finished uploading…</span>
|
||||
<span>Please wait.</span>
|
||||
|
||||
<span class="percent"><h3>{{file.percent}}%</h3></span>
|
||||
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="{{file.percent}}" aria-valuemin="0" aria-valuemax="100" style="width: {{file.percent}}%;">
|
||||
{{file.percent}}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section ng-if="file.status == 'active'" class="container image-preview" ng-show="file.direct['970x']">
|
||||
<img class="img-responsive" ng-src="{{:: file.direct['970x'] }}">
|
||||
</section>
|
||||
|
||||
|
||||
<section ng-if="file.status == 'active'" class="container file-preview" ng-show="file.href && !file.direct['970x']">
|
||||
<span class="file-icon"></span>
|
||||
<span class="filename">{{:: file.name }}<span class="filetype"></span></span>
|
||||
<form action="{{::file | direct}}" method="get" onsubmit="this.submit()">
|
||||
<div class="file-warning">
|
||||
<p>This file has been scanned for viruses but may still not be safe.</p>
|
||||
<p class="agree"><label for="warning"><input type="checkbox" id="warning" name="warning" ng-model="warning"> I understand and want to download</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-download btn-lg" ng-disabled="!warning">Download</button>
|
||||
</form>
|
||||
</section>
|
81
web/public/src/partials/files.html
Normal file
|
@ -0,0 +1,81 @@
|
|||
<app-header></app-header>
|
||||
<section class="container files">
|
||||
<div class="row" >
|
||||
<div class="col-sm-12">
|
||||
<header class="add">
|
||||
<h3>Files</h3><a class="choose-file"><img src="/images/plus.png"></a>
|
||||
</header>
|
||||
|
||||
<div ng-repeat="uploadingFile in uploadingFiles">
|
||||
<div class="file" ng-show="uploadingFile.href" bs-tooltip data-title="Click file to copy link!" data-container="body">
|
||||
<a ng-href="{{uploadingFile.href}}" title="{{ uploadingFile.name }}" data-clipboard-text="{{uploadingFile.href}}" clippy><div class="uploading">
|
||||
<div class="throbber">
|
||||
</div>
|
||||
</div></a>
|
||||
<span class="title truncate"><a ng-href="{{uploadingFile.href}}" title="{{ uploadingFile.name }}">{{ uploadingFile.name }}</a>
|
||||
</span>
|
||||
|
||||
<div class="menu" menu-dropdown>
|
||||
<img src="/images/gear.png" height="13" width="13" alt="">
|
||||
|
||||
<div class="dropdown">
|
||||
<div class="file-info">
|
||||
<ul class="list-unstyled">
|
||||
<li><a data-clipboard-text="{{uploadingFile.href}}" clippy>Copy Link</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="file" ng-hide="uploadingFile.href">
|
||||
<div class="image">
|
||||
<img src="/images/clock-25.png" width="25">
|
||||
</div>
|
||||
<span class="title truncate"><a ng-href="{{uploadingFile.href}}" title="{{ uploadingFile.name }}">{{ uploadingFile.name }}</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="file" ng-repeat="file in files | filter:{trashed:'false'} | filter:searchText">
|
||||
<div class="image">
|
||||
<a href="{{::file.href}}">
|
||||
<img lazy-src="{{:: file.direct['150x'] ? (file.direct['150x']) : '/images/file-adjusted.png'}}" width="160">
|
||||
</a>
|
||||
</div>
|
||||
<span class="title truncate"><a href="{{::file.href}}" title="{{:: file.name }}">{{:: file.name }}</a>
|
||||
</span>
|
||||
|
||||
<div class="menu" menu-dropdown>
|
||||
<img src="/images/gear.png" height="13" width="13" alt="">
|
||||
|
||||
<div class="dropdown">
|
||||
<div class="file-info">
|
||||
<ul class="list-unstyled">
|
||||
<li><a data-clipboard-text="{{::file.href}}" clippy>Copy Link</a></li>
|
||||
</ul>
|
||||
<ul class="list-unstyled">
|
||||
<li>{{:: file.added | date : "d MMM yy 'at' h:mm a"}}</li>
|
||||
<li>{{:: file.size | fileSize }}</li>
|
||||
<li class="sep">{{ file.downloads }} <span ng-show="::file.direct['970x']">views</span><span ng-hide="::file.direct['970x']">downloads</span></li>
|
||||
</ul>
|
||||
<ul class="list-unstyled">
|
||||
<li><a ng-click="remove(file)">Delete</a></div></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ng-cloak jumbotron info" ng-show="uploadingFiles.length === 0 && files.length === 0 && files.$resolved">
|
||||
<div class="drop-zone">
|
||||
<div>
|
||||
<p>Right now you have no files!</p>
|
||||
<p class="plus">Drop a file onto the page or click <a class="choose-file"><img src="/images/plus.png"></a> to begin.</p>
|
||||
<p>For even easier uploading and sharing <a href="/apps">download our apps</a> for Mac and Windows.</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<app-footer></app-footer>
|
20
web/public/src/partials/footer.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<section class="container footer">
|
||||
<img src="/images/bullet-r.png" height="8" width="8" alt="">
|
||||
<ul class="nav nav-pills">
|
||||
<li><a href="/apps">Apps</a>
|
||||
</li>
|
||||
<li><a href="mailto:support@hostr.co">Support</a>
|
||||
</li>
|
||||
<li><a href="/terms">Terms</a>
|
||||
</li>
|
||||
<li><a href="/privacy">Privacy</a>
|
||||
</li>
|
||||
<li><a href="https://twitter.com/gethostr" target="_blank">Twitter</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/pro">
|
||||
<strong>Go Pro</strong>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
35
web/public/src/partials/header.html
Normal file
|
@ -0,0 +1,35 @@
|
|||
<section class="container header clearfix" ng-keyup="keypress">
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-sm-push-4 col-xs-12">
|
||||
<div class="logo">
|
||||
<a href="/"><img src="/images/logo-dark-r.png" height="22" width="26" alt=""></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4 col-sm-pull-4 col-xs-12">
|
||||
<span class="user menu" menu-dropdown ng-click="dropdownClick()">
|
||||
<span class="chevron"><img ng-src="//www.gravatar.com/avatar/{{userMD5}}?s=28&d=blank" height="28" width="28" alt="" class="avatar"></span>
|
||||
{{email}}
|
||||
<div class="dropdown left">
|
||||
<div class="file-info">
|
||||
<a href="/account">Account</a>
|
||||
<a href="/logout" target="_self">Logout</a>
|
||||
<hr>
|
||||
<a href="/apps" target="_self">Get the app</a>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<span ng-if="user.plan =='Free'"
|
||||
class="remaining"
|
||||
ng-class="{limit: user.uploads_today >= user.daily_upload_allowance}"
|
||||
bs-tooltip
|
||||
data-placement="right"
|
||||
data-title="You've uploaded {{user.uploads_today}} files today. You can upload {{user.daily_upload_allowance - user.uploads_today}} more for free.">
|
||||
{{user.uploads_today}}</span>
|
||||
</div>
|
||||
<div class="col-sm-4 col-xs-12">
|
||||
<form id="search" name="search" file-search>
|
||||
<input name="query" type="search" ng-model="searchText" value="" search-shortcut placeholder="Search…">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
52
web/public/src/partials/privacy.html
Normal file
|
@ -0,0 +1,52 @@
|
|||
<app-header></app-header>
|
||||
|
||||
<section class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-offset-2 col-md-8 col-sm-12">
|
||||
<h2>Privacy Policy</h2>
|
||||
<h3>Introduction</h3>
|
||||
<p>Hostr (we" or "us") values its visitors' privacy. This privacy policy is effective 16th February 2013; it summarizes what information we might collect from a registered user or other visitor ("you"), and what we will and will not do with it.
|
||||
Please note that this privacy policy does not govern the collection and use of information by companies that Hostr does not control, nor by individuals not employed or managed by Hostr. If you visit a Web site that we mention or link to, be sure to review its privacy policy before providing the site with information.</p>
|
||||
<h3>What we do with your personally identifiable information</h3>
|
||||
<p>It is always up to you whether to disclose personally identifiable information to us, although if you elect not to do so, we reserve the right not to register you as a user or provide you with any products or services. "Personally identifiable information" means information that can be used to identify you as an individual, such as, for example:</p>
|
||||
<ul>
|
||||
<li>your name, company, email address, phone number, billing address, and shipping address your Hostr user ID and password</li>
|
||||
<li>credit card information</li>
|
||||
<li>any account-preference information you provide us</li>
|
||||
<li>your computer's domain name and IP address, indicating</li>
|
||||
<li>where your computer is located on the Internet</li>
|
||||
<li>session data for your login session, so that our computer can ‘talk' to yours while you are logged in</li>
|
||||
</ul>
|
||||
<p>If you do provide personally identifiable information to us, either directly or through a reseller or other business partner, we will:
|
||||
not sell or rent it to a third party without your permission — although unless you opt out (see below), we may use your contact information to provide you with information we believe you need to know or may find useful, such as (for example) news about our services and products and modifications to the Terms of Service; take commercially reasonable precautions to protect the information from loss, misuse and unauthorized access, disclosure, alteration and destruction;
|
||||
not use or disclose the information except:</p>
|
||||
<ul>
|
||||
<li>as necessary to provide services or products you have ordered, such as (for example) by providing it to a carrier to deliver products you have ordered;</li>
|
||||
<li>in other ways described in this privacy policy or to which you have otherwise consented; in the aggregate with other information in such a way so that your identity cannot reasonably be determined (for example, statistical compilations);</li>
|
||||
<li>as required by law, for example, in response to a subpoena or search warrant;</li>
|
||||
<li>to outside auditors who have agreed to keep the information confidential;</li>
|
||||
<li>as necessary to enforce the Terms of Service;</li>
|
||||
<li>as necessary to protect the rights, safety, or property of Hostr, its users, or others; this may include (for example) exchanging information with other organizations for fraud protection and/or risk reduction.</li>
|
||||
</ul>
|
||||
<h3>Other information we collect</h3>
|
||||
<p>We may collect other information that cannot be readily used to identify you, such as (for example) the domain name and IP address of your computer. We may use this information, individually or in the aggregate, for technical administration of our Web site(s); research and development; customer- and account administration; and to help us focus our marketing efforts more precisely.</p>
|
||||
<h3>Cookies</h3>
|
||||
<p>Hostr uses "cookies" to store personal data on your computer. We may also link information stored on your computer in cookies with personal data about specific individuals stored on our servers. If you set up your Web browser (for example, Internet Explorer or Firefox) so that cookies are not allowed, you might not be able to use some or all of the features of our Web site(s).</p>
|
||||
<h3>External data storage sites</h3>
|
||||
<p>We may store your data on servers provided by third party hosting vendors with whom we have contracted.</p>
|
||||
<h3>Your privacy responsibilities</h3>
|
||||
<p>To help protect your privacy, be sure:</p>
|
||||
<ul>
|
||||
<li>not to share your user ID or password with anyone else;</li>
|
||||
<li>to take customary precautions to guard against "malware" (viruses, Trojan horses, bots, etc.), for example by installing and updating suitable anti-virus software.</li>
|
||||
</ul>
|
||||
<h3>Changes to this privacy policy</h3>
|
||||
<p>We reserve the right to change this privacy policy as we deem necessary or appropriate because of legal compliance requirements or changes in our business practices. If you have provided us with an email address, we will endeavor to notify you, by email to that address, of any material change to how we will use personally identifiable information.</p>
|
||||
<h3>Questions or comments?</h3>
|
||||
<p>If you have questions or comments about Hostr's privacy policy, send an email to support@hostr.com.</p>
|
||||
<p>Thank you for choosing Hostr!</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<app-footer></app-footer>
|
67
web/public/src/partials/pro.html
Normal file
|
@ -0,0 +1,67 @@
|
|||
<app-header></app-header>
|
||||
<section class="container pro">
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-md-offset-3">
|
||||
<h1>Hostr Free</h1>
|
||||
|
||||
<h2>Free!</h2>
|
||||
|
||||
<ul class="list-unstyled">
|
||||
<li>Simple, Secure file sharing</li>
|
||||
<li>15 Uploads per day</li>
|
||||
<li>Share 25MB files</li>
|
||||
<li>Ads on popular files</li>
|
||||
</ul>
|
||||
|
||||
<button ng-hide="user.plan == 'Free'" class="btn btn-primary" ng-click="cancel()">Downgrade</button>
|
||||
<button ng-show="user.plan == 'Free'" class="btn btn-primary" disabled>You</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h1>Hostr Pro</h1>
|
||||
|
||||
<h2>$6/month</h2>
|
||||
|
||||
<ul class="list-unstyled">
|
||||
<li>All the features of Free</li>
|
||||
<li>Unlimited file sharing</li>
|
||||
<li>Share 500MB files</li>
|
||||
<li>No ads, for you or your files</li>
|
||||
</ul>
|
||||
|
||||
<button ng-hide="user.plan == 'Free'" class="btn btn-primary" disabled>You</button>
|
||||
<button ng-show="user.plan == 'Free'" id="upgrade" class="btn btn-primary" stripe-subscribe>Go Pro</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row questions">
|
||||
<div class="col-md-12">
|
||||
<h2>Questions?</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-md-offset-3">
|
||||
<h3>Is there a minimum contract?</h3>
|
||||
<p>You can cancel whenever you like. We will downgrade your account at the end of your billing cycle.</p>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h3>Can I pay yearly?</h3>
|
||||
<p>Not yet, but we do plan to offer it in the near future.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-md-offset-3">
|
||||
<h3>How secure is my credit card?</h3>
|
||||
<p>Every step of the transaction is protected by 256-bit SSL. We use Stripe for our credit card transactions, meaning your card details are never transmitted to us.</p>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h3>What payment methods do you accept?</h3>
|
||||
<p>We accept Visa, MasterCard, and American Express.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-md-offset-3">
|
||||
<h3>Got more questions?</h3>
|
||||
<p>Hit us up on <a href="mailto:support@hostr.co?subject=Hostr%20Pro%20Question">support@hostr.co</a> or click the support link below.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<app-footer></app-footer>
|
36
web/public/src/partials/terms.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
<app-header></app-header>
|
||||
|
||||
<section class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-offset-2 col-md-8 col-sm-12">
|
||||
<h2>Hostr Terms of Service</h2>
|
||||
<h3>Prohibited Content</h3>
|
||||
<h4>Website Hosting</h4>
|
||||
<p>Hotlinking graphics is only permitted in the context of sharing, not for hosting your website.</p>
|
||||
<h4>Copyright Content</h4>
|
||||
<p>The use of warez, media files that you are not the rightful owner of, cracks, and any other forms of copyrighted software that you are not legally allowed to use/distribute are all strictly prohibited.</p>
|
||||
<h4>Pornography</h4>
|
||||
<p>Pornography of any kind is strictly prohibited. Pornography is defined as content which displays or links to displays of genitalia.</p>
|
||||
<h4>Viruses and General Malware</h4>
|
||||
<p>Viruses, Trojans, and any other harmful files or malware are all strictly prohibited.</p>
|
||||
<h4>Passworded Archives</h4>
|
||||
<p>The use of passworded archives is strictly prohibited, Hostr needs to be able to ascertain any archive does not contain any of the above material.</p>
|
||||
<h4>Split Archives</h4>
|
||||
<p>The use of split archives to circumvent the file size limit is strictly prohibited.</p>
|
||||
<hr />
|
||||
|
||||
<p>Breaching any of the above terms may result in your account being terminated without warning.</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<h3>Hotlinking</h3>
|
||||
<p>Hotlinking images is permitted. However, abuse of this service may result in temporary limitations on your ability to hotlink. This will not affect your ability to continue using your account. Limitations may imposed permanently for continued abuse.</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<p>Hostr reserves the right to remove content for any reason it deems appropriate.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<app-footer></app-footer>
|