@xiongtao.xt/tau-plugins 0.4.0 → 0.7.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/AI.md +75 -0
- package/EDITOR.md +188 -0
- package/README.md +5 -3
- package/SERVICES.md +139 -0
- package/package.json +2 -2
- package/runtime.js +1 -1
- package/types/packages/editor/src/types/editor.d.ts +2 -0
- package/types/packages/plugins/src/ai.d.ts +40 -0
- package/types/packages/plugins/src/contract.d.ts +14 -0
- package/types/packages/plugins/src/editor.d.ts +12 -1
- package/types/packages/plugins/src/index.d.ts +6 -1
- package/types/packages/plugins/src/services.d.ts +103 -0
- package/types/packages/plugins/src/skills.d.ts +8 -0
- package/types/packages/plugins/src/source-editor.d.ts +44 -0
- package/types/packages/ui/src/primitives/Dialog.d.ts +1 -1
- package/types/packages/ui/src/primitives/Select.d.ts +1 -1
- package/types/packages/ui/src/primitives/Toast.d.ts +5 -2
package/AI.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# AI 服务
|
|
2
|
+
|
|
3
|
+
此能力需要 SDK 0.7.0 及以上,以及支持插件 AI 服务的 Tau 客户端。
|
|
4
|
+
|
|
5
|
+
插件声明 AI 功能,由用户在插件详情的「设置 → AI 配置」分配已有模型并开启授权。全局「大语言模型」设置管理服务商和密钥;插件只能通过 `ctx.ai` 请求模型,接口不返回密钥、连接地址或用户的聊天历史。
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { definePlugin } from "@tau/plugins";
|
|
9
|
+
|
|
10
|
+
declare const __PLUGIN_META__: { id: string; name: string; version: string };
|
|
11
|
+
|
|
12
|
+
export default definePlugin({
|
|
13
|
+
...__PLUGIN_META__,
|
|
14
|
+
ai: {
|
|
15
|
+
features: [{ id: "summary", name: "文章摘要", description: "概括用户选择的文章" }],
|
|
16
|
+
},
|
|
17
|
+
create: () => ({
|
|
18
|
+
activate(ctx) {
|
|
19
|
+
ctx.commands.register({
|
|
20
|
+
id: "summary",
|
|
21
|
+
title: "生成摘要",
|
|
22
|
+
async run(input) {
|
|
23
|
+
const status = await ctx.ai.getStatus("summary");
|
|
24
|
+
if (!status.configured || !status.enabled) {
|
|
25
|
+
ctx.ai.openSettings("summary");
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const result = await ctx.ai.chat({
|
|
29
|
+
feature: "summary",
|
|
30
|
+
messages: [
|
|
31
|
+
{ role: "system", content: "概括主要内容,不添加原文以外的信息。" },
|
|
32
|
+
{ role: "user", content: typeof input === "string" ? input : "请先选择正文。" },
|
|
33
|
+
],
|
|
34
|
+
maxOutputTokens: 1024,
|
|
35
|
+
});
|
|
36
|
+
return result.text;
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 接口
|
|
45
|
+
|
|
46
|
+
| 接口 | 返回与用途 |
|
|
47
|
+
| ------------------------ | ----------------------------------------------------------------------------------- |
|
|
48
|
+
| `getStatus(feature)` | `{ configured, enabled }`;模型被删除也视为未配置 |
|
|
49
|
+
| `openSettings(feature?)` | 打开当前插件设置,由用户分配模型、管理授权 |
|
|
50
|
+
| `chat(request)` | `{ text, usage }`;usage 未知时为 null |
|
|
51
|
+
| `stream(request)` | 异步迭代器,依次产生 `{ type: "text", delta }`,最后产生 `{ type: "done", result }` |
|
|
52
|
+
|
|
53
|
+
请求字段:`feature` 必须是插件声明的功能 ID;`messages` 为 system/user/assistant 文本消息数组;`maxOutputTokens` 是可选的最大输出 Token;`signal` 为可选的取消信号。多轮历史由插件传入。多个 system 消息合并为系统提示,不提供工具调用或自动代理执行。
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const controller = new AbortController();
|
|
57
|
+
for await (const event of ctx.ai.stream({
|
|
58
|
+
feature: "summary",
|
|
59
|
+
messages: [{ role: "user", content: article }],
|
|
60
|
+
signal: controller.signal,
|
|
61
|
+
})) {
|
|
62
|
+
if (event.type === "text") appendText(event.delta);
|
|
63
|
+
else showUsage(event.result.usage);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
退出迭代、取消 signal、停用插件或用户撤销 AI 授权都会请求取消。不要在模块顶层调用 AI,也不要在遇到额度不足或拒绝授权后自动循环重试。
|
|
68
|
+
|
|
69
|
+
## 审计与额度
|
|
70
|
+
|
|
71
|
+
所有模型调用由宿主归属到插件及功能,进入「大语言模型 → 用量统计」,插件设置的「查看用量」会跳转并筛选本插件。记录包含时间、模型、状态、Token、估算费用和耗时,不保存提示词或回复正文。运行中的请求可取消,也可一次关闭插件的 AI 授权。
|
|
72
|
+
|
|
73
|
+
额度按本机自然日统计,所有窗口共用,并在重启后保留。每日请求次数、每日 Token 和并发限制由用户设置;并发达到上限直接拒绝,不排队。宿主按模型上下文容量加输出上限保守预占 Token,结束后按实际已知用量结算;未知用量保留预占。供应商未报告用量时不能当作零消费;费用为估算,不替代账单。取消无法撤销已经产生的消费。
|
|
74
|
+
|
|
75
|
+
错误消息以稳定代码开头:`AI_NOT_CONFIGURED`(未配置)、`AI_DISABLED`(未授权或插件已停用)、`AI_UNDECLARED_FEATURE`(未声明)、`AI_QUOTA_EXCEEDED`(每日额度不足)、`AI_CONCURRENCY_LIMIT`(并发已满)、`AI_CONFIG_CHANGED`(模型分配变化)、`AI_ABORTED`(取消)、`AI_REQUEST_FAILED`(模型或审计失败)。本地 AbortSignal 取消也可能抛出标准 AbortError。
|
package/EDITOR.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# 编辑器扩展
|
|
2
|
+
|
|
3
|
+
在文档中添加自己的块:比如交互演示、图表、特殊格式的卡片。本文还介绍源码预览块和插件 Skill。需要 Tau 0.59.0 或更高版本。
|
|
4
|
+
|
|
5
|
+
## 定义一个块
|
|
6
|
+
|
|
7
|
+
先在 `manifest.json` 中加入 `"editor": true`(完整示例见下文),然后在插件定义里声明 `editorExtensions`:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { useState } from "react";
|
|
11
|
+
import { definePlugin, defineEditorElement, defineEditorExtension } from "@tau/plugins";
|
|
12
|
+
|
|
13
|
+
interface CounterElement {
|
|
14
|
+
type: "example.counter";
|
|
15
|
+
initial: number;
|
|
16
|
+
children: { type: "formatted"; text: string }[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
declare module "@tau/plugins" {
|
|
20
|
+
interface EditorElementMap {
|
|
21
|
+
"example.counter": CounterElement;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function Counter({ initial }: { initial: number }) {
|
|
26
|
+
const [count, setCount] = useState(initial);
|
|
27
|
+
return (
|
|
28
|
+
<div contentEditable={false}>
|
|
29
|
+
<button onMouseDown={event => event.preventDefault()} onClick={() => setCount(n => n + 1)}>
|
|
30
|
+
计数:{count}
|
|
31
|
+
</button>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default definePlugin({
|
|
37
|
+
id: "example.counter",
|
|
38
|
+
name: "计数块",
|
|
39
|
+
version: "1.0.0",
|
|
40
|
+
editorExtensions: [
|
|
41
|
+
defineEditorExtension({
|
|
42
|
+
id: "example.counter",
|
|
43
|
+
elements: [
|
|
44
|
+
defineEditorElement<CounterElement>({
|
|
45
|
+
type: "example.counter",
|
|
46
|
+
kind: "block",
|
|
47
|
+
isVoid: true,
|
|
48
|
+
render: ({ attributes, element, children }) => (
|
|
49
|
+
<div {...attributes}>
|
|
50
|
+
<Counter initial={element.initial} />
|
|
51
|
+
{children}
|
|
52
|
+
</div>
|
|
53
|
+
),
|
|
54
|
+
}),
|
|
55
|
+
],
|
|
56
|
+
}),
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
几点说明:
|
|
62
|
+
|
|
63
|
+
- 节点上的字段(如 `initial`)是文档数据,会随文档保存;组件里的 `useState` 只是本次显示的状态,不会改动原文。不要把文档内容放进插件私有存储。
|
|
64
|
+
- `editorExtensions` 与插件定义一起静态导出。只提供编辑器扩展的插件可以省略 `create`;同时有命令、页面或设置时照常提供 `create/activate`。
|
|
65
|
+
- 扩展可声明 `elements`、`commands`、`hotkeys`、`menu`、`blockPanel`、`behaviorsFor`,至少包含一个节点类型。`blockPanel` 的 `onClick` 会收到当前编辑器,可用来插入节点。
|
|
66
|
+
- 节点类型和扩展 ID 使用自己的命名空间(如 `yourname.chart`),不要与内置或其他插件的类型重名。
|
|
67
|
+
|
|
68
|
+
完整的 manifest 示例:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"formatVersion": 1,
|
|
73
|
+
"id": "example.counter",
|
|
74
|
+
"name": "计数块",
|
|
75
|
+
"description": "在文档中放入交互计数块。",
|
|
76
|
+
"version": "1.0.0",
|
|
77
|
+
"minAppVersion": "0.59.0",
|
|
78
|
+
"apiVersion": 1,
|
|
79
|
+
"platform": "desktop",
|
|
80
|
+
"format": "cjs",
|
|
81
|
+
"main": "main.js",
|
|
82
|
+
"editor": true
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`styles`、`integrity` 由构建脚本生成,不用手写。
|
|
87
|
+
|
|
88
|
+
## 启用、停用与卸载
|
|
89
|
+
|
|
90
|
+
- 编辑器插件的启用、停用和升级在完整重启 Tau 后生效。
|
|
91
|
+
- 停用后,文档里已有的节点会保留,并显示为不可用占位,不会被删除;重新启用后恢复显示。
|
|
92
|
+
- 卸载正在使用的编辑器插件前,需要先停用并重启。
|
|
93
|
+
|
|
94
|
+
## 块会在哪里显示
|
|
95
|
+
|
|
96
|
+
块除了显示在桌面编辑器里,还会出现在移动端、浏览器扩展和用户发布的网页中,这些地方都直接使用你在 `elements.render` 中写的同一个组件,不需要另写入口。因此渲染代码要满足:
|
|
97
|
+
|
|
98
|
+
- 模块顶层和渲染组件能在浏览器中运行,不导入 Electron、Node 或 Tau 客户端内部路径。
|
|
99
|
+
- 不依赖 `activate` 中才赋值的变量,也不调用 `context` 的存储、页面、服务等桌面能力。
|
|
100
|
+
- React、ReactDOM、`@tau/plugins`、`@tau/ui` 由 Tau 提供,保持模板的 external 配置;其他浏览器兼容的 npm 依赖会打进 `main.js`。
|
|
101
|
+
- JS 与 CSS 自包含,图片等静态资源内联为 data URL。
|
|
102
|
+
- 样式使用插件自己的类名前缀,不覆盖 `body` 或 Tau 自身组件的样式。
|
|
103
|
+
- 只读场景下文档不可编辑,但组件自己的交互(按钮、图表缩放等)照常可用。
|
|
104
|
+
|
|
105
|
+
## Markdown
|
|
106
|
+
|
|
107
|
+
在插件定义中提供 `editorCodecs`(类型为 `EditorMarkdownCodec[]`),让自定义节点支持 Markdown 导入导出、复制和搜索摘要。序列化与反序列化需要成对设计,只写输出函数不会自动获得导入能力。
|
|
108
|
+
|
|
109
|
+
codec 会在后台转换时单独加载,不经过 `create/activate`,所以必须是纯转换函数,模块顶层不能访问 `window`、`document` 或激活后的上下文。
|
|
110
|
+
|
|
111
|
+
## 源码预览块
|
|
112
|
+
|
|
113
|
+
`SourceBlockEditor` 复用 Tau 的源码预览组件,包括编辑、全屏、错误条、删除与相邻块导航。它不是第二份渲染入口,放在同一 `elements.render` 组件中使用。
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import type { ReactNode } from "react";
|
|
117
|
+
import { SourceBlockEditor, useSourceBlock } from "@tau/plugins";
|
|
118
|
+
import { Code } from "lucide-react";
|
|
119
|
+
|
|
120
|
+
interface SourceElement {
|
|
121
|
+
type: "example.source";
|
|
122
|
+
content: string;
|
|
123
|
+
children: { type: "formatted"; text: string }[];
|
|
124
|
+
}
|
|
125
|
+
declare module "@tau/plugins" {
|
|
126
|
+
interface EditorElementMap {
|
|
127
|
+
"example.source": SourceElement;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function SourceView({ element, children }: { element: SourceElement; children: ReactNode }) {
|
|
132
|
+
const { update } = useSourceBlock(element);
|
|
133
|
+
return (
|
|
134
|
+
<SourceBlockEditor
|
|
135
|
+
element={element}
|
|
136
|
+
mode="jsx"
|
|
137
|
+
title="示例"
|
|
138
|
+
icon={Code}
|
|
139
|
+
initValue={element.content}
|
|
140
|
+
onChange={content => update({ content })}
|
|
141
|
+
>
|
|
142
|
+
{children}
|
|
143
|
+
</SourceBlockEditor>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| API | 用途 |
|
|
149
|
+
| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
150
|
+
| `SourceBlockEditor` | `element/mode/title/icon/initValue/onChange` 为必填;`center/extend/fluid` 控制预览布局,`children` 是预览内容 |
|
|
151
|
+
| `useSourceBlock(element)` | 返回当前主题 `theme`、可选样式编译器 `compiler` 与节点字段更新函数 `update`;只能在编辑器内调用 |
|
|
152
|
+
| `useReportPreviewError(message, {stale})` | 向源码预览块上报错误,传 null 清除;stale 表示是否保留了上次预览 |
|
|
153
|
+
| `insertSourceBlock(editor, element, line?)` | 插入或替换空段落并聚焦源码,返回插入路径;不能插入时返回 undefined |
|
|
154
|
+
| `sourceBlockParagraphBehavior(type)` | 放入扩展 `behaviorsFor.paragraph`,支持段首退格聚焦前一源码块 |
|
|
155
|
+
|
|
156
|
+
`theme` 提供 `isDark` 和 `token(word)`,渲染应响应主题变化。`compiler.compile({source, language})` 返回 Tailwind CSS,language 为 html 或 tsx;它只在桌面编辑器中可用,其他场景读不到编译器,需配合下文的 `styleSources` 使用。这些组件和 hook 由 Tau 提供,不要打包自己的编辑器、Slate React 或源码编辑器实例。
|
|
157
|
+
|
|
158
|
+
## 动态样式与静态导出
|
|
159
|
+
|
|
160
|
+
若节点源码中包含动态 Tailwind 类名,在同一个扩展上声明字段:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
defineEditorExtension({
|
|
164
|
+
id: "example.demo",
|
|
165
|
+
elements: [/* 当前扩展的元素定义 */],
|
|
166
|
+
styleSources: [{ type: "example.demo", field: "content", language: "tsx" }],
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
在无法运行时编译的场景(如发布的网页),Tau 会据此读取这些节点的 `content` 字段,预先生成样式。源码字段须为字符串,只能登记本插件声明的节点类型。拼接出来的类名(如 `` `bg-${color}` ``)扫描不到,类名要完整写在源码里。
|
|
171
|
+
|
|
172
|
+
可选 `htmlSerializers: [{type, toHtml(element)}]` 用于复制 HTML、HTML/EPUB 等静态导出,返回 HTML 字符串并自行转义文本。它只用于静态导出,网页显示仍使用 `elements.render`。不声明时按未知节点的默认规则导出,交互式组件在 EPUB 中不会执行。
|
|
173
|
+
|
|
174
|
+
## 插件 Skill
|
|
175
|
+
|
|
176
|
+
在 `definePlugin` 的 `skills` 数组中声明 `{id, name, description, content, invocation?}`,为 AI 提供与你的插件配套的写作规范:
|
|
177
|
+
|
|
178
|
+
| 字段 | 说明 |
|
|
179
|
+
| ------------- | ------------------------------------------------------ |
|
|
180
|
+
| `id` | 插件内唯一标识;AI 以 `插件ID:技能ID` 引用 |
|
|
181
|
+
| `name` | Skill 页面显示的名称 |
|
|
182
|
+
| `description` | AI 据此判断何时加载,写清适用场景 |
|
|
183
|
+
| `content` | 完整的 SKILL.md 正文 |
|
|
184
|
+
| `invocation` | 默认 `auto`(AI 自行加载),设为 `manual` 则仅手动点名 |
|
|
185
|
+
|
|
186
|
+
插件启用后 Skill 才可用,停用时自动移除。Skill 页面会标明它来自你的插件,用户可以调整调用方式、停用或复制到自己的 Skill 目录。目前每个 Skill 只有一份正文,不支持脚本和附件。
|
|
187
|
+
|
|
188
|
+
正文可以写在单独的 SKILL.md 中,构建时用 esbuild 的 `.md: text` loader 作为字符串导入。
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
# Tau plugins
|
|
1
|
+
# Tau 插件开发包 @tau/plugins
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
开发 Tau 插件所需的类型与接口,通过 npm 别名安装为 `@tau/plugins`。实际实现由 Tau 客户端在运行时提供,构建时需设为 external,不能在普通 Node.js 程序中直接运行。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
从模板开始开发:https://github.com/LastKnightCoder/tau-plugin-template
|
|
6
|
+
|
|
7
|
+
编译与转换服务见 [SERVICES.md](SERVICES.md),编辑器扩展、源码预览与插件 Skill 见 [EDITOR.md](EDITOR.md),需要 Tau 0.59.0 及以上。AI 功能声明、模型分配与用量审计见 [AI.md](AI.md)。
|
package/SERVICES.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# 编译与转换服务
|
|
2
|
+
|
|
3
|
+
需要在插件里编译 Tailwind、Typst、TikZ,转换 Markdown,或处理音视频时,可以使用这些服务。通过 `context.services` 调用,类型从 `@tau/plugins` 导入,需要 Tau 0.59.0 或更高版本。
|
|
4
|
+
|
|
5
|
+
所有服务都是异步方法,只处理传入数据并返回结果,不读写笔记、不选择或保存文件、不自动注入样式。
|
|
6
|
+
|
|
7
|
+
## API 一览
|
|
8
|
+
|
|
9
|
+
| 方法 | 输入 | 返回 |
|
|
10
|
+
| ---------------------------------------- | ------------------------------------------------- | ----------------------------------- |
|
|
11
|
+
| `tailwind.compile(input, options?)` | `{ source, language?: "html" \| "tsx" \| "jsx" }` | `{ css }` |
|
|
12
|
+
| `typst.compile(input, options?)` | `{ source, format?: "html", inputs?, files? }` | `{ html }` |
|
|
13
|
+
| `markdown.deserialize(source, options?)` | Markdown 字符串 | `{ nodes: Descendant[], warnings }` |
|
|
14
|
+
| `markdown.serialize(nodes, options?)` | `Descendant[]` | `{ markdown, warnings }` |
|
|
15
|
+
| `markdown.render(source, options?)` | Markdown 字符串 | `{ html, css, warnings }` |
|
|
16
|
+
| `tikz.compile(input, options?)` | `{ source }`,包含 `tikzpicture` 环境 | `{ svg }` |
|
|
17
|
+
| `ffmpeg.probe(file, options?)` | `{ name, data: Blob }` | `{ format, streams }` |
|
|
18
|
+
| `ffmpeg.run(input, options?)` | `{ files, args, outputs }` | `{ files, log }` |
|
|
19
|
+
|
|
20
|
+
## 取消与错误
|
|
21
|
+
|
|
22
|
+
第二个参数均支持 `{ signal: AbortSignal }`。插件停用会自动取消本实例的任务,不必重复传入 `context.signal`。用户主动取消可使用单次任务的 `AbortController`。
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
try {
|
|
27
|
+
const { html } = await context.services.typst.compile(
|
|
28
|
+
{ source: "= 阅读报告\n本周阅读记录" },
|
|
29
|
+
{ signal: controller.signal }
|
|
30
|
+
);
|
|
31
|
+
// 将 html 用于插件自己的预览。
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
// 对应取消按钮:controller.abort();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
失败通过 Promise 拒绝返回 `Error`,`message` 包含可展示的错误信息;不要匹配内部错误文本决定业务流程。Markdown 同步解析/序列化只能在调用前后检查取消,不能中断正在执行的 JavaScript。HTML 转换内部已开始的渲染可能继续完成,但取消后不会交回结果。
|
|
40
|
+
|
|
41
|
+
## Tailwind
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const { css } = await context.services.tailwind.compile({
|
|
45
|
+
source: '<section class="p-4 rounded-tau-md bg-surface text-ink-1">Hello</section>',
|
|
46
|
+
language: "html",
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
使用 Tailwind 4,支持 Tau 语义类。返回 theme/utilities,不重复提供 preflight。源码只用于提取完整类名,不执行 TSX/JSX,也不编译 React。动态拼接类名不能保证被提取。Tau 语义类依赖 Tau 提供的 `--tau-*` 变量,离开 Tau 的页面需要自行提供相应主题变量。
|
|
51
|
+
|
|
52
|
+
固定插件界面通常在打包时编译样式;此服务主要用于运行时生成内容。返回 CSS 由插件自行使用并在停用时清理。
|
|
53
|
+
|
|
54
|
+
## Typst
|
|
55
|
+
|
|
56
|
+
目前只输出 HTML 正文片段,不支持 PDF 和 SVG。`inputs` 对应 Typst 的 `sys.inputs`:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const { html } = await context.services.typst.compile({
|
|
60
|
+
source: '#include "parts/intro.typ"\n#sys.inputs.at("title")',
|
|
61
|
+
inputs: { title: "阅读报告" },
|
|
62
|
+
files: [{ name: "parts/intro.typ", data: new Blob(["本周阅读记录"]) }],
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
附件名是本次编译中的相对路径,不能使用绝对路径、`..` 或反斜杠。图片和包含文件等需显式通过 `files` 提供。可用字体依赖客户端运行环境;不承诺任意宏包或排版效果均可导出为 HTML。公式需要使用 Typst 相应的 HTML 写法,不能把 PDF 能力等同于 HTML 支持。
|
|
67
|
+
|
|
68
|
+
## Markdown 与编辑器节点
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import type { Descendant } from "@tau/plugins";
|
|
72
|
+
|
|
73
|
+
const parsed = await context.services.markdown.deserialize("# 标题\n\n* [x] 已完成");
|
|
74
|
+
const nodes: Descendant[] = parsed.nodes;
|
|
75
|
+
const result = await context.services.markdown.serialize(nodes);
|
|
76
|
+
const preview = await context.services.markdown.render(result.markdown, { theme: "light" });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
使用与 Tau 客户端一致的语法,包括标题、段落、列表和任务列表、表格、代码、数学公式、图片与媒体、Tau directive 等。`Descendant[]` 是数据,不关联某篇笔记,不写入编辑器。转换可能生成新的节点 UUID,不要依靠反复解析保留节点身份。
|
|
80
|
+
|
|
81
|
+
优先处理解析得到的节点。Tau 文本节点包含 `type: "formatted"`,不是只写 `{ text: "..." }`;直接构造节点时遵循 SDK 类型。
|
|
82
|
+
|
|
83
|
+
自定义节点可通过单次调用的 `codecs` 提供编解码器:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const result = await context.services.markdown.serialize(nodes, {
|
|
87
|
+
codecs: [
|
|
88
|
+
{
|
|
89
|
+
type: "example.note",
|
|
90
|
+
serializer: { toMarkdown: (_node: unknown, children: string) => `> ${children}` },
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`codecs` 类型为 `EditorMarkdownCodec[]`;同类型替换本次转换的默认 codec,调用结束不保留。编辑器贡献的 codec 不隐式加入服务,需明确传入。仅声明序列化器不会自动得到反序列化规则;需要往返时还需提供相应的 directive 或解析规则。
|
|
97
|
+
|
|
98
|
+
`warnings` 每项包含 `code`、`message` 和可选节点 `path`。当前报告未识别节点和 HTML 已知降级,不是任意语法、属性的完整无损审计。未知节点序列化可能保留为 Tau 扩展编码,其他 Markdown 工具未必理解。
|
|
99
|
+
|
|
100
|
+
HTML 渲染不自动执行 TikZ、Typst、Mermaid 编译器;相关节点可能显示源码并返回 `DISPLAY_FALLBACK`,音视频可能显示链接。原始 HTML 和远程资源按客户端导出规则保留,输出不是经过净化的 HTML。`css` 是本次生成内容附带的样式,不包含完整 Tau 页面主题,也不打包图片附件。
|
|
101
|
+
|
|
102
|
+
## TikZ
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const { svg } = await context.services.tikz.compile({
|
|
106
|
+
source: String.raw`\begin{tikzpicture}
|
|
107
|
+
\draw (0,0) -- (1,1);
|
|
108
|
+
\end{tikzpicture}`,
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
不要添加 `documentclass` 或 `document` 环境。服务负责包装,返回包含字体样式的 SVG 字符串。这不是完整 LaTeX 文档编译器,不提供任意宏包安装。语法或宏包不支持时明确报错;单次最长 120 秒,可通过 signal 提前取消。
|
|
113
|
+
|
|
114
|
+
## FFmpeg
|
|
115
|
+
|
|
116
|
+
`ServiceFile` 为 `{ name: string, data: Blob }`;文件名仅在任务内使用,不是客户端路径。输入输出名称不得重复,目录使用 `/`。文件整体经内存传输,适合可以放进内存的素材,不支持大文件流式处理。
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
const input = { name: "input.wav", data: audioBlob };
|
|
120
|
+
const info = await context.services.ffmpeg.probe(input);
|
|
121
|
+
// format: name、duration(秒或 null)、size(字节或 null)。
|
|
122
|
+
// streams: index、type、codec,可选 width/height/sampleRate/channels。
|
|
123
|
+
|
|
124
|
+
const result = await context.services.ffmpeg.run(
|
|
125
|
+
{
|
|
126
|
+
files: [input],
|
|
127
|
+
args: ["-i", "input.wav", "-c:a", "pcm_s16le", "output.wav"],
|
|
128
|
+
outputs: [{ name: "output.wav", mimeType: "audio/wav" }],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
onProgress: ({ time }) => console.log("已处理秒数", time),
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
const outputBlob = result.files[0].data;
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`args` 不包含程序名;含空格的参数作为一个数组元素,不额外加 shell 引号。服务设置非交互、覆盖临时输出和进度通道,不要覆盖 `-progress` 或依赖交互 stdin。`outputs` 声明要取回的产物,名称与参数中的输出一致;文件未生成会报错,退出非零不会返回部分成功结果。
|
|
138
|
+
|
|
139
|
+
`onProgress.time` 是已处理媒体时间(秒),不是百分比。首次使用 FFmpeg 时 Tau 可能需要联网下载,之后会复用;取消任务不会删除已下载的 FFmpeg。`log` 保留最后 64 KiB 诊断输出。返回 MIME 类型由 `outputs.mimeType` 指定,调用者须确保与实际格式一致。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiongtao.xt/tau-plugins",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"registry": "https://registry.npmjs.org"
|
|
12
12
|
},
|
|
13
13
|
"type": "module",
|
|
14
|
-
"description": "Tau
|
|
14
|
+
"description": "Tau 插件作者类型契约;运行时由宿主提供",
|
|
15
15
|
"exports": {
|
|
16
16
|
".": {
|
|
17
17
|
"types": "./types/packages/plugins/src/index.d.ts",
|
package/runtime.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
throw new Error("Tau
|
|
1
|
+
throw new Error("Tau 插件模块由宿主提供,请在插件构建中设为 external");
|
|
@@ -25,6 +25,8 @@ export interface EditorRef {
|
|
|
25
25
|
deselect: () => void;
|
|
26
26
|
/** Get the underlying Slate editor instance */
|
|
27
27
|
getEditor: () => SlateEditor;
|
|
28
|
+
/** 获取可编辑区根节点([data-slate-editor]),未挂载时返回 null;宿主测量尺寸用,不要依赖内部 DOM 层级 */
|
|
29
|
+
getEditableElement: () => HTMLElement | null;
|
|
28
30
|
/** Check if the editor is currently focused */
|
|
29
31
|
isFocus: () => boolean;
|
|
30
32
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface PluginAIFeature {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface PluginAIRequest {
|
|
7
|
+
feature: string;
|
|
8
|
+
messages: readonly {
|
|
9
|
+
role: "system" | "user" | "assistant";
|
|
10
|
+
content: string;
|
|
11
|
+
}[];
|
|
12
|
+
maxOutputTokens?: number;
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
}
|
|
15
|
+
export interface PluginAIResult {
|
|
16
|
+
text: string;
|
|
17
|
+
/** 供应商未报告用量时为 null。 */
|
|
18
|
+
usage: {
|
|
19
|
+
inputTokens: number;
|
|
20
|
+
outputTokens: number;
|
|
21
|
+
totalTokens: number;
|
|
22
|
+
} | null;
|
|
23
|
+
}
|
|
24
|
+
export type PluginAIEvent = {
|
|
25
|
+
type: "text";
|
|
26
|
+
delta: string;
|
|
27
|
+
} | {
|
|
28
|
+
type: "done";
|
|
29
|
+
result: PluginAIResult;
|
|
30
|
+
};
|
|
31
|
+
export interface PluginAI {
|
|
32
|
+
getStatus(feature: string): Promise<{
|
|
33
|
+
configured: boolean;
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
}>;
|
|
36
|
+
openSettings(feature?: string): void;
|
|
37
|
+
chat(request: PluginAIRequest): Promise<PluginAIResult>;
|
|
38
|
+
stream(request: PluginAIRequest): AsyncIterable<PluginAIEvent>;
|
|
39
|
+
}
|
|
40
|
+
export declare const unavailableAI: PluginAI;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { PluginPages } from "./pages";
|
|
2
|
+
import type { PluginSkill } from "./skills";
|
|
3
|
+
import type { PluginAI, PluginAIFeature } from "./ai";
|
|
4
|
+
import type { PluginServices } from "./services";
|
|
2
5
|
import type { PluginEditor } from "./editor";
|
|
6
|
+
import type { PluginEditorExtension } from "./editor";
|
|
7
|
+
import type { MarkdownCodec } from "../../markdown/src/index";
|
|
3
8
|
import type { PluginSettings } from "./settings";
|
|
4
9
|
import type { PluginSidebar } from "./sidebar";
|
|
5
10
|
import type { PluginExcerpts, PluginMaterials } from "./excerpts";
|
|
@@ -35,6 +40,8 @@ export interface PluginContext {
|
|
|
35
40
|
readonly excerpts: PluginExcerpts;
|
|
36
41
|
readonly materials: PluginMaterials;
|
|
37
42
|
readonly fetch: typeof globalThis.fetch;
|
|
43
|
+
readonly services: PluginServices;
|
|
44
|
+
readonly ai: PluginAI;
|
|
38
45
|
/** 将订阅等资源交给宿主,在停用或激活失败时逆序回收。 */
|
|
39
46
|
register(resource: Disposable): Disposable;
|
|
40
47
|
}
|
|
@@ -47,6 +54,13 @@ export interface PluginDefinition {
|
|
|
47
54
|
readonly name: string;
|
|
48
55
|
readonly version: string;
|
|
49
56
|
readonly apiVersion: 1;
|
|
57
|
+
/** 静态声明,桌面与博客均装配同一份扩展;模块加载不得依赖桌面激活。 */
|
|
58
|
+
readonly editorExtensions?: readonly PluginEditorExtension[];
|
|
59
|
+
readonly editorCodecs?: readonly MarkdownCodec[];
|
|
60
|
+
readonly skills?: readonly PluginSkill[];
|
|
61
|
+
readonly ai?: {
|
|
62
|
+
readonly features: readonly PluginAIFeature[];
|
|
63
|
+
};
|
|
50
64
|
/** 每次启用创建新实例,运行状态不放在定义对象上。 */
|
|
51
65
|
create(): PluginInstance;
|
|
52
66
|
}
|
|
@@ -21,8 +21,19 @@ declare module "../../editor/api" {
|
|
|
21
21
|
[inlineMapBrand]: never;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
export type PluginEditorExtension = Pick<InternalExtension, "id" | "apiVersion" | "elements" | "commands" | "hotkeys" | "menu"> & {
|
|
24
|
+
export type PluginEditorExtension = Pick<InternalExtension, "id" | "apiVersion" | "elements" | "commands" | "hotkeys" | "menu" | "blockPanel" | "behaviorsFor"> & {
|
|
25
25
|
plugins?: never;
|
|
26
|
+
/** 发布时从节点字段收集 Tailwind 源码,复用宿主编译器。 */
|
|
27
|
+
styleSources?: readonly {
|
|
28
|
+
type: string;
|
|
29
|
+
field: string;
|
|
30
|
+
language: "html" | "tsx";
|
|
31
|
+
}[];
|
|
32
|
+
/** 静态 HTML/EPUB 导出;博客始终使用 elements.render,不经过这里。 */
|
|
33
|
+
htmlSerializers?: readonly {
|
|
34
|
+
type: string;
|
|
35
|
+
toHtml(element: Record<string, unknown>): string;
|
|
36
|
+
}[];
|
|
26
37
|
};
|
|
27
38
|
export declare function defineEditorExtension(definition: Omit<PluginEditorExtension, "apiVersion">): PluginEditorExtension;
|
|
28
39
|
export interface PluginEditorContribution {
|
|
@@ -2,7 +2,12 @@ import type { PluginDefinition } from "./contract";
|
|
|
2
2
|
export type { JsonValue, Disposable, PluginStorage, PluginCommand, PluginContext, PluginInstance, PluginDefinition, } from "./contract";
|
|
3
3
|
export type { PluginSettings, PluginSettingsDefinition, PluginSettingsProps, PluginSettingsHandle, } from "./settings";
|
|
4
4
|
export * from "./editor";
|
|
5
|
+
export { SourceBlockEditor, useSourceBlock, useReportPreviewError, insertSourceBlock, sourceBlockParagraphBehavior, } from "./source-editor";
|
|
6
|
+
export type { SourceBlockEditorProps, PluginRuntimeStyleCompiler } from "./source-editor";
|
|
5
7
|
export * from "./sidebar";
|
|
6
8
|
export type * from "./excerpts";
|
|
9
|
+
export type * from "./services";
|
|
10
|
+
export type { PluginSkill } from "./skills";
|
|
7
11
|
export type { PluginPageDefinition, PluginPageHandle, PluginPageParams, PluginPageProps, PluginPages, } from "./pages";
|
|
8
|
-
export declare function definePlugin(definition: Omit<PluginDefinition, "apiVersion">): PluginDefinition;
|
|
12
|
+
export declare function definePlugin(definition: Omit<PluginDefinition, "apiVersion" | "create"> & Partial<Pick<PluginDefinition, "create">>): PluginDefinition;
|
|
13
|
+
export type { PluginAI, PluginAIFeature, PluginAIRequest, PluginAIResult, PluginAIEvent, } from "./ai";
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Descendant } from "slate";
|
|
2
|
+
import type { MarkdownCodec } from "../../markdown/src/index";
|
|
3
|
+
export type { Descendant } from "slate";
|
|
4
|
+
export interface ServiceOptions {
|
|
5
|
+
signal?: AbortSignal;
|
|
6
|
+
}
|
|
7
|
+
export interface ServiceWarning {
|
|
8
|
+
code: string;
|
|
9
|
+
message: string;
|
|
10
|
+
path?: number[];
|
|
11
|
+
}
|
|
12
|
+
export interface ServiceFile {
|
|
13
|
+
/** 任务内的相对文件名,不是宿主文件路径。 */
|
|
14
|
+
name: string;
|
|
15
|
+
data: Blob;
|
|
16
|
+
}
|
|
17
|
+
export interface MarkdownServiceOptions extends ServiceOptions {
|
|
18
|
+
/** 仅参与本次转换,不修改全局编辑器。 */
|
|
19
|
+
codecs?: readonly MarkdownCodec[];
|
|
20
|
+
}
|
|
21
|
+
export interface MediaProgress {
|
|
22
|
+
/** 已处理的媒体时间,单位秒;不推测总时长或百分比。 */
|
|
23
|
+
time: number;
|
|
24
|
+
}
|
|
25
|
+
export interface MediaInfo {
|
|
26
|
+
format: {
|
|
27
|
+
name: string;
|
|
28
|
+
duration: number | null;
|
|
29
|
+
size: number | null;
|
|
30
|
+
};
|
|
31
|
+
streams: {
|
|
32
|
+
index: number;
|
|
33
|
+
type: string;
|
|
34
|
+
codec: string;
|
|
35
|
+
width?: number;
|
|
36
|
+
height?: number;
|
|
37
|
+
sampleRate?: number;
|
|
38
|
+
channels?: number;
|
|
39
|
+
}[];
|
|
40
|
+
}
|
|
41
|
+
export interface PluginServices {
|
|
42
|
+
tailwind: {
|
|
43
|
+
compile(input: {
|
|
44
|
+
source: string;
|
|
45
|
+
language?: "html" | "tsx" | "jsx";
|
|
46
|
+
}, options?: ServiceOptions): Promise<{
|
|
47
|
+
css: string;
|
|
48
|
+
}>;
|
|
49
|
+
};
|
|
50
|
+
typst: {
|
|
51
|
+
compile(input: {
|
|
52
|
+
source: string;
|
|
53
|
+
format?: "html";
|
|
54
|
+
inputs?: Record<string, string>;
|
|
55
|
+
files?: ServiceFile[];
|
|
56
|
+
}, options?: ServiceOptions): Promise<{
|
|
57
|
+
html: string;
|
|
58
|
+
}>;
|
|
59
|
+
};
|
|
60
|
+
markdown: {
|
|
61
|
+
deserialize(source: string, options?: MarkdownServiceOptions): Promise<{
|
|
62
|
+
nodes: Descendant[];
|
|
63
|
+
warnings: ServiceWarning[];
|
|
64
|
+
}>;
|
|
65
|
+
serialize(nodes: Descendant[], options?: MarkdownServiceOptions): Promise<{
|
|
66
|
+
markdown: string;
|
|
67
|
+
warnings: ServiceWarning[];
|
|
68
|
+
}>;
|
|
69
|
+
render(source: string, options?: MarkdownServiceOptions & {
|
|
70
|
+
theme?: "light" | "dark";
|
|
71
|
+
}): Promise<{
|
|
72
|
+
html: string;
|
|
73
|
+
css: string;
|
|
74
|
+
warnings: ServiceWarning[];
|
|
75
|
+
}>;
|
|
76
|
+
};
|
|
77
|
+
tikz: {
|
|
78
|
+
compile(input: {
|
|
79
|
+
source: string;
|
|
80
|
+
}, options?: ServiceOptions): Promise<{
|
|
81
|
+
svg: string;
|
|
82
|
+
}>;
|
|
83
|
+
};
|
|
84
|
+
ffmpeg: {
|
|
85
|
+
probe(input: ServiceFile, options?: ServiceOptions): Promise<MediaInfo>;
|
|
86
|
+
/** args 中用任务文件名引用输入和输出;不接受 shell 命令。 */
|
|
87
|
+
run(input: {
|
|
88
|
+
files: ServiceFile[];
|
|
89
|
+
args: string[];
|
|
90
|
+
outputs: {
|
|
91
|
+
name: string;
|
|
92
|
+
mimeType: string;
|
|
93
|
+
}[];
|
|
94
|
+
}, options?: ServiceOptions & {
|
|
95
|
+
onProgress?: (progress: MediaProgress) => void;
|
|
96
|
+
}): Promise<{
|
|
97
|
+
files: ServiceFile[];
|
|
98
|
+
log: string;
|
|
99
|
+
}>;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** 每个调用同时受调用方与插件实例生命周期约束。 */
|
|
103
|
+
export declare function bindPluginServices(adapter: PluginServices | undefined, lifetime: AbortSignal, assertActive: () => void): PluginServices;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ReactNode, ReactElement } from "react";
|
|
2
|
+
import type { LucideIcon } from "lucide-react";
|
|
3
|
+
import type { Editor, Element, Path } from "slate";
|
|
4
|
+
import type { ElementBehaviors, ParagraphElement, BlockElement } from "../../editor/api";
|
|
5
|
+
export interface SourceBlockEditorProps {
|
|
6
|
+
mode: string;
|
|
7
|
+
title: string;
|
|
8
|
+
icon: LucideIcon;
|
|
9
|
+
initValue: string;
|
|
10
|
+
onChange(value: string): void;
|
|
11
|
+
element: Element;
|
|
12
|
+
center?: boolean;
|
|
13
|
+
extend?: boolean;
|
|
14
|
+
fluid?: boolean;
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
export interface PluginRuntimeStyleCompiler {
|
|
18
|
+
compile(request: {
|
|
19
|
+
source: string;
|
|
20
|
+
language: "html" | "tsx";
|
|
21
|
+
}): Promise<string>;
|
|
22
|
+
}
|
|
23
|
+
export interface SourceEditorRuntime {
|
|
24
|
+
SourceBlockEditor(props: SourceBlockEditorProps): ReactElement;
|
|
25
|
+
useSourceBlock(element: Element): {
|
|
26
|
+
theme: {
|
|
27
|
+
isDark: boolean;
|
|
28
|
+
token(word: string): string;
|
|
29
|
+
};
|
|
30
|
+
compiler: PluginRuntimeStyleCompiler | undefined;
|
|
31
|
+
update(properties: Record<string, unknown>): void;
|
|
32
|
+
};
|
|
33
|
+
useReportPreviewError(error: string | null, options?: {
|
|
34
|
+
stale?: boolean;
|
|
35
|
+
}): boolean;
|
|
36
|
+
insertSourceBlock(editor: Editor, element: BlockElement, line?: number): Path | undefined;
|
|
37
|
+
sourceBlockParagraphBehavior(type: string): ElementBehaviors<ParagraphElement>;
|
|
38
|
+
}
|
|
39
|
+
export declare function configureSourceEditor(value: SourceEditorRuntime): void;
|
|
40
|
+
export declare const SourceBlockEditor: SourceEditorRuntime["SourceBlockEditor"];
|
|
41
|
+
export declare const useSourceBlock: SourceEditorRuntime["useSourceBlock"];
|
|
42
|
+
export declare const useReportPreviewError: SourceEditorRuntime["useReportPreviewError"];
|
|
43
|
+
export declare const insertSourceBlock: SourceEditorRuntime["insertSourceBlock"];
|
|
44
|
+
export declare const sourceBlockParagraphBehavior: SourceEditorRuntime["sourceBlockParagraphBehavior"];
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
*
|
|
31
31
|
* 尺寸档是预设不是牢笼(D-011):sm 380 / md 440(默认)/ lg 560,width 任意值覆盖;
|
|
32
32
|
* 高度默认随内容(上限 78vh),height 定高(仍不超 82vh)。
|
|
33
|
-
* 层级:遮罩容器 z-[340](
|
|
33
|
+
* 层级:遮罩容器 z-[340](Select/Menu 250 < Tooltip 300 之上,Toast 360 之下——弹窗里的操作反馈须可见)。
|
|
34
34
|
*/
|
|
35
35
|
import { type KeyboardEvent as ReactKeyboardEvent, type ReactNode, type Ref } from "react";
|
|
36
36
|
export interface DialogProps {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* D-003 全局注册表(那是命令面板等壳层浮层的仲裁,ui 基础件不反向依赖 page/)
|
|
20
20
|
*
|
|
21
21
|
* 表单控件家族口径:触发器与弹层内搜索框 = surface 亮底 + line-1 描边 + radius-sm——「浮起物」语感,白卡上同底靠描边显形。
|
|
22
|
-
* 层级 z-[250](命令面板 101 <
|
|
22
|
+
* 层级 z-[250](命令面板 101 < Select/Menu 250 < Tooltip 300 < Dialog 340 < Toast 360)。
|
|
23
23
|
*
|
|
24
24
|
* 明确不做(M0):多选/标签模式(M1 value: V[])· 异步 loading prop(换数组即异步,
|
|
25
25
|
* 过渡态用 emptyText)· 虚拟化(M1)· renderOption 逃生舱(label: ReactNode 已覆盖
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* 可选动作;无关闭钮——整条点击关闭(Sonner 惯例),悬停冻结自动关闭,
|
|
6
6
|
* 同一位置上限 4 条(挤掉最老)。
|
|
7
7
|
* 位置四选一:top-center(默认)/ top-right / bottom-right / bottom-center,
|
|
8
|
-
* 全局默认可经 toast.config 调整、单条可覆写;层级 z-[
|
|
9
|
-
* (z-[101]
|
|
8
|
+
* 全局默认可经 toast.config 调整、单条可覆写;层级 z-[360],高于命令面板
|
|
9
|
+
* (z-[101])与 Dialog / Drawer(z-[340]),面板或弹窗打开时操作反馈仍可见。不接 Esc(浮层退场注册表
|
|
10
10
|
* 只登记阻断后退的浮层,见 D-003)。
|
|
11
11
|
*
|
|
12
12
|
* 用法:app 壳层挂载 <ToastHost /> 一次;任意处调用 toast.success("已保存")、
|
|
@@ -23,6 +23,8 @@ export interface ToastOptions {
|
|
|
23
23
|
position?: ToastPosition;
|
|
24
24
|
/** 落定后的停留毫秒数;0 = 落定后也常驻(只靠点击关闭)。载入中恒常驻,与本值无关 */
|
|
25
25
|
duration?: number;
|
|
26
|
+
/** 关闭时回调(自动到时 / 点击整条 / 动作点击 / 同位置超限被挤出 / Host 卸载);动作 onClick 先于它触发 */
|
|
27
|
+
onClose?: () => void;
|
|
26
28
|
}
|
|
27
29
|
/**
|
|
28
30
|
* 单条胶囊(受 item 驱动的纯展示件)。导出供样板间(src/showcase)构造
|
|
@@ -35,6 +37,7 @@ export interface ToastItem {
|
|
|
35
37
|
action?: ToastOptions["action"];
|
|
36
38
|
position: ToastPosition;
|
|
37
39
|
duration: number;
|
|
40
|
+
onClose?: ToastOptions["onClose"];
|
|
38
41
|
}
|
|
39
42
|
declare const defaults: {
|
|
40
43
|
position: ToastPosition;
|