Skip to content

Commit 105e29b

Browse files
committed
πŸ§‘πŸ»β€πŸ”¬ Add citation renderers to myst
1 parent ba11bf5 commit 105e29b

File tree

4 files changed

+120
-18
lines changed

4 files changed

+120
-18
lines changed

β€Žsrc/bibliography.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Token } from '@lumino/coreutils';
22
import { getCitations, CitationRenderer } from 'citation-js-utils';
3-
import { Contents, ContentsManager } from '@jupyterlab/services';
3+
import { Contents } from '@jupyterlab/services';
44
import { ISignal, Signal } from '@lumino/signaling';
55

66
export interface IBibliographyManager {

β€Žsrc/citations.tsx

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,101 @@
1+
import type { CitationRenderer } from 'citation-js-utils';
2+
import { InlineCite } from 'citation-js-utils';
13
import type { Plugin } from 'unified';
2-
import type { Root } from 'myst-spec';
3-
import type { GenericNode } from 'myst-common';
4+
import type { StaticPhrasingContent, Parent, Root } from 'myst-spec';
5+
import type { References } from 'myst-common';
46
import { selectAll } from 'unist-util-select';
7+
import type { Cite, CiteKind, CiteGroup } from 'myst-spec-ext';
58

6-
/**
7-
* Add fake children to the citations
8-
*/
9-
export async function addCiteChildrenTransform(tree: Root): Promise<void> {
10-
const links = selectAll('cite', tree) as GenericNode[];
11-
links.forEach(async cite => {
12-
if (cite.children && cite.children.length > 0) return;
13-
cite.error = true;
14-
cite.children = [{ type: 'text', value: cite.label }];
9+
function pushCite(
10+
references: Pick<References, 'cite'>,
11+
citeRenderer: CitationRenderer,
12+
label: string
13+
) {
14+
if (!references.cite) {
15+
references.cite = { order: [], data: {} };
16+
}
17+
if (!references.cite?.data[label]) {
18+
references.cite.order.push(label);
19+
}
20+
references.cite.data[label] = {
21+
// TODO: this number isn't right? Should be the last time it was seen, not the current size.
22+
number: references.cite.order.length,
23+
doi: citeRenderer[label]?.getDOI(),
24+
html: citeRenderer[label]?.render()
25+
};
26+
}
27+
28+
export function combineCitationRenderers(renderers: CitationRenderer[]) {
29+
const combined: CitationRenderer = {};
30+
renderers.forEach(renderer => {
31+
Object.keys(renderer).forEach(key => {
32+
if (combined[key]) {
33+
console.log(`Duplicate citation with id: ${key}`);
34+
}
35+
combined[key] = renderer[key];
36+
});
1537
});
38+
return combined;
39+
}
40+
41+
function addCitationChildren(
42+
cite: Cite,
43+
renderer: CitationRenderer,
44+
kind: CiteKind = 'parenthetical'
45+
): boolean {
46+
const render = renderer[cite.label as string];
47+
try {
48+
const children = render?.inline(
49+
kind === 'narrative' ? InlineCite.t : InlineCite.p,
50+
{
51+
prefix: cite.prefix,
52+
suffix: cite.suffix
53+
}
54+
) as StaticPhrasingContent[];
55+
if (children) {
56+
cite.children = children;
57+
return true;
58+
}
59+
} catch (error) {
60+
// pass
61+
}
62+
cite.error = true;
63+
return false;
64+
}
65+
66+
function hasChildren(node: Parent) {
67+
return node.children && node.children.length > 0;
1668
}
1769

18-
export const addCiteChildrenPlugin: Plugin<[], Root, Root> = () => tree => {
19-
addCiteChildrenTransform(tree);
70+
type Options = {
71+
renderer: CitationRenderer;
72+
references: Pick<References, 'cite'>;
2073
};
74+
75+
export function transformCitations(mdast: Root, opts: Options) {
76+
// TODO: this can be simplified if typescript doesn't die on the parent
77+
const citeGroups = selectAll('citeGroup', mdast) as CiteGroup[];
78+
citeGroups.forEach(node => {
79+
const kind = node.kind;
80+
node.children?.forEach(cite => {
81+
addCitationChildren(cite, opts.renderer, kind);
82+
});
83+
});
84+
const citations = selectAll('cite', mdast) as Cite[];
85+
citations.forEach(cite => {
86+
const citeLabel = cite.label as string;
87+
// push cites in order of appearance in the document
88+
pushCite(opts.references, opts.renderer, citeLabel);
89+
if (hasChildren(cite)) return;
90+
// These are picked up as they are *not* cite groups
91+
const success = addCitationChildren(cite, opts.renderer);
92+
if (!success) {
93+
console.error(`⚠️ Could not find citation: ${cite.label}`);
94+
}
95+
});
96+
}
97+
98+
export const addCiteChildrenPlugin: Plugin<[Options], Root, Root> =
99+
opts => (tree, vfile) => {
100+
transformCitations(tree, opts);
101+
};

β€Žsrc/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
2020

2121
import { notebookCellExecuted } from './actions';
2222
import { mystMarkdownRendererFactory } from './mime';
23+
import { citationRenderers } from './myst';
2324

2425
/**
2526
* The notebook content factory provider.
@@ -83,12 +84,20 @@ const bibPlugin: JupyterFrontEndPlugin<IBibliographyManager> = {
8384
activate: (app: JupyterFrontEnd) => {
8485
console.log('Using jupyterlab-myst:bibliography');
8586

87+
const bibFile = 'bibliography.bib';
8688
const manager = new BibliographyManager(
8789
app.serviceManager.contents,
88-
'bibliography.bib'
90+
bibFile
8991
);
9092
manager.changed.connect((manager, renderer) => {
9193
console.log(renderer, 'CHANGE');
94+
// TODO: not sure how to pass this state over to the myst renderer. We need some global state?
95+
// If that is the case, we can do that using redux.
96+
if (renderer) {
97+
citationRenderers[bibFile] = renderer;
98+
} else {
99+
delete citationRenderers[bibFile];
100+
}
92101
});
93102
return manager;
94103
}

β€Žsrc/myst.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
3232
import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
3333
import { imageUrlSourceTransform } from './images';
3434
import { internalLinksPlugin } from './links';
35-
import { addCiteChildrenPlugin } from './citations';
35+
import { addCiteChildrenPlugin, combineCitationRenderers } from './citations';
36+
import { CitationRenderer } from 'citation-js-utils';
3637
import { evalRole } from './roles';
3738
import { IUserExpressionMetadata } from './metadata';
3839
import { IMySTMarkdownCell } from './types';
3940
import { Cell, ICellModel } from '@jupyterlab/cells';
4041
import { MySTModel } from './widget';
4142

43+
export const citationRenderers: Record<string, CitationRenderer> = {};
44+
4245
export interface IMySTDocumentState {
4346
references: References;
4447
frontmatter: PageFrontmatter;
@@ -111,6 +114,11 @@ export async function processArticleMDAST(
111114
numbering: frontmatter.numbering,
112115
file
113116
});
117+
118+
const renderer = combineCitationRenderers(
119+
Object.entries(citationRenderers).map(([, v]) => v)
120+
);
121+
114122
unified()
115123
.use(mathPlugin, { macros: frontmatter?.math ?? {} }) // This must happen before enumeration, as it can add labels
116124
.use(glossaryPlugin, { state }) // This should be before the enumerate plugins
@@ -120,7 +128,7 @@ export async function processArticleMDAST(
120128
.use(footnotesPlugin)
121129
.use(resolveReferencesPlugin, { state })
122130
.use(internalLinksPlugin, { resolver })
123-
.use(addCiteChildrenPlugin)
131+
.use(addCiteChildrenPlugin, { references, renderer })
124132
.use(keysPlugin)
125133
.runSync(mdast as any, file);
126134

@@ -177,6 +185,10 @@ export function processNotebookMDAST(
177185
file
178186
});
179187

188+
const renderer = combineCitationRenderers(
189+
Object.entries(citationRenderers).map(([, v]) => v)
190+
);
191+
180192
unified()
181193
.use(mathPlugin, { macros: frontmatter?.math ?? {} }) // This must happen before enumeration, as it can add labels
182194
.use(glossaryPlugin, { state }) // This should be before the enumerate plugins
@@ -186,7 +198,7 @@ export function processNotebookMDAST(
186198
.use(footnotesPlugin)
187199
.use(resolveReferencesPlugin, { state })
188200
.use(internalLinksPlugin, { resolver: resolver })
189-
.use(addCiteChildrenPlugin)
201+
.use(addCiteChildrenPlugin, { references, renderer })
190202
.use(keysPlugin)
191203
.runSync(mdast as any, file);
192204

0 commit comments

Comments
Β (0)