diff --git a/config.js b/config.js index d41211a32..9688bd476 100644 --- a/config.js +++ b/config.js @@ -21,4 +21,9 @@ module.exports = { moduleName: `_30s`, rollupInputFile: `imports.temp.js`, testModuleFile: `test/_30s.js`, + // Requirable JSONs + requirables: [ + `snippets.json`, + `archivedSnippets.json` + ] }; diff --git a/gatsby-node.js b/gatsby-node.js index 9e8c21e72..570b87f07 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -2,6 +2,14 @@ const path = require(`path`); const { createFilePath } = require(`gatsby-source-filesystem`); const config = require('./config'); +const { getTextualContent, getCodeBlocks, optimizeAllNodes } = require(`./src/docs/util`); + +const requirables = []; + +config.requirables.forEach(fileName => { + requirables.push(require(`./snippet_data/${fileName}`)); +}); + const toKebabCase = str => str && str @@ -9,97 +17,6 @@ const toKebabCase = str => .map(x => x.toLowerCase()) .join('-'); -exports.createPages = ({ graphql, actions }) => { - const { createPage } = actions; - - const snippetPage = path.resolve(`./src/docs/templates/SnippetPage.js`); - const tagPage = path.resolve(`./src/docs/templates/TagPage.js`); - return graphql( - ` - { - allMarkdownRemark( - sort: { fields: [frontmatter___title], order: ASC } - limit: 1000 - ) { - edges { - node { - fields { - slug - } - frontmatter { - tags - } - fileAbsolutePath - } - } - } - } - `, - ).then(result => { - if (result.errors) { - throw result.errors; - } - - // Create individual snippet pages. - const snippets = result.data.allMarkdownRemark.edges; - - snippets.forEach((post, index) => { - if(post.node.fileAbsolutePath.indexOf('README') !== -1) - return; - if (post.node.fileAbsolutePath.indexOf(config.snippetArchivePath) === -1) - createPage({ - path: `/snippet${post.node.fields.slug}`, - component: snippetPage, - context: { - slug: post.node.fields.slug, - scope: `./snippets`, - }, - }); - else - createPage({ - path: `/archive${post.node.fields.slug}`, - component: snippetPage, - context: { - slug: post.node.fields.slug, - scope: `./snippets_archive`, - }, - }); - }); - - // Create tag pages. - const tags = snippets.reduce((acc, post) => { - if (!post.node.frontmatter || !post.node.frontmatter.tags) return acc; - const primaryTag = post.node.frontmatter.tags.split(',')[0]; - if (!acc.includes(primaryTag)) acc.push(primaryTag); - return acc; - }, []); - - tags.forEach(tag => { - const tagPath = `/tag/${toKebabCase(tag)}/`; - const tagRegex = `/^\\s*${tag}/`; - createPage({ - path: tagPath, - component: tagPage, - context: { - tag, - tagRegex, - }, - }); - }); - - createPage({ - path: `/beginner`, - component: tagPage, - context: { - tag: `beginner snippets`, - tagRegex: `/beginner/`, - }, - }); - - return null; - }); -}; - exports.onCreateNode = ({ node, actions, getNode }) => { const { createNodeField } = actions; @@ -112,3 +29,231 @@ exports.onCreateNode = ({ node, actions, getNode }) => { }); } }; + +exports.sourceNodes = ({ actions, createNodeId, createContentDigest, getNodesByType }) => { + const { createTypes, createNode } = actions; + const typeDefs = ` + type Snippet implements Node { + html: HtmlData + tags: TagData + title: String + code: CodeData + id: String + slug: String + path: String + text: TextData + archived: Boolean + } + + type HtmlData @infer { + full: String + text: String + code: String + example: String + } + + type CodeData @infer { + src: String + example: String + } + + type TextData @infer { + full: String + short: String + } + + type TagData @infer { + primary: String + all: [String] + } + `; + createTypes(typeDefs); + + const markdownNodes = getNodesByType('MarkdownRemark'); + + const snippetNodes = requirables + .reduce((acc, sArr) => { + const archivedScope = sArr.meta.scope.indexOf('archive') !== -1; + return ({ + ...acc, + ...sArr.data.reduce((snippets, snippet) => { + return ({ + ...snippets, + [snippet.id]: { ...snippet, archived: archivedScope} + }); + }, {}) + }); + }, {}); + + Object.entries(snippetNodes).forEach(([id, sNode]) => { + let mNode = markdownNodes.find(mN => mN.frontmatter.title === id); + let nodeContent = { + id, + tags: { + all: sNode.attributes.tags, + primary: sNode.attributes.tags[0] + }, + title: mNode.frontmatter.title, + code: { + src: sNode.attributes.codeBlocks.es6, + example: sNode.attributes.codeBlocks.example + }, + slug: mNode.fields.slug, + path: mNode.fileAbsolutePath, + text: { + full: sNode.attributes.text, + short: sNode.attributes.text.slice(0, sNode.attributes.text.indexOf('\n\n')) + }, + archived: sNode.archived + }; + createNode({ + id: createNodeId(`snippet-${sNode.meta.hash}`), + parent: null, + children: [], + internal: { + type: 'Snippet', + content: JSON.stringify(nodeContent), + contentDigest: createContentDigest(nodeContent) + }, + ...nodeContent + }); + }); + +}; + +exports.createResolvers = ({ createResolvers }) => createResolvers({ + Snippet: { + html: { + resolve: async (source, _, context, info) => { + const resolver = info.schema.getType("MarkdownRemark").getFields()["html"].resolve; + const node = await context.nodeModel.nodeStore.getNodesByType('MarkdownRemark').filter(v => v.frontmatter.title === source.title)[0]; + const args = {}; // arguments passed to the resolver + const html = await resolver(node, args); + return { + full: `${html}`, + text: `${getTextualContent(html, true)}`, + code: `${optimizeAllNodes(getCodeBlocks(html).code)}`, + example: `${optimizeAllNodes(getCodeBlocks(html).example)}` + }; + } + } + } +}); + +exports.createPages = ({ graphql, actions }) => { + const { createPage } = actions; + + const snippetPage = path.resolve(`./src/docs/templates/SnippetPage.js`); + const tagPage = path.resolve(`./src/docs/templates/TagPage.js`); + + return graphql( + ` + { + allSnippet(sort: {fields: id}) { + edges { + node { + id + slug + tags { + all + primary + } + text { + full + short + } + title + html { + code + example + full + text + } + code { + src + example + } + archived + } + } + } + } + `, + ).then(result => { + if (result.errors) { + throw result.errors; + } + + // Create individual snippet pages. + const snippets = result.data.allSnippet.edges; + + snippets.forEach(snippet => { + if (!snippet.node.archived) { + createPage({ + path: `/snippet${snippet.node.slug}`, + component: snippetPage, + context: { + snippet: snippet.node + } + }); + } else { + createPage({ + path: `/archive${snippet.node.slug}`, + component: snippetPage, + context: { + snippet: snippet.node + } + }); + } + }); + + // Create tag pages. + const tags = [...new Set( + snippets.map(snippet => (snippet.node.tags || {primary: null}).primary) + )] + .filter(Boolean) + .sort((a, b) => a.localeCompare(b)); + + tags.forEach(tag => { + const tagPath = `/tag/${toKebabCase(tag)}/`; + const taggedSnippets = snippets + .filter(snippet => snippet.node.tags.primary === tag) + .filter(snippet => !snippet.node.archived) + .map(({node}) => ({ + title: node.title, + html: node.html.text, + tags: node.tags.all, + id: node.slug.slice(1) + })); + createPage({ + path: tagPath, + component: tagPage, + context: { + tag, + snippets: taggedSnippets + }, + }); + }); + + const beginnerSnippets = snippets + .filter(({ node }) => node.tags.all.includes('beginner')) + .filter(snippet => !snippet.node.archived) + .map(({ node }) => ({ + title: node.title, + html: node.html.text, + tags: node.tags.all, + id: node.slug.slice(1) + })); + + createPage({ + path: `/beginner`, + component: tagPage, + context: { + tag: `beginner snippets`, + snippets: beginnerSnippets + }, + }); + + return null; + }); +}; diff --git a/src/docs/components/SnippetCard.js b/src/docs/components/SnippetCard.js index e2088ec20..071ac8a97 100644 --- a/src/docs/components/SnippetCard.js +++ b/src/docs/components/SnippetCard.js @@ -42,24 +42,18 @@ const CardCorner = ({ difficulty = 'intermediate' }) => ( // =================================================== const FullCard = ({ snippetData, difficulty, isDarkMode }) => { const [examplesOpen, setExamplesOpen] = React.useState(false); - const tags = snippetData.tags; - let cardCodeHtml = `${optimizeAllNodes( - getCodeBlocks(snippetData.html).code, - )}`; - let cardExamplesHtml = `${optimizeAllNodes( - getCodeBlocks(snippetData.html).example, - )}`; + return (

{snippetData.title}

- {tags.map(tag => ( + {snippetData.tags.map(tag => ( {tag} ))}
@@ -83,12 +77,9 @@ const FullCard = ({ snippetData, difficulty, isDarkMode }) => { aria-label='Copy to clipboard' /> - {/* */}
         
@@ -124,13 +115,12 @@ const ShortCard = ({

- {snippetData.title}

diff --git a/src/docs/pages/archive.js b/src/docs/pages/archive.js index b48aa13e8..12814f0bb 100644 --- a/src/docs/pages/archive.js +++ b/src/docs/pages/archive.js @@ -11,7 +11,7 @@ import SnippetCard from '../components/SnippetCard' // Individual snippet category/tag page // =================================================== const ArchivePage = props => { - const posts = props.data.allMarkdownRemark.edges; + const snippets = props.data.allSnippet.edges; React.useEffect(() => { props.dispatch(pushNewPage('Archived', props.path)); @@ -24,17 +24,17 @@ const ArchivePage = props => {

Archived snippets

These snippets, while useful and interesting, didn't quite make it into the repository due to either having very specific use-cases or being outdated. However we felt like they might still be useful to some readers, so here they are.

Click on a snippet card to view the snippet.

- {posts && - posts.map(({ node }) => ( + {snippets && + snippets.map(({ node }) => ( v.trim()), - id: node.fields.slug.slice(1), + title: node.title, + html: node.html.text, + tags: node.tags.all, + id: node.id }} isDarkMode={props.isDarkMode} /> @@ -55,25 +55,19 @@ export default connect( )(ArchivePage); export const archivePageQuery = graphql` - query ArchivePage { - allMarkdownRemark( - limit: 1000 - sort: { fields: [frontmatter___title], order: ASC } - filter: { fileAbsolutePath: { regex: "/snippets_archive/" }, frontmatter: {title: { ne: "" } } } - ) { - totalCount + query archiveListing { + allSnippet(filter: {archived: {eq: true}}) { edges { node { + title + html { + text + } + tags { + all + primary + } id - html - rawMarkdownBody - fields { - slug - } - frontmatter { - title - tags - } } } } diff --git a/src/docs/pages/index.js b/src/docs/pages/index.js index fbeff5b2d..a13bd4aae 100644 --- a/src/docs/pages/index.js +++ b/src/docs/pages/index.js @@ -13,14 +13,7 @@ import SimpleCard from '../components/SimpleCard'; // Home page (splash and search) // =================================================== const IndexPage = props => { - const snippets = props.data.snippetDataJson.data.map(snippet => ({ - title: snippet.title, - html: props.data.allMarkdownRemark.edges.find( - v => v.node.frontmatter.title === snippet.title, - ).node.html, - tags: snippet.attributes.tags, - id: snippet.id - })); + const snippets = props.data.allSnippet.edges; const [searchQuery, setSearchQuery] = React.useState(props.searchQuery); const [searchResults, setSearchResults] = React.useState(snippets); @@ -31,9 +24,9 @@ const IndexPage = props => { let results = snippets; if (q.trim().length) results = snippets.filter( - v => - v.tags.filter(t => t.indexOf(q) !== -1).length || - v.title.toLowerCase().indexOf(q) !== -1, + ({node}) => + node.tags.all.filter(t => t.indexOf(q) !== -1).length || + node.title.toLowerCase().indexOf(q) !== -1, ); setSearchResults(results); }, [searchQuery]); @@ -80,11 +73,16 @@ const IndexPage = props => { Click on a snippet card to view the snippet.

Search results

- {searchResults.map(snippet => ( + {searchResults.map(({node}) => ( ))} @@ -139,22 +137,17 @@ export const indexPageQuery = graphql` } } } - snippetDataJson(meta: { type: { eq: "snippetListingArray" }, scope: {eq: "./snippets"} }) { - data { - id - title - attributes { - tags - } - } - } - allMarkdownRemark { + allSnippet { edges { node { - html - frontmatter { - title + title + html { + text } + tags { + all + } + id } } } diff --git a/src/docs/pages/list.js b/src/docs/pages/list.js index 18e0dfd43..fb7b462aa 100644 --- a/src/docs/pages/list.js +++ b/src/docs/pages/list.js @@ -15,28 +15,15 @@ import SimpleCard from '../components/SimpleCard'; // Snippet list page // =================================================== const ListPage = props => { - const snippets = props.data.snippetDataJson.data.map(snippet => ({ - title: snippet.title, - html: props.data.allMarkdownRemark.edges.find( - v => v.node.frontmatter.title === snippet.title, - ).node.html, - tags: snippet.attributes.tags, - id: snippet.id, - })); - const archivedSnippets = props.data.snippetsArchiveDataJson.data.map(snippet => ({ - title: snippet.title, - html: props.data.allMarkdownRemark.edges.find( - v => v.node.frontmatter.title === snippet.title, - ).node.html, - tags: snippet.attributes.tags, - id: snippet.id, - })); - const tags = snippets.reduce((acc, snippet) => { - if (!snippet.tags) return acc; - const primaryTag = snippet.tags[0]; - if (!acc.includes(primaryTag)) acc.push(primaryTag); - return acc; - }, []); + const snippets = props.data.allSnippet.edges; + const archivedSnippets = props.data.allArchivedSnippet.edges; + + const tags = [...new Set( + snippets.map(snippet => (snippet.node.tags || { primary: null }).primary) + )] + .filter(Boolean) + .sort((a, b) => a.localeCompare(b)); + const staticPages = [ { url: 'beginner', @@ -79,12 +66,17 @@ const ListPage = props => { {snippets - .filter(snippet => snippet.tags[0] === tag) - .map(snippet => ( + .filter(({node}) => node.tags.primary === tag) + .map(({node}) => ( ))} @@ -95,12 +87,17 @@ const ListPage = props => { Archived snippets {archivedSnippets - .map(snippet => ( + .map(({node}) => ( ))}
@@ -131,31 +128,32 @@ export default connect( export const listPageQuery = graphql` query snippetListing { - snippetDataJson(meta: { type: { eq: "snippetListingArray" }, scope: {eq: "./snippets"} }) { - data { - id - title - attributes { - tags - } - } - } - snippetsArchiveDataJson : snippetDataJson(meta: { type: { eq: "snippetListingArray" }, scope: {eq: "./snippets_archive"} }) { - data { - id - title - attributes { - tags - } - } - } - allMarkdownRemark { + allSnippet(filter: {archived: {eq: false}}) { edges { node { - html - frontmatter { - title + title + html { + text } + tags { + all + primary + } + id + } + } + } + allArchivedSnippet: allSnippet(filter: {archived: {eq: true}}) { + edges { + node { + title + html { + text + } + tags { + all + } + id } } } diff --git a/src/docs/pages/search.js b/src/docs/pages/search.js index 7e582ce60..7f19d78a7 100644 --- a/src/docs/pages/search.js +++ b/src/docs/pages/search.js @@ -12,14 +12,7 @@ import SnippetCard from '../components/SnippetCard'; // Search page // =================================================== const SearchPage = props => { - const snippets = props.data.snippetDataJson.data.map(snippet => ({ - title: snippet.title, - html: props.data.allMarkdownRemark.edges.find( - v => v.node.frontmatter.title === snippet.title, - ).node.html, - tags: snippet.attributes.tags, - id: snippet.id, - })); + const snippets = props.data.allSnippet.edges; const [searchQuery, setSearchQuery] = React.useState(props.searchQuery); const [searchResults, setSearchResults] = React.useState(snippets); @@ -30,9 +23,9 @@ const SearchPage = props => { let results = snippets; if (q.trim().length) results = snippets.filter( - v => - v.tags.filter(t => t.indexOf(q) !== -1).length || - v.title.toLowerCase().indexOf(q) !== -1, + ({ node }) => + node.tags.all.filter(t => t.indexOf(q) !== -1).length || + node.title.toLowerCase().indexOf(q) !== -1, ); setSearchResults(results); }, [searchQuery]); @@ -75,11 +68,16 @@ const SearchPage = props => { ) : ( <>

Search results

- {searchResults.map(snippet => ( + {searchResults.map(({node}) => ( ))} @@ -102,22 +100,17 @@ export default connect( export const searchPageQuery = graphql` query searchSnippetList { - snippetDataJson(meta: { type: { eq: "snippetListingArray" }, scope: {eq: "./snippets"} }) { - data { - id - title - attributes { - tags - } - } - } - allMarkdownRemark { + allSnippet { edges { - node { - html - frontmatter { - title + node { + title + html { + text } + tags { + all + } + id } } } diff --git a/src/docs/templates/SnippetPage.js b/src/docs/templates/SnippetPage.js index 996e996f8..7db74c381 100644 --- a/src/docs/templates/SnippetPage.js +++ b/src/docs/templates/SnippetPage.js @@ -11,14 +11,11 @@ import BackArrowIcon from '../components/SVGs/BackArrowIcon'; // Individual snippet page template // =================================================== const SnippetPage = props => { - const post = props.data.markdownRemark; - const postData = props.data.snippetDataJson.data.find( - v => v.title === post.frontmatter.title, - ); + const snippet = props.pageContext.snippet; return ( <> - + { @@ -50,54 +50,3 @@ export default connect( }), null, )(SnippetPage); - -export const pageQuery = graphql` - query BlogPostBySlug($slug: String!, $scope: String!) { - logo: file(absolutePath: { regex: "/logo_reverse_md.png/" }) { - id - childImageSharp { - fixed(height: 45, width: 45) { - src - } - } - } - allMarkdownRemark { - edges { - node { - fields { - slug - } - fileAbsolutePath - frontmatter { - title - } - } - } - } - markdownRemark(fields: { slug: { eq: $slug } }) { - id - fields { - slug - } - excerpt(pruneLength: 160) - html - frontmatter { - title - } - } - snippetDataJson(meta: { type: { eq: "snippetArray" }, scope: {eq: $scope} }) { - data { - title - id - attributes { - text - codeBlocks { - es6 - example - } - tags - } - } - } - } -`; diff --git a/src/docs/templates/TagPage.js b/src/docs/templates/TagPage.js index 101605ad2..0c7a5a802 100644 --- a/src/docs/templates/TagPage.js +++ b/src/docs/templates/TagPage.js @@ -13,8 +13,8 @@ import { capitalize, getRawCodeBlocks as getCodeBlocks } from '../util'; // Individual snippet category/tag page // =================================================== const TagRoute = props => { - const posts = props.data.allMarkdownRemark.edges; const tag = props.pageContext.tag; + const snippets = props.pageContext.snippets; React.useEffect(() => { props.dispatch(pushNewPage(capitalize(tag), props.path)); @@ -26,16 +26,16 @@ const TagRoute = props => {

{capitalize(tag)}

Click on a snippet card to view the snippet.

- {posts && - posts.map(({ node }) => ( + {snippets && + snippets.map(snippet => ( v.trim()), - id: node.fields.slug.slice(1), + title: snippet.title, + html: snippet.html, + tags: snippet.tags, + id: snippet.id, }} isDarkMode={props.isDarkMode} /> @@ -54,28 +54,3 @@ export default connect( }), null, )(TagRoute); - -export const tagPageQuery = graphql` - query TagPage($tagRegex: String) { - allMarkdownRemark( - limit: 1000 - sort: { fields: [frontmatter___title], order: ASC } - filter: { fileAbsolutePath: { regex: "/snippets(?!_archive)/" }, frontmatter: { tags: { regex: $tagRegex } } } - ) { - totalCount - edges { - node { - id - html - fields { - slug - } - frontmatter { - title - tags - } - } - } - } - } -`;