quill-mention
Version:
@mentions for the Quill rich text editor
132 lines (110 loc) • 3.45 kB
text/typescript
import Quill from "quill";
import type { ScrollBlot, EmbedBlot } from "parchment";
const Embed = Quill.import("blots/embed") as typeof EmbedBlot;
class MentionEvent extends Event {
public value: object;
public event: Event;
constructor(name: string, options: EventInit) {
super(name, options);
this.value = {};
this.event = new Event(name);
}
}
interface MentionBlotData {
value: string;
denotationChar: string;
}
function isMentionBlotData(data: unknown): data is MentionBlotData {
return (
typeof data === "object" &&
data !== null &&
"value" in data &&
typeof data.value === "string" &&
"denotationChar" in data &&
typeof data.denotationChar === "string"
);
}
class MentionBlot extends Embed {
static blotName = "mention";
static tagName = "span";
static className = "mention";
private hoverHandler?: EventListener;
private clickHandler?: EventListener;
private mounted: boolean;
constructor(scroll: ScrollBlot, node: Node) {
super(scroll, node);
this.mounted = false;
}
static render?(data: MentionBlotData): HTMLElement;
static create(data?: MentionBlotData) {
const node = super.create();
if (!isMentionBlotData(data) || node instanceof HTMLElement === false) {
return node;
}
const denotationChar = document.createElement("span");
denotationChar.className = "ql-mention-denotation-char";
denotationChar.innerText = data.denotationChar;
node.appendChild(denotationChar);
if (typeof this.render === "function") {
node.appendChild(this.render(data));
} else {
const mentionValue = document.createElement("span");
mentionValue.className = "ql-mention-value";
mentionValue.innerText = data.value;
node.appendChild(mentionValue);
}
return MentionBlot.setDataValues(node, data);
}
static setDataValues(element: HTMLElement, data: MentionBlotData) {
const domNode = element;
Object.keys(data).forEach((key) => {
domNode.dataset[key] = data[key as keyof MentionBlotData];
});
return domNode;
}
static value(domNode: HTMLElement): any {
return domNode.dataset;
}
attach() {
super.attach();
if (!this.mounted) {
this.mounted = true;
this.clickHandler = this.getClickHandler();
this.hoverHandler = this.getHoverHandler();
this.domNode.addEventListener("click", this.clickHandler, false);
this.domNode.addEventListener("mouseenter", this.hoverHandler, false);
}
}
detach() {
super.detach();
this.mounted = false;
if (this.clickHandler) {
this.domNode.removeEventListener("click", this.clickHandler);
this.clickHandler = undefined;
}
}
getClickHandler(): EventListener {
return (e) => {
const event = this.buildEvent("mention-clicked", e);
window.dispatchEvent(event);
e.preventDefault();
};
}
getHoverHandler(): EventListener {
return (e) => {
const event = this.buildEvent("mention-hovered", e);
window.dispatchEvent(event);
e.preventDefault();
};
}
private buildEvent(name: string, e: Event): MentionEvent {
const event = new MentionEvent(name, {
bubbles: true,
cancelable: true,
});
event.value = Object.assign({}, (this.domNode as HTMLElement).dataset);
event.event = e;
return event;
}
}
export { MentionBlot, MentionBlotData, isMentionBlotData, MentionEvent };