Skip to content

Repository files navigation

@rexovolt/react-native-markdown-display npm versionKnown Vulnerabilities

It is a React Native markdown renderer done right. This is not a web-view markdown renderer but a renderer that uses native components for all its elements. These components can be overwritten and styled as needed.

NOTE: this fork is for use by Clerotri and is more focused on Revolt/Upryzing's version of markdown than on CommonMark - incompatibilities are certain! It also requires Unistyles.

Install

Yarn

yarn add @rexovolt/react-native-markdown-display

NPM

npm install -S @rexovolt/react-native-markdown-display

Get Started

importReactfrom'react';import{SafeAreaView,ScrollView,StatusBar}from'react-native';importMarkdownfrom'@rexovolt/react-native-markdown-display';constcopy=`# h1 heading :3**This is some bold text!**This is normal text.`;constApp: ()=>React$Node=()=>{return(<><StatusBarbarStyle="dark-content"/><SafeAreaView><ScrollViewcontentInsetAdjustmentBehavior="automatic"style={{height: '100%'}}><Markdown>{copy}</Markdown></ScrollView></SafeAreaView></>);};exportdefaultApp;

You can find this example here

This next example worked with "react-native-markdown-display": "^7.0.0-alpha.2", on React18.1.0, React Native 0.70.5 via the Expo command npx create-expo-app --template with typescript selected.

importReactfrom"react";import{SafeAreaView,ScrollView,StatusBar}from"react-native";import{StyleSheet}from'react-native-unistyles';import{useTheme}from"@react-navigation/native";importMarkdownfrom"react-native-markdown-display";constcopy=`# h1 heading :3**This is some bold text!**This is normal text.`;constMarkdownWrapper: React.FC<any> = ({children}) =>{const{colors} = useTheme();
// @ts-ignore
return <Markdownstyle={styles({ colors })}>{children}</Markdown>;
};constApp: ()=>React.ReactElement=()=>{return(<><StatusBarbarStyle="dark-content"/><SafeAreaView><ScrollViewcontentInsetAdjustmentBehavior="automatic"style={{height: "100%"}}><MarkdownWrapper>{copy}</MarkdownWrapper></ScrollView></SafeAreaView></>);};exportconststyles=(props: any)=>StyleSheet.create({text: {color: props.colors.text,},});exportdefaultApp;

With text input

importReactfrom"react";import{SafeAreaView,ScrollView,StatusBar,TextInput,}from"react-native";import{StyleSheet}from'react-native-unistyles';import{useTheme}from"@react-navigation/native";importMarkdownfrom"react-native-markdown-display";constcopy=`# h1 heading :3**This is some bold text!**This is normal text.`;constMarkdownWrapper: React.FC<any> = ({children}) =>{const{colors} = useTheme();
// @ts-ignore
return <Markdownstyle={styles({ colors })}>{children}</Markdown>;
};constApp: ()=>React.ReactElement=()=>{const[text,onChangeText]=React.useState(copy);const{ colors }=useTheme();return(<><StatusBarbarStyle="dark-content"/><SafeAreaView><ScrollViewcontentInsetAdjustmentBehavior="automatic"style={{height: "100%"}}><TextInputmultilinestyle={{ ...styles({colors}).input,color: colors.text}}onChangeText={onChangeText}value={text}/><MarkdownWrapper>{text}</MarkdownWrapper></ScrollView></SafeAreaView></>);};exportconststyles=(props: any)=>StyleSheet.create({text: {color: props.colors.text,},input: {height: 80,margin: 12,borderWidth: 1,padding: 10,},});exportdefaultApp;

Props and Functions

The <Markdown> object takes the following common props:

PropertyDefaultRequiredDescription
childrenN/AtrueThe markdown string to render, or the pre-processed tree
stylesourcefalseAn object providing styling for the various rules - see style section below for more info
rulessourcefalseAn object of rules that specify how to render each markdown item, see rules section below for more info
onLinkPressimport { Linking } from 'react-native'; and Linking.openURL(url);falseA handler function to change click behaviour - see handling links section below for more info
debugPrintTreefalsefalseWill print the AST tree to the console to help you see what the markdown is being translated to

And some additional, less used options:

PropertyDefaultRequiredDescription
rendererinstanceOf(AstRenderer)falseUsed to specify a custom renderer, you can not use the rules or styles props with a custom renderer.
markdownitinstanceOf(MarkdownIt)falseA custom markdownit instance with your configuration, default is MarkdownIt({typographer: true})
maxTopLevelChildrennullfalseIf defined as a number will only render out first n many top level children, then will try to render out topLevelMaxExceededItem
topLevelMaxExceededItem<Text key="dotdotdot">...</Text>falseWill render when maxTopLevelChildren is hit. Make sure to give it a key!

Syntax Support

Headings

 # h1 heading :3
## h2 heading
### h3 heading
#### h4 heading
##### h5 heading
###### h6 heading
iOSAndroid

Horizontal Rules

 Some text above
___
Some text in the middle
---
Some text below
iOSAndroid

Emphasis

 **This is bold text**
__This is bold text__
*This is italic text*
_This is italic text_
~~Strikethrough~~
iOSAndroid

Blockquotes

 > Blockquotes can also be nested...
>> ...by using additional greater-than signs right next to each other...
> > > ...or with spaces between arrows.
iOSAndroid

Lists

 Unordered
+ Create a list by starting a line with `+`, `-`, or `*`
+ Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
* Ac tristique libero volutpat at
+ Facilisis in pretium nisl aliquet. This is a very long list item that will surely wrap onto the next line.
- Nulla volutpat aliquam velit
+ Very easy!
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit. This is a very long list item that will surely wrap onto the next line.
3. Integer molestie lorem at massa
Start numbering with offset:
57. foo
58. bar
iOSAndroid

Code

 Inline \`code\`
Indented code
// Some comments
line 1 of code
line 2 of code
line 3 of code
Block code "fences"
\`\`\`
Sample text here...
\`\`\`
Syntax highlighting
\`\`\` js
var foo = function (bar) {
return bar++;
};
console.log(foo(5));
\`\`\`
iOSAndroid

Tables

 | Option | Description |
| ------ | ----------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
Right aligned columns
| Option | Description |
| ------:| -----------:|
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
iOSAndroid

Links

 [link text](https://www.google.com)
[link with title](https://www.google.com "title text!")
Autoconverted link https://www.google.com (enable linkify to see)
iOSAndroid

Plugins and Extensions

Plugins for extra syntax support can be added using any markdown-it compatible plugins - see plugins for documentation from markdown-it. An example for integration follows:

Step 1

Identify the new components and integrate the plugin with a rendered component. We can use the debugPrintTree property to see what rules we are rendering:

importReactfrom'react';import{SafeAreaView,ScrollView,StatusBar}from'react-native';importMarkdown,{MarkdownIt}from'@rexovolt/react-native-markdown-display';importblockEmbedPluginfrom'markdown-it-block-embed';constmarkdownItInstance=MarkdownIt({typographer: true}).use(blockEmbedPlugin,{containerClassName: "video-embed"});constcopy=`# Some header@[youtube](lJIrF4YjHfQ)`;constApp: ()=>React$Node=()=>{return(<><StatusBarbarStyle="dark-content"/><SafeAreaView><ScrollViewcontentInsetAdjustmentBehavior="automatic"style={{height: '100%'}}><MarkdowndebugPrintTreemarkdownit={markdownItInstance}>{copy}</Markdown></ScrollView></SafeAreaView></>);};exportdefaultApp;

In the console, we will see the following rendered tree:

body
-heading1
--textgroup
---text
-video

With the following error message:

Warning, unknown render rule encountered: video. 'unknown' render rule used (by default, returns null - nothing rendered) 

Step 2

We need to create the render rules and styles to handle this new 'video' component

importReactfrom'react';import{SafeAreaView,ScrollView,StatusBar}from'react-native';importMarkdown,{MarkdownIt}from'@rexovolt/react-native-markdown-display';importblockEmbedPluginfrom'markdown-it-block-embed';constmarkdownItInstance=MarkdownIt({typographer: true}).use(blockEmbedPlugin,{containerClassName: "video-embed"});constcopy=`# Some header@[youtube](lJIrF4YjHfQ)`;constApp: ()=>React$Node=()=>{return(<><StatusBarbarStyle="dark-content"/><SafeAreaView><ScrollViewcontentInsetAdjustmentBehavior="automatic"style={{height: '100%'}}><MarkdowndebugPrintTreemarkdownit={markdownItInstance}style={{video: {color: 'red',}}}rules={{video: (node,children,parent,styles)=>{// examine the node properties to see what video we need to renderconsole.log(node);// expected output of this is in readme.md below this code snipreturn(<Textkey={node.key}style={styles.video}>
Return a video component instead of this text component!
</Text>);}}}>{copy}</Markdown></ScrollView></SafeAreaView></>);};exportdefaultApp;

And all of the video properties needed to render something meaningful are on the node, like this:

{type: "video", sourceType: "video", sourceInfo: {…}, sourceMeta: null, block: true, …}
attributes: {}
block: true
children: []
content: ""
index: 1
key: "rnmr_1720a98f540_video"
markup: "@[youtube](lJIrF4YjHfQ)"
sourceInfo:
service: YouTubeService
env: PluginEnvironment {md: MarkdownIt, options: {…}, services: {…}}
name: "youtube"
options:
height: 390
width: 640
serviceName: "youtube"
videoID: "lJIrF4YjHfQ"
videoReference: "lJIrF4YjHfQ"
sourceMeta: null
sourceType: "video"
tokenIndex: 5
type: "video"

Other Debugging

You can do some additional debugging of what the markdown instance is spitting out like this:

importMarkdown,{MarkdownIt}from'@rexovolt/react-native-markdown-display';importblockEmbedPluginfrom'markdown-it-block-embed';constmarkdownItInstance=MarkdownIt({typographer: true}).use(blockEmbedPlugin,{containerClassName: "video-embed"});constcopy=`# Some header@[youtube](lJIrF4YjHfQ)`;// this shows you the tree that is used by the react-native-markdown-displayconstastTree=markdownItInstance.parse(copy,{});console.log(astTree);//this contains the html that would be generated - not used by react-native-markdown-display but useful for referenceconsthtml=markdownItInstance.render(copy);console.log(html);

The above code will output something like this:

astTree:
(4) [Token, Token, Token, Token]
0: Token {type: "heading_open", tag: "h1", attrs: null, map: Array(2), nesting: 1, …}
1: Token {type: "inline", tag: "", attrs: null, map: Array(2), nesting: 0, …}
2: Token {type: "heading_close", tag: "h1", attrs: null, map: null, nesting: -1, …}
3: Token {type: "video", tag: "div", attrs: null, map: Array(2), nesting: 0, …}
length: 4
html:
<h1>Some header</h1>
<div class="video-embed block-embed-service-youtube"><iframe type="text/html" src="//www.youtube.com/embed/lJIrF4YjHfQ" frameborder="0" width="640" height="390" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div>

All Markdown for Testing

This is all of the markdown in one place for testing that your applied styles work in all cases

Headings
# h1 heading :3
## h2 heading
### h3 heading
#### h4 heading
##### h5 heading
###### h6 heading
Horizontal Rules
Some text above
___
Some text in the middle
---
Some text below
Emphasis
**This is bold text**
__This is bold text__
*This is italic text*
_This is italic text_
~~Strikethrough~~
Blockquotes
> Blockquotes can also be nested...
>> ...by using additional greater-than signs right next to each other...
> > > ...or with spaces between arrows.
Lists
Unordered
+ Create a list by starting a line with `+`, `-`, or `*`
+ Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
* Ac tristique libero volutpat at
+ Facilisis in pretium nisl aliquet. This is a very long list item that will surely wrap onto the next line.
- Nulla volutpat aliquam velit
+ Very easy!
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit. This is a very long list item that will surely wrap onto the next line.
3. Integer molestie lorem at massa
Start numbering with offset:
57. foo
58. bar
Code
Inline \`code\`
Indented code
// Some comments
line 1 of code
line 2 of code
line 3 of code
Block code "fences"
\`\`\`
Sample text here...
\`\`\`
Syntax highlighting
\`\`\` js
var foo = function (bar) {
return bar++;
};
console.log(foo(5));
\`\`\`
Tables
| Option | Description |
| ------ | ----------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
Right aligned columns
| Option | Description |
| ------:| -----------:|
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
Links
[link text](https://www.google.com)
[link with title](https://www.google.com "title text!")
Autoconverted link https://www.google.com (enable linkify to see)

Rules and Styles

How to style stuff

Text styles are applied in a way that makes it much more convenient to manage changes to global styles while also allowing fine tuning of individual elements.

Think of the implementation like applying styles in CSS. changes to the body effect everything, but can be overwritten further down the style / component tree.

Be careful when styling 'text': the text rule is not applied to all rendered text, most notably list bullet points. If you want to, for instance, color all text, change the body style.

Example

importReactfrom'react';import{SafeAreaView,ScrollView,StatusBar}from'react-native';importMarkdownfrom'@rexovolt/react-native-markdown-display';constcopy=`This is some text which is red because of the body style, which is also really small!\`\`\`//This is a code block wooooconst cool = () => { console.log('????');};\`\`\`and some more small text# This is a h1## this is a h2### this is a h3`;constApp: ()=>React$Node=()=>{return(<><StatusBarbarStyle="dark-content"/><SafeAreaView><ScrollViewcontentInsetAdjustmentBehavior="automatic"style={{height: '100%'}}><Markdownstyle={{body: {color: 'red',fontSize: 10},heading1: {color: 'purple'},code_block: {color: 'black',fontSize: 14}}}>{copy}</Markdown></ScrollView></SafeAreaView></>);};exportdefaultApp;

Styles

Styles are used to determine how certain rules are styled. By default, all components are unstyled.

Example Implementation

importReactfrom'react';import{SafeAreaView,ScrollView,StatusBar}from'react-native';import{StyleSheet}from'react-native-unistyles';importMarkdownfrom'@rexovolt/react-native-markdown-display';conststyles=StyleSheet.create({heading1: {fontSize: 32,backgroundColor: '#000000',color: '#FFFFFF',},heading2: {fontSize: 24,},heading3: {fontSize: 18,},heading4: {fontSize: 16,},heading5: {fontSize: 13,},heading6: {fontSize: 11,}});constcopy=`# h1 heading :3## h2 heading :3### h3 heading :3| Option | Description || ------ | ----------- || data | path to data files to supply the data that will be passed into templates. || engine | engine to be used for processing templates. Handlebars is the default. || ext | extension to be used for dest files. |`;constApp: ()=>React$Node=()=>{return(<><StatusBarbarStyle="dark-content"/><SafeAreaView><ScrollViewcontentInsetAdjustmentBehavior="automatic"style={{height: '100%'}}><Markdownstyle={styles}>{copy}</Markdown></ScrollView></SafeAreaView></>);};exportdefaultApp;

Rules

Rules are used to specify how you want certain elements to be displayed. The existing implementation is here

Example Implementation

importReactfrom'react';import{SafeAreaView,ScrollView,StatusBar,Text}from'react-native';importMarkdownfrom'@rexovolt/react-native-markdown-display';construles={heading1: (node,children,parent,styles)=><Textkey={node.key}style={[styles.heading,styles.heading1]}>>>H1TEXTHERE>>"{children}"</Text>,heading2: (node,children,parent,styles)=><Textkey={node.key}style={[styles.heading,styles.heading2]}>>>H2TEXTHERE>>"{children}"</Text>,heading3: (node,children,parent,styles)=><Textkey={node.key}style={[styles.heading,styles.heading3]}>>>H3TEXTHERE>>"{children}"</Text>,};constcopy=`# h1 heading :3## h2 heading :3### h3 heading :3| Option | Description || ------ | ----------- || data | path to data files to supply the data that will be passed into templates. || engine | engine to be used for processing templates. Handlebars is the default. || ext | extension to be used for dest files. |`;constApp: ()=>React$Node=()=>{return(<><StatusBarbarStyle="dark-content"/><SafeAreaView><ScrollViewcontentInsetAdjustmentBehavior="automatic"style={{height: '100%'}}><Markdownrules={rules}>{copy}</Markdown></ScrollView></SafeAreaView></>);};exportdefaultApp;

All rules and their associated styles:

Render RuleStyle(s)
bodybody
heading1heading1
heading2heading2
heading3heading3
heading4heading4
heading5heading5
heading6heading6
hrhr
strongstrong
emem
ss
blockquoteblockquote
bullet_listbullet_list
ordered_listordered_list
list_itemlist_item - This is a special case that contains a set of pseudo classes that don't align to the render rule: ordered_list_icon, ordered_list_content, bullet_list_icon, bullet_list_content
code_inlinecode_inline
code_blockcode_block
fencefence
tabletable
theadthead
tbodytbody
thth
trtr
tdtd
linklink
texttext
textgrouptextgroup
paragraphparagraph
hardbreakhardbreak
softbreaksoftbreak
prepre
inlineinline
spanspan

Handling Links

Links, by default, will be handled with the import { Linking } from 'react-native'; import and Linking.openURL(url); call.

It is possible to overwrite this behaviour in one of two ways:

onLinkPress Callback

importReactfrom'react';import{SafeAreaView,ScrollView,StatusBar}from'react-native';importMarkdownfrom'@rexovolt/react-native-markdown-display';constcopy=`[This is a link!](https://github.com/iamacup/react-native-markdown-display/)`;constonLinkPress=(url)=>{if(url){// some custom logicreturnfalse;}// return true to open with `Linking.openURL`// return false to handle it yourselfreturntrue}constApp: ()=>React$Node=()=>{return(<><StatusBarbarStyle="dark-content"/><SafeAreaView><ScrollViewcontentInsetAdjustmentBehavior="automatic"style={{height: '100%'}}><MarkdownonLinkPress={onLinkPress}>{copy}</Markdown></ScrollView></SafeAreaView></>);};exportdefaultApp;

Using a Custom Rule

You will need to overwrite link. The original defenitions can be found here

Something like this with yourCustomHandlerFunctionOrLogicHere:

importReactfrom'react';import{SafeAreaView,ScrollView,StatusBar,Text}from'react-native';importMarkdownfrom'@rexovolt/react-native-markdown-display';constcopy=`[This is a link!](https://github.com/iamacup/react-native-markdown-display/)`;construles={link: (node,children,parent,styles)=>{return(<Textkey={node.key}style={styles.link}onPress={()=>yourCustomHandlerFunctionOrLogicHere(node.attributes.href)}>{children}</Text>);},};constApp: ()=>React$Node=()=>{return(<><StatusBarbarStyle="dark-content"/><SafeAreaView><ScrollViewcontentInsetAdjustmentBehavior="automatic"style={{height: '100%'}}><Markdownrules={rules}>{copy}</Markdown></ScrollView></SafeAreaView></>);};exportdefaultApp;

Disabling Specific Types of Markdown

You can disable any type of markdown you want, which is very useful in a mobile environment, by passing the markdownit property like below. Note that for convenience we also export the MarkdownIt instance so you don't have to include it as a project dependency directly just to remove some types of markdown.

This example will stop images and links.

importReactfrom'react';import{SafeAreaView,ScrollView,StatusBar,Text}from'react-native';importMarkdown,{MarkdownIt}from'@rexovolt/react-native-markdown-display';constcopy=`# This heading will show with formatting[but this link will just](be displayed as this text)`;constApp: ()=>React$Node=()=>{return(<><StatusBarbarStyle="dark-content"/><SafeAreaView><ScrollViewcontentInsetAdjustmentBehavior="automatic"style={{height: '100%'}}><Markdownmarkdownit={MarkdownIt({typographer: true}).disable(['link','image'])}>{copy}</Markdown></ScrollView></SafeAreaView></>);};exportdefaultApp;

A full list of things you can turn off is here

Pre Processing

It is possible to need to pre-process the data outside of this library (related discussion here). As a result, you can pass an AST tree directly as the children like this:

importReactfrom'react';import{SafeAreaView,ScrollView,StatusBar,Text}from'react-native';importMarkdown,{MarkdownIt,tokensToAST,stringToTokens}from'@rexovolt/react-native-markdown-display';constmarkdownItInstance=MarkdownIt({typographer: true});constcopy=`# Hello this is a titleThis is some text with **BOLD!**`;constast=tokensToAST(stringToTokens(copy,markdownItInstance))constApp: ()=>React$Node=()=>{return(<><StatusBarbarStyle="dark-content"/><SafeAreaView><ScrollViewcontentInsetAdjustmentBehavior="automatic"style={{height: '100%'}}><Markdown>{ast}</Markdown></ScrollView></SafeAreaView></>);};exportdefaultApp;

About

React Native markdown renderer - fork used for Clerotri

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages