重构基线

This commit is contained in:
Agent Board
2026-06-25 21:08:17 +08:00
parent cc4f975466
commit dabaf03bd7
42 changed files with 7720 additions and 138 deletions
@@ -163,6 +163,7 @@ pub fn PageLayout(
<div hidden data-testid="mnote-admin-access-policy-template-admin" inner_html={admin_access_policy_template}></div>
<div hidden data-testid="mnote-admin-access-policy-template-user" inner_html={user_access_policy_template}></div>
<script inner_html={crate::ssr::pages::admin::ADMIN_POLICY_SCRIPT.to_string()}></script>
<script type="module" src={browser_runtime_src("mnote-ui-runtime.js")}></script>
<script type="module" src={browser_runtime_src("resource-open-runtime.js")}></script>
<script type="module" src={browser_runtime_src("local-upload-runtime.js")}></script>
<script type="module" src={browser_runtime_src("filetree-runtime.js")}></script>
@@ -206,6 +207,7 @@ pub fn PageLayout(
<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 id="mnote-portal-root" data-mnote-portal-root="true"></div>
</div>
}
}
@@ -235,6 +237,7 @@ mod tests {
// resume/journal contract checks inspect the actual shipped JS.
const SIDEBAR_PAGE_AI_RUNTIME_JS: &str =
include_str!("../../../browser/sidebar-page-ai-runtime.js");
const MNOTE_UI_RUNTIME_JS: &str = include_str!("../../../browser/mnote-ui-runtime.js");
const SIDEBAR_PAGE_AI_MARKDOWN_RUNTIME_JS: &str =
include_str!("../../../browser/sidebar-page-ai-markdown-runtime.js");
const SIDEBAR_PAGE_AI_RENDER_RUNTIME_JS: &str =
@@ -418,6 +421,15 @@ mod tests {
assert!(!html.contains(r#"data-testid="wolai-public-state">全网公开"#));
assert!(html.contains(r#"data-mnote-action="toggle-sidebar-shortcut""#));
assert!(html.contains(r#"data-mnote-shortcut-kind="page""#));
assert!(html.contains(r#"id="mnote-portal-root""#));
assert!(html.contains("/api/mnote-browser-runtime/mnote-ui-runtime.js"));
}
#[test]
fn mnote_ui_runtime_exposes_toast_api() {
assert!(MNOTE_UI_RUNTIME_JS.contains("window.mnote.toast"));
assert!(MNOTE_UI_RUNTIME_JS.contains("data-mnote-toast-region"));
assert!(MNOTE_UI_RUNTIME_JS.contains("mnote:toast"));
}
#[test]
@@ -445,6 +457,10 @@ mod tests {
html.contains("/api/mnote-browser-runtime/local-folder-event-bus-runtime.js?devHot="),
"dev:hot 下 local-folder event bus URL 必须带 cache buster,避免复用旧连接编排逻辑"
);
assert!(
html.contains("/api/mnote-browser-runtime/mnote-ui-runtime.js?devHot="),
"dev:hot 下 UI runtime URL 必须带 cache buster,避免 toast/portal 使用旧版本"
);
}
#[test]
+20
View File
@@ -13,6 +13,7 @@
/// - `styles/base.css`: 基础重置、排版、SVG 图标、滚动条、侧栏/顶栏布局
/// - `styles/pages/home.css`, `shells.css`, `auth.css`: 页面级样式
/// - `styles/components/main.css`: 核心组件样式(编辑器、侧栏、树、附件等)
/// - `styles/components/toast.css`: 全局 toast 与 portal 反馈样式
/// - `styles/components/search.css`: 搜索弹窗组件
/// - `styles/components/page-ai.css`: Page AI 仪表盘组件
/// - `styles/components/ui-debug.css`: UI Debug 组件矩阵
@@ -29,6 +30,8 @@ pub const MNOTE_CSS: &str = concat!(
"\n",
include_str!("styles/components/main.css"),
"\n",
include_str!("styles/components/toast.css"),
"\n",
include_str!("styles/components/search.css"),
"\n",
include_str!("styles/components/page-ai.css"),
@@ -140,6 +143,23 @@ mod tests {
assert!(MNOTE_CSS.contains(".mnote-tree-context-menu"));
}
#[test]
fn mnote_css_contains_ui_foundation_tokens_and_toast() {
assert!(MNOTE_CSS.contains("--mnote-btn-primary-bg"));
assert!(MNOTE_CSS.contains("--mnote-font-size-base"));
assert!(MNOTE_CSS.contains("--wolai-state-focus-ring"));
assert!(MNOTE_CSS.contains(".mnote-toast-region"));
assert!(MNOTE_CSS.contains(".mnote-toast--success"));
}
#[test]
fn mnote_css_does_not_hide_all_closed_state_triggers() {
assert!(!MNOTE_CSS.contains("[data-state=\"closed\"] { display: none"));
assert!(!MNOTE_CSS.contains("[data-state=\"open\"] { display: block"));
assert!(MNOTE_CSS.contains(".wolai-page-ai-drawer[data-state=\"closed\"]"));
assert!(MNOTE_CSS.contains(".wolai-page-settings-popover[data-state=\"closed\"]"));
}
#[test]
fn sidebar_filetree_icons_use_css_masks_instead_of_text_glyphs() {
assert!(MNOTE_CSS.contains("--mnote-filetree-icon-file"));
@@ -819,11 +819,6 @@ html[data-mnote-sidebar-resizing="true"] .mnote-sidebar-resizer::before {
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"],
@@ -2214,3 +2214,298 @@ button.wolai-page-ai-history-main span {
}
}
.wolai-page-ai-drawer[data-page-ai-opencode-host="true"] .wolai-page-ai-panel {
display: flex;
flex-direction: column;
gap: 0;
overflow: hidden;
}
.wolai-page-ai-opencode-header {
flex: 0 0 auto;
}
.wolai-page-ai-opencode-chrome {
display: flex;
flex: 0 0 auto;
flex-direction: column;
gap: 6px;
padding: 8px 12px 10px;
border-bottom: 1px solid rgba(27, 28, 28, 0.08);
background: rgba(247, 247, 245, 0.92);
}
.wolai-page-ai-opencode-row {
display: grid;
grid-template-columns: 44px minmax(0, 1fr);
gap: 6px;
align-items: baseline;
font-size: 12px;
color: rgba(27, 28, 28, 0.58);
}
.wolai-page-ai-opencode-row strong,
.wolai-page-ai-opencode-row code {
min-width: 0;
overflow: hidden;
color: rgba(27, 28, 28, 0.86);
text-overflow: ellipsis;
white-space: nowrap;
}
.wolai-page-ai-opencode-badges,
.wolai-page-ai-opencode-files {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.wolai-page-ai-opencode-badges span,
.wolai-page-ai-opencode-empty,
.wolai-page-ai-opencode-chip {
display: inline-flex;
align-items: center;
max-width: 100%;
min-height: 24px;
padding: 3px 8px;
border: 1px solid rgba(27, 28, 28, 0.08);
border-radius: 999px;
background: #fff;
color: rgba(27, 28, 28, 0.68);
font: inherit;
font-size: 12px;
}
.wolai-page-ai-opencode-chip {
cursor: pointer;
}
.wolai-page-ai-opencode-chip:hover {
border-color: rgba(35, 131, 226, 0.32);
color: var(--wolai-accent, #2383e2);
}
.wolai-page-ai-opencode-chip span {
margin-left: 6px;
color: rgba(27, 28, 28, 0.45);
}
.wolai-page-ai-opencode-frame-wrap {
position: relative;
display: flex;
flex: 1 1 auto;
min-height: 0;
overflow: hidden;
background: #fff;
}
.wolai-page-ai-opencode-iframe {
flex: 1 1 auto;
width: 100%;
min-height: 0;
border: 0;
background: #fff;
}
.wolai-page-ai-opencode-iframe[hidden] {
display: none;
}
.wolai-page-ai-opencode-iframe-fallback {
position: absolute;
inset: 0;
display: grid;
place-items: center;
padding: 24px;
color: rgba(27, 28, 28, 0.62);
text-align: center;
background: #fff;
}
.wolai-page-ai-opencode-iframe-fallback[hidden] {
display: none;
}
.wolai-page-ai-opencode-chat {
display: flex;
flex: 1 1 auto;
min-height: 0;
flex-direction: column;
background: #fff;
}
.wolai-page-ai-opencode-messages {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
padding: 12px;
}
.wolai-page-ai-opencode-message {
margin: 0 0 10px;
padding: 10px 12px;
border: 1px solid rgba(27, 28, 28, 0.08);
border-radius: 12px;
background: rgba(247, 247, 245, 0.9);
}
.wolai-page-ai-opencode-message[data-role="assistant"] {
background: #fff;
}
.wolai-page-ai-opencode-message-role {
margin-bottom: 4px;
color: rgba(27, 28, 28, 0.52);
font-size: 11px;
font-weight: 600;
}
.wolai-page-ai-opencode-message-body {
color: rgba(27, 28, 28, 0.88);
font-size: 13px;
line-height: 1.55;
}
.wolai-page-ai-opencode-composer {
display: flex;
gap: 8px;
padding: 10px 12px 12px;
border-top: 1px solid rgba(27, 28, 28, 0.08);
}
.wolai-page-ai-opencode-composer textarea {
flex: 1 1 auto;
min-height: 42px;
resize: vertical;
border: 1px solid rgba(27, 28, 28, 0.14);
border-radius: 10px;
padding: 8px 10px;
font: inherit;
}
.wolai-page-ai-opencode-send,
.wolai-page-ai-opencode-permission button {
border: 0;
border-radius: 10px;
padding: 0 12px;
background: #1f6feb;
color: #fff;
font-weight: 600;
}
.wolai-page-ai-opencode-permissions {
display: flex;
flex-direction: column;
gap: 6px;
padding: 0 12px;
}
.wolai-page-ai-opencode-permission {
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto;
gap: 6px;
align-items: center;
padding: 8px;
border: 1px solid rgba(227, 115, 14, 0.24);
border-radius: 10px;
background: rgba(255, 247, 237, 0.92);
font-size: 12px;
}
.wolai-page-ai-opencode-permission span {
overflow: hidden;
color: rgba(27, 28, 28, 0.62);
text-overflow: ellipsis;
white-space: nowrap;
}
.wolai-page-ai-opencode-sessions-wrap {
font-size: 12px;
}
.wolai-page-ai-opencode-sessions {
display: grid;
gap: 4px;
margin-top: 6px;
}
.wolai-page-ai-opencode-session-row {
display: grid;
min-width: 0;
gap: 2px;
border: 1px solid rgba(27, 28, 28, 0.08);
border-radius: 8px;
padding: 6px 8px;
background: rgba(255, 255, 255, 0.72);
color: inherit;
text-align: left;
}
.wolai-page-ai-opencode-session-row[data-active="true"] {
border-color: rgba(31, 111, 235, 0.38);
background: rgba(31, 111, 235, 0.08);
}
.wolai-page-ai-opencode-session-row span,
.wolai-page-ai-opencode-message-role span {
overflow: hidden;
color: rgba(27, 28, 28, 0.52);
text-overflow: ellipsis;
white-space: nowrap;
}
.wolai-page-ai-opencode-message-role {
display: flex;
justify-content: space-between;
gap: 8px;
}
.wolai-page-ai-opencode-part {
margin-top: 8px;
border: 1px solid rgba(27, 28, 28, 0.08);
border-radius: 10px;
padding: 8px;
background: rgba(250, 250, 249, 0.88);
font-size: 12px;
}
.wolai-page-ai-opencode-part summary {
cursor: pointer;
font-weight: 650;
}
.wolai-page-ai-opencode-part pre,
.wolai-page-ai-opencode-error {
margin: 8px 0 0;
overflow: auto;
white-space: pre-wrap;
word-break: break-word;
}
.wolai-page-ai-opencode-tool summary {
display: flex;
justify-content: space-between;
gap: 8px;
}
.wolai-page-ai-opencode-patch {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
}
.wolai-page-ai-opencode-patch button,
.wolai-page-ai-opencode-part button {
border: 1px solid rgba(27, 28, 28, 0.12);
border-radius: 999px;
padding: 3px 8px;
background: #fff;
}
.wolai-page-ai-opencode-todo {
margin: 0;
padding: 0 12px;
list-style-position: inside;
font-size: 12px;
}
@@ -0,0 +1,59 @@
.mnote-toast-region {
position: fixed;
top: var(--wolai-spacing-lg);
right: var(--wolai-spacing-lg);
z-index: var(--wolai-z-toast, 1300);
display: flex;
flex-direction: column;
gap: var(--wolai-spacing-sm);
pointer-events: none;
}
.mnote-toast {
min-width: 220px;
max-width: min(360px, calc(100vw - 32px));
padding: 10px 14px;
border-radius: var(--wolai-radius-md, 6px);
box-shadow: var(--wolai-shadow-overlay, var(--wolai-shadow-lg));
background: var(--wolai-bg);
border: 1px solid var(--wolai-border);
color: var(--wolai-text-primary);
font: var(--mnote-font-weight-medium) var(--mnote-font-size-md) / var(--mnote-line-height-normal) var(--wolai-font-sans);
opacity: 0;
transform: translateY(-6px);
transition: opacity 160ms ease, transform 160ms ease;
}
.mnote-toast--visible {
opacity: 1;
transform: translateY(0);
}
.mnote-toast--success {
border-color: rgba(34, 165, 89, 0.28);
background: #ecfdf3;
color: #166534;
}
.mnote-toast--warning {
border-color: rgba(217, 119, 6, 0.26);
background: #fffbeb;
color: #92400e;
}
.mnote-toast--error {
border-color: rgba(220, 38, 38, 0.24);
background: #fef2f2;
color: #991b1b;
}
@media (max-width: 640px) {
.mnote-toast-region {
left: var(--wolai-spacing-md);
right: var(--wolai-spacing-md);
}
.mnote-toast {
max-width: none;
}
}
@@ -25,6 +25,10 @@
--wolai-accent-hover: #1d4ed8;
--wolai-font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", "PingFang SC", Helvetica, Arial, sans-serif;
--wolai-radius: 4px;
--wolai-radius-sm: 4px;
--wolai-radius-md: 6px;
--wolai-radius-lg: 8px;
--wolai-radius-xl: 12px;
/* font-size scale */
--mnote-font-size-2xs: 10px;
@@ -54,7 +58,11 @@
--wolai-z-popover: 90;
--wolai-z-dropdown: 120;
--wolai-z-context-menu: 1000;
--wolai-z-dialog-backdrop: 1100;
--wolai-z-modal: 1200;
--wolai-z-dialog: 1200;
--wolai-z-toast: 1300;
--wolai-z-tooltip: 1400;
/* spacing tokens */
--wolai-spacing-xs: 4px;
@@ -70,6 +78,7 @@
--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);
--wolai-shadow-overlay: 0 18px 48px rgba(15, 23, 42, 0.22), 0 2px 8px rgba(15, 23, 42, 0.08);
/* state tokens */
--wolai-state-hover-bg: rgba(55, 53, 47, 0.06);