diff options
author | Johannes Zellner <johannes@cloudron.io> | 2020-03-03 17:57:28 +0100 |
---|---|---|
committer | Johannes Zellner <johannes@cloudron.io> | 2020-03-03 17:57:28 +0100 |
commit | 313dfe99cf8f763b36f333c5072e2430a6b7941f (patch) | |
tree | 520922a7f374b816901345fb087d66868eeff954 /frontend | |
parent | 33ee47f3ca0322ccb19153cafa1c1f7b886dc61a (diff) | |
download | Surfer-313dfe99cf8f763b36f333c5072e2430a6b7941f.tar.gz Surfer-313dfe99cf8f763b36f333c5072e2430a6b7941f.tar.zst Surfer-313dfe99cf8f763b36f333c5072e2430a6b7941f.zip |
Use custom public folder listing
Diffstat (limited to 'frontend')
-rw-r--r-- | frontend/404.html | 2 | ||||
-rw-r--r-- | frontend/js/public.js | 144 | ||||
-rw-r--r-- | frontend/public.html | 89 |
3 files changed, 234 insertions, 1 deletions
diff --git a/frontend/404.html b/frontend/404.html index c660424..15da5c2 100644 --- a/frontend/404.html +++ b/frontend/404.html | |||
@@ -9,7 +9,7 @@ | |||
9 | <body> | 9 | <body> |
10 | 10 | ||
11 | <div class="container-center"> | 11 | <div class="container-center"> |
12 | <p>File not found</p> | 12 | <p>File or directory not found</p> |
13 | </div> | 13 | </div> |
14 | 14 | ||
15 | </body> | 15 | </body> |
diff --git a/frontend/js/public.js b/frontend/js/public.js new file mode 100644 index 0000000..c295a05 --- /dev/null +++ b/frontend/js/public.js | |||
@@ -0,0 +1,144 @@ | |||
1 | (function () { | ||
2 | 'use strict'; | ||
3 | |||
4 | /* global superagent */ | ||
5 | /* global Vue */ | ||
6 | /* global $ */ | ||
7 | /* global filesize */ | ||
8 | |||
9 | function sanitize(filePath) { | ||
10 | filePath = '/' + filePath; | ||
11 | return filePath.replace(/\/+/g, '/'); | ||
12 | } | ||
13 | |||
14 | function encode(filePath) { | ||
15 | return filePath.split('/').map(encodeURIComponent).join('/'); | ||
16 | } | ||
17 | |||
18 | function decode(filePath) { | ||
19 | return filePath.split('/').map(decodeURIComponent).join('/'); | ||
20 | } | ||
21 | |||
22 | var mimeTypes = { | ||
23 | images: [ '.png', '.jpg', '.jpeg', '.tiff', '.gif' ], | ||
24 | text: [ '.txt', '.md' ], | ||
25 | pdf: [ '.pdf' ], | ||
26 | html: [ '.html', '.htm', '.php' ], | ||
27 | video: [ '.mp4', '.mpg', '.mpeg', '.ogg', '.mkv', '.avi', '.mov' ] | ||
28 | }; | ||
29 | |||
30 | function getPreviewUrl(entry, basePath) { | ||
31 | var path = '/_admin/img/'; | ||
32 | |||
33 | if (entry.isDirectory) return path + 'directory.png'; | ||
34 | if (mimeTypes.images.some(function (e) { return entry.filePath.endsWith(e); })) return sanitize(basePath + '/' + entry.filePath); | ||
35 | if (mimeTypes.text.some(function (e) { return entry.filePath.endsWith(e); })) return path +'text.png'; | ||
36 | if (mimeTypes.pdf.some(function (e) { return entry.filePath.endsWith(e); })) return path + 'pdf.png'; | ||
37 | if (mimeTypes.html.some(function (e) { return entry.filePath.endsWith(e); })) return path + 'html.png'; | ||
38 | if (mimeTypes.video.some(function (e) { return entry.filePath.endsWith(e); })) return path + 'video.png'; | ||
39 | |||
40 | return path + 'unknown.png'; | ||
41 | } | ||
42 | |||
43 | // simple extension detection, does not work with double extension like .tar.gz | ||
44 | function getExtension(entry) { | ||
45 | if (entry.isFile) return entry.filePath.slice(entry.filePath.lastIndexOf('.') + 1); | ||
46 | return ''; | ||
47 | } | ||
48 | |||
49 | function loadDirectory() { | ||
50 | app.busy = true; | ||
51 | |||
52 | var filePath = sanitize(window.location.pathname); | ||
53 | |||
54 | app.path = filePath; | ||
55 | |||
56 | superagent.get('/api/files/' + encode(filePath)).query({ access_token: localStorage.accessToken }).end(function (error, result) { | ||
57 | app.busy = false; | ||
58 | |||
59 | if (result && result.statusCode === 401) return logout(); | ||
60 | if (error) return console.error(error); | ||
61 | |||
62 | result.body.entries.sort(function (a, b) { return a.isDirectory && b.isFile ? -1 : 1; }); | ||
63 | app.entries = result.body.entries.map(function (entry) { | ||
64 | entry.previewUrl = getPreviewUrl(entry, filePath); | ||
65 | entry.extension = getExtension(entry); | ||
66 | entry.rename = false; | ||
67 | entry.filePathNew = entry.filePath; | ||
68 | return entry; | ||
69 | }); | ||
70 | app.path = filePath; | ||
71 | app.pathParts = decode(filePath).split('/').filter(function (e) { return !!e; }).map(function (e, i, a) { | ||
72 | return { | ||
73 | name: e, | ||
74 | link: '#' + sanitize('/' + a.slice(0, i).join('/') + '/' + e) | ||
75 | }; | ||
76 | }); | ||
77 | }); | ||
78 | } | ||
79 | |||
80 | function open(row, column, event) { | ||
81 | var fullPath = encode(sanitize(app.path + '/' + row.filePath)); | ||
82 | |||
83 | if (row.isDirectory) return window.location.href = fullPath; | ||
84 | |||
85 | app.activeEntry = row; | ||
86 | app.activeEntry.fullPath = fullPath; | ||
87 | app.previewDrawerVisible = true | ||
88 | |||
89 | // need to wait for DOM element to exist | ||
90 | setTimeout(function () { | ||
91 | $('iframe').on('load', function (e) { | ||
92 | if (!e.target.contentWindow.document.body) return; | ||
93 | |||
94 | e.target.contentWindow.document.body.style.display = 'flex' | ||
95 | e.target.contentWindow.document.body.style.justifyContent = 'center' | ||
96 | }); | ||
97 | }, 0); | ||
98 | } | ||
99 | |||
100 | var app = new Vue({ | ||
101 | el: '#app', | ||
102 | data: { | ||
103 | ready: false, | ||
104 | busy: false, | ||
105 | path: '', | ||
106 | previewDrawerVisible: false, | ||
107 | activeEntry: {}, | ||
108 | entries: [] | ||
109 | }, | ||
110 | methods: { | ||
111 | onDownload: function (entry) { | ||
112 | if (entry.isDirectory) return; | ||
113 | window.location.href = encode('/api/files/' + sanitize(this.path + '/' + entry.filePath)) + '?access_token=' + localStorage.accessToken; | ||
114 | }, | ||
115 | prettyDate: function (row, column, cellValue, index) { | ||
116 | var date = new Date(cellValue), | ||
117 | diff = (((new Date()).getTime() - date.getTime()) / 1000), | ||
118 | day_diff = Math.floor(diff / 86400); | ||
119 | |||
120 | if (isNaN(day_diff) || day_diff < 0) | ||
121 | return; | ||
122 | |||
123 | return day_diff === 0 && ( | ||
124 | diff < 60 && 'just now' || | ||
125 | diff < 120 && '1 minute ago' || | ||
126 | diff < 3600 && Math.floor( diff / 60 ) + ' minutes ago' || | ||
127 | diff < 7200 && '1 hour ago' || | ||
128 | diff < 86400 && Math.floor( diff / 3600 ) + ' hours ago') || | ||
129 | day_diff === 1 && 'Yesterday' || | ||
130 | day_diff < 7 && day_diff + ' days ago' || | ||
131 | day_diff < 31 && Math.ceil( day_diff / 7 ) + ' weeks ago' || | ||
132 | day_diff < 365 && Math.round( day_diff / 30 ) + ' months ago' || | ||
133 | Math.round( day_diff / 365 ) + ' years ago'; | ||
134 | }, | ||
135 | prettyFileSize: function (row, column, cellValue, index) { | ||
136 | return filesize(cellValue); | ||
137 | }, | ||
138 | loadDirectory: loadDirectory, | ||
139 | open: open, | ||
140 | } | ||
141 | }); | ||
142 | |||
143 | loadDirectory(); | ||
144 | })(); \ No newline at end of file | ||
diff --git a/frontend/public.html b/frontend/public.html new file mode 100644 index 0000000..2ddf543 --- /dev/null +++ b/frontend/public.html | |||
@@ -0,0 +1,89 @@ | |||
1 | <html> | ||
2 | <head> | ||
3 | <title> Surfer </title> | ||
4 | |||
5 | <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> | ||
6 | |||
7 | <link rel="icon" type="image/png" href="/_admin/img/logo.png"> | ||
8 | |||
9 | <link rel="stylesheet" href="/_admin/css/theme-chalk_2.11.1.css"> | ||
10 | <link rel="stylesheet" href="/_admin/css/style.css"> | ||
11 | |||
12 | <script src="/_admin/js/jquery-1.12.1.min.js"></script> | ||
13 | <script src="/_admin/js/vue.min.js"></script> | ||
14 | <script src="/_admin/js/element-ui_2.11.1.min.js"></script> | ||
15 | <script src="/_admin/js/element-ui_en_2.11.1.min.js"></script> | ||
16 | <script src="/_admin/js/filesize.min.js"></script> | ||
17 | <script src="/_admin/js/superagent.js"></script> | ||
18 | |||
19 | </head> | ||
20 | <body> | ||
21 | |||
22 | <div id="app"> | ||
23 | |||
24 | <el-container> | ||
25 | <el-header> | ||
26 | <el-row type="flex" justify="space-between"> | ||
27 | <div style="flex-grow: 2; padding: 0 7px;"> | ||
28 | <p style="font-size: 24px; margin: 4px 0;">{{ path }}</p> | ||
29 | </div> | ||
30 | <div> | ||
31 | <a href="/_admin"> | ||
32 | <el-button type="primary" icon="el-icon-user" size="small">Login</el-button> | ||
33 | </a> | ||
34 | </div> | ||
35 | </el-row> | ||
36 | </el-header> | ||
37 | <el-main> | ||
38 | |||
39 | <div v-show="busy"> | ||
40 | <center><h1><i class="el-icon-loading"></i></h1></center> | ||
41 | </div> | ||
42 | |||
43 | <div v-show="!busy && entries.length" v-cloak> | ||
44 | <center> | ||
45 | <el-table :data="entries" style="max-width: 1280px; width: 100%" height="100%" empty-text="Folder is emtpy" :default-sort="{ prop: 'filePath', order: 'descending' }" @row-click="open"> | ||
46 | <el-table-column prop="previewUrl" label="Type" width="80px" sortable> | ||
47 | <template slot-scope="scope"> | ||
48 | <el-image v-bind:src="scope.row.previewUrl" class="list-icon" style="width: 32px; height: 32px" fit="cover"></el-image> | ||
49 | </template> | ||
50 | </el-table-column> | ||
51 | <el-table-column prop="filePath" label="Name" sortable></el-table-column> | ||
52 | <el-table-column prop="size" label="Size" width="150px" sortable :formatter="prettyFileSize"></el-table-column> | ||
53 | <el-table-column prop="mtime" label="Modified" width="150px" sortable :formatter="prettyDate"></el-table-column> | ||
54 | <el-table-column label="Actions" align="right" width="200px" class-name="list-actions"> | ||
55 | <template slot-scope="scope"> | ||
56 | <el-button size="small" icon="el-icon-download" type="text" plain circle v-show="scope.row.isFile" @click.stop="onDownload(scope.row)"></el-button> | ||
57 | </template> | ||
58 | </el-table-column> | ||
59 | </el-table> | ||
60 | </center> | ||
61 | </div> | ||
62 | |||
63 | <div v-show="!busy && !entries.length"> | ||
64 | <center> | ||
65 | Folder is empty | ||
66 | </center> | ||
67 | </div> | ||
68 | |||
69 | <el-drawer :title="activeEntry.filePath":with-header="false" :visible.sync="previewDrawerVisible" direction="rtl" size="50%"> | ||
70 | <div style="display: flex; flex-direction: column; height: 100%;"> | ||
71 | <iframe :src="activeEntry.fullPath" style="width: 100%; height: 100%; border: none; margin: 10px;"></iframe> | ||
72 | <center> | ||
73 | <el-button size="small" icon="el-icon-download" style="margin: 10px;" @click.stop="onDownload(activeEntry)">Download</el-button> | ||
74 | <a :href="activeEntry.fullPath" target="_blank"> | ||
75 | <el-button size="small" icon="el-icon-link" style="margin: 10px;">Open</el-button> | ||
76 | </a> | ||
77 | </center> | ||
78 | </div> | ||
79 | </el-drawer> | ||
80 | |||
81 | </el-main> | ||
82 | </el-container> | ||
83 | |||
84 | </div> | ||
85 | |||
86 | <script src="/_admin/js/public.js"></script> | ||
87 | |||
88 | </body> | ||
89 | </html> | ||