[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-admin/js/ -> dashboard.js (source)

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


Generated: Sun Nov 29 01:00:04 2020 Cross-referenced by PHPXref 0.7.1