diff --git a/platform/chromium/vapi-common.js b/platform/chromium/vapi-common.js index 799136d5e..d67f41996 100644 --- a/platform/chromium/vapi-common.js +++ b/platform/chromium/vapi-common.js @@ -83,6 +83,15 @@ vAPI.closePopup = function() { /******************************************************************************/ +// A localStorage-like object which should be accessible from the +// background page or auxiliary pages. +// This storage is optional, but it is nice to have, for a more polished user +// experience. + +vAPI.localStorage = window.localStorage; + +/******************************************************************************/ + })(); /******************************************************************************/ diff --git a/platform/firefox/vapi-common.js b/platform/firefox/vapi-common.js index de8f92fe0..30cac6036 100644 --- a/platform/firefox/vapi-common.js +++ b/platform/firefox/vapi-common.js @@ -109,6 +109,20 @@ vAPI.closePopup = function() { /******************************************************************************/ +// A localStorage-like object which should be accessible from the +// background page or auxiliary pages. +// This storage is optional, but it is nice to have, for a more polished user +// experience. + +vAPI.localStorage = { + key: function(){}, + getItem: function(){}, + setItem: function(){}, + removeItem: function(){} +}; + +/******************************************************************************/ + })(); /******************************************************************************/ diff --git a/platform/safari/vapi-common.js b/platform/safari/vapi-common.js index 2be0ce05d..25d09773d 100644 --- a/platform/safari/vapi-common.js +++ b/platform/safari/vapi-common.js @@ -128,4 +128,18 @@ vAPI.closePopup = function() { } }; +/******************************************************************************/ + +// A localStorage-like object which should be accessible from the +// background page or auxiliary pages. +// This storage is optional, but it is nice to have, for a more polished user +// experience. + +vAPI.localStorage = { + key: function(){}, + getItem: function(){}, + setItem: function(){}, + removeItem: function(){} +}; + })(); diff --git a/src/css/3p-filters.css b/src/css/3p-filters.css index b0b24badb..9bab31d1f 100644 --- a/src/css/3p-filters.css +++ b/src/css/3p-filters.css @@ -22,12 +22,24 @@ body[dir=rtl] #lists { padding: 0; list-style-type: none; } -#lists > li > span { +#lists > .groupEntry > span { + cursor: pointer; font-size: 15px; } -#lists > li > ul { +#lists > .groupEntry:not(:first-child) > span:before { + color: #aaa; + content: '\2212 '; + } +#lists > .groupEntry.collapsed > span:before { + color: #aaa; + content: '+ '; + } +#lists > .groupEntry > ul { margin: 0.25em 0 0 0; } +#lists > .groupEntry.collapsed > ul { + display: none; + } li.listEntry { font-size: 14px; margin: 0 auto 0 auto; diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js index 276706e05..b5daf8325 100644 --- a/src/js/3p-filters.js +++ b/src/js/3p-filters.js @@ -185,7 +185,7 @@ var renderFilterLists = function() { hasCachedContent = false; // Visually split the filter lists in purpose-based groups - var ulLists = uDom('#lists').empty(); + var ulLists = uDom('#lists').empty(), liGroup; var groups = groupsFromLists(details.available); var groupKey, i; var groupKeys = [ @@ -200,7 +200,12 @@ var renderFilterLists = function() { ]; for ( i = 0; i < groupKeys.length; i++ ) { groupKey = groupKeys[i]; - ulLists.append(liFromListGroup(groupKey, groups[groupKey])); + liGroup = liFromListGroup(groupKey, groups[groupKey]); + liGroup.toggleClass( + 'collapsed', + vAPI.localStorage.getItem('collapseGroup' + (i + 1)) === 'y' + ); + ulLists.append(liGroup); delete groups[groupKey]; } // For all groups not covered above (if any left) @@ -485,6 +490,19 @@ var externalListsApplyHandler = function() { /******************************************************************************/ +var groupEntryClickHandler = function() { + var li = uDom(this).ancestors('.groupEntry'); + li.toggleClass('collapsed'); + var key = 'collapseGroup' + li.nthOfType(); + if ( li.hasClass('collapsed') ) { + vAPI.localStorage.setItem(key, 'y'); + } else { + vAPI.localStorage.removeItem(key); + } +}; + +/******************************************************************************/ + uDom.onLoad(function() { uDom('#autoUpdate').on('change', autoUpdateCheckboxChanged); uDom('#parseCosmeticFilters').on('change', cosmeticSwitchChanged); @@ -496,6 +514,7 @@ uDom.onLoad(function() { uDom('#lists').on('click', 'span.purge', onPurgeClicked); uDom('#externalLists').on('input', externalListsChangeHandler); uDom('#externalListsApply').on('click', externalListsApplyHandler); + uDom('#lists').on('click', '.groupEntry > span', groupEntryClickHandler); renderFilterLists(); renderExternalLists(); diff --git a/src/js/udom.js b/src/js/udom.js index 32e857d43..8d5b6a0bc 100644 --- a/src/js/udom.js +++ b/src/js/udom.js @@ -19,6 +19,7 @@ Home: https://github.com/gorhill/uBlock */ +/* global DOMTokenList */ /* exported uDom */ /******************************************************************************/ @@ -488,6 +489,28 @@ DOMList.prototype.clone = function(notDeep) { /******************************************************************************/ +DOMList.prototype.nthOfType = function() { + if ( this.nodes.length === 0 ) { + return 0; + } + var node = this.nodes[0]; + var tagName = node.tagName; + var i = 1; + while ( node.previousElementSibling !== null ) { + node = node.previousElementSibling; + if ( typeof node.tagName !== 'string' ) { + continue; + } + if ( node.tagName !== tagName ) { + continue; + } + i++; + } + return i; +}; + +/******************************************************************************/ + DOMList.prototype.attr = function(attr, value) { var i = this.nodes.length; if ( value === undefined && typeof attr !== 'object' ) {