@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.
- package/dist/cjs/Toolbar/components/EmbedEntityWidget.js +8 -2
- package/dist/cjs/constants/Schema.js +3 -0
- package/dist/cjs/helpers/{newEntitySelectorConfigFromRichTextField.js → config.js} +19 -5
- package/dist/cjs/plugins/DragAndDrop/index.js +4 -2
- package/dist/cjs/plugins/EmbeddedEntityBlock/LinkedEntityBlock.js +1 -1
- package/dist/cjs/plugins/EmbeddedEntityInline/LinkedEntityInline.js +97 -0
- package/dist/cjs/plugins/EmbeddedEntityInline/index.js +27 -193
- package/dist/cjs/plugins/{shared/FetchingWrappedResourceCard.js → EmbeddedResourceBlock/FetchingWrappedResourceBlockCard.js} +3 -3
- package/dist/cjs/plugins/EmbeddedResourceBlock/LinkedResourceBlock.js +3 -3
- package/dist/cjs/plugins/EmbeddedResourceInline/FetchingWrappedResourceInlineCard.js +102 -0
- package/dist/cjs/plugins/EmbeddedResourceInline/LinkedResourceInline.js +51 -0
- package/dist/cjs/plugins/EmbeddedResourceInline/index.js +56 -0
- package/dist/cjs/plugins/index.js +2 -0
- package/dist/cjs/plugins/shared/EmbeddedBlockToolbarIcon.js +2 -4
- package/dist/cjs/plugins/shared/EmbeddedBlockUtil.js +3 -4
- package/dist/cjs/plugins/shared/EmbeddedInlineToolbarIcon.js +105 -0
- package/dist/cjs/plugins/shared/EmbeddedInlineUtil.js +127 -0
- package/dist/cjs/plugins/shared/LinkedBlockWrapper.js +4 -5
- package/dist/cjs/plugins/shared/LinkedInlineWrapper.js +85 -0
- package/dist/cjs/plugins/shared/ResourceNewBadge.js +57 -0
- package/dist/cjs/plugins/shared/__tests__/FetchingWrappedResourceCard.test.js +2 -2
- package/dist/cjs/plugins/shared/utils.js +12 -0
- package/dist/esm/Toolbar/components/EmbedEntityWidget.js +8 -2
- package/dist/esm/constants/Schema.js +3 -0
- package/dist/esm/helpers/{newEntitySelectorConfigFromRichTextField.js → config.js} +8 -2
- package/dist/esm/plugins/DragAndDrop/index.js +4 -2
- package/dist/esm/plugins/EmbeddedEntityBlock/LinkedEntityBlock.js +1 -1
- package/dist/esm/plugins/EmbeddedEntityInline/LinkedEntityInline.js +48 -0
- package/dist/esm/plugins/EmbeddedEntityInline/index.js +24 -138
- package/dist/esm/plugins/{shared/FetchingWrappedResourceCard.js → EmbeddedResourceBlock/FetchingWrappedResourceBlockCard.js} +1 -1
- package/dist/esm/plugins/EmbeddedResourceBlock/LinkedResourceBlock.js +3 -3
- package/dist/esm/plugins/EmbeddedResourceInline/FetchingWrappedResourceInlineCard.js +53 -0
- package/dist/esm/plugins/EmbeddedResourceInline/LinkedResourceInline.js +36 -0
- package/dist/esm/plugins/EmbeddedResourceInline/index.js +46 -0
- package/dist/esm/plugins/index.js +2 -0
- package/dist/esm/plugins/shared/EmbeddedBlockToolbarIcon.js +3 -5
- package/dist/esm/plugins/shared/EmbeddedBlockUtil.js +1 -2
- package/dist/esm/plugins/shared/EmbeddedInlineToolbarIcon.js +51 -0
- package/dist/esm/plugins/shared/EmbeddedInlineUtil.js +101 -0
- package/dist/esm/plugins/shared/LinkedBlockWrapper.js +4 -5
- package/dist/esm/plugins/shared/LinkedInlineWrapper.js +31 -0
- package/dist/esm/plugins/shared/ResourceNewBadge.js +8 -0
- package/dist/esm/plugins/shared/__tests__/FetchingWrappedResourceCard.test.js +2 -2
- package/dist/esm/plugins/shared/utils.js +2 -0
- package/dist/types/constants/Schema.d.ts +3 -0
- package/dist/types/helpers/config.d.ts +33 -0
- package/dist/types/plugins/EmbeddedEntityBlock/LinkedEntityBlock.d.ts +2 -7
- package/dist/types/plugins/EmbeddedEntityInline/LinkedEntityInline.d.ts +14 -0
- package/dist/types/plugins/EmbeddedEntityInline/index.d.ts +0 -7
- package/dist/types/plugins/{shared/FetchingWrappedResourceCard.d.ts → EmbeddedResourceBlock/FetchingWrappedResourceBlockCard.d.ts} +2 -2
- package/dist/types/plugins/EmbeddedResourceInline/FetchingWrappedResourceInlineCard.d.ts +13 -0
- package/dist/types/plugins/EmbeddedResourceInline/LinkedResourceInline.d.ts +13 -0
- package/dist/types/plugins/EmbeddedResourceInline/index.d.ts +3 -0
- package/dist/types/plugins/shared/EmbeddedInlineToolbarIcon.d.ts +8 -0
- package/dist/types/plugins/shared/EmbeddedInlineUtil.d.ts +8 -0
- package/dist/types/plugins/shared/LinkedBlockWrapper.d.ts +4 -19
- package/dist/types/plugins/shared/LinkedInlineWrapper.d.ts +10 -0
- package/dist/types/plugins/shared/ResourceNewBadge.d.ts +2 -0
- package/dist/types/plugins/shared/utils.d.ts +2 -0
- package/package.json +2 -2
- package/dist/cjs/helpers/newResourceEntitySelectorConfigFromRichTextField.js +0 -21
- package/dist/cjs/plugins/EmbeddedEntityInline/Util.js +0 -30
- package/dist/esm/helpers/newResourceEntitySelectorConfigFromRichTextField.js +0 -6
- package/dist/esm/plugins/EmbeddedEntityInline/Util.js +0 -20
- package/dist/types/helpers/newEntitySelectorConfigFromRichTextField.d.ts +0 -14
- package/dist/types/helpers/newResourceEntitySelectorConfigFromRichTextField.d.ts +0 -16
- 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 {
|
|
7
|
-
import
|
|
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:
|
|
129
|
-
type:
|
|
8
|
+
key: nodeType,
|
|
9
|
+
type: nodeType,
|
|
130
10
|
isElement: true,
|
|
131
11
|
isInline: true,
|
|
132
12
|
isVoid: true,
|
|
133
|
-
component:
|
|
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)=>
|
|
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
|
|
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
|
-
|
|
30
|
-
card: React.createElement(
|
|
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 {
|
|
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(
|
|
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
|
-
|
|
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":
|
|
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
|
+
}
|