/vue-markdown

一款使用marked和highlight.js开发的一款markdown编辑器

Primary LanguageJavaScript

简介

一款使用marked和highlight.js开发的一款markdown编辑器,除常见markdown语法外,支持快捷输入、图片粘贴、代码复制、全屏编辑、预览等功能。

使用起来简单方便,只需几行代码,即可在你的页面上引入一个markdown编辑器,编辑区支持像专业编辑器那样。

编辑器涵盖了常用的markdown编辑器功能,可通过已有属性进行配置,对编辑器功能和样式进行基本的配置,也可根据需求进行深度定制。

示例 image.png

特点

  • 使用简单,只需要安装npm包,引入项目即可使用,不需要繁琐的初始化配置。
  • 方便扩展,根据实际需求,支持常见的功能配置,也可根据实际需求进行深度定制。
  • 体积小,加载速度快,npm包删除了highlight.js和codemirror里的依赖。
  • 灵活的主题,默认支持四种代码块风格,也可根据实际需求定制自己的主题样式
  • 功能强大,支持专业版的编辑器,使用codemirror实现编辑窗口,可识别markdown语法
  • 键盘事件监听,如保存、粘贴、回车时上次输入语法判断等
  • 可扩展性强,除了提供的属性配置编辑器,也可直接在原有组件基础上进行二次开发

实现思路

通过监听文本输入区域内内容的变化,实时将输入的markdown语法进行编译,并渲染到预览区域。

编辑器大致分为头部菜单栏、左侧内容输入区域、右侧预览区域三个部分。 头部菜单主要为定自定义标题区域和菜单按钮,菜单按钮可通过配置文件进行显示和隐藏;左侧编辑区域,简单版使用textarea开发,满足基本需求, 专业版使用codemirror开发,编辑区域支持手动输入文本和通过头部菜单插入;右侧预览区域可实时预览输入文本,并可通过菜单按钮,进行编辑区域和预览区域的切换。

安装方式

使用npm安装

  1. 安装依赖
npm i -S vue-meditor

将组件复制到项目内

  1. 将git仓库代码拉到本地
git clone https://github.com/zhaoxuhui1122/vue-markdown.git
  1. 复制src文件夹下内容至components文件夹下

在项目使用

npm包安装时

简单版

import Markdown from 'vue-meditor'

专业版

import { MarkdownPro } from 'vue-meditor'

预览组件

import { MarkdownPreview } from 'vue-meditor'

复制组件到本地时(推荐)

简单版

import Markdown from '@/components/markdown/...';

专业版

import MarkdownPro from '@/components/markdown/pro';

预览组件

import MarkdownPreview from '@/components/markdown/preview';

在页面内使用

<template>
    <div class="markdown">
        <Markdown/>
    </div>
</template>

<script>
    import Markdown from 'vue-meditor';
    
    export default {
        name: "markdown",
        components: {
            Markdown
        }
    }
</script>

API

编辑器基本属性

value

  • Type: String/Number
  • Default: ''

编辑器输入的文本,支持通过v-dodel数据双向绑定设置编辑器内容和获取编辑器的值。

width

  • Type: String/Number
  • Default: auto

编辑器的初始化宽度。

height

  • Type: Number
  • Default: 600

编辑器的初始化高度。

bordered

  • Type: Boolean
  • Default: true

编辑器是否含有边框。

toolbars

  • Type: Object
  • Default: 参见下表

头部菜单按钮,通过设置true or false控制决定是否显示,目前配置支持持控制按钮显示隐藏,后续将支持根据配置显示排列顺序。

名称 说明 默认是否显示
strong 粗体
italic 斜体
overline 删除线
h1 标题1
h2 标题2
h3 标题3
h4 标题4
h5 标题5
h6 标题6
hr 分割线
quote 引用
ul 无序列表
ol 有序列表
code 代码块
link 链接
image image
table 表格
checked 已完成列表
notChecked 未完成列表
preview 预览
split 分屏模式切换
print 打印
theme 主题切换
fullscreen 全屏
exportmd 导出为*.md文件
importmd 导入本地*.md文件
save 保存按钮
clear 清空内容

theme

  • Type: String
  • Default: light

编辑器代码块主题,目前支持lightdarkoneDarkgitHub四种代码块风格,可通过自定义theme并修改样式文件进行主题定制。

自定义theme时,预览区域的会增加一个为markdown-theme-[theme]class

autoSave

  • Type: Boolean
  • Default: false

是否开启自动保存,设置为开启时可通过绑定on-save事件获取编辑器内的值和代码块主题。

<Markdown @on-save="handleOnSave"/>
 handleOnSave({value, theme}){
        console.log(value, theme);
    }

interval

  • Type: Number
  • Default: 10000

自动保存间隔时间,单位:mm,默认10000mm,需要autoSave = true时才有效。

exportFileName

  • Type: String
  • Default: unnamed

导出的md文件名称,默认unnamed.md。

markedOptions

  • Type: Object
  • Default: {}

marked配置项,可以根据需求自定义。

<Markdown :markedOptions="{baseUrl:'http://***.oss-cn-shanghai.aliyuncs.com/'}"/>

isPreview

  • Type: Boolean
  • Default: false

是否是预览模式,开启时可作为一个预览组件使用,与预览组件功能一致。

copyCode

  • Type: Boolean
  • Default: true

是否支持复制代码块内的内容。

copyBtnText

  • Type: String
  • Default: 复制代码

复制代码按钮显示文字。

预览组件基本属性

initialValue

  • Type: String/Number
  • Default: ''

预览组件初始化内容,支持动态更新。

theme

  • Type: String
  • Default: light

代码块主题,与编辑器编辑器代码块主题一致。

markedOptions

  • Type: Object
  • Default: {}

marked配置项,与编辑器内该配置一致。

copyCode

  • Type: Boolean
  • Default: true

是否支持复制代码块内的内容。

copyBtnText

  • Type: String
  • Default: 复制代码

复制代码按钮显示文字。

on-ready

编辑器初始化完成时触发,返回值为Object,包含组件本身和insertContent方法。

on-save

编辑器保存事件,自动保存或者手动保存时触发,支持ctrl+scommand+s触发保存,返回值类型为Object,包含当前输入值value和选择的代码块主题theme

on-paste-image

监听编辑器粘贴图片事件,在编辑区域内手动粘贴图片时触发,可用于支持粘贴插入图片文件,返回file文件,上传文件后可结合on-ready事件内返回的insertContent插入图片。

on-copy

复制代码块内容,触发时返回当前代码块的text,copyCode开启时才有效。

二次开发

粘贴插入图片

on-paste-image虽然可以支持图片粘贴事件的监听,但不会处理图片上传至服务器并将链接插入编辑器这段逻辑。

目前如果想要支持粘贴插入图片,需要在on-paste-image方法里上传图片文件,拿到图片地址后,使用on-ready方法里返回的insertContent方法插入图片。

上述操作显得过于复杂,可以直接在源码里扩展mixins里的handlePaste方法,图片上传完成后,直接调用this.insertContent方法插入图片。

修改/markdown/mixins/common.js

handlePaste(_, e) {// 粘贴图片
    const { clipboardData = {} } = e;
    const { types = [], items } = clipboardData;
    let item = null;
    for (let i = 0; i < types.length; i++) {
        if (types[i] === 'Files') {
            item = items[i];
            break;
        }
    }
    if (item) {
        const file = item.getAsFile();
        if (/image/gi.test(file.type)) {
            e.preventDefault();
            // 1.上传操作
            // 2.插入图片 this.insertContent(`![image](imgUrl)`)
        }
    }
}

支持流程图、甘特图等语法

目前编辑器只支持常见code语法,如果需要实现如流程图等功能,需要进一步扩展,以实现一个简单的流程图为例,具体实现思路如下:

默认情况下,markedjs会使用renderer.code方法对输入的代码块进行解析,并会借助highlight.js支持语法高亮。 可以将流程图语法输入到代码块内,并标明语言,重写marked.Renderer的code解析方法,结合结合flowchart.js路程图代码进行解析,返回文本内容。

修改`/markdown/libs/js/simple.js

import hljs from './hljs';
import index from 'index';
import {parse} from 'flowchart.js'

hljs.initHighlightingOnLoad();

const renderer = new index.Renderer();
renderer.code = (code, language) => {
    if (language === 'flow') {// 流程图
        const dom = document.createElement('div');
        const flowchart = parse(code);
        flowchart.drawSVG(dom, {/*相关配置*/});
        return  dom.innerHTML;
    } else {// 默认解析
        return `<pre class="hljs"><code class="${language}">${hljs.highlightAuto(code).value}</code></pre>`
    }
}
export default index.setOptions({
    renderer,
    gfm: true,
    tables: true,
    breaks: false,
    pedantic: false,
    sanitize: false,
    smartLists: true,
    highlight: function (code) {
        return hljs.highlightAuto(code).value;
    }
})

自定义markdown语法转换

项目内使用的`index.js均为其默认配置功能,如需要特殊转换,可重写其内部的解析方法,即重写其renderer相关方法 参考文档

自动生成文档目录

预览区域和文档预览组件暂不支持自动生成目录,实现自动生成目录思路目前想到的大致有

  • 重写renderer.heading 方法,为生成的标题添加id,输入特定快捷键,如[TOC]时,查找预览区域内的的所有标题标签,分析等级关系,生成目录标签

icon替换

项目内所有的icon和命名参考/assets/font/index.html,替换时需注意,预览区域的checkbox为icon,注意一并替换, 修改/assets/css/index.less内的input[type="checkbox"]:after样式。

代码体积优化

公共代码提取

npm包构建时,三个组件完全独立,没有抽离公共文件,所以,当同一个项目内引入其中的两个或三个组件都引入时,存在一定的重复代码, 主要为highlight.jsmarkediconfont、css样式几个部分。

解决方式:将组件复制到本地项目,打包时将这些文件作为公共文件抽离出来。

注意:三个组件中使用的iconfont为同一套,如果只是单纯的使用preview组件, 将会引入整个项目所使用的iconfont,可删除iconfont的引入, 重写input[type="checkbox"]的样式,preview组件体积将会减少一半,样式文件位于markdown/assets/css/index.less

codemirror体积优化

codemirror主要分为主文件、mode相关文件和样式文件,主文件体积异常的大,mode文件目前只选用了css/jsvascript/markdown/meta/xml五个文件, 其中markdown.js和meta.js为必须引用的,项目中已将常见的编程语言代码风格定义为css/js/xml之一,例如less/sass/scss按照css规则解析,html/vue按照xml规则解析。 优化可从一下方面入手

  • 减少codemirror主文件体积
  • 减少引用的mode依赖

highlight.js体积优化

highlight.js原本体积也是较大的,主要原因为,编译时为支持各种代码语言,引入了相应的解析文件, 项目内已根据常见的代码语言进行了一次筛选,进行按需引入,可根据自身需求,再次对引用文件进行删减

参见src/markdown/libs/js/hljs.js,目前支持的语言有

import hljs from 'highlight.js/lib/highlight'

import javascript from 'highlight.js/lib/languages/javascript'
import java from 'highlight.js/lib/languages/java';
import css from 'highlight.js/lib/languages/css';
import less from 'highlight.js/lib/languages/less';
import go from 'highlight.js/lib/languages/go';
import markdown from src;
import php from 'highlight.js/lib/languages/php';
import python from 'highlight.js/lib/languages/python';
import ruby from 'highlight.js/lib/languages/ruby';
import stylus from 'highlight.js/lib/languages/stylus';
import typescript from 'highlight.js/lib/languages/typescript';
import xml from 'highlight.js/lib/languages/xml';

const languages = {
    javascript,
    java,
    css,
    less,
    markdown,
    go,
    php,
    python,
    ruby,
    stylus,
    typescript,
    xml
}
Object.keys(languages).forEach(key => {
    hljs.registerLanguage(key, languages[key])
})

export default hljs;

专业版编辑器codemirror/simple.js

优化思路:无

iconfont 体积优化

只需要preview组件时,避免引入所有icon,参考功能扩展里icon替换方法。

升级路线

  • 普通版编辑器对选中文本进行操作功能
  • 文档目录功能
  • 优化专业版编辑器体积
  • react版开发
  • ...

问题反馈

对于功能上的缺陷、使用方法和希望扩展的功能,可以提 Issues

license: MIT