Refactor: pull out sections editor section
This commit is contained in:
parent
8016346035
commit
1a5a206fe6
|
@ -95,6 +95,7 @@
|
||||||
<script src="edit/refresh-on-view.js"></script>
|
<script src="edit/refresh-on-view.js"></script>
|
||||||
|
|
||||||
<script src="edit/source-editor.js"></script>
|
<script src="edit/source-editor.js"></script>
|
||||||
|
<script src="edit/sections-editor-section.js"></script>
|
||||||
<script src="edit/sections-editor.js"></script>
|
<script src="edit/sections-editor.js"></script>
|
||||||
|
|
||||||
<script src="edit/edit.js"></script>
|
<script src="edit/edit.js"></script>
|
||||||
|
|
15
edit/edit.js
15
edit/edit.js
|
@ -1,9 +1,9 @@
|
||||||
/* 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
|
beautify ignoreChromeError
|
||||||
moveFocus msg createSectionsEditor rerouteHotkeys */
|
moveFocus msg createSectionsEditor rerouteHotkeys */
|
||||||
/* exported showCodeMirrorPopup editorWorker */
|
/* exported showCodeMirrorPopup editorWorker toggleContextMenuDelete */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const editorWorker = workerUtil.createWorker({
|
const editorWorker = workerUtil.createWorker({
|
||||||
|
@ -557,3 +557,14 @@ function isWindowMaximized() {
|
||||||
window.outerHeight < screen.availHeight + 10
|
window.outerHeight < screen.availHeight + 10
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleContextMenuDelete(event) {
|
||||||
|
if (chrome.contextMenus && event.button === 2 && prefs.get('editor.contextDelete')) {
|
||||||
|
chrome.contextMenus.update('editor.contextDelete', {
|
||||||
|
enabled: Boolean(
|
||||||
|
this.selectionStart !== this.selectionEnd ||
|
||||||
|
this.somethingSelected && this.somethingSelected()
|
||||||
|
),
|
||||||
|
}, ignoreChromeError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
416
edit/sections-editor-section.js
Normal file
416
edit/sections-editor-section.js
Normal file
|
@ -0,0 +1,416 @@
|
||||||
|
/* global template cmFactory $ propertyToCss CssToProperty linter regExpTester
|
||||||
|
FIREFOX toggleContextMenuDelete beautify showHelp t tryRegExp */
|
||||||
|
/* exported createSection */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function createResizeGrip(cm) {
|
||||||
|
const wrapper = cm.display.wrapper;
|
||||||
|
wrapper.classList.add('resize-grip-enabled');
|
||||||
|
const resizeGrip = template.resizeGrip.cloneNode(true);
|
||||||
|
wrapper.appendChild(resizeGrip);
|
||||||
|
let lastClickTime = 0;
|
||||||
|
resizeGrip.onmousedown = event => {
|
||||||
|
if (event.button !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
if (Date.now() - lastClickTime < 500) {
|
||||||
|
lastClickTime = 0;
|
||||||
|
toggleSectionHeight(cm);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastClickTime = Date.now();
|
||||||
|
const minHeight = cm.defaultTextHeight() +
|
||||||
|
/* .CodeMirror-lines padding */
|
||||||
|
cm.display.lineDiv.offsetParent.offsetTop +
|
||||||
|
/* borders */
|
||||||
|
wrapper.offsetHeight - wrapper.clientHeight;
|
||||||
|
wrapper.style.pointerEvents = 'none';
|
||||||
|
document.body.style.cursor = 's-resize';
|
||||||
|
document.addEventListener('mousemove', resize);
|
||||||
|
document.addEventListener('mouseup', resizeStop);
|
||||||
|
|
||||||
|
function resize(e) {
|
||||||
|
const cmPageY = wrapper.getBoundingClientRect().top + window.scrollY;
|
||||||
|
const height = Math.max(minHeight, e.pageY - cmPageY);
|
||||||
|
if (height !== wrapper.clientHeight) {
|
||||||
|
cm.setSize(null, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resizeStop() {
|
||||||
|
document.removeEventListener('mouseup', resizeStop);
|
||||||
|
document.removeEventListener('mousemove', resize);
|
||||||
|
wrapper.style.pointerEvents = '';
|
||||||
|
document.body.style.cursor = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function toggleSectionHeight(cm) {
|
||||||
|
if (cm.state.toggleHeightSaved) {
|
||||||
|
// restore previous size
|
||||||
|
cm.setSize(null, cm.state.toggleHeightSaved);
|
||||||
|
cm.state.toggleHeightSaved = 0;
|
||||||
|
} else {
|
||||||
|
// maximize
|
||||||
|
const wrapper = cm.display.wrapper;
|
||||||
|
const allBounds = $('#sections').getBoundingClientRect();
|
||||||
|
const pageExtrasHeight = allBounds.top + window.scrollY +
|
||||||
|
parseFloat(getComputedStyle($('#sections')).paddingBottom);
|
||||||
|
const sectionEl = wrapper.parentNode;
|
||||||
|
const sectionExtrasHeight = sectionEl.clientHeight - wrapper.offsetHeight;
|
||||||
|
cm.state.toggleHeightSaved = wrapper.clientHeight;
|
||||||
|
cm.setSize(null, window.innerHeight - sectionExtrasHeight - pageExtrasHeight);
|
||||||
|
const bounds = sectionEl.getBoundingClientRect();
|
||||||
|
if (bounds.top < 0 || bounds.bottom > window.innerHeight) {
|
||||||
|
window.scrollBy(0, bounds.top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSection({
|
||||||
|
originalSection,
|
||||||
|
genId,
|
||||||
|
dirty,
|
||||||
|
showMozillaFormatImport,
|
||||||
|
removeSection,
|
||||||
|
insertSectionAfter,
|
||||||
|
moveSectionUp,
|
||||||
|
moveSectionDown,
|
||||||
|
restoreSection,
|
||||||
|
nextEditor,
|
||||||
|
prevEditor
|
||||||
|
}) {
|
||||||
|
const sectionId = genId();
|
||||||
|
const el = template.section.cloneNode(true);
|
||||||
|
const cm = cmFactory.create(wrapper => {
|
||||||
|
el.insertBefore(wrapper, $('.code-label', el).nextSibling);
|
||||||
|
}, {value: originalSection.code});
|
||||||
|
|
||||||
|
const changeListeners = new Set();
|
||||||
|
|
||||||
|
const appliesToContainer = $('.applies-to-list', el);
|
||||||
|
const appliesTo = [];
|
||||||
|
for (const [key, fnName] of Object.entries(propertyToCss)) {
|
||||||
|
if (originalSection[key]) {
|
||||||
|
originalSection[key].forEach(value =>
|
||||||
|
insertApplyAfter({type: fnName, value})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!appliesTo.length) {
|
||||||
|
insertApplyAfter({all: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
let changeGeneration = cm.changeGeneration();
|
||||||
|
let removed = false;
|
||||||
|
|
||||||
|
registerEvents();
|
||||||
|
updateRegexpTester();
|
||||||
|
createResizeGrip(cm);
|
||||||
|
|
||||||
|
linter.enableForEditor(cm);
|
||||||
|
|
||||||
|
let lastActive = 0;
|
||||||
|
|
||||||
|
const section = {
|
||||||
|
id: sectionId,
|
||||||
|
el,
|
||||||
|
cm,
|
||||||
|
render,
|
||||||
|
getCode,
|
||||||
|
getModel,
|
||||||
|
remove,
|
||||||
|
restore,
|
||||||
|
isRemoved: () => removed,
|
||||||
|
onChange,
|
||||||
|
off,
|
||||||
|
getLastActive: () => lastActive,
|
||||||
|
appliesTo
|
||||||
|
};
|
||||||
|
return section;
|
||||||
|
|
||||||
|
function onChange(fn) {
|
||||||
|
changeListeners.add(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
function off(fn) {
|
||||||
|
changeListeners.delete(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitSectionChange() {
|
||||||
|
for (const fn of changeListeners) {
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModel() {
|
||||||
|
const section = {
|
||||||
|
code: cm.getValue()
|
||||||
|
};
|
||||||
|
for (const apply of appliesTo) {
|
||||||
|
if (apply.all) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const key = CssToProperty[apply.getType()];
|
||||||
|
if (!section[key]) {
|
||||||
|
section[key] = [];
|
||||||
|
}
|
||||||
|
section[key].push(apply.getValue());
|
||||||
|
}
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerEvents() {
|
||||||
|
cm.on('changes', () => {
|
||||||
|
const newGeneration = cm.changeGeneration();
|
||||||
|
dirty.modify(`section.${sectionId}.code`, changeGeneration, newGeneration);
|
||||||
|
changeGeneration = newGeneration;
|
||||||
|
emitSectionChange();
|
||||||
|
});
|
||||||
|
cm.on('paste', (cm, event) => {
|
||||||
|
const text = event.clipboardData.getData('text') || '';
|
||||||
|
if (
|
||||||
|
text.includes('@-moz-document') &&
|
||||||
|
text.replace(/\/\*[\s\S]*?(?:\*\/|$)/g, '')
|
||||||
|
.match(/@-moz-document[\s\r\n]+(url|url-prefix|domain|regexp)\(/)
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
showMozillaFormatImport(text);
|
||||||
|
}
|
||||||
|
// FIXME: why?
|
||||||
|
// if (editors.length === 1) {
|
||||||
|
// setTimeout(() => {
|
||||||
|
// if (cm.display.sizer.clientHeight > cm.display.wrapper.clientHeight) {
|
||||||
|
// maximizeCodeHeight.stats = null;
|
||||||
|
// maximizeCodeHeight(cm.getSection(), true);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
if (!FIREFOX) {
|
||||||
|
cm.on('mousedown', (cm, event) => toggleContextMenuDelete.call(cm, event));
|
||||||
|
}
|
||||||
|
cm.on('focus', () => {
|
||||||
|
lastActive = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
|
cm.display.wrapper.addEventListener('keydown', event =>
|
||||||
|
handleKeydown(cm, event), true);
|
||||||
|
|
||||||
|
$('.applies-to-help', el).addEventListener('click', showAppliesToHelp);
|
||||||
|
$('.remove-section', el).addEventListener('click', () => removeSection(section));
|
||||||
|
$('.add-section', el).addEventListener('click', () => insertSectionAfter(undefined, section));
|
||||||
|
$('.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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeydown(cm, event) {
|
||||||
|
const key = event.which;
|
||||||
|
if (key < 37 || key > 40 || event.shiftKey || event.altKey || event.metaKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {line, ch} = cm.getCursor();
|
||||||
|
switch (key) {
|
||||||
|
case 37:
|
||||||
|
// arrow Left
|
||||||
|
if (line || ch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// fallthrough to arrow Up
|
||||||
|
case 38:
|
||||||
|
// arrow Up
|
||||||
|
cm = line === 0 && prevEditor(cm, false);
|
||||||
|
if (!cm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
cm.setCursor(cm.doc.size - 1, key === 37 ? 1e20 : ch);
|
||||||
|
break;
|
||||||
|
case 39:
|
||||||
|
// arrow Right
|
||||||
|
if (line < cm.doc.size - 1 || ch < cm.getLine(line).length - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// fallthrough to arrow Down
|
||||||
|
case 40:
|
||||||
|
// arrow Down
|
||||||
|
cm = line === cm.doc.size - 1 && nextEditor(cm, false);
|
||||||
|
if (!cm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
cm.setCursor(0, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// FIXME: what is this?
|
||||||
|
// const animation = (cm.getSection().firstElementChild.getAnimations() || [])[0];
|
||||||
|
// if (animation) {
|
||||||
|
// animation.playbackRate = -1;
|
||||||
|
// animation.currentTime = 2000;
|
||||||
|
// animation.play();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAppliesToHelp(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
showHelp(t('appliesLabel'), t('appliesHelp'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCode() {
|
||||||
|
return cm.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(destroy = false) {
|
||||||
|
linter.disableForEditor(cm);
|
||||||
|
el.classList.add('removed');
|
||||||
|
removed = true;
|
||||||
|
appliesTo.forEach(a => a.remove());
|
||||||
|
if (destroy) {
|
||||||
|
cmFactory.destroy(cm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function restore() {
|
||||||
|
linter.enableForEditor(cm);
|
||||||
|
el.classList.remove('removed');
|
||||||
|
removed = false;
|
||||||
|
appliesTo.forEach(a => a.restore());
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
cm.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRegexpTester() {
|
||||||
|
const regexps = appliesTo.filter(a => a.getType() === 'regexp')
|
||||||
|
.map(a => a.getValue());
|
||||||
|
if (regexps.length) {
|
||||||
|
el.classList.add('has-regexp');
|
||||||
|
regExpTester.update(regexps);
|
||||||
|
} else {
|
||||||
|
el.classList.remove('has-regexp');
|
||||||
|
regExpTester.toggle(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertApplyAfter(init, base) {
|
||||||
|
const apply = createApply(init);
|
||||||
|
if (base) {
|
||||||
|
const index = appliesTo.indexOf(base);
|
||||||
|
appliesTo.splice(index + 1, 0, apply);
|
||||||
|
appliesToContainer.insertBefore(apply.el, base.el.nextSibling);
|
||||||
|
} else {
|
||||||
|
appliesTo.push(apply);
|
||||||
|
appliesToContainer.appendChild(apply.el);
|
||||||
|
}
|
||||||
|
dirty.add(apply, apply);
|
||||||
|
if (appliesTo.length > 1 && appliesTo[0].all) {
|
||||||
|
removeApply(appliesTo[0]);
|
||||||
|
}
|
||||||
|
emitSectionChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeApply(apply) {
|
||||||
|
const index = appliesTo.indexOf(apply);
|
||||||
|
appliesTo.splice(index, 1);
|
||||||
|
apply.remove();
|
||||||
|
apply.el.remove();
|
||||||
|
dirty.remove(apply, apply);
|
||||||
|
if (!appliesTo.length) {
|
||||||
|
insertApplyAfter({all: true});
|
||||||
|
}
|
||||||
|
emitSectionChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createApply({type = 'url', value, all = false}) {
|
||||||
|
const applyId = genId();
|
||||||
|
const dirtyPrefix = `section.${sectionId}.apply.${applyId}`;
|
||||||
|
const el = all ? template.appliesToEverything.cloneNode(true) :
|
||||||
|
template.appliesTo.cloneNode(true);
|
||||||
|
|
||||||
|
const selectEl = !all && $('.applies-type', el);
|
||||||
|
if (selectEl) {
|
||||||
|
selectEl.value = type;
|
||||||
|
selectEl.addEventListener('change', () => {
|
||||||
|
const oldKey = type;
|
||||||
|
dirty.modify(`${dirtyPrefix}.type`, type, selectEl.value);
|
||||||
|
type = selectEl.value;
|
||||||
|
if (oldKey === 'regexp' || type === 'regexp') {
|
||||||
|
updateRegexpTester();
|
||||||
|
}
|
||||||
|
emitSectionChange();
|
||||||
|
validate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueEl = !all && $('.applies-value', el);
|
||||||
|
if (valueEl) {
|
||||||
|
valueEl.value = value;
|
||||||
|
valueEl.addEventListener('input', () => {
|
||||||
|
dirty.modify(`${dirtyPrefix}.value`, value, valueEl.value);
|
||||||
|
value = valueEl.value;
|
||||||
|
if (type === 'regexp') {
|
||||||
|
updateRegexpTester();
|
||||||
|
}
|
||||||
|
emitSectionChange();
|
||||||
|
});
|
||||||
|
valueEl.addEventListener('change', validate);
|
||||||
|
}
|
||||||
|
|
||||||
|
const apply = {
|
||||||
|
id: applyId,
|
||||||
|
all,
|
||||||
|
remove,
|
||||||
|
restore,
|
||||||
|
el,
|
||||||
|
getType: () => type,
|
||||||
|
getValue: () => value,
|
||||||
|
valueEl
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeButton = $('.remove-applies-to', el);
|
||||||
|
if (removeButton) {
|
||||||
|
removeButton.addEventListener('click', e => {
|
||||||
|
e.preventDefault();
|
||||||
|
removeApply(apply);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$('.add-applies-to', el).addEventListener('click', e => {
|
||||||
|
e.preventDefault();
|
||||||
|
insertApplyAfter({type, value: ''}, apply);
|
||||||
|
});
|
||||||
|
|
||||||
|
return apply;
|
||||||
|
|
||||||
|
function validate() {
|
||||||
|
if (type !== 'regexp' || tryRegExp(value)) {
|
||||||
|
valueEl.setCustomValidity('');
|
||||||
|
} else {
|
||||||
|
valueEl.setCustomValidity(t('styleBadRegexp'));
|
||||||
|
setTimeout(() => valueEl.reportValidity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove() {
|
||||||
|
dirty.remove(`${dirtyPrefix}.type`, type);
|
||||||
|
dirty.remove(`${dirtyPrefix}.value`, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function restore() {
|
||||||
|
dirty.add(`${dirtyPrefix}.type`, type);
|
||||||
|
dirty.add(`${dirtyPrefix}.value`, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,79 +1,11 @@
|
||||||
/* global dirtyReporter showHelp prefs ignoreChromeError
|
/* global dirtyReporter showHelp toggleContextMenuDelete createSection
|
||||||
CodeMirror propertyToCss
|
CodeMirror linter createLivePreview showCodeMirrorPopup
|
||||||
regExpTester linter createLivePreview showCodeMirrorPopup
|
sectionsToMozFormat messageBox clipString
|
||||||
sectionsToMozFormat messageBox clipString beautify
|
rerouteHotkeys $ $$ $create t FIREFOX API
|
||||||
rerouteHotkeys cmFactory CssToProperty template $ $$ $create t FIREFOX API
|
debounce */
|
||||||
debounce tryRegExp
|
|
||||||
*/
|
|
||||||
/* exported createSectionsEditor */
|
/* exported createSectionsEditor */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function createResizeGrip(cm) {
|
|
||||||
const wrapper = cm.display.wrapper;
|
|
||||||
wrapper.classList.add('resize-grip-enabled');
|
|
||||||
const resizeGrip = template.resizeGrip.cloneNode(true);
|
|
||||||
wrapper.appendChild(resizeGrip);
|
|
||||||
let lastClickTime = 0;
|
|
||||||
resizeGrip.onmousedown = event => {
|
|
||||||
if (event.button !== 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
if (Date.now() - lastClickTime < 500) {
|
|
||||||
lastClickTime = 0;
|
|
||||||
toggleSectionHeight(cm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lastClickTime = Date.now();
|
|
||||||
const minHeight = cm.defaultTextHeight() +
|
|
||||||
/* .CodeMirror-lines padding */
|
|
||||||
cm.display.lineDiv.offsetParent.offsetTop +
|
|
||||||
/* borders */
|
|
||||||
wrapper.offsetHeight - wrapper.clientHeight;
|
|
||||||
wrapper.style.pointerEvents = 'none';
|
|
||||||
document.body.style.cursor = 's-resize';
|
|
||||||
document.addEventListener('mousemove', resize);
|
|
||||||
document.addEventListener('mouseup', resizeStop);
|
|
||||||
|
|
||||||
function resize(e) {
|
|
||||||
const cmPageY = wrapper.getBoundingClientRect().top + window.scrollY;
|
|
||||||
const height = Math.max(minHeight, e.pageY - cmPageY);
|
|
||||||
if (height !== wrapper.clientHeight) {
|
|
||||||
cm.setSize(null, height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resizeStop() {
|
|
||||||
document.removeEventListener('mouseup', resizeStop);
|
|
||||||
document.removeEventListener('mousemove', resize);
|
|
||||||
wrapper.style.pointerEvents = '';
|
|
||||||
document.body.style.cursor = '';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function toggleSectionHeight(cm) {
|
|
||||||
if (cm.state.toggleHeightSaved) {
|
|
||||||
// restore previous size
|
|
||||||
cm.setSize(null, cm.state.toggleHeightSaved);
|
|
||||||
cm.state.toggleHeightSaved = 0;
|
|
||||||
} else {
|
|
||||||
// maximize
|
|
||||||
const wrapper = cm.display.wrapper;
|
|
||||||
const allBounds = $('#sections').getBoundingClientRect();
|
|
||||||
const pageExtrasHeight = allBounds.top + window.scrollY +
|
|
||||||
parseFloat(getComputedStyle($('#sections')).paddingBottom);
|
|
||||||
const sectionEl = wrapper.parentNode;
|
|
||||||
const sectionExtrasHeight = sectionEl.clientHeight - wrapper.offsetHeight;
|
|
||||||
cm.state.toggleHeightSaved = wrapper.clientHeight;
|
|
||||||
cm.setSize(null, window.innerHeight - sectionExtrasHeight - pageExtrasHeight);
|
|
||||||
const bounds = sectionEl.getBoundingClientRect();
|
|
||||||
if (bounds.top < 0 || bounds.bottom > window.innerHeight) {
|
|
||||||
window.scrollBy(0, bounds.top);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSectionsEditor(style) {
|
function createSectionsEditor(style) {
|
||||||
let INC_ID = 0; // an increment id that is used by various object to track the order
|
let INC_ID = 0; // an increment id that is used by various object to track the order
|
||||||
const dirty = dirtyReporter();
|
const dirty = dirtyReporter();
|
||||||
|
@ -97,7 +29,7 @@ function createSectionsEditor(style) {
|
||||||
|
|
||||||
$('#to-mozilla').addEventListener('click', showMozillaFormat);
|
$('#to-mozilla').addEventListener('click', showMozillaFormat);
|
||||||
$('#to-mozilla-help').addEventListener('click', showToMozillaHelp);
|
$('#to-mozilla-help').addEventListener('click', showToMozillaHelp);
|
||||||
$('#from-mozilla').addEventListener('click', () => fromMozillaFormat());
|
$('#from-mozilla').addEventListener('click', () => showMozillaFormatImport());
|
||||||
$('#save-button').addEventListener('click', saveStyle);
|
$('#save-button').addEventListener('click', saveStyle);
|
||||||
// FIXME: this element doesn't exist?
|
// FIXME: this element doesn't exist?
|
||||||
$('#sections-help').addEventListener('click', showSectionHelp);
|
$('#sections-help').addEventListener('click', showSectionHelp);
|
||||||
|
@ -150,15 +82,8 @@ function createSectionsEditor(style) {
|
||||||
getSearchableInputs,
|
getSearchableInputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
function toggleContextMenuDelete(event) {
|
function genId() {
|
||||||
if (chrome.contextMenus && event.button === 2 && prefs.get('editor.contextDelete')) {
|
return INC_ID++;
|
||||||
chrome.contextMenus.update('editor.contextDelete', {
|
|
||||||
enabled: Boolean(
|
|
||||||
this.selectionStart !== this.selectionEnd ||
|
|
||||||
this.somethingSelected && this.somethingSelected()
|
|
||||||
),
|
|
||||||
}, ignoreChromeError);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setGlobalProgress(done, total) {
|
function setGlobalProgress(done, total) {
|
||||||
|
@ -180,11 +105,6 @@ function createSectionsEditor(style) {
|
||||||
showHelp(t('styleMozillaFormatHeading'), t('styleToMozillaFormatHelp'));
|
showHelp(t('styleMozillaFormatHeading'), t('styleToMozillaFormatHelp'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function showAppliesToHelp(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
showHelp(t('appliesLabel'), t('appliesHelp'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function showSectionHelp(event) {
|
function showSectionHelp(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
showHelp(t('styleSectionsTitle'), t('sectionHelp'));
|
showHelp(t('styleSectionsTitle'), t('sectionHelp'));
|
||||||
|
@ -287,11 +207,33 @@ function createSectionsEditor(style) {
|
||||||
enabledEl.checked = newValue;
|
enabledEl.checked = newValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextEditor(cm) {
|
function nextEditor(cm, cycle = true) {
|
||||||
|
if (!cycle) {
|
||||||
|
for (const section of sections) {
|
||||||
|
if (section.isRemoved()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cm === section.cm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
return nextPrevEditor(cm, 1);
|
return nextPrevEditor(cm, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function prevEditor(cm) {
|
function prevEditor(cm, cycle = true) {
|
||||||
|
if (!cycle) {
|
||||||
|
for (let i = sections.length - 1; i >= 0; i--) {
|
||||||
|
if (sections[i].isRemoved()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cm === sections[i].cm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
return nextPrevEditor(cm, -1);
|
return nextPrevEditor(cm, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,55 +274,6 @@ function createSectionsEditor(style) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextPrevEditorOnKeydown(cm, event) {
|
|
||||||
const key = event.which;
|
|
||||||
if (key < 37 || key > 40 || event.shiftKey || event.altKey || event.metaKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const {line, ch} = cm.getCursor();
|
|
||||||
switch (key) {
|
|
||||||
case 37:
|
|
||||||
// arrow Left
|
|
||||||
if (line || ch) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// fallthrough to arrow Up
|
|
||||||
case 38:
|
|
||||||
// arrow Up
|
|
||||||
if (line > 0 || cm === sections[0].cm) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
cm = prevEditor(cm);
|
|
||||||
cm.setCursor(cm.doc.size - 1, key === 37 ? 1e20 : ch);
|
|
||||||
break;
|
|
||||||
case 39:
|
|
||||||
// arrow Right
|
|
||||||
if (line < cm.doc.size - 1 || ch < cm.getLine(line).length - 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// fallthrough to arrow Down
|
|
||||||
case 40:
|
|
||||||
// arrow Down
|
|
||||||
if (line < cm.doc.size - 1 || cm === sections[sections.length - 1].cm) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
cm = nextEditor(cm);
|
|
||||||
cm.setCursor(0, 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// FIXME: what is this?
|
|
||||||
// const animation = (cm.getSection().firstElementChild.getAnimations() || [])[0];
|
|
||||||
// if (animation) {
|
|
||||||
// animation.playbackRate = -1;
|
|
||||||
// animation.currentTime = 2000;
|
|
||||||
// animation.play();
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollEntirePageOnCtrlShift(event) {
|
function scrollEntirePageOnCtrlShift(event) {
|
||||||
// make Shift-Ctrl-Wheel scroll entire page even when mouse is over a code editor
|
// make Shift-Ctrl-Wheel scroll entire page even when mouse is over a code editor
|
||||||
if (event.shiftKey && event.ctrlKey && !event.altKey && !event.metaKey) {
|
if (event.shiftKey && event.ctrlKey && !event.altKey && !event.metaKey) {
|
||||||
|
@ -396,7 +289,7 @@ function createSectionsEditor(style) {
|
||||||
popup.codebox.execCommand('selectAll');
|
popup.codebox.execCommand('selectAll');
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromMozillaFormat(text = '') {
|
function showMozillaFormatImport(text = '') {
|
||||||
const popup = showCodeMirrorPopup(t('styleFromMozillaFormatPrompt'),
|
const popup = showCodeMirrorPopup(t('styleFromMozillaFormatPrompt'),
|
||||||
$create('.buttons', [
|
$create('.buttons', [
|
||||||
$create('button', {
|
$create('button', {
|
||||||
|
@ -612,7 +505,19 @@ function createSectionsEditor(style) {
|
||||||
if (!init) {
|
if (!init) {
|
||||||
init = {code: '', urlPrefixes: ['http://example.com']};
|
init = {code: '', urlPrefixes: ['http://example.com']};
|
||||||
}
|
}
|
||||||
const section = createSection(init);
|
const section = createSection({
|
||||||
|
originalSection: init,
|
||||||
|
genId,
|
||||||
|
dirty,
|
||||||
|
showMozillaFormatImport,
|
||||||
|
removeSection,
|
||||||
|
restoreSection,
|
||||||
|
insertSectionAfter,
|
||||||
|
moveSectionUp,
|
||||||
|
moveSectionDown,
|
||||||
|
prevEditor,
|
||||||
|
nextEditor
|
||||||
|
});
|
||||||
if (base) {
|
if (base) {
|
||||||
const index = sections.indexOf(base);
|
const index = sections.indexOf(base);
|
||||||
sections.splice(index + 1, 0, section);
|
sections.splice(index + 1, 0, section);
|
||||||
|
@ -650,286 +555,6 @@ function createSectionsEditor(style) {
|
||||||
updateSectionOrder();
|
updateSectionOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSection(originalSection) {
|
|
||||||
const sectionId = INC_ID++;
|
|
||||||
const el = template.section.cloneNode(true);
|
|
||||||
const cm = cmFactory.create(wrapper => {
|
|
||||||
el.insertBefore(wrapper, $('.code-label', el).nextSibling);
|
|
||||||
}, {value: originalSection.code});
|
|
||||||
|
|
||||||
const changeListeners = new Set();
|
|
||||||
|
|
||||||
const appliesToContainer = $('.applies-to-list', el);
|
|
||||||
const appliesTo = [];
|
|
||||||
for (const [key, fnName] of Object.entries(propertyToCss)) {
|
|
||||||
if (originalSection[key]) {
|
|
||||||
originalSection[key].forEach(value =>
|
|
||||||
insertApplyAfter({type: fnName, value})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!appliesTo.length) {
|
|
||||||
insertApplyAfter({all: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
let changeGeneration = cm.changeGeneration();
|
|
||||||
let removed = false;
|
|
||||||
|
|
||||||
registerEvents();
|
|
||||||
updateRegexpTester();
|
|
||||||
createResizeGrip(cm);
|
|
||||||
|
|
||||||
linter.enableForEditor(cm);
|
|
||||||
|
|
||||||
let lastActive = 0;
|
|
||||||
|
|
||||||
const section = {
|
|
||||||
id: sectionId,
|
|
||||||
el,
|
|
||||||
cm,
|
|
||||||
render,
|
|
||||||
getCode,
|
|
||||||
getModel,
|
|
||||||
remove,
|
|
||||||
restore,
|
|
||||||
isRemoved: () => removed,
|
|
||||||
onChange,
|
|
||||||
off,
|
|
||||||
getLastActive: () => lastActive,
|
|
||||||
appliesTo
|
|
||||||
};
|
|
||||||
return section;
|
|
||||||
|
|
||||||
function onChange(fn) {
|
|
||||||
changeListeners.add(fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
function off(fn) {
|
|
||||||
changeListeners.delete(fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitSectionChange() {
|
|
||||||
for (const fn of changeListeners) {
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getModel() {
|
|
||||||
const section = {
|
|
||||||
code: cm.getValue()
|
|
||||||
};
|
|
||||||
for (const apply of appliesTo) {
|
|
||||||
if (apply.all) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const key = CssToProperty[apply.getType()];
|
|
||||||
if (!section[key]) {
|
|
||||||
section[key] = [];
|
|
||||||
}
|
|
||||||
section[key].push(apply.getValue());
|
|
||||||
}
|
|
||||||
return section;
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerEvents() {
|
|
||||||
cm.on('changes', () => {
|
|
||||||
const newGeneration = cm.changeGeneration();
|
|
||||||
dirty.modify(`section.${sectionId}.code`, changeGeneration, newGeneration);
|
|
||||||
changeGeneration = newGeneration;
|
|
||||||
emitSectionChange();
|
|
||||||
});
|
|
||||||
cm.on('paste', (cm, event) => {
|
|
||||||
const text = event.clipboardData.getData('text') || '';
|
|
||||||
if (
|
|
||||||
text.includes('@-moz-document') &&
|
|
||||||
text.replace(/\/\*[\s\S]*?(?:\*\/|$)/g, '')
|
|
||||||
.match(/@-moz-document[\s\r\n]+(url|url-prefix|domain|regexp)\(/)
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
fromMozillaFormat(text);
|
|
||||||
}
|
|
||||||
// FIXME: why?
|
|
||||||
// if (editors.length === 1) {
|
|
||||||
// setTimeout(() => {
|
|
||||||
// if (cm.display.sizer.clientHeight > cm.display.wrapper.clientHeight) {
|
|
||||||
// maximizeCodeHeight.stats = null;
|
|
||||||
// maximizeCodeHeight(cm.getSection(), true);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
});
|
|
||||||
if (!FIREFOX) {
|
|
||||||
cm.on('mousedown', (cm, event) => toggleContextMenuDelete.call(cm, event));
|
|
||||||
}
|
|
||||||
cm.on('focus', () => {
|
|
||||||
lastActive = Date.now();
|
|
||||||
});
|
|
||||||
|
|
||||||
cm.display.wrapper.addEventListener('keydown', event =>
|
|
||||||
nextPrevEditorOnKeydown(cm, event), true);
|
|
||||||
|
|
||||||
$('.applies-to-help', el).addEventListener('click', showAppliesToHelp);
|
|
||||||
$('.remove-section', el).addEventListener('click', () => removeSection(section));
|
|
||||||
$('.add-section', el).addEventListener('click', () => insertSectionAfter(undefined, section));
|
|
||||||
$('.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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCode() {
|
|
||||||
return cm.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
function remove(destroy = false) {
|
|
||||||
linter.disableForEditor(cm);
|
|
||||||
el.classList.add('removed');
|
|
||||||
removed = true;
|
|
||||||
appliesTo.forEach(a => a.remove());
|
|
||||||
if (destroy) {
|
|
||||||
cmFactory.destroy(cm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function restore() {
|
|
||||||
linter.enableForEditor(cm);
|
|
||||||
el.classList.remove('removed');
|
|
||||||
removed = false;
|
|
||||||
appliesTo.forEach(a => a.restore());
|
|
||||||
render();
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
cm.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateRegexpTester() {
|
|
||||||
const regexps = appliesTo.filter(a => a.getType() === 'regexp')
|
|
||||||
.map(a => a.getValue());
|
|
||||||
if (regexps.length) {
|
|
||||||
el.classList.add('has-regexp');
|
|
||||||
regExpTester.update(regexps);
|
|
||||||
} else {
|
|
||||||
el.classList.remove('has-regexp');
|
|
||||||
regExpTester.toggle(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function insertApplyAfter(init, base) {
|
|
||||||
const apply = createApply(init);
|
|
||||||
if (base) {
|
|
||||||
const index = appliesTo.indexOf(base);
|
|
||||||
appliesTo.splice(index + 1, 0, apply);
|
|
||||||
appliesToContainer.insertBefore(apply.el, base.el.nextSibling);
|
|
||||||
} else {
|
|
||||||
appliesTo.push(apply);
|
|
||||||
appliesToContainer.appendChild(apply.el);
|
|
||||||
}
|
|
||||||
dirty.add(apply, apply);
|
|
||||||
if (appliesTo.length > 1 && appliesTo[0].all) {
|
|
||||||
removeApply(appliesTo[0]);
|
|
||||||
}
|
|
||||||
emitSectionChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeApply(apply) {
|
|
||||||
const index = appliesTo.indexOf(apply);
|
|
||||||
appliesTo.splice(index, 1);
|
|
||||||
apply.remove();
|
|
||||||
apply.el.remove();
|
|
||||||
dirty.remove(apply, apply);
|
|
||||||
if (!appliesTo.length) {
|
|
||||||
insertApplyAfter({all: true});
|
|
||||||
}
|
|
||||||
emitSectionChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
function createApply({type = 'url', value, all = false}) {
|
|
||||||
const applyId = INC_ID++;
|
|
||||||
const dirtyPrefix = `section.${sectionId}.apply.${applyId}`;
|
|
||||||
const el = all ? template.appliesToEverything.cloneNode(true) :
|
|
||||||
template.appliesTo.cloneNode(true);
|
|
||||||
|
|
||||||
const selectEl = !all && $('.applies-type', el);
|
|
||||||
if (selectEl) {
|
|
||||||
selectEl.value = type;
|
|
||||||
selectEl.addEventListener('change', () => {
|
|
||||||
const oldKey = type;
|
|
||||||
dirty.modify(`${dirtyPrefix}.type`, type, selectEl.value);
|
|
||||||
type = selectEl.value;
|
|
||||||
if (oldKey === 'regexp' || type === 'regexp') {
|
|
||||||
updateRegexpTester();
|
|
||||||
}
|
|
||||||
emitSectionChange();
|
|
||||||
validate();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const valueEl = !all && $('.applies-value', el);
|
|
||||||
if (valueEl) {
|
|
||||||
valueEl.value = value;
|
|
||||||
valueEl.addEventListener('input', () => {
|
|
||||||
dirty.modify(`${dirtyPrefix}.value`, value, valueEl.value);
|
|
||||||
value = valueEl.value;
|
|
||||||
if (type === 'regexp') {
|
|
||||||
updateRegexpTester();
|
|
||||||
}
|
|
||||||
emitSectionChange();
|
|
||||||
});
|
|
||||||
valueEl.addEventListener('change', validate);
|
|
||||||
}
|
|
||||||
|
|
||||||
const apply = {
|
|
||||||
id: applyId,
|
|
||||||
all,
|
|
||||||
remove,
|
|
||||||
restore,
|
|
||||||
el,
|
|
||||||
getType: () => type,
|
|
||||||
getValue: () => value,
|
|
||||||
valueEl
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeButton = $('.remove-applies-to', el);
|
|
||||||
if (removeButton) {
|
|
||||||
removeButton.addEventListener('click', e => {
|
|
||||||
e.preventDefault();
|
|
||||||
removeApply(apply);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$('.add-applies-to', el).addEventListener('click', e => {
|
|
||||||
e.preventDefault();
|
|
||||||
insertApplyAfter({type, value: ''}, apply);
|
|
||||||
});
|
|
||||||
|
|
||||||
return apply;
|
|
||||||
|
|
||||||
function validate() {
|
|
||||||
if (type !== 'regexp' || tryRegExp(value)) {
|
|
||||||
valueEl.setCustomValidity('');
|
|
||||||
} else {
|
|
||||||
valueEl.setCustomValidity(t('styleBadRegexp'));
|
|
||||||
setTimeout(() => valueEl.reportValidity());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function remove() {
|
|
||||||
dirty.remove(`${dirtyPrefix}.type`, type);
|
|
||||||
dirty.remove(`${dirtyPrefix}.value`, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function restore() {
|
|
||||||
dirty.add(`${dirtyPrefix}.type`, type);
|
|
||||||
dirty.add(`${dirtyPrefix}.value`, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceSections(originalSections) {
|
function replaceSections(originalSections) {
|
||||||
for (const section of sections) {
|
for (const section of sections) {
|
||||||
section.remove(true);
|
section.remove(true);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user