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 @@