Support expanding/collpasing of specific domains

This is related to the list of domains/subdomains in
the dynamic filtering pane of the popup panel.

Related issue:
- https://github.com/gorhill/uBlock/issues/284

Clicking on the empty space of a row will toggle
the visibility of the subdomains.

Additionally, the root context will always be visible
regardless of the expand/collspase state, along with
a visual indicator that a specific domain or subdomain
is the actual root context.  (the root context is the
hostname in which local rules are created).
This commit is contained in:
Raymond Hill 2019-12-30 09:54:11 -05:00
parent 2da8948928
commit 56cc2b1256
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
4 changed files with 143 additions and 72 deletions

View File

@ -244,42 +244,41 @@ body[dir="rtl"] #tooltip {
text-align: right; text-align: right;
} }
#firewallContainer > div { #firewallContainer > div {
background-color: #e6e6e6;
border: 0; border: 0;
direction: ltr; direction: ltr;
display: flex;
justify-content: flex-end;
margin: 0; margin: 0;
margin-top: 1px;
padding: 0; padding: 0;
} }
#firewallContainer > div:hover { #firewallContainer > div:first-child {
background-color: #f0f0f0; margin-top: 0;
} }
#firewallContainer > div:first-child ~ div:not([class]) { #firewallContainer > div:first-child ~ div[data-des="*"] {
display: none; display: none;
} }
#firewallContainer.minimized > div.isSubDomain { #firewallContainer:not(.expanded) > div.isSubDomain:not(.expandException):not(.isRootContext),
#firewallContainer.expanded > div.isSubDomain.expandException:not(.isRootContext) {
display: none; display: none;
} }
#firewallContainer > div > span { #firewallContainer > div > span {
background-color: transparent; background-color: #e6e6e6;
border: none; border: none;
border-bottom: 1px solid white;
box-sizing: border-box; box-sizing: border-box;
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
color: #000; color: #000;
display: inline-block; display: inline-flex;
height: 1.9em; flex-shrink: 0;
line-height: 1.9; line-height: 2;
overflow: hidden;
position: relative; position: relative;
vertical-align: top;
} }
#firewallContainer > div:first-of-type > span:first-of-type { #firewallContainer > div:first-of-type > span:first-of-type {
cursor: pointer; cursor: pointer;
} }
#firewallContainer > div > span:first-of-type { #firewallContainer > div > span:first-of-type {
justify-content: flex-end;
padding-right: 2px; padding-right: 2px;
position: relative;
text-overflow: ellipsis;
width: calc(100% - 4em); width: calc(100% - 4em);
} }
#firewallContainer > div > span:first-of-type > sup { #firewallContainer > div > span:first-of-type > sup {
@ -295,45 +294,47 @@ body[dir="rtl"] #tooltip {
#firewallContainer > div.isDomain > span.isIDN:first-of-type > sup::before { #firewallContainer > div.isDomain > span.isIDN:first-of-type > sup::before {
content: '\0416\2002'; content: '\0416\2002';
} }
#firewallContainer > div > span:first-of-type ~ span {
margin-left: 1px;
width: 4em;
}
#firewallContainer > div > span:nth-of-type(2) { #firewallContainer > div > span:nth-of-type(2) {
display: none; display: none;
} }
#firewallContainer > div > span:first-of-type ~ span {
border-left: 1px solid white;
width: 4em;
}
#firewallContainer > div > span:nth-of-type(3), #firewallContainer > div > span:nth-of-type(3),
#firewallContainer > div > span:nth-of-type(4) { #firewallContainer > div > span:nth-of-type(4) {
color: #444; color: #444;
text-align: center;
}
#firewallContainer > div > span:nth-of-type(4) {
display: none; display: none;
font-family: monospace;
text-align: center;
} }
#firewallContainer > div.isDomain > span:first-of-type { #firewallContainer > div.isDomain > span:first-of-type {
font-weight: bold; font-weight: bold;
} }
#firewallContainer > div:first-of-type > span:first-of-type::before { #firewallContainer > div:first-of-type > span:first-of-type::before {
color: #aaa; color: #aaa;
content: '\2012'; content: '+';
padding-right: 0.25em; padding-right: 0.25em;
} }
#firewallContainer.minimized > div:first-of-type > span:first-of-type::before { #firewallContainer.expanded > div:first-of-type > span:first-of-type::before {
content: '+'; content: '\2012';
} }
#firewallContainer.minimized > div.isDomain > span:nth-of-type(3) { #firewallContainer > div[data-des="*"] > span:nth-of-type(3),
display: none; #firewallContainer > div.isSubDomain > span:nth-of-type(3),
} #firewallContainer > div.isSubDomain.isRootContext > span:nth-of-type(3),
#firewallContainer.minimized > div.isDomain > span:nth-of-type(4) { #firewallContainer.expanded > div:not(.expandException) > span:nth-of-type(3),
display: inline-block; #firewallContainer:not(.expanded) > div.expandException > span:nth-of-type(3),
#firewallContainer:not(.expanded) > div.isDomain:not(.expandException) > span:nth-of-type(4),
#firewallContainer.expanded > div.isDomain.expandException > span:nth-of-type(4) {
display: inline-flex;
justify-content: space-between;
} }
#firewallContainer > div > span[data-acount]::before, #firewallContainer > div > span[data-acount]::before,
#firewallContainer > div > span[data-bcount]::after { #firewallContainer > div > span[data-bcount]::after {
font-family: monospace; content: ' ';
position: absolute;
} }
#firewallContainer > div > span[data-acount]::before { #firewallContainer > div > span[data-acount]::before {
left: 0.1em; padding-left: 0.1em;
} }
#firewallContainer > div > span[data-acount="1"]::before { #firewallContainer > div > span[data-acount="1"]::before {
content: '+'; content: '+';
@ -345,7 +346,7 @@ body[dir="rtl"] #tooltip {
content: '+++'; content: '+++';
} }
#firewallContainer > div > span[data-bcount]::after { #firewallContainer > div > span[data-bcount]::after {
right: 0.1em; padding-right: 0.1em;
} }
#firewallContainer > div > span[data-bcount="1"]::after { #firewallContainer > div > span[data-bcount="1"]::after {
content: '\2212'; content: '\2212';
@ -361,10 +362,10 @@ body.advancedUser #firewallContainer > div > span:first-of-type {
width: calc(100% - 8em); width: calc(100% - 8em);
} }
body.advancedUser #firewallContainer > div > span:nth-of-type(2) { body.advancedUser #firewallContainer > div > span:nth-of-type(2) {
display: inline-block; display: inline-flex;
} }
body.advancedUser #firewallContainer > div:first-child ~ div:not([class]) { body.advancedUser #firewallContainer > div:first-child ~ div[data-des="*"] {
display: block; display: flex;
} }
body.advancedUser #firewallContainer > div > span:first-of-type ~ span { body.advancedUser #firewallContainer > div > span:first-of-type ~ span {
cursor: pointer; cursor: pointer;
@ -373,10 +374,13 @@ body.advancedUser #firewallContainer > div > span:first-of-type ~ span {
/** /**
Small coloured label at the left of a row Small coloured label at the left of a row
*/ */
#firewallContainer > div.isRootContext > span:first-of-type::before,
#firewallContainer > div.allowed > span:first-of-type::before, #firewallContainer > div.allowed > span:first-of-type::before,
#firewallContainer > div.blocked > span:first-of-type::before, #firewallContainer > div.blocked > span:first-of-type::before,
#firewallContainer.minimized > div.isDomain.totalAllowed > span:first-of-type::before, #firewallContainer:not(.expanded) > div.isDomain.totalAllowed:not(.expandException) > span:first-of-type::before,
#firewallContainer.minimized > div.isDomain.totalBlocked > span:first-of-type::before { #firewallContainer:not(.expanded) > div.isDomain.totalBlocked:not(.expandException) > span:first-of-type::before,
#firewallContainer.expanded > div.isDomain.totalAllowed.expandException > span:first-of-type::before,
#firewallContainer.expanded > div.isDomain.totalBlocked.expandException > span:first-of-type::before {
box-sizing: border-box; box-sizing: border-box;
content: ''; content: '';
display: inline-block; display: inline-block;
@ -386,28 +390,32 @@ body.advancedUser #firewallContainer > div > span:first-of-type ~ span {
position: absolute; position: absolute;
width: 7px; width: 7px;
} }
#firewallContainer > div.isRootContext > span:first-of-type::before {
background-color: rgb(127, 127, 127);
width: 14px !important;
}
/** /**
Source for color-blind color scheme from https://github.com/WyohKnott: Source for color-blind color scheme from https://github.com/WyohKnott:
https://github.com/chrisaljoudi/uBlock/issues/467#issuecomment-95177219 https://github.com/chrisaljoudi/uBlock/issues/467#issuecomment-95177219
*/ */
#firewallContainer > div.allowed > span:first-of-type::before, #firewallContainer > div.allowed > span:first-of-type::before,
#firewallContainer.minimized > div.isDomain.totalAllowed > span:first-of-type::before { #firewallContainer > div.isDomain.totalAllowed > span:first-of-type::before {
background-color: rgb(0, 160, 0); background-color: rgb(0, 160, 0);
} }
#firewallContainer.colorBlind > div.allowed > span:first-of-type::before, #firewallContainer.colorBlind > div.allowed > span:first-of-type::before,
#firewallContainer.colorBlind.minimized > div.isDomain.totalAllowed > span:first-of-type::before { #firewallContainer.colorBlind > div.isDomain.totalAllowed > span:first-of-type::before {
background-color: rgb(255, 194, 57); background-color: rgb(255, 194, 57);
} }
#firewallContainer > div.blocked > span:first-of-type::before, #firewallContainer > div.blocked > span:first-of-type::before,
#firewallContainer.minimized > div.isDomain.totalBlocked > span:first-of-type::before { #firewallContainer > div.isDomain.totalBlocked > span:first-of-type::before {
background-color: rgb(192, 0, 0); background-color: rgb(192, 0, 0);
} }
#firewallContainer.colorBlind > div.blocked > span:first-of-type::before, #firewallContainer.colorBlind > div.blocked > span:first-of-type::before,
#firewallContainer.colorBlind.minimized > div.isDomain.totalBlocked > span:first-of-type::before { #firewallContainer.colorBlind > div.isDomain.totalBlocked > span:first-of-type::before {
background-color: rgb(0, 19, 110); background-color: rgb(0, 19, 110);
} }
#firewallContainer > div.allowed.blocked > span:first-of-type::before, #firewallContainer > div.allowed.blocked > span:first-of-type::before,
#firewallContainer.minimized > div.isDomain.totalAllowed.totalBlocked > span:first-of-type::before { #firewallContainer > div.isDomain.totalAllowed.totalBlocked > span:first-of-type::before {
background-color: rgb(192, 160, 0); background-color: rgb(192, 160, 0);
} }
/* Rule cells */ /* Rule cells */

View File

@ -217,7 +217,7 @@ const getHostnameDict = function(hostnameToCountMap) {
blockCount: blockCount, blockCount: blockCount,
allowCount: allowCount, allowCount: allowCount,
totalBlockCount: 0, totalBlockCount: 0,
totalAllowCount: 0 totalAllowCount: 0,
}; };
} }
return r; return r;

View File

@ -244,12 +244,12 @@ const updateFirewallCell = function(scope, des, type, rule) {
if ( hnDetails.allowCount !== 0 ) { if ( hnDetails.allowCount !== 0 ) {
cell.setAttribute('data-acount', Math.min(Math.ceil(Math.log(hnDetails.allowCount + 1) / Math.LN10), 3)); cell.setAttribute('data-acount', Math.min(Math.ceil(Math.log(hnDetails.allowCount + 1) / Math.LN10), 3));
} else { } else {
cell.removeAttribute('data-acount'); cell.setAttribute('data-acount', '0');
} }
if ( hnDetails.blockCount !== 0 ) { if ( hnDetails.blockCount !== 0 ) {
cell.setAttribute('data-bcount', Math.min(Math.ceil(Math.log(hnDetails.blockCount + 1) / Math.LN10), 3)); cell.setAttribute('data-bcount', Math.min(Math.ceil(Math.log(hnDetails.blockCount + 1) / Math.LN10), 3));
} else { } else {
cell.removeAttribute('data-bcount'); cell.setAttribute('data-bcount', '0');
} }
if ( hnDetails.domain !== des ) { if ( hnDetails.domain !== des ) {
@ -260,12 +260,12 @@ const updateFirewallCell = function(scope, des, type, rule) {
if ( hnDetails.totalAllowCount !== 0 ) { if ( hnDetails.totalAllowCount !== 0 ) {
cell.setAttribute('data-acount', Math.min(Math.ceil(Math.log(hnDetails.totalAllowCount + 1) / Math.LN10), 3)); cell.setAttribute('data-acount', Math.min(Math.ceil(Math.log(hnDetails.totalAllowCount + 1) / Math.LN10), 3));
} else { } else {
cell.removeAttribute('data-acount'); cell.setAttribute('data-acount', '0');
} }
if ( hnDetails.totalBlockCount !== 0 ) { if ( hnDetails.totalBlockCount !== 0 ) {
cell.setAttribute('data-bcount', Math.min(Math.ceil(Math.log(hnDetails.totalBlockCount + 1) / Math.LN10), 3)); cell.setAttribute('data-bcount', Math.min(Math.ceil(Math.log(hnDetails.totalBlockCount + 1) / Math.LN10), 3));
} else { } else {
cell.removeAttribute('data-bcount'); cell.setAttribute('data-bcount', '0');
} }
}; };
@ -302,9 +302,9 @@ const buildAllFirewallRows = function() {
dfHotspots.detach(); dfHotspots.detach();
// Update incrementally: reuse existing rows if possible. // Update incrementally: reuse existing rows if possible.
let rowContainer = document.getElementById('firewallContainer'); const rowContainer = document.getElementById('firewallContainer');
let toAppend = document.createDocumentFragment(); const toAppend = document.createDocumentFragment();
let rowTemplate = document.querySelector('#templates > div:nth-of-type(1)'); const rowTemplate = document.querySelector('#templates > div:nth-of-type(1)');
let row = rowContainer.querySelector('div:nth-of-type(7) + div'); let row = rowContainer.querySelector('div:nth-of-type(7) + div');
for ( const des of allHostnameRows ) { for ( const des of allHostnameRows ) {
@ -331,12 +331,14 @@ const buildAllFirewallRows = function() {
span.title = isDomain && isPunycoded ? des : ''; span.title = isDomain && isPunycoded ? des : '';
const classList = row.classList; const classList = row.classList;
classList.toggle('isRootContext', des === popupData.pageHostname);
classList.toggle('isDomain', isDomain); classList.toggle('isDomain', isDomain);
classList.toggle('isSubDomain', !isDomain); classList.toggle('isSubDomain', !isDomain);
classList.toggle('allowed', hnDetails.allowCount !== 0); classList.toggle('allowed', hnDetails.allowCount !== 0);
classList.toggle('blocked', hnDetails.blockCount !== 0); classList.toggle('blocked', hnDetails.blockCount !== 0);
classList.toggle('totalAllowed', hnDetails.totalAllowCount !== 0); classList.toggle('totalAllowed', hnDetails.totalAllowCount !== 0);
classList.toggle('totalBlocked', hnDetails.totalBlockCount !== 0); classList.toggle('totalBlocked', hnDetails.totalBlockCount !== 0);
classList.toggle('expandException', expandExceptions.has(hnDetails.domain));
row = row.nextElementSibling; row = row.nextElementSibling;
} }
@ -445,7 +447,7 @@ const renderPopup = function() {
popupData.pageURL === '' || popupData.netFilteringSwitch !== true popupData.pageURL === '' || popupData.netFilteringSwitch !== true
); );
let canElementPicker = popupData.canElementPicker === true && const canElementPicker = popupData.canElementPicker === true &&
popupData.netFilteringSwitch === true; popupData.netFilteringSwitch === true;
uDom.nodeFromId('gotoPick').classList.toggle('enabled', canElementPicker); uDom.nodeFromId('gotoPick').classList.toggle('enabled', canElementPicker);
uDom.nodeFromId('gotoZap').classList.toggle('enabled', canElementPicker); uDom.nodeFromId('gotoZap').classList.toggle('enabled', canElementPicker);
@ -510,16 +512,13 @@ const renderPopup = function() {
dfPaneVisible === true dfPaneVisible === true
); );
elem = uDom.nodeFromId('firewallContainer'); uDom.nodeFromId('firewallContainer').classList.toggle(
elem.classList.toggle(
'minimized',
popupData.firewallPaneMinimized === true
);
elem.classList.toggle(
'colorBlind', 'colorBlind',
popupData.colorBlindFriendly === true popupData.colorBlindFriendly === true
); );
setGlobalExpand(popupData.firewallPaneMinimized === false, true);
// Build dynamic filtering pane only if in use // Build dynamic filtering pane only if in use
if ( dfPaneVisible ) { if ( dfPaneVisible ) {
buildAllFirewallRows(); buildAllFirewallRows();
@ -923,7 +922,66 @@ document.addEventListener(
/******************************************************************************/ /******************************************************************************/
const toggleMinimize = function(ev) { const expandExceptions = new Set();
(( ) => {
try {
const exceptions = JSON.parse(
vAPI.localStorage.getItem('popupExpandExceptions')
);
if ( Array.isArray(exceptions) === false ) { return; }
for ( const exception of exceptions ) {
expandExceptions.add(exception);
}
}
catch(ex) {
}
})();
const saveExpandExceptions = function() {
vAPI.localStorage.setItem(
'popupExpandExceptions',
JSON.stringify(Array.from(expandExceptions))
);
};
const setGlobalExpand = function(state, internal = false) {
uDom('.expandException').removeClass('expandException');
if ( state ) {
uDom('#firewallContainer').addClass('expanded');
} else {
uDom('#firewallContainer').removeClass('expanded');
}
positionRulesetTools();
if ( internal ) { return; }
popupData.firewallPaneMinimized = !state;
expandExceptions.clear();
saveExpandExceptions();
messaging.send('popupPanel', {
what: 'userSettings',
name: 'firewallPaneMinimized',
value: popupData.firewallPaneMinimized,
});
};
const setSpecificExpand = function(domain, state, internal = false) {
const unodes = uDom(`[data-des="${domain}"],[data-des$=".${domain}"]`);
if ( state ) {
unodes.addClass('expandException');
} else {
unodes.removeClass('expandException');
}
if ( internal ) { return; }
if ( state ) {
expandExceptions.add(domain);
} else {
expandExceptions.delete(domain);
}
saveExpandExceptions();
};
uDom('[data-i18n="popupAnyRulePrompt"]').on('click', ev => {
// Special display mode: in its own tab/window, with no vertical restraint. // Special display mode: in its own tab/window, with no vertical restraint.
// Useful to take snapshots of the whole list of domains -- example: // Useful to take snapshots of the whole list of domains -- example:
// https://github.com/gorhill/uBlock/issues/736#issuecomment-178879944 // https://github.com/gorhill/uBlock/issues/736#issuecomment-178879944
@ -931,25 +989,31 @@ const toggleMinimize = function(ev) {
messaging.send('popupPanel', { messaging.send('popupPanel', {
what: 'gotoURL', what: 'gotoURL',
details: { details: {
url: 'popup.html?tabId=' + popupData.tabId + '&responsive=1', url: `popup.html?tabId=${popupData.tabId}&responsive=1`,
select: true, select: true,
index: -1 index: -1,
}, },
}); });
vAPI.closePopup(); vAPI.closePopup();
return; return;
} }
popupData.firewallPaneMinimized = setGlobalExpand(
uDom.nodeFromId('firewallContainer').classList.toggle('minimized'); uDom('#firewallContainer').hasClass('expanded') === false
);
});
messaging.send('popupPanel', { uDom('#firewallContainer').on(
what: 'userSettings', 'click', '.isDomain[data-type="*"] > span:first-of-type',
name: 'firewallPaneMinimized', ev => {
value: popupData.firewallPaneMinimized, const div = ev.target.closest('[data-des]');
}); if ( div === null ) { return; }
positionRulesetTools(); setSpecificExpand(
}; div.getAttribute('data-des'),
div.classList.contains('expandException') === false
);
}
);
/******************************************************************************/ /******************************************************************************/
@ -1137,7 +1201,6 @@ uDom('h2').on('click', toggleFirewallPane);
uDom('.hnSwitch').on('click', ev => { toggleHostnameSwitch(ev); }); uDom('.hnSwitch').on('click', ev => { toggleHostnameSwitch(ev); });
uDom('#saveRules').on('click', saveFirewallRules); uDom('#saveRules').on('click', saveFirewallRules);
uDom('#revertRules').on('click', ( ) => { revertFirewallRules(); }); uDom('#revertRules').on('click', ( ) => { revertFirewallRules(); });
uDom('[data-i18n="popupAnyRulePrompt"]').on('click', toggleMinimize);
uDom('body').on('mouseenter', '[data-tip]', onShowTooltip) uDom('body').on('mouseenter', '[data-tip]', onShowTooltip)
.on('mouseleave', '[data-tip]', onHideTooltip); .on('mouseleave', '[data-tip]', onHideTooltip);

View File

@ -42,7 +42,7 @@
<span id="no-scripting" class="hnSwitch fa-icon fa-icon-badged" role="button" aria-label tabindex="0">code<svg class="nope" viewBox="0 0 20 20"><path d="M1,1 19,19M1,19 19,1" /></svg></span> <span id="no-scripting" class="hnSwitch fa-icon fa-icon-badged" role="button" aria-label tabindex="0">code<svg class="nope" viewBox="0 0 20 20"><path d="M1,1 19,19M1,19 19,1" /></svg></span>
</div> </div>
</div><!-- DO NOT REMOVE --><div class="tooltipContainer"> </div><!-- DO NOT REMOVE --><div class="tooltipContainer">
<div id="firewallContainer" class="minimized"> <div id="firewallContainer">
<div data-des="*" data-type="*"><span data-i18n="popupAnyRulePrompt"></span><span data-src="/" data-i18n-tip="popupTipGlobalRules" data-tip-position="under"> </span><span data-src="." data-i18n-tip="popupTipLocalRules" data-tip-position="under"> </span></div> <div data-des="*" data-type="*"><span data-i18n="popupAnyRulePrompt"></span><span data-src="/" data-i18n-tip="popupTipGlobalRules" data-tip-position="under"> </span><span data-src="." data-i18n-tip="popupTipLocalRules" data-tip-position="under"> </span></div>
<div data-des="*" data-type="image"><span data-i18n="popupImageRulePrompt"></span><span data-src="/"> </span><span data-src="."> </span></div> <div data-des="*" data-type="image"><span data-i18n="popupImageRulePrompt"></span><span data-src="/"> </span><span data-src="."> </span></div>
<div data-des="*" data-type="3p"><span data-i18n="popup3pAnyRulePrompt"></span><span data-src="/"> </span><span data-src="."> </span></div> <div data-des="*" data-type="3p"><span data-i18n="popup3pAnyRulePrompt"></span><span data-src="/"> </span><span data-src="."> </span></div>