// HTML 节点:沙箱 iframe 渲染 HTML,{{input}} 会替换为上游文本节点内容。 // 交互:预览态 iframe 默认 pointer-events:none —— 鼠标事件穿透到宿主节点体, // 因此点节点任意位置都能拖动,无需在节点上加任何标题栏/按钮。 // 「编辑/预览」与「交互」开关都放在节点外的悬浮工具条(toolbar 扩展点),状态存 metadata。 import { definePlugin, useMemo, useRef, useState } from "@infinite-canvas/plugin-sdk"; import type { CanvasNodeContentProps } from "@infinite-canvas/plugin-sdk"; // 源码编辑器行高/字号,行号槽与文本域必须完全一致才能对齐 const EDITOR_FONT = 12; const EDITOR_LINE = 20; function HtmlEditor({ ctx, value }: { ctx: CanvasNodeContentProps["ctx"]; value: string }) { const gutterRef = useRef(null); // 行数:按换行统计,至少 1 行;value 变化时重算 const lineCount = useMemo(() => Math.max(1, value.split("\n").length), [value]); const [scrollTop, setScrollTop] = useState(0); const codeStyle = { fontFamily: "monospace", fontSize: EDITOR_FONT, lineHeight: `${EDITOR_LINE}px`, boxSizing: "border-box" } as const; return (
e.stopPropagation()}> {/* 行号槽:跟随文本域滚动,不可单独滚动 */}
{/* 用负 margin 让整列跟随 scrollTop 平移,和 textarea 同步 */}
{Array.from({ length: lineCount }, (_, i) => (
{i + 1}
))}