diff --git a/.github/workflows/deployment.yaml b/.github/workflows/deployment.yaml
index 86855b3f..f9b2e529 100644
--- a/.github/workflows/deployment.yaml
+++ b/.github/workflows/deployment.yaml
@@ -9,7 +9,7 @@ jobs:
strategy:
matrix: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs
platform: [mac, ios]
- runs-on: macos-15 # https://github.com/actions/runner-images
+ runs-on: macos-26 # https://github.com/actions/runner-images
steps:
- uses: actions/checkout@v4 # https://github.com/actions/checkout
with:
@@ -37,9 +37,10 @@ jobs:
if: ${{ github.event.release.prerelease }}
- run: npm run build:${{ matrix.platform }}-safari-15
if: ${{ ! github.event.release.prerelease }}
- - name: Set xcode version
- run: sudo xcode-select -s "/Applications/Xcode_26_beta.app" # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md#xcode
- - run: xcodebuild -downloadPlatform iOS # Temporary fix for Xcode_26_beta
+ # - name: Set xcode version
+ # run: sudo xcode-select -s "/Applications/Xcode_26.1.1.app" # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md#xcode
+ # - run: xcodebuild -downloadPlatform iOS # Temporary fix for Xcode_26_beta
+ # if: matrix.platform == 'ios'
- name: Run fastlane
id: fastlane
working-directory: ./fastlane
diff --git a/fastlane/Gemfile.lock b/fastlane/Gemfile.lock
index a8ba4db2..c6013a88 100644
--- a/fastlane/Gemfile.lock
+++ b/fastlane/Gemfile.lock
@@ -1,39 +1,40 @@
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (3.0.7)
- base64
- nkf
- rexml
- addressable (2.8.7)
- public_suffix (>= 2.0.2, < 7.0)
+ CFPropertyList (3.0.8)
+ abbrev (0.1.2)
+ addressable (2.8.8)
+ public_suffix (>= 2.0.2, < 8.0)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.4.0)
- aws-partitions (1.1125.0)
- aws-sdk-core (3.226.2)
+ aws-partitions (1.1188.0)
+ aws-sdk-core (3.239.2)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
base64
+ bigdecimal
jmespath (~> 1, >= 1.6.1)
logger
- aws-sdk-kms (1.106.0)
- aws-sdk-core (~> 3, >= 3.225.0)
+ aws-sdk-kms (1.118.0)
+ aws-sdk-core (~> 3, >= 3.239.1)
aws-sigv4 (~> 1.5)
- aws-sdk-s3 (1.192.0)
- aws-sdk-core (~> 3, >= 3.225.0)
+ aws-sdk-s3 (1.205.0)
+ aws-sdk-core (~> 3, >= 3.234.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.12.1)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
- base64 (0.3.0)
+ base64 (0.2.0)
+ bigdecimal (3.3.1)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
+ csv (3.3.5)
declarative (0.0.20)
digest-crc (0.7.0)
rake (>= 12.0.0, < 14.0.0)
@@ -53,9 +54,9 @@ GEM
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
- faraday-cookie_jar (0.0.7)
+ faraday-cookie_jar (0.0.8)
faraday (>= 0.8.0)
- http-cookie (~> 1.0.0)
+ http-cookie (>= 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.1)
faraday-excon (1.1.0)
@@ -70,15 +71,18 @@ GEM
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.4.0)
- fastlane (2.228.0)
+ fastlane (2.229.1)
CFPropertyList (>= 2.3, < 4.0.0)
+ abbrev (~> 0.1.2)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
+ base64 (~> 0.2.0)
bundler (>= 1.12.0, < 3.0.0)
colored (~> 1.2)
commander (~> 4.6)
+ csv (~> 3.3)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
@@ -98,7 +102,9 @@ GEM
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
+ mutex_m (~> 0.3.0)
naturally (~> 2.2)
+ nkf (~> 0.2.0)
optparse (>= 0.1.1, < 1.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
@@ -157,37 +163,37 @@ GEM
httpclient (2.9.0)
mutex_m
jmespath (1.6.2)
- json (2.12.2)
+ json (2.16.0)
jwt (2.10.2)
base64
logger (1.7.0)
mini_magick (4.13.2)
mini_mime (1.1.5)
- multi_json (1.15.0)
+ multi_json (1.17.0)
multipart-post (2.4.1)
mutex_m (0.3.0)
nanaimo (0.4.0)
naturally (2.3.0)
nkf (0.2.0)
- optparse (0.6.0)
+ optparse (0.8.0)
os (1.1.4)
plist (3.7.2)
- public_suffix (6.0.2)
- rake (13.3.0)
+ public_suffix (7.0.0)
+ rake (13.3.1)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
- rexml (3.4.1)
+ rexml (3.4.4)
rouge (3.28.0)
ruby2_keywords (0.0.5)
rubyzip (2.4.1)
security (0.1.5)
- signet (0.20.0)
+ signet (0.21.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
- jwt (>= 1.5, < 3.0)
+ jwt (>= 1.5, < 4.0)
multi_json (~> 1.10)
simctl (1.6.10)
CFPropertyList
@@ -217,11 +223,11 @@ GEM
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
- arm64-darwin-23
+ arm64-darwin-25
ruby
DEPENDENCIES
fastlane
BUNDLED WITH
- 2.6.9
+ 2.7.2
diff --git a/public/ext/shared/_locales/en/messages.json b/public/ext/shared/_locales/en/messages.json
index a39bfc24..c17c6cad 100644
--- a/public/ext/shared/_locales/en/messages.json
+++ b/public/ext/shared/_locales/en/messages.json
@@ -85,6 +85,24 @@
"settings_global_active_desc": {
"message": "Toggle on/off script injection for the pages you visit"
},
+ "settings_theme_mode": {
+ "message": "Theme mode"
+ },
+ "settings_theme_mode_desc": {
+ "message": "Toggle theme mode"
+ },
+ "settings_theme_mode_auto": {
+ "message": "Auto (System)"
+ },
+ "settings_theme_mode_dark": {
+ "message": "Dark"
+ },
+ "settings_theme_mode_light": {
+ "message": "Light"
+ },
+ "settings_theme_mode_custom": {
+ "message": "Custom"
+ },
"settings_global_exclude_match": {
"message": "Global Exclude Match patterns"
},
diff --git a/scripts/dev-safari.js b/scripts/dev-safari.js
index 34943850..c9236b9f 100644
--- a/scripts/dev-safari.js
+++ b/scripts/dev-safari.js
@@ -186,6 +186,7 @@ async function serverCheck(url) {
/** run development server */
const server = await createServer({
...sharedConfig,
+ publicDir: "public/ext/vendor/",
plugins: [svelte()],
server: {
host: true,
diff --git a/src/ext/action-popup/App.svelte b/src/ext/action-popup/App.svelte
index 5740e7b5..6d593530 100644
--- a/src/ext/action-popup/App.svelte
+++ b/src/ext/action-popup/App.svelte
@@ -242,6 +242,12 @@
}
platform = pltfm.platform;
+ /* Override theme mode */
+ const theme = await settingsStorage.get("theme_mode");
+ if (theme && theme !== "auto") {
+ document.body.style.setProperty("color-scheme", theme);
+ }
+
// display native error if there is
const errorNative = await settingsStorage.get("error_native");
if (errorNative.error) {
diff --git a/src/ext/content-scripts/entry-script-market.js b/src/ext/content-scripts/entry-script-market.js
index 67022e47..45ba17d6 100644
--- a/src/ext/content-scripts/entry-script-market.js
+++ b/src/ext/content-scripts/entry-script-market.js
@@ -9,10 +9,10 @@ async function injection() {
);
}
for (const link of links) {
+ if (link["href"]) url = link["href"];
link.addEventListener(
"click",
(e) => {
- url = link["href"];
e.stopImmediatePropagation();
e.preventDefault();
browser.runtime.sendMessage({ name: "WEB_USERJS_POPUP" });
@@ -23,14 +23,27 @@ async function injection() {
}
async function listeners() {
- browser.runtime.onMessage.addListener(async (message) => {
+ /**
+ * handle messages from background, popup, etc...
+ * @type {import("webextension-polyfill").Runtime.OnMessageListener}
+ */
+ const handleMessage = async (message) => {
if (import.meta.env.MODE === "development") {
console.debug(message, url);
}
if (message === "TAB_CLICK_USERJS") {
- const response = url;
- url = undefined; // respond only once of click event
- return response;
+ return url;
+ }
+ };
+ /** Dynamically remove listeners to avoid memory leaks */
+ if (document.visibilityState === "visible") {
+ browser.runtime.onMessage.addListener(handleMessage);
+ }
+ document.addEventListener("visibilitychange", () => {
+ if (document.hidden) {
+ browser.runtime.onMessage.removeListener(handleMessage);
+ } else {
+ browser.runtime.onMessage.addListener(handleMessage);
}
});
}
diff --git a/src/ext/content-scripts/entry-userscripts.js b/src/ext/content-scripts/entry-userscripts.js
index 5721dd90..362bfacb 100644
--- a/src/ext/content-scripts/entry-userscripts.js
+++ b/src/ext/content-scripts/entry-userscripts.js
@@ -25,19 +25,23 @@ function triageJS(userscript) {
if (document.readyState !== "loading") {
injectJS(userscript);
} else {
- document.addEventListener("DOMContentLoaded", () => {
- injectJS(userscript);
- });
+ document.addEventListener(
+ "DOMContentLoaded",
+ () => injectJS(userscript),
+ { once: true },
+ );
}
} else if (runAt === "document-idle") {
if (document.readyState === "complete") {
injectJS(userscript);
} else {
- document.addEventListener("readystatechange", () => {
+ const handle = () => {
if (document.readyState === "complete") {
injectJS(userscript);
+ document.removeEventListener("readystatechange", handle);
}
- });
+ };
+ document.addEventListener("readystatechange", handle);
}
}
}
@@ -233,17 +237,23 @@ async function injection() {
}
function listeners() {
- // listens for messages from background, popup, etc...
- browser.runtime.onMessage.addListener((request) => {
- const name = request.name;
+ /** listen for CSP violations */
+ document.addEventListener("securitypolicyviolation", cspFallback, {
+ once: true,
+ });
+ /**
+ * listens for messages from background, popup, etc...
+ * @type {import("webextension-polyfill").Runtime.OnMessageListener}
+ */
+ const handleMessage = (message) => {
+ const name = message.name;
if (name === "CONTEXT_RUN") {
// from bg script when context-menu item is clicked
// double check to ensure context-menu scripts only run in top windows
if (window !== window.top) return;
-
// loop through context-menu scripts saved to data object and find match
// if no match found, nothing will execute and error will log
- const filename = request.menuItemId;
+ const filename = message.menuItemId;
for (let i = 0; i < data.files.menu.length; i++) {
const item = data.files.menu[i];
if (item.scriptObject.filename === filename) {
@@ -254,9 +264,18 @@ function listeners() {
}
console.error(`Couldn't find ${filename} code!`);
}
+ };
+ /** Dynamically remove listeners to avoid memory leaks */
+ if (document.visibilityState === "visible") {
+ browser.runtime.onMessage.addListener(handleMessage);
+ }
+ document.addEventListener("visibilitychange", () => {
+ if (document.hidden) {
+ browser.runtime.onMessage.removeListener(handleMessage);
+ } else {
+ browser.runtime.onMessage.addListener(handleMessage);
+ }
});
- // listen for CSP violations
- document.addEventListener("securitypolicyviolation", cspFallback);
}
async function initialize() {
diff --git a/src/ext/extension-page/App.svelte b/src/ext/extension-page/App.svelte
index 60574f73..1c4ce4d3 100644
--- a/src/ext/extension-page/App.svelte
+++ b/src/ext/extension-page/App.svelte
@@ -19,6 +19,15 @@
}
});
+ /* Override theme mode */
+ $: if ($settings["theme_mode"]) {
+ if ($settings["theme_mode"] === "auto") {
+ document.body.style.removeProperty("color-scheme");
+ } else {
+ document.body.style.setProperty("color-scheme", $settings["theme_mode"]);
+ }
+ }
+
// disables default cmd+s (save) and cmd+f (find) behavior
function preventKeyCommands(e) {
if (e.metaKey && (e.code === "KeyS" || e.code === "KeyF")) {
@@ -58,6 +67,33 @@
window.location.reload();
}
});
+
+ let splitterActive = false;
+ let sidebarHidden = false;
+ let sidebarWidth = "23svw";
+ const sidebarMinWidth = "20rem";
+ const editorMinWidth = "20rem";
+
+ function sidebarSwitch() {
+ if (!sidebarHidden && !$items.find((i) => i.active)) {
+ log.add("Please select or create a new item first.", "info", true);
+ return;
+ }
+ sidebarHidden = !sidebarHidden;
+ }
+
+ function handleSplitterDrag(event) {
+ if (event.type === "mouseup") {
+ splitterActive = false;
+ } else if (event.type === "mousedown") {
+ splitterActive = true;
+ } else if (event.type === "mousemove") {
+ const vw = window.innerWidth;
+ const sw = (event.x / vw) * 100;
+ sidebarWidth = `max(min(${sw}svw, 100svw - ${editorMinWidth}), ${sidebarMinWidth})`;
+ }
+ event.preventDefault();
+ }
@@ -72,10 +108,30 @@
{/if}
{/if}
-
-
-
+
+ {#if !sidebarHidden}
+
+
+ {/if}
+
+{#if splitterActive}
+
+{/if}
{#each $notifications as item (item.id)}
notifications.remove(item.id)} {item} />
@@ -120,6 +176,34 @@
overflow: hidden;
}
+ .resize-mask {
+ background-color: transparent;
+ cursor: col-resize;
+ position: fixed;
+ inset: 0;
+ z-index: 20;
+ }
+
+ .splitter {
+ --splitter-width: 6px;
+ --splitter-offset: calc(var(--splitter-width) / 2);
+ --sidebar-fixed-width: max(var(--sidebar-min-width), var(--sidebar-width));
+
+ background-color: transparent;
+ cursor: col-resize;
+ position: fixed;
+ left: calc(var(--sidebar-fixed-width) - var(--splitter-offset));
+ width: var(--splitter-width);
+ height: 100svh;
+ z-index: 21;
+ transition: background-color 0.2s ease-in-out;
+
+ &:hover,
+ &.dragging {
+ background-color: var(--color-blue);
+ }
+ }
+
ul {
bottom: 1rem;
left: 50%;
diff --git a/src/ext/extension-page/Components/Editor/CodeMirror.svelte b/src/ext/extension-page/Components/Editor/CodeMirror.svelte
index f097189b..b2cbacda 100644
--- a/src/ext/extension-page/Components/Editor/CodeMirror.svelte
+++ b/src/ext/extension-page/Components/Editor/CodeMirror.svelte
@@ -150,11 +150,6 @@
"Cmd-S": () => saveHandler(),
"Cmd-F": () => activateSearch(),
Esc: () => (searchActive = false),
- Tab: (cm) => {
- // convert tabs to spaces and add invisible elements
- const s = Array(cm.getOption("indentUnit") + 1).join(" ");
- cm.replaceSelection(s);
- },
},
});
diff --git a/src/ext/extension-page/Components/Editor/Editor.svelte b/src/ext/extension-page/Components/Editor/Editor.svelte
index 0bdd3507..e0b7c719 100644
--- a/src/ext/extension-page/Components/Editor/Editor.svelte
+++ b/src/ext/extension-page/Components/Editor/Editor.svelte
@@ -13,6 +13,10 @@
import iconDownload from "@shared/img/icon-download.svg?raw";
import iconTrash from "@shared/img/icon-trash.svg?raw";
import iconSync from "@shared/img/icon-sync.svg?raw";
+ import iconSidebar from "@shared/img/icon-sidebar.svg?raw";
+
+ export let sidebarHidden;
+ export let sidebarSwitch;
// the data the populates editor elements
let canUpdate;
@@ -167,13 +171,22 @@
No Item Selected
{/if}