Compare commits

...
Author SHA1 Message Date
JimmyandGitHub 3a0d70ebbd feat(i18n): add i18n support for Markdown alert (#1269)
* docs: add blockquote with custom alert title

* feat: add alert types to i18n and update blockquote rendering

* fix: wrong translation key

* feat: add alert title translation for all supported languages

Done by AI
2026-02-18 20:07:59 +01:00
470295171e feat: add markdown alerts support (#1221)
* Markdown alerts

* refactor(alert): move alert color to variable.scss and simplify the style

* style: fix indent in render-blockquote

* feat(alert): add customizable alert icons for different alert types

* doc: add example usage of markdown alert

---------

Co-authored-by: Jimmy Cai <jimmy@cai.im>
2026-02-18 19:43:34 +01:00
JimmyandGitHub 2058c1ff6d refactor: create a generic taxonomy widget (#1268)
* refactor: introduce a generic taxonomy widget and refactor existing category and tag cloud widgets to use it.

* feat: Implement optional title linking for taxonomy widgets and enable parameter customization for taxonomy partials.

* refactor: remove fallback to taxonomy page title for widget display

Because this field always exists. I think it's better to let user customize via widget params

* Update layouts/_partials/widget/taxonomy.html

* refactor: check for .Params.taxonomy existence before accessing
2026-02-18 18:56:15 +01:00
d6448148cf feat: add option to set a custom canonical url (#1267)
* add option to set a custom canonical url

* style: use `with` to load cannonicalurl

* style: change to canonicalUrl

Frontmatter is case-insensitive so it does not matter

---------

Co-authored-by: FarisZR <35614734+FarisZR@users.noreply.github.com>
2026-02-17 20:01:23 +01:00
JimmyandGitHub 475c956fd6 style: format TOML files (#1266)
* style: format TOML files

* style: format wrangler.toml
2026-02-17 19:20:09 +01:00
5e57b5ad29 feat(comment): add support for Comentario (#1088)
* Add support for comentario.

* feat: add Comentario client configuration options in TOML

* fix: format theme attribute in comentario comments component

---------

Co-authored-by: Jimmy Cai <jimmy@cai.im>
2026-02-17 19:09:18 +01:00
JimmyandGitHub b4e44fda57 fix: enhance image caption handling by using title attribute if available (#1265)
* fix: enhance image caption handling by using title attribute if available

* feat: support markdown syntax inside image caption
2026-02-17 18:38:06 +01:00
JimmyandGitHub 3806cc9230 fix: set width of left and right sidebars to 100% (#1264)
This prevents the width from changing when the left or right sidebar does not have enough content to stretch to 100%.
2026-02-17 18:10:33 +01:00
0cefe05973 fix: preload Google Fonts to reduce layout shift (#1215)
* fix: preload Google Fonts to reduce layout shift

Move font loading from footer JS to head with preconnect and preload
hints. This reduces CLS by ensuring fonts load earlier in the page
lifecycle instead of being dynamically inserted after footer loads.

* style: reorder custom font inclusion in head template

---------

Co-authored-by: delize <4028612+delize@users.noreply.github.com>
Co-authored-by: Jimmy Cai <jimmy@cai.im>
2026-02-17 18:03:53 +01:00
bc1b55e60a fix: improve Mermaid diagram implementation (#1214)
* fix: improve Mermaid diagram implementation (#1213)

This commit addresses multiple issues with Mermaid diagram rendering
and adds significant improvements to the user experience.

- Fix `<br/>` tags rendering as literal text instead of line breaks
- Fix flash of raw mermaid code before diagram renders
- Fix Gantt chart rendering issues in dark mode
- Fix diagrams briefly showing during theme switch re-render
- Fix expand modal showing diagram left-aligned before centering

- Add fullscreen expand modal with pan/zoom functionality
- Add toolbar with zoom controls (zoom in/out, reset, fit to screen)
- Add keyboard support (Escape to close modal)
- Add per-diagram transparent background via `%%transparent%%` directive
- Add comprehensive configuration options:
  - `look`: classic or handDrawn (sketch style)
  - `lightTheme`/`darkTheme`: theme selection per mode
  - `lightThemeVariables`/`darkThemeVariables`: custom theme colors
  - `securityLevel`, `htmlLabels`, `maxTextSize`, `maxEdges`
  - `fontSize`, `fontFamily`, `curve`, `logLevel`

- Switch from ESM to UMD bundle for faster initial load (~1s vs 2s+)
- Lazy-load panzoom library only when expand modal is opened
- Pre-render alternate theme during idle time for instant theme switching
- Cache rendered diagrams to avoid re-rendering on theme toggle

- Move all inline CSS to article.scss using SCSS nesting
- Extract helper functions for better maintainability
- Use data attributes and event delegation for cleaner handlers
- Reduce mermaid.html from 411 to 177 lines (57% reduction)

- Add comprehensive mermaid-diagrams example post with all diagram types
- Document all configuration options in hugo.yaml

* refactor: extract mermaid inline JS to TypeScript module

Move ~155 lines of inline JavaScript from mermaid.html partial into
assets/ts/mermaid.ts. The partial shrinks from 179 to 36 lines and now
only contains modal HTML markup and Hugo Pipes build/import logic.

- Deduplicate initWithTheme() and renderOffscreen() helpers
- Enable minification via Hugo Pipes js.Build in production
- Remove user-configurable mermaid version from params.toml

---------

Co-authored-by: delize <4028612+delize@users.noreply.github.com>
2026-02-17 17:55:03 +01:00
Jimmy Cai 99a8fd9013 docs: Update demo and documentation URLs 2026-02-17 13:48:49 +01:00
JimmyandGitHub 7f47cdf165 fix: check if favicon exists before accessing permalink (#1259) 2026-02-01 13:11:42 +01:00
JimmyandGitHub 768e7de697 refactor: update RSS template to the latest one given by Hugo, and change page output logic (#1257)
* refactor: rename `home.rss.xml` to `rss.xml`

* refactor: update RSS template to the latest one given by Hugo.

Output pages with the same logic as in normal site by using `helper/pages`
2026-01-30 15:27:32 +01:00
JimmyandGitHub 00069b2c45 feat: add muted attribute and refine autoplay condition for video shortcode (#1256) 2026-01-30 15:09:33 +01:00
JimmyandGitHub 31022581d8 refactor: image logic in page (#1255)
* fix: add SVG check in helper/image before accessing .Width and .Height

* refactor: drop image resizing logic in render-image, and use helper/image to process image

* fix: render-image $permalink not correct
2026-01-30 15:07:14 +01:00
Jimmy Cai ff2e308445 fix: correct pluralization of 'minute' in English reading time. 2026-01-27 14:04:16 +01:00
JimmyandGitHub 017fbf982d chore: update Traditional Chinese localization files (#1251)
closes https://github.com/CaiJimmy/hugo-theme-stack/issues/1222
2026-01-27 12:49:44 +01:00
JimmyandGitHub bc54cecf0a refactor: centralize page filtering logic, and allow sort by lastmod (#1250)
* refactor: migrate page visibility control from `Params.hidden` to the new `build.list` parameter.

* refactor: centralize page filtering logic into a new `helper/pages.html` partial

* feat: introduce `SortBy` parameter to enable sorting pages by last modified date

* refactor: extract page sorting logic into a new `pages-sort.html` partial
2026-01-27 12:34:15 +01:00
JimmyandGitHub deceb17144 refactor: migrate page visibility control from Params.hidden to the new build.list parameter. (#1249)
* refactor: migrate page visibility control from `Params.hidden` to the new `build.list` parameter.

* Rename rss.xml to home.rss.xml
2026-01-27 12:00:40 +01:00
Jimmy Cai dc0d105e7c fix: correct global site context access for the home page link in the sidebar 2026-01-27 00:05:05 +01:00
JimmyandGitHub 33b7de9457 refactor: use .Site.Home.RelPermalink for home page links in the sidebar (#1248)
refactor: use `.Site.Home.RelPermalink` for home page links in the sidebar.
2026-01-26 23:59:40 +01:00
JimmyandGitHub 7d4bfa9fc0 refactor: simplify sidebar avatar configuration and don't resize (#1247) 2026-01-26 23:58:18 +01:00
75 changed files with 1733 additions and 324 deletions
+3 -2
View File
@@ -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
+2 -1
View File
@@ -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
---
+2
View File
@@ -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
+164
View File
@@ -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;
}
+28
View File
@@ -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;
+11
View File
@@ -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
View File
@@ -12,6 +12,15 @@ const wrap = (figures: HTMLElement[]) => {
}
}
const unescapeHtml = (html: string) => {
/// Replace special HTML entities with their corresponding characters
return html.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&#39;/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);
}
}
+235
View File
@@ -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
View File
@@ -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 = ""
+4 -6
View File
@@ -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"
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+2 -5
View File
@@ -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 -2
View File
@@ -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 出箱即用地支持它们。
+603
View File
@@ -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)
+7
View File
@@ -22,6 +22,13 @@ darkMode = "الوضع الداكن"
one = "تُقرأ خلال دقيقة"
other = "تُقرأ خلال {{ .Count }} دقائق"
[article.alert]
note = "ملاحظة"
tip = "نصيحة"
important = "مهم"
warning = "تحذير"
caution = "تنبيه"
[notFound]
title = "غير موجود"
subtitle = "تعذر العثور على الصفحة المطلوبة."
+7
View File
@@ -23,6 +23,13 @@ darkMode = "Цёмны рэжым"
tableOfContents = "Змест"
readingTime = "Час чытання: {{ .Count }} хв."
[article.alert]
note = "Заўвага"
tip = "Парада"
important = "Важна"
warning = "Папярэджанне"
caution = "Асцярога"
[notFound]
title = "Не знойдзена"
subtitle = "Запытваемая старонка не існуе"
+7
View File
@@ -22,6 +22,13 @@ darkMode = "Тъмен Режим"
one = "{{ .Count }} minute read"
other = "{{ .Count }} minute read"
[article.alert]
note = "Бележка"
tip = "Съвет"
important = "Важно"
warning = "Предупреждение"
caution = "Внимание"
[notFound]
title = "Не е намерено"
subtitle = "Страницата която търсите не е открита"
+7
View File
@@ -22,6 +22,13 @@ darkMode = "ডার্ক মোড"
one = "{{ .Count }} মিনিটে পড়া যাবে"
other = "{{ .Count }} মিনিটে পড়া যাবে"
[article.alert]
note = "টীকা"
tip = "পরামর্শ"
important = "গুরুত্বপূর্ণ"
warning = "সতর্কতা"
caution = "সাবধানতা"
[notFound]
title = "পাওয়া যায়নি"
subtitle = "এই পাতাটি বিদ্যমান নেই"
+7
View File
@@ -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"
+7
View File
@@ -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"
+7
View File
@@ -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"
+7
View File
@@ -22,6 +22,13 @@ darkMode = "Σκοτεινό θέμα"
one = "{{ .Count }} λεπτό ανάγνωσης"
other = "{{ .Count }} λεπτά ανάγνωσης"
[article.alert]
note = "Σημείωση"
tip = "Συμβουλή"
important = "Σημαντικό"
warning = "Προειδοποίηση"
caution = "Προσοχή"
[notFound]
title = "Δε βρέθηκε"
subtitle = "Η σελίδα δε βρέθηκε."
+8 -1
View File
@@ -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"
+7
View File
@@ -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"
+7
View File
@@ -22,6 +22,13 @@ darkMode = "حالت شب"
one = "مطالعه در {{ .Count }} دقیقه"
other = "مطالعه در {{ .Count }} دقیقه"
[article.alert]
note = "یادداشت"
tip = "نکته"
important = "مهم"
warning = "هشدار"
caution = "احتیاط"
[notFound]
title = "یافت نشد"
subtitle = "این صحه وجود ندارد"
+7
View File
@@ -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."
+7
View File
@@ -22,6 +22,13 @@ darkMode = "डार्क मोड"
one = "{{ .Count }} मिनट पढ़ें"
other = "{{ .Count }} मिनट पढ़ें"
[article.alert]
note = "नोट"
tip = "सलाह"
important = "महत्वपूर्ण"
warning = "चेतावनी"
caution = "सावधानी"
[notFound]
title = "404 नहीं मिला।"
subtitle = "यह पृष्ठ मौजूद नहीं है।"
+7
View File
@@ -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"
+7
View File
@@ -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."
+7
View File
@@ -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."
+7
View File
@@ -13,6 +13,13 @@ darkMode = "ダークモード"
lastUpdatedOn = "最終更新"
readingTime = "読了時間: {{ .Count }}分"
[article.alert]
note = "注記"
tip = "ヒント"
important = "重要"
warning = "警告"
caution = "注意"
[notFound]
title = "404 Not Found"
subtitle = "指定されたページは存在しません。"
+7
View File
@@ -22,6 +22,13 @@ darkMode = "다크 모드"
one = "{{ .Count }} 분 정도"
other = "{{ .Count }} 분 정도"
[article.alert]
note = "참고"
tip = "팁"
important = "중요"
warning = "경고"
caution = "주의"
[notFound]
title = "찾을 수 없음"
subtitle = "페이지를 찾을 수 없습니다."
+7
View File
@@ -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."
+7
View File
@@ -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"
+7
View File
@@ -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"
+7
View File
@@ -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."
+7
View File
@@ -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."
+7
View File
@@ -23,6 +23,13 @@ darkMode = "Тёмный режим"
tableOfContents = "Содержание"
readingTime = "Время чтения: {{ .Count }} мин."
[article.alert]
note = "Примечание"
tip = "Совет"
important = "Важно"
warning = "Предупреждение"
caution = "Осторожно"
[notFound]
title = "Не найдено"
subtitle = "Запрашиваемая страница не существует"
+7
View File
@@ -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"
+7
View File
@@ -22,6 +22,13 @@ darkMode = "ธีมมืด"
one = "น่าจะใช้เวลา {{ .Count }} นาทีในการอ่าน"
other = "น่าจะใช้เวลา {{ .Count }} นาทีในการอ่าน"
[article.alert]
note = "หมายเหตุ"
tip = "เคล็ดลับ"
important = "สำคัญ"
warning = "คำเตือน"
caution = "ระวัง"
[notFound]
title = "ไม่พบหัวข้อ"
subtitle = "ไม่พบหน้านี้ในระบบ"
+7
View File
@@ -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."
+7
View File
@@ -23,6 +23,13 @@ darkMode = "Темна тема"
one = "Час читання: {{ .Count }} хв"
other = "Час читання: {{ .Count }} хв"
[article.alert]
note = "Примітка"
tip = "Порада"
important = "Важливо"
warning = "Попередження"
caution = "Обережно"
[notFound]
title = "Не знайдено"
subtitle = "Ця сторінка не існує"
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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 錯誤"
+7
View File
@@ -13,6 +13,13 @@ darkMode = "暗色模式"
lastUpdatedOn = "最后更新于"
readingTime = "阅读时长: {{ .Count }} 分钟"
[article.alert]
note = "备注"
tip = "提示"
important = "重要"
warning = "警告"
caution = "注意"
[notFound]
title = "404 错误"
subtitle = "页面不存在"
+26
View File
@@ -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 -}}
+10 -27
View File
@@ -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
View File
@@ -1,3 +1,2 @@
{{ partialCached "footer/components/script.html" . }}
{{ partialCached "footer/components/custom-font.html" . }}
{{ partial "footer/custom.html" . }}
+4
View File
@@ -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">
+9 -4
View File
@@ -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" . -}}
+28 -29
View File
@@ -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 }}
+7
View File
@@ -0,0 +1,7 @@
{{- $pages := .Pages -}}
{{- if eq .Context.Site.Params.SortBy "lastmod" -}}
{{- $pages = $pages.ByLastmod.Reverse -}}
{{- end -}}
{{- return $pages -}}
+16
View File
@@ -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 -}}
+5 -25
View File
@@ -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 -}}
+9 -17
View File
@@ -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>
+2 -3
View File
@@ -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) }}
+2 -16
View File
@@ -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) }}
+2 -16
View File
@@ -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) }}
+39
View File
@@ -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>
+5 -4
View File
@@ -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
+2 -3
View File
@@ -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
View File
@@ -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 -}}
+2 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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"