--- a/wp/wp-admin/js/dashboard.js Tue Dec 15 15:52:01 2020 +0100
+++ b/wp/wp-admin/js/dashboard.js Wed Sep 21 18:19:35 2022 +0200
@@ -5,13 +5,14 @@
/* global pagenow, ajaxurl, postboxes, wpActiveEditor:true, ajaxWidgets */
/* global ajaxPopulateWidgets, quickPressLoad, */
window.wp = window.wp || {};
+window.communityEventsData = window.communityEventsData || {};
/**
* Initializes the dashboard widget functionality.
*
* @since 2.7.0
*/
-jQuery(document).ready( function($) {
+jQuery( function($) {
var welcomePanel = $( '#welcome-panel' ),
welcomePanelHide = $('#wp_welcome_panel-hide'),
updateWelcomePanel;
@@ -39,7 +40,7 @@
}
// Hide the welcome panel when the dismiss button or close button is clicked.
- $('.welcome-panel-close, .welcome-panel-dismiss a', welcomePanel).click( function(e) {
+ $('.welcome-panel-close, .welcome-panel-dismiss a', welcomePanel).on( 'click', function(e) {
e.preventDefault();
welcomePanel.addClass('hidden');
updateWelcomePanel( 0 );
@@ -47,7 +48,7 @@
});
// Set welcome panel visibility based on Welcome Option checkbox value.
- welcomePanelHide.click( function() {
+ welcomePanelHide.on( 'click', function() {
welcomePanel.toggleClass('hidden', ! this.checked );
updateWelcomePanel( this.checked ? 1 : 0 );
});
@@ -135,7 +136,7 @@
// Enable the submit buttons.
$( '#quick-press .submit input[type="submit"], #quick-press .submit input[type="reset"]' ).prop( 'disabled' , false );
- t = $('#quick-press').submit( function( e ) {
+ t = $('#quick-press').on( 'submit', function( e ) {
e.preventDefault();
// Show a spinner.
@@ -153,7 +154,7 @@
highlightLatestPost();
// Focus the title to allow for quickly drafting another post.
- $('#title').focus();
+ $('#title').trigger( 'focus' );
});
/**
@@ -171,7 +172,7 @@
} );
// Change the QuickPost action to the publish value.
- $('#publish').click( function() { act.val( 'post-quickpress-publish' ); } );
+ $('#publish').on( 'click', function() { act.val( 'post-quickpress-publish' ); } );
$('#quick-press').on( 'click focusin', function() {
wpActiveEditor = 'content';
@@ -265,7 +266,12 @@
jQuery( function( $ ) {
'use strict';
- var communityEventsData = window.communityEventsData || {},
+ var communityEventsData = window.communityEventsData,
+ dateI18n = wp.date.dateI18n,
+ format = wp.date.format,
+ sprintf = wp.i18n.sprintf,
+ __ = wp.i18n.__,
+ _x = wp.i18n._x,
app;
/**
@@ -322,7 +328,7 @@
* @return {void}
*/
$container.on( 'submit', '.community-events-form', function( event ) {
- var location = $.trim( $( '#community-events-location' ).val() );
+ var location = $( '#community-events-location' ).val().trim();
event.preventDefault();
@@ -385,7 +391,7 @@
* lose their place.
*/
if ( $target.hasClass( 'community-events-cancel' ) ) {
- $toggleButton.focus();
+ $toggleButton.trigger( 'focus' );
}
} else {
$toggleButton.attr( 'aria-expanded', 'true' );
@@ -441,6 +447,7 @@
.fail( function() {
app.renderEventsTemplate({
'location' : false,
+ 'events' : [],
'error' : true
}, initiatedBy );
});
@@ -460,11 +467,15 @@
renderEventsTemplate: function( templateParams, initiatedBy ) {
var template,
elementVisibility,
- l10nPlaceholder = /%(?:\d\$)?s/g, // Match `%s`, `%1$s`, `%2$s`, etc.
$toggleButton = $( '.community-events-toggle-location' ),
$locationMessage = $( '#community-events-location-message' ),
$results = $( '.community-events-results' );
+ templateParams.events = app.populateDynamicEventFields(
+ templateParams.events,
+ communityEventsData.time_format
+ );
+
/*
* Hide all toggleable elements by default, to keep the logic simple.
* Otherwise, each block below would have to turn hide everything that
@@ -494,7 +505,7 @@
* If the API determined the location by geolocating an IP, it will
* provide events, but not a specific location.
*/
- $locationMessage.text( communityEventsData.l10n.attend_event_near_generic );
+ $locationMessage.text( __( 'Attend an upcoming event near you.' ) );
if ( templateParams.events.length ) {
template = wp.template( 'community-events-event-list' );
@@ -521,7 +532,14 @@
}
if ( 'user' === initiatedBy ) {
- wp.a11y.speak( communityEventsData.l10n.city_updated.replace( l10nPlaceholder, templateParams.location.description ), 'assertive' );
+ wp.a11y.speak(
+ sprintf(
+ /* translators: %s: The name of a city. */
+ __( 'City updated. Listing events near %s.' ),
+ templateParams.location.description
+ ),
+ 'assertive'
+ );
}
elementVisibility['#community-events-location-message'] = true;
@@ -531,7 +549,28 @@
} else if ( templateParams.unknownCity ) {
template = wp.template( 'community-events-could-not-locate' );
$( '.community-events-could-not-locate' ).html( template( templateParams ) );
- wp.a11y.speak( communityEventsData.l10n.could_not_locate_city.replace( l10nPlaceholder, templateParams.unknownCity ) );
+ wp.a11y.speak(
+ sprintf(
+ /*
+ * These specific examples were chosen to highlight the fact that a
+ * state is not needed, even for cities whose name is not unique.
+ * It would be too cumbersome to include that in the instructions
+ * to the user, so it's left as an implication.
+ */
+ /*
+ * translators: %s is the name of the city we couldn't locate.
+ * Replace the examples with cities related to your locale. Test that
+ * they match the expected location and have upcoming events before
+ * including them. If no cities related to your locale have events,
+ * then use cities related to your locale that would be recognizable
+ * to most users. Use only the city name itself, without any region
+ * or country. Use the endonym (native locale name) instead of the
+ * English name if possible.
+ */
+ __( 'We couldn’t locate %s. Please try another nearby city. For example: Kansas City; Springfield; Portland.' ),
+ templateParams.unknownCity
+ )
+ );
elementVisibility['.community-events-errors'] = true;
elementVisibility['.community-events-could-not-locate'] = true;
@@ -543,12 +582,12 @@
* Showing error messages for an event that user isn't aware of
* could be confusing or unnecessarily distracting.
*/
- wp.a11y.speak( communityEventsData.l10n.error_occurred_please_try_again );
+ wp.a11y.speak( __( 'An error occurred. Please try again.' ) );
elementVisibility['.community-events-errors'] = true;
elementVisibility['.community-events-error-occurred'] = true;
} else {
- $locationMessage.text( communityEventsData.l10n.enter_closest_city );
+ $locationMessage.text( __( 'Enter your closest city to find nearby events.' ) );
elementVisibility['#community-events-location-message'] = true;
elementVisibility['.community-events-toggle-location'] = true;
@@ -571,11 +610,200 @@
* bring the focus back to the toggle button so users relying
* on screen readers don't lose their place.
*/
- $toggleButton.focus();
+ $toggleButton.trigger( 'focus' );
}
} else {
app.toggleLocationForm( 'show' );
}
+ },
+
+ /**
+ * Populate event fields that have to be calculated on the fly.
+ *
+ * These can't be stored in the database, because they're dependent on
+ * the user's current time zone, locale, etc.
+ *
+ * @since 5.5.2
+ *
+ * @param {Array} rawEvents The events that should have dynamic fields added to them.
+ * @param {string} timeFormat A time format acceptable by `wp.date.dateI18n()`.
+ *
+ * @returns {Array}
+ */
+ populateDynamicEventFields: function( rawEvents, timeFormat ) {
+ // Clone the parameter to avoid mutating it, so that this can remain a pure function.
+ var populatedEvents = JSON.parse( JSON.stringify( rawEvents ) );
+
+ $.each( populatedEvents, function( index, event ) {
+ var timeZone = app.getTimeZone( event.start_unix_timestamp * 1000 );
+
+ event.user_formatted_date = app.getFormattedDate(
+ event.start_unix_timestamp * 1000,
+ event.end_unix_timestamp * 1000,
+ timeZone
+ );
+
+ event.user_formatted_time = dateI18n(
+ timeFormat,
+ event.start_unix_timestamp * 1000,
+ timeZone
+ );
+
+ event.timeZoneAbbreviation = app.getTimeZoneAbbreviation( event.start_unix_timestamp * 1000 );
+ } );
+
+ return populatedEvents;
+ },
+
+ /**
+ * Returns the user's local/browser time zone, in a form suitable for `wp.date.i18n()`.
+ *
+ * @since 5.5.2
+ *
+ * @param startTimestamp
+ *
+ * @returns {string|number}
+ */
+ getTimeZone: function( startTimestamp ) {
+ /*
+ * Prefer a name like `Europe/Helsinki`, since that automatically tracks daylight savings. This
+ * doesn't need to take `startTimestamp` into account for that reason.
+ */
+ var timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
+
+ /*
+ * Fall back to an offset for IE11, which declares the property but doesn't assign a value.
+ */
+ if ( 'undefined' === typeof timeZone ) {
+ /*
+ * It's important to use the _event_ time, not the _current_
+ * time, so that daylight savings time is accounted for.
+ */
+ timeZone = app.getFlippedTimeZoneOffset( startTimestamp );
+ }
+
+ return timeZone;
+ },
+
+ /**
+ * Get intuitive time zone offset.
+ *
+ * `Data.prototype.getTimezoneOffset()` returns a positive value for time zones
+ * that are _behind_ UTC, and a _negative_ value for ones that are ahead.
+ *
+ * See https://stackoverflow.com/questions/21102435/why-does-javascript-date-gettimezoneoffset-consider-0500-as-a-positive-off.
+ *
+ * @since 5.5.2
+ *
+ * @param {number} startTimestamp
+ *
+ * @returns {number}
+ */
+ getFlippedTimeZoneOffset: function( startTimestamp ) {
+ return new Date( startTimestamp ).getTimezoneOffset() * -1;
+ },
+
+ /**
+ * Get a short time zone name, like `PST`.
+ *
+ * @since 5.5.2
+ *
+ * @param {number} startTimestamp
+ *
+ * @returns {string}
+ */
+ getTimeZoneAbbreviation: function( startTimestamp ) {
+ var timeZoneAbbreviation,
+ eventDateTime = new Date( startTimestamp );
+
+ /*
+ * Leaving the `locales` argument undefined is important, so that the browser
+ * displays the abbreviation that's most appropriate for the current locale. For
+ * some that will be `UTC{+|-}{n}`, and for others it will be a code like `PST`.
+ *
+ * This doesn't need to take `startTimestamp` into account, because a name like
+ * `America/Chicago` automatically tracks daylight savings.
+ */
+ var shortTimeStringParts = eventDateTime.toLocaleTimeString( undefined, { timeZoneName : 'short' } ).split( ' ' );
+
+ if ( 3 === shortTimeStringParts.length ) {
+ timeZoneAbbreviation = shortTimeStringParts[2];
+ }
+
+ if ( 'undefined' === typeof timeZoneAbbreviation ) {
+ /*
+ * It's important to use the _event_ time, not the _current_
+ * time, so that daylight savings time is accounted for.
+ */
+ var timeZoneOffset = app.getFlippedTimeZoneOffset( startTimestamp ),
+ sign = -1 === Math.sign( timeZoneOffset ) ? '' : '+';
+
+ // translators: Used as part of a string like `GMT+5` in the Events Widget.
+ timeZoneAbbreviation = _x( 'GMT', 'Events widget offset prefix' ) + sign + ( timeZoneOffset / 60 );
+ }
+
+ return timeZoneAbbreviation;
+ },
+
+ /**
+ * Format a start/end date in the user's local time zone and locale.
+ *
+ * @since 5.5.2
+ *
+ * @param {int} startDate The Unix timestamp in milliseconds when the the event starts.
+ * @param {int} endDate The Unix timestamp in milliseconds when the the event ends.
+ * @param {string} timeZone A time zone string or offset which is parsable by `wp.date.i18n()`.
+ *
+ * @returns {string}
+ */
+ getFormattedDate: function( startDate, endDate, timeZone ) {
+ var formattedDate;
+
+ /*
+ * The `date_format` option is not used because it's important
+ * in this context to keep the day of the week in the displayed date,
+ * so that users can tell at a glance if the event is on a day they
+ * are available, without having to open the link.
+ *
+ * The case of crossing a year boundary is intentionally not handled.
+ * It's so rare in practice that it's not worth the complexity
+ * tradeoff. The _ending_ year should be passed to
+ * `multiple_month_event`, though, just in case.
+ */
+ /* translators: Date format for upcoming events on the dashboard. Include the day of the week. See https://www.php.net/manual/datetime.format.php */
+ var singleDayEvent = __( 'l, M j, Y' ),
+ /* translators: Date string for upcoming events. 1: Month, 2: Starting day, 3: Ending day, 4: Year. */
+ multipleDayEvent = __( '%1$s %2$d–%3$d, %4$d' ),
+ /* translators: Date string for upcoming events. 1: Starting month, 2: Starting day, 3: Ending month, 4: Ending day, 5: Ending year. */
+ multipleMonthEvent = __( '%1$s %2$d – %3$s %4$d, %5$d' );
+
+ // Detect single-day events.
+ if ( ! endDate || format( 'Y-m-d', startDate ) === format( 'Y-m-d', endDate ) ) {
+ formattedDate = dateI18n( singleDayEvent, startDate, timeZone );
+
+ // Multiple day events.
+ } else if ( format( 'Y-m', startDate ) === format( 'Y-m', endDate ) ) {
+ formattedDate = sprintf(
+ multipleDayEvent,
+ dateI18n( _x( 'F', 'upcoming events month format' ), startDate, timeZone ),
+ dateI18n( _x( 'j', 'upcoming events day format' ), startDate, timeZone ),
+ dateI18n( _x( 'j', 'upcoming events day format' ), endDate, timeZone ),
+ dateI18n( _x( 'Y', 'upcoming events year format' ), endDate, timeZone )
+ );
+
+ // Multi-day events that cross a month boundary.
+ } else {
+ formattedDate = sprintf(
+ multipleMonthEvent,
+ dateI18n( _x( 'F', 'upcoming events month format' ), startDate, timeZone ),
+ dateI18n( _x( 'j', 'upcoming events day format' ), startDate, timeZone ),
+ dateI18n( _x( 'F', 'upcoming events month format' ), endDate, timeZone ),
+ dateI18n( _x( 'j', 'upcoming events day format' ), endDate, timeZone ),
+ dateI18n( _x( 'Y', 'upcoming events year format' ), endDate, timeZone )
+ );
+ }
+
+ return formattedDate;
}
};
@@ -591,3 +819,21 @@
});
}
});
+
+/**
+ * Removed in 5.6.0, needed for back-compatibility.
+ *
+ * @since 4.8.0
+ * @deprecated 5.6.0
+ *
+ * @type {object}
+*/
+window.communityEventsData.l10n = window.communityEventsData.l10n || {
+ enter_closest_city: '',
+ error_occurred_please_try_again: '',
+ attend_event_near_generic: '',
+ could_not_locate_city: '',
+ city_updated: ''
+};
+
+window.communityEventsData.l10n = window.wp.deprecateL10nObject( 'communityEventsData.l10n', window.communityEventsData.l10n, '5.6.0' );