trunkless/assets/main.js
nate smith 4dbb32ed0f stylin
2024-02-19 22:47:49 -08:00

338 lines
9.0 KiB
JavaScript

// 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 SourceShower extends Button {
connectedCallback() {
this.innerText = "?"
this.setAttribute("title", "show source");
}
click() {
this.closest("div.line").querySelector("p[is=source-text]").toggle()
if (this.innerHTML.includes("strong")) {
this.innerHTML = "?";
this.setAttribute("title", "show source");
} else {
this.innerHTML = "<strong>?</strong>";
this.setAttribute("title", "hide source");
}
}
}
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.innerText = "pin";
this.setAttribute("title", "pin line in place");
}
click() {
const l = this.closest("div.line");
l.classList.toggle("unpinned");
if (l.classList.contains("unpinned")) {
this.innerText = "pin";
this.setAttribute("title", "pin line in place");
} else {
this.innerHTML = "<strong>pin</strong>";
this.setAttribute("title", "unpin line");
}
}
}
class LineUpper extends Button {
connectedCallback() {
this.setAttribute("title", "move line up");
}
click() {
const l = this.closest("div.line");
const s = l.previousElementSibling;
s.before(l);
this.dispatchEvent(reorder);
}
checkDisabled() {
const l = this.closest("div.line");
if (l.previousElementSibling == null) {
this.setAttribute("disabled", "yeah");
} else {
this.removeAttribute("disabled");
}
}
}
class LineEditor extends Button {
connectedCallback() {
this.setAttribute("title", "edit line text");
this.span = this.closest(".line").querySelector("span.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();
})
}
done() {
this.setAttribute("title", "edit line text");
this.editing = false;
this.innerText = "✎";
this.span.innerText = this.i.value;
this.f.remove();
this.span.setAttribute("style", "display:inline");
this.dispatchEvent(edited);
}
click() {
if (this.editing) {
this.done();
return;
}
this.editing = true;
this.setAttribute("title", "finish editing");
this.innerHTML = "<strong>✓</strong>";
this.span.setAttribute("style", "display:none");
this.i.value = this.span.innerText;
this.parentElement.appendChild(this.f);
this.i.focus();
}
}
class LineInput extends HTMLInputElement {
constructor() {
super();
this.setAttribute("type", "text");
}
}
class LineDowner extends Button {
connectedCallback() {
this.setAttribute("title", "move line down");
}
click() {
const l = this.closest("div.line");
const s = l.nextElementSibling;
s.after(l);
this.dispatchEvent(reorder);
}
checkDisabled() {
const l = this.closest("div.line");
if (l.nextElementSibling == null) {
this.setAttribute("disabled", "yeah");
} else {
this.removeAttribute("disabled");
}
}
}
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 {
click() {
$("div[is=poem-lines]").reset();
}
}
class PoemLine extends HTMLDivElement {
constructor() {
super();
this.ltp = $("#linetmpl");
this.addEventListener("edited", (e) => {
e.preventDefault();
this.querySelector("p[is=source-text]").edited();
});
}
connectedCallback() {
if (this.connected) {
return;
}
this.appendChild(this.ltp.content.cloneNode(true));
this.connected = true;
}
get source() {
return this.querySelector("span.linetext").dataset.source;
}
regen() {
fetch(new Request("/line")).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;
this.querySelector("p[is=source-text]").update(payload.Source);
});
}
}
class PoemLines extends HTMLDivElement {
constructor() {
super();
this.addEventListener("reorder", () => {
this.querySelectorAll("button[is=line-downer]").forEach(invoker("checkDisabled"));
this.querySelectorAll("button[is=line-upper]").forEach(invoker("checkDisabled"));
});
}
connectedCallback() {
this.init();
addEventListener("beforeunload", (e) => {
if (this.querySelectorAll("div.line:not(.unpinned)").length > 0) {
e.preventDefault();
}
});
}
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 HTMLParagraphElement {
constructor() {
super();
}
connectedCallback() {
this.setAttribute("style", `display: none;`);
}
toggle() {
if (this.style.display == "none") {
this.style.display = "block";
} else {
this.style.display = "none";
}
}
edited() {
const line = this.parentElement;
const text = line.querySelector("span.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 fullGutID = source.Name.split(" ", 2)[0];
const sourceName = source.Name.slice(source.Name.indexOf(' '));
const gutID = fullGutID.match(/^pg(.+).txt$/)[1];
const url = `https://www.gutenberg.org/cache/epub/${gutID}/pg${gutID}.txt`;
this.innerHTML = `
<span>${sourceName}</span> (<a href="${url}">${fullGutID}</a>)`
} else {
this.innerHTML = `<span>${source.Name}</span>`;
}
}
}
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";
$$("a").forEach((e) => { e.style.color = "white" });
$$("span.linetext").forEach((e) => { e.style.backgroundColor = "black" });
} else {
this.theme = "light";
$("body").style.backgroundColor = "white";
$("body").style.backgroundImage = 'url("/bg_light.gif")';
$("body").style.color = "black";
$$("a").forEach((e) => { e.style.color = "black" });
$$("span.linetext").forEach((e) => { e.style.backgroundColor = "white" });
}
}
}
const reorder = new CustomEvent("reorder", {bubbles: true});
const edited = new CustomEvent("edited", {bubbles: true});
customElements.define("theme-toggler", ThemeToggler, { extends: "a" });
customElements.define("source-text", SourceText, { extends: "p" });
customElements.define("source-shower", SourceShower, { extends: "button" });
customElements.define("line-remover", LineRemover, { extends: "button" });
customElements.define("line-pinner", LinePinner, { extends: "button" });
customElements.define("line-editor", LineEditor, { extends: "button" });
customElements.define("line-upper", LineUpper, { extends: "button" });
customElements.define("line-downer", LineDowner, { 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"});