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
2 changes: 1 addition & 1 deletion packages/app/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const Loading = () => <div class="size-full flex items-center justify-center tex

declare global {
interface Window {
__OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string }
__OPENCODE__?: { updaterEnabled?: boolean; serverPassword?: string; openLinksExternally?: boolean }
}
}

Expand Down
28 changes: 28 additions & 0 deletions packages/app/src/context/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
sessionTabs: {} as Record<string, SessionTabs>,
sessionView: {} as Record<string, SessionView>,
links: {
openExternally: true,
},
}),
)

Expand Down Expand Up @@ -266,6 +269,13 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
)
})

// Sync openLinksExternally setting to window.__OPENCODE__ for desktop app
createEffect(() => {
const value = store.links?.openExternally ?? true
window.__OPENCODE__ ??= {}
window.__OPENCODE__.openLinksExternally = value
})

return {
ready,
projects: {
Expand Down Expand Up @@ -343,6 +353,24 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
setStore("mobileSidebar", "opened", (x) => !x)
},
},
links: {
openExternally: createMemo(() => store.links?.openExternally ?? true),
setOpenExternally(value: boolean) {
if (!store.links) {
setStore("links", { openExternally: value })
return
}
setStore("links", "openExternally", value)
},
toggle() {
const current = store.links?.openExternally ?? true
if (!store.links) {
setStore("links", { openExternally: !current })
return
}
setStore("links", "openExternally", !current)
},
},
view(sessionKey: string) {
touch(sessionKey)
scroll.seed(sessionKey)
Expand Down
23 changes: 23 additions & 0 deletions packages/app/src/pages/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,12 @@ export default function Layout(props: ParentProps) {
keybind: "mod+shift+t",
onSelect: () => cycleTheme(1),
},
{
id: "links.toggle-external",
title: layout.links.openExternally() ? "Open links in app" : "Open links in browser",
category: "Settings",
onSelect: () => layout.links.toggle(),
},
]

for (const [id, definition] of availableThemeEntries()) {
Expand Down Expand Up @@ -1236,6 +1242,23 @@ export default function Layout(props: ParentProps) {
<Show when={expanded()}>Share feedback</Show>
</Button>
</Tooltip>
<Show when={platform.platform === "desktop"}>
<Tooltip
placement="right"
value={layout.links.openExternally() ? "Links open in browser" : "Links open in app"}
inactive={expanded()}
>
<Button
class="flex w-full text-left justify-start text-text-base stroke-[1.5px] rounded-lg px-2"
variant="ghost"
size="large"
icon={layout.links.openExternally() ? "square-arrow-top-right" : "window-cursor"}
onClick={() => layout.links.toggle()}
>
<Show when={expanded()}>{layout.links.openExternally() ? "Links: Browser" : "Links: In-app"}</Show>
</Button>
</Tooltip>
</Show>
</div>
</div>
)
Expand Down
21 changes: 16 additions & 5 deletions packages/desktop/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,23 @@ root?.addEventListener("mousewheel", (e) => {
e.stopPropagation()
})

// Handle external links - open in system browser instead of webview
document.addEventListener("click", (e) => {
const link = (e.target as HTMLElement).closest("a.external-link") as HTMLAnchorElement | null
if (link?.href) {
// Intercept all link clicks and open external URLs in system browser
root?.addEventListener("click", (e) => {
const anchor = (e.target as HTMLElement).closest("a")
if (!anchor) return

const href = anchor.getAttribute("href")
if (!href) return

// Only intercept external URLs (http/https)
if (href.startsWith("http://") || href.startsWith("https://")) {
// Check if user wants to open links externally (default: true)
const openExternally = window.__OPENCODE__?.openLinksExternally ?? true
if (!openExternally) return

e.preventDefault()
platform.openLink(link.href)
e.stopPropagation()
void shellOpen(href).catch(() => undefined)
}
})

Expand Down