separate storage for order + important styles
This commit is contained in:
parent
da0918f49a
commit
8fb09f1a03
|
@ -1697,10 +1697,14 @@
|
||||||
"message": "Style injection order",
|
"message": "Style injection order",
|
||||||
"description": "Tooltip for the button in the manager to open the dialog and also the title of this dialog"
|
"description": "Tooltip for the button in the manager to open the dialog and also the title of this dialog"
|
||||||
},
|
},
|
||||||
"styleInjectionOrderHint": {
|
"styleInjectionOrderHint_main": {
|
||||||
"message": "Drag'n'drop a style to change its position. Styles are injected sequentially in the order shown below so a style further down the list can override the earlier styles.",
|
"message": "Drag'n'drop a style to change its position. Styles are injected sequentially in the order shown below so a style further down the list can override the earlier styles.",
|
||||||
"description": "Hint in the injection order dialog in the manager"
|
"description": "Hint in the injection order dialog in the manager"
|
||||||
},
|
},
|
||||||
|
"styleInjectionOrderHint_prio": {
|
||||||
|
"message": "Important styles are listed below and are always injected last so they can override any newly installed styles. Click the style's mark to toggle its importance.",
|
||||||
|
"description": "Hint at the bottom of the injection order dialog in the manager"
|
||||||
|
},
|
||||||
"styleExcludeLabel": {
|
"styleExcludeLabel": {
|
||||||
"message": "Custom excluded sites"
|
"message": "Custom excluded sites"
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,8 +9,10 @@
|
||||||
addAPI
|
addAPI
|
||||||
bgReady
|
bgReady
|
||||||
createCache
|
createCache
|
||||||
|
uuidIndex
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const uuidIndex = new Map();
|
||||||
const bgReady = {};
|
const bgReady = {};
|
||||||
bgReady.styles = new Promise(r => (bgReady._resolveStyles = r));
|
bgReady.styles = new Promise(r => (bgReady._resolveStyles = r));
|
||||||
bgReady.all = new Promise(r => (bgReady._resolveAll = r));
|
bgReady.all = new Promise(r => (bgReady._resolveAll = r));
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* global API msg */// msg.js
|
/* global API msg */// msg.js
|
||||||
/* global CHROME URLS isEmptyObj stringAsRegExp tryRegExp tryURL */// toolbox.js
|
/* global CHROME URLS deepEqual isEmptyObj mapObj stringAsRegExp tryRegExp tryURL */// toolbox.js
|
||||||
/* global bgReady createCache */// common.js
|
/* global bgReady createCache uuidIndex */// common.js
|
||||||
/* global calcStyleDigest styleCodeEmpty styleSectionGlobal */// sections-util.js
|
/* global calcStyleDigest styleCodeEmpty styleSectionGlobal */// sections-util.js
|
||||||
/* global db */
|
/* global db */
|
||||||
/* global prefs */
|
/* global prefs */
|
||||||
|
@ -20,6 +20,7 @@ to cleanup the temporary code. See livePreview in /edit.
|
||||||
|
|
||||||
const styleUtil = {};
|
const styleUtil = {};
|
||||||
|
|
||||||
|
/* exported styleMan */
|
||||||
const styleMan = (() => {
|
const styleMan = (() => {
|
||||||
|
|
||||||
Object.assign(styleUtil, {
|
Object.assign(styleUtil, {
|
||||||
|
@ -30,9 +31,9 @@ const styleMan = (() => {
|
||||||
//#region Declarations
|
//#region Declarations
|
||||||
|
|
||||||
/** @typedef {{
|
/** @typedef {{
|
||||||
style: StyleObj
|
style: StyleObj,
|
||||||
preview?: StyleObj
|
preview?: StyleObj,
|
||||||
appliesTo: Set<string>
|
appliesTo: Set<string>,
|
||||||
}} StyleMapData */
|
}} StyleMapData */
|
||||||
/** @type {Map<number,StyleMapData>} */
|
/** @type {Map<number,StyleMapData>} */
|
||||||
const dataMap = new Map();
|
const dataMap = new Map();
|
||||||
|
@ -63,9 +64,20 @@ const styleMan = (() => {
|
||||||
_rev: () => Date.now(),
|
_rev: () => Date.now(),
|
||||||
};
|
};
|
||||||
const DELETE_IF_NULL = ['id', 'customName', 'md5Url', 'originalMd5'];
|
const DELETE_IF_NULL = ['id', 'customName', 'md5Url', 'originalMd5'];
|
||||||
|
const INJ_ORDER = 'injectionOrder';
|
||||||
|
const order = {main: {}, prio: {}};
|
||||||
|
const orderForDb = {
|
||||||
|
id: INJ_ORDER,
|
||||||
|
_id: `${chrome.runtime.id}-${INJ_ORDER}`,
|
||||||
|
get value() {
|
||||||
|
return mapObj(order, group => sortObjectKeysByValue(group, id2uuid));
|
||||||
|
},
|
||||||
|
set value(val) {
|
||||||
|
setOrderFromArray(val);
|
||||||
|
},
|
||||||
|
};
|
||||||
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */
|
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */
|
||||||
let ready = init();
|
let ready = init();
|
||||||
let order = {};
|
|
||||||
|
|
||||||
chrome.runtime.onConnect.addListener(port => {
|
chrome.runtime.onConnect.addListener(port => {
|
||||||
if (port.name === 'livePreview') {
|
if (port.name === 'livePreview') {
|
||||||
|
@ -83,18 +95,6 @@ const styleMan = (() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: will fix in subsequent commit
|
|
||||||
// prefs.subscribe(['injectionOrder'], (key, value) => {
|
|
||||||
// order = {};
|
|
||||||
// value.forEach((uid, i) => {
|
|
||||||
// const id = uuidIndex.get(uid);
|
|
||||||
// if (id) {
|
|
||||||
// order[id] = i;
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// msg.broadcast({method: 'styleSort', order});
|
|
||||||
// });
|
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
//#region Exports
|
//#region Exports
|
||||||
|
|
||||||
|
@ -103,17 +103,17 @@ const styleMan = (() => {
|
||||||
/** @returns {Promise<number>} style id */
|
/** @returns {Promise<number>} style id */
|
||||||
async delete(id, reason) {
|
async delete(id, reason) {
|
||||||
if (ready.then) await ready;
|
if (ready.then) await ready;
|
||||||
const data = id2data(id);
|
const {style, appliesTo} = dataMap.get(id);
|
||||||
const {style, appliesTo} = data;
|
const sync = reason !== 'sync';
|
||||||
db.styles.delete(id);
|
db.styles.delete(id);
|
||||||
if (reason !== 'sync') {
|
if (sync) API.sync.delete(style._id, Date.now());
|
||||||
API.sync.delete(style._id, Date.now());
|
|
||||||
}
|
|
||||||
for (const url of appliesTo) {
|
for (const url of appliesTo) {
|
||||||
const cache = cachedStyleForUrl.get(url);
|
const cache = cachedStyleForUrl.get(url);
|
||||||
if (cache) delete cache.sections[id];
|
if (cache) delete cache.sections[id];
|
||||||
}
|
}
|
||||||
dataMap.delete(id);
|
dataMap.delete(id);
|
||||||
|
mapObj(order, val => delete val[id]);
|
||||||
|
setOrder(orderForDb.value, {sync});
|
||||||
if (style._usw && style._usw.token) {
|
if (style._usw && style._usw.token) {
|
||||||
// Must be called after the style is deleted from dataMap
|
// Must be called after the style is deleted from dataMap
|
||||||
API.usw.revoke(id);
|
API.usw.revoke(id);
|
||||||
|
@ -149,7 +149,21 @@ const styleMan = (() => {
|
||||||
/** @returns {Promise<StyleObj[]>} */
|
/** @returns {Promise<StyleObj[]>} */
|
||||||
async getAll() {
|
async getAll() {
|
||||||
if (ready.then) await ready;
|
if (ready.then) await ready;
|
||||||
return Array.from(dataMap.values(), data2style);
|
return getAllAsArray();
|
||||||
|
},
|
||||||
|
|
||||||
|
/** @returns {Promise<Object<string,StyleObj[]>>}>} */
|
||||||
|
async getAllOrdered() {
|
||||||
|
if (ready.then) await ready;
|
||||||
|
const res = mapObj(order, group => sortObjectKeysByValue(group, id2style));
|
||||||
|
if (res.main.length + res.prio.length < dataMap.size) {
|
||||||
|
for (const {style} of dataMap.values()) {
|
||||||
|
if (!(style.id in order.main) && !(style.id in order.prio)) {
|
||||||
|
res.main.push(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
/** @returns {Promise<StyleSectionsToApply>} */
|
/** @returns {Promise<StyleSectionsToApply>} */
|
||||||
|
@ -199,7 +213,7 @@ const styleMan = (() => {
|
||||||
const result = [];
|
const result = [];
|
||||||
const styles = id
|
const styles = id
|
||||||
? [id2style(id)].filter(Boolean)
|
? [id2style(id)].filter(Boolean)
|
||||||
: Array.from(dataMap.values(), data2style);
|
: getAllAsArray();
|
||||||
const query = createMatchQuery(url);
|
const query = createMatchQuery(url);
|
||||||
for (const style of styles) {
|
for (const style of styles) {
|
||||||
let excluded = false;
|
let excluded = false;
|
||||||
|
@ -269,6 +283,13 @@ const styleMan = (() => {
|
||||||
|
|
||||||
save: saveStyle,
|
save: saveStyle,
|
||||||
|
|
||||||
|
async setOrder(val) {
|
||||||
|
if (ready.then) await ready;
|
||||||
|
return val &&
|
||||||
|
!deepEqual(val, order) &&
|
||||||
|
setOrder(val, {broadcast: true, sync: true});
|
||||||
|
},
|
||||||
|
|
||||||
/** @returns {Promise<number>} style id */
|
/** @returns {Promise<number>} style id */
|
||||||
async toggle(id, enabled) {
|
async toggle(id, enabled) {
|
||||||
if (ready.then) await ready;
|
if (ready.then) await ready;
|
||||||
|
@ -306,12 +327,12 @@ const styleMan = (() => {
|
||||||
|
|
||||||
/** @returns {?StyleObj} */
|
/** @returns {?StyleObj} */
|
||||||
function id2style(id) {
|
function id2style(id) {
|
||||||
return (dataMap.get(id) || {}).style;
|
return (dataMap.get(Number(id)) || {}).style;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {?StyleObj} */
|
/** @returns {?string} */
|
||||||
function data2style(data) {
|
function id2uuid(id) {
|
||||||
return data && data.style;
|
return (id2style(id) || {})._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {StyleObj} */
|
/** @returns {StyleObj} */
|
||||||
|
@ -332,6 +353,7 @@ const styleMan = (() => {
|
||||||
style,
|
style,
|
||||||
appliesTo: new Set(),
|
appliesTo: new Set(),
|
||||||
});
|
});
|
||||||
|
uuidIndex.set(style._id, style.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {StyleObj} */
|
/** @returns {StyleObj} */
|
||||||
|
@ -451,7 +473,7 @@ const styleMan = (() => {
|
||||||
data.style = style;
|
data.style = style;
|
||||||
}
|
}
|
||||||
if (reason !== 'sync') {
|
if (reason !== 'sync') {
|
||||||
API.sync.putStyle(style);
|
API.sync.putDoc(style);
|
||||||
}
|
}
|
||||||
if (broadcast) broadcastStyleUpdated(style, reason, method);
|
if (broadcast) broadcastStyleUpdated(style, reason, method);
|
||||||
return style;
|
return style;
|
||||||
|
@ -477,12 +499,18 @@ const styleMan = (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
|
const orderPromise = db.open(prefs.STORAGE_KEY).get(INJ_ORDER);
|
||||||
const styles = await db.styles.getAll() || [];
|
const styles = await db.styles.getAll() || [];
|
||||||
const updated = await Promise.all(styles.map(fixKnownProblems).filter(Boolean));
|
const updated = await Promise.all(styles.map(fixKnownProblems).filter(Boolean));
|
||||||
if (updated.length) {
|
if (updated.length) {
|
||||||
await db.styles.putMany(updated);
|
await db.styles.putMany(updated);
|
||||||
}
|
}
|
||||||
styles.forEach(storeInMap);
|
styles.forEach(storeInMap);
|
||||||
|
Object.assign(orderForDb, await orderPromise);
|
||||||
|
API.sync.registerDoc(orderForDb, doc => {
|
||||||
|
Object.assign(orderForDb, doc);
|
||||||
|
setOrder();
|
||||||
|
});
|
||||||
ready = true;
|
ready = true;
|
||||||
bgReady._resolveStyles();
|
bgReady._resolveStyles();
|
||||||
}
|
}
|
||||||
|
@ -680,10 +708,40 @@ const styleMan = (() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {StyleObj[]} */
|
||||||
|
function getAllAsArray() {
|
||||||
|
return Array.from(dataMap.values(), v => v.style);
|
||||||
|
}
|
||||||
|
|
||||||
/** uuidv4 helper: converts to a 4-digit hex string and adds "-" at required positions */
|
/** uuidv4 helper: converts to a 4-digit hex string and adds "-" at required positions */
|
||||||
function hex4dashed(num, i) {
|
function hex4dashed(num, i) {
|
||||||
return (num + 0x10000).toString(16).slice(-4) + (i >= 1 && i <= 4 ? '-' : '');
|
return (num + 0x10000).toString(16).slice(-4) + (i >= 1 && i <= 4 ? '-' : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setOrder(val, {broadcast, store = true, sync} = {}) {
|
||||||
|
if (val) {
|
||||||
|
setOrderFromArray(val);
|
||||||
|
orderForDb._rev = Date.now();
|
||||||
|
}
|
||||||
|
if (broadcast) msg.broadcast({method: 'styleSort', order});
|
||||||
|
if (store) await db.open(prefs.STORAGE_KEY).put(orderForDb);
|
||||||
|
if (sync) API.sync.putDoc(orderForDb);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setOrderFromArray(newOrder) {
|
||||||
|
mapObj(order, (_, type) => {
|
||||||
|
const res = order[type] = {};
|
||||||
|
(newOrder && newOrder[type] || []).forEach((uid, i) => {
|
||||||
|
const id = uuidIndex.get(uid);
|
||||||
|
if (id) res[id] = i;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Since JS object's numeric keys are sorted in ascending order, we have to re-sort by value */
|
||||||
|
function sortObjectKeysByValue(obj, map) {
|
||||||
|
return Object.entries(obj).sort((a, b) => a[1] - b[1]).map(e => map(e[0]));
|
||||||
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
/* global API msg */// msg.js
|
/* global API msg */// msg.js
|
||||||
|
/* global uuidIndex */// common.js
|
||||||
/* global chromeLocal chromeSync */// storage-util.js
|
/* global chromeLocal chromeSync */// storage-util.js
|
||||||
/* global db */
|
/* global db */
|
||||||
/* global iconMan */
|
/* global iconMan */
|
||||||
|
@ -29,17 +30,13 @@ const syncMan = (() => {
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
login: false,
|
login: false,
|
||||||
};
|
};
|
||||||
const uuidIndex = new Map();
|
const customDocs = {};
|
||||||
const uuid2style = uuid => styleUtil.id2style(uuidIndex.get(uuid));
|
|
||||||
const compareRevision = (rev1, rev2) => rev1 - rev2;
|
const compareRevision = (rev1, rev2) => rev1 - rev2;
|
||||||
let lastError = null;
|
let lastError = null;
|
||||||
let ctrl;
|
let ctrl;
|
||||||
let currentDrive;
|
let currentDrive;
|
||||||
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */
|
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */
|
||||||
let ready = prefs.ready.then(async () => {
|
let ready = prefs.ready.then(async () => {
|
||||||
for (const {id, _id} of await API.styles.getAll()) {
|
|
||||||
uuidIndex.set(_id, id);
|
|
||||||
}
|
|
||||||
ready = true;
|
ready = true;
|
||||||
prefs.subscribe('sync.enabled',
|
prefs.subscribe('sync.enabled',
|
||||||
(_, val) => val === 'none'
|
(_, val) => val === 'none'
|
||||||
|
@ -87,16 +84,20 @@ const syncMan = (() => {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async put(...args) {
|
async putDoc({id, _id, _rev}) {
|
||||||
if (ready.then) await ready;
|
if (ready.then) await ready;
|
||||||
|
uuidIndex.set(_id, id);
|
||||||
if (!currentDrive) return;
|
if (!currentDrive) return;
|
||||||
schedule();
|
schedule();
|
||||||
return ctrl.put(...args);
|
return ctrl.put(_id, _rev);
|
||||||
},
|
},
|
||||||
|
|
||||||
putStyle({id, _id, _rev}) {
|
registerDoc(doc, setter) {
|
||||||
uuidIndex.set(_id, id);
|
uuidIndex.set(doc._id, doc.id);
|
||||||
return syncMan.put(_id, _rev);
|
Object.defineProperty(customDocs, doc.id, {
|
||||||
|
get: () => doc,
|
||||||
|
set: setter,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async setDriveOptions(driveName, options) {
|
async setDriveOptions(driveName, options) {
|
||||||
|
@ -191,10 +192,13 @@ const syncMan = (() => {
|
||||||
async function initController() {
|
async function initController() {
|
||||||
await require(['/vendor/db-to-cloud/db-to-cloud.min']); /* global dbToCloud */
|
await require(['/vendor/db-to-cloud/db-to-cloud.min']); /* global dbToCloud */
|
||||||
ctrl = dbToCloud.dbToCloud({
|
ctrl = dbToCloud.dbToCloud({
|
||||||
onGet: uuid2style,
|
onGet(uuid) {
|
||||||
|
return styleUtil.id2style(uuidIndex.get(uuid));
|
||||||
|
},
|
||||||
async onPut(doc) {
|
async onPut(doc) {
|
||||||
const id = uuidIndex.get(doc._id);
|
const id = uuidIndex.get(doc._id);
|
||||||
const oldDoc = uuid2style(doc._id);
|
const style = styleUtil.id2style(id);
|
||||||
|
const oldDoc = style || customDocs[id];
|
||||||
if (id) {
|
if (id) {
|
||||||
doc.id = id;
|
doc.id = id;
|
||||||
} else {
|
} else {
|
||||||
|
@ -204,27 +208,30 @@ const syncMan = (() => {
|
||||||
if (oldDoc) {
|
if (oldDoc) {
|
||||||
diff = compareRevision(oldDoc._rev, doc._rev);
|
diff = compareRevision(oldDoc._rev, doc._rev);
|
||||||
if (diff > 0) {
|
if (diff > 0) {
|
||||||
syncMan.put(oldDoc);
|
syncMan.putDoc(oldDoc);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (diff < 0) {
|
if (diff >= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (style) {
|
||||||
doc.id = await db.styles.put(doc);
|
doc.id = await db.styles.put(doc);
|
||||||
uuidIndex.set(doc._id, doc.id);
|
uuidIndex.set(doc._id, doc.id);
|
||||||
return styleUtil.handleSave(doc, {reason: 'sync'});
|
return styleUtil.handleSave(doc, {reason: 'sync'});
|
||||||
}
|
}
|
||||||
|
if (oldDoc) oldDoc[id] = doc;
|
||||||
},
|
},
|
||||||
onDelete(_id, rev) {
|
onDelete(_id, rev) {
|
||||||
const id = uuidIndex.get(_id);
|
const id = uuidIndex.get(_id);
|
||||||
const oldDoc = uuid2style(_id);
|
const oldDoc = styleUtil.id2style(id);
|
||||||
if (oldDoc && compareRevision(oldDoc._rev, rev) <= 0) {
|
if (oldDoc && compareRevision(oldDoc._rev, rev) <= 0) {
|
||||||
// FIXME: does it make sense to set reason to 'sync' in deleteByUUID?
|
|
||||||
uuidIndex.delete(id);
|
uuidIndex.delete(id);
|
||||||
return API.styles.delete(id, 'sync');
|
return API.styles.delete(id, 'sync');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onFirstSync() {
|
async onFirstSync() {
|
||||||
for (const i of await API.styles.getAll()) {
|
for (const i of Object.values(customDocs).concat(await API.styles.getAll())) {
|
||||||
ctrl.put(i._id, i._rev);
|
ctrl.put(i._id, i._rev);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,19 +13,16 @@
|
||||||
let hasStyles = false;
|
let hasStyles = false;
|
||||||
let isDisabled = false;
|
let isDisabled = false;
|
||||||
let isTab = !chrome.tabs || location.pathname !== '/popup.html';
|
let isTab = !chrome.tabs || location.pathname !== '/popup.html';
|
||||||
let order = {};
|
const order = {main: [], prio: []};
|
||||||
|
const calcOrder = ({id}) =>
|
||||||
|
(order.prio[id] || 0) * 1e6 ||
|
||||||
|
order.main[id] ||
|
||||||
|
id + .5e6; // no order = at the end of `main`
|
||||||
const isFrame = window !== parent;
|
const isFrame = window !== parent;
|
||||||
const isFrameAboutBlank = isFrame && location.href === 'about:blank';
|
const isFrameAboutBlank = isFrame && location.href === 'about:blank';
|
||||||
const isUnstylable = !chrome.app && document instanceof XMLDocument;
|
const isUnstylable = !chrome.app && document instanceof XMLDocument;
|
||||||
const styleInjector = StyleInjector({
|
const styleInjector = StyleInjector({
|
||||||
compare: (a, b) => {
|
compare: (a, b) => calcOrder(a) - calcOrder(b),
|
||||||
const ia = order[a.id];
|
|
||||||
const ib = order[b.id];
|
|
||||||
if (ia === ib) return 0;
|
|
||||||
if (ia == null) return 1;
|
|
||||||
if (ib == null) return -1;
|
|
||||||
return ia - ib;
|
|
||||||
},
|
|
||||||
onUpdate: onInjectorUpdate,
|
onUpdate: onInjectorUpdate,
|
||||||
});
|
});
|
||||||
// dynamic iframes don't have a URL yet so we'll use their parent's URL (hash isn't inherited)
|
// dynamic iframes don't have a URL yet so we'll use their parent's URL (hash isn't inherited)
|
||||||
|
@ -110,7 +107,7 @@
|
||||||
await API.styles.getSectionsByUrl(matchUrl, null, true);
|
await API.styles.getSectionsByUrl(matchUrl, null, true);
|
||||||
if (styles.cfg) {
|
if (styles.cfg) {
|
||||||
isDisabled = styles.cfg.disableAll;
|
isDisabled = styles.cfg.disableAll;
|
||||||
order = styles.cfg.order || {};
|
Object.assign(order, styles.cfg.order);
|
||||||
delete styles.cfg;
|
delete styles.cfg;
|
||||||
}
|
}
|
||||||
hasStyles = !isDisabled;
|
hasStyles = !isDisabled;
|
||||||
|
@ -179,7 +176,7 @@
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'styleSort':
|
case 'styleSort':
|
||||||
order = request.order;
|
Object.assign(order, request.order);
|
||||||
styleInjector.sort();
|
styleInjector.sort();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
#message-box.injection-order > div {
|
.injection-order > div {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
}
|
}
|
||||||
#message-box.injection-order #message-box-contents {
|
.injection-order #message-box-contents,
|
||||||
|
.injection-order section {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
--border: 1px solid rgba(128, 128, 128, .25);
|
}
|
||||||
|
.injection-order section[data-main] {
|
||||||
|
flex: 1 0;
|
||||||
}
|
}
|
||||||
.injection-order header {
|
.injection-order header {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
@ -16,39 +19,81 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.injection-order ol {
|
.injection-order ol {
|
||||||
height: 100%;
|
padding: 1px 0; /* 1px for keyboard-focused element's outline */
|
||||||
padding: 1px 0 0; /* 1px for keyboard-focused element's outline */
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border-top: var(--border);
|
|
||||||
}
|
}
|
||||||
.injection-order a {
|
.injection-order ol:empty {
|
||||||
display: block;
|
display: none;
|
||||||
|
}
|
||||||
|
.injection-order [data-prio] header {
|
||||||
|
background-color: hsla(40, 80%, 50%, 0.4);
|
||||||
|
}
|
||||||
|
.injection-order [data-prio] {
|
||||||
|
height: min-content;
|
||||||
|
min-height: 2em;
|
||||||
|
max-height: 50%;
|
||||||
|
}
|
||||||
|
.injection-order-entry {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
color: #000;
|
color: #000;
|
||||||
text-decoration: none;
|
|
||||||
transition: transform .25s ease-in-out;
|
transition: transform .25s ease-in-out;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
padding: 0.3em .5em;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
}
|
}
|
||||||
.injection-order a.enabled {
|
.injection-order-entry a[href] {
|
||||||
|
padding: .4em 0 .4em 1rem;
|
||||||
|
cursor: inherit;
|
||||||
|
}
|
||||||
|
.injection-order-entry.enabled a[href] {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.injection-order a:hover {
|
.injection-order-entry a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.injection-order-entry a[href]:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
.injection-order a:not(:first-child) {
|
.injection-order-toggle {
|
||||||
border-top: var(--border);
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 .5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: .33;
|
||||||
|
transition: .2s;
|
||||||
}
|
}
|
||||||
.injection-order a::before {
|
.injection-order-toggle::after {
|
||||||
content: "\2261";
|
content: '';
|
||||||
padding: 0.6em;
|
width: .75em;
|
||||||
font-weight: normal;
|
height: .75em;
|
||||||
|
border-radius: 100%;
|
||||||
|
border: 2px solid currentColor;
|
||||||
|
}
|
||||||
|
.injection-order-entry:hover .injection-order-toggle {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
[data-prio] .injection-order-toggle::after {
|
||||||
|
background-color: currentColor;
|
||||||
|
background-clip: content-box;
|
||||||
|
width: .5em;
|
||||||
|
height: .5em;
|
||||||
|
padding: 2px;
|
||||||
|
transition: .2s;
|
||||||
|
}
|
||||||
|
.injection-order-toggle:hover::after {
|
||||||
|
background-color: hsl(40, 80%, 50%);
|
||||||
|
}
|
||||||
|
.injection-order [data-prio] header,
|
||||||
|
.injection-order ol,
|
||||||
|
.injection-order #message-box-buttons,
|
||||||
|
.injection-order-entry:nth-child(n + 2) {
|
||||||
|
border-top: 1px solid rgba(128, 128, 128, .25);
|
||||||
}
|
}
|
||||||
.injection-order .draggable-list-target {
|
.injection-order .draggable-list-target {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/* global $create messageBoxProxy */// dom.js
|
/* global $create messageBoxProxy */// dom.js
|
||||||
/* global API */// msg.js
|
/* global API */// msg.js
|
||||||
/* global DraggableList */
|
/* global DraggableList */
|
||||||
/* global prefs */
|
|
||||||
/* global t */// localization.js
|
/* global t */// localization.js
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -10,70 +9,68 @@ async function InjectionOrder(show = true) {
|
||||||
if (!show) {
|
if (!show) {
|
||||||
return messageBoxProxy.close();
|
return messageBoxProxy.close();
|
||||||
}
|
}
|
||||||
const entries = (await getOrderedStyles()).map(makeEntry);
|
const SEL_ENTRY = '.injection-order-entry';
|
||||||
const ol = $create('ol');
|
const groups = await API.styles.getAllOrdered();
|
||||||
let maxTranslateY;
|
const ols = {};
|
||||||
ol.append(...entries.map(l => l.el));
|
|
||||||
ol.on('d:dragstart', ({detail: d}) => {
|
|
||||||
d.origin.dataTransfer.setDragImage(new Image(), 0, 0);
|
|
||||||
maxTranslateY = ol.scrollHeight + ol.offsetTop - d.dragTarget.offsetHeight - d.dragTarget.offsetTop;
|
|
||||||
});
|
|
||||||
ol.on('d:dragmove', ({detail: d}) => {
|
|
||||||
d.origin.stopPropagation(); // preserves dropEffect
|
|
||||||
d.origin.dataTransfer.dropEffect = 'move';
|
|
||||||
const y = Math.min(d.currentPos.y - d.startPos.y, maxTranslateY);
|
|
||||||
d.dragTarget.style.transform = `translateY(${y}px)`;
|
|
||||||
});
|
|
||||||
ol.on('d:dragend', ({detail: d}) => {
|
|
||||||
const [item] = entries.splice(d.originalIndex, 1);
|
|
||||||
entries.splice(d.spliceIndex, 0, item);
|
|
||||||
ol.insertBefore(d.dragTarget, d.insertBefore);
|
|
||||||
prefs.set('injectionOrder', entries.map(l => l.style._id));
|
|
||||||
});
|
|
||||||
DraggableList(ol, {scrollContainer: ol});
|
|
||||||
|
|
||||||
await messageBoxProxy.show({
|
await messageBoxProxy.show({
|
||||||
title: t('styleInjectionOrder'),
|
title: t('styleInjectionOrder'),
|
||||||
contents: $create('fragment', [
|
contents: $create('fragment', Object.entries(groups).map(makeList)),
|
||||||
$create('header', t('styleInjectionOrderHint')),
|
|
||||||
ol,
|
|
||||||
]),
|
|
||||||
className: 'injection-order center-dialog',
|
className: 'injection-order center-dialog',
|
||||||
blockScroll: true,
|
blockScroll: true,
|
||||||
buttons: [t('confirmClose')],
|
buttons: [t('confirmClose')],
|
||||||
});
|
});
|
||||||
|
|
||||||
async function getOrderedStyles() {
|
|
||||||
const [styles] = await Promise.all([
|
|
||||||
API.styles.getAll(),
|
|
||||||
prefs.ready,
|
|
||||||
]);
|
|
||||||
const styleSet = new Set(styles);
|
|
||||||
const uuidIndex = new Map();
|
|
||||||
for (const s of styleSet) {
|
|
||||||
uuidIndex.set(s._id, s);
|
|
||||||
}
|
|
||||||
const orderedStyles = [];
|
|
||||||
for (const uid of prefs.get('injectionOrder')) {
|
|
||||||
const s = uuidIndex.get(uid);
|
|
||||||
if (s) {
|
|
||||||
uuidIndex.delete(uid);
|
|
||||||
orderedStyles.push(s);
|
|
||||||
styleSet.delete(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
orderedStyles.push(...styleSet);
|
|
||||||
return orderedStyles;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeEntry(style) {
|
function makeEntry(style) {
|
||||||
return {
|
return $create('li' + SEL_ENTRY + (style.enabled ? '.enabled' : ''), [
|
||||||
style,
|
$create('a', {
|
||||||
el: $create('a', {
|
|
||||||
className: style.enabled ? 'enabled' : '',
|
|
||||||
href: '/edit.html?id=' + style.id,
|
href: '/edit.html?id=' + style.id,
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
|
draggable: false,
|
||||||
}, style.name),
|
}, style.name),
|
||||||
};
|
$create('a.injection-order-toggle', {
|
||||||
|
tabIndex: 0,
|
||||||
|
draggable: false,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeList([type, styles]) {
|
||||||
|
const ids = groups[type] = styles.map(s => s._id);
|
||||||
|
const ol = ols[type] = $create('ol');
|
||||||
|
let maxTranslateY;
|
||||||
|
ol.append(...styles.map(makeEntry));
|
||||||
|
ol.on('d:dragstart', ({detail: d}) => {
|
||||||
|
d.origin.dataTransfer.setDragImage(new Image(), 0, 0);
|
||||||
|
maxTranslateY =
|
||||||
|
ol.scrollHeight + ol.offsetTop - d.dragTarget.offsetHeight - d.dragTarget.offsetTop;
|
||||||
|
});
|
||||||
|
ol.on('d:dragmove', ({detail: d}) => {
|
||||||
|
d.origin.stopPropagation(); // preserves dropEffect
|
||||||
|
d.origin.dataTransfer.dropEffect = 'move';
|
||||||
|
const y = Math.min(d.currentPos.y - d.startPos.y, maxTranslateY);
|
||||||
|
d.dragTarget.style.transform = `translateY(${y}px)`;
|
||||||
|
});
|
||||||
|
ol.on('d:dragend', ({detail: d}) => {
|
||||||
|
const [item] = ids.splice(d.originalIndex, 1);
|
||||||
|
ids.splice(d.spliceIndex, 0, item);
|
||||||
|
ol.insertBefore(d.dragTarget, d.insertBefore);
|
||||||
|
API.styles.setOrder(groups);
|
||||||
|
});
|
||||||
|
ol.on('click', e => {
|
||||||
|
if (e.target.closest('.injection-order-toggle')) {
|
||||||
|
const el = e.target.closest(SEL_ENTRY);
|
||||||
|
const i = [].indexOf.call(el.parentNode.children, el);
|
||||||
|
const [item] = ids.splice(i, 1);
|
||||||
|
const type2 = type === 'main' ? 'prio' : 'main';
|
||||||
|
groups[type2].push(item);
|
||||||
|
ols[type2].appendChild(el);
|
||||||
|
API.styles.setOrder(groups);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
DraggableList(ol, {scrollContainer: ol});
|
||||||
|
return $create('section', {dataset: {[type]: ''}}, [
|
||||||
|
$create('header', t('styleInjectionOrderHint_' + type)),
|
||||||
|
ol,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,8 +134,6 @@
|
||||||
'popupWidth': 246, // popup width in pixels
|
'popupWidth': 246, // popup width in pixels
|
||||||
|
|
||||||
'updateInterval': 24, // user-style automatic update interval, hours (0 = disable)
|
'updateInterval': 24, // user-style automatic update interval, hours (0 = disable)
|
||||||
|
|
||||||
'injectionOrder': [],
|
|
||||||
};
|
};
|
||||||
const knownKeys = Object.keys(defaults);
|
const knownKeys = Object.keys(defaults);
|
||||||
/** @type {PrefsValues} */
|
/** @type {PrefsValues} */
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
getTab
|
getTab
|
||||||
ignoreChromeError
|
ignoreChromeError
|
||||||
isEmptyObj
|
isEmptyObj
|
||||||
|
mapObj
|
||||||
onTabReady
|
onTabReady
|
||||||
openURL
|
openURL
|
||||||
sessionStore
|
sessionStore
|
||||||
|
@ -272,6 +273,24 @@ function isEmptyObj(obj) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {?Object} obj
|
||||||
|
* @param {function(val:?, key:string, obj:Object):T} [fn]
|
||||||
|
* @param {string[]} [keys]
|
||||||
|
* @returns {?Object<string,T>}
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
|
function mapObj(obj, fn, keys) {
|
||||||
|
if (!obj) return obj;
|
||||||
|
const res = {};
|
||||||
|
for (const k of keys || Object.keys(obj)) {
|
||||||
|
if (!keys || k in obj) {
|
||||||
|
res[k] = fn ? fn(obj[k], k, obj) : obj[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* js engine can't optimize the entire function if it contains try-catch
|
* js engine can't optimize the entire function if it contains try-catch
|
||||||
* so we should keep it isolated from normal code in a minimal wrapper
|
* so we should keep it isolated from normal code in a minimal wrapper
|
||||||
|
|
Loading…
Reference in New Issue
Block a user