Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a0d70ebbd | ||
|
|
470295171e | ||
|
|
2058c1ff6d | ||
|
|
d6448148cf | ||
|
|
475c956fd6 | ||
|
|
5e57b5ad29 | ||
|
|
b4e44fda57 | ||
|
|
3806cc9230 | ||
|
|
0cefe05973 | ||
|
|
bc1b55e60a | ||
|
|
99a8fd9013 | ||
|
|
7f47cdf165 | ||
|
|
768e7de697 | ||
|
|
00069b2c45 | ||
|
|
31022581d8 | ||
|
|
ff2e308445 | ||
|
|
017fbf982d | ||
|
|
bc54cecf0a | ||
|
|
deceb17144 | ||
|
|
dc0d105e7c | ||
|
|
33b7de9457 | ||
|
|
7d4bfa9fc0 |
@@ -12,11 +12,12 @@ Use this template: [CaiJimmy/hugo-theme-stack-starter](https://github.com/CaiJim
|
||||
|
||||
## Demo
|
||||
|
||||
Check [demo.stack.jimmycai.com](https://demo.stack.jimmycai.com)
|
||||
Check [demo.stack.cai.im](https://demo.stack.cai.im)
|
||||
|
||||
## Documentation
|
||||
|
||||
Visit [stack.jimmycai.com](https://stack.jimmycai.com)
|
||||
- [English](https://stack.cai.im)
|
||||
- [中文](https://stack.cai.im/zh)
|
||||
|
||||
## Copyright
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ date: {{ .Date }}
|
||||
image:
|
||||
math:
|
||||
license:
|
||||
hidden: false
|
||||
comments: true
|
||||
draft: true
|
||||
build:
|
||||
list: always # Change to "never" to hide the page from the list
|
||||
---
|
||||
@@ -4,11 +4,13 @@
|
||||
|
||||
.left-sidebar {
|
||||
order: -3;
|
||||
width: 100%;
|
||||
max-width: var(--left-sidebar-max-width);
|
||||
}
|
||||
|
||||
.right-sidebar {
|
||||
order: -1;
|
||||
width: 100%;
|
||||
max-width: var(--right-sidebar-max-width);
|
||||
|
||||
/// Display right sidebar when min-width: lg
|
||||
|
||||
@@ -266,3 +266,167 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mermaid:not([data-processed]) {
|
||||
background: var(--card-background);
|
||||
min-height: 150px;
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
font-size: 0 !important;
|
||||
line-height: 0;
|
||||
color: transparent;
|
||||
|
||||
&::after {
|
||||
visibility: visible;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin: -14px 0 0 -14px;
|
||||
border: 3px solid var(--card-background);
|
||||
border-top-color: var(--accent-color, #3273dc);
|
||||
border-radius: 50%;
|
||||
animation: mermaid-spinner 0.8s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes mermaid-spinner {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
// Mermaid diagram wrapper with expand toolbar
|
||||
.mermaid-wrapper {
|
||||
position: relative;
|
||||
margin: 1rem 0;
|
||||
|
||||
.mermaid {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mermaid-toolbar {
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
right: 0.75rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
z-index: 10;
|
||||
|
||||
button {
|
||||
padding: 0.5rem 1rem;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border: 2px solid #333;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mermaid fullscreen modal
|
||||
.mermaid-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.95);
|
||||
z-index: 9999;
|
||||
|
||||
&.active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.mermaid-modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.mermaid-modal-controls {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.mermaid-modal-controls button,
|
||||
.mermaid-modal-close {
|
||||
padding: 0.5rem 1rem;
|
||||
background: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
|
||||
&:hover {
|
||||
background: #ddd;
|
||||
}
|
||||
}
|
||||
|
||||
.mermaid-modal-body {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mermaid-modal-content {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
bottom: 1rem;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
[data-scheme="dark"] & {
|
||||
background: #1e1e1e;
|
||||
}
|
||||
}
|
||||
|
||||
.mermaid-panzoom-container {
|
||||
display: inline-block;
|
||||
transform-origin: 0 0;
|
||||
visibility: hidden;
|
||||
|
||||
&.ready {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
// Off-screen container for pre-rendering alternate theme
|
||||
.mermaid-offscreen {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
visibility: hidden;
|
||||
width: 800px;
|
||||
}
|
||||
|
||||
@@ -329,6 +329,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
blockquote.alert {
|
||||
.alert-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.alert-body {
|
||||
> :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
$alert-types: note, tip, important, warning, caution;
|
||||
|
||||
@each $type in $alert-types {
|
||||
&.alert-#{$type} {
|
||||
border-color: var(--alert-#{$type}-color);
|
||||
background-color: var(--alert-#{$type}-background);
|
||||
|
||||
.alert-title {
|
||||
color: var(--alert-#{$type}-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
width: 100px;
|
||||
margin: 40px auto;
|
||||
|
||||
@@ -105,6 +105,17 @@
|
||||
|
||||
--heading-border-size: 4px;
|
||||
|
||||
--alert-note-color: #4da3ff;
|
||||
--alert-note-background: rgba(77, 163, 255, 0.08);
|
||||
--alert-tip-color: #3fb950;
|
||||
--alert-tip-background: rgba(63, 185, 80, 0.08);
|
||||
--alert-important-color: #8457d9;
|
||||
--alert-important-background: rgba(130, 80, 223, 0.08);
|
||||
--alert-warning-color: #d29922;
|
||||
--alert-warning-background: rgba(210, 153, 34, 0.1);
|
||||
--alert-caution-color: #f85149;
|
||||
--alert-caution-background: rgba(248, 81, 73, 0.1);
|
||||
|
||||
--link-background-color: 189, 195, 199;
|
||||
--link-background-opacity: 0.5;
|
||||
--link-background-opacity-hover: 0.7;
|
||||
|
||||
+15
-2
@@ -12,6 +12,15 @@ const wrap = (figures: HTMLElement[]) => {
|
||||
}
|
||||
}
|
||||
|
||||
const unescapeHtml = (html: string) => {
|
||||
/// Replace special HTML entities with their corresponding characters
|
||||
return html.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
export default (container: HTMLElement) => {
|
||||
/// The process of wrapping image with figure tag is done using JavaScript instead of only Hugo markdown render hook
|
||||
/// because it can not detect whether image is being wrapped by a link or not
|
||||
@@ -64,9 +73,13 @@ export default (container: HTMLElement) => {
|
||||
figure.appendChild(el);
|
||||
|
||||
/// Add figcaption if it exists
|
||||
if (img.hasAttribute('alt')) {
|
||||
let caption = img.getAttribute('alt');
|
||||
if (img.getAttribute('data-title-escaped')) {
|
||||
caption = unescapeHtml(img.getAttribute('data-title-escaped')!);
|
||||
}
|
||||
if (caption) {
|
||||
const figcaption = document.createElement('figcaption');
|
||||
figcaption.innerText = img.getAttribute('alt')!;
|
||||
figcaption.innerHTML = caption;
|
||||
figure.appendChild(figcaption);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
declare const mermaid: {
|
||||
initialize(config: Record<string, any>): void;
|
||||
run(options: { nodes: HTMLElement[] }): Promise<void>;
|
||||
};
|
||||
|
||||
interface MermaidConfig {
|
||||
transparentBackground?: boolean;
|
||||
lightTheme?: string;
|
||||
darkTheme?: string;
|
||||
lightThemeVariables?: Record<string, any>;
|
||||
darkThemeVariables?: Record<string, any>;
|
||||
securityLevel?: string;
|
||||
look?: string;
|
||||
htmlLabels?: boolean;
|
||||
maxTextSize?: number;
|
||||
maxEdges?: number;
|
||||
fontSize?: number;
|
||||
fontFamily?: string;
|
||||
curve?: string;
|
||||
logLevel?: number;
|
||||
}
|
||||
|
||||
type Scheme = 'light' | 'dark';
|
||||
|
||||
const PANZOOM_CDN = 'https://cdn.jsdelivr.net/npm/panzoom@9.4.3/+esm';
|
||||
|
||||
function getScheme(): Scheme {
|
||||
return document.documentElement.dataset.scheme === 'dark' ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
function buildThemeConfig(cfg: MermaidConfig, scheme: Scheme) {
|
||||
const isLight = scheme === 'light';
|
||||
const theme = isLight ? (cfg.lightTheme ?? 'default') : (cfg.darkTheme ?? 'dark');
|
||||
const vars = isLight ? (cfg.lightThemeVariables ?? {}) : (cfg.darkThemeVariables ?? {});
|
||||
return {
|
||||
theme,
|
||||
themeVariables: { ...vars, ...(cfg.transparentBackground ? { background: 'transparent' } : {}) },
|
||||
};
|
||||
}
|
||||
|
||||
function buildBaseConfig(cfg: MermaidConfig): Record<string, any> {
|
||||
const base: Record<string, any> = {
|
||||
startOnLoad: false,
|
||||
securityLevel: cfg.securityLevel ?? 'strict',
|
||||
look: cfg.look ?? 'classic',
|
||||
flowchart: { htmlLabels: cfg.htmlLabels ?? true, useMaxWidth: true },
|
||||
gantt: { useWidth: 800 },
|
||||
};
|
||||
const optional: (keyof MermaidConfig)[] = ['maxTextSize', 'maxEdges', 'fontSize', 'fontFamily', 'curve', 'logLevel'];
|
||||
for (const key of optional) {
|
||||
if (cfg[key] != null) base[key] = cfg[key];
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
function initWithTheme(
|
||||
scheme: Scheme,
|
||||
themes: Record<Scheme, ReturnType<typeof buildThemeConfig>>,
|
||||
baseConfig: Record<string, any>,
|
||||
) {
|
||||
const { theme, themeVariables } = themes[scheme];
|
||||
mermaid.initialize({
|
||||
...baseConfig,
|
||||
theme,
|
||||
...(Object.keys(themeVariables).length && { themeVariables }),
|
||||
});
|
||||
}
|
||||
|
||||
async function renderOffscreen(sources: string[]): Promise<string[]> {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'mermaid-offscreen';
|
||||
document.body.appendChild(container);
|
||||
const nodes = sources.map(src => {
|
||||
const n = document.createElement('pre');
|
||||
n.innerHTML = src;
|
||||
container.appendChild(n);
|
||||
return n;
|
||||
});
|
||||
await mermaid.run({ nodes });
|
||||
const results = nodes.map(n => n.innerHTML);
|
||||
container.remove();
|
||||
return results;
|
||||
}
|
||||
|
||||
function setupWrappers(elements: NodeListOf<HTMLElement>) {
|
||||
elements.forEach((el, idx) => {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'mermaid-wrapper';
|
||||
el.parentNode!.insertBefore(wrapper, el);
|
||||
wrapper.appendChild(el);
|
||||
wrapper.insertAdjacentHTML(
|
||||
'beforeend',
|
||||
`<div class="mermaid-toolbar"><button data-idx="${idx}" title="Open fullscreen with pan/zoom">⛶ Expand</button></div>`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function setupModal(elements: NodeListOf<HTMLElement>) {
|
||||
const modal = document.getElementById('mermaid-modal')!;
|
||||
const modalBody = document.getElementById('mermaid-modal-body')!;
|
||||
const modalContent = document.getElementById('mermaid-modal-content')!;
|
||||
let pzInstance: any = null;
|
||||
let panzoom: any = null;
|
||||
|
||||
const loadPanzoom = async () => {
|
||||
if (!panzoom) {
|
||||
const url = PANZOOM_CDN;
|
||||
panzoom = (await import(url)).default;
|
||||
}
|
||||
return panzoom;
|
||||
};
|
||||
|
||||
const fitToScreen = () => {
|
||||
const wrapper = modalContent.querySelector('.mermaid-panzoom-container') as HTMLElement | null;
|
||||
if (!pzInstance || !wrapper) return;
|
||||
const w = +(wrapper.dataset.nativeWidth ?? 0);
|
||||
const h = +(wrapper.dataset.nativeHeight ?? 0);
|
||||
const rect = modalContent.getBoundingClientRect();
|
||||
const scale = Math.min((rect.width - 60) / w, (rect.height - 60) / h);
|
||||
pzInstance.zoomAbs(0, 0, scale);
|
||||
pzInstance.moveTo((rect.width - w * scale) / 2, (rect.height - h * scale) / 2);
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
modal.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
pzInstance?.dispose();
|
||||
pzInstance = null;
|
||||
modalContent.innerHTML = '';
|
||||
};
|
||||
|
||||
const openModal = async (idx: number) => {
|
||||
const svg = elements[idx].querySelector('svg');
|
||||
if (!svg) return;
|
||||
|
||||
const svgClone = svg.cloneNode(true) as SVGElement;
|
||||
const viewBox = svg.getAttribute('viewBox');
|
||||
const [w, h] = viewBox
|
||||
? viewBox.split(/[\s,]+/).slice(2).map(Number)
|
||||
: [svg.getBoundingClientRect().width || 800, svg.getBoundingClientRect().height || 600];
|
||||
svgClone.setAttribute('width', String(w));
|
||||
svgClone.setAttribute('height', String(h));
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'mermaid-panzoom-container';
|
||||
wrapper.dataset.nativeWidth = String(w);
|
||||
wrapper.dataset.nativeHeight = String(h);
|
||||
wrapper.appendChild(svgClone);
|
||||
|
||||
modalContent.innerHTML = '';
|
||||
modalContent.appendChild(wrapper);
|
||||
modal.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
const pz = await loadPanzoom();
|
||||
setTimeout(() => {
|
||||
pzInstance = pz(wrapper, { maxZoom: 10, minZoom: 0.05, bounds: false });
|
||||
fitToScreen();
|
||||
wrapper.classList.add('ready');
|
||||
}, 50);
|
||||
};
|
||||
|
||||
// Event delegation
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const toolbarBtn = target.closest('.mermaid-toolbar button') as HTMLElement | null;
|
||||
if (toolbarBtn) return openModal(+(toolbarBtn.dataset.idx!));
|
||||
|
||||
const zoomBtn = target.closest('.mermaid-modal-controls button') as HTMLElement | null;
|
||||
if (zoomBtn && pzInstance) {
|
||||
const z = zoomBtn.dataset.zoom;
|
||||
const rect = modalBody.getBoundingClientRect();
|
||||
if (z === 'fit') fitToScreen();
|
||||
else if (z === '0') { pzInstance.moveTo(0, 0); pzInstance.zoomAbs(0, 0, 1); }
|
||||
else pzInstance.smoothZoom(rect.width / 2, rect.height / 2, z === '1' ? 1.5 : 0.67);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('mermaid-modal-close')!.addEventListener('click', closeModal);
|
||||
modalBody.addEventListener('click', (e) => { if (e.target === modalBody) closeModal(); });
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && modal.classList.contains('active')) closeModal();
|
||||
});
|
||||
}
|
||||
|
||||
export async function initMermaidPage(config: MermaidConfig) {
|
||||
const elements = document.querySelectorAll('.mermaid') as NodeListOf<HTMLElement>;
|
||||
if (!elements.length) return;
|
||||
|
||||
const sources = Array.from(elements).map(el => el.innerHTML);
|
||||
const perDiagramTransparent = sources.map(src => /%%\s*transparent\s*%%/i.test(src));
|
||||
const cache: Record<Scheme, string[]> = { light: [], dark: [] };
|
||||
|
||||
const themes = {
|
||||
light: buildThemeConfig(config, 'light'),
|
||||
dark: buildThemeConfig(config, 'dark'),
|
||||
};
|
||||
const baseConfig = buildBaseConfig(config);
|
||||
|
||||
const applyTransparency = (el: HTMLElement, i: number) => {
|
||||
if (perDiagramTransparent[i]) el.querySelector('svg')?.style.setProperty('background', 'transparent');
|
||||
};
|
||||
|
||||
setupWrappers(elements);
|
||||
setupModal(elements);
|
||||
|
||||
// Initial render
|
||||
const scheme = getScheme();
|
||||
initWithTheme(scheme, themes, baseConfig);
|
||||
await mermaid.run({ nodes: Array.from(elements) });
|
||||
elements.forEach((el, i) => {
|
||||
el.style.visibility = '';
|
||||
cache[scheme][i] = el.innerHTML;
|
||||
applyTransparency(el, i);
|
||||
});
|
||||
|
||||
// Pre-render alternate theme during idle time
|
||||
const alt: Scheme = scheme === 'dark' ? 'light' : 'dark';
|
||||
const idle = window.requestIdleCallback ?? ((fn: IdleRequestCallback) => setTimeout(fn, 1000));
|
||||
idle(() => {
|
||||
if (cache[alt].length) return;
|
||||
initWithTheme(alt, themes, baseConfig);
|
||||
renderOffscreen(sources).then(results => { cache[alt] = results; });
|
||||
});
|
||||
|
||||
// Swap cached diagrams on theme change
|
||||
window.addEventListener('onColorSchemeChange', async () => {
|
||||
const newScheme = getScheme();
|
||||
if (!cache[newScheme].length) {
|
||||
initWithTheme(newScheme, themes, baseConfig);
|
||||
cache[newScheme] = await renderOffscreen(sources);
|
||||
}
|
||||
elements.forEach((el, i) => { el.innerHTML = cache[newScheme][i]; applyTransparency(el, i); });
|
||||
});
|
||||
}
|
||||
+97
-59
@@ -1,6 +1,10 @@
|
||||
mainSections = ["post"]
|
||||
featuredImageField = "image"
|
||||
rssFullContent = true
|
||||
# Accepted values: "default", "lastmod"
|
||||
# default = see https://gohugo.io/quick-reference/glossary/#default-sort-order
|
||||
# lastmod = sort by last modified date, in descending order
|
||||
SortBy = "default"
|
||||
|
||||
[footer]
|
||||
since = 2026
|
||||
@@ -12,11 +16,7 @@ rssFullContent = true
|
||||
|
||||
[sidebar]
|
||||
compact = false
|
||||
|
||||
[sidebar.avatar]
|
||||
enabled = false
|
||||
local = true
|
||||
src = "img/avatar.png"
|
||||
avatar = ""
|
||||
|
||||
[article]
|
||||
headingAnchor = false
|
||||
@@ -28,6 +28,37 @@ rssFullContent = true
|
||||
enabled = false
|
||||
default = "Licensed under CC BY-NC-SA 4.0"
|
||||
|
||||
# Mermaid diagram configuration
|
||||
# Diagrams are only loaded when mermaid code blocks are present
|
||||
# Theme auto-switches based on site light/dark mode
|
||||
[article.mermaid]
|
||||
# Visual style: classic or handDrawn (sketch style)
|
||||
look = "classic"
|
||||
# Theme for light mode: default, neutral, dark, forest, base, null
|
||||
lightTheme = "default"
|
||||
# Theme for dark mode: default, neutral, dark, forest, base, null
|
||||
darkTheme = "dark"
|
||||
# Custom theme variables for light mode (only works with lightTheme: base)
|
||||
# lightThemeVariables = { primaryColor = "#ff0000" }
|
||||
# Custom theme variables for dark mode (only works with darkTheme: base)
|
||||
# darkThemeVariables = { primaryColor = "#00ff00" }
|
||||
# Security level: strict (default), loose, antiscript, sandbox
|
||||
# Set to "loose" to enable HTML labels like <br/>
|
||||
securityLevel = "strict"
|
||||
# Enable HTML labels in diagrams (requires securityLevel: loose)
|
||||
htmlLabels = true
|
||||
# Make diagram backgrounds transparent (default: false)
|
||||
# Can also be set per-diagram with %%transparent%% directive
|
||||
transparentBackground = false
|
||||
|
||||
[article.alertIcon]
|
||||
# Alert icons for different alert types (note, tip, important, warning, caution)
|
||||
note = "📝"
|
||||
tip = "💡"
|
||||
important = "📌"
|
||||
warning = "⚠️"
|
||||
caution = "🚨"
|
||||
|
||||
[widgets]
|
||||
homepage = []
|
||||
page = []
|
||||
@@ -47,94 +78,101 @@ rssFullContent = true
|
||||
enabled = true
|
||||
|
||||
[comments]
|
||||
enabled = true
|
||||
enabled = true
|
||||
provider = "disqus"
|
||||
|
||||
[comments.disqusjs]
|
||||
shortname = ""
|
||||
apiUrl = ""
|
||||
apiKey = ""
|
||||
admin = ""
|
||||
shortname = ""
|
||||
apiUrl = ""
|
||||
apiKey = ""
|
||||
admin = ""
|
||||
adminLabel = ""
|
||||
|
||||
[comments.utterances]
|
||||
repo = ""
|
||||
repo = ""
|
||||
issueTerm = "pathname"
|
||||
label = ""
|
||||
label = ""
|
||||
|
||||
[comments.beaudar]
|
||||
repo = ""
|
||||
repo = ""
|
||||
issueTerm = "pathname"
|
||||
label = ""
|
||||
theme = ""
|
||||
label = ""
|
||||
theme = ""
|
||||
|
||||
[comments.remark42]
|
||||
host = ""
|
||||
site = ""
|
||||
locale = "en"
|
||||
max_shown_comments = 15
|
||||
host = ""
|
||||
site = ""
|
||||
locale = "en"
|
||||
max_shown_comments = 15
|
||||
show_email_subscription = true
|
||||
|
||||
[comments.vssue]
|
||||
platform = ""
|
||||
owner = ""
|
||||
repo = ""
|
||||
clientId = ""
|
||||
clientSecret = ""
|
||||
platform = ""
|
||||
owner = ""
|
||||
repo = ""
|
||||
clientId = ""
|
||||
clientSecret = ""
|
||||
autoCreateIssue = false
|
||||
|
||||
# Waline client configuration see: https://waline.js.org/en/reference/component.html
|
||||
[comments.waline]
|
||||
serverURL = ""
|
||||
lang = ""
|
||||
pageview = ""
|
||||
emoji = [
|
||||
"https://unpkg.com/@waline/emojis@1.0.1/weibo"
|
||||
]
|
||||
requiredMeta = [
|
||||
"name",
|
||||
"email",
|
||||
"url"
|
||||
]
|
||||
serverURL = ""
|
||||
lang = ""
|
||||
pageview = ""
|
||||
emoji = ["https://unpkg.com/@waline/emojis@1.0.1/weibo"]
|
||||
requiredMeta = ["name", "email", "url"]
|
||||
|
||||
[comments.waline.locale]
|
||||
admin = "Admin"
|
||||
admin = "Admin"
|
||||
placeholder = ""
|
||||
|
||||
[comments.twikoo]
|
||||
envId = ""
|
||||
envId = ""
|
||||
region = ""
|
||||
path = ""
|
||||
lang = ""
|
||||
path = ""
|
||||
lang = ""
|
||||
|
||||
# See https://cactus.chat/docs/reference/web-client/#configuration for description of the various options
|
||||
[comments.cactus]
|
||||
defaultHomeserverUrl = "https://matrix.cactus.chat:8448"
|
||||
serverName = "cactus.chat"
|
||||
siteName = "" # You must insert a unique identifier here matching the one you registered (See https://cactus.chat/docs/getting-started/quick-start/#register-your-site)
|
||||
serverName = "cactus.chat"
|
||||
siteName = "" # You must insert a unique identifier here matching the one you registered (See https://cactus.chat/docs/getting-started/quick-start/#register-your-site)
|
||||
|
||||
[comments.giscus]
|
||||
repo = ""
|
||||
repoID = ""
|
||||
category = ""
|
||||
categoryID = ""
|
||||
mapping = "title"
|
||||
lightTheme = "light"
|
||||
darkTheme = "dark_dimmed"
|
||||
repo = ""
|
||||
repoID = ""
|
||||
category = ""
|
||||
categoryID = ""
|
||||
mapping = "title"
|
||||
lightTheme = "light"
|
||||
darkTheme = "dark_dimmed"
|
||||
reactionsEnabled = 1
|
||||
emitMetadata = 0
|
||||
inputPosition = "top"
|
||||
lang = "en"
|
||||
strict = 0
|
||||
loading = "lazy"
|
||||
emitMetadata = 0
|
||||
inputPosition = "top"
|
||||
lang = "en"
|
||||
strict = 0
|
||||
loading = "lazy"
|
||||
|
||||
[comments.gitalk]
|
||||
owner = ""
|
||||
admin = ""
|
||||
repo = ""
|
||||
clientID = ""
|
||||
owner = ""
|
||||
admin = ""
|
||||
repo = ""
|
||||
clientID = ""
|
||||
clientSecret = ""
|
||||
proxy = ""
|
||||
proxy = ""
|
||||
|
||||
[comments.cusdis]
|
||||
host = "https://cusdis.com"
|
||||
id = ""
|
||||
id = ""
|
||||
|
||||
# Comentario client configuration
|
||||
[comments.comentario]
|
||||
url = ""
|
||||
autoInit = false
|
||||
autoNonInteractiveSso = false
|
||||
cssOverride = ""
|
||||
lang = ""
|
||||
liveUpdate = false
|
||||
maxLevel = 0
|
||||
noFonts = false
|
||||
theme = ""
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
baseURL = "https://demo.stack.jimmycai.com/"
|
||||
languageCode = "en-us"
|
||||
title = "Hugo Theme Stack"
|
||||
|
||||
baseURL = "https://demo.stack.cai.im/"
|
||||
languageCode = "en-us"
|
||||
title = "Hugo Theme Stack"
|
||||
# Theme i18n support
|
||||
# See i18n folder for available languages
|
||||
defaultContentLanguage = "en"
|
||||
|
||||
# Set hasCJKLanguage to true if DefaultContentLanguage is a CJK language
|
||||
# This will make .Summary and .WordCount behave correctly
|
||||
hasCJKLanguage = false
|
||||
hasCJKLanguage = false
|
||||
|
||||
[[module.imports]]
|
||||
path = "github.com/CaiJimmy/hugo-theme-stack/v3"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
languageName = "English"
|
||||
title = "Hugo Theme Stack"
|
||||
weight = 1
|
||||
|
||||
[en.params.sidebar]
|
||||
subtitle = "Card-style Hugo theme designed for bloggers"
|
||||
|
||||
@@ -9,6 +10,7 @@
|
||||
languageName = "简体中文"
|
||||
title = "Hugo 主题 Stack"
|
||||
weight = 2
|
||||
|
||||
[zh.params.sidebar]
|
||||
subtitle = "为博客设计的卡片式 Hugo 主题"
|
||||
|
||||
@@ -16,6 +18,7 @@
|
||||
languageName = "正體中文"
|
||||
title = "Hugo 主題 Stack"
|
||||
weight = 3
|
||||
|
||||
[zh-hant-tw.params.sidebar]
|
||||
subtitle = "為博客設計的卡片式 Hugo 主題"
|
||||
|
||||
@@ -23,5 +26,6 @@
|
||||
languageName = "日本語"
|
||||
title = "Hugo Theme Stack"
|
||||
weight = 4
|
||||
|
||||
[ja.params.sidebar]
|
||||
subtitle = "ブログ用のカードスタイルの Hugo テーマ"
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
[goldmark.extensions]
|
||||
[goldmark.extensions.passthrough]
|
||||
enable = true
|
||||
|
||||
[goldmark.extensions.passthrough.delimiters]
|
||||
block = [["\\[", "\\]"], ["$$", "$$"]]
|
||||
inline = [["\\(", "\\)"]]
|
||||
|
||||
[goldmark.renderer]
|
||||
## Set to true if you have HTML content inside Markdown
|
||||
unsafe = true
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
identifier = "github"
|
||||
name = "GitHub"
|
||||
url = "https://github.com/CaiJimmy/hugo-theme-stack"
|
||||
|
||||
[social.params]
|
||||
icon = "brand-github"
|
||||
|
||||
@@ -9,5 +10,6 @@
|
||||
identifier = "twitter"
|
||||
name = "Twitter"
|
||||
url = "https://twitter.com"
|
||||
|
||||
[social.params]
|
||||
icon = "brand-twitter"
|
||||
|
||||
@@ -8,10 +8,7 @@ favicon = "img/avatar.png"
|
||||
[sidebar]
|
||||
emoji = "✏️"
|
||||
subtitle = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
[sidebar.avatar]
|
||||
enabled = true
|
||||
local = true
|
||||
src = "img/avatar.png"
|
||||
avatar = "img/avatar.png"
|
||||
|
||||
[article]
|
||||
[article.license]
|
||||
@@ -25,7 +22,7 @@ favicon = "img/avatar.png"
|
||||
{ type = "categories", params = { limit = 10 } },
|
||||
{ type = "tag-cloud", params = { limit = 10 } },
|
||||
]
|
||||
page = [{ type = "toc" }]
|
||||
page = [{ type = "toc" }]
|
||||
|
||||
[comments]
|
||||
# See complete configuration in config/_default/params.toml
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
includeNewer = true
|
||||
threshold = 60
|
||||
toLower = false
|
||||
|
||||
indices = [
|
||||
indices = [
|
||||
{ name = "tags", weight = 100 },
|
||||
{ name = "categories", weight = 200 },
|
||||
]
|
||||
|
||||
@@ -47,6 +47,26 @@ The blockquote element represents content that is quoted from another source, op
|
||||
|
||||
[^1]: The above quote is excerpted from Rob Pike's [talk](https://www.youtube.com/watch?v=PAAkCSZUG1c) during Gopherfest, November 18, 2015.
|
||||
|
||||
### Blockquote with alert
|
||||
|
||||
> [!NOTE]
|
||||
> Highlights information that users should take into account, even when skimming.
|
||||
|
||||
> [!NOTE] Custom title
|
||||
> You can also provide a custom title for the note alert.
|
||||
|
||||
> [!TIP]
|
||||
> Optional information to help a user be more successful.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Crucial information necessary for users to succeed.
|
||||
|
||||
> [!WARNING]
|
||||
> Critical content demanding immediate user attention due to potential risks.
|
||||
|
||||
> [!CAUTION]
|
||||
> Negative potential consequences of an action.
|
||||
|
||||
## Tables
|
||||
|
||||
Tables aren't part of the core Markdown spec, but Hugo supports supports them out-of-the-box.
|
||||
|
||||
@@ -47,6 +47,26 @@ Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sap
|
||||
|
||||
[^1]: 以上引用摘自 Rob Pike 在 2015 年 11 月 18 日 Gopherfest 期间的[演讲](https://www.youtube.com/watch?v=PAAkCSZUG1c)。
|
||||
|
||||
### 带提示的引用
|
||||
|
||||
> [!NOTE]
|
||||
> 突出显示用户在快速浏览时也应注意的信息。
|
||||
|
||||
> [!TIP]
|
||||
> 可选信息,帮助用户更顺利地完成任务。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 用户成功所必需的关键信息。
|
||||
|
||||
> [!WARNING]
|
||||
> 由于潜在风险而需要用户立即关注的关键内容。
|
||||
|
||||
> [!CAUTION]
|
||||
> 某个操作可能带来的负面后果。
|
||||
|
||||
> [!NOTE] 自定义标题
|
||||
> 如果你想使用自定义标题,可以在方括号后面添加标题文本,如上所示。
|
||||
|
||||
## 表格 (Tables)
|
||||
|
||||
表格虽然不是 Markdown 核心规范的一部分,但 Hugo 出箱即用地支持它们。
|
||||
|
||||
@@ -0,0 +1,603 @@
|
||||
---
|
||||
author: Hugo Authors
|
||||
title: Mermaid Diagrams
|
||||
date: 2025-12-23
|
||||
description: A comprehensive guide to creating diagrams with Mermaid in Hugo
|
||||
categories:
|
||||
- Themes
|
||||
- Syntax
|
||||
tags:
|
||||
- Mermaid
|
||||
- Diagrams
|
||||
- Markdown
|
||||
---
|
||||
|
||||
This theme supports [Mermaid](https://mermaid.js.org/) diagrams directly in your Markdown content. Mermaid lets you create diagrams and visualizations using text and code.
|
||||
|
||||
<!--more-->
|
||||
|
||||
## About Mermaid.js
|
||||
|
||||
This theme integrates [Mermaid.js](https://mermaid.js.org/) (v11) to render diagrams from text definitions within Markdown code blocks. Mermaid is a JavaScript-based diagramming and charting tool that uses text-based syntax inspired by Markdown.
|
||||
|
||||
For complete syntax documentation, see the [Mermaid.js documentation](https://mermaid.js.org/intro/syntax-reference.html).
|
||||
|
||||
## Getting Started
|
||||
|
||||
To create a Mermaid diagram, simply use a fenced code block with `mermaid` as the language identifier:
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Start] --> B[Process]
|
||||
B --> C[End]
|
||||
```
|
||||
````
|
||||
|
||||
The diagram will be automatically rendered when the page loads.
|
||||
|
||||
## Features
|
||||
|
||||
- **Auto-detection**: Mermaid script only loads on pages that contain diagrams
|
||||
- **Theme Support**: Diagrams automatically adapt to light/dark mode
|
||||
- **HTML Labels**: Support for HTML content in labels (like `<br/>` for line breaks)
|
||||
- **Configurable**: Customize version, security level, and more in your site config
|
||||
|
||||
## Configuration
|
||||
|
||||
You can configure Mermaid in your site config:
|
||||
|
||||
**hugo.yaml:**
|
||||
```yaml
|
||||
params:
|
||||
article:
|
||||
mermaid:
|
||||
version: "11" # Mermaid version from CDN
|
||||
look: classic # classic or handDrawn (sketch style)
|
||||
lightTheme: default # Theme for light mode
|
||||
darkTheme: neutral # Theme for dark mode
|
||||
securityLevel: strict # strict (default), loose, antiscript, sandbox
|
||||
htmlLabels: true # Enable HTML in labels
|
||||
```
|
||||
|
||||
**hugo.toml:**
|
||||
```toml
|
||||
[params.article.mermaid]
|
||||
version = "11" # Mermaid version from CDN
|
||||
look = "classic" # classic or handDrawn (sketch style)
|
||||
lightTheme = "default" # Theme for light mode
|
||||
darkTheme = "neutral" # Theme for dark mode
|
||||
securityLevel = "strict" # strict (default), loose, antiscript, sandbox
|
||||
htmlLabels = true # Enable HTML in labels
|
||||
```
|
||||
|
||||
### Additional Global Options
|
||||
|
||||
These optional settings use Mermaid's defaults when not specified:
|
||||
|
||||
**hugo.yaml:**
|
||||
```yaml
|
||||
params:
|
||||
article:
|
||||
mermaid:
|
||||
maxTextSize: 50000 # Maximum text size (default: 50000)
|
||||
maxEdges: 500 # Maximum edges allowed (default: 500)
|
||||
fontSize: 16 # Global font size in pixels (default: 16)
|
||||
fontFamily: "arial" # Global font family
|
||||
curve: "basis" # Line curve: basis, cardinal, linear (default: basis)
|
||||
logLevel: 5 # Debug level 0-5, 0=debug, 5=fatal (default: 5)
|
||||
```
|
||||
|
||||
**hugo.toml:**
|
||||
```toml
|
||||
[params.article.mermaid]
|
||||
maxTextSize = 50000 # Maximum text size (default: 50000)
|
||||
maxEdges = 500 # Maximum edges allowed (default: 500)
|
||||
fontSize = 16 # Global font size in pixels (default: 16)
|
||||
fontFamily = "arial" # Global font family
|
||||
curve = "basis" # Line curve: basis, cardinal, linear (default: basis)
|
||||
logLevel = 5 # Debug level 0-5, 0=debug, 5=fatal (default: 5)
|
||||
```
|
||||
|
||||
For diagram-specific options (like `flowchart.useMaxWidth`), use Mermaid's init directive directly in your diagram:
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
%%{init: {'flowchart': {'useMaxWidth': false}}}%%
|
||||
flowchart LR
|
||||
A --> B
|
||||
```
|
||||
````
|
||||
|
||||
> **Security Note:** The default `securityLevel: strict` is recommended. Set to `loose` only if you need HTML labels like `<br/>` in your diagrams.
|
||||
|
||||
### Available Themes
|
||||
|
||||
| Theme | Description |
|
||||
|-------|-------------|
|
||||
| `default` | Standard colorful theme |
|
||||
| `neutral` | Grayscale, great for printing and dark mode |
|
||||
| `dark` | Designed for dark backgrounds |
|
||||
| `forest` | Green color palette |
|
||||
| `base` | Minimal theme, customizable with themeVariables |
|
||||
| `null` | Disable theming entirely |
|
||||
|
||||
### Custom Theme Variables
|
||||
|
||||
For full control, use the `base` theme with custom variables:
|
||||
|
||||
**hugo.yaml:**
|
||||
```yaml
|
||||
params:
|
||||
article:
|
||||
mermaid:
|
||||
lightTheme: base
|
||||
darkTheme: base
|
||||
lightThemeVariables:
|
||||
primaryColor: "#4a90d9"
|
||||
primaryTextColor: "#ffffff"
|
||||
lineColor: "#333333"
|
||||
darkThemeVariables:
|
||||
primaryColor: "#6ab0f3"
|
||||
primaryTextColor: "#ffffff"
|
||||
lineColor: "#cccccc"
|
||||
background: "#1a1a2e"
|
||||
```
|
||||
|
||||
**hugo.toml:**
|
||||
```toml
|
||||
[params.article.mermaid]
|
||||
lightTheme = "base"
|
||||
darkTheme = "base"
|
||||
|
||||
[params.article.mermaid.lightThemeVariables]
|
||||
primaryColor = "#4a90d9"
|
||||
primaryTextColor = "#ffffff"
|
||||
lineColor = "#333333"
|
||||
|
||||
[params.article.mermaid.darkThemeVariables]
|
||||
primaryColor = "#6ab0f3"
|
||||
primaryTextColor = "#ffffff"
|
||||
lineColor = "#cccccc"
|
||||
background = "#1a1a2e"
|
||||
```
|
||||
|
||||
Common variables: `primaryColor`, `secondaryColor`, `tertiaryColor`, `primaryTextColor`, `lineColor`, `background`, `fontFamily`
|
||||
|
||||
> **Note:** Theme variables only work with the `base` theme and must use hex color values (e.g., `#ff0000`).
|
||||
|
||||
## Diagram Types
|
||||
|
||||
### Flowchart
|
||||
|
||||
Flowcharts are the most common diagram type. Use `graph` or `flowchart` with direction indicators:
|
||||
- `TD` or `TB`: Top to bottom
|
||||
- `BT`: Bottom to top
|
||||
- `LR`: Left to right
|
||||
- `RL`: Right to left
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[Hard edge] -->|Link text| B(Round edge)
|
||||
B --> C{Decision}
|
||||
C -->|One| D[Result one]
|
||||
C -->|Two| E[Result two]
|
||||
```
|
||||
|
||||
### Sequence Diagram
|
||||
|
||||
Perfect for showing interactions between components:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
participant Bob
|
||||
Alice->>John: Hello John, how are you?
|
||||
loop Healthcheck
|
||||
John->>John: Fight against hypochondria
|
||||
end
|
||||
Note right of John: Rational thoughts <br/>prevail!
|
||||
John-->>Alice: Great!
|
||||
John->>Bob: How about you?
|
||||
Bob-->>John: Jolly good!
|
||||
```
|
||||
|
||||
### Class Diagram
|
||||
|
||||
Visualize class structures and relationships:
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
Animal <|-- Duck
|
||||
Animal <|-- Fish
|
||||
Animal <|-- Zebra
|
||||
Animal : +int age
|
||||
Animal : +String gender
|
||||
Animal: +isMammal()
|
||||
Animal: +mate()
|
||||
class Duck{
|
||||
+String beakColor
|
||||
+swim()
|
||||
+quack()
|
||||
}
|
||||
class Fish{
|
||||
-int sizeInFeet
|
||||
-canEat()
|
||||
}
|
||||
class Zebra{
|
||||
+bool is_wild
|
||||
+run()
|
||||
}
|
||||
```
|
||||
|
||||
### State Diagram
|
||||
|
||||
Model state machines and transitions:
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
|
||||
Still --> Moving
|
||||
Moving --> Still
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
```
|
||||
|
||||
### Entity Relationship Diagram
|
||||
|
||||
Document database schemas:
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
|
||||
CUSTOMER {
|
||||
string name
|
||||
string custNumber
|
||||
string sector
|
||||
}
|
||||
ORDER {
|
||||
int orderNumber
|
||||
string deliveryAddress
|
||||
}
|
||||
```
|
||||
|
||||
### Gantt Chart
|
||||
|
||||
Plan and track project schedules:
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
dateFormat YYYY-MM-DD
|
||||
section Section
|
||||
A task :a1, 2024-01-01, 30d
|
||||
Another task :after a1, 20d
|
||||
section Another
|
||||
Task in Another :2024-01-12, 12d
|
||||
another task :24d
|
||||
```
|
||||
|
||||
### Pie Chart
|
||||
|
||||
Display proportional data:
|
||||
|
||||
```mermaid
|
||||
pie showData
|
||||
title Key elements in Product X
|
||||
"Calcium" : 42.96
|
||||
"Potassium" : 50.05
|
||||
"Magnesium" : 10.01
|
||||
"Iron" : 5
|
||||
```
|
||||
|
||||
### Git Graph
|
||||
|
||||
Visualize Git branching strategies:
|
||||
|
||||
```mermaid
|
||||
gitGraph
|
||||
commit
|
||||
commit
|
||||
branch develop
|
||||
checkout develop
|
||||
commit
|
||||
commit
|
||||
checkout main
|
||||
merge develop
|
||||
commit
|
||||
commit
|
||||
```
|
||||
|
||||
### Mindmap
|
||||
|
||||
Create hierarchical mindmaps:
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
root((mindmap))
|
||||
Origins
|
||||
Long history
|
||||
Popularisation
|
||||
British popular psychology author Tony Buzan
|
||||
Research
|
||||
On effectiveness<br/>and features
|
||||
On Automatic creation
|
||||
Uses
|
||||
Creative techniques
|
||||
Strategic planning
|
||||
Argument mapping
|
||||
Tools
|
||||
Pen and paper
|
||||
Mermaid
|
||||
```
|
||||
|
||||
### Timeline
|
||||
|
||||
Display chronological events:
|
||||
|
||||
```mermaid
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook
|
||||
: Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### HTML in Labels
|
||||
|
||||
To use HTML in labels, you must set `securityLevel: loose` in your site config:
|
||||
|
||||
**hugo.yaml:**
|
||||
```yaml
|
||||
params:
|
||||
article:
|
||||
mermaid:
|
||||
securityLevel: loose
|
||||
htmlLabels: true
|
||||
```
|
||||
|
||||
**hugo.toml:**
|
||||
```toml
|
||||
[params.article.mermaid]
|
||||
securityLevel = "loose"
|
||||
htmlLabels = true
|
||||
```
|
||||
|
||||
Then you can use HTML tags like `<br/>` for line breaks:
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Line 1<br/>Line 2] --> B[<b>Bold</b> text]
|
||||
```
|
||||
````
|
||||
|
||||
### Per-Diagram Theming
|
||||
|
||||
Override the theme for a specific diagram using Mermaid's frontmatter:
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
%%{init: {'theme': 'forest'}}%%
|
||||
graph TD
|
||||
A[Start] --> B[End]
|
||||
```
|
||||
````
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'forest'}}%%
|
||||
graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[Car]
|
||||
```
|
||||
|
||||
### Inline Styling with `style`
|
||||
|
||||
You can style individual nodes directly within your diagram using the `style` directive:
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[Start] --> B[Process] --> C[End]
|
||||
style A fill:#4ade80,stroke:#166534,color:#000
|
||||
style B fill:#60a5fa,stroke:#1e40af,color:#000
|
||||
style C fill:#f87171,stroke:#991b1b,color:#fff
|
||||
```
|
||||
````
|
||||
|
||||
**Result:**
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[Start] --> B[Process] --> C[End]
|
||||
style A fill:#4ade80,stroke:#166534,color:#000
|
||||
style B fill:#60a5fa,stroke:#1e40af,color:#000
|
||||
style C fill:#f87171,stroke:#991b1b,color:#fff
|
||||
```
|
||||
|
||||
Style properties include:
|
||||
- `fill` - Background color
|
||||
- `stroke` - Border color
|
||||
- `stroke-width` - Border thickness
|
||||
- `color` - Text color
|
||||
- `stroke-dasharray` - Dashed border (e.g., `5 5`)
|
||||
|
||||
### Styling with CSS Classes
|
||||
|
||||
You can define reusable styles with `classDef` and apply them using `:::className`:
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A:::success --> B:::info --> C:::warning
|
||||
classDef success fill:#4ade80,stroke:#166534,color:#000
|
||||
classDef info fill:#60a5fa,stroke:#1e40af,color:#000
|
||||
classDef warning fill:#fbbf24,stroke:#92400e,color:#000
|
||||
```
|
||||
````
|
||||
|
||||
**Result:**
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A:::success --> B:::info --> C:::warning
|
||||
classDef success fill:#4ade80,stroke:#166534,color:#000
|
||||
classDef info fill:#60a5fa,stroke:#1e40af,color:#000
|
||||
classDef warning fill:#fbbf24,stroke:#92400e,color:#000
|
||||
```
|
||||
|
||||
### Subgraphs
|
||||
|
||||
Group related nodes together:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph one
|
||||
a1-->a2
|
||||
end
|
||||
subgraph two
|
||||
b1-->b2
|
||||
end
|
||||
subgraph three
|
||||
c1-->c2
|
||||
end
|
||||
one --> two
|
||||
three --> two
|
||||
two --> c2
|
||||
```
|
||||
|
||||
## Theme Switching
|
||||
|
||||
This theme automatically detects your site's light/dark mode preference and adjusts the Mermaid diagram theme accordingly:
|
||||
|
||||
- **Light mode**: Uses the `default` Mermaid theme
|
||||
- **Dark mode**: Uses the `dark` Mermaid theme (configurable)
|
||||
|
||||
Try toggling the theme switcher to see diagrams update in real-time!
|
||||
|
||||
## Complex Example
|
||||
|
||||
Here's an example with subgraphs, HTML labels, emojis, and custom styling:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph client["👤 Client"]
|
||||
A["User Device<br/>192.168.1.10"]
|
||||
end
|
||||
|
||||
subgraph cloud["☁️ Cloud Gateway"]
|
||||
B["Load Balancer<br/>(SSL Termination)"]
|
||||
end
|
||||
|
||||
subgraph server["🖥️ Application Server"]
|
||||
C["API Gateway<br/>10.0.0.1"]
|
||||
D["Auth Service<br/>10.0.0.2"]
|
||||
E["Web Server<br/>10.0.0.3"]
|
||||
F["Database<br/>10.0.0.4"]
|
||||
end
|
||||
|
||||
A -- "HTTPS Request" --> B
|
||||
B -- "Forward<br/>(internal)" --> C
|
||||
C -- "Authenticate" --> D
|
||||
D -- "Token" --> C
|
||||
C -- "Route" --> E
|
||||
E --> F
|
||||
|
||||
style client fill:#1a365d,stroke:#2c5282,color:#fff
|
||||
style cloud fill:#f6ad55,stroke:#dd6b20,color:#000
|
||||
style server fill:#276749,stroke:#22543d,color:#fff
|
||||
```
|
||||
|
||||
> **Note:** This example requires `securityLevel: loose` for HTML labels and styling to work.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### Dark Mode Theming
|
||||
|
||||
Mermaid.js's built-in themes have some limitations:
|
||||
|
||||
- **`dark` theme** (default): Best text contrast, but some diagram backgrounds may appear brownish (e.g., Gantt charts)
|
||||
- **`neutral` theme**: Better background colors, but some text (labels, legends) may have reduced contrast
|
||||
|
||||
**For full control**, use the `base` theme with custom variables:
|
||||
|
||||
**hugo.yaml:**
|
||||
```yaml
|
||||
params:
|
||||
article:
|
||||
mermaid:
|
||||
darkTheme: base
|
||||
darkThemeVariables:
|
||||
primaryColor: "#1f2937"
|
||||
primaryTextColor: "#ffffff"
|
||||
lineColor: "#9ca3af"
|
||||
textColor: "#e5e7eb"
|
||||
```
|
||||
|
||||
**hugo.toml:**
|
||||
```toml
|
||||
[params.article.mermaid]
|
||||
darkTheme = "base"
|
||||
|
||||
[params.article.mermaid.darkThemeVariables]
|
||||
primaryColor = "#1f2937"
|
||||
primaryTextColor = "#ffffff"
|
||||
lineColor = "#9ca3af"
|
||||
textColor = "#e5e7eb"
|
||||
```
|
||||
|
||||
We plan to improve dark mode theming in future updates as Mermaid.js evolves.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Diagram not rendering?
|
||||
|
||||
1. Make sure you're using a fenced code block with `mermaid` as the language
|
||||
2. Check your browser's console for syntax errors
|
||||
3. Verify your Mermaid syntax at [Mermaid Live Editor](https://mermaid.live/)
|
||||
|
||||
### HTML not working in labels?
|
||||
|
||||
HTML in labels requires `securityLevel: loose`. Update your configuration:
|
||||
|
||||
**hugo.yaml:**
|
||||
```yaml
|
||||
params:
|
||||
article:
|
||||
mermaid:
|
||||
securityLevel: loose
|
||||
htmlLabels: true
|
||||
```
|
||||
|
||||
**hugo.toml:**
|
||||
```toml
|
||||
[params.article.mermaid]
|
||||
securityLevel = "loose"
|
||||
htmlLabels = true
|
||||
```
|
||||
|
||||
> **Warning:** Using `loose` security level allows HTML in diagrams. Only use this if you trust your diagram content.
|
||||
|
||||
### Syntax errors?
|
||||
|
||||
Mermaid is strict about syntax. Common issues:
|
||||
- Missing spaces around arrows
|
||||
- Unclosed brackets or quotes
|
||||
- Invalid node IDs (avoid special characters)
|
||||
|
||||
## Resources
|
||||
|
||||
- [Mermaid Documentation](https://mermaid.js.org/intro/)
|
||||
- [Mermaid Live Editor](https://mermaid.live/) - Test diagrams interactively
|
||||
- [Mermaid Syntax Reference](https://mermaid.js.org/intro/syntax-reference.html)
|
||||
@@ -22,6 +22,13 @@ darkMode = "الوضع الداكن"
|
||||
one = "تُقرأ خلال دقيقة"
|
||||
other = "تُقرأ خلال {{ .Count }} دقائق"
|
||||
|
||||
[article.alert]
|
||||
note = "ملاحظة"
|
||||
tip = "نصيحة"
|
||||
important = "مهم"
|
||||
warning = "تحذير"
|
||||
caution = "تنبيه"
|
||||
|
||||
[notFound]
|
||||
title = "غير موجود"
|
||||
subtitle = "تعذر العثور على الصفحة المطلوبة."
|
||||
|
||||
@@ -23,6 +23,13 @@ darkMode = "Цёмны рэжым"
|
||||
tableOfContents = "Змест"
|
||||
readingTime = "Час чытання: {{ .Count }} хв."
|
||||
|
||||
[article.alert]
|
||||
note = "Заўвага"
|
||||
tip = "Парада"
|
||||
important = "Важна"
|
||||
warning = "Папярэджанне"
|
||||
caution = "Асцярога"
|
||||
|
||||
[notFound]
|
||||
title = "Не знойдзена"
|
||||
subtitle = "Запытваемая старонка не існуе"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "Тъмен Режим"
|
||||
one = "{{ .Count }} minute read"
|
||||
other = "{{ .Count }} minute read"
|
||||
|
||||
[article.alert]
|
||||
note = "Бележка"
|
||||
tip = "Съвет"
|
||||
important = "Важно"
|
||||
warning = "Предупреждение"
|
||||
caution = "Внимание"
|
||||
|
||||
[notFound]
|
||||
title = "Не е намерено"
|
||||
subtitle = "Страницата която търсите не е открита"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "ডার্ক মোড"
|
||||
one = "{{ .Count }} মিনিটে পড়া যাবে"
|
||||
other = "{{ .Count }} মিনিটে পড়া যাবে"
|
||||
|
||||
[article.alert]
|
||||
note = "টীকা"
|
||||
tip = "পরামর্শ"
|
||||
important = "গুরুত্বপূর্ণ"
|
||||
warning = "সতর্কতা"
|
||||
caution = "সাবধানতা"
|
||||
|
||||
[notFound]
|
||||
title = "পাওয়া যায়নি"
|
||||
subtitle = "এই পাতাটি বিদ্যমান নেই"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "Mode fosc"
|
||||
one = "{{ .Count }} minut a llegir"
|
||||
other = "{{ .Count }} minuts a llegir"
|
||||
|
||||
[article.alert]
|
||||
note = "Nota"
|
||||
tip = "Consell"
|
||||
important = "Important"
|
||||
warning = "Avís"
|
||||
caution = "Precaució"
|
||||
|
||||
[notFound]
|
||||
title = "No Trobat"
|
||||
subtitle = "Aquesta pàgina no existeix"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "Tmavý režim"
|
||||
one = "{{ .Count }} minuta"
|
||||
other = "{{ .Count }} minut"
|
||||
|
||||
[article.alert]
|
||||
note = "Poznámka"
|
||||
tip = "Tip"
|
||||
important = "Důležité"
|
||||
warning = "Varování"
|
||||
caution = "Upozornění"
|
||||
|
||||
[notFound]
|
||||
title = "Nenalezeno"
|
||||
subtitle = "Tato stránka neexistuje"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "Dunkler Modus"
|
||||
one = "{{ .Count }} Minute Lesezeit"
|
||||
other = "{{ .Count }} Minuten Lesezeit"
|
||||
|
||||
[article.alert]
|
||||
note = "Hinweis"
|
||||
tip = "Tipp"
|
||||
important = "Wichtig"
|
||||
warning = "Warnung"
|
||||
caution = "Vorsicht"
|
||||
|
||||
[notFound]
|
||||
title = "Seite nicht gefunden"
|
||||
subtitle = "Diese Seite existiert nicht"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "Σκοτεινό θέμα"
|
||||
one = "{{ .Count }} λεπτό ανάγνωσης"
|
||||
other = "{{ .Count }} λεπτά ανάγνωσης"
|
||||
|
||||
[article.alert]
|
||||
note = "Σημείωση"
|
||||
tip = "Συμβουλή"
|
||||
important = "Σημαντικό"
|
||||
warning = "Προειδοποίηση"
|
||||
caution = "Προσοχή"
|
||||
|
||||
[notFound]
|
||||
title = "Δε βρέθηκε"
|
||||
subtitle = "Η σελίδα δε βρέθηκε."
|
||||
|
||||
+8
-1
@@ -20,7 +20,14 @@ darkMode = "Dark Mode"
|
||||
|
||||
[article.readingTime]
|
||||
one = "{{ .Count }} minute read"
|
||||
other = "{{ .Count }} minute read"
|
||||
other = "{{ .Count }} minutes read"
|
||||
|
||||
[article.alert]
|
||||
note = "Note"
|
||||
tip = "Tip"
|
||||
important = "Important"
|
||||
warning = "Warning"
|
||||
caution = "Caution"
|
||||
|
||||
[notFound]
|
||||
title = "Not Found"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "Modo oscuro"
|
||||
one = "Tiempo de lectura {{ .Count }} minuto"
|
||||
other = "Tiempo de lectura {{ .Count }} minutos"
|
||||
|
||||
[article.alert]
|
||||
note = "Nota"
|
||||
tip = "Consejo"
|
||||
important = "Importante"
|
||||
warning = "Advertencia"
|
||||
caution = "Precaución"
|
||||
|
||||
[notFound]
|
||||
title = "No Encontrado"
|
||||
subtitle = "Esta página no existe"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "حالت شب"
|
||||
one = "مطالعه در {{ .Count }} دقیقه"
|
||||
other = "مطالعه در {{ .Count }} دقیقه"
|
||||
|
||||
[article.alert]
|
||||
note = "یادداشت"
|
||||
tip = "نکته"
|
||||
important = "مهم"
|
||||
warning = "هشدار"
|
||||
caution = "احتیاط"
|
||||
|
||||
[notFound]
|
||||
title = "یافت نشد"
|
||||
subtitle = "این صحه وجود ندارد"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "Mode sombre"
|
||||
one = "{{ .Count }} minute de lecture"
|
||||
other = "{{ .Count }} minutes de lecture"
|
||||
|
||||
[article.alert]
|
||||
note = "Note"
|
||||
tip = "Astuce"
|
||||
important = "Important"
|
||||
warning = "Avertissement"
|
||||
caution = "Attention"
|
||||
|
||||
[notFound]
|
||||
title = "Page non trouvée"
|
||||
subtitle = "Cette page n'existe pas."
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "डार्क मोड"
|
||||
one = "{{ .Count }} मिनट पढ़ें"
|
||||
other = "{{ .Count }} मिनट पढ़ें"
|
||||
|
||||
[article.alert]
|
||||
note = "नोट"
|
||||
tip = "सलाह"
|
||||
important = "महत्वपूर्ण"
|
||||
warning = "चेतावनी"
|
||||
caution = "सावधानी"
|
||||
|
||||
[notFound]
|
||||
title = "404 नहीं मिला।"
|
||||
subtitle = "यह पृष्ठ मौजूद नहीं है।"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "Sötét Mód"
|
||||
one = "{{ .Count }} percnyi olvasmány"
|
||||
other = "{{ .Count }} percnyi olvasmány"
|
||||
|
||||
[article.alert]
|
||||
note = "Megjegyzés"
|
||||
tip = "Tipp"
|
||||
important = "Fontos"
|
||||
warning = "Figyelmeztetés"
|
||||
caution = "Óvatosság"
|
||||
|
||||
[notFound]
|
||||
title = "Nem található"
|
||||
subtitle = "Ez az oldal nem létezik"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "Mode Gelap"
|
||||
one = "Waktu Membaca: {{ .Count }} menit"
|
||||
other = "Waktu Membaca: {{ .Count }} menit"
|
||||
|
||||
[article.alert]
|
||||
note = "Catatan"
|
||||
tip = "Tips"
|
||||
important = "Penting"
|
||||
warning = "Peringatan"
|
||||
caution = "Perhatian"
|
||||
|
||||
[notFound]
|
||||
title = "Tidak ditemukan"
|
||||
subtitle = "Halaman yang Anda akses tidak ditemukan."
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "Modalità scura"
|
||||
one = "{{ .Count }} min per leggere"
|
||||
other = "{{ .Count }} min per leggere"
|
||||
|
||||
[article.alert]
|
||||
note = "Nota"
|
||||
tip = "Suggerimento"
|
||||
important = "Importante"
|
||||
warning = "Avvertenza"
|
||||
caution = "Attenzione"
|
||||
|
||||
[notFound]
|
||||
title = "Non trovato"
|
||||
subtitle = "Questa pagina non esiste."
|
||||
|
||||
@@ -13,6 +13,13 @@ darkMode = "ダークモード"
|
||||
lastUpdatedOn = "最終更新"
|
||||
readingTime = "読了時間: {{ .Count }}分"
|
||||
|
||||
[article.alert]
|
||||
note = "注記"
|
||||
tip = "ヒント"
|
||||
important = "重要"
|
||||
warning = "警告"
|
||||
caution = "注意"
|
||||
|
||||
[notFound]
|
||||
title = "404 Not Found"
|
||||
subtitle = "指定されたページは存在しません。"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "다크 모드"
|
||||
one = "{{ .Count }} 분 정도"
|
||||
other = "{{ .Count }} 분 정도"
|
||||
|
||||
[article.alert]
|
||||
note = "참고"
|
||||
tip = "팁"
|
||||
important = "중요"
|
||||
warning = "경고"
|
||||
caution = "주의"
|
||||
|
||||
[notFound]
|
||||
title = "찾을 수 없음"
|
||||
subtitle = "페이지를 찾을 수 없습니다."
|
||||
|
||||
@@ -17,6 +17,13 @@ darkMode = "Donkere modus"
|
||||
lastUpdatedOn = "Laatst bijgewerkt op"
|
||||
readingTime = "{{ .Count }} leestijd"
|
||||
|
||||
[article.alert]
|
||||
note = "Opmerking"
|
||||
tip = "Tip"
|
||||
important = "Belangrijk"
|
||||
warning = "Waarschuwing"
|
||||
caution = "Let op"
|
||||
|
||||
[notFound]
|
||||
title = "Niet gevonden"
|
||||
subtitle = "Deze pagina bestaat niet."
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "Mòde fosc"
|
||||
one = "{{ .Count }} minuta de lectura"
|
||||
other = "{{ .Count }} minutas de lectura"
|
||||
|
||||
[article.alert]
|
||||
note = "Nòta"
|
||||
tip = "Conselh"
|
||||
important = "Important"
|
||||
warning = "Avertiment"
|
||||
caution = "Atencion"
|
||||
|
||||
[notFound]
|
||||
title = "Non trobat"
|
||||
subtitle = "Aquesta pagina existís pas"
|
||||
|
||||
@@ -28,6 +28,13 @@ darkMode = "Tryb ciemny"
|
||||
many = "Przeczytasz w {{ .Count }} minut"
|
||||
other = "Przeczytasz w {{ .Count }} minut"
|
||||
|
||||
[article.alert]
|
||||
note = "Notatka"
|
||||
tip = "Wskazówka"
|
||||
important = "Ważne"
|
||||
warning = "Ostrzeżenie"
|
||||
caution = "Uwaga"
|
||||
|
||||
[notFound]
|
||||
title = "Nie znaleziono"
|
||||
subtitle = "Ta strona nie istnieje"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "Modo Escuro"
|
||||
one = "{{ .Count }} minuto de leitura"
|
||||
other = "{{ .Count }} minutos de leitura"
|
||||
|
||||
[article.alert]
|
||||
note = "Nota"
|
||||
tip = "Dica"
|
||||
important = "Importante"
|
||||
warning = "Aviso"
|
||||
caution = "Cuidado"
|
||||
|
||||
[notFound]
|
||||
title = "Não Encontrado"
|
||||
subtitle = "Esta página não existe."
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "Modo Escuro"
|
||||
one = "{{ .Count }} minuto de leitura"
|
||||
other = "{{ .Count }} minutos de leitura"
|
||||
|
||||
[article.alert]
|
||||
note = "Nota"
|
||||
tip = "Dica"
|
||||
important = "Importante"
|
||||
warning = "Aviso"
|
||||
caution = "Cuidado"
|
||||
|
||||
[notFound]
|
||||
title = "Não Encontrado"
|
||||
subtitle = "Esta página não existe."
|
||||
|
||||
@@ -23,6 +23,13 @@ darkMode = "Тёмный режим"
|
||||
tableOfContents = "Содержание"
|
||||
readingTime = "Время чтения: {{ .Count }} мин."
|
||||
|
||||
[article.alert]
|
||||
note = "Примечание"
|
||||
tip = "Совет"
|
||||
important = "Важно"
|
||||
warning = "Предупреждение"
|
||||
caution = "Осторожно"
|
||||
|
||||
[notFound]
|
||||
title = "Не найдено"
|
||||
subtitle = "Запрашиваемая страница не существует"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "Tmavý režim"
|
||||
one = "{{ .Count }} minúta"
|
||||
other = "{{ .Count }} minút"
|
||||
|
||||
[article.alert]
|
||||
note = "Poznámka"
|
||||
tip = "Tip"
|
||||
important = "Dôležité"
|
||||
warning = "Varovanie"
|
||||
caution = "Upozornenie"
|
||||
|
||||
[notFound]
|
||||
title = "Nenájdené"
|
||||
subtitle = "Tato stránka neexistuje"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "ธีมมืด"
|
||||
one = "น่าจะใช้เวลา {{ .Count }} นาทีในการอ่าน"
|
||||
other = "น่าจะใช้เวลา {{ .Count }} นาทีในการอ่าน"
|
||||
|
||||
[article.alert]
|
||||
note = "หมายเหตุ"
|
||||
tip = "เคล็ดลับ"
|
||||
important = "สำคัญ"
|
||||
warning = "คำเตือน"
|
||||
caution = "ระวัง"
|
||||
|
||||
[notFound]
|
||||
title = "ไม่พบหัวข้อ"
|
||||
subtitle = "ไม่พบหน้านี้ในระบบ"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "Koyu Mod"
|
||||
one = "{{ .Count }} dakika okuma süresi"
|
||||
other = "{{ .Count }} dakika okuma süresi"
|
||||
|
||||
[article.alert]
|
||||
note = "Not"
|
||||
tip = "İpucu"
|
||||
important = "Önemli"
|
||||
warning = "Uyarı"
|
||||
caution = "Dikkat"
|
||||
|
||||
[notFound]
|
||||
title = "Bulunamadı"
|
||||
subtitle = "Aradığınız sayfa mevcut değil."
|
||||
|
||||
@@ -23,6 +23,13 @@ darkMode = "Темна тема"
|
||||
one = "Час читання: {{ .Count }} хв"
|
||||
other = "Час читання: {{ .Count }} хв"
|
||||
|
||||
[article.alert]
|
||||
note = "Примітка"
|
||||
tip = "Порада"
|
||||
important = "Важливо"
|
||||
warning = "Попередження"
|
||||
caution = "Обережно"
|
||||
|
||||
[notFound]
|
||||
title = "Не знайдено"
|
||||
subtitle = "Ця сторінка не існує"
|
||||
|
||||
@@ -22,6 +22,13 @@ darkMode = "Chế độ nền tối"
|
||||
one = "{{ .Count }} phút đọc"
|
||||
other = "{{ .Count }} phút đọc"
|
||||
|
||||
[article.alert]
|
||||
note = "Ghi chú"
|
||||
tip = "Mẹo"
|
||||
important = "Quan trọng"
|
||||
warning = "Cảnh báo"
|
||||
caution = "Thận trọng"
|
||||
|
||||
[notFound]
|
||||
title = "Không tìm thấy"
|
||||
subtitle = "Trang này không tồn tại"
|
||||
|
||||
+18
-20
@@ -2,46 +2,44 @@ toggleMenu = "切換選單"
|
||||
darkMode = "深色模式"
|
||||
|
||||
[list]
|
||||
section = "Section"
|
||||
|
||||
[list.page]
|
||||
one = "第 {{ .Count }} 頁"
|
||||
other = "第 {{ .Count }} 頁"
|
||||
|
||||
[list.subsection]
|
||||
one = "Subsection"
|
||||
other = "Subsections"
|
||||
page = "{{ .Count }} 篇文章"
|
||||
section = "章節"
|
||||
subsection = "子章節"
|
||||
|
||||
[article]
|
||||
back = "返回"
|
||||
tableOfContents = "目錄"
|
||||
relatedContent = "相關內容"
|
||||
lastUpdatedOn = "上次改過於"
|
||||
readingTime = "閱讀時間 {{ .Count }} 分鐘"
|
||||
|
||||
[article.readingTime]
|
||||
one = "需要 {{ .Count }} 分鐘閱讀"
|
||||
other = "需要 {{ .Count }} 分鐘閱讀"
|
||||
[article.alert]
|
||||
note = "備註"
|
||||
tip = "提示"
|
||||
important = "重要"
|
||||
warning = "警告"
|
||||
caution = "注意"
|
||||
|
||||
[notFound]
|
||||
title = "Not Found"
|
||||
subtitle = "頁面不存在"
|
||||
title = "找不到頁面"
|
||||
subtitle = "本頁面並不存在"
|
||||
|
||||
[widget]
|
||||
[widget.archives]
|
||||
title = "Archives"
|
||||
title = "歸檔"
|
||||
more = "更多"
|
||||
|
||||
[widget.tagCloud]
|
||||
title = "Tags"
|
||||
title = "標籤"
|
||||
|
||||
[widget.categoriesCloud]
|
||||
title = "Categories"
|
||||
title = "分類"
|
||||
|
||||
[search]
|
||||
title = "搜尋"
|
||||
placeholder = "Type 關鍵字..."
|
||||
resultTitle = "#PAGES_COUNT pages (#TIME_SECONDS seconds)"
|
||||
placeholder = "輸入關鍵字..."
|
||||
resultTitle = "找到 #PAGES_COUNT 個結果 (用時 #TIME_SECONDS 秒)"
|
||||
|
||||
[footer]
|
||||
builtWith = "Built with {{ .Generator }}"
|
||||
builtWith = "使用 {{ .Generator }} 建立"
|
||||
designedBy = "主題 {{ .Theme }} 由 {{ .DesignedBy }} 設計"
|
||||
|
||||
+10
-12
@@ -2,25 +2,23 @@ toggleMenu = "切換選單"
|
||||
darkMode = "夜晚模式"
|
||||
|
||||
[list]
|
||||
section = "段落"
|
||||
|
||||
[list.page]
|
||||
one = "第 {{ .Count }} 頁"
|
||||
other = "第 {{ .Count }} 頁"
|
||||
|
||||
[list.subsection]
|
||||
one = "小節"
|
||||
other = "小節"
|
||||
section = "段落"
|
||||
page = "第 {{ .Count }} 頁"
|
||||
subsection = "小節"
|
||||
|
||||
[article]
|
||||
back = "返回"
|
||||
tableOfContents = "目錄"
|
||||
relatedContent = "相關文章"
|
||||
lastUpdatedOn = "最後更新"
|
||||
readingTime = "閱讀時間: {{ .Count }} 分鐘"
|
||||
|
||||
[article.readingTime]
|
||||
one = "閱讀時間: {{ .Count }} 分鐘"
|
||||
other = "閱讀時間: {{ .Count }} 分鐘"
|
||||
[article.alert]
|
||||
note = "備註"
|
||||
tip = "提示"
|
||||
important = "重要"
|
||||
warning = "警告"
|
||||
caution = "注意"
|
||||
|
||||
[notFound]
|
||||
title = "404 錯誤"
|
||||
|
||||
@@ -13,6 +13,13 @@ darkMode = "暗色模式"
|
||||
lastUpdatedOn = "最后更新于"
|
||||
readingTime = "阅读时长: {{ .Count }} 分钟"
|
||||
|
||||
[article.alert]
|
||||
note = "备注"
|
||||
tip = "提示"
|
||||
important = "重要"
|
||||
warning = "警告"
|
||||
caution = "注意"
|
||||
|
||||
[notFound]
|
||||
title = "404 错误"
|
||||
subtitle = "页面不存在"
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{{ if eq .Type "alert" }}
|
||||
{{- $type := .AlertType -}}
|
||||
{{- $icons := site.Params.article.alertIcon -}}
|
||||
{{- $icon := index $icons $type -}}
|
||||
<blockquote class="alert alert-{{ $type }}">
|
||||
<div class="alert-header">
|
||||
<span class="alert-icon">
|
||||
{{- $icon -}}
|
||||
</span>
|
||||
<span class="alert-title">
|
||||
{{- with .AlertTitle -}}
|
||||
{{ . }}
|
||||
{{- else -}}
|
||||
{{ or (i18n (printf "article.alert.%s" $type)) (title $type) }}
|
||||
{{- end -}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="alert-body">
|
||||
{{ .Text }}
|
||||
</div>
|
||||
</blockquote>
|
||||
{{ else }}
|
||||
<blockquote>
|
||||
{{ .Text }}
|
||||
</blockquote>
|
||||
{{ end }}
|
||||
@@ -1,4 +1,4 @@
|
||||
<pre class="mermaid">
|
||||
{{ .Inner | htmlEscape | safeHTML }}
|
||||
<pre class="mermaid" style="visibility:hidden">
|
||||
{{- .Inner | safeHTML -}}
|
||||
</pre>
|
||||
{{ .Page.Store.Set "hasMermaid" true }}
|
||||
{{- .Page.Store.Set "hasMermaid" true -}}
|
||||
|
||||
@@ -1,38 +1,21 @@
|
||||
{{- $image := .Page.Resources.GetMatch (printf "%s" (.Destination | safeURL)) -}}
|
||||
{{- $Permalink := .Destination | relURL | safeURL -}}
|
||||
{{- $image := partial "helper/image" (dict "Resources" .Page.Resources "Image" .Destination) -}}
|
||||
{{- $alt := .PlainText | safeHTML -}}
|
||||
{{- $Width := 0 -}}
|
||||
{{- $Height := 0 -}}
|
||||
{{- $Srcset := "" -}}
|
||||
{{- $title := .Title | markdownify -}}
|
||||
|
||||
{{/* SVG and external images won't work with gallery layout, because their width and height attributes are unknown */}}
|
||||
{{- $galleryImage := false -}}
|
||||
{{- $galleryImage := and $image.Height $image.Width -}}
|
||||
|
||||
{{- if $image -}}
|
||||
{{- $notSVG := ne (path.Ext .Destination) ".svg" -}}
|
||||
{{- $Permalink = $image.RelPermalink -}}
|
||||
|
||||
{{- if $notSVG -}}
|
||||
{{- $Width = $image.Width -}}
|
||||
{{- $Height = $image.Height -}}
|
||||
{{- $galleryImage = true -}}
|
||||
|
||||
{{- if .Page.Site.Params.imageProcessing.content.enabled -}}
|
||||
{{- $small := $image.Resize `480x` -}}
|
||||
{{- $big := $image.Resize `1024x` -}}
|
||||
{{- $Srcset = printf `%s 480w, %s 1024w` $small.RelPermalink $big.RelPermalink -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
<img src="{{ $Permalink }}"
|
||||
{{ with $Width }}width="{{ . }}"{{ end }}
|
||||
{{ with $Height }}height="{{ . }}"{{ end }}
|
||||
{{ with $Srcset }}srcset="{{ . }}"{{ end }}
|
||||
<img src="{{ $image.Permalink }}"
|
||||
{{ with $image.Width }}width="{{ . }}"{{ end }}
|
||||
{{ with $image.Height }}height="{{ . }}"{{ end }}
|
||||
loading="lazy"
|
||||
{{ with $alt }}
|
||||
alt="{{ . }}"
|
||||
{{ end }}
|
||||
{{ with $title }}
|
||||
title="{{ . }}"
|
||||
data-title-escaped="{{ . | htmlEscape }}"
|
||||
{{ end }}
|
||||
{{ if $galleryImage }}
|
||||
class="gallery-image"
|
||||
data-flex-grow="{{ div (mul $image.Width 100) $image.Height }}"
|
||||
|
||||
@@ -1,9 +1,42 @@
|
||||
{{ if .Store.Get "hasMermaid" }}
|
||||
{{- if .Store.Get "hasMermaid" -}}
|
||||
{{- $cfg := site.Params.article.mermaid | default dict -}}
|
||||
|
||||
<div class="mermaid-modal" id="mermaid-modal">
|
||||
<div class="mermaid-modal-header">
|
||||
<div class="mermaid-modal-controls">
|
||||
<button data-zoom="-1">− Zoom Out</button>
|
||||
<button data-zoom="0">Reset (100%)</button>
|
||||
<button data-zoom="1">+ Zoom In</button>
|
||||
<button data-zoom="fit">Fit to Screen</button>
|
||||
</div>
|
||||
<button class="mermaid-modal-close" id="mermaid-modal-close">✕ Close (Esc)</button>
|
||||
</div>
|
||||
<div class="mermaid-modal-body" id="mermaid-modal-body">
|
||||
<div class="mermaid-modal-content" id="mermaid-modal-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
||||
{{- $opts := dict "minify" hugo.IsProduction "format" "esm" -}}
|
||||
{{- $script := resources.Get "ts/mermaid.ts" | js.Build $opts -}}
|
||||
{{- $jsConfig := dict
|
||||
"transparentBackground" ($cfg.transparentBackground | default false)
|
||||
"lightTheme" ($cfg.lightTheme | default "default")
|
||||
"darkTheme" ($cfg.darkTheme | default "dark")
|
||||
"lightThemeVariables" ($cfg.lightThemeVariables | default dict)
|
||||
"darkThemeVariables" ($cfg.darkThemeVariables | default dict)
|
||||
"securityLevel" ($cfg.securityLevel | default "strict")
|
||||
"look" ($cfg.look | default "classic")
|
||||
"htmlLabels" (not (eq ($cfg.htmlLabels | default true) false))
|
||||
-}}
|
||||
{{- with $cfg.maxTextSize }}{{ $jsConfig = merge $jsConfig (dict "maxTextSize" .) }}{{ end -}}
|
||||
{{- with $cfg.maxEdges }}{{ $jsConfig = merge $jsConfig (dict "maxEdges" .) }}{{ end -}}
|
||||
{{- with $cfg.fontSize }}{{ $jsConfig = merge $jsConfig (dict "fontSize" .) }}{{ end -}}
|
||||
{{- with $cfg.fontFamily }}{{ $jsConfig = merge $jsConfig (dict "fontFamily" .) }}{{ end -}}
|
||||
{{- with $cfg.curve }}{{ $jsConfig = merge $jsConfig (dict "curve" .) }}{{ end -}}
|
||||
{{- with $cfg.logLevel }}{{ $jsConfig = merge $jsConfig (dict "logLevel" .) }}{{ end -}}
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11.12.2/+esm';
|
||||
mermaid.initialize({
|
||||
startOnLoad: true,
|
||||
theme: 'neutral',
|
||||
});
|
||||
import { initMermaidPage } from '{{ $script.RelPermalink }}';
|
||||
initMermaidPage({{ $jsConfig | jsonify | safeJS }});
|
||||
</script>
|
||||
{{ end }}
|
||||
{{- end -}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{{ $related := (where (.Site.RegularPages.Related .) "Params.hidden" "!=" true) | first 5 }}
|
||||
{{ $related := (.Site.RegularPages.Related .) | first 5 }}
|
||||
{{ with $related }}
|
||||
<aside class="related-content--wrapper">
|
||||
<h2 class="section-title">{{ T "article.relatedContent" }}</h2>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<script defer src="{{ .Site.Params.comments.comentario.url }}/comentario.js"></script>
|
||||
<comentario-comments auto-init="{{ .Site.Params.comments.comentario.autoInit }}"
|
||||
auto-non-interactive-sso="{{ .Site.Params.comments.comentario.autoNonInteractiveSSO }}"
|
||||
css-override="{{ .Site.Params.comments.comentario.cssOverride }}" lang="{{ .Site.Params.comments.comentario.lang }}"
|
||||
live-update="{{ .Site.Params.comments.comentario.liveUpdate }}"
|
||||
max-level="{{ .Site.Params.comments.comentario.maxLevel }}"
|
||||
no-fonts="{{ .Site.Params.comments.comentario.noFonts }}"
|
||||
theme="{{ .Site.Params.comments.comentario.theme }}"></comentario-comments>
|
||||
<script>
|
||||
window.addEventListener('onColorSchemeChange', (e) => {
|
||||
document.querySelector('comentario-comments').setAttribute('theme', e.detail);
|
||||
})
|
||||
</script>
|
||||
@@ -1,11 +0,0 @@
|
||||
<script>
|
||||
(function () {
|
||||
const customFont = document.createElement('link');
|
||||
customFont.href = "https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap";
|
||||
|
||||
customFont.type = "text/css";
|
||||
customFont.rel = "stylesheet";
|
||||
|
||||
document.head.appendChild(customFont);
|
||||
}());
|
||||
</script>
|
||||
@@ -1,3 +1,2 @@
|
||||
{{ partialCached "footer/components/script.html" . }}
|
||||
{{ partialCached "footer/components/custom-font.html" . }}
|
||||
{{ partial "footer/custom.html" . }}
|
||||
@@ -0,0 +1,4 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap">
|
||||
@@ -8,8 +8,13 @@
|
||||
{{- $title := partial "data/title" . -}}
|
||||
<title>{{ $title }}</title>
|
||||
|
||||
<link rel='canonical' href='{{ .Permalink }}'>
|
||||
{{ with .Params.canonicalUrl }}
|
||||
<link rel="canonical" href="{{ . }}">
|
||||
{{ else }}
|
||||
<link rel="canonical" href="{{ .Permalink }}">
|
||||
{{ end }}
|
||||
|
||||
{{- partial "head/custom-font.html" . -}}
|
||||
{{- partial "head/style.html" . -}}
|
||||
{{- partial "head/script.html" . -}}
|
||||
{{- partial "head/opengraph/include.html" . -}}
|
||||
@@ -18,9 +23,9 @@
|
||||
<link rel="{{ .Rel }}" type="{{ .MediaType.Type }}" href="{{ .Permalink | safeURL }}">
|
||||
{{- end -}}
|
||||
|
||||
{{ with .Site.Params.favicon }}
|
||||
{{- $favicon := partial "helper/image" (dict "Image" . "Resources" resources) -}}
|
||||
<link rel="shortcut icon" href="{{ $favicon.Permalink }}" />
|
||||
{{- $favicon := partial "helper/image" (dict "Image" .Site.Params.favicon "Resources" resources) -}}
|
||||
{{ with $favicon }}
|
||||
<link rel="shortcut icon" href="{{ .Permalink }}" />
|
||||
{{ end }}
|
||||
|
||||
{{- partial "google_analytics.html" . -}}
|
||||
|
||||
@@ -9,45 +9,44 @@
|
||||
/// Height: Image height
|
||||
/// Width: Image width
|
||||
|
||||
{{ $result := dict
|
||||
"Local" false
|
||||
"Resource" nil
|
||||
"Permalink" (.Image | absURL)
|
||||
"Width" nil
|
||||
"Height" nil
|
||||
}}
|
||||
{{ $result := "" }}
|
||||
|
||||
{{ if not .Image }}
|
||||
{{ $result = "" }}
|
||||
{{ else }}
|
||||
{{ if .Image }}
|
||||
{{ $url := urls.Parse .Image }}
|
||||
{{ $resource := "" }}
|
||||
{{ if not (in (slice "https" "http") $url.Scheme) }}
|
||||
{{ $resource := .Resources.Get (printf "%s" (.Image | safeURL)) }}
|
||||
{{ if $resource }}
|
||||
{{ $resource = .Resources.Get (printf "%s" (.Image | safeURL)) }}
|
||||
{{ else }}
|
||||
{{ with try (resources.GetRemote $url) }}
|
||||
{{ with .Err }}
|
||||
{{ errorf "%s" . }}
|
||||
{{ else with .Value }}
|
||||
{{ $resource = . }}
|
||||
{{ else }}
|
||||
{{ errorf "Unable to get remote resource %q" $url }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if $resource }}
|
||||
{{- $isSVG := eq $resource.MediaType.SubType "svg" -}}
|
||||
{{- if $isSVG -}}
|
||||
{{ $result = dict
|
||||
"Local" true
|
||||
"Resource" $resource
|
||||
"Permalink" $resource.RelPermalink
|
||||
"Height" nil
|
||||
"Width" nil
|
||||
}}
|
||||
{{- else -}}
|
||||
{{- $result = dict
|
||||
"Local" true
|
||||
"Resource" $resource
|
||||
"Permalink" $resource.RelPermalink
|
||||
"Height" $resource.Height
|
||||
"Width" $resource.Width
|
||||
}}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ with try (resources.GetRemote $url) }}
|
||||
{{ with .Err }}
|
||||
{{ errorf "%s" . }}
|
||||
{{ else with .Value }}
|
||||
{{ $result = dict
|
||||
"Local" false
|
||||
"Resource" .
|
||||
"Permalink" .RelPermalink
|
||||
"Height" .Height
|
||||
"Width" .Width
|
||||
}}
|
||||
{{ else }}
|
||||
{{ errorf "Unable to get remote resource %q" $url }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{- end -}}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{{- $pages := .Pages -}}
|
||||
|
||||
{{- if eq .Context.Site.Params.SortBy "lastmod" -}}
|
||||
{{- $pages = $pages.ByLastmod.Reverse -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- return $pages -}}
|
||||
@@ -0,0 +1,16 @@
|
||||
{{- $pages := .Pages -}}
|
||||
|
||||
{{- if .IsHome -}}
|
||||
{{- $pages = where .Site.RegularPages "Type" "in" .Site.Params.mainSections -}}
|
||||
{{- else if or (eq .Kind "section") (eq .Kind "taxonomy") (eq .Kind "term") -}}
|
||||
{{- $subsections := .Sections -}}
|
||||
{{- $pages = .Pages | complement $subsections -}}
|
||||
|
||||
{{- if eq (len $pages) 0 -}}
|
||||
{{- $pages = $subsections -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $pages := partial "helper/pages-sort.html" (dict "Pages" $pages "Context" .) -}}
|
||||
|
||||
{{- return $pages -}}
|
||||
@@ -1,28 +1,8 @@
|
||||
{{- $pages := slice -}}
|
||||
{{- $hasPages := false -}}
|
||||
{{- $result := slice -}}
|
||||
|
||||
{{- if .IsHome -}}
|
||||
{{ $pages = where .Site.RegularPages "Section" "in" .Site.Params.mainSections }}
|
||||
{{ $pages = where $pages "Params.hidden" "!=" true }}
|
||||
{{- $hasPages = true -}}
|
||||
{{- else if eq .Kind "term" -}}
|
||||
{{ $pages = where .Pages "Params.hidden" "!=" true }}
|
||||
{{- $hasPages = true -}}
|
||||
{{- else if or (eq .Kind "section") (eq .Kind "taxonomy") -}}
|
||||
{{ $subsections := .Sections }}
|
||||
{{ $pages = .Pages | complement $subsections }}
|
||||
{{ $pages = where $pages "Params.hidden" "!=" true }}
|
||||
|
||||
{{- if eq (len $pages) 0 -}}
|
||||
{{/* See complete logic in list.html */}}
|
||||
{{- $pages = $subsections -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $hasPages = true -}}
|
||||
{{- if or .IsHome (eq .Kind "section") (eq .Kind "taxonomy") (eq .Kind "term") -}}
|
||||
{{- $pages := partial "helper/pages.html" . -}}
|
||||
{{- $result = .Paginate $pages -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if $hasPages -}}
|
||||
{{ $pages = .Paginate $pages }}
|
||||
{{- end -}}
|
||||
|
||||
{{ return $pages }}
|
||||
{{- return $result -}}
|
||||
|
||||
@@ -7,32 +7,24 @@
|
||||
|
||||
<header>
|
||||
{{ with .Site.Params.sidebar.avatar }}
|
||||
{{ if .enabled }}
|
||||
{{- $avatar := partial "helper/image" (dict "Image" . "Resources" resources) -}}
|
||||
<figure class="site-avatar">
|
||||
<a href="{{ .Site.BaseURL | relLangURL }}">
|
||||
{{ if not .local }}
|
||||
<img src="{{ .src }}" width="300" height="300" class="site-logo" loading="lazy" alt="Avatar">
|
||||
{{ else }}
|
||||
{{ $avatar := resources.Get (.src) }}
|
||||
|
||||
{{ if $avatar }}
|
||||
{{ $avatarResized := $avatar.Resize "300x" }}
|
||||
<img src="{{ $avatarResized.RelPermalink }}" width="{{ $avatarResized.Width }}"
|
||||
height="{{ $avatarResized.Height }}" class="site-logo" loading="lazy" alt="Avatar">
|
||||
{{ else }}
|
||||
{{ errorf "Failed loading avatar from %q" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<a href="{{ $.Site.Home.RelPermalink }}">
|
||||
<img src="{{ $avatar.Permalink }}"
|
||||
width="{{ $avatar.Width }}"
|
||||
height="{{ $avatar.Height }}"
|
||||
class="site-logo"
|
||||
loading="lazy"
|
||||
alt="Avatar">
|
||||
</a>
|
||||
{{ with $.Site.Params.sidebar.emoji }}
|
||||
<span class="emoji">{{ . }}</span>
|
||||
{{ end }}
|
||||
</figure>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<div class="site-meta">
|
||||
<h1 class="site-name"><a href="{{ .Site.BaseURL | relLangURL }}">{{ .Site.Title }}</a></h1>
|
||||
<h1 class="site-name"><a href="{{ .Site.Home.RelPermalink }}">{{ .Site.Title }}</a></h1>
|
||||
<h2 class="site-description">{{ .Site.Params.sidebar.subtitle }}</h2>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -9,9 +9,8 @@
|
||||
</div>
|
||||
<h2 class="widget-title section-title">{{ T "widget.archives.title" }}</h2>
|
||||
|
||||
{{ $pages := where $context.Site.RegularPages "Type" "in" $context.Site.Params.mainSections }}
|
||||
{{ $filtered := where $pages "Params.hidden" "!=" true }}
|
||||
{{ $archives := $filtered.GroupByDate "2006" }}
|
||||
{{ $pages := partial "helper/pages.html" $context.Site.Home }}
|
||||
{{ $archives := $pages.GroupByDate "2006" }}
|
||||
|
||||
<div class="widget-archive--list">
|
||||
{{ range $index, $item := first (add $limit 1) ($archives) }}
|
||||
|
||||
@@ -1,16 +1,2 @@
|
||||
{{- $context := .Context -}}
|
||||
{{- $limit := default 10 .Params.limit -}}
|
||||
<section class="widget tagCloud">
|
||||
<div class="widget-icon">
|
||||
{{ partial "helper/icon" "categories" }}
|
||||
</div>
|
||||
<h2 class="widget-title section-title">{{ T "widget.categoriesCloud.title" }}</h2>
|
||||
|
||||
<div class="tagCloud-tags">
|
||||
{{ range first $limit $context.Site.Taxonomies.categories.ByCount }}
|
||||
<a href="{{ .Page.RelPermalink }}" class="font_size_{{ .Count }}">
|
||||
{{ .Page.Title }}
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
{{ $params := merge (dict "taxonomy" "categories" "title" (T "widget.categoriesCloud.title") "icon" "categories") .Params }}
|
||||
{{ partial "widget/taxonomy" (dict "Context" .Context "Params" $params) }}
|
||||
@@ -1,16 +1,2 @@
|
||||
{{- $context := .Context -}}
|
||||
{{- $limit := default 10 .Params.limit -}}
|
||||
<section class="widget tagCloud">
|
||||
<div class="widget-icon">
|
||||
{{ partial "helper/icon" "tag" }}
|
||||
</div>
|
||||
<h2 class="widget-title section-title">{{ T "widget.tagCloud.title" }}</h2>
|
||||
|
||||
<div class="tagCloud-tags">
|
||||
{{ range first $limit $context.Site.Taxonomies.tags.ByCount }}
|
||||
<a href="{{ .Page.RelPermalink }}">
|
||||
{{ .Page.Title }}
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
{{ $params := merge (dict "taxonomy" "tags" "title" (T "widget.tagCloud.title") "icon" "tag") .Params }}
|
||||
{{ partial "widget/taxonomy" (dict "Context" .Context "Params" $params) }}
|
||||
@@ -0,0 +1,39 @@
|
||||
{{- $context := .Context -}}
|
||||
|
||||
{{- if not .Params.taxonomy -}}
|
||||
{{- warnf "Taxonomy field is required" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $taxonomy := index $context.Site.Taxonomies .Params.taxonomy -}}
|
||||
{{- if not $taxonomy -}}
|
||||
{{- warnf "Taxonomy %s not found" .Params.taxonomy -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $taxonomyPage := $taxonomy.Page -}}
|
||||
{{- $widgetTitle := .Params.title -}}
|
||||
{{- $limit := default 10 .Params.limit -}}
|
||||
{{- $icon := default .Params.taxonomy .Params.icon -}}
|
||||
{{- $showLink := default false .Params.showLink -}}
|
||||
|
||||
<section class="widget tagCloud">
|
||||
<div class="widget-icon">
|
||||
{{ partial "helper/icon" $icon }}
|
||||
</div>
|
||||
<h2 class="widget-title section-title">
|
||||
{{ if $showLink }}
|
||||
<a href="{{ $taxonomyPage.RelPermalink }}">
|
||||
{{ $widgetTitle }}
|
||||
</a>
|
||||
{{ else }}
|
||||
{{ $widgetTitle }}
|
||||
{{ end }}
|
||||
</h2>
|
||||
|
||||
<div class="tagCloud-tags">
|
||||
{{ range first $limit $taxonomy.ByCount }}
|
||||
<a href="{{ .Page.RelPermalink }}">
|
||||
{{ .Page.Title }}
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,10 +1,11 @@
|
||||
{{- $src := .Get "src" | default (.Get 0) -}}
|
||||
<div class="video-wrapper">
|
||||
<video
|
||||
controls
|
||||
src="{{- $src -}}"
|
||||
{{ with .Get "poster" }}poster="{{- . -}}"{{ end }}
|
||||
{{ with .Get "autoplay" }}autoplay{{ end }}
|
||||
controls
|
||||
src="{{- $src -}}"
|
||||
{{ with .Get "poster" }}poster="{{- . -}}"{{ end }}
|
||||
{{ if eq (.Get "autoplay") "true" }}autoplay{{ end }}
|
||||
{{ if eq (.Get "muted") "true" }}muted{{ end }}
|
||||
>
|
||||
<p>
|
||||
Your browser doesn't support HTML5 video. Here is a
|
||||
|
||||
@@ -15,10 +15,9 @@
|
||||
{{ end }}
|
||||
</header>
|
||||
|
||||
{{ $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections }}
|
||||
{{ $filtered := where $pages "Params.hidden" "!=" true }}
|
||||
{{ $pages := partial "helper/pages.html" .Site.Home }}
|
||||
|
||||
{{ range $filtered.GroupByDate "2006" }}
|
||||
{{ range $pages.GroupByDate "2006" }}
|
||||
{{ $id := lower (replace .Key " " "-") }}
|
||||
<div class="archives-group" id="{{ $id }}">
|
||||
<h2 class="archives-date section-title"><a href="{{ $.RelPermalink }}#{{ $id }}">{{ .Key }}</a></h2>
|
||||
|
||||
+2
-6
@@ -29,14 +29,10 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{- $pages := partial "helper/pages.html" . -}}
|
||||
{{- $subsections := .Sections -}}
|
||||
{{- $pages := .Pages | complement $subsections -}}
|
||||
{{- $pages = where $pages "Params.hidden" "!=" true -}}
|
||||
|
||||
{{- if eq (len $pages) 0 -}}
|
||||
{{/* If there are no normal pages, display subsections in list style, with pagination */}}
|
||||
{{/* This happens with taxonomies like categories or tags */}}
|
||||
{{- $pages = $subsections -}}
|
||||
{{- if and (eq (len $pages) (len $subsections)) (eq $pages $subsections) -}}
|
||||
{{- $subsections = slice -}}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
{{- $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections -}}
|
||||
{{- $filtered := where $pages "Params.hidden" "!=" true -}}
|
||||
|
||||
{{- $pages := partial "helper/pages.html" .Site.Home -}}
|
||||
{{- $result := slice -}}
|
||||
|
||||
{{- range $filtered -}}
|
||||
{{- range $pages -}}
|
||||
{{- $data := dict "title" .Title "date" .Date "permalink" .RelPermalink "content" (.Plain) -}}
|
||||
|
||||
{{- $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources) -}}
|
||||
|
||||
+48
-33
@@ -1,48 +1,63 @@
|
||||
{{- $pctx := . -}}
|
||||
{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}}
|
||||
{{- $pages := slice -}}
|
||||
{{- if or $.IsHome $.IsSection -}}
|
||||
{{- $pages = $pctx.RegularPages -}}
|
||||
{{- else -}}
|
||||
{{- $pages = $pctx.Pages -}}
|
||||
{{- $authorEmail := "" -}}
|
||||
{{- with site.Params.author -}}
|
||||
{{- if reflect.IsMap . -}}
|
||||
{{- with .email -}}
|
||||
{{- $authorEmail = . -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end }}
|
||||
|
||||
{{- $authorName := "" -}}
|
||||
{{- with site.Params.author -}}
|
||||
{{- if reflect.IsMap . -}}
|
||||
{{- with .name -}}
|
||||
{{- $authorName = . -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- $authorName = . -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- $pages := where $pages "Params.hidden" "!=" true -}}
|
||||
|
||||
{{- $pages := partial "helper/pages.html" . -}}
|
||||
{{- $limit := .Site.Config.Services.RSS.Limit -}}
|
||||
{{- if ge $limit 1 -}}
|
||||
{{- $pages = $pages | first $limit -}}
|
||||
{{- $pages = $pages | first $limit -}}
|
||||
{{- end -}}
|
||||
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
|
||||
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML -}}
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}</title>
|
||||
<title>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} on {{ end }}{{ .Site.Title }}{{ end }}</title>
|
||||
<link>{{ .Permalink }}</link>
|
||||
<description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}</description>
|
||||
<generator>Hugo -- gohugo.io</generator>{{ with .Site.LanguageCode }}
|
||||
<language>{{.}}</language>{{end}}{{ with .Site.Params.Author.email }}
|
||||
<managingEditor>{{.}}{{ with $.Site.Params.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Params.Author.email }}
|
||||
<webMaster>{{.}}{{ with $.Site.Params.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
|
||||
<copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
|
||||
<description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{ . }} {{ end }}{{ end }}on {{ .Site.Title }}</description>
|
||||
<generator>Hugo -- gohugo.io</generator>
|
||||
<language>{{ site.Language.LanguageCode }}</language>{{ with $authorEmail }}
|
||||
<managingEditor>{{.}}{{ with $authorName }} ({{ . }}){{ end }}</managingEditor>{{ end }}{{ with $authorEmail }}
|
||||
<webMaster>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</webMaster>{{ end }}{{ with .Site.Copyright }}
|
||||
<copyright>{{ . }}</copyright>{{ end }}{{ if not .Date.IsZero }}
|
||||
<lastBuildDate>{{ (index $pages.ByLastmod.Reverse 0).Lastmod.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
|
||||
{{- with .OutputFormats.Get "RSS" -}}
|
||||
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
|
||||
{{- end -}}
|
||||
{{ range $pages }}
|
||||
{{- $content := safeHTML (.Summary | html) -}}
|
||||
{{- if .Site.Params.rssFullContent -}}
|
||||
{{- $content = safeHTML (.Content | html) -}}
|
||||
{{- end -}}
|
||||
{{- range $pages -}}
|
||||
<item>
|
||||
<title>{{ .Title }}</title>
|
||||
<link>{{ .Permalink }}</link>
|
||||
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
|
||||
{{ with .Site.Params.Author.email }}<author>{{.}}{{ with $.Site.Params.Author.name }} ({{.}}){{end}}</author>{{end}}
|
||||
<guid>{{ .Permalink }}</guid>
|
||||
<description>
|
||||
{{- $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources) -}}
|
||||
{{- if $image -}}
|
||||
{{ "<" | html }}img src="{{ $image.permalink | absURL }}" alt="Featured image of post {{ .Title }}" {{ "/>" | html}}
|
||||
{{- end -}}{{ $content }}</description>
|
||||
<title>{{ .Title }}</title>
|
||||
<link>{{ .Permalink }}</link>
|
||||
<pubDate>{{ .PublishDate.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
|
||||
{{- with $authorEmail }}<author>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</author>{{ end }}
|
||||
<guid>{{ .Permalink }}</guid>
|
||||
<description>
|
||||
{{- $content := .Summary -}}
|
||||
{{- if .Site.Params.RSSFullContent -}}
|
||||
{{- $content = .Content -}}
|
||||
{{- end -}}
|
||||
{{- $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources) -}}
|
||||
{{- if $image -}}
|
||||
{{- $imgTag := printf "<img src=\"%s\" alt=\"Featured image of post %s\" />" ($image.permalink | absURL) .Title -}}
|
||||
{{- $content = printf "%s%s" $imgTag $content -}}
|
||||
{{- end -}}
|
||||
{{- $content | transform.XMLEscape | safeHTML -}}
|
||||
</description>
|
||||
</item>
|
||||
{{ end }}
|
||||
{{- end -}}
|
||||
</channel>
|
||||
</rss>
|
||||
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
name = "hugo-theme-stack"
|
||||
name = "hugo-theme-stack"
|
||||
compatibility_date = "2026-01-25"
|
||||
|
||||
[build]
|
||||
command = "chmod a+x ./.ci/build.sh && ./.ci/build.sh"
|
||||
command = "chmod a+x ./.ci/build.sh && ./.ci/build.sh"
|
||||
|
||||
[assets]
|
||||
directory = "demo/public"
|
||||
not_found_handling = "404-page"
|
||||
directory = "demo/public"
|
||||
not_found_handling = "404-page"
|
||||
|
||||
Reference in New Issue
Block a user