diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 37c4cce95..a2e44c150 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1131,11 +1131,14 @@ "message": "Share" }, "shortcut_chapters": { - "message": "Show chapters Sidebar" + "message": "Chapters (Sidebar) On/Off" }, "shortcut_screenshot": { "message": "Screenshot" }, + "shortcut_transcript": { + "message": "Transcript (Sidebar) On/Off" + }, "shortcuts": { "message": "Shortcuts" }, diff --git a/js&css/web-accessible/core.js b/js&css/web-accessible/core.js index 446edc86a..e0546cc26 100644 --- a/js&css/web-accessible/core.js +++ b/js&css/web-accessible/core.js @@ -62,14 +62,15 @@ var ImprovedTube = { listening: {}, listeners: {}, pressed: { - keys: [], + keys: new Set(), + wheel: 0, alt: false, ctrl: false, - shift: false, - player: false, - wheel: 0 + shift: false }, - cancelled: [] + cancelled: new Set(), + ignoreElements: ['EMBED', 'INPUT', 'OBJECT', 'TEXTAREA', 'IFRAME'], + modifierKeys: ['AltLeft', 'AltRight', 'ControlLeft', 'ControlRight', 'ShiftLeft', 'ShiftRight'], }, mini_player__mode: false, mini_player__move: false, diff --git a/js&css/web-accessible/functions.js b/js&css/web-accessible/functions.js index 320bad2d1..eb4d029ac 100644 --- a/js&css/web-accessible/functions.js +++ b/js&css/web-accessible/functions.js @@ -85,7 +85,6 @@ ImprovedTube.ytElementsHandler = function (node) { this.playlistReverse(); } } - this.playlistPopupUpdate(); } else if (name === 'YTD-GUIDE-SECTION-RENDERER') { if (!this.elements.sidebar_section) { this.elements.sidebar_section = node; @@ -111,7 +110,7 @@ ImprovedTube.ytElementsHandler = function (node) { // this.hideDetailButton(node.querySelector('#flexible-item-buttons').children); // } } else if (name === 'YTD-PLAYLIST-HEADER-RENDERER' || (name === 'YTD-MENU-RENDERER' && node.classList.contains('ytd-playlist-panel-renderer'))) { - this.playlistPopupUpdate(); + this.playlistPopup(); } else if (name === 'YTD-SUBSCRIBE-BUTTON-RENDERER' || name === 'YT-SUBSCRIBE-BUTTON-VIEW-MODEL' || (name === 'YTD-BUTTON-RENDERER' && node.classList.contains('ytd-c4-tabbed-header-renderer'))) { diff --git a/js&css/web-accessible/init.js b/js&css/web-accessible/init.js index 628171893..bf3e0b677 100644 --- a/js&css/web-accessible/init.js +++ b/js&css/web-accessible/init.js @@ -14,10 +14,6 @@ ImprovedTube.observer = new MutationObserver(function (mutationList) { for (var j = 0, k = mutation.addedNodes.length; j < k; j++) { ImprovedTube.childHandler(mutation.addedNodes[j]); } - - for (const node of mutation.removedNodes) { - if (node.nodeName === 'BUTTON' && node.id === 'it-popup-playlist-button') ImprovedTube.playlistPopupUpdate(); - } } if (mutation.target && mutation.target.id === 'owner-sub-count') { // Extract and store the subscriber count @@ -39,7 +35,7 @@ ImprovedTube.observer = new MutationObserver(function (mutationList) { //console.log("p"+i+"c"+j+":"+node.id+",class:"+node.className+","+node+"("+node.nodeName+")"); j++;} let r = 0; for (const node of mutated.removedNodes){ //might fix our other playlist buttons equally? - if(node.nodeName === 'BUTTON' && node.id === 'it-popup-playlist-button') ImprovedTube.playlistPopupUpdate(); + if(node.nodeName === 'BUTTON' && node.id === 'it-popup-playlist-button') ImprovedTube.playlistPopup(); //console.log("p"+i+"removed"+r+":"+node.id+",class:"+node.className+","+node+"("+node.nodeName+")"+"(from:"+mutated.target.id+",class:"+mutated.target.className+","+mutated.target+"("+mutated.target.nodeName+")"); r++;} if(mutated.target.id === 'owner-sub-count') @@ -83,30 +79,23 @@ if (ImprovedTube.storage.channel_default_tab && ImprovedTube.storage.channel_def ImprovedTube.init = function () { window.addEventListener('yt-page-data-updated', function () { ImprovedTube.pageType(); - if (document.documentElement.dataset.pageType === 'video' && /[?&]list=([^&]+).*$/.test(location.href)) { + if (location.search.match(ImprovedTube.regex.playlist_id)) { ImprovedTube.playlistRepeat(); ImprovedTube.playlistShuffle(); ImprovedTube.playlistReverse(); + ImprovedTube.playlistPopup(); } - ImprovedTube.playlistPopupUpdate(); }); - ImprovedTube.pageType(); - var yt_player_updated = function () { - document.dispatchEvent(new CustomEvent('improvedtube-player-loaded')); - - window.removeEventListener('yt-player-updated', yt_player_updated); - }; - - window.addEventListener('yt-player-updated', yt_player_updated); + this.pageType(); this.playerOnPlay(); this.playerSDR(); - this.shortcuts(); + this.shortcutsInit(); this.onkeydown(); this.onmousedown(); this.youtubeLanguage(); this.myColors(); - if (this.storage.undo_the_new_sidebar === true) {this.undoTheNewSidebar();} - if (this.storage.description === "sidebar") {this.descriptionSidebar();} + if (this.storage.undo_the_new_sidebar) this.undoTheNewSidebar(); + if (this.storage.description === "sidebar") this.descriptionSidebar(); this.channelCompactTheme(); if (ImprovedTube.elements.player && ImprovedTube.elements.player.setPlaybackRate) { diff --git a/js&css/web-accessible/www.youtube.com/playlist.js b/js&css/web-accessible/www.youtube.com/playlist.js index 291b595fd..3f08d54c4 100644 --- a/js&css/web-accessible/www.youtube.com/playlist.js +++ b/js&css/web-accessible/www.youtube.com/playlist.js @@ -57,8 +57,6 @@ ImprovedTube.playlistReverse = function () { button.id = 'it-reverse-playlist'; button.className = 'style-scope yt-icon-button'; button.addEventListener('click', function (event) { - var playlist_manager = document.querySelector('yt-playlist-manager'); - event.preventDefault(); event.stopPropagation(); @@ -131,7 +129,7 @@ ImprovedTube.playlistShuffle = function () { ------------------------------------------------------------------------------*/ /** * ## Creates a playlist popup button (with ID `it-popup-playlist-button`) - * - used by/in {@linkcode ImprovedTube.playlistPopupUpdate} + * - used by/in {@linkcode ImprovedTube.playlistPopup} * - checks {@linkcode ImprovedTube.storage.player_autoplay_disable} if to autoplay the popuped playlist/video * - checks {@linkcode ImprovedTube.elements.player} to get video ID and current time, if available, otherwise starts first video of playlist * - popup has video players width/height or window (inner) width/height when video player is not available @@ -222,7 +220,7 @@ ImprovedTube.playlistPopupCreateButton = function (playlistID, altButtonStyle, c * - saves each button in {@linkcode ImprovedTube.elements.buttons} as `it-popup-playlist-button-playlist`, `it-popup-playlist-button-mini`, and `it-popup-playlist-button-panel` * - called from {@linkcode ImprovedTube.ytElementsHandler} and {@linkcode ImprovedTube.hrefObserver} when DOM changes (somewhat related to playlist renderers) */ -ImprovedTube.playlistPopupUpdate = function () { +ImprovedTube.playlistPopup = function () { "use strict"; if (this.storage.playlist_popup === true) { diff --git a/js&css/web-accessible/www.youtube.com/shortcuts.js b/js&css/web-accessible/www.youtube.com/shortcuts.js index 454e374eb..583e66150 100644 --- a/js&css/web-accessible/www.youtube.com/shortcuts.js +++ b/js&css/web-accessible/www.youtube.com/shortcuts.js @@ -6,107 +6,20 @@ WARNING: Browser Debugger Breakpoint downstream from keydown() event will eat co is disabled and key pressed again OR switching tabs/windows to trigger 'improvedtube-blur'. Make sure to have that in mind when debugging. ------------------------------------------------------------------------------*/ -ImprovedTube.shortcuts = function () { - const ignoreElements = ['EMBED', 'INPUT', 'OBJECT', 'TEXTAREA', 'IFRAME'], - modifierKeys = ['AltLeft', 'AltRight', 'ControlLeft', 'ControlRight', 'ShiftLeft', 'ShiftRight'], - handlers = { - keydown: function (event) { - if (document.activeElement && ignoreElements.includes(document.activeElement.tagName) || event.target.isContentEditable) return; - - ImprovedTube.input.pressed.wheel = 0; - ImprovedTube.input.pressed.alt = event.altKey; - ImprovedTube.input.pressed.ctrl = event.ctrlKey; - ImprovedTube.input.pressed.shift = event.shiftKey; - if (!modifierKeys.includes(event.code) && !ImprovedTube.input.pressed.keys.includes(event.keyCode)) { - ImprovedTube.input.pressed.keys.push(event.keyCode); - } +ImprovedTube.shortcutsInit = function () { + // those four are _references_ to source Objects, not copies + const listening = ImprovedTube.input.listening, + listeners = ImprovedTube.input.listeners; - handler(); - }, - keyup: function (event) { - ImprovedTube.input.pressed.wheel = 0; - ImprovedTube.input.pressed.alt = event.altKey; - ImprovedTube.input.pressed.ctrl = event.ctrlKey; - ImprovedTube.input.pressed.shift = event.shiftKey; - if (ImprovedTube.input.pressed.keys.includes(event.keyCode)) { - ImprovedTube.input.pressed.keys.splice(ImprovedTube.input.pressed.keys.indexOf(event.keyCode), 1); - } - // cancel keyup events corresponding to keys that triggered one of our shortcuts - if (ImprovedTube.input.cancelled.includes(event.keyCode)) { - event.preventDefault(); - event.stopPropagation(); - ImprovedTube.input.cancelled.splice(ImprovedTube.input.cancelled.indexOf(event.keyCode), 1); - } - }, - wheel: function (event) { - if (document.activeElement && ignoreElements.includes(document.activeElement.tagName) || event.target.isContentEditable) return; - - ImprovedTube.input.pressed.wheel = event.deltaY > 0 ? 1 : -1; - ImprovedTube.input.pressed.alt = event.altKey; - ImprovedTube.input.pressed.ctrl = event.ctrlKey; - ImprovedTube.input.pressed.shift = event.shiftKey; - - handler(); - }, - 'improvedtube-player-loaded': function () { - //Please Fix: November2023: this parentNode doesnt exist on youtube.com/shorts - if (ImprovedTube.elements.player && ImprovedTube.elements.player.parentNode) { - ImprovedTube.elements.player?.parentNode?.addEventListener('mouseover', function () { - ImprovedTube.input.pressed.player = true; - ImprovedTube.input.pressed.wheel = 0; - }, true); - - ImprovedTube.elements.player?.parentNode?.addEventListener('mouseout', function () { - ImprovedTube.input.pressed.player = false; - ImprovedTube.input.pressed.wheel = 0; - }, true); - } - }, - 'improvedtube-blur': function () { - ImprovedTube.input.pressed.keys = []; - ImprovedTube.input.pressed.alt = false; - ImprovedTube.input.pressed.ctrl = false; - ImprovedTube.input.pressed.shift = false; - ImprovedTube.input.pressed.player = false; - ImprovedTube.input.pressed.wheel = 0; - } - }; - - function handler () { - for (const [key, shortcut] of Object.entries(ImprovedTube.input.listening)) { - if ((ImprovedTube.input.pressed.keys - && ImprovedTube.input.pressed.keys.length === shortcut.keys.length - && ImprovedTube.input.pressed.keys.every((k, i) => k === shortcut.keys[i])) - && ImprovedTube.input.pressed.alt === shortcut.alt - && ImprovedTube.input.pressed.ctrl === shortcut.ctrl - && ImprovedTube.input.pressed.shift === shortcut.shift - && (ImprovedTube.input.pressed.wheel === shortcut.wheel - // shortcuts with wheel allowed ONLY inside player - && (!ImprovedTube.input.pressed.wheel || ImprovedTube.input.pressed.player))) { - - if (key.startsWith('shortcutQuality')) { - ImprovedTube['shortcutQuality'](key); - } else if (typeof ImprovedTube[key] === 'function') { - ImprovedTube[key](); - } - // cancel keydown/wheel event - event.preventDefault(); - event.stopPropagation(); - // build 'cancelled' list so we also cancel keyup events - ImprovedTube.input.pressed.keys.every((k, i) => ImprovedTube.input.cancelled.push(k)); - } - } - }; - - // reset 'listening' - this.input.listening = {}; + // reset 'listening' shortcuts + for (var key in listening) delete listening[key]; // extract shortcuts from User Settings and initialize 'listening' for (const [name, keys] of Object.entries(this.storage).filter(v => v[0].startsWith('shortcut_'))) { if (!keys) continue; // camelCase(name) const camelName = name.replace(/_(.)/g, (m, l) => l.toUpperCase()); let potentialShortcut = {}; - for (const button of ['alt', 'ctrl', 'shift', 'wheel', 'keys']) { + for (const button of ['alt', 'ctrl', 'shift', 'wheel', 'keys', 'toggle']) { switch (button) { case 'alt': case 'ctrl': @@ -120,21 +33,111 @@ ImprovedTube.shortcuts = function () { break case 'keys': - // array of sorted scancodes - potentialShortcut[button] = keys[button] ? Object.keys(keys[button]).map(s=>Number(s)).sort() : []; + // set of unique scancodes + potentialShortcut[button] = keys[button] ? new Set(Object.keys(keys[button]).map(s=>Number(s))) : new Set(); break } } - if (potentialShortcut['keys'] || potentialShortcut['wheel']) this.input.listening[camelName] = potentialShortcut; + if (potentialShortcut['keys'].size || potentialShortcut['wheel']) listening[camelName] = potentialShortcut; } - // initialize Listeners, but only once! - for (const [name, handler] of Object.entries(handlers)) { - if (!this.input.listeners[name]) { - this.input.listeners[name] = true; - window.addEventListener(name, handler, {passive: false, capture: true}); + // initialize 'listeners' only if there are actual shortcuts active + if (Object.keys(listening).length) { + for (const [name, handler] of Object.entries(this.shortcutsListeners)) { + // only one listener per handle + if (!listeners[name]) { + listeners[name] = true; + window.addEventListener(name, handler, {passive: false, capture: true}); + } + } + } else { + // no shortcuts means we dont need 'listeners', uninstall all + for (const [name, handler] of Object.entries(this.shortcutsListeners)) { + if (listeners[name]) { + delete listeners[name]; + window.removeEventListener(name, handler, {passive: false, capture: true}); + } + } + } +}; + +ImprovedTube.shortcutsHandler = function () { + check: for (const [key, shortcut] of Object.entries(ImprovedTube.input.listening)) { + if (ImprovedTube.input.pressed.keys.size != shortcut.keys.size + || ImprovedTube.input.pressed.wheel != shortcut.wheel + || ImprovedTube.input.pressed.alt != shortcut.alt + || ImprovedTube.input.pressed.ctrl != shortcut.ctrl + || ImprovedTube.input.pressed.shift != shortcut.shift) continue; + + for (const pressedKey of ImprovedTube.input.pressed.keys.values()) { + if (!shortcut.keys.has(pressedKey)) continue check; + } + + // cancel keydown/wheel event before we call target handler + // this way crashing handler wont keep 'cancelled' keys stuck + event.preventDefault(); + event.stopPropagation(); + // build 'cancelled' list so we also cancel keyup events + for (const pressedKey of ImprovedTube.input.pressed.keys.values()) { + ImprovedTube.input.cancelled.add(pressedKey); + } + + if (key.startsWith('shortcutQuality')) { + ImprovedTube['shortcutQuality'](key); + } else if (typeof ImprovedTube[key] === 'function') { + ImprovedTube[key](); } } }; + +ImprovedTube.shortcutsListeners = { + keydown: function (event) { + ImprovedTube.user_interacted = true; + // no shortcuts over 'ignoreElements' + if ((document.activeElement && ImprovedTube.input.ignoreElements.includes(document.activeElement.tagName)) || event.target.isContentEditable) return; + + if (!ImprovedTube.input.modifierKeys.includes(event.code)) { + ImprovedTube.input.pressed.keys.add(event.keyCode); + } + ImprovedTube.input.pressed.wheel = 0; + ImprovedTube.input.pressed.alt = event.altKey; + ImprovedTube.input.pressed.ctrl = event.ctrlKey; + ImprovedTube.input.pressed.shift = event.shiftKey; + + ImprovedTube.shortcutsHandler(); + }, + keyup: function (event) { + ImprovedTube.input.pressed.keys.delete(event.keyCode); + ImprovedTube.input.pressed.wheel = 0; + ImprovedTube.input.pressed.alt = event.altKey; + ImprovedTube.input.pressed.ctrl = event.ctrlKey; + ImprovedTube.input.pressed.shift = event.shiftKey; + + // cancel keyup events corresponding to keys that triggered one of our shortcuts + if (ImprovedTube.input.cancelled.has(event.keyCode)) { + event.preventDefault(); + event.stopPropagation(); + ImprovedTube.input.cancelled.delete(event.keyCode); + } + }, + wheel: function (event) { + // shortcuts with wheel allowed ONLY inside player + if (!ImprovedTube.elements.player?.contains(event.target)) return; + + ImprovedTube.input.pressed.wheel = event.deltaY > 0 ? 1 : -1; + ImprovedTube.input.pressed.alt = event.altKey; + ImprovedTube.input.pressed.ctrl = event.ctrlKey; + ImprovedTube.input.pressed.shift = event.shiftKey; + + ImprovedTube.shortcutsHandler(); + }, + 'improvedtube-blur': function () { + ImprovedTube.input.pressed.keys.clear(); + ImprovedTube.input.pressed.wheel = 0 + ImprovedTube.input.pressed.alt = false; + ImprovedTube.input.pressed.ctrl = false; + ImprovedTube.input.pressed.shift = false; + } +}; /*------------------------------------------------------------------------------ Ambient lighting ------------------------------------------------------------------------------*/ @@ -207,37 +210,13 @@ ImprovedTube.shortcutToggleAutoplay = function () { 4.7.7 NEXT VIDEO ------------------------------------------------------------------------------*/ ImprovedTube.shortcutNextVideo = function () { - if (this.elements.player) { - var playlist_loop_button = document.querySelector('[aria-label="Loop playlist"]'); - - if (playlist_loop_button) { - if (playlist_loop_button.ariaPressed === 'true') { - this.elements.player.setLoop(true); - } else { - this.elements.player.setLoop(false) - } - } - - this.elements.player.nextVideo(); - } + this.elements.player?.nextVideo(); }; /*------------------------------------------------------------------------------ 4.7.8 PREVIOUS VIDEO ------------------------------------------------------------------------------*/ ImprovedTube.shortcutPrevVideo = function () { - if (this.elements.player) { - var playlist_loop_button = document.querySelector('[aria-label="Loop playlist"]'); - - if (playlist_loop_button) { - if (playlist_loop_button.ariaPressed === 'true') { - this.elements.player.setLoop(true); - } else { - this.elements.player.setLoop(false) - } - } - - this.elements.player.previousVideo(); - } + this.elements.player?.previousVideo(); }; /*------------------------------------------------------------------------------ 4.7.9 SEEK BACKWARD @@ -426,27 +405,43 @@ ImprovedTube.shortcutActivateCaptions = function () { }; /*------Chapters------*/ ImprovedTube.shortcutChapters = function () { - try {var height = document.querySelector('*[target-id*=chapters]').clientHeight;} catch {} - if (height) {try {document.querySelector('*[target-id*=chapters] #visibility-button button').click(); console.log("chapters shortcut close")} catch {}} - else { try {document.querySelector('*[target-id*=chapters]').removeAttribute('visibility'); console.log("chapters shortcut open")} catch {} } + const available = document.querySelector('[target-id*=chapters][visibility*=HIDDEN]') || document.querySelector('[target-id*=chapters]').clientHeight; + if (available) { + const modernChapters = document.querySelector('[modern-chapters] #navigation-button button[aria-label]'); + modernChapters ? modernChapters.click() : document.querySelector('[target-id*=chapters]')?.removeAttribute('visibility'); + } else { + const visibilityButton = document.querySelector('[target-id*=chapters][visibility*=EXPANDED] #visibility-button button[aria-label]'); + visibilityButton ? visibilityButton.click() : document.querySelector('*[target-id*=chapters] #visibility-button button')?.click(); + } + if (!modernChapters && visibilityButton) { + console.error('shortcutChapters: Cant fint proper Enble button, falling back to unreliable bruteforce method'); + } }; /*------Transcript------*/ ImprovedTube.shortcutTranscript = function () { - try {var height = document.querySelector('*[target-id*=transcript]').clientHeight;} catch {} - if (height) {try {document.querySelector('*[target-id*=transcript] #visibility-button button').click(); console.log("transcriptshortcut close")} catch {}} - else { try {document.querySelector('*[target-id*=transcript]').removeAttribute('visibility'); console.log("transcriptshortcut open")} catch {} } + const available = document.querySelector('[target-id*=transcript][visibility*=HIDDEN]') || document.querySelector('[target-id*=transcript]').clientHeight; + if (available) { + const descriptionTranscript = document.querySelector('ytd-video-description-transcript-section-renderer button[aria-label]'); + descriptionTranscript ? descriptionTranscript.click() : document.querySelector('[target-id*=transcript]')?.removeAttribute('visibility'); + } else { + const transcriptButton = document.querySelector('ytd-video-description-transcript-section-renderer button[aria-label]'); + transcriptButton ? transcriptButton.click() : document.querySelector('[target-id*=transcript] #visibility-button button')?.click(); + } + if (!descriptionTranscript && transcriptButton) { + console.error('shortcutTranscript: Cant fint proper Enble button, falling back to unreliable bruteforce method'); + } }; /*------------------------------------------------------------------------------ 4.7.22 LIKE ------------------------------------------------------------------------------*/ ImprovedTube.shortcutLike = function () { - document.querySelector('like-button-view-model * * *')?.click(); + document.querySelector('like-button-view-model button')?.click(); }; /*------------------------------------------------------------------------------ 4.7.23 DISLIKE ------------------------------------------------------------------------------*/ ImprovedTube.shortcutDislike = function () { - document.querySelector('dislike-button-view-model * * *')?.click(); + document.querySelector('dislike-button-view-model button')?.click(); }; /*------Report------*/ ImprovedTube.shortcutReport = function () { @@ -549,7 +544,7 @@ ImprovedTube.shortcutPopupPlayer = function () { if (player && document.documentElement.dataset.pageType === 'video') { player.pauseVideo(); - window.open('//www.youtube.com/embed/' + location.href.match(/watch\?v=([A-Za-z0-9\-\_]+)/g)[0].slice(8) + '?start=' + parseInt(player.getCurrentTime()) + '&autoplay=' + (ImprovedTube.storage.player_autoplay_disable ? '0' : '1'), '_blank', 'directories=no,toolbar=no,location=no,menubar=no,status=no,titlebar=no,scrollbars=no,resizable=no,width=' + player.offsetWidth + ',height=' + player.offsetHeight); + window.open('//www.youtube.com/embed/' + location.href.match(ImprovedTube.regex.video_id)?.[1] + '?start=' + parseInt(player.getCurrentTime()) + '&autoplay=' + (ImprovedTube.storage.player_autoplay_disable ? '0' : '1'), '_blank', 'directories=no,toolbar=no,location=no,menubar=no,status=no,titlebar=no,scrollbars=no,resizable=no,width=' + player.offsetWidth + ',height=' + player.offsetHeight); } }; /*------------------------------------------------------------------------------ diff --git a/menu/skeleton-parts/shortcuts.js b/menu/skeleton-parts/shortcuts.js index 03f2db8c7..9dbebc313 100644 --- a/menu/skeleton-parts/shortcuts.js +++ b/menu/skeleton-parts/shortcuts.js @@ -338,7 +338,7 @@ extension.skeleton.main.layers.section.shortcuts = { }, shortcut_transcript: { component: 'shortcut', - text: 'transcript' + text: 'shortcut_transcript' }, shortcut_like: { component: 'shortcut',