419 lines
15 KiB
JavaScript
419 lines
15 KiB
JavaScript
// 导入 NodeCrypt 模块(加密功能模块)
|
|
// Import the NodeCrypt module (used for encryption)
|
|
import './NodeCrypt.js';
|
|
|
|
// 从 util.file.js 中导入设置文件发送的函数
|
|
// Import setupFileSend function from util.file.js
|
|
import {
|
|
setupFileSend,
|
|
handleFileMessage,
|
|
downloadFile
|
|
} from './util.file.js';
|
|
|
|
// 从 util.image.js 中导入图片处理功能
|
|
// Import image processing functions from util.image.js
|
|
import {
|
|
setupImagePaste
|
|
} from './util.image.js';
|
|
|
|
// 从 util.emoji.js 中导入设置表情选择器的函数
|
|
// Import setupEmojiPicker function from util.emoji.js
|
|
import {
|
|
setupEmojiPicker
|
|
} from './util.emoji.js';
|
|
|
|
// 从 util.settings.js 中导入设置面板的功能函数
|
|
// Import functions for settings panel from util.settings.js
|
|
import {
|
|
openSettingsPanel, // 打开设置面板 / Open settings panel
|
|
closeSettingsPanel, // 关闭设置面板 / Close settings panel
|
|
initSettings, // 初始化设置 / Initialize settings
|
|
notifyMessage // 通知信息提示 / Display notification message
|
|
} from './util.settings.js';
|
|
import { t, updateStaticTexts } from './util.i18n.js';
|
|
|
|
// 从 util.theme.js 中导入主题功能函数
|
|
// Import theme functions from util.theme.js
|
|
import {
|
|
initTheme // 初始化主题 / Initialize theme
|
|
} from './util.theme.js';
|
|
|
|
// 从 util.dom.js 中导入常用 DOM 操作函数
|
|
// Import common DOM manipulation functions from util.dom.js
|
|
import {
|
|
$, // 简化的 document.querySelector / Simplified selector
|
|
$id, // document.getElementById 的简写 / Shortcut for getElementById
|
|
removeClass // 移除类名 / Remove a CSS class
|
|
} from './util.dom.js';
|
|
|
|
// 从 room.js 中导入房间管理相关变量和函数
|
|
// Import room-related variables and functions from room.js
|
|
import {
|
|
roomsData, // 当前所有房间的数据 / Data of all rooms
|
|
activeRoomIndex, // 当前激活的房间索引 / Index of the active room
|
|
joinRoom // 加入房间的函数 / Function to join a room
|
|
} from './room.js';
|
|
|
|
// 从 chat.js 中导入聊天功能相关的函数
|
|
// Import chat-related functions from chat.js
|
|
import {
|
|
addMsg, // 添加普通消息到聊天窗口 / Add a normal message to chat
|
|
addOtherMsg, // 添加其他用户消息 / Add message from other users
|
|
addSystemMsg, // 添加系统消息 / Add a system message
|
|
setupImagePreview, // 设置图片预览功能 / Setup image preview
|
|
setupInputPlaceholder, // 设置输入框的占位提示 / Setup placeholder for input box
|
|
autoGrowInput // 自动调整输入框高度 / Auto adjust input height
|
|
} from './chat.js';
|
|
|
|
// 从 ui.js 中导入 UI 界面相关的功能
|
|
// Import user interface functions from ui.js
|
|
import { renderUserList, // 渲染用户列表 / Render user list
|
|
renderMainHeader, // 渲染主标题栏 / Render main header
|
|
setupMoreBtnMenu, // 设置更多按钮的下拉菜单 / Setup "more" button menu
|
|
preventSpaceInput, // 防止输入空格 / Prevent space input in form fields
|
|
loginFormHandler, // 登录表单提交处理器 / Login form handler
|
|
openLoginModal, // 打开登录窗口 / Open login modal
|
|
setupTabs, // 设置页面标签切换 / Setup tab switching
|
|
autofillRoomPwd, // 自动填充房间密码 / Autofill room password
|
|
generateLoginForm, // 生成登录表单HTML / Generate login form HTML
|
|
initLoginForm, // 初始化登录表单 / Initialize login form
|
|
initFlipCard // 初始化翻转卡片功能 / Initialize flip card functionality
|
|
} from './ui.js';
|
|
|
|
// 设置全局配置参数
|
|
// Set global configuration parameters
|
|
window.config = {
|
|
wsAddress: `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}`, // WebSocket 服务器地址 / WebSocket server address
|
|
//wsAddress: `wss://crypt.works`,
|
|
debug: true // 是否开启调试模式 / Enable debug mode
|
|
};
|
|
|
|
// 在文档开始加载前就初始化语言设置,防止闪烁
|
|
// Initialize language settings before document starts loading
|
|
initSettings();
|
|
updateStaticTexts();
|
|
|
|
// 把一些函数挂载到 window 对象上供其他模块使用
|
|
// Expose functions to the global window object for accessibility
|
|
window.addSystemMsg = addSystemMsg;
|
|
window.addOtherMsg = addOtherMsg;
|
|
window.joinRoom = joinRoom;
|
|
window.notifyMessage = notifyMessage;
|
|
window.setupEmojiPicker = setupEmojiPicker;
|
|
window.handleFileMessage = handleFileMessage;
|
|
window.downloadFile = downloadFile;
|
|
|
|
// 当 DOM 内容加载完成后执行初始化逻辑
|
|
// Run initialization logic when the DOM content is fully loaded
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
// 移除预加载样式类,允许过渡效果
|
|
// Remove preload class to allow transitions
|
|
setTimeout(() => {
|
|
document.body.classList.remove('preload');
|
|
}, 300);
|
|
|
|
// 初始化登录表单 / Initialize login form
|
|
initLoginForm();
|
|
|
|
const loginForm = $id('login-form'); // 登录表单 / Login form
|
|
|
|
if (loginForm) {
|
|
// 监听登录表单提交事件 / Listen to login form submission
|
|
loginForm.addEventListener('submit', loginFormHandler(null))
|
|
}
|
|
|
|
const joinBtn = $('.join-room'); // 加入房间按钮 / Join room button
|
|
if (joinBtn) {
|
|
joinBtn.onclick = openLoginModal; // 点击打开登录窗口 / Click to open login modal
|
|
}
|
|
// 阻止用户输入用户名、房间名和密码时输入空格
|
|
// Prevent space input for username, room name, and password fields
|
|
preventSpaceInput($id('userName'));
|
|
preventSpaceInput($id('roomName'));
|
|
preventSpaceInput($id('password'));
|
|
|
|
// 初始化翻转卡片功能 / Initialize flip card functionality
|
|
initFlipCard();
|
|
|
|
// 初始化辅助功能和界面设置
|
|
// Initialize autofill, input placeholders, and menus
|
|
autofillRoomPwd(); setupInputPlaceholder();
|
|
setupMoreBtnMenu();
|
|
setupImagePreview(); setupEmojiPicker();
|
|
// 由于我们已经在DOM加载前预先初始化了语言设置,这里不需要重复初始化
|
|
// initSettings();
|
|
// updateStaticTexts(); // 在初始化设置后更新静态文本 / Update static texts after initializing settings
|
|
initTheme(); // 初始化主题 / Initialize theme
|
|
|
|
const settingsBtn = $id('settings-btn'); // 设置按钮 / Settings button
|
|
if (settingsBtn) {
|
|
settingsBtn.onclick = (e) => {
|
|
e.stopPropagation(); // 阻止事件冒泡 / Stop event from bubbling
|
|
openSettingsPanel(); // 打开设置面板 / Open settings panel
|
|
}
|
|
}
|
|
|
|
// 设置返回按钮事件处理 / Settings back button event handler
|
|
const settingsBackBtn = $id('settings-back-btn');
|
|
if (settingsBackBtn) {
|
|
settingsBackBtn.onclick = (e) => {
|
|
e.stopPropagation();
|
|
closeSettingsPanel(); // 关闭设置面板 / Close settings panel
|
|
}
|
|
}
|
|
// 点击其他地方时关闭设置面板 (已移除,因为现在使用侧边栏形式)
|
|
// Close settings panel when clicking outside (removed since we now use sidebar format)
|
|
const input = document.querySelector('.input-message-input'); // 消息输入框 / Message input box
|
|
|
|
// 设置图片粘贴功能
|
|
// Setup image paste functionality
|
|
const imagePasteHandler = setupImagePaste('.input-message-input');
|
|
|
|
if (input) {
|
|
input.focus(); // 自动聚焦 / Auto focus
|
|
input.addEventListener('keydown', (e) => {
|
|
// 按下 Enter 键并且不按 Shift,表示发送消息
|
|
// Pressing Enter (without Shift) sends the message
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
sendMessage();
|
|
}
|
|
});
|
|
}
|
|
|
|
// 发送消息的统一函数
|
|
// Unified function to send messages
|
|
function sendMessage() {
|
|
const text = input.innerText.trim(); // 获取输入的文本 / Get input text
|
|
const images = imagePasteHandler ? imagePasteHandler.getCurrentImages() : []; // 获取所有图片
|
|
|
|
if (!text && images.length === 0) return; // 如果没有文本且没有图片,则不发送
|
|
const rd = roomsData[activeRoomIndex]; // 当前房间数据 / Current room data
|
|
|
|
if (rd && rd.chat) {
|
|
if (images.length > 0) {
|
|
// 发送包含图片的消息 (支持多图和文字合并)
|
|
// Send message with images (supports multiple images and text combined)
|
|
const messageContent = {
|
|
text: text || '', // 包含文字内容,如果有的话
|
|
images: images // 包含所有图片数据
|
|
};
|
|
|
|
if (rd.privateChatTargetId) {
|
|
// 私聊图片消息加密并发送
|
|
// Encrypt and send private image message
|
|
const targetClient = rd.chat.channel[rd.privateChatTargetId];
|
|
if (targetClient && targetClient.shared) {
|
|
const clientMessagePayload = {
|
|
a: 'm',
|
|
t: 'image_private',
|
|
d: messageContent
|
|
};
|
|
const encryptedClientMessage = rd.chat.encryptClientMessage(clientMessagePayload, targetClient.shared);
|
|
const serverRelayPayload = {
|
|
a: 'c',
|
|
p: encryptedClientMessage,
|
|
c: rd.privateChatTargetId
|
|
};
|
|
const encryptedMessageForServer = rd.chat.encryptServerMessage(serverRelayPayload, rd.chat.serverShared); rd.chat.sendMessage(encryptedMessageForServer);
|
|
addMsg(messageContent, false, 'image_private');
|
|
} else {
|
|
addSystemMsg(`${t('system.private_message_failed', 'Cannot send private message to')} ${rd.privateChatTargetName}. ${t('system.user_not_connected', 'User might not be fully connected.')}`)
|
|
}
|
|
} else {
|
|
// 公共频道图片消息发送
|
|
// Send image message to public channel
|
|
rd.chat.sendChannelMessage('image', messageContent);
|
|
addMsg(messageContent, false, 'image');
|
|
}
|
|
|
|
imagePasteHandler.clearImages(); // 清除所有图片预览
|
|
} else if (text) {
|
|
// 发送纯文本消息
|
|
// Send text-only message
|
|
if (rd.privateChatTargetId) {
|
|
// 私聊消息加密并发送
|
|
// Encrypt and send private message
|
|
const targetClient = rd.chat.channel[rd.privateChatTargetId];
|
|
if (targetClient && targetClient.shared) {
|
|
const clientMessagePayload = {
|
|
a: 'm',
|
|
t: 'text_private',
|
|
d: text
|
|
};
|
|
const encryptedClientMessage = rd.chat.encryptClientMessage(clientMessagePayload, targetClient.shared);
|
|
const serverRelayPayload = {
|
|
a: 'c',
|
|
p: encryptedClientMessage,
|
|
c: rd.privateChatTargetId
|
|
};
|
|
const encryptedMessageForServer = rd.chat.encryptServerMessage(serverRelayPayload, rd.chat.serverShared);
|
|
rd.chat.sendMessage(encryptedMessageForServer); addMsg(text, false, 'text_private');
|
|
} else {
|
|
addSystemMsg(`${t('system.private_message_failed', 'Cannot send private message to')} ${rd.privateChatTargetName}. ${t('system.user_not_connected', 'User might not be fully connected.')}`)
|
|
}
|
|
} else {
|
|
// 公共频道消息发送
|
|
// Send public message
|
|
rd.chat.sendChannelMessage('text', text);
|
|
addMsg(text); }
|
|
}
|
|
|
|
// 清空输入框并触发 input 事件
|
|
// Clear input and trigger input event
|
|
input.innerHTML = ''; // 清空输入框内容 / Clear input field content
|
|
if (imagePasteHandler && typeof imagePasteHandler.refreshPlaceholder === 'function') {
|
|
imagePasteHandler.refreshPlaceholder(); // 更新 placeholder 状态
|
|
}
|
|
autoGrowInput(); // 调整输入框高度
|
|
}
|
|
}
|
|
|
|
// 为发送按钮添加点击事件
|
|
// Add click event for send button
|
|
const sendButton = document.querySelector('.send-message-btn');
|
|
if (sendButton) {
|
|
sendButton.addEventListener('click', sendMessage);
|
|
}
|
|
|
|
// 设置发送文件功能
|
|
// Setup file sending functionality
|
|
setupFileSend({
|
|
inputSelector: '.input-message-input', // 消息输入框选择器 / Message input selector
|
|
attachBtnSelector: '.chat-attach-btn', // 附件按钮选择器 / Attach button selector
|
|
fileInputSelector: '.new-message-wrapper input[type="file"]', // 文件输入框选择器 / File input selector
|
|
onSend: (message) => {
|
|
const rd = roomsData[activeRoomIndex];
|
|
if (rd && rd.chat) {
|
|
const userName = rd.myUserName || '';
|
|
const msgWithUser = { ...message, userName };
|
|
if (rd.privateChatTargetId) {
|
|
// 私聊文件加密并发送
|
|
// Encrypt and send private file message
|
|
const targetClient = rd.chat.channel[rd.privateChatTargetId];
|
|
if (targetClient && targetClient.shared) {
|
|
const clientMessagePayload = {
|
|
a: 'm',
|
|
t: msgWithUser.type + '_private',
|
|
d: msgWithUser
|
|
};
|
|
const encryptedClientMessage = rd.chat.encryptClientMessage(clientMessagePayload, targetClient.shared);
|
|
const serverRelayPayload = {
|
|
a: 'c',
|
|
p: encryptedClientMessage,
|
|
c: rd.privateChatTargetId
|
|
};
|
|
const encryptedMessageForServer = rd.chat.encryptServerMessage(serverRelayPayload, rd.chat.serverShared);
|
|
rd.chat.sendMessage(encryptedMessageForServer);
|
|
|
|
// 添加到自己的聊天记录
|
|
if (msgWithUser.type === 'file_start') {
|
|
addMsg(msgWithUser, false, 'file_private');
|
|
} } else {
|
|
addSystemMsg(`${t('system.private_file_failed', 'Cannot send private file to')} ${rd.privateChatTargetName}. ${t('system.user_not_connected', 'User might not be fully connected.')}`)
|
|
}
|
|
} else {
|
|
// 公共频道文件发送
|
|
// Send file to public channel
|
|
rd.chat.sendChannelMessage(msgWithUser.type, msgWithUser);
|
|
|
|
// 添加到自己的聊天记录
|
|
if (msgWithUser.type === 'file_start') {
|
|
addMsg(msgWithUser, false, 'file');
|
|
}
|
|
}
|
|
} }
|
|
});
|
|
|
|
|
|
// 判断是否为移动端
|
|
// Check if the device is mobile
|
|
const isMobile = () => window.innerWidth <= 768;
|
|
|
|
// 渲染主界面元素
|
|
// Render main UI elements
|
|
renderMainHeader();
|
|
renderUserList();
|
|
setupTabs();
|
|
|
|
const roomList = $id('room-list');
|
|
const sidebar = $id('sidebar');
|
|
const rightbar = $id('rightbar');
|
|
const sidebarMask = $id('mobile-sidebar-mask');
|
|
const rightbarMask = $id('mobile-rightbar-mask');
|
|
|
|
// 在移动端点击房间列表后关闭侧边栏
|
|
// On mobile, clicking room list closes sidebar
|
|
if (roomList) {
|
|
roomList.addEventListener('click', () => {
|
|
if (isMobile()) {
|
|
sidebar?.classList.remove('mobile-open');
|
|
sidebarMask?.classList.remove('active');
|
|
}
|
|
});
|
|
}
|
|
|
|
// 在移动端点击成员标签后关闭右侧面板
|
|
// On mobile, clicking member tabs closes right panel
|
|
const memberTabs = $id('member-tabs');
|
|
if (memberTabs) {
|
|
memberTabs.addEventListener('click', () => {
|
|
if (isMobile()) {
|
|
removeClass(rightbar, 'mobile-open');
|
|
removeClass(rightbarMask, 'active');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Listen for language change events
|
|
// 监听语言切换事件
|
|
window.addEventListener('languageChange', (event) => {
|
|
updateStaticTexts();
|
|
});
|
|
|
|
// 全局拖拽文件自动打开附件功能
|
|
// Global drag file to auto trigger attach button
|
|
let dragCounter = 0;
|
|
let hasTriggeredAttach = false;
|
|
|
|
// 监听文件上传模态框关闭事件,重置拖拽标志位
|
|
window.addEventListener('fileUploadModalClosed', () => {
|
|
hasTriggeredAttach = false;
|
|
});
|
|
|
|
document.addEventListener('dragenter', (e) => {
|
|
dragCounter++;
|
|
if (!hasTriggeredAttach && e.dataTransfer.items.length > 0) {
|
|
// 检查是否有文件
|
|
for (let item of e.dataTransfer.items) {
|
|
if (item.kind === 'file') {
|
|
// 自动点击附件按钮
|
|
const attachBtn = document.querySelector('.chat-attach-btn');
|
|
if (attachBtn) {
|
|
attachBtn.click();
|
|
hasTriggeredAttach = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
document.addEventListener('dragleave', (e) => {
|
|
dragCounter--;
|
|
if (dragCounter === 0) {
|
|
hasTriggeredAttach = false;
|
|
}
|
|
});
|
|
|
|
document.addEventListener('dragover', (e) => {
|
|
e.preventDefault();
|
|
});
|
|
|
|
document.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
dragCounter = 0;
|
|
hasTriggeredAttach = false;
|
|
});
|