Files
mnote/rust/crates/mnote-web/src/ssr/styles.rs
T

925 lines
17 KiB
Rust
Raw Normal View History

2026-04-29 12:24:44 +08:00
//! MNOTE CSS 样式常量
//!
//! 为所有 Leptos SSR 页面提供 wolai / Notion 风格样式,
//! 匹配 3100 (Next.js) 前端的 wolai 设计系统。
/// MNOTE 完整 CSS 样式字符串
///
/// Wolai / Notion 风格:白色背景、近黑正文、浅色侧栏、极简边框。
/// 覆盖所有页面组件使用的 class 和 id。
pub const MNOTE_CSS: &str = r##"
/* ===== 基础重置 ===== */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* ===== Wolai 设计变量(来自设计稿) ===== */
: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;
}
/* ===== 基础排版 ===== */
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);
}
/* ===== 滚动条(悬停时显示) ===== */
::-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);
}
.wolai-workspace-shell {
--wolai-bg-sidebar: #F7F7F6;
--wolai-bg-selected: #F8E6E7;
--wolai-accent-red: #E0525B;
}
.wolai-sidebar {
width: 288px;
background: var(--wolai-bg-sidebar);
}
.wolai-sidebar-header {
height: 52px;
gap: 10px;
padding: 12px 16px 8px;
justify-content: flex-start;
}
.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;
}
.wolai-sidebar-chevron {
margin-left: auto;
color: var(--wolai-text-secondary);
font-size: 16px;
}
.wolai-quick-actions {
flex: 0 0 auto;
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 8px;
padding: 8px 12px 18px;
}
.wolai-quick-actions a {
height: 28px;
justify-content: center;
padding: 0;
margin: 0;
color: #4B4B4B;
}
.wolai-quick-actions a .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;
2026-04-29 14:36:24 +08:00
width: 24px;
height: 24px;
border: 0;
border-radius: 6px;
background: transparent;
2026-04-29 12:24:44 +08:00
color: #B5B5B2;
2026-04-29 14:36:24 +08:00
font-size: 22px;
line-height: 1;
cursor: pointer;
}
.wolai-section-add:hover {
background: var(--wolai-bg-hover);
color: #555;
}
.mnote-empty-create-page {
margin-top: 14px;
height: 34px;
padding: 0 14px;
border: 1px solid var(--wolai-border);
border-radius: 6px;
background: #fff;
color: #333;
font-size: 14px;
cursor: pointer;
}
.mnote-empty-create-page:hover {
background: var(--wolai-bg-hover);
2026-04-29 12:24:44 +08:00
}
.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: 20;
}
.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;
}
2026-04-29 14:36:24 +08:00
.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;
}
2026-04-29 12:24:44 +08:00
.mnote-sidebar-nav a {
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 {
background: var(--wolai-bg-hover);
}
.mnote-sidebar-nav a.active {
background: var(--wolai-bg-active);
}
.mnote-sidebar-nav a .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;
}
2026-04-29 14:36:24 +08:00
.sidebar-tree .tree-empty {
padding: 6px 8px 8px;
color: var(--wolai-text-secondary);
font-size: 13px;
line-height: 1.5;
}
2026-04-29 12:24:44 +08:00
.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;
overflow-y: auto;
padding: 0;
}
/* ===== 首页 ===== */
.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;
}
/* ===== 文档壳 ===== */
.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;
}
/* ===== 认证页面 ===== */
.mnote-auth {
display: flex;
height: 100vh;
align-items: center;
justify-content: center;
background: var(--wolai-bg);
}
.mnote-auth-card {
text-align: center;
padding: 48px 32px;
max-width: 340px;
}
.mnote-auth-card h1 {
font-size: 32px;
font-weight: 700;
color: var(--wolai-text-primary);
margin-bottom: 8px;
}
.mnote-auth-card p {
color: var(--wolai-text-secondary);
font-size: 15px;
line-height: 1.5;
}
/* ===== ProseMirror 兼容 ===== */
.ProseMirror {
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: anywhere;
}
.ProseMirror pre {
white-space: pre-wrap;
}
/* ===== 响应式 ===== */
@media (max-width: 768px) {
.mnote-sidebar {
width: 200px;
}
.mnote-home {
padding: 48px 24px;
}
.mnote-home h1 {
font-size: 28px;
}
.document-shell-header {
padding: 24px 0 0;
max-width: 100%;
}
.document-shell-header h1 {
font-size: 28px;
}
#mnote-editor-island {
margin: 0 24px;
width: auto;
padding-bottom: 24px;
}
#mnote-search-shell,
#mnote-mindmap-shell {
padding: 24px;
}
#mnote-search-shell h1,
#mnote-mindmap-shell h1 {
font-size: 24px;
}
}
@media (max-width: 480px) {
.mnote-sidebar {
width: 100%;
height: auto;
border-right: none;
border-bottom: 1px solid var(--wolai-border);
}
.mnote-shell {
flex-direction: column;
}
.mnote-home {
padding: 32px 16px;
}
.mnote-home h1 {
font-size: 24px;
}
.document-shell-header {
padding: 16px 0 0;
}
.document-shell-header h1 {
font-size: 24px;
}
#mnote-editor-island {
margin: 0 16px;
width: auto;
padding-bottom: 16px;
}
}
"##;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mnote_css_contains_core_selectors() {
// Wolai 设计系统 CSS 变量
assert!(MNOTE_CSS.contains("--wolai-text-primary"));
assert!(MNOTE_CSS.contains("--wolai-text-secondary"));
assert!(MNOTE_CSS.contains("--wolai-border"));
assert!(MNOTE_CSS.contains("--wolai-bg-sidebar"));
assert!(MNOTE_CSS.contains("--wolai-accent"));
// 布局结构
assert!(MNOTE_CSS.contains(".mnote-shell"));
assert!(MNOTE_CSS.contains(".mnote-sidebar"));
assert!(MNOTE_CSS.contains(".mnote-main"));
assert!(MNOTE_CSS.contains(".mnote-content"));
}
#[test]
fn mnote_css_contains_shell_ids() {
assert!(MNOTE_CSS.contains(".document-shell"));
assert!(MNOTE_CSS.contains("#mnote-search-shell"));
assert!(MNOTE_CSS.contains("#mnote-mindmap-shell"));
assert!(MNOTE_CSS.contains("#mnote-editor-island"));
assert!(MNOTE_CSS.contains("#mnote-search-island"));
assert!(MNOTE_CSS.contains("#mnote-mindmap-island"));
}
#[test]
fn mnote_css_contains_responsive_breakpoints() {
assert!(MNOTE_CSS.contains("@media (max-width: 768px)"));
assert!(MNOTE_CSS.contains("@media (max-width: 480px)"));
}
#[test]
fn mnote_css_contains_scrollbar_styles() {
assert!(MNOTE_CSS.contains("::-webkit-scrollbar"));
assert!(MNOTE_CSS.contains("::-webkit-scrollbar-thumb"));
}
#[test]
fn mnote_css_contains_prosemirror_styles() {
assert!(MNOTE_CSS.contains(".ProseMirror"));
}
#[test]
fn mnote_css_is_reasonably_sized() {
// 至少 2000 字符才能包含完整样式
assert!(MNOTE_CSS.len() > 2000);
// 最多 20000 字符避免过于臃肿
assert!(MNOTE_CSS.len() < 20000);
}
}