diff --git a/packages/chronicle/src/components/ui/search.module.css b/packages/chronicle/src/components/ui/search.module.css
index c82985d..66e2d4d 100644
--- a/packages/chronicle/src/components/ui/search.module.css
+++ b/packages/chronicle/src/components/ui/search.module.css
@@ -41,6 +41,12 @@
display: flex;
align-items: center;
gap: 12px;
+ flex: 1;
+}
+
+.sectionBadge {
+ margin-left: auto;
+ flex-shrink: 0;
}
.resultText {
diff --git a/packages/chronicle/src/components/ui/search.tsx b/packages/chronicle/src/components/ui/search.tsx
index 4eee2fc..4c75730 100644
--- a/packages/chronicle/src/components/ui/search.tsx
+++ b/packages/chronicle/src/components/ui/search.tsx
@@ -3,7 +3,7 @@ import {
HashtagIcon,
MagnifyingGlassIcon
} from '@heroicons/react/24/outline';
-import { Command, IconButton, Text } from '@raystack/apsara';
+import { Badge, Command, IconButton, Text } from '@raystack/apsara';
import { debounce } from 'lodash-es';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate } from 'react-router';
@@ -18,6 +18,7 @@ interface SearchResult {
content: string;
match?: 'title' | 'heading' | 'body';
snippet?: string;
+ section?: string;
}
interface SearchProps {
@@ -157,6 +158,7 @@ export function Search({ classNames }: SearchProps) {
html={stripMethod(result.content)}
/>
+ {result.section && {result.section}}
))}
@@ -187,6 +189,7 @@ export function Search({ classNames }: SearchProps) {
)}
+ {result.section && {result.section}}
))}
diff --git a/packages/chronicle/src/server/api/search.ts b/packages/chronicle/src/server/api/search.ts
index d790bec..ac07a74 100644
--- a/packages/chronicle/src/server/api/search.ts
+++ b/packages/chronicle/src/server/api/search.ts
@@ -14,6 +14,7 @@ interface SearchDocument {
headings: string;
body: string;
type: 'page' | 'api';
+ section: string;
}
import fs from 'node:fs/promises';
@@ -61,7 +62,8 @@ async function buildIndex(ctx: VersionContext, key: string) {
headings TEXT NOT NULL,
body TEXT NOT NULL,
type TEXT NOT NULL,
- version TEXT NOT NULL
+ version TEXT NOT NULL,
+ section TEXT NOT NULL DEFAULT ''
)`);
await db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS search_fts USING fts5(
@@ -74,8 +76,8 @@ async function buildIndex(ctx: VersionContext, key: string) {
const docs = await buildDocs(ctx);
for (const doc of docs) {
- await db.sql`INSERT INTO search_docs (id, url, title, headings, body, type, version)
- VALUES (${doc.id}, ${doc.url}, ${doc.title}, ${doc.headings}, ${doc.body}, ${doc.type}, ${key})`;
+ await db.sql`INSERT INTO search_docs (id, url, title, headings, body, type, version, section)
+ VALUES (${doc.id}, ${doc.url}, ${doc.title}, ${doc.headings}, ${doc.body}, ${doc.type}, ${key}, ${doc.section})`;
}
await db.sql`INSERT INTO search_fts (rowid, title, headings, body)
@@ -86,11 +88,15 @@ async function buildIndex(ctx: VersionContext, key: string) {
async function buildDocs(ctx: VersionContext): Promise {
const docs: SearchDocument[] = [];
+ const config = loadConfig();
+ const contentEntries = config.content ?? [];
const pages = await getPagesForVersion(ctx);
for (const p of pages) {
const fm = extractFrontmatter(p);
const { headings, body } = await getPageSearchContent(p);
+ const dir = p.url.replace(/^\//, '').split('/')[0];
+ const entry = contentEntries.find(c => c.dir === dir);
docs.push({
id: p.url,
url: p.url,
@@ -98,10 +104,10 @@ async function buildDocs(ctx: VersionContext): Promise {
headings,
body: [fm.description ?? '', body].join(' '),
type: 'page',
+ section: entry?.label ?? dir ?? '',
});
}
- const config = loadConfig();
const apiConfigs = getApiConfigsForVersion(config, ctx.dir);
if (apiConfigs.length) {
const specs = await loadApiSpecs(apiConfigs);
@@ -122,6 +128,7 @@ async function buildDocs(ctx: VersionContext): Promise {
headings: op.summary ?? opId,
body: [op.description ?? '', pathStr, method.toUpperCase()].join(' '),
type: 'api',
+ section: spec.name,
});
}
}
@@ -183,7 +190,7 @@ export default defineHandler(async event => {
const key = versionKey(ctx);
if (!query) {
- const result = await db.sql`SELECT id, url, title, type FROM search_docs
+ const result = await db.sql`SELECT id, url, title, type, section FROM search_docs
WHERE version = ${key} AND type = 'page'
LIMIT 8`;
return Response.json((result.rows ?? []).map(r => ({
@@ -191,11 +198,12 @@ export default defineHandler(async event => {
url: r.url,
type: r.type,
content: r.title,
+ section: r.section || null,
})));
}
const searchTerm = query.split(/\s+/).map(t => `"${t}"*`).join(' ');
- const result = await db.sql`SELECT s.id, s.url, s.title, s.headings, s.body, s.type,
+ const result = await db.sql`SELECT s.id, s.url, s.title, s.headings, s.body, s.type, s.section,
bm25(search_fts, 10.0, 5.0, 1.0) AS score
FROM search_fts f
JOIN search_docs s ON s.rowid = f.rowid
@@ -214,6 +222,8 @@ export default defineHandler(async event => {
content: r.title,
match,
snippet,
+ section: r.section || null,
};
}));
});
+