Compare commits

...
Author SHA1 Message Date
Jimmy Cai 22b8d091f4 doc(demo): disable cookie consent 2026-02-19 17:50:48 +01:00
JimmyandGitHub bd7a34966c refactor(sidebar): drop support for .Pre and remove old codes (#1271)
* refactor(sidebar): drop support for .Pre and remove old codes

* Simplify
2026-02-19 17:49:50 +01:00
JimmyandGitHub 2be5b48324 feat(list): ability to show article tags on homepage (#1270)
* feat(list): ability to show article tags on homepage

closes https://github.com/CaiJimmy/hugo-theme-stack/issues/880

* Disable showTags by default
2026-02-19 17:45:09 +01:00
4ad88a327e feat: add GDPR cookie consent banner (#1216)
* feat: add GDPR cookie consent banner

Add a GDPR-compliant cookie consent banner that gates analytics (Google
Analytics) and functional cookies (comments) until user consent is given.

Features:
- Theme-consistent styling using CSS variables (light/dark mode)
- Settings panel with category toggles (necessary, analytics, functional)
- Consent stored in cookie for 365 days
- Google Analytics only loads after analytics consent
- Comments only load after functional consent
- Translations for all 32 supported languages
- Accessible with proper ARIA attributes and focus management

Configuration in hugo.yaml:
  params:
    cookies:
      enabled: true
      categories:
        analytics: true
        functional: true

When cookies.enabled is false, scripts load normally without consent gating.

Closes #412

* refactor: move consent-gated GA to cookies/analytics.html

Move Google Analytics consent logic to avoid shadowing Hugo's internal
`_internal/google_analytics.html` template.

Changes:
- Rename google_analytics.html → cookies/analytics.html
- Update head.html to conditionally load:
  - cookies.enabled=true: cookies/analytics.html (consent-gated)
  - cookies.enabled=false: Hugo's _internal/google_analytics.html
- Align cookies/analytics.html with Hugo's internal template:
  - Support Privacy.GoogleAnalytics.Disable setting
  - Support Privacy.GoogleAnalytics.RespectDoNotTrack setting
  - Add UA- prefix deprecation warning

Note: Custom JS is required for consent-gated GA because Hugo templates
render at build time, but consent happens at runtime. We cannot call
Hugo's internal template after the user grants consent - we must
dynamically inject the GA script via JavaScript.

* style: indent cookies/analytics.html

* refactor: don't use default, put default configurations in params.toml

* fix: don't use `description` as translation key because it's reserved and will throw error

* fix: remove footer/components/custom-font.html call

* fix: banner showSettings key

* style: indent comments/include

* Add missing article.alert translation back

* style: format i18n toml files

* style: indent head/head.html

* style adjustment

* style: add missing -

* style: format demo/config/params.toml

* style: remove redundant comments from cookies.scss

* style: update box-shadow for cookie banner content

---------

Co-authored-by: delize <4028612+delize@users.noreply.github.com>
Co-authored-by: Jimmy Cai <jimmy@cai.im>
2026-02-19 17:21:25 +01:00
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
86 changed files with 3161 additions and 335 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
+168 -2
View File
@@ -82,7 +82,8 @@
}
.article-time,
.article-translations {
.article-translations,
.article-header-tags {
display: flex;
color: var(--card-text-color-tertiary);
gap: 15px;
@@ -112,7 +113,8 @@
flex-wrap: wrap;
}
.article-translations {
.article-translations,
.article-header-tags {
& > div {
flex-wrap: wrap;
}
@@ -266,3 +268,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;
}
+199
View File
@@ -0,0 +1,199 @@
.cookie-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 9999;
padding: var(--container-padding);
&[aria-hidden="true"] {
display: none;
}
}
.cookie-banner__content {
max-width: 900px;
margin: 0 auto;
background: var(--card-background);
border-radius: var(--card-border-radius);
box-shadow: var(--shadow-l3);
padding: var(--card-padding);
display: flex;
flex-direction: column;
gap: 15px;
@include respond(md) {
flex-direction: row;
flex-wrap: wrap;
align-items: center;
gap: 20px;
}
}
.cookie-banner__text {
flex: 1;
min-width: 200px;
strong {
display: block;
color: var(--card-text-color-main);
font-size: 1.6rem;
margin-bottom: 5px;
}
p {
color: var(--card-text-color-secondary);
font-size: 1.4rem;
line-height: 1.6;
margin: 0;
}
}
.cookie-banner__actions {
display: flex;
gap: 10px;
flex-shrink: 0;
}
.cookie-banner__settings-link {
width: auto;
align-self: flex-start;
background: none;
border: none;
color: var(--accent-color);
font-size: 1.3rem;
cursor: pointer;
padding: 5px;
&:hover {
text-decoration: underline;
}
@include respond(md) {
width: auto;
margin-left: auto;
align-self: center;
}
}
.cookie-btn {
padding: 10px 20px;
border-radius: var(--tag-border-radius);
font-size: 1.4rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
border: none;
&--primary {
background: var(--accent-color);
color: var(--accent-color-text);
&:hover {
background: var(--accent-color-darker);
}
}
&--secondary {
background: transparent;
color: var(--card-text-color-main);
border: 1.5px solid var(--card-separator-color);
&:hover {
background: var(--card-background-selected);
}
}
}
// Cookie settings panel
.cookie-settings {
padding-top: 20px;
border-top: 1.5px solid var(--card-separator-color);
&[aria-hidden="true"] {
display: none;
}
h3 {
color: var(--card-text-color-main);
font-size: 1.6rem;
margin: 0 0 15px 0;
}
&__actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
}
.cookie-category {
padding: 15px 0;
border-bottom: 1px solid var(--card-separator-color);
&:last-of-type {
border-bottom: none;
}
label {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
input[type="checkbox"] {
width: 18px;
height: 18px;
accent-color: var(--accent-color);
cursor: pointer;
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
strong {
color: var(--card-text-color-main);
font-size: 1.5rem;
}
}
p {
color: var(--card-text-color-secondary);
font-size: 1.3rem;
margin: 8px 0 0 28px;
line-height: 1.5;
}
}
// Footer link for reopening settings
.cookie-settings-link {
display: inline-block;
color: var(--body-text-color);
font-size: 1.2rem;
cursor: pointer;
background: none;
border: none;
padding: 0;
&:hover {
color: var(--accent-color);
text-decoration: underline;
}
}
// Comments placeholder when consent not given
.consent-placeholder {
background: var(--card-background);
border-radius: var(--card-border-radius);
padding: var(--card-padding);
text-align: center;
color: var(--card-text-color-secondary);
font-size: 1.4rem;
p {
margin: 0 0 15px 0;
}
}
+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;
+1
View File
@@ -23,6 +23,7 @@
@import "partials/layout/list.scss";
@import "partials/layout/404.scss";
@import "partials/layout/search.scss";
@import "partials/cookies.scss";
@import "general.scss";
@import "custom.scss";
+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;
+221
View File
@@ -0,0 +1,221 @@
interface ConsentState {
necessary: boolean;
analytics: boolean;
functional: boolean;
timestamp: number;
}
class CookieConsent {
private static COOKIE_NAME = 'cookie_consent';
private static COOKIE_DAYS = 365;
private state: ConsentState | null = null;
private banner: HTMLElement | null = null;
private settingsPanel: HTMLElement | null = null;
constructor() {
this.banner = document.getElementById('cookie-consent-banner');
this.settingsPanel = document.getElementById('cookie-settings-panel');
this.state = this.loadState();
if (!this.state && this.banner) {
this.showBanner();
}
this.bindEvents();
this.dispatchConsentEvent();
}
private loadState(): ConsentState | null {
const cookie = document.cookie
.split('; ')
.find(row => row.startsWith(CookieConsent.COOKIE_NAME + '='));
if (!cookie) return null;
try {
return JSON.parse(decodeURIComponent(cookie.split('=')[1]));
} catch {
return null;
}
}
private saveState(): void {
if (!this.state) return;
const expires = new Date();
expires.setDate(expires.getDate() + CookieConsent.COOKIE_DAYS);
document.cookie = `${CookieConsent.COOKIE_NAME}=${encodeURIComponent(JSON.stringify(this.state))}; expires=${expires.toUTCString()}; path=/; SameSite=Lax`;
}
private showBanner(): void {
if (this.banner) {
this.banner.removeAttribute('aria-hidden');
}
}
private hideBanner(): void {
if (this.banner) {
// Blur any focused element inside the banner before hiding
const activeElement = document.activeElement as HTMLElement;
if (activeElement && this.banner.contains(activeElement)) {
activeElement.blur();
}
this.banner.setAttribute('aria-hidden', 'true');
}
this.hideSettings();
}
private showSettings(): void {
if (this.settingsPanel) {
this.settingsPanel.removeAttribute('aria-hidden');
// Restore checkbox states from current state or defaults
const checkboxes = this.settingsPanel.querySelectorAll('input[data-cookie-category]');
checkboxes.forEach((cb) => {
const input = cb as HTMLInputElement;
const category = input.dataset.cookieCategory as keyof ConsentState;
if (category && this.state && typeof this.state[category] === 'boolean') {
input.checked = this.state[category] as boolean;
} else {
input.checked = false;
}
});
}
}
private hideSettings(): void {
if (this.settingsPanel) {
// Blur any focused element inside the settings panel before hiding
const activeElement = document.activeElement as HTMLElement;
if (activeElement && this.settingsPanel.contains(activeElement)) {
activeElement.blur();
}
this.settingsPanel.setAttribute('aria-hidden', 'true');
}
}
private bindEvents(): void {
document.addEventListener('click', (e) => {
const target = e.target as HTMLElement;
const action = target.dataset.cookieAction;
if (!action) return;
switch (action) {
case 'accept':
this.acceptAll();
break;
case 'deny':
this.denyAll();
break;
case 'settings':
this.showSettings();
break;
case 'save':
this.saveSettings();
break;
case 'cancel':
this.hideSettings();
break;
case 'reopen':
this.showBanner();
break;
}
});
}
private acceptAll(): void {
this.state = {
necessary: true,
analytics: true,
functional: true,
timestamp: Date.now()
};
this.saveState();
this.hideBanner();
this.dispatchConsentEvent();
}
private denyAll(): void {
this.state = {
necessary: true,
analytics: false,
functional: false,
timestamp: Date.now()
};
this.saveState();
this.hideBanner();
this.dispatchConsentEvent();
}
private saveSettings(): void {
const checkboxes = document.querySelectorAll('input[data-cookie-category]');
this.state = {
necessary: true,
analytics: false,
functional: false,
timestamp: Date.now()
};
checkboxes.forEach((cb) => {
const input = cb as HTMLInputElement;
const category = input.dataset.cookieCategory as keyof ConsentState;
if (category && category in this.state!) {
(this.state as any)[category] = input.checked;
}
});
this.saveState();
this.hideBanner();
this.dispatchConsentEvent();
}
private dispatchConsentEvent(): void {
const event = new CustomEvent('onCookieConsentChange', {
detail: this.state
});
window.dispatchEvent(event);
// Set data attributes on document for CSS-based control
if (this.state) {
document.documentElement.dataset.consentAnalytics = String(this.state.analytics);
document.documentElement.dataset.consentFunctional = String(this.state.functional);
}
}
// Public API
public hasConsent(category: keyof Omit<ConsentState, 'timestamp'>): boolean {
if (!this.state) return false;
return this.state[category] ?? false;
}
public getState(): ConsentState | null {
return this.state;
}
public reopenBanner(): void {
this.showBanner();
}
}
// Export for module usage
export default CookieConsent;
// Initialize when DOM is ready and expose globally
declare global {
interface Window {
cookieConsent: CookieConsent;
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
window.cookieConsent = new CookieConsent();
});
} else {
window.cookieConsent = new CookieConsent();
}
+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); });
});
}
+110 -54
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
@@ -20,10 +24,45 @@ rssFullContent = true
toc = true
readingTime = true
[article.list]
# Showing article tags in the list view (e.g. homepage, section pages)
showTags = false
[article.license]
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 = []
@@ -42,95 +81,112 @@ rssFullContent = true
[imageProcessing.content]
enabled = true
# GDPR Cookie Consent Configuration
# When enabled, analytics and functional cookies require user consent
[cookies]
enabled = false
showSettings = true
[cookies.categories]
analytics = true
functional = 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"
+10 -1
View File
@@ -22,9 +22,18 @@ 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
enabled = true
provider = "disqus"
# GDPR Cookie Consent Configuration
# When enabled, analytics and functional cookies require user consent
[cookies]
enabled = false
[cookies.categories]
analytics = true
functional = true
+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)
+31
View File
@@ -22,6 +22,13 @@ darkMode = "الوضع الداكن"
one = "تُقرأ خلال دقيقة"
other = "تُقرأ خلال {{ .Count }} دقائق"
[article.alert]
note = "ملاحظة"
tip = "نصيحة"
important = "مهم"
warning = "تحذير"
caution = "تنبيه"
[notFound]
title = "غير موجود"
subtitle = "تعذر العثور على الصفحة المطلوبة."
@@ -45,3 +52,27 @@ darkMode = "الوضع الداكن"
[footer]
builtWith = "مبني باستخدام {{ .Generator }}"
designedBy = "قالب {{ .Theme }} مصمم من {{ .DesignedBy }}"
[cookies]
title = "موافقة ملفات تعريف الارتباط"
text = "نستخدم ملفات تعريف الارتباط لتحسين تجربة التصفح وتحليل حركة المرور على الموقع."
acceptAll = "قبول الكل"
deny = "رفض"
managePreferences = "إدارة التفضيلات"
settingsTitle = "تفضيلات ملفات تعريف الارتباط"
savePreferences = "حفظ التفضيلات"
cancel = "إلغاء"
footerLink = "إعدادات ملفات تعريف الارتباط"
commentsDisabled = "التعليقات معطلة حتى تقبل ملفات تعريف الارتباط الوظيفية."
[cookies.necessary]
title = "ضروري"
text = "ملفات تعريف الارتباط هذه مطلوبة لعمل الموقع ولا يمكن تعطيلها."
[cookies.analytics]
title = "التحليلات"
text = "تساعدنا ملفات تعريف الارتباط هذه على فهم كيفية تفاعل الزوار مع موقعنا."
[cookies.functional]
title = "وظيفي"
text = "تتيح ملفات تعريف الارتباط هذه ميزات مثل التعليقات والمحتوى المضمن."
+31
View File
@@ -23,6 +23,13 @@ darkMode = "Цёмны рэжым"
tableOfContents = "Змест"
readingTime = "Час чытання: {{ .Count }} хв."
[article.alert]
note = "Заўвага"
tip = "Парада"
important = "Важна"
warning = "Папярэджанне"
caution = "Асцярога"
[notFound]
title = "Не знойдзена"
subtitle = "Запытваемая старонка не існуе"
@@ -43,3 +50,27 @@ darkMode = "Цёмны рэжым"
[footer]
builtWith = "Створана пры дапамозе {{ .Generator }}"
designedBy = "Тэма {{ .Theme }}, дызайн {{ .DesignedBy }}"
[cookies]
title = "Згода на файлы cookie"
text = "Мы выкарыстоўваем файлы cookie для паляпшэння вашага досведу і аналізу трафіку сайта."
acceptAll = "Прыняць усё"
deny = "Адхіліць"
managePreferences = "Кіраваць налады"
settingsTitle = "Налады файлаў cookie"
savePreferences = "Захаваць налады"
cancel = "Скасаваць"
footerLink = "Налады файлаў cookie"
commentsDisabled = "Каментары адключаны, пакуль вы не прымеце функцыянальныя файлы cookie."
[cookies.necessary]
title = "Неабходныя"
text = "Гэтыя файлы cookie неабходныя для працы сайта і не могуць быць адключаны."
[cookies.analytics]
title = "Аналітыка"
text = "Гэтыя файлы cookie дапамагаюць нам зразумець, як наведвальнікі ўзаемадзейнічаюць з нашым сайтам."
[cookies.functional]
title = "Функцыянальныя"
text = "Гэтыя файлы cookie ўключаюць такія функцыі, як каментары і ўбудаваны кантэнт."
+31
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 = "Страницата която търсите не е открита"
@@ -45,3 +52,27 @@ darkMode = "Тъмен Режим"
[footer]
builtWith = "Създадено с {{ .Generator }}"
designedBy = "Тема {{ .Theme }} създадена от {{ .DesignedBy }}"
[cookies]
title = "Съгласие за бисквитки"
text = "Използваме бисквитки, за да подобрим вашето изживяване и да анализираме трафика на сайта."
acceptAll = "Приемане на всички"
deny = "Откажи"
managePreferences = "Управление на предпочитанията"
settingsTitle = "Предпочитания за бисквитки"
savePreferences = "Запазване на предпочитанията"
cancel = "Отказ"
footerLink = "Настройки на бисквитките"
commentsDisabled = "Коментарите са деактивирани, докато не приемете функционални бисквитки."
[cookies.necessary]
title = "Необходими"
text = "Тези бисквитки са необходими за функционирането на уебсайта и не могат да бъдат деактивирани."
[cookies.analytics]
title = "Анализи"
text = "Тези бисквитки ни помагат да разберем как посетителите взаимодействат с нашия уебсайт."
[cookies.functional]
title = "Функционални"
text = "Тези бисквитки активират функции като коментари и вградено съдържание."
+36 -5
View File
@@ -15,15 +15,22 @@ darkMode = "ডার্ক মোড"
[article]
back = "পেছনে"
tableOfContents = "সূচিপত্র"
relatedContent = "সম্পর্কিত বিষবস্তু"
relatedContent = "সম্পর্কিত বিষয়বস্তু"
lastUpdatedOn = "সর্বশেষ আপডেট করা হয়েছে"
[article.readingTime]
one = "{{ .Count }} মিনিটে পা যাবে"
other = "{{ .Count }} মিনিটে পা যাবে"
one = "{{ .Count }} মিনিটে পড়া যাবে"
other = "{{ .Count }} মিনিটে পড়া যাবে"
[article.alert]
note = "টীকা"
tip = "পরামর্শ"
important = "গুরুত্বপূর্ণ"
warning = "সতর্কতা"
caution = "সাবধানতা"
[notFound]
title = "পাওা যানি"
title = "পাওয়া যায়নি"
subtitle = "এই পাতাটি বিদ্যমান নেই"
[widget]
@@ -43,5 +50,29 @@ darkMode = "ডার্ক মোড"
resultTitle = "#PAGES_COUNT পাতা (#TIME_SECONDS সেকেন্ড)"
[footer]
builtWith = "{{ .Generator }} দিে নির্মিত"
builtWith = "{{ .Generator }} দিয়ে নির্মিত"
designedBy = "থিম {{ .Theme }} ডিজাইন করেছেন {{ .DesignedBy }}"
[cookies]
title = "কুকি সম্মতি"
text = "আমরা আপনার ব্রাউজিং অভিজ্ঞতা উন্নত করতে এবং সাইট ট্রাফিক বিশ্লেষণ করতে কুকি ব্যবহার করি।"
acceptAll = "সবগুলো গ্রহণ করুন"
deny = "প্রত্যাখ্যান"
managePreferences = "পছন্দসমূহ পরিচালনা করুন"
settingsTitle = "কুকি পছন্দসমূহ"
savePreferences = "পছন্দসমূহ সংরক্ষণ করুন"
cancel = "বাতিল"
footerLink = "কুকি সেটিংস"
commentsDisabled = "আপনি কার্যকরী কুকি গ্রহণ না করা পর্যন্ত মন্তব্য অক্ষম করা আছে।"
[cookies.necessary]
title = "প্রয়োজনীয়"
text = "এই কুকিগুলি ওয়েবসাইট কাজ করার জন্য প্রয়োজনীয় এবং নিষ্ক্রিয় করা যায় না।"
[cookies.analytics]
title = "বিশ্লেষণ"
text = "এই কুকিগুলি আমাদের বুঝতে সাহায্য করে যে দর্শকরা কীভাবে আমাদের ওয়েবসাইটের সাথে ইন্টারঅ্যাক্ট করে।"
[cookies.functional]
title = "কার্যকরী"
text = "এই কুকিগুলি মন্তব্য এবং এম্বেড করা বিষয়বস্তুর মতো বৈশিষ্ট্যগুলি সক্ষম করে."
+31
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"
@@ -45,3 +52,27 @@ darkMode = "Mode fosc"
[footer]
builtWith = "Creat amb {{ .Generator }}"
designedBy = "Tema {{ .Theme }} dissenyat per {{ .DesignedBy }}"
[cookies]
title = "Consentiment de galetes"
text = "Utilitzem galetes per millorar la vostra experiència de navegació i analitzar el trànsit del lloc."
acceptAll = "Acceptar-ho tot"
deny = "Denegar"
managePreferences = "Gestionar preferències"
settingsTitle = "Preferències de galetes"
savePreferences = "Desar preferències"
cancel = "Cancel·lar"
footerLink = "Configuració de galetes"
commentsDisabled = "Els comentaris estan desactivats fins que accepteu les galetes funcionals."
[cookies.necessary]
title = "Necessàries"
text = "Aquestes galetes són necessàries perquè el lloc web funcioni i no es poden desactivar."
[cookies.analytics]
title = "Analítiques"
text = "Aquestes galetes ens ajuden a entendre com els visitants interactuen amb el nostre lloc web."
[cookies.functional]
title = "Funcionals"
text = "Aquestes galetes habiliten funcionalitats com comentaris i contingut incrustat."
+31
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"
@@ -45,3 +52,27 @@ darkMode = "Tmavý režim"
[footer]
builtWith = "Vytvořeno pomocí {{ .Generator }}"
designedBy = "Šablona {{ .Theme }} od {{ .DesignedBy }}"
[cookies]
title = "Souhlas s cookies"
text = "Používáme cookies ke zlepšení vašeho prohlížení a analýze návštěvnosti webu."
acceptAll = "Přijmout vše"
deny = "Odmítnout"
managePreferences = "Spravovat předvolby"
settingsTitle = "Předvolby cookies"
savePreferences = "Uložit předvolby"
cancel = "Zrušit"
footerLink = "Nastavení cookies"
commentsDisabled = "Komentáře jsou zakázány, dokud nepřijmete funkční cookies."
[cookies.necessary]
title = "Nutné"
text = "Tyto cookies jsou nutné pro fungování webu a nelze je zakázat."
[cookies.analytics]
title = "Analytické"
text = "Tyto cookies nám pomáhají pochopit, jak návštěvníci interagují s naším webem."
[cookies.functional]
title = "Funkční"
text = "Tyto cookies umožňují funkce jako komentáře a vložený obsah."
+31
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"
@@ -45,3 +52,27 @@ darkMode = "Dunkler Modus"
[footer]
builtWith = "Erstellt mit {{ .Generator }}"
designedBy = "Theme {{ .Theme }} gestaltet von {{ .DesignedBy }}"
[cookies]
title = "Cookie-Einwilligung"
text = "Wir verwenden Cookies, um Ihr Surferlebnis zu verbessern und den Website-Traffic zu analysieren."
acceptAll = "Alle akzeptieren"
deny = "Ablehnen"
managePreferences = "Einstellungen verwalten"
settingsTitle = "Cookie-Einstellungen"
savePreferences = "Einstellungen speichern"
cancel = "Abbrechen"
footerLink = "Cookie-Einstellungen"
commentsDisabled = "Kommentare sind deaktiviert, bis Sie funktionale Cookies akzeptieren."
[cookies.necessary]
title = "Notwendig"
text = "Diese Cookies sind für die Funktion der Website erforderlich und können nicht deaktiviert werden."
[cookies.analytics]
title = "Analyse"
text = "Diese Cookies helfen uns zu verstehen, wie Besucher mit unserer Website interagieren."
[cookies.functional]
title = "Funktional"
text = "Diese Cookies ermöglichen Funktionen wie Kommentare und eingebettete Inhalte."
+31
View File
@@ -22,6 +22,13 @@ darkMode = "Σκοτεινό θέμα"
one = "{{ .Count }} λεπτό ανάγνωσης"
other = "{{ .Count }} λεπτά ανάγνωσης"
[article.alert]
note = "Σημείωση"
tip = "Συμβουλή"
important = "Σημαντικό"
warning = "Προειδοποίηση"
caution = "Προσοχή"
[notFound]
title = "Δε βρέθηκε"
subtitle = "Η σελίδα δε βρέθηκε."
@@ -42,3 +49,27 @@ darkMode = "Σκοτεινό θέμα"
[footer]
builtWith = "Δημιουργήθηκε με τη χρήση {{ .Generator }}"
designedBy = "Το θέμα {{ .Theme }} σχεδιάστηκε από το {{ .DesignedBy }}"
[cookies]
title = "Συγκατάθεση για cookies"
text = "Χρησιμοποιούμε cookies για να βελτιώσουμε την εμπειρία περιήγησής σας και να αναλύσουμε την επισκεψιμότητα του ιστότοπου."
acceptAll = "Αποδοχή όλων"
deny = "Άρνηση"
managePreferences = "Διαχείριση προτιμήσεων"
settingsTitle = "Προτιμήσεις cookies"
savePreferences = "Αποθήκευση προτιμήσεων"
cancel = "Ακύρωση"
footerLink = "Ρυθμίσεις cookies"
commentsDisabled = "Τα σχόλια είναι απενεργοποιημένα μέχρι να αποδεχτείτε τα λειτουργικά cookies."
[cookies.necessary]
title = "Απαραίτητα"
text = "Αυτά τα cookies είναι απαραίτητα για τη λειτουργία του ιστότοπου και δεν μπορούν να απενεργοποιηθούν."
[cookies.analytics]
title = "Αναλυτικά"
text = "Αυτά τα cookies μας βοηθούν να κατανοήσουμε πώς οι επισκέπτες αλληλεπιδρούν με τον ιστότοπό μας."
[cookies.functional]
title = "Λειτουργικά"
text = "Αυτά τα cookies ενεργοποιούν λειτουργίες όπως σχόλια και ενσωματωμένο περιεχόμενο."
+32 -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"
@@ -45,3 +52,27 @@ darkMode = "Dark Mode"
[footer]
builtWith = "Built with {{ .Generator }}"
designedBy = "Theme {{ .Theme }} designed by {{ .DesignedBy }}"
[cookies]
title = "Cookie Consent"
text = "We use cookies to enhance your browsing experience and analyze site traffic."
acceptAll = "Accept All"
deny = "Deny"
managePreferences = "Manage preferences"
settingsTitle = "Cookie Preferences"
savePreferences = "Save preferences"
cancel = "Cancel"
footerLink = "Cookie settings"
commentsDisabled = "Comments are disabled until you accept functional cookies."
[cookies.necessary]
title = "Necessary"
text = "These cookies are required for the website to function and cannot be disabled."
[cookies.analytics]
title = "Analytics"
text = "These cookies help us understand how visitors interact with our website."
[cookies.functional]
title = "Functional"
text = "These cookies enable features like comments and embedded content."
+31
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"
@@ -45,3 +52,27 @@ darkMode = "Modo oscuro"
[footer]
builtWith = "Creado con {{ .Generator }}"
designedBy = "Tema {{ .Theme }} diseñado por {{ .DesignedBy }}"
[cookies]
title = "Consentimiento de cookies"
text = "Utilizamos cookies para mejorar su experiencia de navegación y analizar el tráfico del sitio."
acceptAll = "Aceptar todo"
deny = "Rechazar"
managePreferences = "Gestionar preferencias"
settingsTitle = "Preferencias de cookies"
savePreferences = "Guardar preferencias"
cancel = "Cancelar"
footerLink = "Configuración de cookies"
commentsDisabled = "Los comentarios están desactivados hasta que acepte las cookies funcionales."
[cookies.necessary]
title = "Necesarias"
text = "Estas cookies son necesarias para el funcionamiento del sitio web y no se pueden desactivar."
[cookies.analytics]
title = "Analíticas"
text = "Estas cookies nos ayudan a entender cómo los visitantes interactúan con nuestro sitio web."
[cookies.functional]
title = "Funcionales"
text = "Estas cookies permiten funciones como comentarios y contenido incrustado."
+31
View File
@@ -22,6 +22,13 @@ darkMode = "حالت شب"
one = "مطالعه در {{ .Count }} دقیقه"
other = "مطالعه در {{ .Count }} دقیقه"
[article.alert]
note = "یادداشت"
tip = "نکته"
important = "مهم"
warning = "هشدار"
caution = "احتیاط"
[notFound]
title = "یافت نشد"
subtitle = "این صحه وجود ندارد"
@@ -45,3 +52,27 @@ darkMode = "حالت شب"
[footer]
builtWith = "قدرت گرفته از {{ .Generator }}"
designedBy = "قالب {{ .Theme }} ساخته شده توسط {{ .DesignedBy }}"
[cookies]
title = "رضایت کوکی"
text = "ما از کوکی‌ها برای بهبود تجربه مرورگری شما و تجزیه و تحلیل ترافیک سایت استفاده می‌کنیم."
acceptAll = "پذیرش همه"
deny = "رد کردن"
managePreferences = "مدیریت تنظیمات"
settingsTitle = "تنظیمات کوکی"
savePreferences = "ذخیره تنظیمات"
cancel = "لغو"
footerLink = "تنظیمات کوکی"
commentsDisabled = "نظرات تا زمانی که کوکی‌های کاربردی را بپذیرید غیرفعال هستند."
[cookies.necessary]
title = "ضروری"
text = "این کوکی‌ها برای عملکرد وب‌سایت الزامی هستند و نمی‌توان آنها را غیرفعال کرد."
[cookies.analytics]
title = "تجزیه و تحلیل"
text = "این کوکی‌ها به ما کمک می‌کنند تا بفهمیم بازدیدکنندگان چگونه با وب‌سایت ما تعامل دارند."
[cookies.functional]
title = "کاربردی"
text = "این کوکی‌ها ویژگی‌هایی مانند نظرات و محتوای جاسازی‌شده را فعال می‌کنند."
+31
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."
@@ -45,3 +52,27 @@ darkMode = "Mode sombre"
[footer]
builtWith = "Généré avec {{ .Generator }}"
designedBy = "Thème {{ .Theme }} conçu par {{ .DesignedBy }}"
[cookies]
title = "Consentement aux cookies"
text = "Nous utilisons des cookies pour améliorer votre expérience de navigation et analyser le trafic du site."
acceptAll = "Tout accepter"
deny = "Refuser"
managePreferences = "Gérer les préférences"
settingsTitle = "Préférences de cookies"
savePreferences = "Enregistrer les préférences"
cancel = "Annuler"
footerLink = "Paramètres des cookies"
commentsDisabled = "Les commentaires sont désactivés jusqu'à ce que vous acceptiez les cookies fonctionnels."
[cookies.necessary]
title = "Nécessaires"
text = "Ces cookies sont nécessaires au fonctionnement du site et ne peuvent pas être désactivés."
[cookies.analytics]
title = "Analytiques"
text = "Ces cookies nous aident à comprendre comment les visiteurs interagissent avec notre site."
[cookies.functional]
title = "Fonctionnels"
text = "Ces cookies permettent des fonctionnalités comme les commentaires et le contenu intégré."
+31
View File
@@ -22,6 +22,13 @@ darkMode = "डार्क मोड"
one = "{{ .Count }} मिनट पढ़ें"
other = "{{ .Count }} मिनट पढ़ें"
[article.alert]
note = "नोट"
tip = "सलाह"
important = "महत्वपूर्ण"
warning = "चेतावनी"
caution = "सावधानी"
[notFound]
title = "404 नहीं मिला।"
subtitle = "यह पृष्ठ मौजूद नहीं है।"
@@ -45,3 +52,27 @@ darkMode = "डार्क मोड"
[footer]
builtWith = "निर्मित {{ .Generator }} के साथ"
designedBy = "थीम {{ .Theme }} द्वारा डिज़ाइन किया गया {{ .DesignedBy }}"
[cookies]
title = "कुकी सहमति"
text = "हम आपके ब्राउज़िंग अनुभव को बेहतर बनाने और साइट ट्रैफ़िक का विश्लेषण करने के लिए कुकीज़ का उपयोग करते हैं।"
acceptAll = "सभी स्वीकार करें"
deny = "अस्वीकार करें"
managePreferences = "वरीयताएँ प्रबंधित करें"
settingsTitle = "कुकी वरीयताएँ"
savePreferences = "वरीयताएँ सहेजें"
cancel = "रद्द करें"
footerLink = "कुकी सेटिंग्स"
commentsDisabled = "जब तक आप कार्यात्मक कुकीज़ स्वीकार नहीं करते हैं, टिप्पणियाँ अक्षम हैं।"
[cookies.necessary]
title = "आवश्यक"
text = "ये कुकीज़ वेबसाइट के कार्य के लिए आवश्यक हैं और इन्हें अक्षम नहीं किया जा सकता।"
[cookies.analytics]
title = "विश्लेषण"
text = "ये कुकीज़ हमें यह समझने में मदद करती हैं कि विज़िटर हमारी वेबसाइट के साथ कैसे इंटरैक्ट करते हैं।"
[cookies.functional]
title = "कार्यात्मक"
text = "ये कुकीज़ टिप्पणियों और एम्बेडेड सामग्री जैसी सुविधाओं को सक्षम करती हैं."
+31
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"
@@ -45,3 +52,27 @@ darkMode = "Sötét Mód"
[footer]
builtWith = "{{ .Generator }} használatával készült"
designedBy = "A {{ .Theme }} dizájnt {{ .DesignedBy }} tervezte"
[cookies]
title = "Süti hozzájárulás"
text = "Sütiket használunk a böngészési élmény javítása és a webhely forgalmának elemzése érdekében."
acceptAll = "Összes elfogadása"
deny = "Elutasítás"
managePreferences = "Beállítások kezelése"
settingsTitle = "Süti beállítások"
savePreferences = "Beállítások mentése"
cancel = "Mégse"
footerLink = "Süti beállítások"
commentsDisabled = "A hozzászólások le vannak tiltva, amíg nem fogadja el a funkcionális sütiket."
[cookies.necessary]
title = "Szükséges"
text = "Ezek a sütik szükségesek a webhely működéséhez és nem lehet őket letiltani."
[cookies.analytics]
title = "Analitikai"
text = "Ezek a sütik segítenek megérteni, hogyan lépnek kapcsolatba a látogatók a weboldalunkkal."
[cookies.functional]
title = "Funkcionális"
text = "Ezek a sütik olyan funkciókat tesznek lehetővé, mint a hozzászólások és a beágyazott tartalom."
+31
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."
@@ -45,3 +52,27 @@ darkMode = "Mode Gelap"
[footer]
builtWith = "Dibangun dengan {{ .Generator }}"
designedBy = "Tema {{ .Theme }} dirancang oleh {{ .DesignedBy }}"
[cookies]
title = "Persetujuan Cookie"
text = "Kami menggunakan cookie untuk meningkatkan pengalaman browsing Anda dan menganalisis lalu lintas situs."
acceptAll = "Terima Semua"
deny = "Tolak"
managePreferences = "Kelola preferensi"
settingsTitle = "Preferensi Cookie"
savePreferences = "Simpan preferensi"
cancel = "Batal"
footerLink = "Pengaturan cookie"
commentsDisabled = "Komentar dinonaktifkan sampai Anda menerima cookie fungsional."
[cookies.necessary]
title = "Diperlukan"
text = "Cookie ini diperlukan agar situs web berfungsi dan tidak dapat dinonaktifkan."
[cookies.analytics]
title = "Analitik"
text = "Cookie ini membantu kami memahami bagaimana pengunjung berinteraksi dengan situs web kami."
[cookies.functional]
title = "Fungsional"
text = "Cookie ini mengaktifkan fitur seperti komentar dan konten yang disematkan."
+31
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."
@@ -45,3 +52,27 @@ darkMode = "Modalità scura"
[footer]
builtWith = "Realizzato con {{ .Generator }}"
designedBy = "Tema {{ .Theme }} realizzato da {{ .DesignedBy }}"
[cookies]
title = "Consenso ai cookie"
text = "Utilizziamo i cookie per migliorare la tua esperienza di navigazione e analizzare il traffico del sito."
acceptAll = "Accetta tutti"
deny = "Rifiuta"
managePreferences = "Gestisci preferenze"
settingsTitle = "Preferenze cookie"
savePreferences = "Salva preferenze"
cancel = "Annulla"
footerLink = "Impostazioni cookie"
commentsDisabled = "I commenti sono disabilitati finché non accetti i cookie funzionali."
[cookies.necessary]
title = "Necessari"
text = "Questi cookie sono necessari per il funzionamento del sito web e non possono essere disattivati."
[cookies.analytics]
title = "Analitici"
text = "Questi cookie ci aiutano a capire come i visitatori interagiscono con il nostro sito web."
[cookies.functional]
title = "Funzionali"
text = "Questi cookie abilitano funzionalità come commenti e contenuti incorporati."
+31
View File
@@ -13,6 +13,13 @@ darkMode = "ダークモード"
lastUpdatedOn = "最終更新"
readingTime = "読了時間: {{ .Count }}分"
[article.alert]
note = "注記"
tip = "ヒント"
important = "重要"
warning = "警告"
caution = "注意"
[notFound]
title = "404 Not Found"
subtitle = "指定されたページは存在しません。"
@@ -36,3 +43,27 @@ darkMode = "ダークモード"
[footer]
builtWith = "{{ .Generator }} で構築されています。"
designedBy = "テーマ {{ .Theme }} は {{ .DesignedBy }} によって設計されています。"
[cookies]
title = "Cookieの同意"
text = "ブラウジング体験を向上させ、サイトトラフィックを分析するためにCookieを使用しています。"
acceptAll = "すべて受け入れる"
deny = "拒否"
managePreferences = "設定を管理"
settingsTitle = "Cookie設定"
savePreferences = "設定を保存"
cancel = "キャンセル"
footerLink = "Cookie設定"
commentsDisabled = "機能的なCookieを受け入れるまで、コメントは無効になっています。"
[cookies.necessary]
title = "必須"
text = "これらのCookieはウェブサイトの機能に必要であり、無効にすることはできません。"
[cookies.analytics]
title = "分析"
text = "これらのCookieは、訪問者が当サイトとどのように対話するかを理解するのに役立ちます。"
[cookies.functional]
title = "機能的"
text = "これらのCookieは、コメントや埋め込みコンテンツなどの機能を有効にします."
+31
View File
@@ -22,6 +22,13 @@ darkMode = "다크 모드"
one = "{{ .Count }} 분 정도"
other = "{{ .Count }} 분 정도"
[article.alert]
note = "참고"
tip = "팁"
important = "중요"
warning = "경고"
caution = "주의"
[notFound]
title = "찾을 수 없음"
subtitle = "페이지를 찾을 수 없습니다."
@@ -45,3 +52,27 @@ darkMode = "다크 모드"
[footer]
builtWith = "{{ .Generator }}로 만듦"
designedBy = "{{ .DesignedBy }}의 {{ .Theme }} 테마 사용 중"
[cookies]
title = "쿠키 동의"
text = "브라우징 경험을 향상시키고 사이트 트래픽을 분석하기 위해 쿠키를 사용합니다."
acceptAll = "모두 허용"
deny = "거부"
managePreferences = "설정 관리"
settingsTitle = "쿠키 설정"
savePreferences = "설정 저장"
cancel = "취소"
footerLink = "쿠키 설정"
commentsDisabled = "기능적 쿠키를 수락할 때까지 댓글이 비활성화됩니다."
[cookies.necessary]
title = "필수"
text = "이 쿠키는 웹사이트 기능에 필요하며 비활성화할 수 없습니다."
[cookies.analytics]
title = "분석"
text = "이 쿠키는 방문자가 웹사이트와 상호작용하는 방식을 이해하는 데 도움이 됩니다."
[cookies.functional]
title = "기능적"
text = "이 쿠키는 댓글 및 임베디드 콘텐츠와 같은 기능을 활성화합니다."
+31
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."
@@ -40,3 +47,27 @@ darkMode = "Donkere modus"
[footer]
builtWith = "Gemaakt met {{ .Generator }}"
designedBy = "Theme {{ .Theme }} ontworpen door {{ .DesignedBy }}"
[cookies]
title = "Cookie-toestemming"
text = "We gebruiken cookies om uw browse-ervaring te verbeteren en siteverkeer te analyseren."
acceptAll = "Accepteer alles"
deny = "Weigeren"
managePreferences = "Voorkeuren beheren"
settingsTitle = "Cookie-voorkeuren"
savePreferences = "Voorkeuren opslaan"
cancel = "Annuleren"
footerLink = "Cookie-instellingen"
commentsDisabled = "Reacties zijn uitgeschakeld totdat u functionele cookies accepteert."
[cookies.necessary]
title = "Noodzakelijk"
text = "Deze cookies zijn vereist voor de werking van de website en kunnen niet worden uitgeschakeld."
[cookies.analytics]
title = "Analyse"
text = "Deze cookies helpen ons begrijpen hoe bezoekers omgaan met onze website."
[cookies.functional]
title = "Functioneel"
text = "Deze cookies maken functies mogelijk zoals reacties en ingesloten inhoud."
+31
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"
@@ -45,3 +52,27 @@ darkMode = "Mòde fosc"
[footer]
builtWith = "Creat amb {{ .Generator }}"
designedBy = "Tàma {{ .Theme }} concebut per {{ .DesignedBy }}"
[cookies]
title = "Consentiment de cookies"
text = "Utilizem cookies per melhorar vòstra experiéncia de navegacion e analisar lo trafic del site."
acceptAll = "Acceptar tot"
deny = "Refusar"
managePreferences = "Gerir las preferéncias"
settingsTitle = "Preferéncias de cookies"
savePreferences = "Enregistrar las preferéncias"
cancel = "Anullar"
footerLink = "Paramètres de cookies"
commentsDisabled = "Los comentaris son desactivats fins qu'acceptatz las cookies foncionalas."
[cookies.necessary]
title = "Necessaris"
text = "Aquestes cookies son necessàrias pel foncionament del site web e se pòdon pas desactivar."
[cookies.analytics]
title = "Analiticas"
text = "Aquestes cookies nos ajudan a comprendre cossí los visitaires interagisson amb nòstre site web."
[cookies.functional]
title = "Foncionalas"
text = "Aquestes cookies activan de foncionalitats coma los comentaris e lo contengut incorporat."
+31
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"
@@ -56,3 +63,27 @@ darkMode = "Tryb ciemny"
[footer]
builtWith = "Zbudowano z {{ .Generator }}"
designedBy = "Motyw {{ .Theme }} zaprojektowany przez {{ .DesignedBy }}"
[cookies]
title = "Zgoda na pliki cookie"
text = "Używamy plików cookie, aby poprawić Twoje wrażenia z przeglądania i analizować ruch w witrynie."
acceptAll = "Akceptuj wszystkie"
deny = "Odrzuć"
managePreferences = "Zarządzaj preferencjami"
settingsTitle = "Preferencje plików cookie"
savePreferences = "Zapisz preferencje"
cancel = "Anuluj"
footerLink = "Ustawienia plików cookie"
commentsDisabled = "Komentarze są wyłączone, dopóki nie zaakceptujesz funkcjonalnych plików cookie."
[cookies.necessary]
title = "Niezbędne"
text = "Te pliki cookie są wymagane do działania witryny i nie można ich wyłączyć."
[cookies.analytics]
title = "Analityczne"
text = "Te pliki cookie pomagają nam zrozumieć, jak odwiedzający wchodzą w interakcję z naszą witryną."
[cookies.functional]
title = "Funkcjonalne"
text = "Te pliki cookie włączają funkcje takie jak komentarze i osadzone treści."
+31
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."
@@ -45,3 +52,27 @@ darkMode = "Modo Escuro"
[footer]
builtWith = "Criado com {{ .Generator }}"
designedBy = "Tema {{ .Theme }} desenvolvido por {{ .DesignedBy }}"
[cookies]
title = "Consentimento de cookies"
text = "Usamos cookies para melhorar sua experiência de navegação e analisar o tráfego do site."
acceptAll = "Aceitar todos"
deny = "Recusar"
managePreferences = "Gerenciar preferências"
settingsTitle = "Preferências de cookies"
savePreferences = "Salvar preferências"
cancel = "Cancelar"
footerLink = "Configurações de cookies"
commentsDisabled = "Os comentários estão desativados até que você aceite os cookies funcionais."
[cookies.necessary]
title = "Necessários"
text = "Estes cookies são necessários para o funcionamento do site e não podem ser desativados."
[cookies.analytics]
title = "Analíticos"
text = "Estes cookies nos ajudam a entender como os visitantes interagem com nosso site."
[cookies.functional]
title = "Funcionais"
text = "Estes cookies habilitam recursos como comentários e conteúdo incorporado."
+31
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."
@@ -45,3 +52,27 @@ darkMode = "Modo Escuro"
[footer]
builtWith = "Criado com {{ .Generator }}"
designedBy = "Tema {{ .Theme }} desenvolvido por {{ .DesignedBy }}"
[cookies]
title = "Consentimento de cookies"
text = "Utilizamos cookies para melhorar a sua experiência de navegação e analisar o tráfego do site."
acceptAll = "Aceitar todos"
deny = "Recusar"
managePreferences = "Gerir preferências"
settingsTitle = "Preferências de cookies"
savePreferences = "Guardar preferências"
cancel = "Cancelar"
footerLink = "Definições de cookies"
commentsDisabled = "Os comentários estão desativados até que aceite os cookies funcionais."
[cookies.necessary]
title = "Necessários"
text = "Estes cookies são necessários para o funcionamento do website e não podem ser desativados."
[cookies.analytics]
title = "Analíticos"
text = "Estes cookies ajudam-nos a compreender como os visitantes interagem com o nosso website."
[cookies.functional]
title = "Funcionais"
text = "Estes cookies permitem funcionalidades como comentários e conteúdo incorporado."
+31
View File
@@ -23,6 +23,13 @@ darkMode = "Тёмный режим"
tableOfContents = "Содержание"
readingTime = "Время чтения: {{ .Count }} мин."
[article.alert]
note = "Примечание"
tip = "Совет"
important = "Важно"
warning = "Предупреждение"
caution = "Осторожно"
[notFound]
title = "Не найдено"
subtitle = "Запрашиваемая страница не существует"
@@ -46,3 +53,27 @@ darkMode = "Тёмный режим"
[footer]
builtWith = "Создано при помощи {{ .Generator }}"
designedBy = "Тема {{ .Theme }}, дизайн {{ .DesignedBy }}"
[cookies]
title = "Согласие на использование файлов cookie"
text = "Мы используем файлы cookie для улучшения вашего опыта просмотра и анализа трафика сайта."
acceptAll = "Принять все"
deny = "Отклонить"
managePreferences = "Управление настройками"
settingsTitle = "Настройки файлов cookie"
savePreferences = "Сохранить настройки"
cancel = "Отмена"
footerLink = "Настройки файлов cookie"
commentsDisabled = "Комментарии отключены, пока вы не примете функциональные файлы cookie."
[cookies.necessary]
title = "Необходимые"
text = "Эти файлы cookie необходимы для работы сайта и не могут быть отключены."
[cookies.analytics]
title = "Аналитика"
text = "Эти файлы cookie помогают нам понять, как посетители взаимодействуют с нашим сайтом."
[cookies.functional]
title = "Функциональные"
text = "Эти файлы cookie включают такие функции, как комментарии и встроенный контент."
+31
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"
@@ -45,3 +52,27 @@ darkMode = "Tmavý režim"
[footer]
builtWith = "Vytvorené pomocou {{ .Generator }}"
designedBy = "Šablóna {{ .Theme }} od {{ .DesignedBy }}"
[cookies]
title = "Súhlas s cookies"
text = "Používame cookies na zlepšenie vášho prehliadania a analýzu návštevnosti webu."
acceptAll = "Prijať všetko"
deny = "Odmietnuť"
managePreferences = "Spravovať predvoľby"
settingsTitle = "Predvoľby cookies"
savePreferences = "Uložiť predvoľby"
cancel = "Zrušiť"
footerLink = "Nastavenia cookies"
commentsDisabled = "Komentáre sú zakázané, kým neprijmete funkčné cookies."
[cookies.necessary]
title = "Nutné"
text = "Tieto cookies sú nutné pre fungovanie webu a nemožno ich zakázať."
[cookies.analytics]
title = "Analytické"
text = "Tieto cookies nám pomáhajú pochopiť, ako návštevníci interagujú s naším webom."
[cookies.functional]
title = "Funkčné"
text = "Tieto cookies umožňujú funkcie ako komentáre a vložený obsah."
+31
View File
@@ -22,6 +22,13 @@ darkMode = "ธีมมืด"
one = "น่าจะใช้เวลา {{ .Count }} นาทีในการอ่าน"
other = "น่าจะใช้เวลา {{ .Count }} นาทีในการอ่าน"
[article.alert]
note = "หมายเหตุ"
tip = "เคล็ดลับ"
important = "สำคัญ"
warning = "คำเตือน"
caution = "ระวัง"
[notFound]
title = "ไม่พบหัวข้อ"
subtitle = "ไม่พบหน้านี้ในระบบ"
@@ -42,3 +49,27 @@ darkMode = "ธีมมืด"
[footer]
builtWith = "ถูกสร้างด้วย {{ .Generator }}"
designedBy = "ธีม {{ .Theme }} ออกแบบโดย {{ .DesignedBy }}"
[cookies]
title = "ความยินยอมคุกกี้"
text = "เราใช้คุกกี้เพื่อปรับปรุงประสบการณ์การเรียกดูของคุณและวิเคราะห์การเข้าชมเว็บไซต์"
acceptAll = "ยอมรับทั้งหมด"
deny = "ปฏิเสธ"
managePreferences = "จัดการการตั้งค่า"
settingsTitle = "การตั้งค่าคุกกี้"
savePreferences = "บันทึกการตั้งค่า"
cancel = "ยกเลิก"
footerLink = "การตั้งค่าคุกกี้"
commentsDisabled = "ความคิดเห็นถูกปิดการใช้งานจนกว่าคุณจะยอมรับคุกกี้เชิงหน้าที่"
[cookies.necessary]
title = "จำเป็น"
text = "คุกกี้เหล่านี้จำเป็นสำหรับการทำงานของเว็บไซต์และไม่สามารถปิดการใช้งานได้"
[cookies.analytics]
title = "การวิเคราะห์"
text = "คุกกี้เหล่านี้ช่วยให้เราเข้าใจว่าผู้เยี่ยมชมโต้ตอบกับเว็บไซต์ของเราอย่างไร"
[cookies.functional]
title = "เชิงหน้าที่"
text = "คุกกี้เหล่านี้เปิดใช้งานคุณสมบัติต่างๆ เช่น ความคิดเห็นและเนื้อหาที่ฝังไว้"
+31
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."
@@ -45,3 +52,27 @@ darkMode = "Koyu Mod"
[footer]
builtWith = "{{ .Generator }} ile oluşturuldu."
designedBy = "{{ .Theme }} teması {{ .DesignedBy }} tarafından tasarlandı"
[cookies]
title = "Çerez Onayı"
text = "Tarayıcı deneyiminizi iyileştirmek ve site trafiğini analiz etmek için çerezler kullanıyoruz."
acceptAll = "Tümünü Kabul Et"
deny = "Reddet"
managePreferences = "Tercihleri yönet"
settingsTitle = "Çerez Tercihleri"
savePreferences = "Tercihleri kaydet"
cancel = "İptal"
footerLink = "Çerez ayarları"
commentsDisabled = "İşlevsel çerezleri kabul edene kadar yorumlar devre dışıdır."
[cookies.necessary]
title = "Gerekli"
text = "Bu çerezler web sitesinin çalışması için gereklidir ve devre dışı bırakılamaz."
[cookies.analytics]
title = "Analitik"
text = "Bu çerezler, ziyaretçilerin web sitemizle nasıl etkileşimde bulunduğunu anlamamıza yardımcı olur."
[cookies.functional]
title = "İşlevsel"
text = "Bu çerezler, yorumlar ve gömülü içerik gibi özellikleri etkinleştirir."
+31
View File
@@ -23,6 +23,13 @@ darkMode = "Темна тема"
one = "Час читання: {{ .Count }} хв"
other = "Час читання: {{ .Count }} хв"
[article.alert]
note = "Примітка"
tip = "Порада"
important = "Важливо"
warning = "Попередження"
caution = "Обережно"
[notFound]
title = "Не знайдено"
subtitle = "Ця сторінка не існує"
@@ -46,3 +53,27 @@ darkMode = "Темна тема"
[footer]
builtWith = "Створено з {{ .Generator }}"
designedBy = "Тема {{ .Theme }}, дизайн {{ .DesignedBy }}"
[cookies]
title = "Згода на використання файлів cookie"
text = "Ми використовуємо файли cookie для покращення вашого досвіду перегляду та аналізу трафіку сайту."
acceptAll = "Прийняти все"
deny = "Відхилити"
managePreferences = "Керувати налаштуваннями"
settingsTitle = "Налаштування файлів cookie"
savePreferences = "Зберегти налаштування"
cancel = "Скасувати"
footerLink = "Налаштування файлів cookie"
commentsDisabled = "Коментарі вимкнено, доки ви не приймете функціональні файли cookie."
[cookies.necessary]
title = "Необхідні"
text = "Ці файли cookie необхідні для роботи сайту і не можуть бути вимкнені."
[cookies.analytics]
title = "Аналітика"
text = "Ці файли cookie допомагають нам зрозуміти, як відвідувачі взаємодіють з нашим сайтом."
[cookies.functional]
title = "Функціональні"
text = "Ці файли cookie вмикають такі функції, як коментарі та вбудований контент."
+31
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"
@@ -45,3 +52,27 @@ darkMode = "Chế độ nền tối"
[footer]
builtWith = "Built with {{ .Generator }}"
designedBy = "Theme {{ .Theme }} thiết kế bởi {{ .DesignedBy }}"
[cookies]
title = "Đồng ý cookie"
text = "Chúng tôi sử dụng cookie để nâng cao trải nghiệm duyệt web của bạn và phân tích lưu lượng truy cập trang web."
acceptAll = "Chấp nhận tất cả"
deny = "Từ chối"
managePreferences = "Quản lý tùy chọn"
settingsTitle = "Tùy chọn cookie"
savePreferences = "Lưu tùy chọn"
cancel = "Hủy"
footerLink = "Cài đặt cookie"
commentsDisabled = "Bình luận bị vô hiệu hóa cho đến khi bạn chấp nhận cookie chức năng."
[cookies.necessary]
title = "Cần thiết"
text = "Những cookie này là cần thiết để trang web hoạt động và không thể vô hiệu hóa."
[cookies.analytics]
title = "Phân tích"
text = "Những cookie này giúp chúng tôi hiểu cách khách truy cập tương tác với trang web của chúng tôi."
[cookies.functional]
title = "Chức năng"
text = "Những cookie này cho phép các tính năng như bình luận và nội dung nhúng."
+42 -20
View File
@@ -2,46 +2,68 @@ 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 }} 設計"
[cookies]
title = "Cookie 同意"
text = "我們使用 Cookie 來增強您的瀏覽體驗並分析網站流量。"
acceptAll = "接受全部"
deny = "拒絕"
managePreferences = "管理偏好設定"
settingsTitle = "Cookie 偏好設定"
savePreferences = "儲存偏好設定"
cancel = "取消"
footerLink = "Cookie 設定"
commentsDisabled = "評論已停用,直到您接受功能性 Cookie。"
[cookies.necessary]
title = "必要"
text = "這些 Cookie 是網站運作所必需的,無法停用。"
[cookies.analytics]
title = "分析"
text = "這些 Cookie 幫助我們了解訪客如何與我們的網站互動。"
[cookies.functional]
title = "功能性"
text = "這些 Cookie 啟用評論和嵌入內容等功能."
+34 -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 錯誤"
@@ -45,3 +43,27 @@ darkMode = "夜晚模式"
[footer]
builtWith = "使用 {{ .Generator }} 建立"
designedBy = "主題 {{ .Theme }} 由 {{ .DesignedBy }} 設計"
[cookies]
title = "Cookie 同意"
text = "我們使用 Cookie 來增強您的瀏覽體驗並分析網站流量。"
acceptAll = "接受全部"
deny = "拒絕"
managePreferences = "管理偏好設定"
settingsTitle = "Cookie 偏好設定"
savePreferences = "儲存偏好設定"
cancel = "取消"
footerLink = "Cookie 設定"
commentsDisabled = "評論已停用,直到您接受功能性 Cookie。"
[cookies.necessary]
title = "必要"
text = "這些 Cookie 是網站運作所必需的,無法停用。"
[cookies.analytics]
title = "分析"
text = "這些 Cookie 幫助我們了解訪客如何與我們的網站互動。"
[cookies.functional]
title = "功能性"
text = "這些 Cookie 啟用評論和嵌入內容等功能."
+31
View File
@@ -13,6 +13,13 @@ darkMode = "暗色模式"
lastUpdatedOn = "最后更新于"
readingTime = "阅读时长: {{ .Count }} 分钟"
[article.alert]
note = "备注"
tip = "提示"
important = "重要"
warning = "警告"
caution = "注意"
[notFound]
title = "404 错误"
subtitle = "页面不存在"
@@ -36,3 +43,27 @@ darkMode = "暗色模式"
[footer]
builtWith = "使用 {{ .Generator }} 构建"
designedBy = "主题 {{ .Theme }} 由 {{ .DesignedBy }} 设计"
[cookies]
title = "Cookie 同意"
text = "我们使用 Cookie 来增强您的浏览体验并分析网站流量。"
acceptAll = "接受所有"
deny = "拒绝"
managePreferences = "管理偏好设置"
settingsTitle = "Cookie 偏好设置"
savePreferences = "保存偏好设置"
cancel = "取消"
footerLink = "Cookie 设置"
commentsDisabled = "评论已禁用,直到您接受功能性 Cookie。"
[cookies.necessary]
title = "必要"
text = "这些 Cookie 是网站运行所必需的,无法禁用。"
[cookies.analytics]
title = "分析"
text = "这些 Cookie 帮助我们了解访问者如何与我们的网站互动。"
[cookies.functional]
title = "功能性"
text = "这些 Cookie 启用评论和嵌入内容等功能."
+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 -1
View File
@@ -1,4 +1,4 @@
{{ $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources) }}
<article class="{{ if $image }}has-image{{ end }}">
{{ partial "article/components/header" . }}
{{ partial "article/components/header" (dict "Page" . "IsList" true) }}
</article>
+1 -1
View File
@@ -1,5 +1,5 @@
<article class="{{ if .Params.image }}has-image {{ end }}main-article">
{{ partial "article/components/header" . }}
{{ partial "article/components/header" (dict "Page" . "IsList" false) }}
{{ if .Scratch.Get "TOCEnabled" }}
<aside class="article-toc">
@@ -1,7 +1,9 @@
{{- $IsList := .IsList -}}
{{- $Page := .Page -}}
<div class="article-details">
{{ if .Params.categories }}
{{ if $Page.Params.categories }}
<header class="article-category">
{{ range (.GetTerms "categories") }}
{{ range ($Page.GetTerms "categories") }}
{{ $color := partial "helper/color-from-str" .LinkTitle }}
{{ $BackgroundColor := default $color.BackgroundColor .Params.style.background }}
{{ $TextColor := default $color.TextColor .Params.style.color }}
@@ -14,28 +16,28 @@
<div class="article-title-wrapper">
<h2 class="article-title">
<a href="{{ .RelPermalink }}">
{{- .Title -}}
<a href="{{ $Page.RelPermalink }}">
{{- $Page.Title -}}
</a>
</h2>
{{ with .Params.description }}
{{ with $Page.Params.description }}
<h3 class="article-subtitle">
{{ . }}
</h3>
{{ end }}
</div>
{{ $showReadingTime := .Params.readingTime | default (.Site.Params.article.readingTime) }}
{{ $showDate := not .Date.IsZero }}
{{ $showReadingTime := $Page.Params.readingTime | default (.Site.Params.article.readingTime) }}
{{ $showDate := not $Page.Date.IsZero }}
{{ $showFooter := or $showDate $showReadingTime }}
{{ if $showFooter }}
<footer class="article-time">
{{ if $showDate }}
<div>
{{ partial "helper/icon" "date" }}
<time class="article-time--published" datetime='{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}'>
{{- .Date | time.Format .Site.Params.dateFormat.published -}}
<time class="article-time--published" datetime='{{ $Page.Date.Format "2006-01-02T15:04:05Z07:00" }}'>
{{- $Page.Date | time.Format $Page.Site.Params.dateFormat.published -}}
</time>
</div>
{{ end }}
@@ -44,21 +46,32 @@
<div>
{{ partial "helper/icon" "clock" }}
<time class="article-time--reading">
{{ T "article.readingTime" .ReadingTime }}
{{ T "article.readingTime" $Page.ReadingTime }}
</time>
</div>
{{ end }}
</footer>
{{ end }}
{{ if .IsTranslated }}
{{ if $Page.IsTranslated }}
<footer class="article-translations">
{{ partial "helper/icon" "language" }}
<div>
{{ range .Translations }}
{{ range $Page.Translations }}
<a href="{{ .RelPermalink }}" class="link">{{ .Language.LanguageName }}</a>
{{ end }}
</div>
</footer>
{{ end }}
{{ if and $Page.Site.Params.Article.List.ShowTags ($Page.GetTerms "tags") $IsList }}
<footer class="article-header-tags">
{{ partial "helper/icon" "tag" }}
<div>
{{ range ($Page.GetTerms "tags") }}
<a href="{{ .RelPermalink }}" class="link">{{ .LinkTitle }}</a>
{{ end }}
</div>
</footer>
{{ end }}
</div>
@@ -1,13 +1,15 @@
{{- $IsList := .IsList -}}
{{- $Page := .Page -}}
<header class="article-header">
{{- $image := partial "helper/image" (dict "Image" .Params.image "Resources" .Resources) -}}
{{- $image := partial "helper/image" (dict "Image" $Page.Params.image "Resources" $Page.Resources) -}}
{{ if $image }}
<div class="article-image">
<a href="{{ .RelPermalink }}">
<a href="{{ $Page.RelPermalink }}">
<img src="{{ $image.Permalink }}" width="{{ $image.Width }}" height="{{ $image.Height }}" loading="lazy"
alt="Featured image of post {{ .Title }}" />
alt="Featured image of post {{ $Page.Title }}" />
</a>
</div>
{{ end }}
{{ partialCached "article/components/details" . .RelPermalink }}
{{ partial "article/components/details" (dict "Page" $Page "IsList" $IsList) }}
</header>
@@ -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>
+45 -1
View File
@@ -1,3 +1,47 @@
{{ if .Site.Params.comments.enabled }}
{{ partial (printf "comments/provider/%s" .Site.Params.comments.provider) . }}
{{- $needsConsent := and .Site.Params.cookies.enabled .Site.Params.cookies.categories.functional -}}
{{- if $needsConsent -}}
{{/* Consent-gated comments - show placeholder until functional consent */}}
<div id="comments-consent-placeholder" class="consent-placeholder">
<p>{{ T "cookies.commentsDisabled" }}</p>
<button class="cookie-btn cookie-btn--primary" data-cookie-action="reopen">
{{ T "cookies.managePreferences" }}
</button>
</div>
<div id="comments-container" style="display: none;">
{{ partial (printf "comments/provider/%s" .Site.Params.comments.provider) . }}
</div>
<script>
(function() {
var placeholder = document.getElementById('comments-consent-placeholder');
var container = document.getElementById('comments-container');
function showComments() {
if (placeholder) placeholder.style.display = 'none';
if (container) container.style.display = 'block';
}
function hideComments() {
if (placeholder) placeholder.style.display = 'block';
if (container) container.style.display = 'none';
}
window.addEventListener('onCookieConsentChange', function(e) {
if (e.detail && e.detail.functional) {
showComments();
} else {
hideComments();
}
});
// Check if already consented
if (window.cookieConsent && window.cookieConsent.hasConsent('functional')) {
showComments();
}
})();
</script>
{{- else -}}
{{/* No consent required - load comments normally */}}
{{ partial (printf "comments/provider/%s" .Site.Params.comments.provider) . }}
{{- end -}}
{{ end }}
@@ -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>
+50
View File
@@ -0,0 +1,50 @@
{{- if not site.Config.Privacy.GoogleAnalytics.Disable -}}
{{- with site.Config.Services.GoogleAnalytics.ID -}}
{{- if strings.HasPrefix (lower .) "ua-" -}}
{{- warnf "Google Analytics 4 (GA4) replaced Google Universal Analytics (UA) effective 1 July 2023. See https://support.google.com/analytics/answer/11583528. Create a GA4 property and data stream, then replace the Google Analytics ID in your site configuration with the new value." -}}
{{- else -}}
{{/* Consent-gated Google Analytics - only loads after analytics consent */}}
<script>
(function() {
var gaId = {{ . }};
var loaded = false;
var respectDNT = {{ site.Config.Privacy.GoogleAnalytics.RespectDoNotTrack }};
function loadGA() {
if (loaded) return;
// Respect Do Not Track browser setting if configured
if (respectDNT) {
var dnt = (navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack);
if (dnt == "1" || dnt == "yes") {
return;
}
}
loaded = true;
var script = document.createElement('script');
script.async = true;
script.src = 'https://www.googletagmanager.com/gtag/js?id=' + gaId;
document.head.appendChild(script);
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', gaId);
}
window.addEventListener('onCookieConsentChange', function(e) {
if (e.detail && e.detail.analytics) {
loadGA();
}
});
if (window.cookieConsent && window.cookieConsent.hasConsent('analytics')) {
loadGA();
}
})();
</script>
{{- end -}}
{{- end -}}
{{- end -}}
+68
View File
@@ -0,0 +1,68 @@
{{- $cfg := .Site.Params.cookies -}}
{{- $categories := $cfg.categories -}}
<div id="cookie-consent-banner" class="cookie-banner" aria-hidden="true" role="dialog" aria-labelledby="cookie-banner-title">
<div class="cookie-banner__content">
<div class="cookie-banner__text">
<strong id="cookie-banner-title">{{ T "cookies.title" }}</strong>
<p>{{ T "cookies.text" }}</p>
</div>
<div class="cookie-banner__actions">
<button class="cookie-btn cookie-btn--secondary" data-cookie-action="deny">
{{ T "cookies.deny" }}
</button>
<button class="cookie-btn cookie-btn--primary" data-cookie-action="accept">
{{ T "cookies.acceptAll" }}
</button>
</div>
{{- if $cfg.showSettings }}
<button class="cookie-banner__settings-link" data-cookie-action="settings">
{{ T "cookies.managePreferences" }}
</button>
{{- end }}
{{/* Settings panel */}}
<div id="cookie-settings-panel" class="cookie-settings" aria-hidden="true">
<h3>{{ T "cookies.settingsTitle" }}</h3>
<div class="cookie-category">
<label>
<input type="checkbox" name="necessary" checked disabled />
<strong>{{ T "cookies.necessary.title" }}</strong>
</label>
<p>{{ T "cookies.necessary.text" }}</p>
</div>
{{- if $categories.analytics -}}
<div class="cookie-category">
<label>
<input type="checkbox" name="analytics" data-cookie-category="analytics" />
<strong>{{ T "cookies.analytics.title" }}</strong>
</label>
<p>{{ T "cookies.analytics.text" }}</p>
</div>
{{- end -}}
{{- if $categories.functional -}}
<div class="cookie-category">
<label>
<input type="checkbox" name="functional" data-cookie-category="functional" />
<strong>{{ T "cookies.functional.title" }}</strong>
</label>
<p>{{ T "cookies.functional.text" }}</p>
</div>
{{- end -}}
<div class="cookie-settings__actions">
<button class="cookie-btn cookie-btn--secondary" data-cookie-action="cancel">
{{ T "cookies.cancel" }}
</button>
<button class="cookie-btn cookie-btn--primary" data-cookie-action="save">
{{ T "cookies.savePreferences" }}
</button>
</div>
</div>
</div>
</div>
+9
View File
@@ -0,0 +1,9 @@
{{- if .Site.Params.cookies.enabled -}}
{{/* Render the banner HTML */}}
{{ partial "cookies/banner.html" . }}
{{/* Load the consent manager script */}}
{{- $opts := dict "minify" hugo.IsProduction -}}
{{- $script := resources.Get "ts/cookies.ts" | js.Build $opts | fingerprint -}}
<script type="text/javascript" src="{{ $script.RelPermalink }}" defer></script>
{{- end -}}
@@ -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>
+2 -2
View File
@@ -1,3 +1,3 @@
{{ partial "cookies/include.html" . }}
{{ partialCached "footer/components/script.html" . }}
{{ partialCached "footer/components/custom-font.html" . }}
{{ partial "footer/custom.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">
+15 -5
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,10 +23,15 @@
<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" . -}}
{{- if .Site.Params.cookies.enabled -}}
{{- partial "cookies/analytics.html" . -}}
{{- else -}}
{{- template "_internal/google_analytics.html" . -}}
{{- end -}}
{{- partial "head/custom.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 -}}
+5 -9
View File
@@ -9,7 +9,7 @@
{{ with .Site.Params.sidebar.avatar }}
{{- $avatar := partial "helper/image" (dict "Image" . "Resources" resources) -}}
<figure class="site-avatar">
<a href="{{ .Site.Home.RelPermalink }}">
<a href="{{ $.Site.Home.RelPermalink }}">
<img src="{{ $avatar.Permalink }}"
width="{{ $avatar.Width }}"
height="{{ $avatar.Height }}"
@@ -35,7 +35,7 @@
<li>
<a
href='{{ .URL }}'
{{ if eq (default true .Params.newTab) true }}target="_blank"{{ end }}
{{ if .Params.newTab }}target="_blank"{{ end }}
{{ with .Name }}title="{{ . }}"{{ end }}
rel="me"
>
@@ -53,13 +53,9 @@
{{ $currentPage := . }}
{{ range .Site.Menus.main }}
{{ $active := or (eq $currentPage.RelPermalink .URL) (or ($currentPage.HasMenuCurrent "main" .) ($currentPage.IsMenuCurrent "main" .)) }}
<li {{ if $active }} class='current' {{ end }}>
<a href='{{ .URL }}' {{ if eq .Params.newTab true }}target="_blank"{{ end }}>
{{ $icon := default .Pre .Params.Icon }}
{{ if .Pre }}
{{ warnf "Menu item [%s] is using [pre] field to set icon, please use [params.icon] instead.\nMore information: https://stack.jimmycai.com/config/menu" .URL }}
{{ end }}
{{ with $icon }}
<li {{ if $active }} class='current'{{ end }}>
<a href='{{ .URL }}' {{ if .Params.newTab }}target="_blank"{{ end }}>
{{ with .Params.Icon }}
{{ partial "helper/icon" . }}
{{ end }}
<span>{{- .Name -}}</span>
+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"