diff --git a/assets/assets.dev.json b/assets/assets.dev.json
index e71d3bbb44b5e..d1fba3176b3f4 100644
--- a/assets/assets.dev.json
+++ b/assets/assets.dev.json
@@ -40,6 +40,7 @@
"group": "default",
"parent": "uBlock filters",
"title": "uBlock filters – Ads",
+ "tags": "ads",
"contentURL": [
"https://ublockorigin.github.io/uAssets/filters/filters.txt",
"assets/ublock/filters.min.txt"
@@ -57,6 +58,7 @@
"group": "default",
"parent": "uBlock filters",
"title": "uBlock filters – Badware risks",
+ "tags": "security",
"contentURL": [
"https://ublockorigin.github.io/uAssets/filters/badware.txt",
"assets/ublock/badware.txt"
@@ -76,6 +78,7 @@
"group": "default",
"parent": "uBlock filters",
"title": "uBlock filters – Privacy",
+ "tags": "privacy",
"contentURL": [
"https://ublockorigin.github.io/uAssets/filters/privacy.txt",
"assets/ublock/privacy.min.txt"
@@ -128,6 +131,7 @@
"group": "ads",
"off": true,
"title": "AdGuard – Ads",
+ "tags": "ads",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/2_without_easylist.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
"instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters"
@@ -137,6 +141,7 @@
"group": "ads",
"off": true,
"title": "AdGuard – Mobile Ads",
+ "tags": "ads mobile",
"ua": "mobile",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/11.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
@@ -146,6 +151,7 @@
"content": "filters",
"group": "ads",
"title": "EasyList",
+ "tags": "ads",
"contentURL": [
"https://ublockorigin.github.io/uAssets/thirdparties/easylist.txt",
"assets/thirdparties/easylist/easylist.txt",
@@ -164,6 +170,7 @@
"group": "privacy",
"off": true,
"title": "AdGuard URL Tracking Protection",
+ "tags": "privacy",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/17.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
"instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters"
@@ -173,6 +180,7 @@
"group": "privacy",
"off": true,
"title": "Block Outsider Intrusion into LAN",
+ "tags": "privacy security",
"contentURL": "https://ublockorigin.github.io/uAssets/filters/lan-block.txt",
"cdnURLs": [
"https://ublockorigin.github.io/uAssetsCDN/filters/lan-block.txt",
@@ -186,6 +194,7 @@
"content": "filters",
"group": "privacy",
"title": "EasyPrivacy",
+ "tags": "privacy",
"contentURL": [
"https://ublockorigin.github.io/uAssets/thirdparties/easyprivacy.txt",
"assets/thirdparties/easylist/easyprivacy.txt",
@@ -203,6 +212,7 @@
"content": "filters",
"group": "malware",
"title": "Online Malicious URL Blocklist",
+ "tags": "security",
"contentURL": [
"https://malware-filter.gitlab.io/malware-filter/urlhaus-filter-online.txt",
"assets/thirdparties/urlhaus-filter/urlhaus-filter-online.txt"
@@ -219,6 +229,7 @@
"group": "malware",
"off": true,
"title": "Phishing URL Blocklist",
+ "tags": "security",
"contentURL": "https://malware-filter.gitlab.io/malware-filter/phishing-filter.txt",
"cdnURLs": [
"https://curbengh.github.io/phishing-filter/phishing-filter.txt",
@@ -232,6 +243,7 @@
"group": "malware",
"off": true,
"title": "PUP Domains Blocklist",
+ "tags": "security",
"contentURL": "https://malware-filter.gitlab.io/malware-filter/pup-filter.txt",
"cdnURLs": [
"https://curbengh.github.io/pup-filter/pup-filter.txt",
@@ -246,6 +258,7 @@
"parent": "AdGuard – Annoyances",
"off": true,
"title": "AdGuard – Social Media",
+ "tags": "annoyances social",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/4.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
"instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters"
@@ -256,6 +269,7 @@
"parent": "AdGuard – Annoyances",
"off": true,
"title": "AdGuard – Cookie Notices",
+ "tags": "annoyances cookies",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/18.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
"instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters"
@@ -266,6 +280,7 @@
"parent": "AdGuard – Annoyances",
"off": true,
"title": "AdGuard – Popup Overlays",
+ "tags": "annoyances",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/19.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
"instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters"
@@ -276,6 +291,7 @@
"parent": "AdGuard – Annoyances",
"off": true,
"title": "AdGuard – Mobile App Banners",
+ "tags": "annoyances mobile",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/20.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
"instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters"
@@ -286,6 +302,7 @@
"parent": "AdGuard – Annoyances",
"off": true,
"title": "AdGuard – Other Annoyances",
+ "tags": "annoyances",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/21.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
"instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters"
@@ -296,6 +313,7 @@
"parent": "AdGuard – Annoyances",
"off": true,
"title": "AdGuard – Widgets",
+ "tags": "annoyances",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/22.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
"instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters"
@@ -305,6 +323,7 @@
"group": "annoyances",
"off": true,
"title": "Fanboy – Anti-Facebook",
+ "tags": "privacy",
"contentURL": "https://secure.fanboy.co.nz/fanboy-antifacebook.txt",
"supportURL": "https://github.com/ryanbr/fanboy-adblock/issues"
},
@@ -314,6 +333,7 @@
"parent": "EasyList – Annoyances",
"off": true,
"title": "EasyList – Other Annoyances",
+ "tags": "annoyances",
"contentURL": "https://ublockorigin.github.io/uAssets/thirdparties/easylist-annoyances.txt",
"cdnURLs": [
"https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-annoyances.txt",
@@ -329,6 +349,7 @@
"parent": "EasyList – Annoyances",
"off": true,
"title": "EasyList – Cookie Notices",
+ "tags": "annoyances cookies",
"contentURL": [
"https://ublockorigin.github.io/uAssets/thirdparties/easylist-cookies.txt",
"https://secure.fanboy.co.nz/fanboy-cookiemonster_ubo.txt"
@@ -348,6 +369,7 @@
"parent": "EasyList – Annoyances",
"off": true,
"title": "EasyList – Newsletter Notices",
+ "tags": "annoyances",
"contentURL": [
"https://ublockorigin.github.io/uAssets/thirdparties/easylist-newsletters.txt"
],
@@ -365,6 +387,7 @@
"parent": "EasyList – Annoyances",
"off": true,
"title": "EasyList – Notifications",
+ "tags": "annoyances",
"contentURL": [
"https://ublockorigin.github.io/uAssets/thirdparties/easylist-notifications.txt"
],
@@ -382,6 +405,7 @@
"parent": "EasyList – Annoyances",
"off": true,
"title": "EasyList – Social Widgets",
+ "tags": "annoyances social",
"contentURL": [
"https://ublockorigin.github.io/uAssets/thirdparties/easylist-social.txt",
"https://secure.fanboy.co.nz/fanboy-social_ubo.txt"
@@ -398,8 +422,9 @@
"ublock-annoyances": {
"content": "filters",
"group": "annoyances",
- "title": "uBlock filters – Annoyances",
"off": true,
+ "title": "uBlock filters – Annoyances",
+ "tags": "annoyances",
"contentURL": "https://ublockorigin.github.io/uAssets/filters/annoyances.txt",
"cdnURLs": [
"https://ublockorigin.github.io/uAssetsCDN/filters/annoyances.txt",
@@ -415,6 +440,7 @@
"updateAfter": 11,
"off": true,
"title": "Dan Pollock’s hosts file",
+ "tags": "ads privacy security",
"contentURL": "https://someonewhocares.org/hosts/hosts",
"supportURL": "https://someonewhocares.org/hosts/"
},
@@ -423,6 +449,7 @@
"group": "multipurpose",
"updateAfter": 13,
"title": "Peter Lowe’s Ad and tracking server list",
+ "tags": "ads privacy security",
"contentURL": [
"https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=1&mimetype=plaintext",
"assets/thirdparties/pgl.yoyo.org/as/serverlist.txt",
@@ -435,6 +462,7 @@
"group": "regions",
"off": true,
"title": "🇦🇱 ALB: Adblock List for Albania",
+ "tags": "ads albania",
"lang": "sq",
"contentURL": "https://raw.githubusercontent.com/AnXh3L0/blocklist/master/albanian-easylist-addition/Albania.txt",
"supportURL": "https://github.com/AnXh3L0/blocklist"
@@ -444,6 +472,7 @@
"group": "regions",
"off": true,
"title": "ara: Liste AR",
+ "tags": "ads arabic",
"lang": "ar",
"contentURL": "https://easylist-downloads.adblockplus.org/Liste_AR.txt",
"supportURL": "https://forums.lanik.us/viewforum.php?f=98"
@@ -453,6 +482,7 @@
"group": "regions",
"off": true,
"title": "🇧🇬 BGR: Bulgarian Adblock list",
+ "tags": "ads bulgarian macedonian",
"lang": "bg mk",
"contentURL": "https://stanev.org/abp/adblock_bg.txt",
"supportURL": "https://stanev.org/abp/"
@@ -462,6 +492,7 @@
"group": "regions",
"off": true,
"title": "🇨🇳🇹🇼 CHN: AdGuard Chinese (中文)",
+ "tags": "ads chinese 中文",
"lang": "ug zh",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/224.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters"
@@ -471,6 +502,7 @@
"group": "regions",
"off": true,
"title": "🇨🇿 CZE, 🇸🇰 SVK: EasyList Czech and Slovak",
+ "tags": "ads czech slovak",
"lang": "cs sk",
"contentURL": "https://raw.githubusercontent.com/tomasko126/easylistczechandslovak/master/filters.txt",
"supportURL": "https://github.com/tomasko126/easylistczechandslovak"
@@ -480,6 +512,7 @@
"group": "regions",
"off": true,
"title": "🇩🇪 DEU: EasyList Germany",
+ "tags": "ads german luxembourgish romansh",
"lang": "de dsb hsb lb rm",
"contentURL": [
"https://easylist.to/easylistgermany/easylistgermany.txt",
@@ -492,6 +525,7 @@
"group": "regions",
"off": true,
"title": "🇪🇪 EST: Eesti saitidele kohandatud filter",
+ "tags": "ads estonian",
"lang": "et",
"contentURL": "https://adblock.ee/list.php",
"supportURL": "https://adblock.ee/"
@@ -501,6 +535,7 @@
"group": "regions",
"off": true,
"title": "🇫🇮 FIN: Adblock List for Finland",
+ "tags": "ads finnish",
"lang": "fi",
"contentURL": "https://raw.githubusercontent.com/finnish-easylist-addition/finnish-easylist-addition/gh-pages/Finland_adb.txt",
"supportURL": "https://github.com/finnish-easylist-addition/finnish-easylist-addition"
@@ -510,6 +545,7 @@
"group": "regions",
"off": true,
"title": "🇫🇷 FRA: AdGuard Français",
+ "tags": "ads french",
"lang": "ar br ff fr lb oc son",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/16.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters"
@@ -519,6 +555,7 @@
"group": "regions",
"off": true,
"title": "🇬🇷 GRC: Greek AdBlock Filter",
+ "tags": "ads greek",
"lang": "el",
"contentURL": "https://www.void.gr/kargig/void-gr-filters.txt",
"supportURL": "https://github.com/kargig/greek-adblockplus-filter"
@@ -528,6 +565,7 @@
"group": "regions",
"off": true,
"title": "🇭🇷 HRV, 🇷🇸 SRB: Dandelion Sprout's Serbo-Croatian filters",
+ "tags": "ads croatian serbian",
"lang": "hr sr",
"contentURL": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/SerboCroatianList.txt",
"supportURL": "https://github.com/DandelionSprout/adfilt#readme"
@@ -537,6 +575,7 @@
"group": "regions",
"off": true,
"title": "🇭🇺 HUN: hufilter",
+ "tags": "ads hungarian",
"lang": "hu",
"contentURL": "https://raw.githubusercontent.com/hufilter/hufilter/master/hufilter-ublock.txt",
"supportURL": "https://github.com/hufilter/hufilter"
@@ -546,6 +585,7 @@
"group": "regions",
"off": true,
"title": "🇮🇩 IDN, 🇲🇾 MYS: ABPindo",
+ "tags": "ads indonesian malay",
"lang": "id ms",
"contentURL": "https://raw.githubusercontent.com/ABPindo/indonesianadblockrules/master/subscriptions/abpindo.txt",
"supportURL": "https://github.com/ABPindo/indonesianadblockrules"
@@ -555,6 +595,7 @@
"group": "regions",
"off": true,
"title": "🇮🇳 IND, 🇱🇰 LKA, 🇳🇵 NPL: IndianList",
+ "tags": "ads assamese bengali gujarati hindi kannada malayalam marathi nepali punjabi sinhala tamil telugu",
"lang": "as bn gu hi kn ml mr ne pa si ta te",
"contentURL": "https://easylist-downloads.adblockplus.org/indianlist.txt",
"supportURL": "https://github.com/mediumkreation/IndianList"
@@ -564,6 +605,7 @@
"group": "regions",
"off": true,
"title": "🇮🇷 IRN: PersianBlocker",
+ "tags": "ads persian pashto tajik",
"lang": "fa ps tg",
"contentURL": [
"https://raw.githubusercontent.com/MasterKia/PersianBlocker/main/PersianBlocker.txt",
@@ -576,6 +618,7 @@
"group": "regions",
"off": true,
"title": "🇮🇸 ISL: Icelandic ABP List",
+ "tags": "ads icelandic",
"lang": "is",
"contentURL": "https://adblock.gardar.net/is.abp.txt",
"supportURL": "https://adblock.gardar.net/"
@@ -585,6 +628,7 @@
"group": "regions",
"off": true,
"title": "🇮🇱 ISR: EasyList Hebrew",
+ "tags": "ads hebrew",
"lang": "he",
"contentURL": "https://raw.githubusercontent.com/easylist/EasyListHebrew/master/EasyListHebrew.txt",
"supportURL": "https://github.com/easylist/EasyListHebrew"
@@ -594,6 +638,7 @@
"group": "regions",
"off": true,
"title": "🇮🇹 ITA: EasyList Italy",
+ "tags": "ads italian",
"lang": "it lij",
"contentURL": "https://easylist-downloads.adblockplus.org/easylistitaly.txt",
"supportURL": "https://forums.lanik.us/viewforum.php?f=96"
@@ -603,6 +648,7 @@
"group": "regions",
"off": true,
"title": "🇯🇵 JPN: AdGuard Japanese",
+ "tags": "ads japanese 日本語",
"lang": "ja",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/7.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
@@ -612,7 +658,8 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "🇰🇷🇰🇪 KOR: List-KR",
+ "title": "🇰🇷 KOR: List-KR",
+ "tags": "ads korean 한국어",
"lang": "ko",
"contentURL": "https://cdn.jsdelivr.net/gh/List-KR/List-KR@master/filter-uBlockOrigin.txt",
"supportURL": "https://github.com/List-KR/List-KR#readme"
@@ -622,6 +669,7 @@
"group": "regions",
"off": true,
"title": "🇱🇹 LTU: EasyList Lithuania",
+ "tags": "ads lithuanian",
"lang": "lt",
"contentURL": "https://raw.githubusercontent.com/EasyList-Lithuania/easylist_lithuania/master/easylistlithuania.txt",
"cdnURLs": [
@@ -635,6 +683,7 @@
"group": "regions",
"off": true,
"title": "🇱🇻 LVA: Latvian List",
+ "tags": "ads latvian",
"lang": "lv",
"contentURL": "https://raw.githubusercontent.com/Latvian-List/adblock-latvian/master/lists/latvian-list.txt",
"supportURL": "https://github.com/Latvian-List/adblock-latvian"
@@ -644,6 +693,7 @@
"group": "regions",
"off": true,
"title": "🇲🇰 MKD: Macedonian adBlock Filters",
+ "tags": "ads macedonian",
"lang": "mk",
"contentURL": "https://raw.githubusercontent.com/DeepSpaceHarbor/Macedonian-adBlock-Filters/master/Filters",
"supportURL": "https://github.com/DeepSpaceHarbor/Macedonian-adBlock-Filters"
@@ -653,6 +703,7 @@
"group": "regions",
"off": true,
"title": "🇳🇱 NLD: EasyDutch",
+ "tags": "ads afrikaans frisian dutch flemish",
"lang": "af fy nl",
"contentURL": "https://easydutch-ubo.github.io/EasyDutchCDN/EasyDutch.txt",
"cdnURLs": [
@@ -667,6 +718,7 @@
"group": "regions",
"off": true,
"title": "🇳🇴 NOR, 🇩🇰 DNK, 🇮🇸 ISL: Dandelion Sprouts nordiske filtre",
+ "tags": "ads norwegian danish icelandic",
"lang": "nb nn no da is",
"contentURL": [
"https://raw.githubusercontent.com/DandelionSprout/adfilt/master/NorwegianList.txt"
@@ -683,6 +735,7 @@
"parent": "🇵🇱 POL: Oficjalne Polskie Filtry",
"off": true,
"title": "🇵🇱 POL: Oficjalne Polskie Filtry do uBlocka Origin",
+ "tags": "ads polish polski",
"lang": "szl pl",
"contentURL": "https://raw.githubusercontent.com/MajkiIT/polish-ads-filter/master/polish-adblock-filters/adblock.txt",
"supportURL": "https://github.com/MajkiIT/polish-ads-filter/issues",
@@ -694,6 +747,7 @@
"parent": "🇵🇱 POL: Oficjalne Polskie Filtry",
"off": true,
"title": "🇵🇱 POL: Oficjalne polskie filtry przeciwko alertom o Adblocku",
+ "tags": "ads polish polski",
"lang": "szl pl",
"contentURL": "https://raw.githubusercontent.com/olegwukr/polish-privacy-filters/master/anti-adblock.txt",
"supportURL": "https://github.com/olegwukr/polish-privacy-filters/issues"
@@ -703,6 +757,7 @@
"group": "regions",
"off": true,
"title": "🇷🇴 ROU: Romanian Ad (ROad) Block List Light",
+ "tags": "ads romanian română moldavian moldovenească молдовеняскэ",
"lang": "ro",
"contentURL": [
"https://road.adblock.ro/lista.txt",
@@ -715,6 +770,7 @@
"group": "regions",
"off": true,
"title": "🇷🇺🇺🇦🇺🇿🇰🇿 RUS: RU AdList",
+ "tags": "ads belarusian беларуская kazakh tatar russian русский ukrainian українська uzbek",
"lang": "be kk tt ru uk uz",
"contentURL": "https://raw.githubusercontent.com/easylist/ruadlist/master/RuAdList-uBO.txt",
"cdnURLs": [
@@ -729,7 +785,8 @@
"group": "regions",
"off": true,
"title": "🇪🇸 spa: EasyList Spanish",
- "lang": "an ast ca cak es eu gn gl trs quz",
+ "tags": "ads aragonese basque catalan spanish español galician guarani",
+ "lang": "an ast ca cak es eu gl gn trs quz",
"contentURL": "https://easylist-downloads.adblockplus.org/easylistspanish.txt",
"supportURL": "https://forums.lanik.us/viewforum.php?f=103"
},
@@ -738,6 +795,7 @@
"group": "regions",
"off": true,
"title": "🇪🇸 spa, 🇧🇷🇵🇹 por: AdGuard Spanish/Portuguese",
+ "tags": "ads aragonese basque catalan spanish español galician guarani portuguese português",
"lang": "an ast ca cak es eu gl gn trs pt quz",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/9.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
@@ -748,6 +806,7 @@
"group": "regions",
"off": true,
"title": "🇸🇮 SVN: Slovenian List",
+ "tags": "ads slovenian slovenski",
"lang": "sl",
"contentURL": "https://raw.githubusercontent.com/betterwebleon/slovenian-list/master/filters.txt",
"supportURL": "https://github.com/betterwebleon/slovenian-list"
@@ -757,6 +816,7 @@
"group": "regions",
"off": true,
"title": "🇸🇪 SWE: Frellwit's Swedish Filter",
+ "tags": "ads swedish svenska",
"lang": "sv",
"contentURL": "https://raw.githubusercontent.com/lassekongo83/Frellwits-filter-lists/master/Frellwits-Swedish-Filter.txt",
"supportURL": "https://github.com/lassekongo83/Frellwits-filter-lists"
@@ -766,6 +826,7 @@
"group": "regions",
"off": true,
"title": "🇹🇭 THA: EasyList Thailand",
+ "tags": "ads thai ไทย",
"lang": "th",
"contentURL": "https://raw.githubusercontent.com/easylist-thailand/easylist-thailand/master/subscription/easylist-thailand.txt",
"supportURL": "https://github.com/easylist-thailand/easylist-thailand"
@@ -775,6 +836,7 @@
"group": "regions",
"off": true,
"title": "🇹🇷 TUR: AdGuard Turkish",
+ "tags": "ads turkish türkçe",
"lang": "tr",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/13.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
@@ -785,6 +847,7 @@
"group": "regions",
"off": true,
"title": "🇻🇳 VIE: ABPVN List",
+ "tags": "ads vietnamese việt",
"lang": "vi",
"contentURL": "https://raw.githubusercontent.com/abpvn/abpvn/master/filter/abpvn_ublock.txt",
"supportURL": "https://abpvn.com/"
diff --git a/assets/assets.json b/assets/assets.json
index 7b58014d5b0ec..679bcfe922c96 100644
--- a/assets/assets.json
+++ b/assets/assets.json
@@ -38,7 +38,8 @@
"ublock-filters": {
"content": "filters",
"group": "default",
- "title": "uBlock filters",
+ "parent": "uBlock filters",
+ "title": "uBlock filters – Ads",
"contentURL": [
"https://ublockorigin.github.io/uAssets/filters/filters.txt",
"assets/ublock/filters.min.txt"
@@ -54,6 +55,7 @@
"ublock-badware": {
"content": "filters",
"group": "default",
+ "parent": "uBlock filters",
"title": "uBlock filters – Badware risks",
"contentURL": [
"https://ublockorigin.github.io/uAssets/filters/badware.txt",
@@ -72,6 +74,7 @@
"content": "filters",
"updateAfter": 13,
"group": "default",
+ "parent": "uBlock filters",
"title": "uBlock filters – Privacy",
"contentURL": [
"https://ublockorigin.github.io/uAssets/filters/privacy.txt",
@@ -89,6 +92,7 @@
"content": "filters",
"updateAfter": 13,
"group": "default",
+ "parent": "uBlock filters",
"title": "uBlock filters – Resource abuse",
"contentURL": [
"https://ublockorigin.github.io/uAssets/filters/resource-abuse.txt",
@@ -105,6 +109,7 @@
"ublock-unbreak": {
"content": "filters",
"group": "default",
+ "parent": "uBlock filters",
"title": "uBlock filters – Unbreak",
"contentURL": [
"https://ublockorigin.github.io/uAssets/filters/unbreak.txt",
@@ -122,6 +127,7 @@
"content": "filters",
"updateAfter": 1,
"group": "default",
+ "parent": "uBlock filters",
"title": "uBlock filters – Quick fixes",
"contentURL": [
"https://ublockorigin.github.io/uAssets/filters/quick-fixes.txt",
@@ -139,7 +145,7 @@
"content": "filters",
"group": "ads",
"off": true,
- "title": "AdGuard Base",
+ "title": "AdGuard – Ads",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/2_without_easylist.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
"instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters"
@@ -148,7 +154,7 @@
"content": "filters",
"group": "ads",
"off": true,
- "title": "AdGuard Mobile Ads",
+ "title": "AdGuard – Mobile Ads",
"ua": "mobile",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/11.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
@@ -254,43 +260,47 @@
},
"adguard-annoyance": {
"content": "filters",
- "group": "social",
+ "group": "annoyances",
+ "parent": "AdGuard – Annoyances",
"off": true,
- "title": "AdGuard Annoyances",
+ "title": "AdGuard – Annoyances",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/14.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
"instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters"
},
"adguard-social": {
"content": "filters",
- "group": "social",
+ "group": "annoyances",
+ "parent": "AdGuard – Annoyances",
"off": true,
- "title": "AdGuard Social Media",
+ "title": "AdGuard – Social Media",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/4.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
"instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters"
},
"fanboy-thirdparty_social": {
"content": "filters",
- "group": "social",
+ "group": "annoyances",
"off": true,
- "title": "Anti-Facebook",
+ "title": "Fanboy – Anti-Facebook",
"contentURL": "https://secure.fanboy.co.nz/fanboy-antifacebook.txt",
"supportURL": "https://github.com/ryanbr/fanboy-adblock/issues"
},
"fanboy-annoyance": {
"content": "filters",
- "group": "social",
+ "group": "annoyances",
+ "parent": "EasyList – Annoyances",
"off": true,
- "title": "Fanboy’s Annoyance",
+ "title": "Fanboy – Annoyances",
"contentURL": "https://secure.fanboy.co.nz/fanboy-annoyance_ubo.txt",
"supportURL": "https://github.com/easylist/easylist#fanboy-lists"
},
"fanboy-cookiemonster": {
"content": "filters",
- "group": "social",
+ "group": "annoyances",
+ "parent": "EasyList – Annoyances",
"off": true,
- "title": "EasyList Cookie",
+ "title": "EasyList – Cookie Notices",
"contentURL": [
"https://ublockorigin.github.io/uAssets/thirdparties/easylist-cookies.txt",
"https://secure.fanboy.co.nz/fanboy-cookiemonster_ubo.txt"
@@ -305,9 +315,10 @@
},
"fanboy-social": {
"content": "filters",
- "group": "social",
+ "group": "annoyances",
+ "parent": "EasyList – Annoyances",
"off": true,
- "title": "EasyList Social",
+ "title": "EasyList – Social Widgets",
"contentURL": [
"https://ublockorigin.github.io/uAssets/thirdparties/easylist-social.txt",
"https://secure.fanboy.co.nz/fanboy-social.txt"
@@ -322,7 +333,7 @@
},
"ublock-annoyances": {
"content": "filters",
- "group": "social",
+ "group": "annoyances",
"title": "uBlock filters – Annoyances",
"off": true,
"contentURL": "https://ublockorigin.github.io/uAssets/filters/annoyances.txt",
@@ -359,7 +370,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "ALB: Adblock List for Albania",
+ "title": "🇦🇱 ALB: Adblock List for Albania",
"lang": "sq",
"contentURL": "https://raw.githubusercontent.com/AnXh3L0/blocklist/master/albanian-easylist-addition/Albania.txt",
"supportURL": "https://github.com/AnXh3L0/blocklist"
@@ -377,7 +388,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "BGR: Bulgarian Adblock list",
+ "title": "🇧🇬 BGR: Bulgarian Adblock list",
"lang": "bg mk",
"contentURL": "https://stanev.org/abp/adblock_bg.txt",
"supportURL": "https://stanev.org/abp/"
@@ -386,7 +397,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "CHN: AdGuard Chinese (中文)",
+ "title": "🇨🇳 CHN: AdGuard Chinese (中文)",
"lang": "ug zh",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/224.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters"
@@ -395,7 +406,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "CZE, SVK: EasyList Czech and Slovak",
+ "title": "🇨🇿 CZE, 🇸🇰 SVK: EasyList Czech and Slovak",
"lang": "cs sk",
"contentURL": "https://raw.githubusercontent.com/tomasko126/easylistczechandslovak/master/filters.txt",
"supportURL": "https://github.com/tomasko126/easylistczechandslovak"
@@ -404,7 +415,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "DEU: EasyList Germany",
+ "title": "🇩🇪 DEU: EasyList Germany",
"lang": "de dsb hsb lb rm",
"contentURL": [
"https://easylist.to/easylistgermany/easylistgermany.txt",
@@ -416,7 +427,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "EST: Eesti saitidele kohandatud filter",
+ "title": "🇪🇪 EST: Eesti saitidele kohandatud filter",
"lang": "et",
"contentURL": "https://adblock.ee/list.php",
"supportURL": "https://adblock.ee/"
@@ -425,7 +436,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "FIN: Adblock List for Finland",
+ "title": "🇫🇮 FIN: Adblock List for Finland",
"lang": "fi",
"contentURL": "https://raw.githubusercontent.com/finnish-easylist-addition/finnish-easylist-addition/gh-pages/Finland_adb.txt",
"supportURL": "https://github.com/finnish-easylist-addition/finnish-easylist-addition"
@@ -434,7 +445,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "FRA: AdGuard Français",
+ "title": "🇫🇷 FRA: AdGuard Français",
"lang": "ar br ff fr lb oc son",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/16.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters"
@@ -443,7 +454,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "GRC: Greek AdBlock Filter",
+ "title": "🇬🇷 GRC: Greek AdBlock Filter",
"lang": "el",
"contentURL": "https://www.void.gr/kargig/void-gr-filters.txt",
"supportURL": "https://github.com/kargig/greek-adblockplus-filter"
@@ -452,7 +463,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "HRV, SRB: Dandelion Sprout's Serbo-Croatian filters",
+ "title": "🇭🇷 HRV, 🇷🇸 SRB: Dandelion Sprout's Serbo-Croatian filters",
"lang": "hr sr",
"contentURL": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/SerboCroatianList.txt",
"supportURL": "https://github.com/DandelionSprout/adfilt#readme"
@@ -461,7 +472,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "HUN: hufilter",
+ "title": "🇭🇺 HUN: hufilter",
"lang": "hu",
"contentURL": "https://raw.githubusercontent.com/hufilter/hufilter/master/hufilter-ublock.txt",
"supportURL": "https://github.com/hufilter/hufilter"
@@ -470,7 +481,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "IDN, MYS: ABPindo",
+ "title": "🇮🇩 IDN, 🇲🇾 MYS: ABPindo",
"lang": "id ms",
"contentURL": "https://raw.githubusercontent.com/ABPindo/indonesianadblockrules/master/subscriptions/abpindo.txt",
"supportURL": "https://github.com/ABPindo/indonesianadblockrules"
@@ -479,7 +490,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "IND, LKA, NPL: IndianList",
+ "title": "🇮🇳 IND, 🇱🇰 LKA, 🇳🇵 NPL: IndianList",
"lang": "as bn gu hi kn ml mr ne pa si ta te",
"contentURL": "https://easylist-downloads.adblockplus.org/indianlist.txt",
"supportURL": "https://github.com/mediumkreation/IndianList"
@@ -488,7 +499,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "IRN: PersianBlocker",
+ "title": "🇮🇷 IRN: PersianBlocker",
"lang": "fa ps tg",
"contentURL": [
"https://raw.githubusercontent.com/MasterKia/PersianBlocker/main/PersianBlocker.txt",
@@ -500,7 +511,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "ISL: Icelandic ABP List",
+ "title": "🇮🇸 ISL: Icelandic ABP List",
"lang": "is",
"contentURL": "https://adblock.gardar.net/is.abp.txt",
"supportURL": "https://adblock.gardar.net/"
@@ -509,7 +520,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "ISR: EasyList Hebrew",
+ "title": "🇮🇱 ISR: EasyList Hebrew",
"lang": "he",
"contentURL": "https://raw.githubusercontent.com/easylist/EasyListHebrew/master/EasyListHebrew.txt",
"supportURL": "https://github.com/easylist/EasyListHebrew"
@@ -518,7 +529,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "ITA: EasyList Italy",
+ "title": "🇮🇹 ITA: EasyList Italy",
"lang": "it lij",
"contentURL": "https://easylist-downloads.adblockplus.org/easylistitaly.txt",
"supportURL": "https://forums.lanik.us/viewforum.php?f=96"
@@ -527,7 +538,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "JPN: AdGuard Japanese",
+ "title": "🇯🇵 JPN: AdGuard Japanese",
"lang": "ja",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/7.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
@@ -537,7 +548,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "KOR: List-KR",
+ "title": "🇰🇷🇰🇪 KOR: List-KR",
"lang": "ko",
"contentURL": "https://cdn.jsdelivr.net/gh/List-KR/List-KR@master/filter-uBlockOrigin.txt",
"supportURL": "https://github.com/List-KR/List-KR#readme"
@@ -546,7 +557,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "LTU: EasyList Lithuania",
+ "title": "🇱🇹 LTU: EasyList Lithuania",
"lang": "lt",
"contentURL": "https://raw.githubusercontent.com/EasyList-Lithuania/easylist_lithuania/master/easylistlithuania.txt",
"cdnURLs": [
@@ -559,7 +570,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "LVA: Latvian List",
+ "title": "🇱🇻 LVA: Latvian List",
"lang": "lv",
"contentURL": "https://raw.githubusercontent.com/Latvian-List/adblock-latvian/master/lists/latvian-list.txt",
"supportURL": "https://github.com/Latvian-List/adblock-latvian"
@@ -568,7 +579,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "MKD: Macedonian adBlock Filters",
+ "title": "🇲🇰 MKD: Macedonian adBlock Filters",
"lang": "mk",
"contentURL": "https://raw.githubusercontent.com/DeepSpaceHarbor/Macedonian-adBlock-Filters/master/Filters",
"supportURL": "https://github.com/DeepSpaceHarbor/Macedonian-adBlock-Filters"
@@ -577,7 +588,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "NLD: EasyDutch",
+ "title": "🇳🇱 NLD: EasyDutch",
"lang": "af fy nl",
"contentURL": "https://raw.githubusercontent.com/EasyDutch-uBO/EasyDutch/main/EasyDutch.txt",
"cdnURLs": [
@@ -591,7 +602,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "NOR, DNK, ISL: Dandelion Sprouts nordiske filtre",
+ "title": "🇳🇴 NOR, 🇩🇰 DNK, 🇮🇸 ISL: Dandelion Sprouts nordiske filtre",
"lang": "nb nn no da is",
"contentURL": [
"https://raw.githubusercontent.com/DandelionSprout/adfilt/master/NorwegianList.txt"
@@ -605,8 +616,9 @@
"POL-0": {
"content": "filters",
"group": "regions",
+ "parent": "🇵🇱 POL: Oficjalne Polskie Filtry",
"off": true,
- "title": "POL: Oficjalne Polskie Filtry do AdBlocka, uBlocka Origin i AdGuarda",
+ "title": "🇵🇱 POL: Oficjalne Polskie Filtry do uBlocka Origin",
"lang": "szl pl",
"contentURL": "https://raw.githubusercontent.com/MajkiIT/polish-ads-filter/master/polish-adblock-filters/adblock.txt",
"supportURL": "https://github.com/MajkiIT/polish-ads-filter/issues",
@@ -615,8 +627,9 @@
"POL-2": {
"content": "filters",
"group": "regions",
+ "parent": "🇵🇱 POL: Oficjalne Polskie Filtry",
"off": true,
- "title": "POL: Oficjalne polskie filtry przeciwko alertom o Adblocku",
+ "title": "🇵🇱 POL: Oficjalne polskie filtry przeciwko alertom o Adblocku",
"lang": "szl pl",
"contentURL": "https://raw.githubusercontent.com/olegwukr/polish-privacy-filters/master/anti-adblock.txt",
"supportURL": "https://github.com/olegwukr/polish-privacy-filters/issues"
@@ -625,7 +638,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "ROU: Romanian Ad (ROad) Block List Light",
+ "title": "🇷🇴 ROU: Romanian Ad (ROad) Block List Light",
"lang": "ro",
"contentURL": [
"https://road.adblock.ro/lista.txt",
@@ -637,7 +650,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "RUS: RU AdList",
+ "title": "🇷🇺🇺🇦🇺🇿🇰🇿 RUS: RU AdList",
"lang": "be kk tt ru uk uz",
"contentURL": "https://raw.githubusercontent.com/easylist/ruadlist/master/RuAdList-uBO.txt",
"cdnURLs": [
@@ -651,7 +664,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "spa: EasyList Spanish",
+ "title": "🇪🇸 spa: EasyList Spanish",
"lang": "an ast ca cak es eu gn gl trs quz",
"contentURL": "https://easylist-downloads.adblockplus.org/easylistspanish.txt",
"supportURL": "https://forums.lanik.us/viewforum.php?f=103"
@@ -660,7 +673,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "spa, por: AdGuard Spanish/Portuguese",
+ "title": "🇪🇸 spa, 🇧🇷🇵🇹 por: AdGuard Spanish/Portuguese",
"lang": "an ast ca cak es eu gl gn trs pt quz",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/9.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
@@ -670,7 +683,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "SVN: Slovenian List",
+ "title": "🇸🇮 SVN: Slovenian List",
"lang": "sl",
"contentURL": "https://raw.githubusercontent.com/betterwebleon/slovenian-list/master/filters.txt",
"supportURL": "https://github.com/betterwebleon/slovenian-list"
@@ -679,7 +692,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "SWE: Frellwit's Swedish Filter",
+ "title": "🇸🇪 SWE: Frellwit's Swedish Filter",
"lang": "sv",
"contentURL": "https://raw.githubusercontent.com/lassekongo83/Frellwits-filter-lists/master/Frellwits-Swedish-Filter.txt",
"supportURL": "https://github.com/lassekongo83/Frellwits-filter-lists"
@@ -688,7 +701,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "THA: EasyList Thailand",
+ "title": "🇹🇭 THA: EasyList Thailand",
"lang": "th",
"contentURL": "https://raw.githubusercontent.com/easylist-thailand/easylist-thailand/master/subscription/easylist-thailand.txt",
"supportURL": "https://github.com/easylist-thailand/easylist-thailand"
@@ -697,7 +710,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "TUR: AdGuard Turkish",
+ "title": "🇹🇷 TUR: AdGuard Turkish",
"lang": "tr",
"contentURL": "https://filters.adtidy.org/extension/ublock/filters/13.txt",
"supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters",
@@ -707,7 +720,7 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "VIE: ABPVN List",
+ "title": "🇻🇳 VIE: ABPVN List",
"lang": "vi",
"contentURL": "https://raw.githubusercontent.com/abpvn/abpvn/master/filter/abpvn_ublock.txt",
"supportURL": "https://abpvn.com/"
diff --git a/src/3p-filters.html b/src/3p-filters.html
index 29f65092cd318..49bd33ff75bd4 100644
--- a/src/3p-filters.html
+++ b/src/3p-filters.html
@@ -39,34 +39,66 @@
-
+
+
+
+ angle-up
+
+
search
+
+
+
+
+ info-circle
+
+
-
-
-
+
+
-
-
+
-
diff --git a/src/css/3p-filters.css b/src/css/3p-filters.css
index 6da4e08e1f947..5749b3fbba915 100644
--- a/src/css/3p-filters.css
+++ b/src/css/3p-filters.css
@@ -19,137 +19,220 @@ body {
animation: spin 1s linear infinite;
transform-origin: 50%;
}
-#listsOfBlockedHostsPrompt {
- cursor: pointer;
+
+body.updating #actions,
+body.working #actions {
+ cursor: progress;
}
-#lists {
- margin: 0.5em 0 0 0;
+body.updating #actions button,
+body.working #actions button {
+ pointer-events: none;
+ }
+
+.listExpander {
+ font-size: 18px;
padding: 0;
}
-#listsOfBlockedHostsPrompt::before,
-.groupEntry:not([data-groupkey="user"]) .geDetails::before {
- color: var(--ink-3);
- content: '\2212';
- font-family: monospace;
- font-size: large;
- margin-inline-end: 0.25em;
- -webkit-margin-end: 0.25em;
+.listExpander:first-child {
+ justify-content: flex-start;
+ min-width: 20px;
}
-body.hideUnused #listsOfBlockedHostsPrompt::before,
-.groupEntry.hideUnused:not([data-groupkey="user"]) .geDetails::before {
- content: '+';
+.listExpander:not(:first-child) {
+ color: var(--checkbox-checked-ink);
+ fill: var(--checkbox-checked-ink);
}
-.groupEntry {
- margin: 0.5em 0;
+.listExpander svg {
+ transform: rotate(90deg);
+ transform-origin: 50%;
}
-.groupEntry .geDetails {
- cursor: pointer;
+
+#lists .fa-icon:hover {
+ transform: scale(1.25);
}
-.groupEntry .geName {
- pointer-events: none;
+
+#lists .rootstats.expanded .listExpander svg {
+ transform: rotate(180deg);
}
-.groupEntry .geCount {
- color: var(--ink-3);
- font-size: 90%;
- pointer-events: none;
+
+#lists .searchbar {
+ align-items: center;
+ column-gap: var(--default-gap-xxsmall);
+ display: inline-flex;
+ margin-block-start: calc(var(--font-size) * 0.75);
+ margin-inline-start: var(--checkbox-size);
+ position: relative;
+ }
+#lists .searchbar input {
+ padding-inline-start: var(--default-gap-large);
}
-.listEntries {
- margin-inline-start: 0.6em;
- -webkit-margin-start: 0.6em;
+#lists .searchbar .fa-icon {
+ color: var(--ink-4);
+ fill: var(--ink-4);
+ left: 4px;
+ position: absolute;
+ transform: none;
}
-.groupEntry:not([data-groupkey="user"]) .listEntry:not(.isDefault).unused {
+#lists.searchMode > .listEntries .listEntries,
+#lists.searchMode > .listEntries .listEntry.searchMatch {
+ display: flex !important;
+ }
+#lists.searchMode > .listEntries .listEntry {
display: none;
}
-.listEntry > * {
- margin-left: 0;
- margin-right: 0;
- unicode-bidi: embed;
+#lists.searchMode > .listEntries .listExpander {
+ visibility: hidden;
+ }
+
+#listsOfBlockedHostsPrompt {
+ cursor: pointer;
+ }
+
+#lists .listEntries {
+ display: flex;
+ flex-direction: column;
+ margin-inline-start: var(--checkbox-size);
+ }
+#lists > .listEntries {
+ margin-inline-start: 0;
}
-.listEntry .listname {
+#lists .listEntry {
+ align-items: flex-start;
+ flex-direction: column;
+ margin-bottom: 0;
+ margin-inline-start: 0;
white-space: nowrap;
}
-.listEntry.toRemove .checkbox {
- visibility: hidden;
+#lists .listEntry[data-key="user"] {
+ margin-top: 0;
}
-.listEntry.toRemove .listname {
- text-decoration: line-through;
+#lists .listEntry > .detailbar {
+ column-gap: calc(var(--default-gap-xxsmall) + 2px);
+ display: inline-flex;
}
-.listEntry a,
-.listEntry .fa-icon,
-.listEntry .counts {
+#lists .listEntry[data-key="user"] > .detailbar {
+ display: none;
+ }
+#lists .listEntry[data-role="node"].expanded > .detailbar .listExpander svg {
+ transform: rotate(180deg);
+ }
+#lists .listEntry[data-parent="root"]:not(.expanded) > .listEntries > .listEntry:not(.checked):not(.isDefault):not(.stickied) {
+ display: none;
+ }
+#lists .listEntry:not([data-parent="root"]):not(.expanded) > .listEntries > .listEntry {
+ display: none;
+ }
+#lists .nodestats {
+ align-self: flex-end;
color: var(--info0-ink);
fill: var(--info0-ink);
- display: none;
+ cursor: default;
+ font-size: var(--font-size-smaller);
+}
+#lists .iconbar {
+ column-gap: var(--default-gap-xxsmall);
+ color: var(--info0-ink);
+ fill: var(--info0-ink);
+ display: inline-flex;
+ flex-direction: row;
font-size: 120%;
- margin: 0 0.2em 0 0;
}
-.listEntry .fa-icon:hover {
- transform: scale(1.25);
+#lists .iconbar a {
+ color: var(--info0-ink);
+ fill: var(--info0-ink);
}
-.listEntry .content {
+#lists .iconbar .fa-icon {
+ display: none;
+ }
+#lists .iconbar .content {
display: inline-flex;
}
-.listEntry a.towiki {
+#lists .iconbar a.towiki {
display: inline-flex;
}
-.listEntry.support a.support {
+#lists .listEntry > .detailbar .iconbar a.support {
display: inline-flex;
}
-.listEntry .remove,
-.listEntry .unsecure,
-.listEntry .failed {
+#lists .listEntry > .detailbar .iconbar a.support[href="#"] {
+ display: none;
+ }
+#lists .iconbar .remove,
+#lists .iconbar .unsecure,
+#lists .iconbar .failed {
color: var(--info3-ink);
fill: var(--info3-ink);
cursor: pointer;
}
-.listEntry.external .remove {
+#lists .listEntry.external > .detailbar .iconbar .remove {
display: inline-flex;
}
-.listEntry.mustread a.mustread {
+#lists .listEntry > .detailbar .iconbar a.mustread {
color: var(--info1-ink);
fill: var(--info1-ink);
display: inline-flex;
}
-.listEntry .counts {
- font-size: smaller;
+#lists .listEntry > .detailbar .iconbar a.mustread[href="#"] {
+ display: none;
+ }
+#lists .listEntry .leafstats {
+ align-items: flex-end;
+ color: var(--info0-ink);
+ fill: var(--info0-ink);
+ display: none;
+ font-size: var(--font-size-xsmall);
+ margin-inline-start: calc(var(--checkbox-size) + var(--checkbox-margin-end));
}
-.listEntry.checked .counts {
- display: inline-block;
+#lists .listEntry > .detailbar .leafstats {
+ margin-inline-start: 0;
+ }
+#lists .listEntry.checked > .leafstats,
+#lists .listEntry.checked > .detailbar .leafstats {
+ display: inline-flex;
}
-.listEntry .status {
+#lists .iconbar .status {
cursor: default;
display: none;
}
-.listEntry.checked.unsecure .unsecure {
+#lists .listEntry.checked.unsecure > .detailbar .iconbar .unsecure {
display: inline-flex;
}
-.listEntry.failed .failed {
+#lists .listEntry.failed > .detailbar .iconbar .failed {
display: inline-flex;
}
-.listEntry .cache {
+#lists .iconbar .cache {
cursor: pointer;
}
-.listEntry.checked.cached:not(.obsolete) .cache {
+#lists .listEntry.checked.cached:not(.obsolete) > .detailbar .iconbar .cache {
display: inline-flex;
}
-.listEntry .obsolete {
+#lists .iconbar .obsolete {
color: var(--info2-ink);
fill: var(--info2-ink);
}
-body:not(.updating) .listEntry.checked.obsolete .obsolete {
+body:not(.updating) #lists .listEntry.checked.obsolete > .detailbar .iconbar .obsolete {
display: inline-flex;
}
-.listEntry .updating {
+#lists .iconbar .updating {
transform-origin: 50%;
}
-body.updating .listEntry.checked.obsolete .updating {
+body.updating #lists .listEntry.checked.obsolete > .detailbar .iconbar .updating {
animation: spin 1s steps(8) infinite;
display: inline-flex;
}
-.listEntry.toImport {
- margin: 0.5em 0;
+
+#lists .listEntry.toRemove .checkbox {
+ visibility: hidden;
}
-.listEntry.toImport textarea {
+#lists .listEntry.toRemove .listname {
+ text-decoration: line-through;
+ }
+
+#lists .listEntry[data-role="import"].expanded .listExpander svg {
+ transform: rotate(180deg);
+ }
+#lists .listEntry[data-role="import"].expanded textarea {
+ visibility: visible;
+ }
+#lists .listEntry[data-role="import"] textarea {
border: 1px solid #ccc;
box-sizing: border-box;
display: block;
@@ -161,32 +244,3 @@ body.updating .listEntry.checked.obsolete .updating {
white-space: pre;
width: 100%;
}
-.listEntry.toImport.checked textarea {
- visibility: visible;
- }
-
-/* touch-screen devices */
-:root.mobile .listEntry .fa-icon {
- font-size: 120%;
- margin: 0 0.5em 0 0;
- }
-:root.mobile .listEntries {
- margin-inline-start: 0;
- -webkit-margin-start: 0;
- }
-:root.mobile .li.listEntry {
- /* background-color: var(--bg-1); */
- overflow-x: auto;
- }
-:root.mobile .li.listEntry label > span:not([class]) {
- flex-grow: 1;
- }
-:root.mobile .li.listEntry .listname,
-:root.mobile .li.listEntry .iconbar {
- align-items: flex-start;
- display: flex;
- white-space: nowrap;
- }
-:root.mobile .li.listEntry .iconbar {
- margin-top: 0.2em;
- }
diff --git a/src/css/common.css b/src/css/common.css
index c44f43f0cc551..91281a5d22ab3 100644
--- a/src/css/common.css
+++ b/src/css/common.css
@@ -174,14 +174,13 @@ section.notice {
- To have a single checkbox design across all platforms.
*/
.checkbox {
- --margin-end: calc(var(--font-size) * 0.75);
box-sizing: border-box;
display: inline-flex;
flex-shrink: 0;
height: var(--checkbox-size);
margin: 0;
- margin-inline-end: var(--margin-end);
- -webkit-margin-end: var(--margin-end);
+ margin-inline-end: var(--checkbox-margin-end);
+ -webkit-margin-end: var(--checkbox-margin-end);
position: relative;
width: var(--checkbox-size);
}
diff --git a/src/css/fa-icons.css b/src/css/fa-icons.css
index a912e7aa516f9..f6d517d401d00 100644
--- a/src/css/fa-icons.css
+++ b/src/css/fa-icons.css
@@ -38,9 +38,19 @@
}
.fa-icon.fa-icon-hflipped > svg {
transform: scale(-1, 1);
+ transform-origin: 50%;
}
.fa-icon.fa-icon-vflipped > svg {
transform: scale(1, -1);
+ transform-origin: 50%;
+ }
+.fa-icon.fa-icon-rotright > svg {
+ transform: rotate(90deg);
+ transform-origin: 50%;
+ }
+.fa-icon.fa-icon-rotleft > svg {
+ transform: rotate(-90deg);
+ transform-origin: 50%;
}
.fa-icon > svg {
diff --git a/src/css/themes/default.css b/src/css/themes/default.css
index d7162ab52d98b..a9b569af7f5de 100644
--- a/src/css/themes/default.css
+++ b/src/css/themes/default.css
@@ -127,8 +127,8 @@
* */
:root {
--font-size: 14px;
- --font-size-smaller: 13px;
- --font-size-xsmall: 11px;
+ --font-size-smaller: calc(var(--font-size) - 1px);
+ --font-size-xsmall: calc(var(--font-size) - 3px);
--font-size-larger: 15px;
--font-family: Inter, sans-serif;
--monospace-size: 12px;
@@ -417,6 +417,7 @@
--checkbox-ink: var(--ink-3);
--checkbox-checked-ink: var(--accent-surface-1);
--checkbox-disabled-filter: opacity(50%);
+ --checkbox-margin-end: calc(var(--font-size) * 0.75);
--notice-ink: var(--accent-ink-1);
--notice-surface: var(--accent-surface-1);
diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js
index 1d80d803ddfe1..006d51e04ddff 100644
--- a/src/js/3p-filters.js
+++ b/src/js/3p-filters.js
@@ -30,9 +30,7 @@ const lastUpdateTemplateString = i18n$('3pLastUpdate');
const obsoleteTemplateString = i18n$('3pExternalListObsolete');
const reValidExternalList = /^[a-z-]+:\/\/(?:\S+\/\S*|\/\S+)/m;
-let listDetails = {};
-let filteringSettingsHash = '';
-let hideUnusedSet = new Set([ '*' ]);
+let listsetDetails = {};
/******************************************************************************/
@@ -57,262 +55,217 @@ vAPI.broadcastListener.add(msg => {
/******************************************************************************/
-const renderNumber = function(value) {
+const renderNumber = value => {
return value.toLocaleString();
};
-/******************************************************************************/
+const listStatsTemplate = i18n$('3pListsOfBlockedHostsPerListStats');
+
+const renderLeafStats = (used, total) => {
+ if ( isNaN(used) || isNaN(total) ) { return ''; }
+ return listStatsTemplate
+ .replace('{{used}}', renderNumber(used))
+ .replace('{{total}}', renderNumber(total));
+};
+
+const renderNodeStats = (used, total) => {
+ if ( isNaN(used) || isNaN(total) ) { return ''; }
+ return `${used.toLocaleString()}/${total.toLocaleString()}`;
+};
-const renderFilterLists = function(soft) {
- const listGroupTemplate = qs$('#templates .groupEntry');
- const listEntryTemplate = qs$('#templates .listEntry');
- const listStatsTemplate = i18n$('3pListsOfBlockedHostsPerListStats');
- const renderElapsedTimeToString = i18n.renderElapsedTimeToString;
- const groupNames = new Map([ [ 'user', '' ] ]);
+/******************************************************************************/
+const renderFilterLists = ( ) => {
// Assemble a pretty list name if possible
- const listNameFromListKey = function(listKey) {
- const list = listDetails.current[listKey] || listDetails.available[listKey];
- const listTitle = list ? list.title : '';
- if ( listTitle === '' ) { return listKey; }
- return listTitle;
+ const listNameFromListKey = listkey => {
+ const list = listsetDetails.current[listkey] || listsetDetails.available[listkey];
+ const title = list && list.title || '';
+ if ( title !== '' ) { return title; }
+ return listkey;
};
- const liFromListEntry = function(listKey, li, hideUnused) {
- const entry = listDetails.available[listKey];
- if ( !li ) {
- li = dom.clone(listEntryTemplate);
- }
- const on = entry.off !== true;
- dom.cl.toggle(li, 'checked', on);
- let elem;
- if ( dom.attr(li, 'data-listkey') !== listKey ) {
- dom.attr(li, 'data-listkey', listKey);
- elem = qs$(li, 'input[type="checkbox"]');
- elem.checked = on;
- dom.text(qs$(li, '.listname'), listNameFromListKey(listKey));
- elem = qs$(li, 'a.content');
- dom.attr(elem, 'href', 'asset-viewer.html?url=' + encodeURIComponent(listKey));
- dom.attr(elem, 'type', 'text/html');
- dom.cl.remove(li, 'toRemove');
- if ( entry.supportName ) {
- dom.cl.add(li, 'support');
- elem = qs$(li, 'a.support');
- dom.attr(elem, 'href', entry.supportURL);
- dom.attr(elem, 'title', entry.supportName);
- } else {
- dom.cl.remove(li, 'support');
+ const initializeListEntry = (listDetails, listEntry) => {
+ const listkey = listEntry.dataset.key;
+ const listEntryPrevious =
+ qs$(`[data-key="${listDetails.group}"] [data-key="${listkey}"]`);
+ if ( listEntryPrevious !== null ) {
+ if ( dom.cl.has(listEntryPrevious, 'checked') ) {
+ dom.cl.add(listEntry, 'checked');
}
- if ( entry.external ) {
- dom.cl.add(li, 'external');
- } else {
- dom.cl.remove(li, 'external');
+ if ( dom.cl.has(listEntryPrevious, 'stickied') ) {
+ dom.cl.add(listEntry, 'stickied');
}
- if ( entry.instructionURL ) {
- dom.cl.add(li, 'mustread');
- dom.attr(qs$(li, 'a.mustread'), 'href', entry.instructionURL);
- } else {
- dom.cl.remove(li, 'mustread');
+ if ( dom.cl.has(listEntryPrevious, 'toRemove') ) {
+ dom.cl.add(listEntry, 'toRemove');
}
- dom.cl.toggle(li, 'isDefault', entry.isDefault === true);
- dom.cl.toggle(li, 'unused', hideUnused && !on);
+ } else {
+ dom.cl.toggle(listEntry, 'checked', listDetails.off !== true);
}
- // https://github.com/gorhill/uBlock/issues/1429
- if ( !soft ) {
- qs$(li, 'input[type="checkbox"]').checked = on;
+ const on = dom.cl.has(listEntry, 'checked');
+ dom.prop(qs$(listEntry, ':scope > .detailbar input'), 'checked', on);
+ dom.text(qs$(listEntry, ':scope > .detailbar .listname'), listDetails.title);
+ let elem = qs$(listEntry, ':scope > .detailbar a.content');
+ dom.attr(elem, 'href', 'asset-viewer.html?url=' + encodeURIComponent(listkey));
+ dom.attr(elem, 'type', 'text/html');
+ dom.cl.remove(listEntry, 'toRemove');
+ if ( listDetails.supportName ) {
+ elem = qs$(listEntry, ':scope > .detailbar a.support');
+ dom.attr(elem, 'href', listDetails.supportURL || '#');
+ dom.attr(elem, 'title', listDetails.supportName);
}
- elem = qs$(li, 'span.counts');
- let text = '';
- if ( !isNaN(+entry.entryUsedCount) && !isNaN(+entry.entryCount) ) {
- text = listStatsTemplate
- .replace('{{used}}', renderNumber(on ? entry.entryUsedCount : 0))
- .replace('{{total}}', renderNumber(entry.entryCount));
+ if ( listDetails.external ) {
+ dom.cl.add(listEntry, 'external');
+ } else {
+ dom.cl.remove(listEntry, 'external');
}
- dom.text(elem, text);
+ if ( listDetails.instructionURL ) {
+ elem = qs$(listEntry, ':scope > .detailbar a.mustread');
+ dom.attr(elem, 'href', listDetails.instructionURL || '#');
+ }
+ dom.cl.toggle(listEntry, 'isDefault',
+ listDetails.isDefault === true || listkey === 'user-filters'
+ );
+ elem = qs$(listEntry, '.leafstats');
+ dom.text(elem, renderLeafStats(on ? listDetails.entryUsedCount : 0, listDetails.entryCount));
// https://github.com/chrisaljoudi/uBlock/issues/104
- const asset = listDetails.cache[listKey] || {};
+ const asset = listsetDetails.cache[listkey] || {};
const remoteURL = asset.remoteURL;
- dom.cl.toggle(li, 'unsecure',
+ dom.cl.toggle(listEntry, 'unsecure',
typeof remoteURL === 'string' && remoteURL.lastIndexOf('http:', 0) === 0
);
- dom.cl.toggle(li, 'failed', asset.error !== undefined);
- dom.cl.toggle(li, 'obsolete', asset.obsolete === true);
- const lastUpdateString = lastUpdateTemplateString.replace(
- '{{ago}}',
- renderElapsedTimeToString(asset.writeTime || 0)
+ dom.cl.toggle(listEntry, 'failed', asset.error !== undefined);
+ dom.cl.toggle(listEntry, 'obsolete', asset.obsolete === true);
+ const lastUpdateString = lastUpdateTemplateString.replace('{{ago}}',
+ i18n.renderElapsedTimeToString(asset.writeTime || 0)
);
if ( asset.obsolete === true ) {
let title = obsoleteTemplateString;
if ( asset.cached && asset.writeTime !== 0 ) {
title += '\n' + lastUpdateString;
}
- dom.attr(qs$(li, '.status.obsolete'), 'title', title);
+ dom.attr(qs$(listEntry, ':scope > .detailbar .status.obsolete'), 'title', title);
}
if ( asset.cached === true ) {
- dom.cl.add(li, 'cached');
- dom.attr(qs$(li, '.status.cache'), 'title', lastUpdateString);
+ dom.cl.add(listEntry, 'cached');
+ dom.attr(qs$(listEntry, ':scope > .detailbar .status.cache'), 'title', lastUpdateString);
} else {
- dom.cl.remove(li, 'cached');
+ dom.cl.remove(listEntry, 'cached');
}
- dom.cl.remove(li, 'discard');
- return li;
};
- const listEntryCountFromGroup = function(listKeys) {
- if ( Array.isArray(listKeys) === false ) { return ''; }
- let count = 0,
- total = 0;
- for ( const listKey of listKeys ) {
- if ( listDetails.available[listKey].off !== true ) {
- count += 1;
- }
- total += 1;
+ const createListEntry = (listDetails, depth) => {
+ if ( listDetails.lists === undefined ) {
+ return dom.clone('#templates .listEntry[data-role="leaf"]');
}
- return total !== 0 ?
- `(${count.toLocaleString()}/${total.toLocaleString()})` :
- '';
- };
-
- const liFromListGroup = function(groupKey, listKeys) {
- let liGroup = qs$(`#lists > .groupEntry[data-groupkey="${groupKey}"]`);
- if ( liGroup === null ) {
- liGroup = dom.clone(listGroupTemplate);
- let groupName = groupNames.get(groupKey);
- if ( groupName === undefined ) {
- groupName = i18n$('3pGroup' + groupKey.charAt(0).toUpperCase() + groupKey.slice(1));
- groupNames.set(groupKey, groupName);
- }
- if ( groupName !== '' ) {
- dom.text(qs$(liGroup, '.geName'), groupName);
- }
+ if ( depth !== 0 ) {
+ return dom.clone('#templates .listEntry[data-role="node"]');
}
- if ( qs$(liGroup, '.geName:empty') === null ) {
- dom.text(qs$(liGroup, '.geCount'), listEntryCountFromGroup(listKeys));
- }
- let hideUnused = mustHideUnusedLists(groupKey);
- dom.cl.toggle(liGroup, 'hideUnused', hideUnused);
- let ulGroup = qs$(liGroup, '.listEntries');
- if ( !listKeys ) { return liGroup; }
- listKeys.sort(function(a, b) {
- return (listDetails.available[a].title || '').localeCompare(listDetails.available[b].title || '');
- });
- for ( let i = 0; i < listKeys.length; i++ ) {
- let liEntry = liFromListEntry(
- listKeys[i],
- ulGroup.children[i],
- hideUnused
- );
- if ( liEntry.parentElement === null ) {
- ulGroup.appendChild(liEntry);
- }
- }
- return liGroup;
+ return dom.clone('#templates .listEntry[data-role="node"][data-parent="root"]');
};
- const groupsFromLists = function(lists) {
- let groups = new Map();
- let listKeys = Object.keys(lists);
- for ( let listKey of listKeys ) {
- let list = lists[listKey];
- let groupKey = list.group || 'nogroup';
- if ( groupKey === 'social' ) {
- groupKey = 'annoyances';
- }
- let memberKeys = groups.get(groupKey);
- if ( memberKeys === undefined ) {
- groups.set(groupKey, (memberKeys = []));
+ const createListEntries = (parentkey, listTree, depth = 0) => {
+ const listEntries = dom.clone('#templates .listEntries');
+ const treeEntries = Object.entries(listTree);
+ if ( depth !== 0 ) {
+ treeEntries.sort((a ,b) => {
+ const as = a[1].title || a[0];
+ const bs = b[1].title || b[0];
+ return as.localeCompare(bs);
+ });
+ }
+ for ( const [ listkey, listDetails ] of treeEntries ) {
+ const listEntry = createListEntry(listDetails, depth);
+ listEntry.dataset.key = listkey;
+ listEntry.dataset.parent = parentkey;
+ dom.text(qs$(listEntry, '.listname'), listDetails.title);
+ if ( listDetails.lists !== undefined ) {
+ listEntry.append(createListEntries(listEntry.dataset.key, listDetails.lists, depth+1));
+ dom.cl.toggle(listEntry, 'expanded', listIsExpanded(listkey));
+ updateListNode(listEntry);
+ } else {
+ initializeListEntry(listDetails, listEntry);
}
- memberKeys.push(listKey);
+ listEntries.append(listEntry);
}
- return groups;
+ return listEntries;
};
- const onListsReceived = function(details) {
- // Before all, set context vars
- listDetails = details;
-
- // "My filters" will now sit in its own group. The following code
- // ensures smooth transition.
- listDetails.available['user-filters'].group = 'user';
-
- // Incremental rendering: this will allow us to easily discard unused
- // DOM list entries.
- dom.cl.add('#lists .listEntries .listEntry[data-listkey]', 'discard');
+ const onListsReceived = response => {
+ // Store in global variable
+ listsetDetails = response;
+ hashFromListsetDetails();
- // Remove import widget while we recreate list of lists.
- const importWidget = qs$('.listEntry.toImport');
- importWidget.remove();
-
- // Visually split the filter lists in purpose-based groups
- const ulLists = qs$('#lists');
- const groups = groupsFromLists(details.available);
+ // Build list tree
+ const listTree = {};
const groupKeys = [
'user',
'default',
'ads',
'privacy',
'malware',
- 'annoyances',
'multipurpose',
+ 'annoyances',
'regions',
'custom'
];
- dom.cl.toggle(dom.body, 'hideUnused', mustHideUnusedLists('*'));
- for ( let i = 0; i < groupKeys.length; i++ ) {
- let groupKey = groupKeys[i];
- let liGroup = liFromListGroup(groupKey, groups.get(groupKey));
- dom.attr(liGroup, 'data-groupkey', groupKey);
- if ( liGroup.parentElement === null ) {
- ulLists.appendChild(liGroup);
- }
- groups.delete(groupKey);
+ for ( const key of groupKeys ) {
+ listTree[key] = {
+ title: i18n$('3pGroup' + key.charAt(0).toUpperCase() + key.slice(1)),
+ lists: {},
+ };
}
- // For all groups not covered above (if any left)
- for ( const groupKey of Object.keys(groups) ) {
- ulLists.appendChild(liFromListGroup(groupKey, groupKey));
+ for ( const [ listkey, listDetails ] of Object.entries(response.available) ) {
+ let groupKey = listDetails.group;
+ if ( groupKey === 'social' ) {
+ groupKey = 'annoyances';
+ }
+ const groupDetails = listTree[groupKey];
+ if ( listDetails.parent !== undefined ) {
+ if ( groupDetails.lists[listDetails.parent] === undefined ) {
+ groupDetails.lists[listDetails.parent] = {
+ title: listDetails.parent,
+ lists: {},
+ };
+ }
+ groupDetails.lists[listDetails.parent].lists[listkey] = listDetails;
+ } else {
+ listDetails.title = listNameFromListKey(listkey);
+ groupDetails.lists[listkey] = listDetails;
+ }
}
+ const listEntries = createListEntries('root', listTree);
+ qs$('#lists .listEntries').replaceWith(listEntries);
- dom.remove('#lists .listEntries .listEntry.discard');
-
- // Re-insert import widget.
- qs$('[data-groupkey="custom"] .listEntries').append(importWidget);
-
- qs$('#autoUpdate').checked = listDetails.autoUpdate === true;
+ qs$('#autoUpdate').checked = listsetDetails.autoUpdate === true;
dom.text(
'#listsOfBlockedHostsPrompt',
i18n$('3pListsOfBlockedHostsPrompt')
- .replace('{{netFilterCount}}', renderNumber(details.netFilterCount))
- .replace('{{cosmeticFilterCount}}', renderNumber(details.cosmeticFilterCount))
+ .replace('{{netFilterCount}}', renderNumber(response.netFilterCount))
+ .replace('{{cosmeticFilterCount}}', renderNumber(response.cosmeticFilterCount))
);
qs$('#parseCosmeticFilters').checked =
- listDetails.parseCosmeticFilters === true;
+ listsetDetails.parseCosmeticFilters === true;
qs$('#ignoreGenericCosmeticFilters').checked =
- listDetails.ignoreGenericCosmeticFilters === true;
+ listsetDetails.ignoreGenericCosmeticFilters === true;
qs$('#suspendUntilListsAreLoaded').checked =
- listDetails.suspendUntilListsAreLoaded === true;
-
- // Compute a hash of the settings so that we can keep track of changes
- // affecting the loading of filter lists.
- if ( !soft ) {
- filteringSettingsHash = hashFromCurrentFromSettings();
- }
+ listsetDetails.suspendUntilListsAreLoaded === true;
// https://github.com/gorhill/uBlock/issues/2394
- dom.cl.toggle(dom.body, 'updating', listDetails.isUpdating);
+ dom.cl.toggle(dom.body, 'updating', listsetDetails.isUpdating);
renderWidgets();
};
messaging.send('dashboard', {
what: 'getLists',
- }).then(details => {
- onListsReceived(details);
+ }).then(response => {
+ onListsReceived(response);
});
};
/******************************************************************************/
-const renderWidgets = function() {
+const renderWidgets = ( ) => {
dom.cl.toggle('#buttonApply', 'disabled',
filteringSettingsHash === hashFromCurrentFromSettings()
);
@@ -320,7 +273,7 @@ const renderWidgets = function() {
dom.cl.toggle('#buttonUpdate', 'active', updating);
dom.cl.toggle('#buttonUpdate', 'disabled',
updating === false &&
- qs$('#lists .listEntry.obsolete:not(.toRemove) input[type="checkbox"]:checked') === null
+ qs$('#lists .listEntry.checked.obsolete:not(.toRemove)') === null
);
dom.cl.toggle('#buttonPurgeAll', 'disabled',
updating || qs$('#lists .listEntry.cached:not(.obsolete)') === null
@@ -329,20 +282,21 @@ const renderWidgets = function() {
/******************************************************************************/
-const updateAssetStatus = function(details) {
- const li = qs$(`#lists .listEntry[data-listkey="${details.key}"]`);
- if ( li === null ) { return; }
- dom.cl.toggle(li, 'failed', !!details.failed);
- dom.cl.toggle(li, 'obsolete', !details.cached);
- dom.cl.toggle(li, 'cached', !!details.cached);
+const updateAssetStatus = details => {
+ const listEntry = qs$(`#lists .listEntry[data-key="${details.key}"]`);
+ if ( listEntry === null ) { return; }
+ dom.cl.toggle(listEntry, 'failed', !!details.failed);
+ dom.cl.toggle(listEntry, 'obsolete', !details.cached);
+ dom.cl.toggle(listEntry, 'cached', !!details.cached);
if ( details.cached ) {
- dom.attr(qs$(li, '.status.cache'), 'title',
- lastUpdateTemplateString.replace(
- '{{ago}}',
- i18n.renderElapsedTimeToString(Date.now())
- )
+ dom.attr(qs$(listEntry, '.status.cache'), 'title',
+ lastUpdateTemplateString.replace('{{ago}}', i18n.renderElapsedTimeToString(Date.now()))
);
+
}
+ updateAncestorListNodes(listEntry, ancestor => {
+ updateListNode(ancestor);
+ });
renderWidgets();
};
@@ -353,60 +307,169 @@ const updateAssetStatus = function(details) {
**/
-const hashFromCurrentFromSettings = function() {
- const hash = [
+let filteringSettingsHash = '';
+
+const hashFromListsetDetails = ( ) => {
+ const hashParts = [
+ listsetDetails.parseCosmeticFilters === true,
+ listsetDetails.ignoreGenericCosmeticFilters === true,
+ ];
+ const listHashes = [];
+ for ( const [ listkey, listDetails ] of Object.entries(listsetDetails.available) ) {
+ if ( listDetails.off === true ) { continue; }
+ listHashes.push(listkey);
+ }
+ hashParts.push( listHashes.sort().join(), '', false);
+ filteringSettingsHash = hashParts.join();
+};
+
+const hashFromCurrentFromSettings = ( ) => {
+ const hashParts = [
qs$('#parseCosmeticFilters').checked,
- qs$('#ignoreGenericCosmeticFilters').checked
+ qs$('#ignoreGenericCosmeticFilters').checked,
];
- const listHash = [];
- const listEntries = qsa$('#lists .listEntry[data-listkey]:not(.toRemove)');
+ const listHashes = [];
+ const listEntries = qsa$('#lists .listEntry[data-key]:not(.toRemove)');
for ( const liEntry of listEntries ) {
- if ( qs$(liEntry, 'input[type="checkbox"]:checked') !== null ) {
- listHash.push(dom.attr(liEntry, 'data-listkey'));
- }
+ if ( liEntry.dataset.role !== 'leaf' ) { continue; }
+ if ( dom.cl.has(liEntry, 'checked') === false ) { continue; }
+ listHashes.push(liEntry.dataset.key);
}
- hash.push(
- listHash.sort().join(),
- qs$('#importLists').checked &&
- reValidExternalList.test(qs$('#externalLists').value.trim()),
+ const textarea = qs$('#lists .listEntry[data-role="import"].expanded textarea');
+ hashParts.push(
+ listHashes.sort().join(),
+ textarea !== null && textarea.value.trim() || '',
qs$('#lists .listEntry.toRemove') !== null
);
- return hash.join();
+ return hashParts.join();
};
/******************************************************************************/
-const onListsetChanged = function(ev) {
- const input = ev.target;
- dom.cl.toggle(input.closest('.listEntry'), 'checked', input.checked);
+const onListsetChanged = ev => {
+ const input = ev.target.closest('input');
+ if ( input === null ) { return; }
+ toggleFilterList(input, input.checked, true);
+};
+
+dom.on('#lists', 'change', '.listEntry > .detailbar input', onListsetChanged);
+
+const toggleFilterList = (elem, on, ui = false) => {
+ const listEntry = elem.closest('.listEntry');
+ if ( listEntry === null ) { return; }
+ if ( listEntry.dataset.parent === 'root' ) { return; }
+ const input = qs$(listEntry, ':scope > .detailbar input');
+ if ( on === undefined ) {
+ on = input.checked === false;
+ }
+ input.checked = on;
+ dom.cl.toggle(listEntry, 'checked', on);
+ dom.cl.toggle(listEntry, 'stickied', ui && !on);
+ // Select/unselect descendants
+ const childListEntries = qsa$(listEntry, '.listEntry');
+ for ( const descendantList of childListEntries ) {
+ dom.cl.toggle(descendantList, 'checked', on);
+ qs$(descendantList, ':scope > .detailbar input').checked = on;
+ }
+ updateAncestorListNodes(listEntry, ancestor => {
+ updateListNode(ancestor);
+ });
onFilteringSettingsChanged();
};
+const updateListNode = listNode => {
+ if ( listNode === null ) { return; }
+ if ( listNode.dataset.role !== 'node' ) { return; }
+ const listLeaves = qsa$(listNode, '.listEntry[data-role="leaf"].checked');
+ let usedFilterCount = 0;
+ let totalFilterCount = 0;
+ let isCached = false;
+ let isObsolete = false;
+ let writeTime = 0;
+ for ( const listLeaf of listLeaves ) {
+ const listkey = listLeaf.dataset.key;
+ const listDetails = listsetDetails.available[listkey];
+ usedFilterCount += listDetails.off ? 0 : listDetails.entryUsedCount || 0;
+ totalFilterCount += listDetails.entryCount || 0;
+ const assetCache = listsetDetails.cache[listkey] || {};
+ isCached = isCached || dom.cl.has(listLeaf, 'cached');
+ isObsolete = isObsolete || dom.cl.has(listLeaf, 'obsolete');
+ writeTime = Math.max(writeTime, assetCache.writeTime || 0);
+ }
+ dom.cl.toggle(listNode, 'checked', listLeaves.length !== 0);
+ dom.prop(qs$(listNode, ':scope > .detailbar input'), 'checked', listLeaves.length !== 0);
+ dom.text(qs$(listNode, '.nodestats'),
+ renderNodeStats(listLeaves.length, qsa$(listNode, '.listEntry[data-role="leaf"]').length)
+ );
+ dom.text(qs$(listNode, '.leafstats'),
+ renderLeafStats(usedFilterCount, totalFilterCount)
+ );
+ const firstLeaf = qs$(listNode, '.listEntry[data-role="leaf"]');
+ if ( firstLeaf !== null ) {
+ dom.attr(qs$(listNode, ':scope > .detailbar a.support'), 'href',
+ dom.attr(qs$(firstLeaf, ':scope > .detailbar a.support'), 'href') || '#'
+ );
+ dom.attr(qs$(listNode, ':scope > .detailbar a.mustread'), 'href',
+ dom.attr(qs$(firstLeaf, ':scope > .detailbar a.mustread'), 'href') || '#'
+ );
+ }
+ dom.cl.toggle(listNode, 'cached', isCached);
+ dom.cl.toggle(listNode, 'obsolete', isObsolete);
+ if ( isCached ) {
+ dom.attr(qs$(listNode, ':scope > .detailbar .cache'), 'title',
+ lastUpdateTemplateString.replace('{{ago}}', i18n.renderElapsedTimeToString(writeTime))
+ );
+ }
+ if ( qs$(listNode, '.listEntry.stickied') !== null ) {
+ dom.cl.add(listNode, 'stickied');
+ }
+};
+
+const updateAncestorListNodes = (listEntry, fn) => {
+ while ( listEntry !== null ) {
+ fn(listEntry);
+ listEntry = qs$(`.listEntry[data-key="${listEntry.dataset.parent}"]`);
+ }
+};
+
/******************************************************************************/
-const onFilteringSettingsChanged = function() {
+const onFilteringSettingsChanged = ( ) => {
renderWidgets();
};
+dom.on('#parseCosmeticFilters', 'change', onFilteringSettingsChanged);
+dom.on('#ignoreGenericCosmeticFilters', 'change', onFilteringSettingsChanged);
+dom.on('#lists', 'input', '[data-role="import"] textarea', onFilteringSettingsChanged);
+
/******************************************************************************/
-const onRemoveExternalList = function(ev) {
- const liEntry = ev.target.closest('[data-listkey]');
- if ( liEntry === null ) { return; }
- dom.cl.toggle(liEntry, 'toRemove');
+const onRemoveExternalList = ev => {
+ const listEntry = ev.target.closest('[data-key]');
+ if ( listEntry === null ) { return; }
+ dom.cl.toggle(listEntry, 'toRemove');
renderWidgets();
};
+dom.on('#lists', 'click', '.listEntry .remove', onRemoveExternalList);
+
/******************************************************************************/
-const onPurgeClicked = function(ev) {
- const liEntry = ev.target.closest('[data-listkey]');
- const listKey = dom.attr(liEntry, 'data-listkey') || '';
- if ( listKey === '' ) { return; }
+const onPurgeClicked = ev => {
+ const liEntry = ev.target.closest('[data-key]');
+ const listkey = liEntry.dataset.key || '';
+ if ( listkey === '' ) { return; }
+
+ const assetKeys = [ listkey ];
+ for ( const listLeaf of qsa$(liEntry, '[data-role="leaf"]') ) {
+ assetKeys.push(listLeaf.dataset.key);
+ dom.cl.add(listLeaf, 'obsolete');
+ dom.cl.remove(listLeaf, 'cached');
+ }
messaging.send('dashboard', {
- what: 'purgeCache',
- assetKey: listKey,
+ what: 'purgeCaches',
+ assetKeys,
});
// If the cached version is purged, the installed version must be assumed
@@ -422,77 +485,96 @@ const onPurgeClicked = function(ev) {
}
};
+dom.on('#lists', 'click', 'span.cache', onPurgeClicked);
+
/******************************************************************************/
-const selectFilterLists = async function() {
+const selectFilterLists = async ( ) => {
// Cosmetic filtering switch
+ let checked = qs$('#parseCosmeticFilters').checked;
messaging.send('dashboard', {
what: 'userSettings',
name: 'parseAllABPHideFilters',
- value: qs$('#parseCosmeticFilters').checked,
+ value: checked,
});
+ listsetDetails.parseCosmeticFilters = checked;
+
+ checked = qs$('#ignoreGenericCosmeticFilters').checked;
messaging.send('dashboard', {
what: 'userSettings',
name: 'ignoreGenericCosmeticFilters',
- value: qs$('#ignoreGenericCosmeticFilters').checked,
+ value: checked,
});
+ listsetDetails.ignoreGenericCosmeticFilters = checked;
- // Filter lists to select
+ // Filter lists to remove/select
const toSelect = [];
- for ( const liEntry of qsa$('#lists .listEntry[data-listkey]:not(.toRemove)') ) {
- if ( qs$(liEntry, 'input[type="checkbox"]:checked') !== null ) {
- toSelect.push(dom.attr(liEntry, 'data-listkey'));
- }
- }
-
- // External filter lists to remove
const toRemove = [];
- for ( const liEntry of qsa$('#lists .listEntry.toRemove[data-listkey]') ) {
- toRemove.push(dom.attr(liEntry, 'data-listkey'));
+ for ( const liEntry of qsa$('#lists .listEntry[data-role="leaf"]') ) {
+ const listkey = liEntry.dataset.key;
+ if ( listsetDetails.available.hasOwnProperty(listkey) === false ) {
+ continue;
+ }
+ const listDetails = listsetDetails.available[listkey];
+ if ( dom.cl.has(liEntry, 'toRemove') ) {
+ toRemove.push(listkey);
+ listDetails.off = true;
+ continue;
+ }
+ if ( dom.cl.has(liEntry, 'checked') ) {
+ toSelect.push(listkey);
+ listDetails.off = false;
+ } else {
+ listDetails.off = true;
+ }
}
// External filter lists to import
- const externalListsElem = qs$('#externalLists');
- const toImport = externalListsElem.value.trim();
- {
- const liEntry = externalListsElem.closest('.listEntry');
- dom.cl.remove(liEntry, 'checked');
- qs$(liEntry, 'input[type="checkbox"]').checked = false;
- externalListsElem.value = '';
+ const textarea = qs$('#lists .listEntry[data-role="import"].expanded textarea');
+ const toImport = textarea !== null && textarea.value.trim() || '';
+ if ( textarea !== null ) {
+ dom.cl.remove(textarea.closest('expandable'), 'expanded');
+ textarea.value = '';
}
+ hashFromListsetDetails();
+
await messaging.send('dashboard', {
what: 'applyFilterListSelection',
- toSelect: toSelect,
- toImport: toImport,
- toRemove: toRemove,
+ toSelect,
+ toImport,
+ toRemove,
});
-
- filteringSettingsHash = hashFromCurrentFromSettings();
};
/******************************************************************************/
-const buttonApplyHandler = async function() {
- dom.cl.remove('#buttonApply', 'enabled');
+const buttonApplyHandler = async ( ) => {
await selectFilterLists();
+ dom.cl.add(dom.body, 'working');
+ dom.cl.remove('#lists .listEntry.stickied', 'stickied');
renderWidgets();
- messaging.send('dashboard', { what: 'reloadAllFilters' });
+ await messaging.send('dashboard', { what: 'reloadAllFilters' });
+ dom.cl.remove(dom.body, 'working');
};
+dom.on('#buttonApply', 'click', ( ) => { buttonApplyHandler(); });
+
/******************************************************************************/
-const buttonUpdateHandler = async function() {
+const buttonUpdateHandler = async ( ) => {
+ dom.cl.remove('#lists .listEntry.stickied', 'stickied');
await selectFilterLists();
dom.cl.add(dom.body, 'updating');
renderWidgets();
messaging.send('dashboard', { what: 'forceUpdateAssets' });
};
+dom.on('#buttonUpdate', 'click', ( ) => { buttonUpdateHandler(); });
+
/******************************************************************************/
-const buttonPurgeAllHandler = async function(hard) {
- dom.cl.remove('#buttonPurgeAll', 'enabled');
+const buttonPurgeAllHandler = async hard => {
await messaging.send('dashboard', {
what: 'purgeAllCaches',
hard,
@@ -500,153 +582,216 @@ const buttonPurgeAllHandler = async function(hard) {
renderFilterLists(true);
};
+dom.on('#buttonPurgeAll', 'click', ev => { buttonPurgeAllHandler(ev.shiftKey); });
+
/******************************************************************************/
-const userSettingCheckboxChanged = function() {
+const userSettingCheckboxChanged = ( ) => {
const target = event.target;
messaging.send('dashboard', {
what: 'userSettings',
name: target.id,
value: target.checked,
});
+ listsetDetails[target.id] = target.checked;
};
+dom.on('#autoUpdate', 'change', userSettingCheckboxChanged);
+dom.on('#suspendUntilListsAreLoaded', 'change', userSettingCheckboxChanged);
+
/******************************************************************************/
-// Collapsing of unused lists.
+const searchFilterLists = ( ) => {
+ const pattern = dom.prop('.searchbar input', 'value') || '';
+ dom.cl.toggle('#lists', 'searchMode', pattern !== '');
+ if ( pattern === '' ) { return; }
+ const reflectSearchMatches = listEntry => {
+ if ( listEntry.dataset.role !== 'node' ) { return; }
+ dom.cl.toggle(listEntry, 'searchMatch',
+ qs$(listEntry, ':scope > .listEntries > .listEntry.searchMatch') !== null
+ );
+ };
+ const re = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
+ for ( const listEntry of qsa$('#lists [data-role="leaf"]') ) {
+ const listkey = listEntry.dataset.key;
+ const listDetails = listsetDetails.available[listkey];
+ let matches = false;
+ if ( listDetails ) {
+ matches = re.test(listDetails.title);
+ if ( matches === false && listDetails.tags ) {
+ matches = re.test(listDetails.tags);
+ }
+ }
+ dom.cl.toggle(listEntry, 'searchMatch', matches);
+ updateAncestorListNodes(listEntry, reflectSearchMatches);
+ }
+};
+
+dom.on('.searchbar input', 'input', searchFilterLists);
-const mustHideUnusedLists = function(which) {
- const hideAll = hideUnusedSet.has('*');
- if ( which === '*' ) { return hideAll; }
- return hideUnusedSet.has(which) !== hideAll;
+/******************************************************************************/
+
+const expandedListSet = new Set();
+
+const listIsExpanded = which => {
+ return expandedListSet.has(which);
};
-const toggleHideUnusedLists = function(which) {
- const doesHideAll = hideUnusedSet.has('*');
- let groupSelector;
- let mustHide;
+const applyListExpansion = listkeys => {
+ if ( listkeys === undefined ) {
+ listkeys = Array.from(expandedListSet);
+ }
+ expandedListSet.clear();
+ dom.cl.remove('#lists [data-role="node"]', 'expanded');
+ listkeys.forEach(which => {
+ expandedListSet.add(which);
+ dom.cl.add(`#lists [data-key="${which}"]`, 'expanded');
+ });
+};
+
+const toggleListExpansion = which => {
+ const isExpanded = expandedListSet.has(which);
if ( which === '*' ) {
- mustHide = doesHideAll === false;
- groupSelector = '';
- hideUnusedSet.clear();
- if ( mustHide ) {
- hideUnusedSet.add(which);
+ if ( isExpanded ) {
+ expandedListSet.clear();
+ dom.cl.remove('#lists .expandable', 'expanded');
+ dom.cl.remove('#lists .stickied', 'stickied');
+ } else {
+ expandedListSet.clear();
+ expandedListSet.add('*');
+ dom.cl.add('#lists .rootstats', 'expanded');
+ for ( const expandable of qsa$('#lists > .listEntries .expandable') ) {
+ const listkey = expandable.dataset.key || '';
+ if ( listkey === '' ) { continue; }
+ expandedListSet.add(listkey);
+ dom.cl.add(expandable, 'expanded');
+ }
}
- dom.cl.toggle(dom.body, 'hideUnused', mustHide);
- dom.cl.toggle('.groupEntry[data-groupkey]', 'hideUnused', mustHide);
} else {
- const doesHide = hideUnusedSet.has(which);
- if ( doesHide ) {
- hideUnusedSet.delete(which);
+ if ( isExpanded ) {
+ expandedListSet.delete(which);
+ const listNode = qs$(`#lists > .listEntries [data-key="${which}"]`);
+ dom.cl.remove(listNode, 'expanded');
+ if ( listNode.dataset.parent === 'root' ) {
+ dom.cl.remove(qsa$(listNode, '.stickied'), 'stickied');
+ }
} else {
- hideUnusedSet.add(which);
+ expandedListSet.add(which);
+ dom.cl.add(`#lists > .listEntries [data-key="${which}"]`, 'expanded');
}
- mustHide = doesHide === doesHideAll;
- groupSelector = `.groupEntry[data-groupkey="${which}"] `;
- dom.cl.toggle(groupSelector, 'hideUnused', mustHide);
}
- qsa$(`${groupSelector}.listEntry input[type="checkbox"]:not(:checked)`)
- .forEach(elem => {
- dom.cl.toggle(elem.closest('.listEntry[data-listkey]'), 'unused', mustHide);
- });
- vAPI.localStorage.setItem(
- 'hideUnusedFilterLists',
- Array.from(hideUnusedSet)
- );
-};
-
-const revealHiddenUsedLists = function() {
- qsa$('#lists .listEntry.unused input[type="checkbox"]:checked')
- .forEach(elem => {
- dom.cl.remove(elem.closest('.listEntry[data-listkey]'), 'unused');
- });
+ vAPI.localStorage.setItem('expandedListSet', Array.from(expandedListSet));
+ vAPI.localStorage.removeItem('hideUnusedFilterLists');
};
dom.on('#listsOfBlockedHostsPrompt', 'click', ( ) => {
- toggleHideUnusedLists('*');
+ toggleListExpansion('*');
});
-dom.on('#lists', 'click', '.groupEntry[data-groupkey] > .geDetails', ev => {
- toggleHideUnusedLists(
- dom.attr(ev.target.closest('.groupEntry[data-groupkey]'), 'data-groupkey')
- );
+dom.on('#lists', 'click', '.listExpander', ev => {
+ const expandable = ev.target.closest('.expandable');
+ if ( expandable === null ) { return; }
+ const which = expandable.dataset.key;
+ if ( which !== undefined ) {
+ toggleListExpansion(which);
+ } else {
+ dom.cl.toggle(expandable, 'expanded');
+ if ( expandable.dataset.role === 'import' ) {
+ onFilteringSettingsChanged();
+ }
+ }
+ ev.preventDefault();
+});
+
+dom.on('#lists', 'click', '[data-parent="root"] > .detailbar .listname', ev => {
+ const listEntry = ev.target.closest('.listEntry');
+ if ( listEntry === null ) { return; }
+ const listkey = listEntry.dataset.key;
+ if ( listkey === undefined ) { return; }
+ toggleListExpansion(listkey);
+ ev.preventDefault();
+});
+
+dom.on('#lists', 'click', '[data-role="import"] > .detailbar .listname', ev => {
+ const expandable = ev.target.closest('.listEntry');
+ if ( expandable === null ) { return; }
+ dom.cl.toggle(expandable, 'expanded');
+ ev.preventDefault();
+});
+
+dom.on('#lists', 'click', '.listEntry > .detailbar .nodestats', ev => {
+ const listEntry = ev.target.closest('.listEntry');
+ if ( listEntry === null ) { return; }
+ const listkey = listEntry.dataset.key;
+ if ( listkey === undefined ) { return; }
+ toggleListExpansion(listkey);
+ ev.preventDefault();
});
// Initialize from saved state.
-vAPI.localStorage.getItemAsync('hideUnusedFilterLists').then(value => {
- if ( Array.isArray(value) ) {
- hideUnusedSet = new Set(value);
- }
+vAPI.localStorage.getItemAsync('expandedListSet').then(listkeys => {
+ if ( Array.isArray(listkeys) === false ) { return; }
+ applyListExpansion(listkeys);
});
/******************************************************************************/
-// Cloud-related.
+// Cloud storage-related.
-const toCloudData = function() {
+self.cloud.onPush = function toCloudData() {
const bin = {
parseCosmeticFilters: qs$('#parseCosmeticFilters').checked,
ignoreGenericCosmeticFilters: qs$('#ignoreGenericCosmeticFilters').checked,
selectedLists: []
};
- const liEntries = qsa$('#lists .listEntry');
+ const liEntries = qsa$('#lists .listEntry.checked[data-role="leaf"]');
for ( const liEntry of liEntries ) {
- if ( qs$(liEntry, 'input').checked ) {
- bin.selectedLists.push(dom.attr(liEntry, 'data-listkey'));
- }
+ bin.selectedLists.push(liEntry.dataset.key);
}
return bin;
};
-const fromCloudData = function(data, append) {
+self.cloud.onPull = function fromCloudData(data, append) {
if ( typeof data !== 'object' || data === null ) { return; }
- let elem, checked;
-
- elem = qs$('#parseCosmeticFilters');
- checked = data.parseCosmeticFilters === true || append && elem.checked;
- elem.checked = listDetails.parseCosmeticFilters = checked;
+ let elem = qs$('#parseCosmeticFilters');
+ let checked = data.parseCosmeticFilters === true || append && elem.checked;
+ elem.checked = listsetDetails.parseCosmeticFilters = checked;
elem = qs$('#ignoreGenericCosmeticFilters');
checked = data.ignoreGenericCosmeticFilters === true || append && elem.checked;
- elem.checked = listDetails.ignoreGenericCosmeticFilters = checked;
+ elem.checked = listsetDetails.ignoreGenericCosmeticFilters = checked;
const selectedSet = new Set(data.selectedLists);
- for ( const listEntry of qsa$('#lists .listEntry') ) {
- const listKey = dom.attr(listEntry, 'data-listkey');
- const hasListKey = selectedSet.has(listKey);
- selectedSet.delete(listKey);
- const input = qs$(listEntry, 'input');
- if ( append && input.checked ) { continue; }
- input.checked = hasListKey;
+ for ( const listEntry of qsa$('#lists .listEntry[data-role="leaf"]') ) {
+ const listkey = listEntry.dataset.key;
+ const mustEnable = selectedSet.has(listkey);
+ selectedSet.delete(listkey);
+ if ( mustEnable === false && append ) { continue; }
+ toggleFilterList(listEntry, mustEnable);
}
// If there are URL-like list keys left in the selected set, import them.
- for ( const listKey of selectedSet ) {
- if ( reValidExternalList.test(listKey) === false ) {
- selectedSet.delete(listKey);
- }
+ for ( const listkey of selectedSet ) {
+ if ( reValidExternalList.test(listkey) ) { continue; }
+ selectedSet.delete(listkey);
}
if ( selectedSet.size !== 0 ) {
- elem = qs$('#externalLists');
- if ( append ) {
- if ( elem.value.trim() !== '' ) { elem.value += '\n'; }
- } else {
- elem.value = '';
- }
- elem.value += Array.from(selectedSet).join('\n');
- qs$('#importLists').checked = true;
+ const textarea = qs$('#lists .liEntry[data-role="import"] textarea');
+ const lines = append
+ ? textarea.value.split(/[\n\r]+/)
+ : [];
+ lines.push(...selectedSet);
+ if ( lines.length !== 0 ) { lines.push(''); }
+ textarea.value = lines.join('\n');
+ dom.cl.toggle('#lists .liEntry[data-role="import"]', 'expanded', textarea.value !== '');
}
- revealHiddenUsedLists();
renderWidgets();
};
-self.cloud.onPush = toCloudData;
-self.cloud.onPull = fromCloudData;
-
/******************************************************************************/
self.hasUnsavedData = function() {
@@ -655,24 +800,6 @@ self.hasUnsavedData = function() {
/******************************************************************************/
-dom.on('#autoUpdate', 'change', userSettingCheckboxChanged);
-dom.on('#parseCosmeticFilters', 'change', onFilteringSettingsChanged);
-dom.on('#ignoreGenericCosmeticFilters', 'change', onFilteringSettingsChanged);
-dom.on('#suspendUntilListsAreLoaded', 'change', userSettingCheckboxChanged);
-dom.on('#buttonApply', 'click', ( ) => { buttonApplyHandler(); });
-dom.on('#buttonUpdate', 'click', ( ) => { buttonUpdateHandler(); });
-dom.on('#buttonPurgeAll', 'click', ev => { buttonPurgeAllHandler(ev.shiftKey); });
-dom.on('#lists', 'change', '.listEntry input', onListsetChanged);
-dom.on('#lists', 'click', '.listEntry .remove', onRemoveExternalList);
-dom.on('#lists', 'click', 'span.cache', onPurgeClicked);
-dom.on('#externalLists', 'input', onFilteringSettingsChanged);
-dom.on('#lists','click', '.listEntry label *', ev => {
- if ( ev.target.matches('a,input,.forinput') ) { return; }
- ev.preventDefault();
-});
-
-/******************************************************************************/
-
renderFilterLists();
/******************************************************************************/
diff --git a/src/js/dom.js b/src/js/dom.js
index 809f3dcbdb8fc..7db3a2eaa5cc7 100644
--- a/src/js/dom.js
+++ b/src/js/dom.js
@@ -70,8 +70,18 @@ class dom {
}
}
+ static clear(target) {
+ for ( const elem of normalizeTarget(target) ) {
+ while ( elem.firstChild !== null ) {
+ elem.removeChild(elem.firstChild);
+ }
+ }
+ }
+
static clone(target) {
- return normalizeTarget(target)[0].cloneNode(true);
+ const elements = normalizeTarget(target);
+ if ( elements.length === 0 ) { return null; }
+ return elements[0].cloneNode(true);
}
static create(a) {
@@ -80,6 +90,13 @@ class dom {
}
}
+ static prop(target, prop, value = undefined) {
+ for ( const elem of normalizeTarget(target) ) {
+ if ( value === undefined ) { return elem[prop]; }
+ elem[prop] = value;
+ }
+ }
+
static text(target, text) {
const targets = normalizeTarget(target);
if ( text === undefined ) {
@@ -174,6 +191,7 @@ function qs$(a, b) {
if ( typeof a === 'string') {
return document.querySelector(a);
}
+ if ( a === null ) { return null; }
return a.querySelector(b);
}
@@ -181,6 +199,7 @@ function qsa$(a, b) {
if ( typeof a === 'string') {
return document.querySelectorAll(a);
}
+ if ( a === null ) { return []; }
return a.querySelectorAll(b);
}
diff --git a/src/js/i18n.js b/src/js/i18n.js
index 59000de2ab671..85d350f58faed 100644
--- a/src/js/i18n.js
+++ b/src/js/i18n.js
@@ -255,10 +255,9 @@ if ( isBackgroundProcess !== true ) {
}
for ( const elem of root.querySelectorAll('[placeholder]') ) {
- elem.setAttribute(
- 'placeholder',
- i18n$(elem.getAttribute('placeholder'))
- );
+ const text = i18n$(elem.getAttribute('placeholder'));
+ if ( text === '' ) { continue; }
+ elem.setAttribute('placeholder', text);
}
for ( const elem of root.querySelectorAll('[data-i18n-tip]') ) {
diff --git a/src/js/messaging.js b/src/js/messaging.js
index 5ff68c09e4063..29339ac19224e 100644
--- a/src/js/messaging.js
+++ b/src/js/messaging.js
@@ -1616,9 +1616,11 @@ const onMessage = function(request, sender, callback) {
}
break;
- case 'purgeCache':
- io.purge(request.assetKey);
- io.remove('compiled/' + request.assetKey);
+ case 'purgeCaches':
+ for ( const assetKey of request.assetKeys ) {
+ io.purge(assetKey);
+ io.remove(`compiled/${assetKey}`);
+ }
break;
case 'readHiddenSettings':