Add support for new config.yaml
This commit is contained in:
@ -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 },
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
@ -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 } : {}) };
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
Reference in New Issue
Block a user