diff --git a/src/block/countdown/editor.scss b/src/block/countdown/editor.scss deleted file mode 100644 index 328439e10d..0000000000 --- a/src/block/countdown/editor.scss +++ /dev/null @@ -1,25 +0,0 @@ -.ugb-panel--countdown .components-datetime__time-wrapper { - .components-base-control { - margin-bottom: 0px !important; - } - - .components-datetime__timezone { - display: none; - } -} - -.ugb-panel--countdown .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/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 8cfc1b2a1c..bfd7c9084c 100644 --- a/src/block/video-popup/edit.js +++ b/src/block/video-popup/edit.js @@ -17,6 +17,8 @@ import { InspectorBottomTip, AdvancedToggleControl, useBlockCssGenerator, + ControlSeparator, + AdvancedSelectControl, } from '~stackable/components' import { BlockDiv, @@ -38,6 +40,8 @@ import { withQueryLoopContext, } from '~stackable/higher-order' +import { timezones as TIMEZONE_OPTIONS } from '../countdown' + /** * WordPress dependencies */ @@ -46,6 +50,8 @@ import { InnerBlocks } from '@wordpress/block-editor' import { __ } from '@wordpress/i18n' import { addFilter } from '@wordpress/hooks' import { memo } from '@wordpress/element' +import { DateTimePicker } from '@wordpress/components' +import { getSettings as getDateSettings } from '@wordpress/date' export const defaultIcon = '' @@ -104,7 +110,10 @@ const Edit = props => { setAttributes={ setAttributes } videoLink={ attributes.videoLink } videoId={ attributes.videoId } - + videoName={ attributes.videoName } + videoUploadDate={ attributes.videoUploadDate } + videoDescription={ attributes.videoDescription } + videoUploadDateTimezone={ attributes.videoUploadDateTimezone } /> { blockCss && } @@ -129,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 ( <> @@ -136,7 +167,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 +201,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 && <> + +

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

+ props.setAttributes( { videoName } ) } + /> + props.setAttributes( { videoDescription } ) } + isMultiline={ true } + /> + props.setAttributes( { videoUploadDate } ) } + inputType="date" + readOnly={ true } + /> + props.setAttributes( { videoUploadDate } ) } + /> + + } +
diff --git a/src/block/video-popup/editor.scss b/src/block/video-popup/editor.scss index 75ed884e18..68e9c60f61 100644 --- a/src/block/video-popup/editor.scss +++ b/src/block/video-popup/editor.scss @@ -28,3 +28,7 @@ .editor-styles-wrapper .stk-block-video-popup [data-block] { max-width: none; } + +.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..6b390d8de3 100644 --- a/src/block/video-popup/index.php +++ b/src/block/video-popup/index.php @@ -19,3 +19,83 @@ 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 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( + '@context' => 'https://schema.org', + '@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_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' ] : ''; + + 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' ] ) + && 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' ] + : ( isset( $image_attributes[ 'imageExternalUrl' ] ) ? $image_attributes[ 'imageExternalUrl' ] : '' ); + $video_schema[ 'thumbnailUrl' ] = esc_url( $thumbnail_url ); + } + + $video_schema_json = wp_json_encode( $video_schema, 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..61fb9031d6 100644 --- a/src/block/video-popup/schema.js +++ b/src/block/video-popup/schema.js @@ -62,6 +62,22 @@ export const attributes = ( version = VERSION ) => { type: 'boolean', default: false, }, + videoName: { + type: 'string', + default: '', + }, + videoDescription: { + type: 'string', + default: '', + }, + videoUploadDate: { + type: 'string', + default: '', + }, + videoUploadDateTimezone: { + type: 'string', + default: '', + }, }, versionAdded: '3.0.0', versionDeprecated: '', 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; + } + } + } +}