Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion packages/app/src/components/settings-general.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Component, createMemo, type JSX } from "solid-js"
import { Component, createMemo, Show, type JSX } from "solid-js"
import { Select } from "@opencode-ai/ui/select"
import { Switch } from "@opencode-ai/ui/switch"
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
import { useLanguage } from "@/context/language"
import { useSettings, monoFontFamily } from "@/context/settings"
import { usePlatform } from "@/context/platform"
import { playSound, SOUND_OPTIONS } from "@/utils/sound"
import { Link } from "./link"

Expand All @@ -30,6 +31,7 @@ export const SettingsGeneral: Component = () => {
const theme = useTheme()
const language = useLanguage()
const settings = useSettings()
const platform = usePlatform()

const themeOptions = createMemo(() =>
Object.entries(theme.themes()).map(([id, def]) => ({ id, name: def.name ?? id })),
Expand Down Expand Up @@ -292,6 +294,25 @@ export const SettingsGeneral: Component = () => {
</SettingsRow>
</div>
</div>

{/* Browser Section - Desktop only */}
<Show when={platform.platform === "desktop"}>
<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong pb-2">Browser</h3>

<div class="bg-surface-raised-base px-4 rounded-lg">
<SettingsRow
title="Open Links in External Browser"
description="When enabled, clicking links will open them in your default system browser instead of within the app."
>
<Switch
checked={settings.browser.openLinksExternally()}
onChange={(checked) => settings.browser.setOpenLinksExternally(checked)}
/>
</SettingsRow>
</div>
</div>
</Show>
</div>
</div>
)
Expand Down
16 changes: 16 additions & 0 deletions packages/app/src/context/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export interface SoundSettings {
errors: string
}

export interface BrowserSettings {
openLinksExternally: boolean
}

export interface Settings {
general: {
autoSave: boolean
Expand All @@ -29,6 +33,7 @@ export interface Settings {
}
notifications: NotificationSettings
sounds: SoundSettings
browser: BrowserSettings
}

const defaultSettings: Settings = {
Expand All @@ -53,6 +58,9 @@ const defaultSettings: Settings = {
permissions: "staplebops-02",
errors: "nope-03",
},
browser: {
openLinksExternally: true,
},
}

const monoFallback =
Expand Down Expand Up @@ -154,6 +162,14 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
setStore("sounds", "errors", value)
},
},
browser: {
openLinksExternally: createMemo(
() => store.browser?.openLinksExternally ?? defaultSettings.browser.openLinksExternally,
),
setOpenLinksExternally(value: boolean) {
setStore("browser", "openLinksExternally", value)
},
},
}
},
})
43 changes: 38 additions & 5 deletions packages/desktop/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@ window.getComputedStyle = ((elt: Element, pseudoElt?: string | null) => {

let update: Update | null = null

// Settings key for reading browser preferences
const SETTINGS_KEY = "settings.v3"

// Read browser settings from store to check if links should open externally
async function shouldOpenLinksExternally(): Promise<boolean> {
try {
const store = await Store.load("opencode.global.dat")
const settings = await store.get(SETTINGS_KEY)
if (settings && typeof settings === "object" && "browser" in (settings as object)) {
const browser = (settings as { browser?: { openLinksExternally?: boolean } }).browser
return browser?.openLinksExternally !== false // Default to true
}
} catch {
// Ignore errors, default to true
}
return true // Default: open links externally
}

const createPlatform = (password: Accessor<string | null>): Platform => ({
platform: "desktop",
os: (() => {
Expand Down Expand Up @@ -329,11 +347,26 @@ render(() => {
const platform = createPlatform(() => serverPassword())

function handleClick(e: MouseEvent) {
const link = (e.target as HTMLElement).closest("a.external-link") as HTMLAnchorElement | null
if (link?.href) {
e.preventDefault()
platform.openLink(link.href)
}
const link = (e.target as HTMLElement).closest("a") as HTMLAnchorElement | null
if (!link?.href) return

// Check if it's an external link (http/https)
const isExternal = link.href.startsWith("http://") || link.href.startsWith("https://")
if (!isExternal) return

// MUST preventDefault immediately (before any async), otherwise browser handles it
e.preventDefault()
const url = link.href

// Check user preference and handle accordingly
shouldOpenLinksExternally().then((openExternally) => {
if (openExternally) {
platform.openLink(url)
} else {
// Open in app by navigating
window.location.href = url
}
})
}

onMount(() => {
Expand Down