simplify exports

This commit is contained in:
tophf 2020-12-13 20:26:31 +03:00
parent e74a349a88
commit f8269c7424
17 changed files with 240 additions and 253 deletions

View File

@ -18,12 +18,7 @@ define(require => {
const ICON_SIZES = FIREFOX || CHROME >= 55 && !VIVALDI ? [16, 32] : [19, 38];
const staleBadges = new Set();
let exports;
const {
updateIconBadge,
} = exports = /** @namespace API */ {
const iconManager = /** @namespace API */ {
/**
* @param {(number|string)[]} styleIds
* @param {boolean} [lazyBadge=false] preventing flicker during page load
@ -72,7 +67,7 @@ define(require => {
function onPortDisconnected({sender}) {
if (tabManager.get(sender.tab.id, 'styleIds')) {
updateIconBadge.call({sender}, [], {lazyBadge: true});
iconManager.updateIconBadge.call({sender}, [], {lazyBadge: true});
}
}
@ -153,5 +148,5 @@ define(require => {
staleBadges.clear();
}
return exports;
return iconManager;
});

View File

@ -28,13 +28,11 @@ define(require => {
//#region Declarations
const ready = init();
/**
* @typedef StyleMapData
* @property {StyleObj} style
* @property {?StyleObj} [preview]
* @property {Set<string>} appliesTo - urls
*/
/** @typedef {{
style: StyleObj
preview?: StyleObj
appliesTo: Set<string>
}} StyleMapData */
/** @type {Map<number,StyleMapData>} */
const dataMap = new Map();
const uuidIndex = new Map();
@ -59,13 +57,33 @@ define(require => {
};
const DELETE_IF_NULL = ['id', 'customName'];
let ready = false;
const init = db.exec('getAll').then(async res => {
const styles = res || [];
const updated = styles.filter(style =>
addMissingProps(style) +
addCustomName(style));
if (updated.length) {
await db.exec('putMany', updated);
}
for (const style of styles) {
fixUsoMd5Issue(style);
storeInMap(style);
uuidIndex.set(style._id, style.id);
}
ready = true;
});
chrome.runtime.onConnect.addListener(handleLivePreview);
//#endregion
//#region Exports
/** @type {StyleManager} */
const styleManager = /** @namespace StyleManager */ {
/**
* @type StyleManager
* @namespace StyleManager
*/
const styleManager = {
/* props first,
then method shorthands if any,
@ -79,7 +97,7 @@ define(require => {
/** @returns {Promise<number>} style id */
async delete(id, reason) {
await ready;
if (!ready) await init;
const data = id2data(id);
await db.exec('delete', id);
if (reason !== 'sync') {
@ -100,7 +118,7 @@ define(require => {
/** @returns {Promise<number>} style id */
async deleteByUUID(_id, rev) {
await ready;
if (!ready) await init;
const id = uuidIndex.get(_id);
const oldDoc = id && id2style(id);
if (oldDoc && styleManager.compareRevision(oldDoc._rev, rev) <= 0) {
@ -111,7 +129,7 @@ define(require => {
/** @returns {Promise<StyleObj>} */
async editSave(style) {
await ready;
if (!ready) await init;
style = mergeWithMapped(style);
style.updateDate = Date.now();
return handleSave(await saveStyle(style), 'editSave');
@ -119,7 +137,7 @@ define(require => {
/** @returns {Promise<?StyleObj>} */
async find(filter) {
await ready;
if (!ready) await init;
const filterEntries = Object.entries(filter);
for (const {style} of dataMap.values()) {
if (filterEntries.every(([key, val]) => style[key] === val)) {
@ -131,19 +149,19 @@ define(require => {
/** @returns {Promise<StyleObj[]>} */
async getAll() {
await ready;
if (!ready) await init;
return Array.from(dataMap.values(), data2style);
},
/** @returns {Promise<StyleObj>} */
async getByUUID(uuid) {
await ready;
if (!ready) await init;
return id2style(uuidIndex.get(uuid));
},
/** @returns {Promise<StyleSectionsToApply>} */
async getSectionsByUrl(url, id, isInitialApply) {
await ready;
if (!ready) await init;
/* Chrome hides text frament from location.href of the page e.g. #:~:text=foo
so we'll use the real URL reported by webNavigation API */
const {tab, frameId} = this && this.sender || {};
@ -170,13 +188,13 @@ define(require => {
/** @returns {Promise<StyleObj>} */
async get(id) {
await ready;
if (!ready) await init;
return id2style(id);
},
/** @returns {Promise<StylesByUrlResult[]>} */
async getByUrl(url, id = null) {
await ready;
if (!ready) await init;
// FIXME: do we want to cache this? Who would like to open popup rapidly
// or search the DB with the same URL?
const result = [];
@ -218,7 +236,7 @@ define(require => {
/** @returns {Promise<StyleObj[]>} */
async importMany(items) {
await ready;
if (!ready) await init;
items.forEach(beforeSave);
const events = await db.exec('putMany', items);
return Promise.all(items.map((item, i) => {
@ -229,13 +247,13 @@ define(require => {
/** @returns {Promise<StyleObj>} */
async import(data) {
await ready;
if (!ready) await init;
return handleSave(await saveStyle(data), 'import');
},
/** @returns {Promise<StyleObj>} */
async install(style, reason = null) {
await ready;
if (!ready) await init;
reason = reason || dataMap.has(style.id) ? 'update' : 'install';
style = mergeWithMapped(style);
const url = !style.url && style.updateUrl && (
@ -250,7 +268,7 @@ define(require => {
/** @returns {Promise<?StyleObj>} */
async putByUUID(doc) {
await ready;
if (!ready) await init;
const id = uuidIndex.get(doc._id);
if (id) {
doc.id = id;
@ -275,7 +293,7 @@ define(require => {
/** @returns {Promise<number>} style id */
async toggle(id, enabled) {
await ready;
if (!ready) await init;
const style = Object.assign({}, id2style(id), {enabled});
handleSave(await saveStyle(style), 'toggle', false);
return id;
@ -362,7 +380,7 @@ define(require => {
}
async function addIncludeExclude(type, id, rule) {
await ready;
if (!ready) await init;
const style = Object.assign({}, id2style(id));
const list = style[type] || (style[type] = []);
if (list.includes(rule)) {
@ -373,7 +391,7 @@ define(require => {
}
async function removeIncludeExclude(type, id, rule) {
await ready;
if (!ready) await init;
const style = Object.assign({}, id2style(id));
const list = style[type];
if (!list || !list.includes(rule)) {
@ -472,21 +490,6 @@ define(require => {
return code.length && code;
}
async function init() {
const styles = await db.exec('getAll') || [];
const updated = styles.filter(style =>
addMissingProps(style) +
addCustomName(style));
if (updated.length) {
await db.exec('putMany', updated);
}
for (const style of styles) {
fixUsoMd5Issue(style);
storeInMap(style);
uuidIndex.set(style._id, style.id);
}
}
function addMissingProps(style) {
let res = 0;
for (const key in MISSING_PROPS) {

View File

@ -7,9 +7,6 @@ define(require => {
const {compareRevision} = require('./style-manager');
const tokenManager = require('./token-manager');
/** @type Sync */
let sync;
//#region Init
const SYNC_DELAY = 1; // minutes
@ -32,30 +29,23 @@ define(require => {
};
let currentDrive;
let ctrl;
const ready = prefs.initializing.then(() => {
prefs.subscribe('sync.enabled',
(_, val) => val === 'none'
? sync.stop()
: sync.start(val, true),
{runNow: true});
});
chrome.alarms.onAlarm.addListener(info => {
if (info.name === 'syncNow') {
sync.syncNow();
}
});
let ready = false;
const init = prefs.initializing.then(onPrefsReady);
chrome.alarms.onAlarm.addListener(onAlarm);
//#endregion
//#region Exports
sync = /** @namespace Sync */ {
/**
* @type Sync
* @namespace Sync
*/
const sync = {
// sorted alphabetically
async delete(...args) {
await ready;
if (!ready) await init;
if (!currentDrive) return;
schedule();
return ctrl.delete(...args);
@ -67,7 +57,7 @@ define(require => {
},
async login(name = prefs.get('sync.enabled')) {
await ready;
if (!ready) await init;
try {
await tokenManager.getToken(name, true);
} catch (err) {
@ -82,14 +72,14 @@ define(require => {
},
async put(...args) {
await ready;
if (!ready) await init;
if (!currentDrive) return;
schedule();
return ctrl.put(...args);
},
async start(name, fromPref = false) {
await ready;
if (!ready) await init;
if (currentDrive) {
return;
}
@ -121,7 +111,7 @@ define(require => {
},
async stop() {
await ready;
if (!ready) await init;
if (!currentDrive) {
return;
}
@ -142,7 +132,7 @@ define(require => {
},
async syncNow() {
await ready;
if (!ready) await init;
if (!currentDrive) {
return Promise.reject(new Error('cannot sync when disconnected'));
}
@ -197,13 +187,6 @@ define(require => {
});
}
function schedule(delay = SYNC_DELAY) {
chrome.alarms.create('syncNow', {
delayInMinutes: delay,
periodInMinutes: SYNC_INTERVAL,
});
}
async function handle401Error(err) {
let emit;
if (err.code === 401) {
@ -232,6 +215,28 @@ define(require => {
throw new Error(`unknown cloud name: ${name}`);
}
function onAlarm(info) {
if (info.name === 'syncNow') {
sync.syncNow();
}
}
function onPrefsReady() {
ready = true;
prefs.subscribe('sync.enabled',
(_, val) => val === 'none'
? sync.stop()
: sync.start(val, true),
{runNow: true});
}
function schedule(delay = SYNC_DELAY) {
chrome.alarms.create('syncNow', {
delayInMinutes: delay,
periodInMinutes: SYNC_INTERVAL,
});
}
//#endregion
return sync;

View File

@ -7,12 +7,7 @@ define(require => {
const AUTH = createAuth();
const NETWORK_LATENCY = 30; // seconds
let exports;
const {
buildKeys,
} = exports = {
const tokenManager = {
buildKeys(name) {
const k = {
@ -29,7 +24,7 @@ define(require => {
},
getToken(name, interactive) {
const k = buildKeys(name);
const k = tokenManager.buildKeys(name);
return chromeLocal.get(k.LIST)
.then(obj => {
if (!obj[k.TOKEN]) {
@ -53,7 +48,7 @@ define(require => {
async revokeToken(name) {
const provider = AUTH[name];
const k = buildKeys(name);
const k = tokenManager.buildKeys(name);
if (provider.revoke) {
try {
const token = await chromeLocal.getValue(k.TOKEN);
@ -222,5 +217,5 @@ define(require => {
throw err;
}
return exports;
return tokenManager;
});

View File

@ -47,8 +47,11 @@ define(require => {
chrome.alarms.onAlarm.addListener(onAlarm);
});
/** @type {StyleUpdater} */
const updater = /** @namespace StyleUpdater */ {
/**
* @type StyleUpdater
* @namespace StyleUpdater
*/
const updater = {
async checkAllStyles({
save = true,

View File

@ -16,7 +16,11 @@ define(require => {
'missingChar',
];
const usercss = /** @namespace UsercssHelper */ {
/**
* @type UsercssHelper
* @namespace UsercssHelper
*/
const usercss = {
rxMETA: /\/\*!?\s*==userstyle==[\s\S]*?==\/userstyle==\s*\*\//i,

View File

@ -2,6 +2,39 @@
define(require => { // define and require use `importScripts` which is synchronous
/** @namespace EditorWorker */
require('/js/worker-util').createAPI({
async csslint(code, config) {
return require('/js/csslint/csslint')
.verify(code, config).messages
.map(m => Object.assign(m, {rule: {id: m.rule.id}}));
},
getRules(linter) {
return ruleRetriever[linter](); // eslint-disable-line no-use-before-define
},
metalint(code) {
const result = require('/js/meta-parser').lint(code);
// extract needed info
result.errors = result.errors.map(err => ({
code: err.code,
args: err.args,
message: err.message,
index: err.index,
}));
return result;
},
async stylelint(code, config) {
require('/vendor/stylelint-bundle/stylelint-bundle.min');
const {results: [res]} = await self.require('stylelint').lint({code, config});
delete res._postcssResult; // huge and unused
return res;
},
});
const ruleRetriever = {
csslint() {
@ -54,37 +87,4 @@ define(require => { // define and require use `importScripts` which is synchrono
return options;
},
};
/** @namespace EditorWorker */
require('/js/worker-util').createAPI({
async csslint(code, config) {
return require('/js/csslint/csslint')
.verify(code, config).messages
.map(m => Object.assign(m, {rule: {id: m.rule.id}}));
},
getRules(linter) {
return ruleRetriever[linter]();
},
metalint(code) {
const result = require('/js/meta-parser').lint(code);
// extract needed info
result.errors = result.errors.map(err => ({
code: err.code,
args: err.args,
message: err.message,
index: err.index,
}));
return result;
},
async stylelint(code, config) {
require('/vendor/stylelint-bundle/stylelint-bundle.min');
const {results: [res]} = await self.require('stylelint').lint({code, config});
delete res._postcssResult; // huge and unused
return res;
},
});
});

View File

@ -11,9 +11,9 @@ define(require => {
const {clipString} = require('./util');
const dirty = DirtyReporter();
const toc = [];
let style;
let wasDirty = false;
const toc = [];
/**
* @mixes SectionsEditor

View File

@ -4,63 +4,62 @@
* Creates a FIFO limit-size map.
*/
define(require =>
function createCache({size = 1000, onDeleted} = {}) {
const map = new Map();
const buffer = Array(size);
let index = 0;
let lastIndex = 0;
return {
get(id) {
const item = map.get(id);
return item && item.data;
},
set(id, data) {
if (map.size === size) {
// full
map.delete(buffer[lastIndex].id);
if (onDeleted) {
onDeleted(buffer[lastIndex].id, buffer[lastIndex].data);
}
lastIndex = (lastIndex + 1) % size;
}
const item = {id, data, index};
map.set(id, item);
buffer[index] = item;
index = (index + 1) % size;
},
delete(id) {
const item = map.get(id);
if (!item) {
return false;
}
map.delete(item.id);
const lastItem = buffer[lastIndex];
lastItem.index = item.index;
buffer[item.index] = lastItem;
lastIndex = (lastIndex + 1) % size;
define(require => function createCache({size = 1000, onDeleted} = {}) {
const map = new Map();
const buffer = Array(size);
let index = 0;
let lastIndex = 0;
return {
get(id) {
const item = map.get(id);
return item && item.data;
},
set(id, data) {
if (map.size === size) {
// full
map.delete(buffer[lastIndex].id);
if (onDeleted) {
onDeleted(item.id, item.data);
onDeleted(buffer[lastIndex].id, buffer[lastIndex].data);
}
return true;
},
clear() {
map.clear();
index = lastIndex = 0;
},
has: id => map.has(id),
*entries() {
for (const [id, item] of map) {
yield [id, item.data];
}
},
*values() {
for (const item of map.values()) {
yield item.data;
}
},
get size() {
return map.size;
},
};
});
lastIndex = (lastIndex + 1) % size;
}
const item = {id, data, index};
map.set(id, item);
buffer[index] = item;
index = (index + 1) % size;
},
delete(id) {
const item = map.get(id);
if (!item) {
return false;
}
map.delete(item.id);
const lastItem = buffer[lastIndex];
lastItem.index = item.index;
buffer[item.index] = lastItem;
lastIndex = (lastIndex + 1) % size;
if (onDeleted) {
onDeleted(item.id, item.data);
}
return true;
},
clear() {
map.clear();
index = lastIndex = 0;
},
has: id => map.has(id),
*entries() {
for (const [id, item] of map) {
yield [id, item.data];
}
},
*values() {
for (const item of map.values()) {
yield item.data;
}
},
get size() {
return map.size;
},
};
});

View File

@ -9,18 +9,11 @@ define(require => {
/** @type {Prefs} */
let prefs;
let $, $$;
//#region Exports
/** @type {DOM} */
let dom;
const {
$,
$$,
$create,
} = dom = /** @namespace DOM */ {
const dom = {
$(selector, base = document) {
// we have ids with . like #manage.onlyEnabled which looks like #id.class
@ -351,6 +344,8 @@ define(require => {
//#endregion
//#region Init
({$, $$} = dom);
const Collapsible = {
bindEvents(_, elems) {
const prefKeys = [];
@ -413,7 +408,7 @@ define(require => {
function addFaviconFF() {
const iconset = ['', 'light/'][prefs.get('iconset')] || '';
for (const size of [38, 32, 19, 16]) {
document.head.appendChild($create('link', {
document.head.appendChild(dom.$create('link', {
rel: 'icon',
href: `/images/icon/${iconset}${size}.png`,
sizes: size + 'x' + size,

View File

@ -10,8 +10,11 @@ define(require => {
const STORAGE_KEY = 'settings';
const clone = deepCopy || (val => JSON.parse(JSON.stringify(val)));
/** @type {PrefsValues} */
const defaults = /** @namespace PrefsValues */ {
/**
* @type PrefsValues
* @namespace PrefsValues
*/
const defaults = {
'openEditInWindow': false, // new editor opens in a own browser window
'openEditInWindow.popup': false, // new editor opens in a simplified browser window without omnibox
'windowPosition': {}, // detached window position
@ -138,7 +141,10 @@ define(require => {
}
});
/** @namespace Prefs */
/**
* @type Prefs
* @namespace Prefs
*/
const prefs = {
STORAGE_KEY,

View File

@ -1,9 +1,6 @@
'use strict';
define(require => {
/** @type {Toolbox} */
let toolbox;
const ua = navigator.userAgent;
const chromeApp = Boolean(chrome.app);
const CHROME = chromeApp && parseInt(ua.match(/Chrom\w+\/(\d+)|$/)[1]);
@ -14,14 +11,9 @@ define(require => {
// (detecting FF57 by the feature it added, not navigator.ua which may be spoofed in about:config)
const openerTabIdSupported = (!FIREFOX || window.AbortController) && chrome.windows != null;
const debounceTimers = new Map();
const {
let URLS, deepCopy, deepEqual, deepMerge;
URLS,
deepCopy,
deepEqual,
deepMerge,
} = toolbox = /** @namespace Toolbox */ {
const toolbox = {
CHROME,
FIREFOX,
@ -384,6 +376,8 @@ define(require => {
},
};
({URLS, deepCopy, deepEqual, deepMerge} = toolbox);
// see PR #781
if (!CHROME && !chrome.browserAction.openPopup) {
// in FF pre-57 legacy addons can override useragent so we assume the worst

View File

@ -5,6 +5,10 @@ define(require => {
const t = require('/js/localization');
const prefs = require('/js/prefs');
/**
* @type NewUI
* @namespace NewUI
*/
const newUI = {
enabled: null, // the global option should come first
favicons: null,
@ -14,7 +18,7 @@ define(require => {
};
// ...add utility functions
Object.assign(newUI, {
Object.assign(newUI, /** @namespace NewUI */ {
ids: Object.keys(newUI),

View File

@ -336,8 +336,6 @@ define(require => {
},
};
const {$entry} = render;
function createAgeText(el, style) {
let val = style.updateDate || style.installDate;
if (val) {
@ -359,7 +357,7 @@ define(require => {
function highlightEditedStyle() {
if (!sessionStore.justEditedStyleId) return;
const entry = $entry(sessionStore.justEditedStyleId);
const entry = render.$entry(sessionStore.justEditedStyleId);
delete sessionStore.justEditedStyleId;
if (entry) {
animateElement(entry);

View File

@ -15,26 +15,9 @@ define(require => {
const MODAL_SHOWN = 'data-display'; // attribute name
/** @type {PopupEvents} */
let exports;
const {
const Events = {
closeExplanation,
getClickedStyleElement,
getClickedStyleId,
getExcludeRule,
hideModal,
openURLandHide,
showModal,
thisTab,
} = exports = /** @namespace PopupEvents */ {
thisTab: {url: ''},
closeExplanation() {
$('#regexp-explanation').remove();
},
tabURL: '',
async configure(event) {
const {styleId, styleIsUsercss} = getClickedStyleElement(event);
@ -46,7 +29,7 @@ define(require => {
await configDialog(style);
hotkeys.setState(true);
} else {
openURLandHide.call(this, event);
Events.openURLandHide.call(this, event);
}
},
@ -68,19 +51,11 @@ define(require => {
const box = $('#confirm');
box.dataset.id = entry.styleId;
$('b', box).textContent = $('.style-name', entry).textContent;
showModal(box, '[data-cmd=cancel]');
},
getClickedStyleId(event) {
return (getClickedStyleElement(event) || {}).styleId;
},
getClickedStyleElement(event) {
return event.target.closest('.entry');
Events.showModal(box, '[data-cmd=cancel]');
},
getExcludeRule(type) {
const u = new URL(thisTab.url);
const u = new URL(Events.tabURL);
return type === 'domain'
? u.origin + '/*'
: escapeGlob(u.origin + u.pathname); // current page
@ -100,7 +75,7 @@ define(require => {
const entry = getClickedStyleElement(event);
const info = t.template.regexpProblemExplanation.cloneNode(true);
$remove('#' + info.id);
$$('a', info).forEach(el => (el.onclick = openURLandHide));
$$('a', info).forEach(el => (el.onclick = Events.openURLandHide));
$$('button', info).forEach(el => (el.onclick = closeExplanation));
entry.appendChild(info);
},
@ -109,7 +84,7 @@ define(require => {
if (!exclusions) {
return false;
}
const rule = getExcludeRule(type);
const rule = Events.getExcludeRule(type);
return exclusions.includes(rule);
},
@ -149,8 +124,8 @@ define(require => {
async openManager(event) {
event.preventDefault();
const isSearch = thisTab.url && (event.shiftKey || event.button === 2);
await API.openManage(isSearch ? {search: thisTab.url, searchMode: 'url'} : {});
const isSearch = Events.tabURL && (event.shiftKey || event.button === 2);
await API.openManage(isSearch ? {search: Events.tabURL, searchMode: 'url'} : {});
window.close();
},
@ -187,7 +162,7 @@ define(require => {
};
window.on('keydown', box._onkeydown);
moveFocus(box, 0);
hideModal(oldBox);
Events.hideModal(oldBox);
},
async toggleState(event) {
@ -200,9 +175,9 @@ define(require => {
toggleExclude(event, type) {
const entry = getClickedStyleElement(event);
if (event.target.checked) {
API.styles.addExclusion(entry.styleMeta.id, getExcludeRule(type));
API.styles.addExclusion(entry.styleMeta.id, Events.getExcludeRule(type));
} else {
API.styles.removeExclusion(entry.styleMeta.id, getExcludeRule(type));
API.styles.removeExclusion(entry.styleMeta.id, Events.getExcludeRule(type));
}
},
@ -210,17 +185,29 @@ define(require => {
const entry = getClickedStyleElement(event);
const menu = $('.menu', entry);
if (menu.hasAttribute(MODAL_SHOWN)) {
hideModal(menu, {animate: true});
Events.hideModal(menu, {animate: true});
} else {
$('.menu-title', entry).textContent = $('.style-name', entry).textContent;
showModal(menu, '.menu-close');
Events.showModal(menu, '.menu-close');
}
},
};
function closeExplanation() {
$('#regexp-explanation').remove();
}
function escapeGlob(text) {
return text.replace(/\*/g, '\\*');
}
return exports;
function getClickedStyleElement(event) {
return event.target.closest('.entry');
}
function getClickedStyleId(event) {
return (getClickedStyleElement(event) || {}).styleId;
}
return Events;
});

View File

@ -31,8 +31,7 @@ define(require => {
const ENTRY_ID_PREFIX_RAW = 'style-';
initializing.then(({frames, styles, url}) => {
tabURL = url;
Events.thisTab.url = url;
tabURL = Events.tabURL = url;
toggleUiSliders();
initPopup(frames);
if (styles[0]) {

View File

@ -444,7 +444,7 @@ define(require => {
* @returns {boolean} true if the category has actually changed
*/
function calcCategory({retry} = {}) {
const u = tryCatch(() => new URL(Events.thisTab.url));
const u = tryCatch(() => new URL(Events.tabURL));
const old = category;
if (!u) {
// Invalid URL