diff --git a/.github/ISSUE_TEMPLATE/release_post.md b/.github/ISSUE_TEMPLATE/release_post.md index 612d97197..741af5941 100644 --- a/.github/ISSUE_TEMPLATE/release_post.md +++ b/.github/ISSUE_TEMPLATE/release_post.md @@ -4,7 +4,7 @@ title: Eclipse Temurin 8uxxx, 11.x.x, 17.x.x and 21.x.x Available date: "" author: pmc about: Create a new release post -description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8uxxx, 11.x.x, 17.x.x and 21.x.x. As always, all binaries are thoroughly tested and available free of charge without usage restrictions on a wide range of platforms. +description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8uxxx, 11.x.x, 17.x.x and 21.x.x. tags: - temurin - announcement diff --git a/content/blog/a-month-after-eclipsecon-adoptium-community-day-summary-and-more/index.md b/content/blog/a-month-after-eclipsecon-adoptium-community-day-summary-and-more/index.md index 3b39179f1..08c0067d2 100644 --- a/content/blog/a-month-after-eclipsecon-adoptium-community-day-summary-and-more/index.md +++ b/content/blog/a-month-after-eclipsecon-adoptium-community-day-summary-and-more/index.md @@ -3,6 +3,7 @@ title: A month after EclipseCon - Adoptium Community day summary, and more. date: "2022-11-24T12:00:00+00:00" author: carmendelgado description: Our Community Manager gives us a summary of Eclipse Adoptium activities around the EclipseCon event. +featuredImage: "./community-day.jpg" tags: - "adoptium" --- diff --git a/content/blog/eclipse-temurin-11020.1-and-1708.1/index.md b/content/blog/eclipse-temurin-11020.1-and-1708.1/index.md index f28cee829..1400d89dc 100644 --- a/content/blog/eclipse-temurin-11020.1-and-1708.1/index.md +++ b/content/blog/eclipse-temurin-11020.1-and-1708.1/index.md @@ -2,7 +2,7 @@ title: Eclipse Temurin 11.0.20.1, 17.0.8.1 now available date: "2023-08-30T12:00:00+00:00" author: pmc -description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 11.0.20.1 and 17.0.8.1. As always, all binaries are thoroughly tested and available free of charge without usage restrictions on a wide range of platforms. +description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 11.0.20.1 and 17.0.8.1. tags: - temurin - announcement diff --git a/content/blog/eclipse-temurin-8u312-11013-and-1701-available/index.md b/content/blog/eclipse-temurin-8u312-11013-and-1701-available/index.md index eb567b695..ade2720ec 100644 --- a/content/blog/eclipse-temurin-8u312-11013-and-1701-available/index.md +++ b/content/blog/eclipse-temurin-8u312-11013-and-1701-available/index.md @@ -2,7 +2,7 @@ title: Eclipse Temurin 8u312, 11.0.13, and 17.0.1 Available date: "2021-11-05T12:00:00+00:00" author: georgeadams -description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u312, 11.0.13, and 17.0.1. As always, all binaries are thoroughly tested and available free of charge without usage restrictions on a wide range of platforms. +description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u312, 11.0.13, and 17.0.1. tags: - temurin - announcement diff --git a/content/blog/eclipse-temurin-8u342-11016-1704-and-1802-available/index.md b/content/blog/eclipse-temurin-8u342-11016-1704-and-1802-available/index.md index 8d1d863d9..d2a583279 100644 --- a/content/blog/eclipse-temurin-8u342-11016-1704-and-1802-available/index.md +++ b/content/blog/eclipse-temurin-8u342-11016-1704-and-1802-available/index.md @@ -2,7 +2,7 @@ title: Eclipse Temurin 8u342, 11.0.16, 17.0.4 and 18.0.2 Available date: "2022-08-03T12:00:00+00:00" author: georgeadams -description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u342, 11.0.16, 17.0.4 and 18.0.2. As always, all binaries are thoroughly tested and available free of charge without usage restrictions on a wide range of platforms. +description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u342, 11.0.16, 17.0.4 and 18.0.2. tags: - temurin - announcement diff --git a/content/blog/eclipse-temurin-8u362-11018-1706-and-1902-available/index.md b/content/blog/eclipse-temurin-8u362-11018-1706-and-1902-available/index.md index e6539bd7c..2c61b1535 100644 --- a/content/blog/eclipse-temurin-8u362-11018-1706-and-1902-available/index.md +++ b/content/blog/eclipse-temurin-8u362-11018-1706-and-1902-available/index.md @@ -2,7 +2,7 @@ title: Eclipse Temurin 8u362, 11.0.18, 17.0.6 and 19.0.2 Available date: "2023-01-27T12:00:00+00:00" author: pmc -description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u362, 11.0.18, 17.0.6 and 19.0.2. As always, all binaries are thoroughly tested and available free of charge without usage restrictions on a wide range of platforms. +description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u362, 11.0.18, 17.0.6 and 19.0.2. tags: - temurin - announcement diff --git a/content/blog/eclipse-temurin-8u372-11019-1707-and-2001-available/index.md b/content/blog/eclipse-temurin-8u372-11019-1707-and-2001-available/index.md index 3a52d68fb..a41d5d108 100644 --- a/content/blog/eclipse-temurin-8u372-11019-1707-and-2001-available/index.md +++ b/content/blog/eclipse-temurin-8u372-11019-1707-and-2001-available/index.md @@ -2,7 +2,7 @@ title: Eclipse Temurin 8u372, 11.0.19, 17.0.7 and 20.0.1 Available date: "2023-05-04T12:00:00+00:00" author: pmc -description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u372, 11.0.19, 17.0.7 and 20.0.1. As always, all binaries are thoroughly tested and available free of charge without usage restrictions on a wide range of platforms. +description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u372, 11.0.19, 17.0.7 and 20.0.1. tags: - temurin - announcement diff --git a/content/blog/eclipse-temurin-8u382-11020-1708-and-2002-available/index.md b/content/blog/eclipse-temurin-8u382-11020-1708-and-2002-available/index.md index 481f104f5..6ff37692e 100644 --- a/content/blog/eclipse-temurin-8u382-11020-1708-and-2002-available/index.md +++ b/content/blog/eclipse-temurin-8u382-11020-1708-and-2002-available/index.md @@ -2,7 +2,7 @@ title: Eclipse Temurin 8u382, 11.0.20, 17.0.8 and 20.0.2 Available date: "2023-08-04T12:00:00+00:00" author: pmc -description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u382, 11.0.20, 17.0.8 and 20.0.2. As always, all binaries are thoroughly tested and available free of charge without usage restrictions on a wide range of platforms. +description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u382, 11.0.20, 17.0.8 and 20.0.2. tags: - temurin - announcement diff --git a/content/blog/eclipse-temurin-8u392-11021-1709-and-2101-available/index.md b/content/blog/eclipse-temurin-8u392-11021-1709-and-2101-available/index.md index 199e5b48d..545b6bd23 100644 --- a/content/blog/eclipse-temurin-8u392-11021-1709-and-2101-available/index.md +++ b/content/blog/eclipse-temurin-8u392-11021-1709-and-2101-available/index.md @@ -2,7 +2,7 @@ title: Eclipse Temurin 8u392, 11.0.21, 17.0.9 and 21.0.1 Available date: "2023-11-21T12:00:00+00:00" author: pmc -description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u392, 11.0.21, 17.0.9 and 21.0.1. As always, all binaries are thoroughly tested and available free of charge without usage restrictions on a wide range of platforms. +description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u392, 11.0.21, 17.0.9 and 21.0.1. tags: - temurin - announcement diff --git a/content/blog/eclipse-temurin-8u402-11022-1710-and-2102-available/index.md b/content/blog/eclipse-temurin-8u402-11022-1710-and-2102-available/index.md index 31f009e08..34c081d62 100644 --- a/content/blog/eclipse-temurin-8u402-11022-1710-and-2102-available/index.md +++ b/content/blog/eclipse-temurin-8u402-11022-1710-and-2102-available/index.md @@ -2,7 +2,7 @@ title: Eclipse Temurin 8u402, 11.0.22, 17.0.10 and 21.0.2 Available date: "2024-01-26" author: pmc -description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u402, 11.0.22, 17.0.10 and 21.0.2. As always, all binaries are thoroughly tested and available free of charge without usage restrictions on a wide range of platforms. +description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u402, 11.0.22, 17.0.10 and 21.0.2. tags: - temurin - announcement diff --git a/content/blog/eclipse-temurin-8u412-11023-1711-2102-2201-available/index.md b/content/blog/eclipse-temurin-8u412-11023-1711-2102-2201-available/index.md index af0b379d9..cc9f07556 100644 --- a/content/blog/eclipse-temurin-8u412-11023-1711-2102-2201-available/index.md +++ b/content/blog/eclipse-temurin-8u412-11023-1711-2102-2201-available/index.md @@ -2,7 +2,7 @@ title: Eclipse Temurin 8u412, 11.0.23, 17.0.11, 21.0.3 and 22.0.1 Available date: "2024-04-25" author: pmc -description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u412, 11.0.23, 17.0.11 21.0.3 and 22.0.1 - our biggest release set so far. As always, all binaries are thoroughly tested and available free of charge without usage restrictions on a wide range of platforms. +description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u412, 11.0.23, 17.0.11 21.0.3 and 22.0.1 - our biggest release set so far. tags: - temurin - announcement diff --git a/content/blog/eclipse-temurin-8u422-11024-1712-2104-2202-available/index.md b/content/blog/eclipse-temurin-8u422-11024-1712-2104-2202-available/index.md index f67696594..fde2ef215 100644 --- a/content/blog/eclipse-temurin-8u422-11024-1712-2104-2202-available/index.md +++ b/content/blog/eclipse-temurin-8u422-11024-1712-2104-2202-available/index.md @@ -2,7 +2,7 @@ title: Eclipse Temurin 8u422, 11.0.24, 17.0.12, 21.0.4 and 22.0.2 Available date: "2024-07-26" author: pmc -description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u422, 11.0.24, 17.0.12 21.0.4 and 22.0.2 - surpassing April's release as the largest set of platforms published. As always, all binaries are thoroughly tested and available free of charge without usage restrictions on a wide range of platforms. +description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u422, 11.0.24, 17.0.12 21.0.4 and 22.0.2 - surpassing April's release as the largest set of platforms published. tags: - temurin - announcement diff --git a/content/blog/eclipse-temurin-8u432-11025-1713-2105-2301-available/index.md b/content/blog/eclipse-temurin-8u432-11025-1713-2105-2301-available/index.md index 90b1dfa43..b361cc1f0 100644 --- a/content/blog/eclipse-temurin-8u432-11025-1713-2105-2301-available/index.md +++ b/content/blog/eclipse-temurin-8u432-11025-1713-2105-2301-available/index.md @@ -2,7 +2,7 @@ title: Eclipse Temurin 8u432, 11.0.25, 17.0.13, 21.0.5 and 23.0.1 Available date: "2024-11-04" author: pmc -description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u432, 11.0.25, 17.0.13, 21.0.5 and 23.0.1. As always, all binaries are thoroughly tested and available free of charge without usage restrictions on a wide range of platforms. +description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 8u432, 11.0.25, 17.0.13, 21.0.5 and 23.0.1. tags: - temurin - announcement diff --git a/content/blog/external_audit/index.md b/content/blog/external_audit/index.md index 122c58659..979c692a3 100644 --- a/content/blog/external_audit/index.md +++ b/content/blog/external_audit/index.md @@ -3,6 +3,9 @@ title: External audit of Temurin build and distribution processes date: "2024-06-17T17:00:00+00:00" author: pmc description: + Last year, the Eclipse Foundation engaged the Open Source Technology Improvement Fund to + perform an independent audit of the build and distribution processes for + Eclipse Temurin. tags: - temurin - security diff --git a/content/blog/march-2024-jdk22-release/index.md b/content/blog/march-2024-jdk22-release/index.md index 69a50cefc..80b1c2fd7 100644 --- a/content/blog/march-2024-jdk22-release/index.md +++ b/content/blog/march-2024-jdk22-release/index.md @@ -2,7 +2,7 @@ title: Eclipse Temurin 22 Available date: "2024-03-28" author: pmc -description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 22.0.0+36. As always, all binaries are thoroughly tested and available free of charge without usage restrictions on a wide range of platforms. +description: Adoptium is happy to announce the immediate availability of Eclipse Temurin 22.0.0+36. tags: - temurin - announcement diff --git a/gatsby-node.ts b/gatsby-node.ts index b9e233802..c4c426194 100644 --- a/gatsby-node.ts +++ b/gatsby-node.ts @@ -10,6 +10,7 @@ import { createFilePath } from "gatsby-source-filesystem" import locales from "./locales/i18n" import authors from "./src/json/authors.json" import type { GatsbyNode, Node } from "gatsby" +import { generateFeaturedImage } from './src/util/generateFeaturedImage'; import { localizedSlug, @@ -35,6 +36,9 @@ interface AdoptiumData { interface MdxNode extends Node { frontmatter: { date: string + featuredImage: string + title: string + description: string } fields: { slug: string @@ -313,6 +317,27 @@ export const onCreateNode: GatsbyNode["onCreateNode"] = async ({ const date = new Date(mdxNode.frontmatter.date) const year = date.getFullYear() const zeroPaddedMonth = `${date.getMonth() + 1}`.padStart(2, "0") + const title: string = mdxNode.frontmatter.title; + const description: string = mdxNode.frontmatter.description; + const featuredImage: string | undefined = mdxNode.frontmatter.featuredImage; + + // If no featured image is provided and both title and subtitle exist: + if (!featuredImage && title && description) { + const outputFileName = "banner.png"; + const outputDir = path.join(`static/images/blog/${slug}`); + const outputPath = path.join(outputDir, outputFileName); + + try { + await generateFeaturedImage(title, description, outputPath); + createNodeField({ + node, + name: 'generatedFeaturedImage', + value: `/images/blog/${slug}/${outputFileName}`, + }); + } catch (error) { + console.error(`Error generating featured image for ${title}: ${error}`); + } + } createNodeField({ name: "slug", @@ -322,7 +347,7 @@ export const onCreateNode: GatsbyNode["onCreateNode"] = async ({ createNodeField({ name: "postPath", node, - value: `/blog/${year}/${zeroPaddedMonth}${slug}`, + value: `/news/${year}/${zeroPaddedMonth}${slug}`, }) } } @@ -461,7 +486,7 @@ export const createPages: GatsbyNode["createPages"] = async ({ }, ) - // Query all blog posts by author to determine the number of pages needed + // Query all news posts by author to determine the number of pages needed const authorPosts = await graphql<{ allMdx: { totalCount: number @@ -497,8 +522,8 @@ export const createPages: GatsbyNode["createPages"] = async ({ createPage({ path: index === 0 - ? `/blog/author/${author}` - : `/blog/author/${author}/page/${index + 1}`, + ? `/news/author/${author}` + : `/news/author/${author}/page/${index + 1}`, component: authorPage, context: { author, @@ -516,11 +541,11 @@ export const createPages: GatsbyNode["createPages"] = async ({ }) } - // Create blog posts pages. + // Create news posts pages. const tagTemplate = path.resolve("./src/templates/tagPage.tsx") - const blogPost = path.resolve("./src/templates/blogPost.tsx") + const newsPost = path.resolve("./src/templates/newsPost.tsx") - const blogPostResults = await graphql<{ + const newsPostResults = await graphql<{ allMdx: { edges: Array<{ node: { @@ -541,6 +566,7 @@ export const createPages: GatsbyNode["createPages"] = async ({ tagsGroup: { group: Array<{ fieldValue: string + totalCount: number }> } }>(` @@ -565,20 +591,21 @@ export const createPages: GatsbyNode["createPages"] = async ({ tagsGroup: allMdx(limit: 2000) { group(field: { frontmatter: { tags: SELECT } }) { fieldValue + totalCount } } } `) - if (blogPostResults.errors) { - throw blogPostResults.errors + if (newsPostResults.errors) { + throw newsPostResults.errors } - if (!blogPostResults.data) { - throw new Error("Error retrieving blog posts") + if (!newsPostResults.data) { + throw new Error("Error retrieving news posts") } - const posts = blogPostResults.data.allMdx.edges + const posts = newsPostResults.data.allMdx.edges posts.forEach((post, index) => { const previous = index === posts.length - 1 ? null : posts[index + 1].node @@ -586,7 +613,7 @@ export const createPages: GatsbyNode["createPages"] = async ({ createPage({ path: `${post.node.fields.postPath}`, - component: `${blogPost}?__contentFilePath=${post.node.internal.contentFilePath}`, + component: `${newsPost}?__contentFilePath=${post.node.internal.contentFilePath}`, context: { slug: post.node.fields.slug, postPath: `${post.node.fields.postPath}`, @@ -597,38 +624,58 @@ export const createPages: GatsbyNode["createPages"] = async ({ }) // Extract tag data from query - const tags = blogPostResults.data.tagsGroup.group + const tags = newsPostResults.data.tagsGroup.group // Make tag pages tags.forEach(tag => { - createPage({ - path: `/news/tags/${tag.fieldValue}/`, - component: tagTemplate, - context: { - tag: tag.fieldValue, - }, + // Calculate how many pages we need for this tag. + const numTagPages = Math.ceil(tag.totalCount / postsPerPage) + + Array.from({ length: numTagPages }).forEach((_, index) => { + const currentPageNumber = index + 1 + const previousPageNumber = + currentPageNumber === 1 ? null : currentPageNumber - 1 + const nextPageNumber = + currentPageNumber === numTagPages ? null : currentPageNumber + 1 + + createPage({ + // Use a friendly URL for the first page; then add /page/2, etc. + path: + index === 0 + ? `/news/tags/${tag.fieldValue}/` + : `/news/tags/${tag.fieldValue}/page/${index + 1}`, + component: tagTemplate, + context: { + tag: tag.fieldValue, + limit: postsPerPage, + skip: index * postsPerPage, + numTagPages, + currentPageNumber, + previousPageNumber, + nextPageNumber, + }, + }) }) }) const numPages = Math.ceil(posts.length / postsPerPage) Array.from({ length: numPages }).forEach((_, index) => { - const currentPageNumber = index + 1 - const previousPageNumber = - currentPageNumber === 1 ? null : currentPageNumber - 1 - const nextPageNumber = - currentPageNumber === numPages ? null : currentPageNumber + 1 - + const currentPage = index + 1; + const previousPageNumber = currentPage === 1 ? null : currentPage - 1; + const nextPageNumber = currentPage === numPages ? null : currentPage + 1; + createPage({ - path: `/news/page/${index + 1}`, + path: currentPage === 1 ? `/news` : `/news/page/${currentPage}`, component: path.resolve("./src/templates/newsPage.tsx"), context: { limit: postsPerPage, skip: index * postsPerPage, - numPages, - currentPageNumber, + currentPage, + totalPages: numPages, previousPageNumber, nextPageNumber, + baseUrl: "/news", }, - }) - }) + }); + }); } diff --git a/package-lock.json b/package-lock.json index 341427272..0d9323d21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,6 +81,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@testing-library/user-event": "^14.6.1", + "@types/fs-extra": "^11.0.4", "@types/gatsbyjs__reach-router": "^2.0.5", "@types/prismjs": "^1.26.5", "@types/react": "^18.3.13", @@ -92,6 +93,8 @@ "autoprefixer": "^10.4.20", "axios-mock-adapter": "^2.1.0", "babel-preset-gatsby": "^3.14.0", + "canvas": "^3.1.0", + "fs-extra": "^11.3.0", "gatsby-cli": "^5.14.0", "gatsby-plugin-postcss": "^6.14.0", "jest-canvas-mock": "^2.5.2", @@ -6796,6 +6799,16 @@ "resolved": "https://registry.npmjs.org/@types/extend/-/extend-3.0.4.tgz", "integrity": "sha512-ArMouDUTJEz1SQRpFsT2rIw7DeqICFv5aaVzLSIYMYQSLcwcGOfT3VyglQs/p7K3F7fT4zxr0NWxYZIdifD6dA==" }, + "node_modules/@types/fs-extra": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", + "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", + "dev": true, + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, "node_modules/@types/gatsbyjs__reach-router": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/gatsbyjs__reach-router/-/gatsbyjs__reach-router-2.0.5.tgz", @@ -6851,6 +6864,15 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "node_modules/@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/keyv": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", @@ -9225,6 +9247,20 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.1.0.tgz", + "integrity": "sha512-tTj3CqqukVJ9NgSahykNwtGda7V33VLObwrHfzT0vqJXu7J4d4C/7kQQW3fOEGDfZZoILPut5H00gOjyttPGyg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "^18.12.0 || >= 20.9.0" + } + }, "node_modules/capital-case": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", @@ -13173,9 +13209,9 @@ "integrity": "sha512-kSxoARUDn4F2RPXX48UXnaFKwVU7Ivd/6qpzZL29MCDmr9sTvybv4gFCp+qaI4fM9m0z9fgz/yJvi56GAz+BZg==" }, "node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", diff --git a/package.json b/package.json index 08b849704..3ad37f23b 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@testing-library/user-event": "^14.6.1", + "@types/fs-extra": "^11.0.4", "@types/gatsbyjs__reach-router": "^2.0.5", "@types/prismjs": "^1.26.5", "@types/react": "^18.3.13", @@ -88,6 +89,8 @@ "autoprefixer": "^10.4.20", "axios-mock-adapter": "^2.1.0", "babel-preset-gatsby": "^3.14.0", + "canvas": "^3.1.0", + "fs-extra": "^11.3.0", "gatsby-cli": "^5.14.0", "gatsby-plugin-postcss": "^6.14.0", "jest-canvas-mock": "^2.5.2", diff --git a/src/components/Announcements/TabContent.tsx b/src/components/Announcements/TabContent.tsx index da04211ea..c2bc92933 100644 --- a/src/components/Announcements/TabContent.tsx +++ b/src/components/Announcements/TabContent.tsx @@ -1,74 +1,28 @@ import React from "react" -const testResultsText: string = `# Timestamp: Wed Mar 2 10:51:55 2022 UTC -1..168 -ok 1 - MachineInfo_0 - --- - duration_ms: 581 - ... -ok 2 - ClassLoadingTest_5m_0 - --- - duration_ms: 304339 - ... -ok 3 - ClassLoadingTest_5m_1 - --- - duration_ms: 303883 - ... -etc. - ... -ok 168 - MauveMultiThrdLoad_5m_1 - --- - duration_ms: 304296 - ...` +import { Link } from "gatsby-plugin-react-i18next" -const TabContent = () => { +const TabContent = ({ posts }) => { return (
-
-

- 2 weeks ago -

-

- Announcement Title -

-

- Eclipse Temurin offers high-performance, cross-platform, open-source - Java runtime binaries that are enterprise-ready and Java SE TCK-tested - for general use in the Java ecosystem. -

-
-
-
-          {testResultsText}
-        
-
- -
-

- 2 weeks ago -

-

- Announcement Title -

-

- Eclipse Temurin offers high-performance, cross-platform, open-source - Java runtime binaries that are enterprise-ready and Java SE TCK-tested - for general use in the Java ecosystem. -

-
- -
-

- 2 weeks ago -

-

- Announcement Title -

-

- Eclipse Temurin offers high-performance, cross-platform, open-source - Java runtime binaries that are enterprise-ready and Java SE TCK-tested - for general use in the Java ecosystem. -

-
+ {posts.map((post, index) => ( + <> + +
+

+ {Math.floor((new Date().getTime() - new Date(post.node.frontmatter.date).getTime()) / (1000 * 60 * 60 * 24 * 7))} weeks ago +

+ +

+ {post.node.frontmatter.title} +

+

+ {post.node.frontmatter.description} +

+
+ + + + ))}
) } diff --git a/src/components/Announcements/__tests__/__snapshots__/Annnouncements.test.tsx.snap b/src/components/Announcements/__tests__/__snapshots__/Annnouncements.test.tsx.snap index c809b95a5..23cef3778 100644 --- a/src/components/Announcements/__tests__/__snapshots__/Annnouncements.test.tsx.snap +++ b/src/components/Announcements/__tests__/__snapshots__/Annnouncements.test.tsx.snap @@ -53,26 +53,18 @@ exports[`Announcements component > Announcements renders correctly 1`] = ` > - @@ -84,97 +76,81 @@ exports[`Announcements component > Announcements renders correctly 1`] = ` class="overflow-auto h-[88%] scroll-sidebar" >
-
-

- 2 weeks ago -

-

- Announcement Title -

-

- Eclipse Temurin offers high-performance, cross-platform, open-source Java runtime binaries that are enterprise-ready and Java SE TCK-tested for general use in the Java ecosystem. -

-
-
-
+            
- # Timestamp: Wed Mar 2 10:51:55 2022 UTC -1..168 -ok 1 - MachineInfo_0 - --- - duration_ms: 581 - ... -ok 2 - ClassLoadingTest_5m_0 - --- - duration_ms: 304339 - ... -ok 3 - ClassLoadingTest_5m_1 - --- - duration_ms: 303883 - ... -etc. - ... -ok 168 - MauveMultiThrdLoad_5m_1 - --- - duration_ms: 304296 - ... -
-
+

+ 213 + weeks ago +

+

+ Mock Title 1 +

+

+ Mock Description 1 +

+ + -
-

- 2 weeks ago -

-

+
- Announcement Title -

-

- Eclipse Temurin offers high-performance, cross-platform, open-source Java runtime binaries that are enterprise-ready and Java SE TCK-tested for general use in the Java ecosystem. -

-
+

+ 213 + weeks ago +

+

+ Mock Title 2 +

+

+ Mock Description 2 +

+ + -
-

+

- 2 weeks ago -

-

- Announcement Title -

-

- Eclipse Temurin offers high-performance, cross-platform, open-source Java runtime binaries that are enterprise-ready and Java SE TCK-tested for general use in the Java ecosystem. -

-
+

+ 213 + weeks ago +

+

+ Mock Title 3 +

+

+ Mock Description 3 +

+
+ +
diff --git a/src/components/Announcements/index.tsx b/src/components/Announcements/index.tsx index db12cee07..261d787be 100644 --- a/src/components/Announcements/index.tsx +++ b/src/components/Announcements/index.tsx @@ -1,8 +1,35 @@ import React, { useState } from "react" +import { useStaticQuery, graphql } from "gatsby" import Sidebar from "../Common/Sidebar" import TabContent from "./TabContent" const Announcements = ({ handleClose }) => { + const data = useStaticQuery(graphql` + query { + allMdx(sort: {frontmatter: {date: DESC}}) { + edges { + node { + id + frontmatter { + title + date + description + tags + } + fields { + postPath + } + } + } + } + } + `) + + const latest_posts = data.allMdx.edges.slice(0, 3) + const latest_releases = data.allMdx.edges.filter( + ({ node }) => node.frontmatter.tags.includes("release-notes") + ) + const [active, setActive] = useState("Updates") return ( @@ -11,35 +38,35 @@ const Announcements = ({ handleClose }) => {
- - */} +
- {active === "Updates" && } - {active === "Ideas" && } - {active === "Roadmap" && } + {active === "Updates" && } + {active === "Events" && } + {active === "Releases" && }
diff --git a/src/components/ArticlePreview/__tests__/ArticlePreview.test.tsx b/src/components/ArticlePreview/__tests__/ArticlePreview.test.tsx deleted file mode 100644 index 096736987..000000000 --- a/src/components/ArticlePreview/__tests__/ArticlePreview.test.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react" -import { render } from "@testing-library/react" -import { describe, expect, it } from "vitest" -import ArticlePreview from "../index" - -describe("ArticlePreview component", () => { - it("should render correctly", () => { - const { container } = render( - , - ) - - expect(container).toMatchSnapshot() - }) - - it("should render correctly no match", () => { - const { container } = render( - , - ) - - expect(container).toMatchSnapshot() - }) -}) diff --git a/src/components/ArticlePreview/__tests__/__snapshots__/ArticlePreview.test.tsx.snap b/src/components/ArticlePreview/__tests__/__snapshots__/ArticlePreview.test.tsx.snap deleted file mode 100644 index 2a79b86db..000000000 --- a/src/components/ArticlePreview/__tests__/__snapshots__/ArticlePreview.test.tsx.snap +++ /dev/null @@ -1,109 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`ArticlePreview component > should render correctly 1`] = ` -
- -
-`; - -exports[`ArticlePreview component > should render correctly no match 1`] = ` -
- -
-`; diff --git a/src/components/ArticlePreview/index.tsx b/src/components/ArticlePreview/index.tsx deleted file mode 100644 index 52e4cf5f6..000000000 --- a/src/components/ArticlePreview/index.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from "react" -import { Link } from "gatsby-plugin-react-i18next" - -import Byline from "../News/Byline" - -/** - * Article intro displayed on front page, archive, author page. - */ -const ArticlePreview = props => { - const author = props.author - const date = props.date - const postPath = props.postPath - const title = props.title - const description = props.description - const excerpt = props.excerpt - const identifier = props.identifier - - return ( -
-
-

- {title} -

- -
-
-

- {description || excerpt} Read more -

-
-
- ) -} - -export default ArticlePreview diff --git a/src/components/AuthorBio/__tests__/__snapshots__/AuthorBio.test.tsx.snap b/src/components/AuthorBio/__tests__/__snapshots__/AuthorBio.test.tsx.snap index be3b6b840..539239a8c 100644 --- a/src/components/AuthorBio/__tests__/__snapshots__/AuthorBio.test.tsx.snap +++ b/src/components/AuthorBio/__tests__/__snapshots__/AuthorBio.test.tsx.snap @@ -3,14 +3,18 @@ exports[`AuthorBio component > should render correctly 1`] = `
[object Object] +
+
@@ -21,14 +25,16 @@ exports[`AuthorBio component > should render correctly 1`] = ` aria-label="GitHub Profile" class="px-1" href="https://github.com/adoptium" + rel="noopener noreferrer" + target="blank" > should render correctly 1`] = ` aria-label="Twitter Profile" class="px-1" href="https://x.com/adoptium" + rel="noopener noreferrer" + target="blank" > should render correctly 1`] = ` aria-label="LinkedIn Profile" class="px-1" href="https://www.linkedin.com/in/adoptium" + rel="noopener noreferrer" + target="blank" > { const { author, identifier } = sliceContext return ( + <> +
+ +
- {author.summary && <>{author.summary}} @@ -16,6 +19,7 @@ const AuthorBio = ({ sliceContext }) => {
+ ) } diff --git a/src/components/BlogAuthor/__tests__/__snapshots__/BlogAuthor.test.tsx.snap b/src/components/BlogAuthor/__tests__/__snapshots__/BlogAuthor.test.tsx.snap index 8ec4f9ab3..9abc79375 100644 --- a/src/components/BlogAuthor/__tests__/__snapshots__/BlogAuthor.test.tsx.snap +++ b/src/components/BlogAuthor/__tests__/__snapshots__/BlogAuthor.test.tsx.snap @@ -6,14 +6,16 @@ exports[`BlogAuthor component > GitHub link - should render correctly 1`] = ` aria-label="GitHub Profile" class="px-1" href="https://github.com/sample" + rel="noopener noreferrer" + target="blank" > Linkedin link - should render correctly 1`] = ` aria-label="LinkedIn Profile" class="px-1" href="https://www.linkedin.com/in/sample" + rel="noopener noreferrer" + target="blank" > Twitter link - should render correctly 1`] = ` aria-label="Twitter Profile" class="px-1" href="https://x.com/sample" + rel="noopener noreferrer" + target="blank" > should render correctly 1`] = `

Posted by diff --git a/src/components/BlogAuthor/index.tsx b/src/components/BlogAuthor/index.tsx index 00240dd46..284f9785b 100644 --- a/src/components/BlogAuthor/index.tsx +++ b/src/components/BlogAuthor/index.tsx @@ -15,8 +15,10 @@ export const GitHubLink = props => { className="px-1" aria-label="GitHub Profile" href={`https://github.com/${props.name}`} + target="blank" + rel="noopener noreferrer" > - + ) } @@ -31,8 +33,10 @@ export const TwitterLink = props => { className="px-1" aria-label="Twitter Profile" href={`https://x.com/${props.name}`} + target="blank" + rel="noopener noreferrer" > - + ) } @@ -47,8 +51,10 @@ export const LinkedinLink = props => { className="px-1" aria-label="LinkedIn Profile" href={`https://www.linkedin.com/in/${props.name}`} + target="blank" + rel="noopener noreferrer" > - + ) } @@ -56,7 +62,7 @@ export const LinkedinLink = props => { const BlogAuthor = props => { const author = props.author const identifier = props.identifier - const href = `/blog/author/${identifier}` + const href = `/news/author/${identifier}` return (

{ return ( -
- +
+ {post.node.frontmatter.featuredImage ? ( + blog banner image + ) : ( + blog banner image + )} +

{post.node.frontmatter.title}

-
+

{post.node.frontmatter.date} - {/* London */}

- {/*

13:00 - 15:00

*/}
+ {post.node.excerpt}
diff --git a/src/components/NavBar/index.tsx b/src/components/NavBar/index.tsx index efea3aea6..d881d2b4e 100644 --- a/src/components/NavBar/index.tsx +++ b/src/components/NavBar/index.tsx @@ -169,7 +169,7 @@ const NavBar = () => { > - 9 + 1 {/* TODO find a way to calculate the notification count */}
diff --git a/src/components/News/Byline/__tests__/__snapshots__/Byline.test.tsx.snap b/src/components/News/Byline/__tests__/__snapshots__/Byline.test.tsx.snap index 48ec2a36b..7c756326d 100644 --- a/src/components/News/Byline/__tests__/__snapshots__/Byline.test.tsx.snap +++ b/src/components/News/Byline/__tests__/__snapshots__/Byline.test.tsx.snap @@ -15,7 +15,7 @@ exports[`Byline component > should render correctly 1`] = ` class="text-[16px] font-bold leading-[150%] text-white" > { const { author, date, identifier } = props - const href = `/blog/author/${identifier}` + const href = `/news/author/${identifier}` return (
diff --git a/src/components/News/NewsCardList/index.tsx b/src/components/News/NewsCardList/index.tsx index 16a11c6fb..b18d412ca 100644 --- a/src/components/News/NewsCardList/index.tsx +++ b/src/components/News/NewsCardList/index.tsx @@ -1,48 +1,42 @@ -import React from "react" -import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; -import { Link } from "gatsby-plugin-react-i18next" +import React from "react"; +import EventCard from "../../Events/EventCard"; +import Pagination from "../Pagination"; // adjust the import path as needed -import EventCard from "../../Events/EventCard" +const NewsCardList = ({ + posts, + previousPageNumber, + previousPageLink, + nextPage, + currentPage, + totalPages, + baseUrl, +}) => { + const data1 = posts.slice(0, 3); + const data2 = posts.slice(3, 6); -const NewsCardList = ({ posts, previousPageNumber, previousPageLink, nextPage }) => { - const data1 = posts.slice(0, 3) - const data2 = posts.slice(3, 6) - return ( -
-
- {data1.map((post, index) => ( - - ))} -
-
- {data2.map((post, index) => ( - - ))} -
-
- {previousPageNumber && ( - -
- - - -

Previous Page

-
- - )} - {nextPage && ( - -
-

Next Page

- - - -
- - )} -
-
- ) -} + return ( +
+
+ {data1.map((post, index) => ( + + ))} +
+
+ {data2.map((post, index) => ( + + ))} +
-export default NewsCardList + +
+ ); +}; + +export default NewsCardList; diff --git a/src/components/News/Pagination/__tests__/Pagination.test.tsx b/src/components/News/Pagination/__tests__/Pagination.test.tsx new file mode 100644 index 000000000..6d3ef0c4a --- /dev/null +++ b/src/components/News/Pagination/__tests__/Pagination.test.tsx @@ -0,0 +1,114 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { describe, it, expect } from "vitest"; +import Pagination from "../"; + +describe("Pagination component", () => { + it("renders correctly when totalPages <= 7", () => { + render( + + ); + + // Expect pages 1 through 5 to be rendered with no ellipsis. + for (let i = 1; i <= 5; i++) { + expect(screen.getByText(String(i))).toBeInTheDocument(); + } + expect(screen.queryByText("...")).toBeNull(); + + // Check that both "Previous" and "Next" buttons are rendered. + expect(screen.getByText("Previous")).toBeInTheDocument(); + expect(screen.getByText("Next")).toBeInTheDocument(); + }); + + it("renders correctly for currentPage=1 when totalPages > 7", () => { + render( + + ); + + // Expected pages for page 1: 1, 2, 3, 4, 5, ... , 8 + expect(screen.getByText("1")).toBeInTheDocument(); + expect(screen.getByText("2")).toBeInTheDocument(); + expect(screen.getByText("3")).toBeInTheDocument(); + expect(screen.getByText("4")).toBeInTheDocument(); + expect(screen.getByText("5")).toBeInTheDocument(); + expect(screen.getByText("8")).toBeInTheDocument(); + + // There should be one ellipsis. + expect(screen.getAllByText("...")).toHaveLength(1); + + // "Previous" button should not render when previousPageNumber is null. + expect(screen.queryByText("Previous")).toBeNull(); + expect(screen.getByText("Next")).toBeInTheDocument(); + }); + + it("renders correctly for a middle page when totalPages > 7", () => { + render( + + ); + + // Expected pages for currentPage 5: 1, ... , 3, 4, 5, 6, 7, ... , 10 + expect(screen.getByText("1")).toBeInTheDocument(); + expect(screen.getByText("3")).toBeInTheDocument(); + expect(screen.getByText("4")).toBeInTheDocument(); + expect(screen.getByText("5")).toBeInTheDocument(); + expect(screen.getByText("6")).toBeInTheDocument(); + expect(screen.getByText("7")).toBeInTheDocument(); + expect(screen.getByText("10")).toBeInTheDocument(); + + // There should be two ellipses. + expect(screen.getAllByText("...")).toHaveLength(2); + + // Both arrow links should be rendered. + expect(screen.getByText("Previous")).toBeInTheDocument(); + expect(screen.getByText("Next")).toBeInTheDocument(); + }); + + it("renders correctly for a near-end page", () => { + render( + + ); + + // Expected pages for currentPage 7: 1, ... , 4, 5, 6, 7, 8 + expect(screen.getByText("1")).toBeInTheDocument(); + expect(screen.getByText("4")).toBeInTheDocument(); + expect(screen.getByText("5")).toBeInTheDocument(); + expect(screen.getByText("6")).toBeInTheDocument(); + expect(screen.getByText("7")).toBeInTheDocument(); + expect(screen.getByText("8")).toBeInTheDocument(); + + // There should be one ellipsis. + expect(screen.getAllByText("...")).toHaveLength(1); + + // Both arrow links should be rendered. + expect(screen.getByText("Previous")).toBeInTheDocument(); + expect(screen.getByText("Next")).toBeInTheDocument(); + }); +}); diff --git a/src/components/News/Pagination/index.tsx b/src/components/News/Pagination/index.tsx new file mode 100644 index 000000000..790ee2b29 --- /dev/null +++ b/src/components/News/Pagination/index.tsx @@ -0,0 +1,120 @@ +import React from "react"; +import { Link } from "gatsby-plugin-react-i18next"; +import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; + +interface PaginationProps { + previousPageNumber?: number | null; + previousPageLink?: string | null; + nextPage?: string | null; + currentPage: number; + totalPages: number; + baseUrl: string; +} + +const Pagination: React.FC = ({ + previousPageNumber, + previousPageLink, + nextPage, + currentPage, + totalPages, + baseUrl, +}) => { + // Helper to build a link for a given page number. + // If page 1, return the baseUrl; otherwise append "/page/{page}". + const createPageLink = (page: number): string => + page === 1 ? `${baseUrl}` : `${baseUrl}/page/${page}`; + + // Build an array of page numbers and ellipses. + const pages: (number | string)[] = []; + if (totalPages <= 7) { + // If few pages, show them all. + for (let i = 1; i <= totalPages; i++) { + pages.push(i); + } + } else { + // Always show the first page. + pages.push(1); + + // Determine the range around the current page. + let start = Math.max(2, currentPage - 2); + let end = Math.min(totalPages - 1, currentPage + 2); + + // Adjust range if near the beginning. + if (currentPage <= 3) { + start = 2; + end = 5; + } + // Adjust range if near the end. + if (currentPage >= totalPages - 2) { + start = totalPages - 4; + end = totalPages - 1; + } + + // Add ellipsis if there's a gap between 1 and the start. + if (start > 2) { + pages.push("..."); + } + + // Add the page numbers in the range. + for (let i = start; i <= end; i++) { + pages.push(i); + } + + // Add ellipsis if there's a gap between end and the last page. + if (end < totalPages - 1) { + pages.push("..."); + } + + // Always show the last page. + pages.push(totalPages); + } + + return ( +
+ {/* Previous arrow using your passed parameters */} + {previousPageNumber && previousPageLink && ( + + + + +

Previous

+ + )} + + {/* Numbered pagination */} +
+
+ {pages.map((page, index) => + page === "..." ? ( +

+ ... +

+ ) : ( + +

+ {page} +

+ + ) + )} +
+
+ + {/* Next arrow using your passed parameters */} + {nextPage && ( + +

Next

+ + + + + )} +
+ ); +}; + +export default Pagination; diff --git a/src/components/News/RelatedArticles/RelatedArticleCard.tsx b/src/components/News/RelatedArticles/RelatedArticleCard.tsx index 18755cc67..f231a99dc 100644 --- a/src/components/News/RelatedArticles/RelatedArticleCard.tsx +++ b/src/components/News/RelatedArticles/RelatedArticleCard.tsx @@ -2,11 +2,19 @@ import React from "react" import { Link } from "gatsby" import { capitalize } from "../../../util/capitalize" -const RelatedArticleCard = ({title, date, postPath, tags}) => { +const RelatedArticleCard = ({data}) => { + const title = data.node.frontmatter.title + const tags = data.node.frontmatter.tags + const date = data.node.frontmatter.date + const postPath = data.node.fields.postPath return (
- + {data.node.frontmatter.featuredImage ? ( + blog banner image + ) : ( + blog banner image + )} {tags && tags.length > 0 && ( - -
- -
- {data.allMdx.edges.map((card, index) => ( - - ))} -
+ return ( +
+
+
+ Related Articles
- ) +
+ Eclipse Temurin offers high-performance, cross-platform, open-source + Java runtime binaries that are enterprise-ready and Java SE TCK-tested + for general use in the Java ecosystem. +
+ + + +
+ +
+ {data.allMdx.edges.map((card, index) => ( + + ))} +
+
+ ) } export default RelatedArticle \ No newline at end of file diff --git a/src/components/Seo/__tests__/__snapshots__/Seo.test.tsx.snap b/src/components/Seo/__tests__/__snapshots__/Seo.test.tsx.snap index ab8613016..5cafa458c 100644 --- a/src/components/Seo/__tests__/__snapshots__/Seo.test.tsx.snap +++ b/src/components/Seo/__tests__/__snapshots__/Seo.test.tsx.snap @@ -22,7 +22,7 @@ exports[`SEO component > SEO metadata renders correctly 1`] = ` name="og:type" /> SEO metadata renders correctly 1`] = ` name="twitter:site" /> { } if (!twitterCard) { - twitterCard = "images/social-image.png" + twitterCard = "images/social-image.jpg" } const siteTitle = title + " | Adoptium" diff --git a/src/components/Tags/__tests__/__snapshots__/Tags.test.tsx.snap b/src/components/Tags/__tests__/__snapshots__/Tags.test.tsx.snap index 20287ed75..d14ca1581 100644 --- a/src/components/Tags/__tests__/__snapshots__/Tags.test.tsx.snap +++ b/src/components/Tags/__tests__/__snapshots__/Tags.test.tsx.snap @@ -4,45 +4,49 @@ exports[`Tags component > should render correctly - no tags 1`] = `
`; diff --git a/src/components/Tags/index.tsx b/src/components/Tags/index.tsx index fd5af4ad7..c380b36a2 100644 --- a/src/components/Tags/index.tsx +++ b/src/components/Tags/index.tsx @@ -10,18 +10,18 @@ const Tags = props => { } return ( - <> +
{tags.map(tag => ( - + {tag} ))} - +
) } diff --git a/src/components/Temurin/ReleaseRoadmap/ReleaseRoadMapMobile.tsx b/src/components/Temurin/ReleaseRoadmap/ReleaseRoadMapMobile.tsx index 4b2e2d9f2..665f420af 100644 --- a/src/components/Temurin/ReleaseRoadmap/ReleaseRoadMapMobile.tsx +++ b/src/components/Temurin/ReleaseRoadmap/ReleaseRoadMapMobile.tsx @@ -8,7 +8,6 @@ const ReleaseRoadMapMobile = () => { const updatedFaqs = [...openFaqs] updatedFaqs[index] = !updatedFaqs[index] setOpenFaqs(updatedFaqs) - console.log(updatedFaqs) } const faqs = [ diff --git a/src/json/authors.json b/src/json/authors.json index 1735d9a8a..6bfb72889 100644 --- a/src/json/authors.json +++ b/src/json/authors.json @@ -1,7 +1,7 @@ { "georgeadams": { "name": "George Adams", - "summary": "Java Program Manager @microsoft, Chair of Adoptium WG Steering Committee, Core Collaborator @nodejs", + "summary": "Chair of Adoptium WG Steering Committee, Java Champion, Microsoft", "twitter": "gdams_", "github": "gdams", "linkedin": "gdams" diff --git a/src/pages/__tests__/__snapshots__/authorPage.test.tsx.snap b/src/pages/__tests__/__snapshots__/authorPage.test.tsx.snap index 47db07ff8..51542a5ca 100644 --- a/src/pages/__tests__/__snapshots__/authorPage.test.tsx.snap +++ b/src/pages/__tests__/__snapshots__/authorPage.test.tsx.snap @@ -92,7 +92,7 @@ exports[`AuthorPage pages > renders correctly 1`] = ` 2021-01-01 – posted by   Adoptium PMC diff --git a/src/pages/__tests__/__snapshots__/blog.test.tsx.snap b/src/pages/__tests__/__snapshots__/blog.test.tsx.snap index f943557ce..f285bc725 100644 --- a/src/pages/__tests__/__snapshots__/blog.test.tsx.snap +++ b/src/pages/__tests__/__snapshots__/blog.test.tsx.snap @@ -77,18 +77,17 @@ exports[`News page > renders correctly 1`] = `
`; diff --git a/src/pages/__tests__/__snapshots__/blogPage.test.tsx.snap b/src/pages/__tests__/__snapshots__/blogPage.test.tsx.snap index f1b607b73..9030627ef 100644 --- a/src/pages/__tests__/__snapshots__/blogPage.test.tsx.snap +++ b/src/pages/__tests__/__snapshots__/blogPage.test.tsx.snap @@ -31,7 +31,7 @@ exports[`Blog Template page > renders correctly 1`] = ` 2021-01-01 – posted by   Adoptium PMC diff --git a/src/pages/__tests__/__snapshots__/blogPost.test.tsx.snap b/src/pages/__tests__/__snapshots__/blogPost.test.tsx.snap index 4cb613578..bbcf7f0e0 100644 --- a/src/pages/__tests__/__snapshots__/blogPost.test.tsx.snap +++ b/src/pages/__tests__/__snapshots__/blogPost.test.tsx.snap @@ -50,7 +50,7 @@ exports[`BlogPost Template page > renders correctly - featured image 1`] = ` 2021-01-01 – posted by   Adoptium PMC @@ -250,7 +250,7 @@ exports[`BlogPost Template page > renders correctly - featured image 1`] = `

Posted by Adoptium PMC @@ -359,7 +359,7 @@ exports[`BlogPost Template page > renders correctly 1`] = ` 2021-01-01 – posted by   Adoptium PMC @@ -559,7 +559,7 @@ exports[`BlogPost Template page > renders correctly 1`] = `

Posted by Adoptium PMC diff --git a/src/pages/__tests__/__snapshots__/docs.test.tsx.snap b/src/pages/__tests__/__snapshots__/docs.test.tsx.snap index 73e2f1d9c..14d976cfe 100644 --- a/src/pages/__tests__/__snapshots__/docs.test.tsx.snap +++ b/src/pages/__tests__/__snapshots__/docs.test.tsx.snap @@ -938,26 +938,10 @@ exports[`Docs page > renders correctly 1`] = ` docs.adoptium.blog - - - - renders correctly 1`] = ` +

+
+`; diff --git a/src/pages/__tests__/__snapshots__/support-us.test.tsx.snap b/src/pages/__tests__/__snapshots__/support-us.test.tsx.snap index 0b31ebdb1..368f69005 100644 --- a/src/pages/__tests__/__snapshots__/support-us.test.tsx.snap +++ b/src/pages/__tests__/__snapshots__/support-us.test.tsx.snap @@ -230,7 +230,7 @@ exports[`Support Us page > renders correctly 1`] = ` - 9 + 1
diff --git a/src/pages/__tests__/__snapshots__/tagPage.test.tsx.snap b/src/pages/__tests__/__snapshots__/tagPage.test.tsx.snap index ed1b944d6..59aad859b 100644 --- a/src/pages/__tests__/__snapshots__/tagPage.test.tsx.snap +++ b/src/pages/__tests__/__snapshots__/tagPage.test.tsx.snap @@ -37,7 +37,7 @@ exports[`TagPage pages > renders correctly 1`] = ` 2021-01-01 – posted by   Adoptium PMC diff --git a/src/pages/__tests__/blog.test.tsx b/src/pages/__tests__/news.test.tsx similarity index 95% rename from src/pages/__tests__/blog.test.tsx rename to src/pages/__tests__/news.test.tsx index b73e54328..4d545ebe8 100644 --- a/src/pages/__tests__/blog.test.tsx +++ b/src/pages/__tests__/news.test.tsx @@ -23,7 +23,7 @@ describe("News page", () => { // eslint-disable-next-line const pageContent = container.querySelector("main") - expect(pageContent).toHaveTextContent("Next Page") + expect(pageContent).toHaveTextContent("Next") }) it("head renders correctly", () => { diff --git a/src/pages/docs.tsx b/src/pages/docs.tsx index d234b587f..8e8c5af77 100644 --- a/src/pages/docs.tsx +++ b/src/pages/docs.tsx @@ -240,8 +240,8 @@ const DocumentationPage = ({ data }) => { link: "https://api.adoptium.net", }, { - name: t("docs.adoptium.blog", "Adoptium Blog"), - link: "https://adoptium.net/blog", + name: t("docs.adoptium.blog", "Adoptium News"), + link: "/news", }, { name: t("docs.support.us", "Support Us"), diff --git a/src/pages/news.tsx b/src/pages/news.tsx index 0b306bbba..b7f161f22 100644 --- a/src/pages/news.tsx +++ b/src/pages/news.tsx @@ -6,9 +6,22 @@ import Seo from "../components/Seo" import PageHeader from "../components/PageHeader" import NewsCardList from "../components/News/NewsCardList" -const BlogIndex = ({ data }) => { +const NewsIndex = ({ data }) => { const posts = data.allMdx.edges - const nextPageNumber = data.allMdx.totalCount > 10 ? 2 : null + const totalCount = data.allMdx.totalCount + const postsPerPage = 6 + const totalPages = Math.ceil(totalCount / postsPerPage) + const currentPage = 1 + + // For page 1, there is no previous page. + const previousPageNumber = null + const previousPageLink = null + + // If there are more pages, calculate the next page's link. + const nextPageNumber = currentPage < totalPages ? currentPage + 1 : null + const nextPage = nextPageNumber ? `/news/page/${nextPageNumber}` : null + + const baseUrl = "/news" // This is used by your pagination component to build numbered links return ( @@ -16,14 +29,22 @@ const BlogIndex = ({ data }) => { subtitle="News" title="News & Updates" description="Eclipse Temurin offers high-performance, cross-platform, open-source Java runtime binaries that are enterprise-ready and Java SE TCK-tested for general use in the Java ecosystem." - className={"mx-auto max-w-[860px] px-2 w-full"} + className="mx-auto max-w-[860px] px-2 w-full" + /> + - ) } -export default BlogIndex +export default NewsIndex export const Head = () => @@ -42,6 +63,7 @@ export const pageQuery = graphql` fields { slug postPath + generatedFeaturedImage } frontmatter { author @@ -49,6 +71,11 @@ export const pageQuery = graphql` title description tags + featuredImage { + childImageSharp { + gatsbyImageData(layout: FIXED) + } + } } } } diff --git a/src/pages/temurin/__tests__/__snapshots__/releases.test.tsx.snap b/src/pages/temurin/__tests__/__snapshots__/releases.test.tsx.snap index 606ddd9ee..e185148e4 100644 --- a/src/pages/temurin/__tests__/__snapshots__/releases.test.tsx.snap +++ b/src/pages/temurin/__tests__/__snapshots__/releases.test.tsx.snap @@ -230,7 +230,7 @@ exports[`Releases page > renders correctly 1`] = ` - 9 + 1
diff --git a/src/styles/global.scss b/src/styles/global.scss index badcc24f9..0d86812fa 100644 --- a/src/styles/global.scss +++ b/src/styles/global.scss @@ -74,7 +74,7 @@ body { } .newscard-2 { - border-radius: 24px; + border-radius: 1.5rem; background: var(--tones-white-5, rgba(255, 255, 255, 5%)); backdrop-filter: blur(12px); position: relative; @@ -87,7 +87,7 @@ body { inset: 0; width: 100%; height: 100%; - border-radius: 24px; + border-radius: 1.5rem; padding: 2px; background: linear-gradient(285deg, #30195edb 40.01%, #574974f7 63.36%); -webkit-mask: diff --git a/src/templates/__tests__/__snapshots__/authorPage.test.tsx.snap b/src/templates/__tests__/__snapshots__/authorPage.test.tsx.snap index 4842819a3..7c4ffbade 100644 --- a/src/templates/__tests__/__snapshots__/authorPage.test.tsx.snap +++ b/src/templates/__tests__/__snapshots__/authorPage.test.tsx.snap @@ -79,18 +79,17 @@ exports[`AuthorPage pages > renders correctly - with next and previous paginatio
@@ -275,18 +323,17 @@ exports[`AuthorPage pages > renders correctly - with next pagination 1`] = `
@@ -440,18 +539,17 @@ exports[`AuthorPage pages > renders correctly - with previous pagination 1`] = `
@@ -605,18 +783,17 @@ exports[`AuthorPage pages > renders correctly 1`] = `
`; diff --git a/src/templates/__tests__/__snapshots__/blogPage.test.tsx.snap b/src/templates/__tests__/__snapshots__/blogPage.test.tsx.snap index 7d84dd772..48102c166 100644 --- a/src/templates/__tests__/__snapshots__/blogPage.test.tsx.snap +++ b/src/templates/__tests__/__snapshots__/blogPage.test.tsx.snap @@ -37,7 +37,7 @@ exports[`Blog Template page > renders correctly 1`] = ` class="text-[16px] font-bold leading-[150%] text-white" > renders correctly - featured image 1`] = ` class="text-[16px] font-bold leading-[150%] text-white" > renders correctly 1`] = ` class="text-[16px] font-bold leading-[150%] text-white" > renders correctly 1`] = `
diff --git a/src/templates/__tests__/__snapshots__/newsPost.test.tsx.snap b/src/templates/__tests__/__snapshots__/newsPost.test.tsx.snap new file mode 100644 index 000000000..17d0e382c --- /dev/null +++ b/src/templates/__tests__/__snapshots__/newsPost.test.tsx.snap @@ -0,0 +1,733 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`NewsPost Template page > formatDiv renders correctly - inline code block 1`] = ` +
+ + + test + + +
+`; + +exports[`NewsPost Template page > formatDiv renders correctly 1`] = ` +
+
+

+ test +

+
+
+`; + +exports[`NewsPost Template page > renders correctly - featured image 1`] = ` +
+
+
+
+
+
+ + + + +
+ News article +
+
+

+ MDX Page title +

+
+
+ MDX Page excerpt +
+ +
+
+
+
+
+ + +
+
+
+ related-articles +
+
+`; + +exports[`NewsPost Template page > renders correctly 1`] = ` +
+
+
+
+
+
+ + + + +
+ News article +
+
+

+ MDX Page title +

+
+
+ MDX Page excerpt +
+ +
+
+
+
+
+ + +
+
+
+ related-articles +
+
+`; diff --git a/src/templates/__tests__/__snapshots__/tagPage.test.tsx.snap b/src/templates/__tests__/__snapshots__/tagPage.test.tsx.snap index 04904daac..599e89b9e 100644 --- a/src/templates/__tests__/__snapshots__/tagPage.test.tsx.snap +++ b/src/templates/__tests__/__snapshots__/tagPage.test.tsx.snap @@ -1,76 +1,913 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`TagPage pages > renders correctly 1`] = ` +exports[`TagPage pages > renders correctly - with next and previous pagination 1`] = `
-
+
+
+
+
+
+
+
+
+
+ + + + +
+ News articles +
+
+
+ Test +
+
+
+ Posts tagged with test +
+
+
+
+
-

- test -

-
-
+
+ +
+`; + +exports[`TagPage pages > renders correctly - with next pagination 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+ + + +
+ News articles +
+
+
+ Test +
+
+
+ Posts tagged with test +
+
+
+
+
+
+
+ blog banner image +
+

+ MDX Page title +

+
+

+ + 2021-01-01 + +

+
+ + MDX Page excerpt + + + + +
+
+
+ +
+`; + +exports[`TagPage pages > renders correctly - with previous pagination 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+ + + - - - - Adoptium PMC - , - - - 2021-01-01 - + +
+ News articles +
+
+
+ Test +
+
+
+ Posts tagged with test +
+
+
+
+
+
+
+ blog banner image +
+

+ MDX Page title +

+
+

+ + 2021-01-01 +

+
+ + MDX Page excerpt + + + + +
+
+
+ +
+`; + +exports[`TagPage pages > renders correctly 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+ + + + +
+ News articles
- -
-

- MDX Page excerpt - - - Read more - -

-
- +
+
+ Test +
+
+
+ Posts tagged with test +
- +
+
+
+
+ blog banner image +
+

+ MDX Page title +

+
+

+ + 2021-01-01 + +

+
+ + MDX Page excerpt + + + + +
+
+
+
`; diff --git a/src/templates/__tests__/authorPage.test.tsx b/src/templates/__tests__/authorPage.test.tsx index a84eb7517..af2a99510 100644 --- a/src/templates/__tests__/authorPage.test.tsx +++ b/src/templates/__tests__/authorPage.test.tsx @@ -9,6 +9,9 @@ let mockData = createMDXData() const pageContext = { author: "pmc", currentPageNumber: 1, + previousPageNumber: null, + nextPageNumber: 2, + numAuthorPages: 5, } describe("AuthorPage pages", () => { diff --git a/src/templates/__tests__/newsPage.test.tsx b/src/templates/__tests__/newsPage.test.tsx index 4d7033638..d0c06d474 100644 --- a/src/templates/__tests__/newsPage.test.tsx +++ b/src/templates/__tests__/newsPage.test.tsx @@ -7,9 +7,10 @@ import { createMDXData } from "../../__fixtures__/page" let mockData = createMDXData() const pageContext = { - currentPageNumber: 2, + currentPage: 2, previousPageNumber: 1, nextPageNumber: 3, + totalPages: 5, } describe("News Template page", () => { diff --git a/src/templates/__tests__/blogPost.test.tsx b/src/templates/__tests__/newsPost.test.tsx similarity index 91% rename from src/templates/__tests__/blogPost.test.tsx rename to src/templates/__tests__/newsPost.test.tsx index f7222837b..f21f6c86f 100644 --- a/src/templates/__tests__/blogPost.test.tsx +++ b/src/templates/__tests__/newsPost.test.tsx @@ -1,6 +1,6 @@ import React from "react" import { render } from "@testing-library/react" -import BlogPost, { Head, formatDiv } from "../../templates/blogPost" +import NewsPost, { Head, formatDiv } from "../newsPost" import { describe, expect, it, vi } from "vitest" import { axe } from "vitest-axe" import { createSingleMDXData } from "../../__fixtures__/page" @@ -15,7 +15,7 @@ let mockData = createSingleMDXData() const pageContext = { previous: { fields: { - postPath: "/blog/previous", + postPath: "/news/previous", }, frontmatter: { title: "Previous", @@ -23,7 +23,7 @@ const pageContext = { }, next: { fields: { - postPath: "/blog/next", + postPath: "/news/next", }, frontmatter: { title: "Next", @@ -31,10 +31,10 @@ const pageContext = { }, } -describe("BlogPost Template page", () => { +describe("NewsPost Template page", () => { it("renders correctly", () => { const { container } = render( - { }, } const { container } = render( - { it("has no accessibility violations", async () => { const { container } = render( - { @@ -21,6 +25,47 @@ describe("TagPage pages", () => { expect(pageContent).toMatchSnapshot() }) + it("renders correctly - with next pagination", () => { + const { container } = render( + , + ) + // eslint-disable-next-line + const pageContent = container.querySelector("main") + + expect(pageContent).toMatchSnapshot() + }) + + it("renders correctly - with previous pagination", () => { + const { container } = render( + , + ) + // eslint-disable-next-line + const pageContent = container.querySelector("main") + expect(pageContent).toMatchSnapshot() + }) + + it("renders correctly - with next and previous pagination", () => { + const { container } = render( + , + ) + // eslint-disable-next-line + const pageContent = container.querySelector("main") + expect(pageContent).toMatchSnapshot() + }) + it("head renders correctly", () => { const { container } = render() // eslint-disable-next-line diff --git a/src/templates/authorPage.tsx b/src/templates/authorPage.tsx index 73b0ee900..e56e21115 100644 --- a/src/templates/authorPage.tsx +++ b/src/templates/authorPage.tsx @@ -1,6 +1,5 @@ import React from "react" import { graphql, Slice } from "gatsby" -import { Link } from "gatsby-plugin-react-i18next" import Layout from "../components/Layout" import Seo from "../components/Seo" @@ -11,22 +10,38 @@ import AuthorData from "../json/authors.json" const AuthorPage = ({ data, pageContext }) => { const author = AuthorData[pageContext.author] const posts = data.allMdx.edges + const { previousPageNumber, nextPageNumber, currentPageNumber, numAuthorPages } = pageContext - const { previousPageNumber, nextPageNumber } = pageContext + // For page 2, previousPageNumber === 1 should lead to the main author URL + // For pages > 2, include the "/page" segment in the URL. const previousPageLink = previousPageNumber === 1 - ? `/blog/author/${pageContext.author}` - : `/blog/author/${previousPageNumber}` + ? `/news/author/${pageContext.author}` + : `/news/author/${pageContext.author}/page/${previousPageNumber}` + + const nextPageLink = + nextPageNumber ? `/news/author/${pageContext.author}/page/${nextPageNumber}` : null + + // Base URL for numbered pagination links + const baseUrl = `/news/author/${pageContext.author}` return ( } - className={"mx-auto max-w-[860px] px-2 w-full"} + subtitle="Author" + title={author.name} + description={} + className="mx-auto max-w-[860px] px-2 w-full" + /> + - ) } @@ -68,15 +83,22 @@ export const authorPageQuery = graphql` ) { edges { node { + excerpt fields { slug postPath + generatedFeaturedImage } frontmatter { date(formatString: "MMMM DD, YYYY") title description tags + featuredImage { + childImageSharp { + gatsbyImageData(layout: FIXED) + } + } } } } diff --git a/src/templates/newsPage.tsx b/src/templates/newsPage.tsx index dc3fb2561..7c8a84c22 100644 --- a/src/templates/newsPage.tsx +++ b/src/templates/newsPage.tsx @@ -1,16 +1,24 @@ -import React from "react" -import { graphql } from "gatsby" +import React from "react"; +import { graphql } from "gatsby"; -import Layout from "../components/Layout" -import Seo from "../components/Seo" -import PageHeader from "../components/PageHeader" -import NewsCardList from "../components/News/NewsCardList" +import Layout from "../components/Layout"; +import Seo from "../components/Seo"; +import PageHeader from "../components/PageHeader"; +import NewsCardList from "../components/News/NewsCardList"; const NewsPage = ({ data, pageContext }) => { - const posts = data.allMdx.edges - const { previousPageNumber, nextPageNumber } = pageContext + const posts = data.allMdx.edges; + const { + previousPageNumber, + nextPageNumber, + currentPage, + totalPages, + baseUrl, + } = pageContext; + const previousPageLink = - previousPageNumber === 1 ? "/news" : `/news/page/${previousPageNumber}` + previousPageNumber === 1 ? "/news" : `/news/page/${previousPageNumber}`; + const nextPage = nextPageNumber ? `/news/page/${nextPageNumber}` : null; return ( @@ -18,19 +26,27 @@ const NewsPage = ({ data, pageContext }) => { subtitle="News" title="News & Updates" description="Eclipse Temurin offers high-performance, cross-platform, open-source Java runtime binaries that are enterprise-ready and Java SE TCK-tested for general use in the Java ecosystem." - className={"mx-auto max-w-[860px] px-2 w-full"} + className="mx-auto max-w-[860px] px-2 w-full" + /> + - - ) -} + ); +}; -export default NewsPage +export default NewsPage; export const Head = ({ pageContext }) => { - const { currentPageNumber } = pageContext - return -} + const { currentPage } = pageContext; + return ; +}; export const newsPageQuery = graphql` query newsPageQuery($skip: Int!, $limit: Int!, $language: String!) { @@ -39,13 +55,18 @@ export const newsPageQuery = graphql` title } } - allMdx(sort: { frontmatter: { date: DESC } }, limit: $limit, skip: $skip) { + allMdx( + sort: { frontmatter: { date: DESC } } + limit: $limit + skip: $skip + ) { edges { node { excerpt fields { slug postPath + generatedFeaturedImage } frontmatter { date(formatString: "MMMM DD, YYYY") @@ -53,6 +74,11 @@ export const newsPageQuery = graphql` description author tags + featuredImage { + childImageSharp { + gatsbyImageData(layout: FIXED) + } + } } } } @@ -67,4 +93,4 @@ export const newsPageQuery = graphql` } } } -` +`; diff --git a/src/templates/blogPost.tsx b/src/templates/newsPost.tsx similarity index 91% rename from src/templates/blogPost.tsx rename to src/templates/newsPost.tsx index 15b4a14cd..407a5281d 100644 --- a/src/templates/blogPost.tsx +++ b/src/templates/newsPost.tsx @@ -31,7 +31,7 @@ const components = { div: formatDiv, } -const BlogPostTemplate = ({ data, pageContext, location, children }) => { +const NewsPostTemplate = ({ data, pageContext, location, children }) => { const post = data.mdx const { previous, next } = pageContext const author = AuthorData[post.frontmatter.author] @@ -77,8 +77,8 @@ const BlogPostTemplate = ({ data, pageContext, location, children }) => {
{children}
- {/* - */} + + {/* */} @@ -107,16 +107,16 @@ const BlogPostTemplate = ({ data, pageContext, location, children }) => { ) } -export default BlogPostTemplate +export default NewsPostTemplate export const Head = ({ data }) => { const post = data.mdx let twitterCard = "" if (post.frontmatter && post.frontmatter.featuredImage) { - twitterCard = - post.frontmatter.featuredImage.childImageSharp.gatsbyImageData.images - .fallback.src + twitterCard = post.frontmatter.featuredImage.childImageSharp.gatsbyImageData.images.fallback.src + } else if (post.fields.generatedFeaturedImage) { + twitterCard = post.fields.generatedFeaturedImage } return ( { - const tags = data.allMdx.edges + const posts = data.allMdx.edges + const { tag, previousPageNumber, nextPageNumber, currentPageNumber, numTagPages } = pageContext + + // Build previousPageLink: if previous is page 1, use the base URL; otherwise include /page/ + const previousPageLink = + previousPageNumber === 1 + ? `/news/tags/${tag}` + : `/news/tags/${tag}/page/${previousPageNumber}` + + // Build next page link if a next page exists. + const nextPageLink = nextPageNumber ? `/news/tags/${tag}/page/${nextPageNumber}` : null + + // Base URL used for numbered pagination links. + const baseUrl = `/news/tags/${tag}` return ( -
-
-
-

{pageContext.tag}

-
- {tags.map(({ node }) => { - const title = node.frontmatter.title - const author = AuthorData[node.frontmatter.author] - return ( - - ) - })} -
-
-
+ +
) } @@ -42,7 +47,17 @@ const Tags = ({ pageContext, data }) => { export default Tags export const Head = ({ pageContext }) => { - return + const { currentPageNumber, tag } = pageContext + return ( + + ) } export const tagsPageQuery = graphql` @@ -58,15 +73,22 @@ export const tagsPageQuery = graphql` ) { edges { node { + excerpt fields { slug postPath + generatedFeaturedImage } frontmatter { date(formatString: "MMMM DD, YYYY") title description author + featuredImage { + childImageSharp { + gatsbyImageData(layout: FIXED) + } + } } } } diff --git a/src/util/__tests__/generateFeaturedImage.test.tsx b/src/util/__tests__/generateFeaturedImage.test.tsx new file mode 100644 index 000000000..9be463f19 --- /dev/null +++ b/src/util/__tests__/generateFeaturedImage.test.tsx @@ -0,0 +1,111 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { getWrappedLines, drawTitleAndSubtitle, generateFeaturedImage } from '../generateFeaturedImage'; +import fs from 'fs-extra'; +import path from 'path'; + +describe('getWrappedLines', () => { + it('should wrap text into multiple lines when exceeding max width', () => { + // Create a fake canvas context that simulates measureText. + const fakeCtx = { + measureText: (text: string) => ({ width: text.length * 10 }), + } as unknown as import('canvas').CanvasRenderingContext2D; + + const maxWidth = 100; // 100 pixels maximum + const text = "This is a long text that should be wrapped"; + + // Call getWrappedLines to get an array of wrapped lines. + const lines = getWrappedLines(fakeCtx, text, maxWidth); + + // Based on our fake measureText (each character = 10px), one expected split is: + // 1. "This is a" + // 2. "long text" + // 3. "that" + // 4. "should be" + // 5. "wrapped" + const expectedLines = ["This is a", "long text", "that", "should be", "wrapped"]; + expect(lines).toEqual(expectedLines); + }); +}); + +describe('drawTitleAndSubtitle', () => { + it('should draw title and subtitle with correct dynamic spacing for single-line texts', () => { + // Create a fake canvas context with spy functions for fillText. + const fillTextMock = vi.fn(); + const fakeCtx = { + measureText: (text: string) => ({ width: text.length * 10 }), + fillText: fillTextMock, + save: vi.fn(), + restore: vi.fn(), + font: '', + } as unknown as import('canvas').CanvasRenderingContext2D; + + const x = 50; // center x-coordinate + const canvasHeight = 200; // total canvas height for vertical centering + const maxWidth = 100; + const titleFont = "bold 60px Nunito, sans-serif"; + const subtitleFont = "normal 40px Nunito, sans-serif"; + const titleLineHeight = 70; // approximate height for each title line + const subtitleLineHeight = 50; // approximate height for each subtitle line + const baseGap = 20; + const gapExtraPerLine = 10; + + // Use simple one-word texts so that each wraps into a single line. + const title = "Hello"; + const subtitle = "World"; + + // Expected calculations: + // - getWrappedLines("Hello") returns ["Hello"] → titleBlockHeight = 1 * 70 = 70. + // - getWrappedLines("World") returns ["World"] → subtitleBlockHeight = 1 * 50 = 50. + // - Gap = baseGap + (1 - 1) * gapExtraPerLine = 20. + // - Total block height = 70 + 20 + 50 = 140. + // - startY = (canvasHeight - totalBlockHeight) / 2 = (200 - 140) / 2 = 30. + // Then: + // Title is drawn at: 30 + (70 / 2) = 65. + // Subtitle is drawn at: 30 + 70 + 20 + (50 / 2) = 145. + + drawTitleAndSubtitle( + fakeCtx, + title, + subtitle, + x, + canvasHeight, + maxWidth, + titleFont, + subtitleFont, + titleLineHeight, + subtitleLineHeight, + baseGap, + gapExtraPerLine + ); + + expect(fillTextMock).toHaveBeenCalledTimes(2); + expect(fillTextMock).toHaveBeenNthCalledWith(1, "Hello", x, 65); + expect(fillTextMock).toHaveBeenNthCalledWith(2, "World", x, 145); + }); +}); + +describe('generateFeaturedImage', () => { + // Use a temporary directory for output during tests. + const tempOutputDir = path.join(__dirname, 'temp-test-generated'); + + afterEach(async () => { + // Clean up the temporary directory after each test. + await fs.remove(tempOutputDir); + }); + + it('should generate an image file', async () => { + const title = "Test Title"; + const subtitle = "Test Subtitle"; + const outputPath = path.join(tempOutputDir, 'test.png'); + + await generateFeaturedImage(title, subtitle, outputPath); + + // Verify that the file was created. + const fileExists = await fs.pathExists(outputPath); + expect(fileExists).toBe(true); + + // Optionally, check that the file is not empty. + const stats = await fs.stat(outputPath); + expect(stats.size).toBeGreaterThan(0); + }); +}); diff --git a/src/util/generateFeaturedImage.tsx b/src/util/generateFeaturedImage.tsx new file mode 100644 index 000000000..ea1b96e26 --- /dev/null +++ b/src/util/generateFeaturedImage.tsx @@ -0,0 +1,188 @@ +import { createCanvas, loadImage, CanvasRenderingContext2D } from 'canvas'; +import fs from 'fs-extra'; +import path from 'path'; + +/** + * Generates a featured image by overlaying title and subtitle on a predefined background. + * + * @param title - The blog post title. + * @param subtitle - The blog post subtitle. + * @param outputPath - The output file path (e.g., /public/generated-featured/slug.png). + */ +export async function generateFeaturedImage( + title: string, + subtitle: string, + outputPath: string +): Promise { + if (fs.existsSync(outputPath)) { + // Skip the generation if the file already exists. + return; + } + + const width = 1200; + const height = 628; + const canvas = createCanvas(width, height); + const ctx = canvas.getContext('2d'); + + // Use your provided background path: + const backgroundPath = path.resolve( + __dirname, + '../', + '../', + 'static', + 'images', + 'blog', + 'blog-background.png' + ); + const background = await loadImage(backgroundPath); + ctx.drawImage(background, 0, 0, width, height); + + // Set common text properties + ctx.textAlign = 'center'; + ctx.fillStyle = '#ffffff'; + + const maxTextWidth = width * 0.8; + + // Define fonts and line heights: + const titleFont = 'bold 60px Nunito, sans-serif'; + const subtitleFont = 'normal 40px Nunito, sans-serif'; + const titleLineHeight = 70; // approximate height for each title line + const subtitleLineHeight = 50; // approximate height for each subtitle line + + // Define the gap settings: + // baseGap is the gap when the title is a single line; + // gapExtraPerLine is added for each extra title line. + const baseGap = 20; + const gapExtraPerLine = 10; + + // Draw both title and subtitle together with dynamic spacing: + drawTitleAndSubtitle( + ctx, + title, + subtitle, + width / 2, // center x + height, // canvas height (for vertical centering) + maxTextWidth, + titleFont, + subtitleFont, + titleLineHeight, + subtitleLineHeight, + baseGap, + gapExtraPerLine + ); + + // Ensure the output directory exists and write the file + await fs.ensureDir(path.dirname(outputPath)); + const buffer = canvas.toBuffer('image/png'); + await fs.writeFile(outputPath, buffer); +} + +/** + * Splits the text into an array of wrapped lines given a maximum width. + * + * @param ctx - The canvas rendering context. + * @param text - The text to wrap. + * @param maxWidth - The maximum allowed width for a line. + * @returns An array of wrapped lines. + */ +export function getWrappedLines( + ctx: CanvasRenderingContext2D, + text: string, + maxWidth: number +): string[] { + const words = text.split(' '); + let line = ''; + const lines: string[] = []; + + for (let n = 0; n < words.length; n++) { + const testLine = line + words[n] + ' '; + const metrics = ctx.measureText(testLine); + if (metrics.width > maxWidth && n > 0) { + lines.push(line.trim()); + line = words[n] + ' '; + } else { + line = testLine; + } + } + lines.push(line.trim()); + return lines; +} + +/** + * Draws the title and subtitle on the canvas with dynamic spacing between them. + * + * The gap between the title and subtitle is computed as: + * gap = baseGap + (number_of_title_lines - 1) * gapExtraPerLine + * + * The combined text block (title block + gap + subtitle block) is then centered vertically. + * + * @param ctx - The canvas rendering context. + * @param title - The title text. + * @param subtitle - The subtitle text. + * @param x - The x-coordinate for centered text. + * @param canvasHeight - The total height of the canvas. + * @param maxWidth - The maximum allowed width for the text. + * @param titleFont - The font string for the title. + * @param subtitleFont - The font string for the subtitle. + * @param titleLineHeight - The line height for the title. + * @param subtitleLineHeight - The line height for the subtitle. + * @param baseGap - The base gap between title and subtitle for a single-line title. + * @param gapExtraPerLine - Additional gap for each extra title line. + */ +export function drawTitleAndSubtitle( + ctx: CanvasRenderingContext2D, + title: string, + subtitle: string, + x: number, + canvasHeight: number, + maxWidth: number, + titleFont: string, + subtitleFont: string, + titleLineHeight: number, + subtitleLineHeight: number, + baseGap: number, + gapExtraPerLine: number +): void { + // Get wrapped title lines + ctx.save(); + ctx.font = titleFont; + const titleLines = getWrappedLines(ctx, title, maxWidth); + ctx.restore(); + + // Get wrapped subtitle lines + ctx.save(); + ctx.font = subtitleFont; + const subtitleLines = getWrappedLines(ctx, subtitle, maxWidth); + ctx.restore(); + + // Calculate the dynamic gap: larger for multi-line titles. + const gap = baseGap + (titleLines.length - 1) * gapExtraPerLine; + + const titleBlockHeight = titleLines.length * titleLineHeight; + const subtitleBlockHeight = subtitleLines.length * subtitleLineHeight; + const totalBlockHeight = titleBlockHeight + gap + subtitleBlockHeight; + + // Compute the starting y so that the entire block is vertically centered. + const startY = (canvasHeight - totalBlockHeight) / 2; + + // Draw title lines + ctx.save(); + ctx.font = titleFont; + titleLines.forEach((line, index) => { + // Adding half the line height centers each line vertically. + ctx.fillText(line, x, startY + index * titleLineHeight + titleLineHeight / 2); + }); + ctx.restore(); + + // Draw subtitle lines below the title block plus the gap. + ctx.save(); + ctx.font = subtitleFont; + subtitleLines.forEach((line, index) => { + ctx.fillText( + line, + x, + startY + titleBlockHeight + gap + index * subtitleLineHeight + subtitleLineHeight / 2 + ); + }); + ctx.restore(); +} diff --git a/static/images/blog/Adoptium-to-promote-broad-range-of-compatible-OpenJDK-builds/banner.png b/static/images/blog/Adoptium-to-promote-broad-range-of-compatible-OpenJDK-builds/banner.png new file mode 100644 index 000000000..60103529d Binary files /dev/null and b/static/images/blog/Adoptium-to-promote-broad-range-of-compatible-OpenJDK-builds/banner.png differ diff --git a/static/images/blog/Reproducible-Comparison-Builds/banner.png b/static/images/blog/Reproducible-Comparison-Builds/banner.png new file mode 100644 index 000000000..6f9f7d70f Binary files /dev/null and b/static/images/blog/Reproducible-Comparison-Builds/banner.png differ diff --git a/static/images/blog/a-short-exploration-of-java-class-pre-initialization/banner.png b/static/images/blog/a-short-exploration-of-java-class-pre-initialization/banner.png new file mode 100644 index 000000000..987aef8ae Binary files /dev/null and b/static/images/blog/a-short-exploration-of-java-class-pre-initialization/banner.png differ diff --git a/static/images/blog/a-summary-of-the-july-2022-retrospectives/banner.png b/static/images/blog/a-summary-of-the-july-2022-retrospectives/banner.png new file mode 100644 index 000000000..7b1778fad Binary files /dev/null and b/static/images/blog/a-summary-of-the-july-2022-retrospectives/banner.png differ diff --git a/static/images/blog/adoptium-automated-deployment-of-nagios/banner.png b/static/images/blog/adoptium-automated-deployment-of-nagios/banner.png new file mode 100644 index 000000000..bae48f763 Binary files /dev/null and b/static/images/blog/adoptium-automated-deployment-of-nagios/banner.png differ diff --git a/static/images/blog/adoptium-celebrates-first-release/banner.png b/static/images/blog/adoptium-celebrates-first-release/banner.png new file mode 100644 index 000000000..2836d9e30 Binary files /dev/null and b/static/images/blog/adoptium-celebrates-first-release/banner.png differ diff --git a/static/images/blog/adoptium-infrastructure-management-with-nagios/banner.png b/static/images/blog/adoptium-infrastructure-management-with-nagios/banner.png new file mode 100644 index 000000000..a7698a87f Binary files /dev/null and b/static/images/blog/adoptium-infrastructure-management-with-nagios/banner.png differ diff --git a/static/images/blog/adoptium-reproducible-builds/banner.png b/static/images/blog/adoptium-reproducible-builds/banner.png new file mode 100644 index 000000000..fd8e23353 Binary files /dev/null and b/static/images/blog/adoptium-reproducible-builds/banner.png differ diff --git a/static/images/blog/adoptium-reproducible-verification-builds/banner.png b/static/images/blog/adoptium-reproducible-verification-builds/banner.png new file mode 100644 index 000000000..6a3a53d97 Binary files /dev/null and b/static/images/blog/adoptium-reproducible-verification-builds/banner.png differ diff --git a/static/images/blog/adoptium-welcomes-google/banner.png b/static/images/blog/adoptium-welcomes-google/banner.png new file mode 100644 index 000000000..46a13667a Binary files /dev/null and b/static/images/blog/adoptium-welcomes-google/banner.png differ diff --git a/static/images/blog/adoptium-welcomes-rivos/banner.png b/static/images/blog/adoptium-welcomes-rivos/banner.png new file mode 100644 index 000000000..8ac899332 Binary files /dev/null and b/static/images/blog/adoptium-welcomes-rivos/banner.png differ diff --git a/static/images/blog/aqavit-graduation-ceremony/banner.png b/static/images/blog/aqavit-graduation-ceremony/banner.png new file mode 100644 index 000000000..2bb986bf9 Binary files /dev/null and b/static/images/blog/aqavit-graduation-ceremony/banner.png differ diff --git a/static/images/blog/aqavit-scope/banner.png b/static/images/blog/aqavit-scope/banner.png new file mode 100644 index 000000000..780c6e17d Binary files /dev/null and b/static/images/blog/aqavit-scope/banner.png differ diff --git a/static/images/blog/availability-of-jdk8u352-b05-ea/banner.png b/static/images/blog/availability-of-jdk8u352-b05-ea/banner.png new file mode 100644 index 000000000..ff16dfe42 Binary files /dev/null and b/static/images/blog/availability-of-jdk8u352-b05-ea/banner.png differ diff --git a/static/images/blog/blog-background.png b/static/images/blog/blog-background.png new file mode 100644 index 000000000..b57cf684e Binary files /dev/null and b/static/images/blog/blog-background.png differ diff --git a/static/images/blog/early-access-builds-feb2024/banner.png b/static/images/blog/early-access-builds-feb2024/banner.png new file mode 100644 index 000000000..c0aefc47f Binary files /dev/null and b/static/images/blog/early-access-builds-feb2024/banner.png differ diff --git a/static/images/blog/early-access-builds/banner.png b/static/images/blog/early-access-builds/banner.png new file mode 100644 index 000000000..60cb98863 Binary files /dev/null and b/static/images/blog/early-access-builds/banner.png differ diff --git a/static/images/blog/eclipse-temurin-11020.1-and-1708.1/banner.png b/static/images/blog/eclipse-temurin-11020.1-and-1708.1/banner.png new file mode 100644 index 000000000..bd9b5e5c0 Binary files /dev/null and b/static/images/blog/eclipse-temurin-11020.1-and-1708.1/banner.png differ diff --git a/static/images/blog/eclipse-temurin-19-available/banner.png b/static/images/blog/eclipse-temurin-19-available/banner.png new file mode 100644 index 000000000..a332967ce Binary files /dev/null and b/static/images/blog/eclipse-temurin-19-available/banner.png differ diff --git a/static/images/blog/eclipse-temurin-21-and-22-available-on-riscv/banner.png b/static/images/blog/eclipse-temurin-21-and-22-available-on-riscv/banner.png new file mode 100644 index 000000000..a3400335f Binary files /dev/null and b/static/images/blog/eclipse-temurin-21-and-22-available-on-riscv/banner.png differ diff --git a/static/images/blog/eclipse-temurin-23-available/banner.png b/static/images/blog/eclipse-temurin-23-available/banner.png new file mode 100644 index 000000000..c36cec09b Binary files /dev/null and b/static/images/blog/eclipse-temurin-23-available/banner.png differ diff --git a/static/images/blog/eclipse-temurin-8u312-11013-and-1701-available/banner.png b/static/images/blog/eclipse-temurin-8u312-11013-and-1701-available/banner.png new file mode 100644 index 000000000..9e2b008f9 Binary files /dev/null and b/static/images/blog/eclipse-temurin-8u312-11013-and-1701-available/banner.png differ diff --git a/static/images/blog/eclipse-temurin-8u342-11016-1704-and-1802-available/banner.png b/static/images/blog/eclipse-temurin-8u342-11016-1704-and-1802-available/banner.png new file mode 100644 index 000000000..a3d2c1a8f Binary files /dev/null and b/static/images/blog/eclipse-temurin-8u342-11016-1704-and-1802-available/banner.png differ diff --git a/static/images/blog/eclipse-temurin-8u362-11018-1706-and-1902-available/banner.png b/static/images/blog/eclipse-temurin-8u362-11018-1706-and-1902-available/banner.png new file mode 100644 index 000000000..62b038a6b Binary files /dev/null and b/static/images/blog/eclipse-temurin-8u362-11018-1706-and-1902-available/banner.png differ diff --git a/static/images/blog/eclipse-temurin-8u372-11019-1707-and-2001-available/banner.png b/static/images/blog/eclipse-temurin-8u372-11019-1707-and-2001-available/banner.png new file mode 100644 index 000000000..bcfce7525 Binary files /dev/null and b/static/images/blog/eclipse-temurin-8u372-11019-1707-and-2001-available/banner.png differ diff --git a/static/images/blog/eclipse-temurin-8u382-11020-1708-and-2002-available/banner.png b/static/images/blog/eclipse-temurin-8u382-11020-1708-and-2002-available/banner.png new file mode 100644 index 000000000..63ce57ce0 Binary files /dev/null and b/static/images/blog/eclipse-temurin-8u382-11020-1708-and-2002-available/banner.png differ diff --git a/static/images/blog/eclipse-temurin-8u392-11021-1709-and-2101-available/banner.png b/static/images/blog/eclipse-temurin-8u392-11021-1709-and-2101-available/banner.png new file mode 100644 index 000000000..9322f419c Binary files /dev/null and b/static/images/blog/eclipse-temurin-8u392-11021-1709-and-2101-available/banner.png differ diff --git a/static/images/blog/eclipse-temurin-8u402-11022-1710-and-2102-available/banner.png b/static/images/blog/eclipse-temurin-8u402-11022-1710-and-2102-available/banner.png new file mode 100644 index 000000000..0741c9cbb Binary files /dev/null and b/static/images/blog/eclipse-temurin-8u402-11022-1710-and-2102-available/banner.png differ diff --git a/static/images/blog/eclipse-temurin-8u412-11023-1711-2102-2201-available/banner.png b/static/images/blog/eclipse-temurin-8u412-11023-1711-2102-2201-available/banner.png new file mode 100644 index 000000000..37d0f0f05 Binary files /dev/null and b/static/images/blog/eclipse-temurin-8u412-11023-1711-2102-2201-available/banner.png differ diff --git a/static/images/blog/eclipse-temurin-8u422-11024-1712-2104-2202-available/banner.png b/static/images/blog/eclipse-temurin-8u422-11024-1712-2104-2202-available/banner.png new file mode 100644 index 000000000..5ddeb3a34 Binary files /dev/null and b/static/images/blog/eclipse-temurin-8u422-11024-1712-2104-2202-available/banner.png differ diff --git a/static/images/blog/eclipse-temurin-8u432-11025-1713-2105-2301-available/banner.png b/static/images/blog/eclipse-temurin-8u432-11025-1713-2105-2301-available/banner.png new file mode 100644 index 000000000..ffcc26eb7 Binary files /dev/null and b/static/images/blog/eclipse-temurin-8u432-11025-1713-2105-2301-available/banner.png differ diff --git a/static/images/blog/eclipse-temurin-jres-are-back/banner.png b/static/images/blog/eclipse-temurin-jres-are-back/banner.png new file mode 100644 index 000000000..50b5a6911 Binary files /dev/null and b/static/images/blog/eclipse-temurin-jres-are-back/banner.png differ diff --git a/static/images/blog/eclipse-temurin-linux-installers-available/banner.png b/static/images/blog/eclipse-temurin-linux-installers-available/banner.png new file mode 100644 index 000000000..646ab8df9 Binary files /dev/null and b/static/images/blog/eclipse-temurin-linux-installers-available/banner.png differ diff --git a/static/images/blog/emt4j-an-easier-upgrade-for-java-applications/banner.png b/static/images/blog/emt4j-an-easier-upgrade-for-java-applications/banner.png new file mode 100644 index 000000000..a3a711b62 Binary files /dev/null and b/static/images/blog/emt4j-an-easier-upgrade-for-java-applications/banner.png differ diff --git a/static/images/blog/external_audit/banner.png b/static/images/blog/external_audit/banner.png new file mode 100644 index 000000000..9fef23126 Binary files /dev/null and b/static/images/blog/external_audit/banner.png differ diff --git a/static/images/blog/gpg-signed-releases/banner.png b/static/images/blog/gpg-signed-releases/banner.png new file mode 100644 index 000000000..0316c9882 Binary files /dev/null and b/static/images/blog/gpg-signed-releases/banner.png differ diff --git a/static/images/blog/jlink-to-produce-own-runtime/banner.png b/static/images/blog/jlink-to-produce-own-runtime/banner.png new file mode 100644 index 000000000..2e4a7f57a Binary files /dev/null and b/static/images/blog/jlink-to-produce-own-runtime/banner.png differ diff --git a/static/images/blog/march-2024-jdk22-release/banner.png b/static/images/blog/march-2024-jdk22-release/banner.png new file mode 100644 index 000000000..4440f968a Binary files /dev/null and b/static/images/blog/march-2024-jdk22-release/banner.png differ diff --git a/static/images/blog/peeling-the-big-onion/banner.png b/static/images/blog/peeling-the-big-onion/banner.png new file mode 100644 index 000000000..20fcd6fa9 Binary files /dev/null and b/static/images/blog/peeling-the-big-onion/banner.png differ diff --git a/static/images/blog/removal-of-centos7-eclipse-temurin-images/banner.png b/static/images/blog/removal-of-centos7-eclipse-temurin-images/banner.png new file mode 100644 index 000000000..543258174 Binary files /dev/null and b/static/images/blog/removal-of-centos7-eclipse-temurin-images/banner.png differ diff --git a/static/images/blog/secure-software-development/banner.png b/static/images/blog/secure-software-development/banner.png new file mode 100644 index 000000000..50d7483e2 Binary files /dev/null and b/static/images/blog/secure-software-development/banner.png differ diff --git a/static/images/blog/slsa2-temurin/banner.png b/static/images/blog/slsa2-temurin/banner.png new file mode 100644 index 000000000..f3cab1b07 Binary files /dev/null and b/static/images/blog/slsa2-temurin/banner.png differ diff --git a/static/images/blog/slsabuild3-temurin/banner.png b/static/images/blog/slsabuild3-temurin/banner.png new file mode 100644 index 000000000..33e13527e Binary files /dev/null and b/static/images/blog/slsabuild3-temurin/banner.png differ diff --git a/static/images/blog/temurin21-delay/banner.png b/static/images/blog/temurin21-delay/banner.png new file mode 100644 index 000000000..f793fce24 Binary files /dev/null and b/static/images/blog/temurin21-delay/banner.png differ diff --git a/static/images/blog/using-jlink-in-dockerfiles/banner.png b/static/images/blog/using-jlink-in-dockerfiles/banner.png new file mode 100644 index 000000000..faa96c901 Binary files /dev/null and b/static/images/blog/using-jlink-in-dockerfiles/banner.png differ diff --git a/static/images/new-article-card-img.png b/static/images/new-article-card-img.png deleted file mode 100644 index 9d3182662..000000000 Binary files a/static/images/new-article-card-img.png and /dev/null differ diff --git a/static/images/social-image.jpg b/static/images/social-image.jpg new file mode 100644 index 000000000..9930d02e5 Binary files /dev/null and b/static/images/social-image.jpg differ diff --git a/static/images/social-image.png b/static/images/social-image.png deleted file mode 100644 index f0dd5cc11..000000000 Binary files a/static/images/social-image.png and /dev/null differ diff --git a/vitest-setup.tsx b/vitest-setup.tsx index 7c3917de8..def42f808 100644 --- a/vitest-setup.tsx +++ b/vitest-setup.tsx @@ -22,6 +22,7 @@ const mdxMock = number => { title: `Mock Title ${number}`, description: `Mock Description ${number}`, date: "2021-01-01", + tags: ["mock-tag-1", "release-notes"], }, }, }