From 131defff5d908f9918b8223b4139dbbd161faa47 Mon Sep 17 00:00:00 2001 From: David Tuite Date: Fri, 11 Mar 2022 09:18:36 +0000 Subject: [PATCH] Create changelog permalinks (#622) * Create changelog permalinks * Use HTML in feed * Use plain in RSS * Add logo to static folder --- gatsby-node.js | 18 ++++ src/components/changelog/ChangeSet.js | 103 ++++++++++++++----- src/gatsby/rssFeedPlugin.js | 26 ++--- src/pageCreation/createListPagesFromQuery.js | 4 +- src/queries/gatsbyNodeQueries.js | 2 +- src/templates/ChangeSet.js | 70 +++++++++++++ src/templates/ChangelogList.js | 5 +- static/images/roadie-r-square-border-96.png | Bin 0 -> 6461 bytes 8 files changed, 184 insertions(+), 44 deletions(-) create mode 100644 src/templates/ChangeSet.js create mode 100644 static/images/roadie-r-square-border-96.png diff --git a/gatsby-node.js b/gatsby-node.js index bfaaaf396..8557a1aeb 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -135,6 +135,24 @@ exports.createPages = async ({ graphql, actions }) => { itemsPerPage: 20, }); + await createPagesFromQuery({ + templatePath: './src/templates/ChangeSet.js', + query: CHANGELOG_QUERY, + resultName: 'result.edges', + actions, + graphql, + basePath: '/changelog/', + processor: ({ node }, component) => { + return { + path: `/changelog/${node.slug}/`, + component, + context: { + slug: node.slug, + }, + }; + }, + }); + await createLatestLegalNotices({ graphql, actions, diff --git a/src/components/changelog/ChangeSet.js b/src/components/changelog/ChangeSet.js index 12766fe07..4df405d8a 100644 --- a/src/components/changelog/ChangeSet.js +++ b/src/components/changelog/ChangeSet.js @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import classnames from 'classnames'; -import kebabCase from 'lodash/kebabCase'; import format from 'date-fns/format'; +import { TextLink as Link } from 'components'; const ReleasedAt = ({ releasedAt }) => ( ); +const Title = ({ isCollapsible, toggleOpen, title, slug }) => { + let className = 'tracking-tight text-gray-900 text-base sm:text-xl'; + + if (isCollapsible) { + className = className + ' hover:underline'; + } + + const heading = ( +

+ {title} +

+ ); + + if (!isCollapsible) return heading; + + return ( + + ); +}; + +const CollapsiblePart = ({ description, isOpen, isCollapsible, slug }) => { + if (!description) return null; + + let className = 'mt-6 prose max-w-none'; + if (isCollapsible) { + className = classnames(className, { + 'h-0 hidden': !isOpen, + }); + } + + return ( +
+
+ + {isCollapsible && ( +
+ Permalink +
+ )} +
+ ); +}; + +const Wrapper = ({ children, isCollapsible }) => { + if (isCollapsible) { + return ( + <> +
+
  • + {children} +
  • + + ); + } + + return children; +}; + const ChangeSet = ({ releasedAt, description, title, + isCollapsible = true, + slug, }) => { const [isOpen, setOpen] = useState(false); const toggleOpen = () => setOpen(!isOpen); return ( - <> -
    -
  • -
    - -
    - - - {description && ( -
    - )} -
    + +
    + +
    + + <CollapsiblePart + description={description} + isOpen={isOpen} + slug={slug} + isCollapsible={isCollapsible} + /> </div> - </li> - </> + </div> + </Wrapper> ); }; diff --git a/src/gatsby/rssFeedPlugin.js b/src/gatsby/rssFeedPlugin.js index 0b293cd28..7af9f1828 100644 --- a/src/gatsby/rssFeedPlugin.js +++ b/src/gatsby/rssFeedPlugin.js @@ -51,18 +51,18 @@ const blogFeed = { const changelogFeed = { serialize: ({ query: { site, changeSets } }) => { - return changeSets.edges.map(({ node }) => { - return { - title: node.title, - date: node.releasedAt, - description: get(node, 'description.childMarkdownRemark.rawMarkdownBody'), - url: site.siteMetadata.siteUrl + `/changelog/`, - guid: site.siteMetadata.siteUrl + `/changelog/${node.slug}/`, - custom_elements: [{ - 'content:encoded': get(node, 'description.childMarkdownRemark.html'), - }], - }; - }); + return changeSets.edges.map(({ node }) => ({ + title: node.title, + date: node.releasedAt, + // This is plain so we can push it out in Slack messages, which do not support HTML + // or markdown. + description: get(node, 'description.childMarkdownRemark.excerpt'), + url: site.siteMetadata.siteUrl + `/changelog/${node.slug}/`, + guid: site.siteMetadata.siteUrl + `/changelog/${node.slug}/`, + custom_elements: [{ + 'content:encoded': get(node, 'description.childMarkdownRemark.html'), + }], + })); }, query: ` @@ -78,7 +78,7 @@ const changelogFeed = { description { childMarkdownRemark { html - rawMarkdownBody + excerpt(pruneLength: 160, format: PLAIN) } } } diff --git a/src/pageCreation/createListPagesFromQuery.js b/src/pageCreation/createListPagesFromQuery.js index b5b155a76..e3a3d23f7 100644 --- a/src/pageCreation/createListPagesFromQuery.js +++ b/src/pageCreation/createListPagesFromQuery.js @@ -1,7 +1,7 @@ const path = require(`path`); const get = require('lodash/get'); -const createPagesFromQuery = async ({ +const createListPagesFromQuery = async ({ graphql, templatePath, query, @@ -36,4 +36,4 @@ const createPagesFromQuery = async ({ }); }; -module.exports = createPagesFromQuery; +module.exports = createListPagesFromQuery; diff --git a/src/queries/gatsbyNodeQueries.js b/src/queries/gatsbyNodeQueries.js index 572d6e100..457c04ddc 100644 --- a/src/queries/gatsbyNodeQueries.js +++ b/src/queries/gatsbyNodeQueries.js @@ -116,7 +116,7 @@ module.exports.CHANGELOG_QUERY = ` ) { edges { node { - title + slug } } } diff --git a/src/templates/ChangeSet.js b/src/templates/ChangeSet.js new file mode 100644 index 000000000..0dd9863f5 --- /dev/null +++ b/src/templates/ChangeSet.js @@ -0,0 +1,70 @@ +import React from 'react'; +import { graphql } from 'gatsby'; +import { SEO, SitewideHeader, SitewideFooter, TextLink as Link } from 'components'; +import { ChangeSet } from 'components/changelog'; +import { ArrowCircleLeftIcon } from '@heroicons/react/outline'; +import get from 'lodash/get'; + +const ChangeSetPage = ({ + data: { + site: { + siteMetadata: { + title: siteTitle, + }, + }, + + changeSet, + }, +}) => ( + <> + <SEO + title={`Release: ${changeSet.title} | ${siteTitle}`} + description={get(changeSet, 'description.childMarkdownRemark.excerpt', 'Read the changelog')} + /> + + <SitewideHeader /> + + <main className="max-w-5xl sm:max-w-4xl mx-auto px-8 pb-20 pt-8 lg:pb-28"> + <ChangeSet + title={changeSet.title} + releasedAt={changeSet.releasedAt} + description={get(changeSet, 'description.childMarkdownRemark', {})} + isCollapsible={false} + /> + + <div className="mt-6 list-reset lg:flex items-start"> + <div className="md:w-32 lg:w-48 flex-shrink-0" /> + <Link to="/changelog/" color="primary"> + <ArrowCircleLeftIcon className="mr-1 h-6 w-6 inline" /> + <span className="align-middle">Browse the full changlog</span> + </Link> + </div> + </main> + + <SitewideFooter /> + </> +); + +export default ChangeSetPage; + +export const pageQuery = graphql` + query ChangeSetBySlug($slug: String!) { + changeSet: contentfulChangeSet(slug: { eq: $slug }) { + title + releasedAt + slug + description { + childMarkdownRemark { + html + excerpt(pruneLength: 160, format: PLAIN) + } + } + } + + site { + siteMetadata { + title + } + } + } +`; diff --git a/src/templates/ChangelogList.js b/src/templates/ChangelogList.js index 8a92d5a59..8cb55b29a 100644 --- a/src/templates/ChangelogList.js +++ b/src/templates/ChangelogList.js @@ -39,12 +39,14 @@ const Changelog = ({ /> <ul className="container mt-12 mb-8"> - {changeSets.map(({ node: { title, releasedAt, description } }) => ( + {changeSets.map(({ node: { title, releasedAt, description, slug } }) => ( <ChangeSet key={`${title} ${releasedAt}`} title={title} releasedAt={releasedAt} description={description && description.childMarkdownRemark} + slug={slug} + isCollapsible={true} /> ))} </ul> @@ -72,6 +74,7 @@ export const pageQuery = graphql` node { title releasedAt + slug description { childMarkdownRemark { html diff --git a/static/images/roadie-r-square-border-96.png b/static/images/roadie-r-square-border-96.png new file mode 100644 index 0000000000000000000000000000000000000000..42aa2f0fb2d9e54adb71fb14607628faa8e685f3 GIT binary patch literal 6461 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 J>Q`oA{{ydu*A@T( literal 0 HcmV?d00001