From eb45b4a6ea46f59bd188ecc9bb714bec2311b6bd Mon Sep 17 00:00:00 2001 From: Salastil Date: Thu, 3 Sep 2026 18:40:54 -0400 Subject: [PATCH] A kick.com link opens the stream as well as the chat Clicking one used to swallow the link: the chat opened here and the stream went nowhere, so somebody clicking through to watch ended up with half of what they asked for. It does both now - the stream in the browser, where video is watched, and the chat here, which is what this client is for. With no Kick account the stream still opens and the toast says only the chat is missing, rather than reporting a failure for something that half-worked. And a Kick channel's own row has "Open in browser", next to the way to open its chat in a window - a channel is a stream as well as a chat, and the stream is the part moho does not show. --- nobilis | 2 +- src/renderer/src/components/BufferList.tsx | 14 ++++++++++++++ src/renderer/src/lib/richtext.tsx | 7 +++++-- src/renderer/src/state/store.ts | 19 +++++++++++++------ 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/nobilis b/nobilis index 1bd675c..20d087d 160000 --- a/nobilis +++ b/nobilis @@ -1 +1 @@ -Subproject commit 1bd675c32c457684c42061529836d59d37ca18f3 +Subproject commit 20d087d0776b977d02961076db04ac6e22a3c9e2 diff --git a/src/renderer/src/components/BufferList.tsx b/src/renderer/src/components/BufferList.tsx index dea1e67..4b1723d 100644 --- a/src/renderer/src/components/BufferList.tsx +++ b/src/renderer/src/components/BufferList.tsx @@ -277,6 +277,11 @@ export function BufferList(): JSX.Element { : undefined } autojoins={store.autojoins(b.accountId, bufferDisplayName(b.name))} + onOpenInBrowser={ + b.accountId.startsWith('kick:') + ? () => void window.moho.openExternal(`https://kick.com/${bufferDisplayName(b.name)}`) + : undefined + } onHide={() => hideBuffer(b.id)} onClose={() => void store.closeBuffer(b.id)} inCall={voiceSessions.some((s) => s.bufferId === b.id)} @@ -678,6 +683,8 @@ interface BufferRowProps { onToggleMute: () => void /** IRC channels only: rejoin this on every connect, or stop doing so. */ onToggleAutojoin?: () => void + /** Kick channels only: watch the stream where streams are watched. */ + onOpenInBrowser?: () => void /** Whether it is in that list now. */ autojoins?: boolean onHide: () => void @@ -715,6 +722,7 @@ function BufferRow({ onToggleMute, onToggleAutojoin, autojoins, + onOpenInBrowser, onHide, onClose, onCall, @@ -776,6 +784,12 @@ function BufferRow({ poppedOut ? { label: 'Close its window', icon: 'close_fullscreen', onClick: onDock } : { label: 'Open in a new window', icon: 'open_in_new', onClick: onPopOut }, + // A Kick channel is a stream as well as a chat, and the stream is not + // something this client shows - so the way to watch it belongs on the + // row, next to the way to open its chat in a window. + ...(onOpenInBrowser + ? ([{ label: 'Open in browser', icon: 'public', onClick: onOpenInBrowser }] as MenuEntry[]) + : []), { separator: true }, // Which channels an account rejoins on connect is a property of the // channels, so it is set on one - it used to be a comma-separated field diff --git a/src/renderer/src/lib/richtext.tsx b/src/renderer/src/lib/richtext.tsx index 90fada2..b32d607 100644 --- a/src/renderer/src/lib/richtext.tsx +++ b/src/renderer/src/lib/richtext.tsx @@ -216,8 +216,11 @@ function walk(node: Node, handlers: Handlers, depth: number): ReactNode[] { break } - // A link to a channel is handled here rather than handed to the OS, - // which would bounce it straight back to this window. + // A link to a channel is acted on here rather than handed straight to + // the OS. What that means differs by service: an irc: link belongs to + // this client alone, while a kick.com link is a web page as well as a + // channel, so following it opens the stream in the browser *and* the + // chat here - see the store's followDeepLink. if (isDeepLink(href)) { out.push( a.service === 'kick' && a.hasPassword) ?? this.state.accounts.find((a) => a.service === 'kick') if (!account) { - this.toast('error', 'Add a Kick account to open that link here') - void window.moho.openExternal(url) + // The stream is already opening; only the chat is missing, and + // saying so is better than a silence that looks like half of it + // failed. + this.toast('info', 'Add a Kick account to open that chat here too') return } this.set({ activePanel: '' })