Skip to content

Commit

Permalink
Add global node index to make it easier to render JSX (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcospassos authored Sep 26, 2024
1 parent bab9d70 commit c4cd4a8
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 56 deletions.
18 changes: 8 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,14 @@ const markdown = '**Hello**, [World](https://example.com)';

const jsx = render(markdown, {
text: node => node.content,
bold: node => <b>{node.children}</b>,
italic: node => <i>{node.children}</i>,
strike: node => <s>{node.children}</s>,
code: node => <code>{node.content}</code>,
link: node => <a href={node.href}>{node.children}</a>,
image: node => <img src={node.src} alt={node.alt} />,
paragraph: node => <p>{node.children}</p>,
fragment: node => node.children.map(
(child, index) => <Fragment key={index}>{child}</Fragment>
),
bold: node => <b key={node.index}>{node.children}</b>,
italic: node => <i key={node.index}>{node.children}</i>,
strike: node => <s key={node.index}>{node.children}</s>,
code: node => <code key={node.index}>{node.content}</code>,
link: node => <a key={node.index} href={node.href}>{node.children}</a>,
image: node => <img key={node.index} src={node.src} alt={node.alt} />,
paragraph: node => <p key={node.index}>{node.children}</p>,
fragment: node => node.children.map(child => <Fragment key={child.index}>{child}</Fragment>),
});
```

Expand Down
115 changes: 69 additions & 46 deletions src/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type VisitedMarkdownNodeMap<C> = {
export type VisitedMarkdownNode<C, T extends MarkdownNodeType> = {
[K in MarkdownNodeType]: {
type: K,
index: number,
source: string,
} & VisitedMarkdownNodeMap<C>[K]
}[T];
Expand All @@ -57,59 +58,81 @@ export function render<T>(markdown: string|MarkdownNode, visitor: MarkdownRender
return visit(typeof markdown === 'string' ? parse(markdown) : markdown, visitor);
}

function visit<T>(node: MarkdownNode, visitor: MarkdownRenderer<T>): T {
switch (node.type) {
case 'text':
return visitor.text(node);
function visit<T>(root: MarkdownNode, visitor: MarkdownRenderer<T>): T {
let index = 0;

case 'bold':
return visitor.bold({
type: node.type,
children: visit(node.children, visitor),
source: node.source,
});
function visitNode(node: MarkdownNode): T {
switch (node.type) {
case 'text':
return visitor.text({
...node,
index: index++,
});

case 'italic':
return visitor.italic({
type: node.type,
children: visit(node.children, visitor),
source: node.source,
});
case 'bold':
return visitor.bold({
type: node.type,
children: visitNode(node.children),
source: node.source,
index: index++,
});

case 'strike':
return visitor.strike({
type: node.type,
children: visit(node.children, visitor),
source: node.source,
});
case 'italic':
return visitor.italic({
type: node.type,
children: visitNode(node.children),
index: index++,
source: node.source,
});

case 'code':
return visitor.code(node);
case 'strike':
return visitor.strike({
type: node.type,
children: visitNode(node.children),
index: index++,
source: node.source,
});

case 'image':
return visitor.image(node);
case 'code':
return visitor.code({
...node,
index: index++,
});

case 'link':
return visitor.link({
type: node.type,
href: node.href,
title: node.title,
children: visit(node.children, visitor),
source: node.source,
});
case 'image':
return visitor.image({
...node,
index: index++,
});

case 'paragraph':
return visitor.paragraph({
type: node.type,
children: node.children.map(child => visit(child, visitor)),
source: node.source,
});
case 'link':
return visitor.link({
type: node.type,
href: node.href,
title: node.title,
children: visitNode(node.children),
index: index++,
source: node.source,
});

case 'fragment':
return visitor.fragment({
type: node.type,
children: node.children.map(child => visit(child, visitor)),
source: node.source,
});
case 'paragraph': {
return visitor.paragraph({
type: node.type,
children: node.children.map(child => visitNode(child)),
index: index++,
source: node.source,
});
}

case 'fragment':
return visitor.fragment({
type: node.type,
children: node.children.map(child => visitNode(child)),
index: index++,
source: node.source,
});
}
}

return visitNode(root);
}
80 changes: 80 additions & 0 deletions test/rendering.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,84 @@ describe('A Markdown render function', () => {
it('should parse and render a Markdown string', () => {
expect(render(markdown, new TestRenderer())).toEqual(tree);
});

it('should assign a global index to each node', () => {
const input = '**Bold**\n*Italic*\n***Bold and italic***\n';

const result = render<MarkdownNode>(input, {
fragment: node => node,
text: node => node,
bold: node => node,
italic: node => node,
strike: node => node,
code: node => node,
image: node => node,
link: node => node,
paragraph: node => node,
});

expect(result).toEqual({
index: 10,
type: 'fragment',
source: '**Bold**\n*Italic*\n***Bold and italic***\n',
children: [
{
index: 1,
type: 'bold',
source: '**Bold**',
children: {
index: 0,
type: 'text',
content: 'Bold',
source: 'Bold',
},
},
{
index: 2,
type: 'text',
content: '\n',
source: '\n',
},
{
index: 4,
type: 'italic',
source: '*Italic*',
children: {
index: 3,
type: 'text',
content: 'Italic',
source: 'Italic',
},
},
{
index: 5,
type: 'text',
content: '\n',
source: '\n',
},
{
index: 8,
type: 'bold',
source: '***Bold and italic***',
children: {
index: 7,
type: 'italic',
source: '*Bold and italic*',
children: {
index: 6,
type: 'text',
content: 'Bold and italic',
source: 'Bold and italic',
},
},
},
{
index: 9,
type: 'text',
content: '\n',
source: '\n',
},
],
});
});
});

0 comments on commit c4cd4a8

Please sign in to comment.