diff --git a/src/content/about.html b/src/content/about.html index d3894d5..f6459f5 100644 --- a/src/content/about.html +++ b/src/content/about.html @@ -18,8 +18,21 @@
8.9
+
Added "Log" to the toolbar popup buttons (#44)
+
Added limited log display on Chrome (experimental)
+
Added Theme feature (#71, #100)
+
Added toggle more options on toolbar popup (#54)
Fixed proxy DNS in "Import Proxy List" (#102) (from 8.7)
-
Fixed settings upgrade when username/password is missing (#103)
+
Fixed settings upgrade (import older) when hostname is missing (#108)
+
Fixed settings upgrade (import older) when username/password is missing (#103)
+
Increased log content
+
Removed "Show Pattern Proxy" option and made it default (#57) (from 8.7) (Firefox only)
+
Removed Tab Proxy page-action and set it to the toolbar icon (#114) (Firefox only)
+
Updated add pattern user interface (#105)
+
Updated code to process duplicate hostname:port (#33, #76)
+
Updated options to disable "Store Locally" on Firefox (Chrome only)
+
Updated popup include/exclude host feature
+
Updated popup user interface
8.8
Added Show hidden feature
@@ -31,7 +44,7 @@
Added Auto Backup feature
Added FoxyProxy Basic detection (disabled for now)
Added Help translation form
-
Added option to show proxies on the toolbar badge when in Proxy by Patterns mode (Firefox only) (#57)
+
Added "Show Pattern Proxy" option to show proxies when in "Proxy by Patterns" mode (#57) (Firefox only)
Added pattern matching to the Log display (#91)
Added proxy title to the toolbar icon mouse-over title display (#74)
Changed the global Proxy DNS to per-proxy setting (#75)
@@ -60,7 +73,7 @@
8.3
Added enterprise policy & managed storage feature (#42) (experimental)
-
Added locally stored PAC feature (#46) (experimental)
+
Added PAC "Store Locally" feature (#46) (experimental) (Chrome only)
Added PAC view feature
Fixed an issue with empty Global Exclude
Fixed an issue with upgrade sync data on Firefox (#53)
diff --git a/src/content/app.js b/src/content/app.js index f21aae7..bf48b5c 100644 --- a/src/content/app.js +++ b/src/content/app.js @@ -13,8 +13,8 @@ export const pref = { mode: 'disable', sync: false, autoBackup: false, - showPatternProxy: false, passthrough: '', + theme: '', container: {}, commands: {}, data: [] diff --git a/src/content/default.css b/src/content/default.css index d42ea56..9ec61c3 100644 --- a/src/content/default.css +++ b/src/content/default.css @@ -4,6 +4,7 @@ --bg: #fff; --alt-bg: #f5f5f5; --hover: #eaeaea; + --highlight: #f90; --body-bg: #630; --header: #c60; @@ -17,7 +18,7 @@ --link: #e70; --border: #ddd; - /* --shadow: #0004; */ + --shadow: #0004; --dim: #777; --tr: #f5f5f5; } @@ -30,7 +31,7 @@ --alt-bg: #666; --hover: #888; - --body-bg: #630; + /* --body-bg: #630; */ --header: #e70; --btn-bg: #f90; @@ -38,7 +39,7 @@ --link: #f90; --border: #777; - /* --shadow: #fff8; */ + --shadow: #fff8; --dim: #ccc; --tr: #531; } @@ -66,6 +67,11 @@ section { padding: 0; } +a { + color: var(--link); + text-decoration: none; +} + select, textarea, input[type="number"], @@ -79,9 +85,11 @@ input[type="url"] { border-radius: 0.3em; } -a { - color: var(--link); - text-decoration: none; +label[for], +input[type="checkbox"], +summary, +.pointer { + cursor: pointer; } ::placeholder { @@ -90,16 +98,35 @@ a { font-style: italic; } -button { - background: var(--btn-bg); +.invalid, +input:invalid { + box-shadow: 1px 1px 4px #f20, -1px -1px 4px #f20; +} + +/* ----- Buttons ----- */ +button, +label.flat { + background-color: var(--btn-bg); + border: none; color: inherit; cursor: pointer; text-align: center; - transition: 0.5s; + white-space: nowrap; } -button:hover { - background: var(--btn-hover); +button.flat, +label.flat { + display: inline-block; + font-size: 0.9em; + color: #fff; + border-radius: 5px; + padding: 0.4em 1em; + min-width: 8em; +} + +button:hover, +label.flat:hover { + background-color: var(--btn-hover); } button:disabled, @@ -108,7 +135,19 @@ select:disabled { opacity: 0.4; } -.invalid, -input:invalid { - box-shadow: 1px 1px 4px #f20, -1px -1px 4px #f20; -} \ No newline at end of file +button.plain { + background-color: unset; + padding: 0; + margin: 0; + min-width: 1em; +} + +button[type="submit"] { + display: table; + color: #fff; + font-size:0.9em; + border-radius: 5px; + padding: 0.5em 5em; + margin: 1em auto 0; +} +/* ----- /Buttons ----- */ diff --git a/src/content/help.html b/src/content/help.html index e9a00bd..0dea1d4 100644 --- a/src/content/help.html +++ b/src/content/help.html @@ -407,12 +407,16 @@
Individual Proxy
Connections are passed through the selected proxy (or PAC)
+
▶ More
+
Show or hide additional options
+
User choice is temporarily stored in localStorage (not available in Private/Incognito)
+
Search Filter
Proxy Title, Hostname, & Port are searched and display filtered based on the input value (case-insensitive)
Use :port to filter by port
Quick Add
-
Select a proxy to add a pattern based on the current page URL
+
Add current page's host pattern to the selected proxy's include
Only top 10 active proxies are listed
Exclude Host
@@ -494,6 +498,7 @@

There is a discussion to increase the storage sync quota to 1MB.

See also:

@@ -508,8 +513,8 @@

Toggle changes the browser preferences and does not require SAVE.

-

Show Pattern Proxy (Firefox only)

-

Show per-tab proxies on the toolbar badge when in Proxy by Patterns mode

+

Incognito/Container

Set a proxy for Incognito (Private Mode) and/or Containers

@@ -778,7 +783,7 @@
Option to pass DNS to the SOCKS proxy when using SOCKS
See also: -
Default Options (v8.7)
+
Default Options (v8.9)
 {
   "mode": "disable",          // mandatory: current option, necessary to enable
   "sync": false,              // optional:  not necessary as it will be disabled on managed storage
   "autoBackup": false,        // optional:  not necessary as save is disabled on managed storage
-  "showPatternProxy": false,  // optional:  Show proxies on the toolbar badge when in Proxy by Patterns mode (Firefox only)
   "passthrough": "",          // optional:  Global Exclude
+  "theme": "",                // optional:  set the theme
   "container": {},            // optional:  Incognito/Container settings
   "commands": {},             // optional:  keyboard shortcut settings
   "data": []                  // mandatory: array of proxies and their patterns
diff --git a/src/content/i18n.js b/src/content/i18n.js
index 6e3fbcd..8b193bb 100644
--- a/src/content/i18n.js
+++ b/src/content/i18n.js
@@ -5,7 +5,7 @@ class I18n {
   static {
     document.querySelectorAll('template').forEach(i => this.set(i.content));
     this.set();
-    document.body.style.opacity = 1;                        // show after i18n
+    // document.body.style.opacity = 1;                        // show after i18n
   }
 
   static set(target = document) {
diff --git a/src/content/iframe.css b/src/content/iframe.css
index f3fbf27..9ae8ec5 100644
--- a/src/content/iframe.css
+++ b/src/content/iframe.css
@@ -1,4 +1,5 @@
 @import 'default.css';
+@import 'theme.css';
 
 /* ----- General ----- */
 :root {
@@ -27,7 +28,7 @@ article {
 /* ----- h1-h5 ----- */
 h2 {
   color: var(--header);
-  font-size: 2.2em;
+  font-size: 2.5em;
   border-bottom: 1px solid var(--border);
   font-weight: normal;
 }
@@ -37,7 +38,6 @@ h2:first-of-type {
 }
 
 h3 {
-  color: var(--nav-hover);
   font-size: 1.5em;
   font-weight: normal;
 }
@@ -169,7 +169,7 @@ mark {
 /* ----- About ----- */
 .about dt {
   background-color: unset;
-  color: var(--nav-hover);
+  color: var(--h3);
   font-weight: bold;
   margin-bottom: 0.2em;
   font-size: 1.1em;
@@ -201,11 +201,11 @@ nav {
 nav a {
   color: var(--color);
   padding: 0.5em 1em;
-  transition: 0.5s;
+  /* transition: 0.5s; */
 }
 
 nav a:hover {
-  color: var(--btn-bg);
+  background-color: var(--hover);
 }
 
 /* ----- /Navigation ----- */
diff --git a/src/content/log.js b/src/content/log.js
index e3b566c..0afdf2e 100644
--- a/src/content/log.js
+++ b/src/content/log.js
@@ -7,28 +7,22 @@ export class Log {
   static {
     this.trTemplate = document.querySelector('.log template').content.firstElementChild;
     this.tbody = document.querySelector('.log tbody');
-
-    // no proxy info on chrome
-    if (App.firefox) {
-      this.tbody.textContent = '';                          // remove "not available" notice
-      browser.webRequest.onBeforeRequest.addListener(e => this.process(e), {urls: ['*://*/*']});
-    }
-
     this.proxyCache = {};                                   // used to find proxy
     this.mode = 'disable';
+    browser.webRequest.onBeforeRequest.addListener(e => this.process(e), {urls: ['*://*/*']});
   }
 
   static process(e) {
     const tr = this.tbody.children[199] || this.trTemplate.cloneNode(true);
-    const [, time, container, method, doc, url, title, type, host, port, pattern] = tr.children;
+    const [, time, container, method, reqType, doc, url, title, type, host, port, pattern] = tr.children;
 
     time.textContent = this.formatInt(e.timeStamp);
     method.textContent = e.method;
-    doc.textContent = e.documentUrl || '';                  // For a top-level document, documentUrl is undefined
-    doc.title = e.documentUrl || '';
-    url.textContent = decodeURIComponent(e.url);
-    url.title = e.url;
-    container.classList.toggle('incognito', e.incognito);
+    reqType.textContent = e.type;
+    // For a top-level document, documentUrl is undefined, chrome uses e.initiator
+    this.prepareOverflow(doc, e.documentUrl || e.initiator || '');
+    this.prepareOverflow(url, decodeURIComponent(e.url));
+    container.classList.toggle('incognito', !!e.incognito);
     container.textContent = e.cookieStoreId?.startsWith('firefox-container-') ? 'C' + e.cookieStoreId.substring(18) : '';
 
     const info = e.proxyInfo || {host: '', port: '', type: ''};
@@ -43,8 +37,7 @@ export class Log {
     // show matching pattern in pattern mode
     const pat = this.mode === 'pattern' && item?.include.find(i => new RegExp(Pattern.get(i.pattern, i.type), 'i').test(e.url));
     const text = pat?.title || pat?.pattern || '';
-    pattern.textContent = text;
-    pattern.title = text;
+    this.prepareOverflow(pattern, text);
 
     this.tbody.prepend(tr);                                 // in reverse order, new on top
   }
@@ -53,4 +46,10 @@ export class Log {
     return new Intl.DateTimeFormat(navigator.language,
             {hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false}).format(new Date(d));
   }
+
+  // set title, in case text overflows
+  static prepareOverflow(elem, value) {
+    elem.textContent = value;
+    elem.title = value;
+  }
 }
\ No newline at end of file
diff --git a/src/content/migrate.js b/src/content/migrate.js
index d6bef82..3586849 100644
--- a/src/content/migrate.js
+++ b/src/content/migrate.js
@@ -40,30 +40,19 @@ import {CryptoJS} from '../lib/aes.3.1.2.js';
 export class Migrate {
 
   static async init(pref) {
-    // --- 8.8
-    // tidy up left-over obj from 8.0 Sync typo mistake
-    if (Object.hasOwn(pref, 'obj')) {
-      delete pref.obj;
-      await browser.storage.local.remove('obj');
-      await browser.storage.sync.remove('obj');
-    }
-
-    // --- 8.7
-    // change global proxyDNS to per-proxy
+    // --- 8.9
+    // 8.9 remove showPatternProxy (from 8.7)
+    // 8.8 tidy up left-over obj Sync typo mistake (from 8.0)
+    // 8.7 change global proxyDNS to per-proxy (from 8.0)
     if (Object.hasOwn(pref, 'proxyDNS') && pref.data) {
       pref.data.forEach(i => i.proxyDNS = !!pref.proxyDNS);
-      delete pref.proxyDNS;
-      await browser.storage.local.remove('proxyDNS');
       await browser.storage.local.set(pref);
     }
-
-    // --- 8.1
-    if (Object.hasOwn(pref, 'globalExcludeWildcard')) {
-      delete pref.globalExcludeWildcard;                    // from 8.0, removed in 8.1
-      delete pref.globalExcludeRegex;                       // from 8.0, removed in 8.1
-      delete pref.obj;                                      // 8.0 Sync typo mistake
-      await browser.storage.local.remove(['globalExcludeWildcard', 'globalExcludeRegex', 'obj']);
-    }
+    // 8.1 remove globalExcludeWildcard, globalExcludeRegex (from 8.0)
+    const keys = ['showPatternProxy', 'obj', 'proxyDNS', 'globalExcludeWildcard', 'globalExcludeRegex'];
+    keys.forEach(i => delete pref[i]);
+    await browser.storage.local.remove(keys);
+    await browser.storage.sync.remove(keys);
 
     // --- 8.0
     if (pref.data) { return; }
@@ -185,7 +174,9 @@ export class Migrate {
     const db = App.getDefaultPref();
     db.sync = !!pref.sync;
 
-    const data = Object.values(pref).filter(i => i && Object.hasOwn(i, 'address')); // null value causes an error
+    // null value causes an error in hasOwn, direct proxies don't have 'address'
+    const data = Object.values(pref).filter(i => i && ['address', 'type'].some(p => Object.hasOwn(i, p)));
+
     data.sort((a, b) => a.index - b.index);                 // sort by index
 
     data.forEach(item => {
@@ -193,8 +184,8 @@ export class Migrate {
         active: item.active === 'true' || item.active === true, // convert to boolean, some old databases have mixed types
         title: item.title || '',
         type: typeSet[item.type],                           // convert to actual type: http | https | socks4 | socks5 | direct | + add PAC
-        hostname: item.address,                             // rename to hostname
-        port: item.port,
+        hostname: item.address || '',                       // rename to hostname
+        port: item.port || '',
         username: item.username || '',
         password: item.password || '',
         cc: item.cc || '',                                  // remove country, use CC in country-code.js
@@ -208,9 +199,7 @@ export class Migrate {
       };
 
       pxy.cc === 'UK' && (pxy.cc = 'GB');                   // convert UK to ISO 3166-1 GB
-
-      // --- type 'direct'
-      pxy.type === 'direct' && (pxy.hostname = 'DIRECT');
+      pxy.type === 'direct' && (pxy.hostname = 'DIRECT');   // type 'direct'
 
 /*
       {
diff --git a/src/content/on-request.js b/src/content/on-request.js
index 0c6c24a..2c5922f 100644
--- a/src/content/on-request.js
+++ b/src/content/on-request.js
@@ -7,7 +7,6 @@
 // Fixed in Firefox 119
 
 import {Pattern} from './pattern.js';
-import {PageAction} from './page-action.js';
 import {Location} from './location.js';
 
 // ---------- Firefox Proxy Process ------------------------
@@ -16,11 +15,10 @@ export class OnRequest {
   static {
     // --- default values
     this.mode = 'disable';
-    this.proxy = null;                                      // used for Single Proxy
+    this.proxy = {};                                        // used for Single Proxy
     this.data = [];                                         // used for Proxy by Pattern
     this.passthrough = [];                                  // RegExp string
     this.net = [];                                          // [start, end] strings
-    this.showPatternProxy = false;
     this.tabProxy = {};                                     // tab proxy, may be lost in MV3 if bg is unloaded
     this.container = {};                                    // incognito/container proxy
 
@@ -40,12 +38,12 @@ export class OnRequest {
     const [passthrough, , net] = Pattern.getPassthrough(pref.passthrough);
     this.passthrough = passthrough;
     this.net = net;
-    this.showPatternProxy = pref.showPatternProxy;
 
-    const data = pref.data.filter(i => i.active && i.type !== 'pac' && i.hostname); // filter data
+    // filter data
+    const data = pref.data.filter(i => i.active && i.type !== 'pac' && i.hostname);
 
-    // --- single proxy
-    /:\d+$/.test(pref.mode) && (this.proxy = data.find(i => pref.mode === `${i.hostname}:${i.port}`));
+    // --- single proxy (false|undefined|proxy object)
+    this.proxy = /:\d+[^/]*$/.test(pref.mode) && data.find(i => pref.mode === `${i.hostname}:${i.port}`);
 
     // --- proxy by pattern
     this.data = data.filter(i => i.include[0] || i.exclude[0]).map(item => {
@@ -75,34 +73,37 @@ export class OnRequest {
   }
 
   static process(e) {
+    const tabId = e.tabId;
     switch (true) {
       // --- check local & global passthrough
       case this.bypass(e.url):
+        this.setAction(null, tabId);
         return {type: 'direct'};
 
       // --- tab proxy
-      case e.tabId !== -1 && !!this.tabProxy[e.tabId]:
-        return this.processProxy(this.tabProxy[e.tabId]);
+      case tabId !== -1 && !!this.tabProxy[tabId]:
+        return this.processProxy(this.tabProxy[tabId], tabId);
 
       // --- incognito proxy
-      case e.tabId !== -1 && e.incognito && !!this.container.incognito:
-        return this.processProxy(this.container.incognito);
+      case tabId !== -1 && e.incognito && !!this.container.incognito:
+        return this.processProxy(this.container.incognito, tabId);
 
       // --- container proxy
-      case e.tabId !== -1 && e.cookieStoreId && !!this.container[e.cookieStoreId]:
-        return this.processProxy(this.container[e.cookieStoreId]);
+      case tabId !== -1 && e.cookieStoreId && !!this.container[e.cookieStoreId]:
+        return this.processProxy(this.container[e.cookieStoreId], tabId);
 
       // --- standard operation
       case this.mode === 'disable':                         // pass direct
       case this.mode === 'direct':                          // pass direct
       case this.mode.includes('://') && !/:\d+$/.test(this.mode): // PAC URL is set
+        this.setAction(null, tabId);
         return {type: 'direct'};
 
       case this.mode === 'pattern':                         // check if url matches patterns
-        return this.processPattern(e.url, e.tabId);
+        return this.processPattern(e.url, tabId);
 
       default:                                              // get the proxy for all
-        return this.processProxy(this.proxy);
+        return this.processProxy(this.proxy, tabId);
     }
   }
 
@@ -111,31 +112,19 @@ export class OnRequest {
 
     for (const proxy of this.data) {
       if (!match(proxy.exclude) && match(proxy.include)) {
-        this.processShowPatternProxy(proxy, tabId);
-        return this.processProxy(proxy);
+        // this.processShowPatternProxy(proxy, tabId);
+        return this.processProxy(proxy, tabId);
       }
     }
 
+    this.setAction(null, tabId);
     return {type: 'direct'};                                // no match
   }
 
-  static processShowPatternProxy(item, tabId) {
-    // Set to -1 if the request isn't related to a tab
-    if (tabId === -1 || !this.showPatternProxy) { return; }
-
-    const host = [item.hostname, item.port].filter(Boolean).join(':');
-    const title = [item.title, host, item.city, ...Location.get(item.cc)].filter(Boolean).join('\n');
-    const text = item.title || item.hostname;
-    const color = item.color;
-
-    browser.action.setBadgeBackgroundColor({color, tabId});
-    browser.action.setTitle({title, tabId});
-    browser.action.setBadgeText({text, tabId});
-  }
-
-  static processProxy(proxy) {
-    const {type, hostname: host, port, username, password, proxyDNS} = proxy;
-    if (type === 'direct') { return {type: 'direct'}; }
+  static processProxy(proxy, tabId) {
+    this.setAction(proxy, tabId);
+    const {type, hostname: host, port, username, password, proxyDNS} = proxy || {};
+    if (!type || type === 'direct') { return {type: 'direct'}; }
 
     // https://searchfox.org/mozilla-central/source/toolkit/components/extensions/ProxyChannelFilter.sys.mjs#102
     // Although API converts to number -> let port = Number.parseInt(proxyData.port, 10);
@@ -163,6 +152,28 @@ export class OnRequest {
     return response;
   }
 
+  static setAction(item, tabId) {
+    // Set to -1 if the request isn't related to a tab
+    if (tabId === -1) { return; }
+
+    // --- reset values
+    let title = null;
+    let text = null;
+    let color = null;
+
+    // --- set proxy details
+    if (item) {
+      const host = [item.hostname, item.port].filter(Boolean).join(':');
+      title = [item.title, host, item.city, ...Location.get(item.cc)].filter(Boolean).join('\n');
+      text = item.title || item.hostname;
+      color = item.color;
+    }
+
+    browser.action.setBadgeBackgroundColor({color, tabId});
+    browser.action.setTitle({title, tabId});
+    browser.action.setBadgeText({text, tabId});
+  }
+
   // ---------- passthrough --------------------------------
   static bypass(url) {
     switch (true) {
@@ -223,13 +234,21 @@ export class OnRequest {
     }
 
     this.tabProxy[tab.id] = pxy;
-    PageAction.set(tab.id, pxy);
+    // PageAction.set(tab.id, pxy);
   }
 
   static async unsetTabProxy() {
     const [tab] = await browser.tabs.query({currentWindow: true, active: true});
     delete this.tabProxy[tab.id];
-    PageAction.unset(tab.id);
+    // PageAction.unset(tab.id);
+  }
+
+  // ---------- Update Page Action -------------------------
+  static onUpdated(tabId, changeInfo, tab) {
+    if (changeInfo.status !== 'complete') { return; }
+
+    const pxy = this.tabProxy[tabId];
+    pxy ? this.setAction(pxy, tabId) : this.checkPageAction(tab);
   }
 
   // ---------- Incognito/Container ------------------------
@@ -237,14 +256,6 @@ export class OnRequest {
     if (tab.id === -1 || this.tabProxy[tab.id]) { return; } // not if tab proxy is set
 
     const pxy = tab.incognito ? this.container.incognito : this.container[tab.cookieStoreId];
-    pxy && PageAction.set(tab.id, pxy);
-  }
-
-  // ---------- Update Page Action -------------------------
-  static onUpdated(tabId, changeInfo, tab) {
-    if (changeInfo.status !== 'complete') { return; }
-
-    const pxy = this.tabProxy[tab.id];
-    pxy ? PageAction.set(tab.id, pxy) : this.checkPageAction(tab);
+    pxy && this.setAction(pxy, tab.id);
   }
 }
\ No newline at end of file
diff --git a/src/content/options.css b/src/content/options.css
index 17d1d94..94a5da3 100644
--- a/src/content/options.css
+++ b/src/content/options.css
@@ -1,6 +1,7 @@
 @import 'default.css';
 @import 'progress-bar.css';
 @import 'spinner.css';
+@import 'theme.css';
 
 /* ----- General ----- */
 :root {
@@ -58,13 +59,6 @@ iframe {
   vertical-align: text-bottom;
 } */
 
-label[for],
-input[type="checkbox"],
-summary,
-.pointer {
-  cursor: pointer;
-}
-
 label > input[type="checkbox"] {
   margin-right: 0.5em;
 }
@@ -80,29 +74,17 @@ textarea {
   resize: vertical;
 }
 
-.flat {
-  background: var(--btn-bg);
+/* .flat {
   color: #fff;
-  cursor: pointer;
-  text-align: center;
-  transition: 0.5s;
   border-radius: 5px;
   padding: 0.4em 1em;
-  border: 0;
-  font-weight: bold;
   font-size: 0.9em;
   min-width: 8em;
   display: inline-block;
-  white-space: nowrap;
-}
-
-.flat:hover {
-  background: var(--btn-hover);
-  /* box-shadow: 0px 1px 5px var(--shadow); */
-}
+} */
 
 fieldset {
-  background: var(--bg);
+  background-color: var(--bg);
   border-radius: 0.5em;
   border: 0;
   padding: 1.5em;
@@ -154,7 +136,7 @@ input[type="checkbox"].control {
 }
 
 div.nav {
-  background: var(--nav-bg);
+  background-color: var(--nav-bg);
 }
 
 nav {
@@ -175,12 +157,12 @@ nav img {
 
 nav > label {
   padding: 0.5em 1em;
-  transition: 0.5s;
+  /* transition: 0.5s; */
   border-radius: 0.5em 0.5em 0 0;
 }
 
 nav > label:hover {
-  background: var(--nav-hover);
+  background-color: var(--nav-hover);
 }
 
 /* nav > label img {
@@ -231,29 +213,19 @@ input[type="file"] {
 /* ----- /Import/Export ----- */
 
 /* ----- Submit Button ----- */
-button[type="submit"] {
+/* button[type="submit"] {
   display: table;
   color: #fff;
-  background: var(--btn-bg);
-  font-size: 0.9em;
-  font-weight: bold;
+  background-color: var(--btn-bg);
+  font-size:0.9em;
   margin: 1em auto 0;
-  padding: 0.5em 3em;
-  text-shadow: 0 -1px 1px #333;
-  border: 0;
+  padding: 0.5em 5em;
   border-radius: 5px;
-  box-shadow: 0 5px var(--body-bg);
 }
 
 button[type="submit"]:hover {
   background-color: var(--btn-hover);
-}
-
-button[type="submit"]:active {
-  /* background-color: #3e8e41; */
-  box-shadow: 0 1px var(--body-bg);
-  transform: translateY(4px);
-}
+} */
 /* ----- /Submit Button ----- */
 
 /* ----- Toggle Switch ----- */
@@ -274,7 +246,7 @@ button[type="submit"]:active {
   z-index: 2;
   width: 14px;
   height: 14px;
-  background: #fff;
+  background-color: #fff;
   left: 1px;
   top: 1px;
   border-radius: 50%;
@@ -292,14 +264,6 @@ button[type="submit"]:active {
 /* ----- /Toggle Switch ----- */
 
 /* ----- Button ----- */
-button.plain {
-  background-color: transparent;
-  padding: 0;
-  margin: 0;
-  min-width: 1em;
-  border: none;
-}
-
 button.bin,
 button.test,
 button.close {
@@ -344,10 +308,15 @@ textarea {
   font-size: 1em;
 } */
 
+section.options fieldset * {
+  transition: opacity 0.5s;
+}
+
 input[type="color"] {
   border: 0;
 }
 
+.options div.theme,
 .options div.container,
 .options div.commands {
   display: grid;
@@ -357,6 +326,10 @@ input[type="color"] {
   margin-bottom: 1em;
 }
 
+.options div.theme {
+  margin-left: unset;
+}
+
 .options div.buttons {
   display: grid;
   grid-auto-flow: column;
@@ -369,7 +342,7 @@ input[type="color"] {
 /* .options div.buttons {
   padding: 0 1.5em;
   margin: 0 -1.5em 1em;
-  background: var(--btn-bg);
+  background-color: var(--btn-bg);
 }
 
 .options div.buttons > * {
@@ -520,7 +493,7 @@ details.proxy > summary span:nth-of-type(2):empty::before {
 .pattern-box button.bin {
   padding: 0;
   background-color: transparent;
-  border: none;
+  /* border: none; */
   font-size: 1em;
   transition: 0.5s;
   color: #ccc;
@@ -529,74 +502,6 @@ details.proxy > summary span:nth-of-type(2):empty::before {
 }
 /* ----- /Pattern ----- */
 
-/* ----- show/hide elements ----- */
-details.proxy[data-type="direct"] :is(
-  [data-i18n="port"], [data-id="port"],
-  [data-i18n="username"], [data-id="username"],
-  [data-i18n="password"], .password,
-  [data-i18n="country"], [data-id="cc"],
-  [data-i18n="city"], [data-id="city"],
-  [data-type="pac"], .pac) {
-  opacity: 0.3;
-  pointer-events: none;
-  user-select: none;
-}
-
-details.proxy[data-type="pac"] :is(
-  [data-i18n="port"], [data-id="port"],
-  [data-i18n="username"], [data-id="username"],
-  [data-i18n="password"], .password) {
-  opacity: 0.3;
-  pointer-events: none;
-  user-select: none;
-}
-
-details.proxy[data-type="pac"] :is(.pattern-head, .pattern-box) {
-  display: none;
-}
-
-details.proxy:not([data-type="pac"]) :is([data-type="pac"], .pac) {
-  opacity: 0.3;
-  pointer-events: none;
-  user-select: none;
-}
-
-details.proxy[data-type="socks4"] :is(
-  [data-i18n="username"], [data-id="username"],
-  [data-i18n="password"], .password) {
-  opacity: 0.3;
-  pointer-events: none;
-  user-select: none;
-}
-
-details.proxy:not([data-type="socks5"]) :is([data-i18n="proxyDNS"], [data-id="proxyDNS"]) {
-  opacity: 0.3;
-  pointer-events: none;
-  user-select: none;
-}
-
-/* --- Chrome --- */
-.chrome .firefox {
-  opacity: 0.3;
-  pointer-events: none;
-  user-select: none;
-}
-
-.chrome details.proxy[data-type="socks5"] :is(
-  [data-i18n="username"], [data-id="username"],
-  [data-i18n="password"], .password) {
-  opacity: 0.3;
-  pointer-events: none;
-  user-select: none;
-}
-
-/* --- Basic --- */
-.basic :is(.pattern-head, .pattern-box) {
-  display: none;
-}
-
-/* ----- /show/hide elements ----- */
-
 /* ----- Popup ----- */
 div.popup {
   display: none;
@@ -718,7 +623,7 @@ section.log {
 
 .log thead th {
   color: #fff;
-  background: #999;
+  background-color: #999;
   padding: 0.5em 0.2em;
   font-size: 0.9em;
   font-weight: normal;
@@ -730,13 +635,13 @@ section.log {
   vertical-align: unset;
 }
 
-.log tbody tr {
-  /* border-bottom: 1px solid var(--border); */
+/* .log tbody tr {
+  /* border-bottom: 1px solid var(--border); * /
   animation: sect 0.5s ease-in-out;
-}
+} */
 
 .log tr:hover {
-  background: var(--hover) !important;
+  background-color: var(--hover) !important;
 }
 
 .log tbody tr:nth-of-type(even) {
@@ -748,28 +653,27 @@ section.log {
   padding: 0.2em;
 }
 
-.log td:nth-of-type(5),
 .log td:nth-of-type(6),
-.log td:nth-of-type(11) {
+.log td:nth-of-type(7),
+.log td:nth-of-type(12) {
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
   max-width: 20em;
 }
 
-.log td:nth-of-type(6) {
+.log td:nth-of-type(7) {
   max-width: 40em;
 }
 
-.log td:nth-of-type(7) {
+.log td:nth-of-type(8) {
   border-left: 2px solid var(--border);
 }
 
-.log td:nth-of-type(11) {
+.log td:nth-of-type(12) {
   max-width: 5em;
 }
 
-
 .log td.incognito::before {
   content: '';
   width: 1em;
@@ -795,17 +699,87 @@ section.log {
   content: counter(n);
   font-size: 0.8em;
 }
+/* ----- /Log ----- */
 
-/* Not Available On Chrome (Firefox Only)" */
-.log td.unavailable {
-  height: 50vh;
-  text-align: center;
-  vertical-align: middle;
-  color: var(--btn-bg);
-  font-size: 3em;
+/* ----- show/hide elements ----- */
+details.proxy[data-type="direct"] :is(
+  [data-i18n="port"], [data-id="port"],
+  [data-i18n="username"], [data-id="username"],
+  [data-i18n="password"], .password,
+  [data-i18n="country"], [data-id="cc"],
+  [data-i18n="city"], [data-id="city"],
+  [data-type="pac"], .pac) {
+  opacity: 0.3;
+  pointer-events: none;
+  user-select: none;
 }
 
-.log td.unavailable::before {
-  content: none !important;
+details.proxy[data-type="pac"] :is(
+  [data-i18n="port"], [data-id="port"],
+  [data-i18n="username"], [data-id="username"],
+  [data-i18n="password"], .password) {
+  opacity: 0.3;
+  pointer-events: none;
+  user-select: none;
 }
-/* ----- /Log ----- */
\ No newline at end of file
+
+details.proxy[data-type="pac"] :is(.pattern-head, .pattern-box) {
+  display: none;
+}
+
+details.proxy:not([data-type="pac"]) :is([data-type="pac"], .pac) {
+  opacity: 0.3;
+  pointer-events: none;
+  user-select: none;
+}
+
+details.proxy[data-type="socks4"] :is(
+  [data-i18n="username"], [data-id="username"],
+  [data-i18n="password"], .password) {
+  opacity: 0.3;
+  pointer-events: none;
+  user-select: none;
+}
+
+details.proxy:not([data-type="socks5"]) :is([data-i18n="proxyDNS"], [data-id="proxyDNS"]) {
+  opacity: 0.3;
+  pointer-events: none;
+  user-select: none;
+}
+
+/* --- Chrome --- */
+body.chrome .firefox {
+  opacity: 0.3;
+  pointer-events: none;
+  user-select: none;
+}
+
+body.chrome details.proxy[data-type="socks5"] :is(
+  [data-i18n="username"], [data-id="username"],
+  [data-i18n="password"], .password) {
+  opacity: 0.3;
+  pointer-events: none;
+  user-select: none;
+}
+
+body:not(.chrome) [data-i18n="storeLocally"] {
+  opacity: 0.3;
+  pointer-events: none;
+  user-select: none;
+}
+
+caption.firefox {
+  color: var(--nav-color);
+  visibility: hidden;
+}
+
+body.chrome caption.firefox {
+  visibility: visible;
+  opacity: 1;
+}
+
+/* --- Basic --- */
+.basic :is(.pattern-head, .pattern-box) {
+  display: none;
+}
+/* ----- /show/hide elements ----- */
\ No newline at end of file
diff --git a/src/content/options.html b/src/content/options.html
index 4f6ba62..cd0ec6d 100644
--- a/src/content/options.html
+++ b/src/content/options.html
@@ -70,8 +70,17 @@
         
         

- -

+ + +
+ + +

@@ -602,12 +611,14 @@
+ + @@ -618,9 +629,6 @@ - - -
@@ -638,6 +646,7 @@ +
diff --git a/src/content/options.js b/src/content/options.js index 55cbab5..c5cbeb3 100644 --- a/src/content/options.js +++ b/src/content/options.js @@ -61,6 +61,22 @@ class Toggle { } // ---------- /Toggle -------------------------------------- +// ---------- Theme ---------------------------------------- +// eslint-disable-next-line no-unused-vars +class Theme { + static { + this.elem = [document, ...[...document.querySelectorAll('iframe')].map(i => i.contentDocument)]; + pref.theme && this.set(pref.theme); + document.body.style.opacity = 1; // show after + document.getElementById('theme').addEventListener('change', e => this.set(e.target.value)); + } + + static set(value) { + this.elem.forEach(i => i.documentElement.className = value); + } +} +// ---------- /Theme --------------------------------------- + // ---------- Options -------------------------------------- class Options { @@ -77,7 +93,7 @@ class Options { // --- buttons document.querySelector('.options button[data-i18n="restoreDefaults"]').addEventListener('click', () => this.restoreDefaults()); - this.init(['sync', 'autoBackup', 'showPatternProxy', 'passthrough']); + this.init(['sync', 'autoBackup', 'theme', 'showPatternProxy', 'passthrough']); } static init(keys = Object.keys(pref)) { @@ -581,7 +597,11 @@ class Proxies { }); // patterns - pxy.querySelector('button[data-i18n="add|title"]').addEventListener('click', () => this.addPattern(patternBox)); + pxy.querySelector('button[data-i18n="add|title"]').addEventListener('click', () => { + this.addPattern(patternBox); + patternBox.lastElementChild.scrollIntoView(false); + patternBox.lastElementChild.children[4].focus(); + }); pxy.querySelector('input[type="file"]').addEventListener('change', e => this.importPattern(e, patternBox)); pxy.querySelector('button[data-i18n^="export"]').addEventListener('click', () => this.exportPattern(patternBox, title.value.trim() || hostname.value.trim())); diff --git a/src/content/popup.css b/src/content/popup.css index 3142dac..40bde39 100644 --- a/src/content/popup.css +++ b/src/content/popup.css @@ -1,10 +1,16 @@ @import 'default.css'; +@import 'theme.css'; /* ----- Light Theme ----- */ :root { --filter: opacity(0.4) grayscale(1); } +/* for the default theme */ +:root:not([class]) { + --nav-bg: #630; +} + /* ----- Dark Theme ----- */ @media screen and (prefers-color-scheme: dark) { :root { @@ -23,7 +29,7 @@ body { h1 { color: var(--nav-color); - background: var(--body-bg); + background-color: var(--nav-bg); margin: 0; padding: 0.5em; } @@ -34,7 +40,7 @@ h1 img { } /* ----- Buttons ----- */ -div.buttons { +div.popup-buttons { display: grid; grid-auto-flow: column; column-gap: 0.1em; @@ -42,13 +48,13 @@ div.buttons { button { color: #fff; - border: 0; + border: none; padding: 0.8em; - font-weight: bold; + /* font-weight: bold; */ } button:hover { - background: var(--btn-hover); + background-color: var(--btn-hover); } /* ----- /Buttons ----- */ @@ -125,13 +131,19 @@ input#filter { background: url('../image/filter.svg') no-repeat left 0.5em center / 1em; padding-left: 2em; margin-bottom: 0.2em; - grid-column: span 2; + /* grid-column: span 2; */ } div.list label.off { display: none; } +summary { + background-color: var(--alt-bg); + padding: 0.2em 0.5em; + margin-bottom: 0.1em; +} + div.host { display: grid; grid-template-columns: 1fr 1fr; @@ -141,24 +153,23 @@ div.host { } div.host button { - padding: 0.2em; - background: unset; + background-color: unset; + border-radius: 5px; + border: 1px solid var(--border); color: var(--color); font-weight: normal; - border: 1px solid var(--border); - border-radius: 5px; + padding: 0.2em; } div.host button:hover { - background: var(--hover); + background-color: var(--hover); } -div.host select { +/* div.host select { grid-column: span 2; -} +} */ /* ----- show/hide elements ----- */ - /* --- Chrome --- */ .chrome .firefox { opacity: 0.3; @@ -177,5 +188,4 @@ div.host select { .basic .pattern { display: none; } - /* ----- /show/hide elements ----- */ \ No newline at end of file diff --git a/src/content/popup.html b/src/content/popup.html index 17b0067..12123f4 100644 --- a/src/content/popup.html +++ b/src/content/popup.html @@ -26,26 +26,30 @@ -
- +
+ +
+ - + - - + + + + +
+
- - -
- -
+ +