效率提升-Markdown管理系统

530 阅读11分钟

背景

  作为技术出生,日常写文档基本是采用Markdown来编写的,对技术人员来说会简单很多,并且灵活性也非常强,只要你懂前端HTML就可以搞出非常丰富的内容。

  日常编写的文档类型会很多,技术研究,问题整理,会议纪要,日常管理等等都会用markdown记录。前期还好,后面用着用着各类问题就来了:

  1. 文档的可阅读性,markdown一般需要解析过以后,才能是普通人能看的懂的内容,所以如果直接markdown文件发给别人,肯定是有问题的,所以需要一个方便能预览(不依赖任何工具),以及方便导出PDF(转换)等方式;
  2. 文档内容检索(磁盘管理这种就无法实现);
  3. 展现效果,人的习惯就是这样,喜欢漂亮的东西;
  4. 日常编写文档可能会画思维导图、流程图、时序图等各类图形,可能会牵涉Visio,Xminder等工具,这些内容只能以图片的形式嵌入到文档里,其他人无法编辑。所以是不是有一种方式,可以直接在markdown中生成各类需要的图形。并且后续能正常预览、转换。

例如:visio就是windows专有的,mac电脑上还无法直接使用。xminder如果不同电脑版本有差异,还无法解析。

选型

  前期也想过选择typora、印象笔记、有道云笔记等各类支持markdown编辑的产品,但是基本上达不到我的要求,而且格式都是他们产品特定的,导入、导出、各种语法兼容还是比较糟糕的。

所以后面琢磨了一下,包括自己各种试验,终于搭建了一套全面符合我要求的markdown发布系统

  1. VSCode 核心编辑工具,依托于丰富的插件仓库,可以很方便的编辑和预览Markdown,包括导出PDF,HTML等。
  2. PlantUML + Graphviz,编写各类思维导图、类图、时序图、流程图等。
  3. DrawIO, 类Visio的画图工具,基本上满足所有功能,格式支持通用的SVG。
  4. VuePress, HTML发布框架,结合VuePress-theme-reco主题提供好看的样式。通过各种自定义插件实现特殊格式的渲染和转换

编辑效果:

预览效果

介绍

VsCode

  这个产品就不介绍了,微软开源的轻量编辑器。通过丰富的插件市场,可以实现代码的开发,前端、java等都是可以用的。作为markdown编辑器更加没问题,支持各种语法,支持预览等等。所以当时就选择了VsCode作为核心编辑器。

vscode的资料很多,就不展开详细介绍了

插件介绍

介绍几个我自己用的比较好的markdown插件

Markdown All in One

   提供了常用操作便利的快捷键,支持一边书写一边预览,可轻松转换为HTML文件和PDF文件,优化了List editing的编辑,可格式化table以及Task list,支持特殊数学符号渲染等等,效果还是比较强的。

   直观的例子就是,我们如果在markdown中要用粗体标识,会在文字前后加 **, 比如 **加粗** 这样,这个插件就支持,选中文字以后,直接按Ctrl+B,就自动给你补齐加粗需要的格式,编辑会简单很多。

MarkdownPDF

  Markdown导出PDF的工具(还支持其他html等格式),通过设置可以实现PlantUML等插件的完美兼容。后面讲PlantUML的时候会详细介绍下。

Markdown Preview Enhanced

  Markdown预览神器,通过设置以后可以完美的支持预览PlantUML。并且支持多种预览样式。

Markdown TOC

  markdown目录、标题序号生成器,可以一键生成总目录,以及标注列表序号。

自动生成的目录

markdownlint

  这个前端开发人员比较熟,eslint,tslint,markdownlint等,都是用来检查代码格式的。markdownlint也是来检查markdown代码格式的,怎么写出优雅的markdown代码。

  他这个校验有点严格,很多时候处理起来比较麻烦,所以可以设置下,部分是在无法遵守的规则就关闭掉。

  在工程根目录下创建 .markdownlint.json,然后根据提示的规则编号,设置成false,就可以不提醒了。

.markdownlint.json

一般如果不符合规则的格式,你鼠标放上去,就会有规则编号提示的

代码片段

  工欲善其事必先利其器,有充足的准备以后,后面编写起来才会很顺畅。

智能提示开启

可以直接选择编辑settings.json

    "[markdown]": {
        "editor.wordWrap": "on",
        //这里就是开启智能提示的地方
        "editor.quickSuggestions": {
            "comments": "on",
            "strings": "on",
            "other": "on"
        },
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
代码片段

  代码片段是个很有用的东西,日常你会用的比较多的复杂语法,你可以做成代码片段,后面敲一下命令+自动提示,就可以直接写入复杂语法,能省很多时间。

创建表格的代码片段

{
    "mtable": {
		"prefix": "mtable",
		"body": [
			"标题 | 标题 | 标题",
			":---: | :---: | :---:",
			" |  |  "
		],
		"description": "列表"
	}
}

VuePress

安装也比较简单,打开一个文件

yarn init #初始化
yarn add -D vuepress #安装vuepress

这样一个vuepress的初始化工程就安装好了。

工程介绍

整体效果

enhanceApp.js

这个文件其实就是入口文件,其他没什么,就是如果引用第三方组件的时候要注意,ElementUI和Antd可以直接引用进来,但是vue-grid-layout就比较麻烦点。

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
import VueClipboard from 'vue-clipboard2'
import Login from './components/login/login'

export default ({
    Vue,
    options,
    router,
    siteData
}) => {
    // 使用element-ui
    Vue.use(ElementUI)
    // 使用ant design vue
    Vue.use(Antd)
    Vue.use(VueClipboard)

    //部分第三方组件比较特殊,需要在入口注入下,内部页面才能访问
    //后面介绍到VuePress支持markdown文档嵌入Vue页面
    import ('vue-grid-layout').then(function(m) {
        Vue.component('GridLayout', m.GridLayout)
        Vue.component('GridItem', m.GridItem)
    })
}
config.js

  这个是核心配置文件,相关的主题,自定义插件,目录等都是在这里定义的。

目录篇

只需要在config.js的nav,sidebar节点下面配置好层级目录,前端显示就没有问题了

一级目录顶上的

二三级目录侧边的

  这里其实就存在一个问题,我们的目录结构可能随时再变的,比如今天新增了几个文档,目录就要增加几个了,如果每次新增文档的时候都去修改一下目录的结构,太繁琐了。所以后面改造了他的部分代码,使的二三级目录自动获取,也就是你在Vscode新增一个文件,目录也会自动更新。

//config.js
const filehelper = require('./utils/initPage.js');
sidebar: {
    '/business/xxxxxx/': utils.genSidebar('xxxx',
    //这个方法就是动态获取目录的
    filehelper.getFileName(rootpath, "/business/realestate/", []),
    false) // 侧边栏配置
}
const fs = require('fs');
var path = require('path');
// 排除检查的文件
var excludes = ['.DS_Store']

var filehelper = {
    getFileName: function(rpath, queryPath, tempValue, type) {
        rpath = path.join(rpath, queryPath)
            // let fileImg = /\.(png|jpe?g|gif|webp)(\?.*)?$/;
        var filenames = [];
        if (type === 'json') {
            filenames = tempValue.children
        } else {
            filenames = tempValue
        }
        let fileTypes = /\.md$/; //只匹配以md结尾的文件
        var fileList = fs.readdirSync(rpath)
            //重新排序
        fileList.sort(function(a, b) {
            var fileinfoA = fs.statSync(path.join(rpath, a))
            var fileinfoB = fs.statSync(path.join(rpath, b))
            if (!fileinfoA.isFile() && !fileinfoB.isFile()) {
                return fileinfoA.mtime.getTime() - fileinfoB.mtime.getTime()
            } else {
                if (!fileinfoA.isFile()) {
                    return -1
                } else if (!fileinfoB.isFile()) {
                    return 1
                } else {
                    return fileinfoB.mtime.getTime() - fileinfoA.mtime.getTime()
                }
            }
        })
        fileList.forEach(file => {

            if (excludes.indexOf(file) < 0) {
                fullpath = path.join(rpath, file)
                var fileinfo = fs.statSync(fullpath)
                if (fileinfo.isFile()) {
                    // if(file.indexOf('.md') > 0) {
                    if (fileTypes.test(file) > 0) {
                        if (file === 'readme.md') {
                            file = '';
                        } else {
                            file = file.replace('.md', '');
                            var tempPath = path.join(queryPath, file)
                            tempPath = tempPath.replace(/\\/g, "/")
                            filenames.push({
                                title: file,
                                mtime: fileinfo.mtime,
                                collapsable: true,
                                path: tempPath,
                                sidebarDepth: 1
                            });
                        }
                    }
                } else {
                    var json = {
                        title: file,
                        collapsable: true,
                        children: []
                    }
                    filenames.push(json)
                    return this.getFileName(rpath, file, json, 'json')
                }
            }
        })
        return filenames;
    },
    sort: function(a, b) {

        if (a.children && b.children) {
            if (a.mtime && b.mtime) {
                return a.mtime - b.mtime
            } else {
                return a.title - b.title
            }

        } else {
            if (a.children) {
                return -1
            } else if (b.children) {
                return 1
            } else {
                return a.mtime - b.mtime
            }
        }
    }
}
module.exports = filehelper;
主题

  网上也有很多开源的VuePress主题,我挑选了一款我认为比较好的,然后是直接源代码应用进来的,因为主题还是有些不符合我要求的地方。主题配置是在config.js下。

这个看个人喜好,不想改的就直接用,想改的就自己改一下。比如我就是对这块内容魔改了下。

自定义插件

  VuePress在把markdown转换成html的过程中,很多你个性化的内容它是无法转换的,所以就需要通过自动以插件的形式,来实现完美的转换。插件配置也是在config.js下

插件开发过程就是你按照标准开发,基于markdown-it扩展就行,修改完成以后发布到npm仓库,然后你install到工程里就行。

module.exports = function(md, config) {
  md.renderer.rules.image = function(tokens, idx) {
    //  xxxxxx
    return '<img src="' + url + '" alt="' + caption + '" test="123" />';
  };
};

例如: xxxxx-custom-plantuml

plantuml数据解析插件

function generateSourceDefault(umlCode, pluginOptions) {
    var imageFormat = pluginOptions.imageFormat || 'svg';
    var diagramName = pluginOptions.diagramName || 'uml';
    //plantuml解析服务器,我是自己搭建了一个,所以外部传入就行,没有就用互联网的地址
    var server = pluginOptions.server || 'https://www.plantuml.com/plantuml';
    var deflate = require('./lib/deflate.js');
    //针对plantuml的特殊标签进行处理,vscode预览需要,不是最原始的plantuml的格式。
    umlCode = umlCode.replace("```puml", "") // 替换最前面的字符串
    umlCode = umlCode.replace("```", "") //删除最后面的字符串
    var zippedCode = deflate.encode64(
        deflate.zip_deflate(
            unescape(encodeURIComponent(
                '@start' + diagramName + '\n' + umlCode + '\n@end' + diagramName)),
            9
        )
    );
    return server + '/' + imageFormat + '/' + zippedCode;
}

例如: xxxxx-it-image

image图片地址解析插件。

module.exports = function(md, config) {
  md.renderer.rules.image = function(tokens, idx) {
    var token = tokens[idx];
    var srcIndex = token.attrIndex("src");
    var url = token.attrs[srcIndex][1];
    var caption = md.utils.escapeHtml(token.content);
    if(url.indexOf("../../../.vuepress/public")>-1){
      url = url.replace("../../../.vuepress/public","")
    }
    else if(url.indexOf("../../.vuepress/public")>-1){
      url = url.replace("../../.vuepress/public","")
    }
    else if(url.indexOf("../.vuepress/public")>-1){
      url = url.replace("../.vuepress/public","")
    }
    else if(url.indexOf(".vuepress/public")>-1){
      url = url.replace(".vuepress/public","")
    }
    url = decodeURL(url, config);
    return '<img src="' + url + '" alt="' + caption + '" test="123" />';
  };
};

发布

  工程就是类似一个Vue工程,直接yarn run build发布就行,生成的成果,就是一个前端静态页面,直接用nginx发布就行了。

Drawio

  流程图这块是比较重要的场景,很多时候一个图带来的价值是非常大的。日常windows上可能会用Visio等工具,或者采用在线的ProcessON等平台。都会存在很多问题,Visio不是跨平台的,ProcessOn线的,也很多限制。所以想着怎么能有一个能跨平台使用,有方便没有限制的。

后来是看到一个在线的drawio平台,感觉这个还是很好用的,刚好这个有大神在Vscode做了一个drawio的插件,用了以后,完美。

Draw.io Integration

然后你可以创建一个后缀为 "xxxx.drawio.svg"的文件,这时候在Vscode打开就会直接转到drawio的编辑界面,可以可视化画各种图了。

它本质上就是一个svg矢量图,所以放到哪里都可以使用

markdown里面直接引入相对地址就行

这个功能对我来说就是神器,我后面基本上所有的图都是用他画的,保存方便,使用方便。

PlantUML

  最开始是选用PlantUML来画流程图的,它也能画,就是一些复杂流程图展示效果不是太理想,所以后面转用drawio画流程图。

  平常会写很多技术文档,我需要画类图、时序图、思维导图,那plantuml就是一个很好的选择了。

第一步提到的插件Markdown Preview Enhanced默认是支持预览plantuml的,不过实际渲染还是需要 graphviz 支持的

graphviz安装部署

下载地址: graphviz.org/download/

现在以后直接安装就行,注意记住安装位置,后面还要配置环境变量

变量名: GRAPHVIZ_DOT
变量值: C:\Program Files\Graphviz\bin\dot.exe #按照实际路径来

配置完成以后基本上可以正常预览PlantUML了

PlantUML渲染兼容

Markdown Preview Enhanced预览支持的PlantUML格式是这样的

而正常PlantUMLServer能解析的是这样的

差异其实就是起始标识符和结束标识符不一样(开头多了plumtuml的标识),中间内容都是一样的。这样就比较尴尬了,好在都可以通过其他方式解决这个问题。

PDF转换

直接设置下首尾标签就行

VuePress渲染

就是通过前面介绍的 xxxxx-custom-plantuml插件做了兼容

通用样式模板

  plantuml支持配置通用样式模板,可以自定义个puml文件,然后画图的地方就可以引入这个最定义样式,这样你所有画出来的图样式效果都是一样的。如果有个性化的,可以扩展定义

样式文件内容

效果

类图

流程图

思维导图

时序图

Markdown和Vue界面结合

  这块功能也是我最喜欢的功能,文档编辑出来都是静态的,但是结合Vue页面发布以后,就是动态的页面,有些动态如果无法用言语描述,就直接开发一个页面出来,有交互的,体现效果更直接。

检索

  VuePress自带的目录检索功能基本能满足我的要求了,所以目前一直没有上全文检索的功能,思路山没有采用VuePress提供的全文检索功能,而是通过Elasticsearch来实现全文检索。但是有点大材小用,所以一直没有部署上去(云服务资源也有限)。

总结

  整个打造这一套完整的体系,可能花了有两个月的时间,包括各种源代码改造,插件编写,工具调试等,但是花的时间还是非常值的。我写文档、管理文档效率非常高,很多设计文档就直接在这里编码实现效果。通过导出PDF等功能也不影响其他人查看,里面各种drawio图,plantuml发给其他人都可以直接编辑。