-
{#each $notifications as item (item.id)}
No Item Selected
{/if}
+ {#if sidebarHidden}
+
+
+
+ {/if}
{name}
-
+
{#if $v4state.includes("saving")}
Saving...
{:else if $v4state.includes("trashing")}
@@ -233,6 +246,7 @@
flex-grow: 1;
overflow: hidden;
position: relative;
+ width: calc(100svw - var(--sidebar-width));
}
.info {
@@ -241,26 +255,28 @@
}
.editor__empty {
- align-items: center;
background-color: inherit;
color: var(--text-color-disabled);
- display: flex;
font: var(--text-large);
- height: 100%;
- justify-content: center;
- left: 0;
letter-spacing: var(--letter-spacing-large);
+ display: flex;
+ place-content: center;
+ place-items: center;
position: absolute;
- top: 0;
- width: 100%;
- z-index: 85;
+ inset: 0;
+ z-index: 10;
}
.editor__header {
align-items: center;
display: flex;
flex-shrink: 0;
- padding: 1rem 0.5rem 1rem 1.5rem;
+ padding: 1rem;
+ gap: 1rem;
+ }
+
+ .sidebar-switch {
+ scale: 1.5;
}
.editor__header__content {
diff --git a/src/ext/extension-page/Components/Sidebar/Sidebar.svelte b/src/ext/extension-page/Components/Sidebar/Sidebar.svelte
index cbc62445..c5af6b55 100644
--- a/src/ext/extension-page/Components/Sidebar/Sidebar.svelte
+++ b/src/ext/extension-page/Components/Sidebar/Sidebar.svelte
@@ -11,6 +11,7 @@
import Loader from "@shared/Components/Loader.svelte";
import iconPlus from "@shared/img/icon-plus.svg?raw";
import iconSettings from "@shared/img/icon-settings.svg?raw";
+ import iconSidebar from "@shared/img/icon-sidebar.svg?raw";
import {
cmChanged,
cmGetInstance,
@@ -18,6 +19,8 @@
cmSetSessionCode,
} from "../Editor/CodeMirror.svelte";
+ export let sidebarSwitch;
+
// disable buttons accordingly
$: disabled = !$v4state.includes("ready");
@@ -30,6 +33,8 @@
$: if (list.find((a) => a.active)) {
const active = list.find((a) => a.active);
scrollToEl(active.filename);
+ } else {
+ activate(list[0]);
}
/**
@@ -76,8 +81,8 @@
}
async function activate(item) {
- // if not in ready state or the item is already active
- if (!$v4state.includes("ready") || item.active) return;
+ // if item is empty or already active
+ if (!item || item.active) return;
// check if there's a temp item and it's not the item to be activated
// can occur when user clicks a non-temp item while a temp item exists
@@ -204,11 +209,17 @@
+
{#if description}
-
+
+
+
+
+
+
+
diff --git a/src/shared/Components/LogoText.svelte b/src/shared/Components/LogoText.svelte
index 849b023d..e5ad9068 100644
--- a/src/shared/Components/LogoText.svelte
+++ b/src/shared/Components/LogoText.svelte
@@ -1,8 +1,5 @@
-
-
\ No newline at end of file
diff --git a/src/shared/img/logo-icon.png b/src/shared/img/logo-icon.png
deleted file mode 100644
index 9fe96182..00000000
Binary files a/src/shared/img/logo-icon.png and /dev/null differ
diff --git a/src/shared/variables.css b/src/shared/variables.css
index 225c2ae6..f014eba2 100644
--- a/src/shared/variables.css
+++ b/src/shared/variables.css
@@ -24,80 +24,52 @@
--editor-matched-highlight: rgb(116 178 235 / 0.2);
--editor-search-highlight: rgb(255 166 0 / 0.3);
- /* Dark theme (default) */
- --color-bg-primary: #323639;
- --color-bg-secondary: #2f3337;
- --color-bg-theme: var(--color-black);
- --color-script-highlighted: #364049;
- --color-black: #1d2023;
- --color-white: #fff;
- --color-blue: #74b1eb;
- --color-green: #60f36c;
- --color-grey: rgb(255 255 255 / 0.15);
- --color-red: #ff453a;
- --color-yellow: #e4f360;
- --text-color-primary: rgb(255 255 255);
- --text-color-secondary: rgb(255 255 255 / 0.65);
- --text-color-disabled: rgb(255 255 255 / 0.4);
- --box-shadow: 0 0.5rem 1rem 0 rgb(0 0 0 / 0.25);
- --button-color: var(--color-black);
- --border-color: var(--color-black);
+ /* Color Theme */
+ color-scheme: light dark;
+ --color-bg-primary: light-dark(#fafafa, #323639);
+ --color-bg-secondary: light-dark(#f1f1f1, #2f3337);
+ --color-bg-theme: light-dark(var(--color-white), var(--color-black));
+ --color-script-highlighted: light-dark(#e2ebf3, #364049);
+ --color-black: light-dark(#333, #1d2023);
+ --color-white: light-dark(#fff, #fff);
+ --color-blue: light-dark(#317eff, #74b1eb);
+ --color-green: light-dark(#2bb239, #60f36c);
+ --color-grey: light-dark(rgb(51 51 51 / 0.15), rgb(255 255 255 / 0.15));
+ --color-red: light-dark(#ff453a, #ff453a);
+ --color-yellow: light-dark(#b8722c, #e4f360);
+ --text-color-primary: light-dark(rgb(51 51 51), rgb(255 255 255));
+ --text-color-secondary: light-dark(
+ rgb(51 51 51 / 0.65),
+ rgb(255 255 255 / 0.65)
+ );
+ --text-color-disabled: light-dark(
+ rgb(51 51 51 / 0.35),
+ rgb(255 255 255 / 0.4)
+ );
+ --box-shadow: 0 0.5rem 1rem 0 light-dark(rgb(0 0 0 / 0.15), rgb(0 0 0 / 0.25));
+ --button-color: light-dark(var(--color-white), var(--color-black));
+ --border-color: light-dark(var(--color-grey), var(--color-black));
- /* Editor dark theme colors */
- --editor-invisible: rgb(255 255 255 / 0.15);
- --editor-number: #77e26a;
- --editor-comment: rgb(255 255 255 / 0.35);
- --editor-def: #efc371;
- --editor-default: #cdcfd1;
- --editor-keyword: #96c3ed;
- --editor-atom: #59ebf5;
- --editor-operator: #8c99a7;
- --editor-property: #e86c8a;
- --editor-string: #f5eea2;
- --editor-string-2: #cdabff;
- --editor-cursor: #e3e7eb;
- --editor-matching-bracket-color: #fff;
- --editor-matching-bracket-border: var(--editor-number);
- --editor-blurred-background: rgb(50 54 57 / 0.65);
-}
-
-/* Light theme */
-@media (prefers-color-scheme: light) {
- :root {
- color-scheme: light dark;
- --color-bg-primary: #fafafa;
- --color-bg-secondary: #f1f1f1;
- --color-bg-theme: var(--color-white);
- --color-script-highlighted: #e2ebf3;
- --color-black: #333;
- --color-white: #fff;
- --color-blue: #317eff;
- --color-green: #2bb239;
- --color-grey: rgb(51 51 51 / 0.15);
- --color-red: #ff453a;
- --color-yellow: #b8722c;
- --text-color-primary: rgb(51 51 51);
- --text-color-secondary: rgb(51 51 51 / 0.65);
- --text-color-disabled: rgb(51 51 51 / 0.35);
- --box-shadow: 0 0.25rem 1rem 0 rgb(0 0 0 / 0.15);
- --button-color: var(--color-white);
- --border-color: var(--color-grey);
-
- /* Editor light theme colors */
- --editor-invisible: rgb(51 51 51 / 0.15);
- --editor-number: var(--color-green);
- --editor-comment: var(--text-color-disabled);
- --editor-def: #b86d17;
- --editor-default: #777;
- --editor-keyword: #3a68ff;
- --editor-atom: #00adad;
- --editor-operator: #8c99a7;
- --editor-property: #ff453a;
- --editor-string: var(--editor-atom);
- --editor-string-2: #a34fe6;
- --editor-cursor: #7e7e7f;
- --editor-matching-bracket-color: var(--text-color-primary);
- --editor-matching-bracket-border: #ff00ea;
- --editor-blurred-background: rgb(255 255 255 / 0.65);
- }
+ /* Editor colors */
+ --editor-invisible: light-dark(rgb(51 51 51 / 0.15), rgb(255 255 255 / 0.15));
+ --editor-number: light-dark(var(--color-green), #77e26a);
+ --editor-comment: light-dark(
+ var(--text-color-disabled),
+ rgb(255 255 255 / 0.35)
+ );
+ --editor-def: light-dark(#b86d17, #efc371);
+ --editor-default: light-dark(#777, #cdcfd1);
+ --editor-keyword: light-dark(#3a68ff, #96c3ed);
+ --editor-atom: light-dark(#00adad, #59ebf5);
+ --editor-operator: light-dark(#8c99a7, #8c99a7);
+ --editor-property: light-dark(#ff453a, #e86c8a);
+ --editor-string: light-dark(var(--editor-atom), #f5eea2);
+ --editor-string-2: light-dark(#a34fe6, #cdabff);
+ --editor-cursor: light-dark(#7e7e7f, #e3e7eb);
+ --editor-matching-bracket-color: light-dark(var(--text-color-primary), #fff);
+ --editor-matching-bracket-border: light-dark(#ff00ea, var(--editor-number));
+ --editor-blurred-background: light-dark(
+ rgb(255 255 255 / 0.65),
+ rgb(50 54 57 / 0.65)
+ );
}
diff --git a/xcode/App-Mac/ViewController.swift b/xcode/App-Mac/ViewController.swift
index fc7598ee..e09d909c 100644
--- a/xcode/App-Mac/ViewController.swift
+++ b/xcode/App-Mac/ViewController.swift
@@ -36,7 +36,7 @@ class ViewController: NSViewController {
guard let state = state else {
self.enabledText.stringValue = "Safari Extension State Unknown"
if let error = error {
- logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)")
} else {
logger?.error("\(#function, privacy: .public) - couldn't get safari extension state in containing app")
}
diff --git a/xcode/App-Shared/AppIcon.icon/Assets/icon.svg b/xcode/App-Shared/AppIcon.icon/Assets/icon.svg
new file mode 100644
index 00000000..f19d064d
--- /dev/null
+++ b/xcode/App-Shared/AppIcon.icon/Assets/icon.svg
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/xcode/App-Shared/AppIcon.icon/icon.json b/xcode/App-Shared/AppIcon.icon/icon.json
new file mode 100644
index 00000000..a5558deb
--- /dev/null
+++ b/xcode/App-Shared/AppIcon.icon/icon.json
@@ -0,0 +1,94 @@
+{
+ "fill-specializations" : [
+ {
+ "value" : "automatic"
+ },
+ {
+ "appearance" : "dark",
+ "value" : "automatic"
+ }
+ ],
+ "groups" : [
+ {
+ "blend-mode-specializations" : [
+ {
+ "value" : "plus-darker"
+ },
+ {
+ "appearance" : "dark",
+ "value" : "plus-lighter"
+ },
+ {
+ "appearance" : "tinted",
+ "value" : "normal"
+ }
+ ],
+ "blur-material" : 0.5,
+ "hidden" : false,
+ "layers" : [
+ {
+ "blend-mode" : "normal",
+ "fill-specializations" : [
+ {
+ "value" : "automatic"
+ },
+ {
+ "appearance" : "dark",
+ "value" : {
+ "linear-gradient" : [
+ "extended-gray:1.00000,0.80000",
+ "extended-gray:1.00000,0.70000"
+ ]
+ }
+ },
+ {
+ "appearance" : "tinted",
+ "value" : {
+ "linear-gradient" : [
+ "extended-gray:1.00000,0.80000",
+ "extended-gray:1.00000,0.70000"
+ ]
+ }
+ }
+ ],
+ "glass" : true,
+ "hidden" : false,
+ "image-name" : "icon.svg",
+ "name" : "icon",
+ "opacity" : 1
+ }
+ ],
+ "lighting" : "individual",
+ "position" : {
+ "scale" : 1,
+ "translation-in-points" : [
+ 0,
+ 0
+ ]
+ },
+ "shadow-specializations" : [
+ {
+ "value" : {
+ "kind" : "neutral",
+ "opacity" : 0.5
+ }
+ },
+ {
+ "appearance" : "light",
+ "value" : {
+ "kind" : "layer-color",
+ "opacity" : 0.5
+ }
+ }
+ ],
+ "specular" : true,
+ "translucency" : {
+ "enabled" : true,
+ "value" : 0.2
+ }
+ }
+ ],
+ "supported-platforms" : {
+ "squares" : "shared"
+ }
+}
\ No newline at end of file
diff --git a/xcode/App-Shared/ViewController.swift b/xcode/App-Shared/ViewController.swift
index db573177..83e0040d 100644
--- a/xcode/App-Shared/ViewController.swift
+++ b/xcode/App-Shared/ViewController.swift
@@ -149,7 +149,7 @@ class ViewController: PlatformViewController, WKNavigationDelegate, WKScriptMess
self.extensionState = state.isEnabled ? .on : .off
} catch {
self.extensionState = .error
- logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)")
}
if #available(macOS 13, *) {
useSettingsInsteadOfPreferences = true
@@ -193,7 +193,7 @@ class ViewController: PlatformViewController, WKNavigationDelegate, WKScriptMess
do {
try await SFSafariApplication.showPreferencesForExtension(withIdentifier: extIdentifier)
} catch {
- logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)")
return (nil, error.localizedDescription)
}
break
@@ -344,7 +344,7 @@ extension ViewController {
} else {
status = .error
if let error = error {
- logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)")
} else {
logger?.error("\(#function, privacy: .public) - couldn't get safari extension state in containing app")
}
diff --git a/xcode/Ext-Safari/Functions.swift b/xcode/Ext-Safari/Functions.swift
index 77431ac9..220a380a 100644
--- a/xcode/Ext-Safari/Functions.swift
+++ b/xcode/Ext-Safari/Functions.swift
@@ -59,10 +59,12 @@ func validateUrl(_ urlString: String) -> Bool {
logger?.error("\(#function, privacy: .public) - Invalid URL: \(urlString, privacy: .public)")
return false
}
- if
- (ptcl != "https:" && ptcl != "http:")
- || (!path.hasSuffix(".css") && !path.hasSuffix(".js"))
- {
+ if ptcl != "https:" && ptcl != "http:" {
+ logger?.error("\(#function, privacy: .public) - Invalid URL protocol: \(urlString, privacy: .public)")
+ return false
+ }
+ if !path.hasSuffix(".css") && !path.hasSuffix(".js") {
+ logger?.error("\(#function, privacy: .public) - Invalid URL suffix: \(urlString, privacy: .public)")
return false
}
return true
@@ -92,16 +94,20 @@ func isEncoded(_ str: String) -> Bool {
return false
}
+// parser REs - force try b/c pattern is known to be valid regex
+let re0 = try! NSRegularExpression(pattern: #"(?:(\/\/ ==UserScript==[ \t]*?\r?\n([\S\s]*?)\r?\n\/\/ ==\/UserScript==)([\S\s]*)|(\/\* ==UserStyle==[ \t]*?\r?\n([\S\s]*?)\r?\n==\/UserStyle== \*\/)([\S\s]*))"#, options: [])
+// RE for meta line
+let re1 = try! NSRegularExpression(pattern: #"^(?:[ \t]*(?:\/\/)?[ \t]*@)([\w-]+)[ \t]+([^\s]+[^\r\n\t\v\f]*)"#, options: [])
+// this pattern checks for specific keys that won't have values
+let re2 = try! NSRegularExpression(pattern: #"^(?:[ \t]*(?:\/\/)?[ \t]*@)(noframes)[ \t]*$"#, options: [])
+
// parser
func parse(_ content: String) -> [String: Any]? {
// returns structured data from content of file
// will fail to parse if metablock or required @name key missing
- let pattern = #"(?:(\/\/ ==UserScript==[ \t]*?\r?\n([\S\s]*?)\r?\n\/\/ ==\/UserScript==)([\S\s]*)|(\/\* ==UserStyle==[ \t]*?\r?\n([\S\s]*?)\r?\n==\/UserStyle== \*\/)([\S\s]*))"#
- // force try b/c pattern is known to be valid regex
- let regex = try! NSRegularExpression(pattern: pattern, options: [])
let range = NSRange(location: 0, length: content.utf16.count)
// return nil/fail if metablock missing
- guard let match = regex.firstMatch(in: content, options: [], range: range) else {
+ guard let match = re0.firstMatch(in: content, options: [], range: range) else {
logger?.debug("\(#function, privacy: .public) - Non matched content: \(content, privacy: .public)")
return nil
}
@@ -136,17 +142,11 @@ func parse(_ content: String) -> [String: Any]? {
let metaArray = content[metas].split(whereSeparator: \.isNewline)
// loop through metadata lines and populate metadata dictionary
for meta in metaArray {
- let p = #"^(?:[ \t]*(?:\/\/)?[ \t]*@)([\w-]+)[ \t]+([^\s]+[^\r\n\t\v\f]*)"#
- // this pattern checks for specific keys that won't have values
- let p2 = #"^(?:[ \t]*(?:\/\/)?[ \t]*@)(noframes)[ \t]*$"#
// the individual meta string, ie. // @name File Name
let metaString = String(meta).trimmingCharacters(in: .whitespaces)
- // force try b/c pattern is known to be valid regex
- let re = try! NSRegularExpression(pattern: p, options: [])
- let re2 = try! NSRegularExpression(pattern: p2, options: [])
let range = NSRange(location: 0, length: metaString.utf16.count)
// key lines not properly prefixed & without values will be skipped
- if let m = re.firstMatch(in: metaString, options: [], range: range) {
+ if let m = re1.firstMatch(in: metaString, options: [], range: range) {
// force unwrap key & value since matches regex above
let key = metaString[Range(m.range(at: 1), in: metaString)!]
let value = metaString[Range(m.range(at: 2), in: metaString)!]
@@ -218,7 +218,7 @@ func updateManifest(with data: Manifest) -> Bool {
try fileContent.write(to: url, atomically: false, encoding: .utf8)
return true
} catch {
- logger?.error("\(#function, privacy: .public) - failed to update manifest: \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed to update manifest: \(error, privacy: .public)")
return false
}
}
@@ -740,7 +740,7 @@ func getRequiredCode(_ filename: String, _ resources: [String], _ fileType: Stri
do {
try FileManager.default.removeItem(at: directory)
} catch {
- logger?.error("\(#function, privacy: .public) - failed to remove directory: \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed to remove directory: \(error, privacy: .public)")
}
}
return true
@@ -794,11 +794,11 @@ func getRequiredCode(_ filename: String, _ resources: [String], _ fileType: Stri
try FileManager.default.removeItem(at: abandonFileUrl)
logger?.info("\(#function, privacy: .public) - cleanup abandoned resource: \(unsanitize(abandonFileUrl.lastPathComponent), privacy: .public)")
} catch {
- logger?.error("\(#function, privacy: .public) - failed to remove abandoned resource: \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed to remove abandoned resource: \(error, privacy: .public)")
}
}
} catch {
- logger?.error("\(#function, privacy: .public) - failed to cleanup resources: \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed to cleanup resources: \(error, privacy: .public)")
}
return true
}
@@ -829,7 +829,7 @@ func checkForRemoteUpdates(_ optionalFilesArray: [[String: Any]] = []) -> [[Stri
let currentVersion = metadata["version"]![0]
let updateUrl = metadata["updateURL"]![0]
// before fetching remote contents, ensure it points to a file of the same type
- if !updateUrl.hasSuffix(type) {continue}
+ if !validateUrl(updateUrl) {continue}
guard
let remoteFileContents = getRemoteFileContents(updateUrl),
let remoteFileContentsParsed = parse(remoteFileContents),
@@ -875,7 +875,7 @@ func getRemoteFileContents(_ url: String) -> String? {
}
}
if let error = error {
- logger?.error("\(#function, privacy: .public) - task error: \(error.localizedDescription, privacy: .public) (\(url, privacy: .public))")
+ logger?.error("\(#function, privacy: .public) - task error: \(error, privacy: .public) (\(url, privacy: .public))")
}
semaphore.signal()
}
@@ -982,7 +982,7 @@ func checkDefaultDirectories() -> Bool {
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: false)
} catch {
// could not create the save location directory, show error
- logger?.error("\(#function, privacy: .public) - failed at (1) - \(url, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - failed at (1) - \(url, privacy: .public) - \(error, privacy: .public)")
return false
}
}
@@ -1001,6 +1001,9 @@ func stringToRegex(_ stringPattern: String) -> NSRegularExpression? {
return regex
}
+// RE for parse matchPattern
+let re3 = try! NSRegularExpression(pattern: #"^(http:|https:|\*:)\/\/((?:\*\.)?(?:[a-z0-9-]+\.)+(?:[a-z0-9]+)|\*\.[a-z]+|\*|[a-z0-9]+)(\/[^\s]*)$"#, options: .caseInsensitive)
+
func match(_ url: String, _ matchPattern: String) -> Bool {
guard
let parts = jsLikeURL(url),
@@ -1026,10 +1029,8 @@ func match(_ url: String, _ matchPattern: String) -> Bool {
if (ptcl != "http:" && ptcl != "https:") {
return false
}
- let partsPattern = #"^(http:|https:|\*:)\/\/((?:\*\.)?(?:[a-z0-9-]+\.)+(?:[a-z0-9]+)|\*\.[a-z]+|\*|[a-z0-9]+)(\/[^\s]*)$"#
- let partsPatternReg = try! NSRegularExpression(pattern: partsPattern, options: .caseInsensitive)
let range = NSMakeRange(0, matchPattern.utf16.count)
- guard let parts = partsPatternReg.firstMatch(in: matchPattern, options: [], range: range) else {
+ guard let parts = re3.firstMatch(in: matchPattern, options: [], range: range) else {
logger?.error("\(#function, privacy: .public) - malformed regex match pattern - \(matchPattern, privacy: .public)")
return false
}
@@ -1761,7 +1762,7 @@ func trashFile(_ item: [String: Any]) -> Bool {
do {
try FileManager.default.trashItem(at: url, resultingItemURL: nil)
} catch {
- logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)")
return false
}
}
@@ -1812,7 +1813,7 @@ func getFileRemoteUpdate(_ content: String) -> [String: String] {
return ["error": "Update failed, couldn't parse remote file contents"]
}
// check if update is needed
- if version >= remoteVersion {
+ if !isVersionNewer(version, remoteVersion) {
return ["info": "No updates found"]
}
// at this point it is known an update is available, get new code from downloadURL
diff --git a/xcode/Shared/Preferences.swift b/xcode/Shared/Preferences.swift
index d26e10ed..20f0b60e 100644
--- a/xcode/Shared/Preferences.swift
+++ b/xcode/Shared/Preferences.swift
@@ -60,7 +60,7 @@ private struct SecurityScopedBookmark {
relativeTo: nil
)
} catch {
- logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)")
}
return nil
}
@@ -74,7 +74,7 @@ private struct SecurityScopedBookmark {
bookmarkDataIsStale: &isStale
)
} catch {
- logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)")
}
return nil
}
@@ -202,7 +202,7 @@ private struct SecurityScopedBookmark {
do {
return try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil)
} catch {
- logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)")
}
return nil
}
@@ -211,7 +211,7 @@ private struct SecurityScopedBookmark {
do {
return try URL(resolvingBookmarkData: data, bookmarkDataIsStale: &isStale)
} catch {
- logger?.error("\(#function, privacy: .public) - \(error.localizedDescription, privacy: .public)")
+ logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)")
}
return nil
}
@@ -237,7 +237,9 @@ private struct SecurityScopedBookmark {
}
var isStale = false
guard let url = resolvBookmark(data, &isStale) else { // get security-scoped URL
- SDefaults?.removeObject(forKey: key) // remove data that cannot be resolved
+ // Note: temporary mitigation measures for occasional failures on some iOS devices
+// SDefaults?.removeObject(forKey: key) // remove data that cannot be resolved
+ logger?.warning("\(#function, privacy: .public) - faild resolv bookmark, return default")
return defaultValue
}
if isStale, url.startAccessingSecurityScopedResource() { // renew URL bookmark
diff --git a/xcode/Userscripts.xcodeproj/project.pbxproj b/xcode/Userscripts.xcodeproj/project.pbxproj
index 10708e48..8708c6b7 100644
--- a/xcode/Userscripts.xcodeproj/project.pbxproj
+++ b/xcode/Userscripts.xcodeproj/project.pbxproj
@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
+ 007596BF2E10B67F008F8C04 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 007596BE2E10B67F008F8C04 /* AppIcon.icon */; };
+ 007596C02E10B67F008F8C04 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 007596BE2E10B67F008F8C04 /* AppIcon.icon */; };
030C64F62AC62CC900548FBD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 030C64F52AC62CC900548FBD /* Settings.bundle */; };
030C64FC2AD0B24C00548FBD /* Initialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030C64FB2AD0B24C00548FBD /* Initialization.swift */; };
030FCAD42B182008004D13CD /* UrlCodecTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030FCAD32B182008004D13CD /* UrlCodecTests.swift */; };
@@ -112,6 +114,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 007596BE2E10B67F008F8C04 /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = ""; };
030C64F52AC62CC900548FBD /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; };
030C64FB2AD0B24C00548FBD /* Initialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Initialization.swift; sourceTree = ""; };
030FCAD32B182008004D13CD /* UrlCodecTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlCodecTests.swift; sourceTree = ""; };
@@ -204,6 +207,7 @@
isa = PBXGroup;
children = (
039F59512AD95485002E9977 /* Resources */,
+ 007596BE2E10B67F008F8C04 /* AppIcon.icon */,
03D405482CC66DC90091FE6F /* Shared.xcassets */,
4A4CF6E9270A38BD00111584 /* ViewController.swift */,
039F59552ADA2FE2002E9977 /* USchemeHandler.swift */,
@@ -501,6 +505,7 @@
4A4CF6F0270A38BD00111584 /* Main.storyboard in Resources */,
4A4CF6ED270A38BD00111584 /* LaunchScreen.storyboard in Resources */,
03D4054B2CC66DC90091FE6F /* Shared.xcassets in Resources */,
+ 007596BF2E10B67F008F8C04 /* AppIcon.icon in Resources */,
030C64F62AC62CC900548FBD /* Settings.bundle in Resources */,
4A4CF710270A38BF00111584 /* Assets.xcassets in Resources */,
039F59532AD9591C002E9977 /* dist in Resources */,
@@ -523,6 +528,7 @@
files = (
4A57B9F9227235CE008A9763 /* Assets.xcassets in Resources */,
032CBD3C2CBCF19A00B31D5E /* dist in Resources */,
+ 007596C02E10B67F008F8C04 /* AppIcon.icon in Resources */,
03D4054D2CC66DC90091FE6F /* Shared.xcassets in Resources */,
4A57B9F5227235CD008A9763 /* Main.storyboard in Resources */,
);
diff --git a/xcode/xcconfig/Userscripts-Release.xcconfig b/xcode/xcconfig/Userscripts-Release.xcconfig
index 886f5a47..8c52d66b 100644
--- a/xcode/xcconfig/Userscripts-Release.xcconfig
+++ b/xcode/xcconfig/Userscripts-Release.xcconfig
@@ -1,6 +1,6 @@
#include "Userscripts-Base.xcconfig"
-INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © 2018–2024 Justin Wasack. All rights reserved.
+INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © 2018–2025 Justin Wasack. All rights reserved.
CURRENT_PROJECT_VERSION = 1
MARKETING_VERSION = 1.0.0
{description}
{/if}
diff --git a/src/ext/global.d.ts b/src/ext/global.d.ts
index 167d6d97..8b626e08 100644
--- a/src/ext/global.d.ts
+++ b/src/ext/global.d.ts
@@ -11,3 +11,31 @@ declare global {
browser: Browser.Browser;
}
}
+
+declare namespace TypeBackground {
+ interface XHRMessage {
+ handler: string;
+ progress?: TypeExtMessages.XHRProgress;
+ response?: TypeExtMessages.XHRTransportableResponse;
+ }
+
+ interface XHRPort extends Browser.Runtime.Port {
+ onMessage: Browser.Events.Event<
+ (message: TypeContentScripts.XHRMessage, port: XHRPort) => void
+ >;
+ postMessage: (message: XHRMessage) => void;
+ }
+}
+
+declare namespace TypeContentScripts {
+ interface XHRMessage {
+ name: string;
+ }
+
+ interface XHRPort extends Browser.Runtime.Port {
+ onMessage: Browser.Events.Event<
+ (message: TypeBackground.XHRMessage, port: XHRPort) => void
+ >;
+ postMessage(message: XHRMessage): void;
+ }
+}
diff --git a/src/ext/shared/settings.js b/src/ext/shared/settings.js
index 37d83e1d..6154d6bd 100644
--- a/src/ext/shared/settings.js
+++ b/src/ext/shared/settings.js
@@ -124,6 +124,14 @@ const settingsDefinition = /** @type {const} */ [
group: "general",
nodeType: "Toggle",
},
+ {
+ name: "theme_mode",
+ type: "string",
+ values: ["auto", "dark", "light"],
+ default: "auto",
+ group: "general",
+ nodeType: "select",
+ },
{
name: "global_active",
type: "boolean",
diff --git a/src/ext/types.d.ts b/src/ext/types.d.ts
index b194b29f..1ad82689 100644
--- a/src/ext/types.d.ts
+++ b/src/ext/types.d.ts
@@ -43,10 +43,24 @@ declare namespace TypeExtMessages {
"onloadend",
];
+ type XHRUploadHandlers = [
+ "onabort",
+ "onerror",
+ "onload",
+ "onloadend",
+ "onloadstart",
+ "onprogress",
+ "ontimeout",
+ ];
+
type XHRHandlersObj = {
[handler in XHRHandlers[number]]?: (response: XHRResponse) => void;
};
+ type XHRUploadHandlersObj = {
+ [handler in XHRUploadHandlers[number]]?: (response: XHRProgress) => void;
+ };
+
interface XHRTransportableDetails {
binary: boolean;
data: XHRProcessedData;
@@ -59,6 +73,7 @@ declare namespace TypeExtMessages {
url: string;
user: string;
hasHandlers: { [handler in XHRHandlers[number]]?: boolean };
+ hasUploadHandlers: { [handler in XHRUploadHandlers[number]]?: boolean };
}
interface XHRTransportableResponse {
@@ -78,4 +93,10 @@ declare namespace TypeExtMessages {
responseText?: string;
responseXML?: Document;
}
+
+ interface XHRProgress {
+ lengthComputable: boolean;
+ loaded: number;
+ total: number;
+ }
}
diff --git a/src/shared/Components/IconButton.svelte b/src/shared/Components/IconButton.svelte
index 99751f97..52ec42cc 100644
--- a/src/shared/Components/IconButton.svelte
+++ b/src/shared/Components/IconButton.svelte
@@ -28,6 +28,7 @@
color: inherit;
cursor: pointer;
display: flex;
+ flex-shrink: 0;
height: 1.5rem;
justify-content: center;
overflow: visible;
diff --git a/src/shared/Components/LogoIcon.svelte b/src/shared/Components/LogoIcon.svelte
new file mode 100644
index 00000000..7b5f74df
--- /dev/null
+++ b/src/shared/Components/LogoIcon.svelte
@@ -0,0 +1,14 @@
+