make use of CodeMirror for Whitelist pane

This commit is contained in:
Raymond Hill 2018-03-12 08:28:07 -04:00
parent 42a05746e5
commit 9715d1e8b9
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
5 changed files with 127 additions and 151 deletions

View File

@ -5,36 +5,7 @@ div > p:last-child {
margin-bottom: 0;
}
#whitelist {
border: 1px solid gray;
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;
white-space: pre;
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;
}

View File

@ -135,7 +135,11 @@ var onMessage = function(request, sender, callback) {
break;
case 'getWhitelist':
response = µb.stringFromWhitelist(µb.netWhitelist);
response = {
whitelist: µb.stringFromWhitelist(µb.netWhitelist),
reBadHostname: µb.reWhitelistBadHostname.source,
reHostnameExtractor: µb.reWhitelistHostnameExtractor.source
};
break;
case 'launchElementPicker':
@ -985,10 +989,6 @@ var onMessage = function(request, sender, callback) {
resetUserData();
break;
case 'validateWhitelistString':
response = µb.validateWhitelistString(request.raw);
break;
case 'writeHiddenSettings':
µb.hiddenSettings = µb.hiddenSettingsFromString(request.content);
µb.saveHiddenSettings();

View File

@ -219,7 +219,7 @@ var matchBucket = function(url, hostname, bucket, start) {
}
// Plain hostname
else if ( line.indexOf('/') === -1 ) {
if ( reInvalidHostname.test(line) ) {
if ( this.reWhitelistBadHostname.test(line) ) {
key = '#';
directive = '# ' + line;
} else {
@ -242,7 +242,7 @@ var matchBucket = function(url, hostname, bucket, start) {
// label (or else it would be just impossible to make an efficient
// dict.
else {
matches = reHostnameExtractor.exec(line);
matches = this.reWhitelistHostnameExtractor.exec(line);
if ( !matches || matches.length !== 2 ) {
key = '#';
directive = '# ' + line;
@ -266,27 +266,8 @@ var matchBucket = function(url, hostname, bucket, start) {
return whitelist;
};
µBlock.validateWhitelistString = function(s) {
var lineIter = new this.LineIterator(s), line;
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]*$/;
µBlock.reWhitelistBadHostname = /[^a-z0-9.\-\[\]:]/;
µBlock.reWhitelistHostnameExtractor = /([a-z0-9\[][a-z0-9.\-]*[a-z0-9\]])(?::[\d*]+)?\/(?:[^\x00-\x20\/]|$)[^\x00-\x20]*$/;
/******************************************************************************/

View File

@ -1,7 +1,7 @@
/*******************************************************************************
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
it under the terms of the GNU General Public License as published by
@ -19,79 +19,94 @@
Home: https://github.com/gorhill/uBlock
*/
/* global uDom, uBlockDashboard */
/******************************************************************************/
(function() {
/* global CodeMirror, uDom, uBlockDashboard */
'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,
cachedWhitelist = '';
cachedWhitelist = '',
noopFunc = function(){};
/******************************************************************************/
var getTextareaNode = function() {
var me = getTextareaNode,
node = me.theNode;
if ( node === undefined ) {
node = me.theNode = uDom.nodeFromSelector('#whitelist textarea');
var cmEditor = new CodeMirror(
document.getElementById('whitelist'),
{
autofocus: true,
inputStyle: 'contenteditable',
lineNumbers: true,
lineWrapping: true,
styleActiveLine: true
}
return node;
};
var setErrorNodeHorizontalOffset = function(px) {
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
);
/******************************************************************************/
var whitelistChanged = function() {
var whitelistElem = uDom.nodeFromId('whitelist');
var bad = whitelistElem.querySelector('.cm-error') !== null;
var changedWhitelist = cmEditor.getValue().trim();
var changed = changedWhitelist !== cachedWhitelist;
uDom.nodeFromId('whitelistApply').disabled = !changed || bad;
uDom.nodeFromId('whitelistRevert').disabled = !changed;
CodeMirror.commands.save = changed && !bad ? applyChanges : noopFunc;
};
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);
};
})();
cmEditor.on('changes', whitelistChanged);
/******************************************************************************/
var renderWhitelist = function() {
var onRead = function(whitelist) {
cachedWhitelist = whitelist.trim();
getTextareaNode().value = cachedWhitelist + '\n';
whitelistChanged();
var onRead = function(details) {
var first = reBadHostname === undefined;
if ( first ) {
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);
};
@ -100,17 +115,16 @@ var renderWhitelist = function() {
var handleImportFilePicker = function() {
var fileReaderOnLoadHandler = function() {
var textarea = getTextareaNode();
textarea.value = [textarea.value.trim(), this.result.trim()].join('\n').trim();
whitelistChanged();
cmEditor.setValue(
[
cmEditor.getValue().trim(),
this.result.trim()
].join('\n').trim()
);
};
var file = this.files[0];
if ( file === undefined || file.name === '' ) {
return;
}
if ( file.type.indexOf('text') !== 0 ) {
return;
}
if ( file === undefined || file.name === '' ) { return; }
if ( file.type.indexOf('text') !== 0 ) { return; }
var fr = new FileReader();
fr.onload = fileReaderOnLoadHandler;
fr.readAsText(file);
@ -130,7 +144,7 @@ var startImportFilePicker = function() {
/******************************************************************************/
var exportWhitelistToFile = function() {
var val = getTextareaNode().value.trim();
var val = cmEditor.getValue().trim();
if ( val === '' ) { return; }
var filename = vAPI.i18n('whitelistExportFilename')
.replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString())
@ -144,35 +158,35 @@ var exportWhitelistToFile = function() {
/******************************************************************************/
var applyChanges = function() {
cachedWhitelist = getTextareaNode().value.trim();
var request = {
cachedWhitelist = cmEditor.getValue().trim();
messaging.send(
'dashboard',
{
what: 'setWhitelist',
whitelist: cachedWhitelist
};
messaging.send('dashboard', request, renderWhitelist);
},
renderWhitelist
);
};
var revertChanges = function() {
getTextareaNode().value = cachedWhitelist + '\n';
whitelistChanged();
var content = cachedWhitelist;
if ( content !== '' ) { content += '\n'; }
cmEditor.setValue(content);
};
/******************************************************************************/
var getCloudData = function() {
return getTextareaNode().value;
return cmEditor.getValue();
};
var setCloudData = function(data, append) {
if ( typeof data !== 'string' ) {
return;
}
var textarea = getTextareaNode();
if ( typeof data !== 'string' ) { return; }
if ( append ) {
data = uBlockDashboard.mergeNewLines(textarea.value.trim(), data);
data = uBlockDashboard.mergeNewLines(cmEditor.getValue().trim(), data);
}
textarea.value = data.trim() + '\n';
whitelistChanged();
cmEditor.setValue(data.trim() + '\n');
};
self.cloud.onPush = getCloudData;
@ -183,7 +197,6 @@ self.cloud.onPull = setCloudData;
uDom('#importWhitelistFromFile').on('click', startImportFilePicker);
uDom('#importFilePicker').on('change', handleImportFilePicker);
uDom('#exportWhitelistToFile').on('click', exportWhitelistToFile);
uDom('#whitelist textarea').on('input', whitelistChanged);
uDom('#whitelistApply').on('click', applyChanges);
uDom('#whitelistRevert').on('click', revertChanges);

View File

@ -4,10 +4,15 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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" type="text/css" href="css/cloud-ui.css">
<link rel="stylesheet" type="text/css" href="css/whitelist.css">
<link rel="stylesheet" href="lib/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="lib/codemirror/addon/search/matchesonscrollbar.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>
<body>
@ -18,16 +23,22 @@
<p>
<button id="whitelistApply" class="custom important" type="button" disabled="true" data-i18n="whitelistApply"></button>&ensp;
<button id="whitelistRevert" class="custom" type="button" disabled="true" data-i18n="genericRevert"></button>
<p><section id="whitelist">
<textarea dir="auto" spellcheck="false"></textarea>
<div>E</div>
</section>
<p><div id="whitelist" class="codeMirrorContainer"></div>
<p>
<button id="importWhitelistFromFile" class="custom" data-i18n="whitelistImport"></button>&ensp;
<button id="exportWhitelistToFile" class="custom" data-i18n="whitelistExport"></button>
<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="js/codemirror/search.js"></script>
<script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>