Typecho博客的折腾记录——Markdown扩展语法支持

技术性踩雷  ·  2026-06-18

Markdown.png

前言

作为一个追求极致阅读和交互体验的独立博客站长,我使用了一款极简优雅的主题 Jasmine_Plus。在偶然跟别人的一次聊天,我就遇到了这个情况:

我:博客自带编辑器就是方便,走到哪写到哪(截图)
对面:没有语法着色吗()我现在已经离不开语法着色了
我:这个没有,但是有预览(
我:(说的也对,我好像可以弄一个了)
……

为了优化博客对 Markdown 语法的支持,我一开始上网搜索,选用了 EditorMD,但是装上插件以后很快就遇到了麻烦。借此又开启了这次折腾,以下是完整记录。

初遇 Bug

满怀期待部署完插件,上编辑器看看?一切正常,运行良好。

markdown1.png

然后,刷新一下首页看看呢?

markdown2.png

报错了。进文章看看?也是空空如也。

初步定位

在AI的帮助下,我顺着追踪到了 Editor.MD 插件的主控文件 Plugin.php。在插件的 contentexcerpt 过滤器中,代码试图去渲染文章内容:

\(text = \)conent->isMarkdown ? \(conent->markdown(\)text) : \(conent->autoP(\)text);

问题就出在这里:markdown()autoP()Widget\Base\Contents 内部的 protected(受保护)方法。

AI 给出如下解释:

- 在旧版本 PHP 或老旧 Typecho 中,这种外部直接调用可能通过某些魔法方法或宽松的可见性规则被放行。

- 但在现代 PHP 8.x + Typecho 1.2+ 中,外部直接访问受保护方法会触发 Typecho\Widget__call() 魔术方法。由于权限不足,该魔术方法最终返回了 null

- 紧接着,这个 null 被传递给了 Typecho 核心的 Contents.php 进行后续处理(例如 explode() 等字符串操作),从而引发了 Passing null to parameter of type string is deprecated 的警告。

解法一:修复 Content

询问朋友资助的Opus4.6后,AI给出如下解法:

既然是受保护方法,我们只需要利用 PHP 的 Closure::bind,在运行时将闭包的作用域临时绑定到当前对象类中,就能合法地调用这些受保护方法。

同时,考虑到不同 Typecho 版本核心可能发生改变,我还增加了一层回退机制——如果类中不存在这两个方法,直接调用底层的 Utils\MarkdownUtils\AutoP 静态工具类。

修复后的 content 方法核心逻辑如下:

\(markdown = method_exists(\)conent, 'markdown')
    ? \Closure::bind(function(\(t) { return \)this->markdown(\(t); }, \)conent, get_class($conent))
    : function(\(t) { return class_exists('\\Utils\\Markdown') ? \Utils\Markdown::convert(\)t) : $t; };

\(autoP = method_exists(\)conent, 'autoP')
    ? \Closure::bind(function(\(t) { return \)this->autoP(\(t); }, \)conent, get_class($conent))
    : function($t) {
        if (class_exists('\\Utils\\AutoP')) {
            $parser = new \Utils\AutoP();
            return \(parser->parse(\)t);
        }
        return $t;
    };

\(text = \)conent->isMarkdown ? \(markdown(\)text) : \(autoP(\)text);

经过此番修改,至少,首页的 Deprecated 报错消失了。

然而,还没高兴半分钟,当我从首页点击某篇文章的标题跳转进详情页时,傻眼了:文章内容区依然一片雪白,一个字都没有显示。

再战 Bug

上面的事情后,朋友资助的Opus4.6额度已经见底,不足以支撑下一步,于是切换 Gemini3.5Flash,让AI再次审查渲染后的 DOM 结构,我发现详情页的文章区域其实有内容,但它们被包裹在了一个隐藏的 textarea 中:

<div class="md_content">

    <textarea style="display:"># 这里是文章的原始 Markdown 源码...</textarea>
</div>

原来,EditorMD 插件默认开启了“前端解析渲染”。它不依赖服务器将 Markdown 转成 HTML,而是直接把 原始Markdown 丢进一个隐藏的文本框,然后在页面底部(Footer区域)注入一段 JavaScript 脚本,在页面加载完毕后调用 editormd.markdownToHTML() 动态在客户端渲染出最终的 HTML 结构。

这套逻辑在传统的“整页刷新”博客上跑得挺好,但在我的二改主题上踢到了铁板。

为了保持页面完整性与不同页面之间的加载连贯性,我引入了 PJAX 技术来实现无刷新页面跳转。

在这种加载模式下:

  • 当用户点击文章链接时,PJAX 只会通过 AJAX 异步获取目标页面的内容,并替换当前页面中指定的容器(如 #middle#right)。

  • 但,关键在于:页面底部的 Script 脚本在 PJAX 局部加载时是不会被重新执行的!

结果就是:用户通过 PJAX 点击进文章页,隐藏的 textarea 被成功加载了,但负责把 Markdown 转换成 HTML 的 JS 渲染引擎根本没有被触发,页面依旧是一片空白。

解法二:更换插件

折腾至此,我就要准备换插件了,我听说另外一个插件EnhancedMarkdown也能比较好的解决这个问题,又不是我就安装了上去。

但是启用之后我才发现,这个插件只会接管网站显示时候的Markdown渲染,对于后台编辑器没有任何支持。

这个解法不合适。

解法三:双插件并存

既然上文提到,EitorMD默认开启接管前端渲染,那我把前端渲染关掉了不就行了?

然后我就随手一关,编辑页面正常了,经过方案一修复的东西让现在首页也完全正常了。

但是回头一想:除了编辑器以外,啥也没更改,那我做 Markdown 的支持做好了吗?不过是在编辑器里如此罢了,实则并没有完全支持。

于是我配合关闭了前端显示的 Editor.MD ,将首页的 MD 渲染器由 EnhancedMarkdown 来接管。然后我又用了几个测试文档来测试功能,一切正常,没有报错,于是这件事告一段落。

三见 Bug

就这样用了几天,看着编辑器支持的部分语法,放到网站就不支持了,而某些编辑器不支持的,网站上又能正常显示,这种体验非常割裂

于是我就想,与其扭捏半天各取一半,占用双倍空间,不如借助 AI,自己完成嫁接整合,各取一半成为一个完整的插件,岂不美哉?

解法四:另起炉灶

借助 AI 帮助我选用用合适的架构,询问 AI,AI分析如下:

候选方案 运行端 技术栈要求 依赖管理与引入方式 社区活跃度与状态
Parsedown
/
ParsedownExtra
服务端 PHP 7.4+ 零依赖。可单文件引入,或通过 Typecho 自定义 Autoloader 加载。 极高稳定性,已进入维护期(不再频繁更新,但非常稳定)。
League
/
CommonMark
服务端 PHP 8.0+ 强依赖 Composer。需将 vendors 打包在插件内,或在服务器执行 composer install 极高,PHP 基金会及主流框架(Laravel)的标准 CommonMark 实现。
markdown-it 客户端 JavaScript 浏览器环境引入 JS 文件(通过 CDN 或本地静态文件)。 极高,JS/Node 领域的行业标准。
markdown-it
(Node.js/Python)
服务端 Node.js / Python 需要服务器安装 Node.js/Python 环境,PHP 通过 exec() 或本地 HTTP API 进行进程间通信。 活跃,但为 PHP 架构引入了异构栈。
EditorMD
(marked.js)
客户端 / 混合端 PHP + JS Typecho 插件接管后台编辑器,前台可选加载 JS 资源进行解析。 经典解析插件,目前处于维护状态。

注意:

1.Typecho 现存方案:当前项目有了 EnhancedMarkdown 插件,其本质就是基于 Parsedown + ParsedownExtra 进行的深度定制(重写了 Parser::blockFencedCodeComplete 以支持 Mermaid、代码高亮、复制按钮,扩展了 containsMarkdownSyntax 保护纯 HTML 标签,实现了 preprocessMath 保护数学公式)。

  1. League\CommonMark:虽然配置极度灵活,但不支持原生“拼音/中文 Slug 生成”,如需实现 TOC 锚点去重和中文拼音定位,需要编写大量自定义扩展(Extension/Renderer)。

  2. 客户端 JS (markdown-it):完全把渲染工作交给了浏览器,功能极其强大(插件多),但会导致 RSS 订阅源、API 接口(XMLRPC)、主题中的“字数统计”和“摘要截取”功能无法获取正确的 HTML 文本,因为数据库里存的是原始 Markdown,而服务器不再解析它。

  3. 工作区现存的 EditorMD 插件:在 EditorMD 插件中,虽然在设置里提供了“接管前台Markdown解析”选项(利用 marked.js 进行前端渲染),但在当前 codebase 中该行为已被定制和修改
    —— 也就是说,由于当前启用的 Jasmine 主题是一个 PJAX 主题,客户端渲染模式会在 PJAX 局部刷新时失效(因为页脚脚本不再重新执行),因此该插件目前在前端已经退化为直接调用服务端的 \Utils\Markdown::convert,前端解析事实上处于旁路/失效状态。

阅读完这一切,我决定使用 EnhancedMrkdown 插件的 Parsedown / ParsedownExtra 作为解析器,将 EditorMD 的对照式编辑器集成到 EnhancedMrkdown 中,事已至此,开工吧。

拦路 Bug 出现

markdown3.png

让 AI 合并了一次插件,再次尝试部署,页面顶部弹出一行冰冷的字:“无法启用插件”。

让 AI 继续排查原因,汇总如下:

Typecho 插件 EnhancedMarkdownPlus 在启用时因 PHP 类找不到而失败。排查发现,插件配置表单的 config() 方法中使用了 new Form\Element\Layout(...) 来插入 <h3> 标题。由于文件头部存在 use Typecho\Widget\Helper\Form,PHP 会将 Form\Element\Layout 解析为 Typecho\Widget\Helper\Form\Element\Layout。然而,Typecho 核心源码的 var/Typecho/Widget/Helper/Form/Element/ 目录下并不存在 Layout.php,该目录只有 TextRadioTextareaSelectFake 等表单组件类。Layout 实际上是 Typecho\Widget\Helper\Layout,属于底层的 HTML 标签封装类,而非表单元素。因此,实例化一个命名空间错误的“幽灵类”触发了致命的 Class Not Found 错误,导致插件无法激活。

杜绝 Bug

考虑到现有的对话上下文污染,为了防止和已有插件产生命名冲突或资源覆盖报错,我采取了最稳妥的策略——让 AI 彻底重写

markdown4.png

  • 将所有 PHP 文件的命名空间完全修改为 TypechoPlugin\EnhancedMarkdownPlus

  • 将后台读取配置的键值全部替换为 EnhancedMarkdownPlus,使得新插件的激活完全独立,不留一丝旧版插件的影子。

  • 将所有需要的文件及静态库彻底整理进新目录,统一在 EnhancedMarkdownPlusASSETS_SUBDIR 下进行管理。

再次打开后台,输入一段 Markdown,后台的分屏预览瞬间亮起,所有公式、图表、高亮和容器无缝呈现,效果与前台丝毫不差!

这次重写,一遍过,干脆利落,没有任何拖泥带水。

总结

“修一个 Bug”往往只是开始,不是结束:修复了 protected 方法调用问题,却发现 PJAX 不兼容;解决了 PJAX,又发现双插件体验割裂。技术债的连锁反应要求我们从全局视角审视方案。

  1. AI 是加速器,不是替代器。
    本次折腾中,AI 在方案对比、代码生成、Bug 排查上提供了巨大帮助,但也引入了命名空间错误。AI 适合快速原型和思路拓展,核心架构决策和关键代码审查必须人工把关

  2. 兼容性优先于炫技: League\CommonMark 功能强大但配置复杂,markdown-it 生态丰富但破坏服务端一致性。最终选择 Parsedown 不是因为最先进,而是因为最契合现有技术栈、最稳定、最可控

  3. 彻底重写有时比重构更省时间:当旧代码存在命名冲突、历史包袱、AI 引入的隐藏错误时,果断另起炉灶,用清晰的边界和独立的命名空间重新组织,反而降低了长期维护成本。

至此,折腾告一段落。现在,前台文章由 Parsedown 引擎在后端一次性高效解析输出,配合 Prism.js 和 KaTeX 提供完美的语法高亮和公式渲染;后台则由 Editor.md 的对照编辑器接管,提供完全还原前台样式的双栏实时预览。文章跳转在 PJAX 的加持下行云流水,整体体验非常丝滑。


相关折腾成果已发布至Github - EnhancedMarkdown_Plus,如有需要自行取用!

评论
森罗幻想. All Rights Reserved. Theme Jasmine_Plus by 罗伊