diff --git a/src/General/BoardConfig.js b/src/General/BoardConfig.ts similarity index 88% rename from src/General/BoardConfig.js rename to src/General/BoardConfig.ts index e1c5248..b73e275 100644 --- a/src/General/BoardConfig.js +++ b/src/General/BoardConfig.ts @@ -10,7 +10,22 @@ 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 */ -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: [], init() { diff --git a/src/Linkification/Linkify.js b/src/Linkification/Linkify.js index c2b64c1..e3ac40a 100644 --- a/src/Linkification/Linkify.js +++ b/src/Linkification/Linkify.js @@ -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}\ |\ +(\ [-\\w\\d.@]+@[a-z\\d.-]+\\.[a-z\\d]\ +)\ )`, 'i', ), diff --git a/src/Menu/ReportLink.js b/src/Menu/ReportLink.js index 3969948..d36668a 100644 --- a/src/Menu/ReportLink.js +++ b/src/Menu/ReportLink.js @@ -45,7 +45,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, set) + return window.open(url, id.toString(), set) }, } export default ReportLink diff --git a/src/Miscellaneous/Report.js b/src/Miscellaneous/Report.js deleted file mode 100644 index 182d132..0000000 --- a/src/Miscellaneous/Report.js +++ /dev/null @@ -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 diff --git a/src/Posting/Captcha.js b/src/Posting/Captcha.js index af760a6..e6d7bf6 100644 --- a/src/Posting/Captcha.js +++ b/src/Posting/Captcha.js @@ -1,5 +1,4 @@ import $ from '../platform/$' -import CaptchaReplace from './Captcha.replace' import CaptchaT from './Captcha.t' import meta from '../../package.json' import Main from '../main/Main' @@ -178,7 +177,6 @@ const Captcha = { return $.event('CaptchaCount', this.captchas.length) }, }, - Replace: CaptchaReplace, t: CaptchaT, v2: { lifetime: 2 * MINUTE, diff --git a/src/Posting/Captcha.replace.js b/src/Posting/Captcha.replace.js deleted file mode 100644 index 8b780d0..0000000 --- a/src/Posting/Captcha.replace.js +++ /dev/null @@ -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 diff --git a/src/Posting/PassLink.ts b/src/Posting/PassLink.ts index 6b5b72f..123d7ae 100644 --- a/src/Posting/PassLink.ts +++ b/src/Posting/PassLink.ts @@ -11,7 +11,7 @@ const PassLink = { }, ready(): void { - let styleSelector + let styleSelector: HTMLElement if (!(styleSelector = $.id('styleSelector'))) { return } diff --git a/src/Posting/PostRedirect.js b/src/Posting/PostRedirect.ts similarity index 77% rename from src/Posting/PostRedirect.js rename to src/Posting/PostRedirect.ts index 9dd3d78..617439b 100644 --- a/src/Posting/PostRedirect.js +++ b/src/Posting/PostRedirect.ts @@ -1,13 +1,8 @@ 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() { + init(): void { return $.on(d, 'QRPostSuccessful', (e) => { if (!e.detail.redirect) { return @@ -24,7 +19,7 @@ const PostRedirect = { delays: 0, - delay() { + delay(): (() => void) | null { if (!this.event) { return null } diff --git a/src/Quotelinks/QuotePreview.js b/src/Quotelinks/QuotePreview.js index 344ffbe..3a31cea 100644 --- a/src/Quotelinks/QuotePreview.js +++ b/src/Quotelinks/QuotePreview.js @@ -69,14 +69,7 @@ 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, - }) - + UI.hover({root: this, el: qp, latestEvent: e, endEvents: 'mouseout click', cb: QuotePreview.mouseout, cbArgs: [this]}) if ( Conf['Quote Highlighting'] && (origin = g.posts.get(`${boardID}.${postID}`)) diff --git a/src/Quotelinks/QuoteStrikeThrough.js b/src/Quotelinks/QuoteStrikeThrough.ts similarity index 78% rename from src/Quotelinks/QuoteStrikeThrough.js rename to src/Quotelinks/QuoteStrikeThrough.ts index 0a9110c..35d24c3 100644 --- a/src/Quotelinks/QuoteStrikeThrough.js +++ b/src/Quotelinks/QuoteStrikeThrough.ts @@ -3,13 +3,8 @@ 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() { + init(): void { if ( !['index', 'thread'].includes(g.VIEW) || (!Conf['Reply Hiding Buttons'] && @@ -25,7 +20,7 @@ const QuoteStrikeThrough = { }) }, - node() { + node(): void { if (this.isClone) { return } diff --git a/src/classes/Board.js b/src/classes/Board.js index 5b68197..e2add5c 100644 --- a/src/classes/Board.js +++ b/src/classes/Board.js @@ -2,11 +2,6 @@ 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 @@ -20,7 +15,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() { diff --git a/src/classes/Callbacks.js b/src/classes/Callbacks.js index c35106b..9aa4463 100644 --- a/src/classes/Callbacks.js +++ b/src/classes/Callbacks.js @@ -1,11 +1,5 @@ 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') diff --git a/src/classes/CatalogThread.js b/src/classes/CatalogThread.js deleted file mode 100644 index 829ec4d..0000000 --- a/src/classes/CatalogThread.js +++ /dev/null @@ -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 - } -} diff --git a/src/classes/CatalogThread.ts b/src/classes/CatalogThread.ts new file mode 100644 index 0000000..8542f16 --- /dev/null +++ b/src/classes/CatalogThread.ts @@ -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(); + } +} \ No newline at end of file diff --git a/src/classes/CatalogThreadNative.js b/src/classes/CatalogThreadNative.js deleted file mode 100644 index 8cfc701..0000000 --- a/src/classes/CatalogThreadNative.js +++ /dev/null @@ -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) - } -} diff --git a/src/classes/CatalogThreadNative.ts b/src/classes/CatalogThreadNative.ts new file mode 100644 index 0000000..2a322d8 --- /dev/null +++ b/src/classes/CatalogThreadNative.ts @@ -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); + } +} diff --git a/src/classes/Connection.js b/src/classes/Connection.js deleted file mode 100644 index 647cfb9..0000000 --- a/src/classes/Connection.js +++ /dev/null @@ -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) - } - } - } -} diff --git a/src/classes/Connection.ts b/src/classes/Connection.ts new file mode 100644 index 0000000..31ea358 --- /dev/null +++ b/src/classes/Connection.ts @@ -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); + } + } + } +} diff --git a/src/classes/Fetcher.js b/src/classes/Fetcher.js index 48c5bee..6f8d8b7 100644 --- a/src/classes/Fetcher.js +++ b/src/classes/Fetcher.js @@ -11,7 +11,6 @@ import CrossOrigin from '../platform/CrossOrigin' import Get from '../General/Get' import { dict } from '../platform/helpers' - export default class Fetcher { static initClass() { this.prototype.archiveTags = { @@ -80,53 +79,20 @@ export default class Fetcher { this.root.textContent = `Loading post No.${this.postID}...` if (this.threadID) { const that = this - Fetcher.fetchThread( - this.boardID, - this.threadID, - function (req, isCached) { - that.fetchedThread(req, isCached) + $.cache( + g.SITE.urls.threadJSON({ + boardID: this.boardID, + threadID: this.threadID, + }), + function ({ isCached }) { + return that.fetchedPost(this, isCached) }, - true, ) } else { - 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.` + this.archivedPost() } } + insert(post) { // Stop here if the container has been removed while loading. if (!this.root.parentNode) { @@ -180,75 +146,76 @@ export default class Fetcher { } fetchedPost(req, isCached) { - 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; + // 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 } = req if (status !== 200) { - this.handleNon200Status(status); - return; + // 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 } - - 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 { 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 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), + + 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), thread, board, { isFetchedQuote: true }, - ); - 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.`; + ) + Main.callbackNodes('Post', [post]) + return this.insert(post) } archivedPost() { @@ -453,4 +420,4 @@ export default class Fetcher { return this.insert(post) } } -Fetcher.initClass() +Fetcher.initClass() \ No newline at end of file diff --git a/src/classes/ShimSet.js b/src/classes/ShimSet.ts similarity index 63% rename from src/classes/ShimSet.js rename to src/classes/ShimSet.ts index 0ddff27..b89ff77 100644 --- a/src/classes/ShimSet.js +++ b/src/classes/ShimSet.ts @@ -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/$' class ShimSet { + elements: { [key: string]: boolean } + size: number constructor() { this.elements = $.dict() this.size = 0 } - has(value) { + has(value: string) { return value in this.elements } - add(value) { + add(value: string) { if (this.elements[value]) { return } this.elements[value] = true return this.size++ } - delete(value) { + delete(value: string) { if (!this.elements[value]) { return } @@ -29,5 +26,6 @@ class ShimSet { } if (!('Set' in window)) { + // @ts-ignore window.Set = ShimSet } diff --git a/src/main/Main.js b/src/main/Main.js index 980675e..f951515 100644 --- a/src/main/Main.js +++ b/src/main/Main.js @@ -86,7 +86,6 @@ 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' @@ -1075,7 +1074,6 @@ 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], diff --git a/src/platform/$$.js b/src/platform/$$.js deleted file mode 100644 index 11ec207..0000000 --- a/src/platform/$$.js +++ /dev/null @@ -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 $$ diff --git a/src/platform/$$.ts b/src/platform/$$.ts new file mode 100644 index 0000000..48e8e4e --- /dev/null +++ b/src/platform/$$.ts @@ -0,0 +1,4 @@ +const $$ = (selector: string, root: HTMLElement | null = document.body): Element[] => + Array.from(root?.querySelectorAll(selector) ?? []) as Element[]; + +export default $$; diff --git a/src/platform/$.js b/src/platform/$.js index a6f63b5..e4520c3 100644 --- a/src/platform/$.js +++ b/src/platform/$.js @@ -54,6 +54,20 @@ $.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) diff --git a/src/platform/helpers.ts b/src/platform/helpers.ts index 5cbdb27..758dada 100644 --- a/src/platform/helpers.ts +++ b/src/platform/helpers.ts @@ -25,7 +25,7 @@ export const debounce = (wait: number, fn: Function) => { export const dict = () => Object.create(null) -dict.clone = function (obj) { +dict.clone = function (obj: object) { if (typeof obj !== 'object' || obj === null) { return obj } else if (obj instanceof Array) { diff --git a/src/site/SW.yotsuba.Build/CatalogThreadHtml.tsx b/src/site/SW.yotsuba.Build/CatalogThreadHtml.tsx index cd59623..90b6bdd 100644 --- a/src/site/SW.yotsuba.Build/CatalogThreadHtml.tsx +++ b/src/site/SW.yotsuba.Build/CatalogThreadHtml.tsx @@ -2,14 +2,14 @@ import h, { hFragment, EscapedHtml } from '../../globals/jsx' export default function generateCatalogThreadHtml( thread, - src, - imgClass, - data, - postCount, - fileCount, - pageCount, - staticPath, - gifIcon, + src: string, + imgClass: string, + data: any, + postCount: number, + fileCount: number, + pageCount: number, + staticPath: string, + gifIcon: string, ): EscapedHtml { return ( <> diff --git a/src/site/SW.yotsuba.Build/FileHtml.tsx b/src/site/SW.yotsuba.Build/FileHtml.tsx index c0d1497..d7eeec6 100644 --- a/src/site/SW.yotsuba.Build/FileHtml.tsx +++ b/src/site/SW.yotsuba.Build/FileHtml.tsx @@ -1,15 +1,29 @@ 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, - ID, - boardID, - fileURL, - shortFilename, - fileThumb, - o, - staticPath, - gifIcon, + file: File | null, + ID: number, + boardID: string, + fileURL: string, + shortFilename: string, + fileThumb: string, + o: any, + staticPath: string, + gifIcon: string, ): EscapedHtml { if (file) { const fileContent: (EscapedHtml | string)[] = [] diff --git a/src/site/SW.yotsuba.Build/PostInfoHtml.tsx b/src/site/SW.yotsuba.Build/PostInfoHtml.tsx index 7eaf061..f24d248 100644 --- a/src/site/SW.yotsuba.Build/PostInfoHtml.tsx +++ b/src/site/SW.yotsuba.Build/PostInfoHtml.tsx @@ -2,29 +2,29 @@ import { g } from '../../globals/globals' import h, { EscapedHtml } from '../../globals/jsx' export default function generatePostInfoHtml( - ID, - o, - subject, - capcode, - email, - name, - tripcode, - pass, - capcodeLC, - capcodePlural, - staticPath, - gifIcon, - capcodeDescription, - uniqueID, - flag, - flagCode, - flagCodeTroll, - dateUTC, - dateText, - postLink, - quoteLink, - boardID, - threadID, + 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, ): EscapedHtml { const nameHtml: (EscapedHtml | string)[] = [ {name}, diff --git a/src/site/SW.yotsuba.tsx b/src/site/SW.yotsuba.tsx index a1475aa..0ec08c0 100644 --- a/src/site/SW.yotsuba.tsx +++ b/src/site/SW.yotsuba.tsx @@ -1,6 +1,5 @@ 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"; @@ -9,20 +8,12 @@ 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,