diff --git a/gatsby-config.js b/gatsby-config.js index fb66401..7026d16 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -63,6 +63,14 @@ module.exports = { path: `${__dirname}/src/content/resources`, }, }, + { + resolve: `gatsby-source-git`, + options: { + name: `what-is-archive`, + remote: `https://github.com/the-programmers-hangout/technology-spotlight`, + patterns: [`*.md`, `!README.md`], + }, + }, `gatsby-transformer-sharp`, `gatsby-plugin-react-svg`, `gatsby-plugin-sharp`, diff --git a/gatsby-node.js b/gatsby-node.js index d07b343..f5bed1f 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -7,8 +7,21 @@ const fs = require("fs") const requiredArticleFrontmatter = ["authors", "date"] const LAYOUT_RESOURCES = "resources" +const LAYOUT_ARCHIVES = "archives" const LAYOUT_REGULAR = "regular" +function resolveLayout(path) { + if (path.match(/resources/)) { + return LAYOUT_RESOURCES + } + + if (path.match(/archives/)) { + return LAYOUT_ARCHIVES + } + + return LAYOUT_REGULAR +} + let users = [] try { users = JSON.parse(fs.readFileSync("./users.json", "utf-8")) @@ -38,6 +51,7 @@ const createResources = async ({ createPage, graphql }) => { edges { node { relativePath + sourceInstanceName } } } @@ -60,9 +74,43 @@ const createResources = async ({ createPage, graphql }) => { }) } +const createArchives = async ({ createPage, graphql }) => { + const archive = path.resolve(`src/templates/archive.tsx`) + + const result = await graphql(` + query FetchArchives { + allFile(filter: { sourceInstanceName: { eq: "what-is-archive" } }) { + edges { + node { + relativePath + sourceInstanceName + } + } + } + } + `) + + if (result.errors) { + return Promise.reject(result.errors) + } + + return result.data.allFile.edges.forEach(({ node }) => { + createPage({ + path: path.join("archives", node.relativePath), + component: archive, + context: { + file: node.relativePath, + layout: LAYOUT_ARCHIVES, + }, + }) + }) +} + exports.onCreateNode = async ({ node, getNode, actions }) => { const { createPage, createNodeField } = actions - if (node.internal.type === "MarkdownRemark") { + // TODO: find a better way to avoid handling non-resources + // or better, attach authors to what-is archives + if (node.internal.type === "MarkdownRemark" && node.frontmatter.authors) { const { frontmatter } = node const isDoc = Boolean(!frontmatter.path) @@ -91,13 +139,15 @@ exports.onCreateNode = async ({ node, getNode, actions }) => { exports.onCreatePage = ({ page, actions }) => { const { createPage } = actions - page.context.layout = page.path.match(/resources/) - ? LAYOUT_RESOURCES - : LAYOUT_REGULAR + page.context.layout = resolveLayout(page.path) createPage(page) } exports.createPages = ({ actions, graphql }) => { const { createPage } = actions - return createResources({ createPage, graphql }) + + return Promise.all([ + createArchives({ createPage, graphql }), + createResources({ createPage, graphql }), + ]) } diff --git a/package-lock.json b/package-lock.json index 54108b8..2e44d50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7770,7 +7770,8 @@ "ansi-regex": { "version": "2.1.1", "resolved": false, - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "optional": true }, "aproba": { "version": "1.2.0", @@ -7791,12 +7792,14 @@ "balanced-match": { "version": "1.0.0", "resolved": false, - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "optional": true }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7811,17 +7814,20 @@ "code-point-at": { "version": "1.1.0", "resolved": false, - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "optional": true }, "concat-map": { "version": "0.0.1", "resolved": false, - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "optional": true }, "console-control-strings": { "version": "1.1.0", "resolved": false, - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -7938,7 +7944,8 @@ "inherits": { "version": "2.0.3", "resolved": false, - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "optional": true }, "ini": { "version": "1.3.5", @@ -7950,6 +7957,7 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7964,6 +7972,7 @@ "version": "3.0.4", "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -7971,12 +7980,14 @@ "minimist": { "version": "0.0.8", "resolved": false, - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "optional": true }, "minipass": { "version": "2.3.5", "resolved": false, "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -7995,6 +8006,7 @@ "version": "0.5.1", "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "optional": true, "requires": { "minimist": "0.0.8" } @@ -8075,7 +8087,8 @@ "number-is-nan": { "version": "1.0.1", "resolved": false, - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "optional": true }, "object-assign": { "version": "4.1.1", @@ -8087,6 +8100,7 @@ "version": "1.4.0", "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "optional": true, "requires": { "wrappy": "1" } @@ -8172,7 +8186,8 @@ "safe-buffer": { "version": "5.1.2", "resolved": false, - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -8208,6 +8223,7 @@ "version": "1.0.2", "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8227,6 +8243,7 @@ "version": "3.0.1", "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -8270,12 +8287,14 @@ "wrappy": { "version": "1.0.2", "resolved": false, - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "optional": true }, "yallist": { "version": "3.0.3", "resolved": false, - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "optional": true } } }, @@ -9522,6 +9541,39 @@ } } }, + "gatsby-source-git": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/gatsby-source-git/-/gatsby-source-git-1.0.2.tgz", + "integrity": "sha512-TNCC8PnRK3knrO5Q9qpOWVsYuRuLZ07LGB2kQw4/b4bmbciVFZSNUfuJpokpl3/6DPvcePQtNjARVpdKrRYhog==", + "requires": { + "fast-glob": "^2.2.3", + "fs-extra": "^5.0.0", + "gatsby-source-filesystem": "^2.1.19", + "git-url-parse": "^11.1.1", + "rimraf": "^2.6.2", + "simple-git": "^1.105.0" + }, + "dependencies": { + "fs-extra": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", + "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + } + } + }, "gatsby-telemetry": { "version": "1.1.33", "resolved": "https://registry.npmjs.org/gatsby-telemetry/-/gatsby-telemetry-1.1.33.tgz", @@ -9821,6 +9873,14 @@ "parse-url": "^5.0.0" } }, + "git-url-parse": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.1.2.tgz", + "integrity": "sha512-gZeLVGY8QVKMIkckncX+iCq2/L8PlwncvDFKiWkBn9EtCfYDbliRTTp6qzyQ1VMdITUfq7293zDzfpjdiGASSQ==", + "requires": { + "git-up": "^4.0.0" + } + }, "github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -11061,12 +11121,14 @@ "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "optional": true }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "optional": true }, "slice-ansi": { "version": "1.0.0", @@ -11092,6 +11154,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "optional": true, "requires": { "ansi-regex": "^4.1.0" } @@ -16383,6 +16446,7 @@ "version": "0.15.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.15.0.tgz", "integrity": "sha512-xAefmSfN6jqAa7Kuq7LIJY0bwAPG3xlCj0HMEBQk1lxYiDKZscY2xJ5U/61ZTrYbmNQbXa+gc7czPkVo11tnCg==", + "optional": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -16757,6 +16821,24 @@ } } }, + "simple-git": { + "version": "1.126.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.126.0.tgz", + "integrity": "sha512-47mqHxgZnN8XRa9HbpWprzUv3Ooqz9RY/LSZgvA7jCkW8jcwLahMz7LKugY91KZehfG0sCVPtgXiU72hd6b1Bw==", + "requires": { + "debug": "^4.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, "simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", diff --git a/package.json b/package.json index 0056e78..708503b 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "gatsby-remark-auto-headers": "^1.0.3", "gatsby-remark-katex": "^3.1.15", "gatsby-source-filesystem": "^2.1.33", + "gatsby-source-git": "^1.0.2", "gatsby-transformer-remark": "^2.6.30", "gatsby-transformer-sharp": "^2.3.0", "katex": "^0.11.1", diff --git a/src/components/ArchivesSidebar/index.tsx b/src/components/ArchivesSidebar/index.tsx new file mode 100644 index 0000000..6a1bad7 --- /dev/null +++ b/src/components/ArchivesSidebar/index.tsx @@ -0,0 +1,35 @@ +import { graphql, useStaticQuery } from "gatsby" +import React, { FC, HTMLAttributes } from "react" +import { IAllArchivesQuery, IFileOrFolder } from "../../types" +import { humanize } from "../../utils" +import { ColumnSidebar } from "../ColumnSidebar" +import useBuildTree from "./../../hooks/useBuildTree" +import * as SC from "./styles" + +const ALL_ARCHIVES = graphql` + query { + allFile(filter: { sourceInstanceName: { eq: "what-is-archive" } }) { + edges { + node { + relativePath + } + } + } + } +` + +function plantTree(item: IFileOrFolder) { + return ( + + {humanize(item.title)} + + ) +} + +export const ArchivesSidebar: FC> = props => { + const archives = useStaticQuery(ALL_ARCHIVES) + const tree = useBuildTree(archives, "/archives") + const sortedTree = tree.sort((a, b) => a.title.localeCompare(b.title)) + + return {sortedTree.map(plantTree)} +} diff --git a/src/components/ArchivesSidebar/styles.tsx b/src/components/ArchivesSidebar/styles.tsx new file mode 100644 index 0000000..e8bac45 --- /dev/null +++ b/src/components/ArchivesSidebar/styles.tsx @@ -0,0 +1,31 @@ +import { Link } from "gatsby" +import { transparentize } from "polished" +import styled from "styled-components" + +export const PageLink = styled(Link)` + display: block; + padding: 4px 0 0; + margin-bottom: 4px; + color: ${props => props.theme.sidebar.foreground}; + align-self: flex-start; + text-decoration: none; + border-bottom: 2px solid transparent; + + &:hover { + border-color: ${props => + transparentize(0.7, props.theme.sidebar.foreground)}; + } + + & + & { + margin-top: 4px; + } + + &.active { + font-weight: 700; + color: ${props => props.theme.sidebar.active}; + } + + &.active:hover { + border-color: ${props => transparentize(0.7, props.theme.sidebar.active)}; + } +` diff --git a/src/components/ResourceBreadcrumb/index.tsx b/src/components/Breadcrumb/index.tsx similarity index 81% rename from src/components/ResourceBreadcrumb/index.tsx rename to src/components/Breadcrumb/index.tsx index 19baf87..a42e563 100644 --- a/src/components/ResourceBreadcrumb/index.tsx +++ b/src/components/Breadcrumb/index.tsx @@ -9,8 +9,9 @@ interface ILinkProps { item: IFileOrFolder } -interface IResourceBreadcrumbProps { +interface IBreadcrumbProps { relativePath: any + basePath: string } const Link: FC = ({ item }) => { @@ -28,12 +29,12 @@ function flatten([ return flatten([...currNode.children, ...previousNodes, currNode]) } -export function ResourceBreadcrumb({ relativePath }: IResourceBreadcrumbProps) { +export function Breadcrumb({ relativePath, basePath }: IBreadcrumbProps) { const paths = relativePath.split("/") - const breadcrumbItems = flatten([traversePaths(paths)]) + const breadcrumbItems = flatten([traversePaths(paths, basePath)]) return ( - + ) })} - + ) } diff --git a/src/components/ResourceBreadcrumb/styles.tsx b/src/components/Breadcrumb/styles.tsx similarity index 94% rename from src/components/ResourceBreadcrumb/styles.tsx rename to src/components/Breadcrumb/styles.tsx index d0d4545..9601163 100644 --- a/src/components/ResourceBreadcrumb/styles.tsx +++ b/src/components/Breadcrumb/styles.tsx @@ -2,7 +2,7 @@ import { Link } from "gatsby" import { darken, lighten } from "polished" import styled from "styled-components" -export const ResourceBreadcrumbWrapper = styled.div` +export const BreadcrumbWrapper = styled.div` display: flex; align-items: flex-start; diff --git a/src/components/ResourceHeader/index.tsx b/src/components/ColumnHeader/index.tsx similarity index 67% rename from src/components/ResourceHeader/index.tsx rename to src/components/ColumnHeader/index.tsx index bf7e280..9045c1f 100644 --- a/src/components/ResourceHeader/index.tsx +++ b/src/components/ColumnHeader/index.tsx @@ -1,22 +1,23 @@ import React, { FC, Fragment } from "react" import ChevronUp from "../../icons/chevron-up.svg" -import { ResourceBreadcrumb } from "../ResourceBreadcrumb" +import { Breadcrumb } from "../Breadcrumb" import { StackedAvatars } from "../StackedAvatars" import * as SC from "./styles" -interface IResourceHeaderProps { +interface IColumnHeaderProps { relativePath: string + basePath: string title: string - authors: Array<{ + authors?: Array<{ avatar: string hash: string name: string }> createdAt: string timeToRead: number - recommendedReading: string[] - externalResources: string[] + recommendedReading?: string[] + externalResources?: string[] } function ExtraLink({ @@ -47,7 +48,8 @@ function ExtraLink({ ) } -export const ResourceHeader: FC = ({ +export const ColumnHeader: FC = ({ + basePath, relativePath, title, authors, @@ -63,24 +65,26 @@ export const ResourceHeader: FC = ({ const dateToHuman = `${month} ${day}, ${year}` return ( - - + + {title} - - - - {authors.length} contributor{authors.length > 1 && "s"} - - {authors - .map(author => `${author.name}#${author.hash}`) - .join(", ")} - - - - {dateToHuman} + {authors && ( + + + + {authors.length} contributor{authors.length > 1 && "s"} + + {authors + .map(author => `${author.name}#${author.hash}`) + .join(", ")} + + + + )} + {dateToHuman && {dateToHuman}} {timeToRead} minute{timeToRead !== 1 && "s"} read time @@ -103,6 +107,6 @@ export const ResourceHeader: FC = ({ })} )} - + ) } diff --git a/src/components/ResourceHeader/styles.tsx b/src/components/ColumnHeader/styles.tsx similarity index 98% rename from src/components/ResourceHeader/styles.tsx rename to src/components/ColumnHeader/styles.tsx index 95a2374..a45018a 100644 --- a/src/components/ResourceHeader/styles.tsx +++ b/src/components/ColumnHeader/styles.tsx @@ -3,7 +3,7 @@ import { darken, lighten } from "polished" import styled, { css } from "styled-components" import { fontFamily, modularScale } from "../../design/typography" -export const ResourceHeaderWrapper = styled.div` +export const ColumnHeaderWrapper = styled.div` border-bottom: 1px solid #dbdbdb; padding-bottom: 32px; margin-bottom: 32px; diff --git a/src/components/ColumnLink/index.tsx b/src/components/ColumnLink/index.tsx new file mode 100644 index 0000000..b455298 --- /dev/null +++ b/src/components/ColumnLink/index.tsx @@ -0,0 +1,18 @@ +import React, { FC } from "react" +import * as SC from "./styles" + +interface IColumnLinkProps { + to: string +} + +export const ColumnLink: FC = ({ children, to }) => { + if (!to.match(/^(https?:\/\/)/)) { + return {children} + } + + return ( + + {children} + + ) +} diff --git a/src/components/ResourcesLink/styles.tsx b/src/components/ColumnLink/styles.tsx similarity index 76% rename from src/components/ResourcesLink/styles.tsx rename to src/components/ColumnLink/styles.tsx index 27a0eee..e2246b6 100644 --- a/src/components/ResourcesLink/styles.tsx +++ b/src/components/ColumnLink/styles.tsx @@ -15,10 +15,10 @@ const linkStyle = css` } ` -export const ResourcesLinkInternal = styled(Link)` +export const ColumnLinkInternal = styled(Link)` ${linkStyle} ` -export const ResourcesLinkExternal = styled.a` +export const ColumnLinkExternal = styled.a` ${linkStyle} ` diff --git a/src/components/ColumnSidebar/index.tsx b/src/components/ColumnSidebar/index.tsx new file mode 100644 index 0000000..758ff51 --- /dev/null +++ b/src/components/ColumnSidebar/index.tsx @@ -0,0 +1,49 @@ +import { Link } from "gatsby" +import React, { FC, HTMLAttributes, PropsWithChildren } from "react" +import Scrollbar from "react-perfect-scrollbar" +import "react-perfect-scrollbar/dist/css/styles.css" +import Banner from "../../images/tph-banner.svg" +import { ThemeToggler } from "../ThemeToggler" +import * as SC from "./styles" + +interface IMenuItemProps { + to: string +} + +const MenuItem: FC = ({ children, to }) => { + return ( + + {children} + + ) +} + +export const ColumnSidebar: FC< + PropsWithChildren> +> = props => { + const { children, ...restProps } = props + + return ( + + + + + + + {children} + + + about + rules + faq + hotbot + resources + tech spotlights + + + + + + + ) +} diff --git a/src/components/ColumnSidebar/styles.tsx b/src/components/ColumnSidebar/styles.tsx new file mode 100644 index 0000000..1a13450 --- /dev/null +++ b/src/components/ColumnSidebar/styles.tsx @@ -0,0 +1,56 @@ +import { Link } from "gatsby" +import styled from "styled-components" + +export const ColumnSidebarWrapper = styled.div` + box-sizing: border-box; + flex: 0 0 320px; + background: ${props => props.theme.sidebar.background}; + color: ${props => props.theme.sidebar.foreground}; + position: fixed; + top: 0; + bottom: 0; + width: 320px; + + @media screen and (max-width: 767px) { + z-index: 100; + width: auto; + /* TODO: maybe find a less "awkward" way to animate this */ + left: calc(-100% - 100px); + right: calc(100% - 0px); + transition: left 0.3s, right 0.3s; + } + + &.is-open { + left: 0; + right: 100px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); + } +` + +export const Inner = styled.div` + padding: 30px 0 30px 20px; +` + +export const Menu = styled.nav` + display: flex; + flex-direction: column; + border-top: 1px dashed #a5a5a5; + margin: 20px 0 40px; + padding-top: 20px; +` + +export const MenuItem = styled(Link)` + padding: 4px 0; + font-size: 20px; + color: ${props => props.theme.sidebar.foreground}; + text-decoration: none; + + &:hover, + &:active { + text-decoration: underline; + } + + &.active { + font-weight: 700; + } +` diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index 72427eb..651a509 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -86,7 +86,7 @@ export const Header: FC = ({ isHome }) => { faq hotbot resources - tech spotlight + tech spotlights join us {isHome && } diff --git a/src/components/ResourcesLink/index.tsx b/src/components/ResourcesLink/index.tsx deleted file mode 100644 index 39fe754..0000000 --- a/src/components/ResourcesLink/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React, { FC } from "react" -import * as SC from "./styles" - -interface IResourcesLinkProps { - to: string -} - -export const ResourcesLink: FC = ({ children, to }) => { - if (!to.match(/^(https?:\/\/)/)) { - return ( - {children} - ) - } - - return ( - - {children} - - ) -} diff --git a/src/components/ResourcesList/useBuildTree.tsx b/src/components/ResourcesList/useBuildTree.tsx index f347827..1184a09 100644 --- a/src/components/ResourcesList/useBuildTree.tsx +++ b/src/components/ResourcesList/useBuildTree.tsx @@ -3,7 +3,7 @@ import { join, traversePathsToFiles } from "../../utils" export default function useBuildTree(resources: IAllResourcesQuery) { const objects = resources.allFile.edges.map(({ node: file }: IFileQuery) => - traversePathsToFiles(file.relativePath.split("/")) + traversePathsToFiles(file.relativePath.split("/"), "/resources") ) return join(objects) diff --git a/src/components/ResourcesSidebar/index.tsx b/src/components/ResourcesSidebar/index.tsx index 76c6953..913bbe6 100644 --- a/src/components/ResourcesSidebar/index.tsx +++ b/src/components/ResourcesSidebar/index.tsx @@ -1,15 +1,12 @@ -import { graphql, Link, useStaticQuery } from "gatsby" +import { graphql, useStaticQuery } from "gatsby" import React, { FC, HTMLAttributes, memo, useState } from "react" -import Scrollbar from "react-perfect-scrollbar" -import "react-perfect-scrollbar/dist/css/styles.css" import TriangleDown from "../../icons/triangle-down.svg" -import Banner from "../../images/tph-banner.svg" import { IAllResourcesQuery, IFileOrFolder, IFolder } from "../../types" import { humanize } from "../../utils" -import { ThemeToggler } from "../ThemeToggler" +import { ColumnSidebar } from "../ColumnSidebar" +import useBuildTree from "./../../hooks/useBuildTree" import useSidebar from "./../../hooks/useSidebar" import * as SC from "./styles" -import useBuildTree from "./useBuildTree" import useMatchingPath from "./useMatchingPath" const ALL_RESOURCES = graphql` @@ -93,44 +90,14 @@ const FirstLevelFolder = memo( } ) -interface IMenuItemProps { - to: string -} - -const MenuItem: FC = ({ children, to }) => { - return ( - - {children} - - ) -} - export const ResourcesSidebar: FC> = props => { const resources = useStaticQuery(ALL_RESOURCES) - const tree = useBuildTree(resources) + const tree = useBuildTree(resources, "/resources") const sortedTree = tree.sort((a, b) => a.title.localeCompare(b.title)) return ( - - - - - - - {sortedTree.map((node, index) => plantTree(node, index, true))} - - - about - rules - faq - hotbot - resources - tech - - - - - - + + {sortedTree.map((node, index) => plantTree(node, index, true))} + ) } diff --git a/src/components/ResourcesSidebar/styles.tsx b/src/components/ResourcesSidebar/styles.tsx index 95ed131..fbcf973 100644 --- a/src/components/ResourcesSidebar/styles.tsx +++ b/src/components/ResourcesSidebar/styles.tsx @@ -3,32 +3,6 @@ import { transparentize } from "polished" import styled from "styled-components" import ChevronUp from "../../icons/chevron-up.svg" -export const ResourcesSidebarWrapper = styled.div` - box-sizing: border-box; - flex: 0 0 320px; - background: ${props => props.theme.sidebar.background}; - color: ${props => props.theme.sidebar.foreground}; - position: fixed; - top: 0; - bottom: 0; - width: 320px; - - @media screen and (max-width: 767px) { - z-index: 100; - width: auto; - /* TODO: maybe find a less "awkward" way to animate this */ - left: calc(-100% - 100px); - right: calc(100% - 0px); - transition: left 0.3s, right 0.3s; - } - - &.is-open { - left: 0; - right: 100px; - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); - } -` - export const Children = styled.div` padding-left: 16px; padding-bottom: 8px; @@ -131,10 +105,6 @@ export const TreeWrapper = styled.div<{ collapsed: boolean }>` export const CollapseToggler = styled(ChevronUp)`` -export const Inner = styled.div` - padding: 30px 0 30px 20px; -` - export const PageLink = styled(Link)` display: block; padding: 4px 0 0; @@ -162,27 +132,3 @@ export const PageLink = styled(Link)` border-color: ${props => transparentize(0.7, props.theme.sidebar.active)}; } ` - -export const Menu = styled.nav` - display: flex; - flex-direction: column; - border-top: 1px dashed #a5a5a5; - margin: 20px 0 40px; - padding-top: 20px; -` - -export const MenuItem = styled(Link)` - padding: 4px 0; - font-size: 20px; - color: ${props => props.theme.sidebar.foreground}; - text-decoration: none; - - &:hover, - &:active { - text-decoration: underline; - } - - &.active { - font-weight: 700; - } -` diff --git a/src/components/ResourcesSidebar/useBuildTree.tsx b/src/components/ResourcesSidebar/useBuildTree.tsx deleted file mode 100644 index 0be532b..0000000 --- a/src/components/ResourcesSidebar/useBuildTree.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { IAllResourcesQuery, IFileQuery } from "../../types" -import { join, traversePaths } from "../../utils" - -export default function useBuildTree(resources: IAllResourcesQuery) { - const objects = resources.allFile.edges.map(({ node: file }: IFileQuery) => - traversePaths(file.relativePath.split("/")) - ) - - return join(objects) -} diff --git a/src/hooks/useBuildTree.tsx b/src/hooks/useBuildTree.tsx new file mode 100644 index 0000000..df337af --- /dev/null +++ b/src/hooks/useBuildTree.tsx @@ -0,0 +1,13 @@ +import { IAllFilesQuery, IFileQuery } from "../types" +import { join, traversePaths } from "../utils" + +export default function useBuildTree( + resources: IAllFilesQuery, + basePath: string +) { + const objects = resources.allFile.edges.map(({ node: file }: IFileQuery) => + traversePaths(file.relativePath.split("/"), basePath) + ) + + return join(objects) +} diff --git a/src/layouts/ArchivesLayout/index.tsx b/src/layouts/ArchivesLayout/index.tsx new file mode 100644 index 0000000..a0523b1 --- /dev/null +++ b/src/layouts/ArchivesLayout/index.tsx @@ -0,0 +1,8 @@ +import React, { FC } from "react" + +import { ArchivesSidebar } from "../../components/ArchivesSidebar" +import { ColumnLayout } from "../ColumnLayout" + +export const ArchivesLayout: FC = ({ children }) => { + return +} diff --git a/src/layouts/ColumnLayout/index.tsx b/src/layouts/ColumnLayout/index.tsx new file mode 100644 index 0000000..0642fbf --- /dev/null +++ b/src/layouts/ColumnLayout/index.tsx @@ -0,0 +1,48 @@ +import React, { FC, useMemo, useState } from "react" + +import { ColumnSidebar } from "../../components/ColumnSidebar" +import { MobileHeader } from "../../components/MobileHeader" +import { GlobalStyles } from "../../globalStyles" +import { useLockBodyScroll } from "../../hooks/useLockBodyScroll" +import { SidebarProvider } from "../../SidebarProvider" +import { ThemeProvider } from "../../ThemeProvider" +import * as SC from "./styles" + +interface IColumnLayoutProps { + sidebar: React.ReactNode + content: React.ReactNode +} + +export const ColumnLayout: FC = ({ sidebar, content }) => { + const [activeMobileMenu, setActiveMobileMenu] = useState(false) + const { lock, unlock } = useLockBodyScroll() + + function openMenu() { + setActiveMobileMenu(true) + lock() + } + + function closeMenu() { + setActiveMobileMenu(false) + unlock() + } + + return ( + + + + + + {sidebar({ className: activeMobileMenu ? "is-open" : "" })} + + {content} + + + + + + ) +} diff --git a/src/layouts/ResourcesLayout/styles.tsx b/src/layouts/ColumnLayout/styles.tsx similarity index 100% rename from src/layouts/ResourcesLayout/styles.tsx rename to src/layouts/ColumnLayout/styles.tsx diff --git a/src/layouts/Layout/index.tsx b/src/layouts/Layout/index.tsx index cc9fcf9..8f63c2e 100644 --- a/src/layouts/Layout/index.tsx +++ b/src/layouts/Layout/index.tsx @@ -1,10 +1,3 @@ -/** - * Layout component that queries for data - * with Gatsby's useStaticQuery component - * - * See: https://www.gatsbyjs.org/docs/use-static-query/ - */ - import { RouteComponentProps } from "@reach/router" import React, { FC, Fragment, useEffect } from "react" diff --git a/src/layouts/ResourcesLayout/index.tsx b/src/layouts/ResourcesLayout/index.tsx index 45461c1..9d5a020 100644 --- a/src/layouts/ResourcesLayout/index.tsx +++ b/src/layouts/ResourcesLayout/index.tsx @@ -1,50 +1,8 @@ -/** - * Layout component that queries for data - * with Gatsby's useStaticQuery component - * - * See: https://www.gatsbyjs.org/docs/use-static-query/ - */ +import React, { FC } from "react" -import React, { FC, useState } from "react" - -import { MobileHeader } from "../../components/MobileHeader" import { ResourcesSidebar } from "../../components/ResourcesSidebar" -import { GlobalStyles } from "../../globalStyles" -import { useLockBodyScroll } from "../../hooks/useLockBodyScroll" -import { SidebarProvider } from "../../SidebarProvider" -import { ThemeProvider } from "../../ThemeProvider" -import * as SC from "./styles" +import { ColumnLayout } from "../ColumnLayout" export const ResourcesLayout: FC = ({ children }) => { - const [activeMobileMenu, setActiveMobileMenu] = useState(false) - const { lock, unlock } = useLockBodyScroll() - - function openMenu() { - setActiveMobileMenu(true) - lock() - } - - function closeMenu() { - setActiveMobileMenu(false) - unlock() - } - - return ( - - - - - - - - {children} - - - - - - ) + return } diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 10dcad1..e386a6b 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -1,6 +1,7 @@ import { WindowLocation } from "@reach/router" -import React, { PropsWithChildren } from "react" +import React, { PropsWithChildren, useMemo } from "react" import { LocationProvider } from "../LocationProvider" +import { ArchivesLayout } from "./ArchivesLayout" import { Layout } from "./Layout" import { ResourcesLayout } from "./ResourcesLayout" @@ -14,13 +15,20 @@ export default function BaseLayout({ } location: WindowLocation }>) { + const ResolvedLayout = useMemo(() => { + switch (pageContext.layout) { + case "resources": + return ResourcesLayout + case "archives": + return ArchivesLayout + default: + return Layout + } + }, [pageContext.layout]) + return ( - {pageContext.layout === "resources" ? ( - {children} - ) : ( - {children} - )} + {children} ) } diff --git a/src/pages/archives.tsx b/src/pages/archives.tsx new file mode 100644 index 0000000..cc84a1f --- /dev/null +++ b/src/pages/archives.tsx @@ -0,0 +1,21 @@ +import React, { Fragment } from "react" + +import { ColumnLink } from "../components/ColumnLink" +import { SEO } from "../components/SEO" + +function ResourcesPage() { + return ( + + +

Welcome to the TPH tech spotlights archives.

+

+ This regroups the archives of our occasional, temporary channels that + cover a piece of technology that might be unknown to part of our users. + You can find those on{" "} + The Programmer's Hangout. +

+
+ ) +} + +export default ResourcesPage diff --git a/src/pages/resources.tsx b/src/pages/resources.tsx index 9b062e4..a1cafc0 100644 --- a/src/pages/resources.tsx +++ b/src/pages/resources.tsx @@ -1,7 +1,7 @@ import React, { Fragment } from "react" import packageJson from "../../package.json" -import { ResourcesLink } from "../components/ResourcesLink" +import { ColumnLink } from "../components/ColumnLink" import { ResourcesList } from "../components/ResourcesList" import { SEO } from "../components/SEO" @@ -13,11 +13,11 @@ function ResourcesPage() {

This is meant as a small knowledge base for commonly answered questions on our Discord community,{" "} - The Programmer's Hangout. + The Programmer's Hangout.

All of it is open source, and you can contribute to it on{" "} - GitHub. + GitHub.

diff --git a/src/templates/archive.tsx b/src/templates/archive.tsx new file mode 100644 index 0000000..e04d3d6 --- /dev/null +++ b/src/templates/archive.tsx @@ -0,0 +1,44 @@ +import { graphql } from "gatsby" +import React, { FC, Fragment } from "react" +import { ColumnHeader } from "../components/ColumnHeader" +import { Markdown } from "../components/Markdown" +import { SEO } from "../components/SEO" +import { humanize } from "../utils" + +// @todo maybe find alternative type for data +const Archive: FC = ({ data }) => { + const { relativePath, ctime } = data.file + const { html, excerpt, timeToRead } = data.file.post + + const title = humanize(relativePath) + + return ( + + + + + + ) +} + +export default Archive + +export const query = graphql` + query Archive($file: String!) { + file(relativePath: { eq: $file }) { + relativePath + ctime + post: childMarkdownRemark { + html + excerpt + timeToRead + } + } + } +` diff --git a/src/templates/languagePost.tsx b/src/templates/languagePost.tsx index b57977b..fa9828e 100644 --- a/src/templates/languagePost.tsx +++ b/src/templates/languagePost.tsx @@ -1,7 +1,7 @@ import { graphql } from "gatsby" import React, { FC, Fragment } from "react" +import { ColumnHeader } from "../components/ColumnHeader" import { Markdown } from "../components/Markdown" -import { ResourceHeader } from "../components/ResourceHeader" import { SEO } from "../components/SEO" import "katex/dist/katex.min.css" @@ -14,8 +14,9 @@ const LanguagePost: FC = ({ data }) => { return ( -