handoff-app 1.1.2 → 1.1.3-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Changelog.md CHANGED
@@ -6,6 +6,50 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to
7
7
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
+ ## \[1.1.3] - 2026-03-27
10
+
11
+ This release reworks the markdown page rendering system with four improvements:
12
+ n-level deep routing, automatic table-of-contents generation, standardized code
13
+ block styling, and GitHub Flavored Markdown table support.
14
+
15
+ ### Features
16
+
17
+ - **N-level deep markdown routing** — Replaced the fixed two-level routing
18
+ (`[level1]` / `[level1]/[level2]`) with a single `[...slug]` catch-all route
19
+ that supports markdown pages at any directory depth. A new recursive
20
+ `buildCatchAllStaticPaths` function walks `config/docs` and `pages`
21
+ directories to collect `.md` files at any nesting level. The sidebar menu now
22
+ also recursively scans directories to surface deeply nested pages.
23
+
24
+ - **Automatic table of contents** — The existing but unused `PageTOC` component
25
+ is now wired into the catch-all markdown page. It scans rendered headings
26
+ (h1–h6) via a `MutationObserver` and populates the right-hand "On This Page"
27
+ anchor nav automatically. Fixed a timing bug where headings were scanned
28
+ before the markdown content was in the DOM, and fixed anchor IDs that were
29
+ incorrectly prefixed with `#` (producing `##id` in URLs).
30
+
31
+ - **Standardized code block styling** — Markdown fenced code blocks now render
32
+ with a toolbar header matching the site's `CodeHighlight` component: a
33
+ language icon, filename or language label, and a copy-to-clipboard button. A
34
+ new `remarkCodeMeta` remark plugin preserves the fence info string so metadata
35
+ like `` ```bash filename="deploy.sh" `` is parsed and displayed. The Prism
36
+ theme respects dark mode via `next-themes`, and registered languages now
37
+ include bash, css, scss, jsx, tsx, typescript, yaml, handlebars, and more.
38
+
39
+ - **Markdown table support** — Added `remark-gfm` to all `ReactMarkdown`
40
+ instances across the codebase (17 files). Pipe tables, strikethrough,
41
+ autolinks, and task lists now render correctly. Tables are styled via Tailwind
42
+ Typography's `.prose table` defaults.
43
+
44
+ ### Changed
45
+
46
+ - Deleted `src/app/pages/[level1]/index.tsx` and
47
+ `src/app/pages/[level1]/[level2]/index.tsx` (replaced by `[...slug]`).
48
+ - Removed `buildL1StaticPaths`, `buildL2StaticPaths`, and the `SubPageType`
49
+ interface from `src/app/components/util/index.ts`.
50
+ - `ComponentGuidelines` now uses `MarkdownComponents` and the remark plugins
51
+ for consistent rendering.
52
+
9
53
  ## \[1.1.2] - 2026-03-26
10
54
 
11
55
  This release improves integration with downstream platforms (for example HubSpot) by letting
package/dist/cli.js CHANGED
@@ -19,7 +19,7 @@ class HandoffCliError extends Error {
19
19
  * Show the help message
20
20
  */
21
21
  const showVersion = () => {
22
- return 'Handoff App - 1.1.2';
22
+ return 'Handoff App - 1.1.3';
23
23
  };
24
24
  /**
25
25
  * Define a CLI error
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "handoff-app",
3
- "version": "1.1.2",
3
+ "version": "1.1.3-0",
4
4
  "description": "Automated documentation toolchain for building client side documentation from figma",
5
5
  "author ": {
6
6
  "name": "Convertiv",
@@ -109,6 +109,7 @@
109
109
  "react-scroll": "^1.8.9",
110
110
  "react-syntax-highlighter": "^16.1.0",
111
111
  "rehype-raw": "^7.0.0",
112
+ "remark-gfm": "^4.0.1",
112
113
  "sass": "^1.85.0",
113
114
  "tailwind-merge": "^2.5.5",
114
115
  "tailwindcss": "^4.1.14",
@@ -1,6 +1,8 @@
1
1
  import React from 'react';
2
2
  import ReactMarkdown from 'react-markdown';
3
3
  import rehypeRaw from 'rehype-raw';
4
+ import remarkGfm from 'remark-gfm';
5
+ import { MarkdownComponents, remarkCodeMeta } from './Markdown/MarkdownComponents';
4
6
 
5
7
  export interface ComponentGuidelinesProps {
6
8
  content: string;
@@ -10,7 +12,7 @@ export const ComponentGuidelines: React.FC<ComponentGuidelinesProps> = ({ conten
10
12
  return (
11
13
  <>
12
14
  <div className="prose">
13
- <ReactMarkdown rehypePlugins={[rehypeRaw]}>{content}</ReactMarkdown>
15
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>{content}</ReactMarkdown>
14
16
  </div>
15
17
  </>
16
18
  );
@@ -1,23 +1,56 @@
1
+ import { useTheme } from 'next-themes';
1
2
  import React, { ReactElement } from 'react';
3
+ import bash from 'react-syntax-highlighter/dist/esm/languages/prism/bash';
4
+ import css from 'react-syntax-highlighter/dist/esm/languages/prism/css';
5
+ import handlebars from 'react-syntax-highlighter/dist/esm/languages/prism/handlebars';
2
6
  import js from 'react-syntax-highlighter/dist/esm/languages/prism/javascript';
3
7
  import json from 'react-syntax-highlighter/dist/esm/languages/prism/json';
8
+ import jsx from 'react-syntax-highlighter/dist/esm/languages/prism/jsx';
4
9
  import markdown from 'react-syntax-highlighter/dist/esm/languages/prism/markdown';
5
10
  import sass from 'react-syntax-highlighter/dist/esm/languages/prism/scss';
6
- import tsx from 'react-syntax-highlighter/dist/esm/languages/prism/typescript';
11
+ import tsx from 'react-syntax-highlighter/dist/esm/languages/prism/tsx';
12
+ import typescript from 'react-syntax-highlighter/dist/esm/languages/prism/typescript';
7
13
  import html from 'react-syntax-highlighter/dist/esm/languages/prism/xml-doc';
14
+ import yaml from 'react-syntax-highlighter/dist/esm/languages/prism/yaml';
8
15
  import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-light';
9
- import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
16
+ import { oneDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
17
+ import CopyCode from '../CopyCode';
18
+
19
+ import {
20
+ Braces,
21
+ Code,
22
+ FileCode2,
23
+ FileText,
24
+ FileType,
25
+ Terminal,
26
+ } from 'lucide-react';
27
+
28
+ SyntaxHighlighter.registerLanguage('bash', bash);
29
+ SyntaxHighlighter.registerLanguage('sh', bash);
30
+ SyntaxHighlighter.registerLanguage('shell', bash);
31
+ SyntaxHighlighter.registerLanguage('zsh', bash);
32
+ SyntaxHighlighter.registerLanguage('css', css);
33
+ SyntaxHighlighter.registerLanguage('handlebars', handlebars);
34
+ SyntaxHighlighter.registerLanguage('hbs', handlebars);
35
+ SyntaxHighlighter.registerLanguage('javascript', js);
10
36
  SyntaxHighlighter.registerLanguage('js', js);
11
37
  SyntaxHighlighter.registerLanguage('json', json);
38
+ SyntaxHighlighter.registerLanguage('jsx', jsx);
12
39
  SyntaxHighlighter.registerLanguage('markdown', markdown);
40
+ SyntaxHighlighter.registerLanguage('md', markdown);
13
41
  SyntaxHighlighter.registerLanguage('sass', sass);
42
+ SyntaxHighlighter.registerLanguage('scss', sass);
14
43
  SyntaxHighlighter.registerLanguage('tsx', tsx);
44
+ SyntaxHighlighter.registerLanguage('typescript', typescript);
45
+ SyntaxHighlighter.registerLanguage('ts', typescript);
15
46
  SyntaxHighlighter.registerLanguage('html', html);
16
- /**
17
- * Custom renderer type that we support
18
- */
47
+ SyntaxHighlighter.registerLanguage('xml', html);
48
+ SyntaxHighlighter.registerLanguage('yaml', yaml);
49
+ SyntaxHighlighter.registerLanguage('yml', yaml);
50
+
19
51
  export type CustomRenderers = {
20
52
  code: (props: any) => ReactElement;
53
+ pre: (props: any) => ReactElement;
21
54
  h1: (props: any) => ReactElement;
22
55
  h2: (props: any) => ReactElement;
23
56
  h3: (props: any) => ReactElement;
@@ -27,32 +60,27 @@ export type CustomRenderers = {
27
60
  paragraph: (props: { children: React.ReactNode }) => ReactElement;
28
61
  };
29
62
 
30
- /**
31
- * Build the headers in markdown
32
- * @param param0
33
- * @returns
34
- */
35
- const Headings = (element) => {
63
+ function extractText(node: any): string {
64
+ if (typeof node === 'string') return node;
65
+ if (typeof node === 'number') return String(node);
66
+ if (Array.isArray(node)) return node.map(extractText).join('');
67
+ if (node?.props?.children) return extractText(node.props.children);
68
+ return '';
69
+ }
70
+
71
+ const Headings = (element: any) => {
36
72
  const { children, node } = element;
37
- // Access actual (string) value of heading
38
73
  if (children[0]) {
39
- const heading = children[0];
40
74
  const type = node.tagName || 'h6';
41
75
 
42
- // If we have a heading, make it lower case
43
- let anchor = heading.toString().toLowerCase();
44
-
45
- // Clean anchor (replace special characters whitespaces).
46
- // Alternatively, use encodeURIComponent() if you don't care about
47
- // pretty anchor links
76
+ let anchor = extractText(children).toLowerCase();
48
77
  anchor = anchor.replace(/[^a-zA-Z0-9 ]/g, '');
49
78
  anchor = anchor.replace(/ /g, '-');
50
79
 
51
- // Utility
52
80
  const container = (children: React.ReactNode): React.ReactNode => (
53
81
  <>
54
82
  {children}
55
- <a id={anchor} href={`#${anchor}`} className="doc-link" onClick={(e) => console.log(e)}></a>
83
+ <a id={anchor} href={`#${anchor}`} className="doc-link"></a>
56
84
  </>
57
85
  );
58
86
 
@@ -69,22 +97,147 @@ const Headings = (element) => {
69
97
  return <h5 className="text-sm font-bold">{container(children)}</h5>;
70
98
  case 'h6':
71
99
  return <h6 className="text-xs font-bold">{container(children)}</h6>;
72
-
73
100
  default:
74
- return (
75
- <h6 className="text-xs font-bold">
76
- {JSON.stringify(type)}: {container(children)}
77
- </h6>
78
- );
101
+ return <h6 className="text-xs font-bold">{container(children)}</h6>;
79
102
  }
80
103
  } else {
81
104
  return <h1>children</h1>;
82
105
  }
83
106
  };
84
107
 
108
+ const LANGUAGE_ICONS: Record<string, React.FC<{ className?: string }>> = {
109
+ bash: Terminal,
110
+ sh: Terminal,
111
+ shell: Terminal,
112
+ zsh: Terminal,
113
+ js: FileCode2,
114
+ javascript: FileCode2,
115
+ ts: FileCode2,
116
+ typescript: FileCode2,
117
+ jsx: FileCode2,
118
+ tsx: FileCode2,
119
+ css: FileType,
120
+ scss: FileType,
121
+ sass: FileType,
122
+ html: FileCode2,
123
+ xml: FileCode2,
124
+ json: Braces,
125
+ yaml: FileText,
126
+ yml: FileText,
127
+ markdown: FileText,
128
+ md: FileText,
129
+ handlebars: Code,
130
+ hbs: Code,
131
+ };
132
+
133
+ const LANGUAGE_LABELS: Record<string, string> = {
134
+ bash: 'Bash',
135
+ sh: 'Shell',
136
+ shell: 'Shell',
137
+ zsh: 'Zsh',
138
+ js: 'JavaScript',
139
+ javascript: 'JavaScript',
140
+ ts: 'TypeScript',
141
+ typescript: 'TypeScript',
142
+ jsx: 'JSX',
143
+ tsx: 'TSX',
144
+ css: 'CSS',
145
+ scss: 'SCSS',
146
+ sass: 'SASS',
147
+ html: 'HTML',
148
+ xml: 'XML',
149
+ json: 'JSON',
150
+ yaml: 'YAML',
151
+ yml: 'YAML',
152
+ markdown: 'Markdown',
153
+ md: 'Markdown',
154
+ handlebars: 'Handlebars',
155
+ hbs: 'Handlebars',
156
+ text: 'Code',
157
+ };
158
+
159
+ function parseMeta(meta: string | undefined): Record<string, string> {
160
+ if (!meta) return {};
161
+ const result: Record<string, string> = {};
162
+ const regex = /(\w+)="([^"]+)"/g;
163
+ let match;
164
+ while ((match = regex.exec(meta)) !== null) {
165
+ result[match[1]] = match[2];
166
+ }
167
+ return result;
168
+ }
169
+
170
+ function MarkdownCodeBlock(props: any) {
171
+ const { className, children, node, 'data-meta': dataMeta, ...rest } = props;
172
+ const { resolvedTheme } = useTheme();
173
+ const isDark = resolvedTheme === 'dark';
174
+
175
+ const langMatch = /language-(\w+)/.exec(className || '');
176
+ const isBlock = String(children).includes('\n');
177
+
178
+ if (!langMatch && !isBlock) {
179
+ return <code className={className} {...rest}>{children}</code>;
180
+ }
181
+
182
+ const lang = langMatch ? langMatch[1] : 'text';
183
+ const meta = parseMeta(dataMeta);
184
+ const filename = meta.filename;
185
+ const codeString = String(children).replace(/\n$/, '');
186
+ const IconComponent = LANGUAGE_ICONS[lang] || Code;
187
+ const langLabel = LANGUAGE_LABELS[lang] || lang;
188
+
189
+ const theme = isDark ? { ...oneDark } : { ...oneLight };
190
+ theme['pre[class*="language-"]'] = {
191
+ ...theme['pre[class*="language-"]'],
192
+ overflow: 'auto',
193
+ margin: '0',
194
+ borderRadius: '0 0 0.5rem 0.5rem',
195
+ background: isDark ? '#1e1e1e' : '#fafafa',
196
+ };
197
+
198
+ return (
199
+ <div className="not-prose my-4 min-w-0 max-w-full overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700">
200
+ <div className="flex items-center justify-between bg-gray-50 px-4 py-2 dark:bg-gray-800">
201
+ <div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
202
+ <IconComponent className="h-4 w-4" />
203
+ <span>{filename || langLabel}</span>
204
+ </div>
205
+ <CopyCode code={codeString} />
206
+ </div>
207
+ <SyntaxHighlighter
208
+ style={theme}
209
+ language={lang}
210
+ PreTag="div"
211
+ showLineNumbers={lang !== 'text'}
212
+ wrapLines={true}
213
+ useInlineStyles={true}
214
+ >
215
+ {codeString}
216
+ </SyntaxHighlighter>
217
+ </div>
218
+ );
219
+ }
220
+
85
221
  /**
86
- * Build custom renderers for markdown
222
+ * Remark plugin that preserves the code fence meta string
223
+ * (the text after the language identifier) as a hast data attribute.
87
224
  */
225
+ export function remarkCodeMeta() {
226
+ return (tree: any) => {
227
+ const walk = (node: any) => {
228
+ if (node.type === 'code' && node.meta) {
229
+ node.data = node.data || {};
230
+ node.data.hProperties = node.data.hProperties || {};
231
+ node.data.hProperties['data-meta'] = node.meta;
232
+ }
233
+ if (node.children) {
234
+ node.children.forEach(walk);
235
+ }
236
+ };
237
+ walk(tree);
238
+ };
239
+ }
240
+
88
241
  export const MarkdownComponents: CustomRenderers = {
89
242
  h1: Headings,
90
243
  h2: Headings,
@@ -93,28 +246,8 @@ export const MarkdownComponents: CustomRenderers = {
93
246
  h5: Headings,
94
247
  h6: Headings,
95
248
  paragraph: ({ children }) => <p>{children}</p>,
96
- code(props) {
97
- const { className } = props;
98
- const match = /language-(\w+)/.exec(className || '');
99
- // This was designed to flatten out children arrays, but I think its not needed
100
- // if (props.children[0]) {
101
- // props.children[0] = props.children[0].toString().trim();
102
- // }
103
- return match ? (
104
- <SyntaxHighlighter
105
- // @ts-ignore
106
- style={oneLight}
107
- language={match[1]}
108
- PreTag="div"
109
- showLineNumbers={true}
110
- wrapLines={true}
111
- useInlineStyles={true}
112
- {...props}
113
- ></SyntaxHighlighter>
114
- ) : (
115
- <code {...props} />
116
- );
117
- },
249
+ pre: ({ children }) => <>{children}</>,
250
+ code: MarkdownCodeBlock,
118
251
  };
119
252
 
120
253
  export default Headings;
@@ -18,45 +18,45 @@ interface TOCProps {
18
18
  export function PageTOC({ body, title }: TOCProps) {
19
19
  const [headers, setHeaders] = React.useState<{ id: string; title: string | null; level: number }[]>([]);
20
20
  const router = useRouter();
21
- const getHeaders = () => {
22
- if (body.current) {
23
- const headers = Array.from(body.current.querySelectorAll('h1, h2, h3, h4, h5, h6')).map((node) => {
24
- const id = anchorSlugify(node?.textContent?.toString() ?? '');
25
- return {
26
- id,
27
- title: node.textContent,
28
- level: parseInt(node.tagName[1]),
29
- };
30
- });
31
- setHeaders(headers);
32
- }
33
- };
34
- const headersCallback = React.useCallback(getHeaders, [body]);
21
+
22
+ const scanHeaders = React.useCallback(() => {
23
+ if (!body.current) return;
24
+ const found = Array.from(body.current.querySelectorAll('h1, h2, h3, h4, h5, h6')).map((node) => ({
25
+ id: anchorSlugify(node?.textContent?.toString() ?? ''),
26
+ title: node.textContent,
27
+ level: parseInt(node.tagName[1]),
28
+ }));
29
+ setHeaders(found);
30
+ }, [body]);
35
31
 
36
32
  useEffect(() => {
37
- router.events.on('routeChangeComplete', () => {
38
- headersCallback();
39
- });
40
- return () => {
41
- router.events.off('routeChangeComplete', () => {
42
- headersCallback();
43
- });
44
- };
45
- }, [router, headersCallback]);
33
+ // Scan after paint to catch SSR-hydrated content
34
+ requestAnimationFrame(scanHeaders);
35
+
36
+ // Watch for dynamically inserted headings (client-rendered markdown)
37
+ const el = body.current;
38
+ if (!el) return;
39
+ const observer = new MutationObserver(scanHeaders);
40
+ observer.observe(el, { childList: true, subtree: true });
41
+ return () => observer.disconnect();
42
+ }, [body, scanHeaders]);
43
+
46
44
  useEffect(() => {
47
- headersCallback();
48
- }, [body, headersCallback]);
45
+ const handler = () => requestAnimationFrame(scanHeaders);
46
+ router.events.on('routeChangeComplete', handler);
47
+ return () => router.events.off('routeChangeComplete', handler);
48
+ }, [router, scanHeaders]);
49
49
  return (
50
50
  <AnchorNav
51
51
  title={title}
52
52
  groups={headers.reduce((acc, header) => {
53
53
  if (header.level === 1) {
54
- acc.push({ [`#${header.id}`]: header.title ?? '' });
54
+ acc.push({ [header.id]: header.title ?? '' });
55
55
  } else {
56
56
  if (acc.length === 0) {
57
- acc.push({ [`#${header.id}`]: header.title ?? '' });
57
+ acc.push({ [header.id]: header.title ?? '' });
58
58
  } else {
59
- acc[acc.length - 1][`#${header.id}`] = header.title ?? '';
59
+ acc[acc.length - 1][header.id] = header.title ?? '';
60
60
  }
61
61
  }
62
62
  return acc;
@@ -7,13 +7,12 @@ import { Types as CoreTypes } from 'handoff-core';
7
7
  import { groupBy, startCase, uniq } from 'lodash';
8
8
  import path from 'path';
9
9
  import { ParsedUrlQuery } from 'querystring';
10
- import { SubPageType } from '../../pages/[level1]/[level2]';
11
-
12
10
  // Get the parsed url string type
13
11
  export interface IParams extends ParsedUrlQuery {
14
- slug: string;
12
+ slug: string | string[];
15
13
  }
16
14
 
15
+
17
16
  // Type for the metadata from frontmatter
18
17
  export interface Metadata {
19
18
  title: string;
@@ -118,95 +117,99 @@ export const pluralizeComponent = (singular: string): string => {
118
117
  };
119
118
 
120
119
  /**
121
- * Build level 1 static path parameters
122
- * @returns
120
+ * Recursively collect all .md files from a directory, returning their
121
+ * path segments relative to the root (without the .md extension).
123
122
  */
124
- export const buildL1StaticPaths = () => {
125
- const docRoot = path.resolve(process.env.HANDOFF_MODULE_PATH ?? '', 'config/docs');
126
- const files = fs.readdirSync(docRoot);
127
- const pageRoot = path.resolve(process.env.HANDOFF_WORKING_PATH ?? '', 'pages');
128
- let list = files;
129
- if (fs.existsSync(pageRoot)) {
130
- const pages = fs.readdirSync(pageRoot);
131
- list = files.concat(pages);
123
+ const collectMarkdownPaths = (rootDir: string, relativeParts: string[] = []): string[][] => {
124
+ if (!fs.existsSync(rootDir)) return [];
125
+ const entries = fs.readdirSync(rootDir);
126
+ const results: string[][] = [];
127
+ for (const entry of entries) {
128
+ const fullPath = path.join(rootDir, entry);
129
+ if (fs.lstatSync(fullPath).isDirectory()) {
130
+ results.push(...collectMarkdownPaths(fullPath, [...relativeParts, entry]));
131
+ } else if (entry.endsWith('.md') && entry !== 'index.md') {
132
+ results.push([...relativeParts, entry.replace('.md', '')]);
133
+ }
132
134
  }
133
- const paths = list
134
- .filter((fileName) => {
135
- if (fs.existsSync(path.join(docRoot, fileName))) return !fs.lstatSync(path.join(docRoot, fileName)).isDirectory();
136
- if (fs.existsSync(path.join(pageRoot, fileName))) return !fs.lstatSync(path.join(pageRoot, fileName)).isDirectory();
137
- return false;
138
- })
139
- .filter((fileName) => fileName.endsWith('.md'))
140
- .map((fileName) => {
141
- const path = fileName.replace('.md', '');
142
- if (knownPaths.indexOf(path) < 0) {
143
- return {
144
- params: {
145
- level1: path,
146
- },
147
- };
148
- }
149
- })
150
- .filter(filterOutUndefined);
151
- return paths;
135
+ return results;
152
136
  };
153
137
 
154
138
  /**
155
- * Build static paths for level 2
156
- * @returns SubPathType[]
139
+ * Build catch-all static paths for all markdown pages at any depth.
140
+ * Excludes paths in knownPaths (those have dedicated route files).
157
141
  */
158
- export const buildL2StaticPaths = () => {
142
+ export const buildCatchAllStaticPaths = () => {
159
143
  const docRoot = path.resolve(process.env.HANDOFF_MODULE_PATH ?? '', 'config/docs');
160
- const files = fs.readdirSync(docRoot);
161
144
  const pageRoot = path.resolve(process.env.HANDOFF_WORKING_PATH ?? '', 'pages');
162
- let list = files;
163
- if (fs.existsSync(pageRoot)) {
164
- const pages = fs.readdirSync(pageRoot);
165
- list = files.concat(pages);
145
+
146
+ const docPaths = collectMarkdownPaths(docRoot);
147
+ const pagePaths = collectMarkdownPaths(pageRoot);
148
+
149
+ const seen = new Set<string>();
150
+ const allPaths: string[][] = [];
151
+
152
+ // Pages override docs (working copy wins)
153
+ for (const segments of [...pagePaths, ...docPaths]) {
154
+ const key = segments.join('/');
155
+ if (!seen.has(key)) {
156
+ seen.add(key);
157
+ allPaths.push(segments);
158
+ }
166
159
  }
167
- const paths: SubPageType[] = list
168
- .flatMap((fileName) => {
169
- let calculatePath;
170
- if (fs.existsSync(path.join(pageRoot, fileName))) {
171
- calculatePath = path.join(pageRoot, fileName);
172
- } else if (fs.existsSync(path.join(docRoot, fileName))) {
173
- calculatePath = path.join(docRoot, fileName);
174
- } else {
175
- return undefined;
176
- }
177
- if (fs.lstatSync(calculatePath).isDirectory()) {
178
- const subFiles = fs.readdirSync(calculatePath);
179
- return subFiles
180
- .filter((subFile) => subFile.endsWith('.md'))
181
- .flatMap((subFile) => {
182
- const childPath = fileName.replace('.md', '');
183
- if (knownPaths.indexOf(fileName + '/' + subFile.replace('.md', '')) < 0) {
184
- return {
185
- params: {
186
- level1: fileName,
187
- level2: subFile.replace('.md', ''),
188
- },
189
- };
190
- } else {
191
- console.log('file path already exists', fileName + '/' + subFile.replace('.md', ''));
192
- }
193
- })
194
- .filter(filterOutUndefined);
160
+
161
+ return allPaths
162
+ .filter((segments) => knownPaths.indexOf(segments.join('/')) < 0)
163
+ .map((segments) => ({ params: { slug: segments } }));
164
+ };
165
+
166
+ /**
167
+ * Recursively build menu entries from .md files in a directory.
168
+ * Returns sub-section items with nested menu items for subdirectories.
169
+ */
170
+ const buildMenuFromDirectory = (dirPath: string, urlPrefix: string): any[] => {
171
+ if (!fs.existsSync(dirPath)) return [];
172
+ const entries = fs.readdirSync(dirPath);
173
+ const items: any[] = [];
174
+
175
+ for (const entry of entries) {
176
+ const fullPath = path.join(dirPath, entry);
177
+ if (fs.lstatSync(fullPath).isDirectory()) {
178
+ const nestedItems = buildMenuFromDirectory(fullPath, `${urlPrefix}/${entry}`);
179
+ if (nestedItems.length > 0) {
180
+ items.push({
181
+ title: startCase(entry),
182
+ path: `${urlPrefix}/${entry}`,
183
+ menu: nestedItems,
184
+ });
195
185
  }
196
- })
197
- .filter(filterOutUndefined);
198
- return paths;
186
+ } else if (entry.endsWith('.md') && entry !== 'index.md') {
187
+ const slug = entry.replace('.md', '');
188
+ const fullSlugPath = `${urlPrefix}/${slug}`.replace(/^\/+/, '');
189
+ if (knownPaths.indexOf(fullSlugPath) >= 0) continue;
190
+
191
+ const contents = fs.readFileSync(fullPath, 'utf-8');
192
+ const { data: metadata } = matter(contents);
193
+ if (metadata.enabled === false) continue;
194
+
195
+ items.push({
196
+ title: metadata.menuTitle ?? metadata.title ?? startCase(slug),
197
+ path: `/${fullSlugPath}`,
198
+ weight: metadata.weight ?? 0,
199
+ });
200
+ }
201
+ }
202
+
203
+ return items.sort((a, b) => (a.weight ?? 0) - (b.weight ?? 0));
199
204
  };
200
205
 
201
206
  /**
202
- * Build the static menu for rendeirng pages
207
+ * Build the static menu for rendering pages
203
208
  * @returns SectionLink[]
204
209
  */
205
210
  export const staticBuildMenu = () => {
206
- // // Contents of docs
207
211
  const docRoot = path.join(process.env.HANDOFF_MODULE_PATH ?? '', 'config/docs');
208
- // Get the file list
209
- if(!fs.existsSync(docRoot)) {
212
+ if (!fs.existsSync(docRoot)) {
210
213
  return [];
211
214
  }
212
215
  const files = fs.readdirSync(docRoot);
@@ -218,7 +221,6 @@ export const staticBuildMenu = () => {
218
221
  list = list.concat(pages);
219
222
  }
220
223
  const sections: SectionLink[] = [];
221
- // Build path tree
222
224
  const custom = uniq(list)
223
225
  .map((fileName: string) => {
224
226
  let search = '';
@@ -243,19 +245,16 @@ export const staticBuildMenu = () => {
243
245
  let subSections = [];
244
246
 
245
247
  if (metadata.menu) {
246
- // Build the submenu
247
248
  subSections = Object.keys(metadata.menu)
248
249
  .map((key) => {
249
250
  const sub = metadata.menu[key];
250
251
  if (sub.components) {
251
- // The user wants to inject the component menu here
252
252
  return {
253
253
  title: sub.title,
254
254
  menu: staticBuildComponentMenu(sub.components),
255
255
  };
256
256
  }
257
257
  if (sub.tokens) {
258
- // The user wants to inject the component menu here
259
258
  return {
260
259
  title: 'Tokens',
261
260
  menu: staticBuildTokensMenu(),
@@ -266,6 +265,21 @@ export const staticBuildMenu = () => {
266
265
  }
267
266
  })
268
267
  .filter(filterOutUndefined);
268
+ } else {
269
+ // Only auto-scan directories when no frontmatter menu is defined
270
+ const dirName = fileName.replace('.md', '');
271
+ const docDir = path.resolve(docRoot, dirName);
272
+ const pagesDir = path.resolve(workingPages, dirName);
273
+ const nestedFromDocs = buildMenuFromDirectory(docDir, `/${dirName}`);
274
+ const nestedFromPages = buildMenuFromDirectory(pagesDir, `/${dirName}`);
275
+
276
+ const seenPaths = new Set<string>();
277
+ for (const item of [...nestedFromPages, ...nestedFromDocs]) {
278
+ if (item.path && !seenPaths.has(item.path)) {
279
+ seenPaths.add(item.path);
280
+ subSections.push(item);
281
+ }
282
+ }
269
283
  }
270
284
 
271
285
  let external: string | boolean = false;
@@ -6,6 +6,10 @@
6
6
  @plugin '@tailwindcss/typography';
7
7
  @plugin '@tailwindcss/container-queries';
8
8
 
9
+ .prose {
10
+ max-width: 100%;
11
+ }
12
+
9
13
  @custom-variant dark (&:is(.dark *));
10
14
 
11
15
  @theme {
@@ -1,56 +1,46 @@
1
1
  import { GetStaticProps } from 'next';
2
2
  import Head from 'next/head';
3
3
  import Link from 'next/link';
4
+ import { useRef } from 'react';
4
5
  import ReactMarkdown from 'react-markdown';
5
6
  import rehypeRaw from 'rehype-raw';
7
+ import remarkGfm from 'remark-gfm';
6
8
  import Layout from '../../components/Layout/Main';
7
- import { MarkdownComponents } from '../../components/Markdown/MarkdownComponents';
9
+ import { MarkdownComponents, remarkCodeMeta } from '../../components/Markdown/MarkdownComponents';
10
+ import { PageTOC } from '../../components/Navigation/AnchorNav';
8
11
  import HeadersType from '../../components/Typography/Headers';
9
12
  import {
10
- buildL1StaticPaths,
13
+ buildCatchAllStaticPaths,
11
14
  DocumentationProps,
12
15
  fetchDocPageMarkdown,
13
16
  getClientRuntimeConfig,
14
- IParams,
15
- reduceSlugToString,
16
17
  } from '../../components/util';
17
18
 
18
- /**
19
- * Render all index pages
20
- * @returns
21
- */
22
19
  export async function getStaticPaths() {
23
20
  return {
24
- paths: buildL1StaticPaths(),
21
+ paths: buildCatchAllStaticPaths(),
25
22
  fallback: false,
26
23
  };
27
24
  }
28
25
 
29
- /**
30
- * Get all the markdown data, build a menu tree, and then fetch the contents
31
- * of the current page for rendering
32
- *
33
- * This is all done statically at build time
34
- * @param context GetStaticProps
35
- * @returns
36
- */
37
26
  export const getStaticProps: GetStaticProps = (context) => {
38
- // Read current slug
39
- const { level1 } = context.params as IParams;
27
+ const { slug } = context.params as { slug: string[] };
28
+ const dirParts = slug.slice(0, -1);
29
+ const file = slug[slug.length - 1];
30
+ const docPath = dirParts.length > 0 ? `docs/${dirParts.join('/')}/` : 'docs/';
31
+ const sectionId = `/${slug[0]}`;
32
+
40
33
  return {
41
34
  props: {
42
- ...fetchDocPageMarkdown('docs/', reduceSlugToString(level1), `/${level1}`).props,
35
+ ...fetchDocPageMarkdown(docPath, file, sectionId).props,
43
36
  config: getClientRuntimeConfig(),
44
37
  },
45
38
  };
46
39
  };
47
40
 
48
- /**
49
- * Render Docs page
50
- * @param param0
51
- * @returns
52
- */
53
- export default function DocPage({ content, menu, metadata, current, config }: DocumentationProps) {
41
+ export default function DocCatchAllPage({ content, menu, metadata, current, config }: DocumentationProps) {
42
+ const bodyRef = useRef<HTMLDivElement>(null);
43
+
54
44
  if (content) {
55
45
  return (
56
46
  <Layout config={config} menu={menu} current={current} metadata={metadata}>
@@ -58,12 +48,15 @@ export default function DocPage({ content, menu, metadata, current, config }: Do
58
48
  <HeadersType.H1>{metadata.title}</HeadersType.H1>
59
49
  <p className="text-lg leading-relaxed text-gray-600 dark:text-gray-300">{metadata.description}</p>
60
50
  </div>
61
- <div>
62
- <div className="prose mb-10">
63
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
64
- {content}
65
- </ReactMarkdown>
51
+ <div className="lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_280px]">
52
+ <div className="min-w-0">
53
+ <div className="prose mb-10" ref={bodyRef}>
54
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
55
+ {content}
56
+ </ReactMarkdown>
57
+ </div>
66
58
  </div>
59
+ <PageTOC body={bodyRef} title="On This Page" />
67
60
  </div>
68
61
  </Layout>
69
62
  );
@@ -7,8 +7,9 @@ import path from 'path';
7
7
  import * as React from 'react';
8
8
  import ReactMarkdown from 'react-markdown';
9
9
  import rehypeRaw from 'rehype-raw';
10
+ import remarkGfm from 'remark-gfm';
10
11
  import Footer from '../../components/Footer';
11
- import { MarkdownComponents } from '../../components/Markdown/MarkdownComponents';
12
+ import { MarkdownComponents, remarkCodeMeta } from '../../components/Markdown/MarkdownComponents';
12
13
  import Header from '../../components/old/Header';
13
14
  import { fetchDocPageMarkdown, FontDocumentationProps, getClientRuntimeConfig, getTokens } from '../../components/util';
14
15
 
@@ -92,7 +93,7 @@ const FontsPage = ({ content, menu, metadata, current, customFonts, design, conf
92
93
  ))}
93
94
 
94
95
  <div className="prose">
95
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
96
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
96
97
  {content}
97
98
  </ReactMarkdown>
98
99
  </div>
@@ -3,8 +3,9 @@ import type { GetStaticProps } from 'next';
3
3
  import Head from 'next/head';
4
4
  import ReactMarkdown from 'react-markdown';
5
5
  import rehypeRaw from 'rehype-raw';
6
+ import remarkGfm from 'remark-gfm';
6
7
  import Footer from '../../components/Footer';
7
- import { MarkdownComponents } from '../../components/Markdown/MarkdownComponents';
8
+ import { MarkdownComponents, remarkCodeMeta } from '../../components/Markdown/MarkdownComponents';
8
9
  import NavLink from '../../components/NavLink';
9
10
  import Header from '../../components/old/Header';
10
11
  import { DocumentationProps, fetchDocPageMarkdown, getClientRuntimeConfig } from '../../components/util';
@@ -77,7 +78,7 @@ const AssetsPage = ({ content, menu, metadata, current, config }: DocumentationP
77
78
  </div>
78
79
 
79
80
  <div className="prose">
80
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
81
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
81
82
  {content}
82
83
  </ReactMarkdown>
83
84
  </div>
@@ -3,8 +3,9 @@ import type { GetStaticProps } from 'next';
3
3
  import Head from 'next/head';
4
4
  import ReactMarkdown from 'react-markdown';
5
5
  import rehypeRaw from 'rehype-raw';
6
+ import remarkGfm from 'remark-gfm';
6
7
  import Footer from '../../components/Footer';
7
- import { MarkdownComponents } from '../../components/Markdown/MarkdownComponents';
8
+ import { MarkdownComponents, remarkCodeMeta } from '../../components/Markdown/MarkdownComponents';
8
9
  import Header from '../../components/old/Header';
9
10
  import { DocumentationProps, fetchDocPageMarkdown, getClientRuntimeConfig } from '../../components/util';
10
11
 
@@ -57,7 +58,7 @@ const AssetsLogosPage = ({ content, menu, metadata, current, config }: Documenta
57
58
  </div>
58
59
  </div>
59
60
  <div className="prose">
60
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
61
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
61
62
  {content}
62
63
  </ReactMarkdown>
63
64
  </div>
@@ -3,10 +3,11 @@ import upperFirst from 'lodash/upperFirst';
3
3
  import type { GetStaticProps } from 'next';
4
4
  import ReactMarkdown from 'react-markdown';
5
5
  import rehypeRaw from 'rehype-raw';
6
+ import remarkGfm from 'remark-gfm';
6
7
  import ColorGrid from '../..//components/Foundations/ColorGrid';
7
8
  import { DownloadTokens } from '../../components/DownloadTokens';
8
9
  import Layout from '../../components/Layout/Main';
9
- import { MarkdownComponents } from '../../components/Markdown/MarkdownComponents';
10
+ import { MarkdownComponents, remarkCodeMeta } from '../../components/Markdown/MarkdownComponents';
10
11
  import AnchorNav from '../../components/Navigation/AnchorNav';
11
12
  import PrevNextNav from '../../components/Navigation/PrevNextNav';
12
13
  import HeadersType from '../../components/Typography/Headers';
@@ -89,7 +90,7 @@ const ColorsPage = ({
89
90
  />
90
91
 
91
92
  <div className="prose">
92
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
93
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
93
94
  {content}
94
95
  </ReactMarkdown>
95
96
  </div>
@@ -6,9 +6,10 @@ import type { GetStaticProps } from 'next';
6
6
  import * as React from 'react';
7
7
  import ReactMarkdown from 'react-markdown';
8
8
  import rehypeRaw from 'rehype-raw';
9
+ import remarkGfm from 'remark-gfm';
9
10
  import { DownloadTokens } from '../../components/DownloadTokens';
10
11
  import Layout from '../../components/Layout/Main';
11
- import { MarkdownComponents } from '../../components/Markdown/MarkdownComponents';
12
+ import { MarkdownComponents, remarkCodeMeta } from '../../components/Markdown/MarkdownComponents';
12
13
  import AnchorNav from '../../components/Navigation/AnchorNav';
13
14
  import HeadersType from '../../components/Typography/Headers';
14
15
  import * as util from '../../components/util';
@@ -128,7 +129,7 @@ const EffectsPage = ({
128
129
  />
129
130
 
130
131
  <div className="prose">
131
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
132
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
132
133
  {content}
133
134
  </ReactMarkdown>
134
135
  </div>
@@ -5,9 +5,10 @@ import Link from 'next/link';
5
5
  import * as React from 'react';
6
6
  import ReactMarkdown from 'react-markdown';
7
7
  import rehypeRaw from 'rehype-raw';
8
+ import remarkGfm from 'remark-gfm';
8
9
  import Footer from '../../../components/Footer';
9
10
  import Layout from '../../../components/Layout/Main';
10
- import { MarkdownComponents } from '../../../components/Markdown/MarkdownComponents';
11
+ import { MarkdownComponents, remarkCodeMeta } from '../../../components/Markdown/MarkdownComponents';
11
12
  import HeadersType from '../../../components/Typography/Headers';
12
13
  import { buttonVariants } from '../../../components/ui/button';
13
14
  import { Input } from '../../../components/ui/input';
@@ -99,7 +100,7 @@ const IconsPage = ({ content, menu, metadata, current, config, assets }: AssetDo
99
100
  </div>
100
101
  <hr className="mb-10" />
101
102
  <div className="prose">
102
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
103
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
103
104
  {content}
104
105
  </ReactMarkdown>
105
106
  </div>
@@ -2,9 +2,10 @@ import { Hexagon, LayoutPanelLeft, Palette, Shapes, Sun, TypeOutline } from 'luc
2
2
  import type { GetStaticProps } from 'next';
3
3
  import ReactMarkdown from 'react-markdown';
4
4
  import rehypeRaw from 'rehype-raw';
5
+ import remarkGfm from 'remark-gfm';
5
6
  import CardsWithIcons from '../../components/cards/CardsWithIcons';
6
7
  import Layout from '../../components/Layout/Main';
7
- import { MarkdownComponents } from '../../components/Markdown/MarkdownComponents';
8
+ import { MarkdownComponents, remarkCodeMeta } from '../../components/Markdown/MarkdownComponents';
8
9
  import HeadersType from '../../components/Typography/Headers';
9
10
  import { DocumentationProps, fetchDocPageMarkdown, getClientRuntimeConfig } from '../../components/util';
10
11
 
@@ -33,7 +34,7 @@ const DesignPage = ({ content, menu, metadata, current, config }: DocumentationP
33
34
  <p className="text-lg leading-relaxed text-gray-600 dark:text-gray-300">{metadata.description}</p>
34
35
  </div>
35
36
  <div className="prose">
36
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
37
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
37
38
  {content}
38
39
  </ReactMarkdown>
39
40
  </div>
@@ -6,9 +6,10 @@ import { AssetDocumentationProps, fetchDocPageMarkdown, getClientRuntimeConfig,
6
6
  import { Download } from 'lucide-react';
7
7
  import ReactMarkdown from 'react-markdown';
8
8
  import rehypeRaw from 'rehype-raw';
9
+ import remarkGfm from 'remark-gfm';
9
10
  import { DisplayLogo } from '../../components/Foundations/DisplayLogo';
10
11
  import Layout from '../../components/Layout/Main';
11
- import { MarkdownComponents } from '../../components/Markdown/MarkdownComponents';
12
+ import { MarkdownComponents, remarkCodeMeta } from '../../components/Markdown/MarkdownComponents';
12
13
  import HeadersType from '../../components/Typography/Headers';
13
14
  import { buttonVariants } from '../../components/ui/button';
14
15
 
@@ -73,7 +74,7 @@ const LogoPage = ({ content, menu, metadata, current, config, assets }: AssetDoc
73
74
  <hr />
74
75
  <hr />
75
76
  <div className="prose">
76
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
77
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
77
78
  {content}
78
79
  </ReactMarkdown>
79
80
  </div>
@@ -3,10 +3,11 @@ import sortedUniq from 'lodash/sortedUniq';
3
3
  import type * as next from 'next';
4
4
  import ReactMarkdown from 'react-markdown';
5
5
  import rehypeRaw from 'rehype-raw';
6
+ import remarkGfm from 'remark-gfm';
6
7
  import { DownloadTokens } from '../../components/DownloadTokens';
7
8
  import TypographyExamples from '../../components/Foundations/TypographyExample';
8
9
  import Layout from '../../components/Layout/Main';
9
- import { MarkdownComponents } from '../../components/Markdown/MarkdownComponents';
10
+ import { MarkdownComponents, remarkCodeMeta } from '../../components/Markdown/MarkdownComponents';
10
11
  import AnchorNav, { anchorSlugify } from '../../components/Navigation/AnchorNav';
11
12
  import HeadersType from '../../components/Typography/Headers';
12
13
  import { fetchFoundationDocPageMarkdown, FoundationDocumentationProps, getClientRuntimeConfig, getTokens } from '../../components/util';
@@ -124,7 +125,7 @@ const Typography = ({
124
125
  />
125
126
 
126
127
  <div className="prose">
127
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
128
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
128
129
  {content}
129
130
  </ReactMarkdown>
130
131
  </div>
@@ -5,9 +5,10 @@ import Link from 'next/link';
5
5
  import { useRouter } from 'next/router';
6
6
  import ReactMarkdown from 'react-markdown';
7
7
  import rehypeRaw from 'rehype-raw';
8
+ import remarkGfm from 'remark-gfm';
8
9
  import Footer from '../components/Footer';
9
10
  import Layout from '../components/Layout/Main';
10
- import { MarkdownComponents } from '../components/Markdown/MarkdownComponents';
11
+ import { MarkdownComponents, remarkCodeMeta } from '../components/Markdown/MarkdownComponents';
11
12
  import HeadersType from '../components/Typography/Headers';
12
13
  import { Button } from '../components/ui/button';
13
14
  import { DocumentationProps, fetchDocPageMarkdown, getClientRuntimeConfig } from '../components/util';
@@ -41,7 +42,7 @@ const Home = ({ content, menu, metadata, config, current }: DocumentationProps)
41
42
  </HeadersType.H1>
42
43
  {content && (
43
44
  <div className="text-lg max-w-4xl empty:hidden [&>*]:mt-6 [&>p]:font-light [&>h2:first-child]:mt-0 [&>h2:first-child]:font-normal sm:text-xl">
44
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
45
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
45
46
  {content}
46
47
  </ReactMarkdown>
47
48
  </div>
@@ -6,11 +6,12 @@ import { startCase } from 'lodash';
6
6
  import React, { useEffect, useState } from 'react';
7
7
  import ReactMarkdown from 'react-markdown';
8
8
  import rehypeRaw from 'rehype-raw';
9
+ import remarkGfm from 'remark-gfm';
9
10
  import { ComponentPreview } from '../../../../components/Component/Preview';
10
11
  import { HotReloadProvider } from '../../../../components/context/HotReloadProvider';
11
12
  import { PreviewContextProvider } from '../../../../components/context/PreviewContext';
12
13
  import Layout from '../../../../components/Layout/Main';
13
- import { MarkdownComponents } from '../../../../components/Markdown/MarkdownComponents';
14
+ import { MarkdownComponents, remarkCodeMeta } from '../../../../components/Markdown/MarkdownComponents';
14
15
  import AnchorNav from '../../../../components/Navigation/AnchorNav';
15
16
  import PrevNextNav from '../../../../components/Navigation/PrevNextNav';
16
17
  import HeadersType from '../../../../components/Typography/Headers';
@@ -172,7 +173,7 @@ const GenericComponentPage = ({ menu, metadata, current, id, config, componentHo
172
173
  <HeadersType.H1>{metadata.title}</HeadersType.H1>
173
174
  <div className="flex flex-row justify-between gap-4 md:flex-col">
174
175
  <div className="prose max-w-[800px] text-xl font-light leading-relaxed text-gray-600 dark:text-gray-300">
175
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
176
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
176
177
  {metadata.description}
177
178
  </ReactMarkdown>
178
179
  </div>
@@ -5,9 +5,10 @@ import Image from 'next/image';
5
5
  import { useEffect, useState } from 'react';
6
6
  import ReactMarkdown from 'react-markdown';
7
7
  import rehypeRaw from 'rehype-raw';
8
+ import remarkGfm from 'remark-gfm';
8
9
  import { APIComponentList } from '../../components/Component/ComponentLists';
9
10
  import Layout from '../../components/Layout/Main';
10
- import { MarkdownComponents } from '../../components/Markdown/MarkdownComponents';
11
+ import { MarkdownComponents, remarkCodeMeta } from '../../components/Markdown/MarkdownComponents';
11
12
  import HeadersType from '../../components/Typography/Headers';
12
13
  import { Button } from '../../components/ui/button';
13
14
  import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from '../../components/ui/drawer';
@@ -127,7 +128,7 @@ const ComponentsPage = ({ content, menu, metadata, current, config }: ComponentP
127
128
  </div>
128
129
  <div>
129
130
  <div className="prose mb-10">
130
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
131
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
131
132
  {content}
132
133
  </ReactMarkdown>
133
134
  </div>
@@ -3,8 +3,9 @@ import { groupBy, upperFirst } from 'lodash';
3
3
  import type { GetStaticProps } from 'next';
4
4
  import ReactMarkdown from 'react-markdown';
5
5
  import rehypeRaw from 'rehype-raw';
6
+ import remarkGfm from 'remark-gfm';
6
7
  import Layout from '../../../../components/Layout/Main';
7
- import { MarkdownComponents } from '../../../../components/Markdown/MarkdownComponents';
8
+ import { MarkdownComponents, remarkCodeMeta } from '../../../../components/Markdown/MarkdownComponents';
8
9
  import AnchorNav from '../../../../components/Navigation/AnchorNav';
9
10
  import HeadersType from '../../../../components/Typography/Headers';
10
11
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../../../../components/ui/table';
@@ -67,7 +68,7 @@ const ComponentsPage = ({ content, menu, metadata, current, config, design }: Fo
67
68
  <div className="mt-10 lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_280px]">
68
69
  <div>
69
70
  <div className="prose">
70
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
71
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
71
72
  {content}
72
73
  </ReactMarkdown>
73
74
  </div>
@@ -3,8 +3,9 @@ import { groupBy, upperFirst } from 'lodash';
3
3
  import type { GetStaticProps } from 'next';
4
4
  import ReactMarkdown from 'react-markdown';
5
5
  import rehypeRaw from 'rehype-raw';
6
+ import remarkGfm from 'remark-gfm';
6
7
  import Layout from '../../../../components/Layout/Main';
7
- import { MarkdownComponents } from '../../../../components/Markdown/MarkdownComponents';
8
+ import { MarkdownComponents, remarkCodeMeta } from '../../../../components/Markdown/MarkdownComponents';
8
9
  import AnchorNav from '../../../../components/Navigation/AnchorNav';
9
10
  import HeadersType from '../../../../components/Typography/Headers';
10
11
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../../../../components/ui/table';
@@ -61,7 +62,7 @@ const ComponentsPage = ({ content, menu, metadata, current, config, design }: Fo
61
62
  <div className="mt-10 lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_280px]">
62
63
  <div>
63
64
  <div className="prose">
64
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
65
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
65
66
  {content}
66
67
  </ReactMarkdown>
67
68
  </div>
@@ -4,8 +4,9 @@ import type { GetStaticProps } from 'next';
4
4
  import React, { ReactElement } from 'react';
5
5
  import ReactMarkdown from 'react-markdown';
6
6
  import rehypeRaw from 'rehype-raw';
7
+ import remarkGfm from 'remark-gfm';
7
8
  import Layout from '../../../../components/Layout/Main';
8
- import { MarkdownComponents } from '../../../../components/Markdown/MarkdownComponents';
9
+ import { MarkdownComponents, remarkCodeMeta } from '../../../../components/Markdown/MarkdownComponents';
9
10
  import AnchorNav from '../../../../components/Navigation/AnchorNav';
10
11
  import HeadersType from '../../../../components/Typography/Headers';
11
12
  import { Table, TableBody, TableCell, TableRow } from '../../../../components/ui/table';
@@ -53,7 +54,7 @@ const ComponentsPage = ({ content, menu, metadata, current, config, design }: Fo
53
54
  <div className="mt-10 lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_280px]">
54
55
  <div>
55
56
  <div className="prose">
56
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
57
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
57
58
  {content}
58
59
  </ReactMarkdown>
59
60
  </div>
package/src/cli.ts CHANGED
@@ -19,7 +19,7 @@ class HandoffCliError extends Error {
19
19
  * Show the help message
20
20
  */
21
21
  const showVersion = () => {
22
- return 'Handoff App - 1.1.2';
22
+ return 'Handoff App - 1.1.3';
23
23
  };
24
24
 
25
25
  /**
@@ -1,104 +0,0 @@
1
- import { GetStaticProps } from 'next';
2
- import Head from 'next/head';
3
- import Link from 'next/link';
4
- import ReactMarkdown from 'react-markdown';
5
- import rehypeRaw from 'rehype-raw';
6
- import Layout from '../../../components/Layout/Main';
7
- import { MarkdownComponents } from '../../../components/Markdown/MarkdownComponents';
8
- import HeadersType from '../../../components/Typography/Headers';
9
- import {
10
- buildL2StaticPaths,
11
- DocumentationProps,
12
- fetchDocPageMarkdown,
13
- getClientRuntimeConfig,
14
- IParams,
15
- reduceSlugToString,
16
- } from '../../../components/util';
17
-
18
- export interface SubPageType {
19
- params: {
20
- level1: string;
21
- level2: string;
22
- };
23
- }
24
- /**
25
- * Render all index pages
26
- * @returns
27
- */
28
- export async function getStaticPaths() {
29
- return {
30
- paths: buildL2StaticPaths(),
31
- fallback: false,
32
- };
33
- }
34
- /**
35
- * Get all the markdown data, build a menu tree, and then fetch the contents
36
- * of the current page for rendering
37
- *
38
- * This is all done statically at build time
39
- * @param context GetStaticProps
40
- * @returns
41
- */
42
- export const getStaticProps: GetStaticProps = async (context) => {
43
- // Read current slug
44
- const { level1 } = context.params as IParams;
45
- let { level2 } = context.params as IParams;
46
- if (!level2) {
47
- level2 = '404';
48
- }
49
- return {
50
- props: {
51
- ...fetchDocPageMarkdown(`docs/${level1}/`, reduceSlugToString(level2), `/${level1}`).props,
52
- config: getClientRuntimeConfig(),
53
- },
54
- };
55
- };
56
-
57
- /**
58
- * Render Docs page
59
- * @param param0
60
- * @returns
61
- */
62
- export default function DocSubPage({ content, menu, metadata, current, config }: DocumentationProps) {
63
- if (content) {
64
- return (
65
- <Layout config={config} menu={menu} current={current} metadata={metadata}>
66
- <div className="flex flex-col gap-2 pb-7">
67
- <HeadersType.H1>{metadata.title}</HeadersType.H1>
68
- <p className="text-lg leading-relaxed text-gray-600 dark:text-gray-300">{metadata.description}</p>
69
- </div>
70
- <div>
71
- <div className="prose mb-10">
72
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
73
- {content}
74
- </ReactMarkdown>
75
- </div>
76
- </div>
77
- </Layout>
78
- );
79
- } else {
80
- return (
81
- <div className="flex min-h-screen flex-col items-center justify-center bg-white dark:bg-gray-900">
82
- <Head>
83
- <title>404 - Page Not Found</title>
84
- <meta name="description" content="Page Not Found" />
85
- </Head>
86
- <div className="flex flex-col items-center">
87
- <div className="mb-2 text-7xl font-bold text-gray-800 dark:text-white">404</div>
88
- <h1 className="mb-4 text-2xl font-semibold text-gray-700 dark:text-gray-300">Oops! Page not found.</h1>
89
- <p className="mb-6 max-w-md text-center text-gray-500 dark:text-gray-400">
90
- Sorry, the page you are looking for does not exist or has been moved.
91
- <br />
92
- Please check the URL or return to the homepage.
93
- </p>
94
- <Link
95
- href="/"
96
- className="rounded-md bg-blue-600 px-6 py-2 font-medium text-white shadow-md transition-colors duration-200 hover:bg-blue-700"
97
- >
98
- Go Home
99
- </Link>
100
- </div>
101
- </div>
102
- );
103
- }
104
- }