当前位置: 首页 > news >正文

Gemini Pro 2.5 输出

好的,我已经按照您的要求,将顶部横幅提示消息修改为右下角的 Toast 样式通知。

以下是涉及更改的文件及其内容:

1. my/src/html-ui.js

  • 移除了旧的 #message-area div。
  • <body> 底部添加了新的 #toast-container div 用于存放 Toast 通知。

2. cdn/css/style.css

  • 移除了旧的 .message 相关样式。
  • 添加了新的 Toast 通知样式,包括容器、单个 Toast、不同类型(成功、错误、信息、警告)、图标、关闭按钮和进度条的样式及动画效果,使其显示在右下角。

3. cdn/js/main.js

  • 修改了 showMessage 函数,使其动态创建 Toast 通知元素并添加到 #toast-container 中,并处理自动消失、手动关闭及动画。
  • 更新了 clearMessages 函数以清除所有当前的 Toast 通知。
  • 定义了成功、错误、信息和警告状态的 SVG 图标。

提醒: 由于 CDN 缓存的缘故,您可能需要清除浏览器缓存或强制刷新 (Ctrl+F5 / Cmd+Shift+R) 才能看到这些更改的即时效果。

/*
--------------------------------------------------------------------------------qmwneb946/my/my-b30fba1fe5de85674b52ba4a36498afbff0a9c2e/my/src/html-ui.js
--------------------------------------------------------------------------------
*/
export function generateHtmlUi(currentPath = '/', env = {}) {const showLogin = currentPath === '/' || currentPath === '/user/login';const showRegister = currentPath === '/user/register';const showAccountSection = currentPath.startsWith('/user/') && !showLogin && !showRegister && currentPath !== '/user/help' && !currentPath.startsWith('/user/admin');const showAdminSection = currentPath.startsWith('/user/admin');const authSectionVisible = showLogin || showRegister;const siteName = "用户中心";const faviconDataUri = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='.9em' font-size='90'%3E👤%3C/text%3E%3C/svg%3E";const cdnBaseUrl = env.CDN_BASE_URL || "https://cdn.qmwneb946.dpdns.org";const menuIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-6 w-6"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" /></svg>`;const userIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-5 w-5 mr-1"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-5.5-2.5a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0zM10 12a5.99 5.99 0 00-4.793 2.39A6.483 6.483 0 0010 16.5a6.483 6.483 0 004.793-2.11A5.99 5.99 0 0010 12z" clip-rule="evenodd" /></svg>`;const moonIcon = `<svg id="theme-toggle-dark-icon" class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path></svg>`;const messageIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-5 w-5"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.76c0 1.6 1.123 2.994 2.707 3.227 1.087.16 2.185.283 3.293.369V21l4.076-4.076a1.526 1.526 0 011.037-.443 48.282 48.282 0 005.68-.494c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" /></svg>`;const warningIconLarge = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-16 h-16 text-yellow-500"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.008v.008H12v-.008z" /></svg>`;const mailIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-6 w-6 mr-2"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" /></svg>`;const adminIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4"><path fill-rule="evenodd" d="M6.455 1.45A.5.5 0 0 1 6.952 1h2.096a.5.5 0 0 1 .497.45l.186 1.858a4.996 4.996 0 0 1 1.466.848l1.703-.769a.5.5 0 0 1 .639.206l1.047 1.814a.5.5 0 0 1-.14.656l-1.517 1.09a5.026 5.026 0 0 1 0 1.694l1.516 1.09a.5.5 0 0 1 .141.656l-1.047 1.814a.5.5 0 0 1-.639.206l-1.703-.768c-.433.36-.928.649-1.466.847l-.186 1.858a.5.5 0 0 1-.497.45H6.952a.5.5 0 0 1-.497-.45l-.186-1.858a4.993 4.993 0 0 1-1.466-.848l-1.703.769a.5.5 0 0 1-.639-.206l-1.047-1.814a.5.5 0 0 1 .14-.656l1.517-1.09a5.033 5.033 0 0 1 0-1.694l-1.516-1.09a.5.5 0 0 1-.141-.656L2.46 3.593a.5.5 0 0 1 .639-.206l1.703.769c.433-.36.928-.65 1.466-.848l.186-1.858Zm-.177 7.567-.022-.037a2 2 0 0 1 3.466-1.997l.022.037a2 2 0 0 1-3.466 1.997Z" clip-rule="evenodd" /></svg>`;const sunIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-5 w-5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" /></svg>`;const helpIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-5 w-5 mr-2"><path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z" /></svg>`;const oauthIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 mr-2"><path fill-rule="evenodd" d="M8.603 3.799A4.49 4.49 0 0 1 12 2.25c1.357 0 2.573.6 3.397 1.549a4.49 4.49 0 0 1 3.498 1.307 4.491 4.491 0 0 1 1.307 3.497A4.49 4.49 0 0 1 21.75 12a4.49 4.49 0 0 1-1.549 3.397 4.491 4.491 0 0 1-1.307 3.497 4.491 4.491 0 0 1-3.497 1.307A4.49 4.49 0 0 1 12 21.75a4.49 4.49 0 0 1-3.397-1.549 4.49 4.49 0 0 1-3.498-1.306 4.491 4.491 0 0 1-1.307-3.498A4.49 4.49 0 0 1 2.25 12c0-1.357.6-2.573 1.549-3.397a4.49 4.49 0 0 1 1.307-3.497 4.49 4.49 0 0 1 3.497-1.307Zm7.007 6.387a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" /></svg>`;const apiKeyIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 mr-2"><path fill-rule="evenodd" d="M15.75 1.5a6.75 6.75 0 0 0-6.651 7.906c.067.39-.032.717-.221.906l-6.5 6.499a3 3 0 0 0-.878 2.121v2.818c0 .414.336.75.75.75H6a.75.75 0 0 0 .75-.75v-1.5h1.5A.75.75 0 0 0 9 19.5V18h1.5a.75.75 0 0 0 .53-.22l2.658-2.658c.19-.189.517-.288.906-.22A6.75 6.75 0 1 0 15.75 1.5Zm0 3a.75.75 0 0 0 0 1.5A2.25 2.25 0 0 1 18 8.25a.75.75 0 0 0 1.5 0 3.75 3.75 0 0 0-3.75-3.75Z" clip-rule="evenodd" /></svg>`;const personalInfoIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 mr-2"><path fill-rule="evenodd" d="M18.685 19.097A9.723 9.723 0 0 0 21.75 12c0-5.385-4.365-9.75-9.75-9.75S2.25 6.615 2.25 12a9.723 9.723 0 0 0 3.065 7.097A9.716 9.716 0 0 0 12 21.75a9.716 9.716 0 0 0 6.685-2.653Zm-12.54-1.285A7.486 7.486 0 0 1 12 15a7.486 7.486 0 0 1 5.855 2.812A8.224 8.224 0 0 1 12 20.25a8.224 8.224 0 0 1-5.855-2.438ZM15.75 9a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" clip-rule="evenodd" /></svg>`;const securityIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 mr-2"><path fill-rule="evenodd" d="M12 1.5a5.25 5.25 0 0 0-5.25 5.25v3a3 3 0 0 0-3 3v6.75a3 3 0 0 0 3 3h10.5a3 3 0 0 0 3-3v-6.75a3 3 0 0 0-3-3v-3c0-2.9-2.35-5.25-5.25-5.25Zm3.75 8.25v-3a3.75 3.75 0 1 0-7.5 0v3h7.5Z" clip-rule="evenodd" /></svg>`;return `
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>${siteName}</title><link rel="icon" href="${faviconDataUri}"><link rel="stylesheet" href="${cdnBaseUrl}/css/style.css"><script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script><script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script><script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js" defer></script><script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js" defer></script><style>.hidden { display: none !important; }.modal {position: fixed; top: 0; left: 0; width: 100%; height: 100%;background-color: rgba(0,0,0,0.6); display: flex;justify-content: center; align-items: center; z-index: 2000;opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0s linear 0.3s;}.modal.active { opacity: 1; visibility: visible; transition: opacity 0.3s ease; }.modal-content {background-color: var(--current-surface-color); color: var(--current-text-color);padding: 25px 30px; border-radius: var(--border-radius);box-shadow: 0 5px 15px rgba(0,0,0,0.3); width: 90%; max-width: 500px;transform: translateY(-20px); transition: transform 0.3s ease;}.modal.active .modal-content { transform: translateY(0); }.modal-content h4 { margin-top: 0; text-align: left; font-size: 1.4em; color: var(--current-heading-color); }.modal-buttons { margin-top: 25px; text-align: right; }.modal-buttons button { margin-left: 10px; }.license-code-list { list-style-type: none; padding-left: 0; max-height: 200px; overflow-y: auto; border: 1px solid var(--current-border-color); border-radius: var(--border-radius); padding: 10px; background-color: var(--current-bg-color); }.license-code-list li { padding: 5px 0; border-bottom: 1px dashed var(--current-border-color); font-family: var(--font-family-mono); font-size: 0.9em;}.license-code-list li:last-child { border-bottom: none; }.admin-panel-table { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 0.9em; }.admin-panel-table th, .admin-panel-table td { border: 1px solid var(--current-border-color); padding: 8px 12px; text-align: left; }.admin-panel-table th { background-color: var(--current-surface-color); font-weight: 600; }.admin-panel-table tr:nth-child(even) { background-color: color-mix(in srgb, var(--current-surface-color) 95%, var(--current-bg-color));}.admin-panel-table td code { background-color: color-mix(in srgb, var(--current-text-color) 10%, transparent); padding: 2px 4px; border-radius: 3px; font-family: var(--font-family-mono); }.status-active { color: var(--success-color); font-weight: bold; }.status-inactive, .status-suspended { color: var(--danger-color); font-weight: bold; }.admin-actions button { margin-right: 5px; }.pagination { display: flex; justify-content: center; margin-top: 20px; }.pagination ul { list-style: none; padding: 0; margin: 0; display: flex; gap: 5px; }.pagination li a, .pagination li span {display: block; padding: 8px 12px; border: 1px solid var(--current-border-color);border-radius: var(--border-radius); color: var(--primary-color); text-decoration: none;transition: background-color 0.2s ease, color 0.2s ease;}.pagination li a:hover { background-color: color-mix(in srgb, var(--primary-color) 10%, transparent); }.pagination li.active a { background-color: var(--primary-color); color: var(--text-color-inverted); border-color: var(--primary-color); }.pagination li.disabled a, .pagination li.disabled span { color: var(--text-color-muted); cursor: not-allowed; opacity: 0.6; }.pagination li.active a:hover { background-color: var(--primary-color); }.pagination li.ellipsis span { border-color: transparent; }.admin-controls { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }.admin-controls label { margin-right: 10px; }.admin-controls select { padding: 5px 8px; border-radius: var(--border-radius); border: 1px solid var(--current-border-color); background-color: var(--current-input-bg); color: var(--current-text-color); }.admin-controls input[type="search"] { flex-grow: 1; margin-right: 10px; max-width: 250px; }</style>
</head>
<body class="font-sans antialiased"><div id="app-wrapper" class="app-wrapper"><header id="top-bar" class="top-bar"><div class="top-bar-left"><button id="sidebar-toggle" class="sidebar-toggle-button" aria-label="切换导航菜单">${menuIcon}</button><span class="site-title">${siteName}</span></div><div class="top-bar-right"><button type="button" id="theme-toggle-button" aria-label="切换主题" class="theme-toggle-button">${sunIcon}${moonIcon}</button><button type="button" id="top-bar-messaging-button" aria-label="私信" class="messaging-button hidden">${messageIcon}<span id="unread-messages-indicator" class="unread-badge hidden"></span></button><div id="top-bar-auth-buttons" class="auth-actions hidden"><a href="/user/register" class="button secondary small">注册</a><a href="/user/login" class="button primary small">登录</a></div><div id="top-bar-user-info" class="user-info-dropdown hidden"><button id="user-menu-button" class="user-menu-button">${userIcon}<span class="username-text" id="top-bar-user-username"></span></button><div id="user-dropdown-menu" class="dropdown-menu hidden"><div class="dropdown-user-email" id="top-bar-user-email"></div><hr class="dropdown-divider"><a href="/user/profile" id="top-bar-account-link" class="dropdown-item">${personalInfoIcon}账户设置</a><a href="/user/admin" id="top-bar-admin-link" class="dropdown-item hidden">${adminIcon}管理员面板</a><a href="/user/help" class="dropdown-item">${helpIcon}帮助与API示例</a><button type="button" class="dropdown-item logout" id="top-bar-logout-button">安全登出</button></div></div></div></header><aside id="sidebar" class="sidebar"><nav class="sidebar-nav"><ul id="account-tabs"><li><a href="/user/profile" id="nav-profile" class="sidebar-link" data-pane-id="tab-content-personal-info">${personalInfoIcon}个人信息</a></li><li><a href="/user/security" id="nav-security" class="sidebar-link" data-pane-id="tab-content-security-settings">${securityIcon}安全设置</a></li><li><a href="/user/api-keys" id="nav-api-keys" class="sidebar-link" data-pane-id="tab-content-api-keys">${apiKeyIcon}获取/查看密钥</a></li><li><a href="/user/applications" id="nav-applications" class="sidebar-link" data-pane-id="tab-content-my-applications">${oauthIcon}我的应用</a></li></ul><ul id="admin-tabs" class="hidden" style="margin-top: 20px; border-top: 1px solid var(--current-border-color); padding-top:10px;"><li><a href="/user/admin/users" id="nav-admin-users" class="sidebar-link" data-pane-id="tab-content-admin-users">${adminIcon}用户管理</a></li><li><a href="/user/admin/apps" id="nav-admin-apps" class="sidebar-link" data-pane-id="tab-content-admin-apps">${adminIcon}应用管理</a></li></ul></nav></aside><div id="sidebar-overlay" class="sidebar-overlay hidden"></div><main id="main-content" class="main-content"><div class="container"><div id="auth-section" class="auth-section ${authSectionVisible ? '' : 'hidden'}"><div id="login-form" class="form-container ${showLogin ? '' : 'hidden'}"><h2>用户登录</h2><form id="login-form-el"><div class="form-group"><label for="login-identifier">邮箱或用户名</label><input type="text" id="login-identifier" name="identifier" required autocomplete="username email" placeholder="请输入您的邮箱或用户名"></div><div class="form-group"><label for="login-password">密码</label><input type="password" id="login-password" name="password" required autocomplete="current-password" placeholder="请输入密码"></div><div id="login-2fa-section" class="form-group hidden"><label for="login-totp-code">两步验证码</label><input type="text" id="login-totp-code" name="totpCode" pattern="\\d{6}" maxlength="6" placeholder="请输入6位验证码"></div><div class="cf-turnstile" data-sitekey="${env.TURNSTILE_SITE_KEY || '1x00000000000000000000AA'}" data-callback="turnstileCallbackLogin"></div><button type="submit" class="button primary full-width">登录</button></form><div class="toggle-link">还没有账户? <a href="/user/register">立即注册</a></div></div><div id="register-form" class="form-container ${showRegister ? '' : 'hidden'}"><h2>新用户注册</h2><form id="register-form-el"><div class="form-group"><label for="register-username">用户名</label><input type="text" id="register-username" name="username" required minlength="3" maxlength="30" placeholder="3-30位字符,可包含字母、数字、下划线和连字符"></div><div class="form-group"><label for="register-email">邮箱地址</label><input type="email" id="register-email" name="email" required autocomplete="email" placeholder="例如:user@example.com"></div><div class="form-group"><label for="register-phone">手机号码 (可选)</label><input type="tel" id="register-phone" name="phoneNumber" autocomplete="tel" placeholder="例如:+8613800138000"></div><div class="form-group"><label for="register-password">设置密码 (至少6位)</label><input type="password" id="register-password" name="password" required minlength="6" autocomplete="new-password" placeholder="请输入至少6位密码"></div><div class="form-group"><label for="register-confirm-password">确认密码</label><input type="password" id="register-confirm-password" name="confirmPassword" required minlength="6" autocomplete="new-password" placeholder="请再次输入密码"></div><div class="cf-turnstile" data-sitekey="${env.TURNSTILE_SITE_KEY || '1x00000000000000000000AA'}" data-callback="turnstileCallbackRegister"></div><button type="submit" class="button primary full-width">创建账户</button></form><div class="toggle-link">已经有账户了? <a href="/user/login">返回登录</a></div></div></div><div id="logged-in-section" class="account-content ${showAccountSection ? '' : 'hidden'}"><div id="tab-content-personal-info" class="tab-pane hidden" data-path="/user/profile"><h3>${personalInfoIcon}个人信息修改</h3><form id="update-profile-form" class="content-form"><div class="form-group"><label for="profile-username">用户名</label><input type="text" id="profile-username" name="username" required minlength="3" maxlength="30" placeholder="3-30位字符"></div><div class="form-group"><label for="profile-phone">手机号码 (可选)</label><input type="tel" id="profile-phone" name="phoneNumber" placeholder="例如:+8613800138000"></div><div class="form-group"><label for="profile-email">邮箱地址 (不可修改)</label><input type="email" id="profile-email" name="email" readonly disabled></div><button type="submit" class="button primary">保存个人信息</button></form></div><div id="tab-content-security-settings" class="tab-pane hidden" data-path="/user/security"><h3>${securityIcon}安全设置</h3><ul class="security-settings-list"><li class="security-setting-item"><div class="setting-entry" data-target="change-password-content-panel"><span class="entry-title">修改密码</span><span class="entry-arrow">▼</span></div><div id="change-password-content-panel" class="setting-content-panel hidden"><form id="change-password-form" class="content-form"><div class="form-group"><label for="current-password">当前密码</label><input type="password" id="current-password" name="currentPassword" required autocomplete="current-password" placeholder="请输入您当前的密码"></div><div class="form-group"><label for="new-password">新密码 (至少6位)</label><input type="password" id="new-password" name="newPassword" required minlength="6" autocomplete="new-password" placeholder="请输入新的密码"></div><button type="submit" class="button secondary">确认修改密码</button></form></div></li><li class="security-setting-item"><div class="setting-entry" data-target="2fa-content-panel"><span class="entry-title">两步验证 (2FA)</span><span class="entry-status" id="2fa-entry-status">(未知)</span><span class="entry-arrow">▼</span></div><div id="2fa-content-panel" class="setting-content-panel hidden"><p class="description-text">使用两步验证码可以帮您双重保护账户安全。</p><div id="2fa-status-section" class="status-display">当前状态: <span id="2fa-current-status">未知</span></div><div id="2fa-controls" style="margin-bottom: 15px;"><button type="button" id="btn-init-enable-2fa" class="button success small hidden">启用两步验证</button><button type="button" id="btn-disable-2fa" class="button danger small hidden">禁用两步验证</button></div><div id="2fa-setup-section" class="hidden" style="margin-top:20px;"><p>1. 使用您的身份验证器应用扫描二维码或手动输入密钥。</p><div style="text-align: center; margin: 15px 0;"><div id="qrcode-display"></div></div><p>密钥链接: <code id="otpauth-uri-text-display" class="otpauth-uri-text"></code></p><input type="hidden" id="2fa-temp-secret"><div class="form-group" style="margin-top:10px;"><label for="2fa-setup-code">6位验证码</label><input type="text" id="2fa-setup-code" name="totpCode" pattern="\\d{6}" maxlength="6" placeholder="请输入验证码"></div><div class="form-actions"><button type="button" id="btn-complete-enable-2fa" class="button success">验证并启用</button><button type="button" id="btn-cancel-2fa-setup" class="button secondary">取消</button></div></div></div></li></ul></div><div id="tab-content-api-keys" class="tab-pane hidden" data-path="/user/api-keys"><h3>${apiKeyIcon}获取/查看密钥</h3><ul class="security-settings-list"><li class="security-setting-item"><div class="setting-entry" data-target="paste-api-key-content-panel"><span class="entry-title">云剪贴板 API 密钥</span><span class="entry-arrow">▼</span></div><div id="paste-api-key-content-panel" class="setting-content-panel hidden"><p class="description-text">API 密钥名称将自动生成,仅拥有文本和文件权限。</p><p class="description-text">访问云剪贴板服务:<a href="http://go.qmwneb946.dpdns.org/?LinkId=37" target="_blank" rel="noopener noreferrer" class="external-link">http://go.qmwneb946.dpdns.org/?LinkId=37</a></p><form id="create-paste-api-key-form" class="content-form" style="margin-top:15px;"><div class="cf-turnstile" data-sitekey="${env.TURNSTILE_SITE_KEY || '1x00000000000000000000AA'}" data-callback="turnstileCallbackPasteApi"></div><button type="submit" class="button success">创建云剪贴板 API 密钥</button></form><div id="newly-created-api-key-display" class="hidden api-key-display" style="margin-top:15px;"><h5>新创建的云剪贴板 API 密钥:</h5><div class="api-key-value-container"><input type="text" id="new-api-key-value" readonly><button type="button" class="button small secondary" onclick="copyToClipboard(document.getElementById('new-api-key-value').value, '云剪贴板 API 密钥')">复制</button></div></div></div></li><li class="security-setting-item"><div class="setting-entry" data-target="cloud-pc-key-content-panel"><span class="entry-title">Cloud PC 密钥</span><span class="entry-status" id="cloud-pc-key-entry-status">(点击展开查看状态)</span><span class="entry-arrow">▼</span></div><div id="cloud-pc-key-content-panel" class="setting-content-panel hidden"><div id="cloud-pc-key-status-area" class="status-display">正在加载 Cloud PC 密钥状态...</div><p class="description-text">访问 Cloud PC 服务:<a href="http://go.qmwneb946.dpdns.org/?LinkId=17" target="_blank" rel="noopener noreferrer" class="external-link">http://go.qmwneb946.dpdns.org/?LinkId=17</a></p><form id="create-cloud-pc-key-form" class="content-form hidden" style="margin-top:15px;"><p class="description-text">每位用户仅可创建一次,获得 1 次使用次数。</p><div class="cf-turnstile" data-sitekey="${env.TURNSTILE_SITE_KEY || '1x00000000000000000000AA'}" data-callback="turnstileCallbackCloudPc"></div><button type="submit" class="button success">创建 Cloud PC 密钥</button></form><div id="existing-cloud-pc-key-display" class="hidden api-key-display" style="margin-top:15px;"><h5>您的 Cloud PC 密钥:</h5><div class="api-key-value-container"><input type="text" id="cloud-pc-api-key-value" readonly><button type="button" class="button small secondary" onclick="copyToClipboard(document.getElementById('cloud-pc-api-key-value').value, 'Cloud PC API 密钥')">复制</button></div><p>剩余使用次数: <strong id="cloud-pc-usage-count"></strong></p></div></div></li><li class="security-setting-item"><div class="setting-entry" data-target="greenhub-key-content-panel"><span class="entry-title">GreenHub 激活码</span><span class="entry-arrow">▼</span></div><div id="greenhub-key-content-panel" class="setting-content-panel hidden"><p class="description-text">查看您已获取的 GreenHub 激活码。</p><button type="button" id="btn-fetch-greenhub-keys" class="button primary">获取 GreenHub 激活码</button><div id="greenhub-codes-display" style="margin-top:15px;"><p class="placeholder-text">点击按钮获取激活码。</p></div></div></li><li class="security-setting-item"><div class="setting-entry" data-target="fast-download-content-panel"><span class="entry-title">快速下载链接生成</span><span class="entry-arrow">▼</span></div><div id="fast-download-content-panel" class="setting-content-panel hidden"><p class="description-text">输入原始下载链接,生成一个通过 <code class="inline-code">http://fast.qmwneb946.dpdns.org/</code> 代理的高速下载链接。</p><div class="form-group"><label for="original-download-url">原始下载链接</label><input type="url" id="original-download-url" name="originalDownloadUrl" placeholder="例如:https://example.com/file.zip"></div><button type="button" id="btn-generate-fast-link" class="button primary">生成高速链接</button><div id="generated-fast-link-display" class="hidden api-key-display" style="margin-top:15px;"><h5>生成的高速下载链接:</h5><div class="api-key-value-container"><input type="text" id="generated-fast-link-value" readonly><button type="button" class="button small secondary" onclick="copyToClipboard(document.getElementById('generated-fast-link-value').value, '高速下载链接')">复制</button></div></div></div></li></ul></div><div id="tab-content-my-applications" class="tab-pane hidden" data-path="/user/applications"><h3>${oauthIcon}我的 OAuth 应用</h3><div class="setting-block"><h4>注册新应用</h4><form id="register-oauth-client-form" class="content-form"><div class="form-group"><label for="oauth-client-name">应用名称</label><input type="text" id="oauth-client-name" name="clientName" required maxlength="50" placeholder="例如:我的博客评论系统"></div><div class="form-group"><label for="oauth-client-website">应用主页 (可选)</label><input type="url" id="oauth-client-website" name="clientWebsite" maxlength="200" placeholder="例如:https://myblog.com"></div><div class="form-group"><label for="oauth-client-description">应用描述 (可选)</label><input type="text" id="oauth-client-description" name="clientDescription" maxlength="200" placeholder="简要描述您的应用"></div><div class="form-group"><label for="oauth-client-redirect-uri">回调地址 (Redirect URI)</label><input type="url" id="oauth-client-redirect-uri" name="redirectUri" required placeholder="例如:https://myblog.com/oauth/callback"><p class="input-hint">必须是 HTTPS 地址。</p></div><div class="cf-turnstile" data-sitekey="${env.TURNSTILE_SITE_KEY || '1x00000000000000000000AA'}" data-callback="turnstileCallbackOauthClient"></div><button type="submit" class="button success">注册应用</button></form><div id="new-oauth-client-credentials" class="hidden" style="margin-top: 20px;"><h5 style="color: var(--primary-color);">应用注册成功!</h5><p class="description-text">请妥善保管您的应用凭据,特别是 <strong>客户端密钥 (Client Secret)</strong>,它将仅显示这一次。</p><div class="application-card"><p><strong>客户端 ID:</strong> <code id="new-client-id-display"></code> <button type="button" class="button small secondary" onclick="copyToClipboard(document.getElementById('new-client-id-display').textContent, '客户端 ID')">复制</button></p><p><strong>客户端密钥:</strong> <code id="new-client-secret-display"></code> <button type="button" class="button small secondary" onclick="copyToClipboard(document.getElementById('new-client-secret-display').textContent, '客户端密钥')">复制</button></p></div><div class="new-client-secret-warning"><strong>重要提示:</strong> 客户端密钥非常敏感,请立即复制并安全存储。</div></div></div><hr class="section-divider"><h4>已注册的应用</h4><div id="registered-oauth-clients-list"><p>正在加载应用列表...</p></div></div><div id="tab-content-messaging" class="tab-pane hidden" data-path="/user/messaging"><div class="messaging-tab-header">${mailIcon}<h3>个人私信</h3></div><div class="new-conversation-trigger" id="new-conversation-area-messaging"><input type="email" id="new-conversation-email" placeholder="输入对方邮箱开始新对话..."><button type="button" id="btn-start-new-conversation" class="button primary small">开始</button></div><div class="messaging-layout-new"><div class="messaging-contacts-panel"><div class="contact-search-bar"><input type="search" id="contact-search-input" placeholder="搜索联系人..."></div><h4 class="recent-contacts-title">最近联系</h4><ul id="conversations-list" class="contact-list"><p class="placeholder-text">正在加载对话...</p></ul></div><div id="messages-area" class="message-display-panel"><div id="messages-list" class="messages-list"><div id="messages-loading-indicator-wrapper" class="messages-loading-indicator-wrapper hidden"><div class="spinner"></div></div><div id="load-more-messages-button-wrapper" class="load-more-messages-button-wrapper hidden"><button type="button" id="load-more-messages-button" class="button secondary small load-more-messages-button">加载更多...</button></div><div class="empty-messages-placeholder">${warningIconLarge}<p>选择一个联系人开始聊天</p><span>或通过上方搜索框发起新的对话。</span></div></div><div id="message-input-area" class="message-input-area hidden"><textarea id="message-input" placeholder="输入消息..." rows="1"></textarea><button type="button" id="btn-send-message" class="button primary">发送</button></div></div></div></div></div><div id="admin-section" class="admin-content ${showAdminSection ? '' : 'hidden'}"><div id="tab-content-admin-users" class="tab-pane hidden" data-path="/user/admin/users"><h3>用户管理</h3><div class="admin-controls"><input type="search" id="admin-users-search-input" placeholder="搜索用户邮箱/用户名/手机号..."><div><label for="admin-users-per-page">每页显示:</label><select id="admin-users-per-page"><option value="10">10</option><option value="20">20</option><option value="50">50</option><option value="100">100</option><option value="1000">1000</option></select></div></div><div id="admin-users-list-container"><p>正在加载用户列表...</p></div><div id="admin-users-pagination" class="pagination"></div></div><div id="tab-content-admin-apps" class="tab-pane hidden" data-path="/user/admin/apps"><h3>OAuth 应用管理</h3><div id="admin-oauth-clients-list-container"><p>正在加载应用列表...</p></div></div></div></div></main></div><div id="edit-oauth-client-modal" class="modal hidden"><div class="modal-content"><h4 id="edit-oauth-client-modal-title">编辑应用信息</h4><form id="edit-oauth-client-form"><input type="hidden" id="edit-client-id" name="clientId"><div class="form-group"><label for="edit-oauth-client-name">应用名称</label><input type="text" id="edit-oauth-client-name" name="clientName" required maxlength="50"></div><div class="form-group"><label for="edit-oauth-client-website">应用主页 (可选)</label><input type="url" id="edit-oauth-client-website" name="clientWebsite" maxlength="200"></div><div class="form-group"><label for="edit-oauth-client-description">应用描述 (可选)</label><input type="text" id="edit-oauth-client-description" name="clientDescription" maxlength="200"></div><div class="form-group"><label for="edit-oauth-client-redirect-uri">回调地址 (Redirect URI)</label><input type="url" id="edit-oauth-client-redirect-uri" name="redirectUri" required><p class="input-hint">必须是 HTTPS 地址。</p></div><div class="modal-buttons"><button type="button" id="btn-cancel-edit-oauth-client" class="button secondary">取消</button><button type="submit" class="button success">保存更改</button></div></form></div></div><div id="edit-user-modal" class="modal"><div class="modal-content"><h4>编辑用户信息</h4><form id="edit-user-form"><div class="form-group"><label for="edit-user-email">邮箱地址</label><input type="email" id="edit-user-email" name="email" readonly disabled></div><div class="form-group"><label for="edit-username">用户名</label><input type="text" id="edit-username" name="username" required minlength="3" maxlength="30" placeholder="3-30位字符,可包含字母、数字、下划线和连字符"></div><div class="form-group"><label for="edit-phone-number">手机号码 (可选)</label><input type="tel" id="edit-phone-number" name="phoneNumber" placeholder="例如:+8613800138000"></div><div class="form-group"><label for="edit-password">新密码 (留空则不修改)</label><input type="password" id="edit-password" name="newPassword" minlength="6" autocomplete="new-password" placeholder="输入新密码或留空"></div><div class="modal-buttons"><button type="button" id="btn-cancel-edit-user" class="button secondary">取消</button><button type="submit" class="button primary">保存更改</button></div></form></div></div><div id="toast-container" class="toast-container bottom-right"></div><script src="${cdnBaseUrl}/js/main.js" defer></script><script src="${cdnBaseUrl}/js/ui-personal-info.js" defer></script><script src="${cdnBaseUrl}/js/ui-security-settings.js" defer></script><script src="${cdnBaseUrl}/js/ui-api-keys.js" defer></script><script src="${cdnBaseUrl}/js/ui-oauth-apps.js" defer></script><script src="${cdnBaseUrl}/js/ui-messaging.js" defer></script><script src="${cdnBaseUrl}/js/ui-admin.js" defer></script>
</body>
</html>`;
}
export function generateHelpPageHtml(env = {}) {const siteName = "用户中心";const faviconDataUri = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='.9em' font-size='90'%3E👤%3C/text%3E%3C/svg%3E";const cdnBaseUrl = env.CDN_BASE_URL || "https://cdn.qmwneb946.dpdns.org";const menuIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-6 w-6"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" /></svg>`;const userIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-5 w-5 mr-1"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-5.5-2.5a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0zM10 12a5.99 5.99 0 00-4.793 2.39A6.483 6.483 0 0010 16.5a6.483 6.483 0 004.793-2.11A5.99 5.99 0 0010 12z" clip-rule="evenodd" /></svg>`;const sunIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-5 w-5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" /></svg>`;const moonIcon = `<svg id="theme-toggle-dark-icon" class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path></svg>`;const messageIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-5 w-5"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.76c0 1.6 1.123 2.994 2.707 3.227 1.087.16 2.185.283 3.293.369V21l4.076-4.076a1.526 1.526 0 011.037-.443 48.282 48.282 0 005.68-.494c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" /></svg>`;const adminIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-5 w-5 mr-2"><path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z" /></svg>`;const helpIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-5 w-5 mr-2"><path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z" /></svg>`;const personalInfoIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 mr-2"><path fill-rule="evenodd" d="M18.685 19.097A9.723 9.723 0 0 0 21.75 12c0-5.385-4.365-9.75-9.75-9.75S2.25 6.615 2.25 12a9.723 9.723 0 0 0 3.065 7.097A9.716 9.716 0 0 0 12 21.75a9.716 9.716 0 0 0 6.685-2.653Zm-12.54-1.285A7.486 7.486 0 0 1 12 15a7.486 7.486 0 0 1 5.855 2.812A8.224 8.224 0 0 1 12 20.25a8.224 8.224 0 0 1-5.855-2.438ZM15.75 9a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" clip-rule="evenodd" /></svg>`;return `
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>帮助与API示例 - ${siteName}</title><link rel="icon" href="${faviconDataUri}"><link rel="stylesheet" href="${cdnBaseUrl}/css/style.css"><style>.help-content { padding: 20px; }.api-usage-section pre {background-color: color-mix(in srgb, var(--current-bg-color) 95%, var(--current-surface-color));color: var(--current-text-color);padding: 15px; border-radius: var(--border-radius); overflow-x: auto;font-family: var(--font-family-mono); font-size: 0.875em;border: 1px solid var(--current-border-color); margin-bottom: 15px;}.api-usage-section code.inline-code {background-color: color-mix(in srgb, var(--current-text-color) 10%, transparent);padding: 2px 5px; border-radius: 3px; font-family: var(--font-family-mono);}hr.section-divider { border: none; border-top: 1px solid var(--current-border-color); margin: 30px 0; }body.dark-mode .api-usage-section pre {background-color: color-mix(in srgb, var(--current-bg-color) 95%, var(--current-surface-color));}.app-wrapper.help-page-layout .sidebar { display: none; }.app-wrapper.help-page-layout .main-content { margin-left: 0; }</style>
</head>
<body class="font-sans antialiased"><div id="app-wrapper" class="app-wrapper help-page-layout"><header id="top-bar" class="top-bar"><div class="top-bar-left"><button id="sidebar-toggle" class="sidebar-toggle-button" aria-label="切换导航菜单">${menuIcon}</button><a href="/" class="site-title" style="margin-left:0;">${siteName}</a></div><div class="top-bar-right"><button type="button" id="theme-toggle-button" aria-label="切换主题" class="theme-toggle-button">${sunIcon}${moonIcon}</button><button type="button" id="top-bar-messaging-button" aria-label="私信" class="messaging-button hidden">${messageIcon}<span id="unread-messages-indicator" class="unread-badge hidden"></span></button><div id="top-bar-auth-buttons" class="auth-actions hidden"><a href="/user/register" class="button secondary small">注册</a><a href="/user/login" class="button primary small">登录</a></div><div id="top-bar-user-info" class="user-info-dropdown hidden"><button id="user-menu-button" class="user-menu-button">${userIcon}<span class="username-text" id="top-bar-user-username"></span></button><div id="user-dropdown-menu" class="dropdown-menu hidden"><div class="dropdown-user-email" id="top-bar-user-email"></div><hr class="dropdown-divider"><a href="/user/profile" id="top-bar-account-link" class="dropdown-item">${personalInfoIcon}账户设置</a><a href="/user/admin" id="top-bar-admin-link" class="dropdown-item hidden">${adminIcon}管理员面板</a><a href="/user/help" class="dropdown-item">${helpIcon}帮助与API示例</a><button type="button" class="dropdown-item logout" id="top-bar-logout-button">安全登出</button></div></div></div></header><aside id="sidebar" class="sidebar"><nav class="sidebar-nav"><ul id="account-tabs"><li><a href="/user/profile" class="sidebar-link">${personalInfoIcon}返回账户</a></li></ul></nav></aside><div id="sidebar-overlay" class="sidebar-overlay hidden"></div><main id="main-content" class="main-content"><div class="container"><div id="help-page-content" class="help-content api-usage-section"><h3>OAuth&nbsp;验证流程</h3><h4>1. 获取访问令牌 (Access Token) 和 ID 令牌 (ID Token)</h4><p>在您的客户端应用完成 OAuth 授权码流程后,使用授权码向 <code>/oauth/token</code> 端点发起 POST 请求以交换令牌。</p><pre><code>curl -X POST "${OAUTH_ISSUER_URL(env, {url: 'https://example.com'})}/oauth/token" \\-H "Content-Type: application/x-www-form-urlencoded" \\-d "grant_type=authorization_code" \\-d "code=YOUR_AUTHORIZATION_CODE" \\-d "redirect_uri=YOUR_REGISTERED_REDIRECT_URI" \\-d "client_id=YOUR_CLIENT_ID" \\-d "client_secret=YOUR_CLIENT_SECRET"</code></pre><p>成功的响应将包含 <code>access_token</code>, <code>id_token</code> 等。</p><hr class="section-divider"><h4>2. 使用访问令牌获取用户信息</h4><p>获得访问令牌后,向 <code>/oauth/userinfo</code> 端点请求用户信息。</p><pre><code>curl -X GET "${OAUTH_ISSUER_URL(env, {url: 'https://example.com'})}/oauth/userinfo" \\-H "Authorization: Bearer YOUR_ACCESS_TOKEN"</code></pre><p>响应内容取决于授予的权限范围 (scopes)。</p></div></div></main></div><div id="toast-container" class="toast-container bottom-right"></div><script src="${cdnBaseUrl}/js/main.js" defer></script><script>(function() {const themeToggleButton = document.getElementById('theme-toggle-button');const themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');let isDarkMode = localStorage.getItem('theme') === 'dark' ||(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);function applyTheme(dark) {document.body.classList.toggle('dark-mode', dark);if (themeToggleDarkIcon) themeToggleDarkIcon.style.display = dark ? 'block' : 'none';if (themeToggleLightIcon) themeToggleLightIcon.style.display = dark ? 'none' : 'block';}applyTheme(isDarkMode);if (themeToggleButton) {themeToggleButton.addEventListener('click', () => {isDarkMode = !isDarkMode;localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');applyTheme(isDarkMode);});}window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {if (localStorage.getItem('theme') === null) {isDarkMode = e.matches;applyTheme(isDarkMode);}});})();</script>
</body>
</html>`;
}
export function generateConsentScreenHtml(data) {const { clientName, requestedScopes, user, formAction, clientId, redirectUri, scope, state, nonce, responseType, issuerUrl: consentIssuerUrl, cdnBaseUrl: consentCdnBaseUrl, env = {} } = data;const finalCdnBaseUrl = consentCdnBaseUrl || env.CDN_BASE_URL || "https://cdn.qmwneb946.dpdns.org";const siteName = "用户中心授权";const scopesHtml = requestedScopes.map(s => `<li>${escapeHtml(s)}</li>`).join('');return `
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>授权请求 - ${siteName}</title><link rel="stylesheet" href="${finalCdnBaseUrl}/css/style.css"><style>body { display: flex; justify-content: center; align-items: center; min-height: 100vh; padding: 20px; background-color: var(--current-bg-color); color: var(--current-text-color); }.consent-container { background-color: var(--current-surface-color); padding: 30px 40px; border-radius: var(--border-radius); box-shadow: var(--box-shadow-md); max-width: 480px; width:100%; text-align: center; }.consent-container h2 { margin-top: 0; color: var(--current-heading-color); font-size: 1.8em; margin-bottom: 20px; }.consent-container p { margin-bottom: 15px; line-height: 1.6; font-size: 1.05em; }.client-name { font-weight: bold; color: var(--primary-color); }.user-identifier { font-weight: bold; }.scopes-list { list-style: inside disc; text-align: left; margin: 20px auto; padding-left: 25px; max-width: fit-content; }.scopes-list li { margin-bottom: 8px; }.consent-buttons { margin-top: 30px; display: flex; justify-content: space-between; gap: 15px; }.consent-buttons button { flex-grow: 1; }</style>
</head>
<body class="font-sans antialiased"><div class="consent-container"><h2>授权请求</h2><p>应用 <strong class="client-name">${escapeHtml(clientName)}</strong> (${escapeHtml(clientId)})</p><p>正在请求访问您 <strong class="user-identifier">(${escapeHtml(user.username || user.email)})</strong> 的以下信息:</p><ul class="scopes-list">${scopesHtml}</ul><p>您是否允许此应用访问?</p><form method="POST" action="${escapeHtml(formAction)}"><input type="hidden" name="client_id" value="${escapeHtml(clientId)}"><input type="hidden" name="redirect_uri" value="${escapeHtml(redirectUri)}"><input type="hidden" name="scope" value="${escapeHtml(scope)}"><input type="hidden" name="state" value="${escapeHtml(state || '')}"><input type="hidden" name="nonce" value="${escapeHtml(nonce || '')}"><input type="hidden" name="response_type" value="${escapeHtml(responseType)}"><div class="consent-buttons"><button type="submit" name="decision" value="deny" class="button secondary">拒绝</button><button type="submit" name="decision" value="allow" class="button primary">允许</button></div></form></div><div id="toast-container" class="toast-container bottom-right"></div><script>function escapeHtml(unsafe) {if (typeof unsafe !== 'string') return '';return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");}if (localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {document.body.classList.add('dark-mode');}</script>
</body>
</html>`;
}
export function generateErrorPageHtml(data) {const { title, message, issuerUrl, cdnBaseUrl: dataCdnBaseUrl, env = {} } = data;const cdnBaseUrl = dataCdnBaseUrl || env.CDN_BASE_URL || "https://cdn.qmwneb946.dpdns.org";const siteName = "用户中心";const finalIssuerUrl = issuerUrl || OAUTH_ISSUER_URL(env, {url: 'https://example.com'});return `
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>错误 - ${siteName}</title><link rel="stylesheet" href="${cdnBaseUrl}/css/style.css"><style>body { display: flex; justify-content: center; align-items: center; min-height: 100vh; padding: 20px; background-color: var(--current-bg-color); color: var(--current-text-color); }.error-container { background-color: var(--current-surface-color); padding: 30px 40px; border-radius: var(--border-radius); box-shadow: var(--box-shadow-md); max-width: 450px; width: 100%; text-align: center;}.error-container h1 { color: var(--danger-color); margin-top: 0; font-size: 2em; margin-bottom: 15px;}.error-container p { font-size: 1.1em; margin-bottom: 25px; }.error-container a { color: var(--primary-color); text-decoration: none; font-weight: 500; }.error-container a:hover { text-decoration: underline; }</style>
</head>
<body class="font-sans antialiased"><div class="error-container"><h1>${escapeHtml(title)}</h1><p>${escapeHtml(message)}</p><p><a href="${escapeHtml(finalIssuerUrl || '/user/login')}">返回登录或首页</a></p></div><div id="toast-container" class="toast-container bottom-right"></div><script>function escapeHtml(unsafe) {if (typeof unsafe !== 'string') return '';return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");}if (localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {document.body.classList.add('dark-mode');}</script>
</body>
</html>`;
}
function escapeHtml(unsafe) {if (typeof unsafe !== 'string') return '';return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
}
const OAUTH_ISSUER_URL = (env, request) => {if (request && request.url) {try {const url = new URL(request.url);return `${url.protocol}//${url.hostname}`;} catch (e) {}}if (env && env.EXPECTED_ISSUER_URL) {return env.EXPECTED_ISSUER_URL;}const currentHost = (typeof window !== 'undefined' && window.location) ? window.location.origin : 'https://my.qmwneb946.dpdns.org';return currentHost;
};
/*
--------------------------------------------------------------------------------qmwneb946/my/my-b30fba1fe5de85674b52ba4a36498afbff0a9c2e/cdn/css/style.css
--------------------------------------------------------------------------------
*/
:root {
--primary-color: #1abc9c;
--primary-color-dark: #16a085;
--secondary-color: #7f8c8d;
--accent-color: #3498db;
--success-color: #2ecc71;
--danger-color: #e74c3c;
--warning-color: #f39c12;
--info-color: #3498db;
--text-color-base: #34495e;
--text-color-muted: #7f8c8d;
--text-color-inverted: #ffffff;
--heading-color: #2c3e50;
--bg-color-light: #f4f6f8;
--surface-color-light: #ffffff;
--border-color-light: #dfe4e8;
--bg-color-dark: #1e272e;
--surface-color-dark: #2c3a47;
--border-color-dark: #4a5568;
--font-family-sans: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-family-mono: 'SFMono-Regular', Consolas, "Liberation Mono", Menlo, Courier, monospace;
--border-radius: 6px;
--box-shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
--box-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--box-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--sidebar-width: 260px;
--top-bar-height: 64px;
--current-bg-color: var(--bg-color-light);
--current-surface-color: var(--surface-color-light);
--current-text-color: var(--text-color-base);
--current-text-muted-color: var(--text-color-muted);
--current-heading-color: var(--heading-color);
--current-border-color: var(--border-color-light);
--current-input-bg: var(--surface-color-light);
--current-input-placeholder-color: #95a5a6;
}
body.dark-mode {
--current-bg-color: var(--bg-color-dark);
--current-surface-color: var(--surface-color-dark);
--current-text-color: #e2e8f0;
--current-text-muted-color: #a0aec0;
--current-heading-color: #e2e8f0;
--current-border-color: var(--border-color-dark);
--current-input-bg: var(--surface-color-dark);
--current-input-placeholder-color: #718096;
}
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
font-family: var(--font-family-sans);
line-height: 1.6;
margin: 0;
padding: 0;
background-color: var(--current-bg-color);
color: var(--current-text-color);
font-size: 16px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
}
.hidden { display: none !important; }
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
a {
color: var(--primary-color);
text-decoration: none;
transition: color 0.2s ease;
}
a:hover { color: var(--primary-color-dark); text-decoration: underline; }
h1, h2, h3, h4, h5, h6 {
color: var(--current-heading-color);
margin-top: 0;
margin-bottom: 0.75em;
font-weight: 600;
line-height: 1.3;
}
h1 { font-size: 2.25rem; }
h2 { font-size: 1.875rem; }
h3 { font-size: 1.5rem; }
h4 { font-size: 1.25rem; }
.app-wrapper {
display: flex;
min-height: 100vh;
}
.app-wrapper.logged-out-layout .sidebar { display: none; }
.app-wrapper.logged-out-layout .main-content { margin-left: 0; }
.app-wrapper.logged-in-layout .sidebar { display: block; }
.top-bar {
background-color: var(--current-surface-color);
height: var(--top-bar-height);
border-bottom: 1px solid var(--current-border-color);
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
box-shadow: var(--box-shadow-sm);
}
.top-bar-left { display: flex; align-items: center; }
.site-title { font-size: 1.25rem; font-weight: 600; color: var(--current-heading-color); margin-left: 10px; }
.top-bar-right { display: flex; align-items: center; gap: 10px; }
.sidebar-toggle-button, .theme-toggle-button, .user-menu-button, .messaging-button {
background: none;
border: none;
color: var(--current-text-muted-color);
padding: 8px;
border-radius: var(--border-radius);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.sidebar-toggle-button:hover, .theme-toggle-button:hover, .user-menu-button:hover, .messaging-button:hover {
background-color: color-mix(in srgb, var(--current-surface-color) 80%, var(--current-bg-color));
color: var(--current-text-color);
}
.messaging-button.active {
color: var(--primary-color);
background-color: color-mix(in srgb, var(--primary-color) 10%, transparent);
}
.h-5 { height: 1.25rem !important; }
.w-5 { width: 1.25rem !important; }
.h-6 { height: 1.5rem !important; }
.w-6 { width: 1.5rem !important; }
.mr-1 { margin-right: 0.25rem !important; }
.mr-2 { margin-right: 0.5rem !important; }
.sidebar-nav .sidebar-link svg {height: 1.25rem;width: 1.25rem;margin-right: 0.5rem;display: inline-block;vertical-align: middle;position: relative;top: -0.05em;flex-shrink: 0;
}
.unread-badge {
background-color: var(--danger-color);
color: var(--text-color-inverted);
border-radius: 50%;
padding: 0px 5px;
font-size: 0.75em;
min-width: 1.7em;
height: 1.7em;
line-height: 1.7em;
text-align: center;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: bold;
vertical-align: middle;
box-sizing: border-box;
}
.messaging-button .unread-badge {
position: absolute;
top: 4px;
right: 4px;
}
.contact-meta .unread-badge {
margin-top: 2px;
}
.auth-actions a.button { margin-left: 10px; }
.user-info-dropdown { position: relative; }
.user-menu-button .username-text { margin-left: 6px; font-weight: 500; }
.dropdown-menu {
position: absolute;
top: calc(100% + 8px);
right: 0;
background-color: var(--current-surface-color);
border: 1px solid var(--current-border-color);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow-md);
min-width: 200px;
z-index: 1010;
padding: 8px 0;
}
.dropdown-user-email { padding: 8px 16px; font-size: 0.875rem; color: var(--current-text-muted-color); }
.dropdown-divider { border-top: 1px solid var(--current-border-color); margin: 8px 0; }
.dropdown-item {
display: flex;
align-items: center;
padding: 8px 16px;
color: var(--current-text-color);
font-size: 0.95rem;
background: none;
border: none;
width: 100%;
text-align: left;
cursor: pointer;
}
.dropdown-item:hover { background-color: color-mix(in srgb, var(--current-surface-color) 85%, var(--current-bg-color)); }
.dropdown-item.logout { color: var(--danger-color); }
.dropdown-item svg {height: 1.25rem;width: 1.25rem;margin-right: 0.5rem;flex-shrink: 0;
}
.sidebar {
width: var(--sidebar-width);
background-color: var(--current-surface-color);
border-right: 1px solid var(--current-border-color);
position: fixed;
top: var(--top-bar-height);
left: 0;
bottom: 0;
padding: 20px 0;
overflow-y: auto;
z-index: 999;
transition: transform 0.3s ease-in-out;
box-shadow: var(--box-shadow-sm);
}
.sidebar.open { transform: translateX(0); }
.sidebar-nav ul { list-style: none; padding: 0; margin: 0; }
.sidebar-nav .sidebar-link {
display: flex;
align-items: center;
padding: 12px 20px;
color: var(--current-text-muted-color);
font-weight: 500;
border-left: 3px solid transparent;
transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;
}
.sidebar-nav .sidebar-link:hover {
color: var(--primary-color);
background-color: color-mix(in srgb, var(--primary-color) 10%, transparent);
}
.sidebar-nav .sidebar-link.selected {
color: var(--primary-color);
background-color: color-mix(in srgb, var(--primary-color) 15%, transparent);
border-left-color: var(--primary-color);
font-weight: 600;
}
.sidebar-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
z-index: 998;
}
.main-content {
flex-grow: 1;
padding-top: var(--top-bar-height);
transition: margin-left 0.3s ease-in-out;
}
.main-content .container {
padding: 25px;
margin: 0;
max-width: none;
background-color: var(--current-bg-color);
box-shadow: none;
border-radius: 0;
}
.app-wrapper.logged-in-layout .main-content {
margin-left: var(--sidebar-width);
}
.app-wrapper.logged-out-layout .main-content {
margin-left: 0;
}
.main-content h3 {
font-size: 1.75rem;
margin-bottom: 25px;
border-bottom: 1px solid var(--current-border-color);
padding-bottom: 10px;
}
.auth-section {
display: flex;
justify-content: center;
align-items: flex-start;
padding-top: 40px;
}
.form-container {
background-color: var(--current-surface-color);
padding: 30px 40px;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow-md);
width: 100%;
max-width: 450px;
}
.form-container h2 { text-align: center; margin-bottom: 25px; font-size: 1.75rem; }
.form-group { margin-bottom: 20px; }
.form-group label {
display: block;
margin-bottom: 6px;
font-weight: 500;
font-size: 0.9rem;
}
input[type="email"], input[type="password"], input[type="text"], input[type="tel"], input[type="url"], input[type="search"], textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--current-border-color);
border-radius: var(--border-radius);
font-size: 1rem;
color: var(--current-text-color);
background-color: var(--current-input-bg);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
input:focus, textarea:focus {
border-color: var(--primary-color);
outline: 0;
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 20%, transparent);
}
input::placeholder, textarea::placeholder { color: var(--current-input-placeholder-color); }
input[readonly], input[disabled] {
background-color: color-mix(in srgb, var(--current-input-bg) 80%, var(--current-bg-color)) !important;
opacity: 0.7;
cursor: not-allowed;
}
.input-hint { font-size: 0.8rem; color: var(--current-text-muted-color); margin-top: 4px; }
.button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10px 20px;
border: 1px solid transparent;
border-radius: var(--border-radius);
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
text-align: center;
white-space: nowrap;
}
.button.primary { background-color: var(--primary-color); color: var(--text-color-inverted); border-color: var(--primary-color); }
.button.primary:hover { background-color: var(--primary-color-dark); border-color: var(--primary-color-dark); }
.button.secondary { background-color: var(--secondary-color); color: var(--text-color-inverted); border-color: var(--secondary-color); }
.button.secondary:hover { background-color: color-mix(in srgb, var(--secondary-color) 85%, black); border-color: color-mix(in srgb, var(--secondary-color) 85%, black); }
.button.success { background-color: var(--success-color); color: var(--text-color-inverted); border-color: var(--success-color); }
.button.success:hover { background-color: color-mix(in srgb, var(--success-color) 85%, black); border-color: color-mix(in srgb, var(--success-color) 85%, black); }
.button.danger { background-color: var(--danger-color); color: var(--text-color-inverted); border-color: var(--danger-color); }
.button.danger:hover { background-color: color-mix(in srgb, var(--danger-color) 85%, black); border-color: color-mix(in srgb, var(--danger-color) 85%, black); }
.button.full-width { width: 100%; margin-top: 10px; }
.button.small { padding: 8px 16px; font-size: 0.875rem; }
.button svg {height: 1.25rem;width: 1.25rem;margin-right: 0.25rem;
}
.form-actions { margin-top: 20px; display: flex; gap: 10px; justify-content: flex-start; }
.toggle-link { text-align: center; margin-top: 20px; font-size: 0.9rem; }
.account-content .tab-pane { padding: 15px; }
.setting-block { margin-bottom: 30px; padding: 20px; background-color: var(--current-surface-color); border-radius: var(--border-radius); box-shadow: var(--box-shadow-sm); }
.setting-block h4 { font-size: 1.15rem; margin-bottom: 15px; color: var(--current-heading-color); }
.description-text { font-size: 0.9rem; color: var(--current-text-muted-color); margin-bottom: 15px; }
.status-display { margin-bottom: 15px; font-size: 0.95rem; }
#2fa-current-status { font-weight: bold; }
#2fa-controls button { margin-right: 10px; }
.otpauth-uri-text {
word-break: break-all; font-family: var(--font-family-mono);
background: color-mix(in srgb, var(--current-bg-color) 90%, var(--current-surface-color));
padding: 10px; border-radius: 4px; margin: 10px 0; display: block;
border: 1px dashed var(--current-border-color);
}
#qrcode-display {
background: white; padding: 10px; border: 1px solid var(--border-color-light);
display: inline-block; margin: 15px auto;
}
body.dark-mode #qrcode-display { background: var(--surface-color-light); border-color: var(--border-color-light); }
.api-key-display { margin-top: 15px; }
.api-key-value-container { display: flex; align-items: center; margin-bottom: 10px; }
.api-key-value-container input[type="text"] { flex-grow: 1; margin-right: 10px; }
.external-link { color: var(--accent-color); font-weight: 500; }
.external-link:hover { color: color-mix(in srgb, var(--accent-color) 80%, black); }
hr.section-divider { border: none; border-top: 1px solid var(--current-border-color); margin: 30px 0; }
.security-settings-list {
list-style: none;
padding: 0;
margin: 0;
}
.security-setting-item {
background-color: var(--current-surface-color);
border: 1px solid var(--current-border-color);
border-radius: var(--border-radius);
margin-bottom: 15px;
box-shadow: var(--box-shadow-sm);
}
.setting-entry {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
cursor: pointer;
font-weight: 500;
color: var(--current-heading-color);
transition: background-color 0.2s ease;
}
.setting-entry:hover {
background-color: color-mix(in srgb, var(--current-surface-color) 95%, var(--current-bg-color));
}
.setting-entry .entry-title {
flex-grow: 1;
}
.setting-entry .entry-status {
font-size: 0.85rem;
color: var(--current-text-muted-color);
margin-right: 10px;
}
.setting-entry .entry-arrow {
font-size: 0.8rem;
transition: transform 0.2s ease-in-out;
}
.setting-entry.open .entry-arrow {
transform: rotate(90deg);
}
.setting-content-panel {
padding: 0 20px 20px 20px;
border-top: 1px solid var(--current-border-color);
background-color: color-mix(in srgb, var(--current-surface-color) 98%, var(--current-bg-color));
}
.application-card {
background-color: color-mix(in srgb, var(--current-surface-color) 95%, var(--current-bg-color));
border: 1px solid var(--current-border-color);
border-radius: var(--border-radius);
padding: 15px; margin-bottom: 15px;
}
.application-card h4 { font-size: 1.1em; color: var(--primary-color); margin-top: 0; margin-bottom: 8px; }
.application-card p { font-size: 0.9em; margin-bottom: 5px; color: var(--current-text-muted-color); word-break: break-word; }
.application-card code {
background-color: color-mix(in srgb, var(--current-text-color) 10%, transparent);
padding: 2px 5px; border-radius: 3px; font-family: var(--font-family-mono); word-break: break-all;
}
.new-client-secret-warning {
background-color: var(--warning-color); color: var(--text-color-inverted);
padding: 10px; border-radius: var(--border-radius); margin-top: 15px; font-size: 0.9em;
}
body.dark-mode .new-client-secret-warning {
background-color: color-mix(in srgb, var(--warning-color) 70%, black);
color: var(--current-text-color);
}
.messaging-tab-header {
display: flex;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid var(--current-border-color);
}
.messaging-tab-header h3 {
margin-bottom: 0;
border-bottom: none;
padding-bottom: 0;
font-size: 1.75rem;
}
.messaging-tab-header svg {
color: var(--primary-color);
height: 28px;
width: 28px;
margin-right: 10px;
}
.new-conversation-trigger {
margin-bottom: 25px;
display: flex;
gap: 10px;
align-items: center;
}
.new-conversation-trigger input[type="email"] {
flex-grow: 1;
font-size: 0.95rem;
padding: 10px 12px;
}
.new-conversation-trigger button.button.small {
padding: 10px 15px;
font-size: 0.9rem;
flex-shrink: 0;
}
.messaging-layout-new {
display: flex;
height: calc(100vh - var(--top-bar-height) - 230px);
min-height: 450px;
border: 1px solid var(--current-border-color);
border-radius: var(--border-radius);
background-color: var(--current-surface-color);
overflow: hidden;
box-shadow: var(--box-shadow-sm);
}
.messaging-contacts-panel {
width: 320px;
min-width: 280px;
border-right: 1px solid var(--current-border-color);
display: flex;
flex-direction: column;
background-color: var(--current-surface-color);
}
.contact-search-bar {
padding: 12px 15px;
border-bottom: 1px solid var(--current-border-color);
display: flex;
gap: 8px;
background-color: color-mix(in srgb, var(--current-surface-color) 95%, var(--current-bg-color));
}
.contact-search-bar input[type="search"] {
flex-grow: 1;
padding: 8px 10px;
font-size: 0.9rem;
}
.contact-search-bar button {
padding: 8px 12px;
font-size: 0.85rem;
}
.recent-contacts-title {
padding: 12px 15px;
font-size: 0.75rem;
font-weight: 600;
color: var(--current-text-muted-color);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 0;
border-bottom: 1px solid var(--current-border-color);
background-color: color-mix(in srgb, var(--current-surface-color) 98%, var(--current-bg-color));
}
.contact-list {
flex-grow: 1;
overflow-y: auto;
list-style-type: none;
padding: 0;
margin: 0;
}
.contact-list li {
display: flex;
align-items: center;
padding: 10px 15px;
cursor: pointer;
border-bottom: 1px solid var(--current-border-color);
transition: background-color 0.2s ease;
}
.contact-list li:last-child { border-bottom: none; }
.contact-list li:hover { background-color: color-mix(in srgb, var(--current-surface-color) 90%, var(--current-bg-color)); }
.contact-list li.selected { background-color: var(--primary-color); color: var(--text-color-inverted); }
.contact-list li.selected .contact-name,
.contact-list li.selected .contact-last-message,
.contact-list li.selected .contact-meta .contact-time,
.contact-list li.selected .unread-badge { color: var(--text-color-inverted) !important; }
.contact-list li.selected .contact-avatar { border: 2px solid var(--text-color-inverted); }
.contact-avatar {
width: 38px;
height: 38px;
border-radius: 50%;
background-color: var(--secondary-color);
color: var(--text-color-inverted);
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 12px;
font-size: 1rem;
overflow: hidden;
flex-shrink: 0;
}
.contact-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.contact-info {
flex-grow: 1;
overflow: hidden;
}
.contact-name {
font-weight: 500;
display: block; margin-bottom: 2px; color: var(--current-heading-color);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
font-size: 0.95rem;
}
.contact-last-message {
font-size: 0.8rem;
color: var(--current-text-muted-color);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display:block;
}
.contact-meta {
margin-left: auto;
text-align: right;
font-size: 0.75rem;
color: var(--current-text-muted-color);
min-width: 50px;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
line-height: 1.3;
}
.contact-time {
display: block;
margin-bottom: 3px;
}
.message-display-panel {
flex-grow: 1;
display: flex;
flex-direction: column;
background-color: var(--current-bg-color);
}
.messages-list {
flex-grow: 1; overflow-y: auto; padding: 20px;
display: flex; flex-direction: column;
}
.messages-list .messages-loading-indicator-wrapper,
.messages-list .load-more-messages-button-wrapper {display: flex;justify-content: center;padding: 10px 0;
}
.messages-list .messages-loading-indicator-wrapper .spinner {border: 3px solid var(--current-border-color);border-top: 3px solid var(--primary-color);border-radius: 50%;width: 20px;height: 20px;animation: spin 1s linear infinite;
}
.messages-list .load-more-messages-button-wrapper .load-more-messages-button {font-size: 0.8em;padding: 6px 12px;background-color: transparent;border: 1px solid var(--primary-color);color: var(--primary-color);
}
.messages-list .load-more-messages-button-wrapper .load-more-messages-button:hover {background-color: color-mix(in srgb, var(--primary-color) 10%, transparent);
}
@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }
}
.empty-messages-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
color: var(--current-text-muted-color);
height: 100%;
padding: 20px;
}
.empty-messages-placeholder svg {
margin-bottom: 15px;
opacity: 0.6;
width: 64px;
height: 64px;
}
.empty-messages-placeholder p {
font-size: 1.2em;
font-weight: 500;
margin-bottom: 8px;
color: var(--current-heading-color);
}
.empty-messages-placeholder span {
font-size: 0.9em;
max-width: 300px;
}
.message-item {
max-width: 75%; padding: 8px 12px; border-radius: 12px;
margin-bottom: 10px; line-height: 1.5; word-wrap: break-word;
}
.message-item.sent {
background-color: var(--primary-color); color: var(--text-color-inverted);
align-self: flex-end; border-bottom-right-radius: 4px;
}
.message-item.received {
background-color: var(--current-surface-color);
color: var(--current-text-color);
align-self: flex-start; border-bottom-left-radius: 4px;
border: 1px solid var(--current-border-color);
}
.message-sender { font-size: 0.75em; color: var(--current-text-muted-color); margin-bottom: 2px; display: block; }
body.dark-mode .message-item.sent .message-sender { color: rgba(255,255,255,0.6); }
.message-content { font-size: 0.95em; }
.message-content p:first-child { margin-top: 0; }
.message-content p:last-child { margin-bottom: 0; }
.message-content ul, .message-content ol { margin: 8px 0 8px 20px; padding-left: 0; }
.message-content pre {
background-color: color-mix(in srgb, var(--current-text-color) 8%, transparent);
border: 1px solid var(--current-border-color);
padding: 8px; border-radius: 4px; overflow-x: auto;
font-size: 0.85em; margin: 8px 0; font-family: var(--font-family-mono);
}
body.dark-mode .message-content pre {
background-color: color-mix(in srgb, var(--current-surface-color) 80%, var(--current-bg-color));
}
.message-time { font-size: 0.7rem; color: var(--current-text-muted-color); margin-top: 4px; text-align: right; display: block; opacity: 0.8; }
body.dark-mode .message-item.sent .message-time { color: rgba(255,255,255,0.5); }
.message-input-area {
display: flex; padding: 15px;
border-top: 1px solid var(--current-border-color);
background-color: var(--current-surface-color);
}
.message-input-area textarea {
flex-grow: 1; padding: 10px;
border: 1px solid var(--current-border-color);
border-radius: var(--border-radius);
resize: none; margin-right: 10px;
font-family: var(--font-family-sans); font-size: 0.95rem;
min-height: 42px; max-height: 120px; overflow-y: auto;
background-color: var(--current-input-bg); color: var(--current-text-color);
}
.message-input-area button { padding: 10px 18px; }
.api-usage-section pre {
background-color: color-mix(in srgb, var(--current-bg-color) 95%, var(--current-surface-color));
color: var(--current-text-color);
padding: 15px; border-radius: var(--border-radius); overflow-x: auto;
font-family: var(--font-family-mono); font-size: 0.875em;
border: 1px solid var(--current-border-color); margin-bottom: 15px;
}
.api-usage-section code.inline-code {
background-color: color-mix(in srgb, var(--current-text-color) 10%, transparent);
padding: 2px 5px; border-radius: 3px; font-family: var(--font-family-mono);
}
.admin-panel-table { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 0.9em; }
.admin-panel-table th, .admin-panel-table td { border: 1px solid var(--current-border-color); padding: 8px 12px; text-align: left; }
.admin-panel-table th { background-color: var(--current-surface-color); font-weight: 600; }
.admin-panel-table tr:nth-child(even) { background-color: color-mix(in srgb, var(--current-surface-color) 95%, var(--current-bg-color));}
.admin-panel-table td code { background-color: color-mix(in srgb, var(--current-text-color) 10%, transparent); padding: 2px 4px; border-radius: 3px; font-family: var(--font-family-mono); }
.status-active { color: var(--success-color); font-weight: bold; }
.status-inactive, .status-suspended { color: var(--danger-color); font-weight: bold; }
.admin-actions button { margin-right: 5px; }
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
top: 0;
height: 100vh;
border-right: 1px solid var(--current-border-color);
}
.sidebar.open {
transform: translateX(0);
}
.app-wrapper.logged-in-layout .main-content {
margin-left: 0;
}
.app-wrapper.logged-out-layout .main-content {
margin-left: 0;
}
.messaging-layout-new {
flex-direction: column;
height: calc(100vh - var(--top-bar-height) - 70px);
}
.messaging-contacts-panel {
width: 100%; max-width: none; height: 40%; min-height: 250px;
border-right: none; border-bottom: 1px solid var(--current-border-color);
}
.message-display-panel { height: 60%; }
}
@media (min-width: 769px) {
.sidebar-toggle-button { display: none; }
.sidebar-overlay { display: none !important; }
.sidebar { transform: translateX(0); }
}
#login-form-el .cf-turnstile,
#register-form-el .cf-turnstile {
margin-left: 30px;
margin-right: 30px;
margin-top: 20px;
margin-bottom: 20px;
}
.modal {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background-color: rgba(0,0,0,0.6); display: flex;
justify-content: center; align-items: center; z-index: 2000;
opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0s linear 0.3s;
}
.modal.active { opacity: 1; visibility: visible; transition: opacity 0.3s ease; }
.modal-content {
background-color: var(--current-surface-color); color: var(--current-text-color);
padding: 25px 30px; border-radius: var(--border-radius);
box-shadow: 0 5px 15px rgba(0,0,0,0.3); width: 90%; max-width: 500px;
transform: translateY(-20px); transition: transform 0.3s ease;
}
.modal.active .modal-content { transform: translateY(0); }
.toast-container {position: fixed;bottom: 20px;right: 20px;z-index: 9999;display: flex;flex-direction: column-reverse;gap: 10px;max-height: calc(100vh - 40px);overflow-y: auto;overflow-x: hidden;
}
.toast-message {background-color: var(--current-surface-color);color: var(--current-text-color);padding: 12px 18px;border-radius: var(--border-radius);box-shadow: var(--box-shadow-lg);display: flex;align-items: center;min-width: 280px;max-width: 380px;opacity: 0;transform: translateX(calc(100% + 20px));transition: opacity 0.4s ease, transform 0.4s cubic-bezier(0.215, 0.610, 0.355, 1);position: relative;overflow: hidden;border-left: 5px solid transparent;
}
.toast-message.show {opacity: 1;transform: translateX(0);
}
.toast-icon {flex-shrink: 0;margin-right: 12px;width: 20px;height: 20px;
}
.toast-icon svg {width: 100%;height: 100%;
}
.toast-body {flex-grow: 1;font-size: 0.9rem;line-height: 1.4;
}
.toast-close-button {background: none;border: none;color: var(--current-text-muted-color);font-size: 1.2rem;font-weight: bold;cursor: pointer;padding: 0 0 0 15px;margin-left: auto;line-height: 1;align-self: flex-start;
}
.toast-close-button:hover {color: var(--current-text-color);
}
.toast-progress-bar {position: absolute;bottom: 0;left: 0;height: 4px;width: 100%;background-color: rgba(0,0,0,0.2);animation: shrink var(--toast-duration, 5000ms) linear forwards;
}
body.dark-mode .toast-progress-bar {background-color: rgba(255,255,255,0.2);
}
@keyframes shrink {from { width: 100%; }to { width: 0%; }
}
.toast-message.toast-success {border-left-color: var(--success-color);
}
.toast-message.toast-success .toast-icon svg {fill: var(--success-color);
}
.toast-message.toast-success .toast-progress-bar {background-color: var(--success-color);
}
.toast-message.toast-error {border-left-color: var(--danger-color);
}
.toast-message.toast-error .toast-icon svg {fill: var(--danger-color);
}
.toast-message.toast-error .toast-progress-bar {background-color: var(--danger-color);
}
.toast-message.toast-info {border-left-color: var(--info-color);
}
.toast-message.toast-info .toast-icon svg {fill: var(--info-color);
}
.toast-message.toast-info .toast-progress-bar {background-color: var(--info-color);
}
.toast-message.toast-warning {border-left-color: var(--warning-color);
}
.toast-message.toast-warning .toast-icon svg {fill: var(--warning-color);
}
.toast-message.toast-warning .toast-progress-bar {background-color: var(--warning-color);
}
/*
--------------------------------------------------------------------------------qmwneb946/my/my-b30fba1fe5de85674b52ba4a36498afbff0a9c2e/cdn/js/main.js
--------------------------------------------------------------------------------
*/
let TURNSTILE_SITE_KEY = '1x00000000000000000000AA';
const activeTurnstileWidgets = new Map();
let loginEmailFor2FA = null;
let currentUserData = null;
let authSection, loggedInSection, loginFormEl, registerFormEl, adminSection;
let topBarUserEmailEl, topBarUserUsernameEl, topBarUserInfoEl, topBarAuthButtonsEl, topBarLogoutButtonEl, userMenuButtonEl, userDropdownMenuEl, topBarAccountLinkEl, topBarAdminLinkEl;
let sidebarEl, sidebarToggleEl, mainContentContainerEl, sidebarOverlayEl;
let accountTabLinks = [], adminTabLinks = [];
let tabPanes = [];
let themeToggleButton, themeToggleDarkIcon, themeToggleLightIcon;
let unreadMessagesIndicator;
let appWrapper;
let topBarMessagingButton;
let userPresenceSocket = null;
const pathToPaneIdMap = {'/user/profile': 'tab-content-personal-info','/user/security': 'tab-content-security-settings','/user/api-keys': 'tab-content-api-keys','/user/applications': 'tab-content-my-applications','/user/messaging': 'tab-content-messaging','/user/admin': 'tab-content-admin-users','/user/admin/users': 'tab-content-admin-users','/user/admin/apps': 'tab-content-admin-apps'
};
const icons = {success: `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="check-circle" class="svg-inline--fa fa-check-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"></path></svg>`,error: `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="times-circle" class="svg-inline--fa fa-times-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z"></path></svg>`,info: `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="info-circle" class="svg-inline--fa fa-info-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"></path></svg>`,warning: `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="exclamation-triangle" class="svg-inline--fa fa-exclamation-triangle fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.982 12.654z"></path></svg>`
};
function renderTurnstile(containerElement) {if (!containerElement || !window.turnstile || typeof window.turnstile.render !== 'function') return;if (!TURNSTILE_SITE_KEY) { return; }if (activeTurnstileWidgets.has(containerElement)) {try { turnstile.remove(activeTurnstileWidgets.get(containerElement)); } catch (e) { }activeTurnstileWidgets.delete(containerElement);}containerElement.innerHTML = '';try {const widgetId = turnstile.render(containerElement, {sitekey: TURNSTILE_SITE_KEY,callback: (token) => {const specificCallbackName = containerElement.getAttribute('data-callback');if (specificCallbackName && typeof window[specificCallbackName] === 'function') {window[specificCallbackName](token);}}});if (widgetId) activeTurnstileWidgets.set(containerElement, widgetId);} catch (e) { }
}
function removeTurnstile(containerElement) {if (containerElement && activeTurnstileWidgets.has(containerElement)) {try { turnstile.remove(activeTurnstileWidgets.get(containerElement)); } catch (e) { }activeTurnstileWidgets.delete(containerElement);}
}
function resetTurnstileInContainer(containerElement) {if (containerElement && activeTurnstileWidgets.has(containerElement)) {try { turnstile.reset(activeTurnstileWidgets.get(containerElement)); } catch (e) { }} else if (containerElement) {renderTurnstile(containerElement);}
}
function showMessage(text, type = 'error', isHtml = false, duration = 5000) {let toastContainer = document.getElementById('toast-container');if (!toastContainer) {toastContainer = document.createElement('div');toastContainer.id = 'toast-container';toastContainer.className = 'toast-container bottom-right';document.body.appendChild(toastContainer);}const toast = document.createElement('div');toast.className = `toast-message toast-${type}`;toast.style.setProperty('--toast-duration', `${duration}ms`);const iconDiv = document.createElement('div');iconDiv.className = 'toast-icon';iconDiv.innerHTML = icons[type] || icons.info;const bodyDiv = document.createElement('div');bodyDiv.className = 'toast-body';if (isHtml) {bodyDiv.innerHTML = text;} else {bodyDiv.textContent = text;}const closeButton = document.createElement('button');closeButton.className = 'toast-close-button';closeButton.innerHTML = '&times;';closeButton.setAttribute('aria-label', 'close');const progressBar = document.createElement('div');progressBar.className = 'toast-progress-bar';toast.appendChild(iconDiv);toast.appendChild(bodyDiv);toast.appendChild(closeButton);toast.appendChild(progressBar);toastContainer.appendChild(toast);requestAnimationFrame(() => {setTimeout(() => {toast.classList.add('show');}, 50);});const removeToast = () => {toast.classList.remove('show');toast.addEventListener('transitionend', () => {if (toast.parentElement) {toast.parentElement.removeChild(toast);}}, { once: true });setTimeout(() => {if (toast.parentElement) {toast.parentElement.removeChild(toast);}}, 500);};const timerId = setTimeout(removeToast, duration);closeButton.onclick = () => {clearTimeout(timerId);removeToast();};
}
function clearMessages() {const toastContainer = document.getElementById('toast-container');if (toastContainer) {while (toastContainer.firstChild) {toastContainer.removeChild(toastContainer.firstChild);}}
}
async function apiCall(endpoint, method = 'GET', body = null) {const options = { method, headers: {}, credentials: 'include' };if (body) { options.headers['Content-Type'] = 'application/json'; options.body = JSON.stringify(body); }try {const response = await fetch(endpoint, options);let resultData = {};const contentType = response.headers.get("content-type");if (contentType && contentType.includes("application/json") && response.status !== 204) {try { resultData = await response.json(); } catch (e) { }}return { ok: response.ok, status: response.status, data: resultData };} catch (e) {showMessage('发生网络或服务器错误,请稍后重试。', 'error');return { ok: false, status: 0, data: { error: '网络错误' } };}
}
function updateUnreadMessagesIndicatorUI(count) {const localUnreadIndicator = document.getElementById('unread-messages-indicator');const localMessagingButton = document.getElementById('top-bar-messaging-button');if (!localUnreadIndicator || !localMessagingButton) return;if (count > 0) {localUnreadIndicator.textContent = count;localUnreadIndicator.classList.remove('hidden');localMessagingButton.classList.add('active');} else {localUnreadIndicator.textContent = '';localUnreadIndicator.classList.add('hidden');localMessagingButton.classList.remove('active');}
}
function connectUserPresenceWebSocket() {if (userPresenceSocket && (userPresenceSocket.readyState === WebSocket.OPEN || userPresenceSocket.readyState === WebSocket.CONNECTING)) {return;}if (!currentUserData || !currentUserData.email) {return;}const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';const wsUrl = `${protocol}//${window.location.host}/api/ws/user`;userPresenceSocket = new WebSocket(wsUrl);userPresenceSocket.onopen = () => {};userPresenceSocket.onmessage = (event) => {try {const message = JSON.parse(event.data);if (message.type === "CONVERSATIONS_LIST") {if (typeof window.handleConversationsListUpdate === 'function') {window.handleConversationsListUpdate(message.data);}} else if (message.type === "UNREAD_COUNT_TOTAL") {updateUnreadMessagesIndicatorUI(message.data.unread_count);} else if (message.type === "CONVERSATION_UPDATE") {if (typeof window.handleSingleConversationUpdate === 'function') {window.handleSingleConversationUpdate(message.data);}}} catch (e) {}};userPresenceSocket.onclose = (event) => {userPresenceSocket = null;if (currentUserData && currentUserData.email) {setTimeout(connectUserPresenceWebSocket, 5000);}};userPresenceSocket.onerror = (error) => {};
}
function applyTheme(isDark) {document.body.classList.toggle('dark-mode', isDark);if (themeToggleDarkIcon) themeToggleDarkIcon.style.display = isDark ? 'block' : 'none';if (themeToggleLightIcon) themeToggleLightIcon.style.display = isDark ? 'none' : 'block';const qrCodeDisplay = document.getElementById('qrcode-display');const otpAuthUriTextDisplay = document.getElementById('otpauth-uri-text-display');if (qrCodeDisplay && typeof QRCode !== 'undefined' && otpAuthUriTextDisplay) {const otpauthUri = otpAuthUriTextDisplay.textContent;if (otpauthUri && qrCodeDisplay.innerHTML.includes('canvas')) {qrCodeDisplay.innerHTML = '';new QRCode(qrCodeDisplay, {text: otpauthUri, width: 180, height: 180,colorDark: isDark ? "#e2e8f0" : "#000000",colorLight: "#ffffff",correctLevel: QRCode.CorrectLevel.H});}}
}
function toggleSidebar() {if (sidebarEl && sidebarOverlayEl && appWrapper) {const isOpen = sidebarEl.classList.toggle('open');sidebarOverlayEl.classList.toggle('hidden', !isOpen);appWrapper.classList.toggle('sidebar-open-app', isOpen);}
}
function activatePane(paneIdToActivate) {if (!paneIdToActivate) return;const allNavLinks = [...accountTabLinks, ...adminTabLinks];allNavLinks.forEach(link => {link.classList.toggle('selected', link.dataset.paneId === paneIdToActivate);});if (topBarMessagingButton) {topBarMessagingButton.classList.toggle('active', paneIdToActivate === 'tab-content-messaging');}if (!mainContentContainerEl) return;tabPanes.forEach(pane => {const turnstileDivsInPane = pane.querySelectorAll('.cf-turnstile');if (pane.id === paneIdToActivate) {pane.classList.remove('hidden');turnstileDivsInPane.forEach(div => renderTurnstile(div));mainContentContainerEl.classList.toggle('messaging-active', pane.id === 'tab-content-messaging');if (pane.id === 'tab-content-api-keys' && typeof window.initializeApiKeysTab === 'function') window.initializeApiKeysTab();if (pane.id === 'tab-content-my-applications' && typeof window.loadOauthAppsTabData === 'function') window.loadOauthAppsTabData();if (pane.id === 'tab-content-messaging' && typeof window.loadMessagingTabData === 'function') window.loadMessagingTabData();if (pane.id === 'tab-content-security-settings' && typeof window.initializeSecuritySettings === 'function') {if (currentUserData) {window.initializeSecuritySettings(currentUserData);} else {apiCall('/api/me').then(response => {if (response.ok && response.data) {currentUserData = response.data;window.initializeSecuritySettings(currentUserData);}});}}if (pane.id === 'tab-content-admin-users' && typeof window.loadAdminUsersData === 'function') window.loadAdminUsersData();if (pane.id === 'tab-content-admin-apps' && typeof window.loadAdminOauthClientsData === 'function') window.loadAdminOauthClientsData();} else {turnstileDivsInPane.forEach(div => removeTurnstile(div));pane.classList.add('hidden');}});clearMessages();const newlyCreatedApiKeyDisplayDiv = document.getElementById('newly-created-api-key-display');const newOauthClientCredentialsDiv = document.getElementById('new-oauth-client-credentials');if (newlyCreatedApiKeyDisplayDiv) newlyCreatedApiKeyDisplayDiv.classList.add('hidden');if (newOauthClientCredentialsDiv) newOauthClientCredentialsDiv.classList.add('hidden');if (window.innerWidth < 769 && sidebarEl && sidebarEl.classList.contains('open')) {toggleSidebar();}if (paneIdToActivate === 'tab-content-messaging') {if (typeof window.updateUnreadMessagesIndicator === 'function') {window.updateUnreadMessagesIndicator();} else if (userPresenceSocket && userPresenceSocket.readyState === WebSocket.OPEN) {userPresenceSocket.send(JSON.stringify({type: "REQUEST_INITIAL_STATE"}));}}
}
function displayCorrectView(userData) {clearMessages();currentUserData = userData;const isLoggedIn = !!userData?.email;const isAdmin = isLoggedIn && userData.is_admin === true;document.querySelectorAll('.cf-turnstile').forEach(div => removeTurnstile(div));if (topBarUserInfoEl) topBarUserInfoEl.classList.toggle('hidden', !isLoggedIn);if (isLoggedIn && topBarUserEmailEl) topBarUserEmailEl.textContent = userData.email || '未知邮箱';if (isLoggedIn && topBarUserUsernameEl) topBarUserUsernameEl.textContent = userData.username || '用户';if (topBarAuthButtonsEl) topBarAuthButtonsEl.classList.toggle('hidden', isLoggedIn);if (topBarMessagingButton) topBarMessagingButton.classList.toggle('hidden', !isLoggedIn);if (topBarAdminLinkEl) topBarAdminLinkEl.classList.toggle('hidden', !isAdmin);const adminTabsUl = document.getElementById('admin-tabs');if (adminTabsUl) adminTabsUl.classList.toggle('hidden', !isAdmin);const currentPath = window.location.pathname;if (isLoggedIn) {if (sidebarEl) sidebarEl.classList.remove('hidden');if (appWrapper) appWrapper.classList.add('logged-in-layout');if (appWrapper) appWrapper.classList.remove('logged-out-layout');if (['/', '/user/login', '/user/register'].includes(currentPath)) {window.location.pathname = isAdmin ? '/user/admin/users' : '/user/profile';return;}if(authSection) authSection.classList.add('hidden');if(loggedInSection) loggedInSection.classList.toggle('hidden', currentPath.startsWith('/user/admin'));if(adminSection) adminSection.classList.toggle('hidden', !currentPath.startsWith('/user/admin'));if (typeof window.initializePersonalInfoForm === 'function') window.initializePersonalInfoForm(userData);connectUserPresenceWebSocket();let paneIdToActivate = pathToPaneIdMap[currentPath];if (!paneIdToActivate) {paneIdToActivate = isAdmin ? 'tab-content-admin-users' : 'tab-content-personal-info';}if (!isAdmin && paneIdToActivate.startsWith('tab-content-admin-')) {window.location.pathname = '/user/profile'; return;}activatePane(paneIdToActivate);const allNavLinks = [...accountTabLinks, ...adminTabLinks];allNavLinks.forEach(link => {link.classList.toggle('selected', link.dataset.paneId === paneIdToActivate);});} else {if (userPresenceSocket && userPresenceSocket.readyState === WebSocket.OPEN) {userPresenceSocket.close();userPresenceSocket = null;}if (sidebarEl) sidebarEl.classList.add('hidden');if (appWrapper) appWrapper.classList.remove('logged-in-layout');if (appWrapper) appWrapper.classList.add('logged-out-layout');if (mainContentContainerEl) mainContentContainerEl.classList.remove('messaging-active');if (Object.keys(pathToPaneIdMap).includes(currentPath) || currentPath === '/user/account' || currentPath.startsWith('/user/admin')) {window.location.pathname = '/user/login'; return;}if(loggedInSection) loggedInSection.classList.add('hidden');if(adminSection) adminSection.classList.add('hidden');if(authSection) authSection.classList.remove('hidden');const login2FASection = document.getElementById('login-2fa-section');const loginFormContainer = document.getElementById('login-form');const registerFormContainer = document.getElementById('register-form');if (currentPath === '/' || currentPath === '/user/login') {if(loginFormContainer) { loginFormContainer.classList.remove('hidden'); if(loginFormEl) loginFormEl.reset(); renderTurnstile(loginFormContainer.querySelector('.cf-turnstile')); }if(registerFormContainer) registerFormContainer.classList.add('hidden');} else if (currentPath === '/user/register') {if(loginFormContainer) loginFormContainer.classList.add('hidden');if(registerFormContainer) { registerFormContainer.classList.remove('hidden'); if(registerFormEl) registerFormEl.reset(); renderTurnstile(registerFormContainer.querySelector('.cf-turnstile'));}}if(login2FASection) login2FASection.classList.add('hidden');loginEmailFor2FA = null;updateUnreadMessagesIndicatorUI(0);}
}
async function fetchAppConfigAndInitialize() {try {const response = await apiCall('/api/config');if (response.ok && response.data.turnstileSiteKey) {TURNSTILE_SITE_KEY = response.data.turnstileSiteKey;}} catch (error) { }await checkLoginStatus();
}
async function checkLoginStatus() {const { ok, status, data } = await apiCall('/api/me');displayCorrectView(ok && data.email ? data : null);const urlParams = new URLSearchParams(window.location.search);if (urlParams.has('registered') && (window.location.pathname === '/user/login' || window.location.pathname === '/')) {showMessage('注册成功!请使用您的邮箱或用户名登录。', 'success');const newUrl = new URL(window.location);newUrl.searchParams.delete('registered');window.history.replaceState({}, document.title, newUrl.toString());}
}
document.addEventListener('DOMContentLoaded', () => {authSection = document.getElementById('auth-section');loggedInSection = document.getElementById('logged-in-section');adminSection = document.getElementById('admin-section');loginFormEl = document.getElementById('login-form-el');registerFormEl = document.getElementById('register-form-el');appWrapper = document.getElementById('app-wrapper');topBarUserEmailEl = document.getElementById('top-bar-user-email');topBarUserUsernameEl = document.getElementById('top-bar-user-username');topBarUserInfoEl = document.getElementById('top-bar-user-info');topBarAuthButtonsEl = document.getElementById('top-bar-auth-buttons');topBarLogoutButtonEl = document.getElementById('top-bar-logout-button');userMenuButtonEl = document.getElementById('user-menu-button');userDropdownMenuEl = document.getElementById('user-dropdown-menu');topBarAccountLinkEl = document.getElementById('top-bar-account-link');topBarAdminLinkEl = document.getElementById('top-bar-admin-link');topBarMessagingButton = document.getElementById('top-bar-messaging-button');sidebarEl = document.getElementById('sidebar');sidebarToggleEl = document.getElementById('sidebar-toggle');mainContentContainerEl = document.getElementById('main-content').querySelector('.container');sidebarOverlayEl = document.getElementById('sidebar-overlay');accountTabLinks = Array.from(document.querySelectorAll('#account-tabs .sidebar-link'));adminTabLinks = Array.from(document.querySelectorAll('#admin-tabs .sidebar-link'));tabPanes = Array.from(document.querySelectorAll('.tab-pane'));themeToggleButton = document.getElementById('theme-toggle-button');themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');unreadMessagesIndicator = document.getElementById('unread-messages-indicator');let isDarkMode = localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);applyTheme(isDarkMode);if (themeToggleButton) {themeToggleButton.addEventListener('click', () => {isDarkMode = !isDarkMode;localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');applyTheme(isDarkMode);});}window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {if (localStorage.getItem('theme') === null) {isDarkMode = e.matches;applyTheme(isDarkMode);}});[...accountTabLinks, ...adminTabLinks].forEach(link => {link.addEventListener('click', (event) => {});});if (topBarMessagingButton) {topBarMessagingButton.addEventListener('click', (e) => {e.preventDefault();window.location.pathname = '/user/messaging';});}if (sidebarToggleEl) sidebarToggleEl.addEventListener('click', toggleSidebar);if (sidebarOverlayEl) sidebarOverlayEl.addEventListener('click', toggleSidebar);if (userMenuButtonEl && userDropdownMenuEl) {userMenuButtonEl.addEventListener('click', (event) => {event.stopPropagation();userDropdownMenuEl.classList.toggle('hidden');});document.addEventListener('click', (event) => {if (!userDropdownMenuEl.classList.contains('hidden') && !userMenuButtonEl.contains(event.target) && !userDropdownMenuEl.contains(event.target)) {userDropdownMenuEl.classList.add('hidden');}});}if (topBarAccountLinkEl) {topBarAccountLinkEl.addEventListener('click', (e) => {e.preventDefault();window.location.pathname = '/user/profile';if (userDropdownMenuEl) userDropdownMenuEl.classList.add('hidden');});}if (topBarAdminLinkEl) {topBarAdminLinkEl.addEventListener('click', (e) => {e.preventDefault();window.location.pathname = '/user/admin/users';if (userDropdownMenuEl) userDropdownMenuEl.classList.add('hidden');});}if (loginFormEl) loginFormEl.addEventListener('submit', (event) => handleAuth(event, 'login'));if (registerFormEl) registerFormEl.addEventListener('submit', (event) => handleAuth(event, 'register'));if (topBarLogoutButtonEl) topBarLogoutButtonEl.addEventListener('click', handleLogout);fetchAppConfigAndInitialize();
});
window.handleAuth = async function(event, type) {event.preventDefault(); clearMessages();const form = event.target;const turnstileContainer = form.querySelector('.cf-turnstile');const turnstileToken = form.querySelector('[name="cf-turnstile-response"]')?.value;const login2FASection = document.getElementById('login-2fa-section');const loginTotpCodeInput = document.getElementById('login-totp-code');if (!turnstileToken && turnstileContainer) {showMessage('人机验证失败,请刷新页面或稍后重试。', 'error');if (turnstileContainer) resetTurnstileInContainer(turnstileContainer);return;}let endpoint = '', requestBody = {};if (type === 'login') {const identifier = form.elements['identifier'].value, password = form.elements['password'].value;const totpCode = loginTotpCodeInput ? loginTotpCodeInput.value : '';if (!identifier || !password) { showMessage('邮箱/用户名和密码不能为空。'); return; }if (loginEmailFor2FA && totpCode) { endpoint = '/api/login/2fa-verify'; requestBody = { email: loginEmailFor2FA, totpCode }; }else { endpoint = '/api/login'; requestBody = { identifier, password, turnstileToken }; }} else {endpoint = '/api/register';const {email, username, password, confirmPassword, phoneNumber} = Object.fromEntries(new FormData(form));if (password !== confirmPassword) { showMessage('两次输入的密码不一致。'); return; }if (!email || !username || !password) { showMessage('邮箱、用户名和密码为必填项。'); return; }if (password.length < 6) { showMessage('密码至少需要6个字符。'); return; }requestBody = { email, username, password, confirmPassword, phoneNumber, turnstileToken };}const { ok, status, data } = await apiCall(endpoint, 'POST', requestBody);if (turnstileContainer) resetTurnstileInContainer(turnstileContainer);if (ok && data.success) {if (data.twoFactorRequired && data.email) {showMessage('需要两步验证。请输入验证码。', 'info'); loginEmailFor2FA = data.email;if(login2FASection) login2FASection.classList.remove('hidden'); if(loginTotpCodeInput) loginTotpCodeInput.focus();} else {form.reset();if(login2FASection) login2FASection.classList.add('hidden'); loginEmailFor2FA = null;if (type === 'login' || (type === 'login' && loginEmailFor2FA) || (data.twoFactorRequired === undefined)) {window.location.href = (data.is_admin === true) ? '/user/admin/users' : '/user/profile';} else {window.location.href = '/user/login?registered=true';}}} else {showMessage(data.error || ('操作失败 (' + status + ')'), 'error', data.details ? true : false);if (type === 'login' && loginEmailFor2FA && status !== 401 && login2FASection) { login2FASection.classList.remove('hidden'); }else if (status === 401 && data.error === '两步验证码无效' && login2FASection) {login2FASection.classList.remove('hidden'); if(loginTotpCodeInput) { loginTotpCodeInput.value = ''; loginTotpCodeInput.focus(); }} else if (login2FASection) { login2FASection.classList.add('hidden'); loginEmailFor2FA = null; }}
};
window.handleLogout = async function() {clearMessages();if (userPresenceSocket && userPresenceSocket.readyState === WebSocket.OPEN) {userPresenceSocket.close();}if (typeof window.closeActiveConversationSocket === 'function') {window.closeActiveConversationSocket();}await apiCall('/api/logout', 'POST');currentUserData = null;window.location.href = '/user/login';
};
window.turnstileCallbackLogin = function(token) { };
window.turnstileCallbackRegister = function(token) { };
window.turnstileCallbackPasteApi = function(token) { };
window.turnstileCallbackCloudPc = function(token) { };
window.turnstileCallbackOauthClient = function(token) { };
window.copyToClipboard = function(text, itemNameToCopy = '内容') {if (!text) { showMessage('没有可复制的'+itemNameToCopy+'。', 'warning'); return; }navigator.clipboard.writeText(text).then(() => {showMessage(itemNameToCopy + '已复制到剪贴板!', 'success');}).catch(err => {showMessage('复制失败: ' + err, 'error');});
};
window.escapeHtml = function(unsafe) {if (unsafe === null || typeof unsafe === 'undefined') return '';return String(unsafe).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
};
window.apiCall = apiCall;
window.showMessage = showMessage;
window.clearMessages = clearMessages;
window.renderTurnstile = renderTurnstile;
window.removeTurnstile = removeTurnstile;
window.resetTurnstileInContainer = resetTurnstileInContainer;
window.checkLoginStatus = checkLoginStatus;
window.isValidEmail = function(email) {if (typeof email !== 'string') return false;const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;return emailRegex.test(email);
};
window.updateUnreadMessagesIndicatorUI = updateUnreadMessagesIndicatorUI;

相关文章:

  • AI 编程如何让你轻松采集网站数据?
  • 第二十一章:数据治理之数据安全:数据安全的驱动因素以及常见的数据安全举措
  • 阿姆斯特朗数
  • 五大要素协同效益的量化模型与实战策略
  • 【Qt开发】容器类控件
  • 真话与假话
  • Java集合框架详解:List、Set、Map及其实现类
  • C-内存函数,动态内存
  • 人工智能概念股:最新投资机会深度解析
  • 数字人教师:开启教育智慧革新之旅
  • 02_MQ常见问题
  • 网络编程--上篇
  • Minktec 柔性弯曲传感器,灵敏捕捉坐姿弓背、精准监测行走姿态,守护儿童背部健康,为科学健身提供数据支撑,开启职业健康与背痛 AI 干预新方向。
  • 将图层为shapefile类型的文件转成PostGis类型的详细实现步骤
  • java每日精进 5.27【异步实现】
  • SQL计算列
  • vue展示修改前后对比,并显示修改标注diff
  • YOLOv2 深度解析:目标检测领域的进阶之路
  • 借教室--二分+查分
  • 柠檬(lemon)是什么东西?
  • 福田做网站/北京做网站公司哪家好
  • 上海网站定制设计图/新手怎么做电商运营
  • wap网站登陆系统/短视频代运营合作方案
  • 济宁软件开发网站建设/nba最新排行榜
  • 网站备案信息可以改吗/网络营销代运营外包公司
  • 自己做网站还能挣钱吗/百度云官网登录入口