diff --git a/nx-dev/data-access-documents/src/lib/documents.api.ts b/nx-dev/data-access-documents/src/lib/documents.api.ts index f3146e8d9f44e7..89b1a8e6190610 100644 --- a/nx-dev/data-access-documents/src/lib/documents.api.ts +++ b/nx-dev/data-access-documents/src/lib/documents.api.ts @@ -2,7 +2,11 @@ import { readFileSync } from 'fs'; import { join } from 'path'; import matter from 'gray-matter'; import * as marked from 'marked'; -import { archiveRootPath, previewRootPath } from './documents.utils'; +import { + archiveRootPath, + extractTitle, + previewRootPath, +} from './documents.utils'; import { VersionMetadata, DocumentData, @@ -16,7 +20,14 @@ export function getDocument( ): DocumentData { const segments = Array.isArray(_segments) ? [..._segments] : [_segments]; const docPath = getFilePath(version, segments); - const file = matter(readFileSync(docPath, 'utf8')); + const originalContent = readFileSync(docPath, 'utf8'); + const file = matter(originalContent); + + // Set default title if not provided in front-matter section. + if (!file.data.title) { + file.data.title = + extractTitle(originalContent) ?? segments[segments.length - 1]; + } return { filePath: docPath, @@ -35,16 +46,12 @@ export function getFilePath(version: string, segments: string[]): string { items = found.itemList; } else { throw new Error( - `Cannot find document matching segments: ${segments.join(',')}` + `Cannot find document matching segments: ${JSON.stringify(segments)}` ); } } - if (!found.file) { - throw new Error( - `Cannot find document matching segments: ${segments.join(',')}` - ); - } - return join(getDocumentsRoot(version), `${found.file}.md`); + const file = found.file ?? segments.join('/'); + return join(getDocumentsRoot(version), `${file}.md`); } const documentsCache = new Map(); @@ -65,25 +72,24 @@ export function getStaticDocumentPaths(version: string) { const paths = []; function recur(curr, acc) { - if (curr.file) { - paths.push({ - params: { - version, - flavor: acc[0], - segments: acc.slice(2).concat(curr.id), - }, - }); - return; - } if (curr.itemList) { curr.itemList.forEach((ii) => { recur(ii, [...acc, curr.id]); }); + return; } + + paths.push({ + params: { + version, + flavor: acc[0], + segments: acc.slice(1).concat(curr.id), + }, + }); } getDocuments(version).forEach((item) => { - recur(item, [item.id]); + recur(item, []); }); return paths; diff --git a/nx-dev/data-access-documents/src/lib/documents.utils.spec.ts b/nx-dev/data-access-documents/src/lib/documents.utils.spec.ts new file mode 100644 index 00000000000000..dd26dda20af315 --- /dev/null +++ b/nx-dev/data-access-documents/src/lib/documents.utils.spec.ts @@ -0,0 +1,27 @@ +import { extractTitle } from './documents.utils'; + +describe('extractTitle', () => { + it('should return header if it exists', () => { + const content = ` + # This is the title + + Hello, this is just a test document... + `; + + const result = extractTitle(content); + + expect(result).toEqual('This is the title'); + }); + + it('should return null if title cannot be found', () => { + const content = ` + ## Secondary header + + Hello, this is just a test document... + `; + + const result = extractTitle(content); + + expect(result).toEqual(null); + }); +}); diff --git a/nx-dev/data-access-documents/src/lib/documents.utils.ts b/nx-dev/data-access-documents/src/lib/documents.utils.ts index 6ce48d0d0ccfd4..06d4d9343e2d81 100644 --- a/nx-dev/data-access-documents/src/lib/documents.utils.ts +++ b/nx-dev/data-access-documents/src/lib/documents.utils.ts @@ -3,3 +3,9 @@ import { appRootPath } from '@nrwl/workspace/src/utilities/app-root'; export const archiveRootPath = join(appRootPath, 'nx-dev/archive'); export const previewRootPath = join(appRootPath, 'docs'); + +export function extractTitle(markdownContent: string): string | null { + return ( + /^\s*#\s+(?.+)[\n.]+/.exec(markdownContent)?.groups.title ?? null + ); +} diff --git a/nx-dev/feature-doc-viewer/src/lib/doc-viewer.spec.tsx b/nx-dev/feature-doc-viewer/src/lib/doc-viewer.spec.tsx index 78fff9f3aa69b4..165febf1131b7b 100644 --- a/nx-dev/feature-doc-viewer/src/lib/doc-viewer.spec.tsx +++ b/nx-dev/feature-doc-viewer/src/lib/doc-viewer.spec.tsx @@ -7,7 +7,9 @@ describe('DocViewer', () => { it('should render successfully', () => { const { baseElement } = render( <DocViewer - content="" + version="1.0.0" + flavor="react" + document={{ content: '', data: {}, excerpt: '', filePath: '' }} menu={{ version: 'preview', flavor: 'react', sections: [] }} toc="" /> diff --git a/nx-dev/feature-doc-viewer/src/lib/doc-viewer.tsx b/nx-dev/feature-doc-viewer/src/lib/doc-viewer.tsx index 5e8947e0b71029..c439539cb0504e 100644 --- a/nx-dev/feature-doc-viewer/src/lib/doc-viewer.tsx +++ b/nx-dev/feature-doc-viewer/src/lib/doc-viewer.tsx @@ -1,38 +1,51 @@ import React from 'react'; import Content from './content'; +import Head from 'next/head'; +import { Menu } from '@nrwl/nx-dev/data-access-menu'; +import { DocumentData } from '@nrwl/nx-dev/data-access-documents'; + import Sidebar from './sidebar'; import Toc from './toc'; -import { Menu } from '@nrwl/nx-dev/data-access-menu'; export interface DocumentationFeatureDocViewerProps { + version: string; + flavor: string; menu: Menu; - content: any; + document: DocumentData; toc: any; } export function DocViewer({ - content, + document, menu, + flavor, }: DocumentationFeatureDocViewerProps) { return ( - <div className="w-full max-w-screen-xl max-w-8xl mx-auto"> - <div className="lg:flex"> - <Sidebar menu={menu} /> - <div - id="content-wrapper" - className="min-w-0 w-full flex-auto lg:static lg:max-h-full lg:overflow-visible" - > - <div className="w-full flex"> - <Content data={content} /> - <div className="hidden xl:text-sm xl:block flex-none w-64 pl-8 mr-8"> - <div className="flex flex-col justify-between overflow-y-auto sticky max-h-(screen-18) pt-10 pb-6 top-18"> - <Toc /> + <> + <Head> + <title> + {document.data.title} | Nx {flavor} documentation + + +
+
+ +
+
+ +
+
+ +
- + ); } diff --git a/nx-dev/feature-doc-viewer/src/lib/sidebar.spec.tsx b/nx-dev/feature-doc-viewer/src/lib/sidebar.spec.tsx index 48c30478578349..84dab8fb757aa4 100644 --- a/nx-dev/feature-doc-viewer/src/lib/sidebar.spec.tsx +++ b/nx-dev/feature-doc-viewer/src/lib/sidebar.spec.tsx @@ -1,13 +1,55 @@ import React from 'react'; +import { screen } from '@testing-library/dom'; import { render } from '@testing-library/react'; import Sidebar from './sidebar'; describe('Sidebar', () => { - it('should render successfully', () => { - const { baseElement } = render( - + it('should render sections', () => { + render( + ); - expect(baseElement).toBeTruthy(); + + expect(() => screen.getByTestId('section-h4:basic')).toThrow( + /Unable to find/ + ); + expect(screen.getByTestId('section-h4:api')).toBeTruthy(); }); }); diff --git a/nx-dev/feature-doc-viewer/src/lib/sidebar.tsx b/nx-dev/feature-doc-viewer/src/lib/sidebar.tsx index d08901874f18b9..10c2cef12130b8 100644 --- a/nx-dev/feature-doc-viewer/src/lib/sidebar.tsx +++ b/nx-dev/feature-doc-viewer/src/lib/sidebar.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback, useState } from 'react'; import cx from 'classnames'; import Link from 'next/link'; import { Menu, MenuItem, MenuSection } from '@nrwl/nx-dev/data-access-menu'; @@ -35,9 +35,14 @@ export function Sidebar({ menu }: SidebarProps) { function SidebarSection({ section }: { section: MenuSection }) { return ( <> -

- {section.name} -

+ {section.hideSectionHeader ? null : ( +

+ {section.name} +

+ )}