mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-11 17:41:03 +01:00
#731: UI counterpart
This commit is contained in:
parent
287f502321
commit
44768e8dba
5 changed files with 331 additions and 57 deletions
|
@ -339,6 +339,22 @@
|
||||||
"message":"Apply changes",
|
"message":"Apply changes",
|
||||||
"description":"English: Apply changes"
|
"description":"English: Apply changes"
|
||||||
},
|
},
|
||||||
|
"rulesPermanentHeader": {
|
||||||
|
"message": "Permanent rules",
|
||||||
|
"description": "header"
|
||||||
|
},
|
||||||
|
"rulesTemporaryHeader": {
|
||||||
|
"message": "Temporary rules",
|
||||||
|
"description": "header"
|
||||||
|
},
|
||||||
|
"rulesRevert": {
|
||||||
|
"message": "Revert",
|
||||||
|
"description": "This will remove all temporary rules"
|
||||||
|
},
|
||||||
|
"rulesCommit": {
|
||||||
|
"message": "Commit",
|
||||||
|
"description": "This will persist temporary rules"
|
||||||
|
},
|
||||||
"rulesEdit": {
|
"rulesEdit": {
|
||||||
"message": "Edit",
|
"message": "Edit",
|
||||||
"description": "Will enable manual-edit mode (textarea)"
|
"description": "Will enable manual-edit mode (textarea)"
|
||||||
|
@ -356,7 +372,7 @@
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"rulesExport": {
|
"rulesExport": {
|
||||||
"message": "Export to file...",
|
"message": "Export to file",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"rulesDefaultFileName": {
|
"rulesDefaultFileName": {
|
||||||
|
|
|
@ -4,15 +4,155 @@ div > p:first-child {
|
||||||
div > p:last-child {
|
div > p:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
#rulesEditor {
|
|
||||||
font-size: small;
|
|
||||||
width: 48em;
|
|
||||||
height: 40em;
|
|
||||||
white-space: pre;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
code {
|
code {
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
font: 11px monospace;
|
font: 11px monospace;
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
}
|
}
|
||||||
|
#diff {
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5em 0 0 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#diff > .pane {
|
||||||
|
border: 0;
|
||||||
|
box-sizing: box-border;
|
||||||
|
display: inline-block;
|
||||||
|
font: 12px/1.4 monospace;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
white-space: normal;
|
||||||
|
width: calc(50% - 2px);
|
||||||
|
}
|
||||||
|
#diff > .pane > div {
|
||||||
|
padding: 0 0 1em 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#diff > .pane > div > span {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
body[dir="ltr"] #revertButton:after {
|
||||||
|
content: '\2009\f061';
|
||||||
|
font-family: FontAwesome;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 1;
|
||||||
|
vertical-align: baseline;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
body[dir="rtl"] #revertButton:after {
|
||||||
|
content: '\2009\f060';
|
||||||
|
font-family: FontAwesome;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 1;
|
||||||
|
vertical-align: baseline;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
body[dir="ltr"] #commitButton:before {
|
||||||
|
content: '\f060\2009';
|
||||||
|
font-family: FontAwesome;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 1;
|
||||||
|
vertical-align: baseline;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
body[dir="rtl"] #commitButton:before {
|
||||||
|
content: '\f061\2009';
|
||||||
|
font-family: FontAwesome;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 1;
|
||||||
|
vertical-align: baseline;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
#revertButton,
|
||||||
|
#commitButton,
|
||||||
|
#diff.edit #editEnterButton {
|
||||||
|
opacity: 0.25;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
#editStopButton,
|
||||||
|
#editCancelButton {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#diff.dirty:not(.edit) #revertButton,
|
||||||
|
#diff.dirty:not(.edit) #commitButton {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
#diff.edit #editStopButton,
|
||||||
|
#diff.edit #editCancelButton {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
#diff.edit #importButton,
|
||||||
|
#diff.edit #exportButton {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#diff ul {
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 1em 0 0 0;
|
||||||
|
}
|
||||||
|
#diff.edit .right ul {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
#diff .left {
|
||||||
|
padding: 0 0 0 0;
|
||||||
|
}
|
||||||
|
#diff .right > ul {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
#diff li {
|
||||||
|
background-color: white;
|
||||||
|
direction: ltr;
|
||||||
|
padding: 2px 0;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#diff li:nth-of-type(2n+0) {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
#diff .right li {
|
||||||
|
}
|
||||||
|
#diff .right li:hover {
|
||||||
|
}
|
||||||
|
#diff .right li.notLeft {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
#diff .right li.notRight {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
#diff .right li.toRemove {
|
||||||
|
color: #000;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
#diff textarea {
|
||||||
|
background-color: #f8f8ff;
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
direction: ltr;
|
||||||
|
font: 12px monospace;
|
||||||
|
line-height: calc(140% + 4px);
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 1em 0 0 0;
|
||||||
|
position: absolute;
|
||||||
|
resize: none;
|
||||||
|
visibility: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#diff.edit textarea {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
|
@ -10,16 +10,33 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div>
|
<p data-i18n="rulesHint"></p>
|
||||||
<p data-i18n="rulesHint"></p>
|
<p data-i18n="rulesFormatHint"></p>
|
||||||
<p data-i18n="rulesFormatHint"></p>
|
<div id="diff">
|
||||||
<p><button id="rulesApply" disabled="true" data-i18n="1pApplyChanges"></button></p>
|
<div class="pane left">
|
||||||
<textarea id="rulesEditor" spellcheck="false" dir="auto"></textarea>
|
<div>
|
||||||
<p><button id="importButton" type="button" data-i18n="1pImport"></button>  
|
<h2 data-i18n="rulesPermanentHeader"></h2>
|
||||||
<button id="exportButton" type="button" data-i18n="1pExport"></button>
|
<button type="button" id="exportButton" data-i18n="rulesExport"></button>
|
||||||
<input class="hidden" id="importFilePicker" type="file" accept="text/plain" style="display: none;"></p>
|
<button type="button" id="revertButton" data-i18n="rulesRevert"></button>
|
||||||
<span class="hidden" data-i18n="rulesDefaultFileName" style="display: none;"></span>
|
</div>
|
||||||
</div>
|
<ul></ul>
|
||||||
|
</div>
|
||||||
|
<div class="pane right">
|
||||||
|
<div>
|
||||||
|
<h2 data-i18n="rulesTemporaryHeader"></h2>
|
||||||
|
<button type="button" id="commitButton" data-i18n="rulesCommit"></button>
|
||||||
|
<button type="button" id="editEnterButton" data-i18n="rulesEdit"></button>
|
||||||
|
<button type="button" id="editStopButton" data-i18n="rulesEditSave"></button>
|
||||||
|
<button type="button" id="editCancelButton" data-i18n="rulesEditDiscard"></button>
|
||||||
|
<button type="button" id="importButton" data-i18n="rulesImport"></button>
|
||||||
|
</div>
|
||||||
|
<textarea spellcheck="false"></textarea>
|
||||||
|
<ul></ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input class="hidden" id="importFilePicker" type="file" accept="text/plain" style="display: none;">
|
||||||
|
<span class="hidden" data-i18n="rulesDefaultFileName" style="display: none;"></span>
|
||||||
|
|
||||||
<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>
|
||||||
|
|
|
@ -38,7 +38,7 @@ var messager = vAPI.messaging.channel('dyna-rules.js');
|
||||||
var normalizeRawRules = function(s) {
|
var normalizeRawRules = function(s) {
|
||||||
return s.replace(/[ \t]+/g, ' ')
|
return s.replace(/[ \t]+/g, ' ')
|
||||||
.split(/\s*\n+\s*/)
|
.split(/\s*\n+\s*/)
|
||||||
.sort(directiveSort)
|
.sort()
|
||||||
.join('\n')
|
.join('\n')
|
||||||
.trim();
|
.trim();
|
||||||
};
|
};
|
||||||
|
@ -58,36 +58,53 @@ var cachedRawRules = '';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// Switches before, rules after
|
var renderRules = function(details) {
|
||||||
|
var rules, rule, i;
|
||||||
|
var permanentList = [];
|
||||||
|
var sessionList = [];
|
||||||
|
var allRules = {};
|
||||||
|
var permanentRules = {};
|
||||||
|
var sessionRules = {};
|
||||||
|
var onLeft, onRight;
|
||||||
|
|
||||||
var directiveSort = function(a, b) {
|
rules = details.sessionRules.split(/\n+/);
|
||||||
var aIsSwitch = a.indexOf(':') !== -1;
|
i = rules.length;
|
||||||
var bIsSwitch = b.indexOf(':') !== -1;
|
while ( i-- ) {
|
||||||
if ( aIsSwitch === bIsSwitch ) {
|
rule = rules[i].trim();
|
||||||
return a.localeCompare(b);
|
sessionRules[rule] = allRules[rule] = true;
|
||||||
}
|
}
|
||||||
return aIsSwitch ? -1 : 1;
|
details.sessionRules = rules.sort().join('\n');
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
rules = details.permanentRules.split(/\n+/);
|
||||||
|
i = rules.length;
|
||||||
|
while ( i-- ) {
|
||||||
|
rule = rules[i].trim();
|
||||||
|
permanentRules[rule] = allRules[rule] = true;
|
||||||
|
}
|
||||||
|
details.permanentRules = rules.sort().join('\n');
|
||||||
|
|
||||||
var processRules = function(rawRules) {
|
rules = Object.keys(allRules).sort();
|
||||||
cachedRawRules = normalizeRawRules(rawRules);
|
for ( i = 0; i < rules.length; i++ ) {
|
||||||
uDom('#rulesEditor').val(cachedRawRules);
|
rule = rules[i];
|
||||||
};
|
onLeft = permanentRules.hasOwnProperty(rule);
|
||||||
|
onRight = sessionRules.hasOwnProperty(rule);
|
||||||
|
if ( onLeft && onRight ) {
|
||||||
|
permanentList.push('<li>', rule);
|
||||||
|
sessionList.push('<li>', rule);
|
||||||
|
} else if ( onLeft ) {
|
||||||
|
permanentList.push('<li>', rule);
|
||||||
|
sessionList.push('<li class="notRight toRemove">', rule);
|
||||||
|
} else {
|
||||||
|
permanentList.push('<li> ');
|
||||||
|
sessionList.push('<li class="notLeft">', rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
uDom('#diff > .left > ul > li').remove();
|
||||||
|
uDom('#diff > .left > ul').html(permanentList.join(''));
|
||||||
var rulesApplyHandler = function() {
|
uDom('#diff > .right > ul > li').remove();
|
||||||
var onWritten = function(response) {
|
uDom('#diff > .right > ul').html(sessionList.join(''));
|
||||||
processRules(response);
|
uDom('#diff').toggleClass('dirty', details.sessionRules !== details.permanentRules);
|
||||||
rulesChanged();
|
|
||||||
};
|
|
||||||
var request = {
|
|
||||||
what: 'setDynamicRules',
|
|
||||||
rawRules: uDom('#rulesEditor').val()
|
|
||||||
};
|
|
||||||
messager.send(request, onWritten);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -106,9 +123,11 @@ function handleImportFilePicker() {
|
||||||
.replace(/\|/g, ' ')
|
.replace(/\|/g, ' ')
|
||||||
.replace(/\n/g, ' * noop\n');
|
.replace(/\n/g, ' * noop\n');
|
||||||
}
|
}
|
||||||
var textarea = uDom('#rulesEditor');
|
var request = {
|
||||||
textarea.val([textarea.val(), result].join('\n').trim());
|
'what': 'setSessionFirewallRules',
|
||||||
rulesChanged();
|
'rules': rulesFromHTML('#diff .right li') + '\n' + result
|
||||||
|
};
|
||||||
|
messager.send(request, renderRules);
|
||||||
};
|
};
|
||||||
var file = this.files[0];
|
var file = this.files[0];
|
||||||
if ( file === undefined || file.name === '' ) {
|
if ( file === undefined || file.name === '' ) {
|
||||||
|
@ -141,7 +160,7 @@ function exportUserRulesToFile() {
|
||||||
.replace('{{datetime}}', now.toLocaleString())
|
.replace('{{datetime}}', now.toLocaleString())
|
||||||
.replace(/ +/g, '_');
|
.replace(/ +/g, '_');
|
||||||
vAPI.download({
|
vAPI.download({
|
||||||
'url': 'data:text/plain,' + encodeURIComponent(uDom('#rulesEditor').val()),
|
'url': 'data:text/plain,' + encodeURIComponent(rulesFromHTML('#diff .left li')),
|
||||||
'filename': filename,
|
'filename': filename,
|
||||||
'saveAs': true
|
'saveAs': true
|
||||||
});
|
});
|
||||||
|
@ -149,15 +168,83 @@ function exportUserRulesToFile() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var rulesFromHTML = function(selector) {
|
||||||
|
var rules = [];
|
||||||
|
var lis = uDom(selector);
|
||||||
|
var li;
|
||||||
|
for ( var i = 0; i < lis.length; i++ ) {
|
||||||
|
li = lis.at(i);
|
||||||
|
if ( li.hasClassName('toRemove') ) {
|
||||||
|
rules.push('');
|
||||||
|
} else {
|
||||||
|
rules.push(li.text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rules.join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var revertHandler = function() {
|
||||||
|
var request = {
|
||||||
|
'what': 'setSessionFirewallRules',
|
||||||
|
'rules': rulesFromHTML('#diff .left li')
|
||||||
|
};
|
||||||
|
messager.send(request, renderRules);
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var commitHandler = function() {
|
||||||
|
var request = {
|
||||||
|
'what': 'setPermanentFirewallRules',
|
||||||
|
'rules': rulesFromHTML('#diff .right li')
|
||||||
|
};
|
||||||
|
messager.send(request, renderRules);
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var editStartHandler = function(ev) {
|
||||||
|
uDom('#diff .right textarea').val(rulesFromHTML('#diff .right li'));
|
||||||
|
var parent = uDom(this).ancestors('#diff');
|
||||||
|
parent.toggleClass('edit', true);
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var editStopHandler = function(ev) {
|
||||||
|
var parent = uDom(this).ancestors('#diff');
|
||||||
|
parent.toggleClass('edit', false);
|
||||||
|
var request = {
|
||||||
|
'what': 'setSessionFirewallRules',
|
||||||
|
'rules': uDom('#diff .right textarea').val()
|
||||||
|
};
|
||||||
|
messager.send(request, renderRules);
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var editCancelHandler = function(ev) {
|
||||||
|
var parent = uDom(this).ancestors('#diff');
|
||||||
|
parent.toggleClass('edit', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
uDom.onLoad(function() {
|
uDom.onLoad(function() {
|
||||||
// Handle user interaction
|
// Handle user interaction
|
||||||
uDom('#importButton').on('click', startImportFilePicker);
|
uDom('#importButton').on('click', startImportFilePicker);
|
||||||
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
||||||
uDom('#exportButton').on('click', exportUserRulesToFile);
|
uDom('#exportButton').on('click', exportUserRulesToFile);
|
||||||
uDom('#rulesEditor').on('input', rulesChanged);
|
|
||||||
uDom('#rulesApply').on('click', rulesApplyHandler);
|
|
||||||
|
|
||||||
messager.send({ what: 'getDynamicRules' }, processRules);
|
uDom('#revertButton').on('click', revertHandler)
|
||||||
|
uDom('#commitButton').on('click', commitHandler)
|
||||||
|
uDom('#editEnterButton').on('click', editStartHandler)
|
||||||
|
uDom('#editStopButton').on('click', editStopHandler)
|
||||||
|
uDom('#editCancelButton').on('click', editCancelHandler)
|
||||||
|
|
||||||
|
messager.send({ what: 'getFirewallRules' }, renderRules);
|
||||||
});
|
});
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
|
@ -727,6 +727,15 @@ var µb = µBlock;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
var getFirewallRules = function() {
|
||||||
|
return {
|
||||||
|
permanentRules: µb.permanentFirewall.toString(),
|
||||||
|
sessionRules: µb.sessionFirewall.toString()
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
var onMessage = function(request, sender, callback) {
|
var onMessage = function(request, sender, callback) {
|
||||||
// Async
|
// Async
|
||||||
switch ( request.what ) {
|
switch ( request.what ) {
|
||||||
|
@ -738,14 +747,19 @@ var onMessage = function(request, sender, callback) {
|
||||||
var response;
|
var response;
|
||||||
|
|
||||||
switch ( request.what ) {
|
switch ( request.what ) {
|
||||||
case 'getDynamicRules':
|
case 'getFirewallRules':
|
||||||
response = µb.permanentFirewall.toString();
|
response = getFirewallRules();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'setDynamicRules':
|
case 'setSessionFirewallRules':
|
||||||
µb.permanentFirewall.fromString(request.rawRules);
|
µb.sessionFirewall.fromString(request.rules);
|
||||||
|
response = getFirewallRules();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'setPermanentFirewallRules':
|
||||||
|
µb.permanentFirewall.fromString(request.rules);
|
||||||
µb.savePermanentFirewallRules();
|
µb.savePermanentFirewallRules();
|
||||||
response = µb.permanentFirewall.toString();
|
response = getFirewallRules();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
Loading…
Reference in a new issue