mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-22 02:12:44 +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,
|
"eqeqeq": true,
|
||||||
"esversion": 8,
|
"esversion": 8,
|
||||||
"globals": {
|
"globals": {
|
||||||
"browser": false, // global variable in Firefox, Edge
|
|
||||||
"chrome": false, // global variable in Chromium, Chrome, Opera
|
"chrome": false, // global variable in Chromium, Chrome, Opera
|
||||||
"self": false,
|
"self": false,
|
||||||
"vAPI": false,
|
"vAPI": false,
|
||||||
|
6
Makefile
6
Makefile
@ -1,11 +1,11 @@
|
|||||||
# https://stackoverflow.com/a/6273809
|
# https://stackoverflow.com/a/6273809
|
||||||
run_options := $(filter-out $@,$(MAKECMDGOALS))
|
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
|
compare maxcost medcost mincost modifiers record wasm
|
||||||
|
|
||||||
sources := $(wildcard assets/resources/* dist/version src/* src/*/* src/*/*/* src/*/*/*/*)
|
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/* \
|
assets := $(wildcard submodules/uAssets/* \
|
||||||
submodules/uAssets/*/* \
|
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)
|
mv3-quick: tools/make-mv3.sh $(sources) $(platform)
|
||||||
tools/make-mv3.sh quick
|
tools/make-mv3.sh quick
|
||||||
|
|
||||||
mv3-full: tools/make-mv3.sh $(sources) $(platform)
|
mv3-full: tools/make-mv3.sh $(sources) $(platform)
|
||||||
tools/make-mv3.sh full
|
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.
|
# 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...')
|
print('Update GitHub to point to newly signed self-hosted xpi package...')
|
||||||
updates_json_filepath = os.path.join(projdir, 'dist', 'firefox', 'updates.json')
|
updates_json_filepath = os.path.join(projdir, 'dist', 'firefox', 'updates.json')
|
||||||
with open(updates_json_filepath) as f:
|
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:
|
with open(updates_json_filepath, 'w') as f:
|
||||||
f.write(updates_json)
|
f.write(updates_json)
|
||||||
f.close()
|
f.close()
|
||||||
# Automatically git add/commit if needed.
|
|
||||||
# - Stage the changed file
|
# - Stage the changed file
|
||||||
r = subprocess.run(['git', 'status', '-s', updates_json_filepath], stdout=subprocess.PIPE)
|
r = subprocess.run(['git', 'status', '-s', updates_json_filepath], stdout=subprocess.PIPE)
|
||||||
rout = bytes.decode(r.stdout).strip()
|
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
|
// https://github.com/gorhill/uBlock/issues/3057
|
||||||
// - webNavigation.onCreatedNavigationTarget become broken on Firefox when we
|
// - webNavigation.onCreatedNavigationTarget become broken on Firefox when we
|
||||||
// try to make the popup panel close itself using the original
|
// 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';
|
'use strict';
|
||||||
|
|
||||||
import rulesetDetails from '/rulesets/ruleset-details.js';
|
/******************************************************************************/
|
||||||
|
|
||||||
|
import { dnr, i18n, runtime } from './ext.js';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const dnr = chrome.declarativeNetRequest;
|
const RULE_REALM_SIZE = 1000000;
|
||||||
const TRUSTED_DIRECTIVE_BASE_RULE_ID = 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 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 allRules = [];
|
||||||
const toCheck = [];
|
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; }
|
if ( details.enabled !== true ) { continue; }
|
||||||
for ( const rule of details.rules.regexes ) {
|
toFetch.push(fetchJSON(`${details.id}.regexes`));
|
||||||
const regex = rule.condition.regexFilter;
|
}
|
||||||
const isCaseSensitive = rule.condition.isUrlFilterCaseSensitive === true;
|
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);
|
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 results = await Promise.all(toCheck);
|
||||||
const newRules = [];
|
const newRules = [];
|
||||||
for ( let i = 0; i < allRules.length; i++ ) {
|
for ( let i = 0; i < allRules.length; i++ ) {
|
||||||
@ -33,11 +157,18 @@ async function updateRegexRules() {
|
|||||||
console.info(`${result.reason}: ${rule.condition.regexFilter}`);
|
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 newRuleMap = new Map(newRules.map(rule => [ rule.id, rule ]));
|
||||||
const addRules = [];
|
const addRules = [];
|
||||||
const removeRuleIds = [];
|
const removeRuleIds = [];
|
||||||
for ( const oldRule of dynamicRuleMap.values() ) {
|
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);
|
const newRule = newRuleMap.get(oldRule.id);
|
||||||
if ( newRule === undefined ) {
|
if ( newRule === undefined ) {
|
||||||
removeRuleIds.push(oldRule.id);
|
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();
|
const dynamicRules = await dnr.getDynamicRules();
|
||||||
for ( const rule of dynamicRules ) {
|
for ( const rule of dynamicRules ) {
|
||||||
dynamicRuleMap.set(rule.id, rule);
|
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(`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();
|
const enabledRulesets = await dnr.getEnabledRulesets();
|
||||||
console.log(`Enabled rulesets: ${enabledRulesets}`);
|
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 => {
|
dnr.getAvailableStaticRuleCount().then(count => {
|
||||||
console.log(`Available static rule count: ${count}`);
|
console.log(`Available static rule count: ${count}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
dnr.setExtensionActionOptions({ displayActionCountAsBadgeText: true });
|
dnr.setExtensionActionOptions({ displayActionCountAsBadgeText: true });
|
||||||
|
}
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener((request, sender, callback) => {
|
/******************************************************************************/
|
||||||
switch ( request.what ) {
|
|
||||||
case 'popupPanelData':
|
function messageListener(request, sender, callback) {
|
||||||
matchesTrustedSiteDirective(request).then(response => {
|
switch ( request.what ) {
|
||||||
callback({
|
|
||||||
isTrusted: response,
|
case 'getRulesetData': {
|
||||||
rulesetDetails: rulesetDetails.filter(details =>
|
dnr.getEnabledRulesets().then(enabledRulesets => {
|
||||||
details.enabled
|
callback({
|
||||||
).map(details => ({
|
enabledRulesets,
|
||||||
name: details.name,
|
rulesetDetails: Array.from(rulesetDetails.values()),
|
||||||
filterCount: details.filters.accepted,
|
|
||||||
ruleCount: details.rules.accepted,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
return true;
|
});
|
||||||
case 'toggleTrustedSiteDirective':
|
return true;
|
||||||
toggleTrustedSiteDirective(request).then(response => {
|
}
|
||||||
callback(response);
|
|
||||||
|
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:
|
return true;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
});
|
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.
|
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
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@ -19,35 +19,23 @@
|
|||||||
Home: https://github.com/gorhill/uBlock
|
Home: https://github.com/gorhill/uBlock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* jshint esversion:11 */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
import { browser, sendMessage } from './ext.js';
|
||||||
|
import { i18n$ } from './i18n.js';
|
||||||
|
import { simpleStorage } from './storage.js';
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
let currentTab = {};
|
let currentTab = {};
|
||||||
let originalTrustedState = false;
|
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() {
|
async function toggleTrustedSiteDirective() {
|
||||||
let url;
|
let url;
|
||||||
try {
|
try {
|
||||||
@ -57,7 +45,7 @@ async function toggleTrustedSiteDirective() {
|
|||||||
}
|
}
|
||||||
if ( url instanceof URL === false ) { return; }
|
if ( url instanceof URL === false ) { return; }
|
||||||
const targetTrustedState = document.body.classList.contains('off');
|
const targetTrustedState = document.body.classList.contains('off');
|
||||||
const newTrustedState = await chrome.runtime.sendMessage({
|
const newTrustedState = await sendMessage({
|
||||||
what: 'toggleTrustedSiteDirective',
|
what: 'toggleTrustedSiteDirective',
|
||||||
origin: url.origin,
|
origin: url.origin,
|
||||||
state: targetTrustedState,
|
state: targetTrustedState,
|
||||||
@ -75,7 +63,7 @@ async function toggleTrustedSiteDirective() {
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
function reloadTab(ev) {
|
function reloadTab(ev) {
|
||||||
chrome.tabs.reload(currentTab.id, {
|
browser.tabs.reload(currentTab.id, {
|
||||||
bypassCache: ev.ctrlKey || ev.metaKey || ev.shiftKey,
|
bypassCache: ev.ctrlKey || ev.metaKey || ev.shiftKey,
|
||||||
});
|
});
|
||||||
document.body.classList.remove('needReload');
|
document.body.classList.remove('needReload');
|
||||||
@ -85,7 +73,7 @@ function reloadTab(ev) {
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
async function init() {
|
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; }
|
if ( tab instanceof Object === false ) { return true; }
|
||||||
currentTab = tab;
|
currentTab = tab;
|
||||||
|
|
||||||
@ -97,7 +85,7 @@ async function init() {
|
|||||||
|
|
||||||
let popupPanelData;
|
let popupPanelData;
|
||||||
if ( url !== undefined ) {
|
if ( url !== undefined ) {
|
||||||
popupPanelData = await chrome.runtime.sendMessage({
|
popupPanelData = await sendMessage({
|
||||||
what: 'popupPanelData',
|
what: 'popupPanelData',
|
||||||
origin: url.origin,
|
origin: url.origin,
|
||||||
});
|
});
|
||||||
@ -126,7 +114,9 @@ async function init() {
|
|||||||
h1.textContent = details.name;
|
h1.textContent = details.name;
|
||||||
parent.append(h1);
|
parent.append(h1);
|
||||||
const p = document.createElement('p');
|
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);
|
parent.append(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -189,12 +179,12 @@ async function toggleSections(more) {
|
|||||||
}
|
}
|
||||||
if ( newBits === currentBits ) { return; }
|
if ( newBits === currentBits ) { return; }
|
||||||
sectionBitsToAttribute(newBits);
|
sectionBitsToAttribute(newBits);
|
||||||
safeLocalStorage.setItem('popupPanelSections', newBits);
|
simpleStorage.setItem('popupPanelSections', newBits);
|
||||||
}
|
}
|
||||||
|
|
||||||
sectionBitsToAttribute(
|
simpleStorage.getItem('popupPanelSections').then(s => {
|
||||||
parseInt(safeLocalStorage.getItem('popupPanelSections'), 10)
|
sectionBitsToAttribute(parseInt(s, 10) || 0);
|
||||||
);
|
});
|
||||||
|
|
||||||
document.querySelector('#moreButton').addEventListener('click', ( ) => {
|
document.querySelector('#moreButton').addEventListener('click', ( ) => {
|
||||||
toggleSections(true);
|
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": [
|
"rule_resources": [
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"default_locale": "en",
|
||||||
"description": "uBO Minus is permission-less experimental MV3-based network request blocker",
|
"description": "uBO Minus is permission-less experimental MV3-based network request blocker",
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "img/icon_16.png",
|
"16": "img/icon_16.png",
|
||||||
@ -26,6 +27,7 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"minimum_chrome_version": "101.0",
|
"minimum_chrome_version": "101.0",
|
||||||
"name": "uBlock Origin Lite",
|
"name": "uBlock Origin Lite",
|
||||||
|
"options_page": "dashboard.html",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"activeTab",
|
"activeTab",
|
||||||
"declarativeNetRequest"
|
"declarativeNetRequest"
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<span id="saveRules" class="fa-icon" data-i18n-title="popupTipSaveRules">lock</span>
|
<span id="saveRules" class="fa-icon" data-i18n-title="popupTipSaveRules">lock</span>
|
||||||
<span id="revertRules" class="fa-icon" data-i18n-title="popupTipRevertRules">eraser</span>
|
<span id="revertRules" class="fa-icon" data-i18n-title="popupTipRevertRules">eraser</span>
|
||||||
</div>
|
</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"><!--
|
<span class="fa-icon"><!--
|
||||||
Power button taken from Font Awesome v4.7.0 by Dave Gandy.
|
Power button taken from Font Awesome v4.7.0 by Dave Gandy.
|
||||||
Unlike other FA icons, the power button is inlined here so
|
Unlike other FA icons, the power button is inlined here so
|
||||||
@ -40,6 +40,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="hostname"><span></span>­<span></span></div>
|
<div id="hostname"><span></span>­<span></span></div>
|
||||||
</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">
|
<hr data-section="a">
|
||||||
<div id="rulesetStats" data-section="a">
|
<div id="rulesetStats" data-section="a">
|
||||||
</div>
|
</div>
|
||||||
@ -55,6 +62,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="js/fa-icons.js"></script>
|
<script src="js/fa-icons.js"></script>
|
||||||
|
<script src="js/i18n.js" type="module"></script>
|
||||||
<script src="js/popup.js" type="module"></script>
|
<script src="js/popup.js" type="module"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
@ -27,8 +27,8 @@ import fs from 'fs/promises';
|
|||||||
import https from 'https';
|
import https from 'https';
|
||||||
import process from 'process';
|
import process from 'process';
|
||||||
|
|
||||||
import rulesetConfigs from './ruleset-config.js';
|
|
||||||
import { dnrRulesetFromRawLists } from './js/static-dnr-filtering.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() {
|
async function main() {
|
||||||
|
|
||||||
|
const env = [ 'chromium' ];
|
||||||
|
|
||||||
const writeOps = [];
|
const writeOps = [];
|
||||||
const ruleResources = [];
|
const ruleResources = [];
|
||||||
const rulesetDetails = [];
|
const rulesetDetails = [];
|
||||||
|
const regexRulesetDetails = new Map();
|
||||||
const outputDir = commandLineArgs.get('output') || '.';
|
const outputDir = commandLineArgs.get('output') || '.';
|
||||||
|
|
||||||
const output = [];
|
|
||||||
const log = (text, silent = false) => {
|
|
||||||
output.push(text);
|
|
||||||
if ( silent === false ) {
|
|
||||||
console.log(text);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get manifest content
|
// Get manifest content
|
||||||
const manifest = await fs.readFile(
|
const manifest = await fs.readFile(
|
||||||
`${outputDir}/manifest.json`,
|
`${outputDir}/manifest.json`,
|
||||||
@ -104,78 +158,62 @@ async function main() {
|
|||||||
return v;
|
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 rulesetDir = `${outputDir}/rulesets`;
|
||||||
const rulesetDirPromise = fs.mkdir(`${rulesetDir}`, { recursive: true });
|
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) =>
|
const writeFile = (path, data) =>
|
||||||
rulesetDirPromise.then(( ) =>
|
rulesetDirPromise.then(( ) =>
|
||||||
fs.writeFile(path, data));
|
fs.writeFile(path, data));
|
||||||
|
|
||||||
for ( const ruleset of rulesetConfigs ) {
|
const rulesetFromURLS = async function(assetDetails) {
|
||||||
const lists = [];
|
|
||||||
|
|
||||||
log('============================');
|
log('============================');
|
||||||
log(`Listset for '${ruleset.id}':`);
|
log(`Listset for '${assetDetails.id}':`);
|
||||||
|
|
||||||
if ( Array.isArray(ruleset.paths) ) {
|
// Remember fetched URLs
|
||||||
for ( const path of ruleset.paths ) {
|
const fetchedURLs = new Set();
|
||||||
log(`\t${path}`);
|
|
||||||
lists.push(readList(`assets/${path}`));
|
// 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) ) {
|
const text = parts.join('\n');
|
||||||
for ( const url of ruleset.urls ) {
|
|
||||||
log(`\t${url}`);
|
if ( text === '' ) {
|
||||||
lists.push(fetchList(url));
|
log('No filterset found');
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const details = await dnrRulesetFromRawLists(lists, {
|
const details = await dnrRulesetFromRawLists([ { name: assetDetails.id, text } ], { env });
|
||||||
env: [ 'chromium' ],
|
|
||||||
});
|
|
||||||
const { ruleset: rules } = details;
|
const { ruleset: rules } = details;
|
||||||
log(`Input filter count: ${details.filterCount}`);
|
log(`Input filter count: ${details.filterCount}`);
|
||||||
log(`\tAccepted filter count: ${details.acceptedFilterCount}`);
|
log(`\tAccepted filter count: ${details.acceptedFilterCount}`);
|
||||||
@ -215,17 +253,11 @@ async function main() {
|
|||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
writeOps.push(
|
|
||||||
writeFile(
|
|
||||||
`${rulesetDir}/${ruleset.id}.json`,
|
|
||||||
`${JSON.stringify(good, replacer, 2)}\n`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
rulesetDetails.push({
|
rulesetDetails.push({
|
||||||
id: ruleset.id,
|
id: assetDetails.id,
|
||||||
name: ruleset.name,
|
name: assetDetails.name,
|
||||||
enabled: ruleset.enabled,
|
enabled: assetDetails.enabled,
|
||||||
|
lang: assetDetails.lang,
|
||||||
filters: {
|
filters: {
|
||||||
total: details.filterCount,
|
total: details.filterCount,
|
||||||
accepted: details.acceptedFilterCount,
|
accepted: details.acceptedFilterCount,
|
||||||
@ -236,24 +268,100 @@ async function main() {
|
|||||||
accepted: good.length,
|
accepted: good.length,
|
||||||
discarded: redirects.length + headers.length + removeparams.length,
|
discarded: redirects.length + headers.length + removeparams.length,
|
||||||
rejected: bad.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({
|
ruleResources.push({
|
||||||
id: ruleset.id,
|
id: assetDetails.id,
|
||||||
enabled: ruleset.enabled,
|
enabled: assetDetails.enabled,
|
||||||
path: `/rulesets/${ruleset.id}.json`
|
path: `/rulesets/${assetDetails.id}.json`
|
||||||
});
|
});
|
||||||
|
|
||||||
goodTotalCount += good.length;
|
goodTotalCount += good.length;
|
||||||
maybeGoodTotalCount += regexes.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(
|
writeOps.push(
|
||||||
writeFile(
|
writeFile(
|
||||||
`${rulesetDir}/ruleset-details.js`,
|
`${rulesetDir}/ruleset-details.json`,
|
||||||
`export default ${JSON.stringify(rulesetDetails, replacer, 2)};\n`
|
`${JSON.stringify(rulesetDetails, replacer, 2)}\n`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -276,7 +384,7 @@ async function main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Log results
|
// Log results
|
||||||
await fs.writeFile(`${outputDir}/log.txt`, output.join('\n') + '\n');
|
await fs.writeFile(`${outputDir}/log.txt`, stdOutput.join('\n') + '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
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.js"></script>
|
||||||
<script src="js/vapi-client-extra.js"></script>
|
<script src="js/vapi-client-extra.js"></script>
|
||||||
<script src="js/udom.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/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>
|
<script src="js/1p-filters.js" type="module"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
@ -78,10 +78,10 @@
|
|||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/vapi-client-extra.js"></script>
|
<script src="js/vapi-client-extra.js"></script>
|
||||||
<script src="js/udom.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/dashboard-common.js"></script>
|
||||||
<script src="js/cloud-ui.js"></script>
|
<script src="js/cloud-ui.js" type="module"></script>
|
||||||
<script src="js/3p-filters.js"></script>
|
<script src="js/3p-filters.js" type="module"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -52,7 +52,7 @@
|
|||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.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/dashboard-common.js"></script>
|
||||||
<script src="js/about.js"></script>
|
<script src="js/about.js"></script>
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.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/dashboard-common.js"></script>
|
||||||
<script src="js/advanced-settings.js"></script>
|
<script src="js/advanced-settings.js"></script>
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.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/dashboard-common.js"></script>
|
||||||
<script src="js/asset-viewer.js" type="module"></script>
|
<script src="js/asset-viewer.js" type="module"></script>
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>uBlock Origin</title>
|
<title>uBlock Origin Background Page</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="lib/lz4/lz4-block-codec-any.js"></script>
|
<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-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.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>
|
<script src="js/dashboard.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.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/dashboard-common.js"></script>
|
||||||
<script src="js/devtools.js" type="module"></script>
|
<script src="js/devtools.js" type="module"></script>
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@
|
|||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.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/document-blocked.js"></script>
|
<script src="js/document-blocked.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -58,9 +58,9 @@
|
|||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.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/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>
|
<script src="js/dyna-rules.js" type="module"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
import { i18n$ } from './i18n.js';
|
||||||
import './codemirror/ubo-static-filtering.js';
|
import './codemirror/ubo-static-filtering.js';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
@ -234,7 +235,7 @@ const startImportFilePicker = function() {
|
|||||||
const exportUserFiltersToFile = function() {
|
const exportUserFiltersToFile = function() {
|
||||||
const val = getEditorText();
|
const val = getEditorText();
|
||||||
if ( val === '' ) { return; }
|
if ( val === '' ) { return; }
|
||||||
const filename = vAPI.i18n('1pExportFilename')
|
const filename = i18n$('1pExportFilename')
|
||||||
.replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString())
|
.replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString())
|
||||||
.replace(/ +/g, '_');
|
.replace(/ +/g, '_');
|
||||||
vAPI.download({
|
vAPI.download({
|
||||||
|
@ -25,13 +25,12 @@
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
{
|
import { i18n, i18n$ } from './i18n.js';
|
||||||
// >>>>> start of local scope
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const lastUpdateTemplateString = vAPI.i18n('3pLastUpdate');
|
const lastUpdateTemplateString = i18n$('3pLastUpdate');
|
||||||
const obsoleteTemplateString = vAPI.i18n('3pExternalListObsolete');
|
const obsoleteTemplateString = i18n$('3pExternalListObsolete');
|
||||||
const reValidExternalList = /^[a-z-]+:\/\/(?:\S+\/\S*|\/\S+)/m;
|
const reValidExternalList = /^[a-z-]+:\/\/(?:\S+\/\S*|\/\S+)/m;
|
||||||
|
|
||||||
let listDetails = {};
|
let listDetails = {};
|
||||||
@ -70,8 +69,8 @@ const renderNumber = function(value) {
|
|||||||
const renderFilterLists = function(soft) {
|
const renderFilterLists = function(soft) {
|
||||||
const listGroupTemplate = uDom('#templates .groupEntry');
|
const listGroupTemplate = uDom('#templates .groupEntry');
|
||||||
const listEntryTemplate = uDom('#templates .listEntry');
|
const listEntryTemplate = uDom('#templates .listEntry');
|
||||||
const listStatsTemplate = vAPI.i18n('3pListsOfBlockedHostsPerListStats');
|
const listStatsTemplate = i18n$('3pListsOfBlockedHostsPerListStats');
|
||||||
const renderElapsedTimeToString = vAPI.i18n.renderElapsedTimeToString;
|
const renderElapsedTimeToString = i18n.renderElapsedTimeToString;
|
||||||
const groupNames = new Map([ [ 'user', '' ] ]);
|
const groupNames = new Map([ [ 'user', '' ] ]);
|
||||||
|
|
||||||
// Assemble a pretty list name if possible
|
// Assemble a pretty list name if possible
|
||||||
@ -189,7 +188,7 @@ const renderFilterLists = function(soft) {
|
|||||||
liGroup = listGroupTemplate.clone().nodeAt(0);
|
liGroup = listGroupTemplate.clone().nodeAt(0);
|
||||||
let groupName = groupNames.get(groupKey);
|
let groupName = groupNames.get(groupKey);
|
||||||
if ( groupName === undefined ) {
|
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);
|
groupNames.set(groupKey, groupName);
|
||||||
}
|
}
|
||||||
if ( groupName !== '' ) {
|
if ( groupName !== '' ) {
|
||||||
@ -289,7 +288,7 @@ const renderFilterLists = function(soft) {
|
|||||||
uDom.nodeFromId('autoUpdate').checked =
|
uDom.nodeFromId('autoUpdate').checked =
|
||||||
listDetails.autoUpdate === true;
|
listDetails.autoUpdate === true;
|
||||||
uDom.nodeFromId('listsOfBlockedHostsPrompt').textContent =
|
uDom.nodeFromId('listsOfBlockedHostsPrompt').textContent =
|
||||||
vAPI.i18n('3pListsOfBlockedHostsPrompt')
|
i18n$('3pListsOfBlockedHostsPrompt')
|
||||||
.replace(
|
.replace(
|
||||||
'{{netFilterCount}}',
|
'{{netFilterCount}}',
|
||||||
renderNumber(details.netFilterCount)
|
renderNumber(details.netFilterCount)
|
||||||
@ -362,7 +361,7 @@ const updateAssetStatus = function(details) {
|
|||||||
'title',
|
'title',
|
||||||
lastUpdateTemplateString.replace(
|
lastUpdateTemplateString.replace(
|
||||||
'{{ago}}',
|
'{{ago}}',
|
||||||
vAPI.i18n.renderElapsedTimeToString(Date.now())
|
i18n$.renderElapsedTimeToString(Date.now())
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -711,7 +710,3 @@ uDom('#lists').on('click', '.listEntry label *', ev => {
|
|||||||
renderFilterLists();
|
renderFilterLists();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// <<<<< end of local scope
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -27,12 +27,13 @@ import cacheStorage from './cachestorage.js';
|
|||||||
import logger from './logger.js';
|
import logger from './logger.js';
|
||||||
import µb from './background.js';
|
import µb from './background.js';
|
||||||
import { StaticFilteringParser } from './static-filtering-parser.js';
|
import { StaticFilteringParser } from './static-filtering-parser.js';
|
||||||
|
import { i18n$ } from './i18n.js';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const reIsExternalPath = /^(?:[a-z-]+):\/\//;
|
const reIsExternalPath = /^(?:[a-z-]+):\/\//;
|
||||||
const reIsUserAsset = /^user-/;
|
const reIsUserAsset = /^user-/;
|
||||||
const errorCantConnectTo = vAPI.i18n('errorCantConnectTo');
|
const errorCantConnectTo = i18n$('errorCantConnectTo');
|
||||||
|
|
||||||
const assets = {};
|
const assets = {};
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import { i18n, i18n$ } from './i18n.js';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(( ) => {
|
(( ) => {
|
||||||
@ -58,7 +60,7 @@ const fetchStorageUsed = async function() {
|
|||||||
elem.classList.add('hide');
|
elem.classList.add('hide');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const units = ' ' + vAPI.i18n('genericBytes');
|
const units = ' ' + i18n$('genericBytes');
|
||||||
elem.title = result.max.toLocaleString() + units;
|
elem.title = result.max.toLocaleString() + units;
|
||||||
const total = (result.total / result.max * 100).toFixed(1);
|
const total = (result.total / result.max * 100).toFixed(1);
|
||||||
elem = elem.firstElementChild;
|
elem = elem.firstElementChild;
|
||||||
@ -206,7 +208,7 @@ const onInitialize = function(options) {
|
|||||||
|
|
||||||
faIconsInit(widget);
|
faIconsInit(widget);
|
||||||
|
|
||||||
vAPI.i18n.render(widget);
|
i18n.render(widget);
|
||||||
widget.classList.remove('hide');
|
widget.classList.remove('hide');
|
||||||
|
|
||||||
uDom('#cloudPush').on('click', ( ) => { pushData(); });
|
uDom('#cloudPush').on('click', ( ) => { pushData(); });
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
import µb from './background.js';
|
import µb from './background.js';
|
||||||
|
import { i18n$ } from './i18n.js';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
@ -134,28 +135,28 @@ const onEntryClicked = function(details, tab) {
|
|||||||
const menuEntries = {
|
const menuEntries = {
|
||||||
blockElement: {
|
blockElement: {
|
||||||
id: 'uBlock0-blockElement',
|
id: 'uBlock0-blockElement',
|
||||||
title: vAPI.i18n('pickerContextMenuEntry'),
|
title: i18n$('pickerContextMenuEntry'),
|
||||||
contexts: [ 'all' ],
|
contexts: [ 'all' ],
|
||||||
},
|
},
|
||||||
blockElementInFrame: {
|
blockElementInFrame: {
|
||||||
id: 'uBlock0-blockElementInFrame',
|
id: 'uBlock0-blockElementInFrame',
|
||||||
title: vAPI.i18n('contextMenuBlockElementInFrame'),
|
title: i18n$('contextMenuBlockElementInFrame'),
|
||||||
contexts: [ 'frame' ],
|
contexts: [ 'frame' ],
|
||||||
},
|
},
|
||||||
blockResource: {
|
blockResource: {
|
||||||
id: 'uBlock0-blockResource',
|
id: 'uBlock0-blockResource',
|
||||||
title: vAPI.i18n('pickerContextMenuEntry'),
|
title: i18n$('pickerContextMenuEntry'),
|
||||||
contexts: [ 'audio', 'frame', 'image', 'video' ],
|
contexts: [ 'audio', 'frame', 'image', 'video' ],
|
||||||
},
|
},
|
||||||
subscribeToList: {
|
subscribeToList: {
|
||||||
id: 'uBlock0-subscribeToList',
|
id: 'uBlock0-subscribeToList',
|
||||||
title: vAPI.i18n('contextMenuSubscribeToList'),
|
title: i18n$('contextMenuSubscribeToList'),
|
||||||
contexts: [ 'link' ],
|
contexts: [ 'link' ],
|
||||||
targetUrlPatterns: [ 'abp:*', 'https://subscribe.adblockplus.org/*' ],
|
targetUrlPatterns: [ 'abp:*', 'https://subscribe.adblockplus.org/*' ],
|
||||||
},
|
},
|
||||||
temporarilyAllowLargeMediaElements: {
|
temporarilyAllowLargeMediaElements: {
|
||||||
id: 'uBlock0-temporarilyAllowLargeMediaElements',
|
id: 'uBlock0-temporarilyAllowLargeMediaElements',
|
||||||
title: vAPI.i18n('contextMenuTemporarilyAllowLargeMediaElements'),
|
title: i18n$('contextMenuTemporarilyAllowLargeMediaElements'),
|
||||||
contexts: [ 'all' ],
|
contexts: [ 'all' ],
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -25,11 +25,6 @@
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
{
|
|
||||||
// >>>>> start of local scope
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
const discardUnsavedData = function(synchronous = false) {
|
const discardUnsavedData = function(synchronous = false) {
|
||||||
const paneFrame = document.getElementById('iframe');
|
const paneFrame = document.getElementById('iframe');
|
||||||
const paneWindow = paneFrame.contentWindow;
|
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; }
|
if ( search === '' ) { return false; }
|
||||||
|
|
||||||
url.search = '';
|
url.search = '';
|
||||||
const li = liFromParam(vAPI.i18n('docblockedNoParamsPrompt'), url.href);
|
const li = liFromParam(i18n$('docblockedNoParamsPrompt'), url.href);
|
||||||
parentNode.appendChild(li);
|
parentNode.appendChild(li);
|
||||||
|
|
||||||
const params = new self.URLSearchParams(search);
|
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 publicSuffixList from '../lib/publicsuffixlist/publicsuffixlist.js';
|
||||||
|
|
||||||
import { hostnameFromURI } from './uri-utils.js';
|
import { hostnameFromURI } from './uri-utils.js';
|
||||||
|
import { i18n$ } from './i18n.js';
|
||||||
|
|
||||||
import './codemirror/ubo-dynamic-filtering.js';
|
import './codemirror/ubo-dynamic-filtering.js';
|
||||||
|
|
||||||
@ -80,13 +81,13 @@ let isCollapsed = false;
|
|||||||
// reliably the default title attribute assigned by CodeMirror.
|
// reliably the default title attribute assigned by CodeMirror.
|
||||||
|
|
||||||
{
|
{
|
||||||
const i18nCommitStr = vAPI.i18n('rulesCommit');
|
const i18nCommitStr = i18n$('rulesCommit');
|
||||||
const i18nRevertStr = vAPI.i18n('rulesRevert');
|
const i18nRevertStr = i18n$('rulesRevert');
|
||||||
const commitArrowSelector = '.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy-reverse:not([title="' + i18nCommitStr + '"])';
|
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 + '"])';
|
const revertArrowSelector = '.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy:not([title="' + i18nRevertStr + '"])';
|
||||||
|
|
||||||
uDom.nodeFromSelector('.CodeMirror-merge-scrolllock')
|
uDom.nodeFromSelector('.CodeMirror-merge-scrolllock')
|
||||||
.setAttribute('title', vAPI.i18n('genericMergeViewScrollLock'));
|
.setAttribute('title', i18n$('genericMergeViewScrollLock'));
|
||||||
|
|
||||||
const translate = function() {
|
const translate = function() {
|
||||||
let elems = document.querySelectorAll(commitArrowSelector);
|
let elems = document.querySelectorAll(commitArrowSelector);
|
||||||
@ -340,7 +341,7 @@ const startImportFilePicker = function() {
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
function exportUserRulesToFile() {
|
function exportUserRulesToFile() {
|
||||||
const filename = vAPI.i18n('rulesDefaultFileName')
|
const filename = i18n$('rulesDefaultFileName')
|
||||||
.replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString())
|
.replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString())
|
||||||
.replace(/ +/g, '_');
|
.replace(/ +/g, '_');
|
||||||
vAPI.download({
|
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
|
const i18n =
|
||||||
// to ensure all i18n targets are already loaded.
|
self.browser instanceof Object &&
|
||||||
|
self.browser instanceof Element === false
|
||||||
{
|
? self.browser.i18n
|
||||||
// >>>>> start of local scope
|
: self.chrome.i18n;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/2084
|
function i18n$(...args) {
|
||||||
// Anything else than <a>, <b>, <code>, <em>, <i>, and <span> will
|
return i18n.getMessage(...args);
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
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 { hostnameFromURI } from './uri-utils.js';
|
||||||
|
import { i18n, i18n$ } from './i18n.js';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
@ -869,7 +870,7 @@ const viewPort = (( ) => {
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const updateCurrentTabTitle = (( ) => {
|
const updateCurrentTabTitle = (( ) => {
|
||||||
const i18nCurrentTab = vAPI.i18n('loggerCurrentTab');
|
const i18nCurrentTab = i18n$('loggerCurrentTab');
|
||||||
|
|
||||||
return function() {
|
return function() {
|
||||||
const select = uDom.nodeFromId('pageSelector');
|
const select = uDom.nodeFromId('pageSelector');
|
||||||
@ -1572,7 +1573,7 @@ const reloadTab = function(ev) {
|
|||||||
}
|
}
|
||||||
// https://github.com/gorhill/uBlock/issues/2179
|
// https://github.com/gorhill/uBlock/issues/2179
|
||||||
if ( rows[1].children[1].childElementCount === 0 ) {
|
if ( rows[1].children[1].childElementCount === 0 ) {
|
||||||
vAPI.i18n.safeTemplateToDOM(
|
i18n.safeTemplateToDOM(
|
||||||
'loggerStaticFilteringFinderSentence2',
|
'loggerStaticFilteringFinderSentence2',
|
||||||
{ filter: rawFilter },
|
{ filter: rawFilter },
|
||||||
rows[1].children[1]
|
rows[1].children[1]
|
||||||
@ -1728,7 +1729,7 @@ const reloadTab = function(ev) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fillOriginSelect = function(select, hostname, domain) {
|
const fillOriginSelect = function(select, hostname, domain) {
|
||||||
const template = vAPI.i18n('loggerStaticFilteringSentencePartOrigin');
|
const template = i18n$('loggerStaticFilteringSentencePartOrigin');
|
||||||
let value = hostname;
|
let value = hostname;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
@ -1748,7 +1749,7 @@ const reloadTab = function(ev) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const template = vAPI.i18n('loggerStaticFilteringSentence');
|
const template = i18n$('loggerStaticFilteringSentence');
|
||||||
const rePlaceholder = /\{\{[^}]+?\}\}/g;
|
const rePlaceholder = /\{\{[^}]+?\}\}/g;
|
||||||
const nodes = [];
|
const nodes = [];
|
||||||
let pos = 0;
|
let pos = 0;
|
||||||
@ -1770,11 +1771,11 @@ const reloadTab = function(ev) {
|
|||||||
select.className = 'static action';
|
select.className = 'static action';
|
||||||
option = document.createElement('option');
|
option = document.createElement('option');
|
||||||
option.setAttribute('value', '');
|
option.setAttribute('value', '');
|
||||||
option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartBlock');
|
option.textContent = i18n$('loggerStaticFilteringSentencePartBlock');
|
||||||
select.appendChild(option);
|
select.appendChild(option);
|
||||||
option = document.createElement('option');
|
option = document.createElement('option');
|
||||||
option.setAttribute('value', '@@');
|
option.setAttribute('value', '@@');
|
||||||
option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartAllow');
|
option.textContent = i18n$('loggerStaticFilteringSentencePartAllow');
|
||||||
select.appendChild(option);
|
select.appendChild(option);
|
||||||
nodes.push(select);
|
nodes.push(select);
|
||||||
break;
|
break;
|
||||||
@ -1785,11 +1786,11 @@ const reloadTab = function(ev) {
|
|||||||
select.className = 'static type';
|
select.className = 'static type';
|
||||||
option = document.createElement('option');
|
option = document.createElement('option');
|
||||||
option.setAttribute('value', filterType);
|
option.setAttribute('value', filterType);
|
||||||
option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartType').replace('{{type}}', filterType);
|
option.textContent = i18n$('loggerStaticFilteringSentencePartType').replace('{{type}}', filterType);
|
||||||
select.appendChild(option);
|
select.appendChild(option);
|
||||||
option = document.createElement('option');
|
option = document.createElement('option');
|
||||||
option.setAttribute('value', '');
|
option.setAttribute('value', '');
|
||||||
option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartAnyType');
|
option.textContent = i18n$('loggerStaticFilteringSentencePartAnyType');
|
||||||
select.appendChild(option);
|
select.appendChild(option);
|
||||||
nodes.push(select);
|
nodes.push(select);
|
||||||
break;
|
break;
|
||||||
@ -1813,7 +1814,7 @@ const reloadTab = function(ev) {
|
|||||||
fillOriginSelect(select, targetFrameHostname, targetFrameDomain);
|
fillOriginSelect(select, targetFrameHostname, targetFrameDomain);
|
||||||
option = document.createElement('option');
|
option = document.createElement('option');
|
||||||
option.setAttribute('value', '');
|
option.setAttribute('value', '');
|
||||||
option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartAnyOrigin');
|
option.textContent = i18n$('loggerStaticFilteringSentencePartAnyOrigin');
|
||||||
select.appendChild(option);
|
select.appendChild(option);
|
||||||
nodes.push(select);
|
nodes.push(select);
|
||||||
break;
|
break;
|
||||||
@ -1823,11 +1824,11 @@ const reloadTab = function(ev) {
|
|||||||
select.className = 'static importance';
|
select.className = 'static importance';
|
||||||
option = document.createElement('option');
|
option = document.createElement('option');
|
||||||
option.setAttribute('value', '');
|
option.setAttribute('value', '');
|
||||||
option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartNotImportant');
|
option.textContent = i18n$('loggerStaticFilteringSentencePartNotImportant');
|
||||||
select.appendChild(option);
|
select.appendChild(option);
|
||||||
option = document.createElement('option');
|
option = document.createElement('option');
|
||||||
option.setAttribute('value', 'important');
|
option.setAttribute('value', 'important');
|
||||||
option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartImportant');
|
option.textContent = i18n$('loggerStaticFilteringSentencePartImportant');
|
||||||
select.appendChild(option);
|
select.appendChild(option);
|
||||||
nodes.push(select);
|
nodes.push(select);
|
||||||
break;
|
break;
|
||||||
|
@ -40,6 +40,7 @@ import µb from './background.js';
|
|||||||
import webRequest from './traffic.js';
|
import webRequest from './traffic.js';
|
||||||
import { denseBase64 } from './base64-custom.js';
|
import { denseBase64 } from './base64-custom.js';
|
||||||
import { dnrRulesetFromRawLists } from './static-dnr-filtering.js';
|
import { dnrRulesetFromRawLists } from './static-dnr-filtering.js';
|
||||||
|
import { i18n$ } from './i18n.js';
|
||||||
import { redirectEngine } from './redirect-engine.js';
|
import { redirectEngine } from './redirect-engine.js';
|
||||||
import { StaticFilteringParser } from './static-filtering-parser.js';
|
import { StaticFilteringParser } from './static-filtering-parser.js';
|
||||||
|
|
||||||
@ -1075,7 +1076,7 @@ const backupUserData = async function() {
|
|||||||
userFilters: userFilters.content,
|
userFilters: userFilters.content,
|
||||||
};
|
};
|
||||||
|
|
||||||
const filename = vAPI.i18n('aboutBackupFilename')
|
const filename = i18n$('aboutBackupFilename')
|
||||||
.replace('{{datetime}}', µb.dateNowToSensibleString())
|
.replace('{{datetime}}', µb.dateNowToSensibleString())
|
||||||
.replace(/ +/g, '_');
|
.replace(/ +/g, '_');
|
||||||
µb.restoreBackupSettings.lastBackupFile = filename;
|
µb.restoreBackupSettings.lastBackupFile = filename;
|
||||||
@ -1316,7 +1317,7 @@ const getShortcuts = function(callback) {
|
|||||||
let desc = command.description;
|
let desc = command.description;
|
||||||
let match = /^__MSG_(.+?)__$/.exec(desc);
|
let match = /^__MSG_(.+?)__$/.exec(desc);
|
||||||
if ( match !== null ) {
|
if ( match !== null ) {
|
||||||
desc = vAPI.i18n(match[1]);
|
desc = i18n$(match[1]);
|
||||||
}
|
}
|
||||||
if ( desc === '' ) { continue; }
|
if ( desc === '' ) { continue; }
|
||||||
command.description = desc;
|
command.description = desc;
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import punycode from '../lib/punycode.js';
|
import punycode from '../lib/punycode.js';
|
||||||
|
import { i18n$ } from './i18n.js';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
@ -50,8 +51,8 @@ const scopeToSrcHostnameMap = {
|
|||||||
'.': ''
|
'.': ''
|
||||||
};
|
};
|
||||||
const hostnameToSortableTokenMap = new Map();
|
const hostnameToSortableTokenMap = new Map();
|
||||||
const statsStr = vAPI.i18n('popupBlockedStats');
|
const statsStr = i18n$('popupBlockedStats');
|
||||||
const domainsHitStr = vAPI.i18n('popupHitDomainCount');
|
const domainsHitStr = i18n$('popupHitDomainCount');
|
||||||
|
|
||||||
let popupData = {};
|
let popupData = {};
|
||||||
let dfPaneBuilt = false;
|
let dfPaneBuilt = false;
|
||||||
@ -643,7 +644,7 @@ const renderTooltips = function(selector) {
|
|||||||
if ( selector !== undefined && key !== selector ) { continue; }
|
if ( selector !== undefined && key !== selector ) { continue; }
|
||||||
const elem = uDom.nodeFromSelector(key);
|
const elem = uDom.nodeFromSelector(key);
|
||||||
if ( elem.hasAttribute('title') === false ) { continue; }
|
if ( elem.hasAttribute('title') === false ) { continue; }
|
||||||
const text = vAPI.i18n(
|
const text = i18n$(
|
||||||
details.i18n +
|
details.i18n +
|
||||||
(uDom.nodeFromSelector(details.state) === null ? '1' : '2')
|
(uDom.nodeFromSelector(details.state) === null ? '1' : '2')
|
||||||
);
|
);
|
||||||
|
@ -27,6 +27,7 @@ import staticNetFilteringEngine from './static-net-filtering.js';
|
|||||||
import µb from './background.js';
|
import µb from './background.js';
|
||||||
import { CompiledListWriter } from './static-filtering-io.js';
|
import { CompiledListWriter } from './static-filtering-io.js';
|
||||||
import { StaticFilteringParser } from './static-filtering-parser.js';
|
import { StaticFilteringParser } from './static-filtering-parser.js';
|
||||||
|
import { i18n$ } from './i18n.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
domainFromHostname,
|
domainFromHostname,
|
||||||
@ -110,7 +111,7 @@ const initWorker = function() {
|
|||||||
entries.set(listKey, {
|
entries.set(listKey, {
|
||||||
title: listKey !== µb.userFiltersPath ?
|
title: listKey !== µb.userFiltersPath ?
|
||||||
entry.title :
|
entry.title :
|
||||||
vAPI.i18n('1pPageName'),
|
i18n$('1pPageName'),
|
||||||
supportURL: entry.supportURL || ''
|
supportURL: entry.supportURL || ''
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,7 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/******************************************************************************/
|
import { i18n$ } from './i18n.js';
|
||||||
|
|
||||||
(( ) => {
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
@ -63,11 +61,11 @@ const handleImportFilePicker = function() {
|
|||||||
userData = undefined;
|
userData = undefined;
|
||||||
}
|
}
|
||||||
if ( userData === undefined ) {
|
if ( userData === undefined ) {
|
||||||
window.alert(vAPI.i18n('aboutRestoreDataError'));
|
window.alert(i18n$('aboutRestoreDataError'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const time = new Date(userData.timeStamp);
|
const time = new Date(userData.timeStamp);
|
||||||
const msg = vAPI.i18n('aboutRestoreDataConfirm')
|
const msg = i18n$('aboutRestoreDataConfirm')
|
||||||
.replace('{{time}}', time.toLocaleString());
|
.replace('{{time}}', time.toLocaleString());
|
||||||
const proceed = window.confirm(msg);
|
const proceed = window.confirm(msg);
|
||||||
if ( proceed !== true ) { return; }
|
if ( proceed !== true ) { return; }
|
||||||
@ -137,9 +135,9 @@ const onLocalDataReceived = function(details) {
|
|||||||
unit = '';
|
unit = '';
|
||||||
}
|
}
|
||||||
uDom.nodeFromId('storageUsed').textContent =
|
uDom.nodeFromId('storageUsed').textContent =
|
||||||
vAPI.i18n('storageUsed')
|
i18n$('storageUsed')
|
||||||
.replace('{{value}}', v.toLocaleString(undefined, { maximumSignificantDigits: 3 }))
|
.replace('{{value}}', v.toLocaleString(undefined, { maximumSignificantDigits: 3 }))
|
||||||
.replace('{{unit}}', unit && vAPI.i18n(unit) || '');
|
.replace('{{unit}}', unit && i18n$(unit) || '');
|
||||||
|
|
||||||
const timeOptions = {
|
const timeOptions = {
|
||||||
weekday: 'long',
|
weekday: 'long',
|
||||||
@ -154,7 +152,7 @@ const onLocalDataReceived = function(details) {
|
|||||||
const lastBackupFile = details.lastBackupFile || '';
|
const lastBackupFile = details.lastBackupFile || '';
|
||||||
if ( lastBackupFile !== '' ) {
|
if ( lastBackupFile !== '' ) {
|
||||||
const dt = new Date(details.lastBackupTime);
|
const dt = new Date(details.lastBackupTime);
|
||||||
const text = vAPI.i18n('settingsLastBackupPrompt');
|
const text = i18n$('settingsLastBackupPrompt');
|
||||||
const node = uDom.nodeFromId('settingsLastBackupPrompt');
|
const node = uDom.nodeFromId('settingsLastBackupPrompt');
|
||||||
node.textContent = text + '\xA0' + dt.toLocaleString('fullwide', timeOptions);
|
node.textContent = text + '\xA0' + dt.toLocaleString('fullwide', timeOptions);
|
||||||
node.style.display = '';
|
node.style.display = '';
|
||||||
@ -163,7 +161,7 @@ const onLocalDataReceived = function(details) {
|
|||||||
const lastRestoreFile = details.lastRestoreFile || '';
|
const lastRestoreFile = details.lastRestoreFile || '';
|
||||||
if ( lastRestoreFile !== '' ) {
|
if ( lastRestoreFile !== '' ) {
|
||||||
const dt = new Date(details.lastRestoreTime);
|
const dt = new Date(details.lastRestoreTime);
|
||||||
const text = vAPI.i18n('settingsLastRestorePrompt');
|
const text = i18n$('settingsLastRestorePrompt');
|
||||||
const node = uDom.nodeFromId('settingsLastRestorePrompt');
|
const node = uDom.nodeFromId('settingsLastRestorePrompt');
|
||||||
node.textContent = text + '\xA0' + dt.toLocaleString('fullwide', timeOptions);
|
node.textContent = text + '\xA0' + dt.toLocaleString('fullwide', timeOptions);
|
||||||
node.style.display = '';
|
node.style.display = '';
|
||||||
@ -183,7 +181,7 @@ const onLocalDataReceived = function(details) {
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const resetUserData = function() {
|
const resetUserData = function() {
|
||||||
const msg = vAPI.i18n('aboutResetDataConfirm');
|
const msg = i18n$('aboutResetDataConfirm');
|
||||||
const proceed = window.confirm(msg);
|
const proceed = window.confirm(msg);
|
||||||
if ( proceed !== true ) { return; }
|
if ( proceed !== true ) { return; }
|
||||||
vAPI.messaging.send('dashboard', {
|
vAPI.messaging.send('dashboard', {
|
||||||
@ -306,5 +304,3 @@ document.querySelector(
|
|||||||
);
|
);
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
})();
|
|
||||||
|
@ -91,7 +91,11 @@ async function dnrRulesetFromRawLists(lists, options = {}) {
|
|||||||
const toLoad = [];
|
const toLoad = [];
|
||||||
const toDNR = (context, list) => addToDNR(context, list);
|
const toDNR = (context, list) => addToDNR(context, list);
|
||||||
for ( const list of lists ) {
|
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);
|
await Promise.all(toLoad);
|
||||||
return staticNetFilteringEngine.dnrFromCompiled('end', context);
|
return staticNetFilteringEngine.dnrFromCompiled('end', context);
|
||||||
|
@ -3176,10 +3176,17 @@ Parser.utils = Parser.prototype.utils = (( ) => {
|
|||||||
[ 'adguard_ext_safari', 'false' ],
|
[ 'adguard_ext_safari', 'false' ],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const toURL = url => {
|
||||||
|
try {
|
||||||
|
return new URL(url.trim());
|
||||||
|
} catch (ex) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class preparser {
|
class preparser {
|
||||||
// This method returns an array of indices, corresponding to position in
|
// This method returns an array of indices, corresponding to position in
|
||||||
// the content string which should alternatively be parsed and discarded.
|
// 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 reIf = /^!#(if|endif)\b([^\n]*)(?:[\n\r]+|$)/gm;
|
||||||
const stack = [];
|
const stack = [];
|
||||||
const shouldDiscard = ( ) => stack.some(v => v);
|
const shouldDiscard = ( ) => stack.some(v => v);
|
||||||
@ -3205,7 +3212,6 @@ Parser.utils = Parser.prototype.utils = (( ) => {
|
|||||||
}
|
}
|
||||||
stack.push(startDiscard);
|
stack.push(startDiscard);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'endif':
|
case 'endif':
|
||||||
stack.pop();
|
stack.pop();
|
||||||
const stopDiscard = shouldDiscard() === false;
|
const stopDiscard = shouldDiscard() === false;
|
||||||
@ -3214,7 +3220,6 @@ Parser.utils = Parser.prototype.utils = (( ) => {
|
|||||||
discard = false;
|
discard = false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -3224,6 +3229,47 @@ Parser.utils = Parser.prototype.utils = (( ) => {
|
|||||||
return parts;
|
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) {
|
static prune(content, env) {
|
||||||
const parts = this.splitter(content, env);
|
const parts = this.splitter(content, env);
|
||||||
const out = [];
|
const out = [];
|
||||||
|
@ -37,6 +37,7 @@ import staticFilteringReverseLookup from './reverselookup.js';
|
|||||||
import staticNetFilteringEngine from './static-net-filtering.js';
|
import staticNetFilteringEngine from './static-net-filtering.js';
|
||||||
import µb from './background.js';
|
import µb from './background.js';
|
||||||
import { hostnameFromURI } from './uri-utils.js';
|
import { hostnameFromURI } from './uri-utils.js';
|
||||||
|
import { i18n, i18n$ } from './i18n.js';
|
||||||
import { redirectEngine } from './redirect-engine.js';
|
import { redirectEngine } from './redirect-engine.js';
|
||||||
import { sparseBase64 } from './base64-custom.js';
|
import { sparseBase64 } from './base64-custom.js';
|
||||||
import { StaticFilteringParser } from './static-filtering-parser.js';
|
import { StaticFilteringParser } from './static-filtering-parser.js';
|
||||||
@ -625,7 +626,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
|
|||||||
newAvailableLists[this.userFiltersPath] = {
|
newAvailableLists[this.userFiltersPath] = {
|
||||||
content: 'filters',
|
content: 'filters',
|
||||||
group: 'user',
|
group: 'user',
|
||||||
title: vAPI.i18n('1pPageName'),
|
title: i18n$('1pPageName'),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Custom filter lists.
|
// Custom filter lists.
|
||||||
@ -1392,7 +1393,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
|
|||||||
if ( typeof details.lang === 'string' ) {
|
if ( typeof details.lang === 'string' ) {
|
||||||
let re = this.listMatchesEnvironment.reLang;
|
let re = this.listMatchesEnvironment.reLang;
|
||||||
if ( re === undefined ) {
|
if ( re === undefined ) {
|
||||||
const match = /^[a-z]+/.exec(browser.i18n.getUILanguage());
|
const match = /^[a-z]+/.exec(i18n.getUILanguage());
|
||||||
if ( match !== null ) {
|
if ( match !== null ) {
|
||||||
re = new RegExp('\\b' + match[0] + '\\b');
|
re = new RegExp('\\b' + match[0] + '\\b');
|
||||||
this.listMatchesEnvironment.reLang = re;
|
this.listMatchesEnvironment.reLang = re;
|
||||||
|
@ -30,6 +30,7 @@ import staticNetFilteringEngine from './static-net-filtering.js';
|
|||||||
import µb from './background.js';
|
import µb from './background.js';
|
||||||
import webext from './webext.js';
|
import webext from './webext.js';
|
||||||
import { PageStore } from './pagestore.js';
|
import { PageStore } from './pagestore.js';
|
||||||
|
import { i18n$ } from './i18n.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
sessionFirewall,
|
sessionFirewall,
|
||||||
@ -1052,7 +1053,7 @@ vAPI.tabs = new vAPI.Tabs();
|
|||||||
};
|
};
|
||||||
const pageStore = new NoPageStore(vAPI.noTabId);
|
const pageStore = new NoPageStore(vAPI.noTabId);
|
||||||
µb.pageStores.set(pageStore.tabId, pageStore);
|
µb.pageStores.set(pageStore.tabId, pageStore);
|
||||||
pageStore.title = vAPI.i18n('logBehindTheScene');
|
pageStore.title = i18n$('logBehindTheScene');
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -23,9 +23,7 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/******************************************************************************/
|
import { i18n$ } from './i18n.js';
|
||||||
|
|
||||||
(( ) => {
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
@ -204,7 +202,7 @@ const exportWhitelistToFile = function() {
|
|||||||
const val = getEditorText();
|
const val = getEditorText();
|
||||||
if ( val === '' ) { return; }
|
if ( val === '' ) { return; }
|
||||||
const filename =
|
const filename =
|
||||||
vAPI.i18n('whitelistExportFilename')
|
i18n$('whitelistExportFilename')
|
||||||
.replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString())
|
.replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString())
|
||||||
.replace(/ +/g, '_');
|
.replace(/ +/g, '_');
|
||||||
vAPI.download({
|
vAPI.download({
|
||||||
@ -262,5 +260,3 @@ uDom('#whitelistRevert').on('click', revertChanges);
|
|||||||
renderWhitelist();
|
renderWhitelist();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
})();
|
|
||||||
|
@ -213,7 +213,7 @@
|
|||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/vapi-client-extra.js"></script>
|
<script src="js/vapi-client-extra.js"></script>
|
||||||
<script src="js/udom.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.js" type="module"></script>
|
||||||
<script src="js/logger-ui-inspector.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-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.js"></script>
|
<script src="js/udom.js"></script>
|
||||||
<script src="js/i18n.js"></script>
|
<script src="js/i18n.js" type="module"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -101,7 +101,7 @@
|
|||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.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>
|
<script src="js/popup-fenix.js" type="module"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
@ -90,9 +90,9 @@
|
|||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.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/dashboard-common.js"></script>
|
||||||
<script src="js/settings.js"></script>
|
<script src="js/settings.js" type="module"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.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/dashboard-common.js"></script>
|
||||||
<script src="js/shortcuts.js"></script>
|
<script src="js/shortcuts.js"></script>
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@
|
|||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.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/dashboard-common.js"></script>
|
||||||
<script src="js/support.js"></script>
|
<script src="js/support.js"></script>
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@
|
|||||||
<script src="../js/vapi-client.js"></script>
|
<script src="../js/vapi-client.js"></script>
|
||||||
<script src="../js/vapi-client-extra.js"></script>
|
<script src="../js/vapi-client-extra.js"></script>
|
||||||
<script src="../js/udom.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>
|
<script src="../js/epicker-ui.js" type="module"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
@ -53,10 +53,10 @@
|
|||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.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/dashboard-common.js"></script>
|
||||||
<script src="js/cloud-ui.js"></script>
|
<script src="js/cloud-ui.js" type="module"></script>
|
||||||
<script src="js/whitelist.js"></script>
|
<script src="js/whitelist.js" type="module"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -7,72 +7,35 @@ set -e
|
|||||||
echo "*** uBlock: Importing from Crowdin archive"
|
echo "*** uBlock: Importing from Crowdin archive"
|
||||||
|
|
||||||
SRC=~/Downloads/crowdin
|
SRC=~/Downloads/crowdin
|
||||||
rm -r $SRC || true
|
rm -r $SRC || true > /dev/null
|
||||||
unzip -q ~/Downloads/uBlock\ \(translations\).zip -d $SRC
|
unzip -q ~/Downloads/uBlock\ \(translations\).zip -d $SRC
|
||||||
|
|
||||||
|
# https://www.assertnotmagic.com/2018/06/20/bash-brackets-quick-reference/
|
||||||
|
|
||||||
DES=./src/_locales
|
DES=./src/_locales
|
||||||
cp $SRC/ar/messages.json $DES/ar/messages.json
|
DESMV3=./platform/mv3/extension/_locales
|
||||||
cp $SRC/az/messages.json $DES/az/messages.json
|
|
||||||
cp $SRC/bg/messages.json $DES/bg/messages.json
|
for dir in $SRC/*/; do
|
||||||
cp $SRC/bn/messages.json $DES/bn/messages.json
|
srclang=$(basename $dir)
|
||||||
cp $SRC/bs/messages.json $DES/bs/messages.json
|
deslang=${srclang/-/_}
|
||||||
cp $SRC/ca/messages.json $DES/ca/messages.json
|
deslang=${deslang%_AM}
|
||||||
cp $SRC/cs/messages.json $DES/cs/messages.json
|
deslang=${deslang%_ES}
|
||||||
cp $SRC/cv/messages.json $DES/cv/messages.json
|
deslang=${deslang%_IE}
|
||||||
cp $SRC/da/messages.json $DES/da/messages.json
|
deslang=${deslang%_IN}
|
||||||
cp $SRC/de/messages.json $DES/de/messages.json
|
deslang=${deslang%_LK}
|
||||||
cp $SRC/el/messages.json $DES/el/messages.json
|
deslang=${deslang%_NL}
|
||||||
cp $SRC/en-GB/messages.json $DES/en_GB/messages.json
|
deslang=${deslang%_PK}
|
||||||
cp $SRC/eo/messages.json $DES/eo/messages.json
|
deslang=${deslang%_SE}
|
||||||
cp $SRC/es-ES/messages.json $DES/es/messages.json
|
if [[ $deslang == 'en' ]]; then
|
||||||
cp $SRC/et/messages.json $DES/et/messages.json
|
continue
|
||||||
cp $SRC/eu/messages.json $DES/eu/messages.json
|
fi
|
||||||
cp $SRC/fa/messages.json $DES/fa/messages.json
|
# ubo
|
||||||
cp $SRC/fi/messages.json $DES/fi/messages.json
|
mkdir -p "$DES/$deslang/" && cp "$SRC/$srclang/messages.json" "$DES/$deslang/"
|
||||||
cp $SRC/fil/messages.json $DES/fil/messages.json
|
# ubo lite
|
||||||
cp $SRC/fr/messages.json $DES/fr/messages.json
|
mkdir -p "$DESMV3/$deslang/" && cp "$SRC/$srclang/uBO Lite/messages.json" "$DESMV3/$deslang/"
|
||||||
cp $SRC/fy-NL/messages.json $DES/fy/messages.json
|
# descriptions
|
||||||
cp $SRC/gl/messages.json $DES/gl/messages.json
|
cp "$SRC/$srclang/description.txt" "./dist/description/description-${deslang}.txt"
|
||||||
cp $SRC/he/messages.json $DES/he/messages.json
|
done
|
||||||
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
|
|
||||||
|
|
||||||
# Output files with possible misuse of `$`, as this can lead to severe
|
# Output files with possible misuse of `$`, as this can lead to severe
|
||||||
# consequences, such as not being able to run the extension at all.
|
# 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
|
# See https://issues.adblockplus.org/ticket/6666
|
||||||
echo "*** uBlock: Instances of '\$':"
|
echo "*** uBlock: Instances of '\$':"
|
||||||
grep -FR "$" $DES/ || true
|
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
|
rm -r $SRC
|
||||||
echo "*** uBlock: Import done."
|
echo "*** uBlock: Import done."
|
||||||
|
@ -25,8 +25,12 @@ echo "*** uBlock0.mv3: Copying common files"
|
|||||||
cp -R src/css/fonts/* $DES/css/fonts/
|
cp -R src/css/fonts/* $DES/css/fonts/
|
||||||
cp src/css/themes/default.css $DES/css/
|
cp src/css/themes/default.css $DES/css/
|
||||||
cp src/css/common.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/css/fa-icons.css $DES/css/
|
||||||
|
|
||||||
cp src/js/fa-icons.js $DES/js/
|
cp src/js/fa-icons.js $DES/js/
|
||||||
|
cp src/js/i18n.js $DES/js/
|
||||||
|
|
||||||
cp LICENSE.txt $DES/
|
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/css/* $DES/css/
|
||||||
cp platform/mv3/extension/js/* $DES/js/
|
cp platform/mv3/extension/js/* $DES/js/
|
||||||
cp platform/mv3/extension/img/* $DES/img/
|
cp platform/mv3/extension/img/* $DES/img/
|
||||||
|
cp -R platform/mv3/extension/_locales $DES/
|
||||||
|
|
||||||
if [ "$1" != "quick" ]; then
|
if [ "$1" != "quick" ]; then
|
||||||
echo "*** uBlock0.mv3: Generating rulesets"
|
echo "*** uBlock0.mv3: Generating rulesets"
|
||||||
@ -44,6 +49,7 @@ if [ "$1" != "quick" ]; then
|
|||||||
./tools/make-nodejs.sh $TMPDIR
|
./tools/make-nodejs.sh $TMPDIR
|
||||||
cp platform/mv3/package.json $TMPDIR/
|
cp platform/mv3/package.json $TMPDIR/
|
||||||
cp platform/mv3/*.js $TMPDIR/
|
cp platform/mv3/*.js $TMPDIR/
|
||||||
|
cp assets/assets.json $TMPDIR/
|
||||||
cd $TMPDIR
|
cd $TMPDIR
|
||||||
node --no-warnings make-rulesets.js output=$DES quick=$QUICK
|
node --no-warnings make-rulesets.js output=$DES quick=$QUICK
|
||||||
cd - > /dev/null
|
cd - > /dev/null
|
||||||
|
Loading…
Reference in New Issue
Block a user