diff --git a/_locales/en/messages.json b/_locales/en/messages.json index ec4934bb3..1ffa6768d 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1,4 +1,5 @@ { + "livechatBelowTheater": {"message": "Live chat below theater"}, "hideSuggestedAction": { "message": "Hide Suggested Action 'View products'" }, "hideMerchShelf": { "message": "Hide 'Merch Shelf'" }, "about": { diff --git a/js&css/web-accessible/core.js b/js&css/web-accessible/core.js index dbaf8a4ed..3cd65288a 100644 --- a/js&css/web-accessible/core.js +++ b/js&css/web-accessible/core.js @@ -510,6 +510,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 'smartSpeed': if (ImprovedTube.storage.smart_speed === true) { if(ImprovedTube.heatmap) {ImprovedTube.heatmap.init(); }; } else if (ImprovedTube.storage.smart_speed === false) { if(ImprovedTube.heatmap) { ImprovedTube.heatmap.isEnabled = false; document.querySelector("video").playbackRate = 1.0; } diff --git a/js&css/web-accessible/functions.js b/js&css/web-accessible/functions.js index 08534e696..65af3eeeb 100644 --- a/js&css/web-accessible/functions.js +++ b/js&css/web-accessible/functions.js @@ -349,14 +349,22 @@ ImprovedTube.videoPageUpdate = function () { ImprovedTube.playerHamburgerButton(); ImprovedTube.playerControls(); - // Initialize large playlist handler for playlist videos - if (this.getParam(location.href, 'list')) { - ImprovedTube.playlistLargePlaylistHandler(); - } else { - // Cleanup when not on a playlist - if (typeof ImprovedTube.cleanupPlaylistHandlers === 'function') { - ImprovedTube.cleanupPlaylistHandlers(); - } +// Initialize live chat below theater functionality +if (this.storage.livechat_below_theater === true) { + ImprovedTube.livechatBelowTheater(); + ImprovedTube.livechatTheaterModeObserver(); +} + +// Initialize large playlist handler for playlist videos +if (this.getParam(location.href, 'list')) { + ImprovedTube.playlistLargePlaylistHandler(); +} else { + // Cleanup when not on a playlist + if (typeof ImprovedTube.cleanupPlaylistHandlers === 'function') { + ImprovedTube.cleanupPlaylistHandlers(); + } +} + } } }; 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/menu/skeleton-parts/appearance.js b/menu/skeleton-parts/appearance.js index 4c34d92db..0923dd518 100644 --- a/menu/skeleton-parts/appearance.js +++ b/menu/skeleton-parts/appearance.js @@ -1119,6 +1119,10 @@ extension.skeleton.main.layers.section.appearance.on.click.sidebar = { text: "hidden", value: 'hidden' }], + }, + livechat_below_theater: { + component: "switch", + text: 'livechatBelowTheater' }, hide_playlist: { component: "switch", 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; +}