@contentful/field-editor-reference 6.19.3 → 6.19.4-canary.3

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.
Files changed (39) hide show
  1. package/dist/cjs/__fixtures__/FakeSdk.js +13 -20
  2. package/dist/cjs/assets/WrappedAssetCard/AssetCardActions.js +11 -1
  3. package/dist/cjs/assets/WrappedAssetCard/FetchingWrappedAssetCard.js +10 -1
  4. package/dist/cjs/assets/WrappedAssetCard/WrappedAssetCard.js +4 -2
  5. package/dist/cjs/common/EntityStore.js +38 -25
  6. package/dist/cjs/common/MultipleReferenceEditor.js +6 -1
  7. package/dist/cjs/common/SingleReferenceEditor.js +8 -2
  8. package/dist/cjs/common/useContentTypePermissions.js +6 -9
  9. package/dist/cjs/common/useEditorPermissions.js +5 -1
  10. package/dist/cjs/components/LinkActions/LinkEntityActions.js +2 -0
  11. package/dist/cjs/entries/MultipleEntryReferenceEditor.js +2 -1
  12. package/dist/cjs/entries/SingleEntryReferenceEditor.js +1 -0
  13. package/dist/cjs/entries/WrappedEntryCard/FetchingWrappedEntryCard.js +10 -1
  14. package/dist/cjs/entries/WrappedEntryCard/WrappedEntryCard.js +13 -2
  15. package/dist/cjs/resources/Cards/ResourceCard.spec.js +28 -2
  16. package/dist/esm/__fixtures__/FakeSdk.js +13 -20
  17. package/dist/esm/assets/WrappedAssetCard/AssetCardActions.js +11 -1
  18. package/dist/esm/assets/WrappedAssetCard/FetchingWrappedAssetCard.js +10 -1
  19. package/dist/esm/assets/WrappedAssetCard/WrappedAssetCard.js +4 -2
  20. package/dist/esm/common/EntityStore.js +38 -25
  21. package/dist/esm/common/MultipleReferenceEditor.js +6 -1
  22. package/dist/esm/common/SingleReferenceEditor.js +8 -2
  23. package/dist/esm/common/useContentTypePermissions.js +6 -9
  24. package/dist/esm/common/useEditorPermissions.js +5 -1
  25. package/dist/esm/components/LinkActions/LinkEntityActions.js +2 -0
  26. package/dist/esm/entries/MultipleEntryReferenceEditor.js +2 -1
  27. package/dist/esm/entries/SingleEntryReferenceEditor.js +1 -0
  28. package/dist/esm/entries/WrappedEntryCard/FetchingWrappedEntryCard.js +10 -1
  29. package/dist/esm/entries/WrappedEntryCard/WrappedEntryCard.js +13 -2
  30. package/dist/esm/resources/Cards/ResourceCard.spec.js +28 -2
  31. package/dist/types/assets/WrappedAssetCard/AssetCardActions.d.ts +1 -0
  32. package/dist/types/assets/WrappedAssetCard/FetchingWrappedAssetCard.d.ts +5 -1
  33. package/dist/types/assets/WrappedAssetCard/WrappedAssetCard.d.ts +2 -1
  34. package/dist/types/common/EntityStore.d.ts +2 -0
  35. package/dist/types/common/ReferenceEditor.d.ts +5 -1
  36. package/dist/types/common/customCardTypes.d.ts +1 -0
  37. package/dist/types/entries/WrappedEntryCard/FetchingWrappedEntryCard.d.ts +5 -1
  38. package/dist/types/entries/WrappedEntryCard/WrappedEntryCard.d.ts +2 -1
  39. package/package.json +4 -4
@@ -45,15 +45,6 @@ function newReferenceEditorFakeSdk(props) {
45
45
  const delay = (ms)=>{
46
46
  return new Promise((resolve)=>setTimeout(resolve, ms));
47
47
  };
48
- const localizeContentTypes = (contentTypes)=>{
49
- return contentTypes.map((contentType)=>({
50
- ...contentType,
51
- fields: contentType.fields.map((field)=>({
52
- ...field,
53
- localized: true
54
- }))
55
- }));
56
- };
57
48
  const sdk = {
58
49
  field,
59
50
  locales,
@@ -106,6 +97,19 @@ function newReferenceEditorFakeSdk(props) {
106
97
  return _fixtures.contentTypes.published;
107
98
  }
108
99
  return Promise.reject({});
100
+ },
101
+ getMany: async ()=>{
102
+ return Promise.resolve({
103
+ items: [
104
+ _fixtures.contentTypes.published
105
+ ],
106
+ total: 1,
107
+ skip: 0,
108
+ limit: 1000,
109
+ sys: {
110
+ type: 'Array'
111
+ }
112
+ });
109
113
  }
110
114
  },
111
115
  Locale: {
@@ -114,17 +118,6 @@ function newReferenceEditorFakeSdk(props) {
114
118
  },
115
119
  space: {
116
120
  ...space,
117
- getCachedContentTypes () {
118
- return localizeContentTypes(space.getCachedContentTypes());
119
- },
120
- getContentTypes () {
121
- return Promise.resolve(space.getContentTypes().then((response)=>{
122
- return {
123
- ...response,
124
- items: localizeContentTypes(response.items)
125
- };
126
- }));
127
- },
128
121
  async getEntityScheduledActions () {
129
122
  return [];
130
123
  }
@@ -18,6 +18,7 @@ _export(exports, {
18
18
  });
19
19
  const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
20
20
  const _f36components = require("@contentful/f36-components");
21
+ const _f36icons = require("@contentful/f36-icons");
21
22
  const _f36tokens = /*#__PURE__*/ _interop_require_default(require("@contentful/f36-tokens"));
22
23
  const _fieldeditorshared = require("@contentful/field-editor-shared");
23
24
  const _emotion = require("emotion");
@@ -134,7 +135,7 @@ function renderAssetInfo(props) {
134
135
  ];
135
136
  }
136
137
  function renderActions(props) {
137
- const { entityFile, isDisabled, onEdit, onRemove } = props;
138
+ const { entityFile, isDisabled, onEdit, onRemove, onAddToReleaseAction } = props;
138
139
  return [
139
140
  /*#__PURE__*/ _react.createElement(_f36components.MenuSectionTitle, {
140
141
  key: "section-title"
@@ -144,6 +145,15 @@ function renderActions(props) {
144
145
  onClick: onEdit,
145
146
  testId: "card-action-edit"
146
147
  }, "Edit") : null,
148
+ onAddToReleaseAction ? /*#__PURE__*/ _react.createElement(_f36components.MenuItem, {
149
+ key: "add-to-release",
150
+ testId: "add-to-release",
151
+ onClick: ()=>{
152
+ onAddToReleaseAction();
153
+ }
154
+ }, /*#__PURE__*/ _react.createElement(_f36icons.PlusIcon, {
155
+ size: "tiny"
156
+ }), "Add to release") : null,
147
157
  entityFile ? /*#__PURE__*/ _react.createElement(_f36components.MenuItem, {
148
158
  key: "download",
149
159
  onClick: ()=>{
@@ -72,6 +72,14 @@ function FetchingWrappedAssetCard(props) {
72
72
  locales: props.sdk.locales,
73
73
  isReference: true
74
74
  });
75
+ const onAddToRelease = ()=>{
76
+ if (asset && props.addReferenceToRelease) {
77
+ void props.addReferenceToRelease(asset, props.sdk.field.locale, {
78
+ openModalForVersionSelection: true,
79
+ skipNestedReferencesPrompt: true
80
+ });
81
+ }
82
+ };
75
83
  _react.useEffect(()=>{
76
84
  if (asset) {
77
85
  props.onAction && props.onAction({
@@ -138,7 +146,8 @@ function FetchingWrappedAssetCard(props) {
138
146
  activeLocales,
139
147
  releaseStatusMap,
140
148
  release: props.sdk.release,
141
- releaseEntityStatus
149
+ releaseEntityStatus,
150
+ onAddToRelease
142
151
  };
143
152
  if (status === 'loading') {
144
153
  return props.viewType === 'link' ? /*#__PURE__*/ _react.createElement(_f36components.EntryCard, {
@@ -87,7 +87,7 @@ function getFileType(file) {
87
87
  return groupToIconMap[groupName] || 'archive';
88
88
  }
89
89
  const THUMBNAIL_SIZE = 150;
90
- const WrappedAssetCard = ({ asset, className, size, localeCode, defaultLocaleCode, activeLocales, localesStatusMap, isDisabled, isSelected, isClickable, useLocalizedEntityStatus, renderDragHandle, getEntityScheduledActions, onEdit, getAssetUrl, onRemove, releaseEntityStatus, releaseStatusMap, release })=>{
90
+ const WrappedAssetCard = ({ asset, className, size, localeCode, defaultLocaleCode, activeLocales, localesStatusMap, isDisabled, isSelected, isClickable, useLocalizedEntityStatus, renderDragHandle, getEntityScheduledActions, onEdit, getAssetUrl, onRemove, releaseEntityStatus, releaseStatusMap, release, onAddToRelease })=>{
91
91
  const status = _fieldeditorshared.entityHelpers.getEntityStatus(asset.sys, useLocalizedEntityStatus ? localeCode : undefined);
92
92
  const entityFile = asset.fields.file ? asset.fields.file[localeCode] || asset.fields.file[defaultLocaleCode] : undefined;
93
93
  const imageUrl = _react.useMemo(()=>{
@@ -120,6 +120,7 @@ const WrappedAssetCard = ({ asset, className, size, localeCode, defaultLocaleCod
120
120
  defaultTitle: 'Untitled'
121
121
  });
122
122
  const href = getAssetUrl ? getAssetUrl(asset.sys.id) : undefined;
123
+ const onAddToReleaseAction = releaseEntityStatus === 'notInRelease' && release !== undefined && onAddToRelease !== undefined && !isDisabled ? onAddToRelease : undefined;
123
124
  return /*#__PURE__*/ _react.createElement(_f36components.AssetCard, {
124
125
  as: isClickable && href ? 'a' : 'article',
125
126
  type: getFileType(entityFile),
@@ -158,7 +159,8 @@ const WrappedAssetCard = ({ asset, className, size, localeCode, defaultLocaleCod
158
159
  entityFile,
159
160
  isDisabled: isDisabled,
160
161
  onEdit,
161
- onRemove
162
+ onRemove,
163
+ onAddToReleaseAction
162
164
  }),
163
165
  ...entityFile ? (0, _AssetCardActions.renderAssetInfo)({
164
166
  entityFile
@@ -41,10 +41,10 @@ _export(exports, {
41
41
  }
42
42
  });
43
43
  const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
44
+ const _fieldeditorshared = require("@contentful/field-editor-shared");
44
45
  const _constate = /*#__PURE__*/ _interop_require_default(require("constate"));
45
46
  const _contentfulmanagement = require("contentful-management");
46
47
  const _lodash = require("lodash");
47
- const _moment = /*#__PURE__*/ _interop_require_default(require("moment"));
48
48
  const _pqueue = /*#__PURE__*/ _interop_require_default(require("p-queue"));
49
49
  const _queryClient = require("./queryClient");
50
50
  function _define_property(obj, key, value) {
@@ -163,18 +163,10 @@ async function fetchContentfulEntry({ urn, fetch, options }) {
163
163
  const environmentId = resourceIdMatch?.groups?.environmentId || 'master';
164
164
  const entryId = resourceIdMatch.groups.entityId;
165
165
  const [space, entry] = await Promise.all([
166
- fetch([
167
- 'space',
168
- spaceId
169
- ], ({ cmaClient })=>cmaClient.space.get({
166
+ fetch((0, _fieldeditorshared.createGetSpaceKey)(spaceId), ({ cmaClient })=>cmaClient.space.get({
170
167
  spaceId
171
168
  }), options),
172
- fetch([
173
- 'entry',
174
- spaceId,
175
- environmentId,
176
- entryId
177
- ], ({ cmaClient })=>cmaClient.entry.get({
169
+ fetch((0, _fieldeditorshared.createGetEntryKey)(spaceId, environmentId, entryId), ({ cmaClient })=>cmaClient.entry.get({
178
170
  spaceId,
179
171
  environmentId,
180
172
  entryId
@@ -182,12 +174,7 @@ async function fetchContentfulEntry({ urn, fetch, options }) {
182
174
  ]);
183
175
  const contentTypeId = entry.sys.contentType.sys.id;
184
176
  const [contentType, defaultLocaleCode] = await Promise.all([
185
- fetch([
186
- 'contentType',
187
- spaceId,
188
- environmentId,
189
- contentTypeId
190
- ], ({ cmaClient })=>cmaClient.contentType.get({
177
+ fetch((0, _fieldeditorshared.createGetContentTypeKey)(spaceId, environmentId, contentTypeId), ({ cmaClient })=>cmaClient.contentType.get({
191
178
  contentTypeId,
192
179
  spaceId,
193
180
  environmentId
@@ -399,7 +386,6 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = (0,
399
386
  const query = {
400
387
  'environment.sys.id': environmentId,
401
388
  'sys.status': 'scheduled',
402
- 'scheduledFor.datetime[gte]': (0, _moment.default)().startOf('hour').toISOString(),
403
389
  'entity.sys.linkType[in]': 'Entry,Asset',
404
390
  order: '-scheduledFor.datetime',
405
391
  limit: maxScheduledActions
@@ -473,17 +459,41 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = (0,
473
459
  (0, _react.useEffect)(()=>{
474
460
  function findSameSpaceQueries() {
475
461
  const queries = queryCache.findAll({
476
- type: 'active',
477
462
  predicate: (query)=>isSameSpaceEntityQueryKey(query.queryKey)
478
463
  });
479
464
  return queries;
480
465
  }
481
466
  if (typeof onEntityChanged !== 'function') {
482
- return onSlideInNavigation(({ oldSlideLevel, newSlideLevel })=>{
467
+ return onSlideInNavigation(async ({ oldSlideLevel, newSlideLevel })=>{
483
468
  if (oldSlideLevel > newSlideLevel) {
484
- findSameSpaceQueries().forEach((query)=>{
485
- void queryClient.invalidateQueries(query.queryKey);
486
- });
469
+ const queries = findSameSpaceQueries();
470
+ await Promise.all(queries.map(async (query)=>{
471
+ const [entityType, entityId, spaceId, environmentId, releaseId] = query.queryKey;
472
+ try {
473
+ let freshData;
474
+ if (entityType === 'Entry') {
475
+ freshData = await cmaClient.entry.get({
476
+ entryId: entityId,
477
+ spaceId: spaceId,
478
+ environmentId: environmentId,
479
+ releaseId: releaseId
480
+ });
481
+ } else if (entityType === 'Asset') {
482
+ freshData = await cmaClient.asset.get({
483
+ assetId: entityId,
484
+ spaceId: spaceId,
485
+ environmentId: environmentId,
486
+ releaseId: releaseId
487
+ });
488
+ } else {
489
+ await queryClient.invalidateQueries(query.queryKey);
490
+ return;
491
+ }
492
+ queryClient.setQueryData(query.queryKey, freshData);
493
+ } catch (error) {
494
+ await queryClient.invalidateQueries(query.queryKey);
495
+ }
496
+ }));
487
497
  }
488
498
  });
489
499
  }
@@ -526,7 +536,8 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = (0,
526
536
  isSameSpaceEntityQueryKey,
527
537
  queryClient,
528
538
  getEntity,
529
- onSlideInNavigation
539
+ onSlideInNavigation,
540
+ cmaClient
530
541
  ]);
531
542
  const getResourceProvider = (0, _react.useCallback)(function getResourceProvider(organizationId, appDefinitionId) {
532
543
  const queryKey = [
@@ -632,5 +643,7 @@ function useResourceProvider(organizationId, appDefinitionId) {
632
643
  };
633
644
  }
634
645
  function EntityProvider({ children, ...props }) {
635
- return /*#__PURE__*/ _react.default.createElement(_queryClient.SharedQueryClientProvider, null, /*#__PURE__*/ _react.default.createElement(InternalServiceProvider, props, children));
646
+ return /*#__PURE__*/ _react.default.createElement(_queryClient.SharedQueryClientProvider, {
647
+ client: props.queryClient
648
+ }, /*#__PURE__*/ _react.default.createElement(InternalServiceProvider, props, children));
636
649
  }
@@ -9,10 +9,12 @@ Object.defineProperty(exports, "MultipleReferenceEditor", {
9
9
  }
10
10
  });
11
11
  const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
12
+ const _fieldeditorshared = require("@contentful/field-editor-shared");
12
13
  const _sortable = require("@dnd-kit/sortable");
13
14
  const _components = require("../components");
14
15
  const _LinkEntityActions = require("../components/LinkActions/LinkEntityActions");
15
16
  const _useSortIDs = require("../utils/useSortIDs");
17
+ const _queryClient = require("./queryClient");
16
18
  const _ReferenceEditor = require("./ReferenceEditor");
17
19
  const _useEditorPermissions = require("./useEditorPermissions");
18
20
  function _getRequireWildcardCache(nodeInterop) {
@@ -145,7 +147,10 @@ function Editor(props) {
145
147
  }));
146
148
  }
147
149
  function MultipleReferenceEditor(props) {
148
- const allContentTypes = props.sdk.space.getCachedContentTypes();
150
+ return /*#__PURE__*/ _react.createElement(_queryClient.SharedQueryClientProvider, null, /*#__PURE__*/ _react.createElement(MultipleReferenceEditorInner, props));
151
+ }
152
+ function MultipleReferenceEditorInner(props) {
153
+ const { contentTypes: allContentTypes } = (0, _fieldeditorshared.useContentTypes)(props.sdk);
149
154
  return /*#__PURE__*/ _react.createElement(_ReferenceEditor.ReferenceEditor, props, ({ value, disabled, setValue, externalReset })=>{
150
155
  return /*#__PURE__*/ _react.createElement(Editor, {
151
156
  ...props,
@@ -9,8 +9,10 @@ Object.defineProperty(exports, "SingleReferenceEditor", {
9
9
  }
10
10
  });
11
11
  const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
12
+ const _fieldeditorshared = require("@contentful/field-editor-shared");
12
13
  const _components = require("../components");
13
14
  const _LinkEntityActions = require("../components/LinkActions/LinkEntityActions");
15
+ const _queryClient = require("./queryClient");
14
16
  const _ReferenceEditor = require("./ReferenceEditor");
15
17
  const _useEditorPermissions = require("./useEditorPermissions");
16
18
  function _getRequireWildcardCache(nodeInterop) {
@@ -98,11 +100,15 @@ function Editor(props) {
98
100
  }
99
101
  return props.children({
100
102
  ...props,
101
- renderCustomCard: props.renderCustomCard && customCardRenderer
103
+ renderCustomCard: props.renderCustomCard && customCardRenderer,
104
+ addReferenceToRelease: props.addReferenceToRelease
102
105
  });
103
106
  }
104
107
  function SingleReferenceEditor(props) {
105
- const allContentTypes = props.sdk.space.getCachedContentTypes();
108
+ return /*#__PURE__*/ _react.createElement(_queryClient.SharedQueryClientProvider, null, /*#__PURE__*/ _react.createElement(SingleReferenceEditorInner, props));
109
+ }
110
+ function SingleReferenceEditorInner(props) {
111
+ const { contentTypes: allContentTypes } = (0, _fieldeditorshared.useContentTypes)(props.sdk);
106
112
  return /*#__PURE__*/ _react.createElement(_ReferenceEditor.ReferenceEditor, props, ({ value, setValue, disabled, externalReset })=>{
107
113
  return /*#__PURE__*/ _react.createElement(Editor, {
108
114
  ...props,
@@ -26,6 +26,9 @@ function useContentTypePermissions({ entityType, validations, sdk, allContentTyp
26
26
  if (entityType === 'Asset') {
27
27
  return [];
28
28
  }
29
+ if (validations.contentTypes && allContentTypes.length === 0) {
30
+ return [];
31
+ }
29
32
  if (validations.contentTypes) {
30
33
  return allContentTypes.filter((ct)=>validations.contentTypes?.includes(ct.sys.id));
31
34
  }
@@ -38,21 +41,15 @@ function useContentTypePermissions({ entityType, validations, sdk, allContentTyp
38
41
  const [creatableContentTypes, setCreatableContentTypes] = (0, _react.useState)(availableContentTypes);
39
42
  const { canPerformActionOnEntryOfType } = (0, _useAccessApi.useAccessApi)(sdk.access);
40
43
  (0, _react.useEffect)(()=>{
41
- function getContentTypes(action) {
42
- return filter(availableContentTypes, (ct)=>canPerformActionOnEntryOfType(action, ct.sys.id));
43
- }
44
44
  async function checkContentTypeAccess() {
45
- const creatable = await getContentTypes('create');
46
- if (!(0, _isEqual.default)(creatable, creatableContentTypes)) {
47
- setCreatableContentTypes(creatable);
48
- }
45
+ const creatable = await filter(availableContentTypes, (ct)=>canPerformActionOnEntryOfType('create', ct.sys.id));
46
+ setCreatableContentTypes((creatableContentTypes)=>(0, _isEqual.default)(creatable, creatableContentTypes) ? creatableContentTypes : creatable);
49
47
  }
50
48
  if (availableContentTypes.length > 0) {
51
49
  void checkContentTypeAccess();
52
50
  }
53
51
  }, [
54
- availableContentTypes,
55
- creatableContentTypes
52
+ availableContentTypes
56
53
  ]);
57
54
  return {
58
55
  creatableContentTypes,
@@ -13,8 +13,12 @@ const _fromFieldValidations = require("../utils/fromFieldValidations");
13
13
  const _useAccessApi = require("./useAccessApi");
14
14
  const _useContentTypePermissions = require("./useContentTypePermissions");
15
15
  function useEditorPermissions({ sdk, entityType, parameters, allContentTypes }) {
16
+ const fieldValidations = sdk.field.validations;
17
+ const itemsValidations = sdk.field.type === 'Array' ? sdk.field.items?.validations : undefined;
16
18
  const validations = (0, _react.useMemo)(()=>(0, _fromFieldValidations.fromFieldValidations)(sdk.field), [
17
- sdk.field
19
+ sdk.field,
20
+ JSON.stringify(fieldValidations),
21
+ JSON.stringify(itemsValidations)
18
22
  ]);
19
23
  const [canCreateEntity, setCanCreateEntity] = (0, _react.useState)(true);
20
24
  const [canLinkEntity, setCanLinkEntity] = (0, _react.useState)(true);
@@ -130,6 +130,7 @@ function useLinkActionsProps(props) {
130
130
  }, [
131
131
  sdk,
132
132
  entityType,
133
+ editorPermissions,
133
134
  onLinkedExisting
134
135
  ]);
135
136
  const onLinkSeveralExisting = _react.useCallback(async (index)=>{
@@ -145,6 +146,7 @@ function useLinkActionsProps(props) {
145
146
  }, [
146
147
  sdk,
147
148
  entityType,
149
+ editorPermissions,
148
150
  onLinkedExisting
149
151
  ]);
150
152
  return (0, _react.useMemo)(()=>({
@@ -85,7 +85,8 @@ function MultipleEntryReferenceEditor(props) {
85
85
  onMoveTop: index !== 0 ? ()=>childrenProps.onMove(index, 0) : undefined,
86
86
  onMoveBottom: index !== lastIndex ? ()=>childrenProps.onMove(index, lastIndex) : undefined,
87
87
  renderDragHandle: DragHandle,
88
- isBeingDragged: index === indexToUpdate
88
+ isBeingDragged: index === indexToUpdate,
89
+ addReferenceToRelease: props.addReferenceToRelease
89
90
  });
90
91
  }));
91
92
  }
@@ -68,6 +68,7 @@ function SingleEntryReferenceEditor(props) {
68
68
  hasCardEditActions: hasCardEditActions,
69
69
  hasCardRemoveActions: hasCardRemoveActions,
70
70
  activeLocales: activeLocales,
71
+ addReferenceToRelease: props.addReferenceToRelease,
71
72
  onRemove: ()=>{
72
73
  setValue(null);
73
74
  }
@@ -114,6 +114,14 @@ function FetchingWrappedEntryCard(props) {
114
114
  contentTypeId: entry?.sys?.contentType?.sys?.id ?? ''
115
115
  });
116
116
  };
117
+ const onAddToRelease = ()=>{
118
+ if (entry && props.addReferenceToRelease) {
119
+ void props.addReferenceToRelease(entry, props.sdk.field.locale, {
120
+ openModalForVersionSelection: true,
121
+ skipNestedReferencesPrompt: true
122
+ });
123
+ }
124
+ };
117
125
  _react.useEffect(()=>{
118
126
  if (entry) {
119
127
  props.onAction?.({
@@ -168,7 +176,8 @@ function FetchingWrappedEntryCard(props) {
168
176
  activeLocales: props.activeLocales,
169
177
  releaseStatusMap,
170
178
  release: props.sdk.release,
171
- releaseEntityStatus
179
+ releaseEntityStatus,
180
+ onAddToRelease
172
181
  };
173
182
  const { hasCardEditActions, hasCardMoveActions, hasCardRemoveActions } = props;
174
183
  function renderDefaultCard(props) {
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "WrappedEntryCard", {
10
10
  });
11
11
  const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
12
12
  const _f36components = require("@contentful/f36-components");
13
+ const _f36icons = require("@contentful/f36-icons");
13
14
  const _fieldeditorshared = require("@contentful/field-editor-shared");
14
15
  const _components = require("../../components");
15
16
  const _SpaceName = require("../../components/SpaceName/SpaceName");
@@ -61,7 +62,7 @@ const defaultProps = {
61
62
  hasCardMoveActions: true,
62
63
  hasCardRemoveActions: true
63
64
  };
64
- 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 }) {
65
+ 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 }) {
65
66
  const [file, setFile] = _react.useState(null);
66
67
  _react.useEffect(()=>{
67
68
  let mounted = true;
@@ -112,6 +113,7 @@ function WrappedEntryCard({ entry, entryUrl, contentType, activeLocales, localeC
112
113
  localeCode,
113
114
  defaultLocaleCode
114
115
  });
116
+ const showAddToReleaseAction = releaseEntityStatus === 'notInRelease' && release !== undefined && onAddToRelease !== undefined && !isDisabled;
115
117
  return /*#__PURE__*/ _react.createElement(_f36components.EntryCard, {
116
118
  as: isClickable && entryUrl ? 'a' : 'article',
117
119
  href: isClickable ? entryUrl : undefined,
@@ -142,7 +144,7 @@ function WrappedEntryCard({ entry, entryUrl, contentType, activeLocales, localeC
142
144
  dragHandleRender: renderDragHandle,
143
145
  withDragHandle: !!renderDragHandle && !isDisabled,
144
146
  draggable: !!renderDragHandle && !isDisabled,
145
- actions: onEdit || onRemove ? [
147
+ actions: onEdit || onRemove || showAddToReleaseAction ? [
146
148
  hasCardEditActions && onEdit ? /*#__PURE__*/ _react.createElement(_f36components.MenuItem, {
147
149
  key: "edit",
148
150
  testId: "edit",
@@ -157,6 +159,15 @@ function WrappedEntryCard({ entry, entryUrl, contentType, activeLocales, localeC
157
159
  onRemove && onRemove();
158
160
  }
159
161
  }, "Remove") : null,
162
+ showAddToReleaseAction ? /*#__PURE__*/ _react.createElement(_f36components.MenuItem, {
163
+ key: "add-to-release",
164
+ testId: "add-to-release",
165
+ onClick: ()=>{
166
+ onAddToRelease();
167
+ }
168
+ }, /*#__PURE__*/ _react.createElement(_f36icons.PlusIcon, {
169
+ size: "tiny"
170
+ }), "Add to release") : null,
160
171
  hasCardMoveActions && (onMoveTop || onMoveBottom) && !isDisabled ? /*#__PURE__*/ _react.createElement(_f36components.MenuDivider, {
161
172
  key: "divider"
162
173
  }) : null,
@@ -4,6 +4,8 @@ Object.defineProperty(exports, "__esModule", {
4
4
  });
5
5
  const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
6
6
  require("@testing-library/jest-dom");
7
+ const _fieldeditorshared = require("@contentful/field-editor-shared");
8
+ const _fieldeditortestutils = require("@contentful/field-editor-test-utils");
7
9
  const _react1 = require("@testing-library/react");
8
10
  const _published_content_typejson = /*#__PURE__*/ _interop_require_default(require("../../__fixtures__/content-type/published_content_type.json"));
9
11
  const _published_entry_non_masterjson = /*#__PURE__*/ _interop_require_default(require("../../__fixtures__/entry/published_entry_non_master.json"));
@@ -217,9 +219,33 @@ describe('ResourceCard', ()=>{
217
219
  _react1.fireEvent.mouseEnter(getByText(_indifferent_spacejson.default.name));
218
220
  await (0, _react1.waitFor)(()=>expect(getByText(tooltipContent)).toBeDefined());
219
221
  });
220
- it('renders skeleton when no data is provided', ()=>{
221
- const { getByTestId } = renderResourceCard();
222
+ it('renders skeleton while data is loading', async ()=>{
223
+ const queryClient = (0, _fieldeditortestutils.createTestQueryClient)();
224
+ let resolveEntry;
225
+ const pendingPromise = new Promise((resolve)=>{
226
+ resolveEntry = resolve;
227
+ });
228
+ sdk.cma.entry.get.mockReturnValueOnce(pendingPromise);
229
+ const { getByTestId, queryByTestId } = (0, _react1.render)(/*#__PURE__*/ _react.createElement(_fieldeditorshared.SharedQueryClientProvider, {
230
+ client: queryClient
231
+ }, /*#__PURE__*/ _react.createElement(_EntityStore.EntityProvider, {
232
+ sdk: sdk
233
+ }, /*#__PURE__*/ _react.createElement(_ResourceCard.ResourceCard, {
234
+ isDisabled: false,
235
+ getEntryRouteHref: ()=>'',
236
+ resourceLink: {
237
+ sys: {
238
+ type: 'ResourceLink',
239
+ linkType: 'Contentful:Entry',
240
+ urn: resolvableEntryUrn
241
+ }
242
+ }
243
+ }))));
222
244
  expect(getByTestId('cf-ui-skeleton-form')).toBeDefined();
245
+ expect(queryByTestId('cf-ui-entry-card')).toBeNull();
246
+ resolveEntry(_published_entryjson.default);
247
+ await (0, _react1.waitFor)(()=>expect(getByTestId('cf-ui-entry-card')).toBeDefined());
248
+ expect(queryByTestId('cf-ui-skeleton-form')).toBeNull();
223
249
  });
224
250
  it('renders unsupported entity card when resource type is unknown', async ()=>{
225
251
  const { getByText } = renderResourceCard({
@@ -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,10 +12,10 @@ 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';
18
- import moment from 'moment';
19
19
  import PQueue from 'p-queue';
20
20
  import { SharedQueryClientProvider, useQuery, useQueryClient } from './queryClient';
21
21
  export function isContentfulResourceInfo(info) {
@@ -75,18 +75,10 @@ async function fetchContentfulEntry({ urn, fetch, options }) {
75
75
  const environmentId = resourceIdMatch?.groups?.environmentId || 'master';
76
76
  const entryId = resourceIdMatch.groups.entityId;
77
77
  const [space, entry] = await Promise.all([
78
- fetch([
79
- 'space',
80
- spaceId
81
- ], ({ cmaClient })=>cmaClient.space.get({
78
+ fetch(createGetSpaceKey(spaceId), ({ cmaClient })=>cmaClient.space.get({
82
79
  spaceId
83
80
  }), options),
84
- fetch([
85
- 'entry',
86
- spaceId,
87
- environmentId,
88
- entryId
89
- ], ({ cmaClient })=>cmaClient.entry.get({
81
+ fetch(createGetEntryKey(spaceId, environmentId, entryId), ({ cmaClient })=>cmaClient.entry.get({
90
82
  spaceId,
91
83
  environmentId,
92
84
  entryId
@@ -94,12 +86,7 @@ async function fetchContentfulEntry({ urn, fetch, options }) {
94
86
  ]);
95
87
  const contentTypeId = entry.sys.contentType.sys.id;
96
88
  const [contentType, defaultLocaleCode] = await Promise.all([
97
- fetch([
98
- 'contentType',
99
- spaceId,
100
- environmentId,
101
- contentTypeId
102
- ], ({ cmaClient })=>cmaClient.contentType.get({
89
+ fetch(createGetContentTypeKey(spaceId, environmentId, contentTypeId), ({ cmaClient })=>cmaClient.contentType.get({
103
90
  contentTypeId,
104
91
  spaceId,
105
92
  environmentId
@@ -311,7 +298,6 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = cons
311
298
  const query = {
312
299
  'environment.sys.id': environmentId,
313
300
  'sys.status': 'scheduled',
314
- 'scheduledFor.datetime[gte]': moment().startOf('hour').toISOString(),
315
301
  'entity.sys.linkType[in]': 'Entry,Asset',
316
302
  order: '-scheduledFor.datetime',
317
303
  limit: maxScheduledActions
@@ -385,17 +371,41 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = cons
385
371
  useEffect(()=>{
386
372
  function findSameSpaceQueries() {
387
373
  const queries = queryCache.findAll({
388
- type: 'active',
389
374
  predicate: (query)=>isSameSpaceEntityQueryKey(query.queryKey)
390
375
  });
391
376
  return queries;
392
377
  }
393
378
  if (typeof onEntityChanged !== 'function') {
394
- return onSlideInNavigation(({ oldSlideLevel, newSlideLevel })=>{
379
+ return onSlideInNavigation(async ({ oldSlideLevel, newSlideLevel })=>{
395
380
  if (oldSlideLevel > newSlideLevel) {
396
- findSameSpaceQueries().forEach((query)=>{
397
- void queryClient.invalidateQueries(query.queryKey);
398
- });
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
+ }));
399
409
  }
400
410
  });
401
411
  }
@@ -438,7 +448,8 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = cons
438
448
  isSameSpaceEntityQueryKey,
439
449
  queryClient,
440
450
  getEntity,
441
- onSlideInNavigation
451
+ onSlideInNavigation,
452
+ cmaClient
442
453
  ]);
443
454
  const getResourceProvider = useCallback(function getResourceProvider(organizationId, appDefinitionId) {
444
455
  const queryKey = [
@@ -544,6 +555,8 @@ export function useResourceProvider(organizationId, appDefinitionId) {
544
555
  };
545
556
  }
546
557
  function EntityProvider({ children, ...props }) {
547
- return /*#__PURE__*/ React.createElement(SharedQueryClientProvider, null, /*#__PURE__*/ React.createElement(InternalServiceProvider, props, children));
558
+ return /*#__PURE__*/ React.createElement(SharedQueryClientProvider, {
559
+ client: props.queryClient
560
+ }, /*#__PURE__*/ React.createElement(InternalServiceProvider, props, children));
548
561
  }
549
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
- const allContentTypes = props.sdk.space.getCachedContentTypes();
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
- const allContentTypes = props.sdk.space.getCachedContentTypes();
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,
@@ -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 getContentTypes('create');
31
- if (!isEqual(creatable, creatableContentTypes)) {
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 when no data is provided', ()=>{
171
- const { getByTestId } = renderResourceCard();
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({
@@ -8,4 +8,5 @@ export declare function renderActions(props: {
8
8
  onRemove?: () => void;
9
9
  isDisabled: boolean;
10
10
  entityFile?: File;
11
+ onAddToReleaseAction?: () => void;
11
12
  }): (React.JSX.Element | null)[];
@@ -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;
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { FieldConnector } from '@contentful/field-editor-shared';
3
3
  import type { LinkActionsProps } from '../components';
4
- import { Action, ActionLabels, FieldAppSDK, ViewType } from '../types';
4
+ import { Action, ActionLabels, FieldAppSDK, ViewType, Entry, Asset } from '../types';
5
5
  import { CustomCardRenderer, RenderCustomMissingEntityCard } from './customCardTypes';
6
6
  export interface ReferenceEditorProps {
7
7
  /**
@@ -33,6 +33,10 @@ export interface ReferenceEditorProps {
33
33
  oldIndex: number;
34
34
  newIndex: number;
35
35
  }) => void;
36
+ addReferenceToRelease?: (reference: Entry | Asset, localeCode?: string, options?: {
37
+ openModalForVersionSelection?: boolean;
38
+ skipNestedReferencesPrompt?: boolean;
39
+ }) => Promise<void>;
36
40
  }
37
41
  export type CustomActionProps = LinkActionsProps;
38
42
  export declare function ReferenceEditor<T>(props: ReferenceEditorProps & {
@@ -35,4 +35,5 @@ export type CustomEntityCardProps = {
35
35
  releaseStatusMap?: ReleaseStatusMap;
36
36
  releaseAction?: ReleaseAction;
37
37
  release?: ReleaseV2Props;
38
+ onAddToRelease?: () => void;
38
39
  } & Partial<WrappedAssetCardProps>;
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { RenderCustomMissingEntityCard } from '../../common/customCardTypes';
3
3
  import { ReferenceEditorProps } from '../../common/ReferenceEditor';
4
- import { ContentType, RenderDragFn } from '../../types';
4
+ import { ContentType, Entry, RenderDragFn } from '../../types';
5
5
  export type EntryCardReferenceEditorProps = ReferenceEditorProps & {
6
6
  entryId: string;
7
7
  index?: number;
@@ -17,5 +17,9 @@ export type EntryCardReferenceEditorProps = ReferenceEditorProps & {
17
17
  activeLocales?: {
18
18
  code: string;
19
19
  }[];
20
+ addReferenceToRelease?: (reference: Entry, localeCode?: string, options?: {
21
+ openModalForVersionSelection?: boolean;
22
+ skipNestedReferencesPrompt?: boolean;
23
+ }) => Promise<void>;
20
24
  };
21
25
  export declare function FetchingWrappedEntryCard(props: EntryCardReferenceEditorProps): React.JSX.Element;
@@ -31,8 +31,9 @@ export interface WrappedEntryCardProps {
31
31
  releaseEntityStatus?: ReleaseEntityStatus;
32
32
  releaseStatusMap?: ReleaseStatusMap;
33
33
  release?: ReleaseV2Props;
34
+ onAddToRelease?: () => void;
34
35
  }
35
- export declare 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, }: WrappedEntryCardProps): React.JSX.Element;
36
+ export declare 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, }: WrappedEntryCardProps): React.JSX.Element;
36
37
  export declare namespace WrappedEntryCard {
37
38
  var defaultProps: {
38
39
  isClickable: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/field-editor-reference",
3
- "version": "6.19.3",
3
+ "version": "6.19.4-canary.3+91057409",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/esm/index.js",
6
6
  "types": "dist/types/index.d.ts",
@@ -39,7 +39,7 @@
39
39
  "@contentful/f36-components": "^5.8.1",
40
40
  "@contentful/f36-icons": "^5.8.1",
41
41
  "@contentful/f36-tokens": "^5.1.0",
42
- "@contentful/field-editor-shared": "^2.17.1",
42
+ "@contentful/field-editor-shared": "^2.17.2-canary.5+91057409",
43
43
  "@contentful/mimetype": "^2.2.29",
44
44
  "@dnd-kit/core": "^6.0.8",
45
45
  "@dnd-kit/sortable": "^8.0.0",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "devDependencies": {
58
58
  "@contentful/app-sdk": "^4.42.0",
59
- "@contentful/field-editor-test-utils": "^1.7.0",
59
+ "@contentful/field-editor-test-utils": "^1.7.1-canary.80+91057409",
60
60
  "@lingui/core": "5.3.0",
61
61
  "@testing-library/react-hooks": "^8.0.1"
62
62
  },
@@ -68,5 +68,5 @@
68
68
  "publishConfig": {
69
69
  "registry": "https://npm.pkg.github.com/"
70
70
  },
71
- "gitHead": "aae5fce24a644941244f5c19ae688274fbf48f80"
71
+ "gitHead": "91057409eff77b4928882406258a35549f14e88d"
72
72
  }