Vanessa219 / vditor

♏ 一款浏览器端的 Markdown 编辑器,支持所见即所得(富文本)、即时渲染(类似 Typora)和分屏预览模式。An In-browser Markdown editor, support WYSIWYG (Rich Text), Instant Rendering (Typora-like) and Split View modes.
https://b3log.org/vditor
MIT License
8.45k stars 869 forks source link

Vditor.preview #1229

Closed de1ck closed 2 years ago

de1ck commented 2 years ago

连续 previewRender 有概率会导致后面赋值的 text,被前面的覆盖

previewRender => md2Html 需要异步加载,所以首次渲染可能没有第二次快

<template>
  <div>
    <MarkdownViewer :text="content" />
  </div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { MarkdownEditor, MarkdownViewer } from '@/components';
const str = "123";
const content = ref('');

setTimeout(() => {
  content.value = str;
});
</script>
// MarkdownPreview
<template>
  <div ref="viewerRef" @click.prevent="clickView" />
</template>
<script lang="ts" setup>
import {
  ref, onMounted, defineProps, withDefaults, watch,
} from 'vue';
import Vditor from 'vditor';
import 'vditor/dist/index.css';
import '@/style/vditor.scss';
import { VDITOR_CDN } from '@/utils/consts';

const props = withDefaults(defineProps<{
  mode?: 'dark' | 'light',
  text?: string;
  after?(): void;
}>(), {
  mode: 'light',
});

const viewerRef = ref();
function init() {
  const previewElement = viewerRef.value;
  Vditor.preview(previewElement!, props.text ?? '', {
    mode: 'light',
    cdn: VDITOR_CDN,
    theme: {
      current: 'light',
      path: `${VDITOR_CDN}/dist/css/content-theme`,
    },
    // markdown: {
    //   toc: true,
    //   listStyle: true,
    // },
    // anchor: 1,
    after: props.after,
  });
}

function clickView(event) {
  if (event.target?.tagName === 'A' && event.target?.href) {
    // 对捕获到的 a 标签进行处理,需要先禁止它的跳转行为
    window.open(event.target.href, '_blank');
  }
}
onMounted(() => {
  init();
  watch(() => props.text, () => {
    init();
  });
});
</script>
de1ck commented 2 years ago

我这边改进了一下代码,加了个线程锁,避免覆盖问题

<template>
  <div ref="viewerRef" @click.prevent="clickView" />
</template>
<script lang="ts" setup>
import {
  ref, onMounted, defineProps, withDefaults, watch,
} from 'vue';
import Vditor from 'vditor';
import 'vditor/dist/index.css';
import '@/style/vditor.scss';
import { VDITOR_CDN } from '@/utils/consts';

const props = withDefaults(defineProps<{
  mode?: 'dark' | 'light',
  text?: string;
  after?(): void;
}>(), {
  mode: 'light',
});

const viewerRef = ref();
// 是否开始初始化标志
let initial = false;
// 是否有后续内容改动标志  防止一个事件环节内重复preview => 导致后面的数据被前面的覆盖
let atferPool = false;
function init() {
  // 在初始化中,设置标志
  if (initial) {
    atferPool = true;
    return;
  }
  // 开始初始化
  initial = true;
  const previewElement = viewerRef.value;
  Vditor.preview(previewElement!, props.text ?? '', {
    mode: 'light',
    cdn: VDITOR_CDN,
    theme: {
      current: 'light',
      path: `${VDITOR_CDN}/dist/css/content-theme`,
    },
    // markdown: {
    //   toc: true,
    //   listStyle: true,
    // },
    // anchor: 1,
    async after() {
      await props.after?.();
      // 初始化结束
      initial = false;
      // 发现有后续改动,重新初始化
      if (atferPool) {
        atferPool = false;
        init();
      }
    },
  });
}

function clickView(event) {
  if (event.target?.tagName === 'A' && event.target?.href) {
    // 对捕获到的 a 标签进行处理,需要先禁止它的跳转行为
    window.open(event.target.href, '_blank');
  }
}
onMounted(() => {
  init();
  watch(() => props.text, () => {
    init();
  });
});
</script>
Vanessa219 commented 2 years ago

previewRender 方法本身支持 await 的,没太明白。如果是代码修改的话,欢迎直接 PR。

de1ck commented 2 years ago

previewRender 方法本身支持 await 的,没太明白。如果是代码修改的话,欢迎直接 PR。

当连续异步更新数据后,然后多次连续调用previewRender,可能会导致后面的数据,被前面的数据覆盖。

比如:初始数据为空,调用Vditor.preview,然后异步更新数据 'abc', 接着调用Vditor.preview, 最后渲染的数据还是空

单纯使用 await 不能避免这种问题。需要代码里面规避这种情况。 image

image

<template>
  <div>
    <div ref="viewerRef"></div>
    <el-input  v-model="text"></el-input>
  </div>
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue';
import Vditor from 'vditor';
import { VDITOR_CDN } from '@/utils/consts';

// const draftContent = localStorage.getItem('vditor-test');
const text = ref('');
const viewerRef = ref();
function init() {
  const previewElement = viewerRef.value;
  const v = text.value;
  Vditor.preview(previewElement!, text.value ?? '', {
    mode: 'light',
    cdn: VDITOR_CDN,
    theme: {
      current: 'light',
      path: `${VDITOR_CDN}/dist/css/content-theme`,
    },
    // markdown: {
    //   toc: true,
    //   listStyle: true,
    // },
    // anchor: 1,
    async after() {
      console.log('after', v);
    },
  });
}

onMounted(() => {
  init();
  setTimeout(() => {
    text.value = '123';
  });
});
watch(text, () => {
  init();
});
</script>
Vanessa219 commented 2 years ago

外部多次调用的时候需要进行 await