mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-24 03:12:46 +01:00
Add ability to collpase unchanged rules in _My rules_ pane
Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/604
This commit is contained in:
parent
5415b980b1
commit
532ed5c390
@ -136,6 +136,9 @@ div.CodeMirror span.CodeMirror-matchingbracket {
|
||||
.CodeMirror-merge-gap {
|
||||
vertical-align: top;
|
||||
}
|
||||
.CodeMirror-merge-spacer {
|
||||
background-color: var(--bg-code);
|
||||
}
|
||||
|
||||
.CodeMirror-hints {
|
||||
z-index: 10000;
|
||||
|
@ -35,13 +35,6 @@ body {
|
||||
#diff .ruleActions .fieldset-header {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
#ruleFilter {
|
||||
direction: ltr;
|
||||
text-align: center;
|
||||
}
|
||||
#ruleFilter .fa {
|
||||
color: var(--fg-0-60);
|
||||
}
|
||||
#revertButton,
|
||||
#commitButton,
|
||||
body.editing #diff #exportButton,
|
||||
@ -65,6 +58,22 @@ body:not(.editing) #diff.dirty #commitButton:hover {
|
||||
background-color: var(--button-surface-hover);
|
||||
}
|
||||
|
||||
#ruleFilter {
|
||||
direction: ltr;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
#ruleFilter .fa {
|
||||
color: var(--fg-0-60);
|
||||
}
|
||||
#ruleFilter #diffCollapse {
|
||||
padding: 0 0.5em;
|
||||
font-size: 150%;
|
||||
}
|
||||
#ruleFilter #diffCollapse.active {
|
||||
transform: scale(1, -1);
|
||||
}
|
||||
|
||||
.codeMirrorContainer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
@ -35,7 +35,7 @@
|
||||
<button type="button" class="iconifiable important disabled" id="editSaveButton"><span class="fa"></span><span data-i18n="rulesEditSave"></span></button>
|
||||
</div>
|
||||
<div id="ruleFilter">
|
||||
<span><span class="fa-icon">filter</span> <input type="search" size="20"></span> Sort: <select><option value="0">Rule type<option value="1" selected>Source<option value="2">Destination</select>
|
||||
<span><span class="fa-icon">filter</span> <input type="search" size="20"></span> Sort: <select><option value="0" selected>Rule type<option value="1">Source<option value="2">Destination</select> <span id="diffCollapse" class="fa-icon">double-angle-up</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -48,15 +48,22 @@ mergeView.leftOriginal().setOption('readOnly', 'nocursor');
|
||||
|
||||
uBlockDashboard.patchCodeMirrorEditor(mergeView.editor());
|
||||
|
||||
const unfilteredRules = {
|
||||
orig: { doc: mergeView.leftOriginal(), rules: [] },
|
||||
edit: { doc: mergeView.editor(), rules: [] }
|
||||
const thePanes = {
|
||||
orig: {
|
||||
doc: mergeView.leftOriginal(),
|
||||
original: [],
|
||||
modified: [],
|
||||
},
|
||||
edit: {
|
||||
doc: mergeView.editor(),
|
||||
original: [],
|
||||
modified: [],
|
||||
},
|
||||
};
|
||||
|
||||
let cleanEditToken = 0;
|
||||
let cleanEditText = '';
|
||||
|
||||
let differ;
|
||||
let isCollapsed = false;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
@ -97,6 +104,16 @@ let differ;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const getDiffer = (( ) => {
|
||||
let differ;
|
||||
return ( ) => {
|
||||
if ( differ === undefined ) { differ = new diff_match_patch(); }
|
||||
return differ;
|
||||
};
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Borrowed from...
|
||||
// https://github.com/codemirror/CodeMirror/blob/3e1bb5fff682f8f6cbfaef0e56c61d62403d4798/addon/search/search.js#L22
|
||||
// ... and modified as needed.
|
||||
@ -135,13 +152,14 @@ const updateOverlay = (( ) => {
|
||||
// - Minimum amount of text updated
|
||||
|
||||
const rulesToDoc = function(clearHistory) {
|
||||
const orig = unfilteredRules.orig.doc;
|
||||
const edit = unfilteredRules.edit.doc;
|
||||
const orig = thePanes.orig.doc;
|
||||
const edit = thePanes.edit.doc;
|
||||
orig.startOperation();
|
||||
edit.startOperation();
|
||||
for ( const key in unfilteredRules ) {
|
||||
if ( unfilteredRules.hasOwnProperty(key) === false ) { continue; }
|
||||
const doc = unfilteredRules[key].doc;
|
||||
|
||||
for ( const key in thePanes ) {
|
||||
if ( thePanes.hasOwnProperty(key) === false ) { continue; }
|
||||
const doc = thePanes[key].doc;
|
||||
const rules = filterRules(key);
|
||||
if (
|
||||
clearHistory ||
|
||||
@ -151,7 +169,6 @@ const rulesToDoc = function(clearHistory) {
|
||||
doc.setValue(rules.length !== 0 ? rules.join('\n') + '\n' : '');
|
||||
continue;
|
||||
}
|
||||
if ( differ === undefined ) { differ = new diff_match_patch(); }
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/593
|
||||
// Ensure the text content always ends with an empty line to avoid
|
||||
// spurious diff entries.
|
||||
@ -161,7 +178,7 @@ const rulesToDoc = function(clearHistory) {
|
||||
let beforeText = doc.getValue();
|
||||
let afterText = rules.join('\n').trim();
|
||||
if ( afterText !== '' ) { afterText += '\n'; }
|
||||
const diffs = differ.diff_main(beforeText, afterText);
|
||||
const diffs = getDiffer().diff_main(beforeText, afterText);
|
||||
let i = diffs.length;
|
||||
let iedit = beforeText.length;
|
||||
while ( i-- ) {
|
||||
@ -181,112 +198,61 @@ const rulesToDoc = function(clearHistory) {
|
||||
doc.replaceRange('', beg, end);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark ellipses as read-only
|
||||
const marks = edit.getAllMarks();
|
||||
for ( const mark of marks ) {
|
||||
if ( mark.uboEllipsis !== true ) { continue; }
|
||||
mark.clear();
|
||||
}
|
||||
if ( isCollapsed ) {
|
||||
for ( let iline = 0, n = edit.lineCount(); iline < n; iline++ ) {
|
||||
if ( edit.getLine(iline) !== '...' ) { continue; }
|
||||
const mark = edit.markText(
|
||||
{ line: iline, ch: 0 },
|
||||
{ line: iline + 1, ch: 0 },
|
||||
{ readOnly: true }
|
||||
);
|
||||
mark.uboEllipsis = true;
|
||||
}
|
||||
}
|
||||
|
||||
orig.endOperation();
|
||||
edit.endOperation();
|
||||
cleanEditText = mergeView.editor().getValue().trim();
|
||||
cleanEditToken = mergeView.editor().changeGeneration();
|
||||
if ( clearHistory ) {
|
||||
mergeView.editor().clearHistory();
|
||||
}
|
||||
|
||||
if ( clearHistory !== true ) { return; }
|
||||
|
||||
mergeView.editor().clearHistory();
|
||||
const chunks = mergeView.leftChunks();
|
||||
if ( chunks.length === 0 ) { return; }
|
||||
const ldoc = thePanes.orig.doc;
|
||||
const { clientHeight } = ldoc.getScrollInfo();
|
||||
const line = Math.min(chunks[0].editFrom, chunks[0].origFrom);
|
||||
ldoc.setCursor(line, 0);
|
||||
ldoc.scrollIntoView(
|
||||
{ line, ch: 0 },
|
||||
(clientHeight - ldoc.defaultTextHeight()) / 2
|
||||
);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const filterRules = function(key) {
|
||||
const filter = uDom.nodeFromSelector('#ruleFilter input').value;
|
||||
let rules = unfilteredRules[key].rules;
|
||||
if ( filter !== '' ) {
|
||||
rules = rules.slice();
|
||||
let i = rules.length;
|
||||
while ( i-- ) {
|
||||
if ( rules[i].indexOf(filter) === -1 ) {
|
||||
rules.splice(i, 1);
|
||||
}
|
||||
}
|
||||
const rules = thePanes[key].modified;
|
||||
if ( filter === '' ) { return rules; }
|
||||
const out = [];
|
||||
for ( const rule of rules ) {
|
||||
if ( rule.indexOf(filter) === -1 ) { continue; }
|
||||
out.push(rule);
|
||||
}
|
||||
return rules;
|
||||
return out;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const renderRules = (( ) => {
|
||||
let firstVisit = true;
|
||||
let sortType = 1;
|
||||
|
||||
const reSwRule = /^([^/]+): ([^/ ]+) ([^ ]+)/;
|
||||
const reRule = /^([^ ]+) ([^/ ]+) ([^ ]+ [^ ]+)/;
|
||||
const reUrlRule = /^([^ ]+) ([^ ]+) ([^ ]+ [^ ]+)/;
|
||||
|
||||
const reverseHn = function(hn) {
|
||||
return hn.split('.').reverse().join('.');
|
||||
};
|
||||
|
||||
const slotFromRule = function(rule) {
|
||||
let type, srcHn, desHn, extra = '';
|
||||
let match = reSwRule.exec(rule);
|
||||
if ( match !== null ) {
|
||||
type = ' ' + match[1];
|
||||
srcHn = reverseHn(match[2]);
|
||||
desHn = srcHn;
|
||||
} else if ( (match = reRule.exec(rule)) !== null ) {
|
||||
type = '\x10FFFE';
|
||||
srcHn = reverseHn(match[1]);
|
||||
desHn = reverseHn(match[2]);
|
||||
} else if ( (match = reUrlRule.exec(rule)) !== null ) {
|
||||
type = '\x10FFFF';
|
||||
srcHn = reverseHn(match[1]);
|
||||
desHn = reverseHn(vAPI.hostnameFromURI(match[2]));
|
||||
extra = rule;
|
||||
}
|
||||
if ( sortType === 0 ) {
|
||||
return { rule, token: `${type} ${srcHn} ${desHn} ${extra}` };
|
||||
}
|
||||
if ( sortType === 1 ) {
|
||||
return { rule, token: `${srcHn} ${type} ${desHn} ${extra}` };
|
||||
}
|
||||
return { rule, token: `${desHn} ${type} ${srcHn} ${extra}` };
|
||||
};
|
||||
|
||||
const sort = rules => {
|
||||
const slots = [];
|
||||
for ( let i = 0; i < rules.length; i++ ) {
|
||||
slots.push(slotFromRule(rules[i], 1));
|
||||
}
|
||||
slots.sort((a, b) => a.token.localeCompare(b.token));
|
||||
for ( let i = 0; i < rules.length; i++ ) {
|
||||
rules[i] = slots[i].rule;
|
||||
}
|
||||
};
|
||||
|
||||
return function(clearHistory = false) {
|
||||
const select = document.querySelector('#ruleFilter select');
|
||||
sortType = parseInt(select.value, 10);
|
||||
if ( isNaN(sortType) ) { sortType = 1; }
|
||||
unfilteredRules.orig.doc.getMode().sortType = sortType;
|
||||
unfilteredRules.edit.doc.getMode().sortType = sortType;
|
||||
sort(unfilteredRules.orig.rules);
|
||||
sort(unfilteredRules.edit.rules);
|
||||
rulesToDoc(firstVisit || clearHistory);
|
||||
if ( firstVisit || clearHistory ) {
|
||||
firstVisit = false;
|
||||
const chunks = mergeView.leftChunks();
|
||||
if ( chunks.length !== 0 ) {
|
||||
const ldoc = unfilteredRules.orig.doc;
|
||||
const { clientHeight } = ldoc.getScrollInfo();
|
||||
const line = Math.min(chunks[0].editFrom, chunks[0].origFrom);
|
||||
ldoc.setCursor(line, 0);
|
||||
ldoc.scrollIntoView(
|
||||
{ line, ch: 0 },
|
||||
(clientHeight - ldoc.defaultTextHeight()) / 2
|
||||
);
|
||||
}
|
||||
}
|
||||
onTextChanged(true);
|
||||
};
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const applyDiff = async function(permanent, toAdd, toRemove) {
|
||||
const details = await vAPI.messaging.send('dashboard', {
|
||||
what: 'modifyRuleset',
|
||||
@ -294,9 +260,9 @@ const applyDiff = async function(permanent, toAdd, toRemove) {
|
||||
toAdd: toAdd,
|
||||
toRemove: toRemove,
|
||||
});
|
||||
unfilteredRules.orig.rules = details.permanentRules;
|
||||
unfilteredRules.edit.rules = details.sessionRules;
|
||||
renderRules();
|
||||
thePanes.orig.original = details.permanentRules;
|
||||
thePanes.edit.original = details.sessionRules;
|
||||
onPresentationChanged();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
@ -414,9 +380,109 @@ const onFilterChanged = (( ) => {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const onSortChanged = function() {
|
||||
renderRules(true);
|
||||
};
|
||||
const onPresentationChanged = (( ) => {
|
||||
let sortType = 1;
|
||||
|
||||
const reSwRule = /^([^/]+): ([^/ ]+) ([^ ]+)/;
|
||||
const reRule = /^([^ ]+) ([^/ ]+) ([^ ]+ [^ ]+)/;
|
||||
const reUrlRule = /^([^ ]+) ([^ ]+) ([^ ]+ [^ ]+)/;
|
||||
|
||||
const reverseHn = function(hn) {
|
||||
return hn.split('.').reverse().join('.');
|
||||
};
|
||||
|
||||
const slotFromRule = rule => {
|
||||
let type, srcHn, desHn, extra = '';
|
||||
let match = reSwRule.exec(rule);
|
||||
if ( match !== null ) {
|
||||
type = ' ' + match[1];
|
||||
srcHn = reverseHn(match[2]);
|
||||
desHn = srcHn;
|
||||
} else if ( (match = reRule.exec(rule)) !== null ) {
|
||||
type = '\x10FFFE';
|
||||
srcHn = reverseHn(match[1]);
|
||||
desHn = reverseHn(match[2]);
|
||||
} else if ( (match = reUrlRule.exec(rule)) !== null ) {
|
||||
type = '\x10FFFF';
|
||||
srcHn = reverseHn(match[1]);
|
||||
desHn = reverseHn(vAPI.hostnameFromURI(match[2]));
|
||||
extra = rule;
|
||||
}
|
||||
if ( sortType === 0 ) {
|
||||
return { rule, token: `${type} ${srcHn} ${desHn} ${extra}` };
|
||||
}
|
||||
if ( sortType === 1 ) {
|
||||
return { rule, token: `${srcHn} ${type} ${desHn} ${extra}` };
|
||||
}
|
||||
return { rule, token: `${desHn} ${type} ${srcHn} ${extra}` };
|
||||
};
|
||||
|
||||
const sort = rules => {
|
||||
const slots = [];
|
||||
for ( let i = 0; i < rules.length; i++ ) {
|
||||
slots.push(slotFromRule(rules[i], 1));
|
||||
}
|
||||
slots.sort((a, b) => a.token.localeCompare(b.token));
|
||||
for ( let i = 0; i < rules.length; i++ ) {
|
||||
rules[i] = slots[i].rule;
|
||||
}
|
||||
};
|
||||
|
||||
const collapse = ( ) => {
|
||||
if ( isCollapsed !== true ) { return; }
|
||||
const diffs = getDiffer().diff_main(
|
||||
thePanes.orig.modified.join('\n'),
|
||||
thePanes.edit.modified.join('\n')
|
||||
);
|
||||
const ll = []; let il = 0, lellipsis = false;
|
||||
const rr = []; let ir = 0, rellipsis = false;
|
||||
for ( let i = 0; i < diffs.length; i++ ) {
|
||||
const diff = diffs[i];
|
||||
if ( diff[0] === 0 ) {
|
||||
lellipsis = rellipsis = true;
|
||||
il += 1; ir += 1;
|
||||
continue;
|
||||
}
|
||||
if ( diff[0] < 0 ) {
|
||||
if ( lellipsis ) {
|
||||
ll.push('...');
|
||||
if ( rellipsis ) { rr.push('...'); }
|
||||
lellipsis = rellipsis = false;
|
||||
}
|
||||
ll.push(diff[1].trim());
|
||||
il += 1;
|
||||
continue;
|
||||
}
|
||||
/* diff[0] > 0 */
|
||||
if ( rellipsis ) {
|
||||
rr.push('...');
|
||||
if ( lellipsis ) { ll.push('...'); }
|
||||
lellipsis = rellipsis = false;
|
||||
}
|
||||
rr.push(diff[1].trim());
|
||||
ir += 1;
|
||||
}
|
||||
if ( lellipsis ) { ll.push('...'); }
|
||||
if ( rellipsis ) { rr.push('...'); }
|
||||
thePanes.orig.modified = ll;
|
||||
thePanes.edit.modified = rr;
|
||||
};
|
||||
|
||||
return function(clearHistory) {
|
||||
thePanes.orig.modified = thePanes.orig.original.slice();
|
||||
thePanes.edit.modified = thePanes.edit.original.slice();
|
||||
const select = document.querySelector('#ruleFilter select');
|
||||
sortType = parseInt(select.value, 10);
|
||||
if ( isNaN(sortType) ) { sortType = 1; }
|
||||
thePanes.orig.doc.getMode().sortType = sortType;
|
||||
thePanes.edit.doc.getMode().sortType = sortType;
|
||||
sort(thePanes.orig.modified);
|
||||
sort(thePanes.edit.modified);
|
||||
collapse();
|
||||
rulesToDoc(clearHistory);
|
||||
onTextChanged(clearHistory);
|
||||
};
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
@ -506,9 +572,8 @@ const editSaveHandler = function() {
|
||||
onTextChanged(true);
|
||||
return;
|
||||
}
|
||||
if ( differ === undefined ) { differ = new diff_match_patch(); }
|
||||
const toAdd = [], toRemove = [];
|
||||
const diffs = differ.diff_main(cleanEditText, editText);
|
||||
const diffs = getDiffer().diff_main(cleanEditText, editText);
|
||||
for ( const diff of diffs ) {
|
||||
if ( diff[0] === 1 ) {
|
||||
toAdd.push(diff[1]);
|
||||
@ -545,9 +610,9 @@ self.hasUnsavedData = function() {
|
||||
vAPI.messaging.send('dashboard', {
|
||||
what: 'getRules',
|
||||
}).then(details => {
|
||||
unfilteredRules.orig.rules = details.permanentRules;
|
||||
unfilteredRules.edit.rules = details.sessionRules;
|
||||
renderRules();
|
||||
thePanes.orig.original = details.permanentRules;
|
||||
thePanes.edit.original = details.sessionRules;
|
||||
onPresentationChanged(true);
|
||||
});
|
||||
|
||||
// Handle user interaction
|
||||
@ -558,7 +623,13 @@ uDom('#revertButton').on('click', revertAllHandler);
|
||||
uDom('#commitButton').on('click', commitAllHandler);
|
||||
uDom('#editSaveButton').on('click', editSaveHandler);
|
||||
uDom('#ruleFilter input').on('input', onFilterChanged);
|
||||
uDom('#ruleFilter select').on('input', onSortChanged);
|
||||
uDom('#ruleFilter select').on('input', ( ) => {
|
||||
onPresentationChanged(true);
|
||||
});
|
||||
uDom('#ruleFilter #diffCollapse').on('click', ev => {
|
||||
isCollapsed = ev.target.classList.toggle('active');
|
||||
onPresentationChanged(true);
|
||||
});
|
||||
|
||||
// https://groups.google.com/forum/#!topic/codemirror/UQkTrt078Vs
|
||||
mergeView.editor().on('updateDiff', ( ) => { onTextChanged(); });
|
||||
|
Loading…
Reference in New Issue
Block a user