對於創作平臺來說內容編輯器是十分重要的功能,強大的編輯器可以讓創作者專注於創作“筆”下生花。而最好取悅程式設計師創作者的方法之一就是支援 Markdown 寫作,因為大多數程式設計師都是用 Markdown 來寫文章。

Markdown 作為程式設計師寫作的心頭愛,有很多優點:

  • 通過語法實現排版,不需要點選手動設定樣式

  • 快速實現複雜內容,如:程式碼塊、超連結、公式等

  • 讓創作者有更多時間專注於內容

但,同樣的也有些缺點:

  • 有一定的學習門檻,對於非程式設計師不太友好

  • 看原文件就像看“程式碼”,預覽效果需要工具或編輯器支援

那有沒有能夠即保留 Markdown 帶來的便利,同時又降低門檻的辦法呢?大多數老玩家會脫口而出:Typora

Typora 直接使用完全沒有問題,但由於它沒有開源。如果想在自己的專案實現類似的 Markdown 編輯器,就需要另尋方案了。

如果你正在尋找功能強大、易於接入、所見即所得的 Markdown 編輯器、元件、外掛,就請花 5 分鐘讀完本文!

接下來 HelloGitHub 帶來的開源專案完全滿足上述需求。Milkdown 一款高顏值+自由(外掛)的所見即所得,集合 Markdown 編輯器、元件、外掛於一身的開源專案。

https://github.com/Saul-Mirone/milkdown

你想要的功能它都有,不要的功能也可以通過刪減外掛,減少體積。外掛的設計思想+完善的中文文件,讓你分分鐘定製出最適合自己的 Markdown 編輯器!

下面跟著專案作者一起來感受 Milkdown 的魅力吧。

一、上手

下面提供了 2 種方式,可直接體驗:

線上嘗試:https://milkdown.dev/#/online-demo

VS Code 外掛:https://marketplace.visualstudio.com/items?itemName=mirone.milkdown

1.1 功能展示

方便的編寫表格:

直接貼上和複製 Markdown 文字:

甚至協同編輯:

雙欄 Markdown 編輯器很常見。但 雙向繫結 的 Markdown 編輯器,目前僅此一家:

功能方面就介紹這麼多,下面用 Milkdown 輕鬆實現個編輯器。

1.2 第一個編輯器

Milkdown 的核心以及各種外掛都是獨立的 NPM 包,可以直接通過 NPM 來進行安裝。

npm i @milkdown/core @milkdown/preset-commonmark @milkdown/theme-nord

上手也十分簡單:

import { Editor } from '@milkdown/core';
import { nord } from '@milkdown/theme-nord';
import { commonmark } from '@milkdown/preset-commonmark'; Editor
.make()
.use(nord)
.use(commonmark)
.create();

我們先使用 make 來初始化編輯器,然後使用 use 來載入外掛,最後使用 create 來建立編輯器。

1.3 豐富的外掛

外掛是 Milkdown 的核心,它本質上就是一個外掛載入器,一切功能都是通過外掛來提供的。表格是一個外掛、主題是一個外掛、甚至一行簡單的文字也是一個外掛。

目前官方已經提供了許多外掛,確保可以開箱即用。下面僅列舉了部分外掛:

名稱 描述
plugin-clipboard 新增 markdown 格式的複製貼上能力
plugin-cursor 新增 drop 和 gap 游標
plugin-listener 新增監聽器支援
plugin-collaborative 新增協同編輯支援
plugin-table 新增表格語法支援(已經包含在 gfm 中)
plugin-prism 新增 prism 用於支援程式碼塊高亮

也可以自己動手編寫外掛,更多詳情

二、技術棧

Milkdown 基於下面的工具實現:

  • Prosemirror:一個用於在 web 端構建富文字編輯器的工具包
  • Remark:正確的 Markdown 解析器
  • TypeScript:以 TypeScript 編寫
  • Emotion:用於構建樣式的強大的 css in js 工具
  • Prism:程式碼塊支援
  • Katex:高效能的渲染數學公式

富文字編輯器本身是一個天坑。雖然 ContentEditable 看起來很美好,但實際用起來就會發現問題層出不窮。因此我們基於 Prosemirror 來實現富文字編輯器。因為它足夠成熟、久經工業的錘鍊,並且擁有良好的架構和 API 設計。

三、架構

Prosemirror 的核心邏輯其實類似於 React,它通過一種函式式的資料對映來體現編輯器的 UI 和內部狀態的關係,如圖:

編輯器通過 EditorState 來儲存當前狀態,並由 EditorState 產生出 EditorView,即 UI 檢視。 使用者在 UI 檢視上進行的操作最終會產生 DOM event,例如:input 事件、click 事件。DOM event 事件會產生 Transaction,代表了對 State 的修改,類似於 Redux 或 Vuex 中的 Action。 這些 Transaction 會與原來的 EditorState 進行計算,產生新的 EditorState,如此迴圈。

Prosemirror 通過這樣的方式將編輯器中的每個狀態以 EditorState 的方式儲存了下來,它是一顆樹狀結構。而有一點編譯原理基礎的朋友都知道,任何程式語言都有對應的 AST(抽象語法樹)。因此我們需要的就是建立 Prosemirror 中的 EditorState 與 Markdown 的抽象語法樹之間的聯絡。 Remark 完美契合我們的需求,因為它有設計良好的 AST,並且易於擴充套件自己的語法。

這樣一來 Milkdown 的架構也逐漸清晰:

Markdown <-> Remark AST <-> Prosemirror State <-> UI

四、結語

在開始這個專案前,我嘗試過各種各樣的 Markdown 編輯器,但沒有找到一款特別滿意的。因為它們都是閉源,而且功能由開發商提供,有的功能太過於臃腫、有的又太過簡單。 既然這樣,我索性自己做一款能夠輕鬆定製功能,非程式設計師也能輕鬆使用的 Markdown 編輯器,也就有了大家看到的 Milkdown。

希望開源的 Milkdown 讓使用者有更自由的選擇,打破 Markdown 編輯器的“壟斷”。開源不易如果 Milkdown 對您有幫助,也請給個 Star。

https://github.com/Saul-Mirone/milkdown

最後,感謝 HelloGithub 的支援和幫助。Milkdown 先是有幸入選了 第 65 期 月刊,然後受邀合作了這篇文章,讓更多人知道我的開源專案。


關注 HelloGitHub 公眾號 第一時間收到更新。

還有更多開源專案的介紹和寶藏專案等待你的發現。