add buttons to restore, clone, move a section

This commit is contained in:
tophf 2018-07-22 11:59:56 +03:00
parent 0a3ffb0bc8
commit 0c58783a6c
7 changed files with 130 additions and 28 deletions

View File

@ -205,6 +205,14 @@
"configOnChangeTooltip": {
"message": "Autosave and apply changes automatically"
},
"genericAdd": {
"message": "Add",
"description": "Used in various places for an action that adds something"
},
"genericClone": {
"message": "Clone",
"description": "Used in various places for an action that clones something"
},
"genericError": {
"message": "Error",
"description": "Used in various places to indicate some error occurred."
@ -840,6 +848,10 @@
"message": "Remove section",
"description": "Label for the button to remove a section"
},
"sectionRestore": {
"message": "Restore removed section",
"description": "Label for the button to restore a removed section"
},
"shortcuts": {
"message": "Shortcuts",
"description": "Go to shortcut configuration"

View File

@ -121,7 +121,7 @@
</template>
<template data-id="section">
<div>
<div class="section">
<label i18n-text="sectionCode" class="code-label"></label>
<br>
<div class="applies-to">
@ -134,13 +134,23 @@
</div>
<div class="edit-actions">
<button class="remove-section" i18n-text="sectionRemove"></button>
<button class="add-section" i18n-text="sectionAdd"></button>
<button class="add-section" i18n-long-text="sectionAdd" i18n-short-text="genericAdd"></button>
<button class="clone-section" i18n-text="genericClone"></button>
<button class="move-section-up"></button>
<button class="move-section-down"></button>
<button class="beautify-section" i18n-text="styleBeautify"></button>
<button class="test-regexp" i18n-text="styleRegexpTestButton"></button>
</div>
</div>
</template>
<!-- not using DIV to make our CSS work for #sections > div:only-of-type .remove-section -->
<template data-id="deletedSection">
<p class="deleted-section">
<button class="restore-section" i18n-text="sectionRestore"></button>
</p>
</template>
<template data-id="searchReplaceDialog">
<div id="search-replace-dialog">
<div data-type="main">

View File

@ -264,16 +264,22 @@ input:invalid {
margin-top: 4em;
}
/************ content ***********/
#sections > div {
#sections > * {
margin: 0.7rem;
padding: 1rem 1rem .3rem;
}
#sections > div:first-of-type {
#sections > *:first-child {
padding: 0 1rem .3rem;
}
#sections > div:not(:first-of-type) {
#sections > *:not(:first-child) {
border-top: 2px solid hsl(0, 0%, 80%);
}
.add-section:after {
content: attr(short-text);
}
#sections > div:only-of-type .add-section:after {
content: attr(long-text);
}
#sections > div:only-of-type .remove-section {
display: none;
}
@ -291,17 +297,35 @@ input:invalid {
#sections {
counter-reset: codebox;
}
#sections > div > label {
#sections > .section > label {
animation: 2s highlight;
animation-play-state: paused;
animation-direction: reverse;
animation-fill-mode: both;
}
#sections > div > label::after {
#sections > .section > label::after {
counter-increment: codebox;
content: counter(codebox);
margin-left: 0.25rem;
}
.section:only-of-type .move-section-up,
.section:only-of-type .move-section-down {
display: none;
}
.move-section-up:after {
content: "";
display: block;
border-style: solid;
border-width: 0 .3em .5em .3em;
border-color: transparent transparent currentColor transparent;
}
.move-section-down:after {
content: "";
display: block;
border-style: solid;
border-width: .5em .3em 0 .3em;
border-color: currentColor transparent transparent transparent;
}
/* code */
.code {
height: 10rem;
@ -729,7 +753,7 @@ html:not(.usercss) .usercss-only,
}
#sections .single-editor,
#sections > div.single-editor:first-of-type {
#sections > .single-editor:first-child {
margin: 0;
padding: 0;
display: flex;
@ -908,11 +932,11 @@ html:not(.usercss) .usercss-only,
flex-direction: column;
flex: 1;
}
#sections > div {
#sections > * {
margin: 0 .5rem .5rem;
padding: .5rem 0 0;
}
#sections > div:first-of-type {
#sections > *:first-child {
margin: .5rem;
padding: 0;
}

View File

@ -332,7 +332,7 @@ function isCleanGlobal() {
function setCleanGlobal() {
setCleanItem($('#sections'), true);
$$('#header, #sections > div').forEach(setCleanSection);
$$('#header, #sections > .section').forEach(setCleanSection);
// forget the dirty applies-to ids from a deleted section after the style was saved
dirty = {};
}

View File

@ -1,6 +1,7 @@
/* global CodeMirror messageBox */
/* global editors makeSectionVisible showCodeMirrorPopup showHelp */
/* global loadScript require CSSLint stylelint */
/* global clipString */
'use strict';
onDOMready().then(loadLinterAssets);
@ -288,10 +289,6 @@ function updateLintReportInternal(scope, {postponeNewIssues} = {}) {
result.fixedSome |= lintState.reportDisplayed && oldMarkers.size;
return result;
}
function clipString(str, limit) {
return str.length <= limit ? str : str.substr(0, limit) + '...';
}
}
function renderLintReport(someBlockChanged) {

View File

@ -5,6 +5,7 @@ global onChange indicateCodeChange initHooks setCleanGlobal
global fromMozillaFormat maximizeCodeHeight toggleContextMenuDelete
global setCleanItem updateTitle updateLintReportIfEnabled renderLintReport
global showAppliesToHelp beautify regExpTester setGlobalProgress setCleanSection
global clipString
*/
'use strict';
@ -14,7 +15,7 @@ function initWithSectionStyle(style, codeIsUpdated) {
$('#url').href = style.url || '';
if (codeIsUpdated !== false) {
editors.length = 0;
getSections().forEach(div => div.remove());
$('#sections').textContent = '';
addSections(style.sections.length ? style.sections : [{code: ''}]);
initHooks();
}
@ -76,9 +77,12 @@ function addSections(sections, onAdded = () => {}) {
function addSection(event, section) {
const div = template.section.cloneNode(true);
$('.applies-to-help', div).addEventListener('click', showAppliesToHelp, false);
$('.remove-section', div).addEventListener('click', removeSection, false);
$('.add-section', div).addEventListener('click', addSection, false);
$('.applies-to-help', div).addEventListener('click', showAppliesToHelp);
$('.remove-section', div).addEventListener('click', removeSection);
$('.add-section', div).addEventListener('click', addSection);
$('.clone-section', div).addEventListener('click', cloneSection);
$('.move-section-up', div).addEventListener('click', moveSection);
$('.move-section-down', div).addEventListener('click', moveSection);
$('.beautify-section', div).addEventListener('click', beautify);
const code = (section || {}).code || '';
@ -134,8 +138,11 @@ function addSection(event, section) {
const sections = $('#sections');
let cm;
if (event) {
const clickedSection = getSectionForChild(event.target);
sections.insertBefore(div, clickedSection.nextElementSibling);
let clickedSection = event && getSectionForChild(event.target, {includeDeleted: true});
clickedSection.insertAdjacentElement('afterend', div);
while (clickedSection && !clickedSection.matches('.section')) {
clickedSection = clickedSection.previousElementSibling;
}
const newIndex = getSections().indexOf(clickedSection) + 1;
cm = setupCodeMirror(div, code, newIndex);
makeSectionVisible(cm);
@ -192,7 +199,31 @@ function addAppliesTo(list, type, value) {
if (toFocus) toFocus.focus();
}
function setupCodeMirror(sectionDiv, code, index) {
function cloneSection(event) {
const section = getSectionForChild(event.target);
addSection(event, getSectionsHashes([section]).pop());
setCleanItem($('#sections'), false);
updateTitle();
}
function moveSection(event) {
const section = getSectionForChild(event.target);
const dir = event.target.closest('.move-section-up') ? -1 : 1;
const cm = section.CodeMirror;
const index = editors.indexOf(cm);
const newIndex = (index + dir + editors.length) % editors.length;
const currentNextEl = section.nextElementSibling;
const newSection = editors[newIndex].getSection();
newSection.insertAdjacentElement('afterend', section);
section.parentNode.insertBefore(newSection, currentNextEl || null);
cm.focus();
editors[index] = editors[newIndex];
editors[newIndex] = cm;
setCleanItem($('#sections'), false);
updateTitle();
}
function setupCodeMirror(sectionDiv, code, index = editors.length) {
const cm = CodeMirror(wrapper => {
$('.code-label', sectionDiv).insertAdjacentElement('afterend', wrapper);
}, {
@ -269,7 +300,7 @@ function setupCodeMirror(sectionDiv, code, index) {
});
};
editors.splice(index || editors.length, 0, cm);
editors.splice(index, 0, cm);
return cm;
}
@ -385,17 +416,17 @@ function toggleSectionHeight(cm) {
}
}
function getSectionForChild(e) {
return e.closest('#sections > div');
function getSectionForChild(el, {includeDeleted} = {}) {
return el.closest(`#sections > ${includeDeleted ? '*' : '.section'}`);
}
function getSections() {
return $$('#sections > div');
return $$('#sections > .section');
}
function getSectionsHashes() {
function getSectionsHashes(elements = getSections()) {
const sections = [];
for (const div of getSections()) {
for (const div of elements) {
const meta = {urls: [], urlPrefixes: [], domains: [], regexps: []};
for (const li of $('.applies-to-list', div).childNodes) {
if (li.className === template.appliesToEverything.className) {
@ -430,6 +461,29 @@ function removeAppliesTo(event) {
function removeSection(event) {
const section = getSectionForChild(event.target);
const cm = section.CodeMirror;
if (event instanceof Event && (!cm.isClean() || !cm.isBlank())) {
const stub = template.deletedSection.cloneNode(true);
const MAX_LINES = 10;
const lines = [];
cm.doc.iter(0, MAX_LINES + 1, ({text}) => lines.push(text) && false);
stub.title = t('sectionCode') + '\n' +
'-'.repeat(20) + '\n' +
lines.slice(0, MAX_LINES).map(s => clipString(s, 100)).join('\n') +
(lines.length > MAX_LINES ? '\n...' : '');
$('.restore-section', stub).onclick = () => {
let el = stub;
while (el && !el.matches('.section')) {
el = el.previousElementSibling;
}
const index = el ? editors.indexOf(el) + 1 : 0;
editors.splice(index, 0, cm);
stub.parentNode.replaceChild(section, stub);
setCleanItem(section, false);
updateTitle();
cm.focus();
};
section.insertAdjacentElement('afterend', stub);
}
setCleanItem($('#sections'), false);
removeAreaAndSetDirty(section);
editors.splice(editors.indexOf(cm), 1);

View File

@ -116,3 +116,8 @@ function sectionsToMozFormat(style) {
section.code;
}).join('\n\n');
}
function clipString(str, limit = 100) {
return str.length <= limit ? str : str.substr(0, limit) + '...';
}