diff --git a/packages/@kamado-io/page-compiler/src/index.ts b/packages/@kamado-io/page-compiler/src/index.ts index 5527e19..cc1cb82 100644 --- a/packages/@kamado-io/page-compiler/src/index.ts +++ b/packages/@kamado-io/page-compiler/src/index.ts @@ -290,7 +290,7 @@ export const pageCompiler = createCompiler(() => ({ const pageContent = await file.get(cache); const { metaData, content: pageMainContent } = pageContent; - const breadcrumbs = await getBreadcrumbs(file, globalData?.allPages ?? [], { + const breadcrumbs = await getBreadcrumbs(file, globalData?.pageList ?? [], { baseURL: config.pkg.production?.baseURL, optimizeTitle: options?.optimizeTitle, }); diff --git a/packages/kamado/README.ja.md b/packages/kamado/README.ja.md index c1caa1b..a19f682 100644 --- a/packages/kamado/README.ja.md +++ b/packages/kamado/README.ja.md @@ -227,6 +227,41 @@ scriptCompiler({ }); ``` +#### ページリスト設定 + +`pageList`オプションを使用すると、ナビゲーション、パンくずリスト、その他ページリストを必要とする機能で使用されるページリストをカスタマイズできます。 + +```ts +import { urlToFile, getFile } from 'kamado/files'; + +export const config: UserConfig = { + // ... その他の設定 + pageList: async (pageAssetFiles, config) => { + // ページをフィルタリング(例: 下書きを除外) + const filtered = pageAssetFiles.filter((page) => !page.url.includes('/drafts/')); + + // カスタムタイトル付きの外部ページを追加 + const externalPage = { + ...urlToFile('/external-page/', { + inputDir: config.dir.input, + outputDir: config.dir.output, + outputExtension: '.html', + }), + title: '外部ページのタイトル', + }; + + return [...filtered, externalPage]; + }, +}; +``` + +この関数は以下を受け取ります: + +- `pageAssetFiles`: ファイルシステムで見つかったすべてのページファイルの配列 +- `config`: 完全な設定オブジェクト + +`CompilableFile`オブジェクトの配列を返します。オプションで`title`プロパティを含めることができます。`title`が指定された場合、ページコンテンツからタイトルを抽出する代わりにその値が使用されます。 + #### フック関数 - `onBeforeBuild`: ビルド前に実行される関数 diff --git a/packages/kamado/README.md b/packages/kamado/README.md index 10fb6f3..801f539 100644 --- a/packages/kamado/README.md +++ b/packages/kamado/README.md @@ -227,6 +227,41 @@ scriptCompiler({ }); ``` +#### Page List Configuration + +The `pageList` option allows you to customize the page list used for navigation, breadcrumbs, and other features that require a list of pages. + +```ts +import { urlToFile, getFile } from 'kamado/files'; + +export const config: UserConfig = { + // ... other config + pageList: async (pageAssetFiles, config) => { + // Filter pages (e.g., exclude drafts) + const filtered = pageAssetFiles.filter((page) => !page.url.includes('/drafts/')); + + // Add external pages with custom titles + const externalPage = { + ...urlToFile('/external-page/', { + inputDir: config.dir.input, + outputDir: config.dir.output, + outputExtension: '.html', + }), + title: 'External Page Title', + }; + + return [...filtered, externalPage]; + }, +}; +``` + +The function receives: + +- `pageAssetFiles`: Array of all page files found in the file system +- `config`: The full configuration object + +Returns an array of `CompilableFile` objects, optionally with a `title` property. If `title` is provided, it will be used instead of extracting from the page content. + #### Hook Functions - `onBeforeBuild`: Function executed before build diff --git a/packages/kamado/src/config/merge.ts b/packages/kamado/src/config/merge.ts index d5754fd..d2fb72b 100644 --- a/packages/kamado/src/config/merge.ts +++ b/packages/kamado/src/config/merge.ts @@ -33,6 +33,7 @@ export async function mergeConfig( host: 'localhost', ...config.devServer, }, + pageList: config.pageList, compilers: config.compilers ?? [], onBeforeBuild: config.onBeforeBuild, onAfterBuild: config.onAfterBuild, diff --git a/packages/kamado/src/config/types.ts b/packages/kamado/src/config/types.ts index 79de9f2..9d91074 100644 --- a/packages/kamado/src/config/types.ts +++ b/packages/kamado/src/config/types.ts @@ -1,4 +1,5 @@ import type { CompilerWithMetadata } from '../compiler/index.js'; +import type { CompilableFile } from '../files/types.js'; /** * Application configuration @@ -16,6 +17,18 @@ export interface Config { * Development server configuration */ readonly devServer: DevServerConfig; + /** + * Function to filter or transform the page list + * @param pageAssetFiles - Page asset files + * @param config - Configuration object + * @returns Filtered or transformed page list with optional title + */ + readonly pageList?: ( + pageAssetFiles: readonly CompilableFile[], + config: Config, + ) => + | (CompilableFile & { title?: string })[] + | Promise<(CompilableFile & { title?: string })[]>; /** * Compiler configuration (array to guarantee processing order) */ diff --git a/packages/kamado/src/data/global.spec.ts b/packages/kamado/src/data/global.spec.ts index 7fa13a2..16dadc6 100644 --- a/packages/kamado/src/data/global.spec.ts +++ b/packages/kamado/src/data/global.spec.ts @@ -73,7 +73,7 @@ describe('getAssetGroup with virtual file system', async () => { expect(globalData.data2.name).toBe('John'); expect( - globalData.allPages.map((page) => { + globalData.pageAssetFiles.map((page) => { // @ts-ignore delete page.date; // @ts-ignore diff --git a/packages/kamado/src/data/global.ts b/packages/kamado/src/data/global.ts index 5d5c2d5..7dd4c3f 100644 --- a/packages/kamado/src/data/global.ts +++ b/packages/kamado/src/data/global.ts @@ -54,11 +54,11 @@ export interface GlobalData { readonly [key: string]: any; }; /** - * List of all page files + * List of all page asset files (from local file system) */ - readonly allPages: CompilableFile[]; + readonly pageAssetFiles: CompilableFile[]; /** - * List of pages with titles + * List of pages with titles (from user-defined page list) */ readonly pageList: (CompilableFile & { title: string })[]; /** @@ -101,7 +101,7 @@ export async function getGlobalData(dir: string, config: Config): Promise entry.outputExtension === '.html', ); - const allPages = pageCompilerEntry + const pageAssetFiles = pageCompilerEntry ? await getAssetGroup({ inputDir: config.dir.input, outputDir: config.dir.output, @@ -109,17 +109,22 @@ export async function getGlobalData(dir: string, config: Config): Promise ({ + userDefinedPageList.map(async (page) => ({ ...page, - title: (await getTitle(page)) || '__NO_TITLE__', + title: + page.title?.trim() || (await getTitle(page, undefined, true)) || '__NO_TITLE__', })), ); return { pkg: config.pkg as unknown as GlobalData['pkg'], ...data, - allPages, + pageAssetFiles, pageList, filters: { date: (date: dayjs.ConfigType, format: string) => dayjs(date).format(format), diff --git a/packages/kamado/src/features/breadcrumbs.spec.ts b/packages/kamado/src/features/breadcrumbs.spec.ts new file mode 100644 index 0000000..ecb4017 --- /dev/null +++ b/packages/kamado/src/features/breadcrumbs.spec.ts @@ -0,0 +1,27 @@ +import { describe, test, expect } from 'vitest'; + +import { getFile } from '../files/file.js'; + +import { getBreadcrumbs } from './breadcrumbs.js'; + +describe('getAssetGroup with virtual file system', () => { + test('use compiler', async () => { + const page = { + ...getFile('/mock/input/dir/path/to/index.html', { + inputDir: '/mock/input/dir', + outputDir: '/mock/output/dir', + outputExtension: '.html', + }), + title: 'Page Title', + }; + const breadcrumbs = await getBreadcrumbs(page, [page]); + + expect(breadcrumbs).toStrictEqual([ + { + title: 'Page Title', + href: '/path/to/', + depth: 2, + }, + ]); + }); +}); diff --git a/packages/kamado/src/features/breadcrumbs.ts b/packages/kamado/src/features/breadcrumbs.ts index 344e6ef..0f7ce82 100644 --- a/packages/kamado/src/features/breadcrumbs.ts +++ b/packages/kamado/src/features/breadcrumbs.ts @@ -40,31 +40,34 @@ export type GetBreadcrumbsOptions = { /** * Gets breadcrumb list for a page * @param page - Target page file - * @param allPages - List of all page files + * @param pageList - List of all page files * @param options - Options for getting breadcrumbs * @returns Array of breadcrumb items * @example * ```typescript - * const breadcrumbs = await getBreadcrumbs(currentPage, allPages, { + * const breadcrumbs = await getBreadcrumbs(currentPage, pageList, { * baseURL: '/', * optimizeTitle: (title) => title.trim(), * }); * ``` */ export async function getBreadcrumbs( - page: CompilableFile, - allPages: readonly CompilableFile[], + page: CompilableFile & { title?: string }, + pageList: readonly (CompilableFile & { title?: string })[], options?: GetBreadcrumbsOptions, ): Promise { const baseURL = options?.baseURL ?? '/'; const optimizeTitle = options?.optimizeTitle; const baseDepth = baseURL.split('/').filter(Boolean).length; - const pages = allPages.filter((item) => + const pages = pageList.filter((item) => isAncestor(page.filePathStem, item.filePathStem), ); const breadcrumbs = await Promise.all( pages.map(async (item) => ({ - title: await getTitle(item, optimizeTitle), + title: + item.title?.trim() || + (await getTitle(item, optimizeTitle, true)) || + '__NO_TITLE__', href: item.url, depth: item.url.split('/').filter(Boolean).length, })), diff --git a/packages/kamado/src/features/title.ts b/packages/kamado/src/features/title.ts index 2abbac1..7cfc13d 100644 --- a/packages/kamado/src/features/title.ts +++ b/packages/kamado/src/features/title.ts @@ -8,17 +8,27 @@ const titleCache = new Map(); * Gets page title * @param page - Page file * @param optimizeTitle - Function to optimize title (optional) - * @returns Page title (from metadata.title, HTML tag, or file slug as fallback) + * @param safe - Whether to return an empty string if the page content is not found + * @returns Page title (from metadata.title, HTML <title> tag, or file slug as fallback) or empty string if safe is true */ export async function getTitle( page: CompilableFile, optimizeTitle?: (title: string) => string, + safe?: boolean, ) { const filePathStem = page.filePathStem; if (titleCache.has(filePathStem)) { return titleCache.get(filePathStem); } - const pageContent = await page.get(); + const pageContent = await page.get().catch((error) => { + if (safe) { + return null; + } + throw error; + }); + if (!pageContent) { + return ''; + } const { metaData, content } = pageContent; const title = (metaData.title as string | undefined) ||