types and bug fixes

This commit is contained in:
Lalle 2023-05-05 00:43:45 +02:00
parent a1e1a2c6b1
commit e4dda4dbf8
No known key found for this signature in database
GPG Key ID: A6583D207A8F6B0D
16 changed files with 261 additions and 196 deletions

29
.vscode/settings.json vendored
View File

@ -1,16 +1,17 @@
{ {
"search.exclude": { "search.exclude": {
"*.jst": true, "*.jst": true,
"*.md": true, "*.md": true,
"*.yaml": true, "*.yaml": true,
"*.yml": true, "*.yml": true,
"*.css": true, "*.css": true,
"package*.json": true "non-property-errors-logs.txt": true,
}, "package*.json": true
"typescript.tsdk": "node_modules/typescript/lib", },
"editor.formatOnSave": true, "typescript.tsdk": "node_modules/typescript/lib",
"editor.codeActionsOnSave": { "editor.formatOnSave": true,
"source.fixAll.eslint": true, "editor.codeActionsOnSave": {
"source.fixAll": true "source.fixAll.eslint": true,
} "source.fixAll": true
} }
}

View File

@ -125,7 +125,7 @@ const Redirect = {
$.set(items) $.set(items)
$.extend(Conf, items) $.extend(Conf, items)
Redirect.selectArchives() Redirect.selectArchives()
return cb?.() return cb
}, },
to(dest, data) { to(dest, data) {

View File

@ -378,7 +378,7 @@ const Filter = {
} }
}, },
addFilter(type: FilterType, re: string, cb?: () => void) { addFilter(type: FilterType, re: string, cb?: Callbacks) {
if (!$.hasOwn(Config.filter, type)) { return } if (!$.hasOwn(Config.filter, type)) { return }
return $.get(type, Conf[type], function (item) { return $.get(type, Conf[type], function (item) {
let save = item[type] let save = item[type]
@ -392,7 +392,7 @@ const Filter = {
}) })
}, },
removeFilters(type: FilterType, res: FilterObj[] | Map<string, FilterObj[]>, cb?: () => void) { removeFilters(type: FilterType, res: FilterObj[] | Map<string, FilterObj[]>, cb?: Callbacks) {
return $.get(type, Conf[type], function (item) { return $.get(type, Conf[type], function (item) {
let save = item[type] let save = item[type]
const filterArray = Array.isArray(res) ? res : [...res.values()].flat() const filterArray = Array.isArray(res) ? res : [...res.values()].flat()

View File

@ -3,13 +3,7 @@ import { Conf, g } from "../globals/globals"
import $ from "../platform/$" import $ from "../platform/$"
import { dict, HOUR } from "../platform/helpers" import { dict, HOUR } from "../platform/helpers"
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS104: Avoid inline assignments
* DS205: Consider reworking code to avoid use of IIFEs
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
const BoardConfig = { const BoardConfig = {
cbs: [], cbs: [],
@ -19,7 +13,7 @@ const BoardConfig = {
const now = Date.now() const now = Date.now()
if (now - (2 * HOUR) >= ((middle = Conf['boardConfig'].lastChecked || 0)) || middle > now) { if (now - (2 * HOUR) >= ((middle = Conf['boardConfig'].lastChecked || 0)) || middle > now) {
return $.ajax(`${location.protocol}//a.4cdn.org/boards.json`, return $.ajax(`${location.protocol}//a.4cdn.org/boards.json`,
{ onloadend: this.load }) { onloadend: this.load }, dict())
} else { } else {
const { boards } = Conf['boardConfig'] const { boards } = Conf['boardConfig']
return this.set(boards) return this.set(boards)

View File

@ -1,13 +1,7 @@
import { Conf, g } from "../globals/globals" import { Conf, g } from "../globals/globals"
import $ from "../platform/$" import $ from "../platform/$"
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
const Get = { const Get = {
url(type, IDs, ...args) { url(type, IDs, ...args) {
let f, site let f, site

View File

@ -12,13 +12,7 @@ import Get from "./Get"
import Settings from "./Settings" import Settings from "./Settings"
import UI from "./UI" import UI from "./UI"
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS104: Avoid inline assignments
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
const Header = { const Header = {
init() { init() {
$.onExists(doc, 'body', () => { $.onExists(doc, 'body', () => {
@ -404,7 +398,7 @@ const Header = {
Header.setBarFixed(this.checked) Header.setBarFixed(this.checked)
Conf['Fixed Header'] = this.checked Conf['Fixed Header'] = this.checked
return $.set('Fixed Header', this.checked) return $.set('Fixed Header', this.checked, true)
}, },
setShortcutIcons(show) { setShortcutIcons(show) {

View File

@ -71,9 +71,9 @@ const Settings = {
return localStorage.setItem('4chan-settings', JSON.stringify(settings)) return localStorage.setItem('4chan-settings', JSON.stringify(settings))
} catch (error) { } catch (error) {
return Object.defineProperty(window, 'Config', {value: {disableAll: true}}) return Object.defineProperty(window, 'Config', {value: {disableAll: true}})
}}) }}, true)
} else { } else {
return $.global(() => Object.defineProperty(window, 'Config', {value: {disableAll: true}})) return $.global(() => Object.defineProperty(window, 'Config', {value: {disableAll: true}}), true)
} }
} }
}, },
@ -81,7 +81,7 @@ const Settings = {
open(openSection) { open(openSection) {
let dialog, sectionToOpen let dialog, sectionToOpen
if (Settings.dialog) { return } if (Settings.dialog) { return }
$.event('CloseMenu') $.event('CloseMenu', null)
Settings.dialog = (dialog = $.el('div', Settings.dialog = (dialog = $.el('div',
{ id: 'overlay' } { id: 'overlay' }
@ -258,7 +258,7 @@ Enable it on boards.${location.hostname.split('.')[1]}.org in your browser's pri
inputs[key].checked = val inputs[key].checked = val
inputs[key].parentNode.parentNode.dataset.checked = val inputs[key].parentNode.parentNode.dataset.checked = val
} }
}) }, 'Settings')
const div = $.el('div', const div = $.el('div',
{innerHTML: '<button></button><span class="description">: Clear manually-hidden threads and posts on all boards. Reload the page to apply.'}) {innerHTML: '<button></button><span class="description">: Clear manually-hidden threads and posts on all boards. Reload the page to apply.'})
@ -299,6 +299,8 @@ Enable it on boards.${location.hostname.split('.')[1]}.org in your browser's pri
} }
} }
return button.textContent = `Hidden: ${hiddenNum}` return button.textContent = `Hidden: ${hiddenNum}`
}, function() {
return button.textContent = 'Hidden: 0'
}) })
$.on(button, 'click', function() { $.on(button, 'click', function() {
this.textContent = 'Hidden: 0' this.textContent = 'Hidden: 0'
@ -312,7 +314,7 @@ Enable it on boards.${location.hostname.split('.')[1]}.org in your browser's pri
localStorage.removeItem(`4chan-hide-t-${boardID}`) localStorage.removeItem(`4chan-hide-t-${boardID}`)
} }
} }
return ($.delete(['hiddenThreads', 'hiddenPosts'])) return ($.delete(['hiddenThreads', 'hiddenPosts'], dict())
}) })
}) })
return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div) return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div)
@ -326,7 +328,7 @@ Enable it on boards.${location.hostname.split('.')[1]}.org in your browser's pri
// Don't export cached JSON data. // Don't export cached JSON data.
delete Conf2['boardConfig'] delete Conf2['boardConfig']
return (Settings.downloadExport({version: g.VERSION, date: Date.now(), Conf: Conf2})) return (Settings.downloadExport({version: g.VERSION, date: Date.now(), Conf: Conf2}))
}) }, 'Settings')
}, },
downloadExport(data) { downloadExport(data) {
@ -918,6 +920,9 @@ vp-replace
Settings[key].call(input) Settings[key].call(input)
} }
} }
}, function(err) {
if (err) { return }
return $.id('lastarchivecheck').textContent = new Date(Conf['lastarchivecheck']).toLocaleString()
}) })
const listImageHost = $.id('list-fourchanImageHost') const listImageHost = $.id('list-fourchanImageHost')
@ -944,6 +949,9 @@ vp-replace
$.extend(Conf, itemsArchive) $.extend(Conf, itemsArchive)
Redirect.selectArchives() Redirect.selectArchives()
return Settings.addArchiveTable(section) return Settings.addArchiveTable(section)
}, function(err) {
if (err) { return }
return $.id('lastarchivecheck').textContent = new Date(Conf['lastarchivecheck']).toLocaleString()
}) })
const boardSelect = $('#archive-board-select', section) const boardSelect = $('#archive-board-select', section)
@ -1069,7 +1077,7 @@ vp-replace
saveSelectedArchive() { saveSelectedArchive() {
return $.get('selectedArchives', Conf['selectedArchives'], ({selectedArchives}) => { return $.get('selectedArchives', Conf['selectedArchives'], ({selectedArchives}) => {
(selectedArchives[this.dataset.boardid] || (selectedArchives[this.dataset.boardid] = dict()))[this.dataset.type] = JSON.parse(this.value) (selectedArchives[this.dataset.boardid] || (selectedArchives[this.dataset.boardid] = dict()))[this.dataset.type] = JSON.parse(this.value)
$.set('selectedArchives', selectedArchives) $.set('selectedArchives', selectedArchives, () => Conf['selectedArchives'] = selectedArchives)
Conf['selectedArchives'] = selectedArchives Conf['selectedArchives'] = selectedArchives
return Redirect.selectArchives() return Redirect.selectArchives()
}) })
@ -1157,7 +1165,7 @@ vp-replace
const val = items[key] const val = items[key]
inputs[key].value = val inputs[key].value = val
} }
}) }, true)
}, },
keybind(e) { keybind(e) {

View File

@ -1,13 +1,10 @@
import Callbacks from "../classes/Callbacks" import Callbacks from "../classes/Callbacks"
import Post from "../classes/Post"
import Thread from "../classes/Thread"
import Get from "../General/Get" import Get from "../General/Get"
import { Conf, g } from "../globals/globals" import { Conf, g } from "../globals/globals"
import $ from "../platform/$" import $ from "../platform/$"
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
const IDPostCount = { const IDPostCount = {
init() { init() {
if ((g.VIEW !== 'thread') || !Conf['Count Posts by ID']) { return } if ((g.VIEW !== 'thread') || !Conf['Count Posts by ID']) { return }
@ -21,16 +18,16 @@ const IDPostCount = {
}) })
}, },
node() { node(): void {
if (this.nodes.uniqueID && (this.thread === IDPostCount.thread)) { if (this.nodes.uniqueID && (this.thread === IDPostCount.thread)) {
return $.on(this.nodes.uniqueID, 'mouseover', IDPostCount.count) return $.on(this.nodes.uniqueID, 'mouseover', IDPostCount.count)
} }
}, },
count() { count(): string {
const { uniqueID } = Get.postFromNode(this).info const { uniqueID } = Get.postFromNode(this).info
let n = 0 let n = 0
IDPostCount.thread.posts.forEach(function (post) { IDPostCount.thread.posts.forEach((post: Post) => {
if (post.info.uniqueID === uniqueID) { return n++ } if (post.info.uniqueID === uniqueID) { return n++ }
}) })
return this.title = `${n} post${n === 1 ? '' : 's'} by this ID` return this.title = `${n} post${n === 1 ? '' : 's'} by this ID`

View File

@ -1,18 +1,22 @@
import Callbacks from "../classes/Callbacks" import Callbacks from "../classes/Callbacks"
import { Conf,g } from "../globals/globals" import type Post from "../classes/Post"
import { Conf, g } from "../globals/globals"
const ThreadLinks = { const ThreadLinks = {
init(): void { init(): void {
if ((g.VIEW !== 'index') || !Conf['Open Threads in New Tab']) { return } if ((g.VIEW !== 'index') || !Conf['Open Threads in New Tab']) { return }
Callbacks.Post.push({ const postCallback: Post = {
name: 'Thread Links', name: 'Thread Links',
cb: this.node.bind(this) cb: this.node.bind(this)
}) }
Callbacks.CatalogThread.push({ const catalogThreadCallback: Post = {
name: 'Thread Links', name: 'Thread Links',
cb: this.catalogNode.bind(this) cb: this.catalogNode.bind(this)
}) }
Callbacks.Post.push(postCallback)
Callbacks.CatalogThread.push(catalogThreadCallback)
}, },
node(): void { node(): void {

View File

@ -2,31 +2,54 @@ import Callbacks from "../classes/Callbacks"
import { Conf, g } from "../globals/globals" import { Conf, g } from "../globals/globals"
import $ from "../platform/$" import $ from "../platform/$"
/* interface TimeFormatters {
* decaffeinate suggestions: a(): string;
* DS102: Remove unnecessary code created because of implicit returns A(): string;
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md b(): string;
*/ B(): string;
const Time = { d(): string;
init() { e(): number;
if (!['index', 'thread', 'archive'].includes(g.VIEW) || !Conf['Time Formatting']) { return } H(): string;
I(): string;
k(): number;
l(): number;
m(): string;
M(): string;
p(): string;
P(): string;
S(): string;
y(): string;
Y(): number;
'%'(): string;
}
return Callbacks.Post.push({ const Time = {
init(): void {
if (!['index', 'thread', 'archive'].includes(g.VIEW) || !Conf['Time Formatting']) {
return
}
Callbacks.Post.push({
name: 'Time Formatting', name: 'Time Formatting',
cb: this.node cb: this.node,
}) })
}, },
node() { node(): void {
if (!this.info.date || this.isClone) { return } if (!this.info.date || this.isClone) {
return
}
const { textContent } = this.nodes.date const { textContent } = this.nodes.date
return this.nodes.date.textContent = textContent.match(/^\s*/)[0] + Time.format(Conf['time'], this.info.date) + textContent.match(/\s*$/)[0] this.nodes.date.textContent =
textContent.match(/^\s*/)[0] +
Time.format(Conf['time'], this.info.date) +
textContent.match(/\s*$/)[0]
}, },
format(formatString, date) { format(formatString: string, date: Date): string {
return formatString.replace(/%(.)/g, function (s, c) { return formatString.replace(/%(.)/g, (s: string, c: string): string => {
if ($.hasOwn(Time.formatters, c)) { if ($.hasOwn(Time.formatters, c)) {
return Time.formatters[c].call(date) return (Time.formatters as TimeFormatters)[c].call(date)
} else { } else {
return s return s
} }
@ -40,7 +63,7 @@ const Time = {
'Wednesday', 'Wednesday',
'Thursday', 'Thursday',
'Friday', 'Friday',
'Saturday' 'Saturday',
], ],
month: [ month: [
@ -55,49 +78,95 @@ const Time = {
'September', 'September',
'October', 'October',
'November', 'November',
'December' 'December',
], ],
localeFormat(date, options, defaultValue) { localeFormat(date: Date, options: Intl.DateTimeFormatOptions, defaultValue: string): string {
if (Conf['timeLocale']) { if (Conf['timeLocale']) {
try { try {
return Intl.DateTimeFormat(Conf['timeLocale'], options).format(date) return Intl.DateTimeFormat(Conf['timeLocale'], options).format(date)
} catch (error) { } } catch (error) {/* empty */ }
} }
return defaultValue return defaultValue
}, },
localeFormatPart(date, options, part, defaultValue) { localeFormatPart(
date: Date,
options: Intl.DateTimeFormatOptions,
part: string,
defaultValue: string,
): string {
if (Conf['timeLocale']) { if (Conf['timeLocale']) {
try { try {
const parts = Intl.DateTimeFormat(Conf['timeLocale'], options).formatToParts(date) const parts = Intl.DateTimeFormat(Conf['timeLocale'], options).formatToParts(date)
return parts.map(function (x) { if (x.type === part) { return x.value } else { return '' } }).join('') return parts
} catch (error) { } .map((x) => (x.type === part ? x.value : ''))
.join('')
} catch (error) { /* empty */ }
} }
return defaultValue return defaultValue
}, },
zeroPad(n) { if (n < 10) { return `0${n}` } else { return n } }, zeroPad(n: number): string | number {
return n < 10 ? `0${n}` : n
},
formatters: { formatters: {
a() { return Time.localeFormat(this, { weekday: 'short' }, Time.day[this.getDay()].slice(0, 3)) }, a(): string {
A() { return Time.localeFormat(this, { weekday: 'long' }, Time.day[this.getDay()]) }, return Time.localeFormat(this, { weekday: 'short' }, Time.day[this.getDay()].slice(0, 3))
b() { return Time.localeFormat(this, { month: 'short' }, Time.month[this.getMonth()].slice(0, 3)) }, },
B() { return Time.localeFormat(this, { month: 'long' }, Time.month[this.getMonth()]) }, A(): string {
d() { return Time.zeroPad(this.getDate()) }, return Time.localeFormat(this, { weekday: 'long' }, Time.day[this.getDay()])
e() { return this.getDate() }, },
H() { return Time.zeroPad(this.getHours()) }, b(): string {
I() { return Time.zeroPad((this.getHours() % 12) || 12) }, return Time.localeFormat(this, { month: 'short' }, Time.month[this.getMonth()].slice(0, 3))
k() { return this.getHours() }, },
l() { return (this.getHours() % 12) || 12 }, B(): string {
m() { return Time.zeroPad(this.getMonth() + 1) }, return Time.localeFormat(this, { month: 'long' }, Time.month[this.getMonth()])
M() { return Time.zeroPad(this.getMinutes()) }, },
p() { return Time.localeFormatPart(this, { hour: 'numeric', hour12: true }, 'dayperiod', (this.getHours() < 12 ? 'AM' : 'PM')) }, d(): string | number {
P() { return Time.formatters.p.call(this).toLowerCase() }, return Time.zeroPad(this.getDate())
S() { return Time.zeroPad(this.getSeconds()) }, },
y() { return this.getFullYear().toString().slice(2) }, e(): number {
Y() { return this.getFullYear() }, return this.getDate()
'%'() { return '%' } },
} H(): string | number {
return Time.zeroPad(this.getHours())
},
I(): string | number {
return Time.zeroPad((this.getHours() % 12) || 12)
},
k(): number {
return this.getHours()
},
l(): number {
return (this.getHours() % 12) || 12
},
m(): string | number {
return Time.zeroPad(this.getMonth() + 1)
},
M(): string | number {
return Time.zeroPad(this.getMinutes())
},
p(): string {
return Time.localeFormatPart(this, { hour: 'numeric', hour12: true }, 'dayperiod', this.getHours() < 12 ? 'AM' : 'PM')
},
P(): string {
return Time.formatters.p.call(this).toLowerCase()
},
S(): string | number {
return Time.zeroPad(this.getSeconds())
},
y(): string {
return this.getFullYear().toString().slice(2)
},
Y(): number {
return this.getFullYear()
},
'%'(): string {
return '%'
},
},
} }
export default Time export default Time

View File

@ -10,12 +10,11 @@ interface QRPostDetail {
} }
const Tinyboard = { const Tinyboard = {
init() { init(): void {
if (g.SITE.software !== 'tinyboard') { return } if (g.SITE.software !== 'tinyboard') { return }
if (g.VIEW === 'thread') { if (g.VIEW === 'thread') {
return Main.ready(() => $.global(function() { return Main.ready(() => $.global(function (data: { boardID: string, threadID: number }) {
let base const { boardID, threadID } = data
const { boardID, threadID } = document.currentScript.dataset
const threadIdNum = +threadID const threadIdNum = +threadID
const form = document.querySelector('form[name="post"]') as HTMLFormElement const form = document.querySelector('form[name="post"]') as HTMLFormElement
window.$(document).ajaxComplete((event, request, settings) => { window.$(document).ajaxComplete((event, request, settings) => {
@ -29,14 +28,13 @@ const Tinyboard = {
if (redirect && (originalNoko != null) && !originalNoko && !noko) { if (redirect && (originalNoko != null) && !originalNoko && !noko) {
detail.redirect = redirect detail.redirect = redirect
} }
} catch (error) {} } catch (error) { }
event = new CustomEvent('QRPostSuccessful', { bubbles: true, detail }) event = new CustomEvent('QRPostSuccessful', { bubbles: true, detail })
return document.dispatchEvent(event) return document.dispatchEvent(event)
}) })
const originalNoko = window.tb_settings?.ajax?.always_noko_replies; const originalNoko = window.tb_settings?.ajax?.always_noko_replies;
((base = window.tb_settings || (window.tb_settings = {})).ajax || (base.ajax = {})).always_noko_replies = true ((window.tb_settings || (window.tb_settings = {})).ajax || (window.tb_settings.ajax = {})).always_noko_replies = true
} }, { boardID: g.BOARD.ID, threadID: g.THREADID }))
, { boardID: g.BOARD.ID, threadID: g.THREADID }))
} }
} }
} }

View File

@ -2,25 +2,22 @@ import { d, g } from "../globals/globals"
import $ from "../platform/$" import $ from "../platform/$"
import QR from "./QR" import QR from "./QR"
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
const CaptchaT = { const CaptchaT = {
init() { init() {
if (d.cookie.indexOf('pass_enabled=1') >= 0) { return } if (d.cookie.indexOf('pass_enabled=1') >= 0) { return }
if (!(this.isEnabled = !!$('#t-root') || !$.id('postForm'))) { return } if (!(this.isEnabled = !!$('#t-root') || !$.id('postForm'))) { return }
const root = $.el('div', {className: 'captcha-root'}) const root = $.el('div', { className: 'captcha-root' })
this.nodes = {root} this.nodes = { root }
$.addClass(QR.nodes.el, 'has-captcha', 'captcha-t') $.addClass(QR.nodes.el, 'has-captcha', 'captcha-t')
return $.after(QR.nodes.com.parentNode, root) return $.after(QR.nodes.com.parentNode, root)
}, },
moreNeeded() { moreNeeded() {
return this.isEnabled && !this.nodes.container
}, },
currentThread: null,
getThread() { getThread() {
let threadID let threadID
@ -30,26 +27,28 @@ const CaptchaT = {
} else { } else {
threadID = '' + QR.posts[0].thread threadID = '' + QR.posts[0].thread
} }
return {boardID, threadID} return { boardID, threadID }
}, },
setup(focus) { setup(focus?: boolean) {
if (!this.isEnabled) { return } if (!this.isEnabled) { return }
if (!this.nodes.container) { if (!this.nodes.container) {
this.nodes.container = $.el('div', {className: 'captcha-container'}) this.nodes.container = $.el('div', { className: 'captcha-container' })
$.prepend(this.nodes.root, this.nodes.container) $.prepend(this.nodes.root, this.nodes.container)
CaptchaT.currentThread = CaptchaT.getThread() CaptchaT.currentThread = CaptchaT.getThread()
$.global(function() { $.global(function () {
const el = document.querySelector('#qr .captcha-container') const el = document.querySelector('#qr .captcha-container')
window.TCaptcha.init(el, this.boardID, +this.threadID) window.TCaptcha.init(el, this.boardID, +this.threadID)
return window.TCaptcha.setErrorCb(err => window.dispatchEvent(new CustomEvent('CreateNotification', {detail: { return window.TCaptcha.setErrorCb(err => window.dispatchEvent(new CustomEvent('CreateNotification', {
type: 'warning', detail: {
content: '' + err type: 'warning',
}}) content: '' + err
}
})
)) ))
} }
, CaptchaT.currentThread) , CaptchaT.currentThread)
} }
if (focus) { if (focus) {
@ -59,14 +58,14 @@ const CaptchaT = {
destroy() { destroy() {
if (!this.isEnabled || !this.nodes.container) { return } if (!this.isEnabled || !this.nodes.container) { return }
$.global(() => window.TCaptcha.destroy()) $.global(() => window.TCaptcha.destroy(), CaptchaT.currentThread)
$.rm(this.nodes.container) $.rm(this.nodes.container)
return delete this.nodes.container return delete this.nodes.container
}, },
updateThread() { updateThread() {
if (!this.isEnabled) { return } if (!this.isEnabled) { return }
const {boardID, threadID} = (CaptchaT.currentThread || {}) const { boardID, threadID } = (CaptchaT.currentThread || {})
const newThread = CaptchaT.getThread() const newThread = CaptchaT.getThread()
if ((newThread.boardID !== boardID) || (newThread.threadID !== threadID)) { if ((newThread.boardID !== boardID) || (newThread.threadID !== threadID)) {
CaptchaT.destroy() CaptchaT.destroy()
@ -91,7 +90,7 @@ const CaptchaT = {
setUsed() { setUsed() {
if (!this.isEnabled) { return } if (!this.isEnabled) { return }
if (this.nodes.container) { if (this.nodes.container) {
return $.global(() => window.TCaptcha.clearChallenge()) return $.global(() => window.TCaptcha.clearChallenge(), CaptchaT.currentThread)
} }
}, },

View File

@ -65,7 +65,7 @@ export default class DataBoard {
this.data.version = (this.data.version || 0) + 1 this.data.version = (this.data.version || 0) + 1
return $.set(this.key, this.data, () => { return $.set(this.key, this.data, () => {
if (needSync) { this.sync?.() } if (needSync) { this.sync?.() }
return cb?.() return cb
}) })
}) })
} }
@ -77,7 +77,7 @@ export default class DataBoard {
for (const change of DataBoard.changes) { change() } for (const change of DataBoard.changes) { change() }
this.sync?.() this.sync?.()
} }
return cb?.() return cb
}) })
} }

View File

@ -3,6 +3,8 @@ import { d } from "../globals/globals"
import $ from "../platform/$" import $ from "../platform/$"
import { SECOND } from "../platform/helpers" import { SECOND } from "../platform/helpers"
type NoticeType = "success" | "warning" | "error"
export default class Notice { export default class Notice {
private el: HTMLDivElement private el: HTMLDivElement
private timeout?: number private timeout?: number
@ -10,7 +12,7 @@ export default class Notice {
private closed = false private closed = false
constructor( constructor(
private type: string, private type: NoticeType,
private content: string | Node, private content: string | Node,
timeout?: number, timeout?: number,
onclose?: () => void onclose?: () => void
@ -36,7 +38,7 @@ export default class Notice {
this.onclose = onclose this.onclose = onclose
} }
private setType(type: string) { private setType(type: NoticeType) {
this.el.className = `notification ${type}` this.el.className = `notification ${type}`
} }

View File

@ -4,7 +4,6 @@ import Board from "./Board"
import Post from "./Post" import Post from "./Post"
import SimpleDict from "./SimpleDict" import SimpleDict from "./SimpleDict"
export default class Thread { export default class Thread {
ID: number | string ID: number | string
OP: Post OP: Post
@ -26,7 +25,6 @@ export default class Thread {
json: JSON json: JSON
catalogView: Node catalogView: Node
nodes: { root: Post } nodes: { root: Post }
toString() { return this.ID }
constructor(ID: number | string, board: Board) { constructor(ID: number | string, board: Board) {
this.board = board this.board = board
@ -50,8 +48,7 @@ export default class Thread {
this.OP = null this.OP = null
this.catalogView = null this.catalogView = null
this.nodes = this.nodes = { root: null }
{ root: null }
this.board.threads.push(this.ID, this) this.board.threads.push(this.ID, this)
g.threads.push(this.fullID, this) g.threads.push(this.fullID, this)
@ -66,14 +63,18 @@ export default class Thread {
} }
icon.title = `This thread is on page ${pageNum} in the original index.` icon.title = `This thread is on page ${pageNum} in the original index.`
icon.textContent = `[${pageNum}]` icon.textContent = `[${pageNum}]`
if (this.catalogView) { return this.catalogView.nodes.pageCount.textContent = pageNum } if (this.catalogView) { this.catalogView.nodes.pageCount.textContent = pageNum }
} }
setCount(type: string, count: number, reachedLimit: boolean) { setCount(type: string, count: number, reachedLimit: boolean) {
if (!this.catalogView) { return } if (!this.catalogView) { return }
const el = this.catalogView.nodes[`${type}Count`] const el = this.catalogView.nodes[`${type}Count`]
el.textContent = count el.textContent = count
return (reachedLimit ? $.addClass : $.rmClass)(el, 'warning') if (reachedLimit) {
$.addClass(el, 'warning')
} else {
$.rmClass(el, 'warning')
}
} }
setStatus(type: string, status: boolean) { setStatus(type: string, status: boolean) {
@ -83,7 +84,7 @@ export default class Thread {
if (!this.OP) { return } if (!this.OP) { return }
this.setIcon('Sticky', this.isSticky) this.setIcon('Sticky', this.isSticky)
this.setIcon('Closed', this.isClosed && !this.isArchived) this.setIcon('Closed', this.isClosed && !this.isArchived)
return this.setIcon('Archived', this.isArchived) this.setIcon('Archived', this.isArchived)
} }
setIcon(type: string, status: boolean) { setIcon(type: string, status: boolean) {
@ -118,21 +119,25 @@ export default class Thread {
} }
kill() { kill() {
return this.isDead = true this.isDead = true
} }
collect() { collect() {
let n = 0 let n = 0
this.posts.forEach(function (post) { this.posts.forEach(post => {
if (post.clones.length) { if (post.clones.length) {
return n++ n++
} else { } else {
return post.collect() post.collect()
} }
}) })
if (!n) { if (!n) {
g.threads.rm(this.fullID) g.threads.rm(this.fullID)
return this.board.threads.rm(this) this.board.threads.rm(this)
} }
} }
toString() {
return this.ID
}
} }

View File

@ -93,7 +93,6 @@ $.ajaxPage = function (url: string, options: AjaxPageOptions) {
xhr.send(form) xhr.send(form)
return xhr return xhr
} }
$.cache = dict()
$.ready = function (fc: () => void) { $.ready = function (fc: () => void) {
if (d.readyState !== 'loading') { if (d.readyState !== 'loading') {
@ -293,7 +292,7 @@ $.ajax = (function () {
// With the `If-Modified-Since` header we only receive the HTTP headers and no body for 304 responses. // With the `If-Modified-Since` header we only receive the HTTP headers and no body for 304 responses.
// This saves a lot of bandwidth and CPU time for both the users and the servers. // This saves a lot of bandwidth and CPU time for both the users and the servers.
$.lastModified = dict() $.lastModified = dict()
$.whenModified = function (url, bucket, cb, options) { $.whenModified = function (url, bucket, cb, options = {}) {
let t: string let t: string
const { timeout, ajax } = options const { timeout, ajax } = options
const params = [] const params = []
@ -315,42 +314,43 @@ $.whenModified = function (url, bucket, cb, options) {
headers headers
}) })
return r return r
}; }
(function () { $.cache = function (url, cb, options = {}) {
const reqs = dict() const reqs = dict()
$.cache = function (url, cb, options) { let req
let req const { ajax } = options
const { ajax } = options if (req = reqs[url]) {
if (req = reqs[url]) { if (req.callbacks) {
if (req.callbacks) { req.callbacks.push(cb)
req.callbacks.push(cb) } else {
} else { $.queueTask(() => cb.call(req, { isCached: true }))
$.queueTask(() => cb.call(req, { isCached: true }))
}
return req
} }
const onloadend = function () { return req
if (!this.status) {
delete reqs[url]
}
for (cb of this.callbacks) {
(cb => $.queueTask(() => cb.call(this, { isCached: false })))(cb)
}
return delete this.callbacks
}
req = (ajax || $.ajax)(url, { onloadend })
req.callbacks = [cb]
return reqs[url] = req
} }
return $.cleanCache = function (testf) { const onloadend = function () {
for (const url in reqs) { if (!this.status) {
if (testf(url)) { delete reqs[url]
delete reqs[url] }
} for (cb of this.callbacks) {
(cb => $.queueTask(() => cb.call(this, { isCached: false })))(cb)
}
return delete this.callbacks
}
req = (ajax || $.ajax)(url, { onloadend })
req.callbacks = [cb]
return reqs[url] = req
}
$.cleanCache = function (testf) {
const reqs = dict()
for (const url in reqs) {
if (testf(url)) {
delete reqs[url]
} }
} }
})() }
$.cb = { $.cb = {
checked() { checked() {
@ -788,7 +788,7 @@ if (platform === 'crx') {
c.error(err.message) c.error(err.message)
setTimeout(setArea, MINUTE, area) setTimeout(setArea, MINUTE, area)
timeout[area] = Date.now() + MINUTE timeout[area] = Date.now() + MINUTE
return cb?.(err) return cb
} }
delete timeout[area] delete timeout[area]
@ -807,7 +807,7 @@ if (platform === 'crx') {
return result return result
})())) })()))
} }
return cb?.() return cb
}) })
} }
@ -831,7 +831,7 @@ if (platform === 'crx') {
c.error(chrome.runtime.lastError.message) c.error(chrome.runtime.lastError.message)
} }
if (err == null) { err = chrome.runtime.lastError } if (err == null) { err = chrome.runtime.lastError }
if (!--count) { return cb?.(err) } if (!--count) { return cb }
} }
chrome.storage.local.clear(done) chrome.storage.local.clear(done)
return chrome.storage.sync.clear(done) return chrome.storage.sync.clear(done)
@ -862,7 +862,7 @@ if (platform === 'crx') {
$.forceSync = function () { } $.forceSync = function () { }
$.delete = function (keys, cb) { $.delete = function (keys: string | string[], cb: Callbacks) {
let key let key
if (!(keys instanceof Array)) { if (!(keys instanceof Array)) {
keys = [keys] keys = [keys]
@ -877,7 +877,7 @@ if (platform === 'crx') {
const items = dict() const items = dict()
for (key of keys) { items[key] = undefined } for (key of keys) { items[key] = undefined }
$.syncChannel.postMessage(items) $.syncChannel.postMessage(items)
return cb?.() return cb
}) })
} }
@ -905,7 +905,7 @@ if (platform === 'crx') {
return result return result
})()).then(function () { })()).then(function () {
$.syncChannel.postMessage(items) $.syncChannel.postMessage(items)
return cb?.() return cb
}) })
}) })
@ -1044,7 +1044,7 @@ if (platform === 'crx') {
const value = items[key] const value = items[key]
$.setValue(g.NAMESPACE + key, JSON.stringify(value), cb) $.setValue(g.NAMESPACE + key, JSON.stringify(value), cb)
} }
return cb?.() return cb
}) })
}) })
@ -1056,7 +1056,7 @@ if (platform === 'crx') {
try { try {
$.delete($.listValues().map(key => key.replace(g.NAMESPACE, '')), cb) $.delete($.listValues().map(key => key.replace(g.NAMESPACE, '')), cb)
} catch (error) { } } catch (error) { }
return cb?.() return cb
} }
} }
} }