add a hotkey & right-click to beautify silently (#972)

* add a hotkey & right-click to beautify silently

* fix closestVisible
This commit is contained in:
tophf 2020-06-22 19:14:41 +03:00 committed by GitHub
parent e1ed3bf222
commit 60a37af0e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 161 additions and 79 deletions

View File

@ -1273,6 +1273,10 @@
"message": "Beautify",
"description": "Label for the CSS-beautifier button on the edit style page"
},
"styleBeautifyHint": {
"message": "Hint: right-click the “Beautify” button or use the keyboard shortcut defined below to beautify without showing this panel",
"description": "Hint shown inside the CSS-beautifier panel"
},
"styleBeautifyIndentConditional": {
"message": "Indent @media, @supports",
"description": "CSS-beautifier option"

View File

@ -1,8 +1,49 @@
/* global loadScript css_beautify showHelp prefs t $ $create */
/* exported beautify */
/* global editor createHotkeyInput moveFocus CodeMirror */
/* exported initBeautifyButton */
'use strict';
function beautify(scope) {
const HOTKEY_ID = 'editor.beautify.hotkey';
prefs.initializing.then(() => {
CodeMirror.defaults.extraKeys[prefs.get(HOTKEY_ID) || ''] = 'beautify';
CodeMirror.commands.beautify = cm => {
// using per-section mode when code editor or applies-to block is focused
const isPerSection = cm.display.wrapper.parentElement.contains(document.activeElement);
beautify(isPerSection ? [cm] : editor.getEditors(), false);
};
});
prefs.subscribe([HOTKEY_ID], (key, value) => {
const {extraKeys} = CodeMirror.defaults;
for (const [key, cmd] of Object.entries(extraKeys)) {
if (cmd === 'beautify') {
delete extraKeys[key];
break;
}
}
if (value) {
extraKeys[value] = 'beautify';
}
});
/**
* @param {HTMLElement} btn - the button element shown in the UI
* @param {function():CodeMirror[]} getScope
*/
function initBeautifyButton(btn, getScope) {
btn.addEventListener('click', () => beautify(getScope()));
btn.addEventListener('contextmenu', e => {
e.preventDefault();
beautify(getScope(), false);
});
}
/**
* @param {CodeMirror[]} scope
* @param {?boolean} ui
*/
function beautify(scope, ui = true) {
loadScript('/vendor-overwrites/beautify/beautify-css-mod.js')
.then(() => {
if (!window.css_beautify && window.exports) {
@ -19,7 +60,41 @@ function beautify(scope) {
}
options.indent_size = tabs ? 1 : prefs.get('editor.tabSize');
options.indent_char = tabs ? '\t' : ' ';
if (ui) {
createBeautifyUI(scope, options);
}
for (const cm of scope) {
setTimeout(doBeautifyEditor, 0, cm, options);
}
}
function doBeautifyEditor(cm, options) {
const pos = options.translate_positions =
[].concat.apply([], cm.doc.sel.ranges.map(r =>
[Object.assign({}, r.anchor), Object.assign({}, r.head)]));
const text = cm.getValue();
const newText = css_beautify(text, options);
if (newText !== text) {
if (!cm.beautifyChange || !cm.beautifyChange[cm.changeGeneration()]) {
// clear the list if last change wasn't a css-beautify
cm.beautifyChange = {};
}
cm.setValue(newText);
const selections = [];
for (let i = 0; i < pos.length; i += 2) {
selections.push({anchor: pos[i], head: pos[i + 1]});
}
const {scrollX, scrollY} = window;
cm.setSelections(selections);
window.scrollTo(scrollX, scrollY);
cm.beautifyChange[cm.changeGeneration()] = true;
if (ui) {
$('#help-popup button[role="close"]').disabled = false;
}
}
}
function createBeautifyUI(scope, options) {
showHelp(t('styleBeautify'),
$create([
$create('.beautify-options', [
@ -32,6 +107,10 @@ function beautify(scope) {
$createLabeledCheckbox('preserve_newlines', 'styleBeautifyPreserveNewlines'),
$createLabeledCheckbox('indent_conditional', 'styleBeautifyIndentConditional'),
]),
$create('p.beautify-hint', [
$create('span', t('styleBeautifyHint') + '\u00A0'),
createHotkeyInput(HOTKEY_ID, () => moveFocus($('#help-popup'), 1)),
]),
$create('.buttons', [
$create('button', {
attributes: {role: 'close'},
@ -60,32 +139,6 @@ function beautify(scope) {
$('#help-popup').className = 'wide';
scope.forEach(cm => {
setTimeout(() => {
const pos = options.translate_positions =
[].concat.apply([], cm.doc.sel.ranges.map(r =>
[Object.assign({}, r.anchor), Object.assign({}, r.head)]));
const text = cm.getValue();
const newText = css_beautify(text, options);
if (newText !== text) {
if (!cm.beautifyChange || !cm.beautifyChange[cm.changeGeneration()]) {
// clear the list if last change wasn't a css-beautify
cm.beautifyChange = {};
}
cm.setValue(newText);
const selections = [];
for (let i = 0; i < pos.length; i += 2) {
selections.push({anchor: pos[i], head: pos[i + 1]});
}
const {scrollX, scrollY} = window;
cm.setSelections(selections);
window.scrollTo(scrollX, scrollY);
cm.beautifyChange[cm.changeGeneration()] = true;
$('#help-popup button[role="close"]').disabled = false;
}
});
});
$('.beautify-options').onchange = ({target}) => {
const value = target.type === 'checkbox' ? target.checked : target.selectedIndex > 0;
prefs.set('editor.beautify', Object.assign(options, {[target.dataset.option]: value}));

View File

@ -1,4 +1,4 @@
/* global CodeMirror showHelp cmFactory onDOMready $ $create prefs t */
/* global CodeMirror showHelp cmFactory onDOMready $ prefs t createHotkeyInput */
'use strict';
(() => {
@ -62,46 +62,8 @@
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 input = createHotkeyInput('editor.colorpicker.hotkey', () => {
$('#help-popup .dismiss').onclick();
});
const popup = showHelp(t('helpKeyMapHotkey'), input);
if (this instanceof Element) {

View File

@ -779,6 +779,11 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high
padding-left: 4px;
margin-left: 4px;
}
.beautify-hint {
width: 0;
min-width: 100%;
font-size: 90%;
}
/************ single editor **************/
.usercss body {

View File

@ -1,7 +1,7 @@
/* global CodeMirror onDOMready prefs setupLivePrefs $ $$ $create t tHTML
createSourceEditor queryTabs sessionStorageHash getOwnTab FIREFOX API tryCatch
closeCurrentTab messageBox debounce workerUtil
beautify ignoreChromeError
initBeautifyButton ignoreChromeError
moveFocus msg createSectionsEditor rerouteHotkeys CODEMIRROR_THEMES */
/* exported showCodeMirrorPopup editorWorker toggleContextMenuDelete */
'use strict';
@ -170,10 +170,8 @@ preinit();
$('#heading').textContent = t(style.id ? 'editStyleHeading' : 'addStyleTitle');
$('#name').placeholder = t(usercss ? 'usercssEditorNamePlaceholder' : 'styleMissingName');
$('#name').title = usercss ? t('usercssReplaceTemplateName') : '';
$('#preview-label').classList.toggle('hidden', !style.id);
$('#beautify').onclick = () => beautify(editor.getEditors());
initBeautifyButton($('#beautify'), () => editor.getEditors());
window.addEventListener('resize', () => {
debounce(rememberWindowSize, 100);
detectLayout();

View File

@ -12,6 +12,7 @@ const rerouteHotkeys = (() => {
'toggleEditorFocus',
'find', 'findNext', 'findPrev', 'replace', 'replaceAll',
'colorpicker',
'beautify',
]);
return rerouteHotkeys;

View File

@ -1,5 +1,5 @@
/* global template cmFactory $ propertyToCss CssToProperty linter regExpTester
FIREFOX toggleContextMenuDelete beautify showHelp t tryRegExp */
FIREFOX toggleContextMenuDelete initBeautifyButton showHelp t tryRegExp */
/* exported createSection */
'use strict';
@ -94,6 +94,7 @@ function createSection({
const cm = cmFactory.create(wrapper => {
el.insertBefore(wrapper, $('.code-label', el).nextSibling);
}, {value: originalSection.code});
el.CodeMirror = cm; // used by getAssociatedEditor
const changeListeners = new Set();
@ -196,12 +197,12 @@ function createSection({
$('.clone-section', el).addEventListener('click', () => insertSectionAfter(getModel(), section));
$('.move-section-up', el).addEventListener('click', () => moveSectionUp(section));
$('.move-section-down', el).addEventListener('click', () => moveSectionDown(section));
$('.beautify-section', el).addEventListener('click', () => beautify([cm]));
$('.restore-section', el).addEventListener('click', () => restoreSection(section));
$('.test-regexp', el).addEventListener('click', () => {
regExpTester.toggle();
updateRegexpTester();
});
initBeautifyButton($('.beautify-section', el), () => [cm]);
}
function handleKeydown(cm, event) {

View File

@ -154,9 +154,7 @@ function createSectionsEditor({style, onTitleChanged}) {
function closestVisible(nearbyElement) {
const cm =
nearbyElement instanceof CodeMirror ? nearbyElement :
nearbyElement instanceof Node &&
(nearbyElement.closest('#sections > .section') || {}).CodeMirror ||
getLastActivatedEditor();
nearbyElement instanceof Node && getAssociatedEditor(nearbyElement) || getLastActivatedEditor();
if (nearbyElement instanceof Node && cm) {
const {left, top} = nearbyElement.getBoundingClientRect();
const bounds = cm.display.wrapper.getBoundingClientRect();
@ -228,6 +226,15 @@ function createSectionsEditor({style, onTitleChanged}) {
}
}
function getAssociatedEditor(nearbyElement) {
for (let el = nearbyElement; el; el = el.parentElement) {
// added by createSection
if (el.CodeMirror) {
return el.CodeMirror;
}
}
}
function getEditors() {
return sections.filter(s => !s.isRemoved()).map(s => s.cm);
}

View File

@ -1,4 +1,5 @@
/* exported dirtyReporter memoize clipString sectionsToMozFormat */
/* global CodeMirror $create prefs */
/* exported dirtyReporter memoize clipString sectionsToMozFormat createHotkeyInput */
'use strict';
function dirtyReporter() {
@ -135,3 +136,52 @@ function memoize(fn) {
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();
}
});
}

View File

@ -59,6 +59,7 @@ self.prefs = self.INJECTED === 1 ? self.prefs : (() => {
end_with_newline: false,
indent_conditional: true,
},
'editor.beautify.hotkey': '',
'editor.lintDelay': 300, // lint gutter marker update delay, ms
'editor.linter': 'csslint', // 'csslint' or 'stylelint' or ''
'editor.lintReportDelay': 500, // lint report update delay, ms