diff --git a/files/assets/js/submit.js b/files/assets/js/submit.js
index b5c3c48fa..92b4eab07 100644
--- a/files/assets/js/submit.js
+++ b/files/assets/js/submit.js
@@ -208,3 +208,263 @@ if (location.href == '/submit') {
const sub = document.getElementById('sub')
if (sub) sub.value = localStorage.getItem("sub")
}
+
+const filelist = document.getElementById('upload-filelist');
+
+document.addEventListener('DOMContentLoaded', function () {
+ /**
+ * Sets up the elements inside file upload rows.
+ *
+ * @param {File} file
+ * @return {HTMLLIElement} row
+ */
+ function addRow(file) {
+ var row = document.createElement('li');
+
+ var name = document.createElement('span');
+ name.textContent = file.name;
+ name.className = 'file-name';
+
+ var progressIndicator = document.createElement('span');
+ progressIndicator.className = 'progress-percent';
+ progressIndicator.textContent = '0%';
+
+ var progressBar = document.createElement('progress');
+ progressBar.className = 'file-progress';
+ progressBar.setAttribute('max', '100');
+ progressBar.setAttribute('value', '0');
+
+ row.appendChild(name);
+ row.appendChild(progressBar);
+ row.appendChild(progressIndicator);
+
+ filelist.innerHTML = '';
+ filelist.appendChild(row);
+ return row;
+ }
+
+ /**
+ * Updates the page while the file is being uploaded.
+ *
+ * @param {ProgressEvent} evt
+ */
+ function handleUploadProgress(evt) {
+ var xhr = evt.target;
+ var bar = xhr.bar;
+ var percentIndicator = xhr.percent;
+
+ /* If we have amounts of work done/left that we can calculate with
+ (which, unless we're uploading dynamically resizing data, is always), calculate the percentage. */
+ if (evt.lengthComputable) {
+ var progressPercent = Math.floor((evt.loaded / evt.total) * 100);
+ bar.setAttribute('value', progressPercent);
+ percentIndicator.textContent = progressPercent + '%';
+ }
+ }
+
+ /**
+ * Complete the uploading process by checking the response status and, if the
+ * upload was successful, writing the URL(s) and creating the copy element(s)
+ * for the files.
+ *
+ * @param {ProgressEvent} evt
+ */
+ function handleUploadComplete(evt) {
+ var xhr = evt.target;
+ var bar = xhr.bar;
+ var row = xhr.row;
+ var percentIndicator = xhr.percent;
+
+ percentIndicator.style.visibility = 'hidden';
+ bar.style.visibility = 'hidden';
+ row.removeChild(bar);
+ row.removeChild(percentIndicator);
+ var respStatus = xhr.status;
+
+ var url = document.createElement('span');
+ url.className = 'file-url';
+ row.appendChild(url);
+
+ var link = document.createElement('a');
+ if (respStatus === 200) {
+ var response = JSON.parse(xhr.responseText);
+ if (response.success) {
+ link.textContent = response.files[0].url.replace(/.*?:\/\//g, '');
+ link.href = response.files[0].url;
+ url.appendChild(link);
+ var copy = document.createElement('button');
+ copy.className = 'upload-clipboard-btn';
+ var glyph = document.createElement('img');
+ glyph.src = 'img/glyphicons-512-copy.png';
+ copy.appendChild(glyph);
+ url.appendChild(copy);
+ copy.addEventListener("click", function (event) {
+ /* Why create an element? The text needs to be on screen to be
+ selected and thus copied. The only text we have on-screen is the link
+ without the http[s]:// part. So, this creates an element with the
+ full link for a moment and then deletes it.
+
+ See the "Complex Example: Copy to clipboard without displaying
+ input" section at: https://stackoverflow.com/a/30810322 */
+ var element = document.createElement('a');
+ element.textContent = response.files[0].url;
+ link.appendChild(element);
+ var range = document.createRange();
+ range.selectNode(element);
+ window.getSelection().removeAllRanges();
+ window.getSelection().addRange(range);
+ document.execCommand("copy");
+ link.removeChild(element);
+ });
+ } else {
+ bar.innerHTML = 'Error: ' + response.description;
+ }
+ } else if (respStatus === 413) {
+ link.textContent = 'File too big!';
+ url.appendChild(link);
+ } else {
+ var response = JSON.parse(xhr.responseText);
+ link.textContent = response.description;
+ url.appendChild(link);
+ }
+ }
+
+ /**
+ * Updates the page while the file is being uploaded.
+ *
+ * @param {File} file
+ * @param {HTMLLIElement} row
+ */
+ function uploadFile(file, row) {
+ var bar = row.querySelector('.file-progress');
+ var percentIndicator = row.querySelector('.progress-percent');
+ var xhr = new XMLHttpRequest();
+ xhr.open('POST', 'upload.php');
+
+ xhr['row'] = row;
+ xhr['bar'] = bar;
+ xhr['percent'] = percentIndicator;
+ xhr.upload['bar'] = bar;
+ xhr.upload['percent'] = percentIndicator;
+
+ xhr.addEventListener('load', handleUploadComplete, false);
+ xhr.upload.onprogress = handleUploadProgress;
+
+ var form = new FormData();
+ form.append('files[]', file);
+ xhr.send(form);
+ }
+
+ /**
+ * Prevents the browser for allowing the normal actions associated with an event.
+ * This is used by event handlers to allow custom functionality without
+ * having to worry about the other consequences of that action.
+ *
+ * @param {Event} evt
+ */
+ function stopDefaultEvent(evt) {
+ evt.stopPropagation();
+ evt.preventDefault();
+ }
+
+ /**
+ * Adds 1 to the state and changes the text.
+ *
+ * @param {Object} state
+ * @param {HTMLButtonElement} element
+ * @param {DragEvent} evt
+ */
+ function handleDrag(state, element, evt) {
+ stopDefaultEvent(evt);
+ if (state.dragCount == 1) {
+ element.textContent = 'Drop it here~';
+ }
+ state.dragCount += 1;
+ }
+
+ /**
+ * Subtracts 1 from the state and changes the text back.
+ *
+ * @param {Object} state
+ * @param {HTMLButtonElement} element
+ * @param {DragEvent} evt
+ */
+ function handleDragAway(state, element, evt) {
+ stopDefaultEvent(evt);
+ state.dragCount -= 1;
+ if (state.dragCount == 0) {
+ element.textContent = 'Select or drop file(s)';
+ }
+ }
+
+ /**
+ * Prepares files for uploading after being added via drag-drop.
+ *
+ * @param {Object} state
+ * @param {HTMLButtonElement} element
+ * @param {DragEvent} evt
+ */
+ function handleDragDrop(state, element, evt) {
+ stopDefaultEvent(evt);
+ handleDragAway(state, element, evt);
+ var len = evt.dataTransfer.files.length;
+ for (var i = 0; i < len; i++) {
+ var file = evt.dataTransfer.files[i];
+ var row = addRow(file);
+ uploadFile(file, row);
+ }
+ }
+
+ /**
+ * Prepares the files to be uploaded when they're added to the element.
+ *
+ * @param {InputEvent} evt
+ */
+ function uploadFiles(evt) {
+ var len = evt.target.files.length;
+ // For each file, make a row, and upload the file.
+ for (var i = 0; i < len; i++) {
+ var file = evt.target.files[i];
+ var row = addRow(file);
+ uploadFile(file, row);
+ }
+ }
+
+ /**
+ * Opens up a "Select files.." dialog window to allow users to select files to upload.
+ *
+ * @param {HTMLInputElement} target
+ * @param {InputEvent} evt
+ */
+ function selectFiles(target, evt) {
+ stopDefaultEvent(evt);
+ target.click();
+ }
+
+ /* Handles the pasting function */
+ window.addEventListener("paste", e => {
+ var len = e.clipboardData.files.length;
+ for (var i = 0; i < len; i++) {
+ var file = e.clipboardData.files[i];
+ var row = addRow(file);
+ uploadFile(file, row);
+ }
+ });
+
+
+ /* Set-up the event handlers for the