微调
This commit is contained in:
1 parent
feca67635c
commit
1bd4a0ba73
8 files changed
+336
-126
No files matched your search
+32
-12
@@ -454,6 +454,7 @@ body {
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
transition: transform 0.2s ease, color 0.2s ease;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.chat-attach-btn-abs:hover {
|
||||
@@ -522,30 +523,49 @@ body {
|
||||
padding: 0.7em 0.7em;
|
||||
border-radius: 50px;
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
|
||||
margin: 0px 0px 3px 10px;
|
||||
transition: all 0.2s ease;
|
||||
margin: 0px 0px 3px 7px;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 10;
|
||||
}
|
||||
.send-message-btn:hover {
|
||||
background: #fff;
|
||||
transition: all 0.6s;
|
||||
transform: translateY(-2px);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.send-message-btn:active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.15);
|
||||
transition: all 0.1s;
|
||||
transform: scale(0.95) translateY(0px);
|
||||
box-shadow: 0 0.3rem 0.8rem rgba(3, 169, 244, 0.2);
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
.send-message-btn .text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
fill: #fff;
|
||||
transition: all 0.2s;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.send-message-btn:hover .text {
|
||||
fill: rgb(3, 169, 244);
|
||||
transition: all 0.6s;
|
||||
transform: scale(1.05);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.send-message-btn:active .text {
|
||||
fill: #fff;
|
||||
transition: all 0.1s;
|
||||
transform: scale(0.95);
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
/* 确保发送按钮的点击区域不被其他元素遮挡 */
|
||||
.send-message-btn {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 确保发送按钮在所有父容器中都有足够的层级 */
|
||||
.chat-input-wrapper .send-message-btn {
|
||||
position: relative;
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
/* 防止任何绝对定位的元素遮挡发送按钮 */
|
||||
.new-message-wrapper {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
+15
-6
@@ -25,7 +25,7 @@ flex-direction: column;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 2px 0 8px #e0e0e0a0;
|
||||
transition: width 0.3s ease;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar-user {
|
||||
@@ -58,16 +58,25 @@ flex-direction: column;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* New settings sidebar styles */
|
||||
/* Settings sidebar overlay styles */
|
||||
.settings-sidebar {
|
||||
width: 250px;
|
||||
background: #fff;
|
||||
border-right: 1px solid #e0e0e0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 2px 0 8px #e0e0e0a0;
|
||||
transition: width 0.3s ease;
|
||||
position: relative;
|
||||
box-shadow: 2px 0 10px #e0e0e0a0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
z-index: 1000;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.settings-sidebar.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
@@ -544,7 +553,7 @@ flex-direction: column;
|
||||
.chat-area {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 32px 24px 24px 24px;
|
||||
padding: 15px 15px 15px 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: none;
|
||||
|
||||
+60
-20
@@ -7,18 +7,17 @@
|
||||
<link rel="stylesheet" href="css/body.css">
|
||||
<link rel="stylesheet" href="css/phone.css">
|
||||
<link rel="stylesheet" href="css/form-animation.css">
|
||||
<link rel="icon" type="image/svg+xml" href="assets/favicon.svg">
|
||||
<meta name="description" content="NodeCrypt - 真正的端到端加密聊天系统,无数据库,所有消息本地加密,服务器仅做加密数据中转,支持 Cloudflare Workers、Docker、自托管和本地开发。">
|
||||
<meta name="keywords" content="端到端加密, 安全, 聊天, WebSocket, Cloudflare Workers, JavaScript, E2EE, 匿名通信, AES, ECDH, RSA, ChaCha20, 安全, 开源, NodeCrypt, shuaiplus">
|
||||
<link rel="icon" type="image/svg+xml" href="assets/favicon.svg"> <meta name="description" content="NodeCrypt - True end-to-end encrypted chat system, no database, all messages encrypted locally, server only relays encrypted data, supports Cloudflare Workers, Docker, self-hosting and local development.">
|
||||
<meta name="keywords" content="end-to-end encryption, security, chat, WebSocket, Cloudflare Workers, JavaScript, E2EE, anonymous communication, AES, ECDH, RSA, ChaCha20, security, open source, NodeCrypt, shuaiplus">
|
||||
<meta name="author" content="shuaiplus">
|
||||
<meta property="og:title" content="NodeCrypt - 端到端加密聊天系统">
|
||||
<meta property="og:description" content="NodeCrypt 是一个零知识、端到端加密的开源聊天系统,所有加密解密均在客户端本地完成,服务器无法获取明文。支持多平台部署,安全、匿名、无历史消息。">
|
||||
<meta property="og:title" content="NodeCrypt - End-to-End Encrypted Chat System">
|
||||
<meta property="og:description" content="NodeCrypt is a zero-knowledge, end-to-end encrypted open source chat system where all encryption and decryption is done locally on the client side, and servers cannot access plaintext. Supports multi-platform deployment, secure, anonymous, no message history.">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://github.com/shuaiplus/NodeCrypt">
|
||||
<meta property="og:image" content="https://crypt.works/client/assets/favicon.svg">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="NodeCrypt - 端到端加密聊天系统">
|
||||
<meta name="twitter:description" content="NodeCrypt 是一个零知识、端到端加密的开源聊天系统,所有加密解密均在客户端本地完成,服务器无法获取明文。">
|
||||
<meta name="twitter:title" content="NodeCrypt - End-to-End Encrypted Chat System">
|
||||
<meta name="twitter:description" content="NodeCrypt is a zero-knowledge, end-to-end encrypted open source chat system where all encryption and decryption is done locally on the client side, and servers cannot access plaintext.">
|
||||
<meta name="twitter:image" content="https://crypt.works/client/assets/favicon.svg">
|
||||
<meta name="google-site-verification" content="2KGYZcUDyYzGVeYAQ9mMRUGcTiMyZqFF6D7232SyGgw" />
|
||||
<style>
|
||||
@@ -55,30 +54,71 @@
|
||||
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 18L9 12L15 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button> <h2 data-i18n="help.usage_guide">使用说明</h2>
|
||||
</button> <h2 data-i18n="help.usage_guide"></h2>
|
||||
<div class="help-content">
|
||||
<div class="help-section">
|
||||
<h3 data-i18n="help.what_is_nodecrypt">🔐 什么是 NodeCrypt?</h3>
|
||||
<p data-i18n="help.what_is_nodecrypt_desc">NodeCrypt 是一个开源的端到端加密聊天系统,采用无数据库架构设计。所有消息在您的设备上本地加密,服务器和中间人无法解密您的任何聊天内容。</p>
|
||||
<h3 data-i18n="help.what_is_nodecrypt"></h3>
|
||||
<p data-i18n="help.what_is_nodecrypt_desc"></p>
|
||||
</div>
|
||||
|
||||
<div class="help-section">
|
||||
<h3 data-i18n="help.how_to_start">🚀 如何开始?</h3>
|
||||
<h3 data-i18n="help.how_to_start"></h3>
|
||||
<ol>
|
||||
<li><strong data-i18n="help.step_username">输入用户名</strong>:<span data-i18n="help.step_username_desc">选择一个昵称</span></li>
|
||||
<li><strong data-i18n="help.step_node_name">输入节点名</strong>:<span data-i18n="help.step_node_name_desc">创建或加入现有房间</span></li>
|
||||
<li><strong data-i18n="help.step_password">设置节点密码</strong>:<span data-i18n="help.step_password_desc">用于排除巧合性</span></li>
|
||||
<li><strong data-i18n="help.step_join">点击"加入房间"</strong>:<span data-i18n="help.step_join_desc">开始安全聊天</span></li>
|
||||
<li>
|
||||
<strong data-i18n="help.step_username"></strong>:
|
||||
<span data-i18n="help.step_username_desc"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong data-i18n="help.step_node_name"></strong>:
|
||||
<span data-i18n="help.step_node_name_desc"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong data-i18n="help.step_password"></strong>:
|
||||
<span data-i18n="help.step_password_desc"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong data-i18n="help.step_join"></strong>:
|
||||
<span data-i18n="help.step_join_desc"></span>
|
||||
</li>
|
||||
</ol>
|
||||
<div class="help-important">
|
||||
<strong data-i18n="help.important_note"></strong>:<span data-i18n="help.room_isolation_note"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="help-section">
|
||||
<h3 data-i18n="help.security_features">🔑 安全特性</h3>
|
||||
<h3 data-i18n="help.security_features"></h3>
|
||||
<ul>
|
||||
<li><strong data-i18n="help.e2e_encryption">端到端加密</strong>:<span data-i18n="help.e2e_encryption_desc">消息仅您和接收者可解密</span></li>
|
||||
<li><strong data-i18n="help.no_history">无历史记录</strong>:<span data-i18n="help.no_history_desc">新用户无法看到历史消息</span></li>
|
||||
<li><strong data-i18n="help.password_protection">密码保护</strong>:<span data-i18n="help.password_protection_desc">房间密码参与加密过程</span></li>
|
||||
<li><strong data-i18n="help.anonymous_communication">匿名通信</strong>:<span data-i18n="help.anonymous_communication_desc">无需注册真实身份</span></li>
|
||||
<li>
|
||||
<strong data-i18n="help.e2e_encryption"></strong>:
|
||||
<span data-i18n="help.e2e_encryption_desc"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong data-i18n="help.password_enhanced_encryption"></strong>:
|
||||
<span data-i18n="help.password_enhanced_encryption_desc"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong data-i18n="help.no_history"></strong>:
|
||||
<span data-i18n="help.no_history_desc"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong data-i18n="help.anonymous_communication"></strong>:
|
||||
<span data-i18n="help.anonymous_communication_desc"></span>
|
||||
</li>
|
||||
<li>
|
||||
<strong data-i18n="help.decentralized"></strong>:
|
||||
<span data-i18n="help.decentralized_desc"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="help-section">
|
||||
<h3 data-i18n="help.usage_tips"></h3>
|
||||
<ul>
|
||||
<li><strong data-i18n="help.tip_private_chat"></strong>:<span data-i18n="help.tip_private_chat_desc"></span></li>
|
||||
<li><strong data-i18n="help.tip_group_chat"></strong>:<span data-i18n="help.tip_group_chat_desc"></span></li>
|
||||
<li><strong data-i18n="help.tip_security_reminder"></strong>:<span data-i18n="help.tip_security_reminder_desc"></span></li>
|
||||
<li><strong data-i18n="help.tip_password_strategy"></strong>:<span data-i18n="help.tip_password_strategy_desc"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -353,19 +353,6 @@ export function togglePrivateChat(targetId, targetName) {
|
||||
updateChatInputStyle()
|
||||
}
|
||||
|
||||
// Set the status bar text
|
||||
// 设置状态栏文本
|
||||
export function setStatus(text) {
|
||||
let statusBar = $id('status-bar');
|
||||
if (!statusBar) {
|
||||
statusBar = createElement('div', {
|
||||
id: 'status-bar',
|
||||
style: 'color:green;padding:4px 10px;font-size:13px;'
|
||||
});
|
||||
document.body.appendChild(statusBar)
|
||||
}
|
||||
statusBar.innerText = text
|
||||
}
|
||||
|
||||
// Exit the current room
|
||||
// 退出当前房间
|
||||
|
||||
+77
-21
@@ -232,23 +232,48 @@ export function setupMobileUIHandlers() {
|
||||
rightbar.classList.remove('mobile-open');
|
||||
rightbarMask.classList.remove('active')
|
||||
}
|
||||
} document.addEventListener('click', function(ev) {
|
||||
if (!isMobile()) return;
|
||||
if (sidebar && sidebar.classList.contains('mobile-open')) {
|
||||
if (!sidebar.contains(ev.target) && ev.target !== mobileMenuBtn) {
|
||||
sidebar.classList.remove('mobile-open');
|
||||
if (sidebarMask) sidebarMask.classList.remove('active')
|
||||
}
|
||||
}
|
||||
if (settingsSidebar && settingsSidebar.classList.contains('mobile-open')) {
|
||||
if (!settingsSidebar.contains(ev.target)) {
|
||||
} // Consolidated click event listener for closing sidebars
|
||||
document.addEventListener('click', function(ev) {
|
||||
const settingsBtn = $id('settings-btn');
|
||||
const isSettingsButtonClick = settingsBtn && settingsBtn.contains(ev.target);
|
||||
const isSettingsBackButtonClick = $id('settings-back-btn') && $id('settings-back-btn').contains(ev.target);
|
||||
|
||||
// Close settings sidebar if open and click is outside (and not on the open button or back button)
|
||||
if (settingsSidebar && (settingsSidebar.classList.contains('open') || settingsSidebar.classList.contains('mobile-open'))) {
|
||||
if (!settingsSidebar.contains(ev.target) && !isSettingsButtonClick && !isSettingsBackButtonClick) {
|
||||
closeSettingsPanel();
|
||||
}
|
||||
}
|
||||
if (rightbar && rightbar.classList.contains('mobile-open')) {
|
||||
if (!rightbar.contains(ev.target) && ev.target !== mobileInfoBtn) {
|
||||
rightbar.classList.remove('mobile-open');
|
||||
if (rightbarMask) rightbarMask.classList.remove('active')
|
||||
|
||||
if (isMobile()) {
|
||||
// Mobile-specific logic
|
||||
if (sidebar && sidebar.classList.contains('mobile-open')) {
|
||||
if (!sidebar.contains(ev.target) && ev.target !== mobileMenuBtn) {
|
||||
sidebar.classList.remove('mobile-open');
|
||||
if (sidebarMask) sidebarMask.classList.remove('active');
|
||||
}
|
||||
}
|
||||
if (settingsSidebar && settingsSidebar.classList.contains('mobile-open')) {
|
||||
// 检查点击目标是否为设置按钮本身
|
||||
const isSettingsButton = settingsBtn && settingsBtn.contains(ev.target);
|
||||
if (!settingsSidebar.contains(ev.target) && !isSettingsButton) {
|
||||
closeSettingsPanel();
|
||||
}
|
||||
}
|
||||
if (rightbar && rightbar.classList.contains('mobile-open')) {
|
||||
if (!rightbar.contains(ev.target) && ev.target !== mobileInfoBtn) {
|
||||
rightbar.classList.remove('mobile-open');
|
||||
if (rightbarMask) rightbarMask.classList.remove('active');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Desktop-specific logic
|
||||
// 如果设置侧边栏打开,并且点击位置在侧边栏外部且不是设置按钮本身
|
||||
if (settingsSidebar && settingsSidebar.classList.contains('open')) {
|
||||
const isSettingsButton = settingsBtn && settingsBtn.contains(ev.target);
|
||||
if (!settingsSidebar.contains(ev.target) && !isSettingsButton) {
|
||||
closeSettingsPanel();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -633,7 +658,7 @@ export function initFlipCard() {
|
||||
|
||||
// 开始持续旋转
|
||||
function startContinuousRotation() {
|
||||
if (isHovering) return; // 避免重复启动
|
||||
if (isHovering) return; // 已经在旋转中
|
||||
isHovering = true;
|
||||
|
||||
// 立即执行一次旋转
|
||||
@@ -656,18 +681,49 @@ export function initFlipCard() {
|
||||
}
|
||||
}
|
||||
|
||||
// 帮助按钮事件
|
||||
helpBtn.addEventListener('mouseenter', startContinuousRotation);
|
||||
helpBtn.addEventListener('mouseleave', stopContinuousRotation);
|
||||
// 检查鼠标是否在任一按钮上
|
||||
function isMouseOverButton(e) {
|
||||
const helpBtnRect = helpBtn.getBoundingClientRect();
|
||||
const backBtnRect = backBtn.getBoundingClientRect();
|
||||
const x = e.clientX;
|
||||
const y = e.clientY;
|
||||
|
||||
// 检查是否在帮助按钮上
|
||||
const onHelpBtn = x >= helpBtnRect.left && x <= helpBtnRect.right &&
|
||||
y >= helpBtnRect.top && y <= helpBtnRect.bottom;
|
||||
|
||||
// 检查是否在返回按钮上
|
||||
const onBackBtn = x >= backBtnRect.left && x <= backBtnRect.right &&
|
||||
y >= backBtnRect.top && y <= backBtnRect.bottom;
|
||||
|
||||
return onHelpBtn || onBackBtn;
|
||||
}
|
||||
|
||||
// 监听整个卡片的鼠标移动
|
||||
flipCard.addEventListener('mousemove', (e) => {
|
||||
if (isMouseOverButton(e)) {
|
||||
if (!isHovering) {
|
||||
startContinuousRotation();
|
||||
}
|
||||
} else {
|
||||
if (isHovering) {
|
||||
stopContinuousRotation();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 监听鼠标离开卡片
|
||||
flipCard.addEventListener('mouseleave', () => {
|
||||
stopContinuousRotation();
|
||||
});
|
||||
|
||||
// 按钮点击事件(保持原有功能)
|
||||
helpBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
performFlip();
|
||||
});
|
||||
|
||||
// 返回按钮事件(同样的逻辑)
|
||||
backBtn.addEventListener('mouseenter', startContinuousRotation);
|
||||
backBtn.addEventListener('mouseleave', stopContinuousRotation);
|
||||
backBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -11,7 +11,7 @@ const addEmojiPickerStyles = () => {
|
||||
if (document.querySelector('#emoji-picker-styles')) return;
|
||||
const style = document.createElement('style');
|
||||
style.id = 'emoji-picker-styles';
|
||||
style.textContent = `emoji-picker{--background:#fff;--border-color:rgba(0,0,0,0.1);--border-radius:10px;--emoji-padding:0.4rem;--category-emoji-size:1.2rem;--font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;position:absolute;bottom:60px;left:22px;z-index:5;box-shadow:0 3px 12px rgba(0,0,0,0.15);width:320px;display:none;opacity:0;transform:translateY(-10px) scale(0.95);transition:opacity 0.3s ease,transform 0.3s ease}emoji-picker.show{opacity:1;transform:translateY(0) scale(1)}`;
|
||||
style.textContent = `emoji-picker{--background:#fff;--border-color:rgba(0,0,0,0.1);--border-radius:10px;--emoji-padding:0.4rem;--category-emoji-size:1.2rem;--font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;position:absolute;bottom:60px;left:22px;z-index:5;box-shadow:0 3px 12px rgba(0,0,0,0.15);display:none;opacity:0;transform:translateY(-10px) scale(0.95);transition:opacity 0.3s ease,transform 0.3s ease}emoji-picker.show{opacity:1;transform:translateY(0) scale(1)}`;
|
||||
document.head.appendChild(style)
|
||||
};
|
||||
// Setup emoji picker for chat input
|
||||
|
||||
+118
-37
@@ -9,6 +9,14 @@ const LANGUAGES = {
|
||||
name: 'English',
|
||||
flag: '🇺🇸',
|
||||
translations: {
|
||||
// Meta tags for SEO
|
||||
'meta.description': 'NodeCrypt - True end-to-end encrypted chat system, no database, all messages encrypted locally, server only relays encrypted data, supports Cloudflare Workers, Docker, self-hosting and local development.',
|
||||
'meta.keywords': 'end-to-end encryption, security, chat, WebSocket, Cloudflare Workers, JavaScript, E2EE, anonymous communication, AES, ECDH, RSA, ChaCha20, security, open source, NodeCrypt, shuaiplus',
|
||||
'meta.og_title': 'NodeCrypt - End-to-End Encrypted Chat System',
|
||||
'meta.og_description': 'NodeCrypt is a zero-knowledge, end-to-end encrypted open source chat system where all encryption and decryption is done locally on the client side, and servers cannot access plaintext. Supports multi-platform deployment, secure, anonymous, no message history.',
|
||||
'meta.twitter_title': 'NodeCrypt - End-to-End Encrypted Chat System',
|
||||
'meta.twitter_description': 'NodeCrypt is a zero-knowledge, end-to-end encrypted open source chat system where all encryption and decryption is done locally on the client side, and servers cannot access plaintext.',
|
||||
|
||||
// Login and main UI
|
||||
'ui.enter_node': 'Enter a Node',
|
||||
'ui.username': 'Username',
|
||||
@@ -88,26 +96,36 @@ const LANGUAGES = {
|
||||
'help.title': 'User Guide',
|
||||
'help.back_to_login': 'Back to Login',
|
||||
'help.usage_guide': 'User Guide',
|
||||
'help.what_is_nodecrypt': '🔐 What is NodeCrypt?',
|
||||
'help.what_is_nodecrypt_desc': 'NodeCrypt is an open-source end-to-end encrypted chat system with a database-free architecture. All messages are encrypted locally on your device, and servers and intermediaries cannot decrypt any of your chat content.',
|
||||
'help.how_to_start': '🚀 How to Start?',
|
||||
'help.step_username': 'Username',
|
||||
'help.step_username_desc': 'Choose a nickname',
|
||||
'help.step_node_name': 'Node Name',
|
||||
'help.step_node_name_desc': 'Create or join an existing node',
|
||||
'help.step_password': 'Node Password',
|
||||
'help.step_password_desc': 'Ensure node independence when room names are the same',
|
||||
'help.what_is_nodecrypt': '🔐 What is NodeCrypt?', 'help.what_is_nodecrypt_desc': 'NodeCrypt is a true zero-knowledge end-to-end encrypted chat system. With a database-free architecture, all messages are encrypted locally on your device, and the server serves only as an encrypted data relay station, unable to access any of your plaintext content.',
|
||||
'help.how_to_start': '🚀 Quick Start',
|
||||
'help.step_username': 'Enter Username',
|
||||
'help.step_username_desc': 'Choose a display name for the room, can be any name you like',
|
||||
'help.step_node_name': 'Set Node Name',
|
||||
'help.step_node_name_desc': 'Unique identifier for the room, equivalent to room number',
|
||||
'help.step_password': 'Set Node Password',
|
||||
'help.step_password_desc': 'Used to distinguish different rooms while participating in encryption process to enhance security',
|
||||
'help.step_join': 'Click "Join Room"',
|
||||
'help.step_join_desc': 'Start end-to-end encrypted chatting',
|
||||
'help.security_features': '🔑 Security Features',
|
||||
'help.e2e_encryption': 'End-to-End Encryption',
|
||||
'help.e2e_encryption_desc': 'Messages can only be decrypted by you and the recipient',
|
||||
'help.no_history': 'No History Records',
|
||||
'help.no_history_desc': 'New users cannot see historical messages',
|
||||
'help.password_protection': 'Password Protection',
|
||||
'help.password_protection_desc': 'Room password participates in the encryption process',
|
||||
'help.anonymous_communication': 'Anonymous Communication',
|
||||
'help.anonymous_communication_desc': 'No need to register real identity',
|
||||
'help.step_join_desc': 'System will automatically generate encryption keys and start secure chatting',
|
||||
'help.security_features': '🔑 Security Features', 'help.e2e_encryption': '🛡️ End-to-End Encryption',
|
||||
'help.e2e_encryption_desc': 'Uses AES-256 + ECDH encryption algorithm, messages can only be decrypted by you and the recipient',
|
||||
'help.password_enhanced_encryption': '🔐 Password Enhanced Encryption',
|
||||
'help.password_enhanced_encryption_desc': 'Node password directly participates in encryption key generation, providing additional security protection layer',
|
||||
'help.no_history': '🚫 Zero History Records',
|
||||
'help.no_history_desc': 'All messages exist only in current session, offline users cannot get historical messages',
|
||||
'help.anonymous_communication': '🎭 Complete Anonymity',
|
||||
'help.anonymous_communication_desc': 'No account registration required, no personal information collected',
|
||||
'help.decentralized': '🌐 Decentralized',
|
||||
'help.decentralized_desc': 'Supports self-hosted deployment, server does not participate in encryption/decryption process', 'help.usage_tips': '💡 Usage Tips',
|
||||
'help.important_note': '⚠️ Important Note',
|
||||
'help.room_isolation_note': 'Same node name but different passwords are two completely independent rooms that cannot communicate with each other.',
|
||||
'help.tip_private_chat': 'Private Chat',
|
||||
'help.tip_private_chat_desc': 'Use complex node names and passwords, share only with specific people',
|
||||
'help.tip_group_chat': 'Group Chat',
|
||||
'help.tip_group_chat_desc': 'Use simple and memorable node names and passwords for easy multi-user joining',
|
||||
'help.tip_security_reminder': 'Security Reminder',
|
||||
'help.tip_security_reminder_desc': 'Both node name and password must be exactly the same to enter the same room',
|
||||
'help.tip_password_strategy': 'Password Strategy',
|
||||
'help.tip_password_strategy_desc': 'Recommend using strong passwords containing letters, numbers and symbols',
|
||||
}
|
||||
},
|
||||
zh: {
|
||||
@@ -115,6 +133,14 @@ const LANGUAGES = {
|
||||
name: '中文',
|
||||
flag: '🇨🇳',
|
||||
translations: {
|
||||
// Meta tags for SEO
|
||||
'meta.description': 'NodeCrypt - 真正的端到端加密聊天系统,无数据库,所有消息本地加密,服务器仅做加密数据中转,支持 Cloudflare Workers、Docker、自托管和本地开发。',
|
||||
'meta.keywords': '端到端加密, 安全, 聊天, WebSocket, Cloudflare Workers, JavaScript, E2EE, 匿名通信, AES, ECDH, RSA, ChaCha20, 安全, 开源, NodeCrypt, shuaiplus',
|
||||
'meta.og_title': 'NodeCrypt - 端到端加密聊天系统',
|
||||
'meta.og_description': 'NodeCrypt 是一个端到端加密的开源聊天系统,所有加密解密均在客户端本地完成,服务器无法获取明文。支持多平台部署,安全、匿名、无历史消息。',
|
||||
'meta.twitter_title': 'NodeCrypt - 端到端加密聊天系统',
|
||||
'meta.twitter_description': 'NodeCrypt 是一个端到端加密的开源聊天系统,所有加密解密均在客户端本地完成,服务器无法获取明文。',
|
||||
|
||||
// Login and main UI
|
||||
'ui.enter_node': '进入新的节点',
|
||||
'ui.username': '用户名',
|
||||
@@ -195,26 +221,37 @@ const LANGUAGES = {
|
||||
'help.title': '使用说明',
|
||||
'help.back_to_login': '返回登录',
|
||||
'help.usage_guide': '使用说明',
|
||||
'help.what_is_nodecrypt': '🔐 什么是 NodeCrypt?',
|
||||
'help.what_is_nodecrypt_desc': 'NodeCrypt 是一个开源的端到端加密聊天系统,采用无数据库架构设计。所有消息在您的设备上本地加密,服务器和中间人无法解密您的任何聊天内容。',
|
||||
'help.how_to_start': '🚀 如何开始?',
|
||||
'help.step_username': '用户名',
|
||||
'help.step_username_desc': '选择一个昵称',
|
||||
'help.step_node_name': '节点名称',
|
||||
'help.step_node_name_desc': '创建或加入现有节点',
|
||||
'help.step_password': '节点密码',
|
||||
'help.step_password_desc': '确保房间号相同时节点的独立性',
|
||||
'help.what_is_nodecrypt': '🔐 什么是 NodeCrypt?', 'help.what_is_nodecrypt_desc': 'NodeCrypt 是一个真正的端到端加密聊天系统。采用无数据库架构,所有消息在您的设备上本地加密,服务器仅作为加密数据的中转站,无法获取您的任何明文内容。',
|
||||
'help.how_to_start': '🚀 快速开始',
|
||||
'help.step_username': '输入用户名',
|
||||
'help.step_username_desc': '选择一个在房间中显示的昵称,可以是任何您喜欢的名称',
|
||||
'help.step_node_name': '设置节点名',
|
||||
'help.step_node_name_desc': '房间的唯一标识符,相当于房间号',
|
||||
'help.step_password': '设置节点密码',
|
||||
'help.step_password_desc': '用于区分不同房间,同时参与加密过程,提升安全性',
|
||||
'help.step_join': '点击"加入房间"',
|
||||
'help.step_join_desc': '开始端到端加密聊天',
|
||||
'help.step_join_desc': '系统将自动生成加密密钥,开始安全聊天',
|
||||
'help.security_features': '🔑 安全特性',
|
||||
'help.e2e_encryption': '端到端加密',
|
||||
'help.e2e_encryption_desc': '消息仅您和接收者可解密',
|
||||
'help.no_history': '无历史记录',
|
||||
'help.no_history_desc': '新用户无法看到历史消息',
|
||||
'help.password_protection': '密码保护',
|
||||
'help.password_protection_desc': '房间密码参与加密过程',
|
||||
'help.anonymous_communication': '匿名通信',
|
||||
'help.anonymous_communication_desc': '无需注册真实身份',
|
||||
'help.e2e_encryption': '🛡️ 端到端加密',
|
||||
'help.e2e_encryption_desc': '使用 AES-256 + ECDH 加密算法,消息仅您和接收者可解密',
|
||||
'help.password_enhanced_encryption': '🔐 密码增强加密',
|
||||
'help.password_enhanced_encryption_desc': '节点密码直接参与加密密钥生成,提供额外的安全保护层',
|
||||
'help.no_history': '🚫 零历史记录',
|
||||
'help.no_history_desc': '所有消息仅存在于当前会话,离线用户无法获取历史消息',
|
||||
'help.anonymous_communication': '🎭 完全匿名',
|
||||
'help.anonymous_communication_desc': '无需注册账户,不收集任何个人信息',
|
||||
'help.decentralized': '🌐 去中心化',
|
||||
'help.decentralized_desc': '支持自托管部署,服务器不参与加密解密过程', 'help.usage_tips': '💡 使用技巧',
|
||||
'help.important_note': '⚠️ 重要提示',
|
||||
'help.room_isolation_note': '相同节点名但不同密码的是两个完全独立的房间,无法相互通信。',
|
||||
'help.tip_private_chat': '私人对话',
|
||||
'help.tip_private_chat_desc': '使用复杂的节点名和密码,只分享给特定人员',
|
||||
'help.tip_group_chat': '群聊',
|
||||
'help.tip_group_chat_desc': '使用简单易记的节点名和密码,方便多人加入',
|
||||
'help.tip_security_reminder': '安全提醒',
|
||||
'help.tip_security_reminder_desc': '节点名和密码都需要完全一致才能进入同一个房间',
|
||||
'help.tip_password_strategy': '密码策略',
|
||||
'help.tip_password_strategy_desc': '建议使用包含字母、数字和符号的强密码',
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -396,4 +433,48 @@ export function updateStaticTexts() {
|
||||
element.title = t(key, element.title || key);
|
||||
}
|
||||
});
|
||||
|
||||
// Update meta tags
|
||||
// 更新meta标签
|
||||
updateMetaTags();
|
||||
}
|
||||
|
||||
// Update meta tags with current language
|
||||
// 使用当前语言更新meta标签
|
||||
function updateMetaTags() {
|
||||
// Update description meta tag
|
||||
const metaDescription = document.querySelector('meta[name="description"]');
|
||||
if (metaDescription) {
|
||||
metaDescription.content = t('meta.description', metaDescription.content);
|
||||
}
|
||||
|
||||
// Update keywords meta tag
|
||||
const metaKeywords = document.querySelector('meta[name="keywords"]');
|
||||
if (metaKeywords) {
|
||||
metaKeywords.content = t('meta.keywords', metaKeywords.content);
|
||||
}
|
||||
|
||||
// Update og:title meta tag
|
||||
const metaOgTitle = document.querySelector('meta[property="og:title"]');
|
||||
if (metaOgTitle) {
|
||||
metaOgTitle.content = t('meta.og_title', metaOgTitle.content);
|
||||
}
|
||||
|
||||
// Update og:description meta tag
|
||||
const metaOgDescription = document.querySelector('meta[property="og:description"]');
|
||||
if (metaOgDescription) {
|
||||
metaOgDescription.content = t('meta.og_description', metaOgDescription.content);
|
||||
}
|
||||
|
||||
// Update twitter:title meta tag
|
||||
const metaTwitterTitle = document.querySelector('meta[name="twitter:title"]');
|
||||
if (metaTwitterTitle) {
|
||||
metaTwitterTitle.content = t('meta.twitter_title', metaTwitterTitle.content);
|
||||
}
|
||||
|
||||
// Update twitter:description meta tag
|
||||
const metaTwitterDescription = document.querySelector('meta[name="twitter:description"]');
|
||||
if (metaTwitterDescription) {
|
||||
metaTwitterDescription.content = t('meta.twitter_description', metaTwitterDescription.content);
|
||||
}
|
||||
}
|
||||
+33
-16
@@ -379,14 +379,18 @@ function openSettingsPanel() {
|
||||
// Mobile: hide main sidebar and show settings sidebar with mask
|
||||
sidebar.classList.remove('mobile-open');
|
||||
settingsSidebar.style.display = 'flex';
|
||||
// Force reflow then add animation class
|
||||
settingsSidebar.offsetHeight;
|
||||
settingsSidebar.classList.add('mobile-open');
|
||||
if (sidebarMask) {
|
||||
sidebarMask.classList.add('active');
|
||||
}
|
||||
} else {
|
||||
// Desktop: hide main sidebar and show settings sidebar
|
||||
sidebar.style.display = 'none';
|
||||
} } else {
|
||||
// Desktop: show settings sidebar as overlay with slide animation
|
||||
settingsSidebar.style.display = 'flex';
|
||||
// Force reflow then slide in
|
||||
settingsSidebar.offsetHeight;
|
||||
settingsSidebar.classList.add('open');
|
||||
// Keep main sidebar visible - settings sidebar is an overlay
|
||||
}
|
||||
|
||||
// Setup settings content
|
||||
@@ -397,25 +401,38 @@ function openSettingsPanel() {
|
||||
// 关闭设置面板
|
||||
function closeSettingsPanel() {
|
||||
const settingsSidebar = $id('settings-sidebar');
|
||||
const sidebar = $id('sidebar');
|
||||
const sidebarMask = $id('mobile-sidebar-mask');
|
||||
|
||||
if (!settingsSidebar || !sidebar) return;
|
||||
|
||||
const sidebarMask = $id('mobile-sidebar-mask'); // mobile-sidebar-mask is used for settings on mobile
|
||||
|
||||
if (!settingsSidebar) return;
|
||||
|
||||
const animationEnded = () => {
|
||||
settingsSidebar.style.display = 'none';
|
||||
settingsSidebar.removeEventListener('transitionend', animationEnded);
|
||||
};
|
||||
|
||||
if (isMobile()) {
|
||||
// Mobile: hide settings sidebar and mask, show main sidebar if needed
|
||||
settingsSidebar.classList.remove('mobile-open');
|
||||
if (sidebarMask) {
|
||||
sidebarMask.classList.remove('active');
|
||||
}
|
||||
// Don't auto-show main sidebar on mobile - let user click menu button
|
||||
// Listen for transition end to set display none
|
||||
settingsSidebar.addEventListener('transitionend', animationEnded);
|
||||
// Fallback if transitionend doesn't fire (e.g., if no transition is defined or display:none is set too early by other means)
|
||||
setTimeout(() => {
|
||||
settingsSidebar.style.display = 'none';
|
||||
}, 300); // Wait for animation to complete
|
||||
if (!settingsSidebar.classList.contains('mobile-open')) { // check if it wasn't reopened
|
||||
settingsSidebar.style.display = 'none';
|
||||
}
|
||||
}, 350); // Slightly longer than CSS transition
|
||||
} else {
|
||||
// Desktop: hide settings sidebar and show main sidebar
|
||||
settingsSidebar.style.display = 'none';
|
||||
sidebar.style.display = 'flex';
|
||||
settingsSidebar.classList.remove('open');
|
||||
// Listen for transition end to set display none
|
||||
settingsSidebar.addEventListener('transitionend', animationEnded);
|
||||
// Fallback
|
||||
setTimeout(() => {
|
||||
if (!settingsSidebar.classList.contains('open')) { // check if it wasn't reopened
|
||||
settingsSidebar.style.display = 'none';
|
||||
}
|
||||
}, 350);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user