mirror of
https://github.com/LalleSX/4chan-XZ.git
synced 2026-01-30 09:48:12 +01:00
Compare commits
1 Commits
159b3cc760
...
e1965f9394
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1965f9394 |
@ -5,6 +5,7 @@ import { dict, HOUR } from "../platform/helpers"
|
||||
|
||||
|
||||
const BoardConfig = {
|
||||
boards: {},
|
||||
cbs: [],
|
||||
|
||||
init() {
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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?.()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 }
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ declare global {
|
||||
wrappedJSObject: any
|
||||
Tegaki: any
|
||||
FCX: any
|
||||
Parser: any
|
||||
}
|
||||
}
|
||||
// interfaces might be incomplete
|
||||
|
||||
@ -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,
|
||||
|
||||
|
||||
@ -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') {
|
||||
|
||||
@ -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 || ''
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user