@contentful/field-editor-rich-text 3.12.7 → 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 +6 -0
- 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/EmbeddedEntityInline/index.js +21 -4
- package/dist/cjs/plugins/{shared/FetchingWrappedResourceCard.js → EmbeddedResourceBlock/FetchingWrappedResourceBlockCard.js} +3 -3
- package/dist/cjs/plugins/EmbeddedResourceBlock/LinkedResourceBlock.js +2 -2
- 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 +11 -6
- package/dist/cjs/plugins/shared/EmbeddedInlineUtil.js +66 -27
- package/dist/cjs/plugins/shared/ResourceNewBadge.js +57 -0
- package/dist/cjs/plugins/shared/__tests__/FetchingWrappedResourceCard.test.js +2 -2
- package/dist/esm/Toolbar/components/EmbedEntityWidget.js +6 -0
- 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/EmbeddedEntityInline/index.js +22 -5
- package/dist/esm/plugins/{shared/FetchingWrappedResourceCard.js → EmbeddedResourceBlock/FetchingWrappedResourceBlockCard.js} +1 -1
- package/dist/esm/plugins/EmbeddedResourceBlock/LinkedResourceBlock.js +2 -2
- 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 +12 -7
- package/dist/esm/plugins/shared/EmbeddedInlineUtil.js +64 -25
- package/dist/esm/plugins/shared/ResourceNewBadge.js +8 -0
- package/dist/esm/plugins/shared/__tests__/FetchingWrappedResourceCard.test.js +2 -2
- package/dist/types/constants/Schema.d.ts +3 -0
- package/dist/types/helpers/config.d.ts +33 -0
- 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 +2 -1
- package/dist/types/plugins/shared/EmbeddedInlineUtil.d.ts +3 -17
- package/dist/types/plugins/shared/ResourceNewBadge.d.ts +2 -0
- package/package.json +2 -2
- package/dist/cjs/helpers/newResourceEntitySelectorConfigFromRichTextField.js +0 -21
- package/dist/esm/helpers/newResourceEntitySelectorConfigFromRichTextField.js +0 -6
- package/dist/types/helpers/newEntitySelectorConfigFromRichTextField.d.ts +0 -14
- package/dist/types/helpers/newResourceEntitySelectorConfigFromRichTextField.d.ts +0 -16
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "ResourceNewBadge", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return ResourceNewBadge;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _react = _interop_require_wildcard(require("react"));
|
|
12
|
+
const _f36components = require("@contentful/f36-components");
|
|
13
|
+
function _getRequireWildcardCache(nodeInterop) {
|
|
14
|
+
if (typeof WeakMap !== "function") return null;
|
|
15
|
+
var cacheBabelInterop = new WeakMap();
|
|
16
|
+
var cacheNodeInterop = new WeakMap();
|
|
17
|
+
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
18
|
+
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
19
|
+
})(nodeInterop);
|
|
20
|
+
}
|
|
21
|
+
function _interop_require_wildcard(obj, nodeInterop) {
|
|
22
|
+
if (!nodeInterop && obj && obj.__esModule) {
|
|
23
|
+
return obj;
|
|
24
|
+
}
|
|
25
|
+
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
26
|
+
return {
|
|
27
|
+
default: obj
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
var cache = _getRequireWildcardCache(nodeInterop);
|
|
31
|
+
if (cache && cache.has(obj)) {
|
|
32
|
+
return cache.get(obj);
|
|
33
|
+
}
|
|
34
|
+
var newObj = {};
|
|
35
|
+
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
36
|
+
for(var key in obj){
|
|
37
|
+
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
38
|
+
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
39
|
+
if (desc && (desc.get || desc.set)) {
|
|
40
|
+
Object.defineProperty(newObj, key, desc);
|
|
41
|
+
} else {
|
|
42
|
+
newObj[key] = obj[key];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
newObj.default = obj;
|
|
47
|
+
if (cache) {
|
|
48
|
+
cache.set(obj, newObj);
|
|
49
|
+
}
|
|
50
|
+
return newObj;
|
|
51
|
+
}
|
|
52
|
+
const ResourceNewBadge = ()=>{
|
|
53
|
+
return _react.createElement(_react.Fragment, null, ' ', "(different space)", ' ', _react.createElement(_f36components.Badge, {
|
|
54
|
+
variant: "primary-filled",
|
|
55
|
+
size: "small"
|
|
56
|
+
}, "new"));
|
|
57
|
+
};
|
|
@@ -10,7 +10,7 @@ const _react1 = require("@testing-library/react");
|
|
|
10
10
|
const _published_content_typejson = _interop_require_default(require("../__fixtures__/published_content_type.json"));
|
|
11
11
|
const _published_entryjson = _interop_require_default(require("../__fixtures__/published_entry.json"));
|
|
12
12
|
const _spacejson = _interop_require_default(require("../__fixtures__/space.json"));
|
|
13
|
-
const
|
|
13
|
+
const _FetchingWrappedResourceBlockCard = require("../../EmbeddedResourceBlock/FetchingWrappedResourceBlockCard");
|
|
14
14
|
function _interop_require_default(obj) {
|
|
15
15
|
return obj && obj.__esModule ? obj : {
|
|
16
16
|
default: obj
|
|
@@ -111,7 +111,7 @@ beforeEach(()=>{
|
|
|
111
111
|
function renderResourceCard({ linkType ='Contentful:Entry' , entryUrn =resolvableEntryUrn } = {}) {
|
|
112
112
|
return (0, _react1.render)(_react.createElement(_fieldeditorreference.EntityProvider, {
|
|
113
113
|
sdk: sdk
|
|
114
|
-
}, _react.createElement(
|
|
114
|
+
}, _react.createElement(_FetchingWrappedResourceBlockCard.FetchingWrappedResourceBlockCard, {
|
|
115
115
|
isDisabled: false,
|
|
116
116
|
isSelected: false,
|
|
117
117
|
sdk: sdk,
|
|
@@ -14,6 +14,7 @@ export const EmbedEntityWidget = ({ isDisabled , canInsertBlocks })=>{
|
|
|
14
14
|
const onCloseEntityDropdown = ()=>setEmbedDropdownOpen(false);
|
|
15
15
|
const onToggleEntityDropdown = ()=>setEmbedDropdownOpen(!isEmbedDropdownOpen);
|
|
16
16
|
const inlineEntryEmbedEnabled = isNodeTypeEnabled(sdk.field, INLINES.EMBEDDED_ENTRY);
|
|
17
|
+
const inlineResourceEmbedEnabled = isNodeTypeEnabled(sdk.field, INLINES.EMBEDDED_RESOURCE);
|
|
17
18
|
const blockEntryEmbedEnabled = isNodeTypeEnabled(sdk.field, BLOCKS.EMBEDDED_ENTRY) && canInsertBlocks;
|
|
18
19
|
const blockResourceEmbedEnabled = isNodeTypeEnabled(sdk.field, BLOCKS.EMBEDDED_RESOURCE) && canInsertBlocks;
|
|
19
20
|
const blockAssetEmbedEnabled = isNodeTypeEnabled(sdk.field, BLOCKS.EMBEDDED_ASSET) && canInsertBlocks;
|
|
@@ -26,6 +27,11 @@ export const EmbedEntityWidget = ({ isDisabled , canInsertBlocks })=>{
|
|
|
26
27
|
nodeType: BLOCKS.EMBEDDED_RESOURCE,
|
|
27
28
|
onClose: onCloseEntityDropdown
|
|
28
29
|
}), inlineEntryEmbedEnabled && React.createElement(EmbeddedInlineToolbarIcon, {
|
|
30
|
+
nodeType: INLINES.EMBEDDED_ENTRY,
|
|
31
|
+
isDisabled: !!isDisabled || isLinkActive(editor),
|
|
32
|
+
onClose: onCloseEntityDropdown
|
|
33
|
+
}), inlineResourceEmbedEnabled && React.createElement(EmbeddedInlineToolbarIcon, {
|
|
34
|
+
nodeType: INLINES.EMBEDDED_RESOURCE,
|
|
29
35
|
isDisabled: !!isDisabled || isLinkActive(editor),
|
|
30
36
|
onClose: onCloseEntityDropdown
|
|
31
37
|
}), blockAssetEmbedEnabled && React.createElement(EmbeddedBlockToolbarIcon, {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import getAllowedResourcesForNodeType from './getAllowedResourcesForNodeType';
|
|
1
2
|
import getLinkedContentTypeIdsForNodeType from './getLinkedContentTypeIdsForNodeType';
|
|
2
|
-
export
|
|
3
|
+
export const newEntitySelectorConfigFromRichTextField = (field, nodeType)=>{
|
|
3
4
|
return {
|
|
4
5
|
entityType: getEntityTypeFromRichTextNode(nodeType),
|
|
5
6
|
locale: field.locale || null,
|
|
6
7
|
contentTypes: getLinkedContentTypeIdsForNodeType(field, nodeType)
|
|
7
8
|
};
|
|
8
|
-
}
|
|
9
|
+
};
|
|
9
10
|
function getEntityTypeFromRichTextNode(nodeType) {
|
|
10
11
|
const words = nodeType.split('-');
|
|
11
12
|
if (words.indexOf('entry') !== -1) {
|
|
@@ -16,3 +17,8 @@ function getEntityTypeFromRichTextNode(nodeType) {
|
|
|
16
17
|
}
|
|
17
18
|
throw new Error(`RichText node type \`${nodeType}\` has no associated \`entityType\``);
|
|
18
19
|
}
|
|
20
|
+
export const newResourceEntitySelectorConfigFromRichTextField = (field, nodeType)=>{
|
|
21
|
+
return {
|
|
22
|
+
allowedResources: getAllowedResourcesForNodeType(field, nodeType)
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -6,11 +6,13 @@ export function createDragAndDropPlugin() {
|
|
|
6
6
|
BLOCKS.EMBEDDED_ASSET,
|
|
7
7
|
BLOCKS.EMBEDDED_RESOURCE,
|
|
8
8
|
BLOCKS.HR,
|
|
9
|
-
INLINES.EMBEDDED_ENTRY
|
|
9
|
+
INLINES.EMBEDDED_ENTRY,
|
|
10
|
+
INLINES.EMBEDDED_RESOURCE
|
|
10
11
|
];
|
|
11
12
|
const ON_DROP_ALLOWED_TYPES = {
|
|
12
13
|
TABLE: [
|
|
13
|
-
INLINES.EMBEDDED_ENTRY
|
|
14
|
+
INLINES.EMBEDDED_ENTRY,
|
|
15
|
+
INLINES.EMBEDDED_RESOURCE
|
|
14
16
|
]
|
|
15
17
|
};
|
|
16
18
|
return {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { INLINES } from '@contentful/rich-text-types';
|
|
2
|
-
import {
|
|
2
|
+
import { getWithEmbeddedEntryInlineEvents } from '../shared/EmbeddedInlineUtil';
|
|
3
3
|
import { LinkedEntityInline } from './LinkedEntityInline';
|
|
4
4
|
export function createEmbeddedEntityInlinePlugin(sdk) {
|
|
5
5
|
const htmlAttributeName = 'data-embedded-entity-inline-id';
|
|
6
|
+
const nodeType = INLINES.EMBEDDED_ENTRY;
|
|
6
7
|
return {
|
|
7
|
-
key:
|
|
8
|
-
type:
|
|
8
|
+
key: nodeType,
|
|
9
|
+
type: nodeType,
|
|
9
10
|
isElement: true,
|
|
10
11
|
isInline: true,
|
|
11
12
|
isVoid: true,
|
|
@@ -14,7 +15,7 @@ export function createEmbeddedEntityInlinePlugin(sdk) {
|
|
|
14
15
|
hotkey: 'mod+shift+2'
|
|
15
16
|
},
|
|
16
17
|
handlers: {
|
|
17
|
-
onKeyDown: getWithEmbeddedEntryInlineEvents(sdk)
|
|
18
|
+
onKeyDown: getWithEmbeddedEntryInlineEvents(nodeType, sdk)
|
|
18
19
|
},
|
|
19
20
|
deserializeHtml: {
|
|
20
21
|
rules: [
|
|
@@ -23,7 +24,23 @@ export function createEmbeddedEntityInlinePlugin(sdk) {
|
|
|
23
24
|
}
|
|
24
25
|
],
|
|
25
26
|
withoutChildren: true,
|
|
26
|
-
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
|
+
})
|
|
27
44
|
}
|
|
28
45
|
};
|
|
29
46
|
}
|
|
@@ -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();
|
|
@@ -27,7 +27,7 @@ export function LinkedResourceBlock(props) {
|
|
|
27
27
|
return React.createElement(LinkedBlockWrapper, {
|
|
28
28
|
attributes: attributes,
|
|
29
29
|
link: element.data.target,
|
|
30
|
-
card: React.createElement(
|
|
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) {
|
|
@@ -7,7 +7,8 @@ import { css } from 'emotion';
|
|
|
7
7
|
import { useContentfulEditor } from '../../ContentfulEditorProvider';
|
|
8
8
|
import { moveToTheNextChar } from '../../helpers/editor';
|
|
9
9
|
import { useSdkContext } from '../../SdkProvider';
|
|
10
|
-
import { selectEntityAndInsert } from '../shared/EmbeddedInlineUtil';
|
|
10
|
+
import { selectEntityAndInsert, selectResourceEntityAndInsert } from '../shared/EmbeddedInlineUtil';
|
|
11
|
+
import { ResourceNewBadge } from './ResourceNewBadge';
|
|
11
12
|
const styles = {
|
|
12
13
|
icon: css({
|
|
13
14
|
marginRight: '10px'
|
|
@@ -21,20 +22,24 @@ const styles = {
|
|
|
21
22
|
}
|
|
22
23
|
})
|
|
23
24
|
};
|
|
24
|
-
export function EmbeddedInlineToolbarIcon(
|
|
25
|
+
export function EmbeddedInlineToolbarIcon({ onClose , nodeType , isDisabled }) {
|
|
25
26
|
const editor = useContentfulEditor();
|
|
26
27
|
const sdk = useSdkContext();
|
|
27
28
|
async function handleClick(event) {
|
|
28
29
|
event.preventDefault();
|
|
29
30
|
if (!editor) return;
|
|
30
|
-
|
|
31
|
-
|
|
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
|
+
}
|
|
32
37
|
moveToTheNextChar(editor);
|
|
33
38
|
}
|
|
34
39
|
return React.createElement(Menu.Item, {
|
|
35
|
-
disabled:
|
|
40
|
+
disabled: isDisabled,
|
|
36
41
|
className: "rich-text__entry-link-block-button",
|
|
37
|
-
testId: `toolbar-toggle-${
|
|
42
|
+
testId: `toolbar-toggle-${nodeType}`,
|
|
38
43
|
onClick: handleClick
|
|
39
44
|
}, React.createElement(Flex, {
|
|
40
45
|
alignItems: "center",
|
|
@@ -42,5 +47,5 @@ export function EmbeddedInlineToolbarIcon(props) {
|
|
|
42
47
|
}, React.createElement(EmbeddedEntryInlineIcon, {
|
|
43
48
|
variant: "secondary",
|
|
44
49
|
className: `rich-text__embedded-entry-list-icon ${styles.icon}`
|
|
45
|
-
}), React.createElement("span", null, "Inline entry")));
|
|
50
|
+
}), React.createElement("span", null, "Inline entry", nodeType == INLINES.EMBEDDED_RESOURCE && React.createElement(ResourceNewBadge, null))));
|
|
46
51
|
}
|
|
@@ -1,25 +1,59 @@
|
|
|
1
1
|
import { INLINES } from '@contentful/rich-text-types';
|
|
2
2
|
import isHotkey from 'is-hotkey';
|
|
3
|
+
import { newEntitySelectorConfigFromRichTextField, newResourceEntitySelectorConfigFromRichTextField } from '../../helpers/config';
|
|
3
4
|
import { focus } from '../../helpers/editor';
|
|
4
|
-
import newEntitySelectorConfigFromRichTextField from '../../helpers/newEntitySelectorConfigFromRichTextField';
|
|
5
5
|
import { watchCurrentSlide } from '../../helpers/sdkNavigatorSlideIn';
|
|
6
6
|
import { insertNodes, select } from '../../internal/transforms';
|
|
7
|
-
export function getWithEmbeddedEntryInlineEvents(sdk) {
|
|
7
|
+
export function getWithEmbeddedEntryInlineEvents(nodeType, sdk) {
|
|
8
8
|
return function withEmbeddedEntryInlineEvents(editor, { options: { hotkey } }) {
|
|
9
9
|
return function handleEvent(event) {
|
|
10
10
|
if (!editor) return;
|
|
11
11
|
if (hotkey && isHotkey(hotkey, event)) {
|
|
12
|
-
|
|
12
|
+
if (nodeType === INLINES.EMBEDDED_RESOURCE) {
|
|
13
|
+
selectResourceEntityAndInsert(editor, sdk, editor.tracking.onShortcutAction);
|
|
14
|
+
} else {
|
|
15
|
+
selectEntityAndInsert(editor, sdk, editor.tracking.onShortcutAction);
|
|
16
|
+
}
|
|
13
17
|
}
|
|
14
18
|
};
|
|
15
19
|
};
|
|
16
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
|
+
};
|
|
17
50
|
export async function selectEntityAndInsert(editor, sdk, logAction) {
|
|
51
|
+
const nodeType = INLINES.EMBEDDED_ENTRY;
|
|
18
52
|
logAction('openCreateEmbedDialog', {
|
|
19
|
-
nodeType
|
|
53
|
+
nodeType
|
|
20
54
|
});
|
|
21
55
|
const config = {
|
|
22
|
-
...newEntitySelectorConfigFromRichTextField(sdk.field,
|
|
56
|
+
...newEntitySelectorConfigFromRichTextField(sdk.field, nodeType),
|
|
23
57
|
withCreate: true
|
|
24
58
|
};
|
|
25
59
|
const { selection } = editor;
|
|
@@ -27,13 +61,13 @@ export async function selectEntityAndInsert(editor, sdk, logAction) {
|
|
|
27
61
|
const entry = await sdk.dialogs.selectSingleEntry(config);
|
|
28
62
|
if (!entry) {
|
|
29
63
|
logAction('cancelCreateEmbedDialog', {
|
|
30
|
-
nodeType
|
|
64
|
+
nodeType
|
|
31
65
|
});
|
|
32
66
|
} else {
|
|
33
67
|
select(editor, selection);
|
|
34
|
-
insertNodes(editor, createInlineEntryNode(entry
|
|
68
|
+
insertNodes(editor, createInlineEntryNode(nodeType, entry));
|
|
35
69
|
logAction('insert', {
|
|
36
|
-
nodeType
|
|
70
|
+
nodeType
|
|
37
71
|
});
|
|
38
72
|
}
|
|
39
73
|
rteSlide.onActive(()=>{
|
|
@@ -41,22 +75,27 @@ export async function selectEntityAndInsert(editor, sdk, logAction) {
|
|
|
41
75
|
focus(editor);
|
|
42
76
|
});
|
|
43
77
|
}
|
|
44
|
-
export function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
target: {
|
|
54
|
-
sys: {
|
|
55
|
-
id,
|
|
56
|
-
type: 'Link',
|
|
57
|
-
linkType: 'Entry'
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
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
|
|
61
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
|
+
}
|
|
62
101
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Badge } from '@contentful/f36-components';
|
|
3
|
+
export const ResourceNewBadge = ()=>{
|
|
4
|
+
return React.createElement(React.Fragment, null, ' ', "(different space)", ' ', React.createElement(Badge, {
|
|
5
|
+
variant: "primary-filled",
|
|
6
|
+
size: "small"
|
|
7
|
+
}, "new"));
|
|
8
|
+
};
|
|
@@ -6,7 +6,7 @@ import { configure, render, waitFor } from '@testing-library/react';
|
|
|
6
6
|
import publishedCT from '../__fixtures__/published_content_type.json';
|
|
7
7
|
import publishedEntry from '../__fixtures__/published_entry.json';
|
|
8
8
|
import space from '../__fixtures__/space.json';
|
|
9
|
-
import {
|
|
9
|
+
import { FetchingWrappedResourceBlockCard } from '../../EmbeddedResourceBlock/FetchingWrappedResourceBlockCard';
|
|
10
10
|
configure({
|
|
11
11
|
testIdAttribute: 'data-test-id'
|
|
12
12
|
});
|
|
@@ -63,7 +63,7 @@ beforeEach(()=>{
|
|
|
63
63
|
function renderResourceCard({ linkType ='Contentful:Entry' , entryUrn =resolvableEntryUrn } = {}) {
|
|
64
64
|
return render(React.createElement(EntityProvider, {
|
|
65
65
|
sdk: sdk
|
|
66
|
-
}, React.createElement(
|
|
66
|
+
}, React.createElement(FetchingWrappedResourceBlockCard, {
|
|
67
67
|
isDisabled: false,
|
|
68
68
|
isSelected: false,
|
|
69
69
|
sdk: sdk,
|