@crashbytes/contentful-richtext-editor 1.0.7 → 1.0.9
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 +433 -204
- package/dist/components/ContentfulEditor.d.ts +8 -5
- package/dist/components/ContentfulEditor.stories.d.ts +18 -0
- package/dist/components/ContentfulEmbedded.d.ts +12 -0
- package/dist/components/Toolbar.d.ts +2 -0
- package/dist/components/Toolbar.stories.d.ts +10 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +5 -44
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +1031 -1608
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1034 -1616
- package/dist/index.js.map +1 -1
- package/dist/testData/samples.d.ts +11 -0
- package/dist/utils/configParser.d.ts +37 -0
- package/dist/utils/contentfulTransform.d.ts +23 -11
- package/dist/utils/contentfulTransform.stories.d.ts +6 -0
- package/dist/utils/types.d.ts +113 -0
- package/package.json +17 -11
package/README.md
CHANGED
|
@@ -1,304 +1,533 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Contentful Rich Text Editor
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A React component that provides a rich text editor compatible with Contentful's Rich Text field type, with automatic configuration based on Contentful field settings.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
7
|
+
- 🔄 **Automatic Configuration**: Reads Contentful field validation settings to automatically enable/disable editor features
|
|
8
|
+
- 📝 **Rich Text Support**: Full support for headings, lists, tables, quotes, and text formatting
|
|
9
|
+
- 🔗 **Hyperlinks**: Configurable link support with URL validation
|
|
10
|
+
- 📎 **Embeds**: Support for embedded entries, assets, and inline entries
|
|
11
|
+
- 🎨 **Themes**: Multiple built-in themes (contentful, minimal, default)
|
|
12
|
+
- ♿ **Accessibility**: Built with accessibility in mind
|
|
13
|
+
- 🔧 **Customizable**: Extensive configuration options and styling hooks
|
|
14
|
+
- 📱 **Responsive**: Works well on mobile and desktop
|
|
15
|
+
- ⌨️ **Keyboard Shortcuts**: Built-in shortcuts for common actions
|
|
16
|
+
- 🔍 **TypeScript**: Full TypeScript support with comprehensive type definitions
|
|
13
17
|
|
|
14
|
-
##
|
|
18
|
+
## Installation
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- **Quotes**: Blockquotes
|
|
22
|
-
- **Embedded Content**: Callbacks for Contentful entries and assets
|
|
23
|
-
- **Undo/Redo**: Full history support
|
|
20
|
+
```bash
|
|
21
|
+
npm install @your-org/contentful-rich-text-editor
|
|
22
|
+
# or
|
|
23
|
+
yarn add @your-org/contentful-rich-text-editor
|
|
24
|
+
```
|
|
24
25
|
|
|
25
|
-
##
|
|
26
|
+
## Dependencies
|
|
27
|
+
|
|
28
|
+
This component requires the following peer dependencies:
|
|
26
29
|
|
|
27
30
|
```bash
|
|
28
|
-
npm install @
|
|
31
|
+
npm install react @tiptap/react @tiptap/starter-kit @contentful/rich-text-types
|
|
29
32
|
```
|
|
30
33
|
|
|
31
|
-
##
|
|
34
|
+
## Quick Start
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
### Basic Usage
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
34
39
|
import React, { useState } from 'react';
|
|
35
|
-
import { ContentfulRichTextEditor } from '@
|
|
36
|
-
import '@crashbytes/contentful-richtext-editor/dist/index.css';
|
|
40
|
+
import { ContentfulRichTextEditor } from '@your-org/contentful-rich-text-editor';
|
|
37
41
|
import { Document } from '@contentful/rich-text-types';
|
|
38
42
|
|
|
39
|
-
function
|
|
40
|
-
const [content, setContent] = useState();
|
|
41
|
-
|
|
42
|
-
const handleChange = (document) => {
|
|
43
|
-
setContent(document);
|
|
44
|
-
console.log('Contentful document:', document);
|
|
45
|
-
};
|
|
43
|
+
function MyEditor() {
|
|
44
|
+
const [content, setContent] = useState<Document>();
|
|
46
45
|
|
|
47
46
|
return (
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
initialValue={content}
|
|
54
|
-
/>
|
|
55
|
-
</div>
|
|
47
|
+
<ContentfulRichTextEditor
|
|
48
|
+
initialValue={content}
|
|
49
|
+
onChange={setContent}
|
|
50
|
+
placeholder="Start writing..."
|
|
51
|
+
/>
|
|
56
52
|
);
|
|
57
53
|
}
|
|
58
|
-
|
|
59
|
-
export default App;
|
|
60
54
|
```
|
|
61
55
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
### With Contentful Entry/Asset Embedding
|
|
56
|
+
### With Contentful Configuration
|
|
65
57
|
|
|
66
|
-
```
|
|
67
|
-
import {
|
|
68
|
-
import
|
|
58
|
+
```tsx
|
|
59
|
+
import React, { useEffect, useState } from 'react';
|
|
60
|
+
import {
|
|
61
|
+
ContentfulRichTextEditor,
|
|
62
|
+
fetchContentfulFieldConfig,
|
|
63
|
+
ContentfulFieldConfiguration
|
|
64
|
+
} from '@your-org/contentful-rich-text-editor';
|
|
69
65
|
|
|
70
66
|
function ContentfulEditor() {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
};
|
|
67
|
+
const [fieldConfig, setFieldConfig] = useState<ContentfulFieldConfiguration>();
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
// Fetch configuration from Contentful Management API
|
|
71
|
+
fetchContentfulFieldConfig(
|
|
72
|
+
'your-space-id',
|
|
73
|
+
'your-content-type-id',
|
|
74
|
+
'your-field-id',
|
|
75
|
+
'your-management-token'
|
|
76
|
+
).then(setFieldConfig);
|
|
77
|
+
}, []);
|
|
82
78
|
|
|
83
79
|
return (
|
|
84
80
|
<ContentfulRichTextEditor
|
|
85
|
-
|
|
86
|
-
onChange={(doc) =>
|
|
81
|
+
fieldConfiguration={fieldConfig}
|
|
82
|
+
onChange={(doc) => console.log('Content changed:', doc)}
|
|
87
83
|
onEmbedEntry={handleEmbedEntry}
|
|
88
84
|
onEmbedAsset={handleEmbedAsset}
|
|
89
|
-
|
|
85
|
+
placeholder="Start writing..."
|
|
90
86
|
/>
|
|
91
87
|
);
|
|
92
88
|
}
|
|
93
89
|
```
|
|
94
90
|
|
|
95
|
-
### Customizing Features
|
|
96
|
-
|
|
97
|
-
```jsx
|
|
98
|
-
<ContentfulRichTextEditor
|
|
99
|
-
placeholder="Simple editor..."
|
|
100
|
-
disabledFeatures={['table', 'embed', 'quote']}
|
|
101
|
-
theme="minimal"
|
|
102
|
-
readonly={false}
|
|
103
|
-
onChange={handleChange}
|
|
104
|
-
/>
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### Controlling Available Headings and Formatting
|
|
108
|
-
|
|
109
|
-
```jsx
|
|
110
|
-
<ContentfulRichTextEditor
|
|
111
|
-
placeholder="Limited editor..."
|
|
112
|
-
availableHeadings={[1, 2, 3]} // Only H1, H2, H3
|
|
113
|
-
availableMarks={['bold', 'italic']} // Only bold and italic, no underline
|
|
114
|
-
onChange={handleChange}
|
|
115
|
-
/>
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### With Initial Content
|
|
119
|
-
|
|
120
|
-
```jsx
|
|
121
|
-
import { createEmptyDocument } from '@crashbytes/contentful-richtext-editor';
|
|
122
|
-
|
|
123
|
-
const initialContent = {
|
|
124
|
-
nodeType: 'document',
|
|
125
|
-
data: {},
|
|
126
|
-
content: [
|
|
127
|
-
{
|
|
128
|
-
nodeType: 'paragraph',
|
|
129
|
-
data: {},
|
|
130
|
-
content: [
|
|
131
|
-
{
|
|
132
|
-
nodeType: 'text',
|
|
133
|
-
value: 'Hello world!',
|
|
134
|
-
marks: [{ type: 'bold' }],
|
|
135
|
-
data: {}
|
|
136
|
-
}
|
|
137
|
-
]
|
|
138
|
-
}
|
|
139
|
-
]
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
<ContentfulRichTextEditor
|
|
143
|
-
initialValue={initialContent}
|
|
144
|
-
onChange={handleChange}
|
|
145
|
-
/>
|
|
146
|
-
```
|
|
147
|
-
|
|
148
91
|
## API Reference
|
|
149
92
|
|
|
150
|
-
### Props
|
|
93
|
+
### ContentfulRichTextEditor Props
|
|
151
94
|
|
|
152
95
|
| Prop | Type | Default | Description |
|
|
153
96
|
|------|------|---------|-------------|
|
|
154
97
|
| `initialValue` | `Document` | `undefined` | Initial Contentful rich text document |
|
|
155
98
|
| `onChange` | `(document: Document) => void` | `undefined` | Callback when content changes |
|
|
156
|
-
| `onEmbedEntry` | `() => Promise<any
|
|
157
|
-
| `onEmbedAsset` | `() => Promise<any
|
|
158
|
-
| `
|
|
99
|
+
| `onEmbedEntry` | `() => Promise<any>` | `undefined` | Callback for embedding entries |
|
|
100
|
+
| `onEmbedAsset` | `() => Promise<any>` | `undefined` | Callback for embedding assets |
|
|
101
|
+
| `onEmbedInlineEntry` | `() => Promise<any>` | `undefined` | Callback for embedding inline entries |
|
|
102
|
+
| `fieldConfiguration` | `ContentfulFieldConfiguration` | `undefined` | Contentful field validation config |
|
|
159
103
|
| `readonly` | `boolean` | `false` | Whether editor is read-only |
|
|
104
|
+
| `placeholder` | `string` | `'Start writing...'` | Placeholder text |
|
|
105
|
+
| `theme` | `'contentful' \| 'minimal' \| 'default'` | `'contentful'` | Editor theme |
|
|
160
106
|
| `className` | `string` | `''` | Additional CSS classes |
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `availableMarks` | `Array<'bold'\|'italic'\|'underline'>` | `['bold','italic','underline']` | Which text formatting options to show |
|
|
107
|
+
| `availableHeadings` | `Array<1\|2\|3\|4\|5\|6>` | `[1,2,3,4,5,6]` | Available heading levels (fallback) |
|
|
108
|
+
| `availableMarks` | `Array<'bold'\|'italic'\|'underline'>` | `['bold','italic','underline']` | Available text marks (fallback) |
|
|
109
|
+
| `disabledFeatures` | `Array<string>` | `[]` | Manually disabled features (fallback) |
|
|
165
110
|
|
|
166
|
-
###
|
|
111
|
+
### Configuration Types
|
|
167
112
|
|
|
168
|
-
|
|
113
|
+
```tsx
|
|
114
|
+
interface ContentfulFieldConfiguration {
|
|
115
|
+
validations?: Array<{
|
|
116
|
+
enabledMarks?: string[];
|
|
117
|
+
enabledNodeTypes?: string[];
|
|
118
|
+
}>;
|
|
119
|
+
settings?: {
|
|
120
|
+
helpText?: string;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
```
|
|
169
124
|
|
|
170
|
-
|
|
171
|
-
- `'italic'` - Italic text formatting
|
|
172
|
-
- `'underline'` - Underline text formatting
|
|
173
|
-
- `'link'` - Hyperlinks
|
|
174
|
-
- `'lists'` - Ordered and unordered lists
|
|
175
|
-
- `'headings'` - All heading levels
|
|
176
|
-
- `'quote'` - Blockquotes
|
|
177
|
-
- `'table'` - Tables
|
|
178
|
-
- `'embed'` - Embedded content
|
|
125
|
+
### Supported Node Types
|
|
179
126
|
|
|
180
|
-
|
|
127
|
+
- `paragraph` - Regular paragraphs
|
|
128
|
+
- `heading-1` through `heading-6` - Headings
|
|
129
|
+
- `unordered-list`, `ordered-list` - Lists
|
|
130
|
+
- `blockquote` - Quotes
|
|
131
|
+
- `table` - Tables
|
|
132
|
+
- `hyperlink` - Links
|
|
133
|
+
- `embedded-entry-block` - Block-level entry embeds
|
|
134
|
+
- `embedded-asset-block` - Block-level asset embeds
|
|
135
|
+
- `embedded-entry-inline` - Inline entry embeds
|
|
136
|
+
- `hr` - Horizontal rules
|
|
181
137
|
|
|
182
|
-
|
|
183
|
-
import {
|
|
184
|
-
contentfulToTiptap,
|
|
185
|
-
tiptapToContentful,
|
|
186
|
-
validateContentfulDocument,
|
|
187
|
-
createEmptyDocument
|
|
188
|
-
} from '@crashbytes/contentful-richtext-editor';
|
|
138
|
+
### Supported Marks
|
|
189
139
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
140
|
+
- `bold` - Bold text
|
|
141
|
+
- `italic` - Italic text
|
|
142
|
+
- `underline` - Underlined text
|
|
143
|
+
- `code` - Inline code
|
|
193
144
|
|
|
194
|
-
|
|
195
|
-
const isValid = validateContentfulDocument(someDocument);
|
|
145
|
+
## Configuration Examples
|
|
196
146
|
|
|
197
|
-
|
|
198
|
-
const emptyDoc = createEmptyDocument();
|
|
199
|
-
```
|
|
147
|
+
### Mock Configuration for Testing
|
|
200
148
|
|
|
201
|
-
|
|
149
|
+
```tsx
|
|
150
|
+
import { createMockFieldConfig } from '@your-org/contentful-rich-text-editor';
|
|
151
|
+
|
|
152
|
+
const mockConfig = createMockFieldConfig({
|
|
153
|
+
enabledMarks: ['bold', 'italic'],
|
|
154
|
+
enabledNodeTypes: [
|
|
155
|
+
'paragraph',
|
|
156
|
+
'heading-1',
|
|
157
|
+
'heading-2',
|
|
158
|
+
'unordered-list',
|
|
159
|
+
'hyperlink',
|
|
160
|
+
'embedded-entry-block'
|
|
161
|
+
]
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
<ContentfulRichTextEditor fieldConfiguration={mockConfig} />
|
|
165
|
+
```
|
|
202
166
|
|
|
203
|
-
|
|
167
|
+
### Manual Configuration (Legacy)
|
|
204
168
|
|
|
205
|
-
```
|
|
206
|
-
|
|
169
|
+
```tsx
|
|
170
|
+
<ContentfulRichTextEditor
|
|
171
|
+
availableHeadings={[1, 2, 3]}
|
|
172
|
+
availableMarks={['bold', 'italic']}
|
|
173
|
+
disabledFeatures={['table', 'embed']}
|
|
174
|
+
/>
|
|
207
175
|
```
|
|
208
176
|
|
|
209
|
-
|
|
177
|
+
## Styling
|
|
210
178
|
|
|
211
|
-
|
|
179
|
+
The editor comes with built-in CSS classes that you can override:
|
|
212
180
|
|
|
213
181
|
```css
|
|
182
|
+
/* Main editor container */
|
|
214
183
|
.contentful-editor {
|
|
215
|
-
border:
|
|
184
|
+
border: 1px solid #d3dce6;
|
|
185
|
+
border-radius: 6px;
|
|
216
186
|
}
|
|
217
187
|
|
|
188
|
+
/* Toolbar */
|
|
218
189
|
.contentful-toolbar {
|
|
219
|
-
background: #
|
|
190
|
+
background: #f7f9fa;
|
|
191
|
+
border-bottom: 1px solid #d3dce6;
|
|
220
192
|
}
|
|
221
193
|
|
|
194
|
+
/* Content area */
|
|
222
195
|
.contentful-editor-content {
|
|
223
|
-
|
|
196
|
+
padding: 16px;
|
|
197
|
+
min-height: 200px;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* Embedded content */
|
|
201
|
+
.contentful-embedded-entry {
|
|
202
|
+
background: #f0f8ff;
|
|
203
|
+
border: 1px dashed #2e75d4;
|
|
204
|
+
padding: 8px;
|
|
205
|
+
border-radius: 4px;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Inline embedded content */
|
|
209
|
+
.contentful-inline-embedded-entry {
|
|
210
|
+
background: #e8f4fd;
|
|
211
|
+
padding: 2px 4px;
|
|
212
|
+
border-radius: 3px;
|
|
213
|
+
font-weight: 600;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* Tables */
|
|
217
|
+
.contentful-table {
|
|
218
|
+
border-collapse: collapse;
|
|
219
|
+
width: 100%;
|
|
220
|
+
margin: 1em 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.contentful-table-header {
|
|
224
|
+
background: #f7f9fa;
|
|
225
|
+
font-weight: 600;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Lists */
|
|
229
|
+
.contentful-bullet-list,
|
|
230
|
+
.contentful-ordered-list {
|
|
231
|
+
margin: 1em 0;
|
|
232
|
+
padding-left: 1.5em;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* Quotes */
|
|
236
|
+
.contentful-blockquote {
|
|
237
|
+
border-left: 4px solid #2e75d4;
|
|
238
|
+
margin: 1em 0;
|
|
239
|
+
padding-left: 1em;
|
|
240
|
+
font-style: italic;
|
|
224
241
|
}
|
|
225
242
|
```
|
|
226
243
|
|
|
227
|
-
##
|
|
244
|
+
## Advanced Usage
|
|
228
245
|
|
|
229
|
-
###
|
|
230
|
-
Matches Contentful's native editor appearance.
|
|
246
|
+
### Custom Embed Handlers
|
|
231
247
|
|
|
232
|
-
|
|
233
|
-
|
|
248
|
+
```tsx
|
|
249
|
+
const handleEmbedEntry = async () => {
|
|
250
|
+
// Open your entry selector modal/component
|
|
251
|
+
const selectedEntry = await openEntrySelector();
|
|
252
|
+
return {
|
|
253
|
+
sys: { id: selectedEntry.id, type: 'Entry' },
|
|
254
|
+
fields: { title: selectedEntry.title }
|
|
255
|
+
};
|
|
256
|
+
};
|
|
234
257
|
|
|
235
|
-
|
|
236
|
-
|
|
258
|
+
const handleEmbedAsset = async () => {
|
|
259
|
+
const selectedAsset = await openAssetSelector();
|
|
260
|
+
return {
|
|
261
|
+
sys: { id: selectedAsset.id, type: 'Asset' },
|
|
262
|
+
fields: {
|
|
263
|
+
title: selectedAsset.title,
|
|
264
|
+
file: { url: selectedAsset.url }
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const handleEmbedInlineEntry = async () => {
|
|
270
|
+
const selectedEntry = await openInlineEntrySelector();
|
|
271
|
+
return {
|
|
272
|
+
sys: { id: selectedEntry.id, type: 'Entry' },
|
|
273
|
+
fields: { name: selectedEntry.name }
|
|
274
|
+
};
|
|
275
|
+
};
|
|
276
|
+
```
|
|
237
277
|
|
|
238
|
-
|
|
278
|
+
### Keyboard Shortcuts
|
|
239
279
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
280
|
+
- `Ctrl/Cmd + B` - Bold
|
|
281
|
+
- `Ctrl/Cmd + I` - Italic
|
|
282
|
+
- `Ctrl/Cmd + U` - Underline
|
|
283
|
+
- `Ctrl/Cmd + K` - Add link
|
|
284
|
+
- `Ctrl/Cmd + Shift + E` - Embed entry
|
|
285
|
+
- `Ctrl/Cmd + Shift + A` - Embed asset
|
|
286
|
+
- `Ctrl/Cmd + Shift + I` - Embed inline entry
|
|
287
|
+
- `Ctrl/Cmd + Z` - Undo
|
|
288
|
+
- `Ctrl/Cmd + Y` / `Ctrl/Cmd + Shift + Z` - Redo
|
|
243
289
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
290
|
+
### Content Validation and Analysis
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
import {
|
|
294
|
+
validateContentfulDocument,
|
|
295
|
+
extractPlainText,
|
|
296
|
+
countWords,
|
|
297
|
+
findEmbeddedContent
|
|
298
|
+
} from '@your-org/contentful-rich-text-editor';
|
|
299
|
+
|
|
300
|
+
const handleChange = (document: Document) => {
|
|
301
|
+
// Validate document structure
|
|
302
|
+
if (validateContentfulDocument(document)) {
|
|
303
|
+
console.log('Document is valid');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Extract plain text
|
|
307
|
+
const plainText = extractPlainText(document);
|
|
308
|
+
console.log('Plain text:', plainText);
|
|
309
|
+
|
|
310
|
+
// Count words
|
|
311
|
+
const wordCount = countWords(document);
|
|
312
|
+
console.log('Word count:', wordCount);
|
|
313
|
+
|
|
314
|
+
// Find embedded content
|
|
315
|
+
const embedded = findEmbeddedContent(document);
|
|
316
|
+
console.log('Embedded content:', embedded);
|
|
317
|
+
|
|
318
|
+
saveDocument(document);
|
|
319
|
+
};
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Content Sanitization
|
|
323
|
+
|
|
324
|
+
```tsx
|
|
325
|
+
import { sanitizeContentfulDocument } from '@your-org/contentful-rich-text-editor';
|
|
326
|
+
|
|
327
|
+
// Sanitize content based on allowed features
|
|
328
|
+
const sanitizedDocument = sanitizeContentfulDocument(
|
|
329
|
+
document,
|
|
330
|
+
['paragraph', 'heading-1', 'heading-2', 'unordered-list'], // allowed nodes
|
|
331
|
+
['bold', 'italic'] // allowed marks
|
|
247
332
|
);
|
|
333
|
+
```
|
|
248
334
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
335
|
+
## Environment Variables
|
|
336
|
+
|
|
337
|
+
For API integration, set these environment variables:
|
|
338
|
+
|
|
339
|
+
```env
|
|
340
|
+
REACT_APP_CONTENTFUL_SPACE_ID=your_space_id
|
|
341
|
+
REACT_APP_CONTENTFUL_CONTENT_TYPE_ID=your_content_type_id
|
|
342
|
+
REACT_APP_CONTENTFUL_FIELD_ID=your_rich_text_field_id
|
|
343
|
+
REACT_APP_CONTENTFUL_MANAGEMENT_TOKEN=your_management_token
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Utilities
|
|
347
|
+
|
|
348
|
+
### Configuration Parser
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
import { parseContentfulFieldConfig } from '@your-org/contentful-rich-text-editor';
|
|
352
|
+
|
|
353
|
+
const config = parseContentfulFieldConfig(fieldConfiguration);
|
|
354
|
+
// Returns parsed configuration with boolean flags for each feature
|
|
355
|
+
console.log(config.allowHyperlinks); // true/false
|
|
356
|
+
console.log(config.availableHeadings); // [1, 2, 3]
|
|
357
|
+
console.log(config.disabledFeatures); // ['table', 'embed']
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Content Transformation
|
|
361
|
+
|
|
362
|
+
```tsx
|
|
363
|
+
import {
|
|
364
|
+
contentfulToTiptap,
|
|
365
|
+
tiptapToContentful
|
|
366
|
+
} from '@your-org/contentful-rich-text-editor';
|
|
367
|
+
|
|
368
|
+
// Convert between formats
|
|
369
|
+
const tiptapContent = contentfulToTiptap(contentfulDocument);
|
|
370
|
+
const contentfulContent = tiptapToContentful(tiptapDocument);
|
|
259
371
|
```
|
|
260
372
|
|
|
261
373
|
## TypeScript Support
|
|
262
374
|
|
|
263
|
-
This package is written in TypeScript and includes full type definitions. All
|
|
375
|
+
This package is written in TypeScript and includes full type definitions. All major types are exported:
|
|
264
376
|
|
|
265
377
|
```tsx
|
|
266
|
-
import type {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
378
|
+
import type {
|
|
379
|
+
ContentfulRichTextEditorProps,
|
|
380
|
+
ContentfulFieldConfiguration,
|
|
381
|
+
ParsedEditorConfig,
|
|
382
|
+
EmbeddedEntry,
|
|
383
|
+
EmbeddedAsset,
|
|
384
|
+
ContentfulDocument,
|
|
385
|
+
TiptapDocument
|
|
386
|
+
} from '@your-org/contentful-rich-text-editor';
|
|
273
387
|
```
|
|
274
388
|
|
|
275
389
|
## Browser Support
|
|
276
390
|
|
|
277
|
-
- Chrome
|
|
278
|
-
- Firefox
|
|
279
|
-
- Safari
|
|
280
|
-
- Edge
|
|
391
|
+
- Chrome 60+
|
|
392
|
+
- Firefox 55+
|
|
393
|
+
- Safari 12+
|
|
394
|
+
- Edge 79+
|
|
281
395
|
|
|
282
|
-
##
|
|
396
|
+
## Performance Considerations
|
|
397
|
+
|
|
398
|
+
- The editor uses React.memo internally for performance optimization
|
|
399
|
+
- Large documents (>1000 nodes) may experience slower rendering
|
|
400
|
+
- Consider implementing virtualization for very large content
|
|
401
|
+
- Use the `readonly` prop when displaying content to improve performance
|
|
402
|
+
|
|
403
|
+
## Accessibility
|
|
404
|
+
|
|
405
|
+
The editor is built with accessibility in mind:
|
|
283
406
|
|
|
284
|
-
|
|
407
|
+
- Full keyboard navigation support
|
|
408
|
+
- Screen reader compatible
|
|
409
|
+
- ARIA labels and descriptions
|
|
410
|
+
- High contrast mode support
|
|
411
|
+
- Focus management
|
|
412
|
+
|
|
413
|
+
## Troubleshooting
|
|
414
|
+
|
|
415
|
+
### Common Issues
|
|
416
|
+
|
|
417
|
+
1. **Editor not loading**: Check that all peer dependencies are installed
|
|
418
|
+
2. **Configuration not applying**: Ensure `fieldConfiguration` prop is properly set
|
|
419
|
+
3. **Embed callbacks not working**: Verify that embed handlers return proper Contentful objects
|
|
420
|
+
4. **Styling issues**: Check that CSS is properly imported and not conflicting
|
|
421
|
+
|
|
422
|
+
### Debug Mode
|
|
423
|
+
|
|
424
|
+
Enable debug logging:
|
|
425
|
+
|
|
426
|
+
```tsx
|
|
427
|
+
// Add to your component
|
|
428
|
+
useEffect(() => {
|
|
429
|
+
if (process.env.NODE_ENV === 'development') {
|
|
430
|
+
window.contentfulEditorDebug = true;
|
|
431
|
+
}
|
|
432
|
+
}, []);
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Contributing
|
|
285
436
|
|
|
286
437
|
1. Fork the repository
|
|
287
438
|
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
288
|
-
3. Commit your changes (`git commit -m 'Add
|
|
439
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
289
440
|
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
290
441
|
5. Open a Pull Request
|
|
291
442
|
|
|
292
|
-
|
|
443
|
+
### Development Setup
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
# Clone the repo
|
|
447
|
+
git clone https://github.com/your-org/contentful-rich-text-editor.git
|
|
448
|
+
|
|
449
|
+
# Install dependencies
|
|
450
|
+
npm install
|
|
293
451
|
|
|
294
|
-
|
|
452
|
+
# Start development server
|
|
453
|
+
npm run dev
|
|
295
454
|
|
|
296
|
-
|
|
455
|
+
# Run tests
|
|
456
|
+
npm test
|
|
297
457
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
458
|
+
# Build for production
|
|
459
|
+
npm run build
|
|
460
|
+
```
|
|
301
461
|
|
|
302
|
-
|
|
462
|
+
## License
|
|
303
463
|
|
|
304
|
-
|
|
464
|
+
MIT License
|
|
465
|
+
|
|
466
|
+
Copyright (c) 2024 [Your Name]
|
|
467
|
+
|
|
468
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
469
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
470
|
+
in the Software without restriction, including without limitation the rights
|
|
471
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
472
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
473
|
+
furnished to do so, subject to the following conditions:
|
|
474
|
+
|
|
475
|
+
The above copyright notice and this permission notice shall be included in all
|
|
476
|
+
copies or substantial portions of the Software.
|
|
477
|
+
|
|
478
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
479
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
480
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
481
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
482
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
483
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
484
|
+
SOFTWARE.
|
|
485
|
+
|
|
486
|
+
## Changelog
|
|
487
|
+
|
|
488
|
+
### v2.0.0 - 2024-01-XX
|
|
489
|
+
- ✨ **BREAKING**: Added automatic configuration from Contentful field settings
|
|
490
|
+
- ✨ Added support for inline embedded entries
|
|
491
|
+
- ✨ Added comprehensive utility functions (sanitization, analysis, validation)
|
|
492
|
+
- ✨ Added keyboard shortcuts for embed actions
|
|
493
|
+
- ✨ Enhanced TypeScript support with better type definitions
|
|
494
|
+
- ✨ Added content analysis utilities (word count, plain text extraction)
|
|
495
|
+
- 🔧 Improved accessibility with better ARIA support
|
|
496
|
+
- 🔧 Better error handling and validation
|
|
497
|
+
- 🔧 Performance optimizations
|
|
498
|
+
- 🐛 Fixed link handling in complex nested structures
|
|
499
|
+
- 🐛 Fixed table rendering issues
|
|
500
|
+
- 🐛 Various bug fixes and improvements
|
|
501
|
+
|
|
502
|
+
### v1.2.0 - 2023-12-XX
|
|
503
|
+
- ✨ Added table support
|
|
504
|
+
- ✨ Added themes (minimal, default, contentful)
|
|
505
|
+
- 🔧 Improved toolbar layout and responsiveness
|
|
506
|
+
- 🐛 Fixed undo/redo functionality
|
|
507
|
+
|
|
508
|
+
### v1.1.0 - 2023-11-XX
|
|
509
|
+
- ✨ Added embedded assets support
|
|
510
|
+
- ✨ Added link functionality
|
|
511
|
+
- 🔧 Improved content transformation
|
|
512
|
+
- 🐛 Fixed list nesting issues
|
|
513
|
+
|
|
514
|
+
### v1.0.0 - 2023-10-XX
|
|
515
|
+
- 🎉 Initial release
|
|
516
|
+
- ✨ Basic rich text editing
|
|
517
|
+
- ✨ Contentful document format support
|
|
518
|
+
- ✨ Embedded entries support
|
|
519
|
+
- ✨ Manual configuration options
|
|
520
|
+
|
|
521
|
+
## Support
|
|
522
|
+
|
|
523
|
+
For support, please:
|
|
524
|
+
|
|
525
|
+
1. Check the [documentation](https://github.com/your-org/contentful-rich-text-editor/docs)
|
|
526
|
+
2. Search [existing issues](https://github.com/your-org/contentful-rich-text-editor/issues)
|
|
527
|
+
3. Create a [new issue](https://github.com/your-org/contentful-rich-text-editor/issues/new) with detailed information
|
|
528
|
+
|
|
529
|
+
## Related Projects
|
|
530
|
+
|
|
531
|
+
- [@contentful/rich-text-renderer](https://github.com/contentful/rich-text) - Official Contentful rich text renderer
|
|
532
|
+
- [@tiptap/react](https://tiptap.dev/) - The underlying editor framework
|
|
533
|
+
- [Contentful Management API](https://www.contentful.com/developers/docs/references/content-management-api/) - For fetching field configurations
|