@wordpress/edit-site 4.9.0 → 4.12.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 (199) 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 +82 -61
  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 +94 -81
  7. package/build/components/add-new-template/new-template.js.map +1 -1
  8. package/build/components/add-new-template/utils.js +574 -57
  9. package/build/components/add-new-template/utils.js.map +1 -1
  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/dimensions-panel.js +191 -21
  19. package/build/components/global-styles/dimensions-panel.js.map +1 -1
  20. package/build/components/global-styles/global-styles-provider.js +4 -2
  21. package/build/components/global-styles/global-styles-provider.js.map +1 -1
  22. package/build/components/global-styles/hooks.js +11 -2
  23. package/build/components/global-styles/hooks.js.map +1 -1
  24. package/build/components/global-styles/screen-color-palette.js +13 -17
  25. package/build/components/global-styles/screen-color-palette.js.map +1 -1
  26. package/build/components/global-styles/screen-colors.js +59 -7
  27. package/build/components/global-styles/screen-colors.js.map +1 -1
  28. package/build/components/global-styles/screen-heading-color.js +157 -0
  29. package/build/components/global-styles/screen-heading-color.js.map +1 -0
  30. package/build/components/global-styles/screen-link-color.js +48 -14
  31. package/build/components/global-styles/screen-link-color.js.map +1 -1
  32. package/build/components/global-styles/screen-typography-element.js +4 -0
  33. package/build/components/global-styles/screen-typography-element.js.map +1 -1
  34. package/build/components/global-styles/screen-typography.js +5 -0
  35. package/build/components/global-styles/screen-typography.js.map +1 -1
  36. package/build/components/global-styles/typography-panel.js +73 -12
  37. package/build/components/global-styles/typography-panel.js.map +1 -1
  38. package/build/components/global-styles/typography-utils.js +217 -0
  39. package/build/components/global-styles/typography-utils.js.map +1 -0
  40. package/build/components/global-styles/ui.js +11 -0
  41. package/build/components/global-styles/ui.js.map +1 -1
  42. package/build/components/global-styles/use-global-styles-output.js +298 -61
  43. package/build/components/global-styles/use-global-styles-output.js.map +1 -1
  44. package/build/components/global-styles/utils.js +49 -3
  45. package/build/components/global-styles/utils.js.map +1 -1
  46. package/build/components/header/index.js +22 -10
  47. package/build/components/header/index.js.map +1 -1
  48. package/build/components/header/undo-redo/redo.js +2 -1
  49. package/build/components/header/undo-redo/redo.js.map +1 -1
  50. package/build/components/keyboard-shortcut-help-modal/index.js +1 -3
  51. package/build/components/keyboard-shortcut-help-modal/index.js.map +1 -1
  52. package/build/components/list/actions/index.js +1 -1
  53. package/build/components/list/actions/index.js.map +1 -1
  54. package/build/components/save-button/index.js +2 -3
  55. package/build/components/save-button/index.js.map +1 -1
  56. package/build/components/sidebar/navigation-menu-sidebar/navigation-menu.js +2 -2
  57. package/build/components/sidebar/navigation-menu-sidebar/navigation-menu.js.map +1 -1
  58. package/build/components/sidebar/template-card/template-actions.js +1 -1
  59. package/build/components/sidebar/template-card/template-actions.js.map +1 -1
  60. package/build/components/template-details/edit-template-title.js +11 -3
  61. package/build/components/template-details/edit-template-title.js.map +1 -1
  62. package/build/components/template-details/index.js +2 -21
  63. package/build/components/template-details/index.js.map +1 -1
  64. package/build/components/template-details/template-areas.js +1 -1
  65. package/build/components/template-details/template-areas.js.map +1 -1
  66. package/build/components/template-part-converter/convert-to-template-part.js +4 -1
  67. package/build/components/template-part-converter/convert-to-template-part.js.map +1 -1
  68. package/build/hooks/index.js +2 -0
  69. package/build/hooks/index.js.map +1 -1
  70. package/build/hooks/template-part-edit.js +86 -0
  71. package/build/hooks/template-part-edit.js.map +1 -0
  72. package/build/store/selectors.js +4 -1
  73. package/build/store/selectors.js.map +1 -1
  74. package/build-module/components/add-new-template/add-custom-generic-template-modal.js +77 -0
  75. package/build-module/components/add-new-template/add-custom-generic-template-modal.js.map +1 -0
  76. package/build-module/components/add-new-template/add-custom-template-modal.js +82 -61
  77. package/build-module/components/add-new-template/add-custom-template-modal.js.map +1 -1
  78. package/build-module/components/add-new-template/new-template.js +96 -84
  79. package/build-module/components/add-new-template/new-template.js.map +1 -1
  80. package/build-module/components/add-new-template/utils.js +555 -50
  81. package/build-module/components/add-new-template/utils.js.map +1 -1
  82. package/build-module/components/block-editor/index.js +1 -2
  83. package/build-module/components/block-editor/index.js.map +1 -1
  84. package/build-module/components/code-editor/index.js +18 -5
  85. package/build-module/components/code-editor/index.js.map +1 -1
  86. package/build-module/components/editor/index.js +16 -0
  87. package/build-module/components/editor/index.js.map +1 -1
  88. package/build-module/components/error-boundary/index.js +5 -0
  89. package/build-module/components/error-boundary/index.js.map +1 -1
  90. package/build-module/components/global-styles/dimensions-panel.js +191 -22
  91. package/build-module/components/global-styles/dimensions-panel.js.map +1 -1
  92. package/build-module/components/global-styles/global-styles-provider.js +4 -2
  93. package/build-module/components/global-styles/global-styles-provider.js.map +1 -1
  94. package/build-module/components/global-styles/hooks.js +11 -2
  95. package/build-module/components/global-styles/hooks.js.map +1 -1
  96. package/build-module/components/global-styles/screen-color-palette.js +14 -19
  97. package/build-module/components/global-styles/screen-color-palette.js.map +1 -1
  98. package/build-module/components/global-styles/screen-colors.js +59 -7
  99. package/build-module/components/global-styles/screen-colors.js.map +1 -1
  100. package/build-module/components/global-styles/screen-heading-color.js +143 -0
  101. package/build-module/components/global-styles/screen-heading-color.js.map +1 -0
  102. package/build-module/components/global-styles/screen-link-color.js +47 -14
  103. package/build-module/components/global-styles/screen-link-color.js.map +1 -1
  104. package/build-module/components/global-styles/screen-typography-element.js +4 -0
  105. package/build-module/components/global-styles/screen-typography-element.js.map +1 -1
  106. package/build-module/components/global-styles/screen-typography.js +5 -0
  107. package/build-module/components/global-styles/screen-typography.js.map +1 -1
  108. package/build-module/components/global-styles/typography-panel.js +74 -13
  109. package/build-module/components/global-styles/typography-panel.js.map +1 -1
  110. package/build-module/components/global-styles/typography-utils.js +204 -0
  111. package/build-module/components/global-styles/typography-utils.js.map +1 -0
  112. package/build-module/components/global-styles/ui.js +10 -0
  113. package/build-module/components/global-styles/ui.js.map +1 -1
  114. package/build-module/components/global-styles/use-global-styles-output.js +294 -69
  115. package/build-module/components/global-styles/use-global-styles-output.js.map +1 -1
  116. package/build-module/components/global-styles/utils.js +47 -4
  117. package/build-module/components/global-styles/utils.js.map +1 -1
  118. package/build-module/components/header/index.js +25 -12
  119. package/build-module/components/header/index.js.map +1 -1
  120. package/build-module/components/header/undo-redo/redo.js +3 -2
  121. package/build-module/components/header/undo-redo/redo.js.map +1 -1
  122. package/build-module/components/keyboard-shortcut-help-modal/index.js +1 -2
  123. package/build-module/components/keyboard-shortcut-help-modal/index.js.map +1 -1
  124. package/build-module/components/list/actions/index.js +1 -1
  125. package/build-module/components/list/actions/index.js.map +1 -1
  126. package/build-module/components/save-button/index.js +3 -4
  127. package/build-module/components/save-button/index.js.map +1 -1
  128. package/build-module/components/sidebar/navigation-menu-sidebar/navigation-menu.js +3 -3
  129. package/build-module/components/sidebar/navigation-menu-sidebar/navigation-menu.js.map +1 -1
  130. package/build-module/components/sidebar/template-card/template-actions.js +1 -1
  131. package/build-module/components/sidebar/template-card/template-actions.js.map +1 -1
  132. package/build-module/components/template-details/edit-template-title.js +12 -3
  133. package/build-module/components/template-details/edit-template-title.js.map +1 -1
  134. package/build-module/components/template-details/index.js +3 -22
  135. package/build-module/components/template-details/index.js.map +1 -1
  136. package/build-module/components/template-details/template-areas.js +1 -1
  137. package/build-module/components/template-details/template-areas.js.map +1 -1
  138. package/build-module/components/template-part-converter/convert-to-template-part.js +3 -1
  139. package/build-module/components/template-part-converter/convert-to-template-part.js.map +1 -1
  140. package/build-module/hooks/index.js +1 -0
  141. package/build-module/hooks/index.js.map +1 -1
  142. package/build-module/hooks/template-part-edit.js +67 -0
  143. package/build-module/hooks/template-part-edit.js.map +1 -0
  144. package/build-module/store/selectors.js +5 -2
  145. package/build-module/store/selectors.js.map +1 -1
  146. package/build-style/style-rtl.css +55 -48
  147. package/build-style/style.css +55 -48
  148. package/package.json +29 -29
  149. package/src/components/add-new-template/add-custom-generic-template-modal.js +97 -0
  150. package/src/components/add-new-template/add-custom-template-modal.js +93 -68
  151. package/src/components/add-new-template/new-template.js +126 -95
  152. package/src/components/add-new-template/style.scss +41 -8
  153. package/src/components/add-new-template/utils.js +622 -80
  154. package/src/components/block-editor/index.js +0 -2
  155. package/src/components/code-editor/index.js +15 -5
  156. package/src/components/editor/index.js +11 -0
  157. package/src/components/error-boundary/index.js +5 -0
  158. package/src/components/global-styles/dimensions-panel.js +214 -24
  159. package/src/components/global-styles/global-styles-provider.js +8 -9
  160. package/src/components/global-styles/hooks.js +18 -0
  161. package/src/components/global-styles/screen-color-palette.js +25 -27
  162. package/src/components/global-styles/screen-colors.js +55 -7
  163. package/src/components/global-styles/screen-heading-color.js +201 -0
  164. package/src/components/global-styles/screen-link-color.js +65 -23
  165. package/src/components/global-styles/screen-typography-element.js +4 -0
  166. package/src/components/global-styles/screen-typography.js +6 -0
  167. package/src/components/global-styles/style.scss +14 -11
  168. package/src/components/global-styles/test/typography-utils.js +130 -0
  169. package/src/components/global-styles/test/use-global-styles-output.js +296 -2
  170. package/src/components/global-styles/typography-panel.js +85 -16
  171. package/src/components/global-styles/typography-utils.js +228 -0
  172. package/src/components/global-styles/ui.js +13 -0
  173. package/src/components/global-styles/use-global-styles-output.js +387 -89
  174. package/src/components/global-styles/utils.js +43 -2
  175. package/src/components/header/index.js +37 -13
  176. package/src/components/header/style.scss +5 -3
  177. package/src/components/header/undo-redo/redo.js +6 -2
  178. package/src/components/keyboard-shortcut-help-modal/index.js +1 -2
  179. package/src/components/keyboard-shortcut-help-modal/style.scss +0 -5
  180. package/src/components/list/actions/index.js +3 -1
  181. package/src/components/list/style.scss +0 -8
  182. package/src/components/save-button/index.js +10 -13
  183. package/src/components/sidebar/navigation-menu-sidebar/navigation-menu.js +1 -5
  184. package/src/components/sidebar/style.scss +4 -0
  185. package/src/components/sidebar/template-card/template-actions.js +3 -1
  186. package/src/components/template-details/edit-template-title.js +10 -2
  187. package/src/components/template-details/index.js +7 -22
  188. package/src/components/template-details/template-areas.js +3 -1
  189. package/src/components/template-part-converter/convert-to-template-part.js +3 -1
  190. package/src/components/test/error-boundary.js +38 -0
  191. package/src/hooks/index.js +1 -0
  192. package/src/hooks/template-part-edit.js +82 -0
  193. package/src/store/selectors.js +11 -5
  194. package/src/style.scss +0 -1
  195. package/build/components/edit-template-part-menu-button/index.js +0 -90
  196. package/build/components/edit-template-part-menu-button/index.js.map +0 -1
  197. package/build-module/components/edit-template-part-menu-button/index.js +0 -72
  198. package/build-module/components/edit-template-part-menu-button/index.js.map +0 -1
  199. package/src/components/edit-template-part-menu-button/index.js +0 -82
@@ -0,0 +1,97 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { kebabCase } from 'lodash';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { useState } from '@wordpress/element';
10
+ import { __ } from '@wordpress/i18n';
11
+ import {
12
+ Button,
13
+ Flex,
14
+ FlexItem,
15
+ Modal,
16
+ TextControl,
17
+ } from '@wordpress/components';
18
+
19
+ function AddCustomGenericTemplateModal( { onClose, createTemplate } ) {
20
+ const [ title, setTitle ] = useState( '' );
21
+ const defaultTitle = __( 'Custom Template' );
22
+ const [ isBusy, setIsBusy ] = useState( false );
23
+ async function onCreateTemplate( event ) {
24
+ event.preventDefault();
25
+
26
+ if ( isBusy ) {
27
+ return;
28
+ }
29
+
30
+ setIsBusy( true );
31
+
32
+ createTemplate(
33
+ {
34
+ slug:
35
+ 'wp-custom-template-' + kebabCase( title || defaultTitle ),
36
+ title: title || defaultTitle,
37
+ },
38
+ false
39
+ );
40
+ }
41
+ return (
42
+ <Modal
43
+ title={ __( 'Create custom template' ) }
44
+ closeLabel={ __( 'Close' ) }
45
+ onRequestClose={ () => {
46
+ onClose();
47
+ } }
48
+ overlayClassName="edit-site-custom-generic-template__modal"
49
+ >
50
+ <form onSubmit={ onCreateTemplate }>
51
+ <Flex align="flex-start" gap={ 8 }>
52
+ <FlexItem>
53
+ <TextControl
54
+ label={ __( 'Name' ) }
55
+ value={ title }
56
+ onChange={ setTitle }
57
+ placeholder={ defaultTitle }
58
+ disabled={ isBusy }
59
+ help={ __(
60
+ 'Describe the template, e.g. "Post with sidebar".'
61
+ ) }
62
+ />
63
+ </FlexItem>
64
+ </Flex>
65
+
66
+ <Flex
67
+ className="edit-site-custom-generic-template__modal-actions"
68
+ justify="flex-end"
69
+ expanded={ false }
70
+ >
71
+ <FlexItem>
72
+ <Button
73
+ variant="tertiary"
74
+ onClick={ () => {
75
+ onClose();
76
+ } }
77
+ >
78
+ { __( 'Cancel' ) }
79
+ </Button>
80
+ </FlexItem>
81
+ <FlexItem>
82
+ <Button
83
+ variant="primary"
84
+ type="submit"
85
+ isBusy={ isBusy }
86
+ aria-disabled={ isBusy }
87
+ >
88
+ { __( 'Create' ) }
89
+ </Button>
90
+ </FlexItem>
91
+ </Flex>
92
+ </form>
93
+ </Modal>
94
+ );
95
+ }
96
+
97
+ export default AddCustomGenericTemplateModal;
@@ -11,7 +11,6 @@ import {
11
11
  SearchControl,
12
12
  TextHighlight,
13
13
  __experimentalText as Text,
14
- __experimentalHeading as Heading,
15
14
  __unstableComposite as Composite,
16
15
  __unstableUseCompositeState as useCompositeState,
17
16
  __unstableCompositeItem as CompositeItem,
@@ -25,11 +24,6 @@ import { useEntityRecords } from '@wordpress/core-data';
25
24
  import { mapToIHasNameAndId } from './utils';
26
25
 
27
26
  const EMPTY_ARRAY = [];
28
- const BASE_QUERY = {
29
- order: 'asc',
30
- _fields: 'id,title,slug,link',
31
- context: 'view',
32
- };
33
27
 
34
28
  function SuggestionListItem( {
35
29
  suggestion,
@@ -46,23 +40,13 @@ function SuggestionListItem( {
46
40
  as={ Button }
47
41
  { ...composite }
48
42
  className={ baseCssClass }
49
- onClick={ () => {
50
- const title = sprintf(
51
- // translators: Represents the title of a user's custom template in the Site Editor, where %1$s is the singular name of a post type and %2$s is the name of the post, e.g. "Post: Hello, WordPress"
52
- __( '%1$s: %2$s' ),
53
- entityForSuggestions.labels.singular_name,
54
- suggestion.name
55
- );
56
- onSelect( {
57
- title,
58
- description: sprintf(
59
- // translators: Represents the description of a user's custom template in the Site Editor, e.g. "Template for Post: Hello, WordPress"
60
- __( 'Template for %1$s' ),
61
- title
62
- ),
63
- slug: `single-${ entityForSuggestions.slug }-${ suggestion.slug }`,
64
- } );
65
- } }
43
+ onClick={ () =>
44
+ onSelect(
45
+ entityForSuggestions.config.getSpecificTemplate(
46
+ suggestion
47
+ )
48
+ )
49
+ }
66
50
  >
67
51
  <span className={ `${ baseCssClass }__title` }>
68
52
  <TextHighlight text={ suggestion.name } highlight={ search } />
@@ -76,60 +60,90 @@ function SuggestionListItem( {
76
60
  );
77
61
  }
78
62
 
79
- function SuggestionList( { entityForSuggestions, onSelect } ) {
80
- const composite = useCompositeState( { orientation: 'vertical' } );
81
- const [ suggestions, setSuggestions ] = useState( EMPTY_ARRAY );
82
- // We need to track two values, the search input's value(searchInputValue)
83
- // and the one we want to debounce(search) and make REST API requests.
84
- const [ searchInputValue, setSearchInputValue ] = useState( '' );
85
- const [ search, setSearch ] = useState( '' );
86
- const debouncedSearch = useDebounce( setSearch, 250 );
87
- const query = {
88
- ...BASE_QUERY,
89
- search,
90
- orderby: search ? 'relevance' : 'modified',
91
- exclude: entityForSuggestions.postsToExclude,
92
- per_page: search ? 20 : 10,
93
- };
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
+ );
94
87
  const { records: searchResults, hasResolved: searchHasResolved } =
95
88
  useEntityRecords(
96
89
  entityForSuggestions.type,
97
90
  entityForSuggestions.slug,
98
91
  query
99
92
  );
100
- useEffect( () => {
101
- if ( search !== searchInputValue ) {
102
- debouncedSearch( searchInputValue );
103
- }
104
- }, [ search, searchInputValue ] );
105
- const entitiesInfo = useMemo( () => {
106
- if ( ! searchResults?.length ) return EMPTY_ARRAY;
107
- return mapToIHasNameAndId( searchResults, 'title.rendered' );
108
- }, [ searchResults ] );
109
- // Update suggestions only when the query has resolved.
93
+ const [ suggestions, setSuggestions ] = useState( EMPTY_ARRAY );
110
94
  useEffect( () => {
111
95
  if ( ! searchHasResolved ) return;
112
- setSuggestions( entitiesInfo );
113
- }, [ entitiesInfo, searchHasResolved ] );
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
+ }
114
125
  return (
115
126
  <>
116
- <SearchControl
117
- onChange={ setSearchInputValue }
118
- value={ searchInputValue }
119
- label={ entityForSuggestions.labels.search_items }
120
- placeholder={ entityForSuggestions.labels.search_items }
121
- />
127
+ { showSearchControl && (
128
+ <SearchControl
129
+ onChange={ setSearch }
130
+ value={ search }
131
+ label={ labels.search_items }
132
+ placeholder={ labels.search_items }
133
+ />
134
+ ) }
122
135
  { !! suggestions?.length && (
123
136
  <Composite
124
137
  { ...composite }
125
138
  role="listbox"
126
139
  className="edit-site-custom-template-modal__suggestions_list"
140
+ aria-label={ __( 'Suggestions list' ) }
127
141
  >
128
142
  { suggestions.map( ( suggestion ) => (
129
143
  <SuggestionListItem
130
144
  key={ suggestion.slug }
131
145
  suggestion={ suggestion }
132
- search={ search }
146
+ search={ debouncedSearch }
133
147
  onSelect={ onSelect }
134
148
  entityForSuggestions={ entityForSuggestions }
135
149
  composite={ composite }
@@ -137,9 +151,9 @@ function SuggestionList( { entityForSuggestions, onSelect } ) {
137
151
  ) ) }
138
152
  </Composite>
139
153
  ) }
140
- { search && ! suggestions?.length && (
154
+ { debouncedSearch && ! suggestions?.length && (
141
155
  <p className="edit-site-custom-template-modal__no-results">
142
- { entityForSuggestions.labels.not_found }
156
+ { labels.not_found }
143
157
  </p>
144
158
  ) }
145
159
  </>
@@ -176,34 +190,45 @@ function AddCustomTemplateModal( { onClose, onSelect, entityForSuggestions } ) {
176
190
  >
177
191
  <FlexItem
178
192
  isBlock
193
+ as={ Button }
179
194
  onClick={ () => {
180
- const { slug, title, description } =
181
- entityForSuggestions.template;
182
- onSelect( { slug, title, description } );
195
+ const {
196
+ slug,
197
+ title,
198
+ description,
199
+ templatePrefix,
200
+ } = entityForSuggestions.template;
201
+ onSelect( {
202
+ slug,
203
+ title,
204
+ description,
205
+ templatePrefix,
206
+ } );
183
207
  } }
184
208
  >
185
- <Heading level={ 5 }>
209
+ <Text as="span" weight={ 600 }>
186
210
  { entityForSuggestions.labels.all_items }
187
- </Heading>
211
+ </Text>
188
212
  <Text as="span">
189
213
  {
190
- // translators: The user is given the choice to set up a template for all items of a post type, or just a specific one.
214
+ // 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.
191
215
  __( 'For all items' )
192
216
  }
193
217
  </Text>
194
218
  </FlexItem>
195
219
  <FlexItem
196
220
  isBlock
221
+ as={ Button }
197
222
  onClick={ () => {
198
223
  setShowSearchEntities( true );
199
224
  } }
200
225
  >
201
- <Heading level={ 5 }>
226
+ <Text as="span" weight={ 600 }>
202
227
  { entityForSuggestions.labels.singular_name }
203
- </Heading>
228
+ </Text>
204
229
  <Text as="span">
205
230
  {
206
- // translators: The user is given the choice to set up a template for all items of a post type, or just a specific one.
231
+ // 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.
207
232
  __( 'For a specific item' )
208
233
  }
209
234
  </Text>
@@ -1,11 +1,8 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { filter, includes } from 'lodash';
5
-
6
1
  /**
7
2
  * WordPress dependencies
8
3
  */
4
+ import apiFetch from '@wordpress/api-fetch';
5
+ import { addQueryArgs } from '@wordpress/url';
9
6
  import {
10
7
  DropdownMenu,
11
8
  MenuGroup,
@@ -13,9 +10,8 @@ import {
13
10
  NavigableMenu,
14
11
  } from '@wordpress/components';
15
12
  import { useState } from '@wordpress/element';
16
- import { useSelect, useDispatch } from '@wordpress/data';
13
+ import { useDispatch } from '@wordpress/data';
17
14
  import { store as coreStore } from '@wordpress/core-data';
18
- import { store as editorStore } from '@wordpress/editor';
19
15
  import {
20
16
  archive,
21
17
  blockMeta,
@@ -30,15 +26,23 @@ import {
30
26
  postDate,
31
27
  search,
32
28
  tag,
29
+ layout as customGenericTemplateIcon,
33
30
  } from '@wordpress/icons';
34
- import { __, sprintf } from '@wordpress/i18n';
31
+ import { __ } from '@wordpress/i18n';
35
32
  import { store as noticesStore } from '@wordpress/notices';
36
33
 
37
34
  /**
38
35
  * Internal dependencies
39
36
  */
40
37
  import AddCustomTemplateModal from './add-custom-template-modal';
41
- import { usePostTypes, usePostTypesEntitiesInfo } from './utils';
38
+ import {
39
+ useExistingTemplates,
40
+ useDefaultTemplateTypes,
41
+ useTaxonomiesMenuItems,
42
+ usePostTypeMenuItems,
43
+ useAuthorMenuItem,
44
+ } from './utils';
45
+ import AddCustomGenericTemplateModal from './add-custom-generic-template-modal';
42
46
  import { useHistory } from '../routes';
43
47
  import { store as editSiteStore } from '../../store';
44
48
 
@@ -74,31 +78,34 @@ const TEMPLATE_ICONS = {
74
78
  };
75
79
 
76
80
  export default function NewTemplate( { postType } ) {
77
- const history = useHistory();
78
- const postTypes = usePostTypes();
79
81
  const [ showCustomTemplateModal, setShowCustomTemplateModal ] =
80
82
  useState( false );
83
+ const [
84
+ showCustomGenericTemplateModal,
85
+ setShowCustomGenericTemplateModal,
86
+ ] = useState( false );
81
87
  const [ entityForSuggestions, setEntityForSuggestions ] = useState( {} );
82
- const { existingTemplates, defaultTemplateTypes } = useSelect(
83
- ( select ) => ( {
84
- existingTemplates: select( coreStore ).getEntityRecords(
85
- 'postType',
86
- 'wp_template',
87
- { per_page: -1 }
88
- ),
89
- defaultTemplateTypes:
90
- select( editorStore ).__experimentalGetDefaultTemplateTypes(),
91
- } ),
92
- []
93
- );
94
- const postTypesEntitiesInfo = usePostTypesEntitiesInfo( existingTemplates );
88
+
89
+ const history = useHistory();
95
90
  const { saveEntityRecord } = useDispatch( coreStore );
96
91
  const { createErrorNotice } = useDispatch( noticesStore );
97
92
  const { setTemplate } = useDispatch( editSiteStore );
98
93
 
99
- async function createTemplate( template ) {
94
+ async function createTemplate( template, isWPSuggestion = true ) {
100
95
  try {
101
- const { title, description, slug } = template;
96
+ const { title, description, slug, templatePrefix } = template;
97
+ let templateContent = template.content;
98
+ // Try to find fallback content from existing templates.
99
+ if ( ! templateContent ) {
100
+ const fallbackTemplate = await apiFetch( {
101
+ path: addQueryArgs( '/wp/v2/templates/lookup', {
102
+ slug,
103
+ is_custom: ! isWPSuggestion,
104
+ template_prefix: templatePrefix,
105
+ } ),
106
+ } );
107
+ templateContent = fallbackTemplate.content;
108
+ }
102
109
  const newTemplate = await saveEntityRecord(
103
110
  'postType',
104
111
  'wp_template',
@@ -108,8 +115,9 @@ export default function NewTemplate( { postType } ) {
108
115
  slug: slug.toString(),
109
116
  status: 'publish',
110
117
  title,
118
+ content: templateContent,
111
119
  // This adds a post meta field in template that is part of `is_custom` value calculation.
112
- is_wp_suggestion: true,
120
+ is_wp_suggestion: isWPSuggestion,
113
121
  },
114
122
  { throwOnError: true }
115
123
  );
@@ -135,78 +143,14 @@ export default function NewTemplate( { postType } ) {
135
143
  } );
136
144
  }
137
145
  }
138
- const existingTemplateSlugs = ( existingTemplates || [] ).map(
139
- ( { slug } ) => slug
140
- );
141
- const missingTemplates = filter(
142
- defaultTemplateTypes,
143
- ( template ) =>
144
- includes( DEFAULT_TEMPLATE_SLUGS, template.slug ) &&
145
- ! includes( existingTemplateSlugs, template.slug )
146
- );
147
146
 
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
- []
147
+ const missingTemplates = useMissingTemplates(
148
+ setEntityForSuggestions,
149
+ setShowCustomTemplateModal
197
150
  );
198
- if ( ! missingTemplates.length && ! extraTemplates.length ) {
151
+ if ( ! missingTemplates.length ) {
199
152
  return null;
200
153
  }
201
- // Update the sort order to match the DEFAULT_TEMPLATE_SLUGS order.
202
- missingTemplates?.sort( ( template1, template2 ) => {
203
- return (
204
- DEFAULT_TEMPLATE_SLUGS.indexOf( template1.slug ) -
205
- DEFAULT_TEMPLATE_SLUGS.indexOf( template2.slug )
206
- );
207
- } );
208
- // Append all extra templates at the end of the list for now.
209
- missingTemplates.push( ...extraTemplates );
210
154
  return (
211
155
  <>
212
156
  <DropdownMenu
@@ -253,6 +197,21 @@ export default function NewTemplate( { postType } ) {
253
197
  );
254
198
  } ) }
255
199
  </MenuGroup>
200
+ <MenuGroup>
201
+ <MenuItem
202
+ icon={ customGenericTemplateIcon }
203
+ iconPosition="left"
204
+ info={ __(
205
+ 'Custom templates can be applied to any post or page.'
206
+ ) }
207
+ key="custom-template"
208
+ onClick={ () =>
209
+ setShowCustomGenericTemplateModal( true )
210
+ }
211
+ >
212
+ { __( 'Custom template' ) }
213
+ </MenuItem>
214
+ </MenuGroup>
256
215
  </NavigableMenu>
257
216
  ) }
258
217
  </DropdownMenu>
@@ -263,6 +222,78 @@ export default function NewTemplate( { postType } ) {
263
222
  entityForSuggestions={ entityForSuggestions }
264
223
  />
265
224
  ) }
225
+ { showCustomGenericTemplateModal && (
226
+ <AddCustomGenericTemplateModal
227
+ onClose={ () => setShowCustomGenericTemplateModal( false ) }
228
+ createTemplate={ createTemplate }
229
+ />
230
+ ) }
266
231
  </>
267
232
  );
268
233
  }
234
+
235
+ function useMissingTemplates(
236
+ setEntityForSuggestions,
237
+ setShowCustomTemplateModal
238
+ ) {
239
+ const existingTemplates = useExistingTemplates();
240
+ const defaultTemplateTypes = useDefaultTemplateTypes();
241
+ const existingTemplateSlugs = ( existingTemplates || [] ).map(
242
+ ( { slug } ) => slug
243
+ );
244
+ const missingDefaultTemplates = ( defaultTemplateTypes || [] ).filter(
245
+ ( template ) =>
246
+ DEFAULT_TEMPLATE_SLUGS.includes( template.slug ) &&
247
+ ! existingTemplateSlugs.includes( template.slug )
248
+ );
249
+ const onClickMenuItem = ( _entityForSuggestions ) => {
250
+ setShowCustomTemplateModal( true );
251
+ setEntityForSuggestions( _entityForSuggestions );
252
+ };
253
+ // We need to replace existing default template types with
254
+ // the create specific template functionality. The original
255
+ // info (title, description, etc.) is preserved in the
256
+ // used hooks.
257
+ const enhancedMissingDefaultTemplateTypes = [ ...missingDefaultTemplates ];
258
+ const { defaultTaxonomiesMenuItems, taxonomiesMenuItems } =
259
+ useTaxonomiesMenuItems( onClickMenuItem );
260
+ const { defaultPostTypesMenuItems, postTypesMenuItems } =
261
+ usePostTypeMenuItems( onClickMenuItem );
262
+
263
+ const authorMenuItem = useAuthorMenuItem( onClickMenuItem );
264
+ [
265
+ ...defaultTaxonomiesMenuItems,
266
+ ...defaultPostTypesMenuItems,
267
+ authorMenuItem,
268
+ ].forEach( ( menuItem ) => {
269
+ if ( ! menuItem ) {
270
+ return;
271
+ }
272
+ const matchIndex = enhancedMissingDefaultTemplateTypes.findIndex(
273
+ ( template ) => template.slug === menuItem.slug
274
+ );
275
+ // Some default template types might have been filtered above from
276
+ // `missingDefaultTemplates` because they only check for the general
277
+ // template. So here we either replace or append the item, augmented
278
+ // with the check if it has available specific item to create a
279
+ // template for.
280
+ if ( matchIndex > -1 ) {
281
+ enhancedMissingDefaultTemplateTypes[ matchIndex ] = menuItem;
282
+ } else {
283
+ enhancedMissingDefaultTemplateTypes.push( menuItem );
284
+ }
285
+ } );
286
+ // Update the sort order to match the DEFAULT_TEMPLATE_SLUGS order.
287
+ enhancedMissingDefaultTemplateTypes?.sort( ( template1, template2 ) => {
288
+ return (
289
+ DEFAULT_TEMPLATE_SLUGS.indexOf( template1.slug ) -
290
+ DEFAULT_TEMPLATE_SLUGS.indexOf( template2.slug )
291
+ );
292
+ } );
293
+ const missingTemplates = [
294
+ ...enhancedMissingDefaultTemplateTypes,
295
+ ...postTypesMenuItems,
296
+ ...taxonomiesMenuItems,
297
+ ];
298
+ return missingTemplates;
299
+ }