Replace popup with options page offering PAC configuration from a textarea (only frontend)

This commit is contained in:
ilyaigpetrov 2025-09-22 22:36:58 +05:00
parent f90c1adb2a
commit 5cb77c4013
5 changed files with 123 additions and 329 deletions

View File

@ -32,9 +32,9 @@ export const render = ({ version, edition }) => {
"action": { "action": {
"default_title": "default_title":
`__MSG_PacUpdated__ | __MSG_Version__: ${version + localizedSuffix}`, `__MSG_PacUpdated__ | __MSG_Version__: ${version + localizedSuffix}`,
"default_popup": "/src/pages/options/index.html"
}, },
"options_ui": { "options_ui": {
"open_in_tab": true,
"page": "/src/pages/options/index.html" "page": "/src/pages/options/index.html"
}, },
"commands": { "commands": {

View File

@ -1,54 +1 @@
import { versions, storage } from '../lib/index.mjs'; console.log('Data migration checks...');
globalThis.migrationPromise = new Promise(async (resolve) => {
console.log('Checking for migrations...');
const dflts = {
// TODO: Define defaults.
options: {},
};
const ifEmpty = await storage.isEmptyAsync();
if (ifEmpty) {
// Initialisation. First install.
await storage.setAsync({
...dflts,
version: versions.current,
});
return resolve();
}
// Migration (may be already migrated).
console.log(`Current extension version is ${versions.current}.`);
const oldVersion = await storage.getAsync('version');
const ifNoNeedToMigrate = oldVersion === versions.current;
if (ifNoNeedToMigrate) {
console.log('No need for migration.');
return resolve();
}
console.log(`Migrating to ${versions.current} from ${oldVersion || 'a very old version'}.`);
switch(true) {
case !oldVersion: {
// Update from version <= 0.0.1.
const ifSentence = await storage.getAsync('SOME_KEY');
if (ifSentence !== undefined) {
console.log('Migrating to 0.0.1.');
await storage.setAsync({
ifToEncodeUrlTerminators: ifSentence,
});
}
}; // Fallthrough.
case versions.isLeq(oldVersion, '0.0.18'): {
console.log('Migrating to >= 0.0.19.');
const oldState = await storage.getAsync();
// `oldState` looks like `{ 'ifToEncodeSentenceTerminators': true, 'ifFoobar': false }`.
const migratedOpts = dflts.options.reduce((acc, [ dfltKey, dfltValue ]) => {
const oldValue = oldState[dfltKey];
acc.push([ dfltKey, typeof(oldValue) === 'boolean' ? oldValue : dfltValue ]);
return acc;
}, []);
await storage.clearAsync();
await storage.setAsync({ ...dflts, options: migratedOpts });
}; // Fallthrough.
default:
await storage.setAsync({ version: versions.current });
}
return resolve();
});

View File

@ -1,116 +1 @@
import { storage } from '../lib/index.mjs'; browser.action.onClicked.addListener(handleClick);
console.log('Main script starts...');
(async () => {
const theState = await storage.getAsync();
console.log('The state is:', theState);
})();
/*
const ID_TO_MENU_HANDLER = {};
const createMenuEntry = (id, type, title, handler, contexts, rest) => {
ID_TO_MENU_HANDLER[id] = handler;
console.log('Registered handler for', id);
chrome.contextMenus.create({
id,
type,
title,
contexts,
...rest,
}, () => {
if (chrome.runtime.lastError) {
// Suppress menus recreation.
}
});
};
const copyUrlInstalledPromise = (async () => {
console.log('Main waits for migrations...');
await globalThis.migrationPromise;
console.log('Migration is finished.');
const options = await storage.getAsync('options');
// CheckBoxes
const capitalizeFirstLetter = (str) => str
.replace(
/^./g,
(firstLetter) => firstLetter.toUpperCase(),
);
options.forEach(([ key, value ], i) =>
createMenuEntry(key, 'checkbox',
chrome.i18n.getMessage(capitalizeFirstLetter(key)),
(info) => {
options[i] = [ key, info.checked ]; // Ordered.
storage.setAsync({ options });
},
['action'],
{
checked: value === true,
},
),
);
// /CheckBoxes
createMenuEntry('copyUrlFromTheAddressBar', 'normal',
chrome.i18n.getMessage('CopyUrlFromTheAddressBar'),
({ pageUrl }) => copyUrl(pageUrl),
['page'],
);
createMenuEntry('donate', 'normal',
chrome.i18n.getMessage('Donate'),
async (info) => {
chrome.tabs.create({ url: await storage.getAsync('donateUrl') });
},
['action'],
);
createMenuEntry('copyUrl', 'normal',
chrome.i18n.getMessage('CopyUnicodeUrl'),
(info) => copyUrl(
info.linkUrl ||
info.srcUrl ||
info.frameUrl ||
info.selectionText ||
info.pageUrl // Needed?
),
['link', 'image', 'video', 'audio', 'frame', 'selection'],
);
createMenuEntry(
'copyHighlightLink', 'normal',
chrome.i18n.getMessage('CopyUnicodeLinkToHighlight'),
(info) => {
copyUrl(`${info.pageUrl.replace(/#.*\/g, '')}#:~:text=${info.selectionText}`);
},
['selection'],
);
return Promise.resolve();
})();
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
const result = await copyUrlInstalledPromise;
console.log('Promise resolved to:', result);
const id = info.menuItemId;
console.log('ALL THE LISTENERS:', Object.keys(ID_TO_MENU_HANDLER));
const handler = ID_TO_MENU_HANDLER[id];
console.log(`Here is the handler for ${id} w/ 'info':`, handler, info);
if (handler) {
handler(info);
}
});
chrome.action.onClicked.addListener(async ({ url: urlToBeCopied }) => {
console.log('Main waits for listeners to be installed...');
const copyUrl = await copyUrlInstalledPromise;
console.log('Action clicked with url:', urlToBeCopied);
//copyUrl(urlToBeCopied);
});
*/

View File

@ -1,5 +1,4 @@
import './00-start.mjs'; console.log('Extension started.')
import './05-data-init-and-migrations.mjs'; chrome.action.onClicked.addListener(
import './10-main.mjs'; () => chrome.runtime.openOptionsPage(),
);
console.log('Background script finished loading.');

View File

@ -2,41 +2,73 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<script src="./top.mjs" type="module"></script> <title>PAC-конфигуратор</title>
<link rel="icon" href="/icons/default-128.png" type="image/png" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--script src="./top.mjs" type="module"></script-->
<style> <style>
/* /*
Josh's Custom CSS Reset Josh's Custom CSS Reset
https://www.joshwcomeau.com/css/custom-css-reset/ https://www.joshwcomeau.com/css/custom-css-reset/
*/ */
/* 1. Use a more-intuitive box-sizing model */
*, *::before, *::after { *, *::before, *::after {
box-sizing: border-box; box-sizing: border-box;
} }
/* 2. Remove default margin */
* { * {
margin: 0; margin: 0;
} }
/* 3. Enable keyword animations */
@media (prefers-reduced-motion: no-preference) {
html {
interpolate-size: allow-keywords;
}
}
body { body {
/* 4. Add accessible line-height */
line-height: 1.5; line-height: 1.5;
/* 5. Improve text rendering */
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
} }
/* 6. Improve media defaults */
img, picture, video, canvas, svg { img, picture, video, canvas, svg {
display: block; display: block;
max-width: 100%; max-width: 100%;
} }
/* 7. Inherit fonts for form controls */
input, button, textarea, select { input, button, textarea, select {
font: inherit; font: inherit;
} }
/* 8. Avoid text overflows */
p, h1, h2, h3, h4, h5, h6 { p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word; overflow-wrap: break-word;
} }
/* 9. Improve line wrapping */
p {
text-wrap: pretty;
}
h1, h2, h3, h4, h5, h6 {
text-wrap: balance;
}
/*
10. Create a root stacking context
*/
#root, #__next { #root, #__next {
isolation: isolate; isolation: isolate;
} }
/* Reset ends */ /* Reset ends */
:root {
border-color: black;
border-radius: 0;
/* /*
:root {
The initial theme is light but it's quickly inverted to dark. The initial theme is light but it's quickly inverted to dark.
Dark theme should be the first theme user sees by default. Dark theme should be the first theme user sees by default.
TODO: Ponder more. E.g. valid color (green) must remain the TODO: Ponder more. E.g. valid color (green) must remain the
@ -50,185 +82,116 @@
border: 0 none white; border: 0 none white;
outline: 0 none white;*/ outline: 0 none white;*/
/*color-scheme: light; /*color-scheme: light;
/* COLOR INVERTION */ /* COLOR INVERTION *
filter: invert(0); /* TODO: temporary disabled. */ filter: invert(0); TODO: temporary disabled.
} }*/
:root, html, body { :root, html, body {
/*background: url('./gsbg.png') no-repeat;*/ /*background: url('./gsbg.png') no-repeat;*/
padding: 0; padding: 0;
margin: 0; margin: 0;
width: 100%;
height: 100%;
outline: none;
border: 0;
} }
body { body {
--ribbon-color: #0075ff; /* #4169e1;*/ --ribbon-color: #0075ff; /* #4169e1;*/
font-family: Ubuntu, Arial, sans-serif; font-family: Ubuntu, Arial, sans-serif;
font-size: 80%; font-size: 80%;
padding: 10px; display: flex;
flex-direction: column;
}
nav {
display: flex;
flex-direction: row;
padding: 0.2em;
}
button {
margin-left: 0.1em;
padding: 1px 6px 1px 6px;
}
output#status {
flex-grow: 1;
align-content: center;
text-align: left;
padding-left: 0.5em;
}
textarea#editor {
width: 100%;
height: 100%;
align-self: stretch;
resize: none;
border: 0;
outline: none;
} }
menu {
list-style: none;
padding: 0;
}
li {
margin-top: 0.2rem;
}
a, a:visited { a, a:visited {
/*color: #0000ee;*/
color: crimson;
text-decoration: none; text-decoration: none;
color: var(--ribbon-color);
} }
label { #donate {
vertical-align: bottom; align-self: center;
/*padding: 3px;*/ align-content: center;
margin: 0 5px 0 5px;
} }
input {
vertical-align: text-bottom;
}
input[type="url"] {
border: 1px solid black;
/*border-width: 0 0 1px 0;
border-color: crimson;*/
flex-grow: 1;
/*padding: 0 3px 0 3px;*/
/*width: 100%;*/
}
input[type="radio"], label {
cursor: pointer;
}
#disabledRadio:checked + label {
color: red;
}
#ownInputs {
display: flex;
}
/*
input#ownRadio ~ div:has(> input#customPacUrl):after {
border: 5px solid lime;
background-color: navy;
content: "";
}
input#ownRadio ~ div:after:not(:empty) {
border: 5px solid pink;
}
*/
.use-preferred-color-scheme { .use-preferred-color-scheme {
/*background-color: violet; background-color: #9e9eff;/* pink;*/
color: darkred;*/ color: purple;
/*color-scheme: light;*/ }
}/*
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.use-preferred-color-scheme { .use-preferred-color-scheme {
color-scheme: dark; color-scheme: dark;
background-color: green; }
color: #bfbfbf; .use-preferred-color-scheme:has(#ifToggle:checked) {
color-scheme: light;
} }
} }
@media (prefers-color-scheme: light) { @media (prefers-color-scheme: light) {
.use-preferred-color-scheme { .use-preferred-color-scheme {
color-scheme: light;
}
.use-preferred-color-scheme:has(#ifToggle:checked) {
color-scheme: dark; color-scheme: dark;
background-color: pink;
color: purple;
border: 5px solid red;
} }
} }
/*
img.gsbg {
z-index: -1;
position: relative;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
*/
/*
input:invalid {
border: 5px solid red;
}*/
#customPacUrl:disabled {
/*background-color: grey;*/
}
#ownInputs button {
font-family: emoji, monospace;
width: 2em;
width: 2ch;
border-width: 1px 1px 1px 0px;
}
#ownInputs > input {
border-radius: 0;
}
#ownInputs > div {
display: flex;
}
#customPacUrl:disabled ~ .editPanel {
display: none;
}
#customPacUrl:not(:disabled) ~ .unlockPanel {
display: none;
}
#boxes {
padding: 1em 0;
padding: 1ch 0;
}
#boxes > div {
/* Don't break sentences on spaces. */
white-space: nowrap;
}
</style> </style>
</head> </head>
<body class="use-preferred-color-scheme"> <body class="use-preferred-color-scheme">
<!--img src="./gsbg.png" class="gsbg"-->
<header>
PAC-скрипт:
</header>
<nav> <nav>
<form id="pacChooserForm"> <button id="readButton">READ</button>
<menu id="radios"> <button id="saveButton">SAVE</button>
<li> <button id="clearButton">CLEAR</button>
<pac-record label="Антизапрет" value="antizapret"> <output id="status">Press READ button to read PAC from settings</output>
<pac-url>https://e.cen.rodeo:18443/proxy.pac</pac-url>
<pac-url>https://e.cen.rodeo:8443/proxy.pac</pac-url>
<pac-url>https://e.cen.rodeo/proxy.pac</pac-url>
<pac-url>https://antizapret.prostovpn.org:18443/proxy.pac</pac-url>
<pac-url>https://antizapret.prostovpn.org:8443/proxy.pac</pac-url>
<pac-url>https://antizapret.prostovpn.org/proxy.pac</pac-url>
</pac-record>
</li>
<li>
<pac-record label="Антицензорити" value="anticensority">
<pac-url>https://anticensority.github.io/generated-pac-scripts/anticensority.pac</pac-url>
<pac-url>https://raw.githubusercontent.com/anticensority/generated-pac-scripts/master/anticensority.pac</pac-url>
</pac-record>
</li>
<li>
<pac-record label="Свои скрипты" value="own">
<textarea>
https://foo.bar:1221
https://example.com:1223
</textarea>
</pac-record>
</li>
<li>
<pac-record label="Отключить / Сброс" value="disabled" />
</li>
</menu>
<div id="boxes">
<div> <div>
<input type="checkbox" name="resetBox" id="resetBox" checked> <input type="checkbox" id="ifToggle" name="ifToggle" hidden/>
<label for="resetBox">Отключать прокси перед скачиванием</label> <button><label for="ifToggle">INVERT</label></button>
</div> </div>
<div> <div id="donate">
<input type="checkbox" name="updateBox" id="updateBox" checked> <a target="_blank" data-localize="__MSG_Donate__"
<label for="updateBox">Обновлять каждые 12ч</label>
</div>
</div>
</form>
</nav>
<footer style="text-align: center">
<a id="donate" target="_blank" data-localize="__MSG_Donate__"
href="https://github.com/anticensority/runet-censorship-bypass/wiki/Поддержать" href="https://github.com/anticensority/runet-censorship-bypass/wiki/Поддержать"
>Donate ❤</a> >Donate ❤</a>
</footer> </div>
<script src="./bottom.mjs" type="module"></script> </nav>
<textarea id="editor">
# Комментарии начинаются с решётки и действуют до конца строки.
# Для начала работы вам нужно добавить один или несколько
# URL-ссылок на PAC-скрипты, как показано на примере ниже
# (на example.com, только без решётки).
#
# Желательно не добавлять ссылки из различных
# проектов, иначе при каких-либо сбоях вам будет сложно
# определить причину и выявить ту команду поддержки, в которую
# следует обратиться.
#
# Где брать ссылки на PAC-скрипты?
# Из ваших доверенных источников.
# Я их здесь не публикую, т.к. это может стать лишним
# поводом для блокировки расширения в магазинах или
# репозиториях.
# https://example.com/foo/bar/proxy.pac
</textarea>
</body> </body>
</html> </html>