UNPKG

@cairn214/fluent-editor

Version:

A rich text editor based on Quill 2.0, which extends rich modules and formats on the basis of Quill. It's powerful and out-of-the-box.

326 lines (325 loc) 12.2 kB
"use strict"; Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } }); const Quill = require("quill"); const editor_utils = require("../config/editor.utils.cjs.js"); const constants = require("./constants.cjs.js"); const MentionLink = require("./MentionLink.cjs.js"); const Delta = Quill.import("delta"); const Parchment = Quill.import("parchment"); const { Scope } = Parchment; class Mention { // @ts-ignore constructor(quill, options) { this.quill = quill; this.activeMentionIndex = 0; this.searchTerm = ""; this.needInsertBr = true; this.defaultOptions = { defaultLink: "#", target: "_blank", mentionChar: constants.DEFAULT_MENTION_CHAR, maxHeight: 200, renderMentionItem(data) { let mentionItem = data.name || data.id; if (this.itemKey) { mentionItem = data[this.itemKey]; } const dom = document.createElement("SPAN"); dom.textContent = mentionItem; return dom; }, renderMentionText(data) { let mentionText = data.name || data.id; if (this.itemKey) { mentionText = data[this.itemKey]; } return `${mentionText}`; }, containerClass: "ql-mention-list-container", listClass: "ql-mention-list", listHideClass: "ql-mention-list--hide", itemClass: "ql-mention-item", itemActiveClass: "ql-mention-item--active", itemKey: "name", searchKey: "name", // dataAttributes: ['id'], select(_data) { }, remove(_data) { } }; this.handleTextChange = (_delta, _oldDelta, source) => { setTimeout(() => { if (Quill.sources.USER === source) { const range = this.quill.getSelection(); if (!range) { return; } const caretPos = this.latestCaretPos = range.index; const content = this.quill.getContents(); const beforeCaretText = content.reduce((newText, op) => { if (typeof op.insert === "string") { return newText += op.insert; } else { return newText += " "; } }, ""); const mentionCharPos = beforeCaretText.lastIndexOf(this.options.mentionChar); if (mentionCharPos > -1) { const searchTerm = beforeCaretText.substring(mentionCharPos + this.options.mentionChar.length, caretPos); this.searchTerm = searchTerm; if (!"".startsWith.call(searchTerm, " ")) { this.latestMentionCharPos = mentionCharPos; this.searchMentionListByTerm(searchTerm); } else { this.hideMentionList(); } } else { this.hideMentionList(); } } }); }; this.handleArrowUpKey = () => { if (this.isOpen()) { this.activeMentionIndex = (this.activeMentionIndex + this.latestMentionList.length - 1) % this.latestMentionList.length; this.highlightMentionItem(this.activeMentionIndex); return false; } return true; }; this.handleArrowDownKey = () => { if (this.isOpen()) { this.activeMentionIndex = (this.activeMentionIndex + 1) % this.latestMentionList.length; this.highlightMentionItem(this.activeMentionIndex); return false; } return true; }; this.handleEnterKey = () => { if (this.isOpen()) { this.selectMentionItem(); this.needInsertBr = false; } return true; }; this.handleEscapeKey = () => { if (this.isOpen()) { this.hideMentionList(); return false; } return true; }; if (!options.search) { console.warn("please provide a search function!"); return; } this.options = Object.assign(this.defaultOptions, options); const container = document.createElement("div"); container.classList.add("ql-mention-list-container"); if (this.options.containerClass !== "ql-mention-list-container") { container.classList.add(this.options.containerClass); } this.mentionListEL = document.createElement("ul"); this.mentionListEL.classList.add(this.options.listClass, this.options.listHideClass); this.mentionListEL.style.cssText += ` max-height: ${this.options.maxHeight}px; `; quill.on(Quill.events.TEXT_CHANGE, this.handleTextChange); quill.keyboard.addBinding({ key: "ArrowUp" }, this.handleArrowUpKey); quill.keyboard.addBinding({ key: "ArrowDown" }, this.handleArrowDownKey); quill.keyboard.addBinding({ key: "Enter" }, this.handleEnterKey); quill.keyboard.addBinding({ key: "Tab" }, this.handleEnterKey); quill.keyboard.addBinding({ key: "Escape" }, this.handleEscapeKey); quill.keyboard.bindings.Enter.unshift(quill.keyboard.bindings.Enter.pop()); quill.keyboard.bindings.Tab.unshift(quill.keyboard.bindings.Tab.pop()); quill.keyboard.bindings.Escape.unshift(quill.keyboard.bindings.Escape.pop()); const customKeyboardEnter = { key: "Enter", shiftKey: null, handler: (range, context) => { const lineFormats = Object.keys(context.format).reduce( (formats, format) => { if (this.quill.scroll.query(format, Scope.BLOCK) && !Array.isArray(context.format[format])) { formats[format] = context.format[format]; } return formats; }, {} ); let selectionIndex = range.index - this.searchTerm.length; let delta = new Delta().retain(range.index).delete(range.length); if (this.needInsertBr) { delta = delta.insert("\n", lineFormats); selectionIndex = range.index + 1; } this.quill.updateContents(delta, Quill.sources.USER); this.quill.setSelection(selectionIndex, Quill.sources.SILENT); this.quill.focus(); Object.keys(context.format).forEach((name) => { if (!editor_utils.isNullOrUndefined(lineFormats[name])) return; if (Array.isArray(context.format[name])) return; if (name === "code" || name === "link") return; this.quill.format(name, context.format[name], Quill.sources.USER); }); this.needInsertBr = true; } }; quill.keyboard.bindings.Enter = quill.keyboard.bindings.Enter.map((item) => { const buildinKeyboardEnter = item.format === void 0 && item.shiftKey === null; if (buildinKeyboardEnter) { return customKeyboardEnter; } else { return item; } }); this.on("click", this.handleMouseClick); this.on("mouseover", this.handleMouseEnter); quill.emitter.on(constants.ON_MENTION_LINK_REMOVE, async ({ mention, name }) => { const [result] = mention && [mention] || await this.options.search(name); this.options.remove(result); }); container.appendChild(this.mentionListEL); quill.container.parentElement.insertBefore(container, quill.container); } static register() { Quill.register(MentionLink.default); } on(eventName, callback) { this.mentionListEL.addEventListener(eventName, (evt) => { let target = evt.target; let targetItemEL; while (this.mentionListEL.contains(target) && target !== this.mentionListEL) { if (target.classList.contains(this.options.itemClass)) { targetItemEL = target; } target = target.parentElement; } if (targetItemEL) { callback.call(this, targetItemEL, this.getMentionItemIndex(targetItemEL)); } }); } getMentionItemIndex(itemEl) { return [].reduce.call(this.mentionListEL.children, (index, item, idx) => item === itemEl ? idx : index, -1); } handleMouseClick(_itemEl, index) { this.selectMentionItem(index, true); this.quill.focus(); } handleMouseEnter(_itemEl, index) { this.activeMentionIndex = index; this.highlightMentionItem(index); } getActiveMentionItem() { return this.mentionListEL.querySelector(`.${this.options.itemActiveClass}`); } isOpen() { return !this.mentionListEL.classList.contains(this.options.listHideClass); } async searchMentionListByTerm(term) { const mentionList = await this.options.search(term); this.latestMentionList = mentionList; if (!mentionList || mentionList.length === 0) { return this.hideMentionList(); } this.showMentionList(mentionList); } showMentionList(mentionList) { if (!this.isOpen()) { this.mentionListEL.classList.remove(this.options.listHideClass); } this.activeMentionIndex = 0; this.setMentionListPos(); this.render(mentionList); } hideMentionList() { if (this.isOpen()) { this.activeMentionIndex = 0; this.mentionListEL.classList.add(this.options.listHideClass); } } setMentionListPos() { const cursorIndex = this.quill.selection.savedRange.index; const cursorBounds = this.quill.getBounds(cursorIndex); const { left, top } = cursorBounds; const container = this.quill.container; const hostElement = container.parentNode; const { left: editorLeft, top: editorTop } = container.getBoundingClientRect(); const { left: hostElementLeft, top: hostElementTop } = hostElement.getBoundingClientRect(); const relativeLeft = editorLeft - hostElementLeft; const relativeTop = editorTop - hostElementTop; const menuLeft = left + relativeLeft - 5; const menuTop = top + relativeTop + 20; this.mentionListEL.style.cssText += ` left: ${menuLeft}px; top: ${menuTop}px; `; } render(mentionList) { const wrapEl = document.createElement("div"); [].forEach.call(mentionList, (mentionItem, index) => { const mentionItemEl = document.createElement("li"); mentionItemEl.classList.add(this.options.itemClass); if (index === this.activeMentionIndex) { mentionItemEl.classList.add(this.options.itemActiveClass); } const renderResult = this.options.renderMentionItem(mentionItem); if (typeof renderResult === "string") { mentionItemEl.insertAdjacentHTML("afterbegin", renderResult); } else { mentionItemEl.insertAdjacentElement("afterbegin", renderResult); } wrapEl.appendChild(mentionItemEl); }); this.mentionListEL.innerHTML = wrapEl.innerHTML; } highlightMentionItem(index) { const oldActiveItem = this.getActiveMentionItem(); if (oldActiveItem) { oldActiveItem.classList.remove(this.options.itemActiveClass); } const newActiveItem = this.mentionListEL.querySelector(`.${this.options.itemClass}:nth-of-type(${index + 1})`); if (newActiveItem) { newActiveItem.classList.add(this.options.itemActiveClass); this.scrollIntoView(newActiveItem); } } scrollIntoView(node) { const nodeAsAny = node; if (nodeAsAny.scrollIntoViewIfNeeded) { nodeAsAny.scrollIntoViewIfNeeded(false); return; } if (node.scrollIntoView) { node.scrollIntoView(false); } } selectMentionItem(index = this.activeMentionIndex, isClick) { const activeMentionItem = this.latestMentionList[index]; this.insertMentionBlot(activeMentionItem, isClick); this.options.select(activeMentionItem); this.hideMentionList(); } insertMentionBlot(activeMentionItem, isClick) { const mention = this.options.renderMentionText(activeMentionItem); const delta = new Delta().retain(this.latestMentionCharPos).delete(this.latestCaretPos - this.latestMentionCharPos).insert({ [MentionLink.default.blotName]: { char: this.options.mentionChar, text: mention, mention: activeMentionItem, link: activeMentionItem.link || this.options.defaultLink, target: activeMentionItem.target || this.options.target, searchKey: this.options.searchKey } }); if (isClick) { this.quill.updateContents(delta, Quill.sources.USER); } else { this.quill.updateContents(delta, Quill.sources.API); } this.quill.setSelection(this.latestMentionCharPos + 1, Quill.sources.API); } } exports.default = Mention; //# sourceMappingURL=Mention.cjs.js.map