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.

249 lines (248 loc) 8.54 kB
"use strict"; Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } }); const Quill = require("quill"); const emojiList = require("../emoji-list.cjs.js"); const Module = Quill.imports["core/module"]; class ShortNameEmoji extends Module { constructor(quill, options) { super(quill, options); this.emojiList = options.emojiList; this.quill = quill; this.onClose = options.onClose; this.onOpen = options.onOpen; this.container = document.createElement("ul"); this.container.classList.add("emoji_completions"); this.quill.container.appendChild(this.container); this.container.style.position = "absolute"; this.container.style.display = "none"; this.onSelectionChange = this.maybeUnfocus.bind(this); this.onTextChange = this.update.bind(this); this.open = false; this.atIndex = null; this.focusedButton = null; this.isWhiteSpace = function(ch) { let whiteSpace = false; if (/\s/.test(ch)) { whiteSpace = true; } return whiteSpace; }; quill.keyboard.addBinding({ // TODO: Once Quill supports using event.key change this to ":" key: 186, // ":" instead of 190 in Safari. Since it's the same key it doesn't matter if we register both. shiftKey: true }, this.triggerPicker.bind(this)); quill.keyboard.addBinding({ // gecko based browsers (firefox) use 59 as the keycode for semicolon, // which makes a colon character when combined with shift key: 59, shiftKey: true }, this.triggerPicker.bind(this)); quill.keyboard.addBinding({ key: 39, // ArrowRight collapsed: true }, this.handleArrow.bind(this)); quill.keyboard.addBinding({ key: 40, // ArrowRight collapsed: true }, this.handleArrow.bind(this)); } triggerPicker(range, _context) { if (this.open) return true; if (range.length > 0) { this.quill.deleteText(range.index, range.length, Quill.sources.USER); } this.quill.insertText(range.index, ":", "emoji-shortname", Quill.sources.USER); const atSignBounds = this.quill.getBounds(range.index); this.quill.setSelection(range.index + 1, Quill.sources.SILENT); this.atIndex = range.index; const paletteMaxPos = atSignBounds.left + 250; if (paletteMaxPos > this.quill.container.offsetWidth) { this.container.style.left = `${atSignBounds.left - 250}px`; } else { this.container.style.left = `${atSignBounds.left}px`; } this.container.style.top = `${atSignBounds.top + atSignBounds.height}px`; this.open = true; this.quill.on("text-change", this.onTextChange); this.quill.once("selection-change", this.onSelectionChange); if (this.onOpen) { this.onOpen(); } } handleArrow() { if (!this.open) return true; this.buttons[0].classList.remove("emoji-active"); this.buttons[0].focus(); if (this.buttons.length > 1) { this.buttons[1].focus(); } } update(event) { const sel = this.quill.getSelection().index; if (this.atIndex >= sel) { return this.close(null); } this.query = this.quill.getText(this.atIndex + 1, sel - this.atIndex - 1); try { if (this.isWhiteSpace(this.query)) { this.close(null); return; } } catch (_e) { throw new Error("Close failed"); } this.query = this.query.trim(); let emojis = this.emojiList; emojis.sort((a, b) => { return a.emoji_order - b.emoji_order; }); if (this.query.length < this.options.fuse.minMatchCharLength || emojis.length === 0) { this.container.style.display = "none"; return; } if (emojis.length > 15) { emojis = emojis.slice(0, 15); } this.renderCompletions(emojis, event); } maybeUnfocus() { if (this.container.querySelector("*:focus")) return; this.close(null); } renderCompletions(emojis, evt) { try { if (evt) { if (evt.key === "Enter" || evt.keyCode === 13) { this.close(emojis[0], 1); this.container.style.display = "none"; return; } else if (evt.key === "Tab" || evt.keyCode === 9) { this.quill.disable(); this.buttons[0].classList.remove("emoji-active"); this.buttons[1].focus(); return; } } if (evt) return; } catch (_e) { throw new Error("Render failed"); } while (this.container.firstChild) { this.container.removeChild(this.container.firstChild); } const buttons = Array.from({ length: emojis.length }); this.buttons = buttons; const handler = (i, emoji) => (event) => { const arrowLeftKey = event.key === "ArrowLeft" || event.keyCode === 37; const arrowUpKey = event.key === "ArrowUp" || event.keyCode === 38; const arrowRightKey = event.key === "ArrowRight" || event.keyCode === 39; const arrowDownKey = event.key === "ArrowDown" || event.keyCode === 40; if (arrowRightKey || arrowDownKey) { event.preventDefault(); buttons[Math.min(buttons.length - 1, i + 1)].focus(); } else if (event.key === "Tab" || event.keyCode === 9) { event.preventDefault(); if (i + 1 === buttons.length) { buttons[0].focus(); return; } buttons[Math.min(buttons.length - 1, i + 1)].focus(); } else if (arrowLeftKey || arrowUpKey) { event.preventDefault(); buttons[Math.max(0, i - 1)].focus(); } else if (event.key === "Enter" || event.keyCode === 13 || event.key === " " || event.keyCode === 32 || event.key === "Tab" || event.keyCode === 9) { event.preventDefault(); this.quill.enable(); this.close(emoji); } }; emojis.forEach((emoji, i) => { const li = makeElement( "li", {}, makeElement( "button", { type: "button" }, makeElement("span", { className: `button-emoji ap ap-${emoji.name}`, innerHTML: emoji.code_decimal }), makeElement("span", { className: "unmatched" }, emoji.shortname) ) ); this.container.appendChild(li); buttons[i] = li.firstChild; buttons[i].addEventListener("keydown", handler(i, emoji)); buttons[i].addEventListener("mousedown", () => this.close(emoji)); buttons[i].addEventListener("focus", () => { this.focusedButton = i; }); buttons[i].addEventListener("unfocus", () => { this.focusedButton = null; }); }); this.container.style.display = "block"; if (this.quill.container.classList.contains("top-emoji")) { const x = this.container.querySelectorAll("li"); let i; for (i = 0; i < x.length; i++) { x[i].style.display = "block"; } const windowHeight = window.innerHeight; const editorPos = this.quill.container.getBoundingClientRect().top; if (editorPos > windowHeight / 2 && this.container.offsetHeight > 0) { this.container.style.top = `-${this.container.offsetHeight}px`; } } buttons[0].classList.add("emoji-active"); } close(value, trailingDelete = 0) { this.quill.enable(); this.container.style.display = "none"; while (this.container.firstChild) { this.container.removeChild(this.container.firstChild); } this.quill.off("selection-change", this.onSelectionChange); this.quill.off("text-change", this.onTextChange); if (value) { this.quill.deleteText(this.atIndex, this.query.length + 1 + trailingDelete, Quill.sources.USER); this.quill.insertEmbed(this.atIndex, "emoji", value, Quill.sources.USER); setTimeout(() => this.quill.setSelection(this.atIndex + 1), 0); } this.quill.focus(); this.open = false; if (this.onClose) { this.onClose(value); } } } ShortNameEmoji.DEFAULTS = { emojiList: emojiList.default, fuse: { shouldSort: true, threshold: 0.1, location: 0, distance: 100, maxPatternLength: 32, minMatchCharLength: 1, keys: [ "shortname" ] } }; function makeElement(tag, attrs, ...children) { const elem = document.createElement(tag); Object.keys(attrs).forEach((key) => { elem[key] = attrs[key]; }); children.forEach((child) => { if (typeof child === "string") { child = document.createTextNode(child); } elem.appendChild(child); }); return elem; } exports.default = ShortNameEmoji; //# sourceMappingURL=emoji.cjs.js.map