Revert "Typescript and more"

This reverts commit 12483e97c52eb96965811a8e6c4c28cd3c45b19f.
This commit is contained in:
Lalle 2023-04-16 21:54:11 +02:00
parent bc41e49343
commit 27d267b4f0
No known key found for this signature in database
GPG Key ID: A6583D207A8F6B0D
31 changed files with 650 additions and 416 deletions

View File

@ -10,22 +10,7 @@ import { dict, HOUR } from '../platform/helpers'
* DS205: Consider reworking code to avoid use of IIFEs
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
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 = {
var BoardConfig = {
cbs: [],
init() {

View File

@ -161,9 +161,7 @@ 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}\
|\
(\
[-\\w\\d.@]+@[a-z\\d.-]+\\.[a-z\\d]\
)\
)`,
'i',
),

View File

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

161
src/Miscellaneous/Report.js Normal file
View File

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

View File

@ -0,0 +1,79 @@
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 {
let styleSelector: HTMLElement
let styleSelector
if (!(styleSelector = $.id('styleSelector'))) {
return
}

View File

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

View File

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

View File

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

42
src/classes/Board.js Normal file
View File

@ -0,0 +1,42 @@
import BoardConfig from '../General/BoardConfig'
import { d, g } from '../globals/globals'
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 {
toString() {
return this.ID
}
constructor(ID) {
this.ID = ID
this.boardID = this.ID
this.siteID = g.SITE.ID
this.threads = new SimpleDict()
this.posts = new SimpleDict()
this.config = BoardConfig.boards?.[this.ID] || {}
g.boards[this] = this
}
cooldowns() {
const c2 = (this.config || {}).cooldowns || {}
const c = {
thread: c2.threads || 0,
reply: c2.replies || 0,
image: c2.images || 0,
thread_global: 300, // inter-board thread cooldown
}
// Pass users have reduced cooldowns.
if (d.cookie.indexOf('pass_enabled=1') >= 0) {
for (var key of ['reply', 'image']) {
c[key] = Math.ceil(c[key] / 2)
}
}
return c
}
}

View File

@ -1,46 +0,0 @@
import BoardConfig from '../General/BoardConfig';
import { d, g } from '../globals/globals';
import Post from './Post';
import Thread from './Thread';
import SimpleDict from './SimpleDict';
export default class Board {
ID: string;
boardID: string;
siteID: string;
threads: SimpleDict<Thread>;
posts: SimpleDict<Post>;
config: any;
constructor(ID: string) {
this.ID = ID;
this.boardID = this.ID;
this.siteID = g.SITE.ID;
this.threads = new SimpleDict();
this.posts = new SimpleDict();
this.config = BoardConfig.domain(this.ID)
g.boards[this.ID] = this;
}
toString() {
return this.ID;
}
cooldowns() {
const c2 = (this.config || {}).cooldowns || {};
const c = {
thread: c2.threads || 0,
reply: c2.replies || 0,
image: c2.images || 0,
thread_global: 300, // inter-board thread cooldown
};
// Pass users have reduced cooldowns.
if (d.cookie.indexOf('pass_enabled=1') >= 0) {
for (let key of ['reply', 'image']) {
c[key] = Math.ceil(c[key] / 2);
}
}
return c;
}
}

65
src/classes/Callbacks.js Normal file
View File

@ -0,0 +1,65 @@
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 {
static initClass() {
this.Post = new Callbacks('Post')
this.Thread = new Callbacks('Thread')
this.CatalogThread = new Callbacks('Catalog Thread')
this.CatalogThreadNative = new Callbacks('Catalog Thread')
}
constructor(type) {
this.type = type
this.keys = []
}
push({ name, cb }) {
if (!this[name]) {
this.keys.push(name)
}
return (this[name] = cb)
}
execute(node, keys = this.keys, force = false) {
let errors
if (node.callbacksExecuted && !force) {
return
}
node.callbacksExecuted = true
for (var name of keys) {
try {
this[name]?.call(node)
} catch (err) {
if (!errors) {
errors = []
}
errors.push({
message: [
'"',
name,
'" crashed on node ',
this.type,
' No.',
node.ID,
' (',
node.board,
').',
].join(''),
error: err,
html: node.nodes?.root?.outerHTML,
})
}
}
if (errors) {
return Main.handleErrors(errors)
}
}
}
Callbacks.initClass()

View File

@ -1,68 +0,0 @@
import Main from '../main/Main';
export default class Callbacks {
private type: string;
private keys: string[];
static Post: Callbacks;
static Thread: Callbacks;
static CatalogThread: Callbacks;
static CatalogThreadNative: Callbacks;
static initClass() {
this.Post = new Callbacks('Post');
this.Thread = new Callbacks('Thread');
this.CatalogThread = new Callbacks('Catalog Thread');
this.CatalogThreadNative = new Callbacks('Catalog Thread');
}
constructor(type: string) {
this.type = type;
this.keys = [];
}
push({ name, cb }: { name: string; cb: () => void }) {
if (!this[name]) {
this.keys.push(name);
}
return (this[name] = cb);
}
execute(node: any, keys = this.keys, force = false) {
let errors: any[];
if (node.callbacksExecuted && !force) {
return;
}
node.callbacksExecuted = true;
for (let name of keys) {
try {
this[name]?.call(node);
} catch (err: any) {
if (!errors) {
errors = [];
}
errors.push({
message: [
'"',
name,
'" crashed on node ',
this.type,
' No.',
node.ID,
' (',
node.board,
').',
].join(''),
error: err,
html: node.nodes?.root?.outerHTML,
});
}
}
if (errors) {
return Main.handleErrors(errors);
}
}
}
Callbacks.initClass();

View File

@ -0,0 +1,24 @@
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

@ -1,37 +0,0 @@
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

@ -0,0 +1,23 @@
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

@ -1,34 +0,0 @@
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);
}
}

51
src/classes/Connection.js Normal file
View File

@ -0,0 +1,51 @@
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)
}
}
}
}

View File

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

View File

@ -1,22 +1,25 @@
/*
* 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/$'
class ShimSet {
elements: { [key: string]: boolean }
size: number
constructor() {
this.elements = $.dict()
this.size = 0
}
has(value: string) {
has(value) {
return value in this.elements
}
add(value: string) {
add(value) {
if (this.elements[value]) {
return
}
this.elements[value] = true
return this.size++
}
delete(value: string) {
delete(value) {
if (!this.elements[value]) {
return
}
@ -26,6 +29,5 @@ class ShimSet {
}
if (!('Set' in window)) {
// @ts-ignore
window.Set = ShimSet
}

View File

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

12
src/platform/$$.js Normal file
View File

@ -0,0 +1,12 @@
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 $$

View File

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

View File

@ -54,20 +54,6 @@ $.ajaxPage = function (url, options) {
r.send(form)
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) {
if (d.readyState !== 'loading') {
$.queueTask(fc)

View File

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

View File

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

View File

@ -1,29 +1,15 @@
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(
file: File | null,
ID: number,
boardID: string,
fileURL: string,
shortFilename: string,
fileThumb: string,
o: any,
staticPath: string,
gifIcon: string,
file,
ID,
boardID,
fileURL,
shortFilename,
fileThumb,
o,
staticPath,
gifIcon,
): EscapedHtml {
if (file) {
const fileContent: (EscapedHtml | string)[] = []

View File

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

View File

@ -1,5 +1,6 @@
import Redirect from "../Archive/Redirect";
import PassMessage from "../Miscellaneous/PassMessage";
import Report from "../Miscellaneous/Report";
import $ from "../platform/$";
import $$ from "../platform/$$";
import Captcha from "../Posting/Captcha";
@ -8,12 +9,20 @@ import ImageHost from "../Images/ImageHost";
import { g, Conf, E, d, doc } from "../globals/globals";
import BoardConfig from "../General/BoardConfig";
import CSS from "../css/CSS";
import generatePostInfoHtml from './SW.yotsuba.Build/PostInfoHtml';
import generateFileHtml from "./SW.yotsuba.Build/FileHtml";
import generateCatalogThreadHtml from "./SW.yotsuba.Build/CatalogThreadHtml";
import h, { hFragment, isEscaped } from "../globals/jsx";
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 = {
isOPContainerThread: false,
hasIPCount: true,