Skip to content

Commit

Permalink
Events: Restore local time when showing events
Browse files Browse the repository at this point in the history
This moves date rendering back to JavaScript, and posts the grouping there as well. That's necessary because the month boundaries are affected by the user's timezone.
  • Loading branch information
iandunn committed Dec 21, 2023
1 parent bdde62e commit 5d3e6a1
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ body {
max-width: var(--wp--custom--layout--wide-size) !important;
}
}

.wporg-events__hidden {
display: none;
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
}

& .wporg-marker-list-item__location {
&::first-letter {
text-transform: capitalize;
}

@media (--medium-small) {
margin-top: 2px;
Expand Down Expand Up @@ -96,3 +99,14 @@
}
}
}

body.home .wporg-marker-list__loading {
/* The final height could be anywhere between 50px and 750px, so split the difference in order to minimize the
* amount of layout shift. */
height: 350px;
}

body.page-slug-upcoming-events .wporg-marker-list__loading {
/* Take up most of the viewport to avoid a large layout shift. We know this list will have a lot of entries. */
height: 90vh;
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@
"lineHeight": true
}
},
"editorScript": "file:./index.js"
"editorScript": "file:./index.js",
"viewScript": [ "wp-a11y", "file:./view.js" ]
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

namespace WordPressdotorg\Theme\Events_2023\WordPress_Event_List;

use WordPressdotorg\Events_2023;
use WP_Block;
use WordPressdotorg\MU_Plugins\Google_Map;
Expand Down Expand Up @@ -57,63 +58,61 @@ function ( $a, $b ) {
return get_no_result_view();
}

if ( (bool) $attributes['groupByMonth'] ) {
// Group events by month year.
$grouped_events = array();
foreach ( $filtered_events as $event ) {
$event_month_year = gmdate( 'F Y', esc_html( $event->timestamp ) );
$grouped_events[ $event_month_year ][] = $event;
}
// Prune to only the used properties, to reduce the size of the payload.
$filtered_events = array_map(
function ( $event ) {
return array(
'title' => $event->title,
'url' => $event->url,
'location' => $event->location,
'timestamp' => $event->timestamp,
);
},
$filtered_events
);

$content = '';
foreach ( $grouped_events as $month_year => $events ) {
$content .= get_section_title( $month_year );
$content .= get_list_markup( $events );
}
} else {
$content = get_list_markup( $filtered_events );
}
$payload = array(
'events' => $filtered_events,
'groupByMonth' => $attributes['groupByMonth'],
);

wp_add_inline_script(
// `generate_block_asset_handle()` includes the index if `viewScript` is an array, so this is fragile.
// There isn't a way to get it programmatically, though, so it just has to manually be kept in sync.
'wporg-event-list-view-script-2',
'globalEventsPayload = ' . wp_json_encode( $payload ) . ';',
'before'
);

ob_start();

?>

<p class="wporg-marker-list__loading">
Loading global events...
<img
src="<?php echo esc_url( includes_url( 'images/spinner-2x.gif' ) ); ?>"
width="20"
height="20"
alt=""
/>
</p>

<?php

$content = ob_get_clean();

$wrapper_attributes = get_block_wrapper_attributes();

return sprintf(
'<div %1$s>%2$s</div>',
$wrapper_attributes,
do_blocks( $content )
);
}

/**
* Returns the event-list markup.
*
* @param array $events Array of events.
*
* @return string
*/
function get_list_markup( $events ) {
$block_markup = '<ul class="wporg-marker-list__container">';

foreach ( $events as $event ) {
$block_markup .= '<li class="wporg-marker-list-item">';
$block_markup .= '<h3 class="wporg-marker-list-item__title"><a class="external-link" href="' . esc_url( $event->url ) . '">' . esc_html( $event->title ) . '</a></h3>';
$block_markup .= '<div class="wporg-marker-list-item__location">' . ucfirst( esc_html( $event->location ) ). '</div>';
$block_markup .= sprintf(
'<time class="wporg-marker-list-item__date-time" date-time="%1$s" title="%1$s"><span class="wporg-google-map__date">%2$s</span><span class="wporg-google-map__time">%3$s</span></time>',
gmdate( 'c', esc_html( $event->timestamp ) ),
gmdate( 'l, M j', esc_html( $event->timestamp ) ),
esc_html( gmdate('H:i', $event->timestamp) . ' UTC' ),
);
$block_markup .= '</li>';
}

$block_markup .= '</ul>';

return $block_markup;
}

/**
* Get a list of the currently-applied filters.
*
* @return array
*/
function filter_events( array $events ): array {
global $wp_query;
Expand Down Expand Up @@ -143,32 +142,14 @@ function filter_events( array $events ): array {
$filtered_events = array();
foreach ( $events as $event ) {
// Assuming each event has a 'type' property.
if ( isset($event->type) && in_array($event->type, $terms) ) {
if ( isset( $event->type ) && in_array( $event->type, $terms ) ) {
$filtered_events[] = $event;
}
}

return $filtered_events;
}

/**
* Returns core heading block markup for the date groups.
*
* @param string $heading_text Heading text.
*
* @return string
*/
function get_section_title( $heading_text ) {
$block_markup = '<!-- wp:heading {"style":{"elements":{"link":{"color":{"text":"var:preset|color|charcoal-1"}}},"typography":{"fontStyle":"normal","fontWeight":"700"},"spacing":{"margin":{"top":"var:preset|spacing|40","bottom":"var:preset|spacing|20"}}},"textColor":"charcoal-1","fontSize":"medium","fontFamily":"inter"} -->';
$block_markup .= sprintf(
'<h2 class="wp-block-heading has-charcoal-1-color has-text-color has-link-color has-inter-font-family has-medium-font-size" style="margin-top:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--20);font-style:normal;font-weight:700">%s</h2>',
esc_html( $heading_text )
);
$block_markup .= '<!-- /wp:heading -->';

return $block_markup;
}

/**
* Returns a block driven view when no results are found.
*
Expand All @@ -186,9 +167,11 @@ function get_no_result_view() {
'<!-- wp:paragraph {"align":"center"} --><p class="has-text-align-center">%s</p><!-- /wp:paragraph -->',
sprintf(
wp_kses_post(
/* translators: %s is url of the event archives. */
__( 'View <a href="%s">upcoming events</a> or try a different search.', 'wporg' ) ),
esc_url( home_url( '/upcoming-events/' ) ) )
/* translators: %s is the URL of the event archives. */
__( 'View <a href="%s">upcoming events</a> or try a different search.', 'wporg' )
),
esc_url( home_url( '/upcoming-events/' ) )
)
);
$content .= '</div><!-- /wp:group -->';

Expand Down
183 changes: 183 additions & 0 deletions public_html/wp-content/themes/wporg-events-2023/src/event-list/view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/* global globalEventsPayload */

document.addEventListener( 'DOMContentLoaded', function() {
const speak = wp.a11y.speak;
const globalEventList = document.querySelector( '.wp-block-wporg-event-list' );

/**
* Initialize the component.
*/
function init() {
if ( 'undefined' === typeof globalEventsPayload ) {
// eslint-disable-next-line no-console
console.error( 'Missing globalEventsPayload' );
return;
}

renderGlobalEvents( globalEventsPayload.events, globalEventsPayload.groupByMonth );
}

/**
* Render global events
*
* @param {Array} events
* @param {boolean} groupByMonth
*/
function renderGlobalEvents( events, groupByMonth ) {
const loadingElement = globalEventList.querySelector( '.wporg-marker-list__loading' );
const groupedEvents = {};
let markup = '';

if ( groupByMonth ) {
for ( let i = 0; i < events.length; i++ ) {
const eventMonthYear = new Date( events[ i ].timestamp * 1000 ).toLocaleDateString( [], {
year: 'numeric',
month: 'long',
} );

groupedEvents[ eventMonthYear ] = groupedEvents[ eventMonthYear ] || [];
groupedEvents[ eventMonthYear ].push( events[ i ] );
}

for ( const [ month, eventGroup ] of Object.entries( groupedEvents ) ) {
markup += renderEventGroup( eventGroup, month );
}
} else {
markup = renderEventList( events );
}

globalEventList.innerHTML = markup;

loadingElement.classList.add( 'wporg-events__hidden' );
speak( 'Global events loaded.' );
}

/**
* Encode any HTML in a string to prevent XSS.
*
* @param {string} unsafe
*
* @return {string}
*/
function escapeHtml( unsafe ) {
const safe = document.createTextNode( unsafe ).textContent;

return safe;
}

/**
* Render a group of events for a given month
*
* @param {Array} group
* @param {string} month
*
* @return {string}
*/
function renderEventGroup( group, month ) {
let markup = `
<h2
class="wp-block-heading has-charcoal-1-color has-text-color has-link-color has-inter-font-family has-medium-font-size"
style="margin-top:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--20);font-style:normal;font-weight:700">
${ escapeHtml( month ) }
</h2>`;

markup += renderEventList( group );

return markup;
}

/**
* Render a list of events
*
* @param {Array} events
*
* @return {string}
*/
function renderEventList( events ) {
let markup = '<ul class="wporg-marker-list__container">';

for ( let i = 0; i < events.length; i++ ) {
markup += renderEvent( events[ i ] );
}

markup += '</ul>';

return markup;
}

/**
* Render a single event
*
* @param {Object} event
* @param {string} event.title
* @param {string} event.url
* @param {string} event.location
* @param {number} event.timestamp
*
* @return {string}
*/
function renderEvent( { title, url, location, timestamp } ) {
const markup = `
<li class="wporg-marker-list-item">
<h3 class="wporg-marker-list-item__title">
<a class="external-link" href="${ escapeHtml( url ) }">
${ escapeHtml( title ) }
</a>
</h3>
<div class="wporg-marker-list-item__location">
${ escapeHtml( location ) }
</div>
${ getEventDateTime( title, timestamp ) }
</li>
`;

return markup;
}

/**
* Display a timestamp in the user's timezone and locale format.
*
* Note: The start time and day of the week are important pieces of information to include, since that helps
* attendees know at a glance if it's something they can attend. Otherwise they have to click to open it. The
* timezone is also important to make it clear that we're showing the user's timezone, not the venue's.
*
* @see https://make.wordpress.org/community/2017/03/23/showing-upcoming-local-events-in-wp-admin/#comment-23297
* @see https://make.wordpress.org/community/2017/03/23/showing-upcoming-local-events-in-wp-admin/#comment-23307
*
* @param {string} title
* @param {number} timestamp
*
* @return {string} The formatted date and time.
*/
function getEventDateTime( title, timestamp ) {
const eventDate = new Date( parseInt( timestamp ) * 1000 );

const localeDate = eventDate.toLocaleDateString( [], {
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric',
} );

const localeTime = eventDate.toLocaleString( [], {
timeZoneName: 'short',
hour: 'numeric',
minute: '2-digit',
} );

return `
<time
class="wporg-marker-list-item__date-time"
datetime="${ eventDate.toISOString() }"
title="${ escapeHtml( title ) }"
>
<span class="wporg-google-map__date">${ localeDate }</span>
<span class="wporg-google-map__time">${ localeTime }</span>
</time>
`;
}

init();
} );

0 comments on commit 5d3e6a1

Please sign in to comment.