feat: sélection texte → Toggle/Callout dans le BubbleMenu
Some checks failed
CI / Deploy production (on server) (push) Has been cancelled
CI / Lint, Unit Tests & Build (push) Has been cancelled

Quand l'utilisateur sélectionne du texte (1 ou plusieurs paragraphes),
la barre flottante affiche deux boutons:
- ChevronsRightLeft → enveloppe dans un Toggle
- MessageSquareWarning → enveloppe dans un Callout
Le contenu sélectionné devient le contenu du bloc.
This commit is contained in:
Antigravity
2026-06-20 16:14:07 +00:00
parent af3f130549
commit 018db001b4
2 changed files with 29 additions and 2 deletions

View File

@@ -1350,6 +1350,28 @@ function BubbleToolbar({ editor, onSuggestCharts }: { editor: Editor | null; onS
toast.error(msg && msg !== AI_REFORMULATE_FALLBACK ? msg : t('richTextEditor.aiReformulateFailed')) toast.error(msg && msg !== AI_REFORMULATE_FALLBACK ? msg : t('richTextEditor.aiReformulateFailed'))
} }
// Wrap the current text selection in a container block (toggle or callout)
const wrapSelectionInBlock = useCallback((blockType: string) => {
if (!editor) return
const { from, to } = editor.state.selection
if (from === to) return
// Get the selected slice (all blocks in the range)
const slice = editor.state.doc.slice(from, to)
const content = slice.content
// Create the container node with the selected content inside
const containerNode = editor.state.schema.nodes[blockType].create(
blockType === 'calloutBlock' ? { type: 'info' } : { opened: true },
content,
)
// Replace the selection with the container
const tr = editor.state.tr
tr.replaceRangeWith(from, to, containerNode)
editor.view.dispatch(tr)
}, [editor])
const [aiOpen, setAiOpen] = useState(false) const [aiOpen, setAiOpen] = useState(false)
const [aiLoading, setAiLoading] = useState(false) const [aiLoading, setAiLoading] = useState(false)
const [linkOpen, setLinkOpen] = useState(false) const [linkOpen, setLinkOpen] = useState(false)
@@ -1477,6 +1499,13 @@ function BubbleToolbar({ editor, onSuggestCharts }: { editor: Editor | null; onS
<div className="w-px h-4 bg-gray-300 dark:bg-gray-600 mx-0.5" /> <div className="w-px h-4 bg-gray-300 dark:bg-gray-600 mx-0.5" />
<button onClick={openLinkEditor} className={cn('notion-bubble-btn rounded-md', editorState.isLink && 'notion-bubble-btn-active')}><LinkIcon className="w-3.5 h-3.5" /></button> <button onClick={openLinkEditor} className={cn('notion-bubble-btn rounded-md', editorState.isLink && 'notion-bubble-btn-active')}><LinkIcon className="w-3.5 h-3.5" /></button>
<button onClick={() => setAiOpen(!aiOpen)} className={cn('notion-bubble-btn rounded-md', aiLoading && 'animate-pulse')}><Sparkles className="w-3.5 h-3.5" /></button> <button onClick={() => setAiOpen(!aiOpen)} className={cn('notion-bubble-btn rounded-md', aiLoading && 'animate-pulse')}><Sparkles className="w-3.5 h-3.5" /></button>
<div className="w-px h-4 bg-gray-300 dark:bg-gray-600 mx-0.5" />
<button onClick={() => wrapSelectionInBlock('toggleBlock')} title="Section repliable" className="notion-bubble-btn rounded-md">
<ChevronsRightLeft className="w-3.5 h-3.5" />
</button>
<button onClick={() => wrapSelectionInBlock('calloutBlock')} title="Encadré" className="notion-bubble-btn rounded-md">
<MessageSquareWarning className="w-3.5 h-3.5" />
</button>
{editorState.isImage && ( {editorState.isImage && (
<> <>
<div className="w-px h-4 bg-gray-300 dark:bg-gray-600 mx-0.5" /> <div className="w-px h-4 bg-gray-300 dark:bg-gray-600 mx-0.5" />

View File

@@ -296,8 +296,6 @@ export const MathEquationExtension = Node.create({
] ]
}, },
}) })
},
})
// Inline math mark — detects $...$ in text // Inline math mark — detects $...$ in text
export const InlineMathExtension = Node.create({ export const InlineMathExtension = Node.create({