diff --git a/.storybook/main.js b/.storybook/main.js index 671271f9..d46de579 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -63,9 +63,6 @@ module.exports = { config.resolve.alias['../../../hooks/use-addons-search'] = require.resolve( './use-addons-search.mock.js' ); - config.resolve.alias['../../../hooks/use-addons-related-tags'] = require.resolve( - './use-addons-related-tags.mock.js' - ); config.plugins.unshift( new webpack.DefinePlugin({ diff --git a/.storybook/use-addons-related-tags.mock.js b/.storybook/use-addons-related-tags.mock.js deleted file mode 100644 index a8a3a813..00000000 --- a/.storybook/use-addons-related-tags.mock.js +++ /dev/null @@ -1,32 +0,0 @@ -import { useState } from 'react'; - -const relatedTags = [ - { - link: '/notes', - name: '🗒 Notes', - }, - { - link: '/storybook', - name: '📕 Storybook', - }, - { - link: '/qa', - name: '🕵️‍♀️ QA', - }, - { - link: '/prototype', - name: '✨ Prototype', - }, - { - link: '/testing', - name: '✅ Testing', - }, - { - link: '/deploy', - name: '☁️ Deploy', - }, -]; - -export const useAddonsRelatedTags = () => { - return relatedTags; -}; diff --git a/gatsby-node.js b/gatsby-node.js index 031a3db3..7ebbc84d 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -1,15 +1,11 @@ const fs = require('fs'); const path = require('path'); -const remark = require('remark'); -const remarkHTML = require('remark-html'); const { createFilePath } = require(`gatsby-source-filesystem`); const { toc: docsToc } = require('./src/content/docs/toc'); const buildPathWithFramework = require('./src/util/build-path-with-framework'); -const buildTagLinks = require('./src/util/build-tag-links'); - -const processor = remark().use(remarkHTML); +const createAddonsPages = require('./src/util/create-addons-pages'); const githubDocsBaseUrl = 'https://github.com/storybookjs/storybook/tree/next'; const addStateToToc = (items, pathPrefix = '/docs') => @@ -68,21 +64,6 @@ exports.onCreatePage = ({ page, actions }) => { } }; -const addonDetail = ` - id: name - name - displayName - description - icon - authors { - id: username - avatarUrl: gravatarUrl - name: username - } - weeklyDownloads - appearance: verified - verifiedCreator`; - exports.createPages = ({ actions, graphql }) => { const { createRedirect, createPage } = actions; return new Promise((resolve) => { @@ -113,45 +94,6 @@ exports.createPages = ({ actions, graphql }) => { } } } - addons { - addonPages: top(sort: monthlyDownloads) { - ${addonDetail} - tags { - name - displayName - description - icon - } - compatibility { - name - displayName - icon - } - status - readme - publishedAt - repositoryUrl - homepageUrl - } - tagPages: tags(isCategory: false) { - name - displayName - description - icon - addons: top(sort: monthlyDownloads) { - ${addonDetail} - } - } - categoryPages: tags(isCategory: true) { - name - displayName - description - icon - addons: top(sort: monthlyDownloads) { - ${addonDetail} - } - } - } site { siteMetadata { coreFrameworks @@ -159,177 +101,140 @@ exports.createPages = ({ actions, graphql }) => { } } } - `).then( - ({ - data: { - docsPages: { edges: docsPagesEdges }, - releasePages: { edges: releasePagesEdges }, - addons: { addonPages, tagPages, categoryPages }, - site: { - siteMetadata: { coreFrameworks, communityFrameworks }, + `) + .then( + ({ + data: { + docsPages: { edges: docsPagesEdges }, + releasePages: { edges: releasePagesEdges }, + site: { + siteMetadata: { coreFrameworks, communityFrameworks }, + }, }, - }, - }) => { - const sortedReleases = releasePagesEdges.sort( - ({ node: aNode }, { node: bNode }) => - parseFloat(aNode.fields.version) - parseFloat(bNode.fields.version) - ); - let latestRelease; - sortedReleases.forEach(({ node }) => { - const { pageType, iframeSlug, slug, version } = node.fields; - // Data passed to context is available in page queries as GraphQL variables. - const context = { pageType, slug, version }; + }) => { + const sortedReleases = releasePagesEdges.sort( + ({ node: aNode }, { node: bNode }) => + parseFloat(aNode.fields.version) - parseFloat(bNode.fields.version) + ); + let latestRelease; + sortedReleases.forEach(({ node }) => { + const { pageType, iframeSlug, slug, version } = node.fields; + // Data passed to context is available in page queries as GraphQL variables. + const context = { pageType, slug, version }; + + createPage({ + path: slug, + component: path.resolve(`./src/components/screens/ReleasesScreen/ReleasesScreen.js`), + context, + }); - createPage({ - path: slug, - component: path.resolve(`./src/components/screens/ReleasesScreen/ReleasesScreen.js`), - context, - }); + createPage({ + path: iframeSlug, + component: path.resolve( + `./src/components/screens/ReleasesScreen/IframeReleasesScreen.js` + ), + context: { + ...context, + layout: 'iframe', + }, + }); - createPage({ - path: iframeSlug, - component: path.resolve( - `./src/components/screens/ReleasesScreen/IframeReleasesScreen.js` - ), - context: { - ...context, - layout: 'iframe', - }, + if (!node.frontmatter.prerelease) { + latestRelease = node; + } }); - if (!node.frontmatter.prerelease) { - latestRelease = node; + // Leave a /releases/ endpoint, but redirect it to the latest version + if (latestRelease) { + createRedirect({ + fromPath: `/releases/`, + isPermanent: false, + redirectInBrowser: true, + toPath: latestRelease.fields.slug, + }); } - }); - - // Leave a /releases/ endpoint, but redirect it to the latest version - if (latestRelease) { - createRedirect({ - fromPath: `/releases/`, - isPermanent: false, - redirectInBrowser: true, - toPath: latestRelease.fields.slug, - }); - } - - const frameworks = [...coreFrameworks, ...communityFrameworks]; - const docsPagesSlugs = []; - const docsPagesEdgesBySlug = Object.fromEntries( - docsPagesEdges.map((edge) => [edge.node.fields.slug, edge]) - ); - const docsTocByFramework = Object.fromEntries( - frameworks.map((framework) => [ - framework, - addStateToToc(docsTocWithPaths, `/docs/${framework}`), - ]) - ); - const createDocsPages = (tocItems) => { - tocItems.forEach((tocItem, index) => { - const { path: docsPagePath, children } = tocItem; - if (docsPagePath) { - const docEdge = docsPagesEdgesBySlug[docsPagePath]; - - if (docEdge) { - const { pageType, slug } = docEdge.node.fields; - const nextTocItem = tocItems[index + 1]; - - frameworks.forEach((framework) => { - createPage({ - path: buildPathWithFramework(slug, framework), - component: path.resolve(`./src/components/screens/DocsScreen/DocsScreen.tsx`), - context: { - pageType, - layout: 'docs', - slug, - framework, - docsToc: docsTocByFramework[framework], - tocItem, - ...(nextTocItem && - nextTocItem.type === 'bullet-link' && { - nextTocItem, - }), - isFirstTocItem: docsPagesSlugs.length === 0, - }, + const frameworks = [...coreFrameworks, ...communityFrameworks]; + const docsPagesSlugs = []; + const docsPagesEdgesBySlug = Object.fromEntries( + docsPagesEdges.map((edge) => [edge.node.fields.slug, edge]) + ); + const docsTocByFramework = Object.fromEntries( + frameworks.map((framework) => [ + framework, + addStateToToc(docsTocWithPaths, `/docs/${framework}`), + ]) + ); + const createDocsPages = (tocItems) => { + tocItems.forEach((tocItem, index) => { + const { path: docsPagePath, children } = tocItem; + + if (docsPagePath) { + const docEdge = docsPagesEdgesBySlug[docsPagePath]; + + if (docEdge) { + const { pageType, slug } = docEdge.node.fields; + const nextTocItem = tocItems[index + 1]; + + frameworks.forEach((framework) => { + createPage({ + path: buildPathWithFramework(slug, framework), + component: path.resolve(`./src/components/screens/DocsScreen/DocsScreen.tsx`), + context: { + pageType, + layout: 'docs', + slug, + framework, + docsToc: docsTocByFramework[framework], + tocItem, + ...(nextTocItem && + nextTocItem.type === 'bullet-link' && { + nextTocItem, + }), + isFirstTocItem: docsPagesSlugs.length === 0, + }, + }); }); - }); - docsPagesSlugs.push(slug); - } else { - console.log(`Not creating page for '${docsPagePath}'`); + docsPagesSlugs.push(slug); + } else { + console.log(`Not creating page for '${docsPagePath}'`); + } } - } - - if (children) { - createDocsPages(children); - } - }); - }; - createDocsPages(docsTocWithPaths); - const firstDocsPageSlug = docsPagesSlugs[0]; + if (children) { + createDocsPages(children); + } + }); + }; - if (firstDocsPageSlug) { - createRedirect({ - fromPath: `/docs/`, - isPermanent: false, - redirectInBrowser: true, - toPath: buildPathWithFramework(firstDocsPageSlug, frameworks[0]), - }); + createDocsPages(docsTocWithPaths); + const firstDocsPageSlug = docsPagesSlugs[0]; - // Setup a redirect for each framework to the first guide - frameworks.forEach((framework) => { + if (firstDocsPageSlug) { createRedirect({ - fromPath: `/docs/${framework}`, + fromPath: `/docs/`, isPermanent: false, redirectInBrowser: true, - toPath: buildPathWithFramework(firstDocsPageSlug, framework), + toPath: buildPathWithFramework(firstDocsPageSlug, frameworks[0]), }); - }); - } - - addonPages.forEach((addon) => { - createPage({ - path: `/addons/${addon.name}`, - component: path.resolve( - `./src/components/screens/AddonsDetailScreen/AddonsDetailScreen.js` - ), - context: { - ...addon, - tags: buildTagLinks(addon.tags), - readme: processor.processSync(addon.readme).toString(), - }, - }); - }); - - tagPages.forEach((tag) => { - createPage({ - path: `/addons/${tag.name}`, - component: path.resolve(`./src/components/screens/AddonsTagScreen/AddonsTagScreen.js`), - context: { - tag, - name: tag.name, - }, - }); - }); - - categoryPages.forEach((category) => { - createPage({ - path: `/addons/${category.name}`, - component: path.resolve( - `./src/components/screens/AddonsCategoryScreen/AddonsCategoryScreen.js` - ), - context: { - category: category.displayName, - description: category.description, - addons: category.addons, - }, - }); - }); + // Setup a redirect for each framework to the first guide + frameworks.forEach((framework) => { + createRedirect({ + fromPath: `/docs/${framework}`, + isPermanent: false, + redirectInBrowser: true, + toPath: buildPathWithFramework(firstDocsPageSlug, framework), + }); + }); + } + } + ) + .then(() => createAddonsPages({ actions, graphql })) + .then(() => { resolve(); - } - ); + }); }); }; diff --git a/src/components/screens/AddonsTagScreen/AddonsTagScreen.js b/src/components/screens/AddonsTagScreen/AddonsTagScreen.js index 835e2411..1c68599f 100644 --- a/src/components/screens/AddonsTagScreen/AddonsTagScreen.js +++ b/src/components/screens/AddonsTagScreen/AddonsTagScreen.js @@ -12,7 +12,6 @@ import { AddonsAside, AddonsAsideContainer } from '../../layout/addons/AddonsAsi import { AddonsSubheading } from '../../layout/addons/AddonsSubheading'; import { generateBreadcrumb } from '../../../util/generate-breadcrumb'; import buildTagLinks from '../../../util/build-tag-links'; -import { useAddonsRelatedTags } from '../../../hooks/use-addons-related-tags'; const StyledAddonsList = styled(AddonsList)` flex: 1 1 auto; @@ -22,11 +21,11 @@ const RelatedTagsList = styled(TagList)` margin-bottom: 48px; `; -export const AddonsTagScreen = ({ path, pageContext }) => { +export const AddonsTagScreen = ({ path, pageContext, ...props }) => { const { title, ogImageAddons, urls = {} } = useSiteMetadata(); const { home } = urls; const { tag } = pageContext; - const relatedTags = useAddonsRelatedTags(tag.name); + const relatedTags = buildTagLinks(tag.relatedTags || []); const breadcrumb = generateBreadcrumb(); return ( @@ -73,6 +72,13 @@ AddonsTagScreen.propTypes = { displayName: PropTypes.string, icon: PropTypes.string, addons: AddonsList.propTypes.addonItems, + relatedTags: PropTypes.arrayOf( + PropTypes.shape({ + name: PropTypes.string.isRequired, + displayName: PropTypes.string, + icon: PropTypes.string, + }) + ).isRequired, }).isRequired, }).isRequired, }; diff --git a/src/components/screens/AddonsTagScreen/AddonsTagScreen.stories.js b/src/components/screens/AddonsTagScreen/AddonsTagScreen.stories.js index 30c748ca..664ed17a 100644 --- a/src/components/screens/AddonsTagScreen/AddonsTagScreen.stories.js +++ b/src/components/screens/AddonsTagScreen/AddonsTagScreen.stories.js @@ -15,6 +15,32 @@ export const Default = () => ( displayName: 'Notes', icon: '🗒️', addons: addonItemsData, + relatedTags: [ + { + link: '/notes', + name: '🗒 Notes', + }, + { + link: '/storybook', + name: '📕 Storybook', + }, + { + link: '/qa', + name: '🕵️‍♀️ QA', + }, + { + link: '/prototype', + name: '✨ Prototype', + }, + { + link: '/testing', + name: '✅ Testing', + }, + { + link: '/deploy', + name: '☁️ Deploy', + }, + ], }, }} /> diff --git a/src/hooks/use-addons-related-tags.js b/src/hooks/use-addons-related-tags.js deleted file mode 100644 index 3c296e3a..00000000 --- a/src/hooks/use-addons-related-tags.js +++ /dev/null @@ -1,32 +0,0 @@ -/* eslint-env browser */ -import { useState, useEffect } from 'react'; -import buildTagLinks from '../util/build-tag-links'; - -export function useAddonsRelatedTags(query = '') { - const [relatedTags, setRelatedTags] = useState([]); - - useEffect(() => { - fetch('https://boring-heisenberg-43a6ed.netlify.app/', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - query: ` - query { - relatedTags(query: "${query}") { - name - displayName - icon - } - }`, - }), - }) - .then((res) => res.json()) - .then((res) => buildTagLinks(res.data.relatedTags)) - .then(setRelatedTags) - .catch(() => { - return []; - }); - }, [query]); - - return relatedTags; -} diff --git a/src/util/create-addons-pages.js b/src/util/create-addons-pages.js new file mode 100644 index 00000000..ad021512 --- /dev/null +++ b/src/util/create-addons-pages.js @@ -0,0 +1,143 @@ +const path = require('path'); +const remark = require('remark'); +const remarkHTML = require('remark-html'); +const buildTagLinks = require('./build-tag-links'); + +const processor = remark().use(remarkHTML); + +const addonDetail = ` + id: name + name + displayName + description + icon + authors { + id: username + avatarUrl: gravatarUrl + name: username + } + weeklyDownloads + appearance: verified + verifiedCreator`; + +module.exports = function createAddonsPages({ actions, graphql }) { + const { createPage } = actions; + return graphql( + ` + { + addons { + addonPages: top(sort: monthlyDownloads) { + ${addonDetail} + tags { + name + displayName + description + icon + } + compatibility { + name + displayName + icon + } + status + readme + publishedAt + repositoryUrl + homepageUrl + } + categoryPages: tags(isCategory: true) { + name + displayName + description + icon + addons: top(sort: monthlyDownloads) { + ${addonDetail} + } + } + } + } + ` + ) + .then( + ({ + data: { + addons: { addonPages, categoryPages }, + }, + }) => { + addonPages.forEach((addon) => { + createPage({ + path: `/addons/${addon.name}`, + component: path.resolve( + `./src/components/screens/AddonsDetailScreen/AddonsDetailScreen.js` + ), + context: { + ...addon, + tags: buildTagLinks(addon.tags), + readme: processor.processSync(addon.readme).toString(), + }, + }); + }); + + categoryPages.forEach((category) => { + createPage({ + path: `/addons/${category.name}`, + component: path.resolve( + `./src/components/screens/AddonsCategoryScreen/AddonsCategoryScreen.js` + ), + context: { + category: category.displayName, + description: category.description, + addons: category.addons, + }, + }); + }); + } + ) + .then(() => fetchTagPages(createPage, graphql)); +}; + +function fetchTagPages(createPage, graphql, skip = 0) { + return graphql( + `{ + addons { + tagPages: tags(isCategory: false, limit: 30, skip: ${skip}) { + name + displayName + description + icon + relatedTags { + name + displayName + icon + } + addons: top(sort: monthlyDownloads) { + ${addonDetail} + } + } + } + }` + ).then( + ({ + data: { + addons: { tagPages }, + }, + }) => { + if (tagPages && tagPages.length > 0) { + createTagPages(createPage, tagPages); + return fetchTagPages(createPage, graphql, skip + tagPages.length); + } + } + ); +} + +function createTagPages(createPage, tagPages) { + tagPages.forEach((tag) => { + createPage({ + path: `/addons/${tag.name}`, + component: path.resolve(`./src/components/screens/AddonsTagScreen/AddonsTagScreen.js`), + context: { + tag, + }, + }); + }); +}