feat: remove support for nested categories

This commit is contained in:
saicaca 2023-12-14 13:43:06 +08:00
parent f7efa010a2
commit f9a78b3e3b
9 changed files with 50 additions and 99 deletions

View File

@ -20,7 +20,7 @@ if (Array.isArray(tags) && tags.length > 0) {
if (Array.isArray(categories) && categories.length > 0) { if (Array.isArray(categories) && categories.length > 0) {
posts = posts.filter(post => posts = posts.filter(post =>
Array.isArray(post.data.categories) && post.data.categories.some(category => categories.includes(category)) post.data.category && categories.includes(post.data.category)
); );
} }

View File

@ -8,10 +8,9 @@ interface Props {
class: string; class: string;
published: Date; published: Date;
tags: string[]; tags: string[];
categories: string[];
category: string; category: string;
} }
const {published, tags, categories} = Astro.props; const {published, tags, category} = Astro.props;
const className = Astro.props.class; const className = Astro.props.class;
--- ---
@ -25,23 +24,21 @@ const className = Astro.props.class;
<span class="text-black/50 dark:text-white/50 text-sm font-medium">{formatDateToYYYYMMDD(published)}</span> <span class="text-black/50 dark:text-white/50 text-sm font-medium">{formatDateToYYYYMMDD(published)}</span>
</div> </div>
<!-- categories --> <!-- categoriy -->
<div class="flex items-center"> <div class="flex items-center">
<div class="meta-icon" <div class="meta-icon"
> >
<Icon name="material-symbols:menu-rounded" class="text-xl"></Icon> <Icon name="material-symbols:menu-rounded" class="text-xl"></Icon>
</div> </div>
<div class="flex flex-row flex-nowrap"> <div class="flex flex-row flex-nowrap">
{(categories && categories.length > 0) && categories.map(category => <div {category &&
class="with-divider" <div><a href=`/archive/category/${category}` aria-label=`View all posts in the ${category} category`
>
<a href=`/archive/category/${category}` aria-label=`View all posts in the ${category} category`
class="link-lg transition text-black/50 dark:text-white/50 text-sm font-medium class="link-lg transition text-black/50 dark:text-white/50 text-sm font-medium
hover:text-[var(--primary)] dark:hover:text-[var(--primary)] whitespace-nowrap"> hover:text-[var(--primary)] dark:hover:text-[var(--primary)] whitespace-nowrap">
{category} {category}
</a> </a></div>
</div>)} }
{!(categories && categories.length > 0) && <div class="transition text-black/50 dark:text-white/50 text-sm font-medium">{i18n(I18nKey.uncategorized)}</div>} {!category && <div class="transition text-black/50 dark:text-white/50 text-sm font-medium">{i18n(I18nKey.uncategorized)}</div>}
</div> </div>
</div> </div>

View File

@ -7,13 +7,12 @@ interface Props {
url: string; url: string;
published: Date; published: Date;
tags: string[]; tags: string[];
categories: string[];
category: string; category: string;
image: string; image: string;
description: string; description: string;
words: number; words: number;
} }
const { entry, title, url, published, tags, categories, category, image, description, words } = Astro.props; const { entry, title, url, published, tags, category, image, description, words } = Astro.props;
const className = Astro.props.class; const className = Astro.props.class;
// console.log(Astro.props); // console.log(Astro.props);
import ImageBox from "./misc/ImageBox.astro"; import ImageBox from "./misc/ImageBox.astro";
@ -29,11 +28,6 @@ const coverWidth = "28%";
const { remarkPluginFrontmatter } = await entry.render(); const { remarkPluginFrontmatter } = await entry.render();
let cate = categories;
if (category) {
cate = [category];
}
--- ---
<div class:list={["card-base flex flex-col-reverse md:flex-col w-full rounded-[var(--radius-large)] overflow-hidden relative", className]}> <div class:list={["card-base flex flex-col-reverse md:flex-col w-full rounded-[var(--radius-large)] overflow-hidden relative", className]}>
<div class:list={["pl-9 pr-9 md:pr-2 pt-6 md:pt-7 pb-6 relative", {"w-full md:w-[calc(100%_-_52px_-_12px)]": !hasCover, "w-full md:w-[calc(100%_-_var(--coverWidth)_-_12px)]": hasCover}]}> <div class:list={["pl-9 pr-9 md:pr-2 pt-6 md:pt-7 pb-6 relative", {"w-full md:w-[calc(100%_-_52px_-_12px)]": !hasCover, "w-full md:w-[calc(100%_-_var(--coverWidth)_-_12px)]": hasCover}]}>
@ -49,7 +43,7 @@ if (category) {
</a> </a>
<!-- metadata --> <!-- metadata -->
<PostMetadata published={published} tags={tags} categories={cate} class:list={{"mb-4": description, "mb-6": !description}}></PostMetadata> <PostMetadata published={published} tags={tags} category={category} class:list={{"mb-4": description, "mb-6": !description}}></PostMetadata>
<div class="transition text-black/75 dark:text-white/75 mb-3.5"> <div class="transition text-black/75 dark:text-white/75 mb-3.5">
{ description } { description }

View File

@ -3,31 +3,25 @@ import WidgetLayout from "./WidgetLayout.astro";
import {i18n} from "../../i18n/translation"; import {i18n} from "../../i18n/translation";
import I18nKey from "../../i18n/i18nKey"; import I18nKey from "../../i18n/i18nKey";
import {CategoryMap, getCategoryMap} from "../../utils/content-utils"; import {Category, getCategoryList} from "../../utils/content-utils";
import CategoriesLink from "./CategoriesLink.astro"; import {getCategoryUrl} from "../../utils/url-utils";
import ButtonLink from "../control/ButtonLink.astro";
const categories = await getCategoryMap(); const categories = await getCategoryList();
const COLLAPSED_HEIGHT = "120px"; const COLLAPSED_HEIGHT = "120px";
const COLLAPSE_THRESHOLD = 5; const COLLAPSE_THRESHOLD = 5;
function count(categoryMap: CategoryMap): number { const isCollapsed = categories.length >= COLLAPSE_THRESHOLD;
let res = 0;
for (const key in categoryMap) {
res++;
res += count(categoryMap[key].children);
}
return res;
}
const isCollapsed = count(categories) >= COLLAPSE_THRESHOLD;
interface Props { interface Props {
categories: CategoryMap; categories: Category[];
} }
--- ---
<WidgetLayout name={i18n(I18nKey.categories)} id="categories" isCollapsed={isCollapsed} collapsedHeight={COLLAPSED_HEIGHT}> <WidgetLayout name={i18n(I18nKey.categories)} id="categories" isCollapsed={isCollapsed} collapsedHeight={COLLAPSED_HEIGHT}>
<CategoriesLink categories={categories}></CategoriesLink> {categories.map((c) =>
<ButtonLink url={getCategoryUrl(c.name)} badge={c.count} label=`View all posts in the ${c.name} category`>{c.name}</ButtonLink>
)}
</WidgetLayout> </WidgetLayout>

View File

@ -1,21 +0,0 @@
---
import {CategoryMap} from "../../utils/content-utils";
import {getCategoryUrl} from "../../utils/url-utils";
import ButtonLink from "../control/ButtonLink.astro";
interface Props {
categories: CategoryMap;
}
const {categories} = Astro.props;
---
<div>
{Object.entries(categories).map(([key, value]) =>
<ButtonLink url={getCategoryUrl(key)} badge={value.count} label=`View all posts in the ${value.name} category`>{value.name}</ButtonLink>
<div class="ml-2">
{Object.keys(value.children).length > 0 && <Astro.self categories={value.children}></Astro.self>}
</div>
)}
</div>

View File

@ -1,6 +1,5 @@
--- ---
import {getCategoryList, getSortedPosts} from "../../../utils/content-utils";
import {getSortedPosts} from "../../../utils/content-utils";
import MainGridLayout from "../../../layouts/MainGridLayout.astro"; import MainGridLayout from "../../../layouts/MainGridLayout.astro";
import ArchivePanel from "../../../components/ArchivePanel.astro"; import ArchivePanel from "../../../components/ArchivePanel.astro";
import {i18n} from "../../../i18n/translation"; import {i18n} from "../../../i18n/translation";
@ -8,21 +7,11 @@ import I18nKey from "../../../i18n/i18nKey";
export async function getStaticPaths() { export async function getStaticPaths() {
let posts = await getSortedPosts() const categories = await getCategoryList();
return categories.map(category => {
const allCategories = posts.reduce((acc, post) => {
if (!Array.isArray(post.data.categories))
return acc;
post.data.categories.forEach(category => acc.add(category));
return acc;
}, new Set());
const allCategoriesArray = Array.from(allCategories);
return allCategoriesArray.map(category => {
return { return {
params: { params: {
category: category category: category.name
} }
} }
}); });

View File

@ -22,7 +22,6 @@ const {page} = Astro.props;
entry={entry} entry={entry}
title={entry.data.title} title={entry.data.title}
tags={entry.data.tags} tags={entry.data.tags}
categories={entry.data.categories}
category={entry.data.category} category={entry.data.category}
published={entry.data.published} published={entry.data.published}
url={getPostUrlBySlug(entry.slug)} url={getPostUrlBySlug(entry.slug)}

View File

@ -64,7 +64,7 @@ const { remarkPluginFrontmatter } = await entry.render();
class="mb-5" class="mb-5"
published={entry.data.published} published={entry.data.published}
tags={entry.data.tags} tags={entry.data.tags}
categories={entry.data.categories} category={entry.data.category}
></PostMetadata> ></PostMetadata>
<!-- always show cover as long as it has one --> <!-- always show cover as long as it has one -->

View File

@ -20,7 +20,12 @@ export async function getSortedPosts() {
return sorted; return sorted;
} }
export async function getTagList(): Promise<{ name: string; count: number }[]> { export type Tag = {
name: string;
count: number;
}
export async function getTagList(): Promise<Tag[]> {
const allBlogPosts = await getCollection("posts"); const allBlogPosts = await getCollection("posts");
const countMap: { [key: string]: number } = {}; const countMap: { [key: string]: number } = {};
@ -32,44 +37,38 @@ export async function getTagList(): Promise<{ name: string; count: number }[]> {
}); });
// sort tags // sort tags
const keys: string[] = Object.keys(countMap).sort(function (a, b) { const keys: string[] = Object.keys(countMap).sort((a, b) => {
var textA = a.toLowerCase(); return a.toLowerCase().localeCompare(b.toLowerCase());
var textB = b.toLowerCase();
if (textA < textB) {
return -1;
}
if (textA > textB) {
return 1;
}
return 0;
}); });
return keys.map((key) => ({name: key, count: countMap[key]})); return keys.map((key) => ({name: key, count: countMap[key]}));
} }
type Category = { export type Category = {
name: string; name: string;
count: number; count: number;
children: CategoryMap;
} }
export type CategoryMap = { [key: string]: Category }; export async function getCategoryList(): Promise<Category[]> {
export async function getCategoryMap(): Promise<CategoryMap> {
const allBlogPosts = await getCollection("posts"); const allBlogPosts = await getCollection("posts");
let root: CategoryMap = {}; let count : {[key: string]: number} = {};
allBlogPosts.map((post) => { allBlogPosts.map((post) => {
let current = root; if (!post.data.category) {
if (post.data.category) {
post.data.categories = [post.data.category];
}
if (!post.data.categories)
return; return;
for (const c of post.data.categories) {
if (!current[c]) current[c] = {name: c, count: 0, children: {}};
current[c].count++;
current = current[c].children;
} }
if (!count[post.data.category]) {
count[post.data.category] = 0;
}
count[post.data.category]++;
}); });
return root;
let lst = Object.keys(count).sort((a, b) => {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
let ret : Category[] = [];
for (const c of lst) {
ret.push({name: c, count: count[c]});
}
return ret;
} }