Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions packages/chronicle/src/components/ui/search.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}

.sectionBadge {
margin-left: auto;
flex-shrink: 0;
}

.resultText {
Expand Down
5 changes: 4 additions & 1 deletion packages/chronicle/src/components/ui/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -18,6 +18,7 @@ interface SearchResult {
content: string;
match?: 'title' | 'heading' | 'body';
snippet?: string;
section?: string;
}

interface SearchProps {
Expand Down Expand Up @@ -157,6 +158,7 @@ export function Search({ classNames }: SearchProps) {
html={stripMethod(result.content)}
/>
</Text>
{result.section && <Badge size="small" className={styles.sectionBadge}>{result.section}</Badge>}
</div>
</Command.Item>
))}
Expand Down Expand Up @@ -187,6 +189,7 @@ export function Search({ classNames }: SearchProps) {
</Text>
)}
</div>
{result.section && <Badge size="small" className={styles.sectionBadge}>{result.section}</Badge>}
</div>
</Command.Item>
))}
Expand Down
22 changes: 16 additions & 6 deletions packages/chronicle/src/server/api/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface SearchDocument {
headings: string;
body: string;
type: 'page' | 'api';
section: string;
}

import fs from 'node:fs/promises';
Expand Down Expand Up @@ -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(
Expand All @@ -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)
Expand All @@ -86,22 +88,26 @@ async function buildIndex(ctx: VersionContext, key: string) {

async function buildDocs(ctx: VersionContext): Promise<SearchDocument[]> {
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,
title: fm.title,
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);
Expand All @@ -122,6 +128,7 @@ async function buildDocs(ctx: VersionContext): Promise<SearchDocument[]> {
headings: op.summary ?? opId,
body: [op.description ?? '', pathStr, method.toUpperCase()].join(' '),
type: 'api',
section: spec.name,
});
}
}
Expand Down Expand Up @@ -183,19 +190,20 @@ 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 => ({
id: r.id,
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
Expand All @@ -214,6 +222,8 @@ export default defineHandler(async event => {
content: r.title,
match,
snippet,
section: r.section || null,
};
}));
});

Loading