@riebeckite/plugin-permalink 0.0.1
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/LICENSE +201 -0
- package/README.md +965 -0
- package/README_ja.md +981 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +254 -0
- package/package.json +52 -0
package/README_ja.md
ADDED
|
@@ -0,0 +1,981 @@
|
|
|
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)
|