From d33079ac0c924dd952b28c4a149572aa5ec9ec8d Mon Sep 17 00:00:00 2001 From: amit Date: Wed, 7 Aug 2024 15:59:22 +0530 Subject: [PATCH] feat: blog restructure --- blog/2023-graphql-conf-2023-09-29.md | 1 - blog/api-orchestration-2023-06-12.md | 2 - .../automatic-persisted-queries-2023-08-11.md | 2 - blog/bff-challenges-2023-06-19.md | 1 - blog/graphql-angular-clients-2024-07-20.md | 2 - ...raphql-introspection-security-2024-7-12.md | 2 +- blog/graphql-schema-2024-07-11.md | 2 - blog/graphql-schema-part-2-1-2024-07-21.mdx | 4 +- blog/graphql-schema-part-2-2-2024-07-22.mdx | 4 +- blog/graphql-schema-part-2-3-2024-07-23.mdx | 3 +- blog/graphql-vs-openapi-part-1-2024-07-29.md | 1 - blog/graphql-vs-openapi-part-2-2024-07-30.md | 1 - blog/graphql-vs-openapi-part-3-2024-07-31.md | 2 - blog/graphql-vs-rest-vs-grpc-2024-03-30.md | 6 +- blog/graphql-vue-clients-2024-08-01.md | 4 +- blog/grpc-vs-graphql-2024-07-26.mdx | 2 - blog/guide-on-graphiql-2024-07-24.md | 2 - blog/no-code-graphql-2024-05-30.md | 1 - blog/tailcall-n+1-working-2024-08-04.md | 1 - blog/what-is-grpc-2024-07-13.mdx | 2 - src/theme/BlogPostItem/Container/index.tsx | 6 ++ src/theme/BlogPostItem/Content/index.tsx | 19 +++++ .../Footer/ReadMoreLink/index.tsx | 36 ++++++++++ src/theme/BlogPostItem/Footer/index.tsx | 69 +++++++++++++++++++ .../BlogPostItem/Header/Author/index.tsx | 37 ++++++++++ .../BlogPostItem/Header/Authors/index.tsx | 37 ++++++++++ .../Header/Authors/styles.module.css | 14 ++++ src/theme/BlogPostItem/Header/Info/index.tsx | 67 ++++++++++++++++++ .../Header/Info/styles.module.css | 3 + src/theme/BlogPostItem/Header/Title/index.tsx | 18 +++++ .../Header/Title/styles.module.css | 12 ++++ src/theme/BlogPostItem/Header/index.tsx | 14 ++++ src/theme/BlogPostItem/index.tsx | 26 +++++++ 33 files changed, 368 insertions(+), 35 deletions(-) create mode 100644 src/theme/BlogPostItem/Container/index.tsx create mode 100644 src/theme/BlogPostItem/Content/index.tsx create mode 100644 src/theme/BlogPostItem/Footer/ReadMoreLink/index.tsx create mode 100644 src/theme/BlogPostItem/Footer/index.tsx create mode 100644 src/theme/BlogPostItem/Header/Author/index.tsx create mode 100644 src/theme/BlogPostItem/Header/Authors/index.tsx create mode 100644 src/theme/BlogPostItem/Header/Authors/styles.module.css create mode 100644 src/theme/BlogPostItem/Header/Info/index.tsx create mode 100644 src/theme/BlogPostItem/Header/Info/styles.module.css create mode 100644 src/theme/BlogPostItem/Header/Title/index.tsx create mode 100644 src/theme/BlogPostItem/Header/Title/styles.module.css create mode 100644 src/theme/BlogPostItem/Header/index.tsx create mode 100644 src/theme/BlogPostItem/index.tsx diff --git a/blog/2023-graphql-conf-2023-09-29.md b/blog/2023-graphql-conf-2023-09-29.md index be6d7d9900..255b864983 100644 --- a/blog/2023-graphql-conf-2023-09-29.md +++ b/blog/2023-graphql-conf-2023-09-29.md @@ -10,7 +10,6 @@ slug: graphql-conf-2023 canonical_url: https://tailcall.hashnode.dev/graphql-conf-2023 --- -![A Photo from GraphQL Conf 2023](../static/images/blog/graphql-conf-2023.png) GraphQLConf 2023 wasn't just another tech conference; it was a groundbreaking event hosted by the GraphQL Foundation. Bursting with riveting workshops, enlightening talks, and interactive sponsor booths, this conference was a deep dive into the ever-evolving world of GraphQL. diff --git a/blog/api-orchestration-2023-06-12.md b/blog/api-orchestration-2023-06-12.md index 8aa058cf77..321fb0a389 100644 --- a/blog/api-orchestration-2023-06-12.md +++ b/blog/api-orchestration-2023-06-12.md @@ -13,8 +13,6 @@ slug: no-one-talks-about-api-orchestration canonical_url: https://tailcall.hashnode.dev/no-one-talks-about-api-orchestration --- -![bff-architecture.png](../static/images/blog/bff-architecture.png) - diff --git a/blog/automatic-persisted-queries-2023-08-11.md b/blog/automatic-persisted-queries-2023-08-11.md index 99f0c4c245..0e44cececd 100644 --- a/blog/automatic-persisted-queries-2023-08-11.md +++ b/blog/automatic-persisted-queries-2023-08-11.md @@ -12,8 +12,6 @@ slug: the-truth-about-scaling-automatic-persisted-queries canonical_url: https://tailcall.hashnode.dev/the-truth-about-scaling-automatic-persisted-queries --- -![Cover Image for The truth about scaling Automatic Persisted Queries](../static/images/blog/apq-cover.png) - Persisted queries are often hailed as a solution to several challenges in GraphQL related to network performance, caching, and maintenance. However, they may not always be the silver bullet they appear to be. This post delves into the concept of persisted queries (PQ) and automatic persisted queries (APQ), highlighting the limitations and potential scaling issues that accompany these technologies. diff --git a/blog/bff-challenges-2023-06-19.md b/blog/bff-challenges-2023-06-19.md index f5ad748d00..27e6709c36 100644 --- a/blog/bff-challenges-2023-06-19.md +++ b/blog/bff-challenges-2023-06-19.md @@ -13,7 +13,6 @@ slug: unraveling-the-challenges-of-bff-federation canonical_url: https://tailcall.hashnode.dev/unraveling-the-challenges-of-bff-federation --- -![Cover Image for Unraveling the Challenges of BFF Federation](../static/images/blog/bff-cover.png) In our [previous](https://blog.tailcall.run/no-one-talks-about-api-orchestration) blog post, we discussed the challenges of API Orchestration and its often overlooked role in a microservices architecture. We explored how, while it serves as an abstraction for frontend apps and websites, this abstraction's performance is very sensitive to network latency and device performance thus directly impacting end-user experience. One proposed solution was to create a Backend for Frontend (BFF) layer, essentially moving the frontend abstraction to powerful servers within your VPC. Although this approach effectively addresses the user experience problem and simplifies the work of front-end engineers, it introduces a new set of challenges for the backend, leading to difficulties in scaling the monolithic solution. Here's what the BFF architecture looked like: diff --git a/blog/graphql-angular-clients-2024-07-20.md b/blog/graphql-angular-clients-2024-07-20.md index 4bcff2b04e..3522534812 100644 --- a/blog/graphql-angular-clients-2024-07-20.md +++ b/blog/graphql-angular-clients-2024-07-20.md @@ -13,8 +13,6 @@ slug: graphql-angular-client image: /images/blog/angular-with-graphql.png --- -![Cover Image for Angular with GraphQL](../static/images/blog/angular-with-graphql.png) - Angular developers often face the challenge of efficiently fetching and managing data from GraphQL APIs. This comprehensive guide dives into five powerful approaches for integrating GraphQL into your Angular applications. We'll explore everything from full-featured client libraries to lightweight solutions, using a practical example of fetching post data to demonstrate each method's strengths and nuances. diff --git a/blog/graphql-introspection-security-2024-7-12.md b/blog/graphql-introspection-security-2024-7-12.md index 476135ddb0..e4263470b2 100644 --- a/blog/graphql-introspection-security-2024-7-12.md +++ b/blog/graphql-introspection-security-2024-7-12.md @@ -9,9 +9,9 @@ tags: [GraphQL, Schema, Security, Introspection] description: Learn how attackers exploit GraphQL introspection and the battle-tested strategies to keep your data safe. hide_table_of_contents: true slug: graphql-introspection-security +image: /images/blog/introspection-issues.png --- -![GraphQL Introspection Security Issues](../static/images/blog/introspection-issues.png) GraphQL has taken the API world by storm, offering developers a flexible and powerful way to interact with backend systems. But with great power comes great responsibility—especially when it comes to security. diff --git a/blog/graphql-schema-2024-07-11.md b/blog/graphql-schema-2024-07-11.md index c720a5c282..4d89377e84 100644 --- a/blog/graphql-schema-2024-07-11.md +++ b/blog/graphql-schema-2024-07-11.md @@ -12,8 +12,6 @@ hide_table_of_contents: true slug: graphql-schema --- -![GraphQL Schema Structure](../static/images/graphql/graphql-schema-structure.png) - Designing a robust, scalable GraphQL schema is critical for building production-ready APIs that can evolve with your application's needs. In this comprehensive guide, we'll walk through the process of crafting a GraphQL schema for a real-world application, highlighting best practices and considerations along the way. If you are thinking how we could possibly cover all of the lovely intricacies associated with this topic in one go, you are right, we can't and so we are not! We have created an amazing series to take you through the nuances of working with GraphQL schemas. diff --git a/blog/graphql-schema-part-2-1-2024-07-21.mdx b/blog/graphql-schema-part-2-1-2024-07-21.mdx index 3cccafca55..143b49b363 100644 --- a/blog/graphql-schema-part-2-1-2024-07-21.mdx +++ b/blog/graphql-schema-part-2-1-2024-07-21.mdx @@ -12,6 +12,8 @@ hide_table_of_contents: true slug: graphql-schema-part-2-1 --- + + import Quiz from "../src/components/quiz/quiz.tsx" ## What Do You Already Know? 🧠💫 @@ -74,8 +76,6 @@ import Quiz from "../src/components/quiz/quiz.tsx" In our [previous post](/blog/graphql-schema), we learned scalable GraphQL schema is critical for building production-ready APIs that can evolve with your application's needs. - - In this post, we will dive deeper into how to **continuously** evolve your schema to meet your application's changing requirements without hard-coded versioning. ## Adding Without Breaking: The Art of Additive Changes diff --git a/blog/graphql-schema-part-2-2-2024-07-22.mdx b/blog/graphql-schema-part-2-2-2024-07-22.mdx index 6a95b214b6..227bd6944d 100644 --- a/blog/graphql-schema-part-2-2-2024-07-22.mdx +++ b/blog/graphql-schema-part-2-2-2024-07-22.mdx @@ -12,6 +12,8 @@ hide_table_of_contents: true slug: graphql-schema-part-2-2 --- + + import Quiz from "../src/components/quiz/quiz.tsx" ## What Do You Already Know? 🧠💫 @@ -67,8 +69,6 @@ import Quiz from "../src/components/quiz/quiz.tsx" ]} /> - - ## Modifying Without Breaking: Navigating the Modification Minefield In our [previous post](/blog/graphql-schema-part-2-1), we explored how to make additive changes to your GraphQL schema without causing disruptions. Now, we'll dive into the tricky territory of modifying existing schema elements. diff --git a/blog/graphql-schema-part-2-3-2024-07-23.mdx b/blog/graphql-schema-part-2-3-2024-07-23.mdx index 4832d9e7f4..faff1fb206 100644 --- a/blog/graphql-schema-part-2-3-2024-07-23.mdx +++ b/blog/graphql-schema-part-2-3-2024-07-23.mdx @@ -12,6 +12,8 @@ hide_table_of_contents: true slug: graphql-schema-part-2-3 --- + + import Quiz from "../src/components/quiz/quiz.tsx" ## What Do You Already Know? 🧠💫 @@ -87,7 +89,6 @@ import Quiz from "../src/components/quiz/quiz.tsx" }, ]} /> - ## Removing Without Breaking: The Subtraction Subterfuge diff --git a/blog/graphql-vs-openapi-part-1-2024-07-29.md b/blog/graphql-vs-openapi-part-1-2024-07-29.md index cb48cdd402..0555ab8f07 100644 --- a/blog/graphql-vs-openapi-part-1-2024-07-29.md +++ b/blog/graphql-vs-openapi-part-1-2024-07-29.md @@ -13,7 +13,6 @@ authors: image_url: https://avatars.githubusercontent.com/u/68141773?v=4 --- -![OpenAPI vs GraphQL](../static/images/blog/openapi-vs-graphql-part1.png) In today's ever-evolving technological landscape, APIs play a crucial role in enabling software systems to communicate with each other. Among the myriad of API specifications available, GraphQL and OpenAPI stand out as prominent choices, each offering unique advantages and addressing different needs. diff --git a/blog/graphql-vs-openapi-part-2-2024-07-30.md b/blog/graphql-vs-openapi-part-2-2024-07-30.md index 13f4f2511f..3c581894dc 100644 --- a/blog/graphql-vs-openapi-part-2-2024-07-30.md +++ b/blog/graphql-vs-openapi-part-2-2024-07-30.md @@ -13,7 +13,6 @@ authors: image_url: https://avatars.githubusercontent.com/u/68141773?v=4 --- -![OpenAPI vs GraphQL](../static/images/blog/openapi-vs-graphql-part2.png) Welcome back to our API comparison series! In [Part 1](/blog/graphql-vs-openapi-part-1), we covered the fundamentals of GraphQL and OpenAPI, focusing on their core concepts, type systems, and schema definitions. Now, in Part 2, we will dive deeper into the performance, flexibility, and ecosystems of these API specifications. diff --git a/blog/graphql-vs-openapi-part-3-2024-07-31.md b/blog/graphql-vs-openapi-part-3-2024-07-31.md index 7062d838fc..453fc5aab8 100644 --- a/blog/graphql-vs-openapi-part-3-2024-07-31.md +++ b/blog/graphql-vs-openapi-part-3-2024-07-31.md @@ -13,8 +13,6 @@ authors: image_url: https://avatars.githubusercontent.com/u/68141773?v=4 --- -![OpenAPI vs GraphQL](../static/images/blog/openapi-vs-graphql-part3.png) - Welcome to Part 3 of our API comparison series! So far, we've discussed the basics and compared the performance and flexibility of GraphQL and OpenAPI. In this installment, we will focus on exploring their security features, tooling support, and future prospects. diff --git a/blog/graphql-vs-rest-vs-grpc-2024-03-30.md b/blog/graphql-vs-rest-vs-grpc-2024-03-30.md index e5c71afdcc..3487d236e7 100644 --- a/blog/graphql-vs-rest-vs-grpc-2024-03-30.md +++ b/blog/graphql-vs-rest-vs-grpc-2024-03-30.md @@ -12,10 +12,6 @@ slug: graphql-vs-rest-vs-grpc canonical_url: https://tailcall.hashnode.dev/graphql-vs-rest-vs-grpc --- -![Cover Image for GraphQL vs REST vs gRPC - an unfair comparison](../static/images/blog/gql-vs-rest-vs-grpc-cover.png) - - - GraphQL vs REST vs gRPC - an unfair comparison @@ -23,6 +19,8 @@ canonical_url: https://tailcall.hashnode.dev/graphql-vs-rest-vs-grpc Since its inception, GraphQL has steadily gained popularity, often finding itself at the center of comparisons with other data query and manipulation languages such as REST and gRPC. The internet is replete with articles debating the merits and demerits of each, with some even questioning the viability of GraphQL. However, this discourse misses a crucial point: the unique strengths of GraphQL. This article aims to illuminate the distinct advantages GraphQL offers, particularly in addressing a common but complex challenge known as impedance mismatch. + + Impedance mismatch refers to the discordance between the capabilities of an existing API and the ideal features required for a specific use case. From the perspective of a platform engineer, the goal is to develop APIs that cater to a broad range of needs. Yet, crafting a unique API for every conceivable requirement is neither practical nor efficient. Consequently, engineers often end up creating generalized APIs. However, as a consumer, you might find these APIs lacking in some respects while being superfluous in others. Furthermore, as your needs evolve, so does your notion of the ideal API, exacerbating this mismatch. Herein lies the brilliance of GraphQL: it offers a framework for structuring data exposure and queries that significantly mitigates this issue. The GraphQL specification introduces the concept of viewing data as a graph composed of nodes, which represent domain entities for a business, interconnected by relationships that define their interactions. For instance, in the development of a social network, a user entity might have the ability to create a post, which in turn could receive comments, illustrating the interconnected nature of data entities. diff --git a/blog/graphql-vue-clients-2024-08-01.md b/blog/graphql-vue-clients-2024-08-01.md index 41d5c00ddd..a36f1a14ce 100644 --- a/blog/graphql-vue-clients-2024-08-01.md +++ b/blog/graphql-vue-clients-2024-08-01.md @@ -10,11 +10,9 @@ title: "GraphQL in Vue: 5 Best Approaches for Data Fetching" description: "Unleash the power of GraphQL in your Vue applications! Explore the top 5 methods for seamless data fetching, including in-depth comparisons and error handling strategies." sidebar_label: "GraphQL in Vue" slug: graphql-vue-client -image: /image/blog/vue-with-graphql.png +image: /images/blog/vue-with-graphql.png --- -![Cover Image for Vue in GraphQL](../static/images/blog/vue-with-graphql.png) - Are you tired of wrestling with complex data fetching logic in your Vue applications? If you've ever felt like you're battling an octopus to retrieve the data you need, then GraphQL is here to be your data fetching hero! diff --git a/blog/grpc-vs-graphql-2024-07-26.mdx b/blog/grpc-vs-graphql-2024-07-26.mdx index 960a2bba6f..b8fa088039 100644 --- a/blog/grpc-vs-graphql-2024-07-26.mdx +++ b/blog/grpc-vs-graphql-2024-07-26.mdx @@ -12,8 +12,6 @@ hide_table_of_contents: true slug: graphql-vs-grpc --- -![banner](/images/graphql/graphql-vs-grpc.png) - While REST has been the go-to for API development, gRPC and GraphQL are stepping in as game-changing contenders. diff --git a/blog/guide-on-graphiql-2024-07-24.md b/blog/guide-on-graphiql-2024-07-24.md index a82d363605..0d9229cd8f 100644 --- a/blog/guide-on-graphiql-2024-07-24.md +++ b/blog/guide-on-graphiql-2024-07-24.md @@ -13,8 +13,6 @@ slug: exploring-graphiql tags: [GraphQL, GraphiQL, IDE, debugging] --- -![Cover image for Exploring GraphiQL](../static/images/graphql/graphiql-cover.png) - Meet GraphiQL, a true life-saver for testing, debugging and having fun with your graphQL server. diff --git a/blog/no-code-graphql-2024-05-30.md b/blog/no-code-graphql-2024-05-30.md index f9f6b2cbbe..6caf415910 100644 --- a/blog/no-code-graphql-2024-05-30.md +++ b/blog/no-code-graphql-2024-05-30.md @@ -14,7 +14,6 @@ slug: writing-a-graphql-backend-by-hand-is-long-gone canonical_url: https://tailcall.hashnode.dev/writing-a-graphql-backend-by-hand-is-long-gone --- -![Cover Image for Writing a GraphQL Backend by Hand is Long Gone](../static/images/blog/no-code-cover.png) Building a GraphQL backend by hand might seem like a noble pursuit, but the landscape of API development is evolving rapidly, and so are the challenges that come with it. Today, the process is often fraught with complexity, performance bottlenecks, security vulnerabilities, and reliability issues. Yet again, we had a developer expressing [frustration](https://bessey.dev/blog/2024/05/24/why-im-over-graphql/) about the issues with GraphQL and the reasons for leaving our mighty ship. I wish to dive deeper into these challenges and explore why the future points towards automated, high-performance solutions. diff --git a/blog/tailcall-n+1-working-2024-08-04.md b/blog/tailcall-n+1-working-2024-08-04.md index 4399c3af9c..2ee7703f97 100644 --- a/blog/tailcall-n+1-working-2024-08-04.md +++ b/blog/tailcall-n+1-working-2024-08-04.md @@ -10,7 +10,6 @@ slug: tailcall-n+1-identification-algorithm image: /images/blog/n+1-identification-cover.png --- -![Cover image for N+1 Identification in GraphQL](../static/images/blog/n+1-identification-cover.png) As a developer working with GraphQL, you're likely familiar with the concept of N+1 issues. If not, you're in for a treat - check out our [N+1 guide!](/docs/graphql-n-plus-one-problem-solved-tailcall) diff --git a/blog/what-is-grpc-2024-07-13.mdx b/blog/what-is-grpc-2024-07-13.mdx index e02dd33af3..58a0505e3a 100644 --- a/blog/what-is-grpc-2024-07-13.mdx +++ b/blog/what-is-grpc-2024-07-13.mdx @@ -12,8 +12,6 @@ hide_table_of_contents: true slug: what-is-grpc --- -![gRPC Logo](/images/docs/grpc_logo.png) - gRPC is an open-source RPC (Remote Procedure Call) framework initially developed by Google. It enables efficient communication between services across different environments, utilizing a binary serialization format called Protocol Buffers (Protobuf) over HTTP/2. diff --git a/src/theme/BlogPostItem/Container/index.tsx b/src/theme/BlogPostItem/Container/index.tsx new file mode 100644 index 0000000000..ff45cfa38c --- /dev/null +++ b/src/theme/BlogPostItem/Container/index.tsx @@ -0,0 +1,6 @@ +import React from "react" +import type {Props} from "@theme/BlogPostItem/Container" + +export default function BlogPostItemContainer({children, className}: Props): JSX.Element { + return
{children}
+} diff --git a/src/theme/BlogPostItem/Content/index.tsx b/src/theme/BlogPostItem/Content/index.tsx new file mode 100644 index 0000000000..79264c3ca1 --- /dev/null +++ b/src/theme/BlogPostItem/Content/index.tsx @@ -0,0 +1,19 @@ +import React from "react" +import clsx from "clsx" +import {blogPostContainerID} from "@docusaurus/utils-common" +import {useBlogPost} from "@docusaurus/theme-common/internal" +import MDXContent from "@theme/MDXContent" +import type {Props} from "@theme/BlogPostItem/Content" + +export default function BlogPostItemContent({children, className}: Props): JSX.Element { + const {isBlogPostPage} = useBlogPost() + return ( +
+ {children} +
+ ) +} diff --git a/src/theme/BlogPostItem/Footer/ReadMoreLink/index.tsx b/src/theme/BlogPostItem/Footer/ReadMoreLink/index.tsx new file mode 100644 index 0000000000..611dc8da0d --- /dev/null +++ b/src/theme/BlogPostItem/Footer/ReadMoreLink/index.tsx @@ -0,0 +1,36 @@ +import React from "react" +import Translate, {translate} from "@docusaurus/Translate" +import Link from "@docusaurus/Link" +import type {Props} from "@theme/BlogPostItem/Footer/ReadMoreLink" + +function ReadMoreLabel() { + return ( + + + Read More + + + ) +} + +export default function BlogPostItemFooterReadMoreLink(props: Props): JSX.Element { + const {blogPostTitle, ...linkProps} = props + return ( + + + + ) +} diff --git a/src/theme/BlogPostItem/Footer/index.tsx b/src/theme/BlogPostItem/Footer/index.tsx new file mode 100644 index 0000000000..aaf19fa7b8 --- /dev/null +++ b/src/theme/BlogPostItem/Footer/index.tsx @@ -0,0 +1,69 @@ +import React from "react" +import clsx from "clsx" +import {useBlogPost} from "@docusaurus/theme-common/internal" +import {ThemeClassNames} from "@docusaurus/theme-common" +import EditMetaRow from "@theme/EditMetaRow" +import TagsListInline from "@theme/TagsListInline" +import ReadMoreLink from "@theme/BlogPostItem/Footer/ReadMoreLink" + +export default function BlogPostItemFooter(): JSX.Element | null { + const {metadata, isBlogPostPage} = useBlogPost() + const {tags, title, editUrl, hasTruncateMarker, lastUpdatedBy, lastUpdatedAt} = metadata + + // A post is truncated if it's in the "list view" and it has a truncate marker + const truncatedPost = !isBlogPostPage && hasTruncateMarker + + const tagsExists = tags.length > 0 + + const renderFooter = tagsExists || truncatedPost || editUrl + + if (!renderFooter) { + return null + } + + // BlogPost footer - details view + if (isBlogPostPage) { + const canDisplayEditMetaRow = !!(editUrl || lastUpdatedAt || lastUpdatedBy) + + return ( +
+ {tagsExists && ( +
+
+ +
+
+ )} + {canDisplayEditMetaRow && ( + + )} +
+ ) + } + // BlogPost footer - list view + else { + return ( +
+ {tagsExists && ( +
+ +
+ )} + {truncatedPost && ( +
+ +
+ )} +
+ ) + } +} diff --git a/src/theme/BlogPostItem/Header/Author/index.tsx b/src/theme/BlogPostItem/Header/Author/index.tsx new file mode 100644 index 0000000000..337a27873e --- /dev/null +++ b/src/theme/BlogPostItem/Header/Author/index.tsx @@ -0,0 +1,37 @@ +import React from "react" +import clsx from "clsx" +import Link, {type Props as LinkProps} from "@docusaurus/Link" + +import type {Props} from "@theme/BlogPostItem/Header/Author" + +function MaybeLink(props: LinkProps): JSX.Element { + if (props.href) { + return + } + return <>{props.children} +} + +export default function BlogPostItemHeaderAuthor({author, className}: Props): JSX.Element { + const {name, title, url, imageURL, email} = author + const link = url || (email && `mailto:${email}`) || undefined + return ( +
+ {imageURL && ( + + {name} + + )} + + {name && ( +
+
+ + {name} + +
+ {title && {title}} +
+ )} +
+ ) +} diff --git a/src/theme/BlogPostItem/Header/Authors/index.tsx b/src/theme/BlogPostItem/Header/Authors/index.tsx new file mode 100644 index 0000000000..4ed365081c --- /dev/null +++ b/src/theme/BlogPostItem/Header/Authors/index.tsx @@ -0,0 +1,37 @@ +import React from "react" +import clsx from "clsx" +import {useBlogPost} from "@docusaurus/theme-common/internal" +import BlogPostItemHeaderAuthor from "@theme/BlogPostItem/Header/Author" +import type {Props} from "@theme/BlogPostItem/Header/Authors" +import styles from "./styles.module.css" + +// Component responsible for the authors layout +export default function BlogPostItemHeaderAuthors({className}: Props): JSX.Element | null { + const { + metadata: {authors}, + assets, + } = useBlogPost() + const authorsCount = authors.length + if (authorsCount === 0) { + return null + } + const imageOnly = authors.every(({name}) => !name) + return ( +
+ {authors.map((author, idx) => ( +
+ +
+ ))} +
+ ) +} diff --git a/src/theme/BlogPostItem/Header/Authors/styles.module.css b/src/theme/BlogPostItem/Header/Authors/styles.module.css new file mode 100644 index 0000000000..c5aac4d9af --- /dev/null +++ b/src/theme/BlogPostItem/Header/Authors/styles.module.css @@ -0,0 +1,14 @@ +.authorCol { + max-width: inherit !important; + flex-grow: 1 !important; +} + +.imageOnlyAuthorRow { + display: flex; + flex-flow: row wrap; +} + +.imageOnlyAuthorCol { + margin-left: 0.3rem; + margin-right: 0.3rem; +} diff --git a/src/theme/BlogPostItem/Header/Info/index.tsx b/src/theme/BlogPostItem/Header/Info/index.tsx new file mode 100644 index 0000000000..8b796d34fe --- /dev/null +++ b/src/theme/BlogPostItem/Header/Info/index.tsx @@ -0,0 +1,67 @@ +import React from "react" +import clsx from "clsx" +import {translate} from "@docusaurus/Translate" +import {usePluralForm} from "@docusaurus/theme-common" +import {useBlogPost, useDateTimeFormat} from "@docusaurus/theme-common/internal" +import type {Props} from "@theme/BlogPostItem/Header/Info" + +import styles from "./styles.module.css" + +// Very simple pluralization: probably good enough for now +function useReadingTimePlural() { + const {selectMessage} = usePluralForm() + return (readingTimeFloat: number) => { + const readingTime = Math.ceil(readingTimeFloat) + return selectMessage( + readingTime, + translate( + { + id: "theme.blog.post.readingTime.plurals", + description: + 'Pluralized label for "{readingTime} min read". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)', + message: "One min read|{readingTime} min read", + }, + {readingTime}, + ), + ) + } +} + +function ReadingTime({readingTime}: {readingTime: number}) { + const readingTimePlural = useReadingTimePlural() + return <>{readingTimePlural(readingTime)} +} + +function DateTime({date, formattedDate}: {date: string; formattedDate: string}) { + return +} + +function Spacer() { + return <>{" · "} +} + +export default function BlogPostItemHeaderInfo({className}: Props): JSX.Element { + const {metadata} = useBlogPost() + const {date, readingTime} = metadata + + const dateTimeFormat = useDateTimeFormat({ + day: "numeric", + month: "long", + year: "numeric", + timeZone: "UTC", + }) + + const formatDate = (blogDate: string) => dateTimeFormat.format(new Date(blogDate)) + + return ( +
+ + {typeof readingTime !== "undefined" && ( + <> + + + + )} +
+ ) +} diff --git a/src/theme/BlogPostItem/Header/Info/styles.module.css b/src/theme/BlogPostItem/Header/Info/styles.module.css new file mode 100644 index 0000000000..27d569e089 --- /dev/null +++ b/src/theme/BlogPostItem/Header/Info/styles.module.css @@ -0,0 +1,3 @@ +.container { + font-size: 0.9rem; +} diff --git a/src/theme/BlogPostItem/Header/Title/index.tsx b/src/theme/BlogPostItem/Header/Title/index.tsx new file mode 100644 index 0000000000..ac0d8747d9 --- /dev/null +++ b/src/theme/BlogPostItem/Header/Title/index.tsx @@ -0,0 +1,18 @@ +import React from "react" +import clsx from "clsx" +import Link from "@docusaurus/Link" +import {useBlogPost} from "@docusaurus/theme-common/internal" +import type {Props} from "@theme/BlogPostItem/Header/Title" + +import styles from "./styles.module.css" + +export default function BlogPostItemHeaderTitle({className}: Props): JSX.Element { + const {metadata, isBlogPostPage} = useBlogPost() + const {permalink, title} = metadata + const TitleHeading = isBlogPostPage ? "h1" : "h2" + return ( + + {isBlogPostPage ? title : {title}} + + ) +} diff --git a/src/theme/BlogPostItem/Header/Title/styles.module.css b/src/theme/BlogPostItem/Header/Title/styles.module.css new file mode 100644 index 0000000000..04a8d6adcb --- /dev/null +++ b/src/theme/BlogPostItem/Header/Title/styles.module.css @@ -0,0 +1,12 @@ +.title { + font-size: 3rem; +} + +/** + Blog post title should be smaller on smaller devices +**/ +@media (max-width: 576px) { + .title { + font-size: 2rem; + } +} diff --git a/src/theme/BlogPostItem/Header/index.tsx b/src/theme/BlogPostItem/Header/index.tsx new file mode 100644 index 0000000000..334a2ae388 --- /dev/null +++ b/src/theme/BlogPostItem/Header/index.tsx @@ -0,0 +1,14 @@ +import React from "react" +import BlogPostItemHeaderTitle from "@theme/BlogPostItem/Header/Title" +import BlogPostItemHeaderInfo from "@theme/BlogPostItem/Header/Info" +import BlogPostItemHeaderAuthors from "@theme/BlogPostItem/Header/Authors" + +export default function BlogPostItemHeader(): JSX.Element { + return ( +
+ + + +
+ ) +} diff --git a/src/theme/BlogPostItem/index.tsx b/src/theme/BlogPostItem/index.tsx new file mode 100644 index 0000000000..c37e8c847b --- /dev/null +++ b/src/theme/BlogPostItem/index.tsx @@ -0,0 +1,26 @@ +import React from "react" +import clsx from "clsx" +import {useBlogPost} from "@docusaurus/theme-common/internal" +import BlogPostItemContainer from "@theme/BlogPostItem/Container" +import BlogPostItemHeader from "@theme/BlogPostItem/Header" +import BlogPostItemContent from "@theme/BlogPostItem/Content" +import BlogPostItemFooter from "@theme/BlogPostItem/Footer" +import type {Props} from "@theme/BlogPostItem" +// apply a bottom margin in list view +function useContainerClassName() { + const {isBlogPostPage} = useBlogPost() + return !isBlogPostPage ? "margin-bottom--xl" : undefined +} + +export default function BlogPostItem({children, className}: Props): JSX.Element { + const containerClassName = useContainerClassName() + const {frontMatter} = useBlogPost() + return ( + + + {`Cover + {children} + + + ) +}