Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⏩ Extend renderers to allow for kind/subtypes #460

Merged
merged 2 commits into from
Aug 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/tidy-candles-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'myst-to-react': patch
'@myst-theme/providers': patch
'@myst-theme/jupyter': patch
'@myst-theme/site': patch
---

Allow for specific renderers
2 changes: 1 addition & 1 deletion packages/jupyter/src/figure.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import classNames from 'classnames';
import { OutputDecoration } from './decoration.js';

export function Figure({ node }: { node: GenericNode }) {
const { container: Container } = DEFAULT_RENDERERS;
const { base: Container } = DEFAULT_RENDERERS['container'];
const isFromJupyer = node.source?.kind === SourceFileKind.Notebook;
const output = node.children?.find((child) => child.type === 'output');
if (isFromJupyer && !!output) {
Expand Down
14 changes: 12 additions & 2 deletions packages/myst-to-react/src/MyST.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { matches } from 'unist-util-select';
import type { NodeRenderersValidated } from '@myst-theme/providers';
import { useNodeRenderers } from '@myst-theme/providers';
import type { GenericNode } from 'myst-common';

Expand All @@ -10,17 +12,25 @@ function DefaultComponent({ node }: { node: GenericNode }) {
);
}

export function selectRenderer(renderers: NodeRenderersValidated, node: GenericNode) {
const componentRenderers = renderers[node.type] ?? renderers['DefaultComponent'];
const SpecificComponent = Object.entries(componentRenderers)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const SpecificComponent = Object.entries(componentRenderers)
const specificComponent = Object.entries(componentRenderers)

.reverse()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why reverse?

.find(([selector]) => selector !== 'base' && matches(selector, node))?.[1];
return SpecificComponent ?? componentRenderers.base ?? DefaultComponent;
}

export function MyST({ ast }: { ast?: GenericNode | GenericNode[] }) {
const renderers = useNodeRenderers();
if (!ast || ast.length === 0) return null;
if (!Array.isArray(ast)) {
const Component = renderers[ast.type] ?? renderers['DefaultComponent'] ?? DefaultComponent;
const Component = selectRenderer(renderers, ast);
return <Component key={ast.key} node={ast} />;
}
return (
<>
{ast?.map((node) => {
const Component = renderers[node.type] ?? DefaultComponent;
const Component = selectRenderer(renderers, node);
return <Component key={node.key} node={node} />;
})}
</>
Expand Down
55 changes: 29 additions & 26 deletions packages/myst-to-react/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { NodeRenderer } from '@myst-theme/providers';
import { mergeRenderers } from '@myst-theme/providers';
import BASIC_RENDERERS from './basic.js';
import ADMONITION_RENDERERS from './admonitions.js';
import DROPDOWN_RENDERERS from './dropdown.js';
Expand Down Expand Up @@ -30,29 +30,32 @@ export { Details } from './dropdown.js';
export { TabSet, TabItem } from './tabs.js';
export { useFetchMdast } from './crossReference.js';

export const DEFAULT_RENDERERS: Record<string, NodeRenderer> = {
...BASIC_RENDERERS,
...UNKNOWN_MYST_RENDERERS,
...IMAGE_RENDERERS,
...LINK_RENDERERS,
...CODE_RENDERERS,
...MATH_RENDERERS,
...CITE_RENDERERS,
...TAB_RENDERERS,
...IFRAME_RENDERERS,
...FOOTNOTE_RENDERERS,
...ADMONITION_RENDERERS,
...REACTIVE_RENDERERS,
...HEADING_RENDERERS,
...CROSS_REFERENCE_RENDERERS,
...DROPDOWN_RENDERERS,
...CARD_RENDERERS,
...GRID_RENDERERS,
...INLINE_EXPRESSION_RENDERERS,
...EXT_RENDERERS,
...PROOF_RENDERERS,
...EXERCISE_RENDERERS,
...ASIDE_RENDERERS,
};
export const DEFAULT_RENDERERS = mergeRenderers(
[
BASIC_RENDERERS,
UNKNOWN_MYST_RENDERERS,
IMAGE_RENDERERS,
LINK_RENDERERS,
CODE_RENDERERS,
MATH_RENDERERS,
CITE_RENDERERS,
TAB_RENDERERS,
IFRAME_RENDERERS,
FOOTNOTE_RENDERERS,
ADMONITION_RENDERERS,
REACTIVE_RENDERERS,
HEADING_RENDERERS,
CROSS_REFERENCE_RENDERERS,
DROPDOWN_RENDERERS,
CARD_RENDERERS,
GRID_RENDERERS,
INLINE_EXPRESSION_RENDERERS,
EXT_RENDERERS,
PROOF_RENDERERS,
EXERCISE_RENDERERS,
ASIDE_RENDERERS,
],
true,
);

export { MyST } from './MyST.js';
export { MyST, selectRenderer } from './MyST.js';
107 changes: 61 additions & 46 deletions packages/myst-to-react/src/links/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from '@heroicons/react/24/outline';
import { useLinkProvider, useSiteManifest, useBaseurl, withBaseurl } from '@myst-theme/providers';
import type { SiteManifest } from 'myst-config';
import type { NodeRenderer } from '@myst-theme/providers';
import type { NodeRenderer, NodeRenderers } from '@myst-theme/providers';
import { HoverPopover, LinkCard } from '../components/index.js';
import { WikiLink } from './wiki.js';
import { RRIDLink } from './rrid.js';
Expand Down Expand Up @@ -55,51 +55,54 @@ function InternalLink({ url, children }: { url: string; children: React.ReactNod
);
}

export const link: NodeRenderer<TransformedLink> = ({ node }) => {
const internal = node.internal ?? false;
const protocol = node.protocol;
export const WikiLinkRenderer: NodeRenderer<TransformedLink> = ({ node }) => {
return (
<WikiLink url={node.url} page={node.data?.page as string} wiki={node.data?.wiki as string}>
<MyST ast={node.children} />
</WikiLink>
);
};

switch (protocol) {
case 'wiki':
return (
<WikiLink url={node.url} page={node.data?.page as string} wiki={node.data?.wiki as string}>
<MyST ast={node.children} />
</WikiLink>
);
case 'github':
return (
<GithubLink
kind={node.data?.kind as any}
url={node.url}
org={node.data?.org as string}
repo={node.data?.repo as string}
raw={node.data?.raw as string}
file={node.data?.file as string}
from={node.data?.from as number | undefined}
to={node.data?.to as number | undefined}
issue_number={node.data?.issue_number as number | undefined}
>
<MyST ast={node.children} />
</GithubLink>
);
case 'rrid':
return <RRIDLink rrid={node.data?.rrid as string} />;
case 'ror':
return <RORLink node={node} ror={node.data?.ror as string} />;
default:
if (internal) {
return (
<InternalLink url={node.url}>
<MyST ast={node.children} />
</InternalLink>
);
}
return (
<a target="_blank" href={node.url} rel="noreferrer">
<MyST ast={node.children} />
</a>
);
export const GithubLinkRenderer: NodeRenderer<TransformedLink> = ({ node }) => {
return (
<GithubLink
kind={node.data?.kind as any}
url={node.url}
org={node.data?.org as string}
repo={node.data?.repo as string}
raw={node.data?.raw as string}
file={node.data?.file as string}
from={node.data?.from as number | undefined}
to={node.data?.to as number | undefined}
issue_number={node.data?.issue_number as number | undefined}
>
<MyST ast={node.children} />
</GithubLink>
);
};

export const RRIDLinkRenderer: NodeRenderer<TransformedLink> = ({ node }) => (
<RRIDLink rrid={node.data?.rrid as string} />
);

export const RORLinkRenderer: NodeRenderer<TransformedLink> = ({ node }) => (
<RORLink node={node} ror={node.data?.ror as string} />
);

export const SimpleLink: NodeRenderer<TransformedLink> = ({ node }) => {
const internal = node.internal ?? false;
if (internal) {
return (
<InternalLink url={node.url}>
<MyST ast={node.children} />
</InternalLink>
);
}
return (
<a target="_blank" href={node.url} rel="noreferrer">
<MyST ast={node.children} />
</a>
);
};

export const linkBlock: NodeRenderer<TransformedLink> = ({ node }) => {
Expand Down Expand Up @@ -134,8 +137,20 @@ export const linkBlock: NodeRenderer<TransformedLink> = ({ node }) => {
);
};

const LINK_RENDERERS = {
link,
const LINK_RENDERERS: NodeRenderers = {
link: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite see why we need this nested structure as opposed to:

{
  link: SimpleLink,
  'link[protocol=github]': GithubLinkRenderer,
  'link[protocol=wiki]': WikiLinkRenderer,
  ...
}

I guess the nesting lets us first narrow down based on type so we are not doing selector checks against all the renderers?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My next question is: would this work the same if we did something like:

{
  link: {
    base: SimpleLink,
    '[protocol=github]': GithubLinkRenderer,
    '[protocol=wiki]': WikiLinkRenderer,
    ...
  }
}

i.e. we don't need to repeat "link" since we are only dealing with link nodes at this point?

base: SimpleLink,
// Then duplicate the renderers for protocols
'link[protocol=github]': GithubLinkRenderer,
'link[protocol=wiki]': WikiLinkRenderer,
'link[protocol=rrid]': RRIDLinkRenderer,
'link[protocol=ror]': RORLinkRenderer,
// Put the kinds last as they will match first in the future
'link[kind=github]': GithubLinkRenderer,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prepares for:

jupyter-book/mystmd#1417

'link[kind=wiki]': WikiLinkRenderer,
'link[kind=rrid]': RRIDLinkRenderer,
'link[kind=ror]': RORLinkRenderer,
},
linkBlock,
};

Expand Down
4 changes: 3 additions & 1 deletion packages/providers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
"license": "MIT",
"scripts": {
"clean": "rimraf dist",
"lint": "eslint src/**/*.ts*",
"test": "vitest run",
"test:watch": "vitest watch",
"lint": "eslint \"src/**/!(*.spec).ts\" -c ./.eslintrc.cjs",
"lint:format": "prettier --check \"src/**/*.{ts,tsx,md}\"",
"dev": "npm-run-all --parallel \"build:* -- --watch\"",
"build:esm": "tsc",
Expand Down
2 changes: 1 addition & 1 deletion packages/providers/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ export * from './ui.js';
export * from './site.js';
export * from './tabs.js';
export * from './xref.js';
export * from './types.js';
export * from './renderers.js';
export * from './project.js';
Loading
Loading