@wordpress/editor 14.49.1 → 14.51.1-next.v.202607070741.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 (262) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/components/collab-sidebar/format.cjs +83 -0
  3. package/build/components/collab-sidebar/format.cjs.map +7 -0
  4. package/build/components/collab-sidebar/hooks.cjs +117 -16
  5. package/build/components/collab-sidebar/hooks.cjs.map +2 -2
  6. package/build/components/collab-sidebar/index.cjs +18 -2
  7. package/build/components/collab-sidebar/index.cjs.map +2 -2
  8. package/build/components/collab-sidebar/note-highlight-styles.cjs +67 -0
  9. package/build/components/collab-sidebar/note-highlight-styles.cjs.map +7 -0
  10. package/build/components/collab-sidebar/utils.cjs +139 -0
  11. package/build/components/collab-sidebar/utils.cjs.map +2 -2
  12. package/build/components/collaborators-presence/use-collaborator-notifications.cjs +55 -30
  13. package/build/components/collaborators-presence/use-collaborator-notifications.cjs.map +2 -2
  14. package/build/components/commands/index.cjs +99 -3
  15. package/build/components/commands/index.cjs.map +2 -2
  16. package/build/components/document-bar/index.cjs +28 -34
  17. package/build/components/document-bar/index.cjs.map +2 -2
  18. package/build/components/entities-saved-states/index.cjs +1 -4
  19. package/build/components/entities-saved-states/index.cjs.map +2 -2
  20. package/build/components/header/index.cjs +1 -8
  21. package/build/components/header/index.cjs.map +2 -2
  22. package/build/components/media-categories/index.cjs +92 -6
  23. package/build/components/media-categories/index.cjs.map +2 -2
  24. package/build/components/page-attributes/parent.cjs +0 -1
  25. package/build/components/page-attributes/parent.cjs.map +2 -2
  26. package/build/components/post-author/combobox.cjs +0 -1
  27. package/build/components/post-author/combobox.cjs.map +2 -2
  28. package/build/components/post-author/select.cjs +0 -1
  29. package/build/components/post-author/select.cjs.map +2 -2
  30. package/build/components/post-preview-button/index.cjs +29 -2
  31. package/build/components/post-preview-button/index.cjs.map +2 -2
  32. package/build/components/post-publish-panel/maybe-upload-media.cjs +15 -26
  33. package/build/components/post-publish-panel/maybe-upload-media.cjs.map +3 -3
  34. package/build/components/post-revisions-preview/revisions-slider.cjs +3 -1
  35. package/build/components/post-revisions-preview/revisions-slider.cjs.map +2 -2
  36. package/build/components/post-taxonomies/flat-term-selector.cjs +0 -1
  37. package/build/components/post-taxonomies/flat-term-selector.cjs.map +2 -2
  38. package/build/components/post-taxonomies/hierarchical-term-selector.cjs +0 -2
  39. package/build/components/post-taxonomies/hierarchical-term-selector.cjs.map +2 -2
  40. package/build/components/post-template/classic-theme.cjs +0 -1
  41. package/build/components/post-template/classic-theme.cjs.map +2 -2
  42. package/build/components/preferences-modal/index.cjs +54 -28
  43. package/build/components/preferences-modal/index.cjs.map +2 -2
  44. package/build/components/preview-dropdown/index.cjs +38 -9
  45. package/build/components/preview-dropdown/index.cjs.map +3 -3
  46. package/build/components/provider/use-block-editor-settings.cjs +23 -9
  47. package/build/components/provider/use-block-editor-settings.cjs.map +3 -3
  48. package/build/components/resizable-editor/index.cjs +54 -9
  49. package/build/components/resizable-editor/index.cjs.map +3 -3
  50. package/build/components/sidebar/dataform-post-summary.cjs +5 -1
  51. package/build/components/sidebar/dataform-post-summary.cjs.map +3 -3
  52. package/build/components/sidebar/index.cjs +29 -38
  53. package/build/components/sidebar/index.cjs.map +3 -3
  54. package/build/components/sidebar/post-summary.cjs +0 -13
  55. package/build/components/sidebar/post-summary.cjs.map +3 -3
  56. package/build/components/visual-editor/index.cjs +15 -11
  57. package/build/components/visual-editor/index.cjs.map +3 -3
  58. package/build/dataviews/store/private-actions.cjs +24 -19
  59. package/build/dataviews/store/private-actions.cjs.map +2 -2
  60. package/build/store/actions.cjs +5 -3
  61. package/build/store/actions.cjs.map +2 -2
  62. package/build/store/constants.cjs +26 -0
  63. package/build/store/constants.cjs.map +2 -2
  64. package/build/store/private-actions.cjs +25 -18
  65. package/build/store/private-actions.cjs.map +3 -3
  66. package/build/store/private-selectors.cjs +5 -0
  67. package/build/store/private-selectors.cjs.map +2 -2
  68. package/build/store/reducer.cjs +6 -6
  69. package/build/store/reducer.cjs.map +2 -2
  70. package/build/store/selectors.cjs +3 -1
  71. package/build/store/selectors.cjs.map +2 -2
  72. package/build/utils/device-type.cjs +52 -0
  73. package/build/utils/device-type.cjs.map +7 -0
  74. package/build/utils/media-sideload-from-url/index.cjs +63 -0
  75. package/build/utils/media-sideload-from-url/index.cjs.map +7 -0
  76. package/build-module/components/collab-sidebar/format.mjs +57 -0
  77. package/build-module/components/collab-sidebar/format.mjs.map +7 -0
  78. package/build-module/components/collab-sidebar/hooks.mjs +121 -16
  79. package/build-module/components/collab-sidebar/hooks.mjs.map +2 -2
  80. package/build-module/components/collab-sidebar/index.mjs +21 -5
  81. package/build-module/components/collab-sidebar/index.mjs.map +2 -2
  82. package/build-module/components/collab-sidebar/note-highlight-styles.mjs +41 -0
  83. package/build-module/components/collab-sidebar/note-highlight-styles.mjs.map +7 -0
  84. package/build-module/components/collab-sidebar/utils.mjs +133 -0
  85. package/build-module/components/collab-sidebar/utils.mjs.map +2 -2
  86. package/build-module/components/collaborators-presence/use-collaborator-notifications.mjs +55 -30
  87. package/build-module/components/collaborators-presence/use-collaborator-notifications.mjs.map +2 -2
  88. package/build-module/components/commands/index.mjs +99 -3
  89. package/build-module/components/commands/index.mjs.map +2 -2
  90. package/build-module/components/document-bar/index.mjs +28 -34
  91. package/build-module/components/document-bar/index.mjs.map +2 -2
  92. package/build-module/components/entities-saved-states/index.mjs +1 -4
  93. package/build-module/components/entities-saved-states/index.mjs.map +2 -2
  94. package/build-module/components/header/index.mjs +1 -13
  95. package/build-module/components/header/index.mjs.map +2 -2
  96. package/build-module/components/media-categories/index.mjs +93 -7
  97. package/build-module/components/media-categories/index.mjs.map +2 -2
  98. package/build-module/components/page-attributes/parent.mjs +0 -1
  99. package/build-module/components/page-attributes/parent.mjs.map +2 -2
  100. package/build-module/components/post-author/combobox.mjs +0 -1
  101. package/build-module/components/post-author/combobox.mjs.map +2 -2
  102. package/build-module/components/post-author/select.mjs +0 -1
  103. package/build-module/components/post-author/select.mjs.map +2 -2
  104. package/build-module/components/post-preview-button/index.mjs +29 -2
  105. package/build-module/components/post-preview-button/index.mjs.map +2 -2
  106. package/build-module/components/post-publish-panel/maybe-upload-media.mjs +19 -27
  107. package/build-module/components/post-publish-panel/maybe-upload-media.mjs.map +2 -2
  108. package/build-module/components/post-revisions-preview/revisions-slider.mjs +3 -1
  109. package/build-module/components/post-revisions-preview/revisions-slider.mjs.map +2 -2
  110. package/build-module/components/post-taxonomies/flat-term-selector.mjs +0 -1
  111. package/build-module/components/post-taxonomies/flat-term-selector.mjs.map +2 -2
  112. package/build-module/components/post-taxonomies/hierarchical-term-selector.mjs +0 -2
  113. package/build-module/components/post-taxonomies/hierarchical-term-selector.mjs.map +2 -2
  114. package/build-module/components/post-template/classic-theme.mjs +0 -1
  115. package/build-module/components/post-template/classic-theme.mjs.map +2 -2
  116. package/build-module/components/preferences-modal/index.mjs +54 -28
  117. package/build-module/components/preferences-modal/index.mjs.map +2 -2
  118. package/build-module/components/preview-dropdown/index.mjs +38 -9
  119. package/build-module/components/preview-dropdown/index.mjs.map +2 -2
  120. package/build-module/components/provider/use-block-editor-settings.mjs +23 -9
  121. package/build-module/components/provider/use-block-editor-settings.mjs.map +2 -2
  122. package/build-module/components/resizable-editor/index.mjs +55 -10
  123. package/build-module/components/resizable-editor/index.mjs.map +2 -2
  124. package/build-module/components/sidebar/dataform-post-summary.mjs +6 -2
  125. package/build-module/components/sidebar/dataform-post-summary.mjs.map +2 -2
  126. package/build-module/components/sidebar/index.mjs +29 -42
  127. package/build-module/components/sidebar/index.mjs.map +2 -2
  128. package/build-module/components/sidebar/post-summary.mjs +0 -13
  129. package/build-module/components/sidebar/post-summary.mjs.map +2 -2
  130. package/build-module/components/visual-editor/index.mjs +16 -13
  131. package/build-module/components/visual-editor/index.mjs.map +2 -2
  132. package/build-module/dataviews/store/private-actions.mjs +26 -19
  133. package/build-module/dataviews/store/private-actions.mjs.map +2 -2
  134. package/build-module/store/actions.mjs +5 -3
  135. package/build-module/store/actions.mjs.map +2 -2
  136. package/build-module/store/constants.mjs +25 -0
  137. package/build-module/store/constants.mjs.map +2 -2
  138. package/build-module/store/private-actions.mjs +27 -17
  139. package/build-module/store/private-actions.mjs.map +2 -2
  140. package/build-module/store/private-selectors.mjs +4 -0
  141. package/build-module/store/private-selectors.mjs.map +2 -2
  142. package/build-module/store/reducer.mjs +5 -5
  143. package/build-module/store/reducer.mjs.map +2 -2
  144. package/build-module/store/selectors.mjs +3 -1
  145. package/build-module/store/selectors.mjs.map +2 -2
  146. package/build-module/utils/device-type.mjs +25 -0
  147. package/build-module/utils/device-type.mjs.map +7 -0
  148. package/build-module/utils/media-sideload-from-url/index.mjs +32 -0
  149. package/build-module/utils/media-sideload-from-url/index.mjs.map +7 -0
  150. package/build-style/style-rtl.css +84 -39
  151. package/build-style/style.css +84 -39
  152. package/build-types/components/collab-sidebar/format.d.ts +17 -0
  153. package/build-types/components/collab-sidebar/format.d.ts.map +1 -0
  154. package/build-types/components/collab-sidebar/hooks.d.ts.map +1 -1
  155. package/build-types/components/collab-sidebar/index.d.ts.map +1 -1
  156. package/build-types/components/collab-sidebar/note-highlight-styles.d.ts +31 -0
  157. package/build-types/components/collab-sidebar/note-highlight-styles.d.ts.map +1 -0
  158. package/build-types/components/collab-sidebar/utils.d.ts +82 -2
  159. package/build-types/components/collab-sidebar/utils.d.ts.map +1 -1
  160. package/build-types/components/collaborators-presence/use-collaborator-notifications.d.ts.map +1 -1
  161. package/build-types/components/commands/index.d.ts.map +1 -1
  162. package/build-types/components/document-bar/index.d.ts.map +1 -1
  163. package/build-types/components/entities-saved-states/index.d.ts.map +1 -1
  164. package/build-types/components/header/index.d.ts.map +1 -1
  165. package/build-types/components/media-categories/index.d.ts +12 -3
  166. package/build-types/components/media-categories/index.d.ts.map +1 -1
  167. package/build-types/components/page-attributes/parent.d.ts.map +1 -1
  168. package/build-types/components/post-author/combobox.d.ts.map +1 -1
  169. package/build-types/components/post-author/select.d.ts.map +1 -1
  170. package/build-types/components/post-preview-button/index.d.ts.map +1 -1
  171. package/build-types/components/post-publish-panel/maybe-upload-media.d.ts.map +1 -1
  172. package/build-types/components/post-revisions-preview/revisions-slider.d.ts.map +1 -1
  173. package/build-types/components/post-taxonomies/flat-term-selector.d.ts.map +1 -1
  174. package/build-types/components/post-taxonomies/hierarchical-term-selector.d.ts.map +1 -1
  175. package/build-types/components/post-template/classic-theme.d.ts.map +1 -1
  176. package/build-types/components/preview-dropdown/index.d.ts.map +1 -1
  177. package/build-types/components/provider/use-block-editor-settings.d.ts.map +1 -1
  178. package/build-types/components/resizable-editor/index.d.ts.map +1 -1
  179. package/build-types/components/sidebar/dataform-post-summary.d.ts.map +1 -1
  180. package/build-types/components/sidebar/index.d.ts.map +1 -1
  181. package/build-types/components/sidebar/post-summary.d.ts.map +1 -1
  182. package/build-types/components/visual-editor/index.d.ts.map +1 -1
  183. package/build-types/dataviews/store/private-actions.d.ts.map +1 -1
  184. package/build-types/store/actions.d.ts.map +1 -1
  185. package/build-types/store/constants.d.ts +6 -0
  186. package/build-types/store/constants.d.ts.map +1 -1
  187. package/build-types/store/index.d.ts +2 -2
  188. package/build-types/store/private-actions.d.ts +9 -15
  189. package/build-types/store/private-actions.d.ts.map +1 -1
  190. package/build-types/store/private-selectors.d.ts +7 -0
  191. package/build-types/store/private-selectors.d.ts.map +1 -1
  192. package/build-types/store/reducer.d.ts +6 -7
  193. package/build-types/store/reducer.d.ts.map +1 -1
  194. package/build-types/store/selectors.d.ts +1 -1
  195. package/build-types/store/selectors.d.ts.map +1 -1
  196. package/build-types/utils/device-type.d.ts +23 -0
  197. package/build-types/utils/device-type.d.ts.map +1 -0
  198. package/build-types/utils/get-template-part-icon.d.ts.map +1 -1
  199. package/build-types/utils/media-sideload-from-url/index.d.ts +18 -0
  200. package/build-types/utils/media-sideload-from-url/index.d.ts.map +1 -0
  201. package/package.json +48 -46
  202. package/src/components/collab-sidebar/format.js +72 -0
  203. package/src/components/collab-sidebar/hooks.js +203 -22
  204. package/src/components/collab-sidebar/index.js +19 -5
  205. package/src/components/collab-sidebar/note-highlight-styles.js +83 -0
  206. package/src/components/collab-sidebar/style.scss +9 -0
  207. package/src/components/collab-sidebar/test/note-highlight-styles.js +106 -0
  208. package/src/components/collab-sidebar/test/utils.js +471 -0
  209. package/src/components/collab-sidebar/utils.js +225 -2
  210. package/src/components/collaborators-presence/test/use-collaborator-notifications.ts +265 -262
  211. package/src/components/collaborators-presence/use-collaborator-notifications.ts +70 -34
  212. package/src/components/commands/index.js +142 -0
  213. package/src/components/document-bar/index.js +0 -9
  214. package/src/components/document-bar/style.scss +0 -17
  215. package/src/components/entities-saved-states/index.js +1 -5
  216. package/src/components/header/index.js +1 -15
  217. package/src/components/header/style.scss +7 -3
  218. package/src/components/media-categories/index.js +160 -6
  219. package/src/components/media-categories/test/index.js +202 -0
  220. package/src/components/page-attributes/parent.js +0 -1
  221. package/src/components/post-author/combobox.js +0 -1
  222. package/src/components/post-author/select.js +0 -1
  223. package/src/components/post-preview-button/index.js +68 -2
  224. package/src/components/post-publish-panel/maybe-upload-media.js +25 -30
  225. package/src/components/post-publish-panel/style.scss +1 -1
  226. package/src/components/post-publish-panel/test/__snapshots__/index.js.snap +11 -127
  227. package/src/components/post-revisions-preview/revisions-slider.js +4 -1
  228. package/src/components/post-revisions-preview/test/block-diff.js +2 -2
  229. package/src/components/post-taxonomies/flat-term-selector.js +0 -1
  230. package/src/components/post-taxonomies/hierarchical-term-selector.js +0 -2
  231. package/src/components/post-template/classic-theme.js +0 -1
  232. package/src/components/preferences-modal/index.js +40 -22
  233. package/src/components/preferences-modal/test/index.js +83 -0
  234. package/src/components/preview-dropdown/index.js +42 -3
  235. package/src/components/provider/use-block-editor-settings.js +42 -7
  236. package/src/components/resizable-editor/index.js +71 -9
  237. package/src/components/resizable-editor/style.scss +7 -0
  238. package/src/components/sidebar/dataform-post-summary.js +13 -1
  239. package/src/components/sidebar/index.js +32 -47
  240. package/src/components/sidebar/post-summary.js +0 -17
  241. package/src/components/styles-canvas/style.scss +1 -2
  242. package/src/components/visual-editor/index.js +19 -15
  243. package/src/components/visual-editor/style.scss +11 -4
  244. package/src/dataviews/store/private-actions.ts +30 -26
  245. package/src/store/actions.js +6 -3
  246. package/src/store/constants.ts +45 -0
  247. package/src/store/private-actions.js +38 -28
  248. package/src/store/private-selectors.js +10 -0
  249. package/src/store/reducer.js +7 -9
  250. package/src/store/selectors.js +3 -1
  251. package/src/store/test/actions.js +31 -22
  252. package/src/utils/device-type.js +46 -0
  253. package/src/utils/media-sideload-from-url/index.js +54 -0
  254. package/src/utils/media-sideload-from-url/test/index.js +162 -0
  255. package/build/components/post-publish-panel/media-util.cjs +0 -72
  256. package/build/components/post-publish-panel/media-util.cjs.map +0 -7
  257. package/build-module/components/post-publish-panel/media-util.mjs +0 -46
  258. package/build-module/components/post-publish-panel/media-util.mjs.map +0 -7
  259. package/build-types/components/post-publish-panel/media-util.d.ts +0 -20
  260. package/build-types/components/post-publish-panel/media-util.d.ts.map +0 -1
  261. package/src/components/post-publish-panel/media-util.js +0 -87
  262. package/src/components/post-publish-panel/test/media-util.js +0 -118
@@ -37,6 +37,8 @@ import {
37
37
  templateTitleField,
38
38
  pageTitleField,
39
39
  patternTitleField,
40
+ patternDescriptionField,
41
+ patternSyncStatusField,
40
42
  notesField,
41
43
  scheduledDateField,
42
44
  lastEditedDateField,
@@ -265,59 +267,59 @@ export const registerPostTypeSchema =
265
267
  if ( postType === ATTACHMENT_POST_TYPE ) {
266
268
  fields = ORDERED_MEDIA_FIELDS;
267
269
  } else {
270
+ const postTypeSlug = postTypeConfig.slug;
271
+ const isDesignPostType = DESIGN_POST_TYPES.includes( postTypeSlug );
272
+ const isPattern = postTypeSlug === 'wp_block';
273
+
268
274
  fields = [
269
275
  postTypeConfig.supports?.thumbnail &&
270
276
  currentTheme?.theme_supports?.[ 'post-thumbnails' ] &&
271
277
  featuredImageField,
272
- ! DESIGN_POST_TYPES.includes( postTypeConfig.slug ) &&
278
+ ! isDesignPostType &&
273
279
  postTypeConfig.supports?.author &&
274
280
  authorField,
275
- postTypeConfig.slug === 'wp_template' && templateAuthorField,
276
- postTypeConfig.slug === 'wp_template_part' &&
277
- templatePartAuthorField,
278
- ! DESIGN_POST_TYPES.includes( postTypeConfig.slug ) &&
279
- statusField,
280
- ! DESIGN_POST_TYPES.includes( postTypeConfig.slug ) &&
281
- dateField,
282
- ! DESIGN_POST_TYPES.includes( postTypeConfig.slug ) &&
283
- scheduledDateField,
281
+ postTypeSlug === 'wp_template' && templateAuthorField,
282
+ postTypeSlug === 'wp_template_part' && templatePartAuthorField,
283
+ ! isDesignPostType && statusField,
284
+ ! isDesignPostType && dateField,
285
+ ! isDesignPostType && scheduledDateField,
284
286
  lastEditedDateField,
285
- ! DESIGN_POST_TYPES.includes( postTypeConfig.slug ) &&
286
- slugField,
287
- ! DESIGN_POST_TYPES.includes( postTypeConfig.slug ) &&
287
+ ! isDesignPostType && slugField,
288
+ ! isDesignPostType &&
288
289
  postTypeConfig.supports?.excerpt &&
289
290
  excerptField,
291
+ isPattern &&
292
+ postTypeConfig.supports?.excerpt &&
293
+ patternDescriptionField,
290
294
  postTypeConfig.supports?.[ 'page-attributes' ] && parentField,
291
295
  postTypeConfig.supports?.comments && commentStatusField,
292
296
  postTypeConfig.supports?.trackbacks && pingStatusField,
293
297
  ( postTypeConfig.supports?.comments ||
294
298
  postTypeConfig.supports?.trackbacks ) &&
295
299
  discussionField,
296
- ! DESIGN_POST_TYPES.includes( postTypeConfig.slug ) &&
297
- templateField,
300
+ ! isDesignPostType && templateField,
298
301
  postTypeConfig.supports?.[ 'post-formats' ] &&
299
302
  ! disablePostFormats &&
300
303
  formatField,
301
- ! DESIGN_POST_TYPES.includes( postTypeConfig.slug ) &&
304
+ ( ! isDesignPostType || isPattern ) &&
302
305
  postTypeConfig.supports?.editor &&
303
306
  postContentInfoField,
304
- ! DESIGN_POST_TYPES.includes( postTypeConfig.slug ) &&
305
- passwordField,
306
- postTypeConfig.slug === 'post' && stickyField,
307
- postTypeConfig.slug === 'wp_template' && descriptionField,
308
- postTypeConfig.slug === 'wp_template' &&
309
- readOnlyDescriptionField,
307
+ ! isDesignPostType && passwordField,
308
+ postTypeSlug === 'post' && stickyField,
309
+ postTypeSlug === 'wp_template' && descriptionField,
310
+ postTypeSlug === 'wp_template' && readOnlyDescriptionField,
310
311
  // The `home`/`index` template summary exposes a few fields that
311
312
  // target other entities (`root/site` and the posts page).
312
313
  // `DataFormPostSummary` overrides them to read/write the right
313
314
  // entity and to control their visibility.
314
- postTypeConfig.slug === 'wp_template' && postsPageTitleField,
315
- postTypeConfig.slug === 'wp_template' && postsPerPageField,
316
- postTypeConfig.slug === 'wp_template' && siteDiscussionField,
315
+ postTypeSlug === 'wp_template' && postsPageTitleField,
316
+ postTypeSlug === 'wp_template' && postsPerPageField,
317
+ postTypeSlug === 'wp_template' && siteDiscussionField,
317
318
  postTypeConfig.supports?.editor &&
318
319
  postTypeConfig.viewable &&
319
320
  postPreviewField,
320
321
  hasEditorNotesSupport( postTypeConfig.supports ) && notesField,
322
+ isPattern && patternSyncStatusField,
321
323
  ].filter( Boolean );
322
324
  if ( postTypeConfig.supports?.title ) {
323
325
  let _titleField;
@@ -325,7 +327,9 @@ export const registerPostTypeSchema =
325
327
  _titleField = pageTitleField;
326
328
  } else if ( postType === 'wp_template' ) {
327
329
  _titleField = templateTitleField;
328
- } else if ( postType === 'wp_block' ) {
330
+ } else if (
331
+ [ 'wp_block', 'wp_template_part' ].includes( postType )
332
+ ) {
329
333
  _titleField = patternTitleField;
330
334
  } else {
331
335
  _titleField = titleField;
@@ -30,6 +30,9 @@ import {
30
30
  getNotificationArgumentsForTrashFail,
31
31
  } from './utils/notice-builder';
32
32
  import { unlock } from '../lock-unlock';
33
+ import { setCanvasWidth } from './private-actions';
34
+ import { getCanvasWidthByDeviceType } from '../utils/device-type';
35
+
33
36
  /**
34
37
  * Returns an action generator used in signalling that editor has initialized with
35
38
  * the specified post object and editor settings.
@@ -757,9 +760,9 @@ export const setRenderingMode =
757
760
  * @return {Object} Action object.
758
761
  */
759
762
  export function setDeviceType( deviceType ) {
760
- return {
761
- type: 'SET_DEVICE_TYPE',
762
- deviceType,
763
+ return ( { dispatch } ) => {
764
+ const width = getCanvasWidthByDeviceType( deviceType );
765
+ dispatch( setCanvasWidth( width ) );
763
766
  };
764
767
  }
765
768
 
@@ -1,3 +1,9 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __ } from '@wordpress/i18n';
5
+ import { desktop, tablet, mobile } from '@wordpress/icons';
6
+
1
7
  /**
2
8
  * Set of post properties for which edits should assume a merging behavior,
3
9
  * assuming an object value.
@@ -37,3 +43,42 @@ export const DESIGN_POST_TYPES = [
37
43
  PATTERN_POST_TYPE,
38
44
  NAVIGATION_POST_TYPE,
39
45
  ];
46
+
47
+ /*
48
+ * Contains the device types and their corresponding canvas width.
49
+ * Matches the breakpoints in packages/base-styles/_breakpoints.scss,
50
+ * and breakpoints in packages/compose/src/hooks/use-viewport-match/index.js.
51
+ * minus 1 to trigger the media query for device preview.
52
+ *
53
+ * These breakpoints are currently hardcoded, but are expected to become
54
+ * customizable via the `settings.viewport` values in theme.json in the future.
55
+ * See https://github.com/WordPress/gutenberg/pull/79104.
56
+ */
57
+ export const DEVICE_TYPES: Record<
58
+ string,
59
+ {
60
+ value: string;
61
+ label: string;
62
+ icon: React.JSX.Element;
63
+ canvasWidth: number | undefined;
64
+ }
65
+ > = {
66
+ Desktop: {
67
+ value: 'Desktop',
68
+ label: __( 'Desktop' ),
69
+ icon: desktop,
70
+ canvasWidth: undefined,
71
+ },
72
+ Tablet: {
73
+ value: 'Tablet',
74
+ label: __( 'Tablet' ),
75
+ icon: tablet,
76
+ canvasWidth: 782 - 1, // preview for useViewportMatch( 'medium', '<' )
77
+ },
78
+ Mobile: {
79
+ value: 'Mobile',
80
+ label: __( 'Mobile' ),
81
+ icon: mobile,
82
+ canvasWidth: 480 - 1, // preview for useViewportMatch( 'mobile', '<' )
83
+ },
84
+ };
@@ -17,35 +17,13 @@ import { dateI18n, getSettings as getDateSettings } from '@wordpress/date';
17
17
  */
18
18
  import isTemplateRevertable from './utils/is-template-revertable';
19
19
  import { buildRevisionsPageQuery } from './private-selectors';
20
+ import {
21
+ getDeviceTypeByCanvasWidth,
22
+ VIEWPORT_STATE_BY_DEVICE_TYPE,
23
+ } from '../utils/device-type';
20
24
  import { unlock } from '../lock-unlock';
21
25
  export * from '../dataviews/store/private-actions';
22
26
 
23
- const DEVICE_TYPE_BY_VIEWPORT_STATE = {
24
- '@mobile': 'Mobile',
25
- '@tablet': 'Tablet',
26
- };
27
-
28
- /**
29
- * Updates the editor preview device in response to a block-editor viewport
30
- * state signal.
31
- *
32
- * @param {Object} options Viewport state change options.
33
- * @param {string} options.viewport Selected viewport state.
34
- * @param {boolean} options.showStateOnCanvas Whether canvas preview is enabled.
35
- */
36
- export const updateDeviceTypeForViewportState =
37
- ( { viewport = 'default', showStateOnCanvas = true } = {} ) =>
38
- ( { dispatch, registry } ) => {
39
- if ( ! showStateOnCanvas ) {
40
- return;
41
- }
42
-
43
- dispatch.setDeviceType(
44
- DEVICE_TYPE_BY_VIEWPORT_STATE[ viewport ] ?? 'Desktop'
45
- );
46
- unlock( registry.dispatch( blockEditorStore ) ).resetZoomLevel();
47
- };
48
-
49
27
  /**
50
28
  * Returns an action object used to set which template is currently being used/edited.
51
29
  *
@@ -231,9 +209,13 @@ export const saveDirtyEntities =
231
209
  registry
232
210
  .dispatch( blockEditorStore )
233
211
  .__unstableMarkLastChangeAsPersistent();
212
+
234
213
  Promise.all( pendingSavedRecords )
235
- .then( ( values ) => {
236
- return onSave ? onSave( values ) : values;
214
+ .then( async ( values ) => {
215
+ if ( onSave ) {
216
+ await onSave();
217
+ }
218
+ return values;
237
219
  } )
238
220
  .then( ( values ) => {
239
221
  if (
@@ -603,6 +585,34 @@ export function resetStylesNavigation() {
603
585
  };
604
586
  }
605
587
 
588
+ /**
589
+ * Set the width of the canvas.
590
+ *
591
+ * @param {number} width The width of the canvas in pixels.
592
+ */
593
+ export function setCanvasWidth( width ) {
594
+ return ( { dispatch, registry } ) => {
595
+ dispatch( {
596
+ type: 'SET_CANVAS_WIDTH',
597
+ width,
598
+ } );
599
+
600
+ // While Responsive editing is enabled, the canvas width also drives the
601
+ // viewport style state, whether changed via the device preview or by
602
+ // manually resizing the canvas.
603
+ if (
604
+ unlock( registry.select( blockEditorStore ) ).isResponsiveEditing()
605
+ ) {
606
+ const deviceType = getDeviceTypeByCanvasWidth( width );
607
+ unlock(
608
+ registry.dispatch( blockEditorStore )
609
+ ).setStyleStateViewport(
610
+ VIEWPORT_STATE_BY_DEVICE_TYPE[ deviceType ] ?? 'default'
611
+ );
612
+ }
613
+ };
614
+ }
615
+
606
616
  /**
607
617
  * Set the current revision ID for revisions preview mode.
608
618
  * Pass a revision ID to enter revisions mode, or null to exit.
@@ -353,6 +353,16 @@ export function getShowStylebook( state ) {
353
353
  return state.showStylebook ?? false;
354
354
  }
355
355
 
356
+ /**
357
+ * Get the canvas width.
358
+ *
359
+ * @param {Object} state Global application state.
360
+ * @return {number} The canvas width in pixels.
361
+ */
362
+ export function getCanvasWidth( state ) {
363
+ return state.canvasWidth;
364
+ }
365
+
356
366
  /**
357
367
  * Returns the current revisions page number.
358
368
  *
@@ -277,19 +277,17 @@ export function renderingMode( state = 'post-only', action ) {
277
277
  }
278
278
 
279
279
  /**
280
- * Reducer returning the editing canvas device type.
280
+ * Reducer for the canvas width.
281
281
  *
282
- * @param {Object} state Current state.
282
+ * @param {number} state Current state.
283
283
  * @param {Object} action Dispatched action.
284
- *
285
- * @return {Object} Updated state.
284
+ * @return {number} Updated state.
286
285
  */
287
- export function deviceType( state = 'Desktop', action ) {
286
+ export function canvasWidth( state = undefined, action ) {
288
287
  switch ( action.type ) {
289
- case 'SET_DEVICE_TYPE':
290
- return action.deviceType;
288
+ case 'SET_CANVAS_WIDTH':
289
+ return action.width;
291
290
  }
292
-
293
291
  return state;
294
292
  }
295
293
 
@@ -500,7 +498,7 @@ export default combineReducers( {
500
498
  editorSettings,
501
499
  postAutosavingLock,
502
500
  renderingMode,
503
- deviceType,
501
+ canvasWidth,
504
502
  removedPanels,
505
503
  blockInserterPanel,
506
504
  inserterSidebarToggleRef,
@@ -29,6 +29,7 @@ import { getPostRawValue } from './reducer';
29
29
  import { getTemplatePartIcon } from '../utils/get-template-part-icon';
30
30
  import { unlock } from '../lock-unlock';
31
31
  import { getTemplateInfo } from '../utils/get-template-info';
32
+ import { getDeviceTypeByCanvasWidth } from '../utils/device-type';
32
33
 
33
34
  /**
34
35
  * Shared reference to an empty object for cases where it is important to avoid
@@ -1352,7 +1353,8 @@ export const getDeviceType = createRegistrySelector(
1352
1353
  if ( isZoomOut ) {
1353
1354
  return 'Desktop';
1354
1355
  }
1355
- return state.deviceType;
1356
+ const canvasWidth = state.canvasWidth;
1357
+ return getDeviceTypeByCanvasWidth( canvasWidth );
1356
1358
  }
1357
1359
  );
1358
1360
 
@@ -12,8 +12,8 @@ import { store as preferencesStore } from '@wordpress/preferences';
12
12
  * Internal dependencies
13
13
  */
14
14
 
15
- import * as actions from '../actions';
16
15
  import { store as editorStore } from '..';
16
+ import * as actions from '../actions';
17
17
  import { unlock } from '../../lock-unlock';
18
18
 
19
19
  const postId = 44;
@@ -64,36 +64,45 @@ const getMethod = ( options ) =>
64
64
  options.headers?.[ 'X-HTTP-Method-Override' ] || options.method || 'GET';
65
65
 
66
66
  describe( 'Post actions', () => {
67
- describe( 'updateDeviceTypeForViewportState', () => {
68
- it( 'updates the editor device type for a viewport state', () => {
67
+ describe( 'setCanvasWidth', () => {
68
+ it( 'syncs the viewport style state while Responsive editing is enabled', () => {
69
69
  const registry = createRegistryWithStores();
70
+ const getViewport = () =>
71
+ unlock(
72
+ registry.select( blockEditorStore )
73
+ ).getStyleStateViewport();
74
+ const setCanvasWidth = ( width ) =>
75
+ unlock( registry.dispatch( editorStore ) ).setCanvasWidth(
76
+ width
77
+ );
70
78
 
71
79
  unlock(
72
- registry.dispatch( editorStore )
73
- ).updateDeviceTypeForViewportState( {
74
- viewport: '@mobile',
75
- showStateOnCanvas: true,
76
- } );
80
+ registry.dispatch( blockEditorStore )
81
+ ).setResponsiveEditing( true );
77
82
 
78
- expect( registry.select( editorStore ).getDeviceType() ).toBe(
79
- 'Mobile'
80
- );
83
+ // A tablet-sized canvas selects the tablet viewport.
84
+ setCanvasWidth( 600 );
85
+ expect( getViewport() ).toBe( '@tablet' );
86
+
87
+ // A mobile-sized canvas selects the mobile viewport.
88
+ setCanvasWidth( 400 );
89
+ expect( getViewport() ).toBe( '@mobile' );
90
+
91
+ // A full-width (desktop) canvas resets to the default viewport.
92
+ setCanvasWidth( undefined );
93
+ expect( getViewport() ).toBe( 'default' );
81
94
  } );
82
95
 
83
- it( 'keeps the editor device type when canvas preview is disabled', () => {
96
+ it( 'leaves the viewport style state untouched while Responsive editing is disabled', () => {
84
97
  const registry = createRegistryWithStores();
85
- registry.dispatch( editorStore ).setDeviceType( 'Tablet' );
98
+ const getViewport = () =>
99
+ unlock(
100
+ registry.select( blockEditorStore )
101
+ ).getStyleStateViewport();
86
102
 
87
- unlock(
88
- registry.dispatch( editorStore )
89
- ).updateDeviceTypeForViewportState( {
90
- viewport: '@mobile',
91
- showStateOnCanvas: false,
92
- } );
103
+ unlock( registry.dispatch( editorStore ) ).setCanvasWidth( 400 );
93
104
 
94
- expect( registry.select( editorStore ).getDeviceType() ).toBe(
95
- 'Tablet'
96
- );
105
+ expect( getViewport() ).toBe( 'default' );
97
106
  } );
98
107
  } );
99
108
 
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import { DEVICE_TYPES } from '../store/constants';
5
+
6
+ /**
7
+ * Maps a device preview type to its corresponding viewport style state. Used
8
+ * when Responsive editing is enabled so the device preview drives which
9
+ * viewport block style edits are applied to.
10
+ *
11
+ * @type {Object}
12
+ */
13
+ export const VIEWPORT_STATE_BY_DEVICE_TYPE = {
14
+ Desktop: 'default',
15
+ Tablet: '@tablet',
16
+ Mobile: '@mobile',
17
+ };
18
+
19
+ /**
20
+ * Gets the most appropriate device type based on the canvas width.
21
+ *
22
+ * @param {number} canvasWidth The canvas width in pixels.
23
+ * @return {string} The device type.
24
+ */
25
+ export function getDeviceTypeByCanvasWidth( canvasWidth ) {
26
+ // Mobile
27
+ if ( canvasWidth && canvasWidth <= DEVICE_TYPES.Mobile.canvasWidth ) {
28
+ return DEVICE_TYPES.Mobile.value;
29
+ }
30
+ // Tablet
31
+ if ( canvasWidth && canvasWidth <= DEVICE_TYPES.Tablet.canvasWidth ) {
32
+ return DEVICE_TYPES.Tablet.value;
33
+ }
34
+ // Desktop
35
+ return DEVICE_TYPES.Desktop.value;
36
+ }
37
+
38
+ /**
39
+ * Get the canvas width by device type.
40
+ *
41
+ * @param {string} deviceType The device type.
42
+ * @return {number|undefined} The canvas width in pixels.
43
+ */
44
+ export function getCanvasWidthByDeviceType( deviceType ) {
45
+ return DEVICE_TYPES[ deviceType ]?.canvasWidth;
46
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import apiFetch from '@wordpress/api-fetch';
5
+ import { select } from '@wordpress/data';
6
+ import { transformAttachment } from '@wordpress/media-utils';
7
+
8
+ /**
9
+ * Internal dependencies
10
+ */
11
+ import { store as editorStore } from '../../store';
12
+
13
+ const noop = () => {};
14
+
15
+ /**
16
+ * Sideloads an external image into the media library from its URL.
17
+ *
18
+ * The server downloads the remote file, which avoids a cross-origin browser
19
+ * fetch that fails when the editor is cross-origin isolated. Since the server
20
+ * owns the upload it also generates sub-sizes, as with a regular upload.
21
+ *
22
+ * @param {Object} $0 Parameters object.
23
+ * @param {string} $0.url URL of the external image to sideload.
24
+ * @param {Function} $0.onSuccess Function called with the new attachment on success.
25
+ * @param {Function} $0.onError Function called with an error message on failure.
26
+ */
27
+ export default function mediaSideloadFromUrl( {
28
+ url,
29
+ onSuccess,
30
+ onError = noop,
31
+ } ) {
32
+ const currentPost = select( editorStore ).getCurrentPost();
33
+ // Templates and template parts store their numerical ID in `wp_id`.
34
+ const currentPostId =
35
+ typeof currentPost?.id === 'number'
36
+ ? currentPost.id
37
+ : currentPost?.wp_id;
38
+ const postData = currentPostId ? { post: currentPostId } : {};
39
+
40
+ apiFetch( {
41
+ path: '/wp/v2/media',
42
+ method: 'POST',
43
+ data: {
44
+ url,
45
+ ...postData,
46
+ },
47
+ } )
48
+ .then( ( attachment ) => {
49
+ onSuccess?.( transformAttachment( attachment ) );
50
+ } )
51
+ .catch( ( error ) => {
52
+ onError( error?.message || error );
53
+ } );
54
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import apiFetch from '@wordpress/api-fetch';
5
+ import { select } from '@wordpress/data';
6
+ import { transformAttachment } from '@wordpress/media-utils';
7
+
8
+ /**
9
+ * Internal dependencies
10
+ */
11
+ import mediaSideloadFromUrl from '..';
12
+
13
+ jest.mock( '@wordpress/api-fetch', () => jest.fn() );
14
+ jest.mock( '@wordpress/data', () => ( {
15
+ select: jest.fn(),
16
+ } ) );
17
+ /*
18
+ * The store and transformAttachment are mocked so this unit test does not pull
19
+ * in the full @wordpress/media-utils / core-data import chain (which needs the
20
+ * real @wordpress/data). transformAttachment's mapping is covered in
21
+ * @wordpress/media-utils; here we only verify mediaSideloadFromUrl forwards
22
+ * whatever it returns.
23
+ */
24
+ jest.mock( '../../../store', () => ( { store: 'core/editor' } ) );
25
+ jest.mock( '@wordpress/media-utils', () => ( {
26
+ transformAttachment: jest.fn( ( attachment ) => ( {
27
+ id: attachment.id,
28
+ url: attachment.source_url,
29
+ alt: attachment.alt_text,
30
+ } ) ),
31
+ } ) );
32
+
33
+ const mockRestAttachment = {
34
+ id: 123,
35
+ alt_text: 'An external photo.',
36
+ caption: { raw: '' },
37
+ title: { raw: 'photo' },
38
+ source_url: 'https://example.com/wp-content/uploads/photo.jpg',
39
+ };
40
+
41
+ /**
42
+ * Sets the post returned by `getCurrentPost()` for a test.
43
+ *
44
+ * @param {Object|undefined} post The post object, or undefined for no post.
45
+ */
46
+ function mockCurrentPost( post ) {
47
+ select.mockReturnValue( {
48
+ getCurrentPost: () => post,
49
+ } );
50
+ }
51
+
52
+ describe( 'mediaSideloadFromUrl', () => {
53
+ beforeEach( () => {
54
+ jest.clearAllMocks();
55
+ mockCurrentPost( { id: 42 } );
56
+ } );
57
+
58
+ it( 'should POST to /wp/v2/media with the url, leaving sub-size generation to the server', async () => {
59
+ apiFetch.mockResolvedValue( mockRestAttachment );
60
+
61
+ await new Promise( ( resolve ) => {
62
+ mediaSideloadFromUrl( {
63
+ url: 'https://example.com/photo.jpg',
64
+ onSuccess: resolve,
65
+ } );
66
+ } );
67
+
68
+ expect( apiFetch ).toHaveBeenCalledWith( {
69
+ path: '/wp/v2/media',
70
+ method: 'POST',
71
+ data: {
72
+ url: 'https://example.com/photo.jpg',
73
+ post: 42,
74
+ },
75
+ } );
76
+ } );
77
+
78
+ it( 'should pass the transformed attachment to onSuccess', async () => {
79
+ apiFetch.mockResolvedValue( mockRestAttachment );
80
+
81
+ const media = await new Promise( ( resolve ) => {
82
+ mediaSideloadFromUrl( {
83
+ url: 'https://example.com/photo.jpg',
84
+ onSuccess: resolve,
85
+ } );
86
+ } );
87
+
88
+ /*
89
+ * The raw REST attachment is run through transformAttachment, which maps
90
+ * it to the editor shape the image block expects (url, alt).
91
+ */
92
+ expect( transformAttachment ).toHaveBeenCalledWith(
93
+ mockRestAttachment
94
+ );
95
+ expect( media ).toMatchObject( {
96
+ id: 123,
97
+ url: 'https://example.com/wp-content/uploads/photo.jpg',
98
+ alt: 'An external photo.',
99
+ } );
100
+ } );
101
+
102
+ it( 'should fall back to wp_id when the post id is not numeric', async () => {
103
+ apiFetch.mockResolvedValue( mockRestAttachment );
104
+ // Templates and template parts expose their numeric id as `wp_id`.
105
+ mockCurrentPost( { id: 'my-theme//header', wp_id: 99 } );
106
+
107
+ await new Promise( ( resolve ) => {
108
+ mediaSideloadFromUrl( {
109
+ url: 'https://example.com/photo.jpg',
110
+ onSuccess: resolve,
111
+ } );
112
+ } );
113
+
114
+ expect( apiFetch ).toHaveBeenCalledWith(
115
+ expect.objectContaining( {
116
+ data: expect.objectContaining( { post: 99 } ),
117
+ } )
118
+ );
119
+ } );
120
+
121
+ it( 'should omit the post param when there is no current post', async () => {
122
+ apiFetch.mockResolvedValue( mockRestAttachment );
123
+ mockCurrentPost( undefined );
124
+
125
+ await new Promise( ( resolve ) => {
126
+ mediaSideloadFromUrl( {
127
+ url: 'https://example.com/photo.jpg',
128
+ onSuccess: resolve,
129
+ } );
130
+ } );
131
+
132
+ const { data } = apiFetch.mock.calls[ 0 ][ 0 ];
133
+ expect( data ).not.toHaveProperty( 'post' );
134
+ } );
135
+
136
+ it( 'should pass the error message to onError on failure', async () => {
137
+ apiFetch.mockRejectedValue(
138
+ new Error( 'Could not download the image.' )
139
+ );
140
+
141
+ const message = await new Promise( ( resolve ) => {
142
+ mediaSideloadFromUrl( {
143
+ url: 'https://example.com/photo.jpg',
144
+ onSuccess: () => {},
145
+ onError: resolve,
146
+ } );
147
+ } );
148
+
149
+ expect( message ).toBe( 'Could not download the image.' );
150
+ } );
151
+
152
+ it( 'should not throw when onError is omitted and the request fails', async () => {
153
+ apiFetch.mockRejectedValue( new Error( 'Network error' ) );
154
+
155
+ expect( () =>
156
+ mediaSideloadFromUrl( { url: 'https://example.com/photo.jpg' } )
157
+ ).not.toThrow();
158
+
159
+ // Let the rejected promise settle so the default noop onError runs.
160
+ await Promise.resolve();
161
+ } );
162
+ } );