diff --git a/gatsby-node.js b/gatsby-node.js index 996400514e..a3961b4567 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -1,9 +1,9 @@ const path = require("path") const sortLibs = require("./scripts/sort-libraries") -const globby = require('globby'); -const frontmatterParser = require('parser-front-matter'); -const { readFile } = require("fs-extra"); -const { promisify } = require('util'); +const globby = require("globby") +const frontmatterParser = require("parser-front-matter") +const { readFile } = require("fs-extra") +const { promisify } = require("util") exports.onCreatePage = async ({ page, actions }) => { const { createPage, deletePage } = actions @@ -13,59 +13,62 @@ exports.onCreatePage = async ({ page, actions }) => { sourcePath: path.relative(__dirname, page.componentPath), } if (page.path === "/code" || page.path === "/code/") { - const markdownFilePaths = await globby('src/content/code/**/*.md'); + const markdownFilePaths = await globby("src/content/code/**/*.md") const codeData = {} - const slugMap = require('./src/content/code/slug-map.json'); - const parse$ = promisify(frontmatterParser.parse); - await Promise.all(markdownFilePaths.map(async markdownFilePath => { + const slugMap = require("./src/content/code/slug-map.json") + const parse$ = promisify(frontmatterParser.parse) + await Promise.all( + markdownFilePaths.map(async markdownFilePath => { + const markdownFileContent = await readFile(markdownFilePath, "utf-8") + let { + data: { name, description, url, github, npm, gem }, + content: howto, + } = await parse$(markdownFileContent, undefined) + howto = howto.trim() + const pathArr = markdownFilePath.split("/") + if (markdownFilePath.includes("language-support")) { + const languageSupportDirIndex = pathArr.indexOf("language-support") + const languageNameSlugIndex = languageSupportDirIndex + 1 + const languageNameSlug = pathArr[languageNameSlugIndex] + const languageName = slugMap[languageNameSlug] + codeData.Languages = codeData.Languages || {} + codeData.Languages[languageName] = + codeData.Languages[languageName] || {} - const markdownFileContent = await readFile(markdownFilePath, "utf-8") - let { - data: { name, description, url, github, npm, gem }, - content: howto, - } = await parse$(markdownFileContent, undefined) - howto = howto.trim(); - const pathArr = markdownFilePath.split("/") - if (markdownFilePath.includes("language-support")) { - const languageSupportDirIndex = pathArr.indexOf("language-support") - const languageNameSlugIndex = languageSupportDirIndex + 1 - const languageNameSlug = pathArr[languageNameSlugIndex] - const languageName = slugMap[languageNameSlug] - codeData.Languages = codeData.Languages || {} - codeData.Languages[languageName] = codeData.Languages[languageName] || {} - - const categoryNameSlugIndex = languageSupportDirIndex + 2 - const categoryNameSlug = pathArr[categoryNameSlugIndex] - const categoryName = slugMap[categoryNameSlug] - codeData.Languages[languageName][categoryName] = codeData.Languages[languageName][categoryName] || [] - codeData.Languages[languageName][categoryName].push({ - name, - description, - howto, - url, - github, - npm, - gem, - sourcePath: markdownFilePath, - }) - } else { - const codeDirIndex = pathArr.indexOf("code") - const categoryNameSlugIndex = codeDirIndex + 1 - const categoryNameSlug = pathArr[categoryNameSlugIndex] - const categoryName = slugMap[categoryNameSlug] - codeData[categoryName] = codeData[categoryName] || [] - codeData[categoryName].push({ - name, - description, - howto, - url, - github, - npm, - gem, - sourcePath: markdownFilePath, - }) - } - })) + const categoryNameSlugIndex = languageSupportDirIndex + 2 + const categoryNameSlug = pathArr[categoryNameSlugIndex] + const categoryName = slugMap[categoryNameSlug] + codeData.Languages[languageName][categoryName] = + codeData.Languages[languageName][categoryName] || [] + codeData.Languages[languageName][categoryName].push({ + name, + description, + howto, + url, + github, + npm, + gem, + sourcePath: markdownFilePath, + }) + } else { + const codeDirIndex = pathArr.indexOf("code") + const categoryNameSlugIndex = codeDirIndex + 1 + const categoryNameSlug = pathArr[categoryNameSlugIndex] + const categoryName = slugMap[categoryNameSlug] + codeData[categoryName] = codeData[categoryName] || [] + codeData[categoryName].push({ + name, + description, + howto, + url, + github, + npm, + gem, + sourcePath: markdownFilePath, + }) + } + }) + ) const languageList = [] let sortedTools = [] await Promise.all([ @@ -139,11 +142,17 @@ exports.createPages = async ({ graphql, actions }) => { sublinks sidebarTitle date + tags } id } } } + tagsGroup: allMarkdownRemark { + group(field: frontmatter___tags) { + fieldValue + } + } } `) @@ -177,13 +186,24 @@ exports.createPages = async ({ graphql, actions }) => { // Note that this is mutated let sideBardata = {} - // Sidebar items to add which don't come from markdown const additionalSidebarItems = { - foundation: [{ - name: "GraphQL Foundation", - links: [{ frontmatter: { sidebarTitle:"Foundation Members", title: "Foundation Members", permalink: "/foundation/members/", date: null, category: "GraphQL Foundation" } }] - }] + foundation: [ + { + name: "GraphQL Foundation", + links: [ + { + frontmatter: { + sidebarTitle: "Foundation Members", + title: "Foundation Members", + permalink: "/foundation/members/", + date: null, + category: "GraphQL Foundation", + }, + }, + ], + }, + ], } // E.g. @@ -204,7 +224,10 @@ exports.createPages = async ({ graphql, actions }) => { parent: { relativeDirectory, sourceInstanceName }, } = node - if (sourceInstanceName !== "content" || relativeDirectory.includes("code/")) { + if ( + sourceInstanceName !== "content" || + relativeDirectory.includes("code/") + ) { return } @@ -216,13 +239,10 @@ exports.createPages = async ({ graphql, actions }) => { } else { pagesGroupedByFolder = { ...pagesGroupedByFolder, - [relativeDirectory]: [ - ...pagesGroupedByFolder[relativeDirectory], - node, - ], + [relativeDirectory]: [...pagesGroupedByFolder[relativeDirectory], node], } } - + allPages.push({ permalink, relativeDirectory, @@ -231,8 +251,8 @@ exports.createPages = async ({ graphql, actions }) => { sourcePath: path.relative(__dirname, node.fileAbsolutePath), }) }) - - // Loop through the sections in the sidebar, mutating the + + // Loop through the sections in the sidebar, mutating the // next and previous objects for different Object.entries(pagesGroupedByFolder).map(([folder, pages]) => { let pagesByUrl = {} @@ -266,7 +286,7 @@ exports.createPages = async ({ graphql, actions }) => { return } }) - + if (!firstPage) { throw new Error(`First page not found in ${folder}`) } @@ -309,18 +329,17 @@ exports.createPages = async ({ graphql, actions }) => { categoriesMap[currentCategory.name] = currentCategory } - sideBardata[folder] = Object.values(categoriesMap) + sideBardata[folder] = Object.values(categoriesMap) }) Object.entries(additionalSidebarItems).map(([folder, sections]) => { sections.forEach(s => { const originalLinks = sideBardata[folder].find(l => l.name === s.name) - originalLinks.links = [...originalLinks.links, ...s.links] + originalLinks.links = [...originalLinks.links, ...s.links] }) }) - - // Use all the set up data to now tell Gatsby to create pages + // Use all the set up data to now tell Gatsby to create pages // on the site allPages.forEach(page => { createPage({ @@ -334,4 +353,17 @@ exports.createPages = async ({ graphql, actions }) => { }, }) }) + + // Create tag pages + const tagTemplate = path.resolve("src/templates/tags.tsx") + const tags = result.data.tagsGroup.group + tags.forEach(tag => { + createPage({ + path: `/tags/${tag.fieldValue}/`, + component: tagTemplate, + context: { + tag: tag.fieldValue, + }, + }) + }) } diff --git a/src/assets/css/_css/docs.less b/src/assets/css/_css/docs.less index 773e006e06..f4fcc96dd6 100644 --- a/src/assets/css/_css/docs.less +++ b/src/assets/css/_css/docs.less @@ -1,7 +1,7 @@ @import "variables.less"; .documentationContent { - margin-top:3px; + margin-top: 3px; margin-bottom: 8em; display: flex; width: 100%; @@ -13,58 +13,58 @@ } } -.headerline{ +.headerline { display: flex; // padding-right: 5px; // justify-content: center; } .main-block-blog { - /* background-color: #f0ece2; */ - background-color: #fff; - padding: 10px; - margin: 10px; - margin-bottom: 100px; - justify-content: center; - position: relative; - flex-flow: row; - text-align: center; + /* background-color: #f0ece2; */ + background-color: #fff; + padding: 10px; + margin: 10px; + margin-bottom: 100px; + justify-content: center; + position: relative; + flex-flow: row; + text-align: center; } -.column{ +.column { // padding: 0 10px; box-sizing: border-box; border: 3px rgb(44, 38, 38); justify-content: space-around; - margin-right: 15px; - // width: 800px; - // height: 250px; + margin-right: 15px; + // width: 800px; + // height: 250px; } .container-bl { display: flex; justify-content: center; - /* align-items: center; */ - // padding: 10px; - flex-direction: row; - // max-width: 1000px; - margin-right: 10px; - justify-content: space-around; + /* align-items: center; */ + // padding: 10px; + flex-direction: row; + // max-width: 1000px; + margin-right: 10px; + justify-content: space-around; } .container-bl1 { display: flex; justify-content: center; - /* align-items: center; */ - // padding: 10px; - flex-direction: row; - // max-width: 1000px; - margin-right: 10px; - justify-content: space-around; + /* align-items: center; */ + // padding: 10px; + flex-direction: row; + // max-width: 1000px; + margin-right: 10px; + justify-content: space-around; } -.codetitle{ - font-family: 'Rubik', 'Helvetica Neue', Helvetica, Arial, sans-serif; +.codetitle { + font-family: "Rubik", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; color: #202020; font-size: 22px; @@ -72,64 +72,63 @@ } .article { - background: white; - /* background-image: url(../imgs/corona-virus.jpg) ; */ - /* filter: blur(8px); */ - // margin: 0 0 20px; - padding: 5px; - // border-radius: 1.5px; - // border: 0.7px solid black; - // box-shadow: 0 2px 4px #010101; - cursor: pointer; + background: white; + /* background-image: url(../imgs/corona-virus.jpg) ; */ + /* filter: blur(8px); */ + // margin: 0 0 20px; + padding: 5px; + // border-radius: 1.5px; + // border: 0.7px solid black; + // box-shadow: 0 2px 4px #010101; + cursor: pointer; transition: 0.3s ease; width: 140px; height: 160px; } .article:active { - box-shadow: none; - transform-origin: center; - transform: scale(0.98); + box-shadow: none; + transform-origin: center; + transform: scale(0.98); } .article_category { - display: inline-block; - // background: rgb(235, 150, 206); - /* 47bbc5 */ - padding: 5px 8px; - border: black; - // border-radius: 10px; - margin: 0 0 10px; - color: white; - font-size: 0.75rem; - font-weight: 600; - letter-spacing: 0.075rem; + display: inline-block; + // background: rgb(235, 150, 206); + /* 47bbc5 */ + padding: 5px 8px; + border: black; + // border-radius: 10px; + margin: 0 0 10px; + color: white; + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.075rem; text-transform: uppercase; width: 100px; height: 100px; } .article_excerpt { - color: black; - line-height: 1.5rem; - font-size: 0.875rem; + color: black; + line-height: 1.5rem; + font-size: 0.875rem; } .article_title { - margin: 0 0 10px; - color: #E10098; - font-family: "Josefin Sans"; - font-size: 1.25rem; - // font-weight: 600; - line-height: 1.75rem; + margin: 0 0 10px; + color: #e10098; + font-family: "Josefin Sans"; + font-size: 1.25rem; + // font-weight: 600; + line-height: 1.75rem; } - @media only screen and (min-width: 320px) and (max-width: 650px) { - .container-bl1 { - flex-direction: column; - width: 100%; - } + .container-bl1 { + flex-direction: column; + width: 100%; + } } .inner-content { @@ -168,11 +167,9 @@ .prism { position: relative; - box-shadow: - inset 0 1px 0 rgba(0,0,0,0.08), - inset 0 -1px 0 rgba(0,0,0,0.08), - inset -1px 0 0 rgba(0,0,0,0.08), - inset 4px 0 0 rgba(0,0,0,0.08); + box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.08), + inset 0 -1px 0 rgba(0, 0, 0, 0.08), inset -1px 0 0 rgba(0, 0, 0, 0.08), + inset 4px 0 0 rgba(0, 0, 0, 0.08); border-radius: 3px; .line-highlight { @@ -198,7 +195,8 @@ .not(code) > pre.prism { text-shadow: 0 1px white; - &::-moz-selection, &::selection { + &::-moz-selection, + &::selection { text-shadow: none; } } @@ -220,14 +218,17 @@ } border-radius: 3px; - box-shadow: inset 0 0 0 1px rgba(0,0,0,0.1); + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); padding: 0.7em 1.5em 0.5em 1em; - &:hover, &:focus, &:active { + &:hover, + &:focus, + &:active { background: @rhodamine-color; text-decoration: none; - .read-next-continue, .read-next-title { + .read-next-continue, + .read-next-title { color: white; } } @@ -282,7 +283,9 @@ color: #666; display: block; - &:hover, &:focus, &.active { + &:hover, + &:focus, + &.active { text-decoration: none; color: @rhodamine-color; } @@ -292,7 +295,7 @@ .blog-sidebar { .recent-posts { li { - border-bottom: 1px solid #DDD; + border-bottom: 1px solid #ddd; line-height: 18px; padding: 10px 0; font-weight: bold; @@ -304,3 +307,12 @@ } } } + +.blog-sidebar { + .categories { + li { + font-weight: bold; + color: @rhodamine-color; + } + } +} diff --git a/src/assets/css/_css/graphql.less b/src/assets/css/_css/graphql.less index 9d9adc8ddc..8cc4fb0474 100644 --- a/src/assets/css/_css/graphql.less +++ b/src/assets/css/_css/graphql.less @@ -16,7 +16,8 @@ body { a { color: @rhodamine-color; text-decoration: none; - &:hover, &:focus { + &:hover, + &:focus { text-decoration: underline; } } @@ -29,7 +30,12 @@ em { font-style: italic; } -h1, h2, h3, h4, h5, h6 { +h1, +h2, +h3, +h4, +h5, +h6 { margin: 2em 0 0; text-rendering: optimizelegibility; } @@ -83,16 +89,21 @@ h6:hover > .hash-link { visibility: visible; } -ul, ol { +ul, +ol { margin: 1em 0 1em 2em; padding: 0; } -p + ul, p + ol { +p + ul, +p + ol { margin-top: 1em; } -ul ul, ul ol, ol ol, ol ul { +ul ul, +ul ol, +ol ol, +ol ul { margin-bottom: 0; } @@ -162,7 +173,9 @@ nav { padding: 0 1em; transition: color 0.1s ease-out; - &:hover, &:focus, &.active { + &:hover, + &:focus, + &.active { color: @text-color; text-decoration: none; } @@ -201,7 +214,8 @@ footer { height: 50px; background: url("/img/logo-gray.svg") no-repeat content-box; - &:hover, &:focus { + &:hover, + &:focus { background-image: url("/img/logo.svg"); } @@ -215,16 +229,19 @@ footer { display: table; margin: 2px -10px; padding: 3px 10px; - &:hover, &:focus { + &:hover, + &:focus { color: @rhodamine-color; text-decoration: none; } } - h5, h6 { + h5, + h6 { margin: 0 0 10px; - &, & > a { + &, + & > a { color: lighten(@dark-color, 50%); } @@ -242,20 +259,16 @@ footer { width: 170px; &:hover { - opacity: 1.0; + opacity: 1; } } .copyright { - color: rgba(255,255,255,0.4); + color: rgba(255, 255, 255, 0.4); text-align: center; } } - - - - .guestBio { background: #f9f0f3; border-top: solid 2px #e0c3c8; @@ -269,7 +282,8 @@ footer { padding-top: 20px; } -.blogContent:before, .blogContent:after { +.blogContent:before, +.blogContent:after { content: " "; display: table; } @@ -289,7 +303,20 @@ footer { max-width: 100%; } +.tag-wrapper { + margin-bottom: 5px; +} +.tag { + font-weight: 700; + letter-spacing: 1.5px; + text-transform: uppercase; + background-color: #e9e8f3; + font-size: 14px; + padding: 5px; + border-radius: 4px; + margin-right: 5px; +} div[data-twttr-id] iframe { margin: 10px auto !important; @@ -314,7 +341,7 @@ p + .apiIndex { } .apiIndex li a:hover::before { color: @rhodamine-color; - content: '#'; + content: "#"; font-size: 16px; left: -2em; line-height: 20px; @@ -344,8 +371,7 @@ p + .apiIndex { line-height: 17px; padding: 7px 14px; background: white; - box-shadow: inset 0 0 0 1px #EEE, inset 4px 0 0 #EEE; + box-shadow: inset 0 0 0 1px #eee, inset 4px 0 0 #eee; border-radius: 3px; margin-left: -4px; } - diff --git a/src/components/BlogLayout/index.tsx b/src/components/BlogLayout/index.tsx index 8111e5bd1b..c93a2a149e 100644 --- a/src/components/BlogLayout/index.tsx +++ b/src/components/BlogLayout/index.tsx @@ -11,6 +11,7 @@ interface Props { rawMarkdownBody: string sideBarData: any pageContext: any + tags: Array } const index = ({ @@ -21,7 +22,8 @@ const index = ({ guestBio, rawMarkdownBody, sideBarData, - pageContext + pageContext, + tags, }: Props) => { return (
@@ -35,6 +37,7 @@ const index = ({ rawMarkdownBody={rawMarkdownBody} isPermalink={true} pageContext={pageContext} + tags={tags} /> { @@ -55,4 +58,3 @@ const index = ({ } export default index - diff --git a/src/components/BlogPost/index.tsx b/src/components/BlogPost/index.tsx index 3a4a5866c4..3799c69100 100644 --- a/src/components/BlogPost/index.tsx +++ b/src/components/BlogPost/index.tsx @@ -1,5 +1,6 @@ import React from "react" import Marked from "../Marked" +import { Link } from "gatsby" interface Props { title: string @@ -10,8 +11,9 @@ interface Props { rawMarkdownBody: string isPermalink: boolean pageContext: any - excerpt: string + excerpt?: string showExcerpt?: true + tags: Array } const BlogPost = ({ @@ -24,19 +26,32 @@ const BlogPost = ({ isPermalink, pageContext, excerpt, - showExcerpt + showExcerpt, + tags, }: Props) => (

{isPermalink ? title : {title}}

{new Date(date).toLocaleDateString()} by {byline}

+
+ {tags.map(tag => ( + + {tag} + + ))} +
+ {guestBio ? null :
} {guestBio && (

{`This guest article contributed by ${byline}, ${guestBio}.`}

)} - - {showExcerpt ?

{excerpt}

: {rawMarkdownBody}} + + {showExcerpt ? ( +

{excerpt}

+ ) : ( + {rawMarkdownBody} + )}
) diff --git a/src/components/BlogSidebar/index.tsx b/src/components/BlogSidebar/index.tsx index 16a933b9a0..1a828527ec 100644 --- a/src/components/BlogSidebar/index.tsx +++ b/src/components/BlogSidebar/index.tsx @@ -1,34 +1,63 @@ -import React from 'react'; -import { Link } from "gatsby" +import React from "react" +import { Link, useStaticQuery, graphql } from "gatsby" interface Props { posts: any[] currentPermalink?: string } -const BlogSidebar = ({ posts, currentPermalink }: Props) => ( -
-
-

Subscribe

- - RSS - -
-
-

Recent Posts

-
    - {posts.map(({ frontmatter }, i) => ( -
  • - {frontmatter.permalink === currentPermalink ? ( - frontmatter.title - ) : ( - {frontmatter.title} - )} -
  • - ))} -
+const BlogSidebar = ({ posts, currentPermalink }: Props) => { + const allTags = useStaticQuery(graphql` + query allTags { + allMarkdownRemark { + group(field: frontmatter___tags) { + fieldValue + } + } + } + `).allMarkdownRemark.group + + return ( +
+
+

Subscribe

+ + RSS + +
+
+

Categories

+
    + {allTags.map(({ fieldValue }: { fieldValue: string }, i: number) => { + const tag = fieldValue[0].toUpperCase() + fieldValue.substring(1) + return ( +
  • + {fieldValue === currentPermalink ? ( + tag + ) : ( + {tag} + )} +
  • + ) + })} +
+
+
+

Recent Posts

+
    + {posts.map(({ frontmatter }, i) => ( +
  • + {frontmatter.permalink === currentPermalink ? ( + frontmatter.title + ) : ( + {frontmatter.title} + )} +
  • + ))} +
+
-
-) + ) +} -export default BlogSidebar; +export default BlogSidebar diff --git a/src/pages/blog.tsx b/src/pages/blog.tsx index f92503d990..b6d553b727 100644 --- a/src/pages/blog.tsx +++ b/src/pages/blog.tsx @@ -17,6 +17,7 @@ export default ({ pageContext, data }: any) => { } return 0 }) + return (
@@ -25,9 +26,16 @@ export default ({ pageContext, data }: any) => { {posts.map( ( { - frontmatter: { title, date, permalink, byline, guestBio }, + frontmatter: { + title, + date, + permalink, + byline, + guestBio, + tags, + }, rawMarkdownBody, - excerpt + excerpt, }: any, i ) => ( @@ -43,6 +51,7 @@ export default ({ pageContext, data }: any) => { pageContext={pageContext} excerpt={excerpt} showExcerpt + tags={tags} /> ) )} @@ -69,6 +78,7 @@ export const query = graphql` guestBio sublinks layout + tags } id excerpt diff --git a/src/templates/doc.tsx b/src/templates/doc.tsx index e13456505b..7d25d05d51 100644 --- a/src/templates/doc.tsx +++ b/src/templates/doc.tsx @@ -3,9 +3,9 @@ import { graphql } from "gatsby" import Layout from "../components/Layout" import DocsLayout from "../components/DocsLayout" import FoundationLayout from "../components/FoundationLayout" -import BlogLayout from '../components/BlogLayout'; -import CodeLayout from "../components/CodeLayout"; -import FAQLayout from "../components/FAQLayout"; +import BlogLayout from "../components/BlogLayout" +import CodeLayout from "../components/CodeLayout" +import FAQLayout from "../components/FAQLayout" interface Props { data: any @@ -23,12 +23,21 @@ const layoutMap: any = { const Blog = ({ data, pageContext }: Props) => { const { doc: { - frontmatter: { title, date, heroText, permalink, byline, guestBio, layout }, + frontmatter: { + title, + date, + heroText, + permalink, + byline, + guestBio, + layout, + tags, + }, rawMarkdownBody, }, nextDoc, } = data - const InnerLayout = layoutMap[layout]; + const InnerLayout = layoutMap[layout] return ( { nextDoc={nextDoc} sideBarData={pageContext.sideBarData} pageContext={pageContext} + tags={tags} /> ) @@ -59,6 +69,7 @@ export const query = graphql` guestBio sublinks layout + tags } id rawMarkdownBody diff --git a/src/templates/tags.tsx b/src/templates/tags.tsx new file mode 100644 index 0000000000..1730241da4 --- /dev/null +++ b/src/templates/tags.tsx @@ -0,0 +1,95 @@ +import React from "react" +import Layout from "../components/Layout" +import BlogPost from "../components/BlogPost" +import BlogSidebar from "../components/BlogSidebar" +import { graphql } from "gatsby" + +export default ({ pageContext, data }: any) => { + const { tag } = pageContext + const allPosts = data.allMarkdownRemark.edges + .map((e: any) => e.node) + .sort((a: any, b: any) => { + const aDate = new Date(a.frontmatter.date) + const bDate = new Date(b.frontmatter.date) + if (aDate > bDate) { + return -1 + } else if (aDate < bDate) { + return 1 + } + return 0 + }) + + const taggedPosts = allPosts.filter((post: any) => + post.frontmatter.tags.includes(tag) + ) + + return ( + +
+
+
+ {taggedPosts.map( + ( + { + frontmatter: { + title, + date, + permalink, + byline, + guestBio, + tags, + }, + rawMarkdownBody, + excerpt, + }: any, + i + ) => ( + + ) + )} +
+ +
+
+
+ ) +} + +export const query = graphql` + query { + allMarkdownRemark( + filter: { frontmatter: { permalink: { regex: "/blog/" } } } + ) { + edges { + node { + frontmatter { + title + date + permalink + byline + guestBio + sublinks + layout + tags + } + id + excerpt + rawMarkdownBody + } + } + } + } +`