diff --git a/pyproject.toml b/pyproject.toml index e950ea0..f2eebb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,13 +35,8 @@ dependencies = [ "Pygments >= 2.12.0", "xstatic < 2.0.0", "XStatic-asciinema-player <= 2.6.1.1", - "xstatic-bootbox >= 5.4.0, <= 5.5.1.1", - "xstatic-bootstrap >=4.0.0.0, <= 4.5.3.1", "xstatic-font-awesome >= 6.0.0, <= 6.2.1.1", - "xstatic-jquery <= 3.5.1.1", - "xstatic-jquery-ui <= 1.13.0.1", - "xstatic-jquery-file-upload <= 10.31.0.1", - "xstatic-pygments <= 2.9.0.1", + "xstatic-pygments <= 2.9.0.1" ] [project.scripts] diff --git a/src/bepasty/bepasty_xstatic.py b/src/bepasty/bepasty_xstatic.py index bdda38a..05e6e16 100644 --- a/src/bepasty/bepasty_xstatic.py +++ b/src/bepasty/bepasty_xstatic.py @@ -3,12 +3,7 @@ # The names below must be package names. mod_names = [ 'asciinema_player', - 'bootbox', - 'bootstrap', 'font_awesome', - 'jquery', - 'jquery_ui', - 'jquery_file_upload', 'pygments', ] diff --git a/src/bepasty/static/app/css/style.css b/src/bepasty/static/app/css/style.css index ca07c31..e63c1c3 100644 --- a/src/bepasty/static/app/css/style.css +++ b/src/bepasty/static/app/css/style.css @@ -8,12 +8,6 @@ body { margin: 0; -webkit-font-smoothing: antialiased; font-size: 100%; - background-color: #fff; -} - -#footer { - height: 30px; - position: relative; } #formupload { @@ -22,13 +16,9 @@ body { font-family: monospace; } -#files p { +#files a { word-break: break-all; } -#files p .break-word { - word-break: break-word; - font-weight: normal !important; -} #filelist { height: auto; @@ -36,16 +26,6 @@ body { font-family: monospace; } -.fileupload-abort { - float: right; - margin-left: 10px; -} - -.dropzone { - width: 100%; - padding: 3em; -} - #wrapper { min-height: 100%; margin: 0 auto -30px; @@ -56,33 +36,11 @@ body { padding: 0 15px 60px; } -.jumbotron .fa { - font-size: 80px; -} - .jumbotron { font-size: 100%; text-align: center; } -.alert { - padding: 5px 10px; -} - -.alert-processing { - color: #464646; - background-color: #f0f0f0; - border-color: #eeeeee; -} - -.alert-processing hr { - border-top-color: #e6e6e6; -} - -.alert-processing .alert-link { - color: #959595; -} - /* If data is too wide, show scrollbars */ .data { width: 100%; @@ -127,12 +85,39 @@ table.highlighttable { border-left: 1px solid #ccc; } +@media (prefers-color-scheme: dark) { + .line-highlight { + background-color: hsl(var(--bulma-warning-h), var(--bulma-warning-s), 15%); + } + table.highlighttable { + border: 1px solid var(--bulma-dark); + } + .highlight pre { + border-left: 1px solid var(--bulma-dark); + } +} + /* Set minimal width and let the contents push the block width */ .linenos { width: 1px; } -/* Autocomplete for modify dialog */ -.ui-autocomplete { - z-index: 1065; +.mr-auto { + margin-right: auto; +} + +.is-text-6-5 { + font-size: 0.85rem; +} + +#header .navbar { + --bulma-navbar-background-color: var(--bulma-light); +} +.has-text-deemph { + color: hsl(var(--bulma-scheme-h), var(--bulma-scheme-s), var(--bulma-text-l)); +} +@media (prefers-color-scheme:dark) { + #header .navbar { + --bulma-navbar-background-color: var(--bulma-dark); + } } diff --git a/src/bepasty/static/app/js/fileuploader.js b/src/bepasty/static/app/js/fileuploader.js index 927e727..f317df7 100644 --- a/src/bepasty/static/app/js/fileuploader.js +++ b/src/bepasty/static/app/js/fileuploader.js @@ -1,139 +1,169 @@ -jqXHR = {}; -$(function () { - 'use strict'; - - // Generate human-readable file size - function humansize (size) { - var suffix = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"], - tier = 0; - - while (size >= 1024) { - size = size / 1024; - tier++; - } +function humansize (size) { + const suffix = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"]; + let tier = 0; + + while (size >= 1024) { + size = size / 1024; + tier++; + } + + return Math.round(size * 10) / 10 + " " + suffix[tier]; +} + +function process(fieldName, file, metadata, load, error, progress, abort, transfer, options) { + const state = { aborted: false, xhr: null }; + let uploadUrl = null; + let displayUrl = null; + + const controller = new AbortController(); + + const doUpload = (url, name, offset) => { + if (state.aborted) return; + + const end = Math.min(offset + MAX_BODY_SIZE, file.size); + const xhr = new XMLHttpRequest(); + state.xhr = xhr; + + xhr.open("POST", url, true); + xhr.setRequestHeader("Content-Range", `bytes ${offset}-${end - 1}/${file.size}`); + + xhr.upload.onprogress = (e) => progress(true, offset + e.loaded, file.size); + + xhr.onload = () => { + state.xhr = null; + if (xhr.status < 200 || xhr.status >= 300) { + error(`Chunk upload failed: ${xhr.status}`); + return; + } + const result = JSON.parse(xhr.responseText); + if (result.files?.[0]?.url) displayUrl = result.files[0].url; + + if (end < file.size) { + doUpload(url, name, end); + } else { + load(displayUrl ?? url); + const filesDiv = document.getElementById("files"); + const alert = document.createElement("div"); + alert.className = "notification is-success is-light"; + alert.dataset.name = name; + const a = document.createElement("a"); + a.href = displayUrl ?? url; + a.target = "_blank"; + a.textContent = `${file.name} (${humansize(file.size)})`; + alert.appendChild(a); + filesDiv.appendChild(alert); + + const fileList = document.getElementById("filelist"); + fileList.textContent += name + "\n"; + const fileListForm = document.getElementById("filelist-form"); + fileListForm.style.display = ""; + + const previewBody = document.getElementById("tab-copy-preview-body"); + const inlineBody = document.getElementById("tab-copy-inline-body"); + const urlEl = document.createElement("span"); + urlEl.dataset.name = name; + urlEl.append(window.location + name, document.createElement("br")); + previewBody.appendChild(urlEl); + const urlInlineEl = document.createElement("span"); + urlInlineEl.dataset.name = name; + urlInlineEl.append(urlEl.innerText + "/+inline", document.createElement("br")); + inlineBody.appendChild(urlInlineEl); + } + }; + + xhr.onerror = () => { state.xhr = null; error("Network error"); }; + xhr.onabort = () => { state.xhr = null; }; + + const formData = new FormData(); + formData.append("text", ""); + formData.append("contenttype", file.type || "application/octet-stream"); + formData.append("filename", file.name); + formData.append("maxlife-value", document.querySelector("input[name=maxlife-value]").value); + formData.append("maxlife-unit", document.querySelector("select[name=maxlife-unit]").value); + formData.append("file", file.slice(offset, end), file.name); + + xhr.send(formData); + }; + + fetch(UPLOAD_NEW_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + signal: controller.signal, + body: JSON.stringify({ + filename: file.name, + size: file.size, + type: file.type, + maxlife_unit: document.querySelector("select[name=maxlife-unit]").value, + maxlife_value: document.querySelector("input[name=maxlife-value]").value + }) + }) + .then(res => { + if (!res.ok) throw new Error("Failed to initialise upload"); + return res.json(); + }) + .then(({ name, url }) => { + uploadUrl = url; + doUpload(url, name, 0); + }) + .catch(err => { + if (err.name !== "AbortError") error(err.message); + }); - return Math.round(size * 10) / 10 + " " + suffix[tier]; + return { + abort: () => { + state.aborted = true; + controller.abort(); + if (state.xhr) state.xhr.abort(); + if (uploadUrl) fetch(`${uploadUrl}/abort`).catch(() => {}); + abort(); } + }; +} + +function revert(uniqueFileId, load, error) { + const name = uniqueFileId.split("#")[0].slice(1) + fetch(`/${name}/+delete`, { method: "POST" }) + .then(res => { + if (!res.ok) throw new Error(`Delete failed: ${res.status}`); + + document.querySelectorAll(`#right-panel [data-name=${name}]`).forEach(v=>v.remove()); + + const fileList = document.getElementById("filelist"); + fileList.textContent = fileList.textContent + .split("\n") + .filter(n => n !== name) + .join("\n"); + + if (!fileList.textContent.trim()) { + document.getElementById("filelist-form").style.display = "none"; + } + + load(); + }) + .catch(err => error(err.message)); +} + +const fp = FilePond.create(document.getElementById("filepond"), { + chunkUploads: true, + allowMinimumUploadDuration: false, + server: { + fetch: null, + revert, + restore: null, + load: null, + remove: null, + process + } +}); - $('#fileupload') - .fileupload({ - dataType: 'json', - autoUpload: true, - singleFileUploads: true, - maxChunkSize: MAX_BODY_SIZE, - maxFileSize: MAX_ALLOWED_FILE_SIZE - }) - - .on('fileuploadadd', function (e, data) { }) - - .on('fileuploadsubmit', function (e, data) { - var $this = $(this); - var file = data.files[0] - // Create new item - $.ajax({ - type: 'POST', - url: UPLOAD_NEW_URL, - data: JSON.stringify({ - filename: file.name, - size: file.size, - type: file.type, - maxlife_unit: $("select[name=maxlife-unit] option:selected").val(), - maxlife_value: $("input[name=maxlife-value]").val() - }), - contentType: 'application/json', - success: function (result) { - data.url = result.url; - - data.context = $('
') - .appendTo('#files'); - - var abortButton = $(' - - - + +
+ @@ -86,19 +75,18 @@ - - - - - - - - - - + {% block extra_script %}{% endblock %} diff --git a/src/bepasty/templates/_utils.html b/src/bepasty/templates/_utils.html index 3e8cfbf..71af1ab 100644 --- a/src/bepasty/templates/_utils.html +++ b/src/bepasty/templates/_utils.html @@ -1,8 +1,8 @@ {% macro filelist(files) %} - +
- {% for file in files %} -
+ Filename
Type @@ -25,7 +25,7 @@
+ {{ file['filename'] }} @@ -49,33 +49,41 @@ {% endif %} -
- - Display - - - Download - - - Inline - + +
{% if may(DELETE) %} -
-
{% endif %} {% if may(ADMIN) %} {% if not file['locked'] %} -
-
{% else %} -
-
@@ -90,14 +98,14 @@ {% endmacro %} {% macro input_filename(value) -%} - -{%- endmacro %} - -{% macro input_contenttype(value) -%} - + {%- endmacro %} -{% macro contenttype_autocomplete(selector, contenttypes) -%} - var availableTypes = ["{{ contenttypes | join('","') | safe}}"]; - {{ selector|safe }}.autocomplete({source: availableTypes}); +{% macro input_contenttype(contenttypes, value) -%} + + + {% for contenttype in contenttypes %} + + {% endfor %} + {%- endmacro %} diff --git a/src/bepasty/templates/carousel.html b/src/bepasty/templates/carousel.html index 3059cc5..a1e5639 100644 --- a/src/bepasty/templates/carousel.html +++ b/src/bepasty/templates/carousel.html @@ -1,45 +1,39 @@ {% extends "_layout.html" %} +{% block content %} +{% for file in files %} + + {{ file['filename'] }} + +{% endfor %} +{% endblock content %} + + {% block extra_link %} + {% endblock %} -{% block content %} - -{% endblock content %} +{% block extra_script %} + + +{% endblock %} diff --git a/src/bepasty/templates/display.html b/src/bepasty/templates/display.html index a4c51b4..c52eea9 100644 --- a/src/bepasty/templates/display.html +++ b/src/bepasty/templates/display.html @@ -6,77 +6,72 @@ {% block content %}
-
-

+
+

{{ item.meta['filename'] }}

-
+
{% if not item.meta['locked'] or may(ADMIN) %} {% if is_list_item %} - - Carousel - +

+ + + + + Carousel + +

{% endif %} - - QR - - - Download - - - Inline - +

+ + QR + +

+

+ + Download + +

+

+ + Inline + +

{% endif %} {% if may(MODIFY) %} -
- -
-
- -
- {{ utils.input_filename(item.meta['filename']) }} -
-
-
- -
- {{ utils.input_contenttype(item.meta['type']) }} -
-
- -
-
- +

+ +

{% endif %} {% if may(DELETE) %} -
+ -
{% endif %} {% if may(ADMIN) %} {% if not item.meta['locked'] %} -
-
{% else %} -
-
{% endif %} {% endif %}
-
-

+

+

Type: {{ item.meta['type'] }}, Size: {{ item.meta['size'] }} bytes, SHA256: {{ item.meta['hash'] }}. @@ -95,25 +90,47 @@

+ +{% if may(MODIFY) %} + +{% endif %} + {% endblock content %} {% block extra_link %} - + + {% endblock %} {% block extra_script %} - -{% if may(MODIFY) %} - -{% endif %} {% endblock extra_script %} diff --git a/src/bepasty/templates/error.html b/src/bepasty/templates/error.html index e2ba1f6..9c1de6b 100644 --- a/src/bepasty/templates/error.html +++ b/src/bepasty/templates/error.html @@ -2,13 +2,13 @@ {% block content %}
-
-

+
+

{{ heading }}

-

-
-
+ +
+
{{ body }}
diff --git a/src/bepasty/templates/filelist.html b/src/bepasty/templates/filelist.html index 7d440ec..7837d91 100644 --- a/src/bepasty/templates/filelist.html +++ b/src/bepasty/templates/filelist.html @@ -4,9 +4,5 @@ {% set page_title = 'All items' %} {% block content %} -
-
- {{ utils.filelist(files) }} -
-
+{{ utils.filelist(files) }} {% endblock content %} diff --git a/src/bepasty/templates/index.html b/src/bepasty/templates/index.html index cace957..d0ea740 100644 --- a/src/bepasty/templates/index.html +++ b/src/bepasty/templates/index.html @@ -5,143 +5,224 @@ {% macro maximum_lifetime() -%} {% set default_maxlife_value = config.DEFAULT_MAXLIFE_VALUE or 1 %} {% set default_maxlife_unit = (config.DEFAULT_MAXLIFE_UNIT or 'MONTHS')|lower %} -
-
-
-
-
- -
+
+

+
+

+

+ + + +

{% endmacro %} {% block content %} {% if may(CREATE) %} -
-
+
+
-
- +
+
-
-
- {{ utils.input_contenttype() }} -
-
+
+

+ {{ utils.input_contenttype(contenttypes) }} +

+

{{ utils.input_filename() }} -

-
- -
+

+

+ +


- - {{ maximum_lifetime() }} - - - Drag and drop files here — or click to select files - - - - +
+ +
+ {{ maximum_lifetime() }} +
+
+ +
-
- -