add supporting changes

This commit is contained in:
Jon 2024-02-25 22:06:24 +00:00 committed by 0
parent 90004305a0
commit 0d8ad0a786
2 changed files with 199 additions and 33 deletions

View File

@ -6,6 +6,34 @@
globalThis.LCNSite = class LCNSite { globalThis.LCNSite = class LCNSite {
static INSTANCE = null; static INSTANCE = null;
static "createAbortable" () {
const obj = { "abort": null, "controller": null, "signal": null }
const setupController = () => {
obj.controller = new AbortController()
obj.signal = obj.controller.signal
}
obj.abort = () => {
obj.controller.abort()
setupController()
}
setupController()
return obj
}
static "getThreadFromPages" (pages, thread_id) {
for (const page of pages) {
for (const thread of page.threads) {
if (thread_id == String(thread.no)) {
return { "page": page.page, ...thread }
}
}
}
return null
}
#isModerator = document.body.classList.contains("is-moderator"); #isModerator = document.body.classList.contains("is-moderator");
#isThreadPage = document.body.classList.contains("active-thread"); #isThreadPage = document.body.classList.contains("active-thread");
#isBoardPage = document.body.classList.contains("active-board"); #isBoardPage = document.body.classList.contains("active-board");
@ -53,10 +81,28 @@ globalThis.LCNSite = class LCNSite {
this.#favicon.href = `/favicon${type ? "-" + type : ""}.ico` this.#favicon.href = `/favicon${type ? "-" + type : ""}.ico`
} }
"getFloaterLContainer" () { return document.getElementById("bar-bottom-l"); }
"getFloaterRContainer" () { return document.getElementById("bar-bottom-r"); }
"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)
}
this.#generatedStyle.textContent += `${this.#generatedStyle.textContent.length ? "\n\n" : ""}/*** Generated by ${origin} ***/\n${stylesheet}`
}
} }
globalThis.LCNPostInfo = class LCNPostInfo { globalThis.LCNPostInfo = class LCNPostInfo {
static nodeAttrib = "$LCNPostInfo";
static selector = ".post:not(.grid-li)";
#boardId = null; #boardId = null;
#threadId = null; #threadId = null;
#postId = null; #postId = null;
@ -74,7 +120,7 @@ globalThis.LCNPostInfo = class LCNPostInfo {
#isLocked = false; #isLocked = false;
#isSticky = false; #isSticky = false;
static "assign" (post) { return post.$LCNPostInfo ?? (post.$LCNPostInfo = this.from(post)); } static "assign" (post) { return post[this.nodeAttrib] ?? (post[this.nodeAttrib] = this.from(post)); }
static "from" (post) { static "from" (post) {
assert.ok(post.classList.contains("post"), "Arty must be expected Element.") assert.ok(post.classList.contains("post"), "Arty must be expected Element.")
const inst = new this() const inst = new this()
@ -130,6 +176,8 @@ globalThis.LCNPostInfo = class LCNPostInfo {
globalThis.LCNPost = class LCNPost { globalThis.LCNPost = class LCNPost {
static nodeAttrib = "$LCNPost";
static selector = ".post:not(.grid-li)";
#parent = null; #parent = null;
#post = null; #post = null;
#info = null; #info = null;
@ -137,7 +185,7 @@ globalThis.LCNPost = class LCNPost {
#controls = null; #controls = null;
#customControlsSeperatorNode = null; #customControlsSeperatorNode = null;
static "assign" (post) { return post.$LCNPost ?? (post.$LCNPost = this.from(post)); } static "assign" (post) { return post[this.nodeAttrib] ?? (post[this.nodeAttrib] = this.from(post)); }
static "from" (post) { return new this(post); } static "from" (post) { return new this(post); }
"constructor" (post) { "constructor" (post) {
@ -146,7 +194,7 @@ globalThis.LCNPost = class LCNPost {
this.#post = post this.#post = post
this.#info = LCNPostInfo.assign(post) this.#info = LCNPostInfo.assign(post)
this.#ipLink = intro.querySelector(".ip-link") this.#ipLink = intro.querySelector(".ip-link")
this.#controls = arrLast(post.querySelectorAll(".controls")) this.#controls = Array.prototype.at.apply(post.querySelectorAll(".controls"), [ -1 ])
assert.equal(this.#info.getParent(), null, "Info should not have parent.") assert.equal(this.#info.getParent(), null, "Info should not have parent.")
this.#info.__setParent(this) this.#info.__setParent(this)
@ -193,26 +241,28 @@ globalThis.LCNPost = class LCNPost {
globalThis.LCNThread = class LCNThread { globalThis.LCNThread = class LCNThread {
static nodeAttrib = "$LCNThread";
static selector = ".thread:not(.grid-li)";
#element = null;
#parent = null; #parent = null;
#thread = null;
#op = null; #op = null;
static "assign" (thread) { return thread.$LCNThread ?? (thread.$LCNThread = this.from(thread)); } static "assign" (thread) { return thread[this.nodeAttrib] ?? (thread[this.nodeAttrib] = this.from(thread)); }
static "from" (thread) { return new this(thread); } static "from" (thread) { return new this(thread); }
"constructor" (thread) { "constructor" (thread) {
assert.ok(thread.classList.contains("thread"), "Arty must be expected Element.") assert.ok(thread.classList.contains("thread"), "Arty must be expected Element.")
this.#thread = thread this.#element = thread
this.#op = LCNPost.assign(this.#thread.querySelector(".post.op")) this.#op = LCNPost.assign(this.#element.querySelector(".post.op"))
assert.equal(this.#op.getParent(), null, "Op should not have parent.") assert.equal(this.#op.getParent(), null, "Op should not have parent.")
this.#op.__setParent(this) this.#op.__setParent(this)
} }
"getElement" () { return this.#thread; } "getElement" () { return this.#element; }
"getContent" () { return this.#op; } "getContent" () { return this.#op; }
"getPosts" () { return Array.prototype.map.apply(this.#thread.querySelectorAll(".post"), [ el => LCNPost.assign(el) ]); } "getPosts" () { return Array.prototype.map.apply(this.#element.querySelectorAll(".post"), [ el => LCNPost.assign(el) ]); }
"getReplies" () { return Array.prototype.map.apply(this.#thread.querySelectorAll(".post:not(.op)"), [ el => LCNPost.assign(el) ]); } "getReplies" () { return Array.prototype.map.apply(this.#element.querySelectorAll(".post:not(.op)"), [ el => LCNPost.assign(el) ]); }
"getParent" () { return this.#parent; } "getParent" () { return this.#parent; }
"__setParent" (inst) { return this.#parent = inst; } "__setParent" (inst) { return this.#parent = inst; }
@ -221,19 +271,21 @@ globalThis.LCNThread = class LCNThread {
globalThis.LCNPostContainer = class LCNPostContainer { globalThis.LCNPostContainer = class LCNPostContainer {
static nodeAttrib = "$LCNPostContainer";
static selector = ".postcontainer";
#parent = null; #parent = null;
#container = null; #element = null;
#content = null; #content = null;
#postId = null; #postId = null;
#boardId = null; #boardId = null;
static "assign" (container) { return container.$LCNPostContainer ?? (container.$LCNPostContainer = this.from(container)); } static "assign" (container) { return container[this.nodeAttrib] ?? (container[this.nodeAttrib] = this.from(container)); }
static "from" (container) { return new this(container); } static "from" (container) { return new this(container); }
"constructor" (container) { "constructor" (container) {
assert.ok(container.classList.contains("postcontainer"), "Arty must be expected Element.") assert.ok(container.classList.contains("postcontainer"), "Arty must be expected Element.")
const child = container.querySelector(".thread, .post") const child = container.querySelector(".thread, .post")
this.#container = container this.#element = container
this.#content = child.classList.contains("thread") ? LCNThread.assign(child) : LCNPost.assign(child) this.#content = child.classList.contains("thread") ? LCNThread.assign(child) : LCNPost.assign(child)
this.#boardId = container.dataset.board this.#boardId = container.dataset.board
this.#postId = container.id.slice(2) this.#postId = container.id.slice(2)
@ -242,7 +294,7 @@ globalThis.LCNPostContainer = class LCNPostContainer {
this.#content.__setParent(this) this.#content.__setParent(this)
} }
"getContainer" () { return this.#container; } "getElement" () { return this.#element; }
"getContent" () { return this.#content; } "getContent" () { return this.#content; }
"getBoardId" () { return this.#boardId; } "getBoardId" () { return this.#boardId; }
"getPostId" () { return this.#postId; } "getPostId" () { return this.#postId; }
@ -254,13 +306,15 @@ globalThis.LCNPostContainer = class LCNPostContainer {
globalThis.LCNPostWrapper = class LCNPostWrapper { globalThis.LCNPostWrapper = class LCNPostWrapper {
static nodeAttrib = "$LCNPostWrapper";
static selector = ".post-wrapper";
#wrapper = null; #wrapper = null;
#eitaLink = null; #eitaLink = null;
#eitaId = null; #eitaId = null;
#eitaHref = null #eitaHref = null
#content = null; #content = null;
static "assign" (wrapper) { return wrapper.$LCNPostWrapper ?? (wrapper.$LCNPostWrapper = this.from(wrapper)); } static "assign" (wrapper) { return wrapper[this.nodeAttrib] ?? (wrapper[this.nodeAttrib] = this.from(wrapper)); }
static "from" (wrapper) { return new this(wrapper); } static "from" (wrapper) { return new this(wrapper); }
"constructor" (wrapper) { "constructor" (wrapper) {
@ -298,23 +352,140 @@ globalThis.LCNPostWrapper = class LCNPostWrapper {
} }
globalThis.LCNPost.all = () => Array.prototype.map.apply(document.querySelectorAll(".post:not(.grid-li)"), [ node => LCNPost.assign(node) ]); globalThis.LCNSetting = class LCNSetting {
globalThis.LCNThread.all = () => Array.prototype.map.apply(document.querySelectorAll(".thread:not(.grid-li)"), [ node => LCNThread.assign(node) ]); #id = null;
globalThis.LCNPostContainer.all = () => Array.prototype.map.apply(document.querySelectorAll(".postcontainer"), [ node => LCNPostContainer.assign(node) ]); #eventId = null;
globalThis.LCNPostWrapper.all = () => Array.prototype.map.apply(document.querySelectorAll(".post-wrapper"), [ node => LCNPostWrapper.assign(node) ]); #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, `<div id="__${domid}" hidden></div>`)
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(() => { $().ready(() => {
LCNSite.INSTANCE = new LCNSite(); LCNSite.INSTANCE = new LCNSite();
const clazzes = [ LCNPost, LCNThread, LCNPostContainer, LCNPostWrapper ] for (const clazz of [ LCNPost, LCNPostInfo, LCNThread, LCNPostContainer, LCNPostWrapper ]) {
for (const clazz of clazzes) { clazz.allNodes = (node=document) => node.querySelectorAll(clazz.selector)
clazz.forEach = fn => clazz.all().forEach(fn) clazz.all = (node=document) => Array.prototype.map.apply(clazz.allNodes(node), [ elem => clazz.assign(elem) ]);
clazz.filter = fn => clazz.all().filter(fn) 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.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. // XXX: May be a cleaner way to do this but this should be fine for now.
for (const clazz of clazzes) { void clazz.all(); } for (const clazz of [ LCNPostContainer, LCNPostWrapper, LCNThread, LCNPost ]) { void clazz.all(); }
$(document).on("new_post", (e, post) => { $(document).on("new_post", (e, post) => {
if (LCNSite.INSTANCE.isModRecentsPage()) { if (LCNSite.INSTANCE.isModRecentsPage()) {
void LCNPostWrapper.all() void LCNPostWrapper.all()
@ -322,4 +493,7 @@ $().ready(() => {
void LCNPostContainer.all() void LCNPostContainer.all()
} }
}) })
$(window).on("focus", () => LCNSite.INSTANCE.clearUnseen())
$(document.body).on("mousemove", () => LCNSite.INSTANCE.clearUnseen())
}) })

View File

@ -1,16 +1,8 @@
/** /**
* @file Utils for leftychan javascript. * @file Utils for leftychan javascript.
* @author jonsmy * @author jonsmy
*/ */
const arrLast = arr => arr[arr.length-1] ?? undefined;
const getConfigBool = (k,d) => { const v = localStorage.getItem(`jon-modjs::${k}`); return v ? v == "1" : d; }
const writeCSSStyle = textContent => {
const style = document.createElement("style")
style.textContent = textContent
document.head.appendChild(style)
}
const assert = { const assert = {
"equal": (actual, expected, message="No message set") => { "equal": (actual, expected, message="No message set") => {
if (actual !== expected) { if (actual !== expected) {