@xiongtao.xt/tau-plugins 0.22.0 → 0.23.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/API.md +544 -0
- package/CHANGELOG.md +6 -0
- package/DEVELOPMENT.md +164 -0
- package/PUBLISH-DATA.md +19 -1
- package/PUBLISH.md +31 -0
- package/SERVICES.md +16 -15
- package/VIDEO.md +25 -0
- package/package.json +1 -1
- package/types/packages/plugins/src/services.d.ts +0 -17
package/API.md
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
# 插件 API
|
|
2
|
+
|
|
3
|
+
本文介绍编写命令、页面、侧边栏、设置、数据存储、网络请求和摘录所需的接口。插件接口从 `@tau/plugins` 导入,界面组件从 `@tau/ui` 导入。所述能力需要 Tau 0.59.0 或更高版本。
|
|
4
|
+
|
|
5
|
+
本文对应 SDK 0.23.0;最低客户端版本只负责版本门槛,使用新领域接口时仍需包含对应实现的客户端构建。SDK 升级步骤见 [DEVELOPMENT.md](DEVELOPMENT.md#升级-sdk-与同步旧模板)。
|
|
6
|
+
|
|
7
|
+
博客发布提供方见 [PUBLISH.md](PUBLISH.md),需要 SDK 0.21.0 和包含该能力的客户端;独立博客站点读取数据见 [PUBLISH-DATA.md](PUBLISH-DATA.md)。
|
|
8
|
+
|
|
9
|
+
想在文档里添加自定义块,见 [EDITOR.md](EDITOR.md);需要编译 Tailwind、Typst、Markdown 等内容,见 [SERVICES.md](SERVICES.md)。
|
|
10
|
+
|
|
11
|
+
AI 功能声明、用户模型分配、流式对话、取消与统一用量审计见 [AI.md](AI.md)(SDK 0.7.0 及以上)。
|
|
12
|
+
|
|
13
|
+
白板扩展契约见 [WHITEBOARD.md](WHITEBOARD.md),需要 SDK 0.8.0 及以上。
|
|
14
|
+
|
|
15
|
+
同步存储插件见 [SYNC.md](SYNC.md),需要 SDK 0.10.0 及以上。
|
|
16
|
+
|
|
17
|
+
资源上传提供方见 [UPLOAD.md](UPLOAD.md),需要 SDK 0.12.0 和包含上传插件能力的客户端。图片和附件上传与数据库同步使用不同接口。
|
|
18
|
+
|
|
19
|
+
语音合成和音色复刻见 [TTS.md](TTS.md),需要 SDK 0.13.0。账号配置按实例隔离;需要上传参考音频时使用 `context.upload.uploadRemote`,支持取消。
|
|
20
|
+
|
|
21
|
+
图片生成与编辑见 [IMAGES.md](IMAGES.md),需要 SDK 0.18.0。通过 `context.images` 注册提供方,配置组件以 `target: "images"` 挂载到图片生成设置。
|
|
22
|
+
|
|
23
|
+
视频提供方(视频网站、自建服务器等)见 [VIDEO.md](VIDEO.md),需要 SDK 0.22.0。通过 `context.video` 注册,插件返回播放源描述,下载、缓存与播放由客户端完成;配置组件以 `target: "video"` 挂载到账号与连接设置;每个插件在各添加入口中各占一项。
|
|
24
|
+
|
|
25
|
+
向应用 AI 提供本地工具或外部 MCP 服务器见 [MCP.md](MCP.md),需要 SDK 0.11.0 和包含此能力的客户端。
|
|
26
|
+
|
|
27
|
+
聊天工具自定义渲染使用 `await context.chat.registerToolRenderer`,数据与生命周期见 [MCP.md](MCP.md#聊天工具自定义渲染)。类型契约自 SDK 0.15.0 提供,需要包含实现的客户端构建。
|
|
28
|
+
|
|
29
|
+
阅读顺序:先了解[入口与生命周期](#入口与生命周期),再按功能查阅[命令](#命令)、[存储](#私有存储)、[页面](#页面)、[设置](#设置)、[网络请求](#网络请求)、[摘录](#摘录)、[材料动作](#材料动作)和[资源清理](#资源清理)。完整可运行示例见 `src/index.tsx`。
|
|
30
|
+
|
|
31
|
+
## manifest
|
|
32
|
+
|
|
33
|
+
通常修改插件身份、版本和最低客户端版本:
|
|
34
|
+
|
|
35
|
+
| 字段 | 示例 | 含义 |
|
|
36
|
+
| --------------- | ------------------ | ---------------------------------------------------------------------- |
|
|
37
|
+
| `id` | `yourname.counter` | 插件唯一标识;使用小写字母、数字、点或连字符;安装后不要随版本变化 |
|
|
38
|
+
| `name` | `我的计数器` | 插件管理中显示的名称 |
|
|
39
|
+
| `version` | `1.0.0` | 插件版本,格式为 `主版本.次版本.修订号`,也可带 `-beta.1` 等预发布后缀 |
|
|
40
|
+
| `minAppVersion` | `0.59.0` | 所需最低 Tau 客户端版本,不是 SDK 版本;低于要求时不加载 |
|
|
41
|
+
| `formatVersion` | `1` | 包格式版本,保留模板值 |
|
|
42
|
+
| `apiVersion` | `1` | API 协议版本,保留模板值 |
|
|
43
|
+
| `platform` | `desktop` | 运行平台,保留模板值 |
|
|
44
|
+
| `format` | `cjs` | 产物格式,保留模板值 |
|
|
45
|
+
| `main` | `main.js` | 构建入口,保留模板值 |
|
|
46
|
+
| `categories` | `["page"]` | 插件中心分类,可多选 |
|
|
47
|
+
| `keywords` | `["计数", "打卡"]` | 搜索关键词,最多 5 个,每个不超过 20 字 |
|
|
48
|
+
|
|
49
|
+
分类值为 `page`、`editor`、`whiteboard`、`sync`、`upload`、`voice`、`ai`、`video`、`blog-publish`、`blog-template`。仅提供命令或普通设置时可以省略;分类不授予权限。声明静态编辑器、白板扩展时分别设置 `editor: true`、`whiteboard: true`,详见对应文档。
|
|
50
|
+
|
|
51
|
+
`styles` 和 `integrity` 由构建生成,不必手写。修改源文件后重新运行 `pnpm build`,不要直接编辑 ZIP 内文件。
|
|
52
|
+
|
|
53
|
+
`minAppVersion` 使用完整版本号,可带预发布标识,例如 `0.59.0-rc.1`;不写 `>=`、`^` 等范围。正式版高于同号预发布版,构建元数据不影响顺序。模板默认要求 0.59.0,后续使用更高版本能力时由作者提高该值。
|
|
54
|
+
|
|
55
|
+
升级时保持 id 不变并增加 version,正式用户从插件中心更新,开发版客户端可从 ZIP 调试,重启生效。客户端保留上一版代码,支持“恢复上一版”;恢复仍需要重启,且不会恢复旧数据。需要变更存储结构时,优先采用向后兼容读写,避免旧代码恢复后无法读取数据。
|
|
56
|
+
|
|
57
|
+
## 入口与生命周期
|
|
58
|
+
|
|
59
|
+
`definePlugin(definition)` 接收 `id`、`name`、`version` 和生命周期工厂 `create`,返回插件定义,作为入口的默认导出。模板使用 `__PLUGIN_META__` 自动读取 manifest 中的前三项。纯编辑器插件可省略 `create`,通过 `editorExtensions` 声明扩展,详见 EDITOR.md。
|
|
60
|
+
|
|
61
|
+
下面是可替换 `src/index.tsx` 的最小完整插件:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { definePlugin } from "@tau/plugins";
|
|
65
|
+
|
|
66
|
+
declare const __PLUGIN_META__: { id: string; name: string; version: string };
|
|
67
|
+
|
|
68
|
+
export default definePlugin({
|
|
69
|
+
...__PLUGIN_META__,
|
|
70
|
+
create: () => ({
|
|
71
|
+
activate(context) {
|
|
72
|
+
context.commands.register({
|
|
73
|
+
id: "reset-count",
|
|
74
|
+
title: "重置计数",
|
|
75
|
+
run: async () => {
|
|
76
|
+
await context.storage.set("count", 0);
|
|
77
|
+
return null;
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
| 方法 | 调用时机与返回值 |
|
|
86
|
+
| ------------------------------------------ | -------------------------------------------------------------- |
|
|
87
|
+
| `create(): PluginInstance` | 每次启用时调用,返回本次运行的实例 |
|
|
88
|
+
| `activate(context): void \| Promise<void>` | 注册功能、读取必要配置;完成后插件才可用 |
|
|
89
|
+
| `deactivate?(): void \| Promise<void>` | 可选的额外清理;不要依赖它写入最终数据,数据应在用户操作时保存 |
|
|
90
|
+
|
|
91
|
+
停用、工作区切换或激活失败时,本次上下文会失效,命令、页面和设置会被清理。下一次启用会重新调用 `create` 和 `activate`。实例状态放在 `create` 内或组件内;需要跨启用保存的状态放入 storage。
|
|
92
|
+
|
|
93
|
+
`activate` 抛出错误会导致启用失败。不要在其中等待永不结束的任务;注册事件或任务后及时返回,并按下文处理取消与清理。
|
|
94
|
+
|
|
95
|
+
## context
|
|
96
|
+
|
|
97
|
+
`activate` 以及页面、设置组件都会收到 `PluginContext`。
|
|
98
|
+
|
|
99
|
+
| 属性 | 类型 | 用途 |
|
|
100
|
+
| ------------- | -------------------------------------- | ------------------------------------------------ |
|
|
101
|
+
| `pluginId` | `string` | 当前插件标识 |
|
|
102
|
+
| `workspaceId` | `string` | 当前工作区标识;不要解析它的内部格式 |
|
|
103
|
+
| `signal` | `AbortSignal` | 本次运行结束时中止,可用于停止异步工作 |
|
|
104
|
+
| `commands` | `PluginContext["commands"]` | 注册命令 |
|
|
105
|
+
| `storage` | `PluginStorage` | 保存当前插件在当前工作区的数据 |
|
|
106
|
+
| `pages` | `PluginPages` | 注册并打开页面 |
|
|
107
|
+
| `settings` | `PluginSettings` | 注册并打开设置区域 |
|
|
108
|
+
| `editor` | `PluginEditor` | 开发台编辑器贡献;正文使用静态扩展,见 EDITOR.md |
|
|
109
|
+
| `sync` | `PluginSync` | 注册同步存储提供方,见 SYNC.md |
|
|
110
|
+
| `upload` | `PluginUpload` | 资源上传与上传提供方,见 UPLOAD.md |
|
|
111
|
+
| `tts` | `PluginTTS` | 语音合成与音色复刻提供方,见 TTS.md |
|
|
112
|
+
| `images` | `PluginImages` | 图片生成与编辑提供方,见 IMAGES.md |
|
|
113
|
+
| `video` | `PluginVideo` | 视频来源与播放源提供方,见 VIDEO.md |
|
|
114
|
+
| `publish` | `PluginPublish` | 博客发布提供方,见 PUBLISH.md |
|
|
115
|
+
| `sidebar` | `PluginSidebar` | 创建侧边栏分组、注册页面入口 |
|
|
116
|
+
| `fetch` | `typeof fetch` | 发起与插件生命周期关联的网络请求 |
|
|
117
|
+
| `services` | `PluginServices` | 编译与转换服务,见 SERVICES.md |
|
|
118
|
+
| `ai` | `PluginAI` | 使用用户分配的模型,见 AI.md |
|
|
119
|
+
| `mcp` | `PluginMCP` | 注册 MCP,本地工具或外部服务器,见 MCP.md |
|
|
120
|
+
| `chat` | `PluginChat` | 注册 MCP 工具结果渲染器,见 MCP.md |
|
|
121
|
+
| `excerpts` | `PluginExcerpts` | 把外部划线、标注写为 Tau 摘录 |
|
|
122
|
+
| `materials` | `PluginMaterials` | 获取「转为卡片」等材料动作 |
|
|
123
|
+
| `register` | `(resource: Disposable) => Disposable` | 托管需要清理的资源 |
|
|
124
|
+
|
|
125
|
+
不要缓存上下文供下次启用使用。旧上下文失效后调用 storage、注册功能或打开页面会报错。
|
|
126
|
+
|
|
127
|
+
## 命令
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
context.commands.register({
|
|
131
|
+
id: "reset-count",
|
|
132
|
+
title: "重置计数",
|
|
133
|
+
run: async () => {
|
|
134
|
+
await context.storage.set("count", 0);
|
|
135
|
+
return null;
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
此处及下文的 `context` 示例放在 `activate(context)` 内,或用于已获得上下文的函数。
|
|
141
|
+
|
|
142
|
+
| 参数 | 类型 | 说明 |
|
|
143
|
+
| ------- | ------------------------------------------------------- | ------------------------------------------------------- |
|
|
144
|
+
| `id` | `string` | 插件内部唯一的局部 ID,例如 `open`;不要自行拼接插件 ID |
|
|
145
|
+
| `title` | `string` | 非空的命令标题,用户可在 Ctrl+K 命令面板搜索 |
|
|
146
|
+
| `run` | `(input: JsonValue) => JsonValue \| Promise<JsonValue>` | 执行命令;无返回数据时返回 `null` |
|
|
147
|
+
|
|
148
|
+
返回 `Disposable`,可提前 `dispose()` 撤销命令。未提前撤销的命令在停用时自动移除。同一插件不能重复注册相同命令 ID;命令注册时不会立即执行。
|
|
149
|
+
|
|
150
|
+
`run` 应处理预期业务错误,并为异步任务提供必要反馈;抛出的异常代表执行失败。`input` 如需使用,应先校验结构,不假定命令面板会提供自定义参数。
|
|
151
|
+
|
|
152
|
+
命令、页面、设置的局部 ID 和 storage key 均为 1–128 个字符,以字母或数字开头,后续允许字母、数字、点、横线和下划线。
|
|
153
|
+
|
|
154
|
+
## 私有存储
|
|
155
|
+
|
|
156
|
+
| 方法 | 返回值 | 说明 |
|
|
157
|
+
| ------------------------- | --------------------------------- | ------------------------ |
|
|
158
|
+
| `storage.get(key)` | `Promise<JsonValue \| undefined>` | 不存在时返回 `undefined` |
|
|
159
|
+
| `storage.set(key, value)` | `Promise<void>` | 保存或替换该 key 的值 |
|
|
160
|
+
| `storage.delete(key)` | `Promise<void>` | 删除该 key |
|
|
161
|
+
|
|
162
|
+
数据按插件与工作区隔离,停用和重启不会删除。同一个插件在不同工作区读到不同数据;升级时保持插件 ID 不变,并自行处理旧数据结构。
|
|
163
|
+
|
|
164
|
+
`JsonValue` 支持 `null`、布尔值、字符串、有限数字、这些值组成的数组和普通对象。不能存储 `undefined`、函数、Date、Map、循环引用、NaN 或 Infinity;日期应转换为字符串。
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const saved = await context.storage.get("count");
|
|
168
|
+
const count = typeof saved === "number" ? saved : 0;
|
|
169
|
+
await context.storage.set("count", count + 1);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`get` 不提供泛型解码,读取后应校验类型。返回数据的本地修改不会自动保存,必须调用 `set`。连续的读写不是原子自增;按钮保存过程中应禁用重复提交,模板的计数器演示了这一点。
|
|
173
|
+
|
|
174
|
+
## 页面
|
|
175
|
+
|
|
176
|
+
`context.pages.register(definition)` 返回 `PluginPageHandle`:
|
|
177
|
+
|
|
178
|
+
| 定义字段 | 类型 | 说明 |
|
|
179
|
+
| ----------- | ------------------------------------------------------ | ---------------------------------------------------- |
|
|
180
|
+
| `id` | `string` | 插件内页面 ID |
|
|
181
|
+
| `title` | `string` | 非空的页面标题 |
|
|
182
|
+
| `component` | `ComponentType<PluginPageProps>` | React 组件 |
|
|
183
|
+
| `match` | `"single" \| "params"`,可选 | 默认单实例;`params` 按参数区分页面实例 |
|
|
184
|
+
| `getId` | `(params: Readonly<PluginPageParams>) => string`,可选 | `match: "params"` 时自定义实例身份;默认使用完整参数 |
|
|
185
|
+
|
|
186
|
+
句柄提供只读 `id`、`open(params?): Promise<void>` 和 `dispose()`。`open()` 负责导航,不要自行操作浏览器地址或历史。重复打开单实例页面会复用该页面身份;句柄注销或插件停用后不能再打开。
|
|
187
|
+
|
|
188
|
+
组件收到 `context` 和只读 `params`。参数仅支持字符串、有限数字、布尔值组成的对象,不接受数组、嵌套对象或 `null`。
|
|
189
|
+
|
|
190
|
+
下面的完整示例注册一个按条目 ID 区分的页面,以及打开页面的命令:
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
import { definePlugin, type PluginPageProps } from "@tau/plugins";
|
|
194
|
+
|
|
195
|
+
declare const __PLUGIN_META__: { id: string; name: string; version: string };
|
|
196
|
+
|
|
197
|
+
function Detail({ params }: PluginPageProps) {
|
|
198
|
+
return <h1>当前条目:{String(params.itemId ?? "未选择")}</h1>;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export default definePlugin({
|
|
202
|
+
...__PLUGIN_META__,
|
|
203
|
+
create: () => ({
|
|
204
|
+
activate(context) {
|
|
205
|
+
const detail = context.pages.register({
|
|
206
|
+
id: "detail",
|
|
207
|
+
title: "条目详情",
|
|
208
|
+
component: Detail,
|
|
209
|
+
match: "params",
|
|
210
|
+
getId: params => String(params.itemId),
|
|
211
|
+
});
|
|
212
|
+
context.commands.register({
|
|
213
|
+
id: "open-detail",
|
|
214
|
+
title: "打开示例条目",
|
|
215
|
+
run: async () => {
|
|
216
|
+
await detail.open({ itemId: "first" });
|
|
217
|
+
return null;
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
}),
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## 侧边栏
|
|
226
|
+
|
|
227
|
+
`context.sidebar` 为页面添加侧边栏入口。条目关联已有页面,点击自动打开并随当前页面高亮;停用后入口消失,恢复启用时重新注册。
|
|
228
|
+
|
|
229
|
+
### 注册到已有分组
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
import { SIDEBAR_GROUPS } from "@tau/plugins";
|
|
233
|
+
|
|
234
|
+
const page = context.pages.register({ id: "counter", title: "计数", component: Settings });
|
|
235
|
+
context.sidebar.registerItem({
|
|
236
|
+
id: "counter",
|
|
237
|
+
title: "计数",
|
|
238
|
+
group: SIDEBAR_GROUPS.daily,
|
|
239
|
+
page,
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
此处 `Settings` 是模板的计数组件,示例放在 `activate` 内,import 放在文件顶部。分组使用稳定标识,不使用显示名称:
|
|
244
|
+
|
|
245
|
+
| 常量 | 分组 |
|
|
246
|
+
| -------------------------- | ---------------------- |
|
|
247
|
+
| `SIDEBAR_GROUPS.daily` | 日常 |
|
|
248
|
+
| `SIDEBAR_GROUPS.knowledge` | 知识 |
|
|
249
|
+
| `SIDEBAR_GROUPS.learning` | 学习 |
|
|
250
|
+
| `SIDEBAR_GROUPS.ai` | AI |
|
|
251
|
+
| `SIDEBAR_GROUPS.sharing` | 分享 |
|
|
252
|
+
| `SIDEBAR_GROUPS.plugins` | 插件;没有条目时不显示 |
|
|
253
|
+
|
|
254
|
+
### 自建分组
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
const group = context.sidebar.registerGroup({ id: "habits", title: "习惯" });
|
|
258
|
+
const page = context.pages.register({ id: "counter", title: "计数", component: Settings });
|
|
259
|
+
context.sidebar.registerItem({ id: "counter", title: "计数", group: group.id, page });
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
`registerGroup({ id, title })` 返回 `{ readonly id, dispose() }`。传入的 id 为插件内局部 ID,使用返回的 `group.id` 引用分组。空组不显示;不同插件可以使用相同局部 ID 或显示标题,各自独立。自建分组显示在内置分组之后。
|
|
263
|
+
|
|
264
|
+
`registerItem(definition)` 返回 `Disposable`:
|
|
265
|
+
|
|
266
|
+
| 字段 | 类型 | 说明 |
|
|
267
|
+
| -------- | ------------------------- | ----------------------------------------------------------- |
|
|
268
|
+
| `id` | `string` | 插件内唯一条目 ID |
|
|
269
|
+
| `title` | `string` | 非空的入口名称 |
|
|
270
|
+
| `icon` | `PluginSidebarIcon`,可选 | SVG 图标组件,省略时使用默认插件图标 |
|
|
271
|
+
| `group` | `string`,可选 | 公开分组常量或本插件分组句柄的 id;缺省或不存在时归入“插件” |
|
|
272
|
+
| `page` | `PluginPageHandle` | 本插件本次启用注册的有效页面句柄 |
|
|
273
|
+
| `params` | `PluginPageParams`,可选 | 点击时传给页面的参数,缺省为空对象 |
|
|
274
|
+
|
|
275
|
+
条目追加在所在分组末尾。参数页面按页面实例高亮,单实例页面按页面身份高亮;通过命令面板打开相同页面也会高亮侧边栏入口。条目与页面 ID 分别独立,可以为同一个参数页面注册多个入口。
|
|
276
|
+
|
|
277
|
+
### 自定义图标
|
|
278
|
+
|
|
279
|
+
`icon` 接收组件本身,例如 `icon: CounterIcon`,不是 `<CounterIcon />`、图片地址或图标名称字符串。模板的 `src/CounterIcon.tsx` 是可直接修改的 SVG 示例:
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
import type { PluginSidebarIconProps } from "@tau/plugins";
|
|
283
|
+
|
|
284
|
+
export function WaterDropIcon({ size = 24, strokeWidth = 2, ...props }: PluginSidebarIconProps) {
|
|
285
|
+
return (
|
|
286
|
+
<svg
|
|
287
|
+
width={size}
|
|
288
|
+
height={size}
|
|
289
|
+
viewBox="0 0 24 24"
|
|
290
|
+
fill="none"
|
|
291
|
+
stroke="currentColor"
|
|
292
|
+
strokeWidth={strokeWidth}
|
|
293
|
+
{...props}
|
|
294
|
+
>
|
|
295
|
+
<path d="M12 3C9 7 5 11 5 15a7 7 0 0 0 14 0c0-4-4-8-7-12Z" />
|
|
296
|
+
</svg>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
在插件入口导入图标后,将其传给 `context.sidebar.registerItem({ id: "water", title: "喝水打卡", page, icon: WaterDropIcon })`。
|
|
302
|
+
|
|
303
|
+
`PluginSidebarIconProps` 包含标准 SVG 属性及可选的 `size: number | string`。侧边栏传入 `size`、`strokeWidth`、`className` 和 `aria-hidden`,图标应透传这些属性;用 `currentColor` 跟随明暗主题及选中颜色,不写死宽高或颜色。展开和收起状态使用同一个图标,图标不承担按钮点击或业务状态管理。
|
|
304
|
+
|
|
305
|
+
也可以使用满足该属性约定的 React 图标库组件,依赖通过 pnpm 添加,无须额外配置打包。
|
|
306
|
+
|
|
307
|
+
提前调用条目的 `dispose()` 会移除入口;注销页面也会隐藏关联入口。提前注销分组后,剩余条目归入“插件”;当最后一个条目移除后,空分组不显示。注册重复 ID 会报错,不覆盖已有条目。插件只能管理自己注册的分组和条目,不能向其他插件的分组插入,也不能引用其他插件或上一次启用的页面句柄。
|
|
308
|
+
|
|
309
|
+
## 设置
|
|
310
|
+
|
|
311
|
+
`context.settings.register(definition)` 注册插件设置区域,返回 `PluginSettingsHandle`。
|
|
312
|
+
|
|
313
|
+
| 定义字段 | 类型 | 说明 |
|
|
314
|
+
| ------------- | ------------------------------------ | -------------------------------- |
|
|
315
|
+
| `id` | `string` | 插件内设置区域 ID |
|
|
316
|
+
| `title` | `string` | 非空的显示标题 |
|
|
317
|
+
| `description` | `string`,可选 | 设置区域说明 |
|
|
318
|
+
| `component` | `ComponentType<PluginSettingsProps>` | 接收 `{ context }` 的 React 组件 |
|
|
319
|
+
|
|
320
|
+
返回的句柄提供 `id`、`open(): Promise<void>` 和 `dispose()`。设置值不会自动保存,组件必须通过 storage 读写。
|
|
321
|
+
|
|
322
|
+
提供方配置分区声明 `target`,使用宿主注入配置中的 `save` 保存当前提供方或账号,不使用上述普通设置的 storage 流程。配置属性是可选的,组件使用前先检查。配置界面在对应业务设置内挂载,`open()` 跳转到该入口。
|
|
323
|
+
|
|
324
|
+
| `target` | 配置属性 | 保存方法 |
|
|
325
|
+
| --------- | ------------------------------------------- | ---------------------- |
|
|
326
|
+
| `sync` | `syncConfig` | `save(value)` |
|
|
327
|
+
| `upload` | `uploadConfig` | `save(value)` |
|
|
328
|
+
| `tts` | `ttsConfig`(含 `instanceId`、`models`) | `save(value, models)` |
|
|
329
|
+
| `images` | `imageConfig`(含 `instanceId`、`modelId`) | `save(value, modelId)` |
|
|
330
|
+
| `video` | `videoConfig` | `save(value)` |
|
|
331
|
+
| `publish` | `publishConfig` | `save(value)` |
|
|
332
|
+
|
|
333
|
+
各配置都提供 `value: JsonValue`,读取后校验结构;保存失败保留草稿,成功后再提示。TTS 和图片按账号实例隔离,其他分区按对应提供方保存。完整示例与业务约束见各领域文档。
|
|
334
|
+
|
|
335
|
+
模板中的 `Settings` 组件可以直接复用;以下片段放在同一入口的 `activate` 内:
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
const preferences = context.settings.register({
|
|
339
|
+
id: "preferences",
|
|
340
|
+
title: "计数设置",
|
|
341
|
+
description: "查看和调整已保存的计数",
|
|
342
|
+
component: Settings,
|
|
343
|
+
});
|
|
344
|
+
context.commands.register({
|
|
345
|
+
id: "open-preferences",
|
|
346
|
+
title: "打开计数设置",
|
|
347
|
+
run: async () => {
|
|
348
|
+
await preferences.open();
|
|
349
|
+
return null;
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
页面与设置同时编辑同一数据时,需要自行刷新界面上的查询或状态。模板使用 React Query 处理读取、保存、加载中和失败状态;storage 本身不提供响应式订阅。
|
|
355
|
+
|
|
356
|
+
## 网络请求
|
|
357
|
+
|
|
358
|
+
`context.fetch(input, init?)` 的参数和返回值与标准 Fetch API 相同,返回 `Promise<Response>`。请求会随插件停用取消,也支持传入自己的 `AbortSignal`。
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
context.commands.register({
|
|
362
|
+
id: "load-status",
|
|
363
|
+
title: "获取服务状态",
|
|
364
|
+
run: async () => {
|
|
365
|
+
const response = await context.fetch("https://example.com/status.json");
|
|
366
|
+
if (!response.ok) throw new Error(`请求失败:${response.status}`);
|
|
367
|
+
const data: unknown = await response.json();
|
|
368
|
+
if (typeof data !== "object" || data === null || !("status" in data)) {
|
|
369
|
+
throw new Error("响应缺少 status 字段");
|
|
370
|
+
}
|
|
371
|
+
if (typeof data.status !== "string") throw new Error("status 应为字符串");
|
|
372
|
+
return { status: data.status };
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
将示例地址替换成自己的服务。HTTP 4xx/5xx 不会自动抛出异常,需检查 `response.ok`。请求在宿主执行,不自动继承浏览器页面的 Cookie 或 CORS 行为,认证由插件通过标准请求头提供,不保证任意地址均可访问。不要把私密令牌写进源码;请求结果进入存储前也要校验数据。
|
|
378
|
+
|
|
379
|
+
## 摘录
|
|
380
|
+
|
|
381
|
+
如果你的插件从外部服务读取划线、标注或收藏(读书笔记、网页高亮、文献批注等),可以把它们写成 Tau 的摘录。写入后,用户能在全局搜索和 AI 对话中找到它们、在文档中引用,也能转为卡片或问题。摘录属于用户:插件停用或卸载后,已写入的摘录和引用都会保留。
|
|
382
|
+
|
|
383
|
+
### 注册提供方
|
|
384
|
+
|
|
385
|
+
写入前,先在 `activate` 中注册一次提供方:
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
const pages = { book: context.pages.register({ id: "book", title: "书籍", component: Book }) };
|
|
389
|
+
|
|
390
|
+
context.excerpts.register({
|
|
391
|
+
label: "我的读书笔记",
|
|
392
|
+
open: ({ sourceKey, externalId }) => pages.book.open({ bookId: sourceKey, noteId: externalId }),
|
|
393
|
+
});
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
| 字段 | 说明 |
|
|
397
|
+
| ------- | ------------------------------------------------------------------------------------------ |
|
|
398
|
+
| `label` | 显示为摘录的来源名称,1–100 字;插件停用后仍用它显示来源 |
|
|
399
|
+
| `open` | 可选。用户点击「打开原处」时调用,通常打开插件自己的页面并定位;省略时打开条目或来源的 url |
|
|
400
|
+
|
|
401
|
+
每次启用只能注册一次,未注册就写入会报错。
|
|
402
|
+
|
|
403
|
+
### 写入
|
|
404
|
+
|
|
405
|
+
摘录按**来源**(一本书、一个网页、一篇文献)分组写入,有两种方式:
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
const source = { key: "book-42", title: "设计心理学", url: "https://example.com/books/42" };
|
|
409
|
+
|
|
410
|
+
// 全量:以这批条目为准,不在其中的已有条目会被移除
|
|
411
|
+
await context.excerpts.replace(source, [
|
|
412
|
+
{
|
|
413
|
+
externalId: "n1",
|
|
414
|
+
quote: "好的设计是让人一眼就知道怎么用。",
|
|
415
|
+
location: "第 1 章",
|
|
416
|
+
time: 1760000000000,
|
|
417
|
+
},
|
|
418
|
+
{ externalId: "n2", quote: "示能决定了可能的操作。", note: "和交互设计课的观点一致" },
|
|
419
|
+
]);
|
|
420
|
+
|
|
421
|
+
// 增量:只新增、更新或移除指定条目
|
|
422
|
+
await context.excerpts.patch(source, {
|
|
423
|
+
upsert: [{ externalId: "n3", quote: "反馈要及时。" }],
|
|
424
|
+
remove: ["n1"],
|
|
425
|
+
});
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
两者都返回 `{ inserted, updated, deleted }`。外部服务能一次给出某个来源的全部条目时用 `replace`;只拿到变化时用 `patch`。`replace` 传入空数组表示整个来源已不存在。
|
|
429
|
+
|
|
430
|
+
| 来源字段 | 说明 |
|
|
431
|
+
| -------- | ----------------------------------------------------- |
|
|
432
|
+
| `key` | 来源在本插件内的稳定标识,必填,最长 512 字 |
|
|
433
|
+
| `title` | 显示的来源标题,最长 500 字 |
|
|
434
|
+
| `url` | 可选,http(s) 地址;条目没有自己的 url 时用它打开原处 |
|
|
435
|
+
|
|
436
|
+
| 条目字段 | 说明 |
|
|
437
|
+
| ------------ | ----------------------------------------------------------------------------------------- |
|
|
438
|
+
| `externalId` | 条目在本插件内的唯一标识,必填,最长 512 字。**它不变,Tau 中对这条摘录的引用就一直有效** |
|
|
439
|
+
| `quote` | 摘录原文,必填,最长 20000 字 |
|
|
440
|
+
| `note` | 用户写的想法或批注,可选 |
|
|
441
|
+
| `location` | 给人看的位置,如「第 3 章 · 标题」「第 12 页」,最长 500 字 |
|
|
442
|
+
| `url` | 可选,条目级 http(s) 深链,优先于来源 url |
|
|
443
|
+
| `anchor` | 可选,插件自己的定位数据(JSON 对象,序列化后不超过 8000 字);Tau 只保存、不解读 |
|
|
444
|
+
| `color` | 可选,划线颜色,最长 50 字 |
|
|
445
|
+
| `time` | 可选,在原服务中的创建时间(毫秒时间戳) |
|
|
446
|
+
|
|
447
|
+
使用约束:
|
|
448
|
+
|
|
449
|
+
- `externalId` 由你从外部数据中稳定地生成(例如原服务的笔记 ID)。改变生成规则会让旧摘录被移除、用户的引用失效。
|
|
450
|
+
- 被移除的摘录不会真正删除,原文仍保留、引用仍能显示;以后同一个 `externalId` 再次写入时会恢复。
|
|
451
|
+
- 单次写入最多 20000 条,同一批中 `externalId` 不能重复,同一次 `patch` 不能同时写入和移除同一条。任一条不合法时整批拒绝。
|
|
452
|
+
- 外部服务分页拉取时,确认拿到完整列表后再调用 `replace`;用不完整的结果覆盖会误移除条目。
|
|
453
|
+
|
|
454
|
+
### 读回
|
|
455
|
+
|
|
456
|
+
```ts
|
|
457
|
+
const sources = await context.excerpts.sources(); // [{ key, title, count }]
|
|
458
|
+
const entries = await context.excerpts.items("book-42");
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
`sources()` 列出本插件写入过的来源,`count` 为未移除的条目数。`items(sourceKey)` 返回该来源的全部条目,包括已移除的(`deleted: true`),可以用来离线展示或与外部数据比对。每个条目带有 `ref`,用于下文的材料动作。
|
|
462
|
+
|
|
463
|
+
## 材料动作
|
|
464
|
+
|
|
465
|
+
摘录等内容在 Tau 中统称「材料」。`context.materials.actions(ref)` 返回 Tau 当前可以对这条材料执行的操作,例如「转为卡片」「转为问题」,适合放在插件自己的列表或详情页里:
|
|
466
|
+
|
|
467
|
+
```tsx
|
|
468
|
+
import { Button } from "@tau/ui";
|
|
469
|
+
import type { PluginContext, PluginMaterialAction, PluginMaterialRef } from "@tau/plugins";
|
|
470
|
+
import { useEffect, useState } from "react";
|
|
471
|
+
|
|
472
|
+
function NoteActions({ context, target }: { context: PluginContext; target: PluginMaterialRef }) {
|
|
473
|
+
const [actions, setActions] = useState<PluginMaterialAction[]>([]);
|
|
474
|
+
useEffect(() => {
|
|
475
|
+
void context.materials.actions(target).then(list => {
|
|
476
|
+
if (!context.signal.aborted) setActions(list);
|
|
477
|
+
});
|
|
478
|
+
}, [context, target]);
|
|
479
|
+
return actions.map(action => (
|
|
480
|
+
<Button key={action.id} variant="ghost" onClick={() => void action.run()}>
|
|
481
|
+
{action.label}
|
|
482
|
+
</Button>
|
|
483
|
+
));
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
- `ref` 来自 `excerpts.items()` 返回的条目,把它原样传回即可,不要自己构造或解析其中的字段。
|
|
488
|
+
- 每个动作有 `id`、`label` 和 `run()`。直接用 `label` 作为按钮文字,不要按 `id` 写死列表:Tau 以后可能增加新的动作。
|
|
489
|
+
- 动作执行后由 Tau 自己给出成功或失败提示,插件不需要再弹提示。
|
|
490
|
+
|
|
491
|
+
## 资源清理
|
|
492
|
+
|
|
493
|
+
`Disposable` 的签名为 `{ dispose(): void | Promise<void> }`。
|
|
494
|
+
|
|
495
|
+
命令、页面、设置的注册结果会自动清理。自行创建的计时器、事件订阅或第三方对象应交给 `context.register(resource)`,返回的句柄也可用于提前清理。资源在本次运行结束时按注册顺序的逆序清理。
|
|
496
|
+
|
|
497
|
+
```ts
|
|
498
|
+
const onOnline = () => console.info("网络已连接");
|
|
499
|
+
window.addEventListener("online", onOnline);
|
|
500
|
+
const subscription = context.register({
|
|
501
|
+
dispose: () => window.removeEventListener("online", onOnline),
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// 不再需要时可以提前撤销;未撤销时会在停用时清理。
|
|
505
|
+
// await subscription.dispose();
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
清理函数应尽快完成,可安全处理重复调用。React 组件自己的订阅可以在 `useEffect` 的返回函数中清理。异步结果写回界面前检查 `context.signal.aborted`,不要让上一工作区的请求更新新的界面。
|
|
509
|
+
|
|
510
|
+
## 界面与样式
|
|
511
|
+
|
|
512
|
+
模板使用 React 组件,并提供 `@tau/ui` 的基础组件。常用组合:
|
|
513
|
+
|
|
514
|
+
```tsx
|
|
515
|
+
import { useState } from "react";
|
|
516
|
+
import { Button, Panel, Dialog, DialogBody } from "@tau/ui";
|
|
517
|
+
|
|
518
|
+
export function Help() {
|
|
519
|
+
const [open, setOpen] = useState(false);
|
|
520
|
+
return (
|
|
521
|
+
<Panel>
|
|
522
|
+
<Button variant="ghost" onClick={() => setOpen(true)}>
|
|
523
|
+
查看说明
|
|
524
|
+
</Button>
|
|
525
|
+
<Dialog open={open} onOpenChange={setOpen} title="使用说明">
|
|
526
|
+
<DialogBody>数据会保存在当前工作区。</DialogBody>
|
|
527
|
+
</Dialog>
|
|
528
|
+
</Panel>
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
| 组件 | 常用属性 |
|
|
534
|
+
| ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
535
|
+
| `Button` | `onClick`、`disabled`、`loading`、`loadingText`;`variant` 可选 `primary`、`secondary`、`soft`、`ghost`、`danger`、`danger-soft` |
|
|
536
|
+
| `Panel` | `children`、`className`,用于组织内容 |
|
|
537
|
+
| `Dialog` | `open`、`onOpenChange`、`title`;可用 `footer` 放操作按钮 |
|
|
538
|
+
| `DialogBody` | `children`,用于弹窗正文 |
|
|
539
|
+
|
|
540
|
+
自定义样式放入 CSS 并在入口导入。为选择器加插件专属前缀,避免影响其他界面;颜色使用 `var(--tau-accent)` 等主题变量。不要依赖客户端为插件源码生成 Tailwind 样式。
|
|
541
|
+
|
|
542
|
+
## 依赖与升级
|
|
543
|
+
|
|
544
|
+
添加依赖使用 `pnpm add <包名>`;升级 SDK 或 React 前先确认兼容性,并完整验证启用、停用和重启流程。保留 `pnpm-lock.yaml`,以便其他开发者复现依赖版本。
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# 更新记录
|
|
2
2
|
|
|
3
|
+
## 0.23.0
|
|
4
|
+
|
|
5
|
+
- 移除 `PluginServices.ffmpeg.probe` 及其媒体信息类型,宿主不再随包分发 FFprobe;`ffmpeg.run` 保留。
|
|
6
|
+
- 视频页面标题、时长和格式查询继续使用 `services.videoExtractor.probe`,该接口由宿主视频解析器提供,不是本地媒体探测接口的替代品。
|
|
7
|
+
- 作者模板升级到本版 SDK,补齐博客发布、独立站点数据接入与升级说明,并修正服务文档中的已移除接口。
|
|
8
|
+
|
|
3
9
|
## 0.22.0
|
|
4
10
|
|
|
5
11
|
- 新增 `context.video.register`,接入视频来源(视频网站、自建服务器等):插件识别链接、解析标题与清晰度,返回播放源描述(单文件、音视频分离或交给宿主解析器),下载、缓存与播放由宿主完成。
|
package/DEVELOPMENT.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Tau 插件开发指南
|
|
2
|
+
|
|
3
|
+
用 TypeScript 和 React 为 Tau 桌面端开发插件。模板自带一个可运行的计数器,演示命令、页面、侧边栏入口、设置和数据保存,可以在它的基础上改出自己的插件。
|
|
4
|
+
|
|
5
|
+
## 准备
|
|
6
|
+
|
|
7
|
+
- Node.js 22 或更高版本
|
|
8
|
+
- pnpm 11.9.0
|
|
9
|
+
- Tau 桌面客户端 0.59.0 或更高版本
|
|
10
|
+
|
|
11
|
+
模板锁定公开 SDK `@xiongtao.xt/tau-plugins@0.23.0` 和 UI `@xiongtao.xt/tau-ui@0.1.1`,通过 npm 别名安装为 `@tau/plugins`、`@tau/ui`。SDK 提供类型契约,运行实现由 Tau 注入;新领域能力还需包含相应实现的客户端构建,不能只依据客户端版本号判断。
|
|
12
|
+
|
|
13
|
+
## 跑通示例
|
|
14
|
+
|
|
15
|
+
在 GitHub 点击 **Use this template** 创建自己的仓库,克隆后在项目目录执行:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
pnpm install
|
|
19
|
+
pnpm build
|
|
20
|
+
pnpm install:local
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
打开 Tau 的插件管理,点击「刷新列表」,启用「计数器示例」。然后点击侧边栏「日常」分组下的「计数器」,或按 Ctrl+K 执行「打开计数器」,点「增加并保存」。重启客户端后再打开,计数仍然保留。
|
|
24
|
+
|
|
25
|
+
## 改成自己的插件
|
|
26
|
+
|
|
27
|
+
1. 修改 `manifest.json` 的 `id`、`name`、`version`、`description`。`id` 用你自己的唯一名称,例如 `yourname.counter`。
|
|
28
|
+
- `categories` 决定插件在插件中心出现在哪些分类下,可多选,也可不填:`sync` 同步、`upload` 资源上传、`editor` 编辑器、`whiteboard` 白板、`blog-template` 博客模板、`blog-publish` 博客发布、`ai` AI(Skill、MCP、聊天工具渲染、AI 功能、图片)、`voice` 语音、`video` 在线视频、`page` 页面(页面与侧边栏入口)。只提供命令或设置的插件可以不填。
|
|
29
|
+
- `keywords` 最多 5 个、每个不超过 20 字,只用于插件中心搜索,例如 S3 插件可写 `MinIO`、`R2`。
|
|
30
|
+
2. 修改 `package.json` 的项目名称和版本。
|
|
31
|
+
3. 在 `src/index.tsx` 中实现功能,在 `src/styles.css` 中编写样式。
|
|
32
|
+
4. 把 `README.md` 改成给用户看的介绍和使用步骤;需要版本记录时再加 `CHANGELOG.md`。
|
|
33
|
+
5. 执行 `pnpm build`,按下文的方法安装到 Tau 中测试。
|
|
34
|
+
|
|
35
|
+
| 文件 | 用途 |
|
|
36
|
+
| --------------------------------- | ---------------------------------------------------- |
|
|
37
|
+
| `manifest.json` | 插件身份、版本和最低客户端版本 |
|
|
38
|
+
| `README.md` / `CHANGELOG.md` | 随插件包发布,显示在插件详情的「说明」和「更新日志」 |
|
|
39
|
+
| `src/index.tsx` | 插件入口,以及示例页面、设置与命令 |
|
|
40
|
+
| `src/styles.css` | 插件样式 |
|
|
41
|
+
| `src/CounterIcon.tsx` | 侧边栏 SVG 图标示例,使用 currentColor 跟随主题 |
|
|
42
|
+
| `API.md` | 命令、页面、侧边栏、设置、存储、网络等接口 |
|
|
43
|
+
| `EDITOR.md` | 在文档中添加自定义块、源码预览块和插件 Skill |
|
|
44
|
+
| `AI.md` | AI 功能声明、用户模型分配、流式对话和统一用量审计 |
|
|
45
|
+
| `MCP.md` | 注册 MCP、聊天工具渲染,复用工具开关与服务器管理 |
|
|
46
|
+
| `WHITEBOARD.md` | 静态白板扩展、工具与发布边界 |
|
|
47
|
+
| `SYNC.md` | 同步存储提供方、配置视图与取消 |
|
|
48
|
+
| `UPLOAD.md` | 图片与附件上传、上传提供方与配置 |
|
|
49
|
+
| `TTS.md` | 语音合成、音色复刻与多账号配置 |
|
|
50
|
+
| `IMAGES.md` | 图片生成、参考图编辑与插件账号配置视图 |
|
|
51
|
+
| `VIDEO.md` | 视频来源的链接解析、播放源与账号配置视图 |
|
|
52
|
+
| `PUBLISH.md` | 博客发布提供方、文件上传与 Git 快照 |
|
|
53
|
+
| `PUBLISH-DATA.md` | 独立站点读取发布数据与公开子入口的构建边界 |
|
|
54
|
+
| `SERVICES.md` | Tailwind、Typst、Markdown、FFmpeg 编译转换服务 |
|
|
55
|
+
| `AGENTS.md` | 给 AI 编程助手的项目指引 |
|
|
56
|
+
| `build.mjs` / `install-local.mjs` | 打包与本地安装脚本,日常开发通常不需要修改 |
|
|
57
|
+
|
|
58
|
+
插件详情页的名称、简介和版本取自 `manifest.json`,介绍取自 `README.md`,无需在网站上另外填写。`API.md`、`AGENTS.md` 等开发文档不会打进插件包。
|
|
59
|
+
|
|
60
|
+
## 升级 SDK 与同步旧模板
|
|
61
|
+
|
|
62
|
+
GitHub 模板创建的仓库不会自动跟随模板更新。已有项目先提交或备份自己的改动,再按需同步 [上游模板](https://github.com/LastKnightCoder/tau-plugin-template) 的构建脚本、开发文档和 `.github/workflows/release.yml`,保留自己的插件 ID、入口、用户说明与版本记录。
|
|
63
|
+
|
|
64
|
+
先在 `pnpm-workspace.yaml` 的 `minimumReleaseAgeExclude` 中增加新 SDK 的精确例外 `@xiongtao.xt/tau-plugins@0.23.0`,暂时保留旧锁文件所需的已核实例外,再更新依赖及锁文件:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
pnpm add @tau/plugins@npm:@xiongtao.xt/tau-plugins@0.23.0 --save-exact
|
|
68
|
+
pnpm install --frozen-lockfile
|
|
69
|
+
pnpm build
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
成功后移除已不再使用的旧版例外,再运行一次 `pnpm install --frozen-lockfile`,提交 `package.json`、`pnpm-lock.yaml` 和 `pnpm-workspace.yaml`。先移除旧版例外会使 pnpm 在读取旧锁文件时拒绝升级。发布等待例外只针对已核实的 SDK/UI 版本,不关闭全部依赖的供应链策略;保留 `allowBuilds.esbuild: true`。React 继续使用模板锁定的版本,避免和宿主的共享实例不兼容。
|
|
73
|
+
|
|
74
|
+
从旧 SDK 升级时,重点核对:
|
|
75
|
+
|
|
76
|
+
| 版本 | 变化与对应文档 |
|
|
77
|
+
| ------------- | ---------------------------------------------------------------------------- |
|
|
78
|
+
| 0.17.0 | 行内源码接口,见 EDITOR.md |
|
|
79
|
+
| 0.18.0 | 图片生成与编辑提供方,见 IMAGES.md |
|
|
80
|
+
| 0.19.0–0.20.0 | 发布数据子入口、站点级数据归清单、分类与关键词,见 PUBLISH-DATA.md 与 API.md |
|
|
81
|
+
| 0.21.0 | 博客发布提供方、配置与 Git 快照服务,见 PUBLISH.md |
|
|
82
|
+
| 0.22.0 | 视频提供方与视频页面信息查询,见 VIDEO.md |
|
|
83
|
+
| 0.23.0 | 删除 `ffmpeg.probe` 与 `MediaProbeResult`;`ffmpeg.run` 保留,见 SERVICES.md |
|
|
84
|
+
|
|
85
|
+
更早版本的完整变更见已安装包的 `node_modules/@tau/plugins/CHANGELOG.md`。需要本地媒体元数据时自行实现,不能把视频页面解析当作本地文件探测。SDK 根入口、白板/同步契约、UI 和 React 由 Tau 提供,保持模板的 external 配置;普通第三方库由构建打入 ZIP。独立站点数据子入口的用途另见 PUBLISH-DATA.md。
|
|
86
|
+
|
|
87
|
+
构建通过后,在对应客户端验证启用、功能执行、保存、停用与重启;涉及静态编辑器或白板扩展时也验证重启后的贡献,以及发布内容的回退。构建成功只证明类型和打包通过。
|
|
88
|
+
|
|
89
|
+
## 打包与安装
|
|
90
|
+
|
|
91
|
+
`pnpm build` 先检查类型,再生成 `dist/<id>-<version>.zip`。这个 ZIP 就是完整的插件包,分享时不需要附带源码或 node_modules。
|
|
92
|
+
|
|
93
|
+
**开发调试首次安装**:`pnpm install:local` 把 ZIP 解压到 `~/.editor/plugins/<id>/`。正式用户从插件中心安装;作者也可以手动解压调试,最终结构应为:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
~/.editor/plugins/yourname.counter/
|
|
97
|
+
manifest.json
|
|
98
|
+
main.js
|
|
99
|
+
main.css # 使用 CSS 时生成
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**更新**:提高 `manifest.json` 的 `version` 并重新构建,正式版本提交插件中心审核后由用户更新;开发版客户端可从插件页的 ZIP 入口调试。新版本在下次完全退出并重新启动后生效,生效前可以「取消待更新」;新版本启用失败时可以「恢复上一版」。恢复只换回代码,不回退已保存的数据,所以存储结构变化时要让新旧版本都能读懂。
|
|
103
|
+
|
|
104
|
+
`pnpm install:local` 只用于首次安装,目标目录已存在时会拒绝覆盖。插件 ID 不变,已保存的数据就会保留;改了 ID 会被当作另一个插件。
|
|
105
|
+
|
|
106
|
+
## 写更新日志
|
|
107
|
+
|
|
108
|
+
`CHANGELOG.md` 显示在插件详情的「更新日志」里,发布时也会作为 GitHub Release 的说明,读者是使用插件的人,不是开发者。
|
|
109
|
+
|
|
110
|
+
- 每个版本以 `## 1.2.0` 开头,下面逐条写;发布时取与 tag 版本相同的小节,找不到时才用 GitHub 自动生成的提交列表。
|
|
111
|
+
- 写用户能感知的变化:新增了什么能力、修好了什么问题、升级后要注意什么。用界面上的叫法,比如「设置 → 同步」。
|
|
112
|
+
- 不写 SDK 版本、构建方式、仓库迁移、测试或内部字段。确实没有可感知的变化,就写「维护更新,使用上没有变化」。
|
|
113
|
+
|
|
114
|
+
```markdown
|
|
115
|
+
## 1.2.0
|
|
116
|
+
|
|
117
|
+
- 书架可以按「有笔记」筛选了。
|
|
118
|
+
- 修复同步中途断网后,部分划线不显示的问题。
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## 自动发布到 GitHub Release
|
|
122
|
+
|
|
123
|
+
模板内置 `.github/workflows/release.yml`。将版本 tag 推送到 GitHub 后,Actions 会冻结安装 pnpm 依赖、运行项目的 test 脚本(若已定义)、执行构建,再把 `dist/<id>-<version>.zip` 上传到对应 Release。
|
|
124
|
+
|
|
125
|
+
先同步修改 `manifest.json` 和 `package.json` 的版本并提交,再打 tag:
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
git add manifest.json package.json
|
|
129
|
+
git commit -m "chore: release 1.1.0"
|
|
130
|
+
git tag v1.1.0
|
|
131
|
+
git push origin HEAD
|
|
132
|
+
git push origin v1.1.0
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
tag 支持 `v1.1.0` 或 `1.1.0`,必须与两个文件的版本一致,否则发布失败。`v1.1.0-beta.1` 等预发布版本会自动标为 Pre-release。只在本机打 tag 不触发,必须推送到 GitHub。
|
|
136
|
+
|
|
137
|
+
无需配置额外密钥,工作流使用仓库自带的 GITHUB_TOKEN。仓库需启用 Actions,并允许工作流申请 contents:write 权限。发布结果和失败日志在 Actions 页面查看;修复临时错误后可重新运行失败任务,已有 Release 会更新同名 ZIP。代码修复应提高版本并发布新 tag,不移动已发布的版本 tag。
|
|
138
|
+
|
|
139
|
+
Release 的说明取自 `CHANGELOG.md` 中对应版本的小节。Release 发布后,工作流会把同一个 ZIP 提交到插件中心:用 GitHub Actions 的短期身份令牌证明发布来自哪个仓库,不需要配置任何密钥。
|
|
140
|
+
|
|
141
|
+
- **首次发布**仍需在 [插件中心](https://plugins.taunote.app) 网页上传并提交一次,用来把插件 ID 绑定到你的仓库。未绑定时这一步只给出提醒,不让工作流失败。
|
|
142
|
+
- **之后每次推送 tag**,新版本自动进入审核队列,不用再打开网页;插件 ID 只接受来自已绑定仓库的提交,fork 无法冒充。
|
|
143
|
+
- 预发布版本(如 `v1.1.0-beta.1`)只发 GitHub Release,不提交插件中心。
|
|
144
|
+
- 同一 tag 重跑是安全的;同一版本号换了内容会被拒绝,需要提高版本号。
|
|
145
|
+
|
|
146
|
+
从旧模板创建的仓库不会自动获得新文件,需要同步该 workflow(包括 `id-token: write` 权限)。此流程不发布 npm 包。
|
|
147
|
+
|
|
148
|
+
## 常用命令
|
|
149
|
+
|
|
150
|
+
| 命令 | 用途 |
|
|
151
|
+
| -------------------- | ---------------------- |
|
|
152
|
+
| `pnpm check` | 检查 TypeScript 类型 |
|
|
153
|
+
| `pnpm build` | 检查类型并生成 ZIP |
|
|
154
|
+
| `pnpm install:local` | 首次安装最近构建的 ZIP |
|
|
155
|
+
|
|
156
|
+
## 排错
|
|
157
|
+
|
|
158
|
+
- **列表里没有插件**:点「刷新列表」,确认解压后没有多套一层目录,再看插件管理里的错误提示。
|
|
159
|
+
- **提示目标已存在**:提高插件版本后,在开发版客户端使用 ZIP 更新;正式用户使用插件中心更新。
|
|
160
|
+
- **修改没有生效**:重新构建并安装,然后刷新列表并启用。
|
|
161
|
+
- **启用失败**:查看错误提示,检查 `activate` 是否抛出异常;客户端提示需要重启时先重启再启用。
|
|
162
|
+
- **类型检查通过但功能异常**:类型检查不能代替实际测试,至少在 Tau 中试一遍启用、执行命令、保存数据、停用和重启。
|
|
163
|
+
|
|
164
|
+
本模板采用 [MIT 许可](LICENSE)。
|
package/PUBLISH-DATA.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# 独立站点的数据接入
|
|
2
2
|
|
|
3
|
+
自 SDK 0.19.0 提供以下公开子入口;本文的数据结构对应 SDK 0.23.0(含 0.20.0 的站点数据调整)。这些入口用于独立站点,和 [PUBLISH.md](PUBLISH.md) 中向远端上传文件的发布提供方是两种用途。
|
|
4
|
+
|
|
3
5
|
普通 Tau 插件仍然使用 SDK 类型开发,构建时将宿主注入模块设为 external。SDK 根入口和 UI 包不携带编辑器、组件或页面的运行实现。
|
|
4
6
|
|
|
5
7
|
博客模板一般直接使用 `@tau/blog`:它包含数据读取、搜索、正文渲染和内容插件接入,见该包 README。只想读取数据、自己渲染正文的站点,可使用 `@tau/plugins/publish-data` 的纯数据类型和 `@tau/plugins/publish-client` 的数据客户端。客户端只有数据读取、摘要校验、取消和搜索,不包含 React、渲染器、路由或预览界面。
|
|
@@ -9,7 +11,11 @@ import { createClient } from "@tau/plugins/publish-client";
|
|
|
9
11
|
|
|
10
12
|
const client = createClient({ manifestUrl: "https://example.com/data/site.<摘要>.json" });
|
|
11
13
|
const manifest = await client.readManifest();
|
|
12
|
-
const
|
|
14
|
+
const item = manifest.items[0];
|
|
15
|
+
if (item) {
|
|
16
|
+
const result = await client.readData({ key: item.key });
|
|
17
|
+
console.log(result);
|
|
18
|
+
}
|
|
13
19
|
```
|
|
14
20
|
|
|
15
21
|
## 数据分布
|
|
@@ -27,3 +33,15 @@ const result = await client.readData({ key: manifest.items[0].key });
|
|
|
27
33
|
## 插件产物
|
|
28
34
|
|
|
29
35
|
`@tau/plugins/publish-artifacts` 提供 C23 清单校验、定义求值与节点归属检查,仅处理协议,不提供 UI、模块映射或资源生命周期。共享模块由模板通过实际公开能力提供;`@tau/blog` 已内置这部分接入。
|
|
36
|
+
|
|
37
|
+
## 构建边界
|
|
38
|
+
|
|
39
|
+
| 入口 | 用途 | 独立站点构建 |
|
|
40
|
+
| ---------------------------------------------- | ----------------------------- | -------------------------------- |
|
|
41
|
+
| `@tau/plugins`、`@tau/ui` | Tau 注入的插件 API 与界面组件 | 只有类型;不能当作站点运行时打包 |
|
|
42
|
+
| `@tau/plugins/whiteboard`、`@tau/plugins/sync` | 白板与同步类型契约 | 使用 `import type` |
|
|
43
|
+
| `@tau/plugins/publish-data` | 发布数据类型 | 使用 `import type` |
|
|
44
|
+
| `@tau/plugins/publish-client` | 数据读取、摘要校验与搜索 | 有 ESM 实现,可以打包进独立站点 |
|
|
45
|
+
| `@tau/plugins/publish-artifacts` | 插件发布产物协议处理 | 有 ESM 实现,可以打包进独立站点 |
|
|
46
|
+
|
|
47
|
+
本模板的 `build.mjs` 面向运行于 Tau 内的代码插件,生成 CJS ZIP;独立博客站点应使用自己的站点构建,不直接复用计数器入口或本地安装脚本。根入口在普通 Node 或浏览器执行时会报错,这是宿主注入约定。模板分发包与代码插件也不能在同一 manifest 中同时声明。
|
package/PUBLISH.md
CHANGED
|
@@ -6,6 +6,23 @@
|
|
|
6
6
|
|
|
7
7
|
两种实例都实现 `describe(signal)` 与 `checkConnection(signal)`;describe 返回 `identity`、`basePath`、可选 `publicUrl`。identity 是不含密钥的物理目标稳定身份,不随插件版本、模板、Token 或提交方式变化。basePath 以斜杠开头和结尾,根路径为 `/`。
|
|
8
8
|
|
|
9
|
+
`checkConnection` 返回 `Promise<boolean>`,只检查目标可访问性;网络或认证失败可以抛出可读错误。以下注册结构可放在插件的 `activate` 内,实际存储协议由 `createTarget` 实现:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import type { JsonValue, PluginContext, PublishInstance } from "@tau/plugins";
|
|
13
|
+
|
|
14
|
+
export function registerPublisher(
|
|
15
|
+
context: PluginContext,
|
|
16
|
+
createTarget: (config: JsonValue) => PublishInstance
|
|
17
|
+
) {
|
|
18
|
+
return context.publish.register({
|
|
19
|
+
id: context.pluginId,
|
|
20
|
+
name: "我的发布目标",
|
|
21
|
+
create: createTarget,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
9
26
|
文件式实例声明 `mode: "files"`,实现 `uploadFile(path, Uint8Array, signal)` 与 `deleteFile(path, signal)`,可用 `concurrency` 声明 1–32 的上限。方法成功返回 void,失败抛出异常。宿主负责增量、重试、依赖先行和入口后置、旧资源清理,不把远端目录当成可任意删除的文件清单。
|
|
10
27
|
|
|
11
28
|
快照式实例声明 `mode: "snapshot"`,实现 `publish(snapshot, signal)`。snapshot 包含本轮临时 id、待上传文件的路径和摘要及删除路径。只有远端确认整轮提交后才返回;失败抛异常,不返回部分成功。可选 dispose 在会话关闭时回收连接。
|
|
@@ -15,3 +32,17 @@ Git 快照使用 `context.services.git.publishSnapshot({ publicationId: snapshot
|
|
|
15
32
|
配置视图使用 `context.settings.register({ id, title, target: "publish", component })`。组件接收 `PluginSettingsProps.publishConfig` 的 value 与 save,保存成功后再提交表单值,不用按工作区隔离的 context.storage 保存账号。普通插件详情页提供前往博客发布页的入口。
|
|
16
33
|
|
|
17
34
|
离开发布页继续运行;停用、切库和发起窗口关闭使旧会话失效。每次请求都传递 signal,错误信息不得包含凭据。新安装默认停用,插件缺失或未启用时宿主保留用户配置及选择,不自动切换其他目标。
|
|
35
|
+
|
|
36
|
+
## 配置与错误处理
|
|
37
|
+
|
|
38
|
+
`publishConfig` 是可选属性,组件先检查它是否存在,再读取 `value: JsonValue` 并校验字段。`await publishConfig.save(value)` 成功后再提示;失败保留当前输入。配置随用户选择的提供方保存,不将 Token 写入 `identity`、公开 URL、README 或日志。
|
|
39
|
+
|
|
40
|
+
文件路径由宿主给出,是发布站点内的相对路径。适配具体远端接口时保留路径语义并正确编码,不拼成 shell 命令。使用 `context.fetch` 时检查 `response.ok`,每次请求都传入本轮 signal;不吞掉异常后返回成功,否则宿主会误记为已发布。删除操作应能处理文件已经不存在的重试。
|
|
41
|
+
|
|
42
|
+
`selectCacheDirectory` 在用户取消时返回 `null`,不要把它当成失败或自行构造缓存 ID。`create(config)` 同步返回实例,连接和发布任务放入对应异步方法,不在创建实例时发起后台请求。
|
|
43
|
+
|
|
44
|
+
## 验证
|
|
45
|
+
|
|
46
|
+
至少覆盖:配置保存失败、连接失败、重复发布、远端文件已存在或已删除、用户取消、插件停用、切换工作区,以及快照提交中途失败后重试。用真实远端验证成功之后,才能声称支持该发布目标;SDK 类型检查不验证远端协议。
|
|
47
|
+
|
|
48
|
+
发布目标插件负责把文件送到远端,独立站点读取数据与模板构建见 [PUBLISH-DATA.md](PUBLISH-DATA.md)。
|
package/SERVICES.md
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
# 编译与转换服务
|
|
2
2
|
|
|
3
|
-
需要在插件里编译 Tailwind、Typst,转换 Markdown,或处理音视频时,可以使用这些服务。通过 `context.services` 调用,类型从 `@tau/plugins` 导入,需要 Tau 0.59.0
|
|
3
|
+
需要在插件里编译 Tailwind、Typst,转换 Markdown,或处理音视频时,可以使用这些服务。通过 `context.services` 调用,类型从 `@tau/plugins` 导入,需要 Tau 0.59.0 或更高版本及实现相应能力的客户端构建。本文对应 SDK 0.23.0。TikZ 编译属于独立绘图插件,不再提供宿主服务。
|
|
4
4
|
|
|
5
5
|
所有服务都是异步方法,只处理传入数据并返回结果,不读写笔记、不选择或保存文件、不自动注入样式。
|
|
6
6
|
|
|
7
7
|
## API 一览
|
|
8
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
|
-
| `ffmpeg.
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
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
|
+
| `ffmpeg.run(input, options?)` | `{ files, args, outputs }` | `{ files, log }` |
|
|
17
|
+
| `videoExtractor.probe(url, options?)` | 视频页面地址,可选 `proxy`(SDK 0.22.0) | `{ title, cover?, duration?, formats }` |
|
|
18
|
+
| `git.publishSnapshot(input, options?)` | 当前发布快照、HTTPS 仓库、分支与认证 | `void` |
|
|
19
|
+
| `git.selectCacheDirectory({}, options?)` | 打开宿主缓存目录选择器 | `{ id, label } \| null` |
|
|
20
|
+
|
|
21
|
+
Git 服务自 SDK 0.21.0 提供,完整参数与发布会话约束见 [PUBLISH.md](PUBLISH.md)。视频解析与播放源协议见 [VIDEO.md](VIDEO.md)。
|
|
19
22
|
|
|
20
23
|
## 在静态组件中获取服务
|
|
21
24
|
|
|
@@ -107,14 +110,12 @@ HTML 渲染不自动执行 TikZ、Typst、Mermaid 编译器;相关节点可能
|
|
|
107
110
|
|
|
108
111
|
## FFmpeg
|
|
109
112
|
|
|
113
|
+
SDK 0.23.0 已移除 `ffmpeg.probe` 和 `MediaProbeResult`,客户端不再随包分发 FFprobe。已有插件应删除对应调用与类型依赖;需要本地媒体元数据时由插件自行实现。`videoExtractor.probe` 只查询视频页面,不能接收本地文件。
|
|
114
|
+
|
|
110
115
|
`ServiceFile` 为 `{ name: string, data: Blob }`;文件名仅在任务内使用,不是客户端路径。输入输出名称不得重复,目录使用 `/`。文件整体经内存传输,适合可以放进内存的素材,不支持大文件流式处理。
|
|
111
116
|
|
|
112
117
|
```ts
|
|
113
118
|
const input = { name: "input.wav", data: audioBlob };
|
|
114
|
-
const info = await context.services.ffmpeg.probe(input);
|
|
115
|
-
// format: name、duration(秒或 null)、size(字节或 null)。
|
|
116
|
-
// streams: index、type、codec,可选 width/height/sampleRate/channels。
|
|
117
|
-
|
|
118
119
|
const result = await context.services.ffmpeg.run(
|
|
119
120
|
{
|
|
120
121
|
files: [input],
|
package/VIDEO.md
CHANGED
|
@@ -38,3 +38,28 @@
|
|
|
38
38
|
账号、Cookie 或代理通过 `context.settings.register({ id, title, target: "video", component })` 注册配置视图,挂载在「设置 · 账号与连接 · 在线视频」。组件接收 `PluginSettingsProps.videoConfig`:`value` 和 `save(value)`,每个插件一份配置,保存后下一次调用 `create` 即读到新值。保存失败时保留草稿,成功后才提示已保存。
|
|
39
39
|
|
|
40
40
|
插件停用或调用方取消时,进行中的请求会收到取消信号,迟到的结果被丢弃。插件未启用时,已保存的视频显示需要启用插件的提示,原数据保留。移动端和发布站点不运行插件,插件提供的视频在那里只显示说明。
|
|
41
|
+
|
|
42
|
+
## 注册与验证
|
|
43
|
+
|
|
44
|
+
注册放在 `activate` 中,实例的四个方法由来源适配实现,并使用 SDK 类型检查返回值:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import type { JsonValue, PluginContext, VideoInstance } from "@tau/plugins";
|
|
48
|
+
|
|
49
|
+
export function registerVideoSource(
|
|
50
|
+
context: PluginContext,
|
|
51
|
+
createSource: (config: JsonValue) => VideoInstance
|
|
52
|
+
) {
|
|
53
|
+
return context.video.register({
|
|
54
|
+
id: context.pluginId,
|
|
55
|
+
name: "我的视频来源",
|
|
56
|
+
create: createSource,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
在 manifest 中声明 `categories: ["video"]`,便于用户从插件中心筛选;分类本身不注册提供方。解析输入、网络响应与持久化的 ref 都要校验;`defaultVariant` 是从 0 开始的索引,缺省时宿主使用第一项。不要把 `cacheKey` 简化为本次的签名直链,账号刷新或直链过期不应改变同一视频清晰度的缓存身份。
|
|
62
|
+
|
|
63
|
+
验证应覆盖:来源不匹配、格式为空、链接过期后重新取源、音视频分离、已缓存时离线播放、账号更新、取消与停用。单文件流须能由宿主直接下载;需要视频解析器处理的页面使用 `kind: "extract"`。具体网站的限制与认证失败由插件返回可读错误,不返回虚假的可播放结果。
|
|
64
|
+
|
|
65
|
+
SDK 0.23.0 删除的是 `services.ffmpeg.probe` 本地媒体探测,`services.videoExtractor.probe` 保留;区别与 FFmpeg 转换接口见 [SERVICES.md](SERVICES.md)。
|
package/package.json
CHANGED
|
@@ -23,22 +23,6 @@ export interface MediaProgress {
|
|
|
23
23
|
/** 已处理的媒体时间,单位秒;不推测总时长或百分比。 */
|
|
24
24
|
time: number;
|
|
25
25
|
}
|
|
26
|
-
export interface MediaInfo {
|
|
27
|
-
format: {
|
|
28
|
-
name: string;
|
|
29
|
-
duration: number | null;
|
|
30
|
-
size: number | null;
|
|
31
|
-
};
|
|
32
|
-
streams: {
|
|
33
|
-
index: number;
|
|
34
|
-
type: string;
|
|
35
|
-
codec: string;
|
|
36
|
-
width?: number;
|
|
37
|
-
height?: number;
|
|
38
|
-
sampleRate?: number;
|
|
39
|
-
channels?: number;
|
|
40
|
-
}[];
|
|
41
|
-
}
|
|
42
26
|
export interface VideoFormat {
|
|
43
27
|
id: string;
|
|
44
28
|
/** 只含视频或只含音频的流;两者都为 false 时是音视频合一的流。 */
|
|
@@ -100,7 +84,6 @@ export interface PluginServices {
|
|
|
100
84
|
}>;
|
|
101
85
|
};
|
|
102
86
|
ffmpeg: {
|
|
103
|
-
probe(input: ServiceFile, options?: ServiceOptions): Promise<MediaInfo>;
|
|
104
87
|
/** args 中用任务文件名引用输入和输出;不接受 shell 命令。 */
|
|
105
88
|
run(input: {
|
|
106
89
|
files: ServiceFile[];
|