1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-09-15 15:32:28 +02:00

#731: UI counterpart

This commit is contained in:
gorhill 2015-02-11 11:34:51 -05:00
parent 287f502321
commit 44768e8dba
5 changed files with 331 additions and 57 deletions

View File

@ -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": {

View File

@ -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;
}

View File

@ -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> &emsp;
<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>

View File

@ -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>&nbsp;');
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);
});
/******************************************************************************/

View File

@ -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: