Added file dialog, image pasting, and progress UI

This commit is contained in:
Asaf Gartner 2021-09-22 13:59:03 +03:00
parent dc56b1f5d0
commit c224ad55b9
5 changed files with 116 additions and 18 deletions

View File

@ -7216,16 +7216,16 @@ body {
line-height: 1.5; line-height: 1.5;
font-weight: 400; } font-weight: 400; }
a { a, .link {
color: #666; color: #666;
color: var(--link-color); color: var(--link-color);
border-bottom-color: #666; border-bottom-color: #666;
border-bottom-color: var(--link-border-color); border-bottom-color: var(--link-border-color);
border-bottom: none; border-bottom: none;
text-decoration: none; } text-decoration: none; }
a:hover { a:hover, .link:hover {
/* text-decoration:underline; */ } /* text-decoration:underline; */ }
a.external::after { a.external::after, .link.external::after {
font-family: "icons"; font-family: "icons";
content: " 1"; content: " 1";
vertical-align: middle; } vertical-align: middle; }
@ -9434,3 +9434,17 @@ span.icon-rss::before {
.notice-failure { .notice-failure {
background-color: #b42222; background-color: #b42222;
background-color: var(--notice-failure-color); } background-color: var(--notice-failure-color); }
.upload_bar.uploading .instructions, .upload_bar:not(.uploading) .progress {
display: none; }
.upload_bar .progress_bar {
border: 2px solid;
border-color: #666;
border-color: var(--link-color);
padding: 2px; }
.upload_bar .progress_bar > div {
background-color: #666;
background-color: var(--link-color);
height: 100%; }

View File

@ -31,7 +31,7 @@ body {
font-weight: 400; font-weight: 400;
} }
a { a, .link {
@include usevar(color, link-color); @include usevar(color, link-color);
@include usevar(border-bottom-color, link-border-color); @include usevar(border-bottom-color, link-border-color);

View File

@ -0,0 +1,15 @@
.upload_bar.uploading .instructions, .upload_bar:not(.uploading) .progress {
display: none;
}
.upload_bar .progress_bar {
border: 2px solid;
@include usevar('border-color', 'link-color');
padding: 2px;
}
.upload_bar .progress_bar > div {
@include usevar('background-color', 'link-color');
height: 100%;
}

View File

@ -25,3 +25,4 @@
@import 'timeline'; @import 'timeline';
@import 'carousel'; @import 'carousel';
@import 'notices'; @import 'notices';
@import 'progress_bar';

View File

@ -53,8 +53,17 @@
*/}} */}}
<textarea id="editor" class="w-100 h6 minh-6 pa2 mono lh-copy" name="body">{{ .EditInitialContents }}</textarea> <textarea id="editor" class="w-100 h6 minh-6 pa2 mono lh-copy" name="body">{{ .EditInitialContents }}</textarea>
<div class="flex flex-row-reverse justify-start mt2"> <div class="flex justify-end items-center mt2">
<input type="submit" class="button ml2" name="submit" value="{{ .SubmitLabel }}" /> <div class="upload_bar flex-grow-1">
<div class="instructions">
Upload files by dragging & dropping, pasting, or <label class="pointer link" for="file_input">selecting</label> them.
</div>
<div class="progress flex">
<div class="progress_text mr3"></div>
<div class="progress_bar flex-grow-1 flex-shrink-1 pa1"><div class=""></div></div>
</div>
</div>
<input type="submit" class="button ml2 flex-grow-0 flex-shrink-0" name="submit" value="{{ .SubmitLabel }}" />
</div> </div>
{{ if .IsEditing }} {{ if .IsEditing }}
@ -104,6 +113,7 @@
<div id="preview-container" class="post post-preview mathjax flex-fair-ns overflow-auto mv3 mv0-ns ml3-ns pa3 br3 bg--dim"> <div id="preview-container" class="post post-preview mathjax flex-fair-ns overflow-auto mv3 mv0-ns ml3-ns pa3 br3 bg--dim">
<div id="preview" class="post-content"></div> <div id="preview" class="post-content"></div>
</div> </div>
<input type="file" multiple name="file_input" id="file_input" class="dn" />{{/* NOTE(asaf): Placing this outside the form to avoid submitting it to the server by accident */}}
</div> </div>
</div> </div>
@ -185,7 +195,30 @@
window.localStorage.removeItem(storageKey); window.localStorage.removeItem(storageKey);
}); });
/*
/ Asset upload
*/
const submitButton = document.querySelector("#form input[type=submit]");
const submitText = submitButton.value;
const fileInput = document.querySelector('#file_input');
const uploadBar = document.querySelector('.upload_bar');
const uploadProgress = document.querySelector('.upload_bar .progress');
const uploadProgressText = document.querySelector('.upload_bar .progress_text');
const uploadProgressBar = document.querySelector('.upload_bar .progress_bar');
const uploadProgressBarFill = document.querySelector('.upload_bar .progress_bar > div');
let fileCounter = 0; let fileCounter = 0;
let enterCounter = 0;
let uploadQueue = [];
let currentUpload = null;
let currentXhr = null;
let currentBatchSize = 0;
let currentBatchDone = 0;
fileInput.addEventListener("change", function(ev) {
if (fileInput.files.length > 0) {
importUserFiles(fileInput.files);
}
});
textField.addEventListener("dragover", function(ev) { textField.addEventListener("dragover", function(ev) {
let effect = "none"; let effect = "none";
@ -199,8 +232,6 @@
ev.preventDefault(); ev.preventDefault();
}); });
let enterCounter = 0;
textField.addEventListener("dragenter", function(ev) { textField.addEventListener("dragenter", function(ev) {
enterCounter++; enterCounter++;
let droppable = false; let droppable = false;
@ -230,9 +261,23 @@
enterCounter = 0; enterCounter = 0;
textField.classList.remove("drop"); textField.classList.remove("drop");
if (ev.dataTransfer && ev.dataTransfer.files) {
importUserFiles(ev.dataTransfer.files)
}
ev.preventDefault();
});
textField.addEventListener("paste", function(ev) {
if (ev.clipboardData && ev.clipboardData.files) {
importUserFiles(ev.clipboardData.files)
}
});
function importUserFiles(files) {
let items = []; let items = [];
for (let i = 0; i < ev.dataTransfer.files.length; ++i) { for (let i = 0; i < files.length; ++i) {
let f = ev.dataTransfer.files[i]; let f = files[i];
if (f.size < maxFileSize) { if (f.size < maxFileSize) {
items.push({ file: f, error: null }); items.push({ file: f, error: null });
} else { } else {
@ -243,7 +288,12 @@
let cursorStart = textField.selectionStart; let cursorStart = textField.selectionStart;
let cursorEnd = textField.selectionEnd; let cursorEnd = textField.selectionEnd;
let toInsert = "\n"; let toInsert = "";
let linesToCursor = textField.value.substr(0, cursorStart).split("\n");
let cursorLine = linesToCursor[linesToCursor.length-1].trim();
if (cursorLine.length > 0) {
toInsert = "\n";
}
for (let i = 0; i < items.length; ++i) { for (let i = 0; i < items.length; ++i) {
if (items[i].file) { if (items[i].file) {
fileCounter++; fileCounter++;
@ -256,9 +306,7 @@
textField.value = textField.value.substring(0, cursorStart) + toInsert + textField.value.substring(cursorEnd, textField.value.length); textField.value = textField.value.substring(0, cursorStart) + toInsert + textField.value.substring(cursorEnd, textField.value.length);
doMarkdown(); doMarkdown();
}
ev.preventDefault();
});
function replaceUploadString(upload, newString) { function replaceUploadString(upload, newString) {
let cursorStart = textField.selectionStart; let cursorStart = textField.selectionStart;
@ -279,15 +327,14 @@
doMarkdown(); doMarkdown();
} }
let uploadQueue = [];
let currentUpload = null;
let currentXhr = null;
function startUpload(uploadNumber, file) { function startUpload(uploadNumber, file) {
uploadQueue.push({ uploadQueue.push({
uploadNumber: uploadNumber, uploadNumber: uploadNumber,
file: file file: file
}); });
currentBatchSize++;
uploadProgressText.textContent = `Uploading files ${currentBatchDone+1}/${currentBatchSize}`;
uploadNext(); uploadNext();
} }
@ -316,21 +363,42 @@
} }
currentUpload = null; currentUpload = null;
currentXhr = null; currentXhr = null;
currentBatchDone++;
uploadNext(); uploadNext();
} }
function updateUploadProgress(ev) {
if (ev.lengthComputable) {
let progress = ev.loaded / ev.total;
uploadProgressBarFill.style.width = Math.floor(progress * 100) + "%";
}
}
function uploadNext() { function uploadNext() {
if (currentUpload == null) { if (currentUpload == null) {
next = uploadQueue.shift(); next = uploadQueue.shift();
if (next) { if (next) {
uploadProgressText.textContent = `Uploading files ${currentBatchDone+1}/${currentBatchSize}`;
uploadBar.classList.add("uploading");
uploadProgressBarFill.style.width = "0%";
submitButton.disabled = true;
submitButton.value = "Uploading files...";
// NOTE(asaf): We use XHR because fetch can't do upload progress reports. Womp womp. https://youtu.be/Pubd-spHN-0?t=2 // NOTE(asaf): We use XHR because fetch can't do upload progress reports. Womp womp. https://youtu.be/Pubd-spHN-0?t=2
currentXhr = new XMLHttpRequest(); currentXhr = new XMLHttpRequest();
currentXhr.upload.addEventListener("progress", updateUploadProgress);
currentXhr.open("POST", uploadUrl, true); currentXhr.open("POST", uploadUrl, true);
currentXhr.setRequestHeader("Hmn-Upload-Filename", next.file.name); currentXhr.setRequestHeader("Hmn-Upload-Filename", next.file.name);
currentXhr.responseType = "json"; currentXhr.responseType = "json";
currentXhr.addEventListener("loadend", uploadDone); currentXhr.addEventListener("loadend", uploadDone);
currentXhr.send(next.file); currentXhr.send(next.file);
currentUpload = next; currentUpload = next;
} else {
submitButton.disabled = false;
submitButton.value = submitText;
uploadBar.classList.remove("uploading");
currentBatchSize = 0;
currentBatchDone = 0;
} }
} }
} }