ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Claude Code案例-浏览器插件开发之notion to markdown剪切板(已开源)

Claude Code案例-浏览器插件开发之notion to markdown剪切板(已开源) 1. 为什么我要自己写一个 Notion 转 Markdown 剪切板插件我平时主力笔记软件就是 Notion写完之后经常要发到 CSDN、知乎这类支持 Markdown 的平台。Notion 自带的导出功能会给你一个 zip里面是.md文件加一堆本地图片还得手动解压、上传图床、再复制正文流程特别割裂。我真正想要的动作只有一个在 Notion 页面里点一下Markdown 全文含图片外链直接进剪切板切到编辑器 CtrlV 就完事。搜了一圈排名靠前的方案基本是 GitHub Action 形态的 notion2markdown它的定位是把 Notion 内容同步到静态站点需要配 workflow、配 secret跟随手复制完全不是一个场景。也有在线转换网站但要把私有页面内容贴给第三方我是不太放心的。所以结论很明确市面缺一个浏览器插件形态、本地完成转换、结果直接进剪切板的工具。这个需求特别适合拿来练 Claude Code。它足够小两小时能出可用版本又足够完整涉及 manifest v3、content script、跨域请求、第三方 SDK 打包这些真实工程问题。下面我会把插件结构、manifest 配置、内容提取与转换逻辑、以及我踩过的报错全部摊开你可以直接照着复刻一个。核心检索词先摆在这Claude Code 开发浏览器插件、Notion 转 Markdown、复制到剪切板这三件事本文都会给到可复制的配置。技术栈上我选的是 Notion 官方 API 客户端notionhq/client拉取块数据notion-to-md做块到 Markdown 的转换图片走腾讯云 COS 的cos-js-sdk-v5上传后替换链接构建用 Webpack 5。这套组合是 Claude Code 在读完参考项目后自己给出的方案我基本没改。2. 用 Claude Code 搭插件的准备工作与 TaoToken 接入配置在正式写代码前得先让 Claude Code 能稳定跑起来。我这边是通过 TaoToken 接入的它把模型调用统一成一个 OpenAI 兼容的入口配置一次就能在 Claude Code、Cline 这类工具里复用。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 注意 API 地址后面不加任何查询参数。先说清楚三件套这是所有接入的通用公式Base URL、API Key、Model ID。缺任何一个都会在请求阶段直接失败。Base URL 填https://taotoken.net/apiAPI Key 在控制台的 API Keys 页面生成Model ID 按你实际要用的模型填。下面这段是 Claude Code 的 settings 配置路径是~/.claude/settings.json直接复制改 Key 即可{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: sk-你的TaoToken密钥, ANTHROPIC_MODEL: claude-sonnet-4-5-20250929 } }如果你用的是 Codex 系工具配置落在~/.codex/auth.json结构不太一样注意别混{ OPENAI_API_KEY: sk-你的TaoToken密钥, OPENAI_BASE_URL: https://taotoken.net/api }Cline 这类 VS Code 插件则是在设置面板里填 Base URL 和 KeyModel ID 手动输入。三者的共同点就是上面那三件套配完先别急着写业务代码跑一个最小请求验证通路。我习惯用 curl 先探一下curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-你的TaoToken密钥 \ -H Content-Type: application/json \ -d { model: claude-sonnet-4-5-20250929, messages: [{role: user, content: 回复 ok}] }返回里能看到choices[0].message.content就说明链路通了。这一步很关键因为后面插件调试时如果报错你得能区分是模型接入的问题还是插件本身的问题。我建议把 Key 单独放一个环境变量文件别硬编码进仓库Claude Code 生成代码时也提醒它不要写死密钥。准备工作还包括Chrome 打开chrome://extensions开启开发者模式Notion 那边创建一个 Integration 拿到 token并把目标页面授权给这个 Integration。腾讯云 COS 建一个存储桶拿到 SecretId、SecretKey、Bucket 和 Region。这些凭据后面会填进插件的配置页先备齐。3. 可复制的 manifest 与转换逻辑配置插件目录结构我让 Claude Code 按下面这样组织清晰且好维护notion-to-markdown-extension/ ├── manifest.json ├── src/ │ ├── background.js │ ├── content.js │ ├── popup.html │ ├── popup.js │ └── converter.js ├── package.json └── webpack.config.jsmanifest.json是 MV3 格式权限要开activeTab、scripting、storagehost 权限要覆盖 Notion 和 COS 域名否则 fetch 会被拦{ manifest_version: 3, name: Notion to Markdown Clipboard, version: 1.0.0, description: 一键把 Notion 页面转成 Markdown 并写入剪切板, permissions: [activeTab, scripting, storage, clipboardWrite], host_permissions: [ https://api.notion.com/*, https://*.myqcloud.com/* ], background: { service_worker: background.js }, action: { default_popup: popup.html }, content_scripts: [ { matches: [https://www.notion.so/*], js: [content.js] } ] }转换核心在converter.js思路是先用 Notion API 拿到 pageId 对应的 block 树再交给notion-to-md转字符串最后把图片块替换成 COS 外链import { Client } from notionhq/client; import { NotionToMarkdown } from notion-to-md; import COS from cos-js-sdk-v5; const notion new Client({ auth: NOTION_TOKEN }); const n2m new NotionToMarkdown({ notionClient: notion }); const cos new COS({ SecretId: COS_SECRET_ID, SecretKey: COS_SECRET_KEY }); async function uploadImage(buffer, key) { return new Promise((resolve, reject) { cos.putObject({ Bucket: COS_BUCKET, Region: COS_REGION, Key: key, Body: buffer }, (err, data) { if (err) return reject(err); resolve(https://${COS_BUCKET}.cos.${COS_REGION}.myqcloud.com/${key}); }); }); } export async function pageToMarkdown(pageId) { const mdblocks await n2m.pageToMarkdown(pageId); const mdString n2m.toMarkdownString(mdblocks); return mdString.parent; }图片处理是最容易翻车的地方。Notion 的图片块返回的是带签名的临时 URL一小时后失效所以必须下载后转存到自己的 COS再把 Markdown 里的链接替换掉。Claude Code 一开始没处理这个我贴了报错它才补上uploadImage这段。background.js负责接收 popup 的指令、调用 converter、把结果写回剪切板chrome.runtime.onMessage.addListener((msg, sender, sendResponse) { if (msg.type CONVERT) { pageToMarkdown(msg.pageId) .then(md sendResponse({ ok: true, md })) .catch(e sendResponse({ ok: false, error: e.message })); return true; } });注意MV3 的 service worker 不能直接用navigator.clipboard写剪切板的动作要放在 popup 或 content script 里执行background 只做数据搬运。4. 验证请求与成功结果从 Notion 页面到粘贴出 Markdown配置填完后先做一次端到端验证。打开一个已授权给 Integration 的 Notion 页面点插件图标popup 里会显示当前 pageId 和一个转换并复制按钮。点下去之后正常流程是background 调 Notion API 拉块 → converter 转 Markdown → 图片上传 COS → 返回完整字符串 → popup 写入剪切板。验证成功有几个可观察的信号。第一popup 里出现已复制 N 字符的提示第二切到任意 Markdown 编辑器 CtrlV标题层级、列表、代码块都保留第三图片链接是https://你的bucket.cos.区域.myqcloud.com/...这种你自己的域名而不是 Notion 的临时签名地址。我实测下来一篇带 5 张图、3 个代码块的笔记从点击到粘贴完成大概 3 到 5 秒主要耗时在图片上传。如果你想更严谨地验证转换质量可以拿一篇结构复杂的页面测包含 to-do、toggle、callout、嵌套列表。notion-to-md对大部分块支持良好但 callout 会转成引用块toggle 会展开成普通内容这些差异你要心里有数。下面是一个转换前后的对照方便你判断结果是否符合预期Notion 块类型转换后 Markdown备注Heading 1/2/3#/##/###层级保留Bulleted list-嵌套用缩进Code block三反引号 语言语言标识可能丢失Image![alt](COS外链)需自行转存Callout引用块图标丢失验证阶段还有一个动作值得做把转换函数单独抽出来在 Node 里跑一遍不依赖浏览器环境。这样出问题时能快速定位是 API 层、转换层还是插件通信层的问题。我当时的做法是写一个test.js直接 import converter传入 pageId打印结果。这一步帮我省了大量在浏览器里反复点的时间。5. 本篇常见报错排查401、Illegal invocation 与图片失效调试过程中我遇到的报错基本集中在下面几类逐个说清楚原因和解法。第一类是Error: Failed to execute fetch on Window: Illegal invocation。这个报错我卡了最久Claude Code 也迭代了两三次才修好。根因是fetch被解构或赋值后脱离了window上下文比如写成const { fetch } window再调用就会触发。解法是始终用window.fetch(...)或fetch.call(window, ...)别把方法单独拎出来。如果你在 content script 里调用还要注意 MV3 的隔离环境跨域请求建议统一走 background。第二类是 401。这个几乎都是凭据问题对照检查三件套Notion token 是否以secret_或ntn_开头、是否把页面 share 给了对应 Integration、TaoToken 的 Key 是否过期。如果返回体里是unauthorized先看 Notion 侧如果是模型调用返回 401检查ANTHROPIC_AUTH_TOKEN有没有多余空格。我踩过的坑是复制 Key 时带了个换行排查了十分钟。第三类是Cannot read properties of undefined (reading choices)。这通常出现在你直接解析模型返回时说明请求根本没成功返回体是错误对象而不是标准结构。正确做法是先判断response.ok再取choices。这类报错在接入初期很常见本质是错误处理没写全。第四类是图片 403 或链接过期。Notion 的图片 URL 带签名超过有效期就失效。如果你没做转存粘贴出去的 Markdown 过一会儿图就挂了。解法就是前面说的下载后传 COS 再替换。COS 这边如果报AccessDenied检查存储桶权限是不是私有读写、SecretId 有没有对应权限。第五类是 OAuth 相关报错。如果你走的是 Notion 的 OAuth 授权流程而不是内部 Integration token回调地址必须和 Notion 后台配置的完全一致包括协议和端口。本地调试时用http://localhost容易被拒建议直接配一个固定的回调路径。提示排查顺序建议从外到内——先 curl 验证模型通路再验证 Notion API最后才怀疑插件代码。这样能避免在错误的方向上浪费时间。6. 后续迭代与接入入口基础版本跑通后我又迭代了几个方向。一个是兼容 Cookie 方案不依赖 Integration token直接从浏览器已登录的 Notion 会话里取数据好处是用户零配置坏处是稳定性受 Notion 前端改动影响目前还在调。另一个是给 popup 加了转换选项比如是否上传图片、是否保留 callout 图标、代码块语言标识补全。这些都可以让 Claude Code 帮你加描述清楚需求它就能改。如果你也想从零做一遍建议按这个顺序推进先用 curl 把 TaoToken 通路跑通再单独写一个 Node 脚本验证 Notion API 和转换逻辑最后才包成插件。这样每一步都有独立验证点出问题好定位。模型调用入口在 https://taotoken.net/api 密钥在控制台的 API Keys 页面生成接入文档在 doc 页面有完整说明。需要长期跑编码和 Agent 任务的可以看下 Coding Plan按用量规划更省心。真正动手写的时候你会发现卡住你的从来不是不会写代码而是某个具体的报错和某个没配对的环境变量。把这两样解决掉一个小工具两小时就能出来。
返回列表