| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 /** 2 * @output wp-admin/js/dashboard.js 3 */ 4 5 /* global pagenow, ajaxurl, postboxes, wpActiveEditor:true, ajaxWidgets */ 6 /* global ajaxPopulateWidgets, quickPressLoad */ 7 window.wp = window.wp || {}; 8 window.communityEventsData = window.communityEventsData || {}; 9 10 /** 11 * Initializes the dashboard widget functionality. 12 * 13 * @since 2.7.0 14 */ 15 jQuery( function($) { 16 var welcomePanel = $( '#welcome-panel' ), 17 welcomePanelHide = $('#wp_welcome_panel-hide'), 18 updateWelcomePanel; 19 20 /** 21 * Saves the visibility of the welcome panel. 22 * 23 * @since 3.3.0 24 * 25 * @param {boolean} visible Should it be visible or not. 26 * 27 * @return {void} 28 */ 29 updateWelcomePanel = function( visible ) { 30 $.post( 31 ajaxurl, 32 { 33 action: 'update-welcome-panel', 34 visible: visible, 35 welcomepanelnonce: $( '#welcomepanelnonce' ).val() 36 }, 37 function() { 38 wp.a11y.speak( wp.i18n.__( 'Screen Options updated.' ) ); 39 } 40 ); 41 }; 42 43 // Unhide the welcome panel if the Welcome Option checkbox is checked. 44 if ( welcomePanel.hasClass('hidden') && welcomePanelHide.prop('checked') ) { 45 welcomePanel.removeClass('hidden'); 46 } 47 48 // Hide the welcome panel when the dismiss button or close button is clicked. 49 $('.welcome-panel-close, .welcome-panel-dismiss a', welcomePanel).on( 'click', function(e) { 50 e.preventDefault(); 51 welcomePanel.addClass('hidden'); 52 updateWelcomePanel( 0 ); 53 $('#wp_welcome_panel-hide').prop('checked', false); 54 }); 55 56 // Set welcome panel visibility based on Welcome Option checkbox value. 57 welcomePanelHide.on( 'click', function() { 58 welcomePanel.toggleClass('hidden', ! this.checked ); 59 updateWelcomePanel( this.checked ? 1 : 0 ); 60 }); 61 62 /** 63 * These widgets can be populated via ajax. 64 * 65 * @since 2.7.0 66 * 67 * @type {string[]} 68 * 69 * @global 70 */ 71 window.ajaxWidgets = ['dashboard_primary']; 72 73 /** 74 * Triggers widget updates via Ajax. 75 * 76 * @since 2.7.0 77 * 78 * @global 79 * 80 * @param {string} el Optional. Widget to fetch or none to update all. 81 * 82 * @return {void} 83 */ 84 window.ajaxPopulateWidgets = function(el) { 85 /** 86 * Fetch the latest representation of the widget via Ajax and show it. 87 * 88 * @param {number} i Number of half-seconds to use as the timeout. 89 * @param {string} id ID of the element which is going to be checked for changes. 90 * 91 * @return {void} 92 */ 93 function show(i, id) { 94 var p, e = $('#' + id + ' div.inside:visible').find('.widget-loading'); 95 // If the element is found in the dom, queue to load latest representation. 96 if ( e.length ) { 97 p = e.parent(); 98 setTimeout( function(){ 99 // Request the widget content. 100 p.load( ajaxurl + '?action=dashboard-widgets&widget=' + id + '&pagenow=' + pagenow, '', function() { 101 // Hide the parent and slide it out for visual fanciness. 102 p.hide().slideDown('normal', function(){ 103 $(this).css('display', ''); 104 }); 105 }); 106 }, i * 500 ); 107 } 108 } 109 110 // If we have received a specific element to fetch, check if it is valid. 111 if ( el ) { 112 el = el.toString(); 113 // If the element is available as Ajax widget, show it. 114 if ( $.inArray(el, ajaxWidgets) !== -1 ) { 115 // Show element without any delay. 116 show(0, el); 117 } 118 } else { 119 // Walk through all ajaxWidgets, loading them after each other. 120 $.each( ajaxWidgets, show ); 121 } 122 }; 123 124 // Initially populate ajax widgets. 125 ajaxPopulateWidgets(); 126 127 // Register ajax widgets as postbox toggles. 128 postboxes.add_postbox_toggles(pagenow, { pbshow: ajaxPopulateWidgets } ); 129 130 /** 131 * Control the Quick Press (Quick Draft) widget. 132 * 133 * @since 2.7.0 134 * 135 * @global 136 * 137 * @return {void} 138 */ 139 window.quickPressLoad = function() { 140 var act = $( '#quickpost-action' ), t; 141 142 // Enable the submit button. 143 $( '#quick-press .submit input[type="submit"]' ).prop( 'disabled', false ); 144 145 t = $( '#quick-press' ).on( 'submit', function( e ) { 146 e.preventDefault(); 147 148 // Disable the submit button to prevent duplicate submissions. 149 $( '#quick-press .submit input[type="submit"]' ).prop( 'disabled', true ); 150 151 // Post the entered data to save it. 152 $.post( t.attr( 'action' ), t.serializeArray(), function( data ) { 153 // Replace the form, and prepend the published post. 154 $( '#dashboard_quick_press .inside' ).html( data ); 155 quickPressLoad(); 156 highlightLatestPost(); 157 158 // Focus the title to allow for quickly drafting another post. 159 $( '#title' ).trigger( 'focus'); 160 } ); 161 162 /** 163 * Highlights the latest post for one second. 164 * 165 * @return {void} 166 */ 167 function highlightLatestPost () { 168 var latestPost = $( '.drafts ul li' ) .first(), 169 errorNotice = $( '#quick-press .notice-error' ); 170 171 if ( errorNotice.length ) { 172 return; 173 } 174 175 latestPost.css( 'background', '#fffbe5' ); 176 setTimeout( function () { 177 latestPost.css( 'background', 'none' ); 178 }, 1000 ); 179 } 180 } ); 181 182 // Change the QuickPost action to the publish value. 183 $( '#publish' ).on( 'click', function() { act.val( 'post-quickpress-publish' ); } ); 184 185 $( '#quick-press' ).on( 'click focusin', function() { 186 wpActiveEditor = 'content'; 187 } ); 188 189 autoResizeTextarea(); 190 }; 191 window.quickPressLoad(); 192 193 // Enable the dragging functionality of the widgets. 194 $( '.meta-box-sortables' ).sortable( 'option', 'containment', '#wpwrap' ); 195 196 /** 197 * Adjust the height of the textarea based on the content. 198 * 199 * @since 3.6.0 200 * 201 * @return {void} 202 */ 203 function autoResizeTextarea() { 204 // When IE8 or older is used to render this document, exit. 205 if ( document.documentMode && document.documentMode < 9 ) { 206 return; 207 } 208 209 // Add a hidden div. We'll copy over the text from the textarea to measure its height. 210 $('body').append( '<div class="quick-draft-textarea-clone" style="display: none;"></div>' ); 211 212 var clone = $('.quick-draft-textarea-clone'), 213 editor = $('#content'), 214 editorHeight = editor.height(), 215 /* 216 * 100px roughly accounts for browser chrome and allows the 217 * save draft button to show on-screen at the same time. 218 */ 219 editorMaxHeight = $(window).height() - 100; 220 221 /* 222 * Match up textarea and clone div as much as possible. 223 * Padding cannot be reliably retrieved using shorthand in all browsers. 224 */ 225 clone.css({ 226 'font-family': editor.css('font-family'), 227 'font-size': editor.css('font-size'), 228 'line-height': editor.css('line-height'), 229 'padding-bottom': editor.css('paddingBottom'), 230 'padding-left': editor.css('paddingLeft'), 231 'padding-right': editor.css('paddingRight'), 232 'padding-top': editor.css('paddingTop'), 233 'white-space': 'pre-wrap', 234 'word-wrap': 'break-word', 235 'display': 'none' 236 }); 237 238 // The 'propertychange' is used in IE < 9. 239 editor.on('focus input propertychange', function() { 240 var $this = $(this), 241 // Add a non-breaking space to ensure that the height of a trailing newline is 242 // included. 243 textareaContent = $this.val() + ' ', 244 // Add 2px to compensate for border-top & border-bottom. 245 cloneHeight = clone.css('width', $this.css('width')).text(textareaContent).outerHeight() + 2; 246 247 // Default to show a vertical scrollbar, if needed. 248 editor.css('overflow-y', 'auto'); 249 250 // Only change the height if it has changed and both heights are below the max. 251 if ( cloneHeight === editorHeight || ( cloneHeight >= editorMaxHeight && editorHeight >= editorMaxHeight ) ) { 252 return; 253 } 254 255 /* 256 * Don't allow editor to exceed the height of the window. 257 * This is also bound in CSS to a max-height of 1300px to be extra safe. 258 */ 259 if ( cloneHeight > editorMaxHeight ) { 260 editorHeight = editorMaxHeight; 261 } else { 262 editorHeight = cloneHeight; 263 } 264 265 // Disable scrollbars because we adjust the height to the content. 266 editor.css('overflow', 'hidden'); 267 268 $this.css('height', editorHeight + 'px'); 269 }); 270 } 271 272 } ); 273 274 jQuery( function( $ ) { 275 'use strict'; 276 277 var communityEventsData = window.communityEventsData, 278 dateI18n = wp.date.dateI18n, 279 format = wp.date.format, 280 sprintf = wp.i18n.sprintf, 281 __ = wp.i18n.__, 282 _x = wp.i18n._x, 283 app; 284 285 /** 286 * Global Community Events namespace. 287 * 288 * @since 4.8.0 289 * 290 * @memberOf wp 291 * @namespace wp.communityEvents 292 */ 293 app = window.wp.communityEvents = /** @lends wp.communityEvents */{ 294 initialized: false, 295 model: null, 296 297 /** 298 * Initializes the wp.communityEvents object. 299 * 300 * @since 4.8.0 301 * 302 * @return {void} 303 */ 304 init: function() { 305 if ( app.initialized ) { 306 return; 307 } 308 309 var $container = $( '#community-events' ); 310 311 /* 312 * When JavaScript is disabled, the errors container is shown, so 313 * that "This widget requires JavaScript" message can be seen. 314 * 315 * When JS is enabled, the container is hidden at first, and then 316 * revealed during the template rendering, if there actually are 317 * errors to show. 318 * 319 * The display indicator switches from `hide-if-js` to `aria-hidden` 320 * here in order to maintain consistency with all the other fields 321 * that key off of `aria-hidden` to determine their visibility. 322 * `aria-hidden` can't be used initially, because there would be no 323 * way to set it to false when JavaScript is disabled, which would 324 * prevent people from seeing the "This widget requires JavaScript" 325 * message. 326 */ 327 $( '.community-events-errors' ) 328 .attr( 'aria-hidden', 'true' ) 329 .removeClass( 'hide-if-js' ); 330 331 $container.on( 'click', '.community-events-toggle-location, .community-events-cancel', app.toggleLocationForm ); 332 333 /** 334 * Filters events based on entered location. 335 * 336 * @return {void} 337 */ 338 $container.on( 'submit', '.community-events-form', function( event ) { 339 var location = $( '#community-events-location' ).val().trim(); 340 341 event.preventDefault(); 342 343 /* 344 * Don't trigger a search if the search field is empty or the 345 * search term was made of only spaces before being trimmed. 346 */ 347 if ( ! location ) { 348 return; 349 } 350 351 app.getEvents({ 352 location: location 353 }); 354 }); 355 356 if ( communityEventsData && communityEventsData.cache && communityEventsData.cache.location && communityEventsData.cache.events ) { 357 app.renderEventsTemplate( communityEventsData.cache, 'app' ); 358 } else { 359 app.getEvents(); 360 } 361 362 app.initialized = true; 363 }, 364 365 /** 366 * Toggles the visibility of the Edit Location form. 367 * 368 * @since 4.8.0 369 * 370 * @param {event|string} action 'show' or 'hide' to specify a state; 371 * or an event object to flip between states. 372 * 373 * @return {void} 374 */ 375 toggleLocationForm: function( action ) { 376 var $toggleButton = $( '.community-events-toggle-location' ), 377 $cancelButton = $( '.community-events-cancel' ), 378 $form = $( '.community-events-form' ), 379 $target = $(); 380 381 if ( 'object' === typeof action ) { 382 // The action is the event object: get the clicked element. 383 $target = $( action.target ); 384 /* 385 * Strict comparison doesn't work in this case because sometimes 386 * we explicitly pass a string as value of aria-expanded and 387 * sometimes a boolean as the result of an evaluation. 388 */ 389 action = 'true' == $toggleButton.attr( 'aria-expanded' ) ? 'hide' : 'show'; 390 } 391 392 if ( 'hide' === action ) { 393 $toggleButton.attr( 'aria-expanded', 'false' ); 394 $cancelButton.attr( 'aria-expanded', 'false' ); 395 $form.attr( 'aria-hidden', 'true' ); 396 /* 397 * If the Cancel button has been clicked, bring the focus back 398 * to the toggle button so users relying on screen readers don't 399 * lose their place. 400 */ 401 if ( $target.hasClass( 'community-events-cancel' ) ) { 402 $toggleButton.trigger( 'focus' ); 403 } 404 } else { 405 $toggleButton.attr( 'aria-expanded', 'true' ); 406 $cancelButton.attr( 'aria-expanded', 'true' ); 407 $form.attr( 'aria-hidden', 'false' ); 408 } 409 }, 410 411 /** 412 * Sends REST API requests to fetch events for the widget. 413 * 414 * @since 4.8.0 415 * 416 * @param {Object} requestParams REST API Request parameters object. 417 * 418 * @return {void} 419 */ 420 getEvents: function( requestParams ) { 421 var initiatedBy, 422 app = this, 423 $spinner = $( '.community-events-form' ).children( '.spinner' ); 424 425 requestParams = requestParams || {}; 426 requestParams._wpnonce = communityEventsData.nonce; 427 requestParams.timezone = window.Intl ? window.Intl.DateTimeFormat().resolvedOptions().timeZone : ''; 428 429 initiatedBy = requestParams.location ? 'user' : 'app'; 430 431 $spinner.addClass( 'is-active' ); 432 433 wp.ajax.post( 'get-community-events', requestParams ) 434 .always( function() { 435 $spinner.removeClass( 'is-active' ); 436 }) 437 438 .done( function( response ) { 439 if ( 'no_location_available' === response.error ) { 440 if ( requestParams.location ) { 441 response.unknownCity = requestParams.location; 442 } else { 443 /* 444 * No location was passed, which means that this was an automatic query 445 * based on IP, locale, and timezone. Since the user didn't initiate it, 446 * it should fail silently. Otherwise, the error could confuse and/or 447 * annoy them. 448 */ 449 delete response.error; 450 } 451 } 452 app.renderEventsTemplate( response, initiatedBy ); 453 }) 454 455 .fail( function() { 456 app.renderEventsTemplate({ 457 'location' : false, 458 'events' : [], 459 'error' : true 460 }, initiatedBy ); 461 }); 462 }, 463 464 /** 465 * Renders the template for the Events section of the Events & News widget. 466 * 467 * @since 4.8.0 468 * 469 * @param {Object} templateParams The various parameters that will get passed to wp.template. 470 * @param {string} initiatedBy 'user' to indicate that this was triggered manually by the user; 471 * 'app' to indicate it was triggered automatically by the app itself. 472 * 473 * @return {void} 474 */ 475 renderEventsTemplate: function( templateParams, initiatedBy ) { 476 var template, 477 elementVisibility, 478 $toggleButton = $( '.community-events-toggle-location' ), 479 $locationMessage = $( '#community-events-location-message' ), 480 $results = $( '.community-events-results' ); 481 482 templateParams.events = app.populateDynamicEventFields( 483 templateParams.events, 484 communityEventsData.time_format 485 ); 486 487 /* 488 * Hide all toggleable elements by default, to keep the logic simple. 489 * Otherwise, each block below would have to turn hide everything that 490 * could have been shown at an earlier point. 491 * 492 * The exception to that is that the .community-events container is hidden 493 * when the page is first loaded, because the content isn't ready yet, 494 * but once we've reached this point, it should always be shown. 495 */ 496 elementVisibility = { 497 '.community-events' : true, 498 '.community-events-loading' : false, 499 '.community-events-errors' : false, 500 '.community-events-error-occurred' : false, 501 '.community-events-could-not-locate' : false, 502 '#community-events-location-message' : false, 503 '.community-events-toggle-location' : false, 504 '.community-events-results' : false 505 }; 506 507 /* 508 * Determine which templates should be rendered and which elements 509 * should be displayed. 510 */ 511 if ( templateParams.location.ip ) { 512 /* 513 * If the API determined the location by geolocating an IP, it will 514 * provide events, but not a specific location. 515 */ 516 $locationMessage.text( __( 'Attend an upcoming event near you.' ) ); 517 518 if ( templateParams.events.length ) { 519 template = wp.template( 'community-events-event-list' ); 520 $results.html( template( templateParams ) ); 521 } else { 522 template = wp.template( 'community-events-no-upcoming-events' ); 523 $results.html( template( templateParams ) ); 524 } 525 526 elementVisibility['#community-events-location-message'] = true; 527 elementVisibility['.community-events-toggle-location'] = true; 528 elementVisibility['.community-events-results'] = true; 529 530 } else if ( templateParams.location.description ) { 531 template = wp.template( 'community-events-attend-event-near' ); 532 $locationMessage.html( template( templateParams ) ); 533 534 if ( templateParams.events.length ) { 535 template = wp.template( 'community-events-event-list' ); 536 $results.html( template( templateParams ) ); 537 } else { 538 template = wp.template( 'community-events-no-upcoming-events' ); 539 $results.html( template( templateParams ) ); 540 } 541 542 if ( 'user' === initiatedBy ) { 543 wp.a11y.speak( 544 sprintf( 545 /* translators: %s: The name of a city. */ 546 __( 'City updated. Listing events near %s.' ), 547 templateParams.location.description 548 ), 549 'assertive' 550 ); 551 } 552 553 elementVisibility['#community-events-location-message'] = true; 554 elementVisibility['.community-events-toggle-location'] = true; 555 elementVisibility['.community-events-results'] = true; 556 557 } else if ( templateParams.unknownCity ) { 558 template = wp.template( 'community-events-could-not-locate' ); 559 $( '.community-events-could-not-locate' ).html( template( templateParams ) ); 560 wp.a11y.speak( 561 sprintf( 562 /* 563 * These specific examples were chosen to highlight the fact that a 564 * state is not needed, even for cities whose name is not unique. 565 * It would be too cumbersome to include that in the instructions 566 * to the user, so it's left as an implication. 567 */ 568 /* 569 * translators: %s is the name of the city we couldn't locate. 570 * Replace the examples with cities related to your locale. Test that 571 * they match the expected location and have upcoming events before 572 * including them. If no cities related to your locale have events, 573 * then use cities related to your locale that would be recognizable 574 * to most users. Use only the city name itself, without any region 575 * or country. Use the endonym (native locale name) instead of the 576 * English name if possible. 577 */ 578 __( 'We couldn’t locate %s. Please try another nearby city. For example: Kansas City; Springfield; Portland.' ), 579 templateParams.unknownCity 580 ) 581 ); 582 583 elementVisibility['.community-events-errors'] = true; 584 elementVisibility['.community-events-could-not-locate'] = true; 585 586 } else if ( templateParams.error && 'user' === initiatedBy ) { 587 /* 588 * Errors messages are only shown for requests that were initiated 589 * by the user, not for ones that were initiated by the app itself. 590 * Showing error messages for an event that user isn't aware of 591 * could be confusing or unnecessarily distracting. 592 */ 593 wp.a11y.speak( __( 'An error occurred. Please try again.' ) ); 594 595 elementVisibility['.community-events-errors'] = true; 596 elementVisibility['.community-events-error-occurred'] = true; 597 } else { 598 $locationMessage.text( __( 'Enter your closest city to find nearby events.' ) ); 599 600 elementVisibility['#community-events-location-message'] = true; 601 elementVisibility['.community-events-toggle-location'] = true; 602 } 603 604 // Set the visibility of toggleable elements. 605 _.each( elementVisibility, function( isVisible, element ) { 606 $( element ).attr( 'aria-hidden', ! isVisible ); 607 }); 608 609 $toggleButton.attr( 'aria-expanded', elementVisibility['.community-events-toggle-location'] ); 610 611 if ( templateParams.location && ( templateParams.location.ip || templateParams.location.latitude ) ) { 612 // Hide the form when there's a valid location. 613 app.toggleLocationForm( 'hide' ); 614 615 if ( 'user' === initiatedBy ) { 616 /* 617 * When the form is programmatically hidden after a user search, 618 * bring the focus back to the toggle button so users relying 619 * on screen readers don't lose their place. 620 */ 621 $toggleButton.trigger( 'focus' ); 622 } 623 } else { 624 app.toggleLocationForm( 'show' ); 625 } 626 }, 627 628 /** 629 * Populate event fields that have to be calculated on the fly. 630 * 631 * These can't be stored in the database, because they're dependent on 632 * the user's current time zone, locale, etc. 633 * 634 * @since 5.5.2 635 * 636 * @param {Array} rawEvents The events that should have dynamic fields added to them. 637 * @param {string} timeFormat A time format acceptable by `wp.date.dateI18n()`. 638 * 639 * @returns {Array} 640 */ 641 populateDynamicEventFields: function( rawEvents, timeFormat ) { 642 // Clone the parameter to avoid mutating it, so that this can remain a pure function. 643 var populatedEvents = JSON.parse( JSON.stringify( rawEvents ) ); 644 645 $.each( populatedEvents, function( index, event ) { 646 var timeZone = app.getTimeZone( event.start_unix_timestamp * 1000 ); 647 648 event.user_formatted_date = app.getFormattedDate( 649 event.start_unix_timestamp * 1000, 650 event.end_unix_timestamp * 1000, 651 timeZone 652 ); 653 654 event.user_formatted_time = dateI18n( 655 timeFormat, 656 event.start_unix_timestamp * 1000, 657 timeZone 658 ); 659 660 event.timeZoneAbbreviation = app.getTimeZoneAbbreviation( event.start_unix_timestamp * 1000 ); 661 } ); 662 663 return populatedEvents; 664 }, 665 666 /** 667 * Returns the user's local/browser time zone, in a form suitable for `wp.date.i18n()`. 668 * 669 * @since 5.5.2 670 * 671 * @param startTimestamp 672 * 673 * @returns {string|number} 674 */ 675 getTimeZone: function( startTimestamp ) { 676 /* 677 * Prefer a name like `Europe/Helsinki`, since that automatically tracks daylight savings. This 678 * doesn't need to take `startTimestamp` into account for that reason. 679 */ 680 var timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; 681 682 /* 683 * Fall back to an offset for IE11, which declares the property but doesn't assign a value. 684 */ 685 if ( 'undefined' === typeof timeZone ) { 686 /* 687 * It's important to use the _event_ time, not the _current_ 688 * time, so that daylight savings time is accounted for. 689 */ 690 timeZone = app.getFlippedTimeZoneOffset( startTimestamp ); 691 } 692 693 return timeZone; 694 }, 695 696 /** 697 * Get intuitive time zone offset. 698 * 699 * `Data.prototype.getTimezoneOffset()` returns a positive value for time zones 700 * that are _behind_ UTC, and a _negative_ value for ones that are ahead. 701 * 702 * See https://stackoverflow.com/questions/21102435/why-does-javascript-date-gettimezoneoffset-consider-0500-as-a-positive-off. 703 * 704 * @since 5.5.2 705 * 706 * @param {number} startTimestamp 707 * 708 * @returns {number} 709 */ 710 getFlippedTimeZoneOffset: function( startTimestamp ) { 711 return new Date( startTimestamp ).getTimezoneOffset() * -1; 712 }, 713 714 /** 715 * Get a short time zone name, like `PST`. 716 * 717 * @since 5.5.2 718 * 719 * @param {number} startTimestamp 720 * 721 * @returns {string} 722 */ 723 getTimeZoneAbbreviation: function( startTimestamp ) { 724 var timeZoneAbbreviation, 725 eventDateTime = new Date( startTimestamp ); 726 727 /* 728 * Leaving the `locales` argument undefined is important, so that the browser 729 * displays the abbreviation that's most appropriate for the current locale. For 730 * some that will be `UTC{+|-}{n}`, and for others it will be a code like `PST`. 731 * 732 * This doesn't need to take `startTimestamp` into account, because a name like 733 * `America/Chicago` automatically tracks daylight savings. 734 */ 735 var shortTimeStringParts = eventDateTime.toLocaleTimeString( undefined, { timeZoneName : 'short' } ).split( ' ' ); 736 737 if ( 3 === shortTimeStringParts.length ) { 738 timeZoneAbbreviation = shortTimeStringParts[2]; 739 } 740 741 if ( 'undefined' === typeof timeZoneAbbreviation ) { 742 /* 743 * It's important to use the _event_ time, not the _current_ 744 * time, so that daylight savings time is accounted for. 745 */ 746 var timeZoneOffset = app.getFlippedTimeZoneOffset( startTimestamp ), 747 sign = -1 === Math.sign( timeZoneOffset ) ? '' : '+'; 748 749 // translators: Used as part of a string like `GMT+5` in the Events Widget. 750 timeZoneAbbreviation = _x( 'GMT', 'Events widget offset prefix' ) + sign + ( timeZoneOffset / 60 ); 751 } 752 753 return timeZoneAbbreviation; 754 }, 755 756 /** 757 * Format a start/end date in the user's local time zone and locale. 758 * 759 * @since 5.5.2 760 * 761 * @param {int} startDate The Unix timestamp in milliseconds when the event starts. 762 * @param {int} endDate The Unix timestamp in milliseconds when the event ends. 763 * @param {string} timeZone A time zone string or offset which is parsable by `wp.date.i18n()`. 764 * 765 * @returns {string} 766 */ 767 getFormattedDate: function( startDate, endDate, timeZone ) { 768 var formattedDate; 769 770 /* 771 * The `date_format` option is not used because it's important 772 * in this context to keep the day of the week in the displayed date, 773 * so that users can tell at a glance if the event is on a day they 774 * are available, without having to open the link. 775 * 776 * The case of crossing a year boundary is intentionally not handled. 777 * It's so rare in practice that it's not worth the complexity 778 * tradeoff. The _ending_ year should be passed to 779 * `multiple_month_event`, though, just in case. 780 */ 781 /* translators: Date format for upcoming events on the dashboard. Include the day of the week. See https://www.php.net/manual/datetime.format.php */ 782 var singleDayEvent = __( 'l, M j, Y' ), 783 /* translators: Date string for upcoming events. 1: Month, 2: Starting day, 3: Ending day, 4: Year. */ 784 multipleDayEvent = __( '%1$s %2$d–%3$d, %4$d' ), 785 /* translators: Date string for upcoming events. 1: Starting month, 2: Starting day, 3: Ending month, 4: Ending day, 5: Ending year. */ 786 multipleMonthEvent = __( '%1$s %2$d – %3$s %4$d, %5$d' ); 787 788 // Detect single-day events. 789 if ( ! endDate || format( 'Y-m-d', startDate ) === format( 'Y-m-d', endDate ) ) { 790 formattedDate = dateI18n( singleDayEvent, startDate, timeZone ); 791 792 // Multiple day events. 793 } else if ( format( 'Y-m', startDate ) === format( 'Y-m', endDate ) ) { 794 formattedDate = sprintf( 795 multipleDayEvent, 796 dateI18n( _x( 'F', 'upcoming events month format' ), startDate, timeZone ), 797 dateI18n( _x( 'j', 'upcoming events day format' ), startDate, timeZone ), 798 dateI18n( _x( 'j', 'upcoming events day format' ), endDate, timeZone ), 799 dateI18n( _x( 'Y', 'upcoming events year format' ), endDate, timeZone ) 800 ); 801 802 // Multi-day events that cross a month boundary. 803 } else { 804 formattedDate = sprintf( 805 multipleMonthEvent, 806 dateI18n( _x( 'F', 'upcoming events month format' ), startDate, timeZone ), 807 dateI18n( _x( 'j', 'upcoming events day format' ), startDate, timeZone ), 808 dateI18n( _x( 'F', 'upcoming events month format' ), endDate, timeZone ), 809 dateI18n( _x( 'j', 'upcoming events day format' ), endDate, timeZone ), 810 dateI18n( _x( 'Y', 'upcoming events year format' ), endDate, timeZone ) 811 ); 812 } 813 814 return formattedDate; 815 } 816 }; 817 818 if ( $( '#dashboard_primary' ).is( ':visible' ) ) { 819 app.init(); 820 } else { 821 $( document ).on( 'postbox-toggled', function( event, postbox ) { 822 var $postbox = $( postbox ); 823 824 if ( 'dashboard_primary' === $postbox.attr( 'id' ) && $postbox.is( ':visible' ) ) { 825 app.init(); 826 } 827 }); 828 } 829 }); 830 831 /** 832 * Removed in 5.6.0, needed for back-compatibility. 833 * 834 * @since 4.8.0 835 * @deprecated 5.6.0 836 * 837 * @type {object} 838 */ 839 window.communityEventsData.l10n = window.communityEventsData.l10n || { 840 enter_closest_city: '', 841 error_occurred_please_try_again: '', 842 attend_event_near_generic: '', 843 could_not_locate_city: '', 844 city_updated: '' 845 }; 846 847 window.communityEventsData.l10n = window.wp.deprecateL10nObject( 'communityEventsData.l10n', window.communityEventsData.l10n, '5.6.0' );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Tue Jul 21 08:20:16 2026 | Cross-referenced by PHPXref |