mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
e20fd97be7
* feat(ui): AnnotationPanel renderCardFooter + readOnly host props Per-card footer slot for host reply/resolve UI (clicks inside don't select the card); readOnly hides delete/edit on all card kinds. Both optional, both no-op by default — Plannotator unchanged. * feat(ui): bless 6 exports into the strict-consumer surface TableOfContents, ResizeHandle, useResizablePanel, useActiveSection, useScrollViewport, utils/annotationHelpers — verified strict-clean and backend-free (useResizablePanel persists via the storageBackend seam); added to the gate and the HANDOFF supported-imports table. * feat(ui): Viewer allowImages + readOnly host props allowImages threads to both CommentPopover sites (popover already gated its attach affordance; Viewer never exposed the knob). readOnly suppresses every composer entry point — selection toolbar (highlighter 'enabled'), pinpoint, global comment, attachments, checkbox toggles — while existing annotations still render and select. Both default to today's behavior. * fix(ui): lazy-import Viewer/CommentPopover in the consumer test (DOM-less bun test crashed on web-highlighter) * feat(ui): strict-consumer gate gains verbatimModuleSyntax + noUnusedLocals/Parameters Fixed the 24 violations the flags surfaced across the supported-import graph: type-only imports (verbatimModuleSyntax) and dead imports/locals. Consumers with stricter tsconfigs no longer have to relax them. * feat(ui): opt-in content-verifying annotation restore verifyRestoredContent on useAnnotationHighlighter: after a meta-based fromStore restore, the painted text is checked against originalText (whitespace-normalized). Mismatch -> highlight removed, text-search fallback re-anchors; if that fails too, onRestoreMismatch(annotation, restoredText) fires and nothing is painted. Default off — today's trust-the-positions behavior. Workspaces hit this live after document drift; correctness upgrade for every consumer. * chore(ui): 0.24.0 — HANDOFF consumer-enablement notes + version bump
69 lines
2.9 KiB
TypeScript
69 lines
2.9 KiB
TypeScript
import React from 'react';
|
|
import type { EditorAnnotation } from '../types';
|
|
import { cn } from '../lib/utils';
|
|
|
|
// EditorAnnotationCard is SHARED across surfaces:
|
|
// - the plan/annotate AnnotationPanel (flat surface-1 cards), and
|
|
// - the code-review ReviewSidebar (bordered cards, sitting next to
|
|
// renderAnnotationCard's `p-2.5 rounded border border-transparent
|
|
// hover:bg-muted/30` code cards).
|
|
// The `variant` prop keeps each surface visually cohesive:
|
|
// - 'plan' → flat surface-1 hover (matches the plan AnnotationCard restyle)
|
|
// - 'code-review' → bordered + muted hover (matches code-review's code cards)
|
|
type EditorAnnotationVariant = 'plan' | 'code-review';
|
|
|
|
interface EditorAnnotationCardProps {
|
|
annotation: EditorAnnotation;
|
|
/** Omit to render the card read-only (no delete affordance). */
|
|
onDelete?: () => void;
|
|
variant?: EditorAnnotationVariant;
|
|
}
|
|
|
|
export const EditorAnnotationCard: React.FC<EditorAnnotationCardProps> = ({ annotation, onDelete, variant = 'plan' }) => {
|
|
const lineRange = annotation.lineStart === annotation.lineEnd
|
|
? `L${annotation.lineStart}`
|
|
: `L${annotation.lineStart}-${annotation.lineEnd}`;
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'group w-full text-left transition-colors duration-150',
|
|
variant === 'code-review'
|
|
? 'relative p-2.5 rounded border border-transparent hover:bg-muted/30'
|
|
: 'rounded-lg px-3 py-2.5 hover:bg-surface-1/50',
|
|
)}
|
|
>
|
|
{/* Header: type word + file:line + delete */}
|
|
<div className="mb-1.5 flex items-center gap-1.5">
|
|
<span className="text-[11px] font-medium text-amber-600 dark:text-amber-400">Editor</span>
|
|
<span className="text-[10px] font-mono text-muted-foreground/50 truncate" title={annotation.filePath}>
|
|
{annotation.filePath}:{lineRange}
|
|
</span>
|
|
{onDelete && (
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); onDelete(); }}
|
|
className="ml-auto relative rounded-md p-1.5 text-muted-foreground transition-colors before:absolute before:-inset-1.5 before:content-[''] opacity-0 group-hover:opacity-100 [@media(hover:none)]:opacity-100 hover:text-destructive"
|
|
title="Delete annotation"
|
|
>
|
|
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Selected text */}
|
|
<p className="mb-1.5 line-clamp-2 whitespace-pre-wrap font-mono text-[11px] leading-relaxed text-muted-foreground/80">
|
|
{annotation.selectedText}
|
|
</p>
|
|
|
|
{/* Comment */}
|
|
{annotation.comment && (
|
|
<p className="whitespace-pre-wrap text-[13px] leading-relaxed text-foreground/90">
|
|
{annotation.comment}
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|