2014-12-31 23:26:17 +01:00
|
|
|
/*******************************************************************************
|
|
|
|
|
2016-03-06 16:51:06 +01:00
|
|
|
uBlock Origin - a browser extension to block requests.
|
2018-09-03 20:06:49 +02:00
|
|
|
Copyright (C) 2014-present Raymond Hill
|
2014-12-31 23:26:17 +01:00
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|
|
|
|
|
|
|
Home: https://github.com/gorhill/uMatrix
|
|
|
|
*/
|
|
|
|
|
2021-07-25 16:55:35 +02:00
|
|
|
/* global CodeMirror, diff_match_patch, uDom, uBlockDashboard */
|
2018-03-11 15:59:39 +01:00
|
|
|
|
|
|
|
'use strict';
|
2014-12-31 23:26:17 +01:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2021-07-25 16:55:35 +02:00
|
|
|
import '../lib/publicsuffixlist/publicsuffixlist.js';
|
|
|
|
|
|
|
|
import globals from './globals.js';
|
|
|
|
import { hostnameFromURI } from './uri-utils.js';
|
|
|
|
|
|
|
|
import './codemirror/ubo-dynamic-filtering.js';
|
2014-12-31 23:26:17 +01:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2021-07-25 16:55:35 +02:00
|
|
|
const publicSuffixList = globals.publicSuffixList;
|
|
|
|
|
2020-11-18 14:01:00 +01:00
|
|
|
const hostnameToDomainMap = new Map();
|
|
|
|
|
2018-12-22 17:55:13 +01:00
|
|
|
const mergeView = new CodeMirror.MergeView(
|
2018-03-11 15:59:39 +01:00
|
|
|
document.querySelector('.codeMirrorMergeContainer'),
|
|
|
|
{
|
|
|
|
allowEditingOriginals: true,
|
2018-03-16 12:49:56 +01:00
|
|
|
connect: 'align',
|
2018-03-21 12:24:52 +01:00
|
|
|
inputStyle: 'contenteditable',
|
2018-03-11 15:59:39 +01:00
|
|
|
lineNumbers: true,
|
|
|
|
lineWrapping: false,
|
|
|
|
origLeft: '',
|
|
|
|
revertButtons: true,
|
2020-10-21 18:50:24 +02:00
|
|
|
value: '',
|
2015-04-09 17:19:31 +02:00
|
|
|
}
|
2018-03-11 15:59:39 +01:00
|
|
|
);
|
|
|
|
mergeView.editor().setOption('styleActiveLine', true);
|
|
|
|
mergeView.editor().setOption('lineNumbers', false);
|
|
|
|
mergeView.leftOriginal().setOption('readOnly', 'nocursor');
|
2015-04-09 17:19:31 +02:00
|
|
|
|
2018-03-28 01:10:31 +02:00
|
|
|
uBlockDashboard.patchCodeMirrorEditor(mergeView.editor());
|
|
|
|
|
2020-08-25 19:23:30 +02:00
|
|
|
const thePanes = {
|
|
|
|
orig: {
|
|
|
|
doc: mergeView.leftOriginal(),
|
|
|
|
original: [],
|
|
|
|
modified: [],
|
|
|
|
},
|
|
|
|
edit: {
|
|
|
|
doc: mergeView.editor(),
|
|
|
|
original: [],
|
|
|
|
modified: [],
|
|
|
|
},
|
2018-03-21 12:24:52 +01:00
|
|
|
};
|
|
|
|
|
2018-12-22 17:55:13 +01:00
|
|
|
let cleanEditToken = 0;
|
|
|
|
let cleanEditText = '';
|
2020-08-25 19:23:30 +02:00
|
|
|
let isCollapsed = false;
|
2018-12-22 17:55:13 +01:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// The following code is to take care of properly internationalizing
|
|
|
|
// the tooltips of the arrows used by the CodeMirror merge view. These
|
|
|
|
// are hard-coded by CodeMirror ("Push to left", "Push to right"). An
|
|
|
|
// observer is necessary because there is no hook for uBO to overwrite
|
|
|
|
// reliably the default title attribute assigned by CodeMirror.
|
|
|
|
|
2019-05-25 16:04:31 +02:00
|
|
|
{
|
2018-12-22 17:55:13 +01:00
|
|
|
const i18nCommitStr = vAPI.i18n('rulesCommit');
|
|
|
|
const i18nRevertStr = vAPI.i18n('rulesRevert');
|
|
|
|
const commitArrowSelector = '.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy-reverse:not([title="' + i18nCommitStr + '"])';
|
|
|
|
const revertArrowSelector = '.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy:not([title="' + i18nRevertStr + '"])';
|
|
|
|
|
|
|
|
uDom.nodeFromSelector('.CodeMirror-merge-scrolllock')
|
|
|
|
.setAttribute('title', vAPI.i18n('genericMergeViewScrollLock'));
|
|
|
|
|
|
|
|
const translate = function() {
|
|
|
|
let elems = document.querySelectorAll(commitArrowSelector);
|
|
|
|
for ( const elem of elems ) {
|
|
|
|
elem.setAttribute('title', i18nCommitStr);
|
|
|
|
}
|
|
|
|
elems = document.querySelectorAll(revertArrowSelector);
|
|
|
|
for ( const elem of elems ) {
|
|
|
|
elem.setAttribute('title', i18nRevertStr);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const mergeGapObserver = new MutationObserver(translate);
|
|
|
|
|
|
|
|
mergeGapObserver.observe(
|
|
|
|
uDom.nodeFromSelector('.CodeMirror-merge-copybuttons-left'),
|
|
|
|
{ attributes: true, attributeFilter: [ 'title' ], subtree: true }
|
|
|
|
);
|
|
|
|
|
2019-05-25 16:04:31 +02:00
|
|
|
}
|
2018-03-11 15:59:39 +01:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-08-25 19:23:30 +02:00
|
|
|
const getDiffer = (( ) => {
|
|
|
|
let differ;
|
|
|
|
return ( ) => {
|
|
|
|
if ( differ === undefined ) { differ = new diff_match_patch(); }
|
|
|
|
return differ;
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2018-03-21 12:24:52 +01:00
|
|
|
// Borrowed from...
|
|
|
|
// https://github.com/codemirror/CodeMirror/blob/3e1bb5fff682f8f6cbfaef0e56c61d62403d4798/addon/search/search.js#L22
|
|
|
|
// ... and modified as needed.
|
|
|
|
|
2020-08-24 18:39:07 +02:00
|
|
|
const updateOverlay = (( ) => {
|
2018-09-03 20:06:49 +02:00
|
|
|
let reFilter;
|
2019-09-17 21:15:01 +02:00
|
|
|
const mode = {
|
2018-03-21 12:24:52 +01:00
|
|
|
token: function(stream) {
|
|
|
|
if ( reFilter !== undefined ) {
|
|
|
|
reFilter.lastIndex = stream.pos;
|
2018-09-03 20:06:49 +02:00
|
|
|
let match = reFilter.exec(stream.string);
|
2018-03-21 12:24:52 +01:00
|
|
|
if ( match !== null ) {
|
|
|
|
if ( match.index === stream.pos ) {
|
|
|
|
stream.pos += match[0].length || 1;
|
|
|
|
return 'searching';
|
|
|
|
}
|
|
|
|
stream.pos = match.index;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
stream.skipToEnd();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return function(filter) {
|
|
|
|
reFilter = typeof filter === 'string' && filter !== '' ?
|
|
|
|
new RegExp(filter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi') :
|
|
|
|
undefined;
|
|
|
|
return mode;
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2018-03-11 15:59:39 +01:00
|
|
|
// Incrementally update text in a CodeMirror editor for best user experience:
|
|
|
|
// - Scroll position preserved
|
|
|
|
// - Minimum amount of text updated
|
|
|
|
|
2018-12-22 17:55:13 +01:00
|
|
|
const rulesToDoc = function(clearHistory) {
|
2020-08-25 19:23:30 +02:00
|
|
|
const orig = thePanes.orig.doc;
|
|
|
|
const edit = thePanes.edit.doc;
|
2020-08-24 18:39:07 +02:00
|
|
|
orig.startOperation();
|
|
|
|
edit.startOperation();
|
2020-08-25 19:23:30 +02:00
|
|
|
|
|
|
|
for ( const key in thePanes ) {
|
|
|
|
if ( thePanes.hasOwnProperty(key) === false ) { continue; }
|
|
|
|
const doc = thePanes[key].doc;
|
2019-05-25 16:04:31 +02:00
|
|
|
const rules = filterRules(key);
|
2018-09-03 20:06:49 +02:00
|
|
|
if (
|
2020-08-24 18:39:07 +02:00
|
|
|
clearHistory ||
|
2018-09-03 20:06:49 +02:00
|
|
|
doc.lineCount() === 1 && doc.getValue() === '' ||
|
|
|
|
rules.length === 0
|
|
|
|
) {
|
2019-05-25 16:04:31 +02:00
|
|
|
doc.setValue(rules.length !== 0 ? rules.join('\n') + '\n' : '');
|
2015-02-11 17:49:08 +01:00
|
|
|
continue;
|
|
|
|
}
|
2019-05-25 16:04:31 +02:00
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/593
|
|
|
|
// Ensure the text content always ends with an empty line to avoid
|
|
|
|
// spurious diff entries.
|
2019-07-07 12:57:30 +02:00
|
|
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/657
|
|
|
|
// Diff against unmodified beforeText so that the last newline can
|
|
|
|
// be reported in the diff and thus appended if needed.
|
|
|
|
let beforeText = doc.getValue();
|
2019-05-25 16:04:31 +02:00
|
|
|
let afterText = rules.join('\n').trim();
|
|
|
|
if ( afterText !== '' ) { afterText += '\n'; }
|
2020-08-25 19:23:30 +02:00
|
|
|
const diffs = getDiffer().diff_main(beforeText, afterText);
|
2020-08-24 18:39:07 +02:00
|
|
|
let i = diffs.length;
|
|
|
|
let iedit = beforeText.length;
|
2018-03-21 12:24:52 +01:00
|
|
|
while ( i-- ) {
|
2020-08-24 18:39:07 +02:00
|
|
|
const diff = diffs[i];
|
2018-03-21 12:24:52 +01:00
|
|
|
if ( diff[0] === 0 ) {
|
|
|
|
iedit -= diff[1].length;
|
|
|
|
continue;
|
|
|
|
}
|
2020-08-24 18:39:07 +02:00
|
|
|
const end = doc.posFromIndex(iedit);
|
2018-03-21 12:24:52 +01:00
|
|
|
if ( diff[0] === 1 ) {
|
|
|
|
doc.replaceRange(diff[1], end, end);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* diff[0] === -1 */
|
|
|
|
iedit -= diff[1].length;
|
2020-08-24 18:39:07 +02:00
|
|
|
const beg = doc.posFromIndex(iedit);
|
2018-03-21 12:24:52 +01:00
|
|
|
doc.replaceRange('', beg, end);
|
2015-02-11 17:49:08 +01:00
|
|
|
}
|
2015-02-11 17:34:51 +01:00
|
|
|
}
|
2020-08-25 19:23:30 +02:00
|
|
|
|
|
|
|
// 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 },
|
2020-08-25 20:26:44 +02:00
|
|
|
{ atomic: true, readOnly: true }
|
2020-08-25 19:23:30 +02:00
|
|
|
);
|
|
|
|
mark.uboEllipsis = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-24 18:39:07 +02:00
|
|
|
orig.endOperation();
|
|
|
|
edit.endOperation();
|
2018-03-21 12:24:52 +01:00
|
|
|
cleanEditText = mergeView.editor().getValue().trim();
|
|
|
|
cleanEditToken = mergeView.editor().changeGeneration();
|
2020-08-25 19:23:30 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
);
|
2018-03-21 12:24:52 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2018-12-22 17:55:13 +01:00
|
|
|
const filterRules = function(key) {
|
2019-09-17 21:15:01 +02:00
|
|
|
const filter = uDom.nodeFromSelector('#ruleFilter input').value;
|
2020-08-25 19:23:30 +02:00
|
|
|
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);
|
2018-03-21 12:24:52 +01:00
|
|
|
}
|
2020-08-25 19:23:30 +02:00
|
|
|
return out;
|
2018-03-11 15:59:39 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2019-09-17 21:15:01 +02:00
|
|
|
const applyDiff = async function(permanent, toAdd, toRemove) {
|
|
|
|
const details = await vAPI.messaging.send('dashboard', {
|
|
|
|
what: 'modifyRuleset',
|
|
|
|
permanent: permanent,
|
|
|
|
toAdd: toAdd,
|
|
|
|
toRemove: toRemove,
|
|
|
|
});
|
2020-08-25 19:23:30 +02:00
|
|
|
thePanes.orig.original = details.permanentRules;
|
|
|
|
thePanes.edit.original = details.sessionRules;
|
|
|
|
onPresentationChanged();
|
2018-03-11 15:59:39 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
// CodeMirror quirk: sometimes fromStart.ch and/or toStart.ch is undefined.
|
|
|
|
// When this happens, use 0.
|
|
|
|
|
|
|
|
mergeView.options.revertChunk = function(
|
|
|
|
mv,
|
|
|
|
from, fromStart, fromEnd,
|
|
|
|
to, toStart, toEnd
|
|
|
|
) {
|
2018-03-16 23:33:50 +01:00
|
|
|
// https://github.com/gorhill/uBlock/issues/3611
|
|
|
|
if ( document.body.getAttribute('dir') === 'rtl' ) {
|
2018-09-03 20:06:49 +02:00
|
|
|
let tmp = from; from = to; to = tmp;
|
2018-03-16 23:33:50 +01:00
|
|
|
tmp = fromStart; fromStart = toStart; toStart = tmp;
|
|
|
|
tmp = fromEnd; fromEnd = toEnd; toEnd = tmp;
|
|
|
|
}
|
2018-03-11 15:59:39 +01:00
|
|
|
if ( typeof fromStart.ch !== 'number' ) { fromStart.ch = 0; }
|
|
|
|
if ( fromEnd.ch !== 0 ) { fromEnd.line += 1; }
|
2019-09-17 21:15:01 +02:00
|
|
|
const toAdd = from.getRange(
|
2018-03-11 15:59:39 +01:00
|
|
|
{ line: fromStart.line, ch: 0 },
|
|
|
|
{ line: fromEnd.line, ch: 0 }
|
|
|
|
);
|
|
|
|
if ( typeof toStart.ch !== 'number' ) { toStart.ch = 0; }
|
|
|
|
if ( toEnd.ch !== 0 ) { toEnd.line += 1; }
|
2019-09-17 21:15:01 +02:00
|
|
|
const toRemove = to.getRange(
|
2018-03-11 15:59:39 +01:00
|
|
|
{ line: toStart.line, ch: 0 },
|
|
|
|
{ line: toEnd.line, ch: 0 }
|
|
|
|
);
|
2018-03-23 20:05:35 +01:00
|
|
|
applyDiff(from === mv.editor(), toAdd, toRemove);
|
2014-12-31 23:26:17 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
function handleImportFilePicker() {
|
2019-09-17 21:15:01 +02:00
|
|
|
const fileReaderOnLoadHandler = function() {
|
2018-03-21 12:24:52 +01:00
|
|
|
if ( typeof this.result !== 'string' || this.result === '' ) { return; }
|
2015-04-07 03:26:05 +02:00
|
|
|
// https://github.com/chrisaljoudi/uBlock/issues/757
|
2015-02-10 02:12:37 +01:00
|
|
|
// Support RequestPolicy rule syntax
|
2018-09-03 20:06:49 +02:00
|
|
|
let result = this.result;
|
|
|
|
let matches = /\[origins-to-destinations\]([^\[]+)/.exec(result);
|
2015-02-10 02:12:37 +01:00
|
|
|
if ( matches && matches.length === 2 ) {
|
|
|
|
result = matches[1].trim()
|
|
|
|
.replace(/\|/g, ' ')
|
|
|
|
.replace(/\n/g, ' * noop\n');
|
|
|
|
}
|
2018-03-23 20:05:35 +01:00
|
|
|
applyDiff(false, result, '');
|
2014-12-31 23:26:17 +01:00
|
|
|
};
|
2019-09-17 21:15:01 +02:00
|
|
|
const file = this.files[0];
|
2018-03-11 15:59:39 +01:00
|
|
|
if ( file === undefined || file.name === '' ) { return; }
|
|
|
|
if ( file.type.indexOf('text') !== 0 ) { return; }
|
2019-09-17 21:15:01 +02:00
|
|
|
const fr = new FileReader();
|
2014-12-31 23:26:17 +01:00
|
|
|
fr.onload = fileReaderOnLoadHandler;
|
|
|
|
fr.readAsText(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2018-12-22 17:55:13 +01:00
|
|
|
const startImportFilePicker = function() {
|
2019-09-17 21:15:01 +02:00
|
|
|
const input = document.getElementById('importFilePicker');
|
2014-12-31 23:26:17 +01:00
|
|
|
// Reset to empty string, this will ensure an change event is properly
|
|
|
|
// triggered if the user pick a file, even if it is the same as the last
|
|
|
|
// one picked.
|
|
|
|
input.value = '';
|
|
|
|
input.click();
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
function exportUserRulesToFile() {
|
2019-09-17 21:15:01 +02:00
|
|
|
const filename = vAPI.i18n('rulesDefaultFileName')
|
2016-10-13 19:25:57 +02:00
|
|
|
.replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString())
|
2015-01-06 18:14:37 +01:00
|
|
|
.replace(/ +/g, '_');
|
|
|
|
vAPI.download({
|
2018-03-11 15:59:39 +01:00
|
|
|
url: 'data:text/plain,' + encodeURIComponent(
|
|
|
|
mergeView.leftOriginal().getValue().trim() + '\n'
|
|
|
|
),
|
|
|
|
filename: filename,
|
|
|
|
saveAs: true
|
2014-12-31 23:26:17 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-08-24 18:39:07 +02:00
|
|
|
const onFilterChanged = (( ) => {
|
|
|
|
let timer;
|
|
|
|
let overlay = null;
|
|
|
|
let last = '';
|
2015-02-11 17:34:51 +01:00
|
|
|
|
2019-05-19 21:35:00 +02:00
|
|
|
const process = function() {
|
2018-03-11 15:59:39 +01:00
|
|
|
timer = undefined;
|
2018-03-21 12:24:52 +01:00
|
|
|
if ( mergeView.editor().isClean(cleanEditToken) === false ) { return; }
|
2019-09-17 21:15:01 +02:00
|
|
|
const filter = uDom.nodeFromSelector('#ruleFilter input').value;
|
2018-03-21 13:42:21 +01:00
|
|
|
if ( filter === last ) { return; }
|
|
|
|
last = filter;
|
2018-03-21 12:24:52 +01:00
|
|
|
if ( overlay !== null ) {
|
|
|
|
mergeView.leftOriginal().removeOverlay(overlay);
|
|
|
|
mergeView.editor().removeOverlay(overlay);
|
|
|
|
overlay = null;
|
|
|
|
}
|
|
|
|
if ( filter !== '' ) {
|
|
|
|
overlay = updateOverlay(filter);
|
|
|
|
mergeView.leftOriginal().addOverlay(overlay);
|
|
|
|
mergeView.editor().addOverlay(overlay);
|
|
|
|
}
|
|
|
|
rulesToDoc(true);
|
2018-03-11 15:59:39 +01:00
|
|
|
};
|
2015-02-11 17:34:51 +01:00
|
|
|
|
2018-03-11 15:59:39 +01:00
|
|
|
return function() {
|
2021-07-25 16:55:35 +02:00
|
|
|
if ( timer !== undefined ) { globals.cancelIdleCallback(timer); }
|
|
|
|
timer = globals.requestIdleCallback(process, { timeout: 773 });
|
2015-02-11 17:34:51 +01:00
|
|
|
};
|
2018-03-11 15:59:39 +01:00
|
|
|
})();
|
2015-02-11 17:34:51 +01:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2020-08-25 19:23:30 +02:00
|
|
|
const onPresentationChanged = (( ) => {
|
|
|
|
let sortType = 1;
|
|
|
|
|
|
|
|
const reSwRule = /^([^/]+): ([^/ ]+) ([^ ]+)/;
|
|
|
|
const reRule = /^([^ ]+) ([^/ ]+) ([^ ]+ [^ ]+)/;
|
2020-08-27 13:04:37 +02:00
|
|
|
const reUrlRule = /^([^ ]+) ([^ ]+) ([^ ]+ [^ ]+)/;
|
2020-08-25 19:23:30 +02:00
|
|
|
|
2020-11-18 14:01:00 +01:00
|
|
|
const sortNormalizeHn = function(hn) {
|
|
|
|
let domain = hostnameToDomainMap.get(hn);
|
|
|
|
if ( domain === undefined ) {
|
2021-07-25 16:55:35 +02:00
|
|
|
domain = /(\d|\])$/.test(hn)
|
|
|
|
? hn
|
|
|
|
: publicSuffixList.getDomain(hn);
|
2020-11-18 14:01:00 +01:00
|
|
|
hostnameToDomainMap.set(hn, domain);
|
|
|
|
}
|
|
|
|
let normalized = domain || hn;
|
|
|
|
if ( hn.length !== domain.length ) {
|
|
|
|
const subdomains = hn.slice(0, hn.length - domain.length - 1);
|
|
|
|
normalized += '.' + (
|
|
|
|
subdomains.includes('.')
|
|
|
|
? subdomains.split('.').reverse().join('.')
|
|
|
|
: subdomains
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return normalized;
|
2020-08-25 19:23:30 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
const slotFromRule = rule => {
|
2020-08-27 13:04:37 +02:00
|
|
|
let type, srcHn, desHn, extra;
|
2020-08-25 19:23:30 +02:00
|
|
|
let match = reSwRule.exec(rule);
|
|
|
|
if ( match !== null ) {
|
|
|
|
type = ' ' + match[1];
|
2020-11-18 14:01:00 +01:00
|
|
|
srcHn = sortNormalizeHn(match[2]);
|
2020-08-25 19:23:30 +02:00
|
|
|
desHn = srcHn;
|
2020-08-27 13:04:37 +02:00
|
|
|
extra = match[3];
|
2020-08-25 19:23:30 +02:00
|
|
|
} else if ( (match = reRule.exec(rule)) !== null ) {
|
|
|
|
type = '\x10FFFE';
|
2020-11-18 14:01:00 +01:00
|
|
|
srcHn = sortNormalizeHn(match[1]);
|
|
|
|
desHn = sortNormalizeHn(match[2]);
|
2020-08-27 13:04:37 +02:00
|
|
|
extra = match[3];
|
2020-08-25 19:23:30 +02:00
|
|
|
} else if ( (match = reUrlRule.exec(rule)) !== null ) {
|
|
|
|
type = '\x10FFFF';
|
2020-11-18 14:01:00 +01:00
|
|
|
srcHn = sortNormalizeHn(match[1]);
|
2021-07-25 16:55:35 +02:00
|
|
|
desHn = sortNormalizeHn(hostnameFromURI(match[2]));
|
2020-08-27 13:04:37 +02:00
|
|
|
extra = match[3];
|
2020-08-25 19:23:30 +02:00
|
|
|
}
|
|
|
|
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) {
|
2020-11-19 17:33:09 +01:00
|
|
|
const origPane = thePanes.orig;
|
|
|
|
const editPane = thePanes.edit;
|
|
|
|
origPane.modified = origPane.original.slice();
|
|
|
|
editPane.modified = editPane.original.slice();
|
2020-08-25 19:23:30 +02:00
|
|
|
const select = document.querySelector('#ruleFilter select');
|
|
|
|
sortType = parseInt(select.value, 10);
|
|
|
|
if ( isNaN(sortType) ) { sortType = 1; }
|
2020-11-19 17:33:09 +01:00
|
|
|
{
|
|
|
|
const mode = origPane.doc.getMode();
|
|
|
|
mode.sortType = sortType;
|
|
|
|
mode.setHostnameToDomainMap(hostnameToDomainMap);
|
2021-07-25 16:55:35 +02:00
|
|
|
mode.setPSL(publicSuffixList);
|
2020-11-19 17:33:09 +01:00
|
|
|
}
|
|
|
|
{
|
|
|
|
const mode = editPane.doc.getMode();
|
|
|
|
mode.sortType = sortType;
|
|
|
|
mode.setHostnameToDomainMap(hostnameToDomainMap);
|
2021-07-25 16:55:35 +02:00
|
|
|
mode.setPSL(publicSuffixList);
|
2020-11-19 17:33:09 +01:00
|
|
|
}
|
|
|
|
sort(origPane.modified);
|
|
|
|
sort(editPane.modified);
|
2020-08-25 19:23:30 +02:00
|
|
|
collapse();
|
|
|
|
rulesToDoc(clearHistory);
|
|
|
|
onTextChanged(clearHistory);
|
|
|
|
};
|
|
|
|
})();
|
2020-08-24 18:39:07 +02:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2019-05-25 16:04:31 +02:00
|
|
|
const onTextChanged = (( ) => {
|
2018-09-03 20:06:49 +02:00
|
|
|
let timer;
|
2018-03-11 15:59:39 +01:00
|
|
|
|
2020-08-24 18:39:07 +02:00
|
|
|
const process = details => {
|
2018-03-11 15:59:39 +01:00
|
|
|
timer = undefined;
|
2018-12-22 17:55:13 +01:00
|
|
|
const diff = document.getElementById('diff');
|
2018-09-03 20:06:49 +02:00
|
|
|
let isClean = mergeView.editor().isClean(cleanEditToken);
|
2018-03-11 15:59:39 +01:00
|
|
|
if (
|
2020-08-24 18:39:07 +02:00
|
|
|
details === undefined &&
|
2018-03-11 15:59:39 +01:00
|
|
|
isClean === false &&
|
|
|
|
mergeView.editor().getValue().trim() === cleanEditText
|
|
|
|
) {
|
2018-03-21 12:24:52 +01:00
|
|
|
cleanEditToken = mergeView.editor().changeGeneration();
|
2018-03-11 15:59:39 +01:00
|
|
|
isClean = true;
|
|
|
|
}
|
2020-04-13 15:19:58 +02:00
|
|
|
document.body.classList.toggle('editing', isClean === false);
|
2018-03-11 15:59:39 +01:00
|
|
|
diff.classList.toggle('dirty', mergeView.leftChunks().length !== 0);
|
2019-05-25 16:04:31 +02:00
|
|
|
document.getElementById('editSaveButton')
|
|
|
|
.classList.toggle('disabled', isClean);
|
2018-12-22 17:55:13 +01:00
|
|
|
const input = document.querySelector('#ruleFilter input');
|
2018-03-21 12:24:52 +01:00
|
|
|
if ( isClean ) {
|
|
|
|
input.removeAttribute('disabled');
|
|
|
|
CodeMirror.commands.save = undefined;
|
|
|
|
} else {
|
|
|
|
input.setAttribute('disabled', '');
|
|
|
|
CodeMirror.commands.save = editSaveHandler;
|
|
|
|
}
|
2015-02-11 17:34:51 +01:00
|
|
|
};
|
2018-03-11 15:59:39 +01:00
|
|
|
|
|
|
|
return function(now) {
|
2021-07-25 16:55:35 +02:00
|
|
|
if ( timer !== undefined ) { globals.cancelIdleCallback(timer); }
|
|
|
|
timer = now ? process() : globals.requestIdleCallback(process, { timeout: 57 });
|
2018-03-11 15:59:39 +01:00
|
|
|
};
|
|
|
|
})();
|
2015-02-11 17:34:51 +01:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2018-12-22 17:55:13 +01:00
|
|
|
const revertAllHandler = function() {
|
2019-09-17 21:15:01 +02:00
|
|
|
const toAdd = [], toRemove = [];
|
|
|
|
const left = mergeView.leftOriginal();
|
|
|
|
const edit = mergeView.editor();
|
|
|
|
for ( const chunk of mergeView.leftChunks() ) {
|
|
|
|
const addedLines = left.getRange(
|
2018-03-11 15:59:39 +01:00
|
|
|
{ line: chunk.origFrom, ch: 0 },
|
|
|
|
{ line: chunk.origTo, ch: 0 }
|
|
|
|
);
|
2019-09-17 21:15:01 +02:00
|
|
|
const removedLines = edit.getRange(
|
2018-03-11 15:59:39 +01:00
|
|
|
{ line: chunk.editFrom, ch: 0 },
|
|
|
|
{ line: chunk.editTo, ch: 0 }
|
|
|
|
);
|
|
|
|
toAdd.push(addedLines.trim());
|
|
|
|
toRemove.push(removedLines.trim());
|
2015-03-12 06:52:27 +01:00
|
|
|
}
|
2018-03-23 20:05:35 +01:00
|
|
|
applyDiff(false, toAdd.join('\n'), toRemove.join('\n'));
|
2015-02-11 17:34:51 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2018-12-22 17:55:13 +01:00
|
|
|
const commitAllHandler = function() {
|
2019-09-17 21:15:01 +02:00
|
|
|
const toAdd = [], toRemove = [];
|
|
|
|
const left = mergeView.leftOriginal();
|
|
|
|
const edit = mergeView.editor();
|
|
|
|
for ( const chunk of mergeView.leftChunks() ) {
|
|
|
|
const addedLines = edit.getRange(
|
2018-03-11 15:59:39 +01:00
|
|
|
{ line: chunk.editFrom, ch: 0 },
|
|
|
|
{ line: chunk.editTo, ch: 0 }
|
|
|
|
);
|
2019-09-17 21:15:01 +02:00
|
|
|
const removedLines = left.getRange(
|
2018-03-11 15:59:39 +01:00
|
|
|
{ line: chunk.origFrom, ch: 0 },
|
|
|
|
{ line: chunk.origTo, ch: 0 }
|
|
|
|
);
|
|
|
|
toAdd.push(addedLines.trim());
|
|
|
|
toRemove.push(removedLines.trim());
|
|
|
|
}
|
2018-03-23 20:05:35 +01:00
|
|
|
applyDiff(true, toAdd.join('\n'), toRemove.join('\n'));
|
2015-02-11 17:34:51 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2018-12-22 17:55:13 +01:00
|
|
|
const editSaveHandler = function() {
|
2019-09-17 21:15:01 +02:00
|
|
|
const editor = mergeView.editor();
|
|
|
|
const editText = editor.getValue().trim();
|
2018-03-11 15:59:39 +01:00
|
|
|
if ( editText === cleanEditText ) {
|
2018-03-21 12:24:52 +01:00
|
|
|
onTextChanged(true);
|
2018-03-11 15:59:39 +01:00
|
|
|
return;
|
|
|
|
}
|
2019-09-17 21:15:01 +02:00
|
|
|
const toAdd = [], toRemove = [];
|
2020-08-25 19:23:30 +02:00
|
|
|
const diffs = getDiffer().diff_main(cleanEditText, editText);
|
2019-09-17 21:15:01 +02:00
|
|
|
for ( const diff of diffs ) {
|
2018-03-11 15:59:39 +01:00
|
|
|
if ( diff[0] === 1 ) {
|
|
|
|
toAdd.push(diff[1]);
|
|
|
|
} else if ( diff[0] === -1 ) {
|
|
|
|
toRemove.push(diff[1]);
|
|
|
|
}
|
|
|
|
}
|
2018-03-23 20:05:35 +01:00
|
|
|
applyDiff(false, toAdd.join(''), toRemove.join(''));
|
2015-02-11 17:34:51 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2021-07-25 16:55:35 +02:00
|
|
|
globals.cloud.onPush = function() {
|
2021-01-28 15:43:14 +01:00
|
|
|
return thePanes.orig.original.join('\n');
|
2015-08-11 21:29:14 +02:00
|
|
|
};
|
|
|
|
|
2021-07-25 16:55:35 +02:00
|
|
|
globals.cloud.onPull = function(data, append) {
|
2018-03-11 15:59:39 +01:00
|
|
|
if ( typeof data !== 'string' ) { return; }
|
|
|
|
applyDiff(
|
|
|
|
false,
|
|
|
|
data,
|
2018-03-23 20:05:35 +01:00
|
|
|
append ? '' : mergeView.editor().getValue().trim()
|
2018-03-11 15:59:39 +01:00
|
|
|
);
|
2015-08-11 21:29:14 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2021-07-25 16:55:35 +02:00
|
|
|
globals.hasUnsavedData = function() {
|
2019-05-19 21:35:00 +02:00
|
|
|
return mergeView.editor().isClean(cleanEditToken) === false;
|
|
|
|
};
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2019-09-17 21:15:01 +02:00
|
|
|
vAPI.messaging.send('dashboard', {
|
|
|
|
what: 'getRules',
|
|
|
|
}).then(details => {
|
2020-08-25 19:23:30 +02:00
|
|
|
thePanes.orig.original = details.permanentRules;
|
|
|
|
thePanes.edit.original = details.sessionRules;
|
2021-07-25 16:55:35 +02:00
|
|
|
publicSuffixList.fromSelfie(details.pslSelfie);
|
2020-08-25 19:23:30 +02:00
|
|
|
onPresentationChanged(true);
|
2019-09-17 21:15:01 +02:00
|
|
|
});
|
2018-03-11 15:59:39 +01:00
|
|
|
|
2015-08-11 21:29:14 +02:00
|
|
|
// Handle user interaction
|
|
|
|
uDom('#importButton').on('click', startImportFilePicker);
|
|
|
|
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
|
|
|
uDom('#exportButton').on('click', exportUserRulesToFile);
|
2018-03-11 15:59:39 +01:00
|
|
|
uDom('#revertButton').on('click', revertAllHandler);
|
|
|
|
uDom('#commitButton').on('click', commitAllHandler);
|
|
|
|
uDom('#editSaveButton').on('click', editSaveHandler);
|
2018-03-21 12:24:52 +01:00
|
|
|
uDom('#ruleFilter input').on('input', onFilterChanged);
|
2020-08-25 19:23:30 +02:00
|
|
|
uDom('#ruleFilter select').on('input', ( ) => {
|
|
|
|
onPresentationChanged(true);
|
|
|
|
});
|
|
|
|
uDom('#ruleFilter #diffCollapse').on('click', ev => {
|
|
|
|
isCollapsed = ev.target.classList.toggle('active');
|
|
|
|
onPresentationChanged(true);
|
|
|
|
});
|
2015-08-11 21:29:14 +02:00
|
|
|
|
2018-03-11 15:59:39 +01:00
|
|
|
// https://groups.google.com/forum/#!topic/codemirror/UQkTrt078Vs
|
2020-08-24 18:39:07 +02:00
|
|
|
mergeView.editor().on('updateDiff', ( ) => { onTextChanged(); });
|
2014-12-31 23:26:17 +01:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|