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
2 changes: 1 addition & 1 deletion packages/@kamado-io/page-compiler/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ export const pageCompiler = createCompiler<PageCompilerOptions>(() => ({
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,
});
Expand Down
35 changes: 35 additions & 0 deletions packages/kamado/README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`: ビルド前に実行される関数
Expand Down
35 changes: 35 additions & 0 deletions packages/kamado/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/kamado/src/config/merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export async function mergeConfig(
host: 'localhost',
...config.devServer,
},
pageList: config.pageList,
compilers: config.compilers ?? [],
onBeforeBuild: config.onBeforeBuild,
onAfterBuild: config.onAfterBuild,
Expand Down
13 changes: 13 additions & 0 deletions packages/kamado/src/config/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CompilerWithMetadata } from '../compiler/index.js';
import type { CompilableFile } from '../files/types.js';

/**
* Application configuration
Expand All @@ -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)
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/kamado/src/data/global.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 12 additions & 7 deletions packages/kamado/src/data/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 })[];
/**
Expand Down Expand Up @@ -101,25 +101,30 @@ export async function getGlobalData(dir: string, config: Config): Promise<Global
(entry) => entry.outputExtension === '.html',
);

const allPages = pageCompilerEntry
const pageAssetFiles = pageCompilerEntry
? await getAssetGroup({
inputDir: config.dir.input,
outputDir: config.dir.output,
compilerEntry: pageCompilerEntry,
})
: [];

const userDefinedPageList: (CompilableFile & { title?: string })[] = config.pageList
? await config.pageList(pageAssetFiles, config)
: pageAssetFiles;

const pageList = await Promise.all(
allPages.map(async (page) => ({
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),
Expand Down
27 changes: 27 additions & 0 deletions packages/kamado/src/features/breadcrumbs.spec.ts
Original file line number Diff line number Diff line change
@@ -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,
},
]);
});
});
15 changes: 9 additions & 6 deletions packages/kamado/src/features/breadcrumbs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<BreadcrumbItem[]> {
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,
})),
Expand Down
14 changes: 12 additions & 2 deletions packages/kamado/src/features/title.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,27 @@ const titleCache = new Map<string, string>();
* Gets page title
* @param page - Page file
* @param optimizeTitle - Function to optimize title (optional)
* @returns Page title (from metadata.title, HTML <title> 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) ||
Expand Down