chore: WIP simplify data loading

This commit is contained in:
Corbin Crutchley
2022-08-09 02:26:45 -07:00
committed by Corbin Crutchley
parent 7b063092e9
commit e0591c63eb
21 changed files with 182 additions and 294 deletions

View File

@@ -7,11 +7,12 @@ import oembedTransformer from "@remark-embedder/transformer-oembed";
import * as TwitchTransformer from "gatsby-remark-embedder/dist/transformers/Twitch.js"; import * as TwitchTransformer from "gatsby-remark-embedder/dist/transformers/Twitch.js";
import rehypeSlug from "rehype-slug-custom-id"; import rehypeSlug from "rehype-slug-custom-id";
import { parent } from "./src/constants/site-config"; import { parent } from "./src/constants/site-config";
import { rehypeHeaderText } from "./src/utils/markdown/plugins/rehype-header-text"; import { rehypeHeaderText } from "./src/utils/markdown/rehype-header-text";
import { rehypeTabs } from "./src/utils/markdown/plugins/tabs"; import { rehypeTabs } from "./src/utils/markdown/tabs";
import { rehypeAstroImageMd } from "./src/utils/markdown/plugins/rehype-astro-image-md"; import { rehypeAstroImageMd } from "./src/utils/markdown/rehype-astro-image-md";
import { rehypeUnicornElementMap } from "./src/utils/markdown/plugins/rehype-unicorn-element-map"; import { rehypeUnicornElementMap } from "./src/utils/markdown/rehype-unicorn-element-map";
import { rehypeExcerpt } from "./src/utils/markdown/plugins/rehype-excerpt"; import { rehypeExcerpt } from "./src/utils/markdown/rehype-excerpt";
import { rehypeUnicornPopulatePost } from "./src/utils/markdown/rehype-unicorn-populate-post";
// TODO: Create types // TODO: Create types
import behead from "remark-behead"; import behead from "remark-behead";
@@ -45,6 +46,7 @@ export default defineConfig({
], ],
], ],
rehypePlugins: [ rehypePlugins: [
rehypeUnicornPopulatePost,
// This is required to handle unsafe HTML embedded into Markdown // This is required to handle unsafe HTML embedded into Markdown
rehypeRaw, rehypeRaw,
// Do not add the tabs before the slug. We rely on some of the heading // Do not add the tabs before the slug. We rely on some of the heading

View File

@@ -2,16 +2,18 @@
import listStyle from "./post-card-list.module.scss"; import listStyle from "./post-card-list.module.scss";
import PostCard from "../post-card/post-card.astro"; import PostCard from "../post-card/post-card.astro";
// import { PostListContext } from "constants/post-list-context"; // import { PostListContext } from "constants/post-list-context";
import { ListViewPosts } from "utils/fs/api"; // import { ListViewPosts } from "utils/fs/api";
export interface PostListProps { export interface PostListProps {
showWordCount?: boolean; showWordCount?: boolean;
numberOfArticles?: number; numberOfArticles?: number;
wordCount?: number; wordCount?: number;
unicornData?: ListViewPosts[number]["authors"]; // unicornData?: ListViewPosts[number]["authors"];
unicornData?: any
listAriaLabel: string; listAriaLabel: string;
// Added // Added
postsToDisplay: ListViewPosts // postsToDisplay: ListViewPosts
postsToDisplay: any
} }
/** /**
* unicornData - The data with the associated post. If present - you're on profile page * unicornData - The data with the associated post. If present - you're on profile page

View File

@@ -3,12 +3,13 @@
// TODO: Make user-profile-pic clickable again // TODO: Make user-profile-pic clickable again
import cardStyles from "./post-card.module.scss"; import cardStyles from "./post-card.module.scss";
import UserProfilePic from "../user-profile-pic/user-profile-pic.astro"; import UserProfilePic from "../user-profile-pic/user-profile-pic.astro";
import { ListViewPosts } from "utils/fs/api"; // import { ListViewPosts } from "utils/fs/api";
import dayjs from "dayjs"; import dayjs from "dayjs";
interface PostCardProps { interface PostCardProps {
title: string; // The title of the post title: string; // The title of the post
authors: ListViewPosts[number]["authors"]; // Info on the authors of the post // authors: ListViewPosts[number]["authors"]; // Info on the authors of the post
authors: any; // Info on the authors of the post
published: string; // Date the author published the post published: string; // Date the author published the post
tags: string[]; // List of tags associated with the post tags: string[]; // List of tags associated with the post
excerpt: string; // The autogenerated excerpt excerpt: string; // The autogenerated excerpt

View File

@@ -1,32 +0,0 @@
import { PickDeep } from "ts-util-helpers";
import { PostInfo } from "types/PostInfo";
export const seriesPostsPick = {
title: true,
slug: true,
series: true,
order: true,
} as const;
export type SeriesPostInfo = PickDeep<PostInfo, typeof seriesPostsPick>;
export const postBySlug = {
title: true,
slug: true,
content: true,
wordCount: true,
series: true,
order: true,
originalLink: true,
tags: true,
edited: true,
published: true,
authors: true,
description: true,
excerpt: true,
license: true,
translations: true,
collectionSlug: true,
} as const;
export type SlugPostInfo = PickDeep<PostInfo, typeof postBySlug>;

View File

@@ -7,13 +7,14 @@ import PostList from "components/post-card-list/post-card-list.astro";
// import { Pagination } from "components/pagination"; // import { Pagination } from "components/pagination";
// import { FilterSearchBar } from "components/filter-search-bar"; // import { FilterSearchBar } from "components/filter-search-bar";
import { siteMetadata } from "constants/site-config"; import { siteMetadata } from "constants/site-config";
import { ListViewPosts } from "utils/fs/api"; // import { ListViewPosts } from "utils/fs/api";
interface PostListTemplateProps { interface PostListTemplateProps {
numberOfPages: number; numberOfPages: number;
limitNumber: number; limitNumber: number;
pageIndex: number; pageIndex: number;
posts: ListViewPosts; // posts: ListViewPosts;
posts: any;
} }
const { const {

View File

@@ -1,6 +1,5 @@
import { UnicornInfo } from "./UnicornInfo"; import { UnicornInfo } from "./UnicornInfo";
import { PostInfo } from "types/PostInfo"; import { PostInfo } from "types/PostInfo";
import { PickDeep } from "ts-util-helpers";
export interface CollectionInfo { export interface CollectionInfo {
slug: string; slug: string;

View File

@@ -3,28 +3,30 @@ import { LicenseInfo } from "./LicenseInfo";
import { Languages } from "types/index"; import { Languages } from "types/index";
import { MarkdownInstance } from "astro"; import { MarkdownInstance } from "astro";
export interface PostInfo { export interface RawPostInfo {
title: string;
published: string;
authors: string[];
tags: string[];
attached: string[];
license: string;
description?: string;
edited?: string;
series?: string;
order?: number;
originalLink?: string;
}
export interface PostInfo extends RawPostInfo {
slug: string; slug: string;
locale: Languages; locale: Languages;
Content: MarkdownInstance<never>['Content']; Content: MarkdownInstance<never>['Content'];
title: string; authorsMeta: UnicornInfo[];
published: string; licenseMeta: LicenseInfo;
edited?: string;
authors: UnicornInfo[];
license: LicenseInfo;
excerpt: string; excerpt: string;
wordCount: number; wordCount: number;
description?: string;
series?: string;
collectionSlug?: string | null; collectionSlug?: string | null;
order?: number;
originalLink?: string;
content: string;
tags: string[];
translations: Partial<Record<Languages, string>>; translations: Partial<Record<Languages, string>>;
}
export interface RenderedPostInfo {
headingsWithId?: Array<{ headingsWithId?: Array<{
// Title value // Title value
value: string; value: string;
@@ -32,4 +34,4 @@ export interface RenderedPostInfo {
slug: string; slug: string;
depth: number; depth: number;
}>; }>;
} }

View File

@@ -1,14 +1,12 @@
import { PronounInfo } from "./PronounInfo"; import { PronounInfo } from "./PronounInfo";
import { RolesEnum } from "./RolesInfo"; import { RolesEnum } from "./RolesInfo";
export interface UnicornInfo { export interface RawUnicornInfo {
id: string;
name: string; name: string;
firstName: string; firstName: string;
lastName: string; lastName: string;
id: string;
description: string; description: string;
color: string;
roles: RolesEnum[];
socials: { socials: {
twitter?: string; twitter?: string;
github?: string; github?: string;
@@ -17,8 +15,16 @@ export interface UnicornInfo {
twitch?: string; twitch?: string;
dribbble?: string; dribbble?: string;
}; };
pronouns: PronounInfo; pronouns: string;
profileImg: { profileImg: string;
color: string;
roles: Array<RolesEnum['id']>;
}
export interface UnicornInfo extends RawUnicornInfo {
rolesMeta: RolesEnum[];
pronounsMeta: PronounInfo;
profileImgMeta: {
// Relative to "public/unicorns" // Relative to "public/unicorns"
relativePath: string; relativePath: string;
// Relative to site root // Relative to site root

43
src/utils/api.ts Normal file
View File

@@ -0,0 +1,43 @@
import {
unicorns,
licenses,
} from "../data";
import { PostInfo } from "types/PostInfo";
import { Languages } from "types/index";
import { MarkdownInstance } from "astro";
let allPostsCache = new WeakMap<object, PostInfo[]>();
export function getAllPosts(
posts: MarkdownInstance<PostInfo>[],
language: Languages,
cacheString: null | object = null
) {
if (cacheString) {
const cacheData = allPostsCache.get(cacheString);
if (cacheData) return cacheData as any;
}
if (cacheString) allPostsCache.set(cacheString, posts);
return posts
.filter(post => post.frontmatter.locale === language);
}
const listViewCache = {};
export const getAllPostsForListView = (
posts: MarkdownInstance<PostInfo>[],
language: Languages,
): PostInfo[] => {
let allPosts = getAllPosts(posts, language, listViewCache);
// sort posts by date in descending order
allPosts = allPosts.sort((post1, post2) => {
const date1 = new Date(post1.published);
const date2 = new Date(post2.published);
return date1 > date2 ? -1 : 1;
});
return allPosts;
};

View File

@@ -13,7 +13,7 @@ export const sponsorsDirectory = join(process.cwd(), "public/sponsors");
const unicornsRaw: Array< const unicornsRaw: Array<
Omit<UnicornInfo, "roles" | "pronouns" | "profileImg"> & { Omit<UnicornInfo, "roles" | "pronouns" | "profileImg"> & {
roles: string[]; roles: string[];
pronouns?: string; pronouns: string;
profileImg: string; profileImg: string;
} }
> = JSON.parse( > = JSON.parse(
@@ -43,7 +43,7 @@ const fullUnicorns: UnicornInfo[] = unicornsRaw.map((unicorn) => {
// Mutation go BRR // Mutation go BRR
const newUnicorn: UnicornInfo = unicorn as never; const newUnicorn: UnicornInfo = unicorn as never;
newUnicorn.profileImg = { newUnicorn.profileImgMeta = {
height: profileImgSize.height as number, height: profileImgSize.height as number,
width: profileImgSize.width as number, width: profileImgSize.width as number,
relativePath: unicorn.profileImg, relativePath: unicorn.profileImg,
@@ -51,20 +51,13 @@ const fullUnicorns: UnicornInfo[] = unicornsRaw.map((unicorn) => {
absoluteFSPath, absoluteFSPath,
}; };
newUnicorn.roles = unicorn.roles.map( newUnicorn.rolesMeta = unicorn.roles.map(
(role) => rolesRaw.find((rRole) => rRole.id === role)! (role) => rolesRaw.find((rRole) => rRole.id === role)!
); );
newUnicorn.pronouns = pronounsRaw.find( newUnicorn.pronounsMeta = pronounsRaw.find(
(proWithNouns) => proWithNouns.id === unicorn?.pronouns (proWithNouns) => proWithNouns.id === unicorn.pronouns
) || { )!;
id: "",
they: "",
them: "",
their: "",
theirs: "",
themselves: "",
};
return newUnicorn; return newUnicorn;
}); });

View File

@@ -1,21 +0,0 @@
import {
dataDirectory,
unicorns,
pronouns,
licenses,
roles,
postsDirectory,
collectionsDirectory,
} from "./get-datas";
export * from "./posts-and-collections-api";
export {
unicorns,
pronouns,
licenses,
roles,
dataDirectory,
postsDirectory,
collectionsDirectory,
};

View File

@@ -1,120 +0,0 @@
import {
unicorns,
licenses,
} from "utils/fs/get-datas";
import { PostInfo } from "types/PostInfo";
import { getExcerpt } from "utils/markdown/getExcerpt";
import { Languages } from "types/index";
import { MarkdownInstance } from "astro";
// const getIndexPath = (lang: Languages) => {
// const indexPath = lang !== "en" ? `index.${lang}.md` : `index.md`;
// return indexPath;
// };
export function extendPostMetadata(
post: MarkdownInstance<PostInfo>
) {
// Split based on `/`, even in Windows
const directorySplit = post.file.split('/');
// This is the folder name, AKA how we generate the slug ID
const slug = directorySplit.at(-2);
/** Calculate post locale */
// index.md or index.es.md
const indexName = directorySplit.at(-1);
const indexSplit = indexName.split('.');
let locale = indexSplit.at(-2);
if (locale === 'index') {
locale = 'en';
}
// // TODO: Add translations
// if (fields.translations) {
// const langsToQuery: Languages[] = Object.keys(languages).filter(
// (l) => l !== lang
// ) as never;
// pickedData.translations = langsToQuery
// .filter((lang) =>
// fs.existsSync(resolve(dirname(fullPath), getIndexPath(lang)))
// )
// .reduce((prev, lang) => {
// prev[lang] = languages[lang];
// return prev;
// }, {} as Record<Languages, string>);
// }
// // TODO: Add collection slug
// if (fields.collectionSlug) {
// if (frontmatterData.series) {
// pickedData.collectionSlug = collectionsByName.find(
// (collection) => collection.associatedSeries === frontmatterData.series
// )?.slug;
// }
// if (!pickedData.collectionSlug) pickedData.collectionSlug = null;
// }
const authors = (post.frontmatter.authors as never as string[]).map(
(author) => unicorns.find((unicorn) => unicorn.id === author)!
);
let license;
if (post.frontmatter.license) {
license = licenses.find(
(l) => l.id === post.frontmatter.license as never as string
);
}
if (!license) license = null;
return {
...post.frontmatter,
Content: post.Content,
slug,
locale,
authors,
license
} as PostInfo;
}
let allPostsCache = new WeakMap<object, PostInfo[]>();
export function getAllPosts(
posts: MarkdownInstance<PostInfo>[],
language: Languages,
cacheString: null | object = null
) {
if (cacheString) {
const cacheData = allPostsCache.get(cacheString);
if (cacheData) return cacheData as any;
}
// TODO: Move `Astro.glob` here
// const posts = await Astro.glob<PostInfo>('../../content/blog/**/*.md')
const newPosts = posts
.map(post => extendPostMetadata(post));
if (cacheString) allPostsCache.set(cacheString, newPosts);
return newPosts
.filter(post => post.locale === language);
}
const listViewCache = {};
export const getAllPostsForListView = (
posts: MarkdownInstance<PostInfo>[],
language: Languages,
): PostInfo[] => {
let allPosts = getAllPosts(posts, language, listViewCache);
// sort posts by date in descending order
allPosts = allPosts.sort((post1, post2) => {
const date1 = new Date(post1.published);
const date2 = new Date(post2.published);
return date1 > date2 ? -1 : 1;
});
return allPosts;
};

View File

@@ -1,69 +0,0 @@
import { unified } from "unified";
import parse from "remark-parse";
import stringify from "remark-stringify";
import english from "retext-english";
import remark2retext from "remark-retext";
import {visit} from "unist-util-visit";
import { Root, Node, Parent, Text } from "hast";
import flatFilter from "unist-util-flat-filter";
function count(counts: Record<string, number>) {
return () => counter;
function counter(tree: Root) {
visit(tree, visitor);
function visitor(node: Node) {
counts[node.type] = (counts[node.type] || 0) + 1;
}
}
}
function countInline(counts: Record<string, number>) {
return () => counter;
function counter(tree: Root) {
const inlineCodeAST = flatFilter(
tree,
(node) => node.type === "inlineCode"
) as Parent;
counts["InlineCodeWords"] = 0;
if (inlineCodeAST && inlineCodeAST.children) {
counts["InlineCodeWords"] = inlineCodeAST.children.reduce(
(numberOfInline, inlineCodeNode) => {
const { value } = inlineCodeNode as Text;
const words = value.split(/\b/g);
return numberOfInline + words.length;
},
0
);
}
return tree;
}
}
export function countContent(content: string) {
const counts: Record<string, number> = {};
unified()
.use(parse)
.use(countInline(counts))
.use(remark2retext, unified().use(english).use(count(counts)))
.use(stringify)
.processSync(content);
return counts as {
InlineCodeWords: number;
RootNode: number;
ParagraphNode: number;
SentenceNode: number;
WordNode: number;
TextNode: number;
WhiteSpaceNode: number;
PunctuationNode: number;
SymbolNode: number;
SourceNode: number;
};;
}

View File

@@ -10,8 +10,8 @@ import path from "path";
/** /**
* They need to be the same `getImage` with the same `globalThis` instance, thanks to the "hack" workaround. * They need to be the same `getImage` with the same `globalThis` instance, thanks to the "hack" workaround.
*/ */
import { getImage } from "../../../../node_modules/@astrojs/image"; import { getImage } from "../../../node_modules/@astrojs/image";
import sharp_service from "../../../../node_modules/@astrojs/image/dist/loaders/sharp.js"; import sharp_service from "../../../node_modules/@astrojs/image/dist/loaders/sharp.js";
interface RehypeAstroImageProps { interface RehypeAstroImageProps {
maxHeight?: number; maxHeight?: number;

View File

@@ -3,8 +3,8 @@ import { Plugin } from "unified";
import { visit } from "unist-util-visit"; import { visit } from "unist-util-visit";
import { EMBED_SIZE } from "../constants"; import { EMBED_SIZE } from "./constants";
import { isRelativePath } from "../../../utils/url-paths"; import { isRelativePath } from "../../utils/url-paths";
import { fromHtml } from "hast-util-from-html"; import { fromHtml } from "hast-util-from-html";
import path from "path"; import path from "path";

View File

@@ -0,0 +1,81 @@
import { Root } from "hast";
import { Plugin } from "unified";
import {visit} from 'unist-util-visit'
import matter from "gray-matter";
import { readFileSync } from "fs";
import * as path from "path";
import { licenses, unicorns } from "utils/data";
interface RehypeUnicornPopulatePostProps {
}
export const rehypeUnicornPopulatePost: Plugin<
[RehypeUnicornPopulatePostProps | never],
Root
> = () => {
return (tree, file) => {
function setData(key: string, val: any) {
(file.data.astro as any).frontmatter[key] = val;
}
const fileContents = readFileSync(file.path, "utf8");
const { data: frontmatter } = matter(fileContents);
const directorySplit = file.path.split(path.sep);
// This is the folder name, AKA how we generate the slug ID
const slug = directorySplit.at(-2);
// Calculate post locale
// index.md or index.es.md
const indexName = directorySplit.at(-1);
const indexSplit = indexName.split('.');
let locale = indexSplit.at(-2);
if (locale === 'index') {
locale = 'en';
}
// // TODO: Add translations
// if (fields.translations) {
// const langsToQuery: Languages[] = Object.keys(languages).filter(
// (l) => l !== lang
// ) as never;
// pickedData.translations = langsToQuery
// .filter((lang) =>
// fs.existsSync(resolve(dirname(fullPath), getIndexPath(lang)))
// )
// .reduce((prev, lang) => {
// prev[lang] = languages[lang];
// return prev;
// }, {} as Record<Languages, string>);
// }
// // TODO: Add collection slug
// if (fields.collectionSlug) {
// if (frontmatterData.series) {
// pickedData.collectionSlug = collectionsByName.find(
// (collection) => collection.associatedSeries === frontmatterData.series
// )?.slug;
// }
// if (!pickedData.collectionSlug) pickedData.collectionSlug = null;
// }
const authorsMeta = (frontmatter.authors as string[]).map(
(author) => unicorns.find((unicorn) => unicorn.id === author)!
);
let license;
if (frontmatter.license) {
license = licenses.find(
(l) => l.id === frontmatter.license
);
}
if (!license) license = null;
setData('slug', slug);
setData('locale', locale);
setData('authorsMeta', authorsMeta);
setData('license', license);
};
};