Files
NebulaShell/website/blog/posts/markdown-mermaid/index.html
2026-04-10 20:00:25 +08:00

2231 lines
129 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html><html lang="zh-CN" class="bg-[var(--page-bg)] text-[14px] md:text-[16px]" data-overlayscrollbars-initialize data-astro-cid-sckkx6r4 style="--bannerOffset: 15vh;--banner-height-home: 65vh;--banner-height: 35vh;--configHue: 210;--page-width: 90rem;"> <head><title>Markdown Mermaid - FutureOSS Docs</title><meta charset="UTF-8"><meta name="description" content="A simple example of a Markdown blog post with Mermaid."><meta name="author" content="FutureOSS"><meta property="og:site_name" content="FutureOSS Docs"><meta property="og:url" content="https://oss-runtime.dev/blog/posts/markdown-mermaid/"><meta property="og:title" content="Markdown Mermaid - FutureOSS Docs"><meta property="og:description" content="A simple example of a Markdown blog post with Mermaid."><meta property="og:type" content="article"><meta name="twitter:card" content="summary_large_image"><meta property="twitter:url" content="https://oss-runtime.dev/blog/posts/markdown-mermaid/"><meta name="twitter:title" content="Markdown Mermaid - FutureOSS Docs"><meta name="twitter:description" content="A simple example of a Markdown blog post with Mermaid."><meta name="viewport" content="width=device-width"><meta name="generator" content="Astro v5.13.2"><link rel="icon" href="/blog/favicon/favicon-light-32.png" sizes="32x32" media="(prefers-color-scheme: light)"><link rel="icon" href="/blog/favicon/favicon-light-128.png" sizes="128x128" media="(prefers-color-scheme: light)"><link rel="icon" href="/blog/favicon/favicon-light-180.png" sizes="180x180" media="(prefers-color-scheme: light)"><link rel="icon" href="/blog/favicon/favicon-light-192.png" sizes="192x192" media="(prefers-color-scheme: light)"><link rel="icon" href="/blog/favicon/favicon-dark-32.png" sizes="32x32" media="(prefers-color-scheme: dark)"><link rel="icon" href="/blog/favicon/favicon-dark-128.png" sizes="128x128" media="(prefers-color-scheme: dark)"><link rel="icon" href="/blog/favicon/favicon-dark-180.png" sizes="180x180" media="(prefers-color-scheme: dark)"><link rel="icon" href="/blog/favicon/favicon-dark-192.png" sizes="192x192" media="(prefers-color-scheme: dark)"><!-- Set the theme before the page is rendered to avoid a flash --><script>(function(){const DEFAULT_THEME = "auto";
const LIGHT_MODE = "light";
const DARK_MODE = "dark";
const AUTO_MODE = "auto";
const BANNER_HEIGHT_EXTEND = 30;
const PAGE_WIDTH = 90;
const configHue = 210;
// Load the theme from local storage
const theme = localStorage.getItem('theme') || DEFAULT_THEME;
switch (theme) {
case LIGHT_MODE:
document.documentElement.classList.remove('dark');
break
case DARK_MODE:
document.documentElement.classList.add('dark');
break
case AUTO_MODE:
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
// Load the hue from local storage
const hue = localStorage.getItem('hue') || configHue;
document.documentElement.style.setProperty('--hue', hue);
// calculate the --banner-height-extend, which needs to be a multiple of 4 to avoid blurry text
let offset = Math.floor(window.innerHeight * (BANNER_HEIGHT_EXTEND / 100));
offset = offset - offset % 4;
document.documentElement.style.setProperty('--banner-height-extend', `${offset}px`);
})();</script><!-- defines global css variables. This will be applied to <html> <body> and some other elements idk why --><script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"Markdown Mermaid","description":"A simple example of a Markdown blog post with Mermaid.","keywords":["Markdown","Blogging","Mermaid"],"author":{"@type":"Person","name":"FutureOSS","url":"https://oss-runtime.dev/"},"datePublished":"2023-10-01","inLanguage":"zh-CN"}</script><link rel="alternate" type="application/rss+xml" title="FutureOSS" href="https://oss-runtime.dev/rss.xml"><link rel="stylesheet" href="/blog/_astro/Layout.CKPtf-VR.css">
<link rel="stylesheet" href="/blog/_astro/Layout.DSulWsr7.css">
<link rel="stylesheet" href="/blog/_astro/_page_.QUsIdifj.css">
<link rel="stylesheet" href="/blog/_astro/_page_.D6v8Siar.css">
<link rel="stylesheet" href="/blog/_astro/_page_.BQ6XwRvy.css">
<link rel="stylesheet" href="/blog/_astro/about.DhJwTaWu.css">
<style>.card-base[data-astro-cid-fepk5tcl]{background:var(--card-bg);border:1px solid var(--line-divider)}
.card-base[data-astro-cid-pyfuvgrx]{background:var(--card-bg);border:1px solid var(--line-divider);transition:all .3s ease}.moments-header[data-astro-cid-pyfuvgrx]{padding:1rem;border-radius:8px;background:linear-gradient(135deg,var(--primary) 0%,var(--primary-dark, var(--primary)) 100%);color:#fff;position:relative;overflow:hidden}.header-content[data-astro-cid-pyfuvgrx]{display:flex;justify-content:space-between;align-items:center;position:relative;z-index:1}.moments-title[data-astro-cid-pyfuvgrx]{color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.1)}.moments-subtitle[data-astro-cid-pyfuvgrx]{color:#ffffffe6}.stat-number[data-astro-cid-pyfuvgrx]{color:#fff}.stat-label[data-astro-cid-pyfuvgrx]{color:#fffc}.image-item[data-astro-cid-pyfuvgrx] img[data-astro-cid-pyfuvgrx]{transition:transform .3s ease}.image-item[data-astro-cid-pyfuvgrx]:hover img[data-astro-cid-pyfuvgrx]{transform:scale(1.05)}.action-btn[data-astro-cid-pyfuvgrx]{background:none;border:none;cursor:pointer;color:var(--text-muted)}.action-btn[data-astro-cid-pyfuvgrx]:hover{color:var(--primary)}@media(max-width:640px){.moments-header[data-astro-cid-pyfuvgrx]{padding:.75rem}.header-content[data-astro-cid-pyfuvgrx]{flex-direction:column;text-align:center;gap:.75rem}.moment-images[data-astro-cid-pyfuvgrx]{grid-template-columns:repeat(2,1fr)}.moment-footer[data-astro-cid-pyfuvgrx]{flex-direction:column;align-items:flex-start;gap:.5rem}}@media(min-width:641px)and (max-width:900px){.moments-header[data-astro-cid-pyfuvgrx]{padding:1.25rem}.header-content[data-astro-cid-pyfuvgrx]{display:flex;justify-content:space-between;align-items:center}.moment-item[data-astro-cid-pyfuvgrx]{padding:1.5rem}.moment-images[data-astro-cid-pyfuvgrx]{grid-template-columns:repeat(3,1fr);gap:.75rem;max-width:500px}.moment-text[data-astro-cid-pyfuvgrx]{font-size:1rem;line-height:1.7}.moment-footer[data-astro-cid-pyfuvgrx]{margin-top:1rem}}@media(min-width:901px){.moments-header[data-astro-cid-pyfuvgrx]{padding:1.5rem}.moment-item[data-astro-cid-pyfuvgrx]{padding:2rem}.moment-images[data-astro-cid-pyfuvgrx]{grid-template-columns:repeat(auto-fit,minmax(150px,1fr));max-width:600px;gap:1rem}.moment-text[data-astro-cid-pyfuvgrx]{font-size:1.1rem;line-height:1.8}}@media(max-width:480px){.card-base[data-astro-cid-pyfuvgrx]{margin:0 -.5rem}.moment-item[data-astro-cid-pyfuvgrx]{border-radius:8px}.moments-header[data-astro-cid-pyfuvgrx]{border-radius:6px}}
.card-base[data-astro-cid-sahthylw]{border-radius:var(--radius-large);border-width:1px;border-color:var(--line-divider);background-color:var(--card-bg);--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.gallery-group[data-astro-cid-sahthylw].expanded{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.gallery-content[data-astro-cid-sahthylw]{max-height:0;overflow:hidden;transition:max-height .3s ease-out}.gallery-group[data-astro-cid-sahthylw].expanded .gallery-content[data-astro-cid-sahthylw]{max-height:2000px}.gallery-image[data-astro-cid-sahthylw]:hover{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}@media(max-width:768px){.gallery-content[data-astro-cid-sahthylw] .grid[data-astro-cid-sahthylw]{grid-template-columns:repeat(2,1fr)}}@media(max-width:480px){.gallery-content[data-astro-cid-sahthylw] .grid[data-astro-cid-sahthylw]{grid-template-columns:repeat(1,1fr)}}
.line-clamp-1[data-astro-cid-aid3sr62]{display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}.line-clamp-2[data-astro-cid-aid3sr62]{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.line-clamp-2[data-astro-cid-xahix5fp]{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.line-clamp-2[data-astro-cid-qlh7ngej]{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
</style>
<link rel="stylesheet" href="/blog/_astro/_page_.C0I9pl0C.css"><script type="module" src="/blog/_astro/page.OjjRkIOC.js"></script></head> <body class=" min-h-screen enable-banner" data-overlayscrollbars-initialize data-astro-cid-sckkx6r4 style="--bannerOffset: 15vh;--banner-height-home: 65vh;--banner-height: 35vh;--configHue: 210;--page-width: 90rem;"> <!-- 页面顶部渐变高光效果 - 只在full和semifull模式下显示 --> <div class="top-gradient-highlight" data-astro-cid-sckkx6r4 style="--bannerOffset: 15vh;--banner-height-home: 65vh;--banner-height: 35vh;--configHue: 210;--page-width: 90rem;"></div> <div id="config-carrier" data-hue="210"></div> <!-- Iconify图标库加载器 --><script>(function(){const preloadIcons = [];
const timeout = 10000;
const retryCount = 3;
// 全局图标加载逻辑
(function() {
'use strict';
// 避免重复加载
if (window.__iconifyLoaderInitialized) {
return;
}
window.__iconifyLoaderInitialized = true;
// 图标加载器类
class IconifyLoader {
constructor() {
this.isLoaded = false;
this.isLoading = false;
this.loadPromise = null;
this.observers = new Set();
this.preloadQueue = new Set();
}
async load(options = {}) {
const { timeout: loadTimeout = timeout, retryCount: maxRetries = retryCount } = options;
if (this.isLoaded) {
return Promise.resolve();
}
if (this.isLoading && this.loadPromise) {
return this.loadPromise;
}
this.isLoading = true;
this.loadPromise = this.loadWithRetry(loadTimeout, maxRetries);
try {
await this.loadPromise;
this.isLoaded = true;
this.notifyObservers();
await this.processPreloadQueue();
} catch (error) {
console.error('Failed to load Iconify:', error);
throw error;
} finally {
this.isLoading = false;
}
}
async loadWithRetry(timeout, retryCount) {
for (let attempt = 1; attempt <= retryCount; attempt++) {
try {
await this.loadScript(timeout);
return;
} catch (error) {
console.warn(`Iconify load attempt ${attempt} failed:`, error);
if (attempt === retryCount) {
throw new Error(`Failed to load Iconify after ${retryCount} attempts`);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
loadScript(timeout) {
return new Promise((resolve, reject) => {
// 检查是否已经存在
if (this.isIconifyReady()) {
resolve();
return;
}
const existingScript = document.querySelector('script[src*="iconify-icon"]');
if (existingScript) {
this.waitForIconifyReady().then(resolve).catch(reject);
return;
}
const script = document.createElement('script');
script.src = 'https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js';
script.async = true;
script.crossOrigin = 'anonymous';
const timeoutId = setTimeout(() => {
script.remove();
reject(new Error('Script load timeout'));
}, timeout);
script.onload = () => {
clearTimeout(timeoutId);
this.waitForIconifyReady().then(resolve).catch(reject);
};
script.onerror = () => {
clearTimeout(timeoutId);
script.remove();
reject(new Error('Script load error'));
};
document.head.appendChild(script);
});
}
waitForIconifyReady(maxWait = 5000) {
return new Promise((resolve, reject) => {
const startTime = Date.now();
const checkReady = () => {
if (this.isIconifyReady()) {
resolve();
return;
}
if (Date.now() - startTime > maxWait) {
reject(new Error('Iconify initialization timeout'));
return;
}
setTimeout(checkReady, 50);
};
checkReady();
});
}
isIconifyReady() {
return typeof window !== 'undefined' &&
'customElements' in window &&
customElements.get('iconify-icon') !== undefined;
}
onLoad(callback) {
if (this.isLoaded) {
callback();
} else {
this.observers.add(callback);
}
}
notifyObservers() {
this.observers.forEach(callback => {
try {
callback();
} catch (error) {
console.error('Error in icon load observer:', error);
}
});
this.observers.clear();
}
addToPreloadQueue(icons) {
if (Array.isArray(icons)) {
icons.forEach(icon => this.preloadQueue.add(icon));
} else {
this.preloadQueue.add(icons);
}
if (this.isLoaded) {
this.processPreloadQueue();
}
}
async processPreloadQueue() {
if (this.preloadQueue.size === 0) return;
const iconsToLoad = Array.from(this.preloadQueue);
this.preloadQueue.clear();
await this.preloadIcons(iconsToLoad);
}
async preloadIcons(icons) {
if (!this.isLoaded || icons.length === 0) return;
return new Promise((resolve) => {
let loadedCount = 0;
const totalIcons = icons.length;
const tempElements = [];
const cleanup = () => {
tempElements.forEach(el => {
if (el.parentNode) {
el.parentNode.removeChild(el);
}
});
};
const checkComplete = () => {
loadedCount++;
if (loadedCount >= totalIcons) {
cleanup();
resolve();
}
};
icons.forEach(icon => {
const tempIcon = document.createElement('iconify-icon');
tempIcon.setAttribute('icon', icon);
tempIcon.style.cssText = 'position:absolute;top:-9999px;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none;';
tempIcon.addEventListener('load', checkComplete);
tempIcon.addEventListener('error', checkComplete);
tempElements.push(tempIcon);
document.body.appendChild(tempIcon);
});
// 设置超时清理
setTimeout(() => {
cleanup();
resolve();
}, 3000);
});
}
}
// 创建全局实例
window.__iconifyLoader = new IconifyLoader();
// 立即开始加载
window.__iconifyLoader.load().catch(error => {
console.error('Failed to initialize Iconify:', error);
});
// 如果有预加载图标,添加到队列
if (preloadIcons && preloadIcons.length > 0) {
window.__iconifyLoader.addToPreloadQueue(preloadIcons);
}
// 导出便捷函数到全局
window.loadIconify = () => window.__iconifyLoader.load();
window.preloadIcons = (icons) => window.__iconifyLoader.addToPreloadQueue(icons);
window.onIconifyReady = (callback) => window.__iconifyLoader.onLoad(callback);
// 页面可见性变化时重新检查
document.addEventListener('visibilitychange', () => {
if (!document.hidden && !window.__iconifyLoader.isLoaded) {
window.__iconifyLoader.load().catch(console.error);
}
});
})();
})();</script> <!-- 为不支持JavaScript的情况提供备用方案 --><noscript><style>
iconify-icon {
display: none;
}
.icon-fallback {
display: inline-block;
}
</style></noscript> <div id="top-row" class="z-50 pointer-events-none relative transition-all duration-700 max-w-[var(--page-width)] px-0 md:px-4 mx-auto" data-astro-cid-haiuh7kc> <div id="navbar-wrapper" class="pointer-events-auto sticky top-0 transition-all" data-astro-cid-haiuh7kc> <div id="navbar" class="z-50 onload-animation" data-transparent-mode="semifull" data-is-home="false"> <div class="absolute h-8 left-0 right-0 -top-8 bg-[var(--card-bg)] transition"></div> <!-- used for onload animation --> <div class="!overflow-visible max-w-[var(--page-width)] h-[4.5rem] mx-auto flex items-center justify-between px-4"> <a href="/blog/" class="btn-plain scale-animation rounded-lg h-[3.25rem] px-5 font-bold active:scale-95"> <div class="flex flex-row text-[var(--primary)] items-center text-md"> <svg width="1em" height="1em" class="text-[1.75rem] mb-1 mr-2" data-icon="material-symbols:home-outline-rounded"> <symbol id="ai:material-symbols:home-outline-rounded" viewBox="0 0 24 24"><path fill="currentColor" d="M6 19h3v-5q0-.425.288-.712T10 13h4q.425 0 .713.288T15 14v5h3v-9l-6-4.5L6 10zm-2 0v-9q0-.475.213-.9t.587-.7l6-4.5q.525-.4 1.2-.4t1.2.4l6 4.5q.375.275.588.7T20 10v9q0 .825-.588 1.413T18 21h-4q-.425 0-.712-.288T13 20v-5h-2v5q0 .425-.288.713T10 21H6q-.825 0-1.412-.587T4 19m8-6.75"/></symbol><use href="#ai:material-symbols:home-outline-rounded"></use> </svg> FutureOSS Docs </div> </a> <div class="hidden md:flex items-center space-x-1"> <div class="dropdown-container" data-dropdown data-astro-cid-qou34bit> <a aria-label="主页" href="/blog/" class="btn-plain scale-animation rounded-lg h-11 font-bold px-5 active:scale-95" data-astro-cid-qou34bit> <div class="flex items-center" data-astro-cid-qou34bit> <svg width="1em" height="1em" class="text-[1.1rem] mr-2" data-astro-cid-qou34bit="true" data-icon="material-symbols:home"> <symbol id="ai:material-symbols:home" viewBox="0 0 24 24"><path fill="currentColor" d="M4 21V9l8-6l8 6v12h-6v-7h-4v7z"/></symbol><use href="#ai:material-symbols:home"></use> </svg> 主页 </div> </a> </div> <script type="module">document.addEventListener("DOMContentLoaded",function(){const i=document.querySelectorAll("[data-dropdown]");i.forEach(t=>{const e=t.querySelector("[data-dropdown-trigger]"),o=t.querySelector("[data-dropdown-menu]"),n=t.querySelectorAll(".dropdown-item");!e||!o||(e.addEventListener("keydown",function(r){r.key==="Enter"||r.key===" "?(r.preventDefault(),d(t,e,o)):r.key==="ArrowDown"?(r.preventDefault(),u(t,e,o),n.length>0&&n[0].focus()):r.key==="Escape"&&a(t,e,o)}),n.forEach((r,l)=>{r.addEventListener("keydown",function(s){if(s.key==="ArrowDown"){s.preventDefault();const c=(l+1)%n.length;n[c].focus()}else if(s.key==="ArrowUp"){s.preventDefault();const c=(l-1+n.length)%n.length;n[c].focus()}else s.key==="Escape"&&(a(t,e,o),e.focus())})}))}),document.addEventListener("click",function(t){i.forEach(e=>{if(!e.contains(t.target)){const o=e.querySelector("[data-dropdown-trigger]"),n=e.querySelector("[data-dropdown-menu]");o&&n&&a(e,o,n)}})})});function d(i,t,e){t.getAttribute("aria-expanded")==="true"?a(i,t,e):u(i,t,e)}function u(i,t,e){t.setAttribute("aria-expanded","true"),e.classList.remove("opacity-0","invisible","pointer-events-none","translate-y-[-8px]"),e.classList.add("opacity-100","visible","pointer-events-auto","translate-y-0")}function a(i,t,e){t.setAttribute("aria-expanded","false"),e.classList.add("opacity-0","invisible","pointer-events-none","translate-y-[-8px]"),e.classList.remove("opacity-100","visible","pointer-events-auto","translate-y-0")}</script><div class="dropdown-container" data-dropdown data-astro-cid-qou34bit> <a aria-label="归档" href="/blog/archive/" class="btn-plain scale-animation rounded-lg h-11 font-bold px-5 active:scale-95" data-astro-cid-qou34bit> <div class="flex items-center" data-astro-cid-qou34bit> <svg width="1em" height="1em" class="text-[1.1rem] mr-2" data-astro-cid-qou34bit="true" data-icon="material-symbols:archive"> <symbol id="ai:material-symbols:archive" viewBox="0 0 24 24"><path fill="currentColor" d="m12 18l4-4l-1.4-1.4l-1.6 1.6V10h-2v4.2l-1.6-1.6L8 14zm-7 3q-.825 0-1.412-.587T3 19V6.525q0-.35.113-.675t.337-.6L4.7 3.725q.275-.35.687-.538T6.25 3h11.5q.45 0 .863.188t.687.537l1.25 1.525q.225.275.338.6t.112.675V19q0 .825-.587 1.413T19 21zm.4-15h13.2l-.85-1H6.25z"/></symbol><use href="#ai:material-symbols:archive"></use> </svg> 归档 </div> </a> </div> <div class="dropdown-container" data-dropdown data-astro-cid-qou34bit> <button class="btn-plain scale-animation rounded-lg h-11 font-bold px-5 active:scale-95 dropdown-trigger" aria-expanded="false" aria-haspopup="true" data-dropdown-trigger data-astro-cid-qou34bit> <div class="flex items-center" data-astro-cid-qou34bit> <svg width="1em" height="1em" class="text-[1.1rem] mr-2" data-astro-cid-qou34bit="true" data-icon="material-symbols:code"> <symbol id="ai:material-symbols:code" viewBox="0 0 24 24"><path fill="currentColor" d="m8 18l-6-6l6-6l1.425 1.425l-4.6 4.6L9.4 16.6zm8 0l-1.425-1.425l4.6-4.6L14.6 7.4L16 6l6 6z"/></symbol><use href="#ai:material-symbols:code"></use> </svg> 仓库 <svg width="1em" height="1em" class="text-[1.25rem] transition-transform duration-200 dropdown-arrow ml-1" data-astro-cid-qou34bit="true" data-icon="material-symbols:keyboard-arrow-down-rounded"> <symbol id="ai:material-symbols:keyboard-arrow-down-rounded" viewBox="0 0 24 24"><path fill="currentColor" d="M11.625 14.913q-.175-.063-.325-.213l-4.6-4.6q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l3.9 3.9l3.9-3.9q.275-.275.7-.275t.7.275t.275.7t-.275.7l-4.6 4.6q-.15.15-.325.213t-.375.062t-.375-.062"/></symbol><use href="#ai:material-symbols:keyboard-arrow-down-rounded"></use> </svg> </div> </button>
<div class="dropdown-menu" data-dropdown-menu data-astro-cid-qou34bit> <div class="dropdown-content" data-astro-cid-qou34bit> <a href="https://gitee.com/starlight-apk/feature-oss" target="_blank" class="dropdown-item" aria-label="Gitee" data-astro-cid-qou34bit> <div class="flex items-center" data-astro-cid-qou34bit> <svg width="1em" height="1em" viewBox="0 0 24 24" class="text-[1rem] mr-2" data-astro-cid-qou34bit="true" data-icon="mdi:git"> <use href="#ai:mdi:git"></use> </svg> <span data-astro-cid-qou34bit>Gitee</span> </div> <svg width="1em" height="1em" class="text-[0.75rem] text-black/25 dark:text-white/25" data-astro-cid-qou34bit="true" data-icon="fa6-solid:arrow-up-right-from-square"> <symbol id="ai:fa6-solid:arrow-up-right-from-square" viewBox="0 0 512 512"><path fill="currentColor" d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32zM80 32C35.8 32 0 67.8 0 112v320c0 44.2 35.8 80 80 80h320c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32v112c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16h112c17.7 0 32-14.3 32-32s-14.3-32-32-32z"/></symbol><use href="#ai:fa6-solid:arrow-up-right-from-square"></use> </svg> </a><a href="https://github.com/starlight-apk/feature-oss" target="_blank" class="dropdown-item" aria-label="GitHub" data-astro-cid-qou34bit> <div class="flex items-center" data-astro-cid-qou34bit> <svg width="0.97em" height="1em" viewBox="0 0 496 512" class="text-[1rem] mr-2" data-astro-cid-qou34bit="true" data-icon="fa6-brands:github"> <use href="#ai:fa6-brands:github"></use> </svg> <span data-astro-cid-qou34bit>GitHub</span> </div> <svg width="1em" height="1em" viewBox="0 0 512 512" class="text-[0.75rem] text-black/25 dark:text-white/25" data-astro-cid-qou34bit="true" data-icon="fa6-solid:arrow-up-right-from-square"> <use href="#ai:fa6-solid:arrow-up-right-from-square"></use> </svg> </a> </div> </div> </div> </div> <div class="flex"> <!--<SearchPanel client:load>--> <style>astro-island,astro-slot,astro-static-slot{display:contents}</style><script>(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).only=e;window.dispatchEvent(new Event("astro:only"));})();</script><script>(()=>{var A=Object.defineProperty;var g=(i,o,a)=>o in i?A(i,o,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[o]=a;var d=(i,o,a)=>g(i,typeof o!="symbol"?o+"":o,a);{let i={0:t=>m(t),1:t=>a(t),2:t=>new RegExp(t),3:t=>new Date(t),4:t=>new Map(a(t)),5:t=>new Set(a(t)),6:t=>BigInt(t),7:t=>new URL(t),8:t=>new Uint8Array(t),9:t=>new Uint16Array(t),10:t=>new Uint32Array(t),11:t=>1/0*t},o=t=>{let[l,e]=t;return l in i?i[l](e):void 0},a=t=>t.map(o),m=t=>typeof t!="object"||t===null?t:Object.fromEntries(Object.entries(t).map(([l,e])=>[l,o(e)]));class y extends HTMLElement{constructor(){super(...arguments);d(this,"Component");d(this,"hydrator");d(this,"hydrate",async()=>{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest("astro-island[ssr]");if(e){e.addEventListener("astro:hydrate",this.hydrate,{once:!0});return}let c=this.querySelectorAll("astro-slot"),n={},h=this.querySelectorAll("template[data-astro-template]");for(let r of h){let s=r.closest(this.tagName);s!=null&&s.isSameNode(this)&&(n[r.getAttribute("data-astro-template")||"default"]=r.innerHTML,r.remove())}for(let r of c){let s=r.closest(this.tagName);s!=null&&s.isSameNode(this)&&(n[r.getAttribute("name")||"default"]=r.innerHTML)}let p;try{p=this.hasAttribute("props")?m(JSON.parse(this.getAttribute("props"))):{}}catch(r){let s=this.getAttribute("component-url")||"<unknown>",v=this.getAttribute("component-export");throw v&&(s+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${s}`,this.getAttribute("props"),r),r}let u;await this.hydrator(this)(this.Component,p,n,{client:this.getAttribute("client")}),this.removeAttribute("ssr"),this.dispatchEvent(new CustomEvent("astro:hydrate"))});d(this,"unmount",()=>{this.isConnected||this.dispatchEvent(new CustomEvent("astro:unmount"))})}disconnectedCallback(){document.removeEventListener("astro:after-swap",this.unmount),document.addEventListener("astro:after-swap",this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute("await-children")||document.readyState==="interactive"||document.readyState==="complete")this.childrenConnectedCallback();else{let e=()=>{document.removeEventListener("DOMContentLoaded",e),c.disconnect(),this.childrenConnectedCallback()},c=new MutationObserver(()=>{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&&this.lastChild.nodeValue==="astro:end"&&(this.lastChild.remove(),e())});c.observe(this,{childList:!0}),document.addEventListener("DOMContentLoaded",e)}}async childrenConnectedCallback(){let e=this.getAttribute("before-hydration-url");e&&await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute("opts")),c=this.getAttribute("client");if(Astro[c]===void 0){window.addEventListener(`astro:${c}`,()=>this.start(),{once:!0});return}try{await Astro[c](async()=>{let n=this.getAttribute("renderer-url"),[h,{default:p}]=await Promise.all([import(this.getAttribute("component-url")),n?import(n):()=>()=>{}]),u=this.getAttribute("component-export")||"default";if(!u.includes("."))this.Component=h[u];else{this.Component=h;for(let f of u.split("."))this.Component=this.Component[f]}return this.hydrator=p,this.hydrate},e,this)}catch(n){console.error(`[astro-island] Error hydrating ${this.getAttribute("component-url")}`,n)}}attributeChangedCallback(){this.hydrate()}}d(y,"observedAttributes",["props"]),customElements.get("astro-island")||customElements.define("astro-island",y)}})();</script><astro-island uid="8ERU5" component-url="/blog/_astro/Search.C4siwRRu.js" component-export="default" renderer-url="/blog/_astro/client.svelte.9vP7DkWT.js" props="{}" ssr client="only" opts="{&quot;name&quot;:&quot;Search&quot;,&quot;value&quot;:&quot;svelte&quot;}"></astro-island> <astro-island uid="ZgLYMD" component-url="/blog/_astro/MobileTOC.DcxBJmw1.js" component-export="default" renderer-url="/blog/_astro/client.svelte.9vP7DkWT.js" props="{}" ssr client="only" opts="{&quot;name&quot;:&quot;MobileTOC&quot;,&quot;value&quot;:&quot;svelte&quot;}"></astro-island> <astro-island uid="pXLEo" component-url="/blog/_astro/TranslateButton.gpbH5i1G.js" component-export="default" renderer-url="/blog/_astro/client.svelte.9vP7DkWT.js" props="{}" ssr client="only" opts="{&quot;name&quot;:&quot;TranslateButton&quot;,&quot;value&quot;:&quot;svelte&quot;}"></astro-island> <button aria-label="Display Settings" class="btn-plain scale-animation rounded-lg h-11 w-11 active:scale-90" id="display-settings-switch"> <svg width="1em" height="1em" class="text-[1.25rem]" data-icon="material-symbols:palette-outline"> <symbol id="ai:material-symbols:palette-outline" viewBox="0 0 24 24"><path fill="currentColor" d="M12 22q-2.05 0-3.875-.788t-3.187-2.15t-2.15-3.187T2 12q0-2.075.813-3.9t2.2-3.175T8.25 2.788T12.2 2q2 0 3.775.688t3.113 1.9t2.125 2.875T22 11.05q0 2.875-1.75 4.413T16 17h-1.85q-.225 0-.312.125t-.088.275q0 .3.375.863t.375 1.287q0 1.25-.687 1.85T12 22m-4.425-9.425Q8 12.15 8 11.5t-.425-1.075T6.5 10t-1.075.425T5 11.5t.425 1.075T6.5 13t1.075-.425m3-4Q11 8.15 11 7.5t-.425-1.075T9.5 6t-1.075.425T8 7.5t.425 1.075T9.5 9t1.075-.425m5 0Q16 8.15 16 7.5t-.425-1.075T14.5 6t-1.075.425T13 7.5t.425 1.075T14.5 9t1.075-.425m3 4Q19 12.15 19 11.5t-.425-1.075T17.5 10t-1.075.425T16 11.5t.425 1.075T17.5 13t1.075-.425M12 20q.225 0 .363-.125t.137-.325q0-.35-.375-.825T11.75 17.3q0-1.05.725-1.675T14.25 15H16q1.65 0 2.825-.962T20 11.05q0-3.025-2.312-5.038T12.2 4Q8.8 4 6.4 6.325T4 12q0 3.325 2.338 5.663T12 20"/></symbol><use href="#ai:material-symbols:palette-outline"></use> </svg> </button> <astro-island uid="Z1LIWpd" component-url="/blog/_astro/LightDarkSwitch.DQTkCfco.js" component-export="default" renderer-url="/blog/_astro/client.svelte.9vP7DkWT.js" props="{}" ssr client="only" opts="{&quot;name&quot;:&quot;LightDarkSwitch&quot;,&quot;value&quot;:&quot;svelte&quot;}"></astro-island> <button aria-label="Menu" name="Nav Menu" class="btn-plain scale-animation rounded-lg w-11 h-11 active:scale-90 md:!hidden" id="nav-menu-switch"> <svg width="1em" height="1em" class="text-[1.25rem]" data-icon="material-symbols:menu-rounded"> <symbol id="ai:material-symbols:menu-rounded" viewBox="0 0 24 24"><path fill="currentColor" d="M4 18q-.425 0-.712-.288T3 17t.288-.712T4 16h16q.425 0 .713.288T21 17t-.288.713T20 18zm0-5q-.425 0-.712-.288T3 12t.288-.712T4 11h16q.425 0 .713.288T21 12t-.288.713T20 13zm0-5q-.425 0-.712-.288T3 7t.288-.712T4 6h16q.425 0 .713.288T21 7t-.288.713T20 8z"/></symbol><use href="#ai:material-symbols:menu-rounded"></use> </svg> </button> </div> <div id="nav-menu-panel" class="float-panel float-panel-closed absolute transition-all fixed right-4 px-2 py-2 max-h-[80vh] overflow-y-auto" data-astro-cid-h5lvqo6o> <div class="mobile-menu-item" data-astro-cid-h5lvqo6o> <!-- 普通链接项目 -->
<a href="/blog/" class="group flex justify-between items-center py-2 pl-3 pr-1 rounded-lg gap-8
hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)] transition
" data-astro-cid-h5lvqo6o> <div class="flex items-center transition text-black/75 dark:text-white/75 font-bold group-hover:text-[var(--primary)] group-active:text-[var(--primary)]" data-astro-cid-h5lvqo6o> <svg width="1em" height="1em" viewBox="0 0 24 24" class="text-[1.1rem] mr-2" data-astro-cid-h5lvqo6o="true" data-icon="material-symbols:home"> <use href="#ai:material-symbols:home"></use> </svg> 主页 </div> <svg width="1em" height="1em" class="transition text-[1.25rem] text-[var(--primary)]" data-astro-cid-h5lvqo6o="true" data-icon="material-symbols:chevron-right-rounded"> <symbol id="ai:material-symbols:chevron-right-rounded" viewBox="0 0 24 24"><path fill="currentColor" d="M12.6 12L8.7 8.1q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l4.6 4.6q.15.15.213.325t.062.375t-.062.375t-.213.325l-4.6 4.6q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7z"/></symbol><use href="#ai:material-symbols:chevron-right-rounded"></use> </svg> </a> </div><div class="mobile-menu-item" data-astro-cid-h5lvqo6o> <!-- 普通链接项目 -->
<a href="/blog/archive/" class="group flex justify-between items-center py-2 pl-3 pr-1 rounded-lg gap-8
hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)] transition
" data-astro-cid-h5lvqo6o> <div class="flex items-center transition text-black/75 dark:text-white/75 font-bold group-hover:text-[var(--primary)] group-active:text-[var(--primary)]" data-astro-cid-h5lvqo6o> <svg width="1em" height="1em" viewBox="0 0 24 24" class="text-[1.1rem] mr-2" data-astro-cid-h5lvqo6o="true" data-icon="material-symbols:archive"> <use href="#ai:material-symbols:archive"></use> </svg> 归档 </div> <svg width="1em" height="1em" viewBox="0 0 24 24" class="transition text-[1.25rem] text-[var(--primary)]" data-astro-cid-h5lvqo6o="true" data-icon="material-symbols:chevron-right-rounded"> <use href="#ai:material-symbols:chevron-right-rounded"></use> </svg> </a> </div><div class="mobile-menu-item" data-astro-cid-h5lvqo6o> <!-- 有子菜单的项目 -->
<div class="mobile-dropdown" data-mobile-dropdown data-astro-cid-h5lvqo6o> <button class="group flex justify-between items-center py-2 pl-3 pr-1 rounded-lg gap-8 w-full text-left
hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)] transition" data-mobile-dropdown-trigger aria-expanded="false" data-astro-cid-h5lvqo6o> <div class="flex items-center transition text-black/75 dark:text-white/75 font-bold group-hover:text-[var(--primary)] group-active:text-[var(--primary)]" data-astro-cid-h5lvqo6o> <svg width="1em" height="1em" viewBox="0 0 24 24" class="text-[1.1rem] mr-2" data-astro-cid-h5lvqo6o="true" data-icon="material-symbols:code"> <use href="#ai:material-symbols:code"></use> </svg> 仓库 </div> <svg width="1em" height="1em" viewBox="0 0 24 24" class="transition text-[1.25rem] text-[var(--primary)] mobile-dropdown-arrow duration-200" data-astro-cid-h5lvqo6o="true" data-icon="material-symbols:keyboard-arrow-down-rounded"> <use href="#ai:material-symbols:keyboard-arrow-down-rounded"></use> </svg> </button> <div class="mobile-submenu" data-mobile-submenu data-astro-cid-h5lvqo6o> <a href="https://gitee.com/starlight-apk/feature-oss" class="group flex justify-between items-center py-2 pl-6 pr-1 rounded-lg gap-8
hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)] transition" target="_blank" data-astro-cid-h5lvqo6o> <div class="flex items-center transition text-black/60 dark:text-white/60 font-medium group-hover:text-[var(--primary)] group-active:text-[var(--primary)]" data-astro-cid-h5lvqo6o> <svg width="1em" height="1em" viewBox="0 0 24 24" class="text-[1.1rem] mr-2" data-astro-cid-h5lvqo6o="true" data-icon="mdi:git"> <use href="#ai:mdi:git"></use> </svg> Gitee </div> <svg width="1em" height="1em" viewBox="0 0 512 512" class="transition text-[0.75rem] text-black/25 dark:text-white/25 -translate-x-1" data-astro-cid-h5lvqo6o="true" data-icon="fa6-solid:arrow-up-right-from-square"> <use href="#ai:fa6-solid:arrow-up-right-from-square"></use> </svg> </a><a href="https://github.com/starlight-apk/feature-oss" class="group flex justify-between items-center py-2 pl-6 pr-1 rounded-lg gap-8
hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)] transition" target="_blank" data-astro-cid-h5lvqo6o> <div class="flex items-center transition text-black/60 dark:text-white/60 font-medium group-hover:text-[var(--primary)] group-active:text-[var(--primary)]" data-astro-cid-h5lvqo6o> <svg width="0.97em" height="1em" viewBox="0 0 496 512" class="text-[1.1rem] mr-2" data-astro-cid-h5lvqo6o="true" data-icon="fa6-brands:github"> <use href="#ai:fa6-brands:github"></use> </svg> GitHub </div> <svg width="1em" height="1em" viewBox="0 0 512 512" class="transition text-[0.75rem] text-black/25 dark:text-white/25 -translate-x-1" data-astro-cid-h5lvqo6o="true" data-icon="fa6-solid:arrow-up-right-from-square"> <use href="#ai:fa6-solid:arrow-up-right-from-square"></use> </svg> </a> </div> </div> </div> </div> <script type="module">document.addEventListener("DOMContentLoaded",function(){const r=document.querySelectorAll("[data-mobile-dropdown]");r.forEach(e=>{const t=e.querySelector("[data-mobile-dropdown-trigger]"),d=e.querySelector("[data-mobile-submenu]");!t||!d||t.addEventListener("click",function(o){o.preventDefault();const u=e.getAttribute("data-expanded")==="true";r.forEach(a=>{if(a!==e){a.setAttribute("data-expanded","false");const i=a.querySelector("[data-mobile-dropdown-trigger]");i&&i.setAttribute("aria-expanded","false")}});const n=!u;e.setAttribute("data-expanded",n.toString()),t.setAttribute("aria-expanded",n.toString())})})});</script> <astro-island uid="i6lC1" component-url="/blog/_astro/DisplaySettings.BkjQOgCB.js" component-export="default" renderer-url="/blog/_astro/client.svelte.9vP7DkWT.js" props="{}" ssr client="only" opts="{&quot;name&quot;:&quot;DisplaySettings&quot;,&quot;value&quot;:&quot;svelte&quot;}"></astro-island> </div> </div> <script type="module">function c(){localStorage.theme==="dark"?(document.documentElement.classList.remove("dark"),localStorage.theme="light"):(document.documentElement.classList.add("dark"),localStorage.theme="dark")}function d(){let e=document.getElementById("scheme-switch");e&&(e.onclick=function(){c()});let l=document.getElementById("display-settings-switch");l&&(l.onclick=function(){let t=document.getElementById("display-setting");t&&t.classList.toggle("float-panel-closed")});let n=document.getElementById("nav-menu-switch");n&&(n.onclick=function(){let t=document.getElementById("nav-menu-panel");t&&t.classList.toggle("float-panel-closed")})}d();function o(){const e=document.getElementById("navbar");if(!e||e.getAttribute("data-transparent-mode")!=="semifull")return;if(!(e.getAttribute("data-is-home")==="true")){window.semifullScrollHandler&&(window.removeEventListener("scroll",window.semifullScrollHandler),window.semifullScrollHandler=null),e.classList.add("scrolled");return}e.classList.remove("scrolled");let t=!1;function i(){(window.pageYOffset||document.documentElement.scrollTop)>50?e.classList.add("scrolled"):e.classList.remove("scrolled"),t=!1}function s(){t||(requestAnimationFrame(i),t=!0)}window.semifullScrollHandler&&window.removeEventListener("scroll",window.semifullScrollHandler),window.semifullScrollHandler=s,window.addEventListener("scroll",s,{passive:!0}),i()}window.initSemifullScrollDetection=o;document.readyState==="loading"?document.addEventListener("DOMContentLoaded",o):o();</script> <script>(function(){const scriptUrl = "/blog/pagefind/pagefind.js";
async function loadPagefind() {
try {
const response = await fetch(scriptUrl, { method: 'HEAD' });
if (!response.ok) {
throw new Error(`Pagefind script not found: ${response.status}`);
}
const pagefind = await import(scriptUrl);
await pagefind.options({
excerptLength: 20
});
window.pagefind = pagefind;
document.dispatchEvent(new CustomEvent('pagefindready'));
console.log('Pagefind loaded and initialized successfully, event dispatched.');
} catch (error) {
console.error('Failed to load Pagefind:', error);
window.pagefind = {
search: () => Promise.resolve({ results: [] }),
options: () => Promise.resolve(),
};
document.dispatchEvent(new CustomEvent('pagefindloaderror'));
console.log('Pagefind load error, event dispatched.');
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadPagefind);
} else {
loadPagefind();
}
})();</script> </div> </div> <div id="banner-wrapper" class="absolute z-10 w-full transition duration-700 overflow-hidden mobile-hide-banner" style="top: -30vh" data-astro-cid-haiuh7kc> <!-- Single image mode -->
<div class="relative h-full w-full" data-astro-cid-haiuh7kc> <!-- Mobile: use mobile-specific image with same logic as desktop --> <div class="block lg:hidden object-cover h-full w-full transition duration-700 opacity-100 overflow-hidden relative"> <div class="transition absolute inset-0 dark:bg-black/10 bg-opacity-50 pointer-events-none"></div> <img src="/blog/assets/placeholder.jpg" alt="Mobile banner image of the blog" class="w-full h-full object-cover" style="object-position: center"> </div> <!-- Desktop: use desktop-specific image --> <div id="banner" class="hidden lg:block object-cover h-full transition duration-700 opacity-100 overflow-hidden relative"> <div class="transition absolute inset-0 dark:bg-black/10 bg-opacity-50 pointer-events-none"></div> <img src="/blog/assets/placeholder.jpg" alt="Desktop banner image of the blog" class="w-full h-full object-cover" style="object-position: center"> </div> </div> <!-- Home page text overlay --> <div class="banner-text-overlay absolute inset-0 z-20 flex items-center justify-center hidden" data-astro-cid-haiuh7kc> <div class="w-4/5 lg:w-3/4 text-center mb-0" data-astro-cid-haiuh7kc> <div class="flex flex-col" data-astro-cid-haiuh7kc> <h1 class="banner-title text-6xl lg:text-8xl font-bold text-white drop-shadow-lg mb-2 lg:mb-4" data-astro-cid-haiuh7kc> FutureOSS </h1> <h2 class="banner-subtitle text-xl lg:text-3xl text-white/90 drop-shadow-md" data-astro-cid-haiuh7kc> <span class="typewriter " data-text="[&#34;一切皆为插件的开发者工具运行时框架&#34;,&#34;插件热插拔 · 依赖自动解析 · 熔断降级 · 事件驱动&#34;]" data-speed="100" data-delete-speed="50" data-pause-time="2000" data-astro-cid-4iv2rnoh></span> <script type="module">class n{element;texts;currentTextIndex=0;speed;deleteSpeed;pauseTime;currentIndex=0;isDeleting=!1;timeoutId=null;constructor(t){this.element=t;const e=t.dataset.text||"";try{const i=JSON.parse(e);this.texts=Array.isArray(i)?i:[e]}catch{this.texts=[e]}this.speed=parseInt(t.dataset.speed||"100"),this.deleteSpeed=parseInt(t.dataset.deleteSpeed||"50"),this.pauseTime=parseInt(t.dataset.pauseTime||"2000"),this.texts.length>1&&!this.isTypewriterEnabled()?this.showRandomText():this.start()}isTypewriterEnabled(){return this.element.dataset.speed!==void 0||this.element.dataset.deleteSpeed!==void 0||this.element.dataset.pauseTime!==void 0}showRandomText(){const t=Math.floor(Math.random()*this.texts.length);this.element.textContent=this.texts[t]}start(){this.texts.length!==0&&this.type()}getCurrentText(){return this.texts[this.currentTextIndex]||""}type(){const t=this.getCurrentText();this.isDeleting?this.currentIndex>0?(this.currentIndex--,this.element.textContent=t.substring(0,this.currentIndex),this.timeoutId=window.setTimeout(()=>this.type(),this.deleteSpeed)):(this.isDeleting=!1,this.currentTextIndex=(this.currentTextIndex+1)%this.texts.length,this.timeoutId=window.setTimeout(()=>this.type(),this.speed)):this.currentIndex<t.length?(this.currentIndex++,this.element.textContent=t.substring(0,this.currentIndex),this.timeoutId=window.setTimeout(()=>this.type(),this.speed)):this.texts.length>1&&(this.isDeleting=!0,this.timeoutId=window.setTimeout(()=>this.type(),this.pauseTime))}destroy(){this.timeoutId&&clearTimeout(this.timeoutId)}}document.addEventListener("DOMContentLoaded",()=>{document.querySelectorAll(".typewriter").forEach(t=>{new n(t)})});document.addEventListener("swup:contentReplaced",()=>{document.querySelectorAll(".typewriter").forEach(t=>{new n(t)})});</script> </h2> </div> </div> </div> <!-- Water waves effect --> <div class="waves absolute -bottom-[1px] h-[10vh] max-h-[9.375rem] min-h-[3.125rem] w-full md:h-[15vh]" id="header-waves" style="transform: translateZ(0); will-change: fill;" data-astro-cid-haiuh7kc> <svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 20 150 32" preserveAspectRatio="none" shape-rendering="auto" style="transform: translateZ(0); backface-visibility: hidden;" data-astro-cid-haiuh7kc> <defs data-astro-cid-haiuh7kc> <path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v48h-352z" data-astro-cid-haiuh7kc></path> </defs> <g class="parallax" style="transform: translateZ(0);" data-astro-cid-haiuh7kc> <use xlink:href="#gentle-wave" x="48" y="0" class="opacity-25 fill-[var(--page-bg)]" style="animation-delay: -2s; animation-duration: 7s; will-change: transform;" data-astro-cid-haiuh7kc></use> <use xlink:href="#gentle-wave" x="48" y="3" class="opacity-50 fill-[var(--page-bg)]" style="animation-delay: -3s; animation-duration: 10s; will-change: transform;" data-astro-cid-haiuh7kc></use> <use xlink:href="#gentle-wave" x="48" y="5" class="opacity-75 fill-[var(--page-bg)]" style="animation-delay: -4s; animation-duration: 13s; will-change: transform;" data-astro-cid-haiuh7kc></use> <use xlink:href="#gentle-wave" x="48" y="7" class="fill-[var(--page-bg)]" style="animation-delay: -5s; animation-duration: 20s; will-change: transform;" data-astro-cid-haiuh7kc></use> </g> </svg> </div> </div> <div class="absolute w-full z-30 pointer-events-none mobile-main-no-banner " style="top: calc(35vh - 3.5rem)" data-astro-cid-haiuh7kc> <!-- The pointer-events-none here prevent blocking the click event of the TOC --> <div class="relative max-w-[var(--page-width)] mx-auto pointer-events-auto" data-astro-cid-haiuh7kc> <div id="main-grid" class="transition duration-700 w-full left-0 right-0 grid grid-cols-1 md:grid-cols-[17.5rem_1fr] lg:grid-cols-[17.5rem_1fr] grid-rows-[auto_1fr_auto] lg:grid-rows-[auto] mx-auto gap-4 px-0 md:px-4 " data-astro-cid-haiuh7kc> <!-- Banner image credit --> <div id="sidebar" class="mb-4 row-start-2 row-end-3 col-span-2 onload-animation block md:block md:row-start-1 md:row-end-2 md:max-w-[17.5rem] md:col-start-1 md:col-end-2 lg:block lg:row-start-1 lg:row-end-2 lg:max-w-[17.5rem] lg:col-start-1 lg:col-end-2 w-full" data-astro-cid-gfmxq3pg> <!-- 顶部固定组件区域 --> <div class="flex flex-col w-full gap-4 mb-4" data-astro-cid-gfmxq3pg> <div class="card-base p-3"> <a aria-label="Go to About Page" href="/blog/about/" class="group block relative mx-auto mt-1 lg:mx-0 lg:mt-0 mb-3
max-w-[12rem] lg:max-w-none overflow-hidden rounded-xl active:scale-95"> <div class="absolute transition pointer-events-none group-hover:bg-black/30 group-active:bg-black/50
w-full h-full z-50 flex items-center justify-center"> <svg width="1.13em" height="1em" class="transition opacity-0 scale-90 group-hover:scale-100 group-hover:opacity-100 text-white text-5xl" data-icon="fa6-regular:address-card"> <symbol id="ai:fa6-regular:address-card" viewBox="0 0 576 512"><path fill="currentColor" d="M512 80c8.8 0 16 7.2 16 16v320c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16V96c0-8.8 7.2-16 16-16zM64 32C28.7 32 0 60.7 0 96v320c0 35.3 28.7 64 64 64h448c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64zm144 224a64 64 0 1 0 0-128a64 64 0 1 0 0 128m-32 32c-44.2 0-80 35.8-80 80c0 8.8 7.2 16 16 16h192c8.8 0 16-7.2 16-16c0-44.2-35.8-80-80-80zm200-144c-13.3 0-24 10.7-24 24s10.7 24 24 24h80c13.3 0 24-10.7 24-24s-10.7-24-24-24zm0 96c-13.3 0-24 10.7-24 24s10.7 24 24 24h80c13.3 0 24-10.7 24-24s-10.7-24-24-24z"/></symbol><use href="#ai:fa6-regular:address-card"></use> </svg> </div> <div class="mx-auto lg:w-full h-full lg:mt-0 overflow-hidden relative"> <div class="transition absolute inset-0 dark:bg-black/10 bg-opacity-50 pointer-events-none"></div> <img src="/blog/_astro/avatar.DslDo0tY_Z2tanD.webp" alt="Profile Image of the Author" style="object-position: center" loading="lazy" decoding="async" fetchpriority="auto" width="640" height="640" class="w-full h-full object-cover"> </div> </a> <div class="px-2"> <div class="font-bold text-xl text-center mb-1 dark:text-neutral-50 transition">FutureOSS</div> <div class="h-1 w-5 bg-[var(--primary)] mx-auto rounded-full mb-2 transition"></div> <div class="text-center text-neutral-400 mb-2.5 transition">一切皆为插件的开发者工具运行时框架</div> <div class="flex gap-2 justify-center mb-1"> <a rel="me" aria-label="Gitee" href="https://gitee.com/starlight-apk/feature-oss" target="_blank" class="btn-regular rounded-lg h-10 w-10 active:scale-90"> <svg width="1em" height="1em" class="text-[1.5rem]" data-icon="mdi:git"> <symbol id="ai:mdi:git" viewBox="0 0 24 24"><path fill="currentColor" d="M2.6 10.59L8.38 4.8l1.69 1.7c-.24.85.15 1.78.93 2.23v5.54c-.6.34-1 .99-1 1.73a2 2 0 0 0 2 2a2 2 0 0 0 2-2c0-.74-.4-1.39-1-1.73V9.41l2.07 2.09c-.07.15-.07.32-.07.5a2 2 0 0 0 2 2a2 2 0 0 0 2-2a2 2 0 0 0-2-2c-.18 0-.35 0-.5.07L13.93 7.5a1.98 1.98 0 0 0-1.15-2.34c-.43-.16-.88-.2-1.28-.09L9.8 3.38l.79-.78c.78-.79 2.04-.79 2.82 0l7.99 7.99c.79.78.79 2.04 0 2.82l-7.99 7.99c-.78.79-2.04.79-2.82 0L2.6 13.41c-.79-.78-.79-2.04 0-2.82"/></symbol><use href="#ai:mdi:git"></use> </svg> </a><a rel="me" aria-label="GitHub" href="https://github.com/starlight-apk/feature-oss" target="_blank" class="btn-regular rounded-lg h-10 w-10 active:scale-90"> <svg width="0.97em" height="1em" class="text-[1.5rem]" data-icon="fa6-brands:github"> <symbol id="ai:fa6-brands:github" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6c-3.3.3-5.6-1.3-5.6-3.6c0-2 2.3-3.6 5.2-3.6c3-.3 5.6 1.3 5.6 3.6m-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9c2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3m44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9c.3 2 2.9 3.3 5.9 2.6c2.9-.7 4.9-2.6 4.6-4.6c-.3-1.9-3-3.2-5.9-2.9M244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2c12.8 2.3 17.3-5.6 17.3-12.1c0-6.2-.3-40.4-.3-61.4c0 0-70 15-84.7-29.8c0 0-11.4-29.1-27.8-36.6c0 0-22.9-15.7 1.6-15.4c0 0 24.9 2 38.6 25.8c21.9 38.6 58.6 27.5 72.9 20.9c2.3-16 8.8-27.1 16-33.7c-55.9-6.2-112.3-14.3-112.3-110.5c0-27.5 7.6-41.3 23.6-58.9c-2.6-6.5-11.1-33.3 2.6-67.9c20.9-6.5 69 27 69 27c20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27c13.7 34.7 5.2 61.4 2.6 67.9c16 17.7 25.8 31.5 25.8 58.9c0 96.5-58.9 104.2-114.8 110.5c9.2 7.9 17 22.9 17 46.4c0 33.7-.3 75.4-.3 83.6c0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252C496 113.3 383.5 8 244.8 8M97.2 352.9c-1.3 1-1 3.3.7 5.2c1.6 1.6 3.9 2.3 5.2 1c1.3-1 1-3.3-.7-5.2c-1.6-1.6-3.9-2.3-5.2-1m-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9c1.6 1 3.6.7 4.3-.7c.7-1.3-.3-2.9-2.3-3.9c-2-.6-3.6-.3-4.3.7m32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2c2.3 2.3 5.2 2.6 6.5 1c1.3-1.3.7-4.3-1.3-6.2c-2.2-2.3-5.2-2.6-6.5-1m-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2c-1.4-2.3-4-3.3-5.6-2"/></symbol><use href="#ai:fa6-brands:github"></use> </svg> </a> </div> </div> </div><!-- 组件显示现在由sidebarLayoutConfig统一控制无需检查config.enable --><widget-layout data-id="announcement" data-is-collapsed="undefined" class="pb-4 card-base onload-animation" style="animation-delay: 50ms; " data-astro-cid-ucso7hve="true"> <div class="font-bold transition text-lg text-neutral-900 dark:text-neutral-100 relative ml-8 mt-4 mb-2
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
before:absolute before:left-[-16px] before:top-[5.5px]" data-astro-cid-ucso7hve style="">欢迎来到 FutureOSS</div> <div id="announcement" class="collapse-wrapper px-4 overflow-hidden" data-astro-cid-ucso7hve style=""> <div> <!-- 公告栏内容 --> <div class="text-neutral-600 dark:text-neutral-300 leading-relaxed mb-3"> 一切皆为插件的开发者工具运行时框架,支持插件热插拔、依赖自动解析、熔断降级、事件驱动等企业级稳定性机制。 </div> <!-- 可选链接和关闭按钮 --> <div class="flex items-center justify-between gap-3"> <div> <a href="https://gitee.com/starlight-apk/feature-oss/wikis/Home" target="_blank" rel="noopener noreferrer" class="btn-regular rounded-lg px-3 py-1.5 text-sm font-medium active:scale-95 transition-transform"> 快速开始 </a> </div> <button class="btn-regular rounded-lg h-8 w-8 text-sm hover:bg-red-100 dark:hover:bg-red-900/30 transition-colors" onclick="closeAnnouncement()" aria-label="关闭"> <svg width="0.75em" height="1em" class="text-sm" data-icon="fa6-solid:xmark"> <symbol id="ai:fa6-solid:xmark" viewBox="0 0 384 512"><path fill="currentColor" d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7L86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256L41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3l105.4 105.3c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256z"/></symbol><use href="#ai:fa6-solid:xmark"></use> </svg> </button> </div> </div> </div> </widget-layout> <script type="module">class d extends HTMLElement{constructor(){if(super(),this.dataset.isCollapsed!=="true")return;const e=this.dataset.id,t=this.querySelector(".expand-btn"),s=this.querySelector(`#${e}`);t.addEventListener("click",()=>{s.classList.remove("collapsed"),t.classList.add("hidden")})}}customElements.get("widget-layout")||customElements.define("widget-layout",d);</script> <script type="module">function n(){const e=document.querySelector('widget-layout[data-id="announcement"]');e&&(e.style.display="none",localStorage.setItem("announcementClosed","true"))}document.addEventListener("DOMContentLoaded",function(){const e=document.querySelector('widget-layout[data-id="announcement"]');e&&localStorage.getItem("announcementClosed")==="true"&&(e.style.display="none")});window.closeAnnouncement=n;</script> </div> <!-- 粘性组件区域 --> <div id="sidebar-sticky" class="transition-all duration-700 flex flex-col w-full gap-4 top-4 sticky top-4" data-astro-cid-gfmxq3pg> <widget-layout data-id="categories" data-is-collapsed="false" class="pb-4 card-base onload-animation" style="animation-delay: 150ms; --collapsedHeight: 7.5rem;" data-astro-cid-ucso7hve="true"> <div class="font-bold transition text-lg text-neutral-900 dark:text-neutral-100 relative ml-8 mt-4 mb-2
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
before:absolute before:left-[-16px] before:top-[5.5px]" data-astro-cid-ucso7hve style="--collapsedHeight: 7.5rem;">分类</div> <div id="categories" class="collapse-wrapper px-4 overflow-hidden" data-astro-cid-ucso7hve style="--collapsedHeight: 7.5rem;"> <a href="/blog/archive/?category=Examples" aria-label="View all posts in the Examples category"> <button class="
w-full
h-10
rounded-lg
bg-none
hover:bg-[var(--btn-plain-bg-hover)]
active:bg-[var(--btn-plain-bg-active)]
transition-all
pl-2
hover:pl-3
text-neutral-700
hover:text-[var(--primary)]
dark:text-neutral-300
dark:hover:text-[var(--primary)]
"> <div class="flex items-center justify-between relative mr-2"> <div class="overflow-hidden text-left whitespace-nowrap overflow-ellipsis "> Examples </div> <div class="transition px-2 h-7 ml-4 min-w-[2rem] rounded-lg text-sm font-bold
text-[var(--btn-content)] dark:text-[var(--deep-text)]
bg-[oklch(0.95_0.025_var(--hue))] dark:bg-[var(--primary)]
flex items-center justify-center"> 4 </div> </div> </button> </a> </div> </widget-layout> <widget-layout data-id="tags" data-is-collapsed="false" class="pb-4 card-base onload-animation" style="animation-delay: 200ms; --collapsedHeight: 7.5rem;" data-astro-cid-ucso7hve="true"> <div class="font-bold transition text-lg text-neutral-900 dark:text-neutral-100 relative ml-8 mt-4 mb-2
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
before:absolute before:left-[-16px] before:top-[5.5px]" data-astro-cid-ucso7hve style="--collapsedHeight: 7.5rem;">标签</div> <div id="tags" class="collapse-wrapper px-4 overflow-hidden" data-astro-cid-ucso7hve style="--collapsedHeight: 7.5rem;"> <div class="flex gap-2 flex-wrap"> <a href="/blog/archive/?tag=Blogging" aria-label="View all posts with the Blogging tag" class="btn-regular h-8 text-sm px-3 rounded-lg"> Blogging </a><a href="/blog/archive/?tag=Example" aria-label="View all posts with the Example tag" class="btn-regular h-8 text-sm px-3 rounded-lg"> Example </a><a href="/blog/archive/?tag=Markdown" aria-label="View all posts with the Markdown tag" class="btn-regular h-8 text-sm px-3 rounded-lg"> Markdown </a><a href="/blog/archive/?tag=Mermaid" aria-label="View all posts with the Mermaid tag" class="btn-regular h-8 text-sm px-3 rounded-lg"> Mermaid </a><a href="/blog/archive/?tag=Video" aria-label="View all posts with the Video tag" class="btn-regular h-8 text-sm px-3 rounded-lg"> Video </a> </div> </div> </widget-layout> </div> </div> <!-- 响应式样式和JavaScript --> <script type="module" src="/blog/_astro/SideBar.astro_astro_type_script_index_0_lang.Fy0FqEdY.js"></script> <main id="swup-container" class="transition-swup-fade overflow-hidden w-full col-span-2 md:col-start-2 md:col-end-3 lg:col-start-2 lg:col-end-3" data-astro-cid-haiuh7kc> <div id="content-wrapper" class="onload-animation" data-astro-cid-haiuh7kc> <!-- the overflow-hidden here prevent long text break the layout--> <!-- make id different from windows.swup global property --> <div class="flex w-full rounded-[var(--radius-large)] overflow-hidden relative mb-4"> <div id="post-container" class="card-base z-10 px-6 md:px-9 pt-6 pb-4 relative w-full "> <!-- word count and reading time --> <div class="flex flex-row text-black/30 dark:text-white/30 gap-5 mb-3 transition onload-animation"> <div class="flex flex-row items-center"> <div class="transition h-6 w-6 rounded-md bg-black/5 dark:bg-white/10 text-black/50 dark:text-white/50 flex items-center justify-center mr-2"> <svg width="1em" height="1em" data-icon="material-symbols:notes-rounded"> <symbol id="ai:material-symbols:notes-rounded" viewBox="0 0 24 24"><path fill="currentColor" d="M4 18q-.425 0-.712-.288T3 17t.288-.712T4 16h10q.425 0 .713.288T15 17t-.288.713T14 18zm0-5q-.425 0-.712-.288T3 12t.288-.712T4 11h16q.425 0 .713.288T21 12t-.288.713T20 13zm0-5q-.425 0-.712-.288T3 7t.288-.712T4 6h16q.425 0 .713.288T21 7t-.288.713T20 8z"/></symbol><use href="#ai:material-symbols:notes-rounded"></use> </svg> </div> <div class="text-sm">578 字</div> </div> <div class="flex flex-row items-center"> <div class="transition h-6 w-6 rounded-md bg-black/5 dark:bg-white/10 text-black/50 dark:text-white/50 flex items-center justify-center mr-2"> <svg width="1em" height="1em" data-icon="material-symbols:schedule-outline-rounded"> <symbol id="ai:material-symbols:schedule-outline-rounded" viewBox="0 0 24 24"><path fill="currentColor" d="M13 11.6V8q0-.425-.288-.712T12 7t-.712.288T11 8v3.975q0 .2.075.388t.225.337l3.3 3.3q.275.275.7.275T16 16t.275-.7t-.275-.7zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.325 0 5.663-2.337T20 12t-2.337-5.663T12 4T6.337 6.338T4 12t2.338 5.663T12 20"/></symbol><use href="#ai:material-symbols:schedule-outline-rounded"></use> </svg> </div> <div class="text-sm"> 3 分钟 </div> </div> </div> <!-- title --> <div class="relative onload-animation"> <div data-pagefind-body data-pagefind-weight="10" data-pagefind-meta="title" class="transition w-full block font-bold mb-3
text-3xl md:text-[2.25rem]/[2.75rem]
text-black/90 dark:text-white/90
md:before:w-1 before:h-5 before:rounded-md before:bg-[var(--primary)]
before:absolute before:top-[0.75rem] before:left-[-1.125rem]
"> Markdown Mermaid </div> </div> <!-- metadata --> <div class="onload-animation"> <div class="flex flex-wrap text-neutral-500 dark:text-neutral-400 items-center gap-4 gap-x-4 gap-y-2 mb-5"> <!-- publish date --> <div class="flex items-center"> <div class="meta-icon"> <svg width="1em" height="1em" class="text-xl" data-icon="material-symbols:calendar-today-outline-rounded"> <symbol id="ai:material-symbols:calendar-today-outline-rounded" viewBox="0 0 24 24"><path fill="currentColor" d="M5 22q-.825 0-1.412-.587T3 20V6q0-.825.588-1.412T5 4h1V3q0-.425.288-.712T7 2t.713.288T8 3v1h8V3q0-.425.288-.712T17 2t.713.288T18 3v1h1q.825 0 1.413.588T21 6v14q0 .825-.587 1.413T19 22zm0-2h14V10H5zM5 8h14V6H5zm0 0V6z"/></symbol><use href="#ai:material-symbols:calendar-today-outline-rounded"></use> </svg> </div> <span class="text-50 text-sm font-medium">2023-10-01</span> </div> <!-- update date --> <!-- categories --> <div class="flex items-center"> <div class="meta-icon"> <svg width="1em" height="1em" class="text-xl" data-icon="material-symbols:book-2-outline-rounded"> <symbol id="ai:material-symbols:book-2-outline-rounded" viewBox="0 0 24 24"><path fill="currentColor" d="M6 15.325q.35-.175.725-.25T7.5 15H8V4h-.5q-.625 0-1.062.438T6 5.5zM10 15h8V4h-8zm-4 .325V4zM7.5 22q-1.45 0-2.475-1.025T4 18.5v-13q0-1.45 1.025-2.475T7.5 2H18q.825 0 1.413.587T20 4v12.525q0 .2-.162.363t-.588.362q-.35.175-.55.5t-.2.75t.2.763t.55.487t.55.413t.2.562v.25q0 .425-.288.725T19 22zm0-2h9.325q-.15-.35-.237-.712T16.5 18.5q0-.4.075-.775t.25-.725H7.5q-.65 0-1.075.438T6 18.5q0 .65.425 1.075T7.5 20"/></symbol><use href="#ai:material-symbols:book-2-outline-rounded"></use> </svg> </div> <div class="flex flex-row flex-nowrap items-center"> <a href="/blog/archive/?category=Examples" aria-label="View all posts in the Examples category" class="link-lg transition text-50 text-sm font-medium
hover:text-[var(--primary)] dark:hover:text-[var(--primary)] whitespace-nowrap"> Examples </a> </div> </div> <!-- tags --> <div class="items-center flex"> <div class="meta-icon"> <svg width="1em" height="1em" class="text-xl" data-icon="material-symbols:tag-rounded"> <symbol id="ai:material-symbols:tag-rounded" viewBox="0 0 24 24"><path fill="currentColor" d="m9 16l-.825 3.275q-.075.325-.325.525t-.6.2q-.475 0-.775-.375T6.3 18.8L7 16H4.275q-.5 0-.8-.387T3.3 14.75q.075-.35.35-.55t.625-.2H7.5l1-4H5.775q-.5 0-.8-.387T4.8 8.75q.075-.35.35-.55t.625-.2H9l.825-3.275Q9.9 4.4 10.15 4.2t.6-.2q.475 0 .775.375t.175.825L11 8h4l.825-3.275q.075-.325.325-.525t.6-.2q.475 0 .775.375t.175.825L17 8h2.725q.5 0 .8.387t.175.863q-.075.35-.35.55t-.625.2H16.5l-1 4h2.725q.5 0 .8.388t.175.862q-.075.35-.35.55t-.625.2H15l-.825 3.275q-.075.325-.325.525t-.6.2q-.475 0-.775-.375T12.3 18.8L13 16zm.5-2h4l1-4h-4z"/></symbol><use href="#ai:material-symbols:tag-rounded"></use> </svg> </div> <div class="flex flex-row flex-nowrap items-center"> <div class="hidden mx-1.5 text-[var(--meta-divider)] text-sm">/</div>
<a href="/blog/archive/?tag=Markdown" aria-label="View all posts with the Markdown tag" class="link-lg transition text-50 text-sm font-medium
hover:text-[var(--primary)] dark:hover:text-[var(--primary)] whitespace-nowrap"> Markdown </a><div class="mx-1.5 text-[var(--meta-divider)] text-sm">/</div>
<a href="/blog/archive/?tag=Blogging" aria-label="View all posts with the Blogging tag" class="link-lg transition text-50 text-sm font-medium
hover:text-[var(--primary)] dark:hover:text-[var(--primary)] whitespace-nowrap"> Blogging </a><div class="mx-1.5 text-[var(--meta-divider)] text-sm">/</div>
<a href="/blog/archive/?tag=Mermaid" aria-label="View all posts with the Mermaid tag" class="link-lg transition text-50 text-sm font-medium
hover:text-[var(--primary)] dark:hover:text-[var(--primary)] whitespace-nowrap"> Mermaid </a> </div> </div> <!-- 访问量首页不显示且umami.enable为true时显示 --> </div> <div class="border-[var(--line-divider)] border-dashed border-b-[1px] mb-5"></div> </div> <!-- always show cover as long as it has one --> <div data-pagefind-body class="prose dark:prose-invert prose-base !max-w-none custom-md mb-6 markdown-content onload-animation"> <!--<div class="prose dark:prose-invert max-w-none custom-md">--> <!--<div class="max-w-none custom-md">--> <section><h1 id="complete-guide-to-markdown-with-mermaid-diagrams">Complete Guide to Markdown with Mermaid Diagrams<a class="anchor" href="#complete-guide-to-markdown-with-mermaid-diagrams"><span class="anchor-icon" data-pagefind-ignore="">#</span></a></h1><p>This article demonstrates how to create various complex diagrams using Mermaid in Markdown documents, including flowcharts, sequence diagrams, Gantt charts, class diagrams, and state diagrams.</p><section><h2 id="flowchart-example">Flowchart Example<a class="anchor" href="#flowchart-example"><span class="anchor-icon" data-pagefind-ignore="">#</span></a></h2><p>Flowcharts are excellent for representing processes or algorithm steps.</p><div class="mermaid-diagram-container"><div class="mermaid-wrapper" id="mermaid-uhbpzm"><div class="mermaid" data-mermaid-code="graph TD
A[Start] --> B{Condition Check}
B -->|Yes| C[Process Step 1]
B -->|No| D[Process Step 2]
C --> E[Subprocess]
D --> E
subgraph E [Subprocess Details]
E1[Substep 1] --> E2[Substep 2]
E2 --> E3[Substep 3]
end
E --> F{Another Decision}
F -->|Option 1| G[Result 1]
F -->|Option 2| H[Result 2]
F -->|Option 3| I[Result 3]
G --> J[End]
H --> J
I --> J">graph TD
A[Start] --> B{Condition Check}
B -->|Yes| C[Process Step 1]
B -->|No| D[Process Step 2]
C --> E[Subprocess]
D --> E
subgraph E [Subprocess Details]
E1[Substep 1] --> E2[Substep 2]
E2 --> E3[Substep 3]
end
E --> F{Another Decision}
F -->|Option 1| G[Result 1]
F -->|Option 2| H[Result 2]
F -->|Option 3| I[Result 3]
G --> J[End]
H --> J
I --> J</div></div><script type="text/javascript">(() => {
// 单例模式:检查是否已经初始化过
if (window.mermaidInitialized) {
return;
}
window.mermaidInitialized = true;
// 记录当前主题状态,避免不必要的重新渲染
let currentTheme = null;
let isRendering = false; // 防止并发渲染
// 检查主题是否真的发生了变化
function hasThemeChanged() {
const isDark = document.documentElement.classList.contains("dark");
const newTheme = isDark ? "dark" : "default";
if (currentTheme !== newTheme) {
currentTheme = newTheme;
return true;
}
return false;
}
// 设置 MutationObserver 监听 html 元素的 class 属性变化
function setupMutationObserver() {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (
mutation.type === "attributes" &&
mutation.attributeName === "class"
) {
// 检查是否是 dark 类的变化
const target = mutation.target;
const wasDark = mutation.oldValue
? mutation.oldValue.includes("dark")
: false;
const isDark = target.classList.contains("dark");
if (wasDark !== isDark) {
if (hasThemeChanged()) {
renderMermaidDiagrams();
}
}
}
});
});
// 开始观察 html 元素的 class 属性变化
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
attributeOldValue: true,
});
}
// 设置其他事件监听器
function setupEventListeners() {
// 监听页面切换
document.addEventListener("astro:page-load", () => {
// 重新初始化主题状态
currentTheme = null;
if (hasThemeChanged()) {
renderMermaidDiagrams();
}
});
}
function initializeMermaid() {
if (window.mermaid && typeof window.mermaid.initialize === "function") {
// 初始化 Mermaid 配置
window.mermaid.initialize({
startOnLoad: false,
theme: "default",
themeVariables: {
fontFamily: "inherit",
fontSize: "16px",
},
securityLevel: "loose",
});
// 渲染所有 Mermaid 图表
renderMermaidDiagrams();
}
}
function renderMermaidDiagrams() {
// 防止并发渲染
if (isRendering) {
return;
}
isRendering = true;
const mermaidElements = document.querySelectorAll(
".mermaid[data-mermaid-code]",
);
// 延迟检测主题,确保 DOM 已经更新
setTimeout(() => {
const htmlElement = document.documentElement;
const isDark = htmlElement.classList.contains("dark");
const theme = isDark ? "dark" : "default";
// 更新 Mermaid 主题(只需要更新一次)
window.mermaid.initialize({
startOnLoad: false,
theme: theme,
themeVariables: {
fontFamily: "inherit",
fontSize: "16px",
// 强制应用主题变量
primaryColor: isDark ? "#ffffff" : "#000000",
primaryTextColor: isDark ? "#ffffff" : "#000000",
primaryBorderColor: isDark ? "#ffffff" : "#000000",
lineColor: isDark ? "#ffffff" : "#000000",
secondaryColor: isDark ? "#333333" : "#f0f0f0",
tertiaryColor: isDark ? "#555555" : "#e0e0e0",
},
securityLevel: "loose",
});
// 批量渲染所有图表
const renderPromises = Array.from(mermaidElements).map(
async (element, index) => {
try {
const code = element.getAttribute("data-mermaid-code");
if (code) {
// 清空容器
element.innerHTML = "";
// 渲染图表
const { svg } = await window.mermaid.render(
"mermaid-" + Math.random().toString(36).slice(-6),
code,
);
element.innerHTML = svg;
// 添加响应式支持
const svgElement = element.querySelector("svg");
if (svgElement) {
svgElement.setAttribute("width", "100%");
svgElement.removeAttribute("height");
svgElement.style.maxWidth = "100%";
svgElement.style.height = "auto";
// 强制应用样式
if (isDark) {
svgElement.style.filter = "brightness(0.9) contrast(1.1)";
} else {
svgElement.style.filter = "none";
}
}
}
} catch (error) {
console.error("Mermaid rendering error:", error);
element.innerHTML =
'<div class="mermaid-error">Failed to render diagram. Please check the syntax.</div>';
}
},
);
// 等待所有渲染完成
Promise.all(renderPromises)
.then(() => {
isRendering = false;
})
.catch((error) => {
isRendering = false;
console.error("Error rendering Mermaid diagrams:", error);
});
}, 100); // 延迟 100ms 确保 DOM 更新完成
}
// 初始化主题状态
function initializeThemeState() {
const isDark = document.documentElement.classList.contains("dark");
currentTheme = isDark ? "dark" : "default";
}
if (typeof window.mermaid === "undefined") {
// 动态加载 Mermaid 库 - 使用 CDN 链接
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js";
script.onload = () => {
initializeMermaid();
};
document.head.appendChild(script);
} else {
initializeMermaid();
}
// 设置监听器
setupMutationObserver();
setupEventListeners();
// 初始化主题状态
initializeThemeState();
// 初始渲染
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
renderMermaidDiagrams();
});
} else {
renderMermaidDiagrams();
}
})();
</script></div></section><section><h2 id="sequence-diagram-example">Sequence Diagram Example<a class="anchor" href="#sequence-diagram-example"><span class="anchor-icon" data-pagefind-ignore="">#</span></a></h2><p>Sequence diagrams show interactions between objects over time.</p><div class="mermaid-diagram-container"><div class="mermaid-wrapper" id="mermaid-ue6gd9"><div class="mermaid" data-mermaid-code="sequenceDiagram
participant User
participant WebApp
participant Server
participant Database
User->>WebApp: Submit Login Request
WebApp->>Server: Send Auth Request
Server->>Database: Query User Credentials
Database-->>Server: Return User Data
Server-->>WebApp: Return Auth Result
alt Auth Successful
WebApp->>User: Show Welcome Page
WebApp->>Server: Request User Data
Server->>Database: Get User Preferences
Database-->>Server: Return Preferences
Server-->>WebApp: Return User Data
WebApp->>User: Load Personalized Interface
else Auth Failed
WebApp->>User: Show Error Message
WebApp->>User: Prompt Re-entry
end">sequenceDiagram
participant User
participant WebApp
participant Server
participant Database
User->>WebApp: Submit Login Request
WebApp->>Server: Send Auth Request
Server->>Database: Query User Credentials
Database-->>Server: Return User Data
Server-->>WebApp: Return Auth Result
alt Auth Successful
WebApp->>User: Show Welcome Page
WebApp->>Server: Request User Data
Server->>Database: Get User Preferences
Database-->>Server: Return Preferences
Server-->>WebApp: Return User Data
WebApp->>User: Load Personalized Interface
else Auth Failed
WebApp->>User: Show Error Message
WebApp->>User: Prompt Re-entry
end</div></div><script type="text/javascript">(() => {
// 单例模式:检查是否已经初始化过
if (window.mermaidInitialized) {
return;
}
window.mermaidInitialized = true;
// 记录当前主题状态,避免不必要的重新渲染
let currentTheme = null;
let isRendering = false; // 防止并发渲染
// 检查主题是否真的发生了变化
function hasThemeChanged() {
const isDark = document.documentElement.classList.contains("dark");
const newTheme = isDark ? "dark" : "default";
if (currentTheme !== newTheme) {
currentTheme = newTheme;
return true;
}
return false;
}
// 设置 MutationObserver 监听 html 元素的 class 属性变化
function setupMutationObserver() {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (
mutation.type === "attributes" &&
mutation.attributeName === "class"
) {
// 检查是否是 dark 类的变化
const target = mutation.target;
const wasDark = mutation.oldValue
? mutation.oldValue.includes("dark")
: false;
const isDark = target.classList.contains("dark");
if (wasDark !== isDark) {
if (hasThemeChanged()) {
renderMermaidDiagrams();
}
}
}
});
});
// 开始观察 html 元素的 class 属性变化
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
attributeOldValue: true,
});
}
// 设置其他事件监听器
function setupEventListeners() {
// 监听页面切换
document.addEventListener("astro:page-load", () => {
// 重新初始化主题状态
currentTheme = null;
if (hasThemeChanged()) {
renderMermaidDiagrams();
}
});
}
function initializeMermaid() {
if (window.mermaid && typeof window.mermaid.initialize === "function") {
// 初始化 Mermaid 配置
window.mermaid.initialize({
startOnLoad: false,
theme: "default",
themeVariables: {
fontFamily: "inherit",
fontSize: "16px",
},
securityLevel: "loose",
});
// 渲染所有 Mermaid 图表
renderMermaidDiagrams();
}
}
function renderMermaidDiagrams() {
// 防止并发渲染
if (isRendering) {
return;
}
isRendering = true;
const mermaidElements = document.querySelectorAll(
".mermaid[data-mermaid-code]",
);
// 延迟检测主题,确保 DOM 已经更新
setTimeout(() => {
const htmlElement = document.documentElement;
const isDark = htmlElement.classList.contains("dark");
const theme = isDark ? "dark" : "default";
// 更新 Mermaid 主题(只需要更新一次)
window.mermaid.initialize({
startOnLoad: false,
theme: theme,
themeVariables: {
fontFamily: "inherit",
fontSize: "16px",
// 强制应用主题变量
primaryColor: isDark ? "#ffffff" : "#000000",
primaryTextColor: isDark ? "#ffffff" : "#000000",
primaryBorderColor: isDark ? "#ffffff" : "#000000",
lineColor: isDark ? "#ffffff" : "#000000",
secondaryColor: isDark ? "#333333" : "#f0f0f0",
tertiaryColor: isDark ? "#555555" : "#e0e0e0",
},
securityLevel: "loose",
});
// 批量渲染所有图表
const renderPromises = Array.from(mermaidElements).map(
async (element, index) => {
try {
const code = element.getAttribute("data-mermaid-code");
if (code) {
// 清空容器
element.innerHTML = "";
// 渲染图表
const { svg } = await window.mermaid.render(
"mermaid-" + Math.random().toString(36).slice(-6),
code,
);
element.innerHTML = svg;
// 添加响应式支持
const svgElement = element.querySelector("svg");
if (svgElement) {
svgElement.setAttribute("width", "100%");
svgElement.removeAttribute("height");
svgElement.style.maxWidth = "100%";
svgElement.style.height = "auto";
// 强制应用样式
if (isDark) {
svgElement.style.filter = "brightness(0.9) contrast(1.1)";
} else {
svgElement.style.filter = "none";
}
}
}
} catch (error) {
console.error("Mermaid rendering error:", error);
element.innerHTML =
'<div class="mermaid-error">Failed to render diagram. Please check the syntax.</div>';
}
},
);
// 等待所有渲染完成
Promise.all(renderPromises)
.then(() => {
isRendering = false;
})
.catch((error) => {
isRendering = false;
console.error("Error rendering Mermaid diagrams:", error);
});
}, 100); // 延迟 100ms 确保 DOM 更新完成
}
// 初始化主题状态
function initializeThemeState() {
const isDark = document.documentElement.classList.contains("dark");
currentTheme = isDark ? "dark" : "default";
}
if (typeof window.mermaid === "undefined") {
// 动态加载 Mermaid 库 - 使用 CDN 链接
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js";
script.onload = () => {
initializeMermaid();
};
document.head.appendChild(script);
} else {
initializeMermaid();
}
// 设置监听器
setupMutationObserver();
setupEventListeners();
// 初始化主题状态
initializeThemeState();
// 初始渲染
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
renderMermaidDiagrams();
});
} else {
renderMermaidDiagrams();
}
})();
</script></div></section><section><h2 id="gantt-chart-example">Gantt Chart Example<a class="anchor" href="#gantt-chart-example"><span class="anchor-icon" data-pagefind-ignore="">#</span></a></h2><p>Gantt charts are perfect for displaying project schedules and timelines.</p><div class="mermaid-diagram-container"><div class="mermaid-wrapper" id="mermaid-sw1gig"><div class="mermaid" data-mermaid-code="gantt
title Website Development Project Timeline
dateFormat YYYY-MM-DD
axisFormat %m/%d
section Design Phase
Requirements Analysis :a1, 2023-10-01, 7d
UI Design :a2, after a1, 10d
Prototype Creation :a3, after a2, 5d
section Development Phase
Frontend Development :b1, 2023-10-20, 15d
Backend Development :b2, after a2, 18d
Database Design :b3, after a1, 12d
section Testing Phase
Unit Testing :c1, after b1, 8d
Integration Testing :c2, after b2, 10d
User Acceptance Testing :c3, after c2, 7d
section Deployment
Production Deployment :d1, after c3, 3d
Launch :milestone, after d1, 0d">gantt
title Website Development Project Timeline
dateFormat YYYY-MM-DD
axisFormat %m/%d
section Design Phase
Requirements Analysis :a1, 2023-10-01, 7d
UI Design :a2, after a1, 10d
Prototype Creation :a3, after a2, 5d
section Development Phase
Frontend Development :b1, 2023-10-20, 15d
Backend Development :b2, after a2, 18d
Database Design :b3, after a1, 12d
section Testing Phase
Unit Testing :c1, after b1, 8d
Integration Testing :c2, after b2, 10d
User Acceptance Testing :c3, after c2, 7d
section Deployment
Production Deployment :d1, after c3, 3d
Launch :milestone, after d1, 0d</div></div><script type="text/javascript">(() => {
// 单例模式:检查是否已经初始化过
if (window.mermaidInitialized) {
return;
}
window.mermaidInitialized = true;
// 记录当前主题状态,避免不必要的重新渲染
let currentTheme = null;
let isRendering = false; // 防止并发渲染
// 检查主题是否真的发生了变化
function hasThemeChanged() {
const isDark = document.documentElement.classList.contains("dark");
const newTheme = isDark ? "dark" : "default";
if (currentTheme !== newTheme) {
currentTheme = newTheme;
return true;
}
return false;
}
// 设置 MutationObserver 监听 html 元素的 class 属性变化
function setupMutationObserver() {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (
mutation.type === "attributes" &&
mutation.attributeName === "class"
) {
// 检查是否是 dark 类的变化
const target = mutation.target;
const wasDark = mutation.oldValue
? mutation.oldValue.includes("dark")
: false;
const isDark = target.classList.contains("dark");
if (wasDark !== isDark) {
if (hasThemeChanged()) {
renderMermaidDiagrams();
}
}
}
});
});
// 开始观察 html 元素的 class 属性变化
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
attributeOldValue: true,
});
}
// 设置其他事件监听器
function setupEventListeners() {
// 监听页面切换
document.addEventListener("astro:page-load", () => {
// 重新初始化主题状态
currentTheme = null;
if (hasThemeChanged()) {
renderMermaidDiagrams();
}
});
}
function initializeMermaid() {
if (window.mermaid && typeof window.mermaid.initialize === "function") {
// 初始化 Mermaid 配置
window.mermaid.initialize({
startOnLoad: false,
theme: "default",
themeVariables: {
fontFamily: "inherit",
fontSize: "16px",
},
securityLevel: "loose",
});
// 渲染所有 Mermaid 图表
renderMermaidDiagrams();
}
}
function renderMermaidDiagrams() {
// 防止并发渲染
if (isRendering) {
return;
}
isRendering = true;
const mermaidElements = document.querySelectorAll(
".mermaid[data-mermaid-code]",
);
// 延迟检测主题,确保 DOM 已经更新
setTimeout(() => {
const htmlElement = document.documentElement;
const isDark = htmlElement.classList.contains("dark");
const theme = isDark ? "dark" : "default";
// 更新 Mermaid 主题(只需要更新一次)
window.mermaid.initialize({
startOnLoad: false,
theme: theme,
themeVariables: {
fontFamily: "inherit",
fontSize: "16px",
// 强制应用主题变量
primaryColor: isDark ? "#ffffff" : "#000000",
primaryTextColor: isDark ? "#ffffff" : "#000000",
primaryBorderColor: isDark ? "#ffffff" : "#000000",
lineColor: isDark ? "#ffffff" : "#000000",
secondaryColor: isDark ? "#333333" : "#f0f0f0",
tertiaryColor: isDark ? "#555555" : "#e0e0e0",
},
securityLevel: "loose",
});
// 批量渲染所有图表
const renderPromises = Array.from(mermaidElements).map(
async (element, index) => {
try {
const code = element.getAttribute("data-mermaid-code");
if (code) {
// 清空容器
element.innerHTML = "";
// 渲染图表
const { svg } = await window.mermaid.render(
"mermaid-" + Math.random().toString(36).slice(-6),
code,
);
element.innerHTML = svg;
// 添加响应式支持
const svgElement = element.querySelector("svg");
if (svgElement) {
svgElement.setAttribute("width", "100%");
svgElement.removeAttribute("height");
svgElement.style.maxWidth = "100%";
svgElement.style.height = "auto";
// 强制应用样式
if (isDark) {
svgElement.style.filter = "brightness(0.9) contrast(1.1)";
} else {
svgElement.style.filter = "none";
}
}
}
} catch (error) {
console.error("Mermaid rendering error:", error);
element.innerHTML =
'<div class="mermaid-error">Failed to render diagram. Please check the syntax.</div>';
}
},
);
// 等待所有渲染完成
Promise.all(renderPromises)
.then(() => {
isRendering = false;
})
.catch((error) => {
isRendering = false;
console.error("Error rendering Mermaid diagrams:", error);
});
}, 100); // 延迟 100ms 确保 DOM 更新完成
}
// 初始化主题状态
function initializeThemeState() {
const isDark = document.documentElement.classList.contains("dark");
currentTheme = isDark ? "dark" : "default";
}
if (typeof window.mermaid === "undefined") {
// 动态加载 Mermaid 库 - 使用 CDN 链接
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js";
script.onload = () => {
initializeMermaid();
};
document.head.appendChild(script);
} else {
initializeMermaid();
}
// 设置监听器
setupMutationObserver();
setupEventListeners();
// 初始化主题状态
initializeThemeState();
// 初始渲染
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
renderMermaidDiagrams();
});
} else {
renderMermaidDiagrams();
}
})();
</script></div></section><section><h2 id="class-diagram-example">Class Diagram Example<a class="anchor" href="#class-diagram-example"><span class="anchor-icon" data-pagefind-ignore="">#</span></a></h2><p>Class diagrams show the static structure of a system, including classes, attributes, methods, and their relationships.</p><div class="mermaid-diagram-container"><div class="mermaid-wrapper" id="mermaid-leqof8"><div class="mermaid" data-mermaid-code="classDiagram
class User {
+String username
+String password
+String email
+Boolean active
+login()
+logout()
+updateProfile()
}
class Article {
+String title
+String content
+Date publishDate
+Boolean published
+publish()
+edit()
+delete()
}
class Comment {
+String content
+Date commentDate
+addComment()
+deleteComment()
}
class Category {
+String name
+String description
+addArticle()
+removeArticle()
}
User &#x22;1&#x22; -- &#x22;*&#x22; Article : writes
User &#x22;1&#x22; -- &#x22;*&#x22; Comment : posts
Article &#x22;1&#x22; -- &#x22;*&#x22; Comment : has
Article &#x22;1&#x22; -- &#x22;*&#x22; Category : belongs to">classDiagram
class User {
+String username
+String password
+String email
+Boolean active
+login()
+logout()
+updateProfile()
}
class Article {
+String title
+String content
+Date publishDate
+Boolean published
+publish()
+edit()
+delete()
}
class Comment {
+String content
+Date commentDate
+addComment()
+deleteComment()
}
class Category {
+String name
+String description
+addArticle()
+removeArticle()
}
User "1" -- "*" Article : writes
User "1" -- "*" Comment : posts
Article "1" -- "*" Comment : has
Article "1" -- "*" Category : belongs to</div></div><script type="text/javascript">(() => {
// 单例模式:检查是否已经初始化过
if (window.mermaidInitialized) {
return;
}
window.mermaidInitialized = true;
// 记录当前主题状态,避免不必要的重新渲染
let currentTheme = null;
let isRendering = false; // 防止并发渲染
// 检查主题是否真的发生了变化
function hasThemeChanged() {
const isDark = document.documentElement.classList.contains("dark");
const newTheme = isDark ? "dark" : "default";
if (currentTheme !== newTheme) {
currentTheme = newTheme;
return true;
}
return false;
}
// 设置 MutationObserver 监听 html 元素的 class 属性变化
function setupMutationObserver() {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (
mutation.type === "attributes" &&
mutation.attributeName === "class"
) {
// 检查是否是 dark 类的变化
const target = mutation.target;
const wasDark = mutation.oldValue
? mutation.oldValue.includes("dark")
: false;
const isDark = target.classList.contains("dark");
if (wasDark !== isDark) {
if (hasThemeChanged()) {
renderMermaidDiagrams();
}
}
}
});
});
// 开始观察 html 元素的 class 属性变化
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
attributeOldValue: true,
});
}
// 设置其他事件监听器
function setupEventListeners() {
// 监听页面切换
document.addEventListener("astro:page-load", () => {
// 重新初始化主题状态
currentTheme = null;
if (hasThemeChanged()) {
renderMermaidDiagrams();
}
});
}
function initializeMermaid() {
if (window.mermaid && typeof window.mermaid.initialize === "function") {
// 初始化 Mermaid 配置
window.mermaid.initialize({
startOnLoad: false,
theme: "default",
themeVariables: {
fontFamily: "inherit",
fontSize: "16px",
},
securityLevel: "loose",
});
// 渲染所有 Mermaid 图表
renderMermaidDiagrams();
}
}
function renderMermaidDiagrams() {
// 防止并发渲染
if (isRendering) {
return;
}
isRendering = true;
const mermaidElements = document.querySelectorAll(
".mermaid[data-mermaid-code]",
);
// 延迟检测主题,确保 DOM 已经更新
setTimeout(() => {
const htmlElement = document.documentElement;
const isDark = htmlElement.classList.contains("dark");
const theme = isDark ? "dark" : "default";
// 更新 Mermaid 主题(只需要更新一次)
window.mermaid.initialize({
startOnLoad: false,
theme: theme,
themeVariables: {
fontFamily: "inherit",
fontSize: "16px",
// 强制应用主题变量
primaryColor: isDark ? "#ffffff" : "#000000",
primaryTextColor: isDark ? "#ffffff" : "#000000",
primaryBorderColor: isDark ? "#ffffff" : "#000000",
lineColor: isDark ? "#ffffff" : "#000000",
secondaryColor: isDark ? "#333333" : "#f0f0f0",
tertiaryColor: isDark ? "#555555" : "#e0e0e0",
},
securityLevel: "loose",
});
// 批量渲染所有图表
const renderPromises = Array.from(mermaidElements).map(
async (element, index) => {
try {
const code = element.getAttribute("data-mermaid-code");
if (code) {
// 清空容器
element.innerHTML = "";
// 渲染图表
const { svg } = await window.mermaid.render(
"mermaid-" + Math.random().toString(36).slice(-6),
code,
);
element.innerHTML = svg;
// 添加响应式支持
const svgElement = element.querySelector("svg");
if (svgElement) {
svgElement.setAttribute("width", "100%");
svgElement.removeAttribute("height");
svgElement.style.maxWidth = "100%";
svgElement.style.height = "auto";
// 强制应用样式
if (isDark) {
svgElement.style.filter = "brightness(0.9) contrast(1.1)";
} else {
svgElement.style.filter = "none";
}
}
}
} catch (error) {
console.error("Mermaid rendering error:", error);
element.innerHTML =
'<div class="mermaid-error">Failed to render diagram. Please check the syntax.</div>';
}
},
);
// 等待所有渲染完成
Promise.all(renderPromises)
.then(() => {
isRendering = false;
})
.catch((error) => {
isRendering = false;
console.error("Error rendering Mermaid diagrams:", error);
});
}, 100); // 延迟 100ms 确保 DOM 更新完成
}
// 初始化主题状态
function initializeThemeState() {
const isDark = document.documentElement.classList.contains("dark");
currentTheme = isDark ? "dark" : "default";
}
if (typeof window.mermaid === "undefined") {
// 动态加载 Mermaid 库 - 使用 CDN 链接
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js";
script.onload = () => {
initializeMermaid();
};
document.head.appendChild(script);
} else {
initializeMermaid();
}
// 设置监听器
setupMutationObserver();
setupEventListeners();
// 初始化主题状态
initializeThemeState();
// 初始渲染
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
renderMermaidDiagrams();
});
} else {
renderMermaidDiagrams();
}
})();
</script></div></section><section><h2 id="state-diagram-example">State Diagram Example<a class="anchor" href="#state-diagram-example"><span class="anchor-icon" data-pagefind-ignore="">#</span></a></h2><p>State diagrams show the sequence of states an object goes through during its life cycle.</p><div class="mermaid-diagram-container"><div class="mermaid-wrapper" id="mermaid-sj3vpw"><div class="mermaid" data-mermaid-code="stateDiagram-v2
[*] --> Draft
Draft --> UnderReview : submit
UnderReview --> Draft : reject
UnderReview --> Approved : approve
Approved --> Published : publish
Published --> Archived : archive
Published --> Draft : retract
state Published {
[*] --> Active
Active --> Hidden : temporarily hide
Hidden --> Active : restore
Active --> [*]
Hidden --> [*]
}
Archived --> [*]">stateDiagram-v2
[*] --> Draft
Draft --> UnderReview : submit
UnderReview --> Draft : reject
UnderReview --> Approved : approve
Approved --> Published : publish
Published --> Archived : archive
Published --> Draft : retract
state Published {
[*] --> Active
Active --> Hidden : temporarily hide
Hidden --> Active : restore
Active --> [*]
Hidden --> [*]
}
Archived --> [*]</div></div><script type="text/javascript">(() => {
// 单例模式:检查是否已经初始化过
if (window.mermaidInitialized) {
return;
}
window.mermaidInitialized = true;
// 记录当前主题状态,避免不必要的重新渲染
let currentTheme = null;
let isRendering = false; // 防止并发渲染
// 检查主题是否真的发生了变化
function hasThemeChanged() {
const isDark = document.documentElement.classList.contains("dark");
const newTheme = isDark ? "dark" : "default";
if (currentTheme !== newTheme) {
currentTheme = newTheme;
return true;
}
return false;
}
// 设置 MutationObserver 监听 html 元素的 class 属性变化
function setupMutationObserver() {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (
mutation.type === "attributes" &&
mutation.attributeName === "class"
) {
// 检查是否是 dark 类的变化
const target = mutation.target;
const wasDark = mutation.oldValue
? mutation.oldValue.includes("dark")
: false;
const isDark = target.classList.contains("dark");
if (wasDark !== isDark) {
if (hasThemeChanged()) {
renderMermaidDiagrams();
}
}
}
});
});
// 开始观察 html 元素的 class 属性变化
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
attributeOldValue: true,
});
}
// 设置其他事件监听器
function setupEventListeners() {
// 监听页面切换
document.addEventListener("astro:page-load", () => {
// 重新初始化主题状态
currentTheme = null;
if (hasThemeChanged()) {
renderMermaidDiagrams();
}
});
}
function initializeMermaid() {
if (window.mermaid && typeof window.mermaid.initialize === "function") {
// 初始化 Mermaid 配置
window.mermaid.initialize({
startOnLoad: false,
theme: "default",
themeVariables: {
fontFamily: "inherit",
fontSize: "16px",
},
securityLevel: "loose",
});
// 渲染所有 Mermaid 图表
renderMermaidDiagrams();
}
}
function renderMermaidDiagrams() {
// 防止并发渲染
if (isRendering) {
return;
}
isRendering = true;
const mermaidElements = document.querySelectorAll(
".mermaid[data-mermaid-code]",
);
// 延迟检测主题,确保 DOM 已经更新
setTimeout(() => {
const htmlElement = document.documentElement;
const isDark = htmlElement.classList.contains("dark");
const theme = isDark ? "dark" : "default";
// 更新 Mermaid 主题(只需要更新一次)
window.mermaid.initialize({
startOnLoad: false,
theme: theme,
themeVariables: {
fontFamily: "inherit",
fontSize: "16px",
// 强制应用主题变量
primaryColor: isDark ? "#ffffff" : "#000000",
primaryTextColor: isDark ? "#ffffff" : "#000000",
primaryBorderColor: isDark ? "#ffffff" : "#000000",
lineColor: isDark ? "#ffffff" : "#000000",
secondaryColor: isDark ? "#333333" : "#f0f0f0",
tertiaryColor: isDark ? "#555555" : "#e0e0e0",
},
securityLevel: "loose",
});
// 批量渲染所有图表
const renderPromises = Array.from(mermaidElements).map(
async (element, index) => {
try {
const code = element.getAttribute("data-mermaid-code");
if (code) {
// 清空容器
element.innerHTML = "";
// 渲染图表
const { svg } = await window.mermaid.render(
"mermaid-" + Math.random().toString(36).slice(-6),
code,
);
element.innerHTML = svg;
// 添加响应式支持
const svgElement = element.querySelector("svg");
if (svgElement) {
svgElement.setAttribute("width", "100%");
svgElement.removeAttribute("height");
svgElement.style.maxWidth = "100%";
svgElement.style.height = "auto";
// 强制应用样式
if (isDark) {
svgElement.style.filter = "brightness(0.9) contrast(1.1)";
} else {
svgElement.style.filter = "none";
}
}
}
} catch (error) {
console.error("Mermaid rendering error:", error);
element.innerHTML =
'<div class="mermaid-error">Failed to render diagram. Please check the syntax.</div>';
}
},
);
// 等待所有渲染完成
Promise.all(renderPromises)
.then(() => {
isRendering = false;
})
.catch((error) => {
isRendering = false;
console.error("Error rendering Mermaid diagrams:", error);
});
}, 100); // 延迟 100ms 确保 DOM 更新完成
}
// 初始化主题状态
function initializeThemeState() {
const isDark = document.documentElement.classList.contains("dark");
currentTheme = isDark ? "dark" : "default";
}
if (typeof window.mermaid === "undefined") {
// 动态加载 Mermaid 库 - 使用 CDN 链接
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js";
script.onload = () => {
initializeMermaid();
};
document.head.appendChild(script);
} else {
initializeMermaid();
}
// 设置监听器
setupMutationObserver();
setupEventListeners();
// 初始化主题状态
initializeThemeState();
// 初始渲染
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
renderMermaidDiagrams();
});
} else {
renderMermaidDiagrams();
}
})();
</script></div></section><section><h2 id="pie-chart-example">Pie Chart Example<a class="anchor" href="#pie-chart-example"><span class="anchor-icon" data-pagefind-ignore="">#</span></a></h2><p>Pie charts are ideal for displaying proportions and percentage data.</p><div class="mermaid-diagram-container"><div class="mermaid-wrapper" id="mermaid-awbcsm"><div class="mermaid" data-mermaid-code="pie title Website Traffic Sources Analysis
&#x22;Search Engines&#x22; : 45.6
&#x22;Direct Access&#x22; : 30.1
&#x22;Social Media&#x22; : 15.3
&#x22;Referral Links&#x22; : 6.4
&#x22;Other Sources&#x22; : 2.6">pie title Website Traffic Sources Analysis
"Search Engines" : 45.6
"Direct Access" : 30.1
"Social Media" : 15.3
"Referral Links" : 6.4
"Other Sources" : 2.6</div></div><script type="text/javascript">(() => {
// 单例模式:检查是否已经初始化过
if (window.mermaidInitialized) {
return;
}
window.mermaidInitialized = true;
// 记录当前主题状态,避免不必要的重新渲染
let currentTheme = null;
let isRendering = false; // 防止并发渲染
// 检查主题是否真的发生了变化
function hasThemeChanged() {
const isDark = document.documentElement.classList.contains("dark");
const newTheme = isDark ? "dark" : "default";
if (currentTheme !== newTheme) {
currentTheme = newTheme;
return true;
}
return false;
}
// 设置 MutationObserver 监听 html 元素的 class 属性变化
function setupMutationObserver() {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (
mutation.type === "attributes" &&
mutation.attributeName === "class"
) {
// 检查是否是 dark 类的变化
const target = mutation.target;
const wasDark = mutation.oldValue
? mutation.oldValue.includes("dark")
: false;
const isDark = target.classList.contains("dark");
if (wasDark !== isDark) {
if (hasThemeChanged()) {
renderMermaidDiagrams();
}
}
}
});
});
// 开始观察 html 元素的 class 属性变化
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
attributeOldValue: true,
});
}
// 设置其他事件监听器
function setupEventListeners() {
// 监听页面切换
document.addEventListener("astro:page-load", () => {
// 重新初始化主题状态
currentTheme = null;
if (hasThemeChanged()) {
renderMermaidDiagrams();
}
});
}
function initializeMermaid() {
if (window.mermaid && typeof window.mermaid.initialize === "function") {
// 初始化 Mermaid 配置
window.mermaid.initialize({
startOnLoad: false,
theme: "default",
themeVariables: {
fontFamily: "inherit",
fontSize: "16px",
},
securityLevel: "loose",
});
// 渲染所有 Mermaid 图表
renderMermaidDiagrams();
}
}
function renderMermaidDiagrams() {
// 防止并发渲染
if (isRendering) {
return;
}
isRendering = true;
const mermaidElements = document.querySelectorAll(
".mermaid[data-mermaid-code]",
);
// 延迟检测主题,确保 DOM 已经更新
setTimeout(() => {
const htmlElement = document.documentElement;
const isDark = htmlElement.classList.contains("dark");
const theme = isDark ? "dark" : "default";
// 更新 Mermaid 主题(只需要更新一次)
window.mermaid.initialize({
startOnLoad: false,
theme: theme,
themeVariables: {
fontFamily: "inherit",
fontSize: "16px",
// 强制应用主题变量
primaryColor: isDark ? "#ffffff" : "#000000",
primaryTextColor: isDark ? "#ffffff" : "#000000",
primaryBorderColor: isDark ? "#ffffff" : "#000000",
lineColor: isDark ? "#ffffff" : "#000000",
secondaryColor: isDark ? "#333333" : "#f0f0f0",
tertiaryColor: isDark ? "#555555" : "#e0e0e0",
},
securityLevel: "loose",
});
// 批量渲染所有图表
const renderPromises = Array.from(mermaidElements).map(
async (element, index) => {
try {
const code = element.getAttribute("data-mermaid-code");
if (code) {
// 清空容器
element.innerHTML = "";
// 渲染图表
const { svg } = await window.mermaid.render(
"mermaid-" + Math.random().toString(36).slice(-6),
code,
);
element.innerHTML = svg;
// 添加响应式支持
const svgElement = element.querySelector("svg");
if (svgElement) {
svgElement.setAttribute("width", "100%");
svgElement.removeAttribute("height");
svgElement.style.maxWidth = "100%";
svgElement.style.height = "auto";
// 强制应用样式
if (isDark) {
svgElement.style.filter = "brightness(0.9) contrast(1.1)";
} else {
svgElement.style.filter = "none";
}
}
}
} catch (error) {
console.error("Mermaid rendering error:", error);
element.innerHTML =
'<div class="mermaid-error">Failed to render diagram. Please check the syntax.</div>';
}
},
);
// 等待所有渲染完成
Promise.all(renderPromises)
.then(() => {
isRendering = false;
})
.catch((error) => {
isRendering = false;
console.error("Error rendering Mermaid diagrams:", error);
});
}, 100); // 延迟 100ms 确保 DOM 更新完成
}
// 初始化主题状态
function initializeThemeState() {
const isDark = document.documentElement.classList.contains("dark");
currentTheme = isDark ? "dark" : "default";
}
if (typeof window.mermaid === "undefined") {
// 动态加载 Mermaid 库 - 使用 CDN 链接
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js";
script.onload = () => {
initializeMermaid();
};
document.head.appendChild(script);
} else {
initializeMermaid();
}
// 设置监听器
setupMutationObserver();
setupEventListeners();
// 初始化主题状态
initializeThemeState();
// 初始渲染
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
renderMermaidDiagrams();
});
} else {
renderMermaidDiagrams();
}
})();
</script></div></section><section><h2 id="conclusion">Conclusion<a class="anchor" href="#conclusion"><span class="anchor-icon" data-pagefind-ignore="">#</span></a></h2><p>Mermaid is a powerful tool for creating various types of diagrams in Markdown documents. This article demonstrated how to use flowcharts, sequence diagrams, Gantt charts, class diagrams, state diagrams, and pie charts. These diagrams can help you express complex concepts, processes, and data structures more clearly.</p><p>To use Mermaid, simply specify the mermaid language in a code block and describe the diagram using concise text syntax. Mermaid will automatically convert these descriptions into beautiful visual diagrams.</p><p>Try using Mermaid diagrams in your next technical blog post or project documentation - they will make your content more professional and easier to understand!</p></section></section> </div> <script type="module">document.addEventListener("click",function(s){const t=s.target;if(t&&t.classList.contains("copy-btn")){const c=t.closest("pre")?.querySelector("code"),i=Array.from(c?.querySelectorAll(".code:not(summary *)")??[]).map(e=>e.textContent).map(e=>e===`
`?"":e).join(`
`);navigator.clipboard.writeText(i);const o=t.getAttribute("data-timeout-id");o&&clearTimeout(parseInt(o)),t.classList.add("success");const n=setTimeout(()=>{t.classList.remove("success")},1e3);t.setAttribute("data-timeout-id",n.toString())}});</script> <div class="relative transition overflow-hidden bg-[var(--license-block-bg)] py-5 px-6 mb-6 rounded-xl license-container onload-animation"> <div class="transition font-bold text-black/75 dark:text-white/75"> Markdown Mermaid </div> <a href="https://oss-runtime.dev/blog/posts/markdown-mermaid/" class="link text-[var(--primary)]"> https://oss-runtime.dev/blog/posts/markdown-mermaid/ </a> <div class="flex gap-6 mt-2"> <div> <div class="transition text-black/30 dark:text-white/30 text-sm">作者</div> <div class="transition text-black/75 dark:text-white/75 line-clamp-2">FutureOSS</div> </div> <div> <div class="transition text-black/30 dark:text-white/30 text-sm">发布于</div> <div class="transition text-black/75 dark:text-white/75 line-clamp-2">2023-10-01</div> </div> <div> <div class="transition text-black/30 dark:text-white/30 text-sm">许可协议</div> <a href="https://www.apache.org/licenses/LICENSE-2.0" target="_blank" class="link text-[var(--primary)] line-clamp-2">Apache 2.0</a> </div> </div> <svg width="0.97em" height="1em" class="transition text-[15rem] absolute pointer-events-none right-6 top-1/2 -translate-y-1/2 text-black/5 dark:text-white/5" data-icon="fa6-brands:creative-commons"> <symbol id="ai:fa6-brands:creative-commons" viewBox="0 0 496 512"><path fill="currentColor" d="m245.83 214.87l-33.22 17.28c-9.43-19.58-25.24-19.93-27.46-19.93c-22.13 0-33.22 14.61-33.22 43.84c0 23.57 9.21 43.84 33.22 43.84c14.47 0 24.65-7.09 30.57-21.26l30.55 15.5c-6.17 11.51-25.69 38.98-65.1 38.98c-22.6 0-73.96-10.32-73.96-77.05c0-58.69 43-77.06 72.63-77.06c30.72-.01 52.7 11.95 65.99 35.86m143.05 0l-32.78 17.28c-9.5-19.77-25.72-19.93-27.9-19.93c-22.14 0-33.22 14.61-33.22 43.84c0 23.55 9.23 43.84 33.22 43.84c14.45 0 24.65-7.09 30.54-21.26l31 15.5c-2.1 3.75-21.39 38.98-65.09 38.98c-22.69 0-73.96-9.87-73.96-77.05c0-58.67 42.97-77.06 72.63-77.06c30.71-.01 52.58 11.95 65.56 35.86M247.56 8.05C104.74 8.05 0 123.11 0 256.05c0 138.49 113.6 248 247.56 248c129.93 0 248.44-100.87 248.44-248c0-137.87-106.62-248-248.44-248m.87 450.81c-112.54 0-203.7-93.04-203.7-202.81c0-105.42 85.43-203.27 203.72-203.27c112.53 0 202.82 89.46 202.82 203.26c-.01 121.69-99.68 202.82-202.84 202.82"/></symbol><use href="#ai:fa6-brands:creative-commons"></use> </svg> </div> </div> </div> <div class="flex flex-col md:flex-row justify-between mb-4 gap-4 overflow-hidden w-full"> <a href="/blog/posts/markdown-tutorial/" class="w-full font-bold overflow-hidden active:scale-95"> <div class="btn-card rounded-2xl w-full h-[3.75rem] max-w-full px-4 flex items-center !justify-start gap-4"> <svg width="1em" height="1em" class="text-[2rem] text-[var(--primary)]" data-icon="material-symbols:chevron-left-rounded"> <symbol id="ai:material-symbols:chevron-left-rounded" viewBox="0 0 24 24"><path fill="currentColor" d="m10.8 12l3.9 3.9q.275.275.275.7t-.275.7t-.7.275t-.7-.275l-4.6-4.6q-.15-.15-.212-.325T8.425 12t.063-.375t.212-.325l4.6-4.6q.275-.275.7-.275t.7.275t.275.7t-.275.7z"/></symbol><use href="#ai:material-symbols:chevron-left-rounded"></use> </svg> <div class="overflow-hidden transition overflow-ellipsis whitespace-nowrap max-w-[calc(100%_-_3rem)] text-black/75 dark:text-white/75"> Markdown Tutorial </div> </div> </a> <a href="/blog/posts/markdown/" class="w-full font-bold overflow-hidden active:scale-95"> <div class="btn-card rounded-2xl w-full h-[3.75rem] max-w-full px-4 flex items-center !justify-end gap-4"> <div class="overflow-hidden transition overflow-ellipsis whitespace-nowrap max-w-[calc(100%_-_3rem)] text-black/75 dark:text-white/75"> Markdown Example </div> <svg width="1em" height="1em" viewBox="0 0 24 24" class="text-[2rem] text-[var(--primary)]" data-icon="material-symbols:chevron-right-rounded"> <use href="#ai:material-symbols:chevron-right-rounded"></use> </svg> </div> </a> </div> <div class="footer col-span-2 onload-animation hidden lg:block" data-astro-cid-haiuh7kc> <!--<div class="border-t border-[var(&#45;&#45;primary)] mx-16 border-dashed py-8 max-w-[var(&#45;&#45;page-width)] flex flex-col items-center justify-center px-6">--><div class="transition border-t border-black/10 dark:border-white/15 my-10 border-dashed mx-32"></div> <!--<div class="transition bg-[oklch(92%_0.01_var(&#45;&#45;hue))] dark:bg-black rounded-2xl py-8 mt-4 mb-8 flex flex-col items-center justify-center px-6">--> <div class="transition border-dashed border-[oklch(85%_0.01_var(--hue))] dark:border-white/15 rounded-2xl mb-12 flex flex-col items-center justify-center px-6"> <div class="transition text-50 text-sm text-center">
&copy; <span id="copyright-year">2026</span> FutureOSS. All Rights Reserved. /
<a class="transition link text-[var(--primary)] font-medium" target="_blank" href="/blog/rss.xml">RSS</a> /
<a class="transition link text-[var(--primary)] font-medium" target="_blank" href="/blog/sitemap-index.xml">Sitemap</a><br>
Powered by
<a class="transition link text-[var(--primary)] font-medium" target="_blank" href="https://astro.build">Astro</a> &
<a class="transition link text-[var(--primary)] font-medium" target="_blank" href="https://github.com/matsuzaka-yuki/mizuki">Mizuki</a>&nbsp; Version <a class="transition link text-[var(--primary)] font-medium" target="_blank" href="https://github.com/matsuzaka-yuki/mizuki">4.0<br> </a></div><a class="transition link text-[var(--primary)] font-medium" target="_blank" href="https://github.com/matsuzaka-yuki/mizuki"></a></div> </div> </div> </main> <div class="footer col-span-2 onload-animation block lg:hidden" data-astro-cid-haiuh7kc> <!--<div class="border-t border-[var(&#45;&#45;primary)] mx-16 border-dashed py-8 max-w-[var(&#45;&#45;page-width)] flex flex-col items-center justify-center px-6">--><div class="transition border-t border-black/10 dark:border-white/15 my-10 border-dashed mx-32"></div> <!--<div class="transition bg-[oklch(92%_0.01_var(&#45;&#45;hue))] dark:bg-black rounded-2xl py-8 mt-4 mb-8 flex flex-col items-center justify-center px-6">--> <div class="transition border-dashed border-[oklch(85%_0.01_var(--hue))] dark:border-white/15 rounded-2xl mb-12 flex flex-col items-center justify-center px-6"> <div class="transition text-50 text-sm text-center">
&copy; <span id="copyright-year">2026</span> FutureOSS. All Rights Reserved. /
<a class="transition link text-[var(--primary)] font-medium" target="_blank" href="/blog/rss.xml">RSS</a> /
<a class="transition link text-[var(--primary)] font-medium" target="_blank" href="/blog/sitemap-index.xml">Sitemap</a><br>
Powered by
<a class="transition link text-[var(--primary)] font-medium" target="_blank" href="https://astro.build">Astro</a> &
<a class="transition link text-[var(--primary)] font-medium" target="_blank" href="https://github.com/matsuzaka-yuki/mizuki">Mizuki</a>&nbsp; Version <a class="transition link text-[var(--primary)] font-medium" target="_blank" href="https://github.com/matsuzaka-yuki/mizuki">4.0<br> </a></div><a class="transition link text-[var(--primary)] font-medium" target="_blank" href="https://github.com/matsuzaka-yuki/mizuki"></a></div> </div> </div> <!-- There can't be a filter on parent element, or it will break `fixed` --><div class="back-to-top-wrapper hidden lg:block" data-astro-cid-eymb5ayk> <div id="back-to-top-btn" class="back-to-top-btn hide flex items-center rounded-2xl overflow-hidden transition" onclick="backToTop()" data-astro-cid-eymb5ayk> <button aria-label="Back to Top" class="btn-card h-[3.75rem] w-[3.75rem]" data-astro-cid-eymb5ayk> <svg width="1em" height="1em" class="mx-auto" data-astro-cid-eymb5ayk="true" data-icon="material-symbols:keyboard-arrow-up-rounded"> <symbol id="ai:material-symbols:keyboard-arrow-up-rounded" viewBox="0 0 24 24"><path fill="currentColor" d="m12 10.8l-3.9 3.9q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7l4.6-4.6q.3-.3.7-.3t.7.3l4.6 4.6q.275.275.275.7t-.275.7t-.7.275t-.7-.275z"/></symbol><use href="#ai:material-symbols:keyboard-arrow-up-rounded"></use> </svg> </button> </div> </div> <script>
function backToTop() {
// 直接使用原生滚动避免OverlayScrollbars冲突
window.scroll({ top: 0, behavior: 'smooth' });
}
</script> </div> </div> <div class="absolute w-full z-0 hidden xl:block" data-astro-cid-haiuh7kc> <div class="relative max-w-[var(--page-width)] mx-auto" data-astro-cid-haiuh7kc> <!-- TOC component --> <div id="toc-wrapper" class="hidden lg:block transition absolute top-0 w-[var(--toc-width)] items-center -right-[var(--toc-width)] toc-hide" data-astro-cid-haiuh7kc> <div id="toc-inner-wrapper" class="fixed top-14 w-[var(--toc-width)] h-[calc(100vh_-_20rem)] overflow-y-scroll overflow-x-hidden hide-scrollbar" data-astro-cid-haiuh7kc> <div id="toc" class="w-full h-full transition-swup-fade " data-astro-cid-haiuh7kc> <div class="h-8 w-full" data-astro-cid-haiuh7kc></div> <table-of-contents id="toc" class="group"> <!-- TOC内容将由JavaScript动态生成 --> </table-of-contents> <script type="module" src="/blog/_astro/TOC.astro_astro_type_script_index_0_lang.CGKGUlSz.js"></script> <div class="h-8 w-full" data-astro-cid-haiuh7kc></div> </div> </div> </div> <!-- #toc needs to exist for Swup to work normally --> </div> </div> <!-- Music Player --> <script>(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).load=e;window.dispatchEvent(new Event("astro:load"));})();</script><astro-island uid="Z1HtLzS" component-url="/blog/_astro/MusicPlayer.Ckr8jCXu.js" component-export="default" renderer-url="/blog/_astro/client.svelte.9vP7DkWT.js" props="{&quot;data-astro-cid-sckkx6r4&quot;:[0,true]}" ssr client="load" opts="{&quot;name&quot;:&quot;MusicPlayer&quot;,&quot;value&quot;:true}" await-children><!--[--><!--[-1--><!--]--><!--]--><!--astro:end--></astro-island> <!-- increase the page height during page transition to prevent the scrolling animation from jumping --> <div id="page-height-extend" class="hidden h-[300vh]" data-astro-cid-sckkx6r4 style="--bannerOffset: 15vh;--banner-height-home: 65vh;--banner-height: 35vh;--configHue: 210;--page-width: 90rem;"></div> <!-- Sakura Effect --> <script>(function(){const sakuraConfig = {"enable":true,"sakuraNum":21,"limitTimes":-1,"size":{"min":0.5,"max":1.1},"speed":{"horizontal":{"min":-1.7,"max":-1.2},"vertical":{"min":1.5,"max":2.2},"rotation":0.03},"zIndex":100};
// 樱花对象类
class Sakura {
constructor(x, y, s, r, fn, idx, img, limitArray, config) {
this.x = x;
this.y = y;
this.s = s;
this.r = r;
this.fn = fn;
this.idx = idx;
this.img = img;
this.limitArray = limitArray;
this.config = config;
}
draw(cxt) {
cxt.save();
cxt.translate(this.x, this.y);
cxt.rotate(this.r);
cxt.drawImage(this.img, 0, 0, 40 * this.s, 40 * this.s);
cxt.restore();
}
update() {
this.x = this.fn.x(this.x, this.y);
this.y = this.fn.y(this.y, this.y);
this.r = this.fn.r(this.r);
// 如果樱花越界,重新调整位置
if (
this.x > window.innerWidth ||
this.x < 0 ||
this.y > window.innerHeight ||
this.y < 0
) {
// 如果樱花不做限制
if (this.limitArray[this.idx] === -1) {
this.resetPosition();
}
// 否则樱花有限制
else {
if (this.limitArray[this.idx] > 0) {
this.resetPosition();
this.limitArray[this.idx]--;
}
}
}
}
resetPosition() {
this.r = getRandom('fnr', this.config);
if (Math.random() > 0.4) {
this.x = getRandom('x', this.config);
this.y = 0;
this.s = getRandom('s', this.config);
this.r = getRandom('r', this.config);
} else {
this.x = window.innerWidth;
this.y = getRandom('y', this.config);
this.s = getRandom('s', this.config);
this.r = getRandom('r', this.config);
}
}
}
// 樱花列表类
class SakuraList {
constructor() {
this.list = [];
}
push(sakura) {
this.list.push(sakura);
}
update() {
for (let i = 0, len = this.list.length; i < len; i++) {
this.list[i].update();
}
}
draw(cxt) {
for (let i = 0, len = this.list.length; i < len; i++) {
this.list[i].draw(cxt);
}
}
get(i) {
return this.list[i];
}
size() {
return this.list.length;
}
}
// 获取随机值的函数
function getRandom(option, config) {
let ret;
let random;
switch (option) {
case 'x':
ret = Math.random() * window.innerWidth;
break;
case 'y':
ret = Math.random() * window.innerHeight;
break;
case 's':
ret = config.size.min + Math.random() * (config.size.max - config.size.min);
break;
case 'r':
ret = Math.random() * 6;
break;
case 'fnx':
random = config.speed.horizontal.min + Math.random() * (config.speed.horizontal.max - config.speed.horizontal.min);
ret = function (x, y) {
return x + random;
};
break;
case 'fny':
random = config.speed.vertical.min + Math.random() * (config.speed.vertical.max - config.speed.vertical.min);
ret = function (x, y) {
return y + random;
};
break;
case 'fnr':
ret = function (r) {
return r + config.speed.rotation;
};
break;
}
return ret;
}
// 樱花管理器类
class SakuraManager {
constructor(config) {
this.config = config;
this.canvas = null;
this.ctx = null;
this.sakuraList = null;
this.animationId = null;
this.img = null;
this.isRunning = false;
}
// 初始化樱花特效
async init() {
if (!this.config.enable || this.isRunning) {
return;
}
// 创建图片对象
this.img = new Image();
this.img.src = '/sakura.png'; // 使用樱花图片
// 等待图片加载完成
await new Promise((resolve, reject) => {
if (this.img) {
this.img.onload = () => resolve();
this.img.onerror = () => reject(new Error('Failed to load sakura image'));
}
});
this.createCanvas();
this.createSakuraList();
this.startAnimation();
this.isRunning = true;
}
// 创建画布
createCanvas() {
this.canvas = document.createElement('canvas');
this.canvas.height = window.innerHeight;
this.canvas.width = window.innerWidth;
this.canvas.setAttribute('style', `position: fixed; left: 0; top: 0; pointer-events: none; z-index: ${this.config.zIndex};`);
this.canvas.setAttribute('id', 'canvas_sakura');
document.body.appendChild(this.canvas);
this.ctx = this.canvas.getContext('2d');
// 监听窗口大小变化
window.addEventListener('resize', this.handleResize.bind(this));
}
// 创建樱花列表
createSakuraList() {
if (!this.img || !this.ctx) return;
this.sakuraList = new SakuraList();
const limitArray = new Array(this.config.sakuraNum).fill(this.config.limitTimes);
for (let i = 0; i < this.config.sakuraNum; i++) {
const randomX = getRandom('x', this.config);
const randomY = getRandom('y', this.config);
const randomR = getRandom('r', this.config);
const randomS = getRandom('s', this.config);
const randomFnx = getRandom('fnx', this.config);
const randomFny = getRandom('fny', this.config);
const randomFnR = getRandom('fnr', this.config);
const sakura = new Sakura(
randomX,
randomY,
randomS,
randomR,
{
x: randomFnx,
y: randomFny,
r: randomFnR,
},
i,
this.img,
limitArray,
this.config
);
sakura.draw(this.ctx);
this.sakuraList.push(sakura);
}
}
// 开始动画
startAnimation() {
if (!this.ctx || !this.canvas || !this.sakuraList) return;
const animate = () => {
if (!this.ctx || !this.canvas || !this.sakuraList) return;
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.sakuraList.update();
this.sakuraList.draw(this.ctx);
this.animationId = requestAnimationFrame(animate);
};
this.animationId = requestAnimationFrame(animate);
}
// 处理窗口大小变化
handleResize() {
if (this.canvas) {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
}
}
// 停止樱花特效
stop() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
if (this.canvas) {
document.body.removeChild(this.canvas);
this.canvas = null;
}
window.removeEventListener('resize', this.handleResize.bind(this));
this.isRunning = false;
}
// 切换樱花特效
toggle() {
if (this.isRunning) {
this.stop();
} else {
this.init();
}
}
// 更新配置
updateConfig(newConfig) {
const wasRunning = this.isRunning;
if (wasRunning) {
this.stop();
}
this.config = newConfig;
if (wasRunning && newConfig.enable) {
this.init();
}
}
// 获取运行状态
getIsRunning() {
return this.isRunning;
}
}
// 创建全局樱花管理器实例
let globalSakuraManager = null;
// 初始化樱花特效
function initSakura(config) {
if (globalSakuraManager) {
globalSakuraManager.updateConfig(config);
} else {
globalSakuraManager = new SakuraManager(config);
if (config.enable) {
globalSakuraManager.init();
}
}
}
// 樱花特效初始化
(function() {
// 全局标记,确保樱花特效只初始化一次
if (window.sakuraInitialized) {
return;
}
// 初始化樱花特效的函数
const setupSakura = () => {
if (sakuraConfig.enable && !window.sakuraInitialized) {
initSakura(sakuraConfig);
window.sakuraInitialized = true;
}
};
// 页面加载完成后初始化樱花特效
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupSakura);
} else {
setupSakura();
}
})();
})();</script> <!-- Translate.js integration - lazy loading --> <script type="module">class d{constructor(){this.isInitialized=!1,this.clickHandler=null}init(){console.log("GalleryManager init called");const t=document.querySelectorAll(".gallery-group");if(console.log("Found gallery groups:",t.length),t.length===0){console.log("No gallery groups found, skipping initialization");return}this.cleanup(),console.log("Adding click event listener to document"),this.clickHandler=e=>this.handleClick(e),document.addEventListener("click",this.clickHandler),this.isInitialized=!0}cleanup(){this.clickHandler&&(document.removeEventListener("click",this.clickHandler),this.clickHandler=null),this.isInitialized=!1}handleClick(t){console.log("GalleryManager handleClick triggered",t.target);const e=t.target.closest(".gallery-header");if(e){console.log("Gallery header clicked",e),t.preventDefault(),t.stopPropagation(),this.toggleGallery(e);return}const n=t.target.closest(".gallery-image");if(n){console.log("Gallery image clicked",n),t.preventDefault(),t.stopPropagation();const l=n.getAttribute("data-image");l&&this.showImageModal(l)}}toggleGallery(t){console.log("toggleGallery called",t);const e=t.closest(".gallery-group"),n=e.querySelector(".gallery-content"),l=t.querySelector(".expand-icon");console.log("Found elements:",{group:e,content:n,icon:l}),console.log("Content hidden?",n?.classList.contains("hidden")),n&&n.classList.contains("hidden")?(console.log("Expanding gallery"),n.classList.remove("hidden"),e.classList.add("expanded"),l&&(l.style.transform="rotate(180deg)")):n?(console.log("Collapsing gallery"),n.classList.add("hidden"),e.classList.remove("expanded"),l&&(l.style.transform="rotate(0deg)")):console.error("Gallery content not found!")}showImageModal(t){const e=document.createElement("div");e.className="gallery-modal fixed inset-0 bg-black bg-opacity-90 flex items-center justify-center z-50 p-4",e.style.opacity="0",e.style.transition="opacity 0.3s ease";const n=document.createElement("div");n.className="relative max-w-full max-h-full";const l=document.createElement("img");l.src=t,l.alt="查看图片",l.className="max-w-full max-h-full object-contain rounded-lg",l.style.transform="scale(0.9)",l.style.transition="transform 0.3s ease";const o=document.createElement("button");o.className="close-btn absolute top-4 right-4 bg-black bg-opacity-50 text-white p-2 rounded-full hover:bg-opacity-70 transition-colors",o.innerHTML=`
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
`,n.appendChild(l),n.appendChild(o),e.appendChild(n),document.body.appendChild(e),requestAnimationFrame(()=>{e.style.opacity="1",l.style.transform="scale(1)"});const a=()=>{e.style.opacity="0",l.style.transform="scale(0.9)",setTimeout(()=>{document.body.contains(e)&&document.body.removeChild(e),document.removeEventListener("keydown",c)},300)},c=s=>{s.key==="Escape"&&a()};o.addEventListener("click",a),e.addEventListener("click",s=>{s.target===e&&a()}),document.addEventListener("keydown",c)}}window.galleryManager=new d;const r=()=>{window.galleryManager.init()},i=()=>{r(),window.swup&&(window.swup.hooks.on("page:view",()=>{setTimeout(r,50)}),window.swup.hooks.on("content:replace",()=>{window.galleryManager.cleanup()},{before:!0}))};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",i):i();window.swup||document.addEventListener("swup:enable",i);</script> </body> </html> <script type="module" src="/blog/_astro/Layout.astro_astro_type_script_index_1_lang.BilWV2C2.js"></script> <script type="module" src="/blog/_astro/Layout.astro_astro_type_script_index_2_lang.BTdkr2U1.js"></script> <script type="module">window.loadTranslateScript=function(){return window.translate||document.getElementById("translate-script")?Promise.resolve():new Promise((n,t)=>{const e=document.createElement("script");e.src="/translate.js",e.id="translate-script",e.async=!0,e.onload=()=>{typeof window.translate<"u"?n():t(new Error("translate.js loaded but window.translate not available"))},e.onerror=t,document.head.appendChild(e)})};</script>