mirror of
https://github.com/LalleSX/4chan-XZ.git
synced 2026-01-30 09:48:12 +01:00
formats and more of dollar
This commit is contained in:
parent
e23dd2366a
commit
f79199366e
@ -18,19 +18,19 @@ const $ = (selector: string, root: HTMLElement = doc) => root.querySelector(sele
|
||||
|
||||
$.id = id => d.getElementById(id);
|
||||
|
||||
$.ready = function(fc) {
|
||||
$.ready = function (fc) {
|
||||
if (d.readyState !== 'loading') {
|
||||
$.queueTask(fc);
|
||||
return;
|
||||
}
|
||||
var cb = function() {
|
||||
var cb = function () {
|
||||
$.off(d, 'DOMContentLoaded', cb);
|
||||
return fc();
|
||||
};
|
||||
return $.on(d, 'DOMContentLoaded', cb);
|
||||
};
|
||||
|
||||
$.formData = function(form) {
|
||||
$.formData = function (form) {
|
||||
if (form instanceof HTMLFormElement) {
|
||||
return new FormData(form);
|
||||
}
|
||||
@ -48,7 +48,7 @@ $.formData = function(form) {
|
||||
return fd;
|
||||
};
|
||||
|
||||
$.extend = function(object, properties) {
|
||||
$.extend = function (object, properties) {
|
||||
for (var key in properties) {
|
||||
var val = properties[key];
|
||||
object[key] = val;
|
||||
@ -57,11 +57,11 @@ $.extend = function(object, properties) {
|
||||
|
||||
$.hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
||||
|
||||
$.getOwn = function(obj, key) {
|
||||
$.getOwn = function (obj, key) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) { return obj[key]; } else { return undefined; }
|
||||
};
|
||||
|
||||
$.ajax = (function() {
|
||||
$.ajax = (function () {
|
||||
let pageXHR;
|
||||
if (window.wrappedJSObject && !XMLHttpRequest.wrappedJSObject) {
|
||||
pageXHR = XPCNativeWrapper(window.wrappedJSObject.XMLHttpRequest);
|
||||
@ -69,7 +69,7 @@ $.ajax = (function() {
|
||||
pageXHR = XMLHttpRequest;
|
||||
}
|
||||
|
||||
const r = (function (url, options={}) {
|
||||
const r = (function (url, options = {}) {
|
||||
if (options.responseType == null) { options.responseType = 'json'; }
|
||||
if (!options.type) { options.type = (options.form && 'post') || 'get'; }
|
||||
// XXX https://forums.lanik.us/viewtopic.php?f=64&t=24173&p=78310
|
||||
@ -80,7 +80,7 @@ $.ajax = (function() {
|
||||
return $.ajaxPage(url, options);
|
||||
}
|
||||
}
|
||||
const {onloadend, timeout, responseType, withCredentials, type, onprogress, form, headers} = options;
|
||||
const { onloadend, timeout, responseType, withCredentials, type, onprogress, form, headers } = options;
|
||||
const r = new pageXHR();
|
||||
try {
|
||||
r.open(type, url, true);
|
||||
@ -89,15 +89,15 @@ $.ajax = (function() {
|
||||
var value = object[key];
|
||||
r.setRequestHeader(key, value);
|
||||
}
|
||||
$.extend(r, {onloadend, timeout, responseType, withCredentials});
|
||||
$.extend(r.upload, {onprogress});
|
||||
$.extend(r, { onloadend, timeout, responseType, withCredentials });
|
||||
$.extend(r.upload, { onprogress });
|
||||
// connection error or content blocker
|
||||
$.on(r, 'error', function() { if (!r.status) { return c.warn(`4chan X failed to load: ${url}`); } });
|
||||
$.on(r, 'error', function () { if (!r.status) { return c.warn(`4chan X failed to load: ${url}`); } });
|
||||
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) {
|
||||
$.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());
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -106,8 +106,7 @@ $.ajax = (function() {
|
||||
// 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; }
|
||||
r.onloadend = onloadend;
|
||||
$.queueTask($.event, 'error', null, r);
|
||||
$.queueTask($.event, 'loadend', null, r);
|
||||
r.onerror();
|
||||
}
|
||||
return r;
|
||||
});
|
||||
@ -115,17 +114,16 @@ $.ajax = (function() {
|
||||
if (platform === 'userscript') {
|
||||
return r;
|
||||
} else {
|
||||
// # XXX https://bugs.chromium.org/p/chromium/issues/detail?id=920638
|
||||
let requestID = 0;
|
||||
let requestID: number
|
||||
const requests = dict();
|
||||
|
||||
$.ajaxPageInit = function() {
|
||||
$.global(function() {
|
||||
$.ajaxPageInit = function () {
|
||||
$.global(function () {
|
||||
window.FCX.requests = Object.create(null);
|
||||
|
||||
document.addEventListener('4chanXAjax', function(e) {
|
||||
let fd, r;
|
||||
const {url, timeout, responseType, withCredentials, type, onprogress, form, headers, id} = e.detail;
|
||||
document.addEventListener('4chanXAjax', function (e) {
|
||||
let fd: FormData, r: XMLHttpRequest;
|
||||
const { url, timeout, responseType, withCredentials, type, onprogress, form, headers, id } = e.detail;
|
||||
window.FCX.requests[id] = (r = new XMLHttpRequest());
|
||||
r.open(type, url, true);
|
||||
const object = headers || {};
|
||||
@ -137,51 +135,42 @@ $.ajax = (function() {
|
||||
r.timeout = timeout;
|
||||
r.withCredentials = withCredentials;
|
||||
if (onprogress) {
|
||||
r.upload.onprogress = function(e) {
|
||||
const {loaded, total} = e;
|
||||
const detail = {loaded, total, id};
|
||||
return document.dispatchEvent(new CustomEvent('4chanXAjaxProgress', {bubbles: true, detail}));
|
||||
r.upload.onprogress = function (e: ProgressEvent) {
|
||||
const { loaded, total } = e;
|
||||
const detail = { loaded, total, id };
|
||||
return document.dispatchEvent(new CustomEvent('4chanXAjaxProgress', { bubbles: true, detail }));
|
||||
};
|
||||
}
|
||||
r.onloadend = function() {
|
||||
r.onloadend = function () {
|
||||
delete window.FCX.requests[id];
|
||||
const {status, statusText, response} = this;
|
||||
const { status, statusText, response } = this;
|
||||
const responseHeaderString = this.getAllResponseHeaders();
|
||||
const detail = {status, statusText, response, responseHeaderString, id};
|
||||
return document.dispatchEvent(new CustomEvent('4chanXAjaxLoadend', {bubbles: true, detail}));
|
||||
const detail = { status, statusText, response, responseHeaderString, id };
|
||||
return document.dispatchEvent(new CustomEvent('4chanXAjaxLoadend', { bubbles: true, detail }));
|
||||
};
|
||||
// connection error or content blocker
|
||||
r.onerror = function() {
|
||||
r.onerror = function () {
|
||||
if (!r.status) { return console.warn(`4chan X failed to load: ${url}`); }
|
||||
};
|
||||
if (form) {
|
||||
fd = new FormData();
|
||||
for (var entry of form) {
|
||||
fd.append(entry[0], entry[1]);
|
||||
for (var [key, value] of form.entries()) {
|
||||
fd.append(key, value);
|
||||
}
|
||||
} else {
|
||||
fd = null;
|
||||
}
|
||||
return r.send(fd);
|
||||
}
|
||||
, false);
|
||||
});
|
||||
}, 'ajaxPageInit');
|
||||
};
|
||||
|
||||
return document.addEventListener('4chanXAjaxAbort', function(e) {
|
||||
let r;
|
||||
if (!(r = window.FCX.requests[e.detail.id])) { return; }
|
||||
return r.abort();
|
||||
}
|
||||
, false);
|
||||
});
|
||||
|
||||
$.on(d, '4chanXAjaxProgress', function(e) {
|
||||
$.on(d, '4chanXAjaxProgress', function (e: CustomEvent) {
|
||||
let req;
|
||||
if (!(req = requests[e.detail.id])) { return; }
|
||||
return req.upload.onprogress.call(req.upload, e.detail);
|
||||
});
|
||||
|
||||
return $.on(d, '4chanXAjaxLoadend', function(e) {
|
||||
let req;
|
||||
return $.on(d, '4chanXAjaxLoadend', function (e: CustomEvent) {
|
||||
let req: XMLHttpRequest;
|
||||
if (!(req = requests[e.detail.id])) { return; }
|
||||
delete requests[e.detail.id];
|
||||
if (e.detail.status) {
|
||||
@ -192,22 +181,8 @@ $.ajax = (function() {
|
||||
req.response = new DOMParser().parseFromString(e.detail.response, 'text/html');
|
||||
}
|
||||
}
|
||||
return req.onloadend();
|
||||
return req.onloadend.call(req);
|
||||
});
|
||||
};
|
||||
|
||||
return $.ajaxPage = function(url, options={}) {
|
||||
let req;
|
||||
let {onloadend, timeout, responseType, withCredentials, type, onprogress, form, headers} = options;
|
||||
const id = requestID++;
|
||||
requests[id] = (req = new CrossOrigin.Request());
|
||||
$.extend(req, {responseType, onloadend});
|
||||
req.upload = {onprogress};
|
||||
req.abort = () => $.event('4chanXAjaxAbort', {id});
|
||||
if (form) { form = Array.from(form.entries()); }
|
||||
$.event('4chanXAjax', {url, timeout, responseType, withCredentials, type, onprogress: !!onprogress, form, headers, id});
|
||||
return req;
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
@ -215,9 +190,9 @@ $.ajax = (function() {
|
||||
// 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.
|
||||
$.lastModified = dict();
|
||||
$.whenModified = function(url, bucket, cb, options={}) {
|
||||
$.whenModified = function (url, bucket, cb, options = {}) {
|
||||
let t;
|
||||
const {timeout, ajax} = options;
|
||||
const { timeout, ajax } = options;
|
||||
const params = [];
|
||||
// XXX https://bugs.chromium.org/p/chromium/issues/detail?id=643659
|
||||
if ($.engine === 'blink') { params.push(`s=${bucket}`); }
|
||||
@ -239,33 +214,33 @@ $.whenModified = function(url, bucket, cb, options={}) {
|
||||
return r;
|
||||
};
|
||||
|
||||
(function() {
|
||||
(function () {
|
||||
const reqs = dict();
|
||||
$.cache = function(url, cb, options={}) {
|
||||
$.cache = function (url, cb, options = {}) {
|
||||
let req;
|
||||
const {ajax} = options;
|
||||
const { ajax } = options;
|
||||
if (req = reqs[url]) {
|
||||
if (req.callbacks) {
|
||||
req.callbacks.push(cb);
|
||||
} else {
|
||||
$.queueTask(() => cb.call(req, {isCached: true}));
|
||||
$.queueTask(() => cb.call(req, { isCached: true }));
|
||||
}
|
||||
return req;
|
||||
}
|
||||
const onloadend = function() {
|
||||
const onloadend = function () {
|
||||
if (!this.status) {
|
||||
delete reqs[url];
|
||||
}
|
||||
for (cb of this.callbacks) {
|
||||
(cb => $.queueTask(() => cb.call(this, {isCached: false})))(cb);
|
||||
(cb => $.queueTask(() => cb.call(this, { isCached: false })))(cb);
|
||||
}
|
||||
return delete this.callbacks;
|
||||
};
|
||||
req = (ajax || $.ajax)(url, {onloadend});
|
||||
req = (ajax || $.ajax)(url, { onloadend });
|
||||
req.callbacks = [cb];
|
||||
return reqs[url] = req;
|
||||
};
|
||||
return $.cleanCache = function(testf) {
|
||||
return $.cleanCache = function (testf) {
|
||||
for (var url in reqs) {
|
||||
if (testf(url)) {
|
||||
delete reqs[url];
|
||||
@ -289,7 +264,7 @@ $.cb = {
|
||||
}
|
||||
};
|
||||
|
||||
$.asap = function(test, cb) {
|
||||
$.asap = function (test, cb) {
|
||||
if (test()) {
|
||||
return cb();
|
||||
} else {
|
||||
@ -297,32 +272,32 @@ $.asap = function(test, cb) {
|
||||
}
|
||||
};
|
||||
|
||||
$.onExists = function(root, selector, cb) {
|
||||
$.onExists = function (root, selector, cb) {
|
||||
let el;
|
||||
if (el = $(selector, root)) {
|
||||
return cb(el);
|
||||
}
|
||||
var observer = new MutationObserver(function() {
|
||||
var observer = new MutationObserver(function () {
|
||||
if (el = $(selector, root)) {
|
||||
observer.disconnect();
|
||||
return cb(el);
|
||||
}
|
||||
});
|
||||
return observer.observe(root, {childList: true, subtree: true});
|
||||
return observer.observe(root, { childList: true, subtree: true });
|
||||
};
|
||||
|
||||
$.addStyle = function(css, id, test='head') {
|
||||
$.addStyle = function (css, id, test = 'head') {
|
||||
const style = $.el('style',
|
||||
{textContent: css});
|
||||
{ textContent: css });
|
||||
if (id != null) { style.id = id; }
|
||||
$.onExists(doc, test, () => $.add(d.head, style));
|
||||
return style;
|
||||
};
|
||||
|
||||
$.addCSP = function(policy) {
|
||||
$.addCSP = function (policy) {
|
||||
const meta = $.el('meta', {
|
||||
httpEquiv: 'Content-Security-Policy',
|
||||
content: policy
|
||||
content: policy
|
||||
}
|
||||
);
|
||||
if (d.head) {
|
||||
@ -335,23 +310,23 @@ $.addCSP = function(policy) {
|
||||
}
|
||||
};
|
||||
|
||||
$.x = function(path, root) {
|
||||
$.x = function (path, root) {
|
||||
if (!root) { root = d.body; }
|
||||
// XPathResult.ANY_UNORDERED_NODE_TYPE === 8
|
||||
return d.evaluate(path, root, null, 8, null).singleNodeValue;
|
||||
};
|
||||
|
||||
$.X = function(path, root) {
|
||||
$.X = function (path, root) {
|
||||
if (!root) { root = d.body; }
|
||||
// XPathResult.ORDERED_NODE_SNAPSHOT_TYPE === 7
|
||||
return d.evaluate(path, root, null, 7, null);
|
||||
};
|
||||
|
||||
$.addClass = function(el, ...classNames) {
|
||||
$.addClass = function (el, ...classNames) {
|
||||
for (var className of classNames) { el.classList.add(className); }
|
||||
};
|
||||
|
||||
$.rmClass = function(el, ...classNames) {
|
||||
$.rmClass = function (el, ...classNames) {
|
||||
for (var className of classNames) { el.classList.remove(className); }
|
||||
};
|
||||
|
||||
@ -362,13 +337,13 @@ $.hasClass = (el, className) => el.classList.contains(className);
|
||||
$.rm = el => el?.remove();
|
||||
|
||||
$.rmAll = root => // https://gist.github.com/MayhemYDG/8646194
|
||||
root.textContent = null;
|
||||
root.textContent = null;
|
||||
|
||||
$.tn = s => d.createTextNode(s);
|
||||
|
||||
$.frag = () => d.createDocumentFragment();
|
||||
|
||||
$.nodes = function(nodes) {
|
||||
$.nodes = function (nodes) {
|
||||
if (!(nodes instanceof Array)) {
|
||||
return nodes;
|
||||
}
|
||||
@ -389,55 +364,55 @@ $.before = (root, el) => root.parentNode.insertBefore($.nodes(el), root);
|
||||
|
||||
$.replace = (root, el) => root.parentNode.replaceChild($.nodes(el), root);
|
||||
|
||||
$.el = function(tag, properties, properties2) {
|
||||
$.el = function (tag, properties, properties2) {
|
||||
const el = d.createElement(tag);
|
||||
if (properties) { $.extend(el, properties); }
|
||||
if (properties2) { $.extend(el, properties2); }
|
||||
return el;
|
||||
};
|
||||
|
||||
$.on = function(el, events, handler) {
|
||||
$.on = function (el, events, handler) {
|
||||
for (var event of events.split(' ')) {
|
||||
el.addEventListener(event, handler, false);
|
||||
}
|
||||
};
|
||||
|
||||
$.off = function(el, events, handler) {
|
||||
$.off = function (el, events, handler) {
|
||||
for (var event of events.split(' ')) {
|
||||
el.removeEventListener(event, handler, false);
|
||||
}
|
||||
};
|
||||
|
||||
$.one = function(el, events, handler) {
|
||||
var cb = function(e) {
|
||||
$.one = function (el, events, handler) {
|
||||
var cb = function (e) {
|
||||
$.off(el, events, cb);
|
||||
return handler.call(this, e);
|
||||
};
|
||||
return $.on(el, events, cb);
|
||||
};
|
||||
|
||||
$.event = function(event, detail, root=d) {
|
||||
$.event = function (event, detail, root = d) {
|
||||
if (!globalThis.chrome?.extension) {
|
||||
if ((detail != null) && (typeof cloneInto === 'function')) {
|
||||
detail = cloneInto(detail, d.defaultView);
|
||||
}
|
||||
}
|
||||
return root.dispatchEvent(new CustomEvent(event, {bubbles: true, cancelable: true, detail}));
|
||||
return root.dispatchEvent(new CustomEvent(event, { bubbles: true, cancelable: true, detail }));
|
||||
};
|
||||
|
||||
if (platform === 'userscript') {
|
||||
// XXX Make $.event work in Pale Moon with GM 3.x (no cloneInto function).
|
||||
(function() {
|
||||
(function () {
|
||||
if (!/PaleMoon\//.test(navigator.userAgent) || (+GM_info?.version?.split('.')[0] < 2) || (typeof cloneInto !== 'undefined')) { return; }
|
||||
|
||||
try {
|
||||
return new CustomEvent('x', {detail: {}});
|
||||
return new CustomEvent('x', { detail: {} });
|
||||
} catch (err) {
|
||||
const unsafeConstructors = {
|
||||
Object: unsafeWindow.Object,
|
||||
Array: unsafeWindow.Array
|
||||
Array: unsafeWindow.Array
|
||||
};
|
||||
var clone = function(obj) {
|
||||
var clone = function (obj) {
|
||||
let constructor;
|
||||
if ((obj != null) && (typeof obj === 'object') && (constructor = unsafeConstructors[obj.constructor.name])) {
|
||||
const obj2 = new constructor();
|
||||
@ -447,7 +422,7 @@ if (platform === 'userscript') {
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
return $.event = (event, detail, root=d) => root.dispatchEvent(new CustomEvent(event, {bubbles: true, cancelable: true, detail: clone(detail)}));
|
||||
return $.event = (event, detail, root = d) => root.dispatchEvent(new CustomEvent(event, { bubbles: true, cancelable: true, detail: clone(detail) }));
|
||||
}
|
||||
})();
|
||||
}
|
||||
@ -458,25 +433,25 @@ if (!globalThis.chrome?.extension) {
|
||||
$.open =
|
||||
(GM?.openInTab != null) ?
|
||||
GM.openInTab
|
||||
: (typeof GM_openInTab !== 'undefined' && GM_openInTab !== null) ?
|
||||
GM_openInTab
|
||||
:
|
||||
url => window.open(url, '_blank');
|
||||
: (typeof GM_openInTab !== 'undefined' && GM_openInTab !== null) ?
|
||||
GM_openInTab
|
||||
:
|
||||
url => window.open(url, '_blank');
|
||||
} else {
|
||||
$.open =
|
||||
url => window.open(url, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
$.debounce = function(wait, fn) {
|
||||
$.debounce = function (wait, fn) {
|
||||
let lastCall = 0;
|
||||
let timeout = null;
|
||||
let that = null;
|
||||
let args = null;
|
||||
const exec = function() {
|
||||
let timeout = null;
|
||||
let that = null;
|
||||
let args = null;
|
||||
const exec = function () {
|
||||
lastCall = Date.now();
|
||||
return fn.apply(that, args);
|
||||
};
|
||||
return function() {
|
||||
return function () {
|
||||
args = arguments;
|
||||
that = this;
|
||||
if (lastCall < (Date.now() - wait)) {
|
||||
@ -489,34 +464,17 @@ $.debounce = function(wait, fn) {
|
||||
};
|
||||
};
|
||||
|
||||
$.queueTask = (function() {
|
||||
// inspired by https://www.w3.org/Bugs/Public/show_bug.cgi?id=15007
|
||||
const taskQueue = [];
|
||||
const execTask = function() {
|
||||
const task = taskQueue.shift();
|
||||
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);
|
||||
};
|
||||
$.queueTask = function (fn: VoidFunction) {
|
||||
if (typeof queueMicrotask === 'function') {
|
||||
return queueMicrotask(fn);
|
||||
} else {
|
||||
return setTimeout(fn, 0);
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
$.global = function(fn, data) {
|
||||
$.global = function (fn, data) {
|
||||
if (doc) {
|
||||
const script = $.el('script',
|
||||
{textContent: `(${fn}).call(document.currentScript.dataset);`});
|
||||
const script = $.el('script', { type: 'application/json' }, { textContent: JSON.stringify(data) });
|
||||
if (data) { $.extend(script.dataset, data); }
|
||||
$.add((d.head || doc), script);
|
||||
$.rm(script);
|
||||
@ -525,12 +483,12 @@ $.global = function(fn, data) {
|
||||
// XXX dwb
|
||||
try {
|
||||
fn.call(data);
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
$.bytesToString = function(size) {
|
||||
$.bytesToString = function (size: number) {
|
||||
let unit = 0; // Bytes
|
||||
while (size >= 1024) {
|
||||
size /= 1024;
|
||||
@ -542,40 +500,39 @@ $.bytesToString = function(size) {
|
||||
// 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 ?
|
||||
$.minmax = (value: number, min: number, max: number) => value < min ?
|
||||
min
|
||||
:
|
||||
value > max ?
|
||||
: value > max ?
|
||||
max
|
||||
:
|
||||
:
|
||||
value;
|
||||
|
||||
$.hasAudio = video => video.mozHasAudio || !!video.webkitAudioDecodedByteCount;
|
||||
|
||||
$.luma = rgb => (rgb[0] * 0.299) + (rgb[1] * 0.587) + (rgb[2] * 0.114);
|
||||
|
||||
$.unescape = function(text) {
|
||||
$.unescape = function (text) {
|
||||
if (text == null) { return text; }
|
||||
return text.replace(/<[^>]*>/g, '').replace(/&(amp|#039|quot|lt|gt|#44);/g, c => ({'&': '&', ''': "'", '"': '"', '<': '<', '>': '>', ',': ','})[c]);
|
||||
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);
|
||||
|
||||
$.engine = (function() {
|
||||
$.engine = (function () {
|
||||
if (/Edge\//.test(navigator.userAgent)) { return 'edge'; }
|
||||
if (/Chrome\//.test(navigator.userAgent)) { return 'blink'; }
|
||||
if (/WebKit\//.test(navigator.userAgent)) { return 'webkit'; }
|
||||
if (/Gecko\/|Goanna/.test(navigator.userAgent)) { return 'gecko'; } // Goanna = Pale Moon 26+
|
||||
})();
|
||||
|
||||
$.hasStorage = (function() {
|
||||
$.hasStorage = (function () {
|
||||
try {
|
||||
if (localStorage.getItem(g.NAMESPACE + 'hasStorage') === 'true') { return true; }
|
||||
localStorage.setItem(g.NAMESPACE + 'hasStorage', 'true');
|
||||
@ -585,13 +542,13 @@ $.hasStorage = (function() {
|
||||
}
|
||||
})();
|
||||
|
||||
$.item = function(key, val) {
|
||||
$.item = function (key, val) {
|
||||
const item = dict();
|
||||
item[key] = val;
|
||||
return item;
|
||||
};
|
||||
|
||||
$.oneItemSugar = fn => (function(key, val, cb) {
|
||||
$.oneItemSugar = fn => (function (key, val, cb) {
|
||||
if (typeof key === 'string') {
|
||||
return fn($.item(key, val), cb);
|
||||
} else {
|
||||
@ -601,7 +558,7 @@ $.oneItemSugar = fn => (function(key, val, cb) {
|
||||
|
||||
$.syncing = dict();
|
||||
|
||||
$.securityCheck = function(data) {
|
||||
$.securityCheck = function (data) {
|
||||
if (location.protocol !== 'https:') {
|
||||
return delete data['Redirect to HTTPS'];
|
||||
}
|
||||
@ -611,10 +568,10 @@ if (platform === 'crx') {
|
||||
// https://developer.chrome.com/extensions/storage.html
|
||||
$.oldValue = {
|
||||
local: dict(),
|
||||
sync: dict()
|
||||
sync: dict()
|
||||
};
|
||||
|
||||
chrome.storage.onChanged.addListener(function(changes, area) {
|
||||
chrome.storage.onChanged.addListener(function (changes, area) {
|
||||
for (var key in changes) {
|
||||
var oldValue = $.oldValue.local[key] ?? $.oldValue.sync[key];
|
||||
$.oldValue[area][key] = dict.clone(changes[key].newValue);
|
||||
@ -626,17 +583,17 @@ if (platform === 'crx') {
|
||||
}
|
||||
});
|
||||
$.sync = (key, cb) => $.syncing[key] = cb;
|
||||
$.forceSync = function() { };
|
||||
$.forceSync = function () { };
|
||||
|
||||
$.crxWorking = function() {
|
||||
$.crxWorking = function () {
|
||||
try {
|
||||
if (chrome.runtime.getManifest()) {
|
||||
return true;
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
if (!$.crxWarningShown) {
|
||||
const msg = $.el('div',
|
||||
{innerHTML: '4chan X seems to have been updated. You will need to <a href="javascript:;">reload</a> the page.'});
|
||||
{ innerHTML: '4chan X seems to have been updated. You will need to <a href="javascript:;">reload</a> the page.' });
|
||||
$.on($('a', msg), 'click', () => location.reload());
|
||||
new Notice('warning', msg);
|
||||
$.crxWarningShown = true;
|
||||
@ -644,16 +601,16 @@ if (platform === 'crx') {
|
||||
return false;
|
||||
};
|
||||
|
||||
$.get = $.oneItemSugar(function(data, cb) {
|
||||
$.get = $.oneItemSugar(function (data, cb) {
|
||||
if (!$.crxWorking()) { return; }
|
||||
const results = {};
|
||||
const get = function(area) {
|
||||
const get = function (area) {
|
||||
let keys = Object.keys(data);
|
||||
// XXX slow performance in Firefox
|
||||
if (($.engine === 'gecko') && (area === 'sync') && (keys.length > 3)) {
|
||||
keys = null;
|
||||
}
|
||||
return chrome.storage[area].get(keys, function(result) {
|
||||
return chrome.storage[area].get(keys, function (result) {
|
||||
let key;
|
||||
result = dict.clone(result);
|
||||
if (chrome.runtime.lastError) {
|
||||
@ -679,16 +636,16 @@ if (platform === 'crx') {
|
||||
return get('sync');
|
||||
});
|
||||
|
||||
(function() {
|
||||
(function () {
|
||||
const items = {
|
||||
local: dict(),
|
||||
sync: dict()
|
||||
sync: dict()
|
||||
};
|
||||
|
||||
const exceedsQuota = (key, value) => // bytes in UTF-8
|
||||
unescape(encodeURIComponent(JSON.stringify(key))).length + unescape(encodeURIComponent(JSON.stringify(value))).length > chrome.storage.sync.QUOTA_BYTES_PER_ITEM;
|
||||
unescape(encodeURIComponent(JSON.stringify(key))).length + unescape(encodeURIComponent(JSON.stringify(value))).length > chrome.storage.sync.QUOTA_BYTES_PER_ITEM;
|
||||
|
||||
$.delete = function(keys) {
|
||||
$.delete = function (keys) {
|
||||
if (!$.crxWorking()) { return; }
|
||||
if (typeof keys === 'string') {
|
||||
keys = [keys];
|
||||
@ -702,11 +659,11 @@ if (platform === 'crx') {
|
||||
};
|
||||
|
||||
const timeout = {};
|
||||
var setArea = function(area, cb) {
|
||||
var setArea = function (area, cb) {
|
||||
const data = dict();
|
||||
$.extend(data, items[area]);
|
||||
if (!Object.keys(data).length || (timeout[area] > Date.now())) { return; }
|
||||
return chrome.storage[area].set(data, function() {
|
||||
return chrome.storage[area].set(data, function () {
|
||||
let err;
|
||||
let key;
|
||||
if (err = chrome.runtime.lastError) {
|
||||
@ -738,20 +695,20 @@ if (platform === 'crx') {
|
||||
|
||||
var setSync = debounce(SECOND, () => setArea('sync'));
|
||||
|
||||
$.set = $.oneItemSugar(function(data, cb) {
|
||||
$.set = $.oneItemSugar(function (data, cb) {
|
||||
if (!$.crxWorking()) { return; }
|
||||
$.securityCheck(data);
|
||||
$.extend(items.local, data);
|
||||
return setArea('local', cb);
|
||||
});
|
||||
|
||||
return $.clear = function(cb) {
|
||||
return $.clear = function (cb) {
|
||||
if (!$.crxWorking()) { return; }
|
||||
items.local = dict();
|
||||
items.sync = dict();
|
||||
items.sync = dict();
|
||||
let count = 2;
|
||||
let err = null;
|
||||
const done = function() {
|
||||
let err = null;
|
||||
const done = function () {
|
||||
if (chrome.runtime.lastError) {
|
||||
c.error(chrome.runtime.lastError.message);
|
||||
}
|
||||
@ -785,19 +742,20 @@ if (platform === 'crx') {
|
||||
|
||||
$.sync = (key, cb) => $.syncing[key] = cb;
|
||||
|
||||
$.forceSync = function() {};
|
||||
$.forceSync = function () { };
|
||||
|
||||
$.delete = function(keys, cb) {
|
||||
$.delete = function (keys, cb) {
|
||||
let key;
|
||||
if (!(keys instanceof Array)) {
|
||||
keys = [keys];
|
||||
}
|
||||
return Promise.all((() => {
|
||||
const result = [];
|
||||
for (key of keys) { result.push(GM.deleteValue(g.NAMESPACE + key));
|
||||
for (key of keys) {
|
||||
result.push(GM.deleteValue(g.NAMESPACE + key));
|
||||
}
|
||||
return result;
|
||||
})()).then(function() {
|
||||
})()).then(function () {
|
||||
const items = dict();
|
||||
for (key of keys) { items[key] = undefined; }
|
||||
$.syncChannel.postMessage(items);
|
||||
@ -805,9 +763,9 @@ if (platform === 'crx') {
|
||||
});
|
||||
};
|
||||
|
||||
$.get = $.oneItemSugar(function(items, cb) {
|
||||
$.get = $.oneItemSugar(function (items, cb) {
|
||||
const keys = 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) {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
var val = values[i];
|
||||
if (val) {
|
||||
@ -818,7 +776,7 @@ if (platform === 'crx') {
|
||||
});
|
||||
});
|
||||
|
||||
$.set = $.oneItemSugar(function(items, cb) {
|
||||
$.set = $.oneItemSugar(function (items, cb) {
|
||||
$.securityCheck(items);
|
||||
return Promise.all((() => {
|
||||
const result = [];
|
||||
@ -827,13 +785,13 @@ if (platform === 'crx') {
|
||||
result.push(GM.setValue(g.NAMESPACE + key, JSON.stringify(val)));
|
||||
}
|
||||
return result;
|
||||
})()).then(function() {
|
||||
})()).then(function () {
|
||||
$.syncChannel.postMessage(items);
|
||||
return cb?.();
|
||||
});
|
||||
});
|
||||
|
||||
$.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 = 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));
|
||||
} else {
|
||||
|
||||
if (typeof GM_deleteValue === 'undefined' || GM_deleteValue === null) {
|
||||
@ -841,7 +799,7 @@ if (platform === 'crx') {
|
||||
}
|
||||
|
||||
if (typeof GM_deleteValue !== 'undefined' && GM_deleteValue !== null) {
|
||||
$.getValue = GM_getValue;
|
||||
$.getValue = GM_getValue;
|
||||
$.listValues = () => GM_listValues(); // error when called if missing
|
||||
} else if ($.hasStorage) {
|
||||
$.getValue = key => localStorage.getItem(key);
|
||||
@ -855,23 +813,23 @@ if (platform === 'crx') {
|
||||
return result;
|
||||
})();
|
||||
} else {
|
||||
$.getValue = function() {};
|
||||
$.getValue = function () { };
|
||||
$.listValues = () => [];
|
||||
}
|
||||
|
||||
if (typeof GM_addValueChangeListener !== 'undefined' && GM_addValueChangeListener !== null) {
|
||||
$.setValue = GM_setValue;
|
||||
$.setValue = GM_setValue;
|
||||
$.deleteValue = GM_deleteValue;
|
||||
} else if (typeof GM_deleteValue !== 'undefined' && GM_deleteValue !== null) {
|
||||
$.oldValue = dict();
|
||||
$.setValue = function(key, val) {
|
||||
$.setValue = function (key, val) {
|
||||
GM_setValue(key, val);
|
||||
if (key in $.syncing) {
|
||||
$.oldValue[key] = val;
|
||||
$.oldValue[key] = val;
|
||||
if ($.hasStorage) { return localStorage.setItem(key, val); } // for `storage` events
|
||||
}
|
||||
};
|
||||
$.deleteValue = function(key) {
|
||||
$.deleteValue = function (key) {
|
||||
GM_deleteValue(key);
|
||||
if (key in $.syncing) {
|
||||
delete $.oldValue[key];
|
||||
@ -881,37 +839,37 @@ if (platform === 'crx') {
|
||||
if (!$.hasStorage) { $.cantSync = true; }
|
||||
} else if ($.hasStorage) {
|
||||
$.oldValue = dict();
|
||||
$.setValue = function(key, val) {
|
||||
if (key in $.syncing) { $.oldValue[key] = val; }
|
||||
$.setValue = function (key, val) {
|
||||
if (key in $.syncing) { $.oldValue[key] = val; }
|
||||
return localStorage.setItem(key, val);
|
||||
};
|
||||
$.deleteValue = function(key) {
|
||||
$.deleteValue = function (key) {
|
||||
if (key in $.syncing) { delete $.oldValue[key]; }
|
||||
return localStorage.removeItem(key);
|
||||
};
|
||||
} else {
|
||||
$.setValue = function() {};
|
||||
$.deleteValue = function() {};
|
||||
$.setValue = function () { };
|
||||
$.deleteValue = function () { };
|
||||
$.cantSync = ($.cantSet = true);
|
||||
}
|
||||
|
||||
if (typeof GM_addValueChangeListener !== 'undefined' && GM_addValueChangeListener !== null) {
|
||||
$.sync = (key, cb) => $.syncing[key] = GM_addValueChangeListener(g.NAMESPACE + key, function(key2, oldValue, newValue, remote) {
|
||||
$.sync = (key, cb) => $.syncing[key] = GM_addValueChangeListener(g.NAMESPACE + key, function (key2, oldValue, newValue, remote) {
|
||||
if (remote) {
|
||||
if (newValue !== undefined) { newValue = dict.json(newValue); }
|
||||
return cb(newValue, key);
|
||||
}
|
||||
});
|
||||
$.forceSync = function() {};
|
||||
$.forceSync = function () { };
|
||||
} else if ((typeof GM_deleteValue !== 'undefined' && GM_deleteValue !== null) || $.hasStorage) {
|
||||
$.sync = function(key, cb) {
|
||||
$.sync = function (key, cb) {
|
||||
key = g.NAMESPACE + key;
|
||||
$.syncing[key] = cb;
|
||||
return $.oldValue[key] = $.getValue(key);
|
||||
};
|
||||
|
||||
(function() {
|
||||
const onChange = function({key, newValue}) {
|
||||
(function () {
|
||||
const onChange = function ({ key, newValue }) {
|
||||
let cb;
|
||||
if (!(cb = $.syncing[key])) { return; }
|
||||
if (newValue != null) {
|
||||
@ -926,20 +884,20 @@ if (platform === 'crx') {
|
||||
};
|
||||
$.on(window, 'storage', onChange);
|
||||
|
||||
return $.forceSync = function(key) {
|
||||
return $.forceSync = function (key) {
|
||||
// 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)});
|
||||
return onChange({ key, newValue: $.getValue(key) });
|
||||
};
|
||||
})();
|
||||
} else {
|
||||
$.sync = function() {};
|
||||
$.forceSync = function() {};
|
||||
$.sync = function () { };
|
||||
$.forceSync = function () { };
|
||||
}
|
||||
|
||||
$.delete = function(keys) {
|
||||
$.delete = function (keys) {
|
||||
if (!(keys instanceof Array)) {
|
||||
keys = [keys];
|
||||
}
|
||||
@ -948,9 +906,9 @@ if (platform === 'crx') {
|
||||
}
|
||||
};
|
||||
|
||||
$.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) {
|
||||
var val2;
|
||||
if (val2 = $.getValue(g.NAMESPACE + key)) {
|
||||
@ -967,9 +925,9 @@ if (platform === 'crx') {
|
||||
return cb(items);
|
||||
};
|
||||
|
||||
$.set = $.oneItemSugar(function(items, cb) {
|
||||
$.set = $.oneItemSugar(function (items, cb) {
|
||||
$.securityCheck(items);
|
||||
return $.queueTask(function() {
|
||||
return $.queueTask(function () {
|
||||
for (var key in items) {
|
||||
var value = items[key];
|
||||
$.setValue(g.NAMESPACE + key, JSON.stringify(value));
|
||||
@ -978,14 +936,14 @@ if (platform === 'crx') {
|
||||
});
|
||||
});
|
||||
|
||||
$.clear = function(cb) {
|
||||
$.clear = function (cb) {
|
||||
// XXX https://github.com/greasemonkey/greasemonkey/issues/2033
|
||||
// Also support case where GM_listValues is not defined.
|
||||
$.delete(Object.keys(Conf));
|
||||
$.delete(['previousversion', 'QR Size', 'QR.persona']);
|
||||
try {
|
||||
$.delete($.listValues().map(key => key.replace(g.NAMESPACE, '')));
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
return cb?.();
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user