乐趣区

关于markdown:在-vue3-中使用-markdown-编辑器-mdeditorv3

本文将介绍编辑器的应用和随同的某些开发技巧。

该编辑器反对的性能有:根底的 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 解析 mdhtml,没有扩大语法。

通常来讲,编辑内容存储为 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:toolbarstoolbarsExclude,前者显示数组中的全副,后者屏蔽数组中的全副,后者的权重更大。上面是个参考:

案例不显示 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 主题切换

这一块绝对比较简单了,内置了 暗黑主题 默认主题,通过themeapi 切换,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]()

退出移动版