From e1d01d02eba5db2f604a5df786c525e95f32a2f9 Mon Sep 17 00:00:00 2001 From: Tuxedo Takodachi Date: Fri, 3 Mar 2023 21:42:38 +0100 Subject: [PATCH] Unpacked extension more fixes --- README.md | 5 +- cspell.json | 3 +- package-lock.json | 7 +- package.json | 33 +++++---- src/Archive/Redirect.js | 1 - src/General/Header.js | 10 ++- src/General/Index.js | 10 +-- src/General/Settings.tsx | 6 +- src/Miscellaneous/Tinyboard.js | 2 +- src/Monitoring/Favicon.js | 1 - src/Posting/QR.js | 4 +- src/classes/Connection.js | 3 + src/classes/{SimpleDict.js => SimpleDict.ts} | 13 ++-- src/globals/globals.ts | 24 +++---- src/main/Main.js | 14 ++-- src/meta/manifest.json | 36 ---------- src/meta/manifestJson.js | 42 ++++++++++++ src/meta/metadata.js | 10 +-- src/platform/$.js | 24 ++++--- src/platform/CrossOrigin.js | 14 ++-- src/platform/helpers.ts | 2 + src/site/SW.yotsuba.Build/PostInfoHtml.tsx | 9 ++- src/site/SW.yotsuba.tsx | 2 +- tools/rollup.js | 72 +++++++++++++++----- tools/tsconfig.json | 7 +- tsconfig.json | 4 +- 26 files changed, 208 insertions(+), 150 deletions(-) rename src/classes/{SimpleDict.js => SimpleDict.ts} (66%) delete mode 100644 src/meta/manifest.json create mode 100644 src/meta/manifestJson.js diff --git a/README.md b/README.md index 19fb50e049..145644ef38 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,9 @@ The 4chan XT project is a migration of 4chan X from coffeescript to TypeScript/J - build script - [x] userscript - [ ] .crx extension - - [ ] beta - - [ ] noupdate + - [x] crx directory that can be loaded as an unpacked extension is created + - [x] beta + - [x] noupdate - [ ] run and debug - [ ] port updates made to 4chan-X made since this was forked diff --git a/cspell.json b/cspell.json index 4798d0ceda..9f3c446f60 100644 --- a/cspell.json +++ b/cspell.json @@ -39,6 +39,7 @@ "yotsuba" ], "ignoreWords": [ + "noupdate", "tyme", "werk" ], @@ -48,4 +49,4 @@ "flagWords": [ "hte" ] -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d3fafd502a..e416676920 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "@rollup/plugin-typescript": "^11.0.0", "@rollup/pluginutils": "^5.0.2", "@types/chrome": "^0.0.217", + "@types/node": "^18.14.5", "@violentmonkey/types": "^0.1.5", "chrome-webstore-upload": "^0.4.4", "esprima": "^4.0.1", @@ -165,9 +166,9 @@ } }, "node_modules/@types/node": { - "version": "14.14.20", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz", - "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==", + "version": "18.14.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.5.tgz", + "integrity": "sha512-CRT4tMK/DHYhw1fcCEBwME9CSaZNclxfzVMe7GsO6ULSwsttbj70wSiX6rZdIjGblu93sTJxLdhNIT85KKI7Qw==", "dev": true }, "node_modules/@types/responselike": { diff --git a/package.json b/package.json index b3b6bfaae7..e62997f6df 100644 --- a/package.json +++ b/package.json @@ -3,18 +3,18 @@ "description": "4chan XT is a script that adds various features to anonymous imageboards.", "meta": { "name": "4chan XT", - "path": "4chan-xt", - "fork": "ccd0", - "page": "https://www.4chan-x.net/", - "downloads": "https://www.4chan-x.net/builds/", + "path": "4chan-XT", + "fork": "TuxedoTako", + "page": "https://github.com/TuxedoTako/4chan-xt", + "downloads": "https://github.com/TuxedoTako/4chan-xt/releases", "oldVersions": "https://raw.githubusercontent.com/ccd0/4chan-x/", - "faq": "https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions", - "captchaFAQ": "https://github.com/ccd0/4chan-x/wiki/Captcha-FAQ", - "cssGuide": "https://github.com/ccd0/4chan-x/wiki/Styling-Guide", - "license": "https://github.com/ccd0/4chan-x/blob/master/LICENSE", - "changelog": "https://github.com/ccd0/4chan-x/blob/master/CHANGELOG.md", - "issues": "https://github.com/ccd0/4chan-x/issues", - "newIssue": "https://github.com/ccd0/4chan-x/issues", + "faq": "https://github.com/TuxedoTako/wiki/Frequently-Asked-Questions", + "captchaFAQ": "https://github.com/TuxedoTako/wiki/Captcha-FAQ", + "cssGuide": "https://github.com/TuxedoTako/wiki/Styling-Guide", + "license": "https://github.com/TuxedoTako/blob/master/LICENSE", + "changelog": "https://github.com/TuxedoTako/blob/master/CHANGELOG.md", + "issues": "https://github.com/TuxedoTako/issues", + "newIssue": "https://github.com/TuxedoTako/issues", "newIssueMaxLength": 8181, "alternatives": "https://www.4chan-x.net/4chan_alternatives.html", "appid": "lacclbnghgdicfifcamcmcnilckjamag", @@ -95,8 +95,8 @@ "GM.xmlHttpRequest" ], "min": { - "chrome": "33", - "firefox": "26", + "chrome": "80", + "firefox": "74", "greasemonkey": "1.14" } }, @@ -104,6 +104,7 @@ "@rollup/plugin-typescript": "^11.0.0", "@rollup/pluginutils": "^5.0.2", "@types/chrome": "^0.0.217", + "@types/node": "^18.14.5", "@violentmonkey/types": "^0.1.5", "chrome-webstore-upload": "^0.4.4", "esprima": "^4.0.1", @@ -134,10 +135,12 @@ "license": "MIT", "readmeFilename": "README.md", "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" }, "type": "module", "scripts": { - "build": "node ./tools/rollup" + "build": "node ./tools/rollup", + "build:beta": "node ./tools/rollup -beta", + "build:noupdate": "node ./tools/rollup -noupdate" } } diff --git a/src/Archive/Redirect.js b/src/Archive/Redirect.js index fd98157d12..fa4bdec5bd 100644 --- a/src/Archive/Redirect.js +++ b/src/Archive/Redirect.js @@ -11,7 +11,6 @@ import archives from './archives.js'; */ const Redirect = { - // TODO check archives, init() { diff --git a/src/General/Header.js b/src/General/Header.js index 01e51d0c58..2d4ff19ef1 100644 --- a/src/General/Header.js +++ b/src/General/Header.js @@ -10,6 +10,7 @@ import BoardConfig from "./BoardConfig"; import Get from "./Get"; import Settings from "./Settings"; import UI from "./UI"; +import meta from '../../package.json'; /* * decaffeinate suggestions: @@ -670,9 +671,12 @@ var Header = { break; } - // TODO meta - const el = $.el('span', - { innerHTML: "meta.name needs your permission to show desktop notifications. [FAQ]
or " }); + const el = $.el('span', { + innerHTML: + `${meta.name} needs your permission to show desktop notifications. ` + + `[FAQ]` + + `
or ` + }); const [authorize, disable] = Array.from($$('button', el)); $.on(authorize, 'click', () => Notification.requestPermission(function (status) { Header.areNotificationsEnabled = status === 'granted'; diff --git a/src/General/Index.js b/src/General/Index.js index 797acfbd62..721f9caf85 100644 --- a/src/General/Index.js +++ b/src/General/Index.js @@ -747,10 +747,12 @@ const Index = { // Optional notification for manual refreshes if (!Index.notice) { Index.notice = new Notice('info', 'Refreshing index...'); } if (!Index.nTimeout) { - Index.nTimeout = setTimeout(() => // TODO check if notice exists - Index.notice.el.lastElementChild.textContent += ' (disable JSON Index if this takes too long)' - , 3 * SECOND); - } + Index.nTimeout = setTimeout(() => { + if (Index.notice) { + Index.notice.el.lastElementChild.textContent += ' (disable JSON Index if this takes too long)'; + } + }, 3 * SECOND); + }; } else { // Also display notice if Index Refresh is taking too long if (!Index.nTimeout) { diff --git a/src/General/Settings.tsx b/src/General/Settings.tsx index 77553b6ba7..733c2dad84 100644 --- a/src/General/Settings.tsx +++ b/src/General/Settings.tsx @@ -30,9 +30,11 @@ import meta from '../../package.json'; import { Conf, E, g } from '../globals/globals'; import Header from './Header'; import h, { hFragment } from '../globals/jsx'; -import { dict } from '../platform/helpers'; +import { dict, platform } from '../platform/helpers'; const Settings = { + dialog: undefined as HTMLDivElement | undefined, + init() { // 4chan X settings link const link = $.el('a', { @@ -247,7 +249,7 @@ Enable it on boards.${location.hostname.split('.')[1]}.org in your browser's pri if ($.perProtocolSettings || (location.protocol !== 'https:')) { $('div[data-name="Redirect to HTTPS"]', section).hidden = true; } - if ($.platform !== 'crx') { + if (platform !== 'crx') { $('div[data-name="Work around CORB Bug"]', section).hidden = true; } diff --git a/src/Miscellaneous/Tinyboard.js b/src/Miscellaneous/Tinyboard.js index 4c851752ac..a1109ff03b 100644 --- a/src/Miscellaneous/Tinyboard.js +++ b/src/Miscellaneous/Tinyboard.js @@ -18,7 +18,7 @@ const Tinyboard = { let { boardID, threadID } = document.currentScript.dataset; threadID = +threadID; const form = document.querySelector('form[name="post"]'); - window.$(document).ajaxComplete(function (event, request, settings) { + $(document).ajaxComplete(function (event, request, settings) { let postID; if (settings.url !== form.action) { return; } if (!(postID = +request.responseJSON?.id)) { return; } diff --git a/src/Monitoring/Favicon.js b/src/Monitoring/Favicon.js index 8c89aa6242..1441035e97 100644 --- a/src/Monitoring/Favicon.js +++ b/src/Monitoring/Favicon.js @@ -147,7 +147,6 @@ const Favicon = { SFW: '//s.4cdn.org/image/favicon-ws.ico', NSFW: '//s.4cdn.org/image/favicon.ico', - // TODO dead: `data:image/gif;base64,${dead}`, logo: `data:image/png;base64,${empty}`, }; diff --git a/src/Posting/QR.js b/src/Posting/QR.js index 635b49b705..956ec577a1 100644 --- a/src/Posting/QR.js +++ b/src/Posting/QR.js @@ -971,8 +971,8 @@ var QR = { post.unlock(); if (err = this.response?.getElementById('errmsg')) { // error! - // TODO: check if exists - $('a', err).target = '_blank'; // duplicate image link + const el = $('a', err); + if (el) el.target = '_blank'; // duplicate image link } else if (connErr = (!this.response || (this.response.title !== 'Post successful!'))) { err = QR.connectionError(); if ((QR.captcha === Captcha.v2) && QR.currentCaptcha) { Captcha.cache.save(QR.currentCaptcha); } diff --git a/src/classes/Connection.js b/src/classes/Connection.js index 5c559de9e1..d6b17ffbd4 100644 --- a/src/classes/Connection.js +++ b/src/classes/Connection.js @@ -1,3 +1,6 @@ +import $ from "../platform/$"; +import { g } from "../globals/globals"; + export default class Connection { constructor(target, origin, cb = {}) { this.send = this.send.bind(this); diff --git a/src/classes/SimpleDict.js b/src/classes/SimpleDict.ts similarity index 66% rename from src/classes/SimpleDict.js rename to src/classes/SimpleDict.ts index cc46ecb773..eb11be2656 100644 --- a/src/classes/SimpleDict.js +++ b/src/classes/SimpleDict.ts @@ -1,16 +1,13 @@ 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 - */ -export default class SimpleDict { +export default class SimpleDict { + keys: string[] + constructor() { this.keys = []; } - push(key, data) { + push(key, data: T) { key = `${key}`; if (!this[key]) { this.keys.push(key); } return this[key] = data; @@ -29,7 +26,7 @@ export default class SimpleDict { for (var key of this.keys) fn(this[key]); } - get(key) { + get(key): T { if (key === 'keys') { return undefined; } else { diff --git a/src/globals/globals.ts b/src/globals/globals.ts index 3652e8eff5..21a1340e37 100644 --- a/src/globals/globals.ts +++ b/src/globals/globals.ts @@ -1,18 +1,11 @@ import version from "../../version.json"; import meta from "../../package.json"; import type SimpleDict from "../classes/SimpleDict"; +import type Post from "../classes/Post"; +import type Thread from "../classes/Thread"; +import type SWTinyboard from "../site/SW.tinyboard"; // interfaces might be incomplete - -export interface Site { - ID: string, - siteID: string, - properties: { - software: string, - }, - software: string, -} - export interface BoardConfig { board: string bump_limit: number @@ -41,8 +34,8 @@ export interface Board { boardID: string, siteID: string, config: BoardConfig, - posts: SimpleDict, - threads: SimpleDict, + posts: SimpleDict, + threads: SimpleDict, } export const Conf = Object.create(null); @@ -50,9 +43,12 @@ export const Conf = Object.create(null); export const g: { VERSION: string, NAMESPACE: string, - sites: Site[], + sites: (typeof SWTinyboard)[], boards: Board[], - SITE?: Site, + posts?: SimpleDict, + threads?: SimpleDict + THREADID?: number, + SITE?: typeof SWTinyboard, BOARD?: Board, VIEW?: string, } = { diff --git a/src/main/Main.js b/src/main/Main.js index 88b5a96e0c..bed192aceb 100644 --- a/src/main/Main.js +++ b/src/main/Main.js @@ -88,7 +88,7 @@ import Menu from "../Menu/Menu"; import BoardConfig from "../General/BoardConfig"; import CaptchaReplace from "../Posting/Captcha.replace"; import Get from "../General/Get"; -import { dict } from "../platform/helpers"; +import { dict, platform } from "../platform/helpers"; /* * decaffeinate suggestions: @@ -106,7 +106,7 @@ const Main = { let key; try { let w = window; - if ($.platform === 'crx') { w = (w.wrappedJSObject || w); } + if (platform === 'crx') { w = (w.wrappedJSObject || w); } if (`${meta.name} antidup` in w) { return; } w[`${meta.name} antidup`] = true; } catch (error) { } @@ -256,9 +256,8 @@ const Main = { items.previousversion = (changes.previousversion = g.VERSION); return $.set(changes, function () { if (items['Show Updated Notifications'] ?? true) { - // TODO meta const el = $.el('span', - { innerHTML: `meta.name has been updated to version ${g.VERSION}.` }); + { innerHTML: `${meta.name} has been updated to version ${g.VERSION}.` }); return new Notice('info', el, 15); } }); @@ -811,7 +810,7 @@ const Main = { { textContent: `${data.error.name || 'Error'}: ${data.error.message || 'see console for details'}` }); const lines = data.error.stack?.match(/\d+(?=:\d+\)?$)/mg)?.join().replace(/^/, ' at ') || ''; const context = $.el('div', - { textContent: `(${meta.name} ${meta.fork} v${g.VERSION} ${$.platform} on ${$.engine}${lines})` }); + { textContent: `(${meta.name} ${meta.fork} v${g.VERSION} ${platform} on ${$.engine}${lines})` }); return [message, error, context]; }, @@ -822,7 +821,6 @@ const Main = { if (errors.length > 1) { title += ` (+${errors.length - 1} other errors)`; } let details = ''; const addDetails = function (text) { - // TODO meta if (encodeURIComponent(title + details + text + '\n').length <= "meta.newIssueMaxLength - meta.newIssue.replace(/%(title|details)/, '')".length) { return details += text + '\n'; } @@ -830,12 +828,12 @@ const Main = { addDetails(`\ [Please describe the steps needed to reproduce this error.] -Script: ${meta.name} ${meta.fork} v${g.VERSION} ${$.platform} +Script: ${meta.name} ${meta.fork} v${g.VERSION} ${platform} URL: ${location.href} User agent: ${navigator.userAgent}\ ` ); - if (($.platform === 'userscript') && (info = (() => { + if ((platform === 'userscript') && (info = (() => { if (typeof GM !== 'undefined' && GM !== null) { return GM.info; } else { if (typeof GM_info !== 'undefined' && GM_info !== null) { return GM_info; } } diff --git a/src/meta/manifest.json b/src/meta/manifest.json deleted file mode 100644 index 1c82e9c8e0..0000000000 --- a/src/meta/manifest.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "<%= meta.name %>", - "version": "<%= readJSON('/version.json').version %>", - "manifest_version": 2, - "description": "<%= description %>", - "icons": { - "16": "icon16.png", - "48": "icon48.png", - "128": "icon128.png" - }, - "content_scripts": [{ - "js": ["script.js"], - "matches": <%= JSON.stringify(meta.matches_only.concat(meta.matches, meta.matches_extra)) %>, - "exclude_matches": <%= JSON.stringify(meta.exclude_matches) %>, - "all_frames": true, - "run_at": "document_start" - }], - "background": { - "scripts": ["eventPage.js"], - "persistent": false - }, - "homepage_url": "<%= meta.page %>", -<% if (channel !== '-noupdate') { %> "update_url": "<%= meta.downloads %>updates<%= channel %>.xml", - "key": "<%= meta.appid %>", -<% } %> "minimum_chrome_version": "<%= meta.min.chrome %>", - "permissions": <%= JSON.stringify(meta.matches_only.concat(meta.matches, ["storage"])) %>, - "optional_permissions": [ - "*://*/" - ], - "applications": { - "gecko": { - "id": "<%= meta.appidGecko %>"<% if (channel !== '-noupdate') { %>, - "update_url": "<%= meta.downloads %>updates<%= channel %>.json" -<% } %> } - } -} diff --git a/src/meta/manifestJson.js b/src/meta/manifestJson.js new file mode 100644 index 0000000000..92cea7a96e --- /dev/null +++ b/src/meta/manifestJson.js @@ -0,0 +1,42 @@ +export default function generateManifestJson(p, version, channel) { + const manifest = { + "name": p.meta.name, + "version": version.version, + "manifest_version": 2, + "description": p.description, + "icons": { + "16": "icon16.png", + "48": "icon48.png", + "128": "icon128.png" + }, + "content_scripts": [{ + "js": ["script.js"], + "matches": p.meta.matches_only.concat(p.meta.matches, p.meta.matches_extra), + "exclude_matches": p.meta.exclude_matches, + "all_frames": true, + "run_at": "document_start" + }], + "background": { + "scripts": ["eventPage.js"], + "persistent": false + }, + "homepage_url": p.meta.page, + "minimum_chrome_version": p.meta.min.chrome, + "permissions": p.meta.matches_only.concat(p.meta.matches, ["storage"]), + "optional_permissions": [ + "*://*/" + ], + "applications": { + "gecko": { + "id": p.meta.appidGecko, + } + } + }; + + if (channel !== '-noupdate') { + manifest.update_url = `${p.meta.downloads}updates${channel}.xml`; + manifest.applications.gecko.update_url = `${p.meta.downloads}updates${channel}.json`; + } + + return JSON.stringify(manifest, undefined, 2); +} diff --git a/src/meta/metadata.js b/src/meta/metadata.js index 2df3e9e2ae..c207b4072f 100644 --- a/src/meta/metadata.js +++ b/src/meta/metadata.js @@ -7,11 +7,7 @@ import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); -export default async function generateMetadata(packageJson) { - - // TODO - const channel = ''; - +export default async function generateMetadata(packageJson, channel) { const meta = packageJson.meta; const versionFile = await readFile(resolve(__dirname, '../../version.json')); @@ -21,7 +17,7 @@ export default async function generateMetadata(packageJson) { const icon = Buffer.from(iconFile).toString('base64'); let output = `// ==UserScript== -// @name ${meta.name}${channel ? ' beta' : ''} +// @name ${meta.name}${channel === '-beta' ? ' beta' : ''} // @version ${version.version} // @minGMVer ${meta.min.greasemonkey} // @minFFVer ${meta.min.firefox} @@ -84,7 +80,7 @@ export default async function generateMetadata(packageJson) { output += '\n// @run-at document-start'; if (channel === '-noupdate') { - output += '\n// @updateURL https://noupdate.invalid/\n// @downloadURL https://noupdate.invalid/\n'; + output += '\n// @updateURL https://noupdate.invalid/\n// @downloadURL https://noupdate.invalid/'; } else { output += ` // @updateURL ${meta.downloads}${packageJson.name}${channel}.meta.js diff --git a/src/platform/$.js b/src/platform/$.js index d1b8fd9b20..72b34f496b 100644 --- a/src/platform/$.js +++ b/src/platform/$.js @@ -11,7 +11,7 @@ import Notice from "../classes/Notice"; import { Conf, g } from "../globals/globals"; import CrossOrigin from "./CrossOrigin"; -import { debounce, dict, MINUTE, SECOND } from "./helpers"; +import { debounce, dict, MINUTE, platform, SECOND } from "./helpers"; // not chainable const $ = (selector, root = document.body) => root.querySelector(selector); @@ -74,7 +74,7 @@ $.ajax = (function () { if (!options.type) { options.type = (options.form && 'post') || 'get'; } // 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 (globalThis.chrome?.extension) { + if (platform === 'crx') { // XXX https://bugs.chromium.org/p/chromium/issues/detail?id=920638 if (Conf['Work around CORB Bug'] && g.SITE.software === 'yotsuba' && !options.testCORB && FormData.prototype.entries) { return $.ajaxPage(url, options); @@ -93,7 +93,7 @@ $.ajax = (function () { $.extend(r.upload, { onprogress }); // connection error or content blocker $.on(r, 'error', function () { if (!r.status) { return console.warn(`4chan X failed to load: ${url}`); } }); - if (globalThis.chrome?.extension) { + if (platform === 'crx') { // https://bugs.chromium.org/p/chromium/issues/detail?id=920638 $.on(r, 'load', () => { if (!Conf['Work around CORB Bug'] && r.readyState === 4 && r.status === 200 && r.statusText === '' && r.response === null) { @@ -112,7 +112,7 @@ $.ajax = (function () { return r; }); - if (!globalThis.chrome?.extension) { + if (platform === 'userscript') { return r; } else { // # XXX https://bugs.chromium.org/p/chromium/issues/detail?id=920638 @@ -145,8 +145,11 @@ $.ajax = (function () { } r.onloadend = function () { delete window.FCX.requests[id]; - const { status, statusText, response } = this; + let { status, statusText, response } = this; const responseHeaderString = this.getAllResponseHeaders(); + if (this.getResponseHeader('Content-Type') === 'application/json' && typeof response === 'string') { + response = JSON.parse(response); + } const detail = { status, statusText, response, responseHeaderString, id }; return document.dispatchEvent(new CustomEvent('4chanXAjaxLoadend', { bubbles: true, detail })); }; @@ -199,6 +202,7 @@ $.ajax = (function () { return $.ajaxPage = function (url, options = {}) { let req; let { onloadend, timeout, responseType, withCredentials, type, onprogress, form, headers } = options; + if (!type) type = 'GET'; const id = requestID++; requests[id] = (req = new CrossOrigin.Request()); $.extend(req, { responseType, onloadend }); @@ -415,7 +419,7 @@ $.one = function (el, events, handler) { }; $.event = function (event, detail, root = document) { - if (!globalThis.chrome?.extension) { + if (platform === 'userscript') { if ((detail != null) && (typeof cloneInto === 'function')) { detail = cloneInto(detail, document.defaultView); } @@ -423,7 +427,7 @@ $.event = function (event, detail, root = document) { return root.dispatchEvent(new CustomEvent(event, { bubbles: true, cancelable: true, detail })); }; -if (!globalThis.chrome?.extension) { +if (platform === 'userscript') { // XXX Make $.event work in Pale Moon with GM 3.x (no cloneInto function). (function () { if (!/PaleMoon\//.test(navigator.userAgent) || (+GM_info?.version?.split('.')[0] < 2) || (typeof cloneInto !== 'undefined')) { return; } @@ -452,7 +456,7 @@ if (!globalThis.chrome?.extension) { $.modifiedClick = e => e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || (e.button !== 0); -$.open = GM_openInTab || (url => window.open(url, '_blank')); +$.open = window.GM_openInTab || (url => window.open(url, '_blank')); $.queueTask = (function () { // inspired by https://www.w3.org/Bugs/Public/show_bug.cgi?id=15007 @@ -540,8 +544,6 @@ $.engine = (function () { if (/Gecko\/|Goanna/.test(navigator.userAgent)) { return 'gecko'; } // Goanna = Pale Moon 26+ })(); -$.platform = GM ? 'userscript' : 'crx'; - $.hasStorage = (function () { try { if (localStorage.getItem(g.NAMESPACE + 'hasStorage') === 'true') { return true; } @@ -574,7 +576,7 @@ $.securityCheck = function (data) { } }; -if (globalThis.chrome?.extension) { +if (platform === 'crx') { // https://developer.chrome.com/extensions/storage.html $.oldValue = { local: dict(), diff --git a/src/platform/CrossOrigin.js b/src/platform/CrossOrigin.js index 0fc6c4f893..12fba909bc 100644 --- a/src/platform/CrossOrigin.js +++ b/src/platform/CrossOrigin.js @@ -1,6 +1,6 @@ import QR from "../Posting/QR"; import $ from "./$"; -import { dict } from "./helpers"; +import { dict, platform } from "./helpers"; /* * decaffeinate suggestions: @@ -11,7 +11,7 @@ import { dict } from "./helpers"; * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md */ let eventPageRequest; -if (globalThis.chrome?.extension) { +if (platform === 'crx') { eventPageRequest = (function () { const callbacks = []; chrome.runtime.onMessage.addListener(function (response) { @@ -27,7 +27,7 @@ const 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 (globalThis.chrome?.extension) { + if (platform === 'crx') { eventPageRequest({ type: 'ajax', url, headers, responseType: 'arraybuffer' }, function ({ response, responseHeaderString }) { if (response) { response = new Uint8Array(response); } return cb(response, responseHeaderString); @@ -46,7 +46,7 @@ const CrossOrigin = { } }); }; - if ((GM?.xmlHttpRequest == null) && (typeof GM_xmlhttpRequest === 'undefined' || GM_xmlhttpRequest === null)) { + if ((typeof window.GM_xmlhttpRequest === 'undefined' || window.GM_xmlhttpRequest === null)) { fallback(); return; } @@ -151,14 +151,14 @@ const CrossOrigin = { let { onloadend, timeout, responseType, headers } = options; if (responseType == null) { responseType = 'json'; } - if ((GM?.xmlHttpRequest == null) && (typeof GM_xmlhttpRequest === 'undefined' || GM_xmlhttpRequest === null)) { + if (typeof window.GM_xmlhttpRequest === 'undefined' || window.GM_xmlhttpRequest === null) { return $.ajax(url, options); } const req = new CrossOrigin.Request(); req.onloadend = onloadend; - if (!globalThis.chrome?.extension) { + if (platform === 'userscript') { const gmOptions = { method: 'GET', url, @@ -218,7 +218,7 @@ const CrossOrigin = { }, permission(cb, cbFail, origins) { - if (globalThis.chrome?.extension) { + if (platform === 'crx') { return eventPageRequest({ type: 'permission', origins }, function (result) { if (result) { diff --git a/src/platform/helpers.ts b/src/platform/helpers.ts index 98efa65b6d..63db5aa6b2 100644 --- a/src/platform/helpers.ts +++ b/src/platform/helpers.ts @@ -50,3 +50,5 @@ export const SECOND = 1000; export const MINUTE = SECOND * 60; export const HOUR = MINUTE * 60; export const DAY = HOUR * 24; + +export const platform = window.GM_xmlhttpRequest ? 'userscript' : 'crx'; diff --git a/src/site/SW.yotsuba.Build/PostInfoHtml.tsx b/src/site/SW.yotsuba.Build/PostInfoHtml.tsx index d691ec09ba..4b60469752 100644 --- a/src/site/SW.yotsuba.Build/PostInfoHtml.tsx +++ b/src/site/SW.yotsuba.Build/PostInfoHtml.tsx @@ -16,9 +16,8 @@ export default function generatePostInfoHtml( ) } - const nameBlockContent: (EscapedHtml | string)[] = [ - email ? {nameHtml} : nameHtml - ]; + const nameBlockContent: (EscapedHtml | string)[] = + email ? [{...nameHtml}] : nameHtml; if (!(boardID === "f" && !o.isReply || capcodeDescription)) nameBlockContent.push(' '); if (capcodeDescription) { nameBlockContent.push( @@ -41,8 +40,8 @@ export default function generatePostInfoHtml( if (flagCodeTroll) nameBlockContent.push(' ', ); const postNumContent: (EscapedHtml | string)[] = [ - No., - {ID}, + ' ', No., + ' ', {ID}, ]; if (o.isSticky) { diff --git a/src/site/SW.yotsuba.tsx b/src/site/SW.yotsuba.tsx index 89ae92974b..5f0557ba8c 100644 --- a/src/site/SW.yotsuba.tsx +++ b/src/site/SW.yotsuba.tsx @@ -562,7 +562,7 @@ $\ const wholePost = <> {(o.isReply ?
>>
: '')} -
+
{(o.isReply ? <>{postInfo}{fileBlock} : <>{fileBlock}{postInfo})}
{commentHTML}
diff --git a/tools/rollup.js b/tools/rollup.js index 7e6c84b31b..6d58caf553 100644 --- a/tools/rollup.js +++ b/tools/rollup.js @@ -1,20 +1,34 @@ -import { rollup } from "rollup"; +import { rollup } from 'rollup'; import typescript from '@rollup/plugin-typescript'; -import setupFileInliner from "./rollup-plugin-inline-file.js"; -import { dirname, resolve } from "path"; +import setupFileInliner from './rollup-plugin-inline-file.js'; +import { dirname, resolve } from 'path'; import { fileURLToPath } from 'url'; -import generateMetadata from "../src/meta/metadata.js"; -import { readFile, writeFile } from "fs/promises"; -import importBase64 from "./rollup-plugin-base64.js"; +import generateMetadata from '../src/meta/metadata.js'; +import { copyFile, readFile, writeFile } from 'fs/promises'; +import importBase64 from './rollup-plugin-base64.js'; +import generateManifestJson from '../src/meta/manifestJson.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); +const buildDir = resolve(__dirname, '../builds/test/'); + +let channel = ''; + +if (process.argv.includes('-beta')) { + channel = '-beta'; +} else if (process.argv.includes('-noupdate')) { + channel = '-noupdate'; +} + (async () => { - const packageJsonFile = await readFile(resolve(__dirname, '../package.json')); - const packageJson = JSON.parse(packageJsonFile.toString()); + const packageJson = JSON.parse(await readFile(resolve(__dirname, '../package.json'), 'utf-8')); + + const metadata = await generateMetadata(packageJson, channel); + + const license = await readFile(resolve(__dirname, '../LICENSE'), 'utf8'); + + const version = JSON.parse(await readFile(resolve(__dirname, '../version.json'), 'utf-8')); - const metadata = await generateMetadata(packageJson); - await writeFile(resolve(__dirname, `../builds/test/${packageJson.meta.path}.meta.js`), metadata); const inlineFile = await setupFileInliner(packageJson); @@ -45,12 +59,38 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); ] }); - const bundledResult = await bundle.write({ - banner: metadata, - // file: '../builds/test/rollupOutput.js', - name: "bundle.js", - dir: resolve(__dirname, '../builds/test/'), + /** @type {import('rollup').OutputOptions} */ + const sharedBundleOpts = { format: "iife", - generatedCode: { constBindings: false } + generatedCode: { + // needed for possible circular dependencies + constBindings: false, + }, + // Can't be none as long as the root file defined exports + // exports: 'none', + }; + + // user script + await bundle.write({ + ...sharedBundleOpts, + banner: metadata + license, + // file: '../builds/test/rollupOutput.js', + file: resolve(buildDir, `${packageJson.meta.path}${channel}.user.js`), + }); + + // chrome extension + const crxDir = resolve(buildDir, 'crx'); + await bundle.write({ + ...sharedBundleOpts, + banner: license, + file: resolve(crxDir, 'script.js'), }); + + await copyFile(resolve(__dirname, '../src/meta/eventPage.js'), resolve(crxDir, 'eventPage.js')); + + writeFile(resolve(crxDir, 'manifest.json'), generateManifestJson(packageJson, version, channel)); + + for (const file of ['icon16.png', 'icon48.png', 'icon128.png']) { + await copyFile(resolve(__dirname, '../src/meta/', file), resolve(crxDir, file)); + }; })(); diff --git a/tools/tsconfig.json b/tools/tsconfig.json index 78cc0996e3..b8183e413a 100644 --- a/tools/tsconfig.json +++ b/tools/tsconfig.json @@ -1,6 +1,11 @@ { "compilerOptions": { - "moduleResolution": "node16" + "moduleResolution": "node16", + "types": [ + "@violentmonkey/types", + "@types/chrome", + "node" + ], }, "extends": "../tsconfig.json" } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index b5fb124b8b..a728c03942 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,9 @@ "DOM", "ES2020" ], - "outDir": "builds/test/tsOutput", + // needs to be in the deepest dir used as target in the rollup build + // https://stackoverflow.com/q/40460790, https://github.com/rollup/plugins/issues/243 + "outDir": "builds/test/crx/tsOutput", }, "exclude": [ "builds/test/tsOutput"