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,
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; }}
/>
@@ -23,7 +23,7 @@ const SIMPLE_BUTTONS = [
{ 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 [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);
}
@@ -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) {