过了几年了我才发现原来 Typecho 不支持这种下标式超链接设置 title 属性:
[应该有 title 的链接][1] >-< [没 title 的链接][2]
[1]: http://example.com "Example"
[2]: http://example.com/2这次偶然点了自己博客文章的链接才发现,这样会把可选的 title(即上面引号里的内容)当作链接一块解析。过程中会去除不安全的特殊符号,最终会变成指向 http://example.comexample 之类的超链接。搜了一下网上和 issue 没有人提,确定了 Markdown 是有这种语法,不想一个个改文章链接,遂自己从源码层面解决。
最开始按照我转载的《Typecho 中使 Markdown 文章的超链接在新窗口打开》,使劲改 var/CommonMark/HtmlRenderer.php,发现没用。一度以为是 Opcache 缓存原因,直到查看 GitHub 上的源码,后面我才意识到新版本 Typecho 渲染 Markdown 已经不在这了。用 IDE 搜了一下很快确定位置在 var/Utils/HyperDown.php。
瞄了几眼知道几件事。图像是能解析标题的,甚至行内的超链接也能解析标题(正则表达式我让 LLM AI 读的),但下标式的就没实现。貌似 _definitions 是用来存储下标文本的数组。不过这个下标文本经过了 cleanUrl 处理。于是另外加一个变量存储原始值;后来又看到 _definitions 拢共没几处调用,索性在原函数上改,在调用的地方手动包裹 cleanUrl。仿照行内超链接的写法加一个处理标题的逻辑。如此这般向官方提交了一个 pull request。
在合并之前如果你也想要这个逻辑,可以按照我的 commit 去改。
出于好奇搜了一下,我发现 GitHub 竟然能提供 .patch 和 .diff 文件,只需要在 commit url 后加对应的后缀名就能访问到,算是一个隐藏功能。于是也可以下载 commit 为 .patch 文件,然后进入 Typecho 目录用 patch 命令修补:
patch -p0 ./var/Utils/HyperDown.php < /your/d02bf06074e0fb7bbc89699b1bc2fb7a8c0317ed.patch如果你网络环境不佳,我在这里直接贴出 patch 文件内容:
From d02bf06074e0fb7bbc89699b1bc2fb7a8c0317ed Mon Sep 17 00:00:00 2001
From: shansing <i@shansing.com>
Date: Wed, 14 Jan 2026 20:40:15 +0800
Subject: [PATCH] fix: optional title attribute in Markdown reference-style
links
---
var/Utils/HyperDown.php | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/var/Utils/HyperDown.php b/var/Utils/HyperDown.php
index b446bc617b..5d03e72e72 100644
--- a/var/Utils/HyperDown.php
+++ b/var/Utils/HyperDown.php
@@ -506,7 +506,7 @@ function ($matches) {
$escaped = htmlspecialchars($this->escapeBracket($matches[1]));
$result = isset($this->_definitions[$matches[2]]) ?
- "<img src=\"{$this->_definitions[$matches[2]]}\" alt=\"{$escaped}\" title=\"{$escaped}\">"
+ "<img src=\"{$this->cleanUrl($this->_definitions[$matches[2]])}\" alt=\"{$escaped}\" title=\"{$escaped}\">"
: $escaped;
return $this->makeHolder($result);
@@ -536,10 +536,12 @@ function ($matches) {
$escaped = $this->parseInline(
$this->escapeBracket($matches[1]), '', false
);
- $result = isset($this->_definitions[$matches[2]]) ?
- "<a href=\"{$this->_definitions[$matches[2]]}\">{$escaped}</a>"
- : $escaped;
-
+ $result = $escaped;
+ if (isset($this->_definitions[$matches[2]])) {
+ [$url, $title] = $this->cleanUrl($this->_definitions[$matches[2]], true);
+ $title = empty($title) ? '' : " title=\"{$title}\"";
+ $result = "<a href=\"{$url}\"{$title}>{$escaped}</a>";
+ }
return $this->makeHolder($result);
},
$text
@@ -992,7 +994,7 @@ private function parseBlockFootnote(?array $block, int $key, string $line): bool
private function parseBlockDefinition(?array $block, int $key, string $line): bool
{
if (preg_match("/^\s*\[((?:[^\]]|\\]|\\[)+?)\]:\s*(.+)$/", $line, $matches)) {
- $this->_definitions[$matches[1]] = $this->cleanUrl($matches[2]);
+ $this->_definitions[$matches[1]] = $matches[2];
$this->startBlock('definition', $key)
->endBlock();从稳定版到我提交 PR 的这段期间这个文件几乎没怎么变动,实测对于最新正式稳定版(v1.2.1)也能正常修补,不一定需要是开发版。用 patch 命令不会去校验 git 生成的文件摘要。