imzbf / md-editor-v3

Markdown editor for vue3, developed in jsx and typescript, dark theme、beautify content by prettier、render articles directly、paste or clip the picture and upload it...
https://imzbf.github.io/md-editor-v3
MIT License
1.69k stars 162 forks source link

关于IntersectionObserver在含代码块的md数据中无法生效的问题 #711

Closed Lirous587 closed 3 weeks ago

Lirous587 commented 3 weeks ago

关于IntersectionObserver在含代码块的md数据中无法生效的问题

<template>
  <div>
    <!-- <MdEditor
      editorId="editorId-edit"
      :theme="themeStore.theme"
      codeTheme="atom"
      v-model="content"
      previewTheme="smart-blue"
      v-if="ifEdit"
      ref="mdEditorRef"
    ></MdEditor> -->
    <MdPreview
      editorId="editorId-preview"
      v-model="text"
      :mdHeadingId="mdHeadingId"
      :theme="themeStore.theme"
      codeTheme="stackoverflow"
      previewTheme="smart-blue"
    />
  </div>
</template>

<script setup>
import { handleUploadImage, disposeMdAnchor } from "./md";
import { MdEditor } from "md-editor-v3";
import "md-editor-v3/lib/style.css";
import { MdPreview } from "md-editor-v3";
import { useMyThemeStore } from "~/store/theme";

const props = defineProps({
  height: {
    type: String,
    required: false,
    default: "700px",
  },
  mode: {
    type: String,
    required: false,
    default: "read",
  },
});

const content = defineModel("content", {
  type: String,
  required: true,
});

const ifEdit = computed(() => {
  return props.mode == "edit" ? true : false;
});

const themeStore = useMyThemeStore();

const anchorIdList = ref([]);

const mdHeadingId = (text, _level, index) => {
  const anchorId = `${index}`;
  anchorIdList.value.push(anchorId);
  return anchorId;
};

const anchors = ref([]);
const hList = ref([]);

let data = {};

const text = ref(`
# 标签1
//  \`\`\`js
//  console.log("去掉这三行的//IntersectionObserver就无法生效了")
//   \`\`\`
# 标签2

# 标签3

# 标签4

# 标签5

# 标签6

# 标签7

# 标签8

# 标签9

# 标签10
`);

onMounted(() => {
  if (!ifEdit.value) {
    data = disposeMdAnchor(anchorIdList.value);
    anchors.value = data.anchors.value;
    hList.value = data.hList;
  }
});

defineExpose({
  anchors,
});
</script>
export const disposeMdAnchor = (anchorIdList) => {
  const hList = anchorIdList.map((id) => document.getElementById(id));

  const anchors = ref(
    Array.from(hList).map((el, index) => ({
      id: anchorIdList[index],
      title: el.innerText,
      active: false,
    }))
  );

  const myObserver = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        const { id } = entry.target;
        if (entry.isIntersecting) {
          anchors.value.forEach((anchor) => {
            anchor.active = false;
          });
          anchors.value.find((item) => item.id === id).active = true;
        }
      });
    },
    {
      rootMargin: "0px 0px -99% 0px",
    }
  );

  hList.forEach((el, index) => {
    el.id = anchorIdList[index];
    myObserver.observe(el);
  });

  return {
    anchors,
  };
};

代码如上 遇到的问题就是有代码块的时候无法正常使用IntersectionObserver 其他情况却可以

版本号

edge最新版 nuxt3

问题重现链接

No response

imzbf commented 3 weeks ago

有多次编译的过程,没有代码块的时候,highlight库加载完成重新编译后的内容是一样的,所以各个标题都还是之前的节点,但是有代码块后,二次编译的内容不一样了,导致你监听的节点成了过时的节点。两个方法:

  1. 参考 文档,不适用CDN
  2. 通过 onHtmlChanged 事件去添加监听
Lirous587 commented 3 weeks ago

解决了 谢谢佬佬 下面是具体实现方式 以便以后有人遇到类似问题

// 要注意调用await nextTick(); 否则还是不会生效的
const onHtmlChanged = async () => {
  await nextTick();
  observerHList();
};
<template>
  <div>
    <MdPreview
      v-model="content"
      editorId="editorId-preview"
      :mdHeadingId="mdHeadingId"
      :theme="themeStore.theme"
      previewTheme="smart-blue"
      @onHtmlChanged="onHtmlChanged"
    />
  </div>
</template>

<script setup>
import { MdPreview } from "md-editor-v3";
import "md-editor-v3/lib/preview.css";
import { useMyThemeStore } from "~/store/theme";

const content = defineModel("content", {
  type: String,
  required: true,
});

const themeStore = useMyThemeStore();

const anchorIdList = ref([]);

const mdHeadingId = (text, level, index) => {
  const anchorId = `${index}`;
  anchorIdList.value.push(anchorId);
  return anchorId;
};

const anchors = ref([]);

const mdInit = () => {
  anchors.value = [];
  const hList = anchorIdList.value.map((id) => document.getElementById(id));
  anchors.value = Array.from(hList).map((el, index) => ({
    id: anchorIdList.value[index],
    title: el.innerText,
    active: false,
  }));
};

const observerHList = () => {
  const hList = document.body.querySelectorAll("h1, h2, h3, h4, h5, h6");

  const myObserver = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        const { id } = entry.target;
        if (entry.isIntersecting) {
          anchors.value.forEach((anchor) => {
            anchor.active = false;
          });
          anchors.value.find((item) => item.id === id).active = true;
        }
      });
    },
    {
      rootMargin: "0px 0px -99% 0px",
    }
  );

  hList.forEach((el, index) => {
    myObserver.observe(el);
  });
};

const onHtmlChanged = async () => {
  await nextTick();
  observerHList();
};

onMounted(() => {
  mdInit();
})

defineExpose({
  anchors,
});
</script>