@domenico-esposito/react-native-markdown-editor 0.1.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/.eslintrc.js +5 -0
- package/README.md +265 -0
- package/build/MarkdownRenderer.d.ts +12 -0
- package/build/MarkdownRenderer.d.ts.map +1 -0
- package/build/MarkdownRenderer.js +165 -0
- package/build/MarkdownRenderer.js.map +1 -0
- package/build/MarkdownTextInput.d.ts +10 -0
- package/build/MarkdownTextInput.d.ts.map +1 -0
- package/build/MarkdownTextInput.js +233 -0
- package/build/MarkdownTextInput.js.map +1 -0
- package/build/MarkdownToolbar.d.ts +11 -0
- package/build/MarkdownToolbar.d.ts.map +1 -0
- package/build/MarkdownToolbar.js +98 -0
- package/build/MarkdownToolbar.js.map +1 -0
- package/build/index.d.ts +14 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +11 -0
- package/build/index.js.map +1 -0
- package/build/markdownCore.types.d.ts +321 -0
- package/build/markdownCore.types.d.ts.map +1 -0
- package/build/markdownCore.types.js +2 -0
- package/build/markdownCore.types.js.map +1 -0
- package/build/markdownHighlight.d.ts +31 -0
- package/build/markdownHighlight.d.ts.map +1 -0
- package/build/markdownHighlight.js +378 -0
- package/build/markdownHighlight.js.map +1 -0
- package/build/markdownHighlight.types.d.ts +48 -0
- package/build/markdownHighlight.types.d.ts.map +1 -0
- package/build/markdownHighlight.types.js +9 -0
- package/build/markdownHighlight.types.js.map +1 -0
- package/build/markdownParser.d.ts +16 -0
- package/build/markdownParser.d.ts.map +1 -0
- package/build/markdownParser.js +309 -0
- package/build/markdownParser.js.map +1 -0
- package/build/markdownRendererDefaults.d.ts +113 -0
- package/build/markdownRendererDefaults.d.ts.map +1 -0
- package/build/markdownRendererDefaults.js +174 -0
- package/build/markdownRendererDefaults.js.map +1 -0
- package/build/markdownSegment.types.d.ts +22 -0
- package/build/markdownSegment.types.d.ts.map +1 -0
- package/build/markdownSegment.types.js +2 -0
- package/build/markdownSegment.types.js.map +1 -0
- package/build/markdownSegmentDefaults.d.ts +43 -0
- package/build/markdownSegmentDefaults.d.ts.map +1 -0
- package/build/markdownSegmentDefaults.js +176 -0
- package/build/markdownSegmentDefaults.js.map +1 -0
- package/build/markdownSyntaxUtils.d.ts +58 -0
- package/build/markdownSyntaxUtils.d.ts.map +1 -0
- package/build/markdownSyntaxUtils.js +98 -0
- package/build/markdownSyntaxUtils.js.map +1 -0
- package/build/markdownToolbarActions.d.ts +12 -0
- package/build/markdownToolbarActions.d.ts.map +1 -0
- package/build/markdownToolbarActions.js +212 -0
- package/build/markdownToolbarActions.js.map +1 -0
- package/build/useMarkdownEditor.d.ts +10 -0
- package/build/useMarkdownEditor.d.ts.map +1 -0
- package/build/useMarkdownEditor.js +219 -0
- package/build/useMarkdownEditor.js.map +1 -0
- package/jest.config.js +10 -0
- package/package.json +45 -0
- package/src/MarkdownRenderer.tsx +240 -0
- package/src/MarkdownTextInput.tsx +263 -0
- package/src/MarkdownToolbar.tsx +126 -0
- package/src/index.ts +31 -0
- package/src/markdownCore.types.ts +405 -0
- package/src/markdownHighlight.ts +413 -0
- package/src/markdownHighlight.types.ts +75 -0
- package/src/markdownParser.ts +345 -0
- package/src/markdownRendererDefaults.tsx +207 -0
- package/src/markdownSegment.types.ts +24 -0
- package/src/markdownSegmentDefaults.tsx +208 -0
- package/src/markdownSyntaxUtils.ts +139 -0
- package/src/markdownToolbarActions.ts +296 -0
- package/src/useMarkdownEditor.ts +265 -0
- package/tsconfig.json +9 -0
- package/tsconfig.test.json +8 -0
package/.eslintrc.js
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# React Native Markdown Editor
|
|
2
|
+
|
|
3
|
+
A fully native Markdown editor for React Native with real-time syntax highlighting.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 📝 **Real-time Syntax Highlighting**: Text is parsed and styled as you type.
|
|
8
|
+
- ⚡️ **Native Performance**: Uses native components (`TextInput` with _attributed strings_) on iOS and Android, and `contentEditable` with inline syntax highlighting on Web.
|
|
9
|
+
- 🧩 **Customizable Components**: You can replace segment renderers (`bold`, `heading`, `code`, etc.) with your own components.
|
|
10
|
+
- 🛠 **Built-in Toolbar**: Buttons for quick formatting (bold, italic, lists, etc.) and extensible.
|
|
11
|
+
- 🖼 **Image Support**: Insert, edit, and remove images via toolbar and programmatic API.
|
|
12
|
+
- 📄 **Read-Only Renderer**: `MarkdownRenderer` component to display markdown without editing.
|
|
13
|
+
- 📱 **Cross-Platform**: Support for iOS, Android, and Web (Expo), with native syntax highlighting on all platforms.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install react-native-markdown-editor
|
|
19
|
+
# or
|
|
20
|
+
yarn add react-native-markdown-editor
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Editor with Toolbar
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import * as React from 'react';
|
|
29
|
+
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
|
30
|
+
import { MarkdownTextInput, MarkdownToolbar, useMarkdownEditor } from 'react-native-markdown-editor';
|
|
31
|
+
|
|
32
|
+
export default function App() {
|
|
33
|
+
const [value, setValue] = React.useState('# Hello World\n\nType your markdown here...');
|
|
34
|
+
const editor = useMarkdownEditor({
|
|
35
|
+
value,
|
|
36
|
+
onChangeText: setValue,
|
|
37
|
+
// Optional: limit enabled features (default: all)
|
|
38
|
+
// features: ['bold', 'italic', 'heading1', 'heading2', 'quote'],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<SafeAreaProvider>
|
|
43
|
+
<SafeAreaView style={{ flex: 1 }}>
|
|
44
|
+
<MarkdownToolbar editor={editor} />
|
|
45
|
+
<MarkdownTextInput editor={editor} placeholder="Write markdown..." />
|
|
46
|
+
</SafeAreaView>
|
|
47
|
+
</SafeAreaProvider>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Read-Only Renderer
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { MarkdownRenderer } from 'react-native-markdown-editor';
|
|
56
|
+
|
|
57
|
+
function Preview({ markdown }: { markdown: string }) {
|
|
58
|
+
return <MarkdownRenderer markdown={markdown} />;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Limiting Enabled Features
|
|
63
|
+
|
|
64
|
+
By passing `features` to the hook (or directly to `MarkdownRenderer`), only the corresponding features are highlighted/rendered; the rest of the markdown syntax appears as plain text.
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
// Editor: only bold, italic, and heading1 enabled
|
|
68
|
+
const editor = useMarkdownEditor({
|
|
69
|
+
value,
|
|
70
|
+
onChangeText: setValue,
|
|
71
|
+
features: ['bold', 'italic', 'heading1'],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
<MarkdownToolbar editor={editor} /> {/* shows only 3 buttons */}
|
|
75
|
+
<MarkdownTextInput editor={editor} /> {/* highlights only bold/italic/h1 */}
|
|
76
|
+
|
|
77
|
+
// Standalone renderer with the same restrictions
|
|
78
|
+
<MarkdownRenderer
|
|
79
|
+
markdown={value}
|
|
80
|
+
features={['bold', 'italic', 'heading1']}
|
|
81
|
+
/>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## API
|
|
85
|
+
|
|
86
|
+
### `useMarkdownEditor`
|
|
87
|
+
|
|
88
|
+
Hook that manages shared state between `MarkdownTextInput` and `MarkdownToolbar`.
|
|
89
|
+
Returns a `MarkdownEditorHandle` object to pass as the `editor` prop to both components.
|
|
90
|
+
|
|
91
|
+
#### Options
|
|
92
|
+
|
|
93
|
+
| Parameter | Type | Default | Description |
|
|
94
|
+
| ------------------- | ------------------------------------------------------------------------------ | ------------ | ------------------------------------------------------------------------------------------------------------ |
|
|
95
|
+
| `value` | `string` | **Required** | The current markdown text (controlled). |
|
|
96
|
+
| `onChangeText` | `(nextValue: string) => void` | **Required** | Callback called when the text changes. |
|
|
97
|
+
| `onSelectionChange` | `(selection: MarkdownSelection) => void` | - | Optional callback for selection changes. |
|
|
98
|
+
| `onToolbarAction` | `(action: MarkdownToolbarAction, result: MarkdownToolbarActionResult) => void` | - | Callback after each toolbar action. |
|
|
99
|
+
| `features` | `MarkdownToolbarAction[]` | All features | Enabled markdown features. Controls both the toolbar buttons and syntax highlighting in `MarkdownTextInput`. |
|
|
100
|
+
|
|
101
|
+
#### Return Value (`MarkdownEditorHandle`)
|
|
102
|
+
|
|
103
|
+
| Property | Type | Description |
|
|
104
|
+
| --------------------- | ------------------------------- | ----------------------------------------------------------------------------- |
|
|
105
|
+
| `features` | `MarkdownToolbarAction[]` | Features enabled in the hook. |
|
|
106
|
+
| `value` | `string` | Current text value. |
|
|
107
|
+
| `selection` | `MarkdownSelection` | Current selection range. |
|
|
108
|
+
| `activeInlineActions` | `MarkdownInlineToolbarAction[]` | Active inline actions (e.g. bold, italic). |
|
|
109
|
+
| `activeImageInfo` | `MarkdownImageInfo \| null` | Info about the image under the cursor, `null` otherwise. |
|
|
110
|
+
| `imageInfo` | `MarkdownImageInfo \| null` | Image info set by the toolbar (modal state). |
|
|
111
|
+
| `openImageInfo` | `() => void` | Sets `imageInfo` to the current active image. |
|
|
112
|
+
| `dismissImageInfo` | `() => void` | Closes the image popup (`imageInfo = null`). |
|
|
113
|
+
| `deleteActiveImage` | `() => void` | Removes the markdown image at the current position. |
|
|
114
|
+
| `highlightedSegments` | `HighlightSegment[]` | Highlighted segments for live preview. |
|
|
115
|
+
| `inputRef` | `RefObject<TextInput>` | Ref to the underlying `TextInput` (on web points to `<div contentEditable>`). |
|
|
116
|
+
|
|
117
|
+
### `MarkdownTextInput`
|
|
118
|
+
|
|
119
|
+
Input component with live highlighting. Requires `editor`.
|
|
120
|
+
|
|
121
|
+
| Prop | Type | Default | Description |
|
|
122
|
+
| ------------------- | ---------------------- | ------------ | ----------------------------------------------------------------------------------------------- |
|
|
123
|
+
| `editor` | `MarkdownEditorHandle` | **Required** | Handle returned by `useMarkdownEditor`. |
|
|
124
|
+
| `segmentComponents` | `SegmentComponentMap` | - | Override renderers for highlighted segments. |
|
|
125
|
+
| `textInputStyle` | `TextStyle` | - | Style for the inner `TextInput`. |
|
|
126
|
+
| `style` | `ViewStyle` | - | Style for the outer container. |
|
|
127
|
+
| `...rest` | `TextInputProps` | - | All other `TextInput` props (except `value`, `onChangeText`, `onSelectionChange`, `multiline`). |
|
|
128
|
+
|
|
129
|
+
### `MarkdownToolbar`
|
|
130
|
+
|
|
131
|
+
Markdown action toolbar. Can work with `editor` (recommended) or in manual mode.
|
|
132
|
+
|
|
133
|
+
| Prop | Type | Default | Description |
|
|
134
|
+
| ------------------------- | ----------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------- |
|
|
135
|
+
| `editor` | `MarkdownEditorHandle` | - | Handle returned by `useMarkdownEditor`. When present, the toolbar uses `editor.features` as the feature list. |
|
|
136
|
+
| `features` | `MarkdownToolbarAction[]` | All features | List of features to show (used only in manual mode, without `editor`). |
|
|
137
|
+
| `activeInlineActions` | `MarkdownInlineToolbarAction[]` | - | Active inline actions (manual mode only). |
|
|
138
|
+
| `onPressAction` | `(action: MarkdownToolbarAction) => void` | - | Action callback (manual mode only). |
|
|
139
|
+
| `style` | `ViewStyle` | - | Style for the toolbar container. |
|
|
140
|
+
| `buttonStyle` | `ViewStyle \| (state) => ViewStyle` | - | Style applied to each button (static or function). |
|
|
141
|
+
| `buttonTextStyle` | `TextStyle \| (state) => TextStyle` | - | Text style applied to each button (static or function). |
|
|
142
|
+
| `activeButtonStyle` | `ViewStyle` | - | Additional style for active buttons. |
|
|
143
|
+
| `activeButtonTextStyle` | `TextStyle` | - | Additional text style for active buttons. |
|
|
144
|
+
| `inactiveButtonStyle` | `ViewStyle` | - | Additional style for inactive buttons. |
|
|
145
|
+
| `inactiveButtonTextStyle` | `TextStyle` | - | Additional text style for inactive buttons. |
|
|
146
|
+
| `renderButton` | `(params) => ReactNode` | - | Custom renderer for each button. |
|
|
147
|
+
|
|
148
|
+
#### Custom Style Example
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
<MarkdownToolbar
|
|
152
|
+
editor={editor}
|
|
153
|
+
features={['bold', 'italic', 'heading1', 'heading2', 'quote', 'unorderedList']}
|
|
154
|
+
buttonStyle={(state) => ({
|
|
155
|
+
backgroundColor: state.active ? '#007AFF' : '#f0f0f0',
|
|
156
|
+
borderRadius: 6,
|
|
157
|
+
})}
|
|
158
|
+
/>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### `MarkdownRenderer`
|
|
162
|
+
|
|
163
|
+
Read-only component for rendering markdown into React Native components.
|
|
164
|
+
|
|
165
|
+
| Prop | Type | Default | Description |
|
|
166
|
+
| ------------ | ------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
167
|
+
| `markdown` | `string` | **Required** | The markdown string to render. |
|
|
168
|
+
| `components` | `Partial<MarkdownComponentMap>` | - | Component overrides for specific tags. |
|
|
169
|
+
| `style` | `ViewStyle` | - | Style for the root container. |
|
|
170
|
+
| `features` | `MarkdownToolbarAction[]` | - | Enabled markdown features. When provided, only the corresponding features are rendered; disabled syntax is treated as plain text. |
|
|
171
|
+
|
|
172
|
+
#### Component Overrides
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
import { MarkdownRenderer } from 'react-native-markdown-editor';
|
|
176
|
+
import type { MarkdownComponentProps } from 'react-native-markdown-editor';
|
|
177
|
+
|
|
178
|
+
const CustomBold = ({ children }: MarkdownComponentProps) => <Text style={{ fontWeight: '900', color: 'red' }}>{children}</Text>;
|
|
179
|
+
|
|
180
|
+
<MarkdownRenderer markdown="Some **bold** and *italic* text" components={{ bold: CustomBold }} />;
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Utilities
|
|
184
|
+
|
|
185
|
+
| Export | Description |
|
|
186
|
+
| --------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
187
|
+
| `parseMarkdown(markdown, features?)` | Parses markdown into an array of block nodes (`MarkdownBlockNode[]`). |
|
|
188
|
+
| `parseMarkdownInline(content, features?)` | Parses inline content into inline nodes (`MarkdownInlineNode[]`). |
|
|
189
|
+
| `highlightMarkdown(markdown, features?)` | Converts markdown into semantic segments for live preview (`HighlightSegment[]`). |
|
|
190
|
+
| `applyMarkdownToolbarAction(params)` | Applies a toolbar action to the text and returns the result. |
|
|
191
|
+
| `DEFAULT_MARKDOWN_FEATURES` | Default list of enabled features. |
|
|
192
|
+
| `DEFAULT_SEGMENT_COMPONENTS` | Complete map of default segment components. |
|
|
193
|
+
| `getDefaultSegmentStyle(type, meta?)` | Returns the default `TextStyle` for a segment type. |
|
|
194
|
+
| `isMarkdownFeatureEnabled(features, feature)` | Checks if a markdown feature is enabled based on the features array. |
|
|
195
|
+
| `isHeadingLevelEnabled(features, level)` | Checks if a specific heading level (1–6) is enabled. |
|
|
196
|
+
|
|
197
|
+
## Architecture
|
|
198
|
+
|
|
199
|
+
### Source Structure
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
src/
|
|
203
|
+
├── index.ts # Public re-exports
|
|
204
|
+
├── markdownCore.types.ts # Shared types (AST nodes, component props, editor handle)
|
|
205
|
+
│
|
|
206
|
+
├── useMarkdownEditor.ts # Hook: shared state between input and toolbar
|
|
207
|
+
├── MarkdownTextInput.tsx # Editor component with live preview
|
|
208
|
+
├── MarkdownToolbar.tsx # Action toolbar component
|
|
209
|
+
│
|
|
210
|
+
├── MarkdownRenderer.tsx # Read-only component for markdown rendering
|
|
211
|
+
├── markdownRendererDefaults.tsx # Default components and styles for the renderer
|
|
212
|
+
│
|
|
213
|
+
├── markdownParser.ts # Parser: markdown → AST (block + inline nodes)
|
|
214
|
+
├── markdownHighlight.ts # Highlighter: markdown → semantic segments (for TextInput)
|
|
215
|
+
├── markdownHighlight.types.ts # Types for the highlighting module
|
|
216
|
+
│
|
|
217
|
+
├── markdownToolbarActions.ts # Transformation logic for toolbar actions
|
|
218
|
+
├── markdownSyntaxUtils.ts # Shared utilities: escape, token scanning, image parsing
|
|
219
|
+
├── markdownSegment.types.ts # Segment types for TextInput renderers
|
|
220
|
+
└── markdownSegmentDefaults.tsx # Default segment components for TextInput
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### How It Works
|
|
224
|
+
|
|
225
|
+
This module uses an advanced technique to ensure real-time highlighting while keeping the editor editable and performant.
|
|
226
|
+
|
|
227
|
+
#### "Styled Children" Rendering
|
|
228
|
+
|
|
229
|
+
Unlike approaches based on WebView or complex native libraries, `react-native-markdown-editor` uses the standard React Native system:
|
|
230
|
+
|
|
231
|
+
1. **Highlighting** (`markdownHighlight.ts`): The markdown text is parsed and split into segments with associated styles. Unlike the AST parser, this module produces flat segments optimized for rendering in the `TextInput`.
|
|
232
|
+
2. **Attributed Text**: Instead of passing a flat string to the `TextInput`, segments are passed as `<Text>` children with individual styles.
|
|
233
|
+
```jsx
|
|
234
|
+
<TextInput>
|
|
235
|
+
<Text style={styles.heading}># Title</Text>
|
|
236
|
+
<Text style={styles.body}>Normal text</Text>
|
|
237
|
+
<Text style={styles.bold}>**Bold**</Text>
|
|
238
|
+
</TextInput>
|
|
239
|
+
```
|
|
240
|
+
3. **Native**: React Native converts these children into native _Attributed Strings_ (on iOS and Android). The operating system handles mixed text rendering, selection, cursor, and input.
|
|
241
|
+
|
|
242
|
+
#### Web: ContentEditable
|
|
243
|
+
|
|
244
|
+
On Web, `<TextInput>` is rendered as a `<textarea>` by the DOM, which does not support styled children. To maintain syntax highlighting in the browser as well, `MarkdownTextInput` uses a different approach:
|
|
245
|
+
|
|
246
|
+
1. **ContentEditable**: A `<div contentEditable>` replaces the `TextInput`. Highlighted segments are converted into `<span>` elements with inline CSS (same colors, fonts, and sizes as native) and injected via `innerHTML`.
|
|
247
|
+
2. **Cursor Preservation**: On each DOM update, the cursor position is saved as a text offset (via `TreeWalker`) and restored after the rebuild, ensuring a smooth typing experience.
|
|
248
|
+
3. **Input and Paste**: Enter and Paste keys are intercepted to insert plain text (`\n` and clipboard plain text), preventing the browser from inserting unwanted HTML elements.
|
|
249
|
+
4. **Programmatic Selection**: Toolbar actions update the selection via `editor.selection`, which is applied to the `contentEditable` via `useEffect`.
|
|
250
|
+
|
|
251
|
+
#### Parser vs Highlighter
|
|
252
|
+
|
|
253
|
+
The codebase contains two distinct analysis modules:
|
|
254
|
+
|
|
255
|
+
| Module | Output | Usage |
|
|
256
|
+
| ---------------------- | ------------------------------------ | --------------------------------- |
|
|
257
|
+
| `markdownParser.ts` | Tree AST (`MarkdownBlockNode[]`) | `MarkdownRenderer` (read-only) |
|
|
258
|
+
| `markdownHighlight.ts` | Flat segments (`HighlightSegment[]`) | `MarkdownTextInput` (live editor) |
|
|
259
|
+
|
|
260
|
+
Both share low-level utilities in `markdownSyntaxUtils.ts` (escape, token scanning, image URL parsing).
|
|
261
|
+
|
|
262
|
+
#### Input Handling
|
|
263
|
+
|
|
264
|
+
- Uses synchronization techniques (`requestAnimationFrame`) to update the native buffer without conflicts.
|
|
265
|
+
- On Android, applies specific workarounds to force cursor position after programmatic toolbar actions.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only markdown renderer component.
|
|
3
|
+
*
|
|
4
|
+
* Parses a markdown string into an AST and maps each node to a
|
|
5
|
+
* React Native component. Every tag can be overridden via the
|
|
6
|
+
* `components` prop; unspecified tags fall back to defaults
|
|
7
|
+
* defined in `markdownRendererDefaults.tsx`.
|
|
8
|
+
*/
|
|
9
|
+
import * as React from 'react';
|
|
10
|
+
import type { MarkdownRendererProps } from './markdownCore.types';
|
|
11
|
+
export default function MarkdownRenderer({ markdown, components, style, features }: MarkdownRendererProps): React.JSX.Element;
|
|
12
|
+
//# sourceMappingURL=MarkdownRenderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownRenderer.d.ts","sourceRoot":"","sources":["../src/MarkdownRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAGX,qBAAqB,EACrB,MAAM,sBAAsB,CAAC;AAI9B,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,qBAAqB,qBAyJxG"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only markdown renderer component.
|
|
3
|
+
*
|
|
4
|
+
* Parses a markdown string into an AST and maps each node to a
|
|
5
|
+
* React Native component. Every tag can be overridden via the
|
|
6
|
+
* `components` prop; unspecified tags fall back to defaults
|
|
7
|
+
* defined in `markdownRendererDefaults.tsx`.
|
|
8
|
+
*/
|
|
9
|
+
import * as React from 'react';
|
|
10
|
+
import { parseMarkdown } from './markdownParser';
|
|
11
|
+
import { DEFAULT_COMPONENTS } from './markdownRendererDefaults';
|
|
12
|
+
export default function MarkdownRenderer({ markdown, components, style, features }) {
|
|
13
|
+
const blocks = React.useMemo(() => parseMarkdown(markdown, features), [markdown, features]);
|
|
14
|
+
const componentMap = React.useMemo(() => ({
|
|
15
|
+
...DEFAULT_COMPONENTS,
|
|
16
|
+
...components,
|
|
17
|
+
}), [components]);
|
|
18
|
+
const RootComponent = componentMap.root;
|
|
19
|
+
return (<RootComponent type="root" style={style}>
|
|
20
|
+
{blocks.map((block, blockIndex) => {
|
|
21
|
+
const key = `block-${blockIndex}`;
|
|
22
|
+
if (block.type === 'paragraph') {
|
|
23
|
+
if (block.children.length === 1 && block.children[0]?.type === 'image') {
|
|
24
|
+
const imageNode = block.children[0];
|
|
25
|
+
const ImageComponent = componentMap.image;
|
|
26
|
+
return <ImageComponent key={key} type="image" src={imageNode.src} alt={imageNode.alt} title={imageNode.title}/>;
|
|
27
|
+
}
|
|
28
|
+
const ParagraphComponent = componentMap.paragraph;
|
|
29
|
+
return (<ParagraphComponent key={key} type="paragraph">
|
|
30
|
+
{renderInlineNodes(block.children, componentMap, key)}
|
|
31
|
+
</ParagraphComponent>);
|
|
32
|
+
}
|
|
33
|
+
if (block.type === 'heading') {
|
|
34
|
+
if (block.level === 1) {
|
|
35
|
+
const HeadingComponent = componentMap.heading1;
|
|
36
|
+
return (<HeadingComponent key={key} type="heading1" level={1}>
|
|
37
|
+
{renderInlineNodes(block.children, componentMap, key)}
|
|
38
|
+
</HeadingComponent>);
|
|
39
|
+
}
|
|
40
|
+
if (block.level === 2) {
|
|
41
|
+
const HeadingComponent = componentMap.heading2;
|
|
42
|
+
return (<HeadingComponent key={key} type="heading2" level={2}>
|
|
43
|
+
{renderInlineNodes(block.children, componentMap, key)}
|
|
44
|
+
</HeadingComponent>);
|
|
45
|
+
}
|
|
46
|
+
if (block.level === 3) {
|
|
47
|
+
const HeadingComponent = componentMap.heading3;
|
|
48
|
+
return (<HeadingComponent key={key} type="heading3" level={3}>
|
|
49
|
+
{renderInlineNodes(block.children, componentMap, key)}
|
|
50
|
+
</HeadingComponent>);
|
|
51
|
+
}
|
|
52
|
+
if (block.level === 4) {
|
|
53
|
+
const HeadingComponent = componentMap.heading4;
|
|
54
|
+
return (<HeadingComponent key={key} type="heading4" level={4}>
|
|
55
|
+
{renderInlineNodes(block.children, componentMap, key)}
|
|
56
|
+
</HeadingComponent>);
|
|
57
|
+
}
|
|
58
|
+
if (block.level === 5) {
|
|
59
|
+
const HeadingComponent = componentMap.heading5;
|
|
60
|
+
return (<HeadingComponent key={key} type="heading5" level={5}>
|
|
61
|
+
{renderInlineNodes(block.children, componentMap, key)}
|
|
62
|
+
</HeadingComponent>);
|
|
63
|
+
}
|
|
64
|
+
const HeadingComponent = componentMap.heading6;
|
|
65
|
+
return (<HeadingComponent key={key} type="heading6" level={6}>
|
|
66
|
+
{renderInlineNodes(block.children, componentMap, key)}
|
|
67
|
+
</HeadingComponent>);
|
|
68
|
+
}
|
|
69
|
+
if (block.type === 'codeBlock') {
|
|
70
|
+
const CodeBlockComponent = componentMap.codeBlock;
|
|
71
|
+
return <CodeBlockComponent key={key} type="codeBlock" text={block.content} language={block.language}/>;
|
|
72
|
+
}
|
|
73
|
+
if (block.type === 'blockquote') {
|
|
74
|
+
const BlockquoteComponent = componentMap.blockquote;
|
|
75
|
+
return (<BlockquoteComponent key={key} type="blockquote">
|
|
76
|
+
{renderInlineNodes(block.children, componentMap, key)}
|
|
77
|
+
</BlockquoteComponent>);
|
|
78
|
+
}
|
|
79
|
+
if (block.type === 'horizontalRule') {
|
|
80
|
+
const HorizontalRuleComponent = componentMap.horizontalRule;
|
|
81
|
+
return <HorizontalRuleComponent key={key} type="horizontalRule"/>;
|
|
82
|
+
}
|
|
83
|
+
if (block.type === 'spacer') {
|
|
84
|
+
const SpacerComponent = componentMap.spacer;
|
|
85
|
+
return <SpacerComponent key={key} type="spacer"/>;
|
|
86
|
+
}
|
|
87
|
+
const ListItemComponent = componentMap.listItem;
|
|
88
|
+
const TextComponent = componentMap.text;
|
|
89
|
+
if (block.ordered) {
|
|
90
|
+
const OrderedListComponent = componentMap.orderedList;
|
|
91
|
+
return (<OrderedListComponent key={key} type="orderedList" ordered>
|
|
92
|
+
{block.items.map((item, itemIndex) => {
|
|
93
|
+
const marker = `${itemIndex + 1}. `;
|
|
94
|
+
return (<ListItemComponent key={`${key}-item-${itemIndex}`} type="listItem" ordered index={itemIndex}>
|
|
95
|
+
<TextComponent type="text" text={marker}>
|
|
96
|
+
{marker}
|
|
97
|
+
{renderInlineNodes(item, componentMap, `${key}-item-${itemIndex}`)}
|
|
98
|
+
</TextComponent>
|
|
99
|
+
</ListItemComponent>);
|
|
100
|
+
})}
|
|
101
|
+
</OrderedListComponent>);
|
|
102
|
+
}
|
|
103
|
+
const UnorderedListComponent = componentMap.unorderedList;
|
|
104
|
+
return (<UnorderedListComponent key={key} type="unorderedList" ordered={false}>
|
|
105
|
+
{block.items.map((item, itemIndex) => {
|
|
106
|
+
const marker = '- ';
|
|
107
|
+
return (<ListItemComponent key={`${key}-item-${itemIndex}`} type="listItem" ordered={false} index={itemIndex}>
|
|
108
|
+
<TextComponent type="text" text={marker}>
|
|
109
|
+
{marker}
|
|
110
|
+
{renderInlineNodes(item, componentMap, `${key}-item-${itemIndex}`)}
|
|
111
|
+
</TextComponent>
|
|
112
|
+
</ListItemComponent>);
|
|
113
|
+
})}
|
|
114
|
+
</UnorderedListComponent>);
|
|
115
|
+
})}
|
|
116
|
+
</RootComponent>);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Recursively renders an array of inline markdown nodes into React elements.
|
|
120
|
+
* Each node type is mapped to its corresponding component from the component map.
|
|
121
|
+
*/
|
|
122
|
+
function renderInlineNodes(nodes, components, keyPrefix) {
|
|
123
|
+
return nodes.map((node, index) => {
|
|
124
|
+
const key = `${keyPrefix}-inline-${index}`;
|
|
125
|
+
if (node.type === 'text') {
|
|
126
|
+
const TextComponent = components.text;
|
|
127
|
+
return (<TextComponent key={key} type="text" text={node.content}>
|
|
128
|
+
{node.content}
|
|
129
|
+
</TextComponent>);
|
|
130
|
+
}
|
|
131
|
+
if (node.type === 'bold') {
|
|
132
|
+
const BoldComponent = components.bold;
|
|
133
|
+
return (<BoldComponent key={key} type="bold">
|
|
134
|
+
{renderInlineNodes(node.children, components, key)}
|
|
135
|
+
</BoldComponent>);
|
|
136
|
+
}
|
|
137
|
+
if (node.type === 'italic') {
|
|
138
|
+
const ItalicComponent = components.italic;
|
|
139
|
+
return (<ItalicComponent key={key} type="italic">
|
|
140
|
+
{renderInlineNodes(node.children, components, key)}
|
|
141
|
+
</ItalicComponent>);
|
|
142
|
+
}
|
|
143
|
+
if (node.type === 'strikethrough') {
|
|
144
|
+
const StrikethroughComponent = components.strikethrough;
|
|
145
|
+
return (<StrikethroughComponent key={key} type="strikethrough">
|
|
146
|
+
{renderInlineNodes(node.children, components, key)}
|
|
147
|
+
</StrikethroughComponent>);
|
|
148
|
+
}
|
|
149
|
+
if (node.type === 'code') {
|
|
150
|
+
const InlineCodeComponent = components.inlineCode;
|
|
151
|
+
return (<InlineCodeComponent key={key} type="inlineCode" text={node.content}>
|
|
152
|
+
{node.content}
|
|
153
|
+
</InlineCodeComponent>);
|
|
154
|
+
}
|
|
155
|
+
if (node.type === 'image') {
|
|
156
|
+
const ImageComponent = components.image;
|
|
157
|
+
return <ImageComponent key={key} type="image" src={node.src} alt={node.alt} title={node.title}/>;
|
|
158
|
+
}
|
|
159
|
+
const LinkComponent = components.link;
|
|
160
|
+
return (<LinkComponent key={key} type="link" href={node.href}>
|
|
161
|
+
{renderInlineNodes(node.children, components, key)}
|
|
162
|
+
</LinkComponent>);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=MarkdownRenderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownRenderer.js","sourceRoot":"","sources":["../src/MarkdownRenderer.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * Read-only markdown renderer component.\n *\n * Parses a markdown string into an AST and maps each node to a\n * React Native component. Every tag can be overridden via the\n * `components` prop; unspecified tags fall back to defaults\n * defined in `markdownRendererDefaults.tsx`.\n */\n\nimport * as React from 'react';\n\nimport type {\n\tMarkdownComponentMap,\n\tMarkdownInlineNode,\n\tMarkdownRendererProps,\n} from './markdownCore.types';\nimport { parseMarkdown } from './markdownParser';\nimport { DEFAULT_COMPONENTS } from './markdownRendererDefaults';\n\nexport default function MarkdownRenderer({ markdown, components, style, features }: MarkdownRendererProps) {\n\tconst blocks = React.useMemo(() => parseMarkdown(markdown, features), [markdown, features]);\n\tconst componentMap: MarkdownComponentMap = React.useMemo(\n\t\t() => ({\n\t\t\t...DEFAULT_COMPONENTS,\n\t\t\t...components,\n\t\t}),\n\t\t[components],\n\t);\n\n\tconst RootComponent = componentMap.root;\n\n\treturn (\n\t\t<RootComponent type=\"root\" style={style}>\n\t\t\t{blocks.map((block, blockIndex) => {\n\t\t\t\tconst key = `block-${blockIndex}`;\n\n\t\t\t\tif (block.type === 'paragraph') {\n\t\t\t\t\tif (block.children.length === 1 && block.children[0]?.type === 'image') {\n\t\t\t\t\t\tconst imageNode = block.children[0];\n\t\t\t\t\t\tconst ImageComponent = componentMap.image;\n\t\t\t\t\t\treturn <ImageComponent key={key} type=\"image\" src={imageNode.src} alt={imageNode.alt} title={imageNode.title} />;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst ParagraphComponent = componentMap.paragraph;\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<ParagraphComponent key={key} type=\"paragraph\">\n\t\t\t\t\t\t\t{renderInlineNodes(block.children, componentMap, key)}\n\t\t\t\t\t\t</ParagraphComponent>\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (block.type === 'heading') {\n\t\t\t\t\tif (block.level === 1) {\n\t\t\t\t\t\tconst HeadingComponent = componentMap.heading1;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<HeadingComponent key={key} type=\"heading1\" level={1}>\n\t\t\t\t\t\t\t\t{renderInlineNodes(block.children, componentMap, key)}\n\t\t\t\t\t\t\t</HeadingComponent>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (block.level === 2) {\n\t\t\t\t\t\tconst HeadingComponent = componentMap.heading2;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<HeadingComponent key={key} type=\"heading2\" level={2}>\n\t\t\t\t\t\t\t\t{renderInlineNodes(block.children, componentMap, key)}\n\t\t\t\t\t\t\t</HeadingComponent>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (block.level === 3) {\n\t\t\t\t\t\tconst HeadingComponent = componentMap.heading3;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<HeadingComponent key={key} type=\"heading3\" level={3}>\n\t\t\t\t\t\t\t\t{renderInlineNodes(block.children, componentMap, key)}\n\t\t\t\t\t\t\t</HeadingComponent>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (block.level === 4) {\n\t\t\t\t\t\tconst HeadingComponent = componentMap.heading4;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<HeadingComponent key={key} type=\"heading4\" level={4}>\n\t\t\t\t\t\t\t\t{renderInlineNodes(block.children, componentMap, key)}\n\t\t\t\t\t\t\t</HeadingComponent>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (block.level === 5) {\n\t\t\t\t\t\tconst HeadingComponent = componentMap.heading5;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<HeadingComponent key={key} type=\"heading5\" level={5}>\n\t\t\t\t\t\t\t\t{renderInlineNodes(block.children, componentMap, key)}\n\t\t\t\t\t\t\t</HeadingComponent>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst HeadingComponent = componentMap.heading6;\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<HeadingComponent key={key} type=\"heading6\" level={6}>\n\t\t\t\t\t\t\t{renderInlineNodes(block.children, componentMap, key)}\n\t\t\t\t\t\t</HeadingComponent>\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (block.type === 'codeBlock') {\n\t\t\t\t\tconst CodeBlockComponent = componentMap.codeBlock;\n\t\t\t\t\treturn <CodeBlockComponent key={key} type=\"codeBlock\" text={block.content} language={block.language} />;\n\t\t\t\t}\n\n\t\t\t\tif (block.type === 'blockquote') {\n\t\t\t\t\tconst BlockquoteComponent = componentMap.blockquote;\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<BlockquoteComponent key={key} type=\"blockquote\">\n\t\t\t\t\t\t\t{renderInlineNodes(block.children, componentMap, key)}\n\t\t\t\t\t\t</BlockquoteComponent>\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (block.type === 'horizontalRule') {\n\t\t\t\t\tconst HorizontalRuleComponent = componentMap.horizontalRule;\n\t\t\t\t\treturn <HorizontalRuleComponent key={key} type=\"horizontalRule\" />;\n\t\t\t\t}\n\n\t\t\t\tif (block.type === 'spacer') {\n\t\t\t\t\tconst SpacerComponent = componentMap.spacer;\n\t\t\t\t\treturn <SpacerComponent key={key} type=\"spacer\" />;\n\t\t\t\t}\n\n\t\t\t\tconst ListItemComponent = componentMap.listItem;\n\t\t\t\tconst TextComponent = componentMap.text;\n\n\t\t\t\tif (block.ordered) {\n\t\t\t\t\tconst OrderedListComponent = componentMap.orderedList;\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<OrderedListComponent key={key} type=\"orderedList\" ordered>\n\t\t\t\t\t\t\t{block.items.map((item, itemIndex) => {\n\t\t\t\t\t\t\t\tconst marker = `${itemIndex + 1}. `;\n\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<ListItemComponent key={`${key}-item-${itemIndex}`} type=\"listItem\" ordered index={itemIndex}>\n\t\t\t\t\t\t\t\t\t\t<TextComponent type=\"text\" text={marker}>\n\t\t\t\t\t\t\t\t\t\t\t{marker}\n\t\t\t\t\t\t\t\t\t\t\t{renderInlineNodes(item, componentMap, `${key}-item-${itemIndex}`)}\n\t\t\t\t\t\t\t\t\t\t</TextComponent>\n\t\t\t\t\t\t\t\t\t</ListItemComponent>\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t</OrderedListComponent>\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst UnorderedListComponent = componentMap.unorderedList;\n\t\t\t\treturn (\n\t\t\t\t\t<UnorderedListComponent key={key} type=\"unorderedList\" ordered={false}>\n\t\t\t\t\t\t{block.items.map((item, itemIndex) => {\n\t\t\t\t\t\t\tconst marker = '- ';\n\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<ListItemComponent key={`${key}-item-${itemIndex}`} type=\"listItem\" ordered={false} index={itemIndex}>\n\t\t\t\t\t\t\t\t\t<TextComponent type=\"text\" text={marker}>\n\t\t\t\t\t\t\t\t\t\t{marker}\n\t\t\t\t\t\t\t\t\t\t{renderInlineNodes(item, componentMap, `${key}-item-${itemIndex}`)}\n\t\t\t\t\t\t\t\t\t</TextComponent>\n\t\t\t\t\t\t\t\t</ListItemComponent>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</UnorderedListComponent>\n\t\t\t\t);\n\t\t\t})}\n\t\t</RootComponent>\n\t);\n}\n\n/**\n * Recursively renders an array of inline markdown nodes into React elements.\n * Each node type is mapped to its corresponding component from the component map.\n */\nfunction renderInlineNodes(nodes: MarkdownInlineNode[], components: MarkdownComponentMap, keyPrefix: string): React.ReactNode[] {\n\treturn nodes.map((node, index) => {\n\t\tconst key = `${keyPrefix}-inline-${index}`;\n\n\t\tif (node.type === 'text') {\n\t\t\tconst TextComponent = components.text;\n\t\t\treturn (\n\t\t\t\t<TextComponent key={key} type=\"text\" text={node.content}>\n\t\t\t\t\t{node.content}\n\t\t\t\t</TextComponent>\n\t\t\t);\n\t\t}\n\n\t\tif (node.type === 'bold') {\n\t\t\tconst BoldComponent = components.bold;\n\t\t\treturn (\n\t\t\t\t<BoldComponent key={key} type=\"bold\">\n\t\t\t\t\t{renderInlineNodes(node.children, components, key)}\n\t\t\t\t</BoldComponent>\n\t\t\t);\n\t\t}\n\n\t\tif (node.type === 'italic') {\n\t\t\tconst ItalicComponent = components.italic;\n\t\t\treturn (\n\t\t\t\t<ItalicComponent key={key} type=\"italic\">\n\t\t\t\t\t{renderInlineNodes(node.children, components, key)}\n\t\t\t\t</ItalicComponent>\n\t\t\t);\n\t\t}\n\n\t\tif (node.type === 'strikethrough') {\n\t\t\tconst StrikethroughComponent = components.strikethrough;\n\t\t\treturn (\n\t\t\t\t<StrikethroughComponent key={key} type=\"strikethrough\">\n\t\t\t\t\t{renderInlineNodes(node.children, components, key)}\n\t\t\t\t</StrikethroughComponent>\n\t\t\t);\n\t\t}\n\n\t\tif (node.type === 'code') {\n\t\t\tconst InlineCodeComponent = components.inlineCode;\n\t\t\treturn (\n\t\t\t\t<InlineCodeComponent key={key} type=\"inlineCode\" text={node.content}>\n\t\t\t\t\t{node.content}\n\t\t\t\t</InlineCodeComponent>\n\t\t\t);\n\t\t}\n\n\t\tif (node.type === 'image') {\n\t\t\tconst ImageComponent = components.image;\n\t\t\treturn <ImageComponent key={key} type=\"image\" src={node.src} alt={node.alt} title={node.title} />;\n\t\t}\n\n\t\tconst LinkComponent = components.link;\n\t\treturn (\n\t\t\t<LinkComponent key={key} type=\"link\" href={node.href}>\n\t\t\t\t{renderInlineNodes(node.children, components, key)}\n\t\t\t</LinkComponent>\n\t\t);\n\t});\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { MarkdownTextInputProps } from './markdownCore.types';
|
|
3
|
+
/**
|
|
4
|
+
* Pure markdown text input with integrated live preview.
|
|
5
|
+
*
|
|
6
|
+
* Requires an `editor` handle from `useMarkdownEditor()`.
|
|
7
|
+
* Does NOT render a toolbar, use `MarkdownToolbar` separately.
|
|
8
|
+
*/
|
|
9
|
+
export default function MarkdownTextInput({ editor, style, textInputStyle, segmentComponents, ...textInputProps }: MarkdownTextInputProps): React.JSX.Element;
|
|
10
|
+
//# sourceMappingURL=MarkdownTextInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownTextInput.d.ts","sourceRoot":"","sources":["../src/MarkdownTextInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAyGnE;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,GAAG,cAAc,EAAE,EAAE,sBAAsB,qBA4HxI"}
|