@xiongtao.xt/tau-plugins 0.31.1 → 0.33.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +2 -2
  3. package/client/{chunk-ECEBBIHP.js → chunk-SVY5LBAT.js} +44 -0
  4. package/docs/README.md +43 -0
  5. package/docs/ai.md +169 -0
  6. package/docs/backend.md +168 -0
  7. package/docs/editor.md +590 -0
  8. package/docs/excerpts.md +126 -0
  9. package/docs/flashcards.md +127 -0
  10. package/docs/getting-started.md +134 -0
  11. package/docs/mcp.md +173 -0
  12. package/docs/plugin-basics.md +209 -0
  13. package/docs/providers/README.md +108 -0
  14. package/docs/providers/images.md +65 -0
  15. package/docs/providers/publish.md +144 -0
  16. package/docs/providers/sync.md +75 -0
  17. package/docs/providers/tts.md +62 -0
  18. package/docs/providers/upload.md +77 -0
  19. package/docs/providers/video.md +118 -0
  20. package/docs/release.md +130 -0
  21. package/docs/services.md +159 -0
  22. package/docs/site-data.md +90 -0
  23. package/docs/skills.md +129 -0
  24. package/docs/storage-and-network.md +62 -0
  25. package/docs/ui.md +336 -0
  26. package/docs/user-data.md +389 -0
  27. package/docs/whiteboard.md +290 -0
  28. package/package.js +1 -1
  29. package/package.json +1 -1
  30. package/publish-artifacts.js +38 -1
  31. package/types/packages/db-schema/content-resources.d.ts +76 -0
  32. package/types/packages/editor/contracts.d.ts +1 -1
  33. package/types/packages/editor/source-contracts.d.ts +41 -1
  34. package/types/packages/editor/src/core/api/extension.d.ts +15 -0
  35. package/types/packages/editor/src/core/api/index.d.ts +5 -1
  36. package/types/packages/editor/src/core/api/resources.d.ts +6 -0
  37. package/types/packages/editor/src/core/components/ElementFrame/index.d.ts +25 -0
  38. package/types/packages/editor/src/core/utils/blockKeyTarget.d.ts +9 -0
  39. package/types/packages/plugins/src/categories.d.ts +3 -0
  40. package/types/packages/plugins/src/contract.d.ts +0 -2
  41. package/types/packages/plugins/src/editor.d.ts +1 -0
  42. package/types/packages/plugins/src/element.d.ts +11 -0
  43. package/types/packages/plugins/src/index.d.ts +4 -1
  44. package/types/packages/plugins/src/media.d.ts +69 -0
  45. package/types/packages/plugins/src/package.d.ts +2 -0
  46. package/types/packages/ui/src/index.d.ts +1 -0
  47. package/types/packages/ui/src/primitives/Sortable.d.ts +32 -0
  48. package/types/packages/ui/src/primitives/Tabs.d.ts +1 -3
  49. package/AI.md +0 -116
  50. package/API.md +0 -881
  51. package/BACKEND.md +0 -68
  52. package/DEVELOPMENT.md +0 -173
  53. package/EDITOR.md +0 -192
  54. package/FLASHCARDS.md +0 -92
  55. package/IMAGES.md +0 -16
  56. package/MCP.md +0 -118
  57. package/NOTIFICATIONS.md +0 -48
  58. package/PUBLISH-DATA.md +0 -48
  59. package/PUBLISH.md +0 -50
  60. package/SERVICES.md +0 -119
  61. package/SYNC.md +0 -37
  62. package/TTS.md +0 -20
  63. package/UPLOAD.md +0 -35
  64. package/VIDEO.md +0 -65
  65. package/WHITEBOARD.md +0 -115
  66. package/types/packages/plugins/src/skills.d.ts +0 -8
@@ -0,0 +1,290 @@
1
+ # 白板扩展
2
+
3
+ 白板扩展让你在 Tau 的白板上添加自己的元素(如图形、图表、组件),以及创建它们的工具。白板扩展分为两部分:
4
+
5
+ - **元素**:静态声明,描述元素长什么样、能怎么交互。没有打开任何画布时,Tau 也需要读取这些声明,例如生成预览图。
6
+ - **交互贡献**:每块画布单独创建,提供工具、命令、属性面板、快捷键等。
7
+
8
+ ## 第一个元素:方块
9
+
10
+ ### 1. 在 manifest 中声明
11
+
12
+ ```json
13
+ {
14
+ "whiteboard": true
15
+ }
16
+ ```
17
+
18
+ ### 2. 定义扩展
19
+
20
+ ```tsx
21
+ import { definePlugin } from "@tau/plugins";
22
+ import { defineElement, defineWhiteboardExtension } from "@tau/plugins/whiteboard";
23
+
24
+ declare const __PLUGIN_META__: { id: string; name: string; version: string };
25
+
26
+ const BOX = `${__PLUGIN_META__.id}.box`;
27
+
28
+ function BoxIcon({ className }: { className?: string }) {
29
+ return (
30
+ <svg
31
+ className={className}
32
+ viewBox="0 0 24 24"
33
+ fill="none"
34
+ stroke="currentColor"
35
+ strokeWidth="1.5"
36
+ >
37
+ <rect x="3" y="5" width="18" height="14" rx="2" />
38
+ </svg>
39
+ );
40
+ }
41
+
42
+ const boxExtension = defineWhiteboardExtension({
43
+ id: __PLUGIN_META__.id,
44
+ // 元素:静态声明
45
+ elements: [
46
+ defineElement({
47
+ type: BOX,
48
+ presentation: { label: "方块", icon: BoxIcon },
49
+ render: ({ element }) => (
50
+ <rect
51
+ x={element.x}
52
+ y={element.y}
53
+ width={element.width}
54
+ height={element.height}
55
+ rx={8}
56
+ fill="var(--tau-accent-soft)"
57
+ stroke="var(--tau-accent)"
58
+ />
59
+ ),
60
+ create: {
61
+ gesture: "drag",
62
+ defaultSize: [160, 90],
63
+ build: ({ rect }) => ({ id: crypto.randomUUID(), type: BOX, ...rect, data: {} }),
64
+ },
65
+ }),
66
+ ],
67
+ // 交互贡献:每块画布调用一次
68
+ create: () => ({
69
+ tools: [
70
+ {
71
+ id: `${__PLUGIN_META__.id}.box-tool`,
72
+ label: "方块",
73
+ icon: BoxIcon,
74
+ element: BOX,
75
+ group: "drawing",
76
+ order: 90,
77
+ },
78
+ ],
79
+ }),
80
+ });
81
+
82
+ export default definePlugin({ ...__PLUGIN_META__, whiteboardExtensions: [boxExtension] });
83
+ ```
84
+
85
+ ### 3. 构建、安装、重启
86
+
87
+ 白板扩展在 Tau 启动时装配,启用、停用和更新都要**完全退出并重新打开 Tau** 才生效。之后打开白板,在「扩展工具」菜单中就能找到「方块」。
88
+
89
+ ## 命名规则
90
+
91
+ - 扩展 `id` 是你的插件 ID,或以插件 ID 加点开头(如 `yourname.shapes.box`)。
92
+ - 元素 `type` 必须以插件 ID 加点开头(如 `yourname.shapes.box`)。
93
+ - 工具、命令的 `id` 同样以插件 ID 作前缀。
94
+
95
+ 已经有白板用了某个元素类型,就不要再改名,否则已有的元素会显示为未知元素。
96
+
97
+ ## 元素
98
+
99
+ 元素的通用字段(`id`、`type`、`x`、`y`、`width`、`height` 等)在元素顶层,你自己的数据放在 `data` 里。
100
+
101
+ ### 元素声明
102
+
103
+ | 字段 | 说明 |
104
+ | -------------------------- | ------------------------------------------------------------------------------------ |
105
+ | `type` | 元素类型 |
106
+ | `render` | 渲染组件,收到 `element`,返回 SVG 内容 |
107
+ | `presentation` | 可选。`label`(类型名)、`icon`(图标)、`getName(element)`(列表中显示的名称) |
108
+ | `create` | 可选。创建方式,声明后才能注册创建型工具,见[创建工具](#创建工具) |
109
+ | `interaction` | 可选。缩放手柄与连接点,见下文 |
110
+ | `schema` | 可选。约束 `data` 结构的 [Standard Schema](https://standardschema.dev),必须同步校验 |
111
+ | `schemaVersion`、`migrate` | 可选。`data` 结构的版本号,以及从旧版本逐步升级的迁移函数 |
112
+ | `sketch` | 可选。为 `true` 时元素支持手绘画风,见[手绘画风](#手绘画风) |
113
+ | `schematic` | 可选。简略预览,见[简略预览](#简略预览) |
114
+
115
+ 用 `defineElement<TData>` 声明 `data` 的类型,或者传入 `schema` 让类型自动推断:
116
+
117
+ ```ts
118
+ interface StickyData {
119
+ text: string;
120
+ }
121
+
122
+ defineElement<StickyData>({
123
+ type: `${__PLUGIN_META__.id}.sticky`,
124
+ render: ({ element }) => <text x={element.x} y={element.y}>{element.data.text}</text>,
125
+ });
126
+ ```
127
+
128
+ ### 缩放与连接
129
+
130
+ ```ts
131
+ interaction: {
132
+ resize: { handles: "corners", preserveAspectRatio: true },
133
+ connect: "rect",
134
+ },
135
+ ```
136
+
137
+ - `resize.handles` 可选 `corners`(只有四角)或 `all`(默认,四角加四边)。`preserveAspectRatio` 默认 `false`,用户按住 Shift 时仍可等比缩放。
138
+ - 缩放手柄、吸附、撤销都由 Tau 处理,不要在元素里自己画缩放手柄。
139
+ - `connect: "rect"` 让元素有矩形连接点,可以用连线连接。不声明就没有连接点。
140
+
141
+ ### 显示名称
142
+
143
+ `presentation.getName(element)` 返回元素在列表中显示的名称,例如便签显示它的文字。用户手动起的名字优先;返回空时显示 `label`。
144
+
145
+ ### 版本迁移
146
+
147
+ `data` 的结构需要变化时,提高 `schemaVersion`,并在 `migrate` 中提供每一步升级:
148
+
149
+ ```ts
150
+ schemaVersion: 2,
151
+ migrate: {
152
+ // 从版本 1 升到版本 2
153
+ 1: element => ({ ...element, data: { ...element.data, color: "accent" } }),
154
+ },
155
+ ```
156
+
157
+ `migrate` 必须覆盖从 1 到当前版本的每一步。
158
+
159
+ ## 创建工具
160
+
161
+ ### 创建型工具
162
+
163
+ 元素声明了 `create` 后,注册一个 `element` 指向它的工具,用户就能在画布上用手势创建元素。
164
+
165
+ | `create` 字段 | 说明 |
166
+ | ------------- | -------------------------------------------------------------------------------------------------------- |
167
+ | `gesture` | `"click"`(点击放置)或 `"drag"`(拖拽绘制) |
168
+ | `build` | 根据手势生成元素,收到 `rect`、`start`、`end`、`click`、`options` 等,返回 `null` 则不创建;可以是异步的 |
169
+ | `defaultSize` | `click` 时必填,元素的默认尺寸;`drag` 时用于单击创建 |
170
+ | `minSize` | 可选。`drag` 绘制的尺寸小于它时不创建 |
171
+ | `constrain` | 可选。`drag` 时按 Shift 的约束:`box`(等比,默认)或 `line`(45° 吸附) |
172
+ | `after` | 可选。创建后 `select`(选中,默认)、`edit`(进入编辑)或 `none` |
173
+ | `stay` | 可选。为 `true` 时创建后保持当前工具,可以连续创建 |
174
+ | `hint` | 可选。工具激活时画布底部的提示文字 |
175
+
176
+ ### 工具
177
+
178
+ | 工具字段 | 说明 |
179
+ | ---------------- | ------------------------------------------------------ |
180
+ | `id` | 工具 ID |
181
+ | `label` | 名称,显示在菜单和提示中 |
182
+ | `icon` | 图标组件 |
183
+ | `element` | 创建型工具:要创建的元素类型(与 `command` 二选一) |
184
+ | `command` | 命令型工具:点击时执行的命令 ID(与 `element` 二选一) |
185
+ | `group`、`order` | 分组与排序 |
186
+ | `panel` | 可选。参数面板,见下文 |
187
+
188
+ 插件的工具统一放在工具栏的「扩展工具」菜单里。
189
+
190
+ ### 创建前选择参数
191
+
192
+ 工具可以提供 `panel`,让用户先选好参数再执行:
193
+
194
+ ```tsx
195
+ panel: ({ run }) => (
196
+ <div>
197
+ <button onClick={() => run({ color: "accent" })}>强调色</button>
198
+ <button onClick={() => run({ color: "neutral" })}>中性色</button>
199
+ </div>
200
+ ),
201
+ ```
202
+
203
+ - 创建型工具在 `create.build` 的 `options` 中读到 `run` 传入的参数;命令型工具在命令的 `payload` 中读到。
204
+ - 返回、关闭和面板卸载由 Tau 管理,`run` 执行后面板自动关闭。不要在面板里自己监听画布的指针事件。
205
+
206
+ ## 交互贡献
207
+
208
+ `create(api)` 在每块画布上调用一次,返回这块画布上的交互贡献:
209
+
210
+ | 字段 | 说明 |
211
+ | ------------ | -------------------------------------------------------------------------------------------------------- |
212
+ | `tools` | 工具 |
213
+ | `commands` | 命令:`{ id, execute(api, payload), enabled?(api) }` |
214
+ | `hotkeys` | 快捷键:`{ key, command }`,键串格式如 `"mod+shift+b"` |
215
+ | `menus` | 菜单项:`{ id, label, command, order, when? }` |
216
+ | `inspectors` | 属性面板:`{ [元素类型]: 组件 }`。选中单个该类型元素时显示在浮动属性栏,组件收到 `element` 和 `onChange` |
217
+ | `activate` | 可选。画布显示时调用,返回的清理函数在画布隐藏时执行 |
218
+ | `dispose` | 可选。画布销毁时调用 |
219
+
220
+ - 每块画布的状态放在 `create` 的闭包里,不要放在模块顶层,否则会在多块画布之间共享。
221
+ - `elements` 必须写在扩展顶层,不能放进 `create` 的返回值。
222
+ - `mod+k`、`mod+p`、`mod+j`、Alt+方向键和 Esc 由 Tau 保留,不能声明。
223
+
224
+ ### 修改画布
225
+
226
+ 通过 `api` 修改画布,不要直接修改读到的元素对象:
227
+
228
+ | 方法 | 用途 |
229
+ | ----------------------------------------- | ---------------------------------------------- |
230
+ | `api.updateElement(id, patch)` | 修改一个元素的属性 |
231
+ | `api.submit(operations)` | 提交一组结构性操作(增删元素等),作为一步撤销 |
232
+ | `api.select(ids)` | 设置选区 |
233
+ | `api.getElement(id)`、`api.getSnapshot()` | 读取元素与画布快照 |
234
+ | `api.setActiveTool(id)` | 切换当前工具 |
235
+ | `api.readonly` | 画布是否只读 |
236
+
237
+ 这些方法统一处理只读、撤销和生命周期。元素组件中可以用 `useWhiteboardAPI()` 拿到所属扩展的 `api`。
238
+
239
+ ## 手绘画风
240
+
241
+ 声明 `sketch: true` 后,元素可以跟随白板的手绘画风设置:
242
+
243
+ - 在元素组件中调用 `useSketch(element)`,得到 `enabled`、`level`、`seed`,据此决定怎么绘制。页面画风变化时组件会自动更新。
244
+ - `SketchPaths` 渲染手绘路径;`sketch` 命名空间提供手绘算法(`wobble`、`resample`、`inkOutline` 等),直接复用,不要自己复制实现。
245
+ - `usePageSketch()` 读取所在页面的画风;`sketch.hashSeed(text)` 为预览生成稳定的随机种子。
246
+ - 属性面板可以使用 `AttrPopover`、`AttrToggle`、`OptionGrid`、`Seg` 组件,`SketchPopover` 提供与 Tau 一致的笔触设置。
247
+
248
+ ## 简略预览
249
+
250
+ `schematic(element, { dark })` 返回一组简单图元(矩形、路径等,类型见 `SchematicShape`),用于白板列表和导出弹窗里的缩略预览:
251
+
252
+ ```ts
253
+ schematic: element => [
254
+ { kind: "rect", x: element.x, y: element.y, w: element.width, h: element.height, r: 3, fill: "var(--tau-accent)" },
255
+ ],
256
+ ```
257
+
258
+ 它必须是纯函数:只读元素数据,不调用 hook,也不依赖某块画布已经打开。真正导出图片时仍然使用完整的 `render`。
259
+
260
+ ## 记住用户的选择
261
+
262
+ 交互贡献需要保存用户偏好(如上次选的颜色)时,在扩展上声明依赖存储服务:
263
+
264
+ ```ts
265
+ import { IPluginStorage, defineWhiteboardExtension } from "@tau/plugins/whiteboard";
266
+
267
+ defineWhiteboardExtension({
268
+ id: __PLUGIN_META__.id,
269
+ requires: [IPluginStorage],
270
+ elements: [/* ... */],
271
+ create: api => {
272
+ const storage = api.getService(IPluginStorage);
273
+ // storage.get / set / delete,与 context.storage 相同
274
+ return { tools: [/* ... */] };
275
+ },
276
+ });
277
+ ```
278
+
279
+ - 存储按插件和工作区隔离,与 [`context.storage`](storage-and-network.md) 是同一份数据。
280
+ - 只在交互贡献、工具面板、属性面板和 `create.build` 中使用。元素的 `render` 和 `schematic` 只能依据元素数据,不能读存储。
281
+ - 读取失败时不要当成空数据再写回,否则会覆盖用户原有的偏好。
282
+
283
+ ## 样式
284
+
285
+ - 颜色使用 Tau 的主题变量(如 `var(--tau-accent)`),这样会跟随明暗主题。
286
+ - 需要 Tailwind 工具类时,可以导入 `@tau/plugins/tailwind.css`,它提供 Tau 语义类到主题变量的映射。
287
+
288
+ ## 停用与卸载
289
+
290
+ 插件停用或卸载后,白板里已有的元素会保留,显示为未知元素的占位;重新安装并启用后恢复显示。保存、复制白板时也会保留这些元素的数据。
package/package.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  isPluginPermission,
15
15
  validateBackendManifest,
16
16
  validatePluginManifest
17
- } from "./client/chunk-ECEBBIHP.js";
17
+ } from "./client/chunk-SVY5LBAT.js";
18
18
  export {
19
19
  PLUGIN_AI_CAPABILITIES,
20
20
  PLUGIN_CATEGORIES,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiongtao.xt/tau-plugins",
3
- "version": "0.31.1",
3
+ "version": "0.33.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,7 +1,37 @@
1
1
  import {
2
2
  PLUGIN_SHARED_MODULES,
3
3
  validatePluginManifest
4
- } from "./client/chunk-ECEBBIHP.js";
4
+ } from "./client/chunk-SVY5LBAT.js";
5
+
6
+ // packages/db-schema/content-resources.ts
7
+ var CONTENT_RESOURCE_KINDS = [
8
+ "image",
9
+ "video",
10
+ "audio",
11
+ "file"
12
+ ];
13
+ var UNSAFE_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
14
+ var SEGMENT = /^[A-Za-z_$][\w$-]*(\[\])?$/;
15
+ var FIELD = /^[A-Za-z_$][\w$-]*$/;
16
+ var MAX_SPECS = 16;
17
+ var MAX_PATH_LENGTH = 128;
18
+ var isField = (value) => typeof value === "string" && FIELD.test(value) && !UNSAFE_SEGMENTS.has(value);
19
+ function isValidPath(path) {
20
+ if (typeof path !== "string" || !path || path.length > MAX_PATH_LENGTH) return false;
21
+ const parts = path.split(".");
22
+ return parts.every(
23
+ (part, index) => SEGMENT.test(part) && !UNSAFE_SEGMENTS.has(part.replace(/\[\]$/, "")) && (index < parts.length - 1 || !part.endsWith("[]"))
24
+ );
25
+ }
26
+ function isContentResourceSpec(value) {
27
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
28
+ const spec = value;
29
+ return isValidPath(spec.path) && CONTENT_RESOURCE_KINDS.includes(spec.kind) && ["preview", "alt", "caption"].every((key) => spec[key] === void 0 || isField(spec[key])) && Object.keys(spec).every((key) => ["path", "kind", "preview", "alt", "caption"].includes(key));
30
+ }
31
+ function assertContentResourceSpecs(value) {
32
+ if (!Array.isArray(value) || value.length > MAX_SPECS || !value.every(isContentResourceSpec))
33
+ throw new Error("\u8D44\u6E90\u58F0\u660E\u65E0\u6548");
34
+ }
5
35
 
6
36
  // packages/plugins/src/rendering.ts
7
37
  var PLUGIN_RENDER_VERSION = 1;
@@ -49,6 +79,13 @@ function getPluginNodeTypes(definition) {
49
79
  for (const element of extension.elements ?? []) {
50
80
  if (!element.type || types.includes(element.type))
51
81
  throw new Error(`\u63D2\u4EF6\u8282\u70B9\u7C7B\u578B\u4E3A\u7A7A\u6216\u91CD\u590D\uFF1A${element.type}`);
82
+ if (element.resources !== void 0) {
83
+ try {
84
+ assertContentResourceSpecs(element.resources);
85
+ } catch {
86
+ throw new Error(`\u63D2\u4EF6\u8282\u70B9\u8D44\u6E90\u58F0\u660E\u65E0\u6548\uFF1A${element.type}`);
87
+ }
88
+ }
52
89
  types.push(element.type);
53
90
  }
54
91
  }
@@ -0,0 +1,76 @@
1
+ export type ContentResourceKind = "image" | "video" | "audio" | "file";
2
+ export declare const CONTENT_RESOURCE_KINDS: readonly ContentResourceKind[];
3
+ export interface ContentResourceSpec {
4
+ /** 字段路径:`url`、`images[].url`;`[]` 表示数组的每一项 */
5
+ path: string;
6
+ kind: ContentResourceKind;
7
+ /** 同一资源的显示版本(如裁剪结果),与 path 末段同层的字段名,同样参与上传与改写 */
8
+ preview?: string;
9
+ /** 替代文字字段,与 path 末段同层 */
10
+ alt?: string;
11
+ /** 说明文字字段,与 path 末段同层 */
12
+ caption?: string;
13
+ }
14
+ /** 节点类型 → 资源字段 */
15
+ export type ContentResourceDeclarations = Readonly<Record<string, readonly ContentResourceSpec[]>>;
16
+ /** 宿主内置节点的资源字段 */
17
+ export declare const BUILTIN_CONTENT_RESOURCES: {
18
+ readonly image: readonly [{
19
+ readonly path: "url";
20
+ readonly kind: "image";
21
+ readonly preview: "previewUrl";
22
+ readonly alt: "alt";
23
+ readonly caption: "description";
24
+ }];
25
+ readonly "inline-image": readonly [{
26
+ readonly path: "url";
27
+ readonly kind: "image";
28
+ readonly alt: "alt";
29
+ }];
30
+ readonly audio: readonly [{
31
+ readonly path: "src";
32
+ readonly kind: "audio";
33
+ }];
34
+ readonly video: readonly [{
35
+ readonly path: "src";
36
+ readonly kind: "video";
37
+ }];
38
+ readonly "file-attachment": readonly [{
39
+ readonly path: "filePath";
40
+ readonly kind: "file";
41
+ }, {
42
+ readonly path: "localFilePath";
43
+ readonly kind: "file";
44
+ }];
45
+ };
46
+ export declare function isContentResourceSpec(value: unknown): value is ContentResourceSpec;
47
+ /** 校验来自插件或其他设备的声明;形状不对时抛错 */
48
+ export declare function assertContentResourceSpecs(value: unknown): asserts value is readonly ContentResourceSpec[];
49
+ /** 合并多份声明,后者覆盖同类型的前者 */
50
+ export declare function mergeContentResources(...sources: readonly ContentResourceDeclarations[]): ContentResourceDeclarations;
51
+ /** 资源所在的对象(字段的父对象)及字段名 */
52
+ export interface ContentResourceSlot {
53
+ spec: ContentResourceSpec;
54
+ owner: Record<string, unknown>;
55
+ field: string;
56
+ }
57
+ /** 按声明列出一个节点里的资源位置(不深入子节点) */
58
+ export declare function contentResourceSlots(node: unknown, specs: readonly ContentResourceSpec[] | undefined): ContentResourceSlot[];
59
+ /** 资源位置上实际的地址:主地址与显示版本,空串跳过 */
60
+ export declare function contentResourceValues(slot: ContentResourceSlot): string[];
61
+ export interface ContentResourceEntry {
62
+ node: Record<string, unknown>;
63
+ slot: ContentResourceSlot;
64
+ }
65
+ /** 按文档顺序遍历节点树里所有声明了资源的位置 */
66
+ export declare function forEachContentResource(nodes: readonly unknown[], declarations: ContentResourceDeclarations, visit: (entry: ContentResourceEntry) => void): void;
67
+ /** 收集节点树里的资源地址(去重,保持首次出现的顺序) */
68
+ export declare function collectContentResources(nodes: readonly unknown[], declarations: ContentResourceDeclarations): {
69
+ url: string;
70
+ kind: ContentResourceKind;
71
+ }[];
72
+ export type ContentResourceMapper = (url: string, kind: ContentResourceKind) => string | undefined;
73
+ /** 不可变地改写一个节点里声明的资源(不深入子节点);返回 undefined 表示保留原值 */
74
+ export declare function mapNodeResources<T>(node: T, specs: readonly ContentResourceSpec[] | undefined, mapper: ContentResourceMapper): T;
75
+ /** 不可变地改写整棵节点树里声明的资源 */
76
+ export declare function mapContentResources<T>(nodes: readonly T[], declarations: ContentResourceDeclarations, mapper: ContentResourceMapper): T[];
@@ -1,3 +1,3 @@
1
1
  /** 无 React/DOM 运行时代码的声明入口,供应用 SDK 适配现有扩展协议。 */
2
2
  export { defineExtension, defineElement } from "./src/core/api/extension";
3
- export type { EditorExtension, ElementSpec, ElementRenderProps } from "./src/core/api/extension";
3
+ export type { EditorExtension, ElementSpec, ElementRenderProps, ElementFrame, } from "./src/core/api/extension";
@@ -1,4 +1,4 @@
1
- import type { ReactNode, ReactElement } from "react";
1
+ import type { MouseEvent, ReactNode, ReactElement } from "react";
2
2
  import type { LucideIcon } from "lucide-react";
3
3
  import type { Editor, Element, Path } from "slate";
4
4
  import type { ElementBehaviors, ParagraphElement, BlockElement } from "./api";
@@ -35,6 +35,46 @@ export interface SourceEditorRuntime {
35
35
  insertSourceBlock(editor: Editor, element: BlockElement, line?: number): Path | undefined;
36
36
  sourceBlockParagraphBehavior(type: string): ElementBehaviors<ParagraphElement>;
37
37
  }
38
+ export interface ElementAccess {
39
+ readOnly: boolean;
40
+ /** 外框里的块是否被点选;未声明外框时恒为 false */
41
+ selected: boolean;
42
+ /** 字段合并进节点,进入撤销记录并同步给协作者;只读、节点已被替换或卸载后不写入 */
43
+ update(properties: Record<string, unknown>): void;
44
+ remove(): void;
45
+ /** 选中外框里的块 */
46
+ select(): void;
47
+ /** 在指定位置打开块菜单;需要外框 */
48
+ openMenu(position: {
49
+ x: number;
50
+ y: number;
51
+ }): void;
52
+ }
53
+ export interface BlockToolbarProps {
54
+ /** 无障碍名称(如「画廊属性」) */
55
+ label: string;
56
+ className?: string;
57
+ children: ReactNode;
58
+ }
59
+ export interface BlockToolbarButtonProps {
60
+ /** 按钮名称:图标按钮的提示与无障碍名称 */
61
+ title: string;
62
+ shortcut?: string;
63
+ /** 显示在图标旁的文字(主操作用) */
64
+ label?: string;
65
+ icon?: ReactNode;
66
+ active?: boolean;
67
+ mono?: boolean;
68
+ onClick(event: MouseEvent<HTMLButtonElement>): void;
69
+ }
70
+ export interface ElementRuntime {
71
+ useElement(element: Element): ElementAccess;
72
+ /** 打开文档图片查看器,定位到该元素的第 index 个图片资源(按资源声明计) */
73
+ useMediaViewer(element: Element): (index?: number) => void;
74
+ BlockToolbar(props: BlockToolbarProps): ReactElement;
75
+ BlockToolbarButton(props: BlockToolbarButtonProps): ReactElement;
76
+ BlockToolbarGap(): ReactElement;
77
+ }
38
78
  export interface InlineSourceRuntime {
39
79
  useInlineSource(element: Element): {
40
80
  readOnly: boolean;
@@ -9,6 +9,7 @@ import type { ElementDrag } from "./drag";
9
9
  import type { ElementIngestSpec } from "./ingest";
10
10
  import type { ElementCopy } from "./copy";
11
11
  import type { DecorationSpec, MarkSpec } from "./marks";
12
+ import type { ContentResourceSpec } from "../../../../db-schema/content-resources";
12
13
  /**
13
14
  * 扩展 API v2(01 §5.1):声明式描述「这个扩展贡献了什么」,内核负责装配、隔离与分发。
14
15
  * 所有扩展都是 defineExtension 的产物(旧 IExtension 适配层已删除,ER-019)。
@@ -35,6 +36,16 @@ export interface ElementEmptyContext {
35
36
  /** 使用当前编辑器已安装的声明判断子节点;未知或失败时保留。 */
36
37
  isEmpty(node: Descendant): boolean;
37
38
  }
39
+ /**
40
+ * void 块的公共外框:点选描边、右键菜单、选中时 Delete / Backspace 删除与 Escape 取消、末尾「添加段落」。
41
+ * 声明后 render 只渲染内容(收到的 attributes 为空对象、children 为 null)。
42
+ */
43
+ export interface ElementFrame {
44
+ /** 末尾的「添加段落」,默认显示 */
45
+ appendParagraph?: boolean;
46
+ /** 选中时的按键 → 命令 id(如 `{ Enter: "yourname.gallery.edit" }`),命令收到该元素 */
47
+ keys?: Readonly<Record<string, string>>;
48
+ }
38
49
  export interface ElementSpec<E extends Element = Element> {
39
50
  /** 元素 type;内置类型沿用现有名字,兼容已有文档 */
40
51
  type: string;
@@ -53,6 +64,10 @@ export interface ElementSpec<E extends Element = Element> {
53
64
  drag?: ElementDrag;
54
65
  /** 内容注入声明(01 §5.4):drop 与 paste 共用的认领规则;块组件不再改写 insertData */
55
66
  ingest?: ElementIngestSpec;
67
+ /** 节点里的资源字段:导出打包、协作上传、灯箱等只按它找图片、视频、音频与文件 */
68
+ resources?: readonly ContentResourceSpec[];
69
+ /** void 块的公共外框;只适用于 `kind: "block"` 且 `isVoid` 的元素 */
70
+ frame?: boolean | ElementFrame;
56
71
  }
57
72
  export interface ExtensionLayerProps {
58
73
  /** 根 UI 在 Editable 外,不能通过 Slate 的 useReadOnly 读取此状态。 */
@@ -4,7 +4,7 @@
4
4
  * 只放写扩展需要的东西:声明、服务、主题、少量工具。编辑器组件、kit 工厂等宿主用的留在包根入口。
5
5
  * 未标注过渡用途的导出按稳定 API 维护;过渡导出只供现有内部消费者迁移使用。
6
6
  */
7
- export { EXTENSION_API_VERSION, defineExtension, defineElement, type EditorExtension, type ExtensionDefinition, type ElementSpec, type ElementEmptyContext, type ElementBehaviors, type ElementRenderProps, type ExtensionLayerProps, } from "./extension";
7
+ export { EXTENSION_API_VERSION, defineExtension, defineElement, type EditorExtension, type ExtensionDefinition, type ElementSpec, type ElementFrame, type ElementEmptyContext, type ElementBehaviors, type ElementRenderProps, type ExtensionLayerProps, } from "./extension";
8
8
  export type { AnyExtension } from "./legacy";
9
9
  export type { CopyDecision, CopyContext, ElementCopy } from "./copy";
10
10
  export type { MarkSpec, MarkRenderProps, DecorationSpec } from "./marks";
@@ -14,6 +14,10 @@ export { Tooltip as EditorTooltip } from "../ui/Tooltip";
14
14
  export { CaretRight as EditorCaretRight } from "../ui/CaretRight";
15
15
  export { getParagraphShortcutEntry } from "../utils/paragraphShortcut";
16
16
  export type { IngestContext, ElementIngestSpec } from "./ingest";
17
+ export { getElementResources } from "./resources";
18
+ export { ELEMENT_SELECTED_OUTLINE_CLASS, useElementFrame, type ElementFrameState, } from "../components/ElementFrame/index";
19
+ export { isOverlayTarget, isTextTarget, shouldIgnoreBlockKey } from "../utils/blockKeyTarget";
20
+ export type { ContentResourceSpec, ContentResourceKind, ContentResourceDeclarations, } from "../../../../db-schema/content-resources";
17
21
  export { CommandRegistry, createCommandDispatcher, type EditorCommand, type EditorCommandContext, type EditorHotkey, type RegisteredCommand, type CommandDispatcher, type CommandGuard, } from "./command";
18
22
  export { type EditorMenuEntry, type EditorMenuContext, type EditorMenuContribution, type MenuValue, } from "./menu";
19
23
  export { createServiceId, IThemeService, IExtensionErrorReporter, IEditorNotifier, type ServiceIdentifier, } from "../services/tokens";
@@ -0,0 +1,6 @@
1
+ import type { Editor } from "slate";
2
+ import type { ContentResourceDeclarations } from "../../../../db-schema/content-resources";
3
+ /** 内核在创建编辑器时绑定当前装配的资源声明 */
4
+ export declare function bindElementResources(editor: Editor, resources: ContentResourceDeclarations): void;
5
+ /** 当前编辑器已安装的节点资源声明(节点类型 → 资源字段) */
6
+ export declare function getElementResources(editor: Editor): ContentResourceDeclarations;
@@ -0,0 +1,25 @@
1
+ import { type ReactNode } from "react";
2
+ import { type RenderElementProps } from "slate-react";
3
+ import type { ElementFrame as ElementFrameOptions } from "../../api/extension";
4
+ /** 选中描边:外扩 3px,随内容圆角 */
5
+ export declare const ELEMENT_SELECTED_OUTLINE_CLASS = "outline-solid outline-2 outline-offset-[3px] outline-[var(--ed-accent)]";
6
+ export interface ElementFrameState {
7
+ selected: boolean;
8
+ select(): void;
9
+ deselect(): void;
10
+ /** 在指定位置打开块菜单(与右键、块手柄同一份条目) */
11
+ openMenu(position: {
12
+ x: number;
13
+ y: number;
14
+ }): void;
15
+ }
16
+ /** 外框里的块读取选中与菜单状态;不在外框里时为 null */
17
+ export declare const useElementFrame: () => ElementFrameState | null;
18
+ /**
19
+ * void 块的公共外框:点选与选中描边、右键菜单、选中时的删除与声明的按键、末尾「添加段落」。
20
+ * 块的 render 只渲染内容,attributes 与 children 由外框处理。
21
+ */
22
+ export declare function ElementFrame({ attributes, element, children, options, content, }: RenderElementProps & {
23
+ options: ElementFrameOptions;
24
+ content: ReactNode;
25
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,9 @@
1
+ /** 按键目标是文本输入(input / textarea / 可编辑区):块级快捷键不拦截 */
2
+ export declare const isTextTarget: (target: EventTarget | null) => boolean;
3
+ /** 按键目标在浮层里(灯箱、裁剪编辑器、对话框):交给浮层自己处理 */
4
+ export declare const isOverlayTarget: (target: EventTarget | null) => boolean;
5
+ /**
6
+ * 选中媒体块时的块级快捷键是否应忽略这次按键:输入法组合、浮层内、文本输入(如说明框)都不处理。
7
+ * 点选块后焦点通常留在外层编辑器根上——它包含这个块,不算文本输入,快捷键照常生效。
8
+ */
9
+ export declare function shouldIgnoreBlockKey(event: KeyboardEvent, block: HTMLElement | null): boolean;
@@ -20,6 +20,9 @@ export declare const PLUGIN_CATEGORIES: readonly [{
20
20
  }, {
21
21
  readonly id: "ai";
22
22
  readonly label: "AI";
23
+ }, {
24
+ readonly id: "skill";
25
+ readonly label: "Skill";
23
26
  }, {
24
27
  readonly id: "voice";
25
28
  readonly label: "语音";
@@ -1,5 +1,4 @@
1
1
  import type { PluginPages } from "./pages";
2
- import type { PluginSkill } from "./skills";
3
2
  import type { PluginAI, PluginAIFeature } from "./ai";
4
3
  import type { WhiteboardExtension } from "./whiteboard";
5
4
  import type { PluginServices } from "./services";
@@ -97,7 +96,6 @@ export interface PluginDefinition {
97
96
  readonly editorExtensions?: readonly PluginEditorExtension[];
98
97
  readonly whiteboardExtensions?: readonly WhiteboardExtension[];
99
98
  readonly editorCodecs?: readonly MarkdownCodec[];
100
- readonly skills?: readonly PluginSkill[];
101
99
  readonly ai?: {
102
100
  readonly features: readonly PluginAIFeature[];
103
101
  };
@@ -4,6 +4,7 @@ import type { Disposable } from "./index";
4
4
  export { defineElement as defineEditorElement } from "../../editor/contracts";
5
5
  export type { ElementRenderProps as EditorElementRenderProps } from "../../editor/contracts";
6
6
  export type { MarkdownCodec as EditorMarkdownCodec } from "../../markdown/src/index";
7
+ export type { ContentResourceSpec as EditorResourceSpec, ContentResourceKind as EditorResourceKind, } from "../../db-schema/content-resources";
7
8
  /** 作者通过模块增强登记自定义节点,无需引用编辑器内部包。 */
8
9
  declare const elementMapBrand: unique symbol;
9
10
  declare const inlineMapBrand: unique symbol;
@@ -0,0 +1,11 @@
1
+ import type { ElementRuntime } from "../../editor/source-contracts";
2
+ export type { ElementAccess, BlockToolbarProps, BlockToolbarButtonProps, ElementRuntime, } from "../../editor/source-contracts";
3
+ export type { ElementFrame as EditorElementFrame } from "../../editor/contracts";
4
+ export declare function configureElement(value: ElementRuntime): void;
5
+ /** 读写当前节点:只读状态、外框选中、合并字段、删除、打开块菜单 */
6
+ export declare const useElement: ElementRuntime["useElement"];
7
+ /** 打开文档图片查看器,定位到该节点的第 index 个图片资源 */
8
+ export declare const useMediaViewer: ElementRuntime["useMediaViewer"];
9
+ export declare const BlockToolbar: ElementRuntime["BlockToolbar"];
10
+ export declare const BlockToolbarButton: ElementRuntime["BlockToolbarButton"];
11
+ export declare const BlockToolbarGap: ElementRuntime["BlockToolbarGap"];