文档索引

内容引用语法 [@ ] 使用指南

在正文里写 [@<内容ID>],渲染时就会自动嵌入剪贴板文字、投票组件、图床图片或收藏夹卡片。 系统根据 ID 长度自动识别内容类型。

音频那一条是例外:它不按长度,而是写成 [@音频/<ID>] 这样一个带名字的形式 (ID 长度早被占满了,见「一、一览表」)。

能用的地方有四处:博客文章正文、云剪贴板正文、评论区、讨论区。四处支持的引用类型不完全一样,见下。


前置条件

  • 需要已登录且通过邀请码认证(Core 用户及以上角色)
  • 四种内容各有各的加载条件:剪贴板与图片需为公开状态,或你本人是作者;投票没有公开 / 私密之分,Core 及以上都能看到任意未删除的投票;收藏夹只能引用公开的(私密收藏夹没有对外 ID,见下)
  • 单篇博客最多嵌入 50 个内容引用(其中收藏夹卡片最多 3 张)

零、四处地方,支持的不一样

6 位收藏夹 8 位剪贴板 9 位投票 10 位图床图片 [@音频/<ID>] 表情包 用户名片
博客正文 ✅ 最多 3 张 ✅ ✅ ✅ ✅ 最多 3 个 ❌ ❌
云剪贴板正文 ✅ 最多 3 张 ✅ ✅ ✅ ✅ 最多 3 个 ❌ ❌
评论区 ❌ 不展开 ✅ 最多 1 条 ❌ 不展开 ✅ 最多 50 张 ✅ 最多 3 个 ✅ 最多 30 个 ✅ 最多 5 张
讨论区 ❌ 不展开 ✅ 最多 1 条 ❌ 不展开 ✅ 最多 50 张 ✅ 最多 3 个 ✅ 最多 30 个 ✅ 最多 5 张

上面那张表说的是你自己写的时候能写什么。读者是谁还另有一层 —— 文章设为 「凭链接可读」或「对外公开」之后,没登录的人读到的正文里:图床图片、音频播放器、 以及公开的剪贴板照常展开;投票与收藏夹保留字面量(它们的数据接口要求核心用户, 对外视图不请求,也就渲染不出来)。私密剪贴板对访客一律不展开 —— 即使你把它引用进了一篇公开文章。这一条的判据是「这条引用的读口,匿名取不取得到」。

评论与讨论里刻意不支持投票:投票是个可点选的交互组件,塞进讨论气泡和楼中楼里既挤又容易误触,要看投票请把链接贴出来。

评论与讨论里也不展开收藏夹,但理由不同:收藏夹卡片是一份列表(标题 + 若干文章链接), 塞进讨论气泡里会占掉半屏,而且讨论区没有「一篇正文慢慢读」的语境。所以在评论区与讨论区里 [@123456] 原样显示为字面量 —— 与投票同样是刻意的,不是漏了。

表情包([@合集/表情])反过来 —— 只在评论与讨论里能用,博客正文与剪贴板正文里 原样显示。它走的是另一套语法的形状(不是 ID 而是名字),详见 表情包使用指南。

用户名片([@用户/<用户名>])与表情同向 —— 也只在评论与讨论里能用,而且同样 不是 ID 而是名字。它渲染成一枚行内名片(带头像框的小头像 + 用户名),点进那个人的主页, 见本文第五节。

另外两处细节差异:

  • 剪贴板引用在博客里可以写很多条;在评论 / 讨论里一条消息只展开第一条,多写的那些原样留在正文里([@abc12345] 就那么显示着)。这是刻意的上限,避免一条 10 个字的短消息展开成几十万字。
  • 剪贴板正文超过 2000 字时,在评论 / 讨论里会被截断,后面接一句「内容过长,已截断」和回原剪贴板的链接。博客正文里不截断。

一、一览表

标题刻意不写「几种」:这张表加过行(表情、名片),而标题不会跟着动 —— 曾经就写着「三种」却列了五种。

语法 ID 长度 内容类型 渲染效果
[@123456] 6 位 收藏夹 渲染成一张卡片(标题 + 条目数 + 前 10 篇链接),仅博客 / 剪贴板
[@a1b2c3d4] 8 位 云剪贴板 将 ID 对应剪贴板的正文直接替换到正文里
[@vOtE12345] 9 位 投票 渲染为可交互的投票小部件(仅博客 / 剪贴板)
[@AbCdEf1234] 10 位 图床图片 渲染成图片 /api/images/ID/raw,可点开放大
[@音频/AbCdEf1234] —— 音频 渲染成播放器 /api/audio/ID/raw(四条管线都支持,最多 3 个,见音频床使用指南)
[@合集/表情] —— 表情包 行内表情图(仅评论 / 讨论,见表情包使用指南)
[@用户/张三] —— 用户名片 行内名片(头像 + 头像框 + 用户名,点进主页)(仅评论 / 讨论,见第五节)

ID 长度不在 6 或 8-10 之间的 [@...] 不会被处理,以原文形式保留。其中 6 位必须全是数字 —— [@abcdef] 这种 6 位字母不会被当成收藏夹,一样原样保留。8-10 位只认字母和数字,区分大小写。

收藏夹只能引用公开的。 私密收藏夹根本没有对外 ID(不是「有 ID 但不显示」), 所以它既没有可写的 [@ID],也生成不出二维码、读不到机器人接口。详见 收藏夹使用指南。

表情包那一行是另一套语法:它不是 ID 而是「合集名 / 表情名」两个名字,允许中文, 用斜杠分隔。两种语法不会互相误伤 —— ID 只认字母数字,而表情 token 必含斜杠。

想展示这个语法本身(而不是让它生效)?把它写进代码块或行内代码(`[@a1b2c3d4]`)—— 代码里的引用一律不展开,四处都一样。


二、剪贴板嵌入(8 位 ID)

获取 ID

访问 /clipboard/,在列表中查看每个剪贴板的 8 位 ID,或从剪贴板详情页 URL /clipboard/<ID> 中获取。

使用方式

在正文中直接写入:

[@a1b2c3d4]

渲染行为

系统向后端请求剪贴板正文内容,将其原样替换 [@a1b2c3d4] 标记。剪贴板中的 Markdown 格式会正常参与后续渲染(标题、列表、代码块等均生效)。

错误处理

如果剪贴板不存在、非公开或无权限访问,该位置会显示:[剪贴板 a1b2c3d4 加载失败]

在站内这不算异常:剪贴板接口要求登录且 Core 以上,权限不够的人看到的就是那句 失败文案。你自己预览时若也看到它,先确认登录状态。

站外读者看到的引用(文章对外公开时)

文章可以设为「对外可见」(见 docs/architecture.md §6.11),站外读者没有账号。 那种情况下正文里的内容引用原样保留字面量 —— 你会看到 [@a1b2c3d4] 这几个字符 原封不动地留在那里,而不是一张卡片、也不是一句失败文案。

这是刻意的,理由有三条:

  • 那些内容本来就只对站内成员开放(剪贴板 / 投票 / 收藏夹三条接口都是 Core 以上), 展开等于把站内内容顺手带出去;
  • 站外读者是一次请求都不发的 —— 连「这句引用没加载出来」都不该让他知道;
  • 与讨论区那条管线的口径一致:权限不够的人看到的引用就是它本来的样子。

所以:如果你打算把一篇文章对外公开,并且希望站外读者也能看懂, 别把关键信息放在 [@…] 引用里 —— 用普通的文字或图片写出来。


三、投票嵌入(9 位 ID)

⚠️ 只在博客正文与云剪贴板正文里生效。 评论区和讨论区不展开投票,[@vOtE12345] 会原样显示。

获取 ID

访问 /vote/ 查看你自己创建的投票列表(最新在前),或从投票详情页 URL /vote/<ID> 中获取。

列表只含你自己创建的投票。要打开别人的投票,用 /vote/ 顶部那栏「搜索跳转」:输入 ID 后点「前往」。

使用方式

[@vOtE12345]

渲染行为

系统请求投票数据并在文章内渲染一个投票小组件,形态随你的投票状态而变:

  • 你还没投过、且投票未锁定:显示标题 + 可点击的选项 + 「投票」按钮,在文章里直接投票,无需离开本页。
  • 你已投过票、或投票已被发起者锁定:显示结果视图 —— 每个选项的票数、百分比与进度条,顶部显示"共 X 票",你投过的那项带 ✓ 高亮。
  • 无论哪种状态,组件底部都有「查看详情」,新窗口打开投票详情页 /vote/<ID>。

投票后组件会重新拉取服务端的票数并就地刷新。

错误处理

如果投票数据加载失败,该位置会显示:[查看投票](可点击跳转到投票页)


四、图床图片嵌入(10 位 ID)

获取 ID

访问 /image/ 查看你上传的图片列表,或在图片详情中获取 10 位 ID。

使用方式

[@AbCdEf1234]

渲染行为

系统把它换成图片地址 /api/images/AbCdEf1234/raw,四处都是这个地址。图片渲染后支持点击原地放大(不新开窗口)。

说明

图片嵌入不需要额外请求 API —— 系统直接拼接图片 URL,浏览器加载时自然请求 /api/images/<ID>/raw。如果 ID 对应图片不存在或已被删除,会显示为损坏图片。

⚠️ 图片必须是公开的(或者看图的人就是上传者本人)。私有图片只有作者与站长能取到,别人看到的是一张裂图 —— 这一点在四处都一样。

评论 / 讨论里发图还有一条更省事的路:点输入区的「从图床选择」,直接弹出你自己的图片列表挑一张,不用去翻 ID、也不用重新上传。


五、用户名片嵌入([@用户/<用户名>])

⚠️ 只在评论区与讨论区生效。 博客正文与剪贴板正文里 [@用户/张三] 原样显示字面量。

在正文里写一个人的用户名,渲染出来是一枚行内名片:带头像(含头像框)的小头像

  • 用户名,点一下进他的主页。
[@用户/张三]

这一条与其他引用都不一样:它认的是名字,不是 ID —— 你不用去翻谁的主页地址栏。 用户名在本站不可修改(注册之后没有任何改名的入口),所以今天写的名片明天还指得准。

怎么插入

评论区与讨论区的输入区工具栏上有一颗名片按钮:点开 → 搜用户名 → 选一个人, token 就落在光标处。不会替你发送(草稿还在,接着写完再发),也不会多带空格。

手打一样有效 —— 上面那行直接敲进去就行。

规则

  • 一条消息最多 5 张名片,多出来的原样显示为 [@用户/…]。
  • 名字打错 / 查无此人 → 原样显示字面量(不报错、不留半张卡)。所以看到原文 token 就是「这个人没找到」,不是页面坏了。
  • 只认核心用户:输入区那个选择器搜得到的人是 core 及以上;非核心账号仍可以 手打 token 发名片,只是选择器搜不到。
  • 不算 @ 提及:对方不会收到通知,铃铛与讨论红点都不动。想叫他就照常写 @张三 。
  • 写在代码块 / 行内代码里的不展开(与其余引用一致)—— 想展示这个语法本身就这么写。
  • 名片里的头像是站内那条恒定可推导的地址,所以「没上传过头像」的人也会有一张 (系统生成的图案),不会是裂图。

与表情的「用户」合集

[@用户/张三] 与表情 [@合集/表情] 形状完全一样,所以 用户 这个名字归名片 —— 站长的表情合集不能叫「用户」(那个目录会被扫盘跳过)。见 docs/guide/表情包使用指南.md。


六、技术实现简述

博客详情页加载时的处理管道:

博客 Markdown 正文
        │
        ▼
ContentRefProcessor.preprocess()      (MarkdownRenderer.tsx 内部类)
  正则匹配 /\[@\s*(\w+)\s*\]/g
  按 ID 长度分类,并行获取数据
  将 [@id] 替换为对应内容
        │
        ▼
最后单独一趟:展开收藏夹卡片
  重新扫当前位置,**按区间切片**替换
        │
        ▼
MarkdownRenderer 渲染(客户端)
  marked.parse() → HTML
  DOMPurify 清理 → 安全渲染
  highlight.js 代码高亮
  MathJax 数学公式渲染
        │
        ▼
扫描 .vote-embed[data-vote-id] 元素
  逐个拉取数据、渲染只读结果条
  (同一个 MarkdownRenderer 内的 DOM 后处理,不是独立组件)

收藏夹卡片为什么单独一趟、且按位置切片:卡片里含文章标题(别人写的), 标题里若正好有 [@8位] 字样,按内容替换(String.replace)会命中插入内容里的那处, 于是卡片自己又被解释一遍。放在最后做、且此后不再扫描,就不可能出现这种情况。

缓存

预处理器内部使用 Map 缓存已获取的内容,同一 ID 在文章中多次出现只请求一次。

一篇文章最多替换 50 处引用(各类型合计)—— 超出部分原样保留 [@id] 字面量。注意这是 「替换条数」的上限,不是缓存容量:出现在文中的不同 ID 仍会全部发起请求。

收藏夹另有更紧的上限:最多 3 张卡片。 卡片是块级元素,比一段文字高得多,插十几张 会把文章冲成一片卡片墙;扫描出的第 4 张起原样保留 [@123456] 字面量。

并行请求

多个 [@id] 的剪贴板、投票与收藏夹数据并行获取,不会串行阻塞。图片无需请求。

评论 / 讨论走的是另一条管道

那边不共用上面这套(它带着投票组件与代码高亮,是博客专用的)。两条管道的差别:

博客 / 剪贴板 评论 / 讨论
剪贴板 解析前替换成正文,一起过 marked 取到正文后当普通字符串交给渲染器
图片 换成 Markdown 图片语法 净化之后由代码把 [@id] 直接建成 <img>
投票 内联可交互组件 不展开
收藏夹 解析前替换成卡片 HTML(最后单独一趟) 不展开
表情包 不展开(原样显示) 同图片那条:净化之后建成 <img>
缓存 单次渲染内去重 模块级缓存 + 并发去重,5 分钟过期

之所以图片那条要绕一下:评论与讨论的正文白名单里刻意没有 <img>(防止有人贴外链图片做 跟踪像素、泄露访客 IP)。为了不拆掉这条防线又要让图床图片出来,实现是「净化完成后,由我们 自己的代码把 [@id] 建成图片元素」—— 所以能出现的图片只有站内图床这一种形态,而手写的 <img> 标签仍会被当成普通文字显示出来。在博客正文里手写 <img> 是允许的,在评论和讨论里 不是 —— 这一条是刻意的,不是漏了。

表情图走的是完全同一条旁路:净化之后由我们自己的代码建成 <img>,所以手写的 <img> 在评论与讨论里照旧被转义。区别只有两点 —— 表情多一个「图片加载失败就换回 纯文本」的兜底(见表情包使用指南第五节),且它用的是另一个类名 (rich-sticker-ref),所以点表情不会弹出看图器。


七、注意事项

  1. 非用户提及:[@ ] 语法不是 @用户 功能,不能用来提及或通知其他用户。
  2. 数量限制:单篇文章最多替换 50 个引用标记,超出部分保留原文;评论 / 讨论里剪贴板最多 1 条、图片最多 50 张。
  3. 权限检查:私有剪贴板只有作者本人可以在文章中引用;其他人引用会加载失败。图片同理 —— 私有图对别人是裂图。
  4. ID 大小写:ID 区分大小写(包含大小写字母和数字),请完整复制。
  5. 编辑器中不可预览:在 Vditor 编辑器中编写时,[@id] 以文本形式显示,仅在发布后的博客详情页才会渲染为嵌入内容。
  6. 剪贴板内容为纯文本替换:嵌入的剪贴板 Markdown 会成为文章 Markdown 的一部分,注意避免引入破坏性的格式(如未闭合的代码块)。
  7. 代码块里不展开:四处都一样 —— 想展示语法本身,就写进代码块或行内代码。

本页内容来自仓库 docs/guide/内容引用语法指南.md·在 GitHub 上查看