前言
vue配置bytemd掘金同款markdown编辑器
markdown编辑器对于程序员来书写还算比较方便的,特别是学习掌握了语法后,就更是写作的利器了,所以今天就学习看了下掘金同款的bytemd的官方文档介绍来配置配置
为什么使用
因为为自己写博客内容的后台时需要一个,markdown的编辑器,编辑操作我的博客内容。看了掘金的编辑器,感觉很不错,于是开始了搜索发现使用的是bytemd的,于是开始了查看资料,在自己的项目去配置使用!下面就放上项目官方地址和demo
Github地址:Github
官方demo:demo演示
踩坑过程
总体总结下来,就是自己的技术太菜了吧!不太了解书写内容,看过掘友分享的是react的帮助不少,于是开始踩坑vue的,希望可以帮助到有需要的小伙伴吧!
do'w下载安装vue里使用
npm install @bytemd/vue -S (或者使用yarn) yarn add @bytemd/vue -S
vue文件里面使用
<template>
<div>
<Editor class="editos" :value="value" />
<Viewer class="viewer" :tabindex="2" :value="value"
></Viewer>
</div>
</template>
<script>
import { Editor, Viewer } from '@bytemd/vue'
export default {
data () {
return {
value: '', // 获取的markdow文档内容
}
},
components: { Editor, Viewer }, // 组件注册
}
</script>
安装插件
github文档的官网有插件介绍,大家可以安装需求安装,也可以全部安装
我的建议如果是个人项目那就不需要care 直接全部上它丫的
大家npm安装就可以,接下来继续安装两个一个是主题样式一个是配置中文的,大家去github直接拷贝json文件或者是css也可以。
npm i bytemd -S // 等会配置中文
npm i juejin-markdown-themes -S // 等会配置主题
配置中文
npm i bytemd -S
你也可以直接拷贝这个包下面的json文件
<Editor :locale="zhHans" />
js 代码
import zhHans from 'bytemd/lib/locales/zh_Hans.json'
data () { return { zhHans } }
配置图片上传
原本是需要配置插件的,但在最新版已直接集成了,开启的方法也是特别简单!
<Editor :uploadImages="uploadImage"/>
JS代码
配置点击事件就可以了
async uploadImage (files) {
// files 获取的图片文件,这里处理逻辑
console.log('files', files)
return [
{
title: files.map((i) => i.name),
url: 'http'
}
]
}
这里再配置两个图片代码方便不清楚怎么上传图片的小伙伴们查看
使用方法就是通过uploadImage方法获取二进制文件,然后这个文件直接就可以通过axios或者是其他上传到服务器,然后把返回的url赋值到数组列表里面就可以了。同理,二进制文件流是可以转成base64图片的
配置样式主题
这里卡了我半天,后面知道了就是简简单单引入css文件,我人麻了~ 无语
Markdown 主题: Markdown主题Github地址
import 'juejin-markdown-themes/dist/juejin.min.css'
直接引入就可以使用不同的主题了,这里使用的掘金的,小伙伴们喜欢其他的也可以使用不同风格的!
完整配置代码
这里简单解释一下@bytemd/vue里面导出两个组件
编辑器Editor
阅读者的Viewer
咱们理解简单一点,那就是编辑(Editor)可以修改,增加, (Viewer)阅读只可以看,也就是渲染出你存储的markdow文档!同样的编辑器配置了插件,阅读器也是需要配置的才可以对应渲染出你存储的样式代码
<template>
<div class="details">
<Editor
class="editos"
:value="value"
:plugins="plugins"
:locale="zhHans"
@change="handleChange"
:uploadImages="uploadImage"
/>
<Viewer
class="viewer"
:tabindex="2" // github官方文档有解释
:sanitize="23" // 官方文档有解释
:value="value"
:plugins="plugins"
:locale="zhHans"
></Viewer>
<div class="fl al btn">
<el-button @click="send" type="primary">修改</el-button>
<el-button @click="send" type="primary">发布</el-button>
</div>
</div>
</template>
<script>
// 这里就是引入所有的扩展的插件
import 'bytemd/dist/index.css' // 导入编辑器样式
import { Editor, Viewer } from '@bytemd/vue'
import gfm from '@bytemd/plugin-gfm'
import highlightssr from '@bytemd/plugin-highlight-ssr'
import highlight from '@bytemd/plugin-highlight'
import breaks from '@bytemd/plugin-breaks'
import footnotes from '@bytemd/plugin-footnotes'
import frontmatter from '@bytemd/plugin-frontmatter'
import gemoji from '@bytemd/plugin-gemoji'
import mediumZoom from '@bytemd/plugin-medium-zoom'
import zhHans from 'bytemd/lib/locales/zh_Hans.json'
import 'highlight.js/styles/vs.css'
import 'juejin-markdown-themes/dist/juejin.min.css' // 其实就是需要这个css文件
const plugins = [
// 将所有的扩展功能放入插件数组中,然后就可以生效了
gfm(),
highlight(),
highlightssr(),
breaks(),
frontmatter(),
footnotes(),
gemoji(),
mediumZoom()
]
export default {
components: { Editor, Viewer }, // 组件注册
data () {
return {
value: '', // 获取的内容
plugins, // 插件
zhHans, // 简体中文
}
},
methods: {
// 获取书写文档内容
handleChange (v) {
console.warn(v)
this.value = v
},
// 上传图片 点击触发上传图片事件,大家获取文件把图片上传服务器然后返回url既可
async uploadImage (files) {
console.log('files', files)
return [
{
title: files.map((i) => i.name),
url: 'http'
}
]
}
}
}
</script>
<style lang="scss">
.details {
position: fixed;
top: 60px;
left: 0;
width: 100vw;
height: 100vh;
.editos {
.bytemd {
height: calc(100vh - 150px) !important; // 改变编辑器默认高度,不需要的可以不配置
}
}
.viewer {
margin-top: 20px;
background: #fff;
padding: 20px;
.bytemd {
height: calc(100vh - 200px) !important;
}
}
.btn {
flex-direction: row-reverse;
margin: 20px;
.el-button {
margin-right: 20px;
}
}
}
</style>
vue3使用bytemd
实现地址: 个人博客
仓库里面包括了前端样式和接口,我现在博客地址后台就是用的这个,还要API接口也开发完成使用的nodejs, 前端是vite+vue3 前端显示 接口地址
官网出了vue3的这个包,但是我安装了。按照之前的引入就发现渲染出来的markdown文件没有样式 ~
引用方法就是
<Viewer :value="state.content"></Viewer>
import { Viewer } from '@bytemd/vue-next'
vue3的兼容感觉还不是很好,于是就重新封装一下组件实现了渲染效果。
安装配置,发现并不能如vue2那样方便使用 ~ 磕磕绊绊的还是实现了,应该会有更好的实现方法,这里我就抛砖引玉了.
下面代码实现了有样式渲染和目录
编辑模式配置代码:
<Editor
class="editos"
:value="state.value"
:plugins="plugins"
:locale="zhHans"
@change="handleChange"
:uploadImages="uploadImage"
/>
// js
const plugins = [gfm(), highlight(), breaks(), frontmatter(), footnotes(), gemoji(), mediumZoom()]
import 'bytemd/dist/index.css'
import { Editor } from '@bytemd/vue-next'
import gfm from '@bytemd/plugin-gfm'
import highlight from '@bytemd/plugin-highlight'
import breaks from '@bytemd/plugin-breaks'
import footnotes from '@bytemd/plugin-footnotes'
import frontmatter from '@bytemd/plugin-frontmatter'
import gemoji from '@bytemd/plugin-gemoji'
import mediumZoom from '@bytemd/plugin-medium-zoom'
import zhHans from 'bytemd/locales/zh_Hans.json'
编辑的导入,区别不大。主要是渲染的时候,引入viewer出现了问题,后面是通过封装组件的方法实现的,
// MdViewer.vue 组件
<script setup lang="ts">
import { ref, onMounted, getCurrentInstance, watch } from 'vue'
import * as bytemd from 'bytemd'
import 'bytemd/dist/index.min.css'
import 'juejin-markdown-themes/dist/scrolls-light.min.css'
import zhHans from 'bytemd/locales/zh_Hans.json'
import breaks from '@bytemd/plugin-breaks'
import highlight from '@bytemd/plugin-highlight'
import footnotes from '@bytemd/plugin-footnotes'
import frontmatter from '@bytemd/plugin-frontmatter'
import gfm from '@bytemd/plugin-gfm'
import mediumZoom from '@bytemd/plugin-medium-zoom'
import gemoji from '@bytemd/plugin-gemoji'
interface Props {
value?: string
plugins?: any
locale?: any
}
const props = withDefaults(defineProps<Props>(), {
value: '',
locale: zhHans,
plugins: [breaks(), highlight(), footnotes(), frontmatter(), gfm(), mediumZoom(), gemoji()],
})
const viewer = ref<bytemd.Editor | any>(null)
const instance:any = getCurrentInstance()
onMounted(() => {
viewer.value = new bytemd.Viewer({
target: instance?.subTree.el,
props,
})
})
watch(props, newValue => {
viewer.value.$set(Object.fromEntries(Object.entries(newValue).filter(v => v)))
})
</script>
<template>
<div />
</template>
<style lang="less" scoped></style>
实现的方法是参考掘金的一位帅哥的方法。只需要传入数据库保存的md文档内容就可以正确渲染出来,然后还配置了目录,基本实现了掘金的目录定位和滚动跳转到,指定位置。
实现的方法和原理就是 获取h1~ h4 标签,滚动监听,和a标签的锚点去实现
目录实现的代码
// html
<div class="tree">
<h3 class="directory">目录</h3>
<el-divider style="margin: 10px 0" />
<ul class="menu_content">
<li v-for="(item, key) of cata.menuData" :key="key"
:style="menuStyle(item.type)"
>
<a
:href="'#' + item.point"
:class="cata.menuState === item.txt ? `tree_list active`:`tree_list`"
>
{{ item.txt }}
</a>
</li>
</ul>
</div>
// css
.tree {
width: 100%;
min-height: 400px;
background-color: $white;
padding: 20px;
max-height: 70vh;
min-height: 10vh;
overflow-y: scroll;
.directory {
@include font-set($font18, #1a1a1a, 400, 1.5);
}
.menu_content {
width: 100%;
.tree_list {
display: block;
@include font-set($font14, #888, 400, 1.3);
padding: 10px 0;
&:hover {
background-color: #f7f8fa;
border-radius: 6px;
}
}
.active {
position: relative;
color: #1e80ff;
&::before {
content: '';
height: 20px;
width: 5px;
background: #1e80ff;
position: absolute;
left: -17px;
top: 50%;
border-radius: 0 5px 5px 0;
transform: translate(-50%, -50%);
}
}
}
}
// js 核心代码实现目录定位和滚动监听
import { onMounted, reactive, ref, nextTick } from 'vue'
interface Menu {
type: string;
txt: string;
offsetTop: number;
point: string
}
const cata = reactive({
menuData: <Menu[]>[],
menuState: '',
})
/**
* h1 h2 h3 h4 标签样式
* @param type
*/
const menuStyle = (type: string) => {
let style = {}
if (type === 'H2') style = { 'padding-left': 10 + 'px' }
if (type === 'H3') style = { 'padding-left': 20 + 'px' }
if (type === 'H4') style = { 'padding-left': 30 + 'px' }
return style
}
onMounted(() => {
componentDidMount()
window.addEventListener('scroll', onScroll, true)
})
// 重新实现目录的定位
const componentDidMount = () => {
nextTick(() => {
getElement(['H1', 'H2', 'H3', 'H4'])
})
}
/**
* 获取标题锚点
* 参数nodeArr 表示需要解析目录内容的标题
*/
const getElement = (nodeArr: string[]) => {
let nodeInfo: Menu[] = []
const dom: any = document.querySelector('.markdown-body')
// console.log(dom.childNodes)
dom.childNodes.forEach((item: any, key: number) => {
// console.log(item.nodeName)
if (nodeArr.includes(item.nodeName)) {
nodeInfo.push({
type: item.nodeName,
txt: item.innerText,
offsetTop: item.offsetTop,
point: `target_${key}`,
})
item.setAttribute('id', `target_${key}`)
console.log(item)
}
})
cata.menuData = nodeInfo
cata.menuState = nodeInfo[0].txt
console.log('nodeInfo', nodeInfo)
}
/**
* 监听页面开始滚动
*/
const onScroll = (e: any) => {
// 当前页面滚动的距离
let scrollTop = e.target.documentElement.scrollTop || e.target.body.scrollTop
// console.log(scrollTop)
//变量windowHeight是可视区的高度
let windowHeight = document.documentElement.clientHeight || document.body.clientHeight
//变量scrollHeight是滚动条的总高度
let scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight
let currentmenu = cata.menuData[0].txt // 设置menuState对象默认值第一个标题
for (let item of cata.menuData) {
console.log(item.offsetTop)
if (scrollTop >= item.offsetTop) {
currentmenu = item.txt
} else break
}
if (currentmenu !== cata.menuState) {
cata.menuState = currentmenu
}
// 如果到底部,就命中最后一个标题
if (scrollTop + windowHeight === scrollHeight) {
console.log('滚动到底部了')
cata.menuState = cata.menuData[cata.menuData.length - 1].txt
}
}
实现的效果呢,只能说还是不够完美!还需要优化 ~ 不过基本上满足我自己的需求,后续再踩踩试试。
结尾
以上就是我使用Vue去配置使用bytemd这款编辑器时候碰到的问题,记录下来,方便后续查阅读
已更新vue3的踩坑 ~