From 9f328bc818ac3d4e781e1501a9a7c2580bf9b600 Mon Sep 17 00:00:00 2001 From: Hyko Date: Sun, 26 Apr 2026 15:06:31 -0400 Subject: [PATCH] feat(BlockEditor): add setMark utility and wire link submission to it - add `setMark` helper in `types.js` that removes then applies a mark, replacing existing marks of the same type without toggling - expose `applySetMark` in `BlockEditor.client.js` and pass it as `onSetMark` prop to `InlineToolbar` - switch `handleLinkSubmit` in `Toolbar.client.js` to use `onSetMark` instead of `onToggleMark` so re-submitting a link always applies the new href --- .../components/BlockEditor/BlockEditor.client.js | 16 ++++++++++++++++ .../BlockEditor/inline/Toolbar.client.js | 6 +++--- .../components/BlockEditor/inline/types.js | 7 +++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/shared/components/BlockEditor/BlockEditor.client.js b/src/shared/components/BlockEditor/BlockEditor.client.js index fcab9dc..ef4b2cd 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, + setMark, removeAllMarks, marksInRange, collectUsedColors, @@ -625,6 +626,20 @@ export default function BlockEditor({ }); } + function applySetMark(mark) { + if (!toolbar) return; + const { blockId, start, end } = toolbar; + const block = blocks.find(b => b.id === blockId); + if (!block) return; + const next = setMark(block.content ?? [], start, end, mark); + 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); + }); + } + function applyRemoveAllMarks() { if (!toolbar) return; const { blockId, start, end } = toolbar; @@ -1073,6 +1088,7 @@ export default function BlockEditor({ activeMarks={marks} usedColors={usedColors} onToggleMark={applyToggleMark} + onSetMark={applySetMark} onClearMarks={applyRemoveAllMarks} onPinChange={(p) => { toolbarPinnedRef.current = p; }} /> diff --git a/src/shared/components/BlockEditor/inline/Toolbar.client.js b/src/shared/components/BlockEditor/inline/Toolbar.client.js index 1cef64d..642731f 100644 --- a/src/shared/components/BlockEditor/inline/Toolbar.client.js +++ b/src/shared/components/BlockEditor/inline/Toolbar.client.js @@ -23,7 +23,7 @@ const SIMPLE_BUTTONS = [ { type: 'code', label: , title: 'Code (Ctrl+E)', className: '' }, ]; -export default function InlineToolbar({ rect, activeMarks, onToggleMark, onClearMarks, onPinChange, usedColors }) { +export default function InlineToolbar({ rect, activeMarks, onToggleMark, onSetMark, 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 @@ -75,9 +75,9 @@ export default function InlineToolbar({ rect, activeMarks, onToggleMark, onClear } function handleLinkSubmit(e) { - e.preventDefault(); + e.preventDefault?.(); if (!linkUrl) return; - onToggleMark?.({ type: 'link', href: linkUrl, newTab: linkNewTab }); + onSetMark?.({ type: 'link', href: linkUrl, newTab: linkNewTab }); setLinkUrl(''); setPopover(null); } diff --git a/src/shared/components/BlockEditor/inline/types.js b/src/shared/components/BlockEditor/inline/types.js index 2b24f0d..9eb4e3d 100644 --- a/src/shared/components/BlockEditor/inline/types.js +++ b/src/shared/components/BlockEditor/inline/types.js @@ -304,6 +304,13 @@ export function removeMark(nodes, start, end, type) { return mapRange(nodes, start, end, n => removeMarkFromNode(n, type)); } +// Retire le type existant et applique la nouvelle mark — utile pour remplacer +// un lien sans passer par le toggle (qui retirerait si même href). +export function setMark(nodes, start, end, mark) { + const removed = removeMark(nodes, start, end, mark.type); + return applyMark(removed, start, end, mark); +} + // Toggle : si toute la plage porte déjà la mark (au sens markKey strict), // on la retire ; sinon on l'applique partout. export function toggleMark(nodes, start, end, mark) {