魔改editormd元件,優化ToC渲染效果

2022-10-20 18:01:05

前言

我的StarBlog部落格目前使用 editor.md 元件在前端渲染markdown文章,但這個元件自動生成的ToC(內容目錄)不是很美觀,我之前魔改過一個樹形元件 BootStrap-TreeView,所以就想要用這個樹形元件來展示ToC。

原本的效果是這樣的

我魔改完的效果

先分析一波

首先看一下 editor.md 這個元件如何渲染 markdown,根據官方的例子

先寫個 textarea 把 markdown 放進去

<div id="post-markdown-content" class="post-content">
    <textarea style="display:none;">@Model.Content</textarea>
</div>

還要一個 div 來放目錄

<div id="post-toc-container"></div>

然後用js呼叫它的渲染方法

let editorMdView = editormd.markdownToHTML("post-markdown-content", {
    htmlDecode: true,
    tocm: true,    // Using [TOCM]
    tocContainer: "#toc-container", // 自定義 ToC 容器層
    emoji: true,
    taskList: true,
    tex: true,  // 預設不解析
    flowChart: true,  // 預設不解析
    sequenceDiagram: true,  // 預設不解析
});

這樣文章內容和目錄就都出來了

我一開始想的是,既然它可以渲染出來目錄,那一定是有一個目錄樹的結構可以用

結果在控制檯 console.log 半天都沒看到這個樹結構

editormd.markdownToHTML 方法返回的資料就單純是一個 div 元素的物件……

開始折騰

無語,沒辦法我只能去看原始碼

整個元件的原始碼都在一個 js 檔案內,幸好可讀性還行~

直接找到 markdownToHTML 方法的定義,在3885行開始,然後找到裡面有個變數 markdownToC ,應該就是我們要的了,列印一下看看,大致結構如下

[
    {"text": "Node1", "slug": "node1", "level": 2},
    {"text": "Django-Dev", "slug": "django-dev", "level": 3},
    {"text": "Java-Dev", "slug": "java-dev", "level": 3},
    {"text": "Spring-Dev", "slug": "spring-dev", "level": 3}
]

不是樹結構,就是個array

就是簡單的對整個markdown檔案進行遍歷,level 欄位根據標題型別來確定,也就是 # 的數量。

而我之前魔改的 Bootstrap-Treeview 元件需要傳入一個樹結構的物件進行渲染,所以我需要進一步處理。

處理樹結構

一開始我頭鐵,直接把 editor.md 生成的那坨東西丟進遞迴裡面

結果沒搞出來

後面換了思路,先把這個 array 改一下,給每個 item 加上 id 和 pid,這樣再來遞迴生成樹就好處理得多了~

先定義節點物件,這個就是 Bootstrap-Treeview 需要用的資料結構

class TocNode {
    constructor(text, href, tags, nodes) {
        this.text = text
        this.href = href
        this.tags = tags
        this.nodes = nodes
    }
}

然後來把 markdownToC 那坨東西遍歷一遍,頂層節點的 pid 設定成 -1,每個節點從 0 開始按順序賦值 id,然後每個節點往前面找 level 少 1 的節點,找到就是父節點,設定 pid ,完事~

let toc = markdownToC
for (let i = 0; i < toc.length; i++) {
    let item = toc[i]
    item.id = i
    item.pid = -1
    for (let j = i; j >= 0; j--) {
        let preItem = toc[j]
        if (item.level === preItem.level + 1) {
            item.pid = j
            break
        }
    }
}

生成樹結構,很簡單不多說了

function getNodes(pid = -1) {
    let nodes = toc.filter(item => item.pid === pid)
    if (nodes.length === 0) return null
    return nodes.map(item => new TocNode(item.text, `#${item.text}`, null, getNodes(item.id)))
}

搞定~

魔改

然後就是根據這個思路,fork了一份程式碼進行魔改。

原版是在 markdownToHTML 方法執行完直接返回一個 div 元素,我則是在返回的 div 元素上新增了兩個屬性,然後再返回 div:

div.markdownToc = markdownToC
div.markdownTocTree = editormd.tocListToTree(markdownToC)
return div

這樣在使用的時候就可以方便的將 Bootstrap-Treeview 元件整合進來了。

用法

先安裝我釋出的NPM包

// 魔改的 editor.md 元件
npm i editor.md-ext
// 魔改的樹形列表元件
npm i bootstrap5-treeview

頁面上引入 CSS

<link rel="stylesheet" href="~/lib/editormd/css/editormd.preview.css">

引入 JS

<!-- jQuery -->
<script src="~/lib/jquery/dist/jquery.min.js"></script>

<!-- 樹形列表元件 -->
<script src="~/js/bootstrap-treeview.js"></script>

<!-- editor.md 需要的依賴 -->
<script src="~/lib/editormd/lib/marked.min.js"></script>
<script src="~/lib/editormd/lib/prettify.min.js"></script>
<script src="~/lib/editormd/lib/raphael.min.js"></script>
<script src="~/lib/editormd/lib/underscore.min.js"></script>
<script src="~/lib/editormd/lib/sequence-diagram.min.js"></script>
<script src="~/lib/editormd/lib/flowchart.min.js"></script>
<script src="~/lib/editormd/lib/jquery.flowchart.min.js"></script>
<script src="~/lib/editormd/editormd.js"></script>

準備一個 div 作為目錄容器

<div id="post-toc-container"></div>

準備一個 div 存放文章內容,把 Markdown 內容放進這個 textarea 裡面。

<div id="post-markdown-content" class="post-content">
    <textarea style="display:none;">@Model.Content</textarea>
</div>

寫一段 JS 程式碼來啟用渲染

$(function () {
    // 渲染文章
    let editorMdView = editormd.markdownToHTML("post-markdown-content", {
        htmlDecode: true,
        tocm: true,    // Using [TOCM]
        emoji: true,
        taskList: true,
        tex: true,  // 預設不解析
        flowChart: true,  // 預設不解析
        sequenceDiagram: true,  // 預設不解析
    });

    // 渲染目錄
    $('#post-toc-container').treeview({
        data: editorMdView.markdownTocTree,
        levels: 2,
        enableLinks: true,
        highlightSelected: false,
        showTags: true,
    })
})

關於樹形列表元件的用法可以看我的 GitHub :https://github.com/Deali-Axy/bootstrap5-treeview

相關地址

GitHub:https://github.com/Deali-Axy/editor.md-ext

NPM:https://www.npmjs.com/package/editor.md-ext