stylus/edit/colorpicker-helper.js

161 lines
5.3 KiB
JavaScript

/* global CodeMirror loadScript editors showHelp */
'use strict';
onDOMscriptReady('/colorview.js').then(() => {
initOverlayHooks();
onDOMready().then(() => {
$('#colorpicker-settings').onclick = configureColorpicker;
});
prefs.subscribe(['editor.colorpicker.hotkey'], registerHotkey);
prefs.subscribe(['editor.colorpicker'], setColorpickerOption);
setColorpickerOption(null, prefs.get('editor.colorpicker'));
function setColorpickerOption(id, enabled) {
const defaults = CodeMirror.defaults;
const keyName = prefs.get('editor.colorpicker.hotkey');
defaults.colorpicker = enabled;
if (enabled) {
if (keyName) {
CodeMirror.commands.colorpicker = invokeColorpicker;
defaults.extraKeys = defaults.extraKeys || {};
defaults.extraKeys[keyName] = 'colorpicker';
}
defaults.colorpicker = {
forceUpdate: editors.length > 0,
tooltip: t('colorpickerTooltip'),
popupOptions: {
tooltipForSwitcher: t('colorpickerSwitchFormatTooltip'),
hexUppercase: prefs.get('editor.colorpicker.hexUppercase'),
hideDelay: 5000,
embedderCallback: state => {
['hexUppercase', 'color']
.filter(name => state[name] !== prefs.get('editor.colorpicker.' + name))
.forEach(name => prefs.set('editor.colorpicker.' + name, state[name]));
},
},
};
} else {
CodeMirror.modeExtensions.css.unregisterColorviewHooks();
if (defaults.extraKeys) {
delete defaults.extraKeys[keyName];
}
}
// on page load runs before CodeMirror.setOption is defined
editors.forEach(cm => cm.setOption('colorpicker', defaults.colorpicker));
}
function registerHotkey(id, hotkey) {
CodeMirror.commands.colorpicker = invokeColorpicker;
const extraKeys = CodeMirror.defaults.extraKeys;
for (const key in extraKeys) {
if (extraKeys[key] === 'colorpicker') {
delete extraKeys[key];
break;
}
}
if (hotkey) {
extraKeys[hotkey] = 'colorpicker';
}
}
function invokeColorpicker(cm) {
cm.state.colorpicker.openPopup(prefs.get('editor.colorpicker.color'));
}
function configureColorpicker(event) {
event.preventDefault();
const input = $create('input', {
type: 'search',
spellcheck: false,
value: prefs.get('editor.colorpicker.hotkey'),
onkeydown(event) {
event.preventDefault();
event.stopPropagation();
const key = CodeMirror.keyName(event);
switch (key) {
case 'Enter':
if (this.checkValidity()) {
$('#help-popup .dismiss').onclick();
}
return;
case 'Esc':
$('#help-popup .dismiss').onclick();
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('editor.colorpicker.hotkey', key);
},
oninput() {
// fired on pressing "x" to clear the field
prefs.set('editor.colorpicker.hotkey', '');
},
onpaste(event) {
event.preventDefault();
}
});
const popup = showHelp(t('helpKeyMapHotkey'), input);
if (this instanceof Element) {
const bounds = this.getBoundingClientRect();
popup.style.left = bounds.right + 10 + 'px';
popup.style.top = bounds.top - popup.clientHeight / 2 + 'px';
popup.style.right = 'auto';
}
input.focus();
}
function initOverlayHooks() {
const COLORVIEW_DISABLED_SUFFIX = ' colorview-disabled';
const COLORVIEW_NEXT_DISABLED_SUFFIX = ' colorview-next-disabled';
const originalAddOverlay = CodeMirror.prototype.addOverlay;
CodeMirror.prototype.addOverlay = addOverlayHook;
function addOverlayHook(overlay) {
if (overlay.token !== tokenHook && (
overlay === (this.state.matchHighlighter || {}).overlay ||
overlay === (this.state.search || {}).overlay)) {
overlay.colopickerHelper = {token: overlay.token};
overlay.token = tokenHook;
}
originalAddOverlay.apply(this, arguments);
}
function tokenHook(stream) {
const style = this.colopickerHelper.token.apply(this, arguments);
if (!style) {
return style;
}
const {start, pos, lineOracle: {baseTokens}} = stream;
if (!baseTokens) {
return style;
}
for (let prev = 0, i = 1; i < baseTokens.length; i += 2) {
const end = baseTokens[i];
if (prev <= start && start <= end) {
const base = baseTokens[i + 1];
if (base && base.includes('colorview')) {
return style +
(start > prev ? COLORVIEW_DISABLED_SUFFIX : '') +
(pos < end ? COLORVIEW_NEXT_DISABLED_SUFFIX : '');
}
} else if (end > pos) {
break;
}
prev = end;
}
return style;
}
}
});