906508832b
+ simplify css/html + save button and autosave checkbox just like in config-dialog + generalize can-close-on-esc + add `props` parameter to helpPopup.show + deduplicate usage of #help-popup id + uniform padding in popups + disambiguate style settings from editor options
225 lines
6.5 KiB
JavaScript
225 lines
6.5 KiB
JavaScript
/* global $ $create getEventKeyName messageBoxProxy moveFocus */// dom.js
|
|
/* global CodeMirror */
|
|
/* global editor */
|
|
/* global prefs */
|
|
/* global t */// localization.js
|
|
'use strict';
|
|
|
|
const helpPopup = {
|
|
|
|
/**
|
|
* @param {string} title - plain text
|
|
* @param {string|Node} body - Node, html or plain text
|
|
* @param {Node} [props] - DOM props for the popup element
|
|
* @returns {Element} the popup
|
|
*/
|
|
show(title = '', body, props) {
|
|
const div = $('#help-popup');
|
|
const contents = $('.contents', div);
|
|
div.style = '';
|
|
div.className = '';
|
|
contents.textContent = '';
|
|
Object.assign(div, props);
|
|
if (body) {
|
|
contents.appendChild(typeof body === 'string' ? t.HTML(body) : body);
|
|
}
|
|
$('.title', div).textContent = title;
|
|
$('.dismiss', div).onclick = helpPopup.close;
|
|
window.on('keydown', helpPopup.close, true);
|
|
div.style.display = 'block';
|
|
helpPopup.originalFocus = document.activeElement;
|
|
helpPopup.div = div;
|
|
return div;
|
|
},
|
|
|
|
close(event) {
|
|
const canClose =
|
|
!event ||
|
|
event.type === 'click' || (
|
|
getEventKeyName(event) === 'Escape' &&
|
|
!$('.CodeMirror-hints, #message-box') && (
|
|
!document.activeElement ||
|
|
!document.activeElement.closest('#search-replace-dialog') && (
|
|
document.activeElement.tagName !== 'INPUT' ||
|
|
document.activeElement.closest('.can-close-on-esc')
|
|
)
|
|
)
|
|
);
|
|
const {div} = helpPopup;
|
|
if (!canClose || !div) {
|
|
return;
|
|
}
|
|
if (event && div.codebox && !div.codebox.options.readOnly && !div.codebox.isClean()) {
|
|
setTimeout(async () => {
|
|
const ok = await messageBoxProxy.confirm(t('confirmDiscardChanges'));
|
|
return ok && helpPopup.close();
|
|
});
|
|
return;
|
|
}
|
|
if (div.contains(document.activeElement) && helpPopup.originalFocus) {
|
|
helpPopup.originalFocus.focus();
|
|
}
|
|
const contents = $('.contents', div);
|
|
div.style.display = '';
|
|
contents.textContent = '';
|
|
window.off('keydown', helpPopup.close, true);
|
|
window.dispatchEvent(new Event('closeHelp'));
|
|
},
|
|
};
|
|
|
|
// reroute handling to nearest editor when keypress resolves to one of these commands
|
|
const rerouteHotkeys = {
|
|
commands: [
|
|
'beautify',
|
|
'colorpicker',
|
|
'find',
|
|
'findNext',
|
|
'findPrev',
|
|
'jumpToLine',
|
|
'nextEditor',
|
|
'prevEditor',
|
|
'replace',
|
|
'replaceAll',
|
|
'save',
|
|
'toggleEditorFocus',
|
|
'toggleStyle',
|
|
],
|
|
|
|
toggle(enable) {
|
|
document[enable ? 'on' : 'off']('keydown', rerouteHotkeys.handler);
|
|
},
|
|
|
|
handler(event) {
|
|
const keyName = CodeMirror.keyName(event);
|
|
if (!keyName) {
|
|
return;
|
|
}
|
|
const rerouteCommand = name => {
|
|
if (rerouteHotkeys.commands.includes(name)) {
|
|
CodeMirror.commands[name](editor.closestVisible(event.target));
|
|
return true;
|
|
}
|
|
};
|
|
if (CodeMirror.lookupKey(keyName, CodeMirror.defaults.keyMap, rerouteCommand) === 'handled' ||
|
|
CodeMirror.lookupKey(keyName, CodeMirror.defaults.extraKeys, rerouteCommand) === 'handled') {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
},
|
|
};
|
|
|
|
function clipString(str, limit = 100) {
|
|
return str.length <= limit ? str : str.substr(0, limit) + '...';
|
|
}
|
|
|
|
/* exported createHotkeyInput */
|
|
function createHotkeyInput(prefId, {buttons = true, onDone}) {
|
|
const RX_ERR = new RegExp('^(' + [
|
|
/Space/,
|
|
/(Shift-)?./, // a single character
|
|
/(?=.)(Shift-?|Ctrl-?|Control-?|Alt-?|Meta-?)*(Escape|Tab|Page(Up|Down)|Arrow(Up|Down|Left|Right)|Home|End)?/,
|
|
].map(r => r.source || r).join('|') + ')$', 'i');
|
|
const initialValue = prefs.get(prefId);
|
|
const input = $create('input', {
|
|
spellcheck: false,
|
|
onpaste: e => onkeydown(e, e.clipboardData.getData('text')),
|
|
onkeydown,
|
|
});
|
|
buttons = buttons && [
|
|
['confirmOK', 'Enter'],
|
|
['undo', initialValue],
|
|
['genericResetLabel', ''],
|
|
].map(([label, val]) =>
|
|
$create('button', {onclick: e => onkeydown(e, val)}, t(label)));
|
|
const [btnOk, btnUndo, btnReset] = buttons || [];
|
|
onkeydown(null, initialValue);
|
|
return buttons
|
|
? $create('fragment', [input, $create('.buttons', buttons)])
|
|
: input;
|
|
|
|
function onkeydown(e, key) {
|
|
let newValue;
|
|
if (e && e.type === 'keydown') {
|
|
key = getEventKeyName(e);
|
|
}
|
|
switch (e && key) {
|
|
case 'Tab':
|
|
case 'Shift-Tab':
|
|
return;
|
|
case 'BackSpace':
|
|
case 'Delete':
|
|
newValue = '';
|
|
break;
|
|
case 'Enter':
|
|
if (input.checkValidity() && onDone) onDone();
|
|
break;
|
|
case 'Escape':
|
|
if (onDone) onDone();
|
|
break;
|
|
default:
|
|
newValue = key.replace(/\b.$/, c => c.toUpperCase());
|
|
}
|
|
if (newValue != null) {
|
|
const error = RX_ERR.test(newValue) ? t('genericError') : '';
|
|
if (e && !error) prefs.set(prefId, newValue);
|
|
input.setCustomValidity(error);
|
|
input.value = newValue;
|
|
input.focus();
|
|
if (buttons) {
|
|
btnOk.disabled = Boolean(error);
|
|
btnUndo.disabled = newValue === initialValue;
|
|
btnReset.disabled = !newValue;
|
|
}
|
|
}
|
|
if (e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* exported showCodeMirrorPopup */
|
|
function showCodeMirrorPopup(title, html, options) {
|
|
const popup = helpPopup.show(title, html, {className: 'big'});
|
|
|
|
let cm = popup.codebox = CodeMirror($('.contents', popup), Object.assign({
|
|
mode: 'css',
|
|
lineNumbers: true,
|
|
lineWrapping: prefs.get('editor.lineWrapping'),
|
|
foldGutter: true,
|
|
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
|
|
matchBrackets: true,
|
|
styleActiveLine: true,
|
|
theme: prefs.get('editor.theme'),
|
|
keyMap: prefs.get('editor.keyMap'),
|
|
}, options));
|
|
cm.focus();
|
|
|
|
document.documentElement.style.pointerEvents = 'none';
|
|
popup.style.pointerEvents = 'auto';
|
|
|
|
const onKeyDown = event => {
|
|
if (event.key === 'Tab' && !event.ctrlKey && !event.altKey && !event.metaKey) {
|
|
const search = $('#search-replace-dialog');
|
|
const area = search && search.contains(document.activeElement) ? search : popup;
|
|
moveFocus(area, event.shiftKey ? -1 : 1);
|
|
event.preventDefault();
|
|
}
|
|
};
|
|
window.on('keydown', onKeyDown, true);
|
|
|
|
window.on('closeHelp', () => {
|
|
window.off('keydown', onKeyDown, true);
|
|
document.documentElement.style.removeProperty('pointer-events');
|
|
cm = popup.codebox = null;
|
|
}, {once: true});
|
|
|
|
return popup;
|
|
}
|
|
|
|
/* exported trimCommentLabel */
|
|
function trimCommentLabel(str, limit = 1000) {
|
|
// stripping /*** foo ***/ to foo
|
|
return clipString(str.replace(/^[!-/:;=\s]*|[-#$&(+,./:;<=>\s*]*$/g, ''), limit);
|
|
}
|