前言
老大说我们现在后台的编辑器(Tinymce)编辑的内容在不同的客户端展示的不一样 为了使我们客户端的文本格式统一,我们换成掘金的编辑器。然后还给我发bytemd的GitHub链接:github.com/bytedance/b… 让我研究研究。我心想,不同端展示不一样不应该去调客户端展示内容的代码嘛,之前的Tinymce已经经过多方面调试,除了某几个已经发现的奇怪bug(但不影响使用),其他方面(比如解决粘贴第三方图片跨域等问题)都很ok,但是谁叫他是老大呢,那就开始研究。
准备工作
然后对着文档一顿研究,然后开始安装,我是vue3版本的,看好自己的的版本安装,有一堆插件,可以根据自己开发需求来,需要哪个安装哪个就好了。
npm install bytemd
npm install @bytemd/vue-next
npm install @bytemd/plugin-gfm
npm install @bytemd/plugin-gemoji
npm install @bytemd/plugin-highlight
npm install @bytemd/plugin-frontmatter
npm install @bytemd/plugin-medium-zoom
npm install @bytemd/plugin-breaks
npm install juejin-markdown-themes
上代码
<template>
<div class="markdow-page">
<Editor :locale="zhHans" :value="mdValue" :plugins="mdPlugins" @change="handleChange" :upload-images="handleUploadImages"
@paste="handlePaste" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Editor, Viewer } from '@bytemd/vue-next';
import gfm from '@bytemd/plugin-gfm';
import gemoji from '@bytemd/plugin-gemoji';
import highlight from '@bytemd/plugin-highlight';
import frontmatter from '@bytemd/plugin-frontmatter';
import mediumZoom from '@bytemd/plugin-medium-zoom';
import breaks from '@bytemd/plugin-breaks';
import zhHans from 'bytemd/locales/zh_Hans.json';
import 'bytemd/dist/index.css';
import 'juejin-markdown-themes/dist/juejin.min.css';
import { imgConverter, uploadImage, htmlToMarkdown } from "@/api/public";
const mdValue = ref('')
const mdPlugins = ref([
gfm(),
gemoji(),
highlight(),
frontmatter(),
mediumZoom(),
breaks()
])
const handleChange = (val: any) => {
value.value = val;
};
const handlePaste = async (event: any) => {
// 获取 'html' 格式的数据,数据是一个字符串,而不是实际的DOM元素
let htmlData = event.clipboardData.getData('text/html');
// 转成DOM元素
let parser = new DOMParser();
let doc: any = parser.parseFromString(htmlData, "text/html");
// 由于粘贴的是第三方内容,图片需要处理一下
const images = doc.getElementsByTagName("img");
if (images.length) {
let tasks: any = [];
// 你是否会觉得下面的代码为啥要用images[i]而不用item? 因为此处的images[i] != item
Array.from(images).forEach((item: any, i: number) => {
// 如果图片不是可信赖的(自己公司的)
if (!images[i].src.includes("https://img.xxx.com")) {
tasks.push(
// 将第三方图片转成自己公司的,dataset是因为粘贴公众号的内容会发现公众号的图片没有src属性,需要转换一下
imgConverter({ url: images[i].src || images[i].dataset.src }).then((result: any) => {
images[i].src = result.data;
})
);
}
});
await Promise.all(tasks);
}
// 转回html字符串传给后端
let serializer = new XMLSerializer();
let str = "";
for (let node of doc.body.childNodes) {
str += serializer.serializeToString(node);
}
htmlToMarkdown({ text: str }).then((res:any) => {
value.value = res.data;
})
};
const handleUploadImages = async (files: any) => {
let imgs: any = [];
for (let index = 0; index < files.length; index++) {
const item = files[index];
let fromData = new FormData();
fromData.append("file", item);
let res = await uploadImage(fromData); // 上传到阿里云
imgs.push({
title: item.name,
url: res.url,
});
}
return imgs;
};
</script>
<style lang="scss" scoped>
.markdown-page {
width: 100%;
:deep() {
.bytemd {
height: 800px;
}
}
}
</style>
遇到的问题或需要注意的
问题一
由于我是vue3+ts,在引入Editor一直报错(应该是我的tsconfig.json文件没有配置好),说什么Editor是一个类型,这个报错好像是因为源码没有写类型,然后我靠着我蹩脚的TS知识加聪明的Chatgpt,找到了解决办法,我在我的类型声明文件 shims-vue.d.ts 中加入以下代码
declare module '@bytemd/vue-next' {
export const Editor: any;
export const Viewer: any;
}
问题二
这个文本框看起来差不多了,输入什么的调试都ok,但是当我去粘贴第三方的内容时,高亮什么的并不能生效,去打印change事件的val值,发现这个值有问题,打印出来粘贴第三方文本的值并不是html格式,而是纯文本格式,以为是自己搞错了,然后去官方demo上测试,发现给出的demo也是这样的,这怎么和掘金自己的不一样啊。然后又开始研究,然后想了想Tinymce的文本框是怎么处理粘贴第三方的内容,于是测试paste事件,可以看我上面写的handlePaste事件的内容。
问题三
文本框上面的那些插件选项后面三个鼠标放上去的提示是英文,其他的都是中文,目前没找到解决办法,希望有知道的jym评论区留言指导或者私信我
问题四
当我粘贴的文本是HTML格式的时候,是没有问题的,但是如果我复制的是markdown格式的会粘贴出问题
问题五
如果我粘贴了一部分内容,需要粘贴第二部分内容的时候,由于我上面是直接赋值的方式,会导致第二次粘贴的内容将第一次粘贴的内容给覆盖
总结
由于markdown格式不支持背景图,粘贴第三方的东西可能还是会出现样式问题。还有个问题是,文本框编辑好内容后,传给后端需要转成HTML格式的字符串(传给后端,后端再传到客户端,客户端v-html渲染就好了)
// 装了一个html转markdown格式的包
import * as marked from "marked";
// 最后将result传给后端就好了
let result = marked.parse(value.value);
后端将HTML格式转md格式的库是:github.com/thephpleagu…
可能最后还是会选择用Tinymce文本框,但是这次的需求又然让我进步一点点。有啥好的建议欢迎评论区留言。