From 23fde14edad5500a29bd0f93caf9fd46316b2a21 Mon Sep 17 00:00:00 2001 From: teahubxyz Date: Wed, 21 Oct 2026 17:29:56 +0000 Subject: [PATCH] mdecks: open a .md file by drop or button A .md (or any text) file dropped anywhere on the page, or picked with the new Open .md button, replaces the editor text and clears the hash, so the file is the deck. Nothing is uploaded: FileReader reads it in the tab. --- ventures/mdecks/app.js | 58 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/ventures/mdecks/app.js b/ventures/mdecks/app.js index 7a43aa8..0ac3ab7 100644 --- a/ventures/mdecks/app.js +++ b/ventures/mdecks/app.js @@ -576,6 +576,64 @@ if (copied) done(); else failed(); } + // Files: a .md (or any text file) dropped anywhere on the page, or picked with Open .md, becomes the + // deck. It is read in the tab with FileReader; nothing is uploaded. + var fileInput = document.getElementById("file"); + var openBtn = document.getElementById("open"); + var dragDepth = 0; + + function hasFile(e) { + var t = e.dataTransfer && e.dataTransfer.types; + return !!t && Array.prototype.indexOf.call(t, "Files") >= 0; + } + + function openFile(file) { + if (!file) return; + if (file.size > 2000000) { + say(file.name + " is over 2 MB. A deck is plain text, so something else is in that file.", true); + return; + } + var reader = new FileReader(); + reader.onload = function () { + load(String(reader.result).replace(/^/, "")); + current = 0; + history.replaceState(null, "", location.href.split("#")[0]); + parse(); + say("Opened " + file.name + ". Edits stay in this tab; copy a link or the HTML to keep them."); + }; + reader.onerror = function () { say("Couldn't read " + file.name + ".", true); }; + reader.readAsText(file); + } + + openBtn.addEventListener("click", function () { fileInput.click(); }); + fileInput.addEventListener("change", function () { + openFile(fileInput.files[0]); + fileInput.value = ""; + }); + + document.addEventListener("dragenter", function (e) { + if (!hasFile(e)) return; + e.preventDefault(); + dragDepth++; + document.body.classList.add("dropping"); + }); + document.addEventListener("dragover", function (e) { + if (!hasFile(e)) return; + e.preventDefault(); + e.dataTransfer.dropEffect = "copy"; + }); + document.addEventListener("dragleave", function (e) { + if (!hasFile(e)) return; + if (--dragDepth <= 0) { dragDepth = 0; document.body.classList.remove("dropping"); } + }); + document.addEventListener("drop", function (e) { + if (!hasFile(e)) return; + e.preventDefault(); + dragDepth = 0; + document.body.classList.remove("dropping"); + openFile(e.dataTransfer.files[0]); + }); + // A deck in the address opens in place of the sample, and so does one pasted into it later. var linked = /(^#|&)(d|md)=/.test(location.hash); load(linked ? "" : SAMPLE);