// Import necessary modules
// 导入必要的模块
import {
createAvatarSVG
} from './util.avatar.js';
import {
roomsData,
activeRoomIndex
} from './room.js';
import {
escapeHTML,
textToHTML
} from './util.string.js';
import {
$,
$id,
createElement,
on,
off,
addClass,
removeClass
} from './util.dom.js';
import {
formatFileSize
} from './util.file.js';
import {
t
} from './util.i18n.js';
// Render the chat area
// 渲染聊天区域
export function renderChatArea() {
const chatArea = $id('chat-area');
if (!chatArea) return;
if (activeRoomIndex < 0 || !roomsData[activeRoomIndex]) {
chatArea.innerHTML = '';
return
}
chatArea.innerHTML = '';
roomsData[activeRoomIndex].messages.forEach(m => {
if (m.type === 'me') addMsg(m.text, true, m.msgType || 'text', m.timestamp);
else if (m.type === 'system') addSystemMsg(m.text, true, m.timestamp);
else addOtherMsg(m.text, m.userName, m.avatar, true, m.msgType || 'text', m.timestamp)
})
}
// Add a message to the chat area
// 添加消息到聊天区域
export function addMsg(text, isHistory = false, msgType = 'text', timestamp = null) {
let ts = isHistory ? timestamp : (timestamp || Date.now());
if (!ts) return;
if (!isHistory && activeRoomIndex >= 0) {
roomsData[activeRoomIndex].messages.push({
type: 'me',
text,
msgType,
timestamp: ts
})
} const chatArea = $id('chat-area');
if (!chatArea) return;
let className = 'bubble me' + (msgType.includes('_private') ? ' private-message' : '');
const date = new Date(ts);
const time = date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0'); let contentHtml = ''; if (msgType === 'image' || msgType === 'image_private') {
// Handle image messages (can contain both text and images)
if (typeof text === 'object' && text.images && Array.isArray(text.images)) {
// New multi-image format: {text: "", images: ["data:image...", "data:image..."]}
const messageText = text.text ? textToHTML(text.text) : '';
const imageElements = text.images.map(imgData => {
const safeImgSrc = escapeHTML(imgData).replace(/javascript:/gi, '');
return `
`;
}).join('');
if (messageText && imageElements) {
// Mixed content: text + images
contentHtml = `
${messageText}
${imageElements}
`;
} else if (imageElements) {
// Images only
contentHtml = imageElements;
} else {
// Fallback to text only
contentHtml = messageText;
}
} else if (typeof text === 'object' && text.image) {
// Legacy single image format: {text: "", image: "data:image..."}
const safeImgSrc = escapeHTML(text.image).replace(/javascript:/gi, '');
const messageText = text.text ? textToHTML(text.text) : '';
if (messageText) {
// Mixed content: text + image
contentHtml = `
${messageText}
`;
} else {
// Image only
contentHtml = `
`;
}
} else {
// Legacy format: plain image data URL
const safeImgSrc = escapeHTML(text).replace(/javascript:/gi, '');
contentHtml = `
`;
}
} else if (msgType === 'file' || msgType === 'file_private') {
// Handle file messages
contentHtml = renderFileMessage(text, true);
// Add file-bubble class for special timestamp positioning
className += ' file-bubble';
} else {
contentHtml = textToHTML(text)
}
const div = createElement('div', {
class: className
}, `${contentHtml}${time}`);
chatArea.appendChild(div);
chatArea.scrollTop = chatArea.scrollHeight
}
// Add a message from another user to the chat area
// 添加来自其他用户的消息到聊天区域
export function addOtherMsg(msg, userName = '', avatar = '', isHistory = false, msgType = 'text', timestamp = null) {
if (!userName && activeRoomIndex >= 0) {
const rd = roomsData[activeRoomIndex];
// 优先使用文件消息自带的 userName 字段
if (msg && msg.userName) {
userName = msg.userName;
} else if (rd && msg && msg.clientId && rd.userMap[msg.clientId]) {
userName = rd.userMap[msg.clientId].userName || rd.userMap[msg.clientId].username || rd.userMap[msg.clientId].name || t('ui.anonymous', 'Anonymous')
}
}
userName = userName || t('ui.anonymous', 'Anonymous');
let ts = isHistory ? timestamp : (timestamp || Date.now());
if (!ts) return;
const chatArea = $id('chat-area');
if (!chatArea) return;
const bubbleWrap = createElement('div', {
class: 'bubble-other-wrap'
}); let contentHtml = ''; if (msgType === 'image' || msgType === 'image_private') {
// Handle image messages (can contain both text and images)
if (typeof msg === 'object' && msg.images && Array.isArray(msg.images)) {
// New multi-image format: {text: "", images: ["data:image...", "data:image..."]}
const messageText = msg.text ? textToHTML(msg.text) : '';
const imageElements = msg.images.map(imgData => {
const safeImgSrc = escapeHTML(imgData).replace(/javascript:/gi, '');
return `
`;
}).join('');
if (messageText && imageElements) {
// Mixed content: text + images
contentHtml = `
${messageText}
${imageElements}
`;
} else if (imageElements) {
// Images only
contentHtml = imageElements;
} else {
// Fallback to text only
contentHtml = messageText;
}
} else if (typeof msg === 'object' && msg.image) {
// Legacy single image format: {text: "", image: "data:image..."}
const safeImgSrc = escapeHTML(msg.image).replace(/javascript:/gi, '');
const messageText = msg.text ? textToHTML(msg.text) : '';
if (messageText) {
// Mixed content: text + image
contentHtml = `
${messageText}
`;
} else {
// Image only
contentHtml = `
`;
}
} else {
// Legacy format: plain image data URL
const safeImgSrc = escapeHTML(msg).replace(/javascript:/gi, '');
contentHtml = `
`;
}
} else if (msgType === 'file' || msgType === 'file_private') {
// Handle file messages
contentHtml = renderFileMessage(msg, false); } else {
contentHtml = textToHTML(msg)
}
const safeUserName = escapeHTML(userName);
const date = new Date(ts);
const time = date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0');
let bubbleClasses = 'bubble other';
if (msgType && msgType.includes('_private')) {
bubbleClasses += ' private-message'
}
if (msgType === 'file' || msgType === 'file_private') {
bubbleClasses += ' file-bubble';
}
bubbleWrap.innerHTML = `${safeUserName}
${contentHtml}${time} `;
const svg = createAvatarSVG(userName);
const avatarEl = $('.avatar', bubbleWrap);
if (avatarEl) {
const cleanSvg = svg.replace(/