From 470bfb528e19fbf2f557577768dffd26c1eb80b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9B=E3=81=84=E3=81=AF=E3=81=AA?= Date: Tue, 2 Jun 2026 23:05:23 +0800 Subject: [PATCH] fix: sanitize post title for valid CSS view-transition-name (#650) --- src/components/Card.astro | 4 ++-- src/pages/posts/[...slug]/index.astro | 3 ++- src/utils/toTransitionName.ts | 26 ++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 src/utils/toTransitionName.ts diff --git a/src/components/Card.astro b/src/components/Card.astro index e6144a8..3beff57 100644 --- a/src/components/Card.astro +++ b/src/components/Card.astro @@ -1,7 +1,7 @@ --- import type { CollectionEntry } from "astro:content"; import { getPostUrl } from "@/utils/getPostPaths"; -import { slugifyStr } from "@/utils/slugify"; +import { toTransitionName } from "@/utils/toTransitionName"; import Datetime from "./Datetime.astro"; type Props = { @@ -22,7 +22,7 @@ const { title, description, ...props } = data; "focus-visible:no-underline focus-visible:underline-offset-0", ]} > - + {title} diff --git a/src/pages/posts/[...slug]/index.astro b/src/pages/posts/[...slug]/index.astro index eabfaaf..d9df037 100644 --- a/src/pages/posts/[...slug]/index.astro +++ b/src/pages/posts/[...slug]/index.astro @@ -8,6 +8,7 @@ import Tag from "@/components/Tag.astro"; import { getPostSlug, getPostUrl } from "@/utils/getPostPaths"; import { getSortedPosts } from "@/utils/getSortedPosts"; import { slugifyStr } from "@/utils/slugify"; +import { toTransitionName } from "@/utils/toTransitionName"; import EditPost from "./_components/EditPost.astro"; import ShareLinks from "./_components/ShareLinks.astro"; import BackButton from "./_components/BackButton.astro"; @@ -111,7 +112,7 @@ const ogImage = ogImageUrl data-pagefind-body >

{title} diff --git a/src/utils/toTransitionName.ts b/src/utils/toTransitionName.ts new file mode 100644 index 0000000..f0333b6 --- /dev/null +++ b/src/utils/toTransitionName.ts @@ -0,0 +1,26 @@ +import { slugifyStr } from "./slugify"; + +/** + * Produce a valid CSS for view-transition-name. + * CSS idents only allow [a-zA-Z0-9_-] plus Unicode U+00A0+. + * Non-ASCII chars are hex-encoded, ASCII special chars (:, /, etc.) + * are replaced with hyphens to keep the browser from ignoring the name. + */ +export const toTransitionName = (str: string): string => { + const base = slugifyStr(str.replaceAll(".", "-")); + let result = base + // encode non-ASCII chars (Chinese, Japanese, etc.) + .replace( + /[^\x00-\x7F]/gu, + c => "u" + c.codePointAt(0)!.toString(16).padStart(6, "0") + ) + // replace any remaining invalid chars (colons, slashes, etc.) + .replace(/[^a-zA-Z0-9_-]/g, "-") + // collapse consecutive hyphens and trim + .replace(/-+/g, "-") + .replace(/^-+|-+$/g, ""); + // CSS ident must not start with a digit + if (/^\d/.test(result)) result = "p-" + result; + if (!result) result = "post"; + return result; +};