软件开发

md-blog-publisher:把 Push Markdown 搬上网页

/3965 字 · 约 11 分钟
目录 · 9 节

为什么又折腾一遍

之前写过 Push Markdown 的重构,那是把别人的 Electron 桌面工具用 Vue3 + TypeScript 重做了一遍。桌面版用了好几年,确实方便:本地 Markdown 渲染成 HTML,图片批量上传,再通过 MetaWeblog(XML-RPC)发到 WordPress 或博客园。

但桌面版有几个长期痛点:

  1. 跨平台打包麻烦。Windows / macOS 各打一份,Electron 体积又大,升级还得搞自动更新。
  2. 文档其实已经在服务器上了。我自己的文章仓库就放在服务器,桌面端还得先同步到本机,再打开软件发布,多绕了一圈。
  3. 想在手机或别的电脑上改一两处再发布,还得装客户端,不太现实。

于是就有了这次的网页化重写:md-blog-publisher。核心想法很简单——部署一份服务,浏览器打开链接就能用;文档根目录改成服务器上的某个目录,发布逻辑还是原来那套思路。网页版干脆砍掉了 XML-RPC / cnblogs,只对接本服务所在的 WordPress,用当前登录身份走 REST API。

它是什么

一句话:在网页里编辑、预览服务器上的 Markdown,并以 WordPress 登录身份一键发布。

嵌入 WordPress 后台后的桌面端界面大致是这样——左侧文件树,中间编辑,右侧预览,顶栏有同步 Git、设置和发布:

桌面端界面

和桌面版比,有两处本质变化:

  1. 文件系统:从「读用户电脑上的文件夹」变成「读服务器本地的文档目录」(docsRoot)。
  2. Markdown 渲染:从依赖浏览器 DOM,改成后端纯字符串渲染(markdown-it + highlight.js),前端只展示结果。

发布体验尽量对齐原来:front matter(标题、摘要、分类、标签、固定链接、发布时间)、本地图片上传与同名覆盖、文章缓存避免重复创建,这些都还在。不再保存站点账号密码,也不再走 XML-RPC。

整体架构

项目拆成三块:

client/              Vue 3 + Vite 前端
server/              Node + Express 后端
wordpress-plugin/    WordPress 插件(登录态校验 + 媒体上传)

浏览器只跟自己的后端说话。发布请求都由 server 带着 WordPress 登录 cookie 去调 REST API——浏览器不直连 WordPress,账号凭据也不落盘。

大概的数据流是这样的:

浏览器 (Vue)
    │  /api/*
    ▼
Express 后端
    ├── 读/写 docsRoot 下的 .md 与图片
    ├── markdown-it 渲染成 HTML
    ├── 校验 WordPress 登录态,拿 wp_rest nonce
    └── 以当前用户调用 WordPress REST
         │
         ▼
    WordPress(文章 / 分类标签 / 媒体)

前端负责文件树、多标签编辑、预览、站点设置、发布面板;后端负责真正碰磁盘、碰博客接口。生产环境下 client/dist 由后端一并托管,外面再挂 nginx 反代就行。

前端在干什么

技术栈很常规:Vue 3 + Vite + Pinia。宽屏下布局和桌面版差不多——左侧文件树,中间编辑/预览,设置和发布走弹层。

比较有意思的几点:

  • 预览不在前端渲染 Markdown。打开或保存文件时,后端返回渲染好的 HTML,前端直接塞进预览区。这样公式、代码高亮、任务列表等行为前后端一致,发布出去的内容和你看到的预览也一致。
  • 发布进度用 SSE。桌面版是界面状态提示;网页版改成服务端事件流,前端能实时显示「读取中 / 上传图片 / 发布中 / 完成」。
  • Git 同步。文档根目录如果在 git 仓库里,顶栏会出现「同步 Git」:提交该目录内的改动 → pull --rebase → push。文章仓库本身就在服务器上时,网页里改完直接推回远程,很顺手。
  • 移动端适配。窄屏下改成顶栏 +「编辑 / 预览」切换,文件树变成侧滑抽屉(可拖动手势),编辑页还能开一个悬浮预览小窗。网页化本来就是为了手机上也能改两句再发,这块补上之后才算闭环。

从左到右分别是:侧滑文件树、编辑页(带悬浮预览窗)、全屏预览:

移动端界面

后端在干什么

Express 按模块挂路由:文件树与读写、静态资源(文档里的图片)、设置、发布、上传、打开的标签页、Git。

和发布相关的核心可以概括成一层 Publisher + BlogClient:

  • Publisher 管流程:读文章、处理本地图片、查缓存、新建或更新文章。
  • BlogClient 目前只剩 WordPress REST 这一套实现:用浏览器转发来的登录 cookie + 插件签发的 wp_rest nonce,以当前后台用户发文章、传图片。

图片和文章都有发布缓存(publish-cache.json),按「站点地址::用户名」分区。设置里的 url / username 不参与请求,只用来对齐已有发布记录——从桌面版 XML-RPC 迁过来时,填原来的站点地址和用户名就能命中旧缓存,不会把历史文章当成新文章再发一遍。换机器部署时把设置和缓存一起迁过去即可。

文档根目录有路径守卫,请求不能逃出 docsRoot,避免登录用户把任意系统路径读出来。

浏览器和 WordPress 之间,中间隔着一层 Node。 你在网页里点的预览、保存、发布,请求都是浏览器先到本工具的后端;后端再读服务器上的 .md 和图片文件。实时预览是把编辑器里的正文 POST 给后端渲染,不落盘,所以会走你访问站点的那条链路(公网、CDN 都算在内)。编辑时「插图」则是浏览器把文件上传到后端,写进文档目录,同样会经过外网。

发布时传 WordPress 媒体库不一样。 浏览器只负责触发发布流程;真正上传图片是后端从 docsRoot 读本地文件,再带着登录 cookie 调 REST(含插件提供的同名覆盖媒体接口)。同机部署时可以在环境变量里配一个 WordPress 的内网/回环地址(例如本机 nginx 的 HTTP 入口),让校验登录、发文章、传图都走服务器内部,不绕公网域名、也不经 CDN。对外仍用 HTTPS 域名给浏览器用,两套地址各干各的。没配内网地址时,后端会退回对外的 WP_URL,传图也会从服务器再绕一圈公网,又慢又可能多计 CDN 流量。

鉴权与发布

公网挂一个能读写服务器文档目录的工具,鉴权必须认真点。这里直接复用 WordPress 后台登录:把插件丢进 WordPress,后台左侧菜单嵌入本工具。要求本服务和 WordPress 同域名(子路径),这样浏览器会带上 WordPress 的登录 cookie;服务端只把 wordpress_logged_in_* 转发给插件的校验接口确认身份。本地调试可以设 AUTH_DISABLED=true 跳过登录。

发布只走这一条路:

  1. 插件的 mbp_whoami 在校验登录态的同时签发 wp_rest nonce,服务端只在内存里保存,不下发给浏览器。
  2. 发文章时带着 cookie + nonce 调 /wp/v2/posts 等接口,作者就是当前登录用户;WordPress 退出后就不能再发布。
  3. 图片走插件的 POST /wp-json/mbp/v1/media,同名可覆盖,媒体库不会堆出一堆 -1 后缀。这取代了以前每次升级 WordPress 都要对 XML-RPC 打的同名覆盖补丁。

既然本工具已经不依赖 XML-RPC,如果没有别的东西(手机 App、Jetpack 等)还在用,可以在 nginx 或安全插件里直接关掉 xmlrpc.php,少一个被扫的入口。

部署形态

我自己的用法是:和 WordPress 同域、子路径反代。systemd 跑 Node,nginx 把某个子路径转到本机端口;发布接口是 SSE,nginx 那边要关掉 proxy buffering,不然进度会攒到最后一次性吐出来。

前面有 CDN 时还要注意别把带登录态的页面和接口缓存坏了,未登录时应老老实实返回 401,前端才能提示去 WordPress 登录。这些都是「能跑」和「线上真能用」之间的坑,README 里写得比较细,这里就不展开了。

和桌面版还差在哪

网页版砍掉了 Electron 相关的一切:系统托盘、多窗口、自动更新、本地菜单。也砍掉了 cnblogs / MetaWeblog——只发布到本服务所在的 WordPress,用当前登录身份走 REST。

渲染从 DOM 操作改成字符串处理之后,极端 Markdown 写法下预览可能和桌面版有细微差异,但日常文章基本无感。

小结

md-blog-publisher 不是另起炉灶造一个新编辑器,而是把 Push Markdown 的发布链路搬到「服务器文档目录 + 浏览器」这个场景里,并换成更干净的 WordPress REST 路径:

  • 前端负责交互与展示;
  • 后端负责文件、渲染、带着登录态转发 REST;
  • 插件把工具嵌进 WordPress 后台,并提供登录校验和同名覆盖的媒体上传。

对我来说,最大的收益是:文章仓库放在哪,编辑和发布就在哪,不用再在本机和维护桌面安装包之间来回倒腾,也不用再惦记 XML-RPC 补丁。如果你也是「Markdown 写博客 + WordPress 托管」,又懒得装 Electron 客户端,这种网页化形态会舒服很多。

评论

还没有评论,来说第一句。

发表评论

邮箱只用来显示头像,不会公开