Agent task: 开发: Phase A+E+F — CSS 全量改造 (mnote-web)

This commit is contained in:
Agent Board
2026-06-16 01:19:04 +08:00
parent 1f483e3d7c
commit 1a3db17eea
13 changed files with 7539 additions and 6552 deletions
+3 -1
View File
@@ -8,6 +8,7 @@ pub(crate) mod evidence;
mod gateway;
mod health;
mod hermes;
mod ui_debug;
mod hermes_client;
mod hermes_tools;
mod kernel;
@@ -788,7 +789,8 @@ pub fn build_router(state: AppState) -> Router {
if enable_debug_shell_routes {
router = router
.route("/tree", get(tree::tree_shell))
.route("/document-debug", get(editor::document_editor_shell));
.route("/document-debug", get(editor::document_editor_shell))
.route("/ui-debug/components", get(ui_debug::components_shell));
}
router.with_state(state)
@@ -0,0 +1,49 @@
//! MNOTE UI Debug 路由 handler
//!
//! `/ui-debug/components` 调试组件矩阵页面。
//! 仅在 `MNOTE_WEB_ENABLE_DEBUG_SHELL_ROUTES=1` 时可用。
use crate::app::AppState;
use crate::context::RequestContext;
use crate::error::WebError;
use crate::routes::web_shell::load_sidebar_tree_html;
use crate::ssr::pages::ui_debug::UiDebugComponentsPage;
use axum::extract::{Extension, State};
use axum::response::{Html, IntoResponse, Response};
/// GET /ui-debug/components
///
/// 渲染组件矩阵调试页面。
pub async fn components_shell(
State(state): State<AppState>,
Extension(context): Extension<RequestContext>,
) -> Result<Response, WebError> {
let workspace_id = "default";
let sidebar_tree_html =
load_sidebar_tree_html(state.config(), &context, workspace_id, None)
.await
.unwrap_or_default();
let body_content = crate::ssr::render_view(leptos::view! {
<UiDebugComponentsPage
sidebar_tree_html={sidebar_tree_html}
/>
});
let full_page = format!(
r#"<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UI Debug — 组件矩阵 — MNote</title>
<style>{MNOTE_CSS}</style>
</head>
<body>{body_content}</body>
</html>"#,
MNOTE_CSS = crate::ssr::MNOTE_CSS,
body_content = body_content,
);
Ok(Html(full_page).into_response())
}
@@ -129,6 +129,7 @@ pub fn PageLayout(
class="mnote-workspace-source-trigger"
data-testid="mnote-workspace-source-trigger"
data-mnote-action="open-workspace-source-menu"
data-state="closed"
aria-haspopup="menu"
aria-expanded="false"
title="切换我的空间或本地文件夹"
@@ -150,6 +151,7 @@ pub fn PageLayout(
aria-label="更多"
data-testid="mnote-account-menu-trigger"
data-mnote-action="open-account-menu"
data-state="closed"
aria-haspopup="menu"
aria-expanded="false"
>
@@ -178,7 +180,7 @@ pub fn PageLayout(
<div class="mnote-main">
<header class="wolai-topbar" data-testid="wolai-topbar">
<div class="wolai-topbar-left">
<button type="button" class="wolai-icon-button wolai-menu-button" aria-label="切换侧栏" aria-pressed="false" data-mnote-action="toggle-sidebar" data-testid="wolai-sidebar-toggle"><span class="material-symbols-outlined" data-icon="menu" aria-hidden="true"></span></button>
<button type="button" class="wolai-icon-button wolai-menu-button" aria-label="切换侧栏" data-state="closed" aria-haspopup="true" aria-expanded="false" aria-pressed="false" data-mnote-action="toggle-sidebar" data-testid="wolai-sidebar-toggle"><span class="material-symbols-outlined" data-icon="menu" aria-hidden="true"></span></button>
<nav class="wolai-breadcrumb" aria-label="页面路径">
<span class="wolai-breadcrumb-root">{ws_name.clone()}</span>
<span class="wolai-breadcrumb-separator" aria-hidden="true">""</span>
@@ -187,21 +189,21 @@ pub fn PageLayout(
</div>
<div class="wolai-topbar-actions" aria-label="页面操作">
<span class="wolai-public-pill" data-testid="wolai-public-state" hidden></span>
<button type="button" class="wolai-icon-button" title="星标置顶" aria-label="星标置顶" data-mnote-action="toggle-sidebar-shortcut" data-mnote-shortcut-kind="page"><span class="material-symbols-outlined" data-icon="star" aria-hidden="true"></span></button>
<button type="button" class="wolai-icon-button" title="星标置顶" aria-label="星标置顶" data-state="closed" aria-haspopup="true" aria-expanded="false" data-mnote-action="toggle-sidebar-shortcut" data-mnote-shortcut-kind="page"><span class="material-symbols-outlined" data-icon="star" aria-hidden="true"></span></button>
<button type="button" class="wolai-icon-button" title="演示" aria-label="演示"><span class="material-symbols-outlined" data-icon="slideshow" aria-hidden="true"></span></button>
<button type="button" class="wolai-icon-button" title="资料库问答" aria-label="资料库问答" data-testid="mnote-knowledge-rag-settings-toggle" data-mnote-action="open-knowledge-rag-settings"><span class="material-symbols-outlined" data-icon="travel_explore" aria-hidden="true"></span></button>
<button type="button" class="wolai-icon-button" title="评论" aria-label="评论"><span class="material-symbols-outlined" data-icon="comment" aria-hidden="true"></span></button>
<button type="button" class="wolai-icon-button" title="分享" aria-label="分享"><span class="material-symbols-outlined" data-icon="share" aria-hidden="true"></span></button>
<button type="button" class="wolai-icon-button" title="成员" aria-label="成员"><span class="material-symbols-outlined" data-icon="person_add" aria-hidden="true"></span></button>
<button type="button" class="wolai-icon-button" title="页面历史" aria-label="历史" data-mnote-action="open-page-history"><span class="material-symbols-outlined" data-icon="history" aria-hidden="true"></span></button>
<button type="button" class="wolai-icon-button" title="页面选项和全局选项" aria-label="更多" data-testid="wolai-page-settings-trigger" data-mnote-action="open-page-settings"><span class="material-symbols-outlined" data-icon="more_horiz" aria-hidden="true"></span></button>
<button type="button" class="wolai-icon-button" title="页面选项和全局选项" aria-label="更多" data-state="closed" aria-haspopup="menu" aria-expanded="false" data-testid="wolai-page-settings-trigger" data-mnote-action="open-page-settings"><span class="material-symbols-outlined" data-icon="more_horiz" aria-hidden="true"></span></button>
</div>
</header>
<article class="mnote-content">
{children()}
</article>
<div class="wolai-floating-actions" aria-label="浮动操作">
<button type="button" data-testid="wolai-floating-ai" class="wolai-floating-button wolai-floating-button--ai" aria-label="AI 助手" title="点击 进入空间智能问答" data-mnote-action="open-page-ai"><span class="material-symbols-outlined material-symbols-filled" data-icon="auto_awesome" aria-hidden="true"></span></button>
<button type="button" data-testid="wolai-floating-ai" class="wolai-floating-button wolai-floating-button--ai" aria-label="AI 助手" data-state="closed" aria-haspopup="dialog" aria-expanded="false" title="点击 进入空间智能问答" data-mnote-action="open-page-ai"><span class="material-symbols-outlined material-symbols-filled" data-icon="auto_awesome" aria-hidden="true"></span></button>
</div>
</div>
</div>
@@ -7,3 +7,4 @@ pub mod home;
pub mod layout;
pub mod mindmap;
pub mod search;
pub mod ui_debug;
@@ -0,0 +1,328 @@
//! MNOTE UI Debug — 组件矩阵页面 (SSR)
//!
//! 调试用组件展示页面,通过 env var `MNOTE_WEB_ENABLE_DEBUG_SHELL_ROUTES=1` 启用。
//! 包含:Button 3×3×4 矩阵、Dialog/Modal、Popover/DropdownMenu、Toast、Input/Select/Textarea。
//! 仅在生产环境关闭的调试路由下可访问。
use crate::ssr::pages::layout::PageLayout;
use leptos::prelude::*;
/// UI Debug 组件矩阵页面
///
/// 展示设计系统组件矩阵,包含主题切换。
/// 页面使用 `.ui-debug-*` 命名空间的 CSS 样式。
#[component]
pub fn UiDebugComponentsPage(
#[prop(optional)] sidebar_tree_html: Option<String>,
#[prop(optional)] workspace_name: Option<String>,
) -> impl IntoView {
let sidebar_tree_html = sidebar_tree_html.unwrap_or_default();
let workspace_name = workspace_name
.map(|v| v.trim().to_string())
.filter(|v| !v.is_empty())
.unwrap_or_else(|| "开发用户 的空间".to_string());
// Toast JS: 简单 toast 通知系统
let toast_js = r##"
(function(){
if (window.__mnoteUiDebugToastInited) return;
window.__mnoteUiDebugToastInited = true;
function showToast(message, kind) {
kind = kind || 'info';
var container = document.getElementById('ui-debug-toast-container');
if (!container) return;
var toast = document.createElement('div');
toast.className = 'ui-debug-toast ui-debug-toast--' + kind;
toast.setAttribute('role', 'status');
toast.setAttribute('aria-live', 'polite');
toast.textContent = message;
container.appendChild(toast);
requestAnimationFrame(function() { toast.classList.add('ui-debug-toast--visible'); });
setTimeout(function() {
toast.classList.remove('ui-debug-toast--visible');
setTimeout(function() { if (toast.parentNode) toast.parentNode.removeChild(toast); }, 300);
}, 2800);
}
window.__mnoteUiDebugToast = showToast;
document.addEventListener('click', function(e) {
var trigger = e.target.closest('[data-ui-debug-toast]');
if (!trigger) return;
var message = trigger.getAttribute('data-ui-debug-toast') || 'Toast message';
var kind = trigger.getAttribute('data-ui-debug-toast-kind') || 'info';
showToast(message, kind);
});
})();
"##;
view! {
<PageLayout current_nav="home" sidebar_tree_html={sidebar_tree_html} workspace_name={workspace_name.clone()}>
<div class="ui-debug-root" data-ui-debug-page="components">
// Toast container
<div id="ui-debug-toast-container" class="ui-debug-toast-container" aria-label="通知区域"></div>
<script inner_html={toast_js.to_string()}></script>
// Page header with theme toggle
<header class="ui-debug-header">
<h1 class="ui-debug-title">"MNote UI Debug — 组件矩阵"</h1>
<div class="ui-debug-theme-controls">
<button
type="button"
class="ui-debug-theme-btn"
data-ui-debug-theme="light"
title="浅色主题"
>"☀️ 浅色"</button>
<button
type="button"
class="ui-debug-theme-btn"
data-ui-debug-theme="dark"
title="深色主题"
>"🌙 深色"</button>
</div>
// Theme JS
<script inner_html={r##"
(function(){
if (window.__mnoteUiDebugThemeInited) return;
window.__mnoteUiDebugThemeInited = true;
var root = document.querySelector('.ui-debug-root');
if (!root) return;
function setTheme(theme) {
root.setAttribute('data-ui-debug-theme', theme);
root.querySelectorAll('[data-ui-debug-theme]').forEach(function(btn) {
btn.classList.toggle('ui-debug-theme-btn--active', btn.getAttribute('data-ui-debug-theme') === theme);
});
}
root.addEventListener('click', function(e) {
var btn = e.target.closest('[data-ui-debug-theme]');
if (!btn) return;
setTheme(btn.getAttribute('data-ui-debug-theme'));
});
setTheme('light');
})();
"##.to_string()}></script>
</header>
// ─── Button 3×3×4 Matrix ───
<section class="ui-debug-section">
<h2 class="ui-debug-section-title">"Buttons — 3 variant × 3 size × 4 state"</h2>
<div class="ui-debug-matrix">
{
["primary", "secondary", "outline"]
.iter()
.flat_map(|variant| {
["sm", "md", "lg"]
.iter()
.flat_map(move |size| {
["default", "hover", "active", "disabled"]
.iter()
.map(move |state| {
let is_disabled = *state == "disabled";
let class = format!(
"ui-debug-btn ui-debug-btn--{} ui-debug-btn--{}",
variant, size
);
let label = format!("{}-{}-{}", variant, size, state);
view! {
<button
type="button"
class={class.clone()}
disabled={is_disabled}
data-ui-debug-state={*state}
>{label.clone()}</button>
}
})
})
})
.collect::<Vec<_>>()
}
</div>
</section>
// ─── Dialog / Modal ───
<section class="ui-debug-section">
<h2 class="ui-debug-section-title">"Dialog / Modal"</h2>
<div class="ui-debug-row">
<button
type="button"
class="ui-debug-btn ui-debug-btn--primary ui-debug-btn--md"
data-ui-debug-dialog="confirm"
>"打开确认对话框"</button>
<button
type="button"
class="ui-debug-btn ui-debug-btn--secondary ui-debug-btn--md"
data-ui-debug-dialog="info"
>"打开信息对话框"</button>
<button
type="button"
class="ui-debug-btn ui-debug-btn--outline ui-debug-btn--md"
data-ui-debug-dialog="danger"
>"打开危险确认"</button>
</div>
// Dialog modal overlay
<div id="ui-debug-dialog-overlay" class="ui-debug-dialog-overlay" hidden data-ui-debug-dialog-overlay="true" role="dialog" aria-modal="true" aria-label="对话框">
<div class="ui-debug-dialog-panel">
<header class="ui-debug-dialog-header">
<h3 id="ui-debug-dialog-title" class="ui-debug-dialog-title"></h3>
<button type="button" class="ui-debug-dialog-close" data-ui-debug-dialog-close="true" aria-label="关闭">""</button>
</header>
<p id="ui-debug-dialog-body" class="ui-debug-dialog-body"></p>
<footer class="ui-debug-dialog-footer">
<button type="button" class="ui-debug-btn ui-debug-btn--outline ui-debug-btn--sm" data-ui-debug-dialog-close="true">"取消"</button>
<button type="button" class="ui-debug-btn ui-debug-btn--primary ui-debug-btn--sm" id="ui-debug-dialog-confirm">"确认"</button>
</footer>
</div>
</div>
// Dialog JS
<script inner_html={r##"
(function(){
if (window.__mnoteUiDebugDialogInited) return;
window.__mnoteUiDebugDialogInited = true;
var root = document.querySelector('.ui-debug-root');
if (!root) return;
var overlay = document.getElementById('ui-debug-dialog-overlay');
var titleEl = document.getElementById('ui-debug-dialog-title');
var bodyEl = document.getElementById('ui-debug-dialog-body');
var dialogs = {
confirm: { title: '', body: '' },
info: { title: '', body: '' },
danger: { title: '', body: '' }
};
function showDialog(kind) {
var d = dialogs[kind] || dialogs.info;
titleEl.textContent = d.title;
bodyEl.textContent = d.body;
overlay.removeAttribute('hidden');
}
function hideDialog() { overlay.setAttribute('hidden', ''); }
root.addEventListener('click', function(e) {
var trigger = e.target.closest('[data-ui-debug-dialog]');
if (trigger) { showDialog(trigger.getAttribute('data-ui-debug-dialog')); return; }
if (e.target.closest('[data-ui-debug-dialog-close]')) { hideDialog(); return; }
if (e.target === overlay) hideDialog();
});
document.getElementById('ui-debug-dialog-confirm').addEventListener('click', function() {
if (window.__mnoteUiDebugToast) window.__mnoteUiDebugToast('', 'success');
hideDialog();
});
})();
"##.to_string()}></script>
</section>
// ─── Popover / DropdownMenu ───
<section class="ui-debug-section">
<h2 class="ui-debug-section-title">"Popover / DropdownMenu"</h2>
<div class="ui-debug-row">
<div class="ui-debug-popover-host">
<button
type="button"
class="ui-debug-btn ui-debug-btn--primary ui-debug-btn--md"
data-ui-debug-popover="actions"
aria-haspopup="menu"
aria-expanded="false"
>"操作菜单 ▾"</button>
<div class="ui-debug-popover" hidden data-ui-debug-popover-panel="actions" role="menu">
<button type="button" class="ui-debug-popover-item" role="menuitem" data-ui-debug-toast="菜单项 1" data-ui-debug-toast-kind="info">"菜单项 1"</button>
<button type="button" class="ui-debug-popover-item" role="menuitem" data-ui-debug-toast="菜单项 2" data-ui-debug-toast-kind="success">"菜单项 2"</button>
<hr class="ui-debug-popover-separator" />
<button type="button" class="ui-debug-popover-item ui-debug-popover-item--danger" role="menuitem" data-ui-debug-toast="删除" data-ui-debug-toast-kind="error">"删除"</button>
</div>
</div>
<div class="ui-debug-popover-host">
<button
type="button"
class="ui-debug-btn ui-debug-btn--secondary ui-debug-btn--md"
data-ui-debug-popover="settings"
aria-haspopup="menu"
aria-expanded="false"
>"设置菜单 ▾"</button>
<div class="ui-debug-popover" hidden data-ui-debug-popover-panel="settings" role="menu">
<label class="ui-debug-popover-item ui-debug-popover-item--check" role="menuitemcheckbox">
<input type="checkbox" checked /> "选项 A"
</label>
<label class="ui-debug-popover-item ui-debug-popover-item--check" role="menuitemcheckbox">
<input type="checkbox" /> "选项 B"
</label>
<label class="ui-debug-popover-item ui-debug-popover-item--check" role="menuitemcheckbox">
<input type="checkbox" checked /> "选项 C"
</label>
</div>
</div>
</div>
// Popover JS
<script inner_html={r##"
(function(){
if (window.__mnoteUiDebugPopoverInited) return;
window.__mnoteUiDebugPopoverInited = true;
var root = document.querySelector('.ui-debug-root');
if (!root) return;
var activePanel = null;
function closeActive() {
if (activePanel) { activePanel.setAttribute('hidden', ''); activePanel = null; }
}
root.addEventListener('click', function(e) {
var trigger = e.target.closest('[data-ui-debug-popover]');
if (trigger) {
var name = trigger.getAttribute('data-ui-debug-popover');
var panel = root.querySelector('[data-ui-debug-popover-panel="' + name + '"]');
if (!panel) return;
if (activePanel === panel) { closeActive(); return; }
closeActive();
panel.removeAttribute('hidden');
activePanel = panel;
return;
}
var item = e.target.closest('[data-ui-debug-popover-panel]');
if (!item && activePanel) { closeActive(); }
});
document.addEventListener('click', function(e) {
if (activePanel && !root.contains(e.target)) closeActive();
});
})();
"##.to_string()}></script>
</section>
// ─── Toast triggers ───
<section class="ui-debug-section">
<h2 class="ui-debug-section-title">"Toast 通知"</h2>
<div class="ui-debug-row">
<button type="button" class="ui-debug-btn ui-debug-btn--primary ui-debug-btn--md" data-ui-debug-toast="操作成功完成!" data-ui-debug-toast-kind="success">"成功 Toast"</button>
<button type="button" class="ui-debug-btn ui-debug-btn--secondary ui-debug-btn--md" data-ui-debug-toast="请注意此提示信息" data-ui-debug-toast-kind="info">"信息 Toast"</button>
<button type="button" class="ui-debug-btn ui-debug-btn--outline ui-debug-btn--md" data-ui-debug-toast="发生了一个错误" data-ui-debug-toast-kind="error">"错误 Toast"</button>
<button type="button" class="ui-debug-btn ui-debug-btn--primary ui-debug-btn--sm" data-ui-debug-toast="警告:即将超时" data-ui-debug-toast-kind="warning">"警告 Toast"</button>
</div>
</section>
// ─── Input / Select / Textarea ───
<section class="ui-debug-section">
<h2 class="ui-debug-section-title">"Input / Select / Textarea"</h2>
<div class="ui-debug-form-grid">
<div class="ui-debug-field">
<label class="ui-debug-label" for="ui-debug-input-text">"文本输入"</label>
<input id="ui-debug-input-text" type="text" class="ui-debug-input" placeholder="输入文本..." />
</div>
<div class="ui-debug-field">
<label class="ui-debug-label" for="ui-debug-input-email">"邮箱输入"</label>
<input id="ui-debug-input-email" type="email" class="ui-debug-input" placeholder="email@example.com" />
</div>
<div class="ui-debug-field">
<label class="ui-debug-label" for="ui-debug-input-disabled">"禁用输入"</label>
<input id="ui-debug-input-disabled" type="text" class="ui-debug-input" placeholder="已禁用" disabled />
</div>
<div class="ui-debug-field">
<label class="ui-debug-label" for="ui-debug-select">"下拉选择"</label>
<select id="ui-debug-select" class="ui-debug-select">
<option value="">"请选择..."</option>
<option value="a">"选项 A"</option>
<option value="b">"选项 B"</option>
<option value="c">"选项 C"</option>
</select>
</div>
<div class="ui-debug-field ui-debug-field--wide">
<label class="ui-debug-label" for="ui-debug-textarea">"文本区域"</label>
<textarea id="ui-debug-textarea" class="ui-debug-textarea" placeholder="输入多行文本..." rows="4"></textarea>
</div>
</div>
</section>
</div>
</PageLayout>
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,873 @@
/* ===== 基础重置 ===== */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* ===== 基础排版 ===== */
html, body {
height: 100%;
}
body {
font-family: var(--wolai-font-sans);
color: var(--wolai-text-primary);
background: var(--wolai-bg);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
color: var(--wolai-accent);
text-decoration: none;
cursor: pointer;
}
a:hover {
color: var(--wolai-accent-hover);
}
[hidden] {
display: none !important;
}
.mnote-symbol {
width: 18px;
height: 18px;
display: inline-flex;
flex: 0 0 auto;
color: currentColor;
fill: none;
stroke: currentColor;
stroke-width: 1.9;
stroke-linecap: round;
stroke-linejoin: round;
vertical-align: -0.18em;
}
.mnote-symbol[data-icon="home"] {
stroke-width: 2;
}
.mnote-symbol--document {
width: 56px;
height: 56px;
stroke-width: 1.35;
}
.material-symbols-outlined {
font-family: var(--wolai-font-sans);
font-weight: normal;
font-style: normal;
font-size: 20px;
line-height: 1;
letter-spacing: 0;
text-transform: none;
display: inline-flex;
align-items: center;
justify-content: center;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: "liga";
-webkit-font-smoothing: antialiased;
font-variation-settings: "FILL" 0, "wght" 400, "GRAD" 0, "opsz" 24;
}
.material-symbols-outlined::before {
content: "";
display: block;
width: 1em;
height: 1em;
background: currentColor;
-webkit-mask: var(--mnote-icon-mask) center / contain no-repeat;
mask: var(--mnote-icon-mask) center / contain no-repeat;
}
.material-symbols-outlined[data-icon="search"]::before { content: "⌕"; }
.material-symbols-outlined[data-icon="account_tree"]::before { content: "⌘"; }
.material-symbols-outlined[data-icon="bolt"]::before { content: "ϟ"; }
.material-symbols-outlined[data-icon="help"]::before { content: "?"; }
.material-symbols-outlined[data-icon="inventory_2"]::before { content: "▣"; }
.material-symbols-outlined[data-icon="more_horiz"]::before { content: "…"; }
.material-symbols-outlined[data-icon="menu"]::before { content: "☰"; }
.material-symbols-outlined[data-icon="home"]::before { content: "⌂"; }
.material-symbols-outlined[data-icon="star"]::before { content: "☆"; }
.material-symbols-outlined[data-icon="history"]::before { content: "◷"; }
.material-symbols-outlined[data-icon="auto_awesome"]::before { content: "✦"; }
.material-symbols-outlined[data-icon="folder_open"]::before,
.material-symbols-outlined[data-icon="drive_file_move"]::before { content: "▱"; }
.material-symbols-outlined[data-icon="delete"]::before { content: "⌫"; }
.material-symbols-outlined[data-icon="right_panel_open"]::before,
.material-symbols-outlined[data-icon="open_in_new"]::before { content: "↗"; }
.material-symbols-outlined[data-icon="share"]::before { content: "⇪"; }
.material-symbols-outlined[data-icon="link"]::before { content: "∞"; }
.material-symbols-outlined[data-icon="content_copy"]::before,
.material-symbols-outlined[data-icon="file_copy"]::before { content: "⧉"; }
.material-symbols-outlined[data-icon="tag"]::before { content: "#"; }
.material-symbols-outlined[data-icon="edit"]::before { content: "✎"; }
.material-symbols-outlined[data-icon="add"]::before { content: "+"; }
.material-symbols-outlined[data-icon="manage_search"]::before { content: "⌕"; }
.material-symbols-outlined[data-icon="subdirectory_arrow_right"]::before { content: "↳"; }
.material-symbols-filled {
font-variation-settings: "FILL" 1, "wght" 400, "GRAD" 0, "opsz" 24;
}
/* 本地 SVG mask 图标,避免 Google Material Symbols 字体未加载时露出英文图标名。 */
.material-symbols-outlined[data-icon]::before { content: ""; }
.material-symbols-outlined[data-icon="search"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10.8 18a7.2 7.2 0 1 1 0-14.4 7.2 7.2 0 0 1 0 14.4Z' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3Cpath d='m16 16 4.2 4.2' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="manage_search"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10.5 17.5a6.5 6.5 0 1 1 0-13 6.5 6.5 0 0 1 0 13Z' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3Cpath d='m15.5 15.5 4 4M8 8.5h5M8 11h5M8 13.5h3' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="account_tree"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6 6h4v4H6zM14 4h4v4h-4zM14 16h4v4h-4z' fill='none' stroke='black' stroke-width='1.8'/%3E%3Cpath d='M10 8h2a2 2 0 0 0 2-2M10 8h2a2 2 0 0 1 2 2v8' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="bolt"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M13 2 4.5 13h6L9 22l10.5-13h-6z' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="help"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='12' cy='12' r='9' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='M9.8 9a2.4 2.4 0 0 1 4.6 1.1c0 1.7-1.7 2-2.2 3.1' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3Ccircle cx='12' cy='17' r='1.1' fill='black'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="inventory_2"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 5.5h16v4H4zM6 9.5h12V19H6z' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3Cpath d='M9.5 13.5h5' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="more_horiz"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='5' cy='12' r='2' fill='black'/%3E%3Ccircle cx='12' cy='12' r='2' fill='black'/%3E%3Ccircle cx='19' cy='12' r='2' fill='black'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="menu"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 7h16M4 12h16M4 17h16' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="home"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 11 12 4l8 7v9H5v-9Z' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3Cpath d='M9.5 20v-6h5v6' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="star"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 3 2.7 5.4 6 .9-4.4 4.3 1 6-5.3-2.8-5.3 2.8 1-6-4.4-4.3 6-.9z' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="history"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 12a8 8 0 1 0 2.3-5.7L4 8.5' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M4 4v4.5h4.5M12 8v5l3.5 2' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="auto_awesome"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 3 1.6 5.2L19 10l-5.4 1.8L12 17l-1.6-5.2L5 10l5.4-1.8zM5.5 15.5l.8 2.2 2.2.8-2.2.8-.8 2.2-.8-2.2-2.2-.8 2.2-.8zM18.5 2.5l.7 1.8 1.8.7-1.8.7-.7 1.8-.7-1.8-1.8-.7 1.8-.7z' fill='black'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="folder_open"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M3.5 7.5h6l2 2H21v8.5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-9a1.5 1.5 0 0 1 1.5-1.5Z' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3Cpath d='M4 18 7 11h14l-3 7' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="drive_file_move"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 6h6l2 2h8v10H4z' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3Cpath d='m13 12 3 3-3 3M8 15h8' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="delete"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5 6h14M9 6V4h6v2M8 6l1 14h6l1-14M10.5 10v6M13.5 10v6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="right_panel_open"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 5h16v14H4zM14 5v14M8 12h6M11 9l3 3-3 3' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="preview"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6Z' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3Ccircle cx='12' cy='12' r='2.8' fill='none' stroke='black' stroke-width='2'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="download"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 4v10M8.5 11.5 12 15l3.5-3.5M5 19h14' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="notes"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6 5h12v14H6zM9 9h6M9 12h6M9 15h4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="open_in_new"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 6H5v13h13v-3M12 5h7v7M10 14 19 5' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="share"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='18' cy='5' r='3' fill='none' stroke='black' stroke-width='2'/%3E%3Ccircle cx='6' cy='12' r='3' fill='none' stroke='black' stroke-width='2'/%3E%3Ccircle cx='18' cy='19' r='3' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='m8.7 10.7 6.6-4.4M8.7 13.3l6.6 4.4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="comment"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5 5h14v10H9l-4 4V5Z' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="mode_comment"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5 5h14v10H9l-4 4V5Z' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="person_add"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='9' cy='8' r='3.2' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='M3.5 20a5.5 5.5 0 0 1 11 0M18 8v6M15 11h6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="slideshow"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 5h16v12H4z' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3Cpath d='m10 9 5 2-5 2zM9 21h6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="travel_explore"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='11' cy='11' r='7' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='M4 11h14M11 4a10 10 0 0 1 0 14M11 4a10 10 0 0 0 0 14M16.5 16.5 21 21' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3Cpath d='M7.5 8.5h2.5l1 1.5 2.5-.5 1 2-2 1 .5 2.5h-3l-1.5-2-2 .5' fill='none' stroke='black' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="document_scanner"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M7 3H5a2 2 0 0 0-2 2v2M17 3h2a2 2 0 0 1 2 2v2M7 21H5a2 2 0 0 1-2-2v-2M17 21h2a2 2 0 0 0 2-2v-2M7 8h10M7 12h10M7 16h7' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="article"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6 4h12v16H6zM9 8h6M9 12h6M9 16h4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="sync"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20 6v5h-5M4 18v-5h5M7.5 9A6 6 0 0 1 18 6M16.5 15A6 6 0 0 1 6 18' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="drive_file_rename_outline"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m4 17-.5 3.5L7 20l11-11-3-3zM13 8l3 3M5 6h7' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="format_paint"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5 7h10l2 3-2 3H5l-2-3zM9 13v6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="link"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9.5 14.5 14.5 9.5M10.8 6.2l1.1-1.1a4 4 0 0 1 5.7 5.7l-1.6 1.6M13.2 17.8l-1.1 1.1a4 4 0 0 1-5.7-5.7L8 11.6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="content_copy"],
.material-symbols-outlined[data-icon="file_copy"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 8h10v12H8zM6 16H4V4h10v2' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="tag"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9 4 5 20M19 4l-4 16M4 9h16M3 15h16' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="edit"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m4 17-.5 3.5L7 20l11-11-3-3zM13 8l3 3' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="add"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 5v14M5 12h14' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="subdirectory_arrow_right"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6 4v7a3 3 0 0 0 3 3h8M14 10l4 4-4 4' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="account_circle"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='12' cy='12' r='9' fill='none' stroke='black' stroke-width='2'/%3E%3Ccircle cx='12' cy='9' r='3' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='M6.8 19a5.4 5.4 0 0 1 10.4 0' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="admin_panel_settings"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 3 5 6v5c0 4.5 2.9 8 7 10 4.1-2 7-5.5 7-10V6z' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'/%3E%3Cpath d='m9 12 2 2 4-5' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="close"] { --mnote-icon-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6 6 18 18M18 6 6 18' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E"); }
.material-symbols-outlined[data-icon="radio_button_unchecked"]::before {
width: .78em;
height: .78em;
background: transparent;
border: 2px solid currentColor;
border-radius: 999px;
-webkit-mask: none;
mask: none;
}
/* ===== 滚动条(悬停时显示) ===== */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: transparent;
border-radius: 4px;
}
:hover::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.1);
}
/* ===== 页面布局:侧栏 + 内容 ===== */
.mnote-shell {
display: flex;
height: 100vh;
width: 100%;
overflow: hidden;
background: var(--wolai-bg);
}
.mnote-shell[data-mnote-sidebar-collapsed="true"] .mnote-sidebar,
.mnote-shell[data-mnote-sidebar-collapsed="true"] .wolai-sidebar,
.mnote-shell[data-mnote-sidebar-collapsed="true"] .mnote-sidebar-resizer {
display: none;
}
.wolai-workspace-shell {
--wolai-bg-sidebar: #F7F7F6;
--wolai-bg-selected: #F8E6E7;
--wolai-accent-red: #E0525B;
--mnote-sidebar-width: 288px;
}
.wolai-sidebar {
width: var(--mnote-sidebar-width);
background: var(--wolai-bg-sidebar);
}
.mnote-sidebar-resizer {
display: block;
width: 6px;
min-height: 100vh;
cursor: col-resize;
position: relative;
flex: 0 0 auto;
overscroll-behavior: none;
}
.mnote-sidebar-resizer::before {
content: "";
position: absolute;
left: 2px;
top: 0;
bottom: 0;
width: 2px;
background: rgba(27, 28, 28, 0.08);
}
.mnote-sidebar-resizer:hover::before,
html[data-mnote-sidebar-resizing="true"] .mnote-sidebar-resizer::before {
background: rgba(37, 99, 235, 0.42);
}
.wolai-sidebar-header {
height: 52px;
gap: 10px;
padding: 12px 16px 8px;
justify-content: flex-start;
position: relative;
}
.wolai-avatar {
width: 28px;
height: 28px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 4px;
background: #D6545D;
color: #fff;
font-size: 15px;
font-weight: 700;
flex: 0 0 auto;
}
.wolai-sidebar-header .sidebar-workspace-name {
max-width: 190px;
color: #202124;
font-size: 16px;
font-weight: 650;
}
.mnote-workspace-source-trigger {
min-width: 0;
flex: 1 1 auto;
display: inline-flex;
align-items: center;
gap: 4px;
border: 0;
background: transparent;
padding: 2px 4px;
border-radius: 4px;
cursor: pointer;
}
.mnote-workspace-source-trigger:hover,
.mnote-workspace-source-trigger[aria-expanded="true"] {
background: var(--wolai-bg-hover);
}
.wolai-sidebar-chevron {
margin-left: auto;
color: var(--wolai-text-secondary);
font-size: 16px;
}
.mnote-workspace-source-menu {
position: absolute;
inset-inline-start: 0;
top: calc(100% + 6px);
z-index: var(--wolai-z-dropdown);
width: 250px;
max-width: calc(100vw - 24px);
padding: 6px;
border: 1px solid rgba(27, 28, 28, 0.12);
border-radius: 8px;
background: #fff;
box-shadow: 0 16px 36px rgba(15, 23, 42, 0.18);
}
.mnote-workspace-source-menu__label {
padding: 8px 8px 4px;
color: var(--wolai-text-secondary);
font-size: 12px;
}
.mnote-workspace-source-menu__item {
width: 100%;
display: block;
border: 0;
border-radius: 6px;
background: transparent;
padding: 8px;
color: var(--wolai-text-primary);
font-size: 13px;
line-height: 1.35;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}
.mnote-workspace-source-menu__item:hover {
background: var(--wolai-bg-hover);
}
.mnote-workspace-source-menu__item--primary {
color: #1D4ED8;
}
.mnote-local-folder-dialog__recent {
display: grid;
gap: 6px;
min-width: 0;
}
.mnote-local-folder-dialog__recent button {
min-width: 0;
max-width: 100%;
border: 1px solid rgba(27, 28, 28, 0.12);
border-radius: 6px;
background: #fff;
padding: 7px 9px;
color: var(--wolai-text-primary);
font-size: 13px;
line-height: 1.4;
text-align: left;
cursor: pointer;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
}
.mnote-local-folder-dialog__recent button:hover {
background: var(--wolai-bg-hover);
}
.mnote-account-menu{inset-inline-start:auto;right:8px;top:calc(100% - 10px);width:194px}.mnote-account-menu__item{display:flex;align-items:center;gap:10px}.mnote-account-menu__logout{color:#C2410C}.mnote-account-menu__logout:hover{background:#FFF7ED}.mnote-account-menu__error{padding:6px 8px 2px;color:#B91C1C;font-size:12px}.mnote-profile-dialog{position:fixed;inset:0;z-index:var(--wolai-z-modal);display:grid;place-items:center}.mnote-profile-dialog__backdrop{position:absolute;inset:0;background:rgba(15,23,42,.22)}.mnote-profile-dialog__panel{position:relative;width:min(440px,calc(100vw - 32px));max-height:calc(100vh - 48px);overflow:auto;border:1px solid var(--wolai-border);border-radius:8px;background:#fff;box-shadow:0 18px 54px rgba(15,23,42,.18);padding:20px}.mnote-profile-dialog__header,.mnote-profile-dialog__identity,.mnote-profile-dialog__list>div,.mnote-profile-dialog__list dd{display:flex;align-items:center}.mnote-profile-dialog__header{justify-content:space-between;margin-bottom:18px}.mnote-profile-dialog__eyebrow,.mnote-profile-dialog__identity span,.mnote-profile-dialog__list dt{color:var(--wolai-text-secondary);font-size:13px}.mnote-profile-dialog__header h2{font-size:20px}.mnote-profile-dialog__close,.mnote-profile-dialog__list button{border:0;border-radius:6px;background:transparent;color:var(--wolai-text-secondary);cursor:pointer}.mnote-profile-dialog__close{width:32px;height:32px}.mnote-profile-dialog__identity{gap:12px;padding:12px;border-radius:8px;background:var(--wolai-bg-sidebar)}.mnote-profile-dialog__avatar{width:36px;height:36px;display:grid;place-items:center;border-radius:6px;background:#D6545D;color:#fff;font-weight:650}.mnote-profile-dialog__list{margin-top:16px}.mnote-profile-dialog__list>div{justify-content:space-between;gap:16px;padding:11px 0;border-bottom:1px solid var(--wolai-border)}.mnote-profile-dialog__list dd{min-width:0;gap:8px;max-width:280px;font-size:13px;text-align:right;overflow-wrap:anywhere}.mnote-profile-dialog__list code{font-family:"JetBrains Mono","SFMono-Regular",Consolas,monospace;font-size:12px;white-space:normal}
.wolai-quick-actions {
flex: 0 0 auto;
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 8px;
padding: 8px 12px 18px;
position: relative;
}
.wolai-quick-actions a,
.wolai-quick-actions button {
height: 28px;
justify-content: center;
padding: 0;
margin: 0;
color: #4B4B4B;
border: 0;
background: transparent;
cursor: pointer;
}
.wolai-quick-actions a .nav-icon,
.wolai-quick-actions button .nav-icon {
margin: 0;
}
.wolai-sidebar-section {
padding: 4px 8px 14px;
}
.wolai-section-title {
height: 28px;
display: flex;
align-items: center;
gap: 4px;
color: #B5B5B2;
font-size: 14px;
padding: 0 8px;
}
.wolai-section-icon {
color: #FFB33F;
}
.wolai-section-caret {
color: #B5B5B2;
}
.wolai-section-add {
margin-left: auto;
width: 24px;
height: 24px;
border: 0;
border-radius: 6px;
background: transparent;
color: #B5B5B2;
font-size: 22px;
line-height: 1;
cursor: pointer;
}
.wolai-section-add:hover {
background: var(--wolai-bg-hover);
color: #555;
}
.wolai-page-row {
min-height: 38px;
display: flex;
align-items: center;
gap: 8px;
border-radius: 6px;
padding: 0 12px;
color: #535353;
font-size: 17px;
line-height: 1.2;
}
.wolai-page-row:hover {
background: var(--wolai-bg-hover);
color: #202124;
}
.wolai-muted-row {
color: #747471;
}
.wolai-active-row {
background: var(--wolai-bg-selected);
color: var(--wolai-accent-red);
}
.wolai-row-icon {
width: 22px;
color: #111;
text-align: center;
flex: 0 0 auto;
}
.wolai-sidebar-footer {
margin-top: auto;
display: grid;
grid-template-columns: 1fr 1fr;
border-top: 1px solid var(--wolai-border);
background: #FBFBFA;
}
.wolai-footer-entry {
height: 46px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
color: #6D6D69;
font-size: 15px;
}
.wolai-footer-entry + .wolai-footer-entry {
border-left: 1px solid var(--wolai-border);
}
.wolai-topbar {
height: 46px;
display: flex;
align-items: center;
justify-content: space-between;
flex: 0 0 auto;
padding: 0 18px;
color: #222;
font-size: 17px;
}
.wolai-topbar-left,
.wolai-topbar-actions {
display: flex;
align-items: center;
gap: 12px;
}
.wolai-topbar-actions {
color: #222;
font-size: 20px;
}
.wolai-menu-icon {
color: #4A4A4A;
font-size: 22px;
}
.wolai-home-icon {
color: #000;
font-size: 24px;
font-weight: 700;
}
.wolai-floating-actions {
position: fixed;
right: 24px;
bottom: 24px;
display: flex;
flex-direction: column;
gap: 16px;
z-index: var(--wolai-z-dock);
}
.wolai-floating-button {
width: 50px;
height: 50px;
border: 1px solid #ECECF1;
border-radius: 50%;
background: #fff;
color: #222;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
font-size: 18px;
cursor: pointer;
}
/* 侧栏导航 */
.mnote-sidebar {
width: 288px;
flex-shrink: 0;
background: var(--wolai-bg-sidebar);
border-right: 1px solid var(--wolai-border);
display: flex;
flex-direction: column;
overflow-y: auto;
user-select: none;
}
.mnote-sidebar-header {
padding: 14px 16px 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.mnote-sidebar-brand {
font-size: 15px;
font-weight: 600;
color: var(--wolai-text-primary);
text-decoration: none;
}
.mnote-sidebar-brand:hover {
color: var(--wolai-text-primary);
}
.mnote-sidebar-nav {
flex: 1;
padding: 4px 8px;
}
.mnote-sidebar-nav.wolai-quick-actions {
flex: 0 0 auto;
}
.wolai-sidebar-body {
flex: 1 1 auto;
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
.mnote-sidebar-nav a,
.mnote-sidebar-nav button {
display: flex;
align-items: center;
height: 30px;
padding: 4px 8px;
margin-bottom: 2px;
border-radius: var(--wolai-radius);
font-size: 14px;
line-height: 1.4;
color: var(--wolai-text-primary);
text-decoration: none;
transition: none;
}
.mnote-sidebar-nav a:hover,
.mnote-sidebar-nav button:hover {
background: var(--wolai-bg-hover);
}
.mnote-sidebar-nav a.active {
background: var(--wolai-bg-active);
}
.mnote-sidebar-nav a .nav-icon,
.mnote-sidebar-nav button .nav-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
margin-right: 5px;
font-size: 18px;
flex-shrink: 0;
}
/* 工作区名称 */
.sidebar-workspace-name {
font-size: 12px;
color: var(--wolai-text-secondary);
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 侧栏页面树区 */
.sidebar-tree-section {
border-top: 1px solid var(--wolai-border);
margin-top: 4px;
padding-top: 4px;
}
.sidebar-tree-divider {
display: none;
}
.sidebar-tree {
padding: 2px 0;
}
.sidebar-tree .tree-root {
list-style: none;
padding: 0;
margin: 0;
}
.sidebar-tree .tree-empty {
padding: 6px 8px 8px;
color: var(--wolai-text-secondary);
font-size: 13px;
line-height: 1.5;
}
.sidebar-tree .tree-node {
list-style: none;
padding: 0;
margin: 0;
}
.sidebar-tree .tree-row {
display: flex;
align-items: center;
height: 30px;
padding: 0 8px;
border-radius: var(--wolai-radius);
cursor: pointer;
font-size: 14px;
color: var(--wolai-text-primary);
gap: 2px;
}
.sidebar-tree .tree-row:hover {
background: var(--wolai-bg-hover);
}
.sidebar-tree .tree-row[data-active="true"] {
background: var(--wolai-bg-active);
}
.sidebar-tree .tree-row[data-active="true"] .tree-link-title {
font-weight: 500;
}
.sidebar-tree .tree-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
flex-shrink: 0;
border: none;
background: none;
cursor: pointer;
font-size: 12px;
color: var(--wolai-text-secondary);
padding: 0;
border-radius: 3px;
}
.sidebar-tree .tree-toggle:hover {
background: var(--wolai-bg-hover);
}
.sidebar-tree .tree-spacer {
display: inline-flex;
width: 20px;
height: 20px;
flex-shrink: 0;
}
.sidebar-tree .tree-kind-badge {
display: none;
}
.sidebar-tree .tree-link {
flex: 1;
display: flex;
align-items: center;
border: none;
background: none;
cursor: pointer;
padding: 0 4px;
font-size: 14px;
color: var(--wolai-text-primary);
overflow: hidden;
min-width: 0;
}
.sidebar-tree .tree-link-title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sidebar-tree .tree-actions {
display: none;
gap: 2px;
flex-shrink: 0;
}
.sidebar-tree .tree-row:hover .tree-actions {
display: flex;
}
.sidebar-tree .tree-action {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border: none;
background: none;
cursor: pointer;
font-size: 14px;
color: var(--wolai-text-secondary);
border-radius: 3px;
padding: 0;
}
.sidebar-tree .tree-action:hover {
background: var(--wolai-bg-hover);
}
.sidebar-tree .tree-children {
list-style: none;
padding: 0;
margin: 0;
}
.sidebar-tree .tree-children--collapsed {
display: none;
}
/* 树行缩进 — 通过嵌套深度自动偏移 */
.sidebar-tree .tree-node .tree-children .tree-row {
padding-left: 28px;
}
.sidebar-tree .tree-node .tree-children .tree-children .tree-row {
padding-left: 48px;
}
.sidebar-tree .tree-node .tree-children .tree-children .tree-children .tree-row {
padding-left: 68px;
}
.sidebar-tree .tree-node .tree-children .tree-children .tree-children .tree-children .tree-row {
padding-left: 88px;
}
/* 内容区域 */
.mnote-main {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
background: var(--wolai-bg);
}
.mnote-content {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 0;
}
.mnote-content:has(.document-workspace) {
overflow: hidden;
}
/* ===== data-state 通用规则 ===== */
/* popover / dropdown / dialog 打开/关闭状态 */
[data-state="open"] { display: block; }
[data-state="closed"] { display: none !important; }
/* dialog/modal 状态 */
.mnote-profile-dialog[data-state="open"],
.ui-debug-dialog-overlay[data-state="open"],
.wolai-search-overlay[data-state="open"] {
display: grid;
}
.mnote-profile-dialog[data-state="closed"],
.ui-debug-dialog-overlay[data-state="closed"],
.wolai-search-overlay[data-state="closed"] {
display: none !important;
}
/* popover/dropdown 状态 */
.wolai-page-settings-popover[data-state="open"],
.mnote-workspace-source-menu[data-state="open"],
.ui-debug-popover[data-state="open"] {
display: block;
}
.wolai-page-settings-popover[data-state="closed"],
.mnote-workspace-source-menu[data-state="closed"],
.ui-debug-popover[data-state="closed"] {
display: none !important;
}
/* context menu 状态 */
.mnote-tree-context-menu[data-state="open"] {
display: block;
}
.mnote-tree-context-menu[data-state="closed"] {
display: none !important;
}
/* drawer 状态 */
.wolai-page-history-drawer[data-state="open"],
.wolai-page-ai-drawer[data-state="open"] {
display: block;
}
.wolai-page-history-drawer[data-state="closed"],
.wolai-page-ai-drawer[data-state="closed"] {
display: none !important;
}
/* aria-expanded 通用规则 */
[aria-expanded="true"] > .material-symbols-outlined.nav-icon[data-icon] {
color: var(--wolai-accent);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,186 @@
/* ===== 认证页面 ===== */
.mnote-auth {
position: relative;
display: grid;
min-height: 100vh;
place-items: center;
padding: 48px 20px;
background: #FFFFFF;
}
.mnote-auth-brand {
position: fixed;
top: 28px;
left: 34px;
display: inline-flex;
align-items: center;
gap: 10px;
color: #37352F;
font-size: 18px;
font-weight: 650;
letter-spacing: 0;
}
.mnote-auth-brand:hover {
color: #37352F;
}
.mnote-auth-brand-mark {
display: inline-flex;
width: 32px;
height: 32px;
align-items: center;
justify-content: center;
border-radius: 6px;
background: #37352F;
color: #FFF;
font-size: 18px;
font-weight: 700;
line-height: 1;
}
.mnote-auth-panel {
width: min(100%, 360px);
color: #37352F;
}
.mnote-auth-heading {
margin-bottom: 30px;
text-align: center;
}
.mnote-auth-heading h1 {
margin: 0 0 8px;
color: #37352F;
font-size: 28px;
font-weight: 650;
line-height: 1.25;
letter-spacing: 0;
}
.mnote-auth-heading p {
color: #9B9A97;
font-size: 14px;
line-height: 1.5;
}
.mnote-auth-form {
display: grid;
gap: 12px;
}
.mnote-auth-field {
display: grid;
gap: 7px;
color: #6D6D69;
font-size: 13px;
line-height: 1.3;
}
.mnote-auth-field input {
width: 100%;
height: 42px;
border: 1px solid #DCDAD6;
border-radius: 4px;
background: #FFF;
color: #37352F;
font: 400 15px/1.4 var(--wolai-font-sans);
letter-spacing: 0;
outline: none;
padding: 0 12px;
}
.mnote-auth-field input::placeholder {
color: #B5B1AA;
}
.mnote-auth-field input:focus {
border-color: #A8A39A;
box-shadow: 0 0 0 2px rgba(55, 53, 47, 0.08);
}
.mnote-auth-message {
min-height: 20px;
color: #9B9A97;
font-size: 13px;
line-height: 20px;
text-align: center;
}
.mnote-auth-message[data-type="error"] {
color: #D14343;
}
.mnote-auth-message[data-type="success"] {
color: #23834D;
}
.mnote-auth-submit {
width: 100%;
height: 42px;
border: 0;
border-radius: 4px;
background: #37352F;
color: #FFF;
cursor: pointer;
font: 600 15px/1 var(--wolai-font-sans);
letter-spacing: 0;
}
.mnote-auth-submit:hover {
background: #2F2D29;
}
.mnote-auth-submit:disabled {
cursor: default;
opacity: .58;
}
.mnote-auth-quick-login {
width: 100%;
height: 42px;
border: 1px solid #DCDAD6;
border-radius: 4px;
background: #FFF;
color: #37352F;
cursor: pointer;
font: 500 14px/1 var(--wolai-font-sans);
letter-spacing: 0;
}
.mnote-auth-quick-login:hover {
background: #F7F7F5;
}
.mnote-auth-quick-login:disabled {
cursor: default;
opacity: .58;
}
.mnote-auth-switch {
display: block;
margin: 18px auto 0;
border: 0;
background: transparent;
color: #6D6D69;
cursor: pointer;
font: 400 14px/1.4 var(--wolai-font-sans);
letter-spacing: 0;
}
.mnote-auth-switch:hover {
color: #37352F;
}
@media (max-width: 640px) {
.mnote-auth {
align-items: start;
padding-top: 132px;
}
.mnote-auth-brand {
top: 24px;
left: 22px;
}
}
@@ -0,0 +1,70 @@
/* ===== 首页 ===== */
.mnote-home {
max-width: 720px;
margin: 0 auto;
padding: 72px 64px;
}
.mnote-home-icon {
width: 96px;
height: 96px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 22px;
color: #000;
font-size: 92px;
line-height: 1;
}
.mnote-home h1 {
text-align: center;
font-size: 44px;
font-weight: 700;
letter-spacing: 0;
margin-bottom: 34px;
color: var(--wolai-text-primary);
}
.mnote-home p {
font-size: 16px;
color: var(--wolai-text-secondary);
line-height: 1.6;
margin-bottom: 32px;
}
.mnote-home-links {
display: flex;
flex-direction: column;
gap: 8px;
max-width: 360px;
margin: 0 auto;
}
.mnote-home-links a {
display: flex;
align-items: center;
gap: 8px;
padding: 2px 0;
border-radius: var(--wolai-radius);
font-size: 21px;
font-weight: 650;
color: var(--wolai-text-primary);
text-decoration: none;
transition: background-color 80ms ease;
}
.mnote-home-links a:hover {
background: var(--wolai-bg-hover);
}
.mnote-home-links a::before {
content: none;
}
.mnote-home-links a span {
width: 26px;
text-align: center;
color: #111;
}
@@ -0,0 +1,87 @@
/* ===== 文档壳 ===== */
.document-shell {
width: 100%;
min-height: 100vh;
margin: 0 auto;
}
.document-shell-header {
padding: 48px 0 0;
margin: 0 auto 8px;
max-width: 760px;
}
.document-shell-header h1 {
font-size: 40px;
font-weight: 700;
line-height: 1.2;
color: var(--wolai-text-primary);
word-break: break-word;
padding: 3px 0;
}
#mnote-editor-island {
margin: 0 15px;
max-width: 760px;
width: calc(100% - 30px);
min-height: 300px;
padding-bottom: 48px;
}
#mnote-editor-island .bn-editor {
padding: 0;
max-width: none;
margin: 0;
}
/* ===== 搜索壳 ===== */
#mnote-search-shell {
max-width: 800px;
min-height: 100vh;
margin: 0 auto;
padding: 48px 64px;
}
#mnote-search-shell header {
margin-bottom: 24px;
}
#mnote-search-shell h1 {
font-size: 32px;
font-weight: 700;
color: var(--wolai-text-primary);
margin-bottom: 4px;
}
.search-meta,
.search-query {
color: var(--wolai-text-secondary);
font-size: 14px;
}
#mnote-search-island {
min-height: 200px;
}
/* ===== 思维导图壳 ===== */
#mnote-mindmap-shell {
max-width: 1000px;
min-height: 100vh;
margin: 0 auto;
padding: 48px 64px;
}
#mnote-mindmap-shell header {
margin-bottom: 24px;
}
#mnote-mindmap-shell h1 {
font-size: 32px;
font-weight: 700;
color: var(--wolai-text-primary);
}
#mnote-mindmap-island {
min-height: 400px;
}
@@ -0,0 +1,74 @@
:root {
/* 背景色 */
--color-basic-50: #F7F7F5;
--color-basic-75: #F0F0EE;
--color-basic-100: #EBEBEB;
--color-basic-200: #E3E3E0;
--color-basic-300: #D4D4D1;
--color-basic-400: #C4C4C1;
--color-basic-500: #A9A9A6;
--color-basic-600: #9B9A97;
--color-basic-700: #6D6D69;
--color-basic-800: #504F4B;
--color-basic-900: #37352F;
/* wolai 语义变量 */
--wolai-bg: #FFFFFF;
--wolai-bg-sidebar: var(--color-basic-50);
--wolai-bg-hover: rgba(55, 53, 47, 0.08);
--wolai-bg-active: rgba(55, 53, 47, 0.12);
--wolai-text-primary: var(--color-basic-900);
--wolai-text-secondary: var(--color-basic-600);
--wolai-border: #E9E9E8;
--wolai-brand: #22A559;
--wolai-accent: #2563eb;
--wolai-accent-hover: #1d4ed8;
--wolai-font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", "PingFang SC", Helvetica, Arial, sans-serif;
--wolai-radius: 4px;
/* z-index tokens */
--wolai-z-dock: 50;
--wolai-z-popover: 90;
--wolai-z-dropdown: 120;
--wolai-z-context-menu: 1000;
--wolai-z-modal: 1200;
/* spacing tokens */
--wolai-spacing-xs: 4px;
--wolai-spacing-sm: 8px;
--wolai-spacing-md: 12px;
--wolai-spacing-lg: 16px;
--wolai-spacing-xl: 24px;
--wolai-spacing-2xl: 32px;
/* shadow tokens */
--wolai-shadow-sm: 0 2px 4px rgba(15, 23, 42, 0.04);
--wolai-shadow-md: 0 8px 24px rgba(15, 23, 42, 0.08);
--wolai-shadow-lg: 0 18px 48px rgba(15, 23, 42, 0.12);
--wolai-shadow-popover: 0 4px 16px rgba(15, 23, 42, 0.10);
--wolai-shadow-modal: 0 18px 54px rgba(15, 23, 42, 0.18);
/* state tokens */
--wolai-state-hover-bg: rgba(55, 53, 47, 0.06);
--wolai-state-active-bg: rgba(55, 53, 47, 0.10);
--wolai-state-focus-ring: 0 0 0 2px rgba(37, 99, 235, 0.35);
--wolai-state-disabled-opacity: 0.45;
}
/* Stitch 260429 atelier tokens */
:root {
--atelier-surface: #FAF9F9;
--atelier-sidebar: #F5F5F5;
--atelier-sidebar-hover: #ECEBE9;
--atelier-sidebar-active: #E3E2E2;
--atelier-document: #FFFFFF;
--atelier-text: #1B1C1C;
--atelier-text-muted: #8A8782;
--atelier-text-soft: #B7B4AF;
--atelier-outline: #EAEAEA;
--atelier-primary: #006E28;
--atelier-primary-container: #31AA4D;
--atelier-secondary: #2367F6;
--atelier-shadow: 0 16px 32px rgba(27, 28, 28, 0.04);
}
@@ -0,0 +1,147 @@
"use strict";
const { chromium } = require("playwright");
const { BASE_URL, UI_TIMEOUT_MS, assert, ensureAuthenticated } = require("./tree-shell-smoke-helpers");
const MNOTE_WEB_ENABLE_DEBUG_SHELL_ROUTES = process.env.MNOTE_WEB_ENABLE_DEBUG_SHELL_ROUTES || "0";
if (MNOTE_WEB_ENABLE_DEBUG_SHELL_ROUTES !== "1") {
console.log("[task500] 跳过: MNOTE_WEB_ENABLE_DEBUG_SHELL_ROUTES 未设为 1 (当前值: '%s')", MNOTE_WEB_ENABLE_DEBUG_SHELL_ROUTES);
console.log("[task500] 设置 MNOTE_WEB_ENABLE_DEBUG_SHELL_ROUTES=1 并重启 mnote-web 后再运行此 smoke。");
process.exit(0);
}
const PAGE_URL = `${BASE_URL}/ui-debug/components`;
async function main() {
console.log("[task500] 启动 UI Debug 组件矩阵 smoke …");
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
try {
// 1. 认证
await ensureAuthenticated(page);
console.log("[task500] ✓ 已认证");
// 2. 导航到 /ui-debug/components
await page.goto(PAGE_URL, { waitUntil: "networkidle", timeout: UI_TIMEOUT_MS });
console.log("[task500] ✓ 页面已加载: %s", PAGE_URL);
// 3. 页面标题
const title = await page.title();
assert(title.includes("UI Debug"), `标题应包含 "UI Debug",实际: "${title}"`);
console.log("[task500] ✓ 页面标题: %s", title);
// 4. 页面根元素
const root = page.locator(".ui-debug-root");
await root.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
console.log("[task500] ✓ .ui-debug-root 可见");
// 5. Button 矩阵应当可见
const matrix = page.locator(".ui-debug-matrix");
await matrix.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
const btnCount = await matrix.locator("button").count();
assert(btnCount >= 12, `按钮矩阵应至少有 12 个按钮,实际: ${btnCount}`);
console.log("[task500] ✓ 按钮矩阵: %d 个按钮", btnCount);
// 6. 主题切换
const darkBtn = page.locator("[data-ui-debug-theme=\"dark\"]");
await darkBtn.click();
await page.waitForTimeout(200);
const theme = await root.getAttribute("data-ui-debug-theme");
assert(theme === "dark", `主题应为 dark,实际: "${theme}"`);
console.log("[task500] ✓ 主题切换: dark");
// 切回 light
const lightBtn = page.locator("[data-ui-debug-theme=\"light\"]");
await lightBtn.click();
await page.waitForTimeout(200);
const theme2 = await root.getAttribute("data-ui-debug-theme");
assert(theme2 === "light", `主题应为 light,实际: "${theme2}"`);
// 7. Dialog 对话框
const dialogTrigger = page.locator("[data-ui-debug-dialog=\"info\"]");
await dialogTrigger.click();
await page.waitForTimeout(200);
const dialogOverlay = page.locator("#ui-debug-dialog-overlay");
const overlayVisible = await dialogOverlay.isVisible();
assert(overlayVisible, "对话框应可见");
const dialogTitle = await page.locator("#ui-debug-dialog-title").textContent();
assert(dialogTitle.includes("提示"), `对话框标题应包含"提示",实际: "${dialogTitle}"`);
console.log("[task500] ✓ 对话框打开: %s", dialogTitle);
// 关闭对话框
const closeBtn = page.locator("[data-ui-debug-dialog-close=\"true\"]").first();
await closeBtn.click();
await page.waitForTimeout(200);
const hidden = await dialogOverlay.getAttribute("hidden");
assert(hidden !== null, "对话框应已关闭 (hidden)");
console.log("[task500] ✓ 对话框已关闭");
// 8. Popover 菜单
const popoverTrigger = page.locator("[data-ui-debug-popover=\"actions\"]");
await popoverTrigger.click();
await page.waitForTimeout(200);
const popoverPanel = page.locator("[data-ui-debug-popover-panel=\"actions\"]");
const popoverVisible = await popoverPanel.isVisible();
assert(popoverVisible, "Popover 菜单应可见");
const menuItems = await popoverPanel.locator("[role=\"menuitem\"]").count();
assert(menuItems >= 2, `Popover 应至少有 2 个菜单项,实际: ${menuItems}`);
console.log("[task500] ✓ Popover 打开: %d 个菜单项", menuItems);
// 关闭 popover
await page.locator("body").click({ position: { x: 10, y: 10 } });
await page.waitForTimeout(200);
// 9. Toast
const toastTrigger = page.locator("[data-ui-debug-toast-kind=\"success\"]").first();
await toastTrigger.click();
await page.waitForTimeout(400);
const toast = page.locator(".ui-debug-toast--visible");
const toastCount = await toast.count();
assert(toastCount >= 1, `Toast 应可见,实际数量: ${toastCount}`);
console.log("[task500] ✓ Toast 显示");
// 10. Input / Select / Textarea
const textInput = page.locator("#ui-debug-input-text");
await textInput.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
await textInput.fill("测试文本");
const inputValue = await textInput.inputValue();
assert(inputValue === "测试文本", `输入框值应为"测试文本",实际: "${inputValue}"`);
console.log("[task500] ✓ 文本输入: %s", inputValue);
const selectEl = page.locator("#ui-debug-select");
await selectEl.selectOption("b");
const selectValue = await selectEl.inputValue();
assert(selectValue === "b", `下拉选择值应为"b",实际: "${selectValue}"`);
console.log("[task500] ✓ 下拉选择: %s", selectValue);
const textarea = page.locator("#ui-debug-textarea");
await textarea.fill("多行\n文本");
const textareaValue = await textarea.inputValue();
assert(textareaValue === "多行\n文本", `文本区域值应包含"多行",实际: "${textareaValue}"`);
console.log("[task500] ✓ 文本区域: %s", textareaValue.replace(/\n/g, "\\n"));
// 11. CSS 变量检查
const cssVar = await root.evaluate((el) => {
const style = getComputedStyle(el);
return style.getPropertyValue("--ui-debug-primary").trim();
});
assert(cssVar.length > 0, `--ui-debug-primary 应有值,实际: "${cssVar}"`);
console.log("[task500] ✓ CSS 变量 --ui-debug-primary: %s", cssVar);
console.log("[task500] ✅ 全部断言通过");
} catch (error) {
console.error("[task500] ✗ 失败:", error.message);
try {
await page.screenshot({ path: "tmp/task500-failure.png", fullPage: true });
console.log("[task500] 失败截图: tmp/task500-failure.png");
} catch (_) {}
process.exitCode = 1;
} finally {
await browser.close();
}
}
main();