@wordpress/edit-site 4.8.0 → 4.11.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 (204) hide show
  1. package/CHANGELOG.md +6 -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 +204 -0
  5. package/build/components/add-new-template/add-custom-template-modal.js.map +1 -0
  6. package/build/components/add-new-template/new-template.js +91 -33
  7. package/build/components/add-new-template/new-template.js.map +1 -1
  8. package/build/components/add-new-template/utils.js +532 -0
  9. package/build/components/add-new-template/utils.js.map +1 -0
  10. package/build/components/block-editor/index.js +1 -3
  11. package/build/components/block-editor/index.js.map +1 -1
  12. package/build/components/code-editor/index.js +17 -4
  13. package/build/components/code-editor/index.js.map +1 -1
  14. package/build/components/editor/index.js +16 -0
  15. package/build/components/editor/index.js.map +1 -1
  16. package/build/components/error-boundary/index.js +6 -0
  17. package/build/components/error-boundary/index.js.map +1 -1
  18. package/build/components/global-styles/context-menu.js +6 -3
  19. package/build/components/global-styles/context-menu.js.map +1 -1
  20. package/build/components/global-styles/dimensions-panel.js +185 -19
  21. package/build/components/global-styles/dimensions-panel.js.map +1 -1
  22. package/build/components/global-styles/global-styles-provider.js +4 -2
  23. package/build/components/global-styles/global-styles-provider.js.map +1 -1
  24. package/build/components/global-styles/hooks.js +11 -2
  25. package/build/components/global-styles/hooks.js.map +1 -1
  26. package/build/components/global-styles/palette.js +2 -1
  27. package/build/components/global-styles/palette.js.map +1 -1
  28. package/build/components/global-styles/screen-block-list.js +4 -1
  29. package/build/components/global-styles/screen-block-list.js.map +1 -1
  30. package/build/components/global-styles/screen-button-color.js +80 -0
  31. package/build/components/global-styles/screen-button-color.js.map +1 -0
  32. package/build/components/global-styles/screen-color-palette.js +13 -17
  33. package/build/components/global-styles/screen-color-palette.js.map +1 -1
  34. package/build/components/global-styles/screen-colors.js +56 -8
  35. package/build/components/global-styles/screen-colors.js.map +1 -1
  36. package/build/components/global-styles/screen-link-color.js +48 -14
  37. package/build/components/global-styles/screen-link-color.js.map +1 -1
  38. package/build/components/global-styles/screen-root.js +4 -2
  39. package/build/components/global-styles/screen-root.js.map +1 -1
  40. package/build/components/global-styles/screen-typography-element.js +4 -0
  41. package/build/components/global-styles/screen-typography-element.js.map +1 -1
  42. package/build/components/global-styles/screen-typography.js +9 -1
  43. package/build/components/global-styles/screen-typography.js.map +1 -1
  44. package/build/components/global-styles/ui.js +11 -0
  45. package/build/components/global-styles/ui.js.map +1 -1
  46. package/build/components/global-styles/use-global-styles-output.js +282 -35
  47. package/build/components/global-styles/use-global-styles-output.js.map +1 -1
  48. package/build/components/global-styles/utils.js +35 -2
  49. package/build/components/global-styles/utils.js.map +1 -1
  50. package/build/components/header/index.js +29 -10
  51. package/build/components/header/index.js.map +1 -1
  52. package/build/components/header/more-menu/site-export.js +4 -1
  53. package/build/components/header/more-menu/site-export.js.map +1 -1
  54. package/build/components/header/undo-redo/redo.js +13 -4
  55. package/build/components/header/undo-redo/redo.js.map +1 -1
  56. package/build/components/header/undo-redo/undo.js +13 -4
  57. package/build/components/header/undo-redo/undo.js.map +1 -1
  58. package/build/components/keyboard-shortcut-help-modal/config.js +17 -0
  59. package/build/components/keyboard-shortcut-help-modal/config.js.map +1 -1
  60. package/build/components/keyboard-shortcut-help-modal/index.js +1 -3
  61. package/build/components/keyboard-shortcut-help-modal/index.js.map +1 -1
  62. package/build/components/sidebar/template-card/index.js +19 -7
  63. package/build/components/sidebar/template-card/index.js.map +1 -1
  64. package/build/components/sidebar/template-card/template-actions.js +64 -0
  65. package/build/components/sidebar/template-card/template-actions.js.map +1 -0
  66. package/build/components/template-details/edit-template-title.js +11 -3
  67. package/build/components/template-details/edit-template-title.js.map +1 -1
  68. package/build/components/template-details/index.js +1 -20
  69. package/build/components/template-details/index.js.map +1 -1
  70. package/build/hooks/index.js +2 -0
  71. package/build/hooks/index.js.map +1 -1
  72. package/build/hooks/template-part-edit.js +86 -0
  73. package/build/hooks/template-part-edit.js.map +1 -0
  74. package/build/store/selectors.js +4 -1
  75. package/build/store/selectors.js.map +1 -1
  76. package/build-module/components/add-new-template/add-custom-generic-template-modal.js +77 -0
  77. package/build-module/components/add-new-template/add-custom-generic-template-modal.js.map +1 -0
  78. package/build-module/components/add-new-template/add-custom-template-modal.js +189 -0
  79. package/build-module/components/add-new-template/add-custom-template-modal.js.map +1 -0
  80. package/build-module/components/add-new-template/new-template.js +90 -34
  81. package/build-module/components/add-new-template/new-template.js.map +1 -1
  82. package/build-module/components/add-new-template/utils.js +503 -0
  83. package/build-module/components/add-new-template/utils.js.map +1 -0
  84. package/build-module/components/block-editor/index.js +1 -2
  85. package/build-module/components/block-editor/index.js.map +1 -1
  86. package/build-module/components/code-editor/index.js +18 -5
  87. package/build-module/components/code-editor/index.js.map +1 -1
  88. package/build-module/components/editor/index.js +16 -0
  89. package/build-module/components/editor/index.js.map +1 -1
  90. package/build-module/components/error-boundary/index.js +5 -0
  91. package/build-module/components/error-boundary/index.js.map +1 -1
  92. package/build-module/components/global-styles/context-menu.js +6 -3
  93. package/build-module/components/global-styles/context-menu.js.map +1 -1
  94. package/build-module/components/global-styles/dimensions-panel.js +185 -20
  95. package/build-module/components/global-styles/dimensions-panel.js.map +1 -1
  96. package/build-module/components/global-styles/global-styles-provider.js +4 -2
  97. package/build-module/components/global-styles/global-styles-provider.js.map +1 -1
  98. package/build-module/components/global-styles/hooks.js +11 -2
  99. package/build-module/components/global-styles/hooks.js.map +1 -1
  100. package/build-module/components/global-styles/palette.js +2 -1
  101. package/build-module/components/global-styles/palette.js.map +1 -1
  102. package/build-module/components/global-styles/screen-block-list.js +4 -1
  103. package/build-module/components/global-styles/screen-block-list.js.map +1 -1
  104. package/build-module/components/global-styles/screen-button-color.js +67 -0
  105. package/build-module/components/global-styles/screen-button-color.js.map +1 -0
  106. package/build-module/components/global-styles/screen-color-palette.js +14 -19
  107. package/build-module/components/global-styles/screen-color-palette.js.map +1 -1
  108. package/build-module/components/global-styles/screen-colors.js +57 -9
  109. package/build-module/components/global-styles/screen-colors.js.map +1 -1
  110. package/build-module/components/global-styles/screen-link-color.js +47 -14
  111. package/build-module/components/global-styles/screen-link-color.js.map +1 -1
  112. package/build-module/components/global-styles/screen-root.js +4 -2
  113. package/build-module/components/global-styles/screen-root.js.map +1 -1
  114. package/build-module/components/global-styles/screen-typography-element.js +4 -0
  115. package/build-module/components/global-styles/screen-typography-element.js.map +1 -1
  116. package/build-module/components/global-styles/screen-typography.js +10 -2
  117. package/build-module/components/global-styles/screen-typography.js.map +1 -1
  118. package/build-module/components/global-styles/ui.js +10 -0
  119. package/build-module/components/global-styles/ui.js.map +1 -1
  120. package/build-module/components/global-styles/use-global-styles-output.js +280 -42
  121. package/build-module/components/global-styles/use-global-styles-output.js.map +1 -1
  122. package/build-module/components/global-styles/utils.js +34 -3
  123. package/build-module/components/global-styles/utils.js.map +1 -1
  124. package/build-module/components/header/index.js +31 -11
  125. package/build-module/components/header/index.js.map +1 -1
  126. package/build-module/components/header/more-menu/site-export.js +4 -1
  127. package/build-module/components/header/more-menu/site-export.js.map +1 -1
  128. package/build-module/components/header/undo-redo/redo.js +9 -3
  129. package/build-module/components/header/undo-redo/redo.js.map +1 -1
  130. package/build-module/components/header/undo-redo/undo.js +9 -3
  131. package/build-module/components/header/undo-redo/undo.js.map +1 -1
  132. package/build-module/components/keyboard-shortcut-help-modal/config.js +17 -0
  133. package/build-module/components/keyboard-shortcut-help-modal/config.js.map +1 -1
  134. package/build-module/components/keyboard-shortcut-help-modal/index.js +1 -2
  135. package/build-module/components/keyboard-shortcut-help-modal/index.js.map +1 -1
  136. package/build-module/components/sidebar/template-card/index.js +18 -7
  137. package/build-module/components/sidebar/template-card/index.js.map +1 -1
  138. package/build-module/components/sidebar/template-card/template-actions.js +49 -0
  139. package/build-module/components/sidebar/template-card/template-actions.js.map +1 -0
  140. package/build-module/components/template-details/edit-template-title.js +12 -3
  141. package/build-module/components/template-details/edit-template-title.js.map +1 -1
  142. package/build-module/components/template-details/index.js +2 -21
  143. package/build-module/components/template-details/index.js.map +1 -1
  144. package/build-module/hooks/index.js +1 -0
  145. package/build-module/hooks/index.js.map +1 -1
  146. package/build-module/hooks/template-part-edit.js +67 -0
  147. package/build-module/hooks/template-part-edit.js.map +1 -0
  148. package/build-module/store/selectors.js +5 -2
  149. package/build-module/store/selectors.js.map +1 -1
  150. package/build-style/style-rtl.css +198 -49
  151. package/build-style/style.css +198 -49
  152. package/package.json +29 -29
  153. package/src/components/add-new-template/add-custom-generic-template-modal.js +97 -0
  154. package/src/components/add-new-template/add-custom-template-modal.js +247 -0
  155. package/src/components/add-new-template/new-template.js +158 -70
  156. package/src/components/add-new-template/style.scss +149 -0
  157. package/src/components/add-new-template/utils.js +538 -0
  158. package/src/components/block-editor/index.js +0 -2
  159. package/src/components/code-editor/index.js +15 -5
  160. package/src/components/editor/index.js +11 -0
  161. package/src/components/error-boundary/index.js +5 -0
  162. package/src/components/global-styles/context-menu.js +3 -0
  163. package/src/components/global-styles/dimensions-panel.js +209 -21
  164. package/src/components/global-styles/global-styles-provider.js +8 -9
  165. package/src/components/global-styles/hooks.js +18 -0
  166. package/src/components/global-styles/palette.js +4 -1
  167. package/src/components/global-styles/screen-block-list.js +10 -1
  168. package/src/components/global-styles/screen-button-color.js +102 -0
  169. package/src/components/global-styles/screen-color-palette.js +25 -27
  170. package/src/components/global-styles/screen-colors.js +58 -7
  171. package/src/components/global-styles/screen-link-color.js +65 -23
  172. package/src/components/global-styles/screen-root.js +8 -2
  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 +14 -8
  176. package/src/components/global-styles/test/use-global-styles-output.js +313 -16
  177. package/src/components/global-styles/ui.js +13 -0
  178. package/src/components/global-styles/use-global-styles-output.js +344 -38
  179. package/src/components/global-styles/utils.js +36 -2
  180. package/src/components/header/index.js +42 -17
  181. package/src/components/header/more-menu/site-export.js +3 -0
  182. package/src/components/header/style.scss +58 -8
  183. package/src/components/header/undo-redo/redo.js +6 -1
  184. package/src/components/header/undo-redo/undo.js +6 -1
  185. package/src/components/keyboard-shortcut-help-modal/config.js +12 -0
  186. package/src/components/keyboard-shortcut-help-modal/index.js +1 -2
  187. package/src/components/keyboard-shortcut-help-modal/style.scss +0 -5
  188. package/src/components/list/style.scss +0 -8
  189. package/src/components/sidebar/style.scss +4 -0
  190. package/src/components/sidebar/template-card/index.js +15 -6
  191. package/src/components/sidebar/template-card/style.scss +49 -35
  192. package/src/components/sidebar/template-card/template-actions.js +43 -0
  193. package/src/components/template-details/edit-template-title.js +10 -2
  194. package/src/components/template-details/index.js +4 -21
  195. package/src/components/test/error-boundary.js +38 -0
  196. package/src/hooks/index.js +1 -0
  197. package/src/hooks/template-part-edit.js +82 -0
  198. package/src/store/selectors.js +11 -5
  199. package/src/style.scss +0 -1
  200. package/build/components/edit-template-part-menu-button/index.js +0 -90
  201. package/build/components/edit-template-part-menu-button/index.js.map +0 -1
  202. package/build-module/components/edit-template-part-menu-button/index.js +0 -72
  203. package/build-module/components/edit-template-part-menu-button/index.js.map +0 -1
  204. package/src/components/edit-template-part-menu-button/index.js +0 -82
@@ -0,0 +1,247 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useState, useMemo, useEffect } from '@wordpress/element';
5
+ import { __, sprintf } from '@wordpress/i18n';
6
+ import {
7
+ Button,
8
+ Flex,
9
+ FlexItem,
10
+ Modal,
11
+ SearchControl,
12
+ TextHighlight,
13
+ __experimentalText as Text,
14
+ __unstableComposite as Composite,
15
+ __unstableUseCompositeState as useCompositeState,
16
+ __unstableCompositeItem as CompositeItem,
17
+ } from '@wordpress/components';
18
+ import { useDebounce } from '@wordpress/compose';
19
+ import { useEntityRecords } from '@wordpress/core-data';
20
+
21
+ /**
22
+ * Internal dependencies
23
+ */
24
+ import { mapToIHasNameAndId } from './utils';
25
+
26
+ const EMPTY_ARRAY = [];
27
+
28
+ function SuggestionListItem( {
29
+ suggestion,
30
+ search,
31
+ onSelect,
32
+ entityForSuggestions,
33
+ composite,
34
+ } ) {
35
+ const baseCssClass =
36
+ 'edit-site-custom-template-modal__suggestions_list__list-item';
37
+ return (
38
+ <CompositeItem
39
+ role="option"
40
+ as={ Button }
41
+ { ...composite }
42
+ className={ baseCssClass }
43
+ onClick={ () =>
44
+ onSelect(
45
+ entityForSuggestions.config.getSpecificTemplate(
46
+ suggestion
47
+ )
48
+ )
49
+ }
50
+ >
51
+ <span className={ `${ baseCssClass }__title` }>
52
+ <TextHighlight text={ suggestion.name } highlight={ search } />
53
+ </span>
54
+ { suggestion.link && (
55
+ <span className={ `${ baseCssClass }__info` }>
56
+ { suggestion.link }
57
+ </span>
58
+ ) }
59
+ </CompositeItem>
60
+ );
61
+ }
62
+
63
+ function useDebouncedInput() {
64
+ const [ input, setInput ] = useState( '' );
65
+ const [ debounced, setter ] = useState( '' );
66
+ const setDebounced = useDebounce( setter, 250 );
67
+ useEffect( () => {
68
+ if ( debounced !== input ) {
69
+ setDebounced( input );
70
+ }
71
+ }, [ debounced, input ] );
72
+ return [ input, setInput, debounced ];
73
+ }
74
+
75
+ function useSearchSuggestions( entityForSuggestions, search ) {
76
+ const { config } = entityForSuggestions;
77
+ const query = useMemo(
78
+ () => ( {
79
+ order: 'asc',
80
+ context: 'view',
81
+ search,
82
+ per_page: search ? 20 : 10,
83
+ ...config.queryArgs( search ),
84
+ } ),
85
+ [ search, config ]
86
+ );
87
+ const { records: searchResults, hasResolved: searchHasResolved } =
88
+ useEntityRecords(
89
+ entityForSuggestions.type,
90
+ entityForSuggestions.slug,
91
+ query
92
+ );
93
+ const [ suggestions, setSuggestions ] = useState( EMPTY_ARRAY );
94
+ useEffect( () => {
95
+ if ( ! searchHasResolved ) return;
96
+ let newSuggestions = EMPTY_ARRAY;
97
+ if ( searchResults?.length ) {
98
+ newSuggestions = searchResults;
99
+ if ( config.recordNamePath ) {
100
+ newSuggestions = mapToIHasNameAndId(
101
+ newSuggestions,
102
+ config.recordNamePath
103
+ );
104
+ }
105
+ }
106
+ // Update suggestions only when the query has resolved, so as to keep
107
+ // the previous results in the UI.
108
+ setSuggestions( newSuggestions );
109
+ }, [ searchResults, searchHasResolved ] );
110
+ return suggestions;
111
+ }
112
+
113
+ function SuggestionList( { entityForSuggestions, onSelect } ) {
114
+ const composite = useCompositeState( { orientation: 'vertical' } );
115
+ const [ search, setSearch, debouncedSearch ] = useDebouncedInput();
116
+ const suggestions = useSearchSuggestions(
117
+ entityForSuggestions,
118
+ debouncedSearch
119
+ );
120
+ const { labels } = entityForSuggestions;
121
+ const [ showSearchControl, setShowSearchControl ] = useState( false );
122
+ if ( ! showSearchControl && suggestions?.length > 9 ) {
123
+ setShowSearchControl( true );
124
+ }
125
+ return (
126
+ <>
127
+ { showSearchControl && (
128
+ <SearchControl
129
+ onChange={ setSearch }
130
+ value={ search }
131
+ label={ labels.search_items }
132
+ placeholder={ labels.search_items }
133
+ />
134
+ ) }
135
+ { !! suggestions?.length && (
136
+ <Composite
137
+ { ...composite }
138
+ role="listbox"
139
+ className="edit-site-custom-template-modal__suggestions_list"
140
+ aria-label={ __( 'Suggestions list' ) }
141
+ >
142
+ { suggestions.map( ( suggestion ) => (
143
+ <SuggestionListItem
144
+ key={ suggestion.slug }
145
+ suggestion={ suggestion }
146
+ search={ debouncedSearch }
147
+ onSelect={ onSelect }
148
+ entityForSuggestions={ entityForSuggestions }
149
+ composite={ composite }
150
+ />
151
+ ) ) }
152
+ </Composite>
153
+ ) }
154
+ { debouncedSearch && ! suggestions?.length && (
155
+ <p className="edit-site-custom-template-modal__no-results">
156
+ { labels.not_found }
157
+ </p>
158
+ ) }
159
+ </>
160
+ );
161
+ }
162
+
163
+ function AddCustomTemplateModal( { onClose, onSelect, entityForSuggestions } ) {
164
+ const [ showSearchEntities, setShowSearchEntities ] = useState(
165
+ entityForSuggestions.hasGeneralTemplate
166
+ );
167
+ const baseCssClass = 'edit-site-custom-template-modal';
168
+ return (
169
+ <Modal
170
+ title={ sprintf(
171
+ // translators: %s: Name of the post type e.g: "Post".
172
+ __( 'Add template: %s' ),
173
+ entityForSuggestions.labels.singular_name
174
+ ) }
175
+ className={ baseCssClass }
176
+ closeLabel={ __( 'Close' ) }
177
+ onRequestClose={ onClose }
178
+ >
179
+ { ! showSearchEntities && (
180
+ <>
181
+ <p>
182
+ { __(
183
+ 'Select whether to create a single template for all items or a specific one.'
184
+ ) }
185
+ </p>
186
+ <Flex
187
+ className={ `${ baseCssClass }__contents` }
188
+ gap="4"
189
+ align="initial"
190
+ >
191
+ <FlexItem
192
+ isBlock
193
+ as={ Button }
194
+ onClick={ () => {
195
+ const { slug, title, description } =
196
+ entityForSuggestions.template;
197
+ onSelect( { slug, title, description } );
198
+ } }
199
+ >
200
+ <Text as="span" weight={ 600 }>
201
+ { entityForSuggestions.labels.all_items }
202
+ </Text>
203
+ <Text as="span">
204
+ {
205
+ // translators: The user is given the choice to set up a template for all items of a post type or taxonomy, or just a specific one.
206
+ __( 'For all items' )
207
+ }
208
+ </Text>
209
+ </FlexItem>
210
+ <FlexItem
211
+ isBlock
212
+ as={ Button }
213
+ onClick={ () => {
214
+ setShowSearchEntities( true );
215
+ } }
216
+ >
217
+ <Text as="span" weight={ 600 }>
218
+ { entityForSuggestions.labels.singular_name }
219
+ </Text>
220
+ <Text as="span">
221
+ {
222
+ // translators: The user is given the choice to set up a template for all items of a post type or taxonomy, or just a specific one.
223
+ __( 'For a specific item' )
224
+ }
225
+ </Text>
226
+ </FlexItem>
227
+ </Flex>
228
+ </>
229
+ ) }
230
+ { showSearchEntities && (
231
+ <>
232
+ <p>
233
+ { __(
234
+ 'This template will be used only for the specific item chosen.'
235
+ ) }
236
+ </p>
237
+ <SuggestionList
238
+ entityForSuggestions={ entityForSuggestions }
239
+ onSelect={ onSelect }
240
+ />
241
+ </>
242
+ ) }
243
+ </Modal>
244
+ );
245
+ }
246
+
247
+ export default AddCustomTemplateModal;
@@ -1,8 +1,3 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { filter, includes, map } from 'lodash';
5
-
6
1
  /**
7
2
  * WordPress dependencies
8
3
  */
@@ -12,9 +7,9 @@ import {
12
7
  MenuItem,
13
8
  NavigableMenu,
14
9
  } from '@wordpress/components';
15
- import { useSelect, useDispatch } from '@wordpress/data';
10
+ import { useState } from '@wordpress/element';
11
+ import { useDispatch } from '@wordpress/data';
16
12
  import { store as coreStore } from '@wordpress/core-data';
17
- import { store as editorStore } from '@wordpress/editor';
18
13
  import {
19
14
  archive,
20
15
  blockMeta,
@@ -29,6 +24,7 @@ import {
29
24
  postDate,
30
25
  search,
31
26
  tag,
27
+ layout as customGenericTemplateIcon,
32
28
  } from '@wordpress/icons';
33
29
  import { __ } from '@wordpress/i18n';
34
30
  import { store as noticesStore } from '@wordpress/notices';
@@ -36,6 +32,14 @@ import { store as noticesStore } from '@wordpress/notices';
36
32
  /**
37
33
  * Internal dependencies
38
34
  */
35
+ import AddCustomTemplateModal from './add-custom-template-modal';
36
+ import {
37
+ useExistingTemplates,
38
+ useDefaultTemplateTypes,
39
+ useTaxonomiesMenuItems,
40
+ usePostTypeMenuItems,
41
+ } from './utils';
42
+ import AddCustomGenericTemplateModal from './add-custom-generic-template-modal';
39
43
  import { useHistory } from '../routes';
40
44
  import { store as editSiteStore } from '../../store';
41
45
 
@@ -71,27 +75,22 @@ const TEMPLATE_ICONS = {
71
75
  };
72
76
 
73
77
  export default function NewTemplate( { postType } ) {
78
+ const [ showCustomTemplateModal, setShowCustomTemplateModal ] =
79
+ useState( false );
80
+ const [
81
+ showCustomGenericTemplateModal,
82
+ setShowCustomGenericTemplateModal,
83
+ ] = useState( false );
84
+ const [ entityForSuggestions, setEntityForSuggestions ] = useState( {} );
85
+
74
86
  const history = useHistory();
75
- const { templates, defaultTemplateTypes } = useSelect(
76
- ( select ) => ( {
77
- templates: select( coreStore ).getEntityRecords(
78
- 'postType',
79
- 'wp_template',
80
- { per_page: -1 }
81
- ),
82
- defaultTemplateTypes:
83
- select( editorStore ).__experimentalGetDefaultTemplateTypes(),
84
- } ),
85
- []
86
- );
87
87
  const { saveEntityRecord } = useDispatch( coreStore );
88
88
  const { createErrorNotice } = useDispatch( noticesStore );
89
89
  const { setTemplate } = useDispatch( editSiteStore );
90
90
 
91
- async function createTemplate( template ) {
91
+ async function createTemplate( template, isWPSuggestion = true ) {
92
92
  try {
93
93
  const { title, description, slug } = template;
94
-
95
94
  const newTemplate = await saveEntityRecord(
96
95
  'postType',
97
96
  'wp_template',
@@ -102,7 +101,7 @@ export default function NewTemplate( { postType } ) {
102
101
  status: 'publish',
103
102
  title,
104
103
  // This adds a post meta field in template that is part of `is_custom` value calculation.
105
- is_wp_suggestion: true,
104
+ is_wp_suggestion: isWPSuggestion,
106
105
  },
107
106
  { throwOnError: true }
108
107
  );
@@ -129,63 +128,152 @@ export default function NewTemplate( { postType } ) {
129
128
  }
130
129
  }
131
130
 
132
- const existingTemplateSlugs = map( templates, 'slug' );
133
-
134
- const missingTemplates = filter(
135
- defaultTemplateTypes,
136
- ( template ) =>
137
- includes( DEFAULT_TEMPLATE_SLUGS, template.slug ) &&
138
- ! includes( existingTemplateSlugs, template.slug )
131
+ const missingTemplates = useMissingTemplates(
132
+ setEntityForSuggestions,
133
+ setShowCustomTemplateModal
139
134
  );
140
-
141
135
  if ( ! missingTemplates.length ) {
142
136
  return null;
143
137
  }
138
+ return (
139
+ <>
140
+ <DropdownMenu
141
+ className="edit-site-new-template-dropdown"
142
+ icon={ null }
143
+ text={ postType.labels.add_new }
144
+ label={ postType.labels.add_new_item }
145
+ popoverProps={ {
146
+ noArrow: false,
147
+ } }
148
+ toggleProps={ {
149
+ variant: 'primary',
150
+ } }
151
+ >
152
+ { () => (
153
+ <NavigableMenu className="edit-site-new-template-dropdown__popover">
154
+ <MenuGroup label={ postType.labels.add_new_item }>
155
+ { missingTemplates.map( ( template ) => {
156
+ const {
157
+ title,
158
+ description,
159
+ slug,
160
+ onClick,
161
+ icon,
162
+ } = template;
163
+ return (
164
+ <MenuItem
165
+ icon={
166
+ icon ||
167
+ TEMPLATE_ICONS[ slug ] ||
168
+ post
169
+ }
170
+ iconPosition="left"
171
+ info={ description }
172
+ key={ slug }
173
+ onClick={ () =>
174
+ onClick
175
+ ? onClick( template )
176
+ : createTemplate( template )
177
+ }
178
+ >
179
+ { title }
180
+ </MenuItem>
181
+ );
182
+ } ) }
183
+ </MenuGroup>
184
+ <MenuGroup>
185
+ <MenuItem
186
+ icon={ customGenericTemplateIcon }
187
+ iconPosition="left"
188
+ info={ __(
189
+ 'Custom templates can be applied to any post or page.'
190
+ ) }
191
+ key="custom-template"
192
+ onClick={ () =>
193
+ setShowCustomGenericTemplateModal( true )
194
+ }
195
+ >
196
+ { __( 'Custom template' ) }
197
+ </MenuItem>
198
+ </MenuGroup>
199
+ </NavigableMenu>
200
+ ) }
201
+ </DropdownMenu>
202
+ { showCustomTemplateModal && (
203
+ <AddCustomTemplateModal
204
+ onClose={ () => setShowCustomTemplateModal( false ) }
205
+ onSelect={ createTemplate }
206
+ entityForSuggestions={ entityForSuggestions }
207
+ />
208
+ ) }
209
+ { showCustomGenericTemplateModal && (
210
+ <AddCustomGenericTemplateModal
211
+ onClose={ () => setShowCustomGenericTemplateModal( false ) }
212
+ createTemplate={ createTemplate }
213
+ />
214
+ ) }
215
+ </>
216
+ );
217
+ }
144
218
 
219
+ function useMissingTemplates(
220
+ setEntityForSuggestions,
221
+ setShowCustomTemplateModal
222
+ ) {
223
+ const existingTemplates = useExistingTemplates();
224
+ const defaultTemplateTypes = useDefaultTemplateTypes();
225
+ const existingTemplateSlugs = ( existingTemplates || [] ).map(
226
+ ( { slug } ) => slug
227
+ );
228
+ const missingDefaultTemplates = ( defaultTemplateTypes || [] ).filter(
229
+ ( template ) =>
230
+ DEFAULT_TEMPLATE_SLUGS.includes( template.slug ) &&
231
+ ! existingTemplateSlugs.includes( template.slug )
232
+ );
233
+ const onClickMenuItem = ( _entityForSuggestions ) => {
234
+ setShowCustomTemplateModal( true );
235
+ setEntityForSuggestions( _entityForSuggestions );
236
+ };
237
+ // We need to replace existing default template types with
238
+ // the create specific template functionality. The original
239
+ // info (title, description, etc.) is preserved in the
240
+ // used hooks.
241
+ const enhancedMissingDefaultTemplateTypes = [ ...missingDefaultTemplates ];
242
+ const { defaultTaxonomiesMenuItems, taxonomiesMenuItems } =
243
+ useTaxonomiesMenuItems( onClickMenuItem );
244
+ const { defaultPostTypesMenuItems, postTypesMenuItems } =
245
+ usePostTypeMenuItems( onClickMenuItem );
246
+ [ ...defaultTaxonomiesMenuItems, ...defaultPostTypesMenuItems ].forEach(
247
+ ( menuItem ) => {
248
+ if ( ! menuItem ) {
249
+ return;
250
+ }
251
+ const matchIndex = enhancedMissingDefaultTemplateTypes.findIndex(
252
+ ( template ) => template.slug === menuItem.slug
253
+ );
254
+ // Some default template types might have been filtered above from
255
+ // `missingDefaultTemplates` because they only check for the general
256
+ // template. So here we either replace or append the item, augmented
257
+ // with the check if it has available specific item to create a
258
+ // template for.
259
+ if ( matchIndex > -1 ) {
260
+ enhancedMissingDefaultTemplateTypes[ matchIndex ] = menuItem;
261
+ } else {
262
+ enhancedMissingDefaultTemplateTypes.push( menuItem );
263
+ }
264
+ }
265
+ );
145
266
  // Update the sort order to match the DEFAULT_TEMPLATE_SLUGS order.
146
- missingTemplates.sort( ( template1, template2 ) => {
267
+ enhancedMissingDefaultTemplateTypes?.sort( ( template1, template2 ) => {
147
268
  return (
148
269
  DEFAULT_TEMPLATE_SLUGS.indexOf( template1.slug ) -
149
270
  DEFAULT_TEMPLATE_SLUGS.indexOf( template2.slug )
150
271
  );
151
272
  } );
152
-
153
- return (
154
- <DropdownMenu
155
- className="edit-site-new-template-dropdown"
156
- icon={ null }
157
- text={ postType.labels.add_new }
158
- label={ postType.labels.add_new_item }
159
- popoverProps={ {
160
- noArrow: false,
161
- } }
162
- toggleProps={ {
163
- variant: 'primary',
164
- } }
165
- >
166
- { () => (
167
- <NavigableMenu className="edit-site-new-template-dropdown__popover">
168
- <MenuGroup label={ postType.labels.add_new_item }>
169
- { map( missingTemplates, ( template ) => {
170
- const { title, description, slug } = template;
171
- return (
172
- <MenuItem
173
- icon={ TEMPLATE_ICONS[ slug ] }
174
- iconPosition="left"
175
- info={ description }
176
- key={ slug }
177
- onClick={ () => {
178
- createTemplate( template );
179
- // We will be navigated way so no need to close the dropdown.
180
- } }
181
- >
182
- { title }
183
- </MenuItem>
184
- );
185
- } ) }
186
- </MenuGroup>
187
- </NavigableMenu>
188
- ) }
189
- </DropdownMenu>
190
- );
273
+ const missingTemplates = [
274
+ ...enhancedMissingDefaultTemplateTypes,
275
+ ...postTypesMenuItems,
276
+ ...taxonomiesMenuItems,
277
+ ];
278
+ return missingTemplates;
191
279
  }
@@ -9,3 +9,152 @@
9
9
  }
10
10
  }
11
11
  }
12
+
13
+ .edit-site-custom-template-modal {
14
+ &__contents {
15
+ > .components-button {
16
+ padding: $grid-unit-30;
17
+ box-shadow: inset 0 0 0 $border-width $gray-600;
18
+ border-radius: $radius-block-ui;
19
+ width: 256px;
20
+ height: auto;
21
+ display: flex;
22
+ flex-direction: column;
23
+ gap: $grid-unit;
24
+
25
+ // Show the boundary of the button, in High Contrast Mode.
26
+ outline: 1px solid transparent;
27
+
28
+ span:first-child {
29
+ color: $gray-900;
30
+ }
31
+
32
+ span {
33
+ color: $gray-700;
34
+ }
35
+
36
+ &:hover {
37
+ color: var(--wp-admin-theme-color-darker-10);
38
+ box-shadow: inset 0 0 0 $border-width var(--wp-admin-theme-color-darker-10);
39
+
40
+ span:first-child {
41
+ color: var(--wp-admin-theme-color);
42
+ }
43
+ }
44
+
45
+ &:focus {
46
+ box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
47
+
48
+ // Windows High Contrast mode will show this outline, but not the box-shadow.
49
+ outline: 3px solid transparent;
50
+
51
+ span:first-child {
52
+ color: var(--wp-admin-theme-color);
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ .components-search-control {
59
+ input[type="search"].components-search-control__input {
60
+ background: $white;
61
+ border: 1px solid $gray-300;
62
+
63
+ &:focus {
64
+ border-color: var(--wp-admin-theme-color);
65
+ box-shadow: 0 0 0 1px var(--wp-admin-theme-color);
66
+ }
67
+ }
68
+ }
69
+
70
+ @include break-medium() {
71
+ width: 456px;
72
+ }
73
+ }
74
+
75
+ .edit-site-custom-template-modal__suggestions_list {
76
+ margin-top: $grid-unit-20;
77
+
78
+ @include break-small() {
79
+ height: 232px;
80
+ overflow: scroll;
81
+ }
82
+
83
+ &__list-item {
84
+ display: block;
85
+ width: 100%;
86
+ text-align: left;
87
+ white-space: pre-wrap;
88
+ overflow-wrap: break-word;
89
+ height: auto;
90
+
91
+ mark {
92
+ font-weight: 700;
93
+ background: none;
94
+ }
95
+
96
+ &:hover {
97
+ background-color: $gray-100;
98
+
99
+ mark {
100
+ color: var(--wp-admin-theme-color);
101
+ }
102
+ }
103
+
104
+ &:focus {
105
+ background-color: $gray-100;
106
+ }
107
+
108
+ &:focus:not(:disabled) {
109
+ box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color) inset;
110
+ }
111
+
112
+ &__title,
113
+ &__info {
114
+ overflow: hidden;
115
+ text-overflow: ellipsis;
116
+ display: block;
117
+ }
118
+
119
+ &__title {
120
+ font-weight: 500;
121
+ margin-bottom: 0.2em;
122
+ }
123
+
124
+ &__info {
125
+ color: $gray-700;
126
+ font-size: 0.9em;
127
+ line-height: 1.3;
128
+ word-break: break-all;
129
+ }
130
+ }
131
+ }
132
+
133
+ .edit-site-custom-template-modal__no-results {
134
+ border: 1px solid $gray-400;
135
+ border-radius: $radius-block-ui;
136
+ padding: $grid-unit-20;
137
+ margin-bottom: 0;
138
+ margin-top: $grid-unit-20;
139
+ }
140
+
141
+
142
+ .edit-site-custom-generic-template__modal {
143
+ .components-base-control {
144
+ @include break-medium() {
145
+ width: $grid-unit * 40;
146
+ }
147
+ }
148
+
149
+ .components-modal__header {
150
+ border-bottom: none;
151
+ }
152
+
153
+ .components-modal__content::before {
154
+ margin-bottom: $grid-unit-05;
155
+ }
156
+ }
157
+
158
+ .edit-site-custom-generic-template__modal-actions {
159
+ margin-top: $grid-unit-15;
160
+ }