@strapi/content-manager 5.48.0 → 5.48.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/features/DocumentRBAC.js +9 -1
- package/dist/admin/features/DocumentRBAC.js.map +1 -1
- package/dist/admin/features/DocumentRBAC.mjs +9 -1
- package/dist/admin/features/DocumentRBAC.mjs.map +1 -1
- package/dist/admin/hooks/useContentTypeSchema.js +37 -17
- package/dist/admin/hooks/useContentTypeSchema.js.map +1 -1
- package/dist/admin/hooks/useContentTypeSchema.mjs +37 -17
- package/dist/admin/hooks/useContentTypeSchema.mjs.map +1 -1
- package/dist/admin/hooks/useDocumentLayout.js +43 -4
- package/dist/admin/hooks/useDocumentLayout.js.map +1 -1
- package/dist/admin/hooks/useDocumentLayout.mjs +43 -4
- package/dist/admin/hooks/useDocumentLayout.mjs.map +1 -1
- package/dist/admin/pages/ComponentConfigurationPage.js +6 -3
- package/dist/admin/pages/ComponentConfigurationPage.js.map +1 -1
- package/dist/admin/pages/ComponentConfigurationPage.mjs +6 -3
- package/dist/admin/pages/ComponentConfigurationPage.mjs.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksContent.js +5 -2
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksContent.js.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksContent.mjs +5 -2
- package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksContent.mjs.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.js +11 -2
- package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.js.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.mjs +11 -2
- package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.mjs.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.js +9 -4
- package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.js.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.mjs +9 -4
- package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.mjs.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/Wysiwyg/PreviewWysiwyg.js +2 -26
- package/dist/admin/pages/EditView/components/FormInputs/Wysiwyg/PreviewWysiwyg.js.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/Wysiwyg/PreviewWysiwyg.mjs +2 -26
- package/dist/admin/pages/EditView/components/FormInputs/Wysiwyg/PreviewWysiwyg.mjs.map +1 -1
- package/dist/admin/pages/EditView/components/FormInputs/Wysiwyg/utils/sanitizer.js +72 -0
- package/dist/admin/pages/EditView/components/FormInputs/Wysiwyg/utils/sanitizer.js.map +1 -0
- package/dist/admin/pages/EditView/components/FormInputs/Wysiwyg/utils/sanitizer.mjs +70 -0
- package/dist/admin/pages/EditView/components/FormInputs/Wysiwyg/utils/sanitizer.mjs.map +1 -0
- package/dist/admin/pages/ListConfiguration/ListConfigurationPage.js +4 -8
- package/dist/admin/pages/ListConfiguration/ListConfigurationPage.js.map +1 -1
- package/dist/admin/pages/ListConfiguration/ListConfigurationPage.mjs +5 -9
- package/dist/admin/pages/ListConfiguration/ListConfigurationPage.mjs.map +1 -1
- package/dist/admin/pages/ListConfiguration/components/SortDisplayedFields.js +6 -10
- package/dist/admin/pages/ListConfiguration/components/SortDisplayedFields.js.map +1 -1
- package/dist/admin/pages/ListConfiguration/components/SortDisplayedFields.mjs +6 -10
- package/dist/admin/pages/ListConfiguration/components/SortDisplayedFields.mjs.map +1 -1
- package/dist/admin/pages/ListView/components/Filters.js +7 -9
- package/dist/admin/pages/ListView/components/Filters.js.map +1 -1
- package/dist/admin/pages/ListView/components/Filters.mjs +7 -9
- package/dist/admin/pages/ListView/components/Filters.mjs.map +1 -1
- package/dist/admin/pages/ListView/components/TableCells/Media.js +5 -4
- package/dist/admin/pages/ListView/components/TableCells/Media.js.map +1 -1
- package/dist/admin/pages/ListView/components/TableCells/Media.mjs +5 -4
- package/dist/admin/pages/ListView/components/TableCells/Media.mjs.map +1 -1
- package/dist/admin/pages/formatComponentConfigurationEditLayout.js +15 -9
- package/dist/admin/pages/formatComponentConfigurationEditLayout.js.map +1 -1
- package/dist/admin/pages/formatComponentConfigurationEditLayout.mjs +15 -9
- package/dist/admin/pages/formatComponentConfigurationEditLayout.mjs.map +1 -1
- package/dist/admin/services/components.js +3 -2
- package/dist/admin/services/components.js.map +1 -1
- package/dist/admin/services/components.mjs +3 -2
- package/dist/admin/services/components.mjs.map +1 -1
- package/dist/admin/services/contentTypes.js +4 -3
- package/dist/admin/services/contentTypes.js.map +1 -1
- package/dist/admin/services/contentTypes.mjs +4 -3
- package/dist/admin/services/contentTypes.mjs.map +1 -1
- package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.d.ts +1 -1
- package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/utils/sanitizer.d.ts +2 -0
- package/dist/admin/src/pages/ListConfiguration/components/SortDisplayedFields.d.ts +2 -2
- package/dist/admin/src/pages/ListView/components/TableCells/Media.d.ts +2 -2
- package/dist/admin/src/pages/formatComponentConfigurationEditLayout.d.ts +3 -1
- package/dist/admin/src/utils/layouts/normalizeContentManagerLayout.d.ts +24 -0
- package/dist/admin/translations/en.json.js +1 -0
- package/dist/admin/translations/en.json.js.map +1 -1
- package/dist/admin/translations/en.json.mjs +1 -0
- package/dist/admin/translations/en.json.mjs.map +1 -1
- package/dist/admin/utils/attributes.js +17 -2
- package/dist/admin/utils/attributes.js.map +1 -1
- package/dist/admin/utils/attributes.mjs +17 -2
- package/dist/admin/utils/attributes.mjs.map +1 -1
- package/dist/admin/utils/layouts/normalizeContentManagerLayout.js +329 -0
- package/dist/admin/utils/layouts/normalizeContentManagerLayout.js.map +1 -0
- package/dist/admin/utils/layouts/normalizeContentManagerLayout.mjs +321 -0
- package/dist/admin/utils/layouts/normalizeContentManagerLayout.mjs.map +1 -0
- package/dist/server/controllers/collection-types.js +7 -2
- package/dist/server/controllers/collection-types.js.map +1 -1
- package/dist/server/controllers/collection-types.mjs +7 -2
- package/dist/server/controllers/collection-types.mjs.map +1 -1
- package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
- package/package.json +7 -7
|
@@ -58,6 +58,7 @@ const [DocumentRBACProvider, useDocumentRBAC] = strapiAdmin.createContext('Docum
|
|
|
58
58
|
return {
|
|
59
59
|
...acc,
|
|
60
60
|
[action]: [
|
|
61
|
+
...acc[action] ?? [],
|
|
61
62
|
permission
|
|
62
63
|
]
|
|
63
64
|
};
|
|
@@ -110,7 +111,14 @@ const [DocumentRBACProvider, useDocumentRBAC] = strapiAdmin.createContext('Docum
|
|
|
110
111
|
};
|
|
111
112
|
/**
|
|
112
113
|
* @internal it's really small, but it's used three times in a row and DRY for something this straight forward.
|
|
113
|
-
*/ const extractAndDedupeFields = (permissions = [])=>
|
|
114
|
+
*/ const extractAndDedupeFields = (permissions = [])=>{
|
|
115
|
+
const allFields = permissions.flatMap((permission)=>permission.properties?.fields);
|
|
116
|
+
// An undefined entry means this permission grants access to all fields
|
|
117
|
+
// (no field-level restriction). Returning [] signals "no restriction" to callers.
|
|
118
|
+
if (allFields.some((field)=>field === undefined)) return [];
|
|
119
|
+
// Deduplicate fields
|
|
120
|
+
return Array.from(new Set(allFields));
|
|
121
|
+
};
|
|
114
122
|
/**
|
|
115
123
|
* @internal removes numerical strings from arrays.
|
|
116
124
|
* @example
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocumentRBAC.js","sources":["../../../admin/src/features/DocumentRBAC.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n useRBAC,\n useAuth,\n type Permission,\n createContext,\n Page,\n useQueryParams,\n} from '@strapi/admin/strapi-admin';\nimport { useParams } from 'react-router-dom';\n\nimport type { Schema } from '@strapi/types';\n\n/**\n * The boolean values indicate the global actions a user can perform on the document.\n * The `string[]` values tell us specifically which fields the actions can be performed on,\n * for example, if the `canReadFields` array is empty, than no fields can be read by the user.\n * This can happen even if the user can read the document.\n */\ninterface DocumentRBACContextValue {\n canCreate?: boolean;\n canCreateFields: string[];\n canDelete?: boolean;\n canPublish?: boolean;\n canRead?: boolean;\n canReadFields: string[];\n canUpdate?: boolean;\n canUpdateFields: string[];\n canUserAction: (\n fieldName: string,\n fieldsUserCanAction: string[],\n fieldType: Schema.Attribute.Kind\n ) => boolean;\n isLoading: boolean;\n}\n\nconst [DocumentRBACProvider, useDocumentRBAC] = createContext<DocumentRBACContextValue>(\n 'DocumentRBAC',\n {\n canCreate: false,\n canCreateFields: [],\n canDelete: false,\n canPublish: false,\n canRead: false,\n canReadFields: [],\n canUpdate: false,\n canUpdateFields: [],\n canUserAction: () => false,\n isLoading: false,\n }\n);\n\ninterface DocumentRBACProps {\n children: React.ReactNode;\n permissions: Permission[] | null;\n model?: string;\n}\n\n/**\n * @internal This component is not meant to be used outside of the Content Manager plugin.\n * It depends on knowing the slug/model of the content-type using the params of the URL or the model if it is passed as arg.\n * If you do use the hook outside of the context, we default to `false` for all actions.\n *\n * It then creates an list of `can{Action}` that are passed to the context for consumption\n * within the app to enforce RBAC.\n */\nconst DocumentRBAC = ({ children, permissions, model }: DocumentRBACProps) => {\n const { slug } = useParams<{ slug: string }>();\n\n if (!slug && !model) {\n throw new Error('Cannot find the slug param in the URL or the model prop is not provided.');\n }\n\n const contentTypeUid = model ?? slug;\n\n const [{ rawQuery }] = useQueryParams<{ plugins?: { i18n?: { locale?: string } } }>();\n\n const userPermissions = useAuth('DocumentRBAC', (state) => state.permissions);\n\n const contentTypePermissions = React.useMemo(() => {\n const contentTypePermissions = userPermissions.filter(\n (permission) => permission.subject === contentTypeUid\n );\n return contentTypePermissions.reduce<Record<string, Permission[]>>((acc, permission) => {\n const [action] = permission.action.split('.').slice(-1);\n return { ...acc, [action]: [permission] };\n }, {});\n }, [contentTypeUid, userPermissions]);\n\n const { isLoading, allowedActions } = useRBAC(\n contentTypePermissions,\n permissions ?? undefined,\n // TODO: useRBAC context should be typed and built differently\n // We are passing raw query as context to the hook so that it can\n // rely on the locale provided from DocumentRBAC for its permission calculations.\n rawQuery\n );\n\n const canCreateFields =\n !isLoading && allowedActions.canCreate\n ? extractAndDedupeFields(contentTypePermissions.create)\n : [];\n\n const canReadFields =\n !isLoading && allowedActions.canRead ? extractAndDedupeFields(contentTypePermissions.read) : [];\n\n const canUpdateFields =\n !isLoading && allowedActions.canUpdate\n ? extractAndDedupeFields(contentTypePermissions.update)\n : [];\n\n /**\n * @description Checks if the user can perform an action on a field based on the field names\n * provided as the second argument.\n */\n const canUserAction: DocumentRBACContextValue['canUserAction'] = React.useCallback(\n (fieldName, fieldsUserCanAction, fieldType) => {\n const name = removeNumericalStrings(fieldName.split('.'));\n\n const componentFieldNames = fieldsUserCanAction\n // filter out fields that aren't components (components are dot separated)\n .filter((field) => field.split('.').length > 1);\n\n if (fieldType === 'component') {\n // check if the field name is within any of those arrays\n return componentFieldNames.some((field) => {\n return field.includes(name.join('.'));\n });\n }\n\n /**\n * The field is within a component.\n */\n if (name.length > 1) {\n return componentFieldNames.includes(name.join('.'));\n }\n\n /**\n * just a regular field\n */\n return fieldsUserCanAction.includes(fieldName);\n },\n []\n );\n\n if (isLoading) {\n return <Page.Loading />;\n }\n\n return (\n <DocumentRBACProvider\n isLoading={isLoading}\n canCreateFields={canCreateFields}\n canReadFields={canReadFields}\n canUpdateFields={canUpdateFields}\n canUserAction={canUserAction}\n {...allowedActions}\n >\n {children}\n </DocumentRBACProvider>\n );\n};\n\n/**\n * @internal it's really small, but it's used three times in a row and DRY for something this straight forward.\n */\nconst extractAndDedupeFields = (permissions: Permission[] = []) =>\n permissions\n .flatMap((permission) => permission.properties?.fields)\n .filter(\n (field, index, arr): field is string =>\n arr.indexOf(field) === index && typeof field === 'string'\n );\n\n/**\n * @internal removes numerical strings from arrays.\n * @example\n * ```ts\n * const name = 'a.0.b';\n * const res = removeNumericalStrings(name.split('.'));\n * console.log(res); // ['a', 'b']\n * ```\n */\nconst removeNumericalStrings = (arr: string[]) => arr.filter((item) => isNaN(Number(item)));\n\nexport { DocumentRBAC, useDocumentRBAC, DocumentRBACContextValue, DocumentRBACProps };\n"],"names":["DocumentRBACProvider","useDocumentRBAC","createContext","canCreate","canCreateFields","canDelete","canPublish","canRead","canReadFields","canUpdate","canUpdateFields","canUserAction","isLoading","DocumentRBAC","children","permissions","model","slug","useParams","Error","contentTypeUid","rawQuery","useQueryParams","userPermissions","useAuth","state","contentTypePermissions","React","useMemo","filter","permission","subject","reduce","acc","action","split","slice","allowedActions","useRBAC","undefined","extractAndDedupeFields","create","read","update","useCallback","fieldName","fieldsUserCanAction","fieldType","name","removeNumericalStrings","componentFieldNames","field","length","some","includes","join","_jsx","Page","Loading","flatMap","properties","fields","index","arr","indexOf","item","isNaN","Number"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DocumentRBAC.js","sources":["../../../admin/src/features/DocumentRBAC.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n useRBAC,\n useAuth,\n type Permission,\n createContext,\n Page,\n useQueryParams,\n} from '@strapi/admin/strapi-admin';\nimport { useParams } from 'react-router-dom';\n\nimport type { Schema } from '@strapi/types';\n\n/**\n * The boolean values indicate the global actions a user can perform on the document.\n * The `string[]` values tell us specifically which fields the actions can be performed on,\n * for example, if the `canReadFields` array is empty, than no fields can be read by the user.\n * This can happen even if the user can read the document.\n */\ninterface DocumentRBACContextValue {\n canCreate?: boolean;\n canCreateFields: string[];\n canDelete?: boolean;\n canPublish?: boolean;\n canRead?: boolean;\n canReadFields: string[];\n canUpdate?: boolean;\n canUpdateFields: string[];\n canUserAction: (\n fieldName: string,\n fieldsUserCanAction: string[],\n fieldType: Schema.Attribute.Kind\n ) => boolean;\n isLoading: boolean;\n}\n\nconst [DocumentRBACProvider, useDocumentRBAC] = createContext<DocumentRBACContextValue>(\n 'DocumentRBAC',\n {\n canCreate: false,\n canCreateFields: [],\n canDelete: false,\n canPublish: false,\n canRead: false,\n canReadFields: [],\n canUpdate: false,\n canUpdateFields: [],\n canUserAction: () => false,\n isLoading: false,\n }\n);\n\ninterface DocumentRBACProps {\n children: React.ReactNode;\n permissions: Permission[] | null;\n model?: string;\n}\n\n/**\n * @internal This component is not meant to be used outside of the Content Manager plugin.\n * It depends on knowing the slug/model of the content-type using the params of the URL or the model if it is passed as arg.\n * If you do use the hook outside of the context, we default to `false` for all actions.\n *\n * It then creates an list of `can{Action}` that are passed to the context for consumption\n * within the app to enforce RBAC.\n */\nconst DocumentRBAC = ({ children, permissions, model }: DocumentRBACProps) => {\n const { slug } = useParams<{ slug: string }>();\n\n if (!slug && !model) {\n throw new Error('Cannot find the slug param in the URL or the model prop is not provided.');\n }\n\n const contentTypeUid = model ?? slug;\n\n const [{ rawQuery }] = useQueryParams<{ plugins?: { i18n?: { locale?: string } } }>();\n\n const userPermissions = useAuth('DocumentRBAC', (state) => state.permissions);\n\n const contentTypePermissions = React.useMemo(() => {\n const contentTypePermissions = userPermissions.filter(\n (permission) => permission.subject === contentTypeUid\n );\n return contentTypePermissions.reduce<Record<string, Permission[]>>((acc, permission) => {\n const [action] = permission.action.split('.').slice(-1);\n return { ...acc, [action]: [...(acc[action] ?? []), permission] };\n }, {});\n }, [contentTypeUid, userPermissions]);\n\n const { isLoading, allowedActions } = useRBAC(\n contentTypePermissions,\n permissions ?? undefined,\n // TODO: useRBAC context should be typed and built differently\n // We are passing raw query as context to the hook so that it can\n // rely on the locale provided from DocumentRBAC for its permission calculations.\n rawQuery\n );\n\n const canCreateFields =\n !isLoading && allowedActions.canCreate\n ? extractAndDedupeFields(contentTypePermissions.create)\n : [];\n\n const canReadFields =\n !isLoading && allowedActions.canRead ? extractAndDedupeFields(contentTypePermissions.read) : [];\n\n const canUpdateFields =\n !isLoading && allowedActions.canUpdate\n ? extractAndDedupeFields(contentTypePermissions.update)\n : [];\n\n /**\n * @description Checks if the user can perform an action on a field based on the field names\n * provided as the second argument.\n */\n const canUserAction: DocumentRBACContextValue['canUserAction'] = React.useCallback(\n (fieldName, fieldsUserCanAction, fieldType) => {\n const name = removeNumericalStrings(fieldName.split('.'));\n\n const componentFieldNames = fieldsUserCanAction\n // filter out fields that aren't components (components are dot separated)\n .filter((field) => field.split('.').length > 1);\n\n if (fieldType === 'component') {\n // check if the field name is within any of those arrays\n return componentFieldNames.some((field) => {\n return field.includes(name.join('.'));\n });\n }\n\n /**\n * The field is within a component.\n */\n if (name.length > 1) {\n return componentFieldNames.includes(name.join('.'));\n }\n\n /**\n * just a regular field\n */\n return fieldsUserCanAction.includes(fieldName);\n },\n []\n );\n\n if (isLoading) {\n return <Page.Loading />;\n }\n\n return (\n <DocumentRBACProvider\n isLoading={isLoading}\n canCreateFields={canCreateFields}\n canReadFields={canReadFields}\n canUpdateFields={canUpdateFields}\n canUserAction={canUserAction}\n {...allowedActions}\n >\n {children}\n </DocumentRBACProvider>\n );\n};\n\n/**\n * @internal it's really small, but it's used three times in a row and DRY for something this straight forward.\n */\nconst extractAndDedupeFields = (permissions: Permission[] = []) => {\n const allFields = permissions.flatMap((permission) => permission.properties?.fields);\n // An undefined entry means this permission grants access to all fields\n // (no field-level restriction). Returning [] signals \"no restriction\" to callers.\n if (allFields.some((field) => field === undefined)) return [];\n // Deduplicate fields\n return Array.from(new Set(allFields as string[]));\n};\n\n/**\n * @internal removes numerical strings from arrays.\n * @example\n * ```ts\n * const name = 'a.0.b';\n * const res = removeNumericalStrings(name.split('.'));\n * console.log(res); // ['a', 'b']\n * ```\n */\nconst removeNumericalStrings = (arr: string[]) => arr.filter((item) => isNaN(Number(item)));\n\nexport { DocumentRBAC, useDocumentRBAC, DocumentRBACContextValue, DocumentRBACProps };\n"],"names":["DocumentRBACProvider","useDocumentRBAC","createContext","canCreate","canCreateFields","canDelete","canPublish","canRead","canReadFields","canUpdate","canUpdateFields","canUserAction","isLoading","DocumentRBAC","children","permissions","model","slug","useParams","Error","contentTypeUid","rawQuery","useQueryParams","userPermissions","useAuth","state","contentTypePermissions","React","useMemo","filter","permission","subject","reduce","acc","action","split","slice","allowedActions","useRBAC","undefined","extractAndDedupeFields","create","read","update","useCallback","fieldName","fieldsUserCanAction","fieldType","name","removeNumericalStrings","componentFieldNames","field","length","some","includes","join","_jsx","Page","Loading","allFields","flatMap","properties","fields","Array","from","Set","arr","item","isNaN","Number"],"mappings":"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"}
|
|
@@ -37,6 +37,7 @@ const [DocumentRBACProvider, useDocumentRBAC] = createContext('DocumentRBAC', {
|
|
|
37
37
|
return {
|
|
38
38
|
...acc,
|
|
39
39
|
[action]: [
|
|
40
|
+
...acc[action] ?? [],
|
|
40
41
|
permission
|
|
41
42
|
]
|
|
42
43
|
};
|
|
@@ -89,7 +90,14 @@ const [DocumentRBACProvider, useDocumentRBAC] = createContext('DocumentRBAC', {
|
|
|
89
90
|
};
|
|
90
91
|
/**
|
|
91
92
|
* @internal it's really small, but it's used three times in a row and DRY for something this straight forward.
|
|
92
|
-
*/ const extractAndDedupeFields = (permissions = [])=>
|
|
93
|
+
*/ const extractAndDedupeFields = (permissions = [])=>{
|
|
94
|
+
const allFields = permissions.flatMap((permission)=>permission.properties?.fields);
|
|
95
|
+
// An undefined entry means this permission grants access to all fields
|
|
96
|
+
// (no field-level restriction). Returning [] signals "no restriction" to callers.
|
|
97
|
+
if (allFields.some((field)=>field === undefined)) return [];
|
|
98
|
+
// Deduplicate fields
|
|
99
|
+
return Array.from(new Set(allFields));
|
|
100
|
+
};
|
|
93
101
|
/**
|
|
94
102
|
* @internal removes numerical strings from arrays.
|
|
95
103
|
* @example
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocumentRBAC.mjs","sources":["../../../admin/src/features/DocumentRBAC.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n useRBAC,\n useAuth,\n type Permission,\n createContext,\n Page,\n useQueryParams,\n} from '@strapi/admin/strapi-admin';\nimport { useParams } from 'react-router-dom';\n\nimport type { Schema } from '@strapi/types';\n\n/**\n * The boolean values indicate the global actions a user can perform on the document.\n * The `string[]` values tell us specifically which fields the actions can be performed on,\n * for example, if the `canReadFields` array is empty, than no fields can be read by the user.\n * This can happen even if the user can read the document.\n */\ninterface DocumentRBACContextValue {\n canCreate?: boolean;\n canCreateFields: string[];\n canDelete?: boolean;\n canPublish?: boolean;\n canRead?: boolean;\n canReadFields: string[];\n canUpdate?: boolean;\n canUpdateFields: string[];\n canUserAction: (\n fieldName: string,\n fieldsUserCanAction: string[],\n fieldType: Schema.Attribute.Kind\n ) => boolean;\n isLoading: boolean;\n}\n\nconst [DocumentRBACProvider, useDocumentRBAC] = createContext<DocumentRBACContextValue>(\n 'DocumentRBAC',\n {\n canCreate: false,\n canCreateFields: [],\n canDelete: false,\n canPublish: false,\n canRead: false,\n canReadFields: [],\n canUpdate: false,\n canUpdateFields: [],\n canUserAction: () => false,\n isLoading: false,\n }\n);\n\ninterface DocumentRBACProps {\n children: React.ReactNode;\n permissions: Permission[] | null;\n model?: string;\n}\n\n/**\n * @internal This component is not meant to be used outside of the Content Manager plugin.\n * It depends on knowing the slug/model of the content-type using the params of the URL or the model if it is passed as arg.\n * If you do use the hook outside of the context, we default to `false` for all actions.\n *\n * It then creates an list of `can{Action}` that are passed to the context for consumption\n * within the app to enforce RBAC.\n */\nconst DocumentRBAC = ({ children, permissions, model }: DocumentRBACProps) => {\n const { slug } = useParams<{ slug: string }>();\n\n if (!slug && !model) {\n throw new Error('Cannot find the slug param in the URL or the model prop is not provided.');\n }\n\n const contentTypeUid = model ?? slug;\n\n const [{ rawQuery }] = useQueryParams<{ plugins?: { i18n?: { locale?: string } } }>();\n\n const userPermissions = useAuth('DocumentRBAC', (state) => state.permissions);\n\n const contentTypePermissions = React.useMemo(() => {\n const contentTypePermissions = userPermissions.filter(\n (permission) => permission.subject === contentTypeUid\n );\n return contentTypePermissions.reduce<Record<string, Permission[]>>((acc, permission) => {\n const [action] = permission.action.split('.').slice(-1);\n return { ...acc, [action]: [permission] };\n }, {});\n }, [contentTypeUid, userPermissions]);\n\n const { isLoading, allowedActions } = useRBAC(\n contentTypePermissions,\n permissions ?? undefined,\n // TODO: useRBAC context should be typed and built differently\n // We are passing raw query as context to the hook so that it can\n // rely on the locale provided from DocumentRBAC for its permission calculations.\n rawQuery\n );\n\n const canCreateFields =\n !isLoading && allowedActions.canCreate\n ? extractAndDedupeFields(contentTypePermissions.create)\n : [];\n\n const canReadFields =\n !isLoading && allowedActions.canRead ? extractAndDedupeFields(contentTypePermissions.read) : [];\n\n const canUpdateFields =\n !isLoading && allowedActions.canUpdate\n ? extractAndDedupeFields(contentTypePermissions.update)\n : [];\n\n /**\n * @description Checks if the user can perform an action on a field based on the field names\n * provided as the second argument.\n */\n const canUserAction: DocumentRBACContextValue['canUserAction'] = React.useCallback(\n (fieldName, fieldsUserCanAction, fieldType) => {\n const name = removeNumericalStrings(fieldName.split('.'));\n\n const componentFieldNames = fieldsUserCanAction\n // filter out fields that aren't components (components are dot separated)\n .filter((field) => field.split('.').length > 1);\n\n if (fieldType === 'component') {\n // check if the field name is within any of those arrays\n return componentFieldNames.some((field) => {\n return field.includes(name.join('.'));\n });\n }\n\n /**\n * The field is within a component.\n */\n if (name.length > 1) {\n return componentFieldNames.includes(name.join('.'));\n }\n\n /**\n * just a regular field\n */\n return fieldsUserCanAction.includes(fieldName);\n },\n []\n );\n\n if (isLoading) {\n return <Page.Loading />;\n }\n\n return (\n <DocumentRBACProvider\n isLoading={isLoading}\n canCreateFields={canCreateFields}\n canReadFields={canReadFields}\n canUpdateFields={canUpdateFields}\n canUserAction={canUserAction}\n {...allowedActions}\n >\n {children}\n </DocumentRBACProvider>\n );\n};\n\n/**\n * @internal it's really small, but it's used three times in a row and DRY for something this straight forward.\n */\nconst extractAndDedupeFields = (permissions: Permission[] = []) =>\n permissions\n .flatMap((permission) => permission.properties?.fields)\n .filter(\n (field, index, arr): field is string =>\n arr.indexOf(field) === index && typeof field === 'string'\n );\n\n/**\n * @internal removes numerical strings from arrays.\n * @example\n * ```ts\n * const name = 'a.0.b';\n * const res = removeNumericalStrings(name.split('.'));\n * console.log(res); // ['a', 'b']\n * ```\n */\nconst removeNumericalStrings = (arr: string[]) => arr.filter((item) => isNaN(Number(item)));\n\nexport { DocumentRBAC, useDocumentRBAC, DocumentRBACContextValue, DocumentRBACProps };\n"],"names":["DocumentRBACProvider","useDocumentRBAC","createContext","canCreate","canCreateFields","canDelete","canPublish","canRead","canReadFields","canUpdate","canUpdateFields","canUserAction","isLoading","DocumentRBAC","children","permissions","model","slug","useParams","Error","contentTypeUid","rawQuery","useQueryParams","userPermissions","useAuth","state","contentTypePermissions","React","useMemo","filter","permission","subject","reduce","acc","action","split","slice","allowedActions","useRBAC","undefined","extractAndDedupeFields","create","read","update","useCallback","fieldName","fieldsUserCanAction","fieldType","name","removeNumericalStrings","componentFieldNames","field","length","some","includes","join","_jsx","Page","Loading","flatMap","properties","fields","index","arr","indexOf","item","isNaN","Number"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DocumentRBAC.mjs","sources":["../../../admin/src/features/DocumentRBAC.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n useRBAC,\n useAuth,\n type Permission,\n createContext,\n Page,\n useQueryParams,\n} from '@strapi/admin/strapi-admin';\nimport { useParams } from 'react-router-dom';\n\nimport type { Schema } from '@strapi/types';\n\n/**\n * The boolean values indicate the global actions a user can perform on the document.\n * The `string[]` values tell us specifically which fields the actions can be performed on,\n * for example, if the `canReadFields` array is empty, than no fields can be read by the user.\n * This can happen even if the user can read the document.\n */\ninterface DocumentRBACContextValue {\n canCreate?: boolean;\n canCreateFields: string[];\n canDelete?: boolean;\n canPublish?: boolean;\n canRead?: boolean;\n canReadFields: string[];\n canUpdate?: boolean;\n canUpdateFields: string[];\n canUserAction: (\n fieldName: string,\n fieldsUserCanAction: string[],\n fieldType: Schema.Attribute.Kind\n ) => boolean;\n isLoading: boolean;\n}\n\nconst [DocumentRBACProvider, useDocumentRBAC] = createContext<DocumentRBACContextValue>(\n 'DocumentRBAC',\n {\n canCreate: false,\n canCreateFields: [],\n canDelete: false,\n canPublish: false,\n canRead: false,\n canReadFields: [],\n canUpdate: false,\n canUpdateFields: [],\n canUserAction: () => false,\n isLoading: false,\n }\n);\n\ninterface DocumentRBACProps {\n children: React.ReactNode;\n permissions: Permission[] | null;\n model?: string;\n}\n\n/**\n * @internal This component is not meant to be used outside of the Content Manager plugin.\n * It depends on knowing the slug/model of the content-type using the params of the URL or the model if it is passed as arg.\n * If you do use the hook outside of the context, we default to `false` for all actions.\n *\n * It then creates an list of `can{Action}` that are passed to the context for consumption\n * within the app to enforce RBAC.\n */\nconst DocumentRBAC = ({ children, permissions, model }: DocumentRBACProps) => {\n const { slug } = useParams<{ slug: string }>();\n\n if (!slug && !model) {\n throw new Error('Cannot find the slug param in the URL or the model prop is not provided.');\n }\n\n const contentTypeUid = model ?? slug;\n\n const [{ rawQuery }] = useQueryParams<{ plugins?: { i18n?: { locale?: string } } }>();\n\n const userPermissions = useAuth('DocumentRBAC', (state) => state.permissions);\n\n const contentTypePermissions = React.useMemo(() => {\n const contentTypePermissions = userPermissions.filter(\n (permission) => permission.subject === contentTypeUid\n );\n return contentTypePermissions.reduce<Record<string, Permission[]>>((acc, permission) => {\n const [action] = permission.action.split('.').slice(-1);\n return { ...acc, [action]: [...(acc[action] ?? []), permission] };\n }, {});\n }, [contentTypeUid, userPermissions]);\n\n const { isLoading, allowedActions } = useRBAC(\n contentTypePermissions,\n permissions ?? undefined,\n // TODO: useRBAC context should be typed and built differently\n // We are passing raw query as context to the hook so that it can\n // rely on the locale provided from DocumentRBAC for its permission calculations.\n rawQuery\n );\n\n const canCreateFields =\n !isLoading && allowedActions.canCreate\n ? extractAndDedupeFields(contentTypePermissions.create)\n : [];\n\n const canReadFields =\n !isLoading && allowedActions.canRead ? extractAndDedupeFields(contentTypePermissions.read) : [];\n\n const canUpdateFields =\n !isLoading && allowedActions.canUpdate\n ? extractAndDedupeFields(contentTypePermissions.update)\n : [];\n\n /**\n * @description Checks if the user can perform an action on a field based on the field names\n * provided as the second argument.\n */\n const canUserAction: DocumentRBACContextValue['canUserAction'] = React.useCallback(\n (fieldName, fieldsUserCanAction, fieldType) => {\n const name = removeNumericalStrings(fieldName.split('.'));\n\n const componentFieldNames = fieldsUserCanAction\n // filter out fields that aren't components (components are dot separated)\n .filter((field) => field.split('.').length > 1);\n\n if (fieldType === 'component') {\n // check if the field name is within any of those arrays\n return componentFieldNames.some((field) => {\n return field.includes(name.join('.'));\n });\n }\n\n /**\n * The field is within a component.\n */\n if (name.length > 1) {\n return componentFieldNames.includes(name.join('.'));\n }\n\n /**\n * just a regular field\n */\n return fieldsUserCanAction.includes(fieldName);\n },\n []\n );\n\n if (isLoading) {\n return <Page.Loading />;\n }\n\n return (\n <DocumentRBACProvider\n isLoading={isLoading}\n canCreateFields={canCreateFields}\n canReadFields={canReadFields}\n canUpdateFields={canUpdateFields}\n canUserAction={canUserAction}\n {...allowedActions}\n >\n {children}\n </DocumentRBACProvider>\n );\n};\n\n/**\n * @internal it's really small, but it's used three times in a row and DRY for something this straight forward.\n */\nconst extractAndDedupeFields = (permissions: Permission[] = []) => {\n const allFields = permissions.flatMap((permission) => permission.properties?.fields);\n // An undefined entry means this permission grants access to all fields\n // (no field-level restriction). Returning [] signals \"no restriction\" to callers.\n if (allFields.some((field) => field === undefined)) return [];\n // Deduplicate fields\n return Array.from(new Set(allFields as string[]));\n};\n\n/**\n * @internal removes numerical strings from arrays.\n * @example\n * ```ts\n * const name = 'a.0.b';\n * const res = removeNumericalStrings(name.split('.'));\n * console.log(res); // ['a', 'b']\n * ```\n */\nconst removeNumericalStrings = (arr: string[]) => arr.filter((item) => isNaN(Number(item)));\n\nexport { DocumentRBAC, useDocumentRBAC, DocumentRBACContextValue, DocumentRBACProps };\n"],"names":["DocumentRBACProvider","useDocumentRBAC","createContext","canCreate","canCreateFields","canDelete","canPublish","canRead","canReadFields","canUpdate","canUpdateFields","canUserAction","isLoading","DocumentRBAC","children","permissions","model","slug","useParams","Error","contentTypeUid","rawQuery","useQueryParams","userPermissions","useAuth","state","contentTypePermissions","React","useMemo","filter","permission","subject","reduce","acc","action","split","slice","allowedActions","useRBAC","undefined","extractAndDedupeFields","create","read","update","useCallback","fieldName","fieldsUserCanAction","fieldType","name","removeNumericalStrings","componentFieldNames","field","length","some","includes","join","_jsx","Page","Loading","allFields","flatMap","properties","fields","Array","from","Set","arr","item","isNaN","Number"],"mappings":"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"}
|
|
@@ -23,6 +23,41 @@ function _interopNamespaceDefault(e) {
|
|
|
23
23
|
|
|
24
24
|
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
|
|
25
25
|
|
|
26
|
+
const EMPTY_COMPONENTS = {};
|
|
27
|
+
// Module-level cache preserves schema derivation identities across hook instances;
|
|
28
|
+
// `useMemo` would only stabilize values inside a single component tree.
|
|
29
|
+
const schemaInfoCache = new WeakMap();
|
|
30
|
+
const getSchemaInfo = (data, model)=>{
|
|
31
|
+
if (!data) {
|
|
32
|
+
return {
|
|
33
|
+
components: undefined,
|
|
34
|
+
contentType: undefined,
|
|
35
|
+
contentTypes: []
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
let cachedByModel = schemaInfoCache.get(data);
|
|
39
|
+
if (!cachedByModel) {
|
|
40
|
+
cachedByModel = new Map();
|
|
41
|
+
schemaInfoCache.set(data, cachedByModel);
|
|
42
|
+
}
|
|
43
|
+
const cached = cachedByModel.get(model);
|
|
44
|
+
if (cached) {
|
|
45
|
+
return cached;
|
|
46
|
+
}
|
|
47
|
+
const contentType = data.contentTypes.find((ct)=>ct.uid === model);
|
|
48
|
+
const componentsByKey = data.components.reduce((acc, component)=>{
|
|
49
|
+
acc[component.uid] = component;
|
|
50
|
+
return acc;
|
|
51
|
+
}, {});
|
|
52
|
+
const components = extractContentTypeComponents(contentType?.attributes, componentsByKey);
|
|
53
|
+
const schemaInfo = {
|
|
54
|
+
components: Object.keys(components).length === 0 ? undefined : components,
|
|
55
|
+
contentType,
|
|
56
|
+
contentTypes: data.contentTypes
|
|
57
|
+
};
|
|
58
|
+
cachedByModel.set(model, schemaInfo);
|
|
59
|
+
return schemaInfo;
|
|
60
|
+
};
|
|
26
61
|
/**
|
|
27
62
|
* @internal
|
|
28
63
|
* @description Given a model UID, return the schema and the schemas
|
|
@@ -35,19 +70,7 @@ var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
|
|
|
35
70
|
const { toggleNotification } = strapiAdmin.useNotification();
|
|
36
71
|
const { _unstableFormatAPIError: formatAPIError } = strapiAdmin.useAPIErrorHandler();
|
|
37
72
|
const { data, error, isLoading, isFetching } = init.useGetInitialDataQuery(undefined);
|
|
38
|
-
const { components, contentType, contentTypes } = React__namespace.useMemo(()=>
|
|
39
|
-
const contentType = data?.contentTypes.find((ct)=>ct.uid === model);
|
|
40
|
-
const componentsByKey = data?.components.reduce((acc, component)=>{
|
|
41
|
-
acc[component.uid] = component;
|
|
42
|
-
return acc;
|
|
43
|
-
}, {});
|
|
44
|
-
const components = extractContentTypeComponents(contentType?.attributes, componentsByKey);
|
|
45
|
-
return {
|
|
46
|
-
components: Object.keys(components).length === 0 ? undefined : components,
|
|
47
|
-
contentType,
|
|
48
|
-
contentTypes: data?.contentTypes ?? []
|
|
49
|
-
};
|
|
50
|
-
}, [
|
|
73
|
+
const { components, contentType, contentTypes } = React__namespace.useMemo(()=>getSchemaInfo(data, model), [
|
|
51
74
|
model,
|
|
52
75
|
data
|
|
53
76
|
]);
|
|
@@ -64,10 +87,7 @@ var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
|
|
|
64
87
|
formatAPIError
|
|
65
88
|
]);
|
|
66
89
|
return {
|
|
67
|
-
|
|
68
|
-
components: React__namespace.useMemo(()=>components ?? {}, [
|
|
69
|
-
components
|
|
70
|
-
]),
|
|
90
|
+
components: components ?? EMPTY_COMPONENTS,
|
|
71
91
|
schema: contentType,
|
|
72
92
|
schemas: contentTypes,
|
|
73
93
|
isLoading: isLoading || isFetching
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useContentTypeSchema.js","sources":["../../../admin/src/hooks/useContentTypeSchema.ts"],"sourcesContent":["import * as React from 'react';\n\nimport { useNotification, useAPIErrorHandler } from '@strapi/admin/strapi-admin';\n\nimport { useGetInitialDataQuery } from '../services/init';\n\nimport type { Component } from '../../../shared/contracts/components';\nimport type { ContentType } from '../../../shared/contracts/content-types';\nimport type { Schema } from '@strapi/types';\n\n/* -------------------------------------------------------------------------------------------------\n * useContentTypeSchema\n * -----------------------------------------------------------------------------------------------*/\ntype ComponentsDictionary = Record<string, Component>;\n\
|
|
1
|
+
{"version":3,"file":"useContentTypeSchema.js","sources":["../../../admin/src/hooks/useContentTypeSchema.ts"],"sourcesContent":["import * as React from 'react';\n\nimport { useNotification, useAPIErrorHandler } from '@strapi/admin/strapi-admin';\n\nimport { useGetInitialDataQuery } from '../services/init';\n\nimport type { Component } from '../../../shared/contracts/components';\nimport type { ContentType } from '../../../shared/contracts/content-types';\nimport type { GetInitData } from '../../../shared/contracts/init';\nimport type { Schema } from '@strapi/types';\n\n/* -------------------------------------------------------------------------------------------------\n * useContentTypeSchema\n * -----------------------------------------------------------------------------------------------*/\ntype ComponentsDictionary = Record<string, Component>;\n\nconst EMPTY_COMPONENTS: ComponentsDictionary = {};\n\ntype InitialData = GetInitData.Response['data'];\n\n// Module-level cache preserves schema derivation identities across hook instances;\n// `useMemo` would only stabilize values inside a single component tree.\nconst schemaInfoCache = new WeakMap<\n InitialData,\n Map<\n string | undefined,\n {\n components?: ComponentsDictionary;\n contentType?: ContentType;\n contentTypes: ContentType[];\n }\n >\n>();\n\nconst getSchemaInfo = (data: InitialData | undefined, model?: string) => {\n if (!data) {\n return {\n components: undefined,\n contentType: undefined,\n contentTypes: [],\n };\n }\n\n let cachedByModel = schemaInfoCache.get(data);\n\n if (!cachedByModel) {\n cachedByModel = new Map();\n schemaInfoCache.set(data, cachedByModel);\n }\n\n const cached = cachedByModel.get(model);\n\n if (cached) {\n return cached;\n }\n\n const contentType = data.contentTypes.find((ct) => ct.uid === model);\n\n const componentsByKey = data.components.reduce<ComponentsDictionary>((acc, component) => {\n acc[component.uid] = component;\n\n return acc;\n }, {});\n\n const components = extractContentTypeComponents(contentType?.attributes, componentsByKey);\n\n const schemaInfo = {\n components: Object.keys(components).length === 0 ? undefined : components,\n contentType,\n contentTypes: data.contentTypes,\n };\n\n cachedByModel.set(model, schemaInfo);\n\n return schemaInfo;\n};\n\n/**\n * @internal\n * @description Given a model UID, return the schema and the schemas\n * of the associated components within said model's schema. A wrapper\n * implementation around the `useGetInitialDataQuery` with a unique\n * `selectFromResult` function to memoize the calculation.\n *\n * If no model is provided, the hook will return all the schemas.\n */\nconst useContentTypeSchema = (model?: string) => {\n const { toggleNotification } = useNotification();\n const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();\n\n const { data, error, isLoading, isFetching } = useGetInitialDataQuery(undefined);\n\n const { components, contentType, contentTypes } = React.useMemo(\n () => getSchemaInfo(data, model),\n [model, data]\n );\n\n React.useEffect(() => {\n if (error) {\n toggleNotification({\n type: 'danger',\n message: formatAPIError(error),\n });\n }\n }, [toggleNotification, error, formatAPIError]);\n\n return {\n components: components ?? EMPTY_COMPONENTS,\n schema: contentType,\n schemas: contentTypes,\n isLoading: isLoading || isFetching,\n };\n};\n\n/* -------------------------------------------------------------------------------------------------\n * extractContentTypeComponents\n * -----------------------------------------------------------------------------------------------*/\n/**\n * @internal\n * @description Extracts the components used in a content type's attributes recursively.\n */\nconst extractContentTypeComponents = (\n attributes: ContentType['attributes'] = {},\n allComponents: ComponentsDictionary = {}\n): ComponentsDictionary => {\n const getComponents = (attributes: Schema.Attribute.AnyAttribute[]) => {\n return attributes.reduce<string[]>((acc, attribute) => {\n /**\n * If the attribute is a component or dynamiczone, we need to recursively\n * extract the component UIDs from its attributes.\n */\n if (attribute.type === 'component') {\n const componentAttributes = Object.values(\n allComponents[attribute.component]?.attributes ?? {}\n );\n\n acc.push(attribute.component, ...getComponents(componentAttributes));\n } else if (attribute.type === 'dynamiczone') {\n acc.push(\n ...attribute.components,\n /**\n * Dynamic zones have an array of components, so we flatMap over them\n * performing the same search as above.\n */\n ...attribute.components.flatMap((componentUid) => {\n const componentAttributes = Object.values(\n allComponents[componentUid]?.attributes ?? {}\n );\n\n return getComponents(componentAttributes);\n })\n );\n }\n\n return acc;\n }, []);\n };\n\n const componentUids = getComponents(Object.values(attributes));\n\n const uniqueComponentUids = [...new Set(componentUids)];\n\n const componentsByKey = uniqueComponentUids.reduce<ComponentsDictionary>((acc, uid) => {\n const component = allComponents[uid];\n if (component) {\n acc[uid] = component;\n }\n\n return acc;\n }, {});\n\n return componentsByKey;\n};\n\nexport { useContentTypeSchema, extractContentTypeComponents };\nexport type { ComponentsDictionary };\n"],"names":["EMPTY_COMPONENTS","schemaInfoCache","WeakMap","getSchemaInfo","data","model","components","undefined","contentType","contentTypes","cachedByModel","get","Map","set","cached","find","ct","uid","componentsByKey","reduce","acc","component","extractContentTypeComponents","attributes","schemaInfo","Object","keys","length","useContentTypeSchema","toggleNotification","useNotification","_unstableFormatAPIError","formatAPIError","useAPIErrorHandler","error","isLoading","isFetching","useGetInitialDataQuery","React","useMemo","useEffect","type","message","schema","schemas","allComponents","getComponents","attribute","componentAttributes","values","push","flatMap","componentUid","componentUids","uniqueComponentUids","Set"],"mappings":"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"}
|
|
@@ -2,6 +2,41 @@ import * as React from 'react';
|
|
|
2
2
|
import { useNotification, useAPIErrorHandler } from '@strapi/admin/strapi-admin';
|
|
3
3
|
import { useGetInitialDataQuery } from '../services/init.mjs';
|
|
4
4
|
|
|
5
|
+
const EMPTY_COMPONENTS = {};
|
|
6
|
+
// Module-level cache preserves schema derivation identities across hook instances;
|
|
7
|
+
// `useMemo` would only stabilize values inside a single component tree.
|
|
8
|
+
const schemaInfoCache = new WeakMap();
|
|
9
|
+
const getSchemaInfo = (data, model)=>{
|
|
10
|
+
if (!data) {
|
|
11
|
+
return {
|
|
12
|
+
components: undefined,
|
|
13
|
+
contentType: undefined,
|
|
14
|
+
contentTypes: []
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
let cachedByModel = schemaInfoCache.get(data);
|
|
18
|
+
if (!cachedByModel) {
|
|
19
|
+
cachedByModel = new Map();
|
|
20
|
+
schemaInfoCache.set(data, cachedByModel);
|
|
21
|
+
}
|
|
22
|
+
const cached = cachedByModel.get(model);
|
|
23
|
+
if (cached) {
|
|
24
|
+
return cached;
|
|
25
|
+
}
|
|
26
|
+
const contentType = data.contentTypes.find((ct)=>ct.uid === model);
|
|
27
|
+
const componentsByKey = data.components.reduce((acc, component)=>{
|
|
28
|
+
acc[component.uid] = component;
|
|
29
|
+
return acc;
|
|
30
|
+
}, {});
|
|
31
|
+
const components = extractContentTypeComponents(contentType?.attributes, componentsByKey);
|
|
32
|
+
const schemaInfo = {
|
|
33
|
+
components: Object.keys(components).length === 0 ? undefined : components,
|
|
34
|
+
contentType,
|
|
35
|
+
contentTypes: data.contentTypes
|
|
36
|
+
};
|
|
37
|
+
cachedByModel.set(model, schemaInfo);
|
|
38
|
+
return schemaInfo;
|
|
39
|
+
};
|
|
5
40
|
/**
|
|
6
41
|
* @internal
|
|
7
42
|
* @description Given a model UID, return the schema and the schemas
|
|
@@ -14,19 +49,7 @@ import { useGetInitialDataQuery } from '../services/init.mjs';
|
|
|
14
49
|
const { toggleNotification } = useNotification();
|
|
15
50
|
const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
|
|
16
51
|
const { data, error, isLoading, isFetching } = useGetInitialDataQuery(undefined);
|
|
17
|
-
const { components, contentType, contentTypes } = React.useMemo(()=>
|
|
18
|
-
const contentType = data?.contentTypes.find((ct)=>ct.uid === model);
|
|
19
|
-
const componentsByKey = data?.components.reduce((acc, component)=>{
|
|
20
|
-
acc[component.uid] = component;
|
|
21
|
-
return acc;
|
|
22
|
-
}, {});
|
|
23
|
-
const components = extractContentTypeComponents(contentType?.attributes, componentsByKey);
|
|
24
|
-
return {
|
|
25
|
-
components: Object.keys(components).length === 0 ? undefined : components,
|
|
26
|
-
contentType,
|
|
27
|
-
contentTypes: data?.contentTypes ?? []
|
|
28
|
-
};
|
|
29
|
-
}, [
|
|
52
|
+
const { components, contentType, contentTypes } = React.useMemo(()=>getSchemaInfo(data, model), [
|
|
30
53
|
model,
|
|
31
54
|
data
|
|
32
55
|
]);
|
|
@@ -43,10 +66,7 @@ import { useGetInitialDataQuery } from '../services/init.mjs';
|
|
|
43
66
|
formatAPIError
|
|
44
67
|
]);
|
|
45
68
|
return {
|
|
46
|
-
|
|
47
|
-
components: React.useMemo(()=>components ?? {}, [
|
|
48
|
-
components
|
|
49
|
-
]),
|
|
69
|
+
components: components ?? EMPTY_COMPONENTS,
|
|
50
70
|
schema: contentType,
|
|
51
71
|
schemas: contentTypes,
|
|
52
72
|
isLoading: isLoading || isFetching
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useContentTypeSchema.mjs","sources":["../../../admin/src/hooks/useContentTypeSchema.ts"],"sourcesContent":["import * as React from 'react';\n\nimport { useNotification, useAPIErrorHandler } from '@strapi/admin/strapi-admin';\n\nimport { useGetInitialDataQuery } from '../services/init';\n\nimport type { Component } from '../../../shared/contracts/components';\nimport type { ContentType } from '../../../shared/contracts/content-types';\nimport type { Schema } from '@strapi/types';\n\n/* -------------------------------------------------------------------------------------------------\n * useContentTypeSchema\n * -----------------------------------------------------------------------------------------------*/\ntype ComponentsDictionary = Record<string, Component>;\n\
|
|
1
|
+
{"version":3,"file":"useContentTypeSchema.mjs","sources":["../../../admin/src/hooks/useContentTypeSchema.ts"],"sourcesContent":["import * as React from 'react';\n\nimport { useNotification, useAPIErrorHandler } from '@strapi/admin/strapi-admin';\n\nimport { useGetInitialDataQuery } from '../services/init';\n\nimport type { Component } from '../../../shared/contracts/components';\nimport type { ContentType } from '../../../shared/contracts/content-types';\nimport type { GetInitData } from '../../../shared/contracts/init';\nimport type { Schema } from '@strapi/types';\n\n/* -------------------------------------------------------------------------------------------------\n * useContentTypeSchema\n * -----------------------------------------------------------------------------------------------*/\ntype ComponentsDictionary = Record<string, Component>;\n\nconst EMPTY_COMPONENTS: ComponentsDictionary = {};\n\ntype InitialData = GetInitData.Response['data'];\n\n// Module-level cache preserves schema derivation identities across hook instances;\n// `useMemo` would only stabilize values inside a single component tree.\nconst schemaInfoCache = new WeakMap<\n InitialData,\n Map<\n string | undefined,\n {\n components?: ComponentsDictionary;\n contentType?: ContentType;\n contentTypes: ContentType[];\n }\n >\n>();\n\nconst getSchemaInfo = (data: InitialData | undefined, model?: string) => {\n if (!data) {\n return {\n components: undefined,\n contentType: undefined,\n contentTypes: [],\n };\n }\n\n let cachedByModel = schemaInfoCache.get(data);\n\n if (!cachedByModel) {\n cachedByModel = new Map();\n schemaInfoCache.set(data, cachedByModel);\n }\n\n const cached = cachedByModel.get(model);\n\n if (cached) {\n return cached;\n }\n\n const contentType = data.contentTypes.find((ct) => ct.uid === model);\n\n const componentsByKey = data.components.reduce<ComponentsDictionary>((acc, component) => {\n acc[component.uid] = component;\n\n return acc;\n }, {});\n\n const components = extractContentTypeComponents(contentType?.attributes, componentsByKey);\n\n const schemaInfo = {\n components: Object.keys(components).length === 0 ? undefined : components,\n contentType,\n contentTypes: data.contentTypes,\n };\n\n cachedByModel.set(model, schemaInfo);\n\n return schemaInfo;\n};\n\n/**\n * @internal\n * @description Given a model UID, return the schema and the schemas\n * of the associated components within said model's schema. A wrapper\n * implementation around the `useGetInitialDataQuery` with a unique\n * `selectFromResult` function to memoize the calculation.\n *\n * If no model is provided, the hook will return all the schemas.\n */\nconst useContentTypeSchema = (model?: string) => {\n const { toggleNotification } = useNotification();\n const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();\n\n const { data, error, isLoading, isFetching } = useGetInitialDataQuery(undefined);\n\n const { components, contentType, contentTypes } = React.useMemo(\n () => getSchemaInfo(data, model),\n [model, data]\n );\n\n React.useEffect(() => {\n if (error) {\n toggleNotification({\n type: 'danger',\n message: formatAPIError(error),\n });\n }\n }, [toggleNotification, error, formatAPIError]);\n\n return {\n components: components ?? EMPTY_COMPONENTS,\n schema: contentType,\n schemas: contentTypes,\n isLoading: isLoading || isFetching,\n };\n};\n\n/* -------------------------------------------------------------------------------------------------\n * extractContentTypeComponents\n * -----------------------------------------------------------------------------------------------*/\n/**\n * @internal\n * @description Extracts the components used in a content type's attributes recursively.\n */\nconst extractContentTypeComponents = (\n attributes: ContentType['attributes'] = {},\n allComponents: ComponentsDictionary = {}\n): ComponentsDictionary => {\n const getComponents = (attributes: Schema.Attribute.AnyAttribute[]) => {\n return attributes.reduce<string[]>((acc, attribute) => {\n /**\n * If the attribute is a component or dynamiczone, we need to recursively\n * extract the component UIDs from its attributes.\n */\n if (attribute.type === 'component') {\n const componentAttributes = Object.values(\n allComponents[attribute.component]?.attributes ?? {}\n );\n\n acc.push(attribute.component, ...getComponents(componentAttributes));\n } else if (attribute.type === 'dynamiczone') {\n acc.push(\n ...attribute.components,\n /**\n * Dynamic zones have an array of components, so we flatMap over them\n * performing the same search as above.\n */\n ...attribute.components.flatMap((componentUid) => {\n const componentAttributes = Object.values(\n allComponents[componentUid]?.attributes ?? {}\n );\n\n return getComponents(componentAttributes);\n })\n );\n }\n\n return acc;\n }, []);\n };\n\n const componentUids = getComponents(Object.values(attributes));\n\n const uniqueComponentUids = [...new Set(componentUids)];\n\n const componentsByKey = uniqueComponentUids.reduce<ComponentsDictionary>((acc, uid) => {\n const component = allComponents[uid];\n if (component) {\n acc[uid] = component;\n }\n\n return acc;\n }, {});\n\n return componentsByKey;\n};\n\nexport { useContentTypeSchema, extractContentTypeComponents };\nexport type { ComponentsDictionary };\n"],"names":["EMPTY_COMPONENTS","schemaInfoCache","WeakMap","getSchemaInfo","data","model","components","undefined","contentType","contentTypes","cachedByModel","get","Map","set","cached","find","ct","uid","componentsByKey","reduce","acc","component","extractContentTypeComponents","attributes","schemaInfo","Object","keys","length","useContentTypeSchema","toggleNotification","useNotification","_unstableFormatAPIError","formatAPIError","useAPIErrorHandler","error","isLoading","isFetching","useGetInitialDataQuery","React","useMemo","useEffect","type","message","schema","schemas","allComponents","getComponents","attribute","componentAttributes","values","push","flatMap","componentUid","componentUids","uniqueComponentUids","Set"],"mappings":"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"}
|
|
@@ -5,6 +5,7 @@ var strapiAdmin = require('@strapi/admin/strapi-admin');
|
|
|
5
5
|
var hooks = require('../constants/hooks.js');
|
|
6
6
|
var contentTypes = require('../services/contentTypes.js');
|
|
7
7
|
var attributes = require('../utils/attributes.js');
|
|
8
|
+
var normalizeContentManagerLayout = require('../utils/layouts/normalizeContentManagerLayout.js');
|
|
8
9
|
var useContentTypeSchema = require('./useContentTypeSchema.js');
|
|
9
10
|
var useDocument = require('./useDocument.js');
|
|
10
11
|
|
|
@@ -27,6 +28,19 @@ function _interopNamespaceDefault(e) {
|
|
|
27
28
|
|
|
28
29
|
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
|
|
29
30
|
|
|
31
|
+
const RESOLVED_LAYOUT_CACHE_LIMIT = 25;
|
|
32
|
+
// Module-level cache keeps resolved layout object identities stable across hook instances
|
|
33
|
+
// when RTK Query returns the same schema/configuration references.
|
|
34
|
+
const resolvedLayoutCache = [];
|
|
35
|
+
const getCachedResolvedLayouts = ({ components, data, model, schema, schemas })=>{
|
|
36
|
+
return resolvedLayoutCache.find((entry)=>entry.components === components && entry.data === data && entry.model === model && entry.schema === schema && entry.schemas === schemas)?.value;
|
|
37
|
+
};
|
|
38
|
+
const setCachedResolvedLayouts = (entry)=>{
|
|
39
|
+
resolvedLayoutCache.unshift(entry);
|
|
40
|
+
if (resolvedLayoutCache.length > RESOLVED_LAYOUT_CACHE_LIMIT) {
|
|
41
|
+
resolvedLayoutCache.pop();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
30
44
|
/* -------------------------------------------------------------------------------------------------
|
|
31
45
|
* useDocumentLayout
|
|
32
46
|
* -----------------------------------------------------------------------------------------------*/ const DEFAULT_SETTINGS = {
|
|
@@ -89,29 +103,54 @@ var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
|
|
|
89
103
|
if (!data || isLoading) {
|
|
90
104
|
return null;
|
|
91
105
|
}
|
|
92
|
-
const
|
|
106
|
+
const cachedLayouts = getCachedResolvedLayouts({
|
|
107
|
+
components,
|
|
108
|
+
data,
|
|
109
|
+
model,
|
|
110
|
+
schema,
|
|
111
|
+
schemas
|
|
112
|
+
});
|
|
113
|
+
if (cachedLayouts) {
|
|
114
|
+
return cachedLayouts;
|
|
115
|
+
}
|
|
116
|
+
const normalizedData = normalizeContentManagerLayout.normalizeContentManagerLayout(data, {
|
|
117
|
+
schemas,
|
|
118
|
+
schema,
|
|
119
|
+
components
|
|
120
|
+
});
|
|
121
|
+
const edit = formatEditLayout(normalizedData, {
|
|
93
122
|
schemas,
|
|
94
123
|
schema,
|
|
95
124
|
components
|
|
96
125
|
});
|
|
97
|
-
const list = formatListLayout(
|
|
126
|
+
const list = formatListLayout(normalizedData, {
|
|
98
127
|
schemas,
|
|
99
128
|
schema,
|
|
100
129
|
components
|
|
101
130
|
});
|
|
102
131
|
const listViewConversionContext = {
|
|
103
|
-
componentConfigurations:
|
|
132
|
+
componentConfigurations: normalizedData.components,
|
|
104
133
|
componentSchemas: components,
|
|
105
134
|
contentTypeSchemas: schemas
|
|
106
135
|
};
|
|
107
|
-
|
|
136
|
+
const layouts = {
|
|
108
137
|
edit,
|
|
109
138
|
list,
|
|
110
139
|
listViewConversionContext
|
|
111
140
|
};
|
|
141
|
+
setCachedResolvedLayouts({
|
|
142
|
+
components,
|
|
143
|
+
data,
|
|
144
|
+
model,
|
|
145
|
+
schema,
|
|
146
|
+
schemas,
|
|
147
|
+
value: layouts
|
|
148
|
+
});
|
|
149
|
+
return layouts;
|
|
112
150
|
}, [
|
|
113
151
|
data,
|
|
114
152
|
isLoading,
|
|
153
|
+
model,
|
|
115
154
|
schemas,
|
|
116
155
|
schema,
|
|
117
156
|
components
|