初始化模版工程
This commit is contained in:
292
components/html-editor/README.md
Normal file
292
components/html-editor/README.md
Normal file
@@ -0,0 +1,292 @@
|
||||
# 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/ # 图标等静态资源
|
||||
```
|
||||
Reference in New Issue
Block a user