@strapi/content-manager 5.61.0 → 5.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/dist/admin/components/ConfigurationForm/Fields.js +1 -1
  2. package/dist/admin/components/ConfigurationForm/Fields.js.map +1 -1
  3. package/dist/admin/components/ConfigurationForm/Fields.mjs +3 -3
  4. package/dist/admin/components/ConfigurationForm/Fields.mjs.map +1 -1
  5. package/dist/admin/features/I18nSharedFieldsLock.js +54 -0
  6. package/dist/admin/features/I18nSharedFieldsLock.js.map +1 -0
  7. package/dist/admin/features/I18nSharedFieldsLock.mjs +31 -0
  8. package/dist/admin/features/I18nSharedFieldsLock.mjs.map +1 -0
  9. package/dist/admin/history/components/VersionInputRenderer.js +7 -2
  10. package/dist/admin/history/components/VersionInputRenderer.js.map +1 -1
  11. package/dist/admin/history/components/VersionInputRenderer.mjs +7 -3
  12. package/dist/admin/history/components/VersionInputRenderer.mjs.map +1 -1
  13. package/dist/admin/hooks/useContentManagerInitData.js +27 -8
  14. package/dist/admin/hooks/useContentManagerInitData.js.map +1 -1
  15. package/dist/admin/hooks/useContentManagerInitData.mjs +29 -10
  16. package/dist/admin/hooks/useContentManagerInitData.mjs.map +1 -1
  17. package/dist/admin/hooks/useDocument.js +11 -13
  18. package/dist/admin/hooks/useDocument.js.map +1 -1
  19. package/dist/admin/hooks/useDocument.mjs +11 -13
  20. package/dist/admin/hooks/useDocument.mjs.map +1 -1
  21. package/dist/admin/hooks/useI18nFieldLock.js +169 -0
  22. package/dist/admin/hooks/useI18nFieldLock.js.map +1 -0
  23. package/dist/admin/hooks/useI18nFieldLock.mjs +140 -0
  24. package/dist/admin/hooks/useI18nFieldLock.mjs.map +1 -0
  25. package/dist/admin/hooks/usePersistentQueryParams.js +16 -3
  26. package/dist/admin/hooks/usePersistentQueryParams.js.map +1 -1
  27. package/dist/admin/hooks/usePersistentQueryParams.mjs +16 -3
  28. package/dist/admin/hooks/usePersistentQueryParams.mjs.map +1 -1
  29. package/dist/admin/index.js +5 -0
  30. package/dist/admin/index.js.map +1 -1
  31. package/dist/admin/index.mjs +2 -0
  32. package/dist/admin/index.mjs.map +1 -1
  33. package/dist/admin/modules/app.js +8 -1
  34. package/dist/admin/modules/app.js.map +1 -1
  35. package/dist/admin/modules/app.mjs +8 -2
  36. package/dist/admin/modules/app.mjs.map +1 -1
  37. package/dist/admin/pages/EditView/EditViewPage.js +129 -127
  38. package/dist/admin/pages/EditView/EditViewPage.js.map +1 -1
  39. package/dist/admin/pages/EditView/EditViewPage.mjs +129 -127
  40. package/dist/admin/pages/EditView/EditViewPage.mjs.map +1 -1
  41. package/dist/admin/pages/EditView/components/DocumentActions.js +11 -4
  42. package/dist/admin/pages/EditView/components/DocumentActions.js.map +1 -1
  43. package/dist/admin/pages/EditView/components/DocumentActions.mjs +12 -5
  44. package/dist/admin/pages/EditView/components/DocumentActions.mjs.map +1 -1
  45. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/EditorLayout.js +31 -21
  46. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/EditorLayout.js.map +1 -1
  47. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/EditorLayout.mjs +32 -22
  48. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/EditorLayout.mjs.map +1 -1
  49. package/dist/admin/pages/EditView/components/FormInputs/Component/AddComponentMenu.js +98 -0
  50. package/dist/admin/pages/EditView/components/FormInputs/Component/AddComponentMenu.js.map +1 -0
  51. package/dist/admin/pages/EditView/components/FormInputs/Component/AddComponentMenu.mjs +96 -0
  52. package/dist/admin/pages/EditView/components/FormInputs/Component/AddComponentMenu.mjs.map +1 -0
  53. package/dist/admin/pages/EditView/components/FormInputs/Component/ComponentCopyModal.js +1103 -0
  54. package/dist/admin/pages/EditView/components/FormInputs/Component/ComponentCopyModal.js.map +1 -0
  55. package/dist/admin/pages/EditView/components/FormInputs/Component/ComponentCopyModal.mjs +1077 -0
  56. package/dist/admin/pages/EditView/components/FormInputs/Component/ComponentCopyModal.mjs.map +1 -0
  57. package/dist/admin/pages/EditView/components/FormInputs/Component/CopiedComponents.js +211 -0
  58. package/dist/admin/pages/EditView/components/FormInputs/Component/CopiedComponents.js.map +1 -0
  59. package/dist/admin/pages/EditView/components/FormInputs/Component/CopiedComponents.mjs +183 -0
  60. package/dist/admin/pages/EditView/components/FormInputs/Component/CopiedComponents.mjs.map +1 -0
  61. package/dist/admin/pages/EditView/components/FormInputs/Component/Initializer.js +84 -41
  62. package/dist/admin/pages/EditView/components/FormInputs/Component/Initializer.js.map +1 -1
  63. package/dist/admin/pages/EditView/components/FormInputs/Component/Initializer.mjs +86 -43
  64. package/dist/admin/pages/EditView/components/FormInputs/Component/Initializer.mjs.map +1 -1
  65. package/dist/admin/pages/EditView/components/FormInputs/Component/Input.js +86 -20
  66. package/dist/admin/pages/EditView/components/FormInputs/Component/Input.js.map +1 -1
  67. package/dist/admin/pages/EditView/components/FormInputs/Component/Input.mjs +89 -23
  68. package/dist/admin/pages/EditView/components/FormInputs/Component/Input.mjs.map +1 -1
  69. package/dist/admin/pages/EditView/components/FormInputs/Component/NonRepeatable.js +5 -2
  70. package/dist/admin/pages/EditView/components/FormInputs/Component/NonRepeatable.js.map +1 -1
  71. package/dist/admin/pages/EditView/components/FormInputs/Component/NonRepeatable.mjs +5 -2
  72. package/dist/admin/pages/EditView/components/FormInputs/Component/NonRepeatable.mjs.map +1 -1
  73. package/dist/admin/pages/EditView/components/FormInputs/Component/Repeatable.js +142 -22
  74. package/dist/admin/pages/EditView/components/FormInputs/Component/Repeatable.js.map +1 -1
  75. package/dist/admin/pages/EditView/components/FormInputs/Component/Repeatable.mjs +143 -23
  76. package/dist/admin/pages/EditView/components/FormInputs/Component/Repeatable.mjs.map +1 -1
  77. package/dist/admin/pages/EditView/components/FormInputs/Component/utils/componentCopy.js +402 -0
  78. package/dist/admin/pages/EditView/components/FormInputs/Component/utils/componentCopy.js.map +1 -0
  79. package/dist/admin/pages/EditView/components/FormInputs/Component/utils/componentCopy.mjs +389 -0
  80. package/dist/admin/pages/EditView/components/FormInputs/Component/utils/componentCopy.mjs.map +1 -0
  81. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.js +84 -33
  82. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.js.map +1 -1
  83. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.mjs +85 -34
  84. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.mjs.map +1 -1
  85. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/ComponentPicker.js +5 -1
  86. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/ComponentPicker.js.map +1 -1
  87. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/ComponentPicker.mjs +5 -1
  88. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/ComponentPicker.mjs.map +1 -1
  89. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.js +17 -4
  90. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.js.map +1 -1
  91. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.mjs +18 -5
  92. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.mjs.map +1 -1
  93. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.js +54 -6
  94. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.js.map +1 -1
  95. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.mjs +54 -6
  96. package/dist/admin/pages/EditView/components/FormInputs/DynamicZone/Field.mjs.map +1 -1
  97. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.js +11 -9
  98. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.js.map +1 -1
  99. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.mjs +12 -10
  100. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.mjs.map +1 -1
  101. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.js +9 -4
  102. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.js.map +1 -1
  103. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.mjs +9 -4
  104. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.mjs.map +1 -1
  105. package/dist/admin/pages/EditView/components/FormLayout.js +66 -63
  106. package/dist/admin/pages/EditView/components/FormLayout.js.map +1 -1
  107. package/dist/admin/pages/EditView/components/FormLayout.mjs +66 -63
  108. package/dist/admin/pages/EditView/components/FormLayout.mjs.map +1 -1
  109. package/dist/admin/pages/EditView/components/InputRenderer.js +7 -1
  110. package/dist/admin/pages/EditView/components/InputRenderer.js.map +1 -1
  111. package/dist/admin/pages/EditView/components/InputRenderer.mjs +7 -1
  112. package/dist/admin/pages/EditView/components/InputRenderer.mjs.map +1 -1
  113. package/dist/admin/pages/EditView/utils/data.js +14 -9
  114. package/dist/admin/pages/EditView/utils/data.js.map +1 -1
  115. package/dist/admin/pages/EditView/utils/data.mjs +14 -9
  116. package/dist/admin/pages/EditView/utils/data.mjs.map +1 -1
  117. package/dist/admin/pages/EditView/utils/forms.js +34 -0
  118. package/dist/admin/pages/EditView/utils/forms.js.map +1 -1
  119. package/dist/admin/pages/EditView/utils/forms.mjs +34 -1
  120. package/dist/admin/pages/EditView/utils/forms.mjs.map +1 -1
  121. package/dist/admin/pages/ListConfiguration/components/DraggableCard.js +2 -1
  122. package/dist/admin/pages/ListConfiguration/components/DraggableCard.js.map +1 -1
  123. package/dist/admin/pages/ListConfiguration/components/DraggableCard.mjs +2 -1
  124. package/dist/admin/pages/ListConfiguration/components/DraggableCard.mjs.map +1 -1
  125. package/dist/admin/pages/ListConfiguration/components/EditFieldForm.js +7 -6
  126. package/dist/admin/pages/ListConfiguration/components/EditFieldForm.js.map +1 -1
  127. package/dist/admin/pages/ListConfiguration/components/EditFieldForm.mjs +7 -6
  128. package/dist/admin/pages/ListConfiguration/components/EditFieldForm.mjs.map +1 -1
  129. package/dist/admin/pages/ListView/ListViewPage.js +11 -2
  130. package/dist/admin/pages/ListView/ListViewPage.js.map +1 -1
  131. package/dist/admin/pages/ListView/ListViewPage.mjs +13 -4
  132. package/dist/admin/pages/ListView/ListViewPage.mjs.map +1 -1
  133. package/dist/admin/pages/ListView/components/Filters.js +15 -5
  134. package/dist/admin/pages/ListView/components/Filters.js.map +1 -1
  135. package/dist/admin/pages/ListView/components/Filters.mjs +16 -7
  136. package/dist/admin/pages/ListView/components/Filters.mjs.map +1 -1
  137. package/dist/admin/preview/components/InputPopover.js +18 -8
  138. package/dist/admin/preview/components/InputPopover.js.map +1 -1
  139. package/dist/admin/preview/components/InputPopover.mjs +19 -9
  140. package/dist/admin/preview/components/InputPopover.mjs.map +1 -1
  141. package/dist/admin/preview/pages/Preview.js +5 -16
  142. package/dist/admin/preview/pages/Preview.js.map +1 -1
  143. package/dist/admin/preview/pages/Preview.mjs +5 -16
  144. package/dist/admin/preview/pages/Preview.mjs.map +1 -1
  145. package/dist/admin/preview/utils/fieldUtils.js +2 -0
  146. package/dist/admin/preview/utils/fieldUtils.js.map +1 -1
  147. package/dist/admin/preview/utils/fieldUtils.mjs +2 -1
  148. package/dist/admin/preview/utils/fieldUtils.mjs.map +1 -1
  149. package/dist/admin/services/api.js +2 -1
  150. package/dist/admin/services/api.js.map +1 -1
  151. package/dist/admin/services/api.mjs +2 -1
  152. package/dist/admin/services/api.mjs.map +1 -1
  153. package/dist/admin/services/documents.js +21 -1
  154. package/dist/admin/services/documents.js.map +1 -1
  155. package/dist/admin/services/documents.mjs +21 -2
  156. package/dist/admin/services/documents.mjs.map +1 -1
  157. package/dist/admin/services/relations.js +47 -1
  158. package/dist/admin/services/relations.js.map +1 -1
  159. package/dist/admin/services/relations.mjs +47 -2
  160. package/dist/admin/services/relations.mjs.map +1 -1
  161. package/dist/admin/src/exports.d.ts +2 -0
  162. package/dist/admin/src/features/I18nSharedFieldsLock.d.ts +22 -0
  163. package/dist/admin/src/history/components/VersionInputRenderer.d.ts +7 -1
  164. package/dist/admin/src/history/services/historyVersion.d.ts +1 -1
  165. package/dist/admin/src/hooks/useI18nFieldLock.d.ts +61 -0
  166. package/dist/admin/src/modules/app.d.ts +4 -1
  167. package/dist/admin/src/pages/EditView/components/FormInputs/Component/AddComponentMenu.d.ts +15 -0
  168. package/dist/admin/src/pages/EditView/components/FormInputs/Component/ComponentCopyModal.d.ts +58 -0
  169. package/dist/admin/src/pages/EditView/components/FormInputs/Component/CopiedComponents.d.ts +41 -0
  170. package/dist/admin/src/pages/EditView/components/FormInputs/Component/Initializer.d.ts +7 -3
  171. package/dist/admin/src/pages/EditView/components/FormInputs/Component/NonRepeatable.d.ts +1 -1
  172. package/dist/admin/src/pages/EditView/components/FormInputs/Component/Repeatable.d.ts +2 -2
  173. package/dist/admin/src/pages/EditView/components/FormInputs/Component/utils/componentCopy.d.ts +115 -0
  174. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.d.ts +6 -3
  175. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/ComponentPicker.d.ts +2 -1
  176. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/DynamicComponent.d.ts +5 -1
  177. package/dist/admin/src/pages/EditView/utils/forms.d.ts +13 -2
  178. package/dist/admin/src/pages/ListView/components/Filters.d.ts +12 -3
  179. package/dist/admin/src/preview/services/preview.d.ts +1 -1
  180. package/dist/admin/src/preview/utils/fieldUtils.d.ts +1 -0
  181. package/dist/admin/src/services/api.d.ts +1 -1
  182. package/dist/admin/src/services/components.d.ts +2 -2
  183. package/dist/admin/src/services/contentTypes.d.ts +3 -3
  184. package/dist/admin/src/services/documents.d.ts +25 -21
  185. package/dist/admin/src/services/homepage.d.ts +1 -1
  186. package/dist/admin/src/services/init.d.ts +1 -1
  187. package/dist/admin/src/services/relations.d.ts +5 -3
  188. package/dist/admin/src/services/uid.d.ts +3 -3
  189. package/dist/admin/translations/en.json.js +41 -0
  190. package/dist/admin/translations/en.json.js.map +1 -1
  191. package/dist/admin/translations/en.json.mjs +41 -0
  192. package/dist/admin/translations/en.json.mjs.map +1 -1
  193. package/dist/server/controllers/relations.js +4 -2
  194. package/dist/server/controllers/relations.js.map +1 -1
  195. package/dist/server/controllers/relations.mjs +4 -2
  196. package/dist/server/controllers/relations.mjs.map +1 -1
  197. package/dist/server/controllers/utils/metadata.js +3 -1
  198. package/dist/server/controllers/utils/metadata.js.map +1 -1
  199. package/dist/server/controllers/utils/metadata.mjs +3 -1
  200. package/dist/server/controllers/utils/metadata.mjs.map +1 -1
  201. package/dist/server/history/services/utils.js +3 -1
  202. package/dist/server/history/services/utils.js.map +1 -1
  203. package/dist/server/history/services/utils.mjs +3 -1
  204. package/dist/server/history/services/utils.mjs.map +1 -1
  205. package/dist/server/homepage/services/homepage-query-utils.js +14 -0
  206. package/dist/server/homepage/services/homepage-query-utils.js.map +1 -1
  207. package/dist/server/homepage/services/homepage-query-utils.mjs +14 -1
  208. package/dist/server/homepage/services/homepage-query-utils.mjs.map +1 -1
  209. package/dist/server/homepage/services/homepage.js +1 -1
  210. package/dist/server/homepage/services/homepage.js.map +1 -1
  211. package/dist/server/homepage/services/homepage.mjs +2 -2
  212. package/dist/server/homepage/services/homepage.mjs.map +1 -1
  213. package/dist/server/mcp/sanitizers/shape-relations.js +1 -3
  214. package/dist/server/mcp/sanitizers/shape-relations.js.map +1 -1
  215. package/dist/server/mcp/sanitizers/shape-relations.mjs +1 -3
  216. package/dist/server/mcp/sanitizers/shape-relations.mjs.map +1 -1
  217. package/dist/server/mcp/schemas/output-schemas.js +2 -1
  218. package/dist/server/mcp/schemas/output-schemas.js.map +1 -1
  219. package/dist/server/mcp/schemas/output-schemas.mjs +2 -1
  220. package/dist/server/mcp/schemas/output-schemas.mjs.map +1 -1
  221. package/dist/server/services/document-manager.js +3 -2
  222. package/dist/server/services/document-manager.js.map +1 -1
  223. package/dist/server/services/document-manager.mjs +3 -2
  224. package/dist/server/services/document-manager.mjs.map +1 -1
  225. package/dist/server/services/document-metadata.js +92 -4
  226. package/dist/server/services/document-metadata.js.map +1 -1
  227. package/dist/server/services/document-metadata.mjs +92 -4
  228. package/dist/server/services/document-metadata.mjs.map +1 -1
  229. package/dist/server/services/permission-checker.js +20 -8
  230. package/dist/server/services/permission-checker.js.map +1 -1
  231. package/dist/server/services/permission-checker.mjs +20 -8
  232. package/dist/server/services/permission-checker.mjs.map +1 -1
  233. package/dist/server/services/uid.js +2 -1
  234. package/dist/server/services/uid.js.map +1 -1
  235. package/dist/server/services/uid.mjs +2 -1
  236. package/dist/server/services/uid.mjs.map +1 -1
  237. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  238. package/dist/server/src/controllers/utils/metadata.d.ts +1 -0
  239. package/dist/server/src/controllers/utils/metadata.d.ts.map +1 -1
  240. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  241. package/dist/server/src/homepage/services/homepage-query-utils.d.ts +6 -0
  242. package/dist/server/src/homepage/services/homepage-query-utils.d.ts.map +1 -1
  243. package/dist/server/src/homepage/services/homepage.d.ts.map +1 -1
  244. package/dist/server/src/index.d.ts +21 -7
  245. package/dist/server/src/index.d.ts.map +1 -1
  246. package/dist/server/src/mcp/sanitizers/shape-relations.d.ts.map +1 -1
  247. package/dist/server/src/mcp/schemas/output-schemas.d.ts.map +1 -1
  248. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  249. package/dist/server/src/services/document-metadata.d.ts +5 -1
  250. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  251. package/dist/server/src/services/index.d.ts +21 -7
  252. package/dist/server/src/services/index.d.ts.map +1 -1
  253. package/dist/server/src/services/permission-checker.d.ts +6 -5
  254. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  255. package/dist/server/src/services/uid.d.ts.map +1 -1
  256. package/dist/shared/contracts/collection-types.d.ts +7 -0
  257. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  258. package/dist/shared/contracts/single-types.d.ts +4 -0
  259. package/dist/shared/contracts/single-types.d.ts.map +1 -1
  260. package/package.json +9 -9
@@ -0,0 +1,1077 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+ import { useNotification, useAPIErrorHandler, useForm } from '@strapi/admin/strapi-admin';
4
+ import { Modal, Flex, Box, Button, Searchbar, Typography, Loader, Checkbox, Badge, Radio } from '@strapi/design-system';
5
+ import { Duplicate, WarningCircle } from '@strapi/icons';
6
+ import toPath from 'lodash/toPath';
7
+ import { useIntl } from 'react-intl';
8
+ import { styled } from 'styled-components';
9
+ import { COLLECTION_TYPES } from '../../../../../constants/collections.mjs';
10
+ import { useDebounce } from '../../../../../hooks/useDebounce.mjs';
11
+ import { useDocumentContext } from '../../../../../hooks/useDocumentContext.mjs';
12
+ import { useDocumentLayout } from '../../../../../hooks/useDocumentLayout.mjs';
13
+ import { useLazyGetAllDocumentsQuery, useLazyGetDocumentQuery } from '../../../../../services/documents.mjs';
14
+ import { useLazyGetAllRelationsQuery } from '../../../../../services/relations.mjs';
15
+ import { getTranslation } from '../../../../../utils/translations.mjs';
16
+ import { CellValue } from '../../../../ListView/components/TableCells/CellValue.mjs';
17
+ import { getComponentInstances, getComponentPreview, getRelationsToCopy, connectRelations, cloneComponentData } from './utils/componentCopy.mjs';
18
+
19
+ const ENTRIES_PAGE_SIZE = '10';
20
+ const getDocumentTitle = (document1, mainField, fallback, untitled)=>{
21
+ if (mainField !== 'id') {
22
+ const value = document1?.[mainField];
23
+ if (value !== undefined && value !== null && String(value).trim().length > 0) {
24
+ return String(value);
25
+ }
26
+ }
27
+ return fallback || untitled;
28
+ };
29
+ /**
30
+ * The line of text shown under the title of an entry in the list.
31
+ */ const getEntryMeta = ({ count, fieldLabel, firstInstanceLabel, formatMessage, isCurrent, isFormModified, selection })=>{
32
+ if (isCurrent && isFormModified) {
33
+ return formatMessage({
34
+ id: getTranslation('components.ComponentCopyModal.entries.unsaved'),
35
+ defaultMessage: 'Save your changes to copy from this entry'
36
+ });
37
+ }
38
+ if (count === undefined) {
39
+ return undefined;
40
+ }
41
+ if (count === 0) {
42
+ return isCurrent && selection === 'single' ? formatMessage({
43
+ id: getTranslation('components.ComponentCopyModal.entries.noneOther'),
44
+ defaultMessage: 'No other {label} in this entry'
45
+ }, {
46
+ label: fieldLabel
47
+ }) : formatMessage({
48
+ id: getTranslation('components.ComponentCopyModal.entries.none'),
49
+ defaultMessage: 'No {label} yet'
50
+ }, {
51
+ label: fieldLabel
52
+ });
53
+ }
54
+ if (isCurrent) {
55
+ return formatMessage({
56
+ id: getTranslation('components.ComponentCopyModal.entries.count.saved'),
57
+ defaultMessage: '{count, plural, one {# item} other {# items}} · last saved version'
58
+ }, {
59
+ count
60
+ });
61
+ }
62
+ if (selection === 'single' && count === 1 && firstInstanceLabel) {
63
+ return firstInstanceLabel;
64
+ }
65
+ return formatMessage({
66
+ id: getTranslation('components.ComponentCopyModal.entries.count'),
67
+ defaultMessage: '{count, plural, one {# item} other {# items}}'
68
+ }, {
69
+ count
70
+ });
71
+ };
72
+ /**
73
+ * The modal is mounted while it's open and unmounted when it's closed.
74
+ */ const ComponentCopyModal = ({ componentUid, fieldLabel, maxSelection = Infinity, mode, onClose, onInsert, replacesContent = false, selection, sourceFieldName })=>{
75
+ const { formatMessage } = useIntl();
76
+ const { toggleNotification } = useNotification();
77
+ const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler(getTranslation);
78
+ const { currentDocument, currentDocumentMeta } = useDocumentContext('ComponentCopyModal');
79
+ const { edit: { components: componentLayouts, settings } } = useDocumentLayout(currentDocumentMeta.model);
80
+ const targetValues = useForm('ComponentCopyModal', (state)=>state.values);
81
+ const isFormModified = useForm('ComponentCopyModal', (state)=>state.modified);
82
+ const [search, setSearch] = React.useState('');
83
+ const debouncedSearch = useDebounce(search, 300);
84
+ const searchRef = React.useRef(null);
85
+ const [selectedEntry, setSelectedEntry] = React.useState(null);
86
+ const [sourceDocument, setSourceDocument] = React.useState(null);
87
+ const [selectedPaths, setSelectedPaths] = React.useState([]);
88
+ const [isCopying, setIsCopying] = React.useState(false);
89
+ // Read while rendering: once mounted, the modal holds the focus
90
+ const [returnFocusTarget] = React.useState(getReturnFocusTarget);
91
+ /**
92
+ * Loading an entry and copying components are asynchronous, the modal can be closed in the
93
+ * meantime and the user can pick another entry while one loads.
94
+ */ const isMountedRef = React.useRef(true);
95
+ const selectedDocumentIdRef = React.useRef(null);
96
+ React.useEffect(()=>{
97
+ isMountedRef.current = true;
98
+ return ()=>{
99
+ isMountedRef.current = false;
100
+ };
101
+ }, []);
102
+ const [getDocuments, { data: documentsData, error: documentsError, isFetching: isFetchingDocuments, isUninitialized: isDocumentsQueryUninitialized }] = useLazyGetAllDocumentsQuery();
103
+ const [getDocument, { isFetching: isFetchingDocument }] = useLazyGetDocumentQuery();
104
+ const [getAllRelations] = useLazyGetAllRelationsQuery();
105
+ const { components } = currentDocument;
106
+ const componentDisplayName = components[componentUid]?.info?.displayName ?? componentUid;
107
+ const currentDocumentId = currentDocumentMeta.documentId;
108
+ /**
109
+ * The last saved version of the entry being edited: what the API holds for it.
110
+ */ const savedDocument = currentDocument.document;
111
+ /**
112
+ * The list of entries only loads the first level of components: for a field nested in another
113
+ * component, how many components an entry holds is only known once the entry is loaded.
114
+ */ const isNestedField = toPath(sourceFieldName).length > 1;
115
+ React.useEffect(()=>{
116
+ if (currentDocumentMeta.collectionType !== COLLECTION_TYPES) {
117
+ return;
118
+ }
119
+ void getDocuments({
120
+ model: currentDocumentMeta.model,
121
+ params: {
122
+ ...currentDocumentMeta.params,
123
+ _q: debouncedSearch || undefined,
124
+ page: '1',
125
+ pageSize: ENTRIES_PAGE_SIZE,
126
+ sort: 'updatedAt:DESC'
127
+ }
128
+ });
129
+ }, [
130
+ currentDocumentMeta.collectionType,
131
+ currentDocumentMeta.model,
132
+ currentDocumentMeta.params,
133
+ debouncedSearch,
134
+ getDocuments
135
+ ]);
136
+ React.useEffect(()=>{
137
+ if (documentsError) {
138
+ toggleNotification({
139
+ type: 'danger',
140
+ message: formatAPIError(documentsError)
141
+ });
142
+ }
143
+ }, [
144
+ documentsError,
145
+ formatAPIError,
146
+ toggleNotification
147
+ ]);
148
+ const findInstances = React.useCallback((document1)=>getComponentInstances({
149
+ componentUid,
150
+ components,
151
+ getMainField: (uid)=>componentLayouts[uid]?.settings?.mainField,
152
+ mode,
153
+ schema: currentDocument.schema,
154
+ sourceDocument: document1,
155
+ sourceFieldName,
156
+ targetValues
157
+ }), [
158
+ componentLayouts,
159
+ componentUid,
160
+ components,
161
+ currentDocument.schema,
162
+ mode,
163
+ sourceFieldName,
164
+ targetValues
165
+ ]);
166
+ /**
167
+ * In a field holding one component, the saved version of that very component is not something
168
+ * to copy: the field already holds it.
169
+ */ const filterOwnInstance = React.useCallback((instances)=>selection === 'single' ? instances.filter((instance)=>instance.sourcePath !== sourceFieldName) : instances, [
170
+ selection,
171
+ sourceFieldName
172
+ ]);
173
+ const currentEntryInstances = React.useMemo(()=>savedDocument ? filterOwnInstance(findInstances(savedDocument)) : [], [
174
+ filterOwnInstance,
175
+ findInstances,
176
+ savedDocument
177
+ ]);
178
+ const untitled = formatMessage({
179
+ id: 'content-manager.containers.untitled',
180
+ defaultMessage: 'Untitled'
181
+ });
182
+ const getTitle = (document1)=>getDocumentTitle(document1, settings.mainField, document1.documentId, untitled);
183
+ const currentEntryTitle = savedDocument ? getTitle(savedDocument) : '';
184
+ const normalizedSearch = debouncedSearch.trim().toLowerCase();
185
+ // A locale that isn't saved yet has a document id but nothing stored (no `id`)
186
+ const showCurrentEntry = !!savedDocument?.id && !!currentDocumentId && (!normalizedSearch || currentEntryTitle.toLowerCase().includes(normalizedSearch));
187
+ const currentEntryRow = showCurrentEntry ? {
188
+ documentId: currentDocumentId,
189
+ title: currentEntryTitle,
190
+ isCurrent: true,
191
+ disabled: isFormModified || currentEntryInstances.length === 0,
192
+ meta: getEntryMeta({
193
+ count: currentEntryInstances.length,
194
+ fieldLabel,
195
+ formatMessage,
196
+ isCurrent: true,
197
+ isFormModified,
198
+ selection
199
+ })
200
+ } : null;
201
+ const entryRows = [
202
+ ...currentEntryRow ? [
203
+ currentEntryRow
204
+ ] : [],
205
+ ...(documentsData?.results ?? []).filter((document1)=>document1.documentId !== currentDocumentId).map((document1)=>{
206
+ const instances = isNestedField ? undefined : findInstances(document1);
207
+ return {
208
+ documentId: document1.documentId,
209
+ title: getTitle(document1),
210
+ isCurrent: false,
211
+ disabled: instances?.length === 0,
212
+ meta: getEntryMeta({
213
+ count: instances?.length,
214
+ fieldLabel,
215
+ firstInstanceLabel: instances?.[0]?.label,
216
+ formatMessage,
217
+ isCurrent: false,
218
+ isFormModified,
219
+ selection
220
+ })
221
+ };
222
+ })
223
+ ];
224
+ /**
225
+ * Loads the entry components are copied from.
226
+ */ const handleSelectEntry = async (row)=>{
227
+ // Picking the entry that's already selected keeps the components ticked in it.
228
+ if (selectedDocumentIdRef.current === row.documentId) {
229
+ return;
230
+ }
231
+ selectedDocumentIdRef.current = row.documentId;
232
+ setSelectedEntry({
233
+ documentId: row.documentId,
234
+ title: row.title,
235
+ isCurrent: row.isCurrent
236
+ });
237
+ setSelectedPaths([]);
238
+ if (row.isCurrent) {
239
+ setSourceDocument(savedDocument ?? null);
240
+ return;
241
+ }
242
+ setSourceDocument(null);
243
+ const result = await getDocument({
244
+ collectionType: currentDocumentMeta.collectionType,
245
+ documentId: row.documentId,
246
+ model: currentDocumentMeta.model,
247
+ params: currentDocumentMeta.params
248
+ });
249
+ // Another entry was picked in the meantime, or the modal was closed.
250
+ if (!isMountedRef.current || selectedDocumentIdRef.current !== row.documentId) {
251
+ return;
252
+ }
253
+ if (result.error) {
254
+ toggleNotification({
255
+ type: 'danger',
256
+ message: formatAPIError(result.error)
257
+ });
258
+ selectedDocumentIdRef.current = null;
259
+ setSelectedEntry(null);
260
+ return;
261
+ }
262
+ // Without its data the entry can't be previewed: unselect it, so it can be picked again.
263
+ if (!result.data?.data) {
264
+ selectedDocumentIdRef.current = null;
265
+ setSelectedEntry(null);
266
+ return;
267
+ }
268
+ setSourceDocument(result.data.data);
269
+ };
270
+ const isCurrentEntrySelected = selectedEntry?.isCurrent ?? false;
271
+ const instances = React.useMemo(()=>{
272
+ if (isCurrentEntrySelected) {
273
+ return currentEntryInstances;
274
+ }
275
+ return sourceDocument ? findInstances(sourceDocument) : [];
276
+ }, [
277
+ currentEntryInstances,
278
+ findInstances,
279
+ isCurrentEntrySelected,
280
+ sourceDocument
281
+ ]);
282
+ // An entry holding a single component is selected right away: there's nothing else to pick.
283
+ React.useEffect(()=>{
284
+ if (instances.length === 1) {
285
+ const [{ sourcePath }] = instances;
286
+ setSelectedPaths((paths)=>paths.length === 1 && paths[0] === sourcePath ? paths : [
287
+ sourcePath
288
+ ]);
289
+ }
290
+ }, [
291
+ instances
292
+ ]);
293
+ const selectedInstances = instances.filter((instance)=>selectedPaths.includes(instance.sourcePath));
294
+ const areAllSelected = instances.length > 0 && selectedInstances.length === instances.length;
295
+ const isSelectionTooLarge = selectedInstances.length > maxSelection;
296
+ const mainField = componentLayouts[componentUid]?.settings?.mainField;
297
+ const previewFields = React.useMemo(()=>componentLayouts[componentUid]?.layout.flat().filter((field)=>field.name !== mainField).map((field)=>({
298
+ name: field.name,
299
+ label: field.label
300
+ })), [
301
+ componentLayouts,
302
+ componentUid,
303
+ mainField
304
+ ]);
305
+ /**
306
+ * The copy is a snapshot of the component, its relations are connected to the same entries.
307
+ * A relation that can't be loaded is left empty rather than failing the whole copy.
308
+ */ const copyInstance = async (instance)=>{
309
+ const relations = getRelationsToCopy(instance.source, componentUid, components);
310
+ const loadedRelations = await Promise.allSettled(relations.map(({ model, id, targetField })=>getAllRelations({
311
+ model,
312
+ id,
313
+ targetField,
314
+ params: currentDocumentMeta.params
315
+ }).unwrap()));
316
+ const data = loadedRelations.reduce((acc, result, index)=>result.status === 'fulfilled' ? connectRelations(acc, relations[index].path, result.value) : acc, cloneComponentData(instance.source, componentUid, components));
317
+ return {
318
+ data: mode === 'dynamiczone' ? {
319
+ ...data,
320
+ __component: componentUid
321
+ } : data,
322
+ hasMissingRelations: loadedRelations.some((result)=>result.status === 'rejected')
323
+ };
324
+ };
325
+ /**
326
+ * The current entry gets its own messages: "this entry" can't be injected in place of an
327
+ * entry title without breaking the sentence in other languages.
328
+ */ const getSuccessMessage = (count)=>{
329
+ const entry = selectedEntry?.title ?? '';
330
+ if (selection === 'single' && replacesContent) {
331
+ return isCurrentEntrySelected ? formatMessage({
332
+ id: getTranslation('components.ComponentCopyModal.success.replaced.currentEntry'),
333
+ defaultMessage: '{label} replaced with content from this entry'
334
+ }, {
335
+ label: fieldLabel
336
+ }) : formatMessage({
337
+ id: getTranslation('components.ComponentCopyModal.success.replaced'),
338
+ defaultMessage: '{label} replaced with content from {entry}'
339
+ }, {
340
+ label: fieldLabel,
341
+ entry
342
+ });
343
+ }
344
+ if (selection === 'single') {
345
+ return isCurrentEntrySelected ? formatMessage({
346
+ id: getTranslation('components.ComponentCopyModal.success.single.currentEntry'),
347
+ defaultMessage: '{label} copied from this entry'
348
+ }, {
349
+ label: fieldLabel
350
+ }) : formatMessage({
351
+ id: getTranslation('components.ComponentCopyModal.success.single'),
352
+ defaultMessage: '{label} copied from {entry}'
353
+ }, {
354
+ label: fieldLabel,
355
+ entry
356
+ });
357
+ }
358
+ if (mode === 'dynamiczone') {
359
+ return isCurrentEntrySelected ? formatMessage({
360
+ id: getTranslation('components.ComponentCopyModal.success.components.currentEntry'),
361
+ defaultMessage: '{count, plural, one {# {component} component} other {# {component} components}} copied from this entry'
362
+ }, {
363
+ count,
364
+ component: componentDisplayName
365
+ }) : formatMessage({
366
+ id: getTranslation('components.ComponentCopyModal.success.components'),
367
+ defaultMessage: '{count, plural, one {# {component} component} other {# {component} components}} copied from {entry}'
368
+ }, {
369
+ count,
370
+ component: componentDisplayName,
371
+ entry
372
+ });
373
+ }
374
+ return isCurrentEntrySelected ? formatMessage({
375
+ id: getTranslation('components.ComponentCopyModal.success.entries.currentEntry'),
376
+ defaultMessage: '{count, plural, one {# entry} other {# entries}} copied from this entry'
377
+ }, {
378
+ count
379
+ }) : formatMessage({
380
+ id: getTranslation('components.ComponentCopyModal.success.entries'),
381
+ defaultMessage: '{count, plural, one {# entry} other {# entries}} copied from {entry}'
382
+ }, {
383
+ count,
384
+ entry
385
+ });
386
+ };
387
+ const handleConfirm = async ()=>{
388
+ if (selectedInstances.length === 0 || isSelectionTooLarge) {
389
+ return;
390
+ }
391
+ setIsCopying(true);
392
+ const copies = await Promise.all(selectedInstances.map(copyInstance));
393
+ if (!isMountedRef.current) {
394
+ return;
395
+ }
396
+ // The field tells the user why it turns the copies down, e.g. it's full.
397
+ if (!onInsert(copies.map(({ data })=>data))) {
398
+ onClose();
399
+ return;
400
+ }
401
+ onClose();
402
+ toggleNotification(copies.some(({ hasMissingRelations })=>hasMissingRelations) ? {
403
+ type: 'warning',
404
+ message: formatMessage({
405
+ id: getTranslation('components.ComponentCopyModal.relations-warning'),
406
+ defaultMessage: 'Content copied, but some of its relations could not be loaded.'
407
+ })
408
+ } : {
409
+ type: 'success',
410
+ message: getSuccessMessage(copies.length)
411
+ });
412
+ };
413
+ const title = selection === 'single' ? formatMessage({
414
+ id: getTranslation('components.ComponentCopyModal.title.single'),
415
+ defaultMessage: 'Copy an existing {label}'
416
+ }, {
417
+ label: fieldLabel
418
+ }) : formatMessage({
419
+ id: getTranslation('components.ComponentCopyModal.title.multiple'),
420
+ defaultMessage: 'Copy existing {label}'
421
+ }, {
422
+ label: fieldLabel
423
+ });
424
+ const getConfirmLabel = ()=>{
425
+ if (selection === 'single' && replacesContent) {
426
+ return formatMessage({
427
+ id: getTranslation('components.ComponentCopyModal.confirm.replace'),
428
+ defaultMessage: 'Replace content'
429
+ });
430
+ }
431
+ if (selectedInstances.length > 1) {
432
+ return formatMessage({
433
+ id: getTranslation('components.ComponentCopyModal.confirm.multiple'),
434
+ defaultMessage: 'Use content from {count} items'
435
+ }, {
436
+ count: selectedInstances.length
437
+ });
438
+ }
439
+ return formatMessage({
440
+ id: getTranslation('components.ComponentCopyModal.confirm'),
441
+ defaultMessage: 'Use content'
442
+ });
443
+ };
444
+ const renderFooterNote = ()=>{
445
+ if (isSelectionTooLarge) {
446
+ return /*#__PURE__*/ jsx(FooterWarning, {
447
+ children: formatMessage({
448
+ id: getTranslation('components.ComponentCopyModal.footer.tooMany'),
449
+ defaultMessage: 'You can add {remaining, plural, one {# more item} other {# more items}} to {label}. Unselect {excess, plural, one {# item} other {# items}}.'
450
+ }, {
451
+ label: fieldLabel,
452
+ remaining: maxSelection,
453
+ excess: selectedInstances.length - maxSelection
454
+ })
455
+ });
456
+ }
457
+ if (selection === 'single' && replacesContent) {
458
+ return /*#__PURE__*/ jsx(FooterWarning, {
459
+ children: formatMessage({
460
+ id: getTranslation('components.ComponentCopyModal.footer.replace'),
461
+ defaultMessage: 'This will replace the current content of {label}.'
462
+ }, {
463
+ label: fieldLabel
464
+ })
465
+ });
466
+ }
467
+ return /*#__PURE__*/ jsx(Typography, {
468
+ variant: "pi",
469
+ textColor: "neutral600",
470
+ children: formatMessage({
471
+ id: getTranslation('components.ComponentCopyModal.footer.note'),
472
+ defaultMessage: 'The content is copied: editing it here won’t change the original entry.'
473
+ })
474
+ });
475
+ };
476
+ const isLoadingEntries = isFetchingDocuments || isDocumentsQueryUninitialized;
477
+ const isLoadingSource = selectedEntry !== null && !selectedEntry.isCurrent && !sourceDocument;
478
+ const loadingLabel = formatMessage({
479
+ id: getTranslation('components.ComponentCopyModal.loading'),
480
+ defaultMessage: 'Loading'
481
+ });
482
+ const renderInstanceSummary = (instance, titleId)=>/*#__PURE__*/ jsx(InstanceSummary, {
483
+ instance: instance,
484
+ preview: getComponentPreview(instance.source, componentUid, components, previewFields),
485
+ titleId: titleId
486
+ });
487
+ const renderPreview = ()=>{
488
+ if (selectedEntry === null) {
489
+ return /*#__PURE__*/ jsxs(Flex, {
490
+ direction: "column",
491
+ alignItems: "center",
492
+ justifyContent: "center",
493
+ gap: 2,
494
+ padding: 6,
495
+ grow: 1,
496
+ children: [
497
+ /*#__PURE__*/ jsx(Flex, {
498
+ color: "neutral400",
499
+ children: /*#__PURE__*/ jsx(Duplicate, {
500
+ width: "2.4rem",
501
+ height: "2.4rem"
502
+ })
503
+ }),
504
+ /*#__PURE__*/ jsx(Typography, {
505
+ variant: "omega",
506
+ textColor: "neutral600",
507
+ textAlign: "center",
508
+ children: selection === 'single' ? formatMessage({
509
+ id: getTranslation('components.ComponentCopyModal.preview.empty.single'),
510
+ defaultMessage: 'Pick an entry to preview its {label}.'
511
+ }, {
512
+ label: fieldLabel
513
+ }) : formatMessage({
514
+ id: getTranslation('components.ComponentCopyModal.preview.empty'),
515
+ defaultMessage: 'Pick an entry to see its {label}.'
516
+ }, {
517
+ label: fieldLabel
518
+ })
519
+ })
520
+ ]
521
+ });
522
+ }
523
+ if (isLoadingSource) {
524
+ return /*#__PURE__*/ jsx(Flex, {
525
+ justifyContent: "center",
526
+ alignItems: "center",
527
+ grow: 1,
528
+ padding: 6,
529
+ children: /*#__PURE__*/ jsx(Loader, {
530
+ small: true,
531
+ children: loadingLabel
532
+ })
533
+ });
534
+ }
535
+ return /*#__PURE__*/ jsxs(Fragment, {
536
+ children: [
537
+ /*#__PURE__*/ jsxs(PreviewHeader, {
538
+ justifyContent: "space-between",
539
+ alignItems: "center",
540
+ gap: 4,
541
+ paddingTop: 4,
542
+ paddingBottom: 4,
543
+ paddingLeft: 5,
544
+ paddingRight: 5,
545
+ children: [
546
+ /*#__PURE__*/ jsx(Typography, {
547
+ fontWeight: "bold",
548
+ textColor: "neutral800",
549
+ ellipsis: true,
550
+ children: formatMessage({
551
+ id: getTranslation('components.ComponentCopyModal.preview.title'),
552
+ defaultMessage: '{entry} · {label}'
553
+ }, {
554
+ entry: selectedEntry.title,
555
+ label: fieldLabel
556
+ })
557
+ }),
558
+ selection === 'multiple' && instances.length > 1 && /*#__PURE__*/ jsx(Checkbox, {
559
+ checked: areAllSelected,
560
+ onCheckedChange: (checked)=>setSelectedPaths(checked === true ? instances.map((instance)=>instance.sourcePath) : []),
561
+ children: formatMessage({
562
+ id: getTranslation('components.ComponentCopyModal.selectAll'),
563
+ defaultMessage: 'Select all'
564
+ })
565
+ })
566
+ ]
567
+ }),
568
+ /*#__PURE__*/ jsx(Scrollable, {
569
+ direction: "column",
570
+ alignItems: "stretch",
571
+ gap: 3,
572
+ padding: 5,
573
+ children: /*#__PURE__*/ jsx(InstancePicker, {
574
+ fieldLabel: fieldLabel,
575
+ instances: instances,
576
+ onSelectedPathsChange: setSelectedPaths,
577
+ renderSummary: renderInstanceSummary,
578
+ selectedPaths: selectedPaths,
579
+ selection: selection
580
+ })
581
+ })
582
+ ]
583
+ });
584
+ };
585
+ return /*#__PURE__*/ jsx(Modal.Root, {
586
+ open: true,
587
+ onOpenChange: (isOpen)=>!isOpen && onClose(),
588
+ children: /*#__PURE__*/ jsxs(ModalContent, {
589
+ onCloseAutoFocus: (event)=>{
590
+ // The modal has no trigger of its own to give the focus back to
591
+ if (returnFocusTarget?.isConnected) {
592
+ event.preventDefault();
593
+ returnFocusTarget.focus();
594
+ }
595
+ },
596
+ onEscapeKeyDown: (event)=>{
597
+ // Escape in a filled search only clears it, which the searchbar does itself
598
+ if (search !== '' && event.target === searchRef.current) {
599
+ event.preventDefault();
600
+ }
601
+ },
602
+ children: [
603
+ /*#__PURE__*/ jsx(Modal.Header, {
604
+ children: /*#__PURE__*/ jsx(Modal.Title, {
605
+ children: title
606
+ })
607
+ }),
608
+ /*#__PURE__*/ jsxs(Panes, {
609
+ children: [
610
+ /*#__PURE__*/ jsx(EntriesPane, {
611
+ appliedSearch: debouncedSearch,
612
+ isLoading: isLoadingEntries,
613
+ // Picking a second entry while one loads would start a race between the two.
614
+ isSelectionLocked: isFetchingDocument || isCopying,
615
+ onSearchChange: setSearch,
616
+ onSelect: handleSelectEntry,
617
+ rows: entryRows,
618
+ search: search,
619
+ searchRef: searchRef,
620
+ selectedDocumentId: selectedEntry?.documentId ?? null
621
+ }),
622
+ /*#__PURE__*/ jsx(PreviewPane, {
623
+ direction: "column",
624
+ alignItems: "stretch",
625
+ children: renderPreview()
626
+ })
627
+ ]
628
+ }),
629
+ /*#__PURE__*/ jsxs(ModalFooter, {
630
+ gap: 4,
631
+ children: [
632
+ renderFooterNote(),
633
+ /*#__PURE__*/ jsxs(Flex, {
634
+ gap: 2,
635
+ shrink: 0,
636
+ children: [
637
+ /*#__PURE__*/ jsx(Modal.Close, {
638
+ children: /*#__PURE__*/ jsx(Button, {
639
+ variant: "tertiary",
640
+ children: formatMessage({
641
+ id: 'app.components.Button.cancel',
642
+ defaultMessage: 'Cancel'
643
+ })
644
+ })
645
+ }),
646
+ /*#__PURE__*/ jsx(Button, {
647
+ onClick: handleConfirm,
648
+ disabled: selectedInstances.length === 0 || isSelectionTooLarge,
649
+ loading: isCopying,
650
+ children: getConfirmLabel()
651
+ })
652
+ ]
653
+ })
654
+ ]
655
+ })
656
+ ]
657
+ })
658
+ });
659
+ };
660
+ /* -------------------------------------------------------------------------------------------------
661
+ * getReturnFocusTarget
662
+ * -----------------------------------------------------------------------------------------------*/ /**
663
+ * @description The element to focus once the modal is closed: the button that opened it, or when
664
+ * it was opened from a menu item, the button of the menu (the menu is gone by then).
665
+ */ const getReturnFocusTarget = ()=>{
666
+ const active = document.activeElement;
667
+ if (!(active instanceof HTMLElement) || active === document.body) {
668
+ return null;
669
+ }
670
+ const menu = active.closest('[role="menu"]');
671
+ if (!menu) {
672
+ return active;
673
+ }
674
+ return menu.id ? document.querySelector(`[aria-controls="${CSS.escape(menu.id)}"]`) : null;
675
+ };
676
+ /**
677
+ * The left pane of the modal: the search and the list of entries to copy from.
678
+ */ const EntriesPane = ({ appliedSearch, isLoading, isSelectionLocked, onSearchChange, onSelect, rows, search, searchRef, selectedDocumentId })=>{
679
+ const { formatMessage } = useIntl();
680
+ const normalizedSearch = appliedSearch.trim();
681
+ const searchLabel = formatMessage({
682
+ id: getTranslation('components.ComponentCopyModal.search'),
683
+ defaultMessage: 'Search all entries'
684
+ });
685
+ const renderRows = ()=>{
686
+ if (isLoading) {
687
+ return /*#__PURE__*/ jsx(Flex, {
688
+ justifyContent: "center",
689
+ padding: 4,
690
+ children: /*#__PURE__*/ jsx(Loader, {
691
+ small: true,
692
+ children: formatMessage({
693
+ id: getTranslation('components.ComponentCopyModal.loading'),
694
+ defaultMessage: 'Loading'
695
+ })
696
+ })
697
+ });
698
+ }
699
+ if (rows.length === 0) {
700
+ return /*#__PURE__*/ jsx(Box, {
701
+ padding: 2,
702
+ children: /*#__PURE__*/ jsx(Typography, {
703
+ variant: "omega",
704
+ textColor: "neutral600",
705
+ children: normalizedSearch ? formatMessage({
706
+ id: getTranslation('components.ComponentCopyModal.entries.noMatch'),
707
+ defaultMessage: 'No entry matches “{search}”'
708
+ }, {
709
+ search: normalizedSearch
710
+ }) : formatMessage({
711
+ id: getTranslation('components.ComponentCopyModal.noEntries'),
712
+ defaultMessage: 'No entries found'
713
+ })
714
+ })
715
+ });
716
+ }
717
+ // While locked, the entries aren't `disabled`: the picked one keeps the focus while it loads.
718
+ return rows.map((row)=>/*#__PURE__*/ jsxs(EntryButton, {
719
+ type: "button",
720
+ "aria-pressed": row.documentId === selectedDocumentId,
721
+ "aria-disabled": isSelectionLocked || undefined,
722
+ disabled: row.disabled,
723
+ onClick: ()=>{
724
+ if (!isSelectionLocked) {
725
+ onSelect(row);
726
+ }
727
+ },
728
+ children: [
729
+ /*#__PURE__*/ jsxs(Flex, {
730
+ gap: 2,
731
+ alignItems: "center",
732
+ wrap: "wrap",
733
+ children: [
734
+ /*#__PURE__*/ jsx(Typography, {
735
+ fontWeight: "bold",
736
+ textColor: "inherit",
737
+ children: row.title
738
+ }),
739
+ row.isCurrent && /*#__PURE__*/ jsx(Badge, {
740
+ size: "S",
741
+ active: true,
742
+ children: formatMessage({
743
+ id: getTranslation('components.ComponentCopyModal.entries.current'),
744
+ defaultMessage: 'This entry'
745
+ })
746
+ })
747
+ ]
748
+ }),
749
+ row.meta && /*#__PURE__*/ jsx(Typography, {
750
+ variant: "pi",
751
+ textColor: "neutral600",
752
+ children: row.meta
753
+ })
754
+ ]
755
+ }, row.documentId));
756
+ };
757
+ return /*#__PURE__*/ jsxs(EntriesPaneRoot, {
758
+ direction: "column",
759
+ alignItems: "stretch",
760
+ children: [
761
+ /*#__PURE__*/ jsx(Box, {
762
+ paddingTop: 4,
763
+ paddingBottom: 2,
764
+ paddingLeft: 4,
765
+ paddingRight: 4,
766
+ children: /*#__PURE__*/ jsx(Searchbar, {
767
+ ref: searchRef,
768
+ name: "component-copy-search",
769
+ value: search,
770
+ onChange: (event)=>onSearchChange(event.target.value),
771
+ onClear: ()=>onSearchChange(''),
772
+ clearLabel: formatMessage({
773
+ id: 'clearLabel',
774
+ defaultMessage: 'Clear'
775
+ }),
776
+ placeholder: searchLabel,
777
+ size: "S",
778
+ children: searchLabel
779
+ })
780
+ }),
781
+ !normalizedSearch && /*#__PURE__*/ jsx(Box, {
782
+ paddingLeft: 4,
783
+ paddingRight: 4,
784
+ paddingBottom: 1,
785
+ children: /*#__PURE__*/ jsx(Typography, {
786
+ variant: "sigma",
787
+ textColor: "neutral600",
788
+ children: formatMessage({
789
+ id: getTranslation('components.ComponentCopyModal.entries.title'),
790
+ defaultMessage: 'Recently updated entries'
791
+ })
792
+ })
793
+ }),
794
+ /*#__PURE__*/ jsx(Scrollable, {
795
+ direction: "column",
796
+ alignItems: "stretch",
797
+ gap: 1,
798
+ padding: 2,
799
+ children: renderRows()
800
+ })
801
+ ]
802
+ });
803
+ };
804
+ /**
805
+ * The components of the picked entry: a read-only card when there's one to take, radios when one
806
+ * of several is taken, checkboxes when several can be taken.
807
+ */ const InstancePicker = ({ fieldLabel, instances, onSelectedPathsChange, renderSummary, selectedPaths, selection })=>{
808
+ const { formatMessage } = useIntl();
809
+ const radioTitleId = React.useId();
810
+ if (instances.length === 0) {
811
+ return /*#__PURE__*/ jsx(Typography, {
812
+ variant: "omega",
813
+ textColor: "neutral600",
814
+ children: formatMessage({
815
+ id: getTranslation('components.ComponentCopyModal.preview.noComponents'),
816
+ defaultMessage: 'No {label} in this entry'
817
+ }, {
818
+ label: fieldLabel
819
+ })
820
+ });
821
+ }
822
+ if (selection === 'single' && instances.length === 1) {
823
+ return /*#__PURE__*/ jsx(InstanceCard, {
824
+ $selected: true,
825
+ children: renderSummary(instances[0], `${radioTitleId}-0`)
826
+ });
827
+ }
828
+ if (selection === 'single') {
829
+ return /*#__PURE__*/ jsx(Radio.Group, {
830
+ value: selectedPaths[0] ?? '',
831
+ onValueChange: (value)=>onSelectedPathsChange([
832
+ value
833
+ ]),
834
+ children: instances.map((instance, index)=>{
835
+ const titleId = `${radioTitleId}-${index}`;
836
+ return /*#__PURE__*/ jsx(InstanceCard, {
837
+ $selected: selectedPaths.includes(instance.sourcePath),
838
+ children: /*#__PURE__*/ jsx(Radio.Item, {
839
+ value: instance.sourcePath,
840
+ "aria-labelledby": titleId,
841
+ children: renderSummary(instance, titleId)
842
+ })
843
+ }, instance.sourcePath);
844
+ })
845
+ });
846
+ }
847
+ const togglePath = (sourcePath, checked)=>{
848
+ onSelectedPathsChange(checked ? [
849
+ ...selectedPaths.filter((path)=>path !== sourcePath),
850
+ sourcePath
851
+ ] : selectedPaths.filter((path)=>path !== sourcePath));
852
+ };
853
+ return /*#__PURE__*/ jsx(Fragment, {
854
+ children: instances.map((instance)=>/*#__PURE__*/ jsx(SelectableInstanceCard, {
855
+ checked: selectedPaths.includes(instance.sourcePath),
856
+ onCheckedChange: (checked)=>togglePath(instance.sourcePath, checked),
857
+ renderSummary: (titleId)=>renderSummary(instance, titleId)
858
+ }, instance.sourcePath))
859
+ });
860
+ };
861
+ const InstanceSummary = ({ instance, preview, titleId })=>{
862
+ const { formatMessage } = useIntl();
863
+ return /*#__PURE__*/ jsxs(Flex, {
864
+ tag: "span",
865
+ direction: "column",
866
+ alignItems: "stretch",
867
+ gap: 2,
868
+ grow: 1,
869
+ minWidth: 0,
870
+ children: [
871
+ /*#__PURE__*/ jsxs(Flex, {
872
+ tag: "span",
873
+ gap: 2,
874
+ alignItems: "baseline",
875
+ wrap: "wrap",
876
+ children: [
877
+ /*#__PURE__*/ jsx(Typography, {
878
+ tag: "span",
879
+ id: titleId,
880
+ fontWeight: "bold",
881
+ textColor: "neutral800",
882
+ children: instance.label
883
+ }),
884
+ instance.parentLabel && /*#__PURE__*/ jsx(Typography, {
885
+ tag: "span",
886
+ variant: "pi",
887
+ textColor: "neutral600",
888
+ children: formatMessage({
889
+ id: getTranslation('components.ComponentCopyModal.instance.nested'),
890
+ defaultMessage: 'In {parent}'
891
+ }, {
892
+ parent: instance.parentLabel
893
+ })
894
+ })
895
+ ]
896
+ }),
897
+ preview.length > 0 && /*#__PURE__*/ jsx(PreviewGrid, {
898
+ tag: "span",
899
+ children: preview.map((field)=>/*#__PURE__*/ jsxs(Flex, {
900
+ tag: "span",
901
+ direction: "column",
902
+ alignItems: "stretch",
903
+ minWidth: 0,
904
+ children: [
905
+ /*#__PURE__*/ jsx(Typography, {
906
+ tag: "span",
907
+ variant: "sigma",
908
+ textColor: "neutral600",
909
+ children: field.label
910
+ }),
911
+ /*#__PURE__*/ jsx(Typography, {
912
+ tag: "span",
913
+ variant: "omega",
914
+ textColor: "neutral800",
915
+ ellipsis: true,
916
+ children: /*#__PURE__*/ jsx(CellValue, {
917
+ type: field.type,
918
+ value: field.value
919
+ })
920
+ })
921
+ ]
922
+ }, field.name))
923
+ })
924
+ ]
925
+ });
926
+ };
927
+ /**
928
+ * The whole card toggles its checkbox. The checkbox is named after the title of the component
929
+ * only, not after the whole preview.
930
+ */ const SelectableInstanceCard = ({ checked, onCheckedChange, renderSummary })=>{
931
+ const id = React.useId();
932
+ const titleId = `${id}-title`;
933
+ return /*#__PURE__*/ jsx(InstanceCard, {
934
+ tag: "label",
935
+ htmlFor: id,
936
+ $selected: checked,
937
+ $interactive: true,
938
+ children: /*#__PURE__*/ jsxs(Flex, {
939
+ tag: "span",
940
+ gap: 3,
941
+ alignItems: "flex-start",
942
+ children: [
943
+ /*#__PURE__*/ jsx(Checkbox, {
944
+ id: id,
945
+ "aria-labelledby": titleId,
946
+ checked: checked,
947
+ onCheckedChange: (value)=>onCheckedChange(value === true)
948
+ }),
949
+ renderSummary(titleId)
950
+ ]
951
+ })
952
+ });
953
+ };
954
+ /* -------------------------------------------------------------------------------------------------
955
+ * Styles
956
+ * -----------------------------------------------------------------------------------------------*/ const FooterWarning = ({ children })=>/*#__PURE__*/ jsxs(Flex, {
957
+ gap: 2,
958
+ alignItems: "center",
959
+ color: "warning600",
960
+ children: [
961
+ /*#__PURE__*/ jsx(WarningCircle, {
962
+ width: "1.6rem",
963
+ height: "1.6rem",
964
+ "aria-hidden": true
965
+ }),
966
+ /*#__PURE__*/ jsx(Typography, {
967
+ variant: "pi",
968
+ textColor: "warning600",
969
+ children: children
970
+ })
971
+ ]
972
+ });
973
+ const ModalContent = styled(Modal.Content)`
974
+ width: 92rem;
975
+ max-width: calc(100% - ${({ theme })=>theme.spaces[8]});
976
+ height: 60rem;
977
+ `;
978
+ const ModalFooter = styled(Modal.Footer)`
979
+ flex: 0 0 auto;
980
+ `;
981
+ const Panes = styled.div`
982
+ display: flex;
983
+ flex-direction: column;
984
+ flex: 1;
985
+ min-height: 0;
986
+
987
+ ${({ theme })=>theme.breakpoints.medium} {
988
+ flex-direction: row;
989
+ }
990
+ `;
991
+ const EntriesPaneRoot = styled(Flex)`
992
+ flex: 0 0 auto;
993
+ max-height: 40%;
994
+ min-height: 0;
995
+ border-bottom: 1px solid ${({ theme })=>theme.colors.neutral150};
996
+
997
+ ${({ theme })=>theme.breakpoints.medium} {
998
+ width: 30rem;
999
+ max-height: none;
1000
+ border-bottom: 0;
1001
+ border-right: 1px solid ${({ theme })=>theme.colors.neutral150};
1002
+ }
1003
+ `;
1004
+ const PreviewPane = styled(Flex)`
1005
+ flex: 1;
1006
+ min-width: 0;
1007
+ min-height: 0;
1008
+ `;
1009
+ const Scrollable = styled(Flex)`
1010
+ flex: 1;
1011
+ min-height: 0;
1012
+ overflow-y: auto;
1013
+ `;
1014
+ const PreviewHeader = styled(Flex)`
1015
+ border-bottom: 1px solid ${({ theme })=>theme.colors.neutral150};
1016
+ `;
1017
+ const EntryButton = styled.button`
1018
+ display: flex;
1019
+ flex-direction: column;
1020
+ align-items: flex-start;
1021
+ gap: ${({ theme })=>theme.spaces[1]};
1022
+ width: 100%;
1023
+ border: 1px solid transparent;
1024
+ border-radius: ${({ theme })=>theme.borderRadius};
1025
+ background: transparent;
1026
+ color: ${({ theme })=>theme.colors.neutral800};
1027
+ padding: ${({ theme })=>theme.spaces[2]} ${({ theme })=>theme.spaces[3]};
1028
+ text-align: left;
1029
+ cursor: pointer;
1030
+
1031
+ &:not(:disabled):not([aria-disabled='true']):hover {
1032
+ background: ${({ theme })=>theme.colors.neutral100};
1033
+ }
1034
+
1035
+ &:not(:disabled):focus-visible {
1036
+ outline: 2px solid ${({ theme })=>theme.colors.primary600};
1037
+ outline-offset: -2px;
1038
+ }
1039
+
1040
+ &[aria-pressed='true'] {
1041
+ border-color: ${({ theme })=>theme.colors.primary200};
1042
+ background: ${({ theme })=>theme.colors.primary100};
1043
+ }
1044
+
1045
+ &:disabled,
1046
+ &[aria-disabled='true'] {
1047
+ cursor: not-allowed;
1048
+ color: ${({ theme })=>theme.colors.neutral500};
1049
+ }
1050
+ `;
1051
+ const InstanceCard = styled(Box)`
1052
+ display: block;
1053
+ border: 1px solid
1054
+ ${({ theme, $selected })=>$selected ? theme.colors.primary600 : theme.colors.neutral200};
1055
+ border-radius: ${({ theme })=>theme.borderRadius};
1056
+ background: ${({ theme, $selected })=>$selected ? theme.colors.primary100 : theme.colors.neutral100};
1057
+ padding: ${({ theme })=>theme.spaces[4]};
1058
+ cursor: ${({ $interactive })=>$interactive ? 'pointer' : 'default'};
1059
+
1060
+ @media (prefers-reduced-motion: no-preference) {
1061
+ transition:
1062
+ border-color 120ms ${({ theme })=>theme.motion.easings.easeOutQuad},
1063
+ background-color 120ms ${({ theme })=>theme.motion.easings.easeOutQuad};
1064
+ }
1065
+
1066
+ &:hover {
1067
+ border-color: ${({ theme, $interactive, $selected })=>$interactive && !$selected ? theme.colors.primary200 : undefined};
1068
+ }
1069
+ `;
1070
+ const PreviewGrid = styled(Box)`
1071
+ display: grid;
1072
+ grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
1073
+ gap: ${({ theme })=>theme.spaces[2]} ${({ theme })=>theme.spaces[4]};
1074
+ `;
1075
+
1076
+ export { ComponentCopyModal, getEntryMeta };
1077
+ //# sourceMappingURL=ComponentCopyModal.mjs.map