// nostalgia for a simpler, more complicated time const $ = document.querySelector.bind(document); const $$ = document.querySelectorAll.bind(document); const initialLines = 10; // I am truly sorry function invoker(methodName) { return function(a) { return a[methodName](); } } class Button extends HTMLButtonElement { constructor() { super(); this.addEventListener("click", this.click); this.setAttribute("style", "min-width:2.3em"); } } class LineRemover extends Button { connectedCallback() { this.setAttribute("title", "remove line"); } click() { const container = this.closest("div.line"); const gp = container.parentElement; container.remove(); gp.dispatchEvent(reorder); } } class LinePinner extends Button { connectedCallback() { this.setAttribute("title", "pin line in place"); } click() { const l = this.closest("div.line"); l.classList.toggle("unpinned"); if (l.classList.contains("unpinned")) { this.classList.remove("pinned"); this.setAttribute("title", "lock line in place"); } else { this.classList.add("pinned"); this.setAttribute("title", "unlock line"); } } } class LineRegenner extends Button { connectedCallback() { this.setAttribute("title", "regenerate just this line"); } click() { const l = this.closest("div.line"); if (!l.classList.contains("unpinned")) { return; } l.regen() } } class LineEditor extends Button { connected = false connectedCallback() { if (this.connected) { return; } this.setAttribute("title", "edit line text"); this.linetext = this.closest(".line").querySelector(".linetext"); this.f = $("#line-editor-tmpl").content.firstElementChild.cloneNode(true); this.i = this.f.querySelector("input[type=text]"); this.f.addEventListener("submit", (e) => { e.preventDefault(); this.done(); }) this.connected = true; } done() { const l = this.closest(".line"); l.setAttribute("draggable", true); this.setAttribute("title", "edit line text"); this.editing = false; this.linetext.innerText = this.i.value; this.f.remove(); this.linetext.style.display = "inline"; this.style['font-weight'] = ""; this.dispatchEvent(edited); } click() { if (this.editing) { this.done(); return; } this.editing = true; this.setAttribute("title", "finish editing"); this.style['font-weight'] = "bold"; const l = this.closest(".line"); l.setAttribute("draggable", false); this.linetext.style.display = "none"; this.i.value = this.linetext.innerText; this.linetext.parentElement.appendChild(this.f); this.i.focus(); } } class LineInput extends HTMLInputElement { constructor() { super(); this.setAttribute("type", "text"); } } class LineAdder extends Button { click() { $("div[is=poem-lines]").add() } } class PoemRegenner extends Button { connectedCallback() { this.setAttribute("title", "regenerate unpinnned lines"); } click() { $("div[is=poem-lines]").regenerate(); } } class PoemResetter extends Button { connectedCallback() { // TODO set title this.innerText = "reset"; } click() { $("div[is=poem-lines]").reset(); } } class PoemLine extends HTMLDivElement { constructor() { super(); this.ltp = $("#linetmpl"); this.addEventListener("edited", (e) => { e.preventDefault(); this.querySelector("div[is=source-text]").edited(); }); } connectedCallback() { if (this.connected) { return; } this.setAttribute("draggable", true); const lid = Math.floor(Math.random()*100); this.setAttribute("id", `line-${lid}`); this.addEventListener("dragstart", (e) => { e.dataTransfer.dropEffect = "move"; e.dataTransfer.setData("text/plain", this.getAttribute("id")); }); this.appendChild(this.ltp.content.cloneNode(true)); const lt = this.querySelector(".linetext"); lt.addEventListener("mousedown", (e) => { this.setAttribute("draggable", false); }); lt.addEventListener("mouseleave", (e) => { this.setAttribute("draggable", true); }); this.connected = true; } get source() { return this.querySelector(".linetext").dataset.source; } regen() { let params = new URLSearchParams(document.location.search); const corpusid = params.get("corpus"); let p = "/line"; if (corpusid != "" && corpusid != null) { p = `/line?corpus=${corpusid}`; } fetch(new Request(p)).then((resp) => { if (!resp.ok) { throw new Error(`sadness stalks the land in ${resp.status} ways`); } return resp.json(); }).then((payload) => { this.querySelector(".linetext").innerText = payload.Text; this.querySelector(".linetext").setAttribute("data-source", payload.Source.Name); this.originalText = payload.Text; const source = payload.Source // hacks for gutenberg name let sourceName = source.Name; const space = sourceName.indexOf(' '); if (space > 0) { sourceName = sourceName.slice(space); } this.querySelector(".source").innerText = sourceName; }); } } class PoemLines extends HTMLDivElement { constructor() { super(); } connectedCallback() { this.init(); addEventListener("beforeunload", (e) => { if (this.querySelectorAll("div.line:not(.unpinned)").length > 0) { e.preventDefault(); } }); this.addEventListener("dragenter", (e) => { e.preventDefault(); $$(".movetarget").forEach((el) => { el.classList.remove("movetarget"); }) const pl = e.target.closest("div.line"); pl.classList.add("movetarget"); }); this.addEventListener("dragover", (e) => { e.preventDefault(); e.dataTransfer.dropEffect = "move"; }); this.addEventListener("drop", (e) => { e.preventDefault(); const pl = e.target.closest("div.line"); pl.classList.remove("movetarget"); const lid = e.dataTransfer.getData("text/plain"); pl.before(document.getElementById(lid)); }); } init() { for (var i = 0; i < initialLines; i++) { this.add(); } } reset() { this.querySelectorAll("*").forEach(invoker("remove")); this.init(); } add() { var ld = document.createElement("div", {is: "poem-line"}); ld.classList.add("line"); // div[is=poem-line] isn't working, idk why. ld.classList.add("unpinned"); this.append(ld); ld.regen(); this.dispatchEvent(reorder); } regenerate() { this.querySelectorAll(".unpinned").forEach(invoker("regen")); } } class SourceText extends HTMLDivElement { constructor() { super(); } edited() { const line = this.parentElement; const text = line.querySelector(".linetext").innerText; const orig = line.originalText; if (text == "" || (text != orig && !orig.includes(text))) { this.update({"Name": "original"}); } } update(source) { if (source.Name.startsWith("pg")) { const sourceName = source.Name.slice(source.Name.indexOf(' ')); this.innerText = sourceName; } else { this.innerText = source.Name; } } } class AboutToggler extends HTMLAnchorElement { constructor() { super(); this.hide = true; this.style.cursor = "pointer"; } connectedCallback() { this.innerText = "about"; this.addEventListener("click", (e) => { if (this.hide) { $("#about").style.display = "block"; } else { $("#about").style.display = "none"; } this.hide = !this.hide; }); } } class ThemeToggler extends HTMLAnchorElement { constructor() { super(); this.addEventListener("click", this.click); this.theme = "dark"; this.innerText = "◑"; this.setAttribute("aria-hidden", "true"); this.style.cursor = "pointer"; } click() { if (this.theme == "light") { this.theme = "dark"; $("body").style.backgroundColor = "black"; $("body").style.backgroundImage = 'url("/bg_dark.gif")'; $("body").style.color = "white"; $(".main").style.backgroundColor = "black"; $("h1").style.backgroundColor = "black"; $(".controls form").style.backgroundColor = "black"; $$("a").forEach((e) => { e.style.color = "white" }); } else { this.theme = "light"; $("body").style.backgroundColor = "white"; $("body").style.backgroundImage = 'url("/bg_light.gif")'; $("body").style.color = "black"; $(".main").style.backgroundColor = "white"; $(".controls form").style.backgroundColor = "white"; $("h1").style.backgroundColor = "white"; $$("a").forEach((e) => { e.style.color = "black" }); } } } class PoemSaver extends HTMLFormElement { // oops, you can't copy an image on ff. connectedCallback() { this.querySelectorAll("input[name=type]").forEach((e) => { e.addEventListener("change", (e) => { if (e.target.value == "image") { this.querySelector("button.copy").setAttribute("disabled", true); } else { this.querySelector("button").removeAttribute("disabled"); } }); }); this.querySelectorAll("button").forEach((e) => { e.addEventListener("click", (e) => { e.target.classList.add("rainbow"); e.target.addEventListener("animationend", () => { e.target.classList.remove("rainbow") }); }); }); const fd = new FormData(this); if (fd.get("type") == "image") { this.querySelector("button.copy").setAttribute("disabled", true); } this.addEventListener("submit", (e) => { e.preventDefault(); const fd = new FormData(e.target); const includeSources = fd.get("sources") == "on"; const saveType = fd.get("type"); const text = this.toText(includeSources); if (e.submitter.innerText == "copy") { if (saveType == "text") { this.copyText(text); } } else { if (saveType == "text") { this.saveText(text); } else { this.saveImage(text); } } }); } toText(includeSources) { var text = ""; var sources = ""; $$(".linetext").forEach((e) => { text += e.innerText + "\n"; sources += e.dataset.source + "\n" }) if (includeSources) { text += "\n\nsources:\n" + sources; } return text; } copyText(text) { navigator.clipboard.writeText(text); } fname() { return `trunkless-poem-${Math.trunc(Date.now()/1000)}` } saveText(text) { const blob = new Blob([text], {type: "text/plain"}); const dlink = document.createElement("a"); dlink.download = this.fname() + ".txt"; dlink.href = window.URL.createObjectURL(blob); dlink.addEventListener("click", (e)=>{e.target.remove()}); dlink.style.display = "none"; $("body").appendChild(dlink); dlink.click(); } saveImage(text) { const toSave = document.createElement("p"); toSave.setAttribute("id", "toSave"); toSave.style.color = "black"; toSave.style["font-size"] = "75%"; toSave.style["max-width"] = "500px"; toSave.innerText = text; $("body").append(toSave); html2canvas(document.querySelector("#toSave")).then((canvas) => { canvas.toBlob((blob) => { const downloadUrl = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = downloadUrl; a.download = this.fname() + ".png"; document.body.appendChild(a); a.click(); URL.revokeObjectURL(downloadUrl); toSave.remove(); }); }); } } const reorder = new CustomEvent("reorder", {bubbles: true}); const edited = new CustomEvent("edited", {bubbles: true}); customElements.define("about-toggler", AboutToggler, { extends: "a" }); customElements.define("poem-saver", PoemSaver, { extends: "form" }); customElements.define("theme-toggler", ThemeToggler, { extends: "a" }); customElements.define("source-text", SourceText, { extends: "div" }); customElements.define("line-remover", LineRemover, { extends: "button" }); customElements.define("line-pinner", LinePinner, { extends: "button" }); customElements.define("line-regenner", LineRegenner, { extends: "button" }); customElements.define("line-editor", LineEditor, { extends: "button" }); customElements.define("line-adder", LineAdder, { extends: "button" }); customElements.define("poem-regenner", PoemRegenner, {extends: "button"}); customElements.define("poem-resetter", PoemResetter, {extends: "button"}); customElements.define("poem-line", PoemLine, {extends: "div"}); customElements.define("poem-lines", PoemLines, {extends: "div"});