-
Notifications
You must be signed in to change notification settings - Fork 12
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
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'; | ||
|
||
|
@@ -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) | ||
.reverse() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why |
||
.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} />; | ||
})} | ||
</> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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'; | ||
|
@@ -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 }) => { | ||
|
@@ -134,8 +137,20 @@ export const linkBlock: NodeRenderer<TransformedLink> = ({ node }) => { | |
); | ||
}; | ||
|
||
const LINK_RENDERERS = { | ||
link, | ||
const LINK_RENDERERS: NodeRenderers = { | ||
link: { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prepares for: |
||
'link[kind=wiki]': WikiLinkRenderer, | ||
'link[kind=rrid]': RRIDLinkRenderer, | ||
'link[kind=ror]': RORLinkRenderer, | ||
}, | ||
linkBlock, | ||
}; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.