@wordpress/editor 11.0.1 → 12.0.2

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 (241) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +1 -1
  3. package/build/components/autosave-monitor/index.js +5 -0
  4. package/build/components/autosave-monitor/index.js.map +1 -1
  5. package/build/components/character-count/index.js +1 -1
  6. package/build/components/character-count/index.js.map +1 -1
  7. package/build/components/editor-help/add-blocks.native.js +6 -5
  8. package/build/components/editor-help/add-blocks.native.js.map +1 -1
  9. package/build/components/editor-help/customize-blocks.native.js +7 -6
  10. package/build/components/editor-help/customize-blocks.native.js.map +1 -1
  11. package/build/components/editor-help/help-detail-navigation-screen.native.js +29 -15
  12. package/build/components/editor-help/help-detail-navigation-screen.native.js.map +1 -1
  13. package/build/components/editor-help/help-get-support-button.native.js +46 -0
  14. package/build/components/editor-help/help-get-support-button.native.js.map +1 -0
  15. package/build/components/editor-help/help-topic-row.native.js +3 -2
  16. package/build/components/editor-help/help-topic-row.native.js.map +1 -1
  17. package/build/components/editor-help/index.native.js +87 -26
  18. package/build/components/editor-help/index.native.js.map +1 -1
  19. package/build/components/editor-help/intro-to-blocks.native.js +19 -9
  20. package/build/components/editor-help/intro-to-blocks.native.js.map +1 -1
  21. package/build/components/editor-help/move-blocks.native.js +6 -5
  22. package/build/components/editor-help/move-blocks.native.js.map +1 -1
  23. package/build/components/editor-help/remove-blocks.native.js +6 -5
  24. package/build/components/editor-help/remove-blocks.native.js.map +1 -1
  25. package/build/components/editor-help/view-sections.native.js +23 -5
  26. package/build/components/editor-help/view-sections.native.js.map +1 -1
  27. package/build/components/entities-saved-states/entity-type-list.js +22 -13
  28. package/build/components/entities-saved-states/entity-type-list.js.map +1 -1
  29. package/build/components/entities-saved-states/index.js +23 -4
  30. package/build/components/entities-saved-states/index.js.map +1 -1
  31. package/build/components/global-keyboard-shortcuts/save-shortcut.js +12 -14
  32. package/build/components/global-keyboard-shortcuts/save-shortcut.js.map +1 -1
  33. package/build/components/global-keyboard-shortcuts/visual-editor-shortcuts.js +0 -4
  34. package/build/components/global-keyboard-shortcuts/visual-editor-shortcuts.js.map +1 -1
  35. package/build/components/index.js +9 -0
  36. package/build/components/index.js.map +1 -1
  37. package/build/components/local-autosave-monitor/index.js +1 -2
  38. package/build/components/local-autosave-monitor/index.js.map +1 -1
  39. package/build/components/post-format/index.js +3 -1
  40. package/build/components/post-format/index.js.map +1 -1
  41. package/build/components/post-locked-modal/index.js +1 -1
  42. package/build/components/post-locked-modal/index.js.map +1 -1
  43. package/build/components/post-saved-state/index.js +37 -46
  44. package/build/components/post-saved-state/index.js.map +1 -1
  45. package/build/components/post-taxonomies/flat-term-selector.js +154 -211
  46. package/build/components/post-taxonomies/flat-term-selector.js.map +1 -1
  47. package/build/components/post-title/index.js +53 -25
  48. package/build/components/post-title/index.js.map +1 -1
  49. package/build/components/provider/index.native.js +18 -7
  50. package/build/components/provider/index.native.js.map +1 -1
  51. package/build/components/provider/use-block-editor-settings.js +32 -5
  52. package/build/components/provider/use-block-editor-settings.js.map +1 -1
  53. package/build/components/word-count/index.js +1 -1
  54. package/build/components/word-count/index.js.map +1 -1
  55. package/build/store/actions.js +13 -38
  56. package/build/store/actions.js.map +1 -1
  57. package/build/store/defaults.js +7 -2
  58. package/build/store/defaults.js.map +1 -1
  59. package/build/store/selectors.js +4 -93
  60. package/build/store/selectors.js.map +1 -1
  61. package/build/store/utils/notice-builder.js +15 -18
  62. package/build/store/utils/notice-builder.js.map +1 -1
  63. package/build/{store/utils → utils}/get-template-part-icon.js +1 -1
  64. package/build/utils/get-template-part-icon.js.map +1 -0
  65. package/build/utils/index.js +8 -0
  66. package/build/utils/index.js.map +1 -1
  67. package/build-module/components/autosave-monitor/index.js +5 -0
  68. package/build-module/components/autosave-monitor/index.js.map +1 -1
  69. package/build-module/components/character-count/index.js +1 -1
  70. package/build-module/components/character-count/index.js.map +1 -1
  71. package/build-module/components/editor-help/add-blocks.native.js +7 -6
  72. package/build-module/components/editor-help/add-blocks.native.js.map +1 -1
  73. package/build-module/components/editor-help/customize-blocks.native.js +8 -7
  74. package/build-module/components/editor-help/customize-blocks.native.js.map +1 -1
  75. package/build-module/components/editor-help/help-detail-navigation-screen.native.js +31 -18
  76. package/build-module/components/editor-help/help-detail-navigation-screen.native.js.map +1 -1
  77. package/build-module/components/editor-help/help-get-support-button.native.js +34 -0
  78. package/build-module/components/editor-help/help-get-support-button.native.js.map +1 -0
  79. package/build-module/components/editor-help/help-topic-row.native.js +3 -2
  80. package/build-module/components/editor-help/help-topic-row.native.js.map +1 -1
  81. package/build-module/components/editor-help/index.native.js +81 -28
  82. package/build-module/components/editor-help/index.native.js.map +1 -1
  83. package/build-module/components/editor-help/intro-to-blocks.native.js +19 -10
  84. package/build-module/components/editor-help/intro-to-blocks.native.js.map +1 -1
  85. package/build-module/components/editor-help/move-blocks.native.js +7 -6
  86. package/build-module/components/editor-help/move-blocks.native.js.map +1 -1
  87. package/build-module/components/editor-help/remove-blocks.native.js +7 -6
  88. package/build-module/components/editor-help/remove-blocks.native.js.map +1 -1
  89. package/build-module/components/editor-help/view-sections.native.js +22 -5
  90. package/build-module/components/editor-help/view-sections.native.js.map +1 -1
  91. package/build-module/components/entities-saved-states/entity-type-list.js +24 -13
  92. package/build-module/components/entities-saved-states/entity-type-list.js.map +1 -1
  93. package/build-module/components/entities-saved-states/index.js +23 -4
  94. package/build-module/components/entities-saved-states/index.js.map +1 -1
  95. package/build-module/components/global-keyboard-shortcuts/save-shortcut.js +12 -14
  96. package/build-module/components/global-keyboard-shortcuts/save-shortcut.js.map +1 -1
  97. package/build-module/components/global-keyboard-shortcuts/visual-editor-shortcuts.js +0 -4
  98. package/build-module/components/global-keyboard-shortcuts/visual-editor-shortcuts.js.map +1 -1
  99. package/build-module/components/index.js +1 -0
  100. package/build-module/components/index.js.map +1 -1
  101. package/build-module/components/local-autosave-monitor/index.js +1 -2
  102. package/build-module/components/local-autosave-monitor/index.js.map +1 -1
  103. package/build-module/components/post-format/index.js +4 -2
  104. package/build-module/components/post-format/index.js.map +1 -1
  105. package/build-module/components/post-locked-modal/index.js +1 -1
  106. package/build-module/components/post-locked-modal/index.js.map +1 -1
  107. package/build-module/components/post-saved-state/index.js +38 -46
  108. package/build-module/components/post-saved-state/index.js.map +1 -1
  109. package/build-module/components/post-taxonomies/flat-term-selector.js +156 -214
  110. package/build-module/components/post-taxonomies/flat-term-selector.js.map +1 -1
  111. package/build-module/components/post-title/index.js +53 -22
  112. package/build-module/components/post-title/index.js.map +1 -1
  113. package/build-module/components/provider/index.native.js +18 -7
  114. package/build-module/components/provider/index.native.js.map +1 -1
  115. package/build-module/components/provider/use-block-editor-settings.js +31 -5
  116. package/build-module/components/provider/use-block-editor-settings.js.map +1 -1
  117. package/build-module/components/word-count/index.js +1 -1
  118. package/build-module/components/word-count/index.js.map +1 -1
  119. package/build-module/store/actions.js +13 -36
  120. package/build-module/store/actions.js.map +1 -1
  121. package/build-module/store/defaults.js +7 -2
  122. package/build-module/store/defaults.js.map +1 -1
  123. package/build-module/store/selectors.js +3 -85
  124. package/build-module/store/selectors.js.map +1 -1
  125. package/build-module/store/utils/notice-builder.js +15 -18
  126. package/build-module/store/utils/notice-builder.js.map +1 -1
  127. package/build-module/{store/utils → utils}/get-template-part-icon.js +2 -2
  128. package/build-module/utils/get-template-part-icon.js.map +1 -0
  129. package/build-module/utils/index.js +1 -0
  130. package/build-module/utils/index.js.map +1 -1
  131. package/build-style/style-rtl.css +9 -60
  132. package/build-style/style.css +9 -60
  133. package/package.json +28 -28
  134. package/src/components/autosave-monitor/index.js +5 -0
  135. package/src/components/autosave-monitor/test/index.js +10 -4
  136. package/src/components/character-count/index.js +3 -2
  137. package/src/components/editor-help/add-blocks.native.js +17 -12
  138. package/src/components/editor-help/customize-blocks.native.js +16 -13
  139. package/src/components/editor-help/help-detail-navigation-screen.native.js +45 -15
  140. package/src/components/editor-help/help-get-support-button.native.js +38 -0
  141. package/src/components/editor-help/help-topic-row.native.js +2 -2
  142. package/src/components/editor-help/images/add-dark.png +0 -0
  143. package/src/components/editor-help/images/add-dark@2x.png +0 -0
  144. package/src/components/editor-help/images/add-dark@3x.png +0 -0
  145. package/src/components/editor-help/images/add-light.png +0 -0
  146. package/src/components/editor-help/images/add-light@2x.png +0 -0
  147. package/src/components/editor-help/images/add-light@3x.png +0 -0
  148. package/src/components/editor-help/images/block-layout-collage.png +0 -0
  149. package/src/components/editor-help/images/block-layout-collage@2x.png +0 -0
  150. package/src/components/editor-help/images/block-layout-collage@3x.png +0 -0
  151. package/src/components/editor-help/images/build-layouts-dark.png +0 -0
  152. package/src/components/editor-help/images/build-layouts-dark@2x.png +0 -0
  153. package/src/components/editor-help/images/build-layouts-dark@3x.png +0 -0
  154. package/src/components/editor-help/images/build-layouts-light.png +0 -0
  155. package/src/components/editor-help/images/build-layouts-light@2x.png +0 -0
  156. package/src/components/editor-help/images/build-layouts-light@3x.png +0 -0
  157. package/src/components/editor-help/images/edit-media-dark.png +0 -0
  158. package/src/components/editor-help/images/edit-media-dark@2x.png +0 -0
  159. package/src/components/editor-help/images/edit-media-dark@3x.png +0 -0
  160. package/src/components/editor-help/images/edit-media-light.png +0 -0
  161. package/src/components/editor-help/images/edit-media-light@2x.png +0 -0
  162. package/src/components/editor-help/images/edit-media-light@3x.png +0 -0
  163. package/src/components/editor-help/images/embed-media-dark.png +0 -0
  164. package/src/components/editor-help/images/embed-media-dark@2x.png +0 -0
  165. package/src/components/editor-help/images/embed-media-dark@3x.png +0 -0
  166. package/src/components/editor-help/images/embed-media-light.png +0 -0
  167. package/src/components/editor-help/images/embed-media-light@2x.png +0 -0
  168. package/src/components/editor-help/images/embed-media-light@3x.png +0 -0
  169. package/src/components/editor-help/images/move-dark.png +0 -0
  170. package/src/components/editor-help/images/move-dark@2x.png +0 -0
  171. package/src/components/editor-help/images/move-dark@3x.png +0 -0
  172. package/src/components/editor-help/images/move-light.png +0 -0
  173. package/src/components/editor-help/images/move-light@2x.png +0 -0
  174. package/src/components/editor-help/images/move-light@3x.png +0 -0
  175. package/src/components/editor-help/images/options-dark.png +0 -0
  176. package/src/components/editor-help/images/options-dark@2x.png +0 -0
  177. package/src/components/editor-help/images/options-dark@3x.png +0 -0
  178. package/src/components/editor-help/images/options-light.png +0 -0
  179. package/src/components/editor-help/images/options-light@2x.png +0 -0
  180. package/src/components/editor-help/images/options-light@3x.png +0 -0
  181. package/src/components/editor-help/images/rich-text-dark.png +0 -0
  182. package/src/components/editor-help/images/rich-text-dark@2x.png +0 -0
  183. package/src/components/editor-help/images/rich-text-dark@3x.png +0 -0
  184. package/src/components/editor-help/images/rich-text-light.png +0 -0
  185. package/src/components/editor-help/images/rich-text-light@2x.png +0 -0
  186. package/src/components/editor-help/images/rich-text-light@3x.png +0 -0
  187. package/src/components/editor-help/images/settings-dark.png +0 -0
  188. package/src/components/editor-help/images/settings-dark@2x.png +0 -0
  189. package/src/components/editor-help/images/settings-dark@3x.png +0 -0
  190. package/src/components/editor-help/images/settings-light.png +0 -0
  191. package/src/components/editor-help/images/settings-light@2x.png +0 -0
  192. package/src/components/editor-help/images/settings-light@3x.png +0 -0
  193. package/src/components/editor-help/index.native.js +147 -43
  194. package/src/components/editor-help/intro-to-blocks.native.js +63 -43
  195. package/src/components/editor-help/move-blocks.native.js +12 -7
  196. package/src/components/editor-help/remove-blocks.native.js +11 -8
  197. package/src/components/editor-help/style.android.scss +6 -0
  198. package/src/components/editor-help/style.ios.scss +6 -0
  199. package/src/components/editor-help/style.scss +49 -32
  200. package/src/components/editor-help/test/index.native.js +80 -0
  201. package/src/components/editor-help/view-sections.native.js +47 -4
  202. package/src/components/entities-saved-states/entity-type-list.js +29 -10
  203. package/src/components/entities-saved-states/index.js +38 -8
  204. package/src/components/global-keyboard-shortcuts/save-shortcut.js +34 -42
  205. package/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js +8 -16
  206. package/src/components/index.js +1 -0
  207. package/src/components/local-autosave-monitor/index.js +1 -3
  208. package/src/components/post-format/index.js +6 -2
  209. package/src/components/post-format/style.scss +1 -0
  210. package/src/components/post-locked-modal/index.js +1 -1
  211. package/src/components/post-saved-state/index.js +41 -55
  212. package/src/components/post-saved-state/style.scss +10 -1
  213. package/src/components/post-saved-state/test/__snapshots__/index.js.snap +20 -0
  214. package/src/components/post-saved-state/test/index.js +2 -2
  215. package/src/components/post-taxonomies/flat-term-selector.js +220 -254
  216. package/src/components/post-title/index.js +47 -30
  217. package/src/components/post-title/style.scss +1 -70
  218. package/src/components/provider/index.native.js +17 -5
  219. package/src/components/provider/use-block-editor-settings.js +25 -2
  220. package/src/components/word-count/index.js +3 -2
  221. package/src/store/actions.js +13 -41
  222. package/src/store/defaults.js +7 -2
  223. package/src/store/selectors.js +2 -112
  224. package/src/store/test/actions.js +4 -2
  225. package/src/store/utils/notice-builder.js +17 -19
  226. package/src/store/utils/test/notice-builder.js +1 -1
  227. package/src/{store/utils → utils}/get-template-part-icon.js +2 -2
  228. package/src/utils/index.js +1 -0
  229. package/build/store/utils/get-template-part-icon.js.map +0 -1
  230. package/build-module/store/utils/get-template-part-icon.js.map +0 -1
  231. package/src/components/editor-help/images/add-blocks.png +0 -0
  232. package/src/components/editor-help/images/customize-blocks.png +0 -0
  233. package/src/components/editor-help/images/cut-copy-duplicate-blocks.png +0 -0
  234. package/src/components/editor-help/images/edit-or-replace-media.png +0 -0
  235. package/src/components/editor-help/images/intro-blocks-1.png +0 -0
  236. package/src/components/editor-help/images/intro-blocks-2.png +0 -0
  237. package/src/components/editor-help/images/intro-blocks-3.png +0 -0
  238. package/src/components/editor-help/images/intro-blocks-4.png +0 -0
  239. package/src/components/editor-help/images/move-blocks.png +0 -0
  240. package/src/components/editor-help/images/remove-blocks.png +0 -0
  241. package/src/components/editor-help/images/what-is-a-block.png +0 -0
@@ -1,31 +1,20 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import {
5
- debounce,
6
- escape as escapeString,
7
- find,
8
- get,
9
- invoke,
10
- isEmpty,
11
- uniqBy,
12
- } from 'lodash';
4
+ import { escape as escapeString, find, get, uniqBy } from 'lodash';
13
5
 
14
6
  /**
15
7
  * WordPress dependencies
16
8
  */
17
9
  import { __, _x, sprintf } from '@wordpress/i18n';
18
- import { Component } from '@wordpress/element';
19
- import {
20
- FormTokenField,
21
- withFilters,
22
- withSpokenMessages,
23
- } from '@wordpress/components';
24
- import { withSelect, withDispatch } from '@wordpress/data';
10
+ import { useEffect, useMemo, useState } from '@wordpress/element';
11
+ import { FormTokenField, withFilters } from '@wordpress/components';
12
+ import { useSelect, useDispatch } from '@wordpress/data';
25
13
  import { store as coreStore } from '@wordpress/core-data';
26
- import { compose } from '@wordpress/compose';
14
+ import { useDebounce } from '@wordpress/compose';
27
15
  import apiFetch from '@wordpress/api-fetch';
28
16
  import { addQueryArgs } from '@wordpress/url';
17
+ import { speak } from '@wordpress/a11y';
29
18
 
30
19
  /**
31
20
  * Internal dependencies
@@ -34,6 +23,14 @@ import { store as editorStore } from '../../store';
34
23
  import { unescapeString, unescapeTerm, unescapeTerms } from '../../utils/terms';
35
24
  import MostUsedTerms from './most-used-terms';
36
25
 
26
+ /**
27
+ * Shared reference to an empty array for cases where it is important to avoid
28
+ * returning a new array reference on every invocation.
29
+ *
30
+ * @type {Array<any>}
31
+ */
32
+ const EMPTY_ARRAY = [];
33
+
37
34
  /**
38
35
  * Module constants
39
36
  */
@@ -42,7 +39,8 @@ const DEFAULT_QUERY = {
42
39
  per_page: MAX_TERMS_SUGGESTIONS,
43
40
  orderby: 'count',
44
41
  order: 'desc',
45
- _fields: 'id,name,count',
42
+ _fields: 'id,name',
43
+ context: 'view',
46
44
  };
47
45
 
48
46
  const isSameTermName = ( termA, termB ) =>
@@ -56,171 +54,195 @@ const termNamesToIds = ( names, terms ) => {
56
54
  );
57
55
  };
58
56
 
59
- class FlatTermSelector extends Component {
60
- constructor() {
61
- super( ...arguments );
62
- this.onChange = this.onChange.bind( this );
63
- this.searchTerms = debounce( this.searchTerms.bind( this ), 500 );
64
- this.findOrCreateTerm = this.findOrCreateTerm.bind( this );
65
- this.appendTerm = this.appendTerm.bind( this );
66
- this.state = {
67
- loading: ! isEmpty( this.props.terms ),
68
- availableTerms: [],
69
- selectedTerms: [],
70
- };
71
- }
57
+ // Tries to create a term or fetch it if it already exists.
58
+ function findOrCreateTerm( termName, restBase ) {
59
+ const escapedTermName = escapeString( termName );
72
60
 
73
- componentDidMount() {
74
- if ( ! isEmpty( this.props.terms ) ) {
75
- this.initRequest = this.fetchTerms( {
76
- include: this.props.terms.join( ',' ),
77
- per_page: -1,
78
- } );
79
- this.initRequest.then(
80
- () => {
81
- this.setState( { loading: false } );
82
- },
83
- ( xhr ) => {
84
- if ( xhr.statusText === 'abort' ) {
85
- return;
86
- }
87
- this.setState( {
88
- loading: false,
89
- } );
90
- }
61
+ return apiFetch( {
62
+ path: `/wp/v2/${ restBase }`,
63
+ method: 'POST',
64
+ data: { name: escapedTermName },
65
+ } )
66
+ .catch( ( error ) => {
67
+ const errorCode = error.code;
68
+ if ( errorCode === 'term_exists' ) {
69
+ // If the terms exist, fetch it instead of creating a new one.
70
+ const addRequest = apiFetch( {
71
+ path: addQueryArgs( `/wp/v2/${ restBase }`, {
72
+ ...DEFAULT_QUERY,
73
+ search: escapedTermName,
74
+ } ),
75
+ } ).then( unescapeTerms );
76
+
77
+ return addRequest.then( ( searchResult ) => {
78
+ return find( searchResult, ( result ) =>
79
+ isSameTermName( result.name, termName )
80
+ );
81
+ } );
82
+ }
83
+
84
+ return Promise.reject( error );
85
+ } )
86
+ .then( unescapeTerm );
87
+ }
88
+
89
+ function FlatTermSelector( { slug } ) {
90
+ const [ values, setValues ] = useState( [] );
91
+ const [ search, setSearch ] = useState( '' );
92
+ const debouncedSearch = useDebounce( setSearch, 500 );
93
+
94
+ const {
95
+ terms,
96
+ termIds,
97
+ taxonomy,
98
+ hasAssignAction,
99
+ hasCreateAction,
100
+ hasResolvedTerms,
101
+ } = useSelect(
102
+ ( select ) => {
103
+ const { getCurrentPost, getEditedPostAttribute } = select(
104
+ editorStore
91
105
  );
92
- }
93
- }
106
+ const {
107
+ getEntityRecords,
108
+ getTaxonomy,
109
+ hasFinishedResolution,
110
+ } = select( coreStore );
111
+ const post = getCurrentPost();
112
+ const _taxonomy = getTaxonomy( slug );
113
+ const _termIds = _taxonomy
114
+ ? getEditedPostAttribute( _taxonomy.rest_base )
115
+ : EMPTY_ARRAY;
94
116
 
95
- componentWillUnmount() {
96
- invoke( this.initRequest, [ 'abort' ] );
97
- invoke( this.searchRequest, [ 'abort' ] );
98
- }
117
+ const query = {
118
+ ...DEFAULT_QUERY,
119
+ include: _termIds.join( ',' ),
120
+ per_page: -1,
121
+ };
99
122
 
100
- componentDidUpdate( prevProps ) {
101
- if ( prevProps.terms !== this.props.terms ) {
102
- this.updateSelectedTerms( this.props.terms );
103
- }
104
- }
123
+ return {
124
+ hasCreateAction: _taxonomy
125
+ ? get(
126
+ post,
127
+ [
128
+ '_links',
129
+ 'wp:action-create-' + _taxonomy.rest_base,
130
+ ],
131
+ false
132
+ )
133
+ : false,
134
+ hasAssignAction: _taxonomy
135
+ ? get(
136
+ post,
137
+ [
138
+ '_links',
139
+ 'wp:action-assign-' + _taxonomy.rest_base,
140
+ ],
141
+ false
142
+ )
143
+ : false,
144
+ taxonomy: _taxonomy,
145
+ termIds: _termIds,
146
+ terms: _termIds.length
147
+ ? getEntityRecords( 'taxonomy', slug, query )
148
+ : EMPTY_ARRAY,
149
+ hasResolvedTerms: hasFinishedResolution( 'getEntityRecords', [
150
+ 'taxonomy',
151
+ slug,
152
+ query,
153
+ ] ),
154
+ };
155
+ },
156
+ [ slug ]
157
+ );
105
158
 
106
- fetchTerms( params = {} ) {
107
- const { taxonomy } = this.props;
108
- const query = { ...DEFAULT_QUERY, ...params };
109
- const request = apiFetch( {
110
- path: addQueryArgs( `/wp/v2/${ taxonomy.rest_base }`, query ),
111
- } );
112
- request.then( unescapeTerms ).then( ( terms ) => {
113
- this.setState( ( state ) => ( {
114
- availableTerms: state.availableTerms.concat(
115
- terms.filter(
116
- ( term ) =>
117
- ! find(
118
- state.availableTerms,
119
- ( availableTerm ) =>
120
- availableTerm.id === term.id
121
- )
122
- )
123
- ),
124
- } ) );
125
- this.updateSelectedTerms( this.props.terms );
126
- } );
159
+ const { searchResults } = useSelect(
160
+ ( select ) => {
161
+ const { getEntityRecords } = select( coreStore );
127
162
 
128
- return request;
129
- }
163
+ return {
164
+ searchResults: !! search
165
+ ? getEntityRecords( 'taxonomy', slug, {
166
+ ...DEFAULT_QUERY,
167
+ search,
168
+ } )
169
+ : EMPTY_ARRAY,
170
+ };
171
+ },
172
+ [ search ]
173
+ );
130
174
 
131
- updateSelectedTerms( terms = [] ) {
132
- const selectedTerms = terms.reduce( ( accumulator, termId ) => {
133
- const termObject = find(
134
- this.state.availableTerms,
135
- ( term ) => term.id === termId
175
+ // Update terms state only after the selectors are resolved.
176
+ // We're using this to avoid terms temporarily disappearing on slow networks
177
+ // while core data makes REST API requests.
178
+ useEffect( () => {
179
+ if ( hasResolvedTerms ) {
180
+ const newValues = terms.map( ( term ) =>
181
+ unescapeString( term.name )
136
182
  );
137
- if ( termObject ) {
138
- accumulator.push( termObject.name );
139
- }
140
183
 
141
- return accumulator;
142
- }, [] );
143
- this.setState( {
144
- selectedTerms,
145
- } );
184
+ setValues( newValues );
185
+ }
186
+ }, [ terms, hasResolvedTerms ] );
187
+
188
+ const suggestions = useMemo( () => {
189
+ return ( searchResults ?? [] ).map( ( term ) =>
190
+ unescapeString( term.name )
191
+ );
192
+ }, [ searchResults ] );
193
+
194
+ const { editPost } = useDispatch( editorStore );
195
+
196
+ if ( ! hasAssignAction ) {
197
+ return null;
146
198
  }
147
199
 
148
- findOrCreateTerm( termName ) {
149
- const { taxonomy } = this.props;
150
- const termNameEscaped = escapeString( termName );
151
- // Tries to create a term or fetch it if it already exists.
152
- return apiFetch( {
153
- path: `/wp/v2/${ taxonomy.rest_base }`,
154
- method: 'POST',
155
- data: { name: termNameEscaped },
156
- } )
157
- .catch( ( error ) => {
158
- const errorCode = error.code;
159
- if ( errorCode === 'term_exists' ) {
160
- // If the terms exist, fetch it instead of creating a new one.
161
- this.addRequest = apiFetch( {
162
- path: addQueryArgs( `/wp/v2/${ taxonomy.rest_base }`, {
163
- ...DEFAULT_QUERY,
164
- search: termNameEscaped,
165
- } ),
166
- } ).then( unescapeTerms );
167
- return this.addRequest.then( ( searchResult ) => {
168
- return find( searchResult, ( result ) =>
169
- isSameTermName( result.name, termName )
170
- );
171
- } );
172
- }
173
- return Promise.reject( error );
174
- } )
175
- .then( unescapeTerm );
200
+ function onUpdateTerms( newTermIds ) {
201
+ editPost( { [ taxonomy.rest_base ]: newTermIds } );
176
202
  }
177
203
 
178
- onChange( termNames ) {
204
+ function onChange( termNames ) {
205
+ const availableTerms = [ ...terms, ...( searchResults ?? [] ) ];
179
206
  const uniqueTerms = uniqBy( termNames, ( term ) => term.toLowerCase() );
180
- this.setState( { selectedTerms: uniqueTerms } );
181
207
  const newTermNames = uniqueTerms.filter(
182
208
  ( termName ) =>
183
- ! find( this.state.availableTerms, ( term ) =>
209
+ ! find( availableTerms, ( term ) =>
184
210
  isSameTermName( term.name, termName )
185
211
  )
186
212
  );
187
213
 
214
+ // Optimistically update term values.
215
+ // The selector will always re-fetch terms later.
216
+ setValues( uniqueTerms );
217
+
188
218
  if ( newTermNames.length === 0 ) {
189
- return this.props.onUpdateTerms(
190
- termNamesToIds( uniqueTerms, this.state.availableTerms ),
191
- this.props.taxonomy.rest_base
219
+ return onUpdateTerms(
220
+ termNamesToIds( uniqueTerms, availableTerms )
192
221
  );
193
222
  }
194
- Promise.all( newTermNames.map( this.findOrCreateTerm ) ).then(
195
- ( newTerms ) => {
196
- const newAvailableTerms = this.state.availableTerms.concat(
197
- newTerms
198
- );
199
- this.setState( { availableTerms: newAvailableTerms } );
200
- return this.props.onUpdateTerms(
201
- termNamesToIds( uniqueTerms, newAvailableTerms ),
202
- this.props.taxonomy.rest_base
203
- );
204
- }
205
- );
206
- }
207
223
 
208
- searchTerms( search = '' ) {
209
- invoke( this.searchRequest, [ 'abort' ] );
210
- if ( search.length >= 3 ) {
211
- this.searchRequest = this.fetchTerms( { search } );
224
+ if ( ! hasCreateAction ) {
225
+ return;
212
226
  }
213
- }
214
227
 
215
- appendTerm( newTerm ) {
216
- const { onUpdateTerms, taxonomy, terms = [], slug, speak } = this.props;
228
+ Promise.all(
229
+ newTermNames.map( ( termName ) =>
230
+ findOrCreateTerm( termName, taxonomy.rest_base )
231
+ )
232
+ ).then( ( newTerms ) => {
233
+ const newAvailableTerms = availableTerms.concat( newTerms );
234
+ return onUpdateTerms(
235
+ termNamesToIds( uniqueTerms, newAvailableTerms )
236
+ );
237
+ } );
238
+ }
217
239
 
218
- if ( terms.includes( newTerm.id ) ) {
240
+ function appendTerm( newTerm ) {
241
+ if ( termIds.includes( newTerm.id ) ) {
219
242
  return;
220
243
  }
221
244
 
222
- const newTerms = [ ...terms, newTerm.id ];
223
-
245
+ const newTermIds = [ ...termIds, newTerm.id ];
224
246
  const termAddedMessage = sprintf(
225
247
  /* translators: %s: term name. */
226
248
  _x( '%s added', 'term' ),
@@ -232,109 +254,53 @@ class FlatTermSelector extends Component {
232
254
  );
233
255
 
234
256
  speak( termAddedMessage, 'assertive' );
235
-
236
- this.setState( {
237
- availableTerms: [ ...this.state.availableTerms, newTerm ],
238
- } );
239
-
240
- onUpdateTerms( newTerms, taxonomy.rest_base );
257
+ onUpdateTerms( newTermIds );
241
258
  }
242
259
 
243
- render() {
244
- const { slug, taxonomy, hasAssignAction } = this.props;
245
-
246
- if ( ! hasAssignAction ) {
247
- return null;
248
- }
249
-
250
- const { loading, availableTerms, selectedTerms } = this.state;
251
- const termNames = availableTerms.map( ( term ) => term.name );
252
- const newTermLabel = get(
253
- taxonomy,
254
- [ 'labels', 'add_new_item' ],
255
- slug === 'post_tag' ? __( 'Add new tag' ) : __( 'Add new Term' )
256
- );
257
- const singularName = get(
258
- taxonomy,
259
- [ 'labels', 'singular_name' ],
260
- slug === 'post_tag' ? __( 'Tag' ) : __( 'Term' )
261
- );
262
- const termAddedLabel = sprintf(
263
- /* translators: %s: term name. */
264
- _x( '%s added', 'term' ),
265
- singularName
266
- );
267
- const termRemovedLabel = sprintf(
268
- /* translators: %s: term name. */
269
- _x( '%s removed', 'term' ),
270
- singularName
271
- );
272
- const removeTermLabel = sprintf(
273
- /* translators: %s: term name. */
274
- _x( 'Remove %s', 'term' ),
275
- singularName
276
- );
260
+ const newTermLabel = get(
261
+ taxonomy,
262
+ [ 'labels', 'add_new_item' ],
263
+ slug === 'post_tag' ? __( 'Add new tag' ) : __( 'Add new Term' )
264
+ );
265
+ const singularName = get(
266
+ taxonomy,
267
+ [ 'labels', 'singular_name' ],
268
+ slug === 'post_tag' ? __( 'Tag' ) : __( 'Term' )
269
+ );
270
+ const termAddedLabel = sprintf(
271
+ /* translators: %s: term name. */
272
+ _x( '%s added', 'term' ),
273
+ singularName
274
+ );
275
+ const termRemovedLabel = sprintf(
276
+ /* translators: %s: term name. */
277
+ _x( '%s removed', 'term' ),
278
+ singularName
279
+ );
280
+ const removeTermLabel = sprintf(
281
+ /* translators: %s: term name. */
282
+ _x( 'Remove %s', 'term' ),
283
+ singularName
284
+ );
277
285
 
278
- return (
279
- <>
280
- <FormTokenField
281
- value={ selectedTerms }
282
- suggestions={ termNames }
283
- onChange={ this.onChange }
284
- onInputChange={ this.searchTerms }
285
- maxSuggestions={ MAX_TERMS_SUGGESTIONS }
286
- disabled={ loading }
287
- label={ newTermLabel }
288
- messages={ {
289
- added: termAddedLabel,
290
- removed: termRemovedLabel,
291
- remove: removeTermLabel,
292
- } }
293
- />
294
- <MostUsedTerms
295
- taxonomy={ taxonomy }
296
- onSelect={ this.appendTerm }
297
- />
298
- </>
299
- );
300
- }
286
+ return (
287
+ <>
288
+ <FormTokenField
289
+ value={ values }
290
+ suggestions={ suggestions }
291
+ onChange={ onChange }
292
+ onInputChange={ debouncedSearch }
293
+ maxSuggestions={ MAX_TERMS_SUGGESTIONS }
294
+ label={ newTermLabel }
295
+ messages={ {
296
+ added: termAddedLabel,
297
+ removed: termRemovedLabel,
298
+ remove: removeTermLabel,
299
+ } }
300
+ />
301
+ <MostUsedTerms taxonomy={ taxonomy } onSelect={ appendTerm } />
302
+ </>
303
+ );
301
304
  }
302
305
 
303
- export default compose(
304
- withSelect( ( select, { slug } ) => {
305
- const { getCurrentPost } = select( editorStore );
306
- const { getTaxonomy } = select( coreStore );
307
- const taxonomy = getTaxonomy( slug );
308
- return {
309
- hasCreateAction: taxonomy
310
- ? get(
311
- getCurrentPost(),
312
- [ '_links', 'wp:action-create-' + taxonomy.rest_base ],
313
- false
314
- )
315
- : false,
316
- hasAssignAction: taxonomy
317
- ? get(
318
- getCurrentPost(),
319
- [ '_links', 'wp:action-assign-' + taxonomy.rest_base ],
320
- false
321
- )
322
- : false,
323
- terms: taxonomy
324
- ? select( editorStore ).getEditedPostAttribute(
325
- taxonomy.rest_base
326
- )
327
- : [],
328
- taxonomy,
329
- };
330
- } ),
331
- withDispatch( ( dispatch ) => {
332
- return {
333
- onUpdateTerms( terms, restBase ) {
334
- dispatch( editorStore ).editPost( { [ restBase ]: terms } );
335
- },
336
- };
337
- } ),
338
- withSpokenMessages,
339
- withFilters( 'editor.PostTaxonomyType' )
340
- )( FlatTermSelector );
306
+ export default withFilters( 'editor.PostTaxonomyType' )( FlatTermSelector );
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import TextareaAutosize from 'react-autosize-textarea';
5
4
  import classnames from 'classnames';
6
5
 
7
6
  /**
@@ -12,10 +11,10 @@ import { useEffect, useRef, useState } from '@wordpress/element';
12
11
  import { decodeEntities } from '@wordpress/html-entities';
13
12
  import { ENTER } from '@wordpress/keycodes';
14
13
  import { useSelect, useDispatch } from '@wordpress/data';
15
- import { VisuallyHidden } from '@wordpress/components';
16
- import { useInstanceId } from '@wordpress/compose';
17
14
  import { pasteHandler } from '@wordpress/blocks';
18
15
  import { store as blockEditorStore } from '@wordpress/block-editor';
16
+ import { __unstableUseRichText as useRichText } from '@wordpress/rich-text';
17
+ import { useMergeRefs } from '@wordpress/compose';
19
18
 
20
19
  /**
21
20
  * Internal dependencies
@@ -29,7 +28,6 @@ import { store as editorStore } from '../../store';
29
28
  const REGEXP_NEWLINES = /[\r\n]+/g;
30
29
 
31
30
  export default function PostTitle() {
32
- const instanceId = useInstanceId( PostTitle );
33
31
  const ref = useRef();
34
32
  const [ isSelected, setIsSelected ] = useState( false );
35
33
  const { editPost } = useDispatch( editorStore );
@@ -63,7 +61,7 @@ export default function PostTitle() {
63
61
  isFocusMode: focusMode,
64
62
  hasFixedToolbar: _hasFixedToolbar,
65
63
  };
66
- } );
64
+ }, [] );
67
65
 
68
66
  useEffect( () => {
69
67
  if ( ! ref.current ) {
@@ -94,6 +92,8 @@ export default function PostTitle() {
94
92
  editPost( { title: newTitle } );
95
93
  }
96
94
 
95
+ const [ selection, setSelection ] = useState( {} );
96
+
97
97
  function onSelect() {
98
98
  setIsSelected( true );
99
99
  clearSelectedBlock();
@@ -101,10 +101,11 @@ export default function PostTitle() {
101
101
 
102
102
  function onUnselect() {
103
103
  setIsSelected( false );
104
+ setSelection( {} );
104
105
  }
105
106
 
106
- function onChange( event ) {
107
- onUpdate( event.target.value.replace( REGEXP_NEWLINES, ' ' ) );
107
+ function onChange( value ) {
108
+ onUpdate( value.replace( REGEXP_NEWLINES, ' ' ) );
108
109
  }
109
110
 
110
111
  function onKeyDown( event ) {
@@ -167,38 +168,54 @@ export default function PostTitle() {
167
168
  // The wp-block className is important for editor styles.
168
169
  // This same block is used in both the visual and the code editor.
169
170
  const className = classnames(
170
- 'wp-block editor-post-title editor-post-title__block',
171
+ 'wp-block wp-block-post-title block-editor-block-list__block editor-post-title editor-post-title__input rich-text',
171
172
  {
172
173
  'is-selected': isSelected,
173
174
  'is-focus-mode': isFocusMode,
174
175
  'has-fixed-toolbar': hasFixedToolbar,
175
176
  }
176
177
  );
177
- const decodedPlaceholder = decodeEntities( placeholder );
178
+ const decodedPlaceholder =
179
+ decodeEntities( placeholder ) || __( 'Add title' );
180
+ const { ref: richTextRef } = useRichText( {
181
+ value: title,
182
+ onChange,
183
+ placeholder: decodedPlaceholder,
184
+ selectionStart: selection.start,
185
+ selectionEnd: selection.end,
186
+ onSelectionChange( newStart, newEnd ) {
187
+ setSelection( ( sel ) => {
188
+ const { start, end } = sel;
189
+ if ( start === newStart && end === newEnd ) {
190
+ return sel;
191
+ }
192
+ return {
193
+ start: newStart,
194
+ end: newEnd,
195
+ };
196
+ } );
197
+ },
198
+ __unstableDisableFormats: true,
199
+ preserveWhiteSpace: true,
200
+ } );
178
201
 
202
+ /* eslint-disable jsx-a11y/heading-has-content, jsx-a11y/no-noninteractive-element-to-interactive-role */
179
203
  return (
180
204
  <PostTypeSupportCheck supportKeys="title">
181
- <div className={ className }>
182
- <VisuallyHidden
183
- as="label"
184
- htmlFor={ `post-title-${ instanceId }` }
185
- >
186
- { decodedPlaceholder || __( 'Add title' ) }
187
- </VisuallyHidden>
188
- <TextareaAutosize
189
- ref={ ref }
190
- id={ `post-title-${ instanceId }` }
191
- className="editor-post-title__input"
192
- value={ title }
193
- onChange={ onChange }
194
- placeholder={ decodedPlaceholder || __( 'Add title' ) }
195
- onFocus={ onSelect }
196
- onBlur={ onUnselect }
197
- onKeyDown={ onKeyDown }
198
- onKeyPress={ onUnselect }
199
- onPaste={ onPaste }
200
- />
201
- </div>
205
+ <h1
206
+ ref={ useMergeRefs( [ richTextRef, ref ] ) }
207
+ contentEditable
208
+ className={ className }
209
+ aria-label={ decodedPlaceholder }
210
+ role="textbox"
211
+ aria-multiline="true"
212
+ onFocus={ onSelect }
213
+ onBlur={ onUnselect }
214
+ onKeyDown={ onKeyDown }
215
+ onKeyPress={ onUnselect }
216
+ onPaste={ onPaste }
217
+ />
202
218
  </PostTypeSupportCheck>
203
219
  );
220
+ /* eslint-enable jsx-a11y/heading-has-content, jsx-a11y/no-noninteractive-element-to-interactive-role */
204
221
  }