@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.
Files changed (88) hide show
  1. package/dist/admin/features/DocumentRBAC.js +9 -1
  2. package/dist/admin/features/DocumentRBAC.js.map +1 -1
  3. package/dist/admin/features/DocumentRBAC.mjs +9 -1
  4. package/dist/admin/features/DocumentRBAC.mjs.map +1 -1
  5. package/dist/admin/hooks/useContentTypeSchema.js +37 -17
  6. package/dist/admin/hooks/useContentTypeSchema.js.map +1 -1
  7. package/dist/admin/hooks/useContentTypeSchema.mjs +37 -17
  8. package/dist/admin/hooks/useContentTypeSchema.mjs.map +1 -1
  9. package/dist/admin/hooks/useDocumentLayout.js +43 -4
  10. package/dist/admin/hooks/useDocumentLayout.js.map +1 -1
  11. package/dist/admin/hooks/useDocumentLayout.mjs +43 -4
  12. package/dist/admin/hooks/useDocumentLayout.mjs.map +1 -1
  13. package/dist/admin/pages/ComponentConfigurationPage.js +6 -3
  14. package/dist/admin/pages/ComponentConfigurationPage.js.map +1 -1
  15. package/dist/admin/pages/ComponentConfigurationPage.mjs +6 -3
  16. package/dist/admin/pages/ComponentConfigurationPage.mjs.map +1 -1
  17. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksContent.js +5 -2
  18. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksContent.js.map +1 -1
  19. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksContent.mjs +5 -2
  20. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksContent.mjs.map +1 -1
  21. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.js +11 -2
  22. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.js.map +1 -1
  23. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.mjs +11 -2
  24. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.mjs.map +1 -1
  25. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.js +9 -4
  26. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.js.map +1 -1
  27. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.mjs +9 -4
  28. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.mjs.map +1 -1
  29. package/dist/admin/pages/EditView/components/FormInputs/Wysiwyg/PreviewWysiwyg.js +2 -26
  30. package/dist/admin/pages/EditView/components/FormInputs/Wysiwyg/PreviewWysiwyg.js.map +1 -1
  31. package/dist/admin/pages/EditView/components/FormInputs/Wysiwyg/PreviewWysiwyg.mjs +2 -26
  32. package/dist/admin/pages/EditView/components/FormInputs/Wysiwyg/PreviewWysiwyg.mjs.map +1 -1
  33. package/dist/admin/pages/EditView/components/FormInputs/Wysiwyg/utils/sanitizer.js +72 -0
  34. package/dist/admin/pages/EditView/components/FormInputs/Wysiwyg/utils/sanitizer.js.map +1 -0
  35. package/dist/admin/pages/EditView/components/FormInputs/Wysiwyg/utils/sanitizer.mjs +70 -0
  36. package/dist/admin/pages/EditView/components/FormInputs/Wysiwyg/utils/sanitizer.mjs.map +1 -0
  37. package/dist/admin/pages/ListConfiguration/ListConfigurationPage.js +4 -8
  38. package/dist/admin/pages/ListConfiguration/ListConfigurationPage.js.map +1 -1
  39. package/dist/admin/pages/ListConfiguration/ListConfigurationPage.mjs +5 -9
  40. package/dist/admin/pages/ListConfiguration/ListConfigurationPage.mjs.map +1 -1
  41. package/dist/admin/pages/ListConfiguration/components/SortDisplayedFields.js +6 -10
  42. package/dist/admin/pages/ListConfiguration/components/SortDisplayedFields.js.map +1 -1
  43. package/dist/admin/pages/ListConfiguration/components/SortDisplayedFields.mjs +6 -10
  44. package/dist/admin/pages/ListConfiguration/components/SortDisplayedFields.mjs.map +1 -1
  45. package/dist/admin/pages/ListView/components/Filters.js +7 -9
  46. package/dist/admin/pages/ListView/components/Filters.js.map +1 -1
  47. package/dist/admin/pages/ListView/components/Filters.mjs +7 -9
  48. package/dist/admin/pages/ListView/components/Filters.mjs.map +1 -1
  49. package/dist/admin/pages/ListView/components/TableCells/Media.js +5 -4
  50. package/dist/admin/pages/ListView/components/TableCells/Media.js.map +1 -1
  51. package/dist/admin/pages/ListView/components/TableCells/Media.mjs +5 -4
  52. package/dist/admin/pages/ListView/components/TableCells/Media.mjs.map +1 -1
  53. package/dist/admin/pages/formatComponentConfigurationEditLayout.js +15 -9
  54. package/dist/admin/pages/formatComponentConfigurationEditLayout.js.map +1 -1
  55. package/dist/admin/pages/formatComponentConfigurationEditLayout.mjs +15 -9
  56. package/dist/admin/pages/formatComponentConfigurationEditLayout.mjs.map +1 -1
  57. package/dist/admin/services/components.js +3 -2
  58. package/dist/admin/services/components.js.map +1 -1
  59. package/dist/admin/services/components.mjs +3 -2
  60. package/dist/admin/services/components.mjs.map +1 -1
  61. package/dist/admin/services/contentTypes.js +4 -3
  62. package/dist/admin/services/contentTypes.js.map +1 -1
  63. package/dist/admin/services/contentTypes.mjs +4 -3
  64. package/dist/admin/services/contentTypes.mjs.map +1 -1
  65. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.d.ts +1 -1
  66. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/utils/sanitizer.d.ts +2 -0
  67. package/dist/admin/src/pages/ListConfiguration/components/SortDisplayedFields.d.ts +2 -2
  68. package/dist/admin/src/pages/ListView/components/TableCells/Media.d.ts +2 -2
  69. package/dist/admin/src/pages/formatComponentConfigurationEditLayout.d.ts +3 -1
  70. package/dist/admin/src/utils/layouts/normalizeContentManagerLayout.d.ts +24 -0
  71. package/dist/admin/translations/en.json.js +1 -0
  72. package/dist/admin/translations/en.json.js.map +1 -1
  73. package/dist/admin/translations/en.json.mjs +1 -0
  74. package/dist/admin/translations/en.json.mjs.map +1 -1
  75. package/dist/admin/utils/attributes.js +17 -2
  76. package/dist/admin/utils/attributes.js.map +1 -1
  77. package/dist/admin/utils/attributes.mjs +17 -2
  78. package/dist/admin/utils/attributes.mjs.map +1 -1
  79. package/dist/admin/utils/layouts/normalizeContentManagerLayout.js +329 -0
  80. package/dist/admin/utils/layouts/normalizeContentManagerLayout.js.map +1 -0
  81. package/dist/admin/utils/layouts/normalizeContentManagerLayout.mjs +321 -0
  82. package/dist/admin/utils/layouts/normalizeContentManagerLayout.mjs.map +1 -0
  83. package/dist/server/controllers/collection-types.js +7 -2
  84. package/dist/server/controllers/collection-types.js.map +1 -1
  85. package/dist/server/controllers/collection-types.mjs +7 -2
  86. package/dist/server/controllers/collection-types.mjs.map +1 -1
  87. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  88. 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 = [])=>permissions.flatMap((permission)=>permission.properties?.fields).filter((field, index, arr)=>arr.indexOf(field) === index && typeof field === 'string');
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 = [])=>permissions.flatMap((permission)=>permission.properties?.fields).filter((field, index, arr)=>arr.indexOf(field) === index && typeof field === 'string');
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
- // This must be memoized to avoid inifiinite re-renders where the empty object is different everytime.
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\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 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 return {\n components: Object.keys(components).length === 0 ? undefined : components,\n contentType,\n contentTypes: data?.contentTypes ?? [],\n };\n }, [model, data]);\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 // This must be memoized to avoid inifiinite re-renders where the empty object is different everytime.\n components: React.useMemo(() => components ?? {}, [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":["useContentTypeSchema","model","toggleNotification","useNotification","_unstableFormatAPIError","formatAPIError","useAPIErrorHandler","data","error","isLoading","isFetching","useGetInitialDataQuery","undefined","components","contentType","contentTypes","React","useMemo","find","ct","uid","componentsByKey","reduce","acc","component","extractContentTypeComponents","attributes","Object","keys","length","useEffect","type","message","schema","schemas","allComponents","getComponents","attribute","componentAttributes","values","push","flatMap","componentUid","componentUids","uniqueComponentUids","Set"],"mappings":"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"}
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
- // This must be memoized to avoid inifiinite re-renders where the empty object is different everytime.
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\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 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 return {\n components: Object.keys(components).length === 0 ? undefined : components,\n contentType,\n contentTypes: data?.contentTypes ?? [],\n };\n }, [model, data]);\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 // This must be memoized to avoid inifiinite re-renders where the empty object is different everytime.\n components: React.useMemo(() => components ?? {}, [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":["useContentTypeSchema","model","toggleNotification","useNotification","_unstableFormatAPIError","formatAPIError","useAPIErrorHandler","data","error","isLoading","isFetching","useGetInitialDataQuery","undefined","components","contentType","contentTypes","React","useMemo","find","ct","uid","componentsByKey","reduce","acc","component","extractContentTypeComponents","attributes","Object","keys","length","useEffect","type","message","schema","schemas","allComponents","getComponents","attribute","componentAttributes","values","push","flatMap","componentUid","componentUids","uniqueComponentUids","Set"],"mappings":"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"}
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 edit = formatEditLayout(data, {
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(data, {
126
+ const list = formatListLayout(normalizedData, {
98
127
  schemas,
99
128
  schema,
100
129
  components
101
130
  });
102
131
  const listViewConversionContext = {
103
- componentConfigurations: data.components,
132
+ componentConfigurations: normalizedData.components,
104
133
  componentSchemas: components,
105
134
  contentTypeSchemas: schemas
106
135
  };
107
- return {
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