mirror of
https://github.com/Stone-Red-Code/website.git
synced 2026-09-06 23:43:45 +02:00
Merge pull request #273 from the-programmers-hangout/feat-topics
feat(resources): support for topics
This commit is contained in:
+9
-2
@@ -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
@@ -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 }) => {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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 {
|
||||||
Reference in New Issue
Block a user