├── .npmignore
├── doc
└── images
│ ├── ios-1.png
│ ├── ios-2.png
│ ├── ios-3.png
│ ├── ios-4.png
│ ├── ios-5.png
│ ├── ios-6.png
│ ├── ios-7.png
│ ├── ios-8.png
│ ├── ios-9.png
│ ├── ios-10.png
│ ├── android-1.png
│ ├── android-10.png
│ ├── android-2.png
│ ├── android-3.png
│ ├── android-4.png
│ ├── android-5.png
│ ├── android-6.png
│ ├── android-7.png
│ ├── android-8.png
│ ├── android-9.png
│ └── style-example.png
├── .gitignore
├── .prettierrc.js
├── src
├── lib
│ ├── util
│ │ ├── getUniqueID.js
│ │ ├── Token.js
│ │ ├── hasParents.js
│ │ ├── stringToTokens.js
│ │ ├── openUrl.js
│ │ ├── splitTextNonTextNodes.js
│ │ ├── removeTextStyleProps.js
│ │ ├── renderInlineAsText.js
│ │ ├── flattenInlineTokens.js
│ │ ├── convertAdditionalStyles.js
│ │ ├── groupTextTokens.js
│ │ ├── getTokenTypeByToken.js
│ │ ├── omitListItemParagraph.js
│ │ ├── cleanupTokens.js
│ │ └── tokensToAST.js
│ ├── data
│ │ └── textStyleProps.js
│ ├── parser.js
│ ├── styles.js
│ ├── AstRenderer.js
│ └── renderRules.js
├── index.d.ts
└── index.js
├── .eslintrc.js
├── LICENSE
├── package.json
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | /example
2 | /.idea
3 | /bin
4 | /docs
5 | /doc
6 |
--------------------------------------------------------------------------------
/doc/images/ios-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/ios-1.png
--------------------------------------------------------------------------------
/doc/images/ios-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/ios-2.png
--------------------------------------------------------------------------------
/doc/images/ios-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/ios-3.png
--------------------------------------------------------------------------------
/doc/images/ios-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/ios-4.png
--------------------------------------------------------------------------------
/doc/images/ios-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/ios-5.png
--------------------------------------------------------------------------------
/doc/images/ios-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/ios-6.png
--------------------------------------------------------------------------------
/doc/images/ios-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/ios-7.png
--------------------------------------------------------------------------------
/doc/images/ios-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/ios-8.png
--------------------------------------------------------------------------------
/doc/images/ios-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/ios-9.png
--------------------------------------------------------------------------------
/doc/images/ios-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/ios-10.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.idea
3 | package-lock.json
4 | /.DS_Store
5 | yarn-error.log
6 | yarn.lock
7 | .eslintcache
8 |
--------------------------------------------------------------------------------
/doc/images/android-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/android-1.png
--------------------------------------------------------------------------------
/doc/images/android-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/android-10.png
--------------------------------------------------------------------------------
/doc/images/android-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/android-2.png
--------------------------------------------------------------------------------
/doc/images/android-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/android-3.png
--------------------------------------------------------------------------------
/doc/images/android-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/android-4.png
--------------------------------------------------------------------------------
/doc/images/android-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/android-5.png
--------------------------------------------------------------------------------
/doc/images/android-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/android-6.png
--------------------------------------------------------------------------------
/doc/images/android-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/android-7.png
--------------------------------------------------------------------------------
/doc/images/android-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/android-8.png
--------------------------------------------------------------------------------
/doc/images/android-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/android-9.png
--------------------------------------------------------------------------------
/doc/images/style-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonasmerlin/react-native-markdown-display/HEAD/doc/images/style-example.png
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bracketSpacing: false,
3 | jsxBracketSameLine: true,
4 | singleQuote: true,
5 | trailingComma: 'all',
6 | };
7 |
--------------------------------------------------------------------------------
/src/lib/util/getUniqueID.js:
--------------------------------------------------------------------------------
1 | let uuid = new Date().getTime();
2 |
3 | export default function getUniqueID() {
4 | uuid++;
5 | return `rnmr_${uuid.toString(16)}`;
6 | }
7 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: '@react-native-community',
4 | settings: {
5 | react: {
6 | version: require('./package.json').peerDependencies.react,
7 | },
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/src/lib/util/Token.js:
--------------------------------------------------------------------------------
1 | export default class Token {
2 | constructor(type, nesting = 0, children = null, block = false) {
3 | this.type = type;
4 | this.nesting = nesting;
5 | this.children = children;
6 | this.block = block;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/lib/util/hasParents.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @param {Array} parents
4 | * @param {string} type
5 | * @return {boolean}
6 | */
7 | export default function hasParents(parents, type) {
8 | return parents.findIndex((el) => el.type === type) > -1;
9 | }
10 |
--------------------------------------------------------------------------------
/src/lib/util/stringToTokens.js:
--------------------------------------------------------------------------------
1 | export function stringToTokens(source, markdownIt) {
2 | let result = [];
3 | try {
4 | result = markdownIt.parse(source, {});
5 | } catch (err) {
6 | console.warn(err);
7 | }
8 |
9 | return result;
10 | }
11 |
--------------------------------------------------------------------------------
/src/lib/util/openUrl.js:
--------------------------------------------------------------------------------
1 | import {Linking} from 'react-native';
2 |
3 | export default function openUrl(url, customCallback) {
4 | if (customCallback) {
5 | const result = customCallback(url);
6 | if (url && result && typeof result === 'boolean') {
7 | Linking.openURL(url);
8 | }
9 | } else if (url) {
10 | Linking.openURL(url);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/lib/util/splitTextNonTextNodes.js:
--------------------------------------------------------------------------------
1 | export default function splitTextNonTextNodes(children) {
2 | return children.reduce(
3 | (acc, curr) => {
4 | if (curr.type.displayName === 'Text') {
5 | acc.textNodes.push(curr);
6 | } else {
7 | acc.nonTextNodes.push(curr);
8 | }
9 |
10 | return acc;
11 | },
12 | {textNodes: [], nonTextNodes: []},
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/src/lib/util/removeTextStyleProps.js:
--------------------------------------------------------------------------------
1 | import textStyleProps from '../data/textStyleProps';
2 |
3 | export default function removeTextStyleProps(style) {
4 | const intersection = textStyleProps.filter((value) =>
5 | Object.keys(style).includes(value),
6 | );
7 |
8 | const obj = {...style};
9 |
10 | intersection.forEach((value) => {
11 | delete obj[value];
12 | });
13 |
14 | return obj;
15 | }
16 |
--------------------------------------------------------------------------------
/src/lib/util/renderInlineAsText.js:
--------------------------------------------------------------------------------
1 | export default function renderInlineAsText(tokens) {
2 | var result = '';
3 |
4 | for (var i = 0, len = tokens.length; i < len; i++) {
5 | if (tokens[i].type === 'text') {
6 | result += tokens[i].content;
7 | } else if (tokens[i].type === 'image') {
8 | result += renderInlineAsText(tokens[i].children);
9 | }
10 | }
11 |
12 | return result;
13 | }
14 |
--------------------------------------------------------------------------------
/src/lib/util/flattenInlineTokens.js:
--------------------------------------------------------------------------------
1 | export default function flattenTokens(tokens) {
2 | return tokens.reduce((acc, curr) => {
3 | if (curr.type === 'inline' && curr.children && curr.children.length > 0) {
4 | const children = flattenTokens(curr.children);
5 | while (children.length) {
6 | acc.push(children.shift());
7 | }
8 | } else {
9 | acc.push(curr);
10 | }
11 |
12 | return acc;
13 | }, []);
14 | }
15 |
--------------------------------------------------------------------------------
/src/lib/data/textStyleProps.js:
--------------------------------------------------------------------------------
1 | export default [
2 | 'textShadowOffset',
3 | 'color',
4 | 'fontSize',
5 | 'fontStyle',
6 | 'fontWeight',
7 | 'lineHeight',
8 | 'textAlign',
9 | 'textDecorationLine',
10 | 'textShadowColor',
11 | 'fontFamily',
12 | 'textShadowRadius',
13 | 'includeFontPadding',
14 | 'textAlignVertical',
15 | 'fontVariant',
16 | 'letterSpacing',
17 | 'textDecorationColor',
18 | 'textDecorationStyle',
19 | 'textTransform',
20 | 'writingDirection',
21 | ];
22 |
--------------------------------------------------------------------------------
/src/lib/util/convertAdditionalStyles.js:
--------------------------------------------------------------------------------
1 | import cssToReactNative from 'css-to-react-native';
2 |
3 | export default function convertAdditionalStyles(style) {
4 | const rules = style.split(';');
5 |
6 | const tuples = rules
7 | .map((rule) => {
8 | let [key, value] = rule.split(':');
9 |
10 | if (key && value) {
11 | key = key.trim();
12 | value = value.trim();
13 | return [key, value];
14 | } else {
15 | return null;
16 | }
17 | })
18 | .filter((x) => {
19 | return x != null;
20 | });
21 |
22 | const conv = cssToReactNative(tuples);
23 |
24 | return conv;
25 | }
26 |
--------------------------------------------------------------------------------
/src/lib/util/groupTextTokens.js:
--------------------------------------------------------------------------------
1 | import Token from './Token';
2 |
3 | export default function groupTextTokens(tokens) {
4 | const result = [];
5 |
6 | let hasGroup = false;
7 |
8 | tokens.forEach((token, index) => {
9 | if (!token.block && !hasGroup) {
10 | hasGroup = true;
11 | result.push(new Token('textgroup', 1));
12 | result.push(token);
13 | } else if (!token.block && hasGroup) {
14 | result.push(token);
15 | } else if (token.block && hasGroup) {
16 | hasGroup = false;
17 | result.push(new Token('textgroup', -1));
18 | result.push(token);
19 | } else {
20 | result.push(token);
21 | }
22 | });
23 |
24 | return result;
25 | }
26 |
--------------------------------------------------------------------------------
/src/lib/parser.js:
--------------------------------------------------------------------------------
1 | import tokensToAST from './util/tokensToAST';
2 | import {stringToTokens} from './util/stringToTokens';
3 | import {cleanupTokens} from './util/cleanupTokens';
4 | import groupTextTokens from './util/groupTextTokens';
5 | import omitListItemParagraph from './util/omitListItemParagraph';
6 |
7 | /**
8 | *
9 | * @param {string} source
10 | * @param {function} [renderer]
11 | * @param {AstRenderer} [markdownIt]
12 | * @return {View}
13 | */
14 | export default function parser(source, renderer, markdownIt) {
15 | if (Array.isArray(source)) {
16 | return renderer(source);
17 | }
18 |
19 | let tokens = stringToTokens(source, markdownIt);
20 | tokens = cleanupTokens(tokens);
21 | tokens = groupTextTokens(tokens);
22 | tokens = omitListItemParagraph(tokens);
23 |
24 | const astTree = tokensToAST(tokens);
25 |
26 | return renderer(astTree);
27 | }
28 |
--------------------------------------------------------------------------------
/src/lib/util/getTokenTypeByToken.js:
--------------------------------------------------------------------------------
1 | const regSelectOpenClose = /_open|_close/g;
2 |
3 | /**
4 | *
5 | * @example {
6 | "type": "heading_open",
7 | "tag": "h1",
8 | "attrs": null,
9 | "map": [
10 | 1,
11 | 2
12 | ],
13 | "nesting": 1,
14 | "level": 0,
15 | "children": null,
16 | "content": "",
17 | "markup": "#",
18 | "info": "",
19 | "meta": null,
20 | "block": true,
21 | "hidden": false
22 | }
23 | * @param token
24 | * @return {String}
25 | */
26 | export default function getTokenTypeByToken(token) {
27 | let cleanedType = 'unknown';
28 |
29 | if (token.type) {
30 | cleanedType = token.type.replace(regSelectOpenClose, '');
31 | }
32 |
33 | switch (cleanedType) {
34 | case 'heading': {
35 | cleanedType = `${cleanedType}${token.tag.substr(1)}`;
36 | break;
37 | }
38 | default: {
39 | break;
40 | }
41 | }
42 |
43 | return cleanedType;
44 | }
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 - 2019 Mient-jan Stelling and Tom Pickard
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/lib/util/omitListItemParagraph.js:
--------------------------------------------------------------------------------
1 | export default function omitListItemParagraph(tokens) {
2 | // used to ensure that we remove the correct ending paragraph token
3 | let depth = null;
4 | return tokens.filter((token, index) => {
5 | // update depth if we've already removed a starting paragraph token
6 | if (depth !== null) {
7 | depth = depth + token.nesting;
8 | }
9 |
10 | // check for a list_item token followed by paragraph token (to remove)
11 | if (token.type === 'list_item' && token.nesting === 1 && depth === null) {
12 | const next = index + 1 in tokens ? tokens[index + 1] : null;
13 | if (next && next.type === 'paragraph' && next.nesting === 1) {
14 | depth = 0;
15 | return true;
16 | }
17 | } else if (token.type === 'paragraph') {
18 | if (token.nesting === 1 && depth === 1) {
19 | // remove the paragraph token immediately after the list_item token
20 | return false;
21 | } else if (token.nesting === -1 && depth === 0) {
22 | // remove the ending paragraph token; reset depth
23 | depth = null;
24 | return false;
25 | }
26 | }
27 | return true;
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/src/lib/util/cleanupTokens.js:
--------------------------------------------------------------------------------
1 | import getTokenTypeByToken from './getTokenTypeByToken';
2 | import flattenInlineTokens from './flattenInlineTokens';
3 | import renderInlineAsText from './renderInlineAsText';
4 |
5 | export function cleanupTokens(tokens) {
6 | tokens = flattenInlineTokens(tokens);
7 | tokens.forEach((token) => {
8 | token.type = getTokenTypeByToken(token);
9 |
10 | // set image and hardbreak to block elements
11 | if (token.type === 'image' || token.type === 'hardbreak') {
12 | token.block = true;
13 | }
14 |
15 | // Set img alt text
16 | if (token.type === 'image') {
17 | token.attrs[token.attrIndex('alt')][1] = renderInlineAsText(
18 | token.children,
19 | );
20 | }
21 | });
22 |
23 | /**
24 | * changing a link token to a blocklink to fix issue where link tokens with
25 | * nested non text tokens breaks component
26 | */
27 | const stack = [];
28 | tokens = tokens.reduce((acc, token, index) => {
29 | if (token.type === 'link' && token.nesting === 1) {
30 | stack.push(token);
31 | } else if (
32 | stack.length > 0 &&
33 | token.type === 'link' &&
34 | token.nesting === -1
35 | ) {
36 | if (stack.some((stackToken) => stackToken.block)) {
37 | stack[0].type = 'blocklink';
38 | stack[0].block = true;
39 | token.type = 'blocklink';
40 | token.block = true;
41 | }
42 |
43 | stack.push(token);
44 |
45 | while (stack.length) {
46 | acc.push(stack.shift());
47 | }
48 | } else if (stack.length > 0) {
49 | stack.push(token);
50 | } else {
51 | acc.push(token);
52 | }
53 |
54 | return acc;
55 | }, []);
56 |
57 | return tokens;
58 | }
59 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jonasmerlin/react-native-markdown-display",
3 | "version": "1.0.2",
4 | "description": "Markdown renderer for react-native, with CommonMark spec support + adds syntax extensions & sugar (URL autolinking, typographer), originally created by Mient-jan Stelling as react-native-markdown-renderer",
5 | "main": "src/index.js",
6 | "types": "src/index.d.ts",
7 | "scripts": {
8 | "lint": "eslint --fix --cache ./src"
9 | },
10 | "pre-commit": [
11 | "lint"
12 | ],
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/jonasmerlin/react-native-markdown-display.git"
16 | },
17 | "keywords": [
18 | "react",
19 | "react-native",
20 | "native",
21 | "markdown",
22 | "commonmark",
23 | "markdown-it"
24 | ],
25 | "author": "Mient-jan Stelling and Tom Pickard + others from the community",
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.com/jonasmerlin/react-native-markdown-display/issues"
29 | },
30 | "homepage": "https://github.com/jonasmerlin/react-native-markdown-display/",
31 | "dependencies": {
32 | "css-to-react-native": "^3.0.0",
33 | "markdown-it": "^13.0.1",
34 | "prop-types": "^15.8.1",
35 | "react-native-fit-image": "^1.5.5"
36 | },
37 | "peerDependencies": {
38 | "react": "^16.2.0 || ^18.0.0",
39 | "react-native": ">=0.50.4"
40 | },
41 | "devDependencies": {
42 | "@types/markdown-it": "^12.2.3",
43 | "@types/react-native": ">=0.67.7",
44 | "@babel/core": "^7.17.10",
45 | "@babel/runtime": "^7.17.9",
46 | "@react-native-community/eslint-config": "^3.0.2",
47 | "@typescript-eslint/parser": "^5.23.0",
48 | "eslint": "^8.15.0",
49 | "json-schema": "^0.4.0",
50 | "pre-commit": "1.2.2",
51 | "typescript": "^4.6.4"
52 | },
53 | "directories": {
54 | "doc": "doc"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/lib/util/tokensToAST.js:
--------------------------------------------------------------------------------
1 | import getUniqueID from './getUniqueID';
2 | import getTokenTypeByToken from './getTokenTypeByToken';
3 |
4 | /**
5 | *
6 | * @param {{type: string, tag:string, content: string, children: *, attrs: Array, meta, info, block: boolean}} token
7 | * @param {number} tokenIndex
8 | * @return {{type: string, content, tokenIndex: *, index: number, attributes: {}, children: *}}
9 | */
10 | function createNode(token, tokenIndex) {
11 | const type = getTokenTypeByToken(token);
12 | const content = token.content;
13 |
14 | let attributes = {};
15 |
16 | if (token.attrs) {
17 | attributes = token.attrs.reduce((prev, curr) => {
18 | const [name, value] = curr;
19 | return {...prev, [name]: value};
20 | }, {});
21 | }
22 |
23 | return {
24 | type,
25 | sourceType: token.type,
26 | sourceInfo: token.info,
27 | sourceMeta: token.meta,
28 | block: token.block,
29 | markup: token.markup,
30 | key: getUniqueID() + '_' + type,
31 | content,
32 | tokenIndex,
33 | index: 0,
34 | attributes,
35 | children: tokensToAST(token.children),
36 | };
37 | }
38 |
39 | /**
40 | *
41 | * @param {Array<{type: string, tag:string, content: string, children: *, attrs: Array}>}tokens
42 | * @return {Array}
43 | */
44 | export default function tokensToAST(tokens) {
45 | let stack = [];
46 | let children = [];
47 |
48 | if (!tokens || tokens.length === 0) {
49 | return [];
50 | }
51 |
52 | for (let i = 0; i < tokens.length; i++) {
53 | const token = tokens[i];
54 | const astNode = createNode(token, i);
55 |
56 | if (
57 | !(
58 | astNode.type === 'text' &&
59 | astNode.children.length === 0 &&
60 | astNode.content === ''
61 | )
62 | ) {
63 | astNode.index = children.length;
64 |
65 | if (token.nesting === 1) {
66 | children.push(astNode);
67 | stack.push(children);
68 | children = astNode.children;
69 | } else if (token.nesting === -1) {
70 | children = stack.pop();
71 | } else if (token.nesting === 0) {
72 | children.push(astNode);
73 | }
74 | }
75 | }
76 |
77 | return children;
78 | }
79 |
--------------------------------------------------------------------------------
/src/index.d.ts:
--------------------------------------------------------------------------------
1 | // tslint:disable:max-classes-per-file
2 | import MarkdownIt from 'markdown-it';
3 | import Token from 'markdown-it/lib/token';
4 | import {ComponentType, ReactNode} from 'react';
5 | import {StyleSheet, View} from 'react-native';
6 |
7 | export function getUniqueID(): string;
8 | export function openUrl(url: string): void;
9 |
10 | export function hasParents(parents: any[], type: string): boolean;
11 |
12 | export type RenderFunction = (
13 | node: ASTNode,
14 | children: ReactNode[],
15 | parentNodes: ASTNode[],
16 | styles: any,
17 | styleObj?: any,
18 | // must have this so that we can have fixed overrides with more arguments
19 | ...args: any
20 | ) => ReactNode;
21 |
22 | export type RenderLinkFunction = (
23 | node: ASTNode,
24 | children: ReactNode[],
25 | parentNodes: ASTNode[],
26 | styles: any,
27 | onLinkPress?: (url: string) => boolean,
28 | ) => ReactNode;
29 |
30 | export type RenderImageFunction = (
31 | node: ASTNode,
32 | children: ReactNode[],
33 | parentNodes: ASTNode[],
34 | styles: any,
35 | allowedImageHandlers: string[],
36 | defaultImageHandler: string,
37 | ) => ReactNode;
38 |
39 | export interface RenderRules {
40 | [name: string]: RenderFunction | undefined;
41 | link?: RenderLinkFunction;
42 | blocklink?: RenderLinkFunction;
43 | image?: RenderImageFunction;
44 | }
45 |
46 | export const renderRules: RenderRules;
47 |
48 | export interface MarkdownParser {
49 | parse: (value: string, options: any) => Token[];
50 | }
51 |
52 | export interface ASTNode {
53 | type: string;
54 | sourceType: string; // original source token name
55 | key: string;
56 | content: string;
57 | markup: string;
58 | tokenIndex: number;
59 | index: number;
60 | attributes: Record
90 |
91 | ```
92 | # h1 Heading 8-)
93 | ## h2 Heading
94 | ### h3 Heading
95 | #### h4 Heading
96 | ##### h5 Heading
97 | ###### h6 Heading
98 | ```
99 |
100 | | iOS | Android
101 | | --- | ---
102 | |
110 |
111 | ```
112 | Some text above
113 | ___
114 |
115 | Some text in the middle
116 |
117 | ---
118 |
119 | Some text below
120 | ```
121 |
122 | | iOS | Android
123 | | --- | ---
124 | |
134 |
135 | ```
136 | **This is bold text**
137 |
138 | __This is bold text__
139 |
140 | *This is italic text*
141 |
142 | _This is italic text_
143 |
144 | ~~Strikethrough~~
145 | ```
146 |
147 | | iOS | Android
148 | | --- | ---
149 | |
157 |
158 | ```
159 | > Blockquotes can also be nested...
160 | >> ...by using additional greater-than signs right next to each other...
161 | > > > ...or with spaces between arrows.
162 | ```
163 |
164 | | iOS | Android
165 | | --- | ---
166 | |
174 |
175 | ```
176 | Unordered
177 |
178 | + Create a list by starting a line with `+`, `-`, or `*`
179 | + Sub-lists are made by indenting 2 spaces:
180 | - Marker character change forces new list start:
181 | * Ac tristique libero volutpat at
182 | + Facilisis in pretium nisl aliquet. This is a very long list item that will surely wrap onto the next line.
183 | - Nulla volutpat aliquam velit
184 | + Very easy!
185 |
186 | Ordered
187 |
188 | 1. Lorem ipsum dolor sit amet
189 | 2. Consectetur adipiscing elit. This is a very long list item that will surely wrap onto the next line.
190 | 3. Integer molestie lorem at massa
191 |
192 | Start numbering with offset:
193 |
194 | 57. foo
195 | 58. bar
196 | ```
197 |
198 | | iOS | Android
199 | | --- | ---
200 | |
208 |
209 | ```
210 | Inline \`code\`
211 |
212 | Indented code
213 |
214 | // Some comments
215 | line 1 of code
216 | line 2 of code
217 | line 3 of code
218 |
219 |
220 | Block code "fences"
221 |
222 | \`\`\`
223 | Sample text here...
224 | \`\`\`
225 |
226 | Syntax highlighting
227 |
228 | \`\`\` js
229 | var foo = function (bar) {
230 | return bar++;
231 | };
232 |
233 | console.log(foo(5));
234 | \`\`\`
235 | ```
236 |
237 | | iOS | Android
238 | | --- | ---
239 | |
247 |
248 | ```
249 | | Option | Description |
250 | | ------ | ----------- |
251 | | data | path to data files to supply the data that will be passed into templates. |
252 | | engine | engine to be used for processing templates. Handlebars is the default. |
253 | | ext | extension to be used for dest files. |
254 |
255 | Right aligned columns
256 |
257 | | Option | Description |
258 | | ------:| -----------:|
259 | | data | path to data files to supply the data that will be passed into templates. |
260 | | engine | engine to be used for processing templates. Handlebars is the default. |
261 | | ext | extension to be used for dest files. |
262 | ```
263 |
264 | | iOS | Android
265 | | --- | ---
266 | |
273 |
274 | ```
275 | [link text](https://www.google.com)
276 |
277 | [link with title](https://www.google.com "title text!")
278 |
279 | Autoconverted link https://www.google.com (enable linkify to see)
280 | ```
281 |
282 | | iOS | Android
283 | | --- | ---
284 | |
291 |
292 | ```
293 | 
294 | 
295 |
296 | Like links, Images also have a footnote style syntax
297 |
298 | ![Alt text][id]
299 |
300 | With a reference later in the document defining the URL location:
301 |
302 | [id]: https://octodex.github.com/images/dojocat.jpg "The Dojocat"
303 | ```
304 |
305 | | iOS | Android
306 | | --- | ---
307 | |
315 |
316 | ```
317 | Enable typographer option to see result.
318 |
319 | (c) (C) (r) (R) (tm) (TM) (p) (P) +-
320 |
321 | test.. test... test..... test?..... test!....
322 |
323 | !!!!!! ???? ,, -- ---
324 |
325 | "Smartypants, double quotes" and 'single quotes'
326 | ```
327 |
328 | | iOS | Android
329 | | --- | ---
330 | |
338 |
339 | Plugins for **extra** syntax support can be added using any markdown-it compatible plugins - [see plugins](https://www.npmjs.com/browse/keyword/markdown-it-plugin) for documentation from markdown-it. An example for integration follows:
340 |
341 |
342 | #### Step 1
343 |
344 | 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:
345 |
346 |
347 | ```jsx
348 | import React from 'react';
349 | import { SafeAreaView, ScrollView, StatusBar } from 'react-native';
350 |
351 | import Markdown, { MarkdownIt } from 'react-native-markdown-display';
352 | import blockEmbedPlugin from 'markdown-it-block-embed';
353 |
354 | const markdownItInstance =
355 | MarkdownIt({typographer: true})
356 | .use(blockEmbedPlugin, {
357 | containerClassName: "video-embed"
358 | });
359 |
360 | const copy = `
361 | # Some header
362 |
363 | @[youtube](lJIrF4YjHfQ)
364 | `;
365 |
366 | const App: () => React$Node = () => {
367 | return (
368 | <>
369 | Headings
89 |
|
103 |
104 | Horizontal Rules
109 |
|
125 |
126 |
127 | Emphasis
133 |
|
150 |
151 | Blockquotes
156 |
|
167 |
168 | Lists
173 |
|
201 |
202 | Code
207 |
|
240 |
241 | Tables
246 |
|
267 |
268 | Links
272 |
|
285 |
286 | Images
290 |
|
308 |
309 | Typographic Replacements
314 |
|
331 |
332 | Plugins and Extensions
337 | Some header
549 |
550 | ```
551 |
552 |
553 |
559 | 560 | This is all of the markdown in one place for testing that your applied styles work in all cases 561 | 562 | ``` 563 | Headings 564 | 565 | # h1 Heading 8-) 566 | ## h2 Heading 567 | ### h3 Heading 568 | #### h4 Heading 569 | ##### h5 Heading 570 | ###### h6 Heading 571 | 572 | 573 | Horizontal Rules 574 | 575 | Some text above 576 | ___ 577 | 578 | Some text in the middle 579 | 580 | --- 581 | 582 | Some text below 583 | 584 | 585 | Emphasis 586 | 587 | **This is bold text** 588 | 589 | __This is bold text__ 590 | 591 | *This is italic text* 592 | 593 | _This is italic text_ 594 | 595 | ~~Strikethrough~~ 596 | 597 | 598 | Blockquotes 599 | 600 | > Blockquotes can also be nested... 601 | >> ...by using additional greater-than signs right next to each other... 602 | > > > ...or with spaces between arrows. 603 | 604 | 605 | Lists 606 | 607 | Unordered 608 | 609 | + Create a list by starting a line with `+`, `-`, or `*` 610 | + Sub-lists are made by indenting 2 spaces: 611 | - Marker character change forces new list start: 612 | * Ac tristique libero volutpat at 613 | + Facilisis in pretium nisl aliquet. This is a very long list item that will surely wrap onto the next line. 614 | - Nulla volutpat aliquam velit 615 | + Very easy! 616 | 617 | Ordered 618 | 619 | 1. Lorem ipsum dolor sit amet 620 | 2. Consectetur adipiscing elit. This is a very long list item that will surely wrap onto the next line. 621 | 3. Integer molestie lorem at massa 622 | 623 | Start numbering with offset: 624 | 625 | 57. foo 626 | 58. bar 627 | 628 | 629 | Code 630 | 631 | Inline \`code\` 632 | 633 | Indented code 634 | 635 | // Some comments 636 | line 1 of code 637 | line 2 of code 638 | line 3 of code 639 | 640 | 641 | Block code "fences" 642 | 643 | \`\`\` 644 | Sample text here... 645 | \`\`\` 646 | 647 | Syntax highlighting 648 | 649 | \`\`\` js 650 | var foo = function (bar) { 651 | return bar++; 652 | }; 653 | 654 | console.log(foo(5)); 655 | \`\`\` 656 | 657 | 658 | Tables 659 | 660 | | Option | Description | 661 | | ------ | ----------- | 662 | | data | path to data files to supply the data that will be passed into templates. | 663 | | engine | engine to be used for processing templates. Handlebars is the default. | 664 | | ext | extension to be used for dest files. | 665 | 666 | Right aligned columns 667 | 668 | | Option | Description | 669 | | ------:| -----------:| 670 | | data | path to data files to supply the data that will be passed into templates. | 671 | | engine | engine to be used for processing templates. Handlebars is the default. | 672 | | ext | extension to be used for dest files. | 673 | 674 | 675 | Links 676 | 677 | [link text](https://www.google.com) 678 | 679 | [link with title](https://www.google.com "title text!") 680 | 681 | Autoconverted link https://www.google.com (enable linkify to see) 682 | 683 | 684 | Images 685 | 686 |  687 |  688 | 689 | Like links, Images also have a footnote style syntax 690 | 691 | ![Alt text][id] 692 | 693 | With a reference later in the document defining the URL location: 694 | 695 | [id]: https://octodex.github.com/images/dojocat.jpg "The Dojocat" 696 | 697 | 698 | Typographic Replacements 699 | 700 | Enable typographer option to see result. 701 | 702 | (c) (C) (r) (R) (tm) (TM) (p) (P) +- 703 | 704 | test.. test... test..... test?..... test!.... 705 | 706 | !!!!!! ???? ,, -- --- 707 | 708 | "Smartypants, double quotes" and 'single quotes' 709 | 710 | ``` 711 | 712 |
713 |
729 |
730 |
731 |
732 | ```jsx
733 | import React from 'react';
734 | import { SafeAreaView, ScrollView, StatusBar } from 'react-native';
735 |
736 | import Markdown from 'react-native-markdown-display';
737 |
738 | const copy = `
739 | This is some text which is red because of the body style, which is also really small!
740 |
741 | \`\`\`
742 | //This is a code block woooo
743 |
744 | const cool = () => {
745 | console.log('????');
746 | };
747 | \`\`\`
748 |
749 | and some more small text
750 |
751 | # This is a h1
752 | ## this is a h2
753 | ### this is a h3
754 | `;
755 |
756 | const App: () => React$Node = () => {
757 | return (
758 | <>
759 |
794 |
795 | ```jsx
796 | import React from 'react';
797 | import { SafeAreaView, ScrollView, StatusBar, StyleSheet } from 'react-native';
798 |
799 | import Markdown from 'react-native-markdown-display';
800 |
801 | const styles = StyleSheet.create({
802 | heading1: {
803 | fontSize: 32,
804 | backgroundColor: '#000000',
805 | color: '#FFFFFF',
806 | },
807 | heading2: {
808 | fontSize: 24,
809 | },
810 | heading3: {
811 | fontSize: 18,
812 | },
813 | heading4: {
814 | fontSize: 16,
815 | },
816 | heading5: {
817 | fontSize: 13,
818 | },
819 | heading6: {
820 | fontSize: 11,
821 | }
822 | });
823 |
824 | const copy = `
825 | # h1 Heading 8-)
826 | ## h2 Heading 8-)
827 | ### h3 Heading 8-)
828 |
829 | | Option | Description |
830 | | ------ | ----------- |
831 | | data | path to data files to supply the data that will be passed into templates. |
832 | | engine | engine to be used for processing templates. Handlebars is the default. |
833 | | ext | extension to be used for dest files. |
834 | `;
835 |
836 | const App: () => React$Node = () => {
837 | return (
838 | <>
839 |
868 |
869 | ```jsx
870 | import React from 'react';
871 | import { SafeAreaView, ScrollView, StatusBar, Text } from 'react-native';
872 |
873 | import Markdown from 'react-native-markdown-display';
874 |
875 | const rules = {
876 | heading1: (node, children, parent, styles) =>
877 |
977 |
978 | ```jsx
979 | import React from 'react';
980 | import { SafeAreaView, ScrollView, StatusBar } from 'react-native';
981 |
982 | import Markdown from 'react-native-markdown-display';
983 |
984 | const copy = `[This is a link!](https://github.com/iamacup/react-native-markdown-display/)`;
985 |
986 | const onLinkPress = (url) => {
987 | if (url) {
988 | // some custom logic
989 | return false;
990 | }
991 |
992 | // return true to open with `Linking.openURL
993 | // return false to handle it yourself
994 | return true
995 | }
996 |
997 | const App: () => React$Node = () => {
998 | return (
999 | <>
1000 |
1025 |
1026 | You will need to overwrite one or both of `link` and `blocklink`, the original defenitions can be [found here](https://github.com/iamacup/react-native-markdown-display/blob/master/src/lib/renderRules.js)
1027 |
1028 | Something like this with `yourCustomHandlerFunctionOrLogicHere`:
1029 |
1030 | ```jsx
1031 | import React from 'react';
1032 | import { SafeAreaView, ScrollView, StatusBar, Text } from 'react-native';
1033 |
1034 | import Markdown from 'react-native-markdown-display';
1035 |
1036 | const copy = `[This is a link!](https://github.com/iamacup/react-native-markdown-display/)`;
1037 |
1038 | const rules = {
1039 | link: (node, children, parent, styles) => {
1040 | return (
1041 |