@@ -45,7 +45,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
4545 const [ copied , setCopied ] = useState ( false ) ;
4646 const [ showGlobalCommentInput , setShowGlobalCommentInput ] = useState ( false ) ;
4747 const [ globalCommentValue , setGlobalCommentValue ] = useState ( '' ) ;
48- const globalCommentInputRef = useRef < HTMLInputElement > ( null ) ;
48+ const globalCommentInputRef = useRef < HTMLTextAreaElement > ( null ) ;
4949
5050 const handleCopyPlan = async ( ) => {
5151 try {
@@ -90,6 +90,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
9090 const [ toolbarState , setToolbarState ] = useState < { element : HTMLElement ; source : any } | null > ( null ) ;
9191 const [ hoveredCodeBlock , setHoveredCodeBlock ] = useState < { block : Block ; element : HTMLElement } | null > ( null ) ;
9292 const [ isCodeBlockToolbarExiting , setIsCodeBlockToolbarExiting ] = useState ( false ) ;
93+ const [ isCodeBlockToolbarLocked , setIsCodeBlockToolbarLocked ] = useState ( false ) ;
9394 const hoverTimeoutRef = useRef < NodeJS . Timeout | null > ( null ) ;
9495
9596 // Keep refs in sync with props
@@ -451,7 +452,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
451452 window . getSelection ( ) ?. removeAllRanges ( ) ;
452453 } ;
453454
454- const handleCodeBlockAnnotate = ( type : AnnotationType , text ?: string ) => {
455+ const handleCodeBlockAnnotate = ( type : AnnotationType , text ?: string , imagePaths ?: string [ ] ) => {
455456 const highlighter = highlighterRef . current ;
456457 if ( ! hoveredCodeBlock || ! highlighter ) return ;
457458
@@ -499,17 +500,20 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
499500 originalText : codeText ,
500501 createdA : Date . now ( ) ,
501502 author : getIdentity ( ) ,
503+ imagePaths,
502504 } ;
503505
504506 onAddAnnotationRef . current ( newAnnotation ) ;
505507
506508 // Clear selection
507509 selection ?. removeAllRanges ( ) ;
508510 setHoveredCodeBlock ( null ) ;
511+ setIsCodeBlockToolbarLocked ( false ) ;
509512 } ;
510513
511514 const handleCodeBlockToolbarClose = ( ) => {
512515 setHoveredCodeBlock ( null ) ;
516+ setIsCodeBlockToolbarLocked ( false ) ;
513517 } ;
514518
515519 return (
@@ -520,7 +524,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
520524 className = "w-full max-w-3xl bg-card border border-border/50 rounded-xl shadow-xl p-5 md:p-10 lg:p-14 relative"
521525 >
522526 { /* Header buttons */ }
523- < div className = "absolute top-3 right-3 md:top-5 md:right-5 flex items-center gap-2" >
527+ < div className = "absolute top-3 right-3 md:top-5 md:right-5 flex items-start gap-2" >
524528 { /* Attachments button */ }
525529 { onAddGlobalAttachment && onRemoveGlobalAttachment && (
526530 < AttachmentsButton
@@ -538,12 +542,13 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
538542 e . preventDefault ( ) ;
539543 handleAddGlobalComment ( ) ;
540544 } }
541- className = "flex items-center gap-1.5 bg-muted/80 rounded-md p-1"
545+ className = "flex items-start gap-1.5 bg-muted/80 rounded-md p-1"
542546 >
543- < input
547+ < textarea
544548 ref = { globalCommentInputRef }
545- type = "text"
546- className = "bg-transparent border-none outline-none text-xs w-40 md:w-56 px-2 placeholder:text-muted-foreground"
549+ rows = { 1 }
550+ className = "bg-transparent text-xs min-w-40 md:min-w-56 max-w-80 max-h-32 placeholder:text-muted-foreground resize-none px-2 py-1.5 focus:outline-none"
551+ style = { { fieldSizing : 'content' } as React . CSSProperties }
547552 placeholder = "Add a global comment..."
548553 value = { globalCommentValue }
549554 onChange = { ( e ) => setGlobalCommentValue ( e . target . value ) }
@@ -552,12 +557,19 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
552557 setShowGlobalCommentInput ( false ) ;
553558 setGlobalCommentValue ( '' ) ;
554559 }
560+ // Enter to submit, Shift+Enter for newline
561+ if ( e . key === 'Enter' && ! e . shiftKey ) {
562+ e . preventDefault ( ) ;
563+ if ( globalCommentValue . trim ( ) ) {
564+ handleAddGlobalComment ( ) ;
565+ }
566+ }
555567 } }
556568 />
557569 < button
558570 type = "submit"
559571 disabled = { ! globalCommentValue . trim ( ) }
560- className = "px-2 py-1 text-xs font-medium rounded bg-purple-600 text-white hover:bg-purple-500 disabled:opacity-50 transition-all"
572+ className = "self-start px-2 py-1.5 text-xs font-medium rounded bg-purple-600 text-white hover:bg-purple-500 disabled:opacity-50 transition-all"
561573 >
562574 Add
563575 </ button >
@@ -567,7 +579,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
567579 setShowGlobalCommentInput ( false ) ;
568580 setGlobalCommentValue ( '' ) ;
569581 } }
570- className = "p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
582+ className = "self-start p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
571583 >
572584 < svg className = "w-3.5 h-3.5" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" strokeWidth = { 2 } >
573585 < path strokeLinecap = "round" strokeLinejoin = "round" d = "M6 18L18 6M6 6l12 12" />
@@ -667,6 +679,8 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
667679 setIsCodeBlockToolbarExiting ( false ) ;
668680 } }
669681 onMouseLeave = { ( ) => {
682+ // Don't close if toolbar is locked (in input mode)
683+ if ( isCodeBlockToolbarLocked ) return ;
670684 hoverTimeoutRef . current = setTimeout ( ( ) => {
671685 setIsCodeBlockToolbarExiting ( true ) ;
672686 setTimeout ( ( ) => {
@@ -675,6 +689,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
675689 } , 150 ) ;
676690 } , 100 ) ;
677691 } }
692+ onLockChange = { setIsCodeBlockToolbarLocked }
678693 />
679694 ) }
680695 </ article >
@@ -957,21 +972,28 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ block, onHover, onLeave, isHovere
957972
958973const CodeBlockToolbar : React . FC < {
959974 element : HTMLElement ;
960- onAnnotate : ( type : AnnotationType , text ?: string ) => void ;
975+ onAnnotate : ( type : AnnotationType , text ?: string , imagePaths ?: string [ ] ) => void ;
961976 onClose : ( ) => void ;
962977 isExiting : boolean ;
963978 onMouseEnter : ( ) => void ;
964979 onMouseLeave : ( ) => void ;
965- } > = ( { element, onAnnotate, onClose, isExiting, onMouseEnter, onMouseLeave } ) => {
980+ onLockChange ?: ( locked : boolean ) => void ;
981+ } > = ( { element, onAnnotate, onClose, isExiting, onMouseEnter, onMouseLeave, onLockChange } ) => {
966982 const [ step , setStep ] = useState < 'menu' | 'input' > ( 'menu' ) ;
967983 const [ inputValue , setInputValue ] = useState ( '' ) ;
984+ const [ imagePaths , setImagePaths ] = useState < string [ ] > ( [ ] ) ;
968985 const [ position , setPosition ] = useState < { top : number ; right : number } > ( { top : 0 , right : 0 } ) ;
969986 const inputRef = useRef < HTMLTextAreaElement > ( null ) ;
970987
971988 useEffect ( ( ) => {
972989 if ( step === 'input' ) inputRef . current ?. focus ( ) ;
973990 } , [ step ] ) ;
974991
992+ // Notify parent when locked (in input mode)
993+ useEffect ( ( ) => {
994+ onLockChange ?.( step === 'input' ) ;
995+ } , [ step , onLockChange ] ) ;
996+
975997 // Update position on scroll/resize
976998 useEffect ( ( ) => {
977999 const updatePosition = ( ) => {
@@ -996,8 +1018,8 @@ const CodeBlockToolbar: React.FC<{
9961018
9971019 const handleSubmit = ( e : React . FormEvent ) => {
9981020 e . preventDefault ( ) ;
999- if ( inputValue . trim ( ) ) {
1000- onAnnotate ( AnnotationType . COMMENT , inputValue ) ;
1021+ if ( inputValue . trim ( ) || imagePaths . length > 0 ) {
1022+ onAnnotate ( AnnotationType . COMMENT , inputValue || undefined , imagePaths . length > 0 ? imagePaths : undefined ) ;
10011023 }
10021024 } ;
10031025
@@ -1078,15 +1100,24 @@ const CodeBlockToolbar: React.FC<{
10781100 onChange = { e => setInputValue ( e . target . value ) }
10791101 onKeyDown = { e => {
10801102 if ( e . key === 'Escape' ) setStep ( 'menu' ) ;
1081- if ( e . key === 'Enter' && ( e . metaKey || e . ctrlKey ) && inputValue . trim ( ) ) {
1103+ // Enter to submit, Shift+Enter for newline
1104+ if ( e . key === 'Enter' && ! e . shiftKey ) {
10821105 e . preventDefault ( ) ;
1083- onAnnotate ( AnnotationType . COMMENT , inputValue ) ;
1106+ if ( inputValue . trim ( ) || imagePaths . length > 0 ) {
1107+ onAnnotate ( AnnotationType . COMMENT , inputValue || undefined , imagePaths . length > 0 ? imagePaths : undefined ) ;
1108+ }
10841109 }
10851110 } }
10861111 />
1112+ < AttachmentsButton
1113+ paths = { imagePaths }
1114+ onAdd = { ( path ) => setImagePaths ( prev => [ ...prev , path ] ) }
1115+ onRemove = { ( path ) => setImagePaths ( prev => prev . filter ( p => p !== path ) ) }
1116+ variant = "inline"
1117+ />
10871118 < button
10881119 type = "submit"
1089- disabled = { ! inputValue . trim ( ) }
1120+ disabled = { ! inputValue . trim ( ) && imagePaths . length === 0 }
10901121 className = "px-[15px] py-1 text-xs font-medium rounded bg-primary text-primary-foreground hover:opacity-90 disabled:opacity-50 transition-opacity self-stretch"
10911122 >
10921123 Save
0 commit comments