@crashbytes/contentful-richtext-editor 1.0.2 → 1.0.5
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 +30 -17
- package/dist/components/ContentfulEditor.d.ts +4 -0
- package/dist/components/Toolbar.d.ts +2 -0
- package/dist/index.esm.js +65 -59
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +65 -59
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,12 +4,12 @@ A modern, Tiptap-based rich text editor that's fully compatible with Contentful'
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
✅ **Full Contentful Compatibility** - Seamless conversion between Contentful and Tiptap formats
|
|
8
|
-
✅ **Modern UI** - Clean, intuitive interface matching Contentful's design
|
|
9
|
-
✅ **TypeScript Support** - Complete type safety with Contentful's rich text types
|
|
10
|
-
✅ **Extensible** - Built on Tiptap v2 for easy customization
|
|
11
|
-
✅ **Lightweight** - Tree-shakeable, only import what you need
|
|
12
|
-
✅ **Responsive** - Works on desktop and mobile devices
|
|
7
|
+
- ✅ **Full Contentful Compatibility** - Seamless conversion between Contentful and Tiptap formats
|
|
8
|
+
- ✅ **Modern UI** - Clean, intuitive interface matching Contentful's design
|
|
9
|
+
- ✅ **TypeScript Support** - Complete type safety with Contentful's rich text types
|
|
10
|
+
- ✅ **Extensible** - Built on Tiptap v2 for easy customization
|
|
11
|
+
- ✅ **Lightweight** - Tree-shakeable, only import what you need
|
|
12
|
+
- ✅ **Responsive** - Works on desktop and mobile devices
|
|
13
13
|
|
|
14
14
|
## Supported Features
|
|
15
15
|
|
|
@@ -24,22 +24,22 @@ A modern, Tiptap-based rich text editor that's fully compatible with Contentful'
|
|
|
24
24
|
|
|
25
25
|
## Installation
|
|
26
26
|
|
|
27
|
-
```
|
|
27
|
+
```
|
|
28
28
|
npm install @crashbytes/contentful-richtext-editor
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
## Basic Usage
|
|
32
32
|
|
|
33
|
-
```
|
|
33
|
+
```javascript
|
|
34
34
|
import React, { useState } from 'react';
|
|
35
35
|
import { ContentfulRichTextEditor } from '@crashbytes/contentful-richtext-editor';
|
|
36
36
|
import '@crashbytes/contentful-richtext-editor/dist/index.css';
|
|
37
37
|
import { Document } from '@contentful/rich-text-types';
|
|
38
38
|
|
|
39
39
|
function App() {
|
|
40
|
-
const [content, setContent] = useState
|
|
40
|
+
const [content, setContent] = useState();
|
|
41
41
|
|
|
42
|
-
const handleChange = (document
|
|
42
|
+
const handleChange = (document) => {
|
|
43
43
|
setContent(document);
|
|
44
44
|
console.log('Contentful document:', document);
|
|
45
45
|
};
|
|
@@ -63,7 +63,7 @@ export default App;
|
|
|
63
63
|
|
|
64
64
|
### With Contentful Entry/Asset Embedding
|
|
65
65
|
|
|
66
|
-
```
|
|
66
|
+
```javascript
|
|
67
67
|
import { ContentfulRichTextEditor } from '@crashbytes/contentful-richtext-editor';
|
|
68
68
|
import '@crashbytes/contentful-richtext-editor/dist/index.css';
|
|
69
69
|
|
|
@@ -94,7 +94,7 @@ function ContentfulEditor() {
|
|
|
94
94
|
|
|
95
95
|
### Customizing Features
|
|
96
96
|
|
|
97
|
-
```
|
|
97
|
+
```javascript
|
|
98
98
|
<ContentfulRichTextEditor
|
|
99
99
|
placeholder="Simple editor..."
|
|
100
100
|
disabledFeatures={['table', 'embed', 'quote']}
|
|
@@ -104,9 +104,20 @@ function ContentfulEditor() {
|
|
|
104
104
|
/>
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
+
### Controlling Available Headings and Formatting
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
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
|
+
|
|
107
118
|
### With Initial Content
|
|
108
119
|
|
|
109
|
-
```
|
|
120
|
+
```javascript
|
|
110
121
|
import { createEmptyDocument } from '@crashbytes/contentful-richtext-editor';
|
|
111
122
|
|
|
112
123
|
const initialContent = {
|
|
@@ -149,6 +160,8 @@ const initialContent = {
|
|
|
149
160
|
| `className` | `string` | `''` | Additional CSS classes |
|
|
150
161
|
| `theme` | `'default' \| 'minimal' \| 'contentful'` | `'contentful'` | Visual theme |
|
|
151
162
|
| `disabledFeatures` | `Array<string>` | `[]` | Features to disable |
|
|
163
|
+
| `availableHeadings` | `Array<1\|2\|3\|4\|5\|6>` | `[1,2,3,4,5,6]` | Which heading levels to show |
|
|
164
|
+
| `availableMarks` | `Array<'bold'\|'italic'\|'underline'>` | `['bold','italic','underline']` | Which text formatting options to show |
|
|
152
165
|
|
|
153
166
|
### Disabled Features
|
|
154
167
|
|
|
@@ -166,7 +179,7 @@ You can disable specific features by passing them in the `disabledFeatures` arra
|
|
|
166
179
|
|
|
167
180
|
### Utility Functions
|
|
168
181
|
|
|
169
|
-
```
|
|
182
|
+
```javascript
|
|
170
183
|
import {
|
|
171
184
|
contentfulToTiptap,
|
|
172
185
|
tiptapToContentful,
|
|
@@ -189,7 +202,7 @@ const emptyDoc = createEmptyDocument();
|
|
|
189
202
|
|
|
190
203
|
The editor comes with default styles that match Contentful's design. Import the CSS:
|
|
191
204
|
|
|
192
|
-
```
|
|
205
|
+
```javascript
|
|
193
206
|
import '@crashbytes/contentful-richtext-editor/dist/index.css';
|
|
194
207
|
```
|
|
195
208
|
|
|
@@ -224,7 +237,7 @@ Standard rich text editor appearance with serif fonts.
|
|
|
224
237
|
|
|
225
238
|
## Integration with Next.js
|
|
226
239
|
|
|
227
|
-
```
|
|
240
|
+
```javascript
|
|
228
241
|
// pages/editor.tsx or app/editor/page.tsx
|
|
229
242
|
import dynamic from 'next/dynamic';
|
|
230
243
|
|
|
@@ -249,7 +262,7 @@ export default function EditorPage() {
|
|
|
249
262
|
|
|
250
263
|
This package is written in TypeScript and includes full type definitions. All Contentful rich text types are re-exported for convenience:
|
|
251
264
|
|
|
252
|
-
```
|
|
265
|
+
```javascript
|
|
253
266
|
import type {
|
|
254
267
|
Document,
|
|
255
268
|
Block,
|
|
@@ -20,6 +20,10 @@ export interface ContentfulRichTextEditorProps {
|
|
|
20
20
|
disabledFeatures?: Array<'bold' | 'italic' | 'underline' | 'link' | 'lists' | 'headings' | 'quote' | 'table' | 'embed'>;
|
|
21
21
|
/** Custom styling options */
|
|
22
22
|
theme?: 'default' | 'minimal' | 'contentful';
|
|
23
|
+
/** Which heading levels to make available (1-6) */
|
|
24
|
+
availableHeadings?: Array<1 | 2 | 3 | 4 | 5 | 6>;
|
|
25
|
+
/** Which text formatting marks to make available */
|
|
26
|
+
availableMarks?: Array<'bold' | 'italic' | 'underline'>;
|
|
23
27
|
}
|
|
24
28
|
export declare const ContentfulRichTextEditor: React.FC<ContentfulRichTextEditorProps>;
|
|
25
29
|
export default ContentfulRichTextEditor;
|
|
@@ -5,6 +5,8 @@ interface ToolbarProps {
|
|
|
5
5
|
onEmbedEntry?: () => void;
|
|
6
6
|
onEmbedAsset?: () => void;
|
|
7
7
|
disabledFeatures?: Array<string>;
|
|
8
|
+
availableHeadings?: Array<1 | 2 | 3 | 4 | 5 | 6>;
|
|
9
|
+
availableMarks?: Array<'bold' | 'italic' | 'underline'>;
|
|
8
10
|
}
|
|
9
11
|
export declare const ContentfulToolbar: React.FC<ToolbarProps>;
|
|
10
12
|
export {};
|
package/dist/index.esm.js
CHANGED
|
@@ -26529,10 +26529,11 @@ const Underline = Mark.create({
|
|
|
26529
26529
|
},
|
|
26530
26530
|
});
|
|
26531
26531
|
|
|
26532
|
-
const ContentfulToolbar = ({ editor, onEmbedEntry, onEmbedAsset, disabledFeatures = [] }) => {
|
|
26532
|
+
const ContentfulToolbar = ({ editor, onEmbedEntry, onEmbedAsset, disabledFeatures = [], availableHeadings = [1, 2, 3, 4, 5, 6], availableMarks = ['bold', 'italic', 'underline'] }) => {
|
|
26533
26533
|
const [showLinkInput, setShowLinkInput] = useState(false);
|
|
26534
26534
|
const [linkUrl, setLinkUrl] = useState('');
|
|
26535
26535
|
const isDisabled = (feature) => disabledFeatures.includes(feature);
|
|
26536
|
+
const isMarkAvailable = (mark) => availableMarks.includes(mark);
|
|
26536
26537
|
const handleHeadingChange = (level) => {
|
|
26537
26538
|
if (level === 0) {
|
|
26538
26539
|
editor.chain().focus().setParagraph().run();
|
|
@@ -26561,14 +26562,15 @@ const ContentfulToolbar = ({ editor, onEmbedEntry, onEmbedAsset, disabledFeature
|
|
|
26561
26562
|
editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run();
|
|
26562
26563
|
};
|
|
26563
26564
|
const getActiveHeading = () => {
|
|
26564
|
-
for (
|
|
26565
|
-
if (editor.isActive('heading', { level
|
|
26566
|
-
return `Heading ${
|
|
26565
|
+
for (const level of availableHeadings) {
|
|
26566
|
+
if (editor.isActive('heading', { level })) {
|
|
26567
|
+
return `Heading ${level}`;
|
|
26567
26568
|
}
|
|
26568
26569
|
}
|
|
26569
26570
|
return 'Normal text';
|
|
26570
26571
|
};
|
|
26571
|
-
|
|
26572
|
+
const hasHeadings = !isDisabled('headings') && availableHeadings.length > 0;
|
|
26573
|
+
return (jsxs("div", { className: "contentful-toolbar", children: [jsxs("div", { className: "contentful-toolbar__group", children: [hasHeadings && (jsxs("select", { className: "contentful-toolbar__select", value: getActiveHeading(), onChange: (e) => {
|
|
26572
26574
|
const value = e.target.value;
|
|
26573
26575
|
if (value === 'Normal text') {
|
|
26574
26576
|
handleHeadingChange(0);
|
|
@@ -26577,7 +26579,7 @@ const ContentfulToolbar = ({ editor, onEmbedEntry, onEmbedAsset, disabledFeature
|
|
|
26577
26579
|
const level = parseInt(value.replace('Heading ', ''));
|
|
26578
26580
|
handleHeadingChange(level);
|
|
26579
26581
|
}
|
|
26580
|
-
}, children: [jsx("option", { value: "Normal text", children: "Normal text" }),
|
|
26582
|
+
}, children: [jsx("option", { value: "Normal text", children: "Normal text" }), availableHeadings.map(level => (jsxs("option", { value: `Heading ${level}`, children: ["Heading ", level] }, level)))] })), jsx("button", { className: "contentful-toolbar__button", onClick: () => editor.chain().focus().undo().run(), disabled: !editor.can().undo(), title: "Undo", children: "\u21B6" }), jsx("button", { className: "contentful-toolbar__button", onClick: () => editor.chain().focus().redo().run(), disabled: !editor.can().redo(), title: "Redo", children: "\u21B7" })] }), jsx("div", { className: "contentful-toolbar__separator" }), jsxs("div", { className: "contentful-toolbar__group", children: [!isDisabled('bold') && isMarkAvailable('bold') && (jsx("button", { className: `contentful-toolbar__button ${editor.isActive('bold') ? 'contentful-toolbar__button--active' : ''}`, onClick: () => editor.chain().focus().toggleBold().run(), title: "Bold", children: jsx("strong", { children: "B" }) })), !isDisabled('italic') && isMarkAvailable('italic') && (jsx("button", { className: `contentful-toolbar__button ${editor.isActive('italic') ? 'contentful-toolbar__button--active' : ''}`, onClick: () => editor.chain().focus().toggleItalic().run(), title: "Italic", children: jsx("em", { children: "I" }) })), !isDisabled('underline') && isMarkAvailable('underline') && (jsx("button", { className: `contentful-toolbar__button ${editor.isActive('underline') ? 'contentful-toolbar__button--active' : ''}`, onClick: () => editor.chain().focus().toggleUnderline().run(), title: "Underline", children: jsx("u", { children: "U" }) })), jsx("button", { className: "contentful-toolbar__button", title: "More formatting options", children: "\u22EF" }), !isDisabled('link') && (jsxs(Fragment$1, { children: [jsx("button", { className: `contentful-toolbar__button ${editor.isActive('link') ? 'contentful-toolbar__button--active' : ''}`, onClick: handleLinkToggle, title: "Link", children: "\uD83D\uDD17" }), showLinkInput && (jsxs("div", { className: "contentful-toolbar__link-input", children: [jsx("input", { type: "url", value: linkUrl, onChange: (e) => setLinkUrl(e.target.value), placeholder: "Enter URL", onKeyDown: (e) => {
|
|
26581
26583
|
if (e.key === 'Enter') {
|
|
26582
26584
|
handleLinkSubmit();
|
|
26583
26585
|
}
|
|
@@ -27268,59 +27270,63 @@ const createEmptyDocument = () => ({
|
|
|
27268
27270
|
],
|
|
27269
27271
|
});
|
|
27270
27272
|
|
|
27271
|
-
const ContentfulRichTextEditor = ({ initialValue, onChange, onEmbedEntry, onEmbedAsset, className = '', readonly = false, placeholder = 'Start writing...', disabledFeatures = [], theme = 'contentful' }) => {
|
|
27273
|
+
const ContentfulRichTextEditor = ({ initialValue, onChange, onEmbedEntry, onEmbedAsset, className = '', readonly = false, placeholder = 'Start writing...', disabledFeatures = [], theme = 'contentful', availableHeadings = [1, 2, 3, 4, 5, 6], availableMarks = ['bold', 'italic', 'underline'] }) => {
|
|
27274
|
+
// Build extensions array based on available features
|
|
27275
|
+
const extensions = [];
|
|
27276
|
+
// Add StarterKit with configuration
|
|
27277
|
+
extensions.push(StarterKit.configure({
|
|
27278
|
+
heading: {
|
|
27279
|
+
levels: availableHeadings,
|
|
27280
|
+
},
|
|
27281
|
+
bold: availableMarks.includes('bold') ? {} : false,
|
|
27282
|
+
italic: availableMarks.includes('italic') ? {} : false,
|
|
27283
|
+
bulletList: {
|
|
27284
|
+
HTMLAttributes: {
|
|
27285
|
+
class: 'contentful-bullet-list',
|
|
27286
|
+
},
|
|
27287
|
+
},
|
|
27288
|
+
orderedList: {
|
|
27289
|
+
HTMLAttributes: {
|
|
27290
|
+
class: 'contentful-ordered-list',
|
|
27291
|
+
},
|
|
27292
|
+
},
|
|
27293
|
+
blockquote: {
|
|
27294
|
+
HTMLAttributes: {
|
|
27295
|
+
class: 'contentful-blockquote',
|
|
27296
|
+
},
|
|
27297
|
+
},
|
|
27298
|
+
}));
|
|
27299
|
+
// Add underline extension only if it's in availableMarks
|
|
27300
|
+
if (availableMarks.includes('underline')) {
|
|
27301
|
+
extensions.push(Underline);
|
|
27302
|
+
}
|
|
27303
|
+
// Add other extensions
|
|
27304
|
+
extensions.push(Link.configure({
|
|
27305
|
+
openOnClick: false,
|
|
27306
|
+
HTMLAttributes: {
|
|
27307
|
+
class: 'contentful-link',
|
|
27308
|
+
rel: 'noopener noreferrer',
|
|
27309
|
+
},
|
|
27310
|
+
}), Table.configure({
|
|
27311
|
+
resizable: true,
|
|
27312
|
+
HTMLAttributes: {
|
|
27313
|
+
class: 'contentful-table',
|
|
27314
|
+
},
|
|
27315
|
+
}), TableRow.configure({
|
|
27316
|
+
HTMLAttributes: {
|
|
27317
|
+
class: 'contentful-table-row',
|
|
27318
|
+
},
|
|
27319
|
+
}), TableHeader.configure({
|
|
27320
|
+
HTMLAttributes: {
|
|
27321
|
+
class: 'contentful-table-header',
|
|
27322
|
+
},
|
|
27323
|
+
}), TableCell.configure({
|
|
27324
|
+
HTMLAttributes: {
|
|
27325
|
+
class: 'contentful-table-cell',
|
|
27326
|
+
},
|
|
27327
|
+
}));
|
|
27272
27328
|
const editor = useEditor({
|
|
27273
|
-
extensions
|
|
27274
|
-
StarterKit.configure({
|
|
27275
|
-
heading: {
|
|
27276
|
-
levels: [1, 2, 3, 4, 5, 6],
|
|
27277
|
-
},
|
|
27278
|
-
bulletList: {
|
|
27279
|
-
HTMLAttributes: {
|
|
27280
|
-
class: 'contentful-bullet-list',
|
|
27281
|
-
},
|
|
27282
|
-
},
|
|
27283
|
-
orderedList: {
|
|
27284
|
-
HTMLAttributes: {
|
|
27285
|
-
class: 'contentful-ordered-list',
|
|
27286
|
-
},
|
|
27287
|
-
},
|
|
27288
|
-
blockquote: {
|
|
27289
|
-
HTMLAttributes: {
|
|
27290
|
-
class: 'contentful-blockquote',
|
|
27291
|
-
},
|
|
27292
|
-
},
|
|
27293
|
-
}),
|
|
27294
|
-
Underline,
|
|
27295
|
-
Link.configure({
|
|
27296
|
-
openOnClick: false,
|
|
27297
|
-
HTMLAttributes: {
|
|
27298
|
-
class: 'contentful-link',
|
|
27299
|
-
rel: 'noopener noreferrer',
|
|
27300
|
-
},
|
|
27301
|
-
}),
|
|
27302
|
-
Table.configure({
|
|
27303
|
-
resizable: true,
|
|
27304
|
-
HTMLAttributes: {
|
|
27305
|
-
class: 'contentful-table',
|
|
27306
|
-
},
|
|
27307
|
-
}),
|
|
27308
|
-
TableRow.configure({
|
|
27309
|
-
HTMLAttributes: {
|
|
27310
|
-
class: 'contentful-table-row',
|
|
27311
|
-
},
|
|
27312
|
-
}),
|
|
27313
|
-
TableHeader.configure({
|
|
27314
|
-
HTMLAttributes: {
|
|
27315
|
-
class: 'contentful-table-header',
|
|
27316
|
-
},
|
|
27317
|
-
}),
|
|
27318
|
-
TableCell.configure({
|
|
27319
|
-
HTMLAttributes: {
|
|
27320
|
-
class: 'contentful-table-cell',
|
|
27321
|
-
},
|
|
27322
|
-
}),
|
|
27323
|
-
],
|
|
27329
|
+
extensions,
|
|
27324
27330
|
content: initialValue ? contentfulToTiptap(initialValue) : '',
|
|
27325
27331
|
editable: !readonly,
|
|
27326
27332
|
editorProps: {
|
|
@@ -27399,7 +27405,7 @@ const ContentfulRichTextEditor = ({ initialValue, onChange, onEmbedEntry, onEmbe
|
|
|
27399
27405
|
if (!editor) {
|
|
27400
27406
|
return (jsx("div", { className: `contentful-editor contentful-editor--loading ${className}`, children: jsx("div", { className: "contentful-editor__loading", children: "Loading editor..." }) }));
|
|
27401
27407
|
}
|
|
27402
|
-
return (jsxs("div", { className: `contentful-editor contentful-editor--${theme} ${className}`, children: [!readonly && (jsx(ContentfulToolbar, { editor: editor, onEmbedEntry: handleEmbedEntry, onEmbedAsset: handleEmbedAsset, disabledFeatures: disabledFeatures })), jsx("div", { className: "contentful-editor__content-wrapper", children: jsx(EditorContent, { editor: editor, className: "contentful-editor__content" }) })] }));
|
|
27408
|
+
return (jsxs("div", { className: `contentful-editor contentful-editor--${theme} ${className}`, children: [!readonly && (jsx(ContentfulToolbar, { editor: editor, onEmbedEntry: handleEmbedEntry, onEmbedAsset: handleEmbedAsset, disabledFeatures: disabledFeatures, availableHeadings: availableHeadings, availableMarks: availableMarks })), jsx("div", { className: "contentful-editor__content-wrapper", children: jsx(EditorContent, { editor: editor, className: "contentful-editor__content" }) })] }));
|
|
27403
27409
|
};
|
|
27404
27410
|
|
|
27405
27411
|
export { ContentfulRichTextEditor, ContentfulToolbar, contentfulToTiptap, createEmptyDocument, tiptapToContentful, validateContentfulDocument };
|