Skip to content

Commit 1840033

Browse files
committed
anchors fix
1 parent c371a52 commit 1840033

File tree

2 files changed

+105
-18
lines changed

2 files changed

+105
-18
lines changed

orama/generate_orama_index_full.ts

Lines changed: 80 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,25 @@ function buildUrl(relativePath: string, baseUrl?: string): string {
230230
return `${BASE_URL}${url}`;
231231
}
232232

233+
/**
234+
* Build path from relative path (without base URL)
235+
*/
236+
function buildPath(relativePath: string, baseUrl?: string): string {
237+
if (baseUrl) {
238+
// For API docs, return relative path with baseUrl
239+
return `${baseUrl}/${relativePath}`;
240+
}
241+
242+
let path = relativePath.replace(/\.(md|mdx)$/, "");
243+
if (path.endsWith("/index")) {
244+
path = path.replace(/\/index$/, "/");
245+
}
246+
if (!path.startsWith("/")) {
247+
path = "/" + path;
248+
}
249+
return path;
250+
}
251+
233252
/**
234253
* Process a single API symbol from reference documentation
235254
*/
@@ -239,11 +258,11 @@ function processApiSymbol(
239258
apiType: string,
240259
baseUrl: string,
241260
packageName: string,
242-
): OramaDocument | null {
261+
): OramaDocument[] {
243262
try {
244263
// Type guard for symbolData
245264
if (!symbolData || typeof symbolData !== "object") {
246-
return null;
265+
return [];
247266
}
248267

249268
const data = symbolData as Record<string, unknown>;
@@ -255,6 +274,9 @@ function processApiSymbol(
255274
const htmlHeadCtx = data.html_head_ctx as
256275
| Record<string, unknown>
257276
| undefined;
277+
const tocCtx = data.toc_ctx as
278+
| Record<string, unknown>
279+
| undefined;
258280

259281
const symbolName = symbolGroupCtx?.name as string ||
260282
symbolPath.split(".").pop() || "Unknown";
@@ -315,7 +337,7 @@ function processApiSymbol(
315337

316338
// Skip if no meaningful content
317339
if (content.length < 20) {
318-
return null;
340+
return [];
319341
}
320342

321343
// Determine symbol type
@@ -348,15 +370,20 @@ function processApiSymbol(
348370
}
349371
}
350372

351-
// Build clean URL
352-
const cleanPath = symbolPath.replace(/^\.\/~\//, "").replace(/\.json$/, "");
373+
// Build clean URL - preserve the ~/ prefix for API paths
374+
const cleanPath = symbolPath.replace(/^\.\//, "").replace(/\.json$/, "");
353375
const url = buildUrl(cleanPath, baseUrl);
376+
const path = buildPath(cleanPath, baseUrl);
354377

355-
return {
378+
const documents: OramaDocument[] = [];
379+
380+
// Create main document for the symbol
381+
documents.push({
356382
id: generateId(cleanPath, apiType),
357383
title: title.replace(" - Deno documentation", ""),
358384
content: content.trim(),
359385
url,
386+
path,
360387
category: `api-${apiType}`,
361388
section: packageName.toLowerCase(),
362389
subsection: symbolType,
@@ -370,10 +397,48 @@ function processApiSymbol(
370397
symbolPath: symbolName,
371398
packageName,
372399
},
373-
};
400+
});
401+
402+
// Process TOC entries to create anchor documents
403+
const documentNavigation = tocCtx?.document_navigation as unknown[] | undefined;
404+
if (documentNavigation && Array.isArray(documentNavigation)) {
405+
for (const navItem of documentNavigation) {
406+
if (navItem && typeof navItem === "object") {
407+
const navObj = navItem as Record<string, unknown>;
408+
const anchor = navObj.anchor as string | undefined;
409+
const navContent = navObj.content as string | undefined;
410+
411+
if (anchor && navContent) {
412+
// Create a document for this anchor
413+
documents.push({
414+
id: generateId(`${cleanPath}-${anchor}`, apiType),
415+
title: `${symbolName}.${navContent}`,
416+
content: `${navContent} ${content.trim()}`,
417+
url: `${url}#${anchor}`,
418+
path: `${path}#${anchor}`,
419+
category: `api-${apiType}`,
420+
section: packageName.toLowerCase(),
421+
subsection: symbolType,
422+
description: `${navContent} - ${symbolType} in the ${packageName} API`,
423+
tags: [...tags, "anchor"],
424+
headings: [navContent],
425+
lastModified: Date.now(),
426+
docType: "api-reference",
427+
apiInfo: {
428+
symbolType,
429+
symbolPath: symbolName,
430+
packageName,
431+
},
432+
});
433+
}
434+
}
435+
}
436+
}
437+
438+
return documents;
374439
} catch (error) {
375440
console.error(`Error processing API symbol ${symbolPath}:`, error);
376-
return null;
441+
return [];
377442
}
378443
}
379444

@@ -409,17 +474,17 @@ async function processReferenceDocumentation(): Promise<OramaDocument[]> {
409474
continue;
410475
}
411476

412-
const doc = processApiSymbol(
477+
const docs = processApiSymbol(
413478
symbolPath,
414479
symbolData,
415480
refFile.apiType,
416481
refFile.baseUrl,
417482
refFile.apiType.charAt(0).toUpperCase() + refFile.apiType.slice(1),
418483
);
419484

420-
if (doc) {
421-
documents.push(doc);
422-
processed++;
485+
if (docs && docs.length > 0) {
486+
documents.push(...docs);
487+
processed += docs.length;
423488
} else {
424489
skipped++;
425490
}
@@ -535,14 +600,16 @@ async function collectMarkdownFiles(): Promise<OramaDocument[]> {
535600
relativePath,
536601
);
537602

538-
// Build URL
603+
// Build URL and path
539604
const url = buildUrl(relativePath);
605+
const path = buildPath(relativePath);
540606

541607
files.push({
542608
id: generateId(relativePath),
543609
title,
544610
content: prefixedContent,
545611
url,
612+
path,
546613
category,
547614
section,
548615
subsection: subsection || undefined,

search.client.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,24 @@ class OramaSearch {
469469
formatUrl(url: string | undefined, hit?: Hit<OramaDocument>): string {
470470
// First try to use the path field if available (this should be a clean relative path)
471471
if (hit && hit.document && hit.document.path) {
472-
const path = hit.document.path;
472+
const path = this.cleanUrl(hit.document.path);
473+
474+
// Check if the original URL has an anchor that we should preserve
475+
if (url && url.includes("#")) {
476+
const anchorMatch = url.match(/#[^#]*$/);
477+
if (anchorMatch) {
478+
const anchor = anchorMatch[0];
479+
// Only add anchor if it's not a "jump-to-heading" artifact
480+
if (
481+
!anchor.includes("jump-to-heading") &&
482+
!anchor.includes("Jump-to-heading")
483+
) {
484+
const cleanPath = path.startsWith("/") ? path : "/" + path;
485+
return cleanPath + anchor;
486+
}
487+
}
488+
}
489+
473490
// If it starts with '/', it's already a root-relative path
474491
if (path.startsWith("/")) {
475492
return path;
@@ -503,11 +520,11 @@ class OramaSearch {
503520
// Clean the URL first
504521
const cleanedUrl = this.cleanUrl(url);
505522

506-
// If it's already a full URL, extract just the path part
523+
// If it's already a full URL, extract just the path part (including anchor)
507524
if (cleanedUrl.startsWith("http://") || cleanedUrl.startsWith("https://")) {
508525
try {
509526
const urlObj = new URL(cleanedUrl);
510-
return urlObj.pathname;
527+
return urlObj.pathname + urlObj.hash;
511528
} catch {
512529
// If URL parsing fails, fall back to original logic
513530
return cleanedUrl;
@@ -530,10 +547,13 @@ class OramaSearch {
530547
.replace(/\s*jump\s+to\s+heading\s*/gi, "")
531548
.replace(/\s*#jump-to-heading\s*/gi, "")
532549
.replace(/\s*-jump-to-heading\s*/gi, "")
550+
.replace(/#jump-to-heading/gi, "")
551+
.replace(/-jump-to-heading/gi, "")
533552
.trim();
534553

535-
// Remove any trailing or leading hashes that might be left
536-
cleaned = cleaned.replace(/^#+|#+$/g, "");
554+
// Remove empty hash fragments or multiple hashes, but preserve legitimate anchors
555+
cleaned = cleaned.replace(/#+$/, ""); // Remove trailing empty hashes
556+
cleaned = cleaned.replace(/^#+/, ""); // Remove leading hashes only if they're standalone
537557

538558
// If the URL became empty or just whitespace, return the original
539559
if (!cleaned || cleaned.trim() === "") {

0 commit comments

Comments
 (0)