fix: clipboard fallback for insecure contexts, session rename via sessions.patch
- Add copyToClipboard utility with execCommand fallback for HTTP deployments (#76) - Replace all navigator.clipboard.writeText calls with copyToClipboard - Session rename now persists server-side via sessions.patch (#78) - Pass onRename callback from App to Sidebar
This commit is contained in:
@@ -10,6 +10,7 @@ import { CodeBlock } from './CodeBlock';
|
||||
import { ToolCall } from './ToolCall';
|
||||
import { ImageBlock } from './ImageBlock';
|
||||
import { buildImageSrc } from '../lib/image';
|
||||
import { copyToClipboard } from '../lib/clipboard';
|
||||
import { Bot, User, Wrench, Copy, Check, CheckCheck, RefreshCw, Zap, Info, Webhook, Braces, Clock, AlertCircle, Bookmark, ChevronDown, Reply } from 'lucide-react';
|
||||
import { t, getLocale } from '../lib/i18n';
|
||||
import { useLocale } from '../hooks/useLocale';
|
||||
@@ -379,9 +380,11 @@ function RawJsonPanel({ message }: { message: ChatMessageType }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const json = JSON.stringify(message, null, 2);
|
||||
const handleCopy = useCallback(() => {
|
||||
navigator.clipboard.writeText(json).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
copyToClipboard(json).then((ok) => {
|
||||
if (ok) {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
}
|
||||
});
|
||||
}, [json]);
|
||||
|
||||
@@ -557,7 +560,7 @@ export const ChatMessageComponent = memo(function ChatMessageComponent({ message
|
||||
<div className={`flex flex-nowrap gap-0.5 justify-end mt-1.5 -mb-1 opacity-0 group-hover:opacity-100 transition-all`}>
|
||||
{!isUser && !message.isStreaming && getPlainText(message).trim() && (
|
||||
<button
|
||||
onClick={() => { navigator.clipboard.writeText(getPlainText(message)); }}
|
||||
onClick={() => { copyToClipboard(getPlainText(message)); }}
|
||||
className="h-6 w-6 rounded-md flex items-center justify-center text-pc-text-faint hover:text-pc-accent-light transition-colors"
|
||||
title={t('message.copy')}
|
||||
aria-label={t('message.copy')}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useCallback, type HTMLAttributes, type ReactElement } from 'react';
|
||||
import { Check, Copy, Hash, WrapText, AlignLeft, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { copyToClipboard } from '../lib/clipboard';
|
||||
|
||||
/** Extract the language from the nested <code> element's className (e.g. "language-ts"). */
|
||||
function extractLanguage(children: React.ReactNode): string | null {
|
||||
@@ -68,9 +69,11 @@ export function CodeBlock(props: HTMLAttributes<HTMLPreElement>) {
|
||||
|
||||
const handleCopy = useCallback(() => {
|
||||
if (typeof code === 'string') {
|
||||
navigator.clipboard.writeText(code).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
copyToClipboard(code).then((ok) => {
|
||||
if (ok) {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [code]);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Menu, Sparkles, LogOut, Cpu, Bot, Download, Minimize2, Info, Copy, Chec
|
||||
import type { ConnectionStatus, Session, ChatMessage } from '../types';
|
||||
import { useT } from '../hooks/useLocale';
|
||||
import { SettingsModal } from './SettingsModal';
|
||||
import { copyToClipboard } from '../lib/clipboard';
|
||||
import { sessionDisplayName } from '../lib/sessionName';
|
||||
import { messagesToMarkdown, downloadFile } from '../lib/exportChat';
|
||||
|
||||
@@ -160,7 +161,7 @@ function CopyField({ value }: { value: string }) {
|
||||
return (
|
||||
<button
|
||||
className="ml-auto p-0.5 rounded hover:bg-[var(--pc-hover)] text-pc-text-faint hover:text-pc-text-secondary transition-colors"
|
||||
onClick={() => { navigator.clipboard.writeText(value).then(() => { setCopied(true); setTimeout(() => setCopied(false), 1500); }); }}
|
||||
onClick={() => { copyToClipboard(value).then((ok) => { if (ok) { setCopied(true); setTimeout(() => setCopied(false), 1500); } }); }}
|
||||
title="Copy"
|
||||
>
|
||||
{copied ? <Check size={11} className="text-emerald-400" /> : <Copy size={11} />}
|
||||
|
||||
@@ -166,9 +166,10 @@ interface Props {
|
||||
splitSession?: string | null;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onRename?: (key: string, label: string) => Promise<boolean>;
|
||||
}
|
||||
|
||||
export function Sidebar({ sessions, activeSession, onSwitch, onDelete, onSplit, splitSession, open, onClose }: Props) {
|
||||
export function Sidebar({ sessions, activeSession, onSwitch, onDelete, onSplit, splitSession, open, onClose, onRename }: Props) {
|
||||
const t = useT();
|
||||
const [filter, setFilter] = useState('');
|
||||
const [focusIdx, setFocusIdx] = useState(-1);
|
||||
@@ -259,9 +260,13 @@ export function Sidebar({ sessions, activeSession, onSwitch, onDelete, onSplit,
|
||||
saveCustomNames(next);
|
||||
return next;
|
||||
});
|
||||
// Also persist server-side via sessions.patch
|
||||
if (onRename && trimmed) {
|
||||
onRename(renamingKey, trimmed).catch(() => { /* best effort */ });
|
||||
}
|
||||
setRenamingKey(null);
|
||||
setRenameValue('');
|
||||
}, [renamingKey, renameValue]);
|
||||
}, [renamingKey, renameValue, onRename]);
|
||||
|
||||
const cancelRename = useCallback(() => {
|
||||
setRenamingKey(null);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
|
||||
import { ChevronRight, ChevronDown, Check, Copy, WrapText, AlignLeft } from 'lucide-react';
|
||||
import hljs from '../lib/highlight';
|
||||
import { copyToClipboard } from '../lib/clipboard';
|
||||
import { useT } from '../hooks/useLocale';
|
||||
import { useTheme } from '../hooks/useTheme';
|
||||
import { ImageBlock } from './ImageBlock';
|
||||
@@ -153,9 +154,11 @@ function WrapToggle({ wrap, onToggle }: { wrap: boolean; onToggle: () => void })
|
||||
function CopyButton({ text }: { text: string }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const handleCopy = useCallback(() => {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
copyToClipboard(text).then((ok) => {
|
||||
if (ok) {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
}
|
||||
});
|
||||
}, [text]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user