UNPKG

hi-mention

Version:

纯JS聊天框,兼容原生HTML/React/Vue等各种框架;支持@提及功能、插入富文本等多功能编辑器;内置H5和PC的交互样式

577 lines (576 loc) 23.7 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); var const_1 = require("./const"); var UserSelector_1 = __importDefault(require("./UserSelector")); var index_1 = require("./utils/index"); var range_1 = require("./utils/range"); var wordWrap_1 = __importDefault(require("./utils/wordWrap")); var wordDelete_1 = __importDefault(require("./utils/wordDelete")); var moveCursor_1 = __importDefault(require("./utils/moveCursor")); var Mention = /** @class */ (function () { function Mention(el, option) { if (option === void 0) { option = {}; } this._editorBody = (0, index_1.createElement)("section", { className: "hi-mention-body" }); this._editorEl = (0, index_1.createElement)("div", { className: const_1.EDITOR_CLASS }); this._placeholderEl = (0, index_1.createElement)("div", { className: "hi-mention-placeholder" }); this._events = { blurs: [], focuses: [], changes: [], inputs: [], keydowns: [], keyups: [], "mention-users": [], }; this.options = (0, const_1.defaultMentionOptions)(); this._queryStr = ""; // 历史记录 this._history = []; if (!el) { throw new Error("Mention: no element provided"); } if (typeof el === "string") { var e = document.querySelector(el); if (!e) { throw new Error("Mention: no element provided"); } this._rootEl = e; } else { this._rootEl = el; } this.options = Object.assign(this.options, option); this._initElement(); this._initEvent(); this.initUserSelector(); } Mention.prototype._initElement = function () { this._rootEl.style.position = "relative"; var body = this._editorBody; var editor = this._editorEl; editor.setAttribute("contenteditable", "true"); (0, index_1.fixEditorContent)(editor); var placeholderEl = this._placeholderEl; placeholderEl.innerText = this.options.placeholder; placeholderEl.style.color = this.options.placeholderColor; body.appendChild(editor); body.appendChild(placeholderEl); this._rootEl.appendChild(body); }; Mention.prototype._initEvent = function () { var _this = this; this._editorEl.onclick = function (e) { return _this._onclick(e); }; this._editorEl.onblur = function (e) { return _this._onblur(e); }; this._editorEl.onfocus = function (e) { return _this._onfocus(e); }; this._editorEl.onkeydown = function (e) { return _this._onkeydown(e); }; this._editorEl.onkeyup = function (e) { return _this._onkeyup(e); }; this._editorEl.oninput = function (e) { return _this._oninput(e); }; this._editorEl.oncut = function (e) { return _this._oncut(e); }; this._editorEl.onpaste = function (e) { return _this._onpaste(e); }; }; Mention.prototype._onclick = function (e) { if (e.target === this._editorEl) { var range = (0, range_1.getRangeAt)(); // 没有选中内容,并且光标不在_editorEl内 if ((range === null || range === void 0 ? void 0 : range.collapsed) && range.commonAncestorContainer.nodeName !== "#text") { e.preventDefault(); this.focus(); } } }; Mention.prototype._onblur = function (e) { var _this = this; this._events["blurs"].forEach(function (fn) { return fn(e); }); clearTimeout(this.blurtimeout); this.blurtimeout = setTimeout(function () { _this.closeUserSelector(); }, 200); }; Mention.prototype._onfocus = function (e) { this._events["focuses"].forEach(function (fn) { return fn(e); }); }; Mention.prototype._onkeydown = function (e) { var bool = this.onWordWrap(e); if (bool) { e.preventDefault(); this._onchange(); // 不触发默认行为,需要手动触发change事件 this._inputEvent(); return; } bool = this.onWordDelete(e); if (bool) { e.preventDefault(); this._onchange(); // 不触发默认行为,需要手动触发change事件 this._inputEvent(); return; } bool = this.onMoveCursor(e); if (bool) { e.preventDefault(); return; } bool = this.onUndoHistory(e); if (bool) { e.preventDefault(); return; } bool = this.onSelectAll(e); if (bool) { e.preventDefault(); return; } bool = this.onShearContent(e); if (bool) { e.preventDefault(); return; } this._events["keydowns"].forEach(function (fn) { return fn(e); }); }; Mention.prototype._onkeyup = function (e) { this._events["keyups"].forEach(function (fn) { return fn(e); }); }; Mention.prototype._oninput = function (e) { this._events["inputs"].forEach(function (fn) { return fn(e); }); this._onchange(); this._inputEvent(); }; Mention.prototype._oncut = function (e) { this._onchange(); this._inputEvent(); }; Mention.prototype._onpaste = function (e) { var _a; e.preventDefault(); var text = (_a = e.clipboardData) === null || _a === void 0 ? void 0 : _a.getData("text/plain"); if (!text) return; var reg = new RegExp("".concat(const_1.PLACEHOLDER_TEXT), "ig"); text = text.replace(reg, ""); var range = (0, range_1.getRangeAt)(); if (!range) return; var els = (0, range_1.rangeEls)(range); if (!els) return; if (!range.collapsed) { (0, range_1.removeRangeContent)(range, { startEls: els, endEls: els }); } else { (0, range_1.fixTextContent)(range, els); } range.insertNode((0, index_1.createTextNode)(text)); // 修正行元素 (0, index_1.fixRowContent)(els.rowEl); this._onchange(); this._inputEvent(); }; Mention.prototype._onchange = function () { var _this = this; if ((0, index_1.fixEditorContent)(this._editorEl)) { this.focus(); } var text = this._editorEl.textContent; var reg = new RegExp("^[\n".concat(const_1.PLACEHOLDER_TEXT, "\r]*$")); if (!text || reg.test(text)) { this._placeholderEl.style.display = "block"; } else { this._placeholderEl.style.display = "none"; } clearTimeout(this._changetimeout); this._changetimeout = setTimeout(function () { var html = _this._editorEl.innerHTML; _this._events["changes"].forEach(function (fn) { return fn({ text: _this._editorEl.textContent || "", html: html }); }); }, 300); }; Mention.prototype._inputEvent = function () { var _a, _b, _c; var selection = (0, range_1.getSelection)(); if (!((_a = selection === null || selection === void 0 ? void 0 : selection.anchorNode) === null || _a === void 0 ? void 0 : _a.textContent)) return this.closeUserSelector(); var range = (0, range_1.getRangeAt)(0, selection); var text = selection.anchorNode.textContent.slice(0, selection.anchorOffset); var trigger = this.options.trigger; var reg = new RegExp("\\".concat(trigger, "[^\\s\\").concat(trigger, "]*$")); if (!reg.test(text)) return this.closeUserSelector(); var t = reg.exec(text); if (!t) return this.closeUserSelector(); this._inputSelection = selection; if (!range) return; this._inputRange = range; if (((_b = this._inputRange.endContainer) === null || _b === void 0 ? void 0 : _b.nodeName) !== "#text") return this.closeUserSelector(); var query = (_c = t[0]) === null || _c === void 0 ? void 0 : _c.slice(1); this._queryStr = query; this.openUserSelector(query); }; Mention.prototype._isCursorInEditor = function () { var range = (0, range_1.getRangeAt)(); if (!(range === null || range === void 0 ? void 0 : range.commonAncestorContainer)) return false; return (range === null || range === void 0 ? void 0 : range.commonAncestorContainer) === this._editorEl || this._editorEl.contains(range.commonAncestorContainer); }; Mention.prototype.onUndoHistory = function (e) { if (e.ctrlKey && e.code === "KeyZ") { console.log("撤销:开发中!!!"); return true; } return false; }; Mention.prototype.selectAll = function () { if ((0, index_1.isEmptyElement)(this._editorEl)) return; var range = (0, range_1.getRangeAt)(); if (!range) return; var tRow = this._editorEl.children[0]; var tfow = this._editorEl.children[this._editorEl.children.length - 1]; if (tRow) (0, range_1.moveRangeAtRowStart)(range, tRow, false); if (tfow) (0, range_1.moveRangeAtRowEnd)(range, tfow, false); }; Mention.prototype.onSelectAll = function (e) { if (e.ctrlKey && e.code === "KeyA") { this.selectAll(); return true; } return false; }; Mention.prototype.shearContent = function () { var _this = this; var range = (0, range_1.getRangeAt)(); if (!range) return; if (!range.collapsed) { var content_1 = (0, range_1.removeRangeContent)(range); this._onchange(); this._inputEvent(); // 将内容写入剪切板 if (content_1) { setTimeout(function () { return __awaiter(_this, void 0, void 0, function () { var err_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); // 将文本写入剪贴板 return [4 /*yield*/, navigator.clipboard.writeText(content_1)]; case 1: // 将文本写入剪贴板 _a.sent(); console.info("剪切板写入内容:", content_1); return [3 /*break*/, 3]; case 2: err_1 = _a.sent(); console.error("内容写入剪切板失败: ", err_1); return [3 /*break*/, 3]; case 3: return [2 /*return*/]; } }); }); }); } } return true; }; Mention.prototype.onShearContent = function (e) { if (e.ctrlKey && e.code === "KeyX") { this.shearContent(); return true; } return false; }; Mention.prototype.moveCursor = function (direction) { return (0, moveCursor_1.default)(direction); }; Mention.prototype.onMoveCursor = function (e) { if (e.code === "ArrowLeft" || e.code === "ArrowRight") { this.moveCursor(e.code); return true; } return false; }; Mention.prototype.wordDelete = function (e) { return (0, wordDelete_1.default)(e, this._editorEl); }; Mention.prototype.onWordDelete = function (e) { if (["Backspace", "Delete"].includes(e.code)) { return this.wordDelete(e); } return false; }; Mention.prototype.wordWrap = function () { return (0, wordWrap_1.default)(); }; Mention.prototype.onWordWrap = function (e) { if (["Enter", "NumpadEnter"].includes(e.code)) { this.wordWrap(); return true; } return false; }; Mention.prototype.setOptions = function (options) { var _a; this.options = __assign(__assign({}, this.options), options); var trigger = options.trigger, placeholder = options.placeholder, placeholderColor = options.placeholderColor, mentionColor = options.mentionColor, users = options.users, idKey = options.idKey, nameKey = options.nameKey, avatarKey = options.avatarKey, pingyinKey = options.pingyinKey, media = options.media, usersWdith = options.usersWdith, usersHeight = options.usersHeight; if (placeholder) this._placeholderEl.innerText = placeholder; if (placeholderColor) this._placeholderEl.style.color = placeholderColor; (_a = this.userSelector) === null || _a === void 0 ? void 0 : _a.setOptions(options); return this; }; Mention.prototype.getOptions = function () { return __assign({}, this.options); }; /** * 事件监听器 * @param key 监听的事件名称 * @param fn 事件回调函数 * @returns 返回当前实例 */ Mention.prototype.on = function (key, fn) { switch (key) { case "blur": this._events["blurs"].push(fn); break; case "change": this._events["changes"].push(fn); break; case "focus": this._events["focuses"].push(fn); break; case "mention-user": this._events["mention-users"].push(fn); break; case "input": this._events["inputs"].push(fn); break; case "keydown": this._events["keydowns"].push(fn); break; case "keyup": this._events["keyups"].push(fn); break; } return this; }; /** * 提及用户 * @param user 用户信息 * @returns */ Mention.prototype.mentionUser = function (user) { var _a, _b; if (!this._inputRange) return this; var _c = this.options, nameKey = _c.nameKey, idKey = _c.idKey, pingyinKey = _c.pingyinKey, avatarKey = _c.avatarKey; // 创建一个span元素来表示用户 var span = (0, index_1.createElement)("span", { className: const_1.USER_AT_CLASS, content: "@".concat(user[nameKey]), style: { color: this.options.mentionColor }, }); span.setAttribute("data-user-id", user[idKey]); span.setAttribute("data-user-id-type", typeof user[idKey]); span.setAttribute("data-user-name", user[nameKey]); if (user[avatarKey]) span.setAttribute("data-user-avatar", user[avatarKey]); if (user[pingyinKey]) span.setAttribute("data-user-pingyin", user[pingyinKey]); var range = this._inputRange; // 将光标重新设置到输入框中 range.collapse(false); (_a = this._inputSelection) === null || _a === void 0 ? void 0 : _a.removeAllRanges(); (_b = this._inputSelection) === null || _b === void 0 ? void 0 : _b.addRange(range); // 设置光标选中@符号之后的内容 range.setStart(range.endContainer, range.endOffset - (this._queryStr.length + 1)); range.setEnd(range.endContainer, range.endOffset); // 删除选中的内容 range.deleteContents(); // 插入span元素 (0, range_1.insertElement)(span, range); this._events["mention-users"].forEach(function (fn) { return fn(user); }); this._onchange(); this.closeUserSelector(); return this; }; /** * 清空输入框内容 * @returns 返回当前实例 */ Mention.prototype.clear = function () { this._editorEl.innerHTML = ""; (0, index_1.fixEditorContent)(this._editorEl); this._onchange(); this.focus(); return this; }; /** * 在当前位置插入文本内容 * @param text 文本内容 * @returns 返回当前实例 */ Mention.prototype.insertText = function (text) { if (!this._isCursorInEditor()) return this; var selection = (0, range_1.getSelection)(); var range = (0, range_1.getRangeAt)(0, selection); if (!range) return this; (0, range_1.insertText)(text, range); this._onchange(); range.collapse(false); selection === null || selection === void 0 ? void 0 : selection.removeAllRanges(); selection === null || selection === void 0 ? void 0 : selection.addRange(range); return this; }; /** * 在当前位置插入html内容 * @param html html内容 * @returns 返回当前实例 */ Mention.prototype.insertHtml = function (html) { if (!this._isCursorInEditor()) return this; var selection = (0, range_1.getSelection)(); var range = (0, range_1.getRangeAt)(0, selection); if (!range) return this; (0, range_1.insertElement)(html, range); this._onchange(); range.collapse(false); selection === null || selection === void 0 ? void 0 : selection.removeAllRanges(); selection === null || selection === void 0 ? void 0 : selection.addRange(range); return this; }; /** * 获取焦点 * @returns 返回当前实例 */ Mention.prototype.focus = function () { // 获取焦点 var selection = (0, range_1.getSelection)(); if (!selection) return this; var range = document.createRange(); range.selectNodeContents(this._editorEl); range.collapse(false); selection.removeAllRanges(); selection.addRange(range); (0, range_1.moveRangeAtEditorEnd)(range, this._editorEl); return this; }; /** * 获取输入框内容 * @returns 输入框内容 */ Mention.prototype.getData = function () { return { text: this._editorEl.innerText, html: this._editorEl.innerHTML, }; }; /** * 获取@提及的用户列表 * @returns 用户列表 */ Mention.prototype.getMentions = function () { var nodes = this._editorEl.querySelectorAll(".".concat(const_1.USER_AT_CLASS)); var _a = this.options, nameKey = _a.nameKey, idKey = _a.idKey, avatarKey = _a.avatarKey, pingyinKey = _a.pingyinKey; return Array.from(nodes).map(function (node) { var _a; var id = node.getAttribute("data-user-id"); var name = node.getAttribute("data-user-name"); var avatar = node.getAttribute("data-user-avatar"); var pingyin = node.getAttribute("data-user-pingyin"); var idType = node.getAttribute("data-user-id-type"); var user = (_a = {}, _a[idKey] = idType === "number" ? Number(id) : id, _a[nameKey] = name, _a); if (avatar) user[avatarKey] = avatar; if (pingyin) user[pingyinKey] = pingyin; return user; }); }; Mention.prototype.initUserSelector = function () { var _this = this; this.userSelector = new UserSelector_1.default(this._rootEl, this.options); // 监听鼠标在用户列表中按下事件,防止鼠标点击用户列表时,触发编辑器失去焦点事件 this.userSelector.element.onmousedown = function () { return setTimeout(function () { return clearTimeout(_this.blurtimeout); }, 100); }; this.userSelector.onSelectUser(function (user) { _this.mentionUser(user); }); }; Mention.prototype.closeUserSelector = function () { var _a; (_a = this.userSelector) === null || _a === void 0 ? void 0 : _a.close(); }; /** * 打开用户选择器 * @param query 查询字符串 * @returns */ Mention.prototype.openUserSelector = function (query) { var _a; (_a = this.userSelector) === null || _a === void 0 ? void 0 : _a.open(query); }; return Mention; }()); exports.default = Mention;