@contentful/field-editor-reference 5.18.0 → 5.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/__fixtures__/FakeSdk.js +3 -3
- package/dist/cjs/__fixtures__/asset/index.js +10 -10
- package/dist/cjs/__fixtures__/content-type/index.js +1 -1
- package/dist/cjs/__fixtures__/entry/index.js +7 -7
- package/dist/cjs/__fixtures__/fixtures.js +8 -6
- package/dist/cjs/__fixtures__/locale/index.js +2 -2
- package/dist/cjs/__fixtures__/space/index.js +1 -1
- package/dist/cjs/assets/MultipleMediaEditor.js +7 -5
- package/dist/cjs/assets/SingleMediaEditor.js +6 -4
- package/dist/cjs/assets/WrappedAssetCard/AssetCardActions.js +21 -19
- package/dist/cjs/assets/WrappedAssetCard/FetchingWrappedAssetCard.js +18 -11
- package/dist/cjs/assets/WrappedAssetCard/WrappedAssetCard.js +20 -11
- package/dist/cjs/assets/WrappedAssetCard/WrappedAssetLink.js +12 -9
- package/dist/cjs/assets/index.js +3 -3
- package/dist/cjs/common/EntityStore.js +53 -53
- package/dist/cjs/common/MultipleReferenceEditor.js +20 -11
- package/dist/cjs/common/ReferenceEditor.js +7 -5
- package/dist/cjs/common/SingleReferenceEditor.js +11 -7
- package/dist/cjs/common/SortableLinkList.js +14 -14
- package/dist/cjs/common/customCardTypes.js +4 -2
- package/dist/cjs/common/queryClient.js +102 -0
- package/dist/cjs/common/useContentTypePermissions.js +3 -1
- package/dist/cjs/common/useEditorPermissions.js +15 -3
- package/dist/cjs/common/useEditorPermissions.spec.js +13 -12
- package/dist/cjs/components/AssetThumbnail/AssetThumbnail.js +5 -3
- package/dist/cjs/components/CreateEntryLinkButton/CreateEntryLinkButton.js +13 -9
- package/dist/cjs/components/CreateEntryLinkButton/CreateEntryLinkButton.spec.js +17 -15
- package/dist/cjs/components/CreateEntryLinkButton/CreateEntryMenuTrigger.js +29 -19
- package/dist/cjs/components/CreateEntryLinkButton/CreateEntryMenuTrigger.spec.js +14 -12
- package/dist/cjs/components/LinkActions/CombinedLinkActions.js +28 -21
- package/dist/cjs/components/LinkActions/LinkActions.js +17 -15
- package/dist/cjs/components/LinkActions/LinkEntityActions.js +26 -17
- package/dist/cjs/components/LinkActions/NoLinkPermissionsInfo.js +5 -3
- package/dist/cjs/components/LinkActions/helpers.js +10 -5
- package/dist/cjs/components/LinkActions/redesignStyles.js +4 -4
- package/dist/cjs/components/LinkActions/styles.js +1 -1
- package/dist/cjs/components/MissingEntityCard/MissingEntityCard.js +11 -9
- package/dist/cjs/components/ResourceEntityErrorCard/ResourceEntityErrorCard.js +6 -4
- package/dist/cjs/components/ResourceEntityErrorCard/UnsupportedEntityCard.js +6 -4
- package/dist/cjs/components/ScheduledIconWithTooltip/ScheduleTooltip.js +10 -8
- package/dist/cjs/components/ScheduledIconWithTooltip/ScheduledIconWithTooltip.js +9 -4
- package/dist/cjs/components/ScheduledIconWithTooltip/formatDateAndTime.js +5 -4
- package/dist/cjs/components/SpaceName/SpaceName.js +10 -8
- package/dist/cjs/components/index.js +12 -12
- package/dist/cjs/entries/MultipleEntryReferenceEditor.js +9 -7
- package/dist/cjs/entries/SingleEntryReferenceEditor.js +7 -5
- package/dist/cjs/entries/WrappedEntryCard/FetchingWrappedEntryCard.js +20 -11
- package/dist/cjs/entries/WrappedEntryCard/WrappedEntryCard.js +23 -17
- package/dist/cjs/entries/index.js +3 -3
- package/dist/cjs/index.js +38 -34
- package/dist/cjs/resources/Cards/ContentfulEntryCard.js +13 -4
- package/dist/cjs/resources/Cards/ResourceCard.js +16 -12
- package/dist/cjs/resources/Cards/ResourceCard.spec.js +20 -17
- package/dist/cjs/resources/MultipleResourceReferenceEditor.js +20 -17
- package/dist/cjs/resources/MultipleResourceReferenceEditor.spec.js +34 -13
- package/dist/cjs/resources/SingleResourceReferenceEditor.js +11 -9
- package/dist/cjs/resources/SingleResourceReferenceEditor.spec.js +13 -6
- package/dist/cjs/resources/testHelpers/resourceEditorHelpers.js +12 -5
- package/dist/cjs/resources/useResourceLinkActions.js +11 -2
- package/dist/cjs/types.js +3 -3
- package/dist/cjs/utils/fromFieldValidations.js +2 -1
- package/dist/cjs/utils/useSortIDs.js +6 -4
- package/dist/esm/__fixtures__/FakeSdk.js +3 -3
- package/dist/esm/assets/MultipleMediaEditor.js +3 -3
- package/dist/esm/assets/SingleMediaEditor.js +2 -2
- package/dist/esm/assets/WrappedAssetCard/AssetCardActions.js +12 -12
- package/dist/esm/assets/WrappedAssetCard/FetchingWrappedAssetCard.js +14 -9
- package/dist/esm/assets/WrappedAssetCard/WrappedAssetCard.js +15 -7
- package/dist/esm/assets/WrappedAssetCard/WrappedAssetLink.js +7 -6
- package/dist/esm/common/EntityStore.js +40 -42
- package/dist/esm/common/MultipleReferenceEditor.js +16 -9
- package/dist/esm/common/ReferenceEditor.js +2 -2
- package/dist/esm/common/SingleReferenceEditor.js +7 -5
- package/dist/esm/common/SortableLinkList.js +12 -12
- package/dist/esm/common/queryClient.js +44 -0
- package/dist/esm/common/useContentTypePermissions.js +3 -1
- package/dist/esm/common/useEditorPermissions.js +15 -3
- package/dist/esm/common/useEditorPermissions.spec.js +13 -12
- package/dist/esm/components/AssetThumbnail/AssetThumbnail.js +1 -1
- package/dist/esm/components/CreateEntryLinkButton/CreateEntryLinkButton.js +7 -5
- package/dist/esm/components/CreateEntryLinkButton/CreateEntryLinkButton.spec.js +12 -12
- package/dist/esm/components/CreateEntryLinkButton/CreateEntryMenuTrigger.js +23 -15
- package/dist/esm/components/CreateEntryLinkButton/CreateEntryMenuTrigger.spec.js +9 -8
- package/dist/esm/components/LinkActions/CombinedLinkActions.js +30 -19
- package/dist/esm/components/LinkActions/LinkActions.js +9 -9
- package/dist/esm/components/LinkActions/LinkEntityActions.js +18 -11
- package/dist/esm/components/LinkActions/NoLinkPermissionsInfo.js +1 -1
- package/dist/esm/components/LinkActions/helpers.js +7 -2
- package/dist/esm/components/MissingEntityCard/MissingEntityCard.js +6 -6
- package/dist/esm/components/ResourceEntityErrorCard/ResourceEntityErrorCard.js +2 -2
- package/dist/esm/components/ResourceEntityErrorCard/UnsupportedEntityCard.js +2 -2
- package/dist/esm/components/ScheduledIconWithTooltip/ScheduleTooltip.js +3 -3
- package/dist/esm/components/ScheduledIconWithTooltip/ScheduledIconWithTooltip.js +5 -2
- package/dist/esm/components/ScheduledIconWithTooltip/formatDateAndTime.js +11 -2
- package/dist/esm/components/SpaceName/SpaceName.js +5 -5
- package/dist/esm/entries/MultipleEntryReferenceEditor.js +5 -5
- package/dist/esm/entries/SingleEntryReferenceEditor.js +3 -3
- package/dist/esm/entries/WrappedEntryCard/FetchingWrappedEntryCard.js +15 -8
- package/dist/esm/entries/WrappedEntryCard/WrappedEntryCard.js +18 -14
- package/dist/esm/index.js +1 -0
- package/dist/esm/resources/Cards/ContentfulEntryCard.js +9 -2
- package/dist/esm/resources/Cards/ResourceCard.js +12 -10
- package/dist/esm/resources/Cards/ResourceCard.spec.js +12 -11
- package/dist/esm/resources/MultipleResourceReferenceEditor.js +14 -13
- package/dist/esm/resources/MultipleResourceReferenceEditor.spec.js +30 -11
- package/dist/esm/resources/SingleResourceReferenceEditor.js +6 -6
- package/dist/esm/resources/SingleResourceReferenceEditor.spec.js +9 -4
- package/dist/esm/resources/testHelpers/resourceEditorHelpers.js +9 -2
- package/dist/esm/resources/useResourceLinkActions.js +11 -2
- package/dist/esm/utils/fromFieldValidations.js +1 -0
- package/dist/esm/utils/useSortIDs.js +2 -2
- package/dist/types/common/EntityStore.d.ts +1 -1
- package/dist/types/common/queryClient.d.ts +9 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +2 -2
|
@@ -12,10 +12,11 @@ function _define_property(obj, key, value) {
|
|
|
12
12
|
return obj;
|
|
13
13
|
}
|
|
14
14
|
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
15
|
-
import { QueryCache, QueryClient, QueryClientProvider, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
16
15
|
import constate from 'constate';
|
|
17
16
|
import { createClient } from 'contentful-management';
|
|
18
17
|
import PQueue from 'p-queue';
|
|
18
|
+
import { SharedQueryClientProvider, useQuery, useQueryClient } from './queryClient';
|
|
19
|
+
// global queue for all requests, the actual number is picked without scientific research
|
|
19
20
|
const globalQueue = new PQueue({
|
|
20
21
|
concurrency: 50
|
|
21
22
|
});
|
|
@@ -33,7 +34,8 @@ const isEntityQueryKey = (queryKey)=>{
|
|
|
33
34
|
return Array.isArray(queryKey) && (queryKey[0] === 'Entry' || queryKey[0] === 'Asset') && queryKey.length === 4;
|
|
34
35
|
};
|
|
35
36
|
async function fetchContentfulEntry(params) {
|
|
36
|
-
const { urn
|
|
37
|
+
const { urn, fetch, options } = params;
|
|
38
|
+
// TODO use resource-names package EntryResourceName `fromString` method instead when the package becomes public
|
|
37
39
|
const resourceId = urn.split(':', 6)[5];
|
|
38
40
|
const ENTITY_RESOURCE_ID_REGEX = RegExp("^spaces\\/(?<spaceId>[^/]+)(?:\\/environments\\/(?<environmentId>[^/]+))?\\/entries\\/(?<entityId>[^/]+)$");
|
|
39
41
|
const resourceIdMatch = resourceId.match(ENTITY_RESOURCE_ID_REGEX);
|
|
@@ -47,7 +49,7 @@ async function fetchContentfulEntry(params) {
|
|
|
47
49
|
fetch([
|
|
48
50
|
'space',
|
|
49
51
|
spaceId
|
|
50
|
-
], ({ cmaClient
|
|
52
|
+
], ({ cmaClient })=>cmaClient.space.get({
|
|
51
53
|
spaceId
|
|
52
54
|
}), options),
|
|
53
55
|
fetch([
|
|
@@ -55,7 +57,7 @@ async function fetchContentfulEntry(params) {
|
|
|
55
57
|
spaceId,
|
|
56
58
|
environmentId,
|
|
57
59
|
entryId
|
|
58
|
-
], ({ cmaClient
|
|
60
|
+
], ({ cmaClient })=>cmaClient.entry.get({
|
|
59
61
|
spaceId,
|
|
60
62
|
environmentId,
|
|
61
63
|
entryId
|
|
@@ -68,7 +70,7 @@ async function fetchContentfulEntry(params) {
|
|
|
68
70
|
spaceId,
|
|
69
71
|
environmentId,
|
|
70
72
|
contentTypeId
|
|
71
|
-
], ({ cmaClient
|
|
73
|
+
], ({ cmaClient })=>cmaClient.contentType.get({
|
|
72
74
|
contentTypeId,
|
|
73
75
|
spaceId,
|
|
74
76
|
environmentId
|
|
@@ -77,7 +79,7 @@ async function fetchContentfulEntry(params) {
|
|
|
77
79
|
'defaultLocale',
|
|
78
80
|
spaceId,
|
|
79
81
|
environmentId
|
|
80
|
-
], async ({ cmaClient
|
|
82
|
+
], async ({ cmaClient })=>{
|
|
81
83
|
const locales = await cmaClient.locale.getMany({
|
|
82
84
|
spaceId,
|
|
83
85
|
environmentId,
|
|
@@ -127,7 +129,7 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = cons
|
|
|
127
129
|
props.queryConcurrency
|
|
128
130
|
]);
|
|
129
131
|
const fetch = useCallback(function fetch(queryKey, fn, options = {}) {
|
|
130
|
-
const { priority
|
|
132
|
+
const { priority, ...queryOptions } = options;
|
|
131
133
|
return queryClient.fetchQuery(queryKey, ()=>queryQueue.add(()=>fn({
|
|
132
134
|
cmaClient
|
|
133
135
|
}), {
|
|
@@ -147,7 +149,8 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = cons
|
|
|
147
149
|
spaceId,
|
|
148
150
|
environmentId
|
|
149
151
|
];
|
|
150
|
-
return fetch(queryKey,
|
|
152
|
+
return fetch(queryKey, // @ts-expect-error
|
|
153
|
+
({ cmaClient })=>{
|
|
151
154
|
if (entityType === 'Entry') {
|
|
152
155
|
return cmaClient.entry.get({
|
|
153
156
|
entryId: entityId,
|
|
@@ -169,8 +172,18 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = cons
|
|
|
169
172
|
currentSpaceId,
|
|
170
173
|
currentEnvironmentId
|
|
171
174
|
]);
|
|
172
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Fetch all scheduled actions for a given entity.
|
|
177
|
+
* This function fetches all schedules for all entries and then returns
|
|
178
|
+
* a filtered result based on the entityID provided.
|
|
179
|
+
*
|
|
180
|
+
* The result is then reused/cached for subsequent calls to this function.
|
|
181
|
+
*/ const getEntityScheduledActions = useCallback(function getEntityScheduledActions(entityType, entityId, options) {
|
|
182
|
+
// This is fixed to force the cache to reuse previous results
|
|
173
183
|
const fixedEntityCacheId = 'scheduledActionEntityId';
|
|
184
|
+
// A space+environment combo can only have up to 500 scheduled actions
|
|
185
|
+
// With this request we fetch all schedules and can reuse the results.
|
|
186
|
+
// See https://www.contentful.com/developers/docs/references/content-management-api/#/reference/scheduled-actions/limitations
|
|
174
187
|
const maxScheduledActions = 500;
|
|
175
188
|
const spaceId = options?.spaceId ?? currentSpaceId;
|
|
176
189
|
const environmentId = options?.environmentId ?? currentEnvironmentId;
|
|
@@ -181,7 +194,8 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = cons
|
|
|
181
194
|
spaceId,
|
|
182
195
|
environmentId
|
|
183
196
|
];
|
|
184
|
-
|
|
197
|
+
// Fetch + Filter by entity ID in the end
|
|
198
|
+
return fetch(queryKey, async ({ cmaClient })=>{
|
|
185
199
|
const response = await cmaClient.scheduledActions.getMany({
|
|
186
200
|
spaceId,
|
|
187
201
|
query: {
|
|
@@ -225,6 +239,7 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = cons
|
|
|
225
239
|
currentSpaceId,
|
|
226
240
|
environmentIds
|
|
227
241
|
]);
|
|
242
|
+
// @ts-expect-error ...
|
|
228
243
|
const onEntityChanged = props.sdk.space.onEntityChanged;
|
|
229
244
|
const onSlideInNavigation = props.sdk.navigator.onSlideInNavigation;
|
|
230
245
|
useEffect(()=>{
|
|
@@ -235,15 +250,16 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = cons
|
|
|
235
250
|
});
|
|
236
251
|
}
|
|
237
252
|
if (typeof onEntityChanged !== 'function') {
|
|
238
|
-
return onSlideInNavigation(({ oldSlideLevel
|
|
253
|
+
return onSlideInNavigation(({ oldSlideLevel, newSlideLevel })=>{
|
|
239
254
|
if (oldSlideLevel > newSlideLevel) {
|
|
240
255
|
findSameSpaceQueries().forEach((query)=>{
|
|
256
|
+
// automatically refetches the query
|
|
241
257
|
void queryClient.invalidateQueries(query.queryKey);
|
|
242
258
|
});
|
|
243
259
|
}
|
|
244
260
|
});
|
|
245
261
|
}
|
|
246
|
-
const subscribeQuery = ({ queryKey
|
|
262
|
+
const subscribeQuery = ({ queryKey, queryHash })=>{
|
|
247
263
|
const [entityType, entityId] = queryKey;
|
|
248
264
|
entityChangeUnsubscribers.current[queryHash] = onEntityChanged(entityType, entityId, (data)=>{
|
|
249
265
|
queryClient.setQueryData(queryKey, data);
|
|
@@ -254,8 +270,8 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = cons
|
|
|
254
270
|
if (!event) {
|
|
255
271
|
return;
|
|
256
272
|
}
|
|
257
|
-
const { type
|
|
258
|
-
const { queryKey
|
|
273
|
+
const { type, query } = event;
|
|
274
|
+
const { queryKey, queryHash } = query;
|
|
259
275
|
if (!isSameSpaceEntityQueryKey(queryKey)) {
|
|
260
276
|
return;
|
|
261
277
|
}
|
|
@@ -263,6 +279,7 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = cons
|
|
|
263
279
|
subscribeQuery(query);
|
|
264
280
|
}
|
|
265
281
|
if (type === 'removed') {
|
|
282
|
+
// calling unsubscribe
|
|
266
283
|
entityChangeUnsubscribers.current[queryHash]?.();
|
|
267
284
|
}
|
|
268
285
|
});
|
|
@@ -287,24 +304,24 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = cons
|
|
|
287
304
|
getEntity,
|
|
288
305
|
getEntityScheduledActions
|
|
289
306
|
};
|
|
290
|
-
}, ({ fetch
|
|
307
|
+
}, ({ fetch })=>fetch, ({ getResource, getEntity, getEntityScheduledActions })=>({
|
|
291
308
|
getResource,
|
|
292
309
|
getEntity,
|
|
293
310
|
getEntityScheduledActions
|
|
294
|
-
}), ({ ids
|
|
311
|
+
}), ({ ids })=>({
|
|
295
312
|
environment: ids.environmentAlias ?? ids.environment,
|
|
296
313
|
space: ids.space
|
|
297
314
|
}));
|
|
298
315
|
export function useEntity(entityType, entityId, options) {
|
|
299
|
-
const { space
|
|
300
|
-
const { getEntity
|
|
316
|
+
const { space, environment } = useCurrentIds();
|
|
317
|
+
const { getEntity } = useEntityLoader();
|
|
301
318
|
const queryKey = [
|
|
302
319
|
entityType,
|
|
303
320
|
entityId,
|
|
304
321
|
options?.spaceId ?? space,
|
|
305
322
|
options?.environmentId ?? environment
|
|
306
323
|
];
|
|
307
|
-
const { status
|
|
324
|
+
const { status, data } = useQuery(queryKey, ()=>getEntity(entityType, entityId, options), {
|
|
308
325
|
enabled: options?.enabled
|
|
309
326
|
});
|
|
310
327
|
return {
|
|
@@ -318,8 +335,8 @@ export function useResource(resourceType, urn, options) {
|
|
|
318
335
|
resourceType,
|
|
319
336
|
urn
|
|
320
337
|
];
|
|
321
|
-
const { getResource
|
|
322
|
-
const { status
|
|
338
|
+
const { getResource } = useEntityLoader();
|
|
339
|
+
const { status, data, error } = useQuery(queryKey, ()=>getResource(resourceType, urn, options), {
|
|
323
340
|
enabled: options?.enabled
|
|
324
341
|
});
|
|
325
342
|
return {
|
|
@@ -328,26 +345,7 @@ export function useResource(resourceType, urn, options) {
|
|
|
328
345
|
error
|
|
329
346
|
};
|
|
330
347
|
}
|
|
331
|
-
function EntityProvider({ children
|
|
332
|
-
|
|
333
|
-
const queryCache = new QueryCache();
|
|
334
|
-
const queryClient = new QueryClient({
|
|
335
|
-
queryCache,
|
|
336
|
-
defaultOptions: {
|
|
337
|
-
queries: {
|
|
338
|
-
useErrorBoundary: false,
|
|
339
|
-
refetchOnWindowFocus: false,
|
|
340
|
-
refetchOnReconnect: true,
|
|
341
|
-
refetchOnMount: false,
|
|
342
|
-
staleTime: Infinity,
|
|
343
|
-
retry: false
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
return queryClient;
|
|
348
|
-
}, []);
|
|
349
|
-
return React.createElement(QueryClientProvider, {
|
|
350
|
-
client: reactQueryClient
|
|
351
|
-
}, React.createElement(InternalServiceProvider, props, children));
|
|
348
|
+
function EntityProvider({ children, ...props }) {
|
|
349
|
+
return /*#__PURE__*/ React.createElement(SharedQueryClientProvider, null, /*#__PURE__*/ React.createElement(InternalServiceProvider, props, children));
|
|
352
350
|
}
|
|
353
351
|
export { EntityProvider, useEntityLoader };
|
|
@@ -25,18 +25,24 @@ const nullableValue = {
|
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
function Editor(props) {
|
|
28
|
-
const { setValue
|
|
28
|
+
const { setValue, entityType, onSortingEnd, setIndexToUpdate } = props;
|
|
29
29
|
const editorPermissions = useEditorPermissions(props);
|
|
30
30
|
const items = React.useMemo(()=>{
|
|
31
|
-
return (props.items || [])
|
|
31
|
+
return (props.items || [])// If null values have found their way into the persisted
|
|
32
|
+
// value for the multiref field, replace them with an object
|
|
33
|
+
// that has the shape of a Link to make the missing entry/asset
|
|
34
|
+
// card render
|
|
35
|
+
.map((link)=>link || nullableValue);
|
|
32
36
|
}, [
|
|
33
37
|
props.items
|
|
34
38
|
]);
|
|
35
|
-
const { rearrangeSortIDs
|
|
39
|
+
const { rearrangeSortIDs } = useSortIDs(items);
|
|
36
40
|
const onSortStart = useCallback(()=>{
|
|
37
41
|
document.body.classList.add('grabbing');
|
|
38
42
|
}, []);
|
|
39
|
-
const onSortEnd = useCallback(({ oldIndex
|
|
43
|
+
const onSortEnd = useCallback(({ oldIndex, newIndex })=>{
|
|
44
|
+
// custom callback that is invoked *before* we sort the array
|
|
45
|
+
// e.g. in Compose we want to sort the references in the referenceMap before re-rendering drag and drop
|
|
40
46
|
onSortingEnd && onSortingEnd({
|
|
41
47
|
oldIndex,
|
|
42
48
|
newIndex
|
|
@@ -80,24 +86,25 @@ function Editor(props) {
|
|
|
80
86
|
onLink,
|
|
81
87
|
itemsLength: items.length
|
|
82
88
|
});
|
|
83
|
-
const customCardRenderer = useCallback((cardProps, _, renderDefaultCard)=>props.renderCustomCard ? props.renderCustomCard(cardProps, linkActionsProps, renderDefaultCard) : false,
|
|
89
|
+
const customCardRenderer = useCallback((cardProps, _, renderDefaultCard)=>props.renderCustomCard ? props.renderCustomCard(cardProps, linkActionsProps, renderDefaultCard) : false, // eslint-disable-next-line react-hooks/exhaustive-deps -- TODO: Evaluate the dependencies
|
|
90
|
+
[
|
|
84
91
|
linkActionsProps
|
|
85
92
|
]);
|
|
86
|
-
return React.createElement(React.Fragment, null, props.children({
|
|
93
|
+
return /*#__PURE__*/ React.createElement(React.Fragment, null, props.children({
|
|
87
94
|
...props,
|
|
88
95
|
onSortStart,
|
|
89
96
|
onSortEnd,
|
|
90
97
|
onMove,
|
|
91
98
|
renderCustomCard: props.renderCustomCard && customCardRenderer
|
|
92
|
-
}), React.createElement(LinkEntityActions, {
|
|
99
|
+
}), /*#__PURE__*/ React.createElement(LinkEntityActions, {
|
|
93
100
|
renderCustomActions: props.renderCustomActions,
|
|
94
101
|
...linkActionsProps
|
|
95
102
|
}));
|
|
96
103
|
}
|
|
97
104
|
export function MultipleReferenceEditor(props) {
|
|
98
105
|
const allContentTypes = props.sdk.space.getCachedContentTypes();
|
|
99
|
-
return React.createElement(ReferenceEditor, props, ({ value
|
|
100
|
-
return React.createElement(Editor, {
|
|
106
|
+
return /*#__PURE__*/ React.createElement(ReferenceEditor, props, ({ value, disabled, setValue, externalReset })=>{
|
|
107
|
+
return /*#__PURE__*/ React.createElement(Editor, {
|
|
101
108
|
...props,
|
|
102
109
|
items: value || emptyArray,
|
|
103
110
|
isDisabled: disabled,
|
|
@@ -3,9 +3,9 @@ import { FieldConnector } from '@contentful/field-editor-shared';
|
|
|
3
3
|
import deepEqual from 'deep-equal';
|
|
4
4
|
import { EntityProvider } from './EntityStore';
|
|
5
5
|
export function ReferenceEditor(props) {
|
|
6
|
-
return React.createElement(EntityProvider, {
|
|
6
|
+
return /*#__PURE__*/ React.createElement(EntityProvider, {
|
|
7
7
|
sdk: props.sdk
|
|
8
|
-
}, React.createElement(FieldConnector, {
|
|
8
|
+
}, /*#__PURE__*/ React.createElement(FieldConnector, {
|
|
9
9
|
debounce: 0,
|
|
10
10
|
field: props.sdk.field,
|
|
11
11
|
isInitiallyDisabled: props.isInitiallyDisabled,
|
|
@@ -5,7 +5,7 @@ import { useLinkActionsProps } from '../components/LinkActions/LinkEntityActions
|
|
|
5
5
|
import { ReferenceEditor } from './ReferenceEditor';
|
|
6
6
|
import { useEditorPermissions } from './useEditorPermissions';
|
|
7
7
|
function Editor(props) {
|
|
8
|
-
const { setValue
|
|
8
|
+
const { setValue, entityType } = props;
|
|
9
9
|
const editorPermissions = useEditorPermissions(props);
|
|
10
10
|
const onCreate = useCallback((id)=>void setValue({
|
|
11
11
|
sys: {
|
|
@@ -37,11 +37,13 @@ function Editor(props) {
|
|
|
37
37
|
onCreate,
|
|
38
38
|
onLink
|
|
39
39
|
});
|
|
40
|
-
|
|
40
|
+
// Inject card actions props into the given custom card renderer
|
|
41
|
+
const customCardRenderer = useCallback((cardProps, _, renderDefaultCard)=>props.renderCustomCard ? props.renderCustomCard(cardProps, linkActionsProps, renderDefaultCard) : false, // eslint-disable-next-line react-hooks/exhaustive-deps -- TODO: Evaluate the dependencies
|
|
42
|
+
[
|
|
41
43
|
linkActionsProps
|
|
42
44
|
]);
|
|
43
45
|
if (!props.entityId) {
|
|
44
|
-
return React.createElement(LinkEntityActions, {
|
|
46
|
+
return /*#__PURE__*/ React.createElement(LinkEntityActions, {
|
|
45
47
|
renderCustomActions: props.renderCustomActions,
|
|
46
48
|
...linkActionsProps
|
|
47
49
|
});
|
|
@@ -53,8 +55,8 @@ function Editor(props) {
|
|
|
53
55
|
}
|
|
54
56
|
export function SingleReferenceEditor(props) {
|
|
55
57
|
const allContentTypes = props.sdk.space.getCachedContentTypes();
|
|
56
|
-
return React.createElement(ReferenceEditor, props, ({ value
|
|
57
|
-
return React.createElement(Editor, {
|
|
58
|
+
return /*#__PURE__*/ React.createElement(ReferenceEditor, props, ({ value, setValue, disabled, externalReset })=>{
|
|
59
|
+
return /*#__PURE__*/ React.createElement(Editor, {
|
|
58
60
|
...props,
|
|
59
61
|
key: `${externalReset}-reference`,
|
|
60
62
|
entityId: value ? value.sys.id : '',
|
|
@@ -20,8 +20,8 @@ const styles = {
|
|
|
20
20
|
display: 'flex'
|
|
21
21
|
})
|
|
22
22
|
};
|
|
23
|
-
const SortableLink = ({ id
|
|
24
|
-
const { listeners
|
|
23
|
+
const SortableLink = ({ id, items, item, isDisabled = false, index, children })=>{
|
|
24
|
+
const { listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({
|
|
25
25
|
id
|
|
26
26
|
});
|
|
27
27
|
const style = {
|
|
@@ -34,16 +34,16 @@ const SortableLink = ({ id , items , item , isDisabled =false , index , children
|
|
|
34
34
|
};
|
|
35
35
|
const DragHandle = React.useCallback((props)=>{
|
|
36
36
|
const SortableDragHandle = ()=>props.drag;
|
|
37
|
-
return React.createElement("div", {
|
|
37
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
38
38
|
ref: setActivatorNodeRef,
|
|
39
39
|
className: styles.dragHandle,
|
|
40
40
|
...listeners
|
|
41
|
-
}, React.createElement(SortableDragHandle, null));
|
|
41
|
+
}, /*#__PURE__*/ React.createElement(SortableDragHandle, null));
|
|
42
42
|
}, [
|
|
43
43
|
listeners,
|
|
44
44
|
setActivatorNodeRef
|
|
45
45
|
]);
|
|
46
|
-
return React.createElement("div", {
|
|
46
|
+
return /*#__PURE__*/ React.createElement("div", {
|
|
47
47
|
ref: setNodeRef,
|
|
48
48
|
style: style,
|
|
49
49
|
className: cx(styles.item, {
|
|
@@ -57,8 +57,8 @@ const SortableLink = ({ id , items , item , isDisabled =false , index , children
|
|
|
57
57
|
DragHandle: isDisabled ? undefined : DragHandle
|
|
58
58
|
}));
|
|
59
59
|
};
|
|
60
|
-
export const SortableLinkList = ({ items
|
|
61
|
-
const { sortIDs
|
|
60
|
+
export const SortableLinkList = ({ items, isDisabled, className, children, onSortStart, onSortEnd, updateBeforeSortStart, sortingStrategy })=>{
|
|
61
|
+
const { sortIDs, rearrangeSortIDs } = useSortIDs(items);
|
|
62
62
|
const onSortStartHandler = React.useCallback((event)=>{
|
|
63
63
|
const index = sortIDs.findIndex((item)=>item.id === event.active.id);
|
|
64
64
|
updateBeforeSortStart?.({
|
|
@@ -71,7 +71,7 @@ export const SortableLinkList = ({ items , isDisabled , className , children , o
|
|
|
71
71
|
sortIDs
|
|
72
72
|
]);
|
|
73
73
|
const onSortEndHandler = React.useCallback((event)=>{
|
|
74
|
-
const { active
|
|
74
|
+
const { active, over } = event;
|
|
75
75
|
if (active && over && active.id !== over.id) {
|
|
76
76
|
const oldIndex = sortIDs.findIndex((item)=>item.id === active.id);
|
|
77
77
|
const newIndex = sortIDs.findIndex((item)=>item.id === over.id);
|
|
@@ -86,17 +86,17 @@ export const SortableLinkList = ({ items , isDisabled , className , children , o
|
|
|
86
86
|
sortIDs,
|
|
87
87
|
rearrangeSortIDs
|
|
88
88
|
]);
|
|
89
|
-
return React.createElement(DndContext, {
|
|
89
|
+
return /*#__PURE__*/ React.createElement(DndContext, {
|
|
90
90
|
onDragStart: onSortStartHandler,
|
|
91
91
|
onDragEnd: onSortEndHandler
|
|
92
|
-
}, React.createElement(SortableContext, {
|
|
92
|
+
}, /*#__PURE__*/ React.createElement(SortableContext, {
|
|
93
93
|
items: sortIDs,
|
|
94
94
|
strategy: sortingStrategy
|
|
95
|
-
}, React.createElement("div", {
|
|
95
|
+
}, /*#__PURE__*/ React.createElement("div", {
|
|
96
96
|
className: cx(styles.container, className)
|
|
97
97
|
}, items.map((item, index)=>{
|
|
98
98
|
const id = sortIDs[index]?.id;
|
|
99
|
-
return React.createElement(SortableLink, {
|
|
99
|
+
return /*#__PURE__*/ React.createElement(SortableLink, {
|
|
100
100
|
key: id,
|
|
101
101
|
id: id,
|
|
102
102
|
items: items,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { QueryClient, useQuery as useRQ } from '@tanstack/react-query';
|
|
3
|
+
/**
|
|
4
|
+
* A custom client context ensures zero conflict with host apps also using
|
|
5
|
+
* React Query.
|
|
6
|
+
*/ const clientContext = /*#__PURE__*/ React.createContext(undefined);
|
|
7
|
+
export function useQueryClient() {
|
|
8
|
+
const client = React.useContext(clientContext);
|
|
9
|
+
return React.useMemo(()=>{
|
|
10
|
+
if (client) {
|
|
11
|
+
return client;
|
|
12
|
+
}
|
|
13
|
+
return new QueryClient({
|
|
14
|
+
defaultOptions: {
|
|
15
|
+
queries: {
|
|
16
|
+
useErrorBoundary: false,
|
|
17
|
+
refetchOnWindowFocus: false,
|
|
18
|
+
refetchOnReconnect: true,
|
|
19
|
+
refetchOnMount: false,
|
|
20
|
+
staleTime: Infinity,
|
|
21
|
+
retry: false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}, [
|
|
26
|
+
client
|
|
27
|
+
]);
|
|
28
|
+
}
|
|
29
|
+
// @ts-expect-error
|
|
30
|
+
export const useQuery = (key, fn, opt)=>{
|
|
31
|
+
return useRQ(key, fn, {
|
|
32
|
+
...opt,
|
|
33
|
+
context: clientContext
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Provides access to a query client either by sharing an existing client or
|
|
38
|
+
* creating a new one.
|
|
39
|
+
*/ export function SharedQueryClientProvider({ children }) {
|
|
40
|
+
const client = useQueryClient();
|
|
41
|
+
return /*#__PURE__*/ React.createElement(clientContext.Provider, {
|
|
42
|
+
value: client
|
|
43
|
+
}, children);
|
|
44
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { useAccessApi } from './useAccessApi';
|
|
3
3
|
async function filter(arr, predicate) {
|
|
4
|
+
// intentionally parallel as we assume it's cached in the implementation of the access api
|
|
4
5
|
const fail = Symbol();
|
|
5
6
|
const results = await Promise.all(arr.map(async (item)=>await predicate(item) ? item : fail));
|
|
6
7
|
return results.filter((x)=>x !== fail);
|
|
@@ -21,7 +22,7 @@ export function useContentTypePermissions(props) {
|
|
|
21
22
|
]);
|
|
22
23
|
const [creatableContentTypes, setCreatableContentTypes] = useState(availableContentTypes);
|
|
23
24
|
const [readableContentTypes, setReadableContentTypes] = useState(availableContentTypes);
|
|
24
|
-
const { canPerformActionOnEntryOfType
|
|
25
|
+
const { canPerformActionOnEntryOfType } = useAccessApi(props.sdk.access);
|
|
25
26
|
useEffect(()=>{
|
|
26
27
|
function getContentTypes(action) {
|
|
27
28
|
return filter(availableContentTypes, (ct)=>canPerformActionOnEntryOfType(action, ct.sys.id));
|
|
@@ -33,6 +34,7 @@ export function useContentTypePermissions(props) {
|
|
|
33
34
|
setReadableContentTypes(readable);
|
|
34
35
|
}
|
|
35
36
|
void checkContentTypeAccess();
|
|
37
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- TODO: Evaluate the dependencies
|
|
36
38
|
}, [
|
|
37
39
|
availableContentTypes
|
|
38
40
|
]);
|
|
@@ -3,17 +3,17 @@ import { fromFieldValidations } from '../utils/fromFieldValidations';
|
|
|
3
3
|
import { useAccessApi } from './useAccessApi';
|
|
4
4
|
import { useContentTypePermissions } from './useContentTypePermissions';
|
|
5
5
|
export function useEditorPermissions(props) {
|
|
6
|
-
const { sdk
|
|
6
|
+
const { sdk, entityType, parameters } = props;
|
|
7
7
|
const validations = useMemo(()=>fromFieldValidations(props.sdk.field), [
|
|
8
8
|
props.sdk.field
|
|
9
9
|
]);
|
|
10
10
|
const [canCreateEntity, setCanCreateEntity] = useState(true);
|
|
11
11
|
const [canLinkEntity, setCanLinkEntity] = useState(true);
|
|
12
|
-
const { creatableContentTypes
|
|
12
|
+
const { creatableContentTypes, readableContentTypes, availableContentTypes } = useContentTypePermissions({
|
|
13
13
|
...props,
|
|
14
14
|
validations
|
|
15
15
|
});
|
|
16
|
-
const { canPerformAction
|
|
16
|
+
const { canPerformAction } = useAccessApi(sdk.access);
|
|
17
17
|
useEffect(()=>{
|
|
18
18
|
if (parameters.instance.showCreateEntityAction === false) {
|
|
19
19
|
setCanCreateEntity(false);
|
|
@@ -21,15 +21,20 @@ export function useEditorPermissions(props) {
|
|
|
21
21
|
}
|
|
22
22
|
async function checkCreateAccess() {
|
|
23
23
|
if (entityType === 'Asset') {
|
|
24
|
+
// Hardcoded `true` value following https://contentful.atlassian.net/browse/DANTE-486
|
|
25
|
+
// TODO: refine permissions check in order to account for tags in rules
|
|
24
26
|
const canCreate = await canPerformAction('create', 'Asset') || true;
|
|
25
27
|
setCanCreateEntity(canCreate);
|
|
26
28
|
}
|
|
27
29
|
if (entityType === 'Entry') {
|
|
30
|
+
// Hardcoded `true` value following https://contentful.atlassian.net/browse/DANTE-486
|
|
31
|
+
// TODO: refine permissions check in order to account for tags in rules
|
|
28
32
|
const canCreate = creatableContentTypes.length > 0 || true;
|
|
29
33
|
setCanCreateEntity(canCreate);
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
36
|
void checkCreateAccess();
|
|
37
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- TODO: Evaluate the dependencies
|
|
33
38
|
}, [
|
|
34
39
|
entityType,
|
|
35
40
|
parameters.instance,
|
|
@@ -42,15 +47,22 @@ export function useEditorPermissions(props) {
|
|
|
42
47
|
}
|
|
43
48
|
async function checkLinkAccess() {
|
|
44
49
|
if (entityType === 'Asset') {
|
|
50
|
+
// Hardcoded `true` value following https://contentful.atlassian.net/browse/DANTE-486
|
|
51
|
+
// TODO: refine permissions check in order to account for tags in rules
|
|
45
52
|
const canRead = await canPerformAction('read', 'Asset') || true;
|
|
46
53
|
setCanLinkEntity(canRead);
|
|
47
54
|
}
|
|
48
55
|
if (entityType === 'Entry') {
|
|
56
|
+
// Hardcoded `true` value following https://contentful.atlassian.net/browse/DANTE-486
|
|
57
|
+
// TODO: refine permissions check in order to account for tags in rules
|
|
58
|
+
// TODO: always show every content type (it's just a filter) to avoid people not seeing
|
|
59
|
+
// their (partly limited) content types
|
|
49
60
|
const canRead = true;
|
|
50
61
|
setCanLinkEntity(canRead);
|
|
51
62
|
}
|
|
52
63
|
}
|
|
53
64
|
void checkLinkAccess();
|
|
65
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- TODO: Evaluate the dependencies
|
|
54
66
|
}, [
|
|
55
67
|
entityType,
|
|
56
68
|
parameters.instance,
|
|
@@ -14,7 +14,7 @@ describe('useEditorPermissions', ()=>{
|
|
|
14
14
|
id
|
|
15
15
|
}
|
|
16
16
|
});
|
|
17
|
-
const renderEditorPermissions = async ({ entityType
|
|
17
|
+
const renderEditorPermissions = async ({ entityType, params = {}, allContentTypes = [], customizeMock, customizeSdk })=>{
|
|
18
18
|
const sdk = makeFieldAppSDK(customizeMock);
|
|
19
19
|
customizeSdk?.(sdk);
|
|
20
20
|
const renderResult = renderHook(()=>useEditorPermissions({
|
|
@@ -33,7 +33,7 @@ describe('useEditorPermissions', ()=>{
|
|
|
33
33
|
};
|
|
34
34
|
describe(`behaviour on Asset`, ()=>{
|
|
35
35
|
it(`wont check access when turned off via instance params`, async ()=>{
|
|
36
|
-
const { result
|
|
36
|
+
const { result, sdk } = await renderEditorPermissions({
|
|
37
37
|
entityType: 'Asset',
|
|
38
38
|
params: {
|
|
39
39
|
showCreateEntityAction: false,
|
|
@@ -45,20 +45,20 @@ describe('useEditorPermissions', ()=>{
|
|
|
45
45
|
expect(sdk.access.can).not.toHaveBeenCalledWith();
|
|
46
46
|
});
|
|
47
47
|
it(`checks basic access`, async ()=>{
|
|
48
|
-
const { sdk
|
|
48
|
+
const { sdk } = await renderEditorPermissions({
|
|
49
49
|
entityType: 'Asset'
|
|
50
50
|
});
|
|
51
51
|
expect(sdk.access.can).toHaveBeenCalledWith('create', 'Asset');
|
|
52
52
|
expect(sdk.access.can).toHaveBeenCalledWith('read', 'Asset');
|
|
53
53
|
});
|
|
54
54
|
it(`defaults link asset action visibility to true`, async ()=>{
|
|
55
|
-
const { result
|
|
55
|
+
const { result } = await renderEditorPermissions({
|
|
56
56
|
entityType: 'Asset'
|
|
57
57
|
});
|
|
58
58
|
expect(result.current.canLinkEntity).toBeTruthy();
|
|
59
59
|
});
|
|
60
60
|
it(`returns empty contentTypes`, async ()=>{
|
|
61
|
-
const { result
|
|
61
|
+
const { result } = await renderEditorPermissions({
|
|
62
62
|
entityType: 'Asset',
|
|
63
63
|
allContentTypes: [
|
|
64
64
|
makeContentType('one')
|
|
@@ -78,7 +78,7 @@ describe('useEditorPermissions', ()=>{
|
|
|
78
78
|
});
|
|
79
79
|
};
|
|
80
80
|
it(`wont check access when turned off via instance params`, async ()=>{
|
|
81
|
-
const { result
|
|
81
|
+
const { result, sdk } = await renderEditorPermissions({
|
|
82
82
|
entityType: 'Entry',
|
|
83
83
|
params: {
|
|
84
84
|
showCreateEntityAction: false,
|
|
@@ -94,7 +94,7 @@ describe('useEditorPermissions', ()=>{
|
|
|
94
94
|
makeContentType('one'),
|
|
95
95
|
makeContentType('two')
|
|
96
96
|
];
|
|
97
|
-
const { result
|
|
97
|
+
const { result } = await renderEditorPermissions({
|
|
98
98
|
entityType: 'Entry',
|
|
99
99
|
allContentTypes,
|
|
100
100
|
customizeSdk: (sdk)=>{
|
|
@@ -108,7 +108,7 @@ describe('useEditorPermissions', ()=>{
|
|
|
108
108
|
makeContentType('one'),
|
|
109
109
|
makeContentType('two')
|
|
110
110
|
];
|
|
111
|
-
const { result
|
|
111
|
+
const { result } = await renderEditorPermissions({
|
|
112
112
|
entityType: 'Entry',
|
|
113
113
|
allContentTypes,
|
|
114
114
|
customizeSdk: (sdk)=>{
|
|
@@ -122,7 +122,7 @@ describe('useEditorPermissions', ()=>{
|
|
|
122
122
|
makeContentType('one'),
|
|
123
123
|
makeContentType('two')
|
|
124
124
|
];
|
|
125
|
-
const { result
|
|
125
|
+
const { result } = await renderEditorPermissions({
|
|
126
126
|
entityType: 'Entry',
|
|
127
127
|
allContentTypes,
|
|
128
128
|
customizeSdk: (sdk)=>{
|
|
@@ -131,12 +131,13 @@ describe('useEditorPermissions', ()=>{
|
|
|
131
131
|
});
|
|
132
132
|
expect(result.current.canLinkEntity).toBe(true);
|
|
133
133
|
});
|
|
134
|
+
// eslint-disable-next-line -- TODO: describe this disable jest/no-test-prefixes
|
|
134
135
|
it.skip(`denies creation when no content-type can be read`, async ()=>{
|
|
135
136
|
const allContentTypes = [
|
|
136
137
|
makeContentType('one'),
|
|
137
138
|
makeContentType('two')
|
|
138
139
|
];
|
|
139
|
-
const { result
|
|
140
|
+
const { result } = await renderEditorPermissions({
|
|
140
141
|
entityType: 'Entry',
|
|
141
142
|
allContentTypes,
|
|
142
143
|
customizeSdk: (sdk)=>{
|
|
@@ -150,7 +151,7 @@ describe('useEditorPermissions', ()=>{
|
|
|
150
151
|
makeContentType('one'),
|
|
151
152
|
makeContentType('two')
|
|
152
153
|
];
|
|
153
|
-
const { result
|
|
154
|
+
const { result } = await renderEditorPermissions({
|
|
154
155
|
entityType: 'Entry',
|
|
155
156
|
allContentTypes,
|
|
156
157
|
customizeMock: (field)=>{
|
|
@@ -176,7 +177,7 @@ describe('useEditorPermissions', ()=>{
|
|
|
176
177
|
makeContentType('one'),
|
|
177
178
|
makeContentType('two')
|
|
178
179
|
];
|
|
179
|
-
const { result
|
|
180
|
+
const { result } = await renderEditorPermissions({
|
|
180
181
|
entityType: 'Entry',
|
|
181
182
|
allContentTypes,
|
|
182
183
|
customizeMock: (field)=>{
|
|
@@ -4,7 +4,7 @@ const dimensions = {
|
|
|
4
4
|
height: 70
|
|
5
5
|
};
|
|
6
6
|
export function AssetThumbnail(props) {
|
|
7
|
-
return React.createElement("img", {
|
|
7
|
+
return /*#__PURE__*/ React.createElement("img", {
|
|
8
8
|
alt: props.file.fileName,
|
|
9
9
|
src: `${props.file.url}?w=${dimensions.width}&h=${dimensions.height}&fit=thumb`,
|
|
10
10
|
height: dimensions.height,
|