@contentful/field-editor-rich-text 3.5.0 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/RichTextEditor.js +42 -53
- package/dist/cjs/SyncEditorValue.js +107 -0
- package/dist/cjs/Toolbar/components/EmbedEntityWidget.js +8 -3
- package/dist/cjs/helpers/__tests__/removeInternalMarks.test.js +37 -21
- package/dist/cjs/helpers/callbacks.js +35 -0
- package/dist/cjs/helpers/getAllowedResourcesForNodeType.js +25 -0
- package/dist/cjs/helpers/newResourceEntitySelectorConfigFromRichTextField.js +21 -0
- package/dist/cjs/helpers/toSlateValue.js +51 -0
- package/dist/cjs/internal/hooks.js +12 -2
- package/dist/cjs/internal/misc.js +0 -6
- 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/Table/onKeyDownTable.js +14 -0
- package/dist/cjs/plugins/Text/__tests__/createTextPlugin.test.js +5 -15
- package/dist/cjs/plugins/Text/createTextPlugin.js +1 -0
- package/dist/cjs/plugins/index.js +2 -5
- 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/esm/RichTextEditor.js +37 -48
- package/dist/esm/SyncEditorValue.js +53 -0
- package/dist/esm/Toolbar/components/EmbedEntityWidget.js +8 -3
- package/dist/esm/helpers/__tests__/removeInternalMarks.test.js +37 -21
- package/dist/esm/helpers/callbacks.js +20 -0
- package/dist/esm/helpers/getAllowedResourcesForNodeType.js +10 -0
- package/dist/esm/helpers/newResourceEntitySelectorConfigFromRichTextField.js +6 -0
- package/dist/{cjs/helpers/sanitizeIncomingSlateDoc.js → esm/helpers/toSlateValue.js} +13 -10
- package/dist/esm/internal/hooks.js +9 -2
- package/dist/esm/internal/misc.js +0 -3
- 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/Table/onKeyDownTable.js +15 -1
- package/dist/esm/plugins/Text/__tests__/createTextPlugin.test.js +5 -15
- package/dist/esm/plugins/Text/createTextPlugin.js +1 -0
- package/dist/esm/plugins/index.js +2 -5
- 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/types/ContentfulEditorProvider.d.ts +2 -3
- package/dist/types/RichTextEditor.d.ts +2 -2
- package/dist/types/SyncEditorValue.d.ts +13 -0
- package/dist/types/dialogs/HypelinkDialog/HyperlinkDialog.d.ts +3 -3
- package/dist/types/helpers/callbacks.d.ts +3 -0
- package/dist/types/helpers/getAllowedResourcesForNodeType.d.ts +25 -0
- package/dist/types/helpers/newResourceEntitySelectorConfigFromRichTextField.d.ts +16 -0
- package/dist/types/helpers/toSlateValue.d.ts +7 -0
- package/dist/types/internal/hooks.d.ts +4 -2
- package/dist/types/internal/misc.d.ts +2 -2
- 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/createEditor.d.ts +2 -0
- package/dist/types/test-utils/jsx.d.ts +1 -1
- package/package.json +18 -18
- package/dist/cjs/plugins/EmbeddedEntityBlock/Util.js +0 -108
- package/dist/cjs/prepareDocument.js +0 -86
- package/dist/cjs/useOnValueChanged.js +0 -58
- package/dist/esm/helpers/sanitizeIncomingSlateDoc.js +0 -23
- package/dist/esm/plugins/EmbeddedEntityBlock/Util.js +0 -85
- package/dist/esm/prepareDocument.js +0 -57
- package/dist/esm/useOnValueChanged.js +0 -43
- package/dist/types/helpers/sanitizeIncomingSlateDoc.d.ts +0 -6
- package/dist/types/plugins/EmbeddedEntityBlock/ToolbarIcon.d.ts +0 -11
- package/dist/types/plugins/EmbeddedEntityBlock/Util.d.ts +0 -4
- package/dist/types/prepareDocument.d.ts +0 -19
- package/dist/types/useOnValueChanged.d.ts +0 -8
|
@@ -1,45 +1,21 @@
|
|
|
1
1
|
import { BLOCKS } from '@contentful/rich-text-types';
|
|
2
|
-
import
|
|
3
|
-
import { getNodeEntryFromSelection } from '../../helpers/editor';
|
|
4
|
-
import { removeNodes } from '../../internal/transforms';
|
|
5
|
-
import { withLinkTracking } from '../links-tracking';
|
|
2
|
+
import { getWithEmbeddedBlockEvents } from '../shared/EmbeddedBlockUtil';
|
|
6
3
|
import { LinkedEntityBlock } from './LinkedEntityBlock';
|
|
7
|
-
import { selectEntityAndInsert } from './Util';
|
|
8
|
-
export { EmbeddedEntityBlockToolbarIcon as ToolbarIcon } from './ToolbarIcon';
|
|
9
4
|
const entityTypes = {
|
|
10
5
|
[BLOCKS.EMBEDDED_ENTRY]: 'Entry',
|
|
11
6
|
[BLOCKS.EMBEDDED_ASSET]: 'Asset'
|
|
12
7
|
};
|
|
13
|
-
function getWithEmbeddedEntityEvents(nodeType, sdk) {
|
|
14
|
-
return (editor, { options: { hotkey } })=>(event)=>{
|
|
15
|
-
const [, pathToSelectedElement] = getNodeEntryFromSelection(editor, nodeType);
|
|
16
|
-
if (pathToSelectedElement) {
|
|
17
|
-
const isDelete = event.key === 'Delete';
|
|
18
|
-
const isBackspace = event.key === 'Backspace';
|
|
19
|
-
if (isDelete || isBackspace) {
|
|
20
|
-
event.preventDefault();
|
|
21
|
-
removeNodes(editor, {
|
|
22
|
-
at: pathToSelectedElement
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
if (hotkey && isHotkey(hotkey, event)) {
|
|
28
|
-
selectEntityAndInsert(nodeType, sdk, editor, editor.tracking.onShortcutAction);
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
8
|
const createEmbeddedEntityPlugin = (nodeType, hotkey)=>(sdk)=>({
|
|
33
9
|
key: nodeType,
|
|
34
10
|
type: nodeType,
|
|
35
11
|
isElement: true,
|
|
36
12
|
isVoid: true,
|
|
37
|
-
component:
|
|
13
|
+
component: LinkedEntityBlock,
|
|
38
14
|
options: {
|
|
39
15
|
hotkey
|
|
40
16
|
},
|
|
41
17
|
handlers: {
|
|
42
|
-
onKeyDown:
|
|
18
|
+
onKeyDown: getWithEmbeddedBlockEvents(nodeType, sdk)
|
|
43
19
|
},
|
|
44
20
|
deserializeHtml: {
|
|
45
21
|
rules: [
|
|
@@ -14,7 +14,7 @@ import { watchCurrentSlide } from '../../helpers/sdkNavigatorSlideIn';
|
|
|
14
14
|
import { findNodePath } from '../../internal/queries';
|
|
15
15
|
import { insertNodes, removeNodes, select } from '../../internal/transforms';
|
|
16
16
|
import { useSdkContext } from '../../SdkProvider';
|
|
17
|
-
import {
|
|
17
|
+
import { useLinkTracking } from '../links-tracking';
|
|
18
18
|
import { FetchingWrappedInlineEntryCard } from './FetchingWrappedInlineEntryCard';
|
|
19
19
|
import { createInlineEntryNode } from './Util';
|
|
20
20
|
const styles = {
|
|
@@ -36,6 +36,7 @@ function EmbeddedEntityInline(props) {
|
|
|
36
36
|
const isSelected = useSelected();
|
|
37
37
|
const { id: entryId } = props.element.data.target.sys;
|
|
38
38
|
const isDisabled = useReadOnly();
|
|
39
|
+
const { onEntityFetchComplete } = useLinkTracking();
|
|
39
40
|
function handleEditClick() {
|
|
40
41
|
return sdk.navigator.openEntry(entryId, {
|
|
41
42
|
slideIn: {
|
|
@@ -68,7 +69,7 @@ function EmbeddedEntityInline(props) {
|
|
|
68
69
|
isDisabled: isDisabled,
|
|
69
70
|
onRemove: handleRemoveClick,
|
|
70
71
|
onEdit: handleEditClick,
|
|
71
|
-
onEntityFetchComplete:
|
|
72
|
+
onEntityFetchComplete: onEntityFetchComplete
|
|
72
73
|
})), props.children);
|
|
73
74
|
}
|
|
74
75
|
async function selectEntityAndInsert(editor, sdk, logAction) {
|
|
@@ -129,7 +130,7 @@ export function createEmbeddedEntityInlinePlugin(sdk) {
|
|
|
129
130
|
isElement: true,
|
|
130
131
|
isInline: true,
|
|
131
132
|
isVoid: true,
|
|
132
|
-
component:
|
|
133
|
+
component: EmbeddedEntityInline,
|
|
133
134
|
options: {
|
|
134
135
|
hotkey: 'mod+shift+2'
|
|
135
136
|
},
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useSelected, useReadOnly } from 'slate-react';
|
|
3
|
+
import { useContentfulEditor } from '../../ContentfulEditorProvider';
|
|
4
|
+
import { findNodePath, removeNodes } from '../../internal';
|
|
5
|
+
import { useSdkContext } from '../../SdkProvider';
|
|
6
|
+
import { useLinkTracking } from '../links-tracking';
|
|
7
|
+
import { FetchingWrappedResourceCard } from '../shared/FetchingWrappedResourceCard';
|
|
8
|
+
import { LinkedBlockWrapper } from '../shared/LinkedBlockWrapper';
|
|
9
|
+
export function LinkedResourceBlock(props) {
|
|
10
|
+
const { attributes , children , element } = props;
|
|
11
|
+
const { onEntityFetchComplete } = useLinkTracking();
|
|
12
|
+
const isSelected = useSelected();
|
|
13
|
+
const editor = useContentfulEditor();
|
|
14
|
+
const sdk = useSdkContext();
|
|
15
|
+
const isDisabled = useReadOnly();
|
|
16
|
+
const link = element.data.target.sys;
|
|
17
|
+
const handleRemoveClick = React.useCallback(()=>{
|
|
18
|
+
if (!editor) return;
|
|
19
|
+
const pathToElement = findNodePath(editor, element);
|
|
20
|
+
removeNodes(editor, {
|
|
21
|
+
at: pathToElement
|
|
22
|
+
});
|
|
23
|
+
}, [
|
|
24
|
+
editor,
|
|
25
|
+
element
|
|
26
|
+
]);
|
|
27
|
+
return React.createElement(LinkedBlockWrapper, {
|
|
28
|
+
attributes: attributes,
|
|
29
|
+
element: element,
|
|
30
|
+
card: React.createElement(FetchingWrappedResourceCard, {
|
|
31
|
+
sdk: sdk,
|
|
32
|
+
link: link,
|
|
33
|
+
isDisabled: isDisabled,
|
|
34
|
+
isSelected: isSelected,
|
|
35
|
+
onRemove: handleRemoveClick,
|
|
36
|
+
onEntityFetchComplete: onEntityFetchComplete
|
|
37
|
+
})
|
|
38
|
+
}, children);
|
|
39
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { BLOCKS } from '@contentful/rich-text-types';
|
|
2
|
+
import { getWithEmbeddedBlockEvents } from '../shared/EmbeddedBlockUtil';
|
|
3
|
+
import { LinkedResourceBlock } from './LinkedResourceBlock';
|
|
4
|
+
const createEmbeddedResourcePlugin = (nodeType, hotkey)=>(sdk)=>({
|
|
5
|
+
key: nodeType,
|
|
6
|
+
type: nodeType,
|
|
7
|
+
isElement: true,
|
|
8
|
+
isVoid: true,
|
|
9
|
+
component: LinkedResourceBlock,
|
|
10
|
+
options: {
|
|
11
|
+
hotkey
|
|
12
|
+
},
|
|
13
|
+
handlers: {
|
|
14
|
+
onKeyDown: getWithEmbeddedBlockEvents(nodeType, sdk)
|
|
15
|
+
},
|
|
16
|
+
deserializeHtml: {
|
|
17
|
+
rules: [
|
|
18
|
+
{
|
|
19
|
+
validAttribute: {
|
|
20
|
+
'data-entity-type': 'Contentful:Entry'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
withoutChildren: true,
|
|
25
|
+
getNode: (el)=>({
|
|
26
|
+
type: nodeType,
|
|
27
|
+
children: [
|
|
28
|
+
{
|
|
29
|
+
text: ''
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
isVoid: true,
|
|
33
|
+
data: {
|
|
34
|
+
target: {
|
|
35
|
+
sys: {
|
|
36
|
+
urn: el.getAttribute('data-entity-id'),
|
|
37
|
+
linkType: el.getAttribute('data-entity-type'),
|
|
38
|
+
type: 'ResourceLink'
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
export const createEmbeddedResourceBlockPlugin = createEmbeddedResourcePlugin(BLOCKS.EMBEDDED_RESOURCE, 'mod+shift+s');
|
|
@@ -3,6 +3,7 @@ import { Tooltip, TextLink } from '@contentful/f36-components';
|
|
|
3
3
|
import { useContentfulEditor } from '../../../ContentfulEditorProvider';
|
|
4
4
|
import { fromDOMPoint } from '../../../internal';
|
|
5
5
|
import { useSdkContext } from '../../../SdkProvider';
|
|
6
|
+
import { useLinkTracking } from '../../links-tracking';
|
|
6
7
|
import { addOrEditLink } from '../HyperlinkModal';
|
|
7
8
|
import { useEntityInfo } from '../useEntityInfo';
|
|
8
9
|
import { styles } from './styles';
|
|
@@ -10,7 +11,7 @@ export function EntityHyperlink(props) {
|
|
|
10
11
|
const editor = useContentfulEditor();
|
|
11
12
|
const sdk = useSdkContext();
|
|
12
13
|
const { target } = props.element.data;
|
|
13
|
-
const { onEntityFetchComplete } =
|
|
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
|
{
|
|
@@ -2,7 +2,7 @@ import { BLOCKS } from '@contentful/rich-text-types';
|
|
|
2
2
|
import { getTableEntries, onKeyDownTable as defaultKeyDownTable } from '@udecode/plate-table';
|
|
3
3
|
import { insertEmptyParagraph } from '../../helpers/editor';
|
|
4
4
|
import { blurEditor } from '../../internal/misc';
|
|
5
|
-
import { getAboveNode, isLastChildPath } from '../../internal/queries';
|
|
5
|
+
import { getAboveNode, isLastChildPath, getText, isFirstChild } from '../../internal/queries';
|
|
6
6
|
import { addRowBelow } from './actions';
|
|
7
7
|
export const onKeyDownTable = (editor, plugin)=>{
|
|
8
8
|
const defaultHandler = defaultKeyDownTable(editor, plugin);
|
|
@@ -30,6 +30,20 @@ export const onKeyDownTable = (editor, plugin)=>{
|
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
if (event.key === 'Backspace') {
|
|
34
|
+
const entry = getTableEntries(editor, {});
|
|
35
|
+
if (entry) {
|
|
36
|
+
const { table , row , cell } = entry;
|
|
37
|
+
const cellText = getText(editor, cell[1]);
|
|
38
|
+
const isFirstCell = isFirstChild(row[1]);
|
|
39
|
+
const isFirstRow = isFirstChild(table[1]);
|
|
40
|
+
if (isFirstCell && isFirstRow && !cellText) {
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
event.stopPropagation();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
33
47
|
if (event.key === 'Tab' && !event.shiftKey) {
|
|
34
48
|
event.preventDefault();
|
|
35
49
|
const entry = getTableEntries(editor, {});
|
|
@@ -7,19 +7,9 @@ describe('delete backward', ()=>{
|
|
|
7
7
|
expected: jsx("hul", null, jsx("hli", null, jsx("hp", null, "p", jsx("cursor", null))))
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
|
-
title: '
|
|
10
|
+
title: 'does not delete the very first paragraph',
|
|
11
11
|
input: jsx("fragment", null, jsx("hp", null, jsx("cursor", null)), jsx("hp", null, "text")),
|
|
12
|
-
expected: jsx("hp", null, jsx("cursor", null), "text")
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
title: 'deletes the empty paragraph at the beginning of the RTE followed by li',
|
|
16
|
-
input: jsx("fragment", null, jsx("hp", null, jsx("cursor", null)), jsx("hul", null, jsx("hli", null, jsx("hp", null, "p1")))),
|
|
17
|
-
expected: jsx("hul", null, jsx("hli", null, jsx("hp", null, jsx("cursor", null), "p1")))
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
title: 'deletes the empty paragraph at the beginning of the RTE followed by a blockquote',
|
|
21
|
-
input: jsx("fragment", null, jsx("hp", null, jsx("cursor", null)), jsx("hquote", null, jsx("hp", null, "p1"))),
|
|
22
|
-
expected: jsx("hquote", null, jsx("hp", null, jsx("cursor", null), "p1"))
|
|
12
|
+
expected: jsx("fragment", null, jsx("hp", null, jsx("cursor", null)), jsx("hp", null, "text"))
|
|
23
13
|
}
|
|
24
14
|
];
|
|
25
15
|
const render = (children)=>jsx("editor", null, children, jsx("hp", null, jsx("htext", null)));
|
|
@@ -44,17 +34,17 @@ describe('delete forward', ()=>{
|
|
|
44
34
|
expected: jsx("hul", null, jsx("hli", null, jsx("hp", null, jsx("cursor", null), "1")))
|
|
45
35
|
},
|
|
46
36
|
{
|
|
47
|
-
title: 'deletes the
|
|
37
|
+
title: 'deletes the first paragraph when followed by another paragraph',
|
|
48
38
|
input: jsx("fragment", null, jsx("hp", null, jsx("cursor", null)), jsx("hp", null, "text")),
|
|
49
39
|
expected: jsx("hp", null, jsx("cursor", null), "text")
|
|
50
40
|
},
|
|
51
41
|
{
|
|
52
|
-
title: 'deletes the
|
|
42
|
+
title: 'deletes the first paragraph when followed by li',
|
|
53
43
|
input: jsx("fragment", null, jsx("hp", null, jsx("cursor", null)), jsx("hul", null, jsx("hli", null, jsx("hp", null, "p1")))),
|
|
54
44
|
expected: jsx("hul", null, jsx("hli", null, jsx("hp", null, jsx("cursor", null), "p1")))
|
|
55
45
|
},
|
|
56
46
|
{
|
|
57
|
-
title: 'deletes the
|
|
47
|
+
title: 'deletes the first paragraph when followed by a blockquote',
|
|
58
48
|
input: jsx("fragment", null, jsx("hp", null, jsx("cursor", null)), jsx("hquote", null, jsx("hp", null, "p1"))),
|
|
59
49
|
expected: jsx("hquote", null, jsx("hp", null, jsx("cursor", null), "p1"))
|
|
60
50
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createDeserializeAstPlugin, createDeserializeHtmlPlugin } from '@udecode/plate-core';
|
|
2
1
|
import { createDeserializeDocxPlugin } from '@udecode/plate-serializer-docx';
|
|
3
2
|
import { createSoftBreakPlugin, createExitBreakPlugin, createResetNodePlugin } from './Break';
|
|
4
3
|
import { createCommandPalettePlugin } from './CommandPalette';
|
|
@@ -6,6 +5,7 @@ import { isCommandPromptPluginEnabled } from './CommandPalette/useCommands';
|
|
|
6
5
|
import { createDragAndDropPlugin } from './DragAndDrop';
|
|
7
6
|
import { createEmbeddedAssetBlockPlugin, createEmbeddedEntryBlockPlugin } from './EmbeddedEntityBlock';
|
|
8
7
|
import { createEmbeddedEntityInlinePlugin } from './EmbeddedEntityInline';
|
|
8
|
+
import { createEmbeddedResourceBlockPlugin } from './EmbeddedResourceBlock';
|
|
9
9
|
import { createHeadingPlugin } from './Heading';
|
|
10
10
|
import { createHrPlugin } from './Hr';
|
|
11
11
|
import { createHyperlinkPlugin } from './Hyperlink';
|
|
@@ -22,8 +22,6 @@ import { createTrackingPlugin } from './Tracking';
|
|
|
22
22
|
import { createTrailingParagraphPlugin } from './TrailingParagraph';
|
|
23
23
|
import { createVoidsPlugin } from './Voids';
|
|
24
24
|
export const getPlugins = (sdk, onAction, restrictedMarks)=>[
|
|
25
|
-
createDeserializeHtmlPlugin(),
|
|
26
|
-
createDeserializeAstPlugin(),
|
|
27
25
|
createDeserializeDocxPlugin(),
|
|
28
26
|
createTrackingPlugin(onAction),
|
|
29
27
|
createDragAndDropPlugin(),
|
|
@@ -38,6 +36,7 @@ export const getPlugins = (sdk, onAction, restrictedMarks)=>[
|
|
|
38
36
|
createTablePlugin(),
|
|
39
37
|
createEmbeddedEntryBlockPlugin(sdk),
|
|
40
38
|
createEmbeddedAssetBlockPlugin(sdk),
|
|
39
|
+
createEmbeddedResourceBlockPlugin(sdk),
|
|
41
40
|
createHyperlinkPlugin(sdk),
|
|
42
41
|
createEmbeddedEntityInlinePlugin(sdk),
|
|
43
42
|
createMarksPlugin(),
|
|
@@ -52,7 +51,5 @@ export const getPlugins = (sdk, onAction, restrictedMarks)=>[
|
|
|
52
51
|
createNormalizerPlugin()
|
|
53
52
|
];
|
|
54
53
|
export const disableCorePlugins = {
|
|
55
|
-
deserializeAst: true,
|
|
56
|
-
deserializeHtml: true,
|
|
57
54
|
eventEditor: true
|
|
58
55
|
};
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import
|
|
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
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { FieldExtensionSDK } from '@contentful/app-sdk';
|
|
3
|
-
import { PlateEditor } from './internal/types';
|
|
4
3
|
export declare function getContentfulEditorId(sdk: FieldExtensionSDK): string;
|
|
5
4
|
export declare const editorContext: import("react").Context<string>;
|
|
6
5
|
export declare const ContentfulEditorIdProvider: import("react").Provider<string>;
|
|
7
6
|
export declare function useContentfulEditorId(id?: string): string;
|
|
8
|
-
export declare function useContentfulEditor(id?: string): PlateEditor
|
|
9
|
-
export declare function useContentfulEditorRef(id?: string): PlateEditor
|
|
7
|
+
export declare function useContentfulEditor(id?: string): import("./internal").PlateEditor;
|
|
8
|
+
export declare function useContentfulEditorRef(id?: string): import("./internal").PlateEditor;
|