UNPKG

lemon-imui-vue3

Version:

> npm install lemon-imui-vue3 -S

1,806 lines (1,805 loc) 49.6 kB
import { createVNode as n, Transition as X, withDirectives as f, vShow as g, resolveComponent as d, createTextVNode as j, isVNode as O, mergeProps as D, capitalize as Z, resolveDirective as K, reactive as q } from "vue"; function Q(e) { return Object.prototype.toString.call(e) === "[object Object]"; } function p(e) { return typeof e == "string"; } function J(e) { return (/* @__PURE__ */ new Date()).getTime() - e < 864e5; } function C(e) { return !!(!e || Array.isArray(e) && e.length == 0 || Q(e) && Object.values(e).length == 0); } function b(e) { return e && typeof e == "function"; } const N = [], ee = { hover(e) { }, focus(e) { e.addEventListener("focus", (t) => { this.changeVisible(); }), e.addEventListener("blur", (t) => { this.changeVisible(); }); }, click(e) { e.stopPropagation(), T.hide(), this.changeVisible(); }, contextmenu(e) { e.addEventListener("contextmenu", (t) => { t.preventDefault(), this.changeVisible(); }); } }, P = { name: "LemonPopover", props: { trigger: { type: String, default: "click", validator(e) { return Object.keys(ee).includes(e); } } }, data() { return this.defaultElm = null, { popoverStyle: {}, visible: !1 }; }, created() { document.addEventListener("click", this._documentClickEvent), N.push(this.close); }, mounted() { }, render() { return n("span", { style: "position:relative" }, [n(X, { name: "lemon-slide-top" }, { default: () => [this.visible && n("div", { class: "lemon-popover", ref: "popover", style: this.popoverStyle, onClick: (e) => e.stopPropagation() }, [n("div", { class: "lemon-popover__content" }, [this.$slots.content && this.$slots.content()]), n("div", { class: "lemon-popover__arrow" }, null)])] }), n("span", { ref: "trigger", onClick: (e) => { e.stopPropagation(), T.hide(), this.changeVisible(); } }, [this.$slots.default && this.$slots.default()])]); }, destroyed() { document.removeEventListener("click", this._documentClickEvent); }, computed: {}, watch: { async visible(e) { if (e) { await this.$nextTick(); const { trigger: t, popover: s } = this.$refs; this.popoverStyle = { top: `-${s.offsetHeight + 10}px`, left: `${t.offsetWidth / 2 - s.offsetWidth / 2}px` }; } } }, methods: { _documentClickEvent(e) { e.stopPropagation(), this.visible && this.close(); }, changeVisible() { this.visible ? this.close() : this.open(); }, open() { this.closeAll(), this.visible = !0; }, closeAll() { N.forEach((e) => e()); }, close() { this.visible = !1; } } }; let u; const E = () => { u && (u.style.display = "none"); }, te = () => { u && (u.style.display = "block"); }; document.addEventListener("click", (e) => { E(); }); const T = { hide: E, bind(e, t, s) { e.addEventListener( t.modifiers.click ? "click" : "contextmenu", (i) => { if (C(t.value) || !Array.isArray(t.value)) return; t.modifiers.click && i.stopPropagation(), i.preventDefault(), P.methods.closeAll(); let a, o = []; t.modifiers.message ? a = s.context : t.modifiers.contact && (a = s.child), u || (u = document.createElement("div"), u.className = "lemon-contextmenu", document.body.appendChild(u)), u.innerHTML = t.value.map((r) => { let c; if (b(r.visible) ? c = r.visible(a) : c = r.visible === void 0 ? !0 : r.visible, c) { o.push(r); const l = r.icon ? `<i class="lemon-contextmenu__icon ${r.icon}"></i>` : ""; return `<div style="color:${r.color}" title="${r.text}" class="lemon-contextmenu__item">${l}<span>${r.text}</span></div>`; } return ""; }).join(""), u.style.top = `${i.pageY}px`, u.style.left = `${i.pageX}px`, u.childNodes.forEach((r, c) => { const { click: l, render: v } = o[c]; r.addEventListener("click", (y) => { y.stopPropagation(), b(l) && l(y, a, E); }); }), te(); } ); } }, se = { name: "LemonTabs", props: { activeIndex: String }, data() { return { active: this.activeIndex }; }, mounted() { this.active || (this.active = this.$slots["tab-pane"][0].data.attrs.index); }, render() { const e = [], t = []; return this.$slots["tab-pane"].map((s) => { const { tab: i, index: a } = s.data.attrs; e.push(f(n("div", { class: "lemon-tabs-content__pane" }, [s]), [[g, this.active == a]])), t.push(n("div", { class: ["lemon-tabs-nav__item", this.active == a && "lemon-tabs-nav__item--active"], onClick: () => this._handleNavClick(a) }, [i])); }), n("div", { class: "lemon-tabs" }, [n("div", { class: "lemon-tabs-content" }, [e]), n("div", { class: "lemon-tabs-nav" }, [t])]); }, methods: { _handleNavClick(e) { this.active = e; } } }; const ne = { name: "LemonButton", props: { color: { type: String, default: "default" }, disabled: Boolean }, render() { return n("button", { class: ["lemon-button", `lemon-button--color-${this.color}`], disabled: this.disabled, type: "button" }, [this.$slots.default && this.$slots.default()]); }, methods: {} }; const ie = { name: "LemonBadge", props: { count: [Number, Boolean], overflowCount: { type: Number, default: 99 } }, render() { return n("span", { class: "lemon-badge" }, [this.$slots.default && this.$slots.default(), this.count !== 0 && this.count !== void 0 && n("span", { class: ["lemon-badge__label", this.isDot && "lemon-badge__label--dot"] }, [this.label])]); }, computed: { isDot() { return this.count === !0; }, label() { return this.isDot ? "" : this.count > this.overflowCount ? `${this.overflowCount}+` : this.count; } }, methods: {} }; const ae = { name: "LemonAvatar", inject: ["IMUI"], props: { src: String, icon: { type: String, default: "lemon-icon-people" }, circle: { type: Boolean, default() { return this && this.IMUI ? this.IMUI.avatarCricle : !1; } }, size: { type: Number, default: 32 } }, data() { return { imageFinishLoad: !0 }; }, render() { return n("span", { style: this.style, class: ["lemon-avatar", { "lemon-avatar--circle": this.circle }], onClick: (e) => this.$emit("click", e) }, [(this.imageFinishLoad || !this.src) && n("i", { class: this.icon }, null), n("img", { src: this.src, onLoad: this._handleLoad }, null)]); }, computed: { style() { const e = `${this.size}px`; return { width: e, height: e, lineHeight: e, fontSize: `${this.size / 2}px` }; } }, methods: { _handleLoad() { this.imageFinishLoad = !1; } } }; function m(e, t, s) { return e ? e(s) : t; } function M(e) { return e < 10 ? `0${e}` : e; } function B(e) { const t = new Date(e), s = /* @__PURE__ */ new Date(), i = (l) => l.getFullYear(), a = (l) => `${l.getMonth() + 1}-${l.getDate()}`, o = i(t), r = i(s); let c; return o !== r ? c = "y年m月d日 h:i" : `${o}-${a(t)}` == `${r}-${a(s)}` ? c = "h:i" : c = "m月d日 h:i", H(e, c); } function H(e, t) { t || (t = "y-m-d h:i:s"), e ? e = new Date(e) : e = /* @__PURE__ */ new Date(); const s = [ e.getFullYear().toString(), M((e.getMonth() + 1).toString()), M(e.getDate().toString()), M(e.getHours().toString()), M(e.getMinutes().toString()), M(e.getSeconds().toString()) ], i = "ymdhis"; for (let a = 0; a < s.length; a++) t = t.replace(i.charAt(a), s[a]); return t; } function A(e, t) { b(e) ? e(() => { t(); }) : t(); } function oe(e) { return e.replace(/<.*?>/gi, ""); } function z(e) { return e.replace(/<(?!img).*?>/gi, ""); } function re(e) { if (e == null || e == "") return "0 Bytes"; var t = ["B", "K", "M", "G", "T", "P", "E", "Z", "Y"], s = 0, i = parseFloat(e); s = Math.floor(Math.log(i) / Math.log(1024)); var a = i / Math.pow(1024, s); return a = parseFloat(a.toFixed(2)), a + t[s]; } function le() { var e = (/* @__PURE__ */ new Date()).getTime(); window.performance && typeof window.performance.now == "function" && (e += performance.now()); var t = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(s) { var i = (e + Math.random() * 16) % 16 | 0; return e = Math.floor(e / 16), (s == "x" ? i : i & 3 | 8).toString(16); }); return t; } const ce = { name: "LemonContact", components: {}, inject: { IMUI: { from: "IMUI", default() { return this; } } }, data() { return {}; }, props: { contact: Object, simple: Boolean, timeFormat: { type: Function, default(e) { return H(e, J(e) ? "h:i" : "y/m/d"); } } }, render() { return n("div", { class: ["lemon-contact", { "lemon-contact--name-center": this.simple }], title: this.contact.displayName, onClick: (e) => this._handleClick(e, this.contact) }, [m(this.$slots.default, this._renderInner(), this.contact)]); }, created() { }, mounted() { }, computed: {}, watch: {}, methods: { _renderInner() { const { contact: e } = this; return [n(d("lemon-badge"), { count: this.simple ? 0 : e.unread, class: "lemon-contact__avatar" }, { default: () => [n(d("lemon-avatar"), { size: 40, src: e.avatar }, null)] }), n("div", { class: "lemon-contact__inner" }, [n("p", { class: "lemon-contact__label" }, [n("span", { class: "lemon-contact__name" }, [e.displayName]), !this.simple && n("span", { class: "lemon-contact__time" }, [this.timeFormat(e.lastSendTime)])]), !this.simple && n("p", { class: "lemon-contact__content" }, [p(e.lastContent) ? n("span", { innerHTML: e.lastContent }, null) : e.lastContent])])]; }, _handleClick(e, t) { this.$emit("click", t); } } }; function de(e) { return typeof e == "function" || Object.prototype.toString.call(e) === "[object Object]" && !O(e); } const L = (e, t) => { document.execCommand(e, !1, t); }, $ = window.getSelection(); let _, I = []; const he = { name: "LemonEditor", inject: { IMUI: { from: "IMUI", default() { return this; } } }, components: {}, props: { tools: { type: Array, default: () => [] }, sendText: { type: String, default: "发 送" }, wrapKey: { type: Function, default: function(e) { return e.keyCode == 13 && e.ctrlKey == !1 && e.shiftKey == !1; } }, sendKey: { type: Function, default(e) { return e.keyCode == 13 && e.ctrlKey === !0; } } }, data() { return this.clipboardBlob = null, { //剪切板图片URL clipboardUrl: "", submitDisabled: !0, //proxyTools: [], accept: "" }; }, created() { }, mounted() { }, render() { const e = [], t = []; return this.proxyTools.forEach(({ name: s, title: i, render: a, click: o, isRight: r }) => { o = o || new Function(); const c = ["lemon-editor__tool-item", { "lemon-editor__tool-item--right": r }]; let l; s == "emoji" ? l = I.length == 0 ? "" : n(d("lemon-popover"), { class: "lemon-editor__emoji" }, { content: () => this._renderEmojiTabs(), default: () => n("div", { class: c, title: i }, [a()]) }) : l = n("div", { class: c, onClick: o, title: i }, [a()]), r ? t.push(l) : e.push(l); }), n("div", { class: "lemon-editor" }, [this.clipboardUrl && n("div", { class: "lemon-editor__clipboard-image" }, [n("img", { src: this.clipboardUrl }, null), n("div", null, [n(d("lemon-button"), { style: { marginRight: "10px" }, onClick: this.closeClipboardImage, color: "grey" }, { default: () => [j("取消")] }), n(d("lemon-button"), { onClick: this.sendClipboardImage }, { default: () => [j("发送图片")] })])]), n("input", { style: "display:none", type: "file", multiple: "multiple", ref: "fileInput", accept: this.accept, onChange: this._handleChangeFile }, null), n("div", { class: "lemon-editor__tool" }, [n("div", { class: "lemon-editor__tool-left" }, [e]), n("div", { class: "lemon-editor__tool-right" }, [t])]), n("div", { class: "lemon-editor__inner" }, [n("div", { class: "lemon-editor__input", ref: "textarea", contenteditable: "true", onKeyup: this._handleKeyup, onKeydown: this._handleKeydown, onPaste: this._handlePaste, onClick: this._handleClick, spellcheck: "false" }, null)]), n("div", { class: "lemon-editor__footer" }, [n("div", { class: "lemon-editor__tip" }, [m(this.IMUI.$slots["editor-footer"], "使用 ctrl + enter 快捷发送消息")]), n("div", { class: "lemon-editor__submit" }, [n(d("lemon-button"), { disabled: this.submitDisabled, onClick: this._handleSend }, { default: () => [this.sendText] })])])]); }, computed: { proxyTools() { if (!this.tools) return []; const e = [{ name: "emoji", title: "表情", click: null, render: (s) => n("i", { class: "lemon-icon-emoji" }, null) }, { name: "uploadFile", title: "文件上传", click: () => this.selectFile("*"), render: (s) => n("i", { class: "lemon-icon-folder" }, null) }, { name: "uploadImage", title: "图片上传", click: () => this.selectFile("image/*"), render: (s) => n("i", { class: "lemon-icon-image" }, null) }]; let t = []; if (Array.isArray(this.tools)) { const s = { emoji: 0, uploadFile: 1, uploadImage: 2 }, i = Object.keys(s); t = this.tools.map((a) => i.includes(a.name) ? { ...e[s[a.name]], ...a } : a); } else t = e; return t; } }, methods: { closeClipboardImage() { this.clipboardUrl = "", this.clipboardBlob = null; }, sendClipboardImage() { this.clipboardBlob && (this.$emit("upload", this.clipboardBlob), this.closeClipboardImage()); }, saveRangeToLast() { _ || (_ = document.createRange()), _.selectNodeContents(textarea.value), _.collapse(!1), $.removeAllRanges(), $.addRange(_); }, inertContent(e, t = !1) { t && saveRangeToLast(), this.focusRange(), L("insertHTML", e), this.saveRange(); }, saveRange() { _ = $.getRangeAt(0); }, focusRange() { this.$refs.textarea.focus(), _ && ($.removeAllRanges(), $.addRange(_)); }, _handleClick() { this.saveRange(); }, _renderEmojiTabs() { const e = (t) => t.map((s) => n("img", { src: s.src, title: s.title, class: "lemon-editor__emoji-item", onClick: () => this._handleSelectEmoji(s) }, null)); if (I[0].label) { const t = I.map((s, i) => n("div", { slot: "tab-pane", index: i, tab: s.label }, [e(s.children)])); return n(d("lemon-tabs"), { style: "width: 412px" }, de(t) ? t : { default: () => [t] }); } else return n("div", { class: "lemon-tabs-content", style: "width:406px" }, [e(I)]); }, _handleSelectEmoji(e) { this.inertContent(`<img emoji-name="${e.name}" src="${e.src}"></img>`), this._checkSubmitDisabled(); }, async selectFile(e) { this.accept = e, await this.$nextTick(), this.$refs.fileInput.click(); }, _handlePaste(e) { e.preventDefault(); const t = e.clipboardData || window.clipboardData, s = t.getData("Text"); if (s) this.submitDisabled = !1, window.clipboardData ? this.$refs.textarea.innerHTML = s : L("insertText", s); else { const { blob: i, blobUrl: a } = this._getClipboardBlob(t); this.clipboardBlob = i, this.clipboardUrl = a; } }, _getClipboardBlob(e) { let t, s; for (var i = 0; i < e.items.length; ++i) e.items[i].kind == "file" && e.items[i].type.indexOf("image/") !== -1 && (t = e.items[i].getAsFile(), s = (window.URL || window.webkitURL).createObjectURL(t)); return { blob: t, blobUrl: s }; }, _handleKeyup(e) { this.saveRange(), this._checkSubmitDisabled(); }, _handleKeydown(e) { (e.keyCode == 13 || e.keyCode == 13 && e.shiftKey) && e.preventDefault(), this.wrapKey(e) && (e.preventDefault(), L("insertLineBreak")), this.submitDisabled == !1 && this.sendKey(e) && this._handleSend(); }, getFormatValue() { return this.IMUI.emojiImageToName(this.$refs.textarea.innerHTML); }, _checkSubmitDisabled() { this.submitDisabled = !z(this.$refs.textarea.innerHTML.trim()); }, _handleSend(e) { const t = this.getFormatValue(); this.$emit("send", t), this.clear(), this._checkSubmitDisabled(); }, _handleChangeFile(e) { const { fileInput: t } = this.$refs; Array.from(t.files).forEach((s) => { this.$emit("upload", s); }), t.value = ""; }, clear() { this.$refs.textarea.innerHTML = ""; }, initEmoji(e) { I = e, this.$forceUpdate(); }, setValue(e) { this.$refs.textarea.innerHTML = this.IMUI.emojiNameToImage(e), this._checkSubmitDisabled(); } } }; const Y = { name: "lemonMessageText", inheritAttrs: !1, inject: ["IMUI"], render() { return n(d("lemon-message-basic"), D({ class: "lemon-message-text" }, this.$attrs), { content: (e) => { const t = this.IMUI.emojiNameToImage(e.content); return n("span", { innerHTML: t }, null); } }); } }; const W = { name: "lemonMessageFile", inheritAttrs: !1, render() { return n(d("lemon-message-basic"), { class: "lemon-message-file", props: { ...this.$attrs }, scopedSlots: { content: (e) => [n("div", { class: "lemon-message-file__inner" }, [n("p", { class: "lemon-message-file__name" }, [e.fileName]), n("p", { class: "lemon-message-file__byte" }, [re(e.fileSize)])]), n("div", { class: "lemon-message-file__sfx" }, [n("i", { class: "lemon-icon-attah" }, null)])] } }, null); } }; const G = { name: "lemonMessageImage", inheritAttrs: !1, render() { return n(d("lemon-message-basic"), D({ class: "lemon-message-image" }, this.$attrs), { content: (e) => n("img", { src: e.content }, null) }); } }; const ue = { LemonMessageText: Y, LemonMessageFile: W, LemonMessageImage: G }, me = { name: "LemonMessages", props: { //是否隐藏消息发送人昵称 hideName: Boolean, //是否隐藏显示消息时间 hideTime: Boolean, reverseUserId: [String, Number], timeRange: { type: Number, default: 1 }, timeFormat: { type: Function, default(e) { return B(e); } }, loadingText: { type: [String, Function] }, loadendText: { type: [String, Function], default: "暂无更多消息" }, messages: { type: Array, default: () => [] } }, data() { return this._lockScroll = !1, { _loading: !1, _loadend: !1 }; }, render() { return n("div", { class: "lemon-messages", ref: "wrap", onScroll: this._handleScroll }, [n("div", { class: ["lemon-messages__load", `lemon-messages__load--${this._loadend ? "end" : "ing"}`] }, [n("span", { class: "lemon-messages__loadend" }, [p(this.loadendText) ? this.loadendText : this.loadendText()]), n("span", { class: "lemon-messages__loading" }, [this.loadingText ? p(this.loadingText) ? this.loadingText : this.loadingText() : n("i", { class: "lemon-icon-loading lemonani-spin" }, null)])]), this.messages.map((e, t) => { const s = [], i = `LemonMessage${Z(e.type)}`, a = this.messages[t - 1]; a && this.msecRange && e.sendTime - a.sendTime > this.msecRange && s.push(n(d("lemon-message-event"), { message: { id: "__time__", type: "event", content: B(e.sendTime) } }, null)); let o; e.type == "event" ? o = { message: e } : o = { timeFormat: this.timeFormat, message: e, reverse: this.reverseUserId == e.fromUser.id, hideTime: this.hideTime, hideName: this.hideName }; const r = ue[i]; return s.push(n(r, D({ ref: "message", refInFor: !0 }, o), null)), s; })]); }, computed: { msecRange() { return this.timeRange * 1e3 * 60; } }, watch: {}, methods: { loaded() { this._loadend = !0, this.$forceUpdate(); }, resetLoadState() { this._lockScroll = !0, this._loading = !1, this._loadend = !1, setTimeout(() => { this._lockScroll = !1; }, 200); }, async _handleScroll(e) { if (this._lockScroll) return; console.log(123); const { target: t } = e; if (T.hide(), t.scrollTop == 0 && this._loading == !1 && this._loadend == !1) { this._loading = !0, await this.$nextTick(); const s = t.scrollHeight; this.$emit("reach-top", async (i) => { await this.$nextTick(), t.scrollTop = t.scrollHeight - s, this._loading = !1, this._loadend = !!i; }); } }, async scrollToBottom() { await this.$nextTick(); const { wrap: e } = this.$refs; e && (e.scrollTop = e.scrollHeight); } }, created() { }, mounted() { } }; const fe = { name: "lemonMessageBasic", inject: { IMUI: { from: "IMUI", default() { return this; } } }, props: { contextmenu: Array, message: { type: Object, default: () => ({}) }, timeFormat: { type: Function, default: () => "" }, reverse: Boolean, hideName: Boolean, hideTime: Boolean }, data() { return {}; }, render() { const { fromUser: e, status: t, sendTime: s } = this.message, i = this.hideName == !0 && this.hideTime == !0; return n("div", { class: ["lemon-message", `lemon-message--status-${t}`, { "lemon-message--reverse": this.reverse, "lemon-message--hide-title": i }] }, [n("div", { class: "lemon-message__avatar" }, [n(d("lemon-avatar"), { size: 36, shape: "square", src: e.avatar, onClick: (a) => { this._emitClick(a, "avatar"); } }, null)]), n("div", { class: "lemon-message__inner" }, [n("div", { class: "lemon-message__title" }, [this.hideName == !1 && n("span", { onClick: (a) => { this._emitClick(a, "displayName"); } }, [e.displayName]), this.hideTime == !1 && n("span", { class: "lemon-message__time", onClick: (a) => { this._emitClick(a, "sendTime"); } }, [this.timeFormat(s)])]), n("div", { class: "lemon-message__content-flex" }, [f(n("div", { class: "lemon-message__content", onClick: (a) => { this._emitClick(a, "content"); } }, [m(this.$slots.content, null, this.message)]), [[K("lemon-contextmenu"), this.IMUI.contextmenu, void 0, { message: !0 }]]), n("div", { class: "lemon-message__content-after" }, [m(this.IMUI.$slots["message-after"], null, this.message)]), n("div", { class: "lemon-message__status", onClick: (a) => { this._emitClick(a, "status"); } }, [n("i", { class: "lemon-icon-loading lemonani-spin" }, null), n("i", { class: "lemon-icon-prompt", title: "重发消息", style: { color: "#ff2525", cursor: "pointer" } }, null)])])])]); }, created() { }, mounted() { }, computed: {}, watch: {}, methods: { _emitClick(e, t) { this.IMUI.$emit("message-click", e, t, this.message, this.IMUI); } } }; const pe = { name: "lemonMessageEvent", inheritAttrs: !1, inject: ["IMUI"], render() { const { content: e } = this.$attrs.message; return n("div", { class: "lemon-message lemon-message-event" }, [n("span", { class: "lemon-message-event__content", onClick: (t) => this._emitClick(t, "content") }, [e])]); }, methods: { _emitClick(e, t) { this.IMUI.$emit("message-click", e, t, this.$attrs.message, this.IMUI); } } }; const x = "messages", w = "contacts", ge = [x, w], F = { file(e) { return "[文件]"; }, image(e) { return "[图片]"; }, text(e) { return this.emojiNameToImage(oe(e.content)); }, event(e) { return "[通知]"; } }; class S { constructor() { this.table = {}; } get(t) { return t ? this.table[t] : this.table; } set(t, s) { this.table[t] = s; } // setOnly(key, val) { // if (!this.has(key)) this.set(key, val); // } remove(t) { t ? delete this.table[t] : this.table = {}; } has(t) { return !!this.table[t]; } } function _e(e) { return typeof e == "function" || Object.prototype.toString.call(e) === "[object Object]" && !O(e); } let h = q({}); const U = {}, R = (e) => p(e) ? e : `${e}px`, k = (e) => e.replace("%", "") / 100; let V = () => { }; const Ce = { name: "LemonImui", provide() { return { IMUI: this }; }, props: { width: { type: [String, Number], default: 850 }, height: { type: [String, Number], default: 580 }, theme: { type: String, default: "default" }, simple: { type: Boolean, default: !1 }, loadingText: [String, Function], loadendText: [String, Function], /** * 消息时间格式化规则 */ messageTimeFormat: Function, /** * 联系人最新消息时间格式化规则 */ contactTimeFormat: Function, /** * 初始化时是否隐藏抽屉 */ hideDrawer: { type: Boolean, default: !0 }, /** * 是否隐藏导航按钮上的头像 */ hideMenuAvatar: Boolean, hideMenu: Boolean, /** * 是否隐藏消息列表内的联系人名字 */ hideMessageName: Boolean, /** * 是否隐藏消息列表内的发送时间 */ hideMessageTime: Boolean, sendKey: Function, wrapKey: Function, sendText: String, contextmenu: Array, contactContextmenu: Array, avatarCricle: Boolean, user: { type: Object, default: () => ({}) } }, data() { return this.CacheContactContainer = new S(), this.CacheMenuContainer = new S(), this.CacheMessageLoaded = new S(), this.CacheDraft = new S(), { drawerVisible: !this.hideDrawer, currentContactId: null, currentMessages: [], activeSidebar: x, contacts: [], menus: [], editorTools: [{ name: "emoji" }, { name: "uploadFile" }, { name: "uploadImage" }] }; }, render() { return this._renderWrapper([this._renderMenu(), this._renderSidebarMessage(), this._renderSidebarContact(), this._renderContainer(), this._renderDrawer()]); }, created() { this.initMenus(); }, async mounted() { await this.$nextTick(); }, computed: { currentContact() { return this.contacts.find((e) => e.id == this.currentContactId) || {}; }, currentMenu() { return this.menus.find((e) => e.name == this.activeSidebar) || {}; }, currentIsDefSidebar() { return ge.includes(this.activeSidebar); }, lastMessages() { const e = this.contacts.filter((t) => !C(t.lastContent)); return e.sort((t, s) => s.lastSendTime - t.lastSendTime), e; } }, watch: { activeSidebar() { } }, methods: { _menuIsContacts() { return this.activeSidebar == w; }, _menuIsMessages() { return this.activeSidebar == x; }, _createMessage(e) { return { id: le(), type: "text", status: "going", sendTime: (/* @__PURE__ */ new Date()).getTime(), toContactId: this.currentContactId, fromUser: { ...this.user }, ...e }; }, /** * 新增一条消息 */ appendMessage(e, t = !1) { let s = "+1", i = h[e.toContactId]; if ((e.type == "event" || this.user.id == e.fromUser.id) && (s = "+0"), i === void 0) this.updateContact({ id: e.toContactId, unread: s, lastSendTime: e.sendTime, lastContent: this.lastContentRender(e) }); else { if (i.some(({ id: r }) => r == e.id)) return; this._addMessage(e, e.toContactId, 1); const o = { id: e.toContactId, lastContent: this.lastContentRender(e), lastSendTime: e.sendTime }; e.toContactId == this.currentContactId ? (t == !0 && this.messageViewToBottom(), this.CacheDraft.remove(e.toContactId)) : o.unread = s, this.updateContact(o); } }, _emitSend(e, t, s) { this.$emit("send", e, (i = { status: "succeed" }) => { t(), this.updateMessage(Object.assign(e, i)); }, s); }, _handleSend(e) { const t = this._createMessage({ content: e }); this.appendMessage(t, !0), this._emitSend(t, () => { this.updateContact({ id: t.toContactId, lastContent: this.lastContentRender(t), lastSendTime: t.sendTime }), this.CacheDraft.remove(t.toContactId); }); }, _handleUpload(e) { const t = ["image/gif", "image/jpeg", "image/png"]; let s; t.includes(e.type) ? s = { type: "image", content: URL.createObjectURL(e) } : s = { type: "file", fileSize: e.size, fileName: e.name, content: "" }; const i = this._createMessage(s); this.appendMessage(i, !0), this._emitSend(i, () => { this.updateContact({ id: i.toContactId, lastContent: this.lastContentRender(i), lastSendTime: i.sendTime }); }, e); }, _emitPullMessages(e) { this._changeContactLock = !0, this.$emit("pull-messages", this.currentContact, (t = [], s = !1) => { this._addMessage(t, this.currentContactId, 0), this.CacheMessageLoaded.set(this.currentContactId, s), s == !0 && this.$refs.messages.loaded(), this.updateCurrentMessages(), this._changeContactLock = !1, e(s); }, this); }, clearCacheContainer(e) { this.CacheContactContainer.remove(e), this.CacheMenuContainer.remove(e); }, _renderWrapper(e) { return n("div", { style: { width: R(this.width), height: R(this.height) }, ref: "wrapper", class: ["lemon-wrapper", `lemon-wrapper--theme-${this.theme}`, { "lemon-wrapper--simple": this.simple }, this.drawerVisible && "lemon-wrapper--drawer-show"] }, [e]); }, _renderMenu() { const e = this._renderMenuItem(); return f(n("div", { class: "lemon-menu" }, [f(n(d("lemon-avatar"), { onClick: (t) => { this.$emit("menu-avatar-click", t); }, class: "lemon-menu__avatar", src: this.user.avatar }, null), [[g, !this.hideMenuAvatar]]), e.top, this.$slots.menu, n("div", { class: "lemon-menu__bottom" }, [this.$slots["menu-bottom"], e.bottom])]), [[g, !this.hideMenu]]); }, _renderMenuAvatar() { }, _renderMenuItem() { const e = [], t = []; return this.menus.forEach((s) => { let i; const { name: a, title: o, unread: r, render: c, click: l } = s, v = n("div", { class: ["lemon-menu__item", { "lemon-menu__item--active": this.activeSidebar == a }], onClick: () => { A(l, () => { a && this.changeMenu(a); }); }, title: o }, [n(d("lemon-badge"), { count: r }, _e(i = c(s)) ? i : { default: () => [i] })]); s.isBottom === !0 ? t.push(v) : e.push(v); }), { top: e, bottom: t }; }, _renderSidebarMessage() { return this._renderSidebar([m(this.$slots["sidebar-message-top"], null, this), this.lastMessages.map((e) => this._renderContact({ contact: e, timeFormat: this.contactTimeFormat }, () => this.changeContact(e.id), this.$slots["sidebar-message"]))], x, m(this.$slots["sidebar-message-fixedtop"], null, this)); }, _renderContact(e, t, s) { const { click: i, renderContainer: a, id: o } = e.contact, r = () => { A(i, () => { t(), this._customContainerReady(a, this.CacheContactContainer, o); }); }; return f(n(d("lemon-contact"), D({ class: { "lemon-contact--active": this.currentContactId == e.contact.id } }, e, { onClick: r, scopedSlots: { default: s } }), null), [[K("lemon-contextmenu"), this.contactContextmenu, void 0, { contact: !0 }]]); }, _renderSidebarContact() { let e; return this._renderSidebar([m(this.$slots["sidebar-contact-top"], null, this), this.contacts.map((t) => { if (!t.index) return; t.index = t.index.replace(/\[[0-9]*\]/, ""); const s = [t.index !== e && n("p", { class: "lemon-sidebar__label" }, [t.index]), this._renderContact({ contact: t, simple: !0 }, () => { this.changeContact(t.id); }, this.$slots["sidebar-contact"])]; return e = t.index, s; })], w, m(this.$slots["sidebar-contact-fixedtop"], null, this)); }, _renderSidebar(e, t, s) { return f(n("div", { class: "lemon-sidebar", "on-scroll": this._handleSidebarScroll }, [n("div", { class: "lemon-sidebar__fixed-top" }, [s]), n("div", { class: "lemon-sidebar__scroll" }, [e])]), [[g, this.activeSidebar == t]]); }, _renderDrawer() { return this._menuIsMessages() && this.currentContactId ? n("div", { class: "lemon-drawer", ref: "drawer" }, [V(this.currentContact), m(this.$slots.drawer, "", this.currentContact)]) : ""; }, _isContactContainerCache(e) { return e.startsWith("contact#"); }, _renderContainer() { const e = [], t = "lemon-container", s = this.currentContact; let i = !0; for (const a in this.CacheContactContainer.get()) { const o = s.id == a && this.currentIsDefSidebar; o && (i = !o), e.push(f(n("div", { class: t }, [this.CacheContactContainer.get(a)]), [[g, o]])); } for (const a in this.CacheMenuContainer.get()) e.push(f(n("div", { class: t }, [this.CacheMenuContainer.get(a)]), [[g, this.activeSidebar == a && !this.currentIsDefSidebar]])); return e.push(f(n("div", { class: t }, [n("div", { class: "lemon-container__title" }, [m(this.$slots["message-title"], n("div", { class: "lemon-container__displayname" }, [s.displayName]), s)]), n("div", { class: "lemon-vessel" }, [n("div", { class: "lemon-vessel__left" }, [n(d("lemon-messages"), { ref: "messages", "loading-text": this.loadingText, "loadend-text": this.loadendText, "hide-time": this.hideMessageTime, "hide-name": this.hideMessageName, "time-format": this.messageTimeFormat, "reverse-user-id": this.user.id, onReachTop: this._emitPullMessages, messages: this.currentMessages }, null), n(d("lemon-editor"), { ref: "editor", tools: this.editorTools, sendText: this.sendText, sendKey: this.sendKey, wrapKey: this.wrapKey, onSend: this._handleSend, onUpload: this._handleUpload }, null)]), n("div", { class: "lemon-vessel__right" }, [m(this.$slots["message-side"], null, s)])])]), [[g, this._menuIsMessages() && i && s.id]])), e.push(f(n("div", { class: t }, [this.$slots.cover]), [[g, !s.id && this.currentIsDefSidebar]])), e.push(f(n("div", { class: t }, [m(this.$slots["contact-info"], n("div", { class: "lemon-contact-info" }, [n(d("lemon-avatar"), { src: s.avatar, size: 90 }, null), n("h4", null, [s.displayName]), n(d("lemon-button"), { onClick: () => { C(s.lastContent) && this.updateContact({ id: s.id, lastContent: " " }), this.changeContact(s.id, x); } }, { default: () => [j("发送消息")] })]), s)]), [[g, this._menuIsContacts() && i && s.id]])), e; }, _handleSidebarScroll() { T.hide(); }, _addContact(e, t) { const s = { 0: "unshift", 1: "push" }[t]; this.contacts[s](e); }, _addMessage(e, t, s) { const i = { 0: "unshift", 1: "push" }[s]; Array.isArray(e) || (e = [e]), h[t] = h[t] || [], h[t][i](...e); }, /** * 设置最新消息DOM * @param {String} messageType 消息类型 * @param {Function} render 返回消息 vnode */ setLastContentRender(e, t) { F[e] = t; }, lastContentRender(e) { return b(F[e.type]) ? F[e.type].call(this, e) : (console.error(`not found '${e.type}' of the latest message renderer,try to use ‘setLastContentRender()’`), ""); }, /** * 将字符串内的 EmojiItem.name 替换为 img * @param {String} str 被替换的字符串 * @return {String} 替换后的字符串 */ emojiNameToImage(e) { return e.replace(/\[!(\w+)\]/gi, (t, s) => { const i = s; return U[i] ? `<img emoji-name="${s}" src="${U[i]}" />` : `[!${s}]`; }); }, emojiImageToName(e) { return e.replace(/<img emoji-name=\"([^\"]*?)\" [^>]*>/gi, "[!$1]"); }, updateCurrentMessages() { h[this.currentContactId] || (h[this.currentContactId] = []), this.currentMessages = h[this.currentContactId]; }, /** * 将当前聊天窗口滚动到底部 */ messageViewToBottom() { this.$refs.messages.scrollToBottom(); }, /** * 设置联系人的草稿信息 */ setDraft(e, t) { if (C(e) || C(t)) return !1; const s = this.findContact(e); let i = s.lastContent; if (C(s)) return !1; this.CacheDraft.has(e) && (i = this.CacheDraft.get(e).lastContent), this.CacheDraft.set(e, { editorValue: t, lastContent: i }), this.updateContact({ id: e, lastContent: `<span style="color:red;">[草稿]</span><span>${this.lastContentRender({ type: "text", content: t })}</span>` }); }, /** * 清空联系人草稿信息 */ clearDraft(e) { const t = this.CacheDraft.get(e); t && (this.findContact(e).lastContent.indexOf('<span style="color:red;">[草稿]</span>') === 0 && this.updateContact({ id: e, lastContent: t.lastContent }), this.CacheDraft.remove(e)); }, /** * 改变聊天对象 * @param contactId 联系人 id */ async changeContact(e, t) { if (t) this.changeMenu(t); else if (this._changeContactLock || this.currentContactId == e) return !1; if (this.currentContactId) { const i = z(this.getEditorValue()).trim(); i ? (this.setDraft(this.currentContactId, i), this.setEditorValue()) : this.clearDraft(this.currentContactId); } if (this.currentContactId = e, !this.currentContactId) return !1; if (this.$emit("change-contact", this.currentContact, this), b(this.currentContact.renderContainer) || this.activeSidebar == w) return; const s = this.CacheDraft.get(e); s && this.setEditorValue(s.editorValue), this.CacheMessageLoaded.has(e) ? this.$refs.messages.loaded() : this.$refs.messages.resetLoadState(), h[e] ? setTimeout(() => { this.updateCurrentMessages(), this.messageViewToBottom(); }, 0) : (this.updateCurrentMessages(), this._emitPullMessages((i) => { this.messageViewToBottom(); })); }, /** * 删除一条聊天消息 * @param messageId 消息 id * @param contactId 联系人 id */ removeMessage(e) { const t = this.findMessage(e); if (!t) return !1; const s = h[t.toContactId].findIndex(({ id: i }) => i == e); return h[t.toContactId].splice(s, 1), !0; }, /** * 修改聊天一条聊天消息 * @param {Message} data 根据 data.id 查找聊天消息并覆盖传入的值 * @param contactId 联系人 id */ updateMessage(e) { if (!e.id) return !1; let t = this.findMessage(e.id); return t ? (t = Object.assign(t, e, { toContactId: t.toContactId }), !0) : !1; }, /** * 手动更新对话消息 * @param {String} messageId 消息ID,如果为空则更新当前聊天窗口的所有消息 */ forceUpdateMessage(e) { if (!e) this.$refs.messages.$forceUpdate(); else { const t = this.$refs.messages.$refs.message; if (t) { const s = t.find((i) => i.$attrs.message.id == e); s && s.$forceUpdate(); } } }, _customContainerReady(e, t, s) { b(e) && !t.has(s) && t.set(s, e.call(this)); }, /** * 切换左侧按钮 * @param {String} name 按钮 name */ changeMenu(e) { this.$emit("change-menu", e), this.activeSidebar = e; }, /** * 初始化编辑框的 Emoji 表情列表,是 Lemon-editor.initEmoji 的代理方法 * @param {Array<Emoji,EmojiItem>} data emoji 数据 * Emoji = {label: 表情,children: [{name: wx,title: 微笑,src: url}]} 分组 * EmojiItem = {name: wx,title: 微笑,src: url} 无分组 */ initEmoji(e) { let t = []; this.$refs.editor.initEmoji(e), e[0].label ? e.forEach((s) => { t.push(...s.children); }) : t = e, t.forEach(({ name: s, src: i }) => U[s] = i); }, initEditorTools(e) { this.editorTools = e; }, /** * 初始化左侧按钮 * @param {Array<Menu>} data 按钮数据 */ initMenus(e) { const t = [{ name: x, title: "聊天", unread: 0, click: null, render: (i) => n("i", { class: "lemon-icon-message" }, null), isBottom: !1 }, { name: w, title: "通讯录", unread: 0, click: null, render: (i) => n("i", { class: "lemon-icon-addressbook" }, null), isBottom: !1 }]; let s = []; if (Array.isArray(e)) { const i = { messages: 0, contacts: 1 }, a = Object.keys(i); s = e.map((o) => a.includes(o.name) ? { ...t[i[o.name]], ...o, renderContainer: null } : (o.renderContainer && this._customContainerReady(o.renderContainer, this.CacheMenuContainer, o.name), o)); } else s = t; this.menus = s; }, /** * 初始化联系人数据 * @param {Array<Contact>} data 联系人列表 */ initContacts(e) { this.contacts = e, this.sortContacts(); }, /** * 使用 联系人的 index 值进行排序 */ sortContacts() { this.contacts.sort((e, t) => { if (e.index) return e.index.localeCompare(t.index); }); }, appendContact(e) { return C(e.id) || C(e.displayName) ? (console.error("id | displayName cant be empty"), !1) : (this.hasContact(e.id) || this.contacts.push(Object.assign({ id: "", displayName: "", avatar: "", index: "", unread: 0, lastSendTime: "", lastContent: "" }, e)), !0); }, removeContact(e) { const t = this.findContactIndexById(e); return t === -1 ? !1 : (this.contacts.splice(t, 1), this.CacheDraft.remove(e), this.CacheMessageLoaded.remove(e), !0); }, /** * 修改联系人数据 * @param {Contact} data 修改的数据,根据 Contact.id 查找联系人并覆盖传入的值 */ updateContact(e) { const t = e.id; delete e.id; const s = this.findContactIndexById(t); if (s !== -1) { const { unread: i } = e; p(i) && (i.indexOf("+") === 0 || i.indexOf("-") === 0) && (e.unread = parseInt(i) + parseInt(this.contacts[s].unread)); for (const a in e) this.contacts[s][a] = e[a]; } }, /** * 根据 id 查找联系人的索引 * @param contactId 联系人 id * @return {Number} 联系人索引,未找到返回 -1 */ findContactIndexById(e) { return this.contacts.findIndex((t) => t.id == e); }, /** * 根据 id 查找判断是否存在联系人 * @param contactId 联系人 id * @return {Boolean} */ hasContact(e) { return this.findContactIndexById(e) !== -1; }, findMessage(e) { for (const t in h) { const s = h[t].find(({ id: i }) => i == e); if (s) return s; } }, findContact(e) { return this.getContacts().find(({ id: t }) => t == e); }, /** * 返回所有联系人 * @return {Array<Contact>} */ getContacts() { return this.contacts; }, //返回当前聊天窗口联系人信息 getCurrentContact() { return this.currentContact; }, getCurrentMessages() { return this.currentMessages; }, setEditorValue(e = "") { if (!p(e)) return !1; this.$refs.editor.setValue(this.emojiNameToImage(e)); }, getEditorValue() { return this.$refs.editor.getFormatValue(); }, /** * 清空某个联系人的消息,切换到该联系人时会重新触发pull-messages事件 */ clearMessages(e) { return e ? (delete h[e], this.CacheMessageLoaded.remove(e), this.CacheDraft.remove(e)) : (h = {}, this.CacheMessageLoaded.remove(), this.CacheDraft.remove()), !0; }, /** * 返回所有消息 * @return {Object<Contact.id,Message>} */ getMessages(e) { return (e ? h[e] : h) || []; }, changeDrawer(e) { this.drawerVisible = !this.drawerVisible, this.drawerVisible == !0 && this.openDrawer(e); }, // openDrawer(data) { // renderDrawerContent = data || new Function(); // this.drawerVisible = true; // }, openDrawer(e) { V = b(e) ? e : e.render || new Function(); const t = this.$refs.wrapper.clientWidth, s = this.$refs.wrapper.clientHeight; let i = e.width || 200, a = e.height || s, o = e.offsetX || 0, r = e.offsetY || 0; const c = e.position || "right"; p(i) && (i = t * k(i)), p(a) && (a = s * k(a)), p(o) && (o = t * k(o)), p(r) && (r = s * k(r)), this.$refs.drawer.style.width = `${i}px`, this.$refs.drawer.style.height = `${a}px`; let l = 0, v = 0, y = ""; c == "right" ? l = t : c == "rightInside" ? (l = t - i, y = "-15px 0 16px -14px rgba(0,0,0,0.08)") : c == "center" && (l = t / 2 - i / 2, v = s / 2 - a / 2, y = "0 0 20px rgba(0,0,0,0.08)"), l += o, v += r + -1, this.$refs.drawer.style.top = `${v}px`, this.$refs.drawer.style.left = `${l}px`, this.$refs.drawer.style.boxShadow = y, this.drawerVisible = !0; }, closeDrawer() { this.drawerVisible = !1; } } }; const xe = "1.4.2", ve = [ Ce, ce, me, he, ae, ie, ne, P, se, fe, Y, G, W, pe ], be = (e) => { e.directive("LemonContextmenu", T), ve.forEach((t) => { e.component(t.name, t); }); }; typeof window < "u" && window.Vue && be(window.Vue); export { be as install, xe as version };