Skip to content

Commit

Permalink
Merge pull request #38 from thedevdavid/feature/tags
Browse files Browse the repository at this point in the history
Feature/tags
  • Loading branch information
thedevdavid authored Jul 15, 2023
2 parents 906ba6e + 038114e commit f08617f
Show file tree
Hide file tree
Showing 16 changed files with 190 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- SocialIcon component
- Share post component
- Lint staged files
- Tag generation and routing

### Changed

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,15 @@ Note: DO NOT overdo it. You can easily make images look bad with lossy compressi
- [x] Back to top button
- [x] Social icons component
- [x] Social sharing buttons
- [ ] Tags, categories
- [x] Tags
- [ ] newsletter integration (form, api route, keys, welcome page, previous issues)
- [ ] Other analytics providers (fathom, simplelytics, plausible, etc)
- [ ] Post series page
- [ ] prev/next post links
- [ ] related posts
- [ ] Layouts/templates system
- [ ] Notion data source
- [ ] Sanity data source
- [ ] hero title and subtitle text HTML support(?)
- [ ] Design improvements (whitespace, layout, etc.)
- [ ] error, and loading pages
Expand Down
9 changes: 5 additions & 4 deletions app/(site)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Image from "next/image";
import Link from "next/link";
import { notFound } from "next/navigation";
import { allPages, allPosts } from "@/.contentlayer/generated";
import { compareDesc, format, parseISO } from "date-fns";
import { compareDesc } from "date-fns";
import { ArrowRight } from "lucide-react";

import { defaultAuthor } from "@/lib/metadata";
Expand All @@ -16,13 +17,13 @@ import { Mdx } from "@/components/mdx-components";
import PostPreview from "@/components/post-preview";

async function getAboutPage() {
const page = allPages.find((page) => page.slug === "about");
const aboutPage = allPages.find((page) => page.slug === "about");

if (!page) {
if (!aboutPage) {
null;
}

return page;
return aboutPage;
}

export default async function Home() {
Expand Down
8 changes: 5 additions & 3 deletions app/(site)/posts/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,11 @@ export default async function PostPage({ params }: PostProps) {
<div className="flex flex-row items-center justify-between">
{post.tags && (
<ul className="m-0 list-none space-x-2 p-0 text-sm text-muted-foreground">
{post.tags.map((tag: any) => (
<li className="inline-block p-0" key={tag.trim()}>
{tag}
{post.tags.map((tag: string) => (
<li className="inline-block p-0" key={tag}>
<Link href={`/tags/${tag}`} className="inline-block transition hover:text-muted-foreground/70">
{tag}
</Link>
</li>
))}
</ul>
Expand Down
1 change: 1 addition & 0 deletions app/(site)/posts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default function Blog() {
.sort((a, b) =>
compareDesc(new Date(a.lastUpdatedDate || a.publishedDate), new Date(b.lastUpdatedDate || b.publishedDate))
);

return (
<div className="container mb-4">
<div className="prose mx-auto max-w-5xl dark:prose-invert prose-headings:font-heading prose-headings:font-bold prose-headings:leading-tight hover:prose-a:text-accent-foreground prose-a:prose-headings:no-underline">
Expand Down
55 changes: 55 additions & 0 deletions app/(site)/tags/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Metadata } from "next";
import { notFound } from "next/navigation";
import { allPosts, Post } from "@/.contentlayer/generated";
import { compareDesc } from "date-fns";

import PostPreview from "@/components/post-preview";

// Get sorted articles from the contentlayer
async function getSortedArticles(): Promise<Post[]> {
let articles = await allPosts;

articles = articles.filter((article: Post) => article.status === "published");

return articles.sort((a: Post, b: Post) => {
if (a.publishedDate && b.publishedDate) {
return new Date(b.publishedDate).getTime() - new Date(a.publishedDate).getTime();
}
return 0;
});
}

// Dynamic metadata for the page
export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
return {
title: `All posts in ${params.slug}`,
description: `All posts in ${params.slug}`,
};
}

export default async function TagPage({ params }: { params: { slug: string } }) {
const tag = params.slug;

const posts = allPosts
.filter((post) => post.status === "published")
.filter((post) => post.tags?.includes(tag))
.sort((a, b) => compareDesc(new Date(a.publishedDate), new Date(b.publishedDate)));

if (!posts) {
notFound();
}

return (
<div className="container mb-4">
<div className="prose mx-auto max-w-5xl dark:prose-invert prose-headings:font-heading prose-headings:font-bold prose-headings:leading-tight hover:prose-a:text-accent-foreground prose-a:prose-headings:no-underline">
<h1 className="mt-0">All posts in {tag}</h1>
<hr className="my-4" />
<div className="grid grid-flow-row gap-2">
{posts.map((post) => (
<PostPreview post={post} key={post._id} />
))}
</div>
</div>
</div>
);
}
54 changes: 54 additions & 0 deletions app/(site)/tags/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import { allPosts } from "@/.contentlayer/generated";

import siteMetadata from "@/lib/metadata";
import { getTagsWithCount } from "@/lib/utils";
import { Badge } from "@/components/ui/badge";

export async function generateMetadata(): Promise<Metadata> {
return {
title: "Tags",
description: `All tags in ${siteMetadata.title}`,
};
}

export default function TagsPage() {
const posts = allPosts.filter((post) => post.status === "published");

const tags = getTagsWithCount(posts);

if (!tags || Object.keys(tags).length === 0) {
notFound();
}

return (
<div className="container mb-4">
<div className="prose mx-auto max-w-5xl dark:prose-invert prose-headings:font-heading prose-headings:font-bold prose-headings:leading-tight hover:prose-a:text-accent-foreground prose-a:prose-headings:no-underline">
<h1 className="mt-0">All tags</h1>
<hr className="my-4" />
<div className="grid grid-flow-row gap-2">
<ul className="flex list-none flex-wrap gap-2 p-0">
{Object.keys(tags).map((tag) => {
return (
<li className="list-none" key={tag}>
<Link href={`/tags/${tag}`} className="group">
<Badge
variant="outline"
className="inline-block rounded-full border border-muted-foreground/50 bg-muted-foreground/10 px-3 py-1 text-base font-medium text-muted-foreground transition hover:bg-muted-foreground hover:text-foreground"
>
<span>
{tag} &middot; ({tags[tag]})
</span>
</Badge>
</Link>
</li>
);
})}
</ul>
</div>
</div>
</div>
);
}
1 change: 0 additions & 1 deletion components/cta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ const CTA = ({ title, description, buttonText }: CTAProps) => {
</ToastAction>
),
});
console.log(values);
}
return (
<section className="relative isolate my-24 overflow-hidden bg-primary py-6 text-primary-foreground">
Expand Down
6 changes: 3 additions & 3 deletions components/post-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const PostPreview = ({ post }: PostPreviewProps) => {
return (
<article className="w-full">
<Link
href={`posts/${post.slug}`}
href={`/posts/${post.slug}`}
className={cn(
"select-rounded-md block w-full rounded-md p-4 leading-none no-underline outline-none transition-colors hover:bg-foreground/10 hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
)}
Expand All @@ -33,8 +33,8 @@ const PostPreview = ({ post }: PostPreviewProps) => {
</div>
{post?.tags && (
<ul className="my-4 flex flex-wrap gap-2 p-0">
{post.tags.map((tag: any) => (
<li key={tag.trim()}>
{post.tags.map((tag: string) => (
<li key={tag}>
<Badge
variant="outline"
className="inline-block rounded-full border border-muted-foreground/50 bg-muted-foreground/10 px-2 py-0.5 text-xs text-muted-foreground"
Expand Down
6 changes: 3 additions & 3 deletions content/posts/choosing-providers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ title: "Choosing Providers"
publishedDate: "2023-07-05"
lastUpdatedDate: "2023-07-05"
tags:
- "starter"
- "development"
- "docs"
- starter
- docs
- development
description: "How to choose and set up Analytics, newsletter, and other providers for your blog."
status: published
series:
Expand Down
6 changes: 3 additions & 3 deletions content/posts/draft-post.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ title: "An unpublished post"
publishedDate: "2023-06-07"
lastUpdatedDate: "2023-06-07"
tags:
- "freelancing"
- "development"
- "opinion"
- freelancing
- opinion
- development
description: "This is a draft post. It's not published yet."
status: draft
---
Expand Down
11 changes: 5 additions & 6 deletions content/posts/get-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ title: Setting Up Modern Developer Blog Template (Digital Garden Starter)
publishedDate: "2023-05-31"
lastUpdatedDate: "2023-07-05"
tags:
- "jamstack"
- "technology"
- "javascript"
- "frontend"
- "development"
- "opinion"
- starter
- docs
- development
- javascript
- frontend
description: As a developer who creates content, I want to have a blog & digital garden where I can share my thoughts and ideas with the world. Now, there's not really a "perfect solution" for this currently. With included analytics, SEO, email subscriptions, modern tooling, simple design, etc. We either have to build one from scratch, use a design template and code the features, or use a CMS/no-code tool.
status: published
series:
Expand Down
9 changes: 4 additions & 5 deletions content/posts/tailwind-glassmorphism.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ title: "Glassmorphism with Tailwind CSS Under 60 seconds"
publishedDate: "2020-12-17"
lastUpdatedDate: "2021-05-23"
tags:
- "react"
- "tailwind"
- "javascript"
- "frontend"
- "development"
- react
- tailwindcss
- javascript
- frontend
description: Quick guide on how to make glass morph components with Tailwind (updated for latest Tailwind version)
status: published
---
Expand Down
6 changes: 3 additions & 3 deletions content/posts/what-is-this.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ title: "What is this starter?"
publishedDate: "2023-07-05"
lastUpdatedDate: "2023-07-05"
tags:
- "starter"
- "development"
- "docs"
- starter
- docs
- development
description: "The motivation and goals of this starter template. What it is and what it is not."
status: published
series:
Expand Down
33 changes: 32 additions & 1 deletion lib/content-definitions/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ import GithubSlugger from "github-slugger";
import { calculateReadingTime } from "../utils";
import { Series } from "./series";

const tagOptions = [
"starter",
"development",
"docs",
"freelancing",
"opinion",
"jamstack",
"frontend",
"development",
"javascript",
"typescript",
"react",
"nextjs",
"gatsby",
"tailwindcss",
];

export const Post = defineDocumentType(() => ({
name: "Post",
filePathPattern: `posts/**/*.mdx`,
Expand All @@ -26,7 +43,8 @@ export const Post = defineDocumentType(() => ({
},
tags: {
type: "list",
of: { type: "string" },
of: { type: "string", options: tagOptions },
required: false,
},
series: {
type: "nested",
Expand All @@ -39,6 +57,19 @@ export const Post = defineDocumentType(() => ({
},
},
computedFields: {
tagSlugs: {
type: "list",
resolve: async (doc) => {
if (doc.tags) {
// make a new array of tags to use them in computedFields https://github.com/contentlayerdev/contentlayer/issues/149
const tags = [...(doc?.tags ?? ([] as string[]))];
const slugger = new GithubSlugger();

return tags.map((tag) => slugger.slug(tag));
}
return null;
},
},
readTimeMinutes: {
type: "number",
resolve: (doc) => calculateReadingTime(doc.body.raw),
Expand Down
13 changes: 13 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Post } from "@/.contentlayer/generated";
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

Expand All @@ -14,3 +15,15 @@ export const calculateReadingTime = (text: string): number => {

return readTime;
};

export const getTagsWithCount = (posts: Post[]) =>
posts.reduce((acc: any, post: Post) => {
post.tags?.forEach((tag: any) => {
if (acc[tag]) {
acc[tag] += 1;
} else {
acc[tag] = 1;
}
});
return acc;
}, {});

0 comments on commit f08617f

Please sign in to comment.