import React from 'react';
import { Text, TouchableWithoutFeedback, View, Image } from 'react-native';
import type { ReactNode } from 'react';
import openUrl from './util/openUrl';
import hasParents from './util/hasParents';
import applyStyle from './util/applyStyle';
import type { ASTNode, RenderRules, MarkdownStyles } from '../types';
function trimTrailingNewline(content: string): string {
if (content.length > 0 && content.charAt(content.length - 1) === '\n') {
return content.substring(0, content.length - 1);
}
return content;
}
const renderRules: RenderRules = {
unknown: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{node.type}
);
},
textgroup: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{children}
);
},
inline: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return {children};
},
text: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return {node.content};
},
span: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return {children};
},
strong: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{children}
);
},
s: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{children}
);
},
link: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles, ...args: unknown[]) => {
const onLinkPress = args[0] as ((url: string) => boolean | void) | undefined;
const handlePress = () => {
const url = node.attributes.href;
if (onLinkPress) {
onLinkPress(url);
} else {
openUrl(url);
}
};
return (
{children}
);
},
blocklink: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles, ...args: unknown[]) => {
const onLinkPress = args[0] as ((url: string) => boolean | void) | undefined;
const handlePress = () => {
const url = node.attributes.href;
if (onLinkPress) {
onLinkPress(url);
} else {
openUrl(url);
}
};
return (
{children}
);
},
em: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{children}
);
},
heading1: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{applyStyle(children as any, [styles.heading, styles.heading1], 'Text')}
);
},
heading2: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{applyStyle(children as any, [styles.heading, styles.heading2], 'Text')}
);
},
heading3: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => (
{applyStyle(children as any, [styles.heading, styles.heading3], 'Text')}
),
heading4: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => (
{applyStyle(children as any, [styles.heading, styles.heading4], 'Text')}
),
heading5: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => (
{applyStyle(children as any, [styles.heading, styles.heading5], 'Text')}
),
heading6: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => (
{applyStyle(children as any, [styles.heading, styles.heading6], 'Text')}
),
paragraph: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => (
{children}
),
hardbreak: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => (
{'\n'}
),
blockquote: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => (
{children}
),
code_inline: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{node.content}
);
},
code_block: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{trimTrailingNewline(node.content)}
);
},
fence: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{trimTrailingNewline(node.content)}
);
},
pre: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => (
{children}
),
bullet_list: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{children}
);
},
ordered_list: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{children}
);
},
list_item: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
if (hasParents(parent, 'bullet_list')) {
return (
{'\u00B7'}
{children}
);
}
if (hasParents(parent, 'ordered_list')) {
const orderedListParent = parent.find((el) => el.type === 'ordered_list');
const startNumber = orderedListParent?.attributes?.start
? parseInt(orderedListParent.attributes.start, 10)
: 1;
const listItemNumber = startNumber + node.index;
return (
{listItemNumber}
{node.markup}
{children}
);
}
return (
{children}
);
},
table: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => (
{children}
),
thead: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => (
{children}
),
tbody: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => (
{children}
),
th: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{children}
);
},
tr: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{children}
);
},
td: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{children}
);
},
hr: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return ;
},
softbreak: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => (
{'\n'}
),
image: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles, ...args: unknown[]) => {
const allowedImageHandlers = (args[0] as string[] | undefined) ?? [
'data:image/png;base64', 'data:image/gif;base64', 'data:image/jpeg;base64', 'https://', 'http://',
];
const defaultImageHandler = args.length > 1 ? (args[1] as string | null) : 'http://';
let { src } = node.attributes;
const isAllowed = allowedImageHandlers.some((handler) => src.startsWith(handler));
if (!isAllowed) {
if (defaultImageHandler == null) {
return null;
}
src = `${defaultImageHandler}${src}`;
}
return (
);
},
html_block: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{node.content}
);
},
html_inline: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{node.content}
);
},
u: (node: ASTNode, children: ReactNode[], parent: ASTNode[], styles: MarkdownStyles) => {
return (
{children}
);
},
};
export default renderRules;