quilljs copyCode 模块 - 无法在 'Node' 上执行 'insertBefore'

quilljs copyCode module - Failed to execute 'insertBefore' on 'Node'

我正在为 QuillJs 创建一个 copyCode 插件。插件似乎一切正常,但是,当在 textcode-block 之间创建 space 时,您收到此错误:

Failed to execute 'insertBefore' on 'Node'

代码如下:

const copyContentIntoClipboard = (rawData: string) => {
  const encodedContent = encodeURIComponent(rawData);
  const filteredEncodedContent = encodedContent.replace(/%EF%BB%BF/g, "");
  const targetContent = decodeURIComponent(filteredEncodedContent);
  const tmpHolder = document.createElement("textarea");
  tmpHolder.value = targetContent;
  document.body.appendChild(tmpHolder);
  tmpHolder.select();
  document.execCommand("copy");
  document.body.removeChild(tmpHolder);
};

const CodeBlock = Quill.import("formats/code-block");

class CopyCode extends CodeBlock {
 copyBadge: HTMLDivElement | null;
  domNode: HTMLElement;
  container: HTMLElement | null;
  parent: HTMLElement;
  copyHandler: EventListener;

  _mountContainer() {
    const container = document.createElement("div");
    container.classList.add("ql-container");
    if (this.domNode.nextSibling) {
      this.domNode.parentElement?.insertBefore(
        container,
        this.domNode
      );
      container.appendChild(this.domNode); // <-- error starts here
      this.container = container;
    }
  }
  _dismountContainer() {

    if (this.container) {
      this.container.parentElement?.insertBefore(
        this.domNode,
        this.container.nextSibling
      );
      this.domNode.parentElement?.removeChild(this.container);
    }
    this.container = null;
  }

  _mountBadge() {
    const copyBadge = document.createElement("div");
    copyBadge.contentEditable = "false";
    copyBadge.classList.add("ql-badge", "ql-badge-copy");
    copyBadge.textContent = "copy";
    this.domNode.parentElement?.insertBefore(
      copyBadge,
      this.domNode.nextSibling
    );
    const copyHandler = (e: MouseEvent) => {
      e.stopPropagation();
      e.preventDefault();
      const target = e.target as HTMLElement;
      const codeArea = target.previousSibling;
      const copyCode = codeArea?.textContent?.trim() || '';
      if (!codeArea) {
        return;
      }
      copyBadge.textContent = "copied!";
      setTimeout(function() {
        copyBadge.textContent = "copy";
      }, 2000);
      copyContentIntoClipboard(copyCode);
    };
    copyBadge.addEventListener("click", copyHandler, true);
    this.copyHandler = copyHandler;
    this.copyBadge = copyBadge;
  }
  _dismountBadge() {
    const badgeIsInDom = this.domNode.parentElement?.contains(this.copyBadge);
    if (this.copyBadge && badgeIsInDom) {
      this.copyBadge.removeEventListener("click", this.copyHandler, true);
      this.copyBadge.parentElement?.removeChild(this.copyBadge);
    }
    this.copyBadge = null;
    this.copyHandler = () => {};
  }

  _mount() {
    this._mountContainer();
    this._mountBadge();
  }

  insertInto(...args: any) {
    super.insertInto(...args);
    const allowCustomMount = !this.copyBadge && !this.container && this.parent;
    if (allowCustomMount) {
      this._mount();
    }
  }
  remove() {
    this._dismountBadge();
    this._dismountContainer();
    super.remove();
  }
}

这里是 StackBlitzhttps://stackblitz.com/edit/typescript-ggvuuy?file=index.html

我认为错误是由于 QuillJS 认为代码块应该是 pre 块而不是 div 块包含一个 pre 块。但是,我不知道如何解决...

有什么想法吗?

您可以使用 Modules 来扩展 Quill

而不是扩展 formats/code-block
import hljs from "highlight.js";
import "highlight.js/styles/monokai-sublime.css";
import "./style.css";
import Quill from "quill";

hljs.configure({
  languages: ["javascript", "python"] 
});

const copyContentIntoClipboard = (rawData: string) => {
  const encodedContent = encodeURIComponent(rawData);
  const filteredEncodedContent = encodedContent.replace(/%EF%BB%BF/g, "");
  const targetContent = decodeURIComponent(filteredEncodedContent);
  const tmpHolder = document.createElement("textarea");
  tmpHolder.value = targetContent;
  document.body.appendChild(tmpHolder);
  tmpHolder.select();
  document.execCommand("copy");
  document.body.removeChild(tmpHolder);
};

class CopyCode {
  quill: any;
  options: any;
  container: HTMLElement;
  unusedBadges: HTMLElement[] = [];
  reference: { [index: string]: {
    parent : HTMLElement | null,
    copyBadge  : HTMLElement | null
  } } = {};

  constructor(quill: any, options: any) {
    this.quill = quill;
    this.options = options;
    this.container = this.quill.addContainer('ql-badge-container');
    this.quill.root.parentNode.style.position = this.quill.root.parentNode.style.position || 'relative';
    this.registerCodeBlock();
    this.quill.on('editor-change', () => {
      Object.values(this.reference).forEach((item) => {
        this.addCopyBadge(item);
        this.repositionCopyBadge(item);
      })
    });
  }

  registerCodeBlock = () => {
    const self = this;
    const CodeBlock = Quill.import("formats/code-block");
    let counter = 0;
    class CopyMode extends CodeBlock {
      domNode: HTMLElement;
      insertInto(...args: any) {
        super.insertInto(...args);

        const index = String(counter);
        const _node = this.domNode;
        _node.setAttribute('data-index', index);
        counter++;
        self.reference[index] = { parent : _node, copyBadge: null };
      }
      remove() {
        const index = this.domNode.getAttribute("data-index");
        if (self.reference[index] && self.reference[index]['copyBadge']) {
          const copyBadge = self.reference[index]['copyBadge'];
          copyBadge.style.display = 'none';
          self.unusedBadges.push(copyBadge);
        }
        delete self.reference[index];
        super.remove();
      }
    }
    Quill.register(CopyMode, true);
  }

  addCopyBadge = (obj: any) => {
    if (obj.copyBadge != null || obj.parent == null) {
      return;
    }

    const index = obj.parent.getAttribute('data-index');
    const copyBadge = this.unusedBadges.length ? this.unusedBadges.shift() : document.createElement("span");
    copyBadge.style.display = 'block';
    copyBadge.contentEditable = "false";
    copyBadge.classList.add("ql-badge", "ql-badge-copy");
    copyBadge.textContent = "copy";

    const copyHandler = (evt: MouseEvent) => {
      evt.stopPropagation();
      evt.preventDefault();
      const codeArea = obj.parent;
      const copyText = codeArea?.textContent?.trim() || '';
      if (!codeArea) {
        return;
      }
      copyBadge.textContent = "copied!";
      setTimeout(function() {
        copyBadge.textContent = "copy";
      }, 2000);
      copyContentIntoClipboard(copyText);
    };
    copyBadge.addEventListener("click", copyHandler, true);
    this.container.appendChild(copyBadge);
    this.reference[index]['copyBadge'] = copyBadge;
  }

  repositionCopyBadge(obj: any) {
    const parent: HTMLElement = this.quill.root.parentNode;
    const specRect = obj.parent.getBoundingClientRect();
    const parentRect = parent.getBoundingClientRect();

    Object.assign(obj.copyBadge.style, {
      right: `${specRect.left - parentRect.left - 1 + parent.scrollLeft + 4}px`,
      top: `${(specRect.top - parentRect.top + parent.scrollTop) + 3}px`,
    });
  }
}

Quill.register("modules/copy-code", CopyCode);

const quill = new Quill("#editor", {
  modules: {
    syntax: true,
    'copy-code': true,
    toolbar: {
      container: ["code-block"]
    }
  },
  theme: "snow"
});

在这里工作example