这两天好好配置了下本博客,Hexo 配合 Butterfly 主题 开箱提供了很多好用的功能特性,几乎满足了我对一个博客和知识管理平台的所有功能和视觉上的需求,很满意 ^_^ —— 唯独有一个问题,Hexo 使用 markdown 文本来生成静态页面,所以一个方便好用的 MD 编辑器就变得非常有必要了。长久以来不喜欢写 markdown 文档的两大原因就是没找到好用的文档管理工具和编辑器,现在管理的问题解决了,是时候花功夫找一个好用的编辑器了。

本地编辑器用过各种 IDE 的插件和 Typora,感觉都不是很好用,而且为了能加到 blog 上方便随时打开浏览器就能写作,于是决定找一个好用的在线编辑器。之前用 掘金 的编辑器感觉还是很顺手的,尤其喜欢左右实时预览,所见即所得的写作体验,它使用的是一个名为 ByteMD 的项目,但是看了下感觉功能还不够丰富……

初识 Editor.md

直到找到了这个 Editor.md,眼前一亮,功能丰富样式也好看,star 数更是与 ByteMD 拉开了几个数量级,虽然已经很久没有更新了,但足够好用,很适合直接引入到网页里

下载

直接从 github 仓库 clone 源码:https://github.com/pandao/editor.md

引入

在 hexo 的 source 目录下创建目录 md_editor,并创建 index.html 页面,根据 README文档 用最简单的方式引入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="zh">
<head title="MD Editor">
<meta charset="utf-8"/>
<link rel="stylesheet" href="css/editormd.min.css"/>
</head>
<body>
<div id="editor">
<!-- Tips: Editor.md can auto append a `<textarea>` tag -->
<textarea style="display:none;">### Hello Editor.md !</textarea>
</div>
<script src="js/jquery.min.js"></script>
<script src="js/editormd.js"></script>
<script type="text/javascript">
$(function () {
window.md_editor = editormd("editor", {
// width: "100%",
// height: "100%",
// markdown: "xxxx", // dynamic set Markdown text
path : "/lib/" // Autoload modules mode, codemirror, marked... dependents libs path
});
});
</script>
</body>
</html>

根据加载的内容,在 md_editor 目录下创建 cssjs 两个目录,并将下载的源码中的如下三个文件拷贝到对应位置:

1
2
3
examples/css/editormd.min.css
examples/js/jquery.min.js
editormd.js

再将源码中整个 lib 目录拷贝过来,这样基本的引入就完成了

配置 Hexo

现在直接运行 hexo server 并访问编辑器页面是会报错的,因为 Hexo 会尝试渲染引入的目录,所以要在 _config.yml 中配置忽略 md_editor 目录:

1
2
3
4
……
skip_render:
- "md_editor/**"
……

而为了可以方便地直接通过在首页上点击链接进入编辑器,可以把编辑器地址加入 Butterfly 的 menu 中:

1
2
3
4
5
6
menu:
首页: / || fas fa-home
……
工具||fas fa-tools:
- MDEditor || /md_editor/ || fas fa-pen
……

补全缺少的文件

现在运行 hexo server,通过首页的工具链接进入编辑器体验一下,主要功能可用了,但是部分功能按钮无效,还有一些图标无法加载,根据控制台报错,还需要把源码中如下三个目录拷贝到 md_editor下:

1
2
3
plugins/
fonts/
images/

重新运行后编辑器即可正常使用

优化编辑器样式

首先配置主题,我喜欢暗色,在 index.htmleditormd 的初始化配置中加入:

1
2
3
4
5
6
7
8
9
10
$(function() {
var editor = editormd("editor", {
// 整体编辑器的样式
theme: 'dark',
// 预览区域的样式
previewTheme: 'dark',
// 编辑区域的样式
editorTheme: 'pastel-on-dark',
});
});

由于代码编辑使用的 CodeMirror,所以 editorTheme 可以指定 CodeMirror 的 theme,如果有喜欢的主题可以从 https://github.com/codemirror/CodeMirror/tree/master/theme 下载后放入 /md_editor/lib/codemirror/theme 后即可使用。预设的样式可以在这里预览:
http://editor.md.ipandao.com/examples/themes.html

上面配置的效果:
theme

然后在 index.html<head></head> 中加入全局样式以消除滚动条并适应所选主题的颜色:

1
2
3
4
5
6
7
8
9
10
11
<style>
html, body {
width: 100%;
height: 100%;
margin: 0;
background: #2c2827;
}
.editormd {
box-sizing: border-box;
}
</style>

并设置 editormd 的初始化配置指定宽高:

1
2
3
4
5
6
7
8
$(function() {
var editor = editormd("editor", {
……
width: "100%",
height: "98%",
……
});
});

到此为止,Editor.md 的引入及美化就完成了

加入本地 md 文件读取功能

但是现在编辑器还是不够好用。作为一个基于 Web 的编辑器,如果每次想要修改某个 md 文档都需要先在本地打开,复制文档内容粘贴到编辑器里才能继续编辑修改也太麻烦了,所以需要加入读取本地 md 文档的功能

参考 自定义工具栏 - Editor.md examples,想要在工具栏中加入自定义工具,主要需要修改的是初始化配置中的 toolbarIconstoolbarCustomIcons
首先修改 toolbarIcons 来控制显示的图标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$(function() {
var editor = editormd("editor", {
……
toolbarIcons: function() {
// 获取默认的所有工具名称
var icons = editormd.toolbarModes.full;
// 过滤掉不需要的工具
icons = icons.filter(function (i) {return !['fullscreen', 'preview', 'emoji'].includes(i);});
// 加入自定义的工具名
icons.push('|', 'load_md');
return icons;
},
……
});
});

然后在 toolbarCustomIcons 实现加入的 load_md 工具:

1
2
3
4
5
……
toolbarCustomIcons: {
load_md: '<input type="file" id="my_file" accept=".md" onchange="md_file_changed()" style="display: none;"><li><a href="javascript:#" onclick="load_md_file()" title="load md" unselectable="on"><i class="fa fa-upload" name="load_md" unselectable="on"></i></a></li>',
},
……

并实现按钮的点击方法和选择文档后的处理方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
window.load_md_file = function () {
// 点击图标,触发隐藏的<input/>标签的点击事件用以选择本地 md 文档
$("#my_file").click();
}
window.md_file_changed = function () {
// 获取选择到的本地文件对象,利用 HTML5 的 File API 加载其内容
var file = document.getElementById("my_file").files[0];
var reader = new FileReader();
reader.readAsText(file, "utf-8");
reader.onload = function (e) {
// 将文档内容载入编辑器
var fileText = e.target.result;
window.md_editor.cm.replaceSelection(fileText);
}
}

处理文档中相对路径图片的加载问题

现在还有一个问题。参考网上的一些分享,如果使用图床来处理文档中的图片,虽然节省了流量,但是管理起来非常麻烦,而且一旦图床出现问题事情会变的更为棘手,所以在找到足够完全的解决方案前我准备将图片资源都放直接在博客内。而为了管理方便,参考 资源文件夹 | Hexo,每篇文章内引用的相对路径图片都放在了文档同名的文件夹内,而编辑器是无法正确处理这样的相对路径的,而只会傻乎乎得尝试下载相对于当前编辑器页面路径的图片,然后预览页面一堆难看的裂图

Editor.md 使用 marked 这个开源项目对 md 内容进行解析,在查看了 marked 文档 发现其可以通过设置 baseUrl 参数来解决相对路径资源的问题,所以如果可以控制设置这个参数应该就可以解决问题。

查看 js/editormd.js,找到 editormd 生成预览时调用 marked 解析的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 解析和保存Markdown代码
* Parse & Saving Markdown source code
*
* @returns {editormd} 返回editormd的实例对象
*/
save : function() {
……
var markedOptions = this.markedOptions = {
renderer : editormd.markedRenderer(markdownToC, rendererOptions),
gfm : true,
tables : true,
breaks : true,
pedantic : false,
sanitize : (settings.htmlDecode) ? false : true, // 关闭忽略HTML标签,即开启识别HTML标签,默认为false
smartLists : true,
smartypants : true,
// 尝试在options中加入baseUrl,这个md_base_url挂载在window上
baseUrl :window.md_base_url
};
marked.setOptions(markedOptions);
// 这里调用marked解析md内容生成html文档
var newMarkdownDoc = editormd.$marked(cmValue, markedOptions);
……
this.markdownTextarea.text(cmValue);
cm.save();
……
}

发现没有用……跟踪调试 marked 输出的内容没有变化。
追源码半天最后发现 baseUrl 这个参数是在 marked 的 v0.3.9 版本才加入的,而 Editor.md 源码中包含的 marked 是 v0.3.3 版本……所以下载新版本的 marked.min.js 替换 /md_editor/lib/marked.min.js,再试就可以了。。。

然后再在工具栏加入一个输入框,可以直接用来修改 window.md_base_url 的值,这样在其中输入正在编辑的文章在服务器上的路径,即可实现预览区中相对路径图片的正确显示。

给编辑器加入自动保存功能

因为是基于 Web 的工具,如果不小心关掉了页面,正在编辑中的内容就丢失了……好在可以通过 LocalStorage 定时对内容进行储存,实现起来也很方便(顺便把 BaseUrl 输入框的值也做了本地存储处理):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$(function () {
// 页面打开时尝试从LocalStorage中读取已经保存的内容
var content = window.localStorage.getItem("KEY_MD_EDITOR_CONTENT");
window.md_base_url = window.localStorage.getItem("KEY_MD_EDITOR_BASE_URL");
window.md_editor = editormd("editor", {
……
},
});
……
window.md_base_url_changed = function () {
window.md_base_url = $("#md_base_url").val();
// 同样利用LocalStorage保存BaseUrl输入框的值
window.localStorage.setItem("KEY_MD_EDITOR_BASE_URL", window.md_base_url);
}
// 开启定时器,每隔10秒将编辑器中的内容保存到LocalStorage中
// 获取和设置编辑器内容的方法分别是editor.cm.getValue()和editor.cm.setValue()
setInterval(function () {
window.localStorage.setItem("KEY_MD_EDITOR_CONTENT", window.md_editor.cm.getValue());
}, 1000 * 10);
});

目前需要的功能基本就都实现了,后面有需求可能还会继续折腾这个工具……

编辑器代码大小优化

虽然编辑器已经改得足够好用了,但是却有一个实际的问题,就是编辑器本身的大小有点大,足有 3.8MB,光是 内置的 CodeMirror 就有 2.4MB ,这样部署以后一来可能影响加载速度,而来浪费流量配额,所以考虑对其进行优化。

参考一些教程后决定引入 gulp 进行代码压缩,首先安装依赖:

1
yarn add gulp gulp-minify-css gulp-uglify --save

然后在根目录创建编辑 gulpfile.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var gulp = require('gulp');

var minifycss = require('gulp-minify-css');
var uglify = require('gulp-uglify');

gulp.task('minify-css', function () {
return gulp.src('./public/md_editor/**/*.css')
.pipe(minifycss())
.pipe(gulp.dest('./public/md_editor'));
});

gulp.task('minify-js', function () {
return gulp.src('./public/md_editor/**/*.js')
.pipe(uglify())
.pipe(gulp.dest('./public/md_editor'));
});

// 执行 gulp 命令时执行的任务
gulp.task('default', gulp.series('minify-css', 'minify-js'));

目前只用处理 ./public/md_editor/ 下的 js 和 css,后续如果有需要再继续配置吧
最后修改 package.json

1
2
3
4
5
6
7
8
{
……
"scripts": {
"build": "hexo generate && gulp",
……
}
……
}

这样以后执行 yarn run build 生成静态网站时就会自动对指定代码进行压缩,目前编辑器的大小从之前的 3.8MB 缩小到了 2.8MB,还可以。

源码

目前为止的编辑器源码地址:
https://github.com/debuggerx01/md_editor_for_hexo

如果觉得不错可以引入到自己的博客或者单纯当作 md 编辑器。
Feel Free ~ (>‿◠)