mirror of
https://github.com/10h30/blog-balodeplao.git
synced 2026-05-12 15:21:15 +09:00
103 lines
2.8 KiB
TypeScript
103 lines
2.8 KiB
TypeScript
import categoryData from "@/content/taxonomies/category.json";
|
|
import destinationData from "@/content/taxonomies/destination.json";
|
|
import tagData from "@/content/taxonomies/tag.json";
|
|
|
|
export type TaxonomyItem = {
|
|
slug: string;
|
|
name: string;
|
|
parent?: string;
|
|
description?: string;
|
|
};
|
|
|
|
export type TaxonomyMap = Map<string, TaxonomyItem>;
|
|
|
|
export type TaxonomyType = "category" | "destination" | "tag";
|
|
|
|
function isTaxonomyItem(value: unknown): value is TaxonomyItem {
|
|
if (typeof value !== "object" || value === null) return false;
|
|
const v = value as Record<string, unknown>;
|
|
return (
|
|
typeof v.slug === "string" &&
|
|
typeof v.name === "string" &&
|
|
(v.parent === undefined || typeof v.parent === "string") &&
|
|
(v.description === undefined || typeof v.description === "string")
|
|
);
|
|
}
|
|
|
|
function buildMap(data: TaxonomyItem[]): TaxonomyMap {
|
|
if (!Array.isArray(data)) {
|
|
throw new Error(`[taxonomy] expected an array but received ${typeof data}`);
|
|
}
|
|
return new Map(
|
|
data.map((item, i) => {
|
|
if (!isTaxonomyItem(item)) {
|
|
throw new Error(
|
|
`[taxonomy] invalid item at index ${i}: ${JSON.stringify(item)}`,
|
|
);
|
|
}
|
|
return [item.slug, item];
|
|
}),
|
|
);
|
|
}
|
|
|
|
const taxonomies: Record<TaxonomyType, TaxonomyMap> = {
|
|
category: buildMap(categoryData as TaxonomyItem[]),
|
|
destination: buildMap(destinationData as TaxonomyItem[]),
|
|
tag: buildMap(tagData as TaxonomyItem[]),
|
|
};
|
|
|
|
export function getTaxonomyMap(type: TaxonomyType): TaxonomyMap {
|
|
if (!(type in taxonomies)) {
|
|
throw new Error(
|
|
`[taxonomy] unknown taxonomy type "${type}". Expected one of: ${Object.keys(taxonomies).join(", ")}`,
|
|
);
|
|
}
|
|
return taxonomies[type];
|
|
}
|
|
|
|
function buildHierarchicalPath(
|
|
slug: string,
|
|
map: TaxonomyMap,
|
|
visited = new Set<string>(),
|
|
): string {
|
|
if (visited.has(slug)) return slug; // cycle protection
|
|
visited.add(slug);
|
|
const item = map.get(slug);
|
|
if (!item?.parent) return slug;
|
|
return `${buildHierarchicalPath(item.parent, map, visited)}/${slug}`;
|
|
}
|
|
|
|
export function getTaxonomyPath(type: TaxonomyType, slug: string): string {
|
|
return buildHierarchicalPath(slug, getTaxonomyMap(type));
|
|
}
|
|
|
|
export function getTaxonomyItem(
|
|
type: TaxonomyType,
|
|
slug: string,
|
|
): TaxonomyItem | undefined {
|
|
return getTaxonomyMap(type).get(slug);
|
|
}
|
|
|
|
export function getDescendantSlugs(
|
|
taxonomyType: string,
|
|
slug: string,
|
|
): string[] {
|
|
const map = getTaxonomyMap(taxonomyType);
|
|
const result: string[] = [];
|
|
|
|
for (const [key, item] of map.entries()) {
|
|
if (key === slug) continue;
|
|
// Đi ngược từ node này lên root, kiểm tra xem có qua slug không
|
|
let current: TaxonomyItem | undefined = item;
|
|
while (current?.parent) {
|
|
if (current.parent === slug) {
|
|
result.push(key);
|
|
break;
|
|
}
|
|
current = map.get(current.parent);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|