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}
    + {#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 @@