From 12b06cdcdf1a4d92cfe50051e77ebad36098b752 Mon Sep 17 00:00:00 2001 From: towards-a-new-leftypol Date: Tue, 27 Feb 2024 18:28:51 +0000 Subject: [PATCH] class.js seems to load now, idk if i've introduced any bugs though --- js/lcn/classes.js | 768 +++++++++++++++++++++++----------------------- 1 file changed, 392 insertions(+), 376 deletions(-) diff --git a/js/lcn/classes.js b/js/lcn/classes.js index 117bb0f3..3f4e4cfa 100644 --- a/js/lcn/classes.js +++ b/js/lcn/classes.js @@ -3,6 +3,14 @@ * @author jonsmy */ +function cont(value_to_test, fn) { + if (value_to_test != null) { + return fn(value_to_test); + } else { + return null; + } +} + globalThis.LCNSite = class LCNSite { static "createAbortable" () { @@ -105,7 +113,7 @@ globalThis.LCNSite = class LCNSite { } -LCNSite.INSTANCE = null; +globalThis.LCNSite.INSTANCE = null; globalThis.LCNPostInfo = class LCNPostInfo { @@ -128,383 +136,391 @@ globalThis.LCNPostInfo = class LCNPostInfo { } -// 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() -// } -// + static "assign" (post) { + if (post[this.nodeAttrib] == null) { + return post[this.nodeAttrib] = this.from(post); + } else { + return post[this.nodeAttrib]; + } + } + + 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 = cont(intro.querySelector(".subject"), x => x.innerText); + inst._name = cont(intro.querySelector(".name"), x => x.innerText); + inst._email = cont(intro.querySelector(".email"), x => x.href.slice(7)); + inst._flag = cont(intro.querySelector(".flag"), x => x.src.split("/").reverse()[0].slice(0, -4)); + + inst._capcode = cont(intro.querySelector(".capcode"), x => x.innerText); + inst._ip = cont(intro.querySelector(".ip-link"), x => x.innerText); + 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() + } + } LCNPostInfo.nodeAttrib = "$LCNPostInfo"; LCNPostInfo.selector = ".post:not(.grid-li)"; -// 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()) -// }) +globalThis.LCNPost = class LCNPost { + + static "assign" (post) { return post[this.nodeAttrib] || (post[this.nodeAttrib] = this.from(post)); } + static "from" (post) { return new this(post); } + + "constructor" (post) { + this._parent = null; + this._post = null; + this._info = null; + this._ipLink = null; + this._controls = null; + this._customControlsSeperatorNode = null; + + 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; } + + "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.LCNPost.nodeAttrib = "$LCNPost"; +globalThis.LCNPost.selector = ".post:not(.grid-li)"; +globalThis.LCNPost. NBSP = String.fromCharCode(160); + +globalThis.LCNThread = class LCNThread { + + static "assign" (thread) { return thread[this.nodeAttrib] || (thread[this.nodeAttrib] = this.from(thread)); } + static "from" (thread) { return new this(thread); } + + "constructor" (thread) { + this._element = null; + this._parent = null; + this._op = null; + 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.LCNThread.nodeAttrib = "$LCNThread"; +globalThis.LCNThread.selector = ".thread:not(.grid-li)"; + + +globalThis.LCNPostContainer = class LCNPostContainer { + + static "assign" (container) { return container[this.nodeAttrib] || (container[this.nodeAttrib] = this.from(container)); } + static "from" (container) { return new this(container); } + + "constructor" (container) { + this._parent = null; + this._element = null; + this._content = null; + this._postId = null; + this._boardId = null; + + 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.LCNPostContainer.nodeAttrib = "$LCNPostContainer"; +globalThis.LCNPostContainer.selector = ".postcontainer"; + + +globalThis.LCNPostWrapper = class LCNPostWrapper { + + static "assign" (wrapper) { return wrapper[this.nodeAttrib] || (wrapper[this.nodeAttrib] = this.from(wrapper)); } + static "from" (wrapper) { return new this(wrapper); } + + "constructor" (wrapper) { + this._wrapper = null; + this._eitaLink = null; + this._eitaId = null; + this._eitaHref = null + this._content = null; + + 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.LCNPostWrapper.nodeAttrib = "$LCNPostWrapper"; +globalThis.LCNPostWrapper.selector = ".post-wrapper"; + + +globalThis.LCNSetting = class LCNSetting { + static "build" (id) { return new this(id); } + + "constructor" (id) { + this._id = null; + this._eventId = null; + this._label = null; + this._value = null; + this._valueDefault = null; + + 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 { + + static "for" (tab_id, id) { + const domid = `lcnssc_${tab_id}_${id}` + const inst = cont(document.getElementById(domid), x => x.$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 = cont(document.getElementById(`__${domid}`), x => x.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()) +})