@abduljebar/text-editor 2.4.1 → 2.5.2
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 +564 -564
- package/dist/Reproduction.d.ts +2 -0
- package/dist/Reproduction.d.ts.map +1 -0
- package/dist/components/TextEditor.d.ts.map +1 -1
- package/dist/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.js +1018 -1019
- package/dist/index.umd.cjs +9 -9
- package/package.json +84 -84
- package/dist/demo.jpg +0 -1
package/README.md
CHANGED
|
@@ -1,565 +1,565 @@
|
|
|
1
|
-
# @abduljebar/text-editor
|
|
2
|
-
|
|
3
|
-
A modern, feature-rich React text editor component with beautiful styling and extensive customization options. Perfect for blogs, content management systems, and any application requiring rich text editing capabilities.
|
|
4
|
-
|
|
5
|
-
## ✨ Features
|
|
6
|
-
|
|
7
|
-
### 🎨 Rich Text Editing
|
|
8
|
-
- **Text Formatting**: Bold, italic, underline, strikethrough
|
|
9
|
-
- **Headings**: H1, H2 with automatic styling
|
|
10
|
-
- **Lists**: Bulleted and numbered lists
|
|
11
|
-
- **Alignment**: Left, center, right alignment
|
|
12
|
-
- **Block Elements**: Quotes and code blocks
|
|
13
|
-
- **Undo/Redo**: Full history support
|
|
14
|
-
- **Additional Formatting**: Superscript, subscript, indentation
|
|
15
|
-
|
|
16
|
-
### 📁 File Management
|
|
17
|
-
- **Image Upload**: Drag & drop, paste, or file picker
|
|
18
|
-
- **Image Validation**: File type and size validation
|
|
19
|
-
- **Pending Images**: Track and upload pending images
|
|
20
|
-
- **HTML Export**: Generate complete HTML documents with styling
|
|
21
|
-
- **Auto-save**: Debounced content changes with configurable delay
|
|
22
|
-
|
|
23
|
-
### 🎯 Smart UX
|
|
24
|
-
- **Contextual Toolbar**: Appears only when focused and editable
|
|
25
|
-
- **Real-time Stats**: Word and character count in status bar
|
|
26
|
-
- **Smart Format Detection**: Visual indicators for active text formats
|
|
27
|
-
- **Keyboard Shortcuts**: Ctrl+S (save), Ctrl+E (export)
|
|
28
|
-
- **Auto-focus**: Automatic focus on load
|
|
29
|
-
- **Placeholder Support**: Visual placeholder when empty
|
|
30
|
-
|
|
31
|
-
### 🔧 Flexible Configuration
|
|
32
|
-
- **Read-only Mode**: Display content without editing capabilities
|
|
33
|
-
- **Customizable Height**: Flexible sizing options
|
|
34
|
-
- **Title Support**: Optional document title with real-time updates
|
|
35
|
-
- **Action Buttons**: Built-in save and export functionality
|
|
36
|
-
- **Event Handling**: Comprehensive callback system
|
|
37
|
-
- **Debounce Control**: Configurable change debouncing
|
|
38
|
-
|
|
39
|
-
### 🚀 Advanced Features
|
|
40
|
-
- **React Hook**: `useTextEditor` for custom implementations
|
|
41
|
-
- **Ref API**: Exposed methods for programmatic control
|
|
42
|
-
- **Selection Management**: Proper cursor and selection handling
|
|
43
|
-
- **Paste/Upload Handling**: Smart image and content paste handling
|
|
44
|
-
- **TypeScript**: Fully typed for better development experience
|
|
45
|
-
- **Validation**: Built-in content validation
|
|
46
|
-
|
|
47
|
-
## 📦 Installation
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
npm install @abduljebar/text-editor
|
|
51
|
-
# or
|
|
52
|
-
yarn add @abduljebar/text-editor
|
|
53
|
-
# or
|
|
54
|
-
pnpm add @abduljebar/text-editor
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## 🚀 Quick Start
|
|
58
|
-
|
|
59
|
-
```tsx
|
|
60
|
-
import '@abduljebar/text-editor/dist/index.css';
|
|
61
|
-
import { TextEditor } from "@abduljebar/text-editor";
|
|
62
|
-
|
|
63
|
-
function App() {
|
|
64
|
-
return (
|
|
65
|
-
<TextEditor
|
|
66
|
-
height="500px"
|
|
67
|
-
onChange={(content, html, title) => {
|
|
68
|
-
console.log("Content:", content);
|
|
69
|
-
console.log("HTML:", html);
|
|
70
|
-
console.log("Title:", title);
|
|
71
|
-
}}
|
|
72
|
-
initialContent="<h1>Welcome to Your Document</h1><p>Start editing here...</p>"
|
|
73
|
-
/>
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## 📖 Basic Usage
|
|
79
|
-
|
|
80
|
-
### Simple Implementation
|
|
81
|
-
|
|
82
|
-
```tsx
|
|
83
|
-
import '@abduljebar/text-editor/dist/index.css';
|
|
84
|
-
import { TextEditor } from "@abduljebar/text-editor";
|
|
85
|
-
|
|
86
|
-
function MyEditor() {
|
|
87
|
-
const handleSave = (content: string, html: string) => {
|
|
88
|
-
// Save to your backend or state management
|
|
89
|
-
console.log("Saving:", { content, html });
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const handleExport = (html: string) => {
|
|
93
|
-
// Export as HTML file
|
|
94
|
-
const blob = new Blob([html], { type: 'text/html' });
|
|
95
|
-
const url = URL.createObjectURL(blob);
|
|
96
|
-
const a = document.createElement('a');
|
|
97
|
-
a.href = url;
|
|
98
|
-
a.download = 'document.html';
|
|
99
|
-
a.click();
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
<TextEditor
|
|
104
|
-
showButtons={true}
|
|
105
|
-
showSaveTitle={true}
|
|
106
|
-
showStatusBar={true}
|
|
107
|
-
onSave={handleSave}
|
|
108
|
-
onExport={handleExport}
|
|
109
|
-
onChange={(content, html, title) => {
|
|
110
|
-
// Real-time updates
|
|
111
|
-
console.log("Changes:", { content, html, title });
|
|
112
|
-
}}
|
|
113
|
-
/>
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Read-only Mode
|
|
119
|
-
|
|
120
|
-
```tsx
|
|
121
|
-
import '@abduljebar/text-editor/dist/index.css';
|
|
122
|
-
import { TextEditor } from "@abduljebar/text-editor";
|
|
123
|
-
|
|
124
|
-
function ReadOnlyView() {
|
|
125
|
-
return (
|
|
126
|
-
<TextEditor
|
|
127
|
-
readOnly={true}
|
|
128
|
-
initialContent="<h1>Published Article</h1><p>This content cannot be edited.</p>"
|
|
129
|
-
showStatusBar={true}
|
|
130
|
-
/>
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### With Image Upload
|
|
136
|
-
|
|
137
|
-
```tsx
|
|
138
|
-
import '@abduljebar/text-editor/dist/index.css';
|
|
139
|
-
import { TextEditor } from "@abduljebar/text-editor";
|
|
140
|
-
|
|
141
|
-
function EditorWithImages() {
|
|
142
|
-
const handleImageUpload = async (file: File) => {
|
|
143
|
-
// Upload to your backend
|
|
144
|
-
const formData = new FormData();
|
|
145
|
-
formData.append('image', file);
|
|
146
|
-
|
|
147
|
-
const response = await fetch('/api/upload', {
|
|
148
|
-
method: 'POST',
|
|
149
|
-
body: formData,
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
const data = await response.json();
|
|
153
|
-
return data.url; // Return the uploaded image URL
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
return (
|
|
157
|
-
<TextEditor
|
|
158
|
-
showButtons={true}
|
|
159
|
-
onImageUpload={handleImageUpload}
|
|
160
|
-
allowedImageTypes={['image/jpeg', 'image/png', 'image/webp']}
|
|
161
|
-
maxImageSize={10 * 1024 * 1024} // 10MB
|
|
162
|
-
/>
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
## ⚙️ API Reference
|
|
168
|
-
|
|
169
|
-
### TextEditor Props
|
|
170
|
-
|
|
171
|
-
| Prop | Type | Default | Description |
|
|
172
|
-
|------|------|---------|-------------|
|
|
173
|
-
| `initialContent` | `string` | `""` | Initial HTML content for the editor |
|
|
174
|
-
| `onChange` | `(content: string, html: string, title?: string) => void` | `undefined` | Callback when content changes |
|
|
175
|
-
| `onSave` | `(content: string, html: string) => void` | `undefined` | Callback when save is triggered |
|
|
176
|
-
| `onExport` | `(html: string) => void` | `undefined` | Callback when export is triggered |
|
|
177
|
-
| `onImageUpload` | `(file: File) => Promise<string>` | `undefined` | Custom image upload handler |
|
|
178
|
-
| `imageUploadEndpoint` | `string` | `undefined` | Endpoint for default image upload |
|
|
179
|
-
| `readOnly` | `boolean` | `false` | Disable editing when true |
|
|
180
|
-
| `showButtons` | `boolean` | `false` | Show save/export buttons |
|
|
181
|
-
| `showSaveTitle` | `boolean` | `false` | Show document title input |
|
|
182
|
-
| `showStatusBar` | `boolean` | `false` | Show word/character count |
|
|
183
|
-
| `height` | `string` | `"500px"` | Editor height (any CSS value) |
|
|
184
|
-
| `allowedImageTypes` | `string[]` | `["image/jpeg","image/png","image/gif","image/webp"]` | Allowed image MIME types |
|
|
185
|
-
| `maxImageSize` | `number` | `5242880` (5MB) | Maximum image size in bytes |
|
|
186
|
-
| `debounceDelay` | `number` | `300` | Debounce delay for onChange in ms |
|
|
187
|
-
| `className` | `string` | `""` | Additional CSS class name |
|
|
188
|
-
| `placeholder` | `string` | `"Start typing here..."` | Placeholder text when empty |
|
|
189
|
-
| `autoFocus` | `boolean` | `false` | Auto-focus editor on load |
|
|
190
|
-
| `onInit` | `(editor: HTMLDivElement) => void` | `undefined` | Callback after editor initialization |
|
|
191
|
-
|
|
192
|
-
### TextEditor Ref API
|
|
193
|
-
|
|
194
|
-
The component exposes a ref with the following methods:
|
|
195
|
-
|
|
196
|
-
```typescript
|
|
197
|
-
interface TextEditorRef {
|
|
198
|
-
getContent: () => string;
|
|
199
|
-
getHTML: () => string;
|
|
200
|
-
getTitle: () => string;
|
|
201
|
-
clear: () => void;
|
|
202
|
-
focus: () => void;
|
|
203
|
-
insertText: (text: string) => void;
|
|
204
|
-
insertHTML: (html: string) => void;
|
|
205
|
-
executeCommand: (command: string, value?: string) => void;
|
|
206
|
-
}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
Example usage:
|
|
210
|
-
```tsx
|
|
211
|
-
import { useRef } from 'react';
|
|
212
|
-
import { TextEditor, TextEditorRef } from "@abduljebar/text-editor";
|
|
213
|
-
|
|
214
|
-
function EditorWithRef() {
|
|
215
|
-
const editorRef = useRef<TextEditorRef>(null);
|
|
216
|
-
|
|
217
|
-
const handleGetContent = () => {
|
|
218
|
-
if (editorRef.current) {
|
|
219
|
-
const content = editorRef.current.getContent();
|
|
220
|
-
const html = editorRef.current.getHTML();
|
|
221
|
-
console.log({ content, html });
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
return (
|
|
226
|
-
<>
|
|
227
|
-
<TextEditor ref={editorRef} />
|
|
228
|
-
<button onClick={handleGetContent}>Get Content</button>
|
|
229
|
-
</>
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
### useTextEditor Hook
|
|
235
|
-
|
|
236
|
-
For advanced usage and custom implementations:
|
|
237
|
-
|
|
238
|
-
```tsx
|
|
239
|
-
import '@abduljebar/text-editor/dist/index.css';
|
|
240
|
-
import { useTextEditor } from "@abduljebar/text-editor";
|
|
241
|
-
|
|
242
|
-
function CustomEditor() {
|
|
243
|
-
const {
|
|
244
|
-
editorState,
|
|
245
|
-
editorRef,
|
|
246
|
-
updateContent,
|
|
247
|
-
updateTitle,
|
|
248
|
-
executeCommand,
|
|
249
|
-
getValidationResult,
|
|
250
|
-
exportToHTML,
|
|
251
|
-
clearEditor,
|
|
252
|
-
handlePaste,
|
|
253
|
-
handleDrop,
|
|
254
|
-
insertImage,
|
|
255
|
-
uploadPendingImages,
|
|
256
|
-
} = useTextEditor({
|
|
257
|
-
initialContent: "Initial content",
|
|
258
|
-
onImageUpload: async (file) => {
|
|
259
|
-
// Custom upload logic
|
|
260
|
-
return "https://example.com/image.jpg";
|
|
261
|
-
},
|
|
262
|
-
allowedImageTypes: ["image/jpeg", "image/png"],
|
|
263
|
-
maxImageSize: 10 * 1024 * 1024,
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
return (
|
|
267
|
-
<div>
|
|
268
|
-
<div
|
|
269
|
-
ref={editorRef}
|
|
270
|
-
contentEditable
|
|
271
|
-
onInput={(e) => updateContent(e.currentTarget.innerHTML)}
|
|
272
|
-
className="border p-4 min-h-[200px]"
|
|
273
|
-
onPaste={handlePaste}
|
|
274
|
-
onDrop={handleDrop}
|
|
275
|
-
/>
|
|
276
|
-
<button onClick={() => executeCommand('bold')}>
|
|
277
|
-
Bold
|
|
278
|
-
</button>
|
|
279
|
-
<button onClick={() => insertImage(someFile)}>
|
|
280
|
-
Insert Image
|
|
281
|
-
</button>
|
|
282
|
-
</div>
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
#### Hook Parameters
|
|
288
|
-
|
|
289
|
-
| Parameter | Type | Default | Description |
|
|
290
|
-
|-----------|------|---------|-------------|
|
|
291
|
-
| `initialContent` | `string` | `""` | Initial HTML content |
|
|
292
|
-
| `onImageUpload` | `(file: File) => Promise<string>` | `undefined` | Custom image upload handler |
|
|
293
|
-
| `imageUploadEndpoint` | `string` | `undefined` | Endpoint for default image upload |
|
|
294
|
-
| `allowedImageTypes` | `string[]` | `["image/jpeg","image/png","image/gif","image/webp"]` | Allowed image types |
|
|
295
|
-
| `maxImageSize` | `number` | `5242880` | Max image size in bytes |
|
|
296
|
-
|
|
297
|
-
#### Hook Return Values
|
|
298
|
-
|
|
299
|
-
| Property | Type | Description |
|
|
300
|
-
|----------|------|-------------|
|
|
301
|
-
| `editorState` | `object` | Current editor state with content, title, counts, pending images |
|
|
302
|
-
| `editorRef` | `RefObject<HTMLDivElement>` | Reference to the editable element |
|
|
303
|
-
| `updateContent` | `(content: string) => void` | Update editor content |
|
|
304
|
-
| `updateTitle` | `(title: string) => void` | Update document title |
|
|
305
|
-
| `executeCommand` | `(command: string, value?: string) => void` | Execute formatting commands |
|
|
306
|
-
| `getValidationResult` | `() => ValidationResult` | Validate and get editor data |
|
|
307
|
-
| `exportToHTML` | `(options?) => string` | Generate HTML export |
|
|
308
|
-
| `clearEditor` | `() => void` | Clear all content |
|
|
309
|
-
| `handlePaste` | `(e: React.ClipboardEvent) => void` | Handle paste events |
|
|
310
|
-
| `handleDrop` | `(e: React.DragEvent) => void` | Handle drop events |
|
|
311
|
-
| `insertImage` | `(file: File, atCursor?: boolean) => Promise<void>` | Insert image into editor |
|
|
312
|
-
| `uploadPendingImages` | `() => Promise<void>` | Upload all pending images |
|
|
313
|
-
|
|
314
|
-
## 🎨 Styling & Customization
|
|
315
|
-
|
|
316
|
-
### Default Styling
|
|
317
|
-
|
|
318
|
-
The editor comes with beautiful default styling:
|
|
319
|
-
|
|
320
|
-
- **Headings**: Proper hierarchy with appropriate sizing
|
|
321
|
-
- **Paragraphs**: Optimal line height and margins
|
|
322
|
-
- **Lists**: Clean indentation and spacing
|
|
323
|
-
- **Code Blocks**: Proper monospace fonts
|
|
324
|
-
- **Quotes**: Elegant bordered design
|
|
325
|
-
- **Links**: Proper styling with hover effects
|
|
326
|
-
- **Images**: Responsive with rounded corners
|
|
327
|
-
|
|
328
|
-
### Custom Styling
|
|
329
|
-
|
|
330
|
-
You can override the default styles by targeting the editor's CSS classes or using the `className` prop:
|
|
331
|
-
|
|
332
|
-
```tsx
|
|
333
|
-
import '@abduljebar/text-editor/dist/index.css';
|
|
334
|
-
import { TextEditor } from "@abduljebar/text-editor";
|
|
335
|
-
|
|
336
|
-
<TextEditor
|
|
337
|
-
className="my-custom-editor"
|
|
338
|
-
// ... other props
|
|
339
|
-
/>
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
```css
|
|
343
|
-
.my-custom-editor {
|
|
344
|
-
border: 2px solid #4f46e5;
|
|
345
|
-
border-radius: 12px;
|
|
346
|
-
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
.my-custom-editor h1 {
|
|
350
|
-
color: #4f46e5;
|
|
351
|
-
border-bottom: 2px solid #e0e7ff;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
.my-custom-editor .toolbar {
|
|
355
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
356
|
-
}
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
## 🔧 Advanced Examples
|
|
360
|
-
|
|
361
|
-
### Integration with Form Libraries
|
|
362
|
-
|
|
363
|
-
```tsx
|
|
364
|
-
import '@abduljebar/text-editor/dist/index.css';
|
|
365
|
-
import { useForm } from 'react-hook-form';
|
|
366
|
-
import { TextEditor } from "@abduljebar/text-editor";
|
|
367
|
-
|
|
368
|
-
function ArticleForm() {
|
|
369
|
-
const { register, handleSubmit, setValue, watch } = useForm();
|
|
370
|
-
|
|
371
|
-
const handleEditorChange = (content: string, html: string, title?: string) => {
|
|
372
|
-
setValue('content', html);
|
|
373
|
-
setValue('title', title);
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
return (
|
|
377
|
-
<form onSubmit={handleSubmit(data => console.log(data))}>
|
|
378
|
-
<TextEditor
|
|
379
|
-
showSaveTitle={true}
|
|
380
|
-
showButtons={true}
|
|
381
|
-
onChange={handleEditorChange}
|
|
382
|
-
onSave={(content, html) => {
|
|
383
|
-
// Handle form submission
|
|
384
|
-
handleSubmit(data => console.log(data))();
|
|
385
|
-
}}
|
|
386
|
-
/>
|
|
387
|
-
</form>
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
### Custom Toolbar Implementation
|
|
393
|
-
|
|
394
|
-
```tsx
|
|
395
|
-
import '@abduljebar/text-editor/dist/index.css';
|
|
396
|
-
import { useTextEditor } from "@abduljebar/text-editor";
|
|
397
|
-
|
|
398
|
-
function CustomToolbarEditor() {
|
|
399
|
-
const { executeCommand, editorRef, insertImage } = useTextEditor({
|
|
400
|
-
initialContent: "Start typing...",
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
404
|
-
const file = e.target.files?.[0];
|
|
405
|
-
if (file) {
|
|
406
|
-
insertImage(file);
|
|
407
|
-
}
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
return (
|
|
411
|
-
<div className="editor-container">
|
|
412
|
-
<div className="custom-toolbar">
|
|
413
|
-
<button onClick={() => executeCommand('bold')}>Bold</button>
|
|
414
|
-
<button onClick={() => executeCommand('italic')}>Italic</button>
|
|
415
|
-
<button onClick={() => executeCommand('formatBlock', 'h1')}>H1</button>
|
|
416
|
-
<button onClick={() => executeCommand('formatBlock', 'h2')}>H2</button>
|
|
417
|
-
<input type="file" accept="image/*" onChange={handleFileSelect} />
|
|
418
|
-
</div>
|
|
419
|
-
<div
|
|
420
|
-
ref={editorRef}
|
|
421
|
-
contentEditable
|
|
422
|
-
className="editor-content"
|
|
423
|
-
/>
|
|
424
|
-
</div>
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
### Programmatic Control
|
|
430
|
-
|
|
431
|
-
```tsx
|
|
432
|
-
import { useRef, useEffect } from 'react';
|
|
433
|
-
import { TextEditor, TextEditorRef } from "@abduljebar/text-editor";
|
|
434
|
-
|
|
435
|
-
function ProgrammaticEditor() {
|
|
436
|
-
const editorRef = useRef<TextEditorRef>(null);
|
|
437
|
-
|
|
438
|
-
useEffect(() => {
|
|
439
|
-
// Example: Auto-insert content after 2 seconds
|
|
440
|
-
const timer = setTimeout(() => {
|
|
441
|
-
if (editorRef.current) {
|
|
442
|
-
editorRef.current.insertText("Hello, world!");
|
|
443
|
-
editorRef.current.focus();
|
|
444
|
-
}
|
|
445
|
-
}, 2000);
|
|
446
|
-
|
|
447
|
-
return () => clearTimeout(timer);
|
|
448
|
-
}, []);
|
|
449
|
-
|
|
450
|
-
const handleCommand = (command: string) => {
|
|
451
|
-
if (editorRef.current) {
|
|
452
|
-
editorRef.current.executeCommand(command);
|
|
453
|
-
}
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
return (
|
|
457
|
-
<div>
|
|
458
|
-
<TextEditor ref={editorRef} />
|
|
459
|
-
<div className="mt-4">
|
|
460
|
-
<button onClick={() => handleCommand('bold')}>Make Selection Bold</button>
|
|
461
|
-
<button onClick={() => editorRef.current?.clear()}>Clear Editor</button>
|
|
462
|
-
</div>
|
|
463
|
-
</div>
|
|
464
|
-
);
|
|
465
|
-
}
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
## 📋 Browser Support
|
|
469
|
-
|
|
470
|
-
- Chrome 60+
|
|
471
|
-
- Firefox 55+
|
|
472
|
-
- Safari 12+
|
|
473
|
-
- Edge 79+
|
|
474
|
-
- Opera 47+
|
|
475
|
-
|
|
476
|
-
## 🔒 Accessibility
|
|
477
|
-
|
|
478
|
-
- Keyboard navigation support
|
|
479
|
-
- ARIA labels for toolbar buttons
|
|
480
|
-
- Focus management
|
|
481
|
-
- Screen reader compatible
|
|
482
|
-
- Proper semantic HTML structure
|
|
483
|
-
|
|
484
|
-
## 🐛 Troubleshooting
|
|
485
|
-
|
|
486
|
-
### Common Issues
|
|
487
|
-
|
|
488
|
-
1. **Toolbar buttons not working**: Ensure the editor is focused and content is selected
|
|
489
|
-
2. **Images not uploading**: Check CORS settings and upload endpoint configuration
|
|
490
|
-
3. **Styles not appearing**: Import the CSS file: `import '@abduljebar/text-editor/dist/index.css';`
|
|
491
|
-
4. **Content not saving**: Check `onSave` callback and validation messages
|
|
492
|
-
5. **Formatting lost on paste**: Use the built-in `handlePaste` function
|
|
493
|
-
|
|
494
|
-
### Performance Tips
|
|
495
|
-
|
|
496
|
-
- Use appropriate `debounceDelay` for `onChange` to prevent excessive updates
|
|
497
|
-
- Implement proper image compression before upload
|
|
498
|
-
- Consider using `React.memo` if embedding in frequently re-rendering components
|
|
499
|
-
- Use the ref API for programmatic control instead of frequent state updates
|
|
500
|
-
|
|
501
|
-
## 📄 Supported Commands
|
|
502
|
-
|
|
503
|
-
The editor supports standard `document.execCommand` APIs:
|
|
504
|
-
|
|
505
|
-
- **Formatting**: `bold`, `italic`, `underline`, `strikeThrough`
|
|
506
|
-
- **Headings**: `formatBlock` (with `h1`, `h2`, `h3`, `p` values)
|
|
507
|
-
- **Lists**: `insertUnorderedList`, `insertOrderedList`
|
|
508
|
-
- **Alignment**: `justifyLeft`, `justifyCenter`, `justifyRight`
|
|
509
|
-
- **Indentation**: `indent`, `outdent`
|
|
510
|
-
- **Links**: `createLink` (with URL value)
|
|
511
|
-
- **History**: `undo`, `redo`
|
|
512
|
-
- **Special**: `superscript`, `subscript`, `formatBlock` (with `blockquote`, `pre` values)
|
|
513
|
-
|
|
514
|
-
## 🤝 Contributing
|
|
515
|
-
|
|
516
|
-
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
517
|
-
|
|
518
|
-
1. Fork the repository
|
|
519
|
-
2. Create a feature branch
|
|
520
|
-
3. Make your changes
|
|
521
|
-
4. Add tests if applicable
|
|
522
|
-
5. Submit a pull request
|
|
523
|
-
|
|
524
|
-
## 📄 License
|
|
525
|
-
|
|
526
|
-
MIT License - see the [LICENSE](LICENSE) file for details.
|
|
527
|
-
|
|
528
|
-
## 🆘 Support
|
|
529
|
-
|
|
530
|
-
If you encounter any issues or have questions:
|
|
531
|
-
|
|
532
|
-
1. Check the documentation above
|
|
533
|
-
2. Search existing GitHub issues
|
|
534
|
-
3. Create a new issue with:
|
|
535
|
-
- A clear description of the problem
|
|
536
|
-
- Steps to reproduce
|
|
537
|
-
- Expected vs actual behavior
|
|
538
|
-
- Code examples if applicable
|
|
539
|
-
|
|
540
|
-
## 🚀 Changelog
|
|
541
|
-
|
|
542
|
-
### v1.1.0
|
|
543
|
-
- Added image upload support with drag & drop
|
|
544
|
-
- Improved toolbar with better selection tracking
|
|
545
|
-
- Added ref API for programmatic control
|
|
546
|
-
- Enhanced keyboard shortcuts
|
|
547
|
-
- Better validation and error handling
|
|
548
|
-
- Added pending images tracking
|
|
549
|
-
|
|
550
|
-
### v1.0.0
|
|
551
|
-
- Initial release with core editing features
|
|
552
|
-
- Basic text formatting and styling
|
|
553
|
-
- HTML export functionality
|
|
554
|
-
- React hook for advanced usage
|
|
555
|
-
|
|
556
|
-
---
|
|
557
|
-
|
|
558
|
-
Built with ❤️ by [AbdulJebar Sani](https://github.com/abduljebar49)
|
|
559
|
-
|
|
560
|
-
## 🔗 Links
|
|
561
|
-
|
|
562
|
-
- [GitHub Repository](https://github.com/abduljebar/text-editor)
|
|
563
|
-
- [npm Package](https://www.npmjs.com/package/@abduljebar/text-editor)
|
|
564
|
-
- [Issue Tracker](https://github.com/abduljebar/text-editor/issues)
|
|
1
|
+
# @abduljebar/text-editor
|
|
2
|
+
|
|
3
|
+
A modern, feature-rich React text editor component with beautiful styling and extensive customization options. Perfect for blogs, content management systems, and any application requiring rich text editing capabilities.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
### 🎨 Rich Text Editing
|
|
8
|
+
- **Text Formatting**: Bold, italic, underline, strikethrough
|
|
9
|
+
- **Headings**: H1, H2 with automatic styling
|
|
10
|
+
- **Lists**: Bulleted and numbered lists
|
|
11
|
+
- **Alignment**: Left, center, right alignment
|
|
12
|
+
- **Block Elements**: Quotes and code blocks
|
|
13
|
+
- **Undo/Redo**: Full history support
|
|
14
|
+
- **Additional Formatting**: Superscript, subscript, indentation
|
|
15
|
+
|
|
16
|
+
### 📁 File Management
|
|
17
|
+
- **Image Upload**: Drag & drop, paste, or file picker
|
|
18
|
+
- **Image Validation**: File type and size validation
|
|
19
|
+
- **Pending Images**: Track and upload pending images
|
|
20
|
+
- **HTML Export**: Generate complete HTML documents with styling
|
|
21
|
+
- **Auto-save**: Debounced content changes with configurable delay
|
|
22
|
+
|
|
23
|
+
### 🎯 Smart UX
|
|
24
|
+
- **Contextual Toolbar**: Appears only when focused and editable
|
|
25
|
+
- **Real-time Stats**: Word and character count in status bar
|
|
26
|
+
- **Smart Format Detection**: Visual indicators for active text formats
|
|
27
|
+
- **Keyboard Shortcuts**: Ctrl+S (save), Ctrl+E (export)
|
|
28
|
+
- **Auto-focus**: Automatic focus on load
|
|
29
|
+
- **Placeholder Support**: Visual placeholder when empty
|
|
30
|
+
|
|
31
|
+
### 🔧 Flexible Configuration
|
|
32
|
+
- **Read-only Mode**: Display content without editing capabilities
|
|
33
|
+
- **Customizable Height**: Flexible sizing options
|
|
34
|
+
- **Title Support**: Optional document title with real-time updates
|
|
35
|
+
- **Action Buttons**: Built-in save and export functionality
|
|
36
|
+
- **Event Handling**: Comprehensive callback system
|
|
37
|
+
- **Debounce Control**: Configurable change debouncing
|
|
38
|
+
|
|
39
|
+
### 🚀 Advanced Features
|
|
40
|
+
- **React Hook**: `useTextEditor` for custom implementations
|
|
41
|
+
- **Ref API**: Exposed methods for programmatic control
|
|
42
|
+
- **Selection Management**: Proper cursor and selection handling
|
|
43
|
+
- **Paste/Upload Handling**: Smart image and content paste handling
|
|
44
|
+
- **TypeScript**: Fully typed for better development experience
|
|
45
|
+
- **Validation**: Built-in content validation
|
|
46
|
+
|
|
47
|
+
## 📦 Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install @abduljebar/text-editor
|
|
51
|
+
# or
|
|
52
|
+
yarn add @abduljebar/text-editor
|
|
53
|
+
# or
|
|
54
|
+
pnpm add @abduljebar/text-editor
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 🚀 Quick Start
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import '@abduljebar/text-editor/dist/index.css';
|
|
61
|
+
import { TextEditor } from "@abduljebar/text-editor";
|
|
62
|
+
|
|
63
|
+
function App() {
|
|
64
|
+
return (
|
|
65
|
+
<TextEditor
|
|
66
|
+
height="500px"
|
|
67
|
+
onChange={(content, html, title) => {
|
|
68
|
+
console.log("Content:", content);
|
|
69
|
+
console.log("HTML:", html);
|
|
70
|
+
console.log("Title:", title);
|
|
71
|
+
}}
|
|
72
|
+
initialContent="<h1>Welcome to Your Document</h1><p>Start editing here...</p>"
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 📖 Basic Usage
|
|
79
|
+
|
|
80
|
+
### Simple Implementation
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import '@abduljebar/text-editor/dist/index.css';
|
|
84
|
+
import { TextEditor } from "@abduljebar/text-editor";
|
|
85
|
+
|
|
86
|
+
function MyEditor() {
|
|
87
|
+
const handleSave = (content: string, html: string) => {
|
|
88
|
+
// Save to your backend or state management
|
|
89
|
+
console.log("Saving:", { content, html });
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleExport = (html: string) => {
|
|
93
|
+
// Export as HTML file
|
|
94
|
+
const blob = new Blob([html], { type: 'text/html' });
|
|
95
|
+
const url = URL.createObjectURL(blob);
|
|
96
|
+
const a = document.createElement('a');
|
|
97
|
+
a.href = url;
|
|
98
|
+
a.download = 'document.html';
|
|
99
|
+
a.click();
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<TextEditor
|
|
104
|
+
showButtons={true}
|
|
105
|
+
showSaveTitle={true}
|
|
106
|
+
showStatusBar={true}
|
|
107
|
+
onSave={handleSave}
|
|
108
|
+
onExport={handleExport}
|
|
109
|
+
onChange={(content, html, title) => {
|
|
110
|
+
// Real-time updates
|
|
111
|
+
console.log("Changes:", { content, html, title });
|
|
112
|
+
}}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Read-only Mode
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import '@abduljebar/text-editor/dist/index.css';
|
|
122
|
+
import { TextEditor } from "@abduljebar/text-editor";
|
|
123
|
+
|
|
124
|
+
function ReadOnlyView() {
|
|
125
|
+
return (
|
|
126
|
+
<TextEditor
|
|
127
|
+
readOnly={true}
|
|
128
|
+
initialContent="<h1>Published Article</h1><p>This content cannot be edited.</p>"
|
|
129
|
+
showStatusBar={true}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### With Image Upload
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import '@abduljebar/text-editor/dist/index.css';
|
|
139
|
+
import { TextEditor } from "@abduljebar/text-editor";
|
|
140
|
+
|
|
141
|
+
function EditorWithImages() {
|
|
142
|
+
const handleImageUpload = async (file: File) => {
|
|
143
|
+
// Upload to your backend
|
|
144
|
+
const formData = new FormData();
|
|
145
|
+
formData.append('image', file);
|
|
146
|
+
|
|
147
|
+
const response = await fetch('/api/upload', {
|
|
148
|
+
method: 'POST',
|
|
149
|
+
body: formData,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const data = await response.json();
|
|
153
|
+
return data.url; // Return the uploaded image URL
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<TextEditor
|
|
158
|
+
showButtons={true}
|
|
159
|
+
onImageUpload={handleImageUpload}
|
|
160
|
+
allowedImageTypes={['image/jpeg', 'image/png', 'image/webp']}
|
|
161
|
+
maxImageSize={10 * 1024 * 1024} // 10MB
|
|
162
|
+
/>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## ⚙️ API Reference
|
|
168
|
+
|
|
169
|
+
### TextEditor Props
|
|
170
|
+
|
|
171
|
+
| Prop | Type | Default | Description |
|
|
172
|
+
|------|------|---------|-------------|
|
|
173
|
+
| `initialContent` | `string` | `""` | Initial HTML content for the editor |
|
|
174
|
+
| `onChange` | `(content: string, html: string, title?: string) => void` | `undefined` | Callback when content changes |
|
|
175
|
+
| `onSave` | `(content: string, html: string) => void` | `undefined` | Callback when save is triggered |
|
|
176
|
+
| `onExport` | `(html: string) => void` | `undefined` | Callback when export is triggered |
|
|
177
|
+
| `onImageUpload` | `(file: File) => Promise<string>` | `undefined` | Custom image upload handler |
|
|
178
|
+
| `imageUploadEndpoint` | `string` | `undefined` | Endpoint for default image upload |
|
|
179
|
+
| `readOnly` | `boolean` | `false` | Disable editing when true |
|
|
180
|
+
| `showButtons` | `boolean` | `false` | Show save/export buttons |
|
|
181
|
+
| `showSaveTitle` | `boolean` | `false` | Show document title input |
|
|
182
|
+
| `showStatusBar` | `boolean` | `false` | Show word/character count |
|
|
183
|
+
| `height` | `string` | `"500px"` | Editor height (any CSS value) |
|
|
184
|
+
| `allowedImageTypes` | `string[]` | `["image/jpeg","image/png","image/gif","image/webp"]` | Allowed image MIME types |
|
|
185
|
+
| `maxImageSize` | `number` | `5242880` (5MB) | Maximum image size in bytes |
|
|
186
|
+
| `debounceDelay` | `number` | `300` | Debounce delay for onChange in ms |
|
|
187
|
+
| `className` | `string` | `""` | Additional CSS class name |
|
|
188
|
+
| `placeholder` | `string` | `"Start typing here..."` | Placeholder text when empty |
|
|
189
|
+
| `autoFocus` | `boolean` | `false` | Auto-focus editor on load |
|
|
190
|
+
| `onInit` | `(editor: HTMLDivElement) => void` | `undefined` | Callback after editor initialization |
|
|
191
|
+
|
|
192
|
+
### TextEditor Ref API
|
|
193
|
+
|
|
194
|
+
The component exposes a ref with the following methods:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
interface TextEditorRef {
|
|
198
|
+
getContent: () => string;
|
|
199
|
+
getHTML: () => string;
|
|
200
|
+
getTitle: () => string;
|
|
201
|
+
clear: () => void;
|
|
202
|
+
focus: () => void;
|
|
203
|
+
insertText: (text: string) => void;
|
|
204
|
+
insertHTML: (html: string) => void;
|
|
205
|
+
executeCommand: (command: string, value?: string) => void;
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Example usage:
|
|
210
|
+
```tsx
|
|
211
|
+
import { useRef } from 'react';
|
|
212
|
+
import { TextEditor, TextEditorRef } from "@abduljebar/text-editor";
|
|
213
|
+
|
|
214
|
+
function EditorWithRef() {
|
|
215
|
+
const editorRef = useRef<TextEditorRef>(null);
|
|
216
|
+
|
|
217
|
+
const handleGetContent = () => {
|
|
218
|
+
if (editorRef.current) {
|
|
219
|
+
const content = editorRef.current.getContent();
|
|
220
|
+
const html = editorRef.current.getHTML();
|
|
221
|
+
console.log({ content, html });
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<>
|
|
227
|
+
<TextEditor ref={editorRef} />
|
|
228
|
+
<button onClick={handleGetContent}>Get Content</button>
|
|
229
|
+
</>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### useTextEditor Hook
|
|
235
|
+
|
|
236
|
+
For advanced usage and custom implementations:
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
import '@abduljebar/text-editor/dist/index.css';
|
|
240
|
+
import { useTextEditor } from "@abduljebar/text-editor";
|
|
241
|
+
|
|
242
|
+
function CustomEditor() {
|
|
243
|
+
const {
|
|
244
|
+
editorState,
|
|
245
|
+
editorRef,
|
|
246
|
+
updateContent,
|
|
247
|
+
updateTitle,
|
|
248
|
+
executeCommand,
|
|
249
|
+
getValidationResult,
|
|
250
|
+
exportToHTML,
|
|
251
|
+
clearEditor,
|
|
252
|
+
handlePaste,
|
|
253
|
+
handleDrop,
|
|
254
|
+
insertImage,
|
|
255
|
+
uploadPendingImages,
|
|
256
|
+
} = useTextEditor({
|
|
257
|
+
initialContent: "Initial content",
|
|
258
|
+
onImageUpload: async (file) => {
|
|
259
|
+
// Custom upload logic
|
|
260
|
+
return "https://example.com/image.jpg";
|
|
261
|
+
},
|
|
262
|
+
allowedImageTypes: ["image/jpeg", "image/png"],
|
|
263
|
+
maxImageSize: 10 * 1024 * 1024,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
return (
|
|
267
|
+
<div>
|
|
268
|
+
<div
|
|
269
|
+
ref={editorRef}
|
|
270
|
+
contentEditable
|
|
271
|
+
onInput={(e) => updateContent(e.currentTarget.innerHTML)}
|
|
272
|
+
className="border p-4 min-h-[200px]"
|
|
273
|
+
onPaste={handlePaste}
|
|
274
|
+
onDrop={handleDrop}
|
|
275
|
+
/>
|
|
276
|
+
<button onClick={() => executeCommand('bold')}>
|
|
277
|
+
Bold
|
|
278
|
+
</button>
|
|
279
|
+
<button onClick={() => insertImage(someFile)}>
|
|
280
|
+
Insert Image
|
|
281
|
+
</button>
|
|
282
|
+
</div>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
#### Hook Parameters
|
|
288
|
+
|
|
289
|
+
| Parameter | Type | Default | Description |
|
|
290
|
+
|-----------|------|---------|-------------|
|
|
291
|
+
| `initialContent` | `string` | `""` | Initial HTML content |
|
|
292
|
+
| `onImageUpload` | `(file: File) => Promise<string>` | `undefined` | Custom image upload handler |
|
|
293
|
+
| `imageUploadEndpoint` | `string` | `undefined` | Endpoint for default image upload |
|
|
294
|
+
| `allowedImageTypes` | `string[]` | `["image/jpeg","image/png","image/gif","image/webp"]` | Allowed image types |
|
|
295
|
+
| `maxImageSize` | `number` | `5242880` | Max image size in bytes |
|
|
296
|
+
|
|
297
|
+
#### Hook Return Values
|
|
298
|
+
|
|
299
|
+
| Property | Type | Description |
|
|
300
|
+
|----------|------|-------------|
|
|
301
|
+
| `editorState` | `object` | Current editor state with content, title, counts, pending images |
|
|
302
|
+
| `editorRef` | `RefObject<HTMLDivElement>` | Reference to the editable element |
|
|
303
|
+
| `updateContent` | `(content: string) => void` | Update editor content |
|
|
304
|
+
| `updateTitle` | `(title: string) => void` | Update document title |
|
|
305
|
+
| `executeCommand` | `(command: string, value?: string) => void` | Execute formatting commands |
|
|
306
|
+
| `getValidationResult` | `() => ValidationResult` | Validate and get editor data |
|
|
307
|
+
| `exportToHTML` | `(options?) => string` | Generate HTML export |
|
|
308
|
+
| `clearEditor` | `() => void` | Clear all content |
|
|
309
|
+
| `handlePaste` | `(e: React.ClipboardEvent) => void` | Handle paste events |
|
|
310
|
+
| `handleDrop` | `(e: React.DragEvent) => void` | Handle drop events |
|
|
311
|
+
| `insertImage` | `(file: File, atCursor?: boolean) => Promise<void>` | Insert image into editor |
|
|
312
|
+
| `uploadPendingImages` | `() => Promise<void>` | Upload all pending images |
|
|
313
|
+
|
|
314
|
+
## 🎨 Styling & Customization
|
|
315
|
+
|
|
316
|
+
### Default Styling
|
|
317
|
+
|
|
318
|
+
The editor comes with beautiful default styling:
|
|
319
|
+
|
|
320
|
+
- **Headings**: Proper hierarchy with appropriate sizing
|
|
321
|
+
- **Paragraphs**: Optimal line height and margins
|
|
322
|
+
- **Lists**: Clean indentation and spacing
|
|
323
|
+
- **Code Blocks**: Proper monospace fonts
|
|
324
|
+
- **Quotes**: Elegant bordered design
|
|
325
|
+
- **Links**: Proper styling with hover effects
|
|
326
|
+
- **Images**: Responsive with rounded corners
|
|
327
|
+
|
|
328
|
+
### Custom Styling
|
|
329
|
+
|
|
330
|
+
You can override the default styles by targeting the editor's CSS classes or using the `className` prop:
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
import '@abduljebar/text-editor/dist/index.css';
|
|
334
|
+
import { TextEditor } from "@abduljebar/text-editor";
|
|
335
|
+
|
|
336
|
+
<TextEditor
|
|
337
|
+
className="my-custom-editor"
|
|
338
|
+
// ... other props
|
|
339
|
+
/>
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
```css
|
|
343
|
+
.my-custom-editor {
|
|
344
|
+
border: 2px solid #4f46e5;
|
|
345
|
+
border-radius: 12px;
|
|
346
|
+
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.my-custom-editor h1 {
|
|
350
|
+
color: #4f46e5;
|
|
351
|
+
border-bottom: 2px solid #e0e7ff;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.my-custom-editor .toolbar {
|
|
355
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## 🔧 Advanced Examples
|
|
360
|
+
|
|
361
|
+
### Integration with Form Libraries
|
|
362
|
+
|
|
363
|
+
```tsx
|
|
364
|
+
import '@abduljebar/text-editor/dist/index.css';
|
|
365
|
+
import { useForm } from 'react-hook-form';
|
|
366
|
+
import { TextEditor } from "@abduljebar/text-editor";
|
|
367
|
+
|
|
368
|
+
function ArticleForm() {
|
|
369
|
+
const { register, handleSubmit, setValue, watch } = useForm();
|
|
370
|
+
|
|
371
|
+
const handleEditorChange = (content: string, html: string, title?: string) => {
|
|
372
|
+
setValue('content', html);
|
|
373
|
+
setValue('title', title);
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
return (
|
|
377
|
+
<form onSubmit={handleSubmit(data => console.log(data))}>
|
|
378
|
+
<TextEditor
|
|
379
|
+
showSaveTitle={true}
|
|
380
|
+
showButtons={true}
|
|
381
|
+
onChange={handleEditorChange}
|
|
382
|
+
onSave={(content, html) => {
|
|
383
|
+
// Handle form submission
|
|
384
|
+
handleSubmit(data => console.log(data))();
|
|
385
|
+
}}
|
|
386
|
+
/>
|
|
387
|
+
</form>
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Custom Toolbar Implementation
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
import '@abduljebar/text-editor/dist/index.css';
|
|
396
|
+
import { useTextEditor } from "@abduljebar/text-editor";
|
|
397
|
+
|
|
398
|
+
function CustomToolbarEditor() {
|
|
399
|
+
const { executeCommand, editorRef, insertImage } = useTextEditor({
|
|
400
|
+
initialContent: "Start typing...",
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
404
|
+
const file = e.target.files?.[0];
|
|
405
|
+
if (file) {
|
|
406
|
+
insertImage(file);
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
return (
|
|
411
|
+
<div className="editor-container">
|
|
412
|
+
<div className="custom-toolbar">
|
|
413
|
+
<button onClick={() => executeCommand('bold')}>Bold</button>
|
|
414
|
+
<button onClick={() => executeCommand('italic')}>Italic</button>
|
|
415
|
+
<button onClick={() => executeCommand('formatBlock', 'h1')}>H1</button>
|
|
416
|
+
<button onClick={() => executeCommand('formatBlock', 'h2')}>H2</button>
|
|
417
|
+
<input type="file" accept="image/*" onChange={handleFileSelect} />
|
|
418
|
+
</div>
|
|
419
|
+
<div
|
|
420
|
+
ref={editorRef}
|
|
421
|
+
contentEditable
|
|
422
|
+
className="editor-content"
|
|
423
|
+
/>
|
|
424
|
+
</div>
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Programmatic Control
|
|
430
|
+
|
|
431
|
+
```tsx
|
|
432
|
+
import { useRef, useEffect } from 'react';
|
|
433
|
+
import { TextEditor, TextEditorRef } from "@abduljebar/text-editor";
|
|
434
|
+
|
|
435
|
+
function ProgrammaticEditor() {
|
|
436
|
+
const editorRef = useRef<TextEditorRef>(null);
|
|
437
|
+
|
|
438
|
+
useEffect(() => {
|
|
439
|
+
// Example: Auto-insert content after 2 seconds
|
|
440
|
+
const timer = setTimeout(() => {
|
|
441
|
+
if (editorRef.current) {
|
|
442
|
+
editorRef.current.insertText("Hello, world!");
|
|
443
|
+
editorRef.current.focus();
|
|
444
|
+
}
|
|
445
|
+
}, 2000);
|
|
446
|
+
|
|
447
|
+
return () => clearTimeout(timer);
|
|
448
|
+
}, []);
|
|
449
|
+
|
|
450
|
+
const handleCommand = (command: string) => {
|
|
451
|
+
if (editorRef.current) {
|
|
452
|
+
editorRef.current.executeCommand(command);
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
return (
|
|
457
|
+
<div>
|
|
458
|
+
<TextEditor ref={editorRef} />
|
|
459
|
+
<div className="mt-4">
|
|
460
|
+
<button onClick={() => handleCommand('bold')}>Make Selection Bold</button>
|
|
461
|
+
<button onClick={() => editorRef.current?.clear()}>Clear Editor</button>
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## 📋 Browser Support
|
|
469
|
+
|
|
470
|
+
- Chrome 60+
|
|
471
|
+
- Firefox 55+
|
|
472
|
+
- Safari 12+
|
|
473
|
+
- Edge 79+
|
|
474
|
+
- Opera 47+
|
|
475
|
+
|
|
476
|
+
## 🔒 Accessibility
|
|
477
|
+
|
|
478
|
+
- Keyboard navigation support
|
|
479
|
+
- ARIA labels for toolbar buttons
|
|
480
|
+
- Focus management
|
|
481
|
+
- Screen reader compatible
|
|
482
|
+
- Proper semantic HTML structure
|
|
483
|
+
|
|
484
|
+
## 🐛 Troubleshooting
|
|
485
|
+
|
|
486
|
+
### Common Issues
|
|
487
|
+
|
|
488
|
+
1. **Toolbar buttons not working**: Ensure the editor is focused and content is selected
|
|
489
|
+
2. **Images not uploading**: Check CORS settings and upload endpoint configuration
|
|
490
|
+
3. **Styles not appearing**: Import the CSS file: `import '@abduljebar/text-editor/dist/index.css';`
|
|
491
|
+
4. **Content not saving**: Check `onSave` callback and validation messages
|
|
492
|
+
5. **Formatting lost on paste**: Use the built-in `handlePaste` function
|
|
493
|
+
|
|
494
|
+
### Performance Tips
|
|
495
|
+
|
|
496
|
+
- Use appropriate `debounceDelay` for `onChange` to prevent excessive updates
|
|
497
|
+
- Implement proper image compression before upload
|
|
498
|
+
- Consider using `React.memo` if embedding in frequently re-rendering components
|
|
499
|
+
- Use the ref API for programmatic control instead of frequent state updates
|
|
500
|
+
|
|
501
|
+
## 📄 Supported Commands
|
|
502
|
+
|
|
503
|
+
The editor supports standard `document.execCommand` APIs:
|
|
504
|
+
|
|
505
|
+
- **Formatting**: `bold`, `italic`, `underline`, `strikeThrough`
|
|
506
|
+
- **Headings**: `formatBlock` (with `h1`, `h2`, `h3`, `p` values)
|
|
507
|
+
- **Lists**: `insertUnorderedList`, `insertOrderedList`
|
|
508
|
+
- **Alignment**: `justifyLeft`, `justifyCenter`, `justifyRight`
|
|
509
|
+
- **Indentation**: `indent`, `outdent`
|
|
510
|
+
- **Links**: `createLink` (with URL value)
|
|
511
|
+
- **History**: `undo`, `redo`
|
|
512
|
+
- **Special**: `superscript`, `subscript`, `formatBlock` (with `blockquote`, `pre` values)
|
|
513
|
+
|
|
514
|
+
## 🤝 Contributing
|
|
515
|
+
|
|
516
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
517
|
+
|
|
518
|
+
1. Fork the repository
|
|
519
|
+
2. Create a feature branch
|
|
520
|
+
3. Make your changes
|
|
521
|
+
4. Add tests if applicable
|
|
522
|
+
5. Submit a pull request
|
|
523
|
+
|
|
524
|
+
## 📄 License
|
|
525
|
+
|
|
526
|
+
MIT License - see the [LICENSE](LICENSE) file for details.
|
|
527
|
+
|
|
528
|
+
## 🆘 Support
|
|
529
|
+
|
|
530
|
+
If you encounter any issues or have questions:
|
|
531
|
+
|
|
532
|
+
1. Check the documentation above
|
|
533
|
+
2. Search existing GitHub issues
|
|
534
|
+
3. Create a new issue with:
|
|
535
|
+
- A clear description of the problem
|
|
536
|
+
- Steps to reproduce
|
|
537
|
+
- Expected vs actual behavior
|
|
538
|
+
- Code examples if applicable
|
|
539
|
+
|
|
540
|
+
## 🚀 Changelog
|
|
541
|
+
|
|
542
|
+
### v1.1.0
|
|
543
|
+
- Added image upload support with drag & drop
|
|
544
|
+
- Improved toolbar with better selection tracking
|
|
545
|
+
- Added ref API for programmatic control
|
|
546
|
+
- Enhanced keyboard shortcuts
|
|
547
|
+
- Better validation and error handling
|
|
548
|
+
- Added pending images tracking
|
|
549
|
+
|
|
550
|
+
### v1.0.0
|
|
551
|
+
- Initial release with core editing features
|
|
552
|
+
- Basic text formatting and styling
|
|
553
|
+
- HTML export functionality
|
|
554
|
+
- React hook for advanced usage
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
Built with ❤️ by [AbdulJebar Sani](https://github.com/abduljebar49)
|
|
559
|
+
|
|
560
|
+
## 🔗 Links
|
|
561
|
+
|
|
562
|
+
- [GitHub Repository](https://github.com/abduljebar/text-editor)
|
|
563
|
+
- [npm Package](https://www.npmjs.com/package/@abduljebar/text-editor)
|
|
564
|
+
- [Issue Tracker](https://github.com/abduljebar/text-editor/issues)
|
|
565
565
|
- [Documentation](https://github.com/abduljebar/text-editor#readme)
|