From 260f762c839139356a6ac0575af4fc8dce70a23d Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 10 Sep 2020 10:32:53 -0400 Subject: [PATCH] Add widget to control selector depth to element picker Further iterating on the work done in following commit: - https://github.com/gorhill/uBlock/commit/1268f0ae436b039b9f7f6bc5e61568c4f841179e This commit adds a new widget to the element picker to control the depth of a cosmetic filter selector. The new widget is essentially just another way of selecting the depth, which is still controllable through picking one of the cosmetic filters in the list of candidates. --- src/css/epicker-ui.css | 71 ++++-------- src/js/epicker-ui.js | 108 ++++++++++++------- src/js/scriptlets/epicker.js | 8 +- src/web_accessible_resources/epicker-ui.html | 35 ++++-- 4 files changed, 118 insertions(+), 104 deletions(-) diff --git a/src/css/epicker-ui.css b/src/css/epicker-ui.css index 983cccb7b..1010e97d8 100644 --- a/src/css/epicker-ui.css +++ b/src/css/epicker-ui.css @@ -94,7 +94,7 @@ html#ublock0-epicker, margin: 0; overflow: hidden; overflow-y: auto; - padding: 2px; + padding: 2px 2px 1em 2px; resize: none; width: 100%; word-break: break-all; @@ -112,63 +112,35 @@ html#ublock0-epicker, align-items: flex-end; display: inline-flex; flex-grow: 1; - justify-content: center; + justify-content: space-evenly; } -#resultsetSpecificity { - display: inline-flex; - pointer-events: auto; - position: relative; - } -#resultsetSpecificity.hide { +#resultsetModifiers.hide > * { display: none; } -#resultsetSpecificity [data-specificity] { +.resultsetModifier { + border: 1px solid white; + border-bottom: 0; + display: inline-flex; + height: 100%; + pointer-events: auto; + position: relative; + width: 32%; + } +.resultsetModifier span { background-color: var(--button-surface); border: 0; border-left: 1px solid white; display: inline-block; - height: 1.2em; - width: 1.5em; + height: 100%; + width: 12.5%; } -#resultsetSpecificity[data-specificity="0"] [data-specificity="0"], -#resultsetSpecificity[data-specificity="1"] [data-specificity="0"], -#resultsetSpecificity[data-specificity="1"] [data-specificity="1"], -#resultsetSpecificity[data-specificity="2"] [data-specificity="0"], -#resultsetSpecificity[data-specificity="2"] [data-specificity="1"], -#resultsetSpecificity[data-specificity="2"] [data-specificity="2"], -#resultsetSpecificity[data-specificity="3"] [data-specificity="0"], -#resultsetSpecificity[data-specificity="3"] [data-specificity="1"], -#resultsetSpecificity[data-specificity="3"] [data-specificity="2"], -#resultsetSpecificity[data-specificity="3"] [data-specificity="3"], -#resultsetSpecificity[data-specificity="4"] [data-specificity="0"], -#resultsetSpecificity[data-specificity="4"] [data-specificity="1"], -#resultsetSpecificity[data-specificity="4"] [data-specificity="2"], -#resultsetSpecificity[data-specificity="4"] [data-specificity="3"], -#resultsetSpecificity[data-specificity="4"] [data-specificity="4"], -#resultsetSpecificity[data-specificity="5"] [data-specificity="0"], -#resultsetSpecificity[data-specificity="5"] [data-specificity="1"], -#resultsetSpecificity[data-specificity="5"] [data-specificity="2"], -#resultsetSpecificity[data-specificity="5"] [data-specificity="3"], -#resultsetSpecificity[data-specificity="5"] [data-specificity="4"], -#resultsetSpecificity[data-specificity="5"] [data-specificity="5"], -#resultsetSpecificity[data-specificity="6"] [data-specificity="0"], -#resultsetSpecificity[data-specificity="6"] [data-specificity="1"], -#resultsetSpecificity[data-specificity="6"] [data-specificity="2"], -#resultsetSpecificity[data-specificity="6"] [data-specificity="3"], -#resultsetSpecificity[data-specificity="6"] [data-specificity="4"], -#resultsetSpecificity[data-specificity="6"] [data-specificity="5"], -#resultsetSpecificity[data-specificity="6"] [data-specificity="6"], -#resultsetSpecificity[data-specificity="7"] [data-specificity="0"], -#resultsetSpecificity[data-specificity="7"] [data-specificity="1"], -#resultsetSpecificity[data-specificity="7"] [data-specificity="2"], -#resultsetSpecificity[data-specificity="7"] [data-specificity="3"], -#resultsetSpecificity[data-specificity="7"] [data-specificity="4"], -#resultsetSpecificity[data-specificity="7"] [data-specificity="5"], -#resultsetSpecificity[data-specificity="7"] [data-specificity="6"], -#resultsetSpecificity[data-specificity="7"] [data-specificity="7"] { +.resultsetModifier span:first-of-type { + border-left: 0; + } +.resultsetModifier span.active { background-color: var(--button-active-surface); } -#resultsetSpecificity input { +.resultsetModifier input { box-sizing: border-box; height: 100%; left: 0; @@ -183,7 +155,6 @@ html#ublock0-epicker, background-color: #aaa; color: white; min-width: 2.2em; - padding: 2px 0; text-align: center; } #ublock0-epicker section.invalidFilter #resultsetCount { @@ -228,7 +199,7 @@ html#ublock0-epicker, white-space: nowrap; } #ublock0-epicker #candidateFilters .changeFilter li.active { - border: 1px dotted gray; + border: 1px dotted var(--blue-50); } #ublock0-epicker #candidateFilters .changeFilter li:hover { background-color: white; diff --git a/src/js/epicker-ui.js b/src/js/epicker-ui.js index d0924881c..9b329b9ae 100644 --- a/src/js/epicker-ui.js +++ b/src/js/epicker-ui.js @@ -62,6 +62,7 @@ let netFilterCandidates = []; let cosmeticFilterCandidates = []; let computedCandidateSlot = 0; let computedCandidate = ''; +let needBody = false; /******************************************************************************/ @@ -77,6 +78,26 @@ const filterFromTextarea = function() { /******************************************************************************/ +const renderRange = function(id, value, invert = false) { + const cells = $storAll(`#${id} span`); + const input = $stor(`#${id} input`); + const max = parseInt(input.max, 10); + if ( typeof value !== 'number' ) { + value = parseInt(input.value, 10); + } + if ( invert ) { + value = max - value; + } + input.value = value; + for ( let i = 0, n = cells.length; i < n; i++ ) { + cells[i].classList.toggle( + 'active', Math.round(i * max / (n - 1)) <= value + ); + } +}; + +/******************************************************************************/ + const userFilterFromCandidate = function(filter) { if ( filter === '' || filter === '!' ) { return; } @@ -131,24 +152,8 @@ const candidateFromFilterChoice = function(filterChoice) { $stor(`#cosmeticFilters li:nth-of-type(${slot+1})`) .classList.add('active'); - - // Modifier means "target broadly". Hence: - // - Do not compute exact path. - // - Discard narrowing directives. - // - Remove the id if one or more classes exist - // TODO: should remove tag name too? ¯\_(ツ)_/¯ - if ( filterChoice.broad ) { - filter = filter.replace(/:nth-of-type\(\d+\)/, ''); - // https://github.com/uBlockOrigin/uBlock-issues/issues/162 - // Mind escaped periods: they do not denote a class identifier. - if ( filter.charAt(2) === '#' ) { - const pos = filter.search(/[^\\]\./); - if ( pos !== -1 ) { - filter = '##' + filter.slice(pos + 1); - } - } - return filter; - } + renderRange('resultsetDepth', slot, true); + renderRange('resultsetSpecificity'); const specificity = [ 0b0000, // remove hierarchy; remove id, nth-of-type, attribute values @@ -159,12 +164,7 @@ const candidateFromFilterChoice = function(filterChoice) { 0b1100, // remove id, nth-of-type, attribute values 0b1110, // remove id, nth-of-type 0b1111, // keep all = most specific - ][ - parseInt( - $id('resultsetSpecificity').getAttribute('data-specificity'), - 10 - ) - ]; + ][ parseInt($stor('#resultsetSpecificity input').value, 10) ]; // Return path: the target element, then all siblings prepended const paths = []; @@ -222,6 +222,15 @@ const candidateFromFilterChoice = function(filterChoice) { } } + if ( + needBody && + paths.length !== 0 && + paths[0].startsWith('#') === false && + (specificity & 0b1100) !== 0 + ) { + paths.unshift('body > '); + } + computedCandidate = `##${paths.join('')}`; return computedCandidate; @@ -359,7 +368,7 @@ const onCandidateChanged = function() { $id('resultsetCount').textContent = 'E'; $id('create').setAttribute('disabled', ''); } - $id('resultsetSpecificity').classList.toggle( + $id('resultsetModifiers').classList.toggle( 'hide', taCandidate.value === '' || taCandidate.value !== computedCandidate ); @@ -420,16 +429,26 @@ const onQuitClicked = function() { /******************************************************************************/ -const onSpecificityChanged = function(ev) { - const { target } = ev; - $id('resultsetSpecificity').setAttribute('data-specificity', target.value); - if ( taCandidate.value === computedCandidate ) { - taCandidate.value = candidateFromFilterChoice({ - filters: cosmeticFilterCandidates, - slot: computedCandidateSlot, - }); - onCandidateChanged(); - } +const onDepthChanged = function() { + const input = $stor('#resultsetDepth input'); + const max = parseInt(input.max, 10); + const value = parseInt(input.value, 10); + taCandidate.value = candidateFromFilterChoice({ + filters: cosmeticFilterCandidates, + slot: max - value, + }); + onCandidateChanged(); +}; + +/******************************************************************************/ + +const onSpecificityChanged = function() { + if ( taCandidate.value !== computedCandidate ) { return; } + taCandidate.value = candidateFromFilterChoice({ + filters: cosmeticFilterCandidates, + slot: computedCandidateSlot, + }); + onCandidateChanged(); }; /******************************************************************************/ @@ -473,8 +492,8 @@ const onStartMoving = (( ) => { const move = ( ) => { timer = undefined; - let r1 = Math.min(Math.max(r0 - mx1 + mx0, 4), rMax); - let b1 = Math.min(Math.max(b0 - my1 + my0, 4), bMax); + const r1 = Math.min(Math.max(r0 - mx1 + mx0, 4), rMax); + const b1 = Math.min(Math.max(b0 - my1 + my0, 4), bMax); dialog.style.setProperty('right', `${r1}px`, 'important'); dialog.style.setProperty('bottom', `${b1}px`, 'important'); }; @@ -612,6 +631,13 @@ const showDialog = function(details) { const { netFilters, cosmeticFilters, filter } = details; netFilterCandidates = netFilters; + + needBody = + cosmeticFilters.length !== 0 && + cosmeticFilters[cosmeticFilters.length - 1] === '##body'; + if ( needBody ) { + cosmeticFilters.pop(); + } cosmeticFilterCandidates = cosmeticFilters; // https://github.com/gorhill/uBlock/issues/738 @@ -625,6 +651,11 @@ const showDialog = function(details) { populateCandidates(netFilters, '#netFilters'); populateCandidates(cosmeticFilters, '#cosmeticFilters'); + const depthInput = $stor('#resultsetDepth input'); + depthInput.max = cosmeticFilters.length - 1; + depthInput.value = depthInput.max; + onDepthChanged(); + dialog.querySelector('ul').style.display = netFilters.length || cosmeticFilters.length ? '' : 'none'; dialog.querySelector('#create').disabled = true; @@ -682,6 +713,8 @@ const startPicker = function() { if ( pickerRoot.classList.contains('zap') ) { return; } + onSpecificityChanged(); + taCandidate.addEventListener('input', onCandidateChanged); $id('preview').addEventListener('click', onPreviewClicked); $id('create').addEventListener('click', onCreateClicked); @@ -690,6 +723,7 @@ const startPicker = function() { $id('toolbar').addEventListener('mousedown', onStartMoving); $id('toolbar').addEventListener('touchstart', onStartMoving); $id('candidateFilters').addEventListener('click', onCandidateClicked); + $stor('#resultsetDepth input').addEventListener('input', onDepthChanged); $stor('#resultsetSpecificity input').addEventListener('input', onSpecificityChanged); staticFilteringParser = new vAPI.StaticFilteringParser({ interactive: true }); }; diff --git a/src/js/scriptlets/epicker.js b/src/js/scriptlets/epicker.js index 188cfe854..29647d8d1 100644 --- a/src/js/scriptlets/epicker.js +++ b/src/js/scriptlets/epicker.js @@ -529,12 +529,8 @@ const filtersFrom = function(x, y) { // uses `nth-of-type`. let i = cosmeticFilterCandidates.length; if ( i !== 0 ) { - const selector = cosmeticFilterCandidates[i-1]; - if ( - selector.indexOf(':nth-of-type(') !== -1 && - safeQuerySelectorAll(document.body, selector).length > 1 || - safeQuerySelectorAll(document, cosmeticFilterCandidates.join(' > ')).length > 1 - ) { + const selector = cosmeticFilterCandidates[i-1].slice(2); + if ( safeQuerySelectorAll(document.body, selector).length > 1 ) { cosmeticFilterCandidates.push('##body'); } } diff --git a/src/web_accessible_resources/epicker-ui.html b/src/web_accessible_resources/epicker-ui.html index bef9c9123..10b1c4ffe 100644 --- a/src/web_accessible_resources/epicker-ui.html +++ b/src/web_accessible_resources/epicker-ui.html @@ -15,18 +15,31 @@
- - - - - - - - - - + + + + + + + + + + -
+ + + + + + + + + + + + + +