Citations and bibliography
Inline citations, a bibliography that follows them, CSL formatting, and a host adapter for lookups, the user’s library and attachments.
| Package | Contents |
|---|---|
@stesura/bibliography | Plugins, commands, the citeproc formatter, bundled CSL styles and locales, validation, BibTeX / RIS / CSL-JSON importers. Framework-free. |
@stesura/bibliography-react | createBibliographyUiExtension: the References-tab group, the sources panel, the citation picker, the source dialog, node views. |
The citation and bibliography nodes, the bibliography doc attr and the adapter contract are
part of core, so a host can replace the plugin and keep the documents.
Setup
import { bibliographyPlugins } from "@stesura/bibliography";
import { createBibliographyUiExtension, withBundledStyles } from "@stesura/bibliography-react";
import type { BibliographyAdapter } from "@stesura/core/types";
// Your library integration (lookup, searchLibrary, enrich, ...); every method is optional.
declare const host: Partial<BibliographyAdapter>;
// One adapter for both: the plugin formats with it, the UI looks up and searches with it.
const adapter = withBundledStyles(host);
const uiExtensions = [createBibliographyUiExtension({ adapter })];
const extraPlugins = () => [...bibliographyPlugins({ adapter })];withBundledStyles fills loadStyle / loadLocale / listStyles with the bundled CSL files
(APA, MLA, Chicago author-date and notes, IEEE, Harvard Cite Them Right, NLM/Vancouver; 17
locales), each fetched as its own chunk. citeproc-js (~1 MB) also loads on first use.
Data model
- Sources live in the document, as CSL-JSON in
doc.attrs.bibliography({ sources, styleId, locale },locale: null= the document language). The adapter is for the user’s library, never for what the document cites. citationis an inline atom:items({ sourceId, locator?, label?, prefix?, suffix?, suppressAuthor?, authorOnly? }[]),mode(parenthetical|narrative), and the cached renderingtextandmissingflag the plugin writes.bibliographyis a block atom, allowed only directly in a section’s content (not in cells, notes or headers):scope(document|section),includeUncited,types, and the cachedentriesthe plugin writes. Exports and read-only views render from that cache.- Citations work in footnotes and endnotes, ordered at their note’s anchor. In headers and footers they are flattened to text, as in Word.
The plugin heals on every change: sources carried by a pasted citation are registered (a clashing id
is renamed and the citation re-pointed), missing is kept current, and rendering runs debounced and
out of history, so undo never steps through a re-render.
Commands
| Command | Does |
|---|---|
insertCitation({ items, mode?, sources? }) | Replaces the selection with a citation. sources carries new records; works in note sub-editors. |
insertCitationAsFootnote(input) | A footnote whose text is the citation (note styles). |
editCitation({ pos?, items?, mode?, sources? }) | Changes the selected citation. |
insertBibliography({ scope?, includeUncited?, types? }) | Inserts a bibliography at the caret. |
setBibliographyStyle({ styleId, locale? }) | Changes the CSL style. |
addSource / addSources / updateSource / removeSource / mergeSources | Edit the sources; merging re-points citations. |
convertBibliographyToStatic(pos?) | Replaces a bibliography with paragraphs in the Bibliography style. |
goToSource(sourceId) | Selects the bibliography listing it. |
flushBibliography(view) resolves once pending formatting is in the document (tests, exports).
Adapter behaviour
Every optional method the adapter lacks hides its affordance.
| Method | Used by | Expected behaviour |
|---|---|---|
loadStyle(id), loadLocale(lang) | formatter | CSL XML. Required; withBundledStyles supplies them. |
lookup(input) | picker | DOI, ISBN, arXiv, PMID, URL or free text to { item, confidence }[]. The picker runs it at once for identifiers (classifyInput), on request for free text. |
searchLibrary(query) | picker | The user’s library. Set libraryRef on results to enable sync. |
subscribeLibrary(refs, cb) / fetchLibraryItems(refs) | panel | Report changed records; the panel offers to apply them. |
enrich(item) | panel, citations | SourceSignals: retractions above all (shown on the citation too), corrections, counts, open access. Never a composite score. |
resolveAttachmentUrl(ref) / uploadAttachment(file) | panel | Attachment blobs. |
A reference implementation lives in the testbed app (apps/testbed/src/lib/bibliography), not in a
package:
- Lookup ladder (server-side): DOI content negotiation at doi.org (Crossref, DataCite, mEDRA
answer CSL-JSON); ISBN through Open Library; arXiv’s API (preferring the published DOI); PubMed /
PMC through NCBI’s citation exporter; a URL through its Highwire / Dublin Core / JSON-LD / Open
Graph metadata, fetched behind a public-address guard; a PDF through the DOI in its metadata or
first pages, then GROBID when
GROBID_URLis set; free text through Crossref’s bibliographic query, then a model as the last rung. Model output is re-checked against Crossref; what it alone filled is markedprovenance.unverifiedand flagged in the source form. - Enrichment, cached a week per DOI: Crossref update notices (retractions, corrections),
Semantic Scholar counts, Unpaywall’s open-access copy (
UNPAYWALL_EMAIL). - Zotero Web API (
ZOTERO_LIBRARY,ZOTERO_API_KEY): search, fetch by key, and change polling on the library version.
DOCX
Citations export as Word CITATION fields in a citation content control, with the cached text as
the field result, the sources as a b:Sources custom XML part (Manage Sources in Word), and the
bibliography as a BIBLIOGRAPHY field in its content control with the cached entries as its result.
A second custom XML part keeps the full CSL-JSON for a lossless round trip. Imports read Word
citations, and Zotero and Mendeley citation fields.