390 lines
10 KiB
JavaScript
390 lines
10 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 {
|
|
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("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" });
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class PoemSaver extends HTMLFormElement {
|
|
connectedCallback() {
|
|
this.addEventListener("submit", (e) => {
|
|
e.preventDefault();
|
|
if (e.submitter.innerText == "copy") {
|
|
// TODO
|
|
} else {
|
|
const saveType = this.querySelector("input[name=type]");
|
|
const includeSources = this.querySelector("input[name=sources]").checked;
|
|
if (saveType.value == "text") {
|
|
this.saveText(includeSources);
|
|
} else {
|
|
this.saveImage(includeSources);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
saveText(includeSources) {
|
|
var text = "";
|
|
var sources = "";
|
|
$$(".linetext").forEach((e) => {
|
|
text += e.innerText + "\n";
|
|
sources += e.dataset.source + "\n"
|
|
})
|
|
if (includeSources) {
|
|
text += "\n\nsources:\n" + sources;
|
|
}
|
|
|
|
const blob = new Blob([text], {type: "text/plain"});
|
|
const fname = `trunkless-poem-${Math.trunc(Date.now()/1000)}.txt`
|
|
const dlink = document.createElement("a");
|
|
dlink.download = fname;
|
|
dlink.href = window.URL.createObjectURL(blob);
|
|
dlink.addEventListener("click", (e)=>{e.target.remove()});
|
|
dlink.style.display = "none";
|
|
$("body").appendChild(dlink);
|
|
dlink.click();
|
|
}
|
|
|
|
saveImage(includeSources) {
|
|
console.log(includeSources);
|
|
}
|
|
}
|
|
|
|
|
|
const reorder = new CustomEvent("reorder", {bubbles: true});
|
|
const edited = new CustomEvent("edited", {bubbles: true});
|
|
customElements.define("poem-saver", PoemSaver, { extends: "form" });
|
|
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"});
|