From 36cdb9aa8b79ca3d1c06a22842a4cb0dbde6ca5e Mon Sep 17 00:00:00 2001 From: Ambrose Chua Date: Fri, 7 May 2021 20:57:15 +0800 Subject: [PATCH] Upgrade to Bootstrap 5, and improve UI --- assets/cmd.css | 6 +- assets/fonts.css | 6 -- assets/list.css | 21 ++++- assets/list.js | 5 -- assets/login.css | 6 +- assets/login.js | 88 ++++++++++--------- assets/multi.js | 69 +++++++++------ assets/navbar.css | 6 ++ assets/shell.js | 13 ++- assets/tooltip.js | 5 ++ assets/upload.js | 36 ++++---- index.js | 29 +++++-- package-lock.json | 50 ++++++----- package.json | 2 +- views/cmd.handlebars | 52 ++++++------ views/layouts/main.handlebars | 66 ++++++++------- views/list.handlebars | 64 +++++++------- views/login.handlebars | 52 ++++++------ views/partials/dialogue-cmd.handlebars | 42 +++++---- views/partials/dialogue-delete.handlebars | 44 +++++----- views/partials/dialogue-download.handlebars | 45 +++++----- views/partials/dialogue-mkdir.handlebars | 42 +++++---- views/partials/dialogue-upload.handlebars | 57 ++++++------- views/partials/navbar.handlebars | 50 +++++------ views/partials/toolbar.handlebars | 94 +++++++++++---------- views/shell.handlebars | 22 ++--- 26 files changed, 509 insertions(+), 463 deletions(-) delete mode 100644 assets/fonts.css delete mode 100644 assets/list.js create mode 100644 assets/navbar.css create mode 100644 assets/tooltip.js diff --git a/assets/cmd.css b/assets/cmd.css index 996d56d..5f26890 100644 --- a/assets/cmd.css +++ b/assets/cmd.css @@ -1,7 +1,7 @@ .cmd { - word-wrap: break-word; + word-wrap: break-word; } pre { - background-color: #eee; - min-height: 1.5em; + background-color: #eee; + min-height: 1.5em; } diff --git a/assets/fonts.css b/assets/fonts.css deleted file mode 100644 index bb0682e..0000000 --- a/assets/fonts.css +++ /dev/null @@ -1,6 +0,0 @@ -body { - font-family: -apple-system, BlinkMacSystemFont, - "Segoe UI", "Roboto", "Oxygen", - "Ubuntu", "Cantarell", "Fira Sans", - "Droid Sans", "Helvetica Neue", sans-serif; -} diff --git a/assets/list.css b/assets/list.css index 3a98bba..d508986 100644 --- a/assets/list.css +++ b/assets/list.css @@ -1,4 +1,23 @@ .name { overflow: hidden; - word-wrap: break-word; + word-wrap: break-word; +} +.badge-alignment { + margin-top: 0.25em; +} +.stretched-invisible-label { + display: block; +} +.stretched-invisible-label > * { + position: relative; + z-index: 1; +} +.stretched-invisible-label::after { + content: ""; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 0; } diff --git a/assets/list.js b/assets/list.js deleted file mode 100644 index 9d88772..0000000 --- a/assets/list.js +++ /dev/null @@ -1,5 +0,0 @@ -/* jshint esversion: 6 */ - -$(document).ready(() => { - $("[title]").tooltip(); -}); diff --git a/assets/login.css b/assets/login.css index 1dfa508..8d2cab9 100644 --- a/assets/login.css +++ b/assets/login.css @@ -8,12 +8,12 @@ } .input-group-digits input[type=number] { - -moz-appearance: textfield; + -moz-appearance: textfield; } .input-group-digits input[type=number]::-webkit-inner-spin-button, .input-group-digits input[type=number]::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; + -webkit-appearance: none; + margin: 0; } .login { diff --git a/assets/login.js b/assets/login.js index 9e0ccc4..c7bd866 100644 --- a/assets/login.js +++ b/assets/login.js @@ -1,53 +1,51 @@ /* jshint esversion: 6 */ -$(document).ready(() => { - let $inputs = $(".input-group-digits"); - $inputs.each((i, input) => { - let cleanup = () => { - $(input).find("input").each((i, ele) => { - let cleaned = $(ele).val().replace(/[^0-9]/, ""); - $(ele).val(cleaned); - }); - }; - let update = (e) => { - $digits = $(input).find("input"); +let $inputs = $(".input-group-digits"); +$inputs.each((i, input) => { + let cleanup = () => { + $(input).find("input").each((i, ele) => { + let cleaned = $(ele).val().replace(/[^0-9]/, ""); + $(ele).val(cleaned); + }); + }; + let update = (e) => { + $digits = $(input).find("input"); - // Cleanup - cleanup(); + // Cleanup + cleanup(); - // Shift characters - let excess = ""; - $digits.each((i, ele) => { - let now = excess + $(ele).val(); - $(ele).val(now.charAt(0)); - excess = now.substr(1); - }); + // Shift characters + let excess = ""; + $digits.each((i, ele) => { + let now = excess + $(ele).val(); + $(ele).val(now.charAt(0)); + excess = now.substr(1); + }); - // Move cursor to empty - $digits.each((i, ele) => { - if (!$(ele).val()) { - $(ele).focus(); - if (e.which == 8) { - $(ele).prev().focus().val(""); - } - return false; - } - }); + // Move cursor to empty + $digits.each((i, ele) => { + if (!$(ele).val()) { + $(ele).focus(); + if (e.which == 8) { + $(ele).prev().focus().val(""); + } + return false; + } + }); - // Submit if last digit is filled - if ($($digits[$digits.length - 1]).val()) { - let token = $.map( - $digits, - d => $(d).val() - ).join(""); - let $value = $(input).parent().find("#login-token-value"); - $value.val(token); - $value.closest("form").submit(); - } - }; + // Submit if last digit is filled + if ($($digits[$digits.length - 1]).val()) { + let token = $.map( + $digits, + d => $(d).val() + ).join(""); + let $value = $(input).parent().find("#login-token-value"); + $value.val(token); + $value.closest("form").submit(); + } + }; - $digits = $(input).find("input"); - $digits.on("keyup", update); - $digits.on("change", update); - }); + $digits = $(input).find("input"); + $digits.on("keyup", update); + $digits.on("change", update); }); diff --git a/assets/multi.js b/assets/multi.js index 097f007..68ed51c 100644 --- a/assets/multi.js +++ b/assets/multi.js @@ -1,30 +1,51 @@ /* jshint esversion: 6 */ -$(document).ready(() => { - let $select = $(".multi-select"); +function htmlEscape(text) { + const p = document.createElement('p'); + p.innerText = text; + return p.innerHTML; +} - let setSelected = (files) => { - $(".multi-files-value").val(JSON.stringify(files.map(f => f.name))); - $(".multi-files").html( - files.map(f => { - return `
  • ${f.name} ${f.size}
  • `; - }).join("") - ); - }; +let $select = $(".multi-select"); - let updateSelected = () => { - let $selected = $(".multi-select:checked"); - let files = []; - $selected.each((i, ele) => { - files.push({ - name: $(ele).data("select"), - size: $(ele).data("select-size") - }); - }); +let setSelected = (files) => { + $(".multi-files-value").val(JSON.stringify(files.map(f => f.name))); + if (files.length == 0) { + $(".multi-files").html(`
  • No files selected
  • `); + return + } + $(".multi-files").html( + files.map(f => { + return ` +
  • + ${htmlEscape(f.name)} + ${f.type == "directory" ? `` : `${filesize(f.size)}`} +
  • + `; + }).join("") + ); + const hasDirectory = files.reduce((a, f) => a || f.type == "directory", false); + const totalSize = files.map(f => f.size).reduce((a, b) => a + b); + if (hasDirectory) { + $(".multi-files-total").text("Unknown"); + } else { + $(".multi-files-total").text(filesize(totalSize)); + } +}; - setSelected(files); - } +const updateSelected = () => { + let $selected = $(".multi-select:checked"); + let files = []; + $selected.each((i, ele) => { + files.push({ + name: $(ele).data("select"), + type: $(ele).data("select-type"), + size: $(ele).data("select-size") + }); + }); - $select.on("change", updateSelected); - updateSelected(); -}); + setSelected(files); +} + +$select.on("change", updateSelected); +updateSelected(); diff --git a/assets/navbar.css b/assets/navbar.css new file mode 100644 index 0000000..a17b84d --- /dev/null +++ b/assets/navbar.css @@ -0,0 +1,6 @@ +#navbar { + overflow-x: auto; +} +.nav-link { + white-space: nowrap; +} diff --git a/assets/shell.js b/assets/shell.js index 3e5fe7c..9293e52 100644 --- a/assets/shell.js +++ b/assets/shell.js @@ -1,11 +1,9 @@ /* jshint esversion: 6 */ -$(document).ready(() => { - let $shell = $("#shell"); - if ($shell.length < 1) { - return; - } - let $close = $("#shell-close"); +const $shell = $("#shell"); +const $close = $("#shell-close"); + +if ($shell.length > 0) { const ws = new WebSocket("ws" + (window.location.protocol === "https:" ? "s" : "") + "://" + window.location.host + "/websocket?path=" + encodeURIComponent($shell.data("path"))); @@ -54,4 +52,5 @@ $(document).ready(() => { }); }); -}); + +} diff --git a/assets/tooltip.js b/assets/tooltip.js new file mode 100644 index 0000000..299b392 --- /dev/null +++ b/assets/tooltip.js @@ -0,0 +1,5 @@ +/* jshint esversion: 6 */ + +document.querySelectorAll("[title]").forEach(element => { + new bootstrap.Tooltip(element); +}); diff --git a/assets/upload.js b/assets/upload.js index 4cf0a0a..5165d03 100644 --- a/assets/upload.js +++ b/assets/upload.js @@ -1,24 +1,22 @@ /* jshint esversion: 6 */ -$(document).ready(() => { - let $form = $("form[action='@upload']"); - let $file = $("#upload-file"); - - $(".upload-unhide").fadeOut(); - - $file.on("change", () => { - let file = $file[0].files[0]; - let fnElement = $file.parent().find(".custom-file-label"); - fnElement.addClass("file-selected"); - fnElement.text(file.name); +const $form = $("form[action='@upload']"); +const $file = $("#upload-file"); - $form.find("#upload-file-size").val(filesize(file.size)); - $form.find("[name=saveas]").val(file.name); - $(".upload-unhide").fadeIn(); - }); +$(".upload-unhide").fadeOut(); - $form.on("submit", () => { - let putresource = $form.find("[name=saveas]").val(); - // TODO: do XHR to PUT at putresource - }); +$file.on("change", () => { + const file = $file[0].files[0]; + const fnElement = $file.parent().find(".custom-file-label"); + fnElement.addClass("file-selected"); + fnElement.text(file.name); + + $form.find("#upload-file-size").val(filesize(file.size)); + $form.find("[name=saveas]").val(file.name); + $(".upload-unhide").fadeIn(); +}); + +$form.on("submit", () => { + let putresource = $form.find("[name=saveas]").val(); + // TODO: do XHR to PUT at putresource }); diff --git a/index.js b/index.js index 82381c9..248228f 100755 --- a/index.js +++ b/index.js @@ -34,6 +34,12 @@ app.engine("handlebars", hbs({ layoutsDir: path.join(__dirname, "views", "layouts"), defaultLayout: "main", helpers: { + either: (a, b, options) => { + if (a || b) { + return options.fn(); + } + }, + filesize: filesize, octicon: (i, options) => { if (!octicons[i]) { return new handlebars.SafeString(octicons.question.toSVG()); @@ -174,7 +180,7 @@ app.put("/*", (req, res) => { res.redirect("back"); }); save.on("error", (err) => { - res.flash("error", err); + res.flash("error", err.toString()); res.redirect("back"); }); } @@ -230,9 +236,13 @@ app.post("/*@upload", (req, res) => { req.flash("error", "File exists, cannot overwrite. "); res.redirect("back"); }).catch((err) => { - console.log("saving"); - let save = fs.createWriteStream(relative(res.filename, saveas)); + const saveName = relative(res.filename, saveas); + console.log("saving file to " + saveName); + let save = fs.createWriteStream(saveName); save.on("close", () => { + if (res.headersSent) { + return; + } if (buff.length === 0) { req.flash("success", "File saved. Warning: empty file."); } @@ -243,7 +253,7 @@ app.post("/*@upload", (req, res) => { res.redirect("back"); }); save.on("error", (err) => { - req.flash("error", err); + req.flash("error", err.toString()); res.redirect("back"); }); save.write(buff); @@ -277,7 +287,7 @@ app.post("/*@mkdir", (req, res) => { }).catch((err) => { fs.mkdir(relative(res.filename, folder), (err) => { if (err) { - req.flash("error", err); + req.flash("error", err.toString()); res.redirect("back"); return; } @@ -341,7 +351,7 @@ app.post("/*@delete", (req, res) => { res.redirect("back"); }); }).catch((err) => { - req.flash("error", err); + req.flash("error", err.toString()); res.redirect("back"); }); }); @@ -394,7 +404,7 @@ app.get("/*@download", (req, res) => { zip.finalize(); }).catch((err) => { console.log(err); - req.flash("error", err); + req.flash("error", err.toString()); res.redirect("back"); }); }); @@ -515,7 +525,7 @@ app.get("/*", (req, res) => { resolve({ name: f, isdirectory: stats.isDirectory(), - size: filesize(stats.size) + size: stats.size }); }); }); @@ -551,6 +561,9 @@ app.get("/*", (req, res) => { } else if (res.stats.isFile()) { res.sendFile(relative(res.filename), { + headers: { + "Content-Security-Policy": "default-src 'self'; script-src 'none'; sandbox" + }, dotfiles: "allow" }); } diff --git a/package-lock.json b/package-lock.json index d22520d..e0a95c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "archiver": "^5.3.0", "body-parser": "^1.19.0", - "bootstrap": "^4.6.0", + "bootstrap": "^5.0.0", "connect-busboy": "^0.0.2", "connect-flash": "^0.1.1", "express": "^4.17.1", @@ -32,6 +32,16 @@ "file-manager": "index.js" } }, + "node_modules/@popperjs/core": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz", + "integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -168,16 +178,15 @@ } }, "node_modules/bootstrap": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", - "integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.0.tgz", + "integrity": "sha512-tmhPET9B9qCl8dCofvHeiIhi49iBt0EehmIsziZib65k1erBW1rHhj2s/2JsuQh5Pq+xz2E9bEbzp9B7xHG+VA==", "funding": { "type": "opencollective", "url": "https://opencollective.com/bootstrap" }, "peerDependencies": { - "jquery": "1.9.1 - 3", - "popper.js": "^1.16.1" + "@popperjs/core": "^2.9.2" } }, "node_modules/brace-expansion": { @@ -914,17 +923,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, - "node_modules/popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/printj": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", @@ -1295,6 +1293,12 @@ } }, "dependencies": { + "@popperjs/core": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz", + "integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==", + "peer": true + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -1407,9 +1411,9 @@ } }, "bootstrap": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", - "integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.0.tgz", + "integrity": "sha512-tmhPET9B9qCl8dCofvHeiIhi49iBt0EehmIsziZib65k1erBW1rHhj2s/2JsuQh5Pq+xz2E9bEbzp9B7xHG+VA==", "requires": {} }, "brace-expansion": { @@ -1976,12 +1980,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, - "popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "peer": true - }, "printj": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", diff --git a/package.json b/package.json index d10455a..565524d 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "dependencies": { "archiver": "^5.3.0", "body-parser": "^1.19.0", - "bootstrap": "^4.6.0", + "bootstrap": "^5.0.0", "connect-busboy": "^0.0.2", "connect-flash": "^0.1.1", "express": "^4.17.1", diff --git a/views/cmd.handlebars b/views/cmd.handlebars index 2755a1e..70d847e 100644 --- a/views/cmd.handlebars +++ b/views/cmd.handlebars @@ -2,36 +2,36 @@
    - {{#each errors as |error|}} - - {{/each}} - {{#each successes as |success|}} - - {{/each}} -

    command

    -

    {{cmd}}

    -

    stdout

    -
    {{stdout}}
    -

    stderr

    -
    {{stderr}}
    + {{#each errors as |error|}} + + {{/each}} + {{#each successes as |success|}} + + {{/each}} +

    command

    +

    {{cmd}}

    +

    stdout

    +
    {{stdout}}
    +

    stderr

    +
    {{stderr}}
    {{> dialogue-cmd}} diff --git a/views/layouts/main.handlebars b/views/layouts/main.handlebars index 2ee8d51..c6f48b3 100644 --- a/views/layouts/main.handlebars +++ b/views/layouts/main.handlebars @@ -1,38 +1,40 @@ - - - File Manager - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + File Manager + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - {{{body}}} + + {{{body}}} diff --git a/views/list.handlebars b/views/list.handlebars index f4b28dd..1971b18 100644 --- a/views/list.handlebars +++ b/views/list.handlebars @@ -2,37 +2,39 @@
    - {{#each errors as |error|}} - - {{/each}} - {{#each successes as |success|}} - - {{/each}} -
      - {{#each files}} -
    • -
      - - -
      -
    • - {{else}} -
    • - No files -
    • - {{/each}} -
    + {{#each errors as |error|}} + + {{/each}} + {{#each successes as |success|}} + + {{/each}} +
      + {{#each files}} +
    • + +
    • + {{else}} +
    • + No files +
    • + {{/each}} +
    diff --git a/views/login.handlebars b/views/login.handlebars index 896887d..891aa3e 100644 --- a/views/login.handlebars +++ b/views/login.handlebars @@ -2,31 +2,31 @@
    - +
    diff --git a/views/partials/dialogue-cmd.handlebars b/views/partials/dialogue-cmd.handlebars index 23a6bb1..0ac94fc 100644 --- a/views/partials/dialogue-cmd.handlebars +++ b/views/partials/dialogue-cmd.handlebars @@ -1,24 +1,22 @@
    - +
    diff --git a/views/partials/dialogue-delete.handlebars b/views/partials/dialogue-delete.handlebars index 119847d..6e3c7a6 100644 --- a/views/partials/dialogue-delete.handlebars +++ b/views/partials/dialogue-delete.handlebars @@ -1,25 +1,23 @@
    - +
    diff --git a/views/partials/dialogue-download.handlebars b/views/partials/dialogue-download.handlebars index 155df4d..194241c 100644 --- a/views/partials/dialogue-download.handlebars +++ b/views/partials/dialogue-download.handlebars @@ -1,25 +1,24 @@
    - +
    diff --git a/views/partials/dialogue-mkdir.handlebars b/views/partials/dialogue-mkdir.handlebars index fd220ac..657c12f 100644 --- a/views/partials/dialogue-mkdir.handlebars +++ b/views/partials/dialogue-mkdir.handlebars @@ -1,24 +1,22 @@
    - +
    diff --git a/views/partials/dialogue-upload.handlebars b/views/partials/dialogue-upload.handlebars index 14acbec..5d3137d 100644 --- a/views/partials/dialogue-upload.handlebars +++ b/views/partials/dialogue-upload.handlebars @@ -1,34 +1,27 @@
    - +
    diff --git a/views/partials/navbar.handlebars b/views/partials/navbar.handlebars index 556eb93..9c9c4bf 100644 --- a/views/partials/navbar.handlebars +++ b/views/partials/navbar.handlebars @@ -1,26 +1,28 @@ diff --git a/views/partials/toolbar.handlebars b/views/partials/toolbar.handlebars index adea23b..d1ebccf 100644 --- a/views/partials/toolbar.handlebars +++ b/views/partials/toolbar.handlebars @@ -1,44 +1,52 @@ -