Create blocks to integrate Sessionize schedules and speakers#1081
Create blocks to integrate Sessionize schedules and speakers#1081cjyabraham merged 22 commits intomainfrom
Conversation
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
There was a problem hiding this comment.
Pull request overview
This PR aims to introduce a new WordPress block plugin under sessionize-schedule and wire it into the theme’s plugin install workflow.
Changes:
- Added an
install-sessionize-schedulenpm script in thelfeventstheme. - Added a new plugin directory
web/wp-content/plugins/sessionize-schedule/containing a block implementation and built assets. - Updated repo
.gitignoreto include the new plugin directory while ignoring itsnode_modules.
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| web/wp-content/themes/lfevents/package.json | Adds an npm install script for the new plugin. |
| web/wp-content/plugins/sessionize-schedule/custom-iframe.php | Registers a block (currently duplicates the existing Custom iFrame plugin). |
| web/wp-content/plugins/sessionize-schedule/block.json | Block metadata (currently uses the existing lf/custom-iframe name). |
| web/wp-content/plugins/sessionize-schedule/build/index.js | Built editor script (appears to be for Custom iFrame, not Sessionize). |
| web/wp-content/plugins/sessionize-schedule/build/index.css | Editor CSS for the block. |
| web/wp-content/plugins/sessionize-schedule/build/style-index.css | Declared front-end style file, but currently empty. |
| web/wp-content/plugins/sessionize-schedule/build/index.asset.php | WP script dependency/version manifest for the build. |
| web/wp-content/plugins/sessionize-schedule/.gitignore | Plugin-local ignores (node_modules, logs, etc.). |
| web/wp-content/plugins/sessionize-schedule/.editorconfig | Plugin-local editor configuration. |
| .gitignore | Ensures the new plugin directory is committed but its node_modules is ignored. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
|
|
||
| function getSessionAbstract_( session ) { | ||
| const raw = session?.description ?? session?.Description ?? session?.abstract ?? session?.Abstract ?? ''; | ||
| return String( raw || '' ).replace( /<[^>]*>/g, '' ).trim(); |
|
|
||
| function getBio_( speaker ) { | ||
| const b = speaker?.bio ?? speaker?.Bio ?? ''; | ||
| return String( b || '' ).replace( /<[^>]*>/g, '' ).trim(); |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 31 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (1)
web/wp-content/themes/lfevents/package.json:43
- The
install-pluginsaggregate script doesn't include the new Sessionize install script, so runningnpm run install-pluginswill skip this plugin. Add the Sessionize install task toinstall-plugins(and likewise tobuild-pluginsif you want it built with the rest).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <div class="sz-modal__avatar" data-role="szModalAvatar"></div> | ||
|
|
||
| <div class="sz-modal__meta"> | ||
| <h2 class="sz-modal__name" data-role="szModalTitle"></h2> |
There was a problem hiding this comment.
aria-labelledby="szModalTitle" references an element id that isn't present in the markup (the <h2> only has data-role). This breaks screen reader labeling for the dialog. Add id="szModalTitle" to the title element (or update aria-labelledby to match the actual id).
| <h2 class="sz-modal__name" data-role="szModalTitle"></h2> | |
| <h2 class="sz-modal__name" id="szModalTitle" data-role="szModalTitle"></h2> |
| <div | ||
| class="sched-wrapper sched" | ||
| data-sched-config="<?php echo esc_attr( wp_json_encode( $sched_config ) ); ?>" | ||
| <?php echo get_block_wrapper_attributes(); ?> |
There was a problem hiding this comment.
This wrapper renders both a literal class="sched-wrapper sched" attribute and get_block_wrapper_attributes(), which will also output its own class attribute. Duplicate class attributes produce invalid HTML and can break block classes. Prefer using get_block_wrapper_attributes([ 'class' => 'sched-wrapper sched' ]) and remove the hard-coded class attribute.
| <div | |
| class="sched-wrapper sched" | |
| data-sched-config="<?php echo esc_attr( wp_json_encode( $sched_config ) ); ?>" | |
| <?php echo get_block_wrapper_attributes(); ?> | |
| <div | |
| data-sched-config="<?php echo esc_attr( wp_json_encode( $sched_config ) ); ?>" | |
| <?php echo get_block_wrapper_attributes( array( 'class' => 'sched-wrapper sched' ) ); ?> |
| const a = document.createElement( 'a' ); | ||
| a.className = 'sz-modal__link'; | ||
| a.href = String( link.url || '' ).trim(); | ||
| a.target = '_blank'; | ||
| a.rel = 'noopener'; | ||
| a.setAttribute( 'aria-label', label ); | ||
| a.title = label; |
There was a problem hiding this comment.
Speaker social links set a.href directly from Sessionize data without validating the URL scheme. A javascript: (or other non-http/https) URL from upstream data would become an XSS vector. Before assigning href, validate/normalize to allow only http:/https: (and optionally mailto:) and skip/omit links that don't pass.
| document.addEventListener( 'keydown', ( e ) => { | ||
| if ( modal.getAttribute( 'aria-hidden' ) === 'true' ) { | ||
| return; | ||
| } | ||
| if ( e.key === 'Escape' ) { | ||
| e.preventDefault(); | ||
| closeModal_(); | ||
| } | ||
| } ); |
There was a problem hiding this comment.
wireModalControls_ attaches a document-level keydown listener for Escape inside initSpeakerGrid. If multiple Sessionize Speakers blocks appear on a page, this will register multiple global listeners. Consider registering the handler once (e.g., per-modal, or via a shared singleton) to avoid duplicated listeners and make teardown easier.
| "install-sessionize-schedule": "npm install --prefix ../../plugins/sessionize-schedule/", | ||
| "install-icon-list": "npm install --prefix ../../plugins/icon-list/", |
There was a problem hiding this comment.
install-sessionize-schedule points to ../../plugins/sessionize-schedule/, but the plugin added in this PR is web/wp-content/plugins/sessionize-blocks/ (and there is no sessionize-schedule directory). Update the script name/path to install the correct plugin directory.
| class="sz-speakers-wrap" | ||
| data-speaker-config="<?php echo esc_attr( wp_json_encode( $speaker_config ) ); ?>" | ||
| <?php echo get_block_wrapper_attributes(); ?> |
There was a problem hiding this comment.
This wrapper renders both a literal class="sz-speakers-wrap" attribute and get_block_wrapper_attributes(), which will also output its own class attribute. Duplicate class attributes produce invalid HTML and can break block classes. Prefer using get_block_wrapper_attributes([ 'class' => 'sz-speakers-wrap' ]) and remove the hard-coded class attribute.
| class="sz-speakers-wrap" | |
| data-speaker-config="<?php echo esc_attr( wp_json_encode( $speaker_config ) ); ?>" | |
| <?php echo get_block_wrapper_attributes(); ?> | |
| data-speaker-config="<?php echo esc_attr( wp_json_encode( $speaker_config ) ); ?>" | |
| <?php echo get_block_wrapper_attributes( array( 'class' => 'sz-speakers-wrap' ) ); ?> |
…ptions - Changed the data fetching URLs in render.php to use 'apiCode' instead of 'publicSlug'. - Added a new TextControl for 'Sessionize API Code' in edit.js to allow users to input the API code. - Updated the instructions in the Placeholder to reflect the use of API code. - Modified view.js to use 'sessionizeApiCode' for local storage key and adjusted the sessionize public site link generation. - Enhanced date formatting options to include 'Year/Month/Day' format in both display and filtering functions. Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 31 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <div | ||
| class="sched-wrapper sched" | ||
| data-sched-config="<?php echo esc_attr( wp_json_encode( $sched_config ) ); ?>" | ||
| <?php echo get_block_wrapper_attributes(); ?> |
There was a problem hiding this comment.
This <div> renders a class attribute and also prints get_block_wrapper_attributes(), which typically includes its own class="...". That produces duplicate class attributes (invalid HTML) and may break styling/JS targeting. Prefer echo get_block_wrapper_attributes( array( 'class' => 'sched-wrapper sched' ) ); and remove the hard-coded class attribute.
| <div | |
| class="sched-wrapper sched" | |
| data-sched-config="<?php echo esc_attr( wp_json_encode( $sched_config ) ); ?>" | |
| <?php echo get_block_wrapper_attributes(); ?> | |
| <div | |
| data-sched-config="<?php echo esc_attr( wp_json_encode( $sched_config ) ); ?>" | |
| <?php echo get_block_wrapper_attributes( array( 'class' => 'sched-wrapper sched' ) ); ?> |
| function warmSpeakerAssets_( speakers ) { | ||
| const seen = new Set(); | ||
| ( speakers || [] ).forEach( ( s ) => { | ||
| const avatar = String( s?.profilePicture || '' ).trim(); | ||
| if ( ! avatar || seen.has( avatar ) ) { | ||
| return; | ||
| } | ||
| seen.add( avatar ); | ||
| preloadImage_( avatar ); | ||
| } ); |
There was a problem hiding this comment.
warmSpeakerAssets_ preloads every speaker avatar by creating new Image() instances. For large events this can trigger hundreds/thousands of network requests and significantly impact page performance/bandwidth. Consider limiting warming to above-the-fold/visible cards, adding a hard cap, or disabling this by default (or gating behind a config option).
| function sessionize_register_blocks() { | ||
| $blocks_dir = __DIR__ . '/blocks'; | ||
|
|
||
| register_block_type( $blocks_dir . '/sessionize-schedule' ); | ||
| register_block_type( $blocks_dir . '/sessionize-speakers' ); | ||
| } | ||
| add_action( 'init', 'sessionize_register_blocks' ); |
There was a problem hiding this comment.
The init function name sessionize_register_blocks is very generic and could collide with functions from other plugins/themes. Other plugins in this repo use a more specific prefix (e.g. cgb_block_*). Consider renaming to something plugin-scoped like sessionize_blocks_register_blocks and updating the add_action accordingly.
| function sessionize_register_blocks() { | |
| $blocks_dir = __DIR__ . '/blocks'; | |
| register_block_type( $blocks_dir . '/sessionize-schedule' ); | |
| register_block_type( $blocks_dir . '/sessionize-speakers' ); | |
| } | |
| add_action( 'init', 'sessionize_register_blocks' ); | |
| function sessionize_blocks_register_blocks() { | |
| $blocks_dir = __DIR__ . '/blocks'; | |
| register_block_type( $blocks_dir . '/sessionize-schedule' ); | |
| register_block_type( $blocks_dir . '/sessionize-speakers' ); | |
| } | |
| add_action( 'init', 'sessionize_blocks_register_blocks' ); |
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
… slashes Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 31 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $speaker_config = array( | ||
| 'sessionizeAllDataUrl' => 'https://sessionize.com/api/v2/' . esc_attr( $attributes['apiCode'] ) . '/view/All', | ||
| 'scheduleBaseUrl' => esc_url( $attributes['scheduleBaseUrl'] ), | ||
|
|
There was a problem hiding this comment.
The config values are being escaped (esc_attr, esc_url) before being JSON-encoded and placed into a data-* attribute. This can double-escape values (e.g. & becoming &) and break URLs/strings when parsed by JS. Prefer sanitizing (e.g. sanitize_text_field / esc_url_raw) when building the config array, and only escaping once at output time with esc_attr( wp_json_encode(...) ).
| document.documentElement.style.setProperty( '--sz-purple', theme ); | ||
| document.documentElement.style.setProperty( '--sz-purple-2', theme ); |
There was a problem hiding this comment.
This block sets --sz-* CSS variables on document.documentElement, which affects the entire page and can cause conflicts if multiple Sessionize blocks are present or if other components reuse similar variables. Consider scoping these variables to the block root (and to the moved modal element) instead of the global <html> element.
| document.documentElement.style.setProperty( '--sz-purple', theme ); | |
| document.documentElement.style.setProperty( '--sz-purple-2', theme ); | |
| root.style.setProperty( '--sz-purple', theme ); | |
| root.style.setProperty( '--sz-purple-2', theme ); |
| :root { | ||
| --sz-purple: #7a1f52; | ||
| --sz-purple-2: #8b2a62; | ||
| --sz-card: #ffffff; | ||
| --sz-text: #121212; | ||
| --sz-muted: #4b4b4b; | ||
| --sz-radius: 18px; | ||
| --sz-card-radius: 14px; | ||
| --sz-modal-top-offset: 18px; | ||
| --sz-vvh: 100vh; | ||
| --sz-vvtop: 0px; | ||
| --sz-mobile-h: 100dvh; | ||
| } |
There was a problem hiding this comment.
The CSS custom properties are defined on :root, so they apply globally across the site and can unintentionally affect other blocks/pages (and be affected by other CSS). Prefer scoping these variables to .sz-speakers-wrap (or another block-specific root) and rely on cascading for the modal by setting the same vars on the modal/root when opened.
| document.addEventListener( 'keydown', ( e ) => { | ||
| if ( modal.getAttribute( 'aria-hidden' ) === 'true' ) { | ||
| return; | ||
| } | ||
| if ( e.key === 'Escape' ) { | ||
| e.preventDefault(); | ||
| closeModal_(); | ||
| } | ||
| } ); |
There was a problem hiding this comment.
wireModalControls_ adds a document-level keydown listener every time the block initializes. If multiple speaker blocks are on the page, Escape handling will be registered multiple times and all handlers will run. Consider registering a single shared listener (or namespacing/removing listeners on teardown) and scoping it so only the active modal instance handles Escape.
| 'sessionizeAllDataUrl' => 'https://sessionize.com/api/v2/' . esc_attr( $attributes['apiCode'] ) . '/view/All', | ||
| 'sessionizeGridDataUrl' => 'https://sessionize.com/api/v2/' . esc_attr( $attributes['apiCode'] ) . '/view/GridSmart', | ||
| 'sessionizeApiCode' => esc_attr( $attributes['apiCode'] ), | ||
| 'sessionizePublicSlug' => esc_attr( $attributes['publicSlug'] ), | ||
|
|
||
| 'primaryFilterTitle' => esc_attr( $attributes['primaryFilterTitle'] ), | ||
| 'timeFormat' => esc_attr( $attributes['timeFormat'] ), | ||
| 'dateFormat' => esc_attr( $attributes['dateFormat'] ), |
There was a problem hiding this comment.
Like the speakers block, the schedule config values are escaped with esc_attr(...) before JSON encoding. That can double-escape/alter values (notably publicSlug, filter titles, etc.) when the JS reads them. Prefer sanitizing raw values for storage in the config array and escaping only once at output time.
| 'sessionizeAllDataUrl' => 'https://sessionize.com/api/v2/' . esc_attr( $attributes['apiCode'] ) . '/view/All', | |
| 'sessionizeGridDataUrl' => 'https://sessionize.com/api/v2/' . esc_attr( $attributes['apiCode'] ) . '/view/GridSmart', | |
| 'sessionizeApiCode' => esc_attr( $attributes['apiCode'] ), | |
| 'sessionizePublicSlug' => esc_attr( $attributes['publicSlug'] ), | |
| 'primaryFilterTitle' => esc_attr( $attributes['primaryFilterTitle'] ), | |
| 'timeFormat' => esc_attr( $attributes['timeFormat'] ), | |
| 'dateFormat' => esc_attr( $attributes['dateFormat'] ), | |
| 'sessionizeAllDataUrl' => 'https://sessionize.com/api/v2/' . sanitize_text_field( $attributes['apiCode'] ) . '/view/All', | |
| 'sessionizeGridDataUrl' => 'https://sessionize.com/api/v2/' . sanitize_text_field( $attributes['apiCode'] ) . '/view/GridSmart', | |
| 'sessionizeApiCode' => sanitize_text_field( $attributes['apiCode'] ), | |
| 'sessionizePublicSlug' => sanitize_text_field( $attributes['publicSlug'] ), | |
| 'primaryFilterTitle' => sanitize_text_field( $attributes['primaryFilterTitle'] ), | |
| 'timeFormat' => sanitize_text_field( $attributes['timeFormat'] ), | |
| 'dateFormat' => sanitize_text_field( $attributes['dateFormat'] ), |
| const a = document.createElement( 'a' ); | ||
| a.className = 'sz-modal__link'; | ||
| a.href = String( link.url || '' ).trim(); | ||
| a.target = '_blank'; | ||
| a.rel = 'noopener'; | ||
| a.setAttribute( 'aria-label', label ); |
There was a problem hiding this comment.
link.url from the Sessionize API is assigned directly to a.href. If the API data contains a javascript: (or other unsafe) URL, this becomes an XSS vector when clicked. Validate/normalize URLs before assigning (e.g., allow only http/https/mailto, otherwise skip the link), and consider using rel="noopener noreferrer" for external links opened with _blank.
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 31 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
…er responsiveness - Changed modal height and max-height from var(--sz-vvh) to 100dvh in style-index.css and style.css - Ensured consistent modal sizing across different screen sizes and orientations Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 31 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 30 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| document.documentElement.style.setProperty( '--sz-purple', theme ); | ||
| document.documentElement.style.setProperty( '--sz-purple-2', theme ); |
There was a problem hiding this comment.
This block sets CSS variables on document.documentElement (--sz-purple, --sz-purple-2), which makes the styling global. If multiple speaker blocks with different themes are on the same page, the last-initialized block will override the variables for all blocks. Consider scoping these variables to the block root element (e.g., root.style.setProperty(...)) and updating the CSS to read vars from .sz-speakers-wrap rather than :root.
| document.documentElement.style.setProperty( '--sz-purple', theme ); | |
| document.documentElement.style.setProperty( '--sz-purple-2', theme ); | |
| root.style.setProperty( '--sz-purple', theme ); | |
| root.style.setProperty( '--sz-purple-2', theme ); |
| const a = document.createElement( 'a' ); | ||
| a.className = 'sz-modal__link'; | ||
| a.href = String( link.url || '' ).trim(); | ||
| a.target = '_blank'; | ||
| a.rel = 'noopener'; | ||
| a.setAttribute( 'aria-label', label ); | ||
| a.title = label; | ||
| a.innerHTML = '<span class="sz-modal__linkicon" aria-hidden="true">' + icon + '</span>'; |
There was a problem hiding this comment.
The speaker social link href is set directly from Sessionize data. If a link URL is javascript:/data: (or otherwise malformed), clicking it becomes an XSS/vector. Validate/normalize the URL and only allow safe protocols (e.g., http:/https:) before assigning to a.href. Also consider using rel="noopener noreferrer" when target="_blank".
| "attributes": { | ||
| "apiCode": { | ||
| "type": "string", | ||
| "default": "pc6leesj" |
There was a problem hiding this comment.
The default apiCode causes the block to immediately fetch data from a specific external Sessionize event (pc6leesj) even if the editor hasn't configured anything. For production, it’s safer to default this to an empty string and show an editor-side placeholder instead, to avoid unintended external requests/content.
| "default": "pc6leesj" | |
| "default": "" |
| "attributes": { | ||
| "apiCode": { | ||
| "type": "string", | ||
| "default": "pc6leesj" |
There was a problem hiding this comment.
The default apiCode is set to pc6leesj, which will trigger external Sessionize fetches by default and may surface unrelated content if someone inserts the block without configuring it. Consider defaulting apiCode to an empty string and requiring explicit configuration to avoid unintended external dependencies/requests.
| "default": "pc6leesj" | |
| "default": "" |
| function sessionUrl_( sessionId ) { | ||
| const base = String( SPEAKER_CONFIG.scheduleBaseUrl || '' ).trim(); | ||
| if ( ! base ) { | ||
| return '#'; | ||
| } | ||
| const normalizedBase = base.replace( /\/?$/, '/' ); | ||
| try { | ||
| const url = new URL( normalizedBase ); | ||
| url.search = ''; | ||
| url.searchParams.set( 'id', String( sessionId || '' ).trim() ); | ||
| return url.toString(); | ||
| } catch ( _ ) { | ||
| return '#'; | ||
| } | ||
| } |
There was a problem hiding this comment.
The editor help text says leaving scheduleBaseUrl blank disables session links, but sessionUrl_() returns '#' when the base URL is empty/invalid. This still renders clickable <a href="#"> links that will jump the page to the top. Consider returning an empty string/null and conditionally rendering a non-link title when links are disabled.
Dev example of schedule
Dev example of speakers