Compare commits
6 Commits
213f90a0b1
...
224e4a3e1c
Author | SHA1 | Date |
---|---|---|
mark.dev | 224e4a3e1c | |
mark.dev | 742e2dda4e | |
Ben Visness | c36ae9d91b | |
giggs | 85c8c92a0c | |
Ben Visness | 1f731a17c5 | |
Ben Visness | 608235ee29 |
|
@ -0,0 +1,239 @@
|
|||
/* Requires base64.js
|
||||
|
||||
Usage: setupMarkdownUpload(eSubmit, eFileInput, eUploadBar, eText, doMarkdown, maxFileSize, uploadUrl)
|
||||
eSubmit is the element of the button to submit/save the markdown changes. It
|
||||
will be disabled and tell users files are uploading while uploading is
|
||||
happening.
|
||||
eFileInput is the <input type="file">
|
||||
eUploadBar usually looks like
|
||||
<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>
|
||||
eText is the text field that can be dropped into and is editing the markdown.
|
||||
doMarkdown is the function returned by initLiveMarkdown.
|
||||
maxFileSize
|
||||
uploadUrl
|
||||
*/
|
||||
|
||||
function setupMarkdownUpload(eSubmit, eFileInput, eUploadBar, eText, doMarkdown, maxFileSize, uploadUrl) {
|
||||
const submitText = eSubmit.value;
|
||||
const uploadProgress = eUploadBar.querySelector('.progress');
|
||||
const uploadProgressText = eUploadBar.querySelector('.progress_text');
|
||||
const uploadProgressBar = eUploadBar.querySelector('.progress_bar');
|
||||
const uploadProgressBarFill = eUploadBar.querySelector('.progress_bar > div');
|
||||
let fileCounter = 0;
|
||||
let enterCounter = 0;
|
||||
let uploadQueue = [];
|
||||
let currentUpload = null;
|
||||
let currentXhr = null;
|
||||
let currentBatchSize = 0;
|
||||
let currentBatchDone = 0;
|
||||
|
||||
eFileInput.addEventListener("change", function(ev) {
|
||||
if (eFileInput.files.length > 0) {
|
||||
importUserFiles(eFileInput.files);
|
||||
}
|
||||
});
|
||||
|
||||
eText.addEventListener("dragover", function(ev) {
|
||||
let effect = "none";
|
||||
for (let i = 0; i < ev.dataTransfer.items.length; ++i) {
|
||||
if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") {
|
||||
effect = "copy";
|
||||
break;
|
||||
}
|
||||
}
|
||||
ev.dataTransfer.dropEffect = effect;
|
||||
ev.preventDefault();
|
||||
});
|
||||
|
||||
eText.addEventListener("dragenter", function(ev) {
|
||||
enterCounter++;
|
||||
let droppable = false;
|
||||
for (let i = 0; i < ev.dataTransfer.items.length; ++i) {
|
||||
if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") {
|
||||
droppable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (droppable) {
|
||||
eText.classList.add("drop");
|
||||
}
|
||||
});
|
||||
|
||||
eText.addEventListener("dragleave", function(ev) {
|
||||
enterCounter--;
|
||||
if (enterCounter == 0) {
|
||||
eText.classList.remove("drop");
|
||||
}
|
||||
});
|
||||
|
||||
function makeUploadString(uploadNumber, filename) {
|
||||
return `Uploading file #${uploadNumber}: \`${filename}\`...`;
|
||||
}
|
||||
|
||||
eText.addEventListener("drop", function(ev) {
|
||||
enterCounter = 0;
|
||||
eText.classList.remove("drop");
|
||||
|
||||
if (ev.dataTransfer && ev.dataTransfer.files) {
|
||||
importUserFiles(ev.dataTransfer.files)
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
});
|
||||
|
||||
eText.addEventListener("paste", function(ev) {
|
||||
const files = ev.clipboardData?.files ?? [];
|
||||
if (files.length > 0) {
|
||||
importUserFiles(files)
|
||||
}
|
||||
});
|
||||
|
||||
function importUserFiles(files) {
|
||||
let items = [];
|
||||
for (let i = 0; i < files.length; ++i) {
|
||||
let f = files[i];
|
||||
if (f.size < maxFileSize) {
|
||||
items.push({ file: f, error: null });
|
||||
} else {
|
||||
items.push({ file: null, error: `\`${f.name}\` is too big! Max size is ${maxFileSize} but the file is ${f.size}.` });
|
||||
}
|
||||
}
|
||||
|
||||
let cursorStart = eText.selectionStart;
|
||||
let cursorEnd = eText.selectionEnd;
|
||||
|
||||
let toInsert = "";
|
||||
let linesToCursor = eText.value.substr(0, cursorStart).split("\n");
|
||||
let cursorLine = linesToCursor[linesToCursor.length-1].trim();
|
||||
if (cursorLine.length > 0) {
|
||||
toInsert = "\n\n";
|
||||
}
|
||||
for (let i = 0; i < items.length; ++i) {
|
||||
if (items[i].file) {
|
||||
fileCounter++;
|
||||
toInsert += makeUploadString(fileCounter, items[i].file.name) + "\n\n";
|
||||
queueUpload(fileCounter, items[i].file);
|
||||
} else {
|
||||
toInsert += `${items[i].error}\n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
eText.value = eText.value.substring(0, cursorStart) + toInsert + eText.value.substring(cursorEnd, eText.value.length);
|
||||
doMarkdown();
|
||||
uploadNext();
|
||||
}
|
||||
|
||||
function replaceUploadString(upload, newString) {
|
||||
let cursorStart = eText.selectionStart;
|
||||
let cursorEnd = eText.selectionEnd;
|
||||
let uploadString = makeUploadString(upload.uploadNumber, upload.file.name);
|
||||
let insertIndex = eText.value.indexOf(uploadString)
|
||||
eText.value = eText.value.replace(uploadString, newString);
|
||||
if (cursorStart <= insertIndex + uploadString.length) {
|
||||
eText.selectionStart = cursorStart;
|
||||
} else {
|
||||
eText.selectionStart = cursorStart - uploadString.length + newString.length;
|
||||
}
|
||||
if (cursorEnd <= insertIndex + uploadString.length) {
|
||||
eText.selectionEnd = cursorEnd;
|
||||
} else {
|
||||
eText.selectionEnd = cursorEnd - uploadString.length + newString.length;
|
||||
}
|
||||
doMarkdown();
|
||||
}
|
||||
|
||||
function replaceUploadStringError(upload) {
|
||||
replaceUploadString(upload, `There was a problem uploading your file \`${upload.file.name}\`.`);
|
||||
}
|
||||
|
||||
function queueUpload(uploadNumber, file) {
|
||||
uploadQueue.push({
|
||||
uploadNumber: uploadNumber,
|
||||
file: file
|
||||
});
|
||||
|
||||
currentBatchSize++;
|
||||
uploadProgressText.textContent = `Uploading files ${currentBatchDone+1}/${currentBatchSize}`;
|
||||
}
|
||||
|
||||
function uploadDone(ev) {
|
||||
try {
|
||||
if (currentXhr.status == 200 && currentXhr.response) {
|
||||
if (currentXhr.response.url) {
|
||||
let url = currentXhr.response.url;
|
||||
let newString = `[${currentUpload.file.name}](${url})`;
|
||||
if (currentXhr.response.mime.startsWith("image")) {
|
||||
newString = "!" + newString;
|
||||
}
|
||||
|
||||
replaceUploadString(currentUpload, newString);
|
||||
} else if (currentXhr.response.error) {
|
||||
replaceUploadString(currentUpload, `Upload failed for \`${currentUpload.file.name}\`: ${currentXhr.response.error}.`);
|
||||
} else {
|
||||
replaceUploadStringError(currentUpload);
|
||||
}
|
||||
} else {
|
||||
replaceUploadStringError(currentUpload);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
replaceUploadStringError(currentUpload);
|
||||
}
|
||||
currentUpload = null;
|
||||
currentXhr = null;
|
||||
currentBatchDone++;
|
||||
uploadNext();
|
||||
}
|
||||
|
||||
function updateUploadProgress(ev) {
|
||||
if (ev.lengthComputable) {
|
||||
let progress = ev.loaded / ev.total;
|
||||
uploadProgressBarFill.style.width = Math.floor(progress * 100) + "%";
|
||||
}
|
||||
}
|
||||
|
||||
function uploadNext() {
|
||||
if (currentUpload == null) {
|
||||
next = uploadQueue.shift();
|
||||
if (next) {
|
||||
uploadProgressText.textContent = `Uploading files ${currentBatchDone+1}/${currentBatchSize}`;
|
||||
eUploadBar.classList.add("uploading");
|
||||
uploadProgressBarFill.style.width = "0%";
|
||||
eSubmit.disabled = true;
|
||||
eSubmit.value = "Uploading files...";
|
||||
|
||||
try {
|
||||
let utf8Filename = strToUTF8Arr(next.file.name);
|
||||
let base64Filename = base64EncArr(utf8Filename);
|
||||
// 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.upload.addEventListener("progress", updateUploadProgress);
|
||||
currentXhr.open("POST", uploadUrl, true);
|
||||
currentXhr.setRequestHeader("Hmn-Upload-Filename", base64Filename);
|
||||
currentXhr.responseType = "json";
|
||||
currentXhr.addEventListener("loadend", uploadDone);
|
||||
currentXhr.send(next.file);
|
||||
currentUpload = next;
|
||||
} catch (err) {
|
||||
replaceUploadStringError(next);
|
||||
console.error(err);
|
||||
uploadNext();
|
||||
}
|
||||
} else {
|
||||
eSubmit.disabled = false;
|
||||
eSubmit.value = submitText;
|
||||
eUploadBar.classList.remove("uploading");
|
||||
currentBatchSize = 0;
|
||||
currentBatchDone = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8512,6 +8512,7 @@ input[type=submit] {
|
|||
.avatar-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
border-radius: 100%;
|
||||
overflow: hidden;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<h2>Handmade Seattle</h2>
|
||||
</a>
|
||||
<div class="{{ $bannerclass }}" style="background-image: url('{{ static "hms/banner_tall.jpg" }}')"></div>
|
||||
<p>The spiritual successor to HandmadeCon, Handmade Seattle was started in 2019 by Abner Coimbre, the founder of Handmade Network. From the start, Handmade Seattle has been an independent conference, free from corporate sponsorships. The conferences are hybrid online/offline, so you can participate no matter where in the world you live.</p>
|
||||
<p>Handmade Seattle, the spiritual successor to HandmadeCon, was started in 2019 by Abner Coimbre, the founder of Handmade Network. From the start, Handmade Seattle has been an independent conference, free from corporate sponsorships. The conferences are hybrid online/physical, so you can participate no matter where in the world you live.</p>
|
||||
<p>Tickets can be purchased at <a href="https://handmade-seattle.com/">the conference website</a>.</p>
|
||||
<p><a href="https://media.handmade-seattle.com/tag/talks/">Talks</a> and <a href="https://media.handmade-seattle.com/tag/demos/">demos</a> can be viewed on the conference's media site.</p>
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
{{ template "markdown_previews.html" . }}
|
||||
|
||||
<script src="{{ static "js/base64.js" }}"></script>
|
||||
<script src="{{ static "js/markdown_upload.js" }}"></script>
|
||||
|
||||
<style>
|
||||
#editor {
|
||||
|
@ -121,222 +122,14 @@
|
|||
/*
|
||||
/ 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 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) {
|
||||
let effect = "none";
|
||||
for (let i = 0; i < ev.dataTransfer.items.length; ++i) {
|
||||
if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") {
|
||||
effect = "copy";
|
||||
break;
|
||||
}
|
||||
}
|
||||
ev.dataTransfer.dropEffect = effect;
|
||||
ev.preventDefault();
|
||||
});
|
||||
|
||||
textField.addEventListener("dragenter", function(ev) {
|
||||
enterCounter++;
|
||||
let droppable = false;
|
||||
for (let i = 0; i < ev.dataTransfer.items.length; ++i) {
|
||||
if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") {
|
||||
droppable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (droppable) {
|
||||
textField.classList.add("drop");
|
||||
}
|
||||
});
|
||||
|
||||
textField.addEventListener("dragleave", function(ev) {
|
||||
enterCounter--;
|
||||
if (enterCounter == 0) {
|
||||
textField.classList.remove("drop");
|
||||
}
|
||||
});
|
||||
|
||||
function makeUploadString(uploadNumber, filename) {
|
||||
return `Uploading file #${uploadNumber}: \`${filename}\`...`;
|
||||
}
|
||||
|
||||
textField.addEventListener("drop", function(ev) {
|
||||
enterCounter = 0;
|
||||
textField.classList.remove("drop");
|
||||
|
||||
if (ev.dataTransfer && ev.dataTransfer.files) {
|
||||
importUserFiles(ev.dataTransfer.files)
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
});
|
||||
|
||||
textField.addEventListener("paste", function(ev) {
|
||||
const files = ev.clipboardData?.files ?? [];
|
||||
if (files.length > 0) {
|
||||
importUserFiles(files)
|
||||
}
|
||||
});
|
||||
|
||||
function importUserFiles(files) {
|
||||
let items = [];
|
||||
for (let i = 0; i < files.length; ++i) {
|
||||
let f = files[i];
|
||||
if (f.size < maxFileSize) {
|
||||
items.push({ file: f, error: null });
|
||||
} else {
|
||||
items.push({ file: null, error: `\`${f.name}\` is too big! Max size is ${maxFileSize} but the file is ${f.size}.` });
|
||||
}
|
||||
}
|
||||
|
||||
let cursorStart = textField.selectionStart;
|
||||
let cursorEnd = textField.selectionEnd;
|
||||
|
||||
let toInsert = "";
|
||||
let linesToCursor = textField.value.substr(0, cursorStart).split("\n");
|
||||
let cursorLine = linesToCursor[linesToCursor.length-1].trim();
|
||||
if (cursorLine.length > 0) {
|
||||
toInsert = "\n\n";
|
||||
}
|
||||
for (let i = 0; i < items.length; ++i) {
|
||||
if (items[i].file) {
|
||||
fileCounter++;
|
||||
toInsert += makeUploadString(fileCounter, items[i].file.name) + "\n\n";
|
||||
queueUpload(fileCounter, items[i].file);
|
||||
} else {
|
||||
toInsert += `${items[i].error}\n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
textField.value = textField.value.substring(0, cursorStart) + toInsert + textField.value.substring(cursorEnd, textField.value.length);
|
||||
doMarkdown();
|
||||
uploadNext();
|
||||
}
|
||||
|
||||
function replaceUploadString(upload, newString) {
|
||||
let cursorStart = textField.selectionStart;
|
||||
let cursorEnd = textField.selectionEnd;
|
||||
let uploadString = makeUploadString(upload.uploadNumber, upload.file.name);
|
||||
let insertIndex = textField.value.indexOf(uploadString)
|
||||
textField.value = textField.value.replace(uploadString, newString);
|
||||
if (cursorStart <= insertIndex + uploadString.length) {
|
||||
textField.selectionStart = cursorStart;
|
||||
} else {
|
||||
textField.selectionStart = cursorStart - uploadString.length + newString.length;
|
||||
}
|
||||
if (cursorEnd <= insertIndex + uploadString.length) {
|
||||
textField.selectionEnd = cursorEnd;
|
||||
} else {
|
||||
textField.selectionEnd = cursorEnd - uploadString.length + newString.length;
|
||||
}
|
||||
doMarkdown();
|
||||
}
|
||||
|
||||
function replaceUploadStringError(upload) {
|
||||
replaceUploadString(upload, `There was a problem uploading your file \`${upload.file.name}\`.`);
|
||||
}
|
||||
|
||||
function queueUpload(uploadNumber, file) {
|
||||
uploadQueue.push({
|
||||
uploadNumber: uploadNumber,
|
||||
file: file
|
||||
});
|
||||
|
||||
currentBatchSize++;
|
||||
uploadProgressText.textContent = `Uploading files ${currentBatchDone+1}/${currentBatchSize}`;
|
||||
}
|
||||
|
||||
function uploadDone(ev) {
|
||||
try {
|
||||
if (currentXhr.status == 200 && currentXhr.response) {
|
||||
if (currentXhr.response.url) {
|
||||
let url = currentXhr.response.url;
|
||||
let newString = `[${currentUpload.file.name}](${url})`;
|
||||
if (currentXhr.response.mime.startsWith("image")) {
|
||||
newString = "!" + newString;
|
||||
}
|
||||
|
||||
replaceUploadString(currentUpload, newString);
|
||||
} else if (currentXhr.response.error) {
|
||||
replaceUploadString(currentUpload, `Upload failed for \`${currentUpload.file.name}\`: ${currentXhr.response.error}.`);
|
||||
} else {
|
||||
replaceUploadStringError(currentUpload);
|
||||
}
|
||||
} else {
|
||||
replaceUploadStringError(currentUpload);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
replaceUploadStringError(currentUpload);
|
||||
}
|
||||
currentUpload = null;
|
||||
currentXhr = null;
|
||||
currentBatchDone++;
|
||||
uploadNext();
|
||||
}
|
||||
|
||||
function updateUploadProgress(ev) {
|
||||
if (ev.lengthComputable) {
|
||||
let progress = ev.loaded / ev.total;
|
||||
uploadProgressBarFill.style.width = Math.floor(progress * 100) + "%";
|
||||
}
|
||||
}
|
||||
|
||||
function uploadNext() {
|
||||
if (currentUpload == null) {
|
||||
next = uploadQueue.shift();
|
||||
if (next) {
|
||||
uploadProgressText.textContent = `Uploading files ${currentBatchDone+1}/${currentBatchSize}`;
|
||||
uploadBar.classList.add("uploading");
|
||||
uploadProgressBarFill.style.width = "0%";
|
||||
submitButton.disabled = true;
|
||||
submitButton.value = "Uploading files...";
|
||||
|
||||
try {
|
||||
let utf8Filename = strToUTF8Arr(next.file.name);
|
||||
let base64Filename = base64EncArr(utf8Filename);
|
||||
// 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.upload.addEventListener("progress", updateUploadProgress);
|
||||
currentXhr.open("POST", uploadUrl, true);
|
||||
currentXhr.setRequestHeader("Hmn-Upload-Filename", base64Filename);
|
||||
currentXhr.responseType = "json";
|
||||
currentXhr.addEventListener("loadend", uploadDone);
|
||||
currentXhr.send(next.file);
|
||||
currentUpload = next;
|
||||
} catch (err) {
|
||||
replaceUploadStringError(next);
|
||||
console.error(err);
|
||||
uploadNext();
|
||||
}
|
||||
} else {
|
||||
submitButton.disabled = false;
|
||||
submitButton.value = submitText;
|
||||
uploadBar.classList.remove("uploading");
|
||||
currentBatchSize = 0;
|
||||
currentBatchDone = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
setupMarkdownUpload(
|
||||
document.querySelector("#form input[type=submit]"),
|
||||
document.querySelector('#file_input'),
|
||||
document.querySelector('.upload_bar'),
|
||||
textField,
|
||||
doMarkdown,
|
||||
maxFileSize,
|
||||
uploadUrl
|
||||
);
|
||||
</script>
|
||||
{{ end }}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<script src="{{ static "js/image_selector.js" }}"></script>
|
||||
<script src="{{ static "js/templates.js" }}"></script>
|
||||
<script src="{{ static "js/base64.js" }}"></script>
|
||||
<script src="{{ static "js/markdown_upload.js" }}"></script>
|
||||
|
||||
<style>
|
||||
#desc-preview:empty::after {
|
||||
|
@ -387,226 +388,15 @@
|
|||
//////////////////
|
||||
// Asset upload //
|
||||
//////////////////
|
||||
|
||||
const maxFileSize = {{ .MaxFileSize }};
|
||||
const uploadUrl = {{ .UploadUrl }};
|
||||
const submitButton = document.querySelector("#project_form [data-name=Description] 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 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);
|
||||
}
|
||||
});
|
||||
|
||||
description.addEventListener("dragover", function(ev) {
|
||||
let effect = "none";
|
||||
for (let i = 0; i < ev.dataTransfer.items.length; ++i) {
|
||||
if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") {
|
||||
effect = "copy";
|
||||
break;
|
||||
}
|
||||
}
|
||||
ev.dataTransfer.dropEffect = effect;
|
||||
ev.preventDefault();
|
||||
});
|
||||
|
||||
description.addEventListener("dragenter", function(ev) {
|
||||
enterCounter++;
|
||||
let droppable = false;
|
||||
for (let i = 0; i < ev.dataTransfer.items.length; ++i) {
|
||||
if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") {
|
||||
droppable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (droppable) {
|
||||
description.classList.add("drop");
|
||||
}
|
||||
});
|
||||
|
||||
description.addEventListener("dragleave", function(ev) {
|
||||
enterCounter--;
|
||||
if (enterCounter == 0) {
|
||||
description.classList.remove("drop");
|
||||
}
|
||||
});
|
||||
|
||||
function makeUploadString(uploadNumber, filename) {
|
||||
return `Uploading file #${uploadNumber}: \`${filename}\`...`;
|
||||
}
|
||||
|
||||
description.addEventListener("drop", function(ev) {
|
||||
enterCounter = 0;
|
||||
description.classList.remove("drop");
|
||||
|
||||
if (ev.dataTransfer && ev.dataTransfer.files) {
|
||||
importUserFiles(ev.dataTransfer.files)
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
});
|
||||
|
||||
description.addEventListener("paste", function(ev) {
|
||||
const files = ev.clipboardData?.files ?? [];
|
||||
if (files.length > 0) {
|
||||
importUserFiles(files)
|
||||
}
|
||||
});
|
||||
|
||||
function importUserFiles(files) {
|
||||
let items = [];
|
||||
for (let i = 0; i < files.length; ++i) {
|
||||
let f = files[i];
|
||||
if (f.size < maxFileSize) {
|
||||
items.push({ file: f, error: null });
|
||||
} else {
|
||||
items.push({ file: null, error: `\`${f.name}\` is too big! Max size is ${maxFileSize} but the file is ${f.size}.` });
|
||||
}
|
||||
}
|
||||
|
||||
let cursorStart = description.selectionStart;
|
||||
let cursorEnd = description.selectionEnd;
|
||||
|
||||
let toInsert = "";
|
||||
let linesToCursor = description.value.substr(0, cursorStart).split("\n");
|
||||
let cursorLine = linesToCursor[linesToCursor.length-1].trim();
|
||||
if (cursorLine.length > 0) {
|
||||
toInsert = "\n\n";
|
||||
}
|
||||
for (let i = 0; i < items.length; ++i) {
|
||||
if (items[i].file) {
|
||||
fileCounter++;
|
||||
toInsert += makeUploadString(fileCounter, items[i].file.name) + "\n\n";
|
||||
queueUpload(fileCounter, items[i].file);
|
||||
} else {
|
||||
toInsert += `${items[i].error}\n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
description.value = description.value.substring(0, cursorStart) + toInsert + description.value.substring(cursorEnd, description.value.length);
|
||||
doMarkdown();
|
||||
uploadNext();
|
||||
}
|
||||
|
||||
function replaceUploadString(upload, newString) {
|
||||
let cursorStart = description.selectionStart;
|
||||
let cursorEnd = description.selectionEnd;
|
||||
let uploadString = makeUploadString(upload.uploadNumber, upload.file.name);
|
||||
let insertIndex = description.value.indexOf(uploadString)
|
||||
description.value = description.value.replace(uploadString, newString);
|
||||
if (cursorStart <= insertIndex + uploadString.length) {
|
||||
description.selectionStart = cursorStart;
|
||||
} else {
|
||||
description.selectionStart = cursorStart - uploadString.length + newString.length;
|
||||
}
|
||||
if (cursorEnd <= insertIndex + uploadString.length) {
|
||||
description.selectionEnd = cursorEnd;
|
||||
} else {
|
||||
description.selectionEnd = cursorEnd - uploadString.length + newString.length;
|
||||
}
|
||||
doMarkdown();
|
||||
}
|
||||
|
||||
function replaceUploadStringError(upload) {
|
||||
replaceUploadString(upload, `There was a problem uploading your file \`${upload.file.name}\`.`);
|
||||
}
|
||||
|
||||
function queueUpload(uploadNumber, file) {
|
||||
uploadQueue.push({
|
||||
uploadNumber: uploadNumber,
|
||||
file: file
|
||||
});
|
||||
|
||||
currentBatchSize++;
|
||||
uploadProgressText.textContent = `Uploading files ${currentBatchDone+1}/${currentBatchSize}`;
|
||||
}
|
||||
|
||||
function uploadDone(ev) {
|
||||
try {
|
||||
if (currentXhr.status == 200 && currentXhr.response) {
|
||||
if (currentXhr.response.url) {
|
||||
let url = currentXhr.response.url;
|
||||
let newString = `[${currentUpload.file.name}](${url})`;
|
||||
if (currentXhr.response.mime.startsWith("image")) {
|
||||
newString = "!" + newString;
|
||||
}
|
||||
|
||||
replaceUploadString(currentUpload, newString);
|
||||
} else if (currentXhr.response.error) {
|
||||
replaceUploadString(currentUpload, `Upload failed for \`${currentUpload.file.name}\`: ${currentXhr.response.error}.`);
|
||||
} else {
|
||||
replaceUploadStringError(currentUpload);
|
||||
}
|
||||
} else {
|
||||
replaceUploadStringError(currentUpload);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
replaceUploadStringError(currentUpload);
|
||||
}
|
||||
currentUpload = null;
|
||||
currentXhr = null;
|
||||
currentBatchDone++;
|
||||
uploadNext();
|
||||
}
|
||||
|
||||
function updateUploadProgress(ev) {
|
||||
if (ev.lengthComputable) {
|
||||
let progress = ev.loaded / ev.total;
|
||||
uploadProgressBarFill.style.width = Math.floor(progress * 100) + "%";
|
||||
}
|
||||
}
|
||||
|
||||
function uploadNext() {
|
||||
if (currentUpload == null) {
|
||||
next = uploadQueue.shift();
|
||||
if (next) {
|
||||
uploadProgressText.textContent = `Uploading files ${currentBatchDone+1}/${currentBatchSize}`;
|
||||
uploadBar.classList.add("uploading");
|
||||
uploadProgressBarFill.style.width = "0%";
|
||||
submitButton.disabled = true;
|
||||
submitButton.value = "Uploading files...";
|
||||
|
||||
try {
|
||||
let utf8Filename = strToUTF8Arr(next.file.name);
|
||||
let base64Filename = base64EncArr(utf8Filename);
|
||||
// NOTE(mark): copied from 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.upload.addEventListener("progress", updateUploadProgress);
|
||||
currentXhr.open("POST", uploadUrl, true);
|
||||
currentXhr.setRequestHeader("Hmn-Upload-Filename", base64Filename);
|
||||
currentXhr.responseType = "json";
|
||||
currentXhr.addEventListener("loadend", uploadDone);
|
||||
currentXhr.send(next.file);
|
||||
currentUpload = next;
|
||||
} catch (err) {
|
||||
replaceUploadStringError(next);
|
||||
console.error(err);
|
||||
uploadNext();
|
||||
}
|
||||
} else {
|
||||
submitButton.disabled = false;
|
||||
submitButton.value = submitText;
|
||||
uploadBar.classList.remove("uploading");
|
||||
currentBatchSize = 0;
|
||||
currentBatchDone = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
setupMarkdownUpload(
|
||||
document.querySelector("#project_form [data-name=Description] input[type=submit]"),
|
||||
document.querySelector('#file_input'),
|
||||
document.querySelector('.upload_bar'),
|
||||
description,
|
||||
doMarkdown,
|
||||
{{ .MaxFileSize }},
|
||||
{{ .UploadUrl }}
|
||||
);
|
||||
|
||||
</script>
|
||||
{{ end }}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
.admin .cover {
|
||||
background: repeating-linear-gradient( -45deg, #ff6c00, #ff6c00 12px, #000000 5px, #000000 25px );
|
||||
}
|
||||
|
||||
</style>
|
||||
{{ end }}
|
||||
|
||||
|
|
Loading…
Reference in New Issue