quilljs copyCode 模块 - 无法在 'Node' 上执行 'insertBefore'
quilljs copyCode module - Failed to execute 'insertBefore' on 'Node'
我正在为 QuillJs 创建一个 copyCode 插件。插件似乎一切正常,但是,当在 text 和 code-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();
}
}
这里是 StackBlitz:https://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
我正在为 QuillJs 创建一个 copyCode 插件。插件似乎一切正常,但是,当在 text 和 code-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();
}
}
这里是 StackBlitz:https://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