blog utils & helpers
This commit is contained in:
parent
f691a6b377
commit
34f70f0412
|
@ -1,32 +0,0 @@
|
|||
import type { Post } from '$lib/types/post';
|
||||
import type { MarkdownMetadata } from '$content/types';
|
||||
import type { MdsvexImport } from './types';
|
||||
import { parseReadContent } from '$content/utils';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export function listPosts() {
|
||||
const posts = import.meta.glob<Post>('./blog/*.md', {
|
||||
eager: true,
|
||||
import: 'metadata'
|
||||
});
|
||||
|
||||
return parseReadContent(posts);
|
||||
}
|
||||
|
||||
export async function getPostMetadata(slug: string) {
|
||||
const { post } = await getPost(slug);
|
||||
return post;
|
||||
}
|
||||
|
||||
export async function getPost(slug: string) {
|
||||
try {
|
||||
const data: MdsvexImport<Post & MarkdownMetadata> = await import(`./blog/${slug}.md`);
|
||||
|
||||
return {
|
||||
post: { ...data.metadata, slug },
|
||||
Component: data.default
|
||||
};
|
||||
} catch {
|
||||
throw error(404, `Unable to find blog post "${slug}"`);
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
export interface MarkdownHeading {
|
||||
title: string;
|
||||
slug: string;
|
||||
level: number;
|
||||
children: MarkdownHeading[];
|
||||
}
|
||||
|
||||
export interface MarkdownMetadata {
|
||||
headings: MarkdownHeading[];
|
||||
}
|
||||
|
||||
export interface MdsvexImport<T extends MarkdownMetadata = MarkdownMetadata> {
|
||||
// Technically not correct but needed to make language-tools happy
|
||||
default: ConstructorOfATypedSvelteComponent;
|
||||
metadata: T;
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
import type { create_ssr_component } from 'svelte/internal';
|
||||
|
||||
/**
|
||||
* Sorts an array of objects by their `date` property in descending order.
|
||||
* @param a - The first object to compare.
|
||||
* @param b - The second object to compare.
|
||||
* @returns A number that represents the difference between the parsed dates of the two objects.
|
||||
*/
|
||||
export function dateSort<T extends { date?: string }>(a: T, b: T): number {
|
||||
return Date.parse(b.date) - Date.parse(a.date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an mdsvex component and returns the resulting HTML string.
|
||||
* @param component - The mdsvex component to render.
|
||||
* @returns The HTML string that was generated by rendering the component.
|
||||
* @throws An error if the `render` property of the component is not a function.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function renderMdsvexComponent(component: any): string {
|
||||
if (typeof component['render'] != 'function') {
|
||||
throw new Error("Unable to render something that isn't a mdsvex component");
|
||||
}
|
||||
|
||||
return (component as ReturnType<typeof create_ssr_component>).render().html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an md file path to a slug by removing the last segment of the path and the `.md` extension.
|
||||
* @param path - The path of the md file.
|
||||
* @returns The slug of the md file.
|
||||
*/
|
||||
export function mdPathToSlug(path: string) {
|
||||
return path.split('/').at(-1).slice(0, -3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an object of data that has string keys and values of type `T` that have an optional `date` property.
|
||||
* @param data - The object of data to parse.
|
||||
* @param dateSort - A function that sorts an array of objects by their `date` property in descending order.
|
||||
* @param mdPathToSlug - A function that converts an md file path to a slug.
|
||||
* @returns An array of objects that have a `slug` property and the properties of the original data objects.
|
||||
*/
|
||||
export function parseReadContent<T extends { date?: string }>(data: Record<string, T>): T[] {
|
||||
return Object.entries(data)
|
||||
.map(([file, data]) => ({
|
||||
slug: mdPathToSlug(file),
|
||||
...data
|
||||
}))
|
||||
.sort(dateSort);
|
||||
}
|
||||
|
||||
// Old utils
|
||||
// /**
|
||||
// * Formats a date string using the specified date style and locale.
|
||||
// * @param date - The date string to format.
|
||||
// * @param dateStyle - The style to use when formatting the date. Defaults to 'medium'.
|
||||
// * @param locales - The locale to use when formatting the date. Defaults to 'en'.
|
||||
// * @returns The formatted date string.
|
||||
// */
|
||||
// export function formatDate(date: string, dateStyle: DateStyle = 'medium', locales = 'en') {
|
||||
// const formatter = new Intl.DateTimeFormat(locales, { dateStyle });
|
||||
// return formatter.format(new Date(date));
|
||||
// }
|
||||
// type DateStyle = Intl.DateTimeFormatOptions['dateStyle'];
|
||||
/**
|
||||
* Formats a date string into a human-readable format.
|
||||
* @param date - The date string to format.
|
||||
* @returns A string representing the formatted date, or an empty string if the input is invalid.
|
||||
*/
|
||||
export const formatDate = (date) => {
|
||||
try {
|
||||
const d = new Date(date);
|
||||
return `${d.toLocaleString('default', {
|
||||
month: 'long'
|
||||
})} ${d.getDate()}, ${d.getFullYear()}`;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export const scrollIntoView = (selector: string) => {
|
||||
const scrollToElement = document.querySelector(selector);
|
||||
|
||||
if (!scrollToElement) return;
|
||||
|
||||
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||
|
||||
scrollToElement.scrollIntoView({
|
||||
block: 'nearest',
|
||||
inline: 'start',
|
||||
behavior: mediaQuery.matches ? 'auto' : 'smooth'
|
||||
});
|
||||
};
|
|
@ -1,17 +1,40 @@
|
|||
import type { MarkdownMetadata } from '$content/types';
|
||||
export interface MarkdownHeading {
|
||||
title: string;
|
||||
slug: string;
|
||||
level: number;
|
||||
children: MarkdownHeading[];
|
||||
}
|
||||
|
||||
export type Tag = 'DevOps' | 'Philosophy' | 'Updates' | '';
|
||||
export interface MarkdownMetadata {
|
||||
headings: MarkdownHeading[];
|
||||
}
|
||||
|
||||
export interface MdsvexImport<T extends MarkdownMetadata = MarkdownMetadata> {
|
||||
// Technically not correct but needed to make language-tools happy
|
||||
default: ConstructorOfATypedSvelteComponent;
|
||||
metadata: T;
|
||||
}
|
||||
|
||||
export type Tag = 'Projects' | 'Blog' | 'Updates' | '';
|
||||
|
||||
export interface Post extends MarkdownMetadata {
|
||||
type?: 'Blog' | 'projects' | string;
|
||||
date?: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
postTitle: string;
|
||||
type: 'blog' | 'projects';
|
||||
excerpt: string;
|
||||
image: string;
|
||||
slug?: string;
|
||||
datePublished: string;
|
||||
lastUpdated: string;
|
||||
seoMetaDescription: string;
|
||||
focusKeyphrase: string;
|
||||
featuredImage: string;
|
||||
featuredImageAlt: string;
|
||||
imagePublicId: string;
|
||||
href?: string;
|
||||
tags?: Tag[];
|
||||
subtitle?: string;
|
||||
teaserImage: string;
|
||||
title: string;
|
||||
isNotAnActualPost?: boolean;
|
||||
timeToRead: number;
|
||||
isAnExternalLink?: boolean;
|
||||
ogImage?: string;
|
||||
ogSquareImage?: string;
|
||||
twitterImage?: string;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
import type { Post } from '$lib/types/post';
|
||||
import type { MdsvexImport, MarkdownMetadata } from '$lib/types/post';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import readingTime from 'reading-time';
|
||||
|
||||
import { parseReadContent } from '$lib/utils/helpers';
|
||||
|
||||
const posts = import.meta.glob<MdsvexImport<Post & MarkdownMetadata>>('$content/blog/*.md', {
|
||||
eager: true,
|
||||
import: 'metadata'
|
||||
});
|
||||
|
||||
export function listPosts() {
|
||||
return parseReadContent(posts);
|
||||
}
|
||||
|
||||
export function getPost(slug: string) {
|
||||
try {
|
||||
const postKey = `$content/blog/${slug}.md`;
|
||||
const post = posts[postKey];
|
||||
|
||||
if (!post) {
|
||||
throw error(404, `Unable to find blog post "${slug}"`);
|
||||
}
|
||||
|
||||
const { imagePublicId } = data.metadata;
|
||||
try {
|
||||
return {
|
||||
post: {
|
||||
...data.metadata,
|
||||
slug,
|
||||
timeToRead: Math.ceil(readingTime(Component).minutes),
|
||||
featuredImage: getImageUrl(imagePublicId, { width: 1200, height: 630 }),
|
||||
ogImage: getImageUrl(imagePublicId, { width: 1200, height: 630 }),
|
||||
ogSquareImage: getImageUrl(imagePublicId, { width: 400, height: 400 }),
|
||||
twitterImage: getImageUrl(imagePublicId, { width: 1200, height: 630 })
|
||||
},
|
||||
Component: data.default
|
||||
};
|
||||
} catch (error) {
|
||||
throw error(404, `Unable to return blog post "${slug}"`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching blog post:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// export async function getPost(slug: string) {
|
||||
// try {
|
||||
// const postPath = Object.keys(posts).find((path) => path.includes(`${slug}.md`));
|
||||
// if (!postPath) {
|
||||
// throw error(404, `Unable to find blog post "${slug}"`);
|
||||
// }
|
||||
// const data = await posts[postPath]();
|
||||
|
||||
// const { imagePublicId } = data.metadata;
|
||||
|
||||
// const data: MdsvexImport<Post> = await import.meta.glob(`$content/blog/${slug}.md`);
|
||||
|
||||
// return {
|
||||
// post: {
|
||||
// ...data.metadata,
|
||||
// slug,
|
||||
// timeToRead: Math.ceil(readingTime(data.default).minutes),
|
||||
// featuredImage: getImageUrl(imagePublicId, { width: 1200, height: 630 }),
|
||||
// ogImage: getImageUrl(imagePublicId, { width: 1200, height: 630 }),
|
||||
// ogSquareImage: getImageUrl(imagePublicId, { width: 400, height: 400 }),
|
||||
// twitterImage: getImageUrl(imagePublicId, { width: 1200, height: 630 })
|
||||
// },
|
||||
// Component: data.default
|
||||
// };
|
||||
// } catch (error) {
|
||||
// console.error('Error fetching blog post:', error);
|
||||
// //
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Formats a date string into a human-readable format.
|
||||
* @param date - The date string to format.
|
||||
* @param locale - The locale to use when formatting the date. Defaults to 'en-US'.
|
||||
* @returns A string representing the formatted date, or an error something is invalid.
|
||||
*/
|
||||
export const formatDate = (date: string, locale: Intl.Locale = 'en-US') => {
|
||||
try {
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
};
|
||||
return new Date(date).toLocaleDateString(locale, options);
|
||||
} catch (e) {
|
||||
return 'Invalid date string provided or something failed in formatting date.';
|
||||
}
|
||||
};
|
|
@ -1,4 +1,55 @@
|
|||
import { readable } from 'svelte/store';
|
||||
import type { create_ssr_component } from 'svelte/internal';
|
||||
|
||||
/**
|
||||
* Parses an object of data that has string keys and values of type `T` that have an optional `date` property.
|
||||
* @param data - The object of data to parse.
|
||||
* @param dateSort - A function that sorts an array of objects by their `date` property in descending order.
|
||||
* @param mdPathToSlug - A function that converts an md file path to a slug.
|
||||
* @returns An array of objects that have a `slug` property and the properties of the original data objects.
|
||||
*/
|
||||
export function parseReadContent<T extends { date?: string }>(data: Record<string, T>): T[] {
|
||||
return Object.entries(data)
|
||||
.map(([file, data]) => ({
|
||||
slug: mdPathToSlug(file),
|
||||
...data
|
||||
}))
|
||||
.sort(dateSort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts an array of objects by their `date` property in descending order.
|
||||
* @param a - The first object to compare.
|
||||
* @param b - The second object to compare.
|
||||
* @returns A number that represents the difference between the parsed dates of the two objects.
|
||||
*/
|
||||
export function dateSort<T extends { date?: string }>(a: T, b: T): number {
|
||||
return Date.parse(b.date) - Date.parse(a.date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an mdsvex component and returns the resulting HTML string.
|
||||
* @param component - The mdsvex component to render.
|
||||
* @returns The HTML string that was generated by rendering the component.
|
||||
* @throws An error if the `render` property of the component is not a function.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function renderMdsvexComponent(component: any): string {
|
||||
if (typeof component['render'] != 'function') {
|
||||
throw new Error("Unable to render something that isn't a mdsvex component");
|
||||
}
|
||||
|
||||
return (component as ReturnType<typeof create_ssr_component>).render().html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an md file path to a slug by removing the last segment of the path and the `.md` extension.
|
||||
* @param path - The path of the md file.
|
||||
* @returns The slug of the md file.
|
||||
*/
|
||||
export function mdPathToSlug(path: string) {
|
||||
return path.split('/').at(-1).slice(0, -3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current timezone is between 0 and +3 hours UTC.
|
||||
|
@ -105,3 +156,8 @@ export const scrollIntoView = (selector: string) => {
|
|||
behavior: mediaQuery.matches ? 'auto' : 'smooth'
|
||||
});
|
||||
};
|
||||
|
||||
export const generateURL = (href?: string, type: string, slug?: string) => {
|
||||
if (href) return href;
|
||||
return `/${type}/${slug}`;
|
||||
};
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import type { MdsvexImport } from '$content/types';
|
||||
import type { MarkdownMetadata } from '$content/types';
|
||||
import type { MdsvexImport, MarkdownMetadata } from '$lib/types/post';
|
||||
import type { Project } from '$lib/types/projects';
|
||||
import { parseReadContent } from './utils';
|
||||
import { parseReadContent } from '$lib/utils/blog';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
/**
|
Loading…
Reference in New Issue