Requesting Help: Rendering Markdown inside the PDF
I am having a hard time supporting Markdown within the generated Document by react-pdf/renderer.
What I have so far: I am using react-markdown to parse Markdown and transform it into HTML tags. I then map these tags into their respective PDF components.
const markDownComponents = (extraStyle: any) => {
return {
h1: defaultComponent,
h2: defaultComponent,
h3: defaultComponent,
h4: defaultComponent,
h5: defaultComponent,
p: ({ children }: { children: ReactNode }) => (
<Text style={[styles.p, extraStyle]}>{children}</Text>
),
i: defaultComponent,
b: ({ children }: { children: ReactNode }) => (
<Text style={[styles.b, extraStyle]}>{children}</Text>
),
strong: ({ children }: { children: ReactNode }) => (
<Text style={[styles.strong, extraStyle]}>{children}</Text>
),
a: defaultComponent,
ol: ({ children }: { children: ReactNode }) => {
return renderList(children, 'ol');
},
ul: ({ children }: { children: ReactNode }) => {
return renderList(children, 'ul');
},
li: ({ children }: { children: ReactNode }) => <Text>{'wont get rendered'}</Text>,
};
};
type MDProps = {
children: any;
style?: React.CSSProperties;
};
const MD = ({ children, style }: MDProps) => {
return (
// @ts-ignore
<Markdown components={markDownComponents(style)}>{children}</Markdown>
);
};
Two issues here:
-
I'd actually love for the user to see that some Markdown is not supported. So when he types some Markdown inside an input like # MyHeading, I want exactly that to be displayed to him in the pdf. So with the hashtag and everything. I couldn't quite get this to work (tried tokenizer plugin), so I settled for defaultComponents so far. Any working examples would be highly appreciated.
-
It is unbelievably hard to style list items, depending on whether they are an ordered or unordered List.
It works for lists which are not nested:
function renderList(list, listType) {
let result = [];
let itemCounter = 0;
for (let i = 0; i < list.length; i++) {
if (list[i].props?.node?.tagName === 'li') {
list[i].props.node.children.map((child) => {
if (child.type === 'text' && child.value != '\n') {
if (listType === 'ol') {
result.push(
<Text style={styles.listItem}>{`${++itemCounter}. ${child.value}`}</Text>
);
}
if (listType === 'ul') {
result.push(<Text style={styles.listItem}>{`• ${child.value}`}</Text>);
}
} else if (child.type === 'element') {
result.push(renderList(child.children, child.tagName));
}
});
}
}
return result;
}
But as soon as these lists are nested, the structure of the children changes in a way that I dont quite get. props changes to properties, there are children inside children.
Closest I got for nested lists was this:
function renderList(list, listType) {
console.log('renderList', list, 'listType', listType);
let result = [];
let itemCounter = 0;
for (let i = 0; i < list.length; i++) {
if (list[i].props?.node?.tagName === 'li') {
list[i].props.node.children.map((child) => {
if (child.type === 'text' && child.value != '\n') {
if (listType === 'ol') {
result.push(
<Text style={styles.listItem}>{`${++itemCounter}. ${child.value}`}</Text>
);
}
if (listType === 'ul') {
result.push(<Text style={styles.listItem}>{`• ${child.value}`}</Text>);
}
} else if (child.type === 'element') {
result.push(renderList(child.children, child.tagName));
}
});
} else {
if (list[i].type === 'element') {
list[i].children?.map((child) => {
if (child.tagName === 'p') {
if (listType === 'ol') {
result.push(
<Text style={styles.listItem}>{`${++itemCounter}. ${
child.children[0].value
}`}</Text>
);
}
if (listType === 'ul') {
result.push(<Text style={styles.listItem}>{`• ${child.children[0].value}`}</Text>);
}
}
});
}
}
}
return result;
}
But then I realized that some tags are completly ignored and parts of the list omitted. Any hints on how to tackle this issue are, again, highly appreciated.
Hey, any luck getting these to work? Also, any chance you can share what you have so far? I went a different route by implementing the options in <Markdown options={markdownOptions}>{children}</Markdown> with a custom renderRule(next, node, renderChildren, state) method.
But, if you can share your approach, I'd like to check if that works better.
You will need to parse the markdown somehow and transform that into react-pdf primitives. Rendering markdown is outside of this lib responsibilities
Hi @diegomura. Would you accept a PR on this as a new feature to react pdf?
What I propose is a new primitive called Markdown that accepts a markdown as input and maps them into corresponding primitives.
Currently the best solution I found is https://github.com/danomatic/react-pdf-html which is built on this library.
It doesn't seem to be actively maintained though, has anyone else solved this problem in any other way?