2020-06-22 16:14:41 +00:00
|
|
|
/* global CodeMirror $create prefs */
|
|
|
|
/* exported dirtyReporter memoize clipString sectionsToMozFormat createHotkeyInput */
|
2017-09-11 16:09:25 +00:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
function dirtyReporter() {
|
|
|
|
const dirty = new Map();
|
|
|
|
const onchanges = [];
|
|
|
|
|
|
|
|
function add(obj, value) {
|
|
|
|
const saved = dirty.get(obj);
|
|
|
|
if (!saved) {
|
|
|
|
dirty.set(obj, {type: 'add', newValue: value});
|
|
|
|
} else if (saved.type === 'remove') {
|
|
|
|
if (saved.savedValue === value) {
|
|
|
|
dirty.delete(obj);
|
|
|
|
} else {
|
|
|
|
saved.newValue = value;
|
|
|
|
saved.type = 'modify';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function remove(obj, value) {
|
|
|
|
const saved = dirty.get(obj);
|
|
|
|
if (!saved) {
|
|
|
|
dirty.set(obj, {type: 'remove', savedValue: value});
|
|
|
|
} else if (saved.type === 'add') {
|
|
|
|
dirty.delete(obj);
|
|
|
|
} else if (saved.type === 'modify') {
|
|
|
|
saved.type = 'remove';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function modify(obj, oldValue, newValue) {
|
|
|
|
const saved = dirty.get(obj);
|
|
|
|
if (!saved) {
|
|
|
|
if (oldValue !== newValue) {
|
|
|
|
dirty.set(obj, {type: 'modify', savedValue: oldValue, newValue});
|
|
|
|
}
|
|
|
|
} else if (saved.type === 'modify') {
|
|
|
|
if (saved.savedValue === newValue) {
|
|
|
|
dirty.delete(obj);
|
|
|
|
} else {
|
|
|
|
saved.newValue = newValue;
|
|
|
|
}
|
|
|
|
} else if (saved.type === 'add') {
|
|
|
|
saved.newValue = newValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-16 08:05:41 +00:00
|
|
|
function clear(obj) {
|
|
|
|
if (obj === undefined) {
|
|
|
|
dirty.clear();
|
|
|
|
} else {
|
|
|
|
dirty.delete(obj);
|
|
|
|
}
|
2017-09-11 16:09:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function isDirty() {
|
|
|
|
return dirty.size > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
function onChange(cb) {
|
2017-09-12 13:28:26 +00:00
|
|
|
// make sure the callback doesn't throw
|
2017-09-11 16:09:25 +00:00
|
|
|
onchanges.push(cb);
|
|
|
|
}
|
|
|
|
|
|
|
|
function wrap(obj) {
|
|
|
|
for (const key of ['add', 'remove', 'modify', 'clear']) {
|
|
|
|
obj[key] = trackChange(obj[key]);
|
|
|
|
}
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
function emitChange() {
|
|
|
|
for (const cb of onchanges) {
|
2017-09-12 13:28:26 +00:00
|
|
|
cb();
|
2017-09-11 16:09:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function trackChange(fn) {
|
|
|
|
return function () {
|
|
|
|
const dirty = isDirty();
|
|
|
|
const result = fn.apply(null, arguments);
|
|
|
|
if (dirty !== isDirty()) {
|
|
|
|
emitChange();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-09-11 17:23:32 +00:00
|
|
|
function has(key) {
|
|
|
|
return dirty.has(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
return wrap({add, remove, modify, clear, isDirty, onChange, has});
|
2017-09-11 16:09:25 +00:00
|
|
|
}
|
2017-12-26 20:39:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
function sectionsToMozFormat(style) {
|
|
|
|
const propertyToCss = {
|
|
|
|
urls: 'url',
|
|
|
|
urlPrefixes: 'url-prefix',
|
|
|
|
domains: 'domain',
|
|
|
|
regexps: 'regexp',
|
|
|
|
};
|
|
|
|
return style.sections.map(section => {
|
|
|
|
let cssMds = [];
|
|
|
|
for (const i in propertyToCss) {
|
|
|
|
if (section[i]) {
|
|
|
|
cssMds = cssMds.concat(section[i].map(v =>
|
|
|
|
propertyToCss[i] + '("' + v.replace(/\\/g, '\\\\') + '")'
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return cssMds.length ?
|
|
|
|
'@-moz-document ' + cssMds.join(', ') + ' {\n' + section.code + '\n}' :
|
|
|
|
section.code;
|
|
|
|
}).join('\n\n');
|
|
|
|
}
|
2018-07-22 08:59:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
function clipString(str, limit = 100) {
|
|
|
|
return str.length <= limit ? str : str.substr(0, limit) + '...';
|
|
|
|
}
|
2018-10-01 14:03:17 +00:00
|
|
|
|
|
|
|
// this is a decorator. Cache the first call
|
|
|
|
function memoize(fn) {
|
|
|
|
let cached = false;
|
|
|
|
let result;
|
|
|
|
return (...args) => {
|
|
|
|
if (!cached) {
|
|
|
|
result = fn(...args);
|
|
|
|
cached = true;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
}
|
2020-06-22 16:14:41 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!string} prefId
|
|
|
|
* @param {?function(isEnter:boolean)} onDone
|
|
|
|
*/
|
|
|
|
function createHotkeyInput(prefId, onDone = () => {}) {
|
|
|
|
return $create('input', {
|
|
|
|
type: 'search',
|
|
|
|
spellcheck: false,
|
|
|
|
value: prefs.get(prefId),
|
|
|
|
onkeydown(event) {
|
|
|
|
const key = CodeMirror.keyName(event);
|
|
|
|
if (key === 'Tab' || key === 'Shift-Tab') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
|
|
|
switch (key) {
|
|
|
|
case 'Enter':
|
|
|
|
if (this.checkValidity()) onDone(true);
|
|
|
|
return;
|
|
|
|
case 'Esc':
|
|
|
|
onDone(false);
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
// disallow: [Shift?] characters, modifiers-only, [modifiers?] + Esc, Tab, nav keys
|
|
|
|
if (!key || new RegExp('^(' + [
|
|
|
|
'(Back)?Space',
|
|
|
|
'(Shift-)?.', // a single character
|
|
|
|
'(Shift-?|Ctrl-?|Alt-?|Cmd-?){0,2}(|Esc|Tab|(Page)?(Up|Down)|Left|Right|Home|End|Insert|Delete)',
|
|
|
|
].join('|') + ')$', 'i').test(key)) {
|
|
|
|
this.value = key || this.value;
|
|
|
|
this.setCustomValidity('Not allowed');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.value = key;
|
|
|
|
this.setCustomValidity('');
|
|
|
|
prefs.set(prefId, key);
|
|
|
|
},
|
|
|
|
oninput() {
|
|
|
|
// fired on pressing "x" to clear the field
|
|
|
|
prefs.set(prefId, '');
|
|
|
|
},
|
|
|
|
onpaste(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|