stylus/edit/util.js

188 lines
4.4 KiB
JavaScript
Raw Normal View History

/* 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;
}
}
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
}
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');
}
function clipString(str, limit = 100) {
return str.length <= limit ? str : str.substr(0, limit) + '...';
}
Rewrite linter system (#487) * Add: implement new linter system * Refactor: pull out editor worker * Switch to new linter and worker * Enable eslint cache * Fix: undefined error * Windows compatibility * Fix: refresh linter if the editor.linter changes * Add: stylelint * Add: getStylelintRules, getCsslintRules * Fix: logic to get correct linter * WIP: linter-report * Fix: toggle hidden state * Add: matain the order of lint report for section editor * Add: unhook event * Add: gotoLintIssue * Fix: shouldn't delete rule.init * Add: linter-help-dialog * Drop linterConfig * Add: linter-config-dialog, cacheFn * Add: use cacheFn * Drop lint.js * Add: refresh. Fix report order * Fix: hide empty table * Add: updateCount. Fix table caption * Switch to new linter/worker * Fix: remove unneeded comment * Fix: cacheFn -> cacheFirstCall * Fix: use cacheFirstCall * Fix: cache metaIndex * Fix: i < trs.length * Fix: drop isEmpty * Fix: expose some simple states to global * Fix: return object code style * Fix: use proxy to reflect API * Fix: eslint-disable-line -> eslint-disable-next-line * Fix: requestId -> id * Fix: one-liner * Fix: one-liner * Fix: move dom event block to top * Fix: pending -> pendingResponse * Fix: onSuccess -> onUpdated * Fix: optimize row removing when i === 0 * Fix: hook/unhook -> enableForEditor/disableForEditor * Fix: linter.refresh -> linter.run * Fix: some shadowing * Fix: simplify getAnnotations * Fix: cacheFirstCall -> memoize * Fix: table.update -> table.updateCaption * Fix: unneeded reassign * Fix: callbacks -> listeners * Fix: don't compose but extend * Refactor: replace linter modules with linter-defaults and linter-engines * Fix: implement linter fallbacks * Fix: linter.onChange -> linter.onLintingUpdated * Fix: cms -> tables * Fix: parseMozFormat is not called correctly * Move csslint-loader to background * Fix: watch config changes * Fix: switch to LINTER_DEFAULTS * Fix: csslint-loader -> parserlib-loader
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;
};
}
/**
* @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();
}
});
}