Skip to content

Commit 5e42426

Browse files
committed
orama
1 parent 163ad62 commit 5e42426

File tree

15 files changed

+440
-124
lines changed

15 files changed

+440
-124
lines changed

package-lock.json

Lines changed: 249 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"@node-core/ui-components": "^1.0.1-6cb8b0a0c75c24f5ccc84bb07a1ea9b4b810abd2",
4646
"@orama/orama": "^3.1.6",
4747
"@orama/plugin-data-persistence": "^3.1.6",
48+
"@orama/react-components": "^0.8.0",
4849
"@orama/wc-components": "^0.8.0",
4950
"@radix-ui/react-tabs": "^1.1.12",
5051
"@tailwindcss/postcss": "^4.1.8",
@@ -61,6 +62,7 @@
6162
"hast-util-to-string": "^3.0.1",
6263
"hastscript": "^9.0.1",
6364
"html-minifier-terser": "^7.2.0",
65+
"mustache": "^4.2.0",
6466
"postcss": "^8.5.3",
6567
"postcss-calc": "^10.1.1",
6668
"preact": "^10.26.8",
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// This schema comes from
2+
// https://github.com/oramasearch/orama-ui-components/blob/main/packages/ui-stencil/src/types/index.ts#L4
3+
export const SCHEMA = {
4+
title: 'string',
5+
description: 'string',
6+
};

src/generators/orama-db/index.mjs

Lines changed: 42 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,35 @@
11
'use strict';
22

3-
import { create, insert } from '@orama/orama';
3+
import { create, insertMultiple } from '@orama/orama';
44
import { persistToFile } from '@orama/plugin-data-persistence/server';
55

6-
import { enforceArray } from '../../utils/array.mjs';
6+
import { SCHEMA } from './constants.mjs';
77
import { groupNodesByModule } from '../../utils/generators.mjs';
8-
import { createSectionBuilder } from '../legacy-json/utils/buildSection.mjs';
98

109
/**
11-
* Schema definition for the Orama database
10+
* Builds a hierarchical title chain based on heading depths
11+
*
12+
* @param {ApiDocMetadataEntry[]} headings - All headings sorted by order
13+
* @param {number} currentIndex - Index of current heading
14+
* @returns {string} Hierarchical title
1215
*/
13-
const ORAMA_SCHEMA = {
14-
name: 'string',
15-
type: 'string',
16-
desc: 'string',
17-
stability: 'number',
18-
stabilityText: 'string',
19-
meta: {
20-
changes: 'string[]',
21-
added: 'string[]',
22-
napiVersion: 'string[]',
23-
deprecated: 'string[]',
24-
removed: 'string[]',
25-
},
26-
};
16+
function buildHierarchicalTitle(headings, currentIndex) {
17+
const currentNode = headings[currentIndex];
18+
const titleChain = [currentNode.heading.data.name];
19+
let targetDepth = currentNode.heading.depth - 1;
2720

28-
/**
29-
* Transforms a section into the format expected by Orama
30-
* @param {import('../legacy-json/types.d.ts').ModuleSection} node - The section to transform
31-
*/
32-
function transformSectionForOrama(node) {
33-
return {
34-
name: node.name,
35-
type: node.type,
36-
desc: node.desc,
37-
// Account for duplicate stability nodes
38-
stability: enforceArray(node.stability)[0],
39-
stabilityText: enforceArray(node.stabilityText)[0],
40-
meta: {
41-
changes:
42-
node.meta?.changes?.map(
43-
c => `${enforceArray(c.version).join(', ')}: ${c.description}`
44-
) ?? [],
45-
added: node.meta?.added ?? [],
46-
napiVersion: node.meta?.napiVersion ?? [],
47-
deprecated: node.meta?.deprecated ?? [],
48-
removed: node.meta?.removed ?? [],
49-
},
50-
};
21+
// Walk backwards through preceding headings to build hierarchy
22+
for (let i = currentIndex - 1; i >= 0 && targetDepth > 0; i--) {
23+
const heading = headings[i];
24+
const headingDepth = heading.heading.depth;
25+
26+
if (headingDepth <= targetDepth) {
27+
titleChain.unshift(heading.heading.data.name);
28+
targetDepth = headingDepth - 1;
29+
}
30+
}
31+
32+
return titleChain.join(' > ');
5133
}
5234

5335
/**
@@ -70,36 +52,33 @@ export default {
7052
* @param {Input} input
7153
* @param {Partial<GeneratorOptions>} options
7254
*/
73-
async generate(input, { output, version }) {
55+
async generate(input, { output }) {
7456
if (!input?.length) {
7557
throw new Error('Input data is required and must not be empty');
7658
}
7759

78-
if (!output || !version) {
79-
throw new Error('Output path and version are required');
60+
if (!output) {
61+
throw new Error('Output path is required');
8062
}
8163

82-
const db = create({ schema: ORAMA_SCHEMA });
83-
const buildSection = createSectionBuilder();
84-
const groupedModules = groupNodesByModule(input);
85-
const headNodes = input.filter(node => node.heading?.depth === 1);
86-
87-
// Process each head node and insert into database
88-
headNodes.forEach(headNode => {
89-
const nodes = groupedModules.get(headNode.api);
90-
91-
const section = buildSection(headNode, nodes);
92-
const node = (section.modules || section.globals || section.miscs)[0];
93-
if (!node) return;
64+
const db = create({ schema: SCHEMA });
65+
const apiGroups = groupNodesByModule(input);
9466

95-
const oramaData = transformSectionForOrama(node);
96-
insert(db, oramaData);
97-
});
67+
// Process all API groups and flatten into a single document array
68+
const documents = Array.from(apiGroups.values()).flatMap(headings =>
69+
headings.map((node, index) => {
70+
const hierarchicalTitle = buildHierarchicalTitle(headings, index);
9871

99-
// Generate output filename and persist database
100-
const sanitizedVersion = version.raw.replaceAll('.', '-');
101-
const outputFilename = `${output}/${sanitizedVersion}-orama-db.json`;
72+
return {
73+
title: hierarchicalTitle,
74+
// TODO(@avivkeller): Add `description` key.
75+
path: `${node.api}.html#${node.slug}`,
76+
};
77+
})
78+
);
10279

103-
await persistToFile(db, 'json', outputFilename);
80+
// Insert all documents and persist database
81+
await insertMultiple(db, documents);
82+
await persistToFile(db, 'json', `${output}/orama-db.json`);
10483
},
10584
};

src/generators/web/client/components/NavBar.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import ThemeToggle from '@node-core/ui-components/Common/ThemeToggle';
44
import styles from '@node-core/ui-components/Containers/NavBar/index.module.css';
55
import GitHubIcon from '@node-core/ui-components/Icons/Social/GitHub';
66

7+
import SearchBox from './SearchBox';
78
import { useTheme } from '../hooks/useTheme.mjs';
89

910
const MDXNavBar = () => {
@@ -15,6 +16,7 @@ const MDXNavBar = () => {
1516
sidebarItemTogglerAriaLabel="Toggle navigation menu"
1617
navItems={[]}
1718
>
19+
<SearchBox />
1820
<ThemeToggle
1921
onClick={toggleTheme}
2022
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} theme`}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export const themeConfig = {
2+
colors: {
3+
light: {
4+
'--text-color-primary': 'var(--color-neutral-900)',
5+
'--text-color-accent': 'var(--color-green-600)',
6+
'--background-color-secondary': 'var(--color-neutral-100)',
7+
'--background-color-tertiary': 'var(--color-neutral-300)',
8+
'--border-color-accent': 'var(--color-green-600)',
9+
'--border-color-primary': 'var(--color-neutral-200)',
10+
'--border-color-tertiary': 'var(--color-green-700)',
11+
'--button-background-color-primary': 'var(--color-green-600)',
12+
'--button-background-color-secondary': 'var(--color-white)',
13+
'--button-background-color-secondary-hover': 'var(--color-neutral-100)',
14+
'--button-border-color-secondary': 'var(--color-neutral-300)',
15+
'--button-text-color-secondary': 'var(--color-neutral-900)',
16+
},
17+
dark: {
18+
'--text-color-primary': 'var(--color-neutral-100)',
19+
'--text-color-accent': 'var(--color-green-400)',
20+
'--background-color-secondary': 'var(--color-neutral-950)',
21+
'--background-color-tertiary': 'var(--color-neutral-900)',
22+
'--border-color-accent': 'var(--color-green-400)',
23+
'--border-color-primary': 'var(--color-neutral-900)',
24+
'--border-color-tertiary': 'var(--color-green-300)',
25+
'--button-background-color-primary': 'var(--color-green-400)',
26+
'--button-background-color-secondary': 'var(--color-neutral-950)',
27+
'--button-background-color-secondary-hover': 'var(--color-neutral-900)',
28+
'--button-border-color-secondary': 'var(--color-neutral-900)',
29+
'--button-text-color-secondary': 'var(--color-neutral-200)',
30+
},
31+
},
32+
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { useState, useEffect } from 'react';
2+
import { create, load } from '@orama/orama';
3+
import { OramaSearchButton, OramaSearchBox } from '@orama/react-components';
4+
import { useTheme } from '../../hooks/useTheme.mjs';
5+
import { themeConfig } from './config.mjs';
6+
7+
const MDXSearchBox = () => {
8+
const [colorScheme] = useTheme();
9+
const [client, setClient] = useState(null);
10+
const [isLoading, setIsLoading] = useState(true);
11+
12+
useEffect(() => {
13+
const initializeOrama = async () => {
14+
try {
15+
const db = create({
16+
schema: {},
17+
});
18+
19+
// Set the client immediately so it's available
20+
setClient(db);
21+
22+
// Then fetch and load the data
23+
const response = await fetch('/orama-db.json');
24+
if (response.ok) {
25+
load(db, await response.json());
26+
}
27+
} finally {
28+
setIsLoading(false);
29+
}
30+
};
31+
32+
initializeOrama();
33+
}, []);
34+
35+
return (
36+
<>
37+
<OramaSearchButton
38+
style={{ flexGrow: 1 }}
39+
colorScheme={colorScheme}
40+
themeConfig={themeConfig}
41+
aria-label="Search documentation"
42+
>
43+
Search documentation
44+
</OramaSearchButton>
45+
<OramaSearchBox
46+
disabled={!client || isLoading}
47+
disableChat={true}
48+
clientInstance={client}
49+
colorScheme={colorScheme}
50+
themeConfig={themeConfig}
51+
/>
52+
</>
53+
);
54+
};
55+
56+
export default MDXSearchBox;

src/generators/web/client/hooks/useTheme.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
44
* This hook provides theme management functionality
55
*/
66
export const useTheme = () => {
7+
/** @type {['light' | 'dark', Function]} */
78
const [theme, setTheme] = useState('light');
89

910
useEffect(() => {

src/generators/web/constants.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ export const ESBUILD_RESOLVE_DIR = fileURLToPath(
44
new URL('./client', import.meta.url)
55
);
66

7+
export const POLYFILL_PATH = fileURLToPath(
8+
new URL('./server/polyfill.mjs', import.meta.url)
9+
);
10+
711
export const TEMPLATE_PLACEHOLDERS = {
812
TITLE: '__TITLE__',
913
DEHYDRATED: '__DEHYDRATED__',

src/generators/web/index.mjs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { join } from 'node:path';
44

55
import { estreeToBabel } from 'estree-to-babel';
66
import { minify } from 'html-minifier-terser';
7+
import Mustache from 'mustache';
78

8-
import { ESBUILD_RESOLVE_DIR, TEMPLATE_PLACEHOLDERS } from './constants.mjs';
9+
import { ESBUILD_RESOLVE_DIR, POLYFILL_PATH } from './constants.mjs';
910
import { TERSER_MINIFY_OPTIONS } from '../../constants.mjs';
1011
import createASTBuilder from './utils/astBuilder.mjs';
1112
import bundleCode from './utils/bundle.mjs';
@@ -19,6 +20,10 @@ import bundleCode from './utils/bundle.mjs';
1920
async function executeServerCode(serverCode, require) {
2021
const { js: bundledServer } = await bundleCode(serverCode, {
2122
platform: 'node',
23+
alias: {
24+
// These can ONLY be loaded on the client
25+
'@orama/react-components': POLYFILL_PATH,
26+
},
2227
});
2328

2429
const executedFunction = new Function(
@@ -62,10 +67,11 @@ async function processEntry(
6267

6368
// Render the final HTML using the template
6469
const finalHTML = await minify(
65-
template
66-
.replace(TEMPLATE_PLACEHOLDERS.TITLE, entry.data.heading.data.name)
67-
.replace(TEMPLATE_PLACEHOLDERS.DEHYDRATED, serverRenderedHTML)
68-
.replace(TEMPLATE_PLACEHOLDERS.JAVASCRIPT, clientBundle.js),
70+
Mustache.render(template, {
71+
title: entry.data.heading.data.name,
72+
dehydrated: serverRenderedHTML,
73+
javascript: clientBundle.js,
74+
}),
6975
TERSER_MINIFY_OPTIONS
7076
);
7177

0 commit comments

Comments
 (0)