前几天策划写了一个word文档来整理补充一些内容,当时就比较感慨,如果是Web文档就好了,可以超链接过去,也不必东一个文档,西一个文档的查找。

确实如此,工作这么久,各个公司的策划案都是使用的Word文档的形式给别的部门,策划写起来是方便了,但是其他岗位的人要看文档来制作就比较麻烦了,比如程序,一个项目中可能会有几十甚至几百个文档,这些文档之间的内容可能会有相互关联,使用WORD文档,可能过段时间,策划自己都不知道放哪个文档了,于是就出了各种整理文档,补充文档。

使用Web来管理文档是好,但要让策划直接编写网页制作策划案,我想应该基本上没人响应。那就只能迂回一点,平常写博客这类的文档,使用最多的就是Markdown了,而且目前使用Markdown格式作为各种网络文档也是非常常见的,比如github、gitlab、CSDN、各种笔记类APP等等。

有了使用Web来管理策划案的想法后,就着手查资料开干。 查下来目前比较常见的工具有:

笔者之前写过一篇 《使用jekyll写博客》,就是介绍的使用jekyll来编写 github的博客。 jekyll使用的ruby语言来构建静态网页,要使用这个工具,搭建语言环境比较麻烦,笔者在MacOS上为了使用jekyll,记得卡在了ruby的版本上,要求高版本,但OS又支持不了高版本,就此作罢,只能Windows上用用。

其它工具就不一一介绍了,都做了超链接,感兴趣的读者可以自行点开了解。今天要介绍是主角是Docsify,它与Docute一起成为与其它工具不一样的选择。

Docsify与Docute都是基于 Vue,且它们都是完全的运行时驱动,不会生成静态html,因此对 SEO 不够友好。如果你并不关注 SEO,同时也不想安装大量依赖,它们将是非常好的选择!在公司内部做策划文档的管理,不需要SEO,所以使用运行时驱动的Docsify或者Docute是完全可以的。之所以选择Docsify,一是因为最先发现它,二是它比Docute小,插件这些也比较丰富。

下面就介绍一下如何使用docsify构建gitlab Pages上的文档库。

一、开启gitlab pages

编辑/etc/gitlab/gitlab.rb,设置pages_external_url为自定义的域名,并且设置gitlab_pages[‘enable’]为true,如果要打开访问控制,则需要设置gitlab_pages[‘access_control’]为true,如下所示:

 1################################################################################
 2## GitLab Pages                                                             
 3##! Docs: https://docs.gitlab.com/ee/pages/administration.html                          
 4################################################################################
 5                                                                              
 6##! Define to enable GitLab Pages                                           
 7pages_external_url "http://pages.io/"                                                                   
 8gitlab_pages['enable'] = true 
 9
10##! Pages access control                                                                                    
11gitlab_pages['access_control'] = true                    

设置好后运行下面的命令生效:

1gitlab-ctl reconfigure

二、gitlab网站设置

1. 访问控制

如果需要访问控制,则需要: 在“菜单”=>“管理员”=>“设置”=>“偏好设置”中展开Pages选项,勾选

  • 要求用户证明自定义域名的所有权
  • 禁止公开访问 Pages 站点

如下图所示:

最大页面大小默认为100M,可以根据实际情况调整。设置完成后记得执行“保存修改”。

2. 项目设置

在开启Pages后,项目设置,通用里会根据项目可见性自动设置Pages的可见性,并且项目设置里会看到Pages栏,如下图所示:

此时还没有任何可用页面。

三、编写网页

使用docsify+markdown文件来构建网页,只需要编写一个index.html即可。其它的就是根据docsify规则 编写markdown文件。

本地编写markdown文件建议使用VSCode+Office Viewer(Markdown Editor)插件,Office Viewer(Markdown Editor)插件使用了 vditor markdown编辑器,不得不说该编辑器非常强大。

如果想在本地编写好后,预览网页,满意后,再发布到gitlab pages。则需要在本地安装相应的环境。先安装 node.js,再根据 官方文档创建相应的项目。

具体如何编写这里就不再赘述了,可以看 官方文档。官方文档给出了一个插件列表 awesome-docsify,里面有很多插件,可以丰富docsify。

这里主要介绍一下官网上没有的或者需要注意的东西。

docsify内置的 Markdown 解析器是 marked,对markdown的支持有限,像数学公式,mermaid图这些都不支持,官网上对 markdown的配置所提甚少,只提供了mermaid图的配置,数学公式没有提及。

下面结合 awesome-docsify中提供的插件,介绍一下几种常用的额外功能配置:

1. mermaid图

直接使用插件 docsify-mermaid

1	<!--mermaid图支持-->
2    <script src="//unpkg.com/mermaid/dist/mermaid.js"></script>
3    <script src="//unpkg.com/docsify-mermaid@latest/dist/docsify-mermaid.js"></script>
4    <script>
5      mermaid.initialize({ startOnLoad: true });
6    </script>

2. plantuml图

直接使用插件 docsify-plantuml 或者插件 docsify-puml

1<script>
2      window.$docsify = {
3		plantuml: {
4          skin: "classic",
5        },
6     }
7</script>     
8<!--plantuml图支持-->
9<script src="//unpkg.com/docsify-plantuml/dist/docsify-plantuml.min.js"></script>

3.数学公式

数学公式,这里需要提一下,markdown中的数学公式有几种格式,不同的平台支持的写法也不一致。 常见的markdown数学公式有以下几种写法:

  • 以$加`开头,并以反序结尾的行内公式(gitlab支持,vditor会多显示一对单引号)
  • 以$开头和结尾的行内公式(gitlab不支持,vditor支持)
  • 以$$开头和结尾的块公式(gitlab不支持,vditor支持)
  • 以math块标识的块公式(gitlab支持,vditor支持)

有一个插件 docsify-katex 支持使用katex输出数学公式,但是试用了一下,没达到预期。docsify有一个 issues提到怎样支持数学公式,但不尽如意。

笔者通过查资料和自己的修改得出如下配置:

 1<script>
 2      window.$docsify = {
 3      plugins: [
 4          function (hook) {
 5            // 支持$和$$
 6            hook.doneEach(function () {
 7              if (typeof MathJax !== "undefined") {
 8                MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
 9              }
10            });
11            // 支持$``$
12            hook.beforeEach(function (content) {
13              return content.replace(/\$`.*`\$/g, function (a) {
14                return a.replace("`", "").replace("`", "");
15              });
16            });
17            // 支持```math```
18            hook.beforeEach(function (content) {
19              return content.replace("\r\n","\n");
20            });
21            hook.beforeEach(function (content) {
22              return content.replace("\r","\n");
23            });
24            hook.beforeEach(function (content) {
25              c = content.replace(
26                /```math\n[^`]*\n```/g,
27                function (a) {
28                  return a
29                    .replace("```math", "\$\$\$\$")
30                    .replace("```", "\$\$\$\$");
31                }
32              );
33              return c
34            });
35          },
36        ],
37      }
38</script>
39
40<script type="text/x-mathjax-config">
41      MathJax.Hub.Config({
42      tex2jax: {
43        inlineMath: [['$','$'], ['\\(','\\)']],
44        displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
45        processClass: 'math',
46        processEscapes: true
47      },
48      TeX: {
49      equationNumbers: { autoNumber: ['AMS'], useLabelIds: true },
50      extensions: ['extpfeil.js', 'mediawiki-texvc.js'],
51      Macros: {bm: "\\boldsymbol"}
52      },
53      'HTML-CSS': { linebreaks: { automatic: true } },
54      'preview-HTML': { linebreaks: { automatic: true } },
55      SVG: { linebreaks: { automatic: true } }
56      });
57</script>
58
59<!--MathJax数学公式支持-->
60<script src="//cdn.bootcss.com/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>

下图为数学公式示例输出:

四、预览网页

预览网页只需要在项目所在目录的命令行运行:

1docsify serve

五、发布到gitlab pages

1. 编写.gitlab-ci.yml

为了让gitlab pages能正常显示网页,需要使用到gitlab的CI/CD功能,在项目根目录创建一个.gitlab-ci.yml文件,也可以在gitlab网站上新建,选择.gitlab-ci.yml模板,应用html模板。

或者把生成的内容复制到本地建。

 1pages:
 2  stage: deploy
 3  script:
 4    - mkdir .public
 5    - cp -r * .public
 6    - mv .public public
 7  artifacts:
 8    paths:
 9      - public
10  rules:
11    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

这样每次提交更改后,gitlab都会自动更新页面。 成功后就可以在项目设置的Pages下看到网页的访问地址了:

2. 修改hosts

由于前面在配置gitlab.rb时pages_external_url是随便配置的,为了能正常访问,需要将之映射到gitlab所在的ip,修改C:\Windows\System32\drivers\etc\hosts,添加映射:

1192.168.1.6 docs.pages.io
2192.168.1.6 projects.pages.io

docs.pages.io是平常访问用到的域名,其中的docs是gitlab中项目所在的组或者用户 projects.pages.io是访问控制授权时需要用到的域名。

六、附index.html源码

  1<!DOCTYPE html>
  2<html lang="en">
  3  <head>
  4    <meta charset="UTF-8" />
  5    <title>Document</title>
  6    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
  7    <meta name="description" content="Description" />
  8    <meta
  9      name="viewport"
 10      content="width=device-width, initial-scale=1.0, minimum-scale=1.0"
 11    />
 12    <link
 13      rel="stylesheet"
 14      href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css"
 15    />
 16  </head>
 17
 18  <body>
 19    <div id="app"></div>
 20    <script>
 21      window.$docsify = {
 22        loadNavbar: true,
 23        loadSidebar: true,
 24        loadSidebar: "summary.md",
 25        autoHeader: true,
 26        subMaxLevel: 3,
 27        auto2top: true,
 28        name: "主页",
 29        homepage: 'index.md',
 30        basePath: 'docs',
 31        repo: "http://192.168.1.6/",
 32
 33        pagination: {
 34          previousText: "上一篇",
 35          nextText: "下一篇",
 36          crossChapter: true,
 37          crossChapterText: false,
 38        },
 39
 40        count: {
 41          countable: true,
 42          fontsize: "0.9em",
 43          color: "rgb(90,90,90)",
 44          language: "chinese",
 45        },
 46
 47        search: {
 48          maxAge: 864, // 过期时间,单位毫秒,默认一天
 49          placeholder: "搜索",
 50          noData: "找不到结果",
 51          namespace: "文档",
 52        },
 53
 54        plantuml: {
 55          skin: "classic",
 56        },
 57
 58        timeUpdater: {
 59          text: ">最后更新时间: {docsify-updated}",
 60          formatUpdated: "{YYYY}/{MM}/{DD} {HH}:{mm}",
 61        },
 62
 63        plugins: [
 64          function (hook) {
 65            // 支持$$
 66            hook.doneEach(function () {
 67              if (typeof MathJax !== "undefined") {
 68                MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
 69              }
 70            });
 71            // 支持$``$
 72            hook.beforeEach(function (content) {
 73              return content.replace(/\$`.*`\$/g, function (a) {
 74                return a.replace("`", "").replace("`", "");
 75              });
 76            });
 77            // 支持```math```
 78            hook.beforeEach(function (content) {
 79              return content.replace("\r\n","\n");
 80            });
 81            hook.beforeEach(function (content) {
 82              return content.replace("\r","\n");
 83            });
 84            hook.beforeEach(function (content) {
 85              c = content.replace(
 86                /```math\n[^`]*\n```/g,
 87                function (a) {
 88                  return a
 89                    .replace("```math", "\$\$\$\$")
 90                    .replace("```", "\$\$\$\$");
 91                }
 92              );
 93              return c
 94            });
 95          },
 96        ],
 97      };
 98    </script>
 99    <script type="text/x-mathjax-config">
100      MathJax.Hub.Config({
101      tex2jax: {
102        inlineMath: [['$','$'], ['\\(','\\)']],
103        displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
104        processClass: 'math',
105        processEscapes: true
106      },
107      TeX: {
108      equationNumbers: { autoNumber: ['AMS'], useLabelIds: true },
109      extensions: ['extpfeil.js', 'mediawiki-texvc.js'],
110      Macros: {bm: "\\boldsymbol"}
111      },
112      'preview-HTML': { linebreaks: { automatic: true } },
113      SVG: { linebreaks: { automatic: true } }
114      });
115    </script>
116    <!-- Docsify v4 -->
117    <script src="//cdn.jsdelivr.net/npm/docsify"></script>
118
119    <!--MathJax数学公式支持-->
120    <script src="//cdn.bootcss.com/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
121
122    <!--plantuml图支持-->
123    <script src="//unpkg.com/docsify-plantuml/dist/docsify-plantuml.min.js"></script>
124
125    <!--mermaid图支持-->
126    <script src="//unpkg.com/mermaid/dist/mermaid.js"></script>
127    <script src="//unpkg.com/docsify-mermaid@latest/dist/docsify-mermaid.js"></script>
128
129    <!--字数统计-->
130    <script src="//unpkg.com/docsify-count/dist/countable.js"></script>
131
132    <!-- emoji表情支持 -->
133    <script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/emoji.min.js"></script>
134    <!-- 搜索功能支持 -->
135    <script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
136    <!--在所有的代码块上添加一个简单的Click to copy按钮来允许用户从你的文档中轻易地复制代码-->
137    <script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>
138    <!-- 图片放大缩小支持 -->
139    <script src="//unpkg.com/docsify/lib/plugins/zoom-image.min.js"></script>
140    <!--分页支持-->
141    <script src="//unpkg.com/docsify-pagination/dist/docsify-pagination.min.js"></script>
142    <!-- 更新时间支持 -->
143    <script src="https://cdn.jsdelivr.net/npm/docsify-updated@1/src/time-updater.min.js"></script>
144
145    <!--语法高亮-->
146    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js"></script>
147    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-c.min.js"></script>
148    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-cpp.min.js"></script>
149    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-csharp.min.js"></script>
150    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-go.min.js"></script>
151    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-go-module.min.js"></script>
152    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-cmake.min.js"></script>
153    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-lua.min.js"></script>
154    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-mermaid.min.js"></script>
155    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-protobuf.min.js"></script>
156    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-docker.min.js"></script>
157    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-java.min.js"></script>
158  </body>
159</html>

其实我们可以直接使用vditor来渲染,这样就能与VSCode中的表现完全一致(都是使用的vditor),但是docsify的其它插件就用不了了,同时图片的URL生成也有问题,显示不了本地图片。记录一下配置:

 1  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vditor/dist/index.css" />
 2  <script src="https://cdn.jsdelivr.net/npm/vditor/dist/index.min.js"></script>
 3  <script>
 4    window.$docsify = {
 5      plugins: [
 6        function (hook) {
 7          hook.beforeEach(function (content) {
 8            // 获取内容输出的节点
 9            const previewElement = document.getElementById('main')
10            Vditor.preview(previewElement, content, {
11              markdown: {
12                toc: true,
13                // 使用vditor来渲染Markdown最好不使用history模式,否则可能引起路由错误
14                // linkBase这个设置对Hash路由方式非常重要
15                linkBase: '#', 
16              },
17              speech: {
18                enable: true,
19              },
20              math: {
21                // VSCode的Office Viewer(Markdown Editor)插件使用的是'KaTeX'引擎,
22                // 这里与之保持一致,当然也可以使用'MathJax'引擎
23                engine: 'KaTeX', 
24                //engine: 'MathJax',
25              },
26            })
27          })
28        }
29      ]
30    }
31  </script>

经过多番研究,使用docsify插件,配置出了比Vditor更强大的配置,支持

  • LaTex数学公式
  • mermaid图
  • dot图
  • plantuml图
  • graphviz图
  • mindmap图
  • echarts图表
  • charty图表

记录一下: index.html:

  1<!DOCTYPE html>
  2<html lang="en">
  3  <head>
  4    <meta charset="UTF-8" />
  5    <title>文档库</title>
  6    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
  7    <meta name="description" content="Description" />
  8    <meta
  9      name="viewport"
 10      content="width=device-width, initial-scale=1.0, minimum-scale=1.0"
 11    />
 12    <link
 13      rel="stylesheet"
 14      href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css"
 15    />
 16    <!-- 支持 LaTex 语言 -->
 17    <link
 18      rel="stylesheet"
 19      href="//cdn.jsdelivr.net/npm/katex@latest/dist/katex.min.css"
 20    />
 21
 22    <link
 23      rel="stylesheet"
 24      href="//cdn.jsdelivr.net/npm/docsify-dark-mode@latest/dist/style.min.css"
 25    />
 26
 27    <link
 28      rel="stylesheet"
 29      href="https://unpkg.com/docsify-toc@1.0.0/dist/toc.css"
 30    />
 31  </head>
 32
 33  <body>
 34    <!-- markmap is based on d3, so must load those files first. -->
 35    <script src="//unpkg.com/d3@3/d3.min.js"></script>
 36    <script src="//unpkg.com/markmap@0.6.1/lib/d3-flextree.js"></script>
 37    <script src="//unpkg.com/markmap@0.6.1/lib/view.mindmap.js"></script>
 38    <link
 39      rel="stylesheet"
 40      href="//unpkg.com/markmap@0.6.1/style/view.mindmap.css"
 41    />
 42
 43    <div id="app">拼命加载中……</div>
 44
 45    <script>
 46      window.$docsify = {
 47        loadNavbar: true,
 48        loadNavbar: "navbar.md",
 49        loadSidebar: true,
 50        loadSidebar: "sidebar.md",
 51        loadFooter: true,
 52        loadFooter: "footer.md",
 53        autoHeader: true,
 54        subMaxLevel: 3,
 55        auto2top: true,
 56        mergeNavbar: true,
 57        topMargin: 0,
 58        name: "文档库",
 59        //repo: "http://127.0.0.1/",
 60
 61        //themeColor: "#42b983",
 62        tabs: { persist: true, theme: "material" },
 63
 64        // 图表
 65        charty: {
 66          theme: "#EE5599",
 67          mode: "light",
 68        },
 69
 70        toc: {
 71          scope: ".markdown-section",
 72          headings: "h1, h2, h3",
 73          title: "大纲",
 74        },
 75
 76        copyCode: {
 77          buttonText: "复制到剪贴板",
 78          errorText: "错误",
 79          successText: "已复制",
 80        },
 81
 82        // 文本高亮
 83        "flexible-alerts": {
 84          style: "flat",
 85          note: {
 86            label: "信息",
 87          },
 88          tip: {
 89            label: "提示",
 90          },
 91          warning: {
 92            label: "警告",
 93          },
 94          attention: {
 95            label: "注意",
 96          },
 97        },
 98
 99        pagination: {
100          previousText: "上一篇",
101          nextText: "下一篇",
102          crossChapter: true,
103          crossChapterText: false,
104        },
105
106        count: {
107          countable: true,
108          fontsize: "0.9em",
109          color: "rgb(90,90,90)",
110          language: "chinese",
111        },
112
113        search: {
114          maxAge: 180000, // 过期时间,单位毫秒,默认3分钟
115          placeholder: "搜索",
116          noData: "找不到结果",
117          namespace: "文档",
118        },
119
120        plantuml: {
121          skin: "classic",
122          renderSvgAsObject: true,
123        },
124
125        mindmap: {
126          markmap: {
127            preset: "colorful", // or default
128            linkShape: "diagonal", // or bracket
129          },
130        },
131
132        timeUpdater: {
133          text: ">最后更新时间: {docsify-updated}",
134          formatUpdated: "{YYYY}/{MM}/{DD} {HH}:{mm}",
135        },
136
137        markdown: {
138          renderer: {
139            code: function (code, lang, base = null) {
140              if (lang === "dot") {
141                return '<div class="viz">' + Viz(code, "SVG") + "</div>";
142              }
143
144              var pdf_renderer = function (code, lang, verify) {
145                function unique_id_generator() {
146                  function rand_gen() {
147                    return Math.floor((Math.random() + 1) * 65536)
148                      .toString(16)
149                      .substring(1);
150                  }
151                  return (
152                    rand_gen() +
153                    rand_gen() +
154                    "-" +
155                    rand_gen() +
156                    "-" +
157                    rand_gen() +
158                    "-" +
159                    rand_gen() +
160                    "-" +
161                    rand_gen() +
162                    rand_gen() +
163                    rand_gen()
164                  );
165                }
166                if (
167                  lang &&
168                  !lang.localeCompare("pdf", "en", { sensitivity: "base" })
169                ) {
170                  if (verify) {
171                    return true;
172                  } else {
173                    var divId =
174                      "markdown_code_pdf_container_" +
175                      unique_id_generator().toString();
176                    var container_list = new Array();
177                    if (localStorage.getItem("pdf_container_list")) {
178                      container_list = JSON.parse(
179                        localStorage.getItem("pdf_container_list")
180                      );
181                    }
182                    container_list.push({ pdf_location: code, div_id: divId });
183                    localStorage.setItem(
184                      "pdf_container_list",
185                      JSON.stringify(container_list)
186                    );
187                    return (
188                      '<div style="margin-top:' +
189                      PDF_MARGIN_TOP +
190                      "; margin-bottom:" +
191                      PDF_MARGIN_BOTTOM +
192                      ';" id="' +
193                      divId +
194                      '">' +
195                      '<a href="' +
196                      code +
197                      '"> Link </a> to ' +
198                      code +
199                      "</div>"
200                    );
201                  }
202                }
203                return false;
204              };
205
206              if (pdf_renderer(code, lang, true)) {
207                return pdf_renderer(code, lang, false);
208              }
209
210              return base ? base : this.origin.code.apply(this, arguments);
211            },
212          },
213        },
214      };
215    </script>
216
217    <!-- Docsify -->
218    <script src="https://cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
219
220    <script src="//cdn.jsdelivr.net/npm/docsify-dark-mode@latest/dist/index.min.js"></script>
221    <script src="//unpkg.com/docsify-toc@1.0.0/dist/toc.js"></script>
222
223    <!-- charty -->
224    <script src="//cdn.jsdelivr.net/npm/@markbattistella/docsify-charty@latest"></script>
225    <link
226      rel="stylesheet"
227      href="//cdn.jsdelivr.net/npm/@markbattistella/docsify-charty@latest/dist/docsify-charty.min.css"
228    />
229
230    <!-- docsify: tabs -->
231    <script src="//cdn.jsdelivr.net/npm/docsify-tabs@1"></script>
232
233    <!-- 脑图 -->
234    <script src="//unpkg.com/docsify-mindmap/dist/docsify-mindmap.min.js"></script>
235
236    <!--plantuml图支持-->
237    <script src="//unpkg.com/docsify-plantuml/dist/docsify-plantuml.min.js"></script>
238
239    <!-- 支持 DOT 语言 -->
240    <script src="https://cdn.jsdelivr.net/gh/wugenqiang/NoteBook@master/plugin/viz.js"></script>
241    <!-- 支持 LaTex 语言 -->
242    <script src="//cdn.jsdelivr.net/npm/docsify-katex@latest/dist/docsify-katex.js"></script>
243
244    <!--mermaid支持-->
245    <script src="//unpkg.com/mermaid/dist/mermaid.js"></script>
246    <script src="//unpkg.com/docsify-mermaid@latest/dist/docsify-mermaid.js"></script>
247
248    <!--graphviz支持-->
249    <script src="//unpkg.com/@hpcc-js/wasm/dist/index.min.js"></script>
250    <script src="//unpkg.com/docsify-graphviz@latest/dist/docsify-graphviz.js"></script>
251
252    <!-- alerts -->
253    <script src="https://unpkg.com/docsify-plugin-flexible-alerts"></script>
254
255    <!-- echarts -->
256    <script src="//cdn.jsdelivr.net/npm/echarts@latest/dist/echarts.min.js"></script>
257    <script src="//cdn.jsdelivr.net/npm/docsify-echarts-plugin/lib/index.min.js"></script>
258
259    <!-- 添加 PDF 页面展示功能 -->
260    <script src="//cdnjs.cloudflare.com/ajax/libs/pdfobject/2.1.1/pdfobject.min.js"></script>
261    <script src="//unpkg.com/docsify-pdf-embed-plugin/src/docsify-pdf-embed.js"></script>
262
263    <!-- emoji表情支持 -->
264    <script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/emoji.min.js"></script>
265    <!-- 搜索功能支持 -->
266    <script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
267    <!--在所有的代码块上添加一个简单的Click to copy按钮来允许用户从你的文档中轻易地复制代码-->
268    <script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>
269    <!-- 图片放大缩小支持 -->
270    <script src="//unpkg.com/docsify/lib/plugins/zoom-image.min.js"></script>
271    <!--分页支持-->
272    <script src="//unpkg.com/docsify-pagination/dist/docsify-pagination.min.js"></script>
273    <!-- 更新时间支持 -->
274    <script src="//cdn.jsdelivr.net/npm/docsify-updated@1/src/time-updater.min.js"></script>
275    <!--字数统计-->
276    <script src="//unpkg.com/docsify-count/dist/countable.min.js"></script>
277
278    <!-- footer -->
279    <script src="//cdn.jsdelivr.net/npm/@alertbox/docsify-footer/dist/docsify-footer.min.js"></script>
280
281    <!--语法高亮-->
282    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js"></script>
283    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-c.min.js"></script>
284    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-cpp.min.js"></script>
285    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-csharp.min.js"></script>
286    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-go.min.js"></script>
287    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-go-module.min.js"></script>
288    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-cmake.min.js"></script>
289    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-lua.min.js"></script>
290    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-mermaid.min.js"></script>
291    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-protobuf.min.js"></script>
292    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-docker.min.js"></script>
293    <script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-java.min.js"></script>
294
295    <!-- 实现离线化 -->
296    <script>
297      if (typeof navigator.serviceWorker !== "undefined") {
298        navigator.serviceWorker.register("assets/docsify-sw.js");
299      }
300    </script>
301  </body>
302</html>

其中的docsify-sw.js为:

 1/* ===========================================================
 2 * docsify sw.js
 3 * ===========================================================
 4 * Copyright 2016 @huxpro
 5 * Licensed under Apache 2.0
 6 * Register service worker.
 7 * ========================================================== */
 8
 9const RUNTIME = 'docsify'
10const HOSTNAME_WHITELIST = [
11    self.location.hostname,
12    'fonts.gstatic.com',
13    'fonts.googleapis.com',
14    'cdn.jsdelivr.net'
15]
16
17// The Util Function to hack URLs of intercepted requests
18const getFixedUrl = (req) => {
19    var now = Date.now()
20    var url = new URL(req.url)
21
22    // 1. fixed http URL
23    // Just keep syncing with location.protocol
24    // fetch(httpURL) belongs to active mixed content.
25    // And fetch(httpRequest) is not supported yet.
26    url.protocol = self.location.protocol
27
28    // 2. add query for caching-busting.
29    // Github Pages served with Cache-Control: max-age=600
30    // max-age on mutable content is error-prone, with SW life of bugs can even extend.
31    // Until cache mode of Fetch API landed, we have to workaround cache-busting with query string.
32    // Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190
33    if (url.hostname === self.location.hostname) {
34        url.search += (url.search ? '&' : '?') + 'cache-bust=' + now
35    }
36    return url.href
37}
38
39/**
40 *  @Lifecycle Activate
41 *  New one activated when old isnt being used.
42 *
43 *  waitUntil(): activating ====> activated
44 */
45self.addEventListener('activate', event => {
46    event.waitUntil(self.clients.claim())
47})
48
49/**
50 *  @Functional Fetch
51 *  All network requests are being intercepted here.
52 *
53 *  void respondWith(Promise<Response> r)
54 */
55self.addEventListener('fetch', event => {
56    // Skip some of cross-origin requests, like those for Google Analytics.
57    if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) {
58        // Stale-while-revalidate
59        // similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale
60        // Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1
61        const cached = caches.match(event.request)
62        const fixedUrl = getFixedUrl(event.request)
63        const fetched = fetch(fixedUrl, { cache: 'no-store' })
64        const fetchedCopy = fetched.then(resp => resp.clone())
65
66        // Call respondWith() with whatever we get first.
67        // If the fetch fails (e.g disconnected), wait for the cache.
68        // If there’s nothing in cache, wait for the fetch.
69        // If neither yields a response, return offline pages.
70        event.respondWith(
71            Promise.race([fetched.catch(_ => cached), cached])
72                .then(resp => resp || fetched)
73                .catch(_ => { /* eat any errors */ })
74        )
75
76        // Update the cache with the version we fetched (only for ok status)
77        event.waitUntil(
78            Promise.all([fetchedCopy, caches.open(RUNTIME)])
79                .then(([response, cache]) => response.ok && cache.put(event.request, response))
80                .catch(_ => { /* eat any errors */ })
81        )
82    }
83})