From f5e159c407fdb76981d2aa2501edf774e25ef76b Mon Sep 17 00:00:00 2001 From: Zack Stickles Date: Tue, 12 May 2020 11:48:12 -0700 Subject: [PATCH] feat: dynamically creating the sidebar --- src/components/Sidebar.js | 10 ++-- src/pages/reference.js | 67 +++++++----------------- src/utils/nav-from-edges.js | 100 ++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 52 deletions(-) create mode 100644 src/utils/nav-from-edges.js diff --git a/src/components/Sidebar.js b/src/components/Sidebar.js index 64fa2d446..7c63d437c 100644 --- a/src/components/Sidebar.js +++ b/src/components/Sidebar.js @@ -9,9 +9,13 @@ import './Sidebar.scss'; // recursively create navigation const renderNav = (page, index) => (
  • - - {page.displayName} - + {page.url ? ( + + {page.displayName} + + ) : ( +
    {page.displayName}
    + )} {page.children && }
  • ); diff --git a/src/pages/reference.js b/src/pages/reference.js index a4bcc4bd9..7e7f3f9c8 100644 --- a/src/pages/reference.js +++ b/src/pages/reference.js @@ -1,64 +1,33 @@ import React, { useState } from 'react'; +import { useStaticQuery, graphql } from 'gatsby'; import Container from '../components/Container'; import Layout from '../components/Layout'; import Sidebar from '../components/Sidebar'; +import navFromEdges from '../utils/nav-from-edges'; // TODO: move this js file to same directory and update import import '../templates/Reference.scss'; -// TODO: pull this in from Gatsby -const pages = [ - { displayName: 'Overview', url: '' }, - { - displayName: 'CLI', - url: '', - children: [ - { displayName: 'newrelic', url: '' }, - { displayName: 'nr1', url: '' }, - ], - }, - { displayName: 'GraphQL', url: '' }, - { - displayName: 'Applications', - url: '', - children: [ - { displayName: 'Component Library', url: '', active: true }, - { displayName: 'File structure', url: '' }, - ], - }, - { - displayName: 'Data Collectors', - url: '', - children: [ - { displayName: 'Custom Attributes', url: '' }, - { displayName: 'Custom Events', url: '' }, - { displayName: 'Open Telemetry', url: '' }, - { displayName: 'Telemetry SDK', url: '' }, - ], - }, - { - displayName: 'Automation', - url: '', - children: [ - { displayName: 'Cloud Formation Provider', url: '' }, - { displayName: 'Terraform Provider', url: '' }, - { - displayName: 'Agent Deploy', - url: '', - children: [ - { displayName: 'Ansible', url: '' }, - { displayName: 'Chef', url: '' }, - { displayName: 'Puppet', url: '' }, - ], - }, - ], - }, -]; - const Reference = () => { const [isOpen, setIsOpen] = useState(false); + const data = useStaticQuery(graphql` + query { + allMarkdownRemark(limit: 1000) { + edges { + node { + frontmatter { + path + title + } + } + } + } + } + `); + const pages = navFromEdges(data.allMarkdownRemark.edges); + return ( diff --git a/src/utils/nav-from-edges.js b/src/utils/nav-from-edges.js new file mode 100644 index 000000000..2505ae1a5 --- /dev/null +++ b/src/utils/nav-from-edges.js @@ -0,0 +1,100 @@ +/** + * Convert an object used for generating the tree into an object + * to be used in the UI. + * + * @param {Object} link - A link object that includes directories. + * @param {string} link.url - The local path to the page. + * @param {string} link.displayName - The title for the page. + * @return {Object} A link object with just a url and displayName. + */ +const linkWithoutDirs = (link) => ({ + url: link.url, + displayName: link.displayName, +}); + +/** + * Creates a link object (including a list of directories) from an + * edge (generated by GraphQL). + * + * @param {Object} edge - A markdown edge. + * @return {Object} A link object that includes directories. + */ +const linkFromEdge = (edge) => { + const { path, title } = edge?.node?.frontmatter; + return { + url: path, + displayName: title, + dirs: path.split('/').slice(1), + }; +}; + +/** + * Converts a slug into a displayName. + * + * @param {string} str - A slug-like string. + * @return {string} A displayName for use in the UI. + */ +const makeDisplayName = (str) => + str + .split('-') + .map((word) => word.replace(/\b\w/g, (l) => l.toUpperCase())) + .join(' '); + +/** + * Generates an array of links for the UI. Each link can have an + * array of links below that. This function can call itself. + * + * TODO: do we need result? + * + * @param {Object[]} links - An array of links that are >= this level. + * @param {Object[]} [result] - The links to be returned. + * @param {number} [level=0] The nested level. + */ +const genTree = (links, result = [], level = 0) => { + const linksAtLevel = links.filter((link) => level === link.dirs.length - 1); + + // if we have nothing below this, just return a flat list of links + if (linksAtLevel.length === links.length) { + return linksAtLevel.map(linkWithoutDirs); + } + + // get all the directories at this level + const linksBelowLevel = links.filter((link) => level < link.dirs.length - 1); + const dirsAtLevel = linksBelowLevel.map((link) => link.dirs[level]); + const uniqueDirsAtLevel = [...new Set(dirsAtLevel)]; + + return uniqueDirsAtLevel.reduce((acc, dir) => { + const linksUnderDir = links.filter((link) => link.dirs[level] === dir); + + // find the index page, or make a non-link item for this + const index = linksUnderDir.find( + (link) => link.dirs[level + 1] === 'index' + ); + + // get the children for this node + const childLinks = linksUnderDir.filter((link) => link !== index); + + return [ + ...acc, + { + ...(index + ? linkWithoutDirs(index) + : { displayName: makeDisplayName(dir) }), + children: genTree(childLinks, result, level + 1), + }, + ]; + }, []); +}; + +/** + * Given a list of edges, generates the navigation to be used in the UI. + * + * @param {Object[]} edges - An array of edge objects. + * @return {Object[]} An array of link objects. + */ +const getNavFromEdges = (edges) => { + const links = edges.map(linkFromEdge); + return genTree(links); +}; + +export default getNavFromEdges;