@riebeckite/plugin-taxonomy 0.0.32 → 0.0.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,10 @@ client-side JavaScript is required.
8
8
 
9
9
  [日本語](./README_ja.md)
10
10
 
11
+ ## Live example
12
+
13
+ Browse the generated [tags](/tags) and [folders](/folders) indexes. Each index page is produced by this plugin from the site's notes.
14
+
11
15
  ## Overview
12
16
 
13
17
  `taxonomy()` reads the manifest's public entries and produces two sets of
@@ -157,7 +161,7 @@ import "@riebeckite/plugin-taxonomy/style.css";
157
161
  ## Exports
158
162
 
159
163
  - `taxonomy(options?)` — plugin factory
160
- - `taxonomyPlugin` — alias of `taxonomy`
164
+ - `taxonomy` — alias of `taxonomy`
161
165
  - `resolveTaxonomyOptions(options?)` — apply option defaults
162
166
  - `resolveTaxonomyOptionsFromConfig(config)` — read resolved options back from a config
163
167
  - `buildTaxonomyIndex(entries, options)` — build tag and folder terms
package/README_ja.md CHANGED
@@ -2,28 +2,24 @@
2
2
 
3
3
  <!-- Generated from docs/docs/plugins/taxonomy.ja.md. Edit the canonical documentation in docs/docs/plugins and run `pnpm docs:sync`. -->
4
4
 
5
- Riebeckite のビルド時タクソノミー(タグ・フォルダ)プラグインです。一覧用データ、
6
- 語ごとの RSS / Atom / JSON フィード、関連タグナビゲーション、SEO メタデータを
7
- 生成します。クライアント JavaScript は不要です。
5
+ Riebeckite のビルド時タクソノミー(タグ・フォルダ)プラグインです。一覧用データ、タームごとの RSS / Atom / JSON フィード、関連タグナビゲーション、SEO メタデータを生成します。クライアント JavaScript は不要です。
8
6
 
9
7
  [English](./README.md)
10
8
 
9
+ ## 実行例
10
+
11
+ 生成された [タグ](/tags) と [フォルダ](/folders) の一覧を開いてください。どちらもノートからこのプラグインが生成しています。
12
+
11
13
  ## 概要
12
14
 
13
- `taxonomy()` はマニフェストの公開エントリを読み、Core の collection の取り決め
14
- (`buildContentCollections`)で 2 種類の一覧語を生成します。
15
+ `taxonomy()` はマニフェストの公開エントリを読み、Core のコレクションの取り決め(`buildContentCollections`)でタグとフォルダという 2 種類のタームを生成します。
15
16
 
16
- - **タグ**: `tags` でグルーピングし `/tags/<slug>` に配置します。
17
- - **フォルダ**: フォルダでグルーピングし `/folders/<path>` に配置します。
17
+ - **タグターム**: `tags` でグルーピングし `/tags/<slug>` に配置します。
18
+ - **フォルダターム**: フォルダでグルーピングし `/folders/<path>` に配置します。
18
19
 
19
- エントリのリンクには常に解決済みの `permalink` を使い、slug から URL を組み立てる
20
- ことはありません。
20
+ エントリのリンクには常に解決済みの `permalink` を使い、slug から URL を組み立てることはありません。
21
21
 
22
- このプラグインはデータ・フィード・SEO に加え、`/tags/<tag>` と
23
- `/folders/<path>` の Page Type を提供します。語ごとのフィードは build の
24
- generated-output sink 経由で静的ファイルとして出力します。サイトは共通の
25
- Riebeckite catch-all route で Page Type を描画するため、taxonomy 専用の
26
- アプリケーションルートは不要です。
22
+ このプラグインはデータ・フィード・SEO に加え、`/tags/<tag>` と `/folders/<path>` のページ種別を提供します。タームごとのフィードはビルド時に generated-output sink 経由で静的ファイルとして出力します。サイトは共通の Riebeckite catch-all ルートでページ種別を描画するため、タクソノミー専用のアプリケーションルートは不要です。
27
23
 
28
24
  ## 使い方
29
25
 
@@ -47,24 +43,23 @@ export default defineConfig({
47
43
 
48
44
  | オプション | 型 | 既定値 | 説明 |
49
45
  | ---------- | -- | ------ | ---- |
50
- | `tags` | `boolean` | `true` | タグ語を生成する |
51
- | `folders` | `boolean` | `true` | フォルダ語を生成する |
46
+ | `tags` | `boolean` | `true` | タグタームを生成する |
47
+ | `folders` | `boolean` | `true` | フォルダタームを生成する |
52
48
  | `tagsBasePath` | `string` | `"/tags"` | タグ一覧のパス接頭辞 |
53
49
  | `foldersBasePath` | `string` | `"/folders"` | フォルダ一覧のパス接頭辞 |
54
50
  | `folderDepth` | `number` | `0` | フォルダのグルーピング深さ。`0` は完全パス |
55
- | `minEntries` | `number` | `1` | これ未満のエントリ数しかない語を除外する |
51
+ | `minEntries` | `number` | `1` | エントリ数がこれ未満のタームを除外する |
56
52
  | `related` | `boolean` | `true` | 関連タグナビゲーションを生成する |
57
53
  | `relatedLimit` | `number` | `8` | タグごとの関連タグ上限 |
58
- | `feeds` | `{ rss?, atom?, json? }` | すべて `true` | 語ごとのフィード形式 |
54
+ | `feeds` | `{ rss?, atom?, json? }` | すべて `true` | タームごとのフィード形式 |
59
55
  | `feedLimit` | `number` | `50` | フィードあたりの最大エントリ数 |
60
- | `resolveTitle` | `(context) => string` | `#value` / パス | 語の表示タイトル |
56
+ | `resolveTitle` | `(context) => string` | `#value` / パス | タームの表示タイトル |
61
57
  | `className` | `string` | `"rr-taxonomy"` | ページ断片のルート CSS クラス |
62
58
  | `dataEndpoint` | `string` | `"/taxonomy/index.json"` | JSON データのエンドポイント |
63
59
 
64
60
  ## データエンドポイント
65
61
 
66
- プラグインは JSON エンドポイントを 1 つ登録します(endpoint の取り決めは HonoX
67
- integration が接続するため、プラグイン自身は framework のルーティングを持ちません)。
62
+ プラグインは JSON エンドポイントを 1 つ登録します(エンドポイントの取り決めは HonoX 連携が接続するため、プラグイン自身はフレームワークのルーティングを持ちません)。
68
63
 
69
64
  ```
70
65
  GET /taxonomy/index.json
@@ -89,12 +84,11 @@ GET /taxonomy/index.json
89
84
  }
90
85
  ```
91
86
 
92
- ペイロードは決定的で JSON 安全な射影です。描画済み HTML を含まないため、アプリの
93
- ルートやブラウザへそのまま渡せます。
87
+ ペイロードは決定的で JSON 安全な射影です。描画済み HTML を含まないため、アプリのルートやブラウザへそのまま渡せます。
94
88
 
95
89
  ## 生成されるフィード
96
90
 
97
- ビルド時に、語と形式ごとに 1 つのフィードを `context.output.emit` で出力します。
91
+ ビルド時に、タームと形式ごとに 1 つのフィードを `context.output.emit` で出力します。
98
92
 
99
93
  | 形式 | パス |
100
94
  | ---- | ---- |
@@ -102,52 +96,31 @@ GET /taxonomy/index.json
102
96
  | Atom | `/tags/<slug>/atom.xml` |
103
97
  | JSON Feed 1.1 | `/tags/<slug>/feed.json` |
104
98
 
105
- フォルダ語も同じファイルを `/folders/<path>/…` に持ちます。フィードの channel は
106
- 語ごとのタイトルと self link を持つため、タグの購読と
107
- `@riebeckite/plugin-seo` が持つサイト全体のフィードを区別できます。公開かつ
108
- `noindex` でないエントリだけがマニフェストの公開ビューに入り、フィードへ届きます。
109
- 語ごとの RSS と Atom はエントリの要約を出します。語ごとの JSON Feed は同じ要約を
110
- `content_text` として出し、描画済みの記事 HTML は複製しません。
99
+ フォルダタームも同じファイルを `/folders/<path>/…` に持ちます。フィードのチャネルはタームごとのタイトルと自己リンクを持つため、タグの購読と `@riebeckite/plugin-seo` が持つサイト全体のフィードを区別できます。公開かつ `noindex` でないエントリだけがマニフェストの公開ビューに入り、フィードへ届きます。タームごとの RSS と Atom はエントリの要約を出します。タームごとの JSON Feed は同じ要約を `content_text` として出し、描画済みの記事 HTML は複製しません。
111
100
 
112
101
  ## 関連タグ
113
102
 
114
- `related` が有効なとき、各タグ語は同じエントリに共起するタグを保持します。共起
115
- エントリ数で降順、同数ならアルファベット順に並べ、`relatedLimit` で打ち切ります。
116
- ナビゲーションは `renderTaxonomyPage` が各 taxonomy term ページで実際に描画します。共起するタグがあるタグページで確認できます。
103
+ `related` が有効なとき、各タグタームは同じエントリに共起するタグを保持します。共起エントリ数で降順、同数ならアルファベット順に並べ、`relatedLimit` で打ち切ります。ナビゲーションは `renderTaxonomyPage` が各タームページで実際に描画します。共起するタグがあるタグページで確認できます。
117
104
 
118
105
  ## SEO
119
106
 
120
- `buildTaxonomySeo(config, term)` は一覧ページの `SeoMetadata` を返します。設定済みの
121
- Core `seo` 拡張ポイント(たとえば `@riebeckite/plugin-seo`)へ委譲するため、
122
- タイトル・canonical URL・JSON-LD がサイト全体と揃います。SEO プロバイダーが無い
123
- 場合は最小限のオブジェクトへフォールバックします。
107
+ `buildTaxonomySeo(config, term)` は一覧ページの `SeoMetadata` を返します。設定済みの Core `seo` 拡張ポイント(たとえば `@riebeckite/plugin-seo`)へ委譲するため、タイトル・canonical URL・JSON-LD がサイト全体と揃います。SEO プロバイダーが無い場合は最小限のオブジェクトへフォールバックします。
124
108
 
125
- ## フォルダ index note
109
+ ## フォルダの index ノート
126
110
 
127
111
  フォルダ入口の解決は
128
112
  [`@riebeckite/plugin-folder-pages`](../folder-pages/README_ja.md) が所有します。
129
- taxonomy はマニフェストの公開ビューからタグ語とフォルダ語だけを生成します。
130
- `<folder>/README.md` や `<folder>/index.md` をフォルダのランディングページに
131
- する場合は、そのプラグインを有効にしてください。
113
+ タクソノミーはマニフェストの公開ビューからタグタームとフォルダタームだけを生成します。`<folder>/README.md` や `<folder>/index.md` をフォルダのランディングページにする場合は、そのプラグインを有効にしてください。
132
114
 
133
- ## Page Type
115
+ ## ページ種別
134
116
 
135
- `taxonomy()` は 2 つの Page Type を登録します。`taxonomy-term` は 1 つのタグ・
136
- フォルダページを描画し、フィード検出用の `<link rel="alternate">` メタデータを
137
- 含みます。`taxonomy-index` は `tagsBasePath` に全タグ一覧、`foldersBasePath` に
138
- 全フォルダ一覧を描画し、各語へリンクします。どちらも SSG のパスと resolver を
139
- 公開マニフェストから作るため、非公開エントリがタグ・フォルダ・一覧ページへ
140
- 入ることはありません。
117
+ `taxonomy()` は 2 つのページ種別を登録します。`taxonomy-term` は 1 つのタグ・フォルダページを描画し、フィード検出用の `<link rel="alternate">` メタデータを含みます。`taxonomy-index` は `tagsBasePath` に全タグ一覧、`foldersBasePath` に全フォルダ一覧を描画し、各タームへリンクします。どちらも SSG のパスとリゾルバーを公開マニフェストから作るため、非公開エントリがタグ・フォルダ・一覧ページへ入ることはありません。
141
118
 
142
- サイトの共通 catch-all route では、`@riebeckite/honox/server` の
143
- `pluginPageSsgParams(content)` と `resolveRiebeckiteRoute(content, path)` を使います。
144
- これはすべての Plugin Page Type に共通の配線です。
119
+ サイトの共通 catch-all ルートでは、`@riebeckite/honox/server` の `pluginPageSsgParams(content)` と `resolveRiebeckiteRoute(content, path)` を使います。これはすべてのプラグインのページ種別に共通の配線です。
145
120
 
146
121
  ## スタイル
147
122
 
148
- パッケージは安定した `rr-taxonomy` ルートフックと `--rr-taxonomy-*` トークン
149
- (`--rb-*` へフォールバック)を持つ `style.css` を同梱します。他のプラグイン
150
- スタイルシートと同じように登録してください。
123
+ パッケージは安定した `rr-taxonomy` ルートフックと `--rr-taxonomy-*` トークン(`--rb-*` へフォールバック)を持つ `style.css` を同梱します。他のプラグインのスタイルシートと同じように登録してください。
151
124
 
152
125
  ```ts
153
126
  import "@riebeckite/plugin-taxonomy/style.css";
@@ -155,15 +128,15 @@ import "@riebeckite/plugin-taxonomy/style.css";
155
128
 
156
129
  ## エクスポート
157
130
 
158
- - `taxonomy(options?)`:プラグインファクトリー
159
- - `taxonomyPlugin`:`taxonomy` の別名
131
+ - `taxonomy(options?)`:プラグインファクトリ
132
+ - `taxonomy`:`taxonomy` の別名
160
133
  - `resolveTaxonomyOptions(options?)`:既定値を適用する
161
134
  - `resolveTaxonomyOptionsFromConfig(config)`:config から解決済みオプションを読む
162
- - `buildTaxonomyIndex(entries, options)`:タグ・フォルダ語を構築する
135
+ - `buildTaxonomyIndex(entries, options)`:タグ・フォルダタームを構築する
163
136
  - `serializeTaxonomyIndex(index)` / `serializeTaxonomyTerm(term)`:JSON 安全な射影
164
137
  - `renderTaxonomyPage(term, options)` / `renderRelatedTerms(term, options)`:ページ断片
165
138
  - `renderTaxonomyIndexPage(kind, terms, options)`:全タグ・全フォルダ一覧のページ断片
166
- - `renderTermFeed(config, term, format, limit?)` / `buildFeedHeadTags(term)`:語ごとのフィード
139
+ - `renderTermFeed(config, term, format, limit?)` / `buildFeedHeadTags(term)`:タームごとのフィード
167
140
  - `buildTaxonomySeo(config, term)`:一覧ページの SEO メタデータ
168
141
  - `slugifyTaxonomyValue(value)`:URL・ファイル用の slug
169
142
  - `buildTaxonomyAbsoluteUrl(config, pathOrUrl)`:絶対 URL
@@ -172,10 +145,10 @@ import "@riebeckite/plugin-taxonomy/style.css";
172
145
  ## 制約
173
146
 
174
147
  - タクソノミーはビルド時に固定されます。フルビルドで常に正しく再計算されます。
175
- - 語ごとのフィードは静的なビルド成果物です。実行時のサーフェスは固定の JSON
176
- データエンドポイントだけです。dev サーバーは語ごとのフィードファイルを列挙
177
- しません。
178
- - 語はマニフェストの公開ビューのみを反映します。非公開または `noindex` の
148
+ - タームごとのフィードは静的なビルド成果物です。実行時のサーフェスは固定の JSON
149
+ データエンドポイントだけです。開発サーバーはタームごとのフィードファイルを
150
+ 列挙しません。
151
+ - タームはマニフェストの公開ビューのみを反映します。非公開または `noindex` の
179
152
  エントリは除外されます。
180
153
 
181
154
  ## 関連
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export { buildFeedHeadTags, renderTermFeed } from "./src/feeds.js";
5
5
  export { DEFAULT_TAXONOMY_CLASS_NAME, DEFAULT_TAXONOMY_DATA_ENDPOINT, DEFAULT_TAXONOMY_FEED_LIMIT, DEFAULT_TAXONOMY_FOLDERS_BASE_PATH, DEFAULT_TAXONOMY_MIN_ENTRIES, DEFAULT_TAXONOMY_RELATED_LIMIT, DEFAULT_TAXONOMY_TAGS_BASE_PATH, resolveTaxonomyOptions, } from "./src/options.js";
6
6
  export { renderRelatedTerms, renderTaxonomyIndexPage, renderTaxonomyPage, } from "./src/pages.js";
7
7
  export { buildTaxonomySeo } from "./src/seo.js";
8
- export { slugifyTaxonomyValue } from "./src/slug.js";
8
+ export { buildTaxonomyTagHref, slugifyTaxonomyValue } from "./src/slug.js";
9
9
  export type { ResolvedTaxonomyOptions, TaxonomyEntryReference, TaxonomyFeedFormat, TaxonomyFeedLinks, TaxonomyFeedOptions, TaxonomyHeadTag, TaxonomyIndex, TaxonomyIndexData, TaxonomyKind, TaxonomyOptions, TaxonomyPage, TaxonomyRelatedTerm, TaxonomyTerm, TaxonomyTermContext, TaxonomyTermData, } from "./src/types.js";
10
10
  export { buildTaxonomyAbsoluteUrl } from "./src/url.js";
11
11
  export declare const TAXONOMY_PLUGIN_NAME = "taxonomy";
@@ -19,8 +19,6 @@ export declare const TAXONOMY_PLUGIN_NAME = "taxonomy";
19
19
  * generated-output sink, so no client JavaScript is required.
20
20
  */
21
21
  export declare function taxonomy(options?: TaxonomyOptions): import("@riebeckite/core").RiebeckitePlugin<TaxonomyOptions>;
22
- /** Alias matching the `*Plugin` suffix used by other plugin factories. */
23
- export declare const taxonomyPlugin: typeof taxonomy;
24
22
  /**
25
23
  * Reads the taxonomy plugin's resolved options back from a Riebeckite config.
26
24
  *
package/dist/index.js CHANGED
@@ -16,6 +16,12 @@ import {
16
16
  function slugifyTaxonomyValue(value) {
17
17
  return value.split("/").map(slugifyTaxonomySegment).filter((segment) => segment.length > 0).join("/");
18
18
  }
19
+ function buildTaxonomyTagHref(tag, tagsBasePath) {
20
+ const slug = slugifyTaxonomyValue(tag);
21
+ const trimmedBase = tagsBasePath.trim().replace(/\/+$/, "");
22
+ const base = trimmedBase === "" || trimmedBase.startsWith("/") ? trimmedBase : `/${trimmedBase}`;
23
+ return slug ? `${base}/${slug}` : base;
24
+ }
19
25
  function slugifyTaxonomySegment(segment) {
20
26
  const slug = segment.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
21
27
  if (slug) return slug;
@@ -24,18 +30,20 @@ function slugifyTaxonomySegment(segment) {
24
30
  }
25
31
 
26
32
  // src/collections.ts
27
- function buildTaxonomyIndex(entries, options) {
33
+ function buildTaxonomyIndex(entries, options, icons) {
28
34
  const tags = options.tags ? buildTerms(entries, {
29
35
  kind: "tag",
30
36
  basePath: options.tagsBasePath,
31
37
  groupBy: { by: "tags" },
32
- options
38
+ options,
39
+ icon: findRegistryIcon(icons?.tags)
33
40
  }) : [];
34
41
  const folders = options.folders ? buildTerms(entries, {
35
42
  kind: "folder",
36
43
  basePath: options.foldersBasePath,
37
44
  groupBy: options.folderDepth > 0 ? { by: "folder", depth: options.folderDepth } : { by: "folder" },
38
- options
45
+ options,
46
+ icon: findRegistryIcon(icons?.folders)
39
47
  }) : [];
40
48
  if (options.related && tags.length > 0) {
41
49
  attachRelatedTerms(tags, entries, options);
@@ -52,7 +60,8 @@ function serializeTaxonomyTerm(term) {
52
60
  count: term.entries.length,
53
61
  entries: term.entries.map(toEntryReference),
54
62
  related: term.related,
55
- feeds: term.feeds
63
+ feeds: term.feeds,
64
+ ...term.icon ? { icon: term.icon } : {}
56
65
  };
57
66
  }
58
67
  function serializeTaxonomyIndex(index) {
@@ -67,11 +76,12 @@ function toEntryReference(entry) {
67
76
  permalink: entry.permalink,
68
77
  title: entry.title,
69
78
  updated: getEntryUpdatedTime(entry),
70
- summary: getEntrySummary(entry)
79
+ summary: getEntrySummary(entry),
80
+ ...entry.icon ? { icon: entry.icon } : {}
71
81
  };
72
82
  }
73
83
  function buildTerms(entries, input) {
74
- const { kind, basePath, groupBy, options } = input;
84
+ const { kind, basePath, groupBy, options, icon } = input;
75
85
  const definition = {
76
86
  kind,
77
87
  basePath,
@@ -88,11 +98,13 @@ function buildTerms(entries, input) {
88
98
  const path = normalizeTermPath(collection.path, basePath);
89
99
  if (seen.has(path)) continue;
90
100
  seen.add(path);
101
+ const termIcon = icon?.(collection.value);
91
102
  terms.push({
92
103
  kind,
93
104
  value: collection.value,
94
105
  title: collection.title,
95
106
  path,
107
+ ...termIcon ? { icon: termIcon } : {},
96
108
  entries: collection.entries,
97
109
  related: [],
98
110
  feeds: buildFeedLinks(path, options),
@@ -141,7 +153,8 @@ function attachRelatedTerms(tags, entries, options) {
141
153
  title: related.title,
142
154
  path: related.path,
143
155
  permalink: related.path,
144
- count
156
+ count,
157
+ ...related.icon ? { icon: related.icon } : {}
145
158
  }));
146
159
  }
147
160
  }
@@ -181,6 +194,17 @@ function getEntryUpdatedTime(entry) {
181
194
  const date = new Date(value);
182
195
  return Number.isNaN(date.getTime()) ? null : date.toISOString();
183
196
  }
197
+ function findRegistryIcon(registry) {
198
+ if (!registry) return void 0;
199
+ return (value) => {
200
+ if (registry[value]) return registry[value];
201
+ const lower = value.toLowerCase();
202
+ for (const [candidate, icon] of Object.entries(registry)) {
203
+ if (candidate.toLowerCase() === lower) return icon;
204
+ }
205
+ return void 0;
206
+ };
207
+ }
184
208
 
185
209
  // src/url.ts
186
210
  function buildTaxonomyAbsoluteUrl(config, pathOrUrl) {
@@ -395,13 +419,13 @@ function renderTaxonomyPage(term, options) {
395
419
  const entries = term.entries.map(
396
420
  (entry) => `<li class="${className}__item"><a class="${className}__link" href="${escapeHtmlAttribute(
397
421
  entry.permalink
398
- )}">${escapeHtml(entry.title)}</a></li>`
422
+ )}">${renderIcon(entry.icon)}${escapeHtml(entry.title)}</a></li>`
399
423
  ).join("");
400
424
  const feeds = renderFeedLinks(term, className);
401
425
  const related = renderRelatedTerms(term, options);
402
426
  const html = `<section class="${className}" data-rr-taxonomy="${term.kind}" data-rr-taxonomy-value="${escapeHtmlAttribute(
403
427
  term.value
404
- )}"><h1 class="${className}__title">${escapeHtml(term.title)}</h1><p class="${className}__count" data-rr-taxonomy-count="${term.entries.length}">${term.entries.length}</p>${feeds}<ul class="${className}__list">${entries}</ul>${related}</section>`;
428
+ )}"><h1 class="${className}__title">${renderIcon(term.icon)}${escapeHtml(term.title)}</h1><p class="${className}__count" data-rr-taxonomy-count="${term.entries.length}">${term.entries.length}</p>${feeds}<ul class="${className}__list">${entries}</ul>${related}</section>`;
405
429
  return {
406
430
  title: term.title,
407
431
  frontmatter: { title: term.title },
@@ -417,7 +441,7 @@ function renderRelatedTerms(term, options) {
417
441
  const items = term.related.map(
418
442
  (related) => `<li class="${className}__related-item"><a class="${className}__related-link" href="${escapeHtmlAttribute(
419
443
  related.path
420
- )}" data-rr-taxonomy-related-count="${related.count}">${escapeHtml(related.title)}</a></li>`
444
+ )}" data-rr-taxonomy-related-count="${related.count}">${renderIcon(related.icon)}${escapeHtml(related.title)}</a></li>`
421
445
  ).join("");
422
446
  return `<nav class="${className}__related" aria-label="Related tags" data-rr-taxonomy-related><ul>${items}</ul></nav>`;
423
447
  }
@@ -427,7 +451,7 @@ function renderTaxonomyIndexPage(kind, terms, options) {
427
451
  const items = terms.map(
428
452
  (term) => `<li class="${className}__item"><a class="${className}__link" href="${escapeHtmlAttribute(
429
453
  term.path
430
- )}" data-rr-taxonomy-count="${term.entries.length}">${escapeHtml(
454
+ )}" data-rr-taxonomy-count="${term.entries.length}">${renderIcon(term.icon)}${escapeHtml(
431
455
  term.title
432
456
  )}</a></li>`
433
457
  ).join("");
@@ -441,6 +465,9 @@ function renderTaxonomyIndexPage(kind, terms, options) {
441
465
  headTags: []
442
466
  };
443
467
  }
468
+ function renderIcon(icon) {
469
+ return icon ? `<span class="rr-taxonomy__icon">${icon.html}</span>` : "";
470
+ }
444
471
  function renderFeedLinks(term, className) {
445
472
  const links = [];
446
473
  if (term.feeds.rss) {
@@ -512,7 +539,8 @@ function taxonomy(options = {}) {
512
539
  const term = findTaxonomyTerm(
513
540
  manifest.discoverableEntries,
514
541
  resolved,
515
- pathname
542
+ pathname,
543
+ manifest.icons
516
544
  );
517
545
  return term ? taxonomyPageOutputDependencies(term, resolved.related) : [{ type: "unknown" }];
518
546
  },
@@ -520,7 +548,8 @@ function taxonomy(options = {}) {
520
548
  const term = findTaxonomyTerm(
521
549
  manifest.discoverableEntries,
522
550
  resolved,
523
- pathname
551
+ pathname,
552
+ manifest.icons
524
553
  );
525
554
  if (!term) return null;
526
555
  const page = renderTaxonomyPage(term, resolved);
@@ -549,7 +578,8 @@ function taxonomy(options = {}) {
549
578
  if (!kind) return null;
550
579
  const index = buildTaxonomyIndex(
551
580
  manifest.discoverableEntries,
552
- resolved
581
+ resolved,
582
+ manifest.icons
553
583
  );
554
584
  const page = renderTaxonomyIndexPage(
555
585
  kind,
@@ -597,7 +627,6 @@ function taxonomy(options = {}) {
597
627
  ]
598
628
  });
599
629
  }
600
- var taxonomyPlugin = taxonomy;
601
630
  function taxonomyPagePaths(entries, options) {
602
631
  const index = buildTaxonomyIndex(entries, options);
603
632
  return [...index.tags, ...index.folders].map((term) => term.path);
@@ -619,8 +648,8 @@ var pageProjectionDependencies = [
619
648
  { type: "projection", projection: "navigation" },
620
649
  { type: "projection", projection: "recent-posts" }
621
650
  ];
622
- function findTaxonomyTerm(entries, options, pathname) {
623
- const index = buildTaxonomyIndex(entries, options);
651
+ function findTaxonomyTerm(entries, options, pathname, icons) {
652
+ const index = buildTaxonomyIndex(entries, options, icons);
624
653
  return [...index.tags, ...index.folders].find(
625
654
  (term) => term.path === pathname
626
655
  );
@@ -659,7 +688,11 @@ function createTaxonomyEndpoints(resolved) {
659
688
  resolved.dataEndpoint,
660
689
  ({ manifest }) => ({
661
690
  json: serializeTaxonomyIndex(
662
- buildTaxonomyIndex(manifest.discoverableEntries, resolved)
691
+ buildTaxonomyIndex(
692
+ manifest.discoverableEntries,
693
+ resolved,
694
+ manifest.icons
695
+ )
663
696
  )
664
697
  }),
665
698
  { cacheControl: "public, max-age=300" }
@@ -755,6 +788,7 @@ export {
755
788
  buildTaxonomyAbsoluteUrl,
756
789
  buildTaxonomyIndex,
757
790
  buildTaxonomySeo,
791
+ buildTaxonomyTagHref,
758
792
  renderRelatedTerms,
759
793
  renderTaxonomyIndexPage,
760
794
  renderTaxonomyPage,
@@ -765,6 +799,5 @@ export {
765
799
  serializeTaxonomyTerm,
766
800
  slugifyTaxonomyValue,
767
801
  taxonomy,
768
- taxonomyPlugin,
769
802
  toEntryReference
770
803
  };
@@ -1,4 +1,4 @@
1
- import { type ContentManifestEntry } from "@riebeckite/core";
1
+ import { type ContentIconRegistry, type ContentManifestEntry } from "@riebeckite/core";
2
2
  import type { ResolvedTaxonomyOptions, TaxonomyEntryReference, TaxonomyIndex, TaxonomyIndexData, TaxonomyTerm, TaxonomyTermData } from "./types.js";
3
3
  /**
4
4
  * Builds the tag and folder taxonomy for the given entries. It reuses the Core
@@ -9,7 +9,7 @@ import type { ResolvedTaxonomyOptions, TaxonomyEntryReference, TaxonomyIndex, Ta
9
9
  * `entries` should be the manifest's public view; unpublished notes never reach
10
10
  * generated data or feeds.
11
11
  */
12
- export declare function buildTaxonomyIndex(entries: readonly ContentManifestEntry[], options: ResolvedTaxonomyOptions): TaxonomyIndex;
12
+ export declare function buildTaxonomyIndex(entries: readonly ContentManifestEntry[], options: ResolvedTaxonomyOptions, icons?: ContentIconRegistry): TaxonomyIndex;
13
13
  /** Projects a term into a JSON-safe shape for endpoints and client data. */
14
14
  export declare function serializeTaxonomyTerm(term: TaxonomyTerm): TaxonomyTermData;
15
15
  /** Projects the whole index into a JSON-safe shape. */
@@ -8,3 +8,4 @@
8
8
  * percent-encoded text so distinct values never share a URL.
9
9
  */
10
10
  export declare function slugifyTaxonomyValue(value: string): string;
11
+ export declare function buildTaxonomyTagHref(tag: string, tagsBasePath: string): string;
@@ -1,4 +1,4 @@
1
- import type { ContentManifestEntry, SeoMetadata } from "@riebeckite/core";
1
+ import type { ContentIcon, ContentManifestEntry, SeoMetadata } from "@riebeckite/core";
2
2
  /** Feed formats the taxonomy plugin can generate per term. */
3
3
  export type TaxonomyFeedFormat = "rss" | "atom" | "json";
4
4
  /** A taxonomy grouping category. */
@@ -67,6 +67,7 @@ export type TaxonomyEntryReference = {
67
67
  title: string;
68
68
  updated: string | null;
69
69
  summary: string;
70
+ icon?: ContentIcon;
70
71
  };
71
72
  /** A related-term reference used by related-tag navigation. */
72
73
  export type TaxonomyRelatedTerm = {
@@ -76,6 +77,7 @@ export type TaxonomyRelatedTerm = {
76
77
  path: string;
77
78
  permalink: string;
78
79
  count: number;
80
+ icon?: ContentIcon;
79
81
  };
80
82
  /** Resolved feed URLs for one term. */
81
83
  export type TaxonomyFeedLinks = {
@@ -90,6 +92,8 @@ export type TaxonomyTerm = {
90
92
  value: string;
91
93
  /** Display title, `#value` for tags by default. */
92
94
  title: string;
95
+ /** Resolved icon for the term, when one is configured. */
96
+ icon?: ContentIcon;
93
97
  /** Site-local listing path, for example `/tags/guide/intro`. */
94
98
  path: string;
95
99
  /** Resolved entries, in manifest query order. */
@@ -117,6 +121,7 @@ export type TaxonomyTermData = {
117
121
  entries: TaxonomyEntryReference[];
118
122
  related: TaxonomyRelatedTerm[];
119
123
  feeds: TaxonomyFeedLinks;
124
+ icon?: ContentIcon;
120
125
  };
121
126
  /** JSON-safe projection of a {@link TaxonomyIndex}. */
122
127
  export type TaxonomyIndexData = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riebeckite/plugin-taxonomy",
3
- "version": "0.0.32",
3
+ "version": "0.0.33",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "./style.css": "./style.css"
16
16
  },
17
17
  "dependencies": {
18
- "@riebeckite/core": "0.0.32"
18
+ "@riebeckite/core": "0.0.33"
19
19
  },
20
20
  "devDependencies": {
21
21
  "tsx": "^4.22.4"