Browse Source

revamped "My rules" tab

pull/2/head
gorhill 10 years ago
parent
commit
5c05a8773a
  1. 113
      src/css/user-rules.css
  2. 2
      src/js/matrix.js
  3. 27
      src/js/messaging-handlers.js
  4. 173
      src/js/user-rules.js
  5. 34
      src/user-rules.html
  6. 24
      tools/_locales/en/messages.json

113
src/css/user-rules.css

@ -4,10 +4,113 @@ div > p:first-child {
div > p:last-child { div > p:last-child {
margin-bottom: 0; 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; white-space: nowrap;
text-align: left;
}
#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;
width: 100%;
}
#diff.edit textarea {
visibility: visible;
}
.hidden {
display: none;
} }

2
src/js/matrix.js

@ -678,7 +678,7 @@ Matrix.prototype.fromString = function(text, append) {
// type = `*` // type = `*`
// state = `allow` // state = `allow`
// Lines with invalid syntax silently ignored
// Lines with invalid syntax silently ignored
srcHostname = punycode.toASCII(fields[0]); srcHostname = punycode.toASCII(fields[0]);
desHostname = punycode.toASCII(fields[1]); desHostname = punycode.toASCII(fields[1]);

27
src/js/messaging-handlers.js

@ -374,14 +374,6 @@ var onMessage = function(request, sender, callback) {
break; break;
case 'retrieveDomainCosmeticSelectors':
response = µMatrix.abpHideFilters.retrieveDomainSelectors(request);
break;
case 'retrieveGenericCosmeticSelectors':
response = µMatrix.abpHideFilters.retrieveGenericSelectors(request);
break;
default: default:
return µMatrix.messaging.defaultHandler(request, sender, callback); return µMatrix.messaging.defaultHandler(request, sender, callback);
} }
@ -449,13 +441,24 @@ var onMessage = function(request, sender, callback) {
switch ( request.what ) { switch ( request.what ) {
case 'getUserRules': case 'getUserRules':
response = µm.pMatrix.toString();
response = {
temporaryRules: µm.tMatrix.toString(),
permanentRules: µm.pMatrix.toString()
}
break; break;
case 'setUserRules': 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; break;
default: default:

173
src/js/user-rules.js

@ -27,40 +27,69 @@
/******************************************************************************/ /******************************************************************************/
var cachedUserRules = '';
/******************************************************************************/
messaging.start('user-rules.js'); 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('<li>', rule);
temporaryList.push('<li>', rule);
} else if ( onLeft ) {
permanentList.push('<li>', rule);
temporaryList.push('<li class="notRight toRemove">', rule);
} else {
permanentList.push('<li>&nbsp;');
temporaryList.push('<li class="notLeft">', 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() { function handleImportFilePicker() {
var fileReaderOnLoadHandler = function() { 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]; var file = this.files[0];
if ( file === undefined || file.name === '' ) { if ( file === undefined || file.name === '' ) {
@ -89,38 +118,104 @@ var startImportFilePicker = function() {
function exportUserRulesToFile() { function exportUserRulesToFile() {
chrome.downloads.download({ 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 '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 = { 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() { uDom.onLoad(function() {
// Handle user interaction // Handle user interaction
uDom('#importUserRulesFromFile').on('click', startImportFilePicker);
uDom('#importButton').on('click', startImportFilePicker);
uDom('#importFilePicker').on('change', handleImportFilePicker); 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);
}); });
/******************************************************************************/ /******************************************************************************/

34
src/user-rules.html

@ -10,14 +10,32 @@
<body> <body>
<div>
<p data-i18n="userRulesFormatHint">
<p><button id="userRulesApply" disabled="true" data-i18n="userRulesApplyChanges"></button>
<p><textarea id="userRules" dir="auto" spellcheck="false"></textarea>
<p><button id="importUserRulesFromFile" data-i18n="userRulesImport"></button> &emsp;
<button id="exportUserRulesToFile" data-i18n="userRulesExport"></button>
<input id="importFilePicker" type="file" accept="text/plain" style="display:none;">
</div>
<!-- <p data-i18n="userRulesFormatHint"></p> -->
<div id="diff">
<div class="pane left">
<div>
<h2>Permanent rules</h2>
<button type="button" id="revertButton"><span data-i18n="userRulesRevert"></span>&thinsp;<span class="fa">&#xf061;</span></button>
</div>
<ul></ul>
</div>
<div class="pane right">
<div>
<h2>Temporary rules</h2>
<button type="button" id="commitButton"><span class="fa">&#xf060;</span>&thinsp;<span data-i18n="userRulesCommit"></span></button>
<button type="button" id="editEnterButton" data-i18n="userRulesEdit"></button>
<button type="button" id="editStopButton" data-i18n="userRulesEditSave"></button>
<button type="button" id="editCancelButton" data-i18n="userRulesEditDicard"></button>
<button type="button" id="importButton" data-i18n="userRulesImport"></button>
<button type="button" id="exportButton" data-i18n="userRulesExport"></button>
</div>
<textarea spellcheck="false"></textarea>
<ul></ul>
</div>
</div>
<input class="hidden" id="importFilePicker" type="file" accept="text/plain">
<span class="hidden" data-i18n="userRulesDefaultFileName"></span>
<script src="js/udom.js"></script> <script src="js/udom.js"></script>
<script src="js/i18n.js"></script> <script src="js/i18n.js"></script>

24
tools/_locales/en/messages.json

@ -20,7 +20,7 @@
"description": "a tab in dashboard" "description": "a tab in dashboard"
}, },
"userRulesPageName": { "userRulesPageName": {
"message": "Your rules",
"message": "My rules",
"description": "a tab in dashboard" "description": "a tab in dashboard"
}, },
"ubiquitousRulesPageName" : { "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": { "userRulesImport": {
"message": "Import from file...", "message": "Import from file...",

Loading…
Cancel
Save