rn-rich-text-editor 1.3.2 → 1.3.4
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 +211 -532
- package/package.json +1 -1
- package/src/Editor.js +26 -6
- package/src/editor/createHTML.js +15 -5
package/README.md
CHANGED
|
@@ -1,39 +1,38 @@
|
|
|
1
1
|
# React Native Rich Text Editor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/rn-rich-text-editor)
|
|
4
|
+
[](https://www.npmjs.com/package/rn-rich-text-editor)
|
|
5
|
+
[](https://www.npmjs.com/package/rn-rich-text-editor)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
**A powerful and customizable rich text editor built natively for React Native. Everything included, nothing missing.**
|
|
6
8
|
|
|
7
|
-
|
|
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
|
|
9
|
+
## ✨ Features
|
|
19
10
|
|
|
20
|
-
|
|
11
|
+
- **Rich Formatting**: Bold, italic, underline, strikethrough, subscript, superscript
|
|
12
|
+
- **Text Styling**: Font size, family, colors, highlights, line height
|
|
13
|
+
- **Lists & Structure**: Bullet lists, numbered lists, checkbox lists, headings (H1-H6)
|
|
14
|
+
- **Media**: Insert images and videos with custom styling
|
|
15
|
+
- **Alignment**: Left, center, right, justify with cycle button
|
|
16
|
+
- **Code & Blocks**: Code blocks, blockquotes, horizontal rules
|
|
17
|
+
- **Smart Paste**: Automatically preserves HTML formatting from web pages, Word docs, and more
|
|
18
|
+
- **Read-Only Mode**: Display formatted content without editing
|
|
19
|
+
- **Keyboard Management**: Built-in iOS/Android keyboard handling
|
|
20
|
+
- **Fully Customizable**: Toolbar, icons, styles, and behavior
|
|
21
|
+
|
|
22
|
+
## 📦 Installation
|
|
21
23
|
|
|
22
24
|
```bash
|
|
23
25
|
npm install rn-rich-text-editor
|
|
24
26
|
```
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Make sure you have these installed:
|
|
29
|
-
|
|
28
|
+
**Peer dependencies:**
|
|
30
29
|
```bash
|
|
31
30
|
npm install react react-native react-native-webview
|
|
32
31
|
```
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
> **Note**: If you use `react-native-svg-transformer`, SVG icons work automatically. Install `react-native-svg` for runtime.
|
|
35
34
|
|
|
36
|
-
## Quick Start
|
|
35
|
+
## 🚀 Quick Start
|
|
37
36
|
|
|
38
37
|
```tsx
|
|
39
38
|
import React, { useRef } from 'react';
|
|
@@ -48,7 +47,7 @@ function App() {
|
|
|
48
47
|
<Editor
|
|
49
48
|
ref={editorRef}
|
|
50
49
|
placeholder="Start typing..."
|
|
51
|
-
onChange={(html) => console.log('Content
|
|
50
|
+
onChange={(html) => console.log('Content:', html)}
|
|
52
51
|
/>
|
|
53
52
|
<Toolbar editor={editorRef} />
|
|
54
53
|
</View>
|
|
@@ -56,602 +55,282 @@ function App() {
|
|
|
56
55
|
}
|
|
57
56
|
```
|
|
58
57
|
|
|
59
|
-
##
|
|
60
|
-
|
|
61
|
-
### Editor
|
|
62
|
-
|
|
63
|
-
The main editor component that provides the rich text editing interface.
|
|
58
|
+
## 📚 API Reference
|
|
64
59
|
|
|
65
|
-
|
|
60
|
+
### Editor Props
|
|
66
61
|
|
|
67
62
|
| Prop | Type | Default | Description |
|
|
68
63
|
|------|------|---------|-------------|
|
|
69
|
-
| `ref` | `RefObject<EditorRef>` | - |
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
| `html` | `string \| { html: string }` | - | HTML content (alternative to `initialContentHTML`) |
|
|
86
|
-
| `onFocus` | `() => void` | - | Callback when editor gains focus |
|
|
87
|
-
| `onBlur` | `() => void` | - | Callback when editor loses focus |
|
|
88
|
-
| `onChange` | `(html: string) => void` | - | Callback when content changes |
|
|
89
|
-
| `onPaste` | `(data: unknown) => void` | - | Callback when content is pasted |
|
|
90
|
-
| `onKeyUp` | `(data: unknown) => void` | - | Callback on key up event |
|
|
91
|
-
| `onKeyDown` | `(data: unknown) => void` | - | Callback on key down event |
|
|
92
|
-
| `onInput` | `(data: unknown) => void` | - | Callback on input event |
|
|
93
|
-
| `onMessage` | `(message: unknown) => void` | - | Callback for editor messages |
|
|
94
|
-
| `onCursorPosition` | `(offsetY: number) => void` | - | Callback when cursor position changes |
|
|
95
|
-
| `onLink` | `(data: unknown) => void` | - | Callback when a link is clicked |
|
|
96
|
-
| `onHeightChange` | `(height: number) => void` | - | Callback when editor height changes |
|
|
97
|
-
| `errorMessage` | `string` | - | Error message to display |
|
|
98
|
-
|
|
99
|
-
#### EditorStyle Object
|
|
100
|
-
|
|
64
|
+
| `ref` | `RefObject<EditorRef>` | - | Editor reference for methods |
|
|
65
|
+
| `placeholder` | `string` | `''` | Placeholder text |
|
|
66
|
+
| `initialContentHTML` | `string` | `''` | Initial HTML content |
|
|
67
|
+
| `readOnly` | `boolean` | `false` | Display-only mode |
|
|
68
|
+
| `disabled` | `boolean` | `false` | Disable editing |
|
|
69
|
+
| `pasteAsPlainText` | `boolean` | `false` | Disable HTML paste |
|
|
70
|
+
| `initialFocus` | `boolean` | `false` | Auto-focus on mount |
|
|
71
|
+
| `editorStyle` | `object` | - | Style configuration (see below) |
|
|
72
|
+
| `onChange` | `(html: string) => void` | - | Content change callback |
|
|
73
|
+
| `onFocus` / `onBlur` | `() => void` | - | Focus callbacks |
|
|
74
|
+
| `onPaste` | `(data: unknown) => void` | - | Paste event callback |
|
|
75
|
+
| `onLink` | `(data: unknown) => void` | - | Link click callback |
|
|
76
|
+
| `onHeightChange` | `(height: number) => void` | - | Height change callback |
|
|
77
|
+
| `errorMessage` | `string` | - | Error message (shows red border) |
|
|
78
|
+
|
|
79
|
+
**EditorStyle:**
|
|
101
80
|
```typescript
|
|
102
81
|
{
|
|
103
|
-
backgroundColor?: string; // Editor background
|
|
82
|
+
backgroundColor?: string; // Editor background
|
|
104
83
|
color?: string; // Text color
|
|
105
|
-
placeholderColor?: string; // Placeholder
|
|
106
|
-
caretColor?: string; // Cursor
|
|
107
|
-
|
|
108
|
-
cssText?: string; // Additional CSS
|
|
109
|
-
|
|
84
|
+
placeholderColor?: string; // Placeholder color
|
|
85
|
+
caretColor?: string; // Cursor color
|
|
86
|
+
contentCSSText?: string; // Content CSS
|
|
87
|
+
cssText?: string; // Additional CSS
|
|
88
|
+
initialCSSText?: string; // Initial CSS
|
|
110
89
|
}
|
|
111
90
|
```
|
|
112
91
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
Access
|
|
116
|
-
|
|
117
|
-
| Method |
|
|
118
|
-
|
|
119
|
-
| `setContentHTML
|
|
120
|
-
| `getContentHtml
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
123
|
-
| `
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
126
|
-
| `
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
| `setLineHeight` | `(value: number \| string)` | Set line height |
|
|
138
|
-
| `command` | `(command: unknown)` | Execute a command |
|
|
139
|
-
| `commandDOM` | `(command: unknown)` | Execute a DOM command |
|
|
140
|
-
| `injectJavascript` | `(script: string)` | Inject custom JavaScript |
|
|
141
|
-
| `registerToolbar` | `(listener: (items: string[]) => void)` | Register toolbar selection listener |
|
|
142
|
-
|
|
143
|
-
#### Properties
|
|
144
|
-
|
|
145
|
-
| Property | Type | Description |
|
|
146
|
-
|----------|------|-------------|
|
|
147
|
-
| `isKeyboardOpen` | `boolean` | Whether the keyboard is currently open |
|
|
148
|
-
|
|
149
|
-
### Toolbar
|
|
150
|
-
|
|
151
|
-
A customizable toolbar component that provides formatting buttons for the editor.
|
|
152
|
-
|
|
153
|
-
#### Props
|
|
92
|
+
### Editor Methods
|
|
93
|
+
|
|
94
|
+
Access via `editorRef.current`:
|
|
95
|
+
|
|
96
|
+
| Method | Description |
|
|
97
|
+
|--------|-------------|
|
|
98
|
+
| `setContentHTML(html)` | Set editor content |
|
|
99
|
+
| `getContentHtml()` | Get content (Promise, deprecated - use `onChange`) |
|
|
100
|
+
| `insertText(text)` | Insert plain text |
|
|
101
|
+
| `insertHTML(html)` | Insert HTML |
|
|
102
|
+
| `insertImage(attrs, style?)` | Insert image |
|
|
103
|
+
| `insertVideo(attrs, style?)` | Insert video |
|
|
104
|
+
| `insertLink(title, url)` | Insert link |
|
|
105
|
+
| `setFontSize(size)` | Set font size |
|
|
106
|
+
| `setFontName(name)` | Set font family |
|
|
107
|
+
| `setForeColor(color)` | Set text color |
|
|
108
|
+
| `setHiliteColor(color)` | Set highlight color |
|
|
109
|
+
| `setLineHeight(value)` | Set line height |
|
|
110
|
+
| `focusContentEditor()` | Focus editor |
|
|
111
|
+
| `blurContentEditor()` | Blur editor |
|
|
112
|
+
| `dismissKeyboard()` | Dismiss keyboard |
|
|
113
|
+
| `injectJavascript(script)` | Inject custom JS |
|
|
114
|
+
|
|
115
|
+
### Toolbar Props
|
|
154
116
|
|
|
155
117
|
| Prop | Type | Default | Description |
|
|
156
118
|
|------|------|---------|-------------|
|
|
157
|
-
| `editor` | `{ current: EditorRef \| null }` | - | Editor ref
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `iconTint` | `string` | `'#71787F'` | Default icon color |
|
|
119
|
+
| `editor` | `{ current: EditorRef \| null }` | - | Editor ref |
|
|
120
|
+
| `actions` | `string[]` | `defaultActions` | Action array (see Actions) |
|
|
121
|
+
| `disabled` | `boolean` | `false` | Disable toolbar |
|
|
122
|
+
| `readOnly` | `boolean` | `false` | Hide when read-only |
|
|
123
|
+
| `iconTint` | `string` | `'#71787F'` | Icon color |
|
|
163
124
|
| `selectedIconTint` | `string` | - | Selected icon color |
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `disabledButtonStyle` | `object` | - | Styles for disabled buttons |
|
|
172
|
-
| `iconMap` | `Record<string, unknown>` | - | Custom icon map (override default icons) |
|
|
173
|
-
| `renderAction` | `(action: string, selected: boolean) => React.ReactElement` | - | Custom render function for actions |
|
|
174
|
-
| `onPressAddImage` | `() => void` | - | Callback when image button is pressed |
|
|
175
|
-
| `onInsertLink` | `() => void` | - | Callback when link button is pressed |
|
|
176
|
-
| `insertVideo` | `() => void` | - | Callback when video button is pressed |
|
|
177
|
-
| `flatContainerStyle` | `object` | - | Styles for the FlatList container |
|
|
178
|
-
| `horizontal` | `boolean` | `true` | Whether to display toolbar horizontally |
|
|
179
|
-
| `children` | `React.ReactNode` | - | Additional content to render in toolbar |
|
|
180
|
-
|
|
181
|
-
#### Custom Action Handlers
|
|
182
|
-
|
|
183
|
-
You can provide custom handlers for specific actions by passing them as props:
|
|
125
|
+
| `iconSize` | `number` | `20` | Icon size |
|
|
126
|
+
| `iconGap` | `number` | `12` | Icon spacing |
|
|
127
|
+
| `iconMap` | `Record<string, unknown>` | - | Custom icons |
|
|
128
|
+
| `renderAction` | `(action, selected) => ReactNode` | - | Custom renderer |
|
|
129
|
+
| `onPressAddImage` | `() => void` | - | Image button handler |
|
|
130
|
+
| `onInsertLink` | `() => void` | - | Link button handler |
|
|
131
|
+
| `insertVideo` | `() => void` | - | Video button handler |
|
|
184
132
|
|
|
185
|
-
|
|
186
|
-
<Toolbar
|
|
187
|
-
editor={editorRef}
|
|
188
|
-
fontSize={() => {
|
|
189
|
-
// Custom font size handler
|
|
190
|
-
editorRef.current?.setFontSize(18);
|
|
191
|
-
}}
|
|
192
|
-
foreColor={() => {
|
|
193
|
-
// Custom color picker handler
|
|
194
|
-
editorRef.current?.setForeColor('#FF0000');
|
|
195
|
-
}}
|
|
196
|
-
lineHeight={() => {
|
|
197
|
-
// Custom line height handler
|
|
198
|
-
editorRef.current?.setLineHeight(1.5);
|
|
199
|
-
}}
|
|
200
|
-
/>
|
|
201
|
-
```
|
|
133
|
+
### Actions
|
|
202
134
|
|
|
203
|
-
|
|
135
|
+
Import actions: `import { actions } from 'rn-rich-text-editor'`
|
|
204
136
|
|
|
205
|
-
|
|
137
|
+
**Text Formatting:** `setBold`, `setItalic`, `setUnderline`, `setStrikethrough`, `setSubscript`, `setSuperscript`, `removeFormat`
|
|
206
138
|
|
|
207
|
-
|
|
208
|
-
import { actions } from 'rn-rich-text-editor';
|
|
209
|
-
```
|
|
139
|
+
**Headings:** `heading1` through `heading6`, `setParagraph`
|
|
210
140
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
| Action | Constant | Description |
|
|
214
|
-
|--------|----------|-------------|
|
|
215
|
-
| **Text Formatting** |
|
|
216
|
-
| Bold | `actions.setBold` | Make text bold |
|
|
217
|
-
| Italic | `actions.setItalic` | Make text italic |
|
|
218
|
-
| Underline | `actions.setUnderline` | Underline text |
|
|
219
|
-
| Strikethrough | `actions.setStrikethrough` | Strikethrough text |
|
|
220
|
-
| Subscript | `actions.setSubscript` | Subscript text |
|
|
221
|
-
| Superscript | `actions.setSuperscript` | Superscript text |
|
|
222
|
-
| Remove Format | `actions.removeFormat` | Remove all formatting |
|
|
223
|
-
| **Headings** |
|
|
224
|
-
| Heading 1 | `actions.heading1` | Apply H1 heading |
|
|
225
|
-
| Heading 2 | `actions.heading2` | Apply H2 heading |
|
|
226
|
-
| Heading 3 | `actions.heading3` | Apply H3 heading |
|
|
227
|
-
| Heading 4 | `actions.heading4` | Apply H4 heading |
|
|
228
|
-
| Heading 5 | `actions.heading5` | Apply H5 heading |
|
|
229
|
-
| Heading 6 | `actions.heading6` | Apply H6 heading |
|
|
230
|
-
| Paragraph | `actions.setParagraph` | Apply paragraph style |
|
|
231
|
-
| **Alignment** |
|
|
232
|
-
| Align Left | `actions.alignLeft` | Align text left |
|
|
233
|
-
| Align Center | `actions.alignCenter` | Align text center |
|
|
234
|
-
| Align Right | `actions.alignRight` | Align text right |
|
|
235
|
-
| Align Full | `actions.alignFull` | Justify text |
|
|
236
|
-
| Align (Cycle) | `actions.align` | Cycle through alignments |
|
|
237
|
-
| **Lists** |
|
|
238
|
-
| Bullet List | `actions.insertBulletsList` | Insert unordered list |
|
|
239
|
-
| Ordered List | `actions.insertOrderedList` | Insert ordered list |
|
|
240
|
-
| Checkbox List | `actions.checkboxList` | Insert checkbox list |
|
|
241
|
-
| Indent | `actions.indent` | Increase indent |
|
|
242
|
-
| Outdent | `actions.outdent` | Decrease indent |
|
|
243
|
-
| **Media** |
|
|
244
|
-
| Insert Image | `actions.insertImage` | Insert image |
|
|
245
|
-
| Insert Video | `actions.insertVideo` | Insert video |
|
|
246
|
-
| Insert Link | `actions.insertLink` | Insert hyperlink |
|
|
247
|
-
| **Text Styling** |
|
|
248
|
-
| Font Size | `actions.fontSize` | Change font size |
|
|
249
|
-
| Font Name | `actions.fontName` | Change font family |
|
|
250
|
-
| Text Color | `actions.foreColor` | Change text color |
|
|
251
|
-
| Highlight Color | `actions.hiliteColor` | Change highlight color |
|
|
252
|
-
| Line Height | `actions.lineHeight` | Change line height |
|
|
253
|
-
| **Other** |
|
|
254
|
-
| Code Block | `actions.code` | Insert code block |
|
|
255
|
-
| Blockquote | `actions.blockquote` | Insert blockquote |
|
|
256
|
-
| Horizontal Rule | `actions.setHR` | Insert horizontal line |
|
|
257
|
-
| Line | `actions.line` | Insert line break |
|
|
258
|
-
| Undo | `actions.undo` | Undo last action |
|
|
259
|
-
| Redo | `actions.redo` | Redo last action |
|
|
260
|
-
| Table | `actions.table` | Insert table |
|
|
261
|
-
| Keyboard | `actions.keyboard` | Toggle keyboard |
|
|
262
|
-
|
|
263
|
-
### Default Actions
|
|
264
|
-
|
|
265
|
-
The toolbar comes with a default set of actions:
|
|
141
|
+
**Alignment:** `alignLeft`, `alignCenter`, `alignRight`, `alignFull`, `align` (cycle)
|
|
266
142
|
|
|
267
|
-
|
|
268
|
-
import { defaultActions } from 'rn-rich-text-editor';
|
|
269
|
-
|
|
270
|
-
// Default actions include:
|
|
271
|
-
// - keyboard
|
|
272
|
-
// - setBold
|
|
273
|
-
// - setItalic
|
|
274
|
-
// - setUnderline
|
|
275
|
-
// - removeFormat
|
|
276
|
-
// - insertBulletsList
|
|
277
|
-
// - indent
|
|
278
|
-
// - outdent
|
|
279
|
-
// - insertLink
|
|
280
|
-
// - lineHeight
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
## Usage Examples
|
|
143
|
+
**Lists:** `insertBulletsList`, `insertOrderedList`, `checkboxList`, `indent`, `outdent`
|
|
284
144
|
|
|
285
|
-
|
|
145
|
+
**Media:** `insertImage`, `insertVideo`, `insertLink`
|
|
286
146
|
|
|
287
|
-
|
|
288
|
-
import React, { useRef } from 'react';
|
|
289
|
-
import { View, StyleSheet } from 'react-native';
|
|
290
|
-
import { Editor, Toolbar } from 'rn-rich-text-editor';
|
|
147
|
+
**Styling:** `fontSize`, `fontName`, `foreColor`, `hiliteColor`, `lineHeight`
|
|
291
148
|
|
|
292
|
-
|
|
293
|
-
const editorRef = useRef(null);
|
|
149
|
+
**Other:** `code`, `blockquote`, `setHR`, `line`, `undo`, `redo`, `table`, `keyboard`, `separator`
|
|
294
150
|
|
|
295
|
-
|
|
296
|
-
<View style={styles.container}>
|
|
297
|
-
<Editor
|
|
298
|
-
ref={editorRef}
|
|
299
|
-
placeholder="Start typing..."
|
|
300
|
-
onChange={(html) => {
|
|
301
|
-
console.log('Content:', html);
|
|
302
|
-
}}
|
|
303
|
-
style={styles.editor}
|
|
304
|
-
/>
|
|
305
|
-
<Toolbar editor={editorRef} />
|
|
306
|
-
</View>
|
|
307
|
-
);
|
|
308
|
-
}
|
|
151
|
+
**Default actions:** `keyboard`, `setBold`, `setItalic`, `setUnderline`, `removeFormat`, `insertBulletsList`, `indent`, `outdent`, `insertLink`
|
|
309
152
|
|
|
310
|
-
|
|
311
|
-
container: {
|
|
312
|
-
flex: 1,
|
|
313
|
-
backgroundColor: '#fff',
|
|
314
|
-
},
|
|
315
|
-
editor: {
|
|
316
|
-
backgroundColor: '#fff',
|
|
317
|
-
borderColor: '#ddd',
|
|
318
|
-
borderWidth: 1,
|
|
319
|
-
minHeight: 200,
|
|
320
|
-
},
|
|
321
|
-
});
|
|
322
|
-
```
|
|
153
|
+
## 💡 Examples
|
|
323
154
|
|
|
324
|
-
### Custom Toolbar
|
|
155
|
+
### Custom Toolbar
|
|
325
156
|
|
|
326
157
|
```tsx
|
|
327
|
-
import React, { useRef } from 'react';
|
|
328
158
|
import { Editor, Toolbar, actions } from 'rn-rich-text-editor';
|
|
329
159
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
actions.heading1,
|
|
339
|
-
actions.insertBulletsList,
|
|
340
|
-
actions.insertOrderedList,
|
|
341
|
-
actions.insertLink,
|
|
342
|
-
actions.insertImage,
|
|
343
|
-
];
|
|
160
|
+
const customActions = [
|
|
161
|
+
actions.keyboard,
|
|
162
|
+
actions.setBold,
|
|
163
|
+
actions.setItalic,
|
|
164
|
+
actions.heading1,
|
|
165
|
+
actions.insertBulletsList,
|
|
166
|
+
actions.insertLink,
|
|
167
|
+
];
|
|
344
168
|
|
|
345
|
-
|
|
346
|
-
<>
|
|
347
|
-
<Editor ref={editorRef} />
|
|
348
|
-
<Toolbar editor={editorRef} actions={customActions} />
|
|
349
|
-
</>
|
|
350
|
-
);
|
|
351
|
-
}
|
|
169
|
+
<Toolbar editor={editorRef} actions={customActions} />
|
|
352
170
|
```
|
|
353
171
|
|
|
354
|
-
###
|
|
172
|
+
### Programmatic Content
|
|
355
173
|
|
|
356
174
|
```tsx
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
{ width: '100%', height: 'auto' }
|
|
368
|
-
);
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
const insertLink = () => {
|
|
372
|
-
editorRef.current?.insertLink('Click here', 'https://example.com');
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
const insertText = () => {
|
|
376
|
-
editorRef.current?.insertText('Hello World!');
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
return (
|
|
380
|
-
<View>
|
|
381
|
-
<Editor ref={editorRef} />
|
|
382
|
-
<Button title="Insert Image" onPress={insertImage} />
|
|
383
|
-
<Button title="Insert Link" onPress={insertLink} />
|
|
384
|
-
<Button title="Insert Text" onPress={insertText} />
|
|
385
|
-
</View>
|
|
386
|
-
);
|
|
387
|
-
}
|
|
175
|
+
// Insert content
|
|
176
|
+
editorRef.current?.insertText('Hello');
|
|
177
|
+
editorRef.current?.insertHTML('<strong>Bold</strong>');
|
|
178
|
+
editorRef.current?.insertLink('Click', 'https://example.com');
|
|
179
|
+
editorRef.current?.insertImage({ src: 'image.jpg' }, { width: '100%' });
|
|
180
|
+
|
|
181
|
+
// Style text
|
|
182
|
+
editorRef.current?.setFontSize(18);
|
|
183
|
+
editorRef.current?.setForeColor('#FF0000');
|
|
184
|
+
editorRef.current?.setLineHeight(1.5);
|
|
388
185
|
```
|
|
389
186
|
|
|
390
|
-
### Styling
|
|
187
|
+
### Styling
|
|
391
188
|
|
|
392
189
|
```tsx
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
190
|
+
<Editor
|
|
191
|
+
ref={editorRef}
|
|
192
|
+
editorStyle={{
|
|
193
|
+
backgroundColor: '#f5f5f5',
|
|
194
|
+
color: '#333',
|
|
195
|
+
placeholderColor: '#999',
|
|
196
|
+
caretColor: '#007AFF',
|
|
197
|
+
contentCSSText: `
|
|
198
|
+
font-family: -apple-system, sans-serif;
|
|
199
|
+
font-size: 16px;
|
|
200
|
+
line-height: 1.6;
|
|
201
|
+
`,
|
|
202
|
+
}}
|
|
203
|
+
/>
|
|
398
204
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
caretColor: '#007AFF',
|
|
407
|
-
contentCSSText: `
|
|
408
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
409
|
-
font-size: 16px;
|
|
410
|
-
line-height: 1.6;
|
|
411
|
-
`,
|
|
412
|
-
}}
|
|
413
|
-
/>
|
|
414
|
-
);
|
|
415
|
-
}
|
|
205
|
+
<Toolbar
|
|
206
|
+
editor={editorRef}
|
|
207
|
+
iconTint="#333"
|
|
208
|
+
selectedIconTint="#007AFF"
|
|
209
|
+
iconSize={24}
|
|
210
|
+
style={{ backgroundColor: '#f0f0f0', paddingVertical: 8 }}
|
|
211
|
+
/>
|
|
416
212
|
```
|
|
417
213
|
|
|
418
|
-
###
|
|
214
|
+
### Image Handling
|
|
419
215
|
|
|
420
216
|
```tsx
|
|
421
|
-
import
|
|
422
|
-
import { Editor, Toolbar } from 'rn-rich-text-editor';
|
|
217
|
+
import { ImagePicker } from 'react-native-image-picker';
|
|
423
218
|
|
|
424
|
-
|
|
425
|
-
|
|
219
|
+
const handleAddImage = () => {
|
|
220
|
+
ImagePicker.launchImageLibrary({}, (response) => {
|
|
221
|
+
if (response.assets?.[0]) {
|
|
222
|
+
editorRef.current?.insertImage(
|
|
223
|
+
{ src: response.assets[0].uri },
|
|
224
|
+
{ width: '100%', height: 'auto' }
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
};
|
|
426
229
|
|
|
427
|
-
|
|
428
|
-
<>
|
|
429
|
-
<Editor ref={editorRef} />
|
|
430
|
-
<Toolbar
|
|
431
|
-
editor={editorRef}
|
|
432
|
-
iconTint="#333"
|
|
433
|
-
selectedIconTint="#007AFF"
|
|
434
|
-
iconSize={24}
|
|
435
|
-
iconGap={20}
|
|
436
|
-
style={{
|
|
437
|
-
backgroundColor: '#f0f0f0',
|
|
438
|
-
paddingVertical: 8,
|
|
439
|
-
}}
|
|
440
|
-
selectedButtonStyle={{
|
|
441
|
-
backgroundColor: '#e0e0e0',
|
|
442
|
-
borderRadius: 4,
|
|
443
|
-
}}
|
|
444
|
-
/>
|
|
445
|
-
</>
|
|
446
|
-
);
|
|
447
|
-
}
|
|
230
|
+
<Toolbar editor={editorRef} onPressAddImage={handleAddImage} />
|
|
448
231
|
```
|
|
449
232
|
|
|
450
|
-
###
|
|
233
|
+
### Read-Only Display
|
|
451
234
|
|
|
452
235
|
```tsx
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
const editorRef = useRef(null);
|
|
459
|
-
const [html, setHtml] = useState('');
|
|
460
|
-
|
|
461
|
-
const handleAddImage = () => {
|
|
462
|
-
ImagePicker.launchImageLibrary({}, (response) => {
|
|
463
|
-
if (response.assets && response.assets[0]) {
|
|
464
|
-
const imageUri = response.assets[0].uri;
|
|
465
|
-
editorRef.current?.insertImage(
|
|
466
|
-
{ src: imageUri, alt: 'Image' },
|
|
467
|
-
{ width: '100%', height: 'auto' }
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
});
|
|
471
|
-
};
|
|
472
|
-
|
|
473
|
-
return (
|
|
474
|
-
<>
|
|
475
|
-
<Editor
|
|
476
|
-
ref={editorRef}
|
|
477
|
-
onChange={setHtml}
|
|
478
|
-
initialContentHTML={html}
|
|
479
|
-
/>
|
|
480
|
-
<Toolbar
|
|
481
|
-
editor={editorRef}
|
|
482
|
-
onPressAddImage={handleAddImage}
|
|
483
|
-
/>
|
|
484
|
-
</>
|
|
485
|
-
);
|
|
486
|
-
}
|
|
236
|
+
<Editor
|
|
237
|
+
readOnly={true}
|
|
238
|
+
initialContentHTML="<p>Formatted content</p>"
|
|
239
|
+
editorStyle={{ backgroundColor: '#fff', color: '#333' }}
|
|
240
|
+
/>
|
|
487
241
|
```
|
|
488
242
|
|
|
489
|
-
###
|
|
243
|
+
### Custom Action Handlers
|
|
490
244
|
|
|
491
245
|
```tsx
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
return (
|
|
499
|
-
<Editor
|
|
500
|
-
readOnly={true}
|
|
501
|
-
initialContentHTML={htmlContent}
|
|
502
|
-
editorStyle={{
|
|
503
|
-
backgroundColor: '#fff',
|
|
504
|
-
color: '#333',
|
|
505
|
-
}}
|
|
506
|
-
/>
|
|
507
|
-
);
|
|
508
|
-
}
|
|
246
|
+
<Toolbar
|
|
247
|
+
editor={editorRef}
|
|
248
|
+
fontSize={() => editorRef.current?.setFontSize(18)}
|
|
249
|
+
foreColor={() => editorRef.current?.setForeColor('#FF0000')}
|
|
250
|
+
lineHeight={() => editorRef.current?.setLineHeight(1.5)}
|
|
251
|
+
/>
|
|
509
252
|
```
|
|
510
253
|
|
|
511
|
-
###
|
|
254
|
+
### Custom Icons & Renderers
|
|
512
255
|
|
|
513
256
|
```tsx
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
257
|
+
// Custom icons
|
|
258
|
+
const iconMap = {
|
|
259
|
+
[actions.setBold]: require('./assets/bold.png'),
|
|
260
|
+
[actions.setItalic]: require('./assets/italic.png'),
|
|
261
|
+
};
|
|
517
262
|
|
|
518
|
-
|
|
519
|
-
const editorRef = useRef(null);
|
|
263
|
+
<Toolbar editor={editorRef} iconMap={iconMap} />
|
|
520
264
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
};
|
|
265
|
+
// Custom renderer
|
|
266
|
+
const renderAction = (action, selected) => {
|
|
267
|
+
if (action === actions.setBold) {
|
|
268
|
+
return <CustomBoldButton selected={selected} />;
|
|
269
|
+
}
|
|
270
|
+
return null; // Use default for others
|
|
271
|
+
};
|
|
530
272
|
|
|
531
|
-
|
|
532
|
-
<>
|
|
533
|
-
<Editor ref={editorRef} />
|
|
534
|
-
<Button title="Get Content" onPress={getContent} />
|
|
535
|
-
</>
|
|
536
|
-
);
|
|
537
|
-
}
|
|
273
|
+
<Toolbar editor={editorRef} renderAction={renderAction} />
|
|
538
274
|
```
|
|
539
275
|
|
|
540
|
-
##
|
|
276
|
+
## 🎯 Smart Paste
|
|
541
277
|
|
|
542
|
-
|
|
278
|
+
The editor automatically preserves HTML formatting when pasting. It detects HTML in the clipboard and maintains:
|
|
279
|
+
- Text formatting (bold, italic, colors, etc.)
|
|
280
|
+
- Inline styles
|
|
281
|
+
- Links and HTML elements
|
|
543
282
|
|
|
283
|
+
To paste as plain text only:
|
|
544
284
|
```tsx
|
|
545
|
-
|
|
546
|
-
import { Editor, Toolbar, actions } from 'rn-rich-text-editor';
|
|
547
|
-
import { Image } from 'react-native';
|
|
548
|
-
|
|
549
|
-
function CustomIcons() {
|
|
550
|
-
const editorRef = useRef(null);
|
|
551
|
-
|
|
552
|
-
const iconMap = {
|
|
553
|
-
[actions.setBold]: require('./assets/custom-bold.png'),
|
|
554
|
-
[actions.setItalic]: require('./assets/custom-italic.png'),
|
|
555
|
-
// ... more custom icons
|
|
556
|
-
};
|
|
557
|
-
|
|
558
|
-
return (
|
|
559
|
-
<>
|
|
560
|
-
<Editor ref={editorRef} />
|
|
561
|
-
<Toolbar editor={editorRef} iconMap={iconMap} />
|
|
562
|
-
</>
|
|
563
|
-
);
|
|
564
|
-
}
|
|
285
|
+
<Editor pasteAsPlainText={true} />
|
|
565
286
|
```
|
|
566
287
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
```tsx
|
|
570
|
-
import React, { useRef } from 'react';
|
|
571
|
-
import { Text, TouchableOpacity } from 'react-native';
|
|
572
|
-
import { Editor, Toolbar, actions } from 'rn-rich-text-editor';
|
|
573
|
-
|
|
574
|
-
function CustomRenderer() {
|
|
575
|
-
const editorRef = useRef(null);
|
|
288
|
+
## 🔧 Advanced
|
|
576
289
|
|
|
577
|
-
|
|
578
|
-
if (action === actions.setBold) {
|
|
579
|
-
return (
|
|
580
|
-
<TouchableOpacity
|
|
581
|
-
style={{
|
|
582
|
-
padding: 8,
|
|
583
|
-
backgroundColor: selected ? '#007AFF' : 'transparent',
|
|
584
|
-
}}
|
|
585
|
-
onPress={() => editorRef.current?.sendAction(action, 'result')}
|
|
586
|
-
>
|
|
587
|
-
<Text style={{ fontWeight: 'bold' }}>B</Text>
|
|
588
|
-
</TouchableOpacity>
|
|
589
|
-
);
|
|
590
|
-
}
|
|
591
|
-
// Return null to use default rendering
|
|
592
|
-
return null;
|
|
593
|
-
};
|
|
290
|
+
### Custom JavaScript
|
|
594
291
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
</>
|
|
600
|
-
);
|
|
601
|
-
}
|
|
292
|
+
```tsx
|
|
293
|
+
editorRef.current?.injectJavascript(`
|
|
294
|
+
document.querySelector('#content').style.border = '2px solid red';
|
|
295
|
+
`);
|
|
602
296
|
```
|
|
603
297
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
This package includes TypeScript definitions. Import types as needed:
|
|
298
|
+
### TypeScript
|
|
607
299
|
|
|
608
300
|
```typescript
|
|
609
|
-
import { Editor, Toolbar, EditorRef, EditorProps,
|
|
301
|
+
import { Editor, Toolbar, EditorRef, EditorProps, actions } from 'rn-rich-text-editor';
|
|
610
302
|
|
|
611
303
|
const editorRef = useRef<EditorRef>(null);
|
|
612
|
-
|
|
613
|
-
const editorProps: EditorProps = {
|
|
614
|
-
placeholder: 'Type here...',
|
|
615
|
-
onChange: (html) => console.log(html),
|
|
616
|
-
};
|
|
304
|
+
const props: EditorProps = { placeholder: 'Type...', onChange: (html) => {} };
|
|
617
305
|
```
|
|
618
306
|
|
|
619
|
-
##
|
|
307
|
+
## 🐛 Troubleshooting
|
|
620
308
|
|
|
621
|
-
|
|
622
|
-
-
|
|
623
|
-
-
|
|
624
|
-
- Supports all features
|
|
309
|
+
**Editor not focusing?**
|
|
310
|
+
- Set `initialFocus={true}` or call `focusContentEditor()`
|
|
311
|
+
- On Android, use `showAndroidKeyboard()` first
|
|
625
312
|
|
|
626
|
-
|
|
627
|
-
- Uses Android WebView
|
|
628
|
-
- May require additional keyboard handling
|
|
629
|
-
- All features supported
|
|
630
|
-
|
|
631
|
-
## Troubleshooting
|
|
632
|
-
|
|
633
|
-
### Editor not focusing
|
|
634
|
-
- Ensure `initialFocus` is set to `true` if needed
|
|
635
|
-
- Call `editorRef.current?.focusContentEditor()` programmatically
|
|
636
|
-
- On Android, use `showAndroidKeyboard()` before focusing
|
|
637
|
-
|
|
638
|
-
### Content not updating
|
|
313
|
+
**Content not updating?**
|
|
639
314
|
- Use `onChange` callback instead of polling `getContentHtml()`
|
|
640
|
-
-
|
|
315
|
+
- Set `initialContentHTML` correctly on mount
|
|
316
|
+
|
|
317
|
+
**Paste not preserving format?**
|
|
318
|
+
- Ensure `pasteAsPlainText` is `false`
|
|
319
|
+
- HTML paste works when clipboard contains HTML data
|
|
641
320
|
|
|
642
|
-
|
|
643
|
-
- Verify `editor` prop
|
|
644
|
-
-
|
|
645
|
-
- Ensure `disabled` prop is not set to `true`
|
|
321
|
+
**Toolbar not working?**
|
|
322
|
+
- Verify `editor` prop uses ref object: `editor={editorRef}`
|
|
323
|
+
- Ensure editor initializes before toolbar interactions
|
|
646
324
|
|
|
647
|
-
##
|
|
325
|
+
## 📱 Platform Notes
|
|
648
326
|
|
|
649
|
-
|
|
327
|
+
- **iOS**: WebKit WebView, automatic keyboard handling
|
|
328
|
+
- **Android**: Android WebView, may need `showAndroidKeyboard()` for focus
|
|
650
329
|
|
|
651
|
-
## License
|
|
330
|
+
## 📄 License
|
|
652
331
|
|
|
653
332
|
MIT
|
|
654
333
|
|
|
655
|
-
##
|
|
334
|
+
## 🤝 Contributing
|
|
656
335
|
|
|
657
|
-
|
|
336
|
+
Contributions welcome! See the [GitHub repository](https://github.com/vishaal2002/rn-rich-editor) for issues and PRs.
|
package/package.json
CHANGED
package/src/Editor.js
CHANGED
|
@@ -295,12 +295,13 @@ export default class Editor extends Component {
|
|
|
295
295
|
|
|
296
296
|
renderWebView() {
|
|
297
297
|
let that = this;
|
|
298
|
-
const { html, editorStyle, useContainer, style, onLink, dataDetectorTypes, readOnly, ...rest } = that.props;
|
|
298
|
+
const { html, editorStyle, useContainer, style, onLink, dataDetectorTypes, readOnly, disabled, ...rest } = that.props;
|
|
299
299
|
const { html: viewHTML, height: stateHeight } = that.state;
|
|
300
|
+
// Don't apply style prop to WebView to avoid double borders - style is only for container
|
|
300
301
|
const webViewStyle = [
|
|
301
302
|
styles.webview,
|
|
302
|
-
style,
|
|
303
303
|
readOnly && stateHeight > 0 ? { height: stateHeight, flex: 0 } : undefined,
|
|
304
|
+
disabled ? { pointerEvents: 'none' } : undefined,
|
|
304
305
|
].filter(Boolean);
|
|
305
306
|
return (
|
|
306
307
|
<>
|
|
@@ -315,7 +316,7 @@ export default class Editor extends Component {
|
|
|
315
316
|
ref={that.setRef}
|
|
316
317
|
onMessage={that.onMessage}
|
|
317
318
|
originWhitelist={['*']}
|
|
318
|
-
dataDetectorTypes={dataDetectorTypes}
|
|
319
|
+
dataDetectorTypes={disabled ? 'none' : dataDetectorTypes}
|
|
319
320
|
domStorageEnabled={false}
|
|
320
321
|
bounces={false}
|
|
321
322
|
javaScriptEnabled={true}
|
|
@@ -348,11 +349,30 @@ export default class Editor extends Component {
|
|
|
348
349
|
// If set to false, it will not use a View wrapper
|
|
349
350
|
const { useContainer, style, errorMessage, readOnly, disabled } = this.props;
|
|
350
351
|
const errorStyle = !readOnly && errorMessage ? { borderWidth: 1, borderColor: '#d92d20' } : {};
|
|
351
|
-
const disabledStyle = disabled ? { backgroundColor: '#C9CED7' } : {};
|
|
352
|
-
|
|
352
|
+
const disabledStyle = disabled ? { backgroundColor: '#C9CED7', pointerEvents: 'none' } : {};
|
|
353
|
+
|
|
354
|
+
// For readonly, remove border from style prop to ensure no border appears
|
|
355
|
+
let containerStyleProp = style;
|
|
356
|
+
if (readOnly && style) {
|
|
357
|
+
if (Array.isArray(style)) {
|
|
358
|
+
containerStyleProp = style.map(styleItem => {
|
|
359
|
+
if (styleItem && typeof styleItem === 'object') {
|
|
360
|
+
const { borderWidth, borderColor, borderTopWidth, borderBottomWidth, borderLeftWidth, borderRightWidth, ...rest } = styleItem;
|
|
361
|
+
return rest;
|
|
362
|
+
}
|
|
363
|
+
return styleItem;
|
|
364
|
+
});
|
|
365
|
+
} else if (typeof style === 'object') {
|
|
366
|
+
const { borderWidth, borderColor, borderTopWidth, borderBottomWidth, borderLeftWidth, borderRightWidth, ...rest } = style;
|
|
367
|
+
containerStyleProp = rest;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const readOnlyStyle = readOnly ? { borderWidth: 0, borderColor: 'transparent' } : {};
|
|
353
372
|
// overflow: 'hidden' prevents the WebView from visually cutting off the container border at corners
|
|
354
373
|
// disabledStyle last so it overrides user's backgroundColor when disabled
|
|
355
|
-
|
|
374
|
+
// readOnlyStyle after containerStyleProp to ensure border is removed
|
|
375
|
+
const containerStyle = [containerStyleProp, errorStyle, { overflow: 'hidden' }, readOnlyStyle, disabledStyle];
|
|
356
376
|
if (useContainer) {
|
|
357
377
|
containerStyle.push({ height });
|
|
358
378
|
// For readonly, ensure container expands fully to show all content without scrolling
|
package/src/editor/createHTML.js
CHANGED
|
@@ -675,15 +675,25 @@ function createHTML(options = {}) {
|
|
|
675
675
|
addEventListener(content, 'blur', handleBlur);
|
|
676
676
|
addEventListener(content, 'focus', handleFocus);
|
|
677
677
|
addEventListener(content, 'paste', function (e) {
|
|
678
|
-
|
|
679
|
-
var text =
|
|
678
|
+
var clipboardData = (e.originalEvent || e).clipboardData;
|
|
679
|
+
var text = clipboardData.getData('text/plain');
|
|
680
|
+
var html = clipboardData.getData('text/html');
|
|
680
681
|
|
|
681
|
-
${pasteListener} && postAction({type: 'CONTENT_PASTED', data: text});
|
|
682
|
+
${pasteListener} && postAction({type: 'CONTENT_PASTED', data: text || html});
|
|
682
683
|
if (${pasteAsPlainText}) {
|
|
683
|
-
// cancel paste
|
|
684
684
|
e.preventDefault();
|
|
685
|
-
// insert text manually
|
|
686
685
|
exec("insertText", text);
|
|
686
|
+
} else if (html && html.trim() !== '') {
|
|
687
|
+
e.preventDefault();
|
|
688
|
+
exec("insertHTML", html);
|
|
689
|
+
} else if (text && (text.indexOf('<') !== -1 || text.indexOf('<') !== -1)) {
|
|
690
|
+
var htmlFromText = text.indexOf('<') !== -1
|
|
691
|
+
? text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
|
692
|
+
: text;
|
|
693
|
+
if (htmlFromText.indexOf('<') !== -1) {
|
|
694
|
+
e.preventDefault();
|
|
695
|
+
exec("insertHTML", htmlFromText);
|
|
696
|
+
}
|
|
687
697
|
}
|
|
688
698
|
});
|
|
689
699
|
addEventListener(content, 'compositionstart', function(event){
|