| author | |
| committer | |
| log | 13427ecd8ba5bfa7892fc90d1e3f0ef59514870f |
| tree | aa1096c8761e88d2166a20eb484ce003e8fddfbc |
| parent | f6db4206258b0ef30e8dd8949627acfd209bf338 |
| signature | Signed by PGP key B5690EEEBB952194 |
2 files changed, 55 insertions(+), 38 deletions(-)
apps/web/src/components/diffs/DiffCommentAnnotation.tsx+15-3| ... | ... | @@ -1,5 +1,5 @@ |
| 1 | 1 | import { MessageCircle, Trash2 } from "lucide-react"; |
| 2 | import { useState, type ReactNode } from "react"; | |
| 2 | import { useLayoutEffect, useRef, useState, type ReactNode } from "react"; | |
| 3 | 3 | |
| 4 | 4 | import { Button } from "~/components/ui/button"; |
| 5 | 5 | import { Textarea } from "~/components/ui/textarea"; |
| ... | ... | @@ -25,6 +25,7 @@ interface DiffCommentAnnotationProps { |
| 25 | 25 | submitLabel?: string; |
| 26 | 26 | pending?: boolean; |
| 27 | 27 | secondaryAction?: DiffCommentSecondaryAction; |
| 28 | focusOnMount?: boolean; | |
| 28 | 29 | } |
| 29 | 30 | |
| 30 | 31 | /** The shared inline comment treatment for file previews, thread diffs, and pull-request diffs. */ |
| ... | ... | @@ -40,10 +41,20 @@ export function DiffCommentAnnotation({ |
| 40 | 41 | submitLabel = "Comment", |
| 41 | 42 | pending = false, |
| 42 | 43 | secondaryAction, |
| 44 | focusOnMount = true, | |
| 43 | 45 | }: DiffCommentAnnotationProps) { |
| 44 | 46 | const [localDraftText, setLocalDraftText] = useState(""); |
| 45 | 47 | const displayedText = kind === "draft" && !onTextChange ? localDraftText : text; |
| 46 | 48 | const trimmedText = displayedText.trim(); |
| 49 | const textareaRef = useRef<HTMLTextAreaElement | null>(null); | |
| 50 | ||
| 51 | useLayoutEffect(() => { | |
| 52 | if (kind !== "draft" || !focusOnMount) return; | |
| 53 | const frame = window.requestAnimationFrame(() => { | |
| 54 | textareaRef.current?.focus({ preventScroll: true }); | |
| 55 | }); | |
| 56 | return () => window.cancelAnimationFrame(frame); | |
| 57 | }, [focusOnMount, kind]); | |
| 47 | 58 | |
| 48 | 59 | if (kind === "comment") { |
| 49 | 60 | return ( |
| ... | ... | @@ -78,9 +89,10 @@ export function DiffCommentAnnotation({ |
| 78 | 89 | onPointerDown={(event) => event.stopPropagation()} |
| 79 | 90 | > |
| 80 | 91 | <Textarea |
| 81 | autoFocus | |
| 92 | ref={textareaRef} | |
| 93 | autoFocus={focusOnMount} | |
| 82 | 94 | unstyled |
| 83 | className="relative inline-flex w-full rounded-md border border-border/50 bg-background/20 font-sans text-foreground transition-colors focus-within:border-border/70 [&_[data-slot=textarea]]:min-h-12 [&_[data-slot=textarea]]:cursor-text [&_[data-slot=textarea]]:px-2.5 [&_[data-slot=textarea]]:py-1.5 [&_[data-slot=textarea]]:font-sans [&_[data-slot=textarea]]:text-xs [&_[data-slot=textarea]]:leading-5 max-sm:[&_[data-slot=textarea]]:min-h-12" | |
| 95 | className="relative inline-flex w-full rounded-md border border-border/50 bg-background/20 font-sans text-foreground transition-colors focus-within:border-border/70 [&_[data-slot=textarea]]:min-h-12 [&_[data-slot=textarea]]:cursor-text [&_[data-slot=textarea]]:caret-foreground [&_[data-slot=textarea]]:px-2.5 [&_[data-slot=textarea]]:py-1.5 [&_[data-slot=textarea]]:font-sans [&_[data-slot=textarea]]:text-xs [&_[data-slot=textarea]]:leading-5 max-sm:[&_[data-slot=textarea]]:min-h-12" | |
| 84 | 96 | size="sm" |
| 85 | 97 | value={displayedText} |
| 86 | 98 | placeholder={placeholder} |
apps/web/src/components/files/FilePreviewPanel.tsx+40-35| ... | ... | @@ -774,42 +774,47 @@ function EditableFileSurface({ |
| 774 | 774 | ], |
| 775 | 775 | ); |
| 776 | 776 | |
| 777 | const beginComment = useCallback((range: SelectedLineRange) => { | |
| 778 | const { startLine, endLine } = normalizeFileCommentRange(range); | |
| 779 | const draftEntry: FileCommentAnnotationEntry = { | |
| 780 | id: nextFileCommentId(), | |
| 781 | kind: "draft", | |
| 782 | startLine, | |
| 783 | endLine, | |
| 784 | text: "", | |
| 785 | }; | |
| 786 | setLineAnnotations((current) => { | |
| 787 | const withoutDraft = current.flatMap((annotation) => { | |
| 788 | const entries = annotation.metadata.entries.filter((entry) => entry.kind !== "draft"); | |
| 789 | return entries.length > 0 ? [{ ...annotation, metadata: { entries } }] : []; | |
| 777 | const beginComment = useCallback( | |
| 778 | (range: SelectedLineRange) => { | |
| 779 | editor.setSelections([]); | |
| 780 | editor.blur(); | |
| 781 | const { startLine, endLine } = normalizeFileCommentRange(range); | |
| 782 | const draftEntry: FileCommentAnnotationEntry = { | |
| 783 | id: nextFileCommentId(), | |
| 784 | kind: "draft", | |
| 785 | startLine, | |
| 786 | endLine, | |
| 787 | text: "", | |
| 788 | }; | |
| 789 | setLineAnnotations((current) => { | |
| 790 | const withoutDraft = current.flatMap((annotation) => { | |
| 791 | const entries = annotation.metadata.entries.filter((entry) => entry.kind !== "draft"); | |
| 792 | return entries.length > 0 ? [{ ...annotation, metadata: { entries } }] : []; | |
| 793 | }); | |
| 794 | const existingIndex = withoutDraft.findIndex( | |
| 795 | (annotation) => annotation.lineNumber === endLine, | |
| 796 | ); | |
| 797 | if (existingIndex < 0) { | |
| 798 | return [ | |
| 799 | ...withoutDraft, | |
| 800 | { | |
| 801 | lineNumber: endLine, | |
| 802 | metadata: { entries: [draftEntry] }, | |
| 803 | }, | |
| 804 | ]; | |
| 805 | } | |
| 806 | return withoutDraft.map((annotation, index) => | |
| 807 | index === existingIndex | |
| 808 | ? { | |
| 809 | ...annotation, | |
| 810 | metadata: { entries: [...annotation.metadata.entries, draftEntry] }, | |
| 811 | } | |
| 812 | : annotation, | |
| 813 | ); | |
| 790 | 814 | }); |
| 791 | const existingIndex = withoutDraft.findIndex( | |
| 792 | (annotation) => annotation.lineNumber === endLine, | |
| 793 | ); | |
| 794 | if (existingIndex < 0) { | |
| 795 | return [ | |
| 796 | ...withoutDraft, | |
| 797 | { | |
| 798 | lineNumber: endLine, | |
| 799 | metadata: { entries: [draftEntry] }, | |
| 800 | }, | |
| 801 | ]; | |
| 802 | } | |
| 803 | return withoutDraft.map((annotation, index) => | |
| 804 | index === existingIndex | |
| 805 | ? { | |
| 806 | ...annotation, | |
| 807 | metadata: { entries: [...annotation.metadata.entries, draftEntry] }, | |
| 808 | } | |
| 809 | : annotation, | |
| 810 | ); | |
| 811 | }); | |
| 812 | }, []); | |
| 815 | }, | |
| 816 | [editor], | |
| 817 | ); | |
| 813 | 818 | const hasOpenCommentForm = lineAnnotations.some((annotation) => |
| 814 | 819 | annotation.metadata.entries.some((entry) => entry.kind === "draft"), |
| 815 | 820 | ); |