富文本和markdown处理方案
核心业务:编辑文章。
编辑文章 提供了两种编辑方式:
- 富文本
markdown
主要就是分成三个部分:
- 辅助业务:创建文章、编辑文章
- 富文本库:介绍 、使用
markdown
:介绍、使用
# 辅助业务:创建文章基本结构实现
创建文章 的基本结构主要分成三部分:
article-create
页面:基本结构Editor
组件:富文本编辑器Markdown
组件:markdown
编辑器创建
views/article-create/components/Editor
创建
views/article-create/components/Markdown
在
views/article-create
完成基本结构<template> <div class="article-create"> <el-card> <el-input class="title-input" :placeholder="$t('msg.article.titlePlaceholder')" v-model="title" maxlength="20" clearable > </el-input> <el-tabs v-model="activeName"> <el-tab-pane :label="$t('msg.article.markdown')" name="markdown"> <markdown></markdown> </el-tab-pane> <el-tab-pane :label="$t('msg.article.richText')" name="editor"> <editor></editor> </el-tab-pane> </el-tabs> </el-card> </div> </template> <script setup> import Editor from './components/Editor.vue' import Markdown from './components/Markdown.vue' import { ref } from 'vue' const activeName = ref('markdown') const title = ref('') </script> <style lang="scss" scoped> .title-input { margin-bottom: 20px; } </style>
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
29
30
31
32
33
34
35
36
37
38
# 编辑库选择标准
对于现在的前端编辑库(markdown
与 富文本)而言,如果仅从功能上来去看的话,那么其实都是相差无几的。
随便从 github
中挑选编辑库,只要 star
在 10K(保守些)
以上的,编辑器之上的常用功能一应俱全。
如果你现在想要去选择一个编辑库,那么可以从以下几点中进行选择:
- 开源协议 (opens new window):其中尽量选择
MIT
或者BSD
协议的开源项目 - 功能:功能需要满足基本需求
issue
:通过issue
查看作者对该库的维护程度- 文档:文档越详尽越好,最好提供了中文文档(英文好的可以忽略)
- 国产的:或许你
朋友的朋友的朋友
就是这个库的作者
选择以下编辑器库:
markdown
编辑器:tui.editor (opens new window)- 富文本编辑器:wangEditor (opens new window)
推荐编辑器库:
markdown
编辑器:- tui.editor (opens new window):
Markdown
所见即所得编辑器-高效且可扩展,使用MIT开源协议。 - editor (opens new window):纯文本
markdown
编辑器 - editor.md (opens new window):开源可嵌入的在线
markdown
编辑器(组件),基于CodeMirror
&jQuery
&Marked
。国产 - markdown-here (opens new window):谷歌开源,但是已经 多年不更新 了
- stackedit (opens new window):基于
PageDown
,Stack Overflow
和其他Stack Exchange站点使用的Markdown
库的功能齐全的开源Markdown编辑器。两年未更新了 - markdown-it (opens new window):可配置语法,可添加、替换规则。挺长时间未更新了
- tui.editor (opens new window):
- 富文本编辑器:
- wangEditor (opens new window):国产、文档详尽、更新快速
- tinymce (opens new window):对
IE6+
和Firefox1.5+
都有着非常良好的支持 - quill (opens new window):代码高亮功能、视频加载功能、公式处理比较强。
- ckeditor5 (opens new window):编辑能力强
- wysiwyg-editor (opens new window):收费的 , 就是牛
# 新建文章:markdown 实现
下载 tui.editor (opens new window)
npm i @toast-ui/[email protected]
1渲染基本结构
<template> <div class="markdown-container"> <!-- 渲染区 --> <div id="markdown-box"></div> <div class="bottom"> <el-button type="primary" @click="onSubmitClick">{{ $t('msg.article.commit') }}</el-button> </div> </div> </template> <script setup> import {} from 'vue' </script> <style lang="scss" scoped> .markdown-container { .bottom { margin-top: 20px; text-align: right; } } </style>
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初始化
editor
,处理国际化内容<script setup> import MkEditor from '@toast-ui/editor' import '@toast-ui/editor/dist/toastui-editor.css' import '@toast-ui/editor/dist/i18n/zh-cn' import { onMounted } from 'vue' import { useStore } from 'vuex' // Editor实例 let mkEditor // 处理离开页面切换语言导致 dom 无法被获取 let el onMounted(() => { el = document.querySelector('#markdown-box') initEditor() }) const store = useStore() const initEditor = () => { mkEditor = new MkEditor({ el, height: '500px', // 样式 竖向 previewStyle: 'vertical', language: store.getters.language === 'zh' ? 'zh-CN' : 'en' }) mkEditor.getMarkdown() } </script>
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
29在
editor
在语言改变时,重置editor
import { watchSwitchLang } from '@/utils/i18n' watchSwitchLang(() => { if (!el) return // 拿到用户输入的内容 const htmlStr = mkEditor.getHTML() // 先处理掉 mkEditor.destroy() // 重新初始化 initEditor() // 重新赋值 mkEditor.setHTML(htmlStr) })
1
2
3
4
5
6
7
8
9
10
11
12
13
# 新建文章:markdown 文章提交
在
api/article
中,定义创建文章接口/** * 创建文章 */ export const createArticle = (data) => { return request({ url: '/article/create', method: 'POST', data }) }
1
2
3
4
5
6
7
8
9
10因为
markdown
或者是 富文本 最终都会处理提交事件,所以我们可以把这两件事情合并到一个模块中实现:创建
article-create/components/commit.js
import { createArticle } from '@/api/article' import { ElMessage } from 'element-plus' import i18n from '@/i18n' const t = i18n.global.t export const commitArticle = async (data) => { const res = await createArticle(data) ElMessage.success(t('msg.article.createSuccess')) return res }
1
2
3
4
5
6
7
8
9
10在
markdown.vue
中导入该方法import { commitArticle } from './commit'
1触发按钮提交事件
const props = defineProps({ title: { required: true, type: String } }) const emits = defineEmits(['onSuccess']) ... // 处理提交 const onSubmitClick = async () => { // 创建文章 await commitArticle({ title: props.title, content: mkEditor.getHTML() }) // 提交后重置一下 mkEditor.reset() emits('onSuccess') }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20在
article-create
中传递title
,处理onSuccess
事件// 创建成功 const onSuccess = () => { title.value = '' }
1
2
3
4
# 新建文章:markdown 文章编辑
在
article-detail
中点击编辑按钮,进入创建文章页面// 编辑 const router = useRouter() const onEditClick = () => { router.push(`/article/editor/${articleId}`) }
1
2
3
4
5在
article-craete
中,处理 编辑 相关操作获取当前文章数据
// 处理编辑相关 const route = useRoute() const articleId = route.params.id const detail = ref({}) const getArticleDetail = async () => { detail.value = await articleDetail(articleId) // 标题赋值 title.value = detail.value.title } // articleId存在即编辑 if (articleId) { getArticleDetail() }
1
2
3
4
5
6
7
8
9
10
11
12
13把获取到的数据传递给
markdown
组件<markdown :title="title" :detail="detail" @onSuccess="onSuccess" ></markdown>
1
2
3
4
5在
markdown
中接收该数据const props = defineProps({ ... detail: { type: Object } })
1
2
3
4
5
6检测数据变化,存在
detail
时,把detail
赋值给mkEditor
// 编辑相关 watch( () => props.detail, (val) => { if (val && val.content) { mkEditor.setHTML(val.content) } }, { immediate: true } )
1
2
3
4
5
6
7
8
9
10
11
12创建 编辑文章 接口
/** * 编辑文章详情 */ export const articleEdit = (data) => { return request({ url: '/article/edit', method: 'POST', data }) }
1
2
3
4
5
6
7
8
9
10在
commit.js
中生成 编辑文章 方法export const editArticle = async data => { const res = await articleEdit(data) ElMessage.success(t('msg.article.editorSuccess')) return res }
1
2
3
4
5在
markdown
中处理提交按钮事件// 处理提交 const onSubmitClick = async () => { if (props.detail && props.detail._id) { // 编辑文章 await editArticle({ id: props.detail._id, title: props.title, content: mkEditor.getHTML() }) } else { // 创建文章 await commitArticle({ title: props.title, content: mkEditor.getHTML() }) } mkEditor.reset() emits('onSuccess') }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 新建文章:富文本 实现
富文本我们使用 wangEditor (opens new window),所以我们得先去下载 wangEditor (opens new window)
npm i [email protected]
创建基本组件结构
<template> <div class="editor-container"> <div id="editor-box"></div> <div class="bottom"> <el-button type="primary" @click="onSubmitClick">{{ $t('msg.article.commit') }}</el-button> </div> </div> </template> <script setup> import {} from 'vue' </script> <style lang="scss" scoped> .editor-container { .bottom { margin-top: 20px; text-align: right; } } </style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24初始化
wangEditor
<script setup> import E from 'wangeditor' import { onMounted } from 'vue' // Editor实例 let editor // 处理离开页面切换语言导致 dom 无法被获取 let el onMounted(() => { el = document.querySelector('#editor-box') initEditor() }) const initEditor = () => { editor = new E(el) editor.config.zIndex = 1 // 菜单栏提示 editor.config.showMenuTooltips = true editor.config.menuTooltipPosition = 'down' editor.create() } </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22wangEditor
的 国际化处理 (opens new window),官网支持 i18next (opens new window)npm i --save i18next@20.4.0
1对
wangEditor
进行国际化处理import i18next from 'i18next' import { useStore } from 'vuex' const store = useStore() ... const initEditor = () => { ... // 国际化相关处理 editor.config.lang = store.getters.language === 'zh' ? 'zh-CN' : 'en' editor.i18next = i18next editor.create() }
1
2
3
4
5
6
7
8
9
10
11
12
13
14处理提交事件
import { onMounted, defineProps, defineEmits } from 'vue' import { commitArticle } from './commit' const props = defineProps({ title: { required: true, type: String } }) const emits = defineEmits(['onSuccess']) ... const onSubmitClick = async () => { // 创建文章 await commitArticle({ title: props.title, content: editor.txt.html() }) editor.txt.html('') emits('onSuccess') }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22不要忘记在
article-create
中处理对应事件<editor :title="title" :detail="detail" @onSuccess="onSuccess" ></editor>
1
2
3
4
5最后处理编辑
const props = defineProps({ ... detail: { type: Object } }) // 编辑相关 // props.detail检测有无数据 watch( () => props.detail, (val) => { if (val && val.content) { editor.txt.html(val.content) } }, { immediate: true } ) const onSubmitClick = async () => { if (props.detail && props.detail._id) { // 编辑文章 await editArticle({ id: props.detail._id, title: props.title, content: editor.txt.html() }) } else { // 创建文章 await commitArticle({ title: props.title, content: editor.txt.html() }) } editor.txt.html('') emits('onSuccess') }
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
29
30
31
32
33
34
35
36
37
38
39
40
# 总结
核心重点就是 编辑库 的选择
常用的编辑库其实主要就分成了这么两种:
markdown
- 富文本
因为对于编辑器库而言,它的使用方式都是大同小异的,大家只需要根据我们 《编辑器库选择标准》 来选择使用自己当前情况的编辑器库即可