1- import { useImperativeHandle , useRef , MutableRefObject , useState , useEffect } from "react" ;
21import { HocuspocusProvider } from "@hocuspocus/provider" ;
32import { DOMSerializer } from "@tiptap/pm/model" ;
4- import { Selection } from "@tiptap/pm/state" ;
53import { EditorProps } from "@tiptap/pm/view" ;
6- import { useEditor as useTiptapEditor , Editor , Extensions } from "@tiptap/react" ;
4+ import { useEditor as useTiptapEditor , Extensions } from "@tiptap/react" ;
5+ import { useImperativeHandle , MutableRefObject , useEffect } from "react" ;
76import * as Y from "yjs" ;
87// components
9- import { EditorMenuItem , getEditorMenuItems } from "@/components/menus" ;
8+ import { getEditorMenuItems } from "@/components/menus" ;
109// extensions
1110import { CoreEditorExtensions } from "@/extensions" ;
1211// helpers
@@ -71,14 +70,12 @@ export const useEditor = (props: CustomEditorProps) => {
7170 provider,
7271 autofocus = false ,
7372 } = props ;
74- // states
75- const [ savedSelection , setSavedSelection ] = useState < Selection | null > ( null ) ;
76- // refs
77- const editorRef : MutableRefObject < Editor | null > = useRef ( null ) ;
78- const savedSelectionRef = useRef ( savedSelection ) ;
73+
7974 const editor = useTiptapEditor (
8075 {
8176 editable,
77+ immediatelyRender : false ,
78+ shouldRerenderOnTransaction : false ,
8279 autofocus,
8380 editorProps : {
8481 ...CoreEditorProps ( {
@@ -100,8 +97,7 @@ export const useEditor = (props: CustomEditorProps) => {
10097 ] ,
10198 content : typeof initialValue === "string" && initialValue . trim ( ) !== "" ? initialValue : "<p></p>" ,
10299 onCreate : ( ) => handleEditorReady ?.( true ) ,
103- onTransaction : ( { editor } ) => {
104- setSavedSelection ( editor . state . selection ) ;
100+ onTransaction : ( ) => {
105101 onTransaction ?.( ) ;
106102 } ,
107103 onUpdate : ( { editor } ) => onChange ?.( editor . getJSON ( ) , editor . getHTML ( ) ) ,
@@ -110,23 +106,17 @@ export const useEditor = (props: CustomEditorProps) => {
110106 [ editable ]
111107 ) ;
112108
113- // Update the ref whenever savedSelection changes
114- useEffect ( ( ) => {
115- savedSelectionRef . current = savedSelection ;
116- } , [ savedSelection ] ) ;
117-
118109 // Effect for syncing SWR data
119110 useEffect ( ( ) => {
120111 // value is null when intentionally passed where syncing is not yet
121112 // supported and value is undefined when the data from swr is not populated
122- if ( value === null || value === undefined ) return ;
113+ if ( value == null ) return ;
123114 if ( editor && ! editor . isDestroyed && ! editor . storage . imageComponent . uploadInProgress ) {
124115 try {
125116 editor . commands . setContent ( value , false , { preserveWhitespace : "full" } ) ;
126- const currentSavedSelection = savedSelectionRef . current ;
127- if ( currentSavedSelection ) {
117+ if ( editor . state . selection ) {
128118 const docLength = editor . state . doc . content . size ;
129- const relativePosition = Math . min ( currentSavedSelection . from , docLength - 1 ) ;
119+ const relativePosition = Math . min ( editor . state . selection . from , docLength - 1 ) ;
130120 editor . commands . setTextSelection ( relativePosition ) ;
131121 }
132122 } catch ( error ) {
@@ -138,46 +128,40 @@ export const useEditor = (props: CustomEditorProps) => {
138128 useImperativeHandle (
139129 forwardedRef ,
140130 ( ) => ( {
141- blur : ( ) => editorRef . current ? .commands . blur ( ) ,
131+ blur : ( ) => editor . commands . blur ( ) ,
142132 scrollToNodeViaDOMCoordinates ( behavior ?: ScrollBehavior , pos ?: number ) {
143- const resolvedPos = pos ?? savedSelection ? .from ;
144- if ( ! editorRef . current || ! resolvedPos ) return ;
145- scrollToNodeViaDOMCoordinates ( editorRef . current , resolvedPos , behavior ) ;
133+ const resolvedPos = pos ?? editor . state . selection . from ;
134+ if ( ! editor || ! resolvedPos ) return ;
135+ scrollToNodeViaDOMCoordinates ( editor , resolvedPos , behavior ) ;
146136 } ,
147- getCurrentCursorPosition : ( ) => savedSelection ? .from ,
137+ getCurrentCursorPosition : ( ) => editor . state . selection . from ,
148138 clearEditor : ( emitUpdate = false ) => {
149- editorRef . current ?. chain ( ) . setMeta ( "skipImageDeletion" , true ) . clearContent ( emitUpdate ) . run ( ) ;
139+ editor ?. chain ( ) . setMeta ( "skipImageDeletion" , true ) . clearContent ( emitUpdate ) . run ( ) ;
150140 } ,
151141 setEditorValue : ( content : string ) => {
152- editorRef . current ?. commands . setContent ( content , false , { preserveWhitespace : "full" } ) ;
142+ editor ?. commands . setContent ( content , false , { preserveWhitespace : "full" } ) ;
153143 } ,
154144 setEditorValueAtCursorPosition : ( content : string ) => {
155- if ( savedSelection ) {
156- insertContentAtSavedSelection ( editorRef , content , savedSelection ) ;
145+ if ( editor . state . selection ) {
146+ insertContentAtSavedSelection ( editor , content ) ;
157147 }
158148 } ,
159149 executeMenuItemCommand : ( props ) => {
160150 const { itemKey } = props ;
161- const editorItems = getEditorMenuItems ( editorRef . current ) ;
151+ const editorItems = getEditorMenuItems ( editor ) ;
162152
163153 const getEditorMenuItem = ( itemKey : TEditorCommands ) => editorItems . find ( ( item ) => item . key === itemKey ) ;
164154
165155 const item = getEditorMenuItem ( itemKey ) ;
166156 if ( item ) {
167- if ( item . key === "image" ) {
168- ( item as EditorMenuItem < "image" > ) . command ( {
169- savedSelection : savedSelectionRef . current ,
170- } ) ;
171- } else {
172- item . command ( props ) ;
173- }
157+ item . command ( props ) ;
174158 } else {
175159 console . warn ( `No command found for item: ${ itemKey } ` ) ;
176160 }
177161 } ,
178162 isMenuItemActive : ( props ) => {
179163 const { itemKey } = props ;
180- const editorItems = getEditorMenuItems ( editorRef . current ) ;
164+ const editorItems = getEditorMenuItems ( editor ) ;
181165
182166 const getEditorMenuItem = ( itemKey : TEditorCommands ) => editorItems . find ( ( item ) => item . key === itemKey ) ;
183167 const item = getEditorMenuItem ( itemKey ) ;
@@ -187,38 +171,38 @@ export const useEditor = (props: CustomEditorProps) => {
187171 } ,
188172 onHeadingChange : ( callback : ( headings : IMarking [ ] ) => void ) => {
189173 // Subscribe to update event emitted from headers extension
190- editorRef . current ?. on ( "update" , ( ) => {
191- callback ( editorRef . current ?. storage . headingList . headings ) ;
174+ editor ?. on ( "update" , ( ) => {
175+ callback ( editor ?. storage . headingList . headings ) ;
192176 } ) ;
193177 // Return a function to unsubscribe to the continuous transactions of
194178 // the editor on unmounting the component that has subscribed to this
195179 // method
196180 return ( ) => {
197- editorRef . current ?. off ( "update" ) ;
181+ editor ?. off ( "update" ) ;
198182 } ;
199183 } ,
200- getHeadings : ( ) => editorRef ?. current ?. storage . headingList . headings ,
184+ getHeadings : ( ) => editor ?. storage . headingList . headings ,
201185 onStateChange : ( callback : ( ) => void ) => {
202186 // Subscribe to editor state changes
203- editorRef . current ?. on ( "transaction" , ( ) => {
187+ editor ?. on ( "transaction" , ( ) => {
204188 callback ( ) ;
205189 } ) ;
206190
207191 // Return a function to unsubscribe to the continuous transactions of
208192 // the editor on unmounting the component that has subscribed to this
209193 // method
210194 return ( ) => {
211- editorRef . current ?. off ( "transaction" ) ;
195+ editor ?. off ( "transaction" ) ;
212196 } ;
213197 } ,
214198 getMarkDown : ( ) : string => {
215- const markdownOutput = editorRef . current ?. storage . markdown . getMarkdown ( ) ;
199+ const markdownOutput = editor ?. storage . markdown . getMarkdown ( ) ;
216200 return markdownOutput ;
217201 } ,
218202 getDocument : ( ) => {
219203 const documentBinary = provider ?. document ? Y . encodeStateAsUpdate ( provider ?. document ) : null ;
220- const documentHTML = editorRef . current ?. getHTML ( ) ?? "<p></p>" ;
221- const documentJSON = editorRef . current ? .getJSON ( ) ?? null ;
204+ const documentHTML = editor ?. getHTML ( ) ?? "<p></p>" ;
205+ const documentJSON = editor . getJSON ( ) ?? null ;
222206
223207 return {
224208 binary : documentBinary ,
@@ -227,19 +211,19 @@ export const useEditor = (props: CustomEditorProps) => {
227211 } ;
228212 } ,
229213 scrollSummary : ( marking : IMarking ) : void => {
230- if ( ! editorRef . current ) return ;
231- scrollSummary ( editorRef . current , marking ) ;
214+ if ( ! editor ) return ;
215+ scrollSummary ( editor , marking ) ;
232216 } ,
233- isEditorReadyToDiscard : ( ) => editorRef . current ?. storage . imageComponent . uploadInProgress === false ,
217+ isEditorReadyToDiscard : ( ) => editor ?. storage . imageComponent . uploadInProgress === false ,
234218 setFocusAtPosition : ( position : number ) => {
235- if ( ! editorRef . current || editorRef . current . isDestroyed ) {
219+ if ( ! editor || editor . isDestroyed ) {
236220 console . error ( "Editor reference is not available or has been destroyed." ) ;
237221 return ;
238222 }
239223 try {
240- const docSize = editorRef . current . state . doc . content . size ;
224+ const docSize = editor . state . doc . content . size ;
241225 const safePosition = Math . max ( 0 , Math . min ( position , docSize ) ) ;
242- editorRef . current
226+ editor
243227 . chain ( )
244228 . insertContentAt ( safePosition , [ { type : "paragraph" } ] )
245229 . focus ( )
@@ -249,17 +233,17 @@ export const useEditor = (props: CustomEditorProps) => {
249233 }
250234 } ,
251235 getSelectedText : ( ) => {
252- if ( ! editorRef . current ) return null ;
236+ if ( ! editor ) return null ;
253237
254- const { state } = editorRef . current ;
238+ const { state } = editor ;
255239 const { from, to, empty } = state . selection ;
256240
257241 if ( empty ) return null ;
258242
259243 const nodesArray : string [ ] = [ ] ;
260244 state . doc . nodesBetween ( from , to , ( node , _pos , parent ) => {
261- if ( parent === state . doc && editorRef . current ) {
262- const serializer = DOMSerializer . fromSchema ( editorRef . current ? .schema ) ;
245+ if ( parent === state . doc && editor ) {
246+ const serializer = DOMSerializer . fromSchema ( editor . schema ) ;
263247 const dom = serializer . serializeNode ( node ) ;
264248 const tempDiv = document . createElement ( "div" ) ;
265249 tempDiv . appendChild ( dom ) ;
@@ -270,28 +254,21 @@ export const useEditor = (props: CustomEditorProps) => {
270254 return selection ;
271255 } ,
272256 insertText : ( contentHTML , insertOnNextLine ) => {
273- if ( ! editorRef . current ) return ;
274- // get selection
275- const { from, to, empty } = editorRef . current . state . selection ;
257+ if ( ! editor ) return ;
258+ const { from, to, empty } = editor . state . selection ;
276259 if ( empty ) return ;
277260 if ( insertOnNextLine ) {
278261 // move cursor to the end of the selection and insert a new line
279- editorRef . current
280- . chain ( )
281- . focus ( )
282- . setTextSelection ( to )
283- . insertContent ( "<br />" )
284- . insertContent ( contentHTML )
285- . run ( ) ;
262+ editor . chain ( ) . focus ( ) . setTextSelection ( to ) . insertContent ( "<br />" ) . insertContent ( contentHTML ) . run ( ) ;
286263 } else {
287264 // replace selected text with the content provided
288- editorRef . current . chain ( ) . focus ( ) . deleteRange ( { from, to } ) . insertContent ( contentHTML ) . run ( ) ;
265+ editor . chain ( ) . focus ( ) . deleteRange ( { from, to } ) . insertContent ( contentHTML ) . run ( ) ;
289266 }
290267 } ,
291268 getDocumentInfo : ( ) => ( {
292- characters : editorRef ?. current ?. storage ?. characterCount ?. characters ?.( ) ?? 0 ,
293- paragraphs : getParagraphCount ( editorRef ?. current ?. state ) ,
294- words : editorRef ?. current ?. storage ?. characterCount ?. words ?.( ) ?? 0 ,
269+ characters : editor ?. storage ?. characterCount ?. characters ?.( ) ?? 0 ,
270+ paragraphs : getParagraphCount ( editor ?. state ) ,
271+ words : editor ?. storage ?. characterCount ?. words ?.( ) ?? 0 ,
295272 } ) ,
296273 setProviderDocument : ( value ) => {
297274 const document = provider ?. document ;
@@ -301,16 +278,12 @@ export const useEditor = (props: CustomEditorProps) => {
301278 emitRealTimeUpdate : ( message : TDocumentEventsServer ) => provider ?. sendStateless ( message ) ,
302279 listenToRealTimeUpdate : ( ) => provider && { on : provider . on . bind ( provider ) , off : provider . off . bind ( provider ) } ,
303280 } ) ,
304- [ editorRef , savedSelection ]
281+ [ editor ]
305282 ) ;
306283
307284 if ( ! editor ) {
308285 return null ;
309286 }
310287
311- // the editorRef is used to access the editor instance from outside the hook
312- // and should only be used after editor is initialized
313- editorRef . current = editor ;
314-
315288 return editor ;
316289} ;
0 commit comments