Typescript and more

This commit is contained in:
Lalle 2023-04-16 03:42:38 +02:00
parent 23cf79092b
commit 12483e97c5
No known key found for this signature in database
GPG Key ID: A6583D207A8F6B0D
29 changed files with 303 additions and 555 deletions

View File

@ -10,7 +10,22 @@ import { dict, HOUR } from '../platform/helpers'
* DS205: Consider reworking code to avoid use of IIFEs * DS205: Consider reworking code to avoid use of IIFEs
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/ */
var BoardConfig = {
interface BoardConfig {
cbs: (() => void)[]
init(): void
load(): void
set(boards: { [key: string]: any }): void
ready(cb: () => void): void
sfwBoards(sfw: boolean): string[]
isSFW(board: string): boolean
domain(board: string): string
isArchived(board: string): boolean
noAudio(boardID: string): boolean
title(boardID: string): string
}
var BoardConfig: BoardConfig = {
cbs: [], cbs: [],
init() { init() {

View File

@ -161,7 +161,9 @@ aero|asia|biz|cat|com|coop|dance|info|int|jobs|mobi|moe|museum|name|net|org|post
|\ |\
[\\d]{1,3}\\.[\\d]{1,3}\\.[\\d]{1,3}\\.[\\d]{1,3}\ [\\d]{1,3}\\.[\\d]{1,3}\\.[\\d]{1,3}\\.[\\d]{1,3}\
|\ |\
(\
[-\\w\\d.@]+@[a-z\\d.-]+\\.[a-z\\d]\ [-\\w\\d.@]+@[a-z\\d.-]+\\.[a-z\\d]\
)\
)`, )`,
'i', 'i',
), ),

View File

@ -45,7 +45,7 @@ var ReportLink = {
const { url, dims } = ReportLink const { url, dims } = ReportLink
const id = Date.now() const id = Date.now()
const set = `toolbar=0,scrollbars=1,location=0,status=1,menubar=0,resizable=1,${dims}` const set = `toolbar=0,scrollbars=1,location=0,status=1,menubar=0,resizable=1,${dims}`
return window.open(url, id, set) return window.open(url, id.toString(), set)
}, },
} }
export default ReportLink export default ReportLink

View File

@ -1,161 +0,0 @@
import Redirect from '../Archive/Redirect'
import $ from '../platform/$'
import ReportPage from './Report/ArchiveReport.html'
import CSS from '../css/CSS'
import Captcha from '../Posting/Captcha'
import { Conf, d, g } from '../globals/globals'
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
var Report = {
init() {
let match
if (!(match = location.search.match(/\bno=(\d+)/))) {
return
}
Captcha.replace.init()
this.postID = +match[1]
return $.ready(this.ready)
},
ready() {
$.addStyle(CSS.report)
if (Conf['Archive Report']) {
Report.archive()
}
new MutationObserver(function () {
Report.fit('iframe[src^="https://www.google.com/recaptcha/api2/frame"]')
return Report.fit('body')
}).observe(d.body, {
childList: true,
attributes: true,
subtree: true,
})
return Report.fit('body')
},
fit(selector) {
let el
if (
!((el = $(selector, el)) && getComputedStyle(el).visibility !== 'hidden')
) {
return
}
const dy = el.getBoundingClientRect().bottom - el.clientHeight + 8
if (dy > 0) {
return window.resizeBy(0, dy)
}
},
archive() {
let match, urls
if (!(urls = Redirect.report(g.BOARD.ID)).length) {
return
}
const form = $('form')
const types = $.id('reportTypes')
const message = $('h3')
const fieldset = $.el(
'fieldset',
{
id: 'archive-report',
hidden: true,
},
{ innerHTML: ReportPage },
)
const enabled = $('#archive-report-enabled', fieldset)
const reason = $('#archive-report-reason', fieldset)
const submit = $('#archive-report-submit', fieldset)
$.on(enabled, 'change', function () {
return (reason.disabled = !this.checked)
})
if (form && types) {
fieldset.hidden = !$('[value="31"]', types).checked
$.on(types, 'change', function (e) {
fieldset.hidden = e.target.value !== '31'
return Report.fit('body')
})
$.after(types, fieldset)
Report.fit('body')
$.one(form, 'submit', function (e) {
if (!fieldset.hidden && enabled.checked) {
e.preventDefault()
return Report.archiveSubmit(urls, reason.value, (results) => {
this.action =
'#archiveresults=' + encodeURIComponent(JSON.stringify(results))
return this.submit()
})
}
})
} else if (message) {
fieldset.hidden = /Report submitted!/.test(message.textContent)
$.on(enabled, 'change', function () {
return (submit.hidden = !this.checked)
})
$.after(message, fieldset)
$.on(submit, 'click', () =>
Report.archiveSubmit(urls, reason.value, Report.archiveResults),
)
}
if ((match = location.hash.match(/^#archiveresults=(.*)$/))) {
try {
return Report.archiveResults(JSON.parse(decodeURIComponent(match[1])))
} catch (error) {}
}
},
archiveSubmit(urls, reason, cb) {
const form = $.formData({
board: g.BOARD.ID,
num: Report.postID,
reason,
})
const results = []
for (var [name, url] of urls) {
;(function (name, url) {
return $.ajax(url, {
onloadend() {
results.push([name, this.response || { error: '' }])
if (results.length === urls.length) {
return cb(results)
}
},
form,
})
})(name, url)
}
},
archiveResults(results) {
const fieldset = $.id('archive-report')
for (var [name, response] of results) {
var line = $.el('h3', { className: 'archive-report-response' })
if ('success' in response) {
$.addClass(line, 'archive-report-success')
line.textContent = `${name}: ${response.success}`
} else {
$.addClass(line, 'archive-report-error')
line.textContent = `${name}: ${
response.error || 'Error reporting post.'
}`
}
if (fieldset) {
$.before(fieldset, line)
} else {
$.add(d.body, line)
}
}
},
}
export default Report

View File

@ -1,5 +1,4 @@
import $ from '../platform/$' import $ from '../platform/$'
import CaptchaReplace from './Captcha.replace'
import CaptchaT from './Captcha.t' import CaptchaT from './Captcha.t'
import meta from '../../package.json' import meta from '../../package.json'
import Main from '../main/Main' import Main from '../main/Main'
@ -178,7 +177,6 @@ const Captcha = {
return $.event('CaptchaCount', this.captchas.length) return $.event('CaptchaCount', this.captchas.length)
}, },
}, },
Replace: CaptchaReplace,
t: CaptchaT, t: CaptchaT,
v2: { v2: {
lifetime: 2 * MINUTE, lifetime: 2 * MINUTE,

View File

@ -1,79 +0,0 @@
import { g, Conf, doc, d } from '../globals/globals'
import Main from '../main/Main'
import $ from '../platform/$'
import Captcha from './Captcha'
const CaptchaReplace = {
init() {
if (
g.SITE.software !== 'yotsuba' ||
d.cookie.indexOf('pass_enabled=1') >= 0
) {
return
}
if (Conf['Force Noscript Captcha'] && Main.jsEnabled) {
$.ready(Captcha.replace.noscript)
return
}
if (Conf['captchaLanguage'].trim()) {
if (
['boards.4chan.org', 'boards.4channel.org'].includes(location.hostname)
) {
return $.onExists(doc, '#captchaFormPart', (node) =>
$.onExists(
node,
'iframe[src^="https://www.google.com/recaptcha/"]',
Captcha.replace.iframe,
),
)
} else {
return $.onExists(
doc,
'iframe[src^="https://www.google.com/recaptcha/"]',
Captcha.replace.iframe,
)
}
}
},
noscript() {
let noscript, original, toggle
if (
!(
(original = $('#g-recaptcha')) &&
(noscript = $('noscript', original.parentNode))
)
) {
return
}
const span = $.el('span', { id: 'captcha-forced-noscript' })
$.replace(noscript, span)
$.rm(original)
const insert = function () {
span.innerHTML = noscript.textContent
return Captcha.replace.iframe(
$('iframe[src^="https://www.google.com/recaptcha/"]', span),
)
}
if ((toggle = $('#togglePostFormLink a, #form-link'))) {
return $.on(toggle, 'click', insert)
} else {
return insert()
}
},
iframe(iframe) {
let lang
if ((lang = Conf['captchaLanguage'].trim())) {
const src = /[?&]hl=/.test(iframe.src)
? iframe.src.replace(/([?&]hl=)[^&]*/, '$1' + encodeURIComponent(lang))
: iframe.src + `&hl=${encodeURIComponent(lang)}`
if (iframe.src !== src) {
iframe.src = src
}
}
},
}
export default CaptchaReplace

View File

@ -11,7 +11,7 @@ const PassLink = {
}, },
ready(): void { ready(): void {
let styleSelector let styleSelector: HTMLElement
if (!(styleSelector = $.id('styleSelector'))) { if (!(styleSelector = $.id('styleSelector'))) {
return return
} }

View File

@ -1,13 +1,8 @@
import { d } from '../globals/globals' import { d } 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 PostRedirect = { const PostRedirect = {
init() { init(): void {
return $.on(d, 'QRPostSuccessful', (e) => { return $.on(d, 'QRPostSuccessful', (e) => {
if (!e.detail.redirect) { if (!e.detail.redirect) {
return return
@ -24,7 +19,7 @@ const PostRedirect = {
delays: 0, delays: 0,
delay() { delay(): (() => void) | null {
if (!this.event) { if (!this.event) {
return null return null
} }

View File

@ -69,14 +69,7 @@ var QuotePreview = {
$.add(Header.hover, qp) $.add(Header.hover, qp)
new Fetcher(boardID, threadID, postID, qp, Get.postFromNode(this)) new Fetcher(boardID, threadID, postID, qp, Get.postFromNode(this))
UI.hover({ UI.hover({root: this, el: qp, latestEvent: e, endEvents: 'mouseout click', cb: QuotePreview.mouseout, cbArgs: [this]})
root: this,
el: qp,
latestEvent: e,
endEvents: 'mouseout click',
cb: QuotePreview.mouseout,
})
if ( if (
Conf['Quote Highlighting'] && Conf['Quote Highlighting'] &&
(origin = g.posts.get(`${boardID}.${postID}`)) (origin = g.posts.get(`${boardID}.${postID}`))

View File

@ -3,13 +3,8 @@ import Get from '../General/Get'
import { g, Conf } from '../globals/globals' import { g, Conf } 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 QuoteStrikeThrough = { const QuoteStrikeThrough = {
init() { init(): void {
if ( if (
!['index', 'thread'].includes(g.VIEW) || !['index', 'thread'].includes(g.VIEW) ||
(!Conf['Reply Hiding Buttons'] && (!Conf['Reply Hiding Buttons'] &&
@ -25,7 +20,7 @@ const QuoteStrikeThrough = {
}) })
}, },
node() { node(): void {
if (this.isClone) { if (this.isClone) {
return return
} }

View File

@ -2,11 +2,6 @@ import BoardConfig from '../General/BoardConfig'
import { d, g } from '../globals/globals' import { d, g } from '../globals/globals'
import SimpleDict from './SimpleDict' import SimpleDict from './SimpleDict'
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
export default class Board { export default class Board {
toString() { toString() {
return this.ID return this.ID
@ -20,7 +15,7 @@ export default class Board {
this.posts = new SimpleDict() this.posts = new SimpleDict()
this.config = BoardConfig.boards?.[this.ID] || {} this.config = BoardConfig.boards?.[this.ID] || {}
g.boards[this] = this g.boards[this.ID] = this
} }
cooldowns() { cooldowns() {

View File

@ -1,11 +1,5 @@
import Main from '../main/Main' import Main from '../main/Main'
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS206: Consider reworking classes to avoid initClass
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
export default class Callbacks { export default class Callbacks {
static initClass() { static initClass() {
this.Post = new Callbacks('Post') this.Post = new Callbacks('Post')

View File

@ -1,24 +0,0 @@
import $ from '../platform/$'
export default class CatalogThread {
toString() {
return this.ID
}
constructor(root, thread) {
this.thread = thread
this.ID = this.thread.ID
this.board = this.thread.board
const { post } = this.thread.OP.nodes
this.nodes = {
root,
thumb: $('.catalog-thumb', post),
icons: $('.catalog-icons', post),
postCount: $('.post-count', post),
fileCount: $('.file-count', post),
pageCount: $('.page-count', post),
replies: null,
}
this.thread.catalogView = this
}
}

View File

@ -0,0 +1,37 @@
import $ from '../platform/$';
export default class CatalogThread {
private thread: any;
private ID: number;
private board: string;
private nodes: {
root: any,
thumb: HTMLElement,
icons: HTMLElement,
postCount: HTMLElement,
fileCount: HTMLElement,
pageCount: HTMLElement,
replies: null | any,
};
constructor(root: any, thread: any) {
this.thread = thread;
this.ID = this.thread.ID;
this.board = this.thread.board;
const { post } = this.thread.OP.nodes;
this.nodes = {
root,
thumb: $('.catalog-thumb', post),
icons: $('.catalog-icons', post),
postCount: $('.post-count', post),
fileCount: $('.file-count', post),
pageCount: $('.page-count', post),
replies: null,
};
this.thread.catalogView = this;
}
public toString(): string {
return this.ID.toString();
}
}

View File

@ -1,23 +0,0 @@
import { g } from '../globals/globals'
import $ from '../platform/$'
import Board from './Board'
import Thread from './Thread'
export default class CatalogThreadNative {
toString() {
return this.ID
}
constructor(root) {
this.nodes = {
root,
thumb: $(g.SITE.selectors.catalog.thumb, root),
}
this.siteID = g.SITE.ID
this.boardID = this.nodes.thumb.parentNode.pathname.split(/\/+/)[1]
this.board = g.boards[this.boardID] || new Board(this.boardID)
this.ID = this.threadID = +(root.dataset.id || root.id).match(/\d*$/)[0]
this.thread =
this.board.threads.get(this.ID) || new Thread(this.ID, this.board)
}
}

View File

@ -0,0 +1,34 @@
import { g } from '../globals/globals';
import $ from '../platform/$';
import Board from './Board';
import Thread from './Thread';
export default class CatalogThreadNative {
nodes: {
root: HTMLElement;
thumb: any;
};
siteID: string;
boardID: string;
board: Board;
ID: number;
threadID: number;
thread: Thread;
toString() {
return this.ID.toString();
}
constructor(root: HTMLElement) {
this.nodes = {
root,
thumb: $(g.SITE.selectors.catalog.thumb, root),
};
this.siteID = g.SITE.ID;
this.boardID = this.nodes.thumb[0].parentNode.pathname.split(/\/+/)[1];
this.board = g.boards[this.boardID] || new Board(this.boardID);
this.ID = this.threadID = +(root.dataset.id || root.id).match(/\d*$/)[0];
this.thread =
this.board.threads.get(this.ID) || new Thread(this.ID, this.board);
}
}

View File

@ -1,51 +0,0 @@
import $ from '../platform/$'
import { g } from '../globals/globals'
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
export default class Connection {
constructor(target, origin, cb = {}) {
this.send = this.send.bind(this)
this.onMessage = this.onMessage.bind(this)
this.target = target
this.origin = origin
this.cb = cb
$.on(window, 'message', this.onMessage)
}
targetWindow() {
if (this.target instanceof window.HTMLIFrameElement) {
return this.target.contentWindow
} else {
return this.target
}
}
send(data) {
return this.targetWindow().postMessage(
`${g.NAMESPACE}${JSON.stringify(data)}`,
this.origin,
)
}
onMessage(e) {
if (
e.source !== this.targetWindow() ||
e.origin !== this.origin ||
typeof e.data !== 'string' ||
e.data.slice(0, g.NAMESPACE.length) !== g.NAMESPACE
) {
return
}
const data = JSON.parse(e.data.slice(g.NAMESPACE.length))
for (var type in data) {
var value = data[type]
if ($.hasOwn(this.cb, type)) {
this.cb[type](value)
}
}
}
}

54
src/classes/Connection.ts Normal file
View File

@ -0,0 +1,54 @@
import $ from '../platform/$';
import { g } from '../globals/globals';
interface Callbacks {
[key: string]: (value: any) => void;
}
export default class Connection {
private target: Window | HTMLIFrameElement;
private origin: string;
private cb: Callbacks;
constructor(target: Window | HTMLIFrameElement, origin: string, cb: Callbacks = {}) {
this.send = this.send.bind(this);
this.onMessage = this.onMessage.bind(this);
this.target = target;
this.origin = origin;
this.cb = cb;
$.on(window, 'message', this.onMessage);
}
private targetWindow(): Window {
if (this.target instanceof window.HTMLIFrameElement) {
return this.target.contentWindow;
} else {
return this.target;
}
}
public send(data: any): void {
return this.targetWindow().postMessage(
`${g.NAMESPACE}${JSON.stringify(data)}`,
this.origin,
);
}
private onMessage(e: MessageEvent): void {
if (
e.source !== this.targetWindow() ||
e.origin !== this.origin ||
typeof e.data !== 'string' ||
e.data.slice(0, g.NAMESPACE.length) !== g.NAMESPACE
) {
return;
}
const data = JSON.parse(e.data.slice(g.NAMESPACE.length));
for (const type in data) {
const value = data[type];
if ($.hasOwn(this.cb, type)) {
this.cb[type](value);
}
}
}
}

View File

@ -11,7 +11,6 @@ import CrossOrigin from '../platform/CrossOrigin'
import Get from '../General/Get' import Get from '../General/Get'
import { dict } from '../platform/helpers' import { dict } from '../platform/helpers'
export default class Fetcher { export default class Fetcher {
static initClass() { static initClass() {
this.prototype.archiveTags = { this.prototype.archiveTags = {
@ -80,53 +79,20 @@ export default class Fetcher {
this.root.textContent = `Loading post No.${this.postID}...` this.root.textContent = `Loading post No.${this.postID}...`
if (this.threadID) { if (this.threadID) {
const that = this const that = this
Fetcher.fetchThread( $.cache(
this.boardID, g.SITE.urls.threadJSON({
this.threadID, boardID: this.boardID,
function (req, isCached) { threadID: this.threadID,
that.fetchedThread(req, isCached) }),
function ({ isCached }) {
return that.fetchedPost(this, isCached)
}, },
true,
) )
} else { } else {
const that = this this.archivedPost()
Fetcher.fetchPost(
this.boardID,
this.postID,
function (req, isCached) {
that.fetchedPost(req, isCached)
},
true,
)
} }
} }
fetchedThread(req) {
const { status, response } = req
const { boardID, threadID } = this
const board = g.boards[boardID]
if (status === 404) {
this.root.textContent = `Thread No.${threadID} not found.`
return
}
if (status !== 200) {
this.root.textContent = `Error loading thread No.${threadID}.`
return
}
if (response === '') {
this.root.textContent = `Thread No.${threadID} is empty.`
return
}
const thread = new Thread(
g.SITE.Build.threadFromObject(response, boardID),board)
Main.callbackNodes('Thread', [thread])
const post = thread.posts.get(this.postID)
if (post) {
this.insert(post)
} else {
this.root.textContent = `Post No.${this.postID} not found.`
}
}
insert(post) { insert(post) {
// Stop here if the container has been removed while loading. // Stop here if the container has been removed while loading.
if (!this.root.parentNode) { if (!this.root.parentNode) {
@ -180,75 +146,76 @@ export default class Fetcher {
} }
fetchedPost(req, isCached) { fetchedPost(req, isCached) {
const { status, response } = req; // In case of multiple callbacks for the same request,
const { boardID, postID, threadID } = this; // don't parse the same original post more than once.
const postKey = `${boardID}.${postID}`; let post
if ((post = g.posts.get(`${this.boardID}.${this.postID}`))) {
const post = g.posts.get(postKey); this.insert(post)
if (post) { return
this.insert(post);
return;
} }
const { status } = req
if (status !== 200) { if (status !== 200) {
this.handleNon200Status(status); // The thread can die by the time we check a quote.
return; if (status && this.archivedPost()) {
return
}
$.addClass(this.root, 'warning')
this.root.textContent =
status === 404
? `Thread No.${this.threadID} 404'd.`
: !status
? 'Connection Error'
: `Error ${req.statusText} (${req.status}).`
return
} }
const { posts } = response; const { posts } = req.response
g.SITE.Build.spoilerRange[boardID] = posts[0].custom_spoiler; g.SITE.Build.spoilerRange[this.boardID] = posts[0].custom_spoiler
for (post of posts) {
if (post.no === this.postID) {
break
}
} // we found it!
const foundPost = posts.find((p) => p.no === postID); if (post.no !== this.postID) {
// Cached requests can be stale and must be rechecked.
if (isCached) {
const api = g.SITE.urls.threadJSON({
boardID: this.boardID,
threadID: this.threadID,
})
$.cleanCache((url) => url === api)
const that = this
$.cache(api, function () {
return that.fetchedPost(this, false)
})
return
}
if (!foundPost) { // The post can be deleted by the time we check a quote.
this.handlePostNotFound(isCached); if (this.archivedPost()) {
return; return
}
$.addClass(this.root, 'warning')
this.root.textContent = `Post No.${this.postID} was not found.`
return
} }
const board = g.boards[boardID] || new Board(boardID); const board = g.boards[this.boardID] || new Board(this.boardID)
const threadKey = `${boardID}.${threadID}`; const thread =
const thread = g.threads.get(threadKey) || new Thread(threadID, board); g.threads.get(`${this.boardID}.${this.threadID}`) ||
const newPost = new Post( new Thread(this.threadID, board)
g.SITE.Build.postFromObject(foundPost, boardID), post = new Post(
g.SITE.Build.postFromObject(post, this.boardID),
thread, thread,
board, board,
{ isFetchedQuote: true }, { isFetchedQuote: true },
); )
Main.callbackNodes("Post", [newPost]); Main.callbackNodes('Post', [post])
return this.insert(newPost); return this.insert(post)
}
handleNon200Status(status, req) {
$.addClass(this.root, "warning");
this.root.textContent =
status === 404
? `Thread No.${this.threadID} 404'd.`
: !status
? "Connection Error"
: `Error ${req.statusText} (${req.status}).`;
if (status && this.archivedPost()) {
return;
}
}
handlePostNotFound(isCached) {
if (isCached) {
const api = g.SITE.urls.threadJSON({
boardID: this.boardID,
threadID: this.threadID,
});
$.cleanCache((url) => url === api);
$.cache(api, () => this.fetchedPost(this, false));
return;
}
if (this.archivedPost()) {
return;
}
$.addClass(this.root, "warning");
this.root.textContent = `Post No.${this.postID} was not found.`;
} }
archivedPost() { archivedPost() {

View File

@ -1,25 +1,22 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
import $ from '../platform/$' import $ from '../platform/$'
class ShimSet { class ShimSet {
elements: { [key: string]: boolean }
size: number
constructor() { constructor() {
this.elements = $.dict() this.elements = $.dict()
this.size = 0 this.size = 0
} }
has(value) { has(value: string) {
return value in this.elements return value in this.elements
} }
add(value) { add(value: string) {
if (this.elements[value]) { if (this.elements[value]) {
return return
} }
this.elements[value] = true this.elements[value] = true
return this.size++ return this.size++
} }
delete(value) { delete(value: string) {
if (!this.elements[value]) { if (!this.elements[value]) {
return return
} }
@ -29,5 +26,6 @@ class ShimSet {
} }
if (!('Set' in window)) { if (!('Set' in window)) {
// @ts-ignore
window.Set = ShimSet window.Set = ShimSet
} }

View File

@ -86,7 +86,6 @@ import Header from '../General/Header'
import { c, Conf, d, doc, docSet, E, g } from '../globals/globals' import { c, Conf, d, doc, docSet, E, g } from '../globals/globals'
import Menu from '../Menu/Menu' import Menu from '../Menu/Menu'
import BoardConfig from '../General/BoardConfig' import BoardConfig from '../General/BoardConfig'
import CaptchaReplace from '../Posting/Captcha.replace'
import Get from '../General/Get' import Get from '../General/Get'
import { dict, platform } from '../platform/helpers' import { dict, platform } from '../platform/helpers'
import Polyfill from '../General/Polyfill' import Polyfill from '../General/Polyfill'
@ -1075,7 +1074,6 @@ User agent: ${navigator.userAgent}\
['Board Configuration', BoardConfig], ['Board Configuration', BoardConfig],
['Normalize URL', NormalizeURL], ['Normalize URL', NormalizeURL],
['Delay Redirect on Post', PostRedirect], ['Delay Redirect on Post', PostRedirect],
['Captcha Configuration', CaptchaReplace],
['Image Host Rewriting', ImageHost], ['Image Host Rewriting', ImageHost],
['Redirect', Redirect], ['Redirect', Redirect],
['Header', Header], ['Header', Header],

View File

@ -1,12 +0,0 @@
import { d } from '../globals/globals'
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
const $$ = (selector, root = d.body) => [
...Array.from(root.querySelectorAll(selector)),
]
export default $$

4
src/platform/$$.ts Normal file
View File

@ -0,0 +1,4 @@
const $$ = (selector: string, root: HTMLElement | null = document.body): Element[] =>
Array.from(root?.querySelectorAll(selector) ?? []) as Element[];
export default $$;

View File

@ -54,6 +54,20 @@ $.ajaxPage = function (url, options) {
r.send(form) r.send(form)
return r return r
} }
$.cache = function (key, value, time) {
if (value == null) {
value = null
}
if (time == null) {
time = MINUTE
}
if (value) {
return $.set(key, value, time)
} else {
return $.get(key)
}
}
$.ready = function (fc) { $.ready = function (fc) {
if (d.readyState !== 'loading') { if (d.readyState !== 'loading') {
$.queueTask(fc) $.queueTask(fc)

View File

@ -25,7 +25,7 @@ export const debounce = (wait: number, fn: Function) => {
export const dict = () => Object.create(null) export const dict = () => Object.create(null)
dict.clone = function (obj) { dict.clone = function (obj: object) {
if (typeof obj !== 'object' || obj === null) { if (typeof obj !== 'object' || obj === null) {
return obj return obj
} else if (obj instanceof Array) { } else if (obj instanceof Array) {

View File

@ -2,14 +2,14 @@ import h, { hFragment, EscapedHtml } from '../../globals/jsx'
export default function generateCatalogThreadHtml( export default function generateCatalogThreadHtml(
thread, thread,
src, src: string,
imgClass, imgClass: string,
data, data: any,
postCount, postCount: number,
fileCount, fileCount: number,
pageCount, pageCount: number,
staticPath, staticPath: string,
gifIcon, gifIcon: string,
): EscapedHtml { ): EscapedHtml {
return ( return (
<> <>

View File

@ -1,15 +1,29 @@
import h, { EscapedHtml, isEscaped } from '../../globals/jsx' import h, { EscapedHtml, isEscaped } from '../../globals/jsx'
type File = {
MD5: string
name: string
size: string
dimensions: string
tag: string
width: number
height: number
twidth: number
theight: number
hasDownscale: boolean
isSpoiler: boolean
}
export default function generateFileHtml( export default function generateFileHtml(
file, file: File | null,
ID, ID: number,
boardID, boardID: string,
fileURL, fileURL: string,
shortFilename, shortFilename: string,
fileThumb, fileThumb: string,
o, o: any,
staticPath, staticPath: string,
gifIcon, gifIcon: string,
): EscapedHtml { ): EscapedHtml {
if (file) { if (file) {
const fileContent: (EscapedHtml | string)[] = [] const fileContent: (EscapedHtml | string)[] = []

View File

@ -2,29 +2,29 @@ import { g } from '../../globals/globals'
import h, { EscapedHtml } from '../../globals/jsx' import h, { EscapedHtml } from '../../globals/jsx'
export default function generatePostInfoHtml( export default function generatePostInfoHtml(
ID, ID: number,
o, o: any,
subject, subject: string,
capcode, capcode: string,
email, email: string,
name, name: string,
tripcode, tripcode: string,
pass, pass: string,
capcodeLC, capcodeLC: string,
capcodePlural, capcodePlural: string,
staticPath, staticPath: string,
gifIcon, gifIcon: string,
capcodeDescription, capcodeDescription: string,
uniqueID, uniqueID: string,
flag, flag: string,
flagCode, flagCode: string,
flagCodeTroll, flagCodeTroll: string,
dateUTC, dateUTC: string,
dateText, dateText: string,
postLink, postLink: string,
quoteLink, quoteLink: string,
boardID, boardID: string,
threadID, threadID: number,
): EscapedHtml { ): EscapedHtml {
const nameHtml: (EscapedHtml | string)[] = [ const nameHtml: (EscapedHtml | string)[] = [
<span class={`name${capcode ? ' ' + capcode : ''}`}>{name}</span>, <span class={`name${capcode ? ' ' + capcode : ''}`}>{name}</span>,

View File

@ -1,6 +1,5 @@
import Redirect from "../Archive/Redirect"; import Redirect from "../Archive/Redirect";
import PassMessage from "../Miscellaneous/PassMessage"; import PassMessage from "../Miscellaneous/PassMessage";
import Report from "../Miscellaneous/Report";
import $ from "../platform/$"; import $ from "../platform/$";
import $$ from "../platform/$$"; import $$ from "../platform/$$";
import Captcha from "../Posting/Captcha"; import Captcha from "../Posting/Captcha";
@ -9,20 +8,12 @@ import ImageHost from "../Images/ImageHost";
import { g, Conf, E, d, doc } from "../globals/globals"; import { g, Conf, E, d, doc } from "../globals/globals";
import BoardConfig from "../General/BoardConfig"; import BoardConfig from "../General/BoardConfig";
import CSS from "../css/CSS"; import CSS from "../css/CSS";
import generatePostInfoHtml from './SW.yotsuba.Build/PostInfoHtml'; import generatePostInfoHtml from './SW.yotsuba.Build/PostInfoHtml';
import generateFileHtml from "./SW.yotsuba.Build/FileHtml"; import generateFileHtml from "./SW.yotsuba.Build/FileHtml";
import generateCatalogThreadHtml from "./SW.yotsuba.Build/CatalogThreadHtml"; import generateCatalogThreadHtml from "./SW.yotsuba.Build/CatalogThreadHtml";
import h, { hFragment, isEscaped } from "../globals/jsx"; import h, { hFragment, isEscaped } from "../globals/jsx";
import { dict, MINUTE } from "../platform/helpers"; import { dict, MINUTE } from "../platform/helpers";
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
const SWYotsuba = { const SWYotsuba = {
isOPContainerThread: false, isOPContainerThread: false,
hasIPCount: true, hasIPCount: true,