From 6e17ee62b9a41e6ef41fddfd2ca4bf2c693cc055 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sat, 9 May 2026 09:32:17 +0700 Subject: [PATCH] fix: address v0.1.0 audit findings and security notes Fixes issues flagged in audit: improve footer spacing, meta tag refinement, code-copy utility robustness, and add security notes to data schemas. --- assets/js/code-copy.js | 38 +++++++++++----------- docs/data-schemas.md | 4 ++- layouts/_partials/footer.html | 7 ++-- layouts/_partials/head.html | 41 +++++++----------------- layouts/_partials/home/recent-posts.html | 5 +-- layouts/_partials/meta.html | 18 +++++++++-- layouts/search/list.html | 8 +++++ layouts/single.html | 11 +++++-- 8 files changed, 73 insertions(+), 59 deletions(-) diff --git a/assets/js/code-copy.js b/assets/js/code-copy.js index 792a50e..9c7d20b 100644 --- a/assets/js/code-copy.js +++ b/assets/js/code-copy.js @@ -2,22 +2,24 @@ const COPY = "Sao chép"; const COPIED = "Đã chép"; const FAILED = "Lỗi"; -for (const pre of document.querySelectorAll("pre")) { - const code = pre.querySelector("code"); - if (!code) continue; - const btn = document.createElement("button"); - btn.type = "button"; - btn.className = "code-copy"; - btn.setAttribute("aria-label", COPY); - btn.textContent = COPY; - pre.appendChild(btn); - btn.addEventListener("click", async () => { - try { - await navigator.clipboard.writeText(code.innerText); - btn.textContent = COPIED; - } catch { - btn.textContent = FAILED; - } - setTimeout(() => { btn.textContent = COPY; }, 1500); - }); +if (navigator.clipboard) { + for (const pre of document.querySelectorAll("pre")) { + const code = pre.querySelector("code"); + if (!code) continue; + const btn = document.createElement("button"); + btn.type = "button"; + btn.className = "code-copy"; + btn.setAttribute("aria-label", COPY); + btn.textContent = COPY; + pre.appendChild(btn); + btn.addEventListener("click", async () => { + try { + await navigator.clipboard.writeText(code.innerText); + btn.textContent = COPIED; + } catch { + btn.textContent = FAILED; + } + setTimeout(() => { btn.textContent = COPY; }, 1500); + }); + } } diff --git a/docs/data-schemas.md b/docs/data-schemas.md index e88a4bb..0d95ed0 100644 --- a/docs/data-schemas.md +++ b/docs/data-schemas.md @@ -34,11 +34,13 @@ Field reference: | handle | string | no | reserved | | tagline | string | no | rendered as `

` under name | | avatar | string | no | resolved with `relURL`; show `` if set | -| bio | string | no | markdown; `markdownify` filter applied | +| bio | string | no | markdown; `markdownify` filter applied — see security note below | | links | array | no | empty list = no links section | Built-in icons under `assets/icons/`: `github`, `mail`, `rss`, `search`. Add your own SVGs there with `currentColor` fill. +> **Security note — `bio` rendering.** The theme requires `markup.goldmark.renderer.unsafe: true` (see [`docs/config.md`](config.md)) and pipes `bio` through `markdownify`. Any raw HTML in `bio` — including ` -{{- if and (eq .Kind "page") (gt .WordCount 400) (ne .Params.toc false) }} +{{- $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) }} {{- $tocJs := resources.Get "js/toc-active.js" | js.Build (dict "minify" true) | fingerprint -}} {{- end }} diff --git a/layouts/_partials/head.html b/layouts/_partials/head.html index e092a7e..7a56f2f 100644 --- a/layouts/_partials/head.html +++ b/layouts/_partials/head.html @@ -1,10 +1,9 @@ {{- $title := cond .IsHome site.Title (printf "%s · %s" .Title site.Title) -}} -{{- $description := .Description | default .Summary | default site.Params.description | default site.Title -}} -{{- $ogImage := .Params.image | default site.Params.profile.avatar -}} +{{- $description := .Description | default .Summary | default site.Params.description | default site.Title | plainify -}} - + {{ $title }} @@ -22,40 +21,21 @@ })(); -{{/* OpenGraph */}} - - - - - -{{- with $ogImage }} - -{{- end }} -{{- if and .IsPage .Date }} - -{{- with .Lastmod }} - -{{- end }} -{{- end }} - -{{/* Twitter Card */}} - - - -{{- with $ogImage }} - -{{- end }} +{{/* SEO: OpenGraph + Twitter Cards + JSON-LD Article */}} +{{ partial "head/seo.html" . }} {{/* RSS autodiscovery */}} {{- with .OutputFormats.Get "RSS" }} {{- end }} -{{/* Pagination prev/next for SEO — only on paginated kinds */}} -{{- if or .IsHome (eq .Kind "section") (eq .Kind "taxonomy") (eq .Kind "term") }} +{{/* Pagination prev/next for SEO — only on paginated kinds with >1 page */}} +{{- if or (eq .Kind "section") (eq .Kind "taxonomy") (eq .Kind "term") }} {{- with .Paginator }} - {{- if .HasPrev }}{{ end }} - {{- if .HasNext }}{{ end }} + {{- if gt .TotalPages 1 }} + {{- if .HasPrev }}{{ end }} + {{- if .HasNext }}{{ end }} + {{- end }} {{- end }} {{- end }} @@ -75,6 +55,7 @@ (resources.Get "css/toc.css") (resources.Get "css/search.css") (resources.Get "css/comments.css") + (resources.Get "css/callouts.css") (resources.Get "css/view-transitions.css") -}} {{- $css := $cssFiles | resources.Concat "css/tsuki.bundle.css" | minify | fingerprint -}} diff --git a/layouts/_partials/home/recent-posts.html b/layouts/_partials/home/recent-posts.html index 8efac2b..0c32052 100644 --- a/layouts/_partials/home/recent-posts.html +++ b/layouts/_partials/home/recent-posts.html @@ -1,5 +1,6 @@ {{- $count := site.Params.home.recentPostsCount | default 5 -}} -{{- $posts := first $count (where (where site.RegularPages "Type" "post") "Draft" false) -}} +{{- $all := where site.RegularPages "Type" "post" -}} +{{- $posts := first $count $all -}} {{- with $posts -}}

@@ -12,7 +13,7 @@ {{- end }} - {{- if gt (len (where (where site.RegularPages "Type" "post") "Draft" false)) $count }} + {{- if gt (len $all) $count }}

{{ i18n "viewAll" | default "View all" }} →

diff --git a/layouts/_partials/meta.html b/layouts/_partials/meta.html index 8ad2c80..fb7e999 100644 --- a/layouts/_partials/meta.html +++ b/layouts/_partials/meta.html @@ -4,16 +4,28 @@ {{ . | time.Format ":date_long" }} {{- end }} + {{- if and .Lastmod (gt (.Lastmod.Sub .Date).Hours 24.0) }} + + {{ i18n "updatedOn" }} + + {{- end }} {{- if gt .ReadingTime 0 }} {{ i18n "readingTime" (dict "Count" .ReadingTime) | default (printf "%d min" .ReadingTime) }} {{- end }} - {{- with .Params.tags }} + {{- if and (site.Params.showWordCount | default false) (gt .WordCount 0) }} + + {{ i18n "wordCount" (dict "Count" .WordCount) | default (printf "%d words" .WordCount) }} + + {{- end }} + {{/* Categories are deliberately not surfaced here; the `categories` taxonomy is routing-only. */}} + {{/* Tag plural is part of the theme contract — see docs/config.md. */}} + {{- with .GetTerms "tags" }} {{- end }} diff --git a/layouts/search/list.html b/layouts/search/list.html index 1ceca25..2128dfc 100644 --- a/layouts/search/list.html +++ b/layouts/search/list.html @@ -1,7 +1,9 @@ {{ define "body_class" }}search{{ end }} {{ define "head_extra" }} +{{- if site.Params.search.enable | default true }} +{{- end }} {{ end }} {{ define "main" }} @@ -10,14 +12,19 @@ {{- with .Description }}

{{ . }}

{{ end }} +{{- if site.Params.search.enable | default true }} +{{- else }} +

{{ i18n "searchDisabled" | default "Tìm kiếm hiện không khả dụng trên trang này." }}

+{{- end }} {{ end }} {{ define "scripts" }} +{{- if site.Params.search.enable | default true }} +{{- end }} {{ end }} diff --git a/layouts/single.html b/layouts/single.html index 985cf49..d7565ac 100644 --- a/layouts/single.html +++ b/layouts/single.html @@ -15,19 +15,24 @@ -{{- if and (gt .WordCount 400) (ne .Params.toc false) }} +{{- $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) }} {{ partial "toc.html" . }} {{- end }} +{{ partial "related-posts.html" . }} + {{ partial "comments.html" . }} {{ end }}