blob: 5ef4673d4ae55adeb5edc05fa0dd99cdfb8fd408 [file]
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import {
type AdvancedIndex,
type AdvancedOptions,
createI18nSearchAPI,
type SearchAPI,
createSearchAPI
} from "fumadocs-core/search/server";
import { PathUtils } from "fumadocs-core/source";
import type { Language } from "@orama/orama";
import type { LoaderConfig, LoaderOutput, Page } from "fumadocs-core/source";
import type { I18nConfig } from "fumadocs-core/i18n";
import { findPath } from "fumadocs-core/page-tree";
import type { StructuredData } from "fumadocs-core/mdx-plugins";
type Awaitable<T> = T | Promise<T>;
function defaultBuildIndex<C extends LoaderConfig>(
source: LoaderOutput<C>,
tag?: (pageUrl: string) => string
) {
function isBreadcrumbItem(item: unknown): item is string {
return typeof item === "string" && item.length > 0;
}
return async (page: Page): Promise<AdvancedIndex> => {
let breadcrumbs: string[] | undefined;
let structuredData: StructuredData | undefined;
if ("structuredData" in page.data) {
structuredData = page.data.structuredData as StructuredData;
} else if ("load" in page.data && typeof page.data.load === "function") {
structuredData = (await page.data.load()).structuredData;
}
if (!structuredData)
throw new Error(
"Cannot find structured data from page, please define the page to index function."
);
const pageTree = source.getPageTree(page.locale);
const path = findPath(
pageTree.children,
(node) => node.type === "page" && node.url === page.url
);
if (path) {
breadcrumbs = [];
path.pop();
if (isBreadcrumbItem(pageTree.name)) {
breadcrumbs.push(pageTree.name);
}
for (const segment of path) {
if (!isBreadcrumbItem(segment.name)) continue;
breadcrumbs.push(segment.name);
}
}
return {
title: page.data.title ?? PathUtils.basename(page.path, PathUtils.extname(page.path)),
breadcrumbs,
description: page.data.description,
url: page.url,
id: page.url,
structuredData,
tag: tag?.(page.url)
};
};
}
interface Options<C extends LoaderConfig> extends Omit<AdvancedOptions, "indexes"> {
localeMap?: {
[K in C["i18n"] extends I18nConfig<infer Languages> ? Languages : string]?:
| Partial<AdvancedOptions>
| Language;
};
buildIndex?: (page: Page<C["source"]["pageData"]>) => Awaitable<AdvancedIndex>;
tag?: (pageUrl: string) => string;
}
export function createFromSource<C extends LoaderConfig>(
source: LoaderOutput<C>,
options?: Options<C>
): SearchAPI;
export function createFromSource<C extends LoaderConfig>(
source: LoaderOutput<C>,
options: Options<C> = {}
): SearchAPI {
const { buildIndex = defaultBuildIndex(source, options.tag) } = options;
if (source._i18n) {
return createI18nSearchAPI("advanced", {
...options,
i18n: source._i18n,
indexes: async () => {
const indexes = source.getLanguages().flatMap((entry) => {
return entry.pages.map(async (page) => ({
...(await buildIndex(page)),
locale: entry.language
}));
});
return Promise.all(indexes);
}
});
}
return createSearchAPI("advanced", {
...options,
indexes: async () => {
const indexes = source.getPages().map((page) => buildIndex(page));
return Promise.all(indexes);
}
});
}