likes
comments
collection
share

基于 Wangeditor 4 开发一个格式刷功能

作者站长头像
站长
· 阅读数 35

关于Wangeditor Wangeditor提供了扩展菜单的项的功能,使用方法 参考这里

格式刷实现

引入 BtnMenu

const { BtnMenu } = E;

自定义格式刷的样式内容

const $elem = E.$(
      `<div class="w-e-menu" data-title="${'格式刷'}"><span role="img" aria-label="format-painter" class="anticon anticon-format-painter"><svg viewBox="64 64 896 896" focusable="false" data-icon="format-painter" width="1em" height="1em" fill="currentColor" aria-hidden="true"><defs><style></style></defs><path d="M840 192h-56v-72c0-13.3-10.7-24-24-24H168c-13.3 0-24 10.7-24 24v272c0 13.3 10.7 24 24 24h592c13.3 0 24-10.7 24-24V256h32v200H465c-22.1 0-40 17.9-40 40v136h-44c-4.4 0-8 3.6-8 8v228c0 1.1.2 2.2.6 3.1-.4 1.6-.6 3.2-.6 4.9 0 46.4 37.6 84 84 84s84-37.6 84-84c0-1.7-.2-3.3-.6-4.9.4-1 .6-2 .6-3.1V640c0-4.4-3.6-8-8-8h-44V520h351c22.1 0 40-17.9 40-40V232c0-22.1-17.9-40-40-40z"></path></svg></span></div>`
    );
    

构造函数中监听鼠标事件

 editor.$textElem.on('mouseup', () => {
      // 如果格式刷功能处于激活状态
      if (me._active) {
        // 延迟执行,避免获取不到正确的元素
        setTimeout(() => {
          // 复制格式刷样式
          pasteStyle(editor);
          // 取消格式刷激活样式
          me.unActive();
        }, 100);
      }
    });

定义菜单点击事件

 // 菜单点击事件
  clickHandler() {
    const me = this;
    const editor = me.editor;
    if (me._active) {
      // 已经在激活状态时取消激活
      me.unActive();
      // 清空格式刷样式数据
      editor.copyStyleList = [];
    } else {
      // 没有选中则终端
      if (editor.selection.isSelectionEmpty()) return;
      // 激活按钮
      me.active();
      // 获取格式刷样式
      const domToParse = editor.selection.getSelectionContainerElem()?.elems[0];
      const copyStyleList = parseDom(domToParse);
      // 保存格式刷样式
      editor.copyStyleList = copyStyleList || [];
    }
  }

解析选中的DOM

获取目标dom,也就是当前选中模块的最小dom,需要一层层递归进行获取 在获取到对应的dom元素之后,执行getAllStyle方法,生成节点以及对应的属性,为之后进行数据填充做准备 getAllStyle方法负责向上递归获取最小dom的父级节点,在处理父级节点的时候需要额外处理 p、div、td、th标签

function parseDom(dom: HTMLElement | undefined) {
  if (!dom) return;
  let targetDom: Element | null = null;
  const nodeArray: {
    tagName: any;
    attributes: { [index: number]: Attr }[];
  }[] = [];
  getTargetDom(dom);

  targetDom && getAllStyle(targetDom);

  function getTargetDom(dom: HTMLElement) {
    if (dom.children.length <= 0) {
      targetDom = dom;
      return;
    }
    for (let index = 0; index < dom.children.length; index++) {
      if (dom.children[index] && dom.children[index].children.length > 0) {
        getTargetDom(dom.children[index] as HTMLElement);
      } else {
        if (dom.children[index].childNodes?.length > 0) {
          for (const i of dom.children[index].childNodes) {
            if (i.nodeType === 3 && i.nodeValue && i.nodeValue.trim() !== '') {
              targetDom = dom.children[index];
              return;
            }
          }
        }
      }
      if (!!targetDom) {
        return;
      }
      if (index + 1 >= dom.children.length) {
        targetDom = dom;
        return;
      } else {
        getTargetDom(dom.children[index + 1] as HTMLElement);
      }
    }
  }

  function getAllStyle(dom: HTMLElement) {
    if (!dom) return;
    const tagName = dom?.tagName?.toLowerCase();

    if (tagName === 'p') {
      nodeArray.push({
        tagName: 'span',
        attributes: Array.from(dom?.attributes || []).map(i => {
          return {
            name: i.name,
            value: outputAttr(i, tagName),
          };
        }),
      });
      return;
    } else if (tagName === 'div') {
      nodeArray.push({
        tagName: 'div',
        attributes: Array.from(dom?.attributes || []).map(i => {
          return {
            name: i.name,
            value: outputAttr(i, tagName),
          };
        }),
      });
      return;
    } else if (tagName === 'td' || tagName === 'th') {
      /** 这两个元素拦截了表格元素的向上递归 */
      const arrayLikeArr = Array.from(dom?.attributes || []);
      const hasStyleAttr = arrayLikeArr.some(item => item.name === 'style');
      const newAttrList = hasStyleAttr ? arrayLikeArr : arrayLikeArr.concat([{ name: 'style', value: '' }]);
      nodeArray.push({
        tagName: 'span',
        attributes: newAttrList.map(i => {
          return {
            name: i.name,
            value: outputAttr(i, tagName),
          };
        }),
      });

      return;
    } else {
      nodeArray.push({
        tagName: tagName,
        attributes: Array.from(dom?.attributes || []).map(i => {
          return {
            name: i.name,
            value: outputAttr(i, tagName),
          };
        }),
      });
      getAllStyle(dom?.parentNode as HTMLElement);
    }
  }
  • 样式内容会进行处理
  • 如果标签是td\tr\table\tbody\th,则不需要边框,背景色,margin,padding,
  • 其它的标签不需要 边框、背景色
  function outputAttr(i: Attr, tagName: string) {
    const isStyle = i.name === 'style';
    const hasOther =
      tagName === 'td' || tagName === 'tr' || tagName === 'table' || tagName === 'tbody' || tagName === 'th';
    const paddingAndMargin = ';padding:0;margin:0;';
    const normalStyle = ';border:0;background-color:transparent;';
    if (isStyle && hasOther) {
      return i.value + normalStyle + paddingAndMargin;
    }
    if (isStyle && !hasOther) {
      return i.value + normalStyle;
    }
    return i.value;
  }
  return nodeArray;
}

添加样式方法

传入对应的选中的文本内容,将获取到的父级节点,动态生成,之后将选中的文本内容作为文本元素放进去,以此来展示

function addStyle(text: any, nodeArray: TypeCopyStyleList) {
  let currentNode: any = null;
  // 遍历元素节点,动态生成节点,将获取的文本节点,作为内容,进行样式的层级覆盖
  nodeArray.forEach((ele, index) => {
    const node = document.createElement(ele.tagName);
    for (const attr of ele.attributes) {
      node.setAttribute(attr.name, attr.value);
    }
    if (index === 0) {
      node.innerText = text;
      currentNode = node;
    } else {
      node.appendChild(currentNode);
      currentNode = node;
    }
  });

  return currentNode;
}

粘贴

  • 获取格式刷保存的样式
  • 有样式说明格式刷被激活
  • 获取当前选中内容
  • 如果没选中也会执行,再次使用需要重新激活格式刷功能
  • 清空格式刷样式
function pasteStyle(editor: E) {
  const copyStyleList = editor.copyStyleList;
  if (copyStyleList) {
    const text = editor.selection.getSelectionText();
    const targetDom = addStyle(text, copyStyleList);
    if (targetDom) {
      editor.cmd.do('insertHTML', targetDom.outerHTML);
    }
    editor.copyStyleList = null;
  }
}

格式刷功能注册到富文本组件中

  • 注册菜单
   const menuKey = 'formatBrushMenu';
  // 注册进入menus
    editorRef.current.menus.extend(menuKey, FormatBrushMenu);
    // 添加到菜单栏
    editorRef.current.config.menus.push(menuKey);

多语言

按照文档说明如果需要多语言需要i18next模块,通过config.languages模块注入对应的文案内容

转载自:https://juejin.cn/post/7244487194174275639
评论
请登录