mirror of
https://github.com/jaronpate/folio.git
synced 2026-10-18 17:06:56 +00:00
fix date off by one
This commit is contained in:
@@ -1,20 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { contentDateAttr, formatContentDate } from '~/utils/dates';
|
||||
|
||||
defineProps<{
|
||||
pages: Array<{
|
||||
path: string;
|
||||
title: string;
|
||||
date: string | Date;
|
||||
date: string;
|
||||
}>;
|
||||
}>();
|
||||
|
||||
const formatDate = (dateString: string | Date) => {
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
timeZone: 'UTC',
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -27,8 +20,8 @@ const formatDate = (dateString: string | Date) => {
|
||||
>
|
||||
<span class="listing__title">{{ page.title }}</span>
|
||||
<span class="listing__rule" aria-hidden="true"></span>
|
||||
<time :datetime="new Date(page.date).toISOString()">{{
|
||||
formatDate(page.date)
|
||||
<time :datetime="contentDateAttr(page.date)">{{
|
||||
formatContentDate(page.date)
|
||||
}}</time>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { contentDateAttr, formatContentDate } from '~/utils/dates';
|
||||
import {
|
||||
DEFAULT_IMAGE,
|
||||
SITE_NAME,
|
||||
@@ -54,19 +55,11 @@ if (!data.value) {
|
||||
const page = computed(() => data.value?.article ?? null);
|
||||
const isFolder = computed(() => data.value?.kind === 'folder');
|
||||
const articleTitle = page.value?.title ?? folderName.value;
|
||||
const formatDate = (date: Date | string) => {
|
||||
return new Date(date).toLocaleDateString('en-US', {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
timeZone: 'UTC',
|
||||
});
|
||||
};
|
||||
const articleDescription = page.value
|
||||
? page.value.description
|
||||
: `Writing from ${folderName.value}`;
|
||||
const layoutDescription = page.value
|
||||
? formatDate(page.value.date)
|
||||
? formatContentDate(page.value.date, 'long')
|
||||
: articleDescription;
|
||||
|
||||
useSeoMeta({
|
||||
@@ -83,10 +76,10 @@ useSeoMeta({
|
||||
twitterDescription: articleDescription,
|
||||
twitterImage: DEFAULT_IMAGE,
|
||||
articlePublishedTime: page.value
|
||||
? new Date(page.value.date).toISOString()
|
||||
? contentDateAttr(page.value.date)
|
||||
: undefined,
|
||||
articleModifiedTime: page.value
|
||||
? new Date(page.value.date).toISOString()
|
||||
? contentDateAttr(page.value.date)
|
||||
: undefined,
|
||||
articleAuthor: page.value ? [SITE_NAME] : undefined,
|
||||
});
|
||||
@@ -131,12 +124,8 @@ useHead({
|
||||
url: articleUrl,
|
||||
headline: articleTitle,
|
||||
description: articleDescription,
|
||||
datePublished: new Date(
|
||||
page.value.date,
|
||||
).toISOString(),
|
||||
dateModified: new Date(
|
||||
page.value.date,
|
||||
).toISOString(),
|
||||
datePublished: contentDateAttr(page.value.date),
|
||||
dateModified: contentDateAttr(page.value.date),
|
||||
image: {
|
||||
'@type': 'ImageObject',
|
||||
'@id': `${articleUrl}#article-image`,
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
const YMD = /^(\d{4})-(\d{2})-(\d{2})/;
|
||||
const HAS_TZ = /(?:Z|[+-]\d{2}:?\d{2})$/i;
|
||||
|
||||
export function formatContentDate(value: string, month: 'short' | 'long' = 'short') {
|
||||
const match = YMD.exec(value);
|
||||
if (!match) return '';
|
||||
return new Date(Date.UTC(+match[1], +match[2] - 1, +match[3], 12)).toLocaleDateString(
|
||||
'en-US',
|
||||
{ month, day: 'numeric', year: 'numeric', timeZone: 'UTC' },
|
||||
);
|
||||
}
|
||||
|
||||
export function contentDateAttr(value: string) {
|
||||
if (HAS_TZ.test(value)) return new Date(value).toISOString();
|
||||
return YMD.exec(value)?.[0] ?? '';
|
||||
}
|
||||
@@ -5,16 +5,14 @@ export function projectEntryHref(page: {
|
||||
return page.href || `/projects${page.path}`;
|
||||
}
|
||||
|
||||
export function sortProjects<
|
||||
T extends { title: string; date?: Date | string | null },
|
||||
>(pages: T[]) {
|
||||
export function sortProjects<T extends { title: string; date?: string | null }>(
|
||||
pages: T[],
|
||||
) {
|
||||
return [...pages].sort((a, b) => {
|
||||
const aTime = a.date ? new Date(a.date).getTime() : Number.NaN;
|
||||
const bTime = b.date ? new Date(b.date).getTime() : Number.NaN;
|
||||
const aHasDate = !Number.isNaN(aTime);
|
||||
const bHasDate = !Number.isNaN(bTime);
|
||||
if (aHasDate && bHasDate && aTime !== bTime) return bTime - aTime;
|
||||
if (aHasDate !== bHasDate) return aHasDate ? -1 : 1;
|
||||
if (!a.date && !b.date) return a.title.localeCompare(b.title);
|
||||
if (!a.date) return 1;
|
||||
if (!b.date) return -1;
|
||||
if (a.date !== b.date) return b.date.localeCompare(a.date);
|
||||
return a.title.localeCompare(b.title);
|
||||
});
|
||||
}
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ export default defineContentConfig({
|
||||
},
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
date: z.coerce.date(),
|
||||
date: z.string(),
|
||||
description: z.string(),
|
||||
hidden: z.boolean().optional(),
|
||||
}),
|
||||
@@ -26,7 +26,7 @@ export default defineContentConfig({
|
||||
description: z.string(),
|
||||
href: z.string().optional(),
|
||||
hidden: z.boolean().optional(),
|
||||
date: z.coerce.date().optional(),
|
||||
date: z.string().optional(),
|
||||
image: z.string().optional(),
|
||||
}),
|
||||
}),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
title: Unicord 🦄
|
||||
description: A Discord bot SDK that works like a web server.
|
||||
# href: https://unicord.hat.fish
|
||||
date: 2025-01-12
|
||||
---
|
||||
|
||||
I wanted writing a Discord bot to feel like writing a small web app. Register a handler, get a request, send a reply. *Bang!...bot done.* This became particularly interesting once bots could be served over HTTP.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
title: AI and the Barrier of Entry
|
||||
date: 2023-03-25T12:00:00.000Z
|
||||
description: We are witnessing the next major wave of technological innovation. And it's never been easier to be a part of it.
|
||||
hidden: true
|
||||
---
|
||||
|
||||
To be fair the term itself, **AI**, has had its meaning stretched pretty thin in the last few years. :margin-note[(Artificial Intelligence. The label has been doing too much work.)] It's been used to describe everything from a simple chatbot to a self driving car. That use is extremely broad. It doesn't really describe the space we think of as "AI".
|
||||
|
||||
Reference in New Issue
Block a user