add a hotkey & right-click to beautify silently (#972)
* add a hotkey & right-click to beautify silently * fix closestVisible
This commit is contained in:
parent
e1ed3bf222
commit
60a37af0e0
|
@ -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"
|
||||||
|
|
109
edit/beautify.js
109
edit/beautify.js
|
@ -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}));
|
||||||
|
|
|
@ -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',
|
|
||||||
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();
|
$('#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) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
52
edit/util.js
52
edit/util.js
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user