mirror of https://github.com/gorhill/uBlock.git
make use of CodeMirror for Whitelist pane
This commit is contained in:
parent
42a05746e5
commit
9715d1e8b9
|
@ -5,36 +5,7 @@ div > p:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
#whitelist {
|
#whitelist {
|
||||||
border: 1px solid gray;
|
|
||||||
height: 60vh;
|
height: 60vh;
|
||||||
margin: 0;
|
|
||||||
padding: 1px;
|
|
||||||
position: relative;
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
#whitelist.invalid {
|
|
||||||
border-color: red;
|
|
||||||
}
|
|
||||||
#whitelist textarea {
|
|
||||||
border: none;
|
|
||||||
box-sizing: border-box;
|
|
||||||
height: 100%;
|
|
||||||
padding: 0.4em;
|
|
||||||
resize: none;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
white-space: pre;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
#whitelist textarea + div {
|
|
||||||
background-color: red;
|
|
||||||
bottom: 0;
|
|
||||||
color: white;
|
|
||||||
display: none;
|
|
||||||
padding: 2px 4px;
|
|
||||||
pointer-events: none;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
#whitelist.invalid textarea + div {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
|
@ -135,7 +135,11 @@ var onMessage = function(request, sender, callback) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'getWhitelist':
|
case 'getWhitelist':
|
||||||
response = µb.stringFromWhitelist(µb.netWhitelist);
|
response = {
|
||||||
|
whitelist: µb.stringFromWhitelist(µb.netWhitelist),
|
||||||
|
reBadHostname: µb.reWhitelistBadHostname.source,
|
||||||
|
reHostnameExtractor: µb.reWhitelistHostnameExtractor.source
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'launchElementPicker':
|
case 'launchElementPicker':
|
||||||
|
@ -985,10 +989,6 @@ var onMessage = function(request, sender, callback) {
|
||||||
resetUserData();
|
resetUserData();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'validateWhitelistString':
|
|
||||||
response = µb.validateWhitelistString(request.raw);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'writeHiddenSettings':
|
case 'writeHiddenSettings':
|
||||||
µb.hiddenSettings = µb.hiddenSettingsFromString(request.content);
|
µb.hiddenSettings = µb.hiddenSettingsFromString(request.content);
|
||||||
µb.saveHiddenSettings();
|
µb.saveHiddenSettings();
|
||||||
|
|
|
@ -219,7 +219,7 @@ var matchBucket = function(url, hostname, bucket, start) {
|
||||||
}
|
}
|
||||||
// Plain hostname
|
// Plain hostname
|
||||||
else if ( line.indexOf('/') === -1 ) {
|
else if ( line.indexOf('/') === -1 ) {
|
||||||
if ( reInvalidHostname.test(line) ) {
|
if ( this.reWhitelistBadHostname.test(line) ) {
|
||||||
key = '#';
|
key = '#';
|
||||||
directive = '# ' + line;
|
directive = '# ' + line;
|
||||||
} else {
|
} else {
|
||||||
|
@ -242,7 +242,7 @@ var matchBucket = function(url, hostname, bucket, start) {
|
||||||
// label (or else it would be just impossible to make an efficient
|
// label (or else it would be just impossible to make an efficient
|
||||||
// dict.
|
// dict.
|
||||||
else {
|
else {
|
||||||
matches = reHostnameExtractor.exec(line);
|
matches = this.reWhitelistHostnameExtractor.exec(line);
|
||||||
if ( !matches || matches.length !== 2 ) {
|
if ( !matches || matches.length !== 2 ) {
|
||||||
key = '#';
|
key = '#';
|
||||||
directive = '# ' + line;
|
directive = '# ' + line;
|
||||||
|
@ -266,27 +266,8 @@ var matchBucket = function(url, hostname, bucket, start) {
|
||||||
return whitelist;
|
return whitelist;
|
||||||
};
|
};
|
||||||
|
|
||||||
µBlock.validateWhitelistString = function(s) {
|
µBlock.reWhitelistBadHostname = /[^a-z0-9.\-\[\]:]/;
|
||||||
var lineIter = new this.LineIterator(s), line;
|
µBlock.reWhitelistHostnameExtractor = /([a-z0-9\[][a-z0-9.\-]*[a-z0-9\]])(?::[\d*]+)?\/(?:[^\x00-\x20\/]|$)[^\x00-\x20]*$/;
|
||||||
while ( !lineIter.eot() ) {
|
|
||||||
line = lineIter.next().trim();
|
|
||||||
if ( line === '' ) { continue; }
|
|
||||||
if ( line.startsWith('#') ) { continue; } // Comment
|
|
||||||
if ( line.indexOf('/') === -1 ) { // Plain hostname
|
|
||||||
if ( reInvalidHostname.test(line) ) { return false; }
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ( line.length > 2 && line.startsWith('/') && line.endsWith('/') ) { // Regex-based
|
|
||||||
try { new RegExp(line.slice(1, -1)); } catch(ex) { return false; }
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ( reHostnameExtractor.test(line) === false ) { return false; } // URL
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
var reInvalidHostname = /[^a-z0-9.\-\[\]:]/,
|
|
||||||
reHostnameExtractor = /([a-z0-9\[][a-z0-9.\-]*[a-z0-9\]])(?::[\d*]+)?\/(?:[^\x00-\x20\/]|$)[^\x00-\x20]*$/;
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
|
||||||
uBlock Origin - a browser extension to block requests.
|
uBlock Origin - a browser extension to block requests.
|
||||||
Copyright (C) 2014-2016 Raymond Hill
|
Copyright (C) 2014-2018 Raymond Hill
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,79 +19,94 @@
|
||||||
Home: https://github.com/gorhill/uBlock
|
Home: https://github.com/gorhill/uBlock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global uDom, uBlockDashboard */
|
/* global CodeMirror, uDom, uBlockDashboard */
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
CodeMirror.defineMode("ubo-whitelist-directives", function() {
|
||||||
|
var reComment = /^\s*#/,
|
||||||
|
reRegex = /^\/.+\/$/;
|
||||||
|
|
||||||
|
return {
|
||||||
|
token: function(stream) {
|
||||||
|
var line = stream.string.trim();
|
||||||
|
stream.skipToEnd();
|
||||||
|
if ( reBadHostname === undefined ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ( reComment.test(line) ) {
|
||||||
|
return 'comment';
|
||||||
|
}
|
||||||
|
if ( line.indexOf('/') === -1 ) {
|
||||||
|
return reBadHostname.test(line) ? 'error' : null;
|
||||||
|
}
|
||||||
|
if ( reRegex.test(line) ) {
|
||||||
|
try {
|
||||||
|
new RegExp(line.slice(1, -1));
|
||||||
|
} catch(ex) {
|
||||||
|
return 'error';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return reHostnameExtractor.test(line) ? null : 'error';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
var reBadHostname,
|
||||||
|
reHostnameExtractor;
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
var messaging = vAPI.messaging,
|
var messaging = vAPI.messaging,
|
||||||
cachedWhitelist = '';
|
cachedWhitelist = '',
|
||||||
|
noopFunc = function(){};
|
||||||
|
|
||||||
|
var cmEditor = new CodeMirror(
|
||||||
|
document.getElementById('whitelist'),
|
||||||
|
{
|
||||||
|
autofocus: true,
|
||||||
|
inputStyle: 'contenteditable',
|
||||||
|
lineNumbers: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
styleActiveLine: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var getTextareaNode = function() {
|
var whitelistChanged = function() {
|
||||||
var me = getTextareaNode,
|
var whitelistElem = uDom.nodeFromId('whitelist');
|
||||||
node = me.theNode;
|
var bad = whitelistElem.querySelector('.cm-error') !== null;
|
||||||
if ( node === undefined ) {
|
var changedWhitelist = cmEditor.getValue().trim();
|
||||||
node = me.theNode = uDom.nodeFromSelector('#whitelist textarea');
|
var changed = changedWhitelist !== cachedWhitelist;
|
||||||
}
|
uDom.nodeFromId('whitelistApply').disabled = !changed || bad;
|
||||||
return node;
|
uDom.nodeFromId('whitelistRevert').disabled = !changed;
|
||||||
|
CodeMirror.commands.save = changed && !bad ? applyChanges : noopFunc;
|
||||||
};
|
};
|
||||||
|
|
||||||
var setErrorNodeHorizontalOffset = function(px) {
|
cmEditor.on('changes', whitelistChanged);
|
||||||
var me = setErrorNodeHorizontalOffset,
|
|
||||||
offset = me.theOffset || 0;
|
|
||||||
if ( px === offset ) { return; }
|
|
||||||
var node = me.theNode;
|
|
||||||
if ( node === undefined ) {
|
|
||||||
node = me.theNode = uDom.nodeFromSelector('#whitelist textarea + div');
|
|
||||||
}
|
|
||||||
node.style.right = px + 'px';
|
|
||||||
me.theOffset = px;
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
var whitelistChanged = (function() {
|
|
||||||
var changedWhitelist, changed, timer;
|
|
||||||
|
|
||||||
var updateUI = function(good) {
|
|
||||||
uDom.nodeFromId('whitelistApply').disabled = changed || !good;
|
|
||||||
uDom.nodeFromId('whitelistRevert').disabled = changed;
|
|
||||||
uDom.nodeFromId('whitelist').classList.toggle('invalid', !good);
|
|
||||||
};
|
|
||||||
|
|
||||||
var validate = function() {
|
|
||||||
timer = undefined;
|
|
||||||
messaging.send(
|
|
||||||
'dashboard',
|
|
||||||
{ what: 'validateWhitelistString', raw: changedWhitelist },
|
|
||||||
updateUI
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
changedWhitelist = getTextareaNode().value.trim();
|
|
||||||
changed = changedWhitelist === cachedWhitelist;
|
|
||||||
if ( timer !== undefined ) { clearTimeout(timer); }
|
|
||||||
timer = vAPI.setTimeout(validate, 251);
|
|
||||||
var textarea = getTextareaNode();
|
|
||||||
setErrorNodeHorizontalOffset(textarea.offsetWidth - textarea.clientWidth);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var renderWhitelist = function() {
|
var renderWhitelist = function() {
|
||||||
var onRead = function(whitelist) {
|
var onRead = function(details) {
|
||||||
cachedWhitelist = whitelist.trim();
|
var first = reBadHostname === undefined;
|
||||||
getTextareaNode().value = cachedWhitelist + '\n';
|
if ( first ) {
|
||||||
whitelistChanged();
|
reBadHostname = new RegExp(details.reBadHostname);
|
||||||
|
reHostnameExtractor = new RegExp(details.reHostnameExtractor);
|
||||||
|
}
|
||||||
|
cachedWhitelist = details.whitelist.trim();
|
||||||
|
cmEditor.setValue(cachedWhitelist + '\n');
|
||||||
|
if ( first ) {
|
||||||
|
cmEditor.clearHistory();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
messaging.send('dashboard', { what: 'getWhitelist' }, onRead);
|
messaging.send('dashboard', { what: 'getWhitelist' }, onRead);
|
||||||
};
|
};
|
||||||
|
@ -100,17 +115,16 @@ var renderWhitelist = function() {
|
||||||
|
|
||||||
var handleImportFilePicker = function() {
|
var handleImportFilePicker = function() {
|
||||||
var fileReaderOnLoadHandler = function() {
|
var fileReaderOnLoadHandler = function() {
|
||||||
var textarea = getTextareaNode();
|
cmEditor.setValue(
|
||||||
textarea.value = [textarea.value.trim(), this.result.trim()].join('\n').trim();
|
[
|
||||||
whitelistChanged();
|
cmEditor.getValue().trim(),
|
||||||
|
this.result.trim()
|
||||||
|
].join('\n').trim()
|
||||||
|
);
|
||||||
};
|
};
|
||||||
var file = this.files[0];
|
var file = this.files[0];
|
||||||
if ( file === undefined || file.name === '' ) {
|
if ( file === undefined || file.name === '' ) { return; }
|
||||||
return;
|
if ( file.type.indexOf('text') !== 0 ) { return; }
|
||||||
}
|
|
||||||
if ( file.type.indexOf('text') !== 0 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var fr = new FileReader();
|
var fr = new FileReader();
|
||||||
fr.onload = fileReaderOnLoadHandler;
|
fr.onload = fileReaderOnLoadHandler;
|
||||||
fr.readAsText(file);
|
fr.readAsText(file);
|
||||||
|
@ -130,7 +144,7 @@ var startImportFilePicker = function() {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var exportWhitelistToFile = function() {
|
var exportWhitelistToFile = function() {
|
||||||
var val = getTextareaNode().value.trim();
|
var val = cmEditor.getValue().trim();
|
||||||
if ( val === '' ) { return; }
|
if ( val === '' ) { return; }
|
||||||
var filename = vAPI.i18n('whitelistExportFilename')
|
var filename = vAPI.i18n('whitelistExportFilename')
|
||||||
.replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString())
|
.replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString())
|
||||||
|
@ -144,35 +158,35 @@ var exportWhitelistToFile = function() {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var applyChanges = function() {
|
var applyChanges = function() {
|
||||||
cachedWhitelist = getTextareaNode().value.trim();
|
cachedWhitelist = cmEditor.getValue().trim();
|
||||||
var request = {
|
messaging.send(
|
||||||
what: 'setWhitelist',
|
'dashboard',
|
||||||
whitelist: cachedWhitelist
|
{
|
||||||
};
|
what: 'setWhitelist',
|
||||||
messaging.send('dashboard', request, renderWhitelist);
|
whitelist: cachedWhitelist
|
||||||
|
},
|
||||||
|
renderWhitelist
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
var revertChanges = function() {
|
var revertChanges = function() {
|
||||||
getTextareaNode().value = cachedWhitelist + '\n';
|
var content = cachedWhitelist;
|
||||||
whitelistChanged();
|
if ( content !== '' ) { content += '\n'; }
|
||||||
|
cmEditor.setValue(content);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var getCloudData = function() {
|
var getCloudData = function() {
|
||||||
return getTextareaNode().value;
|
return cmEditor.getValue();
|
||||||
};
|
};
|
||||||
|
|
||||||
var setCloudData = function(data, append) {
|
var setCloudData = function(data, append) {
|
||||||
if ( typeof data !== 'string' ) {
|
if ( typeof data !== 'string' ) { return; }
|
||||||
return;
|
|
||||||
}
|
|
||||||
var textarea = getTextareaNode();
|
|
||||||
if ( append ) {
|
if ( append ) {
|
||||||
data = uBlockDashboard.mergeNewLines(textarea.value.trim(), data);
|
data = uBlockDashboard.mergeNewLines(cmEditor.getValue().trim(), data);
|
||||||
}
|
}
|
||||||
textarea.value = data.trim() + '\n';
|
cmEditor.setValue(data.trim() + '\n');
|
||||||
whitelistChanged();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.cloud.onPush = getCloudData;
|
self.cloud.onPush = getCloudData;
|
||||||
|
@ -183,7 +197,6 @@ self.cloud.onPull = setCloudData;
|
||||||
uDom('#importWhitelistFromFile').on('click', startImportFilePicker);
|
uDom('#importWhitelistFromFile').on('click', startImportFilePicker);
|
||||||
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
||||||
uDom('#exportWhitelistToFile').on('click', exportWhitelistToFile);
|
uDom('#exportWhitelistToFile').on('click', exportWhitelistToFile);
|
||||||
uDom('#whitelist textarea').on('input', whitelistChanged);
|
|
||||||
uDom('#whitelistApply').on('click', applyChanges);
|
uDom('#whitelistApply').on('click', applyChanges);
|
||||||
uDom('#whitelistRevert').on('click', revertChanges);
|
uDom('#whitelistRevert').on('click', revertChanges);
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,15 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>uBlock — Whitelist</title>
|
<title>uBlock — Whitelist</title>
|
||||||
<link rel="stylesheet" type="text/css" href="css/common.css">
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
|
<link rel="stylesheet" href="lib/codemirror/lib/codemirror.css">
|
||||||
<link rel="stylesheet" type="text/css" href="css/cloud-ui.css">
|
<link rel="stylesheet" href="lib/codemirror/addon/search/matchesonscrollbar.css">
|
||||||
<link rel="stylesheet" type="text/css" href="css/whitelist.css">
|
|
||||||
|
<link rel="stylesheet" href="css/common.css">
|
||||||
|
<link rel="stylesheet" href="css/dashboard-common.css">
|
||||||
|
<link rel="stylesheet" href="css/cloud-ui.css">
|
||||||
|
<link rel="stylesheet" href="css/whitelist.css">
|
||||||
|
<link rel="stylesheet" href="css/codemirror.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -18,16 +23,22 @@
|
||||||
<p>
|
<p>
|
||||||
<button id="whitelistApply" class="custom important" type="button" disabled="true" data-i18n="whitelistApply"></button> 
|
<button id="whitelistApply" class="custom important" type="button" disabled="true" data-i18n="whitelistApply"></button> 
|
||||||
<button id="whitelistRevert" class="custom" type="button" disabled="true" data-i18n="genericRevert"></button>
|
<button id="whitelistRevert" class="custom" type="button" disabled="true" data-i18n="genericRevert"></button>
|
||||||
<p><section id="whitelist">
|
<p><div id="whitelist" class="codeMirrorContainer"></div>
|
||||||
<textarea dir="auto" spellcheck="false"></textarea>
|
|
||||||
<div>E</div>
|
|
||||||
</section>
|
|
||||||
<p>
|
<p>
|
||||||
<button id="importWhitelistFromFile" class="custom" data-i18n="whitelistImport"></button> 
|
<button id="importWhitelistFromFile" class="custom" data-i18n="whitelistImport"></button> 
|
||||||
<button id="exportWhitelistToFile" class="custom" data-i18n="whitelistExport"></button>
|
<button id="exportWhitelistToFile" class="custom" data-i18n="whitelistExport"></button>
|
||||||
<input id="importFilePicker" type="file" accept="text/plain" class="hiddenFileInput">
|
<input id="importFilePicker" type="file" accept="text/plain" class="hiddenFileInput">
|
||||||
|
|
||||||
|
<script src="lib/codemirror/lib/codemirror.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/display/panel.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/scroll/annotatescrollbar.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/search/matchesonscrollbar.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/search/searchcursor.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/selection/active-line.js"></script>
|
||||||
<script src="lib/punycode.js"></script>
|
<script src="lib/punycode.js"></script>
|
||||||
|
|
||||||
|
<script src="js/codemirror/search.js"></script>
|
||||||
|
|
||||||
<script src="js/vapi.js"></script>
|
<script src="js/vapi.js"></script>
|
||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
|
|
Loading…
Reference in New Issue