Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/tags #38

Merged
merged 2 commits into from
Jul 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}, {});