@wordpress/editor 13.30.0 → 13.32.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 (219) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +857 -0
  3. package/build/bindings/index.js +3 -1
  4. package/build/bindings/index.js.map +1 -1
  5. package/build/components/block-settings-menu/plugin-block-settings-menu-item.js +107 -0
  6. package/build/components/block-settings-menu/plugin-block-settings-menu-item.js.map +1 -0
  7. package/build/components/commands/index.js +1 -1
  8. package/build/components/commands/index.js.map +1 -1
  9. package/build/components/deprecated.js +158 -0
  10. package/build/components/deprecated.js.map +1 -1
  11. package/build/components/document-bar/index.js +7 -10
  12. package/build/components/document-bar/index.js.map +1 -1
  13. package/build/components/document-outline/index.js +1 -1
  14. package/build/components/document-outline/index.js.map +1 -1
  15. package/build/components/editor-canvas/edit-template-blocks-notification.js +2 -39
  16. package/build/components/editor-canvas/edit-template-blocks-notification.js.map +1 -1
  17. package/build/components/editor-canvas/index.js +3 -0
  18. package/build/components/editor-canvas/index.js.map +1 -1
  19. package/build/components/entities-saved-states/hooks/use-is-dirty.js +10 -16
  20. package/build/components/entities-saved-states/hooks/use-is-dirty.js.map +1 -1
  21. package/build/components/entities-saved-states/index.js +28 -88
  22. package/build/components/entities-saved-states/index.js.map +1 -1
  23. package/build/components/error-boundary/index.native.js +133 -0
  24. package/build/components/error-boundary/index.native.js.map +1 -0
  25. package/build/components/index.js +33 -8
  26. package/build/components/index.js.map +1 -1
  27. package/build/components/index.native.js +9 -1
  28. package/build/components/index.native.js.map +1 -1
  29. package/build/components/inserter-sidebar/index.js +5 -1
  30. package/build/components/inserter-sidebar/index.js.map +1 -1
  31. package/build/components/list-view-sidebar/index.js +2 -1
  32. package/build/components/list-view-sidebar/index.js.map +1 -1
  33. package/build/components/pattern-overrides-panel/index.js +30 -0
  34. package/build/components/pattern-overrides-panel/index.js.map +1 -0
  35. package/build/components/plugin-document-setting-panel/index.js +123 -0
  36. package/build/components/plugin-document-setting-panel/index.js.map +1 -0
  37. package/build/components/plugin-post-publish-panel/index.js +68 -0
  38. package/build/components/plugin-post-publish-panel/index.js.map +1 -0
  39. package/build/components/plugin-pre-publish-panel/index.js +71 -0
  40. package/build/components/plugin-pre-publish-panel/index.js.map +1 -0
  41. package/build/components/post-actions/actions.js +455 -0
  42. package/build/components/post-actions/actions.js.map +1 -0
  43. package/build/components/post-card-panel/index.js +93 -0
  44. package/build/components/post-card-panel/index.js.map +1 -0
  45. package/build/components/post-featured-image/index.js +3 -8
  46. package/build/components/post-featured-image/index.js.map +1 -1
  47. package/build/components/post-featured-image/panel.js +7 -3
  48. package/build/components/post-featured-image/panel.js.map +1 -1
  49. package/build/components/post-sync-status/index.js +0 -72
  50. package/build/components/post-sync-status/index.js.map +1 -1
  51. package/build/components/post-taxonomies/flat-term-selector.js +7 -3
  52. package/build/components/post-taxonomies/flat-term-selector.js.map +1 -1
  53. package/build/components/post-title/index.native.js +1 -1
  54. package/build/components/post-title/index.native.js.map +1 -1
  55. package/build/components/provider/disable-non-page-content-blocks.js +36 -20
  56. package/build/components/provider/disable-non-page-content-blocks.js.map +1 -1
  57. package/build/components/provider/index.js +1 -1
  58. package/build/components/provider/index.js.map +1 -1
  59. package/build/components/provider/use-block-editor-settings.js +8 -9
  60. package/build/components/provider/use-block-editor-settings.js.map +1 -1
  61. package/build/components/provider/use-hide-blocks-from-inserter.js +4 -3
  62. package/build/components/provider/use-hide-blocks-from-inserter.js.map +1 -1
  63. package/build/components/template-areas/index.js +70 -0
  64. package/build/components/template-areas/index.js.map +1 -0
  65. package/build/hooks/use-select-nearest-editable-block.js +87 -0
  66. package/build/hooks/use-select-nearest-editable-block.js.map +1 -0
  67. package/build/private-apis.js +6 -2
  68. package/build/private-apis.js.map +1 -1
  69. package/build/store/actions.js +46 -6
  70. package/build/store/actions.js.map +1 -1
  71. package/build/store/constants.js +3 -1
  72. package/build/store/constants.js.map +1 -1
  73. package/build/store/private-actions.js +80 -1
  74. package/build/store/private-actions.js.map +1 -1
  75. package/build/store/private-selectors.js +56 -3
  76. package/build/store/private-selectors.js.map +1 -1
  77. package/build/store/reducer.js +14 -1
  78. package/build/store/reducer.js.map +1 -1
  79. package/build/store/selectors.js +21 -11
  80. package/build/store/selectors.js.map +1 -1
  81. package/build/store/utils/get-filtered-template-parts.js +71 -0
  82. package/build/store/utils/get-filtered-template-parts.js.map +1 -0
  83. package/build-module/bindings/index.js +3 -1
  84. package/build-module/bindings/index.js.map +1 -1
  85. package/build-module/components/block-settings-menu/plugin-block-settings-menu-item.js +100 -0
  86. package/build-module/components/block-settings-menu/plugin-block-settings-menu-item.js.map +1 -0
  87. package/build-module/components/commands/index.js +1 -1
  88. package/build-module/components/commands/index.js.map +1 -1
  89. package/build-module/components/deprecated.js +159 -0
  90. package/build-module/components/deprecated.js.map +1 -1
  91. package/build-module/components/document-bar/index.js +8 -11
  92. package/build-module/components/document-bar/index.js.map +1 -1
  93. package/build-module/components/document-outline/index.js +1 -1
  94. package/build-module/components/document-outline/index.js.map +1 -1
  95. package/build-module/components/editor-canvas/edit-template-blocks-notification.js +4 -41
  96. package/build-module/components/editor-canvas/edit-template-blocks-notification.js.map +1 -1
  97. package/build-module/components/editor-canvas/index.js +3 -0
  98. package/build-module/components/editor-canvas/index.js.map +1 -1
  99. package/build-module/components/entities-saved-states/hooks/use-is-dirty.js +10 -16
  100. package/build-module/components/entities-saved-states/hooks/use-is-dirty.js.map +1 -1
  101. package/build-module/components/entities-saved-states/index.js +29 -89
  102. package/build-module/components/entities-saved-states/index.js.map +1 -1
  103. package/build-module/components/error-boundary/index.native.js +125 -0
  104. package/build-module/components/error-boundary/index.native.js.map +1 -0
  105. package/build-module/components/index.js +5 -1
  106. package/build-module/components/index.js.map +1 -1
  107. package/build-module/components/index.native.js +1 -0
  108. package/build-module/components/index.native.js.map +1 -1
  109. package/build-module/components/inserter-sidebar/index.js +5 -1
  110. package/build-module/components/inserter-sidebar/index.js.map +1 -1
  111. package/build-module/components/list-view-sidebar/index.js +2 -1
  112. package/build-module/components/list-view-sidebar/index.js.map +1 -1
  113. package/build-module/components/pattern-overrides-panel/index.js +23 -0
  114. package/build-module/components/pattern-overrides-panel/index.js.map +1 -0
  115. package/build-module/components/plugin-document-setting-panel/index.js +115 -0
  116. package/build-module/components/plugin-document-setting-panel/index.js.map +1 -0
  117. package/build-module/components/plugin-post-publish-panel/index.js +61 -0
  118. package/build-module/components/plugin-post-publish-panel/index.js.map +1 -0
  119. package/build-module/components/plugin-pre-publish-panel/index.js +64 -0
  120. package/build-module/components/plugin-pre-publish-panel/index.js.map +1 -0
  121. package/build-module/components/post-actions/actions.js +444 -0
  122. package/build-module/components/post-actions/actions.js.map +1 -0
  123. package/build-module/components/post-card-panel/index.js +85 -0
  124. package/build-module/components/post-card-panel/index.js.map +1 -0
  125. package/build-module/components/post-featured-image/index.js +4 -9
  126. package/build-module/components/post-featured-image/index.js.map +1 -1
  127. package/build-module/components/post-featured-image/panel.js +6 -2
  128. package/build-module/components/post-featured-image/panel.js.map +1 -1
  129. package/build-module/components/post-sync-status/index.js +2 -73
  130. package/build-module/components/post-sync-status/index.js.map +1 -1
  131. package/build-module/components/post-taxonomies/flat-term-selector.js +7 -3
  132. package/build-module/components/post-taxonomies/flat-term-selector.js.map +1 -1
  133. package/build-module/components/post-title/index.native.js +1 -1
  134. package/build-module/components/post-title/index.native.js.map +1 -1
  135. package/build-module/components/provider/disable-non-page-content-blocks.js +36 -20
  136. package/build-module/components/provider/disable-non-page-content-blocks.js.map +1 -1
  137. package/build-module/components/provider/index.js +1 -1
  138. package/build-module/components/provider/index.js.map +1 -1
  139. package/build-module/components/provider/use-block-editor-settings.js +9 -10
  140. package/build-module/components/provider/use-block-editor-settings.js.map +1 -1
  141. package/build-module/components/provider/use-hide-blocks-from-inserter.js +4 -3
  142. package/build-module/components/provider/use-hide-blocks-from-inserter.js.map +1 -1
  143. package/build-module/components/template-areas/index.js +63 -0
  144. package/build-module/components/template-areas/index.js.map +1 -0
  145. package/build-module/hooks/use-select-nearest-editable-block.js +80 -0
  146. package/build-module/hooks/use-select-nearest-editable-block.js.map +1 -0
  147. package/build-module/private-apis.js +6 -2
  148. package/build-module/private-apis.js.map +1 -1
  149. package/build-module/store/actions.js +37 -3
  150. package/build-module/store/actions.js.map +1 -1
  151. package/build-module/store/constants.js +2 -0
  152. package/build-module/store/constants.js.map +1 -1
  153. package/build-module/store/private-actions.js +78 -0
  154. package/build-module/store/private-actions.js.map +1 -1
  155. package/build-module/store/private-selectors.js +54 -3
  156. package/build-module/store/private-selectors.js.map +1 -1
  157. package/build-module/store/reducer.js +13 -1
  158. package/build-module/store/reducer.js.map +1 -1
  159. package/build-module/store/selectors.js +19 -10
  160. package/build-module/store/selectors.js.map +1 -1
  161. package/build-module/store/utils/get-filtered-template-parts.js +64 -0
  162. package/build-module/store/utils/get-filtered-template-parts.js.map +1 -0
  163. package/build-style/style-rtl.css +76 -33
  164. package/build-style/style.css +76 -33
  165. package/package.json +35 -33
  166. package/src/bindings/index.js +4 -1
  167. package/src/components/block-settings-menu/plugin-block-settings-menu-item.js +108 -0
  168. package/src/components/commands/index.js +1 -1
  169. package/src/components/deprecated.js +157 -0
  170. package/src/components/document-bar/index.js +12 -17
  171. package/src/components/document-bar/style.scss +9 -12
  172. package/src/components/document-outline/index.js +2 -1
  173. package/src/components/document-tools/style.scss +4 -11
  174. package/src/components/editor-canvas/edit-template-blocks-notification.js +6 -56
  175. package/src/components/editor-canvas/index.js +4 -0
  176. package/src/components/entities-saved-states/hooks/use-is-dirty.js +18 -22
  177. package/src/components/entities-saved-states/index.js +45 -121
  178. package/src/components/entities-saved-states/test/use-is-dirty.js +3 -0
  179. package/src/components/error-boundary/index.native.js +192 -0
  180. package/src/components/error-boundary/style.native.scss +116 -0
  181. package/src/components/index.js +5 -4
  182. package/src/components/index.native.js +1 -0
  183. package/src/components/inserter-sidebar/index.js +7 -1
  184. package/src/components/list-view-sidebar/index.js +1 -0
  185. package/src/components/list-view-sidebar/style.scss +1 -1
  186. package/src/components/pattern-overrides-panel/index.js +26 -0
  187. package/src/components/plugin-document-setting-panel/index.js +121 -0
  188. package/src/components/plugin-post-publish-panel/index.js +64 -0
  189. package/src/components/plugin-post-publish-panel/test/__snapshots__/index.js.snap +39 -0
  190. package/src/components/plugin-post-publish-panel/test/index.js +33 -0
  191. package/src/components/plugin-pre-publish-panel/index.js +67 -0
  192. package/src/components/plugin-pre-publish-panel/test/index.js +33 -0
  193. package/src/components/post-actions/actions.js +582 -0
  194. package/src/components/post-card-panel/index.js +108 -0
  195. package/src/components/post-card-panel/style.scss +32 -0
  196. package/src/components/post-featured-image/index.js +6 -15
  197. package/src/components/post-featured-image/panel.js +9 -3
  198. package/src/components/post-featured-image/style.scss +9 -13
  199. package/src/components/post-sync-status/index.js +1 -94
  200. package/src/components/post-taxonomies/flat-term-selector.js +13 -8
  201. package/src/components/post-title/index.native.js +1 -1
  202. package/src/components/provider/disable-non-page-content-blocks.js +40 -20
  203. package/src/components/provider/index.js +1 -1
  204. package/src/components/provider/test/disable-non-page-content-blocks.js +35 -14
  205. package/src/components/provider/use-block-editor-settings.js +11 -11
  206. package/src/components/provider/use-hide-blocks-from-inserter.js +5 -3
  207. package/src/components/template-areas/index.js +85 -0
  208. package/src/components/template-areas/style.scss +23 -0
  209. package/src/hooks/use-select-nearest-editable-block.js +95 -0
  210. package/src/private-apis.js +6 -2
  211. package/src/store/actions.js +37 -3
  212. package/src/store/constants.js +2 -0
  213. package/src/store/private-actions.js +111 -0
  214. package/src/store/private-selectors.js +105 -17
  215. package/src/store/reducer.js +13 -0
  216. package/src/store/selectors.js +50 -40
  217. package/src/store/utils/get-filtered-template-parts.js +69 -0
  218. package/src/store/utils/test/get-filtered-template-parts.js +189 -0
  219. package/src/style.scss +2 -0
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { useSelect, useDispatch } from '@wordpress/data';
5
- import { useEffect, useState, useRef } from '@wordpress/element';
6
- import { store as noticesStore } from '@wordpress/notices';
4
+ import { useSelect } from '@wordpress/data';
5
+ import { useEffect, useState } from '@wordpress/element';
7
6
  import { __ } from '@wordpress/i18n';
8
7
  import { __experimentalConfirmDialog as ConfirmDialog } from '@wordpress/components';
9
8
 
@@ -38,73 +37,22 @@ export default function EditTemplateBlocksNotification( { contentRef } ) {
38
37
  };
39
38
  }, [] );
40
39
 
41
- const { getNotices } = useSelect( noticesStore );
42
-
43
- const { createInfoNotice, removeNotice } = useDispatch( noticesStore );
44
-
45
40
  const [ isDialogOpen, setIsDialogOpen ] = useState( false );
46
41
 
47
- const lastNoticeId = useRef( 0 );
48
-
49
42
  useEffect( () => {
50
- const handleClick = async ( event ) => {
51
- if ( ! event.target.classList.contains( 'is-root-container' ) ) {
52
- return;
53
- }
54
-
55
- const isNoticeAlreadyShowing = getNotices().some(
56
- ( notice ) => notice.id === lastNoticeId.current
57
- );
58
- if ( isNoticeAlreadyShowing ) {
59
- return;
60
- }
61
-
62
- const { notice } = await createInfoNotice(
63
- __( 'Edit your template to edit this block.' ),
64
- {
65
- isDismissible: true,
66
- type: 'snackbar',
67
- actions: [
68
- {
69
- label: __( 'Edit template' ),
70
- onClick: () =>
71
- onNavigateToEntityRecord( {
72
- postId: templateId,
73
- postType: 'wp_template',
74
- } ),
75
- },
76
- ],
77
- }
78
- );
79
- lastNoticeId.current = notice.id;
80
- };
81
-
82
43
  const handleDblClick = ( event ) => {
83
44
  if ( ! event.target.classList.contains( 'is-root-container' ) ) {
84
45
  return;
85
46
  }
86
- if ( lastNoticeId.current ) {
87
- removeNotice( lastNoticeId.current );
88
- }
89
47
  setIsDialogOpen( true );
90
48
  };
91
49
 
92
50
  const canvas = contentRef.current;
93
- canvas?.addEventListener( 'click', handleClick );
94
51
  canvas?.addEventListener( 'dblclick', handleDblClick );
95
52
  return () => {
96
- canvas?.removeEventListener( 'click', handleClick );
97
53
  canvas?.removeEventListener( 'dblclick', handleDblClick );
98
54
  };
99
- }, [
100
- lastNoticeId,
101
- contentRef,
102
- getNotices,
103
- createInfoNotice,
104
- onNavigateToEntityRecord,
105
- templateId,
106
- removeNotice,
107
- ] );
55
+ }, [ contentRef ] );
108
56
 
109
57
  return (
110
58
  <ConfirmDialog
@@ -119,7 +67,9 @@ export default function EditTemplateBlocksNotification( { contentRef } ) {
119
67
  } }
120
68
  onCancel={ () => setIsDialogOpen( false ) }
121
69
  >
122
- { __( 'Edit your template to edit this block.' ) }
70
+ { __(
71
+ 'You’ve tried to select a block that is part of a template, which may be used on other posts and pages.'
72
+ ) }
123
73
  </ConfirmDialog>
124
74
  );
125
75
  }
@@ -29,6 +29,7 @@ import PostTitle from '../post-title';
29
29
  import { store as editorStore } from '../../store';
30
30
  import { unlock } from '../../lock-unlock';
31
31
  import EditTemplateBlocksNotification from './edit-template-blocks-notification';
32
+ import useSelectNearestEditableBlock from '../../hooks/use-select-nearest-editable-block';
32
33
 
33
34
  const {
34
35
  LayoutStyle,
@@ -313,6 +314,9 @@ function EditorCanvas( {
313
314
  useFlashEditableBlocks( {
314
315
  isEnabled: renderingMode === 'template-locked',
315
316
  } ),
317
+ useSelectNearestEditableBlock( {
318
+ isEnabled: renderingMode === 'template-locked',
319
+ } ),
316
320
  ] );
317
321
 
318
322
  return (
@@ -4,29 +4,24 @@
4
4
  import { useSelect } from '@wordpress/data';
5
5
  import { store as coreStore } from '@wordpress/core-data';
6
6
  import { useMemo, useState } from '@wordpress/element';
7
- import { __ } from '@wordpress/i18n';
8
-
9
- const TRANSLATED_SITE_PROPERTIES = {
10
- title: __( 'Title' ),
11
- description: __( 'Tagline' ),
12
- site_logo: __( 'Logo' ),
13
- site_icon: __( 'Icon' ),
14
- show_on_front: __( 'Show on front' ),
15
- page_on_front: __( 'Page on front' ),
16
- posts_per_page: __( 'Maximum posts per page' ),
17
- default_comment_status: __( 'Allow comments on new posts' ),
18
- };
19
7
 
20
8
  export const useIsDirty = () => {
21
- const { editedEntities, siteEdits } = useSelect( ( select ) => {
22
- const { __experimentalGetDirtyEntityRecords, getEntityRecordEdits } =
23
- select( coreStore );
9
+ const { editedEntities, siteEdits, siteEntityConfig } = useSelect(
10
+ ( select ) => {
11
+ const {
12
+ __experimentalGetDirtyEntityRecords,
13
+ getEntityRecordEdits,
14
+ getEntityConfig,
15
+ } = select( coreStore );
24
16
 
25
- return {
26
- editedEntities: __experimentalGetDirtyEntityRecords(),
27
- siteEdits: getEntityRecordEdits( 'root', 'site' ),
28
- };
29
- }, [] );
17
+ return {
18
+ editedEntities: __experimentalGetDirtyEntityRecords(),
19
+ siteEdits: getEntityRecordEdits( 'root', 'site' ),
20
+ siteEntityConfig: getEntityConfig( 'root', 'site' ),
21
+ };
22
+ },
23
+ []
24
+ );
30
25
 
31
26
  const dirtyEntityRecords = useMemo( () => {
32
27
  // Remove site object and decouple into its edited pieces.
@@ -34,18 +29,19 @@ export const useIsDirty = () => {
34
29
  ( record ) => ! ( record.kind === 'root' && record.name === 'site' )
35
30
  );
36
31
 
32
+ const siteEntityLabels = siteEntityConfig?.meta?.labels ?? {};
37
33
  const editedSiteEntities = [];
38
34
  for ( const property in siteEdits ) {
39
35
  editedSiteEntities.push( {
40
36
  kind: 'root',
41
37
  name: 'site',
42
- title: TRANSLATED_SITE_PROPERTIES[ property ] || property,
38
+ title: siteEntityLabels[ property ] || property,
43
39
  property,
44
40
  } );
45
41
  }
46
42
 
47
43
  return [ ...editedEntitiesWithoutSite, ...editedSiteEntities ];
48
- }, [ editedEntities, siteEdits ] );
44
+ }, [ editedEntities, siteEdits, siteEntityConfig ] );
49
45
 
50
46
  // Unchecked entities to be ignored by save function.
51
47
  const [ unselectedEntities, _setUnselectedEntities ] = useState( [] );
@@ -3,38 +3,40 @@
3
3
  */
4
4
  import { Button, Flex, FlexItem } from '@wordpress/components';
5
5
  import { __, _n, sprintf } from '@wordpress/i18n';
6
- import { useSelect, useDispatch } from '@wordpress/data';
7
6
  import {
8
7
  useCallback,
9
8
  useRef,
10
9
  createInterpolateElement,
11
10
  } from '@wordpress/element';
12
- import { store as coreStore } from '@wordpress/core-data';
13
- import { store as blockEditorStore } from '@wordpress/block-editor';
14
- import { __experimentalUseDialog as useDialog } from '@wordpress/compose';
15
- import { store as noticesStore } from '@wordpress/notices';
11
+ import {
12
+ __experimentalUseDialog as useDialog,
13
+ useInstanceId,
14
+ } from '@wordpress/compose';
15
+ import { useDispatch } from '@wordpress/data';
16
16
 
17
17
  /**
18
18
  * Internal dependencies
19
19
  */
20
20
  import EntityTypeList from './entity-type-list';
21
21
  import { useIsDirty } from './hooks/use-is-dirty';
22
-
23
- const PUBLISH_ON_SAVE_ENTITIES = [
24
- {
25
- kind: 'postType',
26
- name: 'wp_navigation',
27
- },
28
- ];
22
+ import { store as editorStore } from '../../store';
23
+ import { unlock } from '../../lock-unlock';
29
24
 
30
25
  function identity( values ) {
31
26
  return values;
32
27
  }
33
28
 
34
- export default function EntitiesSavedStates( { close } ) {
29
+ export default function EntitiesSavedStates( {
30
+ close,
31
+ renderDialog = undefined,
32
+ } ) {
35
33
  const isDirtyProps = useIsDirty();
36
34
  return (
37
- <EntitiesSavedStatesExtensible close={ close } { ...isDirtyProps } />
35
+ <EntitiesSavedStatesExtensible
36
+ close={ close }
37
+ renderDialog={ renderDialog }
38
+ { ...isDirtyProps }
39
+ />
38
40
  );
39
41
  }
40
42
 
@@ -44,25 +46,14 @@ export function EntitiesSavedStatesExtensible( {
44
46
  onSave = identity,
45
47
  saveEnabled: saveEnabledProp = undefined,
46
48
  saveLabel = __( 'Save' ),
47
-
49
+ renderDialog = undefined,
48
50
  dirtyEntityRecords,
49
51
  isDirty,
50
52
  setUnselectedEntities,
51
53
  unselectedEntities,
52
54
  } ) {
53
55
  const saveButtonRef = useRef();
54
- const {
55
- editEntityRecord,
56
- saveEditedEntityRecord,
57
- __experimentalSaveSpecifiedEntityEdits: saveSpecifiedEntityEdits,
58
- } = useDispatch( coreStore );
59
-
60
- const { __unstableMarkLastChangeAsPersistent } =
61
- useDispatch( blockEditorStore );
62
-
63
- const { createSuccessNotice, createErrorNotice, removeNotice } =
64
- useDispatch( noticesStore );
65
-
56
+ const { saveDirtyEntities } = unlock( useDispatch( editorStore ) );
66
57
  // To group entities by type.
67
58
  const partitionedSavables = dirtyEntityRecords.reduce( ( acc, record ) => {
68
59
  const { name } = record;
@@ -88,94 +79,6 @@ export function EntitiesSavedStatesExtensible( {
88
79
  ].filter( Array.isArray );
89
80
 
90
81
  const saveEnabled = saveEnabledProp ?? isDirty;
91
-
92
- const { homeUrl } = useSelect( ( select ) => {
93
- const {
94
- getUnstableBase, // Site index.
95
- } = select( coreStore );
96
- return {
97
- homeUrl: getUnstableBase()?.home,
98
- };
99
- }, [] );
100
-
101
- const saveCheckedEntities = () => {
102
- const saveNoticeId = 'site-editor-save-success';
103
- removeNotice( saveNoticeId );
104
- const entitiesToSave = dirtyEntityRecords.filter(
105
- ( { kind, name, key, property } ) => {
106
- return ! unselectedEntities.some(
107
- ( elt ) =>
108
- elt.kind === kind &&
109
- elt.name === name &&
110
- elt.key === key &&
111
- elt.property === property
112
- );
113
- }
114
- );
115
-
116
- close( entitiesToSave );
117
-
118
- const siteItemsToSave = [];
119
- const pendingSavedRecords = [];
120
- entitiesToSave.forEach( ( { kind, name, key, property } ) => {
121
- if ( 'root' === kind && 'site' === name ) {
122
- siteItemsToSave.push( property );
123
- } else {
124
- if (
125
- PUBLISH_ON_SAVE_ENTITIES.some(
126
- ( typeToPublish ) =>
127
- typeToPublish.kind === kind &&
128
- typeToPublish.name === name
129
- )
130
- ) {
131
- editEntityRecord( kind, name, key, { status: 'publish' } );
132
- }
133
-
134
- pendingSavedRecords.push(
135
- saveEditedEntityRecord( kind, name, key )
136
- );
137
- }
138
- } );
139
- if ( siteItemsToSave.length ) {
140
- pendingSavedRecords.push(
141
- saveSpecifiedEntityEdits(
142
- 'root',
143
- 'site',
144
- undefined,
145
- siteItemsToSave
146
- )
147
- );
148
- }
149
-
150
- __unstableMarkLastChangeAsPersistent();
151
-
152
- Promise.all( pendingSavedRecords )
153
- .then( ( values ) => {
154
- return onSave( values );
155
- } )
156
- .then( ( values ) => {
157
- if (
158
- values.some( ( value ) => typeof value === 'undefined' )
159
- ) {
160
- createErrorNotice( __( 'Saving failed.' ) );
161
- } else {
162
- createSuccessNotice( __( 'Site updated.' ), {
163
- type: 'snackbar',
164
- id: saveNoticeId,
165
- actions: [
166
- {
167
- label: __( 'View site' ),
168
- url: homeUrl,
169
- },
170
- ],
171
- } );
172
- }
173
- } )
174
- .catch( ( error ) =>
175
- createErrorNotice( `${ __( 'Saving failed.' ) } ${ error }` )
176
- );
177
- };
178
-
179
82
  // Explicitly define this with no argument passed. Using `close` on
180
83
  // its own will use the event object in place of the expected saved entities.
181
84
  const dismissPanel = useCallback( () => close(), [ close ] );
@@ -183,12 +86,20 @@ export function EntitiesSavedStatesExtensible( {
183
86
  const [ saveDialogRef, saveDialogProps ] = useDialog( {
184
87
  onClose: () => dismissPanel(),
185
88
  } );
89
+ const dialogLabel = useInstanceId( EntitiesSavedStatesExtensible, 'label' );
90
+ const dialogDescription = useInstanceId(
91
+ EntitiesSavedStatesExtensible,
92
+ 'description'
93
+ );
186
94
 
187
95
  return (
188
96
  <div
189
97
  ref={ saveDialogRef }
190
98
  { ...saveDialogProps }
191
99
  className="entities-saved-states__panel"
100
+ role={ renderDialog ? 'dialog' : undefined }
101
+ aria-labelledby={ renderDialog ? dialogLabel : undefined }
102
+ aria-describedby={ renderDialog ? dialogDescription : undefined }
192
103
  >
193
104
  <Flex className="entities-saved-states__panel-header" gap={ 2 }>
194
105
  <FlexItem
@@ -197,7 +108,15 @@ export function EntitiesSavedStatesExtensible( {
197
108
  ref={ saveButtonRef }
198
109
  variant="primary"
199
110
  disabled={ ! saveEnabled }
200
- onClick={ saveCheckedEntities }
111
+ __experimentalIsFocusable
112
+ onClick={ () =>
113
+ saveDirtyEntities( {
114
+ onSave,
115
+ dirtyEntityRecords,
116
+ entitiesToSkip: unselectedEntities,
117
+ close,
118
+ } )
119
+ }
201
120
  className="editor-entities-saved-states__save-button"
202
121
  >
203
122
  { saveLabel }
@@ -213,11 +132,16 @@ export function EntitiesSavedStatesExtensible( {
213
132
  </Flex>
214
133
 
215
134
  <div className="entities-saved-states__text-prompt">
216
- <strong className="entities-saved-states__text-prompt--header">
217
- { __( 'Are you ready to save?' ) }
218
- </strong>
219
- { additionalPrompt }
220
- <p>
135
+ <div
136
+ className="entities-saved-states__text-prompt--header-wrapper"
137
+ id={ renderDialog ? dialogLabel : undefined }
138
+ >
139
+ <strong className="entities-saved-states__text-prompt--header">
140
+ { __( 'Are you ready to save?' ) }
141
+ </strong>
142
+ { additionalPrompt }
143
+ </div>
144
+ <p id={ renderDialog ? dialogDescription : undefined }>
221
145
  { isDirty
222
146
  ? createInterpolateElement(
223
147
  sprintf(
@@ -32,6 +32,9 @@ jest.mock( '@wordpress/data', () => {
32
32
  getEntityRecordEdits: jest.fn().mockReturnValue( {
33
33
  title: 'My Site',
34
34
  } ),
35
+ getEntityConfig: jest.fn().mockReturnValue( {
36
+ meta: { labels: { title: 'Title' } },
37
+ } ),
35
38
  };
36
39
  };
37
40
  return fn( select );
@@ -0,0 +1,192 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
5
+ import Clipboard from '@react-native-clipboard/clipboard';
6
+ import { SafeAreaView } from 'react-native-safe-area-context';
7
+
8
+ /**
9
+ * WordPress dependencies
10
+ */
11
+ import { Component } from '@wordpress/element';
12
+ import { __ } from '@wordpress/i18n';
13
+ import { select } from '@wordpress/data';
14
+ import { logException } from '@wordpress/react-native-bridge';
15
+ import {
16
+ usePreferredColorSchemeStyle,
17
+ withPreferredColorScheme,
18
+ } from '@wordpress/compose';
19
+ import { warning } from '@wordpress/icons';
20
+ import { Icon } from '@wordpress/components';
21
+
22
+ /**
23
+ * Internal dependencies
24
+ */
25
+ import { store as editorStore } from '../../store';
26
+ import styles from './style.scss';
27
+
28
+ function getContent() {
29
+ try {
30
+ // While `select` in a component is generally discouraged, it is
31
+ // used here because it (a) reduces the chance of data loss in the
32
+ // case of additional errors by performing a direct retrieval and
33
+ // (b) avoids the performance cost associated with unnecessary
34
+ // content serialization throughout the lifetime of a non-erroring
35
+ // application.
36
+ return select( editorStore ).getEditedPostContent();
37
+ } catch ( error ) {}
38
+ }
39
+
40
+ function CopyButton( {
41
+ text,
42
+ label,
43
+ accessibilityLabel,
44
+ accessibilityHint,
45
+ secondary = false,
46
+ } ) {
47
+ const containerStyle = usePreferredColorSchemeStyle(
48
+ styles[ 'copy-button__container' ],
49
+ styles[ 'copy-button__container--dark' ]
50
+ );
51
+
52
+ const containerSecondaryStyle = usePreferredColorSchemeStyle(
53
+ styles[ 'copy-button__container--secondary' ],
54
+ styles[ 'copy-button__container--secondary-dark' ]
55
+ );
56
+
57
+ const textStyle = usePreferredColorSchemeStyle(
58
+ styles[ 'copy-button__text' ],
59
+ styles[ 'copy-button__text--dark' ]
60
+ );
61
+
62
+ const textSecondaryStyle = usePreferredColorSchemeStyle(
63
+ styles[ 'copy-button__text--secondary' ],
64
+ styles[ 'copy-button__text--secondary-dark' ]
65
+ );
66
+
67
+ return (
68
+ <TouchableOpacity
69
+ activeOpacity={ 0.5 }
70
+ accessibilityLabel={ accessibilityLabel }
71
+ style={ [ containerStyle, secondary && containerSecondaryStyle ] }
72
+ accessibilityRole={ 'button' }
73
+ accessibilityHint={ accessibilityHint }
74
+ onPress={ () => {
75
+ Clipboard.setString(
76
+ typeof text === 'function' ? text() : text || ''
77
+ );
78
+ } }
79
+ >
80
+ <Text style={ [ textStyle, secondary && textSecondaryStyle ] }>
81
+ { label }
82
+ </Text>
83
+ </TouchableOpacity>
84
+ );
85
+ }
86
+
87
+ class ErrorBoundary extends Component {
88
+ constructor() {
89
+ super( ...arguments );
90
+
91
+ this.state = {
92
+ error: null,
93
+ };
94
+ }
95
+
96
+ componentDidCatch( error ) {
97
+ logException( error, {
98
+ context: {
99
+ component_stack: error.componentStack,
100
+ },
101
+ isHandled: true,
102
+ handledBy: 'Editor-level Error Boundary',
103
+ } );
104
+ }
105
+
106
+ static getDerivedStateFromError( error ) {
107
+ return { error };
108
+ }
109
+
110
+ render() {
111
+ const { error } = this.state;
112
+ if ( ! error ) {
113
+ return this.props.children;
114
+ }
115
+
116
+ const { getStylesFromColorScheme } = this.props;
117
+
118
+ const iconContainerStyle = getStylesFromColorScheme(
119
+ styles[ 'error-boundary__icon-container' ],
120
+ styles[ 'error-boundary__icon-container--dark' ]
121
+ );
122
+
123
+ const titleStyle = getStylesFromColorScheme(
124
+ styles[ 'error-boundary__title' ],
125
+ styles[ 'error-boundary__title--dark' ]
126
+ );
127
+
128
+ const messageStyle = getStylesFromColorScheme(
129
+ styles[ 'error-boundary__message' ],
130
+ styles[ 'error-boundary__message--dark' ]
131
+ );
132
+
133
+ return (
134
+ <SafeAreaView>
135
+ <ScrollView
136
+ style={ styles[ 'error-boundary__scroll' ] }
137
+ contentContainerStyle={
138
+ styles[ 'error-boundary__scroll-container' ]
139
+ }
140
+ >
141
+ <View style={ styles[ 'error-boundary__container' ] }>
142
+ <View style={ iconContainerStyle }>
143
+ <Icon
144
+ icon={ warning }
145
+ { ...styles[ 'error-boundary__icon' ] }
146
+ />
147
+ </View>
148
+ <Text style={ titleStyle }>
149
+ { __(
150
+ 'The editor has encountered an unexpected error'
151
+ ) }
152
+ </Text>
153
+ <Text style={ messageStyle }>
154
+ { __(
155
+ 'You can copy your post text in case your content is impacted. Copy error details to debug and share with support.'
156
+ ) }
157
+ </Text>
158
+ <View
159
+ style={
160
+ styles[ 'error-boundary__actions-container' ]
161
+ }
162
+ >
163
+ <CopyButton
164
+ label={ __( 'Copy post text' ) }
165
+ accessibilityLabel={ __(
166
+ 'Button to copy post text'
167
+ ) }
168
+ accessibilityHint={ __(
169
+ 'Tap here to copy post text'
170
+ ) }
171
+ text={ getContent }
172
+ />
173
+ <CopyButton
174
+ label={ __( 'Copy error details' ) }
175
+ accessibilityLabel={ __(
176
+ 'Button to copy error details'
177
+ ) }
178
+ accessibilityHint={ __(
179
+ 'Tap here to copy error details'
180
+ ) }
181
+ text={ error.stack }
182
+ secondary
183
+ />
184
+ </View>
185
+ </View>
186
+ </ScrollView>
187
+ </SafeAreaView>
188
+ );
189
+ }
190
+ }
191
+
192
+ export default withPreferredColorScheme( ErrorBoundary );