diff --git a/src/General/Get.js b/src/General/Get.js index 8478690..78c5098 100644 --- a/src/General/Get.js +++ b/src/General/Get.js @@ -89,9 +89,6 @@ var Get = { } } // First: - // In every posts, - // if it did quote this post, - // get all their backlinks. posts.forEach(function (qPost) { if (qPost.quotes.includes(fullID)) { return handleQuotes(qPost, 'quotelinks') @@ -99,10 +96,6 @@ var Get = { }) // Second: - // If we have quote backlinks: - // in all posts this post quoted - // and their clones, - // get all of their backlinks. if (Conf['Quote Backlinks']) { for (var quote of post.quotes) { var qPost @@ -113,7 +106,6 @@ var Get = { } // Third: - // Filter out irrelevant quotelinks. return quotelinks.filter(function (quotelink) { const { boardID, postID } = Get.postDataFromLink(quotelink) return boardID === post.board.ID && postID === post.ID diff --git a/src/General/Header.js b/src/General/Header.js index de68fcf..920d5b3 100644 --- a/src/General/Header.js +++ b/src/General/Header.js @@ -12,13 +12,7 @@ import Settings from './Settings' import UI from './UI' import meta from '../../package.json' -/* - * decaffeinate suggestions: - * DS101: Remove unnecessary use of Array.from - * DS102: Remove unnecessary code created because of implicit returns - * DS104: Avoid inline assignments - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ + var Header = { init() { $.onExists(doc, 'body', () => { diff --git a/src/General/Test.js b/src/General/Test.js index 4b208fa..b2a63ef 100644 --- a/src/General/Test.js +++ b/src/General/Test.js @@ -143,10 +143,8 @@ const Test = { testOne(post) { Test.postsRemaining++ return $.cache( - g.SITE.urls.threadJSON({ - boardID: post.boardID, - threadID: post.threadID, - }), + g.SITE.urls.threadJSON(post.thread.ID, post.board), + { responseType: 'json' }, function () { if (!this.response) { return @@ -179,7 +177,7 @@ const Test = { for (var key in Config.filter) { if ( - !key === 'General' && + key !== 'MD5' || !(key === 'MD5' && post.board.ID === 'f') ) { var val1 = Filter.values(key, obj) diff --git a/src/Images/ImageLoader.js b/src/Images/ImageLoader.js index ff61def..c14a91b 100644 --- a/src/Images/ImageLoader.js +++ b/src/Images/ImageLoader.js @@ -115,12 +115,12 @@ var ImageLoader = { if (!replace && !ImageLoader.prefetchEnabled) { return } - if ($.hasClass(doc, 'catalog-mode')) { + if ($.hasClass(d, 'catalog-mode')) { return } if ( ![post, ...Array.from(post.clones)].some((clone) => - doc.contains(clone.nodes.root), + d.contains(clone.nodes.root), ) ) { return @@ -175,12 +175,13 @@ var ImageLoader = { return g.posts.forEach(function (post) { for (post of [post, ...Array.from(post.clones)]) { for (var file of post.files) { - if (file.videoThumb) { - var { thumb } = file - if (Header.isNodeVisible(thumb) || post.nodes.root === qpClone) { - thumb.play() - } else { - thumb.pause() + if (file.isVideo && !file.isPrefetched) { + const { thumb } = file + if (qpClone === thumb) { + continue + } + if (thumb.getBoundingClientRect().top < window.innerHeight) { + ImageLoader.prefetch(post, file) } } } diff --git a/src/Images/Volume.js b/src/Images/Volume.js index 5d27c6f..e6439e7 100644 --- a/src/Images/Volume.js +++ b/src/Images/Volume.js @@ -130,8 +130,10 @@ var Volume = { if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) { return } - if (!(el = $('video:not([data-md5])', this))) { - return + if (this instanceof HTMLAnchorElement) { + el = this.querySelector('video') + } else { + el = this.querySelector('video, audio') } if (el.muted || !$.hasAudio(el)) { return diff --git a/src/classes/CatalogThreadNative.ts b/src/classes/CatalogThreadNative.ts index d13c863..fbcdf8c 100644 --- a/src/classes/CatalogThreadNative.ts +++ b/src/classes/CatalogThreadNative.ts @@ -9,6 +9,7 @@ export default class CatalogThreadNative { siteID: number threadID: number ID: string + thread: any toString() { return this.ID } diff --git a/src/platform/$$.ts b/src/platform/$$.ts index 3c3f7ec..2172e08 100644 --- a/src/platform/$$.ts +++ b/src/platform/$$.ts @@ -1,4 +1,4 @@ -function $$(selector: string, root: HTMLElement = document.body): HTMLElement[] { +function $$(selector: string, root: HTMLElement = document.body): HTMLElement[] | HTMLAnchorElement[] { return Array.from(root.querySelectorAll(selector)); } export default $$; \ No newline at end of file diff --git a/src/platform/$.ts b/src/platform/$.ts index 98e1570..2137979 100644 --- a/src/platform/$.ts +++ b/src/platform/$.ts @@ -1,21 +1,14 @@ -/* - * decaffeinate suggestions: - * 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 - */ -// loosely follows the jquery api: -// http://api.jquery.com/ - import Notice from "../classes/Notice"; import { c, Conf, d, doc, g } from "../globals/globals"; import CrossOrigin from "./CrossOrigin"; import { debounce, dict, MINUTE, platform, SECOND } from "./helpers"; +import { AjaxPageOptions, ElementProperties } from "../types/$"; // not chainable const $ = (selector, root = document.body) => root.querySelector(selector); - +type AjaxPageRequest = XMLHttpRequest & { + abort: () => void; +} $.id = id => d.getElementById(id); $.cache = dict(); $.ajaxPage = function (url, options) { @@ -344,19 +337,9 @@ $.addStyle = function (css, id, test = 'head') { }; $.addCSP = function (policy) { - const meta = $.el('meta', { - httpEquiv: 'Content-Security-Policy', - content: policy - } - ); - if (d.head) { - $.add(d.head, meta); - return $.rm(meta); - } else { - const head = $.add((doc || d), $.el('head')); - $.add(head, meta); - return $.rm(head); - } + const meta = $.el('meta', { httpEquiv: 'Content-Security-Policy', content: policy }, { display: 'none' }); + $.onExists(doc, 'head', () => $.add(d.head, meta)); + return meta; }; $.x = function (path, root) { @@ -413,11 +396,10 @@ $.before = (root, el) => root.parentNode.insertBefore($.nodes(el), root); $.replace = (root, el) => root.parentNode.replaceChild($.nodes(el), root); -$.el = function (tag, properties, properties2) { - const el = d.createElement(tag); - if (properties) { $.extend(el, properties); } - if (properties2) { $.extend(el, properties2); } - return el; +$.el = function (tag: keyof HTMLElementTagNameMap, properties?: ElementProperties, properties2?: ElementProperties): HTMLElement { + const element = document.createElement(tag); + Object.assign(element, properties, properties2); + return element; }; $.on = function (el, events, handler) { @@ -555,42 +537,41 @@ $.global = function (fn, data) { }; $.bytesToString = function (size) { - let unit = 0; // Bytes - while (size >= 1024) { - size /= 1024; - unit++; + if (size < 1024) { + return `${size} B`; + } else if (size < 1048576) { + return `${(size / 1024).toFixed(1)} KB`; + } else if (size < 1073741824) { + return `${(size / 1048576).toFixed(1)} MB`; + } else { + return `${(size / 1073741824).toFixed(1)} GB`; } - // Remove trailing 0s. - size = - unit > 1 ? - // Keep the size as a float if the size is greater than 2^20 B. - // Round to hundredth. - Math.round(size * 100) / 100 - : - // Round to an integer otherwise. - Math.round(size); - return `${size} ${['B', 'KB', 'MB', 'GB'][unit]}`; }; -$.minmax = (value, min, max) => value < min ? - min - : - value > max ? - max - : - value; +$.minmax = (value, min, max) => Math.max(min, Math.min(max, value)); -$.hasAudio = video => video.mozHasAudio || !!video.webkitAudioDecodedByteCount; +$.hasAudio = function (el: HTMLVideoElement | HTMLAudioElement) { + if (el.tagName === 'VIDEO') { + return !el.muted; + } else if (el.tagName === 'AUDIO') { + return true; + } else { + return el.querySelector('video:not([muted]), audio') != null; + } +}; -$.luma = rgb => (rgb[0] * 0.299) + (rgb[1] * 0.587) + (rgb[2] * 0.114); +$.luma = (rgb: number[]) => { // rgb: [r, g, b] + const [r, g, b] = rgb; + return 0.2126 * r + 0.7152 * g + 0.0722 * b; +}; $.unescape = function (text) { if (text == null) { return text; } return text.replace(/<[^>]*>/g, '').replace(/&(amp|#039|quot|lt|gt|#44);/g, c => ({ '&': '&', ''': "'", '"': '"', '<': '<', '>': '>', ',': ',' })[c]); }; -$.isImage = url => /\.(jpe?g|jfif|png|gif|bmp|webp|avif|jxl)$/i.test(url); -$.isVideo = url => /\.(webm|mp4|ogv)$/i.test(url); +$.isImage = url => /\.(jpe?g|png|gif|bmp|webp|svg|ico|tiff?)$/i.test(url); +$.isVideo = url => /\.(webm|mp4|og[gv]|m4v|mov|avi|flv|wmv|mpg|mpeg|mkv|rm|rmvb|3gp|3g2|asf|swf|vob)$/i.test(url); $.engine = (function () { if (/Edge\//.test(navigator.userAgent)) { return 'edge'; } diff --git a/src/platform/CrossOrigin.js b/src/platform/CrossOrigin.js deleted file mode 100644 index 1f6ee3b..0000000 --- a/src/platform/CrossOrigin.js +++ /dev/null @@ -1,289 +0,0 @@ -import QR from '../Posting/QR' -import $ from './$' -import { dict, platform } from './helpers' - -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS205: Consider reworking code to avoid use of IIFEs - * 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 - */ -let eventPageRequest -if (platform === 'crx') { - eventPageRequest = (function () { - const callbacks = [] - chrome.runtime.onMessage.addListener(function (response) { - callbacks[response.id](response.data) - return delete callbacks[response.id] - }) - return (params, cb) => - chrome.runtime.sendMessage(params, (id) => (callbacks[id] = cb)) - })() -} -var CrossOrigin = { - binary(url, cb, headers = dict()) { - // XXX https://forums.lanik.us/viewtopic.php?f=64&t=24173&p=78310 - url = url.replace( - /^((?:https?:)?\/\/(?:\w+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//, - '$1//adv/', - ) - if (platform === 'crx') { - eventPageRequest( - { type: 'ajax', url, headers, responseType: 'arraybuffer' }, - function ({ response, responseHeaderString }) { - if (response) { - response = new Uint8Array(response) - } - return cb(response, responseHeaderString) - }, - ) - } else { - const fallback = function () { - return $.ajax(url, { - headers, - responseType: 'arraybuffer', - onloadend() { - if (this.status && this.response) { - return cb( - new Uint8Array(this.response), - this.getAllResponseHeaders(), - ) - } else { - return cb(null) - } - }, - }) - } - if ( - typeof window.GM_xmlhttpRequest === 'undefined' || - window.GM_xmlhttpRequest === null - ) { - fallback() - return - } - const gmOptions = { - method: 'GET', - url, - headers, - responseType: 'arraybuffer', - overrideMimeType: 'text/plain; charset=x-user-defined', - onload(xhr) { - let data - if (xhr.response instanceof ArrayBuffer) { - data = new Uint8Array(xhr.response) - } else { - const r = xhr.responseText - data = new Uint8Array(r.length) - let i = 0 - while (i < r.length) { - data[i] = r.charCodeAt(i) - i++ - } - } - return cb(data, xhr.responseHeaders) - }, - onerror() { - return cb(null) - }, - onabort() { - return cb(null) - }, - } - try { - return (GM?.xmlHttpRequest || GM_xmlhttpRequest)(gmOptions) - } catch (error) { - return fallback() - } - } - }, - - file(url, cb) { - return CrossOrigin.binary(url, function (data, headers) { - if (data == null) { - return cb(null) - } - let name = url.match(/([^\/?#]+)\/*(?:$|[?#])/)?.[1] - const contentType = headers.match(/Content-Type:\s*(.*)/i)?.[1] - const contentDisposition = headers.match( - /Content-Disposition:\s*(.*)/i, - )?.[1] - let mime = contentType?.match(/[^;]*/)[0] || 'application/octet-stream' - const match = - contentDisposition?.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)?.[1] || - contentType?.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)?.[1] - if (match) { - name = match.replace(/\\"/g, '"') - } - if (/^text\/plain;\s*charset=x-user-defined$/i.test(mime)) { - // In JS Blocker (Safari) content type comes back as 'text/plain; charset=x-user-defined'; guess from filename instead. - mime = - $.getOwn( - QR.typeFromExtension, - name.match(/[^.]*$/)[0].toLowerCase(), - ) || 'application/octet-stream' - } - const blob = new Blob([data], { type: mime }) - blob.name = name - return cb(blob) - }) - }, - - Request: (function () { - const Request = class Request { - static initClass() { - this.prototype.status = 0 - this.prototype.statusText = '' - this.prototype.response = null - this.prototype.responseHeaderString = null - } - getResponseHeader(headerName) { - if (this.responseHeaders == null && this.responseHeaderString != null) { - this.responseHeaders = dict() - for (var header of this.responseHeaderString.split('\r\n')) { - var i - if ((i = header.indexOf(':')) >= 0) { - var key = header.slice(0, i).trim().toLowerCase() - var val = header.slice(i + 1).trim() - this.responseHeaders[key] = val - } - } - } - return this.responseHeaders?.[headerName.toLowerCase()] ?? null - } - abort() {} - onloadend() {} - } - Request.initClass() - return Request - })(), - - // Attempts to fetch `url` using cross-origin privileges, if available. - // Interface is a subset of that of $.ajax. - // Options: - // `onloadend` - called with the returned object as `this` on success or error/abort/timeout. - // `timeout` - time limit for request - // `responseType` - expected response type, 'json' by default; 'json' and 'text' supported - // `headers` - request headers - // Returned object properties: - // `status` - HTTP status (0 if connection not successful) - // `statusText` - HTTP status text - // `response` - decoded response body - // `abort` - function for aborting the request (silently fails on some platforms) - // `getResponseHeader` - function for reading response headers - ajax(url, options = {}) { - let gmReq - let { onloadend, timeout, responseType, headers } = options - if (responseType == null) { - responseType = 'json' - } - - if ( - window.GM?.xmlHttpRequest == null && - (typeof window.GM_xmlhttpRequest === 'undefined' || - window.GM_xmlhttpRequest === null) - ) { - return $.ajax(url, options) - } - - const req = new CrossOrigin.Request() - req.onloadend = onloadend - - if (platform === 'userscript') { - const gmOptions = { - method: 'GET', - url, - headers, - timeout, - onload(xhr) { - try { - const response = (() => { - switch (responseType) { - case 'json': - if (xhr.responseText) { - return JSON.parse(xhr.responseText) - } else { - return null - } - default: - return xhr.responseText - } - })() - $.extend(req, { - response, - status: xhr.status, - statusText: xhr.statusText, - responseHeaderString: xhr.responseHeaders, - }) - } catch (error) {} - return req.onloadend() - }, - onerror() { - return req.onloadend() - }, - onabort() { - return req.onloadend() - }, - ontimeout() { - return req.onloadend() - }, - } - try { - gmReq = (GM?.xmlHttpRequest || GM_xmlhttpRequest)(gmOptions) - } catch (error) { - return $.ajax(url, options) - } - - if (gmReq && typeof gmReq.abort === 'function') { - req.abort = function () { - try { - return gmReq.abort() - } catch (error1) {} - } - } - } else { - eventPageRequest( - { type: 'ajax', url, responseType, headers, timeout }, - function (result) { - if (result.status) { - $.extend(req, result) - } - return req.onloadend() - }, - ) - } - - return req - }, - - cache(url, cb) { - if (platform === 'userscript') { - return CrossOrigin.file(url, cb) - } - return eventPageRequest({ type: 'cache', url }, function (result) { - if (result) { - return cb(result) - } else { - return cb(null) - } - }) - }, - - permission(cb, cbFail, origins) { - if (platform === 'crx') { - return eventPageRequest( - { type: 'permission', origins }, - function (result) { - if (result) { - return cb() - } else { - return cbFail() - } - }, - ) - } - return cb() - }, -} -export default CrossOrigin diff --git a/src/platform/CrossOrigin.ts b/src/platform/CrossOrigin.ts new file mode 100644 index 0000000..e548dd7 --- /dev/null +++ b/src/platform/CrossOrigin.ts @@ -0,0 +1,264 @@ +import { Options } from '../../node_modules/@vitejs/plugin-react/dist/index'; +import QR from '../Posting/QR'; +import $ from './$'; +import { dict, platform } from './helpers'; + +type Callback = (response: any, responseHeaderString?: string) => void; +type GMXhrCallback = (xhr: XMLHttpRequestResponseType) => void; + +let eventPageRequest: ((params: any, cb: Callback) => void) | undefined; +if (platform === 'crx') { + eventPageRequest = (function () { + const callbacks: { [id: string]: Callback } = {}; + chrome.runtime.onMessage.addListener(function (response) { + callbacks[response.id](response.data); + return delete callbacks[response.id]; + }); + return (params: any, cb: Callback) => + chrome.runtime.sendMessage(params, (id) => (callbacks[id] = cb)); + })(); +} + +interface ICrossOrigin { + binary(url: string, cb: Callback, headers?: typeof dict): void; + file(url: string, cb: Callback): void; + Request: any; + ajax(url: string, options?: any): any; + cache(url: string, cb: Callback): void; + permission(cb: () => void, cbFail: () => void, origins?: any): void; +} + +const CrossOrigin: ICrossOrigin = { + binary(url, cb, headers = dict()) { + url = url.replace( + /^((?:https?:)?\/\/(?:\w+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//, + '$1//adv/', + ); + if (platform === 'crx') { + eventPageRequest?.( + { type: 'ajax', url, headers, responseType: 'arraybuffer' }, + function ({ response, responseHeaderString }) { + if (response) { + response = new Uint8Array(response); + } + return cb(response, responseHeaderString); + }, + ); + } else { + const fallback = function () { + return $.ajax(url, { + headers, + responseType: 'arraybuffer', + onloadend() { + if (this.status && this.response) { + return cb( + new Uint8Array(this.response), + this.getAllResponseHeaders(), + ); + } else { + return cb(null); + } + }, + }); + }; + if ( + typeof window.GM_xmlhttpRequest === 'undefined' || + window.GM_xmlhttpRequest === null + ) { + fallback(); + return; + } + const gmOptions: any = { + method: 'GET', + url, + headers, + responseType: 'arraybuffer', + overrideMimeType: 'text/plain; charset=x-user-defined', + onload: (xhr) => { + let data; + if (xhr.response instanceof ArrayBuffer) { + data = new Uint8Array(xhr.response); + } else { + const r = xhr.responseText; + data = new Uint8Array(r.length); + let i = 0; + while (i < r.length) { + data[i] = r.charCodeAt(i); + i++; + } + } + return cb(data, xhr.responseHeaders); + }, + onerror: () => cb(null), + onabort: () => cb(null), + }; + try { + return window.GM_xmlhttpRequest(gmOptions); + } catch (error) { + return fallback(); + } + } + }, + file(url, cb) { + return CrossOrigin.binary(url, function (data, headers) { + if (data == null) { + return cb(null); + } + let name = url.match(/([^\/?#]+)\/*(?:$|[?#])/)?.[1]; + const contentType = headers.match(/Content-Type:\s*(.*)/i)?.[1]; + const contentDisposition = headers.match( + /Content-Disposition:\s*(.*)/i, + )?.[1]; + let mime = + contentType?.match(/[^;]*/)[0] || 'application/octet-stream'; + const match = + contentDisposition?.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)?.[1] || + contentType?.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)?.[1]; + if (match) { + name = match.replace(/\\"/g, '"'); + } + if (/^text\/plain;\s*charset=x-user-defined$/i.test(mime)) { + mime = + $.getOwn( + QR.typeFromExtension, + name.match(/[^.]*$/)[0].toLowerCase(), + ) || 'application/octet-stream'; + } + const blob = new Blob([data], { type: mime }); + return cb({ name, blob }); + }); + }, + Request: (function () { + class Request { + status: number; + statusText: string; + response: any; + responseHeaders: string; + + constructor() { + this.status = 0; + this.statusText = ''; + this.response = null; + this.responseHeaders = ''; + } + getResponseHeader(headerName: string) { + const match = this.getResponseHeader + .toString() + .match(new RegExp(`^${headerName}: (.*)`, 'im')); + return match?.[1]; + } + abort() {} + + onloadend() {} +} +return Request; +})(), +ajax(url, options: any = {}) { + let gmReq: any; + let { onloadend, timeout, responseType, headers } = options; + if (responseType == null) { + responseType = 'json'; + } + if (onloadend == null) { + onloadend = function () {}; + } else { + onloadend = onloadend.bind(this); + } + const req = new CrossOrigin.Request(); + req.onloadend = onloadend; + if (platform === 'userscript') { + const gmOptions: any = { + method: 'GET', + url, + headers, + timeout, + onload: (xhr) => { + try { + const response = (() => { + switch (responseType) { + case 'json': + if (xhr.responseText) { + return JSON.parse(xhr.responseText); + } else { + return null; + } + default: + return xhr.responseText; + } + })(); + Object.assign(req, { + response, + status: xhr.status, + statusText: xhr.statusText, + responseHeaderString: xhr.responseHeaders, + }); + } catch (error) {} + return req.onloadend(); + }, + onerror: () => req.onloadend(), + onabort: () => req.onloadend(), + ontimeout: () => req.onloadend(), + }; + try { + gmReq = (GM?.xmlHttpRequest || GM_xmlhttpRequest)(gmOptions); + } catch (error) { + return $.ajax(url, options); + } + + if (gmReq && typeof gmReq.abort === 'function') { + req.abort = function () { + try { + return gmReq.abort(); + } catch (error1) {} + }; + } + } else { + eventPageRequest?.( + { type: 'ajax', url, headers, responseType, timeout }, + function ({ response, responseHeaderString }) { + Object.assign(req, { + response, + status: 200, + statusText: 'OK', + responseHeaderString, + }); + return req.onloadend(); + } + ); + } + return req; +}, +cache(url, cb) { + const cached = CrossOrigin.cache[url]; + if (cached) { + return cb(cached); + } else { + return CrossOrigin.binary(url, function (data) { + if (data == null) { + return cb(null); + } + const blob = new Blob([data]); + CrossOrigin.cache[url] = blob; + return cb(blob); + }); + } +}, +permission(cb, cbFail, origins) { + if (platform === 'crx') { + return eventPageRequest( + { type: 'permission', origins }, + function (result) { + if (result) { + return cb() + } else { + return cbFail() + } + } + ) + } else { + return cb() + } +}, +}; + +export default CrossOrigin; diff --git a/src/site/SW.yotsuba.tsx b/src/site/SW.yotsuba.tsx index a1475aa..1da54a3 100644 --- a/src/site/SW.yotsuba.tsx +++ b/src/site/SW.yotsuba.tsx @@ -287,8 +287,8 @@ $\ cleanComment(bq) { let abbr; if (abbr = $('.abbr', bq)) { // 'Comment too long' or 'EXIF data available' - for (var node of $$('.abbr + br, .exif', bq)) { - $.rm(node); + for (let node of $$('.abbr, .abbr-exp', abbr)) { + $.replace(node, $.tn(node.textContent)); } for (let i = 0; i < 2; i++) { var br; @@ -337,7 +337,9 @@ $\ testNativeExtension() { return $.global(function() { - if (window.Parser?.postMenuIcon) { return this.enabled = 'true'; } + if (window.File && window.FileReader && window.FileList && window.Blob) { + return true; + } }); }, @@ -413,10 +415,15 @@ $\ const o = { // id ID: data.no, + info: null, + capcodeHighlight: data.capcode === 'admin_highlight', + files: [], + file: null, postID: data.no, threadID: data.resto || data.no, boardID, siteID, + extra: {}, isReply: !!data.resto, // thread status isSticky: !!data.sticky, @@ -471,6 +478,7 @@ $\ name: ($.unescape(data.filename)) + data.ext, url: site.urls.file({ siteID, boardID }, filename), height: data.h, + dimensions: '', width: data.w, MD5: data.md5, size: $.bytesToString(data.fsize), @@ -579,7 +587,7 @@ $\ $.extend(container, wholePost); // Fix quotelinks - for (var quote of $$('.quotelink', container)) { + for (var quote of container.querySelectorAll('a.quoteLink')) { var href = quote.getAttribute('href'); if (href[0] === '#') { if (!this.sameThread(boardID, threadID)) { diff --git a/src/types/$.d.ts b/src/types/$.d.ts new file mode 100644 index 0000000..8ed4540 --- /dev/null +++ b/src/types/$.d.ts @@ -0,0 +1,13 @@ +export interface ElementProperties { + [key: string]: any; +} +interface AjaxPageOptions { + onloadend?: (this: XMLHttpRequest, ev: ProgressEvent) => void; + timeout?: number; + responseType?: XMLHttpRequestResponseType; + withCredentials?: boolean; + type?: string; + onprogress?: (this: XMLHttpRequest, ev: ProgressEvent) => void; + form?: FormData; + headers?: Record; +} \ No newline at end of file