Files

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;
});