1+ import {
2+ DragEvent ,
3+ forwardRef ,
4+ KeyboardEvent ,
5+ memo ,
6+ MouseEvent ,
7+ useImperativeHandle ,
8+ useRef ,
9+ useState
10+ } from 'react'
11+ import { Editor as TTEditor } from '@tiptap/core'
12+ import { EditorContent } from '@tiptap/react'
13+
14+ import { ActiveEditorComment , BlurAtTopOptions } from '@gitmono/editor'
15+ import { LayeredHotkeys } from '@gitmono/ui'
16+
17+ import { AttachmentLightbox } from '@/components/AttachmentLightbox'
18+ import { MentionList } from '@/components/MarkdownEditor/MentionList'
19+ import { ReactionList } from '@/components/MarkdownEditor/ReactionList'
20+ import { ResourceMentionList } from '@/components/MarkdownEditor/ResourceMentionList'
21+ import { ADD_ATTACHMENT_SHORTCUT , SlashCommand } from '@/components/Post/Notes/SlashCommand'
22+ import { useAutoScroll } from '@/hooks/useAutoScroll'
23+ import { EMPTY_HTML } from '@/atoms/markdown'
24+ import { CodeBlockLanguagePicker } from '@/components/CodeBlockLanguagePicker'
25+ import { EditorBubbleMenu } from '@/components/EditorBubbleMenu'
26+ import { MentionInteractivity } from '@/components/InlinePost/MemberHovercard'
27+ import { DropProps , useEditorFileHandlers } from '@/components/MarkdownEditor/useEditorFileHandlers'
28+ import { HighlightCommentPopover } from '@/components/NoteComments/HighlightCommentPopover'
29+ import { useUploadNoteAttachments } from '@/components/Post/Notes/Attachments/useUploadAttachments'
30+ import { NoteCommentPreview } from '@/components/Post/Notes/CommentRenderer'
31+ import { useSimpleNoteEditor } from '@/components/SimpleNoteEditor/useSimpleNoteEditor'
32+
33+ interface Props {
34+ commentId : string
35+ editable ?: 'all' | 'viewer'
36+ autofocus ?: boolean
37+ content : string
38+ onBlurAtTop ?: BlurAtTopOptions [ 'onBlur' ]
39+ onKeyDown ?: ( event : KeyboardEvent ) => void
40+ }
41+
42+ export interface SimpleNoteContentRef {
43+ focus ( pos : 'start' | 'end' | 'restore' | 'start-newline' | MouseEvent ) : void
44+ handleDrop ( props : DropProps ) : void
45+ handleDragOver ( isOver : boolean , event : DragEvent ) : void
46+ editor : TTEditor | null
47+ clearAndBlur ( ) : void
48+ }
49+
50+ export const SimpleNoteContent = memo (
51+ forwardRef < SimpleNoteContentRef , Props > ( ( props , ref ) => {
52+ const { commentId, editable = 'viewer' , autofocus = false , onBlurAtTop, content } = props
53+
54+ const [ activeComment , setActiveComment ] = useState < ActiveEditorComment | null > ( null )
55+ const [ hoverComment , setHoverComment ] = useState < ActiveEditorComment | null > ( null )
56+ const [ openAttachmentId , setOpenAttachmentId ] = useState < string | undefined > ( )
57+
58+ const canUploadAttachments = editable === 'all'
59+ const upload = useUploadNoteAttachments ( { noteId : commentId , enabled : canUploadAttachments } )
60+
61+ const editor = useSimpleNoteEditor ( {
62+ content,
63+ autofocus,
64+ editable : editable ,
65+ onHoverComment : setHoverComment ,
66+ onActiveComment : setActiveComment ,
67+ onOpenAttachment : setOpenAttachmentId ,
68+ onBlurAtTop
69+ } )
70+
71+ const { onDrop, onPaste, imperativeHandlers } = useEditorFileHandlers ( {
72+ enabled : canUploadAttachments ,
73+ upload,
74+ editor
75+ } )
76+
77+ // these functions allow us to call editorRef?.current?.handleDrop() etc. on the parent container
78+ useImperativeHandle (
79+ ref ,
80+ ( ) => ( {
81+ clearAndBlur : ( ) => editor . chain ( ) . setContent ( EMPTY_HTML ) . blur ( ) . run ( ) ,
82+ focus : ( pos ) => {
83+ if ( pos === 'start' ) {
84+ editor . commands . focus ( 'start' )
85+ } else if ( pos === 'start-newline' ) {
86+ editor . commands . focus ( 'start' )
87+ editor . commands . insertContent ( '\n' )
88+ } else if ( pos === 'end' ) {
89+ editor . commands . focus ( 'end' )
90+ } else if ( pos === 'restore' ) {
91+ editor . commands . focus ( )
92+ } else if ( 'clientX' in pos && 'clientY' in pos && 'target' in pos ) {
93+ if ( editor . view . dom . contains ( pos . target as Node ) ) {
94+ return
95+ }
96+
97+ const { left, right, top } = editor . view . dom . getBoundingClientRect ( )
98+ const isRight = pos . clientX > right
99+ const editorPos = editor . view . posAtCoords ( {
100+ left : isRight ? right : left ,
101+ top : pos . clientY
102+ } )
103+
104+ if ( editorPos ) {
105+ const posAdjustment = isRight && editor . view . coordsAtPos ( editorPos . pos ) . left === left ? - 1 : 0
106+
107+ editor . commands . focus ( editorPos . pos + posAdjustment )
108+ } else if ( pos . clientY < top ) {
109+ editor . commands . focus ( 'start' )
110+ } else {
111+ editor . commands . focus ( 'end' )
112+ }
113+ }
114+ } ,
115+ ...imperativeHandlers ,
116+ editor
117+ } ) ,
118+ [ editor , imperativeHandlers ]
119+ )
120+
121+ const containerRef = useRef < HTMLDivElement > ( null )
122+
123+ useAutoScroll ( {
124+ ref : containerRef ,
125+ enabled : true
126+ } )
127+
128+ return (
129+ < div ref = { containerRef } className = "relative min-h-[160px]" >
130+ < LayeredHotkeys
131+ keys = { ADD_ATTACHMENT_SHORTCUT }
132+ callback = { ( ) => {
133+ if ( ! editor . isFocused ) return
134+
135+ const input = document . createElement ( 'input' )
136+
137+ input . type = 'file'
138+ input . onchange = async ( ) => {
139+ if ( input . files ?. length ) {
140+ upload ( {
141+ files : Array . from ( input . files ) ,
142+ editor
143+ } )
144+ }
145+ }
146+ input . click ( )
147+ } }
148+ options = { { enableOnContentEditable : true , enableOnFormTags : true } }
149+ />
150+
151+ < NoteCommentPreview
152+ onExpand = { ( ) => {
153+ if ( hoverComment ) {
154+ setHoverComment ( null )
155+ setActiveComment ( hoverComment )
156+ }
157+ } }
158+ previewComment = { activeComment ? null : hoverComment }
159+ editor = { editor }
160+ noteId = { commentId }
161+ />
162+ < MentionInteractivity container = { containerRef } />
163+ < CodeBlockLanguagePicker editor = { editor } />
164+ < SlashCommand editor = { editor } upload = { upload } />
165+ < MentionList editor = { editor } />
166+ < ResourceMentionList editor = { editor } />
167+ < ReactionList editor = { editor } />
168+
169+ < AttachmentLightbox
170+ selectedAttachmentId = { openAttachmentId }
171+ onClose = { ( ) => setOpenAttachmentId ( undefined ) }
172+ onSelectAttachment = { ( { id } ) => setOpenAttachmentId ( id ) }
173+ />
174+
175+ < HighlightCommentPopover
176+ activeComment = { activeComment }
177+ editor = { editor }
178+ noteId = { commentId }
179+ onCommentDeactivated = { ( ) => setActiveComment ( null ) }
180+ />
181+
182+ < EditorBubbleMenu editor = { editor } canComment />
183+
184+ < EditorContent editor = { editor } onKeyDown = { props . onKeyDown } onPaste = { onPaste } onDrop = { onDrop } />
185+ </ div >
186+ )
187+ } )
188+ )
189+
190+ SimpleNoteContent . displayName = 'SimpleNoteContent'
0 commit comments