Obsidian 一键发布:把笔记变成可分享的网页

Obsidian 一键发布:把笔记变成可分享的网页
写笔记写着写着,有时想分享给朋友看。但 Obsidian 的笔记是本地 Markdown,没法直接发链接。
用 Obsidian Publish?一年 $96,而且得把笔记传到他们服务器。用 Notion?我不想再维护两套笔记。
后来花了一个周末自己搞了个方案:在 Obsidian 里按 Cmd+P,选"发布当前笔记",2 秒后链接就在剪贴板里了。笔记还是本地的,只有我主动发布的才会变成网页。
效果:s.chen.rs
这是什么
一套自托管的笔记发布系统,包含:
- Obsidian 插件:一键发布、AI 自动打标签、图片自动上传
- Cloudflare Worker:渲染 Markdown、存储文章、提供网页访问
- R2 存储:存文章 JSON 和索引
架构图:
Obsidian (本地)
│
├─ 发布笔记 ──────────> share-worker (s.chen.rs)
│ │
├─ 上传图片 ──────────> img-worker (i.chen.rs)
│ │
└─ AI 生成标签 ──────> ai-gateway (ai.chen.rs)
│
v
R2 Bucket (文章存储)
效果展示
发布前的 Obsidian 笔记:
---
title: 我的第一篇文章
---
# 我的第一篇文章
这是正文内容。
!<span class="private-link" title="未发布的笔记">screenshot.png</span>
可以引用其他笔记:<span class="private-link" title="未发布的笔记">另一篇笔记</span>
点击"发布当前笔记"后:
- 图片自动上传到图床
- AI 自动生成分类和标签
- Wikilinks 自动转换成真实链接
- 链接复制到剪贴板
发布后 frontmatter 自动更新:
---
title: "我的第一篇文章"
slug: "my-first-post"
category: "技术"
tags: ["Obsidian", "工具"]
share: true
share_url: "<REDACTED_URL>/my-first-post"
---
状态栏显示"📤 已发布",点击直接跳转到线上版本。
快速开始(5 分钟)
1. 部署后端 Worker
# 克隆项目
git clone <REDACTED_URL>
cd share-worker
# 创建 R2 Bucket
wrangler r2 bucket create share
# 设置 API Key
wrangler secret put <REDACTED_SECRET>
# 输入一个随机字符串,比如:sk-share-xxxx
# 部署
cd workers/share && wrangler deploy
部署完成后,绑定自定义域名(比如 s.chen.rs)。
2. 安装 Obsidian 插件
# 构建插件
cd obsidian-share-plugin
bun install && bun run build
# 复制到 Obsidian
mkdir -p "<REDACTED_PATH>"
cp main.js manifest.json "<REDACTED_PATH>/"
在 Obsidian 中启用:Settings → Community plugins → 启用 "Share to Web"
3. 配置插件
Settings → Share Plugin Settings:
| 配置项 | 值 |
|---|---|
| API 地址 | <REDACTED_URL> |
| 图床地址 | https://i.chen.rs(如果有图床) |
| API Key | 你刚才设置的 sk-share-xxxx |
4. 发布第一篇
打开任意笔记,Cmd+P 输入"发布",选择"发布当前笔记"。
链接已经在剪贴板里了,粘贴分享即可。
核心代码
插件核心逻辑不到 200 行,主要做三件事:
1. 解析 frontmatter,生成 slug
const slug = frontmatter.slug || this.generateSlug(title);
generateSlug(title: string): string {
return title
.toLowerCase()
.replace(/[^\w\u4e00-\u9fa5]+/g, "-")
.replace(/^-|-$/g, "")
.slice(0, 50);
}
2. 上传图片,替换链接
// 匹配 Obsidian 图片语法
const images = [...content.matchAll(/!\[\[([^\]]+)\]\]/g)];
for (const match of images) {
const imageFile = this.app.metadataCache.getFirstLinkpathDest(
match[1], file.path
);
const url = await this.uploadImage(imageFile);
processed = processed.replace(match[0], ``);
}
3. 调用 API 发布
const response = await requestUrl({
url: `${apiUrl}/api/publish`,
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ slug, title, category, content }),
});
// 更新 frontmatter
await this.updateFrontmatter(file, {
share: true,
share_url: response.json.url,
});
Worker 端更简单:收到请求后渲染 Markdown,存到 R2,更新索引。
AI 自动打标签
发布时如果没有 category,插件会自动调用 AI 生成:
const response = await requestUrl({
url: `${aiApiUrl}/chat/completions`,
body: JSON.stringify({
model: "gemini-2.5-flash-lite",
messages: [{
role: "user",
content: `分析这篇文章,返回 JSON:
{ "category": "分类", "tags": ["标签1", "标签2"] }
文章内容:${content.slice(0, 4000)}`
}],
response_format: { type: "json_object" },
}),
});
用最便宜的模型就够了,打个标签不需要太聪明。
可选:图床服务
如果笔记里有本地图片,需要配套一个图床。我用的也是 Cloudflare Worker + R2:
# 另一个 Worker:img-worker
wrangler r2 bucket create images
cd workers/img && wrangler deploy
API 很简单:POST /upload 上传图片,返回 URL。
没有图床的话,可以先手动把图片传到任意图床,用标准 Markdown 语法  引用。
我踩过的坑
1. Obsidian 的 requestUrl 和 fetch 不一样
插件里不能直接用 fetch,要用 Obsidian 提供的 requestUrl。它会自动处理 CORS 和证书问题,但 API 略有不同:
// 错误:fetch 风格
const res = await fetch(url);
const data = await res.json();
// 正确:Obsidian 风格
const res = await requestUrl({ url, method: "GET" });
const data = res.json; // 注意:不是函数调用
2. multipart/form-data 要手动拼
Obsidian 的 requestUrl 不支持 FormData,上传文件得手动拼 boundary:
const boundary = "----WebKitFormBoundary" + Math.random().toString(36).slice(2);
const header = `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${name}"\r\n\r\n`;
const footer = `\r\n--${boundary}--\r\n`;
// ... 拼接 Uint8Array
有点烦,但也就这一处需要处理。
3. Wikilinks 要在服务端处理
<span class="private-link" title="未发布的笔记">其他笔记</span> 这种 Obsidian 语法,Markdown 渲染器不认识。我在 Worker 端加了预处理:
// 已发布的文章:转成真实链接
// 未发布的文章:保留纯文本
const processedContent = processWikilinks(content, slugToTitleMap);
进阶:批量发布
右键点击文件夹 → "发布目录所有笔记",会递归发布目录下所有 .md 文件。
或者用 CLI:
# 发布单篇
bun tools/share.ts article.md
# 发布所有 share: true 的笔记
bun tools/share.ts --all
# 列出已发布
bun tools/share.ts --list
CLI 还支持自动脱敏(移除 API Key、IP 地址等敏感信息),插件版本暂时没加这个功能。
项目结构
.
├── obsidian-share-plugin/ # Obsidian 插件源码
│ ├── main.ts # 主逻辑
│ └── manifest.json # 插件配置
├── workers/
│ ├── share/ # 文章发布 Worker
│ │ ├── src/index.ts # API 路由
│ │ ├── src/markdown.ts # Markdown 处理
│ │ └── src/render.ts # HTML 渲染
│ └── img/ # 图床 Worker
└── tools/
└── share.ts # CLI 工具
资源
附录:Claude Code Skill 配置
最重要的交付物:复制下面的内容到 .claude/skills/share/SKILL.md,然后你就可以用自然语言发布笔记了。
---
name: share
description: "发布 Obsidian 笔记到网页。触发词:发布文章、分享笔记、share、发布到网上"
---
# 笔记发布服务
## 功能
把 Obsidian 笔记一键发布成可分享的网页,支持图片上传、AI 自动打标签、Wikilinks 转换。
## 使用方式
直接用自然语言触发:
- "发布这篇笔记"
- "把当前文章分享出去"
- "发布 ~/Documents/Obsidian/article.md"
- "列出已发布的文章"
## 依赖
- Cloudflare Worker(后端 API)
- R2 存储(文章存储)
- 可选:图床 Worker、AI API(自动打标签)
## 配置
环境变量:
- <REDACTED_URL>: 发布 API 地址(如 <REDACTED_URL>)
- SHARE_<REDACTED_SECRET>: 发布认证密钥
- <REDACTED_URL>: 图床地址(可选)
## 核心逻辑
1. 解析笔记 frontmatter,生成 slug
2. 上传笔记中的本地图片到图床
3. 处理 Wikilinks,转换为真实链接
4. 调用 AI 生成分类和标签(如果没有)
5. 发布到 Worker,存储到 R2
6. 回填 share_url 到源文件 frontmatter
## 示例
输入:"发布这篇关于 Cloudflare 的笔记"
输出:
- 笔记发布成功
- 链接 <REDACTED_URL>/cloudflare-guide 已复制到剪贴板
- frontmatter 已更新 share_url
附录:给 AI 的复现指令
帮我搭建一个 Obsidian 笔记一键发布系统。
目标:在 Obsidian 里一键把笔记发布成可分享的网页,支持图片上传和 AI 自动打标签。
技术栈:
- 插件:TypeScript + Obsidian Plugin API
- 后端:Cloudflare Worker + R2
- 构建:esbuild
系统组成:
1. Obsidian 插件(一键发布、图片上传、AI 标签)
2. share-worker(文章 API + 网页渲染)
3. img-worker(图床,可选)
项目结构:
obsidian-share-plugin/
├── main.ts # 插件主逻辑
├── manifest.json # 插件元信息
├── package.json
└── esbuild.config.mjs
workers/share/
├── src/
│ ├── index.ts # API 路由
│ ├── markdown.ts # Markdown 解析
│ └── render.ts # HTML 渲染
├── wrangler.toml
└── package.json
核心 API:
- POST /api/publish - 发布文章
请求:{ slug, title, category, tags, content }
响应:{ success, url, slug, publishedAt }
- GET /api/articles - 文章列表
- DELETE /api/articles/:slug - 删除文章
- GET /:slug - 文章页面
R2 存储结构:
bucket/
├── articles/{slug}.json # 文章内容
└── index/all.json # 全站索引
环境变量:
- <REDACTED_SECRET>: 发布认证密钥
- PUBLIC_URL: 公开访问 URL(如 <REDACTED_URL>)
- IMG_URL: 图床 URL(如 https://i.chen.rs)
部署命令:
wrangler r2 bucket create share
wrangler secret put <REDACTED_SECRET>
wrangler deploy
成功标志:
1. 在 Obsidian 中能看到"发布当前笔记"命令
2. 发布后 frontmatter 自动添加 share_url
3. 访问 share_url 能看到渲染后的文章