quill-react-commercial
Version:
A Quill component for React and more maturely.
122 lines (109 loc) • 3.98 kB
text/typescript
// https://stackblitz.com/edit/typescript-hpk9vc?file=index.ts
// https://stackoverflow.com/questions/65501672/quilljs-copycode-module-failed-to-execute-insertbefore-on-node/65577686#65577686
import Quill from 'quill';
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 CodeCopy {
quill: Quill;
options: any;
container: HTMLElement;
unusedBadges: HTMLElement[] = [];
reference: {
[index: string]: {
parent: HTMLElement | null,
copyBadge: HTMLElement | null,
},
} = {};
constructor(quill: Quill, options: any) {
this.quill = quill;
this.options = options;
this.container = this.quill.addContainer('ql-badge-container');
(this.quill.root.parentNode as HTMLElement).style.position =
(this.quill.root.parentNode as HTMLElement).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 (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 as HTMLElement;
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/codeCopy', CodeCopy);