Files
test1/components/html-editor/README.md
2026-03-20 07:33:46 +00:00

10 KiB
Raw Permalink Blame History

TaskArtifactHtml

HTML 产物渲染与编辑组件,支持 文档模式document网页模式web 两种渲染方式。内容通过 iframe 沙箱加载,编辑态下提供所见即所得的工具栏。


快速开始

import { TaskArtifactHtml } from "@/components/nova-sdk/html-editor";

<TaskArtifactHtml
  taskId="task-123"
  editable={true}
  type="web"
  taskArtifact={{
    path: "/artifacts/page.html",
    file_name: "page.html",
    file_type: "text/html",
  }}
  onStateChange={(state) => {
    // state.canUndo / state.canRedo / state.undo() / state.redo()
    console.log(state);
  }}
/>;

前置依赖:组件内部通过 useNovaKit() 获取 API 实例来加载远程内容,因此使用前需确保外层已包裹 <NovaKitProvider>


Props

属性 类型 必填 说明
taskId string 任务 ID用于关联产物与任务编辑时也用于保存接口
editable boolean 是否开启编辑模式。false 为只读预览,true 显示编辑工具栏
type 'document' | 'web' 渲染模式。document 适合富文本文档编辑,web 适合网页类产物编辑
taskArtifact TaskArtifact 产物数据对象,包含文件路径等信息
onStateChange (state: ArtifactEditState) => void 编辑状态变化回调,每次 undo/redo 历史变化时触发

TaskArtifact 类型定义

interface TaskArtifact {
  path: string; // 产物文件路径
  file_name: string; // 文件名
  file_type: string; // 文件 MIME 类型
  last_modified?: number; // 最后修改时间戳
  url?: string; // 可选的直接访问 URL
  content?: string; // 可选的内联内容
  task_id?: string; // 关联的任务 ID
  event_type?: string; // 工具调用事件类型
  tool_name?: string; // 工具名称
  tool_input?: unknown; // 工具输入
  tool_output?: unknown; // 工具输出
}

ArtifactEditState 类型定义

当传入 onStateChange 后,编辑器内部的历史记录发生变化时会通过该回调通知父组件。父组件可据此实现外部的撤销/重做按钮。

interface ArtifactEditState {
  canUndo: boolean; // 是否可以撤销
  canRedo: boolean; // 是否可以重做
  undo: () => void; // 执行撤销
  redo: () => void; // 执行重做
}

两种模式对比

特性 document 模式 web 模式
适用场景 富文本文档(文章、报告等) 网页类产物(落地页、网站等)
编辑方式 全局 contentEditable,支持文本选区工具栏 元素级选取与属性编辑
工具栏 toolbar-doc — 基于文本选区定位 toolbar-web — 基于选中元素定位,更丰富的样式编辑能力
内部组件 TaskHtmlDoc TaskHtmlWeb

使用示例

只读预览

// 简单预览,不显示任何编辑工具栏
<TaskArtifactHtml
  taskId={task.id}
  editable={false}
  type="document"
  taskArtifact={artifact}
/>

文档编辑模式

// 开启编辑,用户可以直接在文档中选中文本进行格式编辑
<TaskArtifactHtml
  taskId={task.id}
  editable={true}
  type="document"
  taskArtifact={artifact}
/>

网页编辑模式

// 开启编辑,用户可以点选网页中的元素进行样式调整
<TaskArtifactHtml
  taskId={task.id}
  editable={true}
  type="web"
  taskArtifact={artifact}
/>

配合外部撤销/重做按钮

const [editState, setEditState] = useState<ArtifactEditState | null>(null);

<>
  <div className="toolbar">
    <button disabled={!editState?.canUndo} onClick={() => editState?.undo()}>
      撤销
    </button>
    <button disabled={!editState?.canRedo} onClick={() => editState?.redo()}>
      重做
    </button>
  </div>

  <TaskArtifactHtml
    taskId={task.id}
    editable={true}
    type="web"
    taskArtifact={artifact}
    onStateChange={setEditState}
  />
</>;

内部架构

TaskArtifactHtml (入口)
├── type="document" → TaskHtmlDoc
│   ├── editable=false → Html (只读 iframe)
│   └── editable=true
│       └── PPTEditProvider (编辑状态上下文)
│           ├── PPTEditToolBar (toolbar-doc, 文本选区工具栏)
│           └── HtmlWithEditMode (基础编辑层)
│               ├── useIframeMode  → HTMLEditor 实例
│               ├── useDiff        → 同步编辑状态到 Context
│               └── Html (iframe 渲染)
│
└── type="web" → TaskHtmlWeb
    ├── editable=false → Html (只读 iframe)
    └── editable=true
        └── PPTEditProvider
            ├── PPTEditToolBar (toolbar-web, 元素工具栏)
            └── HtmlWithEditMode (isDoc=false)

核心模块说明

Htmlcomponents/html-render/task-html.tsx

底层 iframe 渲染组件,接受 HTML 字符串作为 srcDoc 注入 iframe。配置了 sandbox="allow-same-origin allow-scripts allow-popups allow-popups-to-escape-sandbox",并自动拦截 iframe 内的 _blank 链接和 window.open 调用,转发至父窗口打开。

HtmlWithEditModemode/baseEdit.tsx

编辑模式的基础封装层,负责:

  • 调用 useIframeMode 初始化 HTMLEditor 编辑器实例
  • 调用 useDiff 将编辑器状态同步到 PPTEditContext
  • 监听历史记录变化,通过 onStateChange 向上传递 ArtifactEditStatecanUndo/canRedo/undo/redo
  • 内容变更时自动防抖保存1 秒),通过 saveMarkdown 接口持久化到服务端

PPTEditProvidercontext/index.tsx

编辑状态上下文 Provider维护

  • state — 当前 useIframeMode 的返回值(编辑器实例、选中元素、位置等)
  • setState — 由 useDiff hook 调用,将编辑状态同步至上下文
  • originalSlide — 原始幻灯片数据的引用PPT 场景)

工具栏组件通过 usePPTEditContext() 消费该上下文,获取编辑器实例和选中元素信息来渲染对应的编辑工具。


Hooks

useLoadContent(taskArtifact: TaskArtifact): string

通过 useNovaKit() 提供的 API 加载产物的远程 HTML 内容。先调用 api.getArtifactUrl() 获取签名 URLfetch 获取文本内容。

useIframeMode(id, containerRef, options?, scale?): UseInjectModeReturn

核心编辑 hook负责

  • 创建 HTMLEditor 实例并注入到 iframe 或普通 DOM 容器
  • 管理元素选中状态和位置计算
  • 管理 undo/redo 历史记录
  • 绑定 Cmd+Z / Cmd+Shift+Z 快捷键

返回值:

interface UseInjectModeReturn {
  editor: HTMLEditor | null; // 编辑器实例ref 值)
  editorIns: HTMLEditor | null; // 编辑器实例state 值,触发重渲染)
  selectedElement: HTMLElement | null; // 当前选中的 DOM 元素
  position: Position | null; // 选中元素在 iframe 内的位置
  tipPosition: Position | null; // 经过缩放换算后用于定位工具栏的位置
  injectScript: (target: HTMLElement) => Promise<void>; // 手动注入编辑器
  canUndo: boolean;
  canRedo: boolean;
  clearHistory: () => void;
  loadSuccess: boolean; // iframe 内容是否加载/注入成功
}

useDiff(useIframeReturn, isDoc?)

区分 document / web 两种模式的差异逻辑,将 useIframeMode 的状态同步到 PPTEditContext

  • document 模式:使用 useToolPosition 基于文本选区末尾计算工具栏位置
  • web 模式:基于选中元素的 getBoundingClientRect 定位工具栏;失焦后清除状态

useToolPosition(editor, isDoc): Position | null

仅在 document 模式下生效。监听编辑器内的 mousedown / mouseup / scroll / resize 事件,根据当前文本选区(Selection)末尾位置计算工具栏坐标。


服务端接口

saveMarkdownserver/index.ts

编辑模式下内容变更会自动防抖1 秒)调用此函数保存到服务端:

POST / v1 / super_agent / chat / write_file;
Body: {
  task_id: string;
  path: string;
  content: string;
}

目录结构

html-editor/
├── index.tsx              # 入口组件 TaskArtifactHtml
├── README.md              # 本文档
├── types/
│   └── index.ts           # ArtifactEditState 等类型定义
├── server/
│   └── index.ts           # saveMarkdown 保存接口
├── mode/
│   ├── baseEdit.tsx       # 编辑模式基础层 HtmlWithEditMode
│   ├── html-doc.tsx       # 文档模式 TaskHtmlDoc
│   └── html-web.tsx       # 网页模式 TaskHtmlWeb
├── context/
│   └── index.tsx          # PPTEditProvider / usePPTEditContext
├── hooks/
│   ├── useDiff.ts         # doc/web 差异逻辑
│   ├── useIframeMode.ts   # 核心编辑器 hook
│   ├── useLoadContent.ts  # 远程内容加载
│   └── useToolPostion.ts  # 文本选区工具栏定位
├── components/
│   ├── html-render/       # iframe 渲染组件
│   ├── toolbar-doc/       # 文档模式工具栏
│   └── toolbar-web/       # 网页模式工具栏
├── lib/                   # HTMLEditor 核心库
│   ├── core/              # 编辑器引擎、历史记录管理器
│   ├── types/             # 内部类型定义
│   └── config/            # 编辑器配置
└── assets/                # 图标等静态资源