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

293 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TaskArtifactHtml
HTML 产物渲染与编辑组件,支持 **文档模式document****网页模式web** 两种渲染方式。内容通过 iframe 沙箱加载,编辑态下提供所见即所得的工具栏。
---
## 快速开始
```tsx
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 类型定义
```ts
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` 后,编辑器内部的历史记录发生变化时会通过该回调通知父组件。父组件可据此实现外部的撤销/重做按钮。
```ts
interface ArtifactEditState {
canUndo: boolean; // 是否可以撤销
canRedo: boolean; // 是否可以重做
undo: () => void; // 执行撤销
redo: () => void; // 执行重做
}
```
---
## 两种模式对比
| 特性 | `document` 模式 | `web` 模式 |
| -------- | ------------------------------------------ | ------------------------------------------------------ |
| 适用场景 | 富文本文档(文章、报告等) | 网页类产物(落地页、网站等) |
| 编辑方式 | 全局 `contentEditable`,支持文本选区工具栏 | 元素级选取与属性编辑 |
| 工具栏 | `toolbar-doc` — 基于文本选区定位 | `toolbar-web` — 基于选中元素定位,更丰富的样式编辑能力 |
| 内部组件 | `TaskHtmlDoc` | `TaskHtmlWeb` |
---
## 使用示例
### 只读预览
```tsx
// 简单预览,不显示任何编辑工具栏
<TaskArtifactHtml
taskId={task.id}
editable={false}
type="document"
taskArtifact={artifact}
/>
```
### 文档编辑模式
```tsx
// 开启编辑,用户可以直接在文档中选中文本进行格式编辑
<TaskArtifactHtml
taskId={task.id}
editable={true}
type="document"
taskArtifact={artifact}
/>
```
### 网页编辑模式
```tsx
// 开启编辑,用户可以点选网页中的元素进行样式调整
<TaskArtifactHtml
taskId={task.id}
editable={true}
type="web"
taskArtifact={artifact}
/>
```
### 配合外部撤销/重做按钮
```tsx
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)
```
### 核心模块说明
#### `Html``components/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` 调用,转发至父窗口打开。
#### `HtmlWithEditMode``mode/baseEdit.tsx`
编辑模式的基础封装层,负责:
- 调用 `useIframeMode` 初始化 `HTMLEditor` 编辑器实例
- 调用 `useDiff` 将编辑器状态同步到 `PPTEditContext`
- 监听历史记录变化,通过 `onStateChange` 向上传递 `ArtifactEditState`canUndo/canRedo/undo/redo
- 内容变更时自动防抖保存1 秒),通过 `saveMarkdown` 接口持久化到服务端
#### `PPTEditProvider``context/index.tsx`
编辑状态上下文 Provider维护
- `state` — 当前 `useIframeMode` 的返回值(编辑器实例、选中元素、位置等)
- `setState` — 由 `useDiff` hook 调用,将编辑状态同步至上下文
- `originalSlide` — 原始幻灯片数据的引用PPT 场景)
工具栏组件通过 `usePPTEditContext()` 消费该上下文,获取编辑器实例和选中元素信息来渲染对应的编辑工具。
---
## Hooks
### `useLoadContent(taskArtifact: TaskArtifact): string`
通过 `useNovaKit()` 提供的 API 加载产物的远程 HTML 内容。先调用 `api.getArtifactUrl()` 获取签名 URL`fetch` 获取文本内容。
### `useIframeMode(id, containerRef, options?, scale?): UseInjectModeReturn`
核心编辑 hook负责
- 创建 `HTMLEditor` 实例并注入到 iframe 或普通 DOM 容器
- 管理元素选中状态和位置计算
- 管理 undo/redo 历史记录
- 绑定 `Cmd+Z` / `Cmd+Shift+Z` 快捷键
返回值:
```ts
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`)末尾位置计算工具栏坐标。
---
## 服务端接口
### `saveMarkdown``server/index.ts`
编辑模式下内容变更会自动防抖1 秒)调用此函数保存到服务端:
```ts
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/ # 图标等静态资源
```