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",
|
||||
"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": {
|
||||
"message": "Edit",
|
||||
"description": "Will enable manual-edit mode (textarea)"
|
||||
|
@ -356,7 +372,7 @@
|
|||
"description": ""
|
||||
},
|
||||
"rulesExport": {
|
||||
"message": "Export to file...",
|
||||
"message": "Export to file",
|
||||
"description": ""
|
||||
},
|
||||
"rulesDefaultFileName": {
|
||||
|
|
|
@ -4,15 +4,155 @@ div > p:first-child {
|
|||
div > p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
#rulesEditor {
|
||||
font-size: small;
|
||||
width: 48em;
|
||||
height: 40em;
|
||||
white-space: pre;
|
||||
text-align: left;
|
||||
}
|
||||
code {
|
||||
background-color: #eee;
|
||||
font: 11px monospace;
|
||||
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>
|
||||
|
||||
<div>
|
||||
<p data-i18n="rulesHint"></p>
|
||||
<p data-i18n="rulesFormatHint"></p>
|
||||
<p><button id="rulesApply" disabled="true" data-i18n="1pApplyChanges"></button></p>
|
||||
<textarea id="rulesEditor" spellcheck="false" dir="auto"></textarea>
|
||||
<p><button id="importButton" type="button" data-i18n="1pImport"></button>  
|
||||
<button id="exportButton" type="button" data-i18n="1pExport"></button>
|
||||
<input class="hidden" id="importFilePicker" type="file" accept="text/plain" style="display: none;"></p>
|
||||
<span class="hidden" data-i18n="rulesDefaultFileName" style="display: none;"></span>
|
||||
</div>
|
||||
<p data-i18n="rulesHint"></p>
|
||||
<p data-i18n="rulesFormatHint"></p>
|
||||
<div id="diff">
|
||||
<div class="pane left">
|
||||
<div>
|
||||
<h2 data-i18n="rulesPermanentHeader"></h2>
|
||||
<button type="button" id="exportButton" data-i18n="rulesExport"></button>
|
||||
<button type="button" id="revertButton" data-i18n="rulesRevert"></button>
|
||||
</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-client.js"></script>
|
||||
|
|
|
@ -38,7 +38,7 @@ var messager = vAPI.messaging.channel('dyna-rules.js');
|
|||
var normalizeRawRules = function(s) {
|
||||
return s.replace(/[ \t]+/g, ' ')
|
||||
.split(/\s*\n+\s*/)
|
||||
.sort(directiveSort)
|
||||
.sort()
|
||||
.join('\n')
|
||||
.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) {
|
||||
var aIsSwitch = a.indexOf(':') !== -1;
|
||||
var bIsSwitch = b.indexOf(':') !== -1;
|
||||
if ( aIsSwitch === bIsSwitch ) {
|
||||
return a.localeCompare(b);
|
||||
rules = details.sessionRules.split(/\n+/);
|
||||
i = rules.length;
|
||||
while ( i-- ) {
|
||||
rule = rules[i].trim();
|
||||
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) {
|
||||
cachedRawRules = normalizeRawRules(rawRules);
|
||||
uDom('#rulesEditor').val(cachedRawRules);
|
||||
};
|
||||
rules = Object.keys(allRules).sort();
|
||||
for ( i = 0; i < rules.length; i++ ) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var rulesApplyHandler = function() {
|
||||
var onWritten = function(response) {
|
||||
processRules(response);
|
||||
rulesChanged();
|
||||
};
|
||||
var request = {
|
||||
what: 'setDynamicRules',
|
||||
rawRules: uDom('#rulesEditor').val()
|
||||
};
|
||||
messager.send(request, onWritten);
|
||||
uDom('#diff > .left > ul > li').remove();
|
||||
uDom('#diff > .left > ul').html(permanentList.join(''));
|
||||
uDom('#diff > .right > ul > li').remove();
|
||||
uDom('#diff > .right > ul').html(sessionList.join(''));
|
||||
uDom('#diff').toggleClass('dirty', details.sessionRules !== details.permanentRules);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -106,9 +123,11 @@ function handleImportFilePicker() {
|
|||
.replace(/\|/g, ' ')
|
||||
.replace(/\n/g, ' * noop\n');
|
||||
}
|
||||
var textarea = uDom('#rulesEditor');
|
||||
textarea.val([textarea.val(), result].join('\n').trim());
|
||||
rulesChanged();
|
||||
var request = {
|
||||
'what': 'setSessionFirewallRules',
|
||||
'rules': rulesFromHTML('#diff .right li') + '\n' + result
|
||||
};
|
||||
messager.send(request, renderRules);
|
||||
};
|
||||
var file = this.files[0];
|
||||
if ( file === undefined || file.name === '' ) {
|
||||
|
@ -141,7 +160,7 @@ function exportUserRulesToFile() {
|
|||
.replace('{{datetime}}', now.toLocaleString())
|
||||
.replace(/ +/g, '_');
|
||||
vAPI.download({
|
||||
'url': 'data:text/plain,' + encodeURIComponent(uDom('#rulesEditor').val()),
|
||||
'url': 'data:text/plain,' + encodeURIComponent(rulesFromHTML('#diff .left li')),
|
||||
'filename': filename,
|
||||
'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() {
|
||||
// Handle user interaction
|
||||
uDom('#importButton').on('click', startImportFilePicker);
|
||||
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
||||
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) {
|
||||
// Async
|
||||
switch ( request.what ) {
|
||||
|
@ -738,14 +747,19 @@ var onMessage = function(request, sender, callback) {
|
|||
var response;
|
||||
|
||||
switch ( request.what ) {
|
||||
case 'getDynamicRules':
|
||||
response = µb.permanentFirewall.toString();
|
||||
case 'getFirewallRules':
|
||||
response = getFirewallRules();
|
||||
break;
|
||||
|
||||
case 'setDynamicRules':
|
||||
µb.permanentFirewall.fromString(request.rawRules);
|
||||
case 'setSessionFirewallRules':
|
||||
µb.sessionFirewall.fromString(request.rules);
|
||||
response = getFirewallRules();
|
||||
break;
|
||||
|
||||
case 'setPermanentFirewallRules':
|
||||
µb.permanentFirewall.fromString(request.rules);
|
||||
µb.savePermanentFirewallRules();
|
||||
response = µb.permanentFirewall.toString();
|
||||
response = getFirewallRules();
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
Loading…
Reference in a new issue