@wordpress/editor 14.1.0 → 14.2.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 (128) hide show
  1. package/CHANGELOG.md +14 -12
  2. package/README.md +28 -7
  3. package/build/components/create-template-part-modal/index.js +2 -0
  4. package/build/components/create-template-part-modal/index.js.map +1 -1
  5. package/build/components/document-bar/index.js +11 -4
  6. package/build/components/document-bar/index.js.map +1 -1
  7. package/build/components/editor-interface/index.js +5 -3
  8. package/build/components/editor-interface/index.js.map +1 -1
  9. package/build/components/entities-saved-states/entity-record-item.js +4 -12
  10. package/build/components/entities-saved-states/entity-record-item.js.map +1 -1
  11. package/build/components/global-keyboard-shortcuts/index.js +1 -1
  12. package/build/components/global-keyboard-shortcuts/index.js.map +1 -1
  13. package/build/components/global-styles-provider/index.js +6 -65
  14. package/build/components/global-styles-provider/index.js.map +1 -1
  15. package/build/components/header/index.js +6 -2
  16. package/build/components/header/index.js.map +1 -1
  17. package/build/components/index.js +10 -2
  18. package/build/components/index.js.map +1 -1
  19. package/build/components/post-actions/actions.js +321 -254
  20. package/build/components/post-actions/actions.js.map +1 -1
  21. package/build/components/post-actions/index.js +6 -1
  22. package/build/components/post-actions/index.js.map +1 -1
  23. package/build/components/post-excerpt/index.js +2 -1
  24. package/build/components/post-excerpt/index.js.map +1 -1
  25. package/build/components/post-excerpt/panel.js +2 -1
  26. package/build/components/post-excerpt/panel.js.map +1 -1
  27. package/build/components/post-slug/check.js +8 -1
  28. package/build/components/post-slug/check.js.map +1 -1
  29. package/build/components/post-slug/index.js +6 -0
  30. package/build/components/post-slug/index.js.map +1 -1
  31. package/build/components/post-status/index.js +2 -1
  32. package/build/components/post-status/index.js.map +1 -1
  33. package/build/components/post-sticky/index.js +9 -13
  34. package/build/components/post-sticky/index.js.map +1 -1
  35. package/build/components/post-template/create-new-template-modal.js +2 -0
  36. package/build/components/post-template/create-new-template-modal.js.map +1 -1
  37. package/build/components/sidebar/index.js +2 -1
  38. package/build/components/sidebar/index.js.map +1 -1
  39. package/build/components/sidebar/post-summary.js +1 -2
  40. package/build/components/sidebar/post-summary.js.map +1 -1
  41. package/build/components/template-part-content-panel/index.js +70 -0
  42. package/build/components/template-part-content-panel/index.js.map +1 -0
  43. package/build/hooks/pattern-overrides.js +1 -1
  44. package/build/hooks/pattern-overrides.js.map +1 -1
  45. package/build/lock-unlock.js +1 -1
  46. package/build/lock-unlock.js.map +1 -1
  47. package/build/store/private-actions.js +9 -5
  48. package/build/store/private-actions.js.map +1 -1
  49. package/build-module/components/create-template-part-modal/index.js +2 -0
  50. package/build-module/components/create-template-part-modal/index.js.map +1 -1
  51. package/build-module/components/document-bar/index.js +11 -4
  52. package/build-module/components/document-bar/index.js.map +1 -1
  53. package/build-module/components/editor-interface/index.js +5 -3
  54. package/build-module/components/editor-interface/index.js.map +1 -1
  55. package/build-module/components/entities-saved-states/entity-record-item.js +6 -14
  56. package/build-module/components/entities-saved-states/entity-record-item.js.map +1 -1
  57. package/build-module/components/global-keyboard-shortcuts/index.js +1 -1
  58. package/build-module/components/global-keyboard-shortcuts/index.js.map +1 -1
  59. package/build-module/components/global-styles-provider/index.js +7 -66
  60. package/build-module/components/global-styles-provider/index.js.map +1 -1
  61. package/build-module/components/header/index.js +6 -2
  62. package/build-module/components/header/index.js.map +1 -1
  63. package/build-module/components/index.js +11 -2
  64. package/build-module/components/index.js.map +1 -1
  65. package/build-module/components/post-actions/actions.js +322 -255
  66. package/build-module/components/post-actions/actions.js.map +1 -1
  67. package/build-module/components/post-actions/index.js +6 -1
  68. package/build-module/components/post-actions/index.js.map +1 -1
  69. package/build-module/components/post-excerpt/index.js +2 -1
  70. package/build-module/components/post-excerpt/index.js.map +1 -1
  71. package/build-module/components/post-excerpt/panel.js +2 -1
  72. package/build-module/components/post-excerpt/panel.js.map +1 -1
  73. package/build-module/components/post-slug/check.js +9 -0
  74. package/build-module/components/post-slug/check.js.map +1 -1
  75. package/build-module/components/post-slug/index.js +6 -0
  76. package/build-module/components/post-slug/index.js.map +1 -1
  77. package/build-module/components/post-status/index.js +2 -1
  78. package/build-module/components/post-status/index.js.map +1 -1
  79. package/build-module/components/post-sticky/index.js +10 -14
  80. package/build-module/components/post-sticky/index.js.map +1 -1
  81. package/build-module/components/post-template/create-new-template-modal.js +2 -0
  82. package/build-module/components/post-template/create-new-template-modal.js.map +1 -1
  83. package/build-module/components/sidebar/index.js +2 -1
  84. package/build-module/components/sidebar/index.js.map +1 -1
  85. package/build-module/components/sidebar/post-summary.js +1 -2
  86. package/build-module/components/sidebar/post-summary.js.map +1 -1
  87. package/build-module/components/template-part-content-panel/index.js +63 -0
  88. package/build-module/components/template-part-content-panel/index.js.map +1 -0
  89. package/build-module/hooks/pattern-overrides.js +1 -1
  90. package/build-module/hooks/pattern-overrides.js.map +1 -1
  91. package/build-module/lock-unlock.js +1 -1
  92. package/build-module/lock-unlock.js.map +1 -1
  93. package/build-module/store/private-actions.js +9 -5
  94. package/build-module/store/private-actions.js.map +1 -1
  95. package/build-style/style-rtl.css +11 -23
  96. package/build-style/style.css +11 -23
  97. package/package.json +35 -35
  98. package/src/components/create-template-part-modal/index.js +2 -0
  99. package/src/components/create-template-part-modal/style.scss +0 -6
  100. package/src/components/document-bar/index.js +11 -4
  101. package/src/components/editor-interface/index.js +14 -8
  102. package/src/components/entities-saved-states/entity-record-item.js +4 -14
  103. package/src/components/entities-saved-states/style.scss +0 -13
  104. package/src/components/global-keyboard-shortcuts/index.js +1 -1
  105. package/src/components/global-styles-provider/index.js +7 -90
  106. package/src/components/header/index.js +2 -1
  107. package/src/components/index.js +11 -2
  108. package/src/components/post-actions/actions.js +443 -374
  109. package/src/components/post-actions/index.js +3 -1
  110. package/src/components/post-card-panel/style.scss +1 -0
  111. package/src/components/post-excerpt/index.js +4 -1
  112. package/src/components/post-excerpt/panel.js +2 -1
  113. package/src/components/post-publish-panel/style.scss +1 -1
  114. package/src/components/post-slug/check.js +8 -0
  115. package/src/components/post-slug/index.js +5 -0
  116. package/src/components/post-status/index.js +2 -0
  117. package/src/components/post-sticky/index.js +10 -13
  118. package/src/components/post-sticky/style.scss +3 -2
  119. package/src/components/post-template/create-new-template-modal.js +2 -0
  120. package/src/components/post-url/style.scss +4 -0
  121. package/src/components/sidebar/index.js +2 -0
  122. package/src/components/sidebar/post-summary.js +0 -2
  123. package/src/components/template-part-content-panel/index.js +62 -0
  124. package/src/components/visual-editor/style.scss +2 -0
  125. package/src/hooks/pattern-overrides.js +2 -3
  126. package/src/lock-unlock.js +1 -1
  127. package/src/store/private-actions.js +54 -21
  128. package/tsconfig.tsbuildinfo +1 -1
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { external, trash, backup } from '@wordpress/icons';
5
5
  import { addQueryArgs } from '@wordpress/url';
6
- import { useDispatch, useSelect } from '@wordpress/data';
6
+ import { useDispatch, useSelect, useRegistry } from '@wordpress/data';
7
7
  import { decodeEntities } from '@wordpress/html-entities';
8
8
  import { store as coreStore } from '@wordpress/core-data';
9
9
  import { __, _n, sprintf, _x } from '@wordpress/i18n';
@@ -93,12 +93,7 @@ const deletePostAction = {
93
93
  },
94
94
  supportsBulk: true,
95
95
  hideModalHeader: true,
96
- RenderModal: ( {
97
- items,
98
- closeModal,
99
- onActionStart,
100
- onActionPerformed,
101
- } ) => {
96
+ RenderModal: ( { items, closeModal, onActionPerformed } ) => {
102
97
  const [ isBusy, setIsBusy ] = useState( false );
103
98
  const { removeTemplates } = unlock( useDispatch( editorStore ) );
104
99
  return (
@@ -133,9 +128,6 @@ const deletePostAction = {
133
128
  variant="primary"
134
129
  onClick={ async () => {
135
130
  setIsBusy( true );
136
- if ( onActionStart ) {
137
- onActionStart( items );
138
- }
139
131
  await removeTemplates( items, {
140
132
  allowUndo: false,
141
133
  } );
@@ -165,12 +157,7 @@ const trashPostAction = {
165
157
  },
166
158
  supportsBulk: true,
167
159
  hideModalHeader: true,
168
- RenderModal: ( {
169
- items,
170
- closeModal,
171
- onActionStart,
172
- onActionPerformed,
173
- } ) => {
160
+ RenderModal: ( { items, closeModal, onActionPerformed } ) => {
174
161
  const [ isBusy, setIsBusy ] = useState( false );
175
162
  const { createSuccessNotice, createErrorNotice } =
176
163
  useDispatch( noticesStore );
@@ -209,9 +196,6 @@ const trashPostAction = {
209
196
  variant="primary"
210
197
  onClick={ async () => {
211
198
  setIsBusy( true );
212
- if ( onActionStart ) {
213
- onActionStart( items );
214
- }
215
199
  const promiseResult = await Promise.allSettled(
216
200
  items.map( ( item ) =>
217
201
  deleteEntityRecord(
@@ -323,244 +307,242 @@ const trashPostAction = {
323
307
  },
324
308
  };
325
309
 
326
- function usePermanentlyDeletePostAction() {
327
- const { createSuccessNotice, createErrorNotice } =
328
- useDispatch( noticesStore );
329
- const { deleteEntityRecord } = useDispatch( coreStore );
330
-
310
+ function useCanUserEligibilityCheckPostType( capability, resource, action ) {
311
+ const registry = useRegistry();
331
312
  return useMemo(
332
313
  () => ( {
333
- id: 'permanently-delete',
334
- label: __( 'Permanently delete' ),
335
- supportsBulk: true,
336
- isEligible( { status } ) {
337
- return status === 'trash';
314
+ ...action,
315
+ isEligible( item ) {
316
+ return (
317
+ action.isEligible( item ) &&
318
+ registry
319
+ .select( coreStore )
320
+ .canUser( capability, resource, item.id )
321
+ );
338
322
  },
339
- async callback( posts, onActionPerformed ) {
340
- const promiseResult = await Promise.allSettled(
341
- posts.map( ( post ) => {
342
- return deleteEntityRecord(
343
- 'postType',
344
- post.type,
345
- post.id,
346
- { force: true },
347
- { throwOnError: true }
348
- );
349
- } )
323
+ } ),
324
+ [ action, registry, capability, resource ]
325
+ );
326
+ }
327
+
328
+ function useTrashPostAction( resource ) {
329
+ return useCanUserEligibilityCheckPostType(
330
+ 'delete',
331
+ resource,
332
+ trashPostAction
333
+ );
334
+ }
335
+
336
+ const permanentlyDeletePostAction = {
337
+ id: 'permanently-delete',
338
+ label: __( 'Permanently delete' ),
339
+ supportsBulk: true,
340
+ isEligible( { status } ) {
341
+ return status === 'trash';
342
+ },
343
+ async callback( posts, { registry } ) {
344
+ const { createSuccessNotice, createErrorNotice } =
345
+ registry.dispatch( noticesStore );
346
+ const { deleteEntityRecord } = registry.dispatch( coreStore );
347
+ const promiseResult = await Promise.allSettled(
348
+ posts.map( ( post ) => {
349
+ return deleteEntityRecord(
350
+ 'postType',
351
+ post.type,
352
+ post.id,
353
+ { force: true },
354
+ { throwOnError: true }
350
355
  );
351
- // If all the promises were fulfilled with success.
352
- if (
353
- promiseResult.every(
354
- ( { status } ) => status === 'fulfilled'
355
- )
356
- ) {
357
- let successMessage;
358
- if ( promiseResult.length === 1 ) {
359
- successMessage = sprintf(
360
- /* translators: The posts's title. */
361
- __( '"%s" permanently deleted.' ),
362
- getItemTitle( posts[ 0 ] )
363
- );
364
- } else {
365
- successMessage = __(
366
- 'The posts were permanently deleted.'
367
- );
368
- }
369
- createSuccessNotice( successMessage, {
370
- type: 'snackbar',
371
- id: 'permanently-delete-post-action',
372
- } );
373
- if ( onActionPerformed ) {
374
- onActionPerformed( posts );
375
- }
356
+ } )
357
+ );
358
+ // If all the promises were fulfilled with success.
359
+ if ( promiseResult.every( ( { status } ) => status === 'fulfilled' ) ) {
360
+ let successMessage;
361
+ if ( promiseResult.length === 1 ) {
362
+ successMessage = sprintf(
363
+ /* translators: The posts's title. */
364
+ __( '"%s" permanently deleted.' ),
365
+ getItemTitle( posts[ 0 ] )
366
+ );
367
+ } else {
368
+ successMessage = __( 'The posts were permanently deleted.' );
369
+ }
370
+ createSuccessNotice( successMessage, {
371
+ type: 'snackbar',
372
+ id: 'permanently-delete-post-action',
373
+ } );
374
+ } else {
375
+ // If there was at lease one failure.
376
+ let errorMessage;
377
+ // If we were trying to permanently delete a single post.
378
+ if ( promiseResult.length === 1 ) {
379
+ if ( promiseResult[ 0 ].reason?.message ) {
380
+ errorMessage = promiseResult[ 0 ].reason.message;
376
381
  } else {
377
- // If there was at lease one failure.
378
- let errorMessage;
379
- // If we were trying to permanently delete a single post.
380
- if ( promiseResult.length === 1 ) {
381
- if ( promiseResult[ 0 ].reason?.message ) {
382
- errorMessage = promiseResult[ 0 ].reason.message;
383
- } else {
384
- errorMessage = __(
385
- 'An error occurred while permanently deleting the post.'
386
- );
387
- }
388
- // If we were trying to permanently delete multiple posts
389
- } else {
390
- const errorMessages = new Set();
391
- const failedPromises = promiseResult.filter(
392
- ( { status } ) => status === 'rejected'
393
- );
394
- for ( const failedPromise of failedPromises ) {
395
- if ( failedPromise.reason?.message ) {
396
- errorMessages.add(
397
- failedPromise.reason.message
398
- );
399
- }
400
- }
401
- if ( errorMessages.size === 0 ) {
402
- errorMessage = __(
403
- 'An error occurred while permanently deleting the posts.'
404
- );
405
- } else if ( errorMessages.size === 1 ) {
406
- errorMessage = sprintf(
407
- /* translators: %s: an error message */
408
- __(
409
- 'An error occurred while permanently deleting the posts: %s'
410
- ),
411
- [ ...errorMessages ][ 0 ]
412
- );
413
- } else {
414
- errorMessage = sprintf(
415
- /* translators: %s: a list of comma separated error messages */
416
- __(
417
- 'Some errors occurred while permanently deleting the posts: %s'
418
- ),
419
- [ ...errorMessages ].join( ',' )
420
- );
421
- }
382
+ errorMessage = __(
383
+ 'An error occurred while permanently deleting the post.'
384
+ );
385
+ }
386
+ // If we were trying to permanently delete multiple posts
387
+ } else {
388
+ const errorMessages = new Set();
389
+ const failedPromises = promiseResult.filter(
390
+ ( { status } ) => status === 'rejected'
391
+ );
392
+ for ( const failedPromise of failedPromises ) {
393
+ if ( failedPromise.reason?.message ) {
394
+ errorMessages.add( failedPromise.reason.message );
422
395
  }
423
- createErrorNotice( errorMessage, {
424
- type: 'snackbar',
425
- } );
426
396
  }
427
- },
428
- } ),
429
- [ createSuccessNotice, createErrorNotice, deleteEntityRecord ]
397
+ if ( errorMessages.size === 0 ) {
398
+ errorMessage = __(
399
+ 'An error occurred while permanently deleting the posts.'
400
+ );
401
+ } else if ( errorMessages.size === 1 ) {
402
+ errorMessage = sprintf(
403
+ /* translators: %s: an error message */
404
+ __(
405
+ 'An error occurred while permanently deleting the posts: %s'
406
+ ),
407
+ [ ...errorMessages ][ 0 ]
408
+ );
409
+ } else {
410
+ errorMessage = sprintf(
411
+ /* translators: %s: a list of comma separated error messages */
412
+ __(
413
+ 'Some errors occurred while permanently deleting the posts: %s'
414
+ ),
415
+ [ ...errorMessages ].join( ',' )
416
+ );
417
+ }
418
+ }
419
+ createErrorNotice( errorMessage, {
420
+ type: 'snackbar',
421
+ } );
422
+ }
423
+ },
424
+ };
425
+
426
+ function usePermanentlyDeletePostAction( resource ) {
427
+ return useCanUserEligibilityCheckPostType(
428
+ 'delete',
429
+ resource,
430
+ permanentlyDeletePostAction
430
431
  );
431
432
  }
432
433
 
433
- function useRestorePostAction() {
434
- const { createSuccessNotice, createErrorNotice } =
435
- useDispatch( noticesStore );
436
- const { editEntityRecord, saveEditedEntityRecord } =
437
- useDispatch( coreStore );
434
+ const restorePostAction = {
435
+ id: 'restore',
436
+ label: __( 'Restore' ),
437
+ isPrimary: true,
438
+ icon: backup,
439
+ supportsBulk: true,
440
+ isEligible( { status } ) {
441
+ return status === 'trash';
442
+ },
443
+ async callback( posts, { registry, onActionPerformed } ) {
444
+ const { createSuccessNotice, createErrorNotice } =
445
+ registry.dispatch( noticesStore );
446
+ const { editEntityRecord, saveEditedEntityRecord } =
447
+ registry.dispatch( coreStore );
448
+ await Promise.allSettled(
449
+ posts.map( ( post ) => {
450
+ return editEntityRecord( 'postType', post.type, post.id, {
451
+ status: 'draft',
452
+ } );
453
+ } )
454
+ );
455
+ const promiseResult = await Promise.allSettled(
456
+ posts.map( ( post ) => {
457
+ return saveEditedEntityRecord( 'postType', post.type, post.id, {
458
+ throwOnError: true,
459
+ } );
460
+ } )
461
+ );
438
462
 
439
- return useMemo(
440
- () => ( {
441
- id: 'restore',
442
- label: __( 'Restore' ),
443
- isPrimary: true,
444
- icon: backup,
445
- supportsBulk: true,
446
- isEligible( { status } ) {
447
- return status === 'trash';
448
- },
449
- async callback( posts, onActionPerformed ) {
450
- await Promise.allSettled(
451
- posts.map( ( post ) => {
452
- return editEntityRecord(
453
- 'postType',
454
- post.type,
455
- post.id,
456
- {
457
- status: 'draft',
458
- }
459
- );
460
- } )
463
+ if ( promiseResult.every( ( { status } ) => status === 'fulfilled' ) ) {
464
+ let successMessage;
465
+ if ( posts.length === 1 ) {
466
+ successMessage = sprintf(
467
+ /* translators: The number of posts. */
468
+ __( '"%s" has been restored.' ),
469
+ getItemTitle( posts[ 0 ] )
461
470
  );
462
- const promiseResult = await Promise.allSettled(
463
- posts.map( ( post ) => {
464
- return saveEditedEntityRecord(
465
- 'postType',
466
- post.type,
467
- post.id,
468
- { throwOnError: true }
469
- );
470
- } )
471
+ } else if ( posts[ 0 ].type === 'page' ) {
472
+ successMessage = sprintf(
473
+ /* translators: The number of posts. */
474
+ __( '%d pages have been restored.' ),
475
+ posts.length
471
476
  );
472
-
473
- if (
474
- promiseResult.every(
475
- ( { status } ) => status === 'fulfilled'
476
- )
477
- ) {
478
- let successMessage;
479
- if ( posts.length === 1 ) {
480
- successMessage = sprintf(
481
- /* translators: The number of posts. */
482
- __( '"%s" has been restored.' ),
483
- getItemTitle( posts[ 0 ] )
484
- );
485
- } else if ( posts[ 0 ].type === 'page' ) {
486
- successMessage = sprintf(
487
- /* translators: The number of posts. */
488
- __( '%d pages have been restored.' ),
489
- posts.length
490
- );
491
- } else {
492
- successMessage = sprintf(
493
- /* translators: The number of posts. */
494
- __( '%d posts have been restored.' ),
495
- posts.length
496
- );
497
- }
498
- createSuccessNotice( successMessage, {
499
- type: 'snackbar',
500
- id: 'restore-post-action',
501
- } );
502
- if ( onActionPerformed ) {
503
- onActionPerformed( posts );
504
- }
477
+ } else {
478
+ successMessage = sprintf(
479
+ /* translators: The number of posts. */
480
+ __( '%d posts have been restored.' ),
481
+ posts.length
482
+ );
483
+ }
484
+ createSuccessNotice( successMessage, {
485
+ type: 'snackbar',
486
+ id: 'restore-post-action',
487
+ } );
488
+ if ( onActionPerformed ) {
489
+ onActionPerformed( posts );
490
+ }
491
+ } else {
492
+ // If there was at lease one failure.
493
+ let errorMessage;
494
+ // If we were trying to move a single post to the trash.
495
+ if ( promiseResult.length === 1 ) {
496
+ if ( promiseResult[ 0 ].reason?.message ) {
497
+ errorMessage = promiseResult[ 0 ].reason.message;
505
498
  } else {
506
- // If there was at lease one failure.
507
- let errorMessage;
508
- // If we were trying to move a single post to the trash.
509
- if ( promiseResult.length === 1 ) {
510
- if ( promiseResult[ 0 ].reason?.message ) {
511
- errorMessage = promiseResult[ 0 ].reason.message;
512
- } else {
513
- errorMessage = __(
514
- 'An error occurred while restoring the post.'
515
- );
516
- }
517
- // If we were trying to move multiple posts to the trash
518
- } else {
519
- const errorMessages = new Set();
520
- const failedPromises = promiseResult.filter(
521
- ( { status } ) => status === 'rejected'
522
- );
523
- for ( const failedPromise of failedPromises ) {
524
- if ( failedPromise.reason?.message ) {
525
- errorMessages.add(
526
- failedPromise.reason.message
527
- );
528
- }
529
- }
530
- if ( errorMessages.size === 0 ) {
531
- errorMessage = __(
532
- 'An error occurred while restoring the posts.'
533
- );
534
- } else if ( errorMessages.size === 1 ) {
535
- errorMessage = sprintf(
536
- /* translators: %s: an error message */
537
- __(
538
- 'An error occurred while restoring the posts: %s'
539
- ),
540
- [ ...errorMessages ][ 0 ]
541
- );
542
- } else {
543
- errorMessage = sprintf(
544
- /* translators: %s: a list of comma separated error messages */
545
- __(
546
- 'Some errors occurred while restoring the posts: %s'
547
- ),
548
- [ ...errorMessages ].join( ',' )
549
- );
550
- }
499
+ errorMessage = __(
500
+ 'An error occurred while restoring the post.'
501
+ );
502
+ }
503
+ // If we were trying to move multiple posts to the trash
504
+ } else {
505
+ const errorMessages = new Set();
506
+ const failedPromises = promiseResult.filter(
507
+ ( { status } ) => status === 'rejected'
508
+ );
509
+ for ( const failedPromise of failedPromises ) {
510
+ if ( failedPromise.reason?.message ) {
511
+ errorMessages.add( failedPromise.reason.message );
551
512
  }
552
- createErrorNotice( errorMessage, {
553
- type: 'snackbar',
554
- } );
555
513
  }
556
- },
557
- } ),
558
- [
559
- createSuccessNotice,
560
- createErrorNotice,
561
- editEntityRecord,
562
- saveEditedEntityRecord,
563
- ]
514
+ if ( errorMessages.size === 0 ) {
515
+ errorMessage = __(
516
+ 'An error occurred while restoring the posts.'
517
+ );
518
+ } else if ( errorMessages.size === 1 ) {
519
+ errorMessage = sprintf(
520
+ /* translators: %s: an error message */
521
+ __( 'An error occurred while restoring the posts: %s' ),
522
+ [ ...errorMessages ][ 0 ]
523
+ );
524
+ } else {
525
+ errorMessage = sprintf(
526
+ /* translators: %s: a list of comma separated error messages */
527
+ __(
528
+ 'Some errors occurred while restoring the posts: %s'
529
+ ),
530
+ [ ...errorMessages ].join( ',' )
531
+ );
532
+ }
533
+ }
534
+ createErrorNotice( errorMessage, {
535
+ type: 'snackbar',
536
+ } );
537
+ }
538
+ },
539
+ };
540
+
541
+ function useRestorePostAction( resource ) {
542
+ return useCanUserEligibilityCheckPostType(
543
+ 'update',
544
+ resource,
545
+ restorePostAction
564
546
  );
565
547
  }
566
548
 
@@ -572,7 +554,7 @@ const viewPostAction = {
572
554
  isEligible( post ) {
573
555
  return post.status !== 'trash';
574
556
  },
575
- callback( posts, onActionPerformed ) {
557
+ callback( posts, { onActionPerformed } ) {
576
558
  const post = posts[ 0 ];
577
559
  window.open( post.link, '_blank' );
578
560
  if ( onActionPerformed ) {
@@ -583,6 +565,7 @@ const viewPostAction = {
583
565
 
584
566
  const postRevisionsAction = {
585
567
  id: 'view-post-revisions',
568
+ context: 'list',
586
569
  label( items ) {
587
570
  const revisionsCount =
588
571
  items[ 0 ]._links?.[ 'version-history' ]?.[ 0 ]?.count ?? 0;
@@ -602,7 +585,7 @@ const postRevisionsAction = {
602
585
  post?._links?.[ 'version-history' ]?.[ 0 ]?.count ?? 0;
603
586
  return lastRevisionId && revisionsCount > 1;
604
587
  },
605
- callback( posts, onActionPerformed ) {
588
+ callback( posts, { onActionPerformed } ) {
606
589
  const post = posts[ 0 ];
607
590
  const href = addQueryArgs( 'revision.php', {
608
591
  revision: post?._links?.[ 'predecessor-version' ]?.[ 0 ]?.id,
@@ -722,118 +705,170 @@ const renamePostAction = {
722
705
  },
723
706
  };
724
707
 
725
- const duplicatePostAction = {
726
- id: 'duplicate-post',
727
- label: _x( 'Duplicate', 'action label' ),
728
- isEligible( { status } ) {
729
- return status !== 'trash';
730
- },
731
- RenderModal: ( { items, closeModal, onActionPerformed } ) => {
732
- const [ item ] = items;
733
- const [ isCreatingPage, setIsCreatingPage ] = useState( false );
734
- const [ title, setTitle ] = useState(
735
- sprintf(
736
- /* translators: %s: Existing item title */
737
- __( '%s (Copy)' ),
738
- getItemTitle( item )
739
- )
740
- );
708
+ function useRenamePostAction( resource ) {
709
+ return useCanUserEligibilityCheckPostType(
710
+ 'update',
711
+ resource,
712
+ renamePostAction
713
+ );
714
+ }
741
715
 
742
- const { saveEntityRecord } = useDispatch( coreStore );
743
- const { createSuccessNotice, createErrorNotice } =
744
- useDispatch( noticesStore );
716
+ const useDuplicatePostAction = ( postType ) => {
717
+ const { userCanCreatePost } = useSelect(
718
+ ( select ) => {
719
+ const { getPostType, canUser } = select( coreStore );
720
+ const resource = getPostType( postType )?.rest_base || '';
721
+ return {
722
+ userCanCreatePost: canUser( 'create', resource ),
723
+ };
724
+ },
725
+ [ postType ]
726
+ );
727
+ return useMemo(
728
+ () =>
729
+ userCanCreatePost && {
730
+ id: 'duplicate-post',
731
+ label: _x( 'Duplicate', 'action label' ),
732
+ isEligible( { status } ) {
733
+ return status !== 'trash';
734
+ },
735
+ RenderModal: ( { items, closeModal, onActionPerformed } ) => {
736
+ const [ item ] = items;
737
+ const [ isCreatingPage, setIsCreatingPage ] =
738
+ useState( false );
739
+ const [ title, setTitle ] = useState(
740
+ sprintf(
741
+ /* translators: %s: Existing item title */
742
+ __( '%s (Copy)' ),
743
+ getItemTitle( item )
744
+ )
745
+ );
745
746
 
746
- async function createPage( event ) {
747
- event.preventDefault();
747
+ const { saveEntityRecord } = useDispatch( coreStore );
748
+ const { createSuccessNotice, createErrorNotice } =
749
+ useDispatch( noticesStore );
748
750
 
749
- if ( isCreatingPage ) {
750
- return;
751
- }
752
- setIsCreatingPage( true );
753
- try {
754
- const newItem = await saveEntityRecord(
755
- 'postType',
756
- item.type,
757
- {
758
- status: 'draft',
759
- title,
760
- slug: title || __( 'No title' ),
761
- author: item.author,
762
- comment_status: item.comment_status,
763
- content:
764
- typeof item.content === 'string'
765
- ? item.content
766
- : item.content.raw,
767
- excerpt: item.excerpt.raw,
768
- meta: item.meta,
769
- parent: item.parent,
770
- password: item.password,
771
- template: item.template,
772
- format: item.format,
773
- featured_media: item.featured_media,
774
- menu_order: item.menu_order,
775
- ping_status: item.ping_status,
776
- categories: item.categories,
777
- tags: item.tags,
778
- },
779
- { throwOnError: true }
780
- );
751
+ async function createPage( event ) {
752
+ event.preventDefault();
781
753
 
782
- createSuccessNotice(
783
- sprintf(
784
- // translators: %s: Title of the created template e.g: "Category".
785
- __( '"%s" successfully created.' ),
786
- decodeEntities( newItem.title?.rendered || title )
787
- ),
788
- {
789
- id: 'duplicate-post-action',
790
- type: 'snackbar',
791
- }
792
- );
754
+ if ( isCreatingPage ) {
755
+ return;
756
+ }
793
757
 
794
- if ( onActionPerformed ) {
795
- onActionPerformed( [ newItem ] );
796
- }
797
- } catch ( error ) {
798
- const errorMessage =
799
- error.message && error.code !== 'unknown_error'
800
- ? error.message
801
- : __( 'An error occurred while duplicating the page.' );
758
+ const newItemOject = {
759
+ status: 'draft',
760
+ title,
761
+ slug: title || __( 'No title' ),
762
+ comment_status: item.comment_status,
763
+ content:
764
+ typeof item.content === 'string'
765
+ ? item.content
766
+ : item.content.raw,
767
+ excerpt: item.excerpt.raw,
768
+ meta: item.meta,
769
+ parent: item.parent,
770
+ password: item.password,
771
+ template: item.template,
772
+ format: item.format,
773
+ featured_media: item.featured_media,
774
+ menu_order: item.menu_order,
775
+ ping_status: item.ping_status,
776
+ };
777
+ const assignablePropertiesPrefix = 'wp:action-assign-';
778
+ // Get all the properties that the current user is able to assign normally author, categories, tags,
779
+ // and custom taxonomies.
780
+ const assignableProperties = Object.keys(
781
+ item?._links || {}
782
+ )
783
+ .filter( ( property ) =>
784
+ property.startsWith(
785
+ assignablePropertiesPrefix
786
+ )
787
+ )
788
+ .map( ( property ) =>
789
+ property.slice(
790
+ assignablePropertiesPrefix.length
791
+ )
792
+ );
793
+ assignableProperties.forEach( ( property ) => {
794
+ if ( item[ property ] ) {
795
+ newItemOject[ property ] = item[ property ];
796
+ }
797
+ } );
798
+ setIsCreatingPage( true );
799
+ try {
800
+ const newItem = await saveEntityRecord(
801
+ 'postType',
802
+ item.type,
803
+ newItemOject,
804
+ { throwOnError: true }
805
+ );
802
806
 
803
- createErrorNotice( errorMessage, {
804
- type: 'snackbar',
805
- } );
806
- } finally {
807
- setIsCreatingPage( false );
808
- closeModal();
809
- }
810
- }
811
- return (
812
- <form onSubmit={ createPage }>
813
- <VStack spacing={ 3 }>
814
- <TextControl
815
- label={ __( 'Title' ) }
816
- onChange={ setTitle }
817
- placeholder={ __( 'No title' ) }
818
- value={ title }
819
- />
820
- <HStack spacing={ 2 } justify="end">
821
- <Button variant="tertiary" onClick={ closeModal }>
822
- { __( 'Cancel' ) }
823
- </Button>
824
- <Button
825
- variant="primary"
826
- type="submit"
827
- isBusy={ isCreatingPage }
828
- aria-disabled={ isCreatingPage }
829
- >
830
- { _x( 'Duplicate', 'action label' ) }
831
- </Button>
832
- </HStack>
833
- </VStack>
834
- </form>
835
- );
836
- },
807
+ createSuccessNotice(
808
+ sprintf(
809
+ // translators: %s: Title of the created template e.g: "Category".
810
+ __( '"%s" successfully created.' ),
811
+ decodeEntities(
812
+ newItem.title?.rendered || title
813
+ )
814
+ ),
815
+ {
816
+ id: 'duplicate-post-action',
817
+ type: 'snackbar',
818
+ }
819
+ );
820
+
821
+ if ( onActionPerformed ) {
822
+ onActionPerformed( [ newItem ] );
823
+ }
824
+ } catch ( error ) {
825
+ const errorMessage =
826
+ error.message && error.code !== 'unknown_error'
827
+ ? error.message
828
+ : __(
829
+ 'An error occurred while duplicating the page.'
830
+ );
831
+
832
+ createErrorNotice( errorMessage, {
833
+ type: 'snackbar',
834
+ } );
835
+ } finally {
836
+ setIsCreatingPage( false );
837
+ closeModal();
838
+ }
839
+ }
840
+ return (
841
+ <form onSubmit={ createPage }>
842
+ <VStack spacing={ 3 }>
843
+ <TextControl
844
+ label={ __( 'Title' ) }
845
+ onChange={ setTitle }
846
+ placeholder={ __( 'No title' ) }
847
+ value={ title }
848
+ />
849
+ <HStack spacing={ 2 } justify="end">
850
+ <Button
851
+ variant="tertiary"
852
+ onClick={ closeModal }
853
+ >
854
+ { __( 'Cancel' ) }
855
+ </Button>
856
+ <Button
857
+ variant="primary"
858
+ type="submit"
859
+ isBusy={ isCreatingPage }
860
+ aria-disabled={ isCreatingPage }
861
+ >
862
+ { _x( 'Duplicate', 'action label' ) }
863
+ </Button>
864
+ </HStack>
865
+ </VStack>
866
+ </form>
867
+ );
868
+ },
869
+ },
870
+ [ userCanCreatePost ]
871
+ );
837
872
  };
838
873
 
839
874
  const isTemplatePartRevertable = ( item ) => {
@@ -855,12 +890,7 @@ const resetTemplateAction = {
855
890
  icon: backup,
856
891
  supportsBulk: true,
857
892
  hideModalHeader: true,
858
- RenderModal: ( {
859
- items,
860
- closeModal,
861
- onActionStart,
862
- onActionPerformed,
863
- } ) => {
893
+ RenderModal: ( { items, closeModal, onActionPerformed } ) => {
864
894
  const [ isBusy, setIsBusy ] = useState( false );
865
895
  const { revertTemplate, removeTemplates } = unlock(
866
896
  useDispatch( editorStore )
@@ -950,9 +980,6 @@ const resetTemplateAction = {
950
980
  variant="primary"
951
981
  onClick={ async () => {
952
982
  setIsBusy( true );
953
- if ( onActionStart ) {
954
- onActionStart( items );
955
- }
956
983
  await onConfirm( items );
957
984
  onActionPerformed?.( items );
958
985
  setIsBusy( false );
@@ -1029,21 +1056,37 @@ export const duplicateTemplatePartAction = {
1029
1056
  },
1030
1057
  };
1031
1058
 
1032
- export function usePostActions( postType, onActionPerformed ) {
1033
- const { defaultActions, postTypeObject } = useSelect(
1059
+ export function usePostActions( { postType, onActionPerformed, context } ) {
1060
+ const {
1061
+ defaultActions,
1062
+ postTypeObject,
1063
+ userCanCreatePostType,
1064
+ resource,
1065
+ cachedCanUserResolvers,
1066
+ } = useSelect(
1034
1067
  ( select ) => {
1035
- const { getPostType } = select( coreStore );
1068
+ const { getPostType, canUser, getCachedResolvers } =
1069
+ select( coreStore );
1036
1070
  const { getEntityActions } = unlock( select( editorStore ) );
1071
+ const _postTypeObject = getPostType( postType );
1072
+ const _resource = _postTypeObject?.rest_base || '';
1037
1073
  return {
1038
- postTypeObject: getPostType( postType ),
1074
+ postTypeObject: _postTypeObject,
1039
1075
  defaultActions: getEntityActions( 'postType', postType ),
1076
+ userCanCreatePostType: canUser( 'create', _resource ),
1077
+ resource: _resource,
1078
+ cachedCanUserResolvers: getCachedResolvers()?.canUser,
1040
1079
  };
1041
1080
  },
1042
1081
  [ postType ]
1043
1082
  );
1044
1083
 
1045
- const permanentlyDeletePostAction = usePermanentlyDeletePostAction();
1046
- const restorePostAction = useRestorePostAction();
1084
+ const duplicatePostAction = useDuplicatePostAction( postType );
1085
+ const trashPostActionForPostType = useTrashPostAction( resource );
1086
+ const permanentlyDeletePostActionForPostType =
1087
+ usePermanentlyDeletePostAction( resource );
1088
+ const renamePostActionForPostType = useRenamePostAction( resource );
1089
+ const restorePostActionForPostType = useRestorePostAction( resource );
1047
1090
  const isTemplateOrTemplatePart = [
1048
1091
  TEMPLATE_POST_TYPE,
1049
1092
  TEMPLATE_PART_POST_TYPE,
@@ -1057,7 +1100,7 @@ export function usePostActions( postType, onActionPerformed ) {
1057
1100
  return [];
1058
1101
  }
1059
1102
 
1060
- const actions = [
1103
+ let actions = [
1061
1104
  postTypeObject?.viewable && viewPostAction,
1062
1105
  supportsRevisions && postRevisionsAction,
1063
1106
  globalThis.IS_GUTENBERG_PLUGIN
@@ -1065,17 +1108,34 @@ export function usePostActions( postType, onActionPerformed ) {
1065
1108
  ! isPattern &&
1066
1109
  duplicatePostAction
1067
1110
  : false,
1068
- isTemplateOrTemplatePart && duplicateTemplatePartAction,
1069
- isPattern && duplicatePatternAction,
1070
- supportsTitle && renamePostAction,
1111
+ isTemplateOrTemplatePart &&
1112
+ userCanCreatePostType &&
1113
+ duplicateTemplatePartAction,
1114
+ isPattern && userCanCreatePostType && duplicatePatternAction,
1115
+ supportsTitle && renamePostActionForPostType,
1071
1116
  isPattern && exportPatternAsJSONAction,
1072
- isTemplateOrTemplatePart ? resetTemplateAction : restorePostAction,
1117
+ isTemplateOrTemplatePart
1118
+ ? resetTemplateAction
1119
+ : restorePostActionForPostType,
1073
1120
  isTemplateOrTemplatePart || isPattern
1074
1121
  ? deletePostAction
1075
- : trashPostAction,
1076
- ! isTemplateOrTemplatePart && permanentlyDeletePostAction,
1122
+ : trashPostActionForPostType,
1123
+ ! isTemplateOrTemplatePart &&
1124
+ permanentlyDeletePostActionForPostType,
1077
1125
  ...defaultActions,
1078
1126
  ].filter( Boolean );
1127
+ // Filter actions based on provided context. If not provided
1128
+ // all actions are returned. We'll have a single entry for getting the actions
1129
+ // and the consumer should provide the context to filter the actions, if needed.
1130
+ // Actions should also provide the `context` they support, if it's specific, to
1131
+ // compare with the provided context to get all the actions.
1132
+ // Right now the only supported context is `list`.
1133
+ actions = actions.filter( ( action ) => {
1134
+ if ( ! action.context ) {
1135
+ return true;
1136
+ }
1137
+ return action.context === context;
1138
+ } );
1079
1139
 
1080
1140
  if ( onActionPerformed ) {
1081
1141
  for ( let i = 0; i < actions.length; ++i ) {
@@ -1083,7 +1143,7 @@ export function usePostActions( postType, onActionPerformed ) {
1083
1143
  const existingCallback = actions[ i ].callback;
1084
1144
  actions[ i ] = {
1085
1145
  ...actions[ i ],
1086
- callback: ( items, _onActionPerformed ) => {
1146
+ callback: ( items, { _onActionPerformed } ) => {
1087
1147
  existingCallback( items, ( _items ) => {
1088
1148
  if ( _onActionPerformed ) {
1089
1149
  _onActionPerformed( _items );
@@ -1119,16 +1179,25 @@ export function usePostActions( postType, onActionPerformed ) {
1119
1179
  }
1120
1180
 
1121
1181
  return actions;
1182
+ // We are making this use memo depend on cachedCanUserResolvers as a way to make the component using this hook re-render
1183
+ // when user capabilities are resolved. This makes sure the isEligible functions of actions dependent on capabilities are re-evaluated.
1184
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1122
1185
  }, [
1123
1186
  defaultActions,
1187
+ userCanCreatePostType,
1124
1188
  isTemplateOrTemplatePart,
1125
1189
  isPattern,
1126
1190
  postTypeObject?.viewable,
1127
- permanentlyDeletePostAction,
1128
- restorePostAction,
1191
+ duplicatePostAction,
1192
+ trashPostActionForPostType,
1193
+ restorePostActionForPostType,
1194
+ renamePostActionForPostType,
1195
+ permanentlyDeletePostActionForPostType,
1129
1196
  onActionPerformed,
1130
1197
  isLoaded,
1131
1198
  supportsRevisions,
1132
1199
  supportsTitle,
1200
+ context,
1201
+ cachedCanUserResolvers,
1133
1202
  ] );
1134
1203
  }