本文将介绍编辑器的使用和伴随的某些开发技巧。
该编辑器支持的功能有:基础的md编辑、md语法快捷键、记录保存、暗黑主题、图片上传/复制图片上传/裁剪图片上传、格式化内容、浏览器全屏/屏幕全屏、仅预览模式等功能,静待使用。
详细的编辑器api参考:文档。
- 图片裁剪预览
- 编辑器预览
1. 基本使用
这里演示两种环境三种写法:
1.1 npm安装用法
这种方式支持两种写法,除了.vue
模板写法,还有jsx
语法。
安装
yarn add md-editor-v3
.vue
模板基础使用
<template>
<md-editor v-model="text" />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MdEditor from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
export default defineComponent({
components: { MdEditor },
data() {
return { text: '' };
}
});
</script>
jsx
语法基础使用
import { defineComponent, ref } from 'vue';
import MdEditor from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
export default defineComponent({
name: 'MdEditor',
setup() {
const text = ref('');
return () => (
<MdEditor modelValue={text.value} onChange={(v: string) => (text.value = v)} />
);
}
});
1.2 script标签引入用法
链接可前往https://cdn.jsdelivr.net搜索md-editor-v3
。
<!--添加样式-->
<link href="https://cdn.jsdelivr.net/npm/md-editor-v3@1.2.0/lib/style.css" rel="stylesheet" />
<!--引入vue3-->
<script src="https://cdn.jsdelivr.net/npm/vue@3.1.5/dist/vue.global.prod.min.js"></script>
<!--引入组件-->
<script src="https://cdn.jsdelivr.net/npm/md-editor-v3@1.2.0/lib/md-editor-v3.umd.js"></script>
注册组件
const App = {
data() {
return {
text: 'Hello Editor!!'
};
}
};
Vue.createApp(App).use(MdEditorV3).mount('#md-editor-v3');
使用组件
<div id="md-editor-v3">
<md-editor-v3 v-model="text" />
</div>
2. 渲染内容
该编辑器使用marked
解析md
为html
,没有扩展语法。
通常来讲,编辑内容存储为md
格式,渲染内容时,通过marked
解析为html。
2.1 默认渲染
从1.3.0
版本后,编辑器支持了previewOnly
功能,可以直接使用编辑器预览文章,没有bar、编辑等等。
<template>
<md-editor
v-model="text"
previewOnly
/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MdEditor from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
export default defineComponent({
components: { MdEditor },
data() { return { text: '## 我只会显示预览内容' }; }
});
</script>
2.2 主动解析演示
这种方式用于保存md
,然后自行解析md
内容。
import marked from 'marked';
// 代码高亮
import hljs from 'highlight.js';
// 自选代码高亮样式
import 'highlight.js/scss/atom-one-dark.scss';
// 用于记录标题数,根据业务代替
let count = 0;
// 记录标题内容
const headstemp = [];
// marked设置
const rendererMD = new marked.Renderer();
// 调整标题内容
rendererMD.heading = (text, level) => {
headstemp.push({ text, level });
count++;
return `<h${level} id="heading-${count}"><span class="h-text">${text}</span></h${level}>`;
};
// 设置图片内容,统一显示一张缓存图,用于懒加载~
rendererMD.image = (href, _, text) =>
`<img data-src="${href}" src="/cos/2020/1211175603.png" alt="${text}" >`;
marked.setOptions({
highlight(code) {
return hljs.highlightAuto(code).value
},
renderer: rendererMD
});
// 这里的html就是插入到页面的元素文本了
const html = marked('## md内容');
2.3 标题导航实现
上面的例子headstemp
记录了解析过程中的所有标题,作用是借助UI库的组件Anchor
,构建一个标题导航。
下面演示一个基于ant-design-vue
的版本,如果你使用的UI库是类似的锚点组件,那么代码将只需要小改动即可。代码使用jsx
语法,vue
模板语法请自行分离代码~
Recursive.tsx 导航中的链接组件
import { Anchor } from 'ant-design-vue';
import { defineComponent, PropType } from 'vue';
const { Link } = Anchor;
export interface Head {
text: string;
level: number;
}
export interface TocItem extends Head {
anchor: string;
children?: Array<TocItem>;
}
const Recursive = defineComponent({
props: {
tocItem: {
type: Object as PropType<TocItem>,
default: () => []
}
},
setup({ tocItem }) {
return (
<Link href={`#${tocItem.anchor}`} title={tocItem.text}>
{tocItem.children &&
tocItem.children.map((item) => <Recursive key={item.anchor} tocItem={item} />)}
</Link>
);
}
});
export default Recursive;
Topicfy.tsx 用于生成整个导航内容
import { Anchor } from 'ant-design-vue';
import { computed, defineComponent, PropType, ref, watch } from 'vue';
import Recursive, { Head, TocItem } from './Recursive';
const Topicfy = defineComponent({
props: {
// 解析得到的标题列表
heads: {
type: Array as PropType<Array<Head>>
}
},
setup(props) {
const topics = computed(() => {
const tocItems: TocItem[] = [];
// 标题计数器
let count = 0;
const add = (text: string, level: number) => {
count++;
const item = { anchor: `heading-${count}`, level, text };
if (tocItems.length === 0) {
// 第一个 item 直接 push
tocItems.push(item);
} else {
let lastItem = tocItems[tocItems.length - 1]; // 最后一个 item
if (item.level > lastItem.level) {
// item 是 lastItem 的 children
for (let i = lastItem.level + 1; i <= 6; i++) {
const { children } = lastItem;
if (!children) {
// 如果 children 不存在
lastItem.children = [item];
break;
}
// 重置 lastItem 为 children 的最后一个 item
lastItem = children[children.length - 1];
if (item.level <= lastItem.level) {
// item level 小于或等于 lastItem level 都视为与 children 同级
children.push(item);
break;
}
}
} else {
// 置于最顶级
tocItems.push(item);
}
}
};
props.heads?.forEach((item) => {
add(item.text, item.level);
});
return tocItems;
});
return () => (
<Anchor affix={false} showInkInFixed={true}>
{topics.value.map((item) => (
<Recursive key={item.anchor} tocItem={item} />
))}
</Anchor>
);
}
});
export default Topicfy;
该组件是19年参考了网络上的实现完成的,非本人完全原创,react版本参考Topicfy
2.4 获取html代码
编辑器考虑到了可能后端不存储md
格式的文本,而是html
内容,所以提供了onHtmlChanged
方法,用于编辑内容变化后,marked
编译了内容的回调,入参即是html
内容。
<template>
<md-editor
v-model="text"
@onHtmlChanged="saveHtml"
/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MdEditor from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
export default defineComponent({
components: { MdEditor },
data() { return { text: '' }; },
methods: { saveHtml(h: string) { console.log(h) }}
});
</script>
jsx
语法相同。
3. 编辑器的功能演示
3.1 扩展库链接
编辑器扩展内容大多使用了cdn
,考虑了无外网情况,支持了内网链接扩展,演示(假设外部库都在根目录下):
<template>
<md-editor
v-model="text"
highlightJs="/highlight.min.js"
highlightCss="/atom-one-dark.min.css"
prettierCDN="/standalone.js"
prettierMDCDN="/parser-markdown.js"
cropperJs="/cropper.min.js"
cropperCss="/cropper.min.css"
/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MdEditor from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
export default defineComponent({
components: { MdEditor },
data() { return { text: '' }; }
});
</script>
v1.2.0版本目前支持上述链接,图标链接将在后续补丁中添加。
3.2 工具栏自定义
默认的全部工具栏,并且每个功能都绑定了快捷键,如果需要选择性显示工具栏,提供了两个api:toolbars
和toolbarsExclude
,前者显示数组中的全部,后者屏蔽数组中的全部,后者的权重更大。下面是个参考:
案例不显示github
按钮
<template>
<md-editor
v-model="text"
:toolbars="toobars"
/>
<md-editor
v-model="text"
:toolbarsExclude="toolbarsExclude"
/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MdEditor from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
export default defineComponent({
components: { MdEditor },
data() {
return {
text: '',
toobars: ['bold', 'underline', 'italic', 'strikeThrough',
'sub','sup','quote','unorderedList', 'orderedList', 'codeRow',
'code', 'link', 'image', 'table', 'revoke',
'next', 'save', 'pageFullscreen', 'fullscreen',
'preview', 'htmlPreview'],
toolbarsExclude: ['github']
};
}
});
</script>
3.3 扩展语言
编辑器默认内置了中文和英文,并且两者都可以通过扩展api覆盖,该功能主要用来设置内容提示,比如弹窗中的标题等。
扩展一门语言,我们取名为zh-NB
<template>
<md-editor
v-model="text"
:language="language"
:languageUserDefined="languageUserDefined"
/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MdEditor, { StaticTextDefaultValue } from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
const languageUserDefined: { 'zh-NB': StaticTextDefaultValue } = {
'zh-NB': {
toolbarTips: {
bold: '加粗',
underline: '下划线',
italic: '斜体',
strikeThrough: '删除线',
title: '标题',
sub: '下标',
sup: '上标',
quote: '引用',
unorderedList: '无序列表',
orderedList: '有序列表',
codeRow: '行内代码',
code: '块级代码',
link: '链接',
image: '图片',
table: '表格',
revoke: '后退',
next: '前进',
save: '保存',
prettier: '美化',
pageFullscreen: '浏览器全屏',
fullscreen: '屏幕全屏',
preview: '预览',
htmlPreview: 'html代码预览',
github: '源码地址'
},
titleItem: {
h1: '一级标题',
h2: '二级标题',
h3: '三级标题',
h4: '四级标题',
h5: '五级标题',
h6: '六级标题'
},
linkModalTips: {
title: '添加',
descLable: '链接描述:',
descLablePlaceHolder: '请输入描述...',
urlLable: '链接地址:',
UrlLablePlaceHolder: '请输入链接...',
buttonOK: '确定',
buttonUpload: '上传'
},
// v1.2.0新增
clipModalTips: {
title: '裁剪图片上传',
buttonUpload: '上传'
},
// v1.1.4新增
copyCode: {
text: '复制代码',
tips: '已复制'
}
}
};
export default defineComponent({
components: { MdEditor },
data() {
return {
text: '',
language: "zh-NB",
languageUserDefined
};
}
});
</script>
如果key = 'zh-CN',就可以实现中文覆盖,依次类推。
3.4 主题切换
这一块相对比较简单了,内置了暗黑主题和默认主题,通过theme
api切换,demo如下:
<template>
<md-editor
v-model="text"
:theme="theme"
/>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MdEditor from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
export default defineComponent({
components: { MdEditor },
data() {
return {
text: '',
theme: 'dark'
};
}
});
</script>
4. 结尾
更多的更新请关注:md-editor-v3