@riebeckite/plugin-permalink 0.0.12 → 0.0.14

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.
Files changed (3) hide show
  1. package/README.md +965 -965
  2. package/README_ja.md +985 -985
  3. package/package.json +3 -3
package/README_ja.md CHANGED
@@ -1,985 +1,985 @@
1
- # @riebeckite/plugin-permalink
2
-
3
- Riebeckite の記事に対して、公開 URL(permalink)を決定するプラグインです。
4
-
5
- ファイル構造をそのまま URL に使う代わりに、frontmatter の ID、ファイルパスから導出した ID、独自の resolver などを使って公開 URL を構築できます。
6
-
7
- 旧 URL から正準 URL へのリダイレクトも同じ設定から登録できます。
8
-
9
- [English](./README.md)
10
-
11
- ## 基本的な使い方
12
-
13
- ```ts
14
- import { defineConfig } from "@riebeckite/core";
15
- import { permalink } from "@riebeckite/plugin-permalink";
16
-
17
- export default defineConfig({
18
- plugins: [
19
- permalink({
20
- frontmatter: "id",
21
- id: {
22
- strategy: "frontmatter-or-hash",
23
- length: 12,
24
- },
25
- path: {
26
- mode: "flat",
27
- prefix: "/n",
28
- trailingSlash: false,
29
- },
30
- redirects: {
31
- frontmatter: "redirect_from",
32
- status: 308,
33
- },
34
- }),
35
- ],
36
- });
37
- ```
38
-
39
- 例えば次の記事があるとします。
40
-
41
- ```md
42
- ---
43
- id: hello-world
44
- ---
45
-
46
- # Hello
47
- ```
48
-
49
- `path.mode: "flat"`、`path.prefix: "/n"` なら公開 URL は次のようになります。
50
-
51
- ```text
52
- /n/hello-world
53
- ```
54
-
55
- frontmatter に ID がない場合も、既定の `frontmatter-or-hash` ではファイルパスから決定論的な ID を生成できます。
56
-
57
- そのため、既存の Obsidian Vault の全記事へ `id` を追加する必要はありません。
58
-
59
- ---
60
-
61
- ## 仕組み
62
-
63
- `permalink()` はビルド時の `resolveContentLocations` フックを利用して、各記事の公開位置を解決します。
64
-
65
- 概念的には次の流れです。
66
-
67
- ```text
68
- Content
69
- │
70
- ├─ frontmatter
71
- ├─ slug
72
- └─ source path
73
- │
74
- ▼
75
- @riebeckite/plugin-permalink
76
- │
77
- ├─ resolve ID
78
- ├─ resolve path
79
- ├─ normalize
80
- ├─ validate
81
- └─ redirects
82
- │
83
- ▼
84
- ContentPublicLocation
85
- │
86
- ├─ permalink
87
- ├─ redirects
88
- └─ metadata
89
- ```
90
-
91
- Core は返された `permalink` を正準 URL として扱います。
92
-
93
- その URL は、Wikilink、Backlinks、Search、Content Graph、SEO canonical、Sitemap、RSS / Atom / JSON Feed などから共通して参照されます。
94
-
95
- Permalink Pluginを使用しない場合も、Coreの正式なdefault resolver `resolveDefaultContentLocation` が `index` を `/`、それ以外を `/{slug}` に解決します。これはfallbackではなく、Coreの標準policyです。
96
-
97
- ---
98
-
99
- # Frontmatter
100
-
101
- ## ID
102
-
103
- 既定では `id` を読み取ります。
104
-
105
- ```md
106
- ---
107
- id: hello-world
108
- ---
109
- ```
110
-
111
- フィールド名は変更できます。
112
-
113
- ```ts
114
- permalink({
115
- frontmatter: "uid",
116
- });
117
- ```
118
-
119
- ```md
120
- ---
121
- uid: hello-world
122
- ---
123
- ```
124
-
125
- `frontmatter` が指定するのはトップレベルのフィールドです。
126
-
127
- ## Permalink override
128
-
129
- 特定の記事だけURLを直接指定できます。
130
-
131
- ```md
132
- ---
133
- id: about-page
134
- permalink: /about
135
- ---
136
- ```
137
-
138
- この場合、通常のIDからのパス生成より `/about` が優先されます。
139
-
140
- `id` と公開URLは別の概念なので、
141
-
142
- ```text
143
- ID
144
- about-page
145
-
146
- Canonical URL
147
- /about
148
- ```
149
-
150
- として扱われます。
151
-
152
- フィールド名は変更できます。
153
-
154
- ```ts
155
- permalink({
156
- override: {
157
- frontmatter: "url",
158
- },
159
- });
160
- ```
161
-
162
- ## Redirects
163
-
164
- 旧URLもfrontmatterから指定できます。
165
-
166
- ```md
167
- ---
168
- id: hello-world
169
- redirect_from:
170
- - /posts/hello
171
- - /blog/2024/hello-world
172
- ---
173
- ```
174
-
175
- 例えば正準URLが、
176
-
177
- ```text
178
- /n/hello-world
179
- ```
180
-
181
- なら、
182
-
183
- ```text
184
- /posts/hello
185
- ↓ 308
186
- /n/hello-world
187
-
188
- /blog/2024/hello-world
189
- ↓ 308
190
- /n/hello-world
191
- ```
192
-
193
- として扱われます。
194
-
195
- 設定:
196
-
197
- ```ts
198
- permalink({
199
- redirects: {
200
- frontmatter: "redirect_from",
201
- status: 308,
202
- },
203
- });
204
- ```
205
-
206
- `redirect_from` は文字列または文字列配列を受け取ります。
207
-
208
- ---
209
-
210
- # オプション
211
-
212
- | オプション | 型 | 既定値 | 内容 |
213
- | --- | --- | --- | --- |
214
- | `frontmatter` | `string` | `"id"` | IDとして読むトップレベルのfrontmatterフィールド |
215
- | `id.strategy` | `"frontmatter" \| "hash" \| "frontmatter-or-hash"` | `"frontmatter-or-hash"` | IDの決定方法 |
216
- | `id.length` | `number` | `12` | ハッシュIDの文字数(6〜43) |
217
- | `path.mode` | `"flat" \| "preserve" \| "append"` | `"flat"` | IDを公開URLへ配置する方式 |
218
- | `path.prefix` | `string` | `"/n"` | URL prefix。`"/"`または空文字でprefixなし |
219
- | `path.trailingSlash` | `boolean` | `false` | 正準URL末尾へ `/` を付ける |
220
- | `index.collapse` | `boolean` | `true` | `index`ファイルのパスをcollapseする |
221
- | `override.frontmatter` | `string` | `"permalink"` | URLを直接指定するfrontmatterフィールド |
222
- | `redirects.frontmatter` | `string` | `"redirect_from"` | 旧URLを指定するfrontmatterフィールド |
223
- | `redirects.status` | `301 \| 302 \| 307 \| 308` | `308` | リダイレクトHTTP status |
224
- | `resolveId` | `(content) => string` | — | ID解決を完全にカスタマイズする |
225
- | `resolvePath` | `({ content, id }) => string` | — | 公開パス生成を完全にカスタマイズする |
226
-
227
- ---
228
-
229
- # ID Strategy
230
-
231
- ## `frontmatter`
232
-
233
- frontmatterからIDを取得します。
234
-
235
- ```ts
236
- permalink({
237
- frontmatter: "id",
238
- id: {
239
- strategy: "frontmatter",
240
- },
241
- });
242
- ```
243
-
244
- IDが存在しない記事はビルドエラーになります。
245
-
246
- 永続的なURLを明示的に管理したい場合に向いています。
247
-
248
- ```md
249
- ---
250
- id: article-123
251
- ---
252
- ```
253
-
254
- ファイルをrename/moveしても、`flat` modeならIDおよびURLを維持できます。
255
-
256
- ---
257
-
258
- ## `hash`
259
-
260
- frontmatterを使用せず、ファイルパスからIDを導出します。
261
-
262
- ```ts
263
- permalink({
264
- id: {
265
- strategy: "hash",
266
- length: 12,
267
- },
268
- });
269
- ```
270
-
271
- 入力pathは、
272
-
273
- - `\` → `/`
274
- - 先頭 `/` の除去
275
- - Unicode NFC正規化
276
-
277
- を行った上でSHA-256へ渡されます。
278
-
279
- 結果をbase64urlとして表現し、その先頭`id.length`文字をIDとして利用します。
280
-
281
- ```text
282
- notes/flutter/riverpod.md
283
- │
284
- ▼
285
- normalized path
286
- │
287
- ▼
288
- SHA-256
289
- │
290
- ▼
291
- base64url
292
- │
293
- ▼
294
- K7m3Qp8d...
295
- ```
296
-
297
- 同じpathならOSやbuild環境が異なっても同じIDになります。
298
-
299
- ただし、pathが入力なのでrename/moveするとIDも変わります。
300
-
301
- ---
302
-
303
- ## `frontmatter-or-hash`
304
-
305
- 既定値です。
306
-
307
- ```ts
308
- permalink({
309
- frontmatter: "id",
310
- id: {
311
- strategy: "frontmatter-or-hash",
312
- },
313
- });
314
- ```
315
-
316
- 解決順序は、
317
-
318
- ```text
319
- frontmatter IDあり
320
- ↓
321
- frontmatter ID
322
-
323
- frontmatter IDなし
324
- ↓
325
- path-derived hash
326
- ```
327
-
328
- です。
329
-
330
- 既存Obsidian Vaultを変更せず利用しながら、重要な記事だけ永続IDを与えたい場合に向いています。
331
-
332
- ---
333
-
334
- # ID metadata
335
-
336
- 解決されたIDの由来はmetadataから確認できます。
337
-
338
- | `metadata.idSource` | 意味 |
339
- | --- | --- |
340
- | `frontmatter` | frontmatterから取得 |
341
- | `derived` | path hashから導出 |
342
- | `custom` | `resolveId`から取得 |
343
-
344
- 手動`permalink` overrideはURLを変更しますがidentityは消しません。frontmatterのIDがあれば引き続き記録され(`metadata.idSource` は `frontmatter`)、明示的なIDがない場合のみmetadataは付与されません。overrideから暗黙の `/{id}` URLを生成することはありません。
345
-
346
- ---
347
-
348
- # URL Path Mode
349
-
350
- IDの決定方法と、URLの構築方法は独立しています。
351
-
352
- 例えば、
353
-
354
- ```text
355
- source:
356
- notes/flutter/hello.md
357
-
358
- ID:
359
- hello-world
360
- ```
361
-
362
- とします。
363
-
364
- ## `flat`
365
-
366
- ```ts
367
- path: {
368
- mode: "flat",
369
- prefix: "/n",
370
- }
371
- ```
372
-
373
- 結果:
374
-
375
- ```text
376
- /n/hello-world
377
- ```
378
-
379
- filesystemのディレクトリ構造をURLへ含めません。
380
-
381
- OpaqueなURLを作りたい場合に適しています。
382
-
383
- ---
384
-
385
- ## `preserve`
386
-
387
- ディレクトリ構造を残しつつIDを配置します。
388
-
389
- ```ts
390
- path: {
391
- mode: "preserve",
392
- prefix: "/n",
393
- }
394
- ```
395
-
396
- 例:
397
-
398
- ```text
399
- notes/flutter/hello.md
400
-
401
- ↓
402
-
403
- /n/notes/flutter/hello-world
404
- ```
405
-
406
- ---
407
-
408
- ## `append`
409
-
410
- `append`も選択できます。
411
-
412
- ```ts
413
- path: {
414
- mode: "append",
415
- }
416
- ```
417
-
418
- 現在の実装では`append`は`preserve`と同じパスを返します。
419
-
420
- 将来的に異なる意味を持たせる場合に備えてmodeとして分離されています。`append`固有の挙動を期待する場合は、現在の実装仕様に注意してください。
421
-
422
- ---
423
-
424
- # index ファイル
425
-
426
- `index.collapse`によって`index.md`の扱いを変更できます。
427
-
428
- ```ts
429
- index: {
430
- collapse: true,
431
- }
432
- ```
433
-
434
- 例えば、
435
-
436
- ```text
437
- notes/flutter/index.md
438
- ```
439
-
440
- IDが`hello-world`の場合:
441
-
442
- | 設定 | URL |
443
- | --- | --- |
444
- | `flat` | `/n/hello-world` |
445
- | `preserve` + collapse | `/n/notes/flutter/hello-world` |
446
- | `preserve` + collapseなし | `/n/notes/flutter/index/hello-world` |
447
-
448
- `flat`ではfilesystem structure自体を使用しないため、collapseの影響を受けません。
449
-
450
- ---
451
-
452
- # 上級者向け: Custom Resolver
453
-
454
- 組み込みstrategyで表現できないURL設計では、`resolveId`と`resolvePath`を利用できます。
455
-
456
- Permalink Pluginの通常設定で十分な場合は、custom resolverを使う必要はありません。
457
-
458
- ## `resolveId`
459
-
460
- 記事から独自IDを生成します。
461
-
462
- ```ts
463
- permalink({
464
- resolveId(content) {
465
- return `post-${content.slug}`;
466
- },
467
-
468
- path: {
469
- mode: "flat",
470
- prefix: "/articles",
471
- },
472
- });
473
- ```
474
-
475
- 概念的には、
476
-
477
- ```text
478
- Content
479
- ↓
480
- resolveId(content)
481
- ↓
482
- ID
483
- ↓
484
- built-in path resolver
485
- ↓
486
- canonical URL
487
- ```
488
-
489
- となります。
490
-
491
- `resolveId`の戻り値も組み込みIDと同じvalidationを通ります。
492
-
493
- そのため、不正なIDを返してvalidationを迂回することはできません。
494
-
495
- ### 例: frontmatterを組み合わせる
496
-
497
- 例えばプロジェクト固有のfrontmatterからIDを作る場合:
498
-
499
- ```md
500
- ---
501
- category: flutter
502
- serial: 42
503
- ---
504
- ```
505
-
506
- ```ts
507
- permalink({
508
- resolveId(content) {
509
- const category = content.frontmatter.category;
510
- const serial = content.frontmatter.serial;
511
-
512
- if (typeof category !== "string") {
513
- throw new Error("category is required");
514
- }
515
-
516
- if (typeof serial !== "number") {
517
- throw new Error("serial is required");
518
- }
519
-
520
- return `${category}-${serial}`;
521
- },
522
-
523
- path: {
524
- mode: "flat",
525
- prefix: "/articles",
526
- },
527
- });
528
- ```
529
-
530
- 結果:
531
-
532
- ```text
533
- /articles/flutter-42
534
- ```
535
-
536
- Custom resolver内で扱うfrontmatterのvalidationはresolver側の責務です。
537
-
538
- ---
539
-
540
- # 上級者向け: `resolvePath`
541
-
542
- `resolvePath`を使うと、IDをURLのどこへ配置するかを完全に制御できます。
543
-
544
- ```ts
545
- permalink({
546
- frontmatter: "id",
547
-
548
- id: {
549
- strategy: "frontmatter-or-hash",
550
- },
551
-
552
- resolvePath({ content, id }) {
553
- return `/articles/${id}`;
554
- },
555
- });
556
- ```
557
-
558
- 結果:
559
-
560
- ```text
561
- /articles/hello-world
562
- ```
563
-
564
- `resolvePath`はサイト内の絶対パスを返してください。
565
-
566
- ```text
567
- /articles/hello OK
568
- /articles/hello/ OK
569
- articles/hello NG
570
- https://example.com NG
571
- ```
572
-
573
- 戻り値はPluginの通常のURL normalization / validation / collision detectionを通ります。
574
-
575
- ---
576
-
577
- # `resolveId` と `resolvePath` を組み合わせる
578
-
579
- 両方を指定すると、ID決定とURL構築を完全にカスタマイズできます。
580
-
581
- 例えば年別URLを作る場合:
582
-
583
- ```md
584
- ---
585
- published: 2026-09-28
586
- article_id: riebeckite-permalink
587
- ---
588
- ```
589
-
590
- ```ts
591
- permalink({
592
- resolveId(content) {
593
- const value = content.frontmatter.article_id;
594
-
595
- if (typeof value !== "string") {
596
- throw new Error("article_id is required");
597
- }
598
-
599
- return value;
600
- },
601
-
602
- resolvePath({ content, id }) {
603
- const published = content.frontmatter.published;
604
-
605
- if (typeof published !== "string") {
606
- throw new Error("published is required");
607
- }
608
-
609
- const year = published.slice(0, 4);
610
-
611
- return `/articles/${year}/${id}`;
612
- },
613
- });
614
- ```
615
-
616
- 結果:
617
-
618
- ```text
619
- /articles/2026/riebeckite-permalink
620
- ```
621
-
622
- この場合もRiebeckite内部では最終的に解決されたURLだけが正準URLとして扱われます。
623
-
624
- ---
625
-
626
- # Custom Resolverの使い分け
627
-
628
- 基本的には次の順序で検討してください。
629
-
630
- ```text
631
- built-in strategyで足りる
632
- ↓
633
- 通常optionsを使用
634
-
635
- IDだけ特殊
636
- ↓
637
- resolveId
638
-
639
- URL構造だけ特殊
640
- ↓
641
- resolvePath
642
-
643
- 両方特殊
644
- ↓
645
- resolveId + resolvePath
646
- ```
647
-
648
- 例えば、
649
-
650
- ```text
651
- /n/{id}
652
- ```
653
-
654
- を作るだけならcustom resolverは不要です。
655
-
656
- ```ts
657
- permalink({
658
- id: {
659
- strategy: "frontmatter-or-hash",
660
- },
661
- path: {
662
- mode: "flat",
663
- prefix: "/n",
664
- },
665
- });
666
- ```
667
-
668
- こちらの方が設定意図が明確です。
669
-
670
- ---
671
-
672
- # Custom Resolverでも維持される保証
673
-
674
- Custom resolverを使用しても、Permalink Pluginの以下の処理は維持されます。
675
-
676
- - ID validation
677
- - URL normalization
678
- - URL validation
679
- - ID collision detection
680
- - canonical URL collision detection
681
- - redirect collision detection
682
- - trailing slash handling
683
- - Coreへのcanonical public location登録
684
-
685
- つまりcustom resolverはPluginの安全機構を迂回するAPIではありません。
686
-
687
- ---
688
-
689
- # Manual permalinkとの優先順位
690
-
691
- frontmatterにmanual permalinkが存在する場合は、通常のID解決とpath構築より優先されます。
692
-
693
- 例えば:
694
-
695
- ```md
696
- ---
697
- id: abc
698
- permalink: /about
699
- ---
700
- ```
701
-
702
- では、
703
-
704
- ```text
705
- ID候補
706
- abc
707
-
708
- Canonical URL
709
- /about
710
- ```
711
-
712
- となります。
713
-
714
- `resolvePath`で別のURLを返す設定があっても、manual permalink overrideが優先されます。
715
-
716
- 特殊ページだけURLを固定したい場合に利用できます。
717
-
718
- ---
719
-
720
- # Statelessな設計
721
-
722
- Permalink Pluginは永続的なID registryを持ちません。
723
-
724
- 以下は作成しません。
725
-
726
- ```text
727
- .riebeckite/content-ids.json
728
- state.json
729
- SQLite database
730
- KV database
731
- ```
732
-
733
- 必要な情報は、
734
-
735
- ```text
736
- Plugin configuration
737
- +
738
- source content
739
- ```
740
-
741
- からビルド時に決定されます。
742
-
743
- そのためCloudflare Workers、CI、別PCなどでも同じ入力から同じURLを生成できます。
744
-
745
- `hash` strategyではpathがidentity生成の入力になるため、rename/moveによってURLが変化します。
746
-
747
- rename/move後もURLを維持したい記事にはfrontmatter IDを利用してください。
748
-
749
- ---
750
-
751
- # Rename / Move時の挙動
752
-
753
- URLの安定性はID strategyとpath modeの組み合わせによって変わります。
754
-
755
- | ID | Path mode | Rename | Move |
756
- | --- | --- | --- | --- |
757
- | frontmatter | flat | 維持 | 維持 |
758
- | frontmatter | preserve | 状況により変更 | 変更 |
759
- | frontmatter | append | 状況により変更 | 変更 |
760
- | hash | flat | 変更 | 変更 |
761
- | hash | preserve | 変更 | 変更 |
762
- | hash | append | 変更 | 変更 |
763
-
764
- 永続的なURLが必要なら、
765
-
766
- ```ts
767
- permalink({
768
- frontmatter: "id",
769
- id: {
770
- strategy: "frontmatter",
771
- },
772
- path: {
773
- mode: "flat",
774
- },
775
- });
776
- ```
777
-
778
- が最も明示的です。
779
-
780
- 既存Vaultへの変更を最小化したい場合は、
781
-
782
- ```ts
783
- id: {
784
- strategy: "frontmatter-or-hash",
785
- }
786
- ```
787
-
788
- が便利です。
789
-
790
- ---
791
-
792
- # 検証
793
-
794
- ## ID
795
-
796
- IDは単一のURL path segmentとして扱われます。
797
-
798
- 以下はエラーになります。
799
-
800
- - 空文字列
801
- - `/`を含む
802
- - `#`を含む
803
- - `?`を含む
804
- - 空白を含む
805
- - 正しくdecodeできないpercent encoding
806
-
807
- Custom `resolveId`の結果も同じ検証を受けます。
808
-
809
- ## Permalink
810
-
811
- Permalinkとredirectはサイト内の絶対pathである必要があります。
812
-
813
- 以下は許可されません。
814
-
815
- - relative path
816
- - query string
817
- - fragment
818
- - `\`
819
- - 不正な `//`
820
- - 外部URL
821
-
822
- Custom `resolvePath`の結果も同じ検証を受けます。
823
-
824
- ---
825
-
826
- # Collision Detection
827
-
828
- Permalink Pluginはビルド時に衝突を検出します。
829
-
830
- 対象:
831
-
832
- - ID ↔ ID
833
- - canonical URL ↔ canonical URL
834
- - canonical URL ↔ redirect
835
- - redirect ↔ redirect
836
-
837
- 例えば:
838
-
839
- ```text
840
- a.md
841
- → /about
842
-
843
- b.md
844
- → /about
845
- ```
846
-
847
- はビルドエラーです。
848
-
849
- また、
850
-
851
- ```text
852
- a.md canonical
853
- → /about
854
-
855
- b.md redirect
856
- → /about
857
- ```
858
-
859
- もエラーです。
860
-
861
- 自動的にsuffixを付けて回避することはありません。
862
-
863
- これにより、ビルド順序によってURLが変化することを防ぎます。
864
-
865
- ---
866
-
867
- # Inspect
868
-
869
- 解決結果はRiebeckiteのinspect機能から確認できます。
870
-
871
- ```sh
872
- riebeckite inspect content --list
873
- ```
874
-
875
- Permalink Pluginが有効な場合、記事ごとのID、ID source、permalinkを確認できます。
876
-
877
- 例えば:
878
-
879
- ```text
880
- PATH ID ID SOURCE PERMALINK
881
- notes/a.md K7m3Qp8d... derived /n/K7m3Qp8d...
882
- notes/about.md about frontmatter /about
883
- ```
884
-
885
- 実際の表示形式はCLIバージョンによって異なる場合があります。
886
-
887
- ---
888
-
889
- # 主なエクスポート
890
-
891
- ## Functions
892
-
893
- - `permalink(options?)`
894
- - `permalinkPlugin(options?)`
895
-
896
- どちらもPermalink Pluginを生成します。
897
-
898
- ## Types
899
-
900
- - `PermalinkOptions`
901
- - `PermalinkIdStrategy`
902
- - `PermalinkPathMode`
903
- - `RedirectStatus`
904
-
905
- Custom resolverを書く場合は、公開されている型を利用して設定を型安全に記述できます。
906
-
907
- ---
908
-
909
- # 設定例
910
-
911
- ## 既存Obsidian Vault向け
912
-
913
- frontmatter変更を必須にせず、URLからfilesystem構造を隠します。
914
-
915
- ```ts
916
- permalink({
917
- frontmatter: "id",
918
- id: {
919
- strategy: "frontmatter-or-hash",
920
- length: 12,
921
- },
922
- path: {
923
- mode: "flat",
924
- prefix: "/n",
925
- },
926
- });
927
- ```
928
-
929
- ## 完全に明示的な永続URL
930
-
931
- ```ts
932
- permalink({
933
- frontmatter: "id",
934
- id: {
935
- strategy: "frontmatter",
936
- },
937
- path: {
938
- mode: "flat",
939
- prefix: "/n",
940
- },
941
- });
942
- ```
943
-
944
- すべての記事にfrontmatter IDが必要になります。
945
-
946
- ## ディレクトリ構造を残す
947
-
948
- ```ts
949
- permalink({
950
- id: {
951
- strategy: "frontmatter-or-hash",
952
- },
953
- path: {
954
- mode: "preserve",
955
- prefix: "",
956
- },
957
- });
958
- ```
959
-
960
- ## 独自URL設計
961
-
962
- ```ts
963
- permalink({
964
- resolveId(content) {
965
- // project-specific ID
966
- return "...";
967
- },
968
-
969
- resolvePath({ content, id }) {
970
- // project-specific URL
971
- return `/articles/${id}`;
972
- },
973
- });
974
- ```
975
-
976
- ---
977
-
978
- # 関連資料
979
-
980
- - [プラグインシステム](../../../docs/ja/plugin-system.md)
981
- - [コンテンツシステム](../../../docs/ja/content-system.md)
982
-
983
- ## ????
984
-
985
- - [?????????](../../../docs/ja/plugin-system.md)
1
+ # @riebeckite/plugin-permalink
2
+
3
+ Riebeckite の記事に対して、公開 URL(permalink)を決定するプラグインです。
4
+
5
+ ファイル構造をそのまま URL に使う代わりに、frontmatter の ID、ファイルパスから導出した ID、独自の resolver などを使って公開 URL を構築できます。
6
+
7
+ 旧 URL から正準 URL へのリダイレクトも同じ設定から登録できます。
8
+
9
+ [English](./README.md)
10
+
11
+ ## 基本的な使い方
12
+
13
+ ```ts
14
+ import { defineConfig } from "@riebeckite/core";
15
+ import { permalink } from "@riebeckite/plugin-permalink";
16
+
17
+ export default defineConfig({
18
+ plugins: [
19
+ permalink({
20
+ frontmatter: "id",
21
+ id: {
22
+ strategy: "frontmatter-or-hash",
23
+ length: 12,
24
+ },
25
+ path: {
26
+ mode: "flat",
27
+ prefix: "/n",
28
+ trailingSlash: false,
29
+ },
30
+ redirects: {
31
+ frontmatter: "redirect_from",
32
+ status: 308,
33
+ },
34
+ }),
35
+ ],
36
+ });
37
+ ```
38
+
39
+ 例えば次の記事があるとします。
40
+
41
+ ```md
42
+ ---
43
+ id: hello-world
44
+ ---
45
+
46
+ # Hello
47
+ ```
48
+
49
+ `path.mode: "flat"`、`path.prefix: "/n"` なら公開 URL は次のようになります。
50
+
51
+ ```text
52
+ /n/hello-world
53
+ ```
54
+
55
+ frontmatter に ID がない場合も、既定の `frontmatter-or-hash` ではファイルパスから決定論的な ID を生成できます。
56
+
57
+ そのため、既存の Obsidian Vault の全記事へ `id` を追加する必要はありません。
58
+
59
+ ---
60
+
61
+ ## 仕組み
62
+
63
+ `permalink()` はビルド時の `resolveContentLocations` フックを利用して、各記事の公開位置を解決します。
64
+
65
+ 概念的には次の流れです。
66
+
67
+ ```text
68
+ Content
69
+ │
70
+ ├─ frontmatter
71
+ ├─ slug
72
+ └─ source path
73
+ │
74
+ ▼
75
+ @riebeckite/plugin-permalink
76
+ │
77
+ ├─ resolve ID
78
+ ├─ resolve path
79
+ ├─ normalize
80
+ ├─ validate
81
+ └─ redirects
82
+ │
83
+ ▼
84
+ ContentPublicLocation
85
+ │
86
+ ├─ permalink
87
+ ├─ redirects
88
+ └─ metadata
89
+ ```
90
+
91
+ Core は返された `permalink` を正準 URL として扱います。
92
+
93
+ その URL は、Wikilink、Backlinks、Search、Content Graph、SEO canonical、Sitemap、RSS / Atom / JSON Feed などから共通して参照されます。
94
+
95
+ Permalink Pluginを使用しない場合も、Coreの正式なdefault resolver `resolveDefaultContentLocation` が `index` を `/`、それ以外を `/{slug}` に解決します。これはfallbackではなく、Coreの標準policyです。
96
+
97
+ ---
98
+
99
+ # Frontmatter
100
+
101
+ ## ID
102
+
103
+ 既定では `id` を読み取ります。
104
+
105
+ ```md
106
+ ---
107
+ id: hello-world
108
+ ---
109
+ ```
110
+
111
+ フィールド名は変更できます。
112
+
113
+ ```ts
114
+ permalink({
115
+ frontmatter: "uid",
116
+ });
117
+ ```
118
+
119
+ ```md
120
+ ---
121
+ uid: hello-world
122
+ ---
123
+ ```
124
+
125
+ `frontmatter` が指定するのはトップレベルのフィールドです。
126
+
127
+ ## Permalink override
128
+
129
+ 特定の記事だけURLを直接指定できます。
130
+
131
+ ```md
132
+ ---
133
+ id: about-page
134
+ permalink: /about
135
+ ---
136
+ ```
137
+
138
+ この場合、通常のIDからのパス生成より `/about` が優先されます。
139
+
140
+ `id` と公開URLは別の概念なので、
141
+
142
+ ```text
143
+ ID
144
+ about-page
145
+
146
+ Canonical URL
147
+ /about
148
+ ```
149
+
150
+ として扱われます。
151
+
152
+ フィールド名は変更できます。
153
+
154
+ ```ts
155
+ permalink({
156
+ override: {
157
+ frontmatter: "url",
158
+ },
159
+ });
160
+ ```
161
+
162
+ ## Redirects
163
+
164
+ 旧URLもfrontmatterから指定できます。
165
+
166
+ ```md
167
+ ---
168
+ id: hello-world
169
+ redirect_from:
170
+ - /posts/hello
171
+ - /blog/2024/hello-world
172
+ ---
173
+ ```
174
+
175
+ 例えば正準URLが、
176
+
177
+ ```text
178
+ /n/hello-world
179
+ ```
180
+
181
+ なら、
182
+
183
+ ```text
184
+ /posts/hello
185
+ ↓ 308
186
+ /n/hello-world
187
+
188
+ /blog/2024/hello-world
189
+ ↓ 308
190
+ /n/hello-world
191
+ ```
192
+
193
+ として扱われます。
194
+
195
+ 設定:
196
+
197
+ ```ts
198
+ permalink({
199
+ redirects: {
200
+ frontmatter: "redirect_from",
201
+ status: 308,
202
+ },
203
+ });
204
+ ```
205
+
206
+ `redirect_from` は文字列または文字列配列を受け取ります。
207
+
208
+ ---
209
+
210
+ # オプション
211
+
212
+ | オプション | 型 | 既定値 | 内容 |
213
+ | --- | --- | --- | --- |
214
+ | `frontmatter` | `string` | `"id"` | IDとして読むトップレベルのfrontmatterフィールド |
215
+ | `id.strategy` | `"frontmatter" \| "hash" \| "frontmatter-or-hash"` | `"frontmatter-or-hash"` | IDの決定方法 |
216
+ | `id.length` | `number` | `12` | ハッシュIDの文字数(6〜43) |
217
+ | `path.mode` | `"flat" \| "preserve" \| "append"` | `"flat"` | IDを公開URLへ配置する方式 |
218
+ | `path.prefix` | `string` | `"/n"` | URL prefix。`"/"`または空文字でprefixなし |
219
+ | `path.trailingSlash` | `boolean` | `false` | 正準URL末尾へ `/` を付ける |
220
+ | `index.collapse` | `boolean` | `true` | `index`ファイルのパスをcollapseする |
221
+ | `override.frontmatter` | `string` | `"permalink"` | URLを直接指定するfrontmatterフィールド |
222
+ | `redirects.frontmatter` | `string` | `"redirect_from"` | 旧URLを指定するfrontmatterフィールド |
223
+ | `redirects.status` | `301 \| 302 \| 307 \| 308` | `308` | リダイレクトHTTP status |
224
+ | `resolveId` | `(content) => string` | — | ID解決を完全にカスタマイズする |
225
+ | `resolvePath` | `({ content, id }) => string` | — | 公開パス生成を完全にカスタマイズする |
226
+
227
+ ---
228
+
229
+ # ID Strategy
230
+
231
+ ## `frontmatter`
232
+
233
+ frontmatterからIDを取得します。
234
+
235
+ ```ts
236
+ permalink({
237
+ frontmatter: "id",
238
+ id: {
239
+ strategy: "frontmatter",
240
+ },
241
+ });
242
+ ```
243
+
244
+ IDが存在しない記事はビルドエラーになります。
245
+
246
+ 永続的なURLを明示的に管理したい場合に向いています。
247
+
248
+ ```md
249
+ ---
250
+ id: article-123
251
+ ---
252
+ ```
253
+
254
+ ファイルをrename/moveしても、`flat` modeならIDおよびURLを維持できます。
255
+
256
+ ---
257
+
258
+ ## `hash`
259
+
260
+ frontmatterを使用せず、ファイルパスからIDを導出します。
261
+
262
+ ```ts
263
+ permalink({
264
+ id: {
265
+ strategy: "hash",
266
+ length: 12,
267
+ },
268
+ });
269
+ ```
270
+
271
+ 入力pathは、
272
+
273
+ - `\` → `/`
274
+ - 先頭 `/` の除去
275
+ - Unicode NFC正規化
276
+
277
+ を行った上でSHA-256へ渡されます。
278
+
279
+ 結果をbase64urlとして表現し、その先頭`id.length`文字をIDとして利用します。
280
+
281
+ ```text
282
+ notes/flutter/riverpod.md
283
+ │
284
+ ▼
285
+ normalized path
286
+ │
287
+ ▼
288
+ SHA-256
289
+ │
290
+ ▼
291
+ base64url
292
+ │
293
+ ▼
294
+ K7m3Qp8d...
295
+ ```
296
+
297
+ 同じpathならOSやbuild環境が異なっても同じIDになります。
298
+
299
+ ただし、pathが入力なのでrename/moveするとIDも変わります。
300
+
301
+ ---
302
+
303
+ ## `frontmatter-or-hash`
304
+
305
+ 既定値です。
306
+
307
+ ```ts
308
+ permalink({
309
+ frontmatter: "id",
310
+ id: {
311
+ strategy: "frontmatter-or-hash",
312
+ },
313
+ });
314
+ ```
315
+
316
+ 解決順序は、
317
+
318
+ ```text
319
+ frontmatter IDあり
320
+ ↓
321
+ frontmatter ID
322
+
323
+ frontmatter IDなし
324
+ ↓
325
+ path-derived hash
326
+ ```
327
+
328
+ です。
329
+
330
+ 既存Obsidian Vaultを変更せず利用しながら、重要な記事だけ永続IDを与えたい場合に向いています。
331
+
332
+ ---
333
+
334
+ # ID metadata
335
+
336
+ 解決されたIDの由来はmetadataから確認できます。
337
+
338
+ | `metadata.idSource` | 意味 |
339
+ | --- | --- |
340
+ | `frontmatter` | frontmatterから取得 |
341
+ | `derived` | path hashから導出 |
342
+ | `custom` | `resolveId`から取得 |
343
+
344
+ 手動`permalink` overrideはURLを変更しますがidentityは消しません。frontmatterのIDがあれば引き続き記録され(`metadata.idSource` は `frontmatter`)、明示的なIDがない場合のみmetadataは付与されません。overrideから暗黙の `/{id}` URLを生成することはありません。
345
+
346
+ ---
347
+
348
+ # URL Path Mode
349
+
350
+ IDの決定方法と、URLの構築方法は独立しています。
351
+
352
+ 例えば、
353
+
354
+ ```text
355
+ source:
356
+ notes/flutter/hello.md
357
+
358
+ ID:
359
+ hello-world
360
+ ```
361
+
362
+ とします。
363
+
364
+ ## `flat`
365
+
366
+ ```ts
367
+ path: {
368
+ mode: "flat",
369
+ prefix: "/n",
370
+ }
371
+ ```
372
+
373
+ 結果:
374
+
375
+ ```text
376
+ /n/hello-world
377
+ ```
378
+
379
+ filesystemのディレクトリ構造をURLへ含めません。
380
+
381
+ OpaqueなURLを作りたい場合に適しています。
382
+
383
+ ---
384
+
385
+ ## `preserve`
386
+
387
+ ディレクトリ構造を残しつつIDを配置します。
388
+
389
+ ```ts
390
+ path: {
391
+ mode: "preserve",
392
+ prefix: "/n",
393
+ }
394
+ ```
395
+
396
+ 例:
397
+
398
+ ```text
399
+ notes/flutter/hello.md
400
+
401
+ ↓
402
+
403
+ /n/notes/flutter/hello-world
404
+ ```
405
+
406
+ ---
407
+
408
+ ## `append`
409
+
410
+ `append`も選択できます。
411
+
412
+ ```ts
413
+ path: {
414
+ mode: "append",
415
+ }
416
+ ```
417
+
418
+ 現在の実装では`append`は`preserve`と同じパスを返します。
419
+
420
+ 将来的に異なる意味を持たせる場合に備えてmodeとして分離されています。`append`固有の挙動を期待する場合は、現在の実装仕様に注意してください。
421
+
422
+ ---
423
+
424
+ # index ファイル
425
+
426
+ `index.collapse`によって`index.md`の扱いを変更できます。
427
+
428
+ ```ts
429
+ index: {
430
+ collapse: true,
431
+ }
432
+ ```
433
+
434
+ 例えば、
435
+
436
+ ```text
437
+ notes/flutter/index.md
438
+ ```
439
+
440
+ IDが`hello-world`の場合:
441
+
442
+ | 設定 | URL |
443
+ | --- | --- |
444
+ | `flat` | `/n/hello-world` |
445
+ | `preserve` + collapse | `/n/notes/flutter/hello-world` |
446
+ | `preserve` + collapseなし | `/n/notes/flutter/index/hello-world` |
447
+
448
+ `flat`ではfilesystem structure自体を使用しないため、collapseの影響を受けません。
449
+
450
+ ---
451
+
452
+ # 上級者向け: Custom Resolver
453
+
454
+ 組み込みstrategyで表現できないURL設計では、`resolveId`と`resolvePath`を利用できます。
455
+
456
+ Permalink Pluginの通常設定で十分な場合は、custom resolverを使う必要はありません。
457
+
458
+ ## `resolveId`
459
+
460
+ 記事から独自IDを生成します。
461
+
462
+ ```ts
463
+ permalink({
464
+ resolveId(content) {
465
+ return `post-${content.slug}`;
466
+ },
467
+
468
+ path: {
469
+ mode: "flat",
470
+ prefix: "/articles",
471
+ },
472
+ });
473
+ ```
474
+
475
+ 概念的には、
476
+
477
+ ```text
478
+ Content
479
+ ↓
480
+ resolveId(content)
481
+ ↓
482
+ ID
483
+ ↓
484
+ built-in path resolver
485
+ ↓
486
+ canonical URL
487
+ ```
488
+
489
+ となります。
490
+
491
+ `resolveId`の戻り値も組み込みIDと同じvalidationを通ります。
492
+
493
+ そのため、不正なIDを返してvalidationを迂回することはできません。
494
+
495
+ ### 例: frontmatterを組み合わせる
496
+
497
+ 例えばプロジェクト固有のfrontmatterからIDを作る場合:
498
+
499
+ ```md
500
+ ---
501
+ category: flutter
502
+ serial: 42
503
+ ---
504
+ ```
505
+
506
+ ```ts
507
+ permalink({
508
+ resolveId(content) {
509
+ const category = content.frontmatter.category;
510
+ const serial = content.frontmatter.serial;
511
+
512
+ if (typeof category !== "string") {
513
+ throw new Error("category is required");
514
+ }
515
+
516
+ if (typeof serial !== "number") {
517
+ throw new Error("serial is required");
518
+ }
519
+
520
+ return `${category}-${serial}`;
521
+ },
522
+
523
+ path: {
524
+ mode: "flat",
525
+ prefix: "/articles",
526
+ },
527
+ });
528
+ ```
529
+
530
+ 結果:
531
+
532
+ ```text
533
+ /articles/flutter-42
534
+ ```
535
+
536
+ Custom resolver内で扱うfrontmatterのvalidationはresolver側の責務です。
537
+
538
+ ---
539
+
540
+ # 上級者向け: `resolvePath`
541
+
542
+ `resolvePath`を使うと、IDをURLのどこへ配置するかを完全に制御できます。
543
+
544
+ ```ts
545
+ permalink({
546
+ frontmatter: "id",
547
+
548
+ id: {
549
+ strategy: "frontmatter-or-hash",
550
+ },
551
+
552
+ resolvePath({ content, id }) {
553
+ return `/articles/${id}`;
554
+ },
555
+ });
556
+ ```
557
+
558
+ 結果:
559
+
560
+ ```text
561
+ /articles/hello-world
562
+ ```
563
+
564
+ `resolvePath`はサイト内の絶対パスを返してください。
565
+
566
+ ```text
567
+ /articles/hello OK
568
+ /articles/hello/ OK
569
+ articles/hello NG
570
+ https://example.com NG
571
+ ```
572
+
573
+ 戻り値はPluginの通常のURL normalization / validation / collision detectionを通ります。
574
+
575
+ ---
576
+
577
+ # `resolveId` と `resolvePath` を組み合わせる
578
+
579
+ 両方を指定すると、ID決定とURL構築を完全にカスタマイズできます。
580
+
581
+ 例えば年別URLを作る場合:
582
+
583
+ ```md
584
+ ---
585
+ published: 2026-09-28
586
+ article_id: riebeckite-permalink
587
+ ---
588
+ ```
589
+
590
+ ```ts
591
+ permalink({
592
+ resolveId(content) {
593
+ const value = content.frontmatter.article_id;
594
+
595
+ if (typeof value !== "string") {
596
+ throw new Error("article_id is required");
597
+ }
598
+
599
+ return value;
600
+ },
601
+
602
+ resolvePath({ content, id }) {
603
+ const published = content.frontmatter.published;
604
+
605
+ if (typeof published !== "string") {
606
+ throw new Error("published is required");
607
+ }
608
+
609
+ const year = published.slice(0, 4);
610
+
611
+ return `/articles/${year}/${id}`;
612
+ },
613
+ });
614
+ ```
615
+
616
+ 結果:
617
+
618
+ ```text
619
+ /articles/2026/riebeckite-permalink
620
+ ```
621
+
622
+ この場合もRiebeckite内部では最終的に解決されたURLだけが正準URLとして扱われます。
623
+
624
+ ---
625
+
626
+ # Custom Resolverの使い分け
627
+
628
+ 基本的には次の順序で検討してください。
629
+
630
+ ```text
631
+ built-in strategyで足りる
632
+ ↓
633
+ 通常optionsを使用
634
+
635
+ IDだけ特殊
636
+ ↓
637
+ resolveId
638
+
639
+ URL構造だけ特殊
640
+ ↓
641
+ resolvePath
642
+
643
+ 両方特殊
644
+ ↓
645
+ resolveId + resolvePath
646
+ ```
647
+
648
+ 例えば、
649
+
650
+ ```text
651
+ /n/{id}
652
+ ```
653
+
654
+ を作るだけならcustom resolverは不要です。
655
+
656
+ ```ts
657
+ permalink({
658
+ id: {
659
+ strategy: "frontmatter-or-hash",
660
+ },
661
+ path: {
662
+ mode: "flat",
663
+ prefix: "/n",
664
+ },
665
+ });
666
+ ```
667
+
668
+ こちらの方が設定意図が明確です。
669
+
670
+ ---
671
+
672
+ # Custom Resolverでも維持される保証
673
+
674
+ Custom resolverを使用しても、Permalink Pluginの以下の処理は維持されます。
675
+
676
+ - ID validation
677
+ - URL normalization
678
+ - URL validation
679
+ - ID collision detection
680
+ - canonical URL collision detection
681
+ - redirect collision detection
682
+ - trailing slash handling
683
+ - Coreへのcanonical public location登録
684
+
685
+ つまりcustom resolverはPluginの安全機構を迂回するAPIではありません。
686
+
687
+ ---
688
+
689
+ # Manual permalinkとの優先順位
690
+
691
+ frontmatterにmanual permalinkが存在する場合は、通常のID解決とpath構築より優先されます。
692
+
693
+ 例えば:
694
+
695
+ ```md
696
+ ---
697
+ id: abc
698
+ permalink: /about
699
+ ---
700
+ ```
701
+
702
+ では、
703
+
704
+ ```text
705
+ ID候補
706
+ abc
707
+
708
+ Canonical URL
709
+ /about
710
+ ```
711
+
712
+ となります。
713
+
714
+ `resolvePath`で別のURLを返す設定があっても、manual permalink overrideが優先されます。
715
+
716
+ 特殊ページだけURLを固定したい場合に利用できます。
717
+
718
+ ---
719
+
720
+ # Statelessな設計
721
+
722
+ Permalink Pluginは永続的なID registryを持ちません。
723
+
724
+ 以下は作成しません。
725
+
726
+ ```text
727
+ .riebeckite/content-ids.json
728
+ state.json
729
+ SQLite database
730
+ KV database
731
+ ```
732
+
733
+ 必要な情報は、
734
+
735
+ ```text
736
+ Plugin configuration
737
+ +
738
+ source content
739
+ ```
740
+
741
+ からビルド時に決定されます。
742
+
743
+ そのためCloudflare Workers、CI、別PCなどでも同じ入力から同じURLを生成できます。
744
+
745
+ `hash` strategyではpathがidentity生成の入力になるため、rename/moveによってURLが変化します。
746
+
747
+ rename/move後もURLを維持したい記事にはfrontmatter IDを利用してください。
748
+
749
+ ---
750
+
751
+ # Rename / Move時の挙動
752
+
753
+ URLの安定性はID strategyとpath modeの組み合わせによって変わります。
754
+
755
+ | ID | Path mode | Rename | Move |
756
+ | --- | --- | --- | --- |
757
+ | frontmatter | flat | 維持 | 維持 |
758
+ | frontmatter | preserve | 状況により変更 | 変更 |
759
+ | frontmatter | append | 状況により変更 | 変更 |
760
+ | hash | flat | 変更 | 変更 |
761
+ | hash | preserve | 変更 | 変更 |
762
+ | hash | append | 変更 | 変更 |
763
+
764
+ 永続的なURLが必要なら、
765
+
766
+ ```ts
767
+ permalink({
768
+ frontmatter: "id",
769
+ id: {
770
+ strategy: "frontmatter",
771
+ },
772
+ path: {
773
+ mode: "flat",
774
+ },
775
+ });
776
+ ```
777
+
778
+ が最も明示的です。
779
+
780
+ 既存Vaultへの変更を最小化したい場合は、
781
+
782
+ ```ts
783
+ id: {
784
+ strategy: "frontmatter-or-hash",
785
+ }
786
+ ```
787
+
788
+ が便利です。
789
+
790
+ ---
791
+
792
+ # 検証
793
+
794
+ ## ID
795
+
796
+ IDは単一のURL path segmentとして扱われます。
797
+
798
+ 以下はエラーになります。
799
+
800
+ - 空文字列
801
+ - `/`を含む
802
+ - `#`を含む
803
+ - `?`を含む
804
+ - 空白を含む
805
+ - 正しくdecodeできないpercent encoding
806
+
807
+ Custom `resolveId`の結果も同じ検証を受けます。
808
+
809
+ ## Permalink
810
+
811
+ Permalinkとredirectはサイト内の絶対pathである必要があります。
812
+
813
+ 以下は許可されません。
814
+
815
+ - relative path
816
+ - query string
817
+ - fragment
818
+ - `\`
819
+ - 不正な `//`
820
+ - 外部URL
821
+
822
+ Custom `resolvePath`の結果も同じ検証を受けます。
823
+
824
+ ---
825
+
826
+ # Collision Detection
827
+
828
+ Permalink Pluginはビルド時に衝突を検出します。
829
+
830
+ 対象:
831
+
832
+ - ID ↔ ID
833
+ - canonical URL ↔ canonical URL
834
+ - canonical URL ↔ redirect
835
+ - redirect ↔ redirect
836
+
837
+ 例えば:
838
+
839
+ ```text
840
+ a.md
841
+ → /about
842
+
843
+ b.md
844
+ → /about
845
+ ```
846
+
847
+ はビルドエラーです。
848
+
849
+ また、
850
+
851
+ ```text
852
+ a.md canonical
853
+ → /about
854
+
855
+ b.md redirect
856
+ → /about
857
+ ```
858
+
859
+ もエラーです。
860
+
861
+ 自動的にsuffixを付けて回避することはありません。
862
+
863
+ これにより、ビルド順序によってURLが変化することを防ぎます。
864
+
865
+ ---
866
+
867
+ # Inspect
868
+
869
+ 解決結果はRiebeckiteのinspect機能から確認できます。
870
+
871
+ ```sh
872
+ riebeckite inspect content --list
873
+ ```
874
+
875
+ Permalink Pluginが有効な場合、記事ごとのID、ID source、permalinkを確認できます。
876
+
877
+ 例えば:
878
+
879
+ ```text
880
+ PATH ID ID SOURCE PERMALINK
881
+ notes/a.md K7m3Qp8d... derived /n/K7m3Qp8d...
882
+ notes/about.md about frontmatter /about
883
+ ```
884
+
885
+ 実際の表示形式はCLIバージョンによって異なる場合があります。
886
+
887
+ ---
888
+
889
+ # 主なエクスポート
890
+
891
+ ## Functions
892
+
893
+ - `permalink(options?)`
894
+ - `permalinkPlugin(options?)`
895
+
896
+ どちらもPermalink Pluginを生成します。
897
+
898
+ ## Types
899
+
900
+ - `PermalinkOptions`
901
+ - `PermalinkIdStrategy`
902
+ - `PermalinkPathMode`
903
+ - `RedirectStatus`
904
+
905
+ Custom resolverを書く場合は、公開されている型を利用して設定を型安全に記述できます。
906
+
907
+ ---
908
+
909
+ # 設定例
910
+
911
+ ## 既存Obsidian Vault向け
912
+
913
+ frontmatter変更を必須にせず、URLからfilesystem構造を隠します。
914
+
915
+ ```ts
916
+ permalink({
917
+ frontmatter: "id",
918
+ id: {
919
+ strategy: "frontmatter-or-hash",
920
+ length: 12,
921
+ },
922
+ path: {
923
+ mode: "flat",
924
+ prefix: "/n",
925
+ },
926
+ });
927
+ ```
928
+
929
+ ## 完全に明示的な永続URL
930
+
931
+ ```ts
932
+ permalink({
933
+ frontmatter: "id",
934
+ id: {
935
+ strategy: "frontmatter",
936
+ },
937
+ path: {
938
+ mode: "flat",
939
+ prefix: "/n",
940
+ },
941
+ });
942
+ ```
943
+
944
+ すべての記事にfrontmatter IDが必要になります。
945
+
946
+ ## ディレクトリ構造を残す
947
+
948
+ ```ts
949
+ permalink({
950
+ id: {
951
+ strategy: "frontmatter-or-hash",
952
+ },
953
+ path: {
954
+ mode: "preserve",
955
+ prefix: "",
956
+ },
957
+ });
958
+ ```
959
+
960
+ ## 独自URL設計
961
+
962
+ ```ts
963
+ permalink({
964
+ resolveId(content) {
965
+ // project-specific ID
966
+ return "...";
967
+ },
968
+
969
+ resolvePath({ content, id }) {
970
+ // project-specific URL
971
+ return `/articles/${id}`;
972
+ },
973
+ });
974
+ ```
975
+
976
+ ---
977
+
978
+ # 関連資料
979
+
980
+ - [プラグインシステム](../../../docs/ja/reference/plugin-api.md)
981
+ - [コンテンツシステム](../../../docs/ja/framework/content-system.md)
982
+
983
+ ## ????
984
+
985
+ - [?????????](../../../docs/ja/reference/plugin-api.md)