/* 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 eUploadBar usually looks like
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; } } } }