diff --git a/src/css/user-rules.css b/src/css/user-rules.css
index 58ad530..d2143d8 100644
--- a/src/css/user-rules.css
+++ b/src/css/user-rules.css
@@ -4,10 +4,113 @@ div > p:first-child {
div > p:last-child {
margin-bottom: 0;
}
-#userRules {
- font-size: smaller;
- width: 48em;
- height: 40em;
+#diff {
+ border: 0;
+ margin: 0;
+ padding: 0;
+}
+#diff > div {
+ border: 0;
+ box-sizing: box-border;
+ display: inline-block;
+ font: 12px/1.4 monospace;
+ margin: 0;
+ padding: 0;
+ position: relative;
+ width: calc(50% - 2px);
+ }
+#diff > div > div {
+ padding: 0 0 1em 0;
+ text-align: center;
+ }
+#diff > div > div > span {
+ float: left;
+ }
+#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;
+ padding: 2px 0;
+ white-space: nowrap;
+ }
+#diff li:nth-of-type(2n+0) {
+ background-color: #eee;
+ }
+#diff .right li {
+ cursor: pointer;
+ }
+#diff .right li:hover {
+ background-color: #ffc;
+ }
+#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;
+ visibility: hidden;
+ 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;
white-space: nowrap;
- text-align: left;
+ width: 100%;
+ }
+#diff.edit textarea {
+ visibility: visible;
}
+.hidden {
+ display: none;
+ }
\ No newline at end of file
diff --git a/src/js/matrix.js b/src/js/matrix.js
index 29d2eb7..cf60328 100644
--- a/src/js/matrix.js
+++ b/src/js/matrix.js
@@ -678,7 +678,7 @@ Matrix.prototype.fromString = function(text, append) {
// type = `*`
// state = `allow`
- // Lines with invalid syntax silently ignored
+ // Lines with invalid syntax silently ignored
srcHostname = punycode.toASCII(fields[0]);
desHostname = punycode.toASCII(fields[1]);
diff --git a/src/js/messaging-handlers.js b/src/js/messaging-handlers.js
index 30627cf..80763b6 100644
--- a/src/js/messaging-handlers.js
+++ b/src/js/messaging-handlers.js
@@ -374,14 +374,6 @@ var onMessage = function(request, sender, callback) {
break;
- case 'retrieveDomainCosmeticSelectors':
- response = µMatrix.abpHideFilters.retrieveDomainSelectors(request);
- break;
-
- case 'retrieveGenericCosmeticSelectors':
- response = µMatrix.abpHideFilters.retrieveGenericSelectors(request);
- break;
-
default:
return µMatrix.messaging.defaultHandler(request, sender, callback);
}
@@ -449,13 +441,24 @@ var onMessage = function(request, sender, callback) {
switch ( request.what ) {
case 'getUserRules':
- response = µm.pMatrix.toString();
+ response = {
+ temporaryRules: µm.tMatrix.toString(),
+ permanentRules: µm.pMatrix.toString()
+ }
break;
case 'setUserRules':
- µm.pMatrix.fromString(request.rules);
- µm.tMatrix.assign(µm.pMatrix);
- µm.saveMatrix();
+ if ( typeof request.temporaryRules === 'string' ) {
+ µm.tMatrix.fromString(request.temporaryRules);
+ }
+ if ( typeof request.permanentRules === 'string' ) {
+ µm.pMatrix.fromString(request.permanentRules);
+ µm.saveMatrix();
+ }
+ response = {
+ temporaryRules: µm.tMatrix.toString(),
+ permanentRules: µm.pMatrix.toString()
+ };
break;
default:
diff --git a/src/js/user-rules.js b/src/js/user-rules.js
index e048950..743bf5b 100644
--- a/src/js/user-rules.js
+++ b/src/js/user-rules.js
@@ -27,40 +27,69 @@
/******************************************************************************/
-var cachedUserRules = '';
-
-/******************************************************************************/
-
messaging.start('user-rules.js');
/******************************************************************************/
-// This is to give a visual hint that the content of user blacklist has changed.
-
-function userRulesChanged() {
- uDom('#userRulesApply').prop(
- 'disabled',
- uDom('#userRules').val().trim() === cachedUserRules
- );
-}
+var processUserRules = function(response) {
+ var rules, rule, i;
+ var permanentList = [];
+ var temporaryList = [];
+ var allRules = {};
+ var permanentRules = {};
+ var temporaryRules = {};
+ var onLeft, onRight;
+
+ rules = response.permanentRules.split(/\n+/);
+ i = rules.length;
+ while ( i-- ) {
+ rule = rules[i].trim();
+ permanentRules[rule] = allRules[rule] = true;
+ }
+ rules = response.temporaryRules.split(/\n+/);
+ i = rules.length;
+ while ( i-- ) {
+ rule = rules[i].trim();
+ temporaryRules[rule] = allRules[rule] = true;
+ }
+ rules = Object.keys(allRules).sort();
+ for ( i = 0; i < rules.length; i++ ) {
+ rule = rules[i];
+ onLeft = permanentRules.hasOwnProperty(rule);
+ onRight = temporaryRules.hasOwnProperty(rule);
+ if ( onLeft && onRight ) {
+ permanentList.push('
', rule);
+ temporaryList.push('', rule);
+ } else if ( onLeft ) {
+ permanentList.push('', rule);
+ temporaryList.push('', rule);
+ } else {
+ permanentList.push(' ');
+ temporaryList.push('', rule);
+ }
+ }
-/******************************************************************************/
+ // TODO: build incrementally.
-function renderUserRules() {
- var rulesRead = function(response) {
- cachedUserRules = response;
- uDom('#userRules').val(response);
- };
- messaging.ask({ what: 'getUserRules' }, rulesRead);
-}
+ uDom('#diff > .left > ul > li').remove();
+ uDom('#diff > .left > ul').html(permanentList.join(''));
+ uDom('#diff > .right > ul > li').remove();
+ uDom('#diff > .right > ul').html(temporaryList.join(''));
+ uDom('#diff').toggleClass('dirty', response.temporaryRules !== response.permanentRules);
+};
/******************************************************************************/
function handleImportFilePicker() {
var fileReaderOnLoadHandler = function() {
- var textarea = uDom('#userRules');
- textarea.val([textarea.val(), this.result].join('\n').trim());
- userRulesChanged();
+ if ( typeof this.result !== 'string' || this.result === '' ) {
+ return;
+ }
+ var request = {
+ 'what': 'setUserRules',
+ 'temporaryRules': rulesFromHTML('#diff .right li') + '\n' + this.result
+ };
+ messaging.ask(request, processUserRules);
};
var file = this.files[0];
if ( file === undefined || file.name === '' ) {
@@ -89,38 +118,104 @@ var startImportFilePicker = function() {
function exportUserRulesToFile() {
chrome.downloads.download({
- 'url': 'data:text/plain,' + encodeURIComponent(uDom('#userRules').val()),
- 'filename': chrome.i18n.getMessage('userRulesDefaultFileName'),
+ 'url': 'data:text/plain,' + encodeURIComponent(rulesFromHTML('#diff .left li')),
+ 'filename': uDom('[data-i18n="userRulesDefaultFileName"]').text(),
'saveAs': true
});
}
/******************************************************************************/
-function userRulesApplyHandler() {
- var rules = uDom('#userRules').val();
- var rulesWritten = function(response) {
- cachedUserRules = rules;
- userRulesChanged();
+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': 'setUserRules',
+ 'temporaryRules': rulesFromHTML('#diff .left li')
};
+ messaging.ask(request, processUserRules);
+};
+
+/******************************************************************************/
+
+var commitHandler = function() {
var request = {
- what: 'setUserRules',
- rules: rules
+ 'what': 'setUserRules',
+ 'permanentRules': rulesFromHTML('#diff .right li')
};
- messaging.ask(request, rulesWritten);
-}
+ messaging.ask(request, processUserRules);
+};
+
+/******************************************************************************/
+
+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': 'setUserRules',
+ 'temporaryRules': uDom('#diff .right textarea').val()
+ };
+ messaging.ask(request, processUserRules);
+};
+
+/******************************************************************************/
+
+var editCancelHandler = function(ev) {
+ var parent = uDom(this).ancestors('#diff');
+ parent.toggleClass('edit', false);
+};
+
+/******************************************************************************/
+
+var temporaryRulesToggler = function(ev) {
+ var li = uDom(this);
+ li.toggleClass('toRemove');
+ var request = {
+ 'what': 'setUserRules',
+ 'temporaryRules': rulesFromHTML('#diff .right li')
+ };
+ messaging.ask(request, processUserRules);
+};
/******************************************************************************/
uDom.onLoad(function() {
// Handle user interaction
- uDom('#importUserRulesFromFile').on('click', startImportFilePicker);
+ uDom('#importButton').on('click', startImportFilePicker);
uDom('#importFilePicker').on('change', handleImportFilePicker);
- uDom('#exportUserRulesToFile').on('click', exportUserRulesToFile);
- uDom('#userRules').on('input', userRulesChanged);
- uDom('#userRulesApply').on('click', userRulesApplyHandler);
+ uDom('#exportButton').on('click', exportUserRulesToFile);
+
+ 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)
+ uDom('#diff > .right > ul').on('click', 'li', temporaryRulesToggler)
- renderUserRules();
+ messaging.ask({ what: 'getUserRules' }, processUserRules);
});
/******************************************************************************/
diff --git a/src/user-rules.html b/src/user-rules.html
index 51513fc..843d2f3 100644
--- a/src/user-rules.html
+++ b/src/user-rules.html
@@ -10,14 +10,32 @@
-
+
+
+
+
+
Permanent rules
+
+
+
+
+
+
+
Temporary rules
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/_locales/en/messages.json b/tools/_locales/en/messages.json
index 4e98c77..4a1c361 100644
--- a/tools/_locales/en/messages.json
+++ b/tools/_locales/en/messages.json
@@ -20,7 +20,7 @@
"description": "a tab in dashboard"
},
"userRulesPageName": {
- "message": "Your rules",
+ "message": "My rules",
"description": "a tab in dashboard"
},
"ubiquitousRulesPageName" : {
@@ -323,9 +323,25 @@
},
- "userRulesApplyChanges": {
- "message": "Apply changes",
- "description": ""
+ "userRulesRevert": {
+ "message": "Revert",
+ "description": "Will remove all temporary rules"
+ },
+ "userRulesCommit": {
+ "message": "Commit",
+ "description": "Will save all temporary rules"
+ },
+ "userRulesEdit": {
+ "message": "Edit",
+ "description": "Will enable manual-edit mode (textarea)"
+ },
+ "userRulesEditSave": {
+ "message": "Save",
+ "description": "Will save manually-edited content and exit manual-edit mode"
+ },
+ "userRulesEditDicard": {
+ "message": "Discard",
+ "description": "Will discard manually-edited content and exit manual-edit mode"
},
"userRulesImport": {
"message": "Import from file...",