Skip to content

Commit f62b5c1

Browse files
feat: add support for remark plugins (#60)
## 📝 Description This PR adds support for remark plugins to allow users take advantage of the remark ecosystem and extend the lib's behavior (when using plugins that add new nodes, users will have to inject the needed renderers using the `customRenderers` prop). It finishes the work started in PR #58 by [szymonograbek](https://github.com/szymonograbek). It also adds a section in the `README` file to explain how to use plugins with the lib. ## 📌 Related Issue (Optional) Link: #43 ## 📷 Screenshots (Optional) Example using the `remark-cjk-friendly` plugin. | Before | After | | ------ | ------ | | <img height="550" src="https://github.com/user-attachments/assets/fcb98d17-7065-4c4e-a369-a593c381c439" /> | <img height="550" src="https://github.com/user-attachments/assets/45dfbd55-ef12-41fb-afba-c7d85c9a3f4f" /> | ## 📄 Test Markdown File (Optional) File `05_cjk_plugin_example.md` included in the PR ## ✅ Checklist - [x] My PR title follows the Conventional Commit format (e.g. `feat:`, `fix:`, `chore:`) - [x] This PR addresses the related issue (if applicable) - [x] I have attached a sample Markdown file (`.md`) used for testing - [x] I have attached at least one screenshot of the rendered output (light and/or dark mode preferred) - [x] I have run `pnpm run lint:fix` to fix formatting and lint errors - [x] I have tested the changes in the example app (`pnpm i && cd example && pnpm i && pnpm run ios`) --------- Co-authored-by: Szymon Ograbek <szymon.ograbek2@gmail.com>
1 parent a3f1ce7 commit f62b5c1

6 files changed

Lines changed: 358 additions & 98 deletions

File tree

README.md

Lines changed: 71 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
- 🖼️ Inline and block image rendering
4040
- 🌙 Dark Mode support
4141
- ⚙️ Custom renderers and styles for flexible UI customization
42+
- 🧩 Supports Remark plugins to enhance capabilities
4243

4344
## Installation
4445

@@ -62,33 +63,33 @@ This is a **Markdown** example with [a link](https://reactnative.dev).
6263

6364
export default function App() {
6465
return (
65-
<Markdown
66-
markdown={markdown}
67-
customRenderers={{
68-
// Override default renderers for mdast nodes.
69-
// Checkout https://github.com/imwithye/react-native-remark/blob/main/src/renderers/index.tsx
70-
// for the default renderers.
71-
InlineCodeRenderer: ({ node }) => (
72-
<Text style={{ color: "blue" }}>{node.value}</Text>
73-
),
74-
ThematicBreakRenderer: () => (
75-
<View style={{ height: 5, backgroundColor: "red" }} />
76-
),
77-
}}
78-
customStyles={{
79-
// Override default styles
80-
// Checkout https://github.com/imwithye/react-native-remark/blob/main/src/themes/default.tsx
81-
// for the default styles.
82-
inlineCode: {
83-
color: "red",
84-
},
85-
text: {
86-
color: "red",
87-
},
88-
}}
89-
onCodeCopy={(code) => Clipboard.setStringAsync(code)}
90-
onLinkPress={(url) => Linking.openURL(url)}
91-
/>
66+
<Markdown
67+
markdown={markdown}
68+
customRenderers={{
69+
// Override default renderers for mdast nodes.
70+
// Checkout https://github.com/imwithye/react-native-remark/blob/main/src/renderers/index.tsx
71+
// for the default renderers.
72+
InlineCodeRenderer: ({ node }) => (
73+
<Text style={{ color: "blue" }}>{node.value}</Text>
74+
),
75+
ThematicBreakRenderer: () => (
76+
<View style={{ height: 5, backgroundColor: "red" }} />
77+
),
78+
}}
79+
customStyles={{
80+
// Override default styles
81+
// Checkout https://github.com/imwithye/react-native-remark/blob/main/src/themes/default.tsx
82+
// for the default styles.
83+
inlineCode: {
84+
color: "red",
85+
},
86+
text: {
87+
color: "red",
88+
},
89+
}}
90+
onCodeCopy={(code) => Clipboard.setStringAsync(code)}
91+
onLinkPress={(url) => Linking.openURL(url)}
92+
/>
9293
);
9394
}
9495
```
@@ -110,9 +111,11 @@ index?: number;
110111

111112
Checkout [renderers](./src/renderers/) for the default implementations. To ensure type safety when creating custom renderers, you can use the `RendererArgs<MdastType>` props interface.
112113

114+
Note that the library ships with renderers for the `remark-gfm` plugin since we think it is often used. You don't need to pass custom renderers (though you can!) for this plugin.
115+
113116
## Supported Themes
114117

115-
Themes are pre-defined style collections that provide consistent visual styling for markdown content. Currently, we support two built-in themes:
118+
Themes are pre-defined style collections that provide consistent visual styling for markdown content. Currently, we support two built-in themes:
116119

117120
1. default
118121
2. serif
@@ -124,7 +127,7 @@ To use a theme, you can follow this pattern:
124127
import { themes } from "react-native-remark"
125128
const { serifTheme } = themes;
126129

127-
// Thene you can use it with
130+
// Then you can use it with
128131
<Markdown theme={serifTheme} ... />
129132
```
130133

@@ -134,28 +137,45 @@ Custom styles will override the selected theme's default styles.
134137

135138
Checkout [default.tsx](./src/themes/default.tsx) for default styles.
136139

137-
| Style Key | Description | Example Markdown Element |
138-
|-------------------|---------------------------------------|-------------------------------|
139-
| `blockquote` | Styles for blockquotes | `> This is a blockquote` |
140-
| `borderColor` | Default border color used globally | Borders, thematic breaks |
141-
| `break` | Line break styling (empty by default) | Line breaks |
142-
| `codeBlock` | Styles for code blocks | ```code``` blocks |
143-
| `container` | Container layout spacing | Root container layout |
144-
| `delete` | Deleted text style | ~~strikethrough~~ text |
145-
| `emphasis` | Italic text style | *italic* or _italic_ |
146-
| `footnoteReference` | Style for footnote references | Footnote markers |
147-
| `heading` | Heading styles (h1, h2, h3...) | # Heading |
148-
| `image` | Image styling | Inline or block images |
149-
| `inlineCode` | Inline code styling | `inline code` |
150-
| `link` | Link styling | [link](url) |
151-
| `linkReference` | Reference-style links | [reference][id] |
152-
| `list` | List container styling | Lists (`- item` or `1. item`) |
153-
| `listItem` | List item styling | Each list item |
154-
| `paragraph` | Paragraph text styling | Normal paragraphs |
155-
| `strong` | Bold text style | **bold** |
156-
| `tableCell` | Table cell text styling | Table cell contents |
157-
| `text` | General text style | Plain text |
158-
| `thematicBreak` | Horizontal rule styling | --- |
140+
| Style Key | Description | Example Markdown Element |
141+
| ------------------- | ------------------------------------- | ----------------------------- |
142+
| `blockquote` | Styles for blockquotes | `> This is a blockquote` |
143+
| `borderColor` | Default border color used globally | Borders, thematic breaks |
144+
| `break` | Line break styling (empty by default) | Line breaks |
145+
| `codeBlock` | Styles for code blocks | `code` blocks |
146+
| `container` | Container layout spacing | Root container layout |
147+
| `delete` | Deleted text style | ~~strikethrough~~ text |
148+
| `emphasis` | Italic text style | *italic* or _italic_ |
149+
| `footnoteReference` | Style for footnote references | Footnote markers |
150+
| `heading` | Heading styles (h1, h2, h3...) | # Heading |
151+
| `image` | Image styling | Inline or block images |
152+
| `inlineCode` | Inline code styling | `inline code` |
153+
| `link` | Link styling | [link](url) |
154+
| `linkReference` | Reference-style links | [reference][id] |
155+
| `list` | List container styling | Lists (`- item` or `1. item`) |
156+
| `listItem` | List item styling | Each list item |
157+
| `paragraph` | Paragraph text styling | Normal paragraphs |
158+
| `strong` | Bold text style | **bold** |
159+
| `tableCell` | Table cell text styling | Table cell contents |
160+
| `text` | General text style | Plain text |
161+
| `thematicBreak` | Horizontal rule styling | --- |
162+
163+
## Remark plugins usage
164+
165+
You can inject Remark plugins _via_ the `remarkPlugins` prop. For example, you can use `remark-cjk-friendly` to add support for bold (`**`) with Chinese, Japanese and Korean alphabets by doing the following:
166+
167+
```jsx
168+
import remarkCjkFriendly from "remark-cjk-friendly";
169+
import remarkGfm from "remark-gfm";
170+
171+
<Markdown remarkPlugins={[remarkGfm, remarkCjkFriendly]} /* ... */ />;
172+
```
173+
174+
Note that:
175+
176+
- though `remark-gfm` is supported out of the box, you'll need to add the plugin manually when injecting other Remark plugins
177+
- when using plugins that add support for additional nodes, you'll need to inject the required renderers using the `customRenderers` prop
178+
- ⚠️ asynchronous plugins aren't supported yet; passing any asynchronous plugin will result in a crash ⚠️
159179

160180
## Quick Look
161181

@@ -177,4 +197,3 @@ By interacting with this repository, organization, or community you agree to abi
177197
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=imwithye/react-native-remark&type=Date" width="100%" />
178198
</picture>
179199
</a>
180-

example/App.tsx

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@ import {
22
ActionSheetProvider,
33
useActionSheet,
44
} from "@expo/react-native-action-sheet";
5-
import { Markdown } from "@react-native-remark";
6-
import { themes } from "@react-native-remark";
5+
import { Markdown, themes } from "@react-native-remark";
76
import {
87
createStaticNavigation,
98
useNavigation,
109
} from "@react-navigation/native";
1110
import { createNativeStackNavigator } from "@react-navigation/native-stack";
1211
import { StatusBar } from "expo-status-bar";
13-
import { useEffect, useState } from "react";
12+
import { useCallback, useEffect, useState } from "react";
1413
import {
1514
ActivityIndicator,
1615
Alert,
@@ -19,6 +18,8 @@ import {
1918
ScrollView,
2019
useColorScheme,
2120
} from "react-native";
21+
import remarkCjkFriendly from "remark-cjk-friendly";
22+
import remarkGfm from "remark-gfm";
2223

2324
const { defaultTheme, githubTheme, serifTheme } = themes;
2425

@@ -30,11 +31,23 @@ const HomeScreen = () => {
3031
const colorScheme = useColorScheme();
3132
const navigation = useNavigation();
3233
const { showActionSheetWithOptions } = useActionSheet();
33-
const [url, setUrl] = useState(URL);
3434
const [markdown, setMarkdown] = useState("");
3535
const [theme, setTheme] = useState(defaultTheme);
3636
const [loading, setLoading] = useState(false);
3737

38+
const loadMarkdown = useCallback((url: string) => {
39+
setLoading(true);
40+
41+
const controller = new AbortController();
42+
43+
fetch(url, { signal: controller.signal })
44+
.then((res) => res.text())
45+
.then((text) => setMarkdown(text))
46+
.finally(() => setTimeout(() => setLoading(false), 1000));
47+
48+
return controller;
49+
}, []);
50+
3851
useEffect(() => {
3952
navigation.setOptions({
4053
headerTintColor: colorScheme === "dark" ? "white" : "black",
@@ -103,7 +116,11 @@ const HomeScreen = () => {
103116
url: `${BASE_URL}/04_pytorch.md`,
104117
},
105118
{
106-
title: "5. Load from URL",
119+
title: "5. Bug fixing with CJK plugin",
120+
url: `${BASE_URL}/05_cjk_plugin_example.md`,
121+
},
122+
{
123+
title: "6. Load from URL",
107124
url: "",
108125
},
109126
];
@@ -115,28 +132,34 @@ const HomeScreen = () => {
115132
},
116133
(idx?: number) => {
117134
if (!idx || idx === cancelButtonIndex) return;
135+
118136
if (idx === options.length - 1) {
119137
Alert.prompt("Load Markdown from URL", "", (url) => {
120-
setUrl(url);
138+
loadMarkdown(url);
121139
});
122140
return;
123141
}
124-
setUrl(options[idx].url);
142+
143+
loadMarkdown(options[idx].url);
125144
},
126145
);
127146
}}
128147
/>
129148
),
130149
});
131-
}, [colorScheme, navigation, showActionSheetWithOptions, setTheme, setUrl]);
150+
}, [
151+
colorScheme,
152+
navigation,
153+
showActionSheetWithOptions,
154+
setTheme,
155+
loadMarkdown,
156+
]);
132157

133158
useEffect(() => {
134-
setLoading(true);
135-
fetch(url)
136-
.then((res) => res.text())
137-
.then((text) => setMarkdown(text))
138-
.finally(() => setTimeout(() => setLoading(false), 1000));
139-
}, [url]);
159+
const controller = loadMarkdown(URL);
160+
161+
return () => controller.abort();
162+
}, [loadMarkdown]);
140163

141164
return (
142165
<ScrollView
@@ -153,6 +176,7 @@ const HomeScreen = () => {
153176
<Markdown
154177
markdown={markdown}
155178
theme={theme}
179+
remarkPlugins={[remarkGfm, remarkCjkFriendly]}
156180
onLinkPress={(url) => Linking.openURL(url)}
157181
/>
158182
)}

example/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"expo-status-bar": "~2.2.3",
1717
"react": "19.0.0",
1818
"react-native": "0.79.4",
19-
"react-native-remark": "file:../"
19+
"react-native-remark": "file:../",
20+
"remark-cjk-friendly": "^2.0.1"
2021
},
2122
"devDependencies": {
2223
"@babel/core": "^7.25.2",

0 commit comments

Comments
 (0)