个人网站建站日记-集成Markdown编辑器

一次偶然的机会,我体验的到了markdown的便捷,于是乎,我就着手给我的网站闲蛋博客社区集成了Markdown,现在可以自由的切换Markdown与富文本编辑的使用了。这里我特此分享记录下安装使用的过程。

一、安装Markdown编辑器

这里我采用的是md-editor-v3编辑器,目前看来还是很好用的,安装方便,使用简单

二 pnpm安装 pnpm install md-editor-v3

注意,直接运行的是安装的最新版的,最新版本的使用的vue3.5以上,如果你低于3.5的版本,代码运行的时候可能会报错,所以安装的是其它的版本

pnpm install md-editor-v3@4.21.1

三、页面基本使用

话不多说,直接看代码

<template>
    <MdEditor :autoFocus="true" v-model="textContent"  :toolbars="toolbars">
    </MdEditor>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { MdEditor, DropdownToolbar, ToolbarNames, config } from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
const { checkImg } = useUpload();
const toolbars: ToolbarNames[] = [
    'bold',
    'underline',
    'italic',
    '-',
    'title',
    'strikeThrough',
    'sub',
    'sup',
    'quote',
    'unorderedList',
    'orderedList',
    'task',
    '-',
    'codeRow',
    'code',
    'link',
    'image',
    'table',
    'mermaid',
    'katex',
    '-',
    'revoke',
    'next',
    '=',
    'pageFullscreen',
    'fullscreen',
    'preview',
    'htmlPreview',
    'catalog',
    'github'
];

</script>

让后运行项目如下图
image

这样就可以了,但是我们看官网,它是可以支持切换主题的,那么怎实现呢。

四、编辑器切换主题

实现它就是去使用它的#defToolbars插槽,可以实现

实现切换预览主题

我这里只是默认使用它里面提供的几个主题,定义预览主题如下:

const previewThemeOptions = [
    {
        value: 'default',
        label: 'default'
    },
    {
        value: 'github',
        label: 'github'
    },
    {
        value: 'vuepress',
        label: 'vuepress'
    },
    {
        value: 'mk-cute',
        label: 'mk-cute'
    },
    {
        value: 'smart-blue',
        label: 'smart-blue'
    },
    {
        value: 'cyanosis',
        label: 'cyanosis'
    }
];

然后插槽里面的代码

<MdEditor :autoFocus="true" v-model="textContent" :previewTheme="previewThemeSelected"
          :toolbars="toolbars">
        <template #defToolbars>
            <DropdownToolbar title="预览主题" :visible="showPreviewTheme" :on-change="appendixPreviewThemeChanged">
                <template #overlay>
                    <el-select v-model="previewThemeSelected" size="small" style="width: 70px"
                        @change="previewThemeChange">
                        <el-option v-for="item in previewThemeOptions" :key="item.value" :label="item.label"
                            :value="item.value" />
                    </el-select>
                </template>
                <template #trigger>
                    <el-icon class="md-editor-icon" :size="18">
                        <Platform />
                    </el-icon>
                </template>
            </DropdownToolbar>
        </template>
    </MdEditor>

同时在toolbars里面要加一个对应的索引位置,代表自己的工具栏
image

然后运行看下代码部分截图

default主题

image

github主题

image

smart-blue主题

image

以此类推,其它的主题我就不演示了

图片上传

图片上传要实现它的 @on-upload-img="onUploadImg"方法,参考代码如下,也可以参考官网的写法,比较简单

function onUploadImg(files, callback) {
    files.forEach((s) => {
        let file = s;
        let formData = new FormData();
        formData.append('file', file);
        uploadFileApi(formData).then((res) => {
            let arr = [];
            arr.push(res.urlPath);
            callback(arr);
        });
    });
}

内容超链接target属性

如果想实现target属性需要安装 markdown-it-link-attributes 插件,让后代码加入如下代码

import LinkAttr from 'markdown-it-link-attributes';
config({
    markdownItPlugins(plugins) {
        return [
            ...plugins,
            {
                type: 'linkAttr',
                plugin: LinkAttr,
                options: {
                    matcher(href: string) {
                        return !href.startsWith('#');
                    },
                    attrs: {
                        target: '_blank'
                    }
                }
            }
        ];
    }
});

应该没有什么还有补充了😲

五、页面如何渲染

上面搞的差不多后,就要实现文章页面渲染,我看网上的解决方法都是通过安装marked插件,然后通过它把markdown语法转成html,但是我没有使用。因为我看md-editor-v3已经实现了预览,并且非常简单。就是使用MdPreview组件就好了,也不需要额外安装调样式。

import { MdPreview } from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
  <MdPreview v-else v-model="articleContent" :previewTheme="预览主题" 
    :codeTheme="代码主题" />

然后页面加载渲染就可以了
image

真实效果可以点这里 https://www.xiandanplay.com/article/view?id=17143377224138752&articleCategoryId=16078840161206272

六、代码示例

以下代码仅仅是我的业务代码,可以参考,具体的可以根据需要自行更改

<template>
    <MdEditor :autoFocus="true" v-model="textContent" :previewTheme="previewThemeSelected"
        :codeTheme="codeThemeSelected" @on-upload-img="onUploadImg" :toolbars="toolbars">
        <template #defToolbars>
            <DropdownToolbar title="预览主题" :visible="showPreviewTheme" :on-change="appendixPreviewThemeChanged">
                <template #overlay>
                    <el-select v-model="previewThemeSelected" size="small" style="width: 70px"
                        @change="previewThemeChange">
                        <el-option v-for="item in previewThemeOptions" :key="item.value" :label="item.label"
                            :value="item.value" />
                    </el-select>
                </template>
                <template #trigger>
                    <el-icon class="md-editor-icon" :size="18">
                        <Platform />
                    </el-icon>
                </template>
            </DropdownToolbar>
            <DropdownToolbar title="代码主题" :visible="showCodeTheme" :on-change="appendixCodeThemeChanged">
                <template #overlay>
                    <el-select v-model="codeThemeSelected" size="small" style="width: 70px" @change="codeThemeChange">
                        <el-option v-for="item in codeThemeOptions" :key="item.value" :label="item.label"
                            :value="item.value" />
                    </el-select>
                </template>
                <template #trigger>
                    <el-icon class="md-editor-icon" :size="18">
                        <Postcard />
                    </el-icon>
                </template>
            </DropdownToolbar>
        </template>
    </MdEditor>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { MdEditor, DropdownToolbar, ToolbarNames, config } from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
import { Marked } from 'marked';
import { useUpload } from '@/hooks/useUpload';
import { uploadFileApi } from '@/api/uploadFile';
import { CloudStorageType } from '@/utils/globalDeclare';
import LinkAttr from 'markdown-it-link-attributes';
config({
    markdownItPlugins(plugins) {
        return [
            ...plugins,
            {
                type: 'linkAttr',
                plugin: LinkAttr,
                options: {
                    matcher(href: string) {
                        return !href.startsWith('#');
                    },
                    attrs: {
                        target: '_blank'
                    }
                }
            }
        ];
    }
});
const { checkImg } = useUpload();
const marked = new Marked({ gfm: true });
const props = defineProps({
    codeTheme: {
        type: String,
        default: 'default'
    },
    previewTheme: {
        type: String,
        default: 'default'
    }
});
const toolbars: ToolbarNames[] = [
    'bold',
    'underline',
    'italic',
    '-',
    'title',
    'strikeThrough',
    'sub',
    'sup',
    'quote',
    'unorderedList',
    'orderedList',
    'task',
    '-',
    'codeRow',
    'code',
    'link',
    'image',
    'table',
    'mermaid',
    'katex',
    '-',
    'revoke',
    'next',
    0,
    1,
    '=',
    'pageFullscreen',
    'fullscreen',
    'preview',
    'htmlPreview',
    'catalog',
    'github'
];
const textContent = ref<string>();
const emit = defineEmits(['changeTheme']);
const showPreviewTheme = ref(false);
const showCodeTheme = ref(false);
const previewThemeOptions = [
    {
        value: 'default',
        label: 'default'
    },
    {
        value: 'github',
        label: 'github'
    },
    {
        value: 'vuepress',
        label: 'vuepress'
    },
    {
        value: 'mk-cute',
        label: 'mk-cute'
    },
    {
        value: 'smart-blue',
        label: 'smart-blue'
    },
    {
        value: 'cyanosis',
        label: 'cyanosis'
    }
];
const codeThemeOptions = [
    {
        value: 'atom',
        label: 'atom'
    },
    {
        value: 'a11y',
        label: 'a11y'
    },
    {
        value: 'github',
        label: 'github'
    },
    {
        value: 'gradient',
        label: 'gradient'
    },
    {
        value: 'kimbie',
        label: 'kimbie'
    },
    {
        value: 'paraiso',
        label: 'paraiso'
    },
    {
        value: 'qtcreator',
        label: 'qtcreator'
    },
    {
        value: 'stackoverflow',
        label: 'stackoverflow'
    }
];
const previewThemeSelected = ref<string>(props.previewTheme);
const codeThemeSelected = ref<string>(props.codeTheme);
init();
function init() {
    let themeStore = localStorage.getItem('mdv3_theme_store');
    if (themeStore) {
        let arr = themeStore.split('|');
        codeThemeSelected.value = arr[0];
        previewThemeSelected.value = arr[1];
        emit('changeTheme', {
            codeTheme: codeThemeSelected.value,
            previewTheme: previewThemeSelected.value
        });
    }
}
function previewThemeChange(selected) {
    setThemeStore('preview', selected);
}
function codeThemeChange(selected) {
    setThemeStore('code', selected);
}
function setThemeStore(themeType, themeSelected) {
    let theme: any = {};
    if (themeType == 'code') {
        localStorage.setItem(
            'mdv3_theme_store',
            themeSelected + '|' + previewThemeSelected.value
        );
        theme.codeTheme = themeSelected;
        codeThemeSelected.value = themeSelected;
        theme.previewTheme = previewThemeSelected.value;
    } else {
        localStorage.setItem(
            'mdv3_theme_store',
            codeThemeSelected.value + '|' + themeSelected
        );
        theme.codeTheme = codeThemeSelected.value;
        theme.previewTheme = themeSelected;
        previewThemeSelected.value = themeSelected;
    }
    emit('changeTheme', theme);
}

function appendixPreviewThemeChanged() {
    if (showPreviewTheme.value) {
        showPreviewTheme.value = false;
    } else {
        showPreviewTheme.value = true;
    }
}
function appendixCodeThemeChanged() {
    if (showCodeTheme.value) {
        showCodeTheme.value = false;
    } else {
        showCodeTheme.value = true;
    }
}
function onUploadImg(files, callback) {
    files.forEach((s) => {
        let file = s;
        let result: boolean = checkImg(file, 2);
        if (result == false) {
            return;
        }
        let formData = new FormData();
        formData.append('file', file);
        formData.append('cloudStorageType', CloudStorageType.Qiniu);
        uploadFileApi(formData).then((res) => {
            let arr = [];
            arr.push(res.urlPath);
            callback(arr);
        });
    });
}
function setContent(content) {
    textContent.value = content;
}
function getTheme() {
    let theme = {
        codeTheme: codeThemeSelected.value,
        previewTheme: previewThemeSelected.value
    };
    return theme;
}
function getText() {
    const plainText = marked
        .parse(textContent.value)
        .replace(/<\/?[^>]+(>|$)/g, '')
        .trim();
    return plainText;
}
const getContent = () => {
    return textContent.value
};
defineExpose({ getText, getTheme, setContent, getContent });
</script>

作者:程序员奶牛
个人开源网站:https://www.xiandanplay.com
源码地址:https://gitee.com/MrHanchichi/xian-dan

posted @   灬丶  阅读(154)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 本地部署 DeepSeek:小白也能轻松搞定!
· 基于DeepSeek R1 满血版大模型的个人知识库,回答都源自对你专属文件的深度学习。
· 如何给本地部署的DeepSeek投喂数据,让他更懂你
· 在缓慢中沉淀,在挑战中重生!2024个人总结!
· 大人,时代变了! 赶快把自有业务的本地AI“模型”训练起来!
点击右上角即可分享
微信分享提示