@uniweb/scholar 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/package.json +6 -2
- 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 +9 -3
- 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/scholar",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Academic content utilities for Uniweb - math rendering, bibliographic references, and inline citations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -41,9 +41,13 @@
|
|
|
41
41
|
"node": ">=20.19"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
+
"@citestyle/bibtex": "^1.0.0",
|
|
45
|
+
"@citestyle/core": "^1.0.0",
|
|
46
|
+
"@citestyle/registry": "^1.0.0",
|
|
47
|
+
"@citestyle/styles": "^1.0.0",
|
|
44
48
|
"katex": "^0.16.0"
|
|
45
49
|
},
|
|
46
50
|
"peerDependencies": {
|
|
47
51
|
"react": "^18.0.0 || ^19.0.0"
|
|
48
52
|
}
|
|
49
|
-
}
|
|
53
|
+
}
|
|
@@ -1,39 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bibliography Formatters
|
|
3
3
|
*
|
|
4
|
+
* Delegates to @citestyle/* compiled styles for reference formatting.
|
|
5
|
+
* Returns structured output: { html, text, parts, links }.
|
|
6
|
+
*
|
|
4
7
|
* @module @uniweb/scholar/bibliography/formatters
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
import * as apa from '@citestyle/styles/apa'
|
|
11
|
+
import * as mla from '@citestyle/styles/mla'
|
|
12
|
+
import * as chicagoAD from '@citestyle/styles/chicago-author-date'
|
|
13
|
+
import * as ieee from '@citestyle/styles/ieee'
|
|
14
|
+
import * as harvard from '@citestyle/styles/harvard'
|
|
15
|
+
import * as vancouver from '@citestyle/styles/vancouver'
|
|
16
|
+
import { toCslJson } from '../normalize.js'
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Compiled styles keyed by short name.
|
|
20
|
+
* Each value is a compiled style module with bibliography(), citation(), meta.
|
|
21
|
+
*/
|
|
22
|
+
export const compiledStyles = {
|
|
23
|
+
apa,
|
|
24
|
+
mla,
|
|
25
|
+
chicago: chicagoAD,
|
|
26
|
+
ieee,
|
|
27
|
+
harvard,
|
|
28
|
+
vancouver,
|
|
29
|
+
}
|
|
16
30
|
|
|
17
31
|
/**
|
|
18
|
-
*
|
|
32
|
+
* Human-readable labels for each style.
|
|
19
33
|
*/
|
|
20
|
-
|
|
21
|
-
apa:
|
|
22
|
-
mla:
|
|
23
|
-
chicago:
|
|
24
|
-
ieee:
|
|
34
|
+
const styleLabels = {
|
|
35
|
+
apa: 'APA (7th ed.)',
|
|
36
|
+
mla: 'MLA (9th ed.)',
|
|
37
|
+
chicago: 'Chicago (Author-Date)',
|
|
38
|
+
ieee: 'IEEE',
|
|
39
|
+
harvard: 'Harvard',
|
|
40
|
+
vancouver: 'Vancouver',
|
|
25
41
|
}
|
|
26
42
|
|
|
27
43
|
/**
|
|
28
|
-
* Format a reference in a specific style
|
|
44
|
+
* Format a reference in a specific citation style.
|
|
45
|
+
*
|
|
46
|
+
* Accepts Scholar-shaped publication objects OR CSL-JSON items.
|
|
47
|
+
* Returns structured output from @citestyle — { html, text, parts, links }.
|
|
29
48
|
*
|
|
30
|
-
* @param {Object} publication - Publication data
|
|
49
|
+
* @param {Object} publication - Publication data (Scholar or CSL-JSON)
|
|
31
50
|
* @param {Object} [options]
|
|
32
|
-
* @param {string} [options.style='apa'] - Citation style
|
|
33
|
-
* @returns {string
|
|
51
|
+
* @param {string} [options.style='apa'] - Citation style name
|
|
52
|
+
* @returns {{ html: string, text: string, parts: Object, links: Object }}
|
|
34
53
|
*
|
|
35
54
|
* @example
|
|
36
|
-
* const
|
|
55
|
+
* const entry = formatReference({
|
|
37
56
|
* type: 'article',
|
|
38
57
|
* authors: [{ family: 'Smith', given: 'John' }],
|
|
39
58
|
* year: 2024,
|
|
@@ -43,14 +62,34 @@ export const formatters = {
|
|
|
43
62
|
* pages: '1-10',
|
|
44
63
|
* doi: '10.1234/example'
|
|
45
64
|
* }, { style: 'apa' })
|
|
65
|
+
*
|
|
66
|
+
* entry.text // "Smith, J. (2024). A Study of Something. ..."
|
|
67
|
+
* entry.html // "<span class=\"csl-entry\">Smith, J. (2024)..."
|
|
46
68
|
*/
|
|
47
69
|
export function formatReference(publication, options = {}) {
|
|
48
70
|
const { style = 'apa' } = options
|
|
49
71
|
|
|
50
|
-
const
|
|
51
|
-
if (!
|
|
52
|
-
throw new Error(
|
|
72
|
+
const compiled = compiledStyles[style.toLowerCase()]
|
|
73
|
+
if (!compiled) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Unknown citation style: ${style}. Available: ${Object.keys(compiledStyles).join(', ')}`
|
|
76
|
+
)
|
|
53
77
|
}
|
|
54
78
|
|
|
55
|
-
|
|
79
|
+
const item = toCslJson(publication)
|
|
80
|
+
return compiled.bibliography(item, {})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get metadata for all available citation styles.
|
|
85
|
+
* Useful for building UI style pickers.
|
|
86
|
+
*
|
|
87
|
+
* @returns {Array<{ id: string, label: string, class: string }>}
|
|
88
|
+
*/
|
|
89
|
+
export function getAvailableStyles() {
|
|
90
|
+
return Object.entries(compiledStyles).map(([id, style]) => ({
|
|
91
|
+
id,
|
|
92
|
+
label: styleLabels[id] || style.meta?.title || id,
|
|
93
|
+
class: style.meta?.class || 'in-text',
|
|
94
|
+
}))
|
|
56
95
|
}
|
|
@@ -1,42 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bibliography Module
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Reference formatting powered by @citestyle/* compiled styles,
|
|
5
|
+
* BibTeX parsing and export, and data normalization utilities.
|
|
5
6
|
*
|
|
6
7
|
* @module @uniweb/scholar/bibliography
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
// Formatters
|
|
10
|
+
// Formatters — citestyle-powered
|
|
10
11
|
export {
|
|
11
12
|
formatReference,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
formatChicago,
|
|
15
|
-
formatIeee,
|
|
16
|
-
formatters,
|
|
13
|
+
getAvailableStyles,
|
|
14
|
+
compiledStyles,
|
|
17
15
|
} from './formatters/index.js'
|
|
18
16
|
|
|
19
|
-
// Parsers
|
|
17
|
+
// Parsers — delegated to @citestyle/bibtex
|
|
20
18
|
export { parseBibtex, exportBibtex } from './parsers/index.js'
|
|
21
19
|
|
|
22
|
-
//
|
|
23
|
-
export {
|
|
24
|
-
formatAuthors,
|
|
25
|
-
parseAuthorName,
|
|
26
|
-
parseAuthorString,
|
|
27
|
-
getInitials,
|
|
28
|
-
formatApaAuthors,
|
|
29
|
-
formatMlaAuthors,
|
|
30
|
-
formatChicagoAuthors,
|
|
31
|
-
formatIeeeAuthors,
|
|
32
|
-
} from './utils/authors.js'
|
|
33
|
-
|
|
34
|
-
export {
|
|
35
|
-
formatDate,
|
|
36
|
-
parseDate,
|
|
37
|
-
getYear,
|
|
38
|
-
formatApaDate,
|
|
39
|
-
formatMlaDate,
|
|
40
|
-
formatChicagoDate,
|
|
41
|
-
formatIeeeDate,
|
|
42
|
-
} from './utils/dates.js'
|
|
20
|
+
// Normalization — Scholar ↔ CSL-JSON bridge
|
|
21
|
+
export { toCslJson, fromCslJson, mapType, unmapType } from './normalize.js'
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Normalization Layer
|
|
3
|
+
*
|
|
4
|
+
* Bidirectional bridge between Scholar's publication objects (BibTeX-style
|
|
5
|
+
* field names) and CSL-JSON items (the format @citestyle/* expects).
|
|
6
|
+
*
|
|
7
|
+
* @module @uniweb/scholar/bibliography/normalize
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Scholar type → CSL-JSON type
|
|
11
|
+
const TYPE_TO_CSL = {
|
|
12
|
+
article: 'article-journal',
|
|
13
|
+
journal: 'article-journal',
|
|
14
|
+
book: 'book',
|
|
15
|
+
inbook: 'chapter',
|
|
16
|
+
incollection: 'chapter',
|
|
17
|
+
chapter: 'chapter',
|
|
18
|
+
inproceedings: 'paper-conference',
|
|
19
|
+
conference: 'paper-conference',
|
|
20
|
+
webpage: 'webpage',
|
|
21
|
+
website: 'webpage',
|
|
22
|
+
online: 'webpage',
|
|
23
|
+
thesis: 'thesis',
|
|
24
|
+
phdthesis: 'thesis',
|
|
25
|
+
mastersthesis: 'thesis',
|
|
26
|
+
report: 'report',
|
|
27
|
+
techreport: 'report',
|
|
28
|
+
misc: 'article', // sensible default
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// CSL-JSON type → Scholar type (reverse mapping, picks first match)
|
|
32
|
+
const TYPE_FROM_CSL = {
|
|
33
|
+
'article-journal': 'article',
|
|
34
|
+
book: 'book',
|
|
35
|
+
chapter: 'incollection',
|
|
36
|
+
'paper-conference': 'inproceedings',
|
|
37
|
+
webpage: 'webpage',
|
|
38
|
+
thesis: 'thesis',
|
|
39
|
+
report: 'report',
|
|
40
|
+
article: 'article',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Map a Scholar entry type to a CSL-JSON type.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} scholarType
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
export function mapType(scholarType) {
|
|
50
|
+
if (!scholarType) return 'article-journal'
|
|
51
|
+
return TYPE_TO_CSL[scholarType.toLowerCase()] || 'article-journal'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Map a CSL-JSON type back to a Scholar entry type.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} cslType
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
export function unmapType(cslType) {
|
|
61
|
+
if (!cslType) return 'article'
|
|
62
|
+
return TYPE_FROM_CSL[cslType] || cslType
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Convert a Scholar publication object to a CSL-JSON item.
|
|
67
|
+
*
|
|
68
|
+
* Scholar uses BibTeX-style field names (`authors`, `journal`, `year`).
|
|
69
|
+
* Citestyle expects CSL-JSON (`author`, `container-title`, `issued`).
|
|
70
|
+
*
|
|
71
|
+
* @param {Object} pub - Scholar publication object
|
|
72
|
+
* @returns {Object} CSL-JSON item
|
|
73
|
+
*/
|
|
74
|
+
export function toCslJson(pub) {
|
|
75
|
+
if (!pub) return {}
|
|
76
|
+
|
|
77
|
+
// If it already looks like CSL-JSON (has `issued` or `container-title`),
|
|
78
|
+
// pass through with minimal normalization
|
|
79
|
+
if (pub.issued || pub['container-title']) {
|
|
80
|
+
return { id: pub.id || pub.citationKey || 'unknown', ...pub }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const item = {
|
|
84
|
+
id: pub.id || pub.citationKey || 'unknown',
|
|
85
|
+
type: mapType(pub.type),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Authors — Scholar uses `authors` (array), CSL-JSON uses `author`
|
|
89
|
+
if (pub.authors?.length) {
|
|
90
|
+
item.author = pub.authors.map(normalizeName)
|
|
91
|
+
} else if (pub.author) {
|
|
92
|
+
// Already CSL-JSON shape
|
|
93
|
+
item.author = Array.isArray(pub.author)
|
|
94
|
+
? pub.author.map(normalizeName)
|
|
95
|
+
: undefined
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Editors
|
|
99
|
+
if (pub.editors?.length) {
|
|
100
|
+
item.editor = pub.editors.map(normalizeName)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Title
|
|
104
|
+
if (pub.title) item.title = pub.title
|
|
105
|
+
|
|
106
|
+
// Container title — Scholar uses `journal` or `bookTitle`
|
|
107
|
+
const container = pub.journal || pub.bookTitle || pub.containerTitle
|
|
108
|
+
if (container) item['container-title'] = container
|
|
109
|
+
|
|
110
|
+
// Date — Scholar uses `year` (number), CSL-JSON uses `issued`
|
|
111
|
+
if (pub.year) {
|
|
112
|
+
item.issued = { 'date-parts': [[Number(pub.year)]] }
|
|
113
|
+
} else if (pub.date) {
|
|
114
|
+
// Try to parse an ISO date string
|
|
115
|
+
const parts = String(pub.date).split('-').map(Number)
|
|
116
|
+
item.issued = { 'date-parts': [parts.filter((n) => !isNaN(n))] }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Standard fields
|
|
120
|
+
if (pub.volume) item.volume = String(pub.volume)
|
|
121
|
+
if (pub.issue) item.issue = String(pub.issue)
|
|
122
|
+
if (pub.pages) item.page = pub.pages.replace('–', '-')
|
|
123
|
+
if (pub.page) item.page = pub.page
|
|
124
|
+
if (pub.doi) item.DOI = pub.doi.replace(/^https?:\/\/doi\.org\//, '')
|
|
125
|
+
if (pub.DOI) item.DOI = pub.DOI
|
|
126
|
+
if (pub.url) item.URL = pub.url
|
|
127
|
+
if (pub.URL) item.URL = pub.URL
|
|
128
|
+
if (pub.publisher) item.publisher = pub.publisher
|
|
129
|
+
if (pub.isbn) item.ISBN = pub.isbn
|
|
130
|
+
if (pub.issn) item.ISSN = pub.issn
|
|
131
|
+
if (pub.abstract) item.abstract = pub.abstract
|
|
132
|
+
if (pub.edition) item.edition = pub.edition
|
|
133
|
+
|
|
134
|
+
// Thesis-specific
|
|
135
|
+
if (pub.school || pub.institution) {
|
|
136
|
+
item.publisher = pub.school || pub.institution
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return item
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Convert a CSL-JSON item to a Scholar publication object.
|
|
144
|
+
*
|
|
145
|
+
* Used for backward compatibility when components expect Scholar-shaped
|
|
146
|
+
* objects (`pub.authors`, `pub.journal`, `pub.year`).
|
|
147
|
+
*
|
|
148
|
+
* @param {Object} item - CSL-JSON item
|
|
149
|
+
* @returns {Object} Scholar publication object
|
|
150
|
+
*/
|
|
151
|
+
export function fromCslJson(item) {
|
|
152
|
+
if (!item) return {}
|
|
153
|
+
|
|
154
|
+
const pub = {
|
|
155
|
+
id: item.id,
|
|
156
|
+
type: unmapType(item.type),
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Authors
|
|
160
|
+
if (item.author?.length) {
|
|
161
|
+
pub.authors = item.author.map((a) => ({
|
|
162
|
+
family: a.family || '',
|
|
163
|
+
given: a.given || '',
|
|
164
|
+
...(a.suffix ? { suffix: a.suffix } : {}),
|
|
165
|
+
}))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Editors
|
|
169
|
+
if (item.editor?.length) {
|
|
170
|
+
pub.editors = item.editor.map((a) => ({
|
|
171
|
+
family: a.family || '',
|
|
172
|
+
given: a.given || '',
|
|
173
|
+
}))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Title
|
|
177
|
+
if (item.title) pub.title = item.title
|
|
178
|
+
|
|
179
|
+
// Container title → journal or bookTitle
|
|
180
|
+
if (item['container-title']) {
|
|
181
|
+
if (pub.type === 'article' || pub.type === 'journal') {
|
|
182
|
+
pub.journal = item['container-title']
|
|
183
|
+
} else {
|
|
184
|
+
pub.bookTitle = item['container-title']
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Date
|
|
189
|
+
if (item.issued?.['date-parts']?.[0]) {
|
|
190
|
+
pub.year = item.issued['date-parts'][0][0]
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Standard fields
|
|
194
|
+
if (item.volume) pub.volume = item.volume
|
|
195
|
+
if (item.issue) pub.issue = item.issue
|
|
196
|
+
if (item.page) pub.pages = item.page
|
|
197
|
+
if (item.DOI) pub.doi = item.DOI
|
|
198
|
+
if (item.URL) pub.url = item.URL
|
|
199
|
+
if (item.publisher) pub.publisher = item.publisher
|
|
200
|
+
if (item.ISBN) pub.isbn = item.ISBN
|
|
201
|
+
if (item.ISSN) pub.issn = item.ISSN
|
|
202
|
+
if (item.abstract) pub.abstract = item.abstract
|
|
203
|
+
if (item.edition) pub.edition = item.edition
|
|
204
|
+
|
|
205
|
+
return pub
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Normalize a name object — ensures {family, given} shape.
|
|
210
|
+
* Handles strings, objects with various key patterns.
|
|
211
|
+
*
|
|
212
|
+
* @param {Object|string} name
|
|
213
|
+
* @returns {{ family: string, given: string, suffix?: string }}
|
|
214
|
+
*/
|
|
215
|
+
function normalizeName(name) {
|
|
216
|
+
if (typeof name === 'string') {
|
|
217
|
+
// "Last, First" or "First Last"
|
|
218
|
+
if (name.includes(',')) {
|
|
219
|
+
const [family, given] = name.split(',').map((s) => s.trim())
|
|
220
|
+
return { family, given }
|
|
221
|
+
}
|
|
222
|
+
const parts = name.trim().split(/\s+/)
|
|
223
|
+
return {
|
|
224
|
+
family: parts[parts.length - 1],
|
|
225
|
+
given: parts.slice(0, -1).join(' '),
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
family: name.family || name.lastName || '',
|
|
231
|
+
given: name.given || name.firstName || '',
|
|
232
|
+
...(name.suffix ? { suffix: name.suffix } : {}),
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -1,7 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bibliography Parsers
|
|
3
3
|
*
|
|
4
|
+
* Delegates to @citestyle/bibtex for BibTeX parsing and export.
|
|
5
|
+
* Returns CSL-JSON items directly — no scholar-shaped wrapper.
|
|
6
|
+
*
|
|
4
7
|
* @module @uniweb/scholar/bibliography/parsers
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
import {
|
|
11
|
+
parseBibtex as csParseBibtex,
|
|
12
|
+
exportBibtex as csExportBibtex,
|
|
13
|
+
} from '@citestyle/bibtex'
|
|
14
|
+
import { toCslJson } from '../normalize.js'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse a BibTeX string into an array of CSL-JSON items.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} bibtex - BibTeX source string
|
|
20
|
+
* @returns {Array<Object>} Array of CSL-JSON items
|
|
21
|
+
*/
|
|
22
|
+
export const parseBibtex = csParseBibtex
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Export a single publication (Scholar or CSL-JSON) to BibTeX format.
|
|
26
|
+
*
|
|
27
|
+
* Wraps @citestyle/bibtex's exportBibtex, which expects an array of
|
|
28
|
+
* CSL-JSON items. This accepts a single Scholar-shaped pub for convenience.
|
|
29
|
+
*
|
|
30
|
+
* @param {Object} publication - Publication data (Scholar or CSL-JSON)
|
|
31
|
+
* @returns {string} BibTeX string
|
|
32
|
+
*/
|
|
33
|
+
export function exportBibtex(publication) {
|
|
34
|
+
if (!publication) return ''
|
|
35
|
+
|
|
36
|
+
// If it's a string, it's already BibTeX — pass through
|
|
37
|
+
if (typeof publication === 'string') return publication
|
|
38
|
+
|
|
39
|
+
const item = toCslJson(publication)
|
|
40
|
+
return csExportBibtex([item])
|
|
41
|
+
}
|
|
@@ -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
|
)}
|