perf(editor): optimize typing performance by debouncing context state updates and using useEditorState for BubbleToolbar (US-EDITOR-PERF)
This commit is contained in:
@@ -51,7 +51,27 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
}, [session?.user?.id])
|
||||
|
||||
const [title, setTitle] = useState(note.title || '')
|
||||
const [content, setContent] = useState(note.content)
|
||||
const contentRef = useRef(note.content)
|
||||
const [content, setContentState] = useState(note.content)
|
||||
const debounceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
const setContentImmediate = useCallback((newVal: string) => {
|
||||
contentRef.current = newVal
|
||||
if (debounceTimeoutRef.current) {
|
||||
clearTimeout(debounceTimeoutRef.current)
|
||||
}
|
||||
setContentState(newVal)
|
||||
}, [])
|
||||
|
||||
const setContent = useCallback((newVal: string) => {
|
||||
contentRef.current = newVal
|
||||
if (debounceTimeoutRef.current) {
|
||||
clearTimeout(debounceTimeoutRef.current)
|
||||
}
|
||||
debounceTimeoutRef.current = setTimeout(() => {
|
||||
setContentState(newVal)
|
||||
}, 1000)
|
||||
}, [])
|
||||
const [checkItems, setCheckItems] = useState<CheckItem[]>(note.checkItems || [])
|
||||
const [labels, setLabels] = useState<string[]>(note.labels || [])
|
||||
const [images, setImages] = useState<string[]>(note.images || [])
|
||||
@@ -74,7 +94,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
|
||||
if (note.id !== prev.id) {
|
||||
setTitle(note.title || '')
|
||||
setContent(note.content)
|
||||
setContentImmediate(note.content)
|
||||
setCheckItems(note.checkItems || [])
|
||||
setLabels(note.labels || [])
|
||||
setImages(note.images || [])
|
||||
@@ -87,7 +107,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
} else {
|
||||
if (note.title !== prev.title) setTitle(note.title || '')
|
||||
// Ne pas réinitialiser le contenu quand seuls images/links changent (post-save)
|
||||
if (note.content !== prev.content) setContent(note.content)
|
||||
if (note.content !== prev.content) setContentImmediate(note.content)
|
||||
if (JSON.stringify(note.checkItems || []) !== JSON.stringify(prev.checkItems || [])) {
|
||||
setCheckItems(note.checkItems || [])
|
||||
}
|
||||
@@ -309,8 +329,8 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
const editor = richTextEditorRef.current?.getEditor()
|
||||
if (editor) return editor.getHTML()
|
||||
}
|
||||
return content
|
||||
}, [content, isMarkdown])
|
||||
return contentRef.current
|
||||
}, [isMarkdown])
|
||||
|
||||
const resolveImagesForSave = useCallback((contentToSave: string): string[] => {
|
||||
// Images présentes dans le contenu de l'éditeur (inline dans le HTML ou Markdown)
|
||||
@@ -472,7 +492,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
})
|
||||
const data = await response.json()
|
||||
if (!response.ok) throw new Error(data.error || t('notes.clarifyFailed'))
|
||||
setContent(data.reformulatedText || data.text)
|
||||
setContentImmediate(data.reformulatedText || data.text)
|
||||
toast.success(t('ai.reformulationApplied'))
|
||||
} catch (error) {
|
||||
console.error('Clarify error:', error)
|
||||
@@ -501,7 +521,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
})
|
||||
const data = await response.json()
|
||||
if (!response.ok) throw new Error(data.error || t('notes.shortenFailed'))
|
||||
setContent(data.reformulatedText || data.text)
|
||||
setContentImmediate(data.reformulatedText || data.text)
|
||||
toast.success(t('ai.reformulationApplied'))
|
||||
} catch (error) {
|
||||
console.error('Shorten error:', error)
|
||||
@@ -530,7 +550,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
})
|
||||
const data = await response.json()
|
||||
if (!response.ok) throw new Error(data.error || t('notes.improveFailed'))
|
||||
setContent(data.reformulatedText || data.text)
|
||||
setContentImmediate(data.reformulatedText || data.text)
|
||||
toast.success(t('ai.reformulationApplied'))
|
||||
} catch (error) {
|
||||
console.error('Improve error:', error)
|
||||
@@ -565,7 +585,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
const data = await response.json()
|
||||
if (!response.ok) throw new Error(data.error || t('notes.transformFailed'))
|
||||
|
||||
setContent(data.transformedText)
|
||||
setContentImmediate(data.transformedText)
|
||||
setIsMarkdown(true)
|
||||
setShowMarkdownPreview(false)
|
||||
|
||||
@@ -581,7 +601,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
const handleApplyRefactor = () => {
|
||||
if (!reformulationModal) return
|
||||
|
||||
setContent(reformulationModal.reformulatedText)
|
||||
setContentImmediate(reformulationModal.reformulatedText)
|
||||
setReformulationModal(null)
|
||||
toast.success(t('ai.reformulationApplied'))
|
||||
}
|
||||
@@ -628,7 +648,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
type: isMarkdown ? 'markdown' as const : 'richtext' as const,
|
||||
size,
|
||||
}, { skipRevalidation: true })
|
||||
if (contentToSave !== content) setContent(contentToSave)
|
||||
if (contentToSave !== content) setContentImmediate(contentToSave)
|
||||
if (JSON.stringify(imagesToSave) !== JSON.stringify(images)) setImages(imagesToSave)
|
||||
prevNoteRef.current = { ...prevNoteRef.current, ...result }
|
||||
const deletedImages = Array.from(new Set([
|
||||
@@ -752,7 +772,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
size,
|
||||
}
|
||||
const result = await updateNote(note.id, updatePayload, { skipRevalidation: true })
|
||||
if (contentToSave !== content) setContent(contentToSave)
|
||||
if (contentToSave !== content) setContentImmediate(contentToSave)
|
||||
if (JSON.stringify(imagesToSave) !== JSON.stringify(images)) setImages(imagesToSave)
|
||||
prevNoteRef.current = { ...prevNoteRef.current, ...result }
|
||||
const deletedImages = Array.from(new Set([
|
||||
@@ -853,7 +873,7 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
isAnalyzingSuggestions, isMarkdown, allImages, colorClasses
|
||||
])
|
||||
|
||||
const actions: NoteEditorActions = {
|
||||
const actions: NoteEditorActions = useMemo(() => ({
|
||||
setTitle: (t) => { setTitle(t); setIsDirty(true); setDismissedTitleSuggestions(true) },
|
||||
setDismissedTitleSuggestions,
|
||||
setContent: (c) => { setContent(c); setIsDirty(true) },
|
||||
@@ -904,7 +924,14 @@ export function NoteEditorProvider({ note, readOnly = false, fullPage = false, o
|
||||
setIsGeneratingTitles,
|
||||
setIsAnalyzingSuggestions: (_a) => { /* handled by useAutoTagging */ },
|
||||
setPreviousContentForCopilot,
|
||||
}
|
||||
}), [
|
||||
handleCheckItem, handleUpdateCheckItem, handleAddCheckItem, handleRemoveCheckItem,
|
||||
handleSelectGhostTag, handleDismissGhostTag, handleRemoveLabel, handleImageUpload,
|
||||
handleRemoveImage, handleAddLink, handleRemoveLink, handleReminderSave,
|
||||
handleRemoveReminder, handleGenerateTitles, handleSelectTitle, handleReformulate,
|
||||
handleApplyRefactor, handleClarifyDirect, handleShortenDirect, handleImproveDirect,
|
||||
handleTransformMarkdown, handleSave, handleSaveInPlace, handleMakeCopy
|
||||
])
|
||||
|
||||
const value: NoteEditorContextValue = useMemo(() => ({
|
||||
note,
|
||||
|
||||
Reference in New Issue
Block a user