From a7ca99f59598654e924ba2132e7f5ff253ef0b2c Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sat, 9 May 2026 09:32:29 +0700 Subject: [PATCH] feat: native Markdown callouts, reading time, archetype scaffold Add blockquote-to-callout rendering, reading time estimate, improved default archetype with demo callout content and Vietnamese translations. --- archetypes/default.md | 3 + assets/css/callouts.css | 34 ++++++++++++ docs/customization.md | 55 +++++++++++++++++++ .../content/post/2026/05/07/sample/index.md | 23 +++++++- i18n/vi.yml | 51 +++++++++++++++-- layouts/_markup/render-blockquote.html | 25 +++++++++ 6 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 assets/css/callouts.css create mode 100644 layouts/_markup/render-blockquote.html diff --git a/archetypes/default.md b/archetypes/default.md index 7f4aced..b5393be 100644 --- a/archetypes/default.md +++ b/archetypes/default.md @@ -2,6 +2,9 @@ title: "{{ replace .Name "-" " " | title }}" date: {{ .Date }} draft: true +description: "" tags: [] categories: [] +cover: + image: "" --- diff --git a/assets/css/callouts.css b/assets/css/callouts.css new file mode 100644 index 0000000..af0861d --- /dev/null +++ b/assets/css/callouts.css @@ -0,0 +1,34 @@ +/* tsuki: callouts (Markdown alert syntax: > [!note], > [!warning], etc.) */ + +.callout { + margin-block: var(--tsuki-space-4); + padding: var(--tsuki-space-3) var(--tsuki-space-4); + border-inline-start: 3px solid var(--tsuki-callout, var(--tsuki-accent)); + background: var(--tsuki-callout-bg, transparent); + border-radius: var(--tsuki-radius); + color: var(--tsuki-fg); + font-style: normal; +} +.callout :is(p:last-child) { margin-block-end: 0; } +.callout-title { + font-weight: 600; + margin: 0 0 var(--tsuki-space-2); + color: var(--tsuki-callout, var(--tsuki-accent)); + font-size: var(--tsuki-fs-sm); + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.callout-note { --tsuki-callout: #2563eb; --tsuki-callout-bg: #2563eb14; } +.callout-tip { --tsuki-callout: #16a34a; --tsuki-callout-bg: #16a34a14; } +.callout-important { --tsuki-callout: #7c3aed; --tsuki-callout-bg: #7c3aed14; } +.callout-warning { --tsuki-callout: #d97706; --tsuki-callout-bg: #d9770614; } +.callout-caution { --tsuki-callout: #dc2626; --tsuki-callout-bg: #dc262614; } + +:where([data-theme="dark"]) { + .callout-note { --tsuki-callout: #6ea1ff; --tsuki-callout-bg: #6ea1ff1f; } + .callout-tip { --tsuki-callout: #4ade80; --tsuki-callout-bg: #4ade801f; } + .callout-important { --tsuki-callout: #a78bfa; --tsuki-callout-bg: #a78bfa1f; } + .callout-warning { --tsuki-callout: #fbbf24; --tsuki-callout-bg: #fbbf241f; } + .callout-caution { --tsuki-callout: #f87171; --tsuki-callout-bg: #f871711f; } +} diff --git a/docs/customization.md b/docs/customization.md index d0d7c86..143b27d 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -102,6 +102,61 @@ Set CSS variables inline via a frontmatter `style` you wire up yourself, or use Then style `body.section-work { --tsuki-accent: #...; }` in your custom CSS. +## Callouts (admonitions) + +Use Hugo's native Markdown alert syntax (Hugo 0.140+) — no shortcode needed: + +```markdown +> [!note] +> Useful side information. + +> [!tip] +> Reader-friendly hint. + +> [!important] +> Hard requirement. + +> [!warning] +> Heads-up about a pitfall. + +> [!caution] +> Risk of breakage. +``` + +Titles localize via `i18n/vi.yml` keys `calloutNote`, `calloutTip`, `calloutImportant`, `calloutWarning`, `calloutCaution`. Override per-callout with `> [!note] Custom title`. + +Plain `> blockquote` still renders as the regular muted blockquote — only `[!type]` triggers callout styling. + +**Override callout colors** in your custom CSS — each callout type exposes the `--tsuki-callout` and `--tsuki-callout-bg` tokens through its modifier class: + +```css +/* your-site/assets/css/custom.css */ +.callout-note { --tsuki-callout: #ff5d8f; --tsuki-callout-bg: #ff5d8f1f; } +``` + +## Show word count + +Reading time is shown on every post by default. Add word count under it with: + +```yaml +params: + showWordCount: true # default false +``` + +The byline reads `5 phút đọc · 1024 từ` (translated via the `wordCount` i18n key). + +## Accessibility & SEO defaults + +The theme includes accessibility features that require no configuration: + +- **Skip-link** — first focusable element on every page; jumps to `
` (WCAG 2.1 M3) +- **Focus rings** — visible outline via `--tsuki-accent` on `:focus-visible` for keyboard navigation +- **Lastmod byline** — shows "Cập nhật {date}" when modified date is ≥24h newer than publish date +- **Lazy-load images** — all in-content images get `loading="lazy" decoding="async"` via render hook +- **External link security** — all Markdown links to `http://` / `https://` / `//` get `rel="noopener noreferrer"` automatically + +All of these are transparent — no override hooks needed unless you want to customize the markup. + ## Disable features Every interactive feature has a kill switch: diff --git a/exampleSite/content/post/2026/05/07/sample/index.md b/exampleSite/content/post/2026/05/07/sample/index.md index 0436673..cdce362 100644 --- a/exampleSite/content/post/2026/05/07/sample/index.md +++ b/exampleSite/content/post/2026/05/07/sample/index.md @@ -7,7 +7,7 @@ categories: ["demo"] description: "Bài viết mẫu cho tsuki theme." --- -Đây là một bài viết mẫu để kiểm tra layout của tsuki. +Đây là một bài viết mẫu để kiểm tra layout của tsuki. Bài viết này tham khảo [tài liệu chính thức của Hugo](https://gohugo.io/documentation/) và [đặc tả Markdown của CommonMark](https://commonmark.org/). ## Heading thứ hai @@ -21,3 +21,24 @@ def hello(): - Mục một - Mục hai - Mục ba + +## Trích dẫn + +> Một blockquote thông thường — không có `[!type]`, vẫn hiển thị kiểu mặc định nghiêng và viền nhạt. + +## Hộp thông báo (callouts) + +> [!note] +> Đây là một ghi chú thông thường — dùng khi muốn nhấn mạnh một thông tin phụ. + +> [!tip] +> Mẹo: dùng `> [!tip]` thay cho blockquote khi muốn nội dung nổi bật hơn. + +> [!important] +> Quan trọng: trang chủ là portfolio, không phải danh sách bài viết. + +> [!warning] +> Cảnh báo: `taxonomies: { tag: tags }` là một phần của hợp đồng theme. + +> [!caution] +> Thận trọng: chỉnh sửa `markup.goldmark.renderer.unsafe` có thể vô hiệu hoá footnote. diff --git a/i18n/vi.yml b/i18n/vi.yml index f6dac74..331a337 100644 --- a/i18n/vi.yml +++ b/i18n/vi.yml @@ -1,6 +1,24 @@ - id: readingTime translation: "{{ .Count }} phút đọc" +- id: wordCount + translation: "{{ .Count }} từ" + +- id: calloutNote + translation: "Ghi chú" + +- id: calloutTip + translation: "Mẹo" + +- id: calloutImportant + translation: "Quan trọng" + +- id: calloutWarning + translation: "Cảnh báo" + +- id: calloutCaution + translation: "Thận trọng" + - id: postedOn translation: "Đăng vào" @@ -34,12 +52,6 @@ - id: tableOfContents translation: "Mục lục" -- id: previousPage - translation: "Trang trước" - -- id: nextPage - translation: "Trang tiếp" - - id: prev translation: "Trước" @@ -87,3 +99,30 @@ - id: copyright translation: "© {{ .Year }} {{ .Author }}" + +- id: comments + translation: "Bình luận" + +- id: month + translation: "Tháng {{ .Number }}" + +- id: pageNotFound + translation: "Trang không tồn tại." + +- id: backHome + translation: "Về trang chủ" + +- id: searchSuggestion + translation: "Không có kết quả cho [SEARCH_TERM]. Thử các từ khoá: [DIFFERENT_TERMS]." + +- id: altSearch + translation: "Không có kết quả cho [SEARCH_TERM]. Đang hiển thị kết quả cho [DIFFERENT_TERM]." + +- id: searchDisabled + translation: "Tìm kiếm hiện không khả dụng trên trang này." + +- id: skipToContent + translation: "Đến nội dung chính" + +- id: relatedPosts + translation: "Bài viết liên quan" diff --git a/layouts/_markup/render-blockquote.html b/layouts/_markup/render-blockquote.html new file mode 100644 index 0000000..88f575d --- /dev/null +++ b/layouts/_markup/render-blockquote.html @@ -0,0 +1,25 @@ +{{- /* + Blockquote render hook with native Markdown callout support (Hugo 0.140+). + Markdown: + > [!note] Optional title + > Body content here. + Renders to a styled callout. Plain blockquotes pass through unchanged. + Supported alert types: note, tip, important, warning, caution. +*/ -}} +{{- if eq .Type "alert" -}} +{{- $type := .AlertType | lower -}} +{{- $title := .AlertTitle -}} +{{- if not $title -}} + {{- $title = i18n (printf "callout%s" ($type | title)) | default ($type | title) -}} +{{- end -}} +
+

{{ $title }}

+ {{ .Text }} +
+{{- else -}} +
+ {{ .Text }} +
+{{- end -}}