@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/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
- if (e.key === 'Enter') {
26584
- handleLinkSubmit();
26585
- }
26586
- if (e.key === 'Escape') {
26587
- setShowLinkInput(false);
26588
- }
26589
- }, autoFocus: true }), jsx("button", { onClick: handleLinkSubmit, children: "\u2713" }), jsx("button", { onClick: () => setShowLinkInput(false), children: "\u2717" })] }))] }))] }), 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" }))] }), jsx("div", { className: "contentful-toolbar__separator" }), jsx("div", { className: "contentful-toolbar__group contentful-toolbar__group--right", children: !isDisabled('embed') && (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: "Entry" })), onEmbedAsset && (jsx("button", { className: "contentful-toolbar__embed-option", onClick: onEmbedAsset, children: "Media" }))] })] })) })] }));
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: ${((_b = (_a = node.data.target) === null || _a === void 0 ? void 0 : _a.sys) === null || _b === void 0 ? void 0 : _b.id) || 'Unknown'}]`,
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: ${((_d = (_c = node.data.target) === null || _c === void 0 ? void 0 : _c.sys) === null || _d === void 0 ? void 0 : _d.id) || 'Unknown'}]`,
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 = ((_e = textNode.marks) === null || _e === void 0 ? void 0 : _e.map(mark => {
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
- 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'] }) => {
27352
- // Build extensions array based on available features
27353
- const extensions = [];
27354
- // Add StarterKit with configuration
27355
- extensions.push(StarterKit.configure({
27356
- heading: {
27357
- levels: availableHeadings,
27358
- },
27359
- bold: availableMarks.includes('bold') ? {} : false,
27360
- italic: availableMarks.includes('italic') ? {} : false,
27361
- bulletList: {
27362
- HTMLAttributes: {
27363
- class: 'contentful-bullet-list',
27364
- },
27365
- },
27366
- orderedList: {
27367
- HTMLAttributes: {
27368
- class: 'contentful-ordered-list',
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
- blockquote: {
27372
- HTMLAttributes: {
27373
- class: 'contentful-blockquote',
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
- // Always add underline extension to schema to support content with underline marks
27378
- // The availableMarks prop only controls toolbar visibility, not schema support
27379
- extensions.push(Underline);
27380
- // Add other extensions
27381
- extensions.push(Link.configure({
27382
- openOnClick: false,
27383
- HTMLAttributes: {
27384
- class: 'contentful-link',
27385
- rel: 'noopener noreferrer',
27386
- },
27387
- }), Table.configure({
27388
- resizable: true,
27389
- HTMLAttributes: {
27390
- class: 'contentful-table',
27391
- },
27392
- }), TableRow.configure({
27393
- HTMLAttributes: {
27394
- class: 'contentful-table-row',
27395
- },
27396
- }), TableHeader.configure({
27397
- HTMLAttributes: {
27398
- class: 'contentful-table-header',
27399
- },
27400
- }), TableCell.configure({
27401
- HTMLAttributes: {
27402
- class: 'contentful-table-cell',
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
- export { ContentfulRichTextEditor, ContentfulToolbar, contentfulToTiptap, createEmptyDocument, tiptapToContentful, validateContentfulDocument };
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