1
1
/**
2
2
* External dependencies
3
3
*/
4
- import { get , unescape as unescapeString , without , find , some } from 'lodash' ;
4
+ import { get , unescape as unescapeString , without , find , some , invoke } from 'lodash' ;
5
5
6
6
/**
7
7
* WordPress dependencies
@@ -11,6 +11,8 @@ import { Component } from '@wordpress/element';
11
11
import { TreeSelect , withSpokenMessages , withFilters , Button } from '@wordpress/components' ;
12
12
import { withSelect , withDispatch } from '@wordpress/data' ;
13
13
import { withInstanceId , compose } from '@wordpress/compose' ;
14
+ import apiFetch from '@wordpress/api-fetch' ;
15
+ import { addQueryArgs } from '@wordpress/url' ;
14
16
15
17
/**
16
18
* Internal dependencies
@@ -24,6 +26,7 @@ const DEFAULT_QUERY = {
24
26
per_page : - 1 ,
25
27
orderby : 'name' ,
26
28
order : 'asc' ,
29
+ _fields : 'id,name,parent' ,
27
30
} ;
28
31
29
32
const MIN_TERMS_COUNT_FOR_FILTER = 8 ;
@@ -38,7 +41,12 @@ class HierarchicalTermSelector extends Component {
38
41
this . onAddTerm = this . onAddTerm . bind ( this ) ;
39
42
this . onToggleForm = this . onToggleForm . bind ( this ) ;
40
43
this . setFilterValue = this . setFilterValue . bind ( this ) ;
44
+ this . sortBySelected = this . sortBySelected . bind ( this ) ;
41
45
this . state = {
46
+ loading : true ,
47
+ availableTermsTree : [ ] ,
48
+ availableTerms : [ ] ,
49
+ adding : false ,
42
50
formName : '' ,
43
51
formParent : '' ,
44
52
showForm : false ,
@@ -48,13 +56,13 @@ class HierarchicalTermSelector extends Component {
48
56
}
49
57
50
58
onChange ( event ) {
51
- const { onUpdateTerms, terms = [ ] } = this . props ;
59
+ const { onUpdateTerms, terms = [ ] , taxonomy } = this . props ;
52
60
const termId = parseInt ( event . target . value , 10 ) ;
53
61
const hasTerm = terms . indexOf ( termId ) !== - 1 ;
54
62
const newTerms = hasTerm ?
55
63
without ( terms , termId ) :
56
64
[ ...terms , termId ] ;
57
- onUpdateTerms ( newTerms ) ;
65
+ onUpdateTerms ( newTerms , taxonomy . rest_base ) ;
58
66
}
59
67
60
68
onChangeFormName ( event ) {
@@ -81,9 +89,9 @@ class HierarchicalTermSelector extends Component {
81
89
82
90
onAddTerm ( event ) {
83
91
event . preventDefault ( ) ;
84
- const { onUpdateTerms, addTermToEditedPost , terms, availableTerms } = this . props ;
85
- const { formName, formParent } = this . state ;
86
- if ( formName === '' ) {
92
+ const { onUpdateTerms, taxonomy , terms, slug } = this . props ;
93
+ const { formName, formParent, adding , availableTerms } = this . state ;
94
+ if ( formName === '' || adding ) {
87
95
return ;
88
96
}
89
97
@@ -92,7 +100,7 @@ class HierarchicalTermSelector extends Component {
92
100
if ( existingTerm ) {
93
101
// if the term we are adding exists but is not selected select it
94
102
if ( ! some ( terms , ( term ) => term === existingTerm . id ) ) {
95
- onUpdateTerms ( [ ...terms , existingTerm . id ] ) ;
103
+ onUpdateTerms ( [ ...terms , existingTerm . id ] , taxonomy . rest_base ) ;
96
104
}
97
105
this . setState ( {
98
106
formName : '' ,
@@ -101,14 +109,113 @@ class HierarchicalTermSelector extends Component {
101
109
return ;
102
110
}
103
111
104
- addTermToEditedPost ( {
105
- name : formName ,
106
- parent : formParent ? formParent : undefined ,
107
- } ) ;
108
112
this . setState ( {
109
- formName : '' ,
110
- formParent : '' ,
113
+ adding : true ,
114
+ } ) ;
115
+ this . addRequest = apiFetch ( {
116
+ path : `/wp/v2/${ taxonomy . rest_base } ` ,
117
+ method : 'POST' ,
118
+ data : {
119
+ name : formName ,
120
+ parent : formParent ? formParent : undefined ,
121
+ } ,
122
+ } ) ;
123
+ // Tries to create a term or fetch it if it already exists
124
+ const findOrCreatePromise = this . addRequest
125
+ . catch ( ( error ) => {
126
+ const errorCode = error . code ;
127
+ if ( errorCode === 'term_exists' ) {
128
+ // search the new category created since last fetch
129
+ this . addRequest = apiFetch ( {
130
+ path : addQueryArgs (
131
+ `/wp/v2/${ taxonomy . rest_base } ` ,
132
+ { ...DEFAULT_QUERY , parent : formParent || 0 , search : formName }
133
+ ) ,
134
+ } ) ;
135
+ return this . addRequest
136
+ . then ( ( searchResult ) => {
137
+ return this . findTerm ( searchResult , formParent , formName ) ;
138
+ } ) ;
139
+ }
140
+ return Promise . reject ( error ) ;
141
+ } ) ;
142
+ findOrCreatePromise
143
+ . then ( ( term ) => {
144
+ const hasTerm = ! ! find ( this . state . availableTerms , ( availableTerm ) => availableTerm . id === term . id ) ;
145
+ const newAvailableTerms = hasTerm ? this . state . availableTerms : [ term , ...this . state . availableTerms ] ;
146
+ const termAddedMessage = sprintf (
147
+ _x ( '%s added' , 'term' ) ,
148
+ get (
149
+ this . props . taxonomy ,
150
+ [ 'labels' , 'singular_name' ] ,
151
+ slug === 'category' ? __ ( 'Category' ) : __ ( 'Term' )
152
+ )
153
+ ) ;
154
+ this . props . speak ( termAddedMessage , 'assertive' ) ;
155
+ this . addRequest = null ;
156
+ this . setState ( {
157
+ adding : false ,
158
+ formName : '' ,
159
+ formParent : '' ,
160
+ availableTerms : newAvailableTerms ,
161
+ availableTermsTree : this . sortBySelected ( buildTermsTree ( newAvailableTerms ) ) ,
162
+ } ) ;
163
+ onUpdateTerms ( [ ...terms , term . id ] , taxonomy . rest_base ) ;
164
+ } , ( xhr ) => {
165
+ if ( xhr . statusText === 'abort' ) {
166
+ return ;
167
+ }
168
+ this . addRequest = null ;
169
+ this . setState ( {
170
+ adding : false ,
171
+ } ) ;
172
+ } ) ;
173
+ }
174
+
175
+ componentDidMount ( ) {
176
+ this . fetchTerms ( ) ;
177
+ }
178
+
179
+ componentWillUnmount ( ) {
180
+ invoke ( this . fetchRequest , [ 'abort' ] ) ;
181
+ invoke ( this . addRequest , [ 'abort' ] ) ;
182
+ }
183
+
184
+ componentDidUpdate ( prevProps ) {
185
+ if ( this . props . taxonomy !== prevProps . taxonomy ) {
186
+ this . fetchTerms ( ) ;
187
+ }
188
+ }
189
+
190
+ fetchTerms ( ) {
191
+ const { taxonomy } = this . props ;
192
+ if ( ! taxonomy ) {
193
+ return ;
194
+ }
195
+ this . fetchRequest = apiFetch ( {
196
+ path : addQueryArgs ( `/wp/v2/${ taxonomy . rest_base } ` , DEFAULT_QUERY ) ,
111
197
} ) ;
198
+ this . fetchRequest . then (
199
+ ( terms ) => { // resolve
200
+ const availableTermsTree = this . sortBySelected ( buildTermsTree ( terms ) ) ;
201
+
202
+ this . fetchRequest = null ;
203
+ this . setState ( {
204
+ loading : false ,
205
+ availableTermsTree,
206
+ availableTerms : terms ,
207
+ } ) ;
208
+ } ,
209
+ ( xhr ) => { // reject
210
+ if ( xhr . statusText === 'abort' ) {
211
+ return ;
212
+ }
213
+ this . fetchRequest = null ;
214
+ this . setState ( {
215
+ loading : false ,
216
+ } ) ;
217
+ }
218
+ ) ;
112
219
}
113
220
114
221
sortBySelected ( termsTree ) {
@@ -149,7 +256,7 @@ class HierarchicalTermSelector extends Component {
149
256
}
150
257
151
258
setFilterValue ( event ) {
152
- const { availableTermsTree } = this . props ;
259
+ const { availableTermsTree } = this . state ;
153
260
const filterValue = event . target . value ;
154
261
const filteredTermsTree = availableTermsTree . map ( this . getFilterMatcher ( filterValue ) ) . filter ( ( term ) => term ) ;
155
262
const getResultCount = ( terms ) => {
@@ -162,10 +269,12 @@ class HierarchicalTermSelector extends Component {
162
269
}
163
270
return count ;
164
271
} ;
165
- this . setState ( {
166
- filterValue,
167
- filteredTermsTree,
168
- } ) ;
272
+ this . setState (
273
+ {
274
+ filterValue,
275
+ filteredTermsTree,
276
+ }
277
+ ) ;
169
278
170
279
const resultCount = getResultCount ( filteredTermsTree ) ;
171
280
const resultsFoundMessage = sprintf (
@@ -230,21 +339,13 @@ class HierarchicalTermSelector extends Component {
230
339
}
231
340
232
341
render ( ) {
233
- const {
234
- slug,
235
- taxonomy,
236
- instanceId,
237
- hasCreateAction,
238
- hasAssignAction,
239
- availableTermsTree,
240
- availableTerms,
241
- } = this . props ;
342
+ const { slug, taxonomy, instanceId, hasCreateAction, hasAssignAction } = this . props ;
242
343
243
344
if ( ! hasAssignAction ) {
244
345
return null ;
245
346
}
246
347
247
- const { filteredTermsTree, formName, formParent, isRequestingTerms , showForm, filterValue } = this . state ;
348
+ const { availableTermsTree , availableTerms , filteredTermsTree, formName, formParent, loading , showForm, filterValue } = this . state ;
248
349
const labelWithFallback = ( labelProperty , fallbackIsCategory , fallbackIsNotCategory ) => get (
249
350
taxonomy ,
250
351
[ 'labels' , labelProperty ] ,
@@ -285,7 +386,7 @@ class HierarchicalTermSelector extends Component {
285
386
slug === 'category' ? __ ( 'Categories' ) : __ ( 'Terms' )
286
387
)
287
388
) ;
288
- const showFilter = availableTerms && ( availableTerms . length >= MIN_TERMS_COUNT_FOR_FILTER ) ;
389
+ const showFilter = availableTerms . length >= MIN_TERMS_COUNT_FOR_FILTER ;
289
390
290
391
return [
291
392
showFilter && < label
@@ -308,9 +409,9 @@ class HierarchicalTermSelector extends Component {
308
409
role = "group"
309
410
aria-label = { groupLabel }
310
411
>
311
- { this . renderTerms ( '' !== filterValue ? filteredTermsTree : this . sortBySelected ( availableTermsTree ) ) }
412
+ { this . renderTerms ( '' !== filterValue ? filteredTermsTree : availableTermsTree ) }
312
413
</ div > ,
313
- ! isRequestingTerms && hasCreateAction && (
414
+ ! loading && hasCreateAction && (
314
415
< Button
315
416
key = "term-add-button"
316
417
onClick = { this . onToggleForm }
@@ -363,27 +464,18 @@ class HierarchicalTermSelector extends Component {
363
464
export default compose ( [
364
465
withSelect ( ( select , { slug } ) => {
365
466
const { getCurrentPost } = select ( 'core/editor' ) ;
366
- const { getTaxonomy, getEntityRecords } = select ( 'core' ) ;
367
- const { isResolving } = select ( 'core/data' ) ;
467
+ const { getTaxonomy } = select ( 'core' ) ;
368
468
const taxonomy = getTaxonomy ( slug ) ;
369
- const availableTerms = getEntityRecords ( 'taxonomy' , slug , DEFAULT_QUERY ) ;
370
- const availableTermsTree = buildTermsTree ( availableTerms ) ;
371
469
return {
372
470
hasCreateAction : taxonomy ? get ( getCurrentPost ( ) , [ '_links' , 'wp:action-create-' + taxonomy . rest_base ] , false ) : false ,
373
471
hasAssignAction : taxonomy ? get ( getCurrentPost ( ) , [ '_links' , 'wp:action-assign-' + taxonomy . rest_base ] , false ) : false ,
374
472
terms : taxonomy ? select ( 'core/editor' ) . getEditedPostAttribute ( taxonomy . rest_base ) : [ ] ,
375
- isRequestingTerms : isResolving ( 'core' , 'getEntityRecords' , [ 'taxonomy' , slug , DEFAULT_QUERY ] ) ,
376
473
taxonomy,
377
- availableTerms,
378
- availableTermsTree,
379
474
} ;
380
475
} ) ,
381
- withDispatch ( ( dispatch , { slug, taxonomy } ) => ( {
382
- onUpdateTerms ( terms ) {
383
- dispatch ( 'core/editor' ) . editPost ( { [ taxonomy . rest_base ] : terms } ) ;
384
- } ,
385
- addTermToEditedPost ( term ) {
386
- return dispatch ( 'core/editor' ) . addTermToEditedPost ( slug , term ) ;
476
+ withDispatch ( ( dispatch ) => ( {
477
+ onUpdateTerms ( terms , restBase ) {
478
+ dispatch ( 'core/editor' ) . editPost ( { [ restBase ] : terms } ) ;
387
479
} ,
388
480
} ) ) ,
389
481
withSpokenMessages ,
0 commit comments