@citestyle/registry 1.0.0 → 1.1.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/package.json +3 -3
- package/src/registry.js +85 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@citestyle/registry",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Citation registry: year-suffixes, numbering, sorting, disambiguation (~5-8KB)",
|
|
5
5
|
"author": "Uniweb",
|
|
6
6
|
"homepage": "https://github.com/uniweb/csl/tree/main/packages/registry",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"README.md"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@citestyle/core": "1.0.
|
|
34
|
-
"@citestyle/types": "1.0.
|
|
33
|
+
"@citestyle/core": "1.0.1",
|
|
34
|
+
"@citestyle/types": "1.0.1"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"vitest": "^3.1.1"
|
package/src/registry.js
CHANGED
|
@@ -87,8 +87,22 @@ export function createRegistry(style, options = {}) {
|
|
|
87
87
|
* Format a citation cluster.
|
|
88
88
|
* Resolves item IDs to items and delegates to the compiled style's citation().
|
|
89
89
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
90
|
+
* **Author suppression** (CSL `suppress-author` mode) is implemented as a
|
|
91
|
+
* post-render strip: render normally, then remove the
|
|
92
|
+
* `<span class="csl-author">…</span>` wrappers and any orphaned
|
|
93
|
+
* surrounding punctuation (leading "Smith, " or trailing space). The
|
|
94
|
+
* compiler doesn't emit a per-mode citation branch yet — the standard
|
|
95
|
+
* CSL way — so this layer fills the gap. Two ways to trigger it:
|
|
96
|
+
*
|
|
97
|
+
* - `ctx.mode === 'suppress-author'` (whole cluster, CSL-canonical)
|
|
98
|
+
* - `cite.suppressAuthor === true` on every cite in a single-cite cluster
|
|
99
|
+
*
|
|
100
|
+
* Mixed-suppression clusters (some cites suppressed, others not) aren't
|
|
101
|
+
* supported here — the cluster renderer would need per-cite re-rendering,
|
|
102
|
+
* which is more rework than the current consumer demands.
|
|
103
|
+
*
|
|
104
|
+
* @param {Array<{id: string, item?: object, locator?: string, label?: string, prefix?: string, suffix?: string, suppressAuthor?: boolean}>} cites
|
|
105
|
+
* @param {object} [ctx] - Format context. `mode: 'suppress-author'` strips the author from the rendered cluster.
|
|
92
106
|
* @returns {import('@citestyle/types').FormattedCitation}
|
|
93
107
|
*/
|
|
94
108
|
function cite(cites, ctx = {}) {
|
|
@@ -114,6 +128,7 @@ export function createRegistry(style, options = {}) {
|
|
|
114
128
|
if (c.label != null) citeObj.label = c.label
|
|
115
129
|
if (c.prefix != null) citeObj.prefix = c.prefix
|
|
116
130
|
if (c.suffix != null) citeObj.suffix = c.suffix
|
|
131
|
+
if (c.suppressAuthor) citeObj.suppressAuthor = true
|
|
117
132
|
|
|
118
133
|
// Attach per-item disambiguation overrides
|
|
119
134
|
const disambig = disambigState.get(String(item.id))
|
|
@@ -122,16 +137,26 @@ export function createRegistry(style, options = {}) {
|
|
|
122
137
|
return citeObj
|
|
123
138
|
})
|
|
124
139
|
|
|
140
|
+
// Decide whether to suppress the author across the whole cluster.
|
|
141
|
+
const allCitesSuppress = resolvedCites.length > 0 &&
|
|
142
|
+
resolvedCites.every(c => c.suppressAuthor)
|
|
143
|
+
const suppressAuthor = ctx.mode === 'suppress-author' || allCitesSuppress
|
|
144
|
+
|
|
125
145
|
// Apply cite collapsing
|
|
126
146
|
const collapse = style.meta?.collapse
|
|
147
|
+
let result
|
|
127
148
|
if (collapse === 'citation-number' && resolvedCites.length > 1) {
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
149
|
+
result = collapseNumericCitation(resolvedCites, ctx)
|
|
150
|
+
} else if ((collapse === 'year' || collapse === 'year-suffix' || collapse === 'year-suffix-ranged') && resolvedCites.length > 1) {
|
|
151
|
+
result = collapseAuthorDateCitation(resolvedCites, ctx, collapse)
|
|
152
|
+
} else {
|
|
153
|
+
result = style.citation(resolvedCites, ctx)
|
|
132
154
|
}
|
|
133
155
|
|
|
134
|
-
|
|
156
|
+
if (suppressAuthor) {
|
|
157
|
+
result = stripAuthor(result)
|
|
158
|
+
}
|
|
159
|
+
return result
|
|
135
160
|
}
|
|
136
161
|
|
|
137
162
|
// ── Numeric cite collapsing ──────────────────────────────────────────────
|
|
@@ -565,6 +590,59 @@ export function createRegistry(style, options = {}) {
|
|
|
565
590
|
|
|
566
591
|
// ── Helper functions ──────────────────────────────────────────────────────
|
|
567
592
|
|
|
593
|
+
/**
|
|
594
|
+
* Strip the author from a formatted citation result.
|
|
595
|
+
*
|
|
596
|
+
* Citation styles that opt into `<span class="csl-author">` wrappers
|
|
597
|
+
* (every shipped style does today) can have their author portion
|
|
598
|
+
* removed by string-substitution: drop the wrapper and clean up
|
|
599
|
+
* orphaned surrounding punctuation. The result is the
|
|
600
|
+
* "(2024, p. 42)" / "(2024)" form authors reach for when the author's
|
|
601
|
+
* name already appears in the running prose.
|
|
602
|
+
*
|
|
603
|
+
* Cleanup rules:
|
|
604
|
+
* - "Author, " (author span followed by comma + space) -> ""
|
|
605
|
+
* - "Author " (author span followed by space) -> ""
|
|
606
|
+
* - bare "Author" with no trailing punctuation -> ""
|
|
607
|
+
*
|
|
608
|
+
* The text form is rebuilt from the stripped HTML rather than parallel-
|
|
609
|
+
* stripped — keeps the two views in sync without duplicating the
|
|
610
|
+
* regex set.
|
|
611
|
+
*/
|
|
612
|
+
function stripAuthor(result) {
|
|
613
|
+
if (!result || typeof result.html !== 'string') return result
|
|
614
|
+
|
|
615
|
+
let html = result.html
|
|
616
|
+
// Remove every csl-author span plus a trailing ", " or " " if present.
|
|
617
|
+
// Multi-cite clusters can have more than one — flag g.
|
|
618
|
+
html = html.replace(
|
|
619
|
+
/<span class="csl-author"[^>]*>[\s\S]*?<\/span>(,\s*|\s+)?/g,
|
|
620
|
+
'',
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
// Tidy any leftover doubled spaces or stray opening punctuation.
|
|
624
|
+
html = html
|
|
625
|
+
.replace(/\(\s+/g, '(')
|
|
626
|
+
.replace(/\s{2,}/g, ' ')
|
|
627
|
+
.replace(/\s+,/g, ',')
|
|
628
|
+
.replace(/\(,\s*/g, '(')
|
|
629
|
+
|
|
630
|
+
const text = htmlToPlainText(html)
|
|
631
|
+
return { text, html }
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** Strip HTML tags and decode the entity subset citestyle emits. */
|
|
635
|
+
function htmlToPlainText(html) {
|
|
636
|
+
if (!html) return ''
|
|
637
|
+
return String(html)
|
|
638
|
+
.replace(/<[^>]+>/g, '')
|
|
639
|
+
.replace(/&/g, '&')
|
|
640
|
+
.replace(/</g, '<')
|
|
641
|
+
.replace(/>/g, '>')
|
|
642
|
+
.replace(/"/g, '"')
|
|
643
|
+
.replace(/'/g, "'")
|
|
644
|
+
}
|
|
645
|
+
|
|
568
646
|
/**
|
|
569
647
|
* Apply subsequent-author-substitute to formatted bibliography entries.
|
|
570
648
|
*/
|