Added file dialog, image pasting, and progress UI
This commit is contained in:
parent
dc56b1f5d0
commit
c224ad55b9
|
@ -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%; }
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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%;
|
||||||
|
}
|
||||||
|
|
|
@ -25,3 +25,4 @@
|
||||||
@import 'timeline';
|
@import 'timeline';
|
||||||
@import 'carousel';
|
@import 'carousel';
|
||||||
@import 'notices';
|
@import 'notices';
|
||||||
|
@import 'progress_bar';
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue