diff --git a/js&css/web-accessible/core.js b/js&css/web-accessible/core.js index 06a9a1c28..25a4e209a 100644 --- a/js&css/web-accessible/core.js +++ b/js&css/web-accessible/core.js @@ -507,6 +507,41 @@ document.addEventListener('it-message-from-extension', function () { ImprovedTube.disableAutoDubbing(); } break + case 'livechat': + if (this.storage.livechat === 'hidden') { + document.documentElement.setAttribute('it-livechat', 'hidden'); + } else if (this.storage.livechat === 'collapsed') { + document.documentElement.setAttribute('it-livechat', 'collapsed'); + } else { + document.documentElement.removeAttribute('it-livechat'); + } + break + case 'livechat_below_theater': + if (this.storage.livechat_below_theater === true) { + ImprovedTube.livechatBelowTheater(); + ImprovedTube.livechatTheaterModeObserver(); + } else { + // Clean up observer and restore chat position + if (ImprovedTube.livechatTheaterObserver) { + ImprovedTube.livechatTheaterObserver.disconnect(); + ImprovedTube.livechatTheaterObserver = null; + } + // Restore live chat to original position + const liveChatFrame = document.querySelector("ytd-live-chat-frame#chat"); + const secondaryInner = document.getElementById("secondary-inner"); + if (liveChatFrame && secondaryInner && liveChatFrame.parentNode !== secondaryInner) { + if (liveChatFrame.parentNode) { + liveChatFrame.parentNode.removeChild(liveChatFrame); + } + secondaryInner.appendChild(liveChatFrame); + // Reset styling + liveChatFrame.style.width = ""; + liveChatFrame.style.maxWidth = ""; + liveChatFrame.style.marginTop = ""; + liveChatFrame.style.marginBottom = ""; + } + } + break case 'returnYoutubeDislike': if (ImprovedTube.storage.return_youtube_dislike === true) { ImprovedTube.returnYoutubeDislike(); diff --git a/js&css/web-accessible/functions.js b/js&css/web-accessible/functions.js index 2326e4457..6eeec1ed0 100644 --- a/js&css/web-accessible/functions.js +++ b/js&css/web-accessible/functions.js @@ -345,6 +345,12 @@ ImprovedTube.videoPageUpdate = function () { ImprovedTube.playerCinemaModeButton(); ImprovedTube.playerHamburgerButton(); ImprovedTube.playerControls(); + + // Initialize live chat below theater functionality + if (this.storage.livechat_below_theater === true) { + ImprovedTube.livechatBelowTheater(); + ImprovedTube.livechatTheaterModeObserver(); + } } }; diff --git a/js&css/web-accessible/www.youtube.com/appearance.js b/js&css/web-accessible/www.youtube.com/appearance.js index 0a447dcab..1a8b50d7a 100644 --- a/js&css/web-accessible/www.youtube.com/appearance.js +++ b/js&css/web-accessible/www.youtube.com/appearance.js @@ -422,6 +422,99 @@ ImprovedTube.livechat = function () { } } */ }; + +/*------------------------------------------------------------------------------ + LIVECHAT BELOW THEATER +------------------------------------------------------------------------------*/ +ImprovedTube.livechatBelowTheater = function () { + if (this.storage.livechat_below_theater === true) { + const watchFlexy = document.querySelector("ytd-watch-flexy"); + const liveChatFrame = document.querySelector("ytd-live-chat-frame#chat"); + const chatTemplate = document.getElementById("chat-template"); + + if (!watchFlexy || !liveChatFrame) return; + + // Check if we're in theater mode + const isTheaterMode = watchFlexy.hasAttribute("theater"); + + if (isTheaterMode) { + // Move live chat below the player in theater mode + const primary = document.getElementById("primary"); + const below = document.getElementById("below"); + + if (primary && below) { + // Remove live chat from its current position + if (liveChatFrame.parentNode) { + liveChatFrame.parentNode.removeChild(liveChatFrame); + } + + // Insert live chat below the player (before comments) + const comments = document.querySelector("#comments"); + if (comments && comments.parentNode === below) { + below.insertBefore(liveChatFrame, comments); + } else { + below.appendChild(liveChatFrame); + } + + // Set proper styling for the repositioned chat + liveChatFrame.style.width = "100%"; + liveChatFrame.style.maxWidth = "100%"; + liveChatFrame.style.marginTop = "16px"; + liveChatFrame.style.marginBottom = "16px"; + } + } else { + // Restore live chat to original position when not in theater mode + const secondary = document.getElementById("secondary"); + const secondaryInner = document.getElementById("secondary-inner"); + + if (secondary && secondaryInner && liveChatFrame.parentNode !== secondaryInner) { + // Remove from below position + if (liveChatFrame.parentNode) { + liveChatFrame.parentNode.removeChild(liveChatFrame); + } + + // Restore to secondary column + secondaryInner.appendChild(liveChatFrame); + + // Reset styling + liveChatFrame.style.width = ""; + liveChatFrame.style.maxWidth = ""; + liveChatFrame.style.marginTop = ""; + liveChatFrame.style.marginBottom = ""; + } + } + } +}; + +// Theater mode observer for live chat repositioning +ImprovedTube.livechatTheaterModeObserver = function () { + if (this.storage.livechat_below_theater === true) { + const watchFlexy = document.querySelector("ytd-watch-flexy"); + + if (watchFlexy && !this.livechatTheaterObserver) { + this.livechatTheaterObserver = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && mutation.attributeName === 'theater') { + // Reposition live chat when theater mode changes + ImprovedTube.livechatBelowTheater(); + } + }); + }); + + // Observe theater mode changes + this.livechatTheaterObserver.observe(watchFlexy, { + attributes: true, + attributeFilter: ['theater'] + }); + } + } else { + // Clean up observer when feature is disabled + if (this.livechatTheaterObserver) { + this.livechatTheaterObserver.disconnect(); + this.livechatTheaterObserver = null; + } + } +}; /*------------------------------------------------------------------------------ DETAILS ------------------------------------------------------------------------------*/ diff --git a/test-live-chat-below-theater.js b/test-live-chat-below-theater.js new file mode 100644 index 000000000..feb43c159 --- /dev/null +++ b/test-live-chat-below-theater.js @@ -0,0 +1,117 @@ +// Test script to verify the live chat below theater feature +// This script can be run in the browser console on a YouTube live stream page + +function testLiveChatBelowTheater() { + console.log('๐Ÿงช Testing Live Chat Below Theater Feature'); + + // Check if we're on a video page + if (document.documentElement.dataset.pageType !== 'video') { + console.error('โŒ Not on a video page. Please navigate to a YouTube live stream first.'); + return false; + } + + // Check if live chat is present + const liveChatFrame = document.querySelector("ytd-live-chat-frame#chat"); + if (!liveChatFrame) { + console.error('โŒ Live chat not found. Please navigate to a YouTube live stream first.'); + return false; + } + + console.log('โœ… Found live chat frame'); + + // Check if ImprovedTube is loaded + if (typeof ImprovedTube === 'undefined') { + console.error('โŒ ImprovedTube not loaded. Please ensure the extension is active.'); + return false; + } + + console.log('โœ… ImprovedTube loaded'); + + // Check if our new functions exist + if (typeof ImprovedTube.livechatBelowTheater !== 'function') { + console.error('โŒ livechatBelowTheater function not found'); + return false; + } + + if (typeof ImprovedTube.livechatTheaterModeObserver !== 'function') { + console.error('โŒ livechatTheaterModeObserver function not found'); + return false; + } + + console.log('โœ… New live chat functions are available'); + + // Test theater mode detection + const watchFlexy = document.querySelector("ytd-watch-flexy"); + if (!watchFlexy) { + console.error('โŒ Watch flexy element not found'); + return false; + } + + const isTheaterMode = watchFlexy.hasAttribute("theater"); + console.log('โœ… Current theater mode status:', isTheaterMode); + + // Test the repositioning function + try { + // Enable the feature temporarily for testing + const originalSetting = ImprovedTube.storage.livechat_below_theater; + ImprovedTube.storage.livechat_below_theater = true; + + console.log('๐Ÿ”„ Testing live chat repositioning...'); + ImprovedTube.livechatBelowTheater(); + + // Check if chat moved position + const below = document.getElementById("below"); + const chatInBelow = below && below.contains(liveChatFrame); + + if (isTheaterMode && chatInBelow) { + console.log('โœ… Live chat successfully moved below player in theater mode'); + } else if (!isTheaterMode && !chatInBelow) { + console.log('โœ… Live chat correctly stayed in sidebar when not in theater mode'); + } else { + console.log('โ„น๏ธ Chat position:', { + theaterMode: isTheaterMode, + inBelow: chatInBelow, + currentParent: liveChatFrame.parentNode?.id || liveChatFrame.parentNode?.tagName + }); + } + + // Test theater mode toggle + console.log('๐Ÿ”„ Testing theater mode observer...'); + ImprovedTube.livechatTheaterModeObserver(); + + if (ImprovedTube.livechatTheaterObserver) { + console.log('โœ… Theater mode observer created successfully'); + } else { + console.log('โ„น๏ธ Theater mode observer not created (feature may be disabled)'); + } + + // Restore original setting + ImprovedTube.storage.livechat_below_theater = originalSetting; + + } catch (error) { + console.error('โŒ Error during testing:', error); + return false; + } + + console.log('๐ŸŽ‰ Live chat below theater feature test completed successfully!'); + console.log('๐Ÿ“ Manual testing instructions:'); + console.log(' 1. Go to a YouTube live stream'); + console.log(' 2. Enable "Live Chat Below Theater" in ImprovedTube settings'); + console.log(' 3. Toggle theater mode (theater button on player)'); + console.log(' 4. Verify live chat moves below player in theater mode'); + console.log(' 5. Verify live chat returns to sidebar when exiting theater mode'); + + return true; +} + +// Auto-run test if on a video page +if (document.documentElement.dataset.pageType === 'video') { + setTimeout(testLiveChatBelowTheater, 2000); +} else { + console.log('โ„น๏ธ Navigate to a YouTube live stream to test the live chat below theater feature'); +} + +// Export for manual testing +if (typeof window !== 'undefined') { + window.testLiveChatBelowTheater = testLiveChatBelowTheater; +}