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", "message": "Beautify",
"description": "Label for the CSS-beautifier button on the edit style page" "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": { "styleBeautifyIndentConditional": {
"message": "Indent @media, @supports", "message": "Indent @media, @supports",
"description": "CSS-beautifier option" "description": "CSS-beautifier option"

View File

@ -1,8 +1,49 @@
/* global loadScript css_beautify showHelp prefs t $ $create */ /* global loadScript css_beautify showHelp prefs t $ $create */
/* exported beautify */ /* global editor createHotkeyInput moveFocus CodeMirror */
/* exported initBeautifyButton */
'use strict'; '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') loadScript('/vendor-overwrites/beautify/beautify-css-mod.js')
.then(() => { .then(() => {
if (!window.css_beautify && window.exports) { if (!window.css_beautify && window.exports) {
@ -19,7 +60,41 @@ function beautify(scope) {
} }
options.indent_size = tabs ? 1 : prefs.get('editor.tabSize'); options.indent_size = tabs ? 1 : prefs.get('editor.tabSize');
options.indent_char = tabs ? '\t' : ' '; 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'), showHelp(t('styleBeautify'),
$create([ $create([
$create('.beautify-options', [ $create('.beautify-options', [
@ -32,6 +107,10 @@ function beautify(scope) {
$createLabeledCheckbox('preserve_newlines', 'styleBeautifyPreserveNewlines'), $createLabeledCheckbox('preserve_newlines', 'styleBeautifyPreserveNewlines'),
$createLabeledCheckbox('indent_conditional', 'styleBeautifyIndentConditional'), $createLabeledCheckbox('indent_conditional', 'styleBeautifyIndentConditional'),
]), ]),
$create('p.beautify-hint', [
$create('span', t('styleBeautifyHint') + '\u00A0'),
createHotkeyInput(HOTKEY_ID, () => moveFocus($('#help-popup'), 1)),
]),
$create('.buttons', [ $create('.buttons', [
$create('button', { $create('button', {
attributes: {role: 'close'}, attributes: {role: 'close'},
@ -60,32 +139,6 @@ function beautify(scope) {
$('#help-popup').className = 'wide'; $('#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}) => { $('.beautify-options').onchange = ({target}) => {
const value = target.type === 'checkbox' ? target.checked : target.selectedIndex > 0; const value = target.type === 'checkbox' ? target.checked : target.selectedIndex > 0;
prefs.set('editor.beautify', Object.assign(options, {[target.dataset.option]: value})); 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'; 'use strict';
(() => { (() => {
@ -62,46 +62,8 @@
function configureColorpicker(event) { function configureColorpicker(event) {
event.preventDefault(); event.preventDefault();
const input = $create('input', { const input = createHotkeyInput('editor.colorpicker.hotkey', () => {
type: 'search', $('#help-popup .dismiss').onclick();
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); const popup = showHelp(t('helpKeyMapHotkey'), input);
if (this instanceof Element) { 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; padding-left: 4px;
margin-left: 4px; margin-left: 4px;
} }
.beautify-hint {
width: 0;
min-width: 100%;
font-size: 90%;
}
/************ single editor **************/ /************ single editor **************/
.usercss body { .usercss body {

View File

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

View File

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

View File

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

View File

@ -154,9 +154,7 @@ function createSectionsEditor({style, onTitleChanged}) {
function closestVisible(nearbyElement) { function closestVisible(nearbyElement) {
const cm = const cm =
nearbyElement instanceof CodeMirror ? nearbyElement : nearbyElement instanceof CodeMirror ? nearbyElement :
nearbyElement instanceof Node && nearbyElement instanceof Node && getAssociatedEditor(nearbyElement) || getLastActivatedEditor();
(nearbyElement.closest('#sections > .section') || {}).CodeMirror ||
getLastActivatedEditor();
if (nearbyElement instanceof Node && cm) { if (nearbyElement instanceof Node && cm) {
const {left, top} = nearbyElement.getBoundingClientRect(); const {left, top} = nearbyElement.getBoundingClientRect();
const bounds = cm.display.wrapper.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() { function getEditors() {
return sections.filter(s => !s.isRemoved()).map(s => s.cm); 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'; 'use strict';
function dirtyReporter() { function dirtyReporter() {
@ -135,3 +136,52 @@ function memoize(fn) {
return result; 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, end_with_newline: false,
indent_conditional: true, indent_conditional: true,
}, },
'editor.beautify.hotkey': '',
'editor.lintDelay': 300, // lint gutter marker update delay, ms 'editor.lintDelay': 300, // lint gutter marker update delay, ms
'editor.linter': 'csslint', // 'csslint' or 'stylelint' or '' 'editor.linter': 'csslint', // 'csslint' or 'stylelint' or ''
'editor.lintReportDelay': 500, // lint report update delay, ms 'editor.lintReportDelay': 500, // lint report update delay, ms