@xiongtao.xt/tau-plugins 0.29.0 → 0.31.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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 LastKnightCoder
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LastKnightCoder
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTIFICATIONS.md CHANGED
@@ -1,48 +1,48 @@
1
- # 插件通知能力
2
-
3
- SDK 0.28.0 与包含此接口的客户端通过激活上下文提供 `context.notifications`,旧客户端可先检查该属性是否存在。本接口属于渲染端 `PluginContext`,独立后台可经既有后台事件通知渲染插件,再由渲染插件调用;应用退出后不会继续发送。
4
-
5
- ```ts
6
- const notification = context.notifications.send({
7
- title: "同步完成",
8
- body: "本次更新了 12 条内容。",
9
- mode: "auto",
10
- tone: "ok",
11
- });
12
-
13
- // 需要提前撤销时调用;停用或切库时宿主也会自动回收。
14
- await notification.dispose();
15
- ```
16
-
17
- | mode | 行为 |
18
- | -------------- | ---------------------------------------------------------- |
19
- | `auto`(默认) | 插件所在窗口聚焦时进入应用内通知栈,失焦时进入独立通知窗口 |
20
- | `in-app` | 固定进入插件所在窗口的通知栈,不主动聚焦窗口 |
21
- | `system` | 固定进入 Tau 独立通知窗口,不使用操作系统原生通知 |
22
-
23
- 分流发生在发送时;发送后切换窗口焦点不会搬移通知。Web 预览没有独立窗口,沿平台既有规则退化为应用内展示。
24
-
25
- 标题必填,最多 200 字符;正文可选,最多 5000 字符。`tone` 支持 `neutral`、`ok`、`danger`,默认 `neutral`。`sticky: true` 保持显示直到用户处理、关闭或插件撤销。来源标签固定为插件名称,不接受插件伪造来源或指定全局通知 ID。
26
-
27
- 可传 `actions`(最多两个按钮,最多一个 `primary: true`):
28
-
29
- ```ts
30
- context.notifications.send({
31
- title: "扫描完成",
32
- body: "点击查看结果。",
33
- sticky: true,
34
- actions: [
35
- {
36
- label: "查看结果",
37
- primary: true,
38
- onClick: async () => {
39
- await resultsPage.open();
40
- },
41
- },
42
- ],
43
- });
44
- ```
45
-
46
- 按钮回调返回 Promise 时显示执行中状态,失败在通知内展示;可用 `resolvedText` 指定成功后的结果文案。独立通知窗口通过现有桥接在来源窗口执行回调。通知撤销或插件失活后,旧回调拒绝执行;已经开始的异步业务仍应遵循 `context.signal`。
47
-
48
- 发送不需要业务数据权限或即时用户手势,适用于异步任务完成提示。`send` 返回的是已提交给通知路由的句柄,不是用户已看到通知的回执;主进程不可用时沿 `appNotify` 的既有策略放弃推送。
1
+ # 插件通知能力
2
+
3
+ SDK 0.28.0 与包含此接口的客户端通过激活上下文提供 `context.notifications`,旧客户端可先检查该属性是否存在。本接口属于渲染端 `PluginContext`,独立后台可经既有后台事件通知渲染插件,再由渲染插件调用;应用退出后不会继续发送。
4
+
5
+ ```ts
6
+ const notification = context.notifications.send({
7
+ title: "同步完成",
8
+ body: "本次更新了 12 条内容。",
9
+ mode: "auto",
10
+ tone: "ok",
11
+ });
12
+
13
+ // 需要提前撤销时调用;停用或切库时宿主也会自动回收。
14
+ await notification.dispose();
15
+ ```
16
+
17
+ | mode | 行为 |
18
+ | -------------- | ---------------------------------------------------------- |
19
+ | `auto`(默认) | 插件所在窗口聚焦时进入应用内通知栈,失焦时进入独立通知窗口 |
20
+ | `in-app` | 固定进入插件所在窗口的通知栈,不主动聚焦窗口 |
21
+ | `system` | 固定进入 Tau 独立通知窗口,不使用操作系统原生通知 |
22
+
23
+ 分流发生在发送时;发送后切换窗口焦点不会搬移通知。Web 预览没有独立窗口,沿平台既有规则退化为应用内展示。
24
+
25
+ 标题必填,最多 200 字符;正文可选,最多 5000 字符。`tone` 支持 `neutral`、`ok`、`danger`,默认 `neutral`。`sticky: true` 保持显示直到用户处理、关闭或插件撤销。来源标签固定为插件名称,不接受插件伪造来源或指定全局通知 ID。
26
+
27
+ 可传 `actions`(最多两个按钮,最多一个 `primary: true`):
28
+
29
+ ```ts
30
+ context.notifications.send({
31
+ title: "扫描完成",
32
+ body: "点击查看结果。",
33
+ sticky: true,
34
+ actions: [
35
+ {
36
+ label: "查看结果",
37
+ primary: true,
38
+ onClick: async () => {
39
+ await resultsPage.open();
40
+ },
41
+ },
42
+ ],
43
+ });
44
+ ```
45
+
46
+ 按钮回调返回 Promise 时显示执行中状态,失败在通知内展示;可用 `resolvedText` 指定成功后的结果文案。独立通知窗口通过现有桥接在来源窗口执行回调。通知撤销或插件失活后,旧回调拒绝执行;已经开始的异步业务仍应遵循 `context.signal`。
47
+
48
+ 发送不需要业务数据权限或即时用户手势,适用于异步任务完成提示。`send` 返回的是已提交给通知路由的句柄,不是用户已看到通知的回执;主进程不可用时沿 `appNotify` 的既有策略放弃推送。
package/PUBLISH-DATA.md CHANGED
@@ -1,47 +1,48 @@
1
- # 独立站点的数据接入
2
-
3
- 自 SDK 0.19.0 提供以下公开子入口;本文的数据结构对应 SDK 0.23.0(含 0.20.0 的站点数据调整)。这些入口用于独立站点,和 [PUBLISH.md](PUBLISH.md) 中向远端上传文件的发布提供方是两种用途。
4
-
5
- 普通 Tau 插件仍然使用 SDK 类型开发,构建时将宿主注入模块设为 external。SDK 根入口和 UI 包不携带编辑器、组件或页面的运行实现。
6
-
7
- 博客模板一般直接使用 `@tau/blog`:它包含数据读取、搜索、正文渲染和内容插件接入,见该包 README。只想读取数据、自己渲染正文的站点,可使用 `@tau/plugins/publish-data` 的纯数据类型和 `@tau/plugins/publish-client` 的数据客户端。客户端只有数据读取、摘要校验、取消和搜索,不包含 React、渲染器、路由或预览界面。
8
-
9
- ```ts
10
- import { createClient } from "@tau/plugins/publish-client";
11
-
12
- const client = createClient({ manifestUrl: "https://example.com/data/site.<摘要>.json" });
13
- const manifest = await client.readManifest();
14
- const item = manifest.items[0];
15
- if (item) {
16
- const result = await client.readData({ key: item.key });
17
- console.log(result);
18
- }
19
- ```
20
-
21
- ## 数据分布
22
-
23
- - 清单 `site.<摘要>.json`:站点信息、全部内容的 key 与数据文件、搜索索引引用,以及只出现一次的站点级数据——`settings`(发布外观设置)、`collections`(项目列表)、`cardTagTree`(卡片标签树)、`projectTrees`(按项目 id 的文档目录)。`readData` 会把 `settings` 一并带回。
24
- - 内容文件:`key`、`meta`、`content`,以及可选的 `links`(本文正文里已发布的站内链接目标)、`pluginRenderers`(内容插件产物)和 `contentStyles`(正文动态样式)。资源以所属数据文件的 URL 为上下文解析。
25
- - 公开路由完全由模板决定,数据里只有 ContentRef(`{ key, type, id? }`),没有 URL。
26
-
27
- 正文的站内链接由 `@tau/blog` 按 `links` 渲染,模板只提供 `hrefFor`。自己渲染正文的站点按 `links` 的键(`${contentType}:${refId}`,与求索资料的 `citeKey` 同一口径)查目标,查不到即目标未发布。
28
-
29
- ## 样式
30
-
31
- `contentStyles` 是宿主为正文里的 HTML 节点编译的 Tailwind 工具类,属于过渡产物:按原文加载,含 `:root` 主题变量和全局类名,正文挂载期间对整个页面生效。模板若自定义了同名 Tailwind 主题变量,正文挂载时可能被这些默认值覆盖。
32
-
33
- ## 插件产物
34
-
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 中同时声明。
1
+ # 独立站点的数据接入
2
+
3
+ 自 SDK 0.19.0 提供以下公开子入口;本文的数据结构对应 SDK 0.23.0(含 0.20.0 的站点数据调整)。这些入口用于独立站点,和 [PUBLISH.md](PUBLISH.md) 中向远端上传文件的发布提供方是两种用途。
4
+
5
+ 普通 Tau 插件仍然使用 SDK 类型开发,构建时将宿主注入模块设为 external。SDK 根入口和 UI 包不携带编辑器、组件或页面的运行实现。
6
+
7
+ 博客模板一般直接使用 `@tau/blog`:它包含数据读取、搜索、正文渲染和内容插件接入,见该包 README。只想读取数据、自己渲染正文的站点,可使用 `@tau/plugins/publish-data` 的纯数据类型和 `@tau/plugins/publish-client` 的数据客户端。客户端只有数据读取、摘要校验、取消和搜索,不包含 React、渲染器、路由或预览界面。
8
+
9
+ ```ts
10
+ import { createClient } from "@tau/plugins/publish-client";
11
+
12
+ const client = createClient({ manifestUrl: "https://example.com/data/site.<摘要>.json" });
13
+ const manifest = await client.readManifest();
14
+ const item = manifest.items[0];
15
+ if (item) {
16
+ const result = await client.readData({ key: item.key });
17
+ console.log(result);
18
+ }
19
+ ```
20
+
21
+ ## 数据分布
22
+
23
+ - 清单 `site.<摘要>.json`:站点信息、全部内容的 key 与数据文件、搜索索引引用,以及只出现一次的站点级数据——`settings`(发布外观设置)、`collections`(项目列表)、`cardTagTree`(卡片标签树)、`projectTrees`(按项目 id 的文档目录)。`readData` 会把 `settings` 一并带回。
24
+ - 站点信息 `site`:宿主提供 `title`、`basePath`、可选的 `publicUrl` 与 `favicon`;其余站点配置(描述、副标题、布局开关等)由模板在 manifest 的 `template.siteFields` 中声明,用户在「站点设置 · 博客模板」填写,发布时以 `site.fields[key]` 交给模板。`fields` 只包含已声明的键,未填写时取声明的 `default`;没有默认值时文本类为空字符串、开关为 `false`、下拉为第一个选项。字段类型为 `text`、`textarea`、`url`、`select`、`switch`,最多 32 个。首页数据只含时间线与统计,介绍区由模板根据自己的字段渲染。
25
+ - 内容文件:`key`、`meta`、`content`,以及可选的 `links`(本文正文里已发布的站内链接目标)、`pluginRenderers`(内容插件产物)和 `contentStyles`(正文动态样式)。资源以所属数据文件的 URL 为上下文解析。
26
+ - 公开路由完全由模板决定,数据里只有 ContentRef(`{ key, type, id? }`),没有 URL。
27
+
28
+ 正文的站内链接由 `@tau/blog` 按 `links` 渲染,模板只提供 `hrefFor`。自己渲染正文的站点按 `links` 的键(`${contentType}:${refId}`,与求索资料的 `citeKey` 同一口径)查目标,查不到即目标未发布。
29
+
30
+ ## 样式
31
+
32
+ `contentStyles` 是宿主为正文里的 HTML 节点编译的 Tailwind 工具类,属于过渡产物:按原文加载,含 `:root` 主题变量和全局类名,正文挂载期间对整个页面生效。模板若自定义了同名 Tailwind 主题变量,正文挂载时可能被这些默认值覆盖。
33
+
34
+ ## 插件产物
35
+
36
+ `@tau/plugins/publish-artifacts` 提供 C23 清单校验、定义求值与节点归属检查,仅处理协议,不提供 UI、模块映射或资源生命周期。共享模块由模板通过实际公开能力提供;`@tau/blog` 已内置这部分接入。
37
+
38
+ ## 构建边界
39
+
40
+ | 入口 | 用途 | 独立站点构建 |
41
+ | ---------------------------------------------- | ----------------------------- | -------------------------------- |
42
+ | `@tau/plugins`、`@tau/ui` | Tau 注入的插件 API 与界面组件 | 只有类型;不能当作站点运行时打包 |
43
+ | `@tau/plugins/whiteboard`、`@tau/plugins/sync` | 白板与同步类型契约 | 使用 `import type` |
44
+ | `@tau/plugins/publish-data` | 发布数据类型 | 使用 `import type` |
45
+ | `@tau/plugins/publish-client` | 数据读取、摘要校验与搜索 | 有 ESM 实现,可以打包进独立站点 |
46
+ | `@tau/plugins/publish-artifacts` | 插件发布产物协议处理 | 有 ESM 实现,可以打包进独立站点 |
47
+
48
+ 本模板的 `build.mjs` 面向运行于 Tau 内的代码插件,生成 CJS ZIP;独立博客站点应使用自己的站点构建,不直接复用计数器入口或本地安装脚本。根入口在普通 Node 或浏览器执行时会报错,这是宿主注入约定。模板分发包与代码插件也不能在同一 manifest 中同时声明。
package/PUBLISH.md CHANGED
@@ -1,48 +1,50 @@
1
- # 博客发布提供方
2
-
3
- 需要 SDK 0.21.0 及包含博客发布能力的客户端。提供方只在渲染进程注册和执行,网络使用 `context.fetch`。本地导出由宿主提供,不注册本地发布插件。
4
-
5
- 在 activate 中调用 `context.publish.register({ id: context.pluginId, name, create(config) })`。create 返回文件式或快照式实例,配置是本轮固定的 JSON 副本。提供方 ID 必须等于所属插件 ID。
6
-
7
- 两种实例都实现 `describe(signal)` 与 `checkConnection(signal)`;describe 返回 `identity`、`basePath`、可选 `publicUrl`。identity 是不含密钥的物理目标稳定身份,不随插件版本、模板、Token 或提交方式变化。basePath 以斜杠开头和结尾,根路径为 `/`。
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
-
26
- 文件式实例声明 `mode: "files"`,实现 `uploadFile(path, Uint8Array, signal)` 与 `deleteFile(path, signal)`,可用 `concurrency` 声明 1–32 的上限。方法成功返回 void,失败抛出异常。宿主负责增量、重试、依赖先行和入口后置、旧资源清理,不把远端目录当成可任意删除的文件清单。
27
-
28
- 快照式实例声明 `mode: "snapshot"`,实现 `publish(snapshot, signal)`。snapshot 包含本轮临时 id、待上传文件的路径和摘要及删除路径。只有远端确认整轮提交后才返回;失败抛异常,不返回部分成功。可选 dispose 在会话关闭时回收连接。
29
-
30
- Git 快照使用 `context.services.git.publishSnapshot({ publicationId: snapshot.id, remote, branch, author: { name, email }, authentication: { username, password }, cacheId? }, { signal })`。remote 只接受不含凭据的 HTTPS 地址;认证通过任务通道使用,不写入 remote URL。publicationId 仅在所属窗口本轮发布中有效,不能用于读取任意本地文件。需要缓存目录时调用 `context.services.git.selectCacheDirectory({})`,由宿主选择并返回 `{ id, label }`;只保存 id 和展示用 label,不传入任意 cwd 或 shell 命令。
31
-
32
- 配置视图使用 `context.settings.register({ id, title, target: "publish", component })`。组件接收 `PluginSettingsProps.publishConfig` 的 value 与 save,保存成功后再提交表单值,不用按工作区隔离的 context.storage 保存账号。普通插件详情页提供前往博客发布页的入口。
33
-
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)。
1
+ # 博客发布提供方
2
+
3
+ 需要 SDK 0.21.0 及包含博客发布能力的客户端。提供方只在渲染进程注册和执行,网络使用 `context.fetch`。本地导出由宿主提供,不注册本地发布插件。
4
+
5
+ 在 activate 中调用 `context.publish.register({ id: context.pluginId, name, create(config) })`。create 返回文件式或快照式实例,配置是本轮固定的 JSON 副本。提供方 ID 必须等于所属插件 ID。
6
+
7
+ 两种实例都实现 `describe(signal)` 与 `checkConnection(signal)`;describe 返回 `identity`、`basePath`、可选 `publicUrl`。identity 是不含密钥的物理目标稳定身份,不随插件版本、模板、Token 或提交方式变化。basePath 以斜杠开头和结尾,根路径为 `/`。
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
+
26
+ 文件式实例声明 `mode: "files"`,实现 `uploadFile(path, Uint8Array, signal)` 与 `deleteFile(path, signal)`,可用 `concurrency` 声明 1–32 的上限。方法成功返回 void,失败抛出异常。宿主负责增量、重试、依赖先行和入口后置、旧资源清理,不把远端目录当成可任意删除的文件清单。
27
+
28
+ 快照式实例声明 `mode: "snapshot"`,实现 `publish(snapshot, signal)`。snapshot 包含本轮临时 id、待上传文件的路径和摘要及删除路径。只有远端确认整轮提交后才返回;失败抛异常,不返回部分成功。可选 dispose 在会话关闭时回收连接。
29
+
30
+ Git 快照使用 `context.services.git.publishSnapshot({ publicationId: snapshot.id, remote, branch, author: { name, email }, authentication: { username, password }, cacheId? }, { signal })`。remote 只接受不含凭据的 HTTPS 地址;认证通过任务通道使用,不写入 remote URL。publicationId 仅在所属窗口本轮发布中有效,不能用于读取任意本地文件。需要缓存目录时调用 `context.services.git.selectCacheDirectory({})`,由宿主选择并返回 `{ id, label }`;只保存 id 和展示用 label,不传入任意 cwd 或 shell 命令。
31
+
32
+ 配置视图使用 `context.settings.register({ id, title, target: "publish", component })`。组件接收 `PluginSettingsProps.publishConfig` 的 value 与 save,保存成功后再提交表单值,不用按工作区隔离的 context.storage 保存账号。普通插件详情页提供前往博客发布页的入口。
33
+
34
+ 配置视图渲染在宿主「站点设置 · 发布目标」组卡片内,位于宿主的连接状态条与「服务」行之后。表单按行输出:每行为左侧 64px 标签加右侧控件,`min-height: 46px`、`padding: 8px 12px`,每行顶部用 `--tau-line-2` 分隔,标签字号用 `--tau-text-meta`、颜色用 `--tau-ink-2`。不要再包一层卡片或标题。辅助动作(如「读取仓库」)放在对应输入框的尾部;低频项收进行内的「高级选项」折叠区。宿主负责测试连接和完成按钮,插件不提供「保存」或「测试连接」按钮,改为自动保存:输入停止约 600ms 或失焦时调用 `save`,组件卸载前保存未提交的修改。宿主回写的配置与最近一次提交值相同时,不要覆盖正在输入的表单。
35
+
36
+ 离开发布页继续运行;停用、切库和发起窗口关闭使旧会话失效。每次请求都传递 signal,错误信息不得包含凭据。新安装默认停用,插件缺失或未启用时宿主保留用户配置及选择,不自动切换其他目标。
37
+
38
+ ## 配置与错误处理
39
+
40
+ `publishConfig` 是可选属性,组件先检查它是否存在,再读取 `value: JsonValue` 并校验字段。`await publishConfig.save(value)` 失败时在表单末行显示错误并保留当前输入,下次修改后重试。配置随用户选择的提供方保存,不将 Token 写入 `identity`、公开 URL、README 或日志。
41
+
42
+ 文件路径由宿主给出,是发布站点内的相对路径。适配具体远端接口时保留路径语义并正确编码,不拼成 shell 命令。使用 `context.fetch` 时检查 `response.ok`,每次请求都传入本轮 signal;不吞掉异常后返回成功,否则宿主会误记为已发布。删除操作应能处理文件已经不存在的重试。
43
+
44
+ `selectCacheDirectory` 在用户取消时返回 `null`,不要把它当成失败或自行构造缓存 ID。`create(config)` 同步返回实例,连接和发布任务放入对应异步方法,不在创建实例时发起后台请求。
45
+
46
+ ## 验证
47
+
48
+ 至少覆盖:配置保存失败、连接失败、重复发布、远端文件已存在或已删除、用户取消、插件停用、切换工作区,以及快照提交中途失败后重试。用真实远端验证成功之后,才能声称支持该发布目标;SDK 类型检查不验证远端协议。
49
+
50
+ 发布目标插件负责把文件送到远端,独立站点读取数据与模板构建见 [PUBLISH-DATA.md](PUBLISH-DATA.md)。
package/SERVICES.md CHANGED
@@ -1,119 +1,119 @@
1
- # 编译与转换服务
2
-
3
- 需要在插件里编译 Tailwind,转换 Markdown,或处理音视频时,可以使用这些服务。通过 `context.services` 调用,类型从 `@tau/plugins` 导入,需要 Tau 0.59.0 或更高版本及实现相应能力的客户端构建。本文对应 SDK 0.24.0。TikZ 编译属于独立绘图插件,Typst 排版属于 `tau.typst` 插件(自带 WebAssembly 引擎),均不再提供宿主服务。
4
-
5
- 所有服务都是异步方法,只处理传入数据并返回结果,不读写笔记、不选择或保存文件、不自动注入样式。
6
-
7
- ## API 一览
8
-
9
- | 方法 | 输入 | 返回 |
10
- | ---------------------------------------- | ------------------------------------------------- | --------------------------------------- |
11
- | `tailwind.compile(input, options?)` | `{ source, language?: "html" \| "tsx" \| "jsx" }` | `{ css }` |
12
- | `markdown.deserialize(source, options?)` | Markdown 字符串 | `{ nodes: Descendant[], warnings }` |
13
- | `markdown.serialize(nodes, options?)` | `Descendant[]` | `{ markdown, warnings }` |
14
- | `markdown.render(source, options?)` | Markdown 字符串 | `{ html, css, warnings }` |
15
- | `ffmpeg.run(input, options?)` | `{ files, args, outputs }` | `{ files, log }` |
16
- | `videoExtractor.probe(url, options?)` | 视频页面地址,可选 `proxy`(SDK 0.22.0) | `{ title, cover?, duration?, formats }` |
17
- | `git.publishSnapshot(input, options?)` | 当前发布快照、HTTPS 仓库、分支与认证 | `void` |
18
- | `git.selectCacheDirectory({}, options?)` | 打开宿主缓存目录选择器 | `{ id, label } \| null` |
19
-
20
- Git 服务自 SDK 0.21.0 提供,完整参数与发布会话约束见 [PUBLISH.md](PUBLISH.md)。视频解析与播放源协议见 [VIDEO.md](VIDEO.md)。
21
-
22
- ## 在静态组件中获取服务
23
-
24
- SDK 0.16.0 的 `usePluginServices()` 返回当前插件可用的同一组服务,类型为 `PluginServices | undefined`。服务由插件运行时提供,独立编辑窗口无需 activate;博客和未包裹的普通组件返回 undefined,组件应显示已保存结果或源码。它不属于源码块或 EditorKit,不要将服务存入模块全局变量。
25
-
26
- 组件需用自己的 AbortSignal 取消过期或卸载的调用,宿主同时处理窗口与工作区失效。内容插件的启停按现有重启生效规则处理;写入启停意图不会提前拆掉当前窗口已生效的贡献。
27
-
28
- ## 取消与错误
29
-
30
- 第二个参数均支持 `{ signal: AbortSignal }`。插件停用会自动取消本实例的任务,不必重复传入 `context.signal`。用户主动取消可使用单次任务的 `AbortController`。
31
-
32
- ```ts
33
- const controller = new AbortController();
34
- try {
35
- const { css } = await context.services.tailwind.compile(
36
- { source: '<div class="p-4">Hello</div>', language: "html" },
37
- { signal: controller.signal }
38
- );
39
- // 将 css 用于插件自己的预览。
40
- } catch (error) {
41
- if (error instanceof Error && error.name === "AbortError") return;
42
- throw error;
43
- }
44
- // 对应取消按钮:controller.abort();
45
- ```
46
-
47
- 失败通过 Promise 拒绝返回 `Error`,`message` 包含可展示的错误信息;不要匹配内部错误文本决定业务流程。Markdown 同步解析/序列化只能在调用前后检查取消,不能中断正在执行的 JavaScript。HTML 转换内部已开始的渲染可能继续完成,但取消后不会交回结果。
48
-
49
- ## Tailwind
50
-
51
- ```ts
52
- const { css } = await context.services.tailwind.compile({
53
- source: '<section class="p-4 rounded-tau-md bg-surface text-ink-1">Hello</section>',
54
- language: "html",
55
- });
56
- ```
57
-
58
- 使用 Tailwind 4,支持 Tau 语义类。返回 theme/utilities,不重复提供 preflight。源码只用于提取完整类名,不执行 TSX/JSX,也不编译 React。动态拼接类名不能保证被提取。Tau 语义类依赖 Tau 提供的 `--tau-*` 变量,离开 Tau 的页面需要自行提供相应主题变量。
59
-
60
- 固定插件界面通常在打包时编译样式;此服务主要用于运行时生成内容。返回 CSS 由插件自行使用并在停用时清理。
61
-
62
- ## Markdown 与编辑器节点
63
-
64
- ```ts
65
- import type { Descendant } from "@tau/plugins";
66
-
67
- const parsed = await context.services.markdown.deserialize("# 标题\n\n* [x] 已完成");
68
- const nodes: Descendant[] = parsed.nodes;
69
- const result = await context.services.markdown.serialize(nodes);
70
- const preview = await context.services.markdown.render(result.markdown, { theme: "light" });
71
- ```
72
-
73
- 使用与 Tau 客户端一致的语法,包括标题、段落、列表和任务列表、表格、代码、数学公式、图片与媒体、Tau directive 等。`Descendant[]` 是数据,不关联某篇笔记,不写入编辑器。转换可能生成新的节点 UUID,不要依靠反复解析保留节点身份。
74
-
75
- 优先处理解析得到的节点。Tau 文本节点包含 `type: "formatted"`,不是只写 `{ text: "..." }`;直接构造节点时遵循 SDK 类型。
76
-
77
- 自定义节点可通过单次调用的 `codecs` 提供编解码器:
78
-
79
- ```ts
80
- const result = await context.services.markdown.serialize(nodes, {
81
- codecs: [
82
- {
83
- type: "example.note",
84
- serializer: { toMarkdown: (_node: unknown, children: string) => `> ${children}` },
85
- },
86
- ],
87
- });
88
- ```
89
-
90
- `codecs` 类型为 `EditorMarkdownCodec[]`;同类型替换本次转换的默认 codec,调用结束不保留。编辑器贡献的 codec 不隐式加入服务,需明确传入。仅声明序列化器不会自动得到反序列化规则;需要往返时还需提供相应的 directive 或解析规则。
91
-
92
- `warnings` 每项包含 `code`、`message` 和可选节点 `path`。当前报告未识别节点和 HTML 已知降级,不是任意语法、属性的完整无损审计。未知节点序列化可能保留为 Tau 扩展编码,其他 Markdown 工具未必理解。
93
-
94
- HTML 渲染不自动执行 TikZ、Typst、Mermaid 编译器;相关节点可能显示源码并返回 `DISPLAY_FALLBACK`,音视频可能显示链接。原始 HTML 和远程资源按客户端导出规则保留,输出不是经过净化的 HTML。`css` 是本次生成内容附带的样式,不包含完整 Tau 页面主题,也不打包图片附件。
95
-
96
- ## FFmpeg
97
-
98
- SDK 0.23.0 已移除 `ffmpeg.probe` 和 `MediaInfo`,客户端不再随包分发 FFprobe。已有插件应删除对应调用与类型依赖;需要本地媒体元数据时由插件自行实现。`videoExtractor.probe` 只查询视频页面,不能接收本地文件。
99
-
100
- `ServiceFile` 为 `{ name: string, data: Blob }`;文件名仅在任务内使用,不是客户端路径。输入输出名称不得重复,目录使用 `/`。文件整体经内存传输,适合可以放进内存的素材,不支持大文件流式处理。
101
-
102
- ```ts
103
- const input = { name: "input.wav", data: audioBlob };
104
- const result = await context.services.ffmpeg.run(
105
- {
106
- files: [input],
107
- args: ["-i", "input.wav", "-c:a", "pcm_s16le", "output.wav"],
108
- outputs: [{ name: "output.wav", mimeType: "audio/wav" }],
109
- },
110
- {
111
- onProgress: ({ time }) => console.log("已处理秒数", time),
112
- }
113
- );
114
- const outputBlob = result.files[0].data;
115
- ```
116
-
117
- `args` 不包含程序名;含空格的参数作为一个数组元素,不额外加 shell 引号。服务设置非交互、覆盖临时输出和进度通道,不要覆盖 `-progress` 或依赖交互 stdin。`outputs` 声明要取回的产物,名称与参数中的输出一致;文件未生成会报错,退出非零不会返回部分成功结果。
118
-
119
- `onProgress.time` 是已处理媒体时间(秒),不是百分比。首次使用 FFmpeg 时 Tau 可能需要联网下载,之后会复用;取消任务不会删除已下载的 FFmpeg。`log` 保留最后 64 KiB 诊断输出。返回 MIME 类型由 `outputs.mimeType` 指定,调用者须确保与实际格式一致。
1
+ # 编译与转换服务
2
+
3
+ 需要在插件里编译 Tailwind,转换 Markdown,或处理音视频时,可以使用这些服务。通过 `context.services` 调用,类型从 `@tau/plugins` 导入,需要 Tau 0.59.0 或更高版本及实现相应能力的客户端构建。本文对应 SDK 0.24.0。TikZ 编译属于独立绘图插件,Typst 排版属于 `tau.typst` 插件(自带 WebAssembly 引擎),均不再提供宿主服务。
4
+
5
+ 所有服务都是异步方法,只处理传入数据并返回结果,不读写笔记、不选择或保存文件、不自动注入样式。
6
+
7
+ ## API 一览
8
+
9
+ | 方法 | 输入 | 返回 |
10
+ | ---------------------------------------- | ------------------------------------------------- | --------------------------------------- |
11
+ | `tailwind.compile(input, options?)` | `{ source, language?: "html" \| "tsx" \| "jsx" }` | `{ css }` |
12
+ | `markdown.deserialize(source, options?)` | Markdown 字符串 | `{ nodes: Descendant[], warnings }` |
13
+ | `markdown.serialize(nodes, options?)` | `Descendant[]` | `{ markdown, warnings }` |
14
+ | `markdown.render(source, options?)` | Markdown 字符串 | `{ html, css, warnings }` |
15
+ | `ffmpeg.run(input, options?)` | `{ files, args, outputs }` | `{ files, log }` |
16
+ | `videoExtractor.probe(url, options?)` | 视频页面地址,可选 `proxy`(SDK 0.22.0) | `{ title, cover?, duration?, formats }` |
17
+ | `git.publishSnapshot(input, options?)` | 当前发布快照、HTTPS 仓库、分支与认证 | `void` |
18
+ | `git.selectCacheDirectory({}, options?)` | 打开宿主缓存目录选择器 | `{ id, label } \| null` |
19
+
20
+ Git 服务自 SDK 0.21.0 提供,完整参数与发布会话约束见 [PUBLISH.md](PUBLISH.md)。视频解析与播放源协议见 [VIDEO.md](VIDEO.md)。
21
+
22
+ ## 在静态组件中获取服务
23
+
24
+ SDK 0.16.0 的 `usePluginServices()` 返回当前插件可用的同一组服务,类型为 `PluginServices | undefined`。服务由插件运行时提供,独立编辑窗口无需 activate;博客和未包裹的普通组件返回 undefined,组件应显示已保存结果或源码。它不属于源码块或 EditorKit,不要将服务存入模块全局变量。
25
+
26
+ 组件需用自己的 AbortSignal 取消过期或卸载的调用,宿主同时处理窗口与工作区失效。内容插件的启停按现有重启生效规则处理;写入启停意图不会提前拆掉当前窗口已生效的贡献。
27
+
28
+ ## 取消与错误
29
+
30
+ 第二个参数均支持 `{ signal: AbortSignal }`。插件停用会自动取消本实例的任务,不必重复传入 `context.signal`。用户主动取消可使用单次任务的 `AbortController`。
31
+
32
+ ```ts
33
+ const controller = new AbortController();
34
+ try {
35
+ const { css } = await context.services.tailwind.compile(
36
+ { source: '<div class="p-4">Hello</div>', language: "html" },
37
+ { signal: controller.signal }
38
+ );
39
+ // 将 css 用于插件自己的预览。
40
+ } catch (error) {
41
+ if (error instanceof Error && error.name === "AbortError") return;
42
+ throw error;
43
+ }
44
+ // 对应取消按钮:controller.abort();
45
+ ```
46
+
47
+ 失败通过 Promise 拒绝返回 `Error`,`message` 包含可展示的错误信息;不要匹配内部错误文本决定业务流程。Markdown 同步解析/序列化只能在调用前后检查取消,不能中断正在执行的 JavaScript。HTML 转换内部已开始的渲染可能继续完成,但取消后不会交回结果。
48
+
49
+ ## Tailwind
50
+
51
+ ```ts
52
+ const { css } = await context.services.tailwind.compile({
53
+ source: '<section class="p-4 rounded-tau-md bg-surface text-ink-1">Hello</section>',
54
+ language: "html",
55
+ });
56
+ ```
57
+
58
+ 使用 Tailwind 4,支持 Tau 语义类。返回 theme/utilities,不重复提供 preflight。源码只用于提取完整类名,不执行 TSX/JSX,也不编译 React。动态拼接类名不能保证被提取。Tau 语义类依赖 Tau 提供的 `--tau-*` 变量,离开 Tau 的页面需要自行提供相应主题变量。
59
+
60
+ 固定插件界面通常在打包时编译样式;此服务主要用于运行时生成内容。返回 CSS 由插件自行使用并在停用时清理。
61
+
62
+ ## Markdown 与编辑器节点
63
+
64
+ ```ts
65
+ import type { Descendant } from "@tau/plugins";
66
+
67
+ const parsed = await context.services.markdown.deserialize("# 标题\n\n* [x] 已完成");
68
+ const nodes: Descendant[] = parsed.nodes;
69
+ const result = await context.services.markdown.serialize(nodes);
70
+ const preview = await context.services.markdown.render(result.markdown, { theme: "light" });
71
+ ```
72
+
73
+ 使用与 Tau 客户端一致的语法,包括标题、段落、列表和任务列表、表格、代码、数学公式、图片与媒体、Tau directive 等。`Descendant[]` 是数据,不关联某篇笔记,不写入编辑器。转换可能生成新的节点 UUID,不要依靠反复解析保留节点身份。
74
+
75
+ 优先处理解析得到的节点。Tau 文本节点包含 `type: "formatted"`,不是只写 `{ text: "..." }`;直接构造节点时遵循 SDK 类型。
76
+
77
+ 自定义节点可通过单次调用的 `codecs` 提供编解码器:
78
+
79
+ ```ts
80
+ const result = await context.services.markdown.serialize(nodes, {
81
+ codecs: [
82
+ {
83
+ type: "example.note",
84
+ serializer: { toMarkdown: (_node: unknown, children: string) => `> ${children}` },
85
+ },
86
+ ],
87
+ });
88
+ ```
89
+
90
+ `codecs` 类型为 `EditorMarkdownCodec[]`;同类型替换本次转换的默认 codec,调用结束不保留。编辑器贡献的 codec 不隐式加入服务,需明确传入。仅声明序列化器不会自动得到反序列化规则;需要往返时还需提供相应的 directive 或解析规则。
91
+
92
+ `warnings` 每项包含 `code`、`message` 和可选节点 `path`。当前报告未识别节点和 HTML 已知降级,不是任意语法、属性的完整无损审计。未知节点序列化可能保留为 Tau 扩展编码,其他 Markdown 工具未必理解。
93
+
94
+ HTML 渲染不自动执行 TikZ、Typst、Mermaid 编译器;相关节点可能显示源码并返回 `DISPLAY_FALLBACK`,音视频可能显示链接。原始 HTML 和远程资源按客户端导出规则保留,输出不是经过净化的 HTML。`css` 是本次生成内容附带的样式,不包含完整 Tau 页面主题,也不打包图片附件。
95
+
96
+ ## FFmpeg
97
+
98
+ SDK 0.23.0 已移除 `ffmpeg.probe` 和 `MediaInfo`,客户端不再随包分发 FFprobe。已有插件应删除对应调用与类型依赖;需要本地媒体元数据时由插件自行实现。`videoExtractor.probe` 只查询视频页面,不能接收本地文件。
99
+
100
+ `ServiceFile` 为 `{ name: string, data: Blob }`;文件名仅在任务内使用,不是客户端路径。输入输出名称不得重复,目录使用 `/`。文件整体经内存传输,适合可以放进内存的素材,不支持大文件流式处理。
101
+
102
+ ```ts
103
+ const input = { name: "input.wav", data: audioBlob };
104
+ const result = await context.services.ffmpeg.run(
105
+ {
106
+ files: [input],
107
+ args: ["-i", "input.wav", "-c:a", "pcm_s16le", "output.wav"],
108
+ outputs: [{ name: "output.wav", mimeType: "audio/wav" }],
109
+ },
110
+ {
111
+ onProgress: ({ time }) => console.log("已处理秒数", time),
112
+ }
113
+ );
114
+ const outputBlob = result.files[0].data;
115
+ ```
116
+
117
+ `args` 不包含程序名;含空格的参数作为一个数组元素,不额外加 shell 引号。服务设置非交互、覆盖临时输出和进度通道,不要覆盖 `-progress` 或依赖交互 stdin。`outputs` 声明要取回的产物,名称与参数中的输出一致;文件未生成会报错,退出非零不会返回部分成功结果。
118
+
119
+ `onProgress.time` 是已处理媒体时间(秒),不是百分比。首次使用 FFmpeg 时 Tau 可能需要联网下载,之后会复用;取消任务不会删除已下载的 FFmpeg。`log` 保留最后 64 KiB 诊断输出。返回 MIME 类型由 `outputs.mimeType` 指定,调用者须确保与实际格式一致。