@contentful/field-editor-rich-text 3.6.0 → 3.7.0

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.
Files changed (40) hide show
  1. package/dist/cjs/RichTextEditor.js +42 -53
  2. package/dist/cjs/SyncEditorValue.js +107 -0
  3. package/dist/cjs/helpers/callbacks.js +35 -0
  4. package/dist/cjs/helpers/toSlateValue.js +51 -0
  5. package/dist/cjs/internal/hooks.js +12 -2
  6. package/dist/cjs/internal/misc.js +0 -6
  7. package/dist/cjs/plugins/Table/onKeyDownTable.js +14 -0
  8. package/dist/cjs/plugins/Text/__tests__/createTextPlugin.test.js +5 -15
  9. package/dist/cjs/plugins/index.js +0 -5
  10. package/dist/cjs/plugins/shared/FetchingWrappedResourceCard.js +11 -9
  11. package/dist/cjs/plugins/shared/__tests__/FetchingWrappedResourceCard.test.js +146 -0
  12. package/dist/esm/RichTextEditor.js +37 -48
  13. package/dist/esm/SyncEditorValue.js +53 -0
  14. package/dist/esm/helpers/callbacks.js +20 -0
  15. package/dist/{cjs/helpers/sanitizeIncomingSlateDoc.js → esm/helpers/toSlateValue.js} +13 -10
  16. package/dist/esm/internal/hooks.js +9 -2
  17. package/dist/esm/internal/misc.js +0 -3
  18. package/dist/esm/plugins/Table/onKeyDownTable.js +15 -1
  19. package/dist/esm/plugins/Text/__tests__/createTextPlugin.test.js +5 -15
  20. package/dist/esm/plugins/index.js +0 -5
  21. package/dist/esm/plugins/shared/FetchingWrappedResourceCard.js +12 -10
  22. package/dist/esm/plugins/shared/__tests__/FetchingWrappedResourceCard.test.js +98 -0
  23. package/dist/types/ContentfulEditorProvider.d.ts +2 -3
  24. package/dist/types/RichTextEditor.d.ts +2 -2
  25. package/dist/types/SyncEditorValue.d.ts +13 -0
  26. package/dist/types/helpers/callbacks.d.ts +3 -0
  27. package/dist/types/helpers/toSlateValue.d.ts +7 -0
  28. package/dist/types/internal/hooks.d.ts +4 -2
  29. package/dist/types/internal/misc.d.ts +2 -2
  30. package/dist/types/plugins/shared/__tests__/FetchingWrappedResourceCard.test.d.ts +1 -0
  31. package/dist/types/test-utils/createEditor.d.ts +2 -0
  32. package/package.json +14 -14
  33. package/dist/cjs/prepareDocument.js +0 -86
  34. package/dist/cjs/useOnValueChanged.js +0 -58
  35. package/dist/esm/helpers/sanitizeIncomingSlateDoc.js +0 -23
  36. package/dist/esm/prepareDocument.js +0 -57
  37. package/dist/esm/useOnValueChanged.js +0 -43
  38. package/dist/types/helpers/sanitizeIncomingSlateDoc.d.ts +0 -6
  39. package/dist/types/prepareDocument.d.ts +0 -19
  40. package/dist/types/useOnValueChanged.d.ts +0 -8
@@ -1,20 +1,20 @@
1
- import React, { useCallback, useState, useEffect } from 'react';
1
+ import * as React from 'react';
2
2
  import { EntityProvider } from '@contentful/field-editor-reference';
3
3
  import { FieldConnector } from '@contentful/field-editor-shared';
4
4
  import * as Contentful from '@contentful/rich-text-types';
5
- import { Plate, getPlateActions } from '@udecode/plate-core';
5
+ import { Plate, PlateProvider } from '@udecode/plate-core';
6
6
  import { css, cx } from 'emotion';
7
7
  import deepEquals from 'fast-deep-equal';
8
8
  import noop from 'lodash/noop';
9
9
  import { ContentfulEditorIdProvider, getContentfulEditorId } from './ContentfulEditorProvider';
10
- import { getPlateSelectors } from './internal/misc';
10
+ import { createOnChangeCallback } from './helpers/callbacks';
11
+ import { toSlateValue } from './helpers/toSlateValue';
11
12
  import { getPlugins, disableCorePlugins } from './plugins';
12
- import { documentToEditorValue, normalizeEditorValue, setEditorContent } from './prepareDocument';
13
13
  import { styles } from './RichTextEditor.styles';
14
14
  import { SdkProvider } from './SdkProvider';
15
+ import { SyncEditorValue } from './SyncEditorValue';
15
16
  import Toolbar from './Toolbar';
16
17
  import StickyToolbarWrapper from './Toolbar/components/StickyToolbarWrapper';
17
- import { useOnValueChanged } from './useOnValueChanged';
18
18
  export const ConnectedRichTextEditor = (props)=>{
19
19
  const id = getContentfulEditorId(props.sdk);
20
20
  const plugins = React.useMemo(()=>getPlugins(props.sdk, props.onAction ?? noop, props.restrictedMarks), [
@@ -22,43 +22,24 @@ export const ConnectedRichTextEditor = (props)=>{
22
22
  props.onAction,
23
23
  props.restrictedMarks
24
24
  ]);
25
- const [isFirstRender, setIsFirstRender] = useState(true);
26
- const [pendingExternalUpdate, setPendingExternalUpdate] = useState(false);
27
- const onValueChanged = useOnValueChanged({
28
- editorId: id,
29
- handler: props.onChange,
30
- skip: pendingExternalUpdate || isFirstRender,
31
- onSkip: ()=>setPendingExternalUpdate(false)
32
- });
33
- useEffect(()=>{
34
- setIsFirstRender(false);
35
- const editor = getPlateSelectors(id).editor();
36
- if (!editor) {
37
- return;
38
- }
39
- setPendingExternalUpdate(true);
40
- setEditorContent(editor, documentToEditorValue(props.value));
25
+ const handleChange = props.onChange;
26
+ const isFirstRender = React.useRef(true);
27
+ const value = toSlateValue(props.value);
28
+ const onChange = React.useMemo(()=>createOnChangeCallback((document)=>{
29
+ if (!isFirstRender.current && handleChange) {
30
+ handleChange(document);
31
+ }
32
+ }), [
33
+ handleChange
34
+ ]);
35
+ const firstInteractionHandler = React.useCallback(()=>{
36
+ isFirstRender.current = false;
41
37
  }, [
42
- props.value,
43
- id
38
+ isFirstRender
44
39
  ]);
45
40
  const classNames = cx(styles.editor, props.minHeight !== undefined ? css({
46
41
  minHeight: props.minHeight
47
42
  }) : undefined, props.isDisabled ? styles.disabled : styles.enabled, props.isToolbarHidden && styles.hiddenToolbar);
48
- useEffect(()=>{
49
- if (!isFirstRender) {
50
- return;
51
- }
52
- getPlateActions(id).value(normalizeEditorValue(documentToEditorValue(props.value), {
53
- plugins,
54
- disableCorePlugins
55
- }));
56
- }, [
57
- isFirstRender,
58
- plugins,
59
- id,
60
- props.value
61
- ]);
62
43
  return React.createElement(SdkProvider, {
63
44
  sdk: props.sdk
64
45
  }, React.createElement(ContentfulEditorIdProvider, {
@@ -66,25 +47,33 @@ export const ConnectedRichTextEditor = (props)=>{
66
47
  }, React.createElement("div", {
67
48
  className: styles.root,
68
49
  "data-test-id": "rich-text-editor"
69
- }, React.createElement(Plate, {
50
+ }, React.createElement(PlateProvider, {
70
51
  id: id,
52
+ initialValue: value,
53
+ normalizeInitialValue: true,
71
54
  plugins: plugins,
72
55
  disableCorePlugins: disableCorePlugins,
56
+ onChange: onChange
57
+ }, !props.isToolbarHidden && React.createElement(StickyToolbarWrapper, {
58
+ isDisabled: props.isDisabled
59
+ }, React.createElement(Toolbar, {
60
+ isDisabled: props.isDisabled
61
+ })), React.createElement(SyncEditorValue, {
62
+ incomingValue: value
63
+ }), React.createElement(Plate, {
64
+ id: id,
73
65
  editableProps: {
74
66
  className: classNames,
75
- readOnly: props.isDisabled
76
- },
77
- onChange: onValueChanged,
78
- firstChildren: !props.isToolbarHidden && React.createElement(StickyToolbarWrapper, {
79
- isDisabled: props.isDisabled
80
- }, React.createElement(Toolbar, {
81
- isDisabled: props.isDisabled
82
- }))
83
- }))));
67
+ readOnly: props.isDisabled,
68
+ onKeyDown: firstInteractionHandler,
69
+ onChange: firstInteractionHandler,
70
+ onClick: firstInteractionHandler
71
+ }
72
+ })))));
84
73
  };
85
74
  const RichTextEditor = (props)=>{
86
75
  const { sdk , isInitiallyDisabled , onAction , restrictedMarks , ...otherProps } = props;
87
- const isEmptyValue = useCallback((value)=>!value || deepEquals(value, Contentful.EMPTY_DOCUMENT), []);
76
+ const isEmptyValue = React.useCallback((value)=>!value || deepEquals(value, Contentful.EMPTY_DOCUMENT), []);
88
77
  const id = getContentfulEditorId(props.sdk);
89
78
  return React.createElement(EntityProvider, {
90
79
  sdk: sdk
@@ -0,0 +1,53 @@
1
+ import * as React from 'react';
2
+ import equal from 'fast-deep-equal';
3
+ import { usePlateSelectors } from './internal/hooks';
4
+ import { withoutNormalizing } from './internal/misc';
5
+ import { isNode, getEndPoint } from './internal/queries';
6
+ import { select } from './internal/transforms';
7
+ const setEditorContent = (editor, nodes)=>{
8
+ withoutNormalizing(editor, ()=>{
9
+ const children = [
10
+ ...editor.children
11
+ ];
12
+ children.forEach((node)=>editor.apply({
13
+ type: 'remove_node',
14
+ path: [
15
+ 0
16
+ ],
17
+ node
18
+ }));
19
+ if (nodes) {
20
+ const nodesArray = isNode(nodes) ? [
21
+ nodes
22
+ ] : nodes;
23
+ nodesArray.forEach((node, i)=>{
24
+ editor.apply({
25
+ type: 'insert_node',
26
+ path: [
27
+ i
28
+ ],
29
+ node
30
+ });
31
+ });
32
+ }
33
+ const point = getEndPoint(editor, []);
34
+ if (point) {
35
+ select(editor, point);
36
+ }
37
+ });
38
+ };
39
+ export const SyncEditorValue = ({ incomingValue })=>{
40
+ const editor = usePlateSelectors().editor();
41
+ const lastIncomingValue = React.useRef(incomingValue);
42
+ React.useEffect(()=>{
43
+ if (equal(lastIncomingValue.current, incomingValue)) {
44
+ return;
45
+ }
46
+ lastIncomingValue.current = incomingValue;
47
+ setEditorContent(editor, incomingValue);
48
+ }, [
49
+ editor,
50
+ incomingValue
51
+ ]);
52
+ return null;
53
+ };
@@ -0,0 +1,20 @@
1
+ import { toContentfulDocument } from '@contentful/contentful-slatejs-adapter';
2
+ import equal from 'fast-deep-equal';
3
+ import debounce from 'lodash/debounce';
4
+ import schema from '../constants/Schema';
5
+ import { removeInternalMarks } from './removeInternalMarks';
6
+ export const createOnChangeCallback = (handler)=>{
7
+ let cache = null;
8
+ return debounce((document)=>{
9
+ if (equal(document, cache)) {
10
+ return;
11
+ }
12
+ cache = document;
13
+ const doc = removeInternalMarks(toContentfulDocument({
14
+ document: document,
15
+ schema: schema
16
+ }));
17
+ const cleanedDocument = removeInternalMarks(doc);
18
+ handler?.(cleanedDocument);
19
+ }, 500);
20
+ };
@@ -1,13 +1,6 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- Object.defineProperty(exports, "sanitizeIncomingSlateDoc", {
6
- enumerable: true,
7
- get: function() {
8
- return sanitizeIncomingSlateDoc;
9
- }
10
- });
1
+ import { toSlatejsDocument } from '@contentful/contentful-slatejs-adapter';
2
+ import { EMPTY_DOCUMENT } from '@contentful/rich-text-types';
3
+ import schema from '../constants/Schema';
11
4
  const isTextElement = (node)=>'text' in node;
12
5
  function sanitizeIncomingSlateDoc(nodes = []) {
13
6
  return nodes.map((node)=>{
@@ -31,3 +24,13 @@ function sanitizeIncomingSlateDoc(nodes = []) {
31
24
  };
32
25
  });
33
26
  }
27
+ export const toSlateValue = (doc)=>{
28
+ const hasContent = (doc)=>{
29
+ return (doc?.content || []).length > 0;
30
+ };
31
+ const slateDoc = toSlatejsDocument({
32
+ document: doc && hasContent(doc) ? doc : EMPTY_DOCUMENT,
33
+ schema
34
+ });
35
+ return sanitizeIncomingSlateDoc(slateDoc);
36
+ };
@@ -1,5 +1,12 @@
1
1
  import * as p from '@udecode/plate-core';
2
2
  import * as sr from 'slate-react';
3
3
  export const useReadOnly = sr.useReadOnly;
4
- export const usePlateEditorRef = p.usePlateEditorRef;
5
- export const usePlateEditorState = p.usePlateEditorState;
4
+ export const usePlateEditorRef = (id)=>{
5
+ return p.usePlateEditorRef(id);
6
+ };
7
+ export const usePlateEditorState = (id)=>{
8
+ return p.usePlateEditorState(id);
9
+ };
10
+ export const usePlateSelectors = (id)=>{
11
+ return p.usePlateSelectors(id);
12
+ };
@@ -23,6 +23,3 @@ export const fromDOMPoint = (editor, domPoint, opts = {
23
23
  export const mockPlugin = (plugin)=>{
24
24
  return p.mockPlugin(plugin);
25
25
  };
26
- export const getPlateSelectors = (id)=>{
27
- return p.getPlateSelectors(id);
28
- };
@@ -2,7 +2,7 @@ import { BLOCKS } from '@contentful/rich-text-types';
2
2
  import { getTableEntries, onKeyDownTable as defaultKeyDownTable } from '@udecode/plate-table';
3
3
  import { insertEmptyParagraph } from '../../helpers/editor';
4
4
  import { blurEditor } from '../../internal/misc';
5
- import { getAboveNode, isLastChildPath } from '../../internal/queries';
5
+ import { getAboveNode, isLastChildPath, getText, isFirstChild } from '../../internal/queries';
6
6
  import { addRowBelow } from './actions';
7
7
  export const onKeyDownTable = (editor, plugin)=>{
8
8
  const defaultHandler = defaultKeyDownTable(editor, plugin);
@@ -30,6 +30,20 @@ export const onKeyDownTable = (editor, plugin)=>{
30
30
  return;
31
31
  }
32
32
  }
33
+ if (event.key === 'Backspace') {
34
+ const entry = getTableEntries(editor, {});
35
+ if (entry) {
36
+ const { table , row , cell } = entry;
37
+ const cellText = getText(editor, cell[1]);
38
+ const isFirstCell = isFirstChild(row[1]);
39
+ const isFirstRow = isFirstChild(table[1]);
40
+ if (isFirstCell && isFirstRow && !cellText) {
41
+ event.preventDefault();
42
+ event.stopPropagation();
43
+ return;
44
+ }
45
+ }
46
+ }
33
47
  if (event.key === 'Tab' && !event.shiftKey) {
34
48
  event.preventDefault();
35
49
  const entry = getTableEntries(editor, {});
@@ -7,19 +7,9 @@ describe('delete backward', ()=>{
7
7
  expected: jsx("hul", null, jsx("hli", null, jsx("hp", null, "p", jsx("cursor", null))))
8
8
  },
9
9
  {
10
- title: 'deletes the empty paragraph at the beginning of the RTE followed by another paragraph',
10
+ title: 'does not delete the very first paragraph',
11
11
  input: jsx("fragment", null, jsx("hp", null, jsx("cursor", null)), jsx("hp", null, "text")),
12
- expected: jsx("hp", null, jsx("cursor", null), "text")
13
- },
14
- {
15
- title: 'deletes the empty paragraph at the beginning of the RTE followed by li',
16
- input: jsx("fragment", null, jsx("hp", null, jsx("cursor", null)), jsx("hul", null, jsx("hli", null, jsx("hp", null, "p1")))),
17
- expected: jsx("hul", null, jsx("hli", null, jsx("hp", null, jsx("cursor", null), "p1")))
18
- },
19
- {
20
- title: 'deletes the empty paragraph at the beginning of the RTE followed by a blockquote',
21
- input: jsx("fragment", null, jsx("hp", null, jsx("cursor", null)), jsx("hquote", null, jsx("hp", null, "p1"))),
22
- expected: jsx("hquote", null, jsx("hp", null, jsx("cursor", null), "p1"))
12
+ expected: jsx("fragment", null, jsx("hp", null, jsx("cursor", null)), jsx("hp", null, "text"))
23
13
  }
24
14
  ];
25
15
  const render = (children)=>jsx("editor", null, children, jsx("hp", null, jsx("htext", null)));
@@ -44,17 +34,17 @@ describe('delete forward', ()=>{
44
34
  expected: jsx("hul", null, jsx("hli", null, jsx("hp", null, jsx("cursor", null), "1")))
45
35
  },
46
36
  {
47
- title: 'deletes the empty paragraph at the beginning of the RTE followed by another paragraph',
37
+ title: 'deletes the first paragraph when followed by another paragraph',
48
38
  input: jsx("fragment", null, jsx("hp", null, jsx("cursor", null)), jsx("hp", null, "text")),
49
39
  expected: jsx("hp", null, jsx("cursor", null), "text")
50
40
  },
51
41
  {
52
- title: 'deletes the empty paragraph at the beginning of the RTE followed by li',
42
+ title: 'deletes the first paragraph when followed by li',
53
43
  input: jsx("fragment", null, jsx("hp", null, jsx("cursor", null)), jsx("hul", null, jsx("hli", null, jsx("hp", null, "p1")))),
54
44
  expected: jsx("hul", null, jsx("hli", null, jsx("hp", null, jsx("cursor", null), "p1")))
55
45
  },
56
46
  {
57
- title: 'deletes the empty paragraph at the beginning of the RTE followed by a blockquote',
47
+ title: 'deletes the first paragraph when followed by a blockquote',
58
48
  input: jsx("fragment", null, jsx("hp", null, jsx("cursor", null)), jsx("hquote", null, jsx("hp", null, "p1"))),
59
49
  expected: jsx("hquote", null, jsx("hp", null, jsx("cursor", null), "p1"))
60
50
  }
@@ -1,4 +1,3 @@
1
- import { createDeserializeAstPlugin, createDeserializeHtmlPlugin } from '@udecode/plate-core';
2
1
  import { createDeserializeDocxPlugin } from '@udecode/plate-serializer-docx';
3
2
  import { createSoftBreakPlugin, createExitBreakPlugin, createResetNodePlugin } from './Break';
4
3
  import { createCommandPalettePlugin } from './CommandPalette';
@@ -23,8 +22,6 @@ import { createTrackingPlugin } from './Tracking';
23
22
  import { createTrailingParagraphPlugin } from './TrailingParagraph';
24
23
  import { createVoidsPlugin } from './Voids';
25
24
  export const getPlugins = (sdk, onAction, restrictedMarks)=>[
26
- createDeserializeHtmlPlugin(),
27
- createDeserializeAstPlugin(),
28
25
  createDeserializeDocxPlugin(),
29
26
  createTrackingPlugin(onAction),
30
27
  createDragAndDropPlugin(),
@@ -54,7 +51,5 @@ export const getPlugins = (sdk, onAction, restrictedMarks)=>[
54
51
  createNormalizerPlugin()
55
52
  ];
56
53
  export const disableCorePlugins = {
57
- deserializeAst: true,
58
- deserializeHtml: true,
59
54
  eventEditor: true
60
55
  };
@@ -1,20 +1,13 @@
1
1
  import * as React from 'react';
2
2
  import { EntryCard } from '@contentful/f36-components';
3
- import { MissingEntityCard, WrappedEntryCard, useResource } from '@contentful/field-editor-reference';
3
+ import { ResourceEntityErrorCard, WrappedEntryCard, useResource } from '@contentful/field-editor-reference';
4
4
  import areEqual from 'fast-deep-equal';
5
5
  const InternalEntryCard = React.memo((props)=>{
6
- if (props.status === 'loading') {
6
+ if (props.data === undefined || props.status === 'loading') {
7
7
  return React.createElement(EntryCard, {
8
8
  isLoading: true
9
9
  });
10
10
  }
11
- if (!props.data || props.status === 'error') {
12
- return React.createElement(MissingEntityCard, {
13
- entityType: "Entry",
14
- isDisabled: props.isDisabled,
15
- onRemove: props.onRemove
16
- });
17
- }
18
11
  const { contentType , resource: entry , space } = props.data;
19
12
  return React.createElement(WrappedEntryCard, {
20
13
  size: "default",
@@ -35,7 +28,7 @@ const InternalEntryCard = React.memo((props)=>{
35
28
  InternalEntryCard.displayName = 'ReferenceCard';
36
29
  export const FetchingWrappedResourceCard = (props)=>{
37
30
  const { link , onEntityFetchComplete } = props;
38
- const { data , status } = useResource(link.linkType, link.urn);
31
+ const { data , status , error } = useResource(link.linkType, link.urn);
39
32
  React.useEffect(()=>{
40
33
  if (status === 'success') {
41
34
  onEntityFetchComplete?.();
@@ -44,6 +37,15 @@ export const FetchingWrappedResourceCard = (props)=>{
44
37
  onEntityFetchComplete,
45
38
  status
46
39
  ]);
40
+ if (status === 'error') {
41
+ return React.createElement(ResourceEntityErrorCard, {
42
+ error: error,
43
+ linkType: link.linkType,
44
+ isSelected: props.isSelected,
45
+ isDisabled: props.isDisabled,
46
+ onRemove: props.onRemove
47
+ });
48
+ }
47
49
  return React.createElement(InternalEntryCard, {
48
50
  data: data,
49
51
  status: status,
@@ -0,0 +1,98 @@
1
+ import * as React from 'react';
2
+ import '@testing-library/jest-dom/extend-expect';
3
+ import { EntityProvider } from '@contentful/field-editor-reference';
4
+ import { createFakeCMAAdapter } from '@contentful/field-editor-test-utils';
5
+ import { configure, render, waitFor } from '@testing-library/react';
6
+ import publishedCT from '../__fixtures__/published_content_type.json';
7
+ import publishedEntry from '../__fixtures__/published_entry.json';
8
+ import space from '../__fixtures__/space.json';
9
+ import { FetchingWrappedResourceCard } from '../FetchingWrappedResourceCard';
10
+ configure({
11
+ testIdAttribute: 'data-test-id'
12
+ });
13
+ let sdk;
14
+ const resolvableEntryUrn = 'crn:contentful:::content:spaces/space-id/entries/linked-entry-urn';
15
+ const unknownEntryUrn = 'crn:contentful:::content:spaces/space-id/entries/unknown-entry-urn';
16
+ beforeEach(()=>{
17
+ sdk = {
18
+ locales: {
19
+ default: 'en-US'
20
+ },
21
+ cmaAdapter: createFakeCMAAdapter({
22
+ ContentType: {
23
+ get: jest.fn().mockReturnValue(publishedCT)
24
+ },
25
+ Entry: {
26
+ get: jest.fn().mockImplementation(({ entryId })=>{
27
+ if (entryId === 'linked-entry-urn') {
28
+ return Promise.resolve(publishedEntry);
29
+ }
30
+ return Promise.reject(new Error());
31
+ })
32
+ },
33
+ Locale: {
34
+ getMany: jest.fn().mockResolvedValue({
35
+ items: [
36
+ {
37
+ default: true,
38
+ code: 'en'
39
+ }
40
+ ]
41
+ })
42
+ },
43
+ ScheduledAction: {
44
+ getMany: jest.fn().mockResolvedValue({
45
+ items: [],
46
+ total: 0
47
+ })
48
+ },
49
+ Space: {
50
+ get: jest.fn().mockResolvedValue(space)
51
+ }
52
+ }),
53
+ space: {
54
+ onEntityChanged: jest.fn()
55
+ },
56
+ navigator: {},
57
+ ids: {
58
+ space: 'space-id',
59
+ environment: 'environment-id'
60
+ }
61
+ };
62
+ });
63
+ function renderResourceCard({ linkType ='Contentful:Entry' , entryUrn =resolvableEntryUrn } = {}) {
64
+ return render(React.createElement(EntityProvider, {
65
+ sdk: sdk
66
+ }, React.createElement(FetchingWrappedResourceCard, {
67
+ isDisabled: false,
68
+ isSelected: false,
69
+ sdk: sdk,
70
+ link: {
71
+ type: 'ResourceLink',
72
+ linkType: linkType,
73
+ urn: entryUrn
74
+ }
75
+ })));
76
+ }
77
+ test('renders entry card', async ()=>{
78
+ const { getByTestId , getByText } = renderResourceCard();
79
+ await waitFor(()=>expect(getByTestId('cf-ui-entry-card')).toBeDefined());
80
+ expect(getByText(publishedEntry.fields.exField.en)).toBeDefined();
81
+ expect(getByText(space.name)).toBeDefined();
82
+ });
83
+ test('renders skeleton when no data is provided', ()=>{
84
+ const { getByTestId } = renderResourceCard();
85
+ expect(getByTestId('cf-ui-skeleton-form')).toBeDefined();
86
+ });
87
+ test('renders unsupported entity card when unsupported link is passed', async ()=>{
88
+ const { getByText } = renderResourceCard({
89
+ linkType: 'Contentful:UnsupportedLink'
90
+ });
91
+ await waitFor(()=>expect(getByText('Resource type Contentful:UnsupportedLink is currently not supported')).toBeDefined());
92
+ });
93
+ test('renders missing entity card when unknown error is returned', async ()=>{
94
+ const { getByTestId } = renderResourceCard({
95
+ entryUrn: unknownEntryUrn
96
+ });
97
+ await waitFor(()=>expect(getByTestId('cf-ui-missing-entry-card')).toBeDefined());
98
+ });
@@ -1,9 +1,8 @@
1
1
  /// <reference types="react" />
2
2
  import { FieldExtensionSDK } from '@contentful/app-sdk';
3
- import { PlateEditor } from './internal/types';
4
3
  export declare function getContentfulEditorId(sdk: FieldExtensionSDK): string;
5
4
  export declare const editorContext: import("react").Context<string>;
6
5
  export declare const ContentfulEditorIdProvider: import("react").Provider<string>;
7
6
  export declare function useContentfulEditorId(id?: string): string;
8
- export declare function useContentfulEditor(id?: string): PlateEditor | null;
9
- export declare function useContentfulEditorRef(id?: string): PlateEditor | null;
7
+ export declare function useContentfulEditor(id?: string): import("./internal").PlateEditor;
8
+ export declare function useContentfulEditorRef(id?: string): import("./internal").PlateEditor;
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import * as React from 'react';
2
2
  import { FieldExtensionSDK } from '@contentful/app-sdk';
3
3
  import * as Contentful from '@contentful/rich-text-types';
4
4
  import { RichTextTrackingActionHandler } from './plugins/Tracking';
@@ -6,7 +6,7 @@ type ConnectedProps = {
6
6
  sdk: FieldExtensionSDK;
7
7
  onAction?: RichTextTrackingActionHandler;
8
8
  minHeight?: string | number;
9
- value?: object;
9
+ value?: Contentful.Document;
10
10
  isDisabled?: boolean;
11
11
  onChange?: (doc: Contentful.Document) => unknown;
12
12
  isToolbarHidden?: boolean;
@@ -0,0 +1,13 @@
1
+ import { Value } from './internal/types';
2
+ export type SyncEditorStateProps = {
3
+ incomingValue?: Value;
4
+ };
5
+ /**
6
+ * A void component that's responsible for keeping the editor
7
+ * state in sync with incoming changes (aka. external updates)
8
+ *
9
+ * This component is a hack to workaround the limitation of Plate v17+
10
+ * where we can no longer access the editor instance outside the Plate
11
+ * provider.
12
+ */
13
+ export declare const SyncEditorValue: ({ incomingValue }: SyncEditorStateProps) => null;
@@ -0,0 +1,3 @@
1
+ /// <reference types="lodash" />
2
+ import { Document } from '@contentful/rich-text-types';
3
+ export declare const createOnChangeCallback: (handler?: ((value: Document) => void) | undefined) => import("lodash").DebouncedFunc<(document: unknown) => void>;
@@ -0,0 +1,7 @@
1
+ import { Document } from '@contentful/rich-text-types';
2
+ import { Element } from '../internal/types';
3
+ /**
4
+ * Converts a Contentful rich text document to the corresponding slate editor
5
+ * value
6
+ */
7
+ export declare const toSlateValue: (doc?: Document) => Element[];
@@ -1,4 +1,6 @@
1
1
  import * as p from '@udecode/plate-core';
2
+ import { PlateEditor, Value } from './types';
2
3
  export declare const useReadOnly: () => boolean;
3
- export declare const usePlateEditorRef: <V extends p.Value = p.Value, E extends p.PlateEditor<V> = p.PlateEditor<V>>(id?: string | undefined) => E | null;
4
- export declare const usePlateEditorState: <V extends p.Value = p.Value, E extends p.PlateEditor<V> = p.PlateEditor<V>>(id?: string | undefined) => E | null;
4
+ export declare const usePlateEditorRef: (id?: string) => PlateEditor;
5
+ export declare const usePlateEditorState: (id?: string) => PlateEditor;
6
+ export declare const usePlateSelectors: (id?: string) => p.GetRecord<p.PlateStoreState<Value, PlateEditor>>;
@@ -1,6 +1,5 @@
1
1
  /// <reference types="react" />
2
2
  import * as p from '@udecode/plate-core';
3
- import { StoreApiGet } from '@udecode/zustood';
4
3
  import * as s from 'slate';
5
4
  import type { Value, PlateEditor, Location, PlatePlugin } from './types';
6
5
  export type CreatePlateEditorOptions = Omit<p.CreatePlateEditorOptions<Value, PlateEditor>, 'plugins'> & {
@@ -23,6 +22,8 @@ export declare const createPlateEditor: (options?: CreatePlateEditorOptions) =>
23
22
  plugins: p.WithPlatePlugin<{}, Value, p.PlateEditor<Value>>[];
24
23
  pluginsByKey: Record<string, p.WithPlatePlugin<{}, Value, p.PlateEditor<Value>>>;
25
24
  prevSelection: s.BaseRange | null;
25
+ blockFactory: (node?: Partial<p.TElement> | undefined, path?: s.Path | undefined) => p.TElement;
26
+ childrenFactory: () => Value;
26
27
  currentKeyboardEvent: import("react").KeyboardEvent<Element> | null;
27
28
  };
28
29
  export declare const withoutNormalizing: (editor: PlateEditor, fn: () => boolean | void) => boolean;
@@ -34,4 +35,3 @@ export declare const fromDOMPoint: (editor: PlateEditor, domPoint: [Node, number
34
35
  suppressThrow: boolean;
35
36
  }) => s.BasePoint | null | undefined;
36
37
  export declare const mockPlugin: (plugin?: Partial<PlatePlugin> | undefined) => p.WithPlatePlugin<p.AnyObject, p.Value, p.PlateEditor<p.Value>>;
37
- export declare const getPlateSelectors: (id?: string | undefined) => StoreApiGet<p.PlateStoreState<Value, PlateEditor>, {}>;
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom/extend-expect';
@@ -25,6 +25,8 @@ export declare const createTestEditor: (options: {
25
25
  plugins: import("@udecode/plate-core").WithPlatePlugin<{}, Value, import("@udecode/plate-core").PlateEditor<Value>>[];
26
26
  pluginsByKey: Record<string, import("@udecode/plate-core").WithPlatePlugin<{}, Value, import("@udecode/plate-core").PlateEditor<Value>>>;
27
27
  prevSelection: import("slate").BaseRange | null;
28
+ blockFactory: (node?: Partial<import("@udecode/plate-core").TElement> | undefined, path?: import("slate").Path | undefined) => import("@udecode/plate-core").TElement;
29
+ childrenFactory: () => Value;
28
30
  currentKeyboardEvent: import("react").KeyboardEvent<Element> | null;
29
31
  };
30
32
  normalize: () => void;