@riebeckite/plugin-navigation 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
@@ -10,6 +10,10 @@ the Site owns placement — where each rendered navigation appears.
10
10
 
11
11
  [日本語](./README_ja.md)
12
12
 
13
+ ## Live example
14
+
15
+ The header links — Docs, Plugins, Themes, and GitHub — are generated from the `navigation()` configuration. The Documentation sidebar is the Docs collection navigation.
16
+
13
17
  ## Overview
14
18
 
15
19
  `navigation()` reads the manifest's discoverable entries — the notes that are
@@ -150,7 +154,7 @@ tab.
150
154
  ## Exports
151
155
 
152
156
  - `navigation(options?)` — plugin factory
153
- - `navigationPlugin` — alias of `navigation`
157
+ - `navigation` — alias of `navigation`
154
158
  - `NAVIGATION_PLUGIN_NAME` — the plugin name, `"navigation"`
155
159
  - `buildNavigation(entries, options?)` — build a model from manifest entries
156
160
  - `resolveSiteNavigation(config, manifest)` — find the enabled plugin and build
package/README_ja.md CHANGED
@@ -5,21 +5,25 @@
5
5
  サイトナビゲーションを提供する Riebeckite プラグインです。`navigation()` は
6
6
  Vault にすでにあるノートから主要ナビゲーションを導出します。Riebeckite 専用の
7
7
  ファイルも必須 frontmatter も不要なので、既存の Obsidian Vault をそのまま
8
- 公開できます。ナビゲーションの「モデル」、UI、操作に関わるスタイルはプラグインが持ち、
9
- 配置は Site が担当します。
8
+ 公開できます。ナビゲーションのモデル、描画される UI、インタラクションの
9
+ スタイルはプラグインが持ち、どこに置くかは Site が決めます。
10
10
 
11
11
  [English](./README.md)
12
12
 
13
+ ## 実行例
14
+
15
+ ヘッダーの Docs・Plugins・Themes・GitHub のリンクは、`navigation()` の設定から生成されています。右側の Documentation サイドバーは Docs コレクションのナビゲーションです。
16
+
13
17
  ## 仕組み
14
18
 
15
19
  `navigation()` はマニフェストの discoverable エントリ(公開され、ルーティング
16
20
  可能なノート)を読み、スラッグ階層でまとめます。
17
21
 
18
- - フォルダはセクションになる
22
+ - フォルダはセクションになります
19
23
  - `index` / `README` ノートはそのフォルダへのリンクになり、同じフォルダの
20
- 直下のノートは子になる
21
- - それ以外のノートはリンクになる
22
- - タイトルはノート自身の `title` を使い、なければスラッグのセグメントから作る
24
+ 直下のノートは子になります
25
+ - それ以外のノートはリンクになります
26
+ - タイトルはノート自身の `title` を使い、なければスラッグのセグメントから作ります
23
27
 
24
28
  index ノートを持たないフォルダは、子をまとめたラベルとして表示されます。
25
29
  モデルは `manifest.discoverableEntries` から作るため、下書き・予約公開・
@@ -59,10 +63,11 @@ const secondary = model?.secondary ?? [];
59
63
  `SiteNav` は解決済みのツリーを `<nav>` ランドマークとネストしたリストとして
60
64
  描画し、`MobileSiteNav` はコンパクトな `<details>` メニューを描画します。子の
61
65
  再帰描画、現在パスの判定、言語を考慮した正規化、外部リンクの扱い、`aria-current`
62
- に加え、リスト・リンク・子メニュー・フォーカス表示・モバイルパネルの `rb-nav` UI は
63
- プラグインが自身の `style.css` で担当します(自動生成される plugin styles 経由で
64
- 読み込まれます)。Site はコンポーネントの root を独自のレイアウト用ラッパーに置き、
65
- `rb-nav` の子要素をスタイルしたりモバイルメニューのマークアップを組み立てたりしません。
66
+ の付与はプラグインが行います。リストの流し方、リンク、子メニュー、フォーカス
67
+ 表示、モバイルパネルといった `rb-nav` の UI も、プラグインが自身の `style.css`
68
+ で提供します(自動生成されるプラグインスタイル経由で読み込まれます)。Site は
69
+ コンポーネントのルートを独自のレイアウト用ラッパーに置くだけにし、`rb-nav` の
70
+ 子要素をスタイルしたり、モバイルメニューのマークアップを組み立てたりしないでください。
66
71
 
67
72
  ```tsx
68
73
  import { MobileSiteNav, SiteNav } from "@riebeckite/plugin-navigation";
@@ -87,13 +92,13 @@ import { MobileSiteNav, SiteNav } from "@riebeckite/plugin-navigation";
87
92
  ```
88
93
 
89
94
  `path` は現在のリクエストパスです。`language` は現在のコンテンツ言語で、指定すると
90
- `SiteNav` は `/language` プレフィックスを取り除いてから現在のアイテムを判定します。
95
+ `SiteNav` は `/language` プレフィックスを取り除いてからアクティブな項目を判定します。
91
96
  そのため、言語プレフィックスのない手書き href でもローカライズされたページに
92
97
  一致します。`localizeHref` を渡すと現在の言語に合わせて href を書き換えられます。
93
98
  `label` でランドマークのラベルを、`class` / `className` で `<nav>` のクラスを
94
99
  拡張できます。
95
100
 
96
- root レベルで意図的に見た目を上書きする場合は、`SiteNav` の root またはラッパーに
101
+ ルートレベルで意図的に見た目を上書きする場合は、`SiteNav` のルートまたはラッパーに
97
102
  次の公開カスタムプロパティを指定します。`--rb-nav-gap`、
98
103
  `--rb-nav-list-flex-wrap`、`--rb-nav-list-justify-content`、
99
104
  `--rb-nav-item-display`、`--rb-nav-item-padding`、
@@ -152,7 +157,7 @@ navigation({
152
157
  ## エクスポート
153
158
 
154
159
  - `navigation(options?)`:プラグインファクトリ
155
- - `navigationPlugin`:`navigation` のエイリアス
160
+ - `navigation`:`navigation` のエイリアス
156
161
  - `NAVIGATION_PLUGIN_NAME`:プラグイン名 `"navigation"`
157
162
  - `buildNavigation(entries, options?)`:マニフェストエントリからモデルを構築する
158
163
  - `resolveSiteNavigation(config, manifest)`:有効なプラグインを見つけてモデルを
@@ -165,9 +170,9 @@ navigation({
165
170
 
166
171
  ## 制約
167
172
 
168
- - `l10n` Plugin が言語 metadata を付与している場合、導出は表示中の言語に追従し、
169
- 多言語 Vault の Navigation が言語を混在させることはありません。localization
170
- metadata のない Vault では、discoverable な全 entry を使います。
173
+ - `l10n` プラグインが言語メタデータを付与している場合、導出は表示中の言語に追従し、
174
+ 多言語 Vault のナビゲーションが言語を混在させることはありません。ローカライズの
175
+ メタデータがない Vault では、discoverable な全エントリを使います。
171
176
  - 並び順はタイトルのアルファベット順です。frontmatter の順序は読みません。
172
177
  - `exclude` / `include` / `order` はまだありません。実際のサイトが必要とした
173
178
  ときにだけ追加します。
package/dist/index.d.ts CHANGED
@@ -4,4 +4,3 @@ export { MobileSiteNav, SiteNav } from "./components/site-nav.js";
4
4
  export { buildNavigation, NAVIGATION_PLUGIN_NAME, resolveSiteNavigation, } from "./src/navigation.js";
5
5
  export type { NavigationItem, NavigationOptions, NavigationSource, SiteNavigation, } from "./src/types.js";
6
6
  export declare function navigation(options?: NavigationOptions): import("@riebeckite/core").RiebeckitePlugin<NavigationOptions>;
7
- export declare const navigationPlugin: typeof navigation;
package/dist/index.js CHANGED
@@ -8,9 +8,9 @@ import {
8
8
  var ROOT_SEGMENT = "";
9
9
  var LOCALIZATION_TRANSLATION_KEY = "l10n.translationId";
10
10
  var NAVIGATION_PLUGIN_NAME = "navigation";
11
- function buildNavigation(entries, options = {}) {
11
+ function buildNavigation(entries, options = {}, folderIcons = {}) {
12
12
  return {
13
- primary: options.items ?? deriveNavigation(entries),
13
+ primary: options.items ?? deriveNavigation(entries, folderIcons),
14
14
  secondary: options.secondary ?? []
15
15
  };
16
16
  }
@@ -21,7 +21,8 @@ function resolveSiteNavigation(source, manifest, language) {
21
21
  if (!plugin) return null;
22
22
  return buildNavigation(
23
23
  selectEntriesByLanguage(manifest.discoverableEntries, language),
24
- plugin.options ?? {}
24
+ plugin.options ?? {},
25
+ manifest.icons?.folders ?? {}
25
26
  );
26
27
  }
27
28
  function validateNavigationOptions(options) {
@@ -31,11 +32,11 @@ function validateNavigationOptions(options) {
31
32
  validateItems(options.secondary, "secondary", issues, /* @__PURE__ */ new Set());
32
33
  return issues;
33
34
  }
34
- function deriveNavigation(entries) {
35
+ function deriveNavigation(entries, folderIcons) {
35
36
  const nodes = entries.map(toNavigationEntry).filter((entry) => Boolean(entry));
36
- const root = createTreeNode(ROOT_SEGMENT);
37
+ const root = createTreeNode(ROOT_SEGMENT, "");
37
38
  for (const node of nodes) addEntry(root, node);
38
- return buildItems(root);
39
+ return buildItems(root, folderIcons);
39
40
  }
40
41
  function toNavigationEntry(entry) {
41
42
  const path = entry.publicLocation.metadata?.[LOCALIZATION_TRANSLATION_KEY] ?? entry.slug;
@@ -57,27 +58,28 @@ function addEntry(root, entry) {
57
58
  const path = entry.isIndex ? entry.segments.slice(0, -1) : entry.segments;
58
59
  let node = root;
59
60
  for (const segment of path) {
60
- const child = node.children.get(segment) ?? createTreeNode(segment);
61
+ const child = node.children.get(segment) ?? createTreeNode(segment, node.path ? `${node.path}/${segment}` : segment);
61
62
  node.children.set(segment, child);
62
63
  node = child;
63
64
  }
64
65
  if (entry.isIndex) node.indexes.push(entry);
65
66
  else node.page = entry;
66
67
  }
67
- function buildItems(node) {
68
- return [...node.children.values()].toSorted(compareNodes).map(nodeToItem);
68
+ function buildItems(node, folderIcons) {
69
+ return [...node.children.values()].toSorted(compareNodes).map((child) => nodeToItem(child, folderIcons));
69
70
  }
70
- function nodeToItem(node) {
71
+ function nodeToItem(node, folderIcons) {
71
72
  const singleIndex = node.indexes.length === 1 ? node.indexes[0] : void 0;
72
73
  const primary = singleIndex ?? (node.indexes.length === 0 ? node.page : void 0);
73
74
  const extra = node.indexes.length <= 1 ? singleIndex && node.page ? [node.page] : [] : [...node.indexes, ...node.page ? [node.page] : []];
74
75
  const children = [
75
76
  ...extra.map((entry) => toItem(entry)),
76
- ...buildItems(node)
77
+ ...buildItems(node, folderIcons)
77
78
  ];
78
79
  if (primary) return toItem(primary, children);
79
80
  return {
80
81
  label: titleFromSegment(node.segment),
82
+ ...folderIcons[node.path] ? { icon: folderIcons[node.path] } : {},
81
83
  children
82
84
  };
83
85
  }
@@ -85,6 +87,7 @@ function toItem(entry, children = []) {
85
87
  return {
86
88
  label: entry.title,
87
89
  href: entry.entry.permalink,
90
+ ...entry.entry.icon ? { icon: entry.entry.icon } : {},
88
91
  ...children.length === 0 ? {} : { children }
89
92
  };
90
93
  }
@@ -99,8 +102,8 @@ function compareNodes(a, b) {
99
102
  if (title !== 0) return title;
100
103
  return a.segment.localeCompare(b.segment, "en", { sensitivity: "base" });
101
104
  }
102
- function createTreeNode(segment) {
103
- return { segment, indexes: [], children: /* @__PURE__ */ new Map() };
105
+ function createTreeNode(segment, path) {
106
+ return { segment, path, indexes: [], children: /* @__PURE__ */ new Map() };
104
107
  }
105
108
  function titleFromSegment(segment) {
106
109
  return segment.replace(/[-_]+/g, " ").replace(/\b\p{L}/gu, (letter) => letter.toLocaleUpperCase("en"));
@@ -214,7 +217,7 @@ function NavigationItemView(props) {
214
217
  const active = item.href ? isActive(item.href, path, language) : false;
215
218
  const href = item.href ? localizeHref?.(item.href) ?? item.href : void 0;
216
219
  return /* @__PURE__ */ jsxs("li", { class: "rb-nav__item", children: [
217
- href ? /* @__PURE__ */ jsx(
220
+ href ? /* @__PURE__ */ jsxs(
218
221
  "a",
219
222
  {
220
223
  href,
@@ -222,9 +225,27 @@ function NavigationItemView(props) {
222
225
  "aria-current": active ? "page" : void 0,
223
226
  target: item.external ? "_blank" : void 0,
224
227
  rel: item.external ? "noreferrer" : void 0,
225
- children: item.label
228
+ children: [
229
+ item.icon ? /* @__PURE__ */ jsx(
230
+ "span",
231
+ {
232
+ class: "rb-nav__icon",
233
+ dangerouslySetInnerHTML: { __html: item.icon.html }
234
+ }
235
+ ) : null,
236
+ item.label
237
+ ]
226
238
  }
227
- ) : /* @__PURE__ */ jsx("span", { class: "rb-nav__label", children: item.label }),
239
+ ) : /* @__PURE__ */ jsxs("span", { class: "rb-nav__label", children: [
240
+ item.icon ? /* @__PURE__ */ jsx(
241
+ "span",
242
+ {
243
+ class: "rb-nav__icon",
244
+ dangerouslySetInnerHTML: { __html: item.icon.html }
245
+ }
246
+ ) : null,
247
+ item.label
248
+ ] }),
228
249
  item.children && item.children.length > 0 ? /* @__PURE__ */ jsx(
229
250
  NavigationItems,
230
251
  {
@@ -272,13 +293,11 @@ function navigation(options = {}) {
272
293
  ]
273
294
  });
274
295
  }
275
- var navigationPlugin = navigation;
276
296
  export {
277
297
  MobileSiteNav,
278
298
  NAVIGATION_PLUGIN_NAME,
279
299
  SiteNav,
280
300
  buildNavigation,
281
301
  navigation,
282
- navigationPlugin,
283
302
  resolveSiteNavigation
284
303
  };
@@ -1,6 +1,8 @@
1
- import { type ConfigValidationIssue, type ContentManifest, type ContentManifestEntry } from "@riebeckite/core";
1
+ import { type ConfigValidationIssue, type ContentIcon, type ContentManifest, type ContentManifestEntry } from "@riebeckite/core";
2
2
  import type { NavigationOptions, NavigationSource, SiteNavigation } from "./types.js";
3
+ type FolderIcons = Readonly<Record<string, ContentIcon>>;
3
4
  export declare const NAVIGATION_PLUGIN_NAME = "navigation";
4
- export declare function buildNavigation(entries: readonly ContentManifestEntry[], options?: NavigationOptions): SiteNavigation;
5
+ export declare function buildNavigation(entries: readonly ContentManifestEntry[], options?: NavigationOptions, folderIcons?: FolderIcons): SiteNavigation;
5
6
  export declare function resolveSiteNavigation(source: NavigationSource, manifest: ContentManifest, language?: string): SiteNavigation | null;
6
7
  export declare function validateNavigationOptions(options: NavigationOptions | undefined): readonly ConfigValidationIssue[];
8
+ export {};
@@ -1,7 +1,8 @@
1
- import type { ContentManifestEntry } from "@riebeckite/core";
1
+ import type { ContentIcon, ContentManifestEntry } from "@riebeckite/core";
2
2
  export type NavigationItem = {
3
3
  readonly label: string;
4
4
  readonly href?: string;
5
+ readonly icon?: ContentIcon;
5
6
  readonly children?: readonly NavigationItem[];
6
7
  readonly external?: boolean;
7
8
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riebeckite/plugin-navigation",
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,14 +15,14 @@
15
15
  "./style.css": "./style.css"
16
16
  },
17
17
  "dependencies": {
18
- "hono": "^4.12.25",
19
- "@riebeckite/core": "0.0.32",
20
- "@riebeckite/ui": "0.0.32"
18
+ "@riebeckite/core": "0.0.33",
19
+ "@riebeckite/ui": "0.0.33",
20
+ "hono": "^4.12.25"
21
21
  },
22
22
  "devDependencies": {
23
- "puppeteer": "^25.11.0",
24
- "tsx": "^4.22.4",
25
- "@riebeckite/test": "0.0.32"
23
+ "@riebeckite/test": "0.0.33",
24
+ "puppeteer": "^25.16.0",
25
+ "tsx": "^4.22.4"
26
26
  },
27
27
  "publishConfig": {
28
28
  "access": "public"
package/style.css CHANGED
@@ -65,6 +65,11 @@
65
65
  text-transform: var(--rb-nav-item-text-transform, none);
66
66
  }
67
67
 
68
+ .rb-nav__icon {
69
+ margin-inline-end: 0.35em;
70
+ vertical-align: -0.125em;
71
+ }
72
+
68
73
  .rb-nav__link:hover,
69
74
  .rb-nav__link:focus-visible,
70
75
  .rb-nav__link--active {