- Fix React hooks violations: ref updates during render (useDraftPersistence, useChipDraftPersistence, useAttachments), setState in effects across 15+ components, useCallback self-reference TDZ in useResizableColumns - Fix TypeScript lint: remove unnecessary type assertions, replace inline import() annotations with direct imports, remove unused variables/imports - Fix SonarJS issues: prefer-regexp-exec, slow-regex in SubagentResolver, no-misleading-array-reverse in TeamProvisioningService, use-type-alias in ClaudeLogsSection, variable shadowing in ChangeExtractorService - Fix accessibility: associate labels with controls in filter popovers - Fix template expression safety: wrap unknown errors with String() - Fix flaky FileWatcher test: floor instanceCreatedAt to second granularity to match filesystem birthtimeMs resolution on Linux - Replace TODO comments with NOTE where features are intentionally disabled - Remove unused leadContextByTeam from TeamDetailView store selector 62 files changed across main process, renderer, shared types, and hooks. All 1646 tests pass, typecheck clean, 0 lint errors.
65 lines
2.4 KiB
TypeScript
65 lines
2.4 KiB
TypeScript
import { useState } from 'react';
|
|
|
|
import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer';
|
|
import { MemberBadge } from '@renderer/components/team/MemberBadge';
|
|
|
|
import type { ParsedMessageReply } from '@renderer/utils/agentMessageFormatting';
|
|
|
|
interface ReplyQuoteBlockProps {
|
|
reply: ParsedMessageReply;
|
|
/** Color name for the quoted agent (resolved from memberColorMap). */
|
|
memberColor?: string;
|
|
/** When set, limits height of the reply body (e.g. "max-h-56"). Omit to show full content. */
|
|
bodyMaxHeight?: string;
|
|
}
|
|
|
|
/** Threshold (characters) above which the "more/less" toggle is shown. */
|
|
const LONG_QUOTE_THRESHOLD = 200;
|
|
|
|
export const ReplyQuoteBlock = ({
|
|
reply,
|
|
memberColor,
|
|
bodyMaxHeight = 'max-h-56',
|
|
}: ReplyQuoteBlockProps): React.JSX.Element => {
|
|
const isLong = reply.originalText.length > LONG_QUOTE_THRESHOLD;
|
|
const [expanded, setExpanded] = useState(false);
|
|
|
|
const quoteMaxHeight = expanded ? 'max-h-48' : 'max-h-[3.75rem]';
|
|
|
|
return (
|
|
<div className="space-y-2">
|
|
{/* Quote block — styled like SendMessageDialog */}
|
|
<div className="relative overflow-hidden rounded-md border border-blue-500/20 bg-blue-950/20 py-2 pl-3 pr-2">
|
|
{/* Decorative quotation mark */}
|
|
<span className="pointer-events-none absolute -right-1 top-1/2 -translate-y-1/2 select-none font-serif text-[48px] leading-none text-blue-400/[0.08]">
|
|
“
|
|
</span>
|
|
|
|
{/* "Replying to" + MemberBadge */}
|
|
<div className="mb-1 flex items-center gap-1.5">
|
|
<span className="text-[10px] text-blue-300/60">Replying to</span>
|
|
<MemberBadge name={reply.agentName} color={memberColor} size="sm" />
|
|
</div>
|
|
|
|
{/* Quote text */}
|
|
<div className={`pr-5 opacity-50 ${expanded ? '' : 'max-h-[3.75rem] overflow-hidden'}`}>
|
|
<MarkdownViewer content={reply.originalText} bare maxHeight={quoteMaxHeight} />
|
|
</div>
|
|
|
|
{/* More/less toggle */}
|
|
{isLong ? (
|
|
<button
|
|
type="button"
|
|
className="mt-0.5 text-[10px] text-blue-400/60 hover:text-blue-300"
|
|
onClick={() => setExpanded((v) => !v)}
|
|
>
|
|
{expanded ? 'less' : 'more'}
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
|
|
{/* Reply text */}
|
|
<MarkdownViewer content={reply.replyText} maxHeight={bodyMaxHeight} copyable bare />
|
|
</div>
|
|
);
|
|
};
|