@wordpress/edit-site 4.9.0 → 4.10.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 (105) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/add-new-template/add-custom-generic-template-modal.js +84 -0
  3. package/build/components/add-new-template/add-custom-generic-template-modal.js.map +1 -0
  4. package/build/components/add-new-template/add-custom-template-modal.js +92 -53
  5. package/build/components/add-new-template/add-custom-template-modal.js.map +1 -1
  6. package/build/components/add-new-template/new-template.js +77 -81
  7. package/build/components/add-new-template/new-template.js.map +1 -1
  8. package/build/components/add-new-template/utils.js +310 -44
  9. package/build/components/add-new-template/utils.js.map +1 -1
  10. package/build/components/code-editor/index.js +17 -4
  11. package/build/components/code-editor/index.js.map +1 -1
  12. package/build/components/editor/index.js +16 -0
  13. package/build/components/editor/index.js.map +1 -1
  14. package/build/components/error-boundary/index.js +6 -0
  15. package/build/components/error-boundary/index.js.map +1 -1
  16. package/build/components/global-styles/dimensions-panel.js +2 -6
  17. package/build/components/global-styles/dimensions-panel.js.map +1 -1
  18. package/build/components/global-styles/global-styles-provider.js +4 -2
  19. package/build/components/global-styles/global-styles-provider.js.map +1 -1
  20. package/build/components/global-styles/hooks.js +10 -1
  21. package/build/components/global-styles/hooks.js.map +1 -1
  22. package/build/components/global-styles/screen-color-palette.js +13 -17
  23. package/build/components/global-styles/screen-color-palette.js.map +1 -1
  24. package/build/components/global-styles/screen-colors.js +9 -1
  25. package/build/components/global-styles/screen-colors.js.map +1 -1
  26. package/build/components/global-styles/screen-link-color.js +48 -14
  27. package/build/components/global-styles/screen-link-color.js.map +1 -1
  28. package/build/components/global-styles/use-global-styles-output.js +171 -33
  29. package/build/components/global-styles/use-global-styles-output.js.map +1 -1
  30. package/build/components/global-styles/utils.js +1 -1
  31. package/build/components/global-styles/utils.js.map +1 -1
  32. package/build/components/keyboard-shortcut-help-modal/index.js +1 -3
  33. package/build/components/keyboard-shortcut-help-modal/index.js.map +1 -1
  34. package/build/components/template-details/edit-template-title.js +11 -3
  35. package/build/components/template-details/edit-template-title.js.map +1 -1
  36. package/build/components/template-details/index.js +1 -20
  37. package/build/components/template-details/index.js.map +1 -1
  38. package/build/store/selectors.js +4 -1
  39. package/build/store/selectors.js.map +1 -1
  40. package/build-module/components/add-new-template/add-custom-generic-template-modal.js +77 -0
  41. package/build-module/components/add-new-template/add-custom-generic-template-modal.js.map +1 -0
  42. package/build-module/components/add-new-template/add-custom-template-modal.js +92 -53
  43. package/build-module/components/add-new-template/add-custom-template-modal.js.map +1 -1
  44. package/build-module/components/add-new-template/new-template.js +80 -83
  45. package/build-module/components/add-new-template/new-template.js.map +1 -1
  46. package/build-module/components/add-new-template/utils.js +286 -40
  47. package/build-module/components/add-new-template/utils.js.map +1 -1
  48. package/build-module/components/code-editor/index.js +18 -5
  49. package/build-module/components/code-editor/index.js.map +1 -1
  50. package/build-module/components/editor/index.js +16 -0
  51. package/build-module/components/editor/index.js.map +1 -1
  52. package/build-module/components/error-boundary/index.js +5 -0
  53. package/build-module/components/error-boundary/index.js.map +1 -1
  54. package/build-module/components/global-styles/dimensions-panel.js +2 -6
  55. package/build-module/components/global-styles/dimensions-panel.js.map +1 -1
  56. package/build-module/components/global-styles/global-styles-provider.js +4 -2
  57. package/build-module/components/global-styles/global-styles-provider.js.map +1 -1
  58. package/build-module/components/global-styles/hooks.js +10 -1
  59. package/build-module/components/global-styles/hooks.js.map +1 -1
  60. package/build-module/components/global-styles/screen-color-palette.js +14 -19
  61. package/build-module/components/global-styles/screen-color-palette.js.map +1 -1
  62. package/build-module/components/global-styles/screen-colors.js +9 -1
  63. package/build-module/components/global-styles/screen-colors.js.map +1 -1
  64. package/build-module/components/global-styles/screen-link-color.js +47 -14
  65. package/build-module/components/global-styles/screen-link-color.js.map +1 -1
  66. package/build-module/components/global-styles/use-global-styles-output.js +171 -35
  67. package/build-module/components/global-styles/use-global-styles-output.js.map +1 -1
  68. package/build-module/components/global-styles/utils.js +2 -2
  69. package/build-module/components/global-styles/utils.js.map +1 -1
  70. package/build-module/components/keyboard-shortcut-help-modal/index.js +1 -2
  71. package/build-module/components/keyboard-shortcut-help-modal/index.js.map +1 -1
  72. package/build-module/components/template-details/edit-template-title.js +12 -3
  73. package/build-module/components/template-details/edit-template-title.js.map +1 -1
  74. package/build-module/components/template-details/index.js +2 -21
  75. package/build-module/components/template-details/index.js.map +1 -1
  76. package/build-module/store/selectors.js +5 -2
  77. package/build-module/store/selectors.js.map +1 -1
  78. package/build-style/style-rtl.css +21 -23
  79. package/build-style/style.css +21 -23
  80. package/package.json +29 -29
  81. package/src/components/add-new-template/add-custom-generic-template-modal.js +97 -0
  82. package/src/components/add-new-template/add-custom-template-modal.js +92 -58
  83. package/src/components/add-new-template/new-template.js +142 -94
  84. package/src/components/add-new-template/style.scss +21 -0
  85. package/src/components/add-new-template/utils.js +290 -46
  86. package/src/components/code-editor/index.js +15 -5
  87. package/src/components/editor/index.js +11 -0
  88. package/src/components/error-boundary/index.js +5 -0
  89. package/src/components/global-styles/dimensions-panel.js +2 -7
  90. package/src/components/global-styles/global-styles-provider.js +8 -9
  91. package/src/components/global-styles/hooks.js +15 -0
  92. package/src/components/global-styles/screen-color-palette.js +25 -27
  93. package/src/components/global-styles/screen-colors.js +9 -3
  94. package/src/components/global-styles/screen-link-color.js +65 -23
  95. package/src/components/global-styles/style.scss +7 -11
  96. package/src/components/global-styles/test/use-global-styles-output.js +168 -0
  97. package/src/components/global-styles/use-global-styles-output.js +234 -59
  98. package/src/components/global-styles/utils.js +2 -2
  99. package/src/components/keyboard-shortcut-help-modal/index.js +1 -2
  100. package/src/components/keyboard-shortcut-help-modal/style.scss +0 -5
  101. package/src/components/list/style.scss +0 -8
  102. package/src/components/template-details/edit-template-title.js +10 -2
  103. package/src/components/template-details/index.js +4 -21
  104. package/src/components/test/error-boundary.js +38 -0
  105. package/src/store/selectors.js +11 -5
@@ -8,16 +8,138 @@ import { get } from 'lodash';
8
8
  */
9
9
  import { useSelect } from '@wordpress/data';
10
10
  import { store as coreStore } from '@wordpress/core-data';
11
+ import { store as editorStore } from '@wordpress/editor';
11
12
  import { decodeEntities } from '@wordpress/html-entities';
12
13
  import { useMemo } from '@wordpress/element';
14
+ import { __, sprintf } from '@wordpress/i18n';
15
+ import { blockMeta, post } from '@wordpress/icons';
13
16
 
14
- export const usePostTypes = () => {
17
+ /**
18
+ * @typedef IHasNameAndId
19
+ * @property {string|number} id The entity's id.
20
+ * @property {string} name The entity's name.
21
+ */
22
+
23
+ /**
24
+ * Helper util to map records to add a `name` prop from a
25
+ * provided path, in order to handle all entities in the same
26
+ * fashion(implementing`IHasNameAndId` interface).
27
+ *
28
+ * @param {Object[]} entities The array of entities.
29
+ * @param {string} path The path to map a `name` property from the entity.
30
+ * @return {IHasNameAndId[]} An array of enitities that now implement the `IHasNameAndId` interface.
31
+ */
32
+ export const mapToIHasNameAndId = ( entities, path ) => {
33
+ return ( entities || [] ).map( ( entity ) => ( {
34
+ ...entity,
35
+ name: decodeEntities( get( entity, path ) ),
36
+ } ) );
37
+ };
38
+
39
+ /**
40
+ * @typedef {Object} EntitiesInfo
41
+ * @property {boolean} hasEntities If an entity has available records(posts, terms, etc..).
42
+ * @property {number[]} existingEntitiesIds An array of the existing entities ids.
43
+ */
44
+
45
+ /**
46
+ * @typedef {Object} EntityConfig
47
+ * @property {string} entityName The entity's name.
48
+ * @property {Function} getOrderBy Getter for an entity's `orderBy` query parameter, given the object
49
+ * {search} as argument.
50
+ * @property {Function} getIcon Getter function for returning an entity's icon for the menu item.
51
+ * @property {Function} getTitle Getter function for returning an entity's title for the menu item.
52
+ * @property {Function} getDescription Getter function for returning an entity's description for the menu item.
53
+ * @property {string} recordNamePath The path to an entity's properties to use as a `name`. If not provided
54
+ * is assumed that `name` property exists.
55
+ * @property {string} templatePrefix The template prefix to create new templates and check against existing
56
+ * templates. For example custom post types need a `single-` prefix to all
57
+ * templates(`single-post-hello`), whereas `pages` don't (`page-hello`).
58
+ * @property {string} templateSlug If this property is provided, it is going to be used for the creation of
59
+ * new templates and the check against existing templates in the place
60
+ * of the actual entity's `slug`. An example is `Tag` templates where the
61
+ * the Tag's taxonomy slug is `post_tag`, but template hierarchy is based
62
+ * on `tag` alias.
63
+ */
64
+
65
+ const taxonomyBaseConfig = {
66
+ entityName: 'taxonomy',
67
+ getOrderBy: ( { search } ) => ( search ? 'name' : 'count' ),
68
+ getIcon: () => blockMeta,
69
+ getTitle: ( labels ) =>
70
+ sprintf(
71
+ // translators: %s: Name of the taxonomy e.g: "Cagegory".
72
+ __( 'Single taxonomy: %s' ),
73
+ labels.singular_name
74
+ ),
75
+ getDescription: ( labels ) =>
76
+ sprintf(
77
+ // translators: %s: Name of the taxonomy e.g: "Product Categories".
78
+ __( 'Displays a single taxonomy: %s.' ),
79
+ labels.singular_name
80
+ ),
81
+ };
82
+ const postTypeBaseConfig = {
83
+ entityName: 'postType',
84
+ getOrderBy: ( { search } ) => ( search ? 'relevance' : 'modified' ),
85
+ recordNamePath: 'title.rendered',
86
+ // `icon` is the `menu_icon` property of a post type. We
87
+ // only handle `dashicons` for now, even if the `menu_icon`
88
+ // also supports urls and svg as values.
89
+ getIcon: ( _icon ) =>
90
+ _icon?.startsWith( 'dashicons-' ) ? _icon.slice( 10 ) : post,
91
+ getTitle: ( labels ) =>
92
+ sprintf(
93
+ // translators: %s: Name of the post type e.g: "Post".
94
+ __( 'Single item: %s' ),
95
+ labels.singular_name
96
+ ),
97
+ getDescription: ( labels ) =>
98
+ sprintf(
99
+ // translators: %s: Name of the post type e.g: "Post".
100
+ __( 'Displays a single item: %s.' ),
101
+ labels.singular_name
102
+ ),
103
+ };
104
+ export const entitiesConfig = {
105
+ postType: {
106
+ ...postTypeBaseConfig,
107
+ templatePrefix: 'single-',
108
+ },
109
+ page: { ...postTypeBaseConfig },
110
+ taxonomy: {
111
+ ...taxonomyBaseConfig,
112
+ templatePrefix: 'taxonomy-',
113
+ },
114
+ category: { ...taxonomyBaseConfig },
115
+ tag: { ...taxonomyBaseConfig, templateSlug: 'tag' },
116
+ };
117
+
118
+ export const useExistingTemplates = () => {
119
+ return useSelect(
120
+ ( select ) =>
121
+ select( coreStore ).getEntityRecords( 'postType', 'wp_template', {
122
+ per_page: -1,
123
+ } ),
124
+ []
125
+ );
126
+ };
127
+
128
+ export const useDefaultTemplateTypes = () => {
129
+ return useSelect(
130
+ ( select ) =>
131
+ select( editorStore ).__experimentalGetDefaultTemplateTypes(),
132
+ []
133
+ );
134
+ };
135
+
136
+ const usePublicPostTypes = () => {
15
137
  const postTypes = useSelect(
16
138
  ( select ) => select( coreStore ).getPostTypes( { per_page: -1 } ),
17
139
  []
18
140
  );
19
141
  return useMemo( () => {
20
- const excludedPostTypes = [ 'attachment', 'page' ];
142
+ const excludedPostTypes = [ 'attachment' ];
21
143
  return postTypes?.filter(
22
144
  ( { viewable, slug } ) =>
23
145
  viewable && ! excludedPostTypes.includes( slug )
@@ -25,32 +147,97 @@ export const usePostTypes = () => {
25
147
  }, [ postTypes ] );
26
148
  };
27
149
 
150
+ // `page` post type is a special case in the template hierarchy,
151
+ // so we exclude it from the list of post types and we handle it
152
+ // separately.
153
+ export const usePostTypes = () => {
154
+ const postTypes = usePublicPostTypes();
155
+ return useMemo( () => {
156
+ return postTypes?.filter( ( { slug } ) => slug !== 'page' );
157
+ }, [ postTypes ] );
158
+ };
159
+ export const usePostTypePage = () => {
160
+ const postTypes = usePublicPostTypes();
161
+ return useMemo( () => {
162
+ return postTypes?.filter( ( { slug } ) => slug === 'page' );
163
+ }, [ postTypes ] );
164
+ };
165
+
166
+ const usePublicTaxonomies = () => {
167
+ const taxonomies = useSelect(
168
+ ( select ) => select( coreStore ).getTaxonomies( { per_page: -1 } ),
169
+ []
170
+ );
171
+ return useMemo( () => {
172
+ return taxonomies?.filter(
173
+ ( { visibility } ) => visibility?.publicly_queryable
174
+ );
175
+ }, [ taxonomies ] );
176
+ };
177
+
28
178
  /**
29
- * @typedef {Object} PostTypeEntitiesInfo
30
- * @property {boolean} hasEntities If a postType has available entities.
31
- * @property {number[]} existingPosts An array of the existing entities ids.
179
+ * `category` and `post_tag` are handled specifically in template
180
+ * hierarchy so we need to differentiate them and return the rest,
181
+ * e.g. `category-$slug` and `taxonomy-$taxonomy-$term`.
32
182
  */
183
+ export const useTaxonomies = () => {
184
+ const taxonomies = usePublicTaxonomies();
185
+ const specialTaxonomies = [ 'category', 'post_tag' ];
186
+ return useMemo(
187
+ () =>
188
+ taxonomies?.filter(
189
+ ( { slug } ) => ! specialTaxonomies.includes( slug )
190
+ ),
191
+ [ taxonomies ]
192
+ );
193
+ };
194
+
195
+ export const useTaxonomyCategory = () => {
196
+ const taxonomies = usePublicTaxonomies();
197
+ return useMemo(
198
+ () => taxonomies?.filter( ( { slug } ) => slug === 'category' ),
199
+ [ taxonomies ]
200
+ );
201
+ };
202
+ export const useTaxonomyTag = () => {
203
+ const taxonomies = usePublicTaxonomies();
204
+ return useMemo(
205
+ () => taxonomies?.filter( ( { slug } ) => slug === 'post_tag' ),
206
+ [ taxonomies ]
207
+ );
208
+ };
33
209
 
34
210
  /**
35
- * Helper hook that returns information about a post type having
36
- * posts that we can create a specific template for.
211
+ * Helper hook that returns information about an entity having
212
+ * records that we can create a specific template for.
37
213
  *
38
- * First we need to find the existing posts with an associated template,
39
- * to query afterwards for any remaing post, by excluding them.
214
+ * For example we can search for `terms` in `taxonomy` entity or
215
+ * `posts` in `postType` entity.
40
216
  *
41
- * @param {string[]} existingTemplates The existing templates.
42
- * @return {Record<string,PostTypeEntitiesInfo>} An object with the postTypes as `keys` and PostTypeEntitiesInfo as values.
217
+ * First we need to find the existing records with an associated template,
218
+ * to query afterwards for any remaing record, by excluding them.
219
+ *
220
+ * @param {string[]} existingTemplates The existing templates.
221
+ * @param {Object[]} entities The array of entities we need to get extra information.
222
+ * @param {EntityConfig} entityConfig The entity config.
223
+ * @return {Record<string,EntitiesInfo>} An object with the `entities.slug` as `keys` and EntitiesInfo as values.
43
224
  */
44
- export const usePostTypesEntitiesInfo = ( existingTemplates ) => {
45
- const postTypes = usePostTypes();
225
+ const useEntitiesInfo = (
226
+ existingTemplates,
227
+ entities,
228
+ { entityName, templatePrefix, templateSlug }
229
+ ) => {
46
230
  const slugsToExcludePerEntity = useMemo( () => {
47
- return postTypes?.reduce( ( accumulator, _postType ) => {
231
+ return entities?.reduce( ( accumulator, entity ) => {
232
+ let _prefix = `${ templateSlug || entity.slug }-`;
233
+ if ( templatePrefix ) {
234
+ _prefix = templatePrefix + _prefix;
235
+ }
48
236
  const slugsWithTemplates = ( existingTemplates || [] ).reduce(
49
237
  ( _accumulator, existingTemplate ) => {
50
- const prefix = `single-${ _postType.slug }-`;
51
- if ( existingTemplate.slug.startsWith( prefix ) ) {
238
+ if ( existingTemplate.slug.startsWith( _prefix ) ) {
52
239
  _accumulator.push(
53
- existingTemplate.slug.substring( prefix.length )
240
+ existingTemplate.slug.substring( _prefix.length )
54
241
  );
55
242
  }
56
243
  return _accumulator;
@@ -58,68 +245,125 @@ export const usePostTypesEntitiesInfo = ( existingTemplates ) => {
58
245
  []
59
246
  );
60
247
  if ( slugsWithTemplates.length ) {
61
- accumulator[ _postType.slug ] = slugsWithTemplates;
248
+ accumulator[ entity.slug ] = slugsWithTemplates;
62
249
  }
63
250
  return accumulator;
64
251
  }, {} );
65
- }, [ postTypes, existingTemplates ] );
66
- const postsToExcludePerEntity = useSelect(
252
+ }, [ entities, existingTemplates ] );
253
+ const recordsToExcludePerEntity = useSelect(
67
254
  ( select ) => {
68
255
  if ( ! slugsToExcludePerEntity ) {
69
256
  return;
70
257
  }
71
- const postsToExclude = Object.entries(
72
- slugsToExcludePerEntity
73
- ).reduce( ( accumulator, [ slug, slugsWithTemplates ] ) => {
74
- const postsWithTemplates = select( coreStore ).getEntityRecords(
75
- 'postType',
76
- slug,
77
- {
258
+ return Object.entries( slugsToExcludePerEntity ).reduce(
259
+ ( accumulator, [ slug, slugsWithTemplates ] ) => {
260
+ const postsWithTemplates = select(
261
+ coreStore
262
+ ).getEntityRecords( entityName, slug, {
78
263
  _fields: 'id',
79
264
  context: 'view',
80
265
  slug: slugsWithTemplates,
266
+ } );
267
+ if ( postsWithTemplates?.length ) {
268
+ accumulator[ slug ] = postsWithTemplates;
81
269
  }
82
- );
83
- if ( postsWithTemplates?.length ) {
84
- accumulator[ slug ] = postsWithTemplates;
85
- }
86
- return accumulator;
87
- }, {} );
88
- return postsToExclude;
270
+ return accumulator;
271
+ },
272
+ {}
273
+ );
89
274
  },
90
275
  [ slugsToExcludePerEntity ]
91
276
  );
92
277
  const entitiesInfo = useSelect(
93
278
  ( select ) => {
94
- return postTypes?.reduce( ( accumulator, { slug } ) => {
95
- const existingPosts =
96
- postsToExcludePerEntity?.[ slug ]?.map(
279
+ return entities?.reduce( ( accumulator, { slug } ) => {
280
+ const existingEntitiesIds =
281
+ recordsToExcludePerEntity?.[ slug ]?.map(
97
282
  ( { id } ) => id
98
283
  ) || [];
99
284
  accumulator[ slug ] = {
100
285
  hasEntities: !! select( coreStore ).getEntityRecords(
101
- 'postType',
286
+ entityName,
102
287
  slug,
103
288
  {
104
289
  per_page: 1,
105
290
  _fields: 'id',
106
291
  context: 'view',
107
- exclude: existingPosts,
292
+ exclude: existingEntitiesIds,
108
293
  }
109
294
  )?.length,
110
- existingPosts,
295
+ existingEntitiesIds,
111
296
  };
112
297
  return accumulator;
113
298
  }, {} );
114
299
  },
115
- [ postTypes, postsToExcludePerEntity ]
300
+ [ entities, recordsToExcludePerEntity ]
116
301
  );
117
302
  return entitiesInfo;
118
303
  };
119
304
 
120
- export const mapToIHasNameAndId = ( entities, path ) => {
121
- return ( entities || [] ).map( ( entity ) => ( {
122
- ...entity,
123
- name: decodeEntities( get( entity, path ) ),
124
- } ) );
305
+ export const useExtraTemplates = (
306
+ entities,
307
+ entityConfig,
308
+ onClickMenuItem
309
+ ) => {
310
+ const existingTemplates = useExistingTemplates();
311
+ const defaultTemplateTypes = useDefaultTemplateTypes();
312
+ const entitiesInfo = useEntitiesInfo(
313
+ existingTemplates,
314
+ entities,
315
+ entityConfig
316
+ );
317
+ const existingTemplateSlugs = ( existingTemplates || [] ).map(
318
+ ( { slug } ) => slug
319
+ );
320
+ const extraTemplates = ( entities || [] ).reduce(
321
+ ( accumulator, entity ) => {
322
+ const { slug, labels, icon } = entity;
323
+ const slugForGeneralTemplate = entityConfig.templateSlug || slug;
324
+ // We need to check if the general template is part of the
325
+ // defaultTemplateTypes. If it is, just use that info and
326
+ // augment it with the specific template functionality.
327
+ const defaultTemplateType = defaultTemplateTypes?.find(
328
+ ( { slug: _slug } ) => _slug === slugForGeneralTemplate
329
+ );
330
+ const generalTemplateSlug =
331
+ defaultTemplateType?.slug ||
332
+ `${ entityConfig.templatePrefix }${ slug }`;
333
+ const hasGeneralTemplate =
334
+ existingTemplateSlugs?.includes( generalTemplateSlug );
335
+ const menuItem = defaultTemplateType
336
+ ? { ...defaultTemplateType }
337
+ : {
338
+ slug: generalTemplateSlug,
339
+ title: entityConfig.getTitle( labels ),
340
+ description: entityConfig.getDescription( labels ),
341
+ icon: entityConfig.getIcon?.( icon ),
342
+ };
343
+ const hasEntities = entitiesInfo?.[ slug ]?.hasEntities;
344
+ // We have a different template creation flow only if they have entities.
345
+ if ( hasEntities ) {
346
+ menuItem.onClick = ( template ) => {
347
+ onClickMenuItem( {
348
+ type: entityConfig.entityName,
349
+ slug,
350
+ config: entityConfig,
351
+ labels,
352
+ hasGeneralTemplate,
353
+ template,
354
+ postsToExclude:
355
+ entitiesInfo[ slug ].existingEntitiesIds,
356
+ } );
357
+ };
358
+ }
359
+ // We don't need to add the menu item if there are no
360
+ // entities and the general template exists.
361
+ if ( ! hasGeneralTemplate || hasEntities ) {
362
+ accumulator.push( menuItem );
363
+ }
364
+ return accumulator;
365
+ },
366
+ []
367
+ );
368
+ return extraTemplates;
125
369
  };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { parse } from '@wordpress/blocks';
4
+ import { parse, __unstableSerializeAndClean } from '@wordpress/blocks';
5
5
  import { useEntityBlockEditor, useEntityProp } from '@wordpress/core-data';
6
6
  import { useSelect, useDispatch } from '@wordpress/data';
7
7
  import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
@@ -32,10 +32,20 @@ export default function CodeEditor() {
32
32
  'postType',
33
33
  templateType
34
34
  );
35
- const content =
36
- contentStructure instanceof Function
37
- ? contentStructure( { blocks } )
38
- : contentStructure;
35
+
36
+ // Replicates the logic found in getEditedPostContent().
37
+ let content;
38
+ if ( contentStructure instanceof Function ) {
39
+ content = contentStructure( { blocks } );
40
+ } else if ( blocks ) {
41
+ // If we have parsed blocks already, they should be our source of truth.
42
+ // Parsing applies block deprecations and legacy block conversions that
43
+ // unparsed content will not have.
44
+ content = __unstableSerializeAndClean( blocks );
45
+ } else {
46
+ content = contentStructure;
47
+ }
48
+
39
49
  const { switchEditorMode } = useDispatch( editSiteStore );
40
50
  return (
41
51
  <div className="edit-site-code-editor">
@@ -47,6 +47,17 @@ import { GlobalStylesProvider } from '../global-styles/global-styles-provider';
47
47
  import useTitle from '../routes/use-title';
48
48
 
49
49
  const interfaceLabels = {
50
+ /* translators: accessibility text for the editor top bar landmark region. */
51
+ header: __( 'Editor top bar' ),
52
+ /* translators: accessibility text for the editor content landmark region. */
53
+ body: __( 'Editor content' ),
54
+ /* translators: accessibility text for the editor settings landmark region. */
55
+ sidebar: __( 'Editor settings' ),
56
+ /* translators: accessibility text for the editor publish landmark region. */
57
+ actions: __( 'Editor publish' ),
58
+ /* translators: accessibility text for the editor footer landmark region. */
59
+ footer: __( 'Editor footer' ),
60
+ /* translators: accessibility text for the navigation sidebar landmark region. */
50
61
  drawer: __( 'Navigation Sidebar' ),
51
62
  };
52
63
 
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { Component } from '@wordpress/element';
5
5
  import { __ } from '@wordpress/i18n';
6
+ import { doAction } from '@wordpress/hooks';
6
7
 
7
8
  /**
8
9
  * Internal dependencies
@@ -20,6 +21,10 @@ export default class ErrorBoundary extends Component {
20
21
  };
21
22
  }
22
23
 
24
+ componentDidCatch( error ) {
25
+ doAction( 'editor.ErrorBoundary.errorLogged', error );
26
+ }
27
+
23
28
  static getDerivedStateFromError( error ) {
24
29
  return { error };
25
30
  }
@@ -43,13 +43,8 @@ function useHasMargin( name ) {
43
43
  function useHasGap( name ) {
44
44
  const supports = getSupportedGlobalStylesPanels( name );
45
45
  const [ settings ] = useSetting( 'spacing.blockGap', name );
46
- // Do not show the gap control panel for block-level global styles
47
- // as they do not work on the frontend.
48
- // See: https://github.com/WordPress/gutenberg/pull/39845.
49
- // We can revert this condition when they're working again.
50
- return !! name
51
- ? false
52
- : settings && supports.includes( '--wp--style--block-gap' );
46
+
47
+ return settings && supports.includes( 'blockGap' );
53
48
  }
54
49
 
55
50
  function filterValuesBySides( values, sides ) {
@@ -1,14 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import {
5
- mergeWith,
6
- pickBy,
7
- isEmpty,
8
- isObject,
9
- identity,
10
- mapValues,
11
- } from 'lodash';
4
+ import { mergeWith, pickBy, isEmpty, mapValues } from 'lodash';
12
5
 
13
6
  /**
14
7
  * WordPress dependencies
@@ -22,6 +15,8 @@ import { store as coreStore } from '@wordpress/core-data';
22
15
  */
23
16
  import { GlobalStylesContext } from './context';
24
17
 
18
+ const identity = ( x ) => x;
19
+
25
20
  function mergeTreesCustomizer( _, srcValue ) {
26
21
  // We only pass as arrays the presets,
27
22
  // in which case we want the new array of values
@@ -36,7 +31,11 @@ export function mergeBaseAndUserConfigs( base, user ) {
36
31
  }
37
32
 
38
33
  const cleanEmptyObject = ( object ) => {
39
- if ( ! isObject( object ) || Array.isArray( object ) ) {
34
+ if (
35
+ object === null ||
36
+ typeof object !== 'object' ||
37
+ Array.isArray( object )
38
+ ) {
40
39
  return object;
41
40
  }
42
41
  const cleanedNestedObjects = pickBy(
@@ -182,6 +182,21 @@ export function getSupportedGlobalStylesPanels( name ) {
182
182
  }
183
183
 
184
184
  const supportKeys = [];
185
+
186
+ // Check for blockGap support.
187
+ // Block spacing support doesn't map directly to a single style property, so needs to be handled separately.
188
+ // Also, only allow `blockGap` support if serialization has not been skipped, to be sure global spacing can be applied.
189
+ if (
190
+ blockType?.supports?.spacing?.blockGap &&
191
+ blockType?.supports?.spacing?.__experimentalSkipSerialization !==
192
+ true &&
193
+ ! blockType?.supports?.spacing?.__experimentalSkipSerialization?.some?.(
194
+ ( spacingType ) => spacingType === 'blockGap'
195
+ )
196
+ ) {
197
+ supportKeys.push( 'blockGap' );
198
+ }
199
+
185
200
  Object.keys( STYLE_PROPERTY ).forEach( ( styleName ) => {
186
201
  if ( ! STYLE_PROPERTY[ styleName ].support ) {
187
202
  return;
@@ -2,11 +2,7 @@
2
2
  * WordPress dependencies
3
3
  */
4
4
  import { __ } from '@wordpress/i18n';
5
- import {
6
- __experimentalToggleGroupControl as ToggleGroupControl,
7
- __experimentalToggleGroupControlOption as ToggleGroupControlOption,
8
- } from '@wordpress/components';
9
- import { useState } from '@wordpress/element';
5
+ import { TabPanel } from '@wordpress/components';
10
6
 
11
7
  /**
12
8
  * Internal dependencies
@@ -16,8 +12,6 @@ import GradientPalettePanel from './gradients-palette-panel';
16
12
  import ScreenHeader from './header';
17
13
 
18
14
  function ScreenColorPalette( { name } ) {
19
- const [ currentTab, setCurrentTab ] = useState( 'solid' );
20
-
21
15
  return (
22
16
  <>
23
17
  <ScreenHeader
@@ -26,27 +20,31 @@ function ScreenColorPalette( { name } ) {
26
20
  'Palettes are used to provide default color options for blocks and various design tools. Here you can edit the colors with their labels.'
27
21
  ) }
28
22
  />
29
- <ToggleGroupControl
30
- className="edit-site-screen-color-palette-toggle"
31
- value={ currentTab }
32
- onChange={ setCurrentTab }
33
- label={ __( 'Select palette type' ) }
34
- hideLabelFromVision
35
- isBlock
23
+ <TabPanel
24
+ tabs={ [
25
+ {
26
+ name: 'solid',
27
+ title: 'Solid color',
28
+ value: 'solid',
29
+ },
30
+ {
31
+ name: 'gradient',
32
+ title: 'Gradient',
33
+ value: 'gradient',
34
+ },
35
+ ] }
36
36
  >
37
- <ToggleGroupControlOption
38
- value="solid"
39
- label={ __( 'Solid' ) }
40
- />
41
- <ToggleGroupControlOption
42
- value="gradient"
43
- label={ __( 'Gradient' ) }
44
- />
45
- </ToggleGroupControl>
46
- { currentTab === 'solid' && <ColorPalettePanel name={ name } /> }
47
- { currentTab === 'gradient' && (
48
- <GradientPalettePanel name={ name } />
49
- ) }
37
+ { ( tab ) => (
38
+ <>
39
+ { tab.value === 'solid' && (
40
+ <ColorPalettePanel name={ name } />
41
+ ) }
42
+ { tab.value === 'gradient' && (
43
+ <GradientPalettePanel name={ name } />
44
+ ) }
45
+ </>
46
+ ) }
47
+ </TabPanel>
50
48
  </>
51
49
  );
52
50
  }
@@ -82,6 +82,7 @@ function LinkColorItem( { name, parentMenu } ) {
82
82
  const supports = getSupportedGlobalStylesPanels( name );
83
83
  const hasSupport = supports.includes( 'linkColor' );
84
84
  const [ color ] = useStyle( 'elements.link.color.text', name );
85
+ const [ colorHover ] = useStyle( 'elements.link.:hover.color.text', name );
85
86
 
86
87
  if ( ! hasSupport ) {
87
88
  return null;
@@ -93,9 +94,14 @@ function LinkColorItem( { name, parentMenu } ) {
93
94
  aria-label={ __( 'Colors link styles' ) }
94
95
  >
95
96
  <HStack justify="flex-start">
96
- <ColorIndicatorWrapper expanded={ false }>
97
- <ColorIndicator colorValue={ color } />
98
- </ColorIndicatorWrapper>
97
+ <ZStack isLayered={ false } offset={ -8 }>
98
+ <ColorIndicatorWrapper expanded={ false }>
99
+ <ColorIndicator colorValue={ color } />
100
+ </ColorIndicatorWrapper>
101
+ <ColorIndicatorWrapper expanded={ false }>
102
+ <ColorIndicator colorValue={ colorHover } />
103
+ </ColorIndicatorWrapper>
104
+ </ZStack>
99
105
  <FlexItem>{ __( 'Links' ) }</FlexItem>
100
106
  </HStack>
101
107
  </NavigationButtonAsItem>