UNPKG

@wiajs/ui

Version:

wia app ui packages

500 lines (499 loc) 19.5 kB
/** @jsx-x jsx */ /** @jsxImportSource @wiajs/core */ import { jsx as _jsx, jsxs as _jsxs } from "@wiajs/core/jsx-runtime"; import { Utils, Event } from '@wiajs/core'; const def = { selector: '.messages', autoLayout: true, messages: [], newMessagesFirst: false, scrollMessages: true, scrollMessagesOnEdge: true, firstMessageRule: undefined, lastMessageRule: undefined, tailMessageRule: undefined, sameNameMessageRule: undefined, sameHeaderMessageRule: undefined, sameFooterMessageRule: undefined, sameAvatarMessageRule: undefined, customClassMessageRule: undefined, renderMessage: undefined }; export default class Messages extends Event { constructor(page, opts = {}){ const opt = { ...def, ...opts }; super(opt, [ page ]); const _ = this; _.opt = opt; _.params = opt; // 消息容器 const $el = opt.el || page.view.def.selector; if ($el.length === 0) return _; // 已创建实例,直接返回 if ($el[0].wiaMessages) return $el[0].wiaMessages; $el[0].wiaMessages = _; const $pageContentEl = $el.closest('.page-content').eq(0); Utils.extend(_, { messages: _.params.messages, $el, el: $el[0], $pageContentEl, pageContentEl: $pageContentEl[0] }); // Init _.init(); // return m; } // eslint-disable-next-line getMessageData(messageEl) { const $messageEl = $(messageEl); const data = { name: $messageEl.find('.message-name')?.html(), header: $messageEl.find('.message-header')?.html(), textHeader: $messageEl.find('.message-text-header')?.html(), textFooter: $messageEl.find('.message-text-footer')?.html(), footer: $messageEl.find('.message-footer')?.html(), isTitle: $messageEl.hasClass('messages-title'), type: $messageEl.hasClass('message-sent') ? 'sent' : 'received', text: $messageEl.find('.message-text')?.html(), image: $messageEl.find('.message-image')?.html(), imageSrc: $messageEl.find('.message-image img')?.attr('src'), typing: $messageEl.hasClass('message-typing') }; if (data.isTitle) data.text = $messageEl.html(); if (data.text && data.textHeader) data.text = data.text.replace(`<div class="message-text-header">${data.textHeader}</div>`, ''); if (data.text && data.textFooter) data.text = data.text.replace(`<div class="message-text-footer">${data.textFooter}</div>`, ''); let avatar = $messageEl.find('.message-avatar').css('background-image'); if (avatar === 'none' || avatar === '') avatar = undefined; if (avatar && typeof avatar === 'string') avatar = avatar.replace('url(', '').replace(')', '').replace(/"/g, '').replace(/'/g, ''); else avatar = undefined; data.avatar = avatar; return data; } getMessagesData() { const _ = this; const data = []; _.$el.find('.message, .messages-title').forEach((messageEl)=>{ data.push(_.getMessageData(messageEl)); }); return data; } /** * 根据消息类型,生成消息html文本 * @param {*} messageToRender * @returns */ renderMessage(messageToRender) { const m = this; const message = Utils.extend({ type: 'sent', attrs: {} }, messageToRender); if (m.params.renderMessage) { return m.params.renderMessage.call(m, message); } if (message.isTitle) { return `<div class="messages-title">${message.text}</div>`; } return /*#__PURE__*/ _jsxs("div", { class: `message message-${message.type} ${message.isTyping ? 'message-typing' : ''} ${message.cssClass || ''}`, ...message.attrs, children: [ message.avatar && /*#__PURE__*/ _jsx("div", { class: "message-avatar", style: `background-image:url(${message.avatar})` }), /*#__PURE__*/ _jsxs("div", { class: "message-content", children: [ message.name && /*#__PURE__*/ _jsx("div", { class: "message-name", children: message.name }), message.header && /*#__PURE__*/ _jsx("div", { class: "message-header", children: message.header }), /*#__PURE__*/ _jsxs("div", { class: "message-bubble", children: [ message.textHeader && /*#__PURE__*/ _jsx("div", { class: "message-text-header", children: message.textHeader }), message.image && /*#__PURE__*/ _jsx("div", { class: "message-image", children: message.image }), message.imageSrc && !message.image && /*#__PURE__*/ _jsx("div", { class: "message-image", children: /*#__PURE__*/ _jsx("img", { src: message.imageSrc }) }), (message.text || message.isTyping) && /*#__PURE__*/ _jsxs("div", { class: "message-text", children: [ message.text || '', message.isTyping && /*#__PURE__*/ _jsxs("div", { class: "message-typing-indicator", children: [ /*#__PURE__*/ _jsx("div", {}), /*#__PURE__*/ _jsx("div", {}), /*#__PURE__*/ _jsx("div", {}) ] }) ] }), message.textFooter && /*#__PURE__*/ _jsx("div", { class: "message-text-footer", children: message.textFooter }) ] }), message.footer && /*#__PURE__*/ _jsx("div", { class: "message-footer", children: message.footer }) ] }) ] }); } renderMessages(messagesToRender = this.messages, method = this.params.newMessagesFirst ? 'prepend' : 'append') { const m = this; const html = messagesToRender.map((message)=>m.renderMessage(message)).join(''); m.$el[method](html); } isFirstMessage(...args) { const m = this; if (m.params.firstMessageRule) return m.params.firstMessageRule(...args); return false; } isLastMessage(...args) { const m = this; if (m.params.lastMessageRule) return m.params.lastMessageRule(...args); return false; } isTailMessage(...args) { const m = this; if (m.params.tailMessageRule) return m.params.tailMessageRule(...args); return false; } isSameNameMessage(...args) { const m = this; if (m.params.sameNameMessageRule) return m.params.sameNameMessageRule(...args); return false; } isSameHeaderMessage(...args) { const m = this; if (m.params.sameHeaderMessageRule) return m.params.sameHeaderMessageRule(...args); return false; } isSameFooterMessage(...args) { const m = this; if (m.params.sameFooterMessageRule) return m.params.sameFooterMessageRule(...args); return false; } isSameAvatarMessage(...args) { const m = this; if (m.params.sameAvatarMessageRule) return m.params.sameAvatarMessageRule(...args); return false; } isCustomClassMessage(...args) { const m = this; if (m.params.customClassMessageRule) return m.params.customClassMessageRule(...args); return undefined; } layout() { const m = this; m.$el.find('.message, .messages-title').each((index, messageEl)=>{ const $messageEl = $(messageEl); if (!m.messages) { m.messages = m.getMessagesData(); } const classes = []; const message = m.messages[index]; const previousMessage = m.messages[index - 1]; const nextMessage = m.messages[index + 1]; if (m.isFirstMessage(message, previousMessage, nextMessage)) { classes.push('message-first'); } if (m.isLastMessage(message, previousMessage, nextMessage)) { classes.push('message-last'); } if (m.isTailMessage(message, previousMessage, nextMessage)) { classes.push('message-tail'); } if (m.isSameNameMessage(message, previousMessage, nextMessage)) { classes.push('message-same-name'); } if (m.isSameHeaderMessage(message, previousMessage, nextMessage)) { classes.push('message-same-header'); } if (m.isSameFooterMessage(message, previousMessage, nextMessage)) { classes.push('message-same-footer'); } if (m.isSameAvatarMessage(message, previousMessage, nextMessage)) { classes.push('message-same-avatar'); } let customMessageClasses = m.isCustomClassMessage(message, previousMessage, nextMessage); if (customMessageClasses && customMessageClasses.length) { if (typeof customMessageClasses === 'string') { customMessageClasses = customMessageClasses.split(' '); } customMessageClasses.forEach((customClass)=>{ classes.push(customClass); }); } $messageEl.removeClass(// eslint-disable-next-line 'message-first message-last message-tail message-same-name message-same-header message-same-footer message-same-avatar'); classes.forEach((className)=>{ $messageEl.addClass(className); }); }); } clear() { const m = this; m.messages = []; m.$el.html(''); } removeMessage(messageToRemove, layout = true) { const m = this; // Index or El let index; let $el; if (typeof messageToRemove === 'number') { index = messageToRemove; $el = m.$el.find('.message, .messages-title').eq(index); } else if (m.messages && m.messages.indexOf(messageToRemove) >= 0) { index = m.messages.indexOf(messageToRemove); $el = m.$el.children().eq(index); } else { $el = $(messageToRemove); index = $el.index(); } if ($el.length === 0) { return m; } $el.remove(); m.messages.splice(index, 1); if (m.params.autoLayout && layout) m.layout(); return m; } removeMessages(messagesToRemove, layout = true) { const m = this; if (Array.isArray(messagesToRemove)) { const messagesToRemoveEls = []; messagesToRemove.forEach((messageToRemoveIndex)=>{ messagesToRemoveEls.push(m.$el.find('.message, .messages-title').eq(messageToRemoveIndex)); }); messagesToRemoveEls.forEach((messageToRemove)=>{ m.removeMessage(messageToRemove, false); }); } else { $(messagesToRemove).each((index, messageToRemove)=>{ m.removeMessage(messageToRemove, false); }); } if (m.params.autoLayout && layout) m.layout(); return m; } /** * 头部/尾部添加新消息 * @param {...any} args 消息体 * @returns */ addMessage(...args) { const m = this; let messageToAdd; let animate; let method; if (typeof args[1] === 'boolean') { ; [messageToAdd, animate, method] = args; } else { ; [messageToAdd, method, animate] = args; } if (typeof animate === 'undefined') { animate = true; } if (typeof method === 'undefined') { method = m.params.newMessagesFirst ? 'prepend' : 'append'; } return m.addMessages([ messageToAdd ], animate, method); } setScrollData() { const m = this; // Define scroll positions before new messages added const scrollHeightBefore = m.pageContentEl.scrollHeight; const heightBefore = m.pageContentEl.offsetHeight; const scrollBefore = m.pageContentEl.scrollTop; m.scrollData = { scrollHeightBefore, heightBefore, scrollBefore }; return { scrollHeightBefore, heightBefore, scrollBefore }; } addMessages(...args) { const _ = this; let messagesToAdd; let animate; let method; if (typeof args[1] === 'boolean') { ; [messagesToAdd, animate, method] = args; } else { ; [messagesToAdd, method, animate] = args; } if (typeof animate === 'undefined') { animate = true; } if (typeof method === 'undefined') { method = _.params.newMessagesFirst ? 'prepend' : 'append'; } const { scrollHeightBefore, scrollBefore } = _.setScrollData(); // Add message to DOM and data let messagesHTML = ''; const typingMessage = _.messages.filter((el)=>el.isTyping)[0]; messagesToAdd.forEach((messageToAdd)=>{ if (typingMessage) { if (method === 'append') { _.messages.splice(_.messages.indexOf(typingMessage), 0, messageToAdd); } else { _.messages.splice(_.messages.indexOf(typingMessage) + 1, 0, messageToAdd); } } else { _.messages[method === 'append' ? 'push' : 'unshift'](messageToAdd); } messagesHTML += _.renderMessage(messageToAdd); }); const $messagesEls = $(messagesHTML); if (animate) { if (method === 'append' && !_.params.newMessagesFirst) { $messagesEls.addClass('message-appear-from-bottom'); } if (method === 'prepend' && _.params.newMessagesFirst) { $messagesEls.addClass('message-appear-from-top'); } } if (typingMessage) { if (method === 'append') { $messagesEls.insertBefore(_.$el.find('.message-typing')); } else { $messagesEls.insertAfter(_.$el.find('.message-typing')); } } else { _.$el[method]($messagesEls); } // Layout if (_.params.autoLayout) _.layout(); if (method === 'prepend' && !typingMessage) { _.pageContentEl.scrollTop = scrollBefore + (_.pageContentEl.scrollHeight - scrollHeightBefore); } if (_.params.scrollMessages && (method === 'append' && !_.params.newMessagesFirst || method === 'prepend' && _.params.newMessagesFirst && !typingMessage)) { _.scrollWithEdgeCheck(animate); } return _; } showTyping(message = {}) { const m = this; const typingMessage = m.messages.filter((el)=>el.isTyping)[0]; if (typingMessage) { m.removeMessage(m.messages.indexOf(typingMessage)); } m.addMessage(Utils.extend({ type: 'received', isTyping: true }, message)); return m; } hideTyping() { const m = this; let typingMessageIndex; let typingFound; m.messages.forEach((message, index)=>{ if (message.isTyping) typingMessageIndex = index; }); if (typeof typingMessageIndex !== 'undefined') { if (m.$el.find('.message').eq(typingMessageIndex).hasClass('message-typing')) { typingFound = true; m.removeMessage(typingMessageIndex); } } if (!typingFound) { const $typingMessageEl = m.$el.find('.message-typing'); if ($typingMessageEl.length) { m.removeMessage($typingMessageEl); } } return m; } /** * 滚动边缘检查 * @param {*} animate */ scrollWithEdgeCheck(animate) { const m = this; const { scrollBefore, scrollHeightBefore, heightBefore } = m.scrollData; if (m.params.scrollMessagesOnEdge) { let onEdge = false; if (m.params.newMessagesFirst && scrollBefore === 0) { onEdge = true; } if (!m.params.newMessagesFirst && scrollBefore - (scrollHeightBefore - heightBefore) >= -10) { onEdge = true; } if (onEdge) m.scroll(animate ? undefined : 0); } else { m.scroll(animate ? undefined : 0); } } /** * 滚动屏幕 * @param {*} duration 默认300毫秒内完成滚动, * @param {*} scrollTop 滚动px值 * @returns */ scroll(duration = 300, scrollTop) { const m = this; const currentScroll = m.pageContentEl.scrollTop; let newScrollTop; if (typeof scrollTop !== 'undefined') newScrollTop = scrollTop; else { newScrollTop = m.params.newMessagesFirst ? 0 : m.pageContentEl.scrollHeight - m.pageContentEl.offsetHeight; if (newScrollTop === currentScroll) return m; } m.$pageContentEl.scrollTop(newScrollTop, duration); return m; } /** * 初始化 */ init() { const _ = this; if (!_.messages || _.messages.length === 0) _.messages = _.getMessagesData(); if (_.params.messages && _.params.messages.length) _.renderMessages(); if (_.params.autoLayout) _.layout(); if (_.params.scrollMessages) _.scroll(0); } /** * 解构函数 */ destroy() { const m = this; m.emit('local::beforeDestroy messagesBeforeDestroy', m); m.$el.trigger('messages:beforedestroy'); if (m.$el[0]) { m.$el[0].wiaMessages = null; delete m.$el[0].wiaMessages; } Utils.deleteProps(m); } }