@riebeckite/plugin-citations 0.0.22 → 0.0.23

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
@@ -63,6 +63,49 @@ Use `referencesHeading` for localized headings:
63
63
  citations({ bibliography: "references.bib", referencesHeading: "参考文献" })
64
64
  ```
65
65
 
66
+ ## Bibliography page
67
+
68
+ Setting `pagePath` generates a standalone bibliography page. It is opt-in and
69
+ disabled when omitted:
70
+
71
+ ```ts
72
+ citations({ bibliography: "references.bib", pagePath: "/bibliography" })
73
+ ```
74
+
75
+ The page type (`citations-bibliography`) collects references from two sources:
76
+
77
+ - the plugin's `bibliography` option, resolved from the content root
78
+ - the frontmatter `bibliography` of every discoverable (public) page
79
+
80
+ Only `manifest.discoverableEntries` contribute frontmatter bibliographies. Pages
81
+ that are draft, unlisted, scheduled, or otherwise non-discoverable are ignored,
82
+ so their bibliographies are never leaked into the page.
83
+
84
+ Entries from all files are merged in a deterministic order. Identical duplicates
85
+ are dropped silently; when the same key appears with different content, the first
86
+ definition wins and the conflict is reported as `citation-duplicate-key`. Missing
87
+ files and malformed files are reported as `citation-missing-bibliography` and
88
+ `citation-malformed-bibliography`. When no entry is collected, no page is
89
+ generated. If a Markdown page or redirect already owns the path, Markdown wins
90
+ and the page is skipped with a `citations-page-conflict` diagnostic.
91
+
92
+ The page body provides its own `<main>` and `<h1>`, escapes all entry text, and
93
+ lists entries sorted by key. A `url` or `doi` field becomes a link only when it
94
+ resolves to a safe `http(s)` target. Existing in-page citations and the
95
+ `References` section are unchanged.
96
+
97
+ Bibliography pages declare a `global` output dependency, so changes to a
98
+ bibliography file, a frontmatter `bibliography`, page visibility, or `pagePath`
99
+ invalidate the affected output.
100
+
101
+ ## Options
102
+
103
+ | Option | Default | Description |
104
+ | ------ | ------- | ----------- |
105
+ | `bibliography` | unset | Vault-relative BibTeX/BibLaTeX file path or paths |
106
+ | `referencesHeading` | `"References"` | Heading inserted above generated references |
107
+ | `pagePath` | unset | Path of the standalone bibliography page; disabled when omitted |
108
+
66
109
  ## Diagnostics
67
110
 
68
111
  Reported through Riebeckite diagnostics:
package/README_ja.md CHANGED
@@ -63,6 +63,35 @@ Riebeckite はこのプラグインより先に `:name` を directive として
63
63
  citations({ bibliography: "references.bib", referencesHeading: "参考文献" })
64
64
  ```
65
65
 
66
+ ## 参考文献ページ
67
+
68
+ `pagePath` を設定すると、参考文献の一覧ページを生成します。既定では無効で、指定しないかぎり生成しません。
69
+
70
+ ```ts
71
+ citations({ bibliography: "references.bib", pagePath: "/bibliography" })
72
+ ```
73
+
74
+ ページタイプ(`citations-bibliography`)は、次の2か所から文献を集めます。
75
+
76
+ - プラグインの `bibliography` オプション(コンテンツルートからの相対パス)
77
+ - 公開(discoverable)ページの frontmatter `bibliography`
78
+
79
+ frontmatter の `bibliography` を取り込むのは `manifest.discoverableEntries` だけです。下書き・unlisted・公開予定など、discoverable でないページは無視するため、それらの文献がページへ漏れることはありません。
80
+
81
+ 各ファイルの文献は決定的な順序で統合します。内容まで同じ重複は黙って1件にまとめ、同じキーで内容が異なる場合は先に見つかった定義を採用し、`citation-duplicate-key` として報告します。ファイルが見つからない場合は `citation-missing-bibliography`、解析できない場合は `citation-malformed-bibliography` を報告します。文献が1件も集まらないときはページを生成しません。同じパスを Markdown ページやリダイレクトが既に持っている場合は Markdown を優先し、`citations-page-conflict` を報告してページを生成しません。
82
+
83
+ ページ本文には専用の `<main>` と `<h1>` を持たせ、文献のテキストはすべてエスケープします。`url` と `doi` は安全な `http(s)` のときだけリンクにします。文献はキー順に並べます。既存の本文中の引用と `References` セクションは変更しません。
84
+
85
+ 参考文献ページは `global` の output dependency を宣言します。文献ファイル、frontmatter の `bibliography`、ページの公開状態、`pagePath` の変更で、影響する出力を無効化します。
86
+
87
+ ## オプション
88
+
89
+ | 項目 | 既定値 | 説明 |
90
+ | --- | --- | --- |
91
+ | `bibliography` | 未設定 | コンテンツルートからの相対パスで指定する BibTeX / BibLaTeX ファイル |
92
+ | `referencesHeading` | `"References"` | 生成する参考文献の見出し |
93
+ | `pagePath` | 未設定 | 参考文献一覧ページのパス。指定しないときは生成しない |
94
+
66
95
  ## diagnostics
67
96
 
68
97
  以下を Riebeckite の diagnostics に出力します。
package/dist/index.js CHANGED
@@ -1,11 +1,14 @@
1
1
  // index.ts
2
2
  import {
3
+ createStyleAsset,
3
4
  definePlugin,
4
- readContentSourceEntry
5
+ readContentSourceEntry as readContentSourceEntry2
5
6
  } from "@riebeckite/core";
6
7
 
7
- // src/remark.ts
8
- import { visit } from "unist-util-visit";
8
+ // src/page.ts
9
+ import {
10
+ readContentSourceEntry
11
+ } from "@riebeckite/core";
9
12
 
10
13
  // src/bibliography.ts
11
14
  var SUPPORTED_TYPES = /* @__PURE__ */ new Set(["article", "book", "inproceedings", "misc"]);
@@ -284,7 +287,245 @@ function cleanValue(value) {
284
287
  return value.replace(/[{}]/g, "").replace(/\\([&%$#_{}])/g, "$1").trim();
285
288
  }
286
289
 
290
+ // src/paths.ts
291
+ function pathList(value) {
292
+ if (typeof value === "string") return [value];
293
+ if (Array.isArray(value)) {
294
+ return value.filter((item) => typeof item === "string");
295
+ }
296
+ return [];
297
+ }
298
+ function pageRelativeCandidates(path, sourceSlug) {
299
+ const normalized = normalizeLogicalPath(path);
300
+ if (!sourceSlug || isAbsoluteLikePath(normalized)) return [normalized];
301
+ const directory = normalizeLogicalPath(sourceSlug).split("/").slice(0, -1).join("/");
302
+ if (!directory) return [normalized];
303
+ const relative = normalizeLogicalPath(`${directory}/${normalized}`);
304
+ return relative === normalized ? [normalized] : [relative, normalized];
305
+ }
306
+ function isAbsoluteLikePath(path) {
307
+ return path.startsWith("/") || /^[A-Za-z]:\//.test(path);
308
+ }
309
+ function normalizeLogicalPath(path) {
310
+ const parts = [];
311
+ for (const segment of path.replace(/\\/g, "/").split("/")) {
312
+ if (!segment || segment === ".") continue;
313
+ if (segment === "..") {
314
+ if (parts.length > 0 && parts.at(-1) !== "..") parts.pop();
315
+ else parts.push(segment);
316
+ continue;
317
+ }
318
+ parts.push(segment);
319
+ }
320
+ const prefix = path.startsWith("/") ? "/" : "";
321
+ return `${prefix}${parts.join("/")}`;
322
+ }
323
+ function pushUniqueDiagnostics(target, diagnostics, slug) {
324
+ for (const diagnostic of diagnostics) {
325
+ const next = diagnostic.slug ? diagnostic : { ...diagnostic, slug };
326
+ const signature = diagnosticSignature(next);
327
+ if (target.some((item) => diagnosticSignature(item) === signature))
328
+ continue;
329
+ target.push(next);
330
+ }
331
+ }
332
+ function diagnosticSignature(diagnostic) {
333
+ return `${diagnostic.code}:${diagnostic.slug ?? ""}:${diagnostic.filePath ?? ""}:${diagnostic.target ?? ""}:${diagnostic.message}`;
334
+ }
335
+
336
+ // src/render.ts
337
+ import { escapeHtml, escapeHtmlAttribute } from "@riebeckite/core";
338
+ function renderBibliographyPage(input) {
339
+ const items = input.entries.map(([key, entry]) => renderItem(key, entry)).join("");
340
+ return `<main class="rb-citations-page"><h1 class="rb-citations-page__title">${escapeHtml(
341
+ input.title
342
+ )}</h1><ol class="rb-citations-page__list">${items}</ol></main>`;
343
+ }
344
+ function renderItem(key, entry) {
345
+ const fields = entry.fields;
346
+ const author = fields.author ?? fields.editor;
347
+ const year = fields.year ?? fields.date;
348
+ const container = fields.journal ?? fields.booktitle ?? fields.publisher;
349
+ const parts = [];
350
+ if (author)
351
+ parts.push(
352
+ `<span class="rb-citations-page__author">${escapeHtml(author)}</span>`
353
+ );
354
+ if (year)
355
+ parts.push(
356
+ `<span class="rb-citations-page__year">${escapeHtml(year)}</span>`
357
+ );
358
+ if (fields.title)
359
+ parts.push(
360
+ `<span class="rb-citations-page__work">${escapeHtml(fields.title)}</span>`
361
+ );
362
+ if (container)
363
+ parts.push(
364
+ `<span class="rb-citations-page__container">${escapeHtml(container)}</span>`
365
+ );
366
+ if (parts.length === 0)
367
+ parts.push(
368
+ `<span class="rb-citations-page__key">${escapeHtml(key)}</span>`
369
+ );
370
+ const id = escapeHtmlAttribute(referenceId(key));
371
+ return `<li class="rb-citations-page__item" id="${id}">${parts.join(
372
+ " "
373
+ )}${renderLink(fields)}</li>`;
374
+ }
375
+ function renderLink(fields) {
376
+ const href = safeExternalUrl(fields.url) ?? safeDoiUrl(fields.doi);
377
+ if (!href) return "";
378
+ return ` <a class="rb-citations-page__link" href="${escapeHtmlAttribute(
379
+ href
380
+ )}" rel="noopener noreferrer">${escapeHtml(href)}</a>`;
381
+ }
382
+ function safeExternalUrl(value) {
383
+ if (!value) return null;
384
+ try {
385
+ const parsed = new URL(value);
386
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
387
+ return null;
388
+ return parsed.toString();
389
+ } catch {
390
+ return null;
391
+ }
392
+ }
393
+ function safeDoiUrl(value) {
394
+ if (!value) return null;
395
+ const doi = value.trim().replace(/^https?:\/\/(?:dx\.)?doi\.org\//i, "").replace(/^doi:\s*/i, "");
396
+ if (!/^10\.\d{4,9}\//.test(doi)) return null;
397
+ return `https://doi.org/${encodeURI(doi)}`;
398
+ }
399
+ function referenceId(key) {
400
+ const sanitized = key.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
401
+ return `ref-${sanitized || "key"}`;
402
+ }
403
+
404
+ // src/page.ts
405
+ var PLUGIN_NAME = "citations";
406
+ var PAGE_TITLE = "Bibliography";
407
+ var EXTENSION = /\.[a-zA-Z0-9]+$/;
408
+ async function listBibliographyPagePaths(source, options) {
409
+ const pagePath = bibliographyPagePath(options.pagePath);
410
+ if (!pagePath) return [];
411
+ if (isContentRoute(source.manifest, pagePath)) {
412
+ source.diagnostics.push({
413
+ code: "citations-page-conflict",
414
+ severity: "warning",
415
+ pluginName: PLUGIN_NAME,
416
+ message: `Skipping bibliography page ${pagePath}: a content route already owns it.`,
417
+ target: pagePath
418
+ });
419
+ return [];
420
+ }
421
+ const entries = await collectBibliographyEntries(source, options);
422
+ return entries.size > 0 ? [pagePath] : [];
423
+ }
424
+ async function resolveBibliographyPage(context, options) {
425
+ const pagePath = bibliographyPagePath(options.pagePath);
426
+ if (!pagePath || pagePath !== context.pathname) return null;
427
+ if (isContentRoute(context.manifest, context.pathname)) return null;
428
+ const entries = await collectBibliographyEntries(context, options);
429
+ if (entries.size === 0) return null;
430
+ const sorted = [...entries.entries()].toSorted(
431
+ (a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0
432
+ );
433
+ return {
434
+ type: "citations-bibliography",
435
+ pathname: context.pathname,
436
+ title: PAGE_TITLE,
437
+ body: renderBibliographyPage({ title: PAGE_TITLE, entries: sorted })
438
+ };
439
+ }
440
+ function bibliographyPagePath(value) {
441
+ if (typeof value !== "string") return null;
442
+ const segments = normalizeLogicalPath(value).replace(/^\/+/, "").split("/").filter(Boolean);
443
+ if (segments.length === 0) return null;
444
+ if (EXTENSION.test(segments[segments.length - 1] ?? "")) return null;
445
+ return `/${segments.join("/")}`;
446
+ }
447
+ function isContentRoute(manifest, pathname) {
448
+ return manifest.byRoutablePermalink.has(pathname) || manifest.publicRedirects.has(pathname);
449
+ }
450
+ function collectBibliographyGroups(manifest, options) {
451
+ const groups = [];
452
+ for (const path of pathList(options.bibliography)) {
453
+ groups.push([path]);
454
+ }
455
+ const frontmatterEntries = manifest.discoverableEntries.filter((entry) => entry.frontmatter.bibliography !== void 0).toSorted((a, b) => a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0);
456
+ for (const entry of frontmatterEntries) {
457
+ for (const path of pathList(entry.frontmatter.bibliography)) {
458
+ groups.push([...pageRelativeCandidates(path, entry.slug)]);
459
+ }
460
+ }
461
+ return groups;
462
+ }
463
+ async function collectBibliographyEntries(source, options) {
464
+ const groups = collectBibliographyGroups(source.manifest, options);
465
+ const entries = /* @__PURE__ */ new Map();
466
+ const loaded = /* @__PURE__ */ new Set();
467
+ const collected = [];
468
+ for (const candidates of groups) {
469
+ const resolved = await loadFirstAvailable(source.contentSource, candidates);
470
+ if (!resolved) {
471
+ collected.push({
472
+ code: "citation-missing-bibliography",
473
+ severity: "error",
474
+ message: `Bibliography file was not found: ${candidates[0] ?? ""}`,
475
+ filePath: candidates[0]
476
+ });
477
+ continue;
478
+ }
479
+ if (loaded.has(resolved.path)) continue;
480
+ loaded.add(resolved.path);
481
+ collected.push(...resolved.parsed.diagnostics);
482
+ mergeEntries(entries, resolved.parsed.entries, resolved.path, collected);
483
+ }
484
+ pushUniqueDiagnostics(source.diagnostics, collected, void 0);
485
+ return entries;
486
+ }
487
+ async function loadFirstAvailable(source, candidates) {
488
+ if (!source) return null;
489
+ for (const path of candidates) {
490
+ const raw = await readContentSourceEntry(source, path);
491
+ if (raw === null) continue;
492
+ const text = typeof raw === "string" ? raw : new TextDecoder().decode(raw);
493
+ return { path, parsed: parseBibliography(text, path) };
494
+ }
495
+ return null;
496
+ }
497
+ function mergeEntries(target, incoming, filePath, diagnostics) {
498
+ for (const [key, entry] of incoming) {
499
+ const existing = target.get(key);
500
+ if (!existing) {
501
+ target.set(key, entry);
502
+ continue;
503
+ }
504
+ if (sameEntry(existing, entry)) continue;
505
+ diagnostics.push({
506
+ code: "citation-duplicate-key",
507
+ severity: "warning",
508
+ message: `Duplicate bibliography key: ${key}`,
509
+ filePath,
510
+ target: key
511
+ });
512
+ }
513
+ }
514
+ function sameEntry(a, b) {
515
+ if (a.type !== b.type) return false;
516
+ const aKeys = Object.keys(a.fields).toSorted();
517
+ const bKeys = Object.keys(b.fields).toSorted();
518
+ if (aKeys.length !== bKeys.length) return false;
519
+ for (let index = 0; index < aKeys.length; index += 1) {
520
+ const key = aKeys[index] ?? "";
521
+ if (key !== bKeys[index]) return false;
522
+ if (a.fields[key] !== b.fields[key]) return false;
523
+ }
524
+ return true;
525
+ }
526
+
287
527
  // src/remark.ts
528
+ import { visit } from "unist-util-visit";
288
529
  var BLOCKING_ANCESTORS = /* @__PURE__ */ new Set([
289
530
  "link",
290
531
  "linkReference",
@@ -376,43 +617,11 @@ function bibliographyPathGroups(options, frontmatter) {
376
617
  }
377
618
  return pathList(options.bibliography).map((path) => [path]);
378
619
  }
379
- function pathList(value) {
380
- if (typeof value === "string") return [value];
381
- if (Array.isArray(value)) {
382
- return value.filter((item) => typeof item === "string");
383
- }
384
- return [];
385
- }
386
- function pageRelativeCandidates(path, sourceSlug) {
387
- const normalized = normalizeLogicalPath(path);
388
- if (!sourceSlug || isAbsoluteLikePath(normalized)) return [normalized];
389
- const directory = normalizeLogicalPath(sourceSlug).split("/").slice(0, -1).join("/");
390
- if (!directory) return [normalized];
391
- const relative = normalizeLogicalPath(`${directory}/${normalized}`);
392
- return relative === normalized ? [normalized] : [relative, normalized];
393
- }
394
- function isAbsoluteLikePath(path) {
395
- return path.startsWith("/") || /^[A-Za-z]:\//.test(path);
396
- }
397
- function normalizeLogicalPath(path) {
398
- const parts = [];
399
- for (const segment of path.replace(/\\/g, "/").split("/")) {
400
- if (!segment || segment === ".") continue;
401
- if (segment === "..") {
402
- if (parts.length > 0 && parts.at(-1) !== "..") parts.pop();
403
- else parts.push(segment);
404
- continue;
405
- }
406
- parts.push(segment);
407
- }
408
- const prefix = path.startsWith("/") ? "/" : "";
409
- return `${prefix}${parts.join("/")}`;
410
- }
411
620
  async function loadBibliographies(pathGroups, options) {
412
621
  const entries = /* @__PURE__ */ new Map();
413
622
  const diagnostics = [];
414
623
  for (const candidates of pathGroups) {
415
- const resolved = await loadFirstAvailable(candidates, options);
624
+ const resolved = await loadFirstAvailable2(candidates, options);
416
625
  if (!resolved) {
417
626
  diagnostics.push({
418
627
  code: "citation-missing-bibliography",
@@ -440,7 +649,7 @@ async function loadBibliographies(pathGroups, options) {
440
649
  pushUniqueDiagnostics(options.diagnostics, diagnostics, options.sourceSlug);
441
650
  return { entries, diagnostics };
442
651
  }
443
- async function loadFirstAvailable(candidates, options) {
652
+ async function loadFirstAvailable2(candidates, options) {
444
653
  for (const path of candidates) {
445
654
  const resolved = await cachedBibliography(path, options);
446
655
  if (resolved.found) return { path, parsed: resolved.parsed };
@@ -574,7 +783,7 @@ function renderCitation(references, bracketed, context) {
574
783
  });
575
784
  return {
576
785
  type: "link",
577
- url: `#${referenceId(references[0]?.key ?? "unknown", context)}`,
786
+ url: `#${referenceId2(references[0]?.key ?? "unknown", context)}`,
578
787
  title: null,
579
788
  children: [{ type: "text", value: label }]
580
789
  };
@@ -597,7 +806,7 @@ function numberForReference(reference, context) {
597
806
  const number = context.orderedKeys.length + 1;
598
807
  context.numbers.set(reference.key, number);
599
808
  context.orderedKeys.push(reference.key);
600
- referenceId(reference.key, context);
809
+ referenceId2(reference.key, context);
601
810
  if (!context.entries.has(reference.key)) {
602
811
  pushUniqueDiagnostics(
603
812
  context.options.diagnostics,
@@ -615,7 +824,7 @@ function numberForReference(reference, context) {
615
824
  }
616
825
  return number;
617
826
  }
618
- function referenceId(key, context) {
827
+ function referenceId2(key, context) {
619
828
  const existing = context.referenceIds.get(key);
620
829
  if (existing) return existing;
621
830
  const sanitized = key.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -647,7 +856,7 @@ function createReferencesList(context) {
647
856
  (key) => ({
648
857
  type: "listItem",
649
858
  spread: false,
650
- data: { hProperties: { id: referenceId(key, context) } },
859
+ data: { hProperties: { id: referenceId2(key, context) } },
651
860
  children: [
652
861
  {
653
862
  type: "paragraph",
@@ -671,18 +880,6 @@ function renderBibliographyEntry(entry, key) {
671
880
  const container = entry.fields.journal ?? entry.fields.booktitle ?? entry.fields.publisher;
672
881
  return [author, year ? `(${year})` : void 0, title, container].filter(Boolean).join(". ");
673
882
  }
674
- function pushUniqueDiagnostics(target, diagnostics, slug) {
675
- for (const diagnostic of diagnostics) {
676
- const next = diagnostic.slug ? diagnostic : { ...diagnostic, slug };
677
- const signature = diagnosticSignature(next);
678
- if (target.some((item) => diagnosticSignature(item) === signature))
679
- continue;
680
- target.push(next);
681
- }
682
- }
683
- function diagnosticSignature(diagnostic) {
684
- return `${diagnostic.code}:${diagnostic.slug ?? ""}:${diagnostic.filePath ?? ""}:${diagnostic.target ?? ""}:${diagnostic.message}`;
685
- }
686
883
 
687
884
  // src/state.ts
688
885
  function createCitationState() {
@@ -690,11 +887,11 @@ function createCitationState() {
690
887
  }
691
888
 
692
889
  // index.ts
693
- var PLUGIN_NAME = "citations";
890
+ var PLUGIN_NAME2 = "citations";
694
891
  function citations(options = {}) {
695
892
  const state = createCitationState();
696
893
  return definePlugin({
697
- name: PLUGIN_NAME,
894
+ name: PLUGIN_NAME2,
698
895
  processedContentCache: {
699
896
  version: "citations-v1",
700
897
  dependencyMode: "tracked"
@@ -713,7 +910,7 @@ function citations(options = {}) {
713
910
  diagnostics: state.diagnostics,
714
911
  loadBibliography: async (path) => {
715
912
  if (!context.contentSource) return null;
716
- const content = await readContentSourceEntry(
913
+ const content = await readContentSourceEntry2(
717
914
  context.contentSource,
718
915
  path
719
916
  );
@@ -723,6 +920,32 @@ function citations(options = {}) {
723
920
  cache: state.bibliographies
724
921
  });
725
922
  },
923
+ pageTypes: [
924
+ {
925
+ id: "citations-bibliography",
926
+ paths: (context) => options.pagePath === void 0 ? [] : listBibliographyPagePaths(
927
+ {
928
+ manifest: context.manifest,
929
+ contentSource: context.contentSource,
930
+ diagnostics: context.diagnostics
931
+ },
932
+ options
933
+ ),
934
+ outputDependencies: [{ type: "global" }],
935
+ resolve: (context) => options.pagePath === void 0 ? null : resolveBibliographyPage(
936
+ {
937
+ manifest: context.manifest,
938
+ contentSource: context.contentSource,
939
+ diagnostics: context.diagnostics,
940
+ pathname: context.pathname
941
+ },
942
+ options
943
+ )
944
+ }
945
+ ],
946
+ assets: [
947
+ createStyleAsset(PLUGIN_NAME2, "@riebeckite/plugin-citations/style.css")
948
+ ],
726
949
  addDiagnostics: () => state.diagnostics
727
950
  });
728
951
  }
@@ -743,6 +966,9 @@ function validateCitationsOptions(options) {
743
966
  if (options.referencesHeading !== void 0 && typeof options.referencesHeading !== "string") {
744
967
  issues.push({ path: "referencesHeading", message: "Expected a string." });
745
968
  }
969
+ if (options.pagePath !== void 0 && typeof options.pagePath !== "string") {
970
+ issues.push({ path: "pagePath", message: "Expected a string." });
971
+ }
746
972
  return issues;
747
973
  }
748
974
  export {
@@ -0,0 +1,11 @@
1
+ import { type ContentManifest, type ContentSource, type Diagnostic, type PluginPage } from "@riebeckite/core";
2
+ import type { CitationsOptions } from "./types.js";
3
+ export type BibliographyPageSource = {
4
+ readonly manifest: ContentManifest;
5
+ readonly contentSource?: ContentSource;
6
+ readonly diagnostics: Diagnostic[];
7
+ };
8
+ export declare function listBibliographyPagePaths(source: BibliographyPageSource, options: CitationsOptions): Promise<string[]>;
9
+ export declare function resolveBibliographyPage(context: BibliographyPageSource & {
10
+ readonly pathname: string;
11
+ }, options: CitationsOptions): Promise<PluginPage | null>;
@@ -0,0 +1,12 @@
1
+ import type { Diagnostic } from "@riebeckite/core";
2
+ export declare function pathList(value: unknown): readonly string[];
3
+ /**
4
+ * Frontmatter paths are document-relative first, then content-root relative.
5
+ * Every candidate still goes through `ContentSource.scan()`, so no candidate can
6
+ * escape the configured content root.
7
+ */
8
+ export declare function pageRelativeCandidates(path: string, sourceSlug: string | undefined): readonly string[];
9
+ export declare function isAbsoluteLikePath(path: string): boolean;
10
+ export declare function normalizeLogicalPath(path: string): string;
11
+ export declare function pushUniqueDiagnostics(target: Diagnostic[], diagnostics: readonly Diagnostic[], slug: string | undefined): void;
12
+ export declare function diagnosticSignature(diagnostic: Diagnostic): string;
@@ -0,0 +1,6 @@
1
+ import type { BibliographyEntry } from "./types.js";
2
+ export type RenderBibliographyInput = {
3
+ readonly title: string;
4
+ readonly entries: readonly (readonly [string, BibliographyEntry])[];
5
+ };
6
+ export declare function renderBibliographyPage(input: RenderBibliographyInput): string;
@@ -4,6 +4,7 @@ export type CitationsOptions = {
4
4
  bibliography?: string | readonly string[];
5
5
  /** Heading inserted above generated references. Defaults to "References". */
6
6
  referencesHeading?: string;
7
+ pagePath?: string;
7
8
  };
8
9
  export type BibliographyEntry = {
9
10
  readonly key: string;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@riebeckite/plugin-citations",
3
- "version": "0.0.22",
3
+ "version": "0.0.23",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "dependencies": {
8
8
  "@types/mdast": "^4.0.4",
9
9
  "unist-util-visit": "^5.1.0",
10
- "@riebeckite/core": "0.0.22"
10
+ "@riebeckite/core": "0.0.23"
11
11
  },
12
12
  "publishConfig": {
13
13
  "access": "public"
@@ -16,7 +16,8 @@
16
16
  "LICENSE",
17
17
  "README.md",
18
18
  "README_ja.md",
19
- "dist"
19
+ "dist",
20
+ "style.css"
20
21
  ],
21
22
  "exports": {
22
23
  ".": {
@@ -24,7 +25,8 @@
24
25
  "import": "./dist/index.js",
25
26
  "source": "./index.ts",
26
27
  "default": "./dist/index.js"
27
- }
28
+ },
29
+ "./style.css": "./style.css"
28
30
  },
29
31
  "repository": {
30
32
  "type": "git",
@@ -46,7 +48,9 @@
46
48
  "engines": {
47
49
  "node": "^20.19.0 || >=22.12.0"
48
50
  },
49
- "sideEffects": false,
51
+ "sideEffects": [
52
+ "./style.css"
53
+ ],
50
54
  "scripts": {
51
55
  "build": "node ../../../scripts/build_package.mjs",
52
56
  "test": "node --import tsx --test \"test/*.test.ts\""
package/style.css ADDED
@@ -0,0 +1,28 @@
1
+ .rb-citations-page {
2
+ width: min(100%, 48rem);
3
+ margin-inline: auto;
4
+ padding-inline: var(--rb-space-2, 1rem);
5
+ }
6
+
7
+ .rb-citations-page__title {
8
+ margin-block-end: var(--rb-space-2, 1rem);
9
+ }
10
+
11
+ .rb-citations-page__list {
12
+ display: grid;
13
+ gap: var(--rb-space-1, 0.5rem);
14
+ padding-inline-start: var(--rb-space-2, 1rem);
15
+ }
16
+
17
+ .rb-citations-page__item {
18
+ line-height: 1.6;
19
+ }
20
+
21
+ .rb-citations-page__year,
22
+ .rb-citations-page__container {
23
+ opacity: 0.75;
24
+ }
25
+
26
+ .rb-citations-page__link {
27
+ overflow-wrap: anywhere;
28
+ }