@behackl/citation-js-extras 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @behackl/citation-js-extras
2
2
 
3
+ ![NPM Version](https://img.shields.io/npm/v/%40behackl%2Fcitation-js-extras)
4
+
3
5
  Preserve custom BibTeX fields through [citation-js](https://citation.js.org/) and render academic bibliographies with linked titles, configurable badges, and more.
4
6
 
5
7
  ## The problem
@@ -49,6 +51,112 @@ const html = bib.formatHtml(sorted, {
49
51
  });
50
52
  ```
51
53
 
54
+ ## Preserving mathematics
55
+
56
+ Citation.js normally converts TeX math to text, losing delimiters and potentially
57
+ complex expressions. Enable preservation before parsing:
58
+
59
+ ```ts
60
+ const bib = new Bibliography({
61
+ data: "./references.bib",
62
+ preserveMath: true,
63
+ });
64
+
65
+ // Restore HTML-escaped original TeX for client-side MathJax:
66
+ const html = bib.formatHtml(bib.entries);
67
+
68
+ // Or typeset at build time with your own synchronous renderer:
69
+ const rendered = bib.formatHtml(bib.entries, {
70
+ renderMath: (tex, { display }) => myMathRenderer(tex, display),
71
+ });
72
+ ```
73
+
74
+ `myMathRenderer` is an application-supplied function returning **trusted HTML**
75
+ (e.g. MathJax SVG or KaTeX output). No math renderer is bundled. Configure it for
76
+ untrusted TeX as appropriate; the callback output is inserted verbatim, not
77
+ sanitized. Treat renderer output as untrusted HTML unless you control both the
78
+ renderer configuration and the TeX: substitute it only after the surrounding
79
+ HTML has been sanitized (see below). Exceptions propagate to the caller. `renderMath` also works with
80
+ `formatEntry`; it has no effect unless `preserveMath` was enabled.
81
+
82
+ Supported delimiters are `$…$`, `$$…$$`, `\(…\)`, and `\[…\]`.
83
+ Escape literal dollars as `\$`. Empty or unclosed expressions throw with the
84
+ citation key and field name. This is a delimiter scanner, not a TeX validator:
85
+ unsupported commands and mathematical validity are the renderer's responsibility.
86
+
87
+ Protection covers `title`, `subtitle`, `titleaddon`, `shorttitle`, `booktitle`,
88
+ `booksubtitle`, `booktitleaddon`, `maintitle`, `mainsubtitle`, `maintitleaddon`,
89
+ `journaltitle`, `journalsubtitle`, `journal`, `note`, `annote`, `abstract`, and
90
+ `howpublished`. Fields still need to be supported by Citation.js and the chosen
91
+ CSL style to appear in the output. Names, identifiers, URLs, and custom metadata
92
+ are not protected. BibTeX strings and concatenations are resolved before protection;
93
+ inherited cross-reference text is protected during conversion.
94
+
95
+ Original `.raw` and `.custom` values remain unchanged. With preservation enabled,
96
+ `.csl` contains internal placeholders: use the formatting methods for HTML and
97
+ raw fields for original source text, not `.csl` for plain-text exports. Placeholders
98
+ also mean CSL title-based sorting/disambiguation operates on protected text rather
99
+ than mathematical meaning. Existing caller-controlled ordering is retained.
100
+
101
+ Restoration runs after title linking, badges, and URL linkification. Neither
102
+ renderer output nor restored TeX is fed back through these HTML helpers. Disabling
103
+ preservation (the default) retains the previous behavior.
104
+
105
+ If `renderMath` throws, the error names the entry it came from
106
+ (`entry doe:2024: Undefined control sequence: \\kk`), so a broken formula can be
107
+ traced to a line in the `.bib` file.
108
+
109
+ ### Sanitizing formatted output
110
+
111
+ Rendered mathematics does **not** survive an HTML sanitizer with default
112
+ settings: KaTeX and MathJax output relies on `class`, `style` and MathML
113
+ elements that `rehype-sanitize` strips, leaving empty boxes. Sanitize the
114
+ citation HTML *first*, then insert the trusted renderer output:
115
+
116
+ ```js
117
+ const math = [];
118
+ const html = bib.formatHtml(entries, {
119
+ // Stand in for each formula while the HTML is sanitized ...
120
+ renderMath: (tex, { display }) => {
121
+ math.push(renderWithKatex(tex, display));
122
+ return `mathplaceholder${math.length - 1}end`;
123
+ },
124
+ });
125
+ const safe = sanitize(html);
126
+ // ... then substitute the rendered markup back in.
127
+ const final = safe.replace(/mathplaceholder(\d+)end/g, (_, i) => math[Number(i)]);
128
+ ```
129
+
130
+ Two related gotchas when using `rehype-sanitize`:
131
+
132
+ - its `defaultSchema` allows `className` only with an explicit value list *per
133
+ tag*, and that per-tag rule overrides anything added under `'*'`; to keep
134
+ badge or list classes, merge your class names into the existing entry for
135
+ that tag
136
+ - pick a placeholder that cannot occur in your bibliography (append characters
137
+ until it is absent from the source)
138
+
139
+ This recipe deliberately reinserts renderer output *after* sanitizing, so it is
140
+ only as safe as the renderer: a renderer configured to emit arbitrary markup
141
+ (for instance KaTeX with `trust: true`) can reintroduce `<script>`. Restrict the
142
+ renderer configuration, or sanitize its output separately with a schema that
143
+ keeps the elements and attributes mathematics needs.
144
+
145
+ ## Development checks
146
+
147
+ ```sh
148
+ pnpm install --frozen-lockfile
149
+ pnpm test # Unit tests and actual MathJax SVG integration (base + AMS)
150
+ pnpm test:package # Build, pack, install into a temporary consumer, and test exports
151
+ ```
152
+
153
+ The package check validates ESM imports, TypeScript declarations under both
154
+ NodeNext and Bundler resolution, and MathJax rendering through the installed
155
+ tarball. Its temporary consumer is removed afterwards. Installation prefers the
156
+ local cache but may need registry access on a fresh machine. CI runs both checks.
157
+ MathJax is a development-only dependency, not a runtime dependency for consumers.
158
+ Use Node 24 LTS for these development checks, matching CI and publishing.
159
+
52
160
  ## API
53
161
 
54
162
  ### `new Bibliography(options)`
@@ -58,6 +166,14 @@ const html = bib.formatHtml(sorted, {
58
166
  | `data` | `string` | BibTeX input — a raw string or a file path. |
59
167
  | `cslStyle` | `string?` | CSL style — a registered template name, raw XML, or a file path. Defaults to `'apa'`. |
60
168
  | `customFields` | `string[]?` | BibTeX field names to preserve. These appear on each entry under `.custom`. |
169
+ | `preserveMath` | `boolean?` | Preserve math in display-text fields through CSL formatting. Defaults to `false`. |
170
+ | `titleLink` | `string[]?` | Default fields used for the title link, checked in order. Defaults to `['url', 'doi', 'arxiv']`. |
171
+ | `badges` | `BadgeConfig[]?` | Default badge configuration, used by every `formatHtml`/`formatEntry` call. No badges are rendered unless set. |
172
+ | `linkifyUrls` | `boolean?` | Default for URL linkification. Defaults to `true`. |
173
+
174
+ `titleLink`, `badges` and `linkifyUrls` are usually the same for every call, so
175
+ they can be set once here; a value passed to `formatHtml`/`formatEntry` overrides
176
+ the default for that call.
61
177
 
62
178
  ### `bib.entries`
63
179
 
@@ -107,10 +223,14 @@ bib.formatHtml(entries, {
107
223
 
108
224
  Entries are formatted in one citeproc pass, so style-dependent state (for example numeric labels in Vancouver) remains correct.
109
225
 
226
+ An empty entry list returns an empty string, not an empty wrapper element.
227
+
110
228
  ### `bib.formatEntry(entry, options?)`
111
229
 
112
230
  Render a single entry as an HTML string (no list wrapper). The title link targets the actual CSL title text, regardless of italics.
113
231
 
232
+ Accepts the same title linking, badge and `linkifyUrls` options as `formatHtml` and applies them identically (`list`/`listAttributes` are ignored — there is no wrapper). Bare URLs are linkified unless `linkifyUrls: false` is set, either per call or on the constructor.
233
+
114
234
  For citation styles that depend on multi-entry context (numbered labels, ibid behavior, etc.), prefer `formatHtml(...)`.
115
235
 
116
236
  Title links are only created for safe URL schemes (`http`, `https`, `mailto`) or normalized DOI/arXiv links.
package/dist/index.d.ts CHANGED
@@ -1,11 +1,14 @@
1
1
  import type { BibEntry, BibliographyOptions, FormatOptions } from "./types.js";
2
- export type { BadgeConfig, BibEntry, BibliographyOptions, FormatOptions } from "./types.js";
2
+ export type { BadgeConfig, BibEntry, BibliographyOptions, FormatDefaults, FormatOptions, MathRenderer, } from "./types.js";
3
3
  export declare class Bibliography {
4
4
  /** The CSL template name to use for formatting. */
5
5
  readonly templateName: string;
6
6
  /** All parsed entries. */
7
7
  readonly entries: BibEntry[];
8
8
  private readonly customFieldNames;
9
+ private readonly math?;
10
+ /** Formatting defaults from the constructor; each call may override them. */
11
+ private readonly formatDefaults;
9
12
  constructor(options: BibliographyOptions);
10
13
  /**
11
14
  * Return entries whose custom fields match **all** given key/value pairs.
@@ -27,9 +30,26 @@ export declare class Bibliography {
27
30
  }): BibEntry[];
28
31
  /**
29
32
  * Format a single entry as an HTML string (no wrapper element).
30
- * Applies title linking and badge injection.
33
+ * Applies title linking, badge injection and URL linkification.
31
34
  */
32
35
  formatEntry(entry: BibEntry, options?: FormatOptions): string;
36
+ /**
37
+ * Decorate, linkify and restore math for one rendered entry. Shared by
38
+ * `formatEntry` and `formatHtml` so both honour the same options.
39
+ *
40
+ * Linkification runs before math restoration: rendered MathML carries an
41
+ * xmlns URL that must not be turned into a link. Math is restored per entry
42
+ * so a failing formula can be reported with its citation key.
43
+ */
44
+ private buildEntryHtml;
45
+ /** Per-call options win over the defaults given to the constructor. */
46
+ private mergeOptions;
47
+ /**
48
+ * Restore protected math, naming the entry when a renderer rejects a formula.
49
+ * Without the citation key, `renderMath` failures are near-impossible to
50
+ * trace back to a line in the .bib file.
51
+ */
52
+ private restoreMath;
33
53
  /**
34
54
  * Format a list of entries as a complete HTML bibliography.
35
55
  */
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync, statSync } from "node:fs";
2
2
  import Cite from "citation-js";
3
+ import { MathProtector } from "./math.js";
3
4
  // ---------------------------------------------------------------------------
4
5
  // Bibliography class
5
6
  // ---------------------------------------------------------------------------
@@ -9,9 +10,17 @@ export class Bibliography {
9
10
  /** All parsed entries. */
10
11
  entries;
11
12
  customFieldNames;
13
+ math;
14
+ /** Formatting defaults from the constructor; each call may override them. */
15
+ formatDefaults;
12
16
  constructor(options) {
13
17
  const bibData = maybeReadFile(options.data);
14
18
  this.customFieldNames = options.customFields ?? [];
19
+ this.formatDefaults = {
20
+ titleLink: options.titleLink,
21
+ badges: options.badges,
22
+ linkifyUrls: options.linkifyUrls,
23
+ };
15
24
  // Register CSL style
16
25
  this.templateName = this.registerStyle(options.cslStyle);
17
26
  // Two-pass parse: raw (preserves all fields) + CSL (for formatting)
@@ -21,7 +30,15 @@ export class Bibliography {
21
30
  for (const entry of rawEntries) {
22
31
  rawMap.set(entry.label, entry.properties);
23
32
  }
24
- const cite = new Cite(bibData);
33
+ // Protect resolved raw fields, after BibTeX strings/concatenations are parsed
34
+ // but before the lossy TeX-to-CSL conversion. Keep the original raw map intact.
35
+ this.math = options.preserveMath
36
+ ? new MathProtector(JSON.stringify(rawEntries)) : undefined;
37
+ const cite = this.math
38
+ ? new Cite(rawEntries.map(entry => ({
39
+ ...entry, properties: this.math.protect(entry.properties, entry.label),
40
+ })))
41
+ : new Cite(bibData);
25
42
  this.entries = cite.data.map((csl) => {
26
43
  const key = String(csl["citation-key"] || csl.id);
27
44
  const raw = rawMap.get(key) ?? {};
@@ -71,13 +88,53 @@ export class Bibliography {
71
88
  // -------------------------------------------------------------------------
72
89
  /**
73
90
  * Format a single entry as an HTML string (no wrapper element).
74
- * Applies title linking and badge injection.
91
+ * Applies title linking, badge injection and URL linkification.
75
92
  */
76
93
  formatEntry(entry, options = {}) {
94
+ const merged = this.mergeOptions(options);
77
95
  const rendered = this.renderCslEntries([entry]);
78
96
  const raw = rendered[0]?.[1] ?? "";
79
97
  const innerHtml = unwrapCslEntry(raw) ?? raw.trim();
80
- return this.decorateEntryHtml(entry, innerHtml, options);
98
+ return this.buildEntryHtml(entry, innerHtml, merged);
99
+ }
100
+ /**
101
+ * Decorate, linkify and restore math for one rendered entry. Shared by
102
+ * `formatEntry` and `formatHtml` so both honour the same options.
103
+ *
104
+ * Linkification runs before math restoration: rendered MathML carries an
105
+ * xmlns URL that must not be turned into a link. Math is restored per entry
106
+ * so a failing formula can be reported with its citation key.
107
+ */
108
+ buildEntryHtml(entry, innerRaw, merged) {
109
+ let inner = this.decorateEntryHtml(entry, innerRaw, merged);
110
+ if (merged.linkifyUrls !== false)
111
+ inner = linkifyBareUrls(inner);
112
+ return this.restoreMath(inner, merged, entry);
113
+ }
114
+ /** Per-call options win over the defaults given to the constructor. */
115
+ mergeOptions(options) {
116
+ return {
117
+ ...options,
118
+ titleLink: options.titleLink ?? this.formatDefaults.titleLink,
119
+ badges: options.badges ?? this.formatDefaults.badges,
120
+ linkifyUrls: options.linkifyUrls ?? this.formatDefaults.linkifyUrls,
121
+ };
122
+ }
123
+ /**
124
+ * Restore protected math, naming the entry when a renderer rejects a formula.
125
+ * Without the citation key, `renderMath` failures are near-impossible to
126
+ * trace back to a line in the .bib file.
127
+ */
128
+ restoreMath(html, options, entry) {
129
+ if (!this.math)
130
+ return html;
131
+ try {
132
+ return this.math.restore(html, options.renderMath);
133
+ }
134
+ catch (error) {
135
+ const message = error instanceof Error ? error.message : String(error);
136
+ throw new Error(entry ? `entry ${entry.key}: ${message}` : message, { cause: error });
137
+ }
81
138
  }
82
139
  /**
83
140
  * Format a list of entries as a complete HTML bibliography.
@@ -85,8 +142,9 @@ export class Bibliography {
85
142
  formatHtml(entries, options = {}) {
86
143
  if (entries.length === 0)
87
144
  return "";
88
- const tag = options.list ?? "ol";
89
- const attrs = options.listAttributes ?? (tag === "ol" ? { reversed: true } : {});
145
+ const merged = this.mergeOptions(options);
146
+ const tag = merged.list ?? "ol";
147
+ const attrs = merged.listAttributes ?? (tag === "ol" ? { reversed: true } : {});
90
148
  const attrStr = renderAttributes(attrs);
91
149
  // Render all entries in one citeproc run so style-dependent numbering/state
92
150
  // (e.g. vancouver left-margin labels) remains correct.
@@ -100,14 +158,10 @@ export class Bibliography {
100
158
  ?? rendered[index]?.[1]
101
159
  ?? "";
102
160
  const innerRaw = unwrapCslEntry(raw) ?? raw.trim();
103
- const inner = this.decorateEntryHtml(entry, innerRaw, options);
161
+ const inner = this.buildEntryHtml(entry, innerRaw, merged);
104
162
  return `<${itemTag} data-csl-entry-id="${escapeAttr(entry.key)}" class="csl-entry">${inner}</${itemTag}>`;
105
163
  });
106
- let html = `<${tag}${attrStr} class="csl-bib-body">\n${items.join("\n")}\n</${tag}>`;
107
- if (options.linkifyUrls !== false) {
108
- html = linkifyBareUrls(html);
109
- }
110
- return html;
164
+ return `<${tag}${attrStr} class="csl-bib-body">\n${items.join("\n")}\n</${tag}>`;
111
165
  }
112
166
  // -------------------------------------------------------------------------
113
167
  // Private helpers
package/dist/math.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { MathRenderer } from "./types.js";
2
+ export declare class MathProtector {
3
+ private readonly expressions;
4
+ private readonly tokensBySource;
5
+ private readonly prefix;
6
+ constructor(source: string);
7
+ protect(properties: Record<string, any>, key: string): Record<string, any>;
8
+ private scan;
9
+ restore(html: string, render?: MathRenderer): string;
10
+ }
package/dist/math.js ADDED
@@ -0,0 +1,77 @@
1
+ /** Text fields only: identifiers, URLs, names and custom metadata are untouched. */
2
+ const TEXT_FIELDS = new Set([
3
+ "title", "subtitle", "titleaddon", "shorttitle", "booktitle", "booksubtitle",
4
+ "booktitleaddon", "maintitle", "mainsubtitle", "maintitleaddon", "journaltitle",
5
+ "journalsubtitle", "journal", "note", "annote", "abstract", "howpublished",
6
+ ]);
7
+ export class MathProtector {
8
+ expressions = new Map();
9
+ tokensBySource = new Map();
10
+ prefix;
11
+ constructor(source) {
12
+ let prefix = "bibmathplaceholder";
13
+ while (source.toLowerCase().includes(prefix))
14
+ prefix += "x";
15
+ this.prefix = prefix;
16
+ }
17
+ protect(properties, key) {
18
+ return Object.fromEntries(Object.entries(properties).map(([field, value]) => [
19
+ field,
20
+ TEXT_FIELDS.has(field) && typeof value === "string"
21
+ ? this.scan(value, `${key}.${field}`) : value,
22
+ ]));
23
+ }
24
+ scan(value, context) {
25
+ let out = "";
26
+ for (let i = 0; i < value.length;) {
27
+ const opener = value.startsWith("$$", i) ? "$$"
28
+ : value[i] === "$" ? "$"
29
+ : value.startsWith("\\(", i) ? "\\("
30
+ : value.startsWith("\\[", i) ? "\\[" : null;
31
+ if (!opener) {
32
+ // Consume escaped characters together, including literal dollars.
33
+ const length = value[i] === "\\" && i + 1 < value.length ? 2 : 1;
34
+ out += value.slice(i, i + length);
35
+ i += length;
36
+ continue;
37
+ }
38
+ const closer = opener === "\\(" ? "\\)" : opener === "\\[" ? "\\]" : opener;
39
+ let end = i + opener.length;
40
+ while (end < value.length && !value.startsWith(closer, end)) {
41
+ end += value[end] === "\\" ? 2 : 1;
42
+ }
43
+ if (end >= value.length)
44
+ throw new Error(`Unclosed math delimiter ${opener} in ${context}`);
45
+ const tex = value.slice(i + opener.length, end);
46
+ if (!tex.trim())
47
+ throw new Error(`Empty math expression in ${context}`);
48
+ const source = value.slice(i, end + closer.length);
49
+ // Identical source must remain identical to citeproc (e.g. title grouping).
50
+ let token = this.tokensBySource.get(source);
51
+ if (!token) {
52
+ token = `${this.prefix}${this.expressions.size}end`;
53
+ this.tokensBySource.set(source, token);
54
+ this.expressions.set(token, { tex, source, display: opener === "$$" || opener === "\\[" });
55
+ }
56
+ out += token;
57
+ i = end + closer.length;
58
+ }
59
+ return out;
60
+ }
61
+ restore(html, render) {
62
+ const pattern = new RegExp(`${this.prefix}\\d+end`, "gi");
63
+ // Only replace text, never attribute values. Renderer output is inserted last.
64
+ return html.split(/(<[^>]*>)/g).map(part => part.startsWith("<") ? part
65
+ : part.replace(pattern, token => {
66
+ const expression = this.expressions.get(token.toLowerCase());
67
+ if (!expression)
68
+ throw new Error(`Unknown math placeholder: ${token}`);
69
+ return render ? render(expression.tex, { display: expression.display })
70
+ : escapeHtml(expression.source);
71
+ })).join("");
72
+ }
73
+ }
74
+ function escapeHtml(value) {
75
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
76
+ .replace(/"/g, "&quot;").replace(/'/g, "&#39;");
77
+ }
package/dist/types.d.ts CHANGED
@@ -41,10 +41,24 @@ export interface BadgeConfig {
41
41
  /** CSS class name(s) for the badge `<a>` element. */
42
42
  className?: string;
43
43
  }
44
- /** Options passed to {@link Bibliography.formatHtml}. */
44
+ /** Synchronous renderer returning trusted HTML. Sanitize untrusted renderer output. */
45
+ export type MathRenderer = (tex: string, context: {
46
+ display: boolean;
47
+ }) => string;
48
+ /**
49
+ * Options passed to {@link Bibliography.formatHtml} or
50
+ * {@link Bibliography.formatEntry}. `list` and `listAttributes` only apply to
51
+ * `formatHtml`, which is the only method emitting a wrapper element.
52
+ */
45
53
  export interface FormatOptions {
54
+ /** Render protected math as trusted HTML; requires preserveMath at construction.
55
+ * Without this callback, escaped original TeX delimiters are restored.
56
+ * Not settable on the constructor.
57
+ */
58
+ renderMath?: MathRenderer;
46
59
  /**
47
60
  * Fields to use for linking the title, checked in order.
61
+ * Overrides the constructor default, if any.
48
62
  * A `doi` value is expanded to `https://doi.org/<value>`, an `arxiv`
49
63
  * value to `https://arxiv.org/abs/<value>`, and direct values are only
50
64
  * accepted when they use `http`, `https`, or `mailto`.
@@ -52,7 +66,12 @@ export interface FormatOptions {
52
66
  * @default ['url', 'doi', 'arxiv']
53
67
  */
54
68
  titleLink?: string[];
55
- /** Badge configurations to append to each entry. */
69
+ /**
70
+ * Badge configurations to append to each entry.
71
+ * Overrides the constructor default, if any.
72
+ *
73
+ * @default [] (no badges)
74
+ */
56
75
  badges?: BadgeConfig[];
57
76
  /**
58
77
  * Wrapper list element.
@@ -69,6 +88,7 @@ export interface FormatOptions {
69
88
  /**
70
89
  * Auto-linkify bare `http(s)://` URLs in the rendered output that aren't
71
90
  * already inside `<a>`, `<script>`, or `<style>` tags.
91
+ * Overrides the constructor default, if any.
72
92
  *
73
93
  * @default true
74
94
  */
@@ -90,8 +110,18 @@ export interface BibEntry {
90
110
  /** The full raw BibTeX properties (unfiltered). */
91
111
  raw: Record<string, any>;
92
112
  }
113
+ /**
114
+ * Formatting options that can be set once on the constructor and overridden
115
+ * by the options of an individual `formatHtml`/`formatEntry` call.
116
+ */
117
+ export type FormatDefaults = Pick<FormatOptions, "titleLink" | "badges" | "linkifyUrls">;
93
118
  /** Options for constructing a {@link Bibliography}. */
94
- export interface BibliographyOptions {
119
+ export interface BibliographyOptions extends FormatDefaults {
120
+ /** Preserve math in supported display-text fields before CSL conversion.
121
+ * Opt-in; entries' CSL text contains internal placeholders until HTML formatting.
122
+ * Raw/custom fields remain unchanged. Unclosed or empty math throws.
123
+ */
124
+ preserveMath?: boolean;
95
125
  /**
96
126
  * BibTeX input — either a raw BibTeX string or a file path.
97
127
  * When a file path is given, it is read synchronously at construction time.
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@behackl/citation-js-extras",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Preserve custom BibTeX fields through citation-js and render academic bibliographies with linked titles, badges, and more.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "import": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
12
  }
13
13
  },
14
14
  "files": [
@@ -16,6 +16,13 @@
16
16
  "README.md",
17
17
  "LICENSE"
18
18
  ],
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "test:package": "pnpm build && node scripts/test-package.mjs",
24
+ "prepublishOnly": "tsc"
25
+ },
19
26
  "keywords": [
20
27
  "citation-js",
21
28
  "bibtex",
@@ -28,12 +35,12 @@
28
35
  "license": "MIT",
29
36
  "repository": {
30
37
  "type": "git",
31
- "url": "git+https://github.com/behackl/citation-js-extra.git"
38
+ "url": "git+https://github.com/behackl/citation-js-extras.git"
32
39
  },
33
40
  "bugs": {
34
- "url": "https://github.com/behackl/citation-js-extra/issues"
41
+ "url": "https://github.com/behackl/citation-js-extras/issues"
35
42
  },
36
- "homepage": "https://github.com/behackl/citation-js-extra#readme",
43
+ "homepage": "https://github.com/behackl/citation-js-extras#readme",
37
44
  "publishConfig": {
38
45
  "access": "public"
39
46
  },
@@ -41,18 +48,20 @@
41
48
  "node": ">=18"
42
49
  },
43
50
  "sideEffects": false,
51
+ "packageManager": "pnpm@10.29.1",
44
52
  "peerDependencies": {
45
53
  "citation-js": ">=0.7.0"
46
54
  },
47
55
  "devDependencies": {
56
+ "@mathjax/src": "4.1.0",
48
57
  "@types/node": "^25.2.3",
49
58
  "citation-js": "^0.7.22",
50
59
  "typescript": "^5.9.3",
51
60
  "vitest": "^3.1.4"
52
61
  },
53
- "scripts": {
54
- "build": "tsc",
55
- "test": "vitest run",
56
- "test:watch": "vitest"
62
+ "pnpm": {
63
+ "onlyBuiltDependencies": [
64
+ "esbuild"
65
+ ]
57
66
  }
58
- }
67
+ }