diff --git a/src/shared/components/BlockEditor/BlockEditor.client.js b/src/shared/components/BlockEditor/BlockEditor.client.js index c5abadc..4c46469 100644 --- a/src/shared/components/BlockEditor/BlockEditor.client.js +++ b/src/shared/components/BlockEditor/BlockEditor.client.js @@ -13,6 +13,7 @@ import { sliceInline, concatInline, toggleMark, + removeAllMarks, marksInRange, collectUsedColors, } from './inline/types.js'; @@ -509,7 +510,20 @@ export default function BlockEditor({ const next = toggleMark(block.content ?? [], start, end, mark); const nextBlocks = blocks.map(b => (b.id === blockId ? { ...b, content: next } : b)); commitChange(nextBlocks, { immediate: true }); - // Restaure la sélection après le re-render. + requestAnimationFrame(() => { + const ref = blockRefs.current.get(blockId); + ref?.setCaretRange?.(start, end); + }); + } + + function applyRemoveAllMarks() { + if (!toolbar) return; + const { blockId, start, end } = toolbar; + const block = blocks.find(b => b.id === blockId); + if (!block) return; + const next = removeAllMarks(block.content ?? [], start, end); + const nextBlocks = blocks.map(b => (b.id === blockId ? { ...b, content: next } : b)); + commitChange(nextBlocks, { immediate: true }); requestAnimationFrame(() => { const ref = blockRefs.current.get(blockId); ref?.setCaretRange?.(start, end); @@ -851,6 +865,7 @@ export default function BlockEditor({ activeMarks={marks} usedColors={usedColors} onToggleMark={applyToggleMark} + onClearMarks={applyRemoveAllMarks} onPinChange={(p) => { toolbarPinnedRef.current = p; }} /> ); diff --git a/src/shared/components/BlockEditor/README.md b/src/shared/components/BlockEditor/README.md index f979768..e322cd1 100644 --- a/src/shared/components/BlockEditor/README.md +++ b/src/shared/components/BlockEditor/README.md @@ -75,7 +75,7 @@ Le contenu vide est `[]` (jamais `[{type:'text', text:''}]`). ```js import { inlineLength, inlineToPlainText, inlineFromText, - sliceInline, concatInline, applyMark, toggleMark, + sliceInline, concatInline, applyMark, toggleMark, removeAllMarks, marksAtOffset, marksInRange, INLINE_COLORS, isHexColor, collectUsedColors, } from '@zen/core/shared/components/BlockEditor'; @@ -126,6 +126,7 @@ apparaît au-dessus. Il propose : ``) - **◐** — surlignage (même structure de popover) - **🔗** — lien (popover avec input URL ; ✕ pour retirer) +- **T/** — effacer tout le formatage de la sélection (supprime toutes les marks) L'état actif est calculé à partir des marks **communes à toute la plage** (via `marksInRange`). Toggle off si toute la plage est déjà marquée. diff --git a/src/shared/components/BlockEditor/inline/Toolbar.client.js b/src/shared/components/BlockEditor/inline/Toolbar.client.js index 16187d4..75e6196 100644 --- a/src/shared/components/BlockEditor/inline/Toolbar.client.js +++ b/src/shared/components/BlockEditor/inline/Toolbar.client.js @@ -1,7 +1,7 @@ 'use client'; import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; -import { TextColorIcon, HighlighterIcon, Link02Icon, CodeSimpleIcon } from '@zen/core/shared/icons'; +import { TextColorIcon, HighlighterIcon, Link02Icon, CodeSimpleIcon, TextClearIcon } from '@zen/core/shared/icons'; import { INLINE_COLORS, INLINE_COLOR_KEYS, markKey } from './types.js'; // Toolbar flottant de formatage. Affiché tant qu'une sélection non-vide @@ -23,7 +23,7 @@ const SIMPLE_BUTTONS = [ { type: 'code', label: , title: 'Code (Ctrl+E)', className: '' }, ]; -export default function InlineToolbar({ rect, activeMarks, onToggleMark, onPinChange, usedColors }) { +export default function InlineToolbar({ rect, activeMarks, onToggleMark, onClearMarks, onPinChange, usedColors }) { const ref = useRef(null); const [pos, setPos] = useState({ top: 0, left: 0, flipped: false }); const [popover, setPopover] = useState(null); // 'color' | 'highlight' | 'link' | null @@ -146,6 +146,18 @@ export default function InlineToolbar({ rect, activeMarks, onToggleMark, onPinCh + + + + {popover === 'color' && ( addMarkToNode(n, mark)); } +export function removeAllMarks(nodes, start, end) { + return mapRange(nodes, start, end, n => makeNode(n.text)); +} + export function removeMark(nodes, start, end, type) { return mapRange(nodes, start, end, n => removeMarkFromNode(n, type)); } diff --git a/src/shared/icons/index.js b/src/shared/icons/index.js index 23bdfeb..7f14cee 100644 --- a/src/shared/icons/index.js +++ b/src/shared/icons/index.js @@ -692,4 +692,11 @@ export const CodeSimpleIcon = (props) => ( -); \ No newline at end of file +); + +export const TextClearIcon = (props) => ( + + + + +);