mirror of
https://github.com/LalleSX/4chan-XZ.git
synced 2026-01-30 09:48:12 +01:00
Revert "Typescript and more"
This reverts commit 12483e97c52eb96965811a8e6c4c28cd3c45b19f.
This commit is contained in:
parent
bc41e49343
commit
27d267b4f0
@ -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() {
|
||||
@ -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',
|
||||
),
|
||||
|
||||
@ -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
161
src/Miscellaneous/Report.js
Normal 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
|
||||
@ -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,
|
||||
|
||||
79
src/Posting/Captcha.replace.js
Normal file
79
src/Posting/Captcha.replace.js
Normal 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
|
||||
@ -11,7 +11,7 @@ const PassLink = {
|
||||
},
|
||||
|
||||
ready(): void {
|
||||
let styleSelector: HTMLElement
|
||||
let styleSelector
|
||||
if (!(styleSelector = $.id('styleSelector'))) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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}`))
|
||||
|
||||
@ -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
42
src/classes/Board.js
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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
65
src/classes/Callbacks.js
Normal 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()
|
||||
@ -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();
|
||||
24
src/classes/CatalogThread.js
Normal file
24
src/classes/CatalogThread.js
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
23
src/classes/CatalogThreadNative.js
Normal file
23
src/classes/CatalogThreadNative.js
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -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
51
src/classes/Connection.js
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
12
src/platform/$$.js
Normal 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 $$
|
||||
@ -1,4 +0,0 @@
|
||||
const $$ = (selector: string, root: HTMLElement | null = document.body): Element[] =>
|
||||
Array.from(root?.querySelectorAll(selector) ?? []) as Element[];
|
||||
|
||||
export default $$;
|
||||
@ -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)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
|
||||
@ -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)[] = []
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user