feat: remove support for nested categories
This commit is contained in:
parent
f7efa010a2
commit
f9a78b3e3b
|
@ -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)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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)}
|
||||||
|
|
|
@ -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 -->
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
Loading…
Reference in New Issue