Merge pull request #273 from the-programmers-hangout/feat-topics

feat(resources): support for topics
This commit is contained in:
Jean-Philippe Sirois
2020-05-27 05:43:29 -04:00
committed by GitHub
40 changed files with 166 additions and 106 deletions
+9 -2
View File
@@ -54,8 +54,15 @@ module.exports = {
{ {
resolve: `gatsby-source-filesystem`, resolve: `gatsby-source-filesystem`,
options: { options: {
name: `resources`, name: `languages`,
path: `${__dirname}/src/content/resources`, path: `${__dirname}/src/content/resources/language`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `topics`,
path: `${__dirname}/src/content/resources/topic`,
}, },
}, },
{ {
+69 -45
View File
@@ -48,57 +48,81 @@ const validateResourceArticle = node => {
} }
const createResources = async ({ createPage, graphql }) => { const createResources = async ({ createPage, graphql }) => {
const languageResources = path.resolve(`src/templates/languagePost.tsx`) const resourcePage = path.resolve(`src/templates/resourcePage.tsx`)
const languageHome = path.resolve(`src/templates/languageHome.tsx`) const resourceHome = path.resolve(`src/templates/resourceHome.tsx`)
const result = await graphql(` const languagesQuery = await graphql(
query FetchResources { `
allFile(filter: { sourceInstanceName: { eq: "resources" } }) { query FetchLanguages {
edges { allFile(filter: { sourceInstanceName: { eq: "languages" } }) {
node { edges {
relativePath node {
sourceInstanceName relativePath
sourceInstanceName
}
} }
} }
} }
} `
`) )
if (result.errors) { const topicsQuery = await graphql(
return Promise.reject(result.errors) `
query FetchTopics {
allFile(filter: { sourceInstanceName: { eq: "topics" } }) {
edges {
node {
relativePath
sourceInstanceName
}
}
}
}
`
)
const resources = [
{ key: "topics", query: topicsQuery },
{ key: "languages", query: languagesQuery },
]
for ({ key, query } of resources) {
if (query.errors) {
return Promise.reject(query.errors)
}
const resources = query.data.allFile.edges.reduce((acc, { node }) => {
const [resourceCategory] = node.relativePath.split("/")
if (!acc.includes(resourceCategory)) {
acc.push(resourceCategory)
}
return acc
}, [])
// create home page for each resources
resources.forEach((resource) => {
createPage({
path: path.join("resources", resource),
component: resourceHome,
context: {
resourceType: key,
entry: resource,
layout: LAYOUT_RESOURCES,
},
})
})
query.data.allFile.edges.forEach(({ node }) => {
createPage({
path: path.join("resources", node.relativePath),
component: resourcePage,
context: {
file: node.relativePath,
layout: LAYOUT_RESOURCES,
},
})
})
} }
const languages = result.data.allFile.edges.reduce((acc, { node }) => {
const [language] = node.relativePath.split("/")
if (!acc.includes(language)) {
acc.push(language)
}
return acc
}, [])
// create home page for each languages
languages.forEach(language => {
createPage({
path: path.join("resources", language),
component: languageHome,
context: {
language,
layout: LAYOUT_RESOURCES,
},
})
})
// create resource pages
return result.data.allFile.edges.forEach(({ node }) => {
createPage({
path: path.join("resources", node.relativePath),
component: languageResources,
context: {
file: node.relativePath,
layout: LAYOUT_RESOURCES,
},
})
})
} }
const createArchives = async ({ createPage, graphql }) => { const createArchives = async ({ createPage, graphql }) => {
+4 -2
View File
@@ -11,7 +11,9 @@ import * as SC from "./styles"
const ALL_ARCHIVES = graphql` const ALL_ARCHIVES = graphql`
query { query {
allFile(filter: { sourceInstanceName: { eq: "what-is-archive" } }) { archives: allFile(
filter: { sourceInstanceName: { eq: "what-is-archive" } }
) {
edges { edges {
node { node {
relativePath relativePath
@@ -41,7 +43,7 @@ function Tree({ item }: { item: IFileOrFolder }) {
} }
export const ArchivesSidebar: FC<HTMLAttributes<HTMLDivElement>> = (props) => { export const ArchivesSidebar: FC<HTMLAttributes<HTMLDivElement>> = (props) => {
const archives = useStaticQuery<IAllArchivesQuery>(ALL_ARCHIVES) const { archives } = useStaticQuery(ALL_ARCHIVES)
const tree = useBuildTree(archives, "/archives") const tree = useBuildTree(archives, "/archives")
const sortedTree = sort((a, b) => a.title.localeCompare(b.title), tree) const sortedTree = sort((a, b) => a.title.localeCompare(b.title), tree)
+46 -37
View File
@@ -1,21 +1,33 @@
import { graphql, useStaticQuery } from "gatsby" import { graphql, useStaticQuery } from "gatsby"
import descend from "ramda/es/descend" import descend from "ramda/es/descend"
import sort from "ramda/es/sort"
import sortWith from "ramda/es/sortWith" import sortWith from "ramda/es/sortWith"
import React, { FC, HTMLAttributes, memo, useState } from "react" import React, { FC, HTMLAttributes, memo, useState } from "react"
import useBuildTree from "../../hooks/useBuildTree"
import { useLockBodyScroll } from "../../hooks/useLockBodyScroll" import { useLockBodyScroll } from "../../hooks/useLockBodyScroll"
import useSidebar from "../../hooks/useSidebar" import useSidebar from "../../hooks/useSidebar"
import TriangleDown from "../../icons/triangle-down.svg" import TriangleDown from "../../icons/triangle-down.svg"
import { IAllResourcesQuery, IFileOrFolder, IFolder } from "../../types" import { IFileOrFolder, IFolder } from "../../types"
import { getPath, humanize } from "../../utils" import { getPath, humanize } from "../../utils"
import { Sidebar } from "../Sidebar" import { Sidebar } from "../Sidebar"
import * as SC from "./styles" import * as SC from "./styles"
import useMatchingPath from "./useMatchingPath" import useMatchingPath from "./useMatchingPath"
import useTree from "./useTree"
const ALL_RESOURCES = graphql` const ALL_RESOURCES = graphql`
query { query AllTopicsAndAllLanguages {
allFile(filter: { sourceInstanceName: { eq: "resources" } }) { languages: allFile(filter: { sourceInstanceName: { eq: "languages" } }) {
edges {
node {
relativePath
childMarkdownRemark {
frontmatter {
authors
title
}
}
}
}
}
topics: allFile(filter: { sourceInstanceName: { eq: "topics" } }) {
edges { edges {
node { node {
relativePath relativePath
@@ -129,16 +141,16 @@ const FirstLevelFolder = memo(({ item }: { item: IFolder }) => {
) )
}) })
const LanguageList: FC<{ const ResourceList: FC<{
items: IFileOrFolder[] items: IFileOrFolder[]
setExpanded: React.Dispatch<React.SetStateAction<boolean>> setExpanded: React.Dispatch<React.SetStateAction<boolean>>
}> = ({ items, setExpanded }) => { }> = ({ items, setExpanded }) => {
const { current, setCurrent } = useSidebar() const { current, setCurrent } = useSidebar()
return ( return (
<SC.StyledLanguageList> <SC.StyledResourceList>
{items.map((item) => ( {items.map((item) => (
<SC.Language <SC.Resource
key={item.title} key={item.title}
className={current === item.title ? "active" : ""} className={current === item.title ? "active" : ""}
onClick={() => { onClick={() => {
@@ -147,55 +159,52 @@ const LanguageList: FC<{
}} }}
> >
{item.title} {item.title}
</SC.Language> </SC.Resource>
))} ))}
</SC.StyledLanguageList> </SC.StyledResourceList>
) )
} }
const AllLanguages: FC<{ const ExpandResources: FC<{
items: IFileOrFolder[]
expanded: boolean expanded: boolean
setExpanded: React.Dispatch<React.SetStateAction<boolean>> setExpanded: React.Dispatch<React.SetStateAction<boolean>>
}> = ({ items, expanded, setExpanded }) => { }> = ({ children, expanded, setExpanded }) => {
const { current } = useSidebar() const { current } = useSidebar()
if (!current) { const showList = expanded || !current
return <LanguageList items={items} setExpanded={setExpanded} />
}
return ( return (
<SC.ExpandLanguages> <SC.ExpandResources>
<SC.ExpandLanguagesHeader {current && (
onClick={() => setExpanded((prevState) => !prevState)} <SC.ExpandResourcesHeader
> onClick={() => setExpanded((prevState) => !prevState)}
Expand languages {expanded ? <SC.CollapseIcon /> : <SC.ExpandIcon />} >
</SC.ExpandLanguagesHeader> Expand resources {expanded ? <SC.CollapseIcon /> : <SC.ExpandIcon />}
{expanded && <LanguageList items={items} setExpanded={setExpanded} />} </SC.ExpandResourcesHeader>
</SC.ExpandLanguages> )}
{showList && children}
</SC.ExpandResources>
) )
} }
export const ResourcesSidebar: FC<HTMLAttributes<HTMLDivElement>> = (props) => { export const ResourcesSidebar: FC<HTMLAttributes<HTMLDivElement>> = (props) => {
const [expandedLanguages, setExpandedLanguages] = useState(false) const [expanded, setExpanded] = useState(false)
const resources = useStaticQuery<IAllResourcesQuery>(ALL_RESOURCES) const resources = useStaticQuery(ALL_RESOURCES)
const languages = useBuildTree(resources, "/resources") const languagesTree = useTree(resources.languages)
const topicsTree = useTree(resources.topics)
const { current } = useSidebar() const { current } = useSidebar()
const sortedLanguages = sort(
(a, b) => a.title.localeCompare(b.title),
languages
)
const currentLanguage = sortedLanguages.find((lang) => lang.title === current) const currentLanguage = languagesTree.find((lang) => lang.title === current)
const currentTopic = topicsTree.find((topic) => topic.title === current)
return ( return (
<Sidebar {...props}> <Sidebar {...props}>
<AllLanguages <ExpandResources expanded={expanded} setExpanded={setExpanded}>
items={sortedLanguages} <ResourceList items={languagesTree} setExpanded={setExpanded} />
expanded={expandedLanguages} <ResourceList items={topicsTree} setExpanded={setExpanded} />
setExpanded={setExpandedLanguages} </ExpandResources>
/>
{currentLanguage && <Tree item={currentLanguage} firstLevel={true} />} {currentLanguage && <Tree item={currentLanguage} firstLevel={true} />}
{currentTopic && <Tree item={currentTopic} firstLevel={true} />}
</Sidebar> </Sidebar>
) )
} }
+15 -10
View File
@@ -79,6 +79,8 @@ export const TreeWrapper = styled.div<{ collapsed?: boolean }>`
&.firstLevel { &.firstLevel {
overflow: hidden; overflow: hidden;
border-top: 1px solid
${(props) => transparentize(0.8, props.theme.sidebar.foreground)};
} }
${Children} { ${Children} {
@@ -109,15 +111,12 @@ export const ExpandIcon = styled(Expand)`
} }
` `
export const ExpandLanguages = styled.div` export const ExpandResources = styled.div`
padding: 12px 15px 12px 0; padding-right: 15px;
margin: 20px 0;
border-bottom: 1px solid
${(props) => transparentize(0.8, props.theme.sidebar.foreground)};
margin-bottom: 8px;
` `
export const ExpandLanguagesHeader = styled.div` export const ExpandResourcesHeader = styled.div`
user-select: none; user-select: none;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -130,12 +129,18 @@ export const ExpandLanguagesHeader = styled.div`
} }
` `
export const StyledLanguageList = styled.div` export const StyledResourceList = styled.div`
padding: 8px 0; padding-top: 16px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
overflow: hidden; overflow: hidden;
& + & {
margin-top: 16px;
border-top: 1px solid
${(props) => transparentize(0.8, props.theme.sidebar.foreground)};
}
` `
const item = css` const item = css`
@@ -167,7 +172,7 @@ const item = css`
} }
` `
export const Language = styled.div` export const Resource = styled.div`
${item}; ${item};
` `
@@ -0,0 +1,13 @@
import sort from "ramda/es/sort"
import useBuildTree from "../../hooks/useBuildTree"
import { IAllResourcesQuery } from "../../types"
export default function useTree(resources: IAllResourcesQuery["allFile"]) {
const resourcesTree = useBuildTree(resources, "/resources")
const sortedResources = sort(
(a, b) => a.title.localeCompare(b.title),
resourcesTree
)
return sortedResources
}
+2 -2
View File
@@ -2,10 +2,10 @@ import { IAllFilesQuery, IFileQuery } from "../types"
import { join, traversePaths } from "../utils" import { join, traversePaths } from "../utils"
export default function useBuildTree( export default function useBuildTree(
resources: IAllFilesQuery, resources: IAllFilesQuery["allFile"],
basePath: string basePath: string
) { ) {
const objects = resources.allFile.edges.map(({ node: file }: IFileQuery) => const objects = resources.edges.map(({ node: file }: IFileQuery) =>
traversePaths(file.relativePath.split("/"), basePath) traversePaths(file.relativePath.split("/"), basePath)
) )
@@ -7,7 +7,7 @@ import { PageContent } from "../components/PageContent"
import useSidebar from "../hooks/useSidebar" import useSidebar from "../hooks/useSidebar"
// @todo maybe find alternative type for data // @todo maybe find alternative type for data
const LanguageHome: FC<any> = ({ data, pageContext }) => { const ResourceHome: FC<any> = ({ data, pageContext }) => {
const { current: language } = useSidebar() const { current: language } = useSidebar()
const { relativePath } = data.file const { relativePath } = data.file
const { html, excerpt, fields, frontmatter, timeToRead } = data.file.post const { html, excerpt, fields, frontmatter, timeToRead } = data.file.post
@@ -43,13 +43,13 @@ const LanguageHome: FC<any> = ({ data, pageContext }) => {
) )
} }
export default LanguageHome export default ResourceHome
export const query = graphql` export const query = graphql`
query LanguageHome($language: String!) { query ResourceHome($resourceType: String!, $entry: String!) {
file( file(
sourceInstanceName: { eq: "resources" } sourceInstanceName: { eq: $resourceType }
relativeDirectory: { eq: $language } relativeDirectory: { eq: $entry }
base: { eq: "intro.md" } base: { eq: "intro.md" }
) { ) {
relativePath relativePath
@@ -10,7 +10,7 @@ import useSidebar from "../hooks/useSidebar"
import { buildToc } from "../utils" import { buildToc } from "../utils"
// @todo maybe find alternative type for data // @todo maybe find alternative type for data
const LanguagePost: FC<any> = ({ data }) => { const ResourcePage: FC<any> = ({ data }) => {
const { current: language } = useSidebar() const { current: language } = useSidebar()
const { relativePath } = data.file const { relativePath } = data.file
const { const {
@@ -61,10 +61,10 @@ const LanguagePost: FC<any> = ({ data }) => {
) )
} }
export default LanguagePost export default ResourcePage
export const query = graphql` export const query = graphql`
query LanguagePost($file: String!) { query ResourcePage($file: String!) {
file(relativePath: { eq: $file }) { file(relativePath: { eq: $file }) {
relativePath relativePath
post: childMarkdownRemark { post: childMarkdownRemark {