Add support for new config.yaml

This commit is contained in:
prototypa
2023-07-27 21:52:04 -04:00
parent 8c4698412e
commit d6f3055e31
54 changed files with 860 additions and 591 deletions

View File

@ -1,9 +1,20 @@
import { getCollection } from 'astro:content';
import type { CollectionEntry } from 'astro:content';
import type { Post } from '~/types';
import { cleanSlug, trimSlash, POST_PERMALINK_PATTERN } from './permalinks';
import { APP_BLOG_CONFIG } from '~/utils/config';
import { cleanSlug, trimSlash, BLOG_BASE, POST_PERMALINK_PATTERN, CATEGORY_BASE, TAG_BASE } from './permalinks';
const generatePermalink = async ({ id, slug, publishDate, category }) => {
const generatePermalink = async ({
id,
slug,
publishDate,
category,
}: {
id: string;
slug: string;
publishDate: Date;
category: string | undefined;
}) => {
const year = String(publishDate.getFullYear()).padStart(4, '0');
const month = String(publishDate.getMonth() + 1).padStart(2, '0');
const day = String(publishDate.getDate()).padStart(2, '0');
@ -33,33 +44,46 @@ const getNormalizedPost = async (post: CollectionEntry<'post'>): Promise<Post> =
const { Content, remarkPluginFrontmatter } = await post.render();
const {
publishDate: rawPublishDate = new Date(),
updateDate: rawUpdateDate,
title,
excerpt,
image,
tags: rawTags = [],
category: rawCategory,
author = 'Anonymous',
publishDate: rawPublishDate = new Date(),
...rest
author,
draft = false,
metadata = {},
} = data;
const slug = cleanSlug(rawSlug.split('/').pop());
const publishDate = new Date(rawPublishDate);
const updateDate = rawUpdateDate ? new Date(rawUpdateDate) : undefined;
const category = rawCategory ? cleanSlug(rawCategory) : undefined;
const tags = rawTags.map((tag: string) => cleanSlug(tag));
return {
id: id,
slug: slug,
permalink: await generatePermalink({ id, slug, publishDate, category }),
publishDate: publishDate,
updateDate: updateDate,
title: title,
excerpt: excerpt,
image: image,
category: category,
tags: tags,
author: author,
...rest,
draft: draft,
metadata,
Content: Content,
// or 'body' in case you consume from API
permalink: await generatePermalink({ id, slug, publishDate, category }),
// or 'content' in case you consume from API
readingTime: remarkPluginFrontmatter?.readingTime,
};
@ -78,6 +102,20 @@ const load = async function (): Promise<Array<Post>> {
let _posts: Array<Post>;
/** */
export const isBlogEnabled = APP_BLOG_CONFIG.isEnabled;
export const isBlogListRouteEnabled = APP_BLOG_CONFIG.list.isEnabled;
export const isBlogPostRouteEnabled = APP_BLOG_CONFIG.post.isEnabled;
export const isBlogCategoryRouteEnabled = APP_BLOG_CONFIG.category.isEnabled;
export const isBlogTagRouteEnabled = APP_BLOG_CONFIG.tag.isEnabled;
export const blogListRobots = APP_BLOG_CONFIG.list.robots;
export const blogPostRobots = APP_BLOG_CONFIG.post.robots;
export const blogCategoryRobots = APP_BLOG_CONFIG.category.robots;
export const blogTagRobots = APP_BLOG_CONFIG.tag.robots;
export const blogPostsPerPage = APP_BLOG_CONFIG?.postsPerPage;
/** */
export const fetchPosts = async (): Promise<Array<Post>> => {
if (!_posts) {
@ -124,25 +162,71 @@ export const findLatestPosts = async ({ count }: { count?: number }): Promise<Ar
};
/** */
export const findTags = async (): Promise<Array<string>> => {
const posts = await fetchPosts();
const tags = posts.reduce((acc, post: Post) => {
if (post.tags && Array.isArray(post.tags)) {
return [...acc, ...post.tags];
}
return acc;
}, []);
return [...new Set(tags)];
export const getStaticPathsBlogList =
() =>
async ({ paginate }) => {
if (!isBlogEnabled || !isBlogListRouteEnabled) return [];
return paginate(await fetchPosts(), {
params: { blog: BLOG_BASE || undefined },
pageSize: blogPostsPerPage,
});
};
/** */
export const getStaticPathsBlogPost = () => async () => {
if (!isBlogEnabled || !isBlogPostRouteEnabled) return [];
return (await fetchPosts()).map((post) => ({
params: {
blog: post.permalink,
},
props: { post },
}));
};
/** */
export const findCategories = async (): Promise<Array<string>> => {
const posts = await fetchPosts();
const categories = posts.reduce((acc, post: Post) => {
if (post.category) {
return [...acc, post.category];
}
return acc;
}, []);
return [...new Set(categories)];
};
export const getStaticPathsBlogCategory =
() =>
async ({ paginate }) => {
if (!isBlogEnabled || !isBlogCategoryRouteEnabled) return [];
const posts = await fetchPosts();
const categories = new Set();
posts.map((post) => {
typeof post.category === 'string' && categories.add(post.category.toLowerCase());
});
return Array.from(categories).map((category: string) =>
paginate(
posts.filter((post) => typeof post.category === 'string' && category === post.category.toLowerCase()),
{
params: { category: category, blog: CATEGORY_BASE || undefined },
pageSize: blogPostsPerPage,
props: { category },
}
)
);
};
/** */
export const getStaticPathsBlogTag =
() =>
async ({ paginate }) => {
if (!isBlogEnabled || !isBlogTagRouteEnabled) return [];
const posts = await fetchPosts();
const tags = new Set();
posts.map((post) => {
Array.isArray(post.tags) && post.tags.map((tag) => tags.add(tag.toLowerCase()));
});
return Array.from(tags).map((tag: string) =>
paginate(
posts.filter((post) => Array.isArray(post.tags) && post.tags.find((elem) => elem.toLowerCase() === tag)),
{
params: { tag: tag, blog: TAG_BASE || undefined },
pageSize: blogPostsPerPage,
props: { tag },
}
)
);
};

View File

@ -1,3 +1,7 @@
import { getImage } from '@astrojs/image';
import type { OpenGraph } from '@astrolib/seo/src/types';
import type { ImageMetadata } from 'astro';
const load = async function () {
let images: Record<string, () => Promise<unknown>> | undefined = undefined;
try {
@ -8,12 +12,12 @@ const load = async function () {
return images;
};
let _images;
let _images: Record<string, () => Promise<unknown>> | undefined = undefined;
/** */
export const fetchLocalImages = async () => {
_images = _images || load();
return await _images;
_images = _images || (await load());
return _images;
};
/** */
@ -33,5 +37,58 @@ export const findImage = async (imagePath?: string) => {
const images = await fetchLocalImages();
const key = imagePath.replace('~/', '/src/');
return typeof images[key] === 'function' ? (await images[key]())['default'] : null;
return images && typeof images[key] === 'function'
? ((await images[key]()) as { default: unknown })['default']
: null;
};
/** */
export const adaptOpenGraphImages = async (
openGraph: OpenGraph = {},
astroSite: URL | undefined = new URL('')
): Promise<OpenGraph> => {
if (!openGraph?.images?.length) {
return openGraph;
}
const images = openGraph.images;
const defaultWidth = 1200;
const defaultHeight = 626;
const adaptedImages = await Promise.all(
images.map(async (image) => {
if (image?.url) {
const resolvedImage = (await findImage(image.url)) as ImageMetadata | undefined;
if (!resolvedImage) {
return {
url: '',
};
}
const _image = await getImage({
src: resolvedImage,
alt: 'Placeholder alt',
width: image?.width || defaultWidth,
height: image?.height || defaultHeight,
});
if (typeof _image === 'object') {
return {
url: typeof _image.src === 'string' ? String(new URL(_image.src, astroSite)) : 'pepe',
width: typeof _image.width === 'number' ? _image.width : undefined,
height: typeof _image.height === 'number' ? _image.height : undefined,
};
}
return {
url: '',
};
}
return {
url: '',
};
})
);
return { ...openGraph, ...(adaptedImages ? { images: adaptedImages } : {}) };
};

View File

@ -1,6 +1,7 @@
import slugify from 'limax';
import { SITE, BLOG } from '~/config.mjs';
import { SITE_CONFIG, APP_BLOG_CONFIG } from '~/utils/config';
import { trim } from '~/utils/utils';
export const trimSlash = (s: string) => trim(trim(s, '/'));
@ -9,10 +10,10 @@ const createPath = (...params: string[]) => {
.map((el) => trimSlash(el))
.filter((el) => !!el)
.join('/');
return '/' + paths + (SITE.trailingSlash && paths ? '/' : '');
return '/' + paths + (SITE_CONFIG.trailingSlash && paths ? '/' : '');
};
const BASE_PATHNAME = SITE.basePathname;
const BASE_PATHNAME = SITE_CONFIG.base || '/';
export const cleanSlug = (text = '') =>
trimSlash(text)
@ -20,18 +21,18 @@ export const cleanSlug = (text = '') =>
.map((slug) => slugify(slug))
.join('/');
export const POST_PERMALINK_PATTERN = trimSlash(BLOG?.post?.permalink || '/%slug%');
export const BLOG_BASE = cleanSlug(APP_BLOG_CONFIG?.list?.pathname);
export const CATEGORY_BASE = cleanSlug(APP_BLOG_CONFIG?.category?.pathname);
export const TAG_BASE = cleanSlug(APP_BLOG_CONFIG?.tag?.pathname) || 'tag';
export const BLOG_BASE = cleanSlug(BLOG?.list?.pathname);
export const CATEGORY_BASE = cleanSlug(BLOG?.category?.pathname || 'category');
export const TAG_BASE = cleanSlug(BLOG?.tag?.pathname) || 'tag';
export const POST_PERMALINK_PATTERN = trimSlash(APP_BLOG_CONFIG?.post?.permalink || `${BLOG_BASE}/%slug%`);
/** */
export const getCanonical = (path = ''): string | URL => {
const url = String(new URL(path, SITE.origin));
if (SITE.trailingSlash == false && path && url.endsWith('/')) {
const url = String(new URL(path, SITE_CONFIG.site));
if (SITE_CONFIG.trailingSlash == false && path && url.endsWith('/')) {
return url.slice(0, -1);
} else if (SITE.trailingSlash == true && path && !url.endsWith('/')) {
} else if (SITE_CONFIG.trailingSlash == true && path && !url.endsWith('/')) {
return url + '/';
}
return url;

View File

@ -1,7 +1,7 @@
import { DATE_FORMATTER } from '~/config.mjs';
import { I18N_CONFIG } from '~/utils/config';
const formatter =
DATE_FORMATTER ||
I18N_CONFIG?.dateFormatter ||
new Intl.DateTimeFormat('en', {
year: 'numeric',
month: 'short',
@ -10,7 +10,12 @@ const formatter =
});
/* eslint-disable no-mixed-spaces-and-tabs */
export const getFormattedDate = (date: Date) => (date ? formatter.format(date) : '');
export const getFormattedDate = (date: Date) =>
date
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
/* @ts-ignore */
formatter.format(date)
: '';
export const trim = (str = '', ch?: string) => {
let start = 0,
@ -19,3 +24,37 @@ export const trim = (str = '', ch?: string) => {
while (end > start && str[end - 1] === ch) --end;
return start > 0 || end < str.length ? str.substring(start, end) : str;
};
// Function to format a number in thousands (K) or millions (M) format depending on its value
export const toUiAmount = (amount: number) => {
if (!amount) return 0;
let value;
if (amount >= 1000000000) {
const formattedNumber = (amount / 1000000000).toFixed(1);
if (Number(formattedNumber) === parseInt(formattedNumber)) {
value = parseInt(formattedNumber) + 'B';
} else {
value = formattedNumber + 'B';
}
} else if (amount >= 1000000) {
const formattedNumber = (amount / 1000000).toFixed(1);
if (Number(formattedNumber) === parseInt(formattedNumber)) {
value = parseInt(formattedNumber) + 'M';
} else {
value = formattedNumber + 'M';
}
} else if (amount >= 1000) {
const formattedNumber = (amount / 1000).toFixed(1);
if (Number(formattedNumber) === parseInt(formattedNumber)) {
value = parseInt(formattedNumber) + 'K';
} else {
value = formattedNumber + 'K';
}
} else {
value = Number(amount).toFixed(0);
}
return value;
};