fix: session tooltip click-through, copy buttons stopPropagation, rename spacebar (v1.64.2)
This commit is contained in:
@@ -67,15 +67,17 @@ export function CodeBlock(props: HTMLAttributes<HTMLPreElement>) {
|
|||||||
const hasEnoughLines = lines.length > LINE_THRESHOLD;
|
const hasEnoughLines = lines.length > LINE_THRESHOLD;
|
||||||
const isCollapsible = lines.length > COLLAPSE_THRESHOLD;
|
const isCollapsible = lines.length > COLLAPSE_THRESHOLD;
|
||||||
|
|
||||||
const handleCopy = useCallback(() => {
|
const handleCopy = useCallback((e: React.MouseEvent) => {
|
||||||
if (typeof code === 'string') {
|
e.stopPropagation();
|
||||||
copyToClipboard(code).then((ok) => {
|
e.preventDefault();
|
||||||
if (ok) {
|
const text = typeof code === 'string' ? code : '';
|
||||||
setCopied(true);
|
if (!text) return;
|
||||||
setTimeout(() => setCopied(false), 2000);
|
copyToClipboard(text).then((ok) => {
|
||||||
}
|
if (ok) {
|
||||||
});
|
setCopied(true);
|
||||||
}
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
}, [code]);
|
}, [code]);
|
||||||
|
|
||||||
const toggleLineNumbers = useCallback(() => {
|
const toggleLineNumbers = useCallback(() => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useState, useRef, useEffect } from 'react';
|
import { useCallback, useState, useRef, useEffect, forwardRef } from 'react';
|
||||||
import { Menu, Sparkles, LogOut, Cpu, Bot, Download, Minimize2, Info, Copy, Check, Settings } from 'lucide-react';
|
import { Menu, Sparkles, LogOut, Cpu, Bot, Download, Minimize2, Info, Copy, Check, Settings } from 'lucide-react';
|
||||||
import type { ConnectionStatus, Session, ChatMessage } from '../types';
|
import type { ConnectionStatus, Session, ChatMessage } from '../types';
|
||||||
import { useT } from '../hooks/useLocale';
|
import { useT } from '../hooks/useLocale';
|
||||||
@@ -28,11 +28,17 @@ export function Header({ status, sessionKey, onToggleSidebar, activeSessionData,
|
|||||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
const sessionInfoRef = useRef<HTMLDivElement>(null);
|
const sessionInfoRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Close popover on outside click
|
const popoverRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Close popover on outside click — check both the trigger area AND the popover itself
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showSessionInfo) return;
|
if (!showSessionInfo) return;
|
||||||
const handler = (e: MouseEvent) => {
|
const handler = (e: MouseEvent) => {
|
||||||
if (sessionInfoRef.current && !sessionInfoRef.current.contains(e.target as Node)) {
|
const target = e.target as Node;
|
||||||
|
if (
|
||||||
|
sessionInfoRef.current && !sessionInfoRef.current.contains(target) &&
|
||||||
|
popoverRef.current && !popoverRef.current.contains(target)
|
||||||
|
) {
|
||||||
setShowSessionInfo(false);
|
setShowSessionInfo(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -74,7 +80,7 @@ export function Header({ status, sessionKey, onToggleSidebar, activeSessionData,
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
{showSessionInfo && activeSessionData && (
|
{showSessionInfo && activeSessionData && (
|
||||||
<SessionInfoPopover session={activeSessionData} sessionKey={sessionKey} messageCount={messages?.length ?? 0} onClose={() => setShowSessionInfo(false)} />
|
<SessionInfoPopover ref={popoverRef} session={activeSessionData} sessionKey={sessionKey} messageCount={messages?.length ?? 0} onClose={() => setShowSessionInfo(false)} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
@@ -169,7 +175,7 @@ function CopyField({ value }: { value: string }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SessionInfoPopover({ session, sessionKey, messageCount, onClose }: { session: Session; sessionKey: string; messageCount: number; onClose: () => void }) {
|
const SessionInfoPopover = forwardRef<HTMLDivElement, { session: Session; sessionKey: string; messageCount: number; onClose: () => void }>(function SessionInfoPopover({ session, sessionKey, messageCount, onClose }, ref) {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const rows: Array<{ label: string; value: string; copyable?: boolean }> = [
|
const rows: Array<{ label: string; value: string; copyable?: boolean }> = [
|
||||||
{ label: t('sessionInfo.sessionKey'), value: sessionKey, copyable: true },
|
{ label: t('sessionInfo.sessionKey'), value: sessionKey, copyable: true },
|
||||||
@@ -191,6 +197,7 @@ function SessionInfoPopover({ session, sessionKey, messageCount, onClose }: { se
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
ref={ref}
|
||||||
className="absolute top-full left-0 mt-2 z-50 w-72 rounded-xl border border-pc-border bg-[var(--pc-bg-surface)] shadow-xl backdrop-blur-xl animate-in fade-in slide-in-from-top-2 duration-200"
|
className="absolute top-full left-0 mt-2 z-50 w-72 rounded-xl border border-pc-border bg-[var(--pc-bg-surface)] shadow-xl backdrop-blur-xl animate-in fade-in slide-in-from-top-2 duration-200"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-label={t('header.sessionInfo')}
|
aria-label={t('header.sessionInfo')}
|
||||||
@@ -212,7 +219,7 @@ function SessionInfoPopover({ session, sessionKey, messageCount, onClose }: { se
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
function CompactButton({ sessionKey, onCompact }: { sessionKey: string; onCompact: (key: string) => Promise<boolean> }) {
|
function CompactButton({ sessionKey, onCompact }: { sessionKey: string; onCompact: (key: string) => Promise<boolean> }) {
|
||||||
const [compacting, setCompacting] = useState(false);
|
const [compacting, setCompacting] = useState(false);
|
||||||
|
|||||||
@@ -543,6 +543,7 @@ export function Sidebar({ sessions, activeSession, onSwitch, onDelete, onSplit,
|
|||||||
onChange={(e) => setRenameValue(e.target.value)}
|
onChange={(e) => setRenameValue(e.target.value)}
|
||||||
onBlur={commitRename}
|
onBlur={commitRename}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
if (e.key === 'Enter') { e.preventDefault(); commitRename(); }
|
if (e.key === 'Enter') { e.preventDefault(); commitRename(); }
|
||||||
if (e.key === 'Escape') { e.preventDefault(); cancelRename(); }
|
if (e.key === 'Escape') { e.preventDefault(); cancelRename(); }
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -153,7 +153,9 @@ function WrapToggle({ wrap, onToggle }: { wrap: boolean; onToggle: () => void })
|
|||||||
/** Small copy-to-clipboard button for tool call content blocks. */
|
/** Small copy-to-clipboard button for tool call content blocks. */
|
||||||
function CopyButton({ text }: { text: string }) {
|
function CopyButton({ text }: { text: string }) {
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const handleCopy = useCallback(() => {
|
const handleCopy = useCallback((e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
copyToClipboard(text).then((ok) => {
|
copyToClipboard(text).then((ok) => {
|
||||||
if (ok) {
|
if (ok) {
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user