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
3 changes: 2 additions & 1 deletion src/components/connection-startup-screen.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from 'react'
import type { AuthStatus } from '@/lib/hermes-auth'
import { writeTextToClipboard } from '@/lib/clipboard'
import { fetchHermesAuthStatus } from '@/lib/hermes-auth'

const POLL_INTERVAL_MS = 2_000
Expand Down Expand Up @@ -113,7 +114,7 @@ export function ConnectionStartupScreen({ onConnected }: Props) {

const handleCopy = async (text: string, idx: number) => {
try {
await navigator.clipboard.writeText(text)
await writeTextToClipboard(text)
setCopiedIdx(idx)
} catch {
/* clipboard not available */
Expand Down
3 changes: 2 additions & 1 deletion src/components/mobile-prompt/MobileSetupModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
import { AnimatePresence, motion } from 'motion/react';
import { HugeiconsIcon } from '@hugeicons/react';
import { Cancel01Icon, Sent02Icon, Tick01Icon } from '@hugeicons/core-free-icons';
import { writeTextToClipboard } from '@/lib/clipboard';

const STORAGE_KEY_SEEN = 'hermes-mobile-setup-seen';

Expand Down Expand Up @@ -157,7 +158,7 @@ export function MobileSetupModal({ isOpen, onClose }: MobileSetupModalProps) {
action: (
<button
type="button"
onClick={() => networkUrl && navigator.clipboard.writeText(networkUrl.url).catch(() => {})}
onClick={() => networkUrl && writeTextToClipboard(networkUrl.url).catch(() => {})}
className="group flex w-full items-center justify-between rounded-lg border border-primary-700 bg-primary-950 px-4 py-3 transition-colors hover:border-accent-500/50"
>
<span className="break-all font-mono text-sm text-accent-300">
Expand Down
3 changes: 2 additions & 1 deletion src/components/prompt-kit/code-block/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createHighlighter } from 'shiki'
import { formatLanguageName, normalizeLanguage, resolveLanguage } from './utils'
import type { BundledLanguage, Highlighter } from 'shiki'
import { useResolvedTheme } from '@/hooks/use-chat-settings'
import { writeTextToClipboard } from '@/lib/clipboard'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'

Expand Down Expand Up @@ -85,7 +86,7 @@ export function CodeBlock({

async function handleCopy() {
try {
await navigator.clipboard.writeText(content)
await writeTextToClipboard(content)
setCopied(true)
window.setTimeout(() => setCopied(false), 1600)
} catch {
Expand Down
37 changes: 37 additions & 0 deletions src/lib/clipboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export async function writeTextToClipboard(text: string): Promise<void> {
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(text)
return
} catch {
// Fall through to execCommand for insecure origins / limited browsers.
}
}

if (typeof document === 'undefined') {
throw new Error('Clipboard unavailable')
}

const textarea = document.createElement('textarea')
textarea.value = text
textarea.setAttribute('readonly', 'true')
textarea.style.position = 'fixed'
textarea.style.opacity = '0'
textarea.style.pointerEvents = 'none'
textarea.style.top = '0'
textarea.style.left = '0'

document.body.appendChild(textarea)
textarea.focus()
textarea.select()
textarea.setSelectionRange(0, text.length)

try {
const copied = document.execCommand('copy')
if (!copied) {
throw new Error('Clipboard unavailable')
}
} finally {
textarea.remove()
}
}
3 changes: 2 additions & 1 deletion src/screens/chat/components/message-actions-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
TooltipRoot,
TooltipTrigger,
} from '@/components/ui/tooltip'
import { writeTextToClipboard } from '@/lib/clipboard'
import { cn } from '@/lib/utils'

type MessageActionsBarProps = {
Expand All @@ -37,7 +38,7 @@ export function MessageActionsBar({

const handleCopy = async () => {
try {
await navigator.clipboard.writeText(text)
await writeTextToClipboard(text)
setCopied(true)
window.setTimeout(() => setCopied(false), 1400)
} catch {
Expand Down
5 changes: 3 additions & 2 deletions src/screens/chat/hooks/use-chat-settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback, useState } from 'react'

import { writeTextToClipboard } from '@/lib/clipboard'
import { readError } from '../utils'
import type { PathsPayload } from '../types'

Expand Down Expand Up @@ -54,7 +55,7 @@ export function useChatSettings() {
const copySessionsDir = useCallback(() => {
if (!paths?.sessionsDir) return
try {
void navigator.clipboard.writeText(paths.sessionsDir)
void writeTextToClipboard(paths.sessionsDir)
} catch {
// ignore
}
Expand All @@ -63,7 +64,7 @@ export function useChatSettings() {
const copyStorePath = useCallback(() => {
if (!paths?.storePath) return
try {
void navigator.clipboard.writeText(paths.storePath)
void writeTextToClipboard(paths.storePath)
} catch {
// ignore
}
Expand Down
3 changes: 2 additions & 1 deletion src/screens/settings/components/provider-wizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
getAuthTypeLabel,
getProviderInfo,
} from '@/lib/provider-catalog'
import { writeTextToClipboard } from '@/lib/clipboard'
import { Button } from '@/components/ui/button'
import {
DialogContent,
Expand Down Expand Up @@ -217,7 +218,7 @@ export function ProviderWizard({ open, onOpenChange, editProvider }: ProviderWiz
if (!configExample) return

try {
await navigator.clipboard.writeText(configExample)
await writeTextToClipboard(configExample)
setCopyState('copied')
} catch {
setCopyState('failed')
Expand Down