This commit is contained in:
Lalle 2023-05-07 18:13:05 +02:00
parent f869225c41
commit 55dd58b8c6
No known key found for this signature in database
GPG Key ID: A6583D207A8F6B0D
18 changed files with 167 additions and 174 deletions

View File

@ -38,7 +38,7 @@ const Get = {
if (index) { return post.clones[+index] } else { return post }
},
postFromNode(root): Post {
return Get.postFromRoot($.x(`ancestor-or-self::${g.SITE.xpath.postContainer}[1]`, root))
return Get.postFromRoot($.x(`ancestor-or-self::${g.SITE.xpath.postContainer}[1]`, root)) as Post
},
postDataFromLink(link) {
let boardID, postID, threadID

View File

@ -3,11 +3,7 @@ import { Conf, doc } 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 AntiAutoplay = {
init() {
if (!Conf['Disable Autoplaying Sounds']) { return }
@ -31,10 +27,10 @@ const AntiAutoplay = {
},
node() {
return AntiAutoplay.process(this.nodes.comment)
return AntiAutoplay.process(this.node())
},
process(root) {
process(root: HTMLElement) {
for (const iframe of $$('iframe[src*="youtube"][src*="autoplay=1"]', root)) {
AntiAutoplay.processVideo(iframe, 'src')
}
@ -43,7 +39,7 @@ const AntiAutoplay = {
}
},
processVideo(el, attr) {
processVideo(el: HTMLIFrameElement | HTMLObjectElement, attr: 'src' | 'data') {
el[attr] = el[attr].replace(/\?autoplay=1&?/, '?').replace('&autoplay=1', '')
if (window.getComputedStyle(el).display === 'none') { el.style.display = 'block' }
return $.addClass(el, 'autoplay-removed')

View File

@ -6,13 +6,7 @@ import $ from "../platform/$"
import $$ from "../platform/$$"
import { dict } from "../platform/helpers"
/*
* 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 Banner = {
init() {
if (Conf['Custom Board Titles']) {

View File

@ -81,7 +81,7 @@ const CatalogLinks = {
},
toggle() {
$.event('CloseMenu')
$.event('CloseMenu', { menu: Header.menu })
$.set('Header catalog links', this.checked)
return CatalogLinks.set(this.checked)
},

View File

@ -14,7 +14,7 @@ export default class Board {
config: any
toString() { return this.ID }
constructor(ID) {
constructor(ID: string) {
this.ID = ID
this.boardID = this.ID
this.siteID = g.SITE.ID
@ -22,7 +22,7 @@ export default class Board {
this.posts = new SimpleDict()
this.config = BoardConfig.boards?.[this.ID] || {}
g.boards[this] = this
g.boards[this.ID] = this
}
cooldowns() {

View File

@ -26,7 +26,7 @@ export default class Callbacks {
return this[name] = cb
}
execute(node, keys = this.keys, force = false) {
execute(node: Post, keys = this.keys, force = false) {
let errors
if (node.callbacksExecuted && !force) { return }
node.callbacksExecuted = true

View File

@ -4,8 +4,8 @@ import Callbacks from "./Callbacks"
export default class Connection {
target: any
origin: any
target: Window | HTMLIFrameElement
origin: string
cb: Callbacks
constructor(target: Window, origin: string, cb: Callbacks) {
this.send = this.send.bind(this)

View File

@ -1,6 +1,7 @@
import { Conf, d, g } from "../globals/globals"
import $ from "../platform/$"
import { dict, HOUR } from "../platform/helpers"
import { CacheOptions } from "../types/globals"
/*
* decaffeinate suggestions:
@ -20,7 +21,6 @@ export default class DataBoard {
static keys: string[]
static changes: string[]
key: string
sync: VoidFunction
data: any
static initClass() {
this.keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads', 'watcherLastModified', 'customTitles']
@ -98,7 +98,7 @@ export default class DataBoard {
} else if (threadID) {
if (!this.data[siteID].boards[boardID]) { return }
delete this.data[siteID].boards[boardID][threadID]
return this.deleteIfEmpty({ siteID, boardID })
return this.deleteIfEmpty({ siteID, boardID, threadID: null })
} else {
return delete this.data[siteID].boards[boardID]
}
@ -111,7 +111,7 @@ export default class DataBoard {
if (threadID) {
if (!Object.keys(this.data[siteID].boards[boardID][threadID]).length) {
delete this.data[siteID].boards[boardID][threadID]
return this.deleteIfEmpty({ siteID, boardID })
return this.deleteIfEmpty({ siteID, boardID, threadID: null })
}
} else if (!Object.keys(this.data[siteID].boards[boardID]).length) {
return delete this.data[siteID].boards[boardID]
@ -157,7 +157,10 @@ export default class DataBoard {
setLastChecked(key = 'lastChecked') {
return this.save(() => {
return this.data[key] = Date.now()
})
}, () => {
return this.sync?.()
}
)
}
get({ siteID, boardID, threadID, postID, defaultValue }) {
@ -203,21 +206,21 @@ export default class DataBoard {
}
}
ajaxClean(boardID) {
ajaxClean(boardID: string) {
const that = this
const siteID = g.SITE.ID
const threadsList = g.SITE.urls.threadsListJSON?.({ siteID, boardID })
if (!threadsList) { return }
return $.cache(threadsList, function () {
return $.cache(threadsList, () => {
if (this.status !== 200) { return }
const archiveList = g.SITE.urls.archiveListJSON?.({ siteID, boardID })
if (!archiveList) { return that.ajaxCleanParse(boardID, this.response) }
const response1 = this.response
return $.cache(archiveList, function () {
return $.cache(archiveList, () => {
if ((this.status !== 200) && (!!g.SITE.archivedBoardsKnown || (this.status !== 404))) { return }
return that.ajaxCleanParse(boardID, response1, this.response)
})
})
}, { type: 'json' }) as CacheOptions
}, { type: 'json' }) as CacheOptions
}
ajaxCleanParse(boardID, response1, response2) {

View File

@ -9,6 +9,7 @@ import Callbacks from "./Callbacks"
import type Thread from "./Thread"
export default class Post {
callbacksExecuted: boolean
declare root: HTMLElement
declare thread: Thread
declare board: Board

View File

@ -1,9 +1,8 @@
import $ from '../platform/$'
class ShimSet {
elements: any
elements: Element
size: number
constructor() {
this.elements = $.dict()
this.elements
this.size = 0
}
has(value) {

View File

@ -1,5 +1,3 @@
import $ from "../platform/$"
export default class SimpleDict<T> {
keys: string[]
@ -9,17 +7,15 @@ export default class SimpleDict<T> {
push(key: string, data: T): T {
key = `${key}`
if (!this[key]) { this.keys.push(key) }
return this[key] = data
this[key] = data
this.keys.push(key)
return data
}
rm(key: string) {
let i: number
key = `${key}`
if ((i = this.keys.indexOf(key)) !== -1) {
this.keys.splice(i, 1)
return delete this[key]
}
delete this[key]
this.keys = this.keys.filter(k => k !== key)
}
forEach(fn: (value: T) => void): void {
@ -27,10 +23,6 @@ export default class SimpleDict<T> {
}
get(key: string): T {
if (key === 'keys') {
return undefined
} else {
return $.getOwn(this, key)
}
return this[key]
}
}

View File

@ -7,7 +7,7 @@ import SimpleDict from "./SimpleDict"
export default class Thread {
catalogViewNative: CatalogThreadNative
ID: number | string
ID: string | number
OP: Post
isArchived: boolean
isClosed: boolean
@ -16,7 +16,7 @@ export default class Thread {
board: Board
threadID: number
boardID: number | string
siteID: number
siteID: number | string
fullID: string
isDead: boolean
isHidden: boolean
@ -52,7 +52,7 @@ export default class Thread {
this.nodes = { root: null }
this.board.threads.push(this.ID, this)
this.board.threads.push(this.ID.toString(), this)
g.threads.push(this.fullID, this)
}

View File

@ -763,7 +763,7 @@ const Config = {
comment: `\
# Filter Stallman copypasta on /g/:
#/what you\'re refer+ing to as linux/i;boards:g
#/what you're refer+ing to as linux/i;boards:g
# Filter posts with 20 or more quote links:
#/(?:>>\\d(?:(?!>>\\d)[^])*){20}/
# Filter posts like T H I S / H / I / S:
@ -817,7 +817,7 @@ http://eye.swfchan.com/search/?q=%name;types:swf
`,
FappeT: {
werk: false
werk: false
},
'Custom CSS': true,
@ -826,29 +826,29 @@ http://eye.swfchan.com/search/?q=%name;types:swf
'Index Mode': 'paged',
'Previous Index Mode': 'paged',
'Index Size': 'small',
'Show Replies': [true, 'Show replies in the index, and also in the catalog if "Catalog hover expand" is checked.'],
'Catalog Hover Expand': [false, 'Expand the comment and show more details when you hover over a thread in the catalog.'],
'Catalog Hover Toggle': [true, 'Turn "Catalog hover expand" on and off by clicking in the catalog.'],
'Pin Watched Threads': [false, 'Move watched threads to the start of the index.'],
'Anchor Hidden Threads': [true, 'Move hidden threads to the end of the index.'],
'Refreshed Navigation': [false, 'Refresh index when navigating through pages.']
'Show Replies': [true, 'Show replies in the index, and also in the catalog if "Catalog hover expand" is checked.'],
'Catalog Hover Expand': [false, 'Expand the comment and show more details when you hover over a thread in the catalog.'],
'Catalog Hover Toggle': [true, 'Turn "Catalog hover expand" on and off by clicking in the catalog.'],
'Pin Watched Threads': [false, 'Move watched threads to the start of the index.'],
'Anchor Hidden Threads': [true, 'Move hidden threads to the end of the index.'],
'Refreshed Navigation': [false, 'Refresh index when navigating through pages.']
},
Header: {
'Fixed Header': true,
'Header auto-hide': false,
'Fixed Header': true,
'Header auto-hide': false,
'Header auto-hide on scroll': false,
'Bottom Header': false,
'Centered links': false,
'Header catalog links': false,
'Bottom Board List': true,
'Shortcut Icons': true,
'Custom Board Navigation': true
'Bottom Header': false,
'Centered links': false,
'Header catalog links': false,
'Bottom Board List': true,
'Shortcut Icons': true,
'Custom Board Navigation': true
},
archives: {
archiveLists: 'https://4chenz.github.io/archives.json/archives.json',
lastarchivecheck: 0,
archiveLists: 'https://4chenz.github.io/archives.json/archives.json',
lastarchivecheck: 0,
archiveAutoUpdate: true
},
@ -937,7 +937,7 @@ https://*.hcaptcha.com
'Alt+c',
'Insert code tags.'
],
'Eqn tags': [
'Eqn tags': [
'Alt+e',
'Insert eqn tags.'
],
@ -1184,11 +1184,11 @@ https://*.hcaptcha.com
'Autohiding Scrollbar': false,
position: {
'embedding.position': 'top: 50px; right: 0px;',
'thread-stats.position': 'bottom: 0px; right: 0px;',
'updater.position': 'bottom: 0px; left: 0px;',
'embedding.position': 'top: 50px; right: 0px;',
'thread-stats.position': 'bottom: 0px; right: 0px;',
'updater.position': 'bottom: 0px; left: 0px;',
'thread-watcher.position': 'top: 50px; left: 0px;',
'qr.position': 'top: 50px; right: 0px;'
'qr.position': 'top: 50px; right: 0px;'
},
fourchanImageHost: 'i.4cdn.org',

View File

@ -61,7 +61,7 @@ export const g: {
VERSION: string,
NAMESPACE: string,
sites: (typeof SWTinyboard)[],
boards: Board[],
boards: SimpleDict<Board>,
posts?: SimpleDict<Post>,
threads?: SimpleDict<Thread>
THREADID?: number,
@ -90,7 +90,7 @@ export const E = (function () {
const output = function (text: string) {
return text.toString().replace(regex, fn)
}
output.cat = function (templates) {
output.cat = function (templates: HTMLCollectionOf<Element>) {
let html = ''
for (let i = 0; i < templates.length; i++) {
html += templates[i].innerHTML

View File

@ -62,6 +62,97 @@ $.setValue = function (key: string, value: string, cb) {
}
}
interface AjaxDetail {
url: string;
timeout: number;
responseType: XMLHttpRequestResponseType;
withCredentials: boolean;
type: string;
onprogress?: (e: ProgressEvent) => void;
form?: [string, string][];
headers?: Record<string, string>;
id: string;
}
$.ajaxPageInit = function (): void {
$.global(function (): void {
const r = new XMLHttpRequest()
window.FCX.requests = Object.create(null)
document.addEventListener('4chanXAjax', function (e): void {
let fd: FormData | null
const { url, timeout, responseType, withCredentials, type, onprogress, form, headers, id } = e.detail
window.FCX.requests[id] = (r = new XMLHttpRequest())
r.open(type, url, true)
const object = headers || {}
for (const key in object) {
const value = object[key]
r.setRequestHeader(key, value)
}
r.responseType = responseType === 'document' ? 'text' : responseType
r.timeout = timeout
r.withCredentials = withCredentials
if (onprogress) {
r.upload.onprogress = function (e: ProgressEvent) {
const { loaded, total } = e
const detail = { loaded, total, id }
return document.dispatchEvent(new CustomEvent('4chanXAjaxProgress', { bubbles: true, detail }))
}
}
r.onloadend = function (): void {
delete window.FCX.requests[id]
const { status, statusText, response } = this
const responseHeaderString = this.getAllResponseHeaders()
const detail = { status, statusText, response, responseHeaderString, id }
return document.dispatchEvent(new CustomEvent('4chanXAjaxLoadend', { bubbles: true, detail })) as any
}
// connection error or content blocker
r.onerror = function (): void {
if (!r.status) { return console.warn(`4chan X failed to load: ${url}`) }
}
if (form) {
fd = new FormData()
for (const entry of form) {
fd.append(entry[0], entry[1])
}
} else {
fd = null
}
return r.send(fd)
}, false)
return document.addEventListener('4chanXAbort', function (e): void {
const { id } = e.detail
if (window.FCX.requests[id]) {
window.FCX.requests[id].abort()
return delete window.FCX.requests[id]
}
}, false)
}, '4chanXAjax')
$.on(d, '4chanXAjaxProgress', function (e: CustomEvent<{ id: string; loaded: number; total: number }>): void {
let req: XMLHttpRequest
if (!(req = requests[e.detail.id])) { return }
return req.upload.onprogress.call(req.upload, e.detail)
})
return $.on(d, '4chanXAjaxLoadend', function (e: CustomEvent<AjaxDetail & { status: number; statusText: string; response: string; responseHeaderString: string }>): void {
let req: XMLHttpRequest
if (!(req = Request[e.detail.id])) { return }
delete Request[e.detail.id]
if (e.detail.status) {
for (const key of ['status', 'statusText', 'response', 'responseHeaderString']) {
req[key] = e.detail[key]
}
if (req.responseType === 'document') {
req.response = new DOMParser().parseFromString
(req.response, 'text/html')
}
return req.onloadend.call(req)
}
})
}
$.ajaxPage = function (url: string, options: AjaxPageOptions) {
const {
responseType = 'json',
@ -192,84 +283,7 @@ $.ajax = (function () {
// # XXX https://bugs.chromium.org/p/chromium/issues/detail?id=920638
let requestID = 0
const requests = dict()
$.ajaxPageInit = function () {
$.global(function () {
window.FCX.requests = Object.create(null)
document.addEventListener('4chanXAjax', function (e: CustomEvent) {
let fd, r
const { url, timeout, responseType, withCredentials, type, onprogress, form, headers, id } = e.detail
window.FCX.requests[id] = (r = new XMLHttpRequest())
r.open(type, url, true)
const object = headers || {}
for (const key in object) {
const value = object[key]
r.setRequestHeader(key, value)
}
r.responseType = responseType === 'document' ? 'text' : responseType
r.timeout = timeout
r.withCredentials = withCredentials
if (onprogress) {
r.upload.onprogress = function (e) {
const { loaded, total } = e
const detail = { loaded, total, id }
return document.dispatchEvent(new CustomEvent('4chanXAjaxProgress', { bubbles: true, detail }))
}
}
r.onloadend = function () {
delete window.FCX.requests[id]
const { status, statusText, response } = this
const responseHeaderString = this.getAllResponseHeaders()
const detail = { status, statusText, response, responseHeaderString, id }
return document.dispatchEvent(new CustomEvent('4chanXAjaxLoadend', { bubbles: true, detail }))
}
// connection error or content blocker
r.onerror = function () {
if (!r.status) { return console.warn(`4chan X failed to load: ${url}`) }
}
if (form) {
fd = new FormData()
for (const entry of form) {
fd.append(entry[0], entry[1])
}
} else {
fd = null
}
return r.send(fd)
}
, false)
return document.addEventListener('4chanXAjaxAbort', function (e) {
let r
if (!(r = window.FCX.requests[e.detail.id])) { return }
return r.abort()
}
, false)
}, '4chanXAjax')
$.on(d, '4chanXAjaxProgress', function (e) {
let req
if (!(req = requests[e.detail.id])) { return }
return req.upload.onprogress.call(req.upload, e.detail)
})
return $.on(d, '4chanXAjaxLoadend', function (e) {
let req
if (!(req = requests[e.detail.id])) { return }
delete requests[e.detail.id]
if (e.detail.status) {
for (const key of ['status', 'statusText', 'response', 'responseHeaderString']) {
req[key] = e.detail[key]
}
if (req.responseType === 'document') {
req.response = new DOMParser().parseFromString(e.detail.response, 'text/html')
}
}
return req.onloadend()
})
}
$.ajaxPageInit()
return $.ajaxPage = function (url, options = {}) {
let req: XMLHttpRequest
const { onloadend, timeout, responseType, withCredentials, type, onprogress, headers } = options
@ -314,7 +328,7 @@ $.whenModified = function (url, bucket, cb, options = {}) {
return r
}
$.cache = function (url, cb, options = {}) {
$.cache = function (url, cb, options) {
const reqs = dict()
let req
const { ajax } = options
@ -350,18 +364,9 @@ $.cleanCache = function (testf) {
}
$.cb = {
checked() {
if ($.hasOwn(Conf, this.name)) {
$.set(this.name, this.checked, this.type)
return Conf[this.name] = this.checked
}
},
value() {
if ($.hasOwn(Conf, this.name)) {
$.set(this.name, this.value.trim(), this.type)
return Conf[this.name] = this.value
}
$.cb = function (cb: VoidCallback) {
if (cb) {
return cb()
}
}
@ -492,7 +497,7 @@ $.one = function (el, events, handler) {
return $.on(el, events, cb)
}
let cloneInto: (obj: object, win: Window) => object
$.event = function (event: Event, detail: object, root = d) {
$.event = function (event: string, detail: object, root = d) {
if (!globalThis.chrome?.extension) {
if ((detail != null) && (typeof cloneInto === 'function')) {
detail = cloneInto(detail, d.defaultView)
@ -978,7 +983,7 @@ if (platform === 'crx') {
})
$.forceSync = function () {/* empty */ }
} else if ((typeof GM_deleteValue !== 'undefined' && GM_deleteValue !== null) || $.hasStorage) {
$.sync = function (key, cb) {
$.sync = function (key: string, cb: (newValue: any, key: string) => void) {
key = g.NAMESPACE + key
$.syncing[key] = cb
return $.oldValue[key] = $.getValue(key, cb)
@ -1000,10 +1005,7 @@ if (platform === 'crx') {
}
$.on(window, 'storage', onChange)
return $.forceSync = function (key, cb) {
// Storage events don't work across origins
// e.g. http://boards.4chan.org and https://boards.4chan.org
// so force a check for changes to avoid lost data.
return $.forceSync = function (key: string, cb: (newValue: any, key: string) => void) {
key = g.NAMESPACE + key
return onChange({ key, newValue: $.getValue(key, cb) })
}

View File

@ -19,6 +19,7 @@ const SWTinyboard = {
}
}
},
ID: 'sw-tinyboard',
name: 'Tinyboard',
software: 'Tinyboard',
isOPContainerThread: true,

View File

@ -16,4 +16,9 @@ export interface File {
sizeInBytes: number
isDead: boolean
docIndex: number
}
export interface CacheOptions {
dataType: string
sync: boolean
dontClean: boolean
}

View File

@ -9,7 +9,7 @@
"allowJs": true,
"checkJs": true,
//TODO: Flip this to true
"strict": false,
"strict": true,
"noEmit": true,
"jsx": "react",
"jsxFactory": "h",