From 5d73a139ea0ba13636640dc7ff8071385e0268c7 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sat, 9 May 2026 09:45:57 +0700 Subject: [PATCH] refactor: consolidate TOC gate, inline single-call SEO partial, prune unused i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extract duplicated TOC predicate to _partials/toc-enabled.html (single.html + footer.html were both computing the same 6-line gate). - Inline _partials/head/og-image.html into head/seo.html — single caller, the fallback chain is the contract; the partial wasn't earning its override surface. - Drop 6 unused i18n keys: postedOn, tags, categories, archive, noResults, copyright. None referenced by any template (verified via grep). Also removed an accidental duplicate of the callout title block; vi.yml reorganized into thematic groups. Behavior unchanged. Smoke (11 checks) + Hugo build clean. CSS bundle unchanged at 3962 B gz. --- CHANGELOG.md | 9 ++ i18n/vi.yml | 130 ++++++++++++--------------- layouts/_partials/footer.html | 5 +- layouts/_partials/head/og-image.html | 11 --- layouts/_partials/head/seo.html | 7 +- layouts/_partials/toc-enabled.html | 13 +++ layouts/single.html | 5 +- 7 files changed, 86 insertions(+), 94 deletions(-) delete mode 100644 layouts/_partials/head/og-image.html create mode 100644 layouts/_partials/toc-enabled.html diff --git a/CHANGELOG.md b/CHANGELOG.md index a5767d2..95efe60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to tsuki will be documented here. Format follows [Keep a Cha ## [Unreleased] +### Changed + +- **TOC gate consolidated** to `_partials/toc-enabled.html` — single source for the `params.toc.{enable,minWordCount}` + per-page `toc: false` predicate, called from `single.html` (TOC partial) and `_partials/footer.html` (toc-active.js loader). Was duplicated 6-line logic in two sites; now one partial. No behavior change. +- **`_partials/head/og-image.html` inlined into `head/seo.html`** — single-call partial removed; the OG image fallback chain (`cover.image` → `image` → `params.og.fallbackImage` → `data/profile.yaml: avatar`) is now expressed once at the top of `seo.html`. Override surface unchanged: replace `head/seo.html` to customize. + +### Removed + +- **Six unused i18n keys** — `postedOn`, `tags`, `categories`, `archive`, `noResults`, `copyright` were defined in `vi.yml` but never referenced by any template. Dropped along with one accidental duplicate of the callout title block. `vi.yml` reorganized into thematic groups (search/feeds/UI/callouts). + ### Added - **CI smoke tests** (`scripts/smoke-tests.sh`) run after Hugo build — assert JSON-LD on post / not on home, OG/Twitter image emit, skip-link + `
`, render-link rel marker, reading-time byline, related-posts aside, CSS budget. 11 checks, fails the workflow on regression. Local-runnable: `./scripts/smoke-tests.sh`. diff --git a/i18n/vi.yml b/i18n/vi.yml index 331a337..ddb6c00 100644 --- a/i18n/vi.yml +++ b/i18n/vi.yml @@ -4,45 +4,54 @@ - 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" - - id: updatedOn translation: "Cập nhật" -- id: tags - translation: "Thẻ" - -- id: categories - translation: "Chuyên mục" - -- id: archive - translation: "Lưu trữ" - - id: search translation: "Tìm kiếm" - id: searchPlaceholder translation: "Tìm bài viết..." +- id: searchNoScript + translation: "Tìm kiếm yêu cầu JavaScript." + +- id: searchDisabled + translation: "Tìm kiếm hiện không khả dụng trên trang này." + +- id: clearSearch + translation: "Xoá" + +- id: loadMore + translation: "Xem thêm" + +- id: filters + translation: "Bộ lọc" + +- id: zeroResults + translation: "Không có kết quả cho [SEARCH_TERM]" + +- id: oneResult + translation: "[COUNT] kết quả cho [SEARCH_TERM]" + +- id: manyResults + translation: "[COUNT] kết quả cho [SEARCH_TERM]" + +- id: searching + translation: "Đang tìm [SEARCH_TERM]..." + +- 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: recentPosts translation: "Bài viết gần đây" +- id: relatedPosts + translation: "Bài viết liên quan" + - id: featuredProjects translation: "Dự án nổi bật" @@ -64,47 +73,20 @@ - id: archiveEmpty translation: "Chưa có bài viết." -- id: toggleTheme - translation: "Đổi giao diện" - -- id: poweredBy - translation: "Chạy bằng" - -- id: noResults - translation: "Không có kết quả." - -- id: searchNoScript - translation: "Tìm kiếm yêu cầu JavaScript." - -- id: clearSearch - translation: "Xoá" - -- id: loadMore - translation: "Xem thêm" - -- id: filters - translation: "Bộ lọc" - -- id: zeroResults - translation: "Không có kết quả cho [SEARCH_TERM]" - -- id: manyResults - translation: "[COUNT] kết quả cho [SEARCH_TERM]" - -- id: oneResult - translation: "[COUNT] kết quả cho [SEARCH_TERM]" - -- id: searching - translation: "Đang tìm [SEARCH_TERM]..." - -- id: copyright - translation: "© {{ .Year }} {{ .Author }}" +- id: month + translation: "Tháng {{ .Number }}" - id: comments translation: "Bình luận" -- id: month - translation: "Tháng {{ .Number }}" +- id: toggleTheme + translation: "Đổi giao diện" + +- id: skipToContent + translation: "Đến nội dung chính" + +- id: poweredBy + translation: "Chạy bằng" - id: pageNotFound translation: "Trang không tồn tại." @@ -112,17 +94,17 @@ - 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: calloutNote + translation: "Ghi chú" -- id: altSearch - translation: "Không có kết quả cho [SEARCH_TERM]. Đang hiển thị kết quả cho [DIFFERENT_TERM]." +- id: calloutTip + translation: "Mẹo" -- id: searchDisabled - translation: "Tìm kiếm hiện không khả dụng trên trang này." +- id: calloutImportant + translation: "Quan trọng" -- id: skipToContent - translation: "Đến nội dung chính" +- id: calloutWarning + translation: "Cảnh báo" -- id: relatedPosts - translation: "Bài viết liên quan" +- id: calloutCaution + translation: "Thận trọng" diff --git a/layouts/_partials/footer.html b/layouts/_partials/footer.html index 84c4772..90e04de 100644 --- a/layouts/_partials/footer.html +++ b/layouts/_partials/footer.html @@ -16,10 +16,7 @@ -}} {{- $js := $jsFiles | resources.Concat "js/tsuki.bundle.js" | js.Build (dict "minify" true) | fingerprint -}} -{{- $tocCfg := site.Params.toc | default dict -}} -{{- $tocEnabled := $tocCfg.enable | default true -}} -{{- $tocMin := $tocCfg.minWordCount | default 400 -}} -{{- if and (eq .Kind "page") $tocEnabled (gt .WordCount $tocMin) (ne .Params.toc false) }} +{{- if partial "toc-enabled.html" . }} {{- $tocJs := resources.Get "js/toc-active.js" | js.Build (dict "minify" true) | fingerprint -}} {{- end }} diff --git a/layouts/_partials/head/og-image.html b/layouts/_partials/head/og-image.html deleted file mode 100644 index 23c3d0b..0000000 --- a/layouts/_partials/head/og-image.html +++ /dev/null @@ -1,11 +0,0 @@ -{{- /* - Resolves the OG/Twitter image URL for the current page. - Order: per-post `cover.image` → per-post `image` (legacy) → `params.og.fallbackImage` → `data/profile.yaml: avatar`. - Returns the bare site-relative path; caller applies `absURL`. -*/ -}} -{{- $img := "" -}} -{{- with .Params.cover -}}{{- $img = .image -}}{{- end -}} -{{- if not $img -}}{{- $img = .Params.image -}}{{- end -}} -{{- if not $img -}}{{- $img = site.Params.og.fallbackImage -}}{{- end -}} -{{- if not $img -}}{{- with site.Data.profile -}}{{- $img = .avatar -}}{{- end -}}{{- end -}} -{{- $img -}} diff --git a/layouts/_partials/head/seo.html b/layouts/_partials/head/seo.html index da505b7..97741ed 100644 --- a/layouts/_partials/head/seo.html +++ b/layouts/_partials/head/seo.html @@ -7,7 +7,12 @@ {{- $rawDesc := .Description | default .Summary | default site.Params.description -}} {{- $desc := "" -}} {{- with $rawDesc -}}{{- $desc = . | plainify | truncate 200 -}}{{- end -}} -{{- $img := partial "head/og-image.html" . -}} +{{- /* OG/Twitter image: per-post `cover.image` → per-post `image` → site `og.fallbackImage` → `data/profile.yaml: avatar`. */ -}} +{{- $img := "" -}} +{{- with .Params.cover -}}{{- $img = .image -}}{{- end -}} +{{- if not $img -}}{{- $img = .Params.image -}}{{- end -}} +{{- if not $img -}}{{- $img = site.Params.og.fallbackImage -}}{{- end -}} +{{- if not $img -}}{{- with site.Data.profile -}}{{- $img = .avatar -}}{{- end -}}{{- end -}} {{- /* OG locale: map common Hugo lang codes to OpenGraph IETF-with-region. */ -}} {{- $ogLocale := site.Params.og.locale | default (index (dict "vi" "vi_VN" "en" "en_US" "ja" "ja_JP" "zh" "zh_CN" "ko" "ko_KR" "fr" "fr_FR" "de" "de_DE") site.Language.Lang) | default site.LanguageCode -}} diff --git a/layouts/_partials/toc-enabled.html b/layouts/_partials/toc-enabled.html new file mode 100644 index 0000000..4502c82 --- /dev/null +++ b/layouts/_partials/toc-enabled.html @@ -0,0 +1,13 @@ +{{- /* + Single source of truth for whether to render the TOC for the current page. + Returns the literal string "true" when enabled, empty otherwise. + Callers: `single.html` (TOC partial), `footer.html` (toc-active.js loader). + Site config: `params.toc.enable` (default true), `params.toc.minWordCount` (default 400). + Per-page override: frontmatter `toc: false` always wins. +*/ -}} +{{- $cfg := site.Params.toc | default dict -}} +{{- $enabled := $cfg.enable | default true -}} +{{- $min := $cfg.minWordCount | default 400 -}} +{{- if and $enabled (eq .Kind "page") (gt .WordCount $min) (ne .Params.toc false) -}} +true +{{- end -}} diff --git a/layouts/single.html b/layouts/single.html index d7565ac..26c5412 100644 --- a/layouts/single.html +++ b/layouts/single.html @@ -25,10 +25,7 @@ -{{- $tocCfg := site.Params.toc | default dict -}} -{{- $tocEnabled := $tocCfg.enable | default true -}} -{{- $tocMin := $tocCfg.minWordCount | default 400 -}} -{{- if and $tocEnabled (gt .WordCount $tocMin) (ne .Params.toc false) }} +{{- if partial "toc-enabled.html" . }} {{ partial "toc.html" . }} {{- end }}