diff --git a/src/components/PostMeta.astro b/src/components/PostMeta.astro index 12f270e3..0a67979b 100644 --- a/src/components/PostMeta.astro +++ b/src/components/PostMeta.astro @@ -3,7 +3,7 @@ import { Icon } from "astro-icon/components"; import I18nKey from "../i18n/i18nKey"; import { i18n } from "../i18n/translation"; import { formatDateToYYYYMMDD } from "../utils/date-utils"; -import { url } from "../utils/url-utils"; +import { url, getTagUrl } from "../utils/url-utils"; interface Props { class: string; @@ -70,10 +70,10 @@ const className = Astro.props.class;
{(tags && tags.length > 0) && tags.map((tag, i) => (
/
- - {tag} + {tag.trim()} ))} {!(tags && tags.length > 0) &&
{i18n(I18nKey.noTags)}
} diff --git a/src/components/widget/Categories.astro b/src/components/widget/Categories.astro index 6ae60b2c..db67c0e1 100644 --- a/src/components/widget/Categories.astro +++ b/src/components/widget/Categories.astro @@ -27,11 +27,11 @@ const style = Astro.props.style; > {categories.map((c) => - {c.name} + {c.name.trim()} )} \ No newline at end of file diff --git a/src/components/widget/Tags.astro b/src/components/widget/Tags.astro index 06a14ec9..3ead19db 100644 --- a/src/components/widget/Tags.astro +++ b/src/components/widget/Tags.astro @@ -3,7 +3,7 @@ import I18nKey from "../../i18n/i18nKey"; import { i18n } from "../../i18n/translation"; import { getTagList } from "../../utils/content-utils"; -import { url } from "../../utils/url-utils"; +import { getTagUrl } from "../../utils/url-utils"; import ButtonTag from "../control/ButtonTag.astro"; import WidgetLayout from "./WidgetLayout.astro"; @@ -23,8 +23,8 @@ const style = Astro.props.style;
{tags.map(t => ( - - {t.name} + + {t.name.trim()} ))}
diff --git a/src/content/posts/cjk-test.md b/src/content/posts/cjk-test.md new file mode 100644 index 00000000..53d3bb51 --- /dev/null +++ b/src/content/posts/cjk-test.md @@ -0,0 +1,12 @@ +--- +title: CJK edge case for test +published: 2025-05-04 +updated: 2025-05-04 +description: 'CJK Test' +image: '' +tags: [C#, テスト, 技术, Fuwari] +category: '技术' +draft: true +--- + +CJK Test \ No newline at end of file diff --git a/src/pages/archive/category/[category].astro b/src/pages/archive/category/[category].astro index 8972a5dc..c31673dd 100644 --- a/src/pages/archive/category/[category].astro +++ b/src/pages/archive/category/[category].astro @@ -7,16 +7,37 @@ import { getCategoryList } from "@utils/content-utils"; export async function getStaticPaths() { const categories = await getCategoryList(); - return categories.map((category) => { + + const standardPaths = categories.map((category) => { return { params: { - category: encodeURIComponent(category.name), + category: encodeURIComponent(category.name.trim()), + }, + props: { + decodedCategory: category.name.trim(), }, }; }); + + const nonEncodedCJKPaths = categories + .filter((category) => + /[\u3000-\u9fff\uac00-\ud7af\u4e00-\u9faf]/.test(category.name), + ) + .map((category) => ({ + params: { + category: category.name.trim(), // Do not encode CJK characters + }, + props: { + decodedCategory: category.name.trim(), + }, + })); + + return [...standardPaths, ...nonEncodedCJKPaths]; } -const category = decodeURIComponent(Astro.params.category as string); +const { decodedCategory } = Astro.props; +const category = + decodedCategory || decodeURIComponent(Astro.params.category as string); --- diff --git a/src/pages/archive/tag/[tag].astro b/src/pages/archive/tag/[tag].astro index 30b4463c..34eedd13 100644 --- a/src/pages/archive/tag/[tag].astro +++ b/src/pages/archive/tag/[tag].astro @@ -4,29 +4,61 @@ import I18nKey from "@i18n/i18nKey"; import { i18n } from "@i18n/translation"; import MainGridLayout from "@layouts/MainGridLayout.astro"; import { getSortedPosts } from "@utils/content-utils"; +import { decodePathSegment, encodePathSegment } from "@utils/encoding-utils"; export async function getStaticPaths() { const posts = await getSortedPosts(); - // タグを集めるための Set の型を指定 const allTags = posts.reduce>((acc, post) => { - // biome-ignore lint/complexity/noForEach: - post.data.tags.forEach((tag) => acc.add(tag)); + if (Array.isArray(post.data.tags)) { + // biome-ignore lint/complexity/noForEach: + post.data.tags.forEach((tag) => { + if (typeof tag === "string") { + acc.add(tag.trim()); + } else { + acc.add(String(tag).trim()); + } + }); + } else if (post.data.tags && typeof post.data.tags === "string") { + // biome-ignore lint/complexity/noForEach: + (post.data.tags as string) + .split(",") + .forEach((tag) => acc.add(tag.trim())); + } return acc; }, new Set()); const allTagsArray = Array.from(allTags); - return allTagsArray.map((tag) => ({ + // judge if the string is CJK + const isCJK = (str: string) => + /[\u3000-\u9fff\uac00-\ud7af\u4e00-\u9faf]/.test(str); + + const standardPaths = allTagsArray.map((tag) => ({ params: { - tag: encodeURIComponent(tag), + tag: encodePathSegment(tag), + }, + props: { + decodedTag: tag, }, })); + + const nonEncodedCJKPaths = allTagsArray.filter(isCJK).map((tag) => ({ + params: { + tag: tag, // keep CJK characters unencoded + }, + props: { + decodedTag: tag, + }, + })); + + return [...standardPaths, ...nonEncodedCJKPaths]; } -const tag = decodeURIComponent(Astro.params.tag as string); +const { decodedTag } = Astro.props; +const tag = decodedTag || decodePathSegment(Astro.params.tag as string); --- - \ No newline at end of file + diff --git a/src/utils/content-utils.ts b/src/utils/content-utils.ts index 5b683946..67a421ca 100644 --- a/src/utils/content-utils.ts +++ b/src/utils/content-utils.ts @@ -67,9 +67,13 @@ export async function getCategoryList(): Promise { count[ucKey] = count[ucKey] ? count[ucKey] + 1 : 1; return; } - count[post.data.category] = count[post.data.category] - ? count[post.data.category] + 1 - : 1; + + const categoryName = + typeof post.data.category === "string" + ? post.data.category.trim() + : String(post.data.category).trim(); + + count[categoryName] = count[categoryName] ? count[categoryName] + 1 : 1; }); const lst = Object.keys(count).sort((a, b) => { diff --git a/src/utils/encoding-utils.ts b/src/utils/encoding-utils.ts new file mode 100644 index 00000000..1e3a1599 --- /dev/null +++ b/src/utils/encoding-utils.ts @@ -0,0 +1,32 @@ +/** + * Utility functions for ensuring consistent URL encoding + */ + +/** + * Ensure consistent URL encoding across all tags and categories + * + * @param value The string to encode + * @returns The encoded string + */ +export function encodePathSegment(value: string): string { + if (!value) return ""; + + return encodeURIComponent(value.trim()); +} + +/** + * Decode from the URL path + * + * @param value String to decode + * @returns Decoded string + */ +export function decodePathSegment(value: string): string { + if (!value) return ""; + + try { + return decodeURIComponent(value); + } catch (e) { + console.error(`Failed to decode path segment: ${value}`, e); + return value; + } +} diff --git a/src/utils/url-utils.ts b/src/utils/url-utils.ts index c52060c8..583c5cb8 100644 --- a/src/utils/url-utils.ts +++ b/src/utils/url-utils.ts @@ -1,5 +1,6 @@ import i18nKey from "@i18n/i18nKey"; import { i18n } from "@i18n/translation"; +import { encodePathSegment } from "./encoding-utils"; export function pathsEqual(path1: string, path2: string) { const normalizedPath1 = path1.replace(/^\/|\/$/g, "").toLowerCase(); @@ -16,10 +17,27 @@ export function getPostUrlBySlug(slug: string): string { return url(`/posts/${slug}/`); } +export function getTagUrl(tag: string): string { + if (!tag) return url("/archive/tag/"); + + // use common encoding function + const encodedTag = encodePathSegment(tag); + const tagUrl = `/archive/tag/${encodedTag}/`; + console.log(`Generating URL for tag "${tag.trim()}" => "${tagUrl}"`); + return url(tagUrl); +} + export function getCategoryUrl(category: string): string { - if (category === i18n(i18nKey.uncategorized)) + console.log(`category: ${category}`); + if (!category) return url("/archive/category/"); + + const trimmedCategory = category.trim(); + if (trimmedCategory === i18n(i18nKey.uncategorized)) return url("/archive/category/uncategorized/"); - return url(`/archive/category/${encodeURIComponent(category)}/`); + + return url( + `/archive/category/${encodeURIComponent(trimmedCategory).replace(/%20/g, "+")}/`, + ); } export function getDir(path: string): string {