Share

外观
风格

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

2026年2月8日 · 专栏

封面图

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>

点击"发布当前笔记"后:

  1. 图片自动上传到图床
  2. AI 自动生成分类和标签
  3. Wikilinks 自动转换成真实链接
  4. 链接复制到剪贴板

发布后 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], `![](${url})`);
}

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 语法 ![](url) 引用。

我踩过的坑

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 能看到渲染后的文章