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