@riebeckite/plugin-taxonomy 0.0.31 → 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 +5 -1
- package/README_ja.md +35 -62
- package/dist/index.d.ts +1 -3
- package/dist/index.js +52 -19
- package/dist/src/collections.d.ts +2 -2
- package/dist/src/slug.d.ts +1 -0
- package/dist/src/types.d.ts +6 -1
- package/package.json +2 -2
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
|
-
- `
|
|
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
|
|
14
|
-
(`buildContentCollections`)で 2 種類の一覧語を生成します。
|
|
15
|
+
`taxonomy()` はマニフェストの公開エントリを読み、Core のコレクションの取り決め(`buildContentCollections`)でタグとフォルダという 2 種類のタームを生成します。
|
|
15
16
|
|
|
16
|
-
-
|
|
17
|
-
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
109
|
+
## フォルダの index ノート
|
|
126
110
|
|
|
127
111
|
フォルダ入口の解決は
|
|
128
112
|
[`@riebeckite/plugin-folder-pages`](../folder-pages/README_ja.md) が所有します。
|
|
129
|
-
|
|
130
|
-
`<folder>/README.md` や `<folder>/index.md` をフォルダのランディングページに
|
|
131
|
-
する場合は、そのプラグインを有効にしてください。
|
|
113
|
+
タクソノミーはマニフェストの公開ビューからタグタームとフォルダタームだけを生成します。`<folder>/README.md` や `<folder>/index.md` をフォルダのランディングページにする場合は、そのプラグインを有効にしてください。
|
|
132
114
|
|
|
133
|
-
##
|
|
115
|
+
## ページ種別
|
|
134
116
|
|
|
135
|
-
`taxonomy()` は 2
|
|
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
|
|
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
|
-
- `
|
|
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
|
-
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
-
|
|
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(
|
|
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. */
|
package/dist/src/slug.d.ts
CHANGED
package/dist/src/types.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
18
|
+
"@riebeckite/core": "0.0.33"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"tsx": "^4.22.4"
|