Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ plugins/pdf-reader/assets/
plugins/packages/
docs/designs/b-prototype/site/
plugins/fsrs-optimizer/assets/
plugins/static-publish/assets/
4 changes: 4 additions & 0 deletions build/external-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export function externalPlugins(desktop: boolean): Plugin {
'fsrs-optimizer',
'study-statistics',
'assistant',
'static-publish',
'wordpress-publish',
'discourse-publish',
]
: [
'review-history',
Expand All @@ -32,6 +35,7 @@ export function externalPlugins(desktop: boolean): Plugin {
'fsrs-optimizer',
'study-statistics',
]
if (!desktop) ids.push('static-publish')
const files = new Map<string, string>()
const catalog = ids.map((id) => {
const bundle = JSON.parse(
Expand Down
2 changes: 2 additions & 0 deletions docs/PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Repeaty 在运行时加载外置插件。官方答题历史、LeetCode、复习

## 页面与界面扩展边界

SDK 1.14.0 增加[公开发布投影和受确认的网站连接](PLUGIN_PUBLISHING.md)。独立官方静态站、WordPress 与 Discourse 发布插件从插件页安装,配置注册到统一设置页,发布绑定随学习库保存,设备凭据不随库移动。操作方式见[发布学习材料](PUBLISHING.md)。

SDK 1.13.0 增加带宿主授权、预览、事务及幂等收据的[自动化接口](PLUGIN_AUTOMATION.md),以及设备模型连接。独立官方[学习助手](../plugins/assistant/README.md)提供会话、授权材料问答和可审查的整理建议;通过插件页面安装、启停和卸载。

SDK 1.7.0 支持通过 `registerPage()` 注册独立页面及对应主侧栏入口,通过 `registerWidget()` 在学习概览的指定位置嵌入组件。题目插件、复习面板插件都可注册;只提供页面或嵌入组件的插件导出 `type: 'extension'` 的 `ExtensionPlugin`,激活时至少注册一项页面或组件。现有题目、答案、编辑器、历史记录渲染以及 `registerSettings()` 保持原接口。
Expand Down
71 changes: 71 additions & 0 deletions docs/PLUGIN_PUBLISHING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# 插件发布接口

SDK 1.14.0 提供公开内容投影 `publishing` 与受授权的原生网络 `network`。静态站、WordPress、Discourse 官方插件使用同一接口,独立构建和按学习库安装,源码不进入本体构建。

## 选择与预览

```ts
import { publishing } from '@repeaty/plugin-api'

const index = publishing.list()
const snapshot = await publishing.prepare(
{
sourceIds: selectedIds,
expectedRevision: index.revision,
publicFields: ['author', 'tags'],
},
signal,
)

for (const source of snapshot.sources) {
// Only these explicitly named fields are part of the public projection.
// id/versionId are local binding identities; do not pass them into templates.
for (const asset of source.assets) {
const blob = await publishing.asset(snapshot.id, asset.id, signal)
// Rewrite asset.reference to the generated public asset URL.
}
}
publishing.validate(snapshot.id) // Before export or remote mutation.
publishing.release(snapshot.id)
```

`list()` 仅返回当前库的标题、身份及修订,用于用户选择。`prepare()` 只读取指定的材料,不自动包含子材料;最多 1,000 份、正文合计 32 MiB。草稿或标记为私有的材料会拒绝生成。正文开头的 YAML frontmatter 不直接输出;字段只允许明确指定的标量或字符串数组,拒绝内部身份、私有和凭据字段。未知嵌套属性不会默默转为公开 JSON。

明确传入空选择会生成空快照,可用于移除静态站中的全部文章。`links` 仅解析正文实际引用的材料身份,目标可以不在本次选择中,方便连接器查找先前的公开 URL;不会附带目标正文、字段或附件。内部 ID 不应直接输出到公开内容,尚无公开地址的链接需要明确处理或阻止发布。

快照的附件句柄仅指向这些正文实际引用的资源,不能传任意媒体 ID 读取原件或其他附件。附件 MIME 有白名单,单件不超过 25 MiB。原始 PDF/EPUB、注释、调度、历史、草稿、设备路径、凭据和插件私有数据不自动加入投影。正文中用户自行写入的内容仍需在发布前审查。

快照最多同时保留四份,有效期十分钟。准备时检查调用方提供的库修订;之后所选材料内容、属性、关联附件或链接解析身份变化时失效。纯学习时长或评分保存不会让未变化的公开预览失效,校验也不会重新读取正文。学习库替换、插件停用或卸载使快照失效;返回后还会复核作用域,不能用迟到读取继续发布。插件保存生成物和最终提交远端前都应重新验证;失败保留上一个完整结果,不把半份文件集标记为可发布。

## 网络与凭据

固定网站可沿用 manifest 的 `permissions.origins`。用户指定的网站声明 `permissions.network: true`,先调用 `network.authorize('https://example.org')`;宿主展示实际插件与完整 HTTPS origin,用户同意后才能访问。该授权仅保留在当前库的本次启用实例中,切库或重新启用后重新确认。普通 `nativePlugin` 不能直接调用内部的 `network.authorize` 操作。

```ts
import { network } from '@repeaty/plugin-api'
await network.authorize(new URL(site).origin)
const response = await network.request(
{
url: `${site}/api/posts`,
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(publicPost),
secret: { slot: 'site-account', headers: { Authorization: 'Bearer {{token}}' } },
},
signal,
)
```

密钥通过已有 `nativePlugin('secrets', …)` 写入设备私有存储,不能放入 `pluginData`。原生宿主将凭据绑定到保存时的网站 origin,在发送时插入指定头;不跟随重定向。XML-RPC 可以使用同一 `secret` 的 `body: {template, encoding:'xml'}`,其中凭据插值由宿主进行 XML 转义;不能同时传普通 body。公开凭据状态不回传密钥。

XML 模板中的 `{{field}}` 专用于凭据插槽。文章、标题等用户内容需先进行 XML 转义,并将字面 `{`、`}` 编码为 `&#123;`、`&#125;`,再与凭据插槽组装模板,防止正文中的同形文字被当作凭据插值。插值在每次追加前检查输出限额。

网络请求支持字符串、base64 二进制或 multipart,完整类型见 [publishing.d.ts](../packages/plugin-api/publishing.d.ts)。请求体最多 32 MiB,multipart 单项最多 25 MiB,响应最多 8 MiB;超限明确拒绝。插件调用绑定本次启用实例,AbortSignal、停用、卸载和切库会取消相关请求;更改凭据会停止使用旧凭据的请求。不会因此中断其他插件实例。

## 发布结果和恢复

取消等待、超时和网络异常不代表远端回滚。连接器应在第一次远端写入前持久保存操作意图,以稳定的本地材料身份关联远端 ID。结果不明时先读取并核对远端,禁止直接重新创建。更新前再次核对内容、目标和修订;删除必须是用户明确选择的独立动作。

发布绑定和待核对记录保存到按库的 `pluginData`,随完整工作目录备份保留;凭据仍留在设备。恢复本地备份或撤销学习操作不会撤回已经发生的外部发布,必须重新核对远端。模板、模型输出和文章内容均不能授予网络访问权限。

插件仍是用户信任的 JavaScript;这些接口约束公开发布路径和资源生命周期,不是任意恶意插件代码的强沙箱。网页可以生成、预览和导出静态站;原生网络和设备凭据需要桌面版。
33 changes: 33 additions & 0 deletions docs/PUBLISHING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 发布学习材料

在「插件」中按需安装 **静态发布**、**WordPress 发布** 或 **Discourse 发布**。安装后侧栏出现对应页面。静态站的主题与 Cloudflare 配置位于「设置 → 工具 → 发布」,WordPress 和 Discourse 连接位于「设置 → 连接」的对应分类。三个插件都独立加载;停用或卸载不会删除学习材料、发布关联或远端内容。

## 静态站点

1. 在设置中填写站点名称、说明和公开地址,选择需要公开的自定义字段;可编辑页面、归档、CSS 和 JavaScript 模板。
2. 在「静态发布」中明确勾选材料并生成预览。检查正文、图片、归档、分类、标签及搜索;草稿和私有材料会阻止生成。
3. 导出 ZIP 后,可把解压后的完整文件集放到静态托管服务。站点包含 RSS 与站点地图。重新生成时保留文章地址和首次发布时间,取消选择会从下一版站点移除文章。
4. 桌面版也可在设置中连接 Cloudflare Pages,创建目标项目,再从发布页确认部署。完整文件集替换所选分支的站点;清空选择可生成不含文章的站点。删除整个 Cloudflare 项目是设置里的独立操作,需要再次确认。

主题预览允许站内导航和默认搜索,但不执行用户主题脚本,也不访问外部链接。导出站点保留主题脚本。模板出错或生成失败会保留上次完整站点;重启后可以查看已有预览,导出或发布前需要重新生成并核对当前材料。

网页支持生成、预览和导出 ZIP;使用 Cloudflare 凭据和原生联网需要桌面版。

## WordPress 和 Discourse

在统一设置中填写 HTTPS 网站并连接账号。首次连接用户指定的网站时,Repeaty 会显示完整网站来源供确认。凭据只保存在当前设备,发布关联随工作目录保存。

- WordPress 支持 REST 应用密码,以及启用 XML-RPC 的 WordPress / WordPress.com 站点。连接方式及 Blog ID 取决于网站配置。
- Discourse 使用站点提供的 User API 授权。授权页面生成的加密结果由插件在本地解密,私钥仅保留在本次授权流程中。

从发布页面选择材料,调整文章标题、分类或标签,准备预览后再确认提交。已有发布关联会更新同一篇文章或主题。删除远端文章也是独立的预览和确认操作,不会删除本地学习材料。

这两个连接器不会上传本地图片或附件。含本地资源的正文需要先改用已公开的 HTTPS 地址,或选择静态站点发布。材料之间的链接需要目标已有可核对的公开地址;无法转换的内部链接会明确阻止发布,避免输出内部材料身份。

## 中断、冲突与备份

停止等待不等于撤回远端操作。超时或断网后如果不能确定远端结果,先使用「核对远端状态」,不要重复创建文章。远端在预览后被编辑时会停止更新,要求重新预览;已经发布的内容不会因为学习库撤销或恢复本地备份而自动撤回。

移动完整工作目录后,插件设置、站点产物和发布关联仍在,但新设备需要重新连接账号。卸载插件不会撤下已经公开的站点或文章;需要先执行相应的远端删除操作。

插件包内 README 提供各连接器的具体权限、限制和操作说明。SDK 开发接口见 [插件发布接口](PLUGIN_PUBLISHING.md)。
144 changes: 142 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
"plugins/readwise",
"plugins/fsrs-optimizer",
"plugins/study-statistics",
"plugins/assistant"
"plugins/assistant",
"packages/publishing-client",
"plugins/static-publish",
"plugins/wordpress-publish",
"plugins/discourse-publish"
],
"description": "Desktop flashcards and spaced repetition, with your learning library in one folder.",
"engines": {
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-api/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const runtimeExports = [
'materials',
'study',
'automation',
'publishing',
'network',
'tasks',
'resources',
'practiceContentRevision',
Expand Down
Loading