mirror of
https://github.com/Stone-Red-Code/website.git
synced 2026-09-04 09:15:58 +02:00
feat(archives): bootstrap what-is-archives from Github
This commit is contained in:
@@ -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 (
|
||||
<SC.PageLink key={item.title} to={item.path} activeClassName="active">
|
||||
{humanize(item.title)}
|
||||
</SC.PageLink>
|
||||
)
|
||||
}
|
||||
|
||||
export const ArchivesSidebar: FC<HTMLAttributes<HTMLDivElement>> = props => {
|
||||
const archives = useStaticQuery<IAllArchivesQuery>(ALL_ARCHIVES)
|
||||
const tree = useBuildTree(archives, "/archives")
|
||||
const sortedTree = tree.sort((a, b) => a.title.localeCompare(b.title))
|
||||
|
||||
return <ColumnSidebar {...props}>{sortedTree.map(plantTree)}</ColumnSidebar>
|
||||
}
|
||||
@@ -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)};
|
||||
}
|
||||
`
|
||||
@@ -9,8 +9,9 @@ interface ILinkProps {
|
||||
item: IFileOrFolder
|
||||
}
|
||||
|
||||
interface IResourceBreadcrumbProps {
|
||||
interface IBreadcrumbProps {
|
||||
relativePath: any
|
||||
basePath: string
|
||||
}
|
||||
|
||||
const Link: FC<ILinkProps> = ({ 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 (
|
||||
<SC.ResourceBreadcrumbWrapper>
|
||||
<SC.BreadcrumbWrapper>
|
||||
<SC.LinkWrapper>
|
||||
<Link
|
||||
item={{ path: "/", title: "home", type: "folder", children: [] }}
|
||||
@@ -43,8 +44,8 @@ export function ResourceBreadcrumb({ relativePath }: IResourceBreadcrumbProps) {
|
||||
<SC.LinkWrapper>
|
||||
<Link
|
||||
item={{
|
||||
path: "/resources",
|
||||
title: "resources",
|
||||
path: basePath,
|
||||
title: basePath.replace(/\//, ""),
|
||||
type: "folder",
|
||||
children: [],
|
||||
}}
|
||||
@@ -67,6 +68,6 @@ export function ResourceBreadcrumb({ relativePath }: IResourceBreadcrumbProps) {
|
||||
</SC.CurrentPage>
|
||||
)
|
||||
})}
|
||||
</SC.ResourceBreadcrumbWrapper>
|
||||
</SC.BreadcrumbWrapper>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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<IResourceHeaderProps> = ({
|
||||
export const ColumnHeader: FC<IColumnHeaderProps> = ({
|
||||
basePath,
|
||||
relativePath,
|
||||
title,
|
||||
authors,
|
||||
@@ -63,24 +65,26 @@ export const ResourceHeader: FC<IResourceHeaderProps> = ({
|
||||
const dateToHuman = `${month} ${day}, ${year}`
|
||||
|
||||
return (
|
||||
<SC.ResourceHeaderWrapper>
|
||||
<ResourceBreadcrumb relativePath={relativePath} />
|
||||
<SC.ColumnHeaderWrapper>
|
||||
<Breadcrumb relativePath={relativePath} basePath={basePath} />
|
||||
|
||||
<SC.Title>{title}</SC.Title>
|
||||
|
||||
<SC.Top>
|
||||
<SC.Meta>
|
||||
<StackedAvatars authors={authors} />
|
||||
<SC.PopoverToggler>
|
||||
{authors.length} contributor{authors.length > 1 && "s"}
|
||||
<SC.Popover>
|
||||
{authors
|
||||
.map(author => `${author.name}#${author.hash}`)
|
||||
.join(", ")}
|
||||
</SC.Popover>
|
||||
</SC.PopoverToggler>
|
||||
</SC.Meta>
|
||||
<SC.Meta>{dateToHuman}</SC.Meta>
|
||||
{authors && (
|
||||
<SC.Meta>
|
||||
<StackedAvatars authors={authors} />
|
||||
<SC.PopoverToggler>
|
||||
{authors.length} contributor{authors.length > 1 && "s"}
|
||||
<SC.Popover>
|
||||
{authors
|
||||
.map(author => `${author.name}#${author.hash}`)
|
||||
.join(", ")}
|
||||
</SC.Popover>
|
||||
</SC.PopoverToggler>
|
||||
</SC.Meta>
|
||||
)}
|
||||
{dateToHuman && <SC.Meta>{dateToHuman}</SC.Meta>}
|
||||
<SC.Meta>
|
||||
{timeToRead} minute{timeToRead !== 1 && "s"} read time
|
||||
</SC.Meta>
|
||||
@@ -103,6 +107,6 @@ export const ResourceHeader: FC<IResourceHeaderProps> = ({
|
||||
})}
|
||||
</SC.ExternalResources>
|
||||
)}
|
||||
</SC.ResourceHeaderWrapper>
|
||||
</SC.ColumnHeaderWrapper>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
@@ -0,0 +1,18 @@
|
||||
import React, { FC } from "react"
|
||||
import * as SC from "./styles"
|
||||
|
||||
interface IColumnLinkProps {
|
||||
to: string
|
||||
}
|
||||
|
||||
export const ColumnLink: FC<IColumnLinkProps> = ({ children, to }) => {
|
||||
if (!to.match(/^(https?:\/\/)/)) {
|
||||
return <SC.ColumnLinkInternal to={to}>{children}</SC.ColumnLinkInternal>
|
||||
}
|
||||
|
||||
return (
|
||||
<SC.ColumnLinkExternal href={to} target="_blank">
|
||||
{children}
|
||||
</SC.ColumnLinkExternal>
|
||||
)
|
||||
}
|
||||
@@ -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}
|
||||
`
|
||||
@@ -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<IMenuItemProps> = ({ children, to }) => {
|
||||
return (
|
||||
<SC.MenuItem to={to} activeClassName="active">
|
||||
{children}
|
||||
</SC.MenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
export const ColumnSidebar: FC<
|
||||
PropsWithChildren<HTMLAttributes<HTMLDivElement>>
|
||||
> = props => {
|
||||
const { children, ...restProps } = props
|
||||
|
||||
return (
|
||||
<SC.ColumnSidebarWrapper {...restProps}>
|
||||
<Scrollbar>
|
||||
<Link to="/">
|
||||
<Banner />
|
||||
</Link>
|
||||
<SC.Inner>
|
||||
{children}
|
||||
|
||||
<SC.Menu>
|
||||
<MenuItem to="/about">about</MenuItem>
|
||||
<MenuItem to="/rules">rules</MenuItem>
|
||||
<MenuItem to="/faq">faq</MenuItem>
|
||||
<MenuItem to="/">hotbot</MenuItem>
|
||||
<MenuItem to="/resources">resources</MenuItem>
|
||||
<MenuItem to="/archives">tech spotlights</MenuItem>
|
||||
</SC.Menu>
|
||||
|
||||
<ThemeToggler />
|
||||
</SC.Inner>
|
||||
</Scrollbar>
|
||||
</SC.ColumnSidebarWrapper>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
`
|
||||
@@ -86,7 +86,7 @@ export const Header: FC<IHeaderProps> = ({ isHome }) => {
|
||||
<MenuItem to="/">faq</MenuItem>
|
||||
<MenuItem to="/">hotbot</MenuItem>
|
||||
<MenuItem to="/resources">resources</MenuItem>
|
||||
<MenuItem to="/">tech spotlight</MenuItem>
|
||||
<MenuItem to="/archives">tech spotlights</MenuItem>
|
||||
</SC.Menu>
|
||||
<DiscordButton>join us</DiscordButton>
|
||||
{isHome && <Partner />}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import React, { FC } from "react"
|
||||
import * as SC from "./styles"
|
||||
|
||||
interface IResourcesLinkProps {
|
||||
to: string
|
||||
}
|
||||
|
||||
export const ResourcesLink: FC<IResourcesLinkProps> = ({ children, to }) => {
|
||||
if (!to.match(/^(https?:\/\/)/)) {
|
||||
return (
|
||||
<SC.ResourcesLinkInternal to={to}>{children}</SC.ResourcesLinkInternal>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SC.ResourcesLinkExternal href={to} target="_blank">
|
||||
{children}
|
||||
</SC.ResourcesLinkExternal>
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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<IMenuItemProps> = ({ children, to }) => {
|
||||
return (
|
||||
<SC.MenuItem to={to} activeClassName="active">
|
||||
{children}
|
||||
</SC.MenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
export const ResourcesSidebar: FC<HTMLAttributes<HTMLDivElement>> = props => {
|
||||
const resources = useStaticQuery<IAllResourcesQuery>(ALL_RESOURCES)
|
||||
const tree = useBuildTree(resources)
|
||||
const tree = useBuildTree(resources, "/resources")
|
||||
const sortedTree = tree.sort((a, b) => a.title.localeCompare(b.title))
|
||||
|
||||
return (
|
||||
<SC.ResourcesSidebarWrapper {...props}>
|
||||
<Scrollbar>
|
||||
<Link to="/">
|
||||
<Banner />
|
||||
</Link>
|
||||
<SC.Inner>
|
||||
{sortedTree.map((node, index) => plantTree(node, index, true))}
|
||||
|
||||
<SC.Menu>
|
||||
<MenuItem to="/about">about</MenuItem>
|
||||
<MenuItem to="/rules">rules</MenuItem>
|
||||
<MenuItem to="/faq">faq</MenuItem>
|
||||
<MenuItem to="/">hotbot</MenuItem>
|
||||
<MenuItem to="/resources">resources</MenuItem>
|
||||
<MenuItem to="/">tech</MenuItem>
|
||||
</SC.Menu>
|
||||
|
||||
<ThemeToggler />
|
||||
</SC.Inner>
|
||||
</Scrollbar>
|
||||
</SC.ResourcesSidebarWrapper>
|
||||
<ColumnSidebar {...props}>
|
||||
{sortedTree.map((node, index) => plantTree(node, index, true))}
|
||||
</ColumnSidebar>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user