wangeditor自己实现关键字高亮

工作中用到了这个编辑器,本以为关键字高亮这个如此高频的功能它肯定是实现了的,结果竟然没有!!网上找了一圈也没有,好家伙我排期也没排这块时间啊,所以只能自己加班写一个了。

方案简析

wangeditor底层其实使用的是Slate这个编辑器,你要自己实现此功能,本质上就是调用Slate对应的方法。

主要使用了以下几个大类SlateEditor、SlateTransforms

SlateEditor

调用SlateEditor.nodes获取到所有的文本节点

    // 使用 Editor.nodes 方法遍历所有节点
    const textNodes = Array.from(
      SlateEditor.nodes(editorRef, {
        at: [],
        match: matchText,
      })
    );
SlateTransforms

调用SlateTransforms.setNodes 传入参数首位坐标让指定关键字单独形成一个文本节点,从而实现高亮

    // au 为首尾坐标
 SlateTransforms.setNodes(
    editorRef,
    {
      //@ts-ignore
      color,
    },
    {
      at: au,
      split: true,
      match: (value) => {
        //@ts-ignore
        return isPlainObject(value) && typeof value?.text === "string";
      },
    }
  );

调用SlateTransforms.mergeNodes 传入指定节点让其与其他节点合并,用于关键字被删除后取消高亮

SlateTransforms.mergeNodes(editorRef, {
    at: au.anchor.path,
  });

完整代码

直接上代码,也不难,主要是大家对slate的api不熟悉,相信大家一看就懂,
注意一下findTemplatePositions方法,为判断文字是否高亮的条件,大家按需修改即可

<script setup lang="ts">
import { onBeforeUnmount, ref, shallowRef, onMounted } from "vue";
import { SlateEditor, SlateTransforms } from "@wangeditor/editor";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";

// 编辑器实例,必须用 shallowRef,重要!
const editorRef = shallowRef();

// 内容 HTML
const valueHtml = ref("<p>hello</p>");

function isObject(value) {
  return value !== null && typeof value === "object" && !Array.isArray(value);
}
function isPlainObject(o) {
  let ctor, prot;

  if (isObject(o) === false) return false;

  // If has modified constructor
  // eslint-disable-next-line prefer-const
  ctor = o.constructor;
  if (ctor === undefined) return true;

  // If has modified prototype
  // eslint-disable-next-line prefer-const
  prot = ctor.prototype;
  if (isObject(prot) === false) return false;

  // If constructor does not have an Object-specific method
  if (prot.hasOwnProperty("isPrototypeOf") === false) {
    return false;
  }

  // Most likely a plain Object
  return true;
}
function matchText(value) {
  return isPlainObject(value) && typeof value.text === "string";
}

function findTemplatePositions(str) {
  const regex = /{([^}]*)}/g; // 创建正则表达式,匹配以{开头并以}结尾的内容
  let match;
  const positions: any = [];

  while ((match = regex.exec(str)) !== null) {
    const startIndex = match.index;
    const endIndex = startIndex + match[0].length;
    positions.push({ startIndex, endIndex, content: match[0] });
  }

  return positions;
}

function mergeNodes(au, editorRef) {
  SlateTransforms.setNodes(
    editorRef,
    {
      //@ts-ignore
      color: "",
    },
    {
      at: au,
      split: false,
      match: matchText,
    }
  );
  SlateTransforms.mergeNodes(editorRef, {
    at: au.anchor.path,
  });
}
function setNodes(au, editorRef, color) {
  SlateTransforms.setNodes(
    editorRef,
    {
      //@ts-ignore
      color,
    },
    {
      at: au,
      split: true,
      match: (value) => {
        //@ts-ignore
        return isPlainObject(value) && typeof value?.text === "string";
      },
    }
  );
}

// 模拟 ajax 异步获取内容
onMounted(() => {});

// 编辑器配置
const editorConfig = {
  placeholder: "请输入内容...",
  MENU_CONF: {
    /* 菜单配置,下文解释 */
  },
};

function parseTTSContentWithJsonPathToHtml(editorRef, color) {
  // setTimeout(() => {
  try {
    // 使用 Editor.nodes 方法遍历所有节点
    const textNodes = Array.from(
      SlateEditor.nodes(editorRef, {
        at: [],
        match: matchText,
      })
    );

    // 打印所有文本节点
    textNodes.forEach(([node, path]: any) => {
      const arr = findTemplatePositions(node.text);
      if (node.color === color) {
        if (
          !arr.length ||
          arr[0].startIndex !== 0 ||
          arr[0].endIndex !== node.text.length
        ) {
          const au = {
            anchor: {
              path: path,
              offset: 0,
            },
            focus: {
              path: path,
              offset: node.text.length,
            },
          };
          mergeNodes(au, editorRef);
        }
      } else {
        if (arr.length) {
          arr.forEach((item) => {
            const au = {
              anchor: {
                path: path,
                offset: item.startIndex,
              },
              focus: {
                path: path,
                offset: item.endIndex,
              },
            };
            setNodes(au, editorRef, color);
          });
        }
      }
    });
  } catch (error) {}
  // }, 100)
}

function handChange() {
  parseTTSContentWithJsonPathToHtml(editorRef.value, "#FAAD14");
}

const handleCreated = (editor) => {
  editorRef.value = editor; // 记录 editor 实例,重要!
};

// 组件销毁时,及时销毁编辑器
onBeforeUnmount(() => {
  const editor = editorRef.value;
  if (editor == null) return;
  editor.destroy();
});
</script>

<template>
  <div style="border: 1px solid #ccc">
    <Toolbar
      :editor="editorRef"
      style="border-bottom: 1px solid #ccc"
    />

    <!-- 编辑器 -->
    <Editor
      v-model="valueHtml"
      :defaultConfig="editorConfig"
      style="height: 500px; overflow-y: hidden"
      @onCreated="handleCreated"
      @on-change="handChange"
    />
  </div>
</template>

<!-- 别忘了引入样式 -->
<style src="@wangeditor/editor/dist/css/style.css"></style>

posted @ 2025-03-24 15:27  爱吃巧克力的狗  阅读(211)  评论(0)    收藏  举报