Compare commits
No commits in common. "3831d1af01773ac678e3969e71b847aab56dbd86" and "302e05fe1fc421703411471b9cd0b619a8a64347" have entirely different histories.
3831d1af01
...
302e05fe1f
23
input.css
23
input.css
@ -663,29 +663,6 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── mermaid 图表容器 ──
|
|
||||||
data-mermaid-rendered 由 yggdrasil-core/mermaid.ts 渲染成功后标记在 <pre> 上。
|
|
||||||
渲染后 pre 唯一子元素是 <svg>(mermaid 块不注入 copy-code 按钮),flex 安全。
|
|
||||||
居中 + 缩放适配宽度,消除常规图的横向滚动;超宽图 overflow-x:auto 兜底。 */
|
|
||||||
.md-content pre[data-mermaid-rendered] {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 24px 16px;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md-content pre[data-mermaid-rendered] > svg {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 渲染失败:保留原始源码,加 accent 色左边条提示(站点 accent 为绿,沿用配色体系) */
|
|
||||||
.md-content pre.mermaid-error {
|
|
||||||
border-left: 3px solid var(--color-paper-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.anchor {
|
.anchor {
|
||||||
display: none;
|
display: none;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|||||||
@ -67,46 +67,10 @@ describe('initMermaid', () => {
|
|||||||
window.__initMermaid('.post-content', 'dark');
|
window.__initMermaid('.post-content', 'dark');
|
||||||
|
|
||||||
await vi.waitFor(() => {
|
await vi.waitFor(() => {
|
||||||
// base 主题 + darkMode 标志:让 themeVariables 完全控制 Catppuccin 调色板
|
expect(mockInitialize).toHaveBeenCalledWith(expect.objectContaining({ theme: 'dark' }));
|
||||||
expect(mockInitialize).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ theme: 'base', darkMode: true }),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('亮/暗主题传入对应的 Catppuccin themeVariables', async () => {
|
|
||||||
// 暗色:Mocha 调色板(背景 #313244、文字 #cdd6f4)
|
|
||||||
const darkRoot = document.createElement('div');
|
|
||||||
darkRoot.className = 'post-content';
|
|
||||||
darkRoot.innerHTML = '<pre><code class="language-mermaid">graph TD; A-->B</code></pre>';
|
|
||||||
document.body.appendChild(darkRoot);
|
|
||||||
window.__initMermaid('.post-content', 'dark');
|
|
||||||
await vi.waitFor(() => {
|
|
||||||
expect(mockInitialize).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
const darkCall = mockInitialize.mock.calls.slice(-1)[0]?.[0] as Record<string, unknown>;
|
|
||||||
const darkVars = darkCall.themeVariables as Record<string, string>;
|
|
||||||
expect(darkVars.background).toBe('#313244');
|
|
||||||
expect(darkVars.primaryTextColor).toBe('#cdd6f4');
|
|
||||||
expect(darkVars.lineColor).toBe('#a6adc8');
|
|
||||||
|
|
||||||
// 亮色:Latte 调色板(背景 #dce0e8、文字 #4c4f69)
|
|
||||||
document.body.innerHTML = '';
|
|
||||||
const lightRoot = document.createElement('div');
|
|
||||||
lightRoot.className = 'post-content';
|
|
||||||
lightRoot.innerHTML = '<pre><code class="language-mermaid">graph TD; A-->B</code></pre>';
|
|
||||||
document.body.appendChild(lightRoot);
|
|
||||||
window.__initMermaid('.post-content', 'light');
|
|
||||||
await vi.waitFor(() => {
|
|
||||||
expect(mockInitialize).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
const lightCall = mockInitialize.mock.calls.slice(-1)[0]?.[0] as Record<string, unknown>;
|
|
||||||
const lightVars = lightCall.themeVariables as Record<string, string>;
|
|
||||||
expect(lightVars.background).toBe('#dce0e8');
|
|
||||||
expect(lightVars.primaryTextColor).toBe('#4c4f69');
|
|
||||||
expect(lightVars.lineColor).toBe('#5c5f77');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('幂等:重复调用不重复渲染已处理的块', async () => {
|
it('幂等:重复调用不重复渲染已处理的块', async () => {
|
||||||
const root = document.createElement('div');
|
const root = document.createElement('div');
|
||||||
root.className = 'post-content';
|
root.className = 'post-content';
|
||||||
@ -173,9 +137,7 @@ describe('initMermaid', () => {
|
|||||||
await vi.waitFor(() => {
|
await vi.waitFor(() => {
|
||||||
expect(mockRender.mock.calls.length).toBeGreaterThan(firstRenderCalls);
|
expect(mockRender.mock.calls.length).toBeGreaterThan(firstRenderCalls);
|
||||||
});
|
});
|
||||||
expect(mockInitialize).toHaveBeenLastCalledWith(
|
expect(mockInitialize).toHaveBeenLastCalledWith(expect.objectContaining({ theme: 'dark' }));
|
||||||
expect.objectContaining({ theme: 'base', darkMode: true }),
|
|
||||||
);
|
|
||||||
expect(root.querySelector('pre')?.dataset.mermaidTheme).toBe('dark');
|
expect(root.querySelector('pre')?.dataset.mermaidTheme).toBe('dark');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -30,127 +30,6 @@ type MermaidApi = {
|
|||||||
render: (id: string, text: string) => Promise<{ svg: string }>;
|
render: (id: string, text: string) => Promise<{ svg: string }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Catppuccin themeVariables(与站点 --color-paper-* / .tmTheme 调色板对齐)。
|
|
||||||
*
|
|
||||||
* mermaid 把颜色烤进 SVG 内联 style,无法靠 CSS 原地改主题,故须在 initialize 时注入。
|
|
||||||
* 用 `theme: 'base'`(非 'default'/'dark'):base 主题不硬编码颜色,themeVariables 能完全
|
|
||||||
* 控制调色板;'default' 主题硬编码 mainBkg=#ECECFF 等会阻断覆盖。
|
|
||||||
*
|
|
||||||
* 设计哲学:极简卡片化——节点用 surface 色阶(卡片感)、边框/连线用 subtext 色阶(克制)、
|
|
||||||
* 文字用 primary text。不滥用强调色,绿/紫等 accent 留给作者用 classDef 手动强调。
|
|
||||||
* hex 值取自 themes/Catppuccin Latte.tmTheme 与 Catppuccin Mocha.tmTheme。
|
|
||||||
*
|
|
||||||
* 覆盖的字段涵盖 flowchart / sequence / class 三类图(测试文章用到的全部类型)。
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** Latte(亮)主题:节点 surface 色阶、文字 #4c4f69、连线 subtext1 #5c5f77。 */
|
|
||||||
const LATTE_VARS = {
|
|
||||||
background: '#dce0e8', // = --color-paper-code-block,图背景与 pre 无缝衔接
|
|
||||||
// 节点填充:surface 色阶递进(主/次/三级),卡片质感
|
|
||||||
primaryColor: '#e6e9ef',
|
|
||||||
secondaryColor: '#ccd0da',
|
|
||||||
tertiaryColor: '#bcc0cc',
|
|
||||||
mainBkg: '#e6e9ef',
|
|
||||||
nodeBkg: '#e6e9ef',
|
|
||||||
secondBkg: '#ccd0da',
|
|
||||||
// 节点边框:surface1 偏冷灰
|
|
||||||
primaryBorderColor: '#bcc0cc',
|
|
||||||
secondaryBorderColor: '#acb0be',
|
|
||||||
tertiaryBorderColor: '#9ca0b0',
|
|
||||||
nodeBorder: '#bcc0cc',
|
|
||||||
clusterBorder: '#bcc0cc',
|
|
||||||
labelBoxBorderColor: '#bcc0cc',
|
|
||||||
// 文字:primary text #4c4f69
|
|
||||||
primaryTextColor: '#4c4f69',
|
|
||||||
secondaryTextColor: '#5c5f77',
|
|
||||||
tertiaryTextColor: '#6c6f85',
|
|
||||||
textColor: '#4c4f69',
|
|
||||||
nodeTextColor: '#4c4f69',
|
|
||||||
titleColor: '#4c4f69',
|
|
||||||
classText: '#4c4f69',
|
|
||||||
labelTextColor: '#4c4f69',
|
|
||||||
// 连线/箭头:subtext1 #5c5f77
|
|
||||||
lineColor: '#5c5f77',
|
|
||||||
defaultLinkColor: '#5c5f77',
|
|
||||||
arrowheadColor: '#5c5f77',
|
|
||||||
// 时序图
|
|
||||||
actorBkg: '#e6e9ef',
|
|
||||||
actorBorder: '#bcc0cc',
|
|
||||||
actorTextColor: '#4c4f69',
|
|
||||||
actorLineColor: '#5c5f77',
|
|
||||||
signalColor: '#5c5f77',
|
|
||||||
signalTextColor: '#4c4f69',
|
|
||||||
loopTextColor: '#4c4f69',
|
|
||||||
sequenceNumberColor: '#eff1f5',
|
|
||||||
activationBkgColor: '#ccd0da',
|
|
||||||
activationBorderColor: '#bcc0cc',
|
|
||||||
// 边标签 / 子图背景
|
|
||||||
edgeLabelBackground: '#eff1f5',
|
|
||||||
labelBoxBkgColor: '#eff1f5',
|
|
||||||
clusterBkg: 'rgba(239, 241, 245, 0.5)',
|
|
||||||
// 注释:低饱和黄(Latte yellow #df8e1d)
|
|
||||||
noteBkgColor: 'rgba(223, 142, 29, 0.15)',
|
|
||||||
noteBorderColor: '#df8e1d',
|
|
||||||
noteTextColor: '#4c4f69',
|
|
||||||
// 字体:与正文 sans 对齐,中文友好
|
|
||||||
fontFamily:
|
|
||||||
"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif",
|
|
||||||
fontSize: '16px',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
/** Mocha(暗)主题:节点 surface 色阶、文字 #cdd6f4、连线 subtext0 #a6adc8。 */
|
|
||||||
const MOCHA_VARS = {
|
|
||||||
background: '#313244', // = --color-paper-code-block(暗)
|
|
||||||
primaryColor: '#45475a',
|
|
||||||
secondaryColor: '#585b70',
|
|
||||||
tertiaryColor: '#1e1e2e',
|
|
||||||
mainBkg: '#45475a',
|
|
||||||
nodeBkg: '#45475a',
|
|
||||||
secondBkg: '#585b70',
|
|
||||||
primaryBorderColor: '#585b70',
|
|
||||||
secondaryBorderColor: '#45475a',
|
|
||||||
tertiaryBorderColor: '#313244',
|
|
||||||
nodeBorder: '#585b70',
|
|
||||||
clusterBorder: '#585b70',
|
|
||||||
labelBoxBorderColor: '#585b70',
|
|
||||||
primaryTextColor: '#cdd6f4',
|
|
||||||
secondaryTextColor: '#bac2de',
|
|
||||||
tertiaryTextColor: '#a6adc8',
|
|
||||||
textColor: '#cdd6f4',
|
|
||||||
nodeTextColor: '#cdd6f4',
|
|
||||||
titleColor: '#cdd6f4',
|
|
||||||
classText: '#cdd6f4',
|
|
||||||
labelTextColor: '#cdd6f4',
|
|
||||||
lineColor: '#a6adc8',
|
|
||||||
defaultLinkColor: '#a6adc8',
|
|
||||||
arrowheadColor: '#a6adc8',
|
|
||||||
actorBkg: '#45475a',
|
|
||||||
actorBorder: '#585b70',
|
|
||||||
actorTextColor: '#cdd6f4',
|
|
||||||
actorLineColor: '#a6adc8',
|
|
||||||
signalColor: '#a6adc8',
|
|
||||||
signalTextColor: '#cdd6f4',
|
|
||||||
loopTextColor: '#cdd6f4',
|
|
||||||
sequenceNumberColor: '#1e1e2e',
|
|
||||||
activationBkgColor: '#585b70',
|
|
||||||
activationBorderColor: '#6c7086',
|
|
||||||
edgeLabelBackground: '#1e1e2e',
|
|
||||||
labelBoxBkgColor: '#1e1e2e',
|
|
||||||
clusterBkg: 'rgba(30, 30, 46, 0.5)',
|
|
||||||
noteBkgColor: 'rgba(249, 226, 175, 0.12)',
|
|
||||||
noteBorderColor: '#f9e2af',
|
|
||||||
noteTextColor: '#cdd6f4',
|
|
||||||
fontFamily:
|
|
||||||
"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif",
|
|
||||||
fontSize: '16px',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
/** 按主题返回对应 Catppuccin themeVariables。 */
|
|
||||||
function themeVarsFor(theme: ThemeName): Record<string, unknown> {
|
|
||||||
return theme === 'dark' ? { ...MOCHA_VARS } : { ...LATTE_VARS };
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
MermaidRenderer?: MermaidApi;
|
MermaidRenderer?: MermaidApi;
|
||||||
@ -223,18 +102,8 @@ async function renderBlock(pre: HTMLPreElement, source: string, theme: ThemeName
|
|||||||
const mermaid = await loadMermaid();
|
const mermaid = await loadMermaid();
|
||||||
mermaid.initialize({
|
mermaid.initialize({
|
||||||
startOnLoad: false,
|
startOnLoad: false,
|
||||||
// base 主题不硬编码颜色,让 themeVariables 完全控制 Catppuccin 调色板。
|
theme: theme === 'dark' ? 'dark' : 'default',
|
||||||
theme: 'base',
|
|
||||||
darkMode: theme === 'dark',
|
|
||||||
securityLevel: 'strict',
|
securityLevel: 'strict',
|
||||||
// flowchart:平滑曲线 + 边缘留白 + 缩放至容器宽度(配合 CSS 消除横向滚动)。
|
|
||||||
flowchart: {
|
|
||||||
curve: 'basis',
|
|
||||||
diagramPadding: 16,
|
|
||||||
useMaxWidth: true,
|
|
||||||
htmlLabels: true,
|
|
||||||
},
|
|
||||||
themeVariables: themeVarsFor(theme),
|
|
||||||
});
|
});
|
||||||
const id = `mermaid-svg-${++renderCounter}`;
|
const id = `mermaid-svg-${++renderCounter}`;
|
||||||
const { svg } = await mermaid.render(id, source);
|
const { svg } = await mermaid.render(id, source);
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 376 B |
@ -6,7 +6,7 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
use crate::components::footer::Footer;
|
use crate::components::footer::Footer;
|
||||||
use crate::components::header::{Header, SearchIconLink};
|
use crate::components::header::Header;
|
||||||
use crate::components::nav::use_nav_items;
|
use crate::components::nav::use_nav_items;
|
||||||
use crate::components::skeletons::archive_skeleton::ArchiveSkeleton;
|
use crate::components::skeletons::archive_skeleton::ArchiveSkeleton;
|
||||||
use crate::components::skeletons::delayed_skeleton::DelayedSkeleton;
|
use crate::components::skeletons::delayed_skeleton::DelayedSkeleton;
|
||||||
@ -56,7 +56,6 @@ pub fn FrontendLayout() -> Element {
|
|||||||
max_width: "max-w-4xl",
|
max_width: "max-w-4xl",
|
||||||
nav_items,
|
nav_items,
|
||||||
right_content: rsx! {
|
right_content: rsx! {
|
||||||
SearchIconLink {}
|
|
||||||
ThemeToggle {}
|
ThemeToggle {}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -170,30 +170,3 @@ fn MobileNavItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 搜索图标链接:置于 Header 右侧(主题切换左边),点击跳转搜索页。
|
|
||||||
///
|
|
||||||
/// 样式与 `ThemeToggle` 对齐(圆形 padding + currentColor 图标),保持右侧
|
|
||||||
/// 图标组视觉一致。SVG 来自 `public/icons/search_24dp_E3E3E3_FILL0_wght400_GRAD0_opsz24.svg`,
|
|
||||||
/// 改用 `fill: "currentColor"` 以适配明暗主题。
|
|
||||||
#[component]
|
|
||||||
pub fn SearchIconLink() -> Element {
|
|
||||||
rsx! {
|
|
||||||
Link {
|
|
||||||
class: "p-2 rounded-full text-paper-secondary hover:text-paper-accent transition-colors duration-200",
|
|
||||||
to: Route::Search {},
|
|
||||||
aria_label: "搜索",
|
|
||||||
title: "搜索",
|
|
||||||
svg {
|
|
||||||
xmlns: "http://www.w3.org/2000/svg",
|
|
||||||
height: "24px",
|
|
||||||
view_box: "0 -960 960 960",
|
|
||||||
width: "24px",
|
|
||||||
fill: "currentColor",
|
|
||||||
path {
|
|
||||||
d: "M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -10,8 +10,7 @@ use crate::router::Route;
|
|||||||
/// 参数:
|
/// 参数:
|
||||||
/// - `route`:当前路由
|
/// - `route`:当前路由
|
||||||
///
|
///
|
||||||
/// 返回:包含首页、归档、标签、关于的导航配置数组。
|
/// 返回:包含首页、归档、标签、搜索、关于的导航配置数组。
|
||||||
/// 搜索以图标形式置于 Header 右侧(主题切换左边),不在此文本导航中。
|
|
||||||
pub fn use_nav_items(route: Route) -> Vec<NavItemConfig> {
|
pub fn use_nav_items(route: Route) -> Vec<NavItemConfig> {
|
||||||
vec![
|
vec![
|
||||||
NavItemConfig {
|
NavItemConfig {
|
||||||
@ -29,6 +28,11 @@ pub fn use_nav_items(route: Route) -> Vec<NavItemConfig> {
|
|||||||
label: "标签",
|
label: "标签",
|
||||||
is_active: matches!(route, Route::Tags {}) || matches!(route, Route::TagDetail { .. }),
|
is_active: matches!(route, Route::Tags {}) || matches!(route, Route::TagDetail { .. }),
|
||||||
},
|
},
|
||||||
|
NavItemConfig {
|
||||||
|
route: Route::Search {},
|
||||||
|
label: "搜索",
|
||||||
|
is_active: matches!(route, Route::Search {}),
|
||||||
|
},
|
||||||
NavItemConfig {
|
NavItemConfig {
|
||||||
route: Route::About {},
|
route: Route::About {},
|
||||||
label: "关于",
|
label: "关于",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user