diff --git a/package-lock.json b/package-lock.json index 33efc4ee..f67faae4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,6 +57,7 @@ "reading-time": "^1.5.0", "remark-toc": "^9.0.0", "sanitize-html": "^2.11.0", + "tailwind-scrollbar": "^4.0.0", "unplugin-icons": "^0.18.1" } }, @@ -2666,6 +2667,13 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/prismjs": { + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", + "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", @@ -9053,6 +9061,20 @@ } } }, + "node_modules/prism-react-renderer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", + "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prismjs": "^1.26.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, "node_modules/prismjs": { "version": "1.29.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", @@ -10301,6 +10323,22 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tailwind-scrollbar": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tailwind-scrollbar/-/tailwind-scrollbar-4.0.0.tgz", + "integrity": "sha512-elqx9m09VHY8gkrMiyimFO09JlS3AyLFXT0eaLaWPi7ImwHlbZj1ce/AxSis2LtR+ewBGEyUV7URNEMcjP1Z2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "prism-react-renderer": "^2.4.1" + }, + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "tailwindcss": "4.x" + } + }, "node_modules/tailwindcss": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.4.tgz", diff --git a/package.json b/package.json index 6d6c886a..f5427b38 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "reading-time": "^1.5.0", "remark-toc": "^9.0.0", "sanitize-html": "^2.11.0", + "tailwind-scrollbar": "^4.0.0", "unplugin-icons": "^0.18.1" } } diff --git a/src/components/blog/PostInfo.astro b/src/components/blog/PostInfo.astro index 03b32ae1..92de6f2f 100644 --- a/src/components/blog/PostInfo.astro +++ b/src/components/blog/PostInfo.astro @@ -1,20 +1,17 @@ --- -import type { BlogPost } from "~/content/config.ts"; +import type { BlogPost } from "~/content.config"; import { formatPostDate, calcReadingTime, slugifyPostDate } from "~/lib/utils"; - +import { marked } from "marked"; +import readingTime from "reading-time"; +import { Icon } from "astro-icon/components"; interface Props { post: BlogPost; wordCount: number; } const { post, wordCount } = Astro.props as Props; -import { marked } from "marked"; - -const body = `${post.body - .split(" ") - .slice(0, wordCount + 1) - .join(" ")}...`; -const postBody = marked.parse(body); +const body = post?.body?.split(" ").slice(0, wordCount).join(" ") || ""; +const content = marked(body); ---
@@ -24,13 +21,20 @@ const postBody = marked.parse(body); - - {calcReadingTime(post.body)} - + { + post.body && ( +
+
+ + {readingTime(post.body).text} +
+
+ ) + } + { post.data.series && (
@@ -46,10 +50,10 @@ const postBody = marked.parse(body);
Read More diff --git a/src/content.config.ts b/src/content.config.ts index b77bece3..3154f245 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -1,7 +1,8 @@ import { z, defineCollection, type CollectionEntry } from "astro:content"; +import { glob } from "astro/loaders"; -const blogSchema = defineCollection({ - type: "content", +const blog = defineCollection({ + loader: glob({ pattern: "**/*.{md,mdx}", base: "src/content/blog" }), schema: ({ image }) => z.object({ title: z.string({ @@ -48,8 +49,8 @@ export type BlogPost = CollectionEntry<"blog">; //export type BlogPost = CollectionEntry<"blog">; -const projectSchema = defineCollection({ - type: "content", +const projects = defineCollection({ + loader: glob({ pattern: "**/*.{md,mdx}", base: "src/content/projects" }), schema: z.object({ title: z.string({ required_error: "A title is required for a project." }), draft: z @@ -106,6 +107,5 @@ const projectSchema = defineCollection({ export type Project = CollectionEntry<"projects">; export const collections = { - blog: blogSchema, - projects: projectSchema, + blog, projects }; diff --git a/src/content/config.ts b/src/content/config.ts deleted file mode 100644 index b77bece3..00000000 --- a/src/content/config.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { z, defineCollection, type CollectionEntry } from "astro:content"; - -const blogSchema = defineCollection({ - type: "content", - schema: ({ image }) => - z.object({ - title: z.string({ - required_error: "A title is required for a blog post.", - }), - draft: z - .boolean({ required_error: "Every post is either a draft or not." }) - .default(true), - pubDate: z.date({ - required_error: "You need to have a date when the post was published.", - }), - tags: z.array(z.string()).optional(), - categories: z.array(z.string()).optional(), - lastModDate: z.date().optional(), - featured: z - .boolean({ invalid_type_error: "A post is either featured or not." }) - .default(false), - series: z - .object({ - name: z.string({ required_error: "Any series must have a name." }), - description: z.string({ - required_error: "All series must have a brief description.", - }), - weight: z.number({ - required_error: - "Any post in a series must have a weight to sort by in that series.", - }), - }) - .optional(), - description: z - .string({ - required_error: - "All posts must have a brief description of 120 characters explaining what the post is about.", - invalid_type_error: "A description should always be a string.", - }) - .max(120), - cover: image().optional(), - coverAlt: z.string().optional(), - coverClasses: z.array(z.string()).optional(), - }), -}); - -export type BlogPost = CollectionEntry<"blog">; - -//export type BlogPost = CollectionEntry<"blog">; - -const projectSchema = defineCollection({ - type: "content", - schema: z.object({ - title: z.string({ required_error: "A title is required for a project." }), - draft: z - .boolean({ - invalid_type_error: "A project page is either a draft or it isn't.", - }) - .default(true), - description: z - .string({ - required_error: "A brief description for the project is required.", - invalid_type_error: "A description should always be a string.", - }) - .max(120), - tools: z.array( - z.string({ - required_error: "All projects must have a list of tools used.", - invalid_type_error: "All tools are a list of strings.", - }), - ), - category: z.string({ - required_error: "A project should always have a category.", - invalid_type_error: "A category should always be a string.", - }), - featured: z - .boolean({ - invalid_type_error: "All projects are either featured or not.", - }) - .default(false), - - status: z.enum(["Planning", "In Development", "Completed"], { - invalid_type_error: - "A project is either planning, in development, or completed.", - }), - links: z.object({ - docs: z - .string({ - invalid_type_error: - "The documentation link should be a page to the url.", - }) - .optional(), - repo: z.string({ - invalid_type_error: "Pass the url to the repository as a string.", - required_error: "The repository link is required.", - }), - live: z - .string({ - invalid_type_error: "Pass the url to the live site as a string.", - }) - .optional(), - }), - }), -}); - -export type Project = CollectionEntry<"projects">; - -export const collections = { - blog: blogSchema, - projects: projectSchema, -}; diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 3d66cbcc..ca456bcf 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -49,7 +49,7 @@ const { title, description } = Astro.props;
+ class="grid-area-main overflow-y-scroll scrollbar-thin scroll-smooth motion-reduce:scroll-auto scrollbar-h-32">
diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 3a187c77..8ea3be3e 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -56,3 +56,4 @@ export function toTitleCase(str: string): string { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); }); } + diff --git a/src/pages/blog/[...page].astro b/src/pages/blog/[...page].astro index 56800958..3a56a9a6 100644 --- a/src/pages/blog/[...page].astro +++ b/src/pages/blog/[...page].astro @@ -5,7 +5,7 @@ import PostInfo from "~/components/blog/PostInfo.astro"; import { getCollection, type CollectionEntry } from "astro:content"; import Pagination from "~/components/Pagination.astro"; import { RECENT_POST_COUNT } from "~/consts"; -import type { BlogPost } from "~/content/config.ts"; +import type { BlogPost } from "~/content.config"; export const getStaticPaths = (async ({ paginate }) => { const posts: CollectionEntry<"blog">[] = await getCollection( "blog", @@ -22,7 +22,7 @@ const { page } = Astro.props;
+ class="min-h-full grid grid-rows-(1fr_auto) container mx-auto px-5 overflow-hidden">
{ page.data.map((post: BlogPost) => ( diff --git a/src/pages/blog/[...slug].astro b/src/pages/blog/[...slug].astro index a0fe9c8f..053f902f 100644 --- a/src/pages/blog/[...slug].astro +++ b/src/pages/blog/[...slug].astro @@ -4,7 +4,7 @@ import type { InferGetStaticPropsType, GetStaticPaths, } from "astro"; -import { getCollection } from "astro:content"; +import { getCollection, render } from "astro:content"; import BlogPostLayout from "~/layouts/BlogPostLayout.astro"; import { slugifyPostDate } from "~/lib/utils"; @@ -12,7 +12,7 @@ export const getStaticPaths = (async () => { const posts = await getCollection("blog"); return posts.map((post) => { return { - params: { slug: `${slugifyPostDate(post.data.pubDate)}/${post.slug}` }, + params: { slug: `${slugifyPostDate(post.data.pubDate)}/${post.id}` }, props: { post }, }; }); @@ -24,7 +24,7 @@ type Props = InferGetStaticPropsType; // eslint-disable-next-line const { slug } = Astro.params as Params; const { post } = Astro.props; -const { Content } = await post.render(); +const { Content } = await render(post); --- diff --git a/src/pages/categories/[category]/[...page].astro b/src/pages/categories/[category]/[...page].astro index abcf339d..0004edd3 100644 --- a/src/pages/categories/[category]/[...page].astro +++ b/src/pages/categories/[category]/[...page].astro @@ -1,8 +1,52 @@ --- +import type { GetStaticPaths } from "astro"; + +import { getCollection } from "astro:content"; +import { slugifyUrl, toTitleCase } from "~/lib/utils"; +import type { BlogPost } from "~/content.config"; + import BaseLayout from "~/layouts/BaseLayout.astro"; import PostInfo from "~/components/blog/PostInfo.astro"; import Pagination from "~/components/Pagination.astro"; -import { getCollection } from "astro:content"; -import { slugifyUrl, toTitleCase } from "~/lib/utils"; +export async function getStaticPaths({ paginate }) { + const allPosts = await getCollection("blog"); + const uniqueCats = [ + ...new Set(allPosts.map((post) => post.data?.categories).flat()), + ].filter((category) => category !== undefined); + + return uniqueCats.flatMap((category) => { + const filteredPosts = allPosts.filter((post) => + post.data.categories?.includes(category!), + ); + return paginate(filteredPosts, { + params: { category: slugifyUrl(category) }, + props: { category, slug: slugifyUrl(category) }, + pageSize: 4, + }); + }); +} + +const { category } = Astro.params; +const { page } = Astro.props; --- + + +
+

Category: {toTitleCase(category)}

+
+
+
+
+ { + page.data.map((post: BlogPost) => ( + + )) + } +
+ +
+
+
diff --git a/src/pages/projects/[...page].astro b/src/pages/projects/[...page].astro index fc79b40e..02c1004c 100644 --- a/src/pages/projects/[...page].astro +++ b/src/pages/projects/[...page].astro @@ -3,7 +3,7 @@ import type { GetStaticPaths } from "astro"; import BaseLayout from "~/layouts/BaseLayout.astro"; import Pagination from "~/components/Pagination.astro"; import ProjectCard from "~/components/ProjectCard.astro"; -import { type Project } from "~/content/config.ts"; +import { type Project } from "~/content.config"; import { getCollection } from "astro:content"; export const getStaticPaths = (async ({ paginate }) => { diff --git a/src/pages/projects/[...slug].astro b/src/pages/projects/[...slug].astro index 5998e259..50761506 100644 --- a/src/pages/projects/[...slug].astro +++ b/src/pages/projects/[...slug].astro @@ -4,8 +4,8 @@ import type { InferGetStaticParamsType, InferGetStaticPropsType, } from "astro"; -import { getCollection } from "astro:content"; -import type { Project } from "~/content/config.ts"; +import { getCollection, render } from "astro:content"; +import type { Project } from "~/content.config"; import { slugifyUrl } from "~/lib/utils"; import BaseLayout from "~/layouts/BaseLayout.astro"; import { Icon } from "astro-icon/components"; @@ -13,7 +13,7 @@ export const getStaticPaths = (async () => { const projects: Project[] = await getCollection("projects"); return projects.map((project: Project) => { return { - params: { slug: slugifyUrl(project.slug) }, + params: { slug: slugifyUrl(project.id) }, props: { project }, }; }); @@ -25,7 +25,7 @@ type Props = InferGetStaticPropsType; // eslint-disable-next-line const { slug } = Astro.params as Params; const { project } = Astro.props; -const { Content } = await project.render(); +const { Content } = await render(project); --- diff --git a/src/pages/search.json.ts b/src/pages/search.json.ts index 555c514f..191b88cd 100644 --- a/src/pages/search.json.ts +++ b/src/pages/search.json.ts @@ -1,4 +1,4 @@ -import { type BlogPost } from "../content/config"; +import { type BlogPost } from "~/content.config"; import { getCollection } from "astro:content"; import type { APIRoute } from "astro"; diff --git a/src/styles/global.css b/src/styles/global.css index a0bd3af1..d69fd190 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -5,6 +5,7 @@ @plugin "@tailwindcss/typography"; @plugin "@tailwindcss/forms"; +@plugin "tailwind-scrollbar"; @custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *)); @@ -147,4 +148,4 @@ --color-branding-yellow: var(--color-branding-yellow-400); --color-branding-brown: var(--color-branding-brown-500); --color-branding-neutral: var(--color-branding-neutral-500); -} \ No newline at end of file +}