add supporting changes
This commit is contained in:
parent
90004305a0
commit
0d8ad0a786
|
@ -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())
|
||||||
})
|
})
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue