// 便利贴节点:纯展示便利贴——整块可拖动、双击编辑、右上角自选颜色。 // 不再声明 resource(避免宿主在右上角显示「文本N」资源角标),也不再衍生节点。 import { definePlugin, useEffect, useRef, useState } from "@infinite-canvas/plugin-sdk"; import type { CanvasNodeContentProps } from "@infinite-canvas/plugin-sdk"; // 预设便签色(点选切换),并额外提供自定义取色 const PRESET_COLORS = ["#fde68a", "#fca5a5", "#fdba74", "#a7f3d0", "#bfdbfe", "#ddd6fe", "#f9a8d4", "#e7e5e4"]; const DEFAULT_COLOR = PRESET_COLORS[0]; function StickyNoteContent({ ctx }: CanvasNodeContentProps) { const [editing, setEditing] = useState(false); const [paletteOpen, setPaletteOpen] = useState(false); // 取色时的本地预览色:仅本组件即时重渲染,避免每次都写宿主 store const [draftColor, setDraftColor] = useState(null); const rootRef = useRef(null); const commitTimerRef = useRef(null); // pluginColor 是插件自定义 metadata 字段(读出为 unknown,按需断言) const committedColor = (ctx.node.metadata?.pluginColor as string | undefined) || DEFAULT_COLOR; const color = draftColor ?? committedColor; const content = (ctx.node.metadata?.content as string | undefined) || ""; // 点击便利贴外部时:退出编辑并收起调色板。 // 监听 pointerdown 的 capture 阶段:宿主画布在 pointerdown 上 preventDefault(会抑制 // 兼容 mousedown 事件),所以必须用 pointerdown;capture 阶段又能先于宿主的 stopPropagation // 触发,避免「按 Esc 能退出、点别处退不出」。 useEffect(() => { if (!editing && !paletteOpen) return; const onDocDown = (e: PointerEvent) => { if (rootRef.current && !rootRef.current.contains(e.target as Node)) { setEditing(false); setPaletteOpen(false); } }; document.addEventListener("pointerdown", onDocDown, true); return () => document.removeEventListener("pointerdown", onDocDown, true); }, [editing, paletteOpen]); const pickColor = (next: string) => ctx.updateMetadata({ pluginColor: next }); // 连续取色(系统取色器拖动时 onChange 高频触发)会不停调用 updateMetadata, // 而宿主每次都会整表重建 + 全画布重渲染 + 持久化,导致卡顿。 // 这里先本地预览(setDraftColor),再节流提交到宿主。 const previewColor = (next: string) => { setDraftColor(next); if (commitTimerRef.current) clearTimeout(commitTimerRef.current); commitTimerRef.current = window.setTimeout(() => { commitTimerRef.current = null; pickColor(next); }, 150); }; // 立即提交(点击预设色、取色器关闭时用):清掉待提交的节流并写入。 const commitColor = (next: string) => { if (commitTimerRef.current) { clearTimeout(commitTimerRef.current); commitTimerRef.current = null; } setDraftColor(next); pickColor(next); }; useEffect(() => () => { if (commitTimerRef.current) clearTimeout(commitTimerRef.current); }, []); // 宿主里的颜色一旦真正变化(提交完成 / 撤销重做),清掉本地预览,回到 store 为准。 // 拖动取色期间 committedColor 不变,draftColor 得以保留,故不影响即时预览。 useEffect(() => { setDraftColor(null); }, [committedColor]); // 交互控件上按下时阻止冒泡,避免误触发节点拖动/双击编辑 const stop = (e: { stopPropagation: () => void }) => e.stopPropagation(); return (
{ e.stopPropagation(); setEditing(true); }} > {/* 右上角:当前颜色小圆点,点开后自选颜色(预设 + 自定义) */}
) : null}
{/* 内容区:双击进入编辑;非编辑态整块可直接拖动移动节点 */} {editing ? (