Compare commits

...

1 Commits

Author SHA1 Message Date
Lalle
e1965f9394
types 2023-05-08 02:01:18 +02:00
14 changed files with 108 additions and 89 deletions

View File

@ -5,6 +5,7 @@ import { dict, HOUR } from "../platform/helpers"
const BoardConfig = {
boards: {},
cbs: [],
init() {

View File

@ -1,11 +1,7 @@
import { Conf, g } 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 Flash = {
init() {
if ((g.BOARD.ID === 'f') && Conf['Enable Native Flash Embedding']) {
@ -15,7 +11,7 @@ const Flash = {
initReady() {
if ($.hasStorage) {
return $.global(function () { if (JSON.parse(localStorage['4chan-settings'] || '{}').disableAll) { return window.SWFEmbed.init() } })
return $.global(function () { if (JSON.parse(localStorage['4chan-settings'] || '{}').disableAll) { return window.SWFEmbed.init() } }, 1 * 1000)
} else {
if (g.VIEW === 'thread') {
$.global(() => window.Main.tid = location.pathname.split(/\/+/)[3])

View File

@ -160,7 +160,7 @@ const Captcha = {
return $.event('CaptchaCount', this.captchas.length)
}
},
Replace: CaptchaReplace, t: CaptchaT, v2: {
replace: CaptchaReplace, t: CaptchaT, v2: {
lifetime: 2 * MINUTE,
init() {

View File

@ -7,7 +7,15 @@ export default class CatalogThread {
ID: string | number
thread: Thread
board: Board
nodes: { root: Post; thumb: HTMLElement; icons: any; postCount: number; fileCount: number; pageCount: number; replies: any }
nodes: {
root: Post;
thumb: HTMLElement;
icons: HTMLElement;
postCount: number;
fileCount: number;
pageCount: number;
replies: HTMLElement;
}
toString() { return this.ID }
constructor(root: Post, thread: Thread) {
@ -22,7 +30,7 @@ export default class CatalogThread {
postCount: $('.post-count', post),
fileCount: $('.file-count', post),
pageCount: $('.page-count', post),
replies: null
replies: $('.replies', post),
}
this.thread.catalogView = this
}

View File

@ -8,20 +8,20 @@ export default class CatalogThreadNative {
nodes: { root: Thread; thumb: HTMLElement }
siteID: string
boardID: string
board: Board | import("/home/victor/proj/4chan-XZ/src/globals/globals").Board
board: Board
threadID: number
thread: Thread
toString() { return this.ID }
constructor(root) {
constructor(root: Thread, thread: Thread) {
this.thread = thread
this.ID = this.thread.ID + ''
this.board = this.thread.board
const { post } = this.thread.OP.nodes
this.nodes = {
root,
thumb: $(g.SITE.selectors.catalog.thumb, root)
thumb: $('.catalog-thumb', post),
}
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)
this.thread.catalogView = this
}
}

View File

@ -3,25 +3,13 @@ import $ from "../platform/$"
import { dict, HOUR } from "../platform/helpers"
import { CacheOptions } from "../types/globals"
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS104: Avoid inline assignments
* DS206: Consider reworking classes to avoid initClass
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
export default class DataBoard {
static set(data, cb) {
this.set(data, cb)
}
static get(cb) {
this.get(cb)
}
static keys: string[]
static changes: string[]
key: string
data: any
status: number
static initClass() {
this.keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads', 'watcherLastModified', 'customTitles']
@ -57,6 +45,10 @@ export default class DataBoard {
return this.data[g.SITE.ID] || (this.data[g.SITE.ID] = { boards: dict() })
}
sync() {
return this.sync?.()
}
save(change, cb) {
change()
DataBoard.changes.push(change)
@ -195,7 +187,7 @@ export default class DataBoard {
const siteID = g.SITE.ID
for (boardID in this.data[siteID].boards) {
const val = this.data[siteID].boards[boardID]
this.deleteIfEmpty({ siteID, boardID })
this.deleteIfEmpty({ siteID, boardID, threadID: null })
}
const now = Date.now()
if (now - (2 * HOUR) >= ((middle = this.data[siteID].lastChecked || 0)) || middle > now) {
@ -205,25 +197,25 @@ export default class DataBoard {
}
}
}
response: string
ajaxClean(boardID: string) {
const that = this
const siteID = g.SITE.ID
const threadsList = g.SITE.urls.threadsListJSON?.({ siteID, boardID })
if (!threadsList) { return }
return $.cache(threadsList, () => {
if (this.status !== 200) { return }
const archiveList = g.SITE.urls.archiveListJSON?.({ siteID, boardID })
if (!archiveList) { return that.ajaxCleanParse(boardID, this.response) }
if (!archiveList) { return this.ajaxCleanParse(boardID, this.response) }
const response1 = this.response
return $.cache(archiveList, () => {
if ((this.status !== 200) && (!!g.SITE.archivedBoardsKnown || (this.status !== 404))) { return }
return that.ajaxCleanParse(boardID, response1, this.response)
if ((this.status !== 200) && (this.status !== 404)) { return }
return this.ajaxCleanParse(boardID, response1, this.response)
}, { type: 'json' }) as CacheOptions
}, { type: 'json' }) as CacheOptions
}
ajaxCleanParse(boardID, response1, response2) {
ajaxCleanParse(boardID, response1, response2?) {
let board, ID
const siteID = g.SITE.ID
if (!(board = this.data[siteID].boards[boardID])) { return }
@ -242,13 +234,17 @@ export default class DataBoard {
}
}
this.data[siteID].boards[boardID] = threads
this.deleteIfEmpty({ siteID, boardID })
return $.set(this.key, this.data)
this.deleteIfEmpty({ siteID, boardID, threadID: null })
return $.set(this.key, this.data, { type: 'json' })
}
onSync(data) {
if ((data.version || 0) <= (this.data.version || 0)) { return }
this.initData(data)
if (!data) { return }
const siteID = g.SITE.ID
if (data[siteID]) {
this.data[siteID] = data[siteID]
this.clean()
}
return this.sync?.()
}
}

View File

@ -13,9 +13,9 @@ import Thread from "./Thread"
export default class Fetcher {
archiveTags: { '\n': { innerHTML: string }; '[b]': { innerHTML: string }; '[/b]': { innerHTML: string }; '[spoiler]': { innerHTML: string }; '[/spoiler]': { innerHTML: string }; '[code]': { innerHTML: string }; '[/code]': { innerHTML: string }; '[moot]': { innerHTML: string }; '[/moot]': { innerHTML: string }; '[banned]': { innerHTML: string }; '[/banned]': { innerHTML: string }; '[fortune]'(text: string): { innerHTML: string }; '[/fortune]': { innerHTML: string }; '[i]': { innerHTML: string }; '[/i]': { innerHTML: string }; '[red]': { innerHTML: string }; '[/red]': { innerHTML: string }; '[green]': { innerHTML: string }; '[/green]': { innerHTML: string }; '[blue]': { innerHTML: string }; '[/blue]': { innerHTML: string } }
boardID: string
threadID: number
archiveTags: any
threadID: string
postID: number
root: HTMLElement
quoter: any
@ -46,8 +46,8 @@ export default class Fetcher {
'[/blue]': { innerHTML: "</span>" }
}
}
constructor(boardID, threadID, postID, root, quoter) {
let post, thread
constructor(boardID: string, threadID: string, postID: number, root: HTMLElement, quoter: any) {
let post: Post, thread: Thread
this.boardID = boardID
this.threadID = threadID
this.postID = postID
@ -69,16 +69,15 @@ 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)
})
$.cache(g.SITE.urls.threadJSON({ siteID: this.boardID, boardID: this.boardID, threadID: this.threadID }, false), function ({ isCached }) {
return this.fetchedPost(this, isCached)
}, 'json')
} else {
this.archivedPost()
}
}
insert(post) {
insert(post: Post) {
// Stop here if the container has been removed while loading.
if (!this.root.parentNode) { return }
if (!this.quoter) { this.quoter = post }
@ -112,7 +111,24 @@ export default class Fetcher {
$.rmAll(this.root)
$.add(this.root, nodes.root)
return $.event('PostsInserted', null, this.root)
return $.event(this.root, 'click', this.click)
}
click(e) {
let { target } = e
if (target.tagName === 'IMG') {
target = target.parentNode
}
if (target.tagName !== 'A') { return }
const { boardID, postID } = Get.postDataFromLink(target)
if (boardID && postID) {
e.preventDefault()
const { threadID } = Get.threadDataFromLink(target)
if (threadID) {
return Main.request('Thread', [boardID, threadID, postID])
} else {
return Main.request('Post', [boardID, postID])
}
}
}
fetchedPost(req, isCached) {
@ -149,11 +165,10 @@ export default class Fetcher {
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 }, true)
const api = g.SITE.urls.threadJSON({ siteID: this.boardID, boardID: this.boardID, threadID: this.threadID }, false)
$.cleanCache(url => url === api)
const that = this
$.cache(api, function () {
return that.fetchedPost(this, false)
return this.fetchedPost(this, false)
}, { force: true })
return
}
@ -182,7 +197,6 @@ export default class Fetcher {
const archive = Redirect.data.post[this.boardID]
const encryptionOK = /^https:\/\//.test(url) || (location.protocol === 'http:')
if (encryptionOK || Conf['Exempt Archives from Encryption']) {
const that = this
CrossOrigin.cache(url, function () {
if (!encryptionOK && this.response?.media) {
const { media } = this.response
@ -194,7 +208,7 @@ export default class Fetcher {
}
}
}
return that.parseArchivedPost(this.response, url, archive)
return this.parseArchivedPost(this.response, url, archive)
})
return true
}
@ -231,7 +245,7 @@ export default class Fetcher {
for (let i = 0; i < comment.length; i++) {
let text = comment[i]
if ((i % 2) === 1) {
const tag = Fetcher.archiveTags[text.replace(/\ .*\]/, ']')]
const tag = Fetcher.archiveTags[text.replace(/ .*]/, ']')]
if (typeof tag === 'function') { result.push(tag(text)) } else { result.push(tag) }
} else {
const greentext = text[0] === '>'
@ -245,12 +259,19 @@ export default class Fetcher {
})()
comment = { innerHTML: E.cat(comment) }
this.threadID = +data.thread_num
this.threadID = data.thread_num
const o = {
file: data.media,
ID: this.postID,
threadID: this.threadID,
boardID: this.boardID,
isReply: this.postID !== this.threadID
isReply: true,
info: null,
extra: null,
comment,
root: this.root,
isArchived: true,
isFetchedQuote: true,
}
o.info = {
subject: data.title,
@ -275,6 +296,7 @@ export default class Fetcher {
dateUTC: data.timestamp,
dateText: data.fourchan_date,
commentHTML: comment
}
if (o.info.capcode) { delete o.info.uniqueID }
if (data.media && !!+data.media.banned) {

View File

@ -15,7 +15,7 @@ export default class Post {
declare board: Board
declare ID: number | string
declare postID: number
declare threadID: number
declare threadID: number | string
declare boardID: number | string
declare siteID: number | string
declare fullID: string
@ -33,6 +33,8 @@ export default class Post {
declare files: File[]
declare info: {
reply: string,
quote: string,
comment: string,
subject: string,
name: string,
@ -146,6 +148,7 @@ export default class Post {
this.isFetchedQuote = false
this.isClone = false
}
callbacksExecuted = false
parseNodes(root: HTMLElement) {
const s = g.SITE.selectors

View File

@ -1,23 +0,0 @@
class ShimSet {
elements: Element
size: number
constructor() {
this.elements
this.size = 0
}
has(value) {
return value in this.elements
}
add(value) {
if (this.elements[value]) { return }
this.elements[value] = true
return this.size++
}
delete(value) {
if (!this.elements[value]) { return }
delete this.elements[value]
return this.size--
}
}
if (!('Set' in window)) { window.Set = ShimSet }

View File

@ -135,7 +135,7 @@ export default class Thread {
})
if (!n) {
g.threads.rm(this.fullID)
this.board.threads.rm(this)
this.board.threads.rm(this.ID.toString())
}
}

View File

@ -10,6 +10,7 @@ declare global {
wrappedJSObject: any
Tegaki: any
FCX: any
Parser: any
}
}
// interfaces might be incomplete

View File

@ -258,6 +258,19 @@ const Main = {
})
})
},
request(url: string, cb) {
const xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.responseType = 'text'
xhr.onload = function () {
if (xhr.status === 200) {
return cb(xhr.response)
} else {
return cb(null)
}
}
return xhr.send()
},
isFirstRun: true,
bgColorStyle: null,

View File

@ -74,7 +74,6 @@ const SWTinyboard = {
}
return false
},
ID: 'sw-tinyboard',
parseThreadMetadata(el: HTMLElement) {
const thread = dict()
const op = el.querySelector('.op')
@ -104,9 +103,8 @@ const SWTinyboard = {
post({ postID }) { return `#${postID}` },
index({ siteID, boardID }) { return `${Conf['siteProperties'][siteID]?.root || `http://${siteID}/`}${boardID}/` },
catalog({ siteID, boardID }) { return `${Conf['siteProperties'][siteID]?.root || `http://${siteID}/`}${boardID}/catalog.html` },
threadJSON({ siteID, boardID, threadID }, isArchived) {
const root = Conf['siteProperties'][siteID]?.root
if (root) { return `${root}${boardID}/${isArchived ? 'archive/' : ''}res/${threadID}.json` } else { return '' }
threadJSON({ siteID, boardID, threadID }: { siteID: string, boardID: string, threadID: string }, isArchived = false) {
return `${Conf['siteProperties'][siteID]?.root || `http://${siteID}/`}${boardID}/${isArchived ? 'archive/' : ''}res/${threadID}.json`
},
archivedThreadJSON(thread) {
return SWTinyboard.urls.threadJSON(thread, true)
@ -219,6 +217,10 @@ $\
},
Build: {
gifIcon: function (board, filename) {
return `<img src="${SWTinyboard.urls.thumb(board, filename)}" alt="GIF" title="GIF" class="gif-icon">`
},
staticPath: 'static/',
parseJSON(data, board) {
const o = this.parseJSON(data, board)
if (data.ext === 'deleted') {

View File

@ -691,7 +691,7 @@ $\
id: `t${thread}`
}
)
if (thread.OP.highlights) { $.addClass(root, ...Array.from(thread.OP.highlights)) }
if (thread.OP.highlights) { $.addClass(root, 'highlight') }
if (!thread.OP.file) { $.addClass(root, 'noFile') }
root.style.cssText = cssText || ''