@contentful/field-editor-reference 6.19.0 → 6.19.1-canary.18
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/__fixtures__/FakeSdk.js +13 -20
- package/dist/cjs/assets/WrappedAssetCard/AssetCardActions.js +11 -1
- package/dist/cjs/assets/WrappedAssetCard/FetchingWrappedAssetCard.js +10 -1
- package/dist/cjs/assets/WrappedAssetCard/WrappedAssetCard.js +4 -2
- package/dist/cjs/common/EntityStore.js +45 -26
- package/dist/cjs/common/MultipleReferenceEditor.js +6 -1
- package/dist/cjs/common/SingleReferenceEditor.js +8 -2
- package/dist/cjs/common/queryClient.js +4 -91
- package/dist/cjs/common/useContentTypePermissions.js +6 -9
- package/dist/cjs/common/useEditorPermissions.js +5 -1
- package/dist/cjs/components/LinkActions/LinkEntityActions.js +2 -0
- package/dist/cjs/entries/MultipleEntryReferenceEditor.js +2 -1
- package/dist/cjs/entries/SingleEntryReferenceEditor.js +1 -0
- package/dist/cjs/entries/WrappedEntryCard/FetchingWrappedEntryCard.js +10 -1
- package/dist/cjs/entries/WrappedEntryCard/WrappedEntryCard.js +13 -2
- package/dist/cjs/resources/Cards/ResourceCard.spec.js +28 -2
- package/dist/esm/__fixtures__/FakeSdk.js +13 -20
- package/dist/esm/assets/WrappedAssetCard/AssetCardActions.js +11 -1
- package/dist/esm/assets/WrappedAssetCard/FetchingWrappedAssetCard.js +10 -1
- package/dist/esm/assets/WrappedAssetCard/WrappedAssetCard.js +4 -2
- package/dist/esm/common/EntityStore.js +45 -26
- package/dist/esm/common/MultipleReferenceEditor.js +6 -1
- package/dist/esm/common/SingleReferenceEditor.js +8 -2
- package/dist/esm/common/queryClient.js +1 -47
- package/dist/esm/common/useContentTypePermissions.js +6 -9
- package/dist/esm/common/useEditorPermissions.js +5 -1
- package/dist/esm/components/LinkActions/LinkEntityActions.js +2 -0
- package/dist/esm/entries/MultipleEntryReferenceEditor.js +2 -1
- package/dist/esm/entries/SingleEntryReferenceEditor.js +1 -0
- package/dist/esm/entries/WrappedEntryCard/FetchingWrappedEntryCard.js +10 -1
- package/dist/esm/entries/WrappedEntryCard/WrappedEntryCard.js +13 -2
- package/dist/esm/resources/Cards/ResourceCard.spec.js +28 -2
- package/dist/types/assets/WrappedAssetCard/AssetCardActions.d.ts +1 -0
- package/dist/types/assets/WrappedAssetCard/FetchingWrappedAssetCard.d.ts +5 -1
- package/dist/types/assets/WrappedAssetCard/WrappedAssetCard.d.ts +2 -1
- package/dist/types/common/EntityStore.d.ts +4 -2
- package/dist/types/common/ReferenceEditor.d.ts +5 -1
- package/dist/types/common/customCardTypes.d.ts +1 -0
- package/dist/types/common/queryClient.d.ts +3 -7
- package/dist/types/entries/WrappedEntryCard/FetchingWrappedEntryCard.d.ts +5 -1
- package/dist/types/entries/WrappedEntryCard/WrappedEntryCard.d.ts +2 -1
- package/package.json +4 -4
|
@@ -35,15 +35,6 @@ export function newReferenceEditorFakeSdk(props) {
|
|
|
35
35
|
const delay = (ms)=>{
|
|
36
36
|
return new Promise((resolve)=>setTimeout(resolve, ms));
|
|
37
37
|
};
|
|
38
|
-
const localizeContentTypes = (contentTypes)=>{
|
|
39
|
-
return contentTypes.map((contentType)=>({
|
|
40
|
-
...contentType,
|
|
41
|
-
fields: contentType.fields.map((field)=>({
|
|
42
|
-
...field,
|
|
43
|
-
localized: true
|
|
44
|
-
}))
|
|
45
|
-
}));
|
|
46
|
-
};
|
|
47
38
|
const sdk = {
|
|
48
39
|
field,
|
|
49
40
|
locales,
|
|
@@ -96,6 +87,19 @@ export function newReferenceEditorFakeSdk(props) {
|
|
|
96
87
|
return contentTypes.published;
|
|
97
88
|
}
|
|
98
89
|
return Promise.reject({});
|
|
90
|
+
},
|
|
91
|
+
getMany: async ()=>{
|
|
92
|
+
return Promise.resolve({
|
|
93
|
+
items: [
|
|
94
|
+
contentTypes.published
|
|
95
|
+
],
|
|
96
|
+
total: 1,
|
|
97
|
+
skip: 0,
|
|
98
|
+
limit: 1000,
|
|
99
|
+
sys: {
|
|
100
|
+
type: 'Array'
|
|
101
|
+
}
|
|
102
|
+
});
|
|
99
103
|
}
|
|
100
104
|
},
|
|
101
105
|
Locale: {
|
|
@@ -104,17 +108,6 @@ export function newReferenceEditorFakeSdk(props) {
|
|
|
104
108
|
},
|
|
105
109
|
space: {
|
|
106
110
|
...space,
|
|
107
|
-
getCachedContentTypes () {
|
|
108
|
-
return localizeContentTypes(space.getCachedContentTypes());
|
|
109
|
-
},
|
|
110
|
-
getContentTypes () {
|
|
111
|
-
return Promise.resolve(space.getContentTypes().then((response)=>{
|
|
112
|
-
return {
|
|
113
|
-
...response,
|
|
114
|
-
items: localizeContentTypes(response.items)
|
|
115
|
-
};
|
|
116
|
-
}));
|
|
117
|
-
},
|
|
118
111
|
async getEntityScheduledActions () {
|
|
119
112
|
return [];
|
|
120
113
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { MenuItem, Text, MenuSectionTitle } from '@contentful/f36-components';
|
|
3
|
+
import { PlusIcon } from '@contentful/f36-icons';
|
|
3
4
|
import tokens from '@contentful/f36-tokens';
|
|
4
5
|
import { shortenStorageUnit } from '@contentful/field-editor-shared';
|
|
5
6
|
import { css } from 'emotion';
|
|
@@ -70,7 +71,7 @@ export function renderAssetInfo(props) {
|
|
|
70
71
|
];
|
|
71
72
|
}
|
|
72
73
|
export function renderActions(props) {
|
|
73
|
-
const { entityFile, isDisabled, onEdit, onRemove } = props;
|
|
74
|
+
const { entityFile, isDisabled, onEdit, onRemove, onAddToReleaseAction } = props;
|
|
74
75
|
return [
|
|
75
76
|
/*#__PURE__*/ React.createElement(MenuSectionTitle, {
|
|
76
77
|
key: "section-title"
|
|
@@ -80,6 +81,15 @@ export function renderActions(props) {
|
|
|
80
81
|
onClick: onEdit,
|
|
81
82
|
testId: "card-action-edit"
|
|
82
83
|
}, "Edit") : null,
|
|
84
|
+
onAddToReleaseAction ? /*#__PURE__*/ React.createElement(MenuItem, {
|
|
85
|
+
key: "add-to-release",
|
|
86
|
+
testId: "add-to-release",
|
|
87
|
+
onClick: ()=>{
|
|
88
|
+
onAddToReleaseAction();
|
|
89
|
+
}
|
|
90
|
+
}, /*#__PURE__*/ React.createElement(PlusIcon, {
|
|
91
|
+
size: "tiny"
|
|
92
|
+
}), "Add to release") : null,
|
|
83
93
|
entityFile ? /*#__PURE__*/ React.createElement(MenuItem, {
|
|
84
94
|
key: "download",
|
|
85
95
|
onClick: ()=>{
|
|
@@ -21,6 +21,14 @@ export function FetchingWrappedAssetCard(props) {
|
|
|
21
21
|
locales: props.sdk.locales,
|
|
22
22
|
isReference: true
|
|
23
23
|
});
|
|
24
|
+
const onAddToRelease = ()=>{
|
|
25
|
+
if (asset && props.addReferenceToRelease) {
|
|
26
|
+
void props.addReferenceToRelease(asset, props.sdk.field.locale, {
|
|
27
|
+
openModalForVersionSelection: true,
|
|
28
|
+
skipNestedReferencesPrompt: true
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
};
|
|
24
32
|
React.useEffect(()=>{
|
|
25
33
|
if (asset) {
|
|
26
34
|
props.onAction && props.onAction({
|
|
@@ -87,7 +95,8 @@ export function FetchingWrappedAssetCard(props) {
|
|
|
87
95
|
activeLocales,
|
|
88
96
|
releaseStatusMap,
|
|
89
97
|
release: props.sdk.release,
|
|
90
|
-
releaseEntityStatus
|
|
98
|
+
releaseEntityStatus,
|
|
99
|
+
onAddToRelease
|
|
91
100
|
};
|
|
92
101
|
if (status === 'loading') {
|
|
93
102
|
return props.viewType === 'link' ? /*#__PURE__*/ React.createElement(EntryCard, {
|
|
@@ -31,7 +31,7 @@ function getFileType(file) {
|
|
|
31
31
|
return groupToIconMap[groupName] || 'archive';
|
|
32
32
|
}
|
|
33
33
|
const THUMBNAIL_SIZE = 150;
|
|
34
|
-
export const WrappedAssetCard = ({ asset, className, size, localeCode, defaultLocaleCode, activeLocales, localesStatusMap, isDisabled, isSelected, isClickable, useLocalizedEntityStatus, renderDragHandle, getEntityScheduledActions, onEdit, getAssetUrl, onRemove, releaseEntityStatus, releaseStatusMap, release })=>{
|
|
34
|
+
export const WrappedAssetCard = ({ asset, className, size, localeCode, defaultLocaleCode, activeLocales, localesStatusMap, isDisabled, isSelected, isClickable, useLocalizedEntityStatus, renderDragHandle, getEntityScheduledActions, onEdit, getAssetUrl, onRemove, releaseEntityStatus, releaseStatusMap, release, onAddToRelease })=>{
|
|
35
35
|
const status = entityHelpers.getEntityStatus(asset.sys, useLocalizedEntityStatus ? localeCode : undefined);
|
|
36
36
|
const entityFile = asset.fields.file ? asset.fields.file[localeCode] || asset.fields.file[defaultLocaleCode] : undefined;
|
|
37
37
|
const imageUrl = React.useMemo(()=>{
|
|
@@ -64,6 +64,7 @@ export const WrappedAssetCard = ({ asset, className, size, localeCode, defaultLo
|
|
|
64
64
|
defaultTitle: 'Untitled'
|
|
65
65
|
});
|
|
66
66
|
const href = getAssetUrl ? getAssetUrl(asset.sys.id) : undefined;
|
|
67
|
+
const onAddToReleaseAction = releaseEntityStatus === 'notInRelease' && release !== undefined && onAddToRelease !== undefined && !isDisabled ? onAddToRelease : undefined;
|
|
67
68
|
return /*#__PURE__*/ React.createElement(AssetCard, {
|
|
68
69
|
as: isClickable && href ? 'a' : 'article',
|
|
69
70
|
type: getFileType(entityFile),
|
|
@@ -102,7 +103,8 @@ export const WrappedAssetCard = ({ asset, className, size, localeCode, defaultLo
|
|
|
102
103
|
entityFile,
|
|
103
104
|
isDisabled: isDisabled,
|
|
104
105
|
onEdit,
|
|
105
|
-
onRemove
|
|
106
|
+
onRemove,
|
|
107
|
+
onAddToReleaseAction
|
|
106
108
|
}),
|
|
107
109
|
...entityFile ? renderAssetInfo({
|
|
108
110
|
entityFile
|
|
@@ -12,6 +12,7 @@ function _define_property(obj, key, value) {
|
|
|
12
12
|
return obj;
|
|
13
13
|
}
|
|
14
14
|
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
15
|
+
import { createGetContentTypeKey, createGetEntryKey, createGetSpaceKey } from '@contentful/field-editor-shared';
|
|
15
16
|
import constate from 'constate';
|
|
16
17
|
import { fetchAll } from 'contentful-management';
|
|
17
18
|
import { get } from 'lodash';
|
|
@@ -61,7 +62,7 @@ function handleResourceFetchError(resourceFetchError, resourceTypeEntity) {
|
|
|
61
62
|
throw resourceFetchError;
|
|
62
63
|
}
|
|
63
64
|
const isEntityQueryKey = (queryKey)=>{
|
|
64
|
-
return Array.isArray(queryKey) && (queryKey[0] === 'Entry' || queryKey[0] === 'Asset') && queryKey.length === 4;
|
|
65
|
+
return Array.isArray(queryKey) && (queryKey[0] === 'Entry' || queryKey[0] === 'Asset') && (queryKey.length === 4 || queryKey.length === 5);
|
|
65
66
|
};
|
|
66
67
|
async function fetchContentfulEntry({ urn, fetch, options }) {
|
|
67
68
|
const resourceId = urn.split(':', 6)[5];
|
|
@@ -74,18 +75,10 @@ async function fetchContentfulEntry({ urn, fetch, options }) {
|
|
|
74
75
|
const environmentId = resourceIdMatch?.groups?.environmentId || 'master';
|
|
75
76
|
const entryId = resourceIdMatch.groups.entityId;
|
|
76
77
|
const [space, entry] = await Promise.all([
|
|
77
|
-
fetch(
|
|
78
|
-
'space',
|
|
79
|
-
spaceId
|
|
80
|
-
], ({ cmaClient })=>cmaClient.space.get({
|
|
78
|
+
fetch(createGetSpaceKey(spaceId), ({ cmaClient })=>cmaClient.space.get({
|
|
81
79
|
spaceId
|
|
82
80
|
}), options),
|
|
83
|
-
fetch(
|
|
84
|
-
'entry',
|
|
85
|
-
spaceId,
|
|
86
|
-
environmentId,
|
|
87
|
-
entryId
|
|
88
|
-
], ({ cmaClient })=>cmaClient.entry.get({
|
|
81
|
+
fetch(createGetEntryKey(spaceId, environmentId, entryId), ({ cmaClient })=>cmaClient.entry.get({
|
|
89
82
|
spaceId,
|
|
90
83
|
environmentId,
|
|
91
84
|
entryId
|
|
@@ -93,12 +86,7 @@ async function fetchContentfulEntry({ urn, fetch, options }) {
|
|
|
93
86
|
]);
|
|
94
87
|
const contentTypeId = entry.sys.contentType.sys.id;
|
|
95
88
|
const [contentType, defaultLocaleCode] = await Promise.all([
|
|
96
|
-
fetch(
|
|
97
|
-
'contentType',
|
|
98
|
-
spaceId,
|
|
99
|
-
environmentId,
|
|
100
|
-
contentTypeId
|
|
101
|
-
], ({ cmaClient })=>cmaClient.contentType.get({
|
|
89
|
+
fetch(createGetContentTypeKey(spaceId, environmentId, contentTypeId), ({ cmaClient })=>cmaClient.contentType.get({
|
|
102
90
|
contentTypeId,
|
|
103
91
|
spaceId,
|
|
104
92
|
environmentId
|
|
@@ -382,25 +370,53 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = cons
|
|
|
382
370
|
const onSlideInNavigation = props.sdk.navigator.onSlideInNavigation;
|
|
383
371
|
useEffect(()=>{
|
|
384
372
|
function findSameSpaceQueries() {
|
|
385
|
-
|
|
386
|
-
type: 'active',
|
|
373
|
+
const queries = queryCache.findAll({
|
|
387
374
|
predicate: (query)=>isSameSpaceEntityQueryKey(query.queryKey)
|
|
388
375
|
});
|
|
376
|
+
return queries;
|
|
389
377
|
}
|
|
390
378
|
if (typeof onEntityChanged !== 'function') {
|
|
391
|
-
return onSlideInNavigation(({ oldSlideLevel, newSlideLevel })=>{
|
|
379
|
+
return onSlideInNavigation(async ({ oldSlideLevel, newSlideLevel })=>{
|
|
392
380
|
if (oldSlideLevel > newSlideLevel) {
|
|
393
|
-
findSameSpaceQueries()
|
|
394
|
-
|
|
395
|
-
|
|
381
|
+
const queries = findSameSpaceQueries();
|
|
382
|
+
await Promise.all(queries.map(async (query)=>{
|
|
383
|
+
const [entityType, entityId, spaceId, environmentId, releaseId] = query.queryKey;
|
|
384
|
+
try {
|
|
385
|
+
let freshData;
|
|
386
|
+
if (entityType === 'Entry') {
|
|
387
|
+
freshData = await cmaClient.entry.get({
|
|
388
|
+
entryId: entityId,
|
|
389
|
+
spaceId: spaceId,
|
|
390
|
+
environmentId: environmentId,
|
|
391
|
+
releaseId: releaseId
|
|
392
|
+
});
|
|
393
|
+
} else if (entityType === 'Asset') {
|
|
394
|
+
freshData = await cmaClient.asset.get({
|
|
395
|
+
assetId: entityId,
|
|
396
|
+
spaceId: spaceId,
|
|
397
|
+
environmentId: environmentId,
|
|
398
|
+
releaseId: releaseId
|
|
399
|
+
});
|
|
400
|
+
} else {
|
|
401
|
+
await queryClient.invalidateQueries(query.queryKey);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
queryClient.setQueryData(query.queryKey, freshData);
|
|
405
|
+
} catch (error) {
|
|
406
|
+
await queryClient.invalidateQueries(query.queryKey);
|
|
407
|
+
}
|
|
408
|
+
}));
|
|
396
409
|
}
|
|
397
410
|
});
|
|
398
411
|
}
|
|
399
412
|
const subscribeQuery = ({ queryKey, queryHash })=>{
|
|
400
413
|
const [entityType, entityId, , , releaseId] = queryKey;
|
|
401
414
|
entityChangeUnsubscribers.current[queryHash] = onEntityChanged(entityType, entityId, (data)=>{
|
|
402
|
-
|
|
415
|
+
const dataReleaseId = get(data, 'sys.release.id');
|
|
416
|
+
if (dataReleaseId === releaseId) {
|
|
403
417
|
queryClient.setQueryData(queryKey, data);
|
|
418
|
+
} else if (releaseId && !dataReleaseId) {
|
|
419
|
+
void queryClient.invalidateQueries(queryKey);
|
|
404
420
|
}
|
|
405
421
|
});
|
|
406
422
|
};
|
|
@@ -432,7 +448,8 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = cons
|
|
|
432
448
|
isSameSpaceEntityQueryKey,
|
|
433
449
|
queryClient,
|
|
434
450
|
getEntity,
|
|
435
|
-
onSlideInNavigation
|
|
451
|
+
onSlideInNavigation,
|
|
452
|
+
cmaClient
|
|
436
453
|
]);
|
|
437
454
|
const getResourceProvider = useCallback(function getResourceProvider(organizationId, appDefinitionId) {
|
|
438
455
|
const queryKey = [
|
|
@@ -538,6 +555,8 @@ export function useResourceProvider(organizationId, appDefinitionId) {
|
|
|
538
555
|
};
|
|
539
556
|
}
|
|
540
557
|
function EntityProvider({ children, ...props }) {
|
|
541
|
-
return /*#__PURE__*/ React.createElement(SharedQueryClientProvider,
|
|
558
|
+
return /*#__PURE__*/ React.createElement(SharedQueryClientProvider, {
|
|
559
|
+
client: props.queryClient
|
|
560
|
+
}, /*#__PURE__*/ React.createElement(InternalServiceProvider, props, children));
|
|
542
561
|
}
|
|
543
562
|
export { EntityProvider, useEntityLoader };
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useCallback } from 'react';
|
|
3
|
+
import { useContentTypes } from '@contentful/field-editor-shared';
|
|
3
4
|
import { arrayMove } from '@dnd-kit/sortable';
|
|
4
5
|
import { LinkEntityActions } from '../components';
|
|
5
6
|
import { useLinkActionsProps } from '../components/LinkActions/LinkEntityActions';
|
|
6
7
|
import { useSortIDs } from '../utils/useSortIDs';
|
|
8
|
+
import { SharedQueryClientProvider } from './queryClient';
|
|
7
9
|
import { ReferenceEditor } from './ReferenceEditor';
|
|
8
10
|
import { useEditorPermissions } from './useEditorPermissions';
|
|
9
11
|
function onLinkOrCreate(setValue, entityType, items, ids, index = items.length) {
|
|
@@ -95,7 +97,10 @@ function Editor(props) {
|
|
|
95
97
|
}));
|
|
96
98
|
}
|
|
97
99
|
export function MultipleReferenceEditor(props) {
|
|
98
|
-
|
|
100
|
+
return /*#__PURE__*/ React.createElement(SharedQueryClientProvider, null, /*#__PURE__*/ React.createElement(MultipleReferenceEditorInner, props));
|
|
101
|
+
}
|
|
102
|
+
function MultipleReferenceEditorInner(props) {
|
|
103
|
+
const { contentTypes: allContentTypes } = useContentTypes(props.sdk);
|
|
99
104
|
return /*#__PURE__*/ React.createElement(ReferenceEditor, props, ({ value, disabled, setValue, externalReset })=>{
|
|
100
105
|
return /*#__PURE__*/ React.createElement(Editor, {
|
|
101
106
|
...props,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useCallback } from 'react';
|
|
3
|
+
import { useContentTypes } from '@contentful/field-editor-shared';
|
|
3
4
|
import { LinkEntityActions } from '../components';
|
|
4
5
|
import { useLinkActionsProps } from '../components/LinkActions/LinkEntityActions';
|
|
6
|
+
import { SharedQueryClientProvider } from './queryClient';
|
|
5
7
|
import { ReferenceEditor } from './ReferenceEditor';
|
|
6
8
|
import { useEditorPermissions } from './useEditorPermissions';
|
|
7
9
|
function Editor(props) {
|
|
@@ -48,11 +50,15 @@ function Editor(props) {
|
|
|
48
50
|
}
|
|
49
51
|
return props.children({
|
|
50
52
|
...props,
|
|
51
|
-
renderCustomCard: props.renderCustomCard && customCardRenderer
|
|
53
|
+
renderCustomCard: props.renderCustomCard && customCardRenderer,
|
|
54
|
+
addReferenceToRelease: props.addReferenceToRelease
|
|
52
55
|
});
|
|
53
56
|
}
|
|
54
57
|
export function SingleReferenceEditor(props) {
|
|
55
|
-
|
|
58
|
+
return /*#__PURE__*/ React.createElement(SharedQueryClientProvider, null, /*#__PURE__*/ React.createElement(SingleReferenceEditorInner, props));
|
|
59
|
+
}
|
|
60
|
+
function SingleReferenceEditorInner(props) {
|
|
61
|
+
const { contentTypes: allContentTypes } = useContentTypes(props.sdk);
|
|
56
62
|
return /*#__PURE__*/ React.createElement(ReferenceEditor, props, ({ value, setValue, disabled, externalReset })=>{
|
|
57
63
|
return /*#__PURE__*/ React.createElement(Editor, {
|
|
58
64
|
...props,
|
|
@@ -1,47 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { QueryClient, useQuery as useRQ, useQueryClient as useHostQueryClient } from '@tanstack/react-query';
|
|
3
|
-
const clientContext = /*#__PURE__*/ React.createContext(undefined);
|
|
4
|
-
function useMaybeHostQueryClient() {
|
|
5
|
-
try {
|
|
6
|
-
return useHostQueryClient();
|
|
7
|
-
} catch {
|
|
8
|
-
return undefined;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
export function useQueryClient() {
|
|
12
|
-
const client = React.useContext(clientContext);
|
|
13
|
-
const hostClient = useMaybeHostQueryClient();
|
|
14
|
-
return React.useMemo(()=>{
|
|
15
|
-
if (client) {
|
|
16
|
-
return client;
|
|
17
|
-
}
|
|
18
|
-
if (hostClient) return hostClient;
|
|
19
|
-
return new QueryClient({
|
|
20
|
-
defaultOptions: {
|
|
21
|
-
queries: {
|
|
22
|
-
useErrorBoundary: false,
|
|
23
|
-
refetchOnWindowFocus: false,
|
|
24
|
-
refetchOnReconnect: true,
|
|
25
|
-
refetchOnMount: false,
|
|
26
|
-
staleTime: Infinity,
|
|
27
|
-
retry: false
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
}, [
|
|
32
|
-
client,
|
|
33
|
-
hostClient
|
|
34
|
-
]);
|
|
35
|
-
}
|
|
36
|
-
export const useQuery = (key, fn, opt)=>{
|
|
37
|
-
return useRQ(key, fn, {
|
|
38
|
-
...opt,
|
|
39
|
-
context: clientContext
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
export function SharedQueryClientProvider({ children }) {
|
|
43
|
-
const client = useQueryClient();
|
|
44
|
-
return /*#__PURE__*/ React.createElement(clientContext.Provider, {
|
|
45
|
-
value: client
|
|
46
|
-
}, children);
|
|
47
|
-
}
|
|
1
|
+
export { SharedQueryClientProvider, useQueryClient, useQuery } from '@contentful/field-editor-shared';
|
|
@@ -11,6 +11,9 @@ export function useContentTypePermissions({ entityType, validations, sdk, allCon
|
|
|
11
11
|
if (entityType === 'Asset') {
|
|
12
12
|
return [];
|
|
13
13
|
}
|
|
14
|
+
if (validations.contentTypes && allContentTypes.length === 0) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
14
17
|
if (validations.contentTypes) {
|
|
15
18
|
return allContentTypes.filter((ct)=>validations.contentTypes?.includes(ct.sys.id));
|
|
16
19
|
}
|
|
@@ -23,21 +26,15 @@ export function useContentTypePermissions({ entityType, validations, sdk, allCon
|
|
|
23
26
|
const [creatableContentTypes, setCreatableContentTypes] = useState(availableContentTypes);
|
|
24
27
|
const { canPerformActionOnEntryOfType } = useAccessApi(sdk.access);
|
|
25
28
|
useEffect(()=>{
|
|
26
|
-
function getContentTypes(action) {
|
|
27
|
-
return filter(availableContentTypes, (ct)=>canPerformActionOnEntryOfType(action, ct.sys.id));
|
|
28
|
-
}
|
|
29
29
|
async function checkContentTypeAccess() {
|
|
30
|
-
const creatable = await
|
|
31
|
-
|
|
32
|
-
setCreatableContentTypes(creatable);
|
|
33
|
-
}
|
|
30
|
+
const creatable = await filter(availableContentTypes, (ct)=>canPerformActionOnEntryOfType('create', ct.sys.id));
|
|
31
|
+
setCreatableContentTypes((creatableContentTypes)=>isEqual(creatable, creatableContentTypes) ? creatableContentTypes : creatable);
|
|
34
32
|
}
|
|
35
33
|
if (availableContentTypes.length > 0) {
|
|
36
34
|
void checkContentTypeAccess();
|
|
37
35
|
}
|
|
38
36
|
}, [
|
|
39
|
-
availableContentTypes
|
|
40
|
-
creatableContentTypes
|
|
37
|
+
availableContentTypes
|
|
41
38
|
]);
|
|
42
39
|
return {
|
|
43
40
|
creatableContentTypes,
|
|
@@ -3,8 +3,12 @@ import { fromFieldValidations } from '../utils/fromFieldValidations';
|
|
|
3
3
|
import { useAccessApi } from './useAccessApi';
|
|
4
4
|
import { useContentTypePermissions } from './useContentTypePermissions';
|
|
5
5
|
export function useEditorPermissions({ sdk, entityType, parameters, allContentTypes }) {
|
|
6
|
+
const fieldValidations = sdk.field.validations;
|
|
7
|
+
const itemsValidations = sdk.field.type === 'Array' ? sdk.field.items?.validations : undefined;
|
|
6
8
|
const validations = useMemo(()=>fromFieldValidations(sdk.field), [
|
|
7
|
-
sdk.field
|
|
9
|
+
sdk.field,
|
|
10
|
+
JSON.stringify(fieldValidations),
|
|
11
|
+
JSON.stringify(itemsValidations)
|
|
8
12
|
]);
|
|
9
13
|
const [canCreateEntity, setCanCreateEntity] = useState(true);
|
|
10
14
|
const [canLinkEntity, setCanLinkEntity] = useState(true);
|
|
@@ -69,6 +69,7 @@ export function useLinkActionsProps(props) {
|
|
|
69
69
|
}, [
|
|
70
70
|
sdk,
|
|
71
71
|
entityType,
|
|
72
|
+
editorPermissions,
|
|
72
73
|
onLinkedExisting
|
|
73
74
|
]);
|
|
74
75
|
const onLinkSeveralExisting = React.useCallback(async (index)=>{
|
|
@@ -84,6 +85,7 @@ export function useLinkActionsProps(props) {
|
|
|
84
85
|
}, [
|
|
85
86
|
sdk,
|
|
86
87
|
entityType,
|
|
88
|
+
editorPermissions,
|
|
87
89
|
onLinkedExisting
|
|
88
90
|
]);
|
|
89
91
|
return useMemo(()=>({
|
|
@@ -34,7 +34,8 @@ export function MultipleEntryReferenceEditor(props) {
|
|
|
34
34
|
onMoveTop: index !== 0 ? ()=>childrenProps.onMove(index, 0) : undefined,
|
|
35
35
|
onMoveBottom: index !== lastIndex ? ()=>childrenProps.onMove(index, lastIndex) : undefined,
|
|
36
36
|
renderDragHandle: DragHandle,
|
|
37
|
-
isBeingDragged: index === indexToUpdate
|
|
37
|
+
isBeingDragged: index === indexToUpdate,
|
|
38
|
+
addReferenceToRelease: props.addReferenceToRelease
|
|
38
39
|
});
|
|
39
40
|
}));
|
|
40
41
|
}
|
|
@@ -17,6 +17,7 @@ export function SingleEntryReferenceEditor(props) {
|
|
|
17
17
|
hasCardEditActions: hasCardEditActions,
|
|
18
18
|
hasCardRemoveActions: hasCardRemoveActions,
|
|
19
19
|
activeLocales: activeLocales,
|
|
20
|
+
addReferenceToRelease: props.addReferenceToRelease,
|
|
20
21
|
onRemove: ()=>{
|
|
21
22
|
setValue(null);
|
|
22
23
|
}
|
|
@@ -63,6 +63,14 @@ export function FetchingWrappedEntryCard(props) {
|
|
|
63
63
|
contentTypeId: entry?.sys?.contentType?.sys?.id ?? ''
|
|
64
64
|
});
|
|
65
65
|
};
|
|
66
|
+
const onAddToRelease = ()=>{
|
|
67
|
+
if (entry && props.addReferenceToRelease) {
|
|
68
|
+
void props.addReferenceToRelease(entry, props.sdk.field.locale, {
|
|
69
|
+
openModalForVersionSelection: true,
|
|
70
|
+
skipNestedReferencesPrompt: true
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
};
|
|
66
74
|
React.useEffect(()=>{
|
|
67
75
|
if (entry) {
|
|
68
76
|
props.onAction?.({
|
|
@@ -117,7 +125,8 @@ export function FetchingWrappedEntryCard(props) {
|
|
|
117
125
|
activeLocales: props.activeLocales,
|
|
118
126
|
releaseStatusMap,
|
|
119
127
|
release: props.sdk.release,
|
|
120
|
-
releaseEntityStatus
|
|
128
|
+
releaseEntityStatus,
|
|
129
|
+
onAddToRelease
|
|
121
130
|
};
|
|
122
131
|
const { hasCardEditActions, hasCardMoveActions, hasCardRemoveActions } = props;
|
|
123
132
|
function renderDefaultCard(props) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { EntryCard, MenuDivider, MenuItem } from '@contentful/f36-components';
|
|
3
|
+
import { PlusIcon } from '@contentful/f36-icons';
|
|
3
4
|
import { entityHelpers, isValidImage } from '@contentful/field-editor-shared';
|
|
4
5
|
import { AssetThumbnail, MissingEntityCard, EntityStatusBadge } from '../../components';
|
|
5
6
|
import { SpaceName } from '../../components/SpaceName/SpaceName';
|
|
@@ -10,7 +11,7 @@ const defaultProps = {
|
|
|
10
11
|
hasCardMoveActions: true,
|
|
11
12
|
hasCardRemoveActions: true
|
|
12
13
|
};
|
|
13
|
-
export function WrappedEntryCard({ entry, entryUrl, contentType, activeLocales, localeCode, defaultLocaleCode, localesStatusMap, useLocalizedEntityStatus, size, spaceName, isClickable, isDisabled, isSelected, hasCardMoveActions, hasCardEditActions, hasCardRemoveActions, renderDragHandle, getAsset, getEntityScheduledActions, onClick, onEdit, onRemove, onMoveTop, onMoveBottom, releaseEntityStatus, releaseStatusMap, release }) {
|
|
14
|
+
export function WrappedEntryCard({ entry, entryUrl, contentType, activeLocales, localeCode, defaultLocaleCode, localesStatusMap, useLocalizedEntityStatus, size, spaceName, isClickable, isDisabled, isSelected, hasCardMoveActions, hasCardEditActions, hasCardRemoveActions, renderDragHandle, getAsset, getEntityScheduledActions, onClick, onEdit, onRemove, onMoveTop, onMoveBottom, releaseEntityStatus, releaseStatusMap, release, onAddToRelease }) {
|
|
14
15
|
const [file, setFile] = React.useState(null);
|
|
15
16
|
React.useEffect(()=>{
|
|
16
17
|
let mounted = true;
|
|
@@ -61,6 +62,7 @@ export function WrappedEntryCard({ entry, entryUrl, contentType, activeLocales,
|
|
|
61
62
|
localeCode,
|
|
62
63
|
defaultLocaleCode
|
|
63
64
|
});
|
|
65
|
+
const showAddToReleaseAction = releaseEntityStatus === 'notInRelease' && release !== undefined && onAddToRelease !== undefined && !isDisabled;
|
|
64
66
|
return /*#__PURE__*/ React.createElement(EntryCard, {
|
|
65
67
|
as: isClickable && entryUrl ? 'a' : 'article',
|
|
66
68
|
href: isClickable ? entryUrl : undefined,
|
|
@@ -91,7 +93,7 @@ export function WrappedEntryCard({ entry, entryUrl, contentType, activeLocales,
|
|
|
91
93
|
dragHandleRender: renderDragHandle,
|
|
92
94
|
withDragHandle: !!renderDragHandle && !isDisabled,
|
|
93
95
|
draggable: !!renderDragHandle && !isDisabled,
|
|
94
|
-
actions: onEdit || onRemove ? [
|
|
96
|
+
actions: onEdit || onRemove || showAddToReleaseAction ? [
|
|
95
97
|
hasCardEditActions && onEdit ? /*#__PURE__*/ React.createElement(MenuItem, {
|
|
96
98
|
key: "edit",
|
|
97
99
|
testId: "edit",
|
|
@@ -106,6 +108,15 @@ export function WrappedEntryCard({ entry, entryUrl, contentType, activeLocales,
|
|
|
106
108
|
onRemove && onRemove();
|
|
107
109
|
}
|
|
108
110
|
}, "Remove") : null,
|
|
111
|
+
showAddToReleaseAction ? /*#__PURE__*/ React.createElement(MenuItem, {
|
|
112
|
+
key: "add-to-release",
|
|
113
|
+
testId: "add-to-release",
|
|
114
|
+
onClick: ()=>{
|
|
115
|
+
onAddToRelease();
|
|
116
|
+
}
|
|
117
|
+
}, /*#__PURE__*/ React.createElement(PlusIcon, {
|
|
118
|
+
size: "tiny"
|
|
119
|
+
}), "Add to release") : null,
|
|
109
120
|
hasCardMoveActions && (onMoveTop || onMoveBottom) && !isDisabled ? /*#__PURE__*/ React.createElement(MenuDivider, {
|
|
110
121
|
key: "divider"
|
|
111
122
|
}) : null,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import '@testing-library/jest-dom';
|
|
3
|
+
import { SharedQueryClientProvider } from '@contentful/field-editor-shared';
|
|
4
|
+
import { createTestQueryClient } from '@contentful/field-editor-test-utils';
|
|
3
5
|
import { configure, fireEvent, render, waitFor } from '@testing-library/react';
|
|
4
6
|
import publishedCT from '../../__fixtures__/content-type/published_content_type.json';
|
|
5
7
|
import publishedEntryNonMasterEnvironment from '../../__fixtures__/entry/published_entry_non_master.json';
|
|
@@ -167,9 +169,33 @@ describe('ResourceCard', ()=>{
|
|
|
167
169
|
fireEvent.mouseEnter(getByText(space.name));
|
|
168
170
|
await waitFor(()=>expect(getByText(tooltipContent)).toBeDefined());
|
|
169
171
|
});
|
|
170
|
-
it('renders skeleton
|
|
171
|
-
const
|
|
172
|
+
it('renders skeleton while data is loading', async ()=>{
|
|
173
|
+
const queryClient = createTestQueryClient();
|
|
174
|
+
let resolveEntry;
|
|
175
|
+
const pendingPromise = new Promise((resolve)=>{
|
|
176
|
+
resolveEntry = resolve;
|
|
177
|
+
});
|
|
178
|
+
sdk.cma.entry.get.mockReturnValueOnce(pendingPromise);
|
|
179
|
+
const { getByTestId, queryByTestId } = render(/*#__PURE__*/ React.createElement(SharedQueryClientProvider, {
|
|
180
|
+
client: queryClient
|
|
181
|
+
}, /*#__PURE__*/ React.createElement(EntityProvider, {
|
|
182
|
+
sdk: sdk
|
|
183
|
+
}, /*#__PURE__*/ React.createElement(ResourceCard, {
|
|
184
|
+
isDisabled: false,
|
|
185
|
+
getEntryRouteHref: ()=>'',
|
|
186
|
+
resourceLink: {
|
|
187
|
+
sys: {
|
|
188
|
+
type: 'ResourceLink',
|
|
189
|
+
linkType: 'Contentful:Entry',
|
|
190
|
+
urn: resolvableEntryUrn
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}))));
|
|
172
194
|
expect(getByTestId('cf-ui-skeleton-form')).toBeDefined();
|
|
195
|
+
expect(queryByTestId('cf-ui-entry-card')).toBeNull();
|
|
196
|
+
resolveEntry(publishedEntry);
|
|
197
|
+
await waitFor(()=>expect(getByTestId('cf-ui-entry-card')).toBeDefined());
|
|
198
|
+
expect(queryByTestId('cf-ui-skeleton-form')).toBeNull();
|
|
173
199
|
});
|
|
174
200
|
it('renders unsupported entity card when resource type is unknown', async ()=>{
|
|
175
201
|
const { getByText } = renderResourceCard({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { CustomCardRenderer, RenderCustomMissingEntityCard } from '../../common/customCardTypes';
|
|
3
|
-
import { Action, FieldAppSDK, ViewType, RenderDragFn } from '../../types';
|
|
3
|
+
import { Action, Asset, FieldAppSDK, ViewType, RenderDragFn } from '../../types';
|
|
4
4
|
type FetchingWrappedAssetCardProps = {
|
|
5
5
|
assetId: string;
|
|
6
6
|
isDisabled: boolean;
|
|
@@ -12,6 +12,10 @@ type FetchingWrappedAssetCardProps = {
|
|
|
12
12
|
renderDragHandle?: RenderDragFn;
|
|
13
13
|
renderCustomCard?: CustomCardRenderer;
|
|
14
14
|
renderCustomMissingEntityCard?: RenderCustomMissingEntityCard;
|
|
15
|
+
addReferenceToRelease?: (reference: Asset, localeCode?: string, options?: {
|
|
16
|
+
openModalForVersionSelection?: boolean;
|
|
17
|
+
skipNestedReferencesPrompt?: boolean;
|
|
18
|
+
}) => Promise<void>;
|
|
15
19
|
};
|
|
16
20
|
export declare function FetchingWrappedAssetCard(props: FetchingWrappedAssetCardProps): React.JSX.Element;
|
|
17
21
|
export {};
|
|
@@ -23,9 +23,10 @@ export interface WrappedAssetCardProps {
|
|
|
23
23
|
releaseEntityStatus?: ReleaseEntityStatus;
|
|
24
24
|
releaseStatusMap?: ReleaseStatusMap;
|
|
25
25
|
release?: ReleaseV2Props;
|
|
26
|
+
onAddToRelease?: () => void;
|
|
26
27
|
}
|
|
27
28
|
export declare const WrappedAssetCard: {
|
|
28
|
-
({ asset, className, size, localeCode, defaultLocaleCode, activeLocales, localesStatusMap, isDisabled, isSelected, isClickable, useLocalizedEntityStatus, renderDragHandle, getEntityScheduledActions, onEdit, getAssetUrl, onRemove, releaseEntityStatus, releaseStatusMap, release, }: WrappedAssetCardProps): React.JSX.Element;
|
|
29
|
+
({ asset, className, size, localeCode, defaultLocaleCode, activeLocales, localesStatusMap, isDisabled, isSelected, isClickable, useLocalizedEntityStatus, renderDragHandle, getEntityScheduledActions, onEdit, getAssetUrl, onRemove, releaseEntityStatus, releaseStatusMap, release, onAddToRelease, }: WrappedAssetCardProps): React.JSX.Element;
|
|
29
30
|
defaultProps: {
|
|
30
31
|
isClickable: boolean;
|
|
31
32
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { BaseAppSDK } from '@contentful/app-sdk';
|
|
3
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
3
4
|
import { ResourceProvider } from 'contentful-management';
|
|
4
5
|
import { Asset, ContentType, Entry, ExternalResource, Resource, ResourceType, ScheduledAction, Space } from '../types';
|
|
5
6
|
export type ContentfulResourceInfo = {
|
|
@@ -17,6 +18,7 @@ export declare function isContentfulResourceInfo(info: ResourceInfo): info is Co
|
|
|
17
18
|
type EntityStoreProps = {
|
|
18
19
|
sdk: BaseAppSDK;
|
|
19
20
|
queryConcurrency?: number;
|
|
21
|
+
queryClient?: QueryClient;
|
|
20
22
|
};
|
|
21
23
|
type GetOptions = {
|
|
22
24
|
priority?: number;
|
|
@@ -88,12 +90,12 @@ declare const useEntityLoader: () => {
|
|
|
88
90
|
};
|
|
89
91
|
export declare function useEntity<E extends FetchableEntity>(entityType: FetchableEntityType, entityId: string, options?: Omit<UseEntityOptions, 'releaseId'>): UseEntityResult<E>;
|
|
90
92
|
export declare function useResource<R extends Resource = Resource>(resourceType: string, urn: string, { locale, referencingEntryId, ...options }?: UseResourceOptions): {
|
|
91
|
-
status: "
|
|
93
|
+
status: "loading" | "error" | "success";
|
|
92
94
|
data: ResourceInfo<R> | undefined;
|
|
93
95
|
error: unknown;
|
|
94
96
|
};
|
|
95
97
|
export declare function useResourceProvider(organizationId: string, appDefinitionId: string): {
|
|
96
|
-
status: "
|
|
98
|
+
status: "loading" | "error" | "success";
|
|
97
99
|
data: ResourceProvider | undefined;
|
|
98
100
|
error: unknown;
|
|
99
101
|
};
|