Skip to content

Commit 659b638

Browse files
committed
feat: add table support as well
1 parent df7b753 commit 659b638

File tree

4 files changed

+125
-81
lines changed

4 files changed

+125
-81
lines changed

packages/react-native-sdk/src/MarkdownRichText.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,6 @@ export const MarkdownRichText = ({
8383
// );
8484
// };
8585

86-
// @ts-ignore
87-
const codeBlockReact = (node, _, state) => (
88-
<MarkdownReactiveScrollView key={state.key}>
89-
<Text style={styles.codeBlock}>{node?.content?.trim()}</Text>
90-
</MarkdownReactiveScrollView>
91-
);
92-
9386
// @ts-ignore
9487
const tableReact = (node, output, state) => (
9588
<MarkdownReactiveScrollView key={state.key}>
@@ -109,7 +102,7 @@ export const MarkdownRichText = ({
109102
// Truncate long text content in the message overlay
110103
// paragraph: paragraphNumberOfLines ? { react: paragraphTextReact } : {},
111104
// sublist: { react: listReact },
112-
table: { react: tableReact },
105+
// table: { react: tableReact },
113106
};
114107

115108
/**

packages/react-native-sdk/src/markdown/components/CodeBlock.tsx

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const CodeBlock = ({
77
styles,
88
state,
99
}: MarkdownComponentProps) => (
10-
<MarkdownReactiveScrollView key={state.key}>
10+
<MarkdownReactiveScrollView>
1111
<Text style={styles.codeBlock}>{children}</Text>
1212
</MarkdownReactiveScrollView>
1313
);
@@ -17,16 +17,14 @@ export const renderCodeBlock: RuleRenderFunction = ({
1717
output,
1818
state,
1919
styles,
20-
}) => {
21-
return (
22-
<CodeBlock
23-
key={state.key}
24-
node={node}
25-
output={output}
26-
state={state}
27-
styles={styles}
28-
>
29-
{node.content?.trim()}
30-
</CodeBlock>
31-
);
32-
};
20+
}) => (
21+
<CodeBlock
22+
key={state.key}
23+
node={node}
24+
output={output}
25+
state={state}
26+
styles={styles}
27+
>
28+
{node.content?.trim()}
29+
</CodeBlock>
30+
);
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { Text, View } from 'react-native';
2+
import type {
3+
MarkdownComponentProps,
4+
MarkdownOutputProps,
5+
MarkdownTableRowProps,
6+
RuleRenderFunction,
7+
} from '../types.ts';
8+
import { MarkdownReactiveScrollView } from '../../components';
9+
import styles from '../styles.ts';
10+
import type { SingleASTNode } from '@khanacademy/simple-markdown';
11+
import { useCallback, useMemo } from 'react';
12+
13+
export const Table = ({
14+
node,
15+
output,
16+
styles,
17+
state,
18+
}: MarkdownComponentProps) => (
19+
<MarkdownReactiveScrollView>
20+
<MarkdownTable node={node} output={output} state={state} styles={styles} />
21+
</MarkdownReactiveScrollView>
22+
);
23+
24+
const transpose = (matrix: SingleASTNode[][]) =>
25+
// TS gets confused because it considers the matrix to be potentially ragged,
26+
// while the markdown parser can never output a ragged matrix of AST nodes
27+
// when parsing a table. Hence, we use the forced type coercion.
28+
(matrix[0] ?? []).map((_, colIndex) => matrix.map((row) => row[colIndex]!));
29+
30+
const MarkdownTable = ({
31+
node,
32+
output,
33+
state,
34+
styles,
35+
}: MarkdownOutputProps) => {
36+
const content = useMemo(() => {
37+
const nodeContent = [node?.header, ...(node?.cells ?? [])];
38+
return transpose(nodeContent);
39+
}, [node?.cells, node?.header]);
40+
const columns = content?.map((column, idx) => (
41+
<MarkdownTableColumn
42+
node={node}
43+
items={column}
44+
key={`column-${idx}`}
45+
output={output}
46+
state={state}
47+
styles={styles}
48+
/>
49+
));
50+
51+
return (
52+
<View key={state.key} style={styles.table}>
53+
{columns}
54+
</View>
55+
);
56+
};
57+
58+
const MarkdownTableColumn = ({
59+
items,
60+
output,
61+
state,
62+
styles,
63+
}: MarkdownTableRowProps) => {
64+
const [headerCellContent, ...columnCellContents] = items;
65+
66+
const ColumnCell = useCallback(
67+
({ content }: { content: SingleASTNode }) =>
68+
content ? (
69+
<View style={styles.tableRow}>
70+
<View style={styles.tableRowCell}>{output(content, state)}</View>
71+
</View>
72+
) : null,
73+
[output, state, styles],
74+
);
75+
76+
return (
77+
<View style={{ flex: 1, flexDirection: 'column' }}>
78+
{headerCellContent ? (
79+
<View key={-1} style={styles.tableHeader}>
80+
<Text style={styles.tableHeaderCell}>
81+
{output(headerCellContent, state)}
82+
</Text>
83+
</View>
84+
) : null}
85+
{columnCellContents &&
86+
columnCellContents.map((content, idx) => (
87+
<ColumnCell content={content} key={`cell-${idx}`} />
88+
))}
89+
</View>
90+
);
91+
};
92+
93+
export const renderTable: RuleRenderFunction = ({
94+
node,
95+
output,
96+
state,
97+
styles,
98+
}) => (
99+
<Table
100+
key={state.key}
101+
node={node}
102+
output={output}
103+
state={state}
104+
styles={styles}
105+
>
106+
{node.content?.trim()}
107+
</Table>
108+
);

packages/react-native-sdk/src/markdown/rules.ts

Lines changed: 4 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import React from 'react';
2-
import type { TextStyle, ViewStyle } from 'react-native';
3-
import { Text, View } from 'react-native';
1+
import type React from 'react';
42

53
import type {
64
MatchFunction,
@@ -12,18 +10,13 @@ import type {
1210
SingleASTNode,
1311
} from '@khanacademy/simple-markdown';
1412
import SimpleMarkdown from '@khanacademy/simple-markdown';
15-
import { head, includes, map } from 'lodash';
1613

1714
import type {
18-
ListNode,
1915
MarkdownOptions,
2016
MarkdownState,
2117
MarkdownStyle,
22-
NodeWithContent,
23-
NodeWithStringContent,
2418
RuleRenderFunction,
2519
RuleRenderFunctionEnrichedProps,
26-
TableNode,
2720
} from './types';
2821
import { renderBlockQuote } from './components';
2922
import { renderAutolink } from './components/Autolink.tsx';
@@ -42,6 +35,7 @@ import { renderText } from './components/Text.tsx';
4235
import { renderList } from './components/List.tsx';
4336
import { renderCodeBlock } from './components/CodeBlock.tsx';
4437
import { renderInlineCode } from './components/InlineCode.tsx';
38+
import { renderTable } from './components/Table.tsx';
4539

4640
const LINK_INSIDE = '(?:\\[(?:\\\\.|[^\\\\\\[\\]])*\\]|\\\\.|[^\\[\\]\\\\])*';
4741
/**
@@ -67,7 +61,7 @@ export const getLocalRules = (
6761
const { onLink, paragraphNumberOfLines } = opts;
6862
const openLinkHandler = (target: string) => {
6963
if (onLink) {
70-
// user-supplied handler may be async; we keep your behavior
64+
// user-supplied handler may be async; we keep the behavior
7165
Promise.resolve(onLink(target)).catch((error: unknown) => {
7266
const msg =
7367
error && typeof error === 'object' && 'toString' in error
@@ -175,56 +169,7 @@ export const getLocalRules = (
175169
react: enrichedRenderFunction(renderList),
176170
},
177171
table: {
178-
react(
179-
node: SingleASTNode,
180-
output: Output<React.ReactNode>,
181-
{ ...state }: MarkdownState,
182-
) {
183-
const n = node as TableNode;
184-
185-
const headers = map(n.header, (content, i) =>
186-
React.createElement(
187-
Text,
188-
{
189-
key: i,
190-
style: styles.tableHeaderCell,
191-
},
192-
output(content, state),
193-
),
194-
);
195-
196-
const header = React.createElement(
197-
View,
198-
{ key: -1, style: styles.tableHeader },
199-
headers,
200-
);
201-
202-
const rows = map(n.cells, (row, r) => {
203-
const cells = map(row, (content, c) =>
204-
React.createElement(
205-
View,
206-
{
207-
key: c,
208-
style: styles.tableRowCell,
209-
},
210-
output(content, state),
211-
),
212-
);
213-
const rowStyles: (TextStyle | ViewStyle | undefined)[] = [
214-
styles.tableRow,
215-
];
216-
if (n.cells.length - 1 === r) {
217-
rowStyles.push(styles.tableRowLast);
218-
}
219-
return React.createElement(View, { key: r, style: rowStyles }, cells);
220-
});
221-
222-
return React.createElement(
223-
View,
224-
{ key: state.key, style: styles.table },
225-
[header, rows],
226-
);
227-
},
172+
react: enrichedRenderFunction(renderTable),
228173
},
229174
text: {
230175
react: enrichedRenderFunction(renderText),

0 commit comments

Comments
 (0)