From 74a37bebb14ead5997294db3ab62a677ed997c65 Mon Sep 17 00:00:00 2001 From: Norbert Melzer Date: Sat, 1 Jul 2023 14:49:28 +0200 Subject: [PATCH 01/11] refactor: blog type --- gatsby-config.ts | 2 + package.json | 1 + .../gatsby-node.ts | 119 ++++++++ .../package.json | 5 + src/components/articlepreview.tsx | 40 ++- src/gatsby-types.d.ts | 281 ++++++++++++++++-- src/pages/index.tsx | 6 +- .../index.tsx | 41 ++- src/templates/tags.tsx | 8 +- tests/components/articlepreview.test.tsx | 12 +- tsconfig.json | 3 +- yarn.lock | 5 + 12 files changed, 440 insertions(+), 83 deletions(-) create mode 100644 plugins/gatsby-transformer-source-split/gatsby-node.ts create mode 100644 plugins/gatsby-transformer-source-split/package.json rename src/pages/{{mdx.frontmatter__date}-{mdx.frontmatter__slug} => {blog.date}-{blog.slug}}/index.tsx (79%) diff --git a/gatsby-config.ts b/gatsby-config.ts index 9f7464f..255072f 100644 --- a/gatsby-config.ts +++ b/gatsby-config.ts @@ -19,6 +19,8 @@ const config: GatsbyConfig = { "gatsby-plugin-mdx", "gatsby-plugin-postcss", "gatsby-plugin-readtime-nz", + "gatsby-plugin-mdx-source-name", + "gatsby-transformer-source-split", { resolve: "gatsby-source-filesystem", options: { diff --git a/package.json b/package.json index c833618..23be887 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "gatsby": "^5.10.0", "gatsby-plugin-image": "^3.10.0", "gatsby-plugin-mdx": "^5.10.0", + "gatsby-plugin-mdx-source-name": "^1.0.1", "gatsby-plugin-sharp": "^5.10.0", "gatsby-plugin-sitemap": "^6.10.0", "gatsby-remark-highlight-code": "^3.3.0", diff --git a/plugins/gatsby-transformer-source-split/gatsby-node.ts b/plugins/gatsby-transformer-source-split/gatsby-node.ts new file mode 100644 index 0000000..fe17b5f --- /dev/null +++ b/plugins/gatsby-transformer-source-split/gatsby-node.ts @@ -0,0 +1,119 @@ +import { GatsbyNode, NodeInput } from "gatsby"; +import { IMdxNode } from "gatsby-plugin-mdx/dist/types"; +import { FileSystemNode } from "gatsby-source-filesystem"; + +type SourceTypes = "blog" | "author"; + +interface BlogFrontmatter { + title: string; + slug: string; + date: Date; + tags: string[]; + hero_image_alt: string; + hero_image_link: string; + hero_image_credit_link: string; + hero_image_credit: string; + hero_image: FileSystemNode; +} + +interface MdxNodeWithSource extends IMdxNode { + fields: { source: SourceTypes; readingTime: unknown }; + frontmatter: T; +} + +export const createSchemaCustomization: GatsbyNode["createSchemaCustomization"] = + ({ actions, schema }) => { + const { createTypes } = actions; + + const typeDefs = ` + type HeroImageData { + alt: String! + link: String! + credit: String! + creditLink: String! + } + + type Blog implements Node { + title: String! + slug: String! + date: Date! + tags: [String!]! + heroImage: HeroImageData! + excerpt: String + } + `; + + createTypes(typeDefs); + }; + +export const shouldOnCreateNode: GatsbyNode< + MdxNodeWithSource +>["shouldOnCreateNode"] = ({ node }) => { + return node.internal.type === "Mdx" && !!node.parent; +}; + +export const onCreateNode: GatsbyNode["onCreateNode"] = async ({ + node, + actions, + createNodeId, + getNode, + cache, +}) => { + const createBlogNode = async ( + node: MdxNodeWithSource + ): Promise => { + return { + id: createNodeId(`${node.id} >>> blog`), + children: [], + parent: node.id, + internal: { + type: "Blog", + contentDigest: node.internal.contentDigest, + contentFilePath: node.internal.contentFilePath, + }, + title: node.frontmatter.title, + slug: node.frontmatter.slug, + date: node.frontmatter.date, + tags: node.frontmatter.tags, + // TODO: Excerpt seems to be generated lazily on query time, so we need to re-map a resolver later + excerpt: "", + readingTime: node.fields.readingTime, + heroImage: { + alt: node.frontmatter.hero_image_alt, + link: node.frontmatter.hero_image_link, + creditLink: node.frontmatter.hero_image_credit_link, + credit: node.frontmatter.hero_image_credit, + image: node.frontmatter.hero_image as FileSystemNode, + }, + }; + }; + + const nodeCreator: ( + sourceType: SourceTypes, + node: IMdxNode + ) => Promise = async (sourceType, node) => { + switch (sourceType) { + case "blog": + return createBlogNode(node as MdxNodeWithSource); + + default: + throw new Error(`Unknown source type ${sourceType}`); + } + }; + + if (!node.parent) { + throw new Error("Node has no parent"); + } + + const { createNode, createParentChildLink } = actions; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { sourceInstanceName: source } = getNode( + node.parent + )! as FileSystemNode; + + const newNode = await nodeCreator(source as unknown as SourceTypes, node); + + createNode(newNode); + createParentChildLink({ parent: node, child: newNode }); + await cache.set(`mdxToSplit-${node.internal.contentFilePath}`, newNode); +}; diff --git a/plugins/gatsby-transformer-source-split/package.json b/plugins/gatsby-transformer-source-split/package.json new file mode 100644 index 0000000..2e0651b --- /dev/null +++ b/plugins/gatsby-transformer-source-split/package.json @@ -0,0 +1,5 @@ +{ + "name": "gatsby-transformer-source-split", + "version": "1.0.0", + "private": true +} diff --git a/src/components/articlepreview.tsx b/src/components/articlepreview.tsx index 1a68989..e5d09b1 100644 --- a/src/components/articlepreview.tsx +++ b/src/components/articlepreview.tsx @@ -11,9 +11,9 @@ interface PreviewProps { } export const Preview = ({ node }: PreviewProps) => { - const image = getImage(node.frontmatter.hero_image as ImageDataLike); + const image = getImage(node.heroImage.image as ImageDataLike); - const to = `/${node.frontmatter.date}-${node.frontmatter.slug}`; + const to = `/${node.date}-${node.slug}`; const teaser = image ? (
@@ -21,7 +21,7 @@ export const Preview = ({ node }: PreviewProps) => {
@@ -29,7 +29,7 @@ export const Preview = ({ node }: PreviewProps) => {
); - const tags = node.frontmatter.tags?.map((tag) => + const tags = node.tags.map((tag) => tag ? (
  • @@ -38,8 +38,8 @@ export const Preview = ({ node }: PreviewProps) => { ); const text = - node.fields && node.fields.readingTime && node.fields.readingTime.text - ? node.fields.readingTime.text + node.readingTime && node.readingTime.text + ? node.readingTime.text : undefined; const readingTime = text ? `; ${text}` : undefined; @@ -48,10 +48,10 @@ export const Preview = ({ node }: PreviewProps) => { {teaser}

    - {node.frontmatter.title} + {node.title}

    - Posted: {node.frontmatter.date} + Posted: {node.date} {readingTime}

    {node.excerpt}

    @@ -64,24 +64,22 @@ export const Preview = ({ node }: PreviewProps) => { }; export const query = graphql` - fragment PreviewData on Mdx { + fragment PreviewData on Blog { + date excerpt - fields { - readingTime { - text - } - } - frontmatter { - date - title - slug - tags - hero_image_alt - hero_image { + slug + tags + title + heroImage { + alt + image { childImageSharp { gatsbyImageData(width: 200) } } } + readingTime { + text + } } `; diff --git a/src/gatsby-types.d.ts b/src/gatsby-types.d.ts index feaf66f..c027458 100644 --- a/src/gatsby-types.d.ts +++ b/src/gatsby-types.d.ts @@ -34,6 +34,185 @@ type AVIFOptions = { readonly speed: InputMaybe; }; +type Blog = Node & { + readonly children: ReadonlyArray; + readonly date: Scalars['Date']; + readonly excerpt: Maybe; + readonly gatsbyPath: Maybe; + readonly heroImage: HeroImageData; + readonly id: Scalars['ID']; + readonly internal: Internal; + readonly parent: Maybe; + readonly readingTime: Maybe; + readonly slug: Scalars['String']; + readonly tags: ReadonlyArray; + readonly title: Scalars['String']; +}; + + +type Blog_gatsbyPathArgs = { + filePath: InputMaybe; +}; + +type BlogConnection = { + readonly distinct: ReadonlyArray; + readonly edges: ReadonlyArray; + readonly group: ReadonlyArray; + readonly max: Maybe; + readonly min: Maybe; + readonly nodes: ReadonlyArray; + readonly pageInfo: PageInfo; + readonly sum: Maybe; + readonly totalCount: Scalars['Int']; +}; + + +type BlogConnection_distinctArgs = { + field: BlogFieldSelector; +}; + + +type BlogConnection_groupArgs = { + field: BlogFieldSelector; + limit: InputMaybe; + skip: InputMaybe; +}; + + +type BlogConnection_maxArgs = { + field: BlogFieldSelector; +}; + + +type BlogConnection_minArgs = { + field: BlogFieldSelector; +}; + + +type BlogConnection_sumArgs = { + field: BlogFieldSelector; +}; + +type BlogEdge = { + readonly next: Maybe; + readonly node: Blog; + readonly previous: Maybe; +}; + +type BlogFieldSelector = { + readonly children: InputMaybe; + readonly date: InputMaybe; + readonly excerpt: InputMaybe; + readonly gatsbyPath: InputMaybe; + readonly heroImage: InputMaybe; + readonly id: InputMaybe; + readonly internal: InputMaybe; + readonly parent: InputMaybe; + readonly readingTime: InputMaybe; + readonly slug: InputMaybe; + readonly tags: InputMaybe; + readonly title: InputMaybe; +}; + +type BlogFilterInput = { + readonly children: InputMaybe; + readonly date: InputMaybe; + readonly excerpt: InputMaybe; + readonly gatsbyPath: InputMaybe; + readonly heroImage: InputMaybe; + readonly id: InputMaybe; + readonly internal: InputMaybe; + readonly parent: InputMaybe; + readonly readingTime: InputMaybe; + readonly slug: InputMaybe; + readonly tags: InputMaybe; + readonly title: InputMaybe; +}; + +type BlogFilterListInput = { + readonly elemMatch: InputMaybe; +}; + +type BlogGroupConnection = { + readonly distinct: ReadonlyArray; + readonly edges: ReadonlyArray; + readonly field: Scalars['String']; + readonly fieldValue: Maybe; + readonly group: ReadonlyArray; + readonly max: Maybe; + readonly min: Maybe; + readonly nodes: ReadonlyArray; + readonly pageInfo: PageInfo; + readonly sum: Maybe; + readonly totalCount: Scalars['Int']; +}; + + +type BlogGroupConnection_distinctArgs = { + field: BlogFieldSelector; +}; + + +type BlogGroupConnection_groupArgs = { + field: BlogFieldSelector; + limit: InputMaybe; + skip: InputMaybe; +}; + + +type BlogGroupConnection_maxArgs = { + field: BlogFieldSelector; +}; + + +type BlogGroupConnection_minArgs = { + field: BlogFieldSelector; +}; + + +type BlogGroupConnection_sumArgs = { + field: BlogFieldSelector; +}; + +type BlogReadingTime = { + readonly minutes: Maybe; + readonly text: Maybe; + readonly words: Maybe; +}; + +type BlogReadingTimeFieldSelector = { + readonly minutes: InputMaybe; + readonly text: InputMaybe; + readonly words: InputMaybe; +}; + +type BlogReadingTimeFilterInput = { + readonly minutes: InputMaybe; + readonly text: InputMaybe; + readonly words: InputMaybe; +}; + +type BlogReadingTimeSortInput = { + readonly minutes: InputMaybe; + readonly text: InputMaybe; + readonly words: InputMaybe; +}; + +type BlogSortInput = { + readonly children: InputMaybe; + readonly date: InputMaybe; + readonly excerpt: InputMaybe; + readonly gatsbyPath: InputMaybe; + readonly heroImage: InputMaybe; + readonly id: InputMaybe; + readonly internal: InputMaybe; + readonly parent: InputMaybe; + readonly readingTime: InputMaybe; + readonly slug: InputMaybe; + readonly tags: InputMaybe; + readonly title: InputMaybe; +}; + type BlurredOptions = { /** Force the output format for the low-res preview. Default is to use the same format as the input. You should rarely need to change this */ readonly toFormat: InputMaybe; @@ -730,6 +909,38 @@ type GatsbyImagePlaceholder = | 'none' | 'tracedSVG'; +type HeroImageData = { + readonly alt: Scalars['String']; + readonly credit: Scalars['String']; + readonly creditLink: Scalars['String']; + readonly image: Maybe; + readonly link: Scalars['String']; +}; + +type HeroImageDataFieldSelector = { + readonly alt: InputMaybe; + readonly credit: InputMaybe; + readonly creditLink: InputMaybe; + readonly image: InputMaybe; + readonly link: InputMaybe; +}; + +type HeroImageDataFilterInput = { + readonly alt: InputMaybe; + readonly credit: InputMaybe; + readonly creditLink: InputMaybe; + readonly image: InputMaybe; + readonly link: InputMaybe; +}; + +type HeroImageDataSortInput = { + readonly alt: InputMaybe; + readonly credit: InputMaybe; + readonly creditLink: InputMaybe; + readonly image: InputMaybe; + readonly link: InputMaybe; +}; + type ImageCropFocus = | 17 | 0 @@ -1247,11 +1458,14 @@ type JSONQueryOperatorInput = { type Mdx = Node & { readonly body: Maybe; + /** Returns the first child node of type Blog or null if there are no children of given type on this node */ + readonly childBlog: Maybe; readonly children: ReadonlyArray; + /** Returns all children nodes filtered by type Blog */ + readonly childrenBlog: Maybe>>; readonly excerpt: Maybe; readonly fields: Maybe; readonly frontmatter: MdxFrontmatter; - readonly gatsbyPath: Maybe; readonly id: Scalars['ID']; readonly internal: Internal; readonly parent: Maybe; @@ -1264,11 +1478,6 @@ type Mdx_excerptArgs = { }; -type Mdx_gatsbyPathArgs = { - filePath: InputMaybe; -}; - - type Mdx_tableOfContentsArgs = { maxDepth: InputMaybe; }; @@ -1320,11 +1529,12 @@ type MdxEdge = { type MdxFieldSelector = { readonly body: InputMaybe; + readonly childBlog: InputMaybe; readonly children: InputMaybe; + readonly childrenBlog: InputMaybe; readonly excerpt: InputMaybe; readonly fields: InputMaybe; readonly frontmatter: InputMaybe; - readonly gatsbyPath: InputMaybe; readonly id: InputMaybe; readonly internal: InputMaybe; readonly parent: InputMaybe; @@ -1333,14 +1543,17 @@ type MdxFieldSelector = { type MdxFields = { readonly readingTime: Maybe; + readonly source: Maybe; }; type MdxFieldsFieldSelector = { readonly readingTime: InputMaybe; + readonly source: InputMaybe; }; type MdxFieldsFilterInput = { readonly readingTime: InputMaybe; + readonly source: InputMaybe; }; type MdxFieldsReadingTime = { @@ -1369,15 +1582,17 @@ type MdxFieldsReadingTimeSortInput = { type MdxFieldsSortInput = { readonly readingTime: InputMaybe; + readonly source: InputMaybe; }; type MdxFilterInput = { readonly body: InputMaybe; + readonly childBlog: InputMaybe; readonly children: InputMaybe; + readonly childrenBlog: InputMaybe; readonly excerpt: InputMaybe; readonly fields: InputMaybe; readonly frontmatter: InputMaybe; - readonly gatsbyPath: InputMaybe; readonly id: InputMaybe; readonly internal: InputMaybe; readonly parent: InputMaybe; @@ -1491,11 +1706,12 @@ type MdxGroupConnection_sumArgs = { type MdxSortInput = { readonly body: InputMaybe; + readonly childBlog: InputMaybe; readonly children: InputMaybe; + readonly childrenBlog: InputMaybe; readonly excerpt: InputMaybe; readonly fields: InputMaybe; readonly frontmatter: InputMaybe; - readonly gatsbyPath: InputMaybe; readonly id: InputMaybe; readonly internal: InputMaybe; readonly parent: InputMaybe; @@ -1571,6 +1787,7 @@ type PotraceTurnPolicy = | 'white'; type Query = { + readonly allBlog: BlogConnection; readonly allDirectory: DirectoryConnection; readonly allFile: FileConnection; readonly allImageSharp: ImageSharpConnection; @@ -1580,6 +1797,7 @@ type Query = { readonly allSiteFunction: SiteFunctionConnection; readonly allSitePage: SitePageConnection; readonly allSitePlugin: SitePluginConnection; + readonly blog: Maybe; readonly directory: Maybe; readonly file: Maybe; readonly imageSharp: Maybe; @@ -1592,6 +1810,14 @@ type Query = { }; +type Query_allBlogArgs = { + filter: InputMaybe; + limit: InputMaybe; + skip: InputMaybe; + sort: InputMaybe>>; +}; + + type Query_allDirectoryArgs = { filter: InputMaybe; limit: InputMaybe; @@ -1664,6 +1890,22 @@ type Query_allSitePluginArgs = { }; +type Query_blogArgs = { + children: InputMaybe; + date: InputMaybe; + excerpt: InputMaybe; + gatsbyPath: InputMaybe; + heroImage: InputMaybe; + id: InputMaybe; + internal: InputMaybe; + parent: InputMaybe; + readingTime: InputMaybe; + slug: InputMaybe; + tags: InputMaybe; + title: InputMaybe; +}; + + type Query_directoryArgs = { absolutePath: InputMaybe; accessTime: InputMaybe; @@ -1764,11 +2006,12 @@ type Query_imageSharpArgs = { type Query_mdxArgs = { body: InputMaybe; + childBlog: InputMaybe; children: InputMaybe; + childrenBlog: InputMaybe; excerpt: InputMaybe; fields: InputMaybe; frontmatter: InputMaybe; - gatsbyPath: InputMaybe; id: InputMaybe; internal: InputMaybe; parent: InputMaybe; @@ -1780,14 +2023,12 @@ type Query_siteArgs = { buildTime: InputMaybe; children: InputMaybe; graphqlTypegen: InputMaybe; - host: InputMaybe; id: InputMaybe; internal: InputMaybe; jsxRuntime: InputMaybe; parent: InputMaybe; pathPrefix: InputMaybe; polyfill: InputMaybe; - port: InputMaybe; siteMetadata: InputMaybe; trailingSlash: InputMaybe; }; @@ -1852,14 +2093,12 @@ type Site = Node & { readonly buildTime: Maybe; readonly children: ReadonlyArray; readonly graphqlTypegen: Maybe; - readonly host: Maybe; readonly id: Scalars['ID']; readonly internal: Internal; readonly jsxRuntime: Maybe; readonly parent: Maybe; readonly pathPrefix: Maybe; readonly polyfill: Maybe; - readonly port: Maybe; readonly siteMetadata: SiteSiteMetadata; readonly trailingSlash: Maybe; }; @@ -2047,14 +2286,12 @@ type SiteFieldSelector = { readonly buildTime: InputMaybe; readonly children: InputMaybe; readonly graphqlTypegen: InputMaybe; - readonly host: InputMaybe; readonly id: InputMaybe; readonly internal: InputMaybe; readonly jsxRuntime: InputMaybe; readonly parent: InputMaybe; readonly pathPrefix: InputMaybe; readonly polyfill: InputMaybe; - readonly port: InputMaybe; readonly siteMetadata: InputMaybe; readonly trailingSlash: InputMaybe; }; @@ -2063,14 +2300,12 @@ type SiteFilterInput = { readonly buildTime: InputMaybe; readonly children: InputMaybe; readonly graphqlTypegen: InputMaybe; - readonly host: InputMaybe; readonly id: InputMaybe; readonly internal: InputMaybe; readonly jsxRuntime: InputMaybe; readonly parent: InputMaybe; readonly pathPrefix: InputMaybe; readonly polyfill: InputMaybe; - readonly port: InputMaybe; readonly siteMetadata: InputMaybe; readonly trailingSlash: InputMaybe; }; @@ -2602,14 +2837,12 @@ type SiteSortInput = { readonly buildTime: InputMaybe; readonly children: InputMaybe; readonly graphqlTypegen: InputMaybe; - readonly host: InputMaybe; readonly id: InputMaybe; readonly internal: InputMaybe; readonly jsxRuntime: InputMaybe; readonly parent: InputMaybe; readonly pathPrefix: InputMaybe; readonly polyfill: InputMaybe; - readonly port: InputMaybe; readonly siteMetadata: InputMaybe; readonly trailingSlash: InputMaybe; }; @@ -2645,12 +2878,12 @@ type BlogPostByIdQueryVariables = Exact<{ }>; -type BlogPostByIdQuery = { readonly mdx: { readonly frontmatter: { readonly title: string, readonly date: string, readonly tags: ReadonlyArray, readonly hero_image_alt: string, readonly hero_image_credit_link: string | null, readonly hero_image_credit: string | null, readonly hero_image_link: string, readonly hero_image: { readonly childImageSharp: { readonly gatsbyImageData: import('gatsby-plugin-image').IGatsbyImageData } | null } | null } } | null }; +type BlogPostByIdQuery = { readonly blog: { readonly tags: ReadonlyArray, readonly date: string, readonly title: string, readonly heroImage: { readonly link: string, readonly credit: string, readonly alt: string, readonly image: { readonly childImageSharp: { readonly gatsbyImageData: import('gatsby-plugin-image').IGatsbyImageData } | null } | null } } | null }; type BlogPostsQueryVariables = Exact<{ [key: string]: never; }>; -type BlogPostsQuery = { readonly allMdx: { readonly nodes: ReadonlyArray<{ readonly id: string, readonly excerpt: string | null, readonly fields: { readonly readingTime: { readonly text: string | null } | null } | null, readonly frontmatter: { readonly date: string, readonly title: string, readonly slug: string, readonly tags: ReadonlyArray, readonly hero_image_alt: string, readonly hero_image: { readonly childImageSharp: { readonly gatsbyImageData: import('gatsby-plugin-image').IGatsbyImageData } | null } | null } }> } }; +type BlogPostsQuery = { readonly allBlog: { readonly nodes: ReadonlyArray<{ readonly id: string, readonly date: string, readonly excerpt: string | null, readonly slug: string, readonly tags: ReadonlyArray, readonly title: string, readonly heroImage: { readonly alt: string, readonly image: { readonly childImageSharp: { readonly gatsbyImageData: import('gatsby-plugin-image').IGatsbyImageData } | null } | null }, readonly readingTime: { readonly text: string | null } | null }> } }; type FetchTitleQueryVariables = Exact<{ [key: string]: never; }>; @@ -2683,7 +2916,7 @@ type GatsbyImageSharpFluid_withWebp_tracedSVGFragment = { readonly tracedSVG: st type GatsbyImageSharpFluidLimitPresentationSizeFragment = { readonly maxHeight: number, readonly maxWidth: number }; -type PreviewDataFragment = { readonly excerpt: string | null, readonly fields: { readonly readingTime: { readonly text: string | null } | null } | null, readonly frontmatter: { readonly date: string, readonly title: string, readonly slug: string, readonly tags: ReadonlyArray, readonly hero_image_alt: string, readonly hero_image: { readonly childImageSharp: { readonly gatsbyImageData: import('gatsby-plugin-image').IGatsbyImageData } | null } | null } }; +type PreviewDataFragment = { readonly date: string, readonly excerpt: string | null, readonly slug: string, readonly tags: ReadonlyArray, readonly title: string, readonly heroImage: { readonly alt: string, readonly image: { readonly childImageSharp: { readonly gatsbyImageData: import('gatsby-plugin-image').IGatsbyImageData } | null } | null }, readonly readingTime: { readonly text: string | null } | null }; type SiteTitleQueryVariables = Exact<{ [key: string]: never; }>; @@ -2700,7 +2933,7 @@ type TagInfoQueryVariables = Exact<{ }>; -type TagInfoQuery = { readonly allMdx: { readonly totalCount: number, readonly edges: ReadonlyArray<{ readonly node: { readonly id: string, readonly excerpt: string | null, readonly fields: { readonly readingTime: { readonly text: string | null } | null } | null, readonly frontmatter: { readonly date: string, readonly title: string, readonly slug: string, readonly tags: ReadonlyArray, readonly hero_image_alt: string, readonly hero_image: { readonly childImageSharp: { readonly gatsbyImageData: import('gatsby-plugin-image').IGatsbyImageData } | null } | null } } }> } }; +type TagInfoQuery = { readonly allBlog: { readonly totalCount: number, readonly edges: ReadonlyArray<{ readonly node: { readonly id: string, readonly date: string, readonly excerpt: string | null, readonly slug: string, readonly tags: ReadonlyArray, readonly title: string, readonly heroImage: { readonly alt: string, readonly image: { readonly childImageSharp: { readonly gatsbyImageData: import('gatsby-plugin-image').IGatsbyImageData } | null } | null }, readonly readingTime: { readonly text: string | null } | null } }> } }; type TagListQueryVariables = Exact<{ [key: string]: never; }>; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 8e22c41..3f45581 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -6,12 +6,12 @@ import { ArticlePreview, Layout, Seo } from "~components"; type BlogPageProps = PageProps; -type BlogPostNode = Queries.BlogPostsQuery["allMdx"]["nodes"][0]; +type BlogPostNode = Queries.BlogPostsQuery["allBlog"]["nodes"][0]; const BlogPage = ({ data }: BlogPageProps) => { return ( - {data.allMdx.nodes.map((node: BlogPostNode) => ( + {data.allBlog.nodes.map((node: BlogPostNode) => ( ))} @@ -20,7 +20,7 @@ const BlogPage = ({ data }: BlogPageProps) => { export const query = graphql` query BlogPosts { - allMdx(sort: { frontmatter: { date: DESC } }) { + allBlog(sort: { date: DESC }) { nodes { id ...PreviewData diff --git a/src/pages/{mdx.frontmatter__date}-{mdx.frontmatter__slug}/index.tsx b/src/pages/{blog.date}-{blog.slug}/index.tsx similarity index 79% rename from src/pages/{mdx.frontmatter__date}-{mdx.frontmatter__slug}/index.tsx rename to src/pages/{blog.date}-{blog.slug}/index.tsx index 0615cee..aece431 100644 --- a/src/pages/{mdx.frontmatter__date}-{mdx.frontmatter__slug}/index.tsx +++ b/src/pages/{blog.date}-{blog.slug}/index.tsx @@ -11,11 +11,11 @@ type BlogPostProps = React.PropsWithChildren< >; const BlogPost = ({ data, children }: BlogPostProps) => { - if (!data.mdx) { + if (!data.blog) { throw new Error("No MDX data"); } - const image = getImage(data.mdx.frontmatter.hero_image as ImageDataLike); + const image = getImage(data.blog.heroImage.image as ImageDataLike); // TODO: Make this a component const hero = image ? ( @@ -23,7 +23,7 @@ const BlogPost = ({ data, children }: BlogPostProps) => {

    { d="M19 3h-1V1h-2v2H8V1H6v2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h5v-2H5V8h14v1h2V5a2 2 0 0 0-2-2m2.7 10.35l-1 1l-2.05-2l1-1c.2-.21.54-.22.77 0l1.28 1.28c.19.2.19.52 0 .72M12 18.94l6.07-6.06l2.05 2L14.06 21H12v-2.06Z" /> - {data.mdx.frontmatter.date} + {data.blog.date}

    - + { d="M9 2L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5s5 2.24 5 5s-2.24 5-5 5z" /> - {data.mdx.frontmatter.hero_image_credit} + {data.blog.heroImage.credit}

    ) : null; - const tags = data.mdx.frontmatter.tags.map((tag) => ( + const tags = data.blog.tags.map((tag: string) => ( )); return ( - + {hero}
    {tags} @@ -82,16 +82,15 @@ const BlogPost = ({ data, children }: BlogPostProps) => { export const query = graphql` query BlogPostById($id: String) { - mdx(id: { eq: $id }) { - frontmatter { - title - date - tags - hero_image_alt - hero_image_credit_link - hero_image_credit - hero_image_link - hero_image { + blog(id: { eq: $id }) { + tags + date + title + heroImage { + link + credit + alt + image { childImageSharp { gatsbyImageData(width: 800) } @@ -102,11 +101,11 @@ export const query = graphql` `; export const Head = ({ data }: BlogPostProps) => { - if (!data.mdx) { - throw new Error("No MDX data"); + if (!data.blog) { + throw new Error("No Blog data"); } - return ; + return ; }; export default BlogPost; diff --git a/src/templates/tags.tsx b/src/templates/tags.tsx index 0d7b178..bdca889 100644 --- a/src/templates/tags.tsx +++ b/src/templates/tags.tsx @@ -54,7 +54,7 @@ export const Tag: TagComponent = ({ name, ...props }) => { export const Tags = ({ pageContext, data }: TagsProps) => { const { tag } = pageContext; - const { edges, totalCount } = data.allMdx; + const { edges, totalCount } = data.allBlog; const isSingular = totalCount === 1; const isOrAre = isSingular ? "is" : "are"; @@ -81,11 +81,7 @@ export const Tags = ({ pageContext, data }: TagsProps) => { export const pageQuery = graphql` query TagInfo($tag: String) { - allMdx( - limit: 2000 - sort: { frontmatter: { date: DESC } } - filter: { frontmatter: { tags: { in: [$tag] } } } - ) { + allBlog(filter: { tags: { in: [$tag] } }, sort: { date: DESC }) { totalCount edges { node { diff --git a/tests/components/articlepreview.test.tsx b/tests/components/articlepreview.test.tsx index 3d46df9..492fe58 100644 --- a/tests/components/articlepreview.test.tsx +++ b/tests/components/articlepreview.test.tsx @@ -5,14 +5,12 @@ import { ArticlePreview as Preview } from "../../src/components"; describe("Preview component", () => { const node = { - frontmatter: { - title: "Test Post", - date: "2020-01-01", - slug: "test-post", - tags: ["test-tag"], - hero_image_alt: "Test image", - }, + date: "2020-01-01", excerpt: "This is a test excerpt", + heroImage: { alt: "Test image" }, + slug: "test-post", + tags: ["test-tag"], + title: "Test Post", }; it("renders the post title", () => { diff --git a/tsconfig.json b/tsconfig.json index 6b5a72e..288c83c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,8 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, - "skipLibCheck": true + "skipLibCheck": true, + "sourceMap": true }, "include": [ "./src/**/*", diff --git a/yarn.lock b/yarn.lock index 58f5969..9416765 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6397,6 +6397,11 @@ gatsby-plugin-image@^3.10.0: objectFitPolyfill "^2.3.5" prop-types "^15.8.1" +gatsby-plugin-mdx-source-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gatsby-plugin-mdx-source-name/-/gatsby-plugin-mdx-source-name-1.0.1.tgz#729615aac57a55bb61e5c5df46067f64ea185cb7" + integrity sha512-D8DAOI84ZoWmx3c6BFYuKEnQc+Y1IU+VMhTJZbIOcKbQaoROKx66TkvjLbdHSlrpQ6Skozmi+b6NNrwIyZunzw== + gatsby-plugin-mdx@^5.10.0: version "5.11.0" resolved "https://registry.yarnpkg.com/gatsby-plugin-mdx/-/gatsby-plugin-mdx-5.11.0.tgz#4cff5c28ab8facd1f6a89f2a79e754f6c81670b3" From 140af70d900141efde79c93136b7f47f9ee48a70 Mon Sep 17 00:00:00 2001 From: Norbert Melzer Date: Sun, 2 Jul 2023 10:59:04 +0200 Subject: [PATCH 02/11] feature: add author pages --- author/nobbz.mdx | 70 +++++ gatsby-config.ts | 7 + .../gatsby-node.ts | 39 ++- src/gatsby-types.d.ts | 254 ++++++++++++++++++ src/pages/author/{author.slug}/index.tsx | 106 ++++++++ 5 files changed, 475 insertions(+), 1 deletion(-) create mode 100644 author/nobbz.mdx create mode 100644 src/pages/author/{author.slug}/index.tsx diff --git a/author/nobbz.mdx b/author/nobbz.mdx new file mode 100644 index 0000000..935ec65 --- /dev/null +++ b/author/nobbz.mdx @@ -0,0 +1,70 @@ +--- +slug: "nobbz" +first_name: "Norbert" +last_name: "Melzer" +nick_name: "NobbZ" +social: + github: "NobbZ" + gitlab: "NobbZ" + twitter: "NobbZ1981" + web: + url: "https://blog.nobbz.dev/" + name: "NobbZ Blog" +--- + +Norbert is a software developer from Germany, whos primary language is [Elixir]. + +[Elixir]: https://elixir-lang.org/ + +His strong interest in Elixir began during his studies somewhen in 2014, when +he wanted to use a "functional alternative to [Ruby]" in a programming contest at +the university. + +[Ruby]: https://www.ruby-lang.org/ + +When he wanted to test his contestant on the designated machine, he found out +that the erlang version installed did not support any released version of Elixir, +so he had to rewrite his contestant in [Erlang]. + +[Erlang]: https://www.erlang.org/ + +For a long time he used both languages in parallel, and became an Erlang track +maintainer at [Exercism] during 2015, where he still is. Within the same year +he also became a member and moderator of the [Elixir Forum], where he is still +active as well. + +[Exercism]: https://exercism.org/ +[Elixir Forum]: https://elixirforum.com/ + +In 2016 he started to work professionally to be able to pay for his university. +During that job he used mostly [Go] and [Python], though was able to use any +language necessary to get the job done. + +[Go]: https://golang.org/ +[Python]: https://www.python.org/ + +This did include [PHP], [Java], [C++], and [JavaScript] as well. + +[PHP]: https://www.php.net/ +[Java]: https://www.java.com/ +[C++]: https://isocpp.org/ +[JavaScript]: https://www.javascript.com/ + +In 2022 he finally was able to find a job where he was able to use Elixir +professionally, and is now working as a backend developer. + +Besides of doing software development, he is also a passionate user of [NixOS]. +He currently maintains a few packages in the official repository, and is +also using it on his 2 private machines. He also plays with [`nix`] on a virtual +machine on his work laptop (Apple). + +[NixOS]: https://nixos.org/ +[`nix`]: https://nixos.org/manual/nix/stable/#ch-about-nix + +For all the `nix` related things he is also active on the [NixOS Discourse] and +the inofficial [NixOS Discord]. + +[NixOS Discourse]: https://discourse.nixos.org/ +[NixOS Discord]: https://discord.gg/fFQp8hqY + +Besides all his technical hobbies, he also is a father of 2 children. diff --git a/gatsby-config.ts b/gatsby-config.ts index 255072f..73eca5f 100644 --- a/gatsby-config.ts +++ b/gatsby-config.ts @@ -28,6 +28,13 @@ const config: GatsbyConfig = { path: `${__dirname}/blog`, }, }, + { + resolve: "gatsby-source-filesystem", + options: { + name: "author", + path: `${__dirname}/author`, + }, + }, ], }; diff --git a/plugins/gatsby-transformer-source-split/gatsby-node.ts b/plugins/gatsby-transformer-source-split/gatsby-node.ts index fe17b5f..91dd686 100644 --- a/plugins/gatsby-transformer-source-split/gatsby-node.ts +++ b/plugins/gatsby-transformer-source-split/gatsby-node.ts @@ -16,6 +16,14 @@ interface BlogFrontmatter { hero_image: FileSystemNode; } +interface AuthorFrontmatter { + slug: string; + first_name?: string; + last_name?: string; + nick_name?: string; + social?: Record>; +} + interface MdxNodeWithSource extends IMdxNode { fields: { source: SourceTypes; readingTime: unknown }; frontmatter: T; @@ -41,7 +49,15 @@ export const createSchemaCustomization: GatsbyNode["createSchemaCustomization"] heroImage: HeroImageData! excerpt: String } - `; + + type Author implements Node { + slug: String! + firstName: String! + lastName: String! + nickName: String + social: JSON + } + `; createTypes(typeDefs); }; @@ -88,6 +104,24 @@ export const onCreateNode: GatsbyNode["onCreateNode"] = async ({ }; }; + const createAuthorNode = async ( + node: MdxNodeWithSource + ): Promise => { + return { + id: createNodeId(`${node.id} >>> author`), + internal: { + type: "Author", + contentDigest: node.internal.contentDigest, + contentFilePath: node.internal.contentFilePath, + }, + slug: node.frontmatter.slug, + firstName: node.frontmatter.first_name, + lastName: node.frontmatter.last_name, + nickName: node.frontmatter.nick_name, + social: node.frontmatter.social, + }; + }; + const nodeCreator: ( sourceType: SourceTypes, node: IMdxNode @@ -96,6 +130,9 @@ export const onCreateNode: GatsbyNode["onCreateNode"] = async ({ case "blog": return createBlogNode(node as MdxNodeWithSource); + case "author": + return createAuthorNode(node as MdxNodeWithSource); + default: throw new Error(`Unknown source type ${sourceType}`); } diff --git a/src/gatsby-types.d.ts b/src/gatsby-types.d.ts index c027458..e1105ad 100644 --- a/src/gatsby-types.d.ts +++ b/src/gatsby-types.d.ts @@ -34,6 +34,153 @@ type AVIFOptions = { readonly speed: InputMaybe; }; +type Author = Node & { + readonly children: ReadonlyArray; + readonly firstName: Scalars['String']; + readonly gatsbyPath: Maybe; + readonly id: Scalars['ID']; + readonly internal: Internal; + readonly lastName: Scalars['String']; + readonly nickName: Maybe; + readonly parent: Maybe; + readonly slug: Scalars['String']; + readonly social: Maybe; +}; + + +type Author_gatsbyPathArgs = { + filePath: InputMaybe; +}; + +type AuthorConnection = { + readonly distinct: ReadonlyArray; + readonly edges: ReadonlyArray; + readonly group: ReadonlyArray; + readonly max: Maybe; + readonly min: Maybe; + readonly nodes: ReadonlyArray; + readonly pageInfo: PageInfo; + readonly sum: Maybe; + readonly totalCount: Scalars['Int']; +}; + + +type AuthorConnection_distinctArgs = { + field: AuthorFieldSelector; +}; + + +type AuthorConnection_groupArgs = { + field: AuthorFieldSelector; + limit: InputMaybe; + skip: InputMaybe; +}; + + +type AuthorConnection_maxArgs = { + field: AuthorFieldSelector; +}; + + +type AuthorConnection_minArgs = { + field: AuthorFieldSelector; +}; + + +type AuthorConnection_sumArgs = { + field: AuthorFieldSelector; +}; + +type AuthorEdge = { + readonly next: Maybe; + readonly node: Author; + readonly previous: Maybe; +}; + +type AuthorFieldSelector = { + readonly children: InputMaybe; + readonly firstName: InputMaybe; + readonly gatsbyPath: InputMaybe; + readonly id: InputMaybe; + readonly internal: InputMaybe; + readonly lastName: InputMaybe; + readonly nickName: InputMaybe; + readonly parent: InputMaybe; + readonly slug: InputMaybe; + readonly social: InputMaybe; +}; + +type AuthorFilterInput = { + readonly children: InputMaybe; + readonly firstName: InputMaybe; + readonly gatsbyPath: InputMaybe; + readonly id: InputMaybe; + readonly internal: InputMaybe; + readonly lastName: InputMaybe; + readonly nickName: InputMaybe; + readonly parent: InputMaybe; + readonly slug: InputMaybe; + readonly social: InputMaybe; +}; + +type AuthorFilterListInput = { + readonly elemMatch: InputMaybe; +}; + +type AuthorGroupConnection = { + readonly distinct: ReadonlyArray; + readonly edges: ReadonlyArray; + readonly field: Scalars['String']; + readonly fieldValue: Maybe; + readonly group: ReadonlyArray; + readonly max: Maybe; + readonly min: Maybe; + readonly nodes: ReadonlyArray; + readonly pageInfo: PageInfo; + readonly sum: Maybe; + readonly totalCount: Scalars['Int']; +}; + + +type AuthorGroupConnection_distinctArgs = { + field: AuthorFieldSelector; +}; + + +type AuthorGroupConnection_groupArgs = { + field: AuthorFieldSelector; + limit: InputMaybe; + skip: InputMaybe; +}; + + +type AuthorGroupConnection_maxArgs = { + field: AuthorFieldSelector; +}; + + +type AuthorGroupConnection_minArgs = { + field: AuthorFieldSelector; +}; + + +type AuthorGroupConnection_sumArgs = { + field: AuthorFieldSelector; +}; + +type AuthorSortInput = { + readonly children: InputMaybe; + readonly firstName: InputMaybe; + readonly gatsbyPath: InputMaybe; + readonly id: InputMaybe; + readonly internal: InputMaybe; + readonly lastName: InputMaybe; + readonly nickName: InputMaybe; + readonly parent: InputMaybe; + readonly slug: InputMaybe; + readonly social: InputMaybe; +}; + type Blog = Node & { readonly children: ReadonlyArray; readonly date: Scalars['Date']; @@ -1458,9 +1605,13 @@ type JSONQueryOperatorInput = { type Mdx = Node & { readonly body: Maybe; + /** Returns the first child node of type Author or null if there are no children of given type on this node */ + readonly childAuthor: Maybe; /** Returns the first child node of type Blog or null if there are no children of given type on this node */ readonly childBlog: Maybe; readonly children: ReadonlyArray; + /** Returns all children nodes filtered by type Author */ + readonly childrenAuthor: Maybe>>; /** Returns all children nodes filtered by type Blog */ readonly childrenBlog: Maybe>>; readonly excerpt: Maybe; @@ -1529,8 +1680,10 @@ type MdxEdge = { type MdxFieldSelector = { readonly body: InputMaybe; + readonly childAuthor: InputMaybe; readonly childBlog: InputMaybe; readonly children: InputMaybe; + readonly childrenAuthor: InputMaybe; readonly childrenBlog: InputMaybe; readonly excerpt: InputMaybe; readonly fields: InputMaybe; @@ -1587,8 +1740,10 @@ type MdxFieldsSortInput = { type MdxFilterInput = { readonly body: InputMaybe; + readonly childAuthor: InputMaybe; readonly childBlog: InputMaybe; readonly children: InputMaybe; + readonly childrenAuthor: InputMaybe; readonly childrenBlog: InputMaybe; readonly excerpt: InputMaybe; readonly fields: InputMaybe; @@ -1606,12 +1761,16 @@ type MdxFilterListInput = { type MdxFrontmatter = { readonly date: Scalars['Date']; readonly description: Maybe; + readonly first_name: Maybe; readonly hero_image: Maybe; readonly hero_image_alt: Scalars['String']; readonly hero_image_credit: Maybe; readonly hero_image_credit_link: Maybe; readonly hero_image_link: Scalars['String']; + readonly last_name: Maybe; + readonly nick_name: Maybe; readonly slug: Scalars['String']; + readonly social: Maybe; readonly tags: ReadonlyArray; readonly title: Scalars['String']; }; @@ -1627,12 +1786,16 @@ type MdxFrontmatter_dateArgs = { type MdxFrontmatterFieldSelector = { readonly date: InputMaybe; readonly description: InputMaybe; + readonly first_name: InputMaybe; readonly hero_image: InputMaybe; readonly hero_image_alt: InputMaybe; readonly hero_image_credit: InputMaybe; readonly hero_image_credit_link: InputMaybe; readonly hero_image_link: InputMaybe; + readonly last_name: InputMaybe; + readonly nick_name: InputMaybe; readonly slug: InputMaybe; + readonly social: InputMaybe; readonly tags: InputMaybe; readonly title: InputMaybe; }; @@ -1640,25 +1803,81 @@ type MdxFrontmatterFieldSelector = { type MdxFrontmatterFilterInput = { readonly date: InputMaybe; readonly description: InputMaybe; + readonly first_name: InputMaybe; readonly hero_image: InputMaybe; readonly hero_image_alt: InputMaybe; readonly hero_image_credit: InputMaybe; readonly hero_image_credit_link: InputMaybe; readonly hero_image_link: InputMaybe; + readonly last_name: InputMaybe; + readonly nick_name: InputMaybe; readonly slug: InputMaybe; + readonly social: InputMaybe; readonly tags: InputMaybe; readonly title: InputMaybe; }; +type MdxFrontmatterSocial = { + readonly github: Maybe; + readonly gitlab: Maybe; + readonly twitter: Maybe; + readonly web: Maybe; +}; + +type MdxFrontmatterSocialFieldSelector = { + readonly github: InputMaybe; + readonly gitlab: InputMaybe; + readonly twitter: InputMaybe; + readonly web: InputMaybe; +}; + +type MdxFrontmatterSocialFilterInput = { + readonly github: InputMaybe; + readonly gitlab: InputMaybe; + readonly twitter: InputMaybe; + readonly web: InputMaybe; +}; + +type MdxFrontmatterSocialSortInput = { + readonly github: InputMaybe; + readonly gitlab: InputMaybe; + readonly twitter: InputMaybe; + readonly web: InputMaybe; +}; + +type MdxFrontmatterSocialWeb = { + readonly name: Maybe; + readonly url: Maybe; +}; + +type MdxFrontmatterSocialWebFieldSelector = { + readonly name: InputMaybe; + readonly url: InputMaybe; +}; + +type MdxFrontmatterSocialWebFilterInput = { + readonly name: InputMaybe; + readonly url: InputMaybe; +}; + +type MdxFrontmatterSocialWebSortInput = { + readonly name: InputMaybe; + readonly url: InputMaybe; +}; + type MdxFrontmatterSortInput = { readonly date: InputMaybe; readonly description: InputMaybe; + readonly first_name: InputMaybe; readonly hero_image: InputMaybe; readonly hero_image_alt: InputMaybe; readonly hero_image_credit: InputMaybe; readonly hero_image_credit_link: InputMaybe; readonly hero_image_link: InputMaybe; + readonly last_name: InputMaybe; + readonly nick_name: InputMaybe; readonly slug: InputMaybe; + readonly social: InputMaybe; readonly tags: InputMaybe; readonly title: InputMaybe; }; @@ -1706,8 +1925,10 @@ type MdxGroupConnection_sumArgs = { type MdxSortInput = { readonly body: InputMaybe; + readonly childAuthor: InputMaybe; readonly childBlog: InputMaybe; readonly children: InputMaybe; + readonly childrenAuthor: InputMaybe; readonly childrenBlog: InputMaybe; readonly excerpt: InputMaybe; readonly fields: InputMaybe; @@ -1787,6 +2008,7 @@ type PotraceTurnPolicy = | 'white'; type Query = { + readonly allAuthor: AuthorConnection; readonly allBlog: BlogConnection; readonly allDirectory: DirectoryConnection; readonly allFile: FileConnection; @@ -1797,6 +2019,7 @@ type Query = { readonly allSiteFunction: SiteFunctionConnection; readonly allSitePage: SitePageConnection; readonly allSitePlugin: SitePluginConnection; + readonly author: Maybe; readonly blog: Maybe; readonly directory: Maybe; readonly file: Maybe; @@ -1810,6 +2033,14 @@ type Query = { }; +type Query_allAuthorArgs = { + filter: InputMaybe; + limit: InputMaybe; + skip: InputMaybe; + sort: InputMaybe>>; +}; + + type Query_allBlogArgs = { filter: InputMaybe; limit: InputMaybe; @@ -1890,6 +2121,20 @@ type Query_allSitePluginArgs = { }; +type Query_authorArgs = { + children: InputMaybe; + firstName: InputMaybe; + gatsbyPath: InputMaybe; + id: InputMaybe; + internal: InputMaybe; + lastName: InputMaybe; + nickName: InputMaybe; + parent: InputMaybe; + slug: InputMaybe; + social: InputMaybe; +}; + + type Query_blogArgs = { children: InputMaybe; date: InputMaybe; @@ -2006,8 +2251,10 @@ type Query_imageSharpArgs = { type Query_mdxArgs = { body: InputMaybe; + childAuthor: InputMaybe; childBlog: InputMaybe; children: InputMaybe; + childrenAuthor: InputMaybe; childrenBlog: InputMaybe; excerpt: InputMaybe; fields: InputMaybe; @@ -2873,6 +3120,13 @@ type WebPOptions = { readonly quality: InputMaybe; }; +type AuthorInfoByIdQueryVariables = Exact<{ + id: InputMaybe; +}>; + + +type AuthorInfoByIdQuery = { readonly author: { readonly firstName: string, readonly lastName: string, readonly nickName: string | null, readonly social: Record | null } | null }; + type BlogPostByIdQueryVariables = Exact<{ id: InputMaybe; }>; diff --git a/src/pages/author/{author.slug}/index.tsx b/src/pages/author/{author.slug}/index.tsx new file mode 100644 index 0000000..d3fbfaa --- /dev/null +++ b/src/pages/author/{author.slug}/index.tsx @@ -0,0 +1,106 @@ +import * as React from "react"; + +import { PageProps, graphql } from "gatsby"; +import { Icon } from "@iconify-icon/react"; + +import { Layout, MDXWrapper } from "~components"; + +type AuthorPageProps = PageProps; + +// TODO: properly abstract this +const getSocialLink = ( + platform: string, + data: string | Record +) => { + switch (platform) { + case "twitter": + if (typeof data !== "string") { + throw new Error("Twitter handle is not a string"); + } + + return ( + + {data} + + ); + case "github": + if (typeof data !== "string") { + throw new Error("GitHub handle is not a string"); + } + + return ( + + {data} + + ); + case "gitlab": + if (typeof data !== "string") { + throw new Error("GitLab handle is not a string"); + } + + return ( + + {data} + + ); + case "web": + if (typeof data === "string") { + throw new Error("Web data is not an object"); + } + + return ( + + {data.name} + + ); + } + + throw new Error(`Unknown social platform: ${platform}`); +}; + +const AuthorPage = ({ data, children }: AuthorPageProps) => { + if (!data.author) { + throw new Error("No author data"); + } + + const { firstName, lastName, nickName, social } = data.author; + + const title = nickName + ? `${firstName} "${nickName}" ${lastName}` + : `${firstName} ${lastName}`; + + const socialLinks = + social !== null + ? Object.keys(social) + .sort() + .map((key) => + getSocialLink(key, social[key] as string | Record) + ) + : undefined; + + const socialBox = socialLinks ? ( +
    + {socialLinks} +
    + ) : undefined; + + return ( + + {socialBox} + {children} + + ); +}; + +export const query = graphql` + query AuthorInfoById($id: String) { + author(id: { eq: $id }) { + firstName + lastName + nickName + social + } + } +`; + +export default AuthorPage; From 8291b5fd49eab12c225eb72dfb81dddb7235b84e Mon Sep 17 00:00:00 2001 From: Norbert Melzer Date: Sun, 2 Jul 2023 11:03:24 +0200 Subject: [PATCH 03/11] chore: remove a debug output that has been left in --- gatsby-config.ts | 3 +- .../gatsby-plugin-readtime-nz/gatsby-node.ts | 2 - .../gatsby-node.ts | 66 ++++++++ .../gatsby-transformer-nz-author/package.json | 5 + .../gatsby-transformer-nz-blog/gatsby-node.ts | 90 ++++++++++ .../gatsby-transformer-nz-blog/package.json | 5 + .../gatsby-node.ts | 156 ------------------ .../package.json | 5 - 8 files changed, 168 insertions(+), 164 deletions(-) create mode 100644 plugins/gatsby-transformer-nz-author/gatsby-node.ts create mode 100644 plugins/gatsby-transformer-nz-author/package.json create mode 100644 plugins/gatsby-transformer-nz-blog/gatsby-node.ts create mode 100644 plugins/gatsby-transformer-nz-blog/package.json delete mode 100644 plugins/gatsby-transformer-source-split/gatsby-node.ts delete mode 100644 plugins/gatsby-transformer-source-split/package.json diff --git a/gatsby-config.ts b/gatsby-config.ts index 73eca5f..375c94b 100644 --- a/gatsby-config.ts +++ b/gatsby-config.ts @@ -20,7 +20,8 @@ const config: GatsbyConfig = { "gatsby-plugin-postcss", "gatsby-plugin-readtime-nz", "gatsby-plugin-mdx-source-name", - "gatsby-transformer-source-split", + "gatsby-transformer-nz-author", + "gatsby-transformer-nz-blog", { resolve: "gatsby-source-filesystem", options: { diff --git a/plugins/gatsby-plugin-readtime-nz/gatsby-node.ts b/plugins/gatsby-plugin-readtime-nz/gatsby-node.ts index 1eaa302..fede9c6 100644 --- a/plugins/gatsby-plugin-readtime-nz/gatsby-node.ts +++ b/plugins/gatsby-plugin-readtime-nz/gatsby-node.ts @@ -24,8 +24,6 @@ export const onCreateNode: GatsbyNode["onCreateNode"] = ( const { wordsPerMinute = 200, language = "en" } = options; - console.log("node", node); - createNodeField({ node, name: "readingTime", diff --git a/plugins/gatsby-transformer-nz-author/gatsby-node.ts b/plugins/gatsby-transformer-nz-author/gatsby-node.ts new file mode 100644 index 0000000..ddd5c69 --- /dev/null +++ b/plugins/gatsby-transformer-nz-author/gatsby-node.ts @@ -0,0 +1,66 @@ +import { GatsbyNode, NodeInput } from "gatsby"; + +import { IMdxNode } from "gatsby-plugin-mdx/dist/types"; + +interface AuthorParentNode extends IMdxNode { + parent: string; + fields: { readingTime: unknown }; + frontmatter: { + slug: string; + first_name?: string; + last_name?: string; + nick_name?: string; + social?: Record>; + }; +} + +export const createSchemaCustomization: GatsbyNode["createSchemaCustomization"] = + ({ actions }) => { + const { createTypes } = actions; + + createTypes(` + type Author implements Node { + slug: String! + firstName: String! + lastName: String! + nickName: String + social: JSON + } + `); + }; + +export const shouldOnCreateNode: GatsbyNode["shouldOnCreateNode"] = ({ + node, +}) => { + return ( + node.internal.type === "Mdx" && + !!node.parent && + // node.parent.sourceInstanceName === "author" + (node.fields as undefined | { source: string })?.source === "author" + ); +}; + +export const onCreateNode: GatsbyNode["onCreateNode"] = ({ + node, + actions, + createNodeId, +}) => { + const { createNode, createParentChildLink } = actions; + + const authorNode: NodeInput = { + id: createNodeId(`${node.id} >>> author`), + internal: { + type: "Author", + contentDigest: node.internal.contentDigest, + contentFilePath: node.internal.contentFilePath, + }, + slug: node.frontmatter.slug, + firstName: node.frontmatter.first_name, + lastName: node.frontmatter.last_name, + nickName: node.frontmatter.nick_name, + social: node.frontmatter.social, + }; + + createNode(authorNode); + createParentChildLink({ parent: node, child: authorNode }); +}; diff --git a/plugins/gatsby-transformer-nz-author/package.json b/plugins/gatsby-transformer-nz-author/package.json new file mode 100644 index 0000000..19eb782 --- /dev/null +++ b/plugins/gatsby-transformer-nz-author/package.json @@ -0,0 +1,5 @@ +{ + "name": "gatsby-transformer-nz-author", + "version": "1.0.0", + "private": true +} diff --git a/plugins/gatsby-transformer-nz-blog/gatsby-node.ts b/plugins/gatsby-transformer-nz-blog/gatsby-node.ts new file mode 100644 index 0000000..40a6c38 --- /dev/null +++ b/plugins/gatsby-transformer-nz-blog/gatsby-node.ts @@ -0,0 +1,90 @@ +import { GatsbyNode, NodeInput } from "gatsby"; + +import { IMdxNode } from "gatsby-plugin-mdx/dist/types"; +import { FileSystemNode } from "gatsby-source-filesystem"; + +interface BlogParentNode extends IMdxNode { + parent: string; + fields: { readingTime: unknown }; + frontmatter: { + title: string; + slug: string; + date: Date; + tags: string[]; + hero_image_alt: string; + hero_image_link: string; + hero_image_credit_link: string; + hero_image_credit: string; + hero_image: FileSystemNode; + }; +} + +export const createSchemaCustomization: GatsbyNode["createSchemaCustomization"] = + ({ actions }) => { + const { createTypes } = actions; + + createTypes(` + type HeroImageData { + alt: String! + link: String! + credit: String! + creditLink: String! + } + + type Blog implements Node { + title: String! + slug: String! + date: Date! + tags: [String!]! + heroImage: HeroImageData! + excerpt: String + } + `); + }; + +export const shouldOnCreateNode: GatsbyNode["shouldOnCreateNode"] = ({ + node, +}) => { + return ( + node.internal.type === "Mdx" && + !!node.parent && + // node.parent.sourceInstanceName === "blog" + (node.fields as undefined | { source: string })?.source === "blog" + ); +}; + +export const onCreateNode: GatsbyNode["onCreateNode"] = async ({ + node, + actions, + createNodeId, +}) => { + const { createNode, createParentChildLink } = actions; + + const blogNode: NodeInput = { + id: createNodeId(`${node.id} >>> blog`), + children: [], + parent: node.id, + internal: { + type: "Blog", + contentDigest: node.internal.contentDigest, + contentFilePath: node.internal.contentFilePath, + }, + title: node.frontmatter.title, + slug: node.frontmatter.slug, + date: node.frontmatter.date, + tags: node.frontmatter.tags, + // TODO: Excerpt seems to be generated lazily on query time, so we need to re-map a resolver later + excerpt: "", + readingTime: node.fields.readingTime, + heroImage: { + alt: node.frontmatter.hero_image_alt, + link: node.frontmatter.hero_image_link, + creditLink: node.frontmatter.hero_image_credit_link, + credit: node.frontmatter.hero_image_credit, + image: node.frontmatter.hero_image as FileSystemNode, + }, + }; + + createNode(blogNode); + createParentChildLink({ parent: node, child: blogNode }); +}; diff --git a/plugins/gatsby-transformer-nz-blog/package.json b/plugins/gatsby-transformer-nz-blog/package.json new file mode 100644 index 0000000..4200f1d --- /dev/null +++ b/plugins/gatsby-transformer-nz-blog/package.json @@ -0,0 +1,5 @@ +{ + "name": "gatsby-transformer-nz-blog", + "version": "1.0.0", + "private": true +} diff --git a/plugins/gatsby-transformer-source-split/gatsby-node.ts b/plugins/gatsby-transformer-source-split/gatsby-node.ts deleted file mode 100644 index 91dd686..0000000 --- a/plugins/gatsby-transformer-source-split/gatsby-node.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { GatsbyNode, NodeInput } from "gatsby"; -import { IMdxNode } from "gatsby-plugin-mdx/dist/types"; -import { FileSystemNode } from "gatsby-source-filesystem"; - -type SourceTypes = "blog" | "author"; - -interface BlogFrontmatter { - title: string; - slug: string; - date: Date; - tags: string[]; - hero_image_alt: string; - hero_image_link: string; - hero_image_credit_link: string; - hero_image_credit: string; - hero_image: FileSystemNode; -} - -interface AuthorFrontmatter { - slug: string; - first_name?: string; - last_name?: string; - nick_name?: string; - social?: Record>; -} - -interface MdxNodeWithSource extends IMdxNode { - fields: { source: SourceTypes; readingTime: unknown }; - frontmatter: T; -} - -export const createSchemaCustomization: GatsbyNode["createSchemaCustomization"] = - ({ actions, schema }) => { - const { createTypes } = actions; - - const typeDefs = ` - type HeroImageData { - alt: String! - link: String! - credit: String! - creditLink: String! - } - - type Blog implements Node { - title: String! - slug: String! - date: Date! - tags: [String!]! - heroImage: HeroImageData! - excerpt: String - } - - type Author implements Node { - slug: String! - firstName: String! - lastName: String! - nickName: String - social: JSON - } - `; - - createTypes(typeDefs); - }; - -export const shouldOnCreateNode: GatsbyNode< - MdxNodeWithSource ->["shouldOnCreateNode"] = ({ node }) => { - return node.internal.type === "Mdx" && !!node.parent; -}; - -export const onCreateNode: GatsbyNode["onCreateNode"] = async ({ - node, - actions, - createNodeId, - getNode, - cache, -}) => { - const createBlogNode = async ( - node: MdxNodeWithSource - ): Promise => { - return { - id: createNodeId(`${node.id} >>> blog`), - children: [], - parent: node.id, - internal: { - type: "Blog", - contentDigest: node.internal.contentDigest, - contentFilePath: node.internal.contentFilePath, - }, - title: node.frontmatter.title, - slug: node.frontmatter.slug, - date: node.frontmatter.date, - tags: node.frontmatter.tags, - // TODO: Excerpt seems to be generated lazily on query time, so we need to re-map a resolver later - excerpt: "", - readingTime: node.fields.readingTime, - heroImage: { - alt: node.frontmatter.hero_image_alt, - link: node.frontmatter.hero_image_link, - creditLink: node.frontmatter.hero_image_credit_link, - credit: node.frontmatter.hero_image_credit, - image: node.frontmatter.hero_image as FileSystemNode, - }, - }; - }; - - const createAuthorNode = async ( - node: MdxNodeWithSource - ): Promise => { - return { - id: createNodeId(`${node.id} >>> author`), - internal: { - type: "Author", - contentDigest: node.internal.contentDigest, - contentFilePath: node.internal.contentFilePath, - }, - slug: node.frontmatter.slug, - firstName: node.frontmatter.first_name, - lastName: node.frontmatter.last_name, - nickName: node.frontmatter.nick_name, - social: node.frontmatter.social, - }; - }; - - const nodeCreator: ( - sourceType: SourceTypes, - node: IMdxNode - ) => Promise = async (sourceType, node) => { - switch (sourceType) { - case "blog": - return createBlogNode(node as MdxNodeWithSource); - - case "author": - return createAuthorNode(node as MdxNodeWithSource); - - default: - throw new Error(`Unknown source type ${sourceType}`); - } - }; - - if (!node.parent) { - throw new Error("Node has no parent"); - } - - const { createNode, createParentChildLink } = actions; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const { sourceInstanceName: source } = getNode( - node.parent - )! as FileSystemNode; - - const newNode = await nodeCreator(source as unknown as SourceTypes, node); - - createNode(newNode); - createParentChildLink({ parent: node, child: newNode }); - await cache.set(`mdxToSplit-${node.internal.contentFilePath}`, newNode); -}; diff --git a/plugins/gatsby-transformer-source-split/package.json b/plugins/gatsby-transformer-source-split/package.json deleted file mode 100644 index 2e0651b..0000000 --- a/plugins/gatsby-transformer-source-split/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "gatsby-transformer-source-split", - "version": "1.0.0", - "private": true -} From 490c9a8392980e1ebfe3a7a5dc8d23313e3cf414 Mon Sep 17 00:00:00 2001 From: Norbert Melzer Date: Sun, 2 Jul 2023 12:59:07 +0200 Subject: [PATCH 04/11] feature: display author in blogpost --- blog/001-hello-world/index.mdx | 1 + .../index.mdx | 1 + .../index.mdx | 1 + blog/004-12in23/index.mdx | 1 + .../index.mdx | 1 + .../gatsby-transformer-nz-blog/gatsby-node.ts | 3 ++ src/gatsby-types.d.ts | 11 ++++++- src/pages/{blog.date}-{blog.slug}/index.tsx | 32 +++++++++---------- 8 files changed, 33 insertions(+), 18 deletions(-) diff --git a/blog/001-hello-world/index.mdx b/blog/001-hello-world/index.mdx index 87343e4..7066340 100644 --- a/blog/001-hello-world/index.mdx +++ b/blog/001-hello-world/index.mdx @@ -14,6 +14,7 @@ tags: - elixir - erlang - javascript +author: "nobbz" --- This is mainly a short post to see whether or not the infrastructure is working diff --git a/blog/002-callpackage-a-tool-for-the-lazy/index.mdx b/blog/002-callpackage-a-tool-for-the-lazy/index.mdx index 66431c3..06fa3ec 100644 --- a/blog/002-callpackage-a-tool-for-the-lazy/index.mdx +++ b/blog/002-callpackage-a-tool-for-the-lazy/index.mdx @@ -8,6 +8,7 @@ hero_image_alt: A cat sleeping under a blanket hero_image_credit: Kate Stone Matheson on Unsplash hero_image_credit_link: https://unsplash.com/@kstonematheson hero_image_link: https://unsplash.com/photos/uy5t-CJuIK4 +author: "nobbz" tags: - nix - callPkgs diff --git a/blog/003-getting-inputs-to-modules-in-a-flake/index.mdx b/blog/003-getting-inputs-to-modules-in-a-flake/index.mdx index 692bb71..d805b29 100644 --- a/blog/003-getting-inputs-to-modules-in-a-flake/index.mdx +++ b/blog/003-getting-inputs-to-modules-in-a-flake/index.mdx @@ -11,6 +11,7 @@ hero_image_alt: Close-up of the Enter key on a laptop hero_image_credit: Arthur Mazi on Unsplash hero_image_credit_link: https://unsplash.com/@arthurbizkit hero_image_link: https://unsplash.com/photos/fEXeyNYmO2Y +author: "nobbz" tags: - nix - flake diff --git a/blog/004-12in23/index.mdx b/blog/004-12in23/index.mdx index f10d1f6..2a02e50 100644 --- a/blog/004-12in23/index.mdx +++ b/blog/004-12in23/index.mdx @@ -10,6 +10,7 @@ hero_image_alt: A hand trying to pick a numerical lock with a toothpick hero_image_credit: Ariel on Unsplash hero_image_credit_link: https://unsplash.com/@arielbesagar hero_image_link: https://unsplash.com/photos/Oal07Ai4oTk +author: "nobbz" tags: - exercism - 12in23 diff --git a/blog/005-nixos-flakes-command-not-found/index.mdx b/blog/005-nixos-flakes-command-not-found/index.mdx index 36ed81b..ed0283e 100644 --- a/blog/005-nixos-flakes-command-not-found/index.mdx +++ b/blog/005-nixos-flakes-command-not-found/index.mdx @@ -12,6 +12,7 @@ hero_image_alt: Close-up of a snowflake on black whool hero_image_credit: Darius Cotoi on Unsplash hero_image_credit_link: https://unsplash.com/@dariuscotoi hero_image_link: https://unsplash.com/photos/wzemAWJGc8E +author: "nobbz" tags: - nixos - flakes diff --git a/plugins/gatsby-transformer-nz-blog/gatsby-node.ts b/plugins/gatsby-transformer-nz-blog/gatsby-node.ts index 40a6c38..0543085 100644 --- a/plugins/gatsby-transformer-nz-blog/gatsby-node.ts +++ b/plugins/gatsby-transformer-nz-blog/gatsby-node.ts @@ -7,6 +7,7 @@ interface BlogParentNode extends IMdxNode { parent: string; fields: { readingTime: unknown }; frontmatter: { + author: string; title: string; slug: string; date: Date; @@ -33,6 +34,7 @@ export const createSchemaCustomization: GatsbyNode["createSchemaCustomization"] type Blog implements Node { title: String! + author: Author @link(by: "slug") slug: String! date: Date! tags: [String!]! @@ -73,6 +75,7 @@ export const onCreateNode: GatsbyNode["onCreateNode"] = async ({ slug: node.frontmatter.slug, date: node.frontmatter.date, tags: node.frontmatter.tags, + author: node.frontmatter.author, // TODO: Excerpt seems to be generated lazily on query time, so we need to re-map a resolver later excerpt: "", readingTime: node.fields.readingTime, diff --git a/src/gatsby-types.d.ts b/src/gatsby-types.d.ts index e1105ad..168a1c5 100644 --- a/src/gatsby-types.d.ts +++ b/src/gatsby-types.d.ts @@ -182,6 +182,7 @@ type AuthorSortInput = { }; type Blog = Node & { + readonly author: Maybe; readonly children: ReadonlyArray; readonly date: Scalars['Date']; readonly excerpt: Maybe; @@ -247,6 +248,7 @@ type BlogEdge = { }; type BlogFieldSelector = { + readonly author: InputMaybe; readonly children: InputMaybe; readonly date: InputMaybe; readonly excerpt: InputMaybe; @@ -262,6 +264,7 @@ type BlogFieldSelector = { }; type BlogFilterInput = { + readonly author: InputMaybe; readonly children: InputMaybe; readonly date: InputMaybe; readonly excerpt: InputMaybe; @@ -346,6 +349,7 @@ type BlogReadingTimeSortInput = { }; type BlogSortInput = { + readonly author: InputMaybe; readonly children: InputMaybe; readonly date: InputMaybe; readonly excerpt: InputMaybe; @@ -1759,6 +1763,7 @@ type MdxFilterListInput = { }; type MdxFrontmatter = { + readonly author: Maybe; readonly date: Scalars['Date']; readonly description: Maybe; readonly first_name: Maybe; @@ -1784,6 +1789,7 @@ type MdxFrontmatter_dateArgs = { }; type MdxFrontmatterFieldSelector = { + readonly author: InputMaybe; readonly date: InputMaybe; readonly description: InputMaybe; readonly first_name: InputMaybe; @@ -1801,6 +1807,7 @@ type MdxFrontmatterFieldSelector = { }; type MdxFrontmatterFilterInput = { + readonly author: InputMaybe; readonly date: InputMaybe; readonly description: InputMaybe; readonly first_name: InputMaybe; @@ -1866,6 +1873,7 @@ type MdxFrontmatterSocialWebSortInput = { }; type MdxFrontmatterSortInput = { + readonly author: InputMaybe; readonly date: InputMaybe; readonly description: InputMaybe; readonly first_name: InputMaybe; @@ -2136,6 +2144,7 @@ type Query_authorArgs = { type Query_blogArgs = { + author: InputMaybe; children: InputMaybe; date: InputMaybe; excerpt: InputMaybe; @@ -3132,7 +3141,7 @@ type BlogPostByIdQueryVariables = Exact<{ }>; -type BlogPostByIdQuery = { readonly blog: { readonly tags: ReadonlyArray, readonly date: string, readonly title: string, readonly heroImage: { readonly link: string, readonly credit: string, readonly alt: string, readonly image: { readonly childImageSharp: { readonly gatsbyImageData: import('gatsby-plugin-image').IGatsbyImageData } | null } | null } } | null }; +type BlogPostByIdQuery = { readonly blog: { readonly tags: ReadonlyArray, readonly date: string, readonly title: string, readonly author: { readonly slug: string, readonly firstName: string, readonly lastName: string } | null, readonly heroImage: { readonly link: string, readonly credit: string, readonly alt: string, readonly image: { readonly childImageSharp: { readonly gatsbyImageData: import('gatsby-plugin-image').IGatsbyImageData } | null } | null } } | null }; type BlogPostsQueryVariables = Exact<{ [key: string]: never; }>; diff --git a/src/pages/{blog.date}-{blog.slug}/index.tsx b/src/pages/{blog.date}-{blog.slug}/index.tsx index aece431..e6b62d0 100644 --- a/src/pages/{blog.date}-{blog.slug}/index.tsx +++ b/src/pages/{blog.date}-{blog.slug}/index.tsx @@ -1,7 +1,8 @@ import * as React from "react"; -import { PageProps, graphql } from "gatsby"; +import { Link, PageProps, graphql } from "gatsby"; import { GatsbyImage, ImageDataLike, getImage } from "gatsby-plugin-image"; +import { Icon } from "@iconify-icon/react"; import { Layout, Comments, MDXWrapper, Seo } from "~components"; import { Tag } from "../../templates/tags"; @@ -25,22 +26,14 @@ const BlogPost = ({ data, children }: BlogPostProps) => { className="rounded-t-lg" alt={data.blog.heroImage.alt} /> -

    - - Posted at - - - {data.blog.date} -

    +
    + + {data.blog.date};{" "} + + + {data.blog.author?.firstName} {data.blog.author?.lastName} + +

    Date: Sun, 2 Jul 2023 21:54:08 +0200 Subject: [PATCH 05/11] feature: add author link to article --- src/pages/{blog.date}-{blog.slug}/index.tsx | 22 +++++---------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/pages/{blog.date}-{blog.slug}/index.tsx b/src/pages/{blog.date}-{blog.slug}/index.tsx index e6b62d0..c364a6f 100644 --- a/src/pages/{blog.date}-{blog.slug}/index.tsx +++ b/src/pages/{blog.date}-{blog.slug}/index.tsx @@ -20,35 +20,23 @@ const BlogPost = ({ data, children }: BlogPostProps) => { // TODO: Make this a component const hero = image ? ( -

    +
    -
    +
    {data.blog.date};{" "} - + {data.blog.author?.firstName} {data.blog.author?.lastName}
    -

    +

    - - - - + {data.blog.heroImage.credit}

    From 104174cc36917c2511c1db21e9c8efed8596d7f3 Mon Sep 17 00:00:00 2001 From: Norbert Melzer Date: Sun, 2 Jul 2023 22:41:24 +0200 Subject: [PATCH 06/11] feature: add list of articles to author --- plugins/gatsby-transformer-nz-author/gatsby-node.ts | 1 + src/components/articlepreview.tsx | 2 +- src/gatsby-types.d.ts | 7 ++++++- src/pages/author/{author.slug}/index.tsx | 12 +++++++++++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/plugins/gatsby-transformer-nz-author/gatsby-node.ts b/plugins/gatsby-transformer-nz-author/gatsby-node.ts index ddd5c69..0cd34a1 100644 --- a/plugins/gatsby-transformer-nz-author/gatsby-node.ts +++ b/plugins/gatsby-transformer-nz-author/gatsby-node.ts @@ -25,6 +25,7 @@ export const createSchemaCustomization: GatsbyNode["createSchemaCustomization"] lastName: String! nickName: String social: JSON + articles: [Blog] @link(by: "author.slug", from: "slug") } `); }; diff --git a/src/components/articlepreview.tsx b/src/components/articlepreview.tsx index e5d09b1..3290d40 100644 --- a/src/components/articlepreview.tsx +++ b/src/components/articlepreview.tsx @@ -5,7 +5,7 @@ import { GatsbyImage, ImageDataLike, getImage } from "gatsby-plugin-image"; import { Tag } from "../templates/tags"; -type BlogPostNode = Queries.PreviewDataFragment; +export type BlogPostNode = Queries.PreviewDataFragment; interface PreviewProps { node: BlogPostNode; } diff --git a/src/gatsby-types.d.ts b/src/gatsby-types.d.ts index 168a1c5..f8a9f1d 100644 --- a/src/gatsby-types.d.ts +++ b/src/gatsby-types.d.ts @@ -35,6 +35,7 @@ type AVIFOptions = { }; type Author = Node & { + readonly articles: Maybe>>; readonly children: ReadonlyArray; readonly firstName: Scalars['String']; readonly gatsbyPath: Maybe; @@ -98,6 +99,7 @@ type AuthorEdge = { }; type AuthorFieldSelector = { + readonly articles: InputMaybe; readonly children: InputMaybe; readonly firstName: InputMaybe; readonly gatsbyPath: InputMaybe; @@ -111,6 +113,7 @@ type AuthorFieldSelector = { }; type AuthorFilterInput = { + readonly articles: InputMaybe; readonly children: InputMaybe; readonly firstName: InputMaybe; readonly gatsbyPath: InputMaybe; @@ -169,6 +172,7 @@ type AuthorGroupConnection_sumArgs = { }; type AuthorSortInput = { + readonly articles: InputMaybe; readonly children: InputMaybe; readonly firstName: InputMaybe; readonly gatsbyPath: InputMaybe; @@ -2130,6 +2134,7 @@ type Query_allSitePluginArgs = { type Query_authorArgs = { + articles: InputMaybe; children: InputMaybe; firstName: InputMaybe; gatsbyPath: InputMaybe; @@ -3134,7 +3139,7 @@ type AuthorInfoByIdQueryVariables = Exact<{ }>; -type AuthorInfoByIdQuery = { readonly author: { readonly firstName: string, readonly lastName: string, readonly nickName: string | null, readonly social: Record | null } | null }; +type AuthorInfoByIdQuery = { readonly author: { readonly firstName: string, readonly lastName: string, readonly nickName: string | null, readonly social: Record | null, readonly articles: ReadonlyArray<{ readonly date: string, readonly excerpt: string | null, readonly slug: string, readonly tags: ReadonlyArray, readonly title: string, readonly heroImage: { readonly alt: string, readonly image: { readonly childImageSharp: { readonly gatsbyImageData: import('gatsby-plugin-image').IGatsbyImageData } | null } | null }, readonly readingTime: { readonly text: string | null } | null } | null> | null } | null }; type BlogPostByIdQueryVariables = Exact<{ id: InputMaybe; diff --git a/src/pages/author/{author.slug}/index.tsx b/src/pages/author/{author.slug}/index.tsx index d3fbfaa..8b155a5 100644 --- a/src/pages/author/{author.slug}/index.tsx +++ b/src/pages/author/{author.slug}/index.tsx @@ -3,7 +3,8 @@ import * as React from "react"; import { PageProps, graphql } from "gatsby"; import { Icon } from "@iconify-icon/react"; -import { Layout, MDXWrapper } from "~components"; +import { ArticlePreview, Layout, MDXWrapper } from "~components"; +import type { BlogPostNode } from "~components/articlepreview"; type AuthorPageProps = PageProps; @@ -84,10 +85,16 @@ const AuthorPage = ({ data, children }: AuthorPageProps) => {
    ) : undefined; + const previews = (data.author.articles ? data.author.articles : []) + .filter((article): article is BlogPostNode => article !== null) + .sort((a, b) => (a.date > b.date ? -1 : 1)) + .map((article) => ); + return ( {socialBox} {children} + {previews} ); }; @@ -99,6 +106,9 @@ export const query = graphql` lastName nickName social + articles { + ...PreviewData + } } } `; From 335ae65aef05b50c9e7a9cc77071db7199a054ca Mon Sep 17 00:00:00 2001 From: Norbert Melzer Date: Sun, 2 Jul 2023 23:02:39 +0200 Subject: [PATCH 07/11] feature: add linkedin support --- author/nobbz.mdx | 1 + src/gatsby-types.d.ts | 4 ++++ src/pages/author/{author.slug}/index.tsx | 12 +++++++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/author/nobbz.mdx b/author/nobbz.mdx index 935ec65..41ef981 100644 --- a/author/nobbz.mdx +++ b/author/nobbz.mdx @@ -7,6 +7,7 @@ social: github: "NobbZ" gitlab: "NobbZ" twitter: "NobbZ1981" + linkedin: "norbert-melzer-a175829a" web: url: "https://blog.nobbz.dev/" name: "NobbZ Blog" diff --git a/src/gatsby-types.d.ts b/src/gatsby-types.d.ts index f8a9f1d..c9ec987 100644 --- a/src/gatsby-types.d.ts +++ b/src/gatsby-types.d.ts @@ -1831,6 +1831,7 @@ type MdxFrontmatterFilterInput = { type MdxFrontmatterSocial = { readonly github: Maybe; readonly gitlab: Maybe; + readonly linkedin: Maybe; readonly twitter: Maybe; readonly web: Maybe; }; @@ -1838,6 +1839,7 @@ type MdxFrontmatterSocial = { type MdxFrontmatterSocialFieldSelector = { readonly github: InputMaybe; readonly gitlab: InputMaybe; + readonly linkedin: InputMaybe; readonly twitter: InputMaybe; readonly web: InputMaybe; }; @@ -1845,6 +1847,7 @@ type MdxFrontmatterSocialFieldSelector = { type MdxFrontmatterSocialFilterInput = { readonly github: InputMaybe; readonly gitlab: InputMaybe; + readonly linkedin: InputMaybe; readonly twitter: InputMaybe; readonly web: InputMaybe; }; @@ -1852,6 +1855,7 @@ type MdxFrontmatterSocialFilterInput = { type MdxFrontmatterSocialSortInput = { readonly github: InputMaybe; readonly gitlab: InputMaybe; + readonly linkedin: InputMaybe; readonly twitter: InputMaybe; readonly web: InputMaybe; }; diff --git a/src/pages/author/{author.slug}/index.tsx b/src/pages/author/{author.slug}/index.tsx index 8b155a5..5e716df 100644 --- a/src/pages/author/{author.slug}/index.tsx +++ b/src/pages/author/{author.slug}/index.tsx @@ -44,6 +44,16 @@ const getSocialLink = ( {data} ); + case "linkedin": + if (typeof data !== "string") { + throw new Error("LinkedIn handle is not a string"); + } + + return ( + + {data} + + ); case "web": if (typeof data === "string") { throw new Error("Web data is not an object"); @@ -80,7 +90,7 @@ const AuthorPage = ({ data, children }: AuthorPageProps) => { : undefined; const socialBox = socialLinks ? ( -
    +
    {socialLinks}
    ) : undefined; From fc3bae3844980fef1af6a912808ea5af3afd6f48 Mon Sep 17 00:00:00 2001 From: Norbert Melzer Date: Sun, 2 Jul 2023 23:13:49 +0200 Subject: [PATCH 08/11] feature: add ko-fi support --- author/nobbz.mdx | 1 + src/gatsby-types.d.ts | 4 ++++ src/pages/author/{author.slug}/index.tsx | 10 ++++++++++ 3 files changed, 15 insertions(+) diff --git a/author/nobbz.mdx b/author/nobbz.mdx index 41ef981..17ff45c 100644 --- a/author/nobbz.mdx +++ b/author/nobbz.mdx @@ -8,6 +8,7 @@ social: gitlab: "NobbZ" twitter: "NobbZ1981" linkedin: "norbert-melzer-a175829a" + ko-fi: "nobbz" web: url: "https://blog.nobbz.dev/" name: "NobbZ Blog" diff --git a/src/gatsby-types.d.ts b/src/gatsby-types.d.ts index c9ec987..35d1bc0 100644 --- a/src/gatsby-types.d.ts +++ b/src/gatsby-types.d.ts @@ -1831,6 +1831,7 @@ type MdxFrontmatterFilterInput = { type MdxFrontmatterSocial = { readonly github: Maybe; readonly gitlab: Maybe; + readonly ko_fi: Maybe; readonly linkedin: Maybe; readonly twitter: Maybe; readonly web: Maybe; @@ -1839,6 +1840,7 @@ type MdxFrontmatterSocial = { type MdxFrontmatterSocialFieldSelector = { readonly github: InputMaybe; readonly gitlab: InputMaybe; + readonly ko_fi: InputMaybe; readonly linkedin: InputMaybe; readonly twitter: InputMaybe; readonly web: InputMaybe; @@ -1847,6 +1849,7 @@ type MdxFrontmatterSocialFieldSelector = { type MdxFrontmatterSocialFilterInput = { readonly github: InputMaybe; readonly gitlab: InputMaybe; + readonly ko_fi: InputMaybe; readonly linkedin: InputMaybe; readonly twitter: InputMaybe; readonly web: InputMaybe; @@ -1855,6 +1858,7 @@ type MdxFrontmatterSocialFilterInput = { type MdxFrontmatterSocialSortInput = { readonly github: InputMaybe; readonly gitlab: InputMaybe; + readonly ko_fi: InputMaybe; readonly linkedin: InputMaybe; readonly twitter: InputMaybe; readonly web: InputMaybe; diff --git a/src/pages/author/{author.slug}/index.tsx b/src/pages/author/{author.slug}/index.tsx index 5e716df..1a4724f 100644 --- a/src/pages/author/{author.slug}/index.tsx +++ b/src/pages/author/{author.slug}/index.tsx @@ -44,6 +44,16 @@ const getSocialLink = ( {data} ); + case "ko-fi": + if (typeof data !== "string") { + throw new Error("Ko-fi handle is not a string"); + } + + return ( + + {data} + + ); case "linkedin": if (typeof data !== "string") { throw new Error("LinkedIn handle is not a string"); From d26d28d7ef53f2ba75eea122d8dd6f1ead6e07c5 Mon Sep 17 00:00:00 2001 From: Norbert Melzer Date: Sun, 2 Jul 2023 23:17:57 +0200 Subject: [PATCH 09/11] feature: add wishlist support --- author/nobbz.mdx | 1 + src/gatsby-types.d.ts | 4 ++++ src/pages/author/{author.slug}/index.tsx | 10 ++++++++++ 3 files changed, 15 insertions(+) diff --git a/author/nobbz.mdx b/author/nobbz.mdx index 17ff45c..f4396ed 100644 --- a/author/nobbz.mdx +++ b/author/nobbz.mdx @@ -4,6 +4,7 @@ first_name: "Norbert" last_name: "Melzer" nick_name: "NobbZ" social: + amazon: "https://www.amazon.de/hz/wishlist/ls/1GZRUIT9EP93R?ref_=wl_share" github: "NobbZ" gitlab: "NobbZ" twitter: "NobbZ1981" diff --git a/src/gatsby-types.d.ts b/src/gatsby-types.d.ts index 35d1bc0..d7ea8c0 100644 --- a/src/gatsby-types.d.ts +++ b/src/gatsby-types.d.ts @@ -1829,6 +1829,7 @@ type MdxFrontmatterFilterInput = { }; type MdxFrontmatterSocial = { + readonly amazon: Maybe; readonly github: Maybe; readonly gitlab: Maybe; readonly ko_fi: Maybe; @@ -1838,6 +1839,7 @@ type MdxFrontmatterSocial = { }; type MdxFrontmatterSocialFieldSelector = { + readonly amazon: InputMaybe; readonly github: InputMaybe; readonly gitlab: InputMaybe; readonly ko_fi: InputMaybe; @@ -1847,6 +1849,7 @@ type MdxFrontmatterSocialFieldSelector = { }; type MdxFrontmatterSocialFilterInput = { + readonly amazon: InputMaybe; readonly github: InputMaybe; readonly gitlab: InputMaybe; readonly ko_fi: InputMaybe; @@ -1856,6 +1859,7 @@ type MdxFrontmatterSocialFilterInput = { }; type MdxFrontmatterSocialSortInput = { + readonly amazon: InputMaybe; readonly github: InputMaybe; readonly gitlab: InputMaybe; readonly ko_fi: InputMaybe; diff --git a/src/pages/author/{author.slug}/index.tsx b/src/pages/author/{author.slug}/index.tsx index 1a4724f..0da5e75 100644 --- a/src/pages/author/{author.slug}/index.tsx +++ b/src/pages/author/{author.slug}/index.tsx @@ -14,6 +14,16 @@ const getSocialLink = ( data: string | Record ) => { switch (platform) { + case "amazon": + if (typeof data !== "string") { + throw new Error("Amazon link is not a string"); + } + + return ( + + Wishlist + + ); case "twitter": if (typeof data !== "string") { throw new Error("Twitter handle is not a string"); From 504c955d42338fbd62fbd69e0956befcd1c9895b Mon Sep 17 00:00:00 2001 From: Norbert Melzer Date: Sun, 2 Jul 2023 23:21:22 +0200 Subject: [PATCH 10/11] chore: alphabetize social list --- author/nobbz.mdx | 4 ++-- src/pages/author/{author.slug}/index.tsx | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/author/nobbz.mdx b/author/nobbz.mdx index f4396ed..8595733 100644 --- a/author/nobbz.mdx +++ b/author/nobbz.mdx @@ -7,9 +7,9 @@ social: amazon: "https://www.amazon.de/hz/wishlist/ls/1GZRUIT9EP93R?ref_=wl_share" github: "NobbZ" gitlab: "NobbZ" - twitter: "NobbZ1981" - linkedin: "norbert-melzer-a175829a" ko-fi: "nobbz" + linkedin: "norbert-melzer-a175829a" + twitter: "NobbZ1981" web: url: "https://blog.nobbz.dev/" name: "NobbZ Blog" diff --git a/src/pages/author/{author.slug}/index.tsx b/src/pages/author/{author.slug}/index.tsx index 0da5e75..de244e1 100644 --- a/src/pages/author/{author.slug}/index.tsx +++ b/src/pages/author/{author.slug}/index.tsx @@ -24,16 +24,6 @@ const getSocialLink = ( Wishlist ); - case "twitter": - if (typeof data !== "string") { - throw new Error("Twitter handle is not a string"); - } - - return ( - - {data} - - ); case "github": if (typeof data !== "string") { throw new Error("GitHub handle is not a string"); @@ -74,6 +64,16 @@ const getSocialLink = ( {data} ); + case "twitter": + if (typeof data !== "string") { + throw new Error("Twitter handle is not a string"); + } + + return ( + + {data} + + ); case "web": if (typeof data === "string") { throw new Error("Web data is not an object"); From 49d4ab5bf8b5f58d6a855ddcd8584078b2c14fe2 Mon Sep 17 00:00:00 2001 From: Norbert Melzer Date: Sun, 2 Jul 2023 23:24:35 +0200 Subject: [PATCH 11/11] refactor: use author page for about --- src/components/layout.tsx | 2 +- src/pages/about.tsx | 15 --------------- tests/components/layout.test.tsx | 2 +- 3 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 src/pages/about.tsx diff --git a/src/components/layout.tsx b/src/components/layout.tsx index 9914fec..59e267b 100644 --- a/src/components/layout.tsx +++ b/src/components/layout.tsx @@ -31,7 +31,7 @@ export const Layout = ({ className, pageTitle, children }: LayoutProps) => { Tags
  • - About + About
  • diff --git a/src/pages/about.tsx b/src/pages/about.tsx deleted file mode 100644 index a6cc485..0000000 --- a/src/pages/about.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import * as React from "react"; - -import { Layout, Seo } from "~components"; - -const AboutPage = () => ( - -

    - Hi there! I'm the proud creator of this site, which I built with Gatsby. -

    -
    -); - -export const Head = () => ; - -export default AboutPage; diff --git a/tests/components/layout.test.tsx b/tests/components/layout.test.tsx index 5867248..48ff320 100644 --- a/tests/components/layout.test.tsx +++ b/tests/components/layout.test.tsx @@ -65,7 +65,7 @@ describe("Layout component", () => { const navLinks = { Blog: "/", Tags: "/tags", - About: "/about", + About: "/author/nobbz", }; for (const [name, path] of Object.entries(navLinks)) {