@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
@@ -0,0 +1,582 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { external, trash, edit, backup } from '@wordpress/icons';
5
+ import { addQueryArgs } from '@wordpress/url';
6
+ import { useDispatch } from '@wordpress/data';
7
+ import { decodeEntities } from '@wordpress/html-entities';
8
+ import { store as coreStore } from '@wordpress/core-data';
9
+ import { __, _n, sprintf } from '@wordpress/i18n';
10
+ import { store as noticesStore } from '@wordpress/notices';
11
+ import { useMemo, useState } from '@wordpress/element';
12
+
13
+ import {
14
+ Button,
15
+ TextControl,
16
+ __experimentalText as Text,
17
+ __experimentalHStack as HStack,
18
+ __experimentalVStack as VStack,
19
+ } from '@wordpress/components';
20
+
21
+ function getItemTitle( item ) {
22
+ if ( typeof item.title === 'string' ) {
23
+ return decodeEntities( item.title );
24
+ }
25
+ return decodeEntities( item.title?.rendered || '' );
26
+ }
27
+
28
+ export const trashPostAction = {
29
+ id: 'move-to-trash',
30
+ label: __( 'Move to Trash' ),
31
+ isPrimary: true,
32
+ icon: trash,
33
+ isEligible( { status } ) {
34
+ return status !== 'trash';
35
+ },
36
+ supportsBulk: true,
37
+ hideModalHeader: true,
38
+ RenderModal: ( { items: posts, closeModal, onActionPerformed } ) => {
39
+ const { createSuccessNotice, createErrorNotice } =
40
+ useDispatch( noticesStore );
41
+ const { deleteEntityRecord } = useDispatch( coreStore );
42
+ return (
43
+ <VStack spacing="5">
44
+ <Text>
45
+ { posts.length === 1
46
+ ? sprintf(
47
+ // translators: %s: The page's title.
48
+ __( 'Are you sure you want to delete "%s"?' ),
49
+ getItemTitle( posts[ 0 ] )
50
+ )
51
+ : sprintf(
52
+ // translators: %d: The number of pages (2 or more).
53
+ _n(
54
+ 'Are you sure you want to delete %d page?',
55
+ 'Are you sure you want to delete %d pages?',
56
+ posts.length
57
+ ),
58
+ posts.length
59
+ ) }
60
+ </Text>
61
+ <HStack justify="right">
62
+ <Button variant="tertiary" onClick={ closeModal }>
63
+ { __( 'Cancel' ) }
64
+ </Button>
65
+ <Button
66
+ variant="primary"
67
+ onClick={ async () => {
68
+ const promiseResult = await Promise.allSettled(
69
+ posts.map( ( post ) => {
70
+ return deleteEntityRecord(
71
+ 'postType',
72
+ post.type,
73
+ post.id,
74
+ {},
75
+ { throwOnError: true }
76
+ );
77
+ } )
78
+ );
79
+ // If all the promises were fulfilled with success.
80
+ if (
81
+ promiseResult.every(
82
+ ( { status } ) => status === 'fulfilled'
83
+ )
84
+ ) {
85
+ let successMessage;
86
+ if ( promiseResult.length === 1 ) {
87
+ successMessage = sprintf(
88
+ /* translators: The posts's title. */
89
+ __( '"%s" moved to the Trash.' ),
90
+ getItemTitle( posts[ 0 ] )
91
+ );
92
+ } else {
93
+ successMessage = __(
94
+ 'Pages moved to the Trash.'
95
+ );
96
+ }
97
+ createSuccessNotice( successMessage, {
98
+ type: 'snackbar',
99
+ id: 'edit-site-page-trashed',
100
+ } );
101
+ } else {
102
+ // If there was at lease one failure.
103
+ let errorMessage;
104
+ // If we were trying to move a single post to the trash.
105
+ if ( promiseResult.length === 1 ) {
106
+ if ( promiseResult[ 0 ].reason?.message ) {
107
+ errorMessage =
108
+ promiseResult[ 0 ].reason.message;
109
+ } else {
110
+ errorMessage = __(
111
+ 'An error occurred while moving the post to the trash.'
112
+ );
113
+ }
114
+ // If we were trying to move multiple posts to the trash
115
+ } else {
116
+ const errorMessages = new Set();
117
+ const failedPromises = promiseResult.filter(
118
+ ( { status } ) => status === 'rejected'
119
+ );
120
+ for ( const failedPromise of failedPromises ) {
121
+ if ( failedPromise.reason?.message ) {
122
+ errorMessages.add(
123
+ failedPromise.reason.message
124
+ );
125
+ }
126
+ }
127
+ if ( errorMessages.size === 0 ) {
128
+ errorMessage = __(
129
+ 'An error occurred while moving the posts to the trash.'
130
+ );
131
+ } else if ( errorMessages.size === 1 ) {
132
+ errorMessage = sprintf(
133
+ /* translators: %s: an error message */
134
+ __(
135
+ 'An error occurred while moving the posts to the trash: %s'
136
+ ),
137
+ [ ...errorMessages ][ 0 ]
138
+ );
139
+ } else {
140
+ errorMessage = sprintf(
141
+ /* translators: %s: a list of comma separated error messages */
142
+ __(
143
+ 'Some errors occurred while moving the pages to the trash: %s'
144
+ ),
145
+ [ ...errorMessages ].join( ',' )
146
+ );
147
+ }
148
+ createErrorNotice( errorMessage, {
149
+ type: 'snackbar',
150
+ } );
151
+ }
152
+ }
153
+ if ( onActionPerformed ) {
154
+ onActionPerformed( posts );
155
+ }
156
+ closeModal();
157
+ } }
158
+ >
159
+ { __( 'Delete' ) }
160
+ </Button>
161
+ </HStack>
162
+ </VStack>
163
+ );
164
+ },
165
+ };
166
+
167
+ export function usePermanentlyDeletePostAction() {
168
+ const { createSuccessNotice, createErrorNotice } =
169
+ useDispatch( noticesStore );
170
+ const { deleteEntityRecord } = useDispatch( coreStore );
171
+
172
+ return useMemo(
173
+ () => ( {
174
+ id: 'permanently-delete',
175
+ label: __( 'Permanently delete' ),
176
+ supportsBulk: true,
177
+ isEligible( { status } ) {
178
+ return status === 'trash';
179
+ },
180
+ async callback( posts, onActionPerformed ) {
181
+ const promiseResult = await Promise.allSettled(
182
+ posts.map( ( post ) => {
183
+ return deleteEntityRecord(
184
+ 'postType',
185
+ post.type,
186
+ post.id,
187
+ { force: true },
188
+ { throwOnError: true }
189
+ );
190
+ } )
191
+ );
192
+ // If all the promises were fulfilled with success.
193
+ if (
194
+ promiseResult.every(
195
+ ( { status } ) => status === 'fulfilled'
196
+ )
197
+ ) {
198
+ let successMessage;
199
+ if ( promiseResult.length === 1 ) {
200
+ successMessage = sprintf(
201
+ /* translators: The posts's title. */
202
+ __( '"%s" permanently deleted.' ),
203
+ getItemTitle( posts[ 0 ] )
204
+ );
205
+ } else {
206
+ successMessage = __(
207
+ 'The posts were permanently deleted.'
208
+ );
209
+ }
210
+ createSuccessNotice( successMessage, {
211
+ type: 'snackbar',
212
+ id: 'edit-site-post-permanently-deleted',
213
+ } );
214
+ if ( onActionPerformed ) {
215
+ onActionPerformed( posts );
216
+ }
217
+ } else {
218
+ // If there was at lease one failure.
219
+ let errorMessage;
220
+ // If we were trying to permanently delete a single post.
221
+ if ( promiseResult.length === 1 ) {
222
+ if ( promiseResult[ 0 ].reason?.message ) {
223
+ errorMessage = promiseResult[ 0 ].reason.message;
224
+ } else {
225
+ errorMessage = __(
226
+ 'An error occurred while permanently deleting the post.'
227
+ );
228
+ }
229
+ // If we were trying to permanently delete multiple posts
230
+ } else {
231
+ const errorMessages = new Set();
232
+ const failedPromises = promiseResult.filter(
233
+ ( { status } ) => status === 'rejected'
234
+ );
235
+ for ( const failedPromise of failedPromises ) {
236
+ if ( failedPromise.reason?.message ) {
237
+ errorMessages.add(
238
+ failedPromise.reason.message
239
+ );
240
+ }
241
+ }
242
+ if ( errorMessages.size === 0 ) {
243
+ errorMessage = __(
244
+ 'An error occurred while permanently deleting the posts.'
245
+ );
246
+ } else if ( errorMessages.size === 1 ) {
247
+ errorMessage = sprintf(
248
+ /* translators: %s: an error message */
249
+ __(
250
+ 'An error occurred while permanently deleting the posts: %s'
251
+ ),
252
+ [ ...errorMessages ][ 0 ]
253
+ );
254
+ } else {
255
+ errorMessage = sprintf(
256
+ /* translators: %s: a list of comma separated error messages */
257
+ __(
258
+ 'Some errors occurred while permanently deleting the posts: %s'
259
+ ),
260
+ [ ...errorMessages ].join( ',' )
261
+ );
262
+ }
263
+ createErrorNotice( errorMessage, {
264
+ type: 'snackbar',
265
+ } );
266
+ }
267
+ }
268
+ },
269
+ } ),
270
+ [ createSuccessNotice, createErrorNotice, deleteEntityRecord ]
271
+ );
272
+ }
273
+
274
+ export function useRestorePostAction() {
275
+ const { createSuccessNotice, createErrorNotice } =
276
+ useDispatch( noticesStore );
277
+ const { editEntityRecord, saveEditedEntityRecord } =
278
+ useDispatch( coreStore );
279
+
280
+ return useMemo(
281
+ () => ( {
282
+ id: 'restore',
283
+ label: __( 'Restore' ),
284
+ isPrimary: true,
285
+ icon: backup,
286
+ supportsBulk: true,
287
+ isEligible( { status } ) {
288
+ return status === 'trash';
289
+ },
290
+ async callback( posts, onActionPerformed ) {
291
+ try {
292
+ for ( const post of posts ) {
293
+ await editEntityRecord(
294
+ 'postType',
295
+ post.type,
296
+ post.id,
297
+ {
298
+ status: 'draft',
299
+ }
300
+ );
301
+ await saveEditedEntityRecord(
302
+ 'postType',
303
+ post.type,
304
+ post.id,
305
+ { throwOnError: true }
306
+ );
307
+ }
308
+
309
+ createSuccessNotice(
310
+ posts.length > 1
311
+ ? sprintf(
312
+ /* translators: The number of posts. */
313
+ __( '%d posts have been restored.' ),
314
+ posts.length
315
+ )
316
+ : sprintf(
317
+ /* translators: The number of posts. */
318
+ __( '"%s" has been restored.' ),
319
+ getItemTitle( posts[ 0 ] )
320
+ ),
321
+ {
322
+ type: 'snackbar',
323
+ id: 'edit-site-post-restored',
324
+ }
325
+ );
326
+ if ( onActionPerformed ) {
327
+ onActionPerformed( posts );
328
+ }
329
+ } catch ( error ) {
330
+ let errorMessage;
331
+ if (
332
+ error.message &&
333
+ error.code !== 'unknown_error' &&
334
+ error.message
335
+ ) {
336
+ errorMessage = error.message;
337
+ } else if ( posts.length > 1 ) {
338
+ errorMessage = __(
339
+ 'An error occurred while restoring the posts.'
340
+ );
341
+ } else {
342
+ errorMessage = __(
343
+ 'An error occurred while restoring the post.'
344
+ );
345
+ }
346
+
347
+ createErrorNotice( errorMessage, { type: 'snackbar' } );
348
+ }
349
+ },
350
+ } ),
351
+ [
352
+ createSuccessNotice,
353
+ createErrorNotice,
354
+ editEntityRecord,
355
+ saveEditedEntityRecord,
356
+ ]
357
+ );
358
+ }
359
+
360
+ export const viewPostAction = {
361
+ id: 'view-post',
362
+ label: __( 'View' ),
363
+ isPrimary: true,
364
+ icon: external,
365
+ isEligible( post ) {
366
+ return post.status !== 'trash';
367
+ },
368
+ callback( posts, onActionPerformed ) {
369
+ const post = posts[ 0 ];
370
+ window.open( post.link, '_blank' );
371
+ if ( onActionPerformed ) {
372
+ onActionPerformed( posts );
373
+ }
374
+ },
375
+ };
376
+
377
+ export const editPostAction = {
378
+ id: 'edit-post',
379
+ label: __( 'Edit' ),
380
+ isPrimary: true,
381
+ icon: edit,
382
+ isEligible( { status } ) {
383
+ return status !== 'trash';
384
+ },
385
+ callback( posts, onActionPerformed ) {
386
+ if ( onActionPerformed ) {
387
+ onActionPerformed( posts );
388
+ }
389
+ },
390
+ };
391
+ export const postRevisionsAction = {
392
+ id: 'view-post-revisions',
393
+ label: __( 'View revisions' ),
394
+ isPrimary: false,
395
+ isEligible: ( post ) => {
396
+ if ( post.status === 'trash' ) {
397
+ return false;
398
+ }
399
+ const lastRevisionId =
400
+ post?._links?.[ 'predecessor-version' ]?.[ 0 ]?.id ?? null;
401
+ const revisionsCount =
402
+ post?._links?.[ 'version-history' ]?.[ 0 ]?.count ?? 0;
403
+ return lastRevisionId && revisionsCount > 1;
404
+ },
405
+ callback( posts, onActionPerformed ) {
406
+ const post = posts[ 0 ];
407
+ const href = addQueryArgs( 'revision.php', {
408
+ revision: post?._links?.[ 'predecessor-version' ]?.[ 0 ]?.id,
409
+ } );
410
+ document.location.href = href;
411
+ if ( onActionPerformed ) {
412
+ onActionPerformed( posts );
413
+ }
414
+ },
415
+ };
416
+
417
+ export const renamePostAction = {
418
+ id: 'rename-post',
419
+ label: __( 'Rename' ),
420
+ isEligible( post ) {
421
+ return post.status !== 'trash';
422
+ },
423
+ RenderModal: ( { items, closeModal } ) => {
424
+ const [ item ] = items;
425
+ const originalTitle = decodeEntities(
426
+ typeof item.title === 'string' ? item.title : item.title.rendered
427
+ );
428
+ const [ title, setTitle ] = useState( () => originalTitle );
429
+ const { editEntityRecord, saveEditedEntityRecord } =
430
+ useDispatch( coreStore );
431
+ const { createSuccessNotice, createErrorNotice } =
432
+ useDispatch( noticesStore );
433
+
434
+ async function onRename( event ) {
435
+ event.preventDefault();
436
+ try {
437
+ await editEntityRecord( 'postType', item.type, item.id, {
438
+ title,
439
+ } );
440
+ // Update state before saving rerenders the list.
441
+ setTitle( '' );
442
+ closeModal();
443
+ // Persist edited entity.
444
+ await saveEditedEntityRecord( 'postType', item.type, item.id, {
445
+ throwOnError: true,
446
+ } );
447
+ createSuccessNotice( __( 'Name updated' ), {
448
+ type: 'snackbar',
449
+ } );
450
+ } catch ( error ) {
451
+ const errorMessage =
452
+ error.message && error.code !== 'unknown_error'
453
+ ? error.message
454
+ : __( 'An error occurred while updating the name' );
455
+ createErrorNotice( errorMessage, { type: 'snackbar' } );
456
+ }
457
+ }
458
+
459
+ return (
460
+ <form onSubmit={ onRename }>
461
+ <VStack spacing="5">
462
+ <TextControl
463
+ __nextHasNoMarginBottom
464
+ __next40pxDefaultSize
465
+ label={ __( 'Name' ) }
466
+ value={ title }
467
+ onChange={ setTitle }
468
+ required
469
+ />
470
+ <HStack justify="right">
471
+ <Button
472
+ __next40pxDefaultSize
473
+ variant="tertiary"
474
+ onClick={ () => {
475
+ closeModal();
476
+ } }
477
+ >
478
+ { __( 'Cancel' ) }
479
+ </Button>
480
+ <Button
481
+ __next40pxDefaultSize
482
+ variant="primary"
483
+ type="submit"
484
+ >
485
+ { __( 'Save' ) }
486
+ </Button>
487
+ </HStack>
488
+ </VStack>
489
+ </form>
490
+ );
491
+ },
492
+ };
493
+
494
+ export function usePostActions( onActionPerformed, actionIds = null ) {
495
+ const permanentlyDeletePostAction = usePermanentlyDeletePostAction();
496
+ const restorePostAction = useRestorePostAction();
497
+ return useMemo(
498
+ () => {
499
+ // By default, return all actions...
500
+ const defaultActions = [
501
+ editPostAction,
502
+ viewPostAction,
503
+ restorePostAction,
504
+ permanentlyDeletePostAction,
505
+ postRevisionsAction,
506
+ renamePostAction,
507
+ trashPostAction,
508
+ ];
509
+
510
+ // ... unless `actionIds` was specified, in which case we find the
511
+ // actions matching the given IDs.
512
+ const actions = actionIds
513
+ ? actionIds.map( ( actionId ) =>
514
+ defaultActions.find( ( { id } ) => actionId === id )
515
+ )
516
+ : defaultActions;
517
+
518
+ if ( onActionPerformed ) {
519
+ for ( let i = 0; i < actions.length; ++i ) {
520
+ if ( actions[ i ].callback ) {
521
+ const existingCallback = actions[ i ].callback;
522
+ actions[ i ] = {
523
+ ...actions[ i ],
524
+ callback: ( items, _onActionPerformed ) => {
525
+ existingCallback( items, ( _items ) => {
526
+ if ( _onActionPerformed ) {
527
+ _onActionPerformed( _items );
528
+ }
529
+ onActionPerformed(
530
+ actions[ i ].id,
531
+ _items
532
+ );
533
+ } );
534
+ },
535
+ };
536
+ }
537
+ if ( actions[ i ].RenderModal ) {
538
+ const ExistingRenderModal = actions[ i ].RenderModal;
539
+ actions[ i ] = {
540
+ ...actions[ i ],
541
+ RenderModal: ( props ) => {
542
+ return (
543
+ <ExistingRenderModal
544
+ items={ props.items }
545
+ closeModal={ props.closeModal }
546
+ onActionPerformed={ ( _items ) => {
547
+ if ( props.onActionPerformed ) {
548
+ props.onActionPerformed(
549
+ _items
550
+ );
551
+ }
552
+ onActionPerformed(
553
+ actions[ i ].id,
554
+ _items
555
+ );
556
+ } }
557
+ />
558
+ );
559
+ },
560
+ };
561
+ }
562
+ }
563
+ }
564
+ return actions;
565
+ },
566
+
567
+ // Disable reason: if provided, `actionIds` is a shallow array of
568
+ // strings, and the strings themselves should be part of the useMemo
569
+ // dependencies. Two different disable statements are needed, as the
570
+ // first flags what it thinks are missing dependencies, and the second
571
+ // flags the array spread operation.
572
+ //
573
+ // eslint-disable-next-line react-hooks/exhaustive-deps
574
+ [
575
+ // eslint-disable-next-line react-hooks/exhaustive-deps
576
+ ...( actionIds || [] ),
577
+ permanentlyDeletePostAction,
578
+ restorePostAction,
579
+ onActionPerformed,
580
+ ]
581
+ );
582
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import classnames from 'classnames';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import {
10
+ Icon,
11
+ __experimentalHStack as HStack,
12
+ __experimentalVStack as VStack,
13
+ __experimentalText as Text,
14
+ PanelBody,
15
+ } from '@wordpress/components';
16
+ import { store as coreStore } from '@wordpress/core-data';
17
+ import { useSelect } from '@wordpress/data';
18
+ import { __, sprintf } from '@wordpress/i18n';
19
+ import { humanTimeDiff } from '@wordpress/date';
20
+ import { decodeEntities } from '@wordpress/html-entities';
21
+
22
+ /**
23
+ * Internal dependencies
24
+ */
25
+ import { store as editorStore } from '../../store';
26
+ import { TEMPLATE_POST_TYPE } from '../../store/constants';
27
+ import { unlock } from '../../lock-unlock';
28
+ import TemplateAreas from '../template-areas';
29
+
30
+ export default function PostCardPanel( { className, actions } ) {
31
+ const { modified, title, templateInfo, icon, postType } = useSelect(
32
+ ( select ) => {
33
+ const {
34
+ getEditedPostAttribute,
35
+ getCurrentPostType,
36
+ getCurrentPostId,
37
+ __experimentalGetTemplateInfo,
38
+ } = select( editorStore );
39
+ const { getEditedEntityRecord } = select( coreStore );
40
+ const _type = getCurrentPostType();
41
+ const _id = getCurrentPostId();
42
+ const _record = getEditedEntityRecord( 'postType', _type, _id );
43
+ const _templateInfo = __experimentalGetTemplateInfo( _record );
44
+ return {
45
+ title:
46
+ _templateInfo?.title || getEditedPostAttribute( 'title' ),
47
+ modified: getEditedPostAttribute( 'modified' ),
48
+ id: _id,
49
+ postType: _type,
50
+ templateInfo: _templateInfo,
51
+ icon: unlock( select( editorStore ) ).getPostIcon( _type, {
52
+ area: _record?.area,
53
+ } ),
54
+ };
55
+ }
56
+ );
57
+ const description = templateInfo?.description;
58
+ const lastEditedText =
59
+ modified &&
60
+ sprintf(
61
+ // translators: %s: Human-readable time difference, e.g. "2 days ago".
62
+ __( 'Last edited %s.' ),
63
+ humanTimeDiff( modified )
64
+ );
65
+
66
+ return (
67
+ <PanelBody>
68
+ <div
69
+ className={ classnames( 'editor-post-card-panel', className ) }
70
+ >
71
+ <HStack
72
+ spacing={ 2 }
73
+ className="editor-post-card-panel__header"
74
+ align="flex-start"
75
+ >
76
+ <Icon
77
+ className="editor-post-card-panel__icon"
78
+ icon={ icon }
79
+ />
80
+ <Text
81
+ numberOfLines={ 2 }
82
+ truncate
83
+ className="editor-post-card-panel__title"
84
+ weight={ 500 }
85
+ as="h2"
86
+ >
87
+ { title ? decodeEntities( title ) : __( 'No Title' ) }
88
+ </Text>
89
+ { actions }
90
+ </HStack>
91
+ <VStack className="editor-post-card-panel__content">
92
+ { ( description || lastEditedText ) && (
93
+ <VStack
94
+ className="editor-post-card-panel__description"
95
+ spacing={ 2 }
96
+ >
97
+ { description && <Text>{ description }</Text> }
98
+ { lastEditedText && (
99
+ <Text>{ lastEditedText }</Text>
100
+ ) }
101
+ </VStack>
102
+ ) }
103
+ { postType === TEMPLATE_POST_TYPE && <TemplateAreas /> }
104
+ </VStack>
105
+ </div>
106
+ </PanelBody>
107
+ );
108
+ }
@@ -0,0 +1,32 @@
1
+ .editor-post-card-panel {
2
+ &__content {
3
+ flex-grow: 1;
4
+ }
5
+
6
+ &__title {
7
+ width: 100%;
8
+
9
+ &.editor-post-card-panel__title {
10
+ margin: 3px 0 0 0;
11
+ }
12
+ }
13
+
14
+ &__icon {
15
+ flex: 0 0 $icon-size;
16
+ width: $icon-size;
17
+ height: $icon-size;
18
+ }
19
+
20
+ &__header {
21
+ display: flex;
22
+ justify-content: space-between;
23
+ margin: 0 0 $grid-unit-10;
24
+ }
25
+
26
+ &__description {
27
+ color: $gray-700;
28
+ & .components-text {
29
+ color: inherit;
30
+ }
31
+ }
32
+ }