From 80cec9744984ae303c8fb510d3110ef45eb47a73 Mon Sep 17 00:00:00 2001 From: Mikhaela Tapia Date: Wed, 26 Mar 2025 00:12:57 +0800 Subject: [PATCH 1/5] add video schema --- src/block/video-popup/edit.js | 58 +++++++++++++++++++++++++++---- src/block/video-popup/editor.scss | 18 ++++++++++ src/block/video-popup/index.php | 57 ++++++++++++++++++++++++++++++ src/block/video-popup/schema.js | 12 +++++++ 4 files changed, 139 insertions(+), 6 deletions(-) diff --git a/src/block/video-popup/edit.js b/src/block/video-popup/edit.js index 8cfc1b2a1c..c7c554dac1 100644 --- a/src/block/video-popup/edit.js +++ b/src/block/video-popup/edit.js @@ -17,6 +17,7 @@ import { InspectorBottomTip, AdvancedToggleControl, useBlockCssGenerator, + ControlSeparator, } from '~stackable/components' import { BlockDiv, @@ -46,6 +47,7 @@ import { InnerBlocks } from '@wordpress/block-editor' import { __ } from '@wordpress/i18n' import { addFilter } from '@wordpress/hooks' import { memo } from '@wordpress/element' +import { DatePicker } from '@wordpress/components' export const defaultIcon = '' @@ -104,7 +106,9 @@ const Edit = props => { setAttributes={ setAttributes } videoLink={ attributes.videoLink } videoId={ attributes.videoId } - + videoName={ attributes.videoName } + videoUploadDate={ attributes.videoUploadDate } + videoDescription={ attributes.videoDescription } /> { blockCss && } @@ -136,7 +140,7 @@ const InspectorControls = memo( props => { { onRemove={ () => props.setAttributes( { videoLink: '', videoId: '', + videoName: '', + videoDescription: '', + videoUploadDate: '', } ) } onChange={ media => { props.setAttributes( { videoLink: media.url, videoId: media.url, + videoName: media.title, // Use title, description and date from media library for video schema + videoDescription: media.description, + videoUploadDate: media.date.toISOString(), } ) } } imageId={ urlIsVideo( props.videoLink ) ? props.videoId : '' } @@ -164,10 +174,16 @@ const InspectorControls = memo( props => { isFormatType={ false } placeholder="https://" value={ ! urlIsVideo( props.videoLink ) ? props.videoLink : '' } - onChange={ videoLink => props.setAttributes( { - videoLink, - videoId: getVideoProviderFromURL( videoLink ).id, - } ) } + onChange={ videoLink => { + const videoProvider = getVideoProviderFromURL( videoLink ) + props.setAttributes( { + videoLink, + videoId: videoProvider.id, + videoName: '', + videoDescription: '', + videoUploadDate: '', + } ) + } } /> { isVideoFile( props.videoLink ) && <> { defaultValue={ false } /> } + { props.videoLink && <> + + props.setAttributes( { videoName } ) } + /> + props.setAttributes( { videoDescription } ) } + isMultiline={ true } + /> + + { + props.setAttributes( { videoUploadDate } ) + } } + /> + } + diff --git a/src/block/video-popup/editor.scss b/src/block/video-popup/editor.scss index 75ed884e18..cfdee284f3 100644 --- a/src/block/video-popup/editor.scss +++ b/src/block/video-popup/editor.scss @@ -28,3 +28,21 @@ .editor-styles-wrapper .stk-block-video-popup [data-block] { max-width: none; } +.ugb-panel--video-popup .components-datetime__date { + .components-datetime__date__day[aria-label*="Selected" i] { + color: #fff !important; + &:hover { + color: #fff !important; + } + } + :last-child { + row-gap: 8px; + column-gap: 4px; + div:nth-of-type(7) { + justify-self: auto; + } + } +} +.stk-components-datetime__date-input .components-text-control__input { + background-color: #fff; +} diff --git a/src/block/video-popup/index.php b/src/block/video-popup/index.php index bad29d62a6..8618e0bdf7 100644 --- a/src/block/video-popup/index.php +++ b/src/block/video-popup/index.php @@ -19,3 +19,60 @@ function stackable_load_videopopup_frontend_script() { } add_action( 'stackable/video-popup/enqueue_scripts', 'stackable_load_videopopup_frontend_script' ); } + +if ( ! class_exists( 'Stackable_Video_Popup_Schema' ) ) { + class Stackable_Video_Popup_Schema { + public $video_entities = []; + + function __construct() { + add_filter( 'render_block_stackable/video-popup', array( $this, 'render_block_video_popup_schema' ), 10, 2 ); + add_filter( 'wp_footer', array( $this, 'print_video_popup_schema' ) ); + } + + public function print_video_popup_schema() { + if ( count( $this->video_entities ) ) { + // Compile all video schema entities into a single script + echo ''; + } + } + + public function render_block_video_popup_schema( $block_content, $block ) { + // Initialize video schema + $video_schema = array(); + $video_schema[ '@context' ] = 'https://schema.org'; + $video_schema[ '@type' ] = 'VideoObject'; + + // Get video schema properties from block attributes + $attributes = $block[ 'attrs' ]; + + // Get video name from the title of the post if not set + $name = isset( $attributes[ 'videoName' ] ) ? $attributes[ 'videoName' ] : ( get_the_title() ?? ''); + // Get video upload date from the date of the post if not set + $upload_date = isset( $attributes[ 'videoUploadDate' ] ) ? $attributes[ 'videoUploadDate' ] : ( get_the_date( 'c' ) || ''); + $description = isset( $attributes[ 'videoDescription' ] ) ? $attributes[ 'videoDescription' ] : ''; + $content_url = isset( $attributes[ 'videoLink' ] ) ? $attributes[ 'videoLink' ] : ''; + + $video_schema[ 'name' ] = $name; + $video_schema[ 'description' ] = $description; + $video_schema[ 'uploadDate' ] = $upload_date; + $video_schema[ 'contentUrl' ] = $content_url; + + // Get thumbnail URL from the image block if it exists + if ( isset( $block[ 'innerBlocks' ] ) + && count( $block[ 'innerBlocks' ] ) === 2 + && $block[ 'innerBlocks' ][ 1 ][ 'blockName' ] === 'stackable/image' + ) { + $image_attributes = $block[ 'innerBlocks' ][ 1 ][ 'attrs' ]; + $thumbnail_url = isset( $image_attributes[ 'imageUrl' ] ) ? $image_attributes[ 'imageUrl' ] : ''; + $video_schema[ 'thumbnailUrl' ] = $thumbnail_url; + } + + $video_schema_json = json_encode( $video_schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); + $this->video_entities[] = $video_schema_json; + + return $block_content; + } + } + + new Stackable_Video_Popup_Schema(); +} \ No newline at end of file diff --git a/src/block/video-popup/schema.js b/src/block/video-popup/schema.js index 66798508d0..f78d409fda 100644 --- a/src/block/video-popup/schema.js +++ b/src/block/video-popup/schema.js @@ -62,6 +62,18 @@ export const attributes = ( version = VERSION ) => { type: 'boolean', default: false, }, + videoName: { + type: 'string', + default: '', + }, + videoDescription: { + type: 'string', + default: '', + }, + videoUploadDate: { + type: 'string', + default: '', + }, }, versionAdded: '3.0.0', versionDeprecated: '', From 154adaf9be683706d4d8c10d5219360c4285a9e3 Mon Sep 17 00:00:00 2001 From: Mikhaela Tapia Date: Wed, 26 Mar 2025 00:38:38 +0800 Subject: [PATCH 2/5] add note --- src/block/video-popup/edit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/block/video-popup/edit.js b/src/block/video-popup/edit.js index c7c554dac1..61a5f34812 100644 --- a/src/block/video-popup/edit.js +++ b/src/block/video-popup/edit.js @@ -204,6 +204,7 @@ const InspectorControls = memo( props => { } { props.videoLink && <> +

{ __( 'Note: The following attributes are used to generate the video schema.', i18n ) }

Date: Wed, 26 Mar 2025 00:39:39 +0800 Subject: [PATCH 3/5] update note --- src/block/video-popup/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/block/video-popup/edit.js b/src/block/video-popup/edit.js index 61a5f34812..cab85caf90 100644 --- a/src/block/video-popup/edit.js +++ b/src/block/video-popup/edit.js @@ -204,7 +204,7 @@ const InspectorControls = memo( props => { } { props.videoLink && <> -

{ __( 'Note: The following attributes are used to generate the video schema.', i18n ) }

+

{ __( 'Note: The following attributes are used to create the video schema.', i18n ) }

Date: Fri, 11 Apr 2025 14:03:26 +0800 Subject: [PATCH 4/5] added timezone, escaped attrs, add external thumbnail --- src/block/countdown/editor.scss | 4 +-- src/block/countdown/index.js | 2 ++ src/block/video-popup/edit.js | 45 ++++++++++++++++++++++++++----- src/block/video-popup/editor.scss | 16 +---------- src/block/video-popup/index.php | 45 +++++++++++++++++++++++-------- src/block/video-popup/schema.js | 4 +++ 6 files changed, 82 insertions(+), 34 deletions(-) diff --git a/src/block/countdown/editor.scss b/src/block/countdown/editor.scss index 328439e10d..52441e361d 100644 --- a/src/block/countdown/editor.scss +++ b/src/block/countdown/editor.scss @@ -1,4 +1,4 @@ -.ugb-panel--countdown .components-datetime__time-wrapper { +:is(.ugb-panel--countdown, .ugb-panel--video-popup) .components-datetime__time-wrapper { .components-base-control { margin-bottom: 0px !important; } @@ -8,7 +8,7 @@ } } -.ugb-panel--countdown .components-datetime__date { +:is(.ugb-panel--countdown, .ugb-panel--video-popup) .components-datetime__date { .components-datetime__date__day[aria-label*="Selected" i] { color: #fff; &:hover { diff --git a/src/block/countdown/index.js b/src/block/countdown/index.js index 76dc08a0c1..35d2acecfa 100644 --- a/src/block/countdown/index.js +++ b/src/block/countdown/index.js @@ -17,6 +17,8 @@ import metadata from './block.json' import deprecated from './deprecated' import substitute from './substitute' +export { timezones } from './timezones' + export const settings = { ...metadata, icon: CountdownIcon, diff --git a/src/block/video-popup/edit.js b/src/block/video-popup/edit.js index cab85caf90..bfd7c9084c 100644 --- a/src/block/video-popup/edit.js +++ b/src/block/video-popup/edit.js @@ -18,6 +18,7 @@ import { AdvancedToggleControl, useBlockCssGenerator, ControlSeparator, + AdvancedSelectControl, } from '~stackable/components' import { BlockDiv, @@ -39,6 +40,8 @@ import { withQueryLoopContext, } from '~stackable/higher-order' +import { timezones as TIMEZONE_OPTIONS } from '../countdown' + /** * WordPress dependencies */ @@ -47,7 +50,8 @@ import { InnerBlocks } from '@wordpress/block-editor' import { __ } from '@wordpress/i18n' import { addFilter } from '@wordpress/hooks' import { memo } from '@wordpress/element' -import { DatePicker } from '@wordpress/components' +import { DateTimePicker } from '@wordpress/components' +import { getSettings as getDateSettings } from '@wordpress/date' export const defaultIcon = '' @@ -109,6 +113,7 @@ const Edit = props => { videoName={ attributes.videoName } videoUploadDate={ attributes.videoUploadDate } videoDescription={ attributes.videoDescription } + videoUploadDateTimezone={ attributes.videoUploadDateTimezone } /> { blockCss && } @@ -133,6 +138,28 @@ const Edit = props => { } const InspectorControls = memo( props => { + const getUploadDate = ( uploadDate, timezone ) => { + // If it uses local timezone, get offset from WordPress settings + if ( ! timezone ) { + const { timezone: localTimezone } = getDateSettings() + const offset = Number( localTimezone.offset ) + const hours = Math.floor( Math.abs( offset ) ) + const minutes = Math.round( ( Math.abs( offset ) % 1 ) * 60 ) + + return uploadDate + ( offset >= 0 ? '+' : '-' ) + + String( hours ).padStart( 2, '0' ) + ':' + + String( minutes ).padStart( 2, '0' ) + } + + const date = new Date( uploadDate ) + const offset = new Intl.DateTimeFormat( 'en-US', { + timeZone: timezone, + timeZoneName: 'longOffset', + } ).format( date ).slice( -6 ) + + return uploadDate + offset + } + return ( <> @@ -221,15 +248,21 @@ const InspectorControls = memo( props => { // This text control allows users to see if a date has been set/removed className="stk-components-datetime__date-input" label={ __( 'Video upload date', i18n ) } - value={ props.videoUploadDate ? new Date( props.videoUploadDate ).toISOString().slice( 0, 10 ) : '' } + value={ props.videoUploadDate ? getUploadDate( props.videoUploadDate, props.videoUploadDateTimezone ) : '' } + onChange={ videoUploadDate => props.setAttributes( { videoUploadDate } ) } inputType="date" readOnly={ true } /> - { - props.setAttributes( { videoUploadDate } ) - } } + is12Hour={ true } + onChange={ videoUploadDate => props.setAttributes( { videoUploadDate } ) } + /> + } diff --git a/src/block/video-popup/editor.scss b/src/block/video-popup/editor.scss index cfdee284f3..68e9c60f61 100644 --- a/src/block/video-popup/editor.scss +++ b/src/block/video-popup/editor.scss @@ -28,21 +28,7 @@ .editor-styles-wrapper .stk-block-video-popup [data-block] { max-width: none; } -.ugb-panel--video-popup .components-datetime__date { - .components-datetime__date__day[aria-label*="Selected" i] { - color: #fff !important; - &:hover { - color: #fff !important; - } - } - :last-child { - row-gap: 8px; - column-gap: 4px; - div:nth-of-type(7) { - justify-self: auto; - } - } -} + .stk-components-datetime__date-input .components-text-control__input { background-color: #fff; } diff --git a/src/block/video-popup/index.php b/src/block/video-popup/index.php index 8618e0bdf7..6b390d8de3 100644 --- a/src/block/video-popup/index.php +++ b/src/block/video-popup/index.php @@ -36,11 +36,30 @@ public function print_video_popup_schema() { } } + public function get_upload_date_timezone( $timezone_name ) { + // If it uses local timezone, get offset from WordPress settings + if ( ! $timezone_name ) { + $offset = (float) get_option( 'gmt_offset' ); + $hours = (int) $offset; + $minutes = ( $offset - $hours ); + $sign = ( $offset < 0 ) ? '-' : '+'; + $abs_hour = abs( $hours ); + $abs_mins = abs( $minutes * 60 ); + + return sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins ); + } + + $timezone = new DateTimeZone( $timezone_name ); + $datetime = new DateTime('now', $timezone); + return $datetime->format('P'); + } + public function render_block_video_popup_schema( $block_content, $block ) { // Initialize video schema - $video_schema = array(); - $video_schema[ '@context' ] = 'https://schema.org'; - $video_schema[ '@type' ] = 'VideoObject'; + $video_schema = array( + '@context' => 'https://schema.org', + '@type' => 'VideoObject' + ); // Get video schema properties from block attributes $attributes = $block[ 'attrs' ]; @@ -48,14 +67,17 @@ public function render_block_video_popup_schema( $block_content, $block ) { // Get video name from the title of the post if not set $name = isset( $attributes[ 'videoName' ] ) ? $attributes[ 'videoName' ] : ( get_the_title() ?? ''); // Get video upload date from the date of the post if not set - $upload_date = isset( $attributes[ 'videoUploadDate' ] ) ? $attributes[ 'videoUploadDate' ] : ( get_the_date( 'c' ) || ''); + $upload_date_timezone = $this->get_upload_date_timezone( isset( $attributes[ 'videoUploadDateTimezone' ] ) ? $attributes[ 'videoUploadDateTimezone' ] : false ); + $upload_date = isset( $attributes[ 'videoUploadDate' ] ) ? $attributes[ 'videoUploadDate' ] . $upload_date_timezone : ( get_the_date( 'c' ) ?? ''); $description = isset( $attributes[ 'videoDescription' ] ) ? $attributes[ 'videoDescription' ] : ''; $content_url = isset( $attributes[ 'videoLink' ] ) ? $attributes[ 'videoLink' ] : ''; - $video_schema[ 'name' ] = $name; - $video_schema[ 'description' ] = $description; - $video_schema[ 'uploadDate' ] = $upload_date; - $video_schema[ 'contentUrl' ] = $content_url; + error_log( $upload_date ); + + $video_schema[ 'name' ] = esc_attr( $name ); + $video_schema[ 'description' ] = esc_attr( $description ); + $video_schema[ 'uploadDate' ] = esc_attr( $upload_date ); + $video_schema[ 'contentUrl' ] = esc_url( $content_url ); // Get thumbnail URL from the image block if it exists if ( isset( $block[ 'innerBlocks' ] ) @@ -63,11 +85,12 @@ public function render_block_video_popup_schema( $block_content, $block ) { && $block[ 'innerBlocks' ][ 1 ][ 'blockName' ] === 'stackable/image' ) { $image_attributes = $block[ 'innerBlocks' ][ 1 ][ 'attrs' ]; - $thumbnail_url = isset( $image_attributes[ 'imageUrl' ] ) ? $image_attributes[ 'imageUrl' ] : ''; - $video_schema[ 'thumbnailUrl' ] = $thumbnail_url; + $thumbnail_url = isset( $image_attributes[ 'imageUrl' ] ) ? $image_attributes[ 'imageUrl' ] + : ( isset( $image_attributes[ 'imageExternalUrl' ] ) ? $image_attributes[ 'imageExternalUrl' ] : '' ); + $video_schema[ 'thumbnailUrl' ] = esc_url( $thumbnail_url ); } - $video_schema_json = json_encode( $video_schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); + $video_schema_json = wp_json_encode( $video_schema, JSON_UNESCAPED_SLASHES ); $this->video_entities[] = $video_schema_json; return $block_content; diff --git a/src/block/video-popup/schema.js b/src/block/video-popup/schema.js index f78d409fda..61fb9031d6 100644 --- a/src/block/video-popup/schema.js +++ b/src/block/video-popup/schema.js @@ -74,6 +74,10 @@ export const attributes = ( version = VERSION ) => { type: 'string', default: '', }, + videoUploadDateTimezone: { + type: 'string', + default: '', + }, }, versionAdded: '3.0.0', versionDeprecated: '', From 61fd5846d84f80de489553a4e92383b04cd20a1f Mon Sep 17 00:00:00 2001 From: Mikhaela Tapia Date: Tue, 15 Apr 2025 17:21:58 +0800 Subject: [PATCH 5/5] move datetime picker styles --- src/block/countdown/editor.scss | 25 ------------------------- src/editor.scss | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 25 deletions(-) delete mode 100644 src/block/countdown/editor.scss diff --git a/src/block/countdown/editor.scss b/src/block/countdown/editor.scss deleted file mode 100644 index 52441e361d..0000000000 --- a/src/block/countdown/editor.scss +++ /dev/null @@ -1,25 +0,0 @@ -:is(.ugb-panel--countdown, .ugb-panel--video-popup) .components-datetime__time-wrapper { - .components-base-control { - margin-bottom: 0px !important; - } - - .components-datetime__timezone { - display: none; - } -} - -:is(.ugb-panel--countdown, .ugb-panel--video-popup) .components-datetime__date { - .components-datetime__date__day[aria-label*="Selected" i] { - color: #fff; - &:hover { - color: #fff !important; - } - } - :last-child { - row-gap: 8px; - column-gap: 4px; - div:nth-of-type(7) { - justify-self: auto; - } - } -} diff --git a/src/editor.scss b/src/editor.scss index 512fd0fa24..9888366be8 100644 --- a/src/editor.scss +++ b/src/editor.scss @@ -178,3 +178,32 @@ $block-joined: #{ join($blocks, (), $separator: comma) }; padding: 2px; } } + +// Adjust the styles of †he datetime picker. +.ugb-toggle-panel-body { + .components-datetime__time-wrapper { + .components-base-control { + margin-bottom: 0px !important; + } + + .components-datetime__timezone { + display: none; + } + } + + .components-datetime__date { + .components-datetime__date__day[aria-label*="Selected" i] { + color: #fff; + &:hover { + color: #fff !important; + } + } + :last-child { + row-gap: 8px; + column-gap: 4px; + div:nth-of-type(7) { + justify-self: auto; + } + } + } +}