types fixes etc

This commit is contained in:
Lalle 2023-04-22 04:55:27 +02:00
parent 22895eed98
commit bbcc0ec5db
No known key found for this signature in database
GPG Key ID: A6583D207A8F6B0D
6 changed files with 1200 additions and 669 deletions

View File

@ -75,14 +75,13 @@ var Filter = {
for (var line of (Conf[key] as string).split('\n')) { for (var line of (Conf[key] as string).split('\n')) {
let hl: string let hl: string
let isstring: boolean let isstring: boolean
let regexp: RegExp | string let regexp: RegExp | string | RegExpMatchArray
let top: boolean let top: boolean
let types: string[] let types: string[]
if (line[0] === '#') { if (line[0] === '#') {
continue continue
} }
if (!(regexp = line.match(/\/(.*)\/(\w*)/))) { if (!(regexp = line.match(/\/(.*)\/(\w*)/))) {
continue continue
} }
@ -109,17 +108,8 @@ var Filter = {
regexp = RegExp(regexp[1], regexp[2]) regexp = RegExp(regexp[1], regexp[2])
} catch (err) { } catch (err) {
// I warned you, bro. // I warned you, bro.
new Notice( // Notice(type, content, timeout, onclose)
'warning', new Notice('error', `Invalid regular expression: ${regexp[1]}`)
[
$.tn(`Invalid ${key} filter:`),
$.el('br'),
$.tn(line),
$.el('br'),
$.tn(err.message),
],
60,
)
continue continue
} }
} }
@ -150,14 +140,8 @@ var Filter = {
// Highlight the post. // Highlight the post.
// If not specified, the highlight class will be filter-highlight. // If not specified, the highlight class will be filter-highlight.
if ((hl = /(?:^|;)\s*highlight/.test(filter))) { if ((hl = filter.match(/(?:^|;)\s*highlight:([^;]+)/)?.[1])) {
hl = hl = hl.trim()
filter.match(/(?:^|;)\s*highlight:([\w-]+)/)?.[1] ||
'filter-highlight'
// Put highlighted OP's thread on top of the board page or not.
// Defaults to on top.
top = filter.match(/(?:^|;)\s*top:(yes|no)/)?.[1] || 'yes'
top = top === 'yes' // Turn it into a boolean
} }
// Fields that this filter applies to (for 'general' filters) // Fields that this filter applies to (for 'general' filters)
@ -383,8 +367,8 @@ var Filter = {
} }
} }
g.BOARD.threads.forEach(function (thread) { g.BOARD.threads.forEach(function (thread) {
if (thread.catalogViewNative) { if (thread.catalogView) {
return Filter.catalogNode.call(thread.catalogViewNative) return Filter.catalogNode.call(thread.catalogView)
} }
}) })
}, },
@ -544,7 +528,7 @@ var Filter = {
} }
const filter = files.map((f) => `/${f.MD5}/`).join('\n') const filter = files.map((f) => `/${f.MD5}/`).join('\n')
Filter.addFilter('MD5', filter) Filter.addFilter('MD5', filter)
const origin = post.origin || post Filter.showFilters('MD5')
if (origin.isReply) { if (origin.isReply) {
PostHiding.hide(origin) PostHiding.hide(origin)
} else if (g.VIEW === 'index') { } else if (g.VIEW === 'index') {

View File

@ -1,11 +1,3 @@
/*
* 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
*/
import SettingsPage from './Settings/SettingsHtml' import SettingsPage from './Settings/SettingsHtml'
import FilterGuidePage from './Settings/Filter-guide.html' import FilterGuidePage from './Settings/Filter-guide.html'
import SaucePage from './Settings/Sauce.html' import SaucePage from './Settings/Sauce.html'
@ -79,12 +71,12 @@ var Settings = {
value: { disableAll: true }, value: { disableAll: true },
}) })
} }
}) }, Object.create(null))
} else { } else {
return $.global(() => return $.global(() =>
Object.defineProperty(window, 'Config', { Object.defineProperty(window, 'Config', {
value: { disableAll: true }, value: { disableAll: true },
}), }), Object.create(null)
) )
} }
} }
@ -305,7 +297,7 @@ Enable it on boards.${
$('div[data-name="Work around CORB Bug"]', section).hidden = true $('div[data-name="Work around CORB Bug"]', section).hidden = true
} }
$.get(items, function (items) { $.get(items, function (items: string[]) {
for (key in items) { for (key in items) {
var val = items[key] var val = items[key]
inputs[key].checked = val inputs[key].checked = val

File diff suppressed because it is too large Load Diff

View File

@ -31,6 +31,8 @@ export default class Post {
declare files: ReturnType<Post['parseFile']>[] declare files: ReturnType<Post['parseFile']>[]
declare info: { declare info: {
commentHTML: string
comment: string
subject: string | undefined subject: string | undefined
name: string | undefined name: string | undefined
email: string | undefined email: string | undefined
@ -72,7 +74,6 @@ export default class Post {
this.root = root this.root = root
this.thread = thread this.thread = thread
this.board = board this.board = board
$.extend(this, flags)
this.ID = +root.id.match(/\d*$/)[0] this.ID = +root.id.match(/\d*$/)[0]
this.postID = this.ID this.postID = this.ID
this.threadID = this.thread.ID this.threadID = this.thread.ID
@ -102,12 +103,13 @@ export default class Post {
const name = this.nodes.name?.textContent const name = this.nodes.name?.textContent
const tripcode = this.nodes.tripcode?.textContent const tripcode = this.nodes.tripcode?.textContent
this.info = { this.info = {
commentHTML: this.nodes.comment?.innerHTML || '',
comment: this.nodes.comment?.textContent || '',
subject: this.nodes.subject?.textContent || undefined, subject: this.nodes.subject?.textContent || undefined,
name, name,
email: this.nodes.email email: this.nodes.email
? decodeURIComponent(this.nodes.email.href.replace(/^mailto:/, '')) ? decodeURIComponent(this.nodes.email.baseURI.split('mailto:')[1])
: undefined, : undefined,
tripcode, tripcode,
uniqueID: this.nodes.uniqueID?.textContent, uniqueID: this.nodes.uniqueID?.textContent,
@ -256,7 +258,7 @@ export default class Post {
const nodes = $.X('.//br|.//text()', bq) const nodes = $.X('.//br|.//text()', bq)
let i = 0 let i = 0
while ((node = nodes.snapshotItem(i++))) { while ((node = nodes.snapshotItem(i++))) {
text += node.data || '\n' text += node.nodeName === 'BR' ? '\n' : node.textContent
} }
return text return text
} }
@ -328,6 +330,12 @@ export default class Post {
parseFile(fileRoot: HTMLElement) { parseFile(fileRoot: HTMLElement) {
interface File { interface File {
isImage: boolean
isVideo: boolean
url: string
dimensions: string
name: string
MD5: string
text: string text: string
link: HTMLAnchorElement link: HTMLAnchorElement
thumb: HTMLElement thumb: HTMLElement

View File

@ -115,7 +115,7 @@ var Main = {
return return
} }
w[`${meta.name} antidup`] = true w[`${meta.name} antidup`] = true
} catch (error) {} } catch (error) { }
// Don't run inside ad iframes. // Don't run inside ad iframes.
try { try {
@ -125,7 +125,7 @@ var Main = {
) { ) {
return return
} }
} catch (error1) {} } catch (error1) { }
// Detect multiple copies of 4chan X // Detect multiple copies of 4chan X
if (doc && $.hasClass(doc, 'fourchan-x')) { if (doc && $.hasClass(doc, 'fourchan-x')) {
@ -154,7 +154,7 @@ var Main = {
(() => { (() => {
try { try {
return cb() return cb()
} catch (error2) {} } catch (error2) { }
})(), })(),
) )
} }
@ -189,7 +189,7 @@ var Main = {
} }
return fromCharCode0.apply(this, arguments) return fromCharCode0.apply(this, arguments)
}) })
}) }, Object.create(null))
$.asap(docSet, () => $.onExists(doc, 'iframe[srcdoc]', $.rm)) $.asap(docSet, () => $.onExists(doc, 'iframe[srcdoc]', $.rm))
} }
@ -242,7 +242,7 @@ var Main = {
!$$('script:not([src])', d).filter((s) => /this\[/.test(s.textContent)) !$$('script:not([src])', d).filter((s) => /this\[/.test(s.textContent))
.length .length
) { ) {
;($.getSync || $.get)( ; ($.getSync || $.get)(
{ jsWhitelist: Conf['jsWhitelist'] }, { jsWhitelist: Conf['jsWhitelist'] },
({ jsWhitelist }) => ({ jsWhitelist }) =>
$.addCSP( $.addCSP(
@ -269,10 +269,10 @@ var Main = {
) { ) {
location.replace( location.replace(
'https://' + 'https://' +
location.host + location.host +
location.pathname + location.pathname +
location.search + location.search +
location.hash, location.hash,
) )
return return
} }
@ -355,7 +355,7 @@ var Main = {
$.global(function () { $.global(function () {
document.documentElement.classList.add('js-enabled') document.documentElement.classList.add('js-enabled')
return (window.FCX = {}) return (window.FCX = {})
}) }, Object.assign({}, g.SITE))
Main.jsEnabled = $.hasClass(doc, 'js-enabled') Main.jsEnabled = $.hasClass(doc, 'js-enabled')
// XXX https://bugs.chromium.org/p/chromium/issues/detail?id=920638 // XXX https://bugs.chromium.org/p/chromium/issues/detail?id=920638
@ -639,7 +639,7 @@ var Main = {
try { try {
g.SITE.preParsingFixes?.(board) g.SITE.preParsingFixes?.(board)
} catch (error) {} } catch (error) { }
Main.addThreadsObserver = new MutationObserver(Main.addThreads) Main.addThreadsObserver = new MutationObserver(Main.addThreads)
Main.addPostsObserver = new MutationObserver(Main.addPosts) Main.addPostsObserver = new MutationObserver(Main.addPosts)
@ -943,9 +943,8 @@ var Main = {
} }
const div = $.el('div', { const div = $.el('div', {
innerHTML: `${errors.length} errors occurred.${ innerHTML: `${errors.length} errors occurred.${Main.reportLink(errors).innerHTML
Main.reportLink(errors).innerHTML } [<a href="javascript:;">show</a>]`,
} [<a href="javascript:;">show</a>]`,
}) })
$.on(div.lastElementChild, 'click', function () { $.on(div.lastElementChild, 'click', function () {
let ref let ref
@ -972,9 +971,8 @@ var Main = {
innerHTML: E(data.message) + (reportLink ? reportLink.innerHTML : ''), innerHTML: E(data.message) + (reportLink ? reportLink.innerHTML : ''),
}) })
const error = $.el('div', { const error = $.el('div', {
textContent: `${data.error.name || 'Error'}: ${ textContent: `${data.error.name || 'Error'}: ${data.error.message || 'see console for details'
data.error.message || 'see console for details' }`,
}`,
}) })
const lines = const lines =
data.error.stack data.error.stack
@ -999,7 +997,7 @@ var Main = {
if ( if (
encodeURIComponent(title + details + text + '\n').length <= encodeURIComponent(title + details + text + '\n').length <=
meta.newIssueMaxLength - meta.newIssueMaxLength -
meta.newIssue.replace(/%(title|details)/, '').length meta.newIssue.replace(/%(title|details)/, '').length
) { ) {
return (details += text + '\n') return (details += text + '\n')
} }
@ -1047,7 +1045,7 @@ User agent: ${navigator.userAgent}\
Main.thisPageIsLegit = g.SITE.isThisPageLegit Main.thisPageIsLegit = g.SITE.isThisPageLegit
? g.SITE.isThisPageLegit() ? g.SITE.isThisPageLegit()
: !/^[45]\d\d\b/.test(document.title) && : !/^[45]\d\d\b/.test(document.title) &&
!/\.(?:json|rss)$/.test(location.pathname) !/\.(?:json|rss)$/.test(location.pathname)
} }
return Main.thisPageIsLegit return Main.thisPageIsLegit
}, },
@ -1158,7 +1156,3 @@ User agent: ${navigator.userAgent}\
} }
export default Main export default Main
$.ready(() => Main.init()) $.ready(() => Main.init())
// <% if (readJSON('/.tests_enabled')) { %>
// Main.features.push(['Build Test', Test]);
// <% } %>

View File

@ -6,9 +6,10 @@ import CrossOrigin from "./CrossOrigin";
import { debounce, dict, MINUTE, platform, SECOND } from "./helpers"; import { debounce, dict, MINUTE, platform, SECOND } from "./helpers";
import { AjaxPageOptions, Dict, ElementProperties, SyncObject, WhenModifiedOptions } from "../types/$"; import { AjaxPageOptions, Dict, ElementProperties, SyncObject, WhenModifiedOptions } from "../types/$";
import Callbacks from "../classes/Callbacks"; import Callbacks from "../classes/Callbacks";
import SimpleDict from "../classes/SimpleDict";
// not chainable // not chainable
const $ = (selector, root = document.body) => root.querySelector(selector); const $ = (selector, root = document.body) => root.querySelector(selector);
$.id = id => d.getElementById(id); $.id = (id: string) => d.getElementById(id);
$.cache = dict(); $.cache = dict();
$.ajaxPage = function (url: string, options: AjaxPageOptions = {}) { $.ajaxPage = function (url: string, options: AjaxPageOptions = {}) {
if (options == null) { options = {}; } if (options == null) { options = {}; }
@ -67,8 +68,8 @@ $.getOwn = function (obj: Object, key: string) { if ($.hasOwn(obj, key)) { retur
$.ajax = (function () { $.ajax = (function () {
let pageXHR = XMLHttpRequest; let pageXHR = XMLHttpRequest;
if (window.wrappedJSObject && !XMLHttpRequest.wrappedJSObject) { if (unsafeWindow.wrappedJSObject && !XMLHttpRequest.wrappedJSObject) {
pageXHR = XPCNativeWrapper(window.wrappedJSObject.XMLHttpRequest); pageXHR = XPCNativeWrapper(unsafeWindow.wrappedJSObject.XMLHttpRequest);
} else { } else {
pageXHR = XMLHttpRequest; pageXHR = XMLHttpRequest;
} }
@ -80,8 +81,8 @@ $.ajax = (function () {
url = url.replace(/^((?:https?:)?\/\/(?:\w+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//, '$1//adv/'); url = url.replace(/^((?:https?:)?\/\/(?:\w+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//, '$1//adv/');
if (platform === 'crx') { if (platform === 'crx') {
// XXX https://bugs.chromium.org/p/chromium/issues/detail?id=920638 // 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) { if (Conf['Work around CORB Bug'] && g.SITE.software === 'yotsuba' && Date.now() - Conf['Work around CORB Bug'] < 2 * MINUTE) {
return $.ajaxPage(url, options); options.responseType = 'text';
} }
} }
const { onloadend, timeout, responseType, withCredentials, type, onprogress, form, headers } = options as AjaxPageOptions; const { onloadend, timeout, responseType, withCredentials, type, onprogress, form, headers } = options as AjaxPageOptions;
@ -129,7 +130,7 @@ $.ajax = (function () {
//@ts-ignore //@ts-ignore
window.FCX.requests = Object.create(null); window.FCX.requests = Object.create(null);
document.addEventListener('4chanXAjax', function (e) { document.addEventListener('4chanXAjax', function (e) {
let fd, r; let fd: FormData, r: XMLHttpRequest;
const { url, timeout, responseType, withCredentials, type, onprogress, form, headers, id } = e.detail; const { url, timeout, responseType, withCredentials, type, onprogress, form, headers, id } = e.detail;
//@ts-ignore //@ts-ignore
window.FCX.requests[id] = r = new pageXHR(); window.FCX.requests[id] = r = new pageXHR();
@ -180,7 +181,7 @@ $.ajax = (function () {
return r.abort(); return r.abort();
} }
, false); , false);
}, 0); }, Object.create(null));
$.on(d, '4chanXAjaxProgress', function (e: CustomEvent) { $.on(d, '4chanXAjaxProgress', function (e: CustomEvent) {
let req: any; let req: any;
@ -204,7 +205,7 @@ $.ajax = (function () {
}); });
}; };
return $.ajaxPage = function (url, options = {}) { return $.ajaxPage = function (url: string, options: any = {}) {
let req: any; let req: any;
let { onloadend, timeout, responseType, withCredentials, type, onprogress, form, headers } = options || {}; let { onloadend, timeout, responseType, withCredentials, type, onprogress, form, headers } = options || {};
const id = requestID++; const id = requestID++;
@ -221,7 +222,7 @@ $.ajax = (function () {
} }
} }
} }
$.event('4chanXAjax', { url, timeout, responseType, withCredentials, type, onprogress: !!onprogress, form, headers, id }); $.event('4chanXAjax', { url, timeout, responseType, withCredentials, type, onprogress, form, headers, id });
return req; return req;
}; };
} }
@ -298,6 +299,7 @@ $.whenModified = function (
req.callbacks = [cb]; req.callbacks = [cb];
return reqs[url] = req; return reqs[url] = req;
}; };
// very sensitive errors
return $.cleanCache = function (testf) { return $.cleanCache = function (testf) {
for (var url in reqs) { for (var url in reqs) {
if (testf(url)) { if (testf(url)) {
@ -358,19 +360,19 @@ $.addCSP = function (policy: string) {
return meta; return meta;
}; };
$.x = function (path, root) { $.x = function (path: string, root?: HTMLElement) {
if (!root) { root = d.body; } if (!root) { root = d.body; }
// XPathResult.ANY_UNORDERED_NODE_TYPE === 8 // XPathResult.ANY_UNORDERED_NODE_TYPE === 8
return d.evaluate(path, root, null, 8, null).singleNodeValue; return d.evaluate(path, root, null, 8, null).singleNodeValue;
}; };
$.X = function (path, root) { $.X = function (path: string, root?: HTMLElement) {
if (!root) { root = d.body; } if (!root) { root = d.body; }
// XPathResult.ORDERED_NODE_SNAPSHOT_TYPE === 7 // XPathResult.ORDERED_NODE_SNAPSHOT_TYPE === 7
return d.evaluate(path, root, null, 7, null); return d.evaluate(path, root, null, 7, null);
}; };
$.addClass = function (el, ...classNames) { $.addClass = function (el: HTMLElement, ...classNames: string[]) {
for (var className of classNames) { el.classList.add(className); } for (var className of classNames) { el.classList.add(className); }
}; };
@ -378,11 +380,11 @@ $.rmClass = function (el, ...classNames) {
for (var className of classNames) { el.classList.remove(className); } for (var className of classNames) { el.classList.remove(className); }
}; };
$.toggleClass = (el, className) => el.classList.toggle(className); $.toggleClass = (el: HTMLElement, className: string) => el.classList.toggle(className);
$.hasClass = (el, className) => el.classList.contains(className); $.hasClass = (el: HTMLElement, className: string) => el.classList.contains(className);
$.rm = el => el?.remove(); $.rm = (el: Element) => el?.remove();
$.rmAll = root => // https://gist.github.com/MayhemYDG/8646194 $.rmAll = root => // https://gist.github.com/MayhemYDG/8646194
root.textContent = null; root.textContent = null;
@ -527,7 +529,7 @@ $.debounce = function (wait, fn) {
}; };
}; };
//ok //ok
$.queueTask = function (fn) { $.queueTask = function (fn: VoidFunction) {
if (typeof requestIdleCallback === 'function') { if (typeof requestIdleCallback === 'function') {
return requestIdleCallback(fn); return requestIdleCallback(fn);
} else { } else {
@ -535,16 +537,14 @@ $.queueTask = function (fn) {
} }
}; };
$.global = function (fn: Function, data: object) { $.global = function (fn: Function, data?: object) {
if (doc) { if (doc) {
const script = $.el('script', const script = $.el('script', { textContent: `(${fn}).call(document.currentScript.dataset);` });
{ textContent: `(${fn}).call(document.currentScript.dataset);` });
if (data) { $.extend(script.dataset, data); } if (data) { $.extend(script.dataset, data); }
$.add((d.head || doc), script); $.add((d.head || doc), script);
$.rm(script); $.rm(script);
return script.dataset; return script.dataset;
} else { } else {
// XXX dwb
try { try {
fn.call(data); fn.call(data);
} catch (error) { } } catch (error) { }
@ -612,17 +612,19 @@ $.item = function (key, val) {
return item; return item;
}; };
$.oneItemSugar = fn => (function (key, val, cb) { $.oneItemSugar = (fn: Function) => (function (key: string[], val: any, cb?: Function) {
if (typeof key === 'string') { if (typeof key === 'object') {
return fn($.item(key, val), cb); for (const k in key) {
fn(k, key[k], val);
}
} else { } else {
return fn(key, val); fn(key, val, cb);
} }
}); });
$.syncing = dict(); $.syncing = dict();
$.securityCheck = function (data) { $.securityCheck = function (data: object) {
if (location.protocol !== 'https:') { if (location.protocol !== 'https:') {
return delete data['Redirect to HTTPS']; return delete data['Redirect to HTTPS'];
} }
@ -666,7 +668,7 @@ if (platform === 'crx') {
}; };
$.get = $.oneItemSugar(function (data, cb) { $.get = $.oneItemSugar(function (data: object, key: string, cb: Function) {
if (!$.crxWorking()) { return; } if (!$.crxWorking()) { return; }
const results = {}; const results = {};
const get = function (area) { const get = function (area) {
@ -676,7 +678,7 @@ if (platform === 'crx') {
keys = null; keys = null;
} }
return chrome.storage[area].get(keys, function (result) { return chrome.storage[area].get(keys, function (result) {
let key; let key: string;
result = dict.clone(result); result = dict.clone(result);
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
c.error(chrome.runtime.lastError.message); c.error(chrome.runtime.lastError.message);
@ -777,16 +779,14 @@ if (platform === 'crx') {
c.error(chrome.runtime.lastError.message); c.error(chrome.runtime.lastError.message);
} }
if (err == null) { err = chrome.runtime.lastError; } if (err == null) { err = chrome.runtime.lastError; }
if (!--count) { return cb?.(err); } if (!--count) { return cb?.(); }
}; };
chrome.storage.local.clear(done); chrome.storage.local.clear(done);
return chrome.storage.sync.clear(done); return chrome.storage.sync.clear(done);
}; };
})(); })();
} else { } else {
$.syncing = {};
// http://wiki.greasespot.net/Main_Page
// https://tampermonkey.net/documentation.php
if ((GM?.deleteValue != null) && window.BroadcastChannel && (typeof GM_addValueChangeListener === 'undefined' || GM_addValueChangeListener === null)) { if ((GM?.deleteValue != null) && window.BroadcastChannel && (typeof GM_addValueChangeListener === 'undefined' || GM_addValueChangeListener === null)) {
@ -804,9 +804,9 @@ if (platform === 'crx') {
return result; return result;
})()); })());
$.sync = (key, cb) => $.syncing[key] = cb; $.sync = (key: string, cb: () => void) => $.syncing[key] = cb;
$.forceSync = function () { }; $.forceSync = function (): void { };
$.delete = function (keys, cb) { $.delete = function (keys, cb) {
let key; let key;
@ -827,9 +827,9 @@ if (platform === 'crx') {
}); });
}; };
$.get = $.oneItemSugar(function (items, cb) { $.get = $.oneItemSugar(function <T>(items: SimpleDict<T>, cb: (items: SimpleDict<T>) => void) {
const keys = Object.keys(items); const keys: string[] = Object.keys(items);
return Promise.all(keys.map((key) => GM.getValue(g.NAMESPACE + key))).then(function (values) { return Promise.all(keys.map((key) => GM.getValue(g.NAMESPACE + key))).then(function (values: string[]) {
for (let i = 0; i < values.length; i++) { for (let i = 0; i < values.length; i++) {
var val = values[i]; var val = values[i];
if (val) { if (val) {
@ -840,10 +840,10 @@ if (platform === 'crx') {
}); });
}); });
$.set = $.oneItemSugar(function (items, cb) { $.set = $.oneItemSugar(function (items: Record<string, any>, cb?: () => void) {
$.securityCheck(items); $.securityCheck(items);
return Promise.all((() => { return Promise.all((() => {
const result = []; const result: Promise<void>[] = [];
for (var key in items) { for (var key in items) {
var val = items[key]; var val = items[key];
result.push(GM.setValue(g.NAMESPACE + key, JSON.stringify(val))); result.push(GM.setValue(g.NAMESPACE + key, JSON.stringify(val)));
@ -870,7 +870,6 @@ if (platform === 'crx') {
$.getValue = GM_getValue; $.getValue = GM_getValue;
$.listValues = () => GM_listValues(); // error when called if missing $.listValues = () => GM_listValues(); // error when called if missing
} else if ($.hasStorage) { } else if ($.hasStorage) {
$.getValue = key => localStorage.getItem(key);
$.listValues = () => (() => { $.listValues = () => (() => {
const result = []; const result = [];
for (var key in localStorage) { for (var key in localStorage) {
@ -881,23 +880,20 @@ if (platform === 'crx') {
return result; return result;
})(); })();
} else { } else {
$.getValue = function () { };
$.listValues = () => []; $.listValues = () => [];
} }
if (typeof GM_addValueChangeListener !== 'undefined' && GM_addValueChangeListener !== null) { if (typeof GM_addValueChangeListener !== 'undefined' && GM_addValueChangeListener !== null) {
$.setValue = GM_setValue;
$.deleteValue = GM_deleteValue;
} else if (typeof GM_deleteValue !== 'undefined' && GM_deleteValue !== null) { } else if (typeof GM_deleteValue !== 'undefined' && GM_deleteValue !== null) {
$.oldValue = dict(); $.oldValue = dict();
$.setValue = function (key, val) { $.setValue = function (key: string, val: string) {
GM_setValue(key, val); GM_setValue(key, val);
if (key in $.syncing) { if (key in $.syncing) {
$.oldValue[key] = val; $.oldValue[key] = val;
if ($.hasStorage) { return localStorage.setItem(key, val); } // for `storage` events if ($.hasStorage) { return localStorage.setItem(key, val); } // for `storage` events
} }
}; };
$.deleteValue = function (key) { $.deleteValue = function (key: string) {
GM_deleteValue(key); GM_deleteValue(key);
if (key in $.syncing) { if (key in $.syncing) {
delete $.oldValue[key]; delete $.oldValue[key];
@ -922,10 +918,14 @@ if (platform === 'crx') {
} }
if (typeof GM_addValueChangeListener !== 'undefined' && GM_addValueChangeListener !== null) { if (typeof GM_addValueChangeListener !== 'undefined' && GM_addValueChangeListener !== null) {
$.sync = function (key, cb) { $.sync = function (key: string, cb: (val: any, key: string) => void) {
key = g.NAMESPACE + key; key = g.NAMESPACE + key;
$.syncing[key] = cb; $.syncing[key] = cb;
// if GM_addValueChangeListener was removed
if (GM_addValueChangeListener == null) { return; }
return GM_addValueChangeListener(key, function (name, oldVal, newVal) { return GM_addValueChangeListener(key, function (name, oldVal, newVal) {
// if the callback was removed
if ($.syncing[key] == null) { return; }
if (newVal != null) { if (newVal != null) {
if (newVal === oldVal) { return; } if (newVal === oldVal) { return; }
return cb(dict.json(newVal), name); return cb(dict.json(newVal), name);
@ -937,7 +937,7 @@ if (platform === 'crx') {
}); }; }); };
$.forceSync = function () { }; $.forceSync = function () { };
} else if ((typeof GM_deleteValue !== 'undefined' && GM_deleteValue !== null) || $.hasStorage) { } else if ((typeof GM_deleteValue !== 'undefined' && GM_deleteValue !== null) || $.hasStorage) {
$.sync = function (key, cb) { $.sync = function (key: string, cb: (val: any, key: string) => void) {
key = g.NAMESPACE + key; key = g.NAMESPACE + key;
$.syncing[key] = cb; $.syncing[key] = cb;
return $.oldValue[key] = $.getValue(key); return $.oldValue[key] = $.getValue(key);
@ -945,7 +945,7 @@ if (platform === 'crx') {
(function () { (function () {
const onChange = function ({ key, newValue }) { const onChange = function ({ key, newValue }) {
let cb; let cb: (val: any, key: string) => void;
if (!(cb = $.syncing[key])) { return; } if (!(cb = $.syncing[key])) { return; }
if (newValue != null) { if (newValue != null) {
if (newValue === $.oldValue[key]) { return; } if (newValue === $.oldValue[key]) { return; }
@ -959,27 +959,20 @@ if (platform === 'crx') {
}; };
$.on(window, 'storage', onChange); $.on(window, 'storage', onChange);
return $.forceSync = function (key) { return $.forceSync = function () { return onChange({ key: g.NAMESPACE + 'forceSync', newValue: 1 }); };
// Storage events don't work across origins
// e.g. http://boards.4chan.org and https://boards.4chan.org
// so force a check for changes to avoid lost data.
key = g.NAMESPACE + key;
return onChange({ key, newValue: $.getValue(key) });
};
})(); })();
} else { } else {
$.sync = function () { };
$.forceSync = function () { }; $.forceSync = function () { };
} }
$.delete = function (keys, cb) { $.delete = function (keys: string[], cb?: any) {
if (keys.length === 0) { return cb?.(); } if (keys.length === 0) { return cb?.(); }
return Promise.all(keys.map(key => $.deleteValue(g.NAMESPACE + key))).then(cb); return Promise.all(keys.map(key => $.deleteValue(g.NAMESPACE + key))).then(cb);
}; };
$.get = $.oneItemSugar((items, cb) => $.queueTask(() => $.getSync(items, cb))); $.get = $.oneItemSugar((items: any, cb: Function) => $.queueTask(() => $.getSync(items, cb)));
$.getSync = function (items, cb) { $.getSync = function (items: any, cb: Function) {
for (var key in items) { for (var key in items) {
var val2; var val2;
if (val2 = $.getValue(g.NAMESPACE + key)) { if (val2 = $.getValue(g.NAMESPACE + key)) {
@ -995,7 +988,7 @@ if (platform === 'crx') {
return cb(items); return cb(items);
}; };
$.set = $.oneItemSugar(function (items, cb) { $.set = $.oneItemSugar(function (items: any, cb?: Function) {
$.securityCheck(items); $.securityCheck(items);
return $.queueTask(function () { return $.queueTask(function () {
for (var key in items) { for (var key in items) {
@ -1006,7 +999,7 @@ if (platform === 'crx') {
}); });
}); });
$.clear = function (cb) { $.clear = function (cb: Function) {
// XXX https://github.com/greasemonkey/greasemonkey/issues/2033 // XXX https://github.com/greasemonkey/greasemonkey/issues/2033
// Also support case where GM_listValues is not defined. // Also support case where GM_listValues is not defined.
$.delete(Object.keys(Conf), cb); $.delete(Object.keys(Conf), cb);