Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions src/generators/web/ui/components/SideBar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import SideBar from '@node-core/ui-components/Containers/Sidebar';

import styles from './index.module.css';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style imports should happen after everything afaik

import { relative } from '../../../../../utils/url.mjs';
import { buildSideBarGroups } from '../../utils/sidebar.mjs';

import { title, version, versions, pages } from '#theme/config';

Expand Down Expand Up @@ -36,32 +37,30 @@ export default ({ metadata }) => {
label,
}));

const items = pages.map(([heading, path]) => ({
const items = pages.map(([heading, path, category]) => ({
label: heading,
link:
metadata.path === path
? `${metadata.basename}.html`
: `${relative(path, metadata.path)}.html`,
category,
}));

return (
<SideBar
pathname={`${metadata.basename}.html`}
groups={[{ groupName: 'API Documentation', items }]}
groups={buildSideBarGroups(items)}
onSelect={redirect}
as={props => <a {...props} rel="prefetch" />}
title="Navigation"
>
<div>
<Select
label={`${title} version`}
values={compatibleVersions}
inline={true}
className={styles.select}
placeholder={version}
onChange={redirect}
/>
</div>
<Select
label={`${title} version`}
values={compatibleVersions}
className={styles.select}
placeholder={version}
onChange={redirect}
/>
</SideBar>
);
};
123 changes: 123 additions & 0 deletions src/generators/web/ui/constants.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
* @deprecated This is being exported temporarily during the transition period.
* For a more general solution, category information should be added to pages in
* YAML format, and this array should be removed.
*
* Defines the sidebar navigation groups and their associated page URLs.
* @type {Array<{ groupName: string, items: Array<string> }>}
*/
export const SIDEBAR_GROUPS = [
{
groupName: 'Getting Started',
items: [
'documentation.html',
'synopsis.html',
'cli.html',
'environment_variables.html',
'globals.html',
],
},
{
groupName: 'Module System',
items: [
'modules.html',
'esm.html',
'module.html',
'packages.html',
'typescript.html',
],
},
{
groupName: 'Networking & Protocols',
items: [
'http.html',
'http2.html',
'https.html',
'net.html',
'dns.html',
'dgram.html',
'quic.html',
],
},
{
groupName: 'File System & I/O',
items: [
'fs.html',
'path.html',
'buffer.html',
'stream.html',
'string_decoder.html',
'zlib.html',
'readline.html',
'tty.html',
'zlib_iter.html',
],
},
{
groupName: 'Asynchronous Programming',
items: [
'async_context.html',
'async_hooks.html',
'events.html',
'timers.html',
'webstreams.html',
'stream_iter.html',
],
},
{
groupName: 'Process & Concurrency',
items: [
'process.html',
'child_process.html',
'cluster.html',
'worker_threads.html',
'os.html',
],
},
{
groupName: 'Security & Cryptography',
items: ['crypto.html', 'webcrypto.html', 'permissions.html', 'tls.html'],
},
{
groupName: 'Data & URL Utilities',
items: ['url.html', 'querystring.html', 'punycode.html', 'util.html'],
},
{
groupName: 'Debugging & Diagnostics',
items: [
'debugger.html',
'inspector.html',
'console.html',
'report.html',
'tracing.html',
'diagnostics_channel.html',
'errors.html',
],
},
{
groupName: 'Testing & Assertion',
items: ['test.html', 'assert.html', 'repl.html'],
},
{
groupName: 'Performance & Observability',
items: ['perf_hooks.html', 'v8.html'],
},
{
groupName: 'Runtime & Advanced APIs',
items: [
'vm.html',
'wasi.html',
'sqlite.html',
'single-executable-applications.html',
'intl.html',
],
},
{
groupName: 'Native & Low-level Extensions',
items: ['addons.html', 'n-api.html', 'embedding.html'],
},
{
groupName: 'Legacy & Deprecated',
items: ['deprecations.html', 'domain.html'],
},
];
5 changes: 5 additions & 0 deletions src/generators/web/ui/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,8 @@ main {
}
}
}

/* Override the min-width of the select component used for version selection in the sidebar */
[class*='select'] button[role='combobox'] {
min-width: initial;
}
2 changes: 1 addition & 1 deletion src/generators/web/ui/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ declare module '#theme/config' {
major: number;
}>;
export const editURL: string;
export const pages: Array<[string, string]>;
export const pages: Array<[string, string, string?]>;
export const languageDisplayNameMap: Map<string, string>;
}

Expand Down
66 changes: 66 additions & 0 deletions src/generators/web/ui/utils/__tests__/sidebar.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use strict';

import assert from 'node:assert/strict';
import { describe, it } from 'node:test';

import { buildSideBarGroups } from '../sidebar.mjs';

describe('buildSideBarGroups', () => {
it('groups entries by category and preserves insertion order', () => {
const frontmatter = [
{ label: 'FS', link: '/api/fs.html', category: 'File System' },
{ label: 'HTTP', link: '/api/http.html', category: 'Networking' },
{ label: 'Path', link: '/api/path.html', category: 'File System' },
];

const result = buildSideBarGroups(frontmatter);

assert.deepStrictEqual(result, [
{
groupName: 'File System',
items: [
{ label: 'FS', link: '/api/fs.html' },
{ label: 'Path', link: '/api/path.html' },
],
},
{
groupName: 'Networking',
items: [{ label: 'HTTP', link: '/api/http.html' }],
},
]);
});

it('puts entries without category into an Others group at the end by default', () => {
const frontmatter = [
{ label: 'Buffer', link: '/api/buffer.html', category: 'Binary' },
{ label: 'Unknown', link: '/api/unknown.html' },
{ label: 'Config', link: '/api/config.html', category: '' },
];

const result = buildSideBarGroups(frontmatter);

assert.equal(result.at(-1).groupName, 'Others');
assert.deepStrictEqual(result.at(-1).items, [
{ label: 'Unknown', link: '/api/unknown.html' },
{ label: 'Config', link: '/api/config.html' },
]);
});

it('uses a custom default group name when provided', () => {
const result = buildSideBarGroups(
[{ label: 'Unknown', link: '/api/unknown.html' }],
'General'
);

assert.deepStrictEqual(result, [
{
groupName: 'General',
items: [{ label: 'Unknown', link: '/api/unknown.html' }],
},
]);
});

it('returns an empty array when given no entries', () => {
assert.deepStrictEqual(buildSideBarGroups([]), []);
});
});
59 changes: 59 additions & 0 deletions src/generators/web/ui/utils/sidebar.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { SIDEBAR_GROUPS } from '../constants.mjs';

/**
* @deprecated This is being exported temporarily during the transition period.
* Reverse lookup: filename (e.g. 'fs.html') -> groupName, used as category
* fallback for pages without explicit category in metadata.
*/
export const fileToGroup = new Map(
SIDEBAR_GROUPS.flatMap(({ groupName, items }) =>
items.map(item => [item, groupName])
)
);

/**
* Builds grouped sidebar navigation from categorized page entries.
* Pages without a category are placed under the provided default group.
*
* @param {Array<{ label: string, link: string, category?: string }>} items
* @param {string} [defaultGroupName='Others']
* @returns {Array<{ groupName: string, items: Array<{ label: string, link: string }> }>}
*/
export const buildSideBarGroups = (items, defaultGroupName = 'Others') => {
const groups = new Map();
const others = [];

// Group entries by category while preserving insertion order
for (const { label, link, category } of items) {
const linkFilename = link.split('/').at(-1);

// Skip index pages as they are typically the main entry point for a section
// and may not need to be listed separately in the sidebar.
if (linkFilename === 'index.html') {
continue;
}

const resolvedCategory = category ?? fileToGroup.get(linkFilename);

if (!resolvedCategory) {
others.push({ label, link });
continue;
}

const groupItems = groups.get(resolvedCategory) ?? [];
groupItems.push({ label, link });
groups.set(resolvedCategory, groupItems);
}

// Convert the groups map to an array while preserving the original order of categories
const orderedGroups = [...groups.entries()].map(([groupName, items]) => ({
groupName,
items,
}));

if (others.length > 0) {
orderedGroups.push({ groupName: defaultGroupName, items: others });
}

return orderedGroups;
};
9 changes: 5 additions & 4 deletions src/generators/web/utils/__tests__/config.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const makeEntry = (api, name, path) => ({
data: {
api,
path,
category: api === 'fs' ? 'File System' : undefined,
heading: { depth: 1, data: { name } },
},
});
Expand Down Expand Up @@ -101,7 +102,7 @@ describe('buildVersionEntries', () => {
});

describe('buildPageList', () => {
it('returns sorted [name, path] tuples from input entries', () => {
it('returns sorted [name, path, category] tuples from input entries', () => {
const input = [
makeEntry('http', 'HTTP', '/http'),
makeEntry('fs', 'File System', '/fs'),
Expand All @@ -111,8 +112,8 @@ describe('buildPageList', () => {

assert.equal(result.length, 2);
// Sorted alphabetically by name
assert.deepStrictEqual(result[0], ['File System', '/fs']);
assert.deepStrictEqual(result[1], ['HTTP', '/http']);
assert.deepStrictEqual(result[0], ['File System', '/fs', 'File System']);
assert.deepStrictEqual(result[1], ['HTTP', '/http', undefined]);
});

it('filters out entries whose heading depth is not 1', () => {
Expand All @@ -130,7 +131,7 @@ describe('buildPageList', () => {
const result = buildPageList(input);

assert.equal(result.length, 1);
assert.deepStrictEqual(result[0], ['File System', '/fs']);
assert.deepStrictEqual(result[0], ['File System', '/fs', 'File System']);
});
});

Expand Down
11 changes: 8 additions & 3 deletions src/generators/web/utils/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,16 @@ export function buildVersionEntries(config, pageURLBase) {
* Pre-compute sorted page list for sidebar navigation.
*
* @param {Array<import('../../jsx-ast/utils/buildContent.mjs').JSXContent>} input
* @returns {Array<[string, string]>}
* @returns {Array<[string, string, string?]>}
*/
export function buildPageList(input) {
const headNodes = getSortedHeadNodes(input.map(e => e.data));
return headNodes.map(node => [node.heading.data.name, node.path]);
const headNodes = getSortedHeadNodes(input.map(({ data }) => data));

return headNodes.map(({ path, category, heading }) => [
heading.data.name,
path,
category,
]);
}

/**
Expand Down
Loading