Download dev/editor.js from klein-ml/blog: direct link, hf CLI and curl.
- Browser
- Download file 4.7 kB
-
https://huggingface.co/spaces/klein-ml/blog/resolve/main/dev/editor.js
- Command line
-
hf download hf://spaces/klein-ml/blog/dev/editor.js
-
curl -L -o editor.js https://huggingface.co/spaces/klein-ml/blog/resolve/main/dev/editor.js
4.7 kB
| // Dev editor. Loaded only by dev/server.py, never by the published site. | |
| // "Edit page" makes the post's title, byline and article text editable in the page. | |
| // "Save" (or Ctrl+S) sends them to the server, which writes them back into the .html file. | |
| (function () { | |
| var article = document.querySelector(".article"); | |
| var band = document.querySelector(".band"); | |
| if (!article || !band) return; // only posts have an editable article | |
| var EDITABLE = ".band h1, .band .dek, .band time, .band .read, .band .byline strong, .band .byline span, .article"; | |
| var editing = false; | |
| var style = document.createElement("style"); | |
| style.textContent = | |
| "#dev-editor{position:fixed;right:16px;bottom:16px;z-index:1000;display:flex;gap:8px;align-items:center;" + | |
| "padding:8px;border-radius:999px;background:#0b1238;color:#e8edfb;font:14px/1 'Pixelify Sans',Arial,sans-serif;" + | |
| "box-shadow:0 6px 20px rgba(0,0,0,.25)}" + | |
| "#dev-editor button{border:0;border-radius:999px;padding:9px 16px;background:#002fa7;color:#ffffff;" + | |
| "font:inherit;font-weight:500;cursor:pointer}" + | |
| "#dev-editor button:disabled{opacity:.4;cursor:default}" + | |
| "#dev-editor .dev-status{padding:0 6px;opacity:.85;max-width:18rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}" + | |
| "body.dev-editing [contenteditable=true]{outline:1px dashed rgba(251,188,4,.7);outline-offset:4px;border-radius:4px}"; | |
| document.head.appendChild(style); | |
| var bar = document.createElement("div"); | |
| bar.id = "dev-editor"; | |
| bar.innerHTML = | |
| '<button type="button" data-act="edit">Edit page</button>' + | |
| '<button type="button" data-act="save" disabled>Save</button>' + | |
| '<span class="dev-status"></span>'; | |
| document.body.appendChild(bar); | |
| var editBtn = bar.querySelector('[data-act="edit"]'); | |
| var saveBtn = bar.querySelector('[data-act="save"]'); | |
| var statusEl = bar.querySelector(".dev-status"); | |
| function status(msg) { statusEl.textContent = msg; } | |
| function setEditing(on) { | |
| editing = on; | |
| document.querySelectorAll(EDITABLE).forEach(function (el) { | |
| el.contentEditable = on ? "true" : "false"; | |
| }); | |
| document.body.classList.toggle("dev-editing", on); | |
| editBtn.textContent = on ? "Stop editing" : "Edit page"; | |
| saveBtn.disabled = !on; | |
| status(on ? "Editing. Save writes to the .html file." : ""); | |
| } | |
| // While editing, clicks in the text shouldn't start audio playback or jump the reader. | |
| document.addEventListener("click", function (e) { | |
| if (editing && e.target.closest && e.target.closest(".article, .band") && !e.target.closest("button, .listen")) { | |
| e.stopPropagation(); | |
| } | |
| }, true); | |
| // Copy of an element with the editor's and the page script's runtime state removed. | |
| function clean(el) { | |
| var c = el.cloneNode(true); | |
| c.querySelectorAll("[contenteditable]").forEach(function (e) { e.removeAttribute("contenteditable"); }); | |
| c.querySelectorAll("[style]").forEach(function (e) { e.removeAttribute("style"); }); | |
| c.querySelectorAll(".speaking").forEach(function (e) { e.classList.remove("speaking"); }); | |
| c.querySelectorAll("[class]").forEach(function (e) { | |
| if (!e.getAttribute("class").trim()) e.removeAttribute("class"); | |
| }); | |
| return c; | |
| } | |
| function serialize() { | |
| var a = clean(article); | |
| a.querySelectorAll(".listen").forEach(function (e) { e.remove(); }); // generated by listen.js | |
| a.querySelectorAll("h2[id]").forEach(function (h) { h.removeAttribute("id"); }); // generated by script.js | |
| var b = clean(band); | |
| var share = b.querySelector("#share span"); | |
| if (share) share.textContent = "Share"; // may read "Link copied" | |
| return { article: a.innerHTML, band: b.innerHTML }; | |
| } | |
| function save() { | |
| if (!editing) return; | |
| status("Saving…"); | |
| var body = serialize(); | |
| body.page = location.pathname; | |
| fetch("/__dev/save", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json", "X-Dev-Editor": "1" }, | |
| body: JSON.stringify(body) | |
| }) | |
| .then(function (r) { | |
| return r.json().then(function (j) { | |
| if (!r.ok) throw new Error(j.error || "HTTP " + r.status); | |
| return j; | |
| }); | |
| }) | |
| .then(function (j) { status("Saved " + j.page + " at " + new Date().toLocaleTimeString()); }) | |
| .catch(function (err) { status("Not saved: " + err.message); }); | |
| } | |
| editBtn.addEventListener("click", function () { setEditing(!editing); }); | |
| saveBtn.addEventListener("click", save); | |
| document.addEventListener("keydown", function (e) { | |
| if (editing && (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s") { | |
| e.preventDefault(); | |
| save(); | |
| } | |
| }); | |
| })(); | |