@leoyin/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.
176 lines (175 loc) • 5.8 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const native = require("../node_modules/.pnpm/@emoji-mart_data@1.2.1/node_modules/@emoji-mart/data/sets/15/native.json.cjs.js");
const floatingUi_dom = require("../node_modules/.pnpm/@floating-ui_dom@1.7.2/node_modules/@floating-ui/dom/dist/floating-ui.dom.cjs.js");
const module$1 = require("../node_modules/.pnpm/emoji-mart@5.6.0/node_modules/emoji-mart/dist/module.cjs.js");
const lodashEs = require("lodash-es");
const DEFAULT_OPTIONS = {
theme: "light",
set: "native",
skinTonePosition: "none",
previewPosition: "bottom",
searchPosition: "sticky",
categories: ["frequent", "people", "nature", "foods", "activity", "places", "objects", "symbols", "flags"],
maxFrequentRows: 2,
perLine: 8,
navPosition: "top",
noCountryFlags: false,
dynamicWidth: false
};
const PICKER_DOM_ID = "emoji-picker";
const LOCALE_MAP = {
"zh-CN": "zh",
"en-US": "en"
};
class EmojiModule {
constructor(quill, options = {}) {
__publicField(this, "quill");
__publicField(this, "options");
__publicField(this, "picker", null);
__publicField(this, "isPickerVisible", false);
__publicField(this, "cleanupResizeObserver", null);
this.quill = quill;
this.options = options;
const toolbar = this.quill.getModule("toolbar");
if (toolbar) {
toolbar.addHandler("emoji", () => {
if (this.isPickerVisible) {
this.closeDialog();
} else {
this.openDialog();
}
});
}
}
getEmojiButton() {
return document.querySelector(".ql-emoji");
}
async updatePickerPosition() {
const button = this.getEmojiButton();
const pickerElement = document.getElementById(PICKER_DOM_ID);
if (!button || !this.picker || !pickerElement) {
return;
}
try {
const { x, y } = await floatingUi_dom.computePosition(button, pickerElement);
this.picker.style.top = `${y}px`;
this.picker.style.left = `${x}px`;
} catch (error) {
console.warn("Failed to compute picker position:", error);
}
}
// 监听容器大小变化,更新表情选择弹窗位置
setupContainerResizeObserver() {
const container = this.quill.root.parentElement;
if (!container) {
return null;
}
const debouncedUpdate = lodashEs.debounce(() => {
this.updatePickerPosition();
}, 100);
const resizeObserver = new ResizeObserver(() => {
debouncedUpdate();
});
resizeObserver.observe(container);
return () => {
resizeObserver.disconnect();
debouncedUpdate.cancel();
};
}
// 创建表情选择弹窗
createPicker() {
const pickerConfig = {
...DEFAULT_OPTIONS,
// emoji-mart 与 tiny-editor 国际化的的 locale 不一致使用 LOCALE_MAP 转换
locale: LOCALE_MAP[this.quill.lang] ?? "en",
...this.options,
data: native.default,
onEmojiSelect: this.handleEmojiSelect.bind(this),
onClickOutside: this.handleClickOutside.bind(this)
};
const picker = new module$1.Picker(pickerConfig);
picker.id = PICKER_DOM_ID;
picker.style.position = "absolute";
picker.style.zIndex = "1000";
return picker;
}
// 打开表情弹窗
openDialog() {
if (this.picker) {
return;
}
try {
this.picker = this.createPicker();
document.body.appendChild(this.picker);
this.updatePickerPosition();
this.cleanupResizeObserver = this.setupContainerResizeObserver();
this.isPickerVisible = true;
} catch (error) {
console.error("Failed to open emoji picker:", error);
this.closeDialog();
}
}
// 关闭表情弹窗
closeDialog() {
var _a;
if (!this.picker) {
return;
}
this.isPickerVisible = false;
this.picker.remove();
this.picker = null;
(_a = this.cleanupResizeObserver) == null ? void 0 : _a.call(this);
this.cleanupResizeObserver = null;
}
// 处理表情选择事件
handleEmojiSelect(emoji) {
const selection = this.quill.getSelection(true);
if (!selection) {
console.warn("No selection available for emoji insertion");
return;
}
try {
const emojiDelta = this.quill.insertText(selection.index, emoji.native, "user");
this.closeDialog();
this.setSelectionAfterEmoji(emojiDelta);
} catch (error) {
console.error("Failed to insert emoji:", error);
}
}
// 设置表情插入后的光标位置
setSelectionAfterEmoji(emojiDelta) {
setTimeout(() => {
try {
const newSelection = this.quill.getSelection(true);
if (newSelection && emojiDelta) {
this.quill.setSelection(newSelection.index + emojiDelta.length());
}
} catch (error) {
console.warn("Failed to set selection after emoji insertion:", error);
}
}, 0);
}
// 处理外部点击事件
handleClickOutside(event) {
const button = this.getEmojiButton();
const target = event.target;
const isClickOnButton = target === button || target instanceof Element && (button == null ? void 0 : button.contains(target));
if (isClickOnButton) {
return;
}
this.closeDialog();
}
// 销毁模块,清理资源
destroy() {
var _a;
(_a = this.cleanupResizeObserver) == null ? void 0 : _a.call(this);
this.cleanupResizeObserver = null;
this.closeDialog();
}
}
exports.EmojiModule = EmojiModule;
//# sourceMappingURL=emoji.cjs.js.map