@uniweb/scholar 0.2.0 → 0.2.2
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 +42 -32
- package/package.json +19 -6
- package/src/bibliography/formatters/index.js +62 -23
- package/src/bibliography/index.js +8 -29
- package/src/bibliography/normalize.js +234 -0
- package/src/bibliography/parsers/index.js +35 -1
- package/src/citations/Bibliography.jsx +26 -54
- package/src/citations/Citation.jsx +35 -145
- package/src/citations/CitationProvider.jsx +83 -85
- package/src/components/AuthorList.jsx +65 -40
- package/src/components/BibtexBlock.jsx +1 -1
- package/src/components/CiteButton.jsx +78 -45
- package/src/components/DoiLink.jsx +27 -21
- package/src/index.js +15 -5
- package/src/math/Equation.jsx +45 -75
- package/src/math/index.js +19 -4
- package/src/bibliography/formatters/apa.js +0 -352
- package/src/bibliography/formatters/chicago.js +0 -339
- package/src/bibliography/formatters/ieee.js +0 -332
- package/src/bibliography/formatters/mla.js +0 -335
- package/src/bibliography/parsers/bibtex.js +0 -405
- package/src/bibliography/utils/authors.js +0 -277
- package/src/bibliography/utils/dates.js +0 -228
- package/src/bibliography/utils/index.js +0 -8
- package/src/math/Math.jsx +0 -115
- package/src/math/katex-loader.js +0 -105
|
@@ -1,69 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bibliography Component
|
|
3
3
|
*
|
|
4
|
-
* Renders a formatted list of references
|
|
4
|
+
* Renders a formatted list of references using @citestyle/registry.
|
|
5
|
+
* The registry handles sorting, year-suffix disambiguation, and
|
|
6
|
+
* subsequent-author-substitute (em-dash for repeated authors).
|
|
5
7
|
*
|
|
6
8
|
* @module @uniweb/scholar/citations/Bibliography
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
import React, { useMemo } from 'react'
|
|
10
12
|
import { useCitation } from './CitationProvider.jsx'
|
|
11
|
-
import { formatReference } from '../bibliography/formatters/index.js'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
|
-
* Bibliography - Renders the list of
|
|
15
|
+
* Bibliography - Renders the list of references
|
|
15
16
|
*
|
|
16
17
|
* @param {Object} props
|
|
17
18
|
* @param {string} [props.title='References'] - Section title
|
|
18
19
|
* @param {boolean} [props.showTitle=true] - Whether to show the title
|
|
19
|
-
* @param {boolean} [props.showAll=false] - Show all references, not just cited ones
|
|
20
20
|
* @param {string} [props.className] - Additional CSS classes
|
|
21
21
|
* @param {string} [props.itemClassName] - CSS classes for each item
|
|
22
22
|
*
|
|
23
23
|
* @example
|
|
24
24
|
* <Bibliography title="Works Cited" />
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* // Show all references including uncited ones
|
|
28
|
-
* <Bibliography showAll />
|
|
29
25
|
*/
|
|
30
26
|
export function Bibliography({
|
|
31
27
|
title = 'References',
|
|
32
28
|
showTitle = true,
|
|
33
|
-
showAll = false,
|
|
34
29
|
className,
|
|
35
30
|
itemClassName,
|
|
36
31
|
...props
|
|
37
32
|
}) {
|
|
38
|
-
const {
|
|
39
|
-
|
|
40
|
-
// Get references to display
|
|
41
|
-
const refsToShow = useMemo(() => {
|
|
42
|
-
if (showAll) {
|
|
43
|
-
return references
|
|
44
|
-
}
|
|
45
|
-
return getCitedReferences()
|
|
46
|
-
}, [showAll, references, getCitedReferences])
|
|
33
|
+
const { getBibliography, styleMeta } = useCitation()
|
|
47
34
|
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
id: ref.id || ref.citationKey,
|
|
54
|
-
number,
|
|
55
|
-
formatted: formatReference(ref, { style }),
|
|
56
|
-
reference: ref,
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
}, [refsToShow, style, cited])
|
|
35
|
+
// Get the fully formatted bibliography from the registry.
|
|
36
|
+
// The registry applies: sorting, subsequent-author-substitute,
|
|
37
|
+
// year-suffix disambiguation, and citation numbering.
|
|
38
|
+
const entries = useMemo(() => getBibliography(), [getBibliography])
|
|
60
39
|
|
|
61
|
-
if (!
|
|
40
|
+
if (!entries.length) {
|
|
62
41
|
return null
|
|
63
42
|
}
|
|
64
43
|
|
|
65
|
-
// Use numbered list for IEEE
|
|
66
|
-
const isNumeric =
|
|
44
|
+
// Use numbered list for numeric styles (IEEE, Vancouver, etc.)
|
|
45
|
+
const isNumeric = styleMeta?.class === 'note' ||
|
|
46
|
+
styleMeta?.collapse === 'citation-number'
|
|
67
47
|
|
|
68
48
|
return (
|
|
69
49
|
<section className={className} {...props}>
|
|
@@ -87,38 +67,30 @@ export function Bibliography({
|
|
|
87
67
|
lineHeight: '1.8',
|
|
88
68
|
}}
|
|
89
69
|
>
|
|
90
|
-
{
|
|
70
|
+
{entries.map((entry, i) => (
|
|
91
71
|
<li
|
|
92
|
-
key={
|
|
93
|
-
id={`ref-${
|
|
72
|
+
key={entry.parts?.id || i}
|
|
73
|
+
id={`ref-${entry.parts?.id || i}`}
|
|
94
74
|
className={itemClassName}
|
|
95
|
-
style={{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
>
|
|
99
|
-
{ref.formatted}
|
|
100
|
-
</li>
|
|
75
|
+
style={{ marginBottom: '0.75rem' }}
|
|
76
|
+
dangerouslySetInnerHTML={{ __html: entry.html }}
|
|
77
|
+
/>
|
|
101
78
|
))}
|
|
102
79
|
</ol>
|
|
103
80
|
) : (
|
|
104
|
-
<div
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
{formattedRefs.map((ref) => (
|
|
110
|
-
<p
|
|
111
|
-
key={ref.id}
|
|
112
|
-
id={`ref-${ref.id}`}
|
|
81
|
+
<div style={{ lineHeight: '1.8' }}>
|
|
82
|
+
{entries.map((entry, i) => (
|
|
83
|
+
<div
|
|
84
|
+
key={entry.parts?.id || i}
|
|
85
|
+
id={`ref-${entry.parts?.id || i}`}
|
|
113
86
|
className={itemClassName}
|
|
114
87
|
style={{
|
|
115
88
|
marginBottom: '0.75rem',
|
|
116
89
|
paddingLeft: '2rem',
|
|
117
90
|
textIndent: '-2rem',
|
|
118
91
|
}}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
</p>
|
|
92
|
+
dangerouslySetInnerHTML={{ __html: entry.html }}
|
|
93
|
+
/>
|
|
122
94
|
))}
|
|
123
95
|
</div>
|
|
124
96
|
)}
|
|
@@ -1,44 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Citation Component
|
|
3
3
|
*
|
|
4
|
-
* Renders an inline citation
|
|
4
|
+
* Renders an inline citation using @citestyle/registry for proper
|
|
5
|
+
* author-date formatting, year-suffix disambiguation, numeric citations,
|
|
6
|
+
* and cite collapsing.
|
|
5
7
|
*
|
|
6
8
|
* @module @uniweb/scholar/citations/Citation
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
import React, { useMemo } from 'react'
|
|
10
12
|
import { useCitation } from './CitationProvider.jsx'
|
|
11
|
-
import { getYear } from '../bibliography/utils/dates.js'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Format author names for inline citation
|
|
15
|
-
*
|
|
16
|
-
* @param {Array} authors - Array of author objects
|
|
17
|
-
* @param {string} style - Citation style
|
|
18
|
-
* @returns {string}
|
|
19
|
-
*/
|
|
20
|
-
function formatInlineAuthors(authors, style) {
|
|
21
|
-
if (!authors?.length) return ''
|
|
22
|
-
|
|
23
|
-
const getName = (a) => {
|
|
24
|
-
if (typeof a === 'string') return a.split(',')[0].trim()
|
|
25
|
-
return a.family || a.lastName || ''
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (authors.length === 1) {
|
|
29
|
-
return getName(authors[0])
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (authors.length === 2) {
|
|
33
|
-
if (style === 'apa') {
|
|
34
|
-
return `${getName(authors[0])} & ${getName(authors[1])}`
|
|
35
|
-
}
|
|
36
|
-
return `${getName(authors[0])} and ${getName(authors[1])}`
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// 3+ authors
|
|
40
|
-
return `${getName(authors[0])} et al.`
|
|
41
|
-
}
|
|
42
13
|
|
|
43
14
|
/**
|
|
44
15
|
* Citation - Inline citation component
|
|
@@ -46,11 +17,10 @@ function formatInlineAuthors(authors, style) {
|
|
|
46
17
|
* @param {Object} props
|
|
47
18
|
* @param {string} [props.id] - Single reference ID to cite
|
|
48
19
|
* @param {Array<string>} [props.ids] - Multiple reference IDs to cite
|
|
49
|
-
* @param {string} [props.
|
|
20
|
+
* @param {string} [props.locator] - Page, chapter, or other locator
|
|
21
|
+
* @param {string} [props.label] - Locator label (e.g., 'page', 'chapter')
|
|
50
22
|
* @param {string} [props.prefix] - Text before citation (e.g., "see")
|
|
51
23
|
* @param {string} [props.suffix] - Text after citation
|
|
52
|
-
* @param {boolean} [props.suppressAuthor] - Show only year (for narrative citations)
|
|
53
|
-
* @param {string} [props.format='parenthetical'] - Format: 'parenthetical', 'narrative', or 'numeric'
|
|
54
24
|
* @param {string} [props.className] - Additional CSS classes
|
|
55
25
|
*
|
|
56
26
|
* @example
|
|
@@ -58,138 +28,58 @@ function formatInlineAuthors(authors, style) {
|
|
|
58
28
|
* <Citation id="smith2024" />
|
|
59
29
|
*
|
|
60
30
|
* @example
|
|
61
|
-
* //
|
|
62
|
-
* <Citation id="smith2024"
|
|
63
|
-
*
|
|
64
|
-
* @example
|
|
65
|
-
* // With page number: (Smith, 2024, p. 42)
|
|
66
|
-
* <Citation id="smith2024" page="42" />
|
|
31
|
+
* // With page locator: (Smith, 2024, p. 42)
|
|
32
|
+
* <Citation id="smith2024" locator="42" label="page" />
|
|
67
33
|
*
|
|
68
34
|
* @example
|
|
69
35
|
* // Multiple sources: (Smith, 2024; Jones, 2023)
|
|
70
36
|
* <Citation ids={['smith2024', 'jones2023']} />
|
|
71
37
|
*
|
|
72
38
|
* @example
|
|
73
|
-
* //
|
|
74
|
-
* <Citation id="smith2024"
|
|
39
|
+
* // With prefix: (see Smith, 2024)
|
|
40
|
+
* <Citation id="smith2024" prefix="see" />
|
|
75
41
|
*/
|
|
76
42
|
export function Citation({
|
|
77
43
|
id,
|
|
78
44
|
ids,
|
|
79
|
-
|
|
45
|
+
locator,
|
|
46
|
+
label = 'page',
|
|
80
47
|
prefix,
|
|
81
48
|
suffix,
|
|
82
|
-
suppressAuthor = false,
|
|
83
|
-
format = 'parenthetical',
|
|
84
49
|
className,
|
|
85
50
|
...props
|
|
86
51
|
}) {
|
|
87
|
-
const { cite
|
|
88
|
-
|
|
89
|
-
//
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// Numeric format (IEEE style)
|
|
110
|
-
if (format === 'numeric' || style === 'ieee') {
|
|
111
|
-
const numbers = citations.map((c) => c.number).join(', ')
|
|
112
|
-
return `[${numbers}]`
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Author-date formats
|
|
116
|
-
const parts = citations.map((c) => {
|
|
117
|
-
if (!c.reference) return c.id
|
|
118
|
-
|
|
119
|
-
const authors = c.reference.authors || []
|
|
120
|
-
const year = getYear(c.reference.year || c.reference.date)
|
|
121
|
-
const authorStr = formatInlineAuthors(authors, style)
|
|
122
|
-
|
|
123
|
-
if (suppressAuthor) {
|
|
124
|
-
return year || 'n.d.'
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (!year) {
|
|
128
|
-
return authorStr || c.id
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return `${authorStr}, ${year}`
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
let text = parts.join('; ')
|
|
135
|
-
|
|
136
|
-
// Add page number
|
|
137
|
-
if (page) {
|
|
138
|
-
const pagePrefix = page.includes('-') || page.includes('–') ? 'pp.' : 'p.'
|
|
139
|
-
text += `, ${pagePrefix} ${page}`
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Add prefix/suffix
|
|
143
|
-
if (prefix) text = `${prefix} ${text}`
|
|
144
|
-
if (suffix) text = `${text}, ${suffix}`
|
|
145
|
-
|
|
146
|
-
// Wrap in parentheses for parenthetical format
|
|
147
|
-
if (format === 'parenthetical') {
|
|
148
|
-
return `(${text})`
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return text
|
|
152
|
-
}, [citations, format, style, suppressAuthor, page, prefix, suffix])
|
|
153
|
-
|
|
154
|
-
// For narrative format, we need to split author and year
|
|
155
|
-
if (format === 'narrative' && citations.length === 1 && citations[0].reference) {
|
|
156
|
-
const c = citations[0]
|
|
157
|
-
const authors = c.reference.authors || []
|
|
158
|
-
const year = getYear(c.reference.year || c.reference.date)
|
|
159
|
-
const authorStr = formatInlineAuthors(authors, style)
|
|
160
|
-
|
|
161
|
-
let yearPart = year || 'n.d.'
|
|
162
|
-
if (page) {
|
|
163
|
-
const pagePrefix = page.includes('-') || page.includes('–') ? 'pp.' : 'p.'
|
|
164
|
-
yearPart += `, ${pagePrefix} ${page}`
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return (
|
|
168
|
-
<span className={className} {...props}>
|
|
169
|
-
{prefix && `${prefix} `}
|
|
170
|
-
{authorStr}{' '}
|
|
171
|
-
<a
|
|
172
|
-
href={`#ref-${c.id}`}
|
|
173
|
-
style={{ textDecoration: 'none' }}
|
|
174
|
-
>
|
|
175
|
-
({yearPart})
|
|
176
|
-
</a>
|
|
177
|
-
{suffix && ` ${suffix}`}
|
|
178
|
-
</span>
|
|
179
|
-
)
|
|
180
|
-
}
|
|
52
|
+
const { cite } = useCitation()
|
|
53
|
+
|
|
54
|
+
// Build cite references array
|
|
55
|
+
const citeRefs = useMemo(() => {
|
|
56
|
+
const allIds = ids?.length ? ids : id ? [id] : []
|
|
57
|
+
return allIds.map((refId, i) => ({
|
|
58
|
+
id: refId,
|
|
59
|
+
// Only the first cite gets the locator/prefix/suffix
|
|
60
|
+
...(i === 0 && locator ? { locator, label } : {}),
|
|
61
|
+
...(i === 0 && prefix ? { prefix } : {}),
|
|
62
|
+
...(i === allIds.length - 1 && suffix ? { suffix } : {}),
|
|
63
|
+
}))
|
|
64
|
+
}, [id, ids, locator, label, prefix, suffix])
|
|
65
|
+
|
|
66
|
+
// Format through the registry — handles disambiguation, collapsing, numbering
|
|
67
|
+
const result = useMemo(() => {
|
|
68
|
+
if (!citeRefs.length) return { text: '??', html: '??' }
|
|
69
|
+
return cite(citeRefs)
|
|
70
|
+
}, [citeRefs, cite])
|
|
71
|
+
|
|
72
|
+
// Link to bibliography entry
|
|
73
|
+
const firstId = citeRefs[0]?.id
|
|
181
74
|
|
|
182
|
-
// Single link for all citations
|
|
183
|
-
const firstId = citations[0]?.id
|
|
184
75
|
return (
|
|
185
76
|
<a
|
|
186
77
|
href={firstId ? `#ref-${firstId}` : undefined}
|
|
187
78
|
className={className}
|
|
188
79
|
style={{ textDecoration: 'none' }}
|
|
80
|
+
dangerouslySetInnerHTML={{ __html: result.html }}
|
|
189
81
|
{...props}
|
|
190
|
-
|
|
191
|
-
{citationText}
|
|
192
|
-
</a>
|
|
82
|
+
/>
|
|
193
83
|
)
|
|
194
84
|
}
|
|
195
85
|
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Citation Provider Component
|
|
3
3
|
*
|
|
4
|
-
* Provides context
|
|
5
|
-
*
|
|
4
|
+
* Provides citation context powered by @citestyle/registry.
|
|
5
|
+
* Handles year-suffix disambiguation, bibliography sorting, cite collapsing,
|
|
6
|
+
* and numeric citation numbering — all automatically.
|
|
6
7
|
*
|
|
7
8
|
* @module @uniweb/scholar/citations/CitationProvider
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
|
-
import React, { createContext, useContext,
|
|
11
|
-
import {
|
|
11
|
+
import React, { createContext, useContext, useMemo, useCallback } from 'react'
|
|
12
|
+
import { createRegistry } from '@citestyle/registry'
|
|
13
|
+
import { parseBibtex } from '../bibliography/parsers/index.js'
|
|
14
|
+
import { compiledStyles } from '../bibliography/formatters/index.js'
|
|
15
|
+
import { toCslJson } from '../bibliography/normalize.js'
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
18
|
* Citation context
|
|
@@ -18,7 +22,15 @@ export const CitationContext = createContext(null)
|
|
|
18
22
|
/**
|
|
19
23
|
* Hook to access citation context
|
|
20
24
|
*
|
|
21
|
-
* @returns {{
|
|
25
|
+
* @returns {{
|
|
26
|
+
* registry: Object,
|
|
27
|
+
* cite: Function,
|
|
28
|
+
* getBibliography: Function,
|
|
29
|
+
* getReference: Function,
|
|
30
|
+
* references: Array,
|
|
31
|
+
* style: string,
|
|
32
|
+
* styleMeta: Object
|
|
33
|
+
* }}
|
|
22
34
|
*/
|
|
23
35
|
export function useCitation() {
|
|
24
36
|
const ctx = useContext(CitationContext)
|
|
@@ -28,113 +40,99 @@ export function useCitation() {
|
|
|
28
40
|
return ctx
|
|
29
41
|
}
|
|
30
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Normalize references to CSL-JSON items.
|
|
45
|
+
* Accepts: BibTeX string, array of Scholar pubs, array of CSL-JSON items.
|
|
46
|
+
*/
|
|
47
|
+
function normalizeReferences(references) {
|
|
48
|
+
if (!references) return []
|
|
49
|
+
|
|
50
|
+
// BibTeX string → parse to CSL-JSON
|
|
51
|
+
if (typeof references === 'string') {
|
|
52
|
+
return parseBibtex(references)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!Array.isArray(references)) return []
|
|
56
|
+
|
|
57
|
+
// Detect if items are already CSL-JSON (have `issued` or `container-title`)
|
|
58
|
+
// or Scholar-shaped (have `authors`, `journal`, `year` as number)
|
|
59
|
+
return references.map((ref) => {
|
|
60
|
+
if (ref.issued || ref['container-title'] || ref['date-parts']) {
|
|
61
|
+
// Already CSL-JSON — ensure it has an id
|
|
62
|
+
return { id: ref.id || ref.citationKey || 'unknown', ...ref }
|
|
63
|
+
}
|
|
64
|
+
// Scholar-shaped → convert
|
|
65
|
+
return toCslJson(ref)
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
31
69
|
/**
|
|
32
70
|
* CitationProvider - Manages citation state for a document
|
|
33
71
|
*
|
|
72
|
+
* Uses @citestyle/registry for proper disambiguation, sorting, and numbering.
|
|
73
|
+
*
|
|
34
74
|
* @param {Object} props
|
|
35
|
-
* @param {Array|string} props.references - Array of
|
|
36
|
-
* @param {string} [props.style='apa'] - Citation style
|
|
75
|
+
* @param {Array|string} props.references - Array of publications (Scholar or CSL-JSON) or BibTeX string
|
|
76
|
+
* @param {string} [props.style='apa'] - Citation style name
|
|
37
77
|
* @param {React.ReactNode} props.children
|
|
38
78
|
*
|
|
39
79
|
* @example
|
|
40
|
-
* <CitationProvider references={
|
|
80
|
+
* <CitationProvider references={pubs} style="apa">
|
|
41
81
|
* <p>According to <Citation id="smith2024" />, this is true.</p>
|
|
42
82
|
* <Bibliography />
|
|
43
83
|
* </CitationProvider>
|
|
44
84
|
*/
|
|
45
85
|
export function CitationProvider({ references, style = 'apa', children }) {
|
|
46
|
-
//
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
86
|
+
// Resolve compiled style
|
|
87
|
+
const compiled = useMemo(
|
|
88
|
+
() => compiledStyles[style.toLowerCase()] || compiledStyles.apa,
|
|
89
|
+
[style]
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
// Normalize all references to CSL-JSON
|
|
93
|
+
const cslItems = useMemo(
|
|
94
|
+
() => normalizeReferences(references),
|
|
95
|
+
[references]
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
// Create registry — handles disambiguation, sorting, numbering
|
|
99
|
+
const registry = useMemo(() => {
|
|
100
|
+
const reg = createRegistry(compiled)
|
|
101
|
+
if (cslItems.length) {
|
|
102
|
+
reg.addItems(cslItems)
|
|
57
103
|
}
|
|
58
|
-
return
|
|
59
|
-
}, [
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
const refsById = useMemo(() => {
|
|
63
|
-
const map = new Map()
|
|
64
|
-
refs.forEach((ref) => {
|
|
65
|
-
if (ref.id) map.set(ref.id, ref)
|
|
66
|
-
if (ref.citationKey) map.set(ref.citationKey, ref)
|
|
67
|
-
})
|
|
68
|
-
return map
|
|
69
|
-
}, [refs])
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Cite a reference by id
|
|
73
|
-
* Returns the citation number (order in which it was first cited)
|
|
74
|
-
*/
|
|
104
|
+
return reg
|
|
105
|
+
}, [compiled, cslItems])
|
|
106
|
+
|
|
107
|
+
// Convenience: format an inline citation cluster
|
|
75
108
|
const cite = useCallback(
|
|
76
|
-
(
|
|
77
|
-
|
|
78
|
-
if (cited.has(id)) {
|
|
79
|
-
return cited.get(id)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Add to cited list
|
|
83
|
-
const number = cited.size + 1
|
|
84
|
-
setCited((prev) => {
|
|
85
|
-
if (prev.has(id)) return prev
|
|
86
|
-
const next = new Map(prev)
|
|
87
|
-
next.set(id, number)
|
|
88
|
-
return next
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
return number
|
|
92
|
-
},
|
|
93
|
-
[cited]
|
|
109
|
+
(cites, ctx) => registry.cite(cites, ctx),
|
|
110
|
+
[registry]
|
|
94
111
|
)
|
|
95
112
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
(id) => {
|
|
101
|
-
return {
|
|
102
|
-
number: cited.get(id),
|
|
103
|
-
reference: refsById.get(id),
|
|
104
|
-
isCited: cited.has(id),
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
[cited, refsById]
|
|
113
|
+
// Convenience: get the full formatted bibliography
|
|
114
|
+
const getBibliography = useCallback(
|
|
115
|
+
(ctx) => registry.getBibliography(ctx),
|
|
116
|
+
[registry]
|
|
108
117
|
)
|
|
109
118
|
|
|
110
|
-
|
|
111
|
-
* Get reference by id
|
|
112
|
-
*/
|
|
119
|
+
// Look up a CSL-JSON item by id
|
|
113
120
|
const getReference = useCallback(
|
|
114
|
-
(id) =>
|
|
115
|
-
[
|
|
121
|
+
(id) => registry.getItem(id),
|
|
122
|
+
[registry]
|
|
116
123
|
)
|
|
117
124
|
|
|
118
|
-
/**
|
|
119
|
-
* Get all cited references in citation order
|
|
120
|
-
*/
|
|
121
|
-
const getCitedReferences = useCallback(() => {
|
|
122
|
-
const sorted = Array.from(cited.entries()).sort((a, b) => a[1] - b[1])
|
|
123
|
-
return sorted.map(([id]) => refsById.get(id)).filter(Boolean)
|
|
124
|
-
}, [cited, refsById])
|
|
125
|
-
|
|
126
125
|
const value = useMemo(
|
|
127
126
|
() => ({
|
|
127
|
+
registry,
|
|
128
128
|
cite,
|
|
129
|
-
|
|
129
|
+
getBibliography,
|
|
130
130
|
getReference,
|
|
131
|
-
|
|
132
|
-
references: refs,
|
|
133
|
-
refsById,
|
|
131
|
+
references: cslItems,
|
|
134
132
|
style,
|
|
135
|
-
|
|
133
|
+
styleMeta: compiled.meta,
|
|
136
134
|
}),
|
|
137
|
-
[cite,
|
|
135
|
+
[registry, cite, getBibliography, getReference, cslItems, style, compiled]
|
|
138
136
|
)
|
|
139
137
|
|
|
140
138
|
return (
|