Types and improvements to $

This commit is contained in:
Lalle 2023-04-21 22:28:39 +02:00
parent 6b81346b46
commit 22895eed98
No known key found for this signature in database
GPG Key ID: A6583D207A8F6B0D
5 changed files with 194 additions and 134 deletions

View File

@ -1 +0,0 @@
Chromium 73.0.3683.75 built on Debian buster/sid, running on Debian buster/sid

View File

@ -121,7 +121,13 @@ var ImageExpand = {
) { ) {
return return
} }
return $.queueTask(func, post) return $.queueTask(function () {
if (file.isExpanded) {
return ImageExpand.contract(post)
} else {
return ImageExpand.expand(post)
}
})
} }
if ( if (

View File

@ -1275,7 +1275,7 @@ var QR = {
QR.cooldown.changes = dict(); QR.cooldown.changes = dict();
QR.cooldown.auto = false; QR.cooldown.auto = false;
QR.cooldown.update(); QR.cooldown.update();
return $.queueTask($.delete, 'cooldowns'); return $.queueTask($.delete('cooldowns', dict()));
}, },
update() { update() {

View File

@ -1,30 +1,29 @@
/// <reference path="../types/globals.d.ts" />
import Notice from "../classes/Notice"; import Notice from "../classes/Notice";
import { c, Conf, d, doc, g } from "../globals/globals"; import { c, Conf, d, doc, g } from "../globals/globals";
import CrossOrigin from "./CrossOrigin"; import CrossOrigin from "./CrossOrigin";
import { debounce, dict, MINUTE, platform, SECOND } from "./helpers"; import { debounce, dict, MINUTE, platform, SECOND } from "./helpers";
import { AjaxPageOptions, ElementProperties } from "../types/$"; import { AjaxPageOptions, Dict, ElementProperties, SyncObject, WhenModifiedOptions } from "../types/$";
import Callbacks from "../classes/Callbacks";
// not chainable // not chainable
const $ = (selector, root = document.body) => root.querySelector(selector); const $ = (selector, root = document.body) => root.querySelector(selector);
type AjaxPageRequest = XMLHttpRequest & {
abort: () => void;
}
$.id = id => d.getElementById(id); $.id = id => d.getElementById(id);
$.cache = dict(); $.cache = dict();
$.ajaxPage = function (url, options) { $.ajaxPage = function (url: string, options: AjaxPageOptions = {}) {
if (options == null) { options = {}; } if (options == null) { options = {}; }
const { onloadend, timeout, responseType, withCredentials, type, onprogress, form, headers } = options; const { onloadend, timeout, responseType, withCredentials, type, onprogress, form, headers } = options;
const r = new XMLHttpRequest(); const r = new XMLHttpRequest();
const id = ++Request; const id = Date.now() + Math.random();
const e = new CustomEvent('4chanXAjax', { detail: { url, timeout, responseType, withCredentials, type, onprogress, form, headers, id } }); const e = new CustomEvent('4chanXAjax', { detail: { url, timeout, responseType, withCredentials, type, onprogress, form, headers, id } });
d.dispatchEvent(e); d.dispatchEvent(e);
r.onloadend = function () { r.onloadend = function () {
delete window.FCX.requests[id]; if (onloadend) { onloadend.call(r, r); }
return onloadend.apply(this, arguments); return d.dispatchEvent(new CustomEvent('4chanXAjaxEnd', { detail: { id } }));
}; };
return r; return r;
} }
$.ready = function (fc) { $.ready = function (fc: () => void) {
if (d.readyState !== 'loading') { if (d.readyState !== 'loading') {
$.queueTask(fc); $.queueTask(fc);
return; return;
@ -36,7 +35,7 @@ $.ready = function (fc) {
return $.on(d, 'DOMContentLoaded', cb); return $.on(d, 'DOMContentLoaded', cb);
}; };
$.formData = function (form) { $.formData = function (form: FormData | ElementProperties) {
if (form instanceof HTMLFormElement) { if (form instanceof HTMLFormElement) {
return new FormData(form); return new FormData(form);
} }
@ -54,28 +53,27 @@ $.formData = function (form) {
return fd; return fd;
}; };
$.extend = function (object, properties) { $.extend = function (object: Object, properties: Object) {
for (var key in properties) { for (var key in properties) {
var val = properties[key]; var value = properties[key];
object[key] = val; object[key] = value;
} }
return object;
}; };
$.hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key); $.hasOwn = function (obj: Object, key: string) { return Object.prototype.hasOwnProperty.call(obj, key); };
$.getOwn = function (obj, key) { $.getOwn = function (obj: Object, key: string) { if ($.hasOwn(obj, key)) { return obj[key]; } };
if (Object.prototype.hasOwnProperty.call(obj, key)) { return obj[key]; } else { return undefined; }
};
$.ajax = (function () { $.ajax = (function () {
let pageXHR; let pageXHR = XMLHttpRequest;
if (window.wrappedJSObject && !XMLHttpRequest.wrappedJSObject) { if (window.wrappedJSObject && !XMLHttpRequest.wrappedJSObject) {
pageXHR = XPCNativeWrapper(window.wrappedJSObject.XMLHttpRequest); pageXHR = XPCNativeWrapper(window.wrappedJSObject.XMLHttpRequest);
} else { } else {
pageXHR = XMLHttpRequest; pageXHR = XMLHttpRequest;
} }
const r = (function (url, options = {}) { const r = (function (url, options: AjaxPageOptions = {}) {
if (options.responseType == null) { options.responseType = 'json'; } if (options.responseType == null) { options.responseType = 'json'; }
if (!options.type) { options.type = (options.form && 'post') || 'get'; } if (!options.type) { options.type = (options.form && 'post') || 'get'; }
// XXX https://forums.lanik.us/viewtopic.php?f=64&t=24173&p=78310 // XXX https://forums.lanik.us/viewtopic.php?f=64&t=24173&p=78310
@ -86,8 +84,8 @@ $.ajax = (function () {
return $.ajaxPage(url, options); return $.ajaxPage(url, options);
} }
} }
const { onloadend, timeout, responseType, withCredentials, type, onprogress, form, headers } = options; const { onloadend, timeout, responseType, withCredentials, type, onprogress, form, headers } = options as AjaxPageOptions;
const r = new pageXHR(); const r = new pageXHR() as XMLHttpRequest;
try { try {
r.open(type, url, true); r.open(type, url, true);
const object = headers || {}; const object = headers || {};
@ -103,7 +101,8 @@ $.ajax = (function () {
// https://bugs.chromium.org/p/chromium/issues/detail?id=920638 // https://bugs.chromium.org/p/chromium/issues/detail?id=920638
$.on(r, 'load', () => { $.on(r, 'load', () => {
if (!Conf['Work around CORB Bug'] && r.readyState === 4 && r.status === 200 && r.statusText === '' && r.response === null) { if (!Conf['Work around CORB Bug'] && r.readyState === 4 && r.status === 200 && r.statusText === '' && r.response === null) {
$.set('Work around CORB Bug', (Conf['Work around CORB Bug'] = Date.now())); $.set('Work around CORB Bug', (Conf['Work around CORB Bug'] = Date.now()), cb => cb());
return c.warn(`4chan X failed to load: ${url}`);
} }
}); });
} }
@ -112,8 +111,8 @@ $.ajax = (function () {
// XXX Some content blockers in Firefox (e.g. Adblock Plus and NoScript) throw an exception instead of simulating a connection error. // XXX Some content blockers in Firefox (e.g. Adblock Plus and NoScript) throw an exception instead of simulating a connection error.
if (err.result !== 0x805e0006) { throw err; } if (err.result !== 0x805e0006) { throw err; }
r.onloadend = onloadend; r.onloadend = onloadend;
$.queueTask($.event, 'error', null, r); $.queueTask($.event);
$.queueTask($.event, 'loadend', null, r); $.queueTask($.event);
} }
return r; return r;
}); });
@ -127,12 +126,13 @@ $.ajax = (function () {
$.ajaxPageInit = function () { $.ajaxPageInit = function () {
$.global(function () { $.global(function () {
//@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, r;
const { url, timeout, responseType, withCredentials, type, onprogress, form, headers, id } = e.detail; const { url, timeout, responseType, withCredentials, type, onprogress, form, headers, id } = e.detail;
window.FCX.requests[id] = (r = new XMLHttpRequest()); //@ts-ignore
window.FCX.requests[id] = r = new pageXHR();
r.open(type, url, true); r.open(type, url, true);
const object = headers || {}; const object = headers || {};
for (var key in object) { for (var key in object) {
@ -150,6 +150,7 @@ $.ajax = (function () {
}; };
} }
r.onloadend = function () { r.onloadend = function () {
//@ts-ignore
delete window.FCX.requests[id]; delete window.FCX.requests[id];
const { status, statusText, response } = this; const { status, statusText, response } = this;
const responseHeaderString = this.getAllResponseHeaders(); const responseHeaderString = this.getAllResponseHeaders();
@ -174,20 +175,21 @@ $.ajax = (function () {
return document.addEventListener('4chanXAjaxAbort', function (e) { return document.addEventListener('4chanXAjaxAbort', function (e) {
let r; let r;
//@ts-ignore
if (!(r = window.FCX.requests[e.detail.id])) { return; } if (!(r = window.FCX.requests[e.detail.id])) { return; }
return r.abort(); return r.abort();
} }
, false); , false);
}); }, 0);
$.on(d, '4chanXAjaxProgress', function (e) { $.on(d, '4chanXAjaxProgress', function (e: CustomEvent) {
let req; let req: any;
if (!(req = requests[e.detail.id])) { return; } if (!(req = requests[e.detail.id])) { return; }
return req.upload.onprogress.call(req.upload, e.detail); return req.onprogress(e);
}); });
return $.on(d, '4chanXAjaxLoadend', function (e) { return $.on(d, '4chanXAjaxLoadend', function (e: CustomEvent) {
let req; let req: any;
if (!(req = requests[e.detail.id])) { return; } if (!(req = requests[e.detail.id])) { return; }
delete requests[e.detail.id]; delete requests[e.detail.id];
if (e.detail.status) { if (e.detail.status) {
@ -203,14 +205,22 @@ $.ajax = (function () {
}; };
return $.ajaxPage = function (url, options = {}) { return $.ajaxPage = function (url, options = {}) {
let req; 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++;
requests[id] = (req = new CrossOrigin.Request()); requests[id] = (req = new CrossOrigin.Request());
$.extend(req, { responseType, onloadend }); $.extend(req, { responseType, onloadend });
req.upload = { onprogress }; req.upload = { onprogress };
req.abort = () => $.event('4chanXAjaxAbort', { id }); req.abort = () => $.event('4chanXAjaxAbort', { id });
if (form) { form = Array.from(form.entries()); } if (form) {
form = new FormData(form);
for (var entry of form) {
if (entry[0] === 'json') {
form.delete(entry[0]);
form.append(entry[0], JSON.stringify(entry[1]));
}
}
}
$.event('4chanXAjax', { url, timeout, responseType, withCredentials, type, onprogress: !!onprogress, form, headers, id }); $.event('4chanXAjax', { url, timeout, responseType, withCredentials, type, onprogress: !!onprogress, form, headers, id });
return req; return req;
}; };
@ -221,26 +231,32 @@ $.ajax = (function () {
// With the `If-Modified-Since` header we only receive the HTTP headers and no body for 304 responses. // With the `If-Modified-Since` header we only receive the HTTP headers and no body for 304 responses.
// This saves a lot of bandwidth and CPU time for both the users and the servers. // This saves a lot of bandwidth and CPU time for both the users and the servers.
$.lastModified = dict(); $.lastModified = dict();
$.whenModified = function (url, bucket, cb, options = {}) { $.whenModified = function (
url: string,
bucket: string,
cb: (this: JQueryXHR) => void,
options: WhenModifiedOptions = {}
): JQueryXHR {
const { timeout, ajax = $.ajax } = options; const { timeout, ajax = $.ajax } = options;
let params = []; const params: string[] = [];
let lastModifiedTime; const originalUrl = url;
if ($.engine === 'blink') { if ($.engine === "blink") {
params.push(`s=${bucket}`); params.push(`s=${bucket}`);
} }
if (url.split('/')[2] === 'a.4cdn.org') { if (url.split("/")[2] === "a.4cdn.org") {
params.push(`t=${Date.now()}`); params.push(`t=${Date.now()}`);
} }
const originalUrl = url;
if (params.length) { if (params.length) {
url += '?' + params.join('&'); url += "?" + params.join("&");
} }
const headers = {}; const headers: { [key: string]: string } = {};
if ((lastModifiedTime = $.lastModified[bucket]?.[originalUrl]) != null) { const lastModifiedTime = $.lastModified[bucket]?.[originalUrl];
headers['If-Modified-Since'] = lastModifiedTime;
if (lastModifiedTime != null) {
headers["If-Modified-Since"] = lastModifiedTime;
} }
return ajax(url, { return ajax(url, {
@ -251,15 +267,15 @@ $.whenModified = function (url, bucket, cb, options = {}) {
cb.call(this); cb.call(this);
}, },
timeout, timeout,
headers headers,
}); });
}; };
(function () { (function () {
const reqs = dict(); const reqs = dict();
$.cache = function (url, cb, options = {}) { $.cache = function (url, cb, options: { ajax?: typeof $.ajax } = {}) {
let req; let req: any;
const { ajax } = options; const { ajax } = options;
if (req = reqs[url]) { if (req = reqs[url]) {
if (req.callbacks) { if (req.callbacks) {
@ -294,49 +310,49 @@ $.whenModified = function (url, bucket, cb, options = {}) {
$.cb = { $.cb = {
checked() { checked() {
if ($.hasOwn(Conf, this.name)) { if ($.hasOwn(Conf, this.name)) {
$.set(this.name, this.checked); $.set(this.name, this.checked, true);
return Conf[this.name] = this.checked; return Conf[this.name] = this.checked;
} }
}, },
value() { value() {
if ($.hasOwn(Conf, this.name)) { if ($.hasOwn(Conf, this.name)) {
$.set(this.name, this.value.trim()); $.set(this.name, this.value.trim(), cb => {
if (cb) {
return this.value = cb;
}
});
}
return Conf[this.name] = this.value; return Conf[this.name] = this.value;
} }
} },
};
$.asap = function (test, cb) { $.asap = function (test: () => boolean, cb: () => void) {
if (test()) { if (test()) {
return cb(); return cb();
} else {
return setTimeout($.asap, 25, test, cb);
} }
return setTimeout(() => $.asap(test, cb), 0);
}; };
$.onExists = function (root, selector, cb) { $.onExists = function (root: HTMLElement, selector: string, cb: (el: HTMLElement) => void): MutationObserver {
let el; const observer = new MutationObserver(() => {
if (el = $(selector, root)) { const el = root.querySelector(selector);
return cb(el); if (el) {
}
var observer = new MutationObserver(function () {
if (el = $(selector, root)) {
observer.disconnect(); observer.disconnect();
return cb(el); return cb(root.querySelector(selector));
} }
}); });
return observer.observe(root, { childList: true, subtree: true }); observer.observe(root, { childList: true, subtree: true });
return observer;
}; };
$.addStyle = function (css, id, test = 'head') { $.addStyle = function (css: string, id: string, test = 'head') {
const style = $.el('style', if (id && d.getElementById(id)) { return; }
{ textContent: css }); const style = $.el('style', { id, textContent: css });
if (id != null) { style.id = id; }
$.onExists(doc, test, () => $.add(d.head, style)); $.onExists(doc, test, () => $.add(d.head, style));
return style; return style;
}; };
$.addCSP = function (policy) { $.addCSP = function (policy: string) {
const meta = $.el('meta', { httpEquiv: 'Content-Security-Policy', content: policy }, { display: 'none' }); const meta = $.el('meta', { httpEquiv: 'Content-Security-Policy', content: policy }, { display: 'none' });
$.onExists(doc, 'head', () => $.add(d.head, meta)); $.onExists(doc, 'head', () => $.add(d.head, meta));
return meta; return meta;
@ -440,8 +456,24 @@ if (platform === 'userscript') {
return new CustomEvent('x', { detail: {} }); return new CustomEvent('x', { detail: {} });
} catch (err) { } catch (err) {
const unsafeConstructors = { const unsafeConstructors = {
Object: unsafeWindow.Object, 'Object': Object,
Array: unsafeWindow.Array 'Array': Array,
'String': String,
'Number': Number,
'Boolean': Boolean,
'RegExp': RegExp,
'Date': Date,
'Error': Error,
'EvalError': EvalError,
'RangeError': RangeError,
'ReferenceError': ReferenceError,
'SyntaxError': SyntaxError,
'TypeError': TypeError,
'URIError': URIError,
'Map': Map,
'Set': Set,
'WeakMap': WeakMap,
'WeakSet': WeakSet,
}; };
var clone = function (obj) { var clone = function (obj) {
let constructor; let constructor;
@ -494,32 +526,16 @@ $.debounce = function (wait, fn) {
return timeout = setTimeout(exec, wait); return timeout = setTimeout(exec, wait);
}; };
}; };
//ok
$.queueTask = (function () { $.queueTask = function (fn) {
// inspired by https://www.w3.org/Bugs/Public/show_bug.cgi?id=15007 if (typeof requestIdleCallback === 'function') {
const taskQueue = []; return requestIdleCallback(fn);
const execTask = function () { } else {
const task = taskQueue.shift(); return setTimeout(fn, 0);
const func = task[0];
const args = Array.prototype.slice.call(task, 1);
return func.apply(func, args);
};
if (window.MessageChannel) {
const taskChannel = new MessageChannel();
taskChannel.port1.onmessage = execTask;
return function () {
taskQueue.push(arguments);
return taskChannel.port2.postMessage(null);
};
} else { // XXX Firefox
return function () {
taskQueue.push(arguments);
return setTimeout(execTask, 0);
};
} }
})(); };
$.global = function (fn, data) { $.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);` });
@ -536,7 +552,7 @@ $.global = function (fn, data) {
} }
}; };
$.bytesToString = function (size) { $.bytesToString = function (size: number) {
if (size < 1024) { if (size < 1024) {
return `${size} B`; return `${size} B`;
} else if (size < 1048576) { } else if (size < 1048576) {
@ -548,7 +564,7 @@ $.bytesToString = function (size) {
} }
}; };
$.minmax = (value, min, max) => Math.max(min, Math.min(max, value)); $.minmax = (value: number, min: number, max: number): number => Math.max(min, Math.min(max, value));
$.hasAudio = function (el: HTMLVideoElement | HTMLAudioElement) { $.hasAudio = function (el: HTMLVideoElement | HTMLAudioElement) {
if (el.tagName === 'VIDEO') { if (el.tagName === 'VIDEO') {
@ -565,13 +581,13 @@ $.luma = (rgb: number[]) => { // rgb: [r, g, b]
return 0.2126 * r + 0.7152 * g + 0.0722 * b; return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}; };
$.unescape = function (text) { $.unescape = function (text: string): string {
if (text == null) { return text; } if (text == null) { return text; }
return text.replace(/<[^>]*>/g, '').replace(/&(amp|#039|quot|lt|gt|#44);/g, c => ({ '&amp;': '&', '&#039;': "'", '&quot;': '"', '&lt;': '<', '&gt;': '>', '&#44;': ',' })[c]); return text.replace(/<[^>]*>/g, '').replace(/&(amp|#039|quot|lt|gt|#44);/g, c => ({ '&amp;': '&', '&#039;': "'", '&quot;': '"', '&lt;': '<', '&gt;': '>', '&#44;': ',' })[c]);
}; };
$.isImage = url => /\.(jpe?g|png|gif|bmp|webp|svg|ico|tiff?)$/i.test(url); $.isImage = (url: string) => /\.(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); $.isVideo = (url: string) => /\.(webm|mp4|og[gv]|m4v|mov|avi|flv|wmv|mpg|mpeg|mkv|rm|rmvb|3gp|3g2|asf|swf|vob)$/i.test(url);
$.engine = (function () { $.engine = (function () {
if (/Edge\//.test(navigator.userAgent)) { return 'edge'; } if (/Edge\//.test(navigator.userAgent)) { return 'edge'; }
@ -582,9 +598,9 @@ $.engine = (function () {
$.hasStorage = (function () { $.hasStorage = (function () {
try { try {
if (localStorage.getItem(g.NAMESPACE + 'hasStorage') === 'true') { return true; } localStorage.setItem('test', 'test');
localStorage.setItem(g.NAMESPACE + 'hasStorage', 'true'); localStorage.removeItem('test');
return localStorage.getItem(g.NAMESPACE + 'hasStorage') === 'true'; return true;
} catch (error) { } catch (error) {
return false; return false;
} }
@ -630,8 +646,8 @@ if (platform === 'crx') {
} }
} }
}); });
$.sync = (key, cb) => $.syncing[key] = cb; $.sync = (key: string, cb: () => void) => $.syncing[key] = cb;
$.forceSync = function () { }; $.forceSync = function (): void { };
$.crxWorking = function () { $.crxWorking = function () {
try { try {
@ -649,6 +665,7 @@ if (platform === 'crx') {
return false; return false;
}; };
$.get = $.oneItemSugar(function (data, cb) { $.get = $.oneItemSugar(function (data, cb) {
if (!$.crxWorking()) { return; } if (!$.crxWorking()) { return; }
const results = {}; const results = {};
@ -674,9 +691,8 @@ if (platform === 'crx') {
} }
results[area] = result; results[area] = result;
if (results.local && results.sync) { if (results.local && results.sync) {
$.extend(data, results.sync); for (key in results.local) { var val = results.local[key]; if (val != null) { results.sync[key] = val; } }
$.extend(data, results.local); cb(results.sync);
return cb(data);
} }
}); });
}; };
@ -741,7 +757,7 @@ if (platform === 'crx') {
}); });
}; };
var setSync = debounce(SECOND, () => setArea('sync')); var setSync = debounce(SECOND, () => setArea('sync', () => $.forceSync()));
$.set = $.oneItemSugar(function (data, cb) { $.set = $.oneItemSugar(function (data, cb) {
if (!$.crxWorking()) { return; } if (!$.crxWorking()) { return; }
@ -839,7 +855,11 @@ if (platform === 'crx') {
}); });
}); });
$.clear = cb => GM.listValues().then(keys => $.delete(keys.map(key => key.replace(g.NAMESPACE, '')), cb)).catch(() => $.delete(Object.keys(Conf).concat(['previousversion', 'QR Size', 'QR.persona']), cb)); $.clear = async function (cb: () => void) {
return GM.listValues().then(function (keys) {
return $.delete(keys.map(key => key.slice(g.NAMESPACE.length)), cb);
});
};
} else { } else {
if (typeof GM_deleteValue === 'undefined' || GM_deleteValue === null) { if (typeof GM_deleteValue === 'undefined' || GM_deleteValue === null) {
@ -902,12 +922,19 @@ if (platform === 'crx') {
} }
if (typeof GM_addValueChangeListener !== 'undefined' && GM_addValueChangeListener !== null) { if (typeof GM_addValueChangeListener !== 'undefined' && GM_addValueChangeListener !== null) {
$.sync = (key, cb) => $.syncing[key] = GM_addValueChangeListener(g.NAMESPACE + key, function (key2, oldValue, newValue, remote) { $.sync = function (key, cb) {
if (remote) { key = g.NAMESPACE + key;
if (newValue !== undefined) { newValue = dict.json(newValue); } $.syncing[key] = cb;
return cb(newValue, key); return GM_addValueChangeListener(key, function (name, oldVal, newVal) {
} if (newVal != null) {
}); if (newVal === oldVal) { return; }
return cb(dict.json(newVal), name);
}
});
};
$.forceSync = function () { return GM.getValue(g.NAMESPACE + 'forceSync', 0).then(function (val) {
return GM.setValue(g.NAMESPACE + 'forceSync', val + 1);
}); };
$.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, cb) {
@ -945,16 +972,12 @@ if (platform === 'crx') {
$.forceSync = function () { }; $.forceSync = function () { };
} }
$.delete = function (keys) { $.delete = function (keys, cb) {
if (!(keys instanceof Array)) { if (keys.length === 0) { return cb?.(); }
keys = [keys]; return Promise.all(keys.map(key => $.deleteValue(g.NAMESPACE + key))).then(cb);
}
for (var key of keys) {
$.deleteValue(g.NAMESPACE + key);
}
}; };
$.get = $.oneItemSugar((items, cb) => $.queueTask($.getSync, items, cb)); $.get = $.oneItemSugar((items, cb) => $.queueTask(() => $.getSync(items, cb)));
$.getSync = function (items, cb) { $.getSync = function (items, cb) {
for (var key in items) { for (var key in items) {
@ -963,7 +986,6 @@ if (platform === 'crx') {
try { try {
items[key] = dict.json(val2); items[key] = dict.json(val2);
} catch (err) { } catch (err) {
// XXX https://github.com/ccd0/4chan-x/issues/2218
if (!/^(?:undefined)*$/.test(val2)) { if (!/^(?:undefined)*$/.test(val2)) {
throw err; throw err;
} }
@ -987,14 +1009,15 @@ if (platform === 'crx') {
$.clear = function (cb) { $.clear = function (cb) {
// 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)); $.delete(Object.keys(Conf), cb);
$.delete(['previousversion', 'QR Size', 'QR.persona']); $.delete(Object.keys(Conf), cb);
try { try {
$.delete($.listValues().map(key => key.replace(g.NAMESPACE, ''))); //delete(keys, cb)
$.delete($.listValues(), cb);
} catch (error) { } } catch (error) { }
return cb?.(); return cb?.();
}; };
} }
} }
export default $; export default $;

34
src/types/$.d.ts vendored
View File

@ -1,3 +1,5 @@
import SimpleDict from "../classes/SimpleDict";
export interface ElementProperties { export interface ElementProperties {
[key: string]: any; [key: string]: any;
} }
@ -10,4 +12,34 @@ interface AjaxPageOptions {
onprogress?: (this: XMLHttpRequest, ev: ProgressEvent<EventTarget>) => void; onprogress?: (this: XMLHttpRequest, ev: ProgressEvent<EventTarget>) => void;
form?: FormData; form?: FormData;
headers?: Record<string, string>; headers?: Record<string, string>;
} }
export interface LastModified {
[bucket: string]: { [url: string]: string | undefined };
}
export interface WhenModifiedOptions {
timeout?: number;
ajax?: (url: string, settings?: AjaxPageOptions) => Promise<string>;
}
declare global {
interface JQueryStatic {
engine?: string;
lastModified: LastModified;
whenModified: (
url: string,
bucket: string,
cb: (this: JQueryXHR) => void,
options?: WhenModifiedOptions
) => JQueryXHR;
}
}
export type Dict = { [key: string]: any };
export interface SyncObject {
setValue: (key: string, val: any) => void;
deleteValue: (key: string) => void;
oldValue?: Dict;
syncing?: Dict;
hasStorage?: boolean;
cantSync?: boolean;
cantSet?: boolean;
}