@crashbytes/contentful-richtext-editor 1.0.8 → 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 -3
- package/dist/components/ContentfulEmbedded.d.ts +12 -0
- package/dist/components/Toolbar.d.ts +2 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.esm.js +410 -79
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +415 -77
- package/dist/index.js.map +1 -1
- package/dist/utils/configParser.d.ts +37 -0
- package/dist/utils/contentfulTransform.d.ts +20 -0
- package/dist/utils/types.d.ts +113 -0
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
2
|
-
import React, { forwardRef, useRef, useState, useDebugValue, useEffect, createContext, useContext, useLayoutEffect, useCallback } from 'react';
|
|
2
|
+
import React, { forwardRef, useRef, useState, useDebugValue, useEffect, createContext, useContext, useLayoutEffect, useMemo, useCallback } from 'react';
|
|
3
3
|
import ReactDOM from 'react-dom';
|
|
4
4
|
|
|
5
5
|
// ::- Persistent data structure representing an ordered mapping from
|
|
@@ -26529,7 +26529,7 @@ const Underline = Mark.create({
|
|
|
26529
26529
|
},
|
|
26530
26530
|
});
|
|
26531
26531
|
|
|
26532
|
-
const ContentfulToolbar = ({ editor, onEmbedEntry, onEmbedAsset, disabledFeatures = [], availableHeadings = [1, 2, 3, 4, 5, 6], availableMarks = ['bold', 'italic', 'underline'] }) => {
|
|
26532
|
+
const ContentfulToolbar = ({ editor, onEmbedEntry, onEmbedAsset, onEmbedInlineEntry, disabledFeatures = [], availableHeadings = [1, 2, 3, 4, 5, 6], availableMarks = ['bold', 'italic', 'underline'], allowHyperlinks = true }) => {
|
|
26533
26533
|
const [showLinkInput, setShowLinkInput] = useState(false);
|
|
26534
26534
|
const [linkUrl, setLinkUrl] = useState('');
|
|
26535
26535
|
const isDisabled = (feature) => disabledFeatures.includes(feature);
|
|
@@ -26558,6 +26558,10 @@ const ContentfulToolbar = ({ editor, onEmbedEntry, onEmbedAsset, disabledFeature
|
|
|
26558
26558
|
setShowLinkInput(false);
|
|
26559
26559
|
setLinkUrl('');
|
|
26560
26560
|
};
|
|
26561
|
+
const handleLinkCancel = () => {
|
|
26562
|
+
setShowLinkInput(false);
|
|
26563
|
+
setLinkUrl('');
|
|
26564
|
+
};
|
|
26561
26565
|
const insertTable = () => {
|
|
26562
26566
|
editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run();
|
|
26563
26567
|
};
|
|
@@ -26570,6 +26574,8 @@ const ContentfulToolbar = ({ editor, onEmbedEntry, onEmbedAsset, disabledFeature
|
|
|
26570
26574
|
return 'Normal text';
|
|
26571
26575
|
};
|
|
26572
26576
|
const hasHeadings = !isDisabled('headings') && availableHeadings.length > 0;
|
|
26577
|
+
const hasAnyEmbedOptions = onEmbedEntry || onEmbedAsset || onEmbedInlineEntry;
|
|
26578
|
+
const hasAnyTextFormatting = availableMarks.some(mark => !isDisabled(mark) && isMarkAvailable(mark));
|
|
26573
26579
|
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) => {
|
|
26574
26580
|
const value = e.target.value;
|
|
26575
26581
|
if (value === 'Normal text') {
|
|
@@ -26579,14 +26585,16 @@ const ContentfulToolbar = ({ editor, onEmbedEntry, onEmbedAsset, disabledFeature
|
|
|
26579
26585
|
const level = parseInt(value.replace('Heading ', ''));
|
|
26580
26586
|
handleHeadingChange(level);
|
|
26581
26587
|
}
|
|
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) => {
|
|
26583
|
-
|
|
26584
|
-
|
|
26585
|
-
|
|
26586
|
-
|
|
26587
|
-
|
|
26588
|
-
|
|
26589
|
-
|
|
26588
|
+
}, 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" })] }), (hasAnyTextFormatting || allowHyperlinks) && (jsxs(Fragment$1, { children: [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') && allowHyperlinks && (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) => {
|
|
26589
|
+
if (e.key === 'Enter') {
|
|
26590
|
+
e.preventDefault();
|
|
26591
|
+
handleLinkSubmit();
|
|
26592
|
+
}
|
|
26593
|
+
if (e.key === 'Escape') {
|
|
26594
|
+
e.preventDefault();
|
|
26595
|
+
handleLinkCancel();
|
|
26596
|
+
}
|
|
26597
|
+
}, autoFocus: true }), jsx("button", { onClick: handleLinkSubmit, title: "Apply link", children: "\u2713" }), jsx("button", { onClick: handleLinkCancel, title: "Cancel", children: "\u2717" })] }))] }))] })] })), (!isDisabled('lists') || !isDisabled('quote') || !isDisabled('table')) && (jsxs(Fragment$1, { children: [jsx("div", { className: "contentful-toolbar__separator" }), jsxs("div", { className: "contentful-toolbar__group", children: [!isDisabled('lists') && (jsxs(Fragment$1, { children: [jsx("button", { className: `contentful-toolbar__button ${editor.isActive('bulletList') ? 'contentful-toolbar__button--active' : ''}`, onClick: () => editor.chain().focus().toggleBulletList().run(), title: "Bullet List", children: "\u2022 \u2261" }), jsx("button", { className: `contentful-toolbar__button ${editor.isActive('orderedList') ? 'contentful-toolbar__button--active' : ''}`, onClick: () => editor.chain().focus().toggleOrderedList().run(), title: "Numbered List", children: "1. \u2261" })] })), !isDisabled('quote') && (jsx("button", { className: `contentful-toolbar__button ${editor.isActive('blockquote') ? 'contentful-toolbar__button--active' : ''}`, onClick: () => editor.chain().focus().toggleBlockquote().run(), title: "Quote", children: "\"" })), jsx("button", { className: "contentful-toolbar__button", onClick: () => editor.chain().focus().setHorizontalRule().run(), title: "Horizontal Rule", children: "\u2014" }), !isDisabled('table') && (jsx("button", { className: "contentful-toolbar__button", onClick: insertTable, title: "Insert Table", children: "\u229E" }))] })] })), hasAnyEmbedOptions && !isDisabled('embed') && (jsxs(Fragment$1, { children: [jsx("div", { className: "contentful-toolbar__separator" }), jsx("div", { className: "contentful-toolbar__group contentful-toolbar__group--right", children: jsxs("div", { className: "contentful-toolbar__embed-dropdown", children: [jsx("button", { className: "contentful-toolbar__embed-button", children: "+ Embed \u25BC" }), jsxs("div", { className: "contentful-toolbar__embed-menu", children: [onEmbedEntry && (jsx("button", { className: "contentful-toolbar__embed-option", onClick: onEmbedEntry, children: "\uD83D\uDCC4 Entry" })), onEmbedInlineEntry && (jsx("button", { className: "contentful-toolbar__embed-option", onClick: onEmbedInlineEntry, children: "\uD83D\uDCDD Inline Entry" })), onEmbedAsset && (jsx("button", { className: "contentful-toolbar__embed-option", onClick: onEmbedAsset, children: "\uD83D\uDDBC\uFE0F Media" }))] })] }) })] }))] }));
|
|
26590
26598
|
};
|
|
26591
26599
|
|
|
26592
26600
|
var dist = {};
|
|
@@ -27021,7 +27029,7 @@ var distExports = requireDist();
|
|
|
27021
27029
|
*/
|
|
27022
27030
|
const contentfulToTiptap = (document) => {
|
|
27023
27031
|
const convertNode = (node) => {
|
|
27024
|
-
var _a, _b, _c, _d, _e;
|
|
27032
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
27025
27033
|
switch (node.nodeType) {
|
|
27026
27034
|
case distExports.BLOCKS.DOCUMENT:
|
|
27027
27035
|
return {
|
|
@@ -27124,13 +27132,20 @@ const contentfulToTiptap = (document) => {
|
|
|
27124
27132
|
},
|
|
27125
27133
|
],
|
|
27126
27134
|
};
|
|
27135
|
+
case distExports.INLINES.EMBEDDED_ENTRY:
|
|
27136
|
+
// Inline embedded entry
|
|
27137
|
+
return {
|
|
27138
|
+
type: 'text',
|
|
27139
|
+
text: `[Inline Entry: ${((_b = (_a = node.data.target) === null || _a === void 0 ? void 0 : _a.sys) === null || _b === void 0 ? void 0 : _b.id) || 'Unknown'}]`,
|
|
27140
|
+
marks: [{ type: 'bold' }],
|
|
27141
|
+
};
|
|
27127
27142
|
case distExports.BLOCKS.EMBEDDED_ENTRY:
|
|
27128
27143
|
return {
|
|
27129
27144
|
type: 'paragraph',
|
|
27130
27145
|
content: [
|
|
27131
27146
|
{
|
|
27132
27147
|
type: 'text',
|
|
27133
|
-
text: `[Embedded Entry: ${((
|
|
27148
|
+
text: `[Embedded Entry: ${((_d = (_c = node.data.target) === null || _c === void 0 ? void 0 : _c.sys) === null || _d === void 0 ? void 0 : _d.id) || 'Unknown'}]`,
|
|
27134
27149
|
marks: [{ type: 'bold' }],
|
|
27135
27150
|
},
|
|
27136
27151
|
],
|
|
@@ -27141,14 +27156,14 @@ const contentfulToTiptap = (document) => {
|
|
|
27141
27156
|
content: [
|
|
27142
27157
|
{
|
|
27143
27158
|
type: 'text',
|
|
27144
|
-
text: `[Embedded Asset: ${((
|
|
27159
|
+
text: `[Embedded Asset: ${((_f = (_e = node.data.target) === null || _e === void 0 ? void 0 : _e.sys) === null || _f === void 0 ? void 0 : _f.id) || 'Unknown'}]`,
|
|
27145
27160
|
marks: [{ type: 'bold' }],
|
|
27146
27161
|
},
|
|
27147
27162
|
],
|
|
27148
27163
|
};
|
|
27149
27164
|
case 'text':
|
|
27150
27165
|
const textNode = node;
|
|
27151
|
-
const marks = ((
|
|
27166
|
+
const marks = ((_g = textNode.marks) === null || _g === void 0 ? void 0 : _g.map(mark => {
|
|
27152
27167
|
switch (mark.type) {
|
|
27153
27168
|
case distExports.MARKS.BOLD:
|
|
27154
27169
|
return { type: 'bold' };
|
|
@@ -27182,7 +27197,7 @@ const contentfulToTiptap = (document) => {
|
|
|
27182
27197
|
*/
|
|
27183
27198
|
const tiptapToContentful = (tiptapDoc) => {
|
|
27184
27199
|
const convertNode = (node) => {
|
|
27185
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
27200
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
27186
27201
|
switch (node.type) {
|
|
27187
27202
|
case 'doc':
|
|
27188
27203
|
return {
|
|
@@ -27301,6 +27316,26 @@ const tiptapToContentful = (tiptapDoc) => {
|
|
|
27301
27316
|
],
|
|
27302
27317
|
};
|
|
27303
27318
|
}
|
|
27319
|
+
// Check if this is an inline entry (by looking for specific patterns)
|
|
27320
|
+
const isInlineEntry = node.text && node.text.startsWith('[Inline Entry:');
|
|
27321
|
+
if (isInlineEntry && ((_r = node.marks) === null || _r === void 0 ? void 0 : _r.some((mark) => mark.type === 'bold'))) {
|
|
27322
|
+
// Extract entry ID from the text
|
|
27323
|
+
const match = node.text.match(/\[Inline Entry:\s*([^\]]+)\]/);
|
|
27324
|
+
const entryId = match ? match[1].trim() : 'Unknown';
|
|
27325
|
+
return {
|
|
27326
|
+
nodeType: distExports.INLINES.EMBEDDED_ENTRY,
|
|
27327
|
+
data: {
|
|
27328
|
+
target: {
|
|
27329
|
+
sys: {
|
|
27330
|
+
id: entryId,
|
|
27331
|
+
type: 'Link',
|
|
27332
|
+
linkType: 'Entry',
|
|
27333
|
+
},
|
|
27334
|
+
},
|
|
27335
|
+
},
|
|
27336
|
+
content: [],
|
|
27337
|
+
};
|
|
27338
|
+
}
|
|
27304
27339
|
return {
|
|
27305
27340
|
nodeType: 'text',
|
|
27306
27341
|
value: node.text || '',
|
|
@@ -27347,61 +27382,338 @@ const createEmptyDocument = () => ({
|
|
|
27347
27382
|
},
|
|
27348
27383
|
],
|
|
27349
27384
|
});
|
|
27385
|
+
/**
|
|
27386
|
+
* Sanitizes a Contentful document by removing invalid nodes/marks based on configuration
|
|
27387
|
+
*/
|
|
27388
|
+
const sanitizeContentfulDocument = (document, allowedNodeTypes, allowedMarks) => {
|
|
27389
|
+
const sanitizeNode = (node) => {
|
|
27390
|
+
var _a, _b;
|
|
27391
|
+
// Check if node type is allowed
|
|
27392
|
+
if (!allowedNodeTypes.includes(node.nodeType)) {
|
|
27393
|
+
// Convert to paragraph if it's a block that's not allowed
|
|
27394
|
+
if (node.nodeType.startsWith('heading-') ||
|
|
27395
|
+
node.nodeType === distExports.BLOCKS.QUOTE ||
|
|
27396
|
+
node.nodeType === distExports.BLOCKS.UL_LIST ||
|
|
27397
|
+
node.nodeType === distExports.BLOCKS.OL_LIST) {
|
|
27398
|
+
return {
|
|
27399
|
+
nodeType: distExports.BLOCKS.PARAGRAPH,
|
|
27400
|
+
data: {},
|
|
27401
|
+
content: (_a = node.content) === null || _a === void 0 ? void 0 : _a.map(child => sanitizeNode(child)).filter(Boolean),
|
|
27402
|
+
};
|
|
27403
|
+
}
|
|
27404
|
+
return null;
|
|
27405
|
+
}
|
|
27406
|
+
if (node.nodeType === 'text') {
|
|
27407
|
+
const textNode = node;
|
|
27408
|
+
const sanitizedMarks = ((_b = textNode.marks) === null || _b === void 0 ? void 0 : _b.filter(mark => allowedMarks.includes(mark.type))) || [];
|
|
27409
|
+
return {
|
|
27410
|
+
...textNode,
|
|
27411
|
+
marks: sanitizedMarks,
|
|
27412
|
+
};
|
|
27413
|
+
}
|
|
27414
|
+
if ('content' in node && node.content) {
|
|
27415
|
+
const sanitizedContent = node.content.map(child => sanitizeNode(child)).filter(Boolean);
|
|
27416
|
+
return {
|
|
27417
|
+
...node,
|
|
27418
|
+
content: sanitizedContent,
|
|
27419
|
+
};
|
|
27420
|
+
}
|
|
27421
|
+
return node;
|
|
27422
|
+
};
|
|
27423
|
+
const sanitized = sanitizeNode(document);
|
|
27424
|
+
return sanitized;
|
|
27425
|
+
};
|
|
27426
|
+
/**
|
|
27427
|
+
* Extracts plain text from a Contentful document
|
|
27428
|
+
*/
|
|
27429
|
+
const extractPlainText = (document) => {
|
|
27430
|
+
const extractFromNode = (node) => {
|
|
27431
|
+
if (node.nodeType === 'text') {
|
|
27432
|
+
return node.value;
|
|
27433
|
+
}
|
|
27434
|
+
if ('content' in node && node.content) {
|
|
27435
|
+
return node.content.map(child => extractFromNode(child)).join('');
|
|
27436
|
+
}
|
|
27437
|
+
return '';
|
|
27438
|
+
};
|
|
27439
|
+
return extractFromNode(document);
|
|
27440
|
+
};
|
|
27441
|
+
/**
|
|
27442
|
+
* Counts words in a Contentful document
|
|
27443
|
+
*/
|
|
27444
|
+
const countWords = (document) => {
|
|
27445
|
+
const plainText = extractPlainText(document);
|
|
27446
|
+
const words = plainText.trim().split(/\s+/).filter(word => word.length > 0);
|
|
27447
|
+
return words.length;
|
|
27448
|
+
};
|
|
27449
|
+
/**
|
|
27450
|
+
* Finds all embedded entries/assets in a document
|
|
27451
|
+
*/
|
|
27452
|
+
const findEmbeddedContent = (document) => {
|
|
27453
|
+
const entries = [];
|
|
27454
|
+
const assets = [];
|
|
27455
|
+
const inlineEntries = [];
|
|
27456
|
+
const searchNode = (node) => {
|
|
27457
|
+
var _a, _b, _c, _d, _e, _f;
|
|
27458
|
+
if (node.nodeType === distExports.BLOCKS.EMBEDDED_ENTRY) {
|
|
27459
|
+
const entryId = (_b = (_a = node.data.target) === null || _a === void 0 ? void 0 : _a.sys) === null || _b === void 0 ? void 0 : _b.id;
|
|
27460
|
+
if (entryId)
|
|
27461
|
+
entries.push(entryId);
|
|
27462
|
+
}
|
|
27463
|
+
else if (node.nodeType === distExports.BLOCKS.EMBEDDED_ASSET) {
|
|
27464
|
+
const assetId = (_d = (_c = node.data.target) === null || _c === void 0 ? void 0 : _c.sys) === null || _d === void 0 ? void 0 : _d.id;
|
|
27465
|
+
if (assetId)
|
|
27466
|
+
assets.push(assetId);
|
|
27467
|
+
}
|
|
27468
|
+
else if (node.nodeType === distExports.INLINES.EMBEDDED_ENTRY) {
|
|
27469
|
+
const entryId = (_f = (_e = node.data.target) === null || _e === void 0 ? void 0 : _e.sys) === null || _f === void 0 ? void 0 : _f.id;
|
|
27470
|
+
if (entryId)
|
|
27471
|
+
inlineEntries.push(entryId);
|
|
27472
|
+
}
|
|
27473
|
+
if ('content' in node && node.content) {
|
|
27474
|
+
node.content.forEach(child => searchNode(child));
|
|
27475
|
+
}
|
|
27476
|
+
};
|
|
27477
|
+
searchNode(document);
|
|
27478
|
+
return {
|
|
27479
|
+
entries: [...new Set(entries)], // Remove duplicates
|
|
27480
|
+
assets: [...new Set(assets)],
|
|
27481
|
+
inlineEntries: [...new Set(inlineEntries)],
|
|
27482
|
+
};
|
|
27483
|
+
};
|
|
27350
27484
|
|
|
27351
|
-
|
|
27352
|
-
|
|
27353
|
-
|
|
27354
|
-
|
|
27355
|
-
|
|
27356
|
-
|
|
27357
|
-
|
|
27358
|
-
|
|
27359
|
-
|
|
27360
|
-
|
|
27361
|
-
|
|
27362
|
-
|
|
27363
|
-
|
|
27364
|
-
|
|
27365
|
-
|
|
27366
|
-
|
|
27367
|
-
|
|
27368
|
-
|
|
27485
|
+
// Utility functions for parsing Contentful Rich Text field configurations
|
|
27486
|
+
/**
|
|
27487
|
+
* Parses Contentful field configuration to determine editor capabilities
|
|
27488
|
+
*/
|
|
27489
|
+
const parseContentfulFieldConfig = (fieldConfiguration) => {
|
|
27490
|
+
var _a;
|
|
27491
|
+
// Default configuration when no field config is provided
|
|
27492
|
+
const defaultConfig = {
|
|
27493
|
+
availableHeadings: [1, 2, 3, 4, 5, 6],
|
|
27494
|
+
availableMarks: ['bold', 'italic', 'underline'],
|
|
27495
|
+
disabledFeatures: [],
|
|
27496
|
+
allowHyperlinks: true,
|
|
27497
|
+
allowEmbeddedEntries: true,
|
|
27498
|
+
allowEmbeddedAssets: true,
|
|
27499
|
+
allowInlineEntries: true,
|
|
27500
|
+
allowTables: true,
|
|
27501
|
+
allowQuotes: true,
|
|
27502
|
+
allowLists: true,
|
|
27503
|
+
};
|
|
27504
|
+
if (!((_a = fieldConfiguration === null || fieldConfiguration === void 0 ? void 0 : fieldConfiguration.validations) === null || _a === void 0 ? void 0 : _a[0])) {
|
|
27505
|
+
return defaultConfig;
|
|
27506
|
+
}
|
|
27507
|
+
const validation = fieldConfiguration.validations[0];
|
|
27508
|
+
const enabledMarks = validation.enabledMarks || [];
|
|
27509
|
+
const enabledNodeTypes = validation.enabledNodeTypes || [];
|
|
27510
|
+
// Parse available text marks
|
|
27511
|
+
const marks = [];
|
|
27512
|
+
if (enabledMarks.includes('bold'))
|
|
27513
|
+
marks.push('bold');
|
|
27514
|
+
if (enabledMarks.includes('italic'))
|
|
27515
|
+
marks.push('italic');
|
|
27516
|
+
if (enabledMarks.includes('underline'))
|
|
27517
|
+
marks.push('underline');
|
|
27518
|
+
// Parse available heading levels
|
|
27519
|
+
const headings = [];
|
|
27520
|
+
if (enabledNodeTypes.includes('heading-1'))
|
|
27521
|
+
headings.push(1);
|
|
27522
|
+
if (enabledNodeTypes.includes('heading-2'))
|
|
27523
|
+
headings.push(2);
|
|
27524
|
+
if (enabledNodeTypes.includes('heading-3'))
|
|
27525
|
+
headings.push(3);
|
|
27526
|
+
if (enabledNodeTypes.includes('heading-4'))
|
|
27527
|
+
headings.push(4);
|
|
27528
|
+
if (enabledNodeTypes.includes('heading-5'))
|
|
27529
|
+
headings.push(5);
|
|
27530
|
+
if (enabledNodeTypes.includes('heading-6'))
|
|
27531
|
+
headings.push(6);
|
|
27532
|
+
// Parse other features
|
|
27533
|
+
const allowHyperlinks = enabledNodeTypes.includes('hyperlink');
|
|
27534
|
+
const allowEmbeddedEntries = enabledNodeTypes.includes('embedded-entry-block');
|
|
27535
|
+
const allowEmbeddedAssets = enabledNodeTypes.includes('embedded-asset-block');
|
|
27536
|
+
const allowInlineEntries = enabledNodeTypes.includes('embedded-entry-inline');
|
|
27537
|
+
const allowTables = enabledNodeTypes.includes('table');
|
|
27538
|
+
const allowQuotes = enabledNodeTypes.includes('blockquote') || enabledNodeTypes.includes('quote');
|
|
27539
|
+
const allowLists = enabledNodeTypes.includes('unordered-list') || enabledNodeTypes.includes('ordered-list');
|
|
27540
|
+
// Build disabled features array
|
|
27541
|
+
const disabled = [];
|
|
27542
|
+
if (!marks.includes('bold'))
|
|
27543
|
+
disabled.push('bold');
|
|
27544
|
+
if (!marks.includes('italic'))
|
|
27545
|
+
disabled.push('italic');
|
|
27546
|
+
if (!marks.includes('underline'))
|
|
27547
|
+
disabled.push('underline');
|
|
27548
|
+
if (!allowHyperlinks)
|
|
27549
|
+
disabled.push('link');
|
|
27550
|
+
if (!allowLists)
|
|
27551
|
+
disabled.push('lists');
|
|
27552
|
+
if (headings.length === 0)
|
|
27553
|
+
disabled.push('headings');
|
|
27554
|
+
if (!allowQuotes)
|
|
27555
|
+
disabled.push('quote');
|
|
27556
|
+
if (!allowTables)
|
|
27557
|
+
disabled.push('table');
|
|
27558
|
+
if (!allowEmbeddedEntries && !allowEmbeddedAssets && !allowInlineEntries)
|
|
27559
|
+
disabled.push('embed');
|
|
27560
|
+
return {
|
|
27561
|
+
availableHeadings: headings,
|
|
27562
|
+
availableMarks: marks,
|
|
27563
|
+
disabledFeatures: disabled,
|
|
27564
|
+
allowHyperlinks,
|
|
27565
|
+
allowEmbeddedEntries,
|
|
27566
|
+
allowEmbeddedAssets,
|
|
27567
|
+
allowInlineEntries,
|
|
27568
|
+
allowTables,
|
|
27569
|
+
allowQuotes,
|
|
27570
|
+
allowLists,
|
|
27571
|
+
};
|
|
27572
|
+
};
|
|
27573
|
+
/**
|
|
27574
|
+
* Helper function to fetch Contentful field configuration from Management API
|
|
27575
|
+
*/
|
|
27576
|
+
const fetchContentfulFieldConfig = async (spaceId, contentTypeId, fieldId, accessToken) => {
|
|
27577
|
+
try {
|
|
27578
|
+
const response = await fetch(`https://api.contentful.com/spaces/${spaceId}/content_types/${contentTypeId}`, {
|
|
27579
|
+
headers: {
|
|
27580
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
27581
|
+
'Content-Type': 'application/vnd.contentful.management.v1+json',
|
|
27369
27582
|
},
|
|
27370
|
-
}
|
|
27371
|
-
|
|
27372
|
-
|
|
27373
|
-
|
|
27583
|
+
});
|
|
27584
|
+
if (!response.ok) {
|
|
27585
|
+
throw new Error(`Failed to fetch content type: ${response.status} ${response.statusText}`);
|
|
27586
|
+
}
|
|
27587
|
+
const contentType = await response.json();
|
|
27588
|
+
const field = contentType.fields.find((f) => f.id === fieldId);
|
|
27589
|
+
if (!field || field.type !== 'RichText') {
|
|
27590
|
+
console.warn(`Field "${fieldId}" not found or is not a RichText field`);
|
|
27591
|
+
return null;
|
|
27592
|
+
}
|
|
27593
|
+
return {
|
|
27594
|
+
validations: field.validations || [],
|
|
27595
|
+
settings: {
|
|
27596
|
+
helpText: field.helpText,
|
|
27374
27597
|
},
|
|
27375
|
-
}
|
|
27376
|
-
}
|
|
27377
|
-
|
|
27378
|
-
|
|
27379
|
-
|
|
27380
|
-
|
|
27381
|
-
|
|
27382
|
-
|
|
27383
|
-
|
|
27384
|
-
|
|
27385
|
-
|
|
27386
|
-
|
|
27387
|
-
|
|
27388
|
-
|
|
27389
|
-
|
|
27390
|
-
|
|
27391
|
-
|
|
27392
|
-
|
|
27393
|
-
|
|
27394
|
-
|
|
27395
|
-
|
|
27396
|
-
|
|
27397
|
-
|
|
27398
|
-
|
|
27399
|
-
|
|
27400
|
-
}
|
|
27401
|
-
|
|
27402
|
-
|
|
27403
|
-
|
|
27404
|
-
|
|
27598
|
+
};
|
|
27599
|
+
}
|
|
27600
|
+
catch (error) {
|
|
27601
|
+
console.error('Error fetching Contentful field configuration:', error);
|
|
27602
|
+
return null;
|
|
27603
|
+
}
|
|
27604
|
+
};
|
|
27605
|
+
/**
|
|
27606
|
+
* Creates a mock field configuration for testing purposes
|
|
27607
|
+
*/
|
|
27608
|
+
const createMockFieldConfig = (options) => {
|
|
27609
|
+
return {
|
|
27610
|
+
validations: [{
|
|
27611
|
+
enabledMarks: options.enabledMarks || ['bold', 'italic'],
|
|
27612
|
+
enabledNodeTypes: options.enabledNodeTypes || [
|
|
27613
|
+
'paragraph',
|
|
27614
|
+
'heading-1',
|
|
27615
|
+
'heading-2',
|
|
27616
|
+
'heading-3',
|
|
27617
|
+
'unordered-list',
|
|
27618
|
+
'ordered-list',
|
|
27619
|
+
'hyperlink',
|
|
27620
|
+
'embedded-entry-block',
|
|
27621
|
+
],
|
|
27622
|
+
}],
|
|
27623
|
+
};
|
|
27624
|
+
};
|
|
27625
|
+
|
|
27626
|
+
const ContentfulRichTextEditor = ({ initialValue, onChange, onEmbedEntry, onEmbedAsset, onEmbedInlineEntry, className = '', readonly = false, placeholder = 'Start writing...', fieldConfiguration, disabledFeatures = [], theme = 'contentful', availableHeadings = [1, 2, 3, 4, 5, 6], availableMarks = ['bold', 'italic', 'underline'] }) => {
|
|
27627
|
+
// Parse Contentful field configuration to determine available features
|
|
27628
|
+
const editorConfig = useMemo(() => {
|
|
27629
|
+
if (fieldConfiguration) {
|
|
27630
|
+
return parseContentfulFieldConfig(fieldConfiguration);
|
|
27631
|
+
}
|
|
27632
|
+
// Fallback to manual configuration
|
|
27633
|
+
const disabled = [];
|
|
27634
|
+
if (!availableMarks.includes('bold'))
|
|
27635
|
+
disabled.push('bold');
|
|
27636
|
+
if (!availableMarks.includes('italic'))
|
|
27637
|
+
disabled.push('italic');
|
|
27638
|
+
if (!availableMarks.includes('underline'))
|
|
27639
|
+
disabled.push('underline');
|
|
27640
|
+
disabled.push(...disabledFeatures);
|
|
27641
|
+
return {
|
|
27642
|
+
availableHeadings,
|
|
27643
|
+
availableMarks,
|
|
27644
|
+
disabledFeatures: disabled,
|
|
27645
|
+
allowHyperlinks: !disabledFeatures.includes('link'),
|
|
27646
|
+
allowEmbeddedEntries: !disabledFeatures.includes('embed'),
|
|
27647
|
+
allowEmbeddedAssets: !disabledFeatures.includes('embed'),
|
|
27648
|
+
allowInlineEntries: !disabledFeatures.includes('embed'),
|
|
27649
|
+
allowTables: !disabledFeatures.includes('table'),
|
|
27650
|
+
allowQuotes: !disabledFeatures.includes('quote'),
|
|
27651
|
+
allowLists: !disabledFeatures.includes('lists'),
|
|
27652
|
+
};
|
|
27653
|
+
}, [fieldConfiguration, disabledFeatures, availableHeadings, availableMarks]);
|
|
27654
|
+
// Build extensions array based on configuration
|
|
27655
|
+
const extensions = useMemo(() => {
|
|
27656
|
+
const exts = [];
|
|
27657
|
+
// Add StarterKit with configuration
|
|
27658
|
+
exts.push(StarterKit.configure({
|
|
27659
|
+
heading: editorConfig.availableHeadings.length > 0 ? {
|
|
27660
|
+
levels: editorConfig.availableHeadings,
|
|
27661
|
+
} : false,
|
|
27662
|
+
bold: editorConfig.availableMarks.includes('bold') ? {} : false,
|
|
27663
|
+
italic: editorConfig.availableMarks.includes('italic') ? {} : false,
|
|
27664
|
+
bulletList: editorConfig.allowLists ? {
|
|
27665
|
+
HTMLAttributes: {
|
|
27666
|
+
class: 'contentful-bullet-list',
|
|
27667
|
+
},
|
|
27668
|
+
} : false,
|
|
27669
|
+
orderedList: editorConfig.allowLists ? {
|
|
27670
|
+
HTMLAttributes: {
|
|
27671
|
+
class: 'contentful-ordered-list',
|
|
27672
|
+
},
|
|
27673
|
+
} : false,
|
|
27674
|
+
blockquote: editorConfig.allowQuotes ? {
|
|
27675
|
+
HTMLAttributes: {
|
|
27676
|
+
class: 'contentful-blockquote',
|
|
27677
|
+
},
|
|
27678
|
+
} : false,
|
|
27679
|
+
}));
|
|
27680
|
+
// Add underline extension only if it's in availableMarks
|
|
27681
|
+
if (editorConfig.availableMarks.includes('underline')) {
|
|
27682
|
+
exts.push(Underline);
|
|
27683
|
+
}
|
|
27684
|
+
// Add link extension only if hyperlinks are allowed
|
|
27685
|
+
if (editorConfig.allowHyperlinks) {
|
|
27686
|
+
exts.push(Link.configure({
|
|
27687
|
+
openOnClick: false,
|
|
27688
|
+
HTMLAttributes: {
|
|
27689
|
+
class: 'contentful-link',
|
|
27690
|
+
rel: 'noopener noreferrer',
|
|
27691
|
+
},
|
|
27692
|
+
}));
|
|
27693
|
+
}
|
|
27694
|
+
// Add table extensions only if tables are allowed
|
|
27695
|
+
if (editorConfig.allowTables) {
|
|
27696
|
+
exts.push(Table.configure({
|
|
27697
|
+
resizable: true,
|
|
27698
|
+
HTMLAttributes: {
|
|
27699
|
+
class: 'contentful-table',
|
|
27700
|
+
},
|
|
27701
|
+
}), TableRow.configure({
|
|
27702
|
+
HTMLAttributes: {
|
|
27703
|
+
class: 'contentful-table-row',
|
|
27704
|
+
},
|
|
27705
|
+
}), TableHeader.configure({
|
|
27706
|
+
HTMLAttributes: {
|
|
27707
|
+
class: 'contentful-table-header',
|
|
27708
|
+
},
|
|
27709
|
+
}), TableCell.configure({
|
|
27710
|
+
HTMLAttributes: {
|
|
27711
|
+
class: 'contentful-table-cell',
|
|
27712
|
+
},
|
|
27713
|
+
}));
|
|
27714
|
+
}
|
|
27715
|
+
return exts;
|
|
27716
|
+
}, [editorConfig]);
|
|
27405
27717
|
const editor = useEditor({
|
|
27406
27718
|
extensions,
|
|
27407
27719
|
content: initialValue ? contentfulToTiptap(initialValue) : '',
|
|
@@ -27432,18 +27744,17 @@ const ContentfulRichTextEditor = ({ initialValue, onChange, onEmbedEntry, onEmbe
|
|
|
27432
27744
|
}
|
|
27433
27745
|
}, [editor, initialValue]);
|
|
27434
27746
|
const handleEmbedEntry = useCallback(async () => {
|
|
27435
|
-
var _a;
|
|
27436
|
-
if (onEmbedEntry && editor) {
|
|
27747
|
+
var _a, _b;
|
|
27748
|
+
if (onEmbedEntry && editor && editorConfig.allowEmbeddedEntries) {
|
|
27437
27749
|
try {
|
|
27438
27750
|
const entry = await onEmbedEntry();
|
|
27439
27751
|
if (entry) {
|
|
27440
|
-
// Insert embedded entry at cursor position
|
|
27441
27752
|
editor.chain().focus().insertContent({
|
|
27442
27753
|
type: 'paragraph',
|
|
27443
27754
|
content: [
|
|
27444
27755
|
{
|
|
27445
27756
|
type: 'text',
|
|
27446
|
-
text: `[Embedded Entry: ${((_a = entry.sys) === null || _a === void 0 ? void 0 : _a.id) || 'Unknown'}]`,
|
|
27757
|
+
text: `[Embedded Entry: ${((_a = entry.sys) === null || _a === void 0 ? void 0 : _a.id) || ((_b = entry.fields) === null || _b === void 0 ? void 0 : _b.title) || 'Unknown'}]`,
|
|
27447
27758
|
marks: [{ type: 'bold' }],
|
|
27448
27759
|
},
|
|
27449
27760
|
],
|
|
@@ -27454,20 +27765,19 @@ const ContentfulRichTextEditor = ({ initialValue, onChange, onEmbedEntry, onEmbe
|
|
|
27454
27765
|
console.error('Error embedding entry:', error);
|
|
27455
27766
|
}
|
|
27456
27767
|
}
|
|
27457
|
-
}, [onEmbedEntry, editor]);
|
|
27768
|
+
}, [onEmbedEntry, editor, editorConfig.allowEmbeddedEntries]);
|
|
27458
27769
|
const handleEmbedAsset = useCallback(async () => {
|
|
27459
|
-
var _a;
|
|
27460
|
-
if (onEmbedAsset && editor) {
|
|
27770
|
+
var _a, _b;
|
|
27771
|
+
if (onEmbedAsset && editor && editorConfig.allowEmbeddedAssets) {
|
|
27461
27772
|
try {
|
|
27462
27773
|
const asset = await onEmbedAsset();
|
|
27463
27774
|
if (asset) {
|
|
27464
|
-
// Insert embedded asset at cursor position
|
|
27465
27775
|
editor.chain().focus().insertContent({
|
|
27466
27776
|
type: 'paragraph',
|
|
27467
27777
|
content: [
|
|
27468
27778
|
{
|
|
27469
27779
|
type: 'text',
|
|
27470
|
-
text: `[Embedded Asset: ${((_a = asset.sys) === null || _a === void 0 ? void 0 : _a.id) || 'Unknown'}]`,
|
|
27780
|
+
text: `[Embedded Asset: ${((_a = asset.sys) === null || _a === void 0 ? void 0 : _a.id) || ((_b = asset.fields) === null || _b === void 0 ? void 0 : _b.title) || 'Unknown'}]`,
|
|
27471
27781
|
marks: [{ type: 'bold' }],
|
|
27472
27782
|
},
|
|
27473
27783
|
],
|
|
@@ -27478,12 +27788,33 @@ const ContentfulRichTextEditor = ({ initialValue, onChange, onEmbedEntry, onEmbe
|
|
|
27478
27788
|
console.error('Error embedding asset:', error);
|
|
27479
27789
|
}
|
|
27480
27790
|
}
|
|
27481
|
-
}, [onEmbedAsset, editor]);
|
|
27791
|
+
}, [onEmbedAsset, editor, editorConfig.allowEmbeddedAssets]);
|
|
27792
|
+
const handleEmbedInlineEntry = useCallback(async () => {
|
|
27793
|
+
var _a, _b;
|
|
27794
|
+
if (onEmbedInlineEntry && editor && editorConfig.allowInlineEntries) {
|
|
27795
|
+
try {
|
|
27796
|
+
const entry = await onEmbedInlineEntry();
|
|
27797
|
+
if (entry) {
|
|
27798
|
+
editor.chain().focus().insertContent({
|
|
27799
|
+
type: 'text',
|
|
27800
|
+
text: `[Inline Entry: ${((_a = entry.sys) === null || _a === void 0 ? void 0 : _a.id) || ((_b = entry.fields) === null || _b === void 0 ? void 0 : _b.title) || 'Unknown'}]`,
|
|
27801
|
+
marks: [{ type: 'bold' }],
|
|
27802
|
+
}).run();
|
|
27803
|
+
}
|
|
27804
|
+
}
|
|
27805
|
+
catch (error) {
|
|
27806
|
+
console.error('Error embedding inline entry:', error);
|
|
27807
|
+
}
|
|
27808
|
+
}
|
|
27809
|
+
}, [onEmbedInlineEntry, editor, editorConfig.allowInlineEntries]);
|
|
27482
27810
|
if (!editor) {
|
|
27483
27811
|
return (jsx("div", { className: `contentful-editor contentful-editor--loading ${className}`, children: jsx("div", { className: "contentful-editor__loading", children: "Loading editor..." }) }));
|
|
27484
27812
|
}
|
|
27485
|
-
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" }) })] }));
|
|
27813
|
+
return (jsxs("div", { className: `contentful-editor contentful-editor--${theme} ${className}`, children: [!readonly && (jsx(ContentfulToolbar, { editor: editor, onEmbedEntry: editorConfig.allowEmbeddedEntries ? handleEmbedEntry : undefined, onEmbedAsset: editorConfig.allowEmbeddedAssets ? handleEmbedAsset : undefined, onEmbedInlineEntry: editorConfig.allowInlineEntries ? handleEmbedInlineEntry : undefined, disabledFeatures: editorConfig.disabledFeatures, availableHeadings: editorConfig.availableHeadings, availableMarks: editorConfig.availableMarks, allowHyperlinks: editorConfig.allowHyperlinks })), jsx("div", { className: "contentful-editor__content-wrapper", children: jsx(EditorContent, { editor: editor, className: "contentful-editor__content" }) })] }));
|
|
27486
27814
|
};
|
|
27487
27815
|
|
|
27488
|
-
|
|
27816
|
+
var BLOCKS = distExports.BLOCKS;
|
|
27817
|
+
var INLINES = distExports.INLINES;
|
|
27818
|
+
var MARKS = distExports.MARKS;
|
|
27819
|
+
export { BLOCKS, ContentfulRichTextEditor, ContentfulToolbar, INLINES, MARKS, contentfulToTiptap, countWords, createEmptyDocument, createMockFieldConfig, extractPlainText, fetchContentfulFieldConfig, findEmbeddedContent, parseContentfulFieldConfig, sanitizeContentfulDocument, tiptapToContentful, validateContentfulDocument };
|
|
27489
27820
|
//# sourceMappingURL=index.esm.js.map
|