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": { "configOnChangeTooltip": {
"message": "Autosave and apply changes automatically" "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": { "genericError": {
"message": "Error", "message": "Error",
"description": "Used in various places to indicate some error occurred." "description": "Used in various places to indicate some error occurred."
@ -840,6 +848,10 @@
"message": "Remove section", "message": "Remove section",
"description": "Label for the button to remove a 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": { "shortcuts": {
"message": "Shortcuts", "message": "Shortcuts",
"description": "Go to shortcut configuration" "description": "Go to shortcut configuration"

View File

@ -121,7 +121,7 @@
</template> </template>
<template data-id="section"> <template data-id="section">
<div> <div class="section">
<label i18n-text="sectionCode" class="code-label"></label> <label i18n-text="sectionCode" class="code-label"></label>
<br> <br>
<div class="applies-to"> <div class="applies-to">
@ -134,13 +134,23 @@
</div> </div>
<div class="edit-actions"> <div class="edit-actions">
<button class="remove-section" i18n-text="sectionRemove"></button> <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="beautify-section" i18n-text="styleBeautify"></button>
<button class="test-regexp" i18n-text="styleRegexpTestButton"></button> <button class="test-regexp" i18n-text="styleRegexpTestButton"></button>
</div> </div>
</div> </div>
</template> </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"> <template data-id="searchReplaceDialog">
<div id="search-replace-dialog"> <div id="search-replace-dialog">
<div data-type="main"> <div data-type="main">

View File

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

View File

@ -332,7 +332,7 @@ function isCleanGlobal() {
function setCleanGlobal() { function setCleanGlobal() {
setCleanItem($('#sections'), true); 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 // forget the dirty applies-to ids from a deleted section after the style was saved
dirty = {}; dirty = {};
} }

View File

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

View File

@ -5,6 +5,7 @@ global onChange indicateCodeChange initHooks setCleanGlobal
global fromMozillaFormat maximizeCodeHeight toggleContextMenuDelete global fromMozillaFormat maximizeCodeHeight toggleContextMenuDelete
global setCleanItem updateTitle updateLintReportIfEnabled renderLintReport global setCleanItem updateTitle updateLintReportIfEnabled renderLintReport
global showAppliesToHelp beautify regExpTester setGlobalProgress setCleanSection global showAppliesToHelp beautify regExpTester setGlobalProgress setCleanSection
global clipString
*/ */
'use strict'; 'use strict';
@ -14,7 +15,7 @@ function initWithSectionStyle(style, codeIsUpdated) {
$('#url').href = style.url || ''; $('#url').href = style.url || '';
if (codeIsUpdated !== false) { if (codeIsUpdated !== false) {
editors.length = 0; editors.length = 0;
getSections().forEach(div => div.remove()); $('#sections').textContent = '';
addSections(style.sections.length ? style.sections : [{code: ''}]); addSections(style.sections.length ? style.sections : [{code: ''}]);
initHooks(); initHooks();
} }
@ -76,9 +77,12 @@ function addSections(sections, onAdded = () => {}) {
function addSection(event, section) { function addSection(event, section) {
const div = template.section.cloneNode(true); const div = template.section.cloneNode(true);
$('.applies-to-help', div).addEventListener('click', showAppliesToHelp, false); $('.applies-to-help', div).addEventListener('click', showAppliesToHelp);
$('.remove-section', div).addEventListener('click', removeSection, false); $('.remove-section', div).addEventListener('click', removeSection);
$('.add-section', div).addEventListener('click', addSection, false); $('.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); $('.beautify-section', div).addEventListener('click', beautify);
const code = (section || {}).code || ''; const code = (section || {}).code || '';
@ -134,8 +138,11 @@ function addSection(event, section) {
const sections = $('#sections'); const sections = $('#sections');
let cm; let cm;
if (event) { if (event) {
const clickedSection = getSectionForChild(event.target); let clickedSection = event && getSectionForChild(event.target, {includeDeleted: true});
sections.insertBefore(div, clickedSection.nextElementSibling); clickedSection.insertAdjacentElement('afterend', div);
while (clickedSection && !clickedSection.matches('.section')) {
clickedSection = clickedSection.previousElementSibling;
}
const newIndex = getSections().indexOf(clickedSection) + 1; const newIndex = getSections().indexOf(clickedSection) + 1;
cm = setupCodeMirror(div, code, newIndex); cm = setupCodeMirror(div, code, newIndex);
makeSectionVisible(cm); makeSectionVisible(cm);
@ -192,7 +199,31 @@ function addAppliesTo(list, type, value) {
if (toFocus) toFocus.focus(); 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 => { const cm = CodeMirror(wrapper => {
$('.code-label', sectionDiv).insertAdjacentElement('afterend', 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; return cm;
} }
@ -385,17 +416,17 @@ function toggleSectionHeight(cm) {
} }
} }
function getSectionForChild(e) { function getSectionForChild(el, {includeDeleted} = {}) {
return e.closest('#sections > div'); return el.closest(`#sections > ${includeDeleted ? '*' : '.section'}`);
} }
function getSections() { function getSections() {
return $$('#sections > div'); return $$('#sections > .section');
} }
function getSectionsHashes() { function getSectionsHashes(elements = getSections()) {
const sections = []; const sections = [];
for (const div of getSections()) { for (const div of elements) {
const meta = {urls: [], urlPrefixes: [], domains: [], regexps: []}; const meta = {urls: [], urlPrefixes: [], domains: [], regexps: []};
for (const li of $('.applies-to-list', div).childNodes) { for (const li of $('.applies-to-list', div).childNodes) {
if (li.className === template.appliesToEverything.className) { if (li.className === template.appliesToEverything.className) {
@ -430,6 +461,29 @@ function removeAppliesTo(event) {
function removeSection(event) { function removeSection(event) {
const section = getSectionForChild(event.target); const section = getSectionForChild(event.target);
const cm = section.CodeMirror; 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); setCleanItem($('#sections'), false);
removeAreaAndSetDirty(section); removeAreaAndSetDirty(section);
editors.splice(editors.indexOf(cm), 1); editors.splice(editors.indexOf(cm), 1);

View File

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