@xiongtao.xt/tau-plugins 0.3.0 → 0.6.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/EDITOR.md +189 -0
- package/README.md +3 -1
- 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/contract.d.ts +12 -0
- package/types/packages/plugins/src/editor.d.ts +12 -1
- package/types/packages/plugins/src/excerpts.d.ts +91 -0
- 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/EDITOR.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# 编辑器扩展与博客发布
|
|
2
|
+
|
|
3
|
+
本模板已锁定 SDK 0.6.0,需要 Tau 0.59.0 及支持编辑器插件的博客模板。基础计数页面示例没有使用本章接口;编写编辑器插件时,在 manifest 中加入 `"editor": true`。
|
|
4
|
+
|
|
5
|
+
## 作者只定义一份扩展
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { useState } from "react";
|
|
9
|
+
import { definePlugin, defineEditorElement, defineEditorExtension } from "@tau/plugins";
|
|
10
|
+
|
|
11
|
+
interface CounterElement {
|
|
12
|
+
type: "example.counter";
|
|
13
|
+
initial: number;
|
|
14
|
+
children: { type: "formatted"; text: string }[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
declare module "@tau/plugins" {
|
|
18
|
+
interface EditorElementMap {
|
|
19
|
+
"example.counter": CounterElement;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function Counter({ initial }: { initial: number }) {
|
|
24
|
+
const [count, setCount] = useState(initial);
|
|
25
|
+
return (
|
|
26
|
+
<div contentEditable={false}>
|
|
27
|
+
<button onMouseDown={event => event.preventDefault()} onClick={() => setCount(n => n + 1)}>
|
|
28
|
+
计数:{count}
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default definePlugin({
|
|
35
|
+
id: "example.counter",
|
|
36
|
+
name: "计数块",
|
|
37
|
+
version: "1.0.0",
|
|
38
|
+
editorExtensions: [
|
|
39
|
+
defineEditorExtension({
|
|
40
|
+
id: "example.counter",
|
|
41
|
+
elements: [
|
|
42
|
+
defineEditorElement<CounterElement>({
|
|
43
|
+
type: "example.counter",
|
|
44
|
+
kind: "block",
|
|
45
|
+
isVoid: true,
|
|
46
|
+
render: ({ attributes, element, children }) => (
|
|
47
|
+
<div {...attributes}>
|
|
48
|
+
<Counter initial={element.initial} />
|
|
49
|
+
{children}
|
|
50
|
+
</div>
|
|
51
|
+
),
|
|
52
|
+
}),
|
|
53
|
+
],
|
|
54
|
+
}),
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
节点数据中的 `initial` 是文档数据,示例中的 `useState` 计数仅属于本次展示,不会修改原文。编辑节点时使用编辑器扩展的操作协议;不要通过插件私有存储替代文档正文。
|
|
60
|
+
|
|
61
|
+
`editorExtensions` 是静态贡献,与插件定义一起导出。纯编辑器插件可以省略 `create`;有命令、页面或设置时仍可提供 `create/activate`。博客只读取静态扩展,不调用这些桌面生命周期方法。
|
|
62
|
+
|
|
63
|
+
扩展可声明 `elements`、`commands`、`hotkeys`、`menu`、`blockPanel`、`behaviorsFor`。节点类型和扩展 ID 应使用自己的命名空间,不覆盖内置或其他插件的类型。块面板的 `onClick` 会收到当前编辑器,可使用其节点插入方法。当前不开放原始 Slate 包装器。
|
|
64
|
+
|
|
65
|
+
本版针对带内容节点的插件,至少需要声明一个节点类型;纯快捷键或工具栏增强不属于本轮发布协议。
|
|
66
|
+
|
|
67
|
+
`context.editor.register({ scene: "workbench", ... })` 仍只用于开发台实验,不参与正式正文或博客发布;正式扩展使用 `editorExtensions`。
|
|
68
|
+
|
|
69
|
+
## 清单与启停
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"formatVersion": 1,
|
|
74
|
+
"id": "example.counter",
|
|
75
|
+
"name": "计数块",
|
|
76
|
+
"description": "在文档中放入交互计数块。",
|
|
77
|
+
"version": "1.0.0",
|
|
78
|
+
"minAppVersion": "0.59.0",
|
|
79
|
+
"apiVersion": 1,
|
|
80
|
+
"platform": "desktop",
|
|
81
|
+
"format": "cjs",
|
|
82
|
+
"main": "main.js",
|
|
83
|
+
"editor": true
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
完整性摘要和样式入口由模板构建脚本生成。无需额外维护 `render.js` 或在清单里重复列出节点类型;Tau 从这份扩展定义取得节点归属。
|
|
88
|
+
|
|
89
|
+
编辑器插件的启用、停用及升级在完整重启后生效。所有窗口使用同一次启动的贡献快照,避免替换正在编辑的文档、撤销历史和协作实例。停用后旧节点由编辑器保留并显示不可用占位,不自动删除。当前窗口正在使用的编辑器插件需要先停用并重启,再卸载。
|
|
90
|
+
|
|
91
|
+
编辑器贡献不依赖 `activate` 的完成结果。共享渲染组件不得读取 `activate` 才赋值的上下文;否则博客和辅助编辑窗口无法独立展示它。
|
|
92
|
+
|
|
93
|
+
## Markdown
|
|
94
|
+
|
|
95
|
+
可以在同一个定义中提供 `editorCodecs`,类型为 `EditorMarkdownCodec[]`,供桌面渲染端与博客的 Markdown 处理器装配。反序列化与序列化规则需要成对设计;声明一个输出函数不会自动获得导入能力。
|
|
96
|
+
|
|
97
|
+
已启用插件的 codec 会装配到桌面、主进程索引、转换线程与博客。后台读取同一个 main.js 中的定义,不调用 render/create/activate;codec 必须为纯转换函数,模块顶层不能依赖 window/document 或激活后的上下文。无需再提供 codec 文件或渲染入口。发布正文仍保留原始节点 JSON,不先转成 Markdown;发布搜索摘要等转换使用 codec。
|
|
98
|
+
|
|
99
|
+
## 发布时会发生什么
|
|
100
|
+
|
|
101
|
+
1. Tau 取得当前窗口启动时使用的插件版本、节点归属及 JS/CSS 摘要。
|
|
102
|
+
2. 发布器重新读取本地包;版本或摘要变化时要求重启,不悄悄换用新代码。
|
|
103
|
+
3. 从发布内容收集实际需要的插件,将同一个 main.js 和样式复制到站点的带版本与摘要的路径,向页面注入依赖清单。
|
|
104
|
+
4. 预编译博客模板先加载、验证这些文件,再创建 EditorKit 并显示原始节点。发布阶段不重编译模板。
|
|
105
|
+
|
|
106
|
+
没有对应扩展、包已变化或模板不支持协议时明确报错。已经发布的网站使用它携带的版本,不依赖读者安装 Tau,也不在访问时向插件中心请求最新版。
|
|
107
|
+
|
|
108
|
+
## 浏览器运行约束
|
|
109
|
+
|
|
110
|
+
- 模块顶层和内容渲染必须能在浏览器运行,不可直接导入 Electron、Node 或客户端内部路径。
|
|
111
|
+
- 使用模板已有的 external 配置,React、ReactDOM、@tau/plugins、@tau/ui 由宿主共享。其他浏览器兼容 npm 依赖随 main.js 打包。
|
|
112
|
+
- JS/CSS 需自包含;图片等静态资源可内联为 data URL。首期没有插件任意附件目录的发布协议。
|
|
113
|
+
- 样式使用插件自己的类名,避免覆盖 body 或宿主组件的全局样式;不同页面只加载实际需要的插件。
|
|
114
|
+
- 博客不提供桌面文件系统、私有存储、主进程编译服务和其他 PluginContext 能力。扩展不能通过渲染触发这些桌面调用。
|
|
115
|
+
- 只读指文档不可编辑,不禁止组件自己的交互;图表、按钮等可维护自己的展示状态。
|
|
116
|
+
- 当前 CJS 加载方式需要页面允许对应脚本求值。不要自行在站点配置中禁止它后假定插件仍可执行。
|
|
117
|
+
|
|
118
|
+
跨端共用渲染组件和同一插件版本,但完整视觉一致性仍需要在相同主题、字体和尺寸条件下验收。移动端和浏览器扩展已支持由通用分发加载器装配编辑器插件包,目前没有新增下载管理界面。分发构建直接包装同一个 main.js,不要求作者维护另一个入口。
|
|
119
|
+
|
|
120
|
+
## 源码预览块
|
|
121
|
+
|
|
122
|
+
`SourceBlockEditor` 复用 Tau 的源码预览组件,包括编辑、全屏、错误条、删除与相邻块导航。它不是第二份渲染入口,放在同一 `elements.render` 组件中使用。
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import type { ReactNode } from "react";
|
|
126
|
+
import { SourceBlockEditor, useSourceBlock } from "@tau/plugins";
|
|
127
|
+
import { Code } from "lucide-react";
|
|
128
|
+
|
|
129
|
+
interface SourceElement {
|
|
130
|
+
type: "example.source";
|
|
131
|
+
content: string;
|
|
132
|
+
children: { type: "formatted"; text: string }[];
|
|
133
|
+
}
|
|
134
|
+
declare module "@tau/plugins" {
|
|
135
|
+
interface EditorElementMap {
|
|
136
|
+
"example.source": SourceElement;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function SourceView({ element, children }: { element: SourceElement; children: ReactNode }) {
|
|
141
|
+
const { update } = useSourceBlock(element);
|
|
142
|
+
return (
|
|
143
|
+
<SourceBlockEditor
|
|
144
|
+
element={element}
|
|
145
|
+
mode="jsx"
|
|
146
|
+
title="示例"
|
|
147
|
+
icon={Code}
|
|
148
|
+
initValue={element.content}
|
|
149
|
+
onChange={content => update({ content })}
|
|
150
|
+
>
|
|
151
|
+
{children}
|
|
152
|
+
</SourceBlockEditor>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
| API | 用途 |
|
|
158
|
+
| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
159
|
+
| `SourceBlockEditor` | `element/mode/title/icon/initValue/onChange` 为必填;`center/extend/fluid` 控制预览布局,`children` 是预览内容 |
|
|
160
|
+
| `useSourceBlock(element)` | 返回当前主题 `theme`、可选样式编译器 `compiler` 与节点字段更新函数 `update`;只能在编辑器内调用 |
|
|
161
|
+
| `useReportPreviewError(message, {stale})` | 向源码预览块上报错误,传 null 清除;stale 表示是否保留了上次预览 |
|
|
162
|
+
| `insertSourceBlock(editor, element, line?)` | 插入或替换空段落并聚焦源码,返回插入路径;不能插入时返回 undefined |
|
|
163
|
+
| `sourceBlockParagraphBehavior(type)` | 放入扩展 `behaviorsFor.paragraph`,支持段首退格聚焦前一源码块 |
|
|
164
|
+
|
|
165
|
+
`theme` 提供 `isDark` 和 `token(word)`;渲染应响应主题变化。桌面 `compiler.compile({source, language})` 返回 Tailwind CSS,language 为 html 或 tsx;博客不提供编译器,依赖发布阶段生成的 CSS。这些组件和 hook 由宿主提供,不要打包自己的编辑器、Slate React 或源码编辑器实例。
|
|
166
|
+
|
|
167
|
+
## 动态样式与静态导出
|
|
168
|
+
|
|
169
|
+
若节点源码中包含动态 Tailwind 类名,在同一个扩展上声明字段:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
defineEditorExtension({
|
|
173
|
+
id: "example.demo",
|
|
174
|
+
elements: [/* 当前扩展的元素定义 */],
|
|
175
|
+
styleSources: [{ type: "example.demo", field: "content", language: "tsx" }],
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
发布器读取节点的 content 字段并编译 CSS;源码字段应为字符串。只有本插件声明的节点类型可登记,不需要另写发布回调。动态拼接类名仍无法被 Tailwind 静态扫描。
|
|
180
|
+
|
|
181
|
+
可选 `htmlSerializers: [{type, toHtml(element)}]` 用于复制 HTML、HTML/EPUB 等静态导出,返回 HTML 字符串并自行转义文本。它不参与博客渲染;博客始终使用原来的 `elements.render`。不声明时遵循宿主未知节点导出规则,不承诺交互式组件可在 EPUB 中执行。
|
|
182
|
+
|
|
183
|
+
## 插件 Skill
|
|
184
|
+
|
|
185
|
+
在 `definePlugin` 的 `skills` 数组声明 `{id, name, description, content, invocation?}`。id 是插件内唯一标识,name 是显示名,description 用于 AI 判断何时加载,content 是完整 SKILL.md 文本;invocation 默认 auto,也可设 manual。
|
|
186
|
+
|
|
187
|
+
技能在插件启用成功后可用,停用或激活失败时回收。技能页显示「插件」来源,允许用户调整调用模式、停用或复制到自己的技能目录。AI 使用 `插件ID:技能ID` 寻址,可读取 SKILL.md;首版只提供单文档,不声明脚本和附件能力。
|
|
188
|
+
|
|
189
|
+
技能正文可以写在单独的 SKILL.md 中,构建时通过 esbuild 的 `.md: text` loader 导入到同一个 main.js。博客不会激活 Skill,不需要额外下载技能文件。
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Tau plugins 作者契约
|
|
2
2
|
|
|
3
|
-
通过 npm 别名安装为 `@tau/plugins`。运行时由 Tau
|
|
3
|
+
通过 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 及以上。
|
package/SERVICES.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# 编译与转换服务
|
|
2
|
+
|
|
3
|
+
本文对应本模板锁定的 SDK 0.6.0,需要 Tau 0.59.0 或更高版本。编译与转换通过 `context.services` 调用,类型从 `@tau/plugins` 导入。基础计数示例不依赖这些服务,可按实际需求使用。
|
|
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 的页面需要自行提供相应主题变量。
|
|
51
|
+
|
|
52
|
+
固定插件界面通常在打包时编译样式;此服务主要用于运行时生成内容。返回 CSS 由插件自行使用并在停用时清理。
|
|
53
|
+
|
|
54
|
+
## Typst
|
|
55
|
+
|
|
56
|
+
首期仅支持 HTML,返回 HTML 正文片段。PDF/SVG 不属于本版本 API。`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 首次使用可能需要客户端准备或下载二进制文件,需要网络;已安装版本可复用。取消不删除共享编译器缓存。`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.6.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
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { PluginPages } from "./pages";
|
|
2
|
+
import type { PluginSkill } from "./skills";
|
|
3
|
+
import type { PluginServices } from "./services";
|
|
2
4
|
import type { PluginEditor } from "./editor";
|
|
5
|
+
import type { PluginEditorExtension } from "./editor";
|
|
6
|
+
import type { MarkdownCodec } from "../../markdown/src/index";
|
|
3
7
|
import type { PluginSettings } from "./settings";
|
|
4
8
|
import type { PluginSidebar } from "./sidebar";
|
|
9
|
+
import type { PluginExcerpts, PluginMaterials } from "./excerpts";
|
|
5
10
|
export type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
6
11
|
[key: string]: JsonValue;
|
|
7
12
|
};
|
|
@@ -31,7 +36,10 @@ export interface PluginContext {
|
|
|
31
36
|
readonly editor: PluginEditor;
|
|
32
37
|
readonly settings: PluginSettings;
|
|
33
38
|
readonly sidebar: PluginSidebar;
|
|
39
|
+
readonly excerpts: PluginExcerpts;
|
|
40
|
+
readonly materials: PluginMaterials;
|
|
34
41
|
readonly fetch: typeof globalThis.fetch;
|
|
42
|
+
readonly services: PluginServices;
|
|
35
43
|
/** 将订阅等资源交给宿主,在停用或激活失败时逆序回收。 */
|
|
36
44
|
register(resource: Disposable): Disposable;
|
|
37
45
|
}
|
|
@@ -44,6 +52,10 @@ export interface PluginDefinition {
|
|
|
44
52
|
readonly name: string;
|
|
45
53
|
readonly version: string;
|
|
46
54
|
readonly apiVersion: 1;
|
|
55
|
+
/** 静态声明,桌面与博客均装配同一份扩展;模块加载不得依赖桌面激活。 */
|
|
56
|
+
readonly editorExtensions?: readonly PluginEditorExtension[];
|
|
57
|
+
readonly editorCodecs?: readonly MarkdownCodec[];
|
|
58
|
+
readonly skills?: readonly PluginSkill[];
|
|
47
59
|
/** 每次启用创建新实例,运行状态不放在定义对象上。 */
|
|
48
60
|
create(): PluginInstance;
|
|
49
61
|
}
|
|
@@ -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 {
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Disposable, JsonValue } from "./contract";
|
|
2
|
+
/** Tau 材料的地址。对插件是不透明值:只用于传回宿主(如 materials.actions),不要依赖其字段。 */
|
|
3
|
+
export interface PluginMaterialRef {
|
|
4
|
+
readonly kind: string;
|
|
5
|
+
readonly id: number;
|
|
6
|
+
}
|
|
7
|
+
/** 摘录来源:一本书、一个网页、一篇文献 */
|
|
8
|
+
export interface PluginExcerptSource {
|
|
9
|
+
/** 来源在本插件内的稳定标识 */
|
|
10
|
+
key: string;
|
|
11
|
+
title: string;
|
|
12
|
+
/** 来源级 http(s) 外链,条目没有 url 时作为打开原处的兜底 */
|
|
13
|
+
url?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface PluginExcerptItem {
|
|
16
|
+
/** 本插件内唯一且稳定;它不变,Tau 中对这条摘录的引用就一直有效 */
|
|
17
|
+
externalId: string;
|
|
18
|
+
quote: string;
|
|
19
|
+
note?: string;
|
|
20
|
+
/** 给人看的位置:「第 3 章 · 标题」「第 12 页」 */
|
|
21
|
+
location?: string;
|
|
22
|
+
/** 条目级 http(s) 深链,优先于来源 url */
|
|
23
|
+
url?: string;
|
|
24
|
+
/** 插件私有定位,宿主只存储、按键名规范化后比较,从不解读 */
|
|
25
|
+
anchor?: {
|
|
26
|
+
[key: string]: JsonValue;
|
|
27
|
+
};
|
|
28
|
+
color?: string;
|
|
29
|
+
/** 在来源中的创建时间(毫秒) */
|
|
30
|
+
time?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface PluginExcerptSyncResult {
|
|
33
|
+
inserted: number;
|
|
34
|
+
updated: number;
|
|
35
|
+
deleted: number;
|
|
36
|
+
}
|
|
37
|
+
export interface PluginExcerptSourceSummary {
|
|
38
|
+
key: string;
|
|
39
|
+
title: string;
|
|
40
|
+
/** 未删除的条目数 */
|
|
41
|
+
count: number;
|
|
42
|
+
}
|
|
43
|
+
/** 读回的条目:写入时的字段(含缺省后的外链)加上 Tau 中的地址与删除状态 */
|
|
44
|
+
export interface PluginExcerptEntry {
|
|
45
|
+
ref: PluginMaterialRef;
|
|
46
|
+
externalId: string;
|
|
47
|
+
quote: string;
|
|
48
|
+
note: string;
|
|
49
|
+
location: string;
|
|
50
|
+
url: string | null;
|
|
51
|
+
anchor: {
|
|
52
|
+
[key: string]: JsonValue;
|
|
53
|
+
} | null;
|
|
54
|
+
color: string | null;
|
|
55
|
+
time: number | null;
|
|
56
|
+
/** 已从来源消失(软删除);引文保留,引用仍可解析 */
|
|
57
|
+
deleted: boolean;
|
|
58
|
+
}
|
|
59
|
+
export interface PluginExcerptProviderDefinition {
|
|
60
|
+
/** 提供方名称,持久化;插件停用、卸载后仍用于显示来源 */
|
|
61
|
+
label: string;
|
|
62
|
+
/** 从脉络、搜索、对话打开原处时调用;缺省时打开条目或来源的 url */
|
|
63
|
+
open?(target: {
|
|
64
|
+
sourceKey: string;
|
|
65
|
+
externalId: string;
|
|
66
|
+
}): void | Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
export interface PluginExcerptChange {
|
|
69
|
+
upsert?: PluginExcerptItem[];
|
|
70
|
+
/** 要软删除的 externalId */
|
|
71
|
+
remove?: string[];
|
|
72
|
+
}
|
|
73
|
+
/** 插件作为 Tau 外部摘录的提供方;摘录归用户,插件只能软删除。写入前须在本次激活内 register。 */
|
|
74
|
+
export interface PluginExcerpts {
|
|
75
|
+
register(definition: PluginExcerptProviderDefinition): Disposable;
|
|
76
|
+
/** 快照:以全集覆盖一个来源,缺席条目软删除;items 为空即整个来源下架 */
|
|
77
|
+
replace(source: PluginExcerptSource, items: PluginExcerptItem[]): Promise<PluginExcerptSyncResult>;
|
|
78
|
+
/** 增量:只增改或软删除指定条目 */
|
|
79
|
+
patch(source: PluginExcerptSource, change: PluginExcerptChange): Promise<PluginExcerptSyncResult>;
|
|
80
|
+
sources(): Promise<PluginExcerptSourceSummary[]>;
|
|
81
|
+
items(sourceKey: string): Promise<PluginExcerptEntry[]>;
|
|
82
|
+
}
|
|
83
|
+
export interface PluginMaterialAction {
|
|
84
|
+
id: string;
|
|
85
|
+
label: string;
|
|
86
|
+
run(): Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
export interface PluginMaterials {
|
|
89
|
+
/** 客户端对该材料当前可执行的动作(转为卡片、转为问题…);动作随客户端增加 */
|
|
90
|
+
actions(ref: PluginMaterialRef): Promise<PluginMaterialAction[]>;
|
|
91
|
+
}
|
|
@@ -2,6 +2,11 @@ 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";
|
|
8
|
+
export type * from "./excerpts";
|
|
9
|
+
export type * from "./services";
|
|
10
|
+
export type { PluginSkill } from "./skills";
|
|
6
11
|
export type { PluginPageDefinition, PluginPageHandle, PluginPageParams, PluginPageProps, PluginPages, } from "./pages";
|
|
7
|
-
export declare function definePlugin(definition: Omit<PluginDefinition, "apiVersion">): PluginDefinition;
|
|
12
|
+
export declare function definePlugin(definition: Omit<PluginDefinition, "apiVersion" | "create"> & Partial<Pick<PluginDefinition, "create">>): PluginDefinition;
|
|
@@ -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;
|