@crashbytes/contentful-richtext-editor 1.0.5 → 1.0.7
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 +10 -10
- package/dist/components/ContentfulEditor.d.ts +2 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +43 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +1206 -221
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1216 -219
- package/dist/index.js.map +1 -1
- package/dist/utils/contentfulTransform.d.ts +14 -6
- package/package.json +2 -2
package/dist/index.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import React, { forwardRef, createContext, useRef, useState, useDebugValue, useEffect, useContext, createRef, memo, createElement, useLayoutEffect, version, useCallback } from 'react';
|
|
1
2
|
import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
2
|
-
import
|
|
3
|
-
import ReactDOM from 'react-dom';
|
|
3
|
+
import ReactDOM, { flushSync } from 'react-dom';
|
|
4
4
|
|
|
5
5
|
// ::- Persistent data structure representing an ordered mapping from
|
|
6
6
|
// strings to values, with some convenient update methods.
|
|
@@ -18311,6 +18311,222 @@ class Node {
|
|
|
18311
18311
|
}
|
|
18312
18312
|
}
|
|
18313
18313
|
|
|
18314
|
+
/**
|
|
18315
|
+
* Node views are used to customize the rendered DOM structure of a node.
|
|
18316
|
+
* @see https://tiptap.dev/guide/node-views
|
|
18317
|
+
*/
|
|
18318
|
+
class NodeView {
|
|
18319
|
+
constructor(component, props, options) {
|
|
18320
|
+
this.isDragging = false;
|
|
18321
|
+
this.component = component;
|
|
18322
|
+
this.editor = props.editor;
|
|
18323
|
+
this.options = {
|
|
18324
|
+
stopEvent: null,
|
|
18325
|
+
ignoreMutation: null,
|
|
18326
|
+
...options,
|
|
18327
|
+
};
|
|
18328
|
+
this.extension = props.extension;
|
|
18329
|
+
this.node = props.node;
|
|
18330
|
+
this.decorations = props.decorations;
|
|
18331
|
+
this.innerDecorations = props.innerDecorations;
|
|
18332
|
+
this.view = props.view;
|
|
18333
|
+
this.HTMLAttributes = props.HTMLAttributes;
|
|
18334
|
+
this.getPos = props.getPos;
|
|
18335
|
+
this.mount();
|
|
18336
|
+
}
|
|
18337
|
+
mount() {
|
|
18338
|
+
// eslint-disable-next-line
|
|
18339
|
+
return;
|
|
18340
|
+
}
|
|
18341
|
+
get dom() {
|
|
18342
|
+
return this.editor.view.dom;
|
|
18343
|
+
}
|
|
18344
|
+
get contentDOM() {
|
|
18345
|
+
return null;
|
|
18346
|
+
}
|
|
18347
|
+
onDragStart(event) {
|
|
18348
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
18349
|
+
const { view } = this.editor;
|
|
18350
|
+
const target = event.target;
|
|
18351
|
+
// get the drag handle element
|
|
18352
|
+
// `closest` is not available for text nodes so we may have to use its parent
|
|
18353
|
+
const dragHandle = target.nodeType === 3
|
|
18354
|
+
? (_a = target.parentElement) === null || _a === void 0 ? void 0 : _a.closest('[data-drag-handle]')
|
|
18355
|
+
: target.closest('[data-drag-handle]');
|
|
18356
|
+
if (!this.dom || ((_b = this.contentDOM) === null || _b === void 0 ? void 0 : _b.contains(target)) || !dragHandle) {
|
|
18357
|
+
return;
|
|
18358
|
+
}
|
|
18359
|
+
let x = 0;
|
|
18360
|
+
let y = 0;
|
|
18361
|
+
// calculate offset for drag element if we use a different drag handle element
|
|
18362
|
+
if (this.dom !== dragHandle) {
|
|
18363
|
+
const domBox = this.dom.getBoundingClientRect();
|
|
18364
|
+
const handleBox = dragHandle.getBoundingClientRect();
|
|
18365
|
+
// In React, we have to go through nativeEvent to reach offsetX/offsetY.
|
|
18366
|
+
const offsetX = (_c = event.offsetX) !== null && _c !== void 0 ? _c : (_d = event.nativeEvent) === null || _d === void 0 ? void 0 : _d.offsetX;
|
|
18367
|
+
const offsetY = (_e = event.offsetY) !== null && _e !== void 0 ? _e : (_f = event.nativeEvent) === null || _f === void 0 ? void 0 : _f.offsetY;
|
|
18368
|
+
x = handleBox.x - domBox.x + offsetX;
|
|
18369
|
+
y = handleBox.y - domBox.y + offsetY;
|
|
18370
|
+
}
|
|
18371
|
+
const clonedNode = this.dom.cloneNode(true);
|
|
18372
|
+
(_g = event.dataTransfer) === null || _g === void 0 ? void 0 : _g.setDragImage(clonedNode, x, y);
|
|
18373
|
+
const pos = this.getPos();
|
|
18374
|
+
if (typeof pos !== 'number') {
|
|
18375
|
+
return;
|
|
18376
|
+
}
|
|
18377
|
+
// we need to tell ProseMirror that we want to move the whole node
|
|
18378
|
+
// so we create a NodeSelection
|
|
18379
|
+
const selection = NodeSelection.create(view.state.doc, pos);
|
|
18380
|
+
const transaction = view.state.tr.setSelection(selection);
|
|
18381
|
+
view.dispatch(transaction);
|
|
18382
|
+
}
|
|
18383
|
+
stopEvent(event) {
|
|
18384
|
+
var _a;
|
|
18385
|
+
if (!this.dom) {
|
|
18386
|
+
return false;
|
|
18387
|
+
}
|
|
18388
|
+
if (typeof this.options.stopEvent === 'function') {
|
|
18389
|
+
return this.options.stopEvent({ event });
|
|
18390
|
+
}
|
|
18391
|
+
const target = event.target;
|
|
18392
|
+
const isInElement = this.dom.contains(target) && !((_a = this.contentDOM) === null || _a === void 0 ? void 0 : _a.contains(target));
|
|
18393
|
+
// any event from child nodes should be handled by ProseMirror
|
|
18394
|
+
if (!isInElement) {
|
|
18395
|
+
return false;
|
|
18396
|
+
}
|
|
18397
|
+
const isDragEvent = event.type.startsWith('drag');
|
|
18398
|
+
const isDropEvent = event.type === 'drop';
|
|
18399
|
+
const isInput = ['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA'].includes(target.tagName) || target.isContentEditable;
|
|
18400
|
+
// any input event within node views should be ignored by ProseMirror
|
|
18401
|
+
if (isInput && !isDropEvent && !isDragEvent) {
|
|
18402
|
+
return true;
|
|
18403
|
+
}
|
|
18404
|
+
const { isEditable } = this.editor;
|
|
18405
|
+
const { isDragging } = this;
|
|
18406
|
+
const isDraggable = !!this.node.type.spec.draggable;
|
|
18407
|
+
const isSelectable = NodeSelection.isSelectable(this.node);
|
|
18408
|
+
const isCopyEvent = event.type === 'copy';
|
|
18409
|
+
const isPasteEvent = event.type === 'paste';
|
|
18410
|
+
const isCutEvent = event.type === 'cut';
|
|
18411
|
+
const isClickEvent = event.type === 'mousedown';
|
|
18412
|
+
// ProseMirror tries to drag selectable nodes
|
|
18413
|
+
// even if `draggable` is set to `false`
|
|
18414
|
+
// this fix prevents that
|
|
18415
|
+
if (!isDraggable && isSelectable && isDragEvent && event.target === this.dom) {
|
|
18416
|
+
event.preventDefault();
|
|
18417
|
+
}
|
|
18418
|
+
if (isDraggable && isDragEvent && !isDragging && event.target === this.dom) {
|
|
18419
|
+
event.preventDefault();
|
|
18420
|
+
return false;
|
|
18421
|
+
}
|
|
18422
|
+
// we have to store that dragging started
|
|
18423
|
+
if (isDraggable && isEditable && !isDragging && isClickEvent) {
|
|
18424
|
+
const dragHandle = target.closest('[data-drag-handle]');
|
|
18425
|
+
const isValidDragHandle = dragHandle && (this.dom === dragHandle || this.dom.contains(dragHandle));
|
|
18426
|
+
if (isValidDragHandle) {
|
|
18427
|
+
this.isDragging = true;
|
|
18428
|
+
document.addEventListener('dragend', () => {
|
|
18429
|
+
this.isDragging = false;
|
|
18430
|
+
}, { once: true });
|
|
18431
|
+
document.addEventListener('drop', () => {
|
|
18432
|
+
this.isDragging = false;
|
|
18433
|
+
}, { once: true });
|
|
18434
|
+
document.addEventListener('mouseup', () => {
|
|
18435
|
+
this.isDragging = false;
|
|
18436
|
+
}, { once: true });
|
|
18437
|
+
}
|
|
18438
|
+
}
|
|
18439
|
+
// these events are handled by prosemirror
|
|
18440
|
+
if (isDragging
|
|
18441
|
+
|| isDropEvent
|
|
18442
|
+
|| isCopyEvent
|
|
18443
|
+
|| isPasteEvent
|
|
18444
|
+
|| isCutEvent
|
|
18445
|
+
|| (isClickEvent && isSelectable)) {
|
|
18446
|
+
return false;
|
|
18447
|
+
}
|
|
18448
|
+
return true;
|
|
18449
|
+
}
|
|
18450
|
+
/**
|
|
18451
|
+
* Called when a DOM [mutation](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) or a selection change happens within the view.
|
|
18452
|
+
* @return `false` if the editor should re-read the selection or re-parse the range around the mutation
|
|
18453
|
+
* @return `true` if it can safely be ignored.
|
|
18454
|
+
*/
|
|
18455
|
+
ignoreMutation(mutation) {
|
|
18456
|
+
if (!this.dom || !this.contentDOM) {
|
|
18457
|
+
return true;
|
|
18458
|
+
}
|
|
18459
|
+
if (typeof this.options.ignoreMutation === 'function') {
|
|
18460
|
+
return this.options.ignoreMutation({ mutation });
|
|
18461
|
+
}
|
|
18462
|
+
// a leaf/atom node is like a black box for ProseMirror
|
|
18463
|
+
// and should be fully handled by the node view
|
|
18464
|
+
if (this.node.isLeaf || this.node.isAtom) {
|
|
18465
|
+
return true;
|
|
18466
|
+
}
|
|
18467
|
+
// ProseMirror should handle any selections
|
|
18468
|
+
if (mutation.type === 'selection') {
|
|
18469
|
+
return false;
|
|
18470
|
+
}
|
|
18471
|
+
// try to prevent a bug on iOS and Android that will break node views on enter
|
|
18472
|
+
// this is because ProseMirror can’t preventDispatch on enter
|
|
18473
|
+
// this will lead to a re-render of the node view on enter
|
|
18474
|
+
// see: https://github.com/ueberdosis/tiptap/issues/1214
|
|
18475
|
+
// see: https://github.com/ueberdosis/tiptap/issues/2534
|
|
18476
|
+
if (this.dom.contains(mutation.target)
|
|
18477
|
+
&& mutation.type === 'childList'
|
|
18478
|
+
&& (isiOS() || isAndroid())
|
|
18479
|
+
&& this.editor.isFocused) {
|
|
18480
|
+
const changedNodes = [
|
|
18481
|
+
...Array.from(mutation.addedNodes),
|
|
18482
|
+
...Array.from(mutation.removedNodes),
|
|
18483
|
+
];
|
|
18484
|
+
// we’ll check if every changed node is contentEditable
|
|
18485
|
+
// to make sure it’s probably mutated by ProseMirror
|
|
18486
|
+
if (changedNodes.every(node => node.isContentEditable)) {
|
|
18487
|
+
return false;
|
|
18488
|
+
}
|
|
18489
|
+
}
|
|
18490
|
+
// we will allow mutation contentDOM with attributes
|
|
18491
|
+
// so we can for example adding classes within our node view
|
|
18492
|
+
if (this.contentDOM === mutation.target && mutation.type === 'attributes') {
|
|
18493
|
+
return true;
|
|
18494
|
+
}
|
|
18495
|
+
// ProseMirror should handle any changes within contentDOM
|
|
18496
|
+
if (this.contentDOM.contains(mutation.target)) {
|
|
18497
|
+
return false;
|
|
18498
|
+
}
|
|
18499
|
+
return true;
|
|
18500
|
+
}
|
|
18501
|
+
/**
|
|
18502
|
+
* Update the attributes of the prosemirror node.
|
|
18503
|
+
*/
|
|
18504
|
+
updateAttributes(attributes) {
|
|
18505
|
+
this.editor.commands.command(({ tr }) => {
|
|
18506
|
+
const pos = this.getPos();
|
|
18507
|
+
if (typeof pos !== 'number') {
|
|
18508
|
+
return false;
|
|
18509
|
+
}
|
|
18510
|
+
tr.setNodeMarkup(pos, undefined, {
|
|
18511
|
+
...this.node.attrs,
|
|
18512
|
+
...attributes,
|
|
18513
|
+
});
|
|
18514
|
+
return true;
|
|
18515
|
+
});
|
|
18516
|
+
}
|
|
18517
|
+
/**
|
|
18518
|
+
* Delete the node.
|
|
18519
|
+
*/
|
|
18520
|
+
deleteNode() {
|
|
18521
|
+
const from = this.getPos();
|
|
18522
|
+
if (typeof from !== 'number') {
|
|
18523
|
+
return;
|
|
18524
|
+
}
|
|
18525
|
+
const to = from + this.node.nodeSize;
|
|
18526
|
+
this.editor.commands.deleteRange({ from, to });
|
|
18527
|
+
}
|
|
18528
|
+
}
|
|
18529
|
+
|
|
18314
18530
|
/**
|
|
18315
18531
|
* Build an paste rule that adds a mark when the
|
|
18316
18532
|
* matched text is pasted into it.
|
|
@@ -19471,6 +19687,394 @@ React.forwardRef((props, ref) => {
|
|
|
19471
19687
|
} }));
|
|
19472
19688
|
});
|
|
19473
19689
|
|
|
19690
|
+
/**
|
|
19691
|
+
* Check if a component is a class component.
|
|
19692
|
+
* @param Component
|
|
19693
|
+
* @returns {boolean}
|
|
19694
|
+
*/
|
|
19695
|
+
function isClassComponent(Component) {
|
|
19696
|
+
return !!(typeof Component === 'function'
|
|
19697
|
+
&& Component.prototype
|
|
19698
|
+
&& Component.prototype.isReactComponent);
|
|
19699
|
+
}
|
|
19700
|
+
/**
|
|
19701
|
+
* Check if a component is a forward ref component.
|
|
19702
|
+
* @param Component
|
|
19703
|
+
* @returns {boolean}
|
|
19704
|
+
*/
|
|
19705
|
+
function isForwardRefComponent(Component) {
|
|
19706
|
+
return !!(typeof Component === 'object'
|
|
19707
|
+
&& Component.$$typeof
|
|
19708
|
+
&& (Component.$$typeof.toString() === 'Symbol(react.forward_ref)'
|
|
19709
|
+
|| Component.$$typeof.description === 'react.forward_ref'));
|
|
19710
|
+
}
|
|
19711
|
+
/**
|
|
19712
|
+
* Check if a component is a memoized component.
|
|
19713
|
+
* @param Component
|
|
19714
|
+
* @returns {boolean}
|
|
19715
|
+
*/
|
|
19716
|
+
function isMemoComponent(Component) {
|
|
19717
|
+
return !!(typeof Component === 'object'
|
|
19718
|
+
&& Component.$$typeof
|
|
19719
|
+
&& (Component.$$typeof.toString() === 'Symbol(react.memo)' || Component.$$typeof.description === 'react.memo'));
|
|
19720
|
+
}
|
|
19721
|
+
/**
|
|
19722
|
+
* Check if a component can safely receive a ref prop.
|
|
19723
|
+
* This includes class components, forwardRef components, and memoized components
|
|
19724
|
+
* that wrap forwardRef or class components.
|
|
19725
|
+
* @param Component
|
|
19726
|
+
* @returns {boolean}
|
|
19727
|
+
*/
|
|
19728
|
+
function canReceiveRef(Component) {
|
|
19729
|
+
// Check if it's a class component
|
|
19730
|
+
if (isClassComponent(Component)) {
|
|
19731
|
+
return true;
|
|
19732
|
+
}
|
|
19733
|
+
// Check if it's a forwardRef component
|
|
19734
|
+
if (isForwardRefComponent(Component)) {
|
|
19735
|
+
return true;
|
|
19736
|
+
}
|
|
19737
|
+
// Check if it's a memoized component
|
|
19738
|
+
if (isMemoComponent(Component)) {
|
|
19739
|
+
// For memoized components, check the wrapped component
|
|
19740
|
+
const wrappedComponent = Component.type;
|
|
19741
|
+
if (wrappedComponent) {
|
|
19742
|
+
return isClassComponent(wrappedComponent) || isForwardRefComponent(wrappedComponent);
|
|
19743
|
+
}
|
|
19744
|
+
}
|
|
19745
|
+
return false;
|
|
19746
|
+
}
|
|
19747
|
+
/**
|
|
19748
|
+
* Check if we're running React 19+ by detecting if function components support ref props
|
|
19749
|
+
* @returns {boolean}
|
|
19750
|
+
*/
|
|
19751
|
+
function isReact19Plus() {
|
|
19752
|
+
// React 19 is detected by checking React version if available
|
|
19753
|
+
// In practice, we'll use a more conservative approach and assume React 18 behavior
|
|
19754
|
+
// unless we can definitively detect React 19
|
|
19755
|
+
try {
|
|
19756
|
+
// @ts-ignore
|
|
19757
|
+
if (version) {
|
|
19758
|
+
const majorVersion = parseInt(version.split('.')[0], 10);
|
|
19759
|
+
return majorVersion >= 19;
|
|
19760
|
+
}
|
|
19761
|
+
}
|
|
19762
|
+
catch {
|
|
19763
|
+
// Fallback to React 18 behavior if we can't determine version
|
|
19764
|
+
}
|
|
19765
|
+
return false;
|
|
19766
|
+
}
|
|
19767
|
+
/**
|
|
19768
|
+
* The ReactRenderer class. It's responsible for rendering React components inside the editor.
|
|
19769
|
+
* @example
|
|
19770
|
+
* new ReactRenderer(MyComponent, {
|
|
19771
|
+
* editor,
|
|
19772
|
+
* props: {
|
|
19773
|
+
* foo: 'bar',
|
|
19774
|
+
* },
|
|
19775
|
+
* as: 'span',
|
|
19776
|
+
* })
|
|
19777
|
+
*/
|
|
19778
|
+
class ReactRenderer {
|
|
19779
|
+
/**
|
|
19780
|
+
* Immediately creates element and renders the provided React component.
|
|
19781
|
+
*/
|
|
19782
|
+
constructor(component, { editor, props = {}, as = 'div', className = '', }) {
|
|
19783
|
+
this.ref = null;
|
|
19784
|
+
this.id = Math.floor(Math.random() * 0xFFFFFFFF).toString();
|
|
19785
|
+
this.component = component;
|
|
19786
|
+
this.editor = editor;
|
|
19787
|
+
this.props = props;
|
|
19788
|
+
this.element = document.createElement(as);
|
|
19789
|
+
this.element.classList.add('react-renderer');
|
|
19790
|
+
if (className) {
|
|
19791
|
+
this.element.classList.add(...className.split(' '));
|
|
19792
|
+
}
|
|
19793
|
+
if (this.editor.isInitialized) {
|
|
19794
|
+
// On first render, we need to flush the render synchronously
|
|
19795
|
+
// Renders afterwards can be async, but this fixes a cursor positioning issue
|
|
19796
|
+
flushSync(() => {
|
|
19797
|
+
this.render();
|
|
19798
|
+
});
|
|
19799
|
+
}
|
|
19800
|
+
else {
|
|
19801
|
+
this.render();
|
|
19802
|
+
}
|
|
19803
|
+
}
|
|
19804
|
+
/**
|
|
19805
|
+
* Render the React component.
|
|
19806
|
+
*/
|
|
19807
|
+
render() {
|
|
19808
|
+
var _a;
|
|
19809
|
+
const Component = this.component;
|
|
19810
|
+
const props = this.props;
|
|
19811
|
+
const editor = this.editor;
|
|
19812
|
+
// Handle ref forwarding with React 18/19 compatibility
|
|
19813
|
+
const isReact19 = isReact19Plus();
|
|
19814
|
+
const componentCanReceiveRef = canReceiveRef(Component);
|
|
19815
|
+
const elementProps = { ...props };
|
|
19816
|
+
// Always remove ref if the component cannot receive it (unless React 19+)
|
|
19817
|
+
if (elementProps.ref && !(isReact19 || componentCanReceiveRef)) {
|
|
19818
|
+
delete elementProps.ref;
|
|
19819
|
+
}
|
|
19820
|
+
// Only assign our own ref if allowed
|
|
19821
|
+
if (!elementProps.ref && (isReact19 || componentCanReceiveRef)) {
|
|
19822
|
+
// @ts-ignore - Setting ref prop for compatible components
|
|
19823
|
+
elementProps.ref = (ref) => {
|
|
19824
|
+
this.ref = ref;
|
|
19825
|
+
};
|
|
19826
|
+
}
|
|
19827
|
+
this.reactElement = React.createElement(Component, { ...elementProps });
|
|
19828
|
+
(_a = editor === null || editor === void 0 ? void 0 : editor.contentComponent) === null || _a === void 0 ? void 0 : _a.setRenderer(this.id, this);
|
|
19829
|
+
}
|
|
19830
|
+
/**
|
|
19831
|
+
* Re-renders the React component with new props.
|
|
19832
|
+
*/
|
|
19833
|
+
updateProps(props = {}) {
|
|
19834
|
+
this.props = {
|
|
19835
|
+
...this.props,
|
|
19836
|
+
...props,
|
|
19837
|
+
};
|
|
19838
|
+
this.render();
|
|
19839
|
+
}
|
|
19840
|
+
/**
|
|
19841
|
+
* Destroy the React component.
|
|
19842
|
+
*/
|
|
19843
|
+
destroy() {
|
|
19844
|
+
var _a;
|
|
19845
|
+
const editor = this.editor;
|
|
19846
|
+
(_a = editor === null || editor === void 0 ? void 0 : editor.contentComponent) === null || _a === void 0 ? void 0 : _a.removeRenderer(this.id);
|
|
19847
|
+
}
|
|
19848
|
+
/**
|
|
19849
|
+
* Update the attributes of the element that holds the React component.
|
|
19850
|
+
*/
|
|
19851
|
+
updateAttributes(attributes) {
|
|
19852
|
+
Object.keys(attributes).forEach(key => {
|
|
19853
|
+
this.element.setAttribute(key, attributes[key]);
|
|
19854
|
+
});
|
|
19855
|
+
}
|
|
19856
|
+
}
|
|
19857
|
+
|
|
19858
|
+
class ReactNodeView extends NodeView {
|
|
19859
|
+
/**
|
|
19860
|
+
* Setup the React component.
|
|
19861
|
+
* Called on initialization.
|
|
19862
|
+
*/
|
|
19863
|
+
mount() {
|
|
19864
|
+
const props = {
|
|
19865
|
+
editor: this.editor,
|
|
19866
|
+
node: this.node,
|
|
19867
|
+
decorations: this.decorations,
|
|
19868
|
+
innerDecorations: this.innerDecorations,
|
|
19869
|
+
view: this.view,
|
|
19870
|
+
selected: false,
|
|
19871
|
+
extension: this.extension,
|
|
19872
|
+
HTMLAttributes: this.HTMLAttributes,
|
|
19873
|
+
getPos: () => this.getPos(),
|
|
19874
|
+
updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
|
|
19875
|
+
deleteNode: () => this.deleteNode(),
|
|
19876
|
+
ref: createRef(),
|
|
19877
|
+
};
|
|
19878
|
+
if (!this.component.displayName) {
|
|
19879
|
+
const capitalizeFirstChar = (string) => {
|
|
19880
|
+
return string.charAt(0).toUpperCase() + string.substring(1);
|
|
19881
|
+
};
|
|
19882
|
+
this.component.displayName = capitalizeFirstChar(this.extension.name);
|
|
19883
|
+
}
|
|
19884
|
+
const onDragStart = this.onDragStart.bind(this);
|
|
19885
|
+
const nodeViewContentRef = element => {
|
|
19886
|
+
if (element && this.contentDOMElement && element.firstChild !== this.contentDOMElement) {
|
|
19887
|
+
element.appendChild(this.contentDOMElement);
|
|
19888
|
+
}
|
|
19889
|
+
};
|
|
19890
|
+
const context = { onDragStart, nodeViewContentRef };
|
|
19891
|
+
const Component = this.component;
|
|
19892
|
+
// For performance reasons, we memoize the provider component
|
|
19893
|
+
// And all of the things it requires are declared outside of the component, so it doesn't need to re-render
|
|
19894
|
+
const ReactNodeViewProvider = memo(componentProps => {
|
|
19895
|
+
return (React.createElement(ReactNodeViewContext.Provider, { value: context }, createElement(Component, componentProps)));
|
|
19896
|
+
});
|
|
19897
|
+
ReactNodeViewProvider.displayName = 'ReactNodeView';
|
|
19898
|
+
if (this.node.isLeaf) {
|
|
19899
|
+
this.contentDOMElement = null;
|
|
19900
|
+
}
|
|
19901
|
+
else if (this.options.contentDOMElementTag) {
|
|
19902
|
+
this.contentDOMElement = document.createElement(this.options.contentDOMElementTag);
|
|
19903
|
+
}
|
|
19904
|
+
else {
|
|
19905
|
+
this.contentDOMElement = document.createElement(this.node.isInline ? 'span' : 'div');
|
|
19906
|
+
}
|
|
19907
|
+
if (this.contentDOMElement) {
|
|
19908
|
+
this.contentDOMElement.dataset.nodeViewContentReact = '';
|
|
19909
|
+
// For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
|
|
19910
|
+
// With this fix it seems to work fine
|
|
19911
|
+
// See: https://github.com/ueberdosis/tiptap/issues/1197
|
|
19912
|
+
this.contentDOMElement.style.whiteSpace = 'inherit';
|
|
19913
|
+
}
|
|
19914
|
+
let as = this.node.isInline ? 'span' : 'div';
|
|
19915
|
+
if (this.options.as) {
|
|
19916
|
+
as = this.options.as;
|
|
19917
|
+
}
|
|
19918
|
+
const { className = '' } = this.options;
|
|
19919
|
+
this.handleSelectionUpdate = this.handleSelectionUpdate.bind(this);
|
|
19920
|
+
this.renderer = new ReactRenderer(ReactNodeViewProvider, {
|
|
19921
|
+
editor: this.editor,
|
|
19922
|
+
props,
|
|
19923
|
+
as,
|
|
19924
|
+
className: `node-${this.node.type.name} ${className}`.trim(),
|
|
19925
|
+
});
|
|
19926
|
+
this.editor.on('selectionUpdate', this.handleSelectionUpdate);
|
|
19927
|
+
this.updateElementAttributes();
|
|
19928
|
+
}
|
|
19929
|
+
/**
|
|
19930
|
+
* Return the DOM element.
|
|
19931
|
+
* This is the element that will be used to display the node view.
|
|
19932
|
+
*/
|
|
19933
|
+
get dom() {
|
|
19934
|
+
var _a;
|
|
19935
|
+
if (this.renderer.element.firstElementChild
|
|
19936
|
+
&& !((_a = this.renderer.element.firstElementChild) === null || _a === void 0 ? void 0 : _a.hasAttribute('data-node-view-wrapper'))) {
|
|
19937
|
+
throw Error('Please use the NodeViewWrapper component for your node view.');
|
|
19938
|
+
}
|
|
19939
|
+
return this.renderer.element;
|
|
19940
|
+
}
|
|
19941
|
+
/**
|
|
19942
|
+
* Return the content DOM element.
|
|
19943
|
+
* This is the element that will be used to display the rich-text content of the node.
|
|
19944
|
+
*/
|
|
19945
|
+
get contentDOM() {
|
|
19946
|
+
if (this.node.isLeaf) {
|
|
19947
|
+
return null;
|
|
19948
|
+
}
|
|
19949
|
+
return this.contentDOMElement;
|
|
19950
|
+
}
|
|
19951
|
+
/**
|
|
19952
|
+
* On editor selection update, check if the node is selected.
|
|
19953
|
+
* If it is, call `selectNode`, otherwise call `deselectNode`.
|
|
19954
|
+
*/
|
|
19955
|
+
handleSelectionUpdate() {
|
|
19956
|
+
const { from, to } = this.editor.state.selection;
|
|
19957
|
+
const pos = this.getPos();
|
|
19958
|
+
if (typeof pos !== 'number') {
|
|
19959
|
+
return;
|
|
19960
|
+
}
|
|
19961
|
+
if (from <= pos && to >= pos + this.node.nodeSize) {
|
|
19962
|
+
if (this.renderer.props.selected) {
|
|
19963
|
+
return;
|
|
19964
|
+
}
|
|
19965
|
+
this.selectNode();
|
|
19966
|
+
}
|
|
19967
|
+
else {
|
|
19968
|
+
if (!this.renderer.props.selected) {
|
|
19969
|
+
return;
|
|
19970
|
+
}
|
|
19971
|
+
this.deselectNode();
|
|
19972
|
+
}
|
|
19973
|
+
}
|
|
19974
|
+
/**
|
|
19975
|
+
* On update, update the React component.
|
|
19976
|
+
* To prevent unnecessary updates, the `update` option can be used.
|
|
19977
|
+
*/
|
|
19978
|
+
update(node, decorations, innerDecorations) {
|
|
19979
|
+
const rerenderComponent = (props) => {
|
|
19980
|
+
this.renderer.updateProps(props);
|
|
19981
|
+
if (typeof this.options.attrs === 'function') {
|
|
19982
|
+
this.updateElementAttributes();
|
|
19983
|
+
}
|
|
19984
|
+
};
|
|
19985
|
+
if (node.type !== this.node.type) {
|
|
19986
|
+
return false;
|
|
19987
|
+
}
|
|
19988
|
+
if (typeof this.options.update === 'function') {
|
|
19989
|
+
const oldNode = this.node;
|
|
19990
|
+
const oldDecorations = this.decorations;
|
|
19991
|
+
const oldInnerDecorations = this.innerDecorations;
|
|
19992
|
+
this.node = node;
|
|
19993
|
+
this.decorations = decorations;
|
|
19994
|
+
this.innerDecorations = innerDecorations;
|
|
19995
|
+
return this.options.update({
|
|
19996
|
+
oldNode,
|
|
19997
|
+
oldDecorations,
|
|
19998
|
+
newNode: node,
|
|
19999
|
+
newDecorations: decorations,
|
|
20000
|
+
oldInnerDecorations,
|
|
20001
|
+
innerDecorations,
|
|
20002
|
+
updateProps: () => rerenderComponent({ node, decorations, innerDecorations }),
|
|
20003
|
+
});
|
|
20004
|
+
}
|
|
20005
|
+
if (node === this.node
|
|
20006
|
+
&& this.decorations === decorations
|
|
20007
|
+
&& this.innerDecorations === innerDecorations) {
|
|
20008
|
+
return true;
|
|
20009
|
+
}
|
|
20010
|
+
this.node = node;
|
|
20011
|
+
this.decorations = decorations;
|
|
20012
|
+
this.innerDecorations = innerDecorations;
|
|
20013
|
+
rerenderComponent({ node, decorations, innerDecorations });
|
|
20014
|
+
return true;
|
|
20015
|
+
}
|
|
20016
|
+
/**
|
|
20017
|
+
* Select the node.
|
|
20018
|
+
* Add the `selected` prop and the `ProseMirror-selectednode` class.
|
|
20019
|
+
*/
|
|
20020
|
+
selectNode() {
|
|
20021
|
+
this.renderer.updateProps({
|
|
20022
|
+
selected: true,
|
|
20023
|
+
});
|
|
20024
|
+
this.renderer.element.classList.add('ProseMirror-selectednode');
|
|
20025
|
+
}
|
|
20026
|
+
/**
|
|
20027
|
+
* Deselect the node.
|
|
20028
|
+
* Remove the `selected` prop and the `ProseMirror-selectednode` class.
|
|
20029
|
+
*/
|
|
20030
|
+
deselectNode() {
|
|
20031
|
+
this.renderer.updateProps({
|
|
20032
|
+
selected: false,
|
|
20033
|
+
});
|
|
20034
|
+
this.renderer.element.classList.remove('ProseMirror-selectednode');
|
|
20035
|
+
}
|
|
20036
|
+
/**
|
|
20037
|
+
* Destroy the React component instance.
|
|
20038
|
+
*/
|
|
20039
|
+
destroy() {
|
|
20040
|
+
this.renderer.destroy();
|
|
20041
|
+
this.editor.off('selectionUpdate', this.handleSelectionUpdate);
|
|
20042
|
+
this.contentDOMElement = null;
|
|
20043
|
+
}
|
|
20044
|
+
/**
|
|
20045
|
+
* Update the attributes of the top-level element that holds the React component.
|
|
20046
|
+
* Applying the attributes defined in the `attrs` option.
|
|
20047
|
+
*/
|
|
20048
|
+
updateElementAttributes() {
|
|
20049
|
+
if (this.options.attrs) {
|
|
20050
|
+
let attrsObj = {};
|
|
20051
|
+
if (typeof this.options.attrs === 'function') {
|
|
20052
|
+
const extensionAttributes = this.editor.extensionManager.attributes;
|
|
20053
|
+
const HTMLAttributes = getRenderedAttributes(this.node, extensionAttributes);
|
|
20054
|
+
attrsObj = this.options.attrs({ node: this.node, HTMLAttributes });
|
|
20055
|
+
}
|
|
20056
|
+
else {
|
|
20057
|
+
attrsObj = this.options.attrs;
|
|
20058
|
+
}
|
|
20059
|
+
this.renderer.updateAttributes(attrsObj);
|
|
20060
|
+
}
|
|
20061
|
+
}
|
|
20062
|
+
}
|
|
20063
|
+
/**
|
|
20064
|
+
* Create a React node view renderer.
|
|
20065
|
+
*/
|
|
20066
|
+
function ReactNodeViewRenderer(component, options) {
|
|
20067
|
+
return props => {
|
|
20068
|
+
// try to get the parent component
|
|
20069
|
+
// this is important for vue devtools to show the component hierarchy correctly
|
|
20070
|
+
// maybe it’s `undefined` because <editor-content> isn’t rendered yet
|
|
20071
|
+
if (!props.editor.contentComponent) {
|
|
20072
|
+
return {};
|
|
20073
|
+
}
|
|
20074
|
+
return new ReactNodeView(component, props, options);
|
|
20075
|
+
};
|
|
20076
|
+
}
|
|
20077
|
+
|
|
19474
20078
|
/**
|
|
19475
20079
|
* Matches a blockquote to a `>` as input.
|
|
19476
20080
|
*/
|
|
@@ -26939,77 +27543,120 @@ function getSchemaWithNodeType(nodeType) {
|
|
|
26939
27543
|
} (dist));
|
|
26940
27544
|
|
|
26941
27545
|
/**
|
|
26942
|
-
*
|
|
27546
|
+
* Creates a professional-grade empty Tiptap document
|
|
26943
27547
|
*/
|
|
26944
|
-
const
|
|
26945
|
-
|
|
26946
|
-
|
|
27548
|
+
const createEmptyTiptapDocument = () => ({
|
|
27549
|
+
type: 'doc',
|
|
27550
|
+
content: [
|
|
27551
|
+
{
|
|
27552
|
+
type: 'paragraph',
|
|
27553
|
+
content: []
|
|
27554
|
+
}
|
|
27555
|
+
]
|
|
27556
|
+
});
|
|
27557
|
+
/**
|
|
27558
|
+
* Strategic error handler for transformation operations
|
|
27559
|
+
*/
|
|
27560
|
+
const createTransformError = (message, nodeType, context) => {
|
|
27561
|
+
const error = new Error(message);
|
|
27562
|
+
error.nodeType = nodeType;
|
|
27563
|
+
error.context = context;
|
|
27564
|
+
return error;
|
|
27565
|
+
};
|
|
27566
|
+
/**
|
|
27567
|
+
* Enhanced validation with comprehensive error reporting
|
|
27568
|
+
*/
|
|
27569
|
+
const validateContentfulDocument = (document) => {
|
|
27570
|
+
if (!document || typeof document !== 'object') {
|
|
27571
|
+
console.warn('[ContentfulTransform] Invalid document: not an object');
|
|
27572
|
+
return false;
|
|
27573
|
+
}
|
|
27574
|
+
if (document.nodeType !== dist.BLOCKS.DOCUMENT) {
|
|
27575
|
+
console.warn('[ContentfulTransform] Invalid document: incorrect nodeType', document.nodeType);
|
|
27576
|
+
return false;
|
|
27577
|
+
}
|
|
27578
|
+
if (!Array.isArray(document.content)) {
|
|
27579
|
+
console.warn('[ContentfulTransform] Invalid document: content is not an array');
|
|
27580
|
+
return false;
|
|
27581
|
+
}
|
|
27582
|
+
return true;
|
|
27583
|
+
};
|
|
27584
|
+
/**
|
|
27585
|
+
* Strategic internal conversion with comprehensive error handling
|
|
27586
|
+
*/
|
|
27587
|
+
const convertContentfulNodeInternal = (node) => {
|
|
27588
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
27589
|
+
try {
|
|
26947
27590
|
switch (node.nodeType) {
|
|
26948
27591
|
case dist.BLOCKS.DOCUMENT:
|
|
26949
27592
|
return {
|
|
26950
27593
|
type: 'doc',
|
|
26951
|
-
content: node.content.map(child =>
|
|
27594
|
+
content: node.content.map(child => {
|
|
27595
|
+
const converted = convertContentfulNodeInternal(child);
|
|
27596
|
+
return Array.isArray(converted) ? converted[0] : converted;
|
|
27597
|
+
}),
|
|
26952
27598
|
};
|
|
26953
27599
|
case dist.BLOCKS.PARAGRAPH:
|
|
26954
27600
|
return {
|
|
26955
27601
|
type: 'paragraph',
|
|
26956
|
-
content: node.content ? node.content.map(child =>
|
|
27602
|
+
content: node.content ? node.content.map(child => {
|
|
27603
|
+
const converted = convertContentfulNodeInternal(child);
|
|
27604
|
+
return Array.isArray(converted) ? converted[0] : converted;
|
|
27605
|
+
}) : [],
|
|
26957
27606
|
};
|
|
26958
27607
|
case dist.BLOCKS.HEADING_1:
|
|
26959
|
-
return {
|
|
26960
|
-
type: 'heading',
|
|
26961
|
-
attrs: { level: 1 },
|
|
26962
|
-
content: node.content.map(child => convertNode(child)).flat(),
|
|
26963
|
-
};
|
|
26964
27608
|
case dist.BLOCKS.HEADING_2:
|
|
26965
|
-
return {
|
|
26966
|
-
type: 'heading',
|
|
26967
|
-
attrs: { level: 2 },
|
|
26968
|
-
content: node.content.map(child => convertNode(child)).flat(),
|
|
26969
|
-
};
|
|
26970
27609
|
case dist.BLOCKS.HEADING_3:
|
|
26971
|
-
return {
|
|
26972
|
-
type: 'heading',
|
|
26973
|
-
attrs: { level: 3 },
|
|
26974
|
-
content: node.content.map(child => convertNode(child)).flat(),
|
|
26975
|
-
};
|
|
26976
27610
|
case dist.BLOCKS.HEADING_4:
|
|
26977
|
-
return {
|
|
26978
|
-
type: 'heading',
|
|
26979
|
-
attrs: { level: 4 },
|
|
26980
|
-
content: node.content.map(child => convertNode(child)).flat(),
|
|
26981
|
-
};
|
|
26982
27611
|
case dist.BLOCKS.HEADING_5:
|
|
26983
|
-
return {
|
|
26984
|
-
type: 'heading',
|
|
26985
|
-
attrs: { level: 5 },
|
|
26986
|
-
content: node.content.map(child => convertNode(child)).flat(),
|
|
26987
|
-
};
|
|
26988
27612
|
case dist.BLOCKS.HEADING_6:
|
|
27613
|
+
const headingLevels = {
|
|
27614
|
+
[dist.BLOCKS.HEADING_1]: 1,
|
|
27615
|
+
[dist.BLOCKS.HEADING_2]: 2,
|
|
27616
|
+
[dist.BLOCKS.HEADING_3]: 3,
|
|
27617
|
+
[dist.BLOCKS.HEADING_4]: 4,
|
|
27618
|
+
[dist.BLOCKS.HEADING_5]: 5,
|
|
27619
|
+
[dist.BLOCKS.HEADING_6]: 6,
|
|
27620
|
+
};
|
|
26989
27621
|
return {
|
|
26990
27622
|
type: 'heading',
|
|
26991
|
-
attrs: { level:
|
|
26992
|
-
content: node.content.map(child =>
|
|
27623
|
+
attrs: { level: headingLevels[node.nodeType] },
|
|
27624
|
+
content: node.content.map(child => {
|
|
27625
|
+
const converted = convertContentfulNodeInternal(child);
|
|
27626
|
+
return Array.isArray(converted) ? converted[0] : converted;
|
|
27627
|
+
}),
|
|
26993
27628
|
};
|
|
26994
27629
|
case dist.BLOCKS.UL_LIST:
|
|
26995
27630
|
return {
|
|
26996
27631
|
type: 'bulletList',
|
|
26997
|
-
content: node.content.map(child =>
|
|
27632
|
+
content: node.content.map(child => {
|
|
27633
|
+
const converted = convertContentfulNodeInternal(child);
|
|
27634
|
+
return Array.isArray(converted) ? converted[0] : converted;
|
|
27635
|
+
}),
|
|
26998
27636
|
};
|
|
26999
27637
|
case dist.BLOCKS.OL_LIST:
|
|
27000
27638
|
return {
|
|
27001
27639
|
type: 'orderedList',
|
|
27002
|
-
content: node.content.map(child =>
|
|
27640
|
+
content: node.content.map(child => {
|
|
27641
|
+
const converted = convertContentfulNodeInternal(child);
|
|
27642
|
+
return Array.isArray(converted) ? converted[0] : converted;
|
|
27643
|
+
}),
|
|
27003
27644
|
};
|
|
27004
27645
|
case dist.BLOCKS.LIST_ITEM:
|
|
27005
27646
|
return {
|
|
27006
27647
|
type: 'listItem',
|
|
27007
|
-
content: node.content.map(child =>
|
|
27648
|
+
content: node.content.map(child => {
|
|
27649
|
+
const converted = convertContentfulNodeInternal(child);
|
|
27650
|
+
return Array.isArray(converted) ? converted[0] : converted;
|
|
27651
|
+
}),
|
|
27008
27652
|
};
|
|
27009
27653
|
case dist.BLOCKS.QUOTE:
|
|
27010
27654
|
return {
|
|
27011
27655
|
type: 'blockquote',
|
|
27012
|
-
content: node.content.map(child =>
|
|
27656
|
+
content: node.content.map(child => {
|
|
27657
|
+
const converted = convertContentfulNodeInternal(child);
|
|
27658
|
+
return Array.isArray(converted) ? converted[0] : converted;
|
|
27659
|
+
}),
|
|
27013
27660
|
};
|
|
27014
27661
|
case dist.BLOCKS.HR:
|
|
27015
27662
|
return {
|
|
@@ -27018,31 +27665,44 @@ const contentfulToTiptap = (document) => {
|
|
|
27018
27665
|
case dist.BLOCKS.TABLE:
|
|
27019
27666
|
return {
|
|
27020
27667
|
type: 'table',
|
|
27021
|
-
content: node.content.map(child =>
|
|
27668
|
+
content: node.content.map(child => {
|
|
27669
|
+
const converted = convertContentfulNodeInternal(child);
|
|
27670
|
+
return Array.isArray(converted) ? converted[0] : converted;
|
|
27671
|
+
}),
|
|
27022
27672
|
};
|
|
27023
27673
|
case dist.BLOCKS.TABLE_ROW:
|
|
27024
27674
|
return {
|
|
27025
27675
|
type: 'tableRow',
|
|
27026
|
-
content: node.content.map(child =>
|
|
27676
|
+
content: node.content.map(child => {
|
|
27677
|
+
const converted = convertContentfulNodeInternal(child);
|
|
27678
|
+
return Array.isArray(converted) ? converted[0] : converted;
|
|
27679
|
+
}),
|
|
27027
27680
|
};
|
|
27028
27681
|
case dist.BLOCKS.TABLE_CELL:
|
|
27029
27682
|
return {
|
|
27030
27683
|
type: 'tableCell',
|
|
27031
|
-
content: node.content.map(child =>
|
|
27684
|
+
content: node.content.map(child => {
|
|
27685
|
+
const converted = convertContentfulNodeInternal(child);
|
|
27686
|
+
return Array.isArray(converted) ? converted[0] : converted;
|
|
27687
|
+
}),
|
|
27032
27688
|
};
|
|
27033
27689
|
case dist.BLOCKS.TABLE_HEADER_CELL:
|
|
27034
27690
|
return {
|
|
27035
27691
|
type: 'tableHeader',
|
|
27036
|
-
content: node.content.map(child =>
|
|
27692
|
+
content: node.content.map(child => {
|
|
27693
|
+
const converted = convertContentfulNodeInternal(child);
|
|
27694
|
+
return Array.isArray(converted) ? converted[0] : converted;
|
|
27695
|
+
}),
|
|
27037
27696
|
};
|
|
27038
27697
|
case dist.INLINES.HYPERLINK:
|
|
27698
|
+
const linkText = node.content.map(child => child.nodeType === 'text' ? child.value : '').join('');
|
|
27039
27699
|
return {
|
|
27040
27700
|
type: 'text',
|
|
27041
|
-
text:
|
|
27701
|
+
text: linkText,
|
|
27042
27702
|
marks: [
|
|
27043
27703
|
{
|
|
27044
27704
|
type: 'link',
|
|
27045
|
-
attrs: { href: node.data.uri },
|
|
27705
|
+
attrs: { href: ((_a = node.data) === null || _a === void 0 ? void 0 : _a.uri) || '' },
|
|
27046
27706
|
},
|
|
27047
27707
|
],
|
|
27048
27708
|
};
|
|
@@ -27052,7 +27712,7 @@ const contentfulToTiptap = (document) => {
|
|
|
27052
27712
|
content: [
|
|
27053
27713
|
{
|
|
27054
27714
|
type: 'text',
|
|
27055
|
-
text: `[Embedded Entry: ${((
|
|
27715
|
+
text: `[Embedded Entry: ${((_d = (_c = (_b = node.data) === null || _b === void 0 ? void 0 : _b.target) === null || _c === void 0 ? void 0 : _c.sys) === null || _d === void 0 ? void 0 : _d.id) || 'Unknown'}]`,
|
|
27056
27716
|
marks: [{ type: 'bold' }],
|
|
27057
27717
|
},
|
|
27058
27718
|
],
|
|
@@ -27063,14 +27723,14 @@ const contentfulToTiptap = (document) => {
|
|
|
27063
27723
|
content: [
|
|
27064
27724
|
{
|
|
27065
27725
|
type: 'text',
|
|
27066
|
-
text: `[Embedded Asset: ${((
|
|
27726
|
+
text: `[Embedded Asset: ${((_g = (_f = (_e = node.data) === null || _e === void 0 ? void 0 : _e.target) === null || _f === void 0 ? void 0 : _f.sys) === null || _g === void 0 ? void 0 : _g.id) || 'Unknown'}]`,
|
|
27067
27727
|
marks: [{ type: 'bold' }],
|
|
27068
27728
|
},
|
|
27069
27729
|
],
|
|
27070
27730
|
};
|
|
27071
27731
|
case 'text':
|
|
27072
27732
|
const textNode = node;
|
|
27073
|
-
const marks = ((
|
|
27733
|
+
const marks = ((_h = textNode.marks) === null || _h === void 0 ? void 0 : _h.map(mark => {
|
|
27074
27734
|
switch (mark.type) {
|
|
27075
27735
|
case dist.MARKS.BOLD:
|
|
27076
27736
|
return { type: 'bold' };
|
|
@@ -27081,182 +27741,214 @@ const contentfulToTiptap = (document) => {
|
|
|
27081
27741
|
case dist.MARKS.CODE:
|
|
27082
27742
|
return { type: 'code' };
|
|
27083
27743
|
default:
|
|
27744
|
+
console.warn(`[ContentfulTransform] Unknown mark type: ${mark.type}`);
|
|
27084
27745
|
return null;
|
|
27085
27746
|
}
|
|
27086
27747
|
}).filter((mark) => mark !== null)) || [];
|
|
27087
27748
|
return {
|
|
27088
27749
|
type: 'text',
|
|
27089
|
-
text: textNode.value,
|
|
27750
|
+
text: textNode.value || '',
|
|
27090
27751
|
marks: marks.length > 0 ? marks : undefined,
|
|
27091
27752
|
};
|
|
27092
27753
|
default:
|
|
27093
|
-
console.warn(`Unknown Contentful node type: ${node.nodeType}`);
|
|
27754
|
+
console.warn(`[ContentfulTransform] Unknown Contentful node type: ${node.nodeType}`);
|
|
27094
27755
|
return {
|
|
27095
27756
|
type: 'paragraph',
|
|
27096
27757
|
content: [],
|
|
27097
27758
|
};
|
|
27098
27759
|
}
|
|
27099
|
-
}
|
|
27100
|
-
|
|
27760
|
+
}
|
|
27761
|
+
catch (error) {
|
|
27762
|
+
throw createTransformError(`Failed to convert Contentful node: ${error instanceof Error ? error.message : 'Unknown error'}`, node.nodeType, node);
|
|
27763
|
+
}
|
|
27764
|
+
};
|
|
27765
|
+
/**
|
|
27766
|
+
* Production-grade Contentful to Tiptap conversion with comprehensive error handling
|
|
27767
|
+
*/
|
|
27768
|
+
const contentfulToTiptap = (document) => {
|
|
27769
|
+
if (!validateContentfulDocument(document)) {
|
|
27770
|
+
throw createTransformError('Invalid Contentful document format');
|
|
27771
|
+
}
|
|
27772
|
+
try {
|
|
27773
|
+
const result = convertContentfulNodeInternal(document);
|
|
27774
|
+
return Array.isArray(result) ? result[0] : result;
|
|
27775
|
+
}
|
|
27776
|
+
catch (error) {
|
|
27777
|
+
if (error instanceof Error && 'nodeType' in error) {
|
|
27778
|
+
throw error; // Re-throw our custom errors
|
|
27779
|
+
}
|
|
27780
|
+
throw createTransformError(`Contentful transformation failed: ${error instanceof Error ? error.message : 'Unknown error'}`, document.nodeType);
|
|
27781
|
+
}
|
|
27101
27782
|
};
|
|
27102
27783
|
/**
|
|
27103
|
-
*
|
|
27784
|
+
* Safe wrapper for Contentful to Tiptap conversion with fallback
|
|
27785
|
+
*/
|
|
27786
|
+
const safeContentfulToTiptap = (document) => {
|
|
27787
|
+
try {
|
|
27788
|
+
return contentfulToTiptap(document);
|
|
27789
|
+
}
|
|
27790
|
+
catch (error) {
|
|
27791
|
+
console.error('[ContentfulTransform] Safe conversion fallback triggered:', error);
|
|
27792
|
+
return createEmptyTiptapDocument();
|
|
27793
|
+
}
|
|
27794
|
+
};
|
|
27795
|
+
/**
|
|
27796
|
+
* Strategic Tiptap to Contentful conversion with enhanced error handling
|
|
27104
27797
|
*/
|
|
27105
27798
|
const tiptapToContentful = (tiptapDoc) => {
|
|
27106
|
-
|
|
27107
|
-
|
|
27108
|
-
|
|
27109
|
-
|
|
27110
|
-
|
|
27111
|
-
|
|
27112
|
-
|
|
27113
|
-
|
|
27114
|
-
};
|
|
27115
|
-
case 'paragraph':
|
|
27116
|
-
return {
|
|
27117
|
-
nodeType: dist.BLOCKS.PARAGRAPH,
|
|
27118
|
-
data: {},
|
|
27119
|
-
content: (_b = node.content) === null || _b === void 0 ? void 0 : _b.map((child) => convertNode(child)),
|
|
27120
|
-
};
|
|
27121
|
-
case 'heading':
|
|
27122
|
-
const level = ((_c = node.attrs) === null || _c === void 0 ? void 0 : _c.level) || 1;
|
|
27123
|
-
const headingTypes = {
|
|
27124
|
-
1: dist.BLOCKS.HEADING_1,
|
|
27125
|
-
2: dist.BLOCKS.HEADING_2,
|
|
27126
|
-
3: dist.BLOCKS.HEADING_3,
|
|
27127
|
-
4: dist.BLOCKS.HEADING_4,
|
|
27128
|
-
5: dist.BLOCKS.HEADING_5,
|
|
27129
|
-
6: dist.BLOCKS.HEADING_6,
|
|
27130
|
-
};
|
|
27131
|
-
const headingType = headingTypes[level] || dist.BLOCKS.HEADING_1;
|
|
27132
|
-
return {
|
|
27133
|
-
nodeType: headingType,
|
|
27134
|
-
data: {},
|
|
27135
|
-
content: (_d = node.content) === null || _d === void 0 ? void 0 : _d.map((child) => convertNode(child)),
|
|
27136
|
-
};
|
|
27137
|
-
case 'bulletList':
|
|
27138
|
-
return {
|
|
27139
|
-
nodeType: dist.BLOCKS.UL_LIST,
|
|
27140
|
-
data: {},
|
|
27141
|
-
content: (_e = node.content) === null || _e === void 0 ? void 0 : _e.map((child) => convertNode(child)),
|
|
27142
|
-
};
|
|
27143
|
-
case 'orderedList':
|
|
27144
|
-
return {
|
|
27145
|
-
nodeType: dist.BLOCKS.OL_LIST,
|
|
27146
|
-
data: {},
|
|
27147
|
-
content: (_f = node.content) === null || _f === void 0 ? void 0 : _f.map((child) => convertNode(child)),
|
|
27148
|
-
};
|
|
27149
|
-
case 'listItem':
|
|
27150
|
-
return {
|
|
27151
|
-
nodeType: dist.BLOCKS.LIST_ITEM,
|
|
27152
|
-
data: {},
|
|
27153
|
-
content: (_g = node.content) === null || _g === void 0 ? void 0 : _g.map((child) => convertNode(child)),
|
|
27154
|
-
};
|
|
27155
|
-
case 'blockquote':
|
|
27156
|
-
return {
|
|
27157
|
-
nodeType: dist.BLOCKS.QUOTE,
|
|
27158
|
-
data: {},
|
|
27159
|
-
content: (_h = node.content) === null || _h === void 0 ? void 0 : _h.map((child) => convertNode(child)),
|
|
27160
|
-
};
|
|
27161
|
-
case 'horizontalRule':
|
|
27162
|
-
return {
|
|
27163
|
-
nodeType: dist.BLOCKS.HR,
|
|
27164
|
-
data: {},
|
|
27165
|
-
content: [],
|
|
27166
|
-
};
|
|
27167
|
-
case 'table':
|
|
27168
|
-
return {
|
|
27169
|
-
nodeType: dist.BLOCKS.TABLE,
|
|
27170
|
-
data: {},
|
|
27171
|
-
content: (_j = node.content) === null || _j === void 0 ? void 0 : _j.map((child) => convertNode(child)),
|
|
27172
|
-
};
|
|
27173
|
-
case 'tableRow':
|
|
27174
|
-
return {
|
|
27175
|
-
nodeType: dist.BLOCKS.TABLE_ROW,
|
|
27176
|
-
data: {},
|
|
27177
|
-
content: (_k = node.content) === null || _k === void 0 ? void 0 : _k.map((child) => convertNode(child)),
|
|
27178
|
-
};
|
|
27179
|
-
case 'tableCell':
|
|
27180
|
-
return {
|
|
27181
|
-
nodeType: dist.BLOCKS.TABLE_CELL,
|
|
27182
|
-
data: {},
|
|
27183
|
-
content: (_l = node.content) === null || _l === void 0 ? void 0 : _l.map((child) => convertNode(child)),
|
|
27184
|
-
};
|
|
27185
|
-
case 'tableHeader':
|
|
27186
|
-
return {
|
|
27187
|
-
nodeType: dist.BLOCKS.TABLE_HEADER_CELL,
|
|
27188
|
-
data: {},
|
|
27189
|
-
content: (_m = node.content) === null || _m === void 0 ? void 0 : _m.map((child) => convertNode(child)),
|
|
27190
|
-
};
|
|
27191
|
-
case 'text':
|
|
27192
|
-
const marks = ((_o = node.marks) === null || _o === void 0 ? void 0 : _o.map((mark) => {
|
|
27193
|
-
switch (mark.type) {
|
|
27194
|
-
case 'bold':
|
|
27195
|
-
return { type: dist.MARKS.BOLD };
|
|
27196
|
-
case 'italic':
|
|
27197
|
-
return { type: dist.MARKS.ITALIC };
|
|
27198
|
-
case 'underline':
|
|
27199
|
-
return { type: dist.MARKS.UNDERLINE };
|
|
27200
|
-
case 'code':
|
|
27201
|
-
return { type: dist.MARKS.CODE };
|
|
27202
|
-
case 'link':
|
|
27203
|
-
return null; // Links are handled separately
|
|
27204
|
-
default:
|
|
27205
|
-
return null;
|
|
27206
|
-
}
|
|
27207
|
-
}).filter(Boolean)) || [];
|
|
27208
|
-
// Check if this text has a link mark
|
|
27209
|
-
const linkMark = (_p = node.marks) === null || _p === void 0 ? void 0 : _p.find((mark) => mark.type === 'link');
|
|
27210
|
-
if (linkMark) {
|
|
27799
|
+
if (!tiptapDoc || typeof tiptapDoc !== 'object') {
|
|
27800
|
+
throw createTransformError('Invalid Tiptap document: not an object');
|
|
27801
|
+
}
|
|
27802
|
+
const convertTiptapNode = (node) => {
|
|
27803
|
+
var _a, _b;
|
|
27804
|
+
try {
|
|
27805
|
+
switch (node.type) {
|
|
27806
|
+
case 'doc':
|
|
27211
27807
|
return {
|
|
27212
|
-
nodeType: dist.
|
|
27213
|
-
data: {
|
|
27214
|
-
|
|
27215
|
-
|
|
27216
|
-
|
|
27217
|
-
|
|
27218
|
-
|
|
27219
|
-
|
|
27220
|
-
|
|
27221
|
-
|
|
27808
|
+
nodeType: dist.BLOCKS.DOCUMENT,
|
|
27809
|
+
data: {},
|
|
27810
|
+
content: (node.content || []).map((child) => convertTiptapNode(child)),
|
|
27811
|
+
};
|
|
27812
|
+
case 'paragraph':
|
|
27813
|
+
return {
|
|
27814
|
+
nodeType: dist.BLOCKS.PARAGRAPH,
|
|
27815
|
+
data: {},
|
|
27816
|
+
content: (node.content || []).map((child) => convertTiptapNode(child)),
|
|
27817
|
+
};
|
|
27818
|
+
case 'heading':
|
|
27819
|
+
const level = ((_a = node.attrs) === null || _a === void 0 ? void 0 : _a.level) || 1;
|
|
27820
|
+
const headingTypes = {
|
|
27821
|
+
1: dist.BLOCKS.HEADING_1,
|
|
27822
|
+
2: dist.BLOCKS.HEADING_2,
|
|
27823
|
+
3: dist.BLOCKS.HEADING_3,
|
|
27824
|
+
4: dist.BLOCKS.HEADING_4,
|
|
27825
|
+
5: dist.BLOCKS.HEADING_5,
|
|
27826
|
+
6: dist.BLOCKS.HEADING_6,
|
|
27827
|
+
};
|
|
27828
|
+
const headingType = headingTypes[level] || dist.BLOCKS.HEADING_1;
|
|
27829
|
+
return {
|
|
27830
|
+
nodeType: headingType,
|
|
27831
|
+
data: {},
|
|
27832
|
+
content: (node.content || []).map((child) => convertTiptapNode(child)),
|
|
27833
|
+
};
|
|
27834
|
+
case 'bulletList':
|
|
27835
|
+
return {
|
|
27836
|
+
nodeType: dist.BLOCKS.UL_LIST,
|
|
27837
|
+
data: {},
|
|
27838
|
+
content: (node.content || []).map((child) => convertTiptapNode(child)),
|
|
27839
|
+
};
|
|
27840
|
+
case 'orderedList':
|
|
27841
|
+
return {
|
|
27842
|
+
nodeType: dist.BLOCKS.OL_LIST,
|
|
27843
|
+
data: {},
|
|
27844
|
+
content: (node.content || []).map((child) => convertTiptapNode(child)),
|
|
27845
|
+
};
|
|
27846
|
+
case 'listItem':
|
|
27847
|
+
return {
|
|
27848
|
+
nodeType: dist.BLOCKS.LIST_ITEM,
|
|
27849
|
+
data: {},
|
|
27850
|
+
content: (node.content || []).map((child) => convertTiptapNode(child)),
|
|
27851
|
+
};
|
|
27852
|
+
case 'blockquote':
|
|
27853
|
+
return {
|
|
27854
|
+
nodeType: dist.BLOCKS.QUOTE,
|
|
27855
|
+
data: {},
|
|
27856
|
+
content: (node.content || []).map((child) => convertTiptapNode(child)),
|
|
27857
|
+
};
|
|
27858
|
+
case 'horizontalRule':
|
|
27859
|
+
return {
|
|
27860
|
+
nodeType: dist.BLOCKS.HR,
|
|
27861
|
+
data: {},
|
|
27862
|
+
content: [],
|
|
27863
|
+
};
|
|
27864
|
+
case 'table':
|
|
27865
|
+
return {
|
|
27866
|
+
nodeType: dist.BLOCKS.TABLE,
|
|
27867
|
+
data: {},
|
|
27868
|
+
content: (node.content || []).map((child) => convertTiptapNode(child)),
|
|
27869
|
+
};
|
|
27870
|
+
case 'tableRow':
|
|
27871
|
+
return {
|
|
27872
|
+
nodeType: dist.BLOCKS.TABLE_ROW,
|
|
27873
|
+
data: {},
|
|
27874
|
+
content: (node.content || []).map((child) => convertTiptapNode(child)),
|
|
27875
|
+
};
|
|
27876
|
+
case 'tableCell':
|
|
27877
|
+
return {
|
|
27878
|
+
nodeType: dist.BLOCKS.TABLE_CELL,
|
|
27879
|
+
data: {},
|
|
27880
|
+
content: (node.content || []).map((child) => convertTiptapNode(child)),
|
|
27881
|
+
};
|
|
27882
|
+
case 'tableHeader':
|
|
27883
|
+
return {
|
|
27884
|
+
nodeType: dist.BLOCKS.TABLE_HEADER_CELL,
|
|
27885
|
+
data: {},
|
|
27886
|
+
content: (node.content || []).map((child) => convertTiptapNode(child)),
|
|
27887
|
+
};
|
|
27888
|
+
case 'text':
|
|
27889
|
+
const marks = (node.marks || []).map((mark) => {
|
|
27890
|
+
switch (mark.type) {
|
|
27891
|
+
case 'bold':
|
|
27892
|
+
return { type: dist.MARKS.BOLD };
|
|
27893
|
+
case 'italic':
|
|
27894
|
+
return { type: dist.MARKS.ITALIC };
|
|
27895
|
+
case 'underline':
|
|
27896
|
+
return { type: dist.MARKS.UNDERLINE };
|
|
27897
|
+
case 'code':
|
|
27898
|
+
return { type: dist.MARKS.CODE };
|
|
27899
|
+
case 'link':
|
|
27900
|
+
return null; // Links are handled separately
|
|
27901
|
+
default:
|
|
27902
|
+
console.warn(`[ContentfulTransform] Unknown Tiptap mark type: ${mark.type}`);
|
|
27903
|
+
return null;
|
|
27904
|
+
}
|
|
27905
|
+
}).filter(Boolean) || [];
|
|
27906
|
+
// Strategic link handling
|
|
27907
|
+
const linkMark = (node.marks || []).find((mark) => mark.type === 'link');
|
|
27908
|
+
if (linkMark) {
|
|
27909
|
+
return {
|
|
27910
|
+
nodeType: dist.INLINES.HYPERLINK,
|
|
27911
|
+
data: {
|
|
27912
|
+
uri: ((_b = linkMark.attrs) === null || _b === void 0 ? void 0 : _b.href) || '',
|
|
27222
27913
|
},
|
|
27223
|
-
|
|
27914
|
+
content: [
|
|
27915
|
+
{
|
|
27916
|
+
nodeType: 'text',
|
|
27917
|
+
value: node.text || '',
|
|
27918
|
+
marks: marks,
|
|
27919
|
+
data: {},
|
|
27920
|
+
},
|
|
27921
|
+
],
|
|
27922
|
+
};
|
|
27923
|
+
}
|
|
27924
|
+
return {
|
|
27925
|
+
nodeType: 'text',
|
|
27926
|
+
value: node.text || '',
|
|
27927
|
+
marks: marks,
|
|
27928
|
+
data: {},
|
|
27224
27929
|
};
|
|
27225
|
-
|
|
27226
|
-
|
|
27227
|
-
|
|
27228
|
-
|
|
27229
|
-
|
|
27230
|
-
|
|
27231
|
-
|
|
27232
|
-
|
|
27233
|
-
|
|
27234
|
-
|
|
27235
|
-
|
|
27236
|
-
data: {},
|
|
27237
|
-
content: [],
|
|
27238
|
-
};
|
|
27930
|
+
default:
|
|
27931
|
+
console.warn(`[ContentfulTransform] Unknown Tiptap node type: ${node.type}`);
|
|
27932
|
+
return {
|
|
27933
|
+
nodeType: dist.BLOCKS.PARAGRAPH,
|
|
27934
|
+
data: {},
|
|
27935
|
+
content: [],
|
|
27936
|
+
};
|
|
27937
|
+
}
|
|
27938
|
+
}
|
|
27939
|
+
catch (error) {
|
|
27940
|
+
throw createTransformError(`Failed to convert Tiptap node: ${error instanceof Error ? error.message : 'Unknown error'}`, node.type, node);
|
|
27239
27941
|
}
|
|
27240
27942
|
};
|
|
27241
|
-
|
|
27242
|
-
|
|
27243
|
-
/**
|
|
27244
|
-
* Validates if a Contentful document is properly formatted
|
|
27245
|
-
*/
|
|
27246
|
-
const validateContentfulDocument = (document) => {
|
|
27247
|
-
if (!document || typeof document !== 'object') {
|
|
27248
|
-
return false;
|
|
27249
|
-
}
|
|
27250
|
-
if (document.nodeType !== dist.BLOCKS.DOCUMENT) {
|
|
27251
|
-
return false;
|
|
27943
|
+
try {
|
|
27944
|
+
return convertTiptapNode(tiptapDoc);
|
|
27252
27945
|
}
|
|
27253
|
-
|
|
27254
|
-
|
|
27946
|
+
catch (error) {
|
|
27947
|
+
throw createTransformError(`Tiptap transformation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
27255
27948
|
}
|
|
27256
|
-
return true;
|
|
27257
27949
|
};
|
|
27258
27950
|
/**
|
|
27259
|
-
* Creates an empty Contentful document
|
|
27951
|
+
* Creates an empty Contentful document with professional structure
|
|
27260
27952
|
*/
|
|
27261
27953
|
const createEmptyDocument = () => ({
|
|
27262
27954
|
nodeType: dist.BLOCKS.DOCUMENT,
|
|
@@ -27270,10 +27962,19 @@ const createEmptyDocument = () => ({
|
|
|
27270
27962
|
],
|
|
27271
27963
|
});
|
|
27272
27964
|
|
|
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'] }) => {
|
|
27965
|
+
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'], debug = false }) => {
|
|
27966
|
+
const [isContentLoaded, setIsContentLoaded] = useState(false);
|
|
27967
|
+
const [errorState, setErrorState] = useState(null);
|
|
27968
|
+
const contentInitialized = useRef(false);
|
|
27969
|
+
// Strategic logging for professional debugging
|
|
27970
|
+
const debugLog = useCallback((message, data) => {
|
|
27971
|
+
if (debug) {
|
|
27972
|
+
console.log(`[ContentfulEditor] ${message}`, data);
|
|
27973
|
+
}
|
|
27974
|
+
}, [debug]);
|
|
27274
27975
|
// Build extensions array based on available features
|
|
27275
27976
|
const extensions = [];
|
|
27276
|
-
// Add StarterKit with configuration
|
|
27977
|
+
// Add StarterKit with strategic configuration
|
|
27277
27978
|
extensions.push(StarterKit.configure({
|
|
27278
27979
|
heading: {
|
|
27279
27980
|
levels: availableHeadings,
|
|
@@ -27300,7 +28001,7 @@ const ContentfulRichTextEditor = ({ initialValue, onChange, onEmbedEntry, onEmbe
|
|
|
27300
28001
|
if (availableMarks.includes('underline')) {
|
|
27301
28002
|
extensions.push(Underline);
|
|
27302
28003
|
}
|
|
27303
|
-
// Add other extensions
|
|
28004
|
+
// Add other extensions with optimized configuration
|
|
27304
28005
|
extensions.push(Link.configure({
|
|
27305
28006
|
openOnClick: false,
|
|
27306
28007
|
HTMLAttributes: {
|
|
@@ -27327,7 +28028,7 @@ const ContentfulRichTextEditor = ({ initialValue, onChange, onEmbedEntry, onEmbe
|
|
|
27327
28028
|
}));
|
|
27328
28029
|
const editor = useEditor({
|
|
27329
28030
|
extensions,
|
|
27330
|
-
content:
|
|
28031
|
+
content: '', // Initialize empty to prevent race conditions
|
|
27331
28032
|
editable: !readonly,
|
|
27332
28033
|
editorProps: {
|
|
27333
28034
|
attributes: {
|
|
@@ -27336,31 +28037,104 @@ const ContentfulRichTextEditor = ({ initialValue, onChange, onEmbedEntry, onEmbe
|
|
|
27336
28037
|
},
|
|
27337
28038
|
},
|
|
27338
28039
|
onUpdate: ({ editor }) => {
|
|
27339
|
-
if (onChange) {
|
|
28040
|
+
if (onChange && isContentLoaded) {
|
|
27340
28041
|
try {
|
|
27341
28042
|
const contentfulDoc = tiptapToContentful(editor.getJSON());
|
|
28043
|
+
debugLog('Content transformed successfully', contentfulDoc);
|
|
27342
28044
|
onChange(contentfulDoc);
|
|
27343
28045
|
}
|
|
27344
28046
|
catch (error) {
|
|
27345
|
-
|
|
28047
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown transformation error';
|
|
28048
|
+
debugLog('Content transformation failed', error);
|
|
28049
|
+
setErrorState(`Transformation failed: ${errorMessage}`);
|
|
27346
28050
|
}
|
|
27347
28051
|
}
|
|
27348
28052
|
},
|
|
27349
28053
|
});
|
|
27350
|
-
//
|
|
28054
|
+
// Strategic content initialization with comprehensive error handling
|
|
27351
28055
|
useEffect(() => {
|
|
27352
|
-
if (editor
|
|
27353
|
-
|
|
27354
|
-
|
|
28056
|
+
if (!editor) {
|
|
28057
|
+
debugLog('Editor not yet initialized');
|
|
28058
|
+
return;
|
|
28059
|
+
}
|
|
28060
|
+
if (contentInitialized.current) {
|
|
28061
|
+
debugLog('Content already initialized, skipping');
|
|
28062
|
+
return;
|
|
28063
|
+
}
|
|
28064
|
+
debugLog('Initializing editor content', {
|
|
28065
|
+
hasInitialValue: !!initialValue,
|
|
28066
|
+
editorReady: !!editor
|
|
28067
|
+
});
|
|
28068
|
+
try {
|
|
28069
|
+
if (initialValue && validateContentfulDocument(initialValue)) {
|
|
28070
|
+
debugLog('Valid initial document detected', initialValue);
|
|
28071
|
+
const tiptapContent = safeContentfulToTiptap(initialValue);
|
|
28072
|
+
debugLog('Content transformation successful', tiptapContent);
|
|
28073
|
+
// Strategic content setting with validation
|
|
28074
|
+
editor.commands.setContent(tiptapContent, false);
|
|
28075
|
+
setIsContentLoaded(true);
|
|
28076
|
+
setErrorState(null);
|
|
28077
|
+
debugLog('Content loaded successfully');
|
|
28078
|
+
}
|
|
28079
|
+
else if (initialValue) {
|
|
28080
|
+
debugLog('Invalid initial document detected, using fallback');
|
|
28081
|
+
setErrorState('Invalid document format detected');
|
|
28082
|
+
editor.commands.setContent(createEmptyTiptapDocument(), false);
|
|
28083
|
+
setIsContentLoaded(true);
|
|
28084
|
+
}
|
|
28085
|
+
else {
|
|
28086
|
+
debugLog('No initial value provided, using empty document');
|
|
28087
|
+
editor.commands.setContent(createEmptyTiptapDocument(), false);
|
|
28088
|
+
setIsContentLoaded(true);
|
|
28089
|
+
}
|
|
28090
|
+
contentInitialized.current = true;
|
|
28091
|
+
}
|
|
28092
|
+
catch (error) {
|
|
28093
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown initialization error';
|
|
28094
|
+
debugLog('Content initialization failed', error);
|
|
28095
|
+
setErrorState(`Initialization failed: ${errorMessage}`);
|
|
28096
|
+
// Fallback to empty content
|
|
28097
|
+
editor.commands.setContent(createEmptyTiptapDocument(), false);
|
|
28098
|
+
setIsContentLoaded(true);
|
|
28099
|
+
contentInitialized.current = true;
|
|
28100
|
+
}
|
|
28101
|
+
}, [editor, initialValue, debugLog]);
|
|
28102
|
+
// Handle external value changes with strategic validation
|
|
28103
|
+
useEffect(() => {
|
|
28104
|
+
if (!editor || !isContentLoaded)
|
|
28105
|
+
return;
|
|
28106
|
+
if (initialValue && contentInitialized.current) {
|
|
28107
|
+
try {
|
|
28108
|
+
debugLog('External value change detected', initialValue);
|
|
28109
|
+
if (validateContentfulDocument(initialValue)) {
|
|
28110
|
+
const tiptapContent = safeContentfulToTiptap(initialValue);
|
|
28111
|
+
const currentContent = editor.getJSON();
|
|
28112
|
+
// Only update if content has actually changed
|
|
28113
|
+
if (JSON.stringify(currentContent) !== JSON.stringify(tiptapContent)) {
|
|
28114
|
+
debugLog('Content differs, updating editor');
|
|
28115
|
+
editor.commands.setContent(tiptapContent, false);
|
|
28116
|
+
setErrorState(null);
|
|
28117
|
+
}
|
|
28118
|
+
}
|
|
28119
|
+
else {
|
|
28120
|
+
setErrorState('Invalid document format in update');
|
|
28121
|
+
}
|
|
28122
|
+
}
|
|
28123
|
+
catch (error) {
|
|
28124
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown update error';
|
|
28125
|
+
debugLog('Content update failed', error);
|
|
28126
|
+
setErrorState(`Update failed: ${errorMessage}`);
|
|
28127
|
+
}
|
|
27355
28128
|
}
|
|
27356
|
-
}, [editor,
|
|
28129
|
+
}, [initialValue, editor, isContentLoaded, debugLog]);
|
|
27357
28130
|
const handleEmbedEntry = useCallback(async () => {
|
|
27358
|
-
var _a;
|
|
28131
|
+
var _a, _b;
|
|
27359
28132
|
if (onEmbedEntry && editor) {
|
|
27360
28133
|
try {
|
|
28134
|
+
debugLog('Embedding entry initiated');
|
|
27361
28135
|
const entry = await onEmbedEntry();
|
|
27362
28136
|
if (entry) {
|
|
27363
|
-
//
|
|
28137
|
+
// Strategic entry embedding with validation
|
|
27364
28138
|
editor.chain().focus().insertContent({
|
|
27365
28139
|
type: 'paragraph',
|
|
27366
28140
|
content: [
|
|
@@ -27371,20 +28145,23 @@ const ContentfulRichTextEditor = ({ initialValue, onChange, onEmbedEntry, onEmbe
|
|
|
27371
28145
|
},
|
|
27372
28146
|
],
|
|
27373
28147
|
}).run();
|
|
28148
|
+
debugLog('Entry embedded successfully', (_b = entry.sys) === null || _b === void 0 ? void 0 : _b.id);
|
|
27374
28149
|
}
|
|
27375
28150
|
}
|
|
27376
28151
|
catch (error) {
|
|
27377
|
-
|
|
28152
|
+
debugLog('Entry embedding failed', error);
|
|
28153
|
+
setErrorState('Failed to embed entry');
|
|
27378
28154
|
}
|
|
27379
28155
|
}
|
|
27380
|
-
}, [onEmbedEntry, editor]);
|
|
28156
|
+
}, [onEmbedEntry, editor, debugLog]);
|
|
27381
28157
|
const handleEmbedAsset = useCallback(async () => {
|
|
27382
|
-
var _a;
|
|
28158
|
+
var _a, _b;
|
|
27383
28159
|
if (onEmbedAsset && editor) {
|
|
27384
28160
|
try {
|
|
28161
|
+
debugLog('Embedding asset initiated');
|
|
27385
28162
|
const asset = await onEmbedAsset();
|
|
27386
28163
|
if (asset) {
|
|
27387
|
-
//
|
|
28164
|
+
// Strategic asset embedding with validation
|
|
27388
28165
|
editor.chain().focus().insertContent({
|
|
27389
28166
|
type: 'paragraph',
|
|
27390
28167
|
content: [
|
|
@@ -27395,18 +28172,226 @@ const ContentfulRichTextEditor = ({ initialValue, onChange, onEmbedEntry, onEmbe
|
|
|
27395
28172
|
},
|
|
27396
28173
|
],
|
|
27397
28174
|
}).run();
|
|
28175
|
+
debugLog('Asset embedded successfully', (_b = asset.sys) === null || _b === void 0 ? void 0 : _b.id);
|
|
27398
28176
|
}
|
|
27399
28177
|
}
|
|
27400
28178
|
catch (error) {
|
|
27401
|
-
|
|
28179
|
+
debugLog('Asset embedding failed', error);
|
|
28180
|
+
setErrorState('Failed to embed asset');
|
|
27402
28181
|
}
|
|
27403
28182
|
}
|
|
27404
|
-
}, [onEmbedAsset, editor]);
|
|
27405
|
-
|
|
27406
|
-
|
|
28183
|
+
}, [onEmbedAsset, editor, debugLog]);
|
|
28184
|
+
// Strategic loading state management
|
|
28185
|
+
if (!editor || !isContentLoaded) {
|
|
28186
|
+
return (jsx("div", { className: `contentful-editor contentful-editor--loading ${className}`, children: jsx("div", { className: "contentful-editor__loading", children: errorState ? `Error: ${errorState}` : 'Initializing editor...' }) }));
|
|
27407
28187
|
}
|
|
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" }) })] }));
|
|
28188
|
+
return (jsxs("div", { className: `contentful-editor contentful-editor--${theme} ${className}`, children: [errorState && (jsxs("div", { className: "contentful-editor__error", children: [jsx("strong", { children: "Editor Warning:" }), " ", errorState] })), !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" }) })] }));
|
|
28189
|
+
};
|
|
28190
|
+
|
|
28191
|
+
const ContentfulDocument = Extension.create({
|
|
28192
|
+
name: 'contentfulDocument',
|
|
28193
|
+
addGlobalAttributes() {
|
|
28194
|
+
return [
|
|
28195
|
+
{
|
|
28196
|
+
types: ['document'],
|
|
28197
|
+
attributes: {
|
|
28198
|
+
'data-contentful': {
|
|
28199
|
+
default: true,
|
|
28200
|
+
},
|
|
28201
|
+
},
|
|
28202
|
+
},
|
|
28203
|
+
];
|
|
28204
|
+
},
|
|
28205
|
+
addCommands() {
|
|
28206
|
+
return {
|
|
28207
|
+
insertContentfulEntry: (entry) => ({ commands }) => {
|
|
28208
|
+
var _a, _b;
|
|
28209
|
+
return commands.insertContent({
|
|
28210
|
+
type: 'paragraph',
|
|
28211
|
+
content: [
|
|
28212
|
+
{
|
|
28213
|
+
type: 'text',
|
|
28214
|
+
text: `[Entry: ${((_a = entry.fields) === null || _a === void 0 ? void 0 : _a.title) || ((_b = entry.sys) === null || _b === void 0 ? void 0 : _b.id)}]`,
|
|
28215
|
+
marks: [{ type: 'bold' }],
|
|
28216
|
+
},
|
|
28217
|
+
],
|
|
28218
|
+
});
|
|
28219
|
+
},
|
|
28220
|
+
insertContentfulAsset: (asset) => ({ commands }) => {
|
|
28221
|
+
var _a, _b;
|
|
28222
|
+
return commands.insertContent({
|
|
28223
|
+
type: 'paragraph',
|
|
28224
|
+
content: [
|
|
28225
|
+
{
|
|
28226
|
+
type: 'text',
|
|
28227
|
+
text: `[Asset: ${((_a = asset.fields) === null || _a === void 0 ? void 0 : _a.title) || ((_b = asset.sys) === null || _b === void 0 ? void 0 : _b.id)}]`,
|
|
28228
|
+
marks: [{ type: 'bold' }],
|
|
28229
|
+
},
|
|
28230
|
+
],
|
|
28231
|
+
});
|
|
28232
|
+
},
|
|
28233
|
+
}; // Type assertion to suppress the warning
|
|
28234
|
+
},
|
|
28235
|
+
});
|
|
28236
|
+
|
|
28237
|
+
// React component for rendering embedded entries
|
|
28238
|
+
const EmbeddedEntryComponent = ({ node }) => {
|
|
28239
|
+
const { entryId, contentType } = node.attrs;
|
|
28240
|
+
return React.createElement('div', {
|
|
28241
|
+
className: 'contentful-embedded-entry',
|
|
28242
|
+
'data-entry-id': entryId,
|
|
28243
|
+
'data-content-type': contentType,
|
|
28244
|
+
}, `[Embedded Entry: ${entryId}]`);
|
|
28245
|
+
};
|
|
28246
|
+
// React component for rendering embedded assets
|
|
28247
|
+
const EmbeddedAssetComponent = ({ node }) => {
|
|
28248
|
+
const { assetId, title, url } = node.attrs;
|
|
28249
|
+
if (url && url.includes('image')) {
|
|
28250
|
+
return React.createElement('img', {
|
|
28251
|
+
src: url,
|
|
28252
|
+
alt: title || '',
|
|
28253
|
+
className: 'contentful-embedded-asset-image',
|
|
28254
|
+
'data-asset-id': assetId,
|
|
28255
|
+
});
|
|
28256
|
+
}
|
|
28257
|
+
return React.createElement('div', {
|
|
28258
|
+
className: 'contentful-embedded-asset',
|
|
28259
|
+
'data-asset-id': assetId,
|
|
28260
|
+
}, `[Embedded Asset: ${title || assetId}]`);
|
|
28261
|
+
};
|
|
28262
|
+
const EmbeddedEntry = Node.create({
|
|
28263
|
+
name: 'embeddedEntry',
|
|
28264
|
+
group: 'block',
|
|
28265
|
+
content: '',
|
|
28266
|
+
addAttributes() {
|
|
28267
|
+
return {
|
|
28268
|
+
entryId: {
|
|
28269
|
+
default: null,
|
|
28270
|
+
},
|
|
28271
|
+
contentType: {
|
|
28272
|
+
default: null,
|
|
28273
|
+
},
|
|
28274
|
+
};
|
|
28275
|
+
},
|
|
28276
|
+
parseHTML() {
|
|
28277
|
+
return [
|
|
28278
|
+
{
|
|
28279
|
+
tag: 'div[data-entry-id]',
|
|
28280
|
+
},
|
|
28281
|
+
];
|
|
28282
|
+
},
|
|
28283
|
+
renderHTML({ HTMLAttributes }) {
|
|
28284
|
+
return ['div', mergeAttributes(HTMLAttributes, {
|
|
28285
|
+
class: 'contentful-embedded-entry',
|
|
28286
|
+
})];
|
|
28287
|
+
},
|
|
28288
|
+
addNodeView() {
|
|
28289
|
+
return ReactNodeViewRenderer(EmbeddedEntryComponent);
|
|
28290
|
+
},
|
|
28291
|
+
});
|
|
28292
|
+
const EmbeddedAsset = Node.create({
|
|
28293
|
+
name: 'embeddedAsset',
|
|
28294
|
+
group: 'block',
|
|
28295
|
+
content: '',
|
|
28296
|
+
addAttributes() {
|
|
28297
|
+
return {
|
|
28298
|
+
assetId: {
|
|
28299
|
+
default: null,
|
|
28300
|
+
},
|
|
28301
|
+
title: {
|
|
28302
|
+
default: null,
|
|
28303
|
+
},
|
|
28304
|
+
url: {
|
|
28305
|
+
default: null,
|
|
28306
|
+
},
|
|
28307
|
+
};
|
|
28308
|
+
},
|
|
28309
|
+
parseHTML() {
|
|
28310
|
+
return [
|
|
28311
|
+
{
|
|
28312
|
+
tag: 'div[data-asset-id]',
|
|
28313
|
+
},
|
|
28314
|
+
{
|
|
28315
|
+
tag: 'img[data-asset-id]',
|
|
28316
|
+
},
|
|
28317
|
+
];
|
|
28318
|
+
},
|
|
28319
|
+
renderHTML({ HTMLAttributes }) {
|
|
28320
|
+
return ['div', mergeAttributes(HTMLAttributes, {
|
|
28321
|
+
class: 'contentful-embedded-asset',
|
|
28322
|
+
})];
|
|
28323
|
+
},
|
|
28324
|
+
addNodeView() {
|
|
28325
|
+
return ReactNodeViewRenderer(EmbeddedAssetComponent);
|
|
28326
|
+
},
|
|
28327
|
+
});
|
|
28328
|
+
|
|
28329
|
+
// Strategic Export Architecture for Production-Grade Contentful Editor
|
|
28330
|
+
// Optimized for professional deployment and development efficiency
|
|
28331
|
+
// Development and Debugging Utilities
|
|
28332
|
+
const ContentfulEditorVersion = '1.0.7-optimized';
|
|
28333
|
+
const SupportedContentfulVersion = '^16.0.0';
|
|
28334
|
+
const SupportedTiptapVersion = '^2.0.0';
|
|
28335
|
+
/**
|
|
28336
|
+
* Professional debugging utility for development environments
|
|
28337
|
+
* Enables comprehensive error tracking and performance monitoring
|
|
28338
|
+
*/
|
|
28339
|
+
const enableDebugMode = (enabled = true) => {
|
|
28340
|
+
if (typeof window !== 'undefined') {
|
|
28341
|
+
window.__CONTENTFUL_EDITOR_DEBUG__ = enabled;
|
|
28342
|
+
if (enabled) {
|
|
28343
|
+
console.log(`[ContentfulEditor] Debug mode activated - Version ${ContentfulEditorVersion}`);
|
|
28344
|
+
}
|
|
28345
|
+
}
|
|
28346
|
+
};
|
|
28347
|
+
/**
|
|
28348
|
+
* Strategic compatibility checker for production deployments
|
|
28349
|
+
* Validates environment requirements and dependency versions
|
|
28350
|
+
*/
|
|
28351
|
+
const validateEnvironment = () => {
|
|
28352
|
+
try {
|
|
28353
|
+
// Validate React environment
|
|
28354
|
+
if (typeof React === 'undefined') {
|
|
28355
|
+
console.error('[ContentfulEditor] React not detected in environment');
|
|
28356
|
+
return false;
|
|
28357
|
+
}
|
|
28358
|
+
// Validate DOM environment for browser usage
|
|
28359
|
+
if (typeof window === 'undefined' && typeof document === 'undefined') {
|
|
28360
|
+
console.warn('[ContentfulEditor] Running in server environment - ensure proper SSR handling');
|
|
28361
|
+
}
|
|
28362
|
+
console.log('[ContentfulEditor] Environment validation successful');
|
|
28363
|
+
return true;
|
|
28364
|
+
}
|
|
28365
|
+
catch (error) {
|
|
28366
|
+
console.error('[ContentfulEditor] Environment validation failed:', error);
|
|
28367
|
+
return false;
|
|
28368
|
+
}
|
|
28369
|
+
};
|
|
28370
|
+
/**
|
|
28371
|
+
* Production-grade initialization helper
|
|
28372
|
+
* Streamlines editor setup for enterprise deployments
|
|
28373
|
+
*/
|
|
28374
|
+
const initializeContentfulEditor = (config) => {
|
|
28375
|
+
const { debug = false, validateEnv = true } = config || {};
|
|
28376
|
+
if (validateEnv && !validateEnvironment()) {
|
|
28377
|
+
throw new Error('ContentfulEditor: Environment validation failed');
|
|
28378
|
+
}
|
|
28379
|
+
if (debug) {
|
|
28380
|
+
enableDebugMode(true);
|
|
28381
|
+
}
|
|
28382
|
+
return {
|
|
28383
|
+
version: ContentfulEditorVersion,
|
|
28384
|
+
ready: true,
|
|
28385
|
+
timestamp: new Date().toISOString()
|
|
28386
|
+
};
|
|
28387
|
+
};
|
|
28388
|
+
// Professional development utilities
|
|
28389
|
+
const utils = {
|
|
28390
|
+
validateEnvironment,
|
|
28391
|
+
enableDebugMode,
|
|
28392
|
+
initializeContentfulEditor,
|
|
28393
|
+
version: ContentfulEditorVersion
|
|
27409
28394
|
};
|
|
27410
28395
|
|
|
27411
|
-
export { ContentfulRichTextEditor, ContentfulToolbar, contentfulToTiptap, createEmptyDocument, tiptapToContentful, validateContentfulDocument };
|
|
28396
|
+
export { ContentfulDocument, ContentfulEditorVersion, ContentfulRichTextEditor, ContentfulToolbar, EmbeddedAsset as EmbeddedAssetComponent, EmbeddedEntry as EmbeddedEntryComponent, SupportedContentfulVersion, SupportedTiptapVersion, contentfulToTiptap, createEmptyDocument, createEmptyTiptapDocument, enableDebugMode, initializeContentfulEditor, safeContentfulToTiptap, tiptapToContentful, utils, validateContentfulDocument, validateEnvironment };
|
|
27412
28397
|
//# sourceMappingURL=index.esm.js.map
|