@stesura/references
Framework-free footnotes, endnotes and cross-references: plugins, commands, helpers and the
note-panel store. The UI is in @stesura/references-react; the nodes
are in the core schema. See the references guide for how the feature behaves.
Plugins
referencesPlugins
Every plugin the feature needs, in order. Place them after the core numbering plugins, so cross-references see counters updated in the same transaction.
import { referencesPlugins } from '@stesura/references'
const plugins = [...stesuraPlugins(schema, editorId), ...referencesPlugins()]The cross-reference and note-panel binding plugins are only available through
referencesPlugins(), which keeps their order right.
notesPlugin
Note numbering on its own, for hosts that want neither cross-references nor the note panels.
Takes an optional list of NoteKindConfigs; the default is footnotes and endnotes.
Per note kind: mints missing refs, lists the notes in anchor order (the
anchors’ numbers are noteAnchorStore’s) and keeps the bodies in the doc-level container matched to them,
creating missing bodies and deleting orphans and duplicates. One doc walk and
one appended transaction for all kinds.
| Name | Type | Default |
|---|---|---|
kinds | NoteKindConfig[] |
getFootnotes / getEndnotes
Exported from @stesura/core/plugins.
Footnotes in anchor (= numbering) order, from the merged notes plugin state.
| Name | Type | Default |
|---|---|---|
state | EditorState |
Endnotes in anchor (= numbering) order, from the merged notes plugin state.
| Name | Type | Default |
|---|---|---|
state | EditorState |
Commands
insertCrossReference
Replaces the selection with a cross-reference (or inserts it at the caret)
and node-selects it. The references store derives its label from the target
(docs/partially-executed/derived-state-stores-plan.md, DS-9), except where it is stored:
a text reference’s bookmark text and a page reference’s page.
Returns false if targetId or refTo is empty, if a text or page
reference has no text (an empty label renders as an invisible node), or
if canInsertCrossReference fails.
canInsertCrossReference
Whether a cross-reference could replace the current selection: the
positional half of insertCrossReference’s guard, usable before a
target is picked. The toolbar probes it.
| Name | Type | Default |
|---|---|---|
state | EditorState | |
dispatch | (tr: Transaction) => void | |
view | EditorView |
goToCrossRefTarget
Selects the node a cross-reference points at and scrolls it into view.
Returns false for a missing, unresolvable or missingReference-flagged
target, so the caller’s default handling still runs (REF-44).
| Name | Type | Default |
|---|---|---|
reference | CrossRefLink |
insertFootnote
Inserts a footnote anchor at the end of the selection. The notes plugin
numbers it and creates its body. See createInsertNoteAnchor.
| Name | Type | Default |
|---|---|---|
ref | string | |
onSuccess | (ref: string) => void |
insertEndnote
Inserts an endnote anchor at the end of the selection. The notes plugin
numbers it and creates its body (and the endnotes container if missing).
See createInsertNoteAnchor.
| Name | Type | Default |
|---|---|---|
ref | string | |
onSuccess | (ref: string) => void |
deleteFootnote / deleteEndnote
Deletes a footnote: its body and every anchor referencing it, as one undoable step. Backs the note panel banner’s “Delete footnote”.
| Name | Type | Default |
|---|---|---|
ref | string |
Deletes an endnote: its body and every anchor referencing it, as one undoable step. Backs the note panel banner’s “Delete endnote”.
| Name | Type | Default |
|---|---|---|
ref | string |
navigateFootnote / navigateEndnote
Selects the footnote anchor before (-1) or after (1) footnoteRef in
numbering order, then calls onNavigate with its ref. Doesn’t wrap: false at
either end and for an unknown ref.
| Name | Type | Default |
|---|---|---|
noteRef | string | |
direction | 1 | -1 | |
onNavigate | (ref: string) => void |
Selects the endnote anchor before (-1) or after (1) endnoteRef in
numbering order, then calls onNavigate with its ref. Doesn’t wrap: false at
either end and for an unknown ref.
| Name | Type | Default |
|---|---|---|
noteRef | string | |
direction | 1 | -1 | |
onNavigate | (ref: string) => void |
nextFootnote / previousFootnote / nextEndnote / previousEndnote
Selects the next footnote anchor after the selection, wrapping around. The probe is true whenever the doc has a navigable footnote anchor.
| Name | Type | Default |
|---|---|---|
onSuccess | (ref: string) => void |
Selects the footnote anchor before the selection, wrapping around. The probe is true whenever the doc has a navigable footnote anchor.
| Name | Type | Default |
|---|---|---|
onSuccess | (ref: string) => void |
Selects the next endnote anchor after the selection, wrapping around. The probe is true whenever the doc has a navigable endnote anchor.
| Name | Type | Default |
|---|---|---|
onSuccess | (ref: string) => void |
Selects the endnote anchor before the selection, wrapping around. The probe is true whenever the doc has a navigable endnote anchor.
| Name | Type | Default |
|---|---|---|
onSuccess | (ref: string) => void |
Helpers
isCrossRefSelected
Whether the selection covers exactly one cross_reference.
| Name | Type | Default |
|---|---|---|
state | EditorState |
getCrossRef
The cross_reference node the selection covers exactly, or null. “Edit”
replaces from..to, so a selection running past the node would lose that
text (A1).
| Name | Type | Default |
|---|---|---|
state | EditorState |
findCrossRefTargetWithPos
The node a cross-reference points at, with its position, or undefined.
Mirrors crossReferencesPlugin’s target map so the label, the preview and
the jump agree: numbered types only, header/footer regions and hidden content
skipped (REF-44), last match wins on a duplicate id.
| Name | Type | Default |
|---|---|---|
doc | Node | |
targetId | string |
computeFullContext
The fullContext label of a numbered target: each level of its counter path
rendered with the level’s own segment of the list’s level text (see
ownSegment), joined with separator. Owning segments is what keeps
"%1.%2."-style level texts from repeating the parent chain.
Bullet levels, levels without a config and level texts that don’t name their
own placeholder contribute nothing. The path stops at the first non-numeric
segment (0 is valid).
getAboveBelowText
The localized “above”/“below” label of an aboveBelow cross-reference.
| Name | Type | Default |
|---|---|---|
language | stringBCP 47 tag of the target; unknown languages fall back to English. | |
isAbove | booleanWhether the target precedes the reference. |
findFootnotes / findEndnotes
Returns the node and position of the doc-level footnotes container.
| Name | Type | Default |
|---|---|---|
doc | Node |
Returns the node and position of the doc-level endnotes container, if it exists (it is created lazily by the notes plugin when the first endnote is inserted).
| Name | Type | Default |
|---|---|---|
doc | Node |
Also exported from @stesura/references: findFootnoteAnchor, findFootnoteBodyPos,
findEndnoteBodyPos, the kind-generic findNoteAnchor / findNoteBodyPos /
findNoteContainer, and isHeaderFooterRegion / isSkippedNoteRegion.
Agent issues
The IssueProvider that hands broken cross-references to the agent’s issue tools; pass it to
createIssueTools in @stesura/ai.
referenceIssues
Cross-references whose target is gone (Word’s “Error! Reference source not found.”), for
createIssueTools. No automatic fix: the agent or the user points them at a new target.
Note-panel store
The panel atoms are exported from @stesura/core/stores (the page footer opens notes too); the
registries and kinds from the package root. Which note is open is tracked per editor and per kind
("footnote" | "endnote").
| Export | Purpose |
|---|---|
openNotePanel(editorId, kind, ref, options?) | Opens the panel on ref and closes the other kind’s. { focusOnOpen: false } leaves focus where it is. |
closeNotePanel(editorId, kind) | Closes the panel. |
activeNoteRefAtom(editorId, kind) | Atom holding the open note’s ref, or null. Stable per editor and kind. |
noteFocusRequestAtom(editorId, kind) | { id, focus }, with a new id on every open and close. Consumed by <FocusOnRequest>. |
footnoteEditorRegistry / endnoteEditorRegistry | Mounted note sub-editors. |
footnoteKind / endnoteKind | Sub-editor kind configs, for useSubEditor in a custom panel. |