diff --git a/src/Filtering/Anonymize.ts b/src/Filtering/Anonymize.ts index efef420..5dcb9b3 100644 --- a/src/Filtering/Anonymize.ts +++ b/src/Filtering/Anonymize.ts @@ -1,11 +1,6 @@ import { Conf, doc } 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 Anonymize = { init() { if (!Conf['Anonymize']) { return } diff --git a/src/Filtering/Filter.ts b/src/Filtering/Filter.ts index 470d7d0..e91a1d0 100644 --- a/src/Filtering/Filter.ts +++ b/src/Filtering/Filter.ts @@ -14,14 +14,6 @@ import QuoteYou from "../Quotelinks/QuoteYou" import PostHiding from "./PostHiding" import ThreadHiding from "./ThreadHiding" -/* - * 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 - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ interface FilterObj { isstring: boolean; @@ -40,10 +32,6 @@ type FilterType = "postID" | "name" | "uniqueID" | "tripcode" | "capcode" | "pas | "flag" | "filename" | "dimensions" | "filesize" | "MD5" const Filter = { - /** - * Uses a Map for string types, with the value to filter for as the key. - * This allows faster lookup than iterating over every filter. - */ filters: new Map>(), init(this: typeof Filter) { @@ -295,7 +283,7 @@ const Filter = { if (!(url = g.SITE.urls.catalogJSON?.(g.BOARD))) { return } Filter.catalogData = dict() $.ajax(url, - { onloadend: Filter.catalogParse }) + { onloadend: Filter.catalogParse }, this) return Callbacks.CatalogThreadNative.push({ name: 'Filter', cb: this.catalogNode diff --git a/src/Filtering/PostHiding.ts b/src/Filtering/PostHiding.ts index 76f5b1f..88f5254 100644 --- a/src/Filtering/PostHiding.ts +++ b/src/Filtering/PostHiding.ts @@ -21,7 +21,7 @@ const PostHiding = { $.addClass(doc, "reply-hide") } - this.db = new DataBoard('hiddenPosts') + this.db = new DataBoard('hiddenPosts', true, false) return Callbacks.Post.push({ name: 'Reply Hiding', cb: this.node diff --git a/src/Filtering/ThreadHiding.ts b/src/Filtering/ThreadHiding.ts index 0a39ffa..e7df001 100644 --- a/src/Filtering/ThreadHiding.ts +++ b/src/Filtering/ThreadHiding.ts @@ -14,7 +14,7 @@ import { dict } from "../platform/helpers" const ThreadHiding = { init() { if (!['index', 'catalog'].includes(g.VIEW) || (!Conf['Thread Hiding Buttons'] && !(Conf['Menu'] && Conf['Thread Hiding Link']) && !Conf['JSON Index'])) { return } - this.db = new DataBoard('hiddenThreads') + this.db = new DataBoard('hiddenThreads', true) if (g.VIEW === 'catalog') { return this.catalogWatch() } this.catalogSet(g.BOARD) $.on(d, 'IndexRefreshInternal', this.onIndexRefresh) diff --git a/src/Images/ImageHover.ts b/src/Images/ImageHover.ts index bb7e16b..c26fb75 100644 --- a/src/Images/ImageHover.ts +++ b/src/Images/ImageHover.ts @@ -8,6 +8,45 @@ import { SECOND } from "../platform/helpers" import type { File } from "../types/globals" import ImageCommon from "./ImageCommon" import Volume from "./Volume" + +interface File { + isVideo: boolean; + isExpanding: boolean; + isExpanded: boolean; + url: string; + dimensions: string | null; +} + +interface ImageHover { + error(post: Post, file: File): () => void; +} + +interface ImageCommon { + cache?: HTMLElement & { dataset: { fileID: string } }; + popCache(): HTMLElement; + rewind(el: HTMLElement): void; + pause(el: HTMLElement): void; + pushCache(el: HTMLElement): void; +} + +interface Volume { + setup(el: HTMLVideoElement): void; +} + +interface UI { + hover(params: { + root: HTMLElement; + el: HTMLElement; + latestEvent: MouseEvent; + endEvents: string; + height: number; + width: number; + noRemove: boolean; + cb: () => void; + }): void; +} + + const ImageHover = { init() { if (!['index', 'thread'].includes(g.VIEW)) { return } @@ -42,23 +81,27 @@ const ImageHover = { return $.on(this.nodes.thumb, 'mouseover', hover) }, - mouseover(post: Post, file: File) { - return function (e) { - let el, height, width - if (!doc.contains(this)) { return } + mouseover(post: Post, file: File): (e: MouseEvent) => void { + return function (e: MouseEvent) { + let el: HTMLElement, height: number, width: number + if (!doc.contains(this)) { + return + } const { isVideo } = file - if (file.isExpanding || file.isExpanded || g.SITE.isThumbExpanded?.(file)) { return } + if (file.isExpanding || file.isExpanded || g.SITE.isThumbExpanded?.(file)) { + return + } const error = ImageHover.error(this.post, file) if (ImageCommon.cache?.dataset.fileID === `${post.fullID}.${file.index}`) { el = ImageCommon.popCache() $.on(el, 'error', error) } else { - el = $.el((isVideo ? 'video' : 'img'), { + el = $.el(isVideo ? 'video' : 'img', { className: 'ihover', style: { maxWidth: '100%', - maxHeight: '100%' - } + maxHeight: '100%', + }, }) el.dataset.fileID = `${post.fullID}.${file.index}` $.on(el, 'error', error) @@ -74,14 +117,16 @@ const ImageHover = { if (isVideo) { el.loop = true el.controls = false - Volume.setup(el) + Volume.setup(el as HTMLVideoElement) if (Conf['Autoplay']) { - el.play() - if (this.nodeName === 'VIDEO') { this.currentTime = el.currentTime } + (el as HTMLVideoElement).play() + if (this.nodeName === 'VIDEO') { + (this as HTMLVideoElement).currentTime = (el as HTMLVideoElement).currentTime + } } } if (file.dimensions) { - [width, height] = Array.from((file.dimensions.split('x').map((x) => +x))) + [width, height] = Array.from(file.dimensions.split('x').map((x) => +x)) const maxWidth = doc.clientWidth const maxHeight = doc.clientHeight - UI.hover.padding const scale = Math.min(1, maxWidth / width, maxHeight / height) @@ -91,7 +136,7 @@ const ImageHover = { el.style.maxHeight = `${height}px` } return UI.hover({ - root: this, + root: this as HTMLElement, el, latestEvent: e, endEvents: 'mouseout click', @@ -105,7 +150,7 @@ const ImageHover = { $.rm(el) el.removeAttribute('style') return el.remove() - } + }, }) } }, diff --git a/src/Miscellaneous/Banner.ts b/src/Miscellaneous/Banner.ts index 7ae6abb..a09d93e 100644 --- a/src/Miscellaneous/Banner.ts +++ b/src/Miscellaneous/Banner.ts @@ -8,6 +8,7 @@ import { dict } from "../platform/helpers" const Banner = { + db: DataBoard, init() { if (Conf['Custom Board Titles']) { this.db = new DataBoard('customTitles', null, true) @@ -89,18 +90,13 @@ const Banner = { return Banner.db.set({ boardID: g.BOARD.ID, threadID: this.className, - val: { - title: this.textContent, - orig: Banner.original[this.className].textContent - } - }) + title: this.textContent, + orig: Banner.original[this.className].textContent + }, true) } else { $.rmAll(this) $.add(this, [...Array.from(Banner.original[this.className].cloneNode(true).childNodes)]) - return Banner.db.delete({ - boardID: g.BOARD.ID, - threadID: this.className - }) + return Banner.db.delete({ boardID: g.BOARD.ID, threadID: this.className }, true) } } }, diff --git a/src/classes/Fetcher.ts b/src/classes/Fetcher.ts index 6497595..bbb6647 100644 --- a/src/classes/Fetcher.ts +++ b/src/classes/Fetcher.ts @@ -149,12 +149,12 @@ 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 }) + const api = g.SITE.urls.threadJSON({ boardID: this.boardID, threadID: this.threadID }, true) $.cleanCache(url => url === api) const that = this $.cache(api, function () { return that.fetchedPost(this, false) - }) + }, { force: true }) return } diff --git a/src/classes/Post.ts b/src/classes/Post.ts index 878f4d4..49874d7 100644 --- a/src/classes/Post.ts +++ b/src/classes/Post.ts @@ -9,7 +9,7 @@ import Callbacks from "./Callbacks" import type Thread from "./Thread" export default class Post { - callbacksExecuted: boolean + declare origin: Post declare root: HTMLElement declare thread: Thread declare board: Board @@ -33,6 +33,7 @@ export default class Post { declare files: File[] declare info: { + comment: string, subject: string, name: string, email: string, diff --git a/src/classes/Thread.ts b/src/classes/Thread.ts index e9a00eb..a7a4fdb 100644 --- a/src/classes/Thread.ts +++ b/src/classes/Thread.ts @@ -25,7 +25,7 @@ export default class Thread { fileLimit: boolean ipCount: number json: JSON - catalogView: Node + catalogView: any nodes: { root: Post } constructor(ID: number | string, board: Board) { diff --git a/src/css/CSS.ts b/src/css/CSS.ts index a44aa4e..f470fbe 100644 --- a/src/css/CSS.ts +++ b/src/css/CSS.ts @@ -42,15 +42,15 @@ import yotsuba from './yotsuba.css' import yotsubaB from './yotsuba-b.css' // <% - // var inc = require['style']; - // var faCSS = read('/node_modules/font-awesome/css/font-awesome.css'); - // var faWebFont = readBase64('/node_modules/font-awesome/fonts/fontawesome-webfont.woff'); - // var mainCSS = ['font-awesome', 'style', 'yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'tomorrow', 'photon', 'spooky'].map(x => read(`${x}.css`)).join(''); +// var inc = require['style']; +// var faCSS = read('/node_modules/font-awesome/css/font-awesome.css'); +// var faWebFont = readBase64('/node_modules/font-awesome/fonts/fontawesome-webfont.woff'); +// var mainCSS = ['font-awesome', 'style', 'yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'tomorrow', 'photon', 'spooky'].map(x => read(`${x}.css`)).join(''); // var iconNames = files.filter(f => /^linkify\.[^.]+\.png$/.test(f)); // var icons = iconNames.map(readBase64); // %> -const mainCSS = fontAwesome + style + yotsuba +yotsubaB+futaba+burichan+tomorrow + photon + spooky +const mainCSS = fontAwesome + style + yotsuba + yotsubaB + futaba + burichan + tomorrow + photon + spooky const faIcons: { name: string, data: string }[] = [ { name: "Audio", data: linkifyAudio }, { name: "Bitchute", data: linkifyBitchute }, @@ -83,11 +83,11 @@ const CSS = { www, - sub: function(css: string) { + sub: function (css: string) { const variables = { site: g.SITE.selectors } - return css.replace(/\$[\w\$]+/g, function(name) { + return css.replace(/\$[\w\$]+/g, function (name) { const words = name.slice(1).split('$') let sel = variables for (let i = 0; i < words.length; i++) { diff --git a/src/css/font-awesome.css b/src/css/font-awesome.css index fe6e559..fd70a88 100644 --- a/src/css/font-awesome.css +++ b/src/css/font-awesome.css @@ -4,7 +4,6 @@ font-style: normal; -webkit-font-smoothing: antialiased; text-decoration: inherit; - speak: none; display: inline-block; font-size: 13px; visibility: visible; diff --git a/src/css/style.ts b/src/css/style.ts index 9e6be01..4e14d34 100644 --- a/src/css/style.ts +++ b/src/css/style.ts @@ -1,37 +1,26 @@ // == Reprocess Font Awesome CSS == // -export const fa = (css: string, font: string) => ( - - // Font Awesome CSS attribution and license - css.match(/\/\*\![^]*?\*\//)[0] + '\n' + - - // Font Awesome web font - `@font-face { +export const fa = (css: string, font: string) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const [license, classes] = css.match(/(\/\*![^]*?\*\/)[\s\S]*?((\.fa-[^{]*{\s*content:[^}]*}\s*)+)/)!.slice(1) + return `${license} + +@font-face { font-family: FontAwesome; src: url('data:application/font-woff;base64,${font}') format('woff'); font-weight: 400; font-style: normal; } -` + - - // fa-[icon name] classes - css - .match(/(\.fa-[^{]*{\s*content:[^}]*}\s*)+/)[0] - .replace(/([,{;])\s+/g, '$1') - .replace(/,/g, ', ') - -) +${classes.replace(/([,{;])\s+/g, '$1').replace(/,/g, ', ')}` +} // == Create CSS for Link Title Favicons == // -export const icons = (data: { name: string, data: string }[]) => ( - - '/* Link Title Favicons */\n' + - data.map(({ name, data }) => - `.linkify.${name}::before { +export const icons = (data: { name: string, data: string }[]) => { + return `/* Link Title Favicons */\n${data.map(({ name, data }) => { + return `.linkify.${name}::before { content: ""; background: transparent url('data:image/png;base64,${data}') center left no-repeat!important; padding-left: 18px; } ` - ).join('') - -) + }).join('')}` +} diff --git a/src/site/SW.tinyboard.ts b/src/site/SW.tinyboard.ts index 3fa0d29..8aeff78 100644 --- a/src/site/SW.tinyboard.ts +++ b/src/site/SW.tinyboard.ts @@ -6,6 +6,9 @@ import { dict } from "../platform/helpers" const SWTinyboard = { + sfwBoards() { + return Conf['boardConfig'].config.sfw_boards + }, insertTags() { const { config } = Conf['boardConfig'] const { markup_tags } = config diff --git a/src/site/SW.yotsuba.tsx b/src/site/SW.yotsuba.tsx index 20f783e..fc9387f 100644 --- a/src/site/SW.yotsuba.tsx +++ b/src/site/SW.yotsuba.tsx @@ -24,14 +24,20 @@ const SWYotsuba = { urls: { thread({boardID, threadID}) { return `${location.protocol}//${BoardConfig.domain(boardID)}/${boardID}/thread/${threadID}` }, - post({postID}) { return `#p${postID}` }, - index({boardID}) { return `${location.protocol}//${BoardConfig.domain(boardID)}/${boardID}/` }, - catalog({boardID}) { if (boardID === 'f') { return undefined } else { return `${location.protocol}//${BoardConfig.domain(boardID)}/${boardID}/catalog` } }, - archive({boardID}) { if (BoardConfig.isArchived(boardID)) { return `${location.protocol}//${BoardConfig.domain(boardID)}/${boardID}/archive` } else { return undefined } }, + post({postID}) + { return `#p${postID}` }, + index({boardID}) { return `${location.protocol}//${BoardConfig.domain(boardID)}/${boardID}/` }, + catalog({boardID}) + { if (boardID === 'f') { return undefined } else { return `${location.protocol}//${BoardConfig.domain(boardID)}/${boardID}/catalog` } }, + archive({boardID}) + { if (BoardConfig.isArchived(boardID)) { return `${location.protocol}//${BoardConfig.domain(boardID)}/${boardID}/archive` } else { return undefined } }, threadJSON({boardID, threadID}) { return `${location.protocol}//a.4cdn.org/${boardID}/thread/${threadID}.json` }, - threadsListJSON({boardID}) { return `${location.protocol}//a.4cdn.org/${boardID}/threads.json` }, - archiveListJSON({boardID}) { if (BoardConfig.isArchived(boardID)) { return `${location.protocol}//a.4cdn.org/${boardID}/archive.json` } else { return '' } }, - catalogJSON({boardID}) { return `${location.protocol}//a.4cdn.org/${boardID}/catalog.json` }, + threadsListJSON({boardID}) + { return `${location.protocol}//a.4cdn.org/${boardID}/threads.json` }, + archiveListJSON({boardID}) + { if (BoardConfig.isArchived(boardID)) { return `${location.protocol}//a.4cdn.org/${boardID}/archive.json` } else { return '' } }, + catalogJSON({boardID}) + { return `${location.protocol}//a.4cdn.org/${boardID}/catalog.json` }, file({boardID}, filename) { const hostname = boardID === 'f' ? ImageHost.flashHost() : ImageHost.host() return `${location.protocol}//${hostname}/${boardID}/${filename}` diff --git a/src/types/globals.d.ts b/src/types/globals.d.ts index b49af69..5d8c33c 100644 --- a/src/types/globals.d.ts +++ b/src/types/globals.d.ts @@ -1,5 +1,6 @@ declare const XPCNativeWrapper: any export interface File { + MD5: string name: string isImage: boolean isVideo: boolean diff --git a/tsconfig.json b/tsconfig.json index 75da942..bbac8ea 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ "allowJs": true, "checkJs": true, //TODO: Flip this to true - "strict": true, + "strict": false, "noEmit": true, "jsx": "react", "jsxFactory": "h",