mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-21 18:02:34 +01:00
[mv3] Add ability to enable/disable filter lists
This commit is contained in:
parent
d11a3f2fa3
commit
e31637af78
@ -4,7 +4,6 @@
|
||||
"eqeqeq": true,
|
||||
"esversion": 8,
|
||||
"globals": {
|
||||
"browser": false, // global variable in Firefox, Edge
|
||||
"chrome": false, // global variable in Chromium, Chrome, Opera
|
||||
"self": false,
|
||||
"vAPI": false,
|
||||
|
6
Makefile
6
Makefile
@ -1,11 +1,11 @@
|
||||
# https://stackoverflow.com/a/6273809
|
||||
run_options := $(filter-out $@,$(MAKECMDGOALS))
|
||||
|
||||
.PHONY: all clean test lint chromium firefox npm dig mv3 \
|
||||
.PHONY: all clean test lint chromium firefox npm dig mv3 mv3-quick \
|
||||
compare maxcost medcost mincost modifiers record wasm
|
||||
|
||||
sources := $(wildcard assets/resources/* dist/version src/* src/*/* src/*/*/* src/*/*/*/*)
|
||||
platform := $(wildcard platform/* platform/*/* platform/*/*/*)
|
||||
platform := $(wildcard platform/* platform/*/* platform/*/*/* platform/*/*/*/*)
|
||||
assets := $(wildcard submodules/uAssets/* \
|
||||
submodules/uAssets/*/* \
|
||||
submodules/uAssets/*/*/* \
|
||||
@ -57,7 +57,7 @@ mv3: tools/make-mv3.sh $(sources) $(platform)
|
||||
|
||||
mv3-quick: tools/make-mv3.sh $(sources) $(platform)
|
||||
tools/make-mv3.sh quick
|
||||
|
||||
|
||||
mv3-full: tools/make-mv3.sh $(sources) $(platform)
|
||||
tools/make-mv3.sh full
|
||||
|
||||
|
5
dist/firefox/publish-signed-beta.py
vendored
5
dist/firefox/publish-signed-beta.py
vendored
@ -291,6 +291,10 @@ if response.status_code != 204:
|
||||
# package is higher version than current one.
|
||||
#
|
||||
|
||||
# Be sure in sync with potentially modified files on remote
|
||||
r = subprocess.run(['git', 'checkout', 'origin/master', '--', 'dist/chromium-mv3/log.txt'], stdout=subprocess.PIPE)
|
||||
rout = bytes.decode(r.stdout).strip()
|
||||
|
||||
print('Update GitHub to point to newly signed self-hosted xpi package...')
|
||||
updates_json_filepath = os.path.join(projdir, 'dist', 'firefox', 'updates.json')
|
||||
with open(updates_json_filepath) as f:
|
||||
@ -305,7 +309,6 @@ with open(updates_json_filepath) as f:
|
||||
with open(updates_json_filepath, 'w') as f:
|
||||
f.write(updates_json)
|
||||
f.close()
|
||||
# Automatically git add/commit if needed.
|
||||
# - Stage the changed file
|
||||
r = subprocess.run(['git', 'status', '-s', updates_json_filepath], stdout=subprocess.PIPE)
|
||||
rout = bytes.decode(r.stdout).strip()
|
||||
|
@ -112,18 +112,6 @@ vAPI.getURL = browser.runtime.getURL;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.i18n = browser.i18n.getMessage;
|
||||
|
||||
// http://www.w3.org/International/questions/qa-scripts#directions
|
||||
document.body.setAttribute(
|
||||
'dir',
|
||||
['ar', 'he', 'fa', 'ps', 'ur'].indexOf(vAPI.i18n('@@ui_locale')) !== -1
|
||||
? 'rtl'
|
||||
: 'ltr'
|
||||
);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/3057
|
||||
// - webNavigation.onCreatedNavigationTarget become broken on Firefox when we
|
||||
// try to make the popup panel close itself using the original
|
||||
|
48
platform/mv3/extension/3p-filters.html
Normal file
48
platform/mv3/extension/3p-filters.html
Normal file
@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
|
||||
<title>uBlock Origin Lite — Filter lists</title>
|
||||
<link rel="stylesheet" type="text/css" href="css/default.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/common.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/fa-icons.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/3p-filters.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="body">
|
||||
|
||||
<div id="actions">
|
||||
<p id="listsOfBlockedHostsPrompt">
|
||||
<p><button id="buttonApply" class="preferred disabled iconified" type="button" data-i18n-title="genericApplyChanges"><span class="fa-icon">check</span><span data-i18n="genericApplyChanges">_</span><span class="hover"></span></button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div id="lists"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="templates" style="display: none;">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<script src="js/fa-icons.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js" type="module"></script>
|
||||
<script src="js/3p-filters.js" type="module"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
202
platform/mv3/extension/_locales/en/messages.json
Normal file
202
platform/mv3/extension/_locales/en/messages.json
Normal file
@ -0,0 +1,202 @@
|
||||
{
|
||||
"extName": {
|
||||
"message": "uBlock Origin Lite",
|
||||
"description": "extension name."
|
||||
},
|
||||
"extShortDesc": {
|
||||
"message": "An experimental, permission-less lite content blocker -- block ads, trackers, miners and more by default.",
|
||||
"description": "this will be in the Chrome web store: must be 132 characters or less"
|
||||
},
|
||||
"perRulesetStats": {
|
||||
"message": "{{ruleCount}} rules, converted from {{filterCount}} network filters",
|
||||
"description": "Appears aside each filter list in the _3rd-party filters_ pane"
|
||||
},
|
||||
"dashboardName": {
|
||||
"message": "uBO Lite — Dashboard",
|
||||
"description": "English: uBO Lite — Dashboard"
|
||||
},
|
||||
"dashboardUnsavedWarning": {
|
||||
"message": "Warning! You have unsaved changes",
|
||||
"description": "A warning in the dashboard when navigating away from unsaved changes"
|
||||
},
|
||||
"dashboardUnsavedWarningStay": {
|
||||
"message": "Stay",
|
||||
"description": "Label for button to prevent navigating away from unsaved changes"
|
||||
},
|
||||
"dashboardUnsavedWarningIgnore": {
|
||||
"message": "Ignore",
|
||||
"description": "Label for button to ignore unsaved changes"
|
||||
},
|
||||
"settingsPageName": {
|
||||
"message": "Settings",
|
||||
"description": "appears as tab name in dashboard"
|
||||
},
|
||||
"3pPageName": {
|
||||
"message": "Filter lists",
|
||||
"description": "appears as tab name in dashboard"
|
||||
},
|
||||
"1pPageName": {
|
||||
"message": "My filters",
|
||||
"description": "appears as tab name in dashboard"
|
||||
},
|
||||
"whitelistPageName": {
|
||||
"message": "Trusted sites",
|
||||
"description": "appears as tab name in dashboard"
|
||||
},
|
||||
"aboutPageName": {
|
||||
"message": "About",
|
||||
"description": "appears as tab name in dashboard"
|
||||
},
|
||||
"aboutPrivacyPolicy": {
|
||||
"message": "Privacy policy",
|
||||
"description": "Link to privacy policy on GitHub (English)"
|
||||
},
|
||||
"popupPowerSwitchInfo": {
|
||||
"message": "Disable/enable uBO Lite for this site",
|
||||
"description": "Tooltip for the main power button in the popup panel"
|
||||
},
|
||||
"popupTipDashboard": {
|
||||
"message": "Open the dashboard",
|
||||
"description": "English: Click to open the dashboard"
|
||||
},
|
||||
"popupTipZapper": {
|
||||
"message": "Enter element zapper mode",
|
||||
"description": "Tooltip for the element-zapper icon in the popup panel"
|
||||
},
|
||||
"popupTipPicker": {
|
||||
"message": "Enter element picker mode",
|
||||
"description": "English: Enter element picker mode"
|
||||
},
|
||||
"popupTipReport": {
|
||||
"message": "Report an issue on this website",
|
||||
"description": "Tooltip used for the 'chat' icon in the panel"
|
||||
},
|
||||
"popupTipSaveRules": {
|
||||
"message": "Click to make your changes permanent.",
|
||||
"description": "Tooltip when hovering over the padlock in the dynamic filtering pane."
|
||||
},
|
||||
"popupTipRevertRules": {
|
||||
"message": "Click to revert your changes.",
|
||||
"description": "Tooltip when hovering over the eraser in the dynamic filtering pane."
|
||||
},
|
||||
"settingsIconBadgePrompt": {
|
||||
"message": "Show the number of blocked requests on the icon",
|
||||
"description": "English: Show the number of blocked requests on the icon"
|
||||
},
|
||||
"settingsAppearance": {
|
||||
"message": "Appearance",
|
||||
"description": "Section for controlling user interface appearance"
|
||||
},
|
||||
"settingsThemeLabel": {
|
||||
"message": "Theme",
|
||||
"description": "Label for checkbox to enable a custom dark theme"
|
||||
},
|
||||
"settingsThemeAccent0Label": {
|
||||
"message": "Custom accent color",
|
||||
"description": "Label for checkbox to pick an accent color"
|
||||
},
|
||||
"settingsNoCSPReportsPrompt": {
|
||||
"message": "Block CSP reports",
|
||||
"description": "background information: https://github.com/gorhill/uBlock/issues/3150"
|
||||
},
|
||||
"3pGroupDefault": {
|
||||
"message": "Default",
|
||||
"description": "Header for a ruleset section in 'Filter lists pane'"
|
||||
},
|
||||
"3pGroupAds": {
|
||||
"message": "Ads",
|
||||
"description": "Header for a ruleset section in 'Filter lists pane'"
|
||||
},
|
||||
"3pGroupPrivacy": {
|
||||
"message": "Privacy",
|
||||
"description": "Header for a ruleset section in 'Filter lists pane'"
|
||||
},
|
||||
"3pGroupMalware": {
|
||||
"message": "Malware domains",
|
||||
"description": "Header for a ruleset section in 'Filter lists pane'"
|
||||
},
|
||||
"3pGroupAnnoyances": {
|
||||
"message": "Annoyances",
|
||||
"description": "Header for a ruleset section in 'Filter lists pane'"
|
||||
},
|
||||
"3pGroupMisc": {
|
||||
"message": "Miscellaneous",
|
||||
"description": "Header for a ruleset section in 'Filter lists pane'"
|
||||
},
|
||||
"3pGroupRegions": {
|
||||
"message": "Regions, languages",
|
||||
"description": "Header for a ruleset section in 'Filter lists pane'"
|
||||
},
|
||||
"1pFormatHint": {
|
||||
"message": "One filter per line. A filter can be a plain hostname, or an EasyList-compatible filter. Lines prefixed with <code>!</code> will be ignored.",
|
||||
"description": "Short information about how to create custom filters"
|
||||
},
|
||||
"1pImport": {
|
||||
"message": "Import and append",
|
||||
"description": "English: Import and append"
|
||||
},
|
||||
"1pExport": {
|
||||
"message": "Export",
|
||||
"description": "English: Export"
|
||||
},
|
||||
"1pExportFilename": {
|
||||
"message": "my-ublock-static-filters_{{datetime}}.txt",
|
||||
"description": "English: my-ublock-static-filters_{{datetime}}.txt"
|
||||
},
|
||||
"whitelistPrompt": {
|
||||
"message": "The trusted site directives dictate on which web pages uBO Lite should be disabled. One entry per line.",
|
||||
"description": "A concise description of the 'Trusted sites' pane."
|
||||
},
|
||||
"whitelistImport": {
|
||||
"message": "Import and append",
|
||||
"description": "English: Import and append"
|
||||
},
|
||||
"whitelistExport": {
|
||||
"message": "Export",
|
||||
"description": "English: Export"
|
||||
},
|
||||
"whitelistExportFilename": {
|
||||
"message": "my-ublock-trusted-sites_{{datetime}}.txt",
|
||||
"description": "The default filename to use for import/export purpose"
|
||||
},
|
||||
"aboutChangelog": {
|
||||
"message": "Changelog",
|
||||
"description": ""
|
||||
},
|
||||
"aboutCode": {
|
||||
"message": "Source code (GPLv3)",
|
||||
"description": "English: Source code (GPLv3)"
|
||||
},
|
||||
"aboutContributors": {
|
||||
"message": "Contributors",
|
||||
"description": "English: Contributors"
|
||||
},
|
||||
"aboutSourceCode": {
|
||||
"message": "Source code",
|
||||
"description": "Link text to source code repo"
|
||||
},
|
||||
"aboutTranslations": {
|
||||
"message": "Translations",
|
||||
"description": "Link text to translations repo"
|
||||
},
|
||||
"aboutFilterLists": {
|
||||
"message": "Filter lists",
|
||||
"description": "Link text to uBO's own filter lists repo"
|
||||
},
|
||||
"aboutDependencies": {
|
||||
"message": "External dependencies (GPLv3-compatible):",
|
||||
"description": "Shown in the About pane"
|
||||
},
|
||||
"genericSubmit": {
|
||||
"message": "Submit",
|
||||
"description": "for generic 'Submit' buttons"
|
||||
},
|
||||
"genericApplyChanges": {
|
||||
"message": "Apply changes",
|
||||
"description": "for generic 'Apply changes' buttons"
|
||||
},
|
||||
"genericRevert": {
|
||||
"message": "Revert",
|
||||
"description": "for generic 'Revert' buttons"
|
||||
}
|
||||
}
|
42
platform/mv3/extension/about.html
Normal file
42
platform/mv3/extension/about.html
Normal file
@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>uBlock Origin Lite — About</title>
|
||||
<link rel="stylesheet" type="text/css" href="css/default.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/common.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/about.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="body">
|
||||
<div id="aboutNameVer" class="li"></div>
|
||||
<div class="liul">
|
||||
<div class="li">Copyright (c) Raymond Hill 2014-present</div>
|
||||
</div>
|
||||
<div class="li"><a href="https://github.com/gorhill/uBlock/wiki/Privacy-policy" data-i18n="aboutPrivacyPolicy"></a></div>
|
||||
<div class="li"><a href="https://github.com/gorhill/uBlock/releases" data-i18n="aboutChangelog"></a></div>
|
||||
<div class="li"><a href="https://github.com/gorhill/uBlock" data-i18n="aboutCode"></a></div>
|
||||
<div class="li"><span data-i18n="aboutContributors"></span></div>
|
||||
<div class="liul">
|
||||
<div class="li"><a href="https://github.com/gorhill/uBlock/graphs/contributors" data-i18n="aboutSourceCode"></a></div>
|
||||
<div class="li"><a href="https://crowdin.com/project/ublock" data-i18n="aboutTranslations"></a></div>
|
||||
<div class="li"><a href="https://github.com/uBlockOrigin/uAssets/graphs/contributors" data-i18n="aboutFilterLists"></a></div>
|
||||
</div>
|
||||
<div class="li"><span data-i18n="aboutDependencies"></span></div>
|
||||
<div class="liul">
|
||||
<div class="li"><span><a href="https://github.com/chrismsimpson/Metropolis" target="_blank">Metropolis font family</a> by <a href="https://github.com/chrismsimpson">Chris Simpson</a></span></div>
|
||||
<div class="li"><span><a href="https://github.com/rsms/inter" target="_blank">Inter font family</a> by <a href="https://github.com/rsms">Rasmus Andersson</a></span></div>
|
||||
<div class="li"><span><a href="https://fontawesome.com/" target="_blank">FontAwesome font family</a> by <a href="https://github.com/davegandy">Dave Gandy</a></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js" type="module"></script>
|
||||
<script src="js/about.js" type="module"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
186
platform/mv3/extension/css/3p-filters.css
Normal file
186
platform/mv3/extension/css/3p-filters.css
Normal file
@ -0,0 +1,186 @@
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
body {
|
||||
margin-bottom: 6rem;
|
||||
}
|
||||
#actions {
|
||||
background-color: var(--surface-1);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
#buttonUpdate.active {
|
||||
pointer-events: none;
|
||||
}
|
||||
#buttonUpdate.active .fa-icon svg {
|
||||
animation: spin 1s linear infinite;
|
||||
transform-origin: 50%;
|
||||
}
|
||||
#lists {
|
||||
margin: 0.5em 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.groupEntry.hideUnused:not([data-groupkey="user"]) .geDetails::before {
|
||||
content: '+';
|
||||
}
|
||||
.groupEntry {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.groupEntry .geDetails {
|
||||
cursor: pointer;
|
||||
}
|
||||
.groupEntry .geName {
|
||||
pointer-events: none;
|
||||
}
|
||||
.groupEntry .geCount {
|
||||
color: var(--ink-3);
|
||||
font-size: 90%;
|
||||
pointer-events: none;
|
||||
}
|
||||
.listEntries {
|
||||
margin-inline-start: 0.6em;
|
||||
-webkit-margin-start: 0.6em;
|
||||
}
|
||||
.groupEntry:not([data-groupkey="user"]) .listEntry:not(.isDefault).unused {
|
||||
display: none;
|
||||
}
|
||||
.listEntry > * {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
unicode-bidi: embed;
|
||||
}
|
||||
.listEntry .listname {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.listEntry.toRemove .checkbox {
|
||||
visibility: hidden;
|
||||
}
|
||||
.listEntry.toRemove .listname {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.listEntry a,
|
||||
.listEntry .fa-icon,
|
||||
.listEntry .counts {
|
||||
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 .remove,
|
||||
.listEntry .unsecure,
|
||||
.listEntry .failed {
|
||||
color: var(--info3-ink);
|
||||
fill: var(--info3-ink);
|
||||
cursor: pointer;
|
||||
}
|
||||
.listEntry.external .remove {
|
||||
display: inline-flex;
|
||||
}
|
||||
.listEntry.mustread a.mustread {
|
||||
color: var(--info1-ink);
|
||||
fill: var(--info1-ink);
|
||||
display: inline-flex;
|
||||
}
|
||||
.listEntry .counts {
|
||||
font-size: smaller;
|
||||
}
|
||||
.listEntry.checked .counts {
|
||||
display: inline-block;
|
||||
}
|
||||
.listEntry .status {
|
||||
cursor: default;
|
||||
display: none;
|
||||
}
|
||||
.listEntry.checked.unsecure .unsecure {
|
||||
display: inline-flex;
|
||||
}
|
||||
.listEntry.failed .failed {
|
||||
display: inline-flex;
|
||||
}
|
||||
.listEntry .cache {
|
||||
cursor: pointer;
|
||||
}
|
||||
.listEntry.checked.cached:not(.obsolete) .cache {
|
||||
display: inline-flex;
|
||||
}
|
||||
.listEntry .obsolete {
|
||||
color: var(--info2-ink);
|
||||
fill: var(--info2-ink);
|
||||
}
|
||||
body:not(.updating) .listEntry.checked.obsolete .obsolete {
|
||||
display: inline-flex;
|
||||
}
|
||||
.listEntry .updating {
|
||||
transform-origin: 50%;
|
||||
}
|
||||
body.updating .listEntry.checked.obsolete .updating {
|
||||
animation: spin 1s steps(8) infinite;
|
||||
display: inline-flex;
|
||||
}
|
||||
.listEntry.toImport {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.listEntry.toImport textarea {
|
||||
border: 1px solid #ccc;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
font-size: smaller;
|
||||
height: 6em;
|
||||
margin: 0;
|
||||
resize: vertical;
|
||||
visibility: hidden;
|
||||
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;
|
||||
}
|
55
platform/mv3/extension/css/dashboard-common.css
Normal file
55
platform/mv3/extension/css/dashboard-common.css
Normal file
@ -0,0 +1,55 @@
|
||||
body > div.body {
|
||||
margin: 0 1em;
|
||||
}
|
||||
h2, h3 {
|
||||
margin: 1em 0;
|
||||
}
|
||||
h2 {
|
||||
font-size: 18px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.fa-icon.info {
|
||||
color: var(--info0-ink);
|
||||
fill: var(--info0-ink);
|
||||
font-size: 115%;
|
||||
}
|
||||
.fa-icon.info:hover {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
.fa-icon.info.important {
|
||||
color: var(--info2-ink);
|
||||
fill: var(--info2-ink);
|
||||
}
|
||||
.fa-icon.info.very-important {
|
||||
color: var(--info3-ink);
|
||||
fill: var(--info3-ink);
|
||||
}
|
||||
input[type="number"] {
|
||||
width: 5em;
|
||||
}
|
||||
@media (max-height: 640px), (max-height: 800px) and (max-width: 480px) {
|
||||
.body > p,
|
||||
.body > ul {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.vverbose {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
/**
|
||||
On mobile device, the on-screen keyboard may take up
|
||||
so much space that it overlaps the content being edited.
|
||||
The rule below makes it possible to scroll the edited
|
||||
content within view.
|
||||
*/
|
||||
:root.mobile {
|
||||
overflow: auto;
|
||||
}
|
||||
:root.mobile body {
|
||||
min-height: 600px;
|
||||
}
|
35
platform/mv3/extension/dashboard.html
Normal file
35
platform/mv3/extension/dashboard.html
Normal file
@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
|
||||
<title data-i18n="dashboardName"></title>
|
||||
<link rel="stylesheet" href="css/default.css">
|
||||
<link rel="stylesheet" href="css/common.css">
|
||||
<link rel="stylesheet" href="css/fa-icons.css">
|
||||
<link rel="stylesheet" href="css/dashboard.css">
|
||||
<link rel="shortcut icon" type="image/png" href="img/icon_16.png"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="dashboard-nav">
|
||||
<span class="logo"><img data-i18n-title="extName" src="img/ublock.svg"></span><!--
|
||||
--><button class="tabButton" type="button" data-pane="3p-filters.html" data-i18n="3pPageName" tabindex="0"></button><!--
|
||||
--><button class="tabButton" type="button" data-pane="about.html" data-i18n="aboutPageName" tabindex="0"></button>
|
||||
</div>
|
||||
<section id="unsavedWarning" class="notice">
|
||||
<div>
|
||||
<span data-i18n="dashboardUnsavedWarning"></span> 
|
||||
<button type="button" data-i18n="dashboardUnsavedWarningStay">_<span class="hover"></span></button> 
|
||||
<button type="button" data-i18n="dashboardUnsavedWarningIgnore">_<span class="hover"></span></button>
|
||||
</div>
|
||||
<div></div>
|
||||
</section>
|
||||
|
||||
<iframe id="iframe" src=""></iframe>
|
||||
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard.js" type="module"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
379
platform/mv3/extension/js/3p-filters.js
Normal file
379
platform/mv3/extension/js/3p-filters.js
Normal file
@ -0,0 +1,379 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
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
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
import { sendMessage } from './ext.js';
|
||||
import { i18n$ } from './i18n.js';
|
||||
import { dom, qs$, qsa$ } from './dom.js';
|
||||
import { simpleStorage } from './storage.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
let cachedRulesetData = {};
|
||||
let filteringSettingsHash = '';
|
||||
let hideUnusedSet = new Set([ 'regions' ]);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const renderNumber = function(value) {
|
||||
return value.toLocaleString();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const renderFilterLists = function(soft) {
|
||||
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 = listEntryTemplate.cloneNode(true);
|
||||
}
|
||||
const on = enabledRulesets.includes(ruleset.id);
|
||||
li.classList.toggle('checked', on);
|
||||
let elem;
|
||||
if ( dom.attr(li, 'data-listkey') !== ruleset.id ) {
|
||||
dom.attr(li, 'data-listkey', ruleset.id);
|
||||
qs$('input[type="checkbox"]', li).checked = on;
|
||||
qs$('.listname', li).textContent = ruleset.name || ruleset.id;
|
||||
dom.removeClass(li, 'toRemove');
|
||||
if ( ruleset.supportName ) {
|
||||
dom.addClass(li, 'support');
|
||||
elem = qs$('a.support', li);
|
||||
dom.attr(elem, 'href', ruleset.supportURL);
|
||||
dom.attr(elem, 'title', ruleset.supportName);
|
||||
} else {
|
||||
dom.removeClass(li, 'support');
|
||||
}
|
||||
if ( ruleset.instructionURL ) {
|
||||
dom.addClass(li, 'mustread');
|
||||
dom.attr(qs$('a.mustread', li), 'href', ruleset.instructionURL);
|
||||
} else {
|
||||
dom.removeClass(li, 'mustread');
|
||||
}
|
||||
dom.toggleClass(li, 'isDefault', ruleset.isDefault === true);
|
||||
dom.toggleClass(li, 'unused', hideUnused && !on);
|
||||
}
|
||||
// https://github.com/gorhill/uBlock/issues/1429
|
||||
if ( !soft ) {
|
||||
qs$('input[type="checkbox"]', li).checked = on;
|
||||
}
|
||||
li.title = listStatsTemplate
|
||||
.replace('{{ruleCount}}', renderNumber(ruleset.rules.accepted))
|
||||
.replace('{{filterCount}}', renderNumber(ruleset.filters.accepted));
|
||||
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 = listGroupTemplate.cloneNode(true);
|
||||
let groupName = groupNames.get(groupKey);
|
||||
if ( groupName === undefined ) {
|
||||
groupName = i18n$('3pGroup' + groupKey.charAt(0).toUpperCase() + groupKey.slice(1));
|
||||
groupNames.set(groupKey, groupName);
|
||||
}
|
||||
if ( groupName !== '' ) {
|
||||
qs$('.geName', liGroup).textContent = groupName;
|
||||
}
|
||||
}
|
||||
if ( qs$('.geName:empty', liGroup) === null ) {
|
||||
qs$('.geCount', liGroup).textContent = listEntryCountFromGroup(groupRulesets);
|
||||
}
|
||||
const hideUnused = mustHideUnusedLists(groupKey);
|
||||
liGroup.classList.toggle('hideUnused', hideUnused);
|
||||
const ulGroup = qs$('.listEntries', liGroup);
|
||||
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;
|
||||
};
|
||||
|
||||
// Incremental rendering: this will allow us to easily discard unused
|
||||
// DOM list entries.
|
||||
dom.addClass(
|
||||
qsa$('#lists .listEntries .listEntry[data-listkey]'),
|
||||
'discard'
|
||||
);
|
||||
|
||||
// Visually split the filter lists in three groups
|
||||
const ulLists = qs$('#lists');
|
||||
const groups = new Map([
|
||||
[
|
||||
'default',
|
||||
rulesetDetails.filter(ruleset =>
|
||||
ruleset.id === 'default'
|
||||
),
|
||||
],
|
||||
[
|
||||
'misc',
|
||||
rulesetDetails.filter(ruleset =>
|
||||
ruleset.id !== 'default' && typeof ruleset.lang !== 'string'
|
||||
),
|
||||
],
|
||||
[
|
||||
'regions',
|
||||
rulesetDetails.filter(ruleset =>
|
||||
typeof ruleset.lang === 'string'
|
||||
),
|
||||
],
|
||||
]);
|
||||
|
||||
dom.toggleClass(dom.body, 'hideUnused', mustHideUnusedLists('*'));
|
||||
|
||||
for ( const [ groupKey, groupRulesets ] of groups ) {
|
||||
let liGroup = liFromListGroup(groupKey, groupRulesets);
|
||||
liGroup.setAttribute('data-groupkey', groupKey);
|
||||
if ( liGroup.parentElement === null ) {
|
||||
ulLists.appendChild(liGroup);
|
||||
}
|
||||
}
|
||||
|
||||
dom.remove(qsa$('#lists .listEntries .listEntry.discard'));
|
||||
|
||||
// Compute a hash of the settings so that we can keep track of changes
|
||||
// affecting the loading of filter lists.
|
||||
if ( !soft ) {
|
||||
filteringSettingsHash = hashFromCurrentFromSettings();
|
||||
}
|
||||
|
||||
renderWidgets();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const renderWidgets = function() {
|
||||
dom.toggleClass(
|
||||
qs$('#buttonApply'),
|
||||
'disabled',
|
||||
filteringSettingsHash === hashFromCurrentFromSettings()
|
||||
);
|
||||
|
||||
// Compute total counts
|
||||
const rulesetMap = new Map(
|
||||
cachedRulesetData.rulesetDetails.map(rule => [ rule.id, rule ])
|
||||
);
|
||||
let filterCount = 0;
|
||||
let ruleCount = 0;
|
||||
for ( const liEntry of qsa$('#lists .listEntry[data-listkey]') ) {
|
||||
if ( qs$('input[type="checkbox"]:checked', liEntry) === null ) { continue; }
|
||||
const ruleset = rulesetMap.get(liEntry.dataset.listkey);
|
||||
if ( ruleset === undefined ) { continue; }
|
||||
filterCount += ruleset.filters.accepted;
|
||||
ruleCount += ruleset.rules.accepted;
|
||||
}
|
||||
qs$('#listsOfBlockedHostsPrompt').textContent = i18n$('perRulesetStats')
|
||||
.replace('{{ruleCount}}', ruleCount.toLocaleString())
|
||||
.replace('{{filterCount}}', filterCount.toLocaleString());
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const hashFromCurrentFromSettings = function() {
|
||||
const hash = [];
|
||||
const listHash = [];
|
||||
for ( const liEntry of qsa$('#lists .listEntry[data-listkey]') ) {
|
||||
if ( qs$('input[type="checkbox"]:checked', liEntry) ) {
|
||||
listHash.push(dom.attr(liEntry, 'data-listkey'));
|
||||
}
|
||||
}
|
||||
hash.push(listHash.sort().join());
|
||||
return hash.join();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function onListsetChanged(ev) {
|
||||
const input = ev.target;
|
||||
const li = input.closest('.listEntry');
|
||||
dom.toggleClass(li, 'checked', input.checked);
|
||||
renderWidgets();
|
||||
}
|
||||
|
||||
dom.on(
|
||||
qs$('#lists'),
|
||||
'change',
|
||||
'.listEntry input',
|
||||
onListsetChanged
|
||||
);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const applyEnabledRulesets = async function() {
|
||||
const enabledRulesets = [];
|
||||
for ( const liEntry of qsa$('#lists .listEntry[data-listkey]') ) {
|
||||
if ( qs$('input[type="checkbox"]:checked', liEntry) === null ) { continue; }
|
||||
enabledRulesets.push(liEntry.dataset.listkey);
|
||||
}
|
||||
|
||||
await sendMessage({
|
||||
what: 'applyRulesets',
|
||||
enabledRulesets,
|
||||
});
|
||||
|
||||
filteringSettingsHash = hashFromCurrentFromSettings();
|
||||
};
|
||||
|
||||
const buttonApplyHandler = async function() {
|
||||
dom.removeClass(qs$('#buttonApply'), 'enabled');
|
||||
await applyEnabledRulesets();
|
||||
renderWidgets();
|
||||
};
|
||||
|
||||
dom.on(
|
||||
qs$('#buttonApply'),
|
||||
'click',
|
||||
( ) => { buttonApplyHandler(); }
|
||||
);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Collapsing of unused lists.
|
||||
|
||||
const mustHideUnusedLists = function(which) {
|
||||
const hideAll = hideUnusedSet.has('*');
|
||||
if ( which === '*' ) { return hideAll; }
|
||||
return hideUnusedSet.has(which) !== hideAll;
|
||||
};
|
||||
|
||||
const toggleHideUnusedLists = function(which) {
|
||||
const doesHideAll = hideUnusedSet.has('*');
|
||||
let groupSelector;
|
||||
let mustHide;
|
||||
if ( which === '*' ) {
|
||||
mustHide = doesHideAll === false;
|
||||
groupSelector = '';
|
||||
hideUnusedSet.clear();
|
||||
if ( mustHide ) {
|
||||
hideUnusedSet.add(which);
|
||||
}
|
||||
document.body.classList.toggle('hideUnused', mustHide);
|
||||
dom.toggleClass(qsa$('.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.toggleClass(qsa$(groupSelector), 'hideUnused', mustHide);
|
||||
}
|
||||
|
||||
for ( const elem of qsa$(`#lists ${groupSelector} .listEntry[data-listkey] input[type="checkbox"]:not(:checked)`) ) {
|
||||
dom.toggleClass(
|
||||
elem.closest('.listEntry[data-listkey]'),
|
||||
'unused',
|
||||
mustHide
|
||||
);
|
||||
}
|
||||
|
||||
simpleStorage.setItem(
|
||||
'hideUnusedFilterLists',
|
||||
Array.from(hideUnusedSet)
|
||||
);
|
||||
};
|
||||
|
||||
dom.on(
|
||||
qs$('#lists'),
|
||||
'click',
|
||||
'.groupEntry[data-groupkey] > .geDetails',
|
||||
ev => {
|
||||
toggleHideUnusedLists(
|
||||
dom.attr(ev.target.closest('[data-groupkey]'), 'data-groupkey')
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// Initialize from saved state.
|
||||
simpleStorage.getItem('hideUnusedFilterLists').then(value => {
|
||||
if ( Array.isArray(value) ) {
|
||||
hideUnusedSet = new Set(value);
|
||||
}
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
self.hasUnsavedData = function() {
|
||||
return hashFromCurrentFromSettings() !== filteringSettingsHash;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
dom.on(
|
||||
qs$('#lists'),
|
||||
'click',
|
||||
'.listEntry label *',
|
||||
ev => {
|
||||
if ( ev.target.matches('input,.forinput') ) { return; }
|
||||
ev.preventDefault();
|
||||
}
|
||||
);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
sendMessage({
|
||||
what: 'getRulesetData',
|
||||
}).then(data => {
|
||||
if ( !data ) { return; }
|
||||
cachedRulesetData = data;
|
||||
try {
|
||||
renderFilterLists();
|
||||
} catch(ex) {
|
||||
}
|
||||
}).catch(reason => {
|
||||
console.trace(reason);
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
35
platform/mv3/extension/js/about.js
Normal file
35
platform/mv3/extension/js/about.js
Normal file
@ -0,0 +1,35 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
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
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
import { runtime } from './ext.js';
|
||||
import { qs$ } from './dom.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
(async ( ) => {
|
||||
const manifest = runtime.getManifest();
|
||||
|
||||
qs$('#aboutNameVer').textContent = `${manifest.name} ${manifest.version}`;
|
||||
})();
|
@ -1,27 +1,151 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2022-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
|
||||
*/
|
||||
|
||||
/* jshint esversion:11 */
|
||||
|
||||
'use strict';
|
||||
|
||||
import rulesetDetails from '/rulesets/ruleset-details.js';
|
||||
/******************************************************************************/
|
||||
|
||||
import { dnr, i18n, runtime } from './ext.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const dnr = chrome.declarativeNetRequest;
|
||||
const TRUSTED_DIRECTIVE_BASE_RULE_ID = 1000000;
|
||||
const RULE_REALM_SIZE = 1000000;
|
||||
const REGEXES_REALM_START = 1000000;
|
||||
const REGEXES_REALM_END = REGEXES_REALM_START + RULE_REALM_SIZE;
|
||||
const TRUSTED_DIRECTIVE_BASE_RULE_ID = 8000000;
|
||||
const CURRENT_CONFIG_BASE_RULE_ID = 9000000;
|
||||
|
||||
const dynamicRuleMap = new Map();
|
||||
const rulesetDetails = new Map();
|
||||
|
||||
const rulesetConfig = {
|
||||
version: '',
|
||||
enabledRulesets: [],
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function updateRegexRules() {
|
||||
function getCurrentVersion() {
|
||||
return runtime.getManifest().version;
|
||||
}
|
||||
|
||||
async function loadRulesetConfig() {
|
||||
const configRule = dynamicRuleMap.get(CURRENT_CONFIG_BASE_RULE_ID);
|
||||
if ( configRule === undefined ) {
|
||||
rulesetConfig.enabledRulesets = await defaultRulesetsFromLanguage();
|
||||
return;
|
||||
}
|
||||
|
||||
const match = /^\|\|example.invalid\/([^\/]+)\/(?:([^\/]+)\/)?/.exec(
|
||||
configRule.condition.urlFilter
|
||||
);
|
||||
if ( match === null ) { return; }
|
||||
|
||||
rulesetConfig.version = match[1];
|
||||
if ( match[2] ) {
|
||||
rulesetConfig.enabledRulesets =
|
||||
decodeURIComponent(match[2] || '').split(' ');
|
||||
}
|
||||
}
|
||||
|
||||
async function saveRulesetConfig() {
|
||||
let configRule = dynamicRuleMap.get(CURRENT_CONFIG_BASE_RULE_ID);
|
||||
if ( configRule === undefined ) {
|
||||
configRule = {
|
||||
id: CURRENT_CONFIG_BASE_RULE_ID,
|
||||
action: {
|
||||
type: 'allow',
|
||||
},
|
||||
condition: {
|
||||
urlFilter: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const version = rulesetConfig.version;
|
||||
const enabledRulesets = encodeURIComponent(rulesetConfig.enabledRulesets.join(' '));
|
||||
const urlFilter = `||example.invalid/${version}/${enabledRulesets}/`;
|
||||
if ( urlFilter === configRule.condition.urlFilter ) { return; }
|
||||
configRule.condition.urlFilter = urlFilter;
|
||||
|
||||
return dnr.updateDynamicRules({
|
||||
addRules: [ configRule ],
|
||||
removeRuleIds: [ CURRENT_CONFIG_BASE_RULE_ID ],
|
||||
});
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function fetchJSON(filename) {
|
||||
return fetch(`/rulesets/${filename}.json`).then(response =>
|
||||
response.json()
|
||||
).catch(reason => {
|
||||
console.info(reason);
|
||||
});
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function updateRegexRules(dynamicRules) {
|
||||
// Avoid testing already tested regexes
|
||||
const validRegexSet = new Set(
|
||||
dynamicRules.filter(rule =>
|
||||
rule.condition?.regexFilter && true || false
|
||||
).map(rule =>
|
||||
rule.condition.regexFilter
|
||||
)
|
||||
);
|
||||
const allRules = [];
|
||||
const toCheck = [];
|
||||
for ( const details of rulesetDetails ) {
|
||||
|
||||
// Fetch regexes for all enabled rulesets
|
||||
const toFetch = [];
|
||||
for ( const details of rulesetDetails.values() ) {
|
||||
if ( details.enabled !== true ) { continue; }
|
||||
for ( const rule of details.rules.regexes ) {
|
||||
const regex = rule.condition.regexFilter;
|
||||
const isCaseSensitive = rule.condition.isUrlFilterCaseSensitive === true;
|
||||
toFetch.push(fetchJSON(`${details.id}.regexes`));
|
||||
}
|
||||
const regexRulesets = await Promise.all(toFetch);
|
||||
|
||||
// Validate fetched regexes
|
||||
let regexRuleId = REGEXES_REALM_START;
|
||||
for ( const rules of regexRulesets ) {
|
||||
if ( Array.isArray(rules) === false ) { continue; }
|
||||
for ( const rule of rules ) {
|
||||
rule.id = regexRuleId++;
|
||||
const {
|
||||
regexFilter: regex,
|
||||
isUrlFilterCaseSensitive: isCaseSensitive
|
||||
} = rule.condition;
|
||||
allRules.push(rule);
|
||||
toCheck.push(dnr.isRegexSupported({ regex, isCaseSensitive }));
|
||||
toCheck.push(
|
||||
validRegexSet.has(regex)
|
||||
? { isSupported: true }
|
||||
: dnr.isRegexSupported({ regex, isCaseSensitive })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Collate results
|
||||
const results = await Promise.all(toCheck);
|
||||
const newRules = [];
|
||||
for ( let i = 0; i < allRules.length; i++ ) {
|
||||
@ -33,11 +157,18 @@ async function updateRegexRules() {
|
||||
console.info(`${result.reason}: ${rule.condition.regexFilter}`);
|
||||
}
|
||||
}
|
||||
console.info(
|
||||
`Rejected regex filters: ${allRules.length-newRules.length} out of ${allRules.length}`
|
||||
);
|
||||
|
||||
// Add validated regex rules to dynamic ruleset without affecting rules
|
||||
// outside regex rule realm.
|
||||
const newRuleMap = new Map(newRules.map(rule => [ rule.id, rule ]));
|
||||
const addRules = [];
|
||||
const removeRuleIds = [];
|
||||
for ( const oldRule of dynamicRuleMap.values() ) {
|
||||
if ( oldRule.id >= TRUSTED_DIRECTIVE_BASE_RULE_ID ) { continue; }
|
||||
if ( oldRule.id < REGEXES_REALM_START ) { continue; }
|
||||
if ( oldRule.id >= REGEXES_REALM_END ) { continue; }
|
||||
const newRule = newRuleMap.get(oldRule.id);
|
||||
if ( newRule === undefined ) {
|
||||
removeRuleIds.push(oldRule.id);
|
||||
@ -148,50 +279,168 @@ async function toggleTrustedSiteDirective(details) {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
(async ( ) => {
|
||||
async function enableRulesets(ids) {
|
||||
const afterIds = new Set(ids);
|
||||
const beforeIds = new Set(await dnr.getEnabledRulesets());
|
||||
const enableRulesetIds = [];
|
||||
const disableRulesetIds = [];
|
||||
for ( const id of afterIds ) {
|
||||
if ( beforeIds.has(id) ) { continue; }
|
||||
enableRulesetIds.push(id);
|
||||
}
|
||||
for ( const id of beforeIds ) {
|
||||
if ( afterIds.has(id) ) { continue; }
|
||||
disableRulesetIds.push(id);
|
||||
}
|
||||
if ( enableRulesetIds.length !== 0 || disableRulesetIds.length !== 0 ) {
|
||||
return dnr.updateEnabledRulesets({ enableRulesetIds,disableRulesetIds });
|
||||
}
|
||||
}
|
||||
|
||||
async function getEnabledRulesetsStats() {
|
||||
const ids = await dnr.getEnabledRulesets();
|
||||
const out = [];
|
||||
for ( const id of ids ) {
|
||||
const ruleset = rulesetDetails.get(id);
|
||||
if ( ruleset === undefined ) { continue; }
|
||||
out.push({
|
||||
name: ruleset.name,
|
||||
filterCount: ruleset.filters.accepted,
|
||||
ruleCount: ruleset.rules.accepted,
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
async function defaultRulesetsFromLanguage() {
|
||||
const out = [ 'default' ];
|
||||
|
||||
const dropCountry = lang => {
|
||||
const pos = lang.indexOf('-');
|
||||
if ( pos === -1 ) { return lang; }
|
||||
return lang.slice(0, pos);
|
||||
};
|
||||
|
||||
const langSet = new Set();
|
||||
|
||||
await i18n.getAcceptLanguages().then(langs => {
|
||||
for ( const lang of langs.map(dropCountry) ) {
|
||||
langSet.add(lang);
|
||||
}
|
||||
});
|
||||
langSet.add(dropCountry(i18n.getUILanguage()));
|
||||
|
||||
const reTargetLang = new RegExp(
|
||||
`\\b(${Array.from(langSet).join('|')})\\b`
|
||||
);
|
||||
|
||||
for ( const [ id, details ] of rulesetDetails ) {
|
||||
if ( typeof details.lang !== 'string' ) { continue; }
|
||||
if ( reTargetLang.test(details.lang) === false ) { continue; }
|
||||
out.push(id);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function start() {
|
||||
// Fetch enabled rulesets and dynamic rules
|
||||
const dynamicRules = await dnr.getDynamicRules();
|
||||
for ( const rule of dynamicRules ) {
|
||||
dynamicRuleMap.set(rule.id, rule);
|
||||
}
|
||||
|
||||
await updateRegexRules();
|
||||
// Fetch ruleset details
|
||||
await fetchJSON('ruleset-details').then(entries => {
|
||||
if ( entries === undefined ) { return; }
|
||||
for ( const entry of entries ) {
|
||||
rulesetDetails.set(entry.id, entry);
|
||||
}
|
||||
});
|
||||
|
||||
await loadRulesetConfig();
|
||||
|
||||
console.log(`Dynamic rule count: ${dynamicRuleMap.size}`);
|
||||
console.log(`Available dynamic rule count: ${dnr.MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES - dynamicRuleMap.size}`);
|
||||
|
||||
await enableRulesets(rulesetConfig.enabledRulesets);
|
||||
|
||||
// We need to update the regex rules only when ruleset version changes.
|
||||
const currentVersion = getCurrentVersion();
|
||||
if ( currentVersion !== rulesetConfig.version ) {
|
||||
await updateRegexRules(dynamicRules);
|
||||
console.log(`Version change: ${rulesetConfig.version} => ${currentVersion}`);
|
||||
rulesetConfig.version = currentVersion;
|
||||
}
|
||||
|
||||
saveRulesetConfig();
|
||||
|
||||
const enabledRulesets = await dnr.getEnabledRulesets();
|
||||
console.log(`Enabled rulesets: ${enabledRulesets}`);
|
||||
|
||||
console.log(`Available dynamic rule count: ${dnr.MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES - dynamicRuleMap.size}`);
|
||||
|
||||
dnr.getAvailableStaticRuleCount().then(count => {
|
||||
console.log(`Available static rule count: ${count}`);
|
||||
});
|
||||
|
||||
dnr.setExtensionActionOptions({ displayActionCountAsBadgeText: true });
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener((request, sender, callback) => {
|
||||
switch ( request.what ) {
|
||||
case 'popupPanelData':
|
||||
matchesTrustedSiteDirective(request).then(response => {
|
||||
callback({
|
||||
isTrusted: response,
|
||||
rulesetDetails: rulesetDetails.filter(details =>
|
||||
details.enabled
|
||||
).map(details => ({
|
||||
name: details.name,
|
||||
filterCount: details.filters.accepted,
|
||||
ruleCount: details.rules.accepted,
|
||||
})),
|
||||
});
|
||||
/******************************************************************************/
|
||||
|
||||
function messageListener(request, sender, callback) {
|
||||
switch ( request.what ) {
|
||||
|
||||
case 'getRulesetData': {
|
||||
dnr.getEnabledRulesets().then(enabledRulesets => {
|
||||
callback({
|
||||
enabledRulesets,
|
||||
rulesetDetails: Array.from(rulesetDetails.values()),
|
||||
});
|
||||
return true;
|
||||
case 'toggleTrustedSiteDirective':
|
||||
toggleTrustedSiteDirective(request).then(response => {
|
||||
callback(response);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
case 'applyRulesets': {
|
||||
enableRulesets(request.enabledRulesets).then(( ) => {
|
||||
rulesetConfig.enabledRulesets = request.enabledRulesets;
|
||||
return saveRulesetConfig();
|
||||
}).then(( ) => {
|
||||
callback();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
case 'popupPanelData': {
|
||||
Promise.all([
|
||||
matchesTrustedSiteDirective(request),
|
||||
getEnabledRulesetsStats(),
|
||||
]).then(results => {
|
||||
callback({
|
||||
isTrusted: results[0],
|
||||
rulesetDetails: results[1],
|
||||
});
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
case 'toggleTrustedSiteDirective': {
|
||||
toggleTrustedSiteDirective(request).then(response => {
|
||||
callback(response);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
(async ( ) => {
|
||||
await start();
|
||||
|
||||
runtime.onMessage.addListener(messageListener);
|
||||
})();
|
||||
|
32
platform/mv3/extension/js/dashboard-common.js
Normal file
32
platform/mv3/extension/js/dashboard-common.js
Normal file
@ -0,0 +1,32 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
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
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
import { dom, qsa$ } from './dom.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Open links in the proper window
|
||||
dom.attr(qsa$('a'), 'target', '_blank');
|
||||
dom.attr(qsa$('a[href*="dashboard.html"]'), 'target', '_parent');
|
135
platform/mv3/extension/js/dashboard.js
Normal file
135
platform/mv3/extension/js/dashboard.js
Normal file
@ -0,0 +1,135 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
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
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
import { simpleStorage } from './storage.js';
|
||||
import { dom, qs$ } from './dom.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const discardUnsavedData = function(synchronous = false) {
|
||||
const paneFrame = document.getElementById('iframe');
|
||||
const paneWindow = paneFrame.contentWindow;
|
||||
if (
|
||||
typeof paneWindow.hasUnsavedData !== 'function' ||
|
||||
paneWindow.hasUnsavedData() === false
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( synchronous ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
const modal = document.querySelector('#unsavedWarning');
|
||||
modal.classList.add('on');
|
||||
modal.focus();
|
||||
|
||||
const onDone = status => {
|
||||
modal.classList.remove('on');
|
||||
document.removeEventListener('click', onClick, true);
|
||||
resolve(status);
|
||||
};
|
||||
|
||||
const onClick = ev => {
|
||||
const target = ev.target;
|
||||
if ( target.matches('[data-i18n="dashboardUnsavedWarningStay"]') ) {
|
||||
return onDone(false);
|
||||
}
|
||||
if ( target.matches('[data-i18n="dashboardUnsavedWarningIgnore"]') ) {
|
||||
return onDone(true);
|
||||
}
|
||||
if ( modal.querySelector('[data-i18n="dashboardUnsavedWarning"]').contains(target) ) {
|
||||
return;
|
||||
}
|
||||
onDone(false);
|
||||
};
|
||||
|
||||
document.addEventListener('click', onClick, true);
|
||||
});
|
||||
};
|
||||
|
||||
const loadDashboardPanel = function(pane, first) {
|
||||
const tabButton = document.querySelector(`[data-pane="${pane}"]`);
|
||||
if ( tabButton === null || tabButton.classList.contains('selected') ) {
|
||||
return;
|
||||
}
|
||||
const loadPane = ( ) => {
|
||||
self.location.replace(`#${pane}`);
|
||||
for ( const node of document.querySelectorAll('.tabButton.selected') ) {
|
||||
node.classList.remove('selected');
|
||||
}
|
||||
tabButton.classList.add('selected');
|
||||
tabButton.scrollIntoView();
|
||||
document.querySelector('#iframe').contentWindow.location.replace(pane);
|
||||
if ( pane !== 'no-dashboard.html' ) {
|
||||
simpleStorage.setItem('dashboardLastVisitedPane', pane);
|
||||
}
|
||||
};
|
||||
if ( first ) {
|
||||
return loadPane();
|
||||
}
|
||||
const r = discardUnsavedData();
|
||||
if ( r === false ) { return; }
|
||||
if ( r === true ) {
|
||||
return loadPane();
|
||||
}
|
||||
r.then(status => {
|
||||
if ( status === false ) { return; }
|
||||
loadPane();
|
||||
});
|
||||
};
|
||||
|
||||
const onTabClickHandler = function(ev) {
|
||||
loadDashboardPanel(ev.target.getAttribute('data-pane'));
|
||||
};
|
||||
|
||||
if ( self.location.hash.slice(1) === 'no-dashboard.html' ) {
|
||||
document.body.classList.add('noDashboard');
|
||||
}
|
||||
|
||||
(async ( ) => {
|
||||
let pane = null;
|
||||
if ( self.location.hash !== '' ) {
|
||||
pane = self.location.hash.slice(1) || null;
|
||||
}
|
||||
loadDashboardPanel(pane !== null ? pane : '3p-filters.html', true);
|
||||
|
||||
dom.on(
|
||||
qs$('#dashboard-nav'),
|
||||
'click',
|
||||
'.tabButton',
|
||||
onTabClickHandler
|
||||
);
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
|
||||
window.addEventListener('beforeunload', ( ) => {
|
||||
if ( discardUnsavedData(true) ) { return; }
|
||||
event.preventDefault();
|
||||
event.returnValue = '';
|
||||
});
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
121
platform/mv3/extension/js/dom.js
Normal file
121
platform/mv3/extension/js/dom.js
Normal file
@ -0,0 +1,121 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
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
|
||||
*/
|
||||
|
||||
/* jshint esversion:11 */
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function normalizeTarget(target) {
|
||||
if ( target === null ) { return []; }
|
||||
if ( Array.isArray(target) ) { return target; }
|
||||
return target instanceof Element
|
||||
? [ target ]
|
||||
: Array.from(target);
|
||||
}
|
||||
|
||||
function makeEventHandler(selector, callback) {
|
||||
return function(event) {
|
||||
const dispatcher = event.currentTarget;
|
||||
if (
|
||||
dispatcher instanceof HTMLElement === false ||
|
||||
typeof dispatcher.querySelectorAll !== 'function'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const receiver = event.target;
|
||||
const ancestor = receiver.closest(selector);
|
||||
if (
|
||||
ancestor === receiver &&
|
||||
ancestor !== dispatcher &&
|
||||
dispatcher.contains(ancestor)
|
||||
) {
|
||||
callback.call(receiver, event);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
class dom {
|
||||
|
||||
static addClass(target, cl) {
|
||||
for ( const elem of normalizeTarget(target) ) {
|
||||
elem.classList.add(cl);
|
||||
}
|
||||
}
|
||||
|
||||
static toggleClass(target, cl, state = undefined) {
|
||||
for ( const elem of normalizeTarget(target) ) {
|
||||
elem.classList.toggle(cl, state);
|
||||
}
|
||||
}
|
||||
|
||||
static removeClass(target, cl) {
|
||||
for ( const elem of normalizeTarget(target) ) {
|
||||
elem.classList.remove(cl);
|
||||
}
|
||||
}
|
||||
|
||||
static attr(target, attr, value = undefined) {
|
||||
for ( const elem of normalizeTarget(target) ) {
|
||||
if ( value === undefined ) {
|
||||
return elem.getAttribute(attr);
|
||||
}
|
||||
elem.setAttribute(attr, value);
|
||||
}
|
||||
}
|
||||
|
||||
static remove(target) {
|
||||
for ( const elem of normalizeTarget(target) ) {
|
||||
elem.remove();
|
||||
}
|
||||
}
|
||||
|
||||
static on(target, type, selector, callback) {
|
||||
if ( typeof selector === 'function' ) {
|
||||
callback = selector;
|
||||
selector = undefined;
|
||||
} else {
|
||||
callback = makeEventHandler(selector, callback);
|
||||
}
|
||||
for ( const elem of normalizeTarget(target) ) {
|
||||
elem.addEventListener(type, callback, selector !== undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dom.body = document.body;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function qs$(s, elem = undefined) {
|
||||
return (elem || document).querySelector(s);
|
||||
}
|
||||
|
||||
function qsa$(s, elem = undefined) {
|
||||
return (elem || document).querySelectorAll(s);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export { dom, qs$, qsa$ };
|
64
platform/mv3/extension/js/ext.js
Normal file
64
platform/mv3/extension/js/ext.js
Normal file
@ -0,0 +1,64 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2022-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
|
||||
*/
|
||||
|
||||
/* jshint esversion:11 */
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const browser =
|
||||
self.browser instanceof Object &&
|
||||
self.browser instanceof Element === false
|
||||
? self.browser
|
||||
: self.chrome;
|
||||
|
||||
const dnr = browser.declarativeNetRequest;
|
||||
const i18n = browser.i18n;
|
||||
const runtime = browser.runtime;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// The extension's service worker can be evicted at any time, so when we
|
||||
// send a message, we try a few more times when the message fails to be sent.
|
||||
|
||||
function sendMessage(msg) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let i = 5;
|
||||
const send = ( ) => {
|
||||
runtime.sendMessage(msg).then(response => {
|
||||
resolve(response);
|
||||
}).catch(reason => {
|
||||
i -= 1;
|
||||
if ( i <= 0 ) {
|
||||
reject(reason);
|
||||
} else {
|
||||
setTimeout(send, 200);
|
||||
}
|
||||
});
|
||||
};
|
||||
send();
|
||||
});
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export { browser, dnr, i18n, runtime, sendMessage };
|
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2014-present Raymond Hill
|
||||
Copyright (C) 2022-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
|
||||
@ -19,35 +19,23 @@
|
||||
Home: https://github.com/gorhill/uBlock
|
||||
*/
|
||||
|
||||
/* jshint esversion:11 */
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
import { browser, sendMessage } from './ext.js';
|
||||
import { i18n$ } from './i18n.js';
|
||||
import { simpleStorage } from './storage.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
let currentTab = {};
|
||||
let originalTrustedState = false;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
class safeLocalStorage {
|
||||
static getItem(k) {
|
||||
try {
|
||||
return self.localStorage.getItem(k);
|
||||
}
|
||||
catch(ex) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
static setItem(k, v) {
|
||||
try {
|
||||
self.localStorage.setItem(k, v);
|
||||
}
|
||||
catch(ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function toggleTrustedSiteDirective() {
|
||||
let url;
|
||||
try {
|
||||
@ -57,7 +45,7 @@ async function toggleTrustedSiteDirective() {
|
||||
}
|
||||
if ( url instanceof URL === false ) { return; }
|
||||
const targetTrustedState = document.body.classList.contains('off');
|
||||
const newTrustedState = await chrome.runtime.sendMessage({
|
||||
const newTrustedState = await sendMessage({
|
||||
what: 'toggleTrustedSiteDirective',
|
||||
origin: url.origin,
|
||||
state: targetTrustedState,
|
||||
@ -75,7 +63,7 @@ async function toggleTrustedSiteDirective() {
|
||||
/******************************************************************************/
|
||||
|
||||
function reloadTab(ev) {
|
||||
chrome.tabs.reload(currentTab.id, {
|
||||
browser.tabs.reload(currentTab.id, {
|
||||
bypassCache: ev.ctrlKey || ev.metaKey || ev.shiftKey,
|
||||
});
|
||||
document.body.classList.remove('needReload');
|
||||
@ -85,7 +73,7 @@ function reloadTab(ev) {
|
||||
/******************************************************************************/
|
||||
|
||||
async function init() {
|
||||
const [ tab ] = await chrome.tabs.query({ active: true });
|
||||
const [ tab ] = await browser.tabs.query({ active: true });
|
||||
if ( tab instanceof Object === false ) { return true; }
|
||||
currentTab = tab;
|
||||
|
||||
@ -97,7 +85,7 @@ async function init() {
|
||||
|
||||
let popupPanelData;
|
||||
if ( url !== undefined ) {
|
||||
popupPanelData = await chrome.runtime.sendMessage({
|
||||
popupPanelData = await sendMessage({
|
||||
what: 'popupPanelData',
|
||||
origin: url.origin,
|
||||
});
|
||||
@ -126,7 +114,9 @@ async function init() {
|
||||
h1.textContent = details.name;
|
||||
parent.append(h1);
|
||||
const p = document.createElement('p');
|
||||
p.textContent = `${details.ruleCount.toLocaleString()} rules, converted from ${details.filterCount.toLocaleString()} network filters`;
|
||||
p.textContent = i18n$('perRulesetStats')
|
||||
.replace('{{ruleCount}}', details.ruleCount.toLocaleString())
|
||||
.replace('{{filterCount}}', details.filterCount.toLocaleString());
|
||||
parent.append(p);
|
||||
}
|
||||
}
|
||||
@ -189,12 +179,12 @@ async function toggleSections(more) {
|
||||
}
|
||||
if ( newBits === currentBits ) { return; }
|
||||
sectionBitsToAttribute(newBits);
|
||||
safeLocalStorage.setItem('popupPanelSections', newBits);
|
||||
simpleStorage.setItem('popupPanelSections', newBits);
|
||||
}
|
||||
|
||||
sectionBitsToAttribute(
|
||||
parseInt(safeLocalStorage.getItem('popupPanelSections'), 10)
|
||||
);
|
||||
simpleStorage.getItem('popupPanelSections').then(s => {
|
||||
sectionBitsToAttribute(parseInt(s, 10) || 0);
|
||||
});
|
||||
|
||||
document.querySelector('#moreButton').addEventListener('click', ( ) => {
|
||||
toggleSections(true);
|
||||
|
44
platform/mv3/extension/js/storage.js
Normal file
44
platform/mv3/extension/js/storage.js
Normal file
@ -0,0 +1,44 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2022-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
|
||||
*/
|
||||
|
||||
/* jshint esversion:11 */
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export class simpleStorage {
|
||||
static getItem(k) {
|
||||
try {
|
||||
return Promise.resolve(JSON.parse(self.localStorage.getItem(k)));
|
||||
}
|
||||
catch(ex) {
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
static setItem(k, v) {
|
||||
try {
|
||||
self.localStorage.setItem(k, JSON.stringify(v));
|
||||
}
|
||||
catch(ex) {
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
"rule_resources": [
|
||||
]
|
||||
},
|
||||
"default_locale": "en",
|
||||
"description": "uBO Minus is permission-less experimental MV3-based network request blocker",
|
||||
"icons": {
|
||||
"16": "img/icon_16.png",
|
||||
@ -26,6 +27,7 @@
|
||||
"manifest_version": 3,
|
||||
"minimum_chrome_version": "101.0",
|
||||
"name": "uBlock Origin Lite",
|
||||
"options_page": "dashboard.html",
|
||||
"permissions": [
|
||||
"activeTab",
|
||||
"declarativeNetRequest"
|
||||
|
@ -19,7 +19,7 @@
|
||||
<span id="saveRules" class="fa-icon" data-i18n-title="popupTipSaveRules">lock</span>
|
||||
<span id="revertRules" class="fa-icon" data-i18n-title="popupTipRevertRules">eraser</span>
|
||||
</div>
|
||||
<div id="switch" role="button" aria-label tabindex="0" title>
|
||||
<div id="switch" role="button" aria-label tabindex="0" data-i18n-title="popupPowerSwitchInfo">
|
||||
<span class="fa-icon"><!--
|
||||
Power button taken from Font Awesome v4.7.0 by Dave Gandy.
|
||||
Unlike other FA icons, the power button is inlined here so
|
||||
@ -40,6 +40,13 @@
|
||||
</div>
|
||||
<div id="hostname"><span></span>­<span></span></div>
|
||||
</div>
|
||||
<div id="basicTools" class="toolRibbon" data-more="c">
|
||||
<span class="fa-icon tool needPick" data-i18n-title="popupTipZapper">bolt<span class="caption"></span></span>
|
||||
<span class="fa-icon tool needPick" data-i18n-title="popupTipPicker">eye-dropper<span class="caption"></span></span>
|
||||
<span class="fa-icon tool needPick" data-i18n-title="popupTipReport">comment-alt<span class="caption"></span></span>
|
||||
<a href="logger-ui.html#_" class="fa-icon tool" target="uBOLogger" tabindex="0" data-i18n-title="popupTipLog">list-alt<span class="caption"></span></a>
|
||||
<a href="dashboard.html" class="fa-icon tool enabled" target="uBODashboard" tabindex="0" data-i18n-title="popupTipDashboard">cogs<span class="caption" data-i18n="popupTipDashboard"></span></a>
|
||||
</div>
|
||||
<hr data-section="a">
|
||||
<div id="rulesetStats" data-section="a">
|
||||
</div>
|
||||
@ -55,6 +62,7 @@
|
||||
</div>
|
||||
|
||||
<script src="js/fa-icons.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/popup.js" type="module"></script>
|
||||
|
||||
</body>
|
||||
|
@ -27,8 +27,8 @@ import fs from 'fs/promises';
|
||||
import https from 'https';
|
||||
import process from 'process';
|
||||
|
||||
import rulesetConfigs from './ruleset-config.js';
|
||||
import { dnrRulesetFromRawLists } from './js/static-dnr-filtering.js';
|
||||
import { StaticFilteringParser } from './js/static-filtering-parser.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
@ -51,21 +51,75 @@ const commandLineArgs = (( ) => {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const isUnsupported = rule =>
|
||||
rule._error !== undefined;
|
||||
|
||||
const isRegex = rule =>
|
||||
rule.condition !== undefined &&
|
||||
rule.condition.regexFilter !== undefined;
|
||||
|
||||
const isRedirect = rule =>
|
||||
rule.action !== undefined &&
|
||||
rule.action.type === 'redirect' &&
|
||||
rule.action.redirect.extensionPath !== undefined;
|
||||
|
||||
const isCsp = rule =>
|
||||
rule.action !== undefined &&
|
||||
rule.action.type === 'modifyHeaders';
|
||||
|
||||
const isRemoveparam = rule =>
|
||||
rule.action !== undefined &&
|
||||
rule.action.type === 'redirect' &&
|
||||
rule.action.redirect.transform !== undefined;
|
||||
|
||||
const isGood = rule =>
|
||||
isUnsupported(rule) === false &&
|
||||
isRedirect(rule) === false &&
|
||||
isCsp(rule) === false &&
|
||||
isRemoveparam(rule) === false;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const stdOutput = [];
|
||||
|
||||
const log = (text, silent = false) => {
|
||||
stdOutput.push(text);
|
||||
if ( silent === false ) {
|
||||
console.log(text);
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const fetchList = url => {
|
||||
return new Promise((resolve, reject) => {
|
||||
log(`\tFetching ${url}`);
|
||||
https.get(url, response => {
|
||||
const data = [];
|
||||
response.on('data', chunk => {
|
||||
data.push(chunk.toString());
|
||||
});
|
||||
response.on('end', ( ) => {
|
||||
resolve({ url, content: data.join('') });
|
||||
});
|
||||
}).on('error', error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
async function main() {
|
||||
|
||||
const env = [ 'chromium' ];
|
||||
|
||||
const writeOps = [];
|
||||
const ruleResources = [];
|
||||
const rulesetDetails = [];
|
||||
const regexRulesetDetails = new Map();
|
||||
const outputDir = commandLineArgs.get('output') || '.';
|
||||
|
||||
const output = [];
|
||||
const log = (text, silent = false) => {
|
||||
output.push(text);
|
||||
if ( silent === false ) {
|
||||
console.log(text);
|
||||
}
|
||||
};
|
||||
|
||||
// Get manifest content
|
||||
const manifest = await fs.readFile(
|
||||
`${outputDir}/manifest.json`,
|
||||
@ -104,78 +158,62 @@ async function main() {
|
||||
return v;
|
||||
};
|
||||
|
||||
const isUnsupported = rule =>
|
||||
rule._error !== undefined;
|
||||
const isRegex = rule =>
|
||||
rule.condition !== undefined &&
|
||||
rule.condition.regexFilter !== undefined;
|
||||
const isRedirect = rule =>
|
||||
rule.action !== undefined &&
|
||||
rule.action.type === 'redirect' &&
|
||||
rule.action.redirect.extensionPath !== undefined;
|
||||
const isCsp = rule =>
|
||||
rule.action !== undefined &&
|
||||
rule.action.type === 'modifyHeaders';
|
||||
const isRemoveparam = rule =>
|
||||
rule.action !== undefined &&
|
||||
rule.action.type === 'redirect' &&
|
||||
rule.action.redirect.transform !== undefined;
|
||||
const isGood = rule =>
|
||||
isUnsupported(rule) === false &&
|
||||
isRedirect(rule) === false &&
|
||||
isCsp(rule) === false &&
|
||||
isRemoveparam(rule) === false
|
||||
;
|
||||
|
||||
const rulesetDir = `${outputDir}/rulesets`;
|
||||
const rulesetDirPromise = fs.mkdir(`${rulesetDir}`, { recursive: true });
|
||||
|
||||
const fetchList = url => {
|
||||
return new Promise((resolve, reject) => {
|
||||
https.get(url, response => {
|
||||
const data = [];
|
||||
response.on('data', chunk => {
|
||||
data.push(chunk.toString());
|
||||
});
|
||||
response.on('end', ( ) => {
|
||||
resolve({ name: url, text: data.join('') });
|
||||
});
|
||||
}).on('error', error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const readList = path =>
|
||||
fs.readFile(path, { encoding: 'utf8' })
|
||||
.then(text => ({ name: path, text }));
|
||||
|
||||
const writeFile = (path, data) =>
|
||||
rulesetDirPromise.then(( ) =>
|
||||
fs.writeFile(path, data));
|
||||
|
||||
for ( const ruleset of rulesetConfigs ) {
|
||||
const lists = [];
|
||||
|
||||
const rulesetFromURLS = async function(assetDetails) {
|
||||
log('============================');
|
||||
log(`Listset for '${ruleset.id}':`);
|
||||
log(`Listset for '${assetDetails.id}':`);
|
||||
|
||||
if ( Array.isArray(ruleset.paths) ) {
|
||||
for ( const path of ruleset.paths ) {
|
||||
log(`\t${path}`);
|
||||
lists.push(readList(`assets/${path}`));
|
||||
// Remember fetched URLs
|
||||
const fetchedURLs = new Set();
|
||||
|
||||
// Fetch list and expand `!#include` directives
|
||||
let parts = assetDetails.urls.map(url => ({ url }));
|
||||
while ( parts.every(v => typeof v === 'string') === false ) {
|
||||
const newParts = [];
|
||||
for ( const part of parts ) {
|
||||
if ( typeof part === 'string' ) {
|
||||
newParts.push(part);
|
||||
continue;
|
||||
}
|
||||
if ( fetchedURLs.has(part.url) ) {
|
||||
newParts.push('');
|
||||
continue;
|
||||
}
|
||||
fetchedURLs.add(part.url);
|
||||
newParts.push(
|
||||
fetchList(part.url).then(details => {
|
||||
const { url } = details;
|
||||
const content = details.content.trim();
|
||||
if ( typeof content === 'string' && content !== '' ) {
|
||||
if (
|
||||
content.startsWith('<') === false ||
|
||||
content.endsWith('>') === false
|
||||
) {
|
||||
return { url, content };
|
||||
}
|
||||
}
|
||||
log(`No valid content for ${details.name}`);
|
||||
return { url, content: '' };
|
||||
})
|
||||
);
|
||||
}
|
||||
parts = await Promise.all(newParts);
|
||||
parts = StaticFilteringParser.utils.preparser.expandIncludes(parts, env);
|
||||
}
|
||||
if ( Array.isArray(ruleset.urls) ) {
|
||||
for ( const url of ruleset.urls ) {
|
||||
log(`\t${url}`);
|
||||
lists.push(fetchList(url));
|
||||
}
|
||||
const text = parts.join('\n');
|
||||
|
||||
if ( text === '' ) {
|
||||
log('No filterset found');
|
||||
return;
|
||||
}
|
||||
|
||||
const details = await dnrRulesetFromRawLists(lists, {
|
||||
env: [ 'chromium' ],
|
||||
});
|
||||
const details = await dnrRulesetFromRawLists([ { name: assetDetails.id, text } ], { env });
|
||||
const { ruleset: rules } = details;
|
||||
log(`Input filter count: ${details.filterCount}`);
|
||||
log(`\tAccepted filter count: ${details.acceptedFilterCount}`);
|
||||
@ -215,17 +253,11 @@ async function main() {
|
||||
true
|
||||
);
|
||||
|
||||
writeOps.push(
|
||||
writeFile(
|
||||
`${rulesetDir}/${ruleset.id}.json`,
|
||||
`${JSON.stringify(good, replacer, 2)}\n`
|
||||
)
|
||||
);
|
||||
|
||||
rulesetDetails.push({
|
||||
id: ruleset.id,
|
||||
name: ruleset.name,
|
||||
enabled: ruleset.enabled,
|
||||
id: assetDetails.id,
|
||||
name: assetDetails.name,
|
||||
enabled: assetDetails.enabled,
|
||||
lang: assetDetails.lang,
|
||||
filters: {
|
||||
total: details.filterCount,
|
||||
accepted: details.acceptedFilterCount,
|
||||
@ -236,24 +268,100 @@ async function main() {
|
||||
accepted: good.length,
|
||||
discarded: redirects.length + headers.length + removeparams.length,
|
||||
rejected: bad.length,
|
||||
regexes,
|
||||
regexes: regexes.length,
|
||||
},
|
||||
});
|
||||
|
||||
writeOps.push(
|
||||
writeFile(
|
||||
`${rulesetDir}/${assetDetails.id}.json`,
|
||||
`${JSON.stringify(good, replacer, 2)}\n`
|
||||
)
|
||||
);
|
||||
|
||||
regexRulesetDetails.set(assetDetails.id, regexes);
|
||||
|
||||
writeOps.push(
|
||||
writeFile(
|
||||
`${rulesetDir}/${assetDetails.id}.regexes.json`,
|
||||
`${JSON.stringify(regexes, replacer, 2)}\n`
|
||||
)
|
||||
);
|
||||
|
||||
ruleResources.push({
|
||||
id: ruleset.id,
|
||||
enabled: ruleset.enabled,
|
||||
path: `/rulesets/${ruleset.id}.json`
|
||||
id: assetDetails.id,
|
||||
enabled: assetDetails.enabled,
|
||||
path: `/rulesets/${assetDetails.id}.json`
|
||||
});
|
||||
|
||||
goodTotalCount += good.length;
|
||||
maybeGoodTotalCount += regexes.length;
|
||||
};
|
||||
|
||||
// Get assets.json content
|
||||
const assets = await fs.readFile(
|
||||
`./assets.json`,
|
||||
{ encoding: 'utf8' }
|
||||
).then(text =>
|
||||
JSON.parse(text)
|
||||
);
|
||||
|
||||
// Assemble all default lists as the default ruleset
|
||||
const contentURLs = [];
|
||||
for ( const asset of Object.values(assets) ) {
|
||||
if ( asset.content !== 'filters' ) { continue; }
|
||||
if ( asset.off === true ) { continue; }
|
||||
const contentURL = Array.isArray(asset.contentURL)
|
||||
? asset.contentURL[0]
|
||||
: asset.contentURL;
|
||||
contentURLs.push(contentURL);
|
||||
}
|
||||
await rulesetFromURLS({
|
||||
id: 'default',
|
||||
name: 'Ads, trackers, miners, and more' ,
|
||||
enabled: true,
|
||||
urls: contentURLs,
|
||||
});
|
||||
|
||||
// Regional rulesets
|
||||
for ( const [ id, asset ] of Object.entries(assets) ) {
|
||||
if ( asset.content !== 'filters' ) { continue; }
|
||||
if ( asset.off !== true ) { continue; }
|
||||
if ( typeof asset.lang !== 'string' ) { continue; }
|
||||
|
||||
const contentURL = Array.isArray(asset.contentURL)
|
||||
? asset.contentURL[0]
|
||||
: asset.contentURL;
|
||||
await rulesetFromURLS({
|
||||
id: id.toLowerCase(),
|
||||
lang: asset.lang,
|
||||
name: asset.title,
|
||||
enabled: false,
|
||||
urls: [ contentURL ],
|
||||
});
|
||||
}
|
||||
|
||||
// Handpicked rulesets
|
||||
const handpicked = [ 'block-lan', 'dpollock-0' ];
|
||||
for ( const id of handpicked ) {
|
||||
const asset = assets[id];
|
||||
if ( asset.content !== 'filters' ) { continue; }
|
||||
|
||||
const contentURL = Array.isArray(asset.contentURL)
|
||||
? asset.contentURL[0]
|
||||
: asset.contentURL;
|
||||
await rulesetFromURLS({
|
||||
id: id.toLowerCase(),
|
||||
name: asset.title,
|
||||
enabled: false,
|
||||
urls: [ contentURL ],
|
||||
});
|
||||
}
|
||||
|
||||
writeOps.push(
|
||||
writeFile(
|
||||
`${rulesetDir}/ruleset-details.js`,
|
||||
`export default ${JSON.stringify(rulesetDetails, replacer, 2)};\n`
|
||||
`${rulesetDir}/ruleset-details.json`,
|
||||
`${JSON.stringify(rulesetDetails, replacer, 2)}\n`
|
||||
)
|
||||
);
|
||||
|
||||
@ -276,7 +384,7 @@ async function main() {
|
||||
);
|
||||
|
||||
// Log results
|
||||
await fs.writeFile(`${outputDir}/log.txt`, output.join('\n') + '\n');
|
||||
await fs.writeFile(`${outputDir}/log.txt`, stdOutput.join('\n') + '\n');
|
||||
}
|
||||
|
||||
main();
|
||||
|
@ -1,74 +0,0 @@
|
||||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2022-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
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export default [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default: Ads and trackers',
|
||||
enabled: true,
|
||||
paths: [
|
||||
],
|
||||
urls: [
|
||||
'https://ublockorigin.github.io/uAssets/filters/badware.txt',
|
||||
'https://ublockorigin.github.io/uAssets/filters/filters.txt',
|
||||
'https://ublockorigin.github.io/uAssets/filters/filters-2020.txt',
|
||||
'https://ublockorigin.github.io/uAssets/filters/filters-2021.txt',
|
||||
'https://ublockorigin.github.io/uAssets/filters/filters-2022.txt',
|
||||
'https://ublockorigin.github.io/uAssets/filters/privacy.txt',
|
||||
'https://ublockorigin.github.io/uAssets/filters/quick-fixes.txt',
|
||||
'https://ublockorigin.github.io/uAssets/filters/resource-abuse.txt',
|
||||
'https://ublockorigin.github.io/uAssets/filters/unbreak.txt',
|
||||
'https://easylist.to/easylist/easylist.txt',
|
||||
'https://easylist.to/easylist/easyprivacy.txt',
|
||||
'https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=1&mimetype=plaintext',
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'DEU-0',
|
||||
name: 'DEU: EasyList Germany',
|
||||
enabled: false,
|
||||
paths: [
|
||||
],
|
||||
urls: [
|
||||
'https://easylist.to/easylistgermany/easylistgermany.txt',
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'RUS-0',
|
||||
name: 'RUS: RU AdList',
|
||||
enabled: false,
|
||||
paths: [
|
||||
],
|
||||
urls: [
|
||||
'https://raw.githubusercontent.com/easylist/ruadlist/master/advblock/adservers.txt',
|
||||
'https://raw.githubusercontent.com/easylist/ruadlist/master/advblock/first_level.txt',
|
||||
'https://raw.githubusercontent.com/easylist/ruadlist/master/advblock/general_block.txt',
|
||||
'https://raw.githubusercontent.com/easylist/ruadlist/master/advblock/specific_antisocial.txt',
|
||||
'https://raw.githubusercontent.com/easylist/ruadlist/master/advblock/specific_block.txt',
|
||||
'https://raw.githubusercontent.com/easylist/ruadlist/master/advblock/specific_special.txt',
|
||||
'https://raw.githubusercontent.com/easylist/ruadlist/master/advblock/thirdparty.txt',
|
||||
'https://raw.githubusercontent.com/easylist/ruadlist/master/advblock/whitelist.txt',
|
||||
'https://raw.githubusercontent.com/easylist/ruadlist/master/advblock/AWRL-non-sync.txt',
|
||||
]
|
||||
},
|
||||
];
|
@ -61,9 +61,9 @@
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/vapi-client-extra.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js"></script>
|
||||
<script src="js/cloud-ui.js"></script>
|
||||
<script src="js/cloud-ui.js" type="module"></script>
|
||||
<script src="js/1p-filters.js" type="module"></script>
|
||||
|
||||
</body>
|
||||
|
@ -78,10 +78,10 @@
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/vapi-client-extra.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js"></script>
|
||||
<script src="js/cloud-ui.js"></script>
|
||||
<script src="js/3p-filters.js"></script>
|
||||
<script src="js/cloud-ui.js" type="module"></script>
|
||||
<script src="js/3p-filters.js" type="module"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -52,7 +52,7 @@
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js"></script>
|
||||
<script src="js/about.js"></script>
|
||||
|
||||
|
@ -34,7 +34,7 @@
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js"></script>
|
||||
<script src="js/advanced-settings.js"></script>
|
||||
|
||||
|
@ -43,7 +43,7 @@
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js"></script>
|
||||
<script src="js/asset-viewer.js" type="module"></script>
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>uBlock Origin</title>
|
||||
<title>uBlock Origin Background Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="lib/lz4/lz4-block-codec-any.js"></script>
|
||||
|
@ -40,7 +40,7 @@
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard.js"></script>
|
||||
|
||||
</body>
|
||||
|
@ -49,7 +49,7 @@
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js"></script>
|
||||
<script src="js/devtools.js" type="module"></script>
|
||||
|
||||
|
@ -57,7 +57,7 @@
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/document-blocked.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/document-blocked.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -58,9 +58,9 @@
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js"></script>
|
||||
<script src="js/cloud-ui.js"></script>
|
||||
<script src="js/cloud-ui.js" type="module"></script>
|
||||
<script src="js/dyna-rules.js" type="module"></script>
|
||||
|
||||
</body>
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
import { i18n$ } from './i18n.js';
|
||||
import './codemirror/ubo-static-filtering.js';
|
||||
|
||||
/******************************************************************************/
|
||||
@ -234,7 +235,7 @@ const startImportFilePicker = function() {
|
||||
const exportUserFiltersToFile = function() {
|
||||
const val = getEditorText();
|
||||
if ( val === '' ) { return; }
|
||||
const filename = vAPI.i18n('1pExportFilename')
|
||||
const filename = i18n$('1pExportFilename')
|
||||
.replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString())
|
||||
.replace(/ +/g, '_');
|
||||
vAPI.download({
|
||||
|
@ -25,13 +25,12 @@
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
{
|
||||
// >>>>> start of local scope
|
||||
import { i18n, i18n$ } from './i18n.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const lastUpdateTemplateString = vAPI.i18n('3pLastUpdate');
|
||||
const obsoleteTemplateString = vAPI.i18n('3pExternalListObsolete');
|
||||
const lastUpdateTemplateString = i18n$('3pLastUpdate');
|
||||
const obsoleteTemplateString = i18n$('3pExternalListObsolete');
|
||||
const reValidExternalList = /^[a-z-]+:\/\/(?:\S+\/\S*|\/\S+)/m;
|
||||
|
||||
let listDetails = {};
|
||||
@ -70,8 +69,8 @@ const renderNumber = function(value) {
|
||||
const renderFilterLists = function(soft) {
|
||||
const listGroupTemplate = uDom('#templates .groupEntry');
|
||||
const listEntryTemplate = uDom('#templates .listEntry');
|
||||
const listStatsTemplate = vAPI.i18n('3pListsOfBlockedHostsPerListStats');
|
||||
const renderElapsedTimeToString = vAPI.i18n.renderElapsedTimeToString;
|
||||
const listStatsTemplate = i18n$('3pListsOfBlockedHostsPerListStats');
|
||||
const renderElapsedTimeToString = i18n.renderElapsedTimeToString;
|
||||
const groupNames = new Map([ [ 'user', '' ] ]);
|
||||
|
||||
// Assemble a pretty list name if possible
|
||||
@ -189,7 +188,7 @@ const renderFilterLists = function(soft) {
|
||||
liGroup = listGroupTemplate.clone().nodeAt(0);
|
||||
let groupName = groupNames.get(groupKey);
|
||||
if ( groupName === undefined ) {
|
||||
groupName = vAPI.i18n('3pGroup' + groupKey.charAt(0).toUpperCase() + groupKey.slice(1));
|
||||
groupName = i18n$('3pGroup' + groupKey.charAt(0).toUpperCase() + groupKey.slice(1));
|
||||
groupNames.set(groupKey, groupName);
|
||||
}
|
||||
if ( groupName !== '' ) {
|
||||
@ -289,7 +288,7 @@ const renderFilterLists = function(soft) {
|
||||
uDom.nodeFromId('autoUpdate').checked =
|
||||
listDetails.autoUpdate === true;
|
||||
uDom.nodeFromId('listsOfBlockedHostsPrompt').textContent =
|
||||
vAPI.i18n('3pListsOfBlockedHostsPrompt')
|
||||
i18n$('3pListsOfBlockedHostsPrompt')
|
||||
.replace(
|
||||
'{{netFilterCount}}',
|
||||
renderNumber(details.netFilterCount)
|
||||
@ -362,7 +361,7 @@ const updateAssetStatus = function(details) {
|
||||
'title',
|
||||
lastUpdateTemplateString.replace(
|
||||
'{{ago}}',
|
||||
vAPI.i18n.renderElapsedTimeToString(Date.now())
|
||||
i18n$.renderElapsedTimeToString(Date.now())
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -711,7 +710,3 @@ uDom('#lists').on('click', '.listEntry label *', ev => {
|
||||
renderFilterLists();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// <<<<< end of local scope
|
||||
}
|
||||
|
||||
|
@ -27,12 +27,13 @@ import cacheStorage from './cachestorage.js';
|
||||
import logger from './logger.js';
|
||||
import µb from './background.js';
|
||||
import { StaticFilteringParser } from './static-filtering-parser.js';
|
||||
import { i18n$ } from './i18n.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const reIsExternalPath = /^(?:[a-z-]+):\/\//;
|
||||
const reIsUserAsset = /^user-/;
|
||||
const errorCantConnectTo = vAPI.i18n('errorCantConnectTo');
|
||||
const errorCantConnectTo = i18n$('errorCantConnectTo');
|
||||
|
||||
const assets = {};
|
||||
|
||||
|
@ -23,6 +23,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { i18n, i18n$ } from './i18n.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
(( ) => {
|
||||
@ -58,7 +60,7 @@ const fetchStorageUsed = async function() {
|
||||
elem.classList.add('hide');
|
||||
return;
|
||||
}
|
||||
const units = ' ' + vAPI.i18n('genericBytes');
|
||||
const units = ' ' + i18n$('genericBytes');
|
||||
elem.title = result.max.toLocaleString() + units;
|
||||
const total = (result.total / result.max * 100).toFixed(1);
|
||||
elem = elem.firstElementChild;
|
||||
@ -206,7 +208,7 @@ const onInitialize = function(options) {
|
||||
|
||||
faIconsInit(widget);
|
||||
|
||||
vAPI.i18n.render(widget);
|
||||
i18n.render(widget);
|
||||
widget.classList.remove('hide');
|
||||
|
||||
uDom('#cloudPush').on('click', ( ) => { pushData(); });
|
||||
|
@ -24,6 +24,7 @@
|
||||
/******************************************************************************/
|
||||
|
||||
import µb from './background.js';
|
||||
import { i18n$ } from './i18n.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
@ -134,28 +135,28 @@ const onEntryClicked = function(details, tab) {
|
||||
const menuEntries = {
|
||||
blockElement: {
|
||||
id: 'uBlock0-blockElement',
|
||||
title: vAPI.i18n('pickerContextMenuEntry'),
|
||||
title: i18n$('pickerContextMenuEntry'),
|
||||
contexts: [ 'all' ],
|
||||
},
|
||||
blockElementInFrame: {
|
||||
id: 'uBlock0-blockElementInFrame',
|
||||
title: vAPI.i18n('contextMenuBlockElementInFrame'),
|
||||
title: i18n$('contextMenuBlockElementInFrame'),
|
||||
contexts: [ 'frame' ],
|
||||
},
|
||||
blockResource: {
|
||||
id: 'uBlock0-blockResource',
|
||||
title: vAPI.i18n('pickerContextMenuEntry'),
|
||||
title: i18n$('pickerContextMenuEntry'),
|
||||
contexts: [ 'audio', 'frame', 'image', 'video' ],
|
||||
},
|
||||
subscribeToList: {
|
||||
id: 'uBlock0-subscribeToList',
|
||||
title: vAPI.i18n('contextMenuSubscribeToList'),
|
||||
title: i18n$('contextMenuSubscribeToList'),
|
||||
contexts: [ 'link' ],
|
||||
targetUrlPatterns: [ 'abp:*', 'https://subscribe.adblockplus.org/*' ],
|
||||
},
|
||||
temporarilyAllowLargeMediaElements: {
|
||||
id: 'uBlock0-temporarilyAllowLargeMediaElements',
|
||||
title: vAPI.i18n('contextMenuTemporarilyAllowLargeMediaElements'),
|
||||
title: i18n$('contextMenuTemporarilyAllowLargeMediaElements'),
|
||||
contexts: [ 'all' ],
|
||||
}
|
||||
};
|
||||
|
@ -25,11 +25,6 @@
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
{
|
||||
// >>>>> start of local scope
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const discardUnsavedData = function(synchronous = false) {
|
||||
const paneFrame = document.getElementById('iframe');
|
||||
const paneWindow = paneFrame.contentWindow;
|
||||
@ -148,8 +143,3 @@ if ( self.location.hash.slice(1) === 'no-dashboard.html' ) {
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// <<<<< end of local scope
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
(( ) => {
|
||||
import { i18n$ } from './i18n.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
@ -128,7 +128,7 @@ uDom.nodeFromId('why').textContent = details.fs;
|
||||
if ( search === '' ) { return false; }
|
||||
|
||||
url.search = '';
|
||||
const li = liFromParam(vAPI.i18n('docblockedNoParamsPrompt'), url.href);
|
||||
const li = liFromParam(i18n$('docblockedNoParamsPrompt'), url.href);
|
||||
parentNode.appendChild(li);
|
||||
|
||||
const params = new self.URLSearchParams(search);
|
||||
@ -239,7 +239,3 @@ uDom('#proceed').on('click', ( ) => {
|
||||
});
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -28,6 +28,7 @@
|
||||
import publicSuffixList from '../lib/publicsuffixlist/publicsuffixlist.js';
|
||||
|
||||
import { hostnameFromURI } from './uri-utils.js';
|
||||
import { i18n$ } from './i18n.js';
|
||||
|
||||
import './codemirror/ubo-dynamic-filtering.js';
|
||||
|
||||
@ -80,13 +81,13 @@ let isCollapsed = false;
|
||||
// reliably the default title attribute assigned by CodeMirror.
|
||||
|
||||
{
|
||||
const i18nCommitStr = vAPI.i18n('rulesCommit');
|
||||
const i18nRevertStr = vAPI.i18n('rulesRevert');
|
||||
const i18nCommitStr = i18n$('rulesCommit');
|
||||
const i18nRevertStr = i18n$('rulesRevert');
|
||||
const commitArrowSelector = '.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy-reverse:not([title="' + i18nCommitStr + '"])';
|
||||
const revertArrowSelector = '.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy:not([title="' + i18nRevertStr + '"])';
|
||||
|
||||
uDom.nodeFromSelector('.CodeMirror-merge-scrolllock')
|
||||
.setAttribute('title', vAPI.i18n('genericMergeViewScrollLock'));
|
||||
.setAttribute('title', i18n$('genericMergeViewScrollLock'));
|
||||
|
||||
const translate = function() {
|
||||
let elems = document.querySelectorAll(commitArrowSelector);
|
||||
@ -340,7 +341,7 @@ const startImportFilePicker = function() {
|
||||
/******************************************************************************/
|
||||
|
||||
function exportUserRulesToFile() {
|
||||
const filename = vAPI.i18n('rulesDefaultFileName')
|
||||
const filename = i18n$('rulesDefaultFileName')
|
||||
.replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString())
|
||||
.replace(/ +/g, '_');
|
||||
vAPI.download({
|
||||
|
532
src/js/i18n.js
532
src/js/i18n.js
@ -23,270 +23,280 @@
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// This file should always be included at the end of the `body` tag, so as
|
||||
// to ensure all i18n targets are already loaded.
|
||||
|
||||
{
|
||||
// >>>>> start of local scope
|
||||
const i18n =
|
||||
self.browser instanceof Object &&
|
||||
self.browser instanceof Element === false
|
||||
? self.browser.i18n
|
||||
: self.chrome.i18n;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/2084
|
||||
// Anything else than <a>, <b>, <code>, <em>, <i>, and <span> will
|
||||
// be rendered as plain text.
|
||||
// For <a>, only href attribute must be present, and it MUST starts with
|
||||
// `https://`, and includes no single- or double-quotes.
|
||||
// No HTML entities are allowed, there is code to handle existing HTML
|
||||
// entities already present in translation files until they are all gone.
|
||||
|
||||
const allowedTags = new Set([
|
||||
'a',
|
||||
'b',
|
||||
'code',
|
||||
'em',
|
||||
'i',
|
||||
'span',
|
||||
'u',
|
||||
]);
|
||||
|
||||
const expandHtmlEntities = (( ) => {
|
||||
const entities = new Map([
|
||||
// TODO: Remove quote entities once no longer present in translation
|
||||
// files. Other entities must stay.
|
||||
[ '­', '\u00AD' ],
|
||||
[ '“', '“' ],
|
||||
[ '”', '”' ],
|
||||
[ '‘', '‘' ],
|
||||
[ '’', '’' ],
|
||||
[ '<', '<' ],
|
||||
[ '>', '>' ],
|
||||
]);
|
||||
const decodeEntities = match => {
|
||||
return entities.get(match) || match;
|
||||
};
|
||||
return function(text) {
|
||||
if ( text.indexOf('&') !== -1 ) {
|
||||
text = text.replace(/&[a-z]+;/g, decodeEntities);
|
||||
}
|
||||
return text;
|
||||
};
|
||||
})();
|
||||
|
||||
const safeTextToTextNode = function(text) {
|
||||
return document.createTextNode(expandHtmlEntities(text));
|
||||
};
|
||||
|
||||
const sanitizeElement = function(node) {
|
||||
if ( allowedTags.has(node.localName) === false ) { return null; }
|
||||
node.removeAttribute('style');
|
||||
let child = node.firstElementChild;
|
||||
while ( child !== null ) {
|
||||
const next = child.nextElementSibling;
|
||||
if ( sanitizeElement(child) === null ) {
|
||||
child.remove();
|
||||
}
|
||||
child = next;
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
const safeTextToDOM = function(text, parent) {
|
||||
if ( text === '' ) { return; }
|
||||
|
||||
// Fast path (most common).
|
||||
if ( text.indexOf('<') === -1 ) {
|
||||
const toInsert = safeTextToTextNode(text);
|
||||
let toReplace = parent.childCount !== 0
|
||||
? parent.firstChild
|
||||
: null;
|
||||
while ( toReplace !== null ) {
|
||||
if ( toReplace.nodeType === 3 && toReplace.nodeValue === '_' ) {
|
||||
break;
|
||||
}
|
||||
toReplace = toReplace.nextSibling;
|
||||
}
|
||||
if ( toReplace !== null ) {
|
||||
parent.replaceChild(toInsert, toReplace);
|
||||
} else {
|
||||
parent.appendChild(toInsert);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Slow path.
|
||||
// `<p>` no longer allowed. Code below can be removed once all <p>'s are
|
||||
// gone from translation files.
|
||||
text = text.replace(/^<p>|<\/p>/g, '')
|
||||
.replace(/<p>/g, '\n\n');
|
||||
// Parse allowed HTML tags.
|
||||
const domParser = new DOMParser();
|
||||
const parsedDoc = domParser.parseFromString(text, 'text/html');
|
||||
let node = parsedDoc.body.firstChild;
|
||||
while ( node !== null ) {
|
||||
const next = node.nextSibling;
|
||||
switch ( node.nodeType ) {
|
||||
case 1: // element
|
||||
if ( sanitizeElement(node) === null ) { break; }
|
||||
parent.appendChild(node);
|
||||
break;
|
||||
case 3: // text
|
||||
parent.appendChild(node);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
node = next;
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.i18n.safeTemplateToDOM = function(id, dict, parent) {
|
||||
if ( parent === undefined ) {
|
||||
parent = document.createDocumentFragment();
|
||||
}
|
||||
let textin = vAPI.i18n(id);
|
||||
if ( textin === '' ) {
|
||||
return parent;
|
||||
}
|
||||
if ( textin.indexOf('{{') === -1 ) {
|
||||
safeTextToDOM(textin, parent);
|
||||
return parent;
|
||||
}
|
||||
const re = /\{\{\w+\}\}/g;
|
||||
let textout = '';
|
||||
for (;;) {
|
||||
let match = re.exec(textin);
|
||||
if ( match === null ) {
|
||||
textout += textin;
|
||||
break;
|
||||
}
|
||||
textout += textin.slice(0, match.index);
|
||||
let prop = match[0].slice(2, -2);
|
||||
if ( dict.hasOwnProperty(prop) ) {
|
||||
textout += dict[prop].replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
} else {
|
||||
textout += prop;
|
||||
}
|
||||
textin = textin.slice(re.lastIndex);
|
||||
}
|
||||
safeTextToDOM(textout, parent);
|
||||
return parent;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Helper to deal with the i18n'ing of HTML files.
|
||||
vAPI.i18n.render = function(context) {
|
||||
const docu = document;
|
||||
const root = context || docu;
|
||||
|
||||
for ( const elem of root.querySelectorAll('[data-i18n]') ) {
|
||||
let text = vAPI.i18n(elem.getAttribute('data-i18n'));
|
||||
if ( !text ) { continue; }
|
||||
if ( text.indexOf('{{') === -1 ) {
|
||||
safeTextToDOM(text, elem);
|
||||
continue;
|
||||
}
|
||||
// Handle selector-based placeholders: these placeholders tell where
|
||||
// existing child DOM element are to be positioned relative to the
|
||||
// localized text nodes.
|
||||
const parts = text.split(/(\{\{[^}]+\}\})/);
|
||||
const fragment = document.createDocumentFragment();
|
||||
let textBefore = '';
|
||||
for ( let part of parts ) {
|
||||
if ( part === '' ) { continue; }
|
||||
if ( part.startsWith('{{') && part.endsWith('}}') ) {
|
||||
// TODO: remove detection of ':' once it no longer appears
|
||||
// in translation files.
|
||||
const pos = part.indexOf(':');
|
||||
if ( pos !== -1 ) {
|
||||
part = part.slice(0, pos) + part.slice(-2);
|
||||
}
|
||||
const selector = part.slice(2, -2);
|
||||
let node;
|
||||
// Ideally, the i18n strings explicitly refer to the
|
||||
// class of the element to insert. However for now we
|
||||
// will create a class from what is currently found in
|
||||
// the placeholder and first try to lookup the resulting
|
||||
// selector. This way we don't have to revisit all
|
||||
// translations just for the sake of declaring the proper
|
||||
// selector in the placeholder field.
|
||||
if ( selector.charCodeAt(0) !== 0x2E /* '.' */ ) {
|
||||
node = elem.querySelector(`.${selector}`);
|
||||
}
|
||||
if ( node instanceof Element === false ) {
|
||||
node = elem.querySelector(selector);
|
||||
}
|
||||
if ( node instanceof Element ) {
|
||||
safeTextToDOM(textBefore, fragment);
|
||||
fragment.appendChild(node);
|
||||
textBefore = '';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
textBefore += part;
|
||||
}
|
||||
if ( textBefore !== '' ) {
|
||||
safeTextToDOM(textBefore, fragment);
|
||||
}
|
||||
elem.appendChild(fragment);
|
||||
}
|
||||
|
||||
for ( const elem of root.querySelectorAll('[data-i18n-title]') ) {
|
||||
const text = vAPI.i18n(elem.getAttribute('data-i18n-title'));
|
||||
if ( !text ) { continue; }
|
||||
elem.setAttribute('title', expandHtmlEntities(text));
|
||||
}
|
||||
|
||||
for ( const elem of root.querySelectorAll('[placeholder]') ) {
|
||||
elem.setAttribute(
|
||||
'placeholder',
|
||||
vAPI.i18n(elem.getAttribute('placeholder'))
|
||||
);
|
||||
}
|
||||
|
||||
for ( const elem of root.querySelectorAll('[data-i18n-tip]') ) {
|
||||
const text = vAPI.i18n(elem.getAttribute('data-i18n-tip'))
|
||||
.replace(/<br>/g, '\n')
|
||||
.replace(/\n{3,}/g, '\n\n');
|
||||
elem.setAttribute('data-tip', text);
|
||||
if ( elem.getAttribute('aria-label') === 'data-tip' ) {
|
||||
elem.setAttribute('aria-label', text);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
vAPI.i18n.render();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.i18n.renderElapsedTimeToString = function(tstamp) {
|
||||
let value = (Date.now() - tstamp) / 60000;
|
||||
if ( value < 2 ) {
|
||||
return vAPI.i18n('elapsedOneMinuteAgo');
|
||||
}
|
||||
if ( value < 60 ) {
|
||||
return vAPI.i18n('elapsedManyMinutesAgo').replace('{{value}}', Math.floor(value).toLocaleString());
|
||||
}
|
||||
value /= 60;
|
||||
if ( value < 2 ) {
|
||||
return vAPI.i18n('elapsedOneHourAgo');
|
||||
}
|
||||
if ( value < 24 ) {
|
||||
return vAPI.i18n('elapsedManyHoursAgo').replace('{{value}}', Math.floor(value).toLocaleString());
|
||||
}
|
||||
value /= 24;
|
||||
if ( value < 2 ) {
|
||||
return vAPI.i18n('elapsedOneDayAgo');
|
||||
}
|
||||
return vAPI.i18n('elapsedManyDaysAgo').replace('{{value}}', Math.floor(value).toLocaleString());
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// <<<<< end of local scope
|
||||
function i18n$(...args) {
|
||||
return i18n.getMessage(...args);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const isBackgroundProcess = document.title === 'uBlock Origin Background Page';
|
||||
|
||||
if ( isBackgroundProcess !== true ) {
|
||||
|
||||
// http://www.w3.org/International/questions/qa-scripts#directions
|
||||
document.body.setAttribute(
|
||||
'dir',
|
||||
['ar', 'he', 'fa', 'ps', 'ur'].indexOf(i18n$('@@ui_locale')) !== -1
|
||||
? 'rtl'
|
||||
: 'ltr'
|
||||
);
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/2084
|
||||
// Anything else than <a>, <b>, <code>, <em>, <i>, and <span> will
|
||||
// be rendered as plain text.
|
||||
// For <a>, only href attribute must be present, and it MUST starts with
|
||||
// `https://`, and includes no single- or double-quotes.
|
||||
// No HTML entities are allowed, there is code to handle existing HTML
|
||||
// entities already present in translation files until they are all gone.
|
||||
|
||||
const allowedTags = new Set([
|
||||
'a',
|
||||
'b',
|
||||
'code',
|
||||
'em',
|
||||
'i',
|
||||
'span',
|
||||
'u',
|
||||
]);
|
||||
|
||||
const expandHtmlEntities = (( ) => {
|
||||
const entities = new Map([
|
||||
// TODO: Remove quote entities once no longer present in translation
|
||||
// files. Other entities must stay.
|
||||
[ '­', '\u00AD' ],
|
||||
[ '“', '“' ],
|
||||
[ '”', '”' ],
|
||||
[ '‘', '‘' ],
|
||||
[ '’', '’' ],
|
||||
[ '<', '<' ],
|
||||
[ '>', '>' ],
|
||||
]);
|
||||
const decodeEntities = match => {
|
||||
return entities.get(match) || match;
|
||||
};
|
||||
return function(text) {
|
||||
if ( text.indexOf('&') !== -1 ) {
|
||||
text = text.replace(/&[a-z]+;/g, decodeEntities);
|
||||
}
|
||||
return text;
|
||||
};
|
||||
})();
|
||||
|
||||
const safeTextToTextNode = function(text) {
|
||||
return document.createTextNode(expandHtmlEntities(text));
|
||||
};
|
||||
|
||||
const sanitizeElement = function(node) {
|
||||
if ( allowedTags.has(node.localName) === false ) { return null; }
|
||||
node.removeAttribute('style');
|
||||
let child = node.firstElementChild;
|
||||
while ( child !== null ) {
|
||||
const next = child.nextElementSibling;
|
||||
if ( sanitizeElement(child) === null ) {
|
||||
child.remove();
|
||||
}
|
||||
child = next;
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
const safeTextToDOM = function(text, parent) {
|
||||
if ( text === '' ) { return; }
|
||||
|
||||
// Fast path (most common).
|
||||
if ( text.indexOf('<') === -1 ) {
|
||||
const toInsert = safeTextToTextNode(text);
|
||||
let toReplace = parent.childCount !== 0
|
||||
? parent.firstChild
|
||||
: null;
|
||||
while ( toReplace !== null ) {
|
||||
if ( toReplace.nodeType === 3 && toReplace.nodeValue === '_' ) {
|
||||
break;
|
||||
}
|
||||
toReplace = toReplace.nextSibling;
|
||||
}
|
||||
if ( toReplace !== null ) {
|
||||
parent.replaceChild(toInsert, toReplace);
|
||||
} else {
|
||||
parent.appendChild(toInsert);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Slow path.
|
||||
// `<p>` no longer allowed. Code below can be removed once all <p>'s are
|
||||
// gone from translation files.
|
||||
text = text.replace(/^<p>|<\/p>/g, '')
|
||||
.replace(/<p>/g, '\n\n');
|
||||
// Parse allowed HTML tags.
|
||||
const domParser = new DOMParser();
|
||||
const parsedDoc = domParser.parseFromString(text, 'text/html');
|
||||
let node = parsedDoc.body.firstChild;
|
||||
while ( node !== null ) {
|
||||
const next = node.nextSibling;
|
||||
switch ( node.nodeType ) {
|
||||
case 1: // element
|
||||
if ( sanitizeElement(node) === null ) { break; }
|
||||
parent.appendChild(node);
|
||||
break;
|
||||
case 3: // text
|
||||
parent.appendChild(node);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
node = next;
|
||||
}
|
||||
};
|
||||
|
||||
i18n.safeTemplateToDOM = function(id, dict, parent) {
|
||||
if ( parent === undefined ) {
|
||||
parent = document.createDocumentFragment();
|
||||
}
|
||||
let textin = i18n$(id);
|
||||
if ( textin === '' ) {
|
||||
return parent;
|
||||
}
|
||||
if ( textin.indexOf('{{') === -1 ) {
|
||||
safeTextToDOM(textin, parent);
|
||||
return parent;
|
||||
}
|
||||
const re = /\{\{\w+\}\}/g;
|
||||
let textout = '';
|
||||
for (;;) {
|
||||
let match = re.exec(textin);
|
||||
if ( match === null ) {
|
||||
textout += textin;
|
||||
break;
|
||||
}
|
||||
textout += textin.slice(0, match.index);
|
||||
let prop = match[0].slice(2, -2);
|
||||
if ( dict.hasOwnProperty(prop) ) {
|
||||
textout += dict[prop].replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
} else {
|
||||
textout += prop;
|
||||
}
|
||||
textin = textin.slice(re.lastIndex);
|
||||
}
|
||||
safeTextToDOM(textout, parent);
|
||||
return parent;
|
||||
};
|
||||
|
||||
// Helper to deal with the i18n'ing of HTML files.
|
||||
i18n.render = function(context) {
|
||||
const docu = document;
|
||||
const root = context || docu;
|
||||
|
||||
for ( const elem of root.querySelectorAll('[data-i18n]') ) {
|
||||
let text = i18n$(elem.getAttribute('data-i18n'));
|
||||
if ( !text ) { continue; }
|
||||
if ( text.indexOf('{{') === -1 ) {
|
||||
safeTextToDOM(text, elem);
|
||||
continue;
|
||||
}
|
||||
// Handle selector-based placeholders: these placeholders tell where
|
||||
// existing child DOM element are to be positioned relative to the
|
||||
// localized text nodes.
|
||||
const parts = text.split(/(\{\{[^}]+\}\})/);
|
||||
const fragment = document.createDocumentFragment();
|
||||
let textBefore = '';
|
||||
for ( let part of parts ) {
|
||||
if ( part === '' ) { continue; }
|
||||
if ( part.startsWith('{{') && part.endsWith('}}') ) {
|
||||
// TODO: remove detection of ':' once it no longer appears
|
||||
// in translation files.
|
||||
const pos = part.indexOf(':');
|
||||
if ( pos !== -1 ) {
|
||||
part = part.slice(0, pos) + part.slice(-2);
|
||||
}
|
||||
const selector = part.slice(2, -2);
|
||||
let node;
|
||||
// Ideally, the i18n strings explicitly refer to the
|
||||
// class of the element to insert. However for now we
|
||||
// will create a class from what is currently found in
|
||||
// the placeholder and first try to lookup the resulting
|
||||
// selector. This way we don't have to revisit all
|
||||
// translations just for the sake of declaring the proper
|
||||
// selector in the placeholder field.
|
||||
if ( selector.charCodeAt(0) !== 0x2E /* '.' */ ) {
|
||||
node = elem.querySelector(`.${selector}`);
|
||||
}
|
||||
if ( node instanceof Element === false ) {
|
||||
node = elem.querySelector(selector);
|
||||
}
|
||||
if ( node instanceof Element ) {
|
||||
safeTextToDOM(textBefore, fragment);
|
||||
fragment.appendChild(node);
|
||||
textBefore = '';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
textBefore += part;
|
||||
}
|
||||
if ( textBefore !== '' ) {
|
||||
safeTextToDOM(textBefore, fragment);
|
||||
}
|
||||
elem.appendChild(fragment);
|
||||
}
|
||||
|
||||
for ( const elem of root.querySelectorAll('[data-i18n-title]') ) {
|
||||
const text = i18n$(elem.getAttribute('data-i18n-title'));
|
||||
if ( !text ) { continue; }
|
||||
elem.setAttribute('title', expandHtmlEntities(text));
|
||||
}
|
||||
|
||||
for ( const elem of root.querySelectorAll('[placeholder]') ) {
|
||||
elem.setAttribute(
|
||||
'placeholder',
|
||||
i18n$(elem.getAttribute('placeholder'))
|
||||
);
|
||||
}
|
||||
|
||||
for ( const elem of root.querySelectorAll('[data-i18n-tip]') ) {
|
||||
const text = i18n$(elem.getAttribute('data-i18n-tip'))
|
||||
.replace(/<br>/g, '\n')
|
||||
.replace(/\n{3,}/g, '\n\n');
|
||||
elem.setAttribute('data-tip', text);
|
||||
if ( elem.getAttribute('aria-label') === 'data-tip' ) {
|
||||
elem.setAttribute('aria-label', text);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
i18n.renderElapsedTimeToString = function(tstamp) {
|
||||
let value = (Date.now() - tstamp) / 60000;
|
||||
if ( value < 2 ) {
|
||||
return i18n$('elapsedOneMinuteAgo');
|
||||
}
|
||||
if ( value < 60 ) {
|
||||
return i18n$('elapsedManyMinutesAgo').replace('{{value}}', Math.floor(value).toLocaleString());
|
||||
}
|
||||
value /= 60;
|
||||
if ( value < 2 ) {
|
||||
return i18n$('elapsedOneHourAgo');
|
||||
}
|
||||
if ( value < 24 ) {
|
||||
return i18n$('elapsedManyHoursAgo').replace('{{value}}', Math.floor(value).toLocaleString());
|
||||
}
|
||||
value /= 24;
|
||||
if ( value < 2 ) {
|
||||
return i18n$('elapsedOneDayAgo');
|
||||
}
|
||||
return i18n$('elapsedManyDaysAgo').replace('{{value}}', Math.floor(value).toLocaleString());
|
||||
};
|
||||
|
||||
i18n.render();
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
export { i18n, i18n$ };
|
||||
|
@ -26,6 +26,7 @@
|
||||
/******************************************************************************/
|
||||
|
||||
import { hostnameFromURI } from './uri-utils.js';
|
||||
import { i18n, i18n$ } from './i18n.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
@ -869,7 +870,7 @@ const viewPort = (( ) => {
|
||||
/******************************************************************************/
|
||||
|
||||
const updateCurrentTabTitle = (( ) => {
|
||||
const i18nCurrentTab = vAPI.i18n('loggerCurrentTab');
|
||||
const i18nCurrentTab = i18n$('loggerCurrentTab');
|
||||
|
||||
return function() {
|
||||
const select = uDom.nodeFromId('pageSelector');
|
||||
@ -1572,7 +1573,7 @@ const reloadTab = function(ev) {
|
||||
}
|
||||
// https://github.com/gorhill/uBlock/issues/2179
|
||||
if ( rows[1].children[1].childElementCount === 0 ) {
|
||||
vAPI.i18n.safeTemplateToDOM(
|
||||
i18n.safeTemplateToDOM(
|
||||
'loggerStaticFilteringFinderSentence2',
|
||||
{ filter: rawFilter },
|
||||
rows[1].children[1]
|
||||
@ -1728,7 +1729,7 @@ const reloadTab = function(ev) {
|
||||
};
|
||||
|
||||
const fillOriginSelect = function(select, hostname, domain) {
|
||||
const template = vAPI.i18n('loggerStaticFilteringSentencePartOrigin');
|
||||
const template = i18n$('loggerStaticFilteringSentencePartOrigin');
|
||||
let value = hostname;
|
||||
for (;;) {
|
||||
const option = document.createElement('option');
|
||||
@ -1748,7 +1749,7 @@ const reloadTab = function(ev) {
|
||||
return;
|
||||
}
|
||||
|
||||
const template = vAPI.i18n('loggerStaticFilteringSentence');
|
||||
const template = i18n$('loggerStaticFilteringSentence');
|
||||
const rePlaceholder = /\{\{[^}]+?\}\}/g;
|
||||
const nodes = [];
|
||||
let pos = 0;
|
||||
@ -1770,11 +1771,11 @@ const reloadTab = function(ev) {
|
||||
select.className = 'static action';
|
||||
option = document.createElement('option');
|
||||
option.setAttribute('value', '');
|
||||
option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartBlock');
|
||||
option.textContent = i18n$('loggerStaticFilteringSentencePartBlock');
|
||||
select.appendChild(option);
|
||||
option = document.createElement('option');
|
||||
option.setAttribute('value', '@@');
|
||||
option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartAllow');
|
||||
option.textContent = i18n$('loggerStaticFilteringSentencePartAllow');
|
||||
select.appendChild(option);
|
||||
nodes.push(select);
|
||||
break;
|
||||
@ -1785,11 +1786,11 @@ const reloadTab = function(ev) {
|
||||
select.className = 'static type';
|
||||
option = document.createElement('option');
|
||||
option.setAttribute('value', filterType);
|
||||
option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartType').replace('{{type}}', filterType);
|
||||
option.textContent = i18n$('loggerStaticFilteringSentencePartType').replace('{{type}}', filterType);
|
||||
select.appendChild(option);
|
||||
option = document.createElement('option');
|
||||
option.setAttribute('value', '');
|
||||
option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartAnyType');
|
||||
option.textContent = i18n$('loggerStaticFilteringSentencePartAnyType');
|
||||
select.appendChild(option);
|
||||
nodes.push(select);
|
||||
break;
|
||||
@ -1813,7 +1814,7 @@ const reloadTab = function(ev) {
|
||||
fillOriginSelect(select, targetFrameHostname, targetFrameDomain);
|
||||
option = document.createElement('option');
|
||||
option.setAttribute('value', '');
|
||||
option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartAnyOrigin');
|
||||
option.textContent = i18n$('loggerStaticFilteringSentencePartAnyOrigin');
|
||||
select.appendChild(option);
|
||||
nodes.push(select);
|
||||
break;
|
||||
@ -1823,11 +1824,11 @@ const reloadTab = function(ev) {
|
||||
select.className = 'static importance';
|
||||
option = document.createElement('option');
|
||||
option.setAttribute('value', '');
|
||||
option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartNotImportant');
|
||||
option.textContent = i18n$('loggerStaticFilteringSentencePartNotImportant');
|
||||
select.appendChild(option);
|
||||
option = document.createElement('option');
|
||||
option.setAttribute('value', 'important');
|
||||
option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartImportant');
|
||||
option.textContent = i18n$('loggerStaticFilteringSentencePartImportant');
|
||||
select.appendChild(option);
|
||||
nodes.push(select);
|
||||
break;
|
||||
|
@ -40,6 +40,7 @@ import µb from './background.js';
|
||||
import webRequest from './traffic.js';
|
||||
import { denseBase64 } from './base64-custom.js';
|
||||
import { dnrRulesetFromRawLists } from './static-dnr-filtering.js';
|
||||
import { i18n$ } from './i18n.js';
|
||||
import { redirectEngine } from './redirect-engine.js';
|
||||
import { StaticFilteringParser } from './static-filtering-parser.js';
|
||||
|
||||
@ -1075,7 +1076,7 @@ const backupUserData = async function() {
|
||||
userFilters: userFilters.content,
|
||||
};
|
||||
|
||||
const filename = vAPI.i18n('aboutBackupFilename')
|
||||
const filename = i18n$('aboutBackupFilename')
|
||||
.replace('{{datetime}}', µb.dateNowToSensibleString())
|
||||
.replace(/ +/g, '_');
|
||||
µb.restoreBackupSettings.lastBackupFile = filename;
|
||||
@ -1316,7 +1317,7 @@ const getShortcuts = function(callback) {
|
||||
let desc = command.description;
|
||||
let match = /^__MSG_(.+?)__$/.exec(desc);
|
||||
if ( match !== null ) {
|
||||
desc = vAPI.i18n(match[1]);
|
||||
desc = i18n$(match[1]);
|
||||
}
|
||||
if ( desc === '' ) { continue; }
|
||||
command.description = desc;
|
||||
|
@ -24,6 +24,7 @@
|
||||
'use strict';
|
||||
|
||||
import punycode from '../lib/punycode.js';
|
||||
import { i18n$ } from './i18n.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
@ -50,8 +51,8 @@ const scopeToSrcHostnameMap = {
|
||||
'.': ''
|
||||
};
|
||||
const hostnameToSortableTokenMap = new Map();
|
||||
const statsStr = vAPI.i18n('popupBlockedStats');
|
||||
const domainsHitStr = vAPI.i18n('popupHitDomainCount');
|
||||
const statsStr = i18n$('popupBlockedStats');
|
||||
const domainsHitStr = i18n$('popupHitDomainCount');
|
||||
|
||||
let popupData = {};
|
||||
let dfPaneBuilt = false;
|
||||
@ -643,7 +644,7 @@ const renderTooltips = function(selector) {
|
||||
if ( selector !== undefined && key !== selector ) { continue; }
|
||||
const elem = uDom.nodeFromSelector(key);
|
||||
if ( elem.hasAttribute('title') === false ) { continue; }
|
||||
const text = vAPI.i18n(
|
||||
const text = i18n$(
|
||||
details.i18n +
|
||||
(uDom.nodeFromSelector(details.state) === null ? '1' : '2')
|
||||
);
|
||||
|
@ -27,6 +27,7 @@ import staticNetFilteringEngine from './static-net-filtering.js';
|
||||
import µb from './background.js';
|
||||
import { CompiledListWriter } from './static-filtering-io.js';
|
||||
import { StaticFilteringParser } from './static-filtering-parser.js';
|
||||
import { i18n$ } from './i18n.js';
|
||||
|
||||
import {
|
||||
domainFromHostname,
|
||||
@ -110,7 +111,7 @@ const initWorker = function() {
|
||||
entries.set(listKey, {
|
||||
title: listKey !== µb.userFiltersPath ?
|
||||
entry.title :
|
||||
vAPI.i18n('1pPageName'),
|
||||
i18n$('1pPageName'),
|
||||
supportURL: entry.supportURL || ''
|
||||
});
|
||||
}
|
||||
|
@ -23,9 +23,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
(( ) => {
|
||||
import { i18n$ } from './i18n.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
@ -63,11 +61,11 @@ const handleImportFilePicker = function() {
|
||||
userData = undefined;
|
||||
}
|
||||
if ( userData === undefined ) {
|
||||
window.alert(vAPI.i18n('aboutRestoreDataError'));
|
||||
window.alert(i18n$('aboutRestoreDataError'));
|
||||
return;
|
||||
}
|
||||
const time = new Date(userData.timeStamp);
|
||||
const msg = vAPI.i18n('aboutRestoreDataConfirm')
|
||||
const msg = i18n$('aboutRestoreDataConfirm')
|
||||
.replace('{{time}}', time.toLocaleString());
|
||||
const proceed = window.confirm(msg);
|
||||
if ( proceed !== true ) { return; }
|
||||
@ -137,9 +135,9 @@ const onLocalDataReceived = function(details) {
|
||||
unit = '';
|
||||
}
|
||||
uDom.nodeFromId('storageUsed').textContent =
|
||||
vAPI.i18n('storageUsed')
|
||||
i18n$('storageUsed')
|
||||
.replace('{{value}}', v.toLocaleString(undefined, { maximumSignificantDigits: 3 }))
|
||||
.replace('{{unit}}', unit && vAPI.i18n(unit) || '');
|
||||
.replace('{{unit}}', unit && i18n$(unit) || '');
|
||||
|
||||
const timeOptions = {
|
||||
weekday: 'long',
|
||||
@ -154,7 +152,7 @@ const onLocalDataReceived = function(details) {
|
||||
const lastBackupFile = details.lastBackupFile || '';
|
||||
if ( lastBackupFile !== '' ) {
|
||||
const dt = new Date(details.lastBackupTime);
|
||||
const text = vAPI.i18n('settingsLastBackupPrompt');
|
||||
const text = i18n$('settingsLastBackupPrompt');
|
||||
const node = uDom.nodeFromId('settingsLastBackupPrompt');
|
||||
node.textContent = text + '\xA0' + dt.toLocaleString('fullwide', timeOptions);
|
||||
node.style.display = '';
|
||||
@ -163,7 +161,7 @@ const onLocalDataReceived = function(details) {
|
||||
const lastRestoreFile = details.lastRestoreFile || '';
|
||||
if ( lastRestoreFile !== '' ) {
|
||||
const dt = new Date(details.lastRestoreTime);
|
||||
const text = vAPI.i18n('settingsLastRestorePrompt');
|
||||
const text = i18n$('settingsLastRestorePrompt');
|
||||
const node = uDom.nodeFromId('settingsLastRestorePrompt');
|
||||
node.textContent = text + '\xA0' + dt.toLocaleString('fullwide', timeOptions);
|
||||
node.style.display = '';
|
||||
@ -183,7 +181,7 @@ const onLocalDataReceived = function(details) {
|
||||
/******************************************************************************/
|
||||
|
||||
const resetUserData = function() {
|
||||
const msg = vAPI.i18n('aboutResetDataConfirm');
|
||||
const msg = i18n$('aboutResetDataConfirm');
|
||||
const proceed = window.confirm(msg);
|
||||
if ( proceed !== true ) { return; }
|
||||
vAPI.messaging.send('dashboard', {
|
||||
@ -306,5 +304,3 @@ document.querySelector(
|
||||
);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
||||
|
@ -91,7 +91,11 @@ async function dnrRulesetFromRawLists(lists, options = {}) {
|
||||
const toLoad = [];
|
||||
const toDNR = (context, list) => addToDNR(context, list);
|
||||
for ( const list of lists ) {
|
||||
toLoad.push(list.then(list => toDNR(context, list)));
|
||||
if ( list instanceof Promise ) {
|
||||
toLoad.push(list.then(list => toDNR(context, list)));
|
||||
} else {
|
||||
toLoad.push(toDNR(context, list));
|
||||
}
|
||||
}
|
||||
await Promise.all(toLoad);
|
||||
return staticNetFilteringEngine.dnrFromCompiled('end', context);
|
||||
|
@ -3176,10 +3176,17 @@ Parser.utils = Parser.prototype.utils = (( ) => {
|
||||
[ 'adguard_ext_safari', 'false' ],
|
||||
]);
|
||||
|
||||
const toURL = url => {
|
||||
try {
|
||||
return new URL(url.trim());
|
||||
} catch (ex) {
|
||||
}
|
||||
};
|
||||
|
||||
class preparser {
|
||||
// This method returns an array of indices, corresponding to position in
|
||||
// the content string which should alternatively be parsed and discarded.
|
||||
static splitter(content, env) {
|
||||
static splitter(content, env = []) {
|
||||
const reIf = /^!#(if|endif)\b([^\n]*)(?:[\n\r]+|$)/gm;
|
||||
const stack = [];
|
||||
const shouldDiscard = ( ) => stack.some(v => v);
|
||||
@ -3205,7 +3212,6 @@ Parser.utils = Parser.prototype.utils = (( ) => {
|
||||
}
|
||||
stack.push(startDiscard);
|
||||
break;
|
||||
|
||||
case 'endif':
|
||||
stack.pop();
|
||||
const stopDiscard = shouldDiscard() === false;
|
||||
@ -3214,7 +3220,6 @@ Parser.utils = Parser.prototype.utils = (( ) => {
|
||||
discard = false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -3224,6 +3229,47 @@ Parser.utils = Parser.prototype.utils = (( ) => {
|
||||
return parts;
|
||||
}
|
||||
|
||||
static expandIncludes(parts, env = []) {
|
||||
const out = [];
|
||||
const reInclude = /^!#include +(\S+)[^\n\r]*(?:[\n\r]+|$)/gm;
|
||||
for ( const part of parts ) {
|
||||
if ( typeof part === 'string' ) {
|
||||
out.push(part);
|
||||
continue;
|
||||
}
|
||||
if ( part instanceof Object === false ) { continue; }
|
||||
const content = part.content;
|
||||
const slices = this.splitter(content, env);
|
||||
for ( let i = 0, n = slices.length - 1; i < n; i++ ) {
|
||||
const slice = content.slice(slices[i+0], slices[i+1]);
|
||||
if ( (i & 1) !== 0 ) {
|
||||
out.push(slice);
|
||||
continue;
|
||||
}
|
||||
let lastIndex = 0;
|
||||
for (;;) {
|
||||
const match = reInclude.exec(slice);
|
||||
if ( match === null ) { break; }
|
||||
if ( toURL(match[1]) !== undefined ) { continue; }
|
||||
if ( match[1].indexOf('..') !== -1 ) { continue; }
|
||||
// Compute nested list path relative to parent list path
|
||||
const pos = part.url.lastIndexOf('/');
|
||||
if ( pos === -1 ) { continue; }
|
||||
const subURL = part.url.slice(0, pos + 1) + match[1].trim();
|
||||
out.push(
|
||||
slice.slice(lastIndex, match.index + match[0].length),
|
||||
`! >>>>>>>> ${subURL}\n`,
|
||||
{ url: subURL },
|
||||
`! <<<<<<<< ${subURL}\n`
|
||||
);
|
||||
lastIndex = reInclude.lastIndex;
|
||||
}
|
||||
out.push(lastIndex === 0 ? slice : slice.slice(lastIndex));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static prune(content, env) {
|
||||
const parts = this.splitter(content, env);
|
||||
const out = [];
|
||||
|
@ -37,6 +37,7 @@ import staticFilteringReverseLookup from './reverselookup.js';
|
||||
import staticNetFilteringEngine from './static-net-filtering.js';
|
||||
import µb from './background.js';
|
||||
import { hostnameFromURI } from './uri-utils.js';
|
||||
import { i18n, i18n$ } from './i18n.js';
|
||||
import { redirectEngine } from './redirect-engine.js';
|
||||
import { sparseBase64 } from './base64-custom.js';
|
||||
import { StaticFilteringParser } from './static-filtering-parser.js';
|
||||
@ -625,7 +626,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
|
||||
newAvailableLists[this.userFiltersPath] = {
|
||||
content: 'filters',
|
||||
group: 'user',
|
||||
title: vAPI.i18n('1pPageName'),
|
||||
title: i18n$('1pPageName'),
|
||||
};
|
||||
|
||||
// Custom filter lists.
|
||||
@ -1392,7 +1393,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
|
||||
if ( typeof details.lang === 'string' ) {
|
||||
let re = this.listMatchesEnvironment.reLang;
|
||||
if ( re === undefined ) {
|
||||
const match = /^[a-z]+/.exec(browser.i18n.getUILanguage());
|
||||
const match = /^[a-z]+/.exec(i18n.getUILanguage());
|
||||
if ( match !== null ) {
|
||||
re = new RegExp('\\b' + match[0] + '\\b');
|
||||
this.listMatchesEnvironment.reLang = re;
|
||||
|
@ -30,6 +30,7 @@ import staticNetFilteringEngine from './static-net-filtering.js';
|
||||
import µb from './background.js';
|
||||
import webext from './webext.js';
|
||||
import { PageStore } from './pagestore.js';
|
||||
import { i18n$ } from './i18n.js';
|
||||
|
||||
import {
|
||||
sessionFirewall,
|
||||
@ -1052,7 +1053,7 @@ vAPI.tabs = new vAPI.Tabs();
|
||||
};
|
||||
const pageStore = new NoPageStore(vAPI.noTabId);
|
||||
µb.pageStores.set(pageStore.tabId, pageStore);
|
||||
pageStore.title = vAPI.i18n('logBehindTheScene');
|
||||
pageStore.title = i18n$('logBehindTheScene');
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -23,9 +23,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
(( ) => {
|
||||
import { i18n$ } from './i18n.js';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
@ -204,7 +202,7 @@ const exportWhitelistToFile = function() {
|
||||
const val = getEditorText();
|
||||
if ( val === '' ) { return; }
|
||||
const filename =
|
||||
vAPI.i18n('whitelistExportFilename')
|
||||
i18n$('whitelistExportFilename')
|
||||
.replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString())
|
||||
.replace(/ +/g, '_');
|
||||
vAPI.download({
|
||||
@ -262,5 +260,3 @@ uDom('#whitelistRevert').on('click', revertChanges);
|
||||
renderWhitelist();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
||||
|
@ -213,7 +213,7 @@
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/vapi-client-extra.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/logger-ui.js" type="module"></script>
|
||||
<script src="js/logger-ui-inspector.js" type="module"></script>
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -101,7 +101,7 @@
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/popup-fenix.js" type="module"></script>
|
||||
|
||||
</body>
|
||||
|
@ -90,9 +90,9 @@
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js"></script>
|
||||
<script src="js/settings.js"></script>
|
||||
<script src="js/settings.js" type="module"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -32,7 +32,7 @@
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js"></script>
|
||||
<script src="js/shortcuts.js"></script>
|
||||
|
||||
|
@ -112,7 +112,7 @@
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js"></script>
|
||||
<script src="js/support.js"></script>
|
||||
|
||||
|
@ -69,7 +69,7 @@
|
||||
<script src="../js/vapi-client.js"></script>
|
||||
<script src="../js/vapi-client-extra.js"></script>
|
||||
<script src="../js/udom.js"></script>
|
||||
<script src="../js/i18n.js"></script>
|
||||
<script src="../js/i18n.js" type="module"></script>
|
||||
<script src="../js/epicker-ui.js" type="module"></script>
|
||||
|
||||
</body>
|
||||
|
@ -53,10 +53,10 @@
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/i18n.js" type="module"></script>
|
||||
<script src="js/dashboard-common.js"></script>
|
||||
<script src="js/cloud-ui.js"></script>
|
||||
<script src="js/whitelist.js"></script>
|
||||
<script src="js/cloud-ui.js" type="module"></script>
|
||||
<script src="js/whitelist.js" type="module"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -7,72 +7,35 @@ set -e
|
||||
echo "*** uBlock: Importing from Crowdin archive"
|
||||
|
||||
SRC=~/Downloads/crowdin
|
||||
rm -r $SRC || true
|
||||
rm -r $SRC || true > /dev/null
|
||||
unzip -q ~/Downloads/uBlock\ \(translations\).zip -d $SRC
|
||||
|
||||
# https://www.assertnotmagic.com/2018/06/20/bash-brackets-quick-reference/
|
||||
|
||||
DES=./src/_locales
|
||||
cp $SRC/ar/messages.json $DES/ar/messages.json
|
||||
cp $SRC/az/messages.json $DES/az/messages.json
|
||||
cp $SRC/bg/messages.json $DES/bg/messages.json
|
||||
cp $SRC/bn/messages.json $DES/bn/messages.json
|
||||
cp $SRC/bs/messages.json $DES/bs/messages.json
|
||||
cp $SRC/ca/messages.json $DES/ca/messages.json
|
||||
cp $SRC/cs/messages.json $DES/cs/messages.json
|
||||
cp $SRC/cv/messages.json $DES/cv/messages.json
|
||||
cp $SRC/da/messages.json $DES/da/messages.json
|
||||
cp $SRC/de/messages.json $DES/de/messages.json
|
||||
cp $SRC/el/messages.json $DES/el/messages.json
|
||||
cp $SRC/en-GB/messages.json $DES/en_GB/messages.json
|
||||
cp $SRC/eo/messages.json $DES/eo/messages.json
|
||||
cp $SRC/es-ES/messages.json $DES/es/messages.json
|
||||
cp $SRC/et/messages.json $DES/et/messages.json
|
||||
cp $SRC/eu/messages.json $DES/eu/messages.json
|
||||
cp $SRC/fa/messages.json $DES/fa/messages.json
|
||||
cp $SRC/fi/messages.json $DES/fi/messages.json
|
||||
cp $SRC/fil/messages.json $DES/fil/messages.json
|
||||
cp $SRC/fr/messages.json $DES/fr/messages.json
|
||||
cp $SRC/fy-NL/messages.json $DES/fy/messages.json
|
||||
cp $SRC/gl/messages.json $DES/gl/messages.json
|
||||
cp $SRC/he/messages.json $DES/he/messages.json
|
||||
cp $SRC/hi/messages.json $DES/hi/messages.json
|
||||
cp $SRC/hr/messages.json $DES/hr/messages.json
|
||||
cp $SRC/hu/messages.json $DES/hu/messages.json
|
||||
cp $SRC/hy-AM/messages.json $DES/hy/messages.json
|
||||
cp $SRC/id/messages.json $DES/id/messages.json
|
||||
cp $SRC/it/messages.json $DES/it/messages.json
|
||||
cp $SRC/ja/messages.json $DES/ja/messages.json
|
||||
cp $SRC/ka/messages.json $DES/ka/messages.json
|
||||
cp $SRC/kk/messages.json $DES/kk/messages.json
|
||||
cp $SRC/kn/messages.json $DES/kn/messages.json
|
||||
cp $SRC/ko/messages.json $DES/ko/messages.json
|
||||
cp $SRC/lt/messages.json $DES/lt/messages.json
|
||||
cp $SRC/lv/messages.json $DES/lv/messages.json
|
||||
cp $SRC/ml-IN/messages.json $DES/ml/messages.json
|
||||
cp $SRC/mr/messages.json $DES/mr/messages.json
|
||||
cp $SRC/ms/messages.json $DES/ms/messages.json
|
||||
cp $SRC/nb/messages.json $DES/nb/messages.json
|
||||
cp $SRC/nl/messages.json $DES/nl/messages.json
|
||||
cp $SRC/oc/messages.json $DES/oc/messages.json
|
||||
cp $SRC/pl/messages.json $DES/pl/messages.json
|
||||
cp $SRC/pt-BR/messages.json $DES/pt_BR/messages.json
|
||||
cp $SRC/pt-PT/messages.json $DES/pt_PT/messages.json
|
||||
cp $SRC/ro/messages.json $DES/ro/messages.json
|
||||
cp $SRC/ru/messages.json $DES/ru/messages.json
|
||||
cp $SRC/sk/messages.json $DES/sk/messages.json
|
||||
cp $SRC/sl/messages.json $DES/sl/messages.json
|
||||
cp $SRC/so/messages.json $DES/so/messages.json
|
||||
cp $SRC/sq/messages.json $DES/sq/messages.json
|
||||
cp $SRC/sr/messages.json $DES/sr/messages.json
|
||||
cp $SRC/sv-SE/messages.json $DES/sv/messages.json
|
||||
cp $SRC/ta/messages.json $DES/ta/messages.json
|
||||
cp $SRC/te/messages.json $DES/te/messages.json
|
||||
cp $SRC/th/messages.json $DES/th/messages.json
|
||||
cp $SRC/tr/messages.json $DES/tr/messages.json
|
||||
cp $SRC/ur-PK/messages.json $DES/ur/messages.json
|
||||
cp $SRC/uk/messages.json $DES/uk/messages.json
|
||||
cp $SRC/vi/messages.json $DES/vi/messages.json
|
||||
cp $SRC/zh-CN/messages.json $DES/zh_CN/messages.json
|
||||
cp $SRC/zh-TW/messages.json $DES/zh_TW/messages.json
|
||||
DESMV3=./platform/mv3/extension/_locales
|
||||
|
||||
for dir in $SRC/*/; do
|
||||
srclang=$(basename $dir)
|
||||
deslang=${srclang/-/_}
|
||||
deslang=${deslang%_AM}
|
||||
deslang=${deslang%_ES}
|
||||
deslang=${deslang%_IE}
|
||||
deslang=${deslang%_IN}
|
||||
deslang=${deslang%_LK}
|
||||
deslang=${deslang%_NL}
|
||||
deslang=${deslang%_PK}
|
||||
deslang=${deslang%_SE}
|
||||
if [[ $deslang == 'en' ]]; then
|
||||
continue
|
||||
fi
|
||||
# ubo
|
||||
mkdir -p "$DES/$deslang/" && cp "$SRC/$srclang/messages.json" "$DES/$deslang/"
|
||||
# ubo lite
|
||||
mkdir -p "$DESMV3/$deslang/" && cp "$SRC/$srclang/uBO Lite/messages.json" "$DESMV3/$deslang/"
|
||||
# descriptions
|
||||
cp "$SRC/$srclang/description.txt" "./dist/description/description-${deslang}.txt"
|
||||
done
|
||||
|
||||
# Output files with possible misuse of `$`, as this can lead to severe
|
||||
# consequences, such as not being able to run the extension at all.
|
||||
@ -80,71 +43,8 @@ cp $SRC/zh-TW/messages.json $DES/zh_TW/messages.json
|
||||
# See https://issues.adblockplus.org/ticket/6666
|
||||
echo "*** uBlock: Instances of '\$':"
|
||||
grep -FR "$" $DES/ || true
|
||||
grep -FR "$" $DESMV3/ || true
|
||||
|
||||
#
|
||||
|
||||
DES=./dist/description
|
||||
cp $SRC/ar/description.txt $DES/description-ar.txt
|
||||
cp $SRC/bg/description.txt $DES/description-bg.txt
|
||||
cp $SRC/bn/description.txt $DES/description-bn.txt
|
||||
cp $SRC/bs/description.txt $DES/description-bs.txt
|
||||
cp $SRC/ca/description.txt $DES/description-ca.txt
|
||||
cp $SRC/cs/description.txt $DES/description-cs.txt
|
||||
cp $SRC/cv/description.txt $DES/description-cv.txt
|
||||
cp $SRC/da/description.txt $DES/description-da.txt
|
||||
cp $SRC/de/description.txt $DES/description-de.txt
|
||||
cp $SRC/el/description.txt $DES/description-el.txt
|
||||
cp $SRC/en-GB/description.txt $DES/description-en_GB.txt
|
||||
cp $SRC/eo/description.txt $DES/description-eo.txt
|
||||
cp $SRC/es-ES/description.txt $DES/description-es.txt
|
||||
cp $SRC/et/description.txt $DES/description-et.txt
|
||||
cp $SRC/eu/description.txt $DES/description-eu.txt
|
||||
cp $SRC/fa/description.txt $DES/description-fa.txt
|
||||
cp $SRC/fi/description.txt $DES/description-fi.txt
|
||||
cp $SRC/fil/description.txt $DES/description-fil.txt
|
||||
cp $SRC/fr/description.txt $DES/description-fr.txt
|
||||
cp $SRC/fy-NL/description.txt $DES/description-fy.txt
|
||||
cp $SRC/gl/description.txt $DES/description-gl.txt
|
||||
cp $SRC/he/description.txt $DES/description-he.txt
|
||||
cp $SRC/hi/description.txt $DES/description-hi.txt
|
||||
cp $SRC/hr/description.txt $DES/description-hr.txt
|
||||
cp $SRC/hu/description.txt $DES/description-hu.txt
|
||||
cp $SRC/hy-AM/description.txt $DES/description-hy.txt
|
||||
cp $SRC/id/description.txt $DES/description-id.txt
|
||||
cp $SRC/it/description.txt $DES/description-it.txt
|
||||
cp $SRC/ja/description.txt $DES/description-ja.txt
|
||||
cp $SRC/ka/description.txt $DES/description-ka.txt
|
||||
cp $SRC/kk/description.txt $DES/description-kk.txt
|
||||
cp $SRC/ko/description.txt $DES/description-ko.txt
|
||||
cp $SRC/kn/description.txt $DES/description-kn.txt
|
||||
cp $SRC/lt/description.txt $DES/description-lt.txt
|
||||
cp $SRC/lv/description.txt $DES/description-lv.txt
|
||||
cp $SRC/ml-IN/description.txt $DES/description-ml.txt
|
||||
cp $SRC/ms/description.txt $DES/description-ms.txt
|
||||
cp $SRC/mr/description.txt $DES/description-mr.txt
|
||||
cp $SRC/nb/description.txt $DES/description-nb.txt
|
||||
cp $SRC/nl/description.txt $DES/description-nl.txt
|
||||
cp $SRC/oc/description.txt $DES/description-oc.txt
|
||||
cp $SRC/pl/description.txt $DES/description-pl.txt
|
||||
cp $SRC/pt-BR/description.txt $DES/description-pt_BR.txt
|
||||
cp $SRC/pt-PT/description.txt $DES/description-pt_PT.txt
|
||||
cp $SRC/ro/description.txt $DES/description-ro.txt
|
||||
cp $SRC/ru/description.txt $DES/description-ru.txt
|
||||
cp $SRC/sk/description.txt $DES/description-sk.txt
|
||||
cp $SRC/sl/description.txt $DES/description-sl.txt
|
||||
cp $SRC/sq/description.txt $DES/description-sq.txt
|
||||
cp $SRC/sr/description.txt $DES/description-sr.txt
|
||||
cp $SRC/sv-SE/description.txt $DES/description-sv.txt
|
||||
cp $SRC/ta/description.txt $DES/description-ta.txt
|
||||
cp $SRC/te/description.txt $DES/description-te.txt
|
||||
cp $SRC/tr/description.txt $DES/description-tr.txt
|
||||
cp $SRC/ur-PK/description.txt $DES/description-ur.txt
|
||||
cp $SRC/uk/description.txt $DES/description-uk.txt
|
||||
cp $SRC/vi/description.txt $DES/description-vi.txt
|
||||
cp $SRC/zh-CN/description.txt $DES/description-zh_CN.txt
|
||||
cp $SRC/zh-TW/description.txt $DES/description-zh_TW.txt
|
||||
|
||||
#
|
||||
|
||||
rm -r $SRC
|
||||
echo "*** uBlock: Import done."
|
||||
|
@ -25,8 +25,12 @@ echo "*** uBlock0.mv3: Copying common files"
|
||||
cp -R src/css/fonts/* $DES/css/fonts/
|
||||
cp src/css/themes/default.css $DES/css/
|
||||
cp src/css/common.css $DES/css/
|
||||
cp src/css/dashboard.css $DES/css/
|
||||
cp src/css/dashboard-common.css $DES/css/
|
||||
cp src/css/fa-icons.css $DES/css/
|
||||
|
||||
cp src/js/fa-icons.js $DES/js/
|
||||
cp src/js/i18n.js $DES/js/
|
||||
|
||||
cp LICENSE.txt $DES/
|
||||
|
||||
@ -35,6 +39,7 @@ cp platform/mv3/extension/*.html $DES/
|
||||
cp platform/mv3/extension/css/* $DES/css/
|
||||
cp platform/mv3/extension/js/* $DES/js/
|
||||
cp platform/mv3/extension/img/* $DES/img/
|
||||
cp -R platform/mv3/extension/_locales $DES/
|
||||
|
||||
if [ "$1" != "quick" ]; then
|
||||
echo "*** uBlock0.mv3: Generating rulesets"
|
||||
@ -44,6 +49,7 @@ if [ "$1" != "quick" ]; then
|
||||
./tools/make-nodejs.sh $TMPDIR
|
||||
cp platform/mv3/package.json $TMPDIR/
|
||||
cp platform/mv3/*.js $TMPDIR/
|
||||
cp assets/assets.json $TMPDIR/
|
||||
cd $TMPDIR
|
||||
node --no-warnings make-rulesets.js output=$DES quick=$QUICK
|
||||
cd - > /dev/null
|
||||
|
Loading…
Reference in New Issue
Block a user