rn-rich-text-editor 0.0.18 → 1.0.0
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/README.md +623 -19
- package/package.json +1 -1
- package/src/editor/createHTML.js +3 -0
package/README.md
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
# React Native Rich Editor
|
|
1
|
+
# React Native Rich Text Editor
|
|
2
2
|
|
|
3
|
-
A rich text editor component for React Native
|
|
3
|
+
A powerful, feature-rich text editor component for React Native built with WebView. Perfect for creating rich text editing experiences in your React Native applications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✨ **Rich Text Formatting**: Bold, italic, underline, strikethrough, and more
|
|
8
|
+
- 📝 **Text Styling**: Font size, font family, text color, highlight color, line height
|
|
9
|
+
- 📋 **Lists**: Bullet lists, ordered lists, and checkbox lists
|
|
10
|
+
- 🔗 **Media Support**: Insert images and videos
|
|
11
|
+
- 📐 **Alignment**: Left, center, right, and justify alignment
|
|
12
|
+
- 📑 **Headings**: Support for H1-H6 headings
|
|
13
|
+
- 💻 **Code Blocks**: Insert code blocks and inline code
|
|
14
|
+
- 🔄 **Undo/Redo**: Full undo and redo support
|
|
15
|
+
- ⌨️ **Keyboard Management**: Built-in keyboard handling for iOS and Android
|
|
16
|
+
- 🎨 **Customizable**: Fully customizable toolbar and editor styles
|
|
17
|
+
- 📱 **Cross-Platform**: Works on both iOS and Android
|
|
18
|
+
- ♿ **Accessible**: Built with accessibility in mind
|
|
4
19
|
|
|
5
20
|
## Installation
|
|
6
21
|
|
|
@@ -8,44 +23,633 @@ A rich text editor component for React Native using WebView.
|
|
|
8
23
|
npm install rn-rich-text-editor
|
|
9
24
|
```
|
|
10
25
|
|
|
11
|
-
|
|
26
|
+
### Peer Dependencies
|
|
27
|
+
|
|
28
|
+
Make sure you have these installed:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install react react-native react-native-webview
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
12
35
|
|
|
13
36
|
```tsx
|
|
14
|
-
import
|
|
37
|
+
import React, { useRef } from 'react';
|
|
38
|
+
import { View } from 'react-native';
|
|
39
|
+
import { Editor, Toolbar } from 'rn-rich-text-editor';
|
|
15
40
|
|
|
16
41
|
function App() {
|
|
42
|
+
const editorRef = useRef(null);
|
|
43
|
+
|
|
17
44
|
return (
|
|
18
|
-
|
|
19
|
-
<Editor
|
|
20
|
-
|
|
21
|
-
|
|
45
|
+
<View style={{ flex: 1 }}>
|
|
46
|
+
<Editor
|
|
47
|
+
ref={editorRef}
|
|
48
|
+
placeholder="Start typing..."
|
|
49
|
+
onChange={(html) => console.log('Content changed:', html)}
|
|
50
|
+
/>
|
|
51
|
+
<Toolbar editor={editorRef} />
|
|
52
|
+
</View>
|
|
22
53
|
);
|
|
23
54
|
}
|
|
24
55
|
```
|
|
25
56
|
|
|
26
|
-
##
|
|
57
|
+
## Components
|
|
27
58
|
|
|
28
59
|
### Editor
|
|
29
60
|
|
|
30
|
-
|
|
61
|
+
The main editor component that provides the rich text editing interface.
|
|
31
62
|
|
|
32
|
-
|
|
33
|
-
- `onMessage?: (event: any) => void` - Callback for editor messages
|
|
63
|
+
#### Props
|
|
34
64
|
|
|
35
|
-
|
|
36
|
-
|
|
65
|
+
| Prop | Type | Default | Description |
|
|
66
|
+
|------|------|---------|-------------|
|
|
67
|
+
| `ref` | `RefObject<EditorRef>` | - | Reference to access editor methods |
|
|
68
|
+
| `contentInset` | `object` | `{}` | Content inset for the editor |
|
|
69
|
+
| `style` | `object` | `{}` | Custom styles for the editor container |
|
|
70
|
+
| `placeholder` | `string` | `''` | Placeholder text shown when editor is empty |
|
|
71
|
+
| `initialContentHTML` | `string` | `''` | Initial HTML content to load |
|
|
72
|
+
| `initialFocus` | `boolean` | `false` | Whether to focus the editor on mount |
|
|
73
|
+
| `disabled` | `boolean` | `false` | Disable the editor |
|
|
74
|
+
| `readOnly` | `boolean` | `false` | Make the editor read-only |
|
|
75
|
+
| `useContainer` | `boolean` | `true` | Use a container wrapper with height |
|
|
76
|
+
| `pasteAsPlainText` | `boolean` | `false` | Paste content as plain text |
|
|
77
|
+
| `autoCapitalize` | `string` | `'off'` | Auto-capitalization setting |
|
|
78
|
+
| `defaultParagraphSeparator` | `string` | `'div'` | Default paragraph separator (`'div'` or `'p'`) |
|
|
79
|
+
| `editorInitializedCallback` | `() => void` | `() => {}` | Callback when editor is initialized |
|
|
80
|
+
| `initialHeight` | `number` | `0` | Initial height of the editor (0 = auto) |
|
|
81
|
+
| `dataDetectorTypes` | `string[]` | `['none']` | Data detector types for links, phone numbers, etc. |
|
|
82
|
+
| `editorStyle` | `object` | - | Custom editor styles (see EditorStyle below) |
|
|
83
|
+
| `html` | `string \| { html: string }` | - | HTML content (alternative to `initialContentHTML`) |
|
|
84
|
+
| `onFocus` | `() => void` | - | Callback when editor gains focus |
|
|
85
|
+
| `onBlur` | `() => void` | - | Callback when editor loses focus |
|
|
86
|
+
| `onChange` | `(html: string) => void` | - | Callback when content changes |
|
|
87
|
+
| `onPaste` | `(data: unknown) => void` | - | Callback when content is pasted |
|
|
88
|
+
| `onKeyUp` | `(data: unknown) => void` | - | Callback on key up event |
|
|
89
|
+
| `onKeyDown` | `(data: unknown) => void` | - | Callback on key down event |
|
|
90
|
+
| `onInput` | `(data: unknown) => void` | - | Callback on input event |
|
|
91
|
+
| `onMessage` | `(message: unknown) => void` | - | Callback for editor messages |
|
|
92
|
+
| `onCursorPosition` | `(offsetY: number) => void` | - | Callback when cursor position changes |
|
|
93
|
+
| `onLink` | `(data: unknown) => void` | - | Callback when a link is clicked |
|
|
94
|
+
| `onHeightChange` | `(height: number) => void` | - | Callback when editor height changes |
|
|
95
|
+
| `errorMessage` | `string` | - | Error message to display |
|
|
96
|
+
|
|
97
|
+
#### EditorStyle Object
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
{
|
|
101
|
+
backgroundColor?: string; // Editor background color
|
|
102
|
+
color?: string; // Text color
|
|
103
|
+
placeholderColor?: string; // Placeholder text color
|
|
104
|
+
caretColor?: string; // Cursor/caret color
|
|
105
|
+
initialCSSText?: string; // Initial CSS styles
|
|
106
|
+
cssText?: string; // Additional CSS styles
|
|
107
|
+
contentCSSText?: string; // Content area CSS styles
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### EditorRef Methods
|
|
112
|
+
|
|
113
|
+
Access these methods through the editor ref:
|
|
114
|
+
|
|
115
|
+
| Method | Parameters | Description |
|
|
116
|
+
|--------|------------|-------------|
|
|
117
|
+
| `setContentHTML` | `(html: string)` | Set the editor content HTML |
|
|
118
|
+
| `getContentHtml` | `()` | Get the current content HTML (returns Promise) |
|
|
119
|
+
| `setPlaceholder` | `(placeholder: string)` | Set the placeholder text |
|
|
120
|
+
| `setContentStyle` | `(styles: object)` | Update editor styles dynamically |
|
|
121
|
+
| `setDisable` | `(disabled: boolean)` | Enable/disable the editor |
|
|
122
|
+
| `focusContentEditor` | `()` | Focus the editor |
|
|
123
|
+
| `blurContentEditor` | `()` | Blur the editor |
|
|
124
|
+
| `showAndroidKeyboard` | `()` | Show keyboard on Android |
|
|
125
|
+
| `dismissKeyboard` | `()` | Dismiss the keyboard |
|
|
126
|
+
| `insertText` | `(text: string)` | Insert plain text at cursor |
|
|
127
|
+
| `insertHTML` | `(html: string)` | Insert HTML at cursor |
|
|
128
|
+
| `insertImage` | `(attributes: object, style?: object)` | Insert an image |
|
|
129
|
+
| `insertVideo` | `(attributes: object, style?: object)` | Insert a video |
|
|
130
|
+
| `insertLink` | `(title: string, url: string)` | Insert a link |
|
|
131
|
+
| `setFontSize` | `(size: unknown)` | Set font size |
|
|
132
|
+
| `setFontName` | `(name: string)` | Set font family |
|
|
133
|
+
| `setForeColor` | `(color: string)` | Set text color |
|
|
134
|
+
| `setHiliteColor` | `(color: string)` | Set highlight/background color |
|
|
135
|
+
| `setLineHeight` | `(value: number \| string)` | Set line height |
|
|
136
|
+
| `command` | `(command: unknown)` | Execute a command |
|
|
137
|
+
| `commandDOM` | `(command: unknown)` | Execute a DOM command |
|
|
138
|
+
| `injectJavascript` | `(script: string)` | Inject custom JavaScript |
|
|
139
|
+
| `registerToolbar` | `(listener: (items: string[]) => void)` | Register toolbar selection listener |
|
|
140
|
+
|
|
141
|
+
#### Properties
|
|
142
|
+
|
|
143
|
+
| Property | Type | Description |
|
|
144
|
+
|----------|------|-------------|
|
|
145
|
+
| `isKeyboardOpen` | `boolean` | Whether the keyboard is currently open |
|
|
37
146
|
|
|
38
147
|
### Toolbar
|
|
39
148
|
|
|
40
|
-
|
|
149
|
+
A customizable toolbar component that provides formatting buttons for the editor.
|
|
150
|
+
|
|
151
|
+
#### Props
|
|
152
|
+
|
|
153
|
+
| Prop | Type | Default | Description |
|
|
154
|
+
|------|------|---------|-------------|
|
|
155
|
+
| `editor` | `{ current: EditorRef \| null }` | - | Editor ref object (use `editor={editorRef}`) |
|
|
156
|
+
| `getEditor` | `() => EditorRef \| null` | - | Function that returns editor ref (alternative to `editor`) |
|
|
157
|
+
| `actions` | `string[]` | `defaultActions` | Array of action keys to display (see Actions below) |
|
|
158
|
+
| `disabled` | `boolean` | `false` | Disable all toolbar buttons |
|
|
159
|
+
| `readOnly` | `boolean` | `false` | Hide toolbar when read-only |
|
|
160
|
+
| `iconTint` | `string` | `'#71787F'` | Default icon color |
|
|
161
|
+
| `selectedIconTint` | `string` | - | Selected icon color |
|
|
162
|
+
| `disabledIconTint` | `string` | - | Disabled icon color |
|
|
163
|
+
| `iconSize` | `number` | `20` | Size of toolbar icons |
|
|
164
|
+
| `iconGap` | `number` | `16` | Gap between toolbar icons |
|
|
165
|
+
| `style` | `object` | - | Custom styles for toolbar container |
|
|
166
|
+
| `itemStyle` | `object` | - | Custom styles for toolbar items |
|
|
167
|
+
| `selectedButtonStyle` | `object` | - | Styles for selected buttons |
|
|
168
|
+
| `unselectedButtonStyle` | `object` | - | Styles for unselected buttons |
|
|
169
|
+
| `disabledButtonStyle` | `object` | - | Styles for disabled buttons |
|
|
170
|
+
| `iconMap` | `Record<string, unknown>` | - | Custom icon map (override default icons) |
|
|
171
|
+
| `renderAction` | `(action: string, selected: boolean) => React.ReactElement` | - | Custom render function for actions |
|
|
172
|
+
| `onPressAddImage` | `() => void` | - | Callback when image button is pressed |
|
|
173
|
+
| `onInsertLink` | `() => void` | - | Callback when link button is pressed |
|
|
174
|
+
| `insertVideo` | `() => void` | - | Callback when video button is pressed |
|
|
175
|
+
| `flatContainerStyle` | `object` | - | Styles for the FlatList container |
|
|
176
|
+
| `horizontal` | `boolean` | `true` | Whether to display toolbar horizontally |
|
|
177
|
+
| `children` | `React.ReactNode` | - | Additional content to render in toolbar |
|
|
178
|
+
|
|
179
|
+
#### Custom Action Handlers
|
|
180
|
+
|
|
181
|
+
You can provide custom handlers for specific actions by passing them as props:
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
<Toolbar
|
|
185
|
+
editor={editorRef}
|
|
186
|
+
fontSize={() => {
|
|
187
|
+
// Custom font size handler
|
|
188
|
+
editorRef.current?.setFontSize(18);
|
|
189
|
+
}}
|
|
190
|
+
foreColor={() => {
|
|
191
|
+
// Custom color picker handler
|
|
192
|
+
editorRef.current?.setForeColor('#FF0000');
|
|
193
|
+
}}
|
|
194
|
+
lineHeight={() => {
|
|
195
|
+
// Custom line height handler
|
|
196
|
+
editorRef.current?.setLineHeight(1.5);
|
|
197
|
+
}}
|
|
198
|
+
/>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Actions
|
|
202
|
+
|
|
203
|
+
The toolbar uses action constants to identify different formatting options. Import them from the package:
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
import { actions } from 'rn-rich-text-editor';
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Available Actions
|
|
210
|
+
|
|
211
|
+
| Action | Constant | Description |
|
|
212
|
+
|--------|----------|-------------|
|
|
213
|
+
| **Text Formatting** |
|
|
214
|
+
| Bold | `actions.setBold` | Make text bold |
|
|
215
|
+
| Italic | `actions.setItalic` | Make text italic |
|
|
216
|
+
| Underline | `actions.setUnderline` | Underline text |
|
|
217
|
+
| Strikethrough | `actions.setStrikethrough` | Strikethrough text |
|
|
218
|
+
| Subscript | `actions.setSubscript` | Subscript text |
|
|
219
|
+
| Superscript | `actions.setSuperscript` | Superscript text |
|
|
220
|
+
| Remove Format | `actions.removeFormat` | Remove all formatting |
|
|
221
|
+
| **Headings** |
|
|
222
|
+
| Heading 1 | `actions.heading1` | Apply H1 heading |
|
|
223
|
+
| Heading 2 | `actions.heading2` | Apply H2 heading |
|
|
224
|
+
| Heading 3 | `actions.heading3` | Apply H3 heading |
|
|
225
|
+
| Heading 4 | `actions.heading4` | Apply H4 heading |
|
|
226
|
+
| Heading 5 | `actions.heading5` | Apply H5 heading |
|
|
227
|
+
| Heading 6 | `actions.heading6` | Apply H6 heading |
|
|
228
|
+
| Paragraph | `actions.setParagraph` | Apply paragraph style |
|
|
229
|
+
| **Alignment** |
|
|
230
|
+
| Align Left | `actions.alignLeft` | Align text left |
|
|
231
|
+
| Align Center | `actions.alignCenter` | Align text center |
|
|
232
|
+
| Align Right | `actions.alignRight` | Align text right |
|
|
233
|
+
| Align Full | `actions.alignFull` | Justify text |
|
|
234
|
+
| Align (Cycle) | `actions.align` | Cycle through alignments |
|
|
235
|
+
| **Lists** |
|
|
236
|
+
| Bullet List | `actions.insertBulletsList` | Insert unordered list |
|
|
237
|
+
| Ordered List | `actions.insertOrderedList` | Insert ordered list |
|
|
238
|
+
| Checkbox List | `actions.checkboxList` | Insert checkbox list |
|
|
239
|
+
| Indent | `actions.indent` | Increase indent |
|
|
240
|
+
| Outdent | `actions.outdent` | Decrease indent |
|
|
241
|
+
| **Media** |
|
|
242
|
+
| Insert Image | `actions.insertImage` | Insert image |
|
|
243
|
+
| Insert Video | `actions.insertVideo` | Insert video |
|
|
244
|
+
| Insert Link | `actions.insertLink` | Insert hyperlink |
|
|
245
|
+
| **Text Styling** |
|
|
246
|
+
| Font Size | `actions.fontSize` | Change font size |
|
|
247
|
+
| Font Name | `actions.fontName` | Change font family |
|
|
248
|
+
| Text Color | `actions.foreColor` | Change text color |
|
|
249
|
+
| Highlight Color | `actions.hiliteColor` | Change highlight color |
|
|
250
|
+
| Line Height | `actions.lineHeight` | Change line height |
|
|
251
|
+
| **Other** |
|
|
252
|
+
| Code Block | `actions.code` | Insert code block |
|
|
253
|
+
| Blockquote | `actions.blockquote` | Insert blockquote |
|
|
254
|
+
| Horizontal Rule | `actions.setHR` | Insert horizontal line |
|
|
255
|
+
| Line | `actions.line` | Insert line break |
|
|
256
|
+
| Undo | `actions.undo` | Undo last action |
|
|
257
|
+
| Redo | `actions.redo` | Redo last action |
|
|
258
|
+
| Table | `actions.table` | Insert table |
|
|
259
|
+
| Keyboard | `actions.keyboard` | Toggle keyboard |
|
|
260
|
+
|
|
261
|
+
### Default Actions
|
|
262
|
+
|
|
263
|
+
The toolbar comes with a default set of actions:
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
import { defaultActions } from 'rn-rich-text-editor';
|
|
267
|
+
|
|
268
|
+
// Default actions include:
|
|
269
|
+
// - keyboard
|
|
270
|
+
// - setBold
|
|
271
|
+
// - setItalic
|
|
272
|
+
// - setUnderline
|
|
273
|
+
// - removeFormat
|
|
274
|
+
// - insertBulletsList
|
|
275
|
+
// - indent
|
|
276
|
+
// - outdent
|
|
277
|
+
// - insertLink
|
|
278
|
+
// - lineHeight
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Usage Examples
|
|
282
|
+
|
|
283
|
+
### Basic Editor with Toolbar
|
|
284
|
+
|
|
285
|
+
```tsx
|
|
286
|
+
import React, { useRef } from 'react';
|
|
287
|
+
import { View, StyleSheet } from 'react-native';
|
|
288
|
+
import { Editor, Toolbar } from 'rn-rich-text-editor';
|
|
289
|
+
|
|
290
|
+
function RichTextEditor() {
|
|
291
|
+
const editorRef = useRef(null);
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<View style={styles.container}>
|
|
295
|
+
<Editor
|
|
296
|
+
ref={editorRef}
|
|
297
|
+
placeholder="Start typing..."
|
|
298
|
+
onChange={(html) => {
|
|
299
|
+
console.log('Content:', html);
|
|
300
|
+
}}
|
|
301
|
+
style={styles.editor}
|
|
302
|
+
/>
|
|
303
|
+
<Toolbar editor={editorRef} />
|
|
304
|
+
</View>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const styles = StyleSheet.create({
|
|
309
|
+
container: {
|
|
310
|
+
flex: 1,
|
|
311
|
+
backgroundColor: '#fff',
|
|
312
|
+
},
|
|
313
|
+
editor: {
|
|
314
|
+
backgroundColor: '#fff',
|
|
315
|
+
borderColor: '#ddd',
|
|
316
|
+
borderWidth: 1,
|
|
317
|
+
minHeight: 200,
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Custom Toolbar Actions
|
|
323
|
+
|
|
324
|
+
```tsx
|
|
325
|
+
import React, { useRef } from 'react';
|
|
326
|
+
import { Editor, Toolbar, actions } from 'rn-rich-text-editor';
|
|
327
|
+
|
|
328
|
+
function CustomToolbar() {
|
|
329
|
+
const editorRef = useRef(null);
|
|
41
330
|
|
|
42
|
-
|
|
43
|
-
|
|
331
|
+
const customActions = [
|
|
332
|
+
actions.keyboard,
|
|
333
|
+
actions.setBold,
|
|
334
|
+
actions.setItalic,
|
|
335
|
+
actions.setUnderline,
|
|
336
|
+
actions.heading1,
|
|
337
|
+
actions.insertBulletsList,
|
|
338
|
+
actions.insertOrderedList,
|
|
339
|
+
actions.insertLink,
|
|
340
|
+
actions.insertImage,
|
|
341
|
+
];
|
|
44
342
|
|
|
45
|
-
|
|
343
|
+
return (
|
|
344
|
+
<>
|
|
345
|
+
<Editor ref={editorRef} />
|
|
346
|
+
<Toolbar editor={editorRef} actions={customActions} />
|
|
347
|
+
</>
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Inserting Content Programmatically
|
|
353
|
+
|
|
354
|
+
```tsx
|
|
355
|
+
import React, { useRef } from 'react';
|
|
356
|
+
import { View, Button } from 'react-native';
|
|
357
|
+
import { Editor } from 'rn-rich-text-editor';
|
|
358
|
+
|
|
359
|
+
function ProgrammaticEditor() {
|
|
360
|
+
const editorRef = useRef(null);
|
|
361
|
+
|
|
362
|
+
const insertImage = () => {
|
|
363
|
+
editorRef.current?.insertImage(
|
|
364
|
+
{ src: 'https://example.com/image.jpg', alt: 'Image' },
|
|
365
|
+
{ width: '100%', height: 'auto' }
|
|
366
|
+
);
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const insertLink = () => {
|
|
370
|
+
editorRef.current?.insertLink('Click here', 'https://example.com');
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const insertText = () => {
|
|
374
|
+
editorRef.current?.insertText('Hello World!');
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
return (
|
|
378
|
+
<View>
|
|
379
|
+
<Editor ref={editorRef} />
|
|
380
|
+
<Button title="Insert Image" onPress={insertImage} />
|
|
381
|
+
<Button title="Insert Link" onPress={insertLink} />
|
|
382
|
+
<Button title="Insert Text" onPress={insertText} />
|
|
383
|
+
</View>
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Styling the Editor
|
|
389
|
+
|
|
390
|
+
```tsx
|
|
391
|
+
import React, { useRef } from 'react';
|
|
392
|
+
import { Editor } from 'rn-rich-text-editor';
|
|
393
|
+
|
|
394
|
+
function StyledEditor() {
|
|
395
|
+
const editorRef = useRef(null);
|
|
396
|
+
|
|
397
|
+
return (
|
|
398
|
+
<Editor
|
|
399
|
+
ref={editorRef}
|
|
400
|
+
editorStyle={{
|
|
401
|
+
backgroundColor: '#f5f5f5',
|
|
402
|
+
color: '#333',
|
|
403
|
+
placeholderColor: '#999',
|
|
404
|
+
caretColor: '#007AFF',
|
|
405
|
+
contentCSSText: `
|
|
406
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
407
|
+
font-size: 16px;
|
|
408
|
+
line-height: 1.6;
|
|
409
|
+
`,
|
|
410
|
+
}}
|
|
411
|
+
/>
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Custom Toolbar Styling
|
|
417
|
+
|
|
418
|
+
```tsx
|
|
419
|
+
import React, { useRef } from 'react';
|
|
420
|
+
import { Editor, Toolbar } from 'rn-rich-text-editor';
|
|
46
421
|
|
|
47
|
-
|
|
422
|
+
function CustomStyledToolbar() {
|
|
423
|
+
const editorRef = useRef(null);
|
|
424
|
+
|
|
425
|
+
return (
|
|
426
|
+
<>
|
|
427
|
+
<Editor ref={editorRef} />
|
|
428
|
+
<Toolbar
|
|
429
|
+
editor={editorRef}
|
|
430
|
+
iconTint="#333"
|
|
431
|
+
selectedIconTint="#007AFF"
|
|
432
|
+
iconSize={24}
|
|
433
|
+
iconGap={20}
|
|
434
|
+
style={{
|
|
435
|
+
backgroundColor: '#f0f0f0',
|
|
436
|
+
paddingVertical: 8,
|
|
437
|
+
}}
|
|
438
|
+
selectedButtonStyle={{
|
|
439
|
+
backgroundColor: '#e0e0e0',
|
|
440
|
+
borderRadius: 4,
|
|
441
|
+
}}
|
|
442
|
+
/>
|
|
443
|
+
</>
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Handling Image Insertion
|
|
449
|
+
|
|
450
|
+
```tsx
|
|
451
|
+
import React, { useRef, useState } from 'react';
|
|
452
|
+
import { Editor, Toolbar, actions } from 'rn-rich-text-editor';
|
|
453
|
+
import { ImagePicker } from 'react-native-image-picker';
|
|
454
|
+
|
|
455
|
+
function ImageEditor() {
|
|
456
|
+
const editorRef = useRef(null);
|
|
457
|
+
const [html, setHtml] = useState('');
|
|
458
|
+
|
|
459
|
+
const handleAddImage = () => {
|
|
460
|
+
ImagePicker.launchImageLibrary({}, (response) => {
|
|
461
|
+
if (response.assets && response.assets[0]) {
|
|
462
|
+
const imageUri = response.assets[0].uri;
|
|
463
|
+
editorRef.current?.insertImage(
|
|
464
|
+
{ src: imageUri, alt: 'Image' },
|
|
465
|
+
{ width: '100%', height: 'auto' }
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
return (
|
|
472
|
+
<>
|
|
473
|
+
<Editor
|
|
474
|
+
ref={editorRef}
|
|
475
|
+
onChange={setHtml}
|
|
476
|
+
initialContentHTML={html}
|
|
477
|
+
/>
|
|
478
|
+
<Toolbar
|
|
479
|
+
editor={editorRef}
|
|
480
|
+
onPressAddImage={handleAddImage}
|
|
481
|
+
/>
|
|
482
|
+
</>
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Read-Only Mode
|
|
488
|
+
|
|
489
|
+
```tsx
|
|
490
|
+
import React from 'react';
|
|
491
|
+
import { Editor } from 'rn-rich-text-editor';
|
|
492
|
+
|
|
493
|
+
function ReadOnlyViewer() {
|
|
494
|
+
const htmlContent = '<p>This is read-only content</p>';
|
|
495
|
+
|
|
496
|
+
return (
|
|
497
|
+
<Editor
|
|
498
|
+
readOnly={true}
|
|
499
|
+
initialContentHTML={htmlContent}
|
|
500
|
+
editorStyle={{
|
|
501
|
+
backgroundColor: '#fff',
|
|
502
|
+
color: '#333',
|
|
503
|
+
}}
|
|
504
|
+
/>
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Getting Content
|
|
510
|
+
|
|
511
|
+
```tsx
|
|
512
|
+
import React, { useRef } from 'react';
|
|
513
|
+
import { Button } from 'react-native';
|
|
514
|
+
import { Editor } from 'rn-rich-text-editor';
|
|
515
|
+
|
|
516
|
+
function ContentGetter() {
|
|
517
|
+
const editorRef = useRef(null);
|
|
518
|
+
|
|
519
|
+
const getContent = async () => {
|
|
520
|
+
try {
|
|
521
|
+
const html = await editorRef.current?.getContentHtml();
|
|
522
|
+
console.log('Editor content:', html);
|
|
523
|
+
// Use html for saving, sending, etc.
|
|
524
|
+
} catch (error) {
|
|
525
|
+
console.error('Error getting content:', error);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
return (
|
|
530
|
+
<>
|
|
531
|
+
<Editor ref={editorRef} />
|
|
532
|
+
<Button title="Get Content" onPress={getContent} />
|
|
533
|
+
</>
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
## Advanced Usage
|
|
539
|
+
|
|
540
|
+
### Custom Icon Map
|
|
541
|
+
|
|
542
|
+
```tsx
|
|
543
|
+
import React, { useRef } from 'react';
|
|
544
|
+
import { Editor, Toolbar, actions } from 'rn-rich-text-editor';
|
|
545
|
+
import { Image } from 'react-native';
|
|
546
|
+
|
|
547
|
+
function CustomIcons() {
|
|
548
|
+
const editorRef = useRef(null);
|
|
549
|
+
|
|
550
|
+
const iconMap = {
|
|
551
|
+
[actions.setBold]: require('./assets/custom-bold.png'),
|
|
552
|
+
[actions.setItalic]: require('./assets/custom-italic.png'),
|
|
553
|
+
// ... more custom icons
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
return (
|
|
557
|
+
<>
|
|
558
|
+
<Editor ref={editorRef} />
|
|
559
|
+
<Toolbar editor={editorRef} iconMap={iconMap} />
|
|
560
|
+
</>
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### Custom Action Renderer
|
|
566
|
+
|
|
567
|
+
```tsx
|
|
568
|
+
import React, { useRef } from 'react';
|
|
569
|
+
import { Text, TouchableOpacity } from 'react-native';
|
|
570
|
+
import { Editor, Toolbar, actions } from 'rn-rich-text-editor';
|
|
571
|
+
|
|
572
|
+
function CustomRenderer() {
|
|
573
|
+
const editorRef = useRef(null);
|
|
574
|
+
|
|
575
|
+
const renderAction = (action, selected) => {
|
|
576
|
+
if (action === actions.setBold) {
|
|
577
|
+
return (
|
|
578
|
+
<TouchableOpacity
|
|
579
|
+
style={{
|
|
580
|
+
padding: 8,
|
|
581
|
+
backgroundColor: selected ? '#007AFF' : 'transparent',
|
|
582
|
+
}}
|
|
583
|
+
onPress={() => editorRef.current?.sendAction(action, 'result')}
|
|
584
|
+
>
|
|
585
|
+
<Text style={{ fontWeight: 'bold' }}>B</Text>
|
|
586
|
+
</TouchableOpacity>
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
// Return null to use default rendering
|
|
590
|
+
return null;
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
return (
|
|
594
|
+
<>
|
|
595
|
+
<Editor ref={editorRef} />
|
|
596
|
+
<Toolbar editor={editorRef} renderAction={renderAction} />
|
|
597
|
+
</>
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
## TypeScript Support
|
|
603
|
+
|
|
604
|
+
This package includes TypeScript definitions. Import types as needed:
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
import { Editor, Toolbar, EditorRef, EditorProps, ToolbarProps, actions } from 'rn-rich-text-editor';
|
|
608
|
+
|
|
609
|
+
const editorRef = useRef<EditorRef>(null);
|
|
610
|
+
|
|
611
|
+
const editorProps: EditorProps = {
|
|
612
|
+
placeholder: 'Type here...',
|
|
613
|
+
onChange: (html) => console.log(html),
|
|
614
|
+
};
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
## Platform-Specific Notes
|
|
618
|
+
|
|
619
|
+
### iOS
|
|
620
|
+
- Uses WebKit WebView
|
|
621
|
+
- Keyboard handling is automatic
|
|
622
|
+
- Supports all features
|
|
623
|
+
|
|
624
|
+
### Android
|
|
625
|
+
- Uses Android WebView
|
|
626
|
+
- May require additional keyboard handling
|
|
627
|
+
- All features supported
|
|
628
|
+
|
|
629
|
+
## Troubleshooting
|
|
630
|
+
|
|
631
|
+
### Editor not focusing
|
|
632
|
+
- Ensure `initialFocus` is set to `true` if needed
|
|
633
|
+
- Call `editorRef.current?.focusContentEditor()` programmatically
|
|
634
|
+
- On Android, use `showAndroidKeyboard()` before focusing
|
|
635
|
+
|
|
636
|
+
### Content not updating
|
|
637
|
+
- Use `onChange` callback instead of polling `getContentHtml()`
|
|
638
|
+
- Ensure `initialContentHTML` is set correctly on mount
|
|
639
|
+
|
|
640
|
+
### Toolbar not responding
|
|
641
|
+
- Verify `editor` prop is correctly passed with ref object
|
|
642
|
+
- Check that editor is initialized before toolbar interactions
|
|
643
|
+
- Ensure `disabled` prop is not set to `true`
|
|
644
|
+
|
|
645
|
+
## Contributing
|
|
646
|
+
|
|
647
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
48
648
|
|
|
49
649
|
## License
|
|
50
650
|
|
|
51
651
|
MIT
|
|
652
|
+
|
|
653
|
+
## Support
|
|
654
|
+
|
|
655
|
+
For issues and feature requests, please visit the [GitHub repository](https://github.com/vishaal2002/rn-rich-text-editor).
|
package/package.json
CHANGED
package/src/editor/createHTML.js
CHANGED
|
@@ -757,12 +757,15 @@ function createHTML(options = {}) {
|
|
|
757
757
|
|
|
758
758
|
/**
|
|
759
759
|
* Escapes a string for safe embedding inside a JavaScript double-quoted string in HTML.
|
|
760
|
+
* Newlines and quotes must be escaped or the script will be invalid.
|
|
760
761
|
*/
|
|
761
762
|
function escapeForScript(s) {
|
|
762
763
|
if (s == null) return '';
|
|
763
764
|
return String(s)
|
|
764
765
|
.replace(/\\/g, '\\\\')
|
|
765
766
|
.replace(/"/g, '\\"')
|
|
767
|
+
.replace(/\r/g, '\\r')
|
|
768
|
+
.replace(/\n/g, '\\n')
|
|
766
769
|
.replace(/\u2028/g, '\\u2028')
|
|
767
770
|
.replace(/\u2029/g, '\\u2029')
|
|
768
771
|
.replace(/<\/script/gi, '<\\/script');
|