@contentful/field-editor-rich-text 3.5.0 → 3.6.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 -3
- package/dist/cjs/helpers/__tests__/removeInternalMarks.test.js +37 -21
- package/dist/cjs/helpers/getAllowedResourcesForNodeType.js +25 -0
- package/dist/cjs/helpers/newResourceEntitySelectorConfigFromRichTextField.js +21 -0
- package/dist/cjs/plugins/DragAndDrop/index.js +1 -0
- package/dist/cjs/plugins/EmbeddedEntityBlock/LinkedEntityBlock.js +27 -44
- package/dist/cjs/plugins/EmbeddedEntityBlock/index.js +3 -35
- package/dist/cjs/plugins/EmbeddedEntityInline/index.js +3 -2
- package/dist/cjs/plugins/EmbeddedResourceBlock/LinkedResourceBlock.js +54 -0
- package/dist/cjs/plugins/EmbeddedResourceBlock/index.js +55 -0
- package/dist/cjs/plugins/Hyperlink/components/EntityHyperlink.js +2 -1
- package/dist/cjs/plugins/Hyperlink/createHyperlinkPlugin.js +2 -3
- package/dist/cjs/plugins/Text/createTextPlugin.js +1 -0
- package/dist/cjs/plugins/index.js +2 -0
- package/dist/cjs/plugins/links-tracking.js +8 -17
- package/dist/cjs/plugins/{EmbeddedEntityBlock/ToolbarIcon.js → shared/EmbeddedBlockToolbarIcon.js} +15 -7
- package/dist/cjs/plugins/shared/EmbeddedBlockUtil.js +170 -0
- package/dist/cjs/plugins/shared/FetchingWrappedResourceCard.js +110 -0
- package/dist/cjs/plugins/shared/LinkedBlockWrapper.js +45 -0
- package/dist/cjs/useOnValueChanged.js +1 -1
- package/dist/esm/Toolbar/components/EmbedEntityWidget.js +8 -3
- package/dist/esm/helpers/__tests__/removeInternalMarks.test.js +37 -21
- package/dist/esm/helpers/getAllowedResourcesForNodeType.js +10 -0
- package/dist/esm/helpers/newResourceEntitySelectorConfigFromRichTextField.js +6 -0
- package/dist/esm/plugins/DragAndDrop/index.js +1 -0
- package/dist/esm/plugins/EmbeddedEntityBlock/LinkedEntityBlock.js +27 -44
- package/dist/esm/plugins/EmbeddedEntityBlock/index.js +3 -27
- package/dist/esm/plugins/EmbeddedEntityInline/index.js +4 -3
- package/dist/esm/plugins/EmbeddedResourceBlock/LinkedResourceBlock.js +39 -0
- package/dist/esm/plugins/EmbeddedResourceBlock/index.js +45 -0
- package/dist/esm/plugins/Hyperlink/components/EntityHyperlink.js +2 -1
- package/dist/esm/plugins/Hyperlink/createHyperlinkPlugin.js +2 -3
- package/dist/esm/plugins/Text/createTextPlugin.js +1 -0
- package/dist/esm/plugins/index.js +2 -0
- package/dist/esm/plugins/links-tracking.js +6 -10
- package/dist/esm/plugins/{EmbeddedEntityBlock/ToolbarIcon.js → shared/EmbeddedBlockToolbarIcon.js} +14 -6
- package/dist/esm/plugins/shared/EmbeddedBlockUtil.js +144 -0
- package/dist/esm/plugins/shared/FetchingWrappedResourceCard.js +56 -0
- package/dist/esm/plugins/shared/LinkedBlockWrapper.js +30 -0
- package/dist/esm/useOnValueChanged.js +1 -1
- package/dist/types/dialogs/HypelinkDialog/HyperlinkDialog.d.ts +3 -3
- package/dist/types/helpers/getAllowedResourcesForNodeType.d.ts +25 -0
- package/dist/types/helpers/newResourceEntitySelectorConfigFromRichTextField.d.ts +16 -0
- package/dist/types/plugins/EmbeddedEntityBlock/LinkedEntityBlock.d.ts +0 -1
- package/dist/types/plugins/EmbeddedEntityBlock/index.d.ts +1 -2
- package/dist/types/plugins/EmbeddedResourceBlock/LinkedResourceBlock.d.ts +18 -0
- package/dist/types/plugins/EmbeddedResourceBlock/index.d.ts +3 -0
- package/dist/types/plugins/links-tracking.d.ts +3 -3
- package/dist/types/plugins/shared/EmbeddedBlockToolbarIcon.d.ts +11 -0
- package/dist/types/plugins/shared/EmbeddedBlockUtil.d.ts +8 -0
- package/dist/types/plugins/shared/FetchingWrappedResourceCard.d.ts +14 -0
- package/dist/types/plugins/shared/LinkedBlockWrapper.d.ts +25 -0
- package/dist/types/test-utils/jsx.d.ts +1 -1
- package/package.json +7 -7
- package/dist/cjs/plugins/EmbeddedEntityBlock/Util.js +0 -108
- package/dist/esm/plugins/EmbeddedEntityBlock/Util.js +0 -85
- package/dist/types/plugins/EmbeddedEntityBlock/ToolbarIcon.d.ts +0 -11
- package/dist/types/plugins/EmbeddedEntityBlock/Util.d.ts +0 -4
|
@@ -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 } =
|
|
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:
|
|
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:
|
|
96
|
+
component: EntityHyperlink,
|
|
98
97
|
deserializeHtml: {
|
|
99
98
|
rules: [
|
|
100
99
|
{
|
|
@@ -6,6 +6,7 @@ import { isCommandPromptPluginEnabled } from './CommandPalette/useCommands';
|
|
|
6
6
|
import { createDragAndDropPlugin } from './DragAndDrop';
|
|
7
7
|
import { createEmbeddedAssetBlockPlugin, createEmbeddedEntryBlockPlugin } from './EmbeddedEntityBlock';
|
|
8
8
|
import { createEmbeddedEntityInlinePlugin } from './EmbeddedEntityInline';
|
|
9
|
+
import { createEmbeddedResourceBlockPlugin } from './EmbeddedResourceBlock';
|
|
9
10
|
import { createHeadingPlugin } from './Heading';
|
|
10
11
|
import { createHrPlugin } from './Hr';
|
|
11
12
|
import { createHyperlinkPlugin } from './Hyperlink';
|
|
@@ -38,6 +39,7 @@ export const getPlugins = (sdk, onAction, restrictedMarks)=>[
|
|
|
38
39
|
createTablePlugin(),
|
|
39
40
|
createEmbeddedEntryBlockPlugin(sdk),
|
|
40
41
|
createEmbeddedAssetBlockPlugin(sdk),
|
|
42
|
+
createEmbeddedResourceBlockPlugin(sdk),
|
|
41
43
|
createHyperlinkPlugin(sdk),
|
|
42
44
|
createEmbeddedEntityInlinePlugin(sdk),
|
|
43
45
|
createMarksPlugin(),
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
2
|
import { useContentfulEditorRef } from '../ContentfulEditorProvider';
|
|
3
|
-
export function
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
}
|
package/dist/esm/plugins/{EmbeddedEntityBlock/ToolbarIcon.js → shared/EmbeddedBlockToolbarIcon.js}
RENAMED
|
@@ -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 '
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|
|
@@ -13,7 +13,7 @@ const isRelevantOperation = (op)=>{
|
|
|
13
13
|
export const useOnValueChanged = ({ editorId , handler , skip , onSkip })=>{
|
|
14
14
|
const onChange = useMemo(()=>debounce((document)=>{
|
|
15
15
|
const contentfulDocument = toContentfulDocument({
|
|
16
|
-
document,
|
|
16
|
+
document: document,
|
|
17
17
|
schema
|
|
18
18
|
});
|
|
19
19
|
const cleanedDocument = removeInternalMarks(contentfulDocument);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type AllowedResource = {
|
|
2
|
+
type: 'Contentful:Entry';
|
|
3
|
+
source: string;
|
|
4
|
+
contentTypes: string[];
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Given a field object and a rich text node type, return a list of allowed
|
|
8
|
+
* resources associated with the node type, based on that node type's
|
|
9
|
+
* `allowedResources` property.
|
|
10
|
+
*
|
|
11
|
+
* The navigation here is explained by the `nodes` validation having signature:
|
|
12
|
+
* { nodes: { [nodeType]: { allowedResources: AllowedResource[] } } }
|
|
13
|
+
*
|
|
14
|
+
* We defensively navigate through this object because
|
|
15
|
+
* 1) the field may not have a `validations` array,
|
|
16
|
+
* 2) the `validations` array may be empty,
|
|
17
|
+
* 3) the `validations` array may not have a `nodes` validation, and
|
|
18
|
+
* 4) the `nodes` validation may not validate the `nodeType`.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} field
|
|
21
|
+
* @param {string} nodeType
|
|
22
|
+
* @returns {AllowedResource[]}
|
|
23
|
+
*/
|
|
24
|
+
export default function getAllowedResourcesForNodeType(field: any, nodeType: any): AllowedResource[];
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a config for the entity selector based on a given rich text field and a
|
|
3
|
+
* rich text node type that the entity should be picked for. Takes the field
|
|
4
|
+
* validations for the given node type into account.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} field
|
|
7
|
+
* @param {string} nodeType
|
|
8
|
+
* @returns {object}
|
|
9
|
+
*/
|
|
10
|
+
export default function newResourceEntitySelectorConfigFromRichTextField(field: any, nodeType: any): {
|
|
11
|
+
allowedResources: {
|
|
12
|
+
type: "Contentful:Entry";
|
|
13
|
+
source: string;
|
|
14
|
+
contentTypes: string[];
|
|
15
|
+
}[];
|
|
16
|
+
};
|
|
@@ -14,7 +14,6 @@ type LinkedEntityBlockProps = {
|
|
|
14
14
|
};
|
|
15
15
|
attributes: Pick<RenderElementProps, 'attributes'>;
|
|
16
16
|
children: Pick<RenderElementProps, 'children'>;
|
|
17
|
-
onEntityFetchComplete: VoidFunction;
|
|
18
17
|
};
|
|
19
18
|
export declare function LinkedEntityBlock(props: LinkedEntityBlockProps): React.JSX.Element;
|
|
20
19
|
export {};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { FieldExtensionSDK } from '@contentful/app-sdk';
|
|
2
|
-
import { PlatePlugin } from '../../internal
|
|
3
|
-
export { EmbeddedEntityBlockToolbarIcon as ToolbarIcon } from './ToolbarIcon';
|
|
2
|
+
import { PlatePlugin } from '../../internal';
|
|
4
3
|
export declare const createEmbeddedEntryBlockPlugin: (sdk: FieldExtensionSDK) => PlatePlugin;
|
|
5
4
|
export declare const createEmbeddedAssetBlockPlugin: (sdk: FieldExtensionSDK) => PlatePlugin;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Element, RenderElementProps } from '../../internal';
|
|
3
|
+
export type LinkedResourceBlockProps = {
|
|
4
|
+
element: Element & {
|
|
5
|
+
data: {
|
|
6
|
+
target: {
|
|
7
|
+
sys: {
|
|
8
|
+
urn: string;
|
|
9
|
+
linkType: 'Contentful:Entry';
|
|
10
|
+
type: 'ResourceLink';
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
attributes: Pick<RenderElementProps, 'attributes'>;
|
|
16
|
+
children: Pick<RenderElementProps, 'children'>;
|
|
17
|
+
};
|
|
18
|
+
export declare function LinkedResourceBlock(props: LinkedResourceBlockProps): React.JSX.Element;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export declare function useLinkTracking(): {
|
|
2
|
+
onEntityFetchComplete: () => unknown;
|
|
3
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export declare const styles: {
|
|
3
|
+
icon: string;
|
|
4
|
+
};
|
|
5
|
+
interface EmbeddedBlockToolbarIconProps {
|
|
6
|
+
isDisabled: boolean;
|
|
7
|
+
nodeType: string;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function EmbeddedBlockToolbarIcon({ isDisabled, nodeType, onClose, }: EmbeddedBlockToolbarIconProps): React.JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FieldExtensionSDK } from '@contentful/app-sdk';
|
|
2
|
+
import { BLOCKS } from '@contentful/rich-text-types';
|
|
3
|
+
import { HotkeyPlugin } from '@udecode/plate-core';
|
|
4
|
+
import { KeyboardHandler } from '../../internal';
|
|
5
|
+
import { TrackingPluginActions } from '../Tracking';
|
|
6
|
+
export declare function getWithEmbeddedBlockEvents(nodeType: BLOCKS.EMBEDDED_ENTRY | BLOCKS.EMBEDDED_ASSET | BLOCKS.EMBEDDED_RESOURCE, sdk: FieldExtensionSDK): KeyboardHandler<HotkeyPlugin>;
|
|
7
|
+
export declare function selectEntityAndInsert(nodeType: any, sdk: any, editor: any, logAction: TrackingPluginActions['onToolbarAction'] | TrackingPluginActions['onShortcutAction']): Promise<void>;
|
|
8
|
+
export declare function selectResourceEntityAndInsert(sdk: any, editor: any, logAction: TrackingPluginActions['onToolbarAction'] | TrackingPluginActions['onShortcutAction']): Promise<void>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { FieldExtensionSDK } from '@contentful/app-sdk';
|
|
3
|
+
import { ResourceLink } from '@contentful/rich-text-types';
|
|
4
|
+
interface FetchingWrappedResourceCardProps {
|
|
5
|
+
link: ResourceLink['sys'];
|
|
6
|
+
isDisabled: boolean;
|
|
7
|
+
isSelected: boolean;
|
|
8
|
+
sdk: FieldExtensionSDK;
|
|
9
|
+
onEntityFetchComplete?: VoidFunction;
|
|
10
|
+
onEdit?: VoidFunction;
|
|
11
|
+
onRemove?: VoidFunction;
|
|
12
|
+
}
|
|
13
|
+
export declare const FetchingWrappedResourceCard: (props: FetchingWrappedResourceCardProps) => React.JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Element, RenderElementProps } from '../../internal';
|
|
3
|
+
type EntityLink = {
|
|
4
|
+
id: string;
|
|
5
|
+
linkType: 'Entry' | 'Asset';
|
|
6
|
+
type: 'Link';
|
|
7
|
+
};
|
|
8
|
+
type ResourceLink = {
|
|
9
|
+
urn: string;
|
|
10
|
+
linkType: 'Contentful:Entry';
|
|
11
|
+
type: 'ResourceLink';
|
|
12
|
+
};
|
|
13
|
+
type LinkedBlockWrapperProps = React.PropsWithChildren<{
|
|
14
|
+
attributes: Pick<RenderElementProps, 'attributes'>;
|
|
15
|
+
card: JSX.Element;
|
|
16
|
+
element: Element & {
|
|
17
|
+
data: {
|
|
18
|
+
target: {
|
|
19
|
+
sys: ResourceLink | EntityLink;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
}>;
|
|
24
|
+
export declare function LinkedBlockWrapper({ attributes, card, children, element, }: LinkedBlockWrapperProps): React.JSX.Element;
|
|
25
|
+
export {};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Add items as needed. Don't forget to adjust hyperscript.d.ts
|
|
5
5
|
*/
|
|
6
|
-
export declare const jsx: <S extends "text" | "selection" | "editor" | "anchor" | "
|
|
6
|
+
export declare const jsx: <S extends "text" | "selection" | "editor" | "anchor" | "element" | "focus" | "cursor" | "fragment">(tagName: S, attributes?: Object | undefined, ...children: any[]) => ReturnType<({
|
|
7
7
|
anchor: typeof import("slate-hyperscript/dist/creators").createAnchor;
|
|
8
8
|
cursor: typeof import("slate-hyperscript/dist/creators").createCursor;
|
|
9
9
|
editor: (tagName: string, attributes: {
|