Skip to content

Commit

Permalink
feat: add modified datetime in blog posts
Browse files Browse the repository at this point in the history
* feat: add modified datetime in blog posts

Add modDatetime in blogSchema. Update Datetime component to accept modDatetime. Update sorting logic. Rename datetime to pubDatetime in Datetime component.

Closes: #134

* feat: add published_time & modified_time meta tags

* docs: update related blog posts to be up-to-date

* fix: remove extra spaces in breadcrumbs
  • Loading branch information
satnaing authored Dec 26, 2023
1 parent 2f602fe commit 80e67a1
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 50 deletions.
4 changes: 2 additions & 2 deletions src/components/Breadcrumbs.astro
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ breadcrumbList[0] === "posts" &&
<ul>
<li>
<a href="/">Home</a>
<span aria-hidden="true">&nbsp;&gt;&nbsp;</span>
<span aria-hidden="true">&gt;</span>
</li>
{
breadcrumbList.map((breadcrumb, index) =>
Expand All @@ -33,7 +33,7 @@ breadcrumbList[0] === "posts" &&
) : (
<li>
<a href={`/${breadcrumb}`}>{breadcrumb}</a>
<span aria-hidden="true">&nbsp;&gt;&nbsp;</span>
<span aria-hidden="true">&gt;</span>
</li>
)
)
Expand Down
4 changes: 2 additions & 2 deletions src/components/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface Props {
}

export default function Card({ href, frontmatter, secHeading = true }: Props) {
const { title, pubDatetime, description } = frontmatter;
const { title, pubDatetime, modDatetime, description } = frontmatter;

const headerProps = {
style: { viewTransitionName: slugifyStr(title) },
Expand All @@ -28,7 +28,7 @@ export default function Card({ href, frontmatter, secHeading = true }: Props) {
<h3 {...headerProps}>{title}</h3>
)}
</a>
<Datetime datetime={pubDatetime} />
<Datetime pubDatetime={pubDatetime} modDatetime={modDatetime} />
<p>{description}</p>
</li>
);
Expand Down
40 changes: 29 additions & 11 deletions src/components/Datetime.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,56 @@
import { LOCALE } from "@config";

export interface Props {
datetime: string | Date;
interface DatetimesProps {
pubDatetime: string | Date;
modDatetime: string | Date | undefined;
}

interface Props extends DatetimesProps {
size?: "sm" | "lg";
className?: string;
}

export default function Datetime({ datetime, size = "sm", className }: Props) {
export default function Datetime({
pubDatetime,
modDatetime,
size = "sm",
className,
}: Props) {
return (
<div className={`flex items-center space-x-2 opacity-80 ${className}`}>
<svg
xmlns="http://www.w3.org/2000/svg"
className={`${
size === "sm" ? "scale-90" : "scale-100"
} inline-block h-6 w-6 fill-skin-base`}
} inline-block h-6 w-6 min-w-[1.375rem] fill-skin-base`}
aria-hidden="true"
>
<path d="M7 11h2v2H7zm0 4h2v2H7zm4-4h2v2h-2zm0 4h2v2h-2zm4-4h2v2h-2zm0 4h2v2h-2z"></path>
<path d="M5 22h14c1.103 0 2-.897 2-2V6c0-1.103-.897-2-2-2h-2V2h-2v2H9V2H7v2H5c-1.103 0-2 .897-2 2v14c0 1.103.897 2 2 2zM19 8l.001 12H5V8h14z"></path>
</svg>
<span className="sr-only">Posted on:</span>
{modDatetime ? (
<span className={`italic ${size === "sm" ? "text-sm" : "text-base"}`}>
Updated:
</span>
) : (
<span className="sr-only">Published:</span>
)}
<span className={`italic ${size === "sm" ? "text-sm" : "text-base"}`}>
<FormattedDatetime datetime={datetime} />
<FormattedDatetime
pubDatetime={pubDatetime}
modDatetime={modDatetime}
/>
</span>
</div>
);
}

const FormattedDatetime = ({ datetime }: { datetime: string | Date }) => {
const myDatetime = new Date(datetime);
const FormattedDatetime = ({ pubDatetime, modDatetime }: DatetimesProps) => {
const myDatetime = new Date(modDatetime ? modDatetime : pubDatetime);

const date = myDatetime.toLocaleDateString(LOCALE.langTag, {
year: "numeric",
month: "long",
month: "short",
day: "numeric",
});

Expand All @@ -43,10 +61,10 @@ const FormattedDatetime = ({ datetime }: { datetime: string | Date }) => {

return (
<>
{date}
<time dateTime={myDatetime.toISOString()}>{date}</time>
<span aria-hidden="true"> | </span>
<span className="sr-only">&nbsp;at&nbsp;</span>
{time}
<span className="text-nowrap">{time}</span>
</>
);
};
28 changes: 16 additions & 12 deletions src/content/blog/adding-new-post.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
author: Sat Naing
pubDatetime: 2022-09-23T15:22:00Z
modDatetime: 2023-12-21T09:12:47.400Z
title: Adding new posts in AstroPaper theme
postSlug: adding-new-posts-in-astropaper-theme
featured: true
Expand All @@ -22,18 +23,21 @@ Frontmatter is the main place to store some important information about the blog

Here is the list of frontmatter property for each post.

| Property | Description | Remark |
| ------------------ | ------------------------------------------------------------------------------- | --------------------------------------------- |
| **_title_** | Title of the post. (h1) | required<sup>\*</sup> |
| **_description_** | Description of the post. Used in post excerpt and site description of the post. | required<sup>\*</sup> |
| **_pubDatetime_** | Published datetime in ISO 8601 format. | required<sup>\*</sup> |
| **_author_** | Author of the post. | default = SITE.author |
| **_postSlug_** | Slug for the post. Will automatically be slugified. | default = slugified title |
| **_featured_** | Whether or not display this post in featured section of home page | default = false |
| **_draft_** | Mark this post 'unpublished'. | default = false |
| **_tags_** | Related keywords for this post. Written in array yaml format. | default = others |
| **_ogImage_** | OG image of the post. Useful for social media sharing and SEO. | default = SITE.ogImage or generated OG image |
| **_canonicalURL_** | Canonical URL (absolute), in case the article already exists on other source. | default = `Astro.site` + `Astro.url.pathname` |
| Property | Description | Remark |
| ------------------ | ------------------------------------------------------------------------------------------- | --------------------------------------------- |
| **_title_** | Title of the post. (h1) | required<sup>\*</sup> |
| **_description_** | Description of the post. Used in post excerpt and site description of the post. | required<sup>\*</sup> |
| **_pubDatetime_** | Published datetime in ISO 8601 format. | required<sup>\*</sup> |
| **_modDatetime_** | Modified datetime in ISO 8601 format. (only add this property when a blog post is modified) | optional |
| **_author_** | Author of the post. | default = SITE.author |
| **_postSlug_** | Slug for the post. Will automatically be slugified. | default = slugified title |
| **_featured_** | Whether or not display this post in featured section of home page | default = false |
| **_draft_** | Mark this post 'unpublished'. | default = false |
| **_tags_** | Related keywords for this post. Written in array yaml format. | default = others |
| **_ogImage_** | OG image of the post. Useful for social media sharing and SEO. | default = SITE.ogImage or generated OG image |
| **_canonicalURL_** | Canonical URL (absolute), in case the article already exists on other source. | default = `Astro.site` + `Astro.url.pathname` |

> Tip! You can get ISO 8601 datetime by running `new Date().toISOString()` in the console. Make sure you remove quotes though.
Only `title`, `description` and `pubDatetime` fields in frontmatter must be specified.

Expand Down
35 changes: 26 additions & 9 deletions src/content/blog/how-to-add-an-estimated-reading-time.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: How to add an estimated reading time in AstroPaper
author: Sat Naing
pubDatetime: 2023-07-21T10:11:06.130Z
modDatetime: 2023-12-21T05:03:41.955Z
postSlug: how-to-add-estimated-reading-time
featured: false
draft: false
Expand Down Expand Up @@ -157,8 +158,15 @@ export interface Props {

const { post } = Astro.props;

const { title, author, description, ogImage, pubDatetime, tags, readingTime } =
post.data; // we can now directly access readingTime from frontmatter
const {
title,
author,
description,
ogImage,
readingTime, // we can now directly access readingTime from frontmatter
pubDatetime,
modDatetime,
tags } = post.data;

// other codes
---
Expand All @@ -181,8 +189,12 @@ const getSortedPosts = async (posts: CollectionEntry<"blog">[]) => {
.filter(({ data }) => !data.draft)
.sort(
(a, b) =>
Math.floor(new Date(b.data.pubDatetime).getTime() / 1000) -
Math.floor(new Date(a.data.pubDatetime).getTime() / 1000)
Math.floor(
new Date(b.data.modDatetime ?? b.data.pubDatetime).getTime() / 1000
) -
Math.floor(
new Date(a.data.modDatetime ?? a.data.pubDatetime).getTime() / 1000
)
);
};

Expand Down Expand Up @@ -215,7 +227,7 @@ But in this section, I'm gonna show you how I would display `readingTime` in my
Step (1) Update `Datetime` component to display `readingTime`
```ts
```tsx
import { LOCALE } from "@config";

export interface Props {
Expand All @@ -234,7 +246,7 @@ export default function Datetime({
return (
// other codes
<span className={`italic ${size === "sm" ? "text-sm" : "text-base"}`}>
<FormattedDatetime datetime={datetime} />
<FormattedDatetime pubDatetime={pubDatetime} modDatetime={modDatetime} />
<span> ({readingTime})</span> {/* display reading time */}
</span>
// other codes
Expand All @@ -248,10 +260,14 @@ file: Card.tsx
```ts
export default function Card({ href, frontmatter, secHeading = true }: Props) {
const { title, pubDatetime, description, readingTime } = frontmatter;
const { title, pubDatetime, modDatetime description, readingTime } = frontmatter;
return (
...
<Datetime datetime={pubDatetime} readingTime={readingTime} />
<Datetime
pubDatetime={pubDatetime}
modDatetime={modDatetime}
readingTime={readingTime}
/>
...
);
}
Expand All @@ -264,7 +280,8 @@ file: PostDetails.tsx
<main id="main-content">
<h1 class="post-title">{title}</h1>
<Datetime
datetime={pubDatetime}
pubDatetime={pubDatetime}
modDatetime={modDatetime}
size="lg"
className="my-2"
readingTime={readingTime}
Expand Down
1 change: 1 addition & 0 deletions src/content/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const blog = defineCollection({
z.object({
author: z.string().default(SITE.author),
pubDatetime: z.date(),
modDatetime: z.date().optional(),
title: z.string(),
postSlug: z.string().optional(),
featured: z.boolean().optional(),
Expand Down
22 changes: 22 additions & 0 deletions src/layouts/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export interface Props {
description?: string;
ogImage?: string;
canonicalURL?: string;
pubDatetime?: Date;
modDatetime?: Date;
}
const {
Expand All @@ -19,6 +21,8 @@ const {
description = SITE.desc,
ogImage = SITE.ogImage,
canonicalURL = new URL(Astro.url.pathname, Astro.site).href,
pubDatetime,
modDatetime,
} = Astro.props;
const socialImageURL = new URL(
Expand Down Expand Up @@ -49,6 +53,24 @@ const socialImageURL = new URL(
<meta property="og:url" content={canonicalURL} />
<meta property="og:image" content={socialImageURL} />

<!-- Article Published/Modified time -->
{
pubDatetime && (
<meta
property="article:published_time"
content={pubDatetime.toISOString()}
/>
)
}
{
modDatetime && (
<meta
property="article:modified_time"
content={modDatetime.toISOString()}
/>
)
}

<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={canonicalURL} />
Expand Down
37 changes: 27 additions & 10 deletions src/layouts/PostDetails.astro
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,16 @@ export interface Props {
const { post } = Astro.props;
const { title, author, description, ogImage, canonicalURL, pubDatetime, tags } =
post.data;
const {
title,
author,
description,
ogImage,
canonicalURL,
pubDatetime,
modDatetime,
tags,
} = post.data;
const { Content } = await post.render();
Expand All @@ -23,15 +31,19 @@ const ogUrl = new URL(
ogImageUrl ?? `/posts/${slugifyStr(title)}.png`,
Astro.url.origin
).href;
const layoutProps = {
title,
author,
description,
pubDatetime,
modDatetime,
canonicalURL,
ogImage: ogUrl,
};
---

<Layout
title={title}
author={author}
description={description}
ogImage={ogUrl}
canonicalURL={canonicalURL}
>
<Layout {...layoutProps}>
<Header />
<div class="mx-auto flex w-full max-w-3xl justify-start px-2">
<button
Expand All @@ -47,7 +59,12 @@ const ogUrl = new URL(
</div>
<main id="main-content">
<h1 transition:name={slugifyStr(title)} class="post-title">{title}</h1>
<Datetime datetime={pubDatetime} size="lg" className="my-2" />
<Datetime
pubDatetime={pubDatetime}
modDatetime={modDatetime}
size="lg"
className="my-2"
/>
<article id="article" role="article" class="prose mx-auto mt-8 max-w-3xl">
<Content />
</article>
Expand Down
13 changes: 9 additions & 4 deletions src/utils/getSortedPosts.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import type { CollectionEntry } from "astro:content";

const getSortedPosts = (posts: CollectionEntry<"blog">[]) =>
posts
const getSortedPosts = (posts: CollectionEntry<"blog">[]) => {
return posts
.filter(({ data }) => !data.draft)
.sort(
(a, b) =>
Math.floor(new Date(b.data.pubDatetime).getTime() / 1000) -
Math.floor(new Date(a.data.pubDatetime).getTime() / 1000)
Math.floor(
new Date(b.data.modDatetime ?? b.data.pubDatetime).getTime() / 1000
) -
Math.floor(
new Date(a.data.modDatetime ?? a.data.pubDatetime).getTime() / 1000
)
);
};

export default getSortedPosts;

0 comments on commit 80e67a1

Please sign in to comment.