Remove JS magic for async upload reply
This commit is contained in:
@@ -538,6 +538,15 @@ func TestFormPostRendersHTML(t *testing.T) {
|
|||||||
if !strings.Contains(string(page), "Delete token") {
|
if !strings.Contains(string(page), "Delete token") {
|
||||||
t.Error("the success page does not show the delete token")
|
t.Error("the success page does not show the delete token")
|
||||||
}
|
}
|
||||||
|
// The script asks for this same page and writes it into the document it is
|
||||||
|
// running in, so a fragment would leave the browser with a half a page.
|
||||||
|
if !strings.HasPrefix(string(page), "<!doctype html>") ||
|
||||||
|
!strings.Contains(string(page), "</html>") {
|
||||||
|
t.Error("the success page is not a whole document")
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(page), "static/app.js") {
|
||||||
|
t.Error("the success page does not load the script, so its copy buttons stay dead")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBasePathMounting(t *testing.T) {
|
func TestBasePathMounting(t *testing.T) {
|
||||||
|
|||||||
+14
-103
@@ -3,13 +3,11 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var base = document.body.dataset.base || '/';
|
|
||||||
|
|
||||||
// Copy buttons appear on the server-rendered result page too, which has no
|
// Copy buttons appear on the server-rendered result page too, which has no
|
||||||
// upload form, so they are wired before anything form-specific bails out.
|
// upload form, so they are wired before anything form-specific bails out.
|
||||||
// They ship hidden so that without this script there is no dead button.
|
// They ship hidden so that without this script there is no dead button.
|
||||||
function wireCopy(root) {
|
function wireCopy() {
|
||||||
var buttons = root.querySelectorAll('button.copy[data-copy]');
|
var buttons = document.querySelectorAll('button.copy[data-copy]');
|
||||||
Array.prototype.forEach.call(buttons, function (button) {
|
Array.prototype.forEach.call(buttons, function (button) {
|
||||||
var target = document.getElementById(button.dataset.copy);
|
var target = document.getElementById(button.dataset.copy);
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
@@ -26,7 +24,7 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
wireCopy(document);
|
wireCopy();
|
||||||
|
|
||||||
// Destructive buttons ask first. The attribute carries the question, so the
|
// Destructive buttons ask first. The attribute carries the question, so the
|
||||||
// markup decides what is worth confirming and this stays generic.
|
// markup decides what is worth confirming and this stays generic.
|
||||||
@@ -53,7 +51,6 @@
|
|||||||
var maxSize = parseInt(form.dataset.maxSize, 10) || 0;
|
var maxSize = parseInt(form.dataset.maxSize, 10) || 0;
|
||||||
|
|
||||||
function formatSize(n) {
|
function formatSize(n) {
|
||||||
if (n === null || n === undefined) return 'unlimited';
|
|
||||||
var units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'], i = 0;
|
var units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'], i = 0;
|
||||||
while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; }
|
while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; }
|
||||||
return (i === 0 ? n : n.toFixed(2).replace(/\.00$/, '')) + ' ' + units[i];
|
return (i === 0 ? n : n.toFixed(2).replace(/\.00$/, '')) + ' ' + units[i];
|
||||||
@@ -116,7 +113,10 @@
|
|||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open('POST', form.action);
|
xhr.open('POST', form.action);
|
||||||
xhr.setRequestHeader('Accept', 'application/json');
|
// Ask for the page a plain form post would have been given. What a
|
||||||
|
// finished upload looks like is the server's business, and it already
|
||||||
|
// renders it; this script only adds the progress bar on the way there.
|
||||||
|
xhr.setRequestHeader('Accept', 'text/html');
|
||||||
|
|
||||||
xhr.upload.onprogress = function (ev) {
|
xhr.upload.onprogress = function (ev) {
|
||||||
if (!ev.lengthComputable) return;
|
if (!ev.lengthComputable) return;
|
||||||
@@ -132,15 +132,13 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
xhr.onload = function () {
|
xhr.onload = function () {
|
||||||
submit.disabled = false;
|
// Whatever came back, the result page or an error page, is the page the
|
||||||
var body;
|
// browser should be showing now. Writing it into this document rather
|
||||||
try { body = JSON.parse(xhr.responseText); } catch (err) { body = null; }
|
// than navigating leaves the address on the form's own URL, so a reload
|
||||||
if (xhr.status >= 200 && xhr.status < 300 && body) {
|
// offers a fresh form instead of posting the file a second time.
|
||||||
showResult(body);
|
document.open();
|
||||||
} else {
|
document.write(xhr.responseText);
|
||||||
progress.hidden = true;
|
document.close();
|
||||||
showError(body && body.error ? body.error : 'Upload failed (' + xhr.status + ').');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
xhr.onerror = function () {
|
xhr.onerror = function () {
|
||||||
submit.disabled = false;
|
submit.disabled = false;
|
||||||
@@ -158,91 +156,4 @@
|
|||||||
if (m < 60) return m + 'm ' + (s % 60) + 's';
|
if (m < 60) return m + 'm ' + (s % 60) + 's';
|
||||||
return Math.floor(m / 60) + 'h ' + (m % 60) + 'm';
|
return Math.floor(m / 60) + 'h ' + (m % 60) + 'm';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mirrors web/templates/result.html, so the JavaScript and no-JavaScript
|
|
||||||
// paths show the same thing.
|
|
||||||
function showResult(r) {
|
|
||||||
var card = el('section', 'card result');
|
|
||||||
card.appendChild(el('h1', null, 'Uploaded'));
|
|
||||||
card.appendChild(el('p', 'filename', r.filename));
|
|
||||||
|
|
||||||
var links = el('div', 'links');
|
|
||||||
links.appendChild(linkField(
|
|
||||||
'Share link', 'a page showing size, expiry and a download button',
|
|
||||||
'link-info', r.info_url));
|
|
||||||
links.appendChild(linkField(
|
|
||||||
'Direct download', 'the file itself, for curl, wget or a browser',
|
|
||||||
'link-file', r.url));
|
|
||||||
card.appendChild(links);
|
|
||||||
|
|
||||||
var dl = document.createElement('dl');
|
|
||||||
addRow(dl, 'Size', formatSize(r.size));
|
|
||||||
addRow(dl, 'Expires', r.expires || 'never');
|
|
||||||
addRow(dl, 'SHA-256', r.sha256, 'mono wrap');
|
|
||||||
card.appendChild(dl);
|
|
||||||
|
|
||||||
var warn = el('div', 'warn');
|
|
||||||
warn.appendChild(el('h2', null, 'Delete token'));
|
|
||||||
warn.appendChild(el('p', null,
|
|
||||||
'Shown once. Keep it alongside the share link: pasting it under ' +
|
|
||||||
'"Remove this file" on that page deletes the file before it expires.'));
|
|
||||||
warn.appendChild(el('p', 'mono wrap', r.delete_token));
|
|
||||||
card.appendChild(warn);
|
|
||||||
|
|
||||||
var actions = el('p', 'actions');
|
|
||||||
var again = document.createElement('a');
|
|
||||||
again.className = 'button';
|
|
||||||
again.href = base;
|
|
||||||
again.textContent = 'Upload another file';
|
|
||||||
actions.appendChild(again);
|
|
||||||
card.appendChild(actions);
|
|
||||||
|
|
||||||
form.parentNode.insertBefore(card, form);
|
|
||||||
form.hidden = true;
|
|
||||||
wireCopy(card);
|
|
||||||
card.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
||||||
}
|
|
||||||
|
|
||||||
function el(tag, cls, text) {
|
|
||||||
var node = document.createElement(tag);
|
|
||||||
if (cls) node.className = cls;
|
|
||||||
if (text !== undefined && text !== null) node.textContent = text;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
// One labelled, read-only URL with a copy button beside it.
|
|
||||||
function linkField(label, hint, id, value) {
|
|
||||||
var field = el('div', 'field');
|
|
||||||
var span = document.createElement('span');
|
|
||||||
span.appendChild(document.createTextNode(label + ' '));
|
|
||||||
span.appendChild(el('em', null, hint));
|
|
||||||
field.appendChild(span);
|
|
||||||
|
|
||||||
var row = el('div', 'copyrow');
|
|
||||||
var input = document.createElement('input');
|
|
||||||
input.type = 'text';
|
|
||||||
input.id = id;
|
|
||||||
input.readOnly = true;
|
|
||||||
input.value = value;
|
|
||||||
row.appendChild(input);
|
|
||||||
|
|
||||||
var button = el('button', 'copy', 'Copy');
|
|
||||||
button.type = 'button';
|
|
||||||
button.hidden = true;
|
|
||||||
button.dataset.copy = id;
|
|
||||||
row.appendChild(button);
|
|
||||||
|
|
||||||
field.appendChild(row);
|
|
||||||
return field;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addRow(dl, label, value, cls) {
|
|
||||||
var dt = document.createElement('dt');
|
|
||||||
dt.textContent = label;
|
|
||||||
var dd = document.createElement('dd');
|
|
||||||
dd.textContent = value;
|
|
||||||
if (cls) dd.className = cls;
|
|
||||||
dl.appendChild(dt);
|
|
||||||
dl.appendChild(dd);
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -84,6 +84,9 @@ input[type=text], input[type=password] {
|
|||||||
#upload { display: flex; flex-direction: column; }
|
#upload { display: flex; flex-direction: column; }
|
||||||
#upload > .drop { order: -1; }
|
#upload > .drop { order: -1; }
|
||||||
#upload > button { align-self: flex-start; }
|
#upload > button { align-self: flex-start; }
|
||||||
|
/* An author `display` beats the browser's own [hidden] rule, so anything given
|
||||||
|
a display here has to say what hidden means for it. */
|
||||||
|
#upload[hidden] { display: none; }
|
||||||
|
|
||||||
.drop {
|
.drop {
|
||||||
border: 2px dashed var(--line);
|
border: 2px dashed var(--line);
|
||||||
|
|||||||
Reference in New Issue
Block a user