diff --git a/assets/src/css/youzify-media-component.scss b/assets/src/css/youzify-media-component.scss new file mode 100644 index 000000000..61dc2a25e --- /dev/null +++ b/assets/src/css/youzify-media-component.scss @@ -0,0 +1,196 @@ +// Video Popup Modal Styles +.godam-video-popup-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 999999; + display: flex; + align-items: center; + justify-content: center; + animation: fadeIn 0.3s ease; +} + +.godam-video-popup-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.85); + backdrop-filter: blur(4px); +} + +.godam-video-popup-content { + position: relative; + width: 90%; + max-width: 880px; + max-height: 90vh; + z-index: 1; + animation: slideUp 0.3s ease; +} + +.godam-video-popup-close { + position: absolute; + top: -40px; + right: 0; + background: rgba(255, 255, 255, 0.2); + border: none; + border-radius: 50%; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + z-index: 2; + + svg { + width: 20px; + height: 20px; + fill: #fff; + } + + &:hover { + background: rgba(255, 255, 255, 0.3); + transform: rotate(90deg); + } +} + +.godam-video-popup-player { + position: relative; + width: 100%; + + figure { + margin: 0; + } + + .godam-video-wrapper { + width: 100%; + } +} + +// Prevent body scroll when popup is open +body.godam-video-popup-open { + overflow: hidden; +} + +// Animations +@keyframes fadeIn { + + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes slideUp { + + from { + opacity: 0; + transform: translateY(30px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +// Responsive adjustments +@media (max-width: 768px) { + + .godam-video-popup-content { + width: 95%; + max-height: 80vh; + } + + .godam-video-popup-close { + top: -35px; + width: 32px; + height: 32px; + } +} + +// Youzify Media Item Video Styles +.youzify-media-item-godam-video { + position: relative; + container-name: youzify-media-item-godam-video; + container-type: inline-size; + + &--tools { + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + display: flex; + justify-content: center; + align-items: center; + gap: 10px; + z-index: 9999; + + a { + color: #fff; + width: 45px; + height: 45px; + text-align: center; + border-radius: 100%; + background: rgba(0, 0, 0, .3); + display: inline-flex; + justify-content: center; + align-items: center; + } + + svg { + width: 20px; + height: 20px; + fill: #fff; + } + } + + &--play-button { + cursor: pointer; + transition: transform 0.2s ease; + + &:hover i { + transform: scale(1.1); + } + } + + .video-js .vjs-control-bar { + display: none; + } + + figure, + :where(figure) { + margin: 0 !important; + } + + .godam-video-wrapper { + + .vjs-big-play-button { + display: none; + } + } +} + +@container youzify-media-item-godam-video (max-width: 120px) { + + .youzify-media-item-godam-video--tools a { + + width: 20px; + height: 20px; + + svg { + width: 16px; + height: 16px; + } + } +} \ No newline at end of file diff --git a/assets/src/js/youzify-activity-observer.js b/assets/src/js/youzify-activity-observer.js new file mode 100644 index 000000000..d65980250 --- /dev/null +++ b/assets/src/js/youzify-activity-observer.js @@ -0,0 +1,69 @@ +( function( window, document ) { + if ( window.godamYouzifyObserverInitialized ) { + return; + } + window.godamYouzifyObserverInitialized = true; + + const boot = function() { + const hasMutationObserver = 'MutationObserver' in window; + + const initializePlayer = function( container ) { + if ( ! container || container.dataset.godamPlayerBootstrapped === '1' ) { + return; + } + + const video = container.querySelector( '.easydam-player.video-js' ); + if ( ! video || typeof window.GODAMPlayer !== 'function' ) { + return; + } + + container.dataset.godamPlayerBootstrapped = '1'; + window.GODAMPlayer( container ); + }; + + const discoverContainers = function( root ) { + if ( ! root || typeof root.querySelectorAll !== 'function' ) { + return; + } + + root.querySelectorAll( '.easydam-video-container' ).forEach( initializePlayer ); + }; + + // Initialize existing containers on page load + discoverContainers( document ); + + // If MutationObserver is not available, we've already initialized existing containers + if ( ! hasMutationObserver ) { + return; + } + + // Watch for new containers being added to the DOM + const mutationObserver = new MutationObserver( ( mutationList ) => { + mutationList.forEach( ( mutation ) => { + mutation.addedNodes.forEach( ( node ) => { + if ( ! ( node instanceof HTMLElement ) ) { + return; + } + + // If the added node itself is a video container, initialize it + if ( node.classList.contains( 'easydam-video-container' ) ) { + initializePlayer( node ); + } + + // Also check for containers within the added node + discoverContainers( node ); + } ); + } ); + } ); + + if ( document.body ) { + mutationObserver.observe( document.body, { childList: true, subtree: true } ); + } + }; + + if ( document.readyState === 'loading' ) { + document.addEventListener( 'DOMContentLoaded', boot ); + } else { + boot(); + } +}( window, document ) ); diff --git a/assets/src/js/youzify-media-page.js b/assets/src/js/youzify-media-page.js new file mode 100644 index 000000000..6560da01d --- /dev/null +++ b/assets/src/js/youzify-media-page.js @@ -0,0 +1,211 @@ +document.addEventListener( 'DOMContentLoaded', function() { + // Store processed items to avoid duplicate processing + const processedItems = new WeakSet(); + + /** + * Process a single media item + * + * @param {HTMLElement} item The media item element + */ + function processMediaItem( item ) { + // Skip if already processed + if ( processedItems.has( item ) ) { + return; + } + + const videoWrapper = item.querySelector( 'div.youzify-media-item-godam-video' ); + + if ( ! videoWrapper ) { + return; + } + + // Mark as processed + processedItems.add( item ); + + const contentEl = item.querySelector( '.youzify-media-item-content' ); + + if ( contentEl ) { + contentEl.style.display = 'none'; + } + + const sourceLink = contentEl ? contentEl.querySelector( '.youzify-media-post-link' ) : null; + const sourceAnchor = sourceLink ? sourceLink.closest( 'a' ) || sourceLink : null; + const href = sourceAnchor ? sourceAnchor.getAttribute( 'href' ) : null; + + if ( href ) { + const targetLink = videoWrapper.querySelector( '.youzify-media-post-link' ); + const targetAnchor = targetLink ? targetLink.closest( 'a' ) || targetLink : null; + + if ( targetAnchor ) { + targetAnchor.setAttribute( 'href', href ); + } + } + + // Add click event listener to play button + const playButton = videoWrapper.querySelector( '.youzify-media-item-godam-video--play-button' ); + if ( playButton ) { + playButton.addEventListener( 'click', function( e ) { + e.preventDefault(); + openVideoPopup( videoWrapper ); + } ); + } + } + + // Process existing media items on page load + const existingMediaItems = document.querySelectorAll( '.youzify-media-item' ); + existingMediaItems.forEach( processMediaItem ); + + // Set up MutationObserver to watch for dynamically added media items + const observer = new MutationObserver( function( mutations ) { + mutations.forEach( function( mutation ) { + // Check added nodes + mutation.addedNodes.forEach( function( node ) { + // Skip non-element nodes + if ( node.nodeType !== Node.ELEMENT_NODE ) { + return; + } + + // Check if the added node itself is a media item + if ( node.classList && node.classList.contains( 'youzify-media-item' ) ) { + processMediaItem( node ); + } + + // Check for media items within the added node + if ( node.querySelectorAll ) { + const mediaItems = node.querySelectorAll( '.youzify-media-item' ); + mediaItems.forEach( processMediaItem ); + } + } ); + } ); + } ); + + // Start observing the document body for changes + observer.observe( document.body, { + childList: true, + subtree: true, + } ); + + /** + * Open video player in popup modal + * + * @param {HTMLElement} videoWrapper The video wrapper element + * + * @return {void} + */ + function openVideoPopup( videoWrapper ) { + // Get the figure element (GoDAM player container) + const figureElement = videoWrapper.querySelector( 'figure[id^="godam-player-container-"]' ); + + if ( ! figureElement ) { + return; + } + + // Extract media ID from videoWrapper ID (format: godam-video-{media_id}) + const mediaId = videoWrapper.id.replace( 'godam-video-', '' ); + + // Store the original parent and next sibling for restoration + const originalParent = figureElement.parentNode; + const originalNextSibling = figureElement.nextSibling; + + // Create modal overlay + const modal = document.createElement( 'div' ); + modal.className = 'godam-video-popup-modal'; + modal.innerHTML = ` +
+
+ +
+
+ `; + + // Get player container and move the figure element into it + const playerContainer = modal.querySelector( '.godam-video-popup-player' ); + playerContainer.appendChild( figureElement ); + + // Append modal to body + document.body.appendChild( modal ); + document.body.classList.add( 'godam-video-popup-open' ); + + // Get the GoDAM player instance + let player = null; + + if ( window.GoDAMAPI && mediaId ) { + try { + // Get the video element reference + const videoElement = figureElement.querySelector( 'video' ); + player = window.GoDAMAPI.getPlayer( mediaId, videoElement ); + + if ( player ) { + // Show controls and attempt auto-play + player.controls( true ); + + const playPromise = player.play(); + + if ( playPromise !== undefined ) { + playPromise.catch( function() { + // Auto-play was prevented + } ); + } + } + } catch ( error ) { + // Error getting player instance + // This may occur if the GoDAM player API is not available (e.g., not loaded), + // or if no player instance is found for the given mediaId. + // No further action is taken here as the modal will still be displayed, allowing the user to close it. + } + } + + // Close modal handlers + const closeButton = modal.querySelector( '.godam-video-popup-close' ); + const overlay = modal.querySelector( '.godam-video-popup-overlay' ); + + // Close on Escape key + function escapeHandler( e ) { + if ( e.key === 'Escape' ) { + closeModal(); + } + } + + function closeModal() { + // Remove escape key handler first + document.removeEventListener( 'keydown', escapeHandler ); + + // Stop the video if playing using GoDAMAPI + if ( window.GoDAMAPI && mediaId ) { + try { + // Get the video element reference + const videoElement = figureElement.querySelector( 'video' ); + const godamPlayer = window.GoDAMAPI.getPlayer( mediaId, videoElement ); + if ( godamPlayer ) { + godamPlayer.pause(); + godamPlayer.currentTime( 0 ); + } + } catch ( error ) { + // Error stopping player + // eslint-disable-next-line no-console + console.error( 'Error stopping GoDAM player instance:', error ); + } + } + + // Move the figure element back to its original position + if ( originalNextSibling ) { + originalParent.insertBefore( figureElement, originalNextSibling ); + } else { + originalParent.appendChild( figureElement ); + } + + // Remove modal + document.body.classList.remove( 'godam-video-popup-open' ); + modal.remove(); + } + + document.addEventListener( 'keydown', escapeHandler ); + closeButton.addEventListener( 'click', closeModal ); + overlay.addEventListener( 'click', closeModal ); + } +} ); + diff --git a/inc/classes/class-plugin.php b/inc/classes/class-plugin.php index b713b9124..1f1753b75 100644 --- a/inc/classes/class-plugin.php +++ b/inc/classes/class-plugin.php @@ -63,6 +63,7 @@ use RTGODAM\Inc\Metform\Metform_Integration; use RTGODAM\Inc\Metform\Metform_Rest_Api; use RTGODAM\Inc\Lifter_LMS\Lifter_LMS; +use RTGODAM\Inc\Youzify\Youzify; /** * Class Plugin. @@ -121,6 +122,9 @@ protected function __construct() { // Load Elementor widgets. $this->load_elementor_widgets(); + // Load Youzify integration. + $this->load_youzify_integration(); + $this->load_media_library(); } @@ -225,4 +229,15 @@ public function load_sureforms() { public function load_fluentforms() { \RTGODAM\Inc\FluentForms\Init::get_instance(); } + + /** + * Load Youzify integration. + * + * @since n.e.x.t + * + * @return void + */ + public function load_youzify_integration() { + Youzify::get_instance(); + } } diff --git a/inc/classes/youzify/class-youzify.php b/inc/classes/youzify/class-youzify.php new file mode 100644 index 000000000..bb535ab10 --- /dev/null +++ b/inc/classes/youzify/class-youzify.php @@ -0,0 +1,158 @@ +is_youzify_active() ) { + return; + } + $this->setup_hooks(); + } + + /** + * Setup WordPress hooks and filters. + */ + private function setup_hooks() { + add_filter( 'youzify_get_wall_post_video', array( $this, 'replace_youzify_wall_video_player' ), 10, 3 ); + + add_action( 'youzify_after_media_item', array( $this, 'add_youzify_godam_player' ), 10, 2 ); + + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_youzify_scripts' ) ); + } + + /** + * Replace Youzify wall video player with GoDAM player. + * + * @since n.e.x.t + * + * @param string $video_html Original Youzify video HTML. + * @param string $video_url Video URL. + * @param int $media_id Media ID. + * + * @return string Modified video HTML with GoDAM player. + */ + public function replace_youzify_wall_video_player( $video_html, $video_url, $media_id = 0 ) { + $media_id = intval( $media_id ); + + if ( empty( $media_id ) ) { + return $video_html; + } + + return do_shortcode( '[godam_video id="' . esc_attr( $media_id ) . '" aspectRatio="16:9"]' ); + } + + /** + * Add GoDAM player after Youzify media item. + * + * @since n.e.x.t + * + * @param int $media_id Media ID. + * @param array $args Media arguments. + */ + public function add_youzify_godam_player( $media_id = 0, $args = array() ) { + $media_id = intval( $media_id ); + + if ( 'videos' === $args['type'] ) { + ?> +
+
+ + + + + + + +
+ +
+ 'godam-player-minimal-skin', + 'Pills' => 'godam-player-pills-skin', + 'Bubble' => 'godam-player-bubble-skin', + 'Classic' => 'godam-player-classic-skin', + ); + + if ( isset( $skins[ $selected_skin ] ) ) { + wp_enqueue_style( $skins[ $selected_skin ] ); + } +} diff --git a/webpack.config.js b/webpack.config.js index 0b820c9a8..203732bf6 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -223,6 +223,20 @@ const ninjaForms = { }, }; +const youzifyActivityObserver = { + ...sharedConfig, + entry: { + 'youzify-activity-observer': path.resolve( process.cwd(), 'assets', 'src', 'js', 'youzify-activity-observer.js' ), + }, +}; + +const youzifyMediaPage = { + ...sharedConfig, + entry: { + 'youzify-media-page': path.resolve( process.cwd(), 'assets', 'src', 'js', 'youzify-media-page.js' ), + }, +}; + // Define the `pages` directory const pagesDir = path.resolve( __dirname, './pages' ); @@ -311,4 +325,6 @@ module.exports = [ lifterLMSBlock, lifterLMSEmbed, ninjaForms, + youzifyActivityObserver, + youzifyMediaPage, ];