weifuwu 0.84.0 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/components/Editor/Editor.d.ts +8 -7
- package/dist/components/Editor/edit-events.d.ts +27 -0
- package/dist/components/Editor/model/apply.d.ts +23 -0
- package/dist/components/Editor/model/diff.d.ts +25 -0
- package/dist/components/Editor/model/dom.d.ts +27 -0
- package/dist/components/Editor/model/history.d.ts +21 -0
- package/dist/components/Editor/model/html.d.ts +16 -0
- package/dist/components/Editor/model/inverse.d.ts +20 -0
- package/dist/components/Editor/model/types.d.ts +116 -0
- package/dist/components/Editor/tools/table.d.ts +5 -2
- package/dist/components/Editor/tools/toolbar.d.ts +1 -1
- package/dist/components/Editor/tools/types.d.ts +25 -0
- package/dist/components/FilePreview/FilePreview.d.ts +42 -0
- package/dist/components/FilePreview/markdown.d.ts +23 -0
- package/dist/components/OfficeEditor/ai/ai-bridge.d.ts +39 -0
- package/dist/components/OfficeEditor/model/apply.d.ts +25 -0
- package/dist/components/OfficeEditor/model/types.d.ts +224 -0
- package/dist/components/SheetGrid/SheetGrid.d.ts +30 -0
- package/dist/components/SlideCanvas/SlideCanvas.d.ts +28 -0
- package/dist/components/index.d.ts +7 -0
- package/dist/components/index.js +54 -14
- package/dist/components/style.css +679 -4
- package/dist/index.js +128 -17
- package/dist/office/docx.d.ts +33 -0
- package/dist/office/pptx.d.ts +24 -0
- package/dist/office/xlsx.d.ts +25 -0
- package/dist/office/xml-serialize.d.ts +18 -0
- package/dist/office/xml.d.ts +21 -0
- package/dist/office/zip.d.ts +21 -0
- package/dist/ui-dom/index.js +8 -7
- package/dist/ui-dom/locale/en_US.d.ts +14 -0
- package/dist/ui-dom/locale/zh_CN.d.ts +14 -0
- package/dist/ui-dom/middleware/ws.d.ts +8 -0
- package/dist/ui-dom/testing.js +5 -5
- package/dist/ui-dom/types.d.ts +3 -1
- package/dist/ui-dom/vdom3/delegate.d.ts +9 -0
- package/dist/ui-dom/vdom3/types.d.ts +27 -1
- package/dist/ui-dom/vdom3.js +10 -9
- package/docs/components-map.md +12 -0
- package/docs/components.md +124 -2
- package/docs/frontend-ui-dom.md +1 -0
- package/package.json +1 -1
- package/dist/components/Editor/tools/format.d.ts +0 -11
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# weifuwu
|
|
2
2
|
|
|
3
|
-
**自托管全栈框架** — 一个 npm 包 = 后端 HTTP + 前端 VDOM +
|
|
3
|
+
**自托管全栈框架** — 一个 npm 包 = 后端 HTTP + 前端 VDOM + 118 组件 + CSS 设计系统 + SaaS 地基(认证 / 消息 / 队列 / AI)。全自研、零构建、消灭样板。
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install weifuwu # 一个依赖,完整应用栈
|
|
@@ -29,7 +29,7 @@ npm install weifuwu # 一个依赖,完整应用栈
|
|
|
29
29
|
|----|------|------|
|
|
30
30
|
| 后端 | `weifuwu` | Trie 路由 / 中间件链 / serve / 自研 PG+Redis / SSR / GraphQL / WebSocket |
|
|
31
31
|
| 前端 | `weifuwu/ui-dom` | **vdom3 精准事件流引擎**——createRouter(路由)+ createRoot(挂载)+ 事件流(渲染本体:`entity:action` 统一命名、DOM = fold、可回放可断言)+ SSR(事件流序列化);组件=两阶段异步组件 / ctx.params 对齐后端;**weifuwu/components 直接复用**(VNode 契约唯一来源 ui-dom,见 `docs/frontend-ui-dom.md`) |
|
|
32
|
-
| 组件 | `weifuwu/components` |
|
|
32
|
+
| 组件 | `weifuwu/components` | 118 个 HTML 原语组件(表单/表格/弹层/AiChat…),引用 `--wf-*` 主题变量 |
|
|
33
33
|
| 样式 | `weifuwu/layout` | 66 布局原语 + 156 工具类 + 177 主题 Token,零自定义 CSS 文件 |
|
|
34
34
|
| SaaS 地基 | 随包内置 | rateLimit / email / userSystem / messager / queue / ai → `ctx.*` 一行接入 |
|
|
35
35
|
|
|
@@ -116,7 +116,7 @@ npm install weifuwu # 一个依赖,完整应用栈
|
|
|
116
116
|
|
|
117
117
|
**自研数据层** — `ctx.sql`(PG v3 协议)与 `ctx.redis`(RESP2 协议)为**自研客户端**:确定性输出、行为可预测、统一错误模型。jsonb 自动解码、TTL 安全 API、schema 写前校验——高频痛点(双重编码/parseRow 样板/`'EX'` 参数顺序)从根上消除。
|
|
118
118
|
|
|
119
|
-
> **实践验证**:多租户 AI 平台(`apps/agent-platform`——14 页 + 部门聊天 + 知识库 + HITL 审批)已完全运行在框架上:auth(userSystem)/ AI 引擎(ai)/ 实时消息(messager)/ UI(
|
|
119
|
+
> **实践验证**:多租户 AI 平台(`apps/agent-platform`——14 页 + 部门聊天 + 知识库 + HITL 审批)已完全运行在框架上:auth(userSystem)/ AI 引擎(ai)/ 实时消息(messager)/ UI(118 组件)/ 数据管道(ctx.api)零自研替代。框架哲学(中间件注入、诚实裁剪、机制与策略分离)经受住了真实复杂应用的检验——这也是我们确定「哪些进框架、哪些留应用层」的依据。
|
|
120
120
|
|
|
121
121
|
---
|
|
122
122
|
|
|
@@ -232,7 +232,7 @@ createRouter(routes, root) // 之后正常交
|
|
|
232
232
|
### 30 秒体验(跑现有 demo)
|
|
233
233
|
|
|
234
234
|
```bash
|
|
235
|
-
# ① 组件 cheatsheet——
|
|
235
|
+
# ① 组件 cheatsheet——118 组件全部可交互预览(零依赖,5 秒起)
|
|
236
236
|
cd apps/components-demo && node server.ts
|
|
237
237
|
# 打开 http://localhost:3000
|
|
238
238
|
|
|
@@ -321,7 +321,7 @@ cd apps/agent-platform && npm run seed && npm run dev
|
|
|
321
321
|
| 资源 | CDN 地址 | 说明 |
|
|
322
322
|
|------|---------|------|
|
|
323
323
|
| `weifuwu/ui-dom` | `https://unpkg.com/weifuwu@latest/dist/ui-dom/index.js` | 前端运行时(createRouter, createRoot, h, 事件流, 状态管理等) |
|
|
324
|
-
| `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/index.js` |
|
|
324
|
+
| `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/index.js` | 118 个 UI 组件(Button, Card, Table, Modal, Icon 等) |
|
|
325
325
|
| `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/style.css` | 组件 CSS + 177 个主题 Token + 66 个布局原语 + 156 个工具类 |
|
|
326
326
|
| 独立布局系统 | `https://unpkg.com/weifuwu@latest/dist/layout/weifuwu-layout.css` | 仅 CSS 布局,不依赖 JS |
|
|
327
327
|
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* weifuwu/components —
|
|
2
|
+
* weifuwu/components/Editor — 富文本编辑器(事件流事务层,阶段 1)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* 架构(design/editor-events-plan.md):文档 = fold(edit 事件流)。
|
|
5
|
+
* - 模型真相:DocState(contentEditable DOM 是渲染)
|
|
6
|
+
* - 语义操作(工具栏/快捷键)→ edit:commit(before 快照 + 事件)→ undo/redo 精确
|
|
7
|
+
* - 用户输入(键盘/IME/粘贴)暂由浏览器直写 DOM → onInput 同步回模型
|
|
8
|
+
* (输入入流是阶段 2——阶段 1 撤销分流:语义操作走自建栈,输入退浏览器)
|
|
9
|
+
* - 受控 value 契约保留:onChange 发 serializeHtml(doc)
|
|
9
10
|
*/
|
|
10
11
|
import type { Component } from '../../ui-dom/vnode.ts';
|
|
11
12
|
import type { EditorProps } from './tools/types.ts';
|
|
12
|
-
export type { EditorProps, ToolbarItem } from './tools/types.ts';
|
|
13
|
+
export type { EditorProps, ToolbarItem, EditorAiAction, EditorAiOptions } from './tools/types.ts';
|
|
13
14
|
export declare const Editor: Component<EditorProps>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/components/Editor/edit-events — 编辑事件流(第四端)
|
|
3
|
+
*
|
|
4
|
+
* 与 ai/sandbox 事件流同构(design/ai-events-plan.md / sandbox-events-plan.md):
|
|
5
|
+
* 环形缓冲 + 查询(按动作过滤)+ 订阅 + 全局调试工具。
|
|
6
|
+
*
|
|
7
|
+
* 桥接(跨层一条链):
|
|
8
|
+
* - vdom3 stream:每 commit 一条摘要(降频——细粒度事件只在本缓冲)
|
|
9
|
+
* - ai:edit:ai-apply 事件带 messageId 关联键(↔ ai 事件流 wf:token/done)
|
|
10
|
+
* - sandbox:source: 'sandbox' 的 commit 带 toolCallId(↔ sandbox exec 事件)
|
|
11
|
+
*/
|
|
12
|
+
export type EditAction = 'text-insert' | 'text-delete' | 'mark-apply' | 'block-set' | 'embed-insert' | 'embed-delete' | 'ai-apply' | 'ai-accept' | 'ai-reject' | 'commit' | 'undo' | 'redo' | 'clear' | 'preview' | 'office';
|
|
13
|
+
export interface EditStreamEvent {
|
|
14
|
+
entity: 'edit';
|
|
15
|
+
action: EditAction;
|
|
16
|
+
ts: number;
|
|
17
|
+
target?: string;
|
|
18
|
+
payload?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
export declare function editEmit(action: EditAction, payload?: Record<string, unknown>, target?: string): void;
|
|
21
|
+
/** 查询(倒序返回——最新在前;filter 按 action 过滤) */
|
|
22
|
+
export declare function editEvents(n?: number, filter?: {
|
|
23
|
+
action?: EditAction | EditAction[];
|
|
24
|
+
}): EditStreamEvent[];
|
|
25
|
+
export declare function subscribeEditEvents(fn: (e: EditStreamEvent) => void): () => void;
|
|
26
|
+
/** 测试隔离 */
|
|
27
|
+
export declare function resetEditEvents(): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/components/Editor/model/apply — 事件应用(文档 = fold(事件流))
|
|
3
|
+
*
|
|
4
|
+
* applyEdit(doc, ev) 纯函数:任意事件 → 新 DocState(不可变)。
|
|
5
|
+
* 所有 offset 平移在单一入口维护(marks/blockProps/embeds/caret 同步——
|
|
6
|
+
* 防止各路径各自修补的漂移)。段结构查找用二分(undo/redo 高频路径)。
|
|
7
|
+
*/
|
|
8
|
+
import type { DocState, EditEvent, BlockProp } from './types.ts';
|
|
9
|
+
/** 所有段起点(升序;空文档 = [0]——单空段) */
|
|
10
|
+
export declare function segmentStarts(text: string): number[];
|
|
11
|
+
/** 最后一个 <= pos 的段起点(二分——O(log n)) */
|
|
12
|
+
export declare function segmentStartAt(text: string, pos: number, starts?: number[]): number;
|
|
13
|
+
/** 位置 pos 所属段的块属性(默认 p/null) */
|
|
14
|
+
export declare function blockPropAt(doc: DocState, pos: number): BlockProp | null;
|
|
15
|
+
export declare function applyEdit(doc: DocState, ev: EditEvent): DocState;
|
|
16
|
+
/** 应用事件并记录实际效果(ai-apply 返回带 original 校验后的事件——供历史审计) */
|
|
17
|
+
export declare function applyEditChecked(doc: DocState, ev: EditEvent): {
|
|
18
|
+
doc: DocState;
|
|
19
|
+
ev: EditEvent;
|
|
20
|
+
};
|
|
21
|
+
/** 删除区间内/边界段起点被覆盖的段属性(含边界——段起点恰在 at+len 时合并吞属性;
|
|
22
|
+
* 逆操作按原段起点恢复——文本恢复后起点仍有效,幂等安全) */
|
|
23
|
+
export declare function removedBlocksOf(doc: DocState, at: number, end: number): BlockProp[];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/components/Editor/model/diff — 文本 diff(AI 建议对比用)
|
|
3
|
+
*
|
|
4
|
+
* 零依赖:公共前缀/后缀剪枝 + 中间 LCS(滚动数组 O(m) 空间)。
|
|
5
|
+
* 裁剪:总长度超 DIFF_MAX_LEN 退化为整体替换(诚实裁剪——大文本不追求
|
|
6
|
+
* 精细 diff,AI 选区级文本通常 < 4k)。
|
|
7
|
+
*/
|
|
8
|
+
export type DiffOp = {
|
|
9
|
+
type: 'equal';
|
|
10
|
+
text: string;
|
|
11
|
+
} | {
|
|
12
|
+
type: 'insert';
|
|
13
|
+
text: string;
|
|
14
|
+
} | {
|
|
15
|
+
type: 'delete';
|
|
16
|
+
text: string;
|
|
17
|
+
};
|
|
18
|
+
export declare const DIFF_MAX_LEN = 4000;
|
|
19
|
+
/** a → b 的文本 diff(公共前后缀剪枝——编辑两端不动的场景零 DP) */
|
|
20
|
+
export declare function textDiff(a: string, b: string): DiffOp[];
|
|
21
|
+
/** diff 统计(AI 面板摘要:+N/-M) */
|
|
22
|
+
export declare function diffStats(ops: DiffOp[]): {
|
|
23
|
+
added: number;
|
|
24
|
+
removed: number;
|
|
25
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/components/Editor/model/dom — offset ↔ DOM 桥
|
|
3
|
+
*
|
|
4
|
+
* doc.text 的 offset 与 contentEditable DOM 的双向映射:
|
|
5
|
+
* - 文本节点:按 nodeValue.length 累加
|
|
6
|
+
* - 嵌入元素(IMG/TABLE/HR):占位符 1 个 offset(与模型 embeds 对应)
|
|
7
|
+
* - **块级元素边界:计 1 个 \n**(模型段分隔——serialize 输出块标签,
|
|
8
|
+
* \n 无 DOM 文本节点——不计数则 offset 偏 1(真实事故:AI 选区替换错位))
|
|
9
|
+
*
|
|
10
|
+
* 前提:DOM 内容 = serialize(doc) 的渲染(文本 + 嵌入 + 块结构对应)。
|
|
11
|
+
*/
|
|
12
|
+
/** 嵌入元素判定(与 html.ts EMBED_TAGS 一致) */
|
|
13
|
+
export declare function isEmbedElement(el: Element): boolean;
|
|
14
|
+
/** Range → 文档 offset(相对 root 内容起点) */
|
|
15
|
+
export declare function rangeToOffsets(root: HTMLElement, range: Range): {
|
|
16
|
+
start: number;
|
|
17
|
+
end: number;
|
|
18
|
+
};
|
|
19
|
+
/** 当前选区 → offsets(编辑器内/无 DOM 时返回 null) */
|
|
20
|
+
export declare function selectionOffsets(root: HTMLElement | null): {
|
|
21
|
+
start: number;
|
|
22
|
+
end: number;
|
|
23
|
+
} | null;
|
|
24
|
+
/** offset 区间 → Range(恢复选区;越界时 clamp 到内容末尾) */
|
|
25
|
+
export declare function offsetsToRange(root: HTMLElement, start: number, end: number): Range | null;
|
|
26
|
+
/** offsets → 选区(focus + 设置) */
|
|
27
|
+
export declare function setSelectionOffsets(root: HTMLElement | null, start: number, end: number): void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/components/Editor/model/history — undo/redo 栈(commit 粒度)
|
|
3
|
+
*
|
|
4
|
+
* 撤销粒度 = commit 粒度(AI 流式接受 = 1 个 commit = 1 个撤销步)。
|
|
5
|
+
* 栈本身只存事件——状态折叠由调用方(Editor 层)执行 applyEdit。
|
|
6
|
+
*/
|
|
7
|
+
import type { Commit } from './types.ts';
|
|
8
|
+
export interface HistoryState {
|
|
9
|
+
undoStack: Commit[];
|
|
10
|
+
redoStack: Commit[];
|
|
11
|
+
}
|
|
12
|
+
export declare function createHistory(maxDepth?: number): HistoryState;
|
|
13
|
+
/** 压入新 commit(清空 redo 栈——新分支;超深丢弃最旧) */
|
|
14
|
+
export declare function pushCommit(h: HistoryState, commit: Commit, maxDepth?: number): void;
|
|
15
|
+
/** 弹出 undo 栈顶(不动 redo——由调用方折叠后决定;返回 null = 无可撤销) */
|
|
16
|
+
export declare function popUndo(h: HistoryState): Commit | null;
|
|
17
|
+
/** 弹出 redo 栈顶(同时收回 undo——撤销后重做路径一致) */
|
|
18
|
+
export declare function popRedo(h: HistoryState): Commit | null;
|
|
19
|
+
export declare function canUndo(h: HistoryState): boolean;
|
|
20
|
+
export declare function canRedo(h: HistoryState): boolean;
|
|
21
|
+
export declare function clearHistory(h: HistoryState): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/components/Editor/model/html — HTML ⇄ DocState 序列化
|
|
3
|
+
*
|
|
4
|
+
* 只认 Editor 格式子集(诚实裁剪):p/div/h1-3/blockquote/ul/ol(块)、
|
|
5
|
+
* b/strong/i/em/u/a(内联)、img/table/hr(embed 快照——内部不解析)、
|
|
6
|
+
* text-align(inline style 或 wf-text-* class)。未知标签降级取文本。
|
|
7
|
+
*
|
|
8
|
+
* parse 需要 DOM(DOMParser)——浏览器环境全局可用;测试注入 jsdom document。
|
|
9
|
+
*/
|
|
10
|
+
import type { DocState } from './types.ts';
|
|
11
|
+
/** HTML → DocState(doc 可注入——jsdom 测试;浏览器默认全局) */
|
|
12
|
+
export declare function parseHtml(html: string, doc?: Document): DocState;
|
|
13
|
+
/** DocState → HTML(块标签 + 对齐 + marks + embeds;相邻 ul/ol 段合并列表) */
|
|
14
|
+
export declare function serializeHtml(doc: DocState): string;
|
|
15
|
+
/** 往返幂等校验辅助:parse → serialize → parse 不再变化 */
|
|
16
|
+
export declare function normalizeHtml(html: string, doc?: Document): string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/components/Editor/model/inverse — 事件逆操作(undo 基元)
|
|
3
|
+
*
|
|
4
|
+
* 任意事件 → 逆事件(与 vdom3 events.ts inverse() 同构)。不变量:
|
|
5
|
+
* applyEdit(applyEdit(doc, ev), inverseEdit(ev)) === doc。
|
|
6
|
+
*/
|
|
7
|
+
import type { DocState, EditEvent, EmbedSpan } from './types.ts';
|
|
8
|
+
import { EMBED_CHAR } from './types.ts';
|
|
9
|
+
/** 逆操作可能产生多个事件(text-delete 覆盖 embed → 恢复条目需补 embed-insert) */
|
|
10
|
+
export type InverseResult = EditEvent | EditEvent[];
|
|
11
|
+
/**
|
|
12
|
+
* 逆操作(undo 审计/验证用——生产 undo 走 commit 快照)。
|
|
13
|
+
* doc 可选:text-insert/ai-apply 的逆需要操作后文档提取区间内段属性
|
|
14
|
+
* (text-delete 校验要求——无 doc 时跳过(不精确——仅审计场景))。
|
|
15
|
+
*/
|
|
16
|
+
export declare function inverseEdit(ev: EditEvent, doc?: DocState): InverseResult;
|
|
17
|
+
/** commit 的逆(事件逆序 + 各自逆操作展开——undo 应用顺序) */
|
|
18
|
+
export declare function inverseCommit(events: EditEvent[]): EditEvent[];
|
|
19
|
+
export { EMBED_CHAR };
|
|
20
|
+
export type { EmbedSpan };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/components/Editor/model — 文档模型(事件流底座)
|
|
3
|
+
*
|
|
4
|
+
* 核心不变量(design/editor-events-plan.md):文档状态 = fold(edit 事件流)。
|
|
5
|
+
* 本文档模型是纯 TS 数据结构 + 纯函数(无 DOM、无 UI)——阶段 0 验收:
|
|
6
|
+
* 折叠不变量(fuzz)/ 逆操作 / HTML 往返。
|
|
7
|
+
*
|
|
8
|
+
* 模型设计:
|
|
9
|
+
* - text 为全文纯文本,`\n` 为段落分隔符(offset 连续——选区/diff/提示词天然)
|
|
10
|
+
* - blockProps:非默认块属性(按段起点 offset 记录;段结构由 text 派生)
|
|
11
|
+
* - marks:内联格式区间(左闭右开;b/i/u/link 可重叠)
|
|
12
|
+
* - embeds:img/table/hr 用占位符 `\uFFFC` 占 1 个字符位(标准对象替换字符)
|
|
13
|
+
*/
|
|
14
|
+
/** 块类型(p 为默认——不记录) */
|
|
15
|
+
export type BlockKind = 'p' | 'h1' | 'h2' | 'h3' | 'ul' | 'ol' | 'quote';
|
|
16
|
+
export type Align = 'left' | 'center' | 'right';
|
|
17
|
+
export type MarkType = 'b' | 'i' | 'u' | 'link';
|
|
18
|
+
/** 非默认块属性(段起点 offset → 块格式) */
|
|
19
|
+
export interface BlockProp {
|
|
20
|
+
start: number;
|
|
21
|
+
kind: BlockKind;
|
|
22
|
+
align?: Align;
|
|
23
|
+
}
|
|
24
|
+
/** 内联格式区间 */
|
|
25
|
+
export interface MarkSpan {
|
|
26
|
+
start: number;
|
|
27
|
+
end: number;
|
|
28
|
+
type: MarkType;
|
|
29
|
+
href?: string;
|
|
30
|
+
}
|
|
31
|
+
/** 嵌入(占位符 \uFFFC 占据 text 1 字符位;id 唯一——删除按 id 精确匹配) */
|
|
32
|
+
export interface EmbedSpan {
|
|
33
|
+
id: string;
|
|
34
|
+
at: number;
|
|
35
|
+
type: 'img' | 'table' | 'hr' | 'pre';
|
|
36
|
+
html: string;
|
|
37
|
+
}
|
|
38
|
+
/** 文档状态(不可变——applyEdit 返回新对象;undo/redo 快照对比免费) */
|
|
39
|
+
export interface DocState {
|
|
40
|
+
text: string;
|
|
41
|
+
blockProps: BlockProp[];
|
|
42
|
+
marks: MarkSpan[];
|
|
43
|
+
embeds: EmbedSpan[];
|
|
44
|
+
}
|
|
45
|
+
export declare const EMPTY_DOC: DocState;
|
|
46
|
+
/** 嵌入占位符(OBJECT REPLACEMENT CHARACTER——业界标准) */
|
|
47
|
+
export declare const EMBED_CHAR = "\uFFFC";
|
|
48
|
+
export type EditEvent = {
|
|
49
|
+
type: 'text-insert';
|
|
50
|
+
at: number;
|
|
51
|
+
text: string;
|
|
52
|
+
} | {
|
|
53
|
+
type: 'text-delete';
|
|
54
|
+
at: number;
|
|
55
|
+
len: number;
|
|
56
|
+
removed: string;
|
|
57
|
+
removedEmbeds: EmbedSpan[];
|
|
58
|
+
/** 被完整删除的段属性(起点在区间内)——逆操作恢复段格式(合并段继承丢失) */
|
|
59
|
+
removedBlocks: BlockProp[];
|
|
60
|
+
} | {
|
|
61
|
+
type: 'mark-apply';
|
|
62
|
+
start: number;
|
|
63
|
+
end: number;
|
|
64
|
+
mark: MarkType;
|
|
65
|
+
on: boolean;
|
|
66
|
+
href?: string;
|
|
67
|
+
prev: MarkSpan[];
|
|
68
|
+
}
|
|
69
|
+
/** 绝对恢复(mark 逆操作——区间表示不可逆,undo 用快照精确还原) */
|
|
70
|
+
| {
|
|
71
|
+
type: 'mark-restore';
|
|
72
|
+
mark: MarkType;
|
|
73
|
+
spans: MarkSpan[];
|
|
74
|
+
prev: MarkSpan[];
|
|
75
|
+
} | {
|
|
76
|
+
type: 'block-set';
|
|
77
|
+
start: number;
|
|
78
|
+
kind: BlockKind;
|
|
79
|
+
align?: Align | null;
|
|
80
|
+
prev: BlockProp | null;
|
|
81
|
+
} | {
|
|
82
|
+
type: 'embed-insert';
|
|
83
|
+
at: number;
|
|
84
|
+
embed: EmbedSpan;
|
|
85
|
+
} | {
|
|
86
|
+
type: 'embed-delete';
|
|
87
|
+
at: number;
|
|
88
|
+
embed: EmbedSpan;
|
|
89
|
+
}
|
|
90
|
+
/** 快照式:AI 替换(original 由创建者提供——diff 面板直接读;原子撤销一步;
|
|
91
|
+
* removedEmbeds/removedBlocks:区间内嵌入与被删段属性——逆操作恢复) */
|
|
92
|
+
| {
|
|
93
|
+
type: 'ai-apply';
|
|
94
|
+
start: number;
|
|
95
|
+
end: number;
|
|
96
|
+
original: string;
|
|
97
|
+
revised: string;
|
|
98
|
+
removedEmbeds: EmbedSpan[];
|
|
99
|
+
removedBlocks: BlockProp[];
|
|
100
|
+
};
|
|
101
|
+
/** 事务边界:N 个事件 = 1 个撤销步(AI 流式接受 = 1 个 commit)
|
|
102
|
+
* before:操作前状态快照——undo 精确恢复(mark 区间表示不可逆——
|
|
103
|
+
* 收缩/合并后无法从逆事件还原;快照 + 重放混合:undo=恢复 before,
|
|
104
|
+
* redo=从 before 重放 events——vdom3 remove 逆操作存快照同款先例) */
|
|
105
|
+
export interface Commit {
|
|
106
|
+
label: string;
|
|
107
|
+
events: EditEvent[];
|
|
108
|
+
before: DocState;
|
|
109
|
+
/** 操作时间(历史面板显示;缺省 = 记录时) */
|
|
110
|
+
ts?: number;
|
|
111
|
+
/** 操作前光标(undo 恢复位置;可选——默认不恢复) */
|
|
112
|
+
caret?: {
|
|
113
|
+
start: number;
|
|
114
|
+
end: number;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* weifuwu/components/Editor/tools — 表格工具
|
|
3
|
+
*
|
|
4
|
+
* 事件流事务层(design/editor-events-plan.md 阶段 1):表格 = embed 事件——
|
|
5
|
+
* 本模块只生成 HTML(不再操作 DOM/execCommand)。
|
|
3
6
|
*/
|
|
4
|
-
/**
|
|
5
|
-
export declare function
|
|
7
|
+
/** 生成表格 HTML(表头行加粗——模型 embed 快照) */
|
|
8
|
+
export declare function tableHtml(rows: number, cols: number): string;
|
|
6
9
|
/** 渲染 6×6 表格选择网格 */
|
|
7
10
|
export declare function renderTableGrid(hoverRow: number, hoverCol: number, onSelect: (rows: number, cols: number) => void, onHover: (row: number, col: number) => void, onLeave: () => void): any;
|
|
@@ -6,4 +6,4 @@ import type { FormatState, ToolbarItem } from './types.ts';
|
|
|
6
6
|
export declare const DEFAULT_TOOLBAR: ToolbarItem[];
|
|
7
7
|
export declare const TOOLBAR_LABELS: Record<ToolbarItem, string>;
|
|
8
8
|
export declare const TOOLBAR_TITLES: Record<ToolbarItem, string>;
|
|
9
|
-
export declare function renderToolbar(items: ToolbarItem[], active: FormatState, isSource: boolean, onItem: (item: ToolbarItem) => void, customRender?: Record<string, (item: ToolbarItem) => VNode
|
|
9
|
+
export declare function renderToolbar(items: ToolbarItem[], active: FormatState, isSource: boolean, onItem: (item: ToolbarItem, anchor?: HTMLElement | null) => void, customRender?: Record<string, (item: ToolbarItem) => VNode>, extra?: VNode[]): VNode;
|
|
@@ -11,5 +11,30 @@ export interface EditorProps {
|
|
|
11
11
|
toolbar?: ToolbarItem[];
|
|
12
12
|
/** 图片上传回调 — 接收 File 返回图片 URL。不传时弹出 URL 输入框 */
|
|
13
13
|
onUpload?: (file: File) => Promise<string>;
|
|
14
|
+
/** AI 协作(可选——不传则无 AI 能力):选区操作 → wf: 流式端点 → 建议浮层 →
|
|
15
|
+
* 接受 = edit:ai-apply commit(原子撤销一步) */
|
|
16
|
+
ai?: EditorAiOptions;
|
|
17
|
+
/** 草稿持久化 key(可选):传入时内容自动保存/恢复(ctx.browser.storage*——
|
|
18
|
+
* SSR 无害 no-op)。挂载时 value 为空且存在草稿 → 恢复草稿。 */
|
|
19
|
+
draftKey?: string;
|
|
20
|
+
}
|
|
21
|
+
/** AI 动作(选区文本 → 提示词 → 建议) */
|
|
22
|
+
export interface EditorAiAction {
|
|
23
|
+
id: string;
|
|
24
|
+
/** 按钮标签(如 '润色') */
|
|
25
|
+
label: string;
|
|
26
|
+
/** 提示词模板(选区文本注入 {selection} 由调用方替换) */
|
|
27
|
+
prompt: (ctx: {
|
|
28
|
+
selection: string;
|
|
29
|
+
}) => string;
|
|
30
|
+
}
|
|
31
|
+
export interface EditorAiOptions {
|
|
32
|
+
/** wf: SSE 端点(POST——协议 docs/ai-contract.md) */
|
|
33
|
+
url: string;
|
|
34
|
+
headers?: Record<string, string>;
|
|
35
|
+
/** 自定义动作(缺省 = 内置 5 个:润色/翻译/缩写/扩写/纠错) */
|
|
36
|
+
actions?: EditorAiAction[];
|
|
37
|
+
/** x:* 自定义事件透传 */
|
|
38
|
+
onEvent?: (name: string, data: unknown) => void;
|
|
14
39
|
}
|
|
15
40
|
export type FormatState = Record<string, boolean>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/components/FilePreview — 文件预览(office/pdf/md/html/text)
|
|
3
|
+
*
|
|
4
|
+
* 架构(design/file-preview-plan.md):
|
|
5
|
+
* - md 预览:复用 `<Markdown>` 组件(安全 token 渲染——表格/任务列表/URL 白名单)
|
|
6
|
+
* - md/text 编辑:复用 `<Editor>`(事件流事务层——撤销/时光机/AI 全继承)——
|
|
7
|
+
* 编辑闭环:md → markdownToHtml → Editor(DocState 模型)→ serializeMarkdown 回写
|
|
8
|
+
* - html 预览:iframe sandbox(安全隔离——untrusted HTML 不直插 DOM)
|
|
9
|
+
* - pdf/office:浏览器原生/服务端转换 URL(只读)
|
|
10
|
+
*
|
|
11
|
+
* 事件流:预览加载 `editEmit('preview')`(__edit_tail 可审计);编辑 = Editor
|
|
12
|
+
* commit 事件流(同一时间线)。sandbox 集成:url 加载 + onSave 回写由消费方接。
|
|
13
|
+
*/
|
|
14
|
+
import type { Component } from '../../ui-dom/vnode.ts';
|
|
15
|
+
import type { EditorAiOptions } from '../Editor/Editor.ts';
|
|
16
|
+
export type FileType = 'md' | 'html' | 'pdf' | 'office' | 'text';
|
|
17
|
+
/** 从文件名/URL 推断类型(type 未传时自动探测) */
|
|
18
|
+
export declare function detectType(fileName?: string, url?: string): FileType;
|
|
19
|
+
export interface FilePreviewProps {
|
|
20
|
+
/** 文件类型(缺省按 fileName/url 扩展名自动探测) */
|
|
21
|
+
type?: FileType;
|
|
22
|
+
/** md/html/text 内容(直接传入);pdf/office 用 url */
|
|
23
|
+
content?: string;
|
|
24
|
+
/** pdf/office 文件 URL(或 html 远程加载) */
|
|
25
|
+
url?: string;
|
|
26
|
+
fileName?: string;
|
|
27
|
+
/** md/text:切换 Editor(复用事件流事务层——编辑/撤销/时光机/AI) */
|
|
28
|
+
editable?: boolean;
|
|
29
|
+
/** 编辑模式 AI 协作(透传 Editor) */
|
|
30
|
+
ai?: EditorAiOptions;
|
|
31
|
+
/** 编辑保存回调(md/text 序列化回写) */
|
|
32
|
+
onSave?: (content: string, type: 'md' | 'text') => void;
|
|
33
|
+
/** 加载完成(解析成功) */
|
|
34
|
+
onLoad?: (info: {
|
|
35
|
+
type: FileType;
|
|
36
|
+
chars: number;
|
|
37
|
+
blocks: number;
|
|
38
|
+
}) => void;
|
|
39
|
+
/** 内容高度 */
|
|
40
|
+
height?: string;
|
|
41
|
+
}
|
|
42
|
+
export declare const FilePreview: Component<FilePreviewProps>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/components/FilePreview/markdown — Markdown 转换(编辑桥)
|
|
3
|
+
*
|
|
4
|
+
* 复用现有 Markdown 组件(src/components/Markdown/parser.ts——安全子集 token 树:
|
|
5
|
+
* 表格/任务列表/删除线/安全 URL 白名单——不重复造轮子)。
|
|
6
|
+
*
|
|
7
|
+
* 本模块只提供两个编辑桥转换:
|
|
8
|
+
* - markdownToHtml(md):md → HTML 字符串(Editor 编辑入口——Editor 是 HTML 模型)
|
|
9
|
+
* - serializeMarkdown(doc):DocState → md(编辑保存回写)
|
|
10
|
+
*
|
|
11
|
+
* 编辑闭环(事件流):md → markdownToHtml → Editor(DocState 模型——撤销/时光机/
|
|
12
|
+
* AI 全继承)→ serializeMarkdown → 保存。
|
|
13
|
+
*/
|
|
14
|
+
import type { DocState } from '../Editor/model/types.ts';
|
|
15
|
+
import { safeUrl, type MdBlock, type MdInline } from '../Markdown/parser.ts';
|
|
16
|
+
/** md → HTML(Editor 模型子集内:标题/段落/引用/列表/代码块/分割线/图片——
|
|
17
|
+
* 表格/任务列表/删除线超出 Editor 模型——裁剪为段落(编辑时降级——诚实)) */
|
|
18
|
+
export declare function markdownToHtml(md: string): string;
|
|
19
|
+
/** DocState → Markdown(编辑保存回写——格式子集内语义等价)
|
|
20
|
+
* 块间空行分隔(md 规范);相邻列表项单换行(保持列表连续) */
|
|
21
|
+
export declare function serializeMarkdown(doc: DocState): string;
|
|
22
|
+
export { safeUrl };
|
|
23
|
+
export type { MdBlock, MdInline };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/components/OfficeEditor/ai — ODES × AI 事件流对接
|
|
3
|
+
*
|
|
4
|
+
* 设计(design/office-events-plan.md §9):AI 落地 = 普通 op + ai 元数据。
|
|
5
|
+
* 本模块:AI 回复 → OfficeOp[] 默认解析(按 mode)+ 事件流桥接发射。
|
|
6
|
+
*
|
|
7
|
+
* xlsx formula 模式解析:
|
|
8
|
+
* - 回复含 `=...` → 公式单元格(kind:'f'——值 + formula 字符串保留——不计算)
|
|
9
|
+
* - 纯数字 → kind:'n';true/false → kind:'b';其余 → kind:'s'
|
|
10
|
+
* - 位置:回复内嵌 ref(`A1:` 前缀 / `B2=...`)优先,否则 ctx.ref(活动单元格)
|
|
11
|
+
*/
|
|
12
|
+
import type { AiContext, OfficeOp, OfficeStreamPayload } from '../model/types.ts';
|
|
13
|
+
import type { DocType } from '../model/types.ts';
|
|
14
|
+
export interface ParseResult {
|
|
15
|
+
ops: OfficeOp[];
|
|
16
|
+
/** 解析说明(UI 展示;不可解析时建议) */
|
|
17
|
+
note?: string;
|
|
18
|
+
}
|
|
19
|
+
/** AI 回复 → cell-set ops(formula 模式) */
|
|
20
|
+
export declare function parseFormulaReply(text: string, ctx: AiContext): ParseResult;
|
|
21
|
+
/** AI 回复 → shape-set op(pptx text 模式——选中 shape 文本替换) */
|
|
22
|
+
export declare function parseShapeTextReply(text: string, ctx: AiContext): ParseResult;
|
|
23
|
+
/** docx/text 模式:回复即正文——Editor ai-apply 语义由 Editor 层处理(本模块不产生 op) */
|
|
24
|
+
export declare function parseTextReply(_text: string, _ctx: AiContext): ParseResult;
|
|
25
|
+
export declare function parseReplyByMode(text: string, ctx: AiContext): ParseResult;
|
|
26
|
+
/**
|
|
27
|
+
* 发射 office 事件(AI 关联——payload.ai.messageId + target = messageId——
|
|
28
|
+
* editEvents ↔ aiEvents 一条链:`__edit_tail(50,'office')` ↔ `__ai_events(n,{messageId})`)
|
|
29
|
+
*/
|
|
30
|
+
export declare function officeAiEmit(payload: Omit<OfficeStreamPayload, 'ai'> & {
|
|
31
|
+
ai: {
|
|
32
|
+
messageId: string;
|
|
33
|
+
status: 'suggested' | 'accepted' | 'rejected';
|
|
34
|
+
};
|
|
35
|
+
}): void;
|
|
36
|
+
/** 便捷:AI 建议接受 = 普通 op 落地(ai 元数据——接受状态) */
|
|
37
|
+
export declare function emitAiApply(docType: DocType, op: OfficeOp, messageId: string): void;
|
|
38
|
+
/** 便捷:AI 建议拒绝(不产生 op——状态记录审计) */
|
|
39
|
+
export declare function emitAiReject(docType: DocType, messageId: string, reason?: string): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/components/OfficeEditor/model/apply — ODES 折叠(fold)纯函数
|
|
3
|
+
*
|
|
4
|
+
* 核心不变量:snapshot = fold(全部事件)——apply 每个 op 得到新状态(不可变)。
|
|
5
|
+
* - docx:委托 Editor applyEdit(EditEvent 全量复用——零新逻辑)
|
|
6
|
+
* - xlsx/pptx:各自 apply(纯函数——Map 复制、数组复制)
|
|
7
|
+
*/
|
|
8
|
+
import type { DeckState, DocType, OfficeCheckpoint, OfficeOp, OfficeSnapshot, SheetOp, SlideOp, WorkbookState } from './types.ts';
|
|
9
|
+
export declare function emptySnapshot(docType: DocType): OfficeSnapshot;
|
|
10
|
+
/** 'A1' → { row: 0, col: 0 }(col 字母段、row 数字段) */
|
|
11
|
+
export declare function parseRef(ref: string): {
|
|
12
|
+
row: number;
|
|
13
|
+
col: number;
|
|
14
|
+
};
|
|
15
|
+
export declare function toRef(row: number, col: number): string;
|
|
16
|
+
/** 行列插入后引用平移(单元格重定位;公式字符串不重算——裁剪登记) */
|
|
17
|
+
export declare function shiftCellRef(ref: string, atRow: number, atCol: number, dRow: number, dCol: number): string | null;
|
|
18
|
+
export declare function applySheetOp(wb: WorkbookState, op: SheetOp): WorkbookState;
|
|
19
|
+
export declare function applySlideOp(deck: DeckState, op: SlideOp): DeckState;
|
|
20
|
+
/** 单 op 应用(docx 委托 applyEdit——EditEvent 全量复用) */
|
|
21
|
+
export declare function applyOfficeOp(snapshot: OfficeSnapshot, op: OfficeOp): OfficeSnapshot;
|
|
22
|
+
/** checkpoint 折叠:snapshot + apply tail 全部 op(不变量:结果 = 全部事件 fold) */
|
|
23
|
+
export declare function foldOffice(cp: OfficeCheckpoint): OfficeSnapshot;
|
|
24
|
+
/** 校验 op 是否属于 docType 命名空间(跨类型 op 拒绝——诚实裁剪) */
|
|
25
|
+
export declare function opBelongsTo(op: OfficeOp, docType: DocType): boolean;
|