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...",