From 55b9f1c64561186a77ab5b45af69b6eaced18a9a Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 3 Feb 2018 17:31:35 -0500 Subject: [PATCH] add contributor mode and tools to contribute ruleset recipes (need more) --- src/_locales/en/messages.json | 8 +++ src/css/hosts-files.css | 17 +++-- src/hosts-files.html | 6 +- src/js/background.js | 11 ++- src/js/hosts-files.js | 129 ++++++++++++++++++++++------------ src/js/messaging.js | 4 +- src/js/storage.js | 50 ++++++++++--- 7 files changed, 163 insertions(+), 62 deletions(-) diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 867587b..b007b4b 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -551,6 +551,14 @@ "message": "Import...", "description": "" }, + "assetsInlineHostsLabel": { + "message": "My hosts", + "description": "" + }, + "assetsInlineRecipesLabel": { + "message": "My recipes", + "description": "" + }, "rawSettingsWarning" : { "message": "Warning! Change these raw configuration settings at your own risk.", diff --git a/src/css/hosts-files.css b/src/css/hosts-files.css index 2218944..1aa982d 100644 --- a/src/css/hosts-files.css +++ b/src/css/hosts-files.css @@ -139,20 +139,29 @@ body.updating li.listEntry.obsolete > input[type="checkbox"]:checked ~ span.upda .dim { opacity: 0.5; } -li.listEntry.importURL > input[type="checkbox"] ~ textarea { +li.listEntry.notAnAsset > input[type="checkbox"] ~ textarea { display: none; margin-left: 1.6em; } -li.listEntry.importURL > input[type="checkbox"]:checked ~ textarea { +li.listEntry.notAnAsset > input[type="checkbox"]:checked ~ textarea { display: block; } -li.listEntry.importURL > textarea { +li.listEntry.notAnAsset > textarea { border: 1px solid #ddd; box-sizing: border-box; display: block; font-size: smaller; width: calc(100% - 4em); height: 6em; - resize: none; + resize: vertical; white-space: pre; } +#recipes li.listEntry.toInline { + display: none; + } +body.contributor #recipes li.listEntry.toInline { + display: block; + } +#recipes li.listEntry.toInline > textarea { + height: 18em; + } diff --git a/src/hosts-files.html b/src/hosts-files.html index f07386d..61c0af7 100644 --- a/src/hosts-files.html +++ b/src/hosts-files.html @@ -24,7 +24,7 @@

@@ -35,8 +35,10 @@

diff --git a/src/js/background.js b/src/js/background.js index 8825702..cc5661b 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -101,6 +101,7 @@ var requestStatsFactory = function() { */ var rawSettingsDefault = { + contributorMode: false, disableCSPReportInjection: false, placeholderBackground: [ @@ -188,7 +189,15 @@ return { processHyperlinkAuditing: true, processReferer: false, selectedHostsFiles: [ '' ], - selectedRecipeFiles: [ '' ] + selectedRecipeFiles: [ '' ], + userHosts: { + enabled: false, + content: '' + }, + userRecipes: { + enabled: false, + content: '' + } }, rawSettingsDefault: rawSettingsDefault, diff --git a/src/js/hosts-files.js b/src/js/hosts-files.js index e0f4d34..960de02 100644 --- a/src/js/hosts-files.js +++ b/src/js/hosts-files.js @@ -145,22 +145,23 @@ var renderHostsFiles = function(soft) { }); let ulList = document.querySelector(listSelector), - liImport = ulList.querySelector('.importURL'); - if ( liImport.parentNode !== null ) { - liImport.parentNode.removeChild(liImport); - } + liLast = ulList.querySelector('.notAnAsset'); + for ( let i = 0; i < assetKeys.length; i++ ) { - let liEntry = liFromListEntry( - collection, - assetKeys[i], - ulList.children[i] - ); + let liReuse = i < ulList.childElementCount ? + ulList.children[i] : + null; + if ( + liReuse !== null && + liReuse.classList.contains('notAnAsset') + ) { + liReuse = null; + } + let liEntry = liFromListEntry(collection, assetKeys[i], liReuse); if ( liEntry.parentElement === null ) { - ulList.appendChild(liEntry); + ulList.insertBefore(liEntry, liLast); } } - - ulList.appendChild(liImport); }; var onAssetDataReceived = function(details) { @@ -171,9 +172,14 @@ var renderHostsFiles = function(soft) { // Before all, set context vars listDetails = details; + document.body.classList.toggle( + 'contributor', + listDetails.contributor === true + ); + // Incremental rendering: this will allow us to easily discard unused // DOM list entries. - uDom('#hosts .listEntry:not(.importURL)').addClass('discard'); + uDom('#hosts .listEntry:not(.notAnAsset)').addClass('discard'); onRenderAssetFiles(details.hosts, '#hosts'); onRenderAssetFiles(details.recipes, '#recipes'); @@ -188,6 +194,12 @@ var renderHostsFiles = function(soft) { ); uDom('#autoUpdate').prop('checked', listDetails.autoUpdate === true); + uDom.nodeFromSelector('#recipes .toInline > input[type="checkbox"]').checked = + listDetails.userRecipes.enabled === true; + uDom.nodeFromSelector('#recipes .toInline > textarea').value = + listDetails.userRecipes.content; + + if ( !soft ) { hostsFilesSettingsHash = hashFromCurrentFromSettings(); } @@ -231,31 +243,50 @@ var updateAssetStatus = function(details) { /******************************************************************************* - Compute a hash from all the settings affecting how filter lists are loaded + Compute a hash from all the settings affecting how assets are loaded in memory. **/ var hashFromCurrentFromSettings = function() { - var hash = [], - listHash = [], - listEntries = document.querySelectorAll('.assets .listEntry[data-listkey]:not(.toRemove)'), - i = listEntries.length; - while ( i-- ) { - let liEntry = listEntries[i]; + let listHash = [], + listEntries = document.querySelectorAll( + '.assets .listEntry[data-listkey]:not(.toRemove)' + ); + for ( let liEntry of listEntries ) { if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) { listHash.push(liEntry.getAttribute('data-listkey')); } } - let textarea1 = document.querySelector('.assets .importURL > input[type="checkbox"]:checked ~ textarea'); - let textarea2 = document.querySelector('#recipes .importURL > input[type="checkbox"]:checked ~ textarea'); - hash.push( - listHash.sort().join(), - textarea1 !== null && reValidExternalList.test(textarea1.value), - textarea2 !== null && reValidExternalList.test(textarea2.value), - document.querySelector('.listEntry.toRemove') !== null - ); - return hash.join(); + return [ + listHash.join(), + document.querySelector('.listEntry.toRemove') !== null, + reValidExternalList.test( + textFromTextarea( + '#hosts .toImport > input[type="checkbox"]:checked ~ textarea' + ) + ), + textFromTextarea( + '#hosts .toInline > input[type="checkbox"]:checked ~ textarea' + ), + reValidExternalList.test( + textFromTextarea( + '#recipes .toImport > input[type="checkbox"]:checked ~ textarea' + ) + ), + textFromTextarea( + '#recipes .toInline > input[type="checkbox"]:checked ~ textarea' + ), + ].join('\n'); +}; + +/******************************************************************************/ + +var textFromTextarea = function(textarea) { + if ( typeof textarea === 'string' ) { + textarea = document.querySelector(textarea); + } + return textarea !== null ? textarea.value.trim() : ''; }; /******************************************************************************/ @@ -300,36 +331,44 @@ var selectAssets = function(callback) { var out = { toSelect: [], toImport: '', - toRemove: [] + toRemove: [], + toInline: { + enabled: false, + content: '' + } }; let root = document.querySelector(listSelector); - let liEntries = root.querySelectorAll('.listEntry[data-listkey]:not(.toRemove)'), - i = liEntries.length; - while ( i-- ) { - let liEntry = liEntries[i]; - if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) { + // Lists to select or remove + let liEntries = root.querySelectorAll( + '.listEntry[data-listkey]:not(.notAnAsset)' + ); + for ( let liEntry of liEntries ) { + if ( liEntry.classList.contains('toRemove') ) { + out.toRemove.push(liEntry.getAttribute('data-listkey')); + } else if ( liEntry.querySelector('input[type="checkbox"]:checked') ) { out.toSelect.push(liEntry.getAttribute('data-listkey')); } } - // External hosts files to remove - liEntries = root.querySelectorAll('.listEntry.toRemove[data-listkey]'); - i = liEntries.length; - while ( i-- ) { - out.toRemove.push(liEntries[i].getAttribute('data-listkey')); - } - // External hosts files to import - let input = root.querySelector('.importURL > input[type="checkbox"]:checked'); + let input = root.querySelector( + '.toImport > input[type="checkbox"]:checked' + ); if ( input !== null ) { - let textarea = root.querySelector('.importURL textarea'); + let textarea = root.querySelector('.toImport textarea'); out.toImport = textarea.value.trim(); textarea.value = ''; input.checked = false; } + // Inline data + out.toInline.enabled = root.querySelector( + '.toInline > input[type="checkbox"]:checked' + ) !== null; + out.toInline.content = textFromTextarea('.toInline > textarea'); + return out; }; @@ -406,7 +445,7 @@ uDom('#buttonApply').on('click', buttonApplyHandler); uDom('#buttonUpdate').on('click', buttonUpdateHandler); uDom('#buttonPurgeAll').on('click', buttonPurgeAllHandler); uDom('.assets').on('change', '.listEntry > input', onHostsFilesSettingsChanged); -uDom('.assets').on('input', '.listEntry.importURL textarea', onHostsFilesSettingsChanged); +uDom('.assets').on('input', '.listEntry > textarea', onHostsFilesSettingsChanged); uDom('.assets').on('click', '.listEntry > a.remove', onRemoveExternalAsset); uDom('.assets').on('click', 'span.cache', onPurgeClicked); diff --git a/src/js/messaging.js b/src/js/messaging.js index 70a9793..6397ce3 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -736,7 +736,9 @@ var getAssets = function(callback) { blockedHostnameCount: µm.ubiquitousBlacklist.count, hosts: null, recipes: null, - cache: null + userRecipes: µm.userSettings.userRecipes, + cache: null, + contributor: µm.rawSettings.contributorMode }; var onMetadataReady = function(entries) { r.cache = entries; diff --git a/src/js/storage.js b/src/js/storage.js index 22ac5e1..3787589 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -292,6 +292,14 @@ for ( let assetKey of µm.userSettings.selectedRecipeFiles ) { this.assets.get(assetKey, onLoaded); } + + let userRecipes = µm.userSettings.userRecipes; + if ( userRecipes.enabled ) { + µm.recipeManager.fromString( + '! uMatrix: Ruleset recipes 1.0\n' + userRecipes.content + ); + } + }; /******************************************************************************/ @@ -572,7 +580,8 @@ metadata, details, propSelectedAssetKeys, - propImportedAssetKeys + propImportedAssetKeys, + propInlineAsset ) { let µmus = µm.userSettings; let selectedAssetKeys = new Set(); @@ -629,14 +638,35 @@ } let bin = {}, - changed = false; - + needReload = false; + + if ( details.toInline instanceof Object ) { + let newInline = details.toInline; + let oldInline = µmus[propInlineAsset]; + newInline.content = newInline.content.trim(); + if ( newInline.content.length !== 0 ) { + newInline.content += '\n'; + } + let newContent = newInline.enabled ? newInline.content : ''; + let oldContent = oldInline.enabled ? oldInline.content : ''; + if ( newContent !== oldContent ) { + needReload = true; + } + if ( + newInline.enabled !== oldInline.enabled || + newInline.content !== oldInline.content + ) { + µmus[propInlineAsset] = newInline; + bin[propInlineAsset] = newInline; + } + } + selectedAssetKeys = Array.from(selectedAssetKeys).sort(); µmus[propSelectedAssetKeys].sort(); if ( selectedAssetKeys.join() !== µmus[propSelectedAssetKeys].join() ) { µmus[propSelectedAssetKeys] = selectedAssetKeys; bin[propSelectedAssetKeys] = selectedAssetKeys; - changed = true; + needReload = true; } importedAssetKeys = Array.from(importedAssetKeys).sort(); @@ -644,14 +674,14 @@ if ( importedAssetKeys.join() !== µmus[propImportedAssetKeys].join() ) { µmus[propImportedAssetKeys] = importedAssetKeys; bin[propImportedAssetKeys] = importedAssetKeys; - changed = true; + needReload = true; } - if ( changed ) { + if ( Object.keys(bin).length !== 0 ) { vAPI.storage.set(bin); } - return changed; + return needReload; }; var onMetadataReady = function(response) { @@ -660,7 +690,8 @@ metadata, details.hosts, 'selectedHostsFiles', - 'externalHostsFiles' + 'externalHostsFiles', + 'userHosts' ); if ( hostsChanged ) { µm.hostsFilesSelfie.destroy(); @@ -669,7 +700,8 @@ metadata, details.recipes, 'selectedRecipeFiles', - 'externalRecipeFiles' + 'externalRecipeFiles', + 'userRecipes' ); if ( recipesChanged ) { µm.recipeManager.reset();