From d581e67a5df2b12965e4ef1571ddd8e5617402b8 Mon Sep 17 00:00:00 2001
From: ACTCD <101378590+ACTCD@users.noreply.github.com>
Date: Mon, 11 Dec 2023 03:27:03 +0800
Subject: [PATCH 01/24] fix(style): cancel the use of global styles
---
src/ext/extension-page/Components/Editor/Editor.svelte | 1 +
.../extension-page/Components/Editor/EditorSearch.svelte | 8 ++------
2 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/src/ext/extension-page/Components/Editor/Editor.svelte b/src/ext/extension-page/Components/Editor/Editor.svelte
index 5d831d39..362d7ed9 100644
--- a/src/ext/extension-page/Components/Editor/Editor.svelte
+++ b/src/ext/extension-page/Components/Editor/Editor.svelte
@@ -307,6 +307,7 @@
.editor__header__buttons {
display: flex;
+ margin-right: 1rem;
}
:global(.editor__header__buttons > button:nth-of-type(2)) {
diff --git a/src/ext/extension-page/Components/Editor/EditorSearch.svelte b/src/ext/extension-page/Components/Editor/EditorSearch.svelte
index cc1f70db..ad132f0e 100644
--- a/src/ext/extension-page/Components/Editor/EditorSearch.svelte
+++ b/src/ext/extension-page/Components/Editor/EditorSearch.svelte
@@ -168,9 +168,9 @@
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
display: flex;
- padding: 0.25rem 0;
+ padding: 0.25rem;
position: absolute;
- right: 0.5rem;
+ right: 1.5rem;
top: 0;
z-index: 4;
}
@@ -197,10 +197,6 @@
flex-shrink: 0;
}
- :global(button:nth-of-type(3)) {
- margin-right: 0.25rem;
- }
-
:global(div.editor__search button svg) {
width: 45%;
}
From d9f52a714f1e319adf98b8c65f77d7feb79f1708 Mon Sep 17 00:00:00 2001
From: ACTCD <101378590+ACTCD@users.noreply.github.com>
Date: Fri, 15 Dec 2023 12:36:05 +0800
Subject: [PATCH 02/24] fix: avoid code scroll bouncing
---
.../Components/Editor/CodeMirror.svelte | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/src/ext/extension-page/Components/Editor/CodeMirror.svelte b/src/ext/extension-page/Components/Editor/CodeMirror.svelte
index e0c39081..f2d2d13b 100644
--- a/src/ext/extension-page/Components/Editor/CodeMirror.svelte
+++ b/src/ext/extension-page/Components/Editor/CodeMirror.svelte
@@ -406,10 +406,10 @@
{#if instance}
{/if}
+
+
From 63ccaa72cb97a47e1a4e19fa5383f642ee85a754 Mon Sep 17 00:00:00 2001
From: ACTCD <101378590+ACTCD@users.noreply.github.com>
Date: Fri, 15 Dec 2023 13:10:32 +0800
Subject: [PATCH 03/24] refactor: using css instead of window listener
---
src/ext/extension-page/App.svelte | 19 ++++++-------------
src/ext/extension-page/app.css | 1 +
2 files changed, 7 insertions(+), 13 deletions(-)
diff --git a/src/ext/extension-page/App.svelte b/src/ext/extension-page/App.svelte
index a457e035..d802643a 100644
--- a/src/ext/extension-page/App.svelte
+++ b/src/ext/extension-page/App.svelte
@@ -1,5 +1,5 @@
-
+
{#if $state.includes("init")}
diff --git a/src/ext/extension-page/app.css b/src/ext/extension-page/app.css
index 425bcaa4..f34a9b9a 100644
--- a/src/ext/extension-page/app.css
+++ b/src/ext/extension-page/app.css
@@ -1,6 +1,7 @@
html {
font-size: 100%;
height: 100vh;
+ height: 100svh; /* safari 15.4 */
overflow: hidden;
}
From d8f01d3c7390bfcf58a1f74406aae422774bdbdb Mon Sep 17 00:00:00 2001
From: ACTCD <101378590+ACTCD@users.noreply.github.com>
Date: Sat, 30 Dec 2023 18:51:55 +0800
Subject: [PATCH 04/24] fix: replace broken app launch process
It seems that due to macOS upgrades, the order in which the launch delegate is called has changed, now `application(_:open:)` is called after `applicationDidFinishLaunching(_:)` (their order itself is also unclear). Replaced with using custom event handler to ensure proper launch flow.
---
xcode/App-Mac/AppDelegate.swift | 28 +++++++++++++++++++++++-----
1 file changed, 23 insertions(+), 5 deletions(-)
diff --git a/xcode/App-Mac/AppDelegate.swift b/xcode/App-Mac/AppDelegate.swift
index ffdf0758..cb1a3837 100644
--- a/xcode/App-Mac/AppDelegate.swift
+++ b/xcode/App-Mac/AppDelegate.swift
@@ -6,16 +6,20 @@ class AppDelegate: NSObject, NSApplicationDelegate {
private var window: NSWindow!
private var windowForego = false
private var windowLoaded = false
+ private let logger = USLogger(#fileID)
@IBOutlet weak var enbaleNativeLogger: NSMenuItem!
- func application(_ application: NSApplication, open urls: [URL]) {
+ @objc func handleGetURL(event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor) {
// if open panel is already open, stop processing the URL scheme
if NSApplication.shared.keyWindow?.accessibilityIdentifier() == "open-panel" { return }
- for url in urls {
+ // handle URL scheme
+ if let urlString = event.paramDescriptor(forKeyword: AEKeyword(keyDirectObject))?.stringValue,
+ let url = URL(string: urlString) {
+ logger?.info("\(#function, privacy: .public) - \(urlString, privacy: .public)")
if url.host == "changesavelocation" {
// avoid opening the panel repeatedly and playing unnecessary warning sounds
- if NSApplication.shared.keyWindow?.identifier?.rawValue == "changeSaveLocation" { continue }
+ if NSApplication.shared.keyWindow?.identifier?.rawValue == "changeSaveLocation" { return }
if windowLoaded {
let viewController = window.contentViewController as? ViewController
viewController?.changeSaveLocation(nil)
@@ -27,9 +31,23 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
}
- func applicationDidFinishLaunching(_ aNotification: Notification) {
+ // https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428623-applicationwillfinishlaunching/
+ func applicationWillFinishLaunching(_ notification: Notification) {
+ // https://developer.apple.com/documentation/foundation/nsappleeventmanager/1416131-seteventhandler
+ NSAppleEventManager.shared().setEventHandler(
+ self,
+ andSelector: #selector(handleGetURL(event:replyEvent:)),
+ forEventClass: AEEventClass(kInternetEventClass),
+ andEventID: AEEventID(kAEGetURL)
+ )
+ }
+
+ // https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428385-applicationdidfinishlaunching
+ func applicationDidFinishLaunching(_ notification: Notification) {
// Initialize menu items
enbaleNativeLogger.state = Preferences.enableLogger ? .on : .off
+ // Whether to initialize the main view
+ logger?.debug("\(#function, privacy: .public) - windowForego: \(self.windowForego, privacy: .public)")
if windowForego { return }
let storyboard = NSStoryboard(name: "View", bundle: Bundle.main)
let windowController = storyboard.instantiateInitialController() as! NSWindowController
@@ -43,7 +61,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
return true
}
- func applicationWillTerminate(_ aNotification: Notification) {
+ func applicationWillTerminate(_ notification: Notification) {
// Insert code here to tear down your application
}
From 10abba429955d8594591d26c53841c025cec9be1 Mon Sep 17 00:00:00 2001
From: ACTCD <101378590+ACTCD@users.noreply.github.com>
Date: Sun, 7 Jan 2024 17:47:47 +0800
Subject: [PATCH 05/24] chore: increase the priority of prettier
---
.vscode/settings.json | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/.vscode/settings.json b/.vscode/settings.json
index f86b8368..24436b8d 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -5,6 +5,15 @@
},
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
+ "[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
+ "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
+ "[jsonc]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
+ "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
+ "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
+ "[css]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
+ "[html]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
+ "[svelte]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
+ "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
// https://github.com/microsoft/vscode-eslint#settings-options
"eslint.validate": ["javascript", "svelte"],
"eslint.experimental.useFlatConfig": true,
From 9dbf6ab51ee199319a47fa0d15013bec8b069942 Mon Sep 17 00:00:00 2001
From: ACTCD <101378590+ACTCD@users.noreply.github.com>
Date: Mon, 8 Jan 2024 18:19:29 +0800
Subject: [PATCH 06/24] refactor: adjust settings and better jsdoc
Revision settings define and remove language content. Improve JSDoc for better IntelliSense in vscode.
---
scripts/build-ext-safari-15.js | 5 +-
scripts/build-ext-safari-16.4.js | 5 +-
scripts/dev-ext-safari.js | 5 +-
src/ext/shared/native.js | 2 +-
src/ext/shared/settings.js | 647 +++++++++++++------------------
5 files changed, 284 insertions(+), 380 deletions(-)
diff --git a/scripts/build-ext-safari-15.js b/scripts/build-ext-safari-15.js
index 3242f700..50a5862e 100644
--- a/scripts/build-ext-safari-15.js
+++ b/scripts/build-ext-safari-15.js
@@ -31,8 +31,11 @@ const defineConfig = {
root: await rootDir(),
base: "./",
define: {
- "import.meta.env.BROWSER": JSON.stringify("safari"),
+ "import.meta.env.BROWSER": JSON.stringify("Safari"),
"import.meta.env.NATIVE_APP": JSON.stringify("app"),
+ "import.meta.env.SAFARI_PLATFORM": JSON.stringify(
+ process.env.SAFARI_PLATFORM,
+ ),
},
};
diff --git a/scripts/build-ext-safari-16.4.js b/scripts/build-ext-safari-16.4.js
index 12a67481..1c635833 100644
--- a/scripts/build-ext-safari-16.4.js
+++ b/scripts/build-ext-safari-16.4.js
@@ -30,8 +30,11 @@ const defineConfig = {
root: await rootDir(),
base: "./",
define: {
- "import.meta.env.BROWSER": JSON.stringify("safari"),
+ "import.meta.env.BROWSER": JSON.stringify("Safari"),
"import.meta.env.NATIVE_APP": JSON.stringify("app"),
+ "import.meta.env.SAFARI_PLATFORM": JSON.stringify(
+ process.env.SAFARI_PLATFORM,
+ ),
},
};
diff --git a/scripts/dev-ext-safari.js b/scripts/dev-ext-safari.js
index 2b4e7c25..ecd00126 100644
--- a/scripts/dev-ext-safari.js
+++ b/scripts/dev-ext-safari.js
@@ -31,8 +31,11 @@ const defineConfig = {
root: await rootDir(),
base: "./",
define: {
- "import.meta.env.BROWSER": JSON.stringify("safari"),
+ "import.meta.env.BROWSER": JSON.stringify("Safari"),
"import.meta.env.NATIVE_APP": JSON.stringify("app"),
+ "import.meta.env.SAFARI_PLATFORM": JSON.stringify(
+ process.env.SAFARI_PLATFORM,
+ ),
},
};
diff --git a/src/ext/shared/native.js b/src/ext/shared/native.js
index 524f3613..82566825 100644
--- a/src/ext/shared/native.js
+++ b/src/ext/shared/native.js
@@ -12,7 +12,7 @@ const application = () => import.meta.env.NATIVE_APP ?? "application";
/**
* @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/connectNative}
- * @returns {object} A `runtime.Port` object
+ * @returns {import("webextension-polyfill").Runtime.Port} A `runtime.Port` object
*/
export function connectNative() {
return browser.runtime.connectNative(application());
diff --git a/src/ext/shared/settings.js b/src/ext/shared/settings.js
index 92ed61bf..045cbc30 100644
--- a/src/ext/shared/settings.js
+++ b/src/ext/shared/settings.js
@@ -8,14 +8,15 @@ const storagePrefix = "US_";
/**
* Convert name to storage key
* @param {string} name
- * @returns {string} prefixed storage key
+ * @returns prefixed storage key
*/
const storageKey = (name) => storagePrefix + name.toUpperCase();
/**
- * Dynamic storage reference
- * @param {"sync"|"local"|undefined} area
- * @returns {Promise}
+ * @typedef {"sync"|"local"|"managed"|"session"} Areas
+ *
+ * @param {"sync"|"local"=} area - storage area
+ * @returns Dynamic storage reference
*/
const storageRef = async (area) => {
const storages = {
@@ -30,7 +31,7 @@ const storageRef = async (area) => {
};
// https://developer.apple.com/documentation/safariservices/safari_web_extensions/assessing_your_safari_web_extension_s_browser_compatibility#3584139
// since storage sync is not implemented in Safari, currently only returns using local storage
- if (import.meta.env.BROWSER === "safari") {
+ if (import.meta.env.BROWSER === "Safari") {
return storages.local;
}
if (area in storages) {
@@ -44,344 +45,236 @@ const storageRef = async (area) => {
}
};
+/**
+ * @typedef {Object} Platforms platform availability
+ * @property {any=} macos - overriding defaults
+ * @property {any=} ipados - overriding defaults
+ * @property {any=} ios - overriding defaults
+ *
+ * @typedef {Object} Setting The setting item
+ * @property {string} name - setting's name
+ * @property {"string"|"number"|"boolean"|"object"|"array"} type - setting's value type
+ * @property {boolean=} local - local settings will not be synced
+ * @property {Array=} values - setting's values list
+ * @property {any} default - setting's default value
+ * @property {boolean=} disable - disabled settings not be displayed
+ * @property {boolean=} protect - protected settings cannot be reset
+ * @property {boolean=} confirm - double confirmation is required when change
+ * @property {Platforms=} platforms - platform availability and overriding defaults
+ * @property {"INTERNAL"|"general"|"editor"} group - setting's group name
+ * @property {string=} legacy - setting's legacy name
+ * @property {"Toggle"|"select"|"textarea"=} nodeType - setting's node type
+ * @property {Object=} nodeClass - node class name with setting's value
+ *
+ * @typedef {Setting & {key: string}} SettingWithKey The setting item with storage key
+ */
+
+/** @type {Readonly
} - Read-only setting template and fallback defaults */
const settingDefault = deepFreeze({
name: "setting_default",
type: undefined,
local: false,
values: [],
default: undefined,
+ disable: false,
protect: false,
confirm: false,
- platforms: ["macos", "ipados", "ios"],
- langLabel: {},
- langTitle: {},
- group: "",
+ platforms: { macos: undefined, ipados: undefined, ios: undefined },
+ group: "INTERNAL",
legacy: "",
- nodeType: "",
+ nodeType: undefined,
nodeClass: {},
});
-export const settingsDefine = deepFreeze(
- [
- {
- name: "error_native",
- type: "object",
- local: true,
- default: { error: undefined },
- platforms: ["macos", "ipados", "ios"],
- group: "Internal",
- },
- {
- name: "legacy_imported",
- type: "number",
- local: true,
- default: 0,
- protect: true,
- platforms: ["macos"],
- group: "Internal",
- },
- {
- name: "language_code",
- type: "string",
- default: "en",
- platforms: ["macos", "ipados", "ios"],
- group: "Internal",
- legacy: "languageCode",
- },
- {
- name: "scripts_settings",
- type: "object",
- default: {},
- platforms: ["macos", "ipados", "ios"],
- langLabel: {
- en: "Scripts update check active",
- zh_hans: "脚本更新检查激活",
- },
- langTitle: {
- en: "Whether to enable each single script update check",
- zh_hans: "是否开启单个脚本更新检查",
- },
- group: "Internal",
- nodeType: "Subpage",
- },
- // {
- // name: "settings_sync",
- // type: "boolean",
- // local: true,
- // default: false,
- // protect: true,
- // platforms: ["macos", "ipados", "ios"],
- // langLabel: {
- // en: "Sync settings",
- // zh_hans: "同步设置"
- // },
- // langTitle: {
- // en: "Sync settings across devices",
- // zh_hans: "跨设备同步设置"
- // },
- // group: "General",
- // nodeType: "Toggle"
- // },
- {
- name: "toolbar_badge_count",
- type: "boolean",
- default: true,
- platforms: ["macos", "ipados"],
- langLabel: {
- en: "Show Toolbar Count",
- zh_hans: "工具栏图标显示计数徽章",
- },
- langTitle: {
- en: "displays a badge on the toolbar icon with a number that represents how many enabled scripts match the url for the page you are on",
- zh_hans: "简体中文描述",
- },
- group: "General",
- legacy: "showCount",
- nodeType: "Toggle",
- },
- {
- name: "global_active",
- type: "boolean",
- local: true,
- default: true,
- platforms: ["macos"],
- langLabel: {
- en: "Enable Injection",
- zh_hans: "启用注入",
- },
- langTitle: {
- en: "toggle on/off script injection for the pages you visit",
- zh_hans: "简体中文描述",
- },
- group: "General",
- legacy: "active",
- nodeType: "Toggle",
- nodeClass: { red: false },
- },
- {
- name: "global_scripts_update_check",
- type: "boolean",
- default: true,
- platforms: ["macos", "ipados", "ios"],
- langLabel: {
- en: "Global scripts update check",
- zh_hans: "全局脚本更新检查",
- },
- langTitle: {
- en: "Whether to enable global periodic script update check",
- zh_hans: "是否开启全局定期脚本更新检查",
- },
- group: "General",
- nodeType: "Toggle",
- },
- {
- name: "scripts_update_check_interval",
- type: "number",
- default: 86400000,
- platforms: ["macos", "ipados", "ios"],
- langLabel: {
- en: "Scripts update check interval",
- zh_hans: "脚本更新检查间隔",
- },
- langTitle: {
- en: "The interval for script update check in background",
- zh_hans: "脚本更新检查的间隔时间",
- },
- group: "General",
- nodeType: "Toggle",
- },
- {
- name: "scripts_update_check_lasttime",
- type: "number",
- default: 0,
- platforms: ["macos", "ipados", "ios"],
- langLabel: {
- en: "Scripts update check lasttime",
- zh_hans: "脚本更新上次检查时间",
- },
- langTitle: {
- en: "The lasttime for script update check in background",
- zh_hans: "后台脚本更新上次检查时间",
- },
- group: "Internal",
- },
- {
- name: "scripts_auto_update",
- type: "boolean",
- default: false,
- confirm: true,
- platforms: ["macos", "ipados", "ios"],
- langLabel: {
- en: "Scripts silent auto update",
- zh_hans: "脚本后台静默自动更新",
- },
- langTitle: {
- en: "Script silently auto-updates in the background, which is dangerous and may introduce unconfirmed malicious code",
- zh_hans:
- "脚本在后台静默自动更新,这是危险的,可能引入未经确认的恶意代码",
- },
- group: "General",
- nodeType: "Toggle",
- nodeClass: { warn: true },
- },
- {
- name: "global_exclude_match",
- type: "object",
- default: [],
- platforms: ["macos", "ipados", "ios"],
- langLabel: {
- en: "Global exclude match patterns",
- zh_hans: "全局排除匹配模式列表",
- },
- langTitle: {
- en: "this input accepts a comma separated list of @match patterns, a page url that matches against a pattern in this list will be ignored for script injection",
- zh_hans: "简体中文描述",
- },
- group: "General",
- legacy: "blacklist",
- nodeType: "textarea",
- nodeClass: { red: "blacklistError" },
- },
- {
- name: "editor_close_brackets",
- type: "boolean",
- default: true,
- platforms: ["macos"],
- langLabel: {
- en: "Auto Close Brackets",
- zh_hans: "自动关闭括号",
- },
- langTitle: {
- en: "toggles on/off auto closing of brackets in the editor, this affects the following characters: () [] {} \"\" ''",
- zh_hans: "简体中文描述",
- },
- group: "Editor",
- legacy: "autoCloseBrackets",
- nodeType: "Toggle",
- },
- {
- name: "editor_auto_hint",
- type: "boolean",
- default: true,
- platforms: ["macos"],
- langLabel: {
- en: "Auto Hint",
- zh_hans: "自动提示(Hint)",
- },
- langTitle: {
- en: "automatically shows completion hints while editing",
- zh_hans: "简体中文描述",
- },
- group: "Editor",
- legacy: "autoHint",
- nodeType: "Toggle",
- },
- {
- name: "editor_list_sort",
- type: "string",
- values: ["nameAsc", "nameDesc", "lastModifiedAsc", "lastModifiedDesc"],
- default: "lastModifiedDesc",
- platforms: ["macos"],
- langLabel: {
- en: "Sort order",
- zh_hans: "排序顺序",
- },
- langTitle: {
- en: "Display order of items in sidebar",
- zh_hans: "侧栏中项目的显示顺序",
- },
- group: "Editor",
- legacy: "sortOrder",
- nodeType: "Dropdown",
- },
- {
- name: "editor_list_descriptions",
- type: "boolean",
- default: true,
- platforms: ["macos"],
- langLabel: {
- en: "Show List Descriptions",
- zh_hans: "显示列表项目描述",
- },
- langTitle: {
- en: "show or hides the item descriptions in the sidebar",
- zh_hans: "简体中文描述",
- },
- group: "Editor",
- legacy: "descriptions",
- nodeType: "Toggle",
- },
- {
- name: "editor_javascript_lint",
- type: "boolean",
- default: false,
- platforms: ["macos"],
- langLabel: {
- en: "Javascript Linter",
- zh_hans: "Javascript Linter",
- },
- langTitle: {
- en: "toggles basic Javascript linting within the editor",
- zh_hans: "简体中文描述",
- },
- group: "Editor",
- legacy: "lint",
- nodeType: "Toggle",
- },
- {
- name: "editor_show_whitespace",
- type: "boolean",
- default: true,
- platforms: ["macos"],
- langLabel: {
- en: "Show whitespace characters",
- zh_hans: "显示空白字符",
- },
- langTitle: {
- en: "toggles the display of invisible characters in the editor",
- zh_hans: "简体中文描述",
- },
- group: "Editor",
- legacy: "showInvisibles",
- nodeType: "Toggle",
- },
- {
- name: "editor_tab_size",
- type: "number",
- values: [2, 4],
- default: 4,
- platforms: ["macos"],
- langLabel: {
- en: "Tab Size",
- zh_hans: "制表符大小",
- },
- langTitle: {
- en: "the number of spaces a tab is equal to while editing",
- zh_hans: "简体中文描述",
- },
- group: "Editor",
- legacy: "tabSize",
- nodeType: "select",
- },
- ].reduce(settingsDefineReduceCallback, {}),
+/** @type {Readonly} - Read-only settings definition */
+const settingsDefinition = [
+ {
+ name: "error_native",
+ type: "object",
+ local: true,
+ default: { error: undefined },
+ group: "INTERNAL",
+ },
+ {
+ name: "legacy_imported",
+ type: "number",
+ local: true,
+ default: 0,
+ protect: true,
+ platforms: { macos: undefined },
+ group: "INTERNAL",
+ },
+ {
+ name: "language_code",
+ type: "string",
+ default: "en",
+ group: "INTERNAL",
+ legacy: "languageCode",
+ },
+ {
+ name: "settings_sync",
+ type: "boolean",
+ local: true,
+ default: false,
+ disable: true,
+ protect: true,
+ group: "general",
+ nodeType: "Toggle",
+ },
+ {
+ name: "toolbar_badge_count",
+ type: "boolean",
+ default: true,
+ platforms: { macos: true, ipados: true, ios: false },
+ group: "general",
+ legacy: "showCount",
+ nodeType: "Toggle",
+ },
+ {
+ name: "global_active",
+ type: "boolean",
+ local: true,
+ default: true,
+ group: "general",
+ legacy: "active",
+ nodeType: "Toggle",
+ nodeClass: { warn: false },
+ },
+ {
+ name: "global_scripts_update_check",
+ type: "boolean",
+ default: true,
+ group: "general",
+ nodeType: "Toggle",
+ },
+ {
+ name: "scripts_settings",
+ type: "object",
+ default: {},
+ disable: true,
+ group: "INTERNAL",
+ },
+ {
+ name: "scripts_update_check_interval",
+ type: "number",
+ values: [0, 1, 3, 7, 15, 30],
+ default: 0,
+ group: "general",
+ nodeType: "select",
+ },
+ {
+ name: "scripts_update_check_lasttime",
+ type: "number",
+ default: 0,
+ group: "INTERNAL",
+ },
+ {
+ name: "scripts_update_automation",
+ type: "boolean",
+ default: false,
+ disable: true,
+ confirm: true,
+ group: "general",
+ nodeType: "Toggle",
+ nodeClass: { warn: true },
+ },
+ {
+ name: "global_exclude_match",
+ type: "object",
+ default: [],
+ group: "general",
+ legacy: "blacklist",
+ nodeType: "textarea",
+ },
+ {
+ name: "editor_close_brackets",
+ type: "boolean",
+ default: true,
+ platforms: { macos: undefined },
+ group: "editor",
+ legacy: "autoCloseBrackets",
+ nodeType: "Toggle",
+ },
+ {
+ name: "editor_auto_hint",
+ type: "boolean",
+ default: true,
+ platforms: { macos: undefined },
+ group: "editor",
+ legacy: "autoHint",
+ nodeType: "Toggle",
+ },
+ {
+ name: "editor_list_sort",
+ type: "string",
+ values: ["nameAsc", "nameDesc", "lastModifiedAsc", "lastModifiedDesc"],
+ default: "lastModifiedDesc",
+ platforms: { macos: undefined },
+ group: "editor",
+ legacy: "sortOrder",
+ nodeType: "select",
+ },
+ {
+ name: "editor_list_descriptions",
+ type: "boolean",
+ default: true,
+ platforms: { macos: undefined },
+ group: "editor",
+ legacy: "descriptions",
+ nodeType: "Toggle",
+ },
+ {
+ name: "editor_javascript_lint",
+ type: "boolean",
+ default: false,
+ platforms: { macos: undefined },
+ group: "editor",
+ legacy: "lint",
+ nodeType: "Toggle",
+ },
+ {
+ name: "editor_show_whitespace",
+ type: "boolean",
+ default: true,
+ platforms: { macos: undefined },
+ group: "editor",
+ legacy: "showInvisibles",
+ nodeType: "Toggle",
+ },
+ {
+ name: "editor_tab_size",
+ type: "number",
+ values: [1, 2, 3, 4, 5, 6, 8, 10, 12],
+ default: 4,
+ platforms: { macos: undefined },
+ group: "editor",
+ legacy: "tabSize",
+ nodeType: "select",
+ },
+];
+
+/** @type {Readonly<{[key: string]: SettingWithKey}>} - Read-only settings dictionary */
+export const settingsDictionary = deepFreeze(
+ settingsDefinition.reduce(settingsDefinitionReduceCallbackFn, {}),
);
/**
- * populate the settingsDefine with settingDefault
- * and convert settingsDefine to storageKey object
- * @param {object} settings new settings object
- * @param {object} setting each setting define
- * @returns {object} {US_GLOBAL_ACTIVE: {key: US_GLOBAL_ACTIVE, name: global_active, ... }, ...}
+ * populate the settings-define with setting-default
+ * and convert settings-define to storage-key object
+ * @param {{[key: string]: SettingWithKey}} settings settings dictionary
+ * @param {Setting} setting each setting define
+ * @returns // {US_GLOBAL_ACTIVE: {key: US_GLOBAL_ACTIVE, name: global_active, ... }, ...}
*/
-function settingsDefineReduceCallback(settings, setting) {
- setting.key = storageKey(setting.name);
- settings[setting.key] = { ...settingDefault, ...setting };
+function settingsDefinitionReduceCallbackFn(settings, setting) {
+ const key = storageKey(setting.name);
+ settings[key] = { ...settingDefault, ...setting, key };
return settings;
}
/**
* prevent settings define from being modified in any case
* otherwise user settings may be lost in the worst case
+ * @type {(o: T) => Readonly}
* @param {object} object any object
* @returns {object} deep frozen object
*/
@@ -410,20 +303,21 @@ if (Object.hasOwn === undefined) {
/**
* settings.get
- * @param {string|Array} keys key | array of keys | undefined for all
+ * @param {string|string[]} keys key | array of keys | undefined for all
* @param {"local"|"sync"} area
- * @returns {Promise} settings object
+ * @returns settings object
*/
export async function get(keys = undefined, area = undefined) {
if (![undefined, "local", "sync"].includes(area)) {
return console.error("Unexpected storage area:", area);
}
// validate setting value and fix surprises to default
+ /** @param {string} key @param {any} val */
const valueFix = (key, val) => {
- if (!key || !Object.hasOwn(settingsDefine, key)) return;
- const def = settingsDefine[key].default;
- // check if value type conforms to settingsDefine
- const type = settingsDefine[key].type;
+ if (!key || !Object.hasOwn(settingsDictionary, key)) return;
+ const def = settingsDictionary[key].default;
+ // check if value type conforms to settings-dictionary
+ const type = settingsDictionary[key].type;
// eslint-disable-next-line valid-typeof -- type known to be valid string literal
if (typeof val != type) {
console.warn(
@@ -431,8 +325,8 @@ export async function get(keys = undefined, area = undefined) {
);
return def;
}
- // check if value conforms to settingsDefine
- const values = settingsDefine[key].values;
+ // check if value conforms to settings-dictionary
+ const values = settingsDictionary[key].values;
if (values.length && !values.includes(val)) {
console.warn(
`Unexpected ${key} value '${val}' should one of '${values}', fix to default`,
@@ -445,17 +339,17 @@ export async function get(keys = undefined, area = undefined) {
// [single setting]
if (typeof keys == "string") {
const key = storageKey(keys);
- // check if key exist in settingsDefine
- if (!Object.hasOwn(settingsDefine, key)) {
+ // check if key exist in settings-dictionary
+ if (!Object.hasOwn(settingsDictionary, key)) {
return console.error("unexpected settings key:", key);
}
// check if only locally stored setting
// eslint-disable-next-line no-param-reassign -- change the area is expected
- settingsDefine[key].local === true && (area = "local");
+ settingsDictionary[key].local === true && (area = "local");
const storage = await storageRef(area);
const result = await storage.ref.get(key);
if (Object.hasOwn(result, key)) return valueFix(key, result[key]);
- return settingsDefine[key].default;
+ return settingsDictionary[key].default;
}
const complexGet = async (settingsDefault, areaKeys) => {
const storage = await storageRef(area);
@@ -474,7 +368,7 @@ export async function get(keys = undefined, area = undefined) {
const result = Object.assign(settingsDefault, local, sync);
// revert settings object property name
return Object.entries(result).reduce((p, c) => {
- p[settingsDefine[c[0]].name] = valueFix(...c);
+ p[settingsDictionary[c[0]].name] = valueFix(...c);
return p;
}, {});
};
@@ -487,13 +381,13 @@ export async function get(keys = undefined, area = undefined) {
const areaKeys = { local: [], sync: [], all: [] };
for (const k of keys) {
const key = storageKey(k);
- // check if key exist in settingsDefine
- if (!Object.hasOwn(settingsDefine, key)) {
+ // check if key exist in settings-dictionary
+ if (!Object.hasOwn(settingsDictionary, key)) {
return console.error("unexpected settings key:", key);
}
- settingsDefault[key] = settingsDefine[key].default;
+ settingsDefault[key] = settingsDictionary[key].default;
// detach only locally stored settings
- settingsDefine[key].local === true
+ settingsDictionary[key].local === true
? areaKeys.local.push(key)
: areaKeys.sync.push(key);
// record all keys in case sync storage is not enabled
@@ -505,10 +399,10 @@ export async function get(keys = undefined, area = undefined) {
if (typeof keys == "undefined" || keys === null) {
const settingsDefault = {};
const areaKeys = { local: [], sync: [], all: [] };
- for (const key of Object.keys(settingsDefine)) {
- settingsDefault[key] = settingsDefine[key].default;
+ for (const key of Object.keys(settingsDictionary)) {
+ settingsDefault[key] = settingsDictionary[key].default;
// detach only locally stored settings
- settingsDefine[key].local === true
+ settingsDictionary[key].local === true
? areaKeys.local.push(key)
: areaKeys.sync.push(key);
// record all keys in case sync storage is not enabled
@@ -523,7 +417,6 @@ export async function get(keys = undefined, area = undefined) {
* settings.set
* @param {object} keys settings object
* @param {"local"|"sync"} area
- * @returns {Promise}
*/
export async function set(keys, area = undefined) {
if (![undefined, "local", "sync"].includes(area)) {
@@ -538,12 +431,12 @@ export async function set(keys, area = undefined) {
const areaKeys = { local: {}, sync: {}, all: {} };
for (const k of Object.keys(keys)) {
const key = storageKey(k);
- // check if key exist in settingsDefine
- if (!Object.hasOwn(settingsDefine, key)) {
+ // check if key exist in settings-dictionary
+ if (!Object.hasOwn(settingsDictionary, key)) {
return console.error("Unexpected settings keys:", key);
}
- // check if value type conforms to settingsDefine
- const type = settingsDefine[key].type;
+ // check if value type conforms to settings-dictionary
+ const type = settingsDictionary[key].type;
// eslint-disable-next-line valid-typeof -- type known to be valid string literal
if (typeof keys[k] != type) {
if (type === "number" && !Number.isNaN(Number(keys[k]))) {
@@ -555,15 +448,15 @@ export async function set(keys, area = undefined) {
);
}
}
- // check if value conforms to settingsDefine
- const values = settingsDefine[key].values;
+ // check if value conforms to settings-dictionary
+ const values = settingsDictionary[key].values;
if (values.length && !values.includes(keys[k])) {
return console.error(
`Unexpected ${k} value '${keys[k]}' should one of '${values}'`,
);
}
// detach only locally stored settings
- settingsDefine[key].local === true
+ settingsDictionary[key].local === true
? (areaKeys.local[key] = keys[k])
: (areaKeys.sync[key] = keys[k]);
// record all keys in case sync storage is not enabled
@@ -591,9 +484,8 @@ export async function set(keys, area = undefined) {
/**
* settings.reset
* reset to default
- * @param {string|Array} keys key | array of keys | undefined for all
+ * @param {string|string[]} keys key | array of keys | undefined for all
* @param {"local"|"sync"} area
- * @returns {Promise}
*/
export async function reset(keys = undefined, area = undefined) {
if (![undefined, "local", "sync"].includes(area)) {
@@ -602,16 +494,16 @@ export async function reset(keys = undefined, area = undefined) {
// [single setting]
if (typeof keys == "string") {
const key = storageKey(keys);
- // check if key exist in settingsDefine
- if (!Object.hasOwn(settingsDefine, key)) {
+ // check if key exist in settings-dictionary
+ if (!Object.hasOwn(settingsDictionary, key)) {
return console.error("unexpected settings key:", key);
}
// check if key is protected
- if (settingsDefine[key].protect === true) {
+ if (settingsDictionary[key].protect === true) {
return console.error("protected settings key:", key);
}
// eslint-disable-next-line no-param-reassign -- change the area is expected
- settingsDefine[key].local === true && (area = "local");
+ settingsDictionary[key].local === true && (area = "local");
const storage = await storageRef(area);
return storage.ref.remove(key);
}
@@ -641,16 +533,16 @@ export async function reset(keys = undefined, area = undefined) {
const areaKeys = { local: [], sync: [], all: [] };
for (const k of keys) {
const key = storageKey(k);
- // check if key exist in settingsDefine
- if (!Object.hasOwn(settingsDefine, key)) {
+ // check if key exist in settings-dictionary
+ if (!Object.hasOwn(settingsDictionary, key)) {
return console.error("unexpected settings key:", key);
}
// check if key is protected
- if (settingsDefine[key].protect === true) {
+ if (settingsDictionary[key].protect === true) {
return console.error("protected settings key:", key);
}
// detach only locally stored settings
- settingsDefine[key].local === true
+ settingsDictionary[key].local === true
? areaKeys.local.push(key)
: areaKeys.sync.push(key);
// record all keys in case sync storage is not enabled
@@ -661,11 +553,11 @@ export async function reset(keys = undefined, area = undefined) {
// [all settings]
if (typeof keys == "undefined" || keys === null) {
const areaKeys = { local: [], sync: [], all: [] };
- for (const key in settingsDefine) {
+ for (const key in settingsDictionary) {
// skip protected keys
- if (settingsDefine[key].protect === true) continue;
+ if (settingsDictionary[key].protect === true) continue;
// detach only locally stored settings
- settingsDefine[key].local === true
+ settingsDictionary[key].local === true
? areaKeys.local.push(key)
: areaKeys.sync.push(key);
// record all keys in case sync storage is not enabled
@@ -679,8 +571,11 @@ export async function reset(keys = undefined, area = undefined) {
/**
* complex onChanged
* this function is convenient for the svelte store to update the state
- * @param {Function} callback
+ * @callback onChangedSettingsCallback
+ * @param {{[key: string]: any}} settings - changed settings
+ * @param {Areas} area - storage area
* @returns {void}
+ * @param {onChangedSettingsCallback} callback
*/
export function onChangedSettings(callback) {
if (typeof callback != "function") {
@@ -690,14 +585,14 @@ export function onChangedSettings(callback) {
/**
* @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/onChanged#listener}
* @param {object} changes
- * @param {"sync"|"local"} area
+ * @param {Areas} area
*/
const listener = (changes, area) => {
// console.log(`storage.${area}.onChanged`, changes); // DEBUG
const settings = {};
for (const key in changes) {
- if (!Object.hasOwn(settingsDefine, key)) continue;
- settings[settingsDefine[key].name] = changes[key].newValue;
+ if (!Object.hasOwn(settingsDictionary, key)) continue;
+ settings[settingsDictionary[key].name] = changes[key].newValue;
}
try {
callback(settings, area);
@@ -713,14 +608,14 @@ export function onChangedSettings(callback) {
/**
* settings.legacyGet
- * @param {string|Array.} keys
- * @returns {Promise} settings object with legacy keys
+ * @param {string|string[]} keys
+ * @returns settings object with legacy keys
*/
export async function legacyGet(keys = undefined) {
const result = await get(keys);
// console.log("legacy_get", keys, result);
for (const key of Object.keys(result)) {
- const legacy = settingsDefine[storageKey(key)]?.legacy;
+ const legacy = settingsDictionary[storageKey(key)]?.legacy;
if (legacy) result[legacy] = result[key];
}
return result;
@@ -729,7 +624,6 @@ export async function legacyGet(keys = undefined) {
/**
* settings.legacySet
* @param {object} keys legacy keys
- * @returns {Promise}
*/
export async function legacySet(keys) {
if (typeof keys != "object") {
@@ -739,8 +633,8 @@ export async function legacySet(keys) {
return console.error("Settings object empty:", keys);
}
const settings = {};
- for (const key of Object.keys(settingsDefine)) {
- const setting = settingsDefine[key];
+ for (const key of Object.keys(settingsDictionary)) {
+ const setting = settingsDictionary[key];
if (!setting.legacy) continue;
if (setting.legacy in keys) {
settings[setting.name] = keys[setting.legacy];
@@ -758,14 +652,15 @@ export async function legacyImport() {
const result = await browser.runtime.sendNativeMessage("app", {
name: "PAGE_LEGACY_IMPORT",
});
+ if (!result) return console.error("PAGE_LEGACY_IMPORT not response");
if (result.error) return console.error(result.error);
console.info("Import settings data from legacy manifest file");
const settings = {};
- for (const key of Object.keys(settingsDefine)) {
- const legacy = settingsDefine[key].legacy;
+ for (const key of Object.keys(settingsDictionary)) {
+ const legacy = settingsDictionary[key].legacy;
if (legacy in result) {
let value = result[legacy];
- switch (settingsDefine[key].type) {
+ switch (settingsDictionary[key].type) {
case "boolean":
value = JSON.parse(value);
break;
@@ -774,7 +669,7 @@ export async function legacyImport() {
break;
}
console.info(`Importing legacy setting: ${legacy}`, value);
- settings[settingsDefine[key].name] = value;
+ settings[settingsDictionary[key].name] = value;
}
}
// import complete tag, to ensure will only be import once
From ba7afea9e4f96338bc64cb982abdd777fc3b6822 Mon Sep 17 00:00:00 2001
From: ACTCD <101378590+ACTCD@users.noreply.github.com>
Date: Mon, 8 Jan 2024 18:26:16 +0800
Subject: [PATCH 07/24] feat: create new extension page app for ios
---
src/ext/extension-page/Appios.svelte | 92 +++++++++++++++++++
src/ext/extension-page/app.css | 9 ++
src/ext/extension-page/main.js | 25 ++++-
src/ext/extension-page/store.js | 38 ++++++--
.../SafariWebExtensionHandler.swift | 12 +--
5 files changed, 160 insertions(+), 16 deletions(-)
create mode 100644 src/ext/extension-page/Appios.svelte
diff --git a/src/ext/extension-page/Appios.svelte b/src/ext/extension-page/Appios.svelte
new file mode 100644
index 00000000..ed66fb5c
--- /dev/null
+++ b/src/ext/extension-page/Appios.svelte
@@ -0,0 +1,92 @@
+
+
+{#if $state.includes("init")}
+
+
+ {@html logo}
+ {#if $state.includes("init-error")}
+ Failed to initialize app, check the browser console
+ {:else}
+ Initializing app...
+ {/if}
+
+{/if}
+
+ {#each $notifications as item (item.id)}
+ notifications.remove(item.id)} {item} />
+ {/each}
+
+{#if $state.includes("settings")}
+
+{/if}
+
+
diff --git a/src/ext/extension-page/app.css b/src/ext/extension-page/app.css
index f34a9b9a..61b9195a 100644
--- a/src/ext/extension-page/app.css
+++ b/src/ext/extension-page/app.css
@@ -3,6 +3,15 @@ html {
height: 100vh;
height: 100svh; /* safari 15.4 */
overflow: hidden;
+ overscroll-behavior: none;
+}
+
+/* ios */
+@supports (-webkit-touch-callout: none) {
+ html {
+ height: auto;
+ overflow: visible;
+ }
}
body {
diff --git a/src/ext/extension-page/main.js b/src/ext/extension-page/main.js
index ec2580e9..7c45ed52 100644
--- a/src/ext/extension-page/main.js
+++ b/src/ext/extension-page/main.js
@@ -2,6 +2,7 @@ import "../shared/reset.css";
import "../shared/variables.css";
import "./app.css";
import App from "./App.svelte";
+import Appios from "./Appios.svelte";
// vite feat that only import in dev mode
if (import.meta.env.MODE === "development") {
@@ -14,8 +15,26 @@ if (import.meta.env.MODE === "development") {
}
}
-const app = new App({
- target: document.getElementById("app"),
-});
+let app;
+const target = document.getElementById("app");
+if (import.meta.env.MODE === "development") {
+ const platform = await browser.runtime.getPlatformInfo();
+ // @ts-ignore -- incomplete polyfill types
+ if (platform.os === "ios") {
+ app = new Appios({ target });
+ } else {
+ app = new App({ target });
+ }
+} else {
+ if (import.meta.env.SAFARI_PLATFORM === "ios") {
+ app = new Appios({ target });
+ } else {
+ app = new App({ target });
+ }
+}
+
+// const app = new App({
+// target: document.getElementById("app"),
+// });
export default app;
diff --git a/src/ext/extension-page/store.js b/src/ext/extension-page/store.js
index dc9b0234..22550c7c 100644
--- a/src/ext/extension-page/store.js
+++ b/src/ext/extension-page/store.js
@@ -37,7 +37,7 @@ function stateStore() {
// store oldState to see how state transitioned
// ex. if (newState === foo && oldState === bar) baz();
let oldState = [];
- const add = (stateModifier) =>
+ const add = (stateModifier) => {
update((state) => {
// list of acceptable states, mostly for state definition tracking
const states = [
@@ -68,7 +68,14 @@ function stateStore() {
);
return state;
});
- const remove = (stateModifier) =>
+ // URL hash handle
+ const params = new URLSearchParams(location.hash.slice(1));
+ if (["settings"].includes(stateModifier)) {
+ params.set("state", stateModifier);
+ location.hash = params.toString();
+ }
+ };
+ const remove = (stateModifier) => {
update((state) => {
// save pre-changed state to oldState var
oldState = [...state];
@@ -84,20 +91,38 @@ function stateStore() {
);
return state;
});
+ // URL hash handle
+ const params = new URLSearchParams(location.hash.slice(1));
+ const state = params.get("state");
+ if (state === stateModifier) {
+ params.delete("state");
+ location.hash = params.toString();
+ }
+ };
const getOldState = () => oldState;
- return { subscribe, add, getOldState, remove };
+ // URL hash handle
+ const loadUrlState = () => {
+ const params = new URLSearchParams(location.hash.slice(1));
+ const state = params.get("state");
+ state && add(state);
+ };
+ return { subscribe, add, getOldState, remove, loadUrlState };
}
export const state = stateStore();
function settingsStore() {
const { subscribe, update, set } = writable({});
const init = async (initData) => {
- // import legacy settings data just one-time
- await settingsStorage.legacyImport();
+ if (import.meta.env.SAFARI_PLATFORM === "mac") {
+ // import legacy settings data just one-time
+ await settingsStorage.legacyImport();
+ }
// for compatibility with legacy getting names only
// once all new name is used, use settingsStorage.get()
const settings = await settingsStorage.legacyGet();
- console.info("store.js settingsStore init", initData, settings);
+ if (import.meta.env.MODE === "development") {
+ console.info("store.js settingsStore init", initData, settings);
+ }
set({ ...initData, ...settings });
// sync popup, backgound, etc... settings changes
settingsStorage.onChangedSettings((sets, area) => {
@@ -135,6 +160,7 @@ function settingsStore() {
// for compatibility with legacy setting names only
// once all new name is used, use settingsStorage.set()
settingsStorage.legacySet({ [key]: value }); // Durable Storage
+ settingsStorage.set({ [key]: value }); // Durable Storage
// temporarily keep the old storage method until it is confirmed that all dependencies are removed
updateSingleSettingOld(key, value);
};
diff --git a/xcode/Ext-Safari/SafariWebExtensionHandler.swift b/xcode/Ext-Safari/SafariWebExtensionHandler.swift
index ac5cec13..b296be2a 100644
--- a/xcode/Ext-Safari/SafariWebExtensionHandler.swift
+++ b/xcode/Ext-Safari/SafariWebExtensionHandler.swift
@@ -186,13 +186,11 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
}
}
else if name == "PAGE_INIT_DATA" {
- #if os(macOS)
- if let initData = getInitData(), checkDefaultDirectories() {
- response.userInfo = [SFExtensionMessageKey: initData]
- } else {
- response.userInfo = [SFExtensionMessageKey: ["error": "failed to get init data"]]
- }
- #endif
+ if let initData = getInitData(), checkDefaultDirectories() {
+ response.userInfo = [SFExtensionMessageKey: initData]
+ } else {
+ response.userInfo = [SFExtensionMessageKey: ["error": "failed to get init data"]]
+ }
}
else if name == "PAGE_LEGACY_IMPORT" {
#if os(macOS)
From 1f79d588f840e69b0806568c27d946916977445c Mon Sep 17 00:00:00 2001
From: ACTCD <101378590+ACTCD@users.noreply.github.com>
Date: Mon, 8 Jan 2024 18:28:18 +0800
Subject: [PATCH 08/24] refactor: extract generic modal wrapper for macos app
---
src/ext/extension-page/App.svelte | 16 +++-
.../Components/ModalWrapper.svelte | 87 +++++++++++++++++++
2 files changed, 100 insertions(+), 3 deletions(-)
create mode 100644 src/ext/extension-page/Components/ModalWrapper.svelte
diff --git a/src/ext/extension-page/App.svelte b/src/ext/extension-page/App.svelte
index d802643a..2d0dd096 100644
--- a/src/ext/extension-page/App.svelte
+++ b/src/ext/extension-page/App.svelte
@@ -5,6 +5,7 @@
import Sidebar from "./Components/Sidebar/Sidebar.svelte";
import Editor from "./Components/Editor/Editor.svelte";
import Settings from "./Components/Settings.svelte";
+ import ModalWrapper from "./Components/ModalWrapper.svelte";
import Notification from "./Components/Notification.svelte";
import logo from "../shared/img/logo.svg?raw";
import { connectNative, sendNativeMessage } from "../shared/native.js";
@@ -47,16 +48,19 @@
if (files.error) return console.error(files.error);
items.set(files);
state.remove("items-loading");
+ state.loadUrlState();
});
// handle native app messages
- const port = connectNative();
- port.onMessage.addListener((message) => {
+ const nativePort = connectNative();
+ nativePort.onMessage.addListener((message) => {
// console.info(message); // DEBUG
if (message.name === "SAVE_LOCATION_CHANGED") {
window.location.reload();
}
});
+
+ const settingsProps = { nativePort, platform: "macos" };
@@ -81,7 +85,13 @@
notifications.remove(item.id)} {item} />
{/each}
-{#if $state.includes("settings")}{/if}
+{#if $state.includes("settings")}
+ state.remove("settings")}
+ />
+{/if}
From e0a01052e805e0566e578e8e21343b1f4bbd3816 Mon Sep 17 00:00:00 2001
From: ACTCD <101378590+ACTCD@users.noreply.github.com>
Date: Mon, 8 Jan 2024 18:39:50 +0800
Subject: [PATCH 09/24] feat: setup extension options page entrance
---
public/ext/safari-15/manifest.json | 3 +++
public/ext/safari-16.4/manifest.json | 3 +++
public/ext/safari-dev/manifest-ios.json | 2 +-
public/ext/safari-dev/manifest-mac.json | 2 +-
4 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/public/ext/safari-15/manifest.json b/public/ext/safari-15/manifest.json
index 99c503ee..1b4e52db 100644
--- a/public/ext/safari-15/manifest.json
+++ b/public/ext/safari-15/manifest.json
@@ -22,6 +22,9 @@
"32": "images/toolbar-icon-32.png"
}
},
+ "options_ui": {
+ "page": "dist/entry-ext-extension-page.html#state=settings"
+ },
"content_scripts": [
{
"js": ["dist/content-scripts/userscripts.js"],
diff --git a/public/ext/safari-16.4/manifest.json b/public/ext/safari-16.4/manifest.json
index 7d2c00da..ab445895 100644
--- a/public/ext/safari-16.4/manifest.json
+++ b/public/ext/safari-16.4/manifest.json
@@ -19,6 +19,9 @@
"default_popup": "dist/entry-ext-action-popup.html",
"default_icon": "images/action.svg"
},
+ "options_ui": {
+ "page": "dist/entry-ext-extension-page.html#state=settings"
+ },
"content_scripts": [
{
"js": ["dist/content-scripts/userscripts.js"],
diff --git a/public/ext/safari-dev/manifest-ios.json b/public/ext/safari-dev/manifest-ios.json
index 2cb0308a..e1cac050 100644
--- a/public/ext/safari-dev/manifest-ios.json
+++ b/public/ext/safari-dev/manifest-ios.json
@@ -20,7 +20,7 @@
"default_icon": "images/action.svg"
},
"options_ui": {
- "page": "dist/entry-ext-extension-page.html#settings"
+ "page": "dist/entry-ext-extension-page.html#state=settings"
},
"content_scripts": [
{
diff --git a/public/ext/safari-dev/manifest-mac.json b/public/ext/safari-dev/manifest-mac.json
index 046f8239..e469aa66 100644
--- a/public/ext/safari-dev/manifest-mac.json
+++ b/public/ext/safari-dev/manifest-mac.json
@@ -20,7 +20,7 @@
"default_icon": "images/action.svg"
},
"options_ui": {
- "page": "dist/entry-ext-extension-page.html#settings"
+ "page": "dist/entry-ext-extension-page.html#state=settings"
},
"content_scripts": [
{
From a6f9d9b961c365e6c3470e558fcbbea306d3c24a Mon Sep 17 00:00:00 2001
From: ACTCD <101378590+ACTCD@users.noreply.github.com>
Date: Mon, 8 Jan 2024 18:40:30 +0800
Subject: [PATCH 10/24] refactor: introducing new match patterns parser and
i18n
---
public/ext/shared/_locales/en/messages.json | 201 +++++++++++++++++++
public/ext/shared/_locales/zh/messages.json | 207 ++++++++++++++++++++
src/ext/shared/utils.js | 178 +++++++++++++++++
3 files changed, 586 insertions(+)
create mode 100644 public/ext/shared/_locales/zh/messages.json
diff --git a/public/ext/shared/_locales/en/messages.json b/public/ext/shared/_locales/en/messages.json
index e840a3a2..9b035e5c 100644
--- a/public/ext/shared/_locales/en/messages.json
+++ b/public/ext/shared/_locales/en/messages.json
@@ -6,5 +6,206 @@
"extension_description": {
"message": "Save and run javascript for the web pages you visit",
"description": "Description of what the extension does."
+ },
+ "settings": {
+ "message": "Settings"
+ },
+ "settings_section_editor": {
+ "message": "Editor Settings"
+ },
+ "settings_section_general": {
+ "message": "General Settings"
+ },
+ "settings_section_native": {
+ "message": "Native Settings"
+ },
+ "settings_section_about": {
+ "message": "About"
+ },
+ "settings_editor_auto_hint": {
+ "message": "Auto Hint"
+ },
+ "settings_editor_auto_hint_desc": {
+ "message": "Automatically shows completion hints while editing"
+ },
+ "settings_editor_close_brackets": {
+ "message": "Auto Close Brackets"
+ },
+ "settings_editor_close_brackets_desc": {
+ "message": "Toggles on/off auto closing of brackets in the editor, this affects the following characters: () [] {} \"\" ''"
+ },
+ "settings_editor_javascript_lint": {
+ "message": "Javascript Linter"
+ },
+ "settings_editor_javascript_lint_desc": {
+ "message": "Toggles basic Javascript linting within the editor"
+ },
+ "settings_editor_list_descriptions": {
+ "message": "Show List Descriptions"
+ },
+ "settings_editor_list_descriptions_desc": {
+ "message": "Show or hides the item descriptions in the sidebar"
+ },
+ "settings_editor_list_sort": {
+ "message": "Sort order"
+ },
+ "settings_editor_list_sort_nameAsc": {
+ "message": "Scripts Name: Asc"
+ },
+ "settings_editor_list_sort_nameDesc": {
+ "message": "Scripts Name: Desc"
+ },
+ "settings_editor_list_sort_lastModifiedAsc": {
+ "message": "Last Modified: Asc"
+ },
+ "settings_editor_list_sort_lastModifiedDesc": {
+ "message": "Last Modified: Desc"
+ },
+ "settings_editor_list_sort_desc": {
+ "message": "Display order of items in sidebar"
+ },
+ "settings_editor_show_whitespace": {
+ "message": "Show whitespace characters"
+ },
+ "settings_editor_show_whitespace_desc": {
+ "message": "Toggles the display of invisible characters in the editor"
+ },
+ "settings_editor_tab_size": {
+ "message": "Tab Size"
+ },
+ "settings_editor_tab_size_desc": {
+ "message": "Choose the number of spaces a tab is equal to when rendering code"
+ },
+ "settings_global_active": {
+ "message": "Enable Injection"
+ },
+ "settings_global_active_desc": {
+ "message": "Toggle on/off script injection for the pages you visit"
+ },
+ "settings_global_exclude_match": {
+ "message": "Global exclude match patterns"
+ },
+ "settings_global_exclude_match_desc": {
+ "message": "This input accepts a whitespace (spaces, newlines etc.) separated list of @match patterns, a page url that matches against a pattern in this list will be ignored for script injection"
+ },
+ "settings_global_exclude_match_saving": {
+ "message": "Saving..."
+ },
+ "settings_global_exclude_match_placeholder": {
+ "message": "list of @match patterns, for example: \n*://*/*foo.bar\n*://*/*foo.bar?*\n*://*.example.net/*\nhttps://example.net/*/foo/*/\nhttps://*.example.net/a/b/c/?foo=/"
+ },
+ "settings_global_exclude_match_refer": {
+ "message": "Please refer to:"
+ },
+ "settings_global_scripts_update_check": {
+ "message": "Global scripts update check"
+ },
+ "settings_global_scripts_update_check_desc": {
+ "message": "Whether to enable global periodic script update check"
+ },
+ "settings_scripts_settings": {
+ "message": "Scripts update check active"
+ },
+ "settings_scripts_settings_desc": {
+ "message": "Whether to enable each single script update check"
+ },
+ "settings_scripts_update_automation": {
+ "message": "Scripts updates automatically"
+ },
+ "settings_scripts_update_automation_desc": {
+ "message": "Script silently auto-updates in the background, which is dangerous and may introduce unconfirmed malicious code"
+ },
+ "settings_scripts_update_check_interval": {
+ "message": "Scripts update check interval"
+ },
+ "settings_scripts_update_check_interval_desc": {
+ "message": "The interval for script update check in background (days)"
+ },
+ "settings_scripts_update_check_interval_0": {
+ "message": "Never"
+ },
+ "settings_scripts_update_check_lasttime": {
+ "message": "Scripts update check lasttime"
+ },
+ "settings_scripts_update_check_lasttime_desc": {
+ "message": "The lasttime for script update check in background"
+ },
+ "settings_settings_sync": {
+ "message": "Sync settings"
+ },
+ "settings_settings_sync_desc": {
+ "message": "Sync settings across devices"
+ },
+ "settings_toolbar_badge_count": {
+ "message": "Show Toolbar Count"
+ },
+ "settings_toolbar_badge_count_desc": {
+ "message": "Displays a badge on the toolbar icon with a number that represents how many enabled scripts match the url for the page you are on"
+ },
+ "settings_scripts_directory": {
+ "message": "Save Location"
+ },
+ "settings_scripts_directory_desc": {
+ "message": "Path to the folder where user scripts are stored"
+ },
+ "settings_set_scripts_directory": {
+ "message": "Change save location"
+ },
+ "settings_about_text1": {
+ "message": "Get more information about this extension by visiting the open source project:"
+ },
+ "settings_about_text2": {
+ "message": "If you enjoy using this extension, please consider leaving a review on the App Store or sign up to beta test new versions:"
+ },
+ "settings_about_button_repo": {
+ "message": "Code repository"
+ },
+ "settings_about_button_docs": {
+ "message": "Documentation"
+ },
+ "settings_about_button_issues": {
+ "message": "Report bugs"
+ },
+ "settings_about_button_store": {
+ "message": "Open in the App Store"
+ },
+ "settings_about_button_beta": {
+ "message": "Sign up for beta testing"
+ },
+ "utils_check_match_patterns_1": {
+ "message": "The scheme component should one of *, https, http"
+ },
+ "utils_check_match_patterns_2": {
+ "message": "The scheme and host should separated by `://`"
+ },
+ "utils_check_match_patterns_3": {
+ "message": "The match pattern has no path component"
+ },
+ "utils_check_match_patterns_4": {
+ "message": "The `*.` should followed by part of the hostname"
+ },
+ "utils_check_match_patterns_5": {
+ "message": "The host component length should be 1-255"
+ },
+ "utils_check_match_patterns_6": {
+ "message": "The `*` should be independent or `*.` at the start"
+ },
+ "utils_check_match_patterns_7": {
+ "message": "The host component contains empty label(s)"
+ },
+ "utils_check_match_patterns_8": {
+ "message": "The hostname label cannot start or end with `-` character"
+ },
+ "utils_check_match_patterns_9": {
+ "message": "The maximum length of the hostname label cannot exceed 63"
+ },
+ "utils_check_match_patterns_10": {
+ "message": "The host component contains invalid character(s): $1"
+ },
+ "utils_check_match_patterns_11": {
+ "message": "The path component contains invalid character(s): $1"
+ },
+ "msg_invalid_match_pattern": {
+ "message": "Invalid match pattern"
}
}
diff --git a/public/ext/shared/_locales/zh/messages.json b/public/ext/shared/_locales/zh/messages.json
new file mode 100644
index 00000000..610008bd
--- /dev/null
+++ b/public/ext/shared/_locales/zh/messages.json
@@ -0,0 +1,207 @@
+{
+ "extension_description": {
+ "message": "用户脚本和样式管理器",
+ "description": "Description of what the extension does."
+ },
+ "settings": {
+ "message": "设置"
+ },
+ "settings_section_editor": {
+ "message": "编辑器设置"
+ },
+ "settings_section_general": {
+ "message": "通用设置"
+ },
+ "settings_section_native": {
+ "message": "本地设置"
+ },
+ "settings_section_about": {
+ "message": "关于"
+ },
+ "settings_editor_auto_hint": {
+ "message": "自动提示(Hint)"
+ },
+ "settings_editor_auto_hint_desc": {
+ "message": "编辑时自动显示完成提示"
+ },
+ "settings_editor_close_brackets": {
+ "message": "自动关闭括号"
+ },
+ "settings_editor_close_brackets_desc": {
+ "message": "在编辑器中启用自动关闭括号,这会影响以下字符:() [] {} \"\" ''"
+ },
+ "settings_editor_javascript_lint": {
+ "message": "Javascript Linter"
+ },
+ "settings_editor_javascript_lint_desc": {
+ "message": "在编辑器中启用基本的 Javascript linting 检查"
+ },
+ "settings_editor_list_descriptions": {
+ "message": "显示列表项目描述"
+ },
+ "settings_editor_list_descriptions_desc": {
+ "message": "显示或隐藏侧边栏中的用户脚本项目描述"
+ },
+ "settings_editor_list_sort": {
+ "message": "项目排序顺序"
+ },
+ "settings_editor_list_sort_desc": {
+ "message": "侧栏中项目的显示顺序"
+ },
+ "settings_editor_list_sort_nameAsc": {
+ "message": "项目名称: 升序"
+ },
+ "settings_editor_list_sort_nameDesc": {
+ "message": "项目名称: 降序"
+ },
+ "settings_editor_list_sort_lastModifiedAsc": {
+ "message": "最后修改: 升序"
+ },
+ "settings_editor_list_sort_lastModifiedDesc": {
+ "message": "最后修改: 降序"
+ },
+ "settings_editor_show_whitespace": {
+ "message": "显示空白字符"
+ },
+ "settings_editor_show_whitespace_desc": {
+ "message": "切换编辑器中不可见字符的显示"
+ },
+ "settings_editor_tab_size": {
+ "message": "制表符大小"
+ },
+ "settings_editor_tab_size_desc": {
+ "message": "选择渲染代码时制表符等于的空格数"
+ },
+ "settings_global_active": {
+ "message": "启用注入"
+ },
+ "settings_global_active_desc": {
+ "message": "全局脚本注入的开启或关闭"
+ },
+ "settings_global_exclude_match": {
+ "message": "全局排除匹配模式列表"
+ },
+ "settings_global_exclude_match_desc": {
+ "message": "此输入接受以空白符(空格、换行等)分隔的 @match 模式列表,与此列表中的模式匹配的页面 URL 将在脚本注入时被忽略"
+ },
+ "settings_global_exclude_match_saving": {
+ "message": "保存中..."
+ },
+ "settings_global_exclude_match_placeholder": {
+ "message": "@match 模式列表,例如:\n*://*/*foo.bar\n*://*/*foo.bar?*\n*://*.example.net/*\nhttps://example.net/*/foo/*/\nhttps://*.example.net/a/b/c/?foo=/"
+ },
+ "settings_global_exclude_match_refer": {
+ "message": "匹配模式结构请参考:"
+ },
+ "settings_global_scripts_update_check": {
+ "message": "全局脚本更新检查"
+ },
+ "settings_global_scripts_update_check_desc": {
+ "message": "是否开启全局定期脚本更新检查"
+ },
+ "settings_scripts_settings": {
+ "message": "脚本更新检查激活"
+ },
+ "settings_scripts_settings_desc": {
+ "message": "是否开启单个脚本更新检查"
+ },
+ "settings_scripts_update_automation": {
+ "message": "自动更新脚本"
+ },
+ "settings_scripts_update_automation_desc": {
+ "message": "脚本在后台静默自动更新,这是危险的,可能引入未经确认的恶意代码"
+ },
+ "settings_scripts_update_check_interval": {
+ "message": "脚本更新检查间隔"
+ },
+ "settings_scripts_update_check_interval_desc": {
+ "message": "脚本在后台检查更新的间隔时间(天)"
+ },
+ "settings_scripts_update_check_interval_0": {
+ "message": "从不"
+ },
+ "settings_scripts_update_check_lasttime": {
+ "message": "脚本更新上次检查时间"
+ },
+ "settings_scripts_update_check_lasttime_desc": {
+ "message": "后台脚本更新上次检查时间"
+ },
+ "settings_settings_sync": {
+ "message": "同步设置"
+ },
+ "settings_settings_sync_desc": {
+ "message": "跨设备同步设置"
+ },
+ "settings_toolbar_badge_count": {
+ "message": "工具栏图标显示计数徽章"
+ },
+ "settings_toolbar_badge_count_desc": {
+ "message": "在工具栏图标上显示一个徽章,其中的数字代表有多少个已启用的脚本与您所在页面的 URL 匹配"
+ },
+ "settings_scripts_directory": {
+ "message": "保存位置"
+ },
+ "settings_scripts_directory_desc": {
+ "message": "存储用户脚本的文件夹路径"
+ },
+ "settings_set_scripts_directory": {
+ "message": "更改保存位置"
+ },
+ "settings_about_text1": {
+ "message": "获取有关此扩展的更多信息,请访问本开源项目:"
+ },
+ "settings_about_text2": {
+ "message": "如果您喜欢使用此扩展,请考虑在 App Store 上留下您的评论或注册 Beta 测试新版本:"
+ },
+ "settings_about_button_repo": {
+ "message": "代码库"
+ },
+ "settings_about_button_docs": {
+ "message": "文档"
+ },
+ "settings_about_button_issues": {
+ "message": "报告错误"
+ },
+ "settings_about_button_store": {
+ "message": "在 App Store 中打开"
+ },
+ "settings_about_button_beta": {
+ "message": "注册 Beta 测试版"
+ },
+ "utils_check_match_patterns_1": {
+ "message": "这 scheme 部分应当为 *、https、http 之一"
+ },
+ "utils_check_match_patterns_2": {
+ "message": "这 scheme 和 host 部分应当用 `://` 分隔"
+ },
+ "utils_check_match_patterns_3": {
+ "message": "匹配模式缺少 path 部分(至少应当有`/`)"
+ },
+ "utils_check_match_patterns_4": {
+ "message": "这 `*.` 后面应当跟随主机名的一部分"
+ },
+ "utils_check_match_patterns_5": {
+ "message": "这 host 部分长度应当为 1-255"
+ },
+ "utils_check_match_patterns_6": {
+ "message": "这 `*` 应该是独立的或者为 `*.` 在开头"
+ },
+ "utils_check_match_patterns_7": {
+ "message": "这 host 部分包含一个或多个空标签"
+ },
+ "utils_check_match_patterns_8": {
+ "message": "主机名标签不能以 `-` 字符开头或结尾"
+ },
+ "utils_check_match_patterns_9": {
+ "message": "主机名标签最大长度不能超过 63"
+ },
+ "utils_check_match_patterns_10": {
+ "message": "这 host 部分包含无效字符:$1"
+ },
+ "utils_check_match_patterns_11": {
+ "message": "这 path 部分包含无效字符:$1"
+ },
+ "msg_invalid_match_pattern": {
+ "message": "无效的匹配模式"
+ }
+}
diff --git a/src/ext/shared/utils.js b/src/ext/shared/utils.js
index fae3e29e..1f401f7a 100644
--- a/src/ext/shared/utils.js
+++ b/src/ext/shared/utils.js
@@ -147,6 +147,184 @@ export function parseMetadata(text) {
return metadata;
}
+/**
+ * @param {string} input a match pattern
+ * @typedef {string} value - the match pattern
+ * @typedef {Object} parsedItem - parsed result
+ * @property {string} start - leading whitespace
+ * @property {string} separ - separator whitespace
+ * @property {value} value - the match pattern
+ * @property {boolean} error - the match pattern valid or not
+ * @property {string} point - invalid point or error message
+ * @returns {{error: boolean, items: parsedItem[], values: value[]}}
+ */
+export function parseMatchPatterns(input) {
+ if (typeof input !== "string") return;
+ const result = {
+ error: false,
+ items: [],
+ values: [],
+ };
+ // match the separated values from input string
+ const matches = input.matchAll(
+ /(?^\s*|)(?\S+?)(?\s+|$)/g,
+ );
+ for (const match of matches) {
+ const item = checkMatchPatterns(match.groups.value);
+ // setting the global error indicator
+ if (item.error === true) {
+ result.error = true;
+ }
+ result.items.push({ ...match.groups, ...item });
+ result.values.push(item.value.toLowerCase());
+ }
+ return result;
+}
+
+/**
+ * @param {string} input whitespace separated list of match patterns
+ * @typedef {Object} checkedItem - checked result
+ * @property {string} value - the match pattern with fixes
+ * @property {boolean} error - the match pattern valid or not
+ * @property {string} point - invalid point or error message
+ * @returns {checkedItem}
+ */
+export function checkMatchPatterns(input) {
+ if (typeof input !== "string") return;
+ const result = {
+ value: input,
+ error: true,
+ point: "",
+ };
+ if (input === "") {
+ result.error = false;
+ result.value = "*://*/*";
+ return result;
+ }
+ let scheme, host, path;
+ /** check scheme component */
+ if (input.slice(0, 5).toLowerCase() === "https") {
+ scheme = input.slice(0, 5);
+ } else if (input.slice(0, 4).toLowerCase() === "http") {
+ scheme = input.slice(0, 4);
+ } else if (input.startsWith("*")) {
+ scheme = "*";
+ } else {
+ // The scheme component should one of *, https, http
+ result.point = gl("utils_check_match_patterns_1");
+ return result;
+ }
+ /** check :// separator */
+ if (input.slice(scheme.length, scheme.length + 3) !== "://") {
+ // The scheme and host should separated by `://`
+ result.point = gl("utils_check_match_patterns_2");
+ return result;
+ }
+ // separate host and path
+ const array = input.slice(scheme.length + 3).split("/");
+ if (array.length < 2) {
+ // The match pattern has no path component
+ result.point = gl("utils_check_match_patterns_3");
+ return result;
+ }
+ host = array[0];
+ path = "/" + array.slice(1).join("/");
+ /** check host component */
+ if (host === "*.") {
+ // The `*.` should followed by part of the hostname
+ result.point = gl("utils_check_match_patterns_4");
+ return result;
+ }
+ // allow fully qualified domain name (FQDN)
+ if (host.at(-1) === ".") host = host.slice(0, -1);
+ if (host.length < 1 || 255 - 1 < host.length) {
+ // The host component length should be 1-255
+ result.point = gl("utils_check_match_patterns_5");
+ return result;
+ }
+ let labels = []; // domain labels
+ let hostPart = ""; // rest part that exclude wildcard
+ if (host.startsWith("*.")) {
+ hostPart = host.slice(2);
+ labels = hostPart.split(".");
+ } else if (host !== "*") {
+ hostPart = host;
+ labels = host.split(".");
+ }
+ if (hostPart.includes("*")) {
+ // The `*` should be independent or `*.` at the start
+ result.point = gl("utils_check_match_patterns_6");
+ return result;
+ }
+ for (const label of labels) {
+ if (label.length === 0) {
+ // The host component contains empty label(s)
+ result.point = gl("utils_check_match_patterns_7");
+ return result;
+ }
+ if (label.startsWith("-") || label.endsWith("-")) {
+ // The label cannot start or end with `-` character
+ result.point = gl("utils_check_match_patterns_8");
+ return result;
+ }
+ if (label.length > 63) {
+ // The maximum length of the label cannot exceed 63
+ result.point = gl("utils_check_match_patterns_9");
+ return result;
+ }
+ }
+ // allowed character set of host component
+ const hostInvalidMatches = hostPart.match(/[^A-Za-z0-9.-]/g);
+ if (hostInvalidMatches) {
+ const characters = hostInvalidMatches.join("");
+ // The host component contains invalid character(s): ${c}
+ result.point = gl("utils_check_match_patterns_10", characters);
+ return result;
+ }
+ /** check path component */
+ // allowed character set of path component
+ const pathInvalidMatches = path.match(
+ /[^\w\]\\!$%&'()*+,./:;=?@[^`{|}~-]/g, // toolsGetValidPathCharacters
+ );
+ if (pathInvalidMatches) {
+ const characters = pathInvalidMatches.join("");
+ // The path component contains invalid character(s): ${c}
+ result.point = gl("utils_check_match_patterns_11", characters);
+ return result;
+ }
+ result.error = false;
+ return result;
+}
+
+/** Generate valid path characters in browser js runtime */
+export function toolsGetValidPathCharacters() {
+ const set = new Set();
+ for (let i = 32; i < 128; i++) set.add(String.fromCharCode(i));
+ set.delete("?");
+ set.delete("#");
+ const p = [...set].join("");
+ set.delete("=");
+ set.delete("&");
+ const s = [...set].join("");
+ const url = new URL(`https://host/${p}?1=${s}&${s}=2`);
+ const possibles = [...url.pathname, ...url.search];
+ const characters = [...new Set(possibles)].sort().join("");
+ const symbols = characters.match(/[^\w]/g).join("");
+ return {
+ characters,
+ symbols,
+ restr: `/\\w${symbols.replace(/[\^[\]-]/g, "\\$&")}]/`,
+ };
+}
+
+/**
+ * get lang
+ * @param {string} n messageName
+ * @param {string | string[]} s substitutions
+ * const gl = browser.i18n.getMessage; // issue: safari return `undefined`
+ */
+export const gl = (n, s = undefined) => browser.i18n.getMessage(n, s);
+
export const validGrants = new Set([
"GM.info",
"GM_info",
From a388190638e2c0bd37212d631ff78c0b7617b7db Mon Sep 17 00:00:00 2001
From: ACTCD <101378590+ACTCD@users.noreply.github.com>
Date: Mon, 8 Jan 2024 18:55:00 +0800
Subject: [PATCH 11/24] refactor: introducing new settings component
---
README.md | 8 +-
.../extension-page/Components/Settings.svelte | 657 +++++++++++-------
src/ext/shared/Components/Toggle.svelte | 11 +-
xcode/App-Mac/AppDelegate.swift | 2 +
4 files changed, 413 insertions(+), 265 deletions(-)
diff --git a/README.md b/README.md
index 5864d9b8..a9051730 100644
--- a/README.md
+++ b/README.md
@@ -102,7 +102,7 @@ After installing Userscripts on macOS, you **do not** need to select a userscrip
- **Show Toolbar Count** - displays a badge on the toolbar icon with a number that represents how many enabled scripts match the url for the page you are on
- **Save Location** - where your file are currently located and being saved to (click the blue text to open location)
- **Change Save Location (cogs icon)** - this button, located directly to the right of the save location, is a shortcut for opening the host app, which will allow you to change the save location
-- **Global Blacklist** - this input accepts a comma separated list of [`@match` patterns](https://developer.chrome.com/docs/extensions/mv3/match_patterns/), a page url that matches against a pattern in this list will be ignored for script injection
+- **Global Blacklist** - this input accepts a comma separated list of `@match` patterns ([Match pattern structure](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns#match_pattern_structure)), a page url that matches against a pattern in this list will be ignored for script injection
### Popup:
@@ -125,7 +125,7 @@ Userscripts Safari currently supports the following userscript metadata:
- `@name` - This will be the name that displays in the sidebar and be used as the filename - you can _not_ use the same name for multiple files of the same type
- `@description`- Use this to describe what your userscript does - this will be displayed in the sidebar - there is a setting to hide descriptions
- `@icon` - This doesn't have a function with this userscript manager, but the **first value** provided in the metadata will be accessible in the `GM_/GM.info` object
-- `@match` - Domain match patterns - you can use several instances of this field if you'd like multiple domain matches - view [this article for more information on constructing patterns](https://developer.chrome.com/extensions/match_patterns)
+- `@match` - Domain match patterns - you can use several instances of this field if you'd like multiple domain matches - please refer to: [Match pattern structure](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns#match_pattern_structure)
- **Note:** this extension only supports `http/s`
- `@exclude-match` - Domain patterns where you do _not_ want the script to run
- `@include` - Used to match against urls for injection, globs and regular expressions are allowed, [read more here](https://wiki.greasespot.net/Include_and_exclude_rules)
@@ -325,9 +325,7 @@ The quickest and easiest way to support the project is by [leaving a positive re
The second best way to help out is to sign up to beta test new versions of the app. Since this extension values your privacy, and **does not collect any data from users**, it is difficult to gauge how the extension is being used. By signing up to be a beta tester it not only allows you to test upcoming features, but also gives me the opportunity to elicit direct feedback from real users.
-**[iOS Beta Sign Up Form](https://forms.gle/QB46uYQHVyCxULue9)**
-
-**[macOS Beta Sign Up Form](https://forms.gle/cUDtKg1ip4Vc9Xhc7)**
+**Please join and test the corresponding beta version in [releases](https://github.com/quoid/userscripts/releases) via the TestFlight public link.**
## Privacy Policy
diff --git a/src/ext/extension-page/Components/Settings.svelte b/src/ext/extension-page/Components/Settings.svelte
index 0d759d11..51cb25ea 100644
--- a/src/ext/extension-page/Components/Settings.svelte
+++ b/src/ext/extension-page/Components/Settings.svelte
@@ -1,341 +1,477 @@
-
-
-
-
state.remove("settings")}>
-
-
-
-
Editor Settings
-
state.remove("settings")}
- />
-
-
-
Auto Close Brackets
-
- update("autoCloseBrackets", !$settings.autoCloseBrackets)}
- />
-
-
-
Auto Hint
-
update("autoHint", !$settings.autoHint)}
- />
-
-
-
Hide Descriptions
-
update("descriptions", !$settings.descriptions)}
- />
-
-
-
Javascript Linter
-
update("lint", !$settings.lint)}
- />
-
-
-
Show Invisibles
-
update("showInvisibles", !$settings.showInvisibles)}
- />
+
+ {#each groups as group}
+
+
+
{gl(`settings_section_${group}`)}
-
-
Tab Size
-
-
-
-
-
-
-
Enable Injection
-
update("active", !$settings.active)}
- />
-
-
-
Show Toolbar Count
-
update("showCount", !$settings.showCount)}
- />
-
-
-
Save Location
-
-
-
-
-
-
Global exclude match patterns
-
- {#if blacklistSaving}{@html iconLoader}{/if}
+ {#each groupItems(group) as item}
+
+
+ {gl(`settings_${item.name}`)}
+
+
+ {#if item.nodeType === "textarea" && item.name === "global_exclude_match"}
+ {#if indicators.saving[item.name]}
+
+
{@html iconLoader}
+ {gl(`settings_${item.name}_saving`)}
+ {/if}
+
+
+
+ {#each gemParsed.items as p}
+
+ {p.start}{#if p.error}{p.value}{:else}{p.value}{/if}{p.separ}
+ {/each}
+
+
+
+ {gl(`settings_${item.name}_refer`)}
+
+
+ {/if}
+
+ {gl(`settings_${item.name}_desc`)}
+
-
-
+ {/each}
+
+ {/each}
+
+
+
{gl(`settings_section_native`)}
-
-
Information
-
- Userscripts Safari Version {$settings.version} ({$settings.build})
-
- You can review the documentation, report bugs and get more information about
- this extension by visiting
-
-
- If you enjoy using this extension, please consider
-
- on the App Store or
-
-
+
+
{gl("settings_scripts_directory")}
+ {#if indicators.loading.changeSaveLocation}
+
+
{@html iconLoader}
+ {/if}
+
+
+
{gl("settings_scripts_directory_desc")}
+
+
{gl(`settings_section_about`)}
+
+ Userscripts {import.meta.env.BROWSER ?? ""}
+ v{$settings["version"]}
+ ({$settings["build"]})
+
+ {gl("settings_about_text1")}
+
+
+
+
+ {gl("settings_about_text2")}
+
+
+
+
diff --git a/src/ext/shared/Components/Toggle.svelte b/src/ext/shared/Components/Toggle.svelte
index a724fecb..40044dc6 100644
--- a/src/ext/shared/Components/Toggle.svelte
+++ b/src/ext/shared/Components/Toggle.svelte
@@ -1,7 +1,7 @@
@@ -18,7 +18,7 @@
cursor: pointer;
display: block;
- font-size: 1rem;
+ font-size: 1.1rem;
height: 1em;
min-width: 1.75em;
position: relative;
@@ -26,6 +26,13 @@
width: 1.75em;
}
+ /* ios */
+ @supports (-webkit-touch-callout: none) {
+ label {
+ font-size: 1.8rem;
+ }
+ }
+
label.disabled {
cursor: default;
}
diff --git a/xcode/App-Mac/AppDelegate.swift b/xcode/App-Mac/AppDelegate.swift
index cb1a3837..c5df851c 100644
--- a/xcode/App-Mac/AppDelegate.swift
+++ b/xcode/App-Mac/AppDelegate.swift
@@ -11,6 +11,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet weak var enbaleNativeLogger: NSMenuItem!
@objc func handleGetURL(event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor) {
+ // issue: https://developer.apple.com/forums/thread/697217
+// sendExtensionMessage(name: "URL_SCHEME_STARTED")
// if open panel is already open, stop processing the URL scheme
if NSApplication.shared.keyWindow?.accessibilityIdentifier() == "open-panel" { return }
// handle URL scheme
From b1797e8df3d8efca20beb1b67649b7c50e1e6d33 Mon Sep 17 00:00:00 2001
From: ACTCD <101378590+ACTCD@users.noreply.github.com>
Date: Fri, 12 Jan 2024 04:18:00 +0800
Subject: [PATCH 12/24] refactor: migrating legacy settings storage references
---
.../Components/Editor/CodeMirror.svelte | 27 +++++++------
.../extension-page/Components/Settings.svelte | 7 +---
.../Components/Sidebar/Sidebar.svelte | 10 +++--
.../Components/Sidebar/SidebarFilter.svelte | 2 +-
src/ext/extension-page/store.js | 28 +++++---------
src/ext/shared/settings.js | 38 -------------------
6 files changed, 35 insertions(+), 77 deletions(-)
diff --git a/src/ext/extension-page/Components/Editor/CodeMirror.svelte b/src/ext/extension-page/Components/Editor/CodeMirror.svelte
index f2d2d13b..2eebb3d4 100644
--- a/src/ext/extension-page/Components/Editor/CodeMirror.svelte
+++ b/src/ext/extension-page/Components/Editor/CodeMirror.svelte
@@ -65,10 +65,13 @@
// update settings when changed
$: if (instance) {
- instance.setOption("autoCloseBrackets", $settings.autoCloseBrackets);
- instance.setOption("showInvisibles", $settings.showInvisibles);
- instance.setOption("tabSize", parseInt($settings.tabSize, 10));
- instance.setOption("indentUnit", parseInt($settings.tabSize, 10));
+ instance.setOption("autoCloseBrackets", $settings["editor_close_brackets"]);
+ instance.setOption("showInvisibles", $settings["editor_show_whitespace"]);
+ instance.setOption("tabSize", parseInt($settings["editor_tab_size"], 10));
+ instance.setOption(
+ "indentUnit",
+ parseInt($settings["editor_tab_size"], 10),
+ );
}
// store cursor position and disable on save
@@ -100,20 +103,20 @@
}
// track lint settings and update accordingly
- $: if (instance && $settings.lint) {
+ $: if (instance && $settings["editor_javascript_lint"]) {
toggleLint("enable");
- } else if (instance && !$settings.lint) {
+ } else if (instance && !$settings["editor_javascript_lint"]) {
toggleLint("disable");
}
export function init() {
// do lint settings check
- const lint = $settings.lint ? lintOptions : false;
+ const lint = $settings["editor_javascript_lint"] ? lintOptions : false;
// create codemirror instance
instance = CodeMirror.fromTextArea(textarea, {
mode: "javascript",
- autoCloseBrackets: $settings.autoCloseBrackets,
+ autoCloseBrackets: $settings["editor_close_brackets"],
continueComments: true,
foldGutter: true,
lineNumbers: true,
@@ -121,9 +124,9 @@
matchBrackets: true,
smartIndent: true,
styleActiveLine: true,
- indentUnit: parseInt($settings.tabSize, 10),
- showInvisibles: $settings.showInvisibles,
- tabSize: parseInt($settings.tabSize, 10),
+ indentUnit: parseInt($settings["editor_tab_size"], 10),
+ showInvisibles: $settings["editor_show_whitespace"],
+ tabSize: parseInt($settings["editor_tab_size"], 10),
highlightSelectionMatches: false,
lint,
hintOptions: {
@@ -192,7 +195,7 @@
});
if (
// check if setting is enabled
- $settings.autoHint &&
+ $settings["editor_auto_hint"] &&
// ensure hinting not active already
!cm.state.completionActive &&
// not first position on the line
diff --git a/src/ext/extension-page/Components/Settings.svelte b/src/ext/extension-page/Components/Settings.svelte
index 51cb25ea..4f357697 100644
--- a/src/ext/extension-page/Components/Settings.svelte
+++ b/src/ext/extension-page/Components/Settings.svelte
@@ -176,12 +176,9 @@
{/if}
{#if item.nodeType === "select"}