mirror of https://github.com/gorhill/uBlock.git
[mv3] Re-work dashboard: move list of rulesets in its own pane
Related issue: https://github.com/uBlockOrigin/uBOL-home/issues/229 Add "Filter lists" pane in dashboard The DNR API now supports enabling 50 static rulesets put of a maximum of 100 (instead of 10 out of 50 originally). Thus given the potentially growing number of static rulesets, the available stock rulesets has been moved to its own pane, with the following improvements: - Support sublists - Support search Aditionally, "RU AdList: Counter" has been added as a stock ruleset. Other changes: - Do not re-evaluate regexes which failed validation - Better reduce `removeparam` rules
This commit is contained in:
parent
b4a5b411b5
commit
ae4754415c
|
@ -33,6 +33,7 @@
|
|||
}
|
||||
|
||||
body[data-pane="settings"] #dashboard-nav .tabButton[data-pane="settings"],
|
||||
body[data-pane="rulesets"] #dashboard-nav .tabButton[data-pane="rulesets"],
|
||||
body[data-pane="about"] #dashboard-nav .tabButton[data-pane="about"] {
|
||||
background-color: var(--dashboard-tab-active-surface);
|
||||
border-bottom: 3px solid var(--dashboard-tab-active-ink);
|
||||
|
@ -44,6 +45,7 @@ body > section {
|
|||
display: none;
|
||||
}
|
||||
body[data-pane="settings"] > section[data-pane="settings"],
|
||||
body[data-pane="rulesets"] > section[data-pane="rulesets"],
|
||||
body[data-pane="about"] > section[data-pane="about"] {
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -79,92 +79,101 @@ h3[data-i18n="filteringMode0Name"]::first-letter {
|
|||
}
|
||||
|
||||
#lists {
|
||||
margin: 0.5em 0 0 0;
|
||||
padding: 0;
|
||||
padding-block-end: 8rem;
|
||||
}
|
||||
.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;
|
||||
.listEntry {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.groupEntry.hideUnused:not([data-groupkey="user"]) .geDetails::before {
|
||||
content: '+';
|
||||
.listEntry[data-nodeid] > .detailbar .listExpander {
|
||||
cursor: pointer;
|
||||
top: 2px;
|
||||
}
|
||||
.groupEntry {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.groupEntry .geDetails {
|
||||
.listEntry[data-role="rootnode"] > .detailbar,
|
||||
.listEntry[data-nodeid] > .detailbar .count {
|
||||
cursor: pointer;
|
||||
}
|
||||
.groupEntry .geName {
|
||||
.listEntry[data-role="rootnode"] > .detailbar > *:not(.listExpander) {
|
||||
pointer-events: none;
|
||||
}
|
||||
.groupEntry .geCount {
|
||||
.listEntry .detailbar .count {
|
||||
align-self: flex-end;
|
||||
color: var(--ink-3);
|
||||
font-size: 90%;
|
||||
font-size: small;
|
||||
pointer-events: none;
|
||||
}
|
||||
.listEntries {
|
||||
margin-inline-start: 0.6em;
|
||||
-webkit-margin-start: 0.6em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.groupEntry:not([data-groupkey="user"]) .listEntry:not(.isDefault).unused {
|
||||
.listEntry:not([data-role="rootnode"]) > .listEntries {
|
||||
margin-inline-start: var(--checkbox-size);
|
||||
}
|
||||
.listEntry.hideUnused > .listEntries > .listEntry:not(.isDefault):has(> .detailbar input:not(:checked)) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.listEntry.fromAdmin:has(input[disabled]:not(:checked)) {
|
||||
display: none;
|
||||
}
|
||||
.listEntry > * {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
unicode-bidi: embed;
|
||||
}
|
||||
.listEntry h3 {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
}
|
||||
.listEntry > .detailbar {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
margin: calc(var(--default-gap-xsmall) / 2 + var(--default-gap-xxsmall) / 2) 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.listEntry > .detailbar > *:not(:first-child) {
|
||||
margin-inline-start: var(--default-gap-xxsmall);
|
||||
}
|
||||
.listEntry[data-nodeid="default"] > .detailbar > .listExpander {
|
||||
display: none;
|
||||
}
|
||||
.listEntry > .detailbar > .listExpander svg {
|
||||
transform: rotate(180deg);
|
||||
transform-origin: 50%;
|
||||
}
|
||||
.listEntry.hideUnused > .detailbar > .listExpander svg {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.listEntry .checkbox:has(input[disabled]),
|
||||
.listEntry .checkbox:has(input[disabled]) ~ span {
|
||||
filter: var(--checkbox-disabled-filter);
|
||||
}
|
||||
.listEntry .listname {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.listEntry a,
|
||||
.listEntry .fa-icon {
|
||||
color: var(--info0-ink);
|
||||
fill: var(--info0-ink);
|
||||
display: none;
|
||||
font-size: 120%;
|
||||
margin: 0 0.2em 0 0;
|
||||
}
|
||||
.listEntry .fa-icon:hover {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
.listEntry .content {
|
||||
display: inline-flex;
|
||||
}
|
||||
.listEntry a.towiki {
|
||||
display: inline-flex;
|
||||
}
|
||||
.listEntry.support a.support {
|
||||
display: inline-flex;
|
||||
}
|
||||
.listEntry.mustread a.mustread {
|
||||
color: var(--info1-ink);
|
||||
fill: var(--info1-ink);
|
||||
display: inline-flex;
|
||||
}
|
||||
.listEntry .status {
|
||||
cursor: default;
|
||||
.listEntry .iconbar a.support[href="#"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
body.noMoreRuleset .listEntry:not(.checked) {
|
||||
body.noMoreRuleset .listEntry:has(> .detailbar input:not(:checked)) {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#lists.searchMode > .listEntries .listEntries,
|
||||
#lists.searchMode > .listEntries .listEntry.searchMatch {
|
||||
display: flex !important;
|
||||
}
|
||||
#lists.searchMode > .listEntries .listEntry {
|
||||
display: none;
|
||||
}
|
||||
#lists.searchMode > .listEntries .listExpander {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* touch-screen devices */
|
||||
:root.mobile .listEntry .fa-icon {
|
||||
font-size: 120%;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
<div id="dashboard-nav">
|
||||
<span class="logo"><img data-i18n-title="extName" src="img/ublock.svg" alt="uBO Lite"></span><!--
|
||||
--><button class="tabButton" type="button" data-pane="settings" data-i18n="settingsPageName" tabindex="0"></button><!--
|
||||
--><button class="tabButton" type="button" data-pane="rulesets" data-i18n="aboutFilterLists" tabindex="0"></button><!--
|
||||
--><button class="tabButton" type="button" data-pane="about" data-i18n="aboutPageName" tabindex="0"></button>
|
||||
</div>
|
||||
<!-- -------- -->
|
||||
|
@ -95,29 +96,14 @@
|
|||
<p><textarea id="trustedSites" spellcheck="false" placeholder="noFilteringModePlaceholder"></textarea>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<!-- -------- -->
|
||||
<section data-pane="rulesets">
|
||||
<div>
|
||||
<h3 data-i18n="aboutFilterLists"></h3>
|
||||
<div>
|
||||
<p id="listsOfBlockedHostsPrompt"></p>
|
||||
</div>
|
||||
<div>
|
||||
<div id="lists"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="templates">
|
||||
<div class="groupEntry">
|
||||
<div class="geDetails"><span class="geName"></span> <span class="geCount"></span></div>
|
||||
<div class="listEntries"></div>
|
||||
</div>
|
||||
<div class="li listEntry">
|
||||
<label><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span><span class="listname forinput"></span> <span class="iconbar"><!--
|
||||
--><a class="fa-icon support" href="#" target="_blank">home</a><!--
|
||||
--><a class="fa-icon mustread" href="#" target="_blank">info-circle</a><!--
|
||||
--></span></span></label>
|
||||
</div>
|
||||
<p id="listsOfBlockedHostsPrompt"></p>
|
||||
</div>
|
||||
<div class="searchfield"><input type="search" spellcheck="false" placeholder="" /><span class="fa-icon">search</span></div>
|
||||
<div id="lists"></div>
|
||||
</section>
|
||||
<!-- -------- -->
|
||||
<section data-pane="about">
|
||||
|
@ -146,6 +132,32 @@
|
|||
</div>
|
||||
</section>
|
||||
<!-- -------- -->
|
||||
<div id="templates">
|
||||
<div class="listEntries"></div>
|
||||
<div class="listEntry" data-role="leaf">
|
||||
<span class="detailbar">
|
||||
<label><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span class="listname forinput"></span>
|
||||
</label>
|
||||
<span class="iconbar"><!--
|
||||
--><a class="fa-icon support" href="#" target="_blank">home</a>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="listEntry expandable" data-role="node">
|
||||
<span class="detailbar">
|
||||
<label><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span class="listname forinput"></span></label>
|
||||
<span class="count"></span>
|
||||
<span class="fa-icon listExpander">angle-up</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="listEntry expandable" data-role="rootnode">
|
||||
<span class="detailbar">
|
||||
<h3 class="listname"></h3>
|
||||
<span class="count"></span>
|
||||
<span class="fa-icon listExpander">angle-up</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/theme.js" type="module"></script>
|
||||
<script src="js/fa-icons.js" type="module"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
|
|
|
@ -0,0 +1,402 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
|
||||
Copyright (C) 2014-present Raymond Hill
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
|
||||
Home: https://github.com/gorhill/uBlock
|
||||
*/
|
||||
|
||||
import { dom, qs$, qsa$ } from './dom.js';
|
||||
import { i18n, i18n$ } from './i18n.js';
|
||||
import { localRead, localWrite, sendMessage } from './ext.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export const rulesetMap = new Map();
|
||||
|
||||
let cachedRulesetData = {};
|
||||
let hideUnusedSet = new Set([ 'regions' ]);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function renderNumber(value) {
|
||||
return value.toLocaleString();
|
||||
}
|
||||
|
||||
function renderRuleCounts() {
|
||||
let rulesetCount = 0;
|
||||
let filterCount = 0;
|
||||
let ruleCount = 0;
|
||||
for ( const liEntry of qsa$('#lists .listEntry[data-role="leaf"][data-rulesetid]') ) {
|
||||
if ( qs$(liEntry, 'input[type="checkbox"]:checked') === null ) { continue; }
|
||||
rulesetCount += 1;
|
||||
const stats = rulesetStats(liEntry.dataset.rulesetid);
|
||||
if ( stats === undefined ) { continue; }
|
||||
ruleCount += stats.ruleCount;
|
||||
filterCount += stats.filterCount;
|
||||
}
|
||||
dom.text('#listsOfBlockedHostsPrompt', i18n$('perRulesetStats')
|
||||
.replace('{{ruleCount}}', ruleCount.toLocaleString())
|
||||
.replace('{{filterCount}}', filterCount.toLocaleString())
|
||||
);
|
||||
|
||||
dom.cl.toggle(dom.body, 'noMoreRuleset',
|
||||
rulesetCount === cachedRulesetData.maxNumberOfEnabledRulesets
|
||||
);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function updateNodes(listEntries) {
|
||||
listEntries = listEntries || qs$('#lists');
|
||||
for ( const listEntry of qsa$(listEntries, '.listEntry[data-nodeid]') ) {
|
||||
const totalCount = qsa$(listEntry, '.listEntry[data-rulesetid] input').length;
|
||||
const checkedCount = qsa$(listEntry, '.listEntry[data-rulesetid] input:checked').length;
|
||||
dom.text(qs$(listEntry, '.detailbar .count'), `${checkedCount}/${totalCount}`);
|
||||
const checkbox = qs$(listEntry, ':scope > .detailbar .checkbox');
|
||||
if ( checkbox === null ) { continue; }
|
||||
dom.prop(qs$(checkbox, 'input'), 'checked', checkedCount !== 0);
|
||||
dom.cl.toggle(checkbox, 'partial',
|
||||
checkedCount !== 0 && checkedCount !== totalCount
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function rulesetStats(rulesetId) {
|
||||
const hasOmnipotence = cachedRulesetData.defaultFilteringMode > 1;
|
||||
const rulesetDetails = rulesetMap.get(rulesetId);
|
||||
if ( rulesetDetails === undefined ) { return; }
|
||||
const { rules, filters } = rulesetDetails;
|
||||
let ruleCount = rules.plain + rules.regex;
|
||||
if ( hasOmnipotence ) {
|
||||
ruleCount += rules.removeparam + rules.redirect + rules.modifyHeaders;
|
||||
}
|
||||
const filterCount = filters.accepted;
|
||||
return { ruleCount, filterCount };
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function isAdminRuleset(listkey) {
|
||||
const { adminRulesets = [] } = cachedRulesetData;
|
||||
for ( const id of adminRulesets ) {
|
||||
const pos = id.indexOf(listkey);
|
||||
if ( pos === 0 ) { return true; }
|
||||
if ( pos !== 1 ) { continue; }
|
||||
const c = id.charAt(0);
|
||||
if ( c === '+' || c === '-' ) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export function renderFilterLists(rulesetData) {
|
||||
cachedRulesetData = rulesetData;
|
||||
const { enabledRulesets, rulesetDetails } = cachedRulesetData;
|
||||
|
||||
const shouldUpdate = rulesetMap.size !== 0;
|
||||
|
||||
rulesetDetails.forEach(rule => rulesetMap.set(rule.id, rule));
|
||||
|
||||
const listStatsTemplate = i18n$('perRulesetStats');
|
||||
|
||||
const initializeListEntry = (ruleset, listEntry) => {
|
||||
const on = enabledRulesets.includes(ruleset.id);
|
||||
dom.prop(qs$(listEntry, ':scope > .detailbar input'), 'checked', on);
|
||||
if ( ruleset.homeURL ) {
|
||||
dom.attr(qs$(listEntry, 'a.support'), 'href', ruleset.homeURL);
|
||||
}
|
||||
dom.cl.toggle(listEntry, 'isDefault', ruleset.id === 'default');
|
||||
const stats = rulesetStats(ruleset.id);
|
||||
listEntry.title = listStatsTemplate
|
||||
.replace('{{ruleCount}}', renderNumber(stats.ruleCount))
|
||||
.replace('{{filterCount}}', renderNumber(stats.filterCount));
|
||||
const fromAdmin = isAdminRuleset(ruleset.id);
|
||||
dom.cl.toggle(listEntry, 'fromAdmin', fromAdmin);
|
||||
const disabled = stats.ruleCount === 0 || fromAdmin;
|
||||
dom.attr(
|
||||
qs$(listEntry, '.input.checkbox input'),
|
||||
'disabled',
|
||||
disabled ? '' : null
|
||||
);
|
||||
return listEntry;
|
||||
};
|
||||
|
||||
// Update already rendered DOM lists
|
||||
if ( shouldUpdate ) {
|
||||
for ( const listEntry of qsa$('#lists .listEntry[data-rulesetid]') ) {
|
||||
const rulesetid = listEntry.dataset.rulesetid;
|
||||
const ruleset = rulesetMap.get(rulesetid);
|
||||
initializeListEntry(ruleset, listEntry);
|
||||
}
|
||||
updateNodes();
|
||||
renderRuleCounts();
|
||||
return;
|
||||
}
|
||||
|
||||
const createListEntry = (listDetails, depth) => {
|
||||
if ( listDetails.lists === undefined ) {
|
||||
return dom.clone('#templates .listEntry[data-role="leaf"]');
|
||||
}
|
||||
if ( depth !== 0 ) {
|
||||
return dom.clone('#templates .listEntry[data-role="node"]');
|
||||
}
|
||||
return dom.clone('#templates .listEntry[data-role="rootnode"]');
|
||||
};
|
||||
|
||||
const createListEntries = (parentkey, listTree, depth = 0) => {
|
||||
const listEntries = dom.clone('#templates .listEntries');
|
||||
const treeEntries = Object.entries(listTree);
|
||||
if ( depth !== 0 ) {
|
||||
const reEmojis = /\p{Emoji}+/gu;
|
||||
treeEntries.sort((a ,b) => {
|
||||
const ap = a[1].preferred === true;
|
||||
const bp = b[1].preferred === true;
|
||||
if ( ap !== bp ) { return ap ? -1 : 1; }
|
||||
const as = (a[1].title || a[0]).replace(reEmojis, '');
|
||||
const bs = (b[1].title || b[0]).replace(reEmojis, '');
|
||||
return as.localeCompare(bs);
|
||||
});
|
||||
}
|
||||
for ( const [ listkey, listDetails ] of treeEntries ) {
|
||||
const listEntry = createListEntry(listDetails, depth);
|
||||
if ( listDetails.lists === undefined ) {
|
||||
listEntry.dataset.rulesetid = listkey;
|
||||
} else {
|
||||
listEntry.dataset.nodeid = listkey;
|
||||
dom.cl.toggle(listEntry, 'hideUnused', hideUnusedSet.has(listkey));
|
||||
}
|
||||
qs$(listEntry, ':scope > .detailbar .listname').append(
|
||||
i18n.patchUnicodeFlags(listDetails.name)
|
||||
);
|
||||
if ( listDetails.lists !== undefined ) {
|
||||
listEntry.append(createListEntries(listkey, listDetails.lists, depth+1));
|
||||
dom.cl.toggle(listEntry, 'expanded', true/*listIsExpanded(listkey)*/);
|
||||
//updateListNode(listEntry);
|
||||
} else {
|
||||
initializeListEntry(listDetails, listEntry);
|
||||
}
|
||||
listEntries.append(listEntry);
|
||||
}
|
||||
return listEntries;
|
||||
};
|
||||
|
||||
// Visually split the filter lists in groups
|
||||
const groups = new Map([
|
||||
[
|
||||
'default',
|
||||
rulesetDetails.filter(ruleset =>
|
||||
ruleset.id === 'default'
|
||||
),
|
||||
], [
|
||||
'annoyances',
|
||||
rulesetDetails.filter(ruleset =>
|
||||
ruleset.group === 'annoyances'
|
||||
),
|
||||
], [
|
||||
'misc',
|
||||
rulesetDetails.filter(ruleset =>
|
||||
ruleset.id !== 'default' &&
|
||||
ruleset.group === undefined &&
|
||||
typeof ruleset.lang !== 'string'
|
||||
),
|
||||
], [
|
||||
'regions',
|
||||
rulesetDetails.filter(ruleset =>
|
||||
ruleset.group === 'regions'
|
||||
),
|
||||
],
|
||||
]);
|
||||
|
||||
dom.cl.toggle(dom.body, 'hideUnused', mustHideUnusedLists('*'));
|
||||
|
||||
// Build list tree
|
||||
const listTree = {};
|
||||
const groupNames = new Map();
|
||||
for ( const [ groupKey, groupRulesets ] of groups ) {
|
||||
let groupName = groupNames.get(groupKey);
|
||||
if ( groupName === undefined ) {
|
||||
groupName = i18n$('3pGroup' + groupKey.charAt(0).toUpperCase() + groupKey.slice(1));
|
||||
groupNames.set(groupKey, groupName);
|
||||
}
|
||||
const groupDetails = {
|
||||
name: groupName,
|
||||
lists: {},
|
||||
};
|
||||
listTree[groupKey] = groupDetails;
|
||||
for ( const ruleset of groupRulesets ) {
|
||||
if ( ruleset.parent !== undefined ) {
|
||||
let lists = groupDetails.lists;
|
||||
for ( const parent of ruleset.parent.split('|') ) {
|
||||
if ( lists[parent] === undefined ) {
|
||||
lists[parent] = { name: parent, lists: {} };
|
||||
}
|
||||
lists = lists[parent].lists;
|
||||
}
|
||||
lists[ruleset.id] = ruleset;
|
||||
} else {
|
||||
groupDetails.lists[ruleset.id] = ruleset;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Move lonely sublist to list level
|
||||
const promoteLonelySublist = (parent, depth = 0) => {
|
||||
if ( Boolean(parent.lists) === false ) { return parent; }
|
||||
const childKeys = Object.keys(parent.lists);
|
||||
for ( const childKey of childKeys ) {
|
||||
const child = promoteLonelySublist(parent.lists[childKey], depth + 1);
|
||||
if ( child === parent.lists[childKey] ) { continue; }
|
||||
parent.lists[child.id] = child;
|
||||
delete parent.lists[childKey];
|
||||
}
|
||||
if ( depth === 0 ) { return parent; }
|
||||
if ( childKeys.length > 1 ) { return parent; }
|
||||
return parent.lists[childKeys[0]]
|
||||
};
|
||||
for ( const key of Object.keys(listTree) ) {
|
||||
promoteLonelySublist(listTree[key]);
|
||||
}
|
||||
const listEntries = createListEntries('root', listTree);
|
||||
|
||||
updateNodes(listEntries);
|
||||
|
||||
dom.clear('#lists');
|
||||
qs$('#lists').append(listEntries);
|
||||
|
||||
renderRuleCounts();
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Collapsing of unused lists.
|
||||
|
||||
function mustHideUnusedLists(which) {
|
||||
const hideAll = hideUnusedSet.has('*');
|
||||
if ( which === '*' ) { return hideAll; }
|
||||
return hideUnusedSet.has(which) !== hideAll;
|
||||
}
|
||||
|
||||
function toggleHideUnusedLists(which) {
|
||||
const doesHideAll = hideUnusedSet.has('*');
|
||||
if ( which === '*' ) {
|
||||
const mustHide = doesHideAll === false;
|
||||
hideUnusedSet.clear();
|
||||
if ( mustHide ) {
|
||||
hideUnusedSet.add(which);
|
||||
}
|
||||
dom.cl.toggle('#lists', 'hideUnused', mustHide);
|
||||
dom.cl.toggle('.listEntry[data-nodeid]', 'hideUnused', mustHide);
|
||||
} else {
|
||||
const doesHide = hideUnusedSet.has(which);
|
||||
if ( doesHide ) {
|
||||
hideUnusedSet.delete(which);
|
||||
} else {
|
||||
hideUnusedSet.add(which);
|
||||
}
|
||||
const mustHide = doesHide === doesHideAll;
|
||||
const groupSelector = `.listEntry[data-nodeid="${which}"]`;
|
||||
dom.cl.toggle(groupSelector, 'hideUnused', mustHide);
|
||||
}
|
||||
|
||||
localWrite('hideUnusedFilterLists', Array.from(hideUnusedSet));
|
||||
}
|
||||
|
||||
dom.on('#lists', 'click', '.listEntry[data-nodeid] > .detailbar, .listExpander', ev => {
|
||||
toggleHideUnusedLists(
|
||||
dom.attr(ev.target.closest('[data-nodeid]'), 'data-nodeid')
|
||||
);
|
||||
});
|
||||
|
||||
// Initialize from saved state.
|
||||
localRead('hideUnusedFilterLists').then(value => {
|
||||
if ( Array.isArray(value) === false ) { return; }
|
||||
hideUnusedSet = new Set(value);
|
||||
for ( const listEntry of qsa$('[data-nodeid]') ) {
|
||||
dom.cl.toggle(listEntry, 'hideUnused',
|
||||
hideUnusedSet.has(listEntry.dataset.nodeid)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const searchFilterLists = ( ) => {
|
||||
const pattern = dom.prop('.searchfield input', 'value') || '';
|
||||
dom.cl.toggle('#lists', 'searchMode', pattern !== '');
|
||||
if ( pattern === '' ) { return; }
|
||||
const re = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
|
||||
for ( const listEntry of qsa$('#lists [data-role="leaf"]') ) {
|
||||
const rulesetid = listEntry.dataset.rulesetid;
|
||||
const rulesetDetails = rulesetMap.get(rulesetid);
|
||||
if ( rulesetDetails === undefined ) { continue; }
|
||||
let haystack = perListHaystack.get(rulesetDetails);
|
||||
if ( haystack === undefined ) {
|
||||
haystack = [
|
||||
rulesetDetails.name,
|
||||
listEntry.dataset.nodeid,
|
||||
rulesetDetails.tags || '',
|
||||
].join(' ').trim();
|
||||
perListHaystack.set(rulesetDetails, haystack);
|
||||
}
|
||||
dom.cl.toggle(listEntry, 'searchMatch', re.test(haystack));
|
||||
}
|
||||
for ( const listEntry of qsa$('#lists .listEntry:not([data-role="leaf"])') ) {
|
||||
dom.cl.toggle(listEntry, 'searchMatch',
|
||||
qs$(listEntry, '.listEntries .listEntry.searchMatch') !== null
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const perListHaystack = new WeakMap();
|
||||
|
||||
dom.on('.searchfield input', 'input', searchFilterLists);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function applyEnabledRulesets() {
|
||||
const enabledRulesets = [];
|
||||
for ( const liEntry of qsa$('#lists .listEntry[data-role="leaf"][data-rulesetid]') ) {
|
||||
const checked = qs$(liEntry, 'input[type="checkbox"]:checked') !== null;
|
||||
if ( checked === false ) { continue; }
|
||||
const { rulesetid } = liEntry.dataset;
|
||||
if ( dom.cl.has(liEntry, 'fromAdmin') ) { continue; }
|
||||
enabledRulesets.push(rulesetid);
|
||||
}
|
||||
|
||||
await sendMessage({
|
||||
what: 'applyRulesets',
|
||||
enabledRulesets,
|
||||
});
|
||||
}
|
||||
|
||||
dom.on('#lists', 'change', '.listEntry input[type="checkbox"]', ev => {
|
||||
const input = ev.target;
|
||||
const listEntry = input.closest('.listEntry');
|
||||
if ( listEntry === null ) { return; }
|
||||
if ( listEntry.dataset.nodeid !== undefined ) {
|
||||
let checkAll = input.checked ||
|
||||
dom.cl.has(qs$(listEntry, ':scope > .detailbar .checkbox'), 'partial');
|
||||
for ( const input of qsa$(listEntry, '.listEntries input') ) {
|
||||
input.checked = checkAll;
|
||||
}
|
||||
}
|
||||
renderRuleCounts();
|
||||
updateNodes();
|
||||
applyEnabledRulesets();
|
||||
});
|
|
@ -101,9 +101,14 @@ async function pruneInvalidRegexRules(realm, rulesIn) {
|
|||
toCheck.push(true);
|
||||
continue;
|
||||
}
|
||||
if ( pruneInvalidRegexRules.invalidRegexes.has(regex) ) {
|
||||
toCheck.push(false);
|
||||
continue;
|
||||
}
|
||||
toCheck.push(
|
||||
dnr.isRegexSupported({ regex, isCaseSensitive }).then(result => {
|
||||
if ( result.isSupported ) { return true; }
|
||||
pruneInvalidRegexRules.invalidRegexes.add(regex);
|
||||
rejectedRegexRules.push(`\t${regex} ${result.reason}`);
|
||||
return false;
|
||||
})
|
||||
|
@ -122,6 +127,7 @@ async function pruneInvalidRegexRules(realm, rulesIn) {
|
|||
|
||||
return rulesIn.filter((v, i) => isValid[i]);
|
||||
}
|
||||
pruneInvalidRegexRules.invalidRegexes = new Set();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
|
@ -19,201 +19,21 @@
|
|||
Home: https://github.com/gorhill/uBlock
|
||||
*/
|
||||
|
||||
import { browser, localRead, localWrite, sendMessage } from './ext.js';
|
||||
import { dom, qs$, qsa$ } from './dom.js';
|
||||
import { i18n, i18n$ } from './i18n.js';
|
||||
import { browser, sendMessage } from './ext.js';
|
||||
import { dom, qs$ } from './dom.js';
|
||||
import punycode from './punycode.js';
|
||||
import { renderFilterLists } from './filter-lists.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const rulesetMap = new Map();
|
||||
let cachedRulesetData = {};
|
||||
let hideUnusedSet = new Set([ 'regions' ]);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function renderNumber(value) {
|
||||
return value.toLocaleString();
|
||||
}
|
||||
|
||||
function hashFromIterable(iter) {
|
||||
return Array.from(iter).sort().join('\n');
|
||||
}
|
||||
|
||||
function isAdminRuleset(listkey) {
|
||||
const { adminRulesets = [] } = cachedRulesetData;
|
||||
for ( const id of adminRulesets ) {
|
||||
const pos = id.indexOf(listkey);
|
||||
if ( pos === 0 ) { return true; }
|
||||
if ( pos !== 1 ) { continue; }
|
||||
const c = id.charAt(0);
|
||||
if ( c === '+' || c === '-' ) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function rulesetStats(rulesetId) {
|
||||
const hasOmnipotence = cachedRulesetData.defaultFilteringMode > 1;
|
||||
const rulesetDetails = rulesetMap.get(rulesetId);
|
||||
if ( rulesetDetails === undefined ) { return; }
|
||||
const { rules, filters } = rulesetDetails;
|
||||
let ruleCount = rules.plain + rules.regex;
|
||||
if ( hasOmnipotence ) {
|
||||
ruleCount += rules.removeparam + rules.redirect + rules.modifyHeaders;
|
||||
}
|
||||
const filterCount = filters.accepted;
|
||||
return { ruleCount, filterCount };
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function renderFilterLists() {
|
||||
const { enabledRulesets, rulesetDetails } = cachedRulesetData;
|
||||
const listGroupTemplate = qs$('#templates .groupEntry');
|
||||
const listEntryTemplate = qs$('#templates .listEntry');
|
||||
const listStatsTemplate = i18n$('perRulesetStats');
|
||||
const groupNames = new Map([ [ 'user', '' ] ]);
|
||||
|
||||
const liFromListEntry = function(ruleset, li, hideUnused) {
|
||||
if ( !li ) {
|
||||
li = dom.clone(listEntryTemplate);
|
||||
}
|
||||
const on = enabledRulesets.includes(ruleset.id);
|
||||
dom.cl.toggle(li, 'checked', on);
|
||||
dom.cl.toggle(li, 'unused', hideUnused && !on);
|
||||
qs$(li, 'input[type="checkbox"]').checked = on;
|
||||
if ( dom.attr(li, 'data-listkey') !== ruleset.id ) {
|
||||
dom.attr(li, 'data-listkey', ruleset.id);
|
||||
qs$(li, '.listname').append(i18n.patchUnicodeFlags(ruleset.name));
|
||||
dom.cl.remove(li, 'toRemove');
|
||||
if ( ruleset.homeURL ) {
|
||||
dom.cl.add(li, 'support');
|
||||
dom.attr(qs$(li, 'a.support'), 'href', ruleset.homeURL);
|
||||
} else {
|
||||
dom.cl.remove(li, 'support');
|
||||
}
|
||||
if ( ruleset.instructionURL ) {
|
||||
dom.cl.add(li, 'mustread');
|
||||
dom.attr(qs$(li, 'a.mustread'), 'href', ruleset.instructionURL);
|
||||
} else {
|
||||
dom.cl.remove(li, 'mustread');
|
||||
}
|
||||
dom.cl.toggle(li, 'isDefault', ruleset.id === 'default');
|
||||
}
|
||||
const stats = rulesetStats(ruleset.id);
|
||||
li.title = listStatsTemplate
|
||||
.replace('{{ruleCount}}', renderNumber(stats.ruleCount))
|
||||
.replace('{{filterCount}}', renderNumber(stats.filterCount));
|
||||
const fromAdmin = isAdminRuleset(ruleset.id);
|
||||
dom.cl.toggle(li, 'fromAdmin', fromAdmin);
|
||||
const disabled = stats.ruleCount === 0 || fromAdmin;
|
||||
dom.attr(
|
||||
qs$(li, '.input.checkbox input'),
|
||||
'disabled',
|
||||
disabled ? '' : null
|
||||
);
|
||||
dom.cl.remove(li, 'discard');
|
||||
return li;
|
||||
};
|
||||
|
||||
const listEntryCountFromGroup = function(groupRulesets) {
|
||||
if ( Array.isArray(groupRulesets) === false ) { return ''; }
|
||||
let count = 0,
|
||||
total = 0;
|
||||
for ( const ruleset of groupRulesets ) {
|
||||
if ( enabledRulesets.includes(ruleset.id) ) {
|
||||
count += 1;
|
||||
}
|
||||
total += 1;
|
||||
}
|
||||
return total !== 0 ?
|
||||
`(${count.toLocaleString()}/${total.toLocaleString()})` :
|
||||
'';
|
||||
};
|
||||
|
||||
const liFromListGroup = function(groupKey, groupRulesets) {
|
||||
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 ( qs$(liGroup, '.geName:empty') === null ) {
|
||||
dom.text(
|
||||
qs$(liGroup, '.geCount'),
|
||||
listEntryCountFromGroup(groupRulesets)
|
||||
);
|
||||
}
|
||||
const hideUnused = mustHideUnusedLists(groupKey);
|
||||
dom.cl.toggle(liGroup, 'hideUnused', hideUnused);
|
||||
const ulGroup = qs$(liGroup, '.listEntries');
|
||||
if ( !groupRulesets ) { return liGroup; }
|
||||
groupRulesets.sort(function(a, b) {
|
||||
return (a.name || '').localeCompare(b.name || '');
|
||||
});
|
||||
for ( let i = 0; i < groupRulesets.length; i++ ) {
|
||||
const liEntry = liFromListEntry(
|
||||
groupRulesets[i],
|
||||
ulGroup.children[i],
|
||||
hideUnused
|
||||
);
|
||||
if ( liEntry.parentElement === null ) {
|
||||
ulGroup.appendChild(liEntry);
|
||||
}
|
||||
}
|
||||
return liGroup;
|
||||
};
|
||||
|
||||
// Visually split the filter lists in groups
|
||||
const ulLists = qs$('#lists');
|
||||
const groups = new Map([
|
||||
[
|
||||
'default',
|
||||
rulesetDetails.filter(ruleset =>
|
||||
ruleset.id === 'default'
|
||||
),
|
||||
],
|
||||
[
|
||||
'annoyances',
|
||||
rulesetDetails.filter(ruleset =>
|
||||
ruleset.group === 'annoyances'
|
||||
),
|
||||
],
|
||||
[
|
||||
'misc',
|
||||
rulesetDetails.filter(ruleset =>
|
||||
ruleset.id !== 'default' &&
|
||||
ruleset.group === undefined &&
|
||||
typeof ruleset.lang !== 'string'
|
||||
),
|
||||
],
|
||||
[
|
||||
'regions',
|
||||
rulesetDetails.filter(ruleset =>
|
||||
typeof ruleset.lang === 'string'
|
||||
),
|
||||
],
|
||||
]);
|
||||
|
||||
dom.cl.toggle(dom.body, 'hideUnused', mustHideUnusedLists('*'));
|
||||
|
||||
for ( const [ groupKey, groupRulesets ] of groups ) {
|
||||
const liGroup = liFromListGroup(groupKey, groupRulesets);
|
||||
dom.attr(liGroup, 'data-groupkey', groupKey);
|
||||
if ( liGroup.parentElement === null ) {
|
||||
ulLists.appendChild(liGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function renderWidgets() {
|
||||
|
@ -235,27 +55,6 @@ function renderWidgets() {
|
|||
dom.attr(input, 'disabled', '');
|
||||
}
|
||||
}
|
||||
|
||||
// Compute total counts
|
||||
let rulesetCount = 0;
|
||||
let filterCount = 0;
|
||||
let ruleCount = 0;
|
||||
for ( const liEntry of qsa$('#lists .listEntry[data-listkey]') ) {
|
||||
if ( qs$(liEntry, 'input[type="checkbox"]:checked') === null ) { continue; }
|
||||
rulesetCount += 1;
|
||||
const stats = rulesetStats(liEntry.dataset.listkey);
|
||||
if ( stats === undefined ) { continue; }
|
||||
ruleCount += stats.ruleCount;
|
||||
filterCount += stats.filterCount;
|
||||
}
|
||||
dom.text('#listsOfBlockedHostsPrompt', i18n$('perRulesetStats')
|
||||
.replace('{{ruleCount}}', ruleCount.toLocaleString())
|
||||
.replace('{{filterCount}}', filterCount.toLocaleString())
|
||||
);
|
||||
|
||||
dom.cl.toggle(dom.body, 'noMoreRuleset',
|
||||
rulesetCount === cachedRulesetData.maxNumberOfEnabledRulesets
|
||||
);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -300,7 +99,7 @@ async function onFilteringModeChange(ev) {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
renderFilterLists();
|
||||
renderFilterLists(cachedRulesetData);
|
||||
renderWidgets();
|
||||
}
|
||||
|
||||
|
@ -367,89 +166,6 @@ self.addEventListener('beforeunload', changeTrustedSites);
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
async function applyEnabledRulesets() {
|
||||
const enabledRulesets = [];
|
||||
for ( const liEntry of qsa$('#lists .listEntry[data-listkey]') ) {
|
||||
const checked = qs$(liEntry, 'input[type="checkbox"]:checked') !== null;
|
||||
dom.cl.toggle(liEntry, 'checked', checked);
|
||||
if ( checked === false ) { continue; }
|
||||
const { listkey } = liEntry.dataset;
|
||||
if ( isAdminRuleset(listkey) ) { continue; }
|
||||
enabledRulesets.push(listkey);
|
||||
}
|
||||
|
||||
await sendMessage({
|
||||
what: 'applyRulesets',
|
||||
enabledRulesets,
|
||||
});
|
||||
|
||||
renderWidgets();
|
||||
}
|
||||
|
||||
dom.on('#lists', 'change', '.listEntry input[type="checkbox"]', ( ) => {
|
||||
applyEnabledRulesets();
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Collapsing of unused lists.
|
||||
|
||||
function mustHideUnusedLists(which) {
|
||||
const hideAll = hideUnusedSet.has('*');
|
||||
if ( which === '*' ) { return hideAll; }
|
||||
return hideUnusedSet.has(which) !== hideAll;
|
||||
}
|
||||
|
||||
function toggleHideUnusedLists(which) {
|
||||
const doesHideAll = hideUnusedSet.has('*');
|
||||
let groupSelector;
|
||||
let mustHide;
|
||||
if ( which === '*' ) {
|
||||
mustHide = doesHideAll === false;
|
||||
groupSelector = '';
|
||||
hideUnusedSet.clear();
|
||||
if ( mustHide ) {
|
||||
hideUnusedSet.add(which);
|
||||
}
|
||||
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);
|
||||
} else {
|
||||
hideUnusedSet.add(which);
|
||||
}
|
||||
mustHide = doesHide === doesHideAll;
|
||||
groupSelector = `.groupEntry[data-groupkey="${which}"]`;
|
||||
dom.cl.toggle(groupSelector, 'hideUnused', mustHide);
|
||||
}
|
||||
|
||||
for ( const elem of qsa$(`#lists ${groupSelector} .listEntry[data-listkey] input[type="checkbox"]:not(:checked)`) ) {
|
||||
dom.cl.toggle(
|
||||
elem.closest('.listEntry[data-listkey]'),
|
||||
'unused',
|
||||
mustHide
|
||||
);
|
||||
}
|
||||
|
||||
localWrite('hideUnusedFilterLists', Array.from(hideUnusedSet));
|
||||
}
|
||||
|
||||
dom.on('#lists', 'click', '.groupEntry[data-groupkey] > .geDetails', ev => {
|
||||
toggleHideUnusedLists(
|
||||
dom.attr(ev.target.closest('[data-groupkey]'), 'data-groupkey')
|
||||
);
|
||||
});
|
||||
|
||||
// Initialize from saved state.
|
||||
localRead('hideUnusedFilterLists').then(value => {
|
||||
if ( Array.isArray(value) === false ) { return; }
|
||||
hideUnusedSet = new Set(value);
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function listen() {
|
||||
const bc = new self.BroadcastChannel('uBOL');
|
||||
bc.onmessage = listen.onmessage;
|
||||
|
@ -512,7 +228,7 @@ listen.onmessage = ev => {
|
|||
}
|
||||
|
||||
if ( render === false ) { return; }
|
||||
renderFilterLists();
|
||||
renderFilterLists(cachedRulesetData);
|
||||
renderWidgets();
|
||||
};
|
||||
|
||||
|
@ -523,10 +239,8 @@ sendMessage({
|
|||
}).then(data => {
|
||||
if ( !data ) { return; }
|
||||
cachedRulesetData = data;
|
||||
rulesetMap.clear();
|
||||
cachedRulesetData.rulesetDetails.forEach(rule => rulesetMap.set(rule.id, rule));
|
||||
try {
|
||||
renderFilterLists();
|
||||
renderFilterLists(cachedRulesetData);
|
||||
renderWidgets();
|
||||
} catch(ex) {
|
||||
}
|
||||
|
|
|
@ -1068,8 +1068,10 @@ async function rulesetFromURLs(assetDetails) {
|
|||
id: assetDetails.id,
|
||||
name: assetDetails.name,
|
||||
group: assetDetails.group,
|
||||
parent: assetDetails.parent,
|
||||
enabled: assetDetails.enabled,
|
||||
lang: assetDetails.lang,
|
||||
tags: assetDetails.tags,
|
||||
homeURL: assetDetails.homeURL,
|
||||
filters: {
|
||||
total: results.network.filterCount,
|
||||
|
@ -1121,7 +1123,7 @@ async function main() {
|
|||
|
||||
// Get assets.json content
|
||||
const assets = await fs.readFile(
|
||||
`./assets.json`,
|
||||
`./assets.dev.json`,
|
||||
{ encoding: 'utf8' }
|
||||
).then(text =>
|
||||
JSON.parse(text)
|
||||
|
@ -1155,55 +1157,6 @@ async function main() {
|
|||
],
|
||||
});
|
||||
|
||||
// Regional rulesets
|
||||
const excludedLists = [
|
||||
'ara-0',
|
||||
'EST-0',
|
||||
];
|
||||
// Merge lists which have same target languages
|
||||
const langToListsMap = new Map();
|
||||
for ( const [ id, asset ] of Object.entries(assets) ) {
|
||||
if ( asset.content !== 'filters' ) { continue; }
|
||||
if ( asset.off !== true ) { continue; }
|
||||
if ( typeof asset.lang !== 'string' ) { continue; }
|
||||
if ( excludedLists.includes(id) ) { continue; }
|
||||
let ids = langToListsMap.get(asset.lang);
|
||||
if ( ids === undefined ) {
|
||||
langToListsMap.set(asset.lang, ids = []);
|
||||
}
|
||||
ids.push(id);
|
||||
}
|
||||
for ( const ids of langToListsMap.values() ) {
|
||||
const urls = [];
|
||||
for ( const id of ids ) {
|
||||
const asset = assets[id];
|
||||
const contentURL = Array.isArray(asset.contentURL)
|
||||
? asset.contentURL[0]
|
||||
: asset.contentURL;
|
||||
urls.push(contentURL);
|
||||
}
|
||||
const id = ids[0];
|
||||
const asset = assets[id];
|
||||
await rulesetFromURLs({
|
||||
id: id.toLowerCase(),
|
||||
lang: asset.lang,
|
||||
name: asset.title,
|
||||
enabled: false,
|
||||
urls,
|
||||
homeURL: asset.supportURL,
|
||||
});
|
||||
}
|
||||
|
||||
await rulesetFromURLs({
|
||||
id: 'est-0',
|
||||
group: 'regions',
|
||||
lang: 'et',
|
||||
name: '🇪🇪ee: Eesti saitidele kohandatud filter',
|
||||
enabled: false,
|
||||
urls: [ 'https://ubol-et.adblock.ee/list.txt' ],
|
||||
homeURL: 'https://github.com/sander85/uBOL-et',
|
||||
});
|
||||
|
||||
// Handpicked rulesets from assets.json
|
||||
const handpicked = [
|
||||
'block-lan',
|
||||
|
@ -1290,6 +1243,62 @@ async function main() {
|
|||
homeURL: 'https://github.com/StevenBlack/hosts#readme',
|
||||
});
|
||||
|
||||
// Regional rulesets
|
||||
const excludedLists = [
|
||||
'ara-0',
|
||||
'EST-0',
|
||||
];
|
||||
// Merge lists which have same target languages
|
||||
const langToListsMap = new Map();
|
||||
for ( const [ id, asset ] of Object.entries(assets) ) {
|
||||
if ( asset.content !== 'filters' ) { continue; }
|
||||
if ( asset.off !== true ) { continue; }
|
||||
if ( asset.group !== 'regions' ) { continue; }
|
||||
if ( excludedLists.includes(id) ) { continue; }
|
||||
// Not all "regions" lists have a set language
|
||||
const bundleId = asset.lang ||
|
||||
createHash('sha256').update(randomBytes(16)).digest('hex').slice(0,16);
|
||||
let ids = langToListsMap.get(bundleId);
|
||||
if ( ids === undefined ) {
|
||||
langToListsMap.set(bundleId, ids = []);
|
||||
}
|
||||
ids.push(id);
|
||||
}
|
||||
for ( const ids of langToListsMap.values() ) {
|
||||
const urls = [];
|
||||
for ( const id of ids ) {
|
||||
const asset = assets[id];
|
||||
const contentURL = Array.isArray(asset.contentURL)
|
||||
? asset.contentURL[0]
|
||||
: asset.contentURL;
|
||||
urls.push(contentURL);
|
||||
}
|
||||
const id = ids[0];
|
||||
const asset = assets[id];
|
||||
const rulesetDetails = {
|
||||
id: id.toLowerCase(),
|
||||
group: 'regions',
|
||||
parent: asset.parent,
|
||||
lang: asset.lang,
|
||||
name: asset.title,
|
||||
tags: asset.tags,
|
||||
enabled: false,
|
||||
urls,
|
||||
homeURL: asset.supportURL,
|
||||
};
|
||||
await rulesetFromURLs(rulesetDetails);
|
||||
}
|
||||
|
||||
await rulesetFromURLs({
|
||||
id: 'est-0',
|
||||
group: 'regions',
|
||||
lang: 'et',
|
||||
name: '🇪🇪ee: Eesti saitidele kohandatud filter',
|
||||
enabled: false,
|
||||
urls: [ 'https://ubol-et.adblock.ee/list.txt' ],
|
||||
homeURL: 'https://github.com/sander85/uBOL-et',
|
||||
});
|
||||
|
||||
writeFile(
|
||||
`${rulesetDir}/ruleset-details.json`,
|
||||
`${JSON.stringify(rulesetDetails, null, 1)}\n`
|
||||
|
|
|
@ -440,9 +440,9 @@ function finalizeRuleset(context, network) {
|
|||
}
|
||||
};
|
||||
mergeRules(rulesetMap, 'resourceTypes');
|
||||
mergeRules(rulesetMap, 'removeParams');
|
||||
mergeRules(rulesetMap, 'initiatorDomains');
|
||||
mergeRules(rulesetMap, 'requestDomains');
|
||||
mergeRules(rulesetMap, 'removeParams');
|
||||
mergeRules(rulesetMap, 'responseHeaders');
|
||||
|
||||
// Patch id
|
||||
|
|
|
@ -108,7 +108,7 @@ if [ "$QUICK" != "yes" ]; then
|
|||
cp platform/mv3/*.js "$TMPDIR"/
|
||||
cp platform/mv3/*.mjs "$TMPDIR"/
|
||||
cp platform/mv3/extension/js/utils.js "$TMPDIR"/js/
|
||||
cp "$UBO_DIR"/assets/assets.json "$TMPDIR"/
|
||||
cp "$UBO_DIR"/assets/assets.dev.json "$TMPDIR"/
|
||||
cp "$UBO_DIR"/assets/resources/*.js "$TMPDIR"/
|
||||
cp -R platform/mv3/scriptlets "$TMPDIR"/
|
||||
mkdir -p "$TMPDIR"/web_accessible_resources
|
||||
|
|
Loading…
Reference in New Issue