@contentful/field-editor-rich-text 3.5.0 → 3.6.1

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 (88) hide show
  1. package/dist/cjs/RichTextEditor.js +42 -53
  2. package/dist/cjs/SyncEditorValue.js +107 -0
  3. package/dist/cjs/Toolbar/components/EmbedEntityWidget.js +8 -3
  4. package/dist/cjs/helpers/__tests__/removeInternalMarks.test.js +37 -21
  5. package/dist/cjs/helpers/callbacks.js +35 -0
  6. package/dist/cjs/helpers/getAllowedResourcesForNodeType.js +25 -0
  7. package/dist/cjs/helpers/newResourceEntitySelectorConfigFromRichTextField.js +21 -0
  8. package/dist/cjs/helpers/toSlateValue.js +51 -0
  9. package/dist/cjs/internal/hooks.js +12 -2
  10. package/dist/cjs/internal/misc.js +0 -6
  11. package/dist/cjs/plugins/DragAndDrop/index.js +1 -0
  12. package/dist/cjs/plugins/EmbeddedEntityBlock/LinkedEntityBlock.js +27 -44
  13. package/dist/cjs/plugins/EmbeddedEntityBlock/index.js +3 -35
  14. package/dist/cjs/plugins/EmbeddedEntityInline/index.js +3 -2
  15. package/dist/cjs/plugins/EmbeddedResourceBlock/LinkedResourceBlock.js +54 -0
  16. package/dist/cjs/plugins/EmbeddedResourceBlock/index.js +55 -0
  17. package/dist/cjs/plugins/Hyperlink/components/EntityHyperlink.js +2 -1
  18. package/dist/cjs/plugins/Hyperlink/createHyperlinkPlugin.js +2 -3
  19. package/dist/cjs/plugins/Table/onKeyDownTable.js +14 -0
  20. package/dist/cjs/plugins/Text/__tests__/createTextPlugin.test.js +5 -15
  21. package/dist/cjs/plugins/Text/createTextPlugin.js +1 -0
  22. package/dist/cjs/plugins/index.js +2 -5
  23. package/dist/cjs/plugins/links-tracking.js +8 -17
  24. package/dist/cjs/plugins/{EmbeddedEntityBlock/ToolbarIcon.js → shared/EmbeddedBlockToolbarIcon.js} +15 -7
  25. package/dist/cjs/plugins/shared/EmbeddedBlockUtil.js +170 -0
  26. package/dist/cjs/plugins/shared/FetchingWrappedResourceCard.js +110 -0
  27. package/dist/cjs/plugins/shared/LinkedBlockWrapper.js +45 -0
  28. package/dist/esm/RichTextEditor.js +37 -48
  29. package/dist/esm/SyncEditorValue.js +53 -0
  30. package/dist/esm/Toolbar/components/EmbedEntityWidget.js +8 -3
  31. package/dist/esm/helpers/__tests__/removeInternalMarks.test.js +37 -21
  32. package/dist/esm/helpers/callbacks.js +20 -0
  33. package/dist/esm/helpers/getAllowedResourcesForNodeType.js +10 -0
  34. package/dist/esm/helpers/newResourceEntitySelectorConfigFromRichTextField.js +6 -0
  35. package/dist/{cjs/helpers/sanitizeIncomingSlateDoc.js → esm/helpers/toSlateValue.js} +13 -10
  36. package/dist/esm/internal/hooks.js +9 -2
  37. package/dist/esm/internal/misc.js +0 -3
  38. package/dist/esm/plugins/DragAndDrop/index.js +1 -0
  39. package/dist/esm/plugins/EmbeddedEntityBlock/LinkedEntityBlock.js +27 -44
  40. package/dist/esm/plugins/EmbeddedEntityBlock/index.js +3 -27
  41. package/dist/esm/plugins/EmbeddedEntityInline/index.js +4 -3
  42. package/dist/esm/plugins/EmbeddedResourceBlock/LinkedResourceBlock.js +39 -0
  43. package/dist/esm/plugins/EmbeddedResourceBlock/index.js +45 -0
  44. package/dist/esm/plugins/Hyperlink/components/EntityHyperlink.js +2 -1
  45. package/dist/esm/plugins/Hyperlink/createHyperlinkPlugin.js +2 -3
  46. package/dist/esm/plugins/Table/onKeyDownTable.js +15 -1
  47. package/dist/esm/plugins/Text/__tests__/createTextPlugin.test.js +5 -15
  48. package/dist/esm/plugins/Text/createTextPlugin.js +1 -0
  49. package/dist/esm/plugins/index.js +2 -5
  50. package/dist/esm/plugins/links-tracking.js +6 -10
  51. package/dist/esm/plugins/{EmbeddedEntityBlock/ToolbarIcon.js → shared/EmbeddedBlockToolbarIcon.js} +14 -6
  52. package/dist/esm/plugins/shared/EmbeddedBlockUtil.js +144 -0
  53. package/dist/esm/plugins/shared/FetchingWrappedResourceCard.js +56 -0
  54. package/dist/esm/plugins/shared/LinkedBlockWrapper.js +30 -0
  55. package/dist/types/ContentfulEditorProvider.d.ts +2 -3
  56. package/dist/types/RichTextEditor.d.ts +2 -2
  57. package/dist/types/SyncEditorValue.d.ts +13 -0
  58. package/dist/types/dialogs/HypelinkDialog/HyperlinkDialog.d.ts +3 -3
  59. package/dist/types/helpers/callbacks.d.ts +3 -0
  60. package/dist/types/helpers/getAllowedResourcesForNodeType.d.ts +25 -0
  61. package/dist/types/helpers/newResourceEntitySelectorConfigFromRichTextField.d.ts +16 -0
  62. package/dist/types/helpers/toSlateValue.d.ts +7 -0
  63. package/dist/types/internal/hooks.d.ts +4 -2
  64. package/dist/types/internal/misc.d.ts +2 -2
  65. package/dist/types/plugins/EmbeddedEntityBlock/LinkedEntityBlock.d.ts +0 -1
  66. package/dist/types/plugins/EmbeddedEntityBlock/index.d.ts +1 -2
  67. package/dist/types/plugins/EmbeddedResourceBlock/LinkedResourceBlock.d.ts +18 -0
  68. package/dist/types/plugins/EmbeddedResourceBlock/index.d.ts +3 -0
  69. package/dist/types/plugins/links-tracking.d.ts +3 -3
  70. package/dist/types/plugins/shared/EmbeddedBlockToolbarIcon.d.ts +11 -0
  71. package/dist/types/plugins/shared/EmbeddedBlockUtil.d.ts +8 -0
  72. package/dist/types/plugins/shared/FetchingWrappedResourceCard.d.ts +14 -0
  73. package/dist/types/plugins/shared/LinkedBlockWrapper.d.ts +25 -0
  74. package/dist/types/test-utils/createEditor.d.ts +2 -0
  75. package/dist/types/test-utils/jsx.d.ts +1 -1
  76. package/package.json +18 -18
  77. package/dist/cjs/plugins/EmbeddedEntityBlock/Util.js +0 -108
  78. package/dist/cjs/prepareDocument.js +0 -86
  79. package/dist/cjs/useOnValueChanged.js +0 -58
  80. package/dist/esm/helpers/sanitizeIncomingSlateDoc.js +0 -23
  81. package/dist/esm/plugins/EmbeddedEntityBlock/Util.js +0 -85
  82. package/dist/esm/prepareDocument.js +0 -57
  83. package/dist/esm/useOnValueChanged.js +0 -43
  84. package/dist/types/helpers/sanitizeIncomingSlateDoc.d.ts +0 -6
  85. package/dist/types/plugins/EmbeddedEntityBlock/ToolbarIcon.d.ts +0 -11
  86. package/dist/types/plugins/EmbeddedEntityBlock/Util.d.ts +0 -4
  87. package/dist/types/prepareDocument.d.ts +0 -19
  88. package/dist/types/useOnValueChanged.d.ts +0 -8
@@ -1,45 +1,21 @@
1
1
  import { BLOCKS } from '@contentful/rich-text-types';
2
- import isHotkey from 'is-hotkey';
3
- import { getNodeEntryFromSelection } from '../../helpers/editor';
4
- import { removeNodes } from '../../internal/transforms';
5
- import { withLinkTracking } from '../links-tracking';
2
+ import { getWithEmbeddedBlockEvents } from '../shared/EmbeddedBlockUtil';
6
3
  import { LinkedEntityBlock } from './LinkedEntityBlock';
7
- import { selectEntityAndInsert } from './Util';
8
- export { EmbeddedEntityBlockToolbarIcon as ToolbarIcon } from './ToolbarIcon';
9
4
  const entityTypes = {
10
5
  [BLOCKS.EMBEDDED_ENTRY]: 'Entry',
11
6
  [BLOCKS.EMBEDDED_ASSET]: 'Asset'
12
7
  };
13
- function getWithEmbeddedEntityEvents(nodeType, sdk) {
14
- return (editor, { options: { hotkey } })=>(event)=>{
15
- const [, pathToSelectedElement] = getNodeEntryFromSelection(editor, nodeType);
16
- if (pathToSelectedElement) {
17
- const isDelete = event.key === 'Delete';
18
- const isBackspace = event.key === 'Backspace';
19
- if (isDelete || isBackspace) {
20
- event.preventDefault();
21
- removeNodes(editor, {
22
- at: pathToSelectedElement
23
- });
24
- }
25
- return;
26
- }
27
- if (hotkey && isHotkey(hotkey, event)) {
28
- selectEntityAndInsert(nodeType, sdk, editor, editor.tracking.onShortcutAction);
29
- }
30
- };
31
- }
32
8
  const createEmbeddedEntityPlugin = (nodeType, hotkey)=>(sdk)=>({
33
9
  key: nodeType,
34
10
  type: nodeType,
35
11
  isElement: true,
36
12
  isVoid: true,
37
- component: withLinkTracking(LinkedEntityBlock),
13
+ component: LinkedEntityBlock,
38
14
  options: {
39
15
  hotkey
40
16
  },
41
17
  handlers: {
42
- onKeyDown: getWithEmbeddedEntityEvents(nodeType, sdk)
18
+ onKeyDown: getWithEmbeddedBlockEvents(nodeType, sdk)
43
19
  },
44
20
  deserializeHtml: {
45
21
  rules: [
@@ -14,7 +14,7 @@ import { watchCurrentSlide } from '../../helpers/sdkNavigatorSlideIn';
14
14
  import { findNodePath } from '../../internal/queries';
15
15
  import { insertNodes, removeNodes, select } from '../../internal/transforms';
16
16
  import { useSdkContext } from '../../SdkProvider';
17
- import { withLinkTracking } from '../links-tracking';
17
+ import { useLinkTracking } from '../links-tracking';
18
18
  import { FetchingWrappedInlineEntryCard } from './FetchingWrappedInlineEntryCard';
19
19
  import { createInlineEntryNode } from './Util';
20
20
  const styles = {
@@ -36,6 +36,7 @@ function EmbeddedEntityInline(props) {
36
36
  const isSelected = useSelected();
37
37
  const { id: entryId } = props.element.data.target.sys;
38
38
  const isDisabled = useReadOnly();
39
+ const { onEntityFetchComplete } = useLinkTracking();
39
40
  function handleEditClick() {
40
41
  return sdk.navigator.openEntry(entryId, {
41
42
  slideIn: {
@@ -68,7 +69,7 @@ function EmbeddedEntityInline(props) {
68
69
  isDisabled: isDisabled,
69
70
  onRemove: handleRemoveClick,
70
71
  onEdit: handleEditClick,
71
- onEntityFetchComplete: props.onEntityFetchComplete
72
+ onEntityFetchComplete: onEntityFetchComplete
72
73
  })), props.children);
73
74
  }
74
75
  async function selectEntityAndInsert(editor, sdk, logAction) {
@@ -129,7 +130,7 @@ export function createEmbeddedEntityInlinePlugin(sdk) {
129
130
  isElement: true,
130
131
  isInline: true,
131
132
  isVoid: true,
132
- component: withLinkTracking(EmbeddedEntityInline),
133
+ component: EmbeddedEntityInline,
133
134
  options: {
134
135
  hotkey: 'mod+shift+2'
135
136
  },
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { useSelected, useReadOnly } from 'slate-react';
3
+ import { useContentfulEditor } from '../../ContentfulEditorProvider';
4
+ import { findNodePath, removeNodes } from '../../internal';
5
+ import { useSdkContext } from '../../SdkProvider';
6
+ import { useLinkTracking } from '../links-tracking';
7
+ import { FetchingWrappedResourceCard } from '../shared/FetchingWrappedResourceCard';
8
+ import { LinkedBlockWrapper } from '../shared/LinkedBlockWrapper';
9
+ export function LinkedResourceBlock(props) {
10
+ const { attributes , children , element } = props;
11
+ const { onEntityFetchComplete } = useLinkTracking();
12
+ const isSelected = useSelected();
13
+ const editor = useContentfulEditor();
14
+ const sdk = useSdkContext();
15
+ const isDisabled = useReadOnly();
16
+ const link = element.data.target.sys;
17
+ const handleRemoveClick = React.useCallback(()=>{
18
+ if (!editor) return;
19
+ const pathToElement = findNodePath(editor, element);
20
+ removeNodes(editor, {
21
+ at: pathToElement
22
+ });
23
+ }, [
24
+ editor,
25
+ element
26
+ ]);
27
+ return React.createElement(LinkedBlockWrapper, {
28
+ attributes: attributes,
29
+ element: element,
30
+ card: React.createElement(FetchingWrappedResourceCard, {
31
+ sdk: sdk,
32
+ link: link,
33
+ isDisabled: isDisabled,
34
+ isSelected: isSelected,
35
+ onRemove: handleRemoveClick,
36
+ onEntityFetchComplete: onEntityFetchComplete
37
+ })
38
+ }, children);
39
+ }
@@ -0,0 +1,45 @@
1
+ import { BLOCKS } from '@contentful/rich-text-types';
2
+ import { getWithEmbeddedBlockEvents } from '../shared/EmbeddedBlockUtil';
3
+ import { LinkedResourceBlock } from './LinkedResourceBlock';
4
+ const createEmbeddedResourcePlugin = (nodeType, hotkey)=>(sdk)=>({
5
+ key: nodeType,
6
+ type: nodeType,
7
+ isElement: true,
8
+ isVoid: true,
9
+ component: LinkedResourceBlock,
10
+ options: {
11
+ hotkey
12
+ },
13
+ handlers: {
14
+ onKeyDown: getWithEmbeddedBlockEvents(nodeType, sdk)
15
+ },
16
+ deserializeHtml: {
17
+ rules: [
18
+ {
19
+ validAttribute: {
20
+ 'data-entity-type': 'Contentful:Entry'
21
+ }
22
+ }
23
+ ],
24
+ withoutChildren: true,
25
+ getNode: (el)=>({
26
+ type: nodeType,
27
+ children: [
28
+ {
29
+ text: ''
30
+ }
31
+ ],
32
+ isVoid: true,
33
+ data: {
34
+ target: {
35
+ sys: {
36
+ urn: el.getAttribute('data-entity-id'),
37
+ linkType: el.getAttribute('data-entity-type'),
38
+ type: 'ResourceLink'
39
+ }
40
+ }
41
+ }
42
+ })
43
+ }
44
+ });
45
+ export const createEmbeddedResourceBlockPlugin = createEmbeddedResourcePlugin(BLOCKS.EMBEDDED_RESOURCE, 'mod+shift+s');
@@ -3,6 +3,7 @@ import { Tooltip, TextLink } from '@contentful/f36-components';
3
3
  import { useContentfulEditor } from '../../../ContentfulEditorProvider';
4
4
  import { fromDOMPoint } from '../../../internal';
5
5
  import { useSdkContext } from '../../../SdkProvider';
6
+ import { useLinkTracking } from '../../links-tracking';
6
7
  import { addOrEditLink } from '../HyperlinkModal';
7
8
  import { useEntityInfo } from '../useEntityInfo';
8
9
  import { styles } from './styles';
@@ -10,7 +11,7 @@ export function EntityHyperlink(props) {
10
11
  const editor = useContentfulEditor();
11
12
  const sdk = useSdkContext();
12
13
  const { target } = props.element.data;
13
- const { onEntityFetchComplete } = props;
14
+ const { onEntityFetchComplete } = useLinkTracking();
14
15
  const tooltipContent = useEntityInfo({
15
16
  target,
16
17
  sdk,
@@ -3,7 +3,6 @@ import { INLINES } from '@contentful/rich-text-types';
3
3
  import isHotkey from 'is-hotkey';
4
4
  import { isLinkActive, unwrapLink } from '../../helpers/editor';
5
5
  import { transformRemove } from '../../helpers/transformers';
6
- import { withLinkTracking } from '../links-tracking';
7
6
  import { EntityHyperlink } from './components/EntityHyperlink';
8
7
  import { UrlHyperlink } from './components/UrlHyperlink';
9
8
  import { addOrEditLink } from './HyperlinkModal';
@@ -77,7 +76,7 @@ export const createHyperlinkPlugin = (sdk)=>{
77
76
  ...common,
78
77
  key: INLINES.ENTRY_HYPERLINK,
79
78
  type: INLINES.ENTRY_HYPERLINK,
80
- component: withLinkTracking(EntityHyperlink),
79
+ component: EntityHyperlink,
81
80
  deserializeHtml: {
82
81
  rules: [
83
82
  {
@@ -94,7 +93,7 @@ export const createHyperlinkPlugin = (sdk)=>{
94
93
  ...common,
95
94
  key: INLINES.ASSET_HYPERLINK,
96
95
  type: INLINES.ASSET_HYPERLINK,
97
- component: withLinkTracking(EntityHyperlink),
96
+ component: EntityHyperlink,
98
97
  deserializeHtml: {
99
98
  rules: [
100
99
  {
@@ -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
  }
@@ -85,6 +85,7 @@ function deleteEmptyParagraph(unit, editor, deleteFunction) {
85
85
  allow: [
86
86
  BLOCKS.EMBEDDED_ASSET,
87
87
  BLOCKS.EMBEDDED_ENTRY,
88
+ BLOCKS.EMBEDDED_RESOURCE,
88
89
  BLOCKS.HR
89
90
  ]
90
91
  }),
@@ -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';
@@ -6,6 +5,7 @@ import { isCommandPromptPluginEnabled } from './CommandPalette/useCommands';
6
5
  import { createDragAndDropPlugin } from './DragAndDrop';
7
6
  import { createEmbeddedAssetBlockPlugin, createEmbeddedEntryBlockPlugin } from './EmbeddedEntityBlock';
8
7
  import { createEmbeddedEntityInlinePlugin } from './EmbeddedEntityInline';
8
+ import { createEmbeddedResourceBlockPlugin } from './EmbeddedResourceBlock';
9
9
  import { createHeadingPlugin } from './Heading';
10
10
  import { createHrPlugin } from './Hr';
11
11
  import { createHyperlinkPlugin } from './Hyperlink';
@@ -22,8 +22,6 @@ import { createTrackingPlugin } from './Tracking';
22
22
  import { createTrailingParagraphPlugin } from './TrailingParagraph';
23
23
  import { createVoidsPlugin } from './Voids';
24
24
  export const getPlugins = (sdk, onAction, restrictedMarks)=>[
25
- createDeserializeHtmlPlugin(),
26
- createDeserializeAstPlugin(),
27
25
  createDeserializeDocxPlugin(),
28
26
  createTrackingPlugin(onAction),
29
27
  createDragAndDropPlugin(),
@@ -38,6 +36,7 @@ export const getPlugins = (sdk, onAction, restrictedMarks)=>[
38
36
  createTablePlugin(),
39
37
  createEmbeddedEntryBlockPlugin(sdk),
40
38
  createEmbeddedAssetBlockPlugin(sdk),
39
+ createEmbeddedResourceBlockPlugin(sdk),
41
40
  createHyperlinkPlugin(sdk),
42
41
  createEmbeddedEntityInlinePlugin(sdk),
43
42
  createMarksPlugin(),
@@ -52,7 +51,5 @@ export const getPlugins = (sdk, onAction, restrictedMarks)=>[
52
51
  createNormalizerPlugin()
53
52
  ];
54
53
  export const disableCorePlugins = {
55
- deserializeAst: true,
56
- deserializeHtml: true,
57
54
  eventEditor: true
58
55
  };
@@ -1,14 +1,10 @@
1
- import React from 'react';
1
+ import { useCallback } from 'react';
2
2
  import { useContentfulEditorRef } from '../ContentfulEditorProvider';
3
- export function withLinkTracking(Component) {
4
- return function ComponentWithTracking(props) {
5
- const editor = useContentfulEditorRef();
6
- const onEntityFetchComplete = React.useCallback(()=>editor?.tracking.onViewportAction('linkRendered'), [
3
+ export function useLinkTracking() {
4
+ const editor = useContentfulEditorRef();
5
+ return {
6
+ onEntityFetchComplete: useCallback(()=>editor?.tracking.onViewportAction('linkRendered'), [
7
7
  editor
8
- ]);
9
- return React.createElement(Component, {
10
- ...props,
11
- onEntityFetchComplete: onEntityFetchComplete
12
- });
8
+ ])
13
9
  };
14
10
  }
@@ -1,16 +1,17 @@
1
1
  import * as React from 'react';
2
- import { Flex, Icon, Menu } from '@contentful/f36-components';
2
+ import { Badge, Flex, Icon, Menu } from '@contentful/f36-components';
3
3
  import { AssetIcon, EmbeddedEntryBlockIcon } from '@contentful/f36-icons';
4
+ import { BLOCKS } from '@contentful/rich-text-types';
4
5
  import { css } from 'emotion';
5
6
  import { useContentfulEditor } from '../../ContentfulEditorProvider';
6
7
  import { useSdkContext } from '../../SdkProvider';
7
- import { selectEntityAndInsert } from './Util';
8
+ import { selectEntityAndInsert, selectResourceEntityAndInsert } from '../shared/EmbeddedBlockUtil';
8
9
  export const styles = {
9
10
  icon: css({
10
11
  marginRight: '10px'
11
12
  })
12
13
  };
13
- export function EmbeddedEntityBlockToolbarIcon({ isDisabled , nodeType , onClose }) {
14
+ export function EmbeddedBlockToolbarIcon({ isDisabled , nodeType , onClose }) {
14
15
  const editor = useContentfulEditor();
15
16
  const sdk = useSdkContext();
16
17
  const handleClick = async (event)=>{
@@ -19,7 +20,11 @@ export function EmbeddedEntityBlockToolbarIcon({ isDisabled , nodeType , onClose
19
20
  return;
20
21
  }
21
22
  onClose();
22
- await selectEntityAndInsert(nodeType, sdk, editor, editor.tracking.onToolbarAction);
23
+ if (nodeType == BLOCKS.EMBEDDED_RESOURCE) {
24
+ await selectResourceEntityAndInsert(sdk, editor, editor.tracking.onToolbarAction);
25
+ } else {
26
+ await selectEntityAndInsert(nodeType, sdk, editor, editor.tracking.onToolbarAction);
27
+ }
23
28
  };
24
29
  const type = getEntityTypeFromNodeType(nodeType);
25
30
  const baseClass = `rich-text__${nodeType}`;
@@ -35,11 +40,14 @@ export function EmbeddedEntityBlockToolbarIcon({ isDisabled , nodeType , onClose
35
40
  as: type === 'Asset' ? AssetIcon : EmbeddedEntryBlockIcon,
36
41
  className: `rich-text__embedded-entry-list-icon ${styles.icon}`,
37
42
  variant: "secondary"
38
- }), React.createElement("span", null, type)));
43
+ }), React.createElement("span", null, type, nodeType == BLOCKS.EMBEDDED_RESOURCE && React.createElement(React.Fragment, null, ' ', "(different space)", ' ', React.createElement(Badge, {
44
+ variant: "primary-filled",
45
+ size: "small"
46
+ }, "new")))));
39
47
  }
40
48
  function getEntityTypeFromNodeType(nodeType) {
41
49
  const words = nodeType.toLowerCase().split('-');
42
- if (words.includes('entry')) {
50
+ if (words.includes('entry') || words.includes('resource')) {
43
51
  return 'Entry';
44
52
  }
45
53
  if (words.includes('asset')) {
@@ -0,0 +1,144 @@
1
+ import { BLOCKS, TEXT_CONTAINERS } from '@contentful/rich-text-types';
2
+ import isHotkey from 'is-hotkey';
3
+ import { focus, getNodeEntryFromSelection, insertEmptyParagraph, moveToTheNextChar } from '../../helpers/editor';
4
+ import newEntitySelectorConfigFromRichTextField from '../../helpers/newEntitySelectorConfigFromRichTextField';
5
+ import newResourceEntitySelectorConfigFromRichTextField from '../../helpers/newResourceEntitySelectorConfigFromRichTextField';
6
+ import { watchCurrentSlide } from '../../helpers/sdkNavigatorSlideIn';
7
+ import { getText, getAboveNode, getLastNodeByLevel, insertNodes, setNodes, select, removeNodes } from '../../internal';
8
+ export function getWithEmbeddedBlockEvents(nodeType, sdk) {
9
+ return (editor, { options: { hotkey } })=>(event)=>{
10
+ const [, pathToSelectedElement] = getNodeEntryFromSelection(editor, nodeType);
11
+ if (pathToSelectedElement) {
12
+ const isDelete = event.key === 'Delete';
13
+ const isBackspace = event.key === 'Backspace';
14
+ if (isDelete || isBackspace) {
15
+ event.preventDefault();
16
+ removeNodes(editor, {
17
+ at: pathToSelectedElement
18
+ });
19
+ }
20
+ return;
21
+ }
22
+ if (hotkey && isHotkey(hotkey, event)) {
23
+ if (nodeType === BLOCKS.EMBEDDED_RESOURCE) {
24
+ selectResourceEntityAndInsert(sdk, editor, editor.tracking.onShortcutAction);
25
+ } else {
26
+ selectEntityAndInsert(nodeType, sdk, editor, editor.tracking.onShortcutAction);
27
+ }
28
+ }
29
+ };
30
+ }
31
+ export async function selectEntityAndInsert(nodeType, sdk, editor, logAction) {
32
+ logAction('openCreateEmbedDialog', {
33
+ nodeType
34
+ });
35
+ const { field , dialogs } = sdk;
36
+ const baseConfig = newEntitySelectorConfigFromRichTextField(field, nodeType);
37
+ const selectEntity = baseConfig.entityType === 'Asset' ? dialogs.selectSingleAsset : dialogs.selectSingleEntry;
38
+ const config = {
39
+ ...baseConfig,
40
+ withCreate: true
41
+ };
42
+ const { selection } = editor;
43
+ const rteSlide = watchCurrentSlide(sdk.navigator);
44
+ const entity = await selectEntity(config);
45
+ if (!entity) {
46
+ logAction('cancelCreateEmbedDialog', {
47
+ nodeType
48
+ });
49
+ } else {
50
+ select(editor, selection);
51
+ insertBlock(editor, nodeType, entity);
52
+ ensureFollowingParagraph(editor, [
53
+ BLOCKS.EMBEDDED_ASSET,
54
+ BLOCKS.EMBEDDED_ENTRY
55
+ ]);
56
+ logAction('insert', {
57
+ nodeType
58
+ });
59
+ }
60
+ rteSlide.onActive(()=>{
61
+ rteSlide.unwatch();
62
+ focus(editor);
63
+ });
64
+ }
65
+ export async function selectResourceEntityAndInsert(sdk, editor, logAction) {
66
+ logAction('openCreateEmbedDialog', {
67
+ nodeType: BLOCKS.EMBEDDED_RESOURCE
68
+ });
69
+ const { field , dialogs } = sdk;
70
+ const config = newResourceEntitySelectorConfigFromRichTextField(field, BLOCKS.EMBEDDED_RESOURCE);
71
+ const { selection } = editor;
72
+ const entity = await dialogs.selectSingleResourceEntry(config);
73
+ if (!entity) {
74
+ logAction('cancelCreateEmbedDialog', {
75
+ nodeType: BLOCKS.EMBEDDED_RESOURCE
76
+ });
77
+ } else {
78
+ select(editor, selection);
79
+ insertBlock(editor, BLOCKS.EMBEDDED_RESOURCE, entity);
80
+ ensureFollowingParagraph(editor, [
81
+ BLOCKS.EMBEDDED_RESOURCE
82
+ ]);
83
+ logAction('insert', {
84
+ nodeType: BLOCKS.EMBEDDED_RESOURCE
85
+ });
86
+ }
87
+ }
88
+ function ensureFollowingParagraph(editor, nodeTypes) {
89
+ const entityBlock = getAboveNode(editor, {
90
+ match: {
91
+ type: nodeTypes
92
+ }
93
+ });
94
+ if (!entityBlock) {
95
+ return;
96
+ }
97
+ const level = entityBlock[1].length - 1;
98
+ const lastNode = getLastNodeByLevel(editor, level);
99
+ const isTextContainer = TEXT_CONTAINERS.includes(lastNode?.[0].type ?? '');
100
+ if (level !== 0 && !isTextContainer) {
101
+ insertEmptyParagraph(editor);
102
+ }
103
+ moveToTheNextChar(editor);
104
+ }
105
+ const getLink = (nodeType, entity)=>{
106
+ if (nodeType === BLOCKS.EMBEDDED_RESOURCE) {
107
+ return {
108
+ urn: entity.sys.urn,
109
+ type: 'ResourceLink',
110
+ linkType: 'Contentful:Entry'
111
+ };
112
+ }
113
+ return {
114
+ id: entity.sys.id,
115
+ type: 'Link',
116
+ linkType: entity.sys.type
117
+ };
118
+ };
119
+ const createNode = (nodeType, entity)=>{
120
+ return {
121
+ type: nodeType,
122
+ data: {
123
+ target: {
124
+ sys: getLink(nodeType, entity)
125
+ }
126
+ },
127
+ children: [
128
+ {
129
+ text: ''
130
+ }
131
+ ],
132
+ isVoid: true
133
+ };
134
+ };
135
+ function insertBlock(editor, nodeType, entity) {
136
+ if (!editor?.selection) return;
137
+ const linkedEntityBlock = createNode(nodeType, entity);
138
+ const hasText = editor.selection && !!getText(editor, editor.selection.focus.path);
139
+ if (hasText) {
140
+ insertNodes(editor, linkedEntityBlock);
141
+ } else {
142
+ setNodes(editor, linkedEntityBlock);
143
+ }
144
+ }
@@ -0,0 +1,56 @@
1
+ import * as React from 'react';
2
+ import { EntryCard } from '@contentful/f36-components';
3
+ import { MissingEntityCard, WrappedEntryCard, useResource } from '@contentful/field-editor-reference';
4
+ import areEqual from 'fast-deep-equal';
5
+ const InternalEntryCard = React.memo((props)=>{
6
+ if (props.status === 'loading') {
7
+ return React.createElement(EntryCard, {
8
+ isLoading: true
9
+ });
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
+ const { contentType , resource: entry , space } = props.data;
19
+ return React.createElement(WrappedEntryCard, {
20
+ size: "default",
21
+ getAsset: ()=>Promise.resolve(),
22
+ isSelected: props.isSelected,
23
+ isDisabled: props.isDisabled,
24
+ localeCode: props.data.defaultLocaleCode,
25
+ defaultLocaleCode: props.data.defaultLocaleCode,
26
+ contentType: contentType,
27
+ spaceName: space?.name,
28
+ entry: entry,
29
+ onEdit: props.onEdit,
30
+ onRemove: props.isDisabled ? undefined : props.onRemove,
31
+ isClickable: false,
32
+ getEntityScheduledActions: ()=>Promise.resolve([])
33
+ });
34
+ }, areEqual);
35
+ InternalEntryCard.displayName = 'ReferenceCard';
36
+ export const FetchingWrappedResourceCard = (props)=>{
37
+ const { link , onEntityFetchComplete } = props;
38
+ const { data , status } = useResource(link.linkType, link.urn);
39
+ React.useEffect(()=>{
40
+ if (status === 'success') {
41
+ onEntityFetchComplete?.();
42
+ }
43
+ }, [
44
+ onEntityFetchComplete,
45
+ status
46
+ ]);
47
+ return React.createElement(InternalEntryCard, {
48
+ data: data,
49
+ status: status,
50
+ sdk: props.sdk,
51
+ isDisabled: props.isDisabled,
52
+ isSelected: props.isSelected,
53
+ onEdit: props.onEdit,
54
+ onRemove: props.onRemove
55
+ });
56
+ };
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ import { css } from 'emotion';
3
+ import { IS_CHROME } from '../../helpers/environment';
4
+ const styles = {
5
+ root: css({
6
+ marginBottom: '1.25rem !important',
7
+ display: 'block'
8
+ }),
9
+ container: css({
10
+ display: 'inline-block',
11
+ verticalAlign: 'text-top',
12
+ width: '100%'
13
+ })
14
+ };
15
+ const isResourceLink = (link)=>!!link.urn;
16
+ export function LinkedBlockWrapper({ attributes , card , children , element }) {
17
+ const link = element.data.target.sys;
18
+ return React.createElement("div", {
19
+ ...attributes,
20
+ className: styles.root,
21
+ "data-entity-type": link.linkType,
22
+ "data-entity-id": isResourceLink(link) ? link.urn : link.id,
23
+ contentEditable: IS_CHROME ? undefined : false,
24
+ draggable: IS_CHROME ? true : undefined
25
+ }, React.createElement("div", {
26
+ contentEditable: IS_CHROME ? false : undefined,
27
+ draggable: IS_CHROME ? true : undefined,
28
+ className: styles.container
29
+ }, card), children);
30
+ }
@@ -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;