@wordpress/edit-site 4.6.0 → 4.9.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 (205) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/build/components/add-new-template/add-custom-template-modal.js +185 -0
  3. package/build/components/add-new-template/add-custom-template-modal.js.map +1 -0
  4. package/build/components/add-new-template/new-template.js +92 -32
  5. package/build/components/add-new-template/new-template.js.map +1 -1
  6. package/build/components/add-new-template/utils.js +139 -0
  7. package/build/components/add-new-template/utils.js.map +1 -0
  8. package/build/components/block-editor/block-inspector-button.js.map +1 -1
  9. package/build/components/block-editor/index.js.map +1 -1
  10. package/build/components/code-editor/code-editor-text-area.js +11 -9
  11. package/build/components/code-editor/code-editor-text-area.js.map +1 -1
  12. package/build/components/edit-template-part-menu-button/index.js.map +1 -1
  13. package/build/components/editor/index.js.map +1 -1
  14. package/build/components/global-styles/context-menu.js +6 -3
  15. package/build/components/global-styles/context-menu.js.map +1 -1
  16. package/build/components/global-styles/global-styles-provider.js.map +1 -1
  17. package/build/components/global-styles/gradients-palette-panel.js +3 -7
  18. package/build/components/global-styles/gradients-palette-panel.js.map +1 -1
  19. package/build/components/global-styles/hooks.js +1 -1
  20. package/build/components/global-styles/hooks.js.map +1 -1
  21. package/build/components/global-styles/palette.js +2 -1
  22. package/build/components/global-styles/palette.js.map +1 -1
  23. package/build/components/global-styles/preview.js +13 -2
  24. package/build/components/global-styles/preview.js.map +1 -1
  25. package/build/components/global-styles/screen-block-list.js +4 -1
  26. package/build/components/global-styles/screen-block-list.js.map +1 -1
  27. package/build/components/global-styles/screen-button-color.js +80 -0
  28. package/build/components/global-styles/screen-button-color.js.map +1 -0
  29. package/build/components/global-styles/screen-colors.js +47 -7
  30. package/build/components/global-styles/screen-colors.js.map +1 -1
  31. package/build/components/global-styles/screen-root.js +4 -2
  32. package/build/components/global-styles/screen-root.js.map +1 -1
  33. package/build/components/global-styles/screen-style-variations.js +9 -1
  34. package/build/components/global-styles/screen-style-variations.js.map +1 -1
  35. package/build/components/global-styles/screen-typography-element.js +4 -0
  36. package/build/components/global-styles/screen-typography-element.js.map +1 -1
  37. package/build/components/global-styles/screen-typography.js +9 -1
  38. package/build/components/global-styles/screen-typography.js.map +1 -1
  39. package/build/components/global-styles/ui.js +11 -0
  40. package/build/components/global-styles/ui.js.map +1 -1
  41. package/build/components/global-styles/use-global-styles-output.js +40 -9
  42. package/build/components/global-styles/use-global-styles-output.js.map +1 -1
  43. package/build/components/global-styles/utils.js +3 -1
  44. package/build/components/global-styles/utils.js.map +1 -1
  45. package/build/components/header/index.js +28 -10
  46. package/build/components/header/index.js.map +1 -1
  47. package/build/components/header/mode-switcher/index.js.map +1 -1
  48. package/build/components/header/more-menu/copy-content-menu-item.js +1 -1
  49. package/build/components/header/more-menu/copy-content-menu-item.js.map +1 -1
  50. package/build/components/header/more-menu/site-export.js +4 -1
  51. package/build/components/header/more-menu/site-export.js.map +1 -1
  52. package/build/components/header/undo-redo/redo.js +13 -4
  53. package/build/components/header/undo-redo/redo.js.map +1 -1
  54. package/build/components/header/undo-redo/undo.js +13 -4
  55. package/build/components/header/undo-redo/undo.js.map +1 -1
  56. package/build/components/keyboard-shortcut-help-modal/config.js +17 -0
  57. package/build/components/keyboard-shortcut-help-modal/config.js.map +1 -1
  58. package/build/components/keyboard-shortcuts/index.js.map +1 -1
  59. package/build/components/list/actions/index.js.map +1 -1
  60. package/build/components/list/actions/rename-menu-item.js.map +1 -1
  61. package/build/components/list/added-by.js.map +1 -1
  62. package/build/components/navigation-sidebar/index.js.map +1 -1
  63. package/build/components/save-button/index.js.map +1 -1
  64. package/build/components/sidebar/index.js.map +1 -1
  65. package/build/components/sidebar/navigation-menu-sidebar/navigation-inspector.js.map +1 -1
  66. package/build/components/sidebar/template-card/index.js +19 -7
  67. package/build/components/sidebar/template-card/index.js.map +1 -1
  68. package/build/components/sidebar/template-card/template-actions.js +64 -0
  69. package/build/components/sidebar/template-card/template-actions.js.map +1 -0
  70. package/build/components/sidebar/template-card/template-areas.js.map +1 -1
  71. package/build/components/template-details/template-areas.js.map +1 -1
  72. package/build/components/template-part-converter/index.js.map +1 -1
  73. package/build/components/url-query-controller/index.js.map +1 -1
  74. package/build/store/actions.js.map +1 -1
  75. package/build/store/selectors.js.map +1 -1
  76. package/build-module/components/add-new-template/add-custom-template-modal.js +170 -0
  77. package/build-module/components/add-new-template/add-custom-template-modal.js.map +1 -0
  78. package/build-module/components/add-new-template/new-template.js +92 -35
  79. package/build-module/components/add-new-template/new-template.js.map +1 -1
  80. package/build-module/components/add-new-template/utils.js +119 -0
  81. package/build-module/components/add-new-template/utils.js.map +1 -0
  82. package/build-module/components/block-editor/block-inspector-button.js.map +1 -1
  83. package/build-module/components/block-editor/index.js.map +1 -1
  84. package/build-module/components/code-editor/code-editor-text-area.js +12 -10
  85. package/build-module/components/code-editor/code-editor-text-area.js.map +1 -1
  86. package/build-module/components/edit-template-part-menu-button/index.js.map +1 -1
  87. package/build-module/components/editor/index.js.map +1 -1
  88. package/build-module/components/global-styles/context-menu.js +6 -3
  89. package/build-module/components/global-styles/context-menu.js.map +1 -1
  90. package/build-module/components/global-styles/global-styles-provider.js.map +1 -1
  91. package/build-module/components/global-styles/gradients-palette-panel.js +3 -5
  92. package/build-module/components/global-styles/gradients-palette-panel.js.map +1 -1
  93. package/build-module/components/global-styles/hooks.js +1 -1
  94. package/build-module/components/global-styles/hooks.js.map +1 -1
  95. package/build-module/components/global-styles/palette.js +2 -1
  96. package/build-module/components/global-styles/palette.js.map +1 -1
  97. package/build-module/components/global-styles/preview.js +14 -3
  98. package/build-module/components/global-styles/preview.js.map +1 -1
  99. package/build-module/components/global-styles/screen-block-list.js +4 -1
  100. package/build-module/components/global-styles/screen-block-list.js.map +1 -1
  101. package/build-module/components/global-styles/screen-button-color.js +67 -0
  102. package/build-module/components/global-styles/screen-button-color.js.map +1 -0
  103. package/build-module/components/global-styles/screen-colors.js +48 -8
  104. package/build-module/components/global-styles/screen-colors.js.map +1 -1
  105. package/build-module/components/global-styles/screen-root.js +4 -2
  106. package/build-module/components/global-styles/screen-root.js.map +1 -1
  107. package/build-module/components/global-styles/screen-style-variations.js +9 -1
  108. package/build-module/components/global-styles/screen-style-variations.js.map +1 -1
  109. package/build-module/components/global-styles/screen-typography-element.js +4 -0
  110. package/build-module/components/global-styles/screen-typography-element.js.map +1 -1
  111. package/build-module/components/global-styles/screen-typography.js +10 -2
  112. package/build-module/components/global-styles/screen-typography.js.map +1 -1
  113. package/build-module/components/global-styles/ui.js +10 -0
  114. package/build-module/components/global-styles/ui.js.map +1 -1
  115. package/build-module/components/global-styles/use-global-styles-output.js +39 -9
  116. package/build-module/components/global-styles/use-global-styles-output.js.map +1 -1
  117. package/build-module/components/global-styles/utils.js +3 -1
  118. package/build-module/components/global-styles/utils.js.map +1 -1
  119. package/build-module/components/header/index.js +29 -11
  120. package/build-module/components/header/index.js.map +1 -1
  121. package/build-module/components/header/mode-switcher/index.js.map +1 -1
  122. package/build-module/components/header/more-menu/copy-content-menu-item.js +1 -1
  123. package/build-module/components/header/more-menu/copy-content-menu-item.js.map +1 -1
  124. package/build-module/components/header/more-menu/site-export.js +4 -1
  125. package/build-module/components/header/more-menu/site-export.js.map +1 -1
  126. package/build-module/components/header/undo-redo/redo.js +9 -3
  127. package/build-module/components/header/undo-redo/redo.js.map +1 -1
  128. package/build-module/components/header/undo-redo/undo.js +9 -3
  129. package/build-module/components/header/undo-redo/undo.js.map +1 -1
  130. package/build-module/components/keyboard-shortcut-help-modal/config.js +17 -0
  131. package/build-module/components/keyboard-shortcut-help-modal/config.js.map +1 -1
  132. package/build-module/components/keyboard-shortcuts/index.js.map +1 -1
  133. package/build-module/components/list/actions/index.js.map +1 -1
  134. package/build-module/components/list/actions/rename-menu-item.js.map +1 -1
  135. package/build-module/components/list/added-by.js.map +1 -1
  136. package/build-module/components/navigation-sidebar/index.js.map +1 -1
  137. package/build-module/components/save-button/index.js.map +1 -1
  138. package/build-module/components/sidebar/index.js.map +1 -1
  139. package/build-module/components/sidebar/navigation-menu-sidebar/navigation-inspector.js.map +1 -1
  140. package/build-module/components/sidebar/template-card/index.js +18 -7
  141. package/build-module/components/sidebar/template-card/index.js.map +1 -1
  142. package/build-module/components/sidebar/template-card/template-actions.js +49 -0
  143. package/build-module/components/sidebar/template-card/template-actions.js.map +1 -0
  144. package/build-module/components/sidebar/template-card/template-areas.js.map +1 -1
  145. package/build-module/components/template-details/template-areas.js.map +1 -1
  146. package/build-module/components/template-part-converter/index.js.map +1 -1
  147. package/build-module/components/url-query-controller/index.js.map +1 -1
  148. package/build-module/store/actions.js.map +1 -1
  149. package/build-module/store/selectors.js.map +1 -1
  150. package/build-style/style-rtl.css +170 -23
  151. package/build-style/style.css +170 -23
  152. package/package.json +30 -30
  153. package/src/components/add-new-template/add-custom-template-modal.js +231 -0
  154. package/src/components/add-new-template/new-template.js +135 -59
  155. package/src/components/add-new-template/style.scss +116 -0
  156. package/src/components/add-new-template/utils.js +125 -0
  157. package/src/components/block-editor/block-inspector-button.js +2 -3
  158. package/src/components/block-editor/index.js +4 -9
  159. package/src/components/code-editor/code-editor-text-area.js +12 -7
  160. package/src/components/edit-template-part-menu-button/index.js +2 -3
  161. package/src/components/editor/index.js +4 -5
  162. package/src/components/global-styles/context-menu.js +3 -0
  163. package/src/components/global-styles/global-styles-provider.js +4 -8
  164. package/src/components/global-styles/gradients-palette-panel.js +2 -5
  165. package/src/components/global-styles/hooks.js +5 -3
  166. package/src/components/global-styles/palette.js +4 -1
  167. package/src/components/global-styles/preview.js +17 -2
  168. package/src/components/global-styles/screen-block-list.js +14 -5
  169. package/src/components/global-styles/screen-button-color.js +102 -0
  170. package/src/components/global-styles/screen-colors.js +49 -4
  171. package/src/components/global-styles/screen-root.js +12 -5
  172. package/src/components/global-styles/screen-style-variations.js +10 -4
  173. package/src/components/global-styles/screen-typography-element.js +4 -0
  174. package/src/components/global-styles/screen-typography.js +17 -2
  175. package/src/components/global-styles/style.scss +10 -0
  176. package/src/components/global-styles/test/use-global-styles-output.js +82 -16
  177. package/src/components/global-styles/ui.js +13 -0
  178. package/src/components/global-styles/use-global-styles-output.js +43 -4
  179. package/src/components/global-styles/utils.js +3 -0
  180. package/src/components/header/index.js +38 -13
  181. package/src/components/header/mode-switcher/index.js +4 -4
  182. package/src/components/header/more-menu/copy-content-menu-item.js +3 -4
  183. package/src/components/header/more-menu/site-export.js +5 -3
  184. package/src/components/header/style.scss +53 -5
  185. package/src/components/header/undo-redo/redo.js +6 -1
  186. package/src/components/header/undo-redo/undo.js +6 -1
  187. package/src/components/keyboard-shortcut-help-modal/config.js +12 -0
  188. package/src/components/keyboard-shortcuts/index.js +6 -10
  189. package/src/components/list/actions/index.js +2 -3
  190. package/src/components/list/actions/rename-menu-item.js +4 -6
  191. package/src/components/list/added-by.js +4 -3
  192. package/src/components/navigation-sidebar/index.js +2 -4
  193. package/src/components/save-button/index.js +2 -4
  194. package/src/components/sidebar/index.js +6 -6
  195. package/src/components/sidebar/navigation-menu-sidebar/navigation-inspector.js +6 -9
  196. package/src/components/sidebar/navigation-menu-sidebar/style.scss +0 -1
  197. package/src/components/sidebar/template-card/index.js +17 -9
  198. package/src/components/sidebar/template-card/style.scss +49 -35
  199. package/src/components/sidebar/template-card/template-actions.js +43 -0
  200. package/src/components/sidebar/template-card/template-areas.js +6 -6
  201. package/src/components/template-details/template-areas.js +6 -6
  202. package/src/components/template-part-converter/index.js +2 -3
  203. package/src/components/url-query-controller/index.js +2 -3
  204. package/src/store/actions.js +257 -233
  205. package/src/store/selectors.js +9 -10
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { filter, find, includes, map } from 'lodash';
4
+ import { filter, includes } from 'lodash';
5
5
 
6
6
  /**
7
7
  * WordPress dependencies
@@ -12,6 +12,7 @@ import {
12
12
  MenuItem,
13
13
  NavigableMenu,
14
14
  } from '@wordpress/components';
15
+ import { useState } from '@wordpress/element';
15
16
  import { useSelect, useDispatch } from '@wordpress/data';
16
17
  import { store as coreStore } from '@wordpress/core-data';
17
18
  import { store as editorStore } from '@wordpress/editor';
@@ -30,12 +31,14 @@ import {
30
31
  search,
31
32
  tag,
32
33
  } from '@wordpress/icons';
33
- import { __ } from '@wordpress/i18n';
34
+ import { __, sprintf } from '@wordpress/i18n';
34
35
  import { store as noticesStore } from '@wordpress/notices';
35
36
 
36
37
  /**
37
38
  * Internal dependencies
38
39
  */
40
+ import AddCustomTemplateModal from './add-custom-template-modal';
41
+ import { usePostTypes, usePostTypesEntitiesInfo } from './utils';
39
42
  import { useHistory } from '../routes';
40
43
  import { store as editSiteStore } from '../../store';
41
44
 
@@ -72,49 +75,52 @@ const TEMPLATE_ICONS = {
72
75
 
73
76
  export default function NewTemplate( { postType } ) {
74
77
  const history = useHistory();
75
- const { templates, defaultTemplateTypes } = useSelect(
78
+ const postTypes = usePostTypes();
79
+ const [ showCustomTemplateModal, setShowCustomTemplateModal ] =
80
+ useState( false );
81
+ const [ entityForSuggestions, setEntityForSuggestions ] = useState( {} );
82
+ const { existingTemplates, defaultTemplateTypes } = useSelect(
76
83
  ( select ) => ( {
77
- templates: select( coreStore ).getEntityRecords(
84
+ existingTemplates: select( coreStore ).getEntityRecords(
78
85
  'postType',
79
86
  'wp_template',
80
87
  { per_page: -1 }
81
88
  ),
82
- defaultTemplateTypes: select(
83
- editorStore
84
- ).__experimentalGetDefaultTemplateTypes(),
89
+ defaultTemplateTypes:
90
+ select( editorStore ).__experimentalGetDefaultTemplateTypes(),
85
91
  } ),
86
92
  []
87
93
  );
94
+ const postTypesEntitiesInfo = usePostTypesEntitiesInfo( existingTemplates );
88
95
  const { saveEntityRecord } = useDispatch( coreStore );
89
96
  const { createErrorNotice } = useDispatch( noticesStore );
90
97
  const { setTemplate } = useDispatch( editSiteStore );
91
98
 
92
- async function createTemplate( { slug } ) {
99
+ async function createTemplate( template ) {
93
100
  try {
94
- const { title, description } = find( defaultTemplateTypes, {
95
- slug,
96
- } );
97
-
98
- const template = await saveEntityRecord(
101
+ const { title, description, slug } = template;
102
+ const newTemplate = await saveEntityRecord(
99
103
  'postType',
100
104
  'wp_template',
101
105
  {
102
- excerpt: description,
106
+ description,
103
107
  // Slugs need to be strings, so this is for template `404`
104
108
  slug: slug.toString(),
105
109
  status: 'publish',
106
110
  title,
111
+ // This adds a post meta field in template that is part of `is_custom` value calculation.
112
+ is_wp_suggestion: true,
107
113
  },
108
114
  { throwOnError: true }
109
115
  );
110
116
 
111
117
  // Set template before navigating away to avoid initial stale value.
112
- setTemplate( template.id, template.slug );
118
+ setTemplate( newTemplate.id, newTemplate.slug );
113
119
 
114
120
  // Navigate to the created template editor.
115
121
  history.push( {
116
- postId: template.id,
117
- postType: template.type,
122
+ postId: newTemplate.id,
123
+ postType: newTemplate.type,
118
124
  } );
119
125
 
120
126
  // TODO: Add a success notice?
@@ -129,9 +135,9 @@ export default function NewTemplate( { postType } ) {
129
135
  } );
130
136
  }
131
137
  }
132
-
133
- const existingTemplateSlugs = map( templates, 'slug' );
134
-
138
+ const existingTemplateSlugs = ( existingTemplates || [] ).map(
139
+ ( { slug } ) => slug
140
+ );
135
141
  const missingTemplates = filter(
136
142
  defaultTemplateTypes,
137
143
  ( template ) =>
@@ -139,54 +145,124 @@ export default function NewTemplate( { postType } ) {
139
145
  ! includes( existingTemplateSlugs, template.slug )
140
146
  );
141
147
 
142
- if ( ! missingTemplates.length ) {
148
+ const extraTemplates = ( postTypes || [] ).reduce(
149
+ ( accumulator, _postType ) => {
150
+ const { slug, labels, icon } = _postType;
151
+ const hasGeneralTemplate = existingTemplateSlugs?.includes(
152
+ `single-${ slug }`
153
+ );
154
+ const hasEntities = postTypesEntitiesInfo?.[ slug ]?.hasEntities;
155
+ const menuItem = {
156
+ slug: `single-${ slug }`,
157
+ title: sprintf(
158
+ // translators: %s: Name of the post type e.g: "Post".
159
+ __( 'Single item: %s' ),
160
+ labels.singular_name
161
+ ),
162
+ description: sprintf(
163
+ // translators: %s: Name of the post type e.g: "Post".
164
+ __( 'Displays a single item: %s.' ),
165
+ labels.singular_name
166
+ ),
167
+ // `icon` is the `menu_icon` property of a post type. We
168
+ // only handle `dashicons` for now, even if the `menu_icon`
169
+ // also supports urls and svg as values.
170
+ icon: icon?.startsWith( 'dashicons-' )
171
+ ? icon.slice( 10 )
172
+ : null,
173
+ };
174
+ // We have a different template creation flow only if they have entities.
175
+ if ( hasEntities ) {
176
+ menuItem.onClick = ( template ) => {
177
+ setShowCustomTemplateModal( true );
178
+ setEntityForSuggestions( {
179
+ type: 'postType',
180
+ slug,
181
+ labels,
182
+ hasGeneralTemplate,
183
+ template,
184
+ postsToExclude:
185
+ postTypesEntitiesInfo[ slug ].existingPosts,
186
+ } );
187
+ };
188
+ }
189
+ // We don't need to add the menu item if there are no
190
+ // entities and the general template exists.
191
+ if ( ! hasGeneralTemplate || hasEntities ) {
192
+ accumulator.push( menuItem );
193
+ }
194
+ return accumulator;
195
+ },
196
+ []
197
+ );
198
+ if ( ! missingTemplates.length && ! extraTemplates.length ) {
143
199
  return null;
144
200
  }
145
-
146
201
  // Update the sort order to match the DEFAULT_TEMPLATE_SLUGS order.
147
- missingTemplates.sort( ( template1, template2 ) => {
202
+ missingTemplates?.sort( ( template1, template2 ) => {
148
203
  return (
149
204
  DEFAULT_TEMPLATE_SLUGS.indexOf( template1.slug ) -
150
205
  DEFAULT_TEMPLATE_SLUGS.indexOf( template2.slug )
151
206
  );
152
207
  } );
153
-
208
+ // Append all extra templates at the end of the list for now.
209
+ missingTemplates.push( ...extraTemplates );
154
210
  return (
155
- <DropdownMenu
156
- className="edit-site-new-template-dropdown"
157
- icon={ null }
158
- text={ postType.labels.add_new }
159
- label={ postType.labels.add_new_item }
160
- popoverProps={ {
161
- noArrow: false,
162
- } }
163
- toggleProps={ {
164
- variant: 'primary',
165
- } }
166
- >
167
- { () => (
168
- <NavigableMenu className="edit-site-new-template-dropdown__popover">
169
- <MenuGroup label={ postType.labels.add_new_item }>
170
- { map(
171
- missingTemplates,
172
- ( { title, description, slug } ) => (
173
- <MenuItem
174
- icon={ TEMPLATE_ICONS[ slug ] }
175
- iconPosition="left"
176
- info={ description }
177
- key={ slug }
178
- onClick={ () => {
179
- createTemplate( { slug } );
180
- // We will be navigated way so no need to close the dropdown.
181
- } }
182
- >
183
- { title }
184
- </MenuItem>
185
- )
186
- ) }
187
- </MenuGroup>
188
- </NavigableMenu>
211
+ <>
212
+ <DropdownMenu
213
+ className="edit-site-new-template-dropdown"
214
+ icon={ null }
215
+ text={ postType.labels.add_new }
216
+ label={ postType.labels.add_new_item }
217
+ popoverProps={ {
218
+ noArrow: false,
219
+ } }
220
+ toggleProps={ {
221
+ variant: 'primary',
222
+ } }
223
+ >
224
+ { () => (
225
+ <NavigableMenu className="edit-site-new-template-dropdown__popover">
226
+ <MenuGroup label={ postType.labels.add_new_item }>
227
+ { missingTemplates.map( ( template ) => {
228
+ const {
229
+ title,
230
+ description,
231
+ slug,
232
+ onClick,
233
+ icon,
234
+ } = template;
235
+ return (
236
+ <MenuItem
237
+ icon={
238
+ icon ||
239
+ TEMPLATE_ICONS[ slug ] ||
240
+ post
241
+ }
242
+ iconPosition="left"
243
+ info={ description }
244
+ key={ slug }
245
+ onClick={ () =>
246
+ onClick
247
+ ? onClick( template )
248
+ : createTemplate( template )
249
+ }
250
+ >
251
+ { title }
252
+ </MenuItem>
253
+ );
254
+ } ) }
255
+ </MenuGroup>
256
+ </NavigableMenu>
257
+ ) }
258
+ </DropdownMenu>
259
+ { showCustomTemplateModal && (
260
+ <AddCustomTemplateModal
261
+ onClose={ () => setShowCustomTemplateModal( false ) }
262
+ onSelect={ createTemplate }
263
+ entityForSuggestions={ entityForSuggestions }
264
+ />
189
265
  ) }
190
- </DropdownMenu>
266
+ </>
191
267
  );
192
268
  }
@@ -9,3 +9,119 @@
9
9
  }
10
10
  }
11
11
  }
12
+
13
+ .edit-site-custom-template-modal {
14
+ &__contents {
15
+ > div {
16
+ text-align: center;
17
+ cursor: pointer;
18
+ padding: $grid-unit-30;
19
+ border: 1px solid $gray-300;
20
+ border-radius: $radius-block-ui;
21
+ width: 256px;
22
+ display: flex;
23
+ flex-direction: column;
24
+ gap: $grid-unit;
25
+ align-items: center;
26
+ justify-content: center;
27
+
28
+ span {
29
+ color: $gray-700;
30
+ }
31
+
32
+ &:hover {
33
+ border-color: var(--wp-admin-theme-color);
34
+
35
+ h5 {
36
+ color: var(--wp-admin-theme-color);
37
+ }
38
+ }
39
+
40
+ &:focus {
41
+ box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
42
+ }
43
+ }
44
+ }
45
+
46
+ .components-search-control {
47
+ input[type="search"].components-search-control__input {
48
+ background: $white;
49
+ border: 1px solid $gray-300;
50
+
51
+ &:focus {
52
+ border-color: var(--wp-admin-theme-color);
53
+ box-shadow: 0 0 0 1px var(--wp-admin-theme-color);
54
+ }
55
+ }
56
+ }
57
+
58
+ @include break-medium() {
59
+ width: 456px;
60
+ }
61
+ }
62
+
63
+ .edit-site-custom-template-modal__suggestions_list {
64
+ margin-top: $grid-unit-20;
65
+
66
+ @include break-small() {
67
+ height: 232px;
68
+ overflow: scroll;
69
+ }
70
+
71
+ &__list-item {
72
+ display: block;
73
+ width: 100%;
74
+ text-align: left;
75
+ white-space: pre-wrap;
76
+ overflow-wrap: break-word;
77
+ height: auto;
78
+
79
+ mark {
80
+ font-weight: 700;
81
+ background: none;
82
+ }
83
+
84
+ &:hover {
85
+ background-color: $gray-100;
86
+
87
+ mark {
88
+ color: var(--wp-admin-theme-color);
89
+ }
90
+ }
91
+
92
+ &:focus {
93
+ background-color: $gray-100;
94
+ }
95
+
96
+ &:focus:not(:disabled) {
97
+ box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color) inset;
98
+ }
99
+
100
+ &__title,
101
+ &__info {
102
+ overflow: hidden;
103
+ text-overflow: ellipsis;
104
+ display: block;
105
+ }
106
+
107
+ &__title {
108
+ font-weight: 500;
109
+ margin-bottom: 0.2em;
110
+ }
111
+
112
+ &__info {
113
+ color: $gray-700;
114
+ font-size: 0.9em;
115
+ line-height: 1.3;
116
+ word-break: break-all;
117
+ }
118
+ }
119
+ }
120
+
121
+ .edit-site-custom-template-modal__no-results {
122
+ border: 1px solid $gray-400;
123
+ border-radius: $radius-block-ui;
124
+ padding: $grid-unit-20;
125
+ margin-bottom: 0;
126
+ margin-top: $grid-unit-20;
127
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { get } from 'lodash';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { useSelect } from '@wordpress/data';
10
+ import { store as coreStore } from '@wordpress/core-data';
11
+ import { decodeEntities } from '@wordpress/html-entities';
12
+ import { useMemo } from '@wordpress/element';
13
+
14
+ export const usePostTypes = () => {
15
+ const postTypes = useSelect(
16
+ ( select ) => select( coreStore ).getPostTypes( { per_page: -1 } ),
17
+ []
18
+ );
19
+ return useMemo( () => {
20
+ const excludedPostTypes = [ 'attachment', 'page' ];
21
+ return postTypes?.filter(
22
+ ( { viewable, slug } ) =>
23
+ viewable && ! excludedPostTypes.includes( slug )
24
+ );
25
+ }, [ postTypes ] );
26
+ };
27
+
28
+ /**
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.
32
+ */
33
+
34
+ /**
35
+ * Helper hook that returns information about a post type having
36
+ * posts that we can create a specific template for.
37
+ *
38
+ * First we need to find the existing posts with an associated template,
39
+ * to query afterwards for any remaing post, by excluding them.
40
+ *
41
+ * @param {string[]} existingTemplates The existing templates.
42
+ * @return {Record<string,PostTypeEntitiesInfo>} An object with the postTypes as `keys` and PostTypeEntitiesInfo as values.
43
+ */
44
+ export const usePostTypesEntitiesInfo = ( existingTemplates ) => {
45
+ const postTypes = usePostTypes();
46
+ const slugsToExcludePerEntity = useMemo( () => {
47
+ return postTypes?.reduce( ( accumulator, _postType ) => {
48
+ const slugsWithTemplates = ( existingTemplates || [] ).reduce(
49
+ ( _accumulator, existingTemplate ) => {
50
+ const prefix = `single-${ _postType.slug }-`;
51
+ if ( existingTemplate.slug.startsWith( prefix ) ) {
52
+ _accumulator.push(
53
+ existingTemplate.slug.substring( prefix.length )
54
+ );
55
+ }
56
+ return _accumulator;
57
+ },
58
+ []
59
+ );
60
+ if ( slugsWithTemplates.length ) {
61
+ accumulator[ _postType.slug ] = slugsWithTemplates;
62
+ }
63
+ return accumulator;
64
+ }, {} );
65
+ }, [ postTypes, existingTemplates ] );
66
+ const postsToExcludePerEntity = useSelect(
67
+ ( select ) => {
68
+ if ( ! slugsToExcludePerEntity ) {
69
+ return;
70
+ }
71
+ const postsToExclude = Object.entries(
72
+ slugsToExcludePerEntity
73
+ ).reduce( ( accumulator, [ slug, slugsWithTemplates ] ) => {
74
+ const postsWithTemplates = select( coreStore ).getEntityRecords(
75
+ 'postType',
76
+ slug,
77
+ {
78
+ _fields: 'id',
79
+ context: 'view',
80
+ slug: slugsWithTemplates,
81
+ }
82
+ );
83
+ if ( postsWithTemplates?.length ) {
84
+ accumulator[ slug ] = postsWithTemplates;
85
+ }
86
+ return accumulator;
87
+ }, {} );
88
+ return postsToExclude;
89
+ },
90
+ [ slugsToExcludePerEntity ]
91
+ );
92
+ const entitiesInfo = useSelect(
93
+ ( select ) => {
94
+ return postTypes?.reduce( ( accumulator, { slug } ) => {
95
+ const existingPosts =
96
+ postsToExcludePerEntity?.[ slug ]?.map(
97
+ ( { id } ) => id
98
+ ) || [];
99
+ accumulator[ slug ] = {
100
+ hasEntities: !! select( coreStore ).getEntityRecords(
101
+ 'postType',
102
+ slug,
103
+ {
104
+ per_page: 1,
105
+ _fields: 'id',
106
+ context: 'view',
107
+ exclude: existingPosts,
108
+ }
109
+ )?.length,
110
+ existingPosts,
111
+ };
112
+ return accumulator;
113
+ }, {} );
114
+ },
115
+ [ postTypes, postsToExcludePerEntity ]
116
+ );
117
+ return entitiesInfo;
118
+ };
119
+
120
+ export const mapToIHasNameAndId = ( entities, path ) => {
121
+ return ( entities || [] ).map( ( entity ) => ( {
122
+ ...entity,
123
+ name: decodeEntities( get( entity, path ) ),
124
+ } ) );
125
+ };
@@ -30,9 +30,8 @@ export default function BlockInspectorButton( { onClick = () => {} } ) {
30
30
  } ),
31
31
  []
32
32
  );
33
- const { enableComplementaryArea, disableComplementaryArea } = useDispatch(
34
- interfaceStore
35
- );
33
+ const { enableComplementaryArea, disableComplementaryArea } =
34
+ useDispatch( interfaceStore );
36
35
 
37
36
  const label = isBlockInspectorOpen
38
37
  ? __( 'Hide more settings' )
@@ -51,12 +51,8 @@ const LAYOUT = {
51
51
  export default function BlockEditor( { setIsInserterOpen } ) {
52
52
  const { storedSettings, templateType, templateId, page } = useSelect(
53
53
  ( select ) => {
54
- const {
55
- getSettings,
56
- getEditedPostType,
57
- getEditedPostId,
58
- getPage,
59
- } = select( editSiteStore );
54
+ const { getSettings, getEditedPostType, getEditedPostId, getPage } =
55
+ select( editSiteStore );
60
56
 
61
57
  return {
62
58
  storedSettings: getSettings( setIsInserterOpen ),
@@ -78,9 +74,8 @@ export default function BlockEditor( { setIsInserterOpen } ) {
78
74
  const { restBlockPatterns, restBlockPatternCategories } = useSelect(
79
75
  ( select ) => ( {
80
76
  restBlockPatterns: select( coreStore ).getBlockPatterns(),
81
- restBlockPatternCategories: select(
82
- coreStore
83
- ).getBlockPatternCategories(),
77
+ restBlockPatternCategories:
78
+ select( coreStore ).getBlockPatternCategories(),
84
79
  } ),
85
80
  []
86
81
  );
@@ -3,17 +3,11 @@
3
3
  */
4
4
  import Textarea from 'react-autosize-textarea';
5
5
 
6
- /**
7
- * WordPress dependencies
8
- */
9
- /**
10
- * WordPress dependencies
11
- */
12
6
  /**
13
7
  * WordPress dependencies
14
8
  */
15
9
  import { __ } from '@wordpress/i18n';
16
- import { useState } from '@wordpress/element';
10
+ import { useEffect, useState, useRef } from '@wordpress/element';
17
11
  import { useInstanceId } from '@wordpress/compose';
18
12
  import { VisuallyHidden } from '@wordpress/components';
19
13
 
@@ -21,6 +15,7 @@ export default function CodeEditorTextArea( { value, onChange, onInput } ) {
21
15
  const [ stateValue, setStateValue ] = useState( value );
22
16
  const [ isDirty, setIsDirty ] = useState( false );
23
17
  const instanceId = useInstanceId( CodeEditorTextArea );
18
+ const valueRef = useRef();
24
19
 
25
20
  if ( ! isDirty && stateValue !== value ) {
26
21
  setStateValue( value );
@@ -42,6 +37,7 @@ export default function CodeEditorTextArea( { value, onChange, onInput } ) {
42
37
  onInput( newValue );
43
38
  setStateValue( newValue );
44
39
  setIsDirty( true );
40
+ valueRef.current = newValue;
45
41
  };
46
42
 
47
43
  /**
@@ -56,6 +52,15 @@ export default function CodeEditorTextArea( { value, onChange, onInput } ) {
56
52
  }
57
53
  };
58
54
 
55
+ // Ensure changes aren't lost when component unmounts.
56
+ useEffect( () => {
57
+ return () => {
58
+ if ( valueRef.current ) {
59
+ onChange( valueRef.current );
60
+ }
61
+ };
62
+ }, [] );
63
+
59
64
  return (
60
65
  <>
61
66
  <VisuallyHidden
@@ -34,9 +34,8 @@ function EditTemplatePartMenuItem( { selectedClientId, onClose } ) {
34
34
  const { params } = useLocation();
35
35
  const selectedTemplatePart = useSelect(
36
36
  ( select ) => {
37
- const block = select( blockEditorStore ).getBlock(
38
- selectedClientId
39
- );
37
+ const block =
38
+ select( blockEditorStore ).getBlock( selectedClientId );
40
39
 
41
40
  if ( block && isTemplatePart( block ) ) {
42
41
  const { theme, slug } = block.attributes;
@@ -119,10 +119,8 @@ function Editor( { onError } ) {
119
119
  const { setPage, setIsInserterOpened } = useDispatch( editSiteStore );
120
120
  const { enableComplementaryArea } = useDispatch( interfaceStore );
121
121
 
122
- const [
123
- isEntitiesSavedStatesOpen,
124
- setIsEntitiesSavedStatesOpen,
125
- ] = useState( false );
122
+ const [ isEntitiesSavedStatesOpen, setIsEntitiesSavedStatesOpen ] =
123
+ useState( false );
126
124
  const openEntitiesSavedStates = useCallback(
127
125
  () => setIsEntitiesSavedStatesOpen( true ),
128
126
  []
@@ -217,7 +215,8 @@ function Editor( { onError } ) {
217
215
  <InterfaceSkeleton
218
216
  labels={ {
219
217
  ...interfaceLabels,
220
- secondarySidebar: secondarySidebarLabel,
218
+ secondarySidebar:
219
+ secondarySidebarLabel,
221
220
  } }
222
221
  className={
223
222
  showIconLabels &&
@@ -27,6 +27,7 @@ function ContextMenu( { name, parentMenu = '' } ) {
27
27
  <NavigationButtonAsItem
28
28
  icon={ typography }
29
29
  path={ parentMenu + '/typography' }
30
+ aria-label={ __( 'Typography styles' ) }
30
31
  >
31
32
  { __( 'Typography' ) }
32
33
  </NavigationButtonAsItem>
@@ -35,6 +36,7 @@ function ContextMenu( { name, parentMenu = '' } ) {
35
36
  <NavigationButtonAsItem
36
37
  icon={ color }
37
38
  path={ parentMenu + '/colors' }
39
+ aria-label={ __( 'Colors styles' ) }
38
40
  >
39
41
  { __( 'Colors' ) }
40
42
  </NavigationButtonAsItem>
@@ -43,6 +45,7 @@ function ContextMenu( { name, parentMenu = '' } ) {
43
45
  <NavigationButtonAsItem
44
46
  icon={ layout }
45
47
  path={ parentMenu + '/layout' }
48
+ aria-label={ __( 'Layout styles' ) }
46
49
  >
47
50
  { __( 'Layout' ) }
48
51
  </NavigationButtonAsItem>
@@ -48,9 +48,8 @@ const cleanEmptyObject = ( object ) => {
48
48
 
49
49
  function useGlobalStylesUserConfig() {
50
50
  const { globalStylesId, settings, styles } = useSelect( ( select ) => {
51
- const _globalStylesId = select(
52
- coreStore
53
- ).__experimentalGetCurrentGlobalStylesId();
51
+ const _globalStylesId =
52
+ select( coreStore ).__experimentalGetCurrentGlobalStylesId();
54
53
  const record = _globalStylesId
55
54
  ? select( coreStore ).getEditedEntityRecord(
56
55
  'root',
@@ -108,11 +107,8 @@ function useGlobalStylesBaseConfig() {
108
107
  }
109
108
 
110
109
  function useGlobalStylesContext() {
111
- const [
112
- isUserConfigReady,
113
- userConfig,
114
- setUserConfig,
115
- ] = useGlobalStylesUserConfig();
110
+ const [ isUserConfigReady, userConfig, setUserConfig ] =
111
+ useGlobalStylesUserConfig();
116
112
  const [ isBaseConfigReady, baseConfig ] = useGlobalStylesBaseConfig();
117
113
  const mergedConfig = useMemo( () => {
118
114
  if ( ! baseConfig || ! userConfig ) {