diff --git a/public_html/wp-content/themes/wporg-events-2023/images/close.svg b/public_html/wp-content/themes/wporg-events-2023/images/close.svg new file mode 100644 index 0000000000..4dcdb34ec2 --- /dev/null +++ b/public_html/wp-content/themes/wporg-events-2023/images/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/public_html/wp-content/themes/wporg-events-2023/images/location.svg b/public_html/wp-content/themes/wporg-events-2023/images/location.svg new file mode 100644 index 0000000000..ff676cc21c --- /dev/null +++ b/public_html/wp-content/themes/wporg-events-2023/images/location.svg @@ -0,0 +1,7 @@ + + + + diff --git a/public_html/wp-content/themes/wporg-events-2023/patterns/events-list-filters.php b/public_html/wp-content/themes/wporg-events-2023/patterns/event-list-filters-with-nearby.php similarity index 76% rename from public_html/wp-content/themes/wporg-events-2023/patterns/events-list-filters.php rename to public_html/wp-content/themes/wporg-events-2023/patterns/event-list-filters-with-nearby.php index 3f6b5c4922..2e8e19e074 100644 --- a/public_html/wp-content/themes/wporg-events-2023/patterns/events-list-filters.php +++ b/public_html/wp-content/themes/wporg-events-2023/patterns/event-list-filters-with-nearby.php @@ -1,7 +1,7 @@ + +
+ + + +
+ +
diff --git a/public_html/wp-content/themes/wporg-events-2023/patterns/front-events.php b/public_html/wp-content/themes/wporg-events-2023/patterns/front-events.php index a75a50090a..5737e5e0df 100644 --- a/public_html/wp-content/themes/wporg-events-2023/patterns/front-events.php +++ b/public_html/wp-content/themes/wporg-events-2023/patterns/front-events.php @@ -13,9 +13,10 @@

Upcoming events

- + - + +
diff --git a/public_html/wp-content/themes/wporg-events-2023/postcss/base/layout.pcss b/public_html/wp-content/themes/wporg-events-2023/postcss/base/layout.pcss index d95ce0e325..f3cb5b8953 100644 --- a/public_html/wp-content/themes/wporg-events-2023/postcss/base/layout.pcss +++ b/public_html/wp-content/themes/wporg-events-2023/postcss/base/layout.pcss @@ -10,3 +10,13 @@ body { max-width: var(--wp--custom--layout--wide-size) !important; } } + +.wporg-events__hidden { + display: none; +} + +.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; +} diff --git a/public_html/wp-content/themes/wporg-events-2023/postcss/patterns/event-list-filters-with-nearby.pcss b/public_html/wp-content/themes/wporg-events-2023/postcss/patterns/event-list-filters-with-nearby.pcss new file mode 100644 index 0000000000..0b05f92315 --- /dev/null +++ b/public_html/wp-content/themes/wporg-events-2023/postcss/patterns/event-list-filters-with-nearby.pcss @@ -0,0 +1,16 @@ +.wporg-events__nearby-chips { + button::before { + content: ""; + width: 24px; + height: 24px; + display: inline-block; + } + + #wporg-events__see-global::before { + background-image: url( 'images/close.svg' ); + } + + #wporg-events__see-nearby::before { + background-image: url( 'images/location.svg' ); + } +} diff --git a/public_html/wp-content/themes/wporg-events-2023/postcss/style.pcss b/public_html/wp-content/themes/wporg-events-2023/postcss/style.pcss index b118199354..3fd15c95ae 100644 --- a/public_html/wp-content/themes/wporg-events-2023/postcss/style.pcss +++ b/public_html/wp-content/themes/wporg-events-2023/postcss/style.pcss @@ -11,6 +11,9 @@ @import "base/layout.pcss"; @import "footer/community-callout.pcss"; +/* Patterns */ +@import "patterns/event-list-filters-with-nearby.pcss"; + /* Pages */ @import "page/misc.pcss"; @import "page/front-page/cover.pcss"; diff --git a/public_html/wp-content/themes/wporg-events-2023/src/event-list/block.json b/public_html/wp-content/themes/wporg-events-2023/src/event-list/block.json index 41bf7ad48e..331df6dd74 100644 --- a/public_html/wp-content/themes/wporg-events-2023/src/event-list/block.json +++ b/public_html/wp-content/themes/wporg-events-2023/src/event-list/block.json @@ -24,6 +24,7 @@ }, "supports": { "align": true, + "anchor": true, "color": { "background": true, "text": true @@ -38,5 +39,6 @@ "lineHeight": true } }, - "editorScript": "file:./index.js" + "editorScript": "file:./index.js", + "viewScript" : "file:./view.js" } diff --git a/public_html/wp-content/themes/wporg-events-2023/src/event-list/index.php b/public_html/wp-content/themes/wporg-events-2023/src/event-list/index.php index 2c1f5fad1a..31482b58a8 100644 --- a/public_html/wp-content/themes/wporg-events-2023/src/event-list/index.php +++ b/public_html/wp-content/themes/wporg-events-2023/src/event-list/index.php @@ -10,9 +10,10 @@ use WordPressdotorg\Events_2023; use WP_Block; use WordPressdotorg\MU_Plugins\Google_Map; +use WP_Community_Events; add_action( 'init', __NAMESPACE__ . '\init' ); - +add_action( 'enqueue_block_assets', __NAMESPACE__ . '\enqueue_assets' ); /** * Registers the block using the metadata loaded from the `block.json` file. @@ -30,6 +31,28 @@ function init() { ); } +/** + * Enqueue scripts and styles. + */ +function enqueue_assets() { + require_once ABSPATH . 'wp-admin/includes/class-wp-community-events.php'; + + $payload = array( + 'ip' => WP_Community_Events::get_unsafe_client_ip(), + 'number' => 10, + ); + + if ( is_user_logged_in() ) { + $payload['locale'] = get_user_locale( get_current_user_id() ); + } + + wp_add_inline_script( + 'wporg-event-list-view-script', + 'localEventsPayload = ' . wp_json_encode( $payload ) . ';', + 'before' + ); +} + /** * Render the block content. * @@ -40,15 +63,34 @@ function init() { * @return string Returns the block markup. */ function render( $attributes, $content, $block ) { + if ( 'nearby' === $attributes['events'] ) { + $content = get_nearby_events_markup(); + } else { + $content = get_global_events_markup( $attributes['events'], $attributes['limit'], $attributes['groupByMonth'] ); + } + + $wrapper_attributes = get_block_wrapper_attributes( array( 'id' => $attributes['id'] ?? '' ) ); + + return sprintf( + '
%2$s
', + $wrapper_attributes, + do_blocks( $content ) + ); +} + +/** + * Get markup for a list of global events. + */ +function get_global_events_markup( string $filter, int $limit, bool $group_by_month ): string { $facets = Events_2023\get_query_var_facets(); - $events = Google_Map\get_events( $attributes['events'], 0, 0, $facets ); + $events = Google_Map\get_events( $filter, 0, 0, $facets ); // Get all the filters that are currently applied. - $filtered_events = array_slice( filter_events( $events ), 0, (int) $attributes['limit'] ); + $filtered_events = array_slice( filter_events( $events ), 0, $limit ); // The results are not guaranteed to be in order, so sort them. usort( $filtered_events, - function ( $a, $b ) { + function( $a, $b ) { return $a->timestamp - $b->timestamp; } ); @@ -57,7 +99,7 @@ function ( $a, $b ) { return get_no_result_view(); } - if ( (bool) $attributes['groupByMonth'] ) { + if ( $group_by_month ) { // Group events by month year. $grouped_events = array(); foreach ( $filtered_events as $event ) { @@ -74,12 +116,43 @@ function ( $a, $b ) { $content = get_list_markup( $filtered_events ); } - $wrapper_attributes = get_block_wrapper_attributes(); - return sprintf( - '
%2$s
', - $wrapper_attributes, - do_blocks( $content ) - ); + return $content; +} + +/** + * Get markup for a list of nearby events. + * + * The events themselves are populated by an XHR, to avoid blocking the TTFB with an external HTTP request. See `view.js`. + */ +function get_nearby_events_markup(): string { + ob_start(); + + ?> + +

+ Loading nearby events... + +

+ + + + +
+

+ There are no events scheduled near you at the moment. You can browse global events, or + learn how to organize an event in your area. +

+
+ + + 0 ) { + for ( let i = 0; i < results.events.length; i++ ) { + const eventDiv = document.createElement( 'li' ); + eventDiv.classList.add( 'wporg-marker-list-item' ); + eventDiv.innerHTML = renderEvent( results.events[ i ] ); + listContainer.appendChild( eventDiv ); + + // todo convert this to jsx to avoid xss + } + + } else { + noEventsMessage.classList.remove( 'wporg-events__hidden' ); + } + + } catch ( error ) { + // This matches the format returned by the API for application-layer errors (e.g., invalid API key). + showGlobal(); + chipsContainer.classList.add( 'wporg-events__hidden' ); + + } finally { + loadingElement.classList.add( 'wporg-events__hidden' ); + } + } + + /** + * Encode any HTML in a string to prevent XSS. + * + * @param {string} unsafe + * + * @returns {string} + */ + function escapeHtml( unsafe ) { + const safe = document.createTextNode( unsafe ).textContent; + + return safe; + } + + // note needs to be in sync with get_list_markup + // maybe echo that as a template, then copy this into it? that might be just as tightly coupled though + // unless maybe pass a var to the function which tells it to php vs js, and then the value would be `${title}` for js and $event->title for php + function renderEvent( { title, url, location, start_unix_timestamp: timestamp } ) { + const markup = ` +

+ + ${ escapeHtml( title ) } + +

+ +
+ ${ escapeHtml( location.location ) } +
+ + ${ getEventDateTime( title, timestamp ) } + `; + + 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( escapeHtml( timestamp ) * 1000 ); + + const localeDate = eventDate.toLocaleDateString( [], { + weekday: 'long', + year: 'numeric', + month: 'short', + day: 'numeric', + } ); + + const localeTime = eventDate.toLocaleString( [], { + timeZoneName: 'short', + hour: 'numeric', + minute: '2-digit', + } ); + + return ` + + `; + } + + init(); + fetchLocalEvents(); +} );