diff --git a/src/lib/Parser.tsx b/src/lib/Parser.tsx
index 872642e1..794fcd8c 100644
--- a/src/lib/Parser.tsx
+++ b/src/lib/Parser.tsx
@@ -54,21 +54,25 @@ class Parser {
this.styles.text,
);
- return this.renderer.paragraph(children, this.styles.paragraph);
+ return this.renderer.paragraph(children, this.styles.paragraph, token);
}
case "blockquote": {
const children = this.parse(token.tokens);
- return this.renderer.blockquote(children, this.styles.blockquote);
+ return this.renderer.blockquote(
+ children,
+ this.styles.blockquote,
+ token,
+ );
}
case "heading": {
const styles = this.headingStylesMap[token.depth];
if (this.hasDuplicateTextChildToken(token)) {
- return this.renderer.heading(token.text, styles, token.depth);
+ return this.renderer.heading(token.text, styles, token.depth, token);
}
const children = this._parse(token.tokens, styles);
- return this.renderer.heading(children, styles, token.depth);
+ return this.renderer.heading(children, styles, token.depth, token);
}
case "code": {
return this.renderer.code(
@@ -76,6 +80,7 @@ class Parser {
token.lang,
this.styles.code,
this.styles.em,
+ token,
);
}
case "hr": {
@@ -104,7 +109,7 @@ class Parser {
return this._parseToken(cItem);
});
- return this.renderer.listItem(children, this.styles.li);
+ return this.renderer.listItem(children, this.styles.li, token);
});
return this.renderer.list(
@@ -139,17 +144,18 @@ class Parser {
const href = getValidURL(this.baseUrl, token.href);
if (this.hasDuplicateTextChildToken(token)) {
- return this.renderer.link(token.text, href, linkStyle);
+ return this.renderer.link(token.text, href, linkStyle, token);
}
const children = this._parse(token.tokens, linkStyle);
- return this.renderer.link(children, href, linkStyle);
+ return this.renderer.link(children, href, linkStyle, token);
}
case "image": {
return this.renderer.image(
token.href,
token.text || token.title,
this.styles.image,
+ token,
);
}
case "strong": {
@@ -158,11 +164,11 @@ class Parser {
...styles,
};
if (this.hasDuplicateTextChildToken(token)) {
- return this.renderer.strong(token.text, boldStyle);
+ return this.renderer.strong(token.text, boldStyle, token);
}
const children = this._parse(token.tokens, boldStyle);
- return this.renderer.strong(children, boldStyle);
+ return this.renderer.strong(children, boldStyle, token);
}
case "em": {
const italicStyle = {
@@ -170,17 +176,21 @@ class Parser {
...styles,
};
if (this.hasDuplicateTextChildToken(token)) {
- return this.renderer.em(token.text, italicStyle);
+ return this.renderer.em(token.text, italicStyle, token);
}
const children = this._parse(token.tokens, italicStyle);
- return this.renderer.em(children, italicStyle);
+ return this.renderer.em(children, italicStyle, token);
}
case "codespan": {
- return this.renderer.codespan(decode(token.text), {
- ...this.styles.codespan,
- ...styles,
- });
+ return this.renderer.codespan(
+ decode(token.text),
+ {
+ ...this.styles.codespan,
+ ...styles,
+ },
+ token,
+ );
}
case "br": {
return this.renderer.br();
@@ -191,25 +201,33 @@ class Parser {
...styles,
};
if (this.hasDuplicateTextChildToken(token)) {
- return this.renderer.del(token.text, strikethroughStyle);
+ return this.renderer.del(token.text, strikethroughStyle, token);
}
const children = this._parse(token.tokens, strikethroughStyle);
- return this.renderer.del(children, strikethroughStyle);
+ return this.renderer.del(children, strikethroughStyle, token);
}
case "text":
- return this.renderer.text(token.raw, {
- ...this.styles.text,
- ...styles,
- });
+ return this.renderer.text(
+ token.raw,
+ {
+ ...this.styles.text,
+ ...styles,
+ },
+ token,
+ );
case "html": {
console.warn(
"react-native-marked: rendering html from markdown is not supported",
);
- return this.renderer.html(token.raw, {
- ...this.styles.text,
- ...styles,
- });
+ return this.renderer.html(
+ token.raw,
+ {
+ ...this.styles.text,
+ ...styles,
+ },
+ token,
+ );
}
case "table": {
const header = (token as Tokens.Table).header.map((row, i) =>
diff --git a/src/lib/Renderer.tsx b/src/lib/Renderer.tsx
index 47a39841..9b18dab3 100644
--- a/src/lib/Renderer.tsx
+++ b/src/lib/Renderer.tsx
@@ -19,6 +19,8 @@ import type { RendererInterface } from "./types";
import { getTableWidthArr } from "../utils/table";
import MDSvg from "./../components/MDSvg";
import MDTable from "./../components/MDTable";
+import type { Token } from "marked";
+import { superFastHash } from "../utils/hash";
class Renderer implements RendererInterface {
private slugPrefix = "react-native-marked-ele";
@@ -30,16 +32,29 @@ class Renderer implements RendererInterface {
this.windowWidth = width;
}
- paragraph(children: ReactNode[], styles?: ViewStyle): ReactNode {
- return this.getViewNode(children, styles);
+ paragraph(
+ children: ReactNode[],
+ styles?: ViewStyle,
+ token?: Token,
+ ): ReactNode {
+ return this.getViewNode(children, styles, token);
}
- blockquote(children: ReactNode[], styles?: ViewStyle): ReactNode {
- return this.getBlockquoteNode(children, styles);
+ blockquote(
+ children: ReactNode[],
+ styles?: ViewStyle,
+ token?: Token,
+ ): ReactNode {
+ return this.getBlockquoteNode(children, styles, token);
}
- heading(text: string | ReactNode[], styles?: TextStyle): ReactNode {
- return this.getTextNode(text, styles);
+ heading(
+ text: string | ReactNode[],
+ styles?: TextStyle,
+ _depth?: number,
+ token?: Token,
+ ): ReactNode {
+ return this.getTextNode(text, styles, token);
}
code(
@@ -47,11 +62,12 @@ class Renderer implements RendererInterface {
_language?: string,
containerStyle?: ViewStyle,
textStyle?: TextStyle,
+ token?: Token,
): ReactNode {
return (
{/*
@@ -59,7 +75,7 @@ class Renderer implements RendererInterface {
Error: Cannot add a child that doesn't have a YogaNode to a parent without a measure function!
ref: https://github.com/facebook/react-native/issues/18773
*/}
- {this.getTextNode(text, textStyle)}
+ {this.getTextNode(text, textStyle, token)}
);
}
@@ -68,8 +84,12 @@ class Renderer implements RendererInterface {
return this.getViewNode(null, styles);
}
- listItem(children: ReactNode[], styles?: ViewStyle): ReactNode {
- return this.getViewNode(children, styles);
+ listItem(
+ children: ReactNode[],
+ styles?: ViewStyle,
+ token?: Token,
+ ): ReactNode {
+ return this.getViewNode(children, styles, token);
}
list(
@@ -100,13 +120,14 @@ class Renderer implements RendererInterface {
children: string | ReactNode[],
href: string,
styles?: TextStyle,
+ token?: Token,
): ReactNode {
return (
@@ -115,40 +136,65 @@ class Renderer implements RendererInterface {
);
}
- image(uri: string, alt?: string, style?: ImageStyle): ReactNode {
- const key = this.getKey();
+ image(
+ uri: string,
+ alt?: string,
+ style?: ImageStyle,
+ token?: Token,
+ ): ReactNode {
+ const key = this.getKey(token?.type, token?.raw);
if (uri.endsWith(".svg")) {
return ;
}
return ;
}
- strong(children: string | ReactNode[], styles?: TextStyle): ReactNode {
- return this.getTextNode(children, styles);
+ strong(
+ children: string | ReactNode[],
+ styles?: TextStyle,
+ token?: Token,
+ ): ReactNode {
+ return this.getTextNode(children, styles, token);
}
- em(children: string | ReactNode[], styles?: TextStyle): ReactNode {
- return this.getTextNode(children, styles);
+ em(
+ children: string | ReactNode[],
+ styles?: TextStyle,
+ token?: Token,
+ ): ReactNode {
+ return this.getTextNode(children, styles, token);
}
- codespan(text: string, styles?: TextStyle): ReactNode {
- return this.getTextNode(text, styles);
+ codespan(text: string, styles?: TextStyle, token?: Token): ReactNode {
+ return this.getTextNode(text, styles, token);
}
br(): ReactNode {
return this.getTextNode("\n", {});
}
- del(children: string | ReactNode[], styles?: TextStyle): ReactNode {
- return this.getTextNode(children, styles);
+ del(
+ children: string | ReactNode[],
+ styles?: TextStyle,
+ token?: Token,
+ ): ReactNode {
+ return this.getTextNode(children, styles, token);
}
- text(text: string | ReactNode[], styles?: TextStyle): ReactNode {
- return this.getTextNode(text, styles);
+ text(
+ text: string | ReactNode[],
+ styles?: TextStyle,
+ token?: Token,
+ ): ReactNode {
+ return this.getTextNode(text, styles, token);
}
- html(text: string | ReactNode[], styles?: TextStyle): ReactNode {
- return this.getTextNode(text, styles);
+ html(
+ text: string | ReactNode[],
+ styles?: TextStyle,
+ token?: Token,
+ ): ReactNode {
+ return this.getTextNode(text, styles, token);
}
linkImage(
@@ -156,6 +202,7 @@ class Renderer implements RendererInterface {
imageUrl: string,
alt?: string,
style?: ImageStyle,
+ token?: Token,
): ReactNode {
const imageNode = this.image(imageUrl, alt, style);
return (
@@ -163,7 +210,7 @@ class Renderer implements RendererInterface {
accessibilityRole="link"
accessibilityHint="Opens in a new window"
onPress={onLinkPress(href)}
- key={this.getKey()}
+ key={this.getKey(token?.type, token?.raw)}
>
{imageNode}
@@ -192,16 +239,24 @@ class Renderer implements RendererInterface {
);
}
- getKey(): string {
- return this.slugger.slug(this.slugPrefix);
+ getKey(type = "", text = ""): string {
+ if (!type && !text) return this.slugger.slug(this.slugPrefix);
+
+ const hash = superFastHash(type + text);
+ return String(hash);
}
private getTextNode(
children: string | ReactNode[],
styles?: TextStyle,
+ token?: Token,
): ReactNode {
return (
-
+
{children}
);
@@ -210,9 +265,10 @@ class Renderer implements RendererInterface {
private getViewNode(
children: ReactNode[] | null,
styles?: ViewStyle,
+ token?: Token,
): ReactNode {
return (
-
+
{children}
);
@@ -221,9 +277,10 @@ class Renderer implements RendererInterface {
private getBlockquoteNode(
children: ReactNode[],
styles?: ViewStyle,
+ token?: Token,
): ReactNode {
return (
-
+
{children}
);
diff --git a/src/lib/types.ts b/src/lib/types.ts
index e2197013..33211636 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -6,7 +6,7 @@ import type {
ImageStyle,
} from "react-native";
import type { MarkedStyles, UserTheme } from "./../theme/types";
-import type { Tokenizer } from "marked";
+import type { Token, Tokenizer } from "marked";
export interface ParserOptions {
styles?: MarkedStyles;
@@ -27,21 +27,31 @@ export interface MarkdownProps extends Partial {
export type TableColAlignment = "center" | "left" | "right" | null;
export interface RendererInterface {
- paragraph(children: ReactNode[], styles?: ViewStyle): ReactNode;
- blockquote(children: ReactNode[], styles?: ViewStyle): ReactNode;
+ paragraph(
+ children: ReactNode[],
+ styles?: ViewStyle,
+ token?: Token,
+ ): ReactNode;
+ blockquote(
+ children: ReactNode[],
+ styles?: ViewStyle,
+ token?: Token,
+ ): ReactNode;
heading(
text: string | ReactNode[],
styles?: TextStyle,
depth?: number,
+ token?: Token,
): ReactNode;
code(
text: string,
language?: string,
containerStyle?: ViewStyle,
textStyle?: TextStyle,
+ token?: Token,
): ReactNode;
hr(styles?: ViewStyle): ReactNode;
- listItem(children: ReactNode[], styles?: ViewStyle): ReactNode;
+ listItem(children: ReactNode[], styles?: ViewStyle, token?: Token): ReactNode;
list(
ordered: boolean,
li: ReactNode[],
@@ -54,20 +64,47 @@ export interface RendererInterface {
children: string | ReactNode[],
href: string,
styles?: TextStyle,
+ token?: Token,
+ ): ReactNode;
+ image(
+ uri: string,
+ alt?: string,
+ style?: ImageStyle,
+ token?: Token,
+ ): ReactNode;
+ strong(
+ children: string | ReactNode[],
+ styles?: TextStyle,
+ token?: Token,
+ ): ReactNode;
+ em(
+ children: string | ReactNode[],
+ styles?: TextStyle,
+ token?: Token,
): ReactNode;
- image(uri: string, alt?: string, style?: ImageStyle): ReactNode;
- strong(children: string | ReactNode[], styles?: TextStyle): ReactNode;
- em(children: string | ReactNode[], styles?: TextStyle): ReactNode;
- codespan(text: string, styles?: TextStyle): ReactNode;
+ codespan(text: string, styles?: TextStyle, token?: Token): ReactNode;
br(): ReactNode;
- del(children: string | ReactNode[], styles?: TextStyle): ReactNode;
- text(text: string | ReactNode[], styles?: TextStyle): ReactNode;
- html(text: string | ReactNode[], styles?: TextStyle): ReactNode;
+ del(
+ children: string | ReactNode[],
+ styles?: TextStyle,
+ token?: Token,
+ ): ReactNode;
+ text(
+ text: string | ReactNode[],
+ styles?: TextStyle,
+ token?: Token,
+ ): ReactNode;
+ html(
+ text: string | ReactNode[],
+ styles?: TextStyle,
+ token?: Token,
+ ): ReactNode;
linkImage(
href: string,
imageUrl: string,
alt?: string,
style?: ImageStyle,
+ token?: Token,
): ReactNode;
table(
header: ReactNode[][],
diff --git a/src/utils/hash.ts b/src/utils/hash.ts
new file mode 100644
index 00000000..1513b69f
--- /dev/null
+++ b/src/utils/hash.ts
@@ -0,0 +1,16 @@
+// Paul Hsieh's SuperFastHash
+// Ref: https://mojoauth.com/hashing/paul-hsiehs-superfasthash-in-javascript-in-browser/
+const superFastHash = (str: string): number => {
+ let hash = 0;
+ let i: number;
+ let chr: number;
+ if (str.length === 0) return hash;
+ for (i = 0; i < str.length; i++) {
+ chr = str.charCodeAt(i);
+ hash = (hash << 5) - hash + chr; // hash * 31 + chr
+ hash |= 0; // Convert to 32bit integer
+ }
+ return hash >>> 0; // Ensure a positive integer
+};
+
+export { superFastHash };