From e122d86fb700c1119c655df9d23df1cc94c78aa8 Mon Sep 17 00:00:00 2001 From: towards-a-new-leftypol Date: Tue, 27 Feb 2024 02:58:55 +0000 Subject: [PATCH] WIP: slowly but surely --- js/lcn/classes.js | 884 +++++++++++++++++++++++----------------------- js/lcn/utils.js | 40 ++- 2 files changed, 466 insertions(+), 458 deletions(-) diff --git a/js/lcn/classes.js b/js/lcn/classes.js index 255a09d5..8a48ddac 100644 --- a/js/lcn/classes.js +++ b/js/lcn/classes.js @@ -4,7 +4,6 @@ */ globalThis.LCNSite = class LCNSite { - static INSTANCE = null; static "createAbortable" () { const obj = { "abort": null, "controller": null, "signal": null } @@ -32,54 +31,60 @@ globalThis.LCNSite = class LCNSite { } return null + }; + + static "constructor" () { + this._isModerator = document.body.classList.contains("is-moderator"); + + this._isThreadPage = document.body.classList.contains("active-thread"); + this._isBoardPage = document.body.classList.contains("active-board"); + this._isCatalogPage = document.body.classList.contains("active-catalog"); + + this._isModPage = location.pathname == "/mod.php"; + this._isModRecentsPage = this._isModPage && (location.search == "?/recent" || location.search.startsWith("?/recent/")); + this._isModReportsPage = this._isModPage && (location.search == "?/reports" || location.search.startsWith("?/reports/")); + this._isModLogPage = this._isModPage && (location.search == "?/log" || location.search.startsWith("?/log/")); + this._unseen = 0; + this._pageTitle = document.title; + this._doTitleUpdate = () => { + document.title = (this._unseen > 0 ? `(${this._unseen}) ` : "") + this._pageTitle; + }; + this._favicon = document.querySelector("head > link[rel=\"shortcut icon\"]"); + this._generatedStyle = null; } - #isModerator = document.body.classList.contains("is-moderator"); - #isThreadPage = document.body.classList.contains("active-thread"); - #isBoardPage = document.body.classList.contains("active-board"); - #isCatalogPage = document.body.classList.contains("active-catalog"); + "isModerator" () { return this._isModerator; } + "isThreadPage" () { return this._isThreadPage; } + "isBoardPage" () { return this._isBoardPage; } + "isCatalogPage" () { return this._isCatalogPage; } - #isModPage = location.pathname == "/mod.php"; - #isModRecentsPage = this.#isModPage && (location.search == "?/recent" || location.search.startsWith("?/recent/")); - #isModReportsPage = this.#isModPage && (location.search == "?/reports" || location.search.startsWith("?/reports/")); - #isModLogPage = this.#isModPage && (location.search == "?/log" || location.search.startsWith("?/log/")); + "isModPage" () { return this._isModPage; } + "isModRecentsPage" () { return this._isModRecentsPage; } + "isModReportsPage" () { return this._isModReportsPage; } + "isModLogPage" () { return this._isModLogPage; } - "isModerator" () { return this.#isModerator; } - "isThreadPage" () { return this.#isThreadPage; } - "isBoardPage" () { return this.#isBoardPage; } - "isCatalogPage" () { return this.#isCatalogPage; } - - "isModPage" () { return this.#isModPage; } - "isModRecentsPage" () { return this.#isModRecentsPage; } - "isModReportsPage" () { return this.#isModReportsPage; } - "isModLogPage" () { return this.#isModLogPage; } - - #unseen = 0; - "getUnseen" () { return this.#unseen; } - "clearUnseen" () { if (this.#unseen != 0) { this.setUnseen(0); } } + "getUnseen" () { return this._unseen; } + "clearUnseen" () { if (this._unseen != 0) { this.setUnseen(0); } } "setUnseen" (int) { const bool = !!int - if (bool != !!this.#unseen) { + if (bool != !!this._unseen) { this.setFaviconType(bool ? "reply" : null) } - this.#unseen = int - this.#doTitleUpdate() + this._unseen = int + this._doTitleUpdate() } - #pageTitle = document.title; - "getTitle" () { return this.#pageTitle; } - "setTitle" (title) { this.#pageTitle = title; this.#doTitleUpdate(); } - #doTitleUpdate () { document.title = (this.#unseen > 0 ? `(${this.#unseen}) ` : "") + this.#pageTitle; } + "getTitle" () { return this._pageTitle; } + "setTitle" (title) { this._pageTitle = title; this._doTitleUpdate(); } - #favicon = document.querySelector("head > link[rel=\"shortcut icon\"]"); "setFaviconType" (type=null) { - if (this.#favicon == null) { - this.#favicon = document.createElement("link") - this.#favicon.rel = "shortcut icon" - document.head.appendChild(this.#favicon) + if (this._favicon == null) { + this._favicon = document.createElement("link") + this._favicon.rel = "shortcut icon" + document.head.appendChild(this._favicon) } - this.#favicon.href = `/favicon${type ? "-" + type : ""}.ico` + this._favicon.href = `/favicon${type ? "-" + type : ""}.ico` } "getFloaterLContainer" () { return document.getElementById("bar-bottom-l"); } @@ -87,413 +92,414 @@ globalThis.LCNSite = class LCNSite { "getThreadStatsLContainer" () { return document.getElementById("lcn-threadstats-l"); } "getThreadStatsRContainer" () { return document.getElementById("lcn-threadstats-r"); } - #generatedStyle = null; "writeCSSStyle" (origin, stylesheet) { - if (this.#generatedStyle == null && (this.#generatedStyle = document.querySelector("head > style.generated-css")) == null) { - this.#generatedStyle = document.createElement("style") - this.#generatedStyle.classList.add("generated-css") - document.head.appendChild(this.#generatedStyle) + if (this._generatedStyle == null && (this._generatedStyle = document.querySelector("head > style.generated-css")) == null) { + this._generatedStyle = document.createElement("style") + this._generatedStyle.classList.add("generated-css") + document.head.appendChild(this._generatedStyle) } - this.#generatedStyle.textContent += `${this.#generatedStyle.textContent.length ? "\n\n" : ""}/*** Generated by ${origin} ***/\n${stylesheet}` + this._generatedStyle.textContent += `${this._generatedStyle.textContent.length ? "\n\n" : ""}/*** Generated by ${origin} ***/\n${stylesheet}` } } -globalThis.LCNPostInfo = class LCNPostInfo { - - static nodeAttrib = "$LCNPostInfo"; - static selector = ".post:not(.grid-li)"; - #boardId = null; - #threadId = null; - #postId = null; - #name = null; - #email = null; - #capcode = null; - #flag = null; - #ip = null; - #subject = null; - #createdAt = null; - - #parent = null; - #isThread = false; - #isReply = false; - #isLocked = false; - #isSticky = false; - - static "assign" (post) { return post[this.nodeAttrib] ?? (post[this.nodeAttrib] = this.from(post)); } - static "from" (post) { - assert.ok(post.classList.contains("post"), "Arty must be expected Element.") - const inst = new this() - const intro = post.querySelector(".intro") - const link = intro.querySelector(".post_no:not([id])").href.split("/").reverse() - inst.#postId = link[0].slice(link[0].indexOf("#q") + 2) - inst.#threadId = link[0].slice(0, link[0].indexOf(".")) - inst.#boardId = link[2] - inst.#isThread = post.classList.contains("op") - inst.#isReply = !inst.#isThread - - inst.#subject = intro.querySelector(".subject")?.innerText ?? null - inst.#name = intro.querySelector(".name")?.innerText ?? null - inst.#email = intro.querySelector(".email")?.href.slice(7) ?? null - inst.#flag = intro.querySelector(".flag")?.src.split("/").reverse()[0].slice(0, -4) ?? null - - inst.#capcode = intro.querySelector(".capcode")?.innerText ?? null - inst.#ip = intro.querySelector(".ip-link")?.innerText ?? null - inst.#createdAt = new Date(intro.querySelector("time[datetime]").dateTime ?? NaN) - - inst.#isSticky = !!intro.querySelector("i.fa-thumb-tack") - inst.#isLocked = !!intro.querySelector("i.fa-lock") - - return inst - } - - "getParent" () { return this.#parent; } - "__setParent" (inst) { return this.#parent = inst; } - - "getBoardId" () { return this.#boardId; } - "getThreadId" () { return this.#threadId; } - "getPostId" () { return this.#postId; } - "getHref" () { return `/${this.boardId}/res/${this.threadId}.html#q${this.postId}`; } - - "getName" () { return this.#name; } - "getEmail" () { return this.#email; } - "getIP" () { return this.#ip; } - "getCapcode" () { return this.#capcode; } - "getSubject" () { return this.#subject; } - "getCreatedAt" () { return this.#createdAt; } - - "isSticky" () { return this.#isSticky; } - "isLocked" () { return this.#isLocked; } - "isThread" () { return this.#isThread; } - "isReply" () { return this.#isReply; } - - "is" (info) { - assert.ok(info, "Must be LCNPost.") - return this.getBoardId() == info.getBoardId() && this.getPostId() == info.getPostId() - } - -} - -globalThis.LCNPost = class LCNPost { - - static nodeAttrib = "$LCNPost"; - static selector = ".post:not(.grid-li)"; - #parent = null; - #post = null; - #info = null; - #ipLink = null; - #controls = null; - #customControlsSeperatorNode = null; - - static "assign" (post) { return post[this.nodeAttrib] ?? (post[this.nodeAttrib] = this.from(post)); } - static "from" (post) { return new this(post); } - - "constructor" (post) { - assert.ok(post.classList.contains("post"), "Arty must be expected Element.") - const intro = post.querySelector(".intro") - this.#post = post - this.#info = LCNPostInfo.assign(post) - this.#ipLink = intro.querySelector(".ip-link") - this.#controls = Array.prototype.at.apply(post.querySelectorAll(".controls"), [ -1 ]) - - assert.equal(this.#info.getParent(), null, "Info should not have parent.") - this.#info.__setParent(this) - } - - "jQuery" () { return $(this.#post); } - "trigger" (event_id, data=null) { $(this.#post).trigger(event_id, [ data ]); } - - "getElement" () { return this.#post; } - "getInfo" () { return this.#info; } - - "getIPLink" () { return this.#ipLink; } - "setIP" (ip) { this.#ipLink.innerText = ip; } - - "getParent" () { return this.#parent; } - "__setParent" (inst) { return this.#parent = inst; } - - static #NBSP = String.fromCharCode(160); - "addCustomControl" (obj) { - if (LCNSite.INSTANCE.isModerator()) { - const link = document.createElement("a") - link.innerText = `[${obj.btn}]` - link.title = obj.tooltip - - if (typeof obj.href == "string") { - link.href = obj.href - link.referrerPolicy = "no-referrer" - } else if (obj.onClick != undefined) { - link.style.cursor = "pointer" - link.addEventListener("click", e => { e.preventDefault(); obj.onClick(this); }) - } - - if (this.#customControlsSeperatorNode == null) { - this.#controls.insertBefore(this.#customControlsSeperatorNode = new Text(`${this.constructor.#NBSP}-${this.constructor.#NBSP}`), this.#controls.firstElementChild) - } else { - this.#controls.insertBefore(new Text(this.constructor.#NBSP), this.#customControlsSeperatorNode) - } - - this.#controls.insertBefore(link, this.#customControlsSeperatorNode) - } - } - -} - -globalThis.LCNThread = class LCNThread { - - static nodeAttrib = "$LCNThread"; - static selector = ".thread:not(.grid-li)"; - #element = null; - #parent = null; - #op = null; - - static "assign" (thread) { return thread[this.nodeAttrib] ?? (thread[this.nodeAttrib] = this.from(thread)); } - static "from" (thread) { return new this(thread); } - - "constructor" (thread) { - assert.ok(thread.classList.contains("thread"), "Arty must be expected Element.") - this.#element = thread - this.#op = LCNPost.assign(this.#element.querySelector(".post.op")) - - //assert.equal(this.#op.getParent(), null, "Op should not have parent.") - this.#op.__setParent(this) - } - - "getElement" () { return this.#element; } - "getContent" () { return this.#op; } - "getPosts" () { return Array.prototype.map.apply(this.#element.querySelectorAll(".post"), [ el => LCNPost.assign(el) ]); } - "getReplies" () { return Array.prototype.map.apply(this.#element.querySelectorAll(".post:not(.op)"), [ el => LCNPost.assign(el) ]); } - - "getParent" () { return this.#parent; } - "__setParent" (inst) { return this.#parent = inst; } -} - - -globalThis.LCNPostContainer = class LCNPostContainer { - - static nodeAttrib = "$LCNPostContainer"; - static selector = ".postcontainer"; - #parent = null; - #element = null; - #content = null; - #postId = null; - #boardId = null; - - static "assign" (container) { return container[this.nodeAttrib] ?? (container[this.nodeAttrib] = this.from(container)); } - static "from" (container) { return new this(container); } - - "constructor" (container) { - assert.ok(container.classList.contains("postcontainer"), "Arty must be expected Element.") - const child = container.querySelector(".thread, .post") - this.#element = container - this.#content = child.classList.contains("thread") ? LCNThread.assign(child) : LCNPost.assign(child) - this.#boardId = container.dataset.board - this.#postId = container.id.slice(2) - - assert.equal(this.#content.getParent(), null, "Content should not have parent.") - this.#content.__setParent(this) - } - - "getElement" () { return this.#element; } - "getContent" () { return this.#content; } - "getBoardId" () { return this.#boardId; } - "getPostId" () { return this.#postId; } - - "getParent" () { return this.#parent; } - "__setParent" (inst) { return this.#parent = inst; } - -} - -globalThis.LCNPostWrapper = class LCNPostWrapper { - - static nodeAttrib = "$LCNPostWrapper"; - static selector = ".post-wrapper"; - #wrapper = null; - #eitaLink = null; - #eitaId = null; - #eitaHref = null - #content = null; - - static "assign" (wrapper) { return wrapper[this.nodeAttrib] ?? (wrapper[this.nodeAttrib] = this.from(wrapper)); } - static "from" (wrapper) { return new this(wrapper); } - - "constructor" (wrapper) { - assert.ok(wrapper.classList.contains("post-wrapper"), "Arty must be expected Element.") - this.#wrapper = wrapper - this.#eitaLink = wrapper.querySelector(".eita-link") - this.#eitaId = this.#eitaLink.id - this.#eitaHref = this.#eitaLink.href - void Array.prototype.find.apply(wrapper.children, [ - el => { - if (el.classList.contains("thread")) { - return this.#content = LCNThread.assign(el) - } else if (el.classList.contains("postcontainer")) { - return this.#content = LCNPostContainer.assign(el) - } - } - ]) - - assert.ok(this.#content, "Wrapper should contain content.") - assert.equal(this.#content.getParent(), null, "Content should not have parent.") - this.#content.__setParent(this) - } - - "getPost" () { - const post = this.getContent().getContent() - assert.ok(post instanceof LCNPost, "Post should be LCNPost.") - return post - } - - "getElement" () { return this.#wrapper; } - "getContent" () { return this.#content; } - "getEitaId" () { return this.#eitaId; } - "getEitaHref" () { return this.#eitaHref; } - "getEitaLink" () { return this.#eitaLink; } - -} - -globalThis.LCNSetting = class LCNSetting { - #id = null; - #eventId = null; - #label = null; - #value = null; - #valueDefault = null; - - static "build" (id) { return new this(id); } - - "constructor" (id) { - this.#id = id; - this.#eventId = `lcnsetting::${this.#id}` - } - - #getValue () { - const v = localStorage.getItem(this.#id) - if (v != null) { - return this.__builtinValueImporter(v) - } else { - return this.#valueDefault - } - } - - "getValue" () { return this.#value ?? (this.#value = this.#getValue()); } - "setValue" (v) { - if (this.#value !== v) { - this.#value = v - localStorage.setItem(this.#id, this.__builtinValueExporter(this.#value)) - setTimeout(() => $(document).trigger(`${this.#eventId}::change`, [ v, this ]), 1) - } - } - - "getLabel" () { return this.#label; } - "setLabel" (label) { this.#label = label; return this; } - - "getDefaultValue" () { return this.#valueDefault; } - "setDefaultValue" (vd) { this.#valueDefault = vd; return this; } - - "onChange" (fn) { $(document).on(`${this.#eventId}::change`, (_,v,i) => fn(v, i)); } - __setIdPrefix (prefix) { this.#id = `${prefix}_${this.#id}`; } -} - -globalThis.LCNToggleSetting = class LCNToggleSetting extends LCNSetting { - __builtinValueImporter (v) { return v == "1"; } - __builtinValueExporter (v) { return v ? "1" : ""; } - __builtinDOMConstructor () { - const div = document.createElement("div") - const chk = document.createElement("input") - const txt = document.createElement("label") - txt.innerText = this.getLabel() - chk.type = "checkbox" - chk.checked = this.getValue() - chk.addEventListener("click", e => { - e.preventDefault(); - this.setValue(!this.getValue()) - }) - this.onChange(v => chk.checked = v) - - div.appendChild(chk) - div.appendChild(txt) - return div - } -} - -globalThis.LCNSettingsSubcategory = class LCNSettingsSubcategory { - - #tab_id = null; - #id = null; - - #fieldset = null; - #legend = null; - #label = null; - - static "for" (tab_id, id) { - const domid = `lcnssc_${tab_id}_${id}` - const inst = document.getElementById(domid)?.$LCNSettingsSubcategory - if (inst == null) { - const fieldset = document.createElement("fieldset") - const legend = document.createElement("legend") - fieldset.id = domid - fieldset.appendChild(legend) - - // XXX: extend_tab only takes a string so this hacky workaround is used to let us use the regular dom api - Options.extend_tab(tab_id, ``) - const div = document.getElementById(`__${domid}`)?.parentElement - assert.ok(div) - - div.replaceChildren(fieldset) - return new this(tab_id, id, fieldset) - } else { - return inst - } - } - - "constructor" (tab_id, id, fieldset) { - this.#tab_id = tab_id - this.#id = id - this.#fieldset = fieldset - this.#legend = this.#fieldset.querySelector("legend") - this.#fieldset.$LCNSettingsSubcategory = this - } - - "getLabel" () { return this.#label; } - "setLabel" (label) { this.#legend.innerText = this.#label = label; return this; } - "addSetting" (setting) { - assert.ok(setting instanceof LCNSetting) - setting.__setIdPrefix(`lcnsetting_${this.#tab_id}_${this.#id}`) - if (setting.__builtinDOMConstructor != null) { - const div = setting.__builtinDOMConstructor() - div.classList.add("lcn-setting-entry") - this.#fieldset.appendChild(div) - } - - return this - } - -} - -$().ready(() => { - LCNSite.INSTANCE = new LCNSite(); - - for (const clazz of [ LCNPost, LCNPostInfo, LCNThread, LCNPostContainer, LCNPostWrapper ]) { - clazz.allNodes = (node=document) => node.querySelectorAll(clazz.selector) - clazz.all = (node=document) => Array.prototype.map.apply(clazz.allNodes(node), [ elem => clazz.assign(elem) ]); - clazz.clear = (node=document) => Array.prototype.forEach.apply(clazz.allNodes(node), [ elem => elem[clazz.nodeAttrib] = null ]) - clazz.forEach = (fn, node=document) => clazz.allNodes(node).forEach(elem => fn(clazz.assign(elem))) - clazz.filter = (fn, node=document) => clazz.all(node).filter(fn) - clazz.find = fn => clazz.all().find(fn) - clazz.first = (node=document) => clazz.assign(node.querySelector(clazz.selector)) - clazz.last = (node=document) => clazz.assign(Array.prototype.at.apply(clazz.allNodes(node), [ -1 ])) - } - - // XXX: May be a cleaner way to do this but this should be fine for now. - for (const clazz of [ LCNPostContainer, LCNPostWrapper, LCNThread, LCNPost ]) { void clazz.all(); } - $(document).on("new_post", (e, post) => { - if (LCNSite.INSTANCE.isModRecentsPage()) { - void LCNPostWrapper.all() - } else { - void LCNPostContainer.all() - } - }) - - $(window).on("focus", () => LCNSite.INSTANCE.clearUnseen()) - $(document.body).on("mousemove", () => LCNSite.INSTANCE.clearUnseen()) -}) +LCNSite.INSTANCE = null; + +// globalThis.LCNPostInfo = class LCNPostInfo { +// +// static nodeAttrib = "$LCNPostInfo"; +// static selector = ".post:not(.grid-li)"; +// #boardId = null; +// #threadId = null; +// #postId = null; +// #name = null; +// #email = null; +// #capcode = null; +// #flag = null; +// #ip = null; +// #subject = null; +// #createdAt = null; +// +// #parent = null; +// #isThread = false; +// #isReply = false; +// #isLocked = false; +// #isSticky = false; +// +// static "assign" (post) { return post[this.nodeAttrib] ?? (post[this.nodeAttrib] = this.from(post)); } +// static "from" (post) { +// assert.ok(post.classList.contains("post"), "Arty must be expected Element.") +// const inst = new this() +// const intro = post.querySelector(".intro") +// const link = intro.querySelector(".post_no:not([id])").href.split("/").reverse() +// inst.#postId = link[0].slice(link[0].indexOf("#q") + 2) +// inst.#threadId = link[0].slice(0, link[0].indexOf(".")) +// inst.#boardId = link[2] +// inst.#isThread = post.classList.contains("op") +// inst.#isReply = !inst.#isThread +// +// inst.#subject = intro.querySelector(".subject")?.innerText ?? null +// inst.#name = intro.querySelector(".name")?.innerText ?? null +// inst.#email = intro.querySelector(".email")?.href.slice(7) ?? null +// inst.#flag = intro.querySelector(".flag")?.src.split("/").reverse()[0].slice(0, -4) ?? null +// +// inst.#capcode = intro.querySelector(".capcode")?.innerText ?? null +// inst.#ip = intro.querySelector(".ip-link")?.innerText ?? null +// inst.#createdAt = new Date(intro.querySelector("time[datetime]").dateTime ?? NaN) +// +// inst.#isSticky = !!intro.querySelector("i.fa-thumb-tack") +// inst.#isLocked = !!intro.querySelector("i.fa-lock") +// +// return inst +// } +// +// "getParent" () { return this.#parent; } +// "__setParent" (inst) { return this.#parent = inst; } +// +// "getBoardId" () { return this.#boardId; } +// "getThreadId" () { return this.#threadId; } +// "getPostId" () { return this.#postId; } +// "getHref" () { return `/${this.boardId}/res/${this.threadId}.html#q${this.postId}`; } +// +// "getName" () { return this.#name; } +// "getEmail" () { return this.#email; } +// "getIP" () { return this.#ip; } +// "getCapcode" () { return this.#capcode; } +// "getSubject" () { return this.#subject; } +// "getCreatedAt" () { return this.#createdAt; } +// +// "isSticky" () { return this.#isSticky; } +// "isLocked" () { return this.#isLocked; } +// "isThread" () { return this.#isThread; } +// "isReply" () { return this.#isReply; } +// +// "is" (info) { +// assert.ok(info, "Must be LCNPost.") +// return this.getBoardId() == info.getBoardId() && this.getPostId() == info.getPostId() +// } +// +// } +// +// globalThis.LCNPost = class LCNPost { +// +// static nodeAttrib = "$LCNPost"; +// static selector = ".post:not(.grid-li)"; +// #parent = null; +// #post = null; +// #info = null; +// #ipLink = null; +// #controls = null; +// #customControlsSeperatorNode = null; +// +// static "assign" (post) { return post[this.nodeAttrib] ?? (post[this.nodeAttrib] = this.from(post)); } +// static "from" (post) { return new this(post); } +// +// "constructor" (post) { +// assert.ok(post.classList.contains("post"), "Arty must be expected Element.") +// const intro = post.querySelector(".intro") +// this.#post = post +// this.#info = LCNPostInfo.assign(post) +// this.#ipLink = intro.querySelector(".ip-link") +// this.#controls = Array.prototype.at.apply(post.querySelectorAll(".controls"), [ -1 ]) +// +// assert.equal(this.#info.getParent(), null, "Info should not have parent.") +// this.#info.__setParent(this) +// } +// +// "jQuery" () { return $(this.#post); } +// "trigger" (event_id, data=null) { $(this.#post).trigger(event_id, [ data ]); } +// +// "getElement" () { return this.#post; } +// "getInfo" () { return this.#info; } +// +// "getIPLink" () { return this.#ipLink; } +// "setIP" (ip) { this.#ipLink.innerText = ip; } +// +// "getParent" () { return this.#parent; } +// "__setParent" (inst) { return this.#parent = inst; } +// +// static #NBSP = String.fromCharCode(160); +// "addCustomControl" (obj) { +// if (LCNSite.INSTANCE.isModerator()) { +// const link = document.createElement("a") +// link.innerText = `[${obj.btn}]` +// link.title = obj.tooltip +// +// if (typeof obj.href == "string") { +// link.href = obj.href +// link.referrerPolicy = "no-referrer" +// } else if (obj.onClick != undefined) { +// link.style.cursor = "pointer" +// link.addEventListener("click", e => { e.preventDefault(); obj.onClick(this); }) +// } +// +// if (this.#customControlsSeperatorNode == null) { +// this.#controls.insertBefore(this.#customControlsSeperatorNode = new Text(`${this.constructor.#NBSP}-${this.constructor.#NBSP}`), this.#controls.firstElementChild) +// } else { +// this.#controls.insertBefore(new Text(this.constructor.#NBSP), this.#customControlsSeperatorNode) +// } +// +// this.#controls.insertBefore(link, this.#customControlsSeperatorNode) +// } +// } +// +// } +// +// globalThis.LCNThread = class LCNThread { +// +// static nodeAttrib = "$LCNThread"; +// static selector = ".thread:not(.grid-li)"; +// #element = null; +// #parent = null; +// #op = null; +// +// static "assign" (thread) { return thread[this.nodeAttrib] ?? (thread[this.nodeAttrib] = this.from(thread)); } +// static "from" (thread) { return new this(thread); } +// +// "constructor" (thread) { +// assert.ok(thread.classList.contains("thread"), "Arty must be expected Element.") +// this.#element = thread +// this.#op = LCNPost.assign(this.#element.querySelector(".post.op")) +// +// //assert.equal(this.#op.getParent(), null, "Op should not have parent.") +// this.#op.__setParent(this) +// } +// +// "getElement" () { return this.#element; } +// "getContent" () { return this.#op; } +// "getPosts" () { return Array.prototype.map.apply(this.#element.querySelectorAll(".post"), [ el => LCNPost.assign(el) ]); } +// "getReplies" () { return Array.prototype.map.apply(this.#element.querySelectorAll(".post:not(.op)"), [ el => LCNPost.assign(el) ]); } +// +// "getParent" () { return this.#parent; } +// "__setParent" (inst) { return this.#parent = inst; } +// } +// +// +// globalThis.LCNPostContainer = class LCNPostContainer { +// +// static nodeAttrib = "$LCNPostContainer"; +// static selector = ".postcontainer"; +// #parent = null; +// #element = null; +// #content = null; +// #postId = null; +// #boardId = null; +// +// static "assign" (container) { return container[this.nodeAttrib] ?? (container[this.nodeAttrib] = this.from(container)); } +// static "from" (container) { return new this(container); } +// +// "constructor" (container) { +// assert.ok(container.classList.contains("postcontainer"), "Arty must be expected Element.") +// const child = container.querySelector(".thread, .post") +// this.#element = container +// this.#content = child.classList.contains("thread") ? LCNThread.assign(child) : LCNPost.assign(child) +// this.#boardId = container.dataset.board +// this.#postId = container.id.slice(2) +// +// assert.equal(this.#content.getParent(), null, "Content should not have parent.") +// this.#content.__setParent(this) +// } +// +// "getElement" () { return this.#element; } +// "getContent" () { return this.#content; } +// "getBoardId" () { return this.#boardId; } +// "getPostId" () { return this.#postId; } +// +// "getParent" () { return this.#parent; } +// "__setParent" (inst) { return this.#parent = inst; } +// +// } +// +// globalThis.LCNPostWrapper = class LCNPostWrapper { +// +// static nodeAttrib = "$LCNPostWrapper"; +// static selector = ".post-wrapper"; +// #wrapper = null; +// #eitaLink = null; +// #eitaId = null; +// #eitaHref = null +// #content = null; +// +// static "assign" (wrapper) { return wrapper[this.nodeAttrib] ?? (wrapper[this.nodeAttrib] = this.from(wrapper)); } +// static "from" (wrapper) { return new this(wrapper); } +// +// "constructor" (wrapper) { +// assert.ok(wrapper.classList.contains("post-wrapper"), "Arty must be expected Element.") +// this.#wrapper = wrapper +// this.#eitaLink = wrapper.querySelector(".eita-link") +// this.#eitaId = this.#eitaLink.id +// this.#eitaHref = this.#eitaLink.href +// void Array.prototype.find.apply(wrapper.children, [ +// el => { +// if (el.classList.contains("thread")) { +// return this.#content = LCNThread.assign(el) +// } else if (el.classList.contains("postcontainer")) { +// return this.#content = LCNPostContainer.assign(el) +// } +// } +// ]) +// +// assert.ok(this.#content, "Wrapper should contain content.") +// assert.equal(this.#content.getParent(), null, "Content should not have parent.") +// this.#content.__setParent(this) +// } +// +// "getPost" () { +// const post = this.getContent().getContent() +// assert.ok(post instanceof LCNPost, "Post should be LCNPost.") +// return post +// } +// +// "getElement" () { return this.#wrapper; } +// "getContent" () { return this.#content; } +// "getEitaId" () { return this.#eitaId; } +// "getEitaHref" () { return this.#eitaHref; } +// "getEitaLink" () { return this.#eitaLink; } +// +// } +// +// globalThis.LCNSetting = class LCNSetting { +// #id = null; +// #eventId = null; +// #label = null; +// #value = null; +// #valueDefault = null; +// +// static "build" (id) { return new this(id); } +// +// "constructor" (id) { +// this.#id = id; +// this.#eventId = `lcnsetting::${this.#id}` +// } +// +// #getValue () { +// const v = localStorage.getItem(this.#id) +// if (v != null) { +// return this.__builtinValueImporter(v) +// } else { +// return this.#valueDefault +// } +// } +// +// "getValue" () { return this.#value ?? (this.#value = this.#getValue()); } +// "setValue" (v) { +// if (this.#value !== v) { +// this.#value = v +// localStorage.setItem(this.#id, this.__builtinValueExporter(this.#value)) +// setTimeout(() => $(document).trigger(`${this.#eventId}::change`, [ v, this ]), 1) +// } +// } +// +// "getLabel" () { return this.#label; } +// "setLabel" (label) { this.#label = label; return this; } +// +// "getDefaultValue" () { return this.#valueDefault; } +// "setDefaultValue" (vd) { this.#valueDefault = vd; return this; } +// +// "onChange" (fn) { $(document).on(`${this.#eventId}::change`, (_,v,i) => fn(v, i)); } +// __setIdPrefix (prefix) { this.#id = `${prefix}_${this.#id}`; } +// } +// +// globalThis.LCNToggleSetting = class LCNToggleSetting extends LCNSetting { +// __builtinValueImporter (v) { return v == "1"; } +// __builtinValueExporter (v) { return v ? "1" : ""; } +// __builtinDOMConstructor () { +// const div = document.createElement("div") +// const chk = document.createElement("input") +// const txt = document.createElement("label") +// txt.innerText = this.getLabel() +// chk.type = "checkbox" +// chk.checked = this.getValue() +// chk.addEventListener("click", e => { +// e.preventDefault(); +// this.setValue(!this.getValue()) +// }) +// this.onChange(v => chk.checked = v) +// +// div.appendChild(chk) +// div.appendChild(txt) +// return div +// } +// } +// +// globalThis.LCNSettingsSubcategory = class LCNSettingsSubcategory { +// +// #tab_id = null; +// #id = null; +// +// #fieldset = null; +// #legend = null; +// #label = null; +// +// static "for" (tab_id, id) { +// const domid = `lcnssc_${tab_id}_${id}` +// const inst = document.getElementById(domid)?.$LCNSettingsSubcategory +// if (inst == null) { +// const fieldset = document.createElement("fieldset") +// const legend = document.createElement("legend") +// fieldset.id = domid +// fieldset.appendChild(legend) +// +// // XXX: extend_tab only takes a string so this hacky workaround is used to let us use the regular dom api +// Options.extend_tab(tab_id, ``) +// const div = document.getElementById(`__${domid}`)?.parentElement +// assert.ok(div) +// +// div.replaceChildren(fieldset) +// return new this(tab_id, id, fieldset) +// } else { +// return inst +// } +// } +// +// "constructor" (tab_id, id, fieldset) { +// this.#tab_id = tab_id +// this.#id = id +// this.#fieldset = fieldset +// this.#legend = this.#fieldset.querySelector("legend") +// this.#fieldset.$LCNSettingsSubcategory = this +// } +// +// "getLabel" () { return this.#label; } +// "setLabel" (label) { this.#legend.innerText = this.#label = label; return this; } +// "addSetting" (setting) { +// assert.ok(setting instanceof LCNSetting) +// setting.__setIdPrefix(`lcnsetting_${this.#tab_id}_${this.#id}`) +// if (setting.__builtinDOMConstructor != null) { +// const div = setting.__builtinDOMConstructor() +// div.classList.add("lcn-setting-entry") +// this.#fieldset.appendChild(div) +// } +// +// return this +// } +// +// } +// +// $().ready(() => { +// LCNSite.INSTANCE = new LCNSite(); +// +// for (const clazz of [ LCNPost, LCNPostInfo, LCNThread, LCNPostContainer, LCNPostWrapper ]) { +// clazz.allNodes = (node=document) => node.querySelectorAll(clazz.selector) +// clazz.all = (node=document) => Array.prototype.map.apply(clazz.allNodes(node), [ elem => clazz.assign(elem) ]); +// clazz.clear = (node=document) => Array.prototype.forEach.apply(clazz.allNodes(node), [ elem => elem[clazz.nodeAttrib] = null ]) +// clazz.forEach = (fn, node=document) => clazz.allNodes(node).forEach(elem => fn(clazz.assign(elem))) +// clazz.filter = (fn, node=document) => clazz.all(node).filter(fn) +// clazz.find = fn => clazz.all().find(fn) +// clazz.first = (node=document) => clazz.assign(node.querySelector(clazz.selector)) +// clazz.last = (node=document) => clazz.assign(Array.prototype.at.apply(clazz.allNodes(node), [ -1 ])) +// } +// +// // XXX: May be a cleaner way to do this but this should be fine for now. +// for (const clazz of [ LCNPostContainer, LCNPostWrapper, LCNThread, LCNPost ]) { void clazz.all(); } +// $(document).on("new_post", (e, post) => { +// if (LCNSite.INSTANCE.isModRecentsPage()) { +// void LCNPostWrapper.all() +// } else { +// void LCNPostContainer.all() +// } +// }) +// +// $(window).on("focus", () => LCNSite.INSTANCE.clearUnseen()) +// $(document.body).on("mousemove", () => LCNSite.INSTANCE.clearUnseen()) +// }) diff --git a/js/lcn/utils.js b/js/lcn/utils.js index a8b2771b..250f5bf9 100644 --- a/js/lcn/utils.js +++ b/js/lcn/utils.js @@ -6,36 +6,38 @@ const assert = { "equal": (actual, expected, message="No message set") => { if (actual !== expected) { - const err = new Error(`Assertion Failed. ${message}`) + const err = new Error(`Assertion Failed. ${message}`); err.data = { actual, expected} - Error.captureStackTrace?.(err, assert.equal) - debugger - throw err + //Error.captureStackTrace?.(err, assert.equal); + debugger; + throw err; } }, "ok": (actual, message="No message set") => { if (!actual) { - const err = new Error(`Assertion Failed. ${message}`) + const err = new Error(`Assertion Failed. ${message}`); err.data = { actual } - Error.captureStackTrace?.(err, assert.ok) - debugger - throw err + //Error.captureStackTrace?.(err, assert.ok); + debugger; + throw err; } } -} +}; + +if (AbortSignal.any == null) { + AbortSignal.any = (signals) => { + const controller = new AbortController(); + const abortFn = () => { + for (const signal of signals) { + signal.removeEventListener("abort", abortFn); + } + controller.abort(); + } -AbortSignal.any ??= function (signals) { - const controller = new AbortController() - const abortFn = () => { for (const signal of signals) { - signal.removeEventListener("abort", abortFn) + signal.addEventListener("abort", abortFn); } - controller.abort() - } - for (const signal of signals) { - signal.addEventListener("abort", abortFn) + return controller.signal; } - - return controller.signal }