From 813dfae35ee21756f3d956876f49c39c5222463d Mon Sep 17 00:00:00 2001 From: Zach Shilton <4624598+zchsh@users.noreply.github.com> Date: Thu, 25 Feb 2021 10:09:27 -0500 Subject: [PATCH 01/62] WIP initial refactor of DocsSidenav --- packages/docs-page/docs.mdx | 16 +- packages/docs-page/index.js | 23 +- packages/docs-page/package-lock.json | 261 ++++---- packages/docs-page/package.json | 6 +- packages/docs-page/props.js | 11 +- packages/docs-page/server.js | 14 +- packages/docs-sidenav/README.md | 2 +- packages/docs-sidenav/chevron-icon.js | 18 - packages/docs-sidenav/docs.mdx | 9 +- .../fixtures/navdata-example.json | 60 ++ packages/docs-sidenav/icons/bullet.svg | 3 + packages/docs-sidenav/icons/chevron.svg | 3 + .../external.svg => icons/external-link.svg} | 0 .../{menu-icon.js => icons/menu.svg} | 14 +- packages/docs-sidenav/index.js | 590 +++++------------- packages/docs-sidenav/index.test.js | 159 +---- packages/docs-sidenav/package-lock.json | 15 +- packages/docs-sidenav/package.json | 3 +- packages/docs-sidenav/props.js | 176 +----- packages/docs-sidenav/style.module.css | 237 +++++++ packages/docs-sidenav/types.ts | 45 ++ .../utils/_wip-migrate-to-nav-json.js | 186 ++++++ .../utils/validate-file-paths/index.js | 58 ++ .../utils/validate-route-structure/index.js | 110 ++++ pages/global.css | 1 - 25 files changed, 1085 insertions(+), 935 deletions(-) delete mode 100644 packages/docs-sidenav/chevron-icon.js create mode 100644 packages/docs-sidenav/fixtures/navdata-example.json create mode 100644 packages/docs-sidenav/icons/bullet.svg create mode 100644 packages/docs-sidenav/icons/chevron.svg rename packages/docs-sidenav/{img/external.svg => icons/external-link.svg} (100%) rename packages/docs-sidenav/{menu-icon.js => icons/menu.svg} (94%) create mode 100644 packages/docs-sidenav/style.module.css create mode 100644 packages/docs-sidenav/types.ts create mode 100644 packages/docs-sidenav/utils/_wip-migrate-to-nav-json.js create mode 100644 packages/docs-sidenav/utils/validate-file-paths/index.js create mode 100644 packages/docs-sidenav/utils/validate-route-structure/index.js diff --git a/packages/docs-page/docs.mdx b/packages/docs-page/docs.mdx index 1ff5e3924..5ed0f23e5 100644 --- a/packages/docs-page/docs.mdx +++ b/packages/docs-page/docs.mdx @@ -39,27 +39,15 @@ We have a lot of docs sites, all of which render content in exactly the same way '
This is a cool docs page!
', scope: {}, }, - data: [ - { - __resourcePath: 'docs/test.mdx', - page_title: 'Testing Page', - sidebar_title: 'Testing Page', - }, - { - __resourcePath: 'docs/test2.mdx', - page_title: 'Other Testing Page', - sidebar_title: 'Other Testing Page', - }, - ], frontMatter: { page_title: 'Test Page', description: 'test description' }, - pagePath: '/docs/test', filePath: 'test.mdx', + currentPath: componentProps.currentPath.testValue, }, }} >{`GCP",
+ "path": "agent/autoauth/methods/gcp"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "title": "No Index Category",
+ "routes": [
+ {
+ "title": "Foo Item",
+ "path": "agent/no-index-test/foo"
+ }
+ ]
+ },
+ {
+ "title": "Only Index Test ENT",
+ "routes": [
+ {
+ "title": "Overview",
+ "path": "agent/only-index-test"
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/packages/docs-sidenav/icons/bullet.svg b/packages/docs-sidenav/icons/bullet.svg
new file mode 100644
index 000000000..6e68f3963
--- /dev/null
+++ b/packages/docs-sidenav/icons/bullet.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/docs-sidenav/icons/chevron.svg b/packages/docs-sidenav/icons/chevron.svg
new file mode 100644
index 000000000..7b0cfaaee
--- /dev/null
+++ b/packages/docs-sidenav/icons/chevron.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/docs-sidenav/img/external.svg b/packages/docs-sidenav/icons/external-link.svg
similarity index 100%
rename from packages/docs-sidenav/img/external.svg
rename to packages/docs-sidenav/icons/external-link.svg
diff --git a/packages/docs-sidenav/menu-icon.js b/packages/docs-sidenav/icons/menu.svg
similarity index 94%
rename from packages/docs-sidenav/menu-icon.js
rename to packages/docs-sidenav/icons/menu.svg
index a3b50f53b..c586219cc 100644
--- a/packages/docs-sidenav/menu-icon.js
+++ b/packages/docs-sidenav/icons/menu.svg
@@ -1,14 +1,8 @@
-import React from 'react'
-
-export default function MenuIcon() {
- return (
-
\ No newline at end of file
diff --git a/packages/docs-sidenav/index.js b/packages/docs-sidenav/index.js
index 997179d4b..4cc6a96d6 100644
--- a/packages/docs-sidenav/index.js
+++ b/packages/docs-sidenav/index.js
@@ -1,478 +1,218 @@
-import React, { useState, useMemo } from 'react'
-import useProductMeta from '@hashicorp/nextjs-scripts/lib/providers/product-meta'
-import LinkWrap from '@hashicorp/react-link-wrap'
-import MenuIcon from './menu-icon'
-import ChevronIcon from './chevron-icon'
+import React, { useEffect, useState } from 'react'
+import Link from 'next/link'
+import InlineSvg from '@hashicorp/react-inline-svg'
+import svgMenuIcon from './icons/menu.svg?include'
+import svgChevron from './icons/chevron.svg?include'
+import svgBullet from './icons/bullet.svg?include'
+import svgExternalLink from './icons/external-link.svg?include'
import fuzzysearch from 'fuzzysearch'
+import s from './style.module.css'
export default function DocsSidenav({
- data,
- order,
- currentPage,
- category,
- Link,
+ currentPath,
+ rootPath,
product,
disableFilter = false,
+ navData,
}) {
const [open, setOpen] = useState(false)
const [filterInput, setFilterInput] = useState('')
- const { themeClass } = useProductMeta(product)
-
- // we memoize here as the page matching is a pure, expensive calculation that
- // does not need to re-run every render
- const allContent = useMemo(
- () => matchOrderToData(currentPage, order, calculatePath(data, category)),
- [order, data, category, currentPage]
- )
- const [content, setContent] = useState(allContent)
-
- // remove leading slash and base level "docs"/"api"/etc
- const currentPath = currentPage
- .split('/')
- .slice(1 + category.split('/').length)
+ const [content, setContent] = useState(navData)
+ const [filteredContent, setFilteredContent] = useState(navData)
+
+ // When currentPath changes, update content
+ // to ensure `__isActive` props on each content item
+ // are accurate and up-to-date
+ // (Note: we could also reset filter input here,
+ // if we don't want to filter input to persist
+ // across client-side navigation, with something like:
+ // setFilterInput("")
+ useEffect(() => {
+ if (!navData) return
+ setContent(addIsActiveToNodes(navData, currentPath))
+ }, [currentPath, navData])
+
+ // When filter input changes, update content
+ // to filter out items that don't match
+ useEffect(() => {
+ setFilteredContent(filterContent(content, filterInput))
+ }, [filterInput, content])
return (
- GCP',
- },
- {
- __resourcePath: 'docs/agent/autoauth/methods/index.mdx',
- page_title: 'Vault Agent Auto-Auth Methods',
- sidebar_title: 'Methods',
- },
- {
- __resourcePath: 'docs/agent/autoauth/methods/aws.mdx',
- page_title: 'Vault Agent Auto-Auth AWS Method',
- sidebar_title: 'AWS',
- sidebar_current: 'docs-agent-autoauth-methods-aws',
- },
- {
- __resourcePath: 'docs/agent/autoauth/methods/kubernetes.mdx',
- page_title: 'Vault Agent Auto-Auth Kubernetes Method',
- sidebar_title: 'Kubernetes',
- },
- {
- __resourcePath: 'docs/agent/autoauth/methods/azure.mdx',
- page_title: 'Vault Agent Auto-Auth Azure Method',
- sidebar_title: 'Azure',
- },
- {
- __resourcePath: 'docs/agent/autoauth/methods/alicloud.mdx',
- page_title: 'Vault Agent Auto-Auth AliCloud Method',
- sidebar_title: 'AliCloud',
- },
- {
- __resourcePath: 'docs/agent/autoauth/methods/jwt.mdx',
- page_title: 'Vault Agent Auto-Auth JWT Method',
- sidebar_title: 'JWT',
- },
- {
- __resourcePath: 'docs/agent/autoauth/index.mdx',
- page_title: 'Vault Agent Auto-Auth',
- sidebar_title: 'Auto-Auth',
- },
- {
- __resourcePath: 'docs/agent/autoauth/sinks/file.mdx',
- page_title: 'Vault Agent Auto-Auth File Sink',
- sidebar_title: 'File',
- },
- {
- __resourcePath: 'docs/agent/autoauth/sinks/index.mdx',
- page_title: 'Vault Agent Auto-Auth Sinks',
- sidebar_title: 'Sinks',
- },
- {
- __resourcePath: 'docs/agent/index.mdx',
- page_title: 'Vault Agent',
- sidebar_title: 'Vault Agent',
- },
- {
- __resourcePath: 'docs/agent/test/index.mdx',
- page_title: 'Test Item',
- },
- {
- __resourcePath: 'docs/agent/no-index-test/foo.mdx',
- page_title: 'Foo Item',
- },
- {
- __resourcePath: 'docs/agent/autoauth/aws.mdx',
- page_title: 'AWS',
- },
- {
- __resourcePath: 'docs/agent/only-index-test/index.mdx',
- page_title: 'Only Index Test ENT',
- },
- {
- __resourcePath: 'docs/agent/only-index-no-content/index.mdx',
- page_title: 'Only Index No Content ENT',
- },
- ],
+ description: 'Tree of navigation data to render',
+ testValue: sampleNavData,
},
}
diff --git a/packages/docs-sidenav/style.module.css b/packages/docs-sidenav/style.module.css
new file mode 100644
index 000000000..081c6b8e1
--- /dev/null
+++ b/packages/docs-sidenav/style.module.css
@@ -0,0 +1,237 @@
+@custom-media --mobile-viewports (max-width: 939px);
+
+.root {
+ position: relative;
+
+ @media (--mobile-viewports) {
+ z-index: 901; /* higher than g-subnav */
+ }
+}
+
+.rootList {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ width: 275px;
+ padding-left: 5px;
+
+ @media (--mobile-viewports) {
+ background: var(--white);
+ bottom: 0;
+ right: 100%;
+ width: min(100%, 375px);
+ padding: 25px 32px 120px;
+ position: fixed;
+ overflow: auto;
+ transition: 0.3s ease-in-out;
+ transition-property: box-shadow, transform;
+ top: 0;
+
+ &[data-open='true'] {
+ box-shadow: 2px 2px 20px rgba(37, 38, 45, 0.2);
+ transform: translateX(100%);
+ padding-left: 25px;
+ }
+ }
+}
+
+.mobileClose {
+ display: none;
+
+ @media (--mobile-viewports) {
+ display: block;
+ position: absolute;
+ top: 18px;
+ right: 13px;
+ font-size: 1.7em;
+ padding: 0 14px;
+ cursor: pointer;
+ color: #333;
+ transition: opacity 0.3s ease;
+ z-index: 100;
+
+ &:hover {
+ opacity: 0.7;
+ }
+ }
+}
+
+.toggle {
+ align-items: center;
+ background: var(--white);
+ bottom: 0;
+ border-top: 1px solid var(--gray-6);
+ border-bottom: 1px solid var(--gray-6);
+ cursor: pointer;
+ display: none;
+ justify-content: center;
+ left: 0;
+ padding: 12px;
+ transition-delay: 0.3s; /* waits for menu to close before adjusting z-index */
+ width: 100%;
+ z-index: 74; /* less than product-subnav */
+
+ @media (--mobile-viewports) {
+ display: flex;
+ }
+
+ & span {
+ align-items: center;
+ display: flex;
+ justify-content: center;
+ }
+
+ & svg {
+ display: block;
+ margin-right: 12px;
+ }
+}
+
+.filterInput {
+ color: var(--gray-2);
+ border: 1px solid var(--gray-6);
+ background: var(--white);
+ border-radius: 2px;
+ padding: 8px;
+ width: 100%;
+ margin-bottom: 10px;
+ max-width: 90%;
+ font-family: inherit;
+ font-size: inherit;
+
+ &[data-has-error='true'] {
+ border-color: var(--danger);
+ }
+}
+
+.navItem {
+ color: var(--DEPRECATED-gray-4);
+ cursor: pointer;
+ display: flex;
+ padding: 7px 0;
+ transition: color 0.2s ease;
+ align-items: center;
+ background: none;
+ border: none;
+ font-size: inherit;
+ font-family: inherit;
+ line-height: inherit;
+
+ &:hover {
+ color: var(--DEPRECATED-gray-2);
+ }
+
+ &[data-is-active='true'] {
+ color: var(--brand);
+
+ &:hover {
+ color: var(--brand);
+ }
+ }
+
+ & code {
+ margin-left: 2px;
+ font-size: 0.875rem;
+
+ /* Alt design option: with background, as inline
+ color: var(--code-dark);
+ background: var(--code-light-transparent);
+ padding: 0.3em 0.625em;
+ border-radius: 3px; */
+ }
+}
+
+.navItemIcon {
+ position: relative;
+ left: -8.5px;
+ top: 1px;
+ width: 16px;
+ height: 16px;
+ border-radius: 8px;
+ background: white;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ & svg {
+ display: block;
+ & [stroke] {
+ stroke: var(--gray-4);
+ }
+ & [fill] {
+ fill: var(--gray-4);
+ }
+ }
+
+ &[data-is-active='true'] {
+ & svg {
+ & [stroke] {
+ stroke: var(--brand);
+ }
+ & [fill] {
+ fill: var(--brand);
+ }
+ }
+ }
+}
+
+.navLeafIcon {
+ composes: navItemIcon;
+ opacity: 0;
+
+ &[data-is-active='true'] {
+ opacity: 1;
+ }
+}
+
+.navCategoryIcon {
+ composes: navItemIcon;
+ transition: transform 0.15s ease;
+
+ &[data-is-open='true'] {
+ transform: rotate(90deg);
+ }
+}
+
+.navCategorySubnav {
+ display: none;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+
+ &[data-is-open='true'] {
+ display: block;
+ }
+
+ & > li {
+ margin-left: 21.5px;
+ border-left: 1px solid var(--DEPRECATED-gray-9);
+ }
+
+ & > hr {
+ border-left: 1px solid var(--DEPRECATED-gray-9);
+ margin-left: 21.5px;
+ }
+}
+
+.externalLinkIcon {
+ display: block;
+ margin-left: 8px;
+}
+
+.divider {
+ background: none;
+ padding: 8px 0;
+ margin: 0;
+
+ &::after {
+ content: '';
+ border-bottom: 1px solid var(--DEPRECATED-gray-9);
+ display: block;
+ width: 90%;
+
+ @media (--mobile-viewports) {
+ width: 100%;
+ }
+ }
+}
diff --git a/packages/docs-sidenav/types.ts b/packages/docs-sidenav/types.ts
new file mode 100644
index 000000000..9628c167a
--- /dev/null
+++ b/packages/docs-sidenav/types.ts
@@ -0,0 +1,45 @@
+// navData is an array of NavNodes
+declare const navData: NavNode[]
+
+// A NavNode can be any of these types
+type NavNode = NavLeaf | NavDirectLink | NavDivider | NavBranch
+
+// A NavLeaf represents a page with content.
+//
+// The "path" refers to the URL route from the content subpath.
+// For all current docs sites, this "path" also
+// corresponds to the content location in the filesystem.
+//
+// Note that "path" can refer to either "named" or "index" files.
+// For example, we will automatically resolve the path
+// "commands" to either "commands.mdx" or "commands/index.mdx".
+// If both exist, we will throw an error to alert authors
+// to the ambiguity.
+interface NavLeaf {
+ title: string
+ path: string
+}
+
+// A NavDirectLink allows linking outside the content subpath.
+//
+// This includes links on the same domain,
+// for example, where the content subpath is `/docs`,
+// one can create a direct link with href `/use-cases`.
+//
+// This also allows for linking to external URLs,
+// for example, one could link to `https://hashiconf.com/`.
+interface NavDirectLink {
+ title: string
+ href: string
+}
+
+// A NavDivider represents a divider line
+interface NavDivider {
+ divider: true
+}
+
+// A NavBranch represents nested navigation data.
+interface NavBranch {
+ title: string
+ routes: NavNode[]
+}
diff --git a/packages/docs-sidenav/utils/_wip-migrate-to-nav-json.js b/packages/docs-sidenav/utils/_wip-migrate-to-nav-json.js
new file mode 100644
index 000000000..d0773ce67
--- /dev/null
+++ b/packages/docs-sidenav/utils/_wip-migrate-to-nav-json.js
@@ -0,0 +1,186 @@
+const fs = require('fs')
+const path = require('path')
+const grayMatter = require('gray-matter')
+const klawSync = require('klaw-sync')
+const navigationJs = require('../../website/data/docs-navigation')
+// Temp - setting this up as part of a GitHub action, just to make
+// things easier during dev, really the JSON format would be
+// updated itself and the old format would be dropped
+const util = require('util')
+const exec = util.promisify(require('child_process').exec)
+
+const INPUT_DIR = 'website/content'
+const OUTPUT_DIR = '.generated'
+const OUTPUT_FILE = 'docs-navigation.json'
+
+convertAndWrite().then(() => {
+ // Stage the changes so they're ready to commit
+ // (kind of relies on the outputDir, which is why it happens here)
+ const outputFile = path.join(process.cwd(), OUTPUT_DIR, OUTPUT_FILE)
+ exec(`git add ${outputFile}`).then(() => {
+ console.log('✅ Done')
+ })
+})
+
+async function convertAndWrite() {
+ const fileFilter = (f) => path.extname(f.path) === '.mdx'
+ const collectedFrontmatter = await collectFrontmatter(INPUT_DIR, fileFilter)
+ const navJson = convertNavTree(navigationJs, collectedFrontmatter, [], 'docs')
+ writeJsonFile(navJson, OUTPUT_DIR, OUTPUT_FILE)
+}
+
+function convertNavTree(navTree, collectedFrontmatter, pathStack, subfolder) {
+ const convertedTree = navTree.map((navNode) => {
+ // if the node is a string like '-----',
+ // we want to render a divider
+ const isString = typeof navNode === 'string'
+ const isDivider = isString && navNode.match(/^-+$/)
+ if (isDivider) return { divider: true }
+ // if the node is a string, but not a divider,
+ // we want to render a leaf node
+ if (isString)
+ return convertNavLeaf(navNode, collectedFrontmatter, pathStack, subfolder)
+ // if the node has an `href` or `title`, it's a direct link
+ if (navNode.href || navNode.title) {
+ // if a direct link doesn't have both `href` and `title`, we throw an error
+ throw new Error(
+ `Direct sidebar links must have both a "href" and "title". Found a direct link with only one of the two:\n\n ${JSON.stringify(
+ navNode
+ )}`
+ )
+ }
+ // Otherwise, we expect the node to be a nested category
+ return convertNavCategory(
+ navNode,
+ collectedFrontmatter,
+ pathStack,
+ subfolder
+ )
+ })
+ return convertedTree
+}
+
+function convertNavCategory(
+ navNode,
+ collectedFrontmatter,
+ pathStack,
+ subfolder
+) {
+ // Throw an error if the category is invalid
+ if (!navNode.category || !navNode.content) {
+ throw new Error(
+ `Nav category nodes must have either a .name or .category property.`
+ )
+ }
+ // Process the navNode's nested content into the new format
+ const nestedPathStack = pathStack.concat(navNode.category)
+ const nestedRoutes = convertNavTree(
+ navNode.content,
+ collectedFrontmatter,
+ nestedPathStack,
+ subfolder
+ )
+ // Then, handle index data, which is a bit more of an undertaking...
+ // First, we try to gather index data for the entry
+ // The path we want for our new format does NOT contain
+ // the content subfolder or the file extension (always `.mdx`)
+ const USE_EXPLICIT_INDEX = false
+ const pathParts = USE_EXPLICIT_INDEX
+ ? [navNode.category, 'index']
+ : [navNode.category]
+ const pathNewFormat = pathStack.concat(pathParts).join('/')
+ // The path we need to match from the older format
+ // includes the content subfolder as well as the file extension
+ const pathFromSubfolder = `${pathNewFormat}${
+ USE_EXPLICIT_INDEX ? '' : '/index'
+ }.mdx`
+ const pathToMatch = path.join(subfolder, pathFromSubfolder)
+ // Try to find the corresponding index page resource
+ const matchedFrontmatter = collectedFrontmatter.filter((resource) => {
+ return resource.__resourcePath === pathToMatch
+ })[0]
+ const fmTitle = matchedFrontmatter
+ ? matchedFrontmatter.sidebar_title || matchedFrontmatter.page_title
+ : false
+ if (!fmTitle && !navNode.name) {
+ throw new Error(
+ `Nav category nodes must have either an index file with a sidebar_title or page_title in the frontmatter, or a .name property.`
+ )
+ }
+ const title = fmTitle || navNode.name
+ // Set up an index page entry, if applicable
+ const indexRoute = matchedFrontmatter
+ ? {
+ title: 'Overview',
+ path: pathNewFormat,
+ }
+ : false
+
+ // Finally, construct and return the category node
+ const routes = indexRoute ? [indexRoute, ...nestedRoutes] : nestedRoutes
+ return {
+ title: formatTitle(title),
+ routes,
+ }
+}
+
+function convertNavLeaf(navNode, collectedFrontmatter, pathStack, subfolder) {
+ // The path we want for our new format does NOT contain
+ // the content subfolder or the file extension (always `.mdx`)
+ const pathNewFormat = pathStack.concat(navNode).join('/')
+ // The path we need to match from the older format
+ // includes the content subfolder as well as the file extension
+ const pathToMatch = path.join(subfolder, `${pathNewFormat}.mdx`)
+ // We filter for matching frontmatter to get the "title" for the nav leaf.
+ // We throw an error if there is no matching resource - there should be!
+ const matchedFrontmatter = collectedFrontmatter.filter((resource) => {
+ return resource.__resourcePath === pathToMatch
+ })[0]
+ if (!matchedFrontmatter) {
+ throw new Error(
+ `Could not find frontmatter for resource path ${pathToMatch}.`
+ )
+ }
+ // We pull the title from frontmatter.
+ // We throw an error if there is no title in frontmatter - there should be!
+ const { sidebar_title, page_title } = matchedFrontmatter
+ const title = sidebar_title || page_title
+ if (!title) {
+ throw new Error(`Could not find title in frontmatter of ${pathToMatch}.`)
+ }
+ // Return the new format for the nav leaf
+ return { title: formatTitle(title), path: pathNewFormat }
+}
+
+async function collectFrontmatter(inputDir, fileFilter) {
+ // Traverse directories and parse frontmatter
+ const targetFilepaths = klawSync(inputDir, {
+ traverseAll: true,
+ filter: fileFilter,
+ }).map((f) => f.path)
+ const inputDirPath = path.join(process.cwd(), inputDir)
+ const collectedFrontmatter = await Promise.all(
+ targetFilepaths.map(async (filePath) => {
+ const rawFile = fs.readFileSync(filePath, 'utf-8')
+ const { data: frontmatter } = grayMatter(rawFile)
+ const __resourcePath = path.relative(inputDirPath, filePath)
+ return { __resourcePath, ...frontmatter }
+ })
+ )
+ return collectedFrontmatter
+}
+
+function formatTitle(title) {
+ return title.replace(//g, '').replace(/<\/tt>/g, '')
+}
+
+function writeJsonFile(data, dir, file) {
+ // Set up a directory for output
+ const outputDir = path.join(process.cwd(), dir)
+ const outputFile = path.join(outputDir, file)
+ fs.mkdirSync(outputDir, { recursive: true })
+ // Stringify the collected frontmatter, and write the file
+ const fileString = JSON.stringify(data, null, 2)
+ fs.writeFileSync(outputFile, fileString)
+ return true
+}
diff --git a/packages/docs-sidenav/utils/validate-file-paths/index.js b/packages/docs-sidenav/utils/validate-file-paths/index.js
new file mode 100644
index 000000000..059e38ad9
--- /dev/null
+++ b/packages/docs-sidenav/utils/validate-file-paths/index.js
@@ -0,0 +1,58 @@
+const fs = require('fs')
+const path = require('path')
+
+async function validateFilePaths(navNodes, localDir) {
+ // Clone the nodes, and validate each one
+ return await Promise.all(
+ navNodes.slice(0).map(async (navNode) => {
+ return await validateNode(navNode, localDir)
+ })
+ )
+}
+
+async function validateNode(navNode, localDir) {
+ // Handle leaf nodes
+ if (navNode.path) {
+ // Ignore remote leaf nodes, these already
+ // have their content file explicitly defined
+ // TODO is there a way we can avoid remoteFile nodes, without making this implementation have to know about them?
+ if (navNode.remoteFile) return navNode
+ // Handle local leaf nodes
+ const indexFilePath = path.join(navNode.path, 'index.mdx')
+ const namedFilePath = `${navNode.path}.mdx`
+ const hasIndexFile = fs.existsSync(
+ path.join(process.cwd(), localDir, indexFilePath)
+ )
+ const hasNamedFile = fs.existsSync(
+ path.join(process.cwd(), localDir, namedFilePath)
+ )
+ if (!hasIndexFile && !hasNamedFile) {
+ console.log(JSON.stringify(navNode, null, 2))
+ throw new Error(
+ `Could not find file to match path "${navNode.path}". Neither "${namedFilePath}" or "${indexFilePath}" could be found.`
+ )
+ }
+ if (hasIndexFile && hasNamedFile) {
+ console.warn(
+ `Ambiguous path "${navNode.path}". Both "${namedFilePath}" and "${indexFilePath}" exist.`
+ )
+ }
+ const filePath = path.join(
+ localDir,
+ hasIndexFile ? indexFilePath : namedFilePath
+ )
+ return { ...navNode, filePath }
+ }
+ // Handle local branch nodes
+ if (navNode.routes) {
+ const routesWithFilePaths = await validateFilePaths(
+ navNode.routes,
+ localDir
+ )
+ return { ...navNode, routes: routesWithFilePaths }
+ }
+ // Return all other node types unmodified
+ return navNode
+}
+
+module.exports = validateFilePaths
diff --git a/packages/docs-sidenav/utils/validate-route-structure/index.js b/packages/docs-sidenav/utils/validate-route-structure/index.js
new file mode 100644
index 000000000..af23f6bb9
--- /dev/null
+++ b/packages/docs-sidenav/utils/validate-route-structure/index.js
@@ -0,0 +1,110 @@
+function validateRouteStructure(navData) {
+ return validateBranchRoutes(navData)[1]
+}
+
+function validateBranchRoutes(navNodes, depth = 0) {
+ // In order to be a valid branch, there needs to be at least one navNode.
+ if (navNodes.length === 0) {
+ throw new Error(`Found empty array of navNodes. Depth: ${depth}`)
+ }
+ // Augment each navNode with its path __stack
+ const navNodesWithStacks = navNodes.map((navNode) => {
+ // Handle leaf nodes - split their paths into a __stack
+ if (navNode.path) return { ...navNode, __stack: navNode.path.split('/') }
+ // Handle branch nodes - we recurse depth-first here
+ if (navNode.routes) return handleBranchNode(navNode, depth)
+ // Other nodes aren't relevant, we don't touch them
+ return navNode
+ })
+ // Gather all the path stacks at this level
+ const routeStacks = navNodesWithStacks.reduce((acc, navNode) => {
+ // Ignore nodes that don't have a path stack
+ if (!navNode.__stack) return acc
+ // For other nodes, add their stacks
+ return acc.concat([navNode.__stack])
+ }, [])
+ // Ensure that there are no duplicate routes
+ // (for example, a nested route with a particular path,
+ // and a named page at the same level with the same path)
+ const routePaths = routeStacks.map((s) => s.join('/'))
+ const duplicateRoutes = routePaths.filter((value, index, self) => {
+ return self.indexOf(value) !== index
+ })
+ if (duplicateRoutes.length > 0) {
+ throw new Error(
+ `Duplicate routes found:\n\n${JSON.stringify(duplicateRoutes)}\n`
+ )
+ }
+ // Gather an array of all resolved paths at this level
+ const parentRoutes = routeStacks.map((stack) => {
+ // Index leaf nodes will have the same
+ // number of path parts as the current nesting depth.
+ const isIndexNode = stack.length === depth
+ if (isIndexNode) {
+ // The "dirPath" for index nodes is
+ // just the original path
+ return stack.join('/')
+ }
+ // Named leaf nodes, and nested routes,
+ // will have one more path part than the current nesting depth.
+ const isNamedNode = stack.length === depth + 1
+ if (isNamedNode) {
+ // The "dirPath" for named nodes is
+ // the original path with the last part dropped.
+ return stack.slice(0, stack.length - 1).join('/')
+ }
+ // If we have any other number of parts in the
+ // leaf node's path, then it is invalid.
+ throw new Error(
+ `Invalid path depth. At depth ${depth}, found path "${stack.join('/')}"`
+ )
+ })
+ // We expect all routes at any level to share the same parent directory.
+ // In other words, we expect there to be exactly one unique "dirPath"
+ // shared across all the routes at this level.
+ const uniqueParents = parentRoutes.filter((value, index, self) => {
+ return self.indexOf(value) === index
+ })
+ // We throw an error if we find mismatched paths
+ // that don't share the same parent path.
+ if (uniqueParents.length > 1) {
+ throw new Error(`Found mismatched paths: ${JSON.stringify(uniqueParents)}`)
+ }
+ const path = uniqueParents[0]
+ // Finally, we return
+ return [path, navNodesWithStacks]
+}
+
+function handleBranchNode(navNode, depth) {
+ // We recurse depth-first here, and we'll throw an error
+ // if the nested routes had structural issues
+ const [path, routesWithStacks] = validateBranchRoutes(
+ navNode.routes,
+ depth + 1
+ )
+ const __stack = path.split('/')
+ return { ...navNode, __stack, routes: routesWithStacks }
+}
+
+module.exports = validateRouteStructure
+
+//
+//
+//
+//
+//
+//
+//
+//
+
+// TODO - throw error if any non-divider node does not have a title
+// function collectEmptyTitleErrors(navData) {
+// const errors = [];
+// return errors;
+// }
+
+// TODO - throw error if direct link nodes don't have both { title, href }
+// function collectDirectLinkErrors(navData) {
+// const errors = [];
+// return errors;
+// }
diff --git a/pages/global.css b/pages/global.css
index 9ee5aa1d7..73d718068 100644
--- a/pages/global.css
+++ b/pages/global.css
@@ -11,7 +11,6 @@
@import '../packages/button/styles/index.css';
@import '../packages/callouts/styles/index.css';
@import '../packages/checkbox-input/style.css';
-@import '../packages/docs-sidenav/style.css';
@import '../packages/accordion/style.css';
@import '../packages/subnav/style.css';
@import '../packages/call-to-action/style.css';
From 8bc32139fc5e60ab6af6756f64abe363eea74bae Mon Sep 17 00:00:00 2001
From: Zach Shilton <4624598+zchsh@users.noreply.github.com>
Date: Thu, 25 Feb 2021 10:11:44 -0500
Subject: [PATCH 02/62] Reorder props for clearer diff
---
packages/docs-page/index.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/docs-page/index.js b/packages/docs-page/index.js
index 986100c51..67392199f 100644
--- a/packages/docs-page/index.js
+++ b/packages/docs-page/index.js
@@ -15,12 +15,12 @@ export function DocsPageWrapper({
description,
filePath,
mainBranch = 'main',
+ navData,
currentPath,
pageTitle,
product: { name, slug },
showEditPage = true,
subpath,
- navData,
}) {
// TEMPORARY (https://app.asana.com/0/1100423001970639/1160656182754009)
// activates the "jump to section" feature
@@ -99,11 +99,11 @@ export default function DocsPage({
return (
').replace(/<\/tt>/g, '')
}
-
-function writeJsonFile(data, dir, file) {
- // Set up a directory for output
- const outputDir = path.join(process.cwd(), dir)
- const outputFile = path.join(outputDir, file)
- fs.mkdirSync(outputDir, { recursive: true })
- // Stringify the collected frontmatter, and write the file
- const fileString = JSON.stringify(data, null, 2)
- fs.writeFileSync(outputFile, fileString)
- return true
-}
diff --git a/packages/docs-sidenav/bin/npx-hello-world.js b/packages/docs-sidenav/bin/npx-hello-world.js
new file mode 100644
index 000000000..593097b40
--- /dev/null
+++ b/packages/docs-sidenav/bin/npx-hello-world.js
@@ -0,0 +1,3 @@
+#! /usr/bin/env node
+console.log(process.argv)
+console.log('Hello world, executing from react-docs-subnav!')
diff --git a/packages/docs-sidenav/package-lock.json b/packages/docs-sidenav/package-lock.json
index 935a9a04b..8cff3fe71 100644
--- a/packages/docs-sidenav/package-lock.json
+++ b/packages/docs-sidenav/package-lock.json
@@ -4,10 +4,105 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
"fuzzysearch": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/fuzzysearch/-/fuzzysearch-1.0.3.tgz",
"integrity": "sha1-3/yA9tawQiPyImqnndGUIxCW0Ag="
+ },
+ "graceful-fs": {
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
+ "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
+ "dev": true
+ },
+ "gray-matter": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.2.tgz",
+ "integrity": "sha512-7hB/+LxrOjq/dd8APlK0r24uL/67w7SkYnfwhNFwg/VDIGWGmduTDYf3WNstLW2fbbmRwrDGCVSJ2isuf2+4Hw==",
+ "dev": true,
+ "requires": {
+ "js-yaml": "^3.11.0",
+ "kind-of": "^6.0.2",
+ "section-matter": "^1.0.0",
+ "strip-bom-string": "^1.0.0"
+ }
+ },
+ "is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true
+ },
+ "klaw-sync": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
+ "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.11"
+ }
+ },
+ "section-matter": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
+ "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "kind-of": "^6.0.0"
+ }
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "strip-bom-string": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
+ "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=",
+ "dev": true
}
}
}
diff --git a/packages/docs-sidenav/package.json b/packages/docs-sidenav/package.json
index eefcda6f9..ed98d6384 100644
--- a/packages/docs-sidenav/package.json
+++ b/packages/docs-sidenav/package.json
@@ -3,6 +3,10 @@
"description": "Sidebar used for navigation HashiCorp docs",
"version": "7.0.0",
"author": "HashiCorp",
+ "bin": {
+ "npx-hello-world": "bin/npx-hello-world.js",
+ "migrate-nav-data": "bin/migrate-nav-data.js"
+ },
"contributors": [
"Jeff Escalante"
],
@@ -17,5 +21,9 @@
"publishConfig": {
"access": "public"
},
- "gitHead": "c47c50a8419f6278073505ebbe25c9fbe4faab07"
+ "gitHead": "c47c50a8419f6278073505ebbe25c9fbe4faab07",
+ "devDependencies": {
+ "gray-matter": "^4.0.2",
+ "klaw-sync": "^6.0.0"
+ }
}
From 644398e8274b37e73abb4dc956a7743df1a9bdbf Mon Sep 17 00:00:00 2001
From: Zach Shilton <4624598+zchsh@users.noreply.github.com>
Date: Thu, 25 Feb 2021 13:26:18 -0500
Subject: [PATCH 05/62] Remove devdeps, not useful
---
packages/docs-sidenav/bin/migrate-nav-data.js | 11 +++
packages/docs-sidenav/package-lock.json | 95 -------------------
packages/docs-sidenav/package.json | 6 +-
3 files changed, 12 insertions(+), 100 deletions(-)
diff --git a/packages/docs-sidenav/bin/migrate-nav-data.js b/packages/docs-sidenav/bin/migrate-nav-data.js
index dad42acb2..dbf377c18 100644
--- a/packages/docs-sidenav/bin/migrate-nav-data.js
+++ b/packages/docs-sidenav/bin/migrate-nav-data.js
@@ -4,6 +4,17 @@ const path = require('path')
const grayMatter = require('gray-matter')
const klawSync = require('klaw-sync')
+/*
+USAGE
+
+Eg:
+- old nav data is in ./data/docs-navigation.js
+- content is in ./content/docs
+- output file is ./data/docs-nav-data.json
+
+npx --package gray-matter --package klaw-sync --package @hashicorp/react-docs-sidenav@6.1.1-alpha.7 migrate-nav-data ./data/docs-navigation.js ./content/docs ./data/docs-nav-data.json
+*/
+
const navigationJsData = require(path.resolve(process.argv[2]))
const CONTENT_DIR = path.join(process.cwd(), process.argv[3])
const OUT_FILE = path.join(process.cwd(), process.argv[4])
diff --git a/packages/docs-sidenav/package-lock.json b/packages/docs-sidenav/package-lock.json
index 8cff3fe71..935a9a04b 100644
--- a/packages/docs-sidenav/package-lock.json
+++ b/packages/docs-sidenav/package-lock.json
@@ -4,105 +4,10 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
- "argparse": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
- "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
- "dev": true,
- "requires": {
- "sprintf-js": "~1.0.2"
- }
- },
- "esprima": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
- "dev": true
- },
- "extend-shallow": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
- "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
- "dev": true,
- "requires": {
- "is-extendable": "^0.1.0"
- }
- },
"fuzzysearch": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/fuzzysearch/-/fuzzysearch-1.0.3.tgz",
"integrity": "sha1-3/yA9tawQiPyImqnndGUIxCW0Ag="
- },
- "graceful-fs": {
- "version": "4.2.6",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
- "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
- "dev": true
- },
- "gray-matter": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.2.tgz",
- "integrity": "sha512-7hB/+LxrOjq/dd8APlK0r24uL/67w7SkYnfwhNFwg/VDIGWGmduTDYf3WNstLW2fbbmRwrDGCVSJ2isuf2+4Hw==",
- "dev": true,
- "requires": {
- "js-yaml": "^3.11.0",
- "kind-of": "^6.0.2",
- "section-matter": "^1.0.0",
- "strip-bom-string": "^1.0.0"
- }
- },
- "is-extendable": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
- "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
- "dev": true
- },
- "js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
- "dev": true,
- "requires": {
- "argparse": "^1.0.7",
- "esprima": "^4.0.0"
- }
- },
- "kind-of": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
- "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
- "dev": true
- },
- "klaw-sync": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
- "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.11"
- }
- },
- "section-matter": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
- "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
- "dev": true,
- "requires": {
- "extend-shallow": "^2.0.1",
- "kind-of": "^6.0.0"
- }
- },
- "sprintf-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
- "dev": true
- },
- "strip-bom-string": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
- "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=",
- "dev": true
}
}
}
diff --git a/packages/docs-sidenav/package.json b/packages/docs-sidenav/package.json
index ed98d6384..135e11a16 100644
--- a/packages/docs-sidenav/package.json
+++ b/packages/docs-sidenav/package.json
@@ -21,9 +21,5 @@
"publishConfig": {
"access": "public"
},
- "gitHead": "c47c50a8419f6278073505ebbe25c9fbe4faab07",
- "devDependencies": {
- "gray-matter": "^4.0.2",
- "klaw-sync": "^6.0.0"
- }
+ "gitHead": "c47c50a8419f6278073505ebbe25c9fbe4faab07"
}
From 8620fb583f1908c4eaf881948e9059b1648c6010 Mon Sep 17 00:00:00 2001
From: Zach Shilton <4624598+zchsh@users.noreply.github.com>
Date: Thu, 25 Feb 2021 13:27:34 -0500
Subject: [PATCH 06/62] Update comment
---
packages/docs-sidenav/bin/migrate-nav-data.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/docs-sidenav/bin/migrate-nav-data.js b/packages/docs-sidenav/bin/migrate-nav-data.js
index dbf377c18..9e5ddeb3f 100644
--- a/packages/docs-sidenav/bin/migrate-nav-data.js
+++ b/packages/docs-sidenav/bin/migrate-nav-data.js
@@ -12,7 +12,7 @@ Eg:
- content is in ./content/docs
- output file is ./data/docs-nav-data.json
-npx --package gray-matter --package klaw-sync --package @hashicorp/react-docs-sidenav@6.1.1-alpha.7 migrate-nav-data ./data/docs-navigation.js ./content/docs ./data/docs-nav-data.json
+npx --package gray-matter --package klaw-sync --package @hashicorp/react-docs-sidenav@6.1.1-alpha.8 migrate-nav-data ./data/docs-navigation.js ./content/docs ./data/docs-nav-data.json
*/
const navigationJsData = require(path.resolve(process.argv[2]))
From 87bc56cf9aa3042fe7b7477f60a75d8209a1243e Mon Sep 17 00:00:00 2001
From: Zach Shilton <4624598+zchsh@users.noreply.github.com>
Date: Thu, 25 Feb 2021 15:47:49 -0500
Subject: [PATCH 07/62] Fix issue with direct links
---
packages/docs-sidenav/bin/migrate-nav-data.js | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/packages/docs-sidenav/bin/migrate-nav-data.js b/packages/docs-sidenav/bin/migrate-nav-data.js
index 9e5ddeb3f..0255bebe9 100644
--- a/packages/docs-sidenav/bin/migrate-nav-data.js
+++ b/packages/docs-sidenav/bin/migrate-nav-data.js
@@ -47,12 +47,15 @@ function convertNavTree(navTree, collectedFrontmatter, pathStack, subfolder) {
return convertNavLeaf(navNode, collectedFrontmatter, pathStack, subfolder)
// if the node has an `href` or `title`, it's a direct link
if (navNode.href || navNode.title) {
- // if a direct link doesn't have both `href` and `title`, we throw an error
- throw new Error(
- `Direct sidebar links must have both a "href" and "title". Found a direct link with only one of the two:\n\n ${JSON.stringify(
- navNode
- )}`
- )
+ if (!navNode.href || !navNode.title) {
+ // if a direct link doesn't have both `href` and `title`, we throw an error
+ throw new Error(
+ `Direct sidebar links must have both a "href" and "title". Found a direct link with only one of the two:\n\n ${JSON.stringify(
+ navNode
+ )}`
+ )
+ }
+ return { title: navNode.title, href: navNode.href }
}
// Otherwise, we expect the node to be a nested category
return convertNavCategory(
From a6ca716937356007bfc839720959ee7be91532de Mon Sep 17 00:00:00 2001
From: Zach Shilton <4624598+zchsh@users.noreply.github.com>
Date: Thu, 25 Feb 2021 16:31:59 -0500
Subject: [PATCH 08/62] Refine DocsPage props API
---
packages/docs-page/index.js | 33 ++++++++++++++-----
packages/docs-page/package-lock.json | 6 ++--
packages/docs-page/package.json | 2 +-
packages/docs-sidenav/bin/migrate-nav-data.js | 2 +-
4 files changed, 29 insertions(+), 14 deletions(-)
diff --git a/packages/docs-page/index.js b/packages/docs-page/index.js
index 67392199f..9fa2ff3cb 100644
--- a/packages/docs-page/index.js
+++ b/packages/docs-page/index.js
@@ -13,8 +13,7 @@ export function DocsPageWrapper({
canonicalUrl,
children,
description,
- filePath,
- mainBranch = 'main',
+ editLink,
navData,
currentPath,
pageTitle,
@@ -71,9 +70,7 @@ export function DocsPageWrapper({
{/* if desired, show an "edit this page" link on the bottom right, linking to github */}
{showEditPage && (
This is a cool docs page!
', scope: {}, }, - frontMatter: { page_title: 'Test Page', description: 'test description' }, - filePath: 'test.mdx', - currentPath: componentProps.currentPath.testValue, + navData: componentProps.navData.testValue, }, }} >{`This is a cool docs page!
', scope: {}, }, - navData: componentProps.navData.testValue, + navData: componentProps.staticProps.properties.navData.testValue, + currentPath: componentProps.staticProps.properties.currentPath.testValue, + frontMatter: { page_title: 'Test Page', description: 'test description' }, }, }} >{`hello world
}`', + 'The default branch of the project being documented, typically either "master" or "main". Used for the `Edit this page` link.', + default: 'main', }, showEditPage: { type: 'boolean', description: - 'if true, an "edit this page" link will appear on the bottom right', + 'If `true`, an `Edit this page` link will appear on the bottom right of each page.', default: true, }, - mainBranch: { - type: 'string', + additionalComponents: { + type: 'object', description: - 'The default branch of the project being documented, typically either "master" or "main". Used for the `showEditPage` prop', - default: 'main', + 'Object containing additional components to be made available within mdx pages. Uses the format `{ [key]: Component }`, for example, `{ TestComponent: () =>hello world
}`', }, staticProps: { type: 'object', description: 'Directly pass the return value of `server/generateStaticProps` in here.', + properties: { + mdxSource: { + type: 'object', + description: + "Data returned from running `next-mdx-remote/render-to-string` on the page's isolated `.mdx` file contents.", + }, + frontmatter: { + type: 'object', + description: "Frontmatter object parsed from the page's `.mdx` file.", + }, + currentPath: docsSidenavProps.currentPath, + navData: docsSidenavProps.navData, + }, }, } diff --git a/packages/docs-sidenav/fixtures/navdata-example.json b/packages/docs-sidenav/fixtures/nav-data.json similarity index 100% rename from packages/docs-sidenav/fixtures/navdata-example.json rename to packages/docs-sidenav/fixtures/nav-data.json diff --git a/packages/docs-sidenav/props.js b/packages/docs-sidenav/props.js index 8acb10a0a..4866a9e48 100644 --- a/packages/docs-sidenav/props.js +++ b/packages/docs-sidenav/props.js @@ -1,22 +1,8 @@ -const sampleNavData = require('./fixtures/navdata-example.json') - -const productSlugs = [ - 'nomad', - 'consul', - 'terraform', - 'packer', - 'vagrant', - 'boundary', - 'waypoint', -] +const sampleNavData = require('./fixtures/nav-data.json') +const sharedProps = require('../../props') module.exports = { - product: { - type: 'string', - description: 'Slug of the current product for color theming', - testValue: 'terraform', - options: productSlugs, - }, + product: sharedProps.product, currentPath: { type: 'string', description: @@ -25,7 +11,8 @@ module.exports = { }, baseRoute: { type: 'string', - description: 'Top level navigation route, for example `docs`, `api`, etc.', + description: + 'Top level navigation route, for example `docs`, `api-docs`, etc.', testValue: 'docs', }, disableFilter: { @@ -35,7 +22,8 @@ module.exports = { }, navData: { type: 'object', - description: 'Tree of navigation data to render', + description: + 'Tree of navigation data to render. See `docs-sidenav/types.js` for details.', testValue: sampleNavData, }, } diff --git a/packages/docs-sidenav/style.module.css b/packages/docs-sidenav/style.module.css index b1679872b..ae79220fe 100644 --- a/packages/docs-sidenav/style.module.css +++ b/packages/docs-sidenav/style.module.css @@ -132,12 +132,6 @@ & code { margin-left: 2px; font-size: 0.875rem; - - /* Alt design option: with background, as inline - color: var(--code-dark); - background: var(--code-light-transparent); - padding: 0.3em 0.625em; - border-radius: 3px; */ } } diff --git a/packages/docs-sidenav/types.ts b/packages/docs-sidenav/types.ts index 9628c167a..57cdad876 100644 --- a/packages/docs-sidenav/types.ts +++ b/packages/docs-sidenav/types.ts @@ -1,8 +1,8 @@ -// navData is an array of NavNodes -declare const navData: NavNode[] +// NavData is an array of NavNodes +export type NavData = NavNode[] // A NavNode can be any of these types -type NavNode = NavLeaf | NavDirectLink | NavDivider | NavBranch +export type NavNode = NavLeaf | NavDirectLink | NavDivider | NavBranch // A NavLeaf represents a page with content. // From 8b3f2f36075fdf93be85648db80746b2495eca9d Mon Sep 17 00:00:00 2001 From: Zach Shilton <4624598+zchsh@users.noreply.github.com> Date: Thu, 4 Mar 2021 13:16:45 -0500 Subject: [PATCH 24/62] Nit to cleanup diff --- packages/docs-page/docs.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/docs-page/docs.mdx b/packages/docs-page/docs.mdx index d4d9c0c52..4839a25ae 100644 --- a/packages/docs-page/docs.mdx +++ b/packages/docs-page/docs.mdx @@ -47,8 +47,7 @@ We have a lot of docs sites, all of which render content in exactly the same way >{`GCP",
"path": "agent/autoauth/methods/gcp"
+ },
+ { "divider": true },
+ {
+ "title": "External Link",
+ "href": "https://google.com"
+ },
+ {
+ "title": "Internal Direct Link",
+ "href": "/some-non-docs-path"
}
]
}
diff --git a/packages/docs-sidenav/index.js b/packages/docs-sidenav/index.js
index 325d389b3..1d484fcac 100644
--- a/packages/docs-sidenav/index.js
+++ b/packages/docs-sidenav/index.js
@@ -1,6 +1,8 @@
import React, { useEffect, useState } from 'react'
import useProductMeta from '@hashicorp/nextjs-scripts/lib/providers/product-meta'
import Link from 'next/link'
+import { useRouter } from 'next/router'
+import LinkWrap, { isAbsoluteURL } from '@hashicorp/react-link-wrap'
import InlineSvg from '@hashicorp/react-inline-svg'
import svgMenuIcon from './icons/menu.svg?include'
import svgChevron from './icons/chevron.svg?include'
@@ -16,23 +18,23 @@ export default function DocsSidenav({
navData,
disableFilter = false,
}) {
+ const router = useRouter()
+ const pathname = router ? router.pathname : null
const [open, setOpen] = useState(false)
const [filterInput, setFilterInput] = useState('')
- const { themeClass } = useProductMeta(product)
const [content, setContent] = useState(navData)
const [filteredContent, setFilteredContent] = useState(navData)
+ const { themeClass } = useProductMeta(product)
- // When currentPath changes, update content
- // to ensure `__isActive` props on each content item
- // are accurate and up-to-date
- // (Note: we could also reset filter input here,
- // if we don't want to filter input to persist
- // across client-side navigation, with something like:
+ // When currentPath changes, update content to ensure
+ // `__isActive` props on each content item are up-to-date
+ // Note: we could also reset filter input here, if we don't
+ // want to filter input to persist across client-side nav, ie:
// setFilterInput("")
useEffect(() => {
if (!navData) return
- setContent(addIsActiveToNodes(navData, currentPath))
- }, [currentPath, navData])
+ setContent(addIsActiveToNodes(navData, currentPath, pathname))
+ }, [currentPath, navData, pathname])
// When filter input changes, update content
// to filter out items that don't match
@@ -70,15 +72,21 @@ export default function DocsSidenav({
)
}
-function addIsActiveToNodes(navNodes, currentPath) {
- return navNodes.slice().map((node) => addIsActiveToNode(node, currentPath))
+function addIsActiveToNodes(navNodes, currentPath, pathname) {
+ return navNodes
+ .slice()
+ .map((node) => addIsActiveToNode(node, currentPath, pathname))
}
-function addIsActiveToNode(navNode, currentPath) {
+function addIsActiveToNode(navNode, currentPath, pathname) {
// If it's a node with child routes, return true
// if any of the child routes are active
if (navNode.routes) {
- const routesWithActive = addIsActiveToNodes(navNode.routes, currentPath)
+ const routesWithActive = addIsActiveToNodes(
+ navNode.routes,
+ currentPath,
+ pathname
+ )
const isActive = routesWithActive.filter((r) => r.__isActive).length > 0
return { ...navNode, routes: routesWithActive, __isActive: isActive }
}
@@ -88,10 +96,14 @@ function addIsActiveToNode(navNode, currentPath) {
const isActive = navNode.path === currentPath
return { ...navNode, __isActive: isActive }
}
- // Otherwise, return false
- // (for dividers, external links, etc)
- // TODO - do we need to worry about highlighting external links? yes probably,
- // sometimes these are used not as "external" but to internal links outside the baseRoute
+ // If it's a direct link,
+ // return true if the path matches the router.pathname
+ if (navNode.href) {
+ const isActive = navNode.href === pathname
+ console.log({ href: navNode.href, pathname, isActive })
+ return { ...navNode, __isActive: isActive }
+ }
+ // Otherwise, it's a divider, so return unmodified
return navNode
}
@@ -116,7 +128,7 @@ function filterContent(content, searchValue) {
}, [])
}
-function NavTree({ content, baseRoute, Link }) {
+function NavTree({ baseRoute, content }) {
return content.map((item, idx) => {
// Dividers
if (item.divider) {
@@ -125,7 +137,14 @@ function NavTree({ content, baseRoute, Link }) {
}
// Direct links
if (item.title && item.href) {
- return ([vault][vault]: ) - Specifies the remote Vault server the Agent connects to.
+
+- `auto_auth` ([auto_auth][autoauth]: ) - Specifies the method and other options used for Auto-Auth functionality.
+
+- `cache` ([cache][caching]: ) - Specifies options used for Caching functionality.
+
+- `listener` ([listener][listener]: ) - Specifies the addresses and ports on which the Agent will respond to requests.
+
+- `pid_file` `(string: "")` - Path to the file in which the agent's Process ID
+ (PID) should be stored
+
+- `exit_after_auth` `(bool: false)` - If set to `true`, the agent will exit
+ with code `0` after a single successful auth, where success means that a
+ token was retrieved and all sinks successfully wrote it
+
+- `template` ([template][template]: ) - Specifies options used for templating Vault secrets to files.
+
+### vault Stanza
+
+There can at most be one top level `vault` block and it has the following
+configuration entries:
+
+- `address` `(string: GCP",
diff --git a/packages/docs-sidenav/index.js b/packages/docs-sidenav/index.js
index bc2730373..c44af36bd 100644
--- a/packages/docs-sidenav/index.js
+++ b/packages/docs-sidenav/index.js
@@ -20,7 +20,7 @@ export default function DocsSidenav({
}) {
const router = useRouter()
const pathname = router ? router.pathname : null
- const [open, setOpen] = useState(false)
+ const [isMobileOpen, setIsMobileOpen] = useState(false)
const [filterInput, setFilterInput] = useState('')
const [content, setContent] = useState(navData)
const [filteredContent, setFilteredContent] = useState(navData)
@@ -43,14 +43,20 @@ export default function DocsSidenav({
}, [filterInput, content])
return (
- This is a cool docs page!
', - scope: {}, - }, - navData: componentProps.staticProps.properties.navData.testValue, - frontMatter: { page_title: 'Test Page', description: 'test description' }, - currentPath: componentProps.staticProps.properties.currentPath.testValue, - }, - }} ->{`This is a cool docs page!
-This is a cool docs page!
', + scope: {}, + }, }, - frontmatter: { + frontMatter: { type: 'object', required: true, description: "Frontmatter object parsed from the page's `.mdx` file.", properties: { - canonicalUrl: { + canonical_url: { type: 'string', description: 'Optional canonical URL. Passed directly to [@hashicorp/react-head](/?component=Head).', @@ -67,17 +101,21 @@ module.exports = { description: 'Used for the ``. Passed directly to [@hashicorp/react-head](/?component=Head).', required: true, + testValue: 'Test description', }, - pageTitle: { + page_title: { type: 'string', description: 'Used to construct the meta `This is a cool docs page!
-').replace(/<\/tt>/g, '')
-}
diff --git a/packages/docs-sidenav/package.json b/packages/docs-sidenav/package.json
index 054e68120..06eaba39e 100644
--- a/packages/docs-sidenav/package.json
+++ b/packages/docs-sidenav/package.json
@@ -3,10 +3,6 @@
"description": "Sidebar used for navigation HashiCorp docs",
"version": "6.1.0",
"author": "HashiCorp",
- "bin": {
- "delete-sidebar-title-frontmatter": "bin/delete-sidebar-title-frontmatter.js",
- "migrate-to-mktg-032": "bin/migrate-to-mktg-032.js"
- },
"contributors": [
"Jeff Escalante"
],
From 89f85aea1f832525cb0caeb6cc4df1172a5ecfe8 Mon Sep 17 00:00:00 2001
From: Zach Shilton <4624598+zchsh@users.noreply.github.com>
Date: Mon, 8 Mar 2021 18:34:02 -0500
Subject: [PATCH 62/62] Update tests to avoid unstable parentNode selection
---
packages/docs-page/index.test.js | 10 ++++---
packages/docs-sidenav/index.test.js | 45 ++++++++++++++---------------
2 files changed, 28 insertions(+), 27 deletions(-)
diff --git a/packages/docs-page/index.test.js b/packages/docs-page/index.test.js
index 670002ddf..b59aaaac0 100644
--- a/packages/docs-page/index.test.js
+++ b/packages/docs-page/index.test.js
@@ -44,8 +44,10 @@ describe('