@wiajs/ui
Version:
wia app ui packages
500 lines (499 loc) • 19.5 kB
JavaScript
/** @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);
}
}