@novastera-oss/react-native-markdown-display 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +1206 -0
- package/dist/cjs/index.js +164 -0
- package/dist/cjs/lib/AstRenderer.js +129 -0
- package/dist/cjs/lib/data/textStyleProps.js +24 -0
- package/dist/cjs/lib/parser.js +22 -0
- package/dist/cjs/lib/renderRules.js +148 -0
- package/dist/cjs/lib/styles.js +183 -0
- package/dist/cjs/lib/util/Token.js +11 -0
- package/dist/cjs/lib/util/cleanupTokens.js +61 -0
- package/dist/cjs/lib/util/convertAdditionalStyles.js +39 -0
- package/dist/cjs/lib/util/flattenInlineTokens.js +17 -0
- package/dist/cjs/lib/util/getTokenTypeByToken.js +21 -0
- package/dist/cjs/lib/util/getUniqueID.js +8 -0
- package/dist/cjs/lib/util/groupTextTokens.js +30 -0
- package/dist/cjs/lib/util/hasParents.js +6 -0
- package/dist/cjs/lib/util/omitListItemParagraph.js +33 -0
- package/dist/cjs/lib/util/openUrl.js +15 -0
- package/dist/cjs/lib/util/removeTextStyleProps.js +15 -0
- package/dist/cjs/lib/util/renderInlineAsText.js +16 -0
- package/dist/cjs/lib/util/splitTextNonTextNodes.js +21 -0
- package/dist/cjs/lib/util/stringToTokens.js +13 -0
- package/dist/cjs/lib/util/tokensToAST.js +63 -0
- package/dist/cjs/types.js +2 -0
- package/dist/esm/index.js +112 -0
- package/dist/esm/lib/AstRenderer.js +123 -0
- package/dist/esm/lib/data/textStyleProps.js +22 -0
- package/dist/esm/lib/parser.js +16 -0
- package/dist/esm/lib/renderRules.js +143 -0
- package/dist/esm/lib/styles.js +180 -0
- package/dist/esm/lib/util/Token.js +8 -0
- package/dist/esm/lib/util/cleanupTokens.js +55 -0
- package/dist/esm/lib/util/convertAdditionalStyles.js +36 -0
- package/dist/esm/lib/util/flattenInlineTokens.js +14 -0
- package/dist/esm/lib/util/getTokenTypeByToken.js +18 -0
- package/dist/esm/lib/util/getUniqueID.js +5 -0
- package/dist/esm/lib/util/groupTextTokens.js +24 -0
- package/dist/esm/lib/util/hasParents.js +3 -0
- package/dist/esm/lib/util/omitListItemParagraph.js +30 -0
- package/dist/esm/lib/util/openUrl.js +12 -0
- package/dist/esm/lib/util/removeTextStyleProps.js +9 -0
- package/dist/esm/lib/util/renderInlineAsText.js +13 -0
- package/dist/esm/lib/util/splitTextNonTextNodes.js +18 -0
- package/dist/esm/lib/util/stringToTokens.js +10 -0
- package/dist/esm/lib/util/tokensToAST.js +57 -0
- package/dist/esm/types.js +1 -0
- package/dist/types/index.d.ts +23 -0
- package/dist/types/lib/AstRenderer.d.ts +16 -0
- package/dist/types/lib/data/textStyleProps.d.ts +2 -0
- package/dist/types/lib/parser.d.ts +3 -0
- package/dist/types/lib/renderRules.d.ts +3 -0
- package/dist/types/lib/styles.d.ts +141 -0
- package/dist/types/lib/util/Token.d.ts +8 -0
- package/dist/types/lib/util/cleanupTokens.d.ts +2 -0
- package/dist/types/lib/util/convertAdditionalStyles.d.ts +1 -0
- package/dist/types/lib/util/flattenInlineTokens.d.ts +2 -0
- package/dist/types/lib/util/getTokenTypeByToken.d.ts +2 -0
- package/dist/types/lib/util/getUniqueID.d.ts +1 -0
- package/dist/types/lib/util/groupTextTokens.d.ts +2 -0
- package/dist/types/lib/util/hasParents.d.ts +2 -0
- package/dist/types/lib/util/omitListItemParagraph.d.ts +2 -0
- package/dist/types/lib/util/openUrl.d.ts +1 -0
- package/dist/types/lib/util/removeTextStyleProps.d.ts +1 -0
- package/dist/types/lib/util/renderInlineAsText.d.ts +2 -0
- package/dist/types/lib/util/splitTextNonTextNodes.d.ts +7 -0
- package/dist/types/lib/util/stringToTokens.d.ts +2 -0
- package/dist/types/lib/util/tokensToAST.d.ts +2 -0
- package/dist/types/types.d.ts +59 -0
- package/package.json +69 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class Token {
|
|
4
|
+
constructor(type, nesting = 0, children = null, block = false) {
|
|
5
|
+
this.type = type;
|
|
6
|
+
this.nesting = nesting;
|
|
7
|
+
this.children = children;
|
|
8
|
+
this.block = block;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.default = Token;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.cleanupTokens = cleanupTokens;
|
|
7
|
+
const getTokenTypeByToken_1 = __importDefault(require("./getTokenTypeByToken"));
|
|
8
|
+
const flattenInlineTokens_1 = __importDefault(require("./flattenInlineTokens"));
|
|
9
|
+
const renderInlineAsText_1 = __importDefault(require("./renderInlineAsText"));
|
|
10
|
+
function cleanupTokens(tokens) {
|
|
11
|
+
tokens = (0, flattenInlineTokens_1.default)(tokens);
|
|
12
|
+
tokens.forEach((token) => {
|
|
13
|
+
var _a;
|
|
14
|
+
token.type = (0, getTokenTypeByToken_1.default)(token);
|
|
15
|
+
// set image and hardbreak to block elements
|
|
16
|
+
if (token.type === 'image' || token.type === 'hardbreak') {
|
|
17
|
+
token.block = true;
|
|
18
|
+
}
|
|
19
|
+
// Set img alt text
|
|
20
|
+
if (token.type === 'image' && token.attrs) {
|
|
21
|
+
const altIndex = typeof token.attrIndex === 'function'
|
|
22
|
+
? token.attrIndex('alt')
|
|
23
|
+
: token.attrs.findIndex((attr) => attr[0] === 'alt');
|
|
24
|
+
if (altIndex >= 0) {
|
|
25
|
+
token.attrs[altIndex][1] = (0, renderInlineAsText_1.default)((_a = token.children) !== null && _a !== void 0 ? _a : []);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* changing a link token to a blocklink to fix issue where link tokens with
|
|
31
|
+
* nested non text tokens breaks component
|
|
32
|
+
*/
|
|
33
|
+
const stack = [];
|
|
34
|
+
tokens = tokens.reduce((acc, token) => {
|
|
35
|
+
if (token.type === 'link' && token.nesting === 1) {
|
|
36
|
+
stack.push(token);
|
|
37
|
+
}
|
|
38
|
+
else if (stack.length > 0 &&
|
|
39
|
+
token.type === 'link' &&
|
|
40
|
+
token.nesting === -1) {
|
|
41
|
+
if (stack.some((stackToken) => stackToken.block)) {
|
|
42
|
+
stack[0].type = 'blocklink';
|
|
43
|
+
stack[0].block = true;
|
|
44
|
+
token.type = 'blocklink';
|
|
45
|
+
token.block = true;
|
|
46
|
+
}
|
|
47
|
+
stack.push(token);
|
|
48
|
+
while (stack.length) {
|
|
49
|
+
acc.push(stack.shift());
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (stack.length > 0) {
|
|
53
|
+
stack.push(token);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
acc.push(token);
|
|
57
|
+
}
|
|
58
|
+
return acc;
|
|
59
|
+
}, []);
|
|
60
|
+
return tokens;
|
|
61
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = convertAdditionalStyles;
|
|
4
|
+
function toCamelCase(input) {
|
|
5
|
+
return input.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
6
|
+
}
|
|
7
|
+
function parseValue(value) {
|
|
8
|
+
const trimmed = value.trim();
|
|
9
|
+
const pxMatch = trimmed.match(/^(-?\d+(\.\d+)?)px$/);
|
|
10
|
+
if (pxMatch) {
|
|
11
|
+
return Number(pxMatch[1]);
|
|
12
|
+
}
|
|
13
|
+
const numMatch = trimmed.match(/^-?\d+(\.\d+)?$/);
|
|
14
|
+
if (numMatch) {
|
|
15
|
+
return Number(trimmed);
|
|
16
|
+
}
|
|
17
|
+
return trimmed;
|
|
18
|
+
}
|
|
19
|
+
function convertAdditionalStyles(style) {
|
|
20
|
+
if (!style || typeof style !== 'string') {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
const rules = style.split(';');
|
|
24
|
+
const tuples = rules
|
|
25
|
+
.map((rule) => {
|
|
26
|
+
const [rawKey, rawValue] = rule.split(':');
|
|
27
|
+
if (rawKey && rawValue) {
|
|
28
|
+
const key = toCamelCase(rawKey.trim());
|
|
29
|
+
const value = parseValue(rawValue);
|
|
30
|
+
return [key, value];
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
})
|
|
34
|
+
.filter((x) => x !== null);
|
|
35
|
+
return tuples.reduce((acc, [key, value]) => {
|
|
36
|
+
acc[key] = value;
|
|
37
|
+
return acc;
|
|
38
|
+
}, {});
|
|
39
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = flattenTokens;
|
|
4
|
+
function flattenTokens(tokens) {
|
|
5
|
+
return tokens.reduce((acc, curr) => {
|
|
6
|
+
if (curr.type === 'inline' && curr.children && curr.children.length > 0) {
|
|
7
|
+
const children = flattenTokens(curr.children);
|
|
8
|
+
while (children.length) {
|
|
9
|
+
acc.push(children.shift());
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
acc.push(curr);
|
|
14
|
+
}
|
|
15
|
+
return acc;
|
|
16
|
+
}, []);
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = getTokenTypeByToken;
|
|
4
|
+
const regSelectOpenClose = /_open|_close/g;
|
|
5
|
+
function getTokenTypeByToken(token) {
|
|
6
|
+
var _a, _b;
|
|
7
|
+
let cleanedType = 'unknown';
|
|
8
|
+
if (token.type) {
|
|
9
|
+
cleanedType = token.type.replace(regSelectOpenClose, '');
|
|
10
|
+
}
|
|
11
|
+
switch (cleanedType) {
|
|
12
|
+
case 'heading': {
|
|
13
|
+
cleanedType = `${cleanedType}${(_b = (_a = token.tag) === null || _a === void 0 ? void 0 : _a.substr(1)) !== null && _b !== void 0 ? _b : ''}`;
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
default: {
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return cleanedType;
|
|
21
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = groupTextTokens;
|
|
7
|
+
const Token_1 = __importDefault(require("./Token"));
|
|
8
|
+
function groupTextTokens(tokens) {
|
|
9
|
+
const result = [];
|
|
10
|
+
let hasGroup = false;
|
|
11
|
+
tokens.forEach((token) => {
|
|
12
|
+
if (!token.block && !hasGroup) {
|
|
13
|
+
hasGroup = true;
|
|
14
|
+
result.push(new Token_1.default('textgroup', 1));
|
|
15
|
+
result.push(token);
|
|
16
|
+
}
|
|
17
|
+
else if (!token.block && hasGroup) {
|
|
18
|
+
result.push(token);
|
|
19
|
+
}
|
|
20
|
+
else if (token.block && hasGroup) {
|
|
21
|
+
hasGroup = false;
|
|
22
|
+
result.push(new Token_1.default('textgroup', -1));
|
|
23
|
+
result.push(token);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
result.push(token);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = omitListItemParagraph;
|
|
4
|
+
function omitListItemParagraph(tokens) {
|
|
5
|
+
// used to ensure that we remove the correct ending paragraph token
|
|
6
|
+
let depth = null;
|
|
7
|
+
return tokens.filter((token, index) => {
|
|
8
|
+
// update depth if we've already removed a starting paragraph token
|
|
9
|
+
if (depth !== null) {
|
|
10
|
+
depth = depth + token.nesting;
|
|
11
|
+
}
|
|
12
|
+
// check for a list_item token followed by paragraph token (to remove)
|
|
13
|
+
if (token.type === 'list_item' && token.nesting === 1 && depth === null) {
|
|
14
|
+
const next = index + 1 in tokens ? tokens[index + 1] : null;
|
|
15
|
+
if (next && next.type === 'paragraph' && next.nesting === 1) {
|
|
16
|
+
depth = 0;
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
else if (token.type === 'paragraph') {
|
|
21
|
+
if (token.nesting === 1 && depth === 1) {
|
|
22
|
+
// remove the paragraph token immediately after the list_item token
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
else if (token.nesting === -1 && depth === 0) {
|
|
26
|
+
// remove the ending paragraph token; reset depth
|
|
27
|
+
depth = null;
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = openUrl;
|
|
4
|
+
const react_native_1 = require("react-native");
|
|
5
|
+
function openUrl(url, customCallback) {
|
|
6
|
+
if (customCallback) {
|
|
7
|
+
const result = customCallback(url);
|
|
8
|
+
if (url && result && typeof result === 'boolean') {
|
|
9
|
+
react_native_1.Linking.openURL(url);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
else if (url) {
|
|
13
|
+
react_native_1.Linking.openURL(url);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = removeTextStyleProps;
|
|
7
|
+
const textStyleProps_1 = __importDefault(require("../data/textStyleProps"));
|
|
8
|
+
function removeTextStyleProps(style) {
|
|
9
|
+
const intersection = textStyleProps_1.default.filter((value) => Object.keys(style).includes(value));
|
|
10
|
+
const obj = { ...style };
|
|
11
|
+
intersection.forEach((value) => {
|
|
12
|
+
delete obj[value];
|
|
13
|
+
});
|
|
14
|
+
return obj;
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = renderInlineAsText;
|
|
4
|
+
function renderInlineAsText(tokens) {
|
|
5
|
+
var _a;
|
|
6
|
+
let result = '';
|
|
7
|
+
for (let i = 0, len = tokens.length; i < len; i++) {
|
|
8
|
+
if (tokens[i].type === 'text') {
|
|
9
|
+
result += tokens[i].content;
|
|
10
|
+
}
|
|
11
|
+
else if (tokens[i].type === 'image') {
|
|
12
|
+
result += renderInlineAsText((_a = tokens[i].children) !== null && _a !== void 0 ? _a : []);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = splitTextNonTextNodes;
|
|
4
|
+
function isTextElement(node) {
|
|
5
|
+
if (typeof node.type === 'string') {
|
|
6
|
+
return node.type === 'Text';
|
|
7
|
+
}
|
|
8
|
+
const displayName = node.type.displayName;
|
|
9
|
+
return displayName === 'Text';
|
|
10
|
+
}
|
|
11
|
+
function splitTextNonTextNodes(children) {
|
|
12
|
+
return children.reduce((acc, curr) => {
|
|
13
|
+
if (isTextElement(curr)) {
|
|
14
|
+
acc.textNodes.push(curr);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
acc.nonTextNodes.push(curr);
|
|
18
|
+
}
|
|
19
|
+
return acc;
|
|
20
|
+
}, { textNodes: [], nonTextNodes: [] });
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stringToTokens = stringToTokens;
|
|
4
|
+
function stringToTokens(source, markdownIt) {
|
|
5
|
+
let result = [];
|
|
6
|
+
try {
|
|
7
|
+
result = markdownIt.parse(source, {});
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
console.warn(err);
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = tokensToAST;
|
|
7
|
+
const getUniqueID_1 = __importDefault(require("./getUniqueID"));
|
|
8
|
+
const getTokenTypeByToken_1 = __importDefault(require("./getTokenTypeByToken"));
|
|
9
|
+
function createNode(token, tokenIndex) {
|
|
10
|
+
var _a, _b, _c;
|
|
11
|
+
const type = (0, getTokenTypeByToken_1.default)(token);
|
|
12
|
+
const content = (_a = token.content) !== null && _a !== void 0 ? _a : '';
|
|
13
|
+
let attributes = {};
|
|
14
|
+
if (token.attrs) {
|
|
15
|
+
attributes = token.attrs.reduce((prev, curr) => {
|
|
16
|
+
const [name, value] = curr;
|
|
17
|
+
return { ...prev, [name]: value };
|
|
18
|
+
}, {});
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
type,
|
|
22
|
+
sourceType: token.type,
|
|
23
|
+
sourceInfo: token.info,
|
|
24
|
+
sourceMeta: token.meta,
|
|
25
|
+
block: token.block,
|
|
26
|
+
markup: (_b = token.markup) !== null && _b !== void 0 ? _b : '',
|
|
27
|
+
key: `${(0, getUniqueID_1.default)()}_${type}`,
|
|
28
|
+
content,
|
|
29
|
+
tokenIndex,
|
|
30
|
+
index: 0,
|
|
31
|
+
attributes,
|
|
32
|
+
children: tokensToAST((_c = token.children) !== null && _c !== void 0 ? _c : []),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function tokensToAST(tokens) {
|
|
36
|
+
var _a;
|
|
37
|
+
let stack = [];
|
|
38
|
+
let children = [];
|
|
39
|
+
if (!tokens || tokens.length === 0) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
43
|
+
const token = tokens[i];
|
|
44
|
+
const astNode = createNode(token, i);
|
|
45
|
+
if (!(astNode.type === 'text' &&
|
|
46
|
+
astNode.children.length === 0 &&
|
|
47
|
+
astNode.content === '')) {
|
|
48
|
+
astNode.index = children.length;
|
|
49
|
+
if (token.nesting === 1) {
|
|
50
|
+
children.push(astNode);
|
|
51
|
+
stack.push(children);
|
|
52
|
+
children = astNode.children;
|
|
53
|
+
}
|
|
54
|
+
else if (token.nesting === -1) {
|
|
55
|
+
children = (_a = stack.pop()) !== null && _a !== void 0 ? _a : [];
|
|
56
|
+
}
|
|
57
|
+
else if (token.nesting === 0) {
|
|
58
|
+
children.push(astNode);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return children;
|
|
63
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Base Markdown component
|
|
4
|
+
* @author Novastera + contributors
|
|
5
|
+
*/
|
|
6
|
+
import React, { useMemo } from 'react';
|
|
7
|
+
import { Text, StyleSheet } from 'react-native';
|
|
8
|
+
import MarkdownIt from 'markdown-it';
|
|
9
|
+
import { Image as MarkdownImage } from 'expo-image';
|
|
10
|
+
import parser from './lib/parser';
|
|
11
|
+
import getUniqueID from './lib/util/getUniqueID';
|
|
12
|
+
import hasParents from './lib/util/hasParents';
|
|
13
|
+
import openUrl from './lib/util/openUrl';
|
|
14
|
+
import tokensToAST from './lib/util/tokensToAST';
|
|
15
|
+
import renderRules from './lib/renderRules';
|
|
16
|
+
import AstRenderer from './lib/AstRenderer';
|
|
17
|
+
import removeTextStyleProps from './lib/util/removeTextStyleProps';
|
|
18
|
+
import { styles } from './lib/styles';
|
|
19
|
+
import { stringToTokens } from './lib/util/stringToTokens';
|
|
20
|
+
import textStyleProps from './lib/data/textStyleProps';
|
|
21
|
+
export { getUniqueID, openUrl, hasParents, renderRules, AstRenderer, parser, stringToTokens, tokensToAST, MarkdownIt, styles, removeTextStyleProps, textStyleProps, MarkdownImage, MarkdownImage as FitImage, };
|
|
22
|
+
// we use StyleSheet.flatten here to make sure we have an object, in case someone
|
|
23
|
+
// passes in a StyleSheet.create result to the style prop
|
|
24
|
+
const getStyle = (mergeStyle, style) => {
|
|
25
|
+
let useStyles = {};
|
|
26
|
+
const styleRecord = (style !== null && style !== void 0 ? style : {});
|
|
27
|
+
if (mergeStyle === true && style) {
|
|
28
|
+
// make sure we get anything user defuned
|
|
29
|
+
Object.keys(styleRecord).forEach((value) => {
|
|
30
|
+
const styleEntry = styleRecord[value];
|
|
31
|
+
useStyles[value] = {
|
|
32
|
+
...StyleSheet.flatten(styleEntry !== null && styleEntry !== void 0 ? styleEntry : {}),
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
// combine any existing styles
|
|
36
|
+
Object.keys(styles).forEach((value) => {
|
|
37
|
+
const styleEntry = styleRecord[value];
|
|
38
|
+
useStyles[value] = {
|
|
39
|
+
...styles[value],
|
|
40
|
+
...StyleSheet.flatten(styleEntry !== null && styleEntry !== void 0 ? styleEntry : {}),
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
useStyles = {
|
|
46
|
+
...styles,
|
|
47
|
+
};
|
|
48
|
+
if (style) {
|
|
49
|
+
Object.keys(styleRecord).forEach((value) => {
|
|
50
|
+
const styleEntry = styleRecord[value];
|
|
51
|
+
useStyles[value] = {
|
|
52
|
+
...StyleSheet.flatten(styleEntry !== null && styleEntry !== void 0 ? styleEntry : {}),
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
Object.keys(useStyles).forEach((value) => {
|
|
58
|
+
useStyles['_VIEW_SAFE_' + value] = removeTextStyleProps(useStyles[value]);
|
|
59
|
+
});
|
|
60
|
+
return StyleSheet.create(useStyles);
|
|
61
|
+
};
|
|
62
|
+
const getRenderer = (renderer, rules, style, mergeStyle = true, onLinkPress, maxTopLevelChildren, topLevelMaxExceededItem, allowedImageHandlers, defaultImageHandler, debugPrintTree) => {
|
|
63
|
+
if (renderer && rules) {
|
|
64
|
+
console.warn('react-native-markdown-display you are using renderer and rules at the same time. This is not possible, props.rules is ignored');
|
|
65
|
+
}
|
|
66
|
+
if (renderer && style) {
|
|
67
|
+
console.warn('react-native-markdown-display you are using renderer and style at the same time. This is not possible, props.style is ignored');
|
|
68
|
+
}
|
|
69
|
+
// these checks are here to prevent extra overhead.
|
|
70
|
+
if (renderer) {
|
|
71
|
+
if (typeof renderer === 'function') {
|
|
72
|
+
return renderer;
|
|
73
|
+
}
|
|
74
|
+
if (typeof renderer.render === 'function') {
|
|
75
|
+
return renderer;
|
|
76
|
+
}
|
|
77
|
+
throw new Error('Provided renderer is not compatible with function or AstRenderer. please change');
|
|
78
|
+
}
|
|
79
|
+
const useStyles = getStyle(mergeStyle, style);
|
|
80
|
+
return new AstRenderer({
|
|
81
|
+
...renderRules,
|
|
82
|
+
...(rules || {}),
|
|
83
|
+
}, useStyles, onLinkPress, maxTopLevelChildren, topLevelMaxExceededItem, allowedImageHandlers, defaultImageHandler, debugPrintTree);
|
|
84
|
+
};
|
|
85
|
+
const Markdown = React.memo(({ children, renderer, rules, style, mergeStyle = true, markdownit = MarkdownIt({
|
|
86
|
+
typographer: true,
|
|
87
|
+
}), onLinkPress, maxTopLevelChildren = null, topLevelMaxExceededItem = _jsx(Text, { children: "..." }, "dotdotdot"), allowedImageHandlers = [
|
|
88
|
+
'data:image/png;base64',
|
|
89
|
+
'data:image/gif;base64',
|
|
90
|
+
'data:image/jpeg;base64',
|
|
91
|
+
'https://',
|
|
92
|
+
'http://',
|
|
93
|
+
], defaultImageHandler = 'https://', debugPrintTree = false, }) => {
|
|
94
|
+
const memoizedRenderer = useMemo(() => getRenderer(renderer, rules, style, mergeStyle, onLinkPress, maxTopLevelChildren, topLevelMaxExceededItem, allowedImageHandlers, defaultImageHandler, debugPrintTree), [
|
|
95
|
+
maxTopLevelChildren,
|
|
96
|
+
onLinkPress,
|
|
97
|
+
renderer,
|
|
98
|
+
rules,
|
|
99
|
+
style,
|
|
100
|
+
mergeStyle,
|
|
101
|
+
topLevelMaxExceededItem,
|
|
102
|
+
allowedImageHandlers,
|
|
103
|
+
defaultImageHandler,
|
|
104
|
+
debugPrintTree,
|
|
105
|
+
]);
|
|
106
|
+
const memoizedParser = useMemo(() => markdownit, [markdownit]);
|
|
107
|
+
const render = typeof memoizedRenderer === 'function'
|
|
108
|
+
? memoizedRenderer
|
|
109
|
+
: memoizedRenderer.render;
|
|
110
|
+
return parser(children, render, memoizedParser);
|
|
111
|
+
});
|
|
112
|
+
export default Markdown;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import getUniqueID from './util/getUniqueID';
|
|
3
|
+
import convertAdditionalStyles from './util/convertAdditionalStyles';
|
|
4
|
+
import textStyleProps from './data/textStyleProps';
|
|
5
|
+
export default class AstRenderer {
|
|
6
|
+
constructor(renderRules, style, onLinkPress, maxTopLevelChildren, topLevelMaxExceededItem, allowedImageHandlers, defaultImageHandler, debugPrintTree) {
|
|
7
|
+
this.getRenderFunction = (type) => {
|
|
8
|
+
const renderFunction = this._renderRules[type];
|
|
9
|
+
if (!renderFunction) {
|
|
10
|
+
console.warn(`Warning, unknown render rule encountered: ${type}. 'unknown' render rule used (by default, returns null - nothing rendered)`);
|
|
11
|
+
return this._renderRules.unknown;
|
|
12
|
+
}
|
|
13
|
+
return renderFunction;
|
|
14
|
+
};
|
|
15
|
+
this.renderNode = (node, parentNodes, isRoot = false) => {
|
|
16
|
+
var _a, _b;
|
|
17
|
+
const renderFunction = this.getRenderFunction(node.type);
|
|
18
|
+
const parents = [...parentNodes];
|
|
19
|
+
if (this._debugPrintTree === true) {
|
|
20
|
+
let str = '';
|
|
21
|
+
for (let a = 0; a < parents.length; a++) {
|
|
22
|
+
str = str + '-';
|
|
23
|
+
}
|
|
24
|
+
console.log(`${str}${node.type}`);
|
|
25
|
+
}
|
|
26
|
+
parents.unshift(node);
|
|
27
|
+
// calculate the children first
|
|
28
|
+
let children = node.children.map((value) => {
|
|
29
|
+
return this.renderNode(value, parents);
|
|
30
|
+
});
|
|
31
|
+
// render any special types of nodes that have different renderRule function signatures
|
|
32
|
+
if (node.type === 'link' || node.type === 'blocklink') {
|
|
33
|
+
return renderFunction(node, children, parentNodes, this._style, this._onLinkPress);
|
|
34
|
+
}
|
|
35
|
+
if (node.type === 'image') {
|
|
36
|
+
return renderFunction(node, children, parentNodes, this._style, (_a = this._allowedImageHandlers) !== null && _a !== void 0 ? _a : [], (_b = this._defaultImageHandler) !== null && _b !== void 0 ? _b : null);
|
|
37
|
+
}
|
|
38
|
+
// We are at the bottom of some tree - grab all the parent styles
|
|
39
|
+
// this effectively grabs the styles from parents and
|
|
40
|
+
// applies them in order of priority parent (least) to child (most)
|
|
41
|
+
// to allow styling global, then lower down things individually
|
|
42
|
+
// we have to handle list_item seperately here because they have some child
|
|
43
|
+
// pseudo classes that need the additional style props from parents passed down to them
|
|
44
|
+
if (children.length === 0 || node.type === 'list_item') {
|
|
45
|
+
const styleObj = {};
|
|
46
|
+
for (let a = parentNodes.length - 1; a > -1; a--) {
|
|
47
|
+
// grab and additional attributes specified by markdown-it
|
|
48
|
+
let refStyle = {};
|
|
49
|
+
const styleAttr = parentNodes[a].attributes.style;
|
|
50
|
+
if (typeof styleAttr === 'string') {
|
|
51
|
+
refStyle = convertAdditionalStyles(styleAttr);
|
|
52
|
+
}
|
|
53
|
+
// combine in specific styles for the object
|
|
54
|
+
if (this._style[parentNodes[a].type]) {
|
|
55
|
+
refStyle = {
|
|
56
|
+
...refStyle,
|
|
57
|
+
...StyleSheet.flatten(this._style[parentNodes[a].type]),
|
|
58
|
+
};
|
|
59
|
+
// workaround for list_items and their content cascading down the tree
|
|
60
|
+
if (parentNodes[a].type === 'list_item') {
|
|
61
|
+
let contentStyle = {};
|
|
62
|
+
if (parentNodes[a + 1].type === 'bullet_list') {
|
|
63
|
+
contentStyle = this._style
|
|
64
|
+
.bullet_list_content;
|
|
65
|
+
}
|
|
66
|
+
else if (parentNodes[a + 1].type === 'ordered_list') {
|
|
67
|
+
contentStyle = this._style
|
|
68
|
+
.ordered_list_content;
|
|
69
|
+
}
|
|
70
|
+
refStyle = {
|
|
71
|
+
...refStyle,
|
|
72
|
+
...StyleSheet.flatten(contentStyle),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// then work out if any of them are text styles that should be used in the end.
|
|
77
|
+
const arr = Object.keys(refStyle);
|
|
78
|
+
for (let b = 0; b < arr.length; b++) {
|
|
79
|
+
if (textStyleProps.includes(arr[b])) {
|
|
80
|
+
styleObj[arr[b]] = refStyle[arr[b]];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const baseRenderFunction = renderFunction;
|
|
85
|
+
return baseRenderFunction(node, children, parentNodes, this._style, styleObj);
|
|
86
|
+
}
|
|
87
|
+
// cull top level children
|
|
88
|
+
if (isRoot === true &&
|
|
89
|
+
this._maxTopLevelChildren &&
|
|
90
|
+
children.length > this._maxTopLevelChildren) {
|
|
91
|
+
children = children.slice(0, this._maxTopLevelChildren);
|
|
92
|
+
if (this._topLevelMaxExceededItem) {
|
|
93
|
+
children.push(this._topLevelMaxExceededItem);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// render anythign else that has a normal signature
|
|
97
|
+
const baseRenderFunction = renderFunction;
|
|
98
|
+
return baseRenderFunction(node, children, parentNodes, this._style);
|
|
99
|
+
};
|
|
100
|
+
this.render = (nodes) => {
|
|
101
|
+
const root = {
|
|
102
|
+
type: 'body',
|
|
103
|
+
sourceType: 'body',
|
|
104
|
+
key: getUniqueID(),
|
|
105
|
+
content: '',
|
|
106
|
+
markup: '',
|
|
107
|
+
tokenIndex: -1,
|
|
108
|
+
index: 0,
|
|
109
|
+
attributes: {},
|
|
110
|
+
children: nodes,
|
|
111
|
+
};
|
|
112
|
+
return this.renderNode(root, [], true);
|
|
113
|
+
};
|
|
114
|
+
this._renderRules = renderRules;
|
|
115
|
+
this._style = style;
|
|
116
|
+
this._onLinkPress = onLinkPress;
|
|
117
|
+
this._maxTopLevelChildren = maxTopLevelChildren;
|
|
118
|
+
this._topLevelMaxExceededItem = topLevelMaxExceededItem;
|
|
119
|
+
this._allowedImageHandlers = allowedImageHandlers;
|
|
120
|
+
this._defaultImageHandler = defaultImageHandler;
|
|
121
|
+
this._debugPrintTree = debugPrintTree;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const textStyleProps = [
|
|
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
|
+
export default textStyleProps;
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
export default function parser(source, renderer, markdownIt) {
|
|
7
|
+
if (Array.isArray(source)) {
|
|
8
|
+
return renderer(source);
|
|
9
|
+
}
|
|
10
|
+
let tokens = stringToTokens(source, markdownIt);
|
|
11
|
+
tokens = cleanupTokens(tokens);
|
|
12
|
+
tokens = groupTextTokens(tokens);
|
|
13
|
+
tokens = omitListItemParagraph(tokens);
|
|
14
|
+
const astTree = tokensToAST(tokens);
|
|
15
|
+
return renderer(astTree);
|
|
16
|
+
}
|