@contentful/field-editor-rich-text 3.12.6 → 3.13.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 (67) hide show
  1. package/dist/cjs/Toolbar/components/EmbedEntityWidget.js +8 -2
  2. package/dist/cjs/constants/Schema.js +3 -0
  3. package/dist/cjs/helpers/{newEntitySelectorConfigFromRichTextField.js → config.js} +19 -5
  4. package/dist/cjs/plugins/DragAndDrop/index.js +4 -2
  5. package/dist/cjs/plugins/EmbeddedEntityBlock/LinkedEntityBlock.js +1 -1
  6. package/dist/cjs/plugins/EmbeddedEntityInline/LinkedEntityInline.js +97 -0
  7. package/dist/cjs/plugins/EmbeddedEntityInline/index.js +27 -193
  8. package/dist/cjs/plugins/{shared/FetchingWrappedResourceCard.js → EmbeddedResourceBlock/FetchingWrappedResourceBlockCard.js} +3 -3
  9. package/dist/cjs/plugins/EmbeddedResourceBlock/LinkedResourceBlock.js +3 -3
  10. package/dist/cjs/plugins/EmbeddedResourceInline/FetchingWrappedResourceInlineCard.js +102 -0
  11. package/dist/cjs/plugins/EmbeddedResourceInline/LinkedResourceInline.js +51 -0
  12. package/dist/cjs/plugins/EmbeddedResourceInline/index.js +56 -0
  13. package/dist/cjs/plugins/index.js +2 -0
  14. package/dist/cjs/plugins/shared/EmbeddedBlockToolbarIcon.js +2 -4
  15. package/dist/cjs/plugins/shared/EmbeddedBlockUtil.js +3 -4
  16. package/dist/cjs/plugins/shared/EmbeddedInlineToolbarIcon.js +105 -0
  17. package/dist/cjs/plugins/shared/EmbeddedInlineUtil.js +127 -0
  18. package/dist/cjs/plugins/shared/LinkedBlockWrapper.js +4 -5
  19. package/dist/cjs/plugins/shared/LinkedInlineWrapper.js +85 -0
  20. package/dist/cjs/plugins/shared/ResourceNewBadge.js +57 -0
  21. package/dist/cjs/plugins/shared/__tests__/FetchingWrappedResourceCard.test.js +2 -2
  22. package/dist/cjs/plugins/shared/utils.js +12 -0
  23. package/dist/esm/Toolbar/components/EmbedEntityWidget.js +8 -2
  24. package/dist/esm/constants/Schema.js +3 -0
  25. package/dist/esm/helpers/{newEntitySelectorConfigFromRichTextField.js → config.js} +8 -2
  26. package/dist/esm/plugins/DragAndDrop/index.js +4 -2
  27. package/dist/esm/plugins/EmbeddedEntityBlock/LinkedEntityBlock.js +1 -1
  28. package/dist/esm/plugins/EmbeddedEntityInline/LinkedEntityInline.js +48 -0
  29. package/dist/esm/plugins/EmbeddedEntityInline/index.js +24 -138
  30. package/dist/esm/plugins/{shared/FetchingWrappedResourceCard.js → EmbeddedResourceBlock/FetchingWrappedResourceBlockCard.js} +1 -1
  31. package/dist/esm/plugins/EmbeddedResourceBlock/LinkedResourceBlock.js +3 -3
  32. package/dist/esm/plugins/EmbeddedResourceInline/FetchingWrappedResourceInlineCard.js +53 -0
  33. package/dist/esm/plugins/EmbeddedResourceInline/LinkedResourceInline.js +36 -0
  34. package/dist/esm/plugins/EmbeddedResourceInline/index.js +46 -0
  35. package/dist/esm/plugins/index.js +2 -0
  36. package/dist/esm/plugins/shared/EmbeddedBlockToolbarIcon.js +3 -5
  37. package/dist/esm/plugins/shared/EmbeddedBlockUtil.js +1 -2
  38. package/dist/esm/plugins/shared/EmbeddedInlineToolbarIcon.js +51 -0
  39. package/dist/esm/plugins/shared/EmbeddedInlineUtil.js +101 -0
  40. package/dist/esm/plugins/shared/LinkedBlockWrapper.js +4 -5
  41. package/dist/esm/plugins/shared/LinkedInlineWrapper.js +31 -0
  42. package/dist/esm/plugins/shared/ResourceNewBadge.js +8 -0
  43. package/dist/esm/plugins/shared/__tests__/FetchingWrappedResourceCard.test.js +2 -2
  44. package/dist/esm/plugins/shared/utils.js +2 -0
  45. package/dist/types/constants/Schema.d.ts +3 -0
  46. package/dist/types/helpers/config.d.ts +33 -0
  47. package/dist/types/plugins/EmbeddedEntityBlock/LinkedEntityBlock.d.ts +2 -7
  48. package/dist/types/plugins/EmbeddedEntityInline/LinkedEntityInline.d.ts +14 -0
  49. package/dist/types/plugins/EmbeddedEntityInline/index.d.ts +0 -7
  50. package/dist/types/plugins/{shared/FetchingWrappedResourceCard.d.ts → EmbeddedResourceBlock/FetchingWrappedResourceBlockCard.d.ts} +2 -2
  51. package/dist/types/plugins/EmbeddedResourceInline/FetchingWrappedResourceInlineCard.d.ts +13 -0
  52. package/dist/types/plugins/EmbeddedResourceInline/LinkedResourceInline.d.ts +13 -0
  53. package/dist/types/plugins/EmbeddedResourceInline/index.d.ts +3 -0
  54. package/dist/types/plugins/shared/EmbeddedInlineToolbarIcon.d.ts +8 -0
  55. package/dist/types/plugins/shared/EmbeddedInlineUtil.d.ts +8 -0
  56. package/dist/types/plugins/shared/LinkedBlockWrapper.d.ts +4 -19
  57. package/dist/types/plugins/shared/LinkedInlineWrapper.d.ts +10 -0
  58. package/dist/types/plugins/shared/ResourceNewBadge.d.ts +2 -0
  59. package/dist/types/plugins/shared/utils.d.ts +2 -0
  60. package/package.json +2 -2
  61. package/dist/cjs/helpers/newResourceEntitySelectorConfigFromRichTextField.js +0 -21
  62. package/dist/cjs/plugins/EmbeddedEntityInline/Util.js +0 -30
  63. package/dist/esm/helpers/newResourceEntitySelectorConfigFromRichTextField.js +0 -6
  64. package/dist/esm/plugins/EmbeddedEntityInline/Util.js +0 -20
  65. package/dist/types/helpers/newEntitySelectorConfigFromRichTextField.d.ts +0 -14
  66. package/dist/types/helpers/newResourceEntitySelectorConfigFromRichTextField.d.ts +0 -16
  67. package/dist/types/plugins/EmbeddedEntityInline/Util.d.ts +0 -16
@@ -0,0 +1,48 @@
1
+ import * as React from 'react';
2
+ import { useReadOnly, useSelected } from 'slate-react';
3
+ import { useContentfulEditor } from '../../ContentfulEditorProvider';
4
+ import { focus } from '../../helpers/editor';
5
+ import { findNodePath } from '../../internal/queries';
6
+ import { removeNodes } from '../../internal/transforms';
7
+ import { useSdkContext } from '../../SdkProvider';
8
+ import { useLinkTracking } from '../links-tracking';
9
+ import { LinkedInlineWrapper } from '../shared/LinkedInlineWrapper';
10
+ import { FetchingWrappedInlineEntryCard } from './FetchingWrappedInlineEntryCard';
11
+ export function LinkedEntityInline(props) {
12
+ const { attributes , children , element } = props;
13
+ const { onEntityFetchComplete } = useLinkTracking();
14
+ const isSelected = useSelected();
15
+ const editor = useContentfulEditor();
16
+ const sdk = useSdkContext();
17
+ const isDisabled = useReadOnly();
18
+ const { id: entryId } = element.data.target.sys;
19
+ function handleEditClick() {
20
+ return sdk.navigator.openEntry(entryId, {
21
+ slideIn: {
22
+ waitForClose: true
23
+ }
24
+ }).then(()=>{
25
+ editor && focus(editor);
26
+ });
27
+ }
28
+ function handleRemoveClick() {
29
+ if (!editor) return;
30
+ const pathToElement = findNodePath(editor, element);
31
+ removeNodes(editor, {
32
+ at: pathToElement
33
+ });
34
+ }
35
+ return React.createElement(LinkedInlineWrapper, {
36
+ attributes: attributes,
37
+ card: React.createElement(FetchingWrappedInlineEntryCard, {
38
+ sdk: sdk,
39
+ entryId: entryId,
40
+ isSelected: isSelected,
41
+ isDisabled: isDisabled,
42
+ onRemove: handleRemoveClick,
43
+ onEdit: handleEditClick,
44
+ onEntityFetchComplete: onEntityFetchComplete
45
+ }),
46
+ link: element.data.target
47
+ }, children);
48
+ }
@@ -1,141 +1,21 @@
1
- import * as React from 'react';
2
- import { Menu, Flex } from '@contentful/f36-components';
3
- import { EmbeddedEntryInlineIcon } from '@contentful/f36-icons';
4
- import tokens from '@contentful/f36-tokens';
5
1
  import { INLINES } from '@contentful/rich-text-types';
6
- import { css } from 'emotion';
7
- import isHotkey from 'is-hotkey';
8
- import { useSelected, useReadOnly } from 'slate-react';
9
- import { useContentfulEditor } from '../../ContentfulEditorProvider';
10
- import { focus, moveToTheNextChar } from '../../helpers/editor';
11
- import { IS_CHROME } from '../../helpers/environment';
12
- import newEntitySelectorConfigFromRichTextField from '../../helpers/newEntitySelectorConfigFromRichTextField';
13
- import { watchCurrentSlide } from '../../helpers/sdkNavigatorSlideIn';
14
- import { findNodePath } from '../../internal/queries';
15
- import { insertNodes, removeNodes, select } from '../../internal/transforms';
16
- import { useSdkContext } from '../../SdkProvider';
17
- import { useLinkTracking } from '../links-tracking';
18
- import { FetchingWrappedInlineEntryCard } from './FetchingWrappedInlineEntryCard';
19
- import { createInlineEntryNode } from './Util';
20
- const styles = {
21
- icon: css({
22
- marginRight: '10px'
23
- }),
24
- root: css({
25
- display: 'inline-block',
26
- margin: `0 ${tokens.spacing2Xs}`,
27
- fontSize: 'inherit',
28
- span: {
29
- userSelect: 'none'
30
- }
31
- })
32
- };
33
- function EmbeddedEntityInline(props) {
34
- const editor = useContentfulEditor();
35
- const sdk = useSdkContext();
36
- const isSelected = useSelected();
37
- const { id: entryId } = props.element.data.target.sys;
38
- const isDisabled = useReadOnly();
39
- const { onEntityFetchComplete } = useLinkTracking();
40
- function handleEditClick() {
41
- return sdk.navigator.openEntry(entryId, {
42
- slideIn: {
43
- waitForClose: true
44
- }
45
- }).then(()=>{
46
- editor && focus(editor);
47
- });
48
- }
49
- function handleRemoveClick() {
50
- if (!editor) return;
51
- const pathToElement = findNodePath(editor, props.element);
52
- removeNodes(editor, {
53
- at: pathToElement
54
- });
55
- }
56
- return React.createElement("span", {
57
- ...props.attributes,
58
- className: styles.root,
59
- "data-embedded-entity-inline-id": entryId,
60
- contentEditable: IS_CHROME ? undefined : false,
61
- draggable: IS_CHROME ? true : undefined
62
- }, React.createElement("span", {
63
- contentEditable: IS_CHROME ? false : undefined,
64
- draggable: IS_CHROME ? true : undefined
65
- }, React.createElement(FetchingWrappedInlineEntryCard, {
66
- sdk: sdk,
67
- entryId: entryId,
68
- isSelected: isSelected,
69
- isDisabled: isDisabled,
70
- onRemove: handleRemoveClick,
71
- onEdit: handleEditClick,
72
- onEntityFetchComplete: onEntityFetchComplete
73
- })), props.children);
74
- }
75
- async function selectEntityAndInsert(editor, sdk, logAction) {
76
- logAction('openCreateEmbedDialog', {
77
- nodeType: INLINES.EMBEDDED_ENTRY
78
- });
79
- const config = {
80
- ...newEntitySelectorConfigFromRichTextField(sdk.field, INLINES.EMBEDDED_ENTRY),
81
- withCreate: true
82
- };
83
- const { selection } = editor;
84
- const rteSlide = watchCurrentSlide(sdk.navigator);
85
- const entry = await sdk.dialogs.selectSingleEntry(config);
86
- if (!entry) {
87
- logAction('cancelCreateEmbedDialog', {
88
- nodeType: INLINES.EMBEDDED_ENTRY
89
- });
90
- } else {
91
- select(editor, selection);
92
- insertNodes(editor, createInlineEntryNode(entry.sys.id));
93
- logAction('insert', {
94
- nodeType: INLINES.EMBEDDED_ENTRY
95
- });
96
- }
97
- rteSlide.onActive(()=>{
98
- rteSlide.unwatch();
99
- focus(editor);
100
- });
101
- }
102
- export function ToolbarEmbeddedEntityInlineButton(props) {
103
- const editor = useContentfulEditor();
104
- const sdk = useSdkContext();
105
- async function handleClick(event) {
106
- event.preventDefault();
107
- if (!editor) return;
108
- props.onClose();
109
- await selectEntityAndInsert(editor, sdk, editor.tracking.onToolbarAction);
110
- moveToTheNextChar(editor);
111
- }
112
- return React.createElement(Menu.Item, {
113
- disabled: props.isDisabled,
114
- className: "rich-text__entry-link-block-button",
115
- testId: `toolbar-toggle-${INLINES.EMBEDDED_ENTRY}`,
116
- onClick: handleClick
117
- }, React.createElement(Flex, {
118
- alignItems: "center",
119
- flexDirection: "row"
120
- }, React.createElement(EmbeddedEntryInlineIcon, {
121
- variant: "secondary",
122
- className: `rich-text__embedded-entry-list-icon ${styles.icon}`
123
- }), React.createElement("span", null, "Inline entry")));
124
- }
2
+ import { getWithEmbeddedEntryInlineEvents } from '../shared/EmbeddedInlineUtil';
3
+ import { LinkedEntityInline } from './LinkedEntityInline';
125
4
  export function createEmbeddedEntityInlinePlugin(sdk) {
126
5
  const htmlAttributeName = 'data-embedded-entity-inline-id';
6
+ const nodeType = INLINES.EMBEDDED_ENTRY;
127
7
  return {
128
- key: INLINES.EMBEDDED_ENTRY,
129
- type: INLINES.EMBEDDED_ENTRY,
8
+ key: nodeType,
9
+ type: nodeType,
130
10
  isElement: true,
131
11
  isInline: true,
132
12
  isVoid: true,
133
- component: EmbeddedEntityInline,
13
+ component: LinkedEntityInline,
134
14
  options: {
135
15
  hotkey: 'mod+shift+2'
136
16
  },
137
17
  handlers: {
138
- onKeyDown: getWithEmbeddedEntryInlineEvents(sdk)
18
+ onKeyDown: getWithEmbeddedEntryInlineEvents(nodeType, sdk)
139
19
  },
140
20
  deserializeHtml: {
141
21
  rules: [
@@ -144,17 +24,23 @@ export function createEmbeddedEntityInlinePlugin(sdk) {
144
24
  }
145
25
  ],
146
26
  withoutChildren: true,
147
- getNode: (el)=>createInlineEntryNode(el.getAttribute(htmlAttributeName))
27
+ getNode: (el)=>({
28
+ type: nodeType,
29
+ children: [
30
+ {
31
+ text: ''
32
+ }
33
+ ],
34
+ data: {
35
+ target: {
36
+ sys: {
37
+ id: el.getAttribute('data-entity-id'),
38
+ type: 'Link',
39
+ linkType: el.getAttribute('data-entity-type')
40
+ }
41
+ }
42
+ }
43
+ })
148
44
  }
149
45
  };
150
46
  }
151
- function getWithEmbeddedEntryInlineEvents(sdk) {
152
- return function withEmbeddedEntryInlineEvents(editor, { options: { hotkey } }) {
153
- return function handleEvent(event) {
154
- if (!editor) return;
155
- if (hotkey && isHotkey(hotkey, event)) {
156
- selectEntityAndInsert(editor, sdk, editor.tracking.onShortcutAction);
157
- }
158
- };
159
- };
160
- }
@@ -26,7 +26,7 @@ const InternalEntryCard = React.memo((props)=>{
26
26
  });
27
27
  }, areEqual);
28
28
  InternalEntryCard.displayName = 'ReferenceCard';
29
- export const FetchingWrappedResourceCard = (props)=>{
29
+ export const FetchingWrappedResourceBlockCard = (props)=>{
30
30
  const { link , onEntityFetchComplete } = props;
31
31
  const { data , status , error } = useResource(link.linkType, link.urn);
32
32
  React.useEffect(()=>{
@@ -4,8 +4,8 @@ import { useContentfulEditor } from '../../ContentfulEditorProvider';
4
4
  import { findNodePath, removeNodes } from '../../internal';
5
5
  import { useSdkContext } from '../../SdkProvider';
6
6
  import { useLinkTracking } from '../links-tracking';
7
- import { FetchingWrappedResourceCard } from '../shared/FetchingWrappedResourceCard';
8
7
  import { LinkedBlockWrapper } from '../shared/LinkedBlockWrapper';
8
+ import { FetchingWrappedResourceBlockCard } from './FetchingWrappedResourceBlockCard';
9
9
  export function LinkedResourceBlock(props) {
10
10
  const { attributes , children , element } = props;
11
11
  const { onEntityFetchComplete } = useLinkTracking();
@@ -26,8 +26,8 @@ export function LinkedResourceBlock(props) {
26
26
  ]);
27
27
  return React.createElement(LinkedBlockWrapper, {
28
28
  attributes: attributes,
29
- element: element,
30
- card: React.createElement(FetchingWrappedResourceCard, {
29
+ link: element.data.target,
30
+ card: React.createElement(FetchingWrappedResourceBlockCard, {
31
31
  sdk: sdk,
32
32
  link: link,
33
33
  isDisabled: isDisabled,
@@ -0,0 +1,53 @@
1
+ import * as React from 'react';
2
+ import { InlineEntryCard, MenuItem, Text } from '@contentful/f36-components';
3
+ import { useResource } from '@contentful/field-editor-reference';
4
+ import { entityHelpers } from '@contentful/field-editor-shared';
5
+ import { INLINES } from '@contentful/rich-text-types';
6
+ const { getEntryTitle , getEntryStatus } = entityHelpers;
7
+ export function FetchingWrappedResourceInlineCard(props) {
8
+ const { link , onEntityFetchComplete } = props;
9
+ const { data , status: requestStatus } = useResource(link.linkType, link.urn);
10
+ React.useEffect(()=>{
11
+ if (requestStatus === 'success') {
12
+ onEntityFetchComplete?.();
13
+ }
14
+ }, [
15
+ onEntityFetchComplete,
16
+ requestStatus
17
+ ]);
18
+ if (requestStatus === 'error') {
19
+ return React.createElement(InlineEntryCard, {
20
+ title: "Entry missing or inaccessible",
21
+ testId: INLINES.EMBEDDED_RESOURCE,
22
+ isSelected: props.isSelected
23
+ });
24
+ }
25
+ if (requestStatus === 'loading' || data === undefined) {
26
+ return React.createElement(InlineEntryCard, {
27
+ isLoading: true
28
+ });
29
+ }
30
+ const { resource: entry , contentType , defaultLocaleCode , space } = data;
31
+ const title = getEntryTitle({
32
+ entry,
33
+ contentType,
34
+ defaultLocaleCode,
35
+ localeCode: defaultLocaleCode,
36
+ defaultTitle: 'Untitled'
37
+ });
38
+ const status = getEntryStatus(entry?.sys);
39
+ return React.createElement(InlineEntryCard, {
40
+ testId: INLINES.EMBEDDED_RESOURCE,
41
+ isSelected: props.isSelected,
42
+ title: `${data.contentType.name}: ${title} (Space: ${space.name})`,
43
+ status: status,
44
+ actions: [
45
+ React.createElement(MenuItem, {
46
+ key: "remove",
47
+ onClick: props.onRemove,
48
+ disabled: props.isDisabled,
49
+ testId: "delete"
50
+ }, "Remove")
51
+ ]
52
+ }, React.createElement(Text, null, title));
53
+ }
@@ -0,0 +1,36 @@
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 { LinkedInlineWrapper } from '../shared/LinkedInlineWrapper';
8
+ import { FetchingWrappedResourceInlineCard } from './FetchingWrappedResourceInlineCard';
9
+ export function LinkedResourceInline(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
+ function handleRemoveClick() {
18
+ if (!editor) return;
19
+ const pathToElement = findNodePath(editor, element);
20
+ removeNodes(editor, {
21
+ at: pathToElement
22
+ });
23
+ }
24
+ return React.createElement(LinkedInlineWrapper, {
25
+ attributes: attributes,
26
+ link: element.data.target,
27
+ card: React.createElement(FetchingWrappedResourceInlineCard, {
28
+ sdk: sdk,
29
+ link: link,
30
+ isDisabled: isDisabled,
31
+ isSelected: isSelected,
32
+ onRemove: handleRemoveClick,
33
+ onEntityFetchComplete: onEntityFetchComplete
34
+ })
35
+ }, children);
36
+ }
@@ -0,0 +1,46 @@
1
+ import { INLINES } from '@contentful/rich-text-types';
2
+ import { getWithEmbeddedEntryInlineEvents } from '../shared/EmbeddedInlineUtil';
3
+ import { LinkedResourceInline } from './LinkedResourceInline';
4
+ export function createEmbeddedResourceInlinePlugin(sdk) {
5
+ const htmlAttributeName = 'data-embedded-resource-inline-id';
6
+ const nodeType = INLINES.EMBEDDED_RESOURCE;
7
+ return {
8
+ key: nodeType,
9
+ type: nodeType,
10
+ isElement: true,
11
+ isInline: true,
12
+ isVoid: true,
13
+ component: LinkedResourceInline,
14
+ options: {
15
+ hotkey: 'mod+shift+p'
16
+ },
17
+ handlers: {
18
+ onKeyDown: getWithEmbeddedEntryInlineEvents(nodeType, sdk)
19
+ },
20
+ deserializeHtml: {
21
+ rules: [
22
+ {
23
+ validAttribute: htmlAttributeName
24
+ }
25
+ ],
26
+ withoutChildren: true,
27
+ getNode: (el)=>({
28
+ type: nodeType,
29
+ children: [
30
+ {
31
+ text: ''
32
+ }
33
+ ],
34
+ data: {
35
+ target: {
36
+ sys: {
37
+ urn: el.getAttribute('data-entity-id'),
38
+ linkType: el.getAttribute('data-entity-type'),
39
+ type: 'ResourceLink'
40
+ }
41
+ }
42
+ }
43
+ })
44
+ }
45
+ };
46
+ }
@@ -6,6 +6,7 @@ import { createDragAndDropPlugin } from './DragAndDrop';
6
6
  import { createEmbeddedAssetBlockPlugin, createEmbeddedEntryBlockPlugin } from './EmbeddedEntityBlock';
7
7
  import { createEmbeddedEntityInlinePlugin } from './EmbeddedEntityInline';
8
8
  import { createEmbeddedResourceBlockPlugin } from './EmbeddedResourceBlock';
9
+ import { createEmbeddedResourceInlinePlugin } from './EmbeddedResourceInline';
9
10
  import { createHeadingPlugin } from './Heading';
10
11
  import { createHrPlugin } from './Hr';
11
12
  import { createHyperlinkPlugin } from './Hyperlink';
@@ -39,6 +40,7 @@ export const getPlugins = (sdk, onAction, restrictedMarks)=>[
39
40
  createEmbeddedResourceBlockPlugin(sdk),
40
41
  createHyperlinkPlugin(sdk),
41
42
  createEmbeddedEntityInlinePlugin(sdk),
43
+ createEmbeddedResourceInlinePlugin(sdk),
42
44
  createMarksPlugin(),
43
45
  createTrailingParagraphPlugin(),
44
46
  createTextPlugin(restrictedMarks),
@@ -1,11 +1,12 @@
1
1
  import * as React from 'react';
2
- import { Badge, Flex, Icon, Menu } from '@contentful/f36-components';
2
+ import { Flex, Icon, Menu } from '@contentful/f36-components';
3
3
  import { AssetIcon, EmbeddedEntryBlockIcon } from '@contentful/f36-icons';
4
4
  import { BLOCKS } from '@contentful/rich-text-types';
5
5
  import { css } from 'emotion';
6
6
  import { useContentfulEditor } from '../../ContentfulEditorProvider';
7
7
  import { useSdkContext } from '../../SdkProvider';
8
8
  import { selectEntityAndInsert, selectResourceEntityAndInsert } from '../shared/EmbeddedBlockUtil';
9
+ import { ResourceNewBadge } from './ResourceNewBadge';
9
10
  export const styles = {
10
11
  icon: css({
11
12
  marginRight: '10px'
@@ -40,10 +41,7 @@ export function EmbeddedBlockToolbarIcon({ isDisabled , nodeType , onClose }) {
40
41
  as: type === 'Asset' ? AssetIcon : EmbeddedEntryBlockIcon,
41
42
  className: `rich-text__embedded-entry-list-icon ${styles.icon}`,
42
43
  variant: "secondary"
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")))));
44
+ }), React.createElement("span", null, type, nodeType == BLOCKS.EMBEDDED_RESOURCE && React.createElement(ResourceNewBadge, null))));
47
45
  }
48
46
  function getEntityTypeFromNodeType(nodeType) {
49
47
  const words = nodeType.toLowerCase().split('-');
@@ -1,8 +1,7 @@
1
1
  import { BLOCKS, TEXT_CONTAINERS } from '@contentful/rich-text-types';
2
2
  import isHotkey from 'is-hotkey';
3
+ import { newEntitySelectorConfigFromRichTextField, newResourceEntitySelectorConfigFromRichTextField } from '../../helpers/config';
3
4
  import { focus, getNodeEntryFromSelection, insertEmptyParagraph, moveToTheNextChar } from '../../helpers/editor';
4
- import newEntitySelectorConfigFromRichTextField from '../../helpers/newEntitySelectorConfigFromRichTextField';
5
- import newResourceEntitySelectorConfigFromRichTextField from '../../helpers/newResourceEntitySelectorConfigFromRichTextField';
6
5
  import { watchCurrentSlide } from '../../helpers/sdkNavigatorSlideIn';
7
6
  import { getText, getAboveNode, getLastNodeByLevel, insertNodes, setNodes, select, removeNodes } from '../../internal';
8
7
  export function getWithEmbeddedBlockEvents(nodeType, sdk) {
@@ -0,0 +1,51 @@
1
+ import * as React from 'react';
2
+ import { Menu, Flex } from '@contentful/f36-components';
3
+ import { EmbeddedEntryInlineIcon } from '@contentful/f36-icons';
4
+ import tokens from '@contentful/f36-tokens';
5
+ import { INLINES } from '@contentful/rich-text-types';
6
+ import { css } from 'emotion';
7
+ import { useContentfulEditor } from '../../ContentfulEditorProvider';
8
+ import { moveToTheNextChar } from '../../helpers/editor';
9
+ import { useSdkContext } from '../../SdkProvider';
10
+ import { selectEntityAndInsert, selectResourceEntityAndInsert } from '../shared/EmbeddedInlineUtil';
11
+ import { ResourceNewBadge } from './ResourceNewBadge';
12
+ const styles = {
13
+ icon: css({
14
+ marginRight: '10px'
15
+ }),
16
+ root: css({
17
+ display: 'inline-block',
18
+ margin: `0 ${tokens.spacing2Xs}`,
19
+ fontSize: 'inherit',
20
+ span: {
21
+ userSelect: 'none'
22
+ }
23
+ })
24
+ };
25
+ export function EmbeddedInlineToolbarIcon({ onClose , nodeType , isDisabled }) {
26
+ const editor = useContentfulEditor();
27
+ const sdk = useSdkContext();
28
+ async function handleClick(event) {
29
+ event.preventDefault();
30
+ if (!editor) return;
31
+ onClose();
32
+ if (nodeType === INLINES.EMBEDDED_RESOURCE) {
33
+ await selectResourceEntityAndInsert(editor, sdk, editor.tracking.onToolbarAction);
34
+ } else {
35
+ await selectEntityAndInsert(editor, sdk, editor.tracking.onToolbarAction);
36
+ }
37
+ moveToTheNextChar(editor);
38
+ }
39
+ return React.createElement(Menu.Item, {
40
+ disabled: isDisabled,
41
+ className: "rich-text__entry-link-block-button",
42
+ testId: `toolbar-toggle-${nodeType}`,
43
+ onClick: handleClick
44
+ }, React.createElement(Flex, {
45
+ alignItems: "center",
46
+ flexDirection: "row"
47
+ }, React.createElement(EmbeddedEntryInlineIcon, {
48
+ variant: "secondary",
49
+ className: `rich-text__embedded-entry-list-icon ${styles.icon}`
50
+ }), React.createElement("span", null, "Inline entry", nodeType == INLINES.EMBEDDED_RESOURCE && React.createElement(ResourceNewBadge, null))));
51
+ }
@@ -0,0 +1,101 @@
1
+ import { INLINES } from '@contentful/rich-text-types';
2
+ import isHotkey from 'is-hotkey';
3
+ import { newEntitySelectorConfigFromRichTextField, newResourceEntitySelectorConfigFromRichTextField } from '../../helpers/config';
4
+ import { focus } from '../../helpers/editor';
5
+ import { watchCurrentSlide } from '../../helpers/sdkNavigatorSlideIn';
6
+ import { insertNodes, select } from '../../internal/transforms';
7
+ export function getWithEmbeddedEntryInlineEvents(nodeType, sdk) {
8
+ return function withEmbeddedEntryInlineEvents(editor, { options: { hotkey } }) {
9
+ return function handleEvent(event) {
10
+ if (!editor) return;
11
+ if (hotkey && isHotkey(hotkey, event)) {
12
+ if (nodeType === INLINES.EMBEDDED_RESOURCE) {
13
+ selectResourceEntityAndInsert(editor, sdk, editor.tracking.onShortcutAction);
14
+ } else {
15
+ selectEntityAndInsert(editor, sdk, editor.tracking.onShortcutAction);
16
+ }
17
+ }
18
+ };
19
+ };
20
+ }
21
+ const getLink = (nodeType, entity)=>{
22
+ if (nodeType === INLINES.EMBEDDED_RESOURCE) {
23
+ return {
24
+ urn: entity.sys.urn,
25
+ type: 'ResourceLink',
26
+ linkType: 'Contentful:Entry'
27
+ };
28
+ }
29
+ return {
30
+ id: entity.sys.id,
31
+ type: 'Link',
32
+ linkType: entity.sys.type
33
+ };
34
+ };
35
+ const createInlineEntryNode = (nodeType, entity)=>{
36
+ return {
37
+ type: nodeType,
38
+ children: [
39
+ {
40
+ text: ''
41
+ }
42
+ ],
43
+ data: {
44
+ target: {
45
+ sys: getLink(nodeType, entity)
46
+ }
47
+ }
48
+ };
49
+ };
50
+ export async function selectEntityAndInsert(editor, sdk, logAction) {
51
+ const nodeType = INLINES.EMBEDDED_ENTRY;
52
+ logAction('openCreateEmbedDialog', {
53
+ nodeType
54
+ });
55
+ const config = {
56
+ ...newEntitySelectorConfigFromRichTextField(sdk.field, nodeType),
57
+ withCreate: true
58
+ };
59
+ const { selection } = editor;
60
+ const rteSlide = watchCurrentSlide(sdk.navigator);
61
+ const entry = await sdk.dialogs.selectSingleEntry(config);
62
+ if (!entry) {
63
+ logAction('cancelCreateEmbedDialog', {
64
+ nodeType
65
+ });
66
+ } else {
67
+ select(editor, selection);
68
+ insertNodes(editor, createInlineEntryNode(nodeType, entry));
69
+ logAction('insert', {
70
+ nodeType
71
+ });
72
+ }
73
+ rteSlide.onActive(()=>{
74
+ rteSlide.unwatch();
75
+ focus(editor);
76
+ });
77
+ }
78
+ export async function selectResourceEntityAndInsert(editor, sdk, logAction) {
79
+ const nodeType = INLINES.EMBEDDED_RESOURCE;
80
+ logAction('openCreateEmbedDialog', {
81
+ nodeType
82
+ });
83
+ const { dialogs , field } = sdk;
84
+ const config = {
85
+ ...newResourceEntitySelectorConfigFromRichTextField(field, nodeType),
86
+ withCreate: true
87
+ };
88
+ const { selection } = editor;
89
+ const entry = await dialogs.selectSingleResourceEntry(config);
90
+ if (!entry) {
91
+ logAction('cancelCreateEmbedDialog', {
92
+ nodeType
93
+ });
94
+ } else {
95
+ select(editor, selection);
96
+ insertNodes(editor, createInlineEntryNode(nodeType, entry));
97
+ logAction('insert', {
98
+ nodeType
99
+ });
100
+ }
101
+ }
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { css } from 'emotion';
3
3
  import { IS_CHROME } from '../../helpers/environment';
4
+ import { getLinkEntityId } from './utils';
4
5
  const styles = {
5
6
  root: css({
6
7
  marginBottom: '1.25rem !important',
@@ -12,14 +13,12 @@ const styles = {
12
13
  width: '100%'
13
14
  })
14
15
  };
15
- const isResourceLink = (link)=>!!link.urn;
16
- export function LinkedBlockWrapper({ attributes , card , children , element }) {
17
- const link = element.data.target.sys;
16
+ export function LinkedBlockWrapper({ attributes , card , children , link }) {
18
17
  return React.createElement("div", {
19
18
  ...attributes,
20
19
  className: styles.root,
21
- "data-entity-type": link.linkType,
22
- "data-entity-id": isResourceLink(link) ? link.urn : link.id,
20
+ "data-entity-type": link.sys.linkType,
21
+ "data-entity-id": getLinkEntityId(link),
23
22
  contentEditable: IS_CHROME ? undefined : false,
24
23
  draggable: IS_CHROME ? true : undefined
25
24
  }, React.createElement("div", {
@@ -0,0 +1,31 @@
1
+ import * as React from 'react';
2
+ import tokens from '@contentful/f36-tokens';
3
+ import { css } from 'emotion';
4
+ import { IS_CHROME } from '../../helpers/environment';
5
+ import { getLinkEntityId } from './utils';
6
+ const styles = {
7
+ icon: css({
8
+ marginRight: '10px'
9
+ }),
10
+ root: css({
11
+ display: 'inline-block',
12
+ margin: `0 ${tokens.spacing2Xs}`,
13
+ fontSize: 'inherit',
14
+ span: {
15
+ userSelect: 'none'
16
+ }
17
+ })
18
+ };
19
+ export function LinkedInlineWrapper({ attributes , card , children , link }) {
20
+ return React.createElement("span", {
21
+ ...attributes,
22
+ className: styles.root,
23
+ "data-entity-type": link.sys.linkType,
24
+ "data-entity-id": getLinkEntityId(link),
25
+ contentEditable: IS_CHROME ? undefined : false,
26
+ draggable: IS_CHROME ? true : undefined
27
+ }, React.createElement("span", {
28
+ contentEditable: IS_CHROME ? false : undefined,
29
+ draggable: IS_CHROME ? true : undefined
30
+ }, card), children);
31
+ }