mirror of
https://github.com/vxcontrol/pentagi.git
synced 2026-08-28 05:56:42 +00:00
fix(markdown-editor): drive toolbar state via useEditorState (M4)
tiptap v3's useEditor does not re-render the component on every transaction, so the toolbar's 16 editor.isActive()/can() reads went stale on selection-only cursor moves — click into a bold word and the Bold button stayed unlit until the next doc edit. Read all button states through a single useEditorState selector, which re-runs per transaction and re-renders only when a state flips. Fixes the staleness and drops the incidental full re-render. Idiomatic tiptap v3 pattern. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e324db3488
commit
996fcf49fa
@@ -2,7 +2,7 @@ import type { Editor } from '@tiptap/react';
|
||||
|
||||
import { history } from '@tiptap/pm/history';
|
||||
import { EditorState, TextSelection } from '@tiptap/pm/state';
|
||||
import { EditorContent, useEditor } from '@tiptap/react';
|
||||
import { EditorContent, useEditor, useEditorState } from '@tiptap/react';
|
||||
import {
|
||||
Bold,
|
||||
Code,
|
||||
@@ -327,6 +327,31 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run();
|
||||
}, [editor]);
|
||||
|
||||
// tiptap v3's useEditor does NOT re-render on every transaction, so reading editor.isActive/can() inline
|
||||
// would leave the toolbar stale on selection-only moves (click into a bold word → Bold stays unlit).
|
||||
// useEditorState re-runs this selector per transaction and re-renders only when a button's state flips.
|
||||
const state = useEditorState({
|
||||
editor,
|
||||
selector: ({ editor }) => ({
|
||||
canRedo: editor.can().redo(),
|
||||
canUndo: editor.can().undo(),
|
||||
isBlockquote: editor.isActive('blockquote'),
|
||||
isBold: editor.isActive('bold'),
|
||||
isBulletList: editor.isActive('bulletList'),
|
||||
isCode: editor.isActive('code'),
|
||||
isCodeBlock: editor.isActive('codeBlock'),
|
||||
isH1: editor.isActive('heading', { level: 1 }),
|
||||
isH2: editor.isActive('heading', { level: 2 }),
|
||||
isH3: editor.isActive('heading', { level: 3 }),
|
||||
isItalic: editor.isActive('italic'),
|
||||
isLink: editor.isActive('link'),
|
||||
isOrderedList: editor.isActive('orderedList'),
|
||||
isStrike: editor.isActive('strike'),
|
||||
isTable: editor.isActive('table'),
|
||||
isTaskList: editor.isActive('taskList'),
|
||||
}),
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -338,7 +363,7 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
<Toggle
|
||||
aria-label="Bold"
|
||||
onPressedChange={() => editor.chain().focus().toggleBold().run()}
|
||||
pressed={editor.isActive('bold')}
|
||||
pressed={state.isBold}
|
||||
size="sm"
|
||||
title="Bold (Ctrl+B)"
|
||||
>
|
||||
@@ -347,7 +372,7 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
<Toggle
|
||||
aria-label="Italic"
|
||||
onPressedChange={() => editor.chain().focus().toggleItalic().run()}
|
||||
pressed={editor.isActive('italic')}
|
||||
pressed={state.isItalic}
|
||||
size="sm"
|
||||
title="Italic (Ctrl+I)"
|
||||
>
|
||||
@@ -356,7 +381,7 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
<Toggle
|
||||
aria-label="Strikethrough"
|
||||
onPressedChange={() => editor.chain().focus().toggleStrike().run()}
|
||||
pressed={editor.isActive('strike')}
|
||||
pressed={state.isStrike}
|
||||
size="sm"
|
||||
title="Strikethrough"
|
||||
>
|
||||
@@ -365,7 +390,7 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
<Toggle
|
||||
aria-label="Inline code"
|
||||
onPressedChange={() => editor.chain().focus().toggleCode().run()}
|
||||
pressed={editor.isActive('code')}
|
||||
pressed={state.isCode}
|
||||
size="sm"
|
||||
title="Inline code"
|
||||
>
|
||||
@@ -380,7 +405,7 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
<Toggle
|
||||
aria-label="Heading 1"
|
||||
onPressedChange={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
|
||||
pressed={editor.isActive('heading', { level: 1 })}
|
||||
pressed={state.isH1}
|
||||
size="sm"
|
||||
title="Heading 1"
|
||||
>
|
||||
@@ -389,7 +414,7 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
<Toggle
|
||||
aria-label="Heading 2"
|
||||
onPressedChange={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
|
||||
pressed={editor.isActive('heading', { level: 2 })}
|
||||
pressed={state.isH2}
|
||||
size="sm"
|
||||
title="Heading 2"
|
||||
>
|
||||
@@ -398,7 +423,7 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
<Toggle
|
||||
aria-label="Heading 3"
|
||||
onPressedChange={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
|
||||
pressed={editor.isActive('heading', { level: 3 })}
|
||||
pressed={state.isH3}
|
||||
size="sm"
|
||||
title="Heading 3"
|
||||
>
|
||||
@@ -413,7 +438,7 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
<Toggle
|
||||
aria-label="Bullet list"
|
||||
onPressedChange={() => editor.chain().focus().toggleBulletList().run()}
|
||||
pressed={editor.isActive('bulletList')}
|
||||
pressed={state.isBulletList}
|
||||
size="sm"
|
||||
title="Bullet list"
|
||||
>
|
||||
@@ -422,7 +447,7 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
<Toggle
|
||||
aria-label="Ordered list"
|
||||
onPressedChange={() => editor.chain().focus().toggleOrderedList().run()}
|
||||
pressed={editor.isActive('orderedList')}
|
||||
pressed={state.isOrderedList}
|
||||
size="sm"
|
||||
title="Ordered list"
|
||||
>
|
||||
@@ -431,7 +456,7 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
<Toggle
|
||||
aria-label="Task list"
|
||||
onPressedChange={() => editor.chain().focus().toggleTaskList().run()}
|
||||
pressed={editor.isActive('taskList')}
|
||||
pressed={state.isTaskList}
|
||||
size="sm"
|
||||
title="Task list"
|
||||
>
|
||||
@@ -446,7 +471,7 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
<Toggle
|
||||
aria-label="Blockquote"
|
||||
onPressedChange={() => editor.chain().focus().toggleBlockquote().run()}
|
||||
pressed={editor.isActive('blockquote')}
|
||||
pressed={state.isBlockquote}
|
||||
size="sm"
|
||||
title="Blockquote"
|
||||
>
|
||||
@@ -455,7 +480,7 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
<Toggle
|
||||
aria-label="Code block"
|
||||
onPressedChange={() => editor.chain().focus().toggleCodeBlock().run()}
|
||||
pressed={editor.isActive('codeBlock')}
|
||||
pressed={state.isCodeBlock}
|
||||
size="sm"
|
||||
title="Code block"
|
||||
>
|
||||
@@ -464,7 +489,7 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
<Toggle
|
||||
aria-label="Link"
|
||||
onPressedChange={handleSetLink}
|
||||
pressed={editor.isActive('link')}
|
||||
pressed={state.isLink}
|
||||
size="sm"
|
||||
title="Insert link"
|
||||
>
|
||||
@@ -499,7 +524,7 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
onPressedChange={() =>
|
||||
editor.chain().focus().insertTable({ cols: 3, rows: 3, withHeaderRow: true }).run()
|
||||
}
|
||||
pressed={editor.isActive('table')}
|
||||
pressed={state.isTable}
|
||||
size="sm"
|
||||
title="Insert table"
|
||||
>
|
||||
@@ -509,7 +534,7 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
<div className="ml-auto flex items-center gap-0.5">
|
||||
<Toggle
|
||||
aria-label="Undo"
|
||||
disabled={!editor.can().undo()}
|
||||
disabled={!state.canUndo}
|
||||
onPressedChange={() => editor.chain().focus().undo().run()}
|
||||
pressed={false}
|
||||
size="sm"
|
||||
@@ -519,7 +544,7 @@ function MarkdownEditorToolbar({ disabled, editor }: MarkdownEditorToolbarProps)
|
||||
</Toggle>
|
||||
<Toggle
|
||||
aria-label="Redo"
|
||||
disabled={!editor.can().redo()}
|
||||
disabled={!state.canRedo}
|
||||
onPressedChange={() => editor.chain().focus().redo().run()}
|
||||
pressed={false}
|
||||
size="sm"
|
||||
|
||||
Reference in New Issue
Block a user