skip to content
鰭狀漏斗

Markdown Remark 環境下 Callout 的實現

/ 閱讀時間 6 分鐘

在程式文件或部落格中偶爾會出現一些文字方塊,這些文字方塊可能會有邊框、圖示、或是不同的背景顏色,補充本文沒提到的東西,或是凸顯跟其他內容的不同,讓讀者可以注意到。

它們似乎沒有一個統一的名稱。我看過有人這樣稱呼它們:admonition、alert、callout,中文的名稱又更分散了。為了方便起見,下面我就叫它們 callout 吧。

在 Markdown 中也是沒有統一的語法表示 callout,因為 callout 不在 Markdown 語法中,不在標準化的 CommonMark 規格中,也不在事實標準的 GitHub Flavored Markdown Spec 中。所以我找了幾個方法在 Markdown(Remark 生態系)中使用 callout,他們的語法都不太一樣。

使用 blockquote 代替

blockquote 跟 callout 一樣都可以凸顯裡面的文字,所以有些不常引用別人文字的人就直接用 blockquote 取代 callout 的功能。除了會跟引文混淆外,就算 blockquote 純當 callout 用,也只能有一種顏色,無法做出不同種類的 callout。

> 用 blockquote 代替的類 callout

MDX

有一個簡單粗暴的方法是用 MDX,雖然不知道這樣還算不算是在 Markdown 使用。MDX 讓你可以在 Markdown 用 React component,所以只要做一個 callout component 在 MDX 用,我們就可以在 Markdown 顯示 callout 了,例如 React 文件就是這麼做的。

你可以在 MDX import 一個 callout:

import Note from "./callouts/Note";
The following is a note:
<Note>I am a note.</Note>

或是用 components prop 傳進去 callout component。

remark-directive

在 CommonMark 有一個提議想要為自訂的 directive / plugin 創立一個通用的語法,比如讓一個自訂的 container block 表示成這樣:

:::note
Lorem ipsum.
:::

有人將這個提議做成了 remark plugin:remark-directive

這個 plugin 會將這種語法轉換成 Markdown 語法樹(mdast)的一個特殊型別的節點,像上面的範例會轉換成 containerDirective 這個型別的節點。它不是 mdast 正規的型別,所以還需要搭配另外的 plugin 將這個節點轉換成 HTML。

它的缺點是要另外寫一個 Remark plugin 跟它配合,不過應該會比從頭開始寫簡單,因為自訂語法的 parse 已經做好了,而好處就是可以自己決定產生的 HTML 要長怎樣。

不想寫的話也有人已經寫好了可以直接用:@microflash/remark-callout-directives

此外有人仿照這個語法做了一個 plugin:remark-admonitions,它並不依賴 remark-directive,所以也可以直接用。只是這個很久沒更新了,所以不支援最新版本的 Remark 13。

像是 StarlightDocusaurus 都是用 remark-directive 搭配自己寫的 plugin 轉換 callout 的(Starlight 稱它做 aside,Docusaurus 則叫做 admonition)。

GitHub Alert

GitHub 的 Markdown 最近其實有一個 beta 的語法,也是可以產生 callout:

> [!NOTE]
> Highlights information that users should take into account, even when skimming.

這個語法是以 blockquote 為基礎改編的,它在第一行多了一個 [!NOTE] 代表它是一個 callout,這個語法也決定了 callout 的類型,像上述就代表是一個 note callout。

callout 的種類不是只有 note 這個,還有 tip、important、warning、caution,一共 5 個。

Obsidian 也有這個語法,不過種類跟 GitHub 不太一樣,大致上 Obsidian 的 callout 種類比 GitHub 的還要多。

我找到有 6 個 Remark plugin 實現了這個語法,可能還有其他沒被我發現:

其他五個都是將 GitHub 的語法直接轉換成 callout 的 HTML 語法,但是 remark-github-admonitions-to-directives 這個 plugin 不一樣,它是將 GitHub 的語法轉換成 remark-directive 的語法。

這些 plugin 有一個缺點。callout 的呈現方式沒有一個標準,我們不一定要照著 GitHub 上 callout 的 HTML 程式碼和 CSS 樣式轉換,上面列出的 plugin 也有不完全遵照 GitHub 樣式的。但是如果覺得這些 plugin 輸出的 callout 樣式都不滿意,要客製化 callout 的話,只能 fork 其中一個 plugin,然後修改它的輸出。

這個語法的缺點是對非英文使用者不友善,因為他們要知道這些以英文表示的類型是什麼意思才能完整知道這些 callout 種類代表的意涵。如果是 GitHub 原版樣式的 callout 的話,會在 callout 頂端顯示 callout 種類,一樣是英文的,所以不懂英文的讀者可能也會不知道 callout 的用意。

結論

callout 有時候蠻好用的,而且不少人也這麼認為,所以有很多 callout 的實現方式被產生出來。如果只看 Remark plugin,大致就分兩派:remark-directive 和 GitHub 語法。我最後選擇用 GitHub 語法,不過大家可以依照自己的需求選取適合的方案。

其他上面沒提到的參考資料