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
This commit is contained in:
2026-04-26 15:06:31 -04:00
parent 0941994e44
commit 9f328bc818
3 changed files with 26 additions and 3 deletions
@@ -13,6 +13,7 @@ import {
sliceInline, sliceInline,
concatInline, concatInline,
toggleMark, toggleMark,
setMark,
removeAllMarks, removeAllMarks,
marksInRange, marksInRange,
collectUsedColors, 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() { function applyRemoveAllMarks() {
if (!toolbar) return; if (!toolbar) return;
const { blockId, start, end } = toolbar; const { blockId, start, end } = toolbar;
@@ -1073,6 +1088,7 @@ export default function BlockEditor({
activeMarks={marks} activeMarks={marks}
usedColors={usedColors} usedColors={usedColors}
onToggleMark={applyToggleMark} onToggleMark={applyToggleMark}
onSetMark={applySetMark}
onClearMarks={applyRemoveAllMarks} onClearMarks={applyRemoveAllMarks}
onPinChange={(p) => { toolbarPinnedRef.current = p; }} onPinChange={(p) => { toolbarPinnedRef.current = p; }}
/> />
@@ -23,7 +23,7 @@ const SIMPLE_BUTTONS = [
{ type: 'code', label: <CodeSimpleIcon width={15} height={15} />, title: 'Code (Ctrl+E)', className: '' }, { type: 'code', label: <CodeSimpleIcon width={15} height={15} />, 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 ref = useRef(null);
const [pos, setPos] = useState({ top: 0, left: 0, flipped: false }); const [pos, setPos] = useState({ top: 0, left: 0, flipped: false });
const [popover, setPopover] = useState(null); // 'color' | 'highlight' | 'link' | null const [popover, setPopover] = useState(null); // 'color' | 'highlight' | 'link' | null
@@ -75,9 +75,9 @@ export default function InlineToolbar({ rect, activeMarks, onToggleMark, onClear
} }
function handleLinkSubmit(e) { function handleLinkSubmit(e) {
e.preventDefault(); e.preventDefault?.();
if (!linkUrl) return; if (!linkUrl) return;
onToggleMark?.({ type: 'link', href: linkUrl, newTab: linkNewTab }); onSetMark?.({ type: 'link', href: linkUrl, newTab: linkNewTab });
setLinkUrl(''); setLinkUrl('');
setPopover(null); setPopover(null);
} }
@@ -304,6 +304,13 @@ export function removeMark(nodes, start, end, type) {
return mapRange(nodes, start, end, n => removeMarkFromNode(n, 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), // Toggle : si toute la plage porte déjà la mark (au sens markKey strict),
// on la retire ; sinon on l'applique partout. // on la retire ; sinon on l'applique partout.
export function toggleMark(nodes, start, end, mark) { export function toggleMark(nodes, start, end, mark) {