Editor fixes, make sectioned editor open quickly again (#1061)
* make usercss editor full-height again * make sectioned editor open quickly again * remove leftovers * autofocus when add/clone button is clicked * don't fit to content on clicking the add button * scroll the window to show a manually added section entirely * autofocus on a manually added applies-to * disable Save button while loading * use standard CSS for a focused CodeMirror outline * trigger refresh sooner by one viewport in advance * declare refreshOnView as a standard function * run fixedHeader asynchronously to prevent self-triggering * account for header in compact mode when fitting to content * code cosmetics
This commit is contained in:
parent
ad24ee0c15
commit
60fc6f2456
|
@ -91,7 +91,6 @@
|
||||||
<script src="edit/colorpicker-helper.js"></script>
|
<script src="edit/colorpicker-helper.js"></script>
|
||||||
<script src="edit/beautify.js"></script>
|
<script src="edit/beautify.js"></script>
|
||||||
<script src="edit/show-keymap-help.js"></script>
|
<script src="edit/show-keymap-help.js"></script>
|
||||||
<script src="edit/refresh-on-view.js"></script>
|
|
||||||
<script src="edit/codemirror-themes.js"></script>
|
<script src="edit/codemirror-themes.js"></script>
|
||||||
|
|
||||||
<script src="edit/source-editor.js"></script>
|
<script src="edit/source-editor.js"></script>
|
||||||
|
@ -305,7 +304,7 @@
|
||||||
</section>
|
</section>
|
||||||
<section id="actions">
|
<section id="actions">
|
||||||
<div>
|
<div>
|
||||||
<button id="save-button" i18n-text="styleSaveLabel" data-hotkey-tooltip="save"></button>
|
<button id="save-button" i18n-text="styleSaveLabel" data-hotkey-tooltip="save" disabled></button>
|
||||||
<button id="beautify" i18n-text="styleBeautify"></button>
|
<button id="beautify" i18n-text="styleBeautify"></button>
|
||||||
<a href="manage.html" tabindex="-1"><button id="cancel-button" i18n-text="styleCancelEditLabel"></button></a>
|
<a href="manage.html" tabindex="-1"><button id="cancel-button" i18n-text="styleCancelEditLabel"></button></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
-webkit-animation: highlight 3s cubic-bezier(.18, .02, 0, .94);
|
-webkit-animation: highlight 3s cubic-bezier(.18, .02, 0, .94);
|
||||||
}
|
}
|
||||||
.CodeMirror-focused {
|
.CodeMirror-focused {
|
||||||
outline: -webkit-focus-ring-color auto 5px;
|
outline: #7dadd9 auto 1px; /* not using the ring-color hack as it became ugly in new Chrome */
|
||||||
outline-offset: -2px;
|
|
||||||
}
|
}
|
||||||
.CodeMirror-bookmark {
|
.CodeMirror-bookmark {
|
||||||
background: linear-gradient(to right, currentColor, transparent);
|
background: linear-gradient(to right, currentColor, transparent);
|
||||||
|
@ -24,13 +23,6 @@
|
||||||
width: 2em;
|
width: 2em;
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
}
|
}
|
||||||
@supports (-moz-appearance:none) {
|
|
||||||
/* restrict to FF */
|
|
||||||
.CodeMirror-focused {
|
|
||||||
outline: #7dadd9 auto 1px;
|
|
||||||
outline-offset: -1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.CodeMirror-search-field {
|
.CodeMirror-search-field {
|
||||||
width: 10em;
|
width: 10em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,7 @@ label {
|
||||||
#sections {
|
#sections {
|
||||||
padding-left: 280px;
|
padding-left: 280px;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
#sections h2 {
|
#sections h2 {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
@ -278,12 +279,6 @@ input:invalid {
|
||||||
.section-editor .section:not(:first-child) {
|
.section-editor .section:not(:first-child) {
|
||||||
border-top: 2px solid hsl(0, 0%, 80%);
|
border-top: 2px solid hsl(0, 0%, 80%);
|
||||||
}
|
}
|
||||||
.section-editor:not(.section-editor-ready) .section {
|
|
||||||
opacity: 0 !important;
|
|
||||||
}
|
|
||||||
.section-editor:not(.section-editor-ready) .CodeMirror {
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
.add-section:after {
|
.add-section:after {
|
||||||
content: attr(short-text);
|
content: attr(short-text);
|
||||||
}
|
}
|
||||||
|
@ -817,13 +812,8 @@ body.linter-disabled .hidden-unless-compact {
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* FIXME: remove the ID selector */
|
.single-editor {
|
||||||
#sections .single-editor {
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.single-editor .CodeMirror {
|
.single-editor .CodeMirror {
|
||||||
|
@ -843,7 +833,6 @@ body.linter-disabled .hidden-unless-compact {
|
||||||
}
|
}
|
||||||
|
|
||||||
.usercss.firefox #sections,
|
.usercss.firefox #sections,
|
||||||
.usercss.firefox .single-editor,
|
|
||||||
.usercss.firefox .CodeMirror {
|
.usercss.firefox .CodeMirror {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
@ -996,7 +985,7 @@ body.linter-disabled .hidden-unless-compact {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
#sections > * {
|
#sections > :not(.single-editor) {
|
||||||
margin: 0 .5rem;
|
margin: 0 .5rem;
|
||||||
padding: .5rem 0;
|
padding: .5rem 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -541,7 +541,7 @@ function detectLayout() {
|
||||||
body.classList.add('fixed-header');
|
body.classList.add('fixed-header');
|
||||||
}
|
}
|
||||||
}, 250);
|
}, 250);
|
||||||
window.addEventListener('scroll', fixedHeader);
|
window.addEventListener('scroll', fixedHeader, {passive: true});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
body.classList.remove('compact-layout');
|
body.classList.remove('compact-layout');
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
/* global CodeMirror */
|
|
||||||
/*
|
|
||||||
Initialization of the multi-sections editor is slow if there are many editors
|
|
||||||
e.g. https://github.com/openstyles/stylus/issues/178. So we only refresh the
|
|
||||||
editor when they were scroll into view.
|
|
||||||
*/
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
CodeMirror.defineExtension('refreshOnView', function () {
|
|
||||||
const cm = this;
|
|
||||||
if (typeof IntersectionObserver === 'undefined') {
|
|
||||||
// uh
|
|
||||||
cm.isRefreshed = true;
|
|
||||||
cm.refresh();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const wrapper = cm.display.wrapper;
|
|
||||||
const observer = new IntersectionObserver(entries => {
|
|
||||||
for (const entry of entries) {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
// wrapper.style.visibility = 'visible';
|
|
||||||
cm.isRefreshed = true;
|
|
||||||
cm.refresh();
|
|
||||||
observer.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
observer.observe(wrapper);
|
|
||||||
});
|
|
|
@ -296,19 +296,14 @@ function createSection({
|
||||||
|
|
||||||
function insertApplyAfter(init, base) {
|
function insertApplyAfter(init, base) {
|
||||||
const apply = createApply(init);
|
const apply = createApply(init);
|
||||||
if (base) {
|
appliesTo.splice(base ? appliesTo.indexOf(base) + 1 : appliesTo.length, 0, apply);
|
||||||
const index = appliesTo.indexOf(base);
|
appliesToContainer.insertBefore(apply.el, base ? base.el.nextSibling : null);
|
||||||
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);
|
dirty.add(apply, apply);
|
||||||
if (appliesTo.length > 1 && appliesTo[0].all) {
|
if (appliesTo.length > 1 && appliesTo[0].all) {
|
||||||
removeApply(appliesTo[0]);
|
removeApply(appliesTo[0]);
|
||||||
}
|
}
|
||||||
emitSectionChange();
|
emitSectionChange();
|
||||||
|
return apply;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeApply(apply) {
|
function removeApply(apply) {
|
||||||
|
@ -380,7 +375,8 @@ function createSection({
|
||||||
}
|
}
|
||||||
$('.add-applies-to', el).addEventListener('click', e => {
|
$('.add-applies-to', el).addEventListener('click', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
insertApplyAfter({type, value: ''}, apply);
|
const newApply = insertApplyAfter({type, value: ''}, apply);
|
||||||
|
$('input', newApply.el).focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
return apply;
|
return apply;
|
||||||
|
|
|
@ -29,6 +29,9 @@ function createSectionsEditor({style, onTitleChanged}) {
|
||||||
updateLivePreview();
|
updateLivePreview();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateHeader();
|
||||||
|
rerouteHotkeys(true);
|
||||||
|
|
||||||
$('#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', () => showMozillaFormatImport());
|
$('#from-mozilla').addEventListener('click', () => showMozillaFormatImport());
|
||||||
|
@ -47,17 +50,22 @@ function createSectionsEditor({style, onTitleChanged}) {
|
||||||
.forEach(e => e.addEventListener('mousedown', toggleContextMenuDelete));
|
.forEach(e => e.addEventListener('mousedown', toggleContextMenuDelete));
|
||||||
}
|
}
|
||||||
|
|
||||||
let sectionOrder = '';
|
const xo = window.IntersectionObserver && new IntersectionObserver(entries => {
|
||||||
const initializing = new Promise(resolve => initSection({
|
for (const {isIntersecting, target} of entries) {
|
||||||
sections: style.sections.slice(),
|
if (isIntersecting) {
|
||||||
done:() => {
|
target.CodeMirror.refresh();
|
||||||
dirty.clear();
|
xo.unobserve(target);
|
||||||
rerouteHotkeys(true);
|
|
||||||
resolve();
|
|
||||||
updateHeader();
|
|
||||||
sections.forEach(fitToContent);
|
|
||||||
}
|
}
|
||||||
}));
|
}
|
||||||
|
}, {rootMargin: '100%'});
|
||||||
|
const refreshOnView = (cm, force) =>
|
||||||
|
force || !xo ?
|
||||||
|
cm.refresh() :
|
||||||
|
xo.observe(cm.display.wrapper);
|
||||||
|
|
||||||
|
let sectionOrder = '';
|
||||||
|
let headerOffset; // in compact mode the header is at the top so it reduces the available height
|
||||||
|
const initializing = initSections(style.sections.slice());
|
||||||
|
|
||||||
const livePreview = createLivePreview();
|
const livePreview = createLivePreview();
|
||||||
livePreview.show(Boolean(style.id));
|
livePreview.show(Boolean(style.id));
|
||||||
|
@ -83,28 +91,26 @@ function createSectionsEditor({style, onTitleChanged}) {
|
||||||
};
|
};
|
||||||
|
|
||||||
function fitToContent(section) {
|
function fitToContent(section) {
|
||||||
if (section.cm.isRefreshed) {
|
const {cm, cm: {display: {wrapper, sizer}}} = section;
|
||||||
|
if (cm.display.renderedView) {
|
||||||
resize();
|
resize();
|
||||||
} else {
|
} else {
|
||||||
section.cm.on('update', resize);
|
cm.on('update', resize);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resize() {
|
function resize() {
|
||||||
let contentHeight = section.el.querySelector('.CodeMirror-sizer').offsetHeight;
|
let contentHeight = sizer.offsetHeight;
|
||||||
if (contentHeight < section.cm.defaultTextHeight()) {
|
if (contentHeight < cm.defaultTextHeight()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
contentHeight += 9; // border & resize grip
|
if (headerOffset == null) {
|
||||||
section.cm.off('update', resize);
|
headerOffset = wrapper.getBoundingClientRect().top;
|
||||||
const cmHeight = section.cm.getWrapperElement().offsetHeight;
|
|
||||||
const maxHeight = cmHeight + window.innerHeight - section.el.offsetHeight;
|
|
||||||
section.cm.setSize(null, Math.min(contentHeight, maxHeight));
|
|
||||||
if (sections.every(s => s.cm.isRefreshed)) {
|
|
||||||
fitToAvailableSpace();
|
|
||||||
}
|
}
|
||||||
setTimeout(() => {
|
contentHeight += 9; // border & resize grip
|
||||||
container.classList.add('section-editor-ready');
|
cm.off('update', resize);
|
||||||
}, 50);
|
const cmHeight = wrapper.offsetHeight;
|
||||||
|
const maxHeight = (window.innerHeight - headerOffset) - (section.el.offsetHeight - cmHeight);
|
||||||
|
cm.setSize(null, Math.min(contentHeight, maxHeight));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,7 +373,7 @@ function createSectionsEditor({style, onTitleChanged}) {
|
||||||
if (replaceOldStyle) {
|
if (replaceOldStyle) {
|
||||||
return replaceSections(sections);
|
return replaceSections(sections);
|
||||||
}
|
}
|
||||||
return new Promise(resolve => initSection({sections, done: resolve, focusOn: false}));
|
return initSections(sections, {focusOn: false});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
$('.dismiss').dispatchEvent(new Event('click'));
|
$('.dismiss').dispatchEvent(new Event('click'));
|
||||||
|
@ -472,38 +478,35 @@ function createSectionsEditor({style, onTitleChanged}) {
|
||||||
livePreview.update(getModel());
|
livePreview.update(getModel());
|
||||||
}
|
}
|
||||||
|
|
||||||
function initSection({
|
function initSections(originalSections, {
|
||||||
sections: originalSections,
|
|
||||||
total = originalSections.length,
|
total = originalSections.length,
|
||||||
focusOn = 0,
|
focusOn = 0,
|
||||||
done
|
} = {}) {
|
||||||
}) {
|
let done;
|
||||||
container.classList.add('hidden');
|
return new Promise(resolve => {
|
||||||
chunk();
|
done = resolve;
|
||||||
|
chunk(true);
|
||||||
function chunk() {
|
});
|
||||||
if (!originalSections.length) {
|
function chunk(forceRefresh) {
|
||||||
setGlobalProgress();
|
|
||||||
if (focusOn !== false) {
|
|
||||||
setTimeout(() => sections[focusOn].cm.focus());
|
|
||||||
}
|
|
||||||
container.classList.remove('hidden');
|
|
||||||
for (const section of sections) {
|
|
||||||
section.cm.refreshOnView();
|
|
||||||
}
|
|
||||||
if (done) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const t0 = performance.now();
|
const t0 = performance.now();
|
||||||
while (originalSections.length && performance.now() - t0 < 100) {
|
while (originalSections.length && performance.now() - t0 < 100) {
|
||||||
insertSectionAfter(originalSections.shift());
|
insertSectionAfter(originalSections.shift(), undefined, forceRefresh);
|
||||||
|
dirty.clear();
|
||||||
|
if (focusOn !== false && sections[focusOn]) {
|
||||||
|
sections[focusOn].cm.focus();
|
||||||
|
focusOn = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setGlobalProgress(total - originalSections.length, total);
|
setGlobalProgress(total - originalSections.length, total);
|
||||||
|
if (!originalSections.length) {
|
||||||
|
setGlobalProgress();
|
||||||
|
fitToAvailableSpace();
|
||||||
|
done();
|
||||||
|
} else {
|
||||||
setTimeout(chunk);
|
setTimeout(chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function removeSection(section) {
|
function removeSection(section) {
|
||||||
if (sections.every(s => s.isRemoved() || s === section)) {
|
if (sections.every(s => s.isRemoved() || s === section)) {
|
||||||
|
@ -540,7 +543,7 @@ function createSectionsEditor({style, onTitleChanged}) {
|
||||||
updateLivePreview();
|
updateLivePreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertSectionAfter(init, base) {
|
function insertSectionAfter(init, base, forceRefresh) {
|
||||||
if (!init) {
|
if (!init) {
|
||||||
init = {code: '', urlPrefixes: ['http://example.com']};
|
init = {code: '', urlPrefixes: ['http://example.com']};
|
||||||
}
|
}
|
||||||
|
@ -557,15 +560,18 @@ function createSectionsEditor({style, onTitleChanged}) {
|
||||||
prevEditor,
|
prevEditor,
|
||||||
nextEditor
|
nextEditor
|
||||||
});
|
});
|
||||||
if (base) {
|
const {cm} = section;
|
||||||
const index = sections.indexOf(base);
|
sections.splice(base ? sections.indexOf(base) + 1 : sections.length, 0, section);
|
||||||
sections.splice(index + 1, 0, section);
|
container.insertBefore(section.el, base ? base.el.nextSibling : null);
|
||||||
container.insertBefore(section.el, base.el.nextSibling);
|
refreshOnView(cm, forceRefresh);
|
||||||
} else {
|
if (!base || init.code) {
|
||||||
sections.push(section);
|
// Fit a) during startup or b) when the clone button is clicked on a section with some code
|
||||||
container.appendChild(section.el);
|
fitToContent(section);
|
||||||
|
}
|
||||||
|
if (base) {
|
||||||
|
cm.focus();
|
||||||
|
setTimeout(scrollToEditor, 0, cm);
|
||||||
}
|
}
|
||||||
section.render();
|
|
||||||
updateSectionOrder();
|
updateSectionOrder();
|
||||||
section.onChange(updateLivePreview);
|
section.onChange(updateLivePreview);
|
||||||
updateLivePreview();
|
updateLivePreview();
|
||||||
|
@ -599,7 +605,7 @@ function createSectionsEditor({style, onTitleChanged}) {
|
||||||
}
|
}
|
||||||
sections.length = 0;
|
sections.length = 0;
|
||||||
container.textContent = '';
|
container.textContent = '';
|
||||||
return new Promise(resolve => initSection({sections: originalSections, done: resolve}));
|
return initSections(originalSections);
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceStyle(newStyle, codeIsUpdated) {
|
function replaceStyle(newStyle, codeIsUpdated) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user