Skip to content

Commit

Permalink
πŸ§‘πŸ»β€πŸ”¬ Add citation renderers to myst
Browse files Browse the repository at this point in the history
  • Loading branch information
rowanc1 committed Jun 23, 2023
1 parent ba11bf5 commit 105e29b
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 18 deletions.
2 changes: 1 addition & 1 deletion src/bibliography.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Token } from '@lumino/coreutils';
import { getCitations, CitationRenderer } from 'citation-js-utils';
import { Contents, ContentsManager } from '@jupyterlab/services';
import { Contents } from '@jupyterlab/services';
import { ISignal, Signal } from '@lumino/signaling';

export interface IBibliographyManager {
Expand Down
107 changes: 94 additions & 13 deletions src/citations.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,101 @@
import type { CitationRenderer } from 'citation-js-utils';
import { InlineCite } from 'citation-js-utils';
import type { Plugin } from 'unified';
import type { Root } from 'myst-spec';
import type { GenericNode } from 'myst-common';
import type { StaticPhrasingContent, Parent, Root } from 'myst-spec';
import type { References } from 'myst-common';
import { selectAll } from 'unist-util-select';
import type { Cite, CiteKind, CiteGroup } from 'myst-spec-ext';

/**
* Add fake children to the citations
*/
export async function addCiteChildrenTransform(tree: Root): Promise<void> {
const links = selectAll('cite', tree) as GenericNode[];
links.forEach(async cite => {
if (cite.children && cite.children.length > 0) return;
cite.error = true;
cite.children = [{ type: 'text', value: cite.label }];
function pushCite(
references: Pick<References, 'cite'>,
citeRenderer: CitationRenderer,
label: string
) {
if (!references.cite) {
references.cite = { order: [], data: {} };
}
if (!references.cite?.data[label]) {
references.cite.order.push(label);
}
references.cite.data[label] = {
// TODO: this number isn't right? Should be the last time it was seen, not the current size.
number: references.cite.order.length,
doi: citeRenderer[label]?.getDOI(),
html: citeRenderer[label]?.render()
};
}

export function combineCitationRenderers(renderers: CitationRenderer[]) {
const combined: CitationRenderer = {};
renderers.forEach(renderer => {
Object.keys(renderer).forEach(key => {
if (combined[key]) {
console.log(`Duplicate citation with id: ${key}`);
}
combined[key] = renderer[key];
});
});
return combined;
}

function addCitationChildren(
cite: Cite,
renderer: CitationRenderer,
kind: CiteKind = 'parenthetical'
): boolean {
const render = renderer[cite.label as string];
try {
const children = render?.inline(
kind === 'narrative' ? InlineCite.t : InlineCite.p,
{
prefix: cite.prefix,
suffix: cite.suffix
}
) as StaticPhrasingContent[];
if (children) {
cite.children = children;
return true;
}
} catch (error) {
// pass
}
cite.error = true;
return false;
}

function hasChildren(node: Parent) {
return node.children && node.children.length > 0;
}

export const addCiteChildrenPlugin: Plugin<[], Root, Root> = () => tree => {
addCiteChildrenTransform(tree);
type Options = {
renderer: CitationRenderer;
references: Pick<References, 'cite'>;
};

export function transformCitations(mdast: Root, opts: Options) {
// TODO: this can be simplified if typescript doesn't die on the parent
const citeGroups = selectAll('citeGroup', mdast) as CiteGroup[];
citeGroups.forEach(node => {
const kind = node.kind;
node.children?.forEach(cite => {
addCitationChildren(cite, opts.renderer, kind);
});
});
const citations = selectAll('cite', mdast) as Cite[];
citations.forEach(cite => {
const citeLabel = cite.label as string;
// push cites in order of appearance in the document
pushCite(opts.references, opts.renderer, citeLabel);
if (hasChildren(cite)) return;
// These are picked up as they are *not* cite groups
const success = addCitationChildren(cite, opts.renderer);
if (!success) {
console.error(`⚠️ Could not find citation: ${cite.label}`);
}
});
}

export const addCiteChildrenPlugin: Plugin<[Options], Root, Root> =
opts => (tree, vfile) => {
transformCitations(tree, opts);
};
11 changes: 10 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';

import { notebookCellExecuted } from './actions';
import { mystMarkdownRendererFactory } from './mime';
import { citationRenderers } from './myst';

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

const bibFile = 'bibliography.bib';
const manager = new BibliographyManager(
app.serviceManager.contents,
'bibliography.bib'
bibFile
);
manager.changed.connect((manager, renderer) => {
console.log(renderer, 'CHANGE');
// TODO: not sure how to pass this state over to the myst renderer. We need some global state?
// If that is the case, we can do that using redux.
if (renderer) {
citationRenderers[bibFile] = renderer;
} else {
delete citationRenderers[bibFile];
}
});
return manager;
}
Expand Down
18 changes: 15 additions & 3 deletions src/myst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,16 @@ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
import { imageUrlSourceTransform } from './images';
import { internalLinksPlugin } from './links';
import { addCiteChildrenPlugin } from './citations';
import { addCiteChildrenPlugin, combineCitationRenderers } from './citations';
import { CitationRenderer } from 'citation-js-utils';
import { evalRole } from './roles';
import { IUserExpressionMetadata } from './metadata';
import { IMySTMarkdownCell } from './types';
import { Cell, ICellModel } from '@jupyterlab/cells';
import { MySTModel } from './widget';

export const citationRenderers: Record<string, CitationRenderer> = {};

export interface IMySTDocumentState {
references: References;
frontmatter: PageFrontmatter;
Expand Down Expand Up @@ -111,6 +114,11 @@ export async function processArticleMDAST(
numbering: frontmatter.numbering,
file
});

const renderer = combineCitationRenderers(
Object.entries(citationRenderers).map(([, v]) => v)
);

unified()
.use(mathPlugin, { macros: frontmatter?.math ?? {} }) // This must happen before enumeration, as it can add labels
.use(glossaryPlugin, { state }) // This should be before the enumerate plugins
Expand All @@ -120,7 +128,7 @@ export async function processArticleMDAST(
.use(footnotesPlugin)
.use(resolveReferencesPlugin, { state })
.use(internalLinksPlugin, { resolver })
.use(addCiteChildrenPlugin)
.use(addCiteChildrenPlugin, { references, renderer })
.use(keysPlugin)
.runSync(mdast as any, file);

Expand Down Expand Up @@ -177,6 +185,10 @@ export function processNotebookMDAST(
file
});

const renderer = combineCitationRenderers(
Object.entries(citationRenderers).map(([, v]) => v)
);

unified()
.use(mathPlugin, { macros: frontmatter?.math ?? {} }) // This must happen before enumeration, as it can add labels
.use(glossaryPlugin, { state }) // This should be before the enumerate plugins
Expand All @@ -186,7 +198,7 @@ export function processNotebookMDAST(
.use(footnotesPlugin)
.use(resolveReferencesPlugin, { state })
.use(internalLinksPlugin, { resolver: resolver })
.use(addCiteChildrenPlugin)
.use(addCiteChildrenPlugin, { references, renderer })
.use(keysPlugin)
.runSync(mdast as any, file);

Expand Down

0 comments on commit 105e29b

Please sign in to comment.