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/source-editor.js"></script>
|
||||
<script src="edit/sections-editor-section.js"></script>
|
||||
<script src="edit/sections-editor.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
|
||||
createSourceEditor queryTabs sessionStorageHash getOwnTab FIREFOX API tryCatch
|
||||
closeCurrentTab messageBox debounce workerUtil
|
||||
beautify
|
||||
beautify ignoreChromeError
|
||||
moveFocus msg createSectionsEditor rerouteHotkeys */
|
||||
/* exported showCodeMirrorPopup editorWorker */
|
||||
/* exported showCodeMirrorPopup editorWorker toggleContextMenuDelete */
|
||||
'use strict';
|
||||
|
||||
const editorWorker = workerUtil.createWorker({
|
||||
|
@ -557,3 +557,14 @@ function isWindowMaximized() {
|
|||
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
|
||||
CodeMirror propertyToCss
|
||||
regExpTester linter createLivePreview showCodeMirrorPopup
|
||||
sectionsToMozFormat messageBox clipString beautify
|
||||
rerouteHotkeys cmFactory CssToProperty template $ $$ $create t FIREFOX API
|
||||
debounce tryRegExp
|
||||
*/
|
||||
/* global dirtyReporter showHelp toggleContextMenuDelete createSection
|
||||
CodeMirror linter createLivePreview showCodeMirrorPopup
|
||||
sectionsToMozFormat messageBox clipString
|
||||
rerouteHotkeys $ $$ $create t FIREFOX API
|
||||
debounce */
|
||||
/* exported createSectionsEditor */
|
||||
'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) {
|
||||
let INC_ID = 0; // an increment id that is used by various object to track the order
|
||||
const dirty = dirtyReporter();
|
||||
|
@ -97,7 +29,7 @@ function createSectionsEditor(style) {
|
|||
|
||||
$('#to-mozilla').addEventListener('click', showMozillaFormat);
|
||||
$('#to-mozilla-help').addEventListener('click', showToMozillaHelp);
|
||||
$('#from-mozilla').addEventListener('click', () => fromMozillaFormat());
|
||||
$('#from-mozilla').addEventListener('click', () => showMozillaFormatImport());
|
||||
$('#save-button').addEventListener('click', saveStyle);
|
||||
// FIXME: this element doesn't exist?
|
||||
$('#sections-help').addEventListener('click', showSectionHelp);
|
||||
|
@ -150,15 +82,8 @@ function createSectionsEditor(style) {
|
|||
getSearchableInputs,
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
function genId() {
|
||||
return INC_ID++;
|
||||
}
|
||||
|
||||
function setGlobalProgress(done, total) {
|
||||
|
@ -180,11 +105,6 @@ function createSectionsEditor(style) {
|
|||
showHelp(t('styleMozillaFormatHeading'), t('styleToMozillaFormatHelp'));
|
||||
}
|
||||
|
||||
function showAppliesToHelp(event) {
|
||||
event.preventDefault();
|
||||
showHelp(t('appliesLabel'), t('appliesHelp'));
|
||||
}
|
||||
|
||||
function showSectionHelp(event) {
|
||||
event.preventDefault();
|
||||
showHelp(t('styleSectionsTitle'), t('sectionHelp'));
|
||||
|
@ -287,11 +207,33 @@ function createSectionsEditor(style) {
|
|||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -332,55 +274,6 @@ function createSectionsEditor(style) {
|
|||
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) {
|
||||
// make Shift-Ctrl-Wheel scroll entire page even when mouse is over a code editor
|
||||
if (event.shiftKey && event.ctrlKey && !event.altKey && !event.metaKey) {
|
||||
|
@ -396,7 +289,7 @@ function createSectionsEditor(style) {
|
|||
popup.codebox.execCommand('selectAll');
|
||||
}
|
||||
|
||||
function fromMozillaFormat(text = '') {
|
||||
function showMozillaFormatImport(text = '') {
|
||||
const popup = showCodeMirrorPopup(t('styleFromMozillaFormatPrompt'),
|
||||
$create('.buttons', [
|
||||
$create('button', {
|
||||
|
@ -612,7 +505,19 @@ function createSectionsEditor(style) {
|
|||
if (!init) {
|
||||
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) {
|
||||
const index = sections.indexOf(base);
|
||||
sections.splice(index + 1, 0, section);
|
||||
|
@ -650,286 +555,6 @@ function createSectionsEditor(style) {
|
|||
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) {
|
||||
for (const section of sections) {
|
||||
section.remove(true);
|
||||
|
|
Loading…
Reference in New Issue
Block a user