您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Make comments and lists into tabs
当前为
// ==UserScript== // @name Tabview Youtube // @namespace http://tampermonkey.net/ // @version 1.8.41 // @description Make comments and lists into tabs // @author CY Fung // @match https://www.youtube.com/* // @resource contentCSS https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/a7d332f398a4ba137e32a3e90469a9ff047aa7d1/css/style_content.css // @icon https://github.com/cyfung1031/Tabview-Youtube/raw/main/images/icon128p.png // @require https://cdnjs.cloudflare.com/ajax/libs/cash/8.1.0/cash.min.js // @grant GM_getResourceText // @run-at document-start // @license MIT https://github.com/cyfung1031/Tabview-Youtube/blob/main/LICENSE // @noframes // ==/UserScript== /* jshint esversion:6 */ function main($){ // MIT License // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js -(function() { function inIframe() { try { return window.self !== window.top; } catch (e) { return true; } } if (inIframe()) return; if (!$) return; /** * SVG resources: * <div>Icons made by <a href="https://www.flaticon.com/authors/smashicons" title="Smashicons">Smashicons</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div> */ const scriptVersionForExternal = '2021/07/03'; const githubURLBase = "https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube"; const githubURLCommit = "bdf401045266e5224663f80b276bc7f56d122b8d"; const isMyScriptInChromeRuntime = () => { try{ typeof(window.chrome?.runtime?.getURL) == 'function' }catch(e){} } const svgComments = ` <path d="M40.068,13.465L5.93,13.535c-3.27,0-5.93,2.66-5.93,5.93v21.141c0,3.27,2.66,5.929,5.93,5.929H12v10 c0,0.413,0.254,0.784,0.64,0.933c0.117,0.045,0.239,0.067,0.36,0.067c0.276,0,0.547-0.115,0.74-0.327l9.704-10.675l16.626-0.068 c3.27,0,5.93-2.66,5.93-5.929V19.395C46,16.125,43.34,13.465,40.068,13.465z M10,23.465h13c0.553,0,1,0.448,1,1s-0.447,1-1,1H10 c-0.553,0-1-0.448-1-1S9.447,23.465,10,23.465z M36,37.465H10c-0.553,0-1-0.448-1-1s0.447-1,1-1h26c0.553,0,1,0.448,1,1 S36.553,37.465,36,37.465z M36,31.465H10c-0.553,0-1-0.448-1-1s0.447-1,1-1h26c0.553,0,1,0.448,1,1S36.553,31.465,36,31.465z"/> <path d="M54.072,2.535L19.93,2.465c-3.27,0-5.93,2.66-5.93,5.93v3.124l26.064-0.054c4.377,0,7.936,3.557,7.936,7.93v21.07v0.071 v2.087l3.26,3.586c0.193,0.212,0.464,0.327,0.74,0.327c0.121,0,0.243-0.022,0.36-0.067c0.386-0.149,0.64-0.52,0.64-0.933v-10h1.07 c3.27,0,5.93-2.66,5.93-5.929V8.465C60,5.195,57.34,2.535,54.072,2.535z"/> `.trim(); const svgVideos = `<path d="M298,33c0-13.255-10.745-24-24-24H24C10.745,9,0,19.745,0,33v232c0,13.255,10.745,24,24,24h250c13.255,0,24-10.745,24-24V33 z M91,39h43v34H91V39z M61,259H30v-34h31V259z M61,73H30V39h31V73z M134,259H91v-34h43V259z M123,176.708v-55.417 c0-8.25,5.868-11.302,12.77-6.783l40.237,26.272c6.902,4.519,6.958,11.914,0.056,16.434l-40.321,26.277 C128.84,188.011,123,184.958,123,176.708z M207,259h-43v-34h43V259z M207,73h-43V39h43V73z M268,259h-31v-34h31V259z M268,73h-31V39 h31V73z"/>`.trim(); const svgInfo = `<path d="M11.812,0C5.289,0,0,5.289,0,11.812s5.289,11.813,11.812,11.813s11.813-5.29,11.813-11.813 S18.335,0,11.812,0z M14.271,18.307c-0.608,0.24-1.092,0.422-1.455,0.548c-0.362,0.126-0.783,0.189-1.262,0.189 c-0.736,0-1.309-0.18-1.717-0.539s-0.611-0.814-0.611-1.367c0-0.215,0.015-0.435,0.045-0.659c0.031-0.224,0.08-0.476,0.147-0.759 l0.761-2.688c0.067-0.258,0.125-0.503,0.171-0.731c0.046-0.23,0.068-0.441,0.068-0.633c0-0.342-0.071-0.582-0.212-0.717 c-0.143-0.135-0.412-0.201-0.813-0.201c-0.196,0-0.398,0.029-0.605,0.09c-0.205,0.063-0.383,0.12-0.529,0.176l0.201-0.828 c0.498-0.203,0.975-0.377,1.43-0.521c0.455-0.146,0.885-0.218,1.29-0.218c0.731,0,1.295,0.178,1.692,0.53 c0.395,0.353,0.594,0.812,0.594,1.376c0,0.117-0.014,0.323-0.041,0.617c-0.027,0.295-0.078,0.564-0.152,0.811l-0.757,2.68 c-0.062,0.215-0.117,0.461-0.167,0.736c-0.049,0.275-0.073,0.485-0.073,0.626c0,0.356,0.079,0.599,0.239,0.728 c0.158,0.129,0.435,0.194,0.827,0.194c0.185,0,0.392-0.033,0.626-0.097c0.232-0.064,0.4-0.121,0.506-0.17L14.271,18.307z M14.137,7.429c-0.353,0.328-0.778,0.492-1.275,0.492c-0.496,0-0.924-0.164-1.28-0.492c-0.354-0.328-0.533-0.727-0.533-1.193 c0-0.465,0.18-0.865,0.533-1.196c0.356-0.332,0.784-0.497,1.28-0.497c0.497,0,0.923,0.165,1.275,0.497 c0.353,0.331,0.53,0.731,0.53,1.196C14.667,6.703,14.49,7.101,14.137,7.429z"/>`.trim(); const svgPlayList = ` <rect x="0" y="64" width="256" height="42.667"/> <rect x="0" y="149.333" width="256" height="42.667"/> <rect x="0" y="234.667" width="170.667" height="42.667"/> <polygon points="341.333,234.667 341.333,149.333 298.667,149.333 298.667,234.667 213.333,234.667 213.333,277.333 298.667,277.333 298.667,362.667 341.333,362.667 341.333,277.333 426.667,277.333 426.667,234.667"/> `.trim(); // --- Youtube Video Testing : // Square Video: https://www.youtube.com/watch?v=L0RXVnRbFg8 // Square Video: https://www.youtube.com/watch?v=bK_rKhMIotU // --- const LAYOUT_TWO_COLUMNS = 1; const LAYOUT_THEATER = 2; const LAYOUT_FULLSCREEN = 4; const LAYOUT_CHATROOM = 8; const LAYOUT_CHATROOM_COLLASPED = 16; const LAYOUT_TAB_EXPANDED = 32; const LAYOUT_ENGAGEMENT_PANEL_EXPAND = 64; const mtoInterval1 = 40; const mtoInterval2 = 150; let lastVideoURL = null; const WeakRef = window.WeakRef; const mWeakRef = WeakRef ? (o => o ? new WeakRef(o) : null) : (o => o || null); const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr); let cmItem = null; function tracer(key, cmp) { if (cmp > 0) return tracer[key] === cmp; return (tracer[key] = Date.now()); } function racer(key, f) { let now = Date.now(); const kTime = `${key}$$1` let t = racer[kTime] || 0; if (now < t) { const kCount = `${key}$$2`; racer[kCount] = (racer[kCount] || 0) + 1; if (racer[kCount] === 1) { let g = f; requestAnimationFrame(() => { racer[kCount] = 0; g(); g = null; }) } } else { racer[kTime] = now + 16; f(); } } class ScriptEF { constructor() { this._id = scriptEC; } isValid() { return this._id === scriptEC; } } class Timeout { set(f, d, repeatCount) { if (this.cid > 0) return; let sEF = new ScriptEF(); if (repeatCount > 0) { let rc = repeatCount; const g = () => { this.cid = 0; if (!sEF.isValid()) return; let res = f(); if (--rc <= 0) return; if (res === true) this.cid = setTimeout(g, d); } g(); } else { const g = () => { this.cid = 0; if (!sEF.isValid()) return; if (f() === true) this.cid = setTimeout(g, d); } this.cid = setTimeout(g, d); } } clear() { if (this.cid > 0) clearTimeout(this.cid); } isEmpty() { return !this.cid } } class Mutex { constructor() { this.p = Promise.resolve() } lockWith(f) { this.p = this.p.then(() => { return new Promise(f) }).catch(console.warn) } } function prettyElm(elm) { if (!elm || !elm.nodeName) return null; const eId = elm.id || null; const eClsName = elm.className || null; return [elm.nodeName.toLowerCase(), typeof eId == 'string' ? "#" + eId : '', typeof eClsName == 'string' ? '.' + eClsName.replace(/\s+/g, '.') : ''].join('').trim(); } function extractTextContent(elm) { return elm.textContent.replace(/\s+/g, '').replace(/[^\da-zA-Z\u4E00-\u9FFF\u00C0-\u00FF\u00C0-\u02AF\u1E00-\u1EFF\u0590-\u05FF\u0400-\u052F\u0E00-\u0E7F\u0600-\u06FF\u0750-\u077F\u1100-\u11FF\u3130-\u318F\uAC00-\uD7AF\u3040-\u30FF\u31F0-\u31FF]/g, '') } function addScript(scriptText) { const scriptNode = document.createElement('script'); scriptNode.type = 'text/javascript'; scriptNode.textContent = scriptText; document.documentElement.appendChild(scriptNode); return scriptNode; } function addScriptByURL(scriptURL) { const scriptNode = document.createElement('script'); scriptNode.type = 'text/javascript'; scriptNode.src = scriptURL; document.documentElement.appendChild(scriptNode); return scriptNode; } function addStyle(styleText, container) { const styleNode = document.createElement('style'); //styleNode.type = 'text/css'; styleNode.textContent = styleText; (container || document.documentElement).appendChild(styleNode); return styleNode; } function isDOMVisible(elem) { // jQuery version : https://github.com/jquery/jquery/blob/a684e6ba836f7c553968d7d026ed7941e1a612d8/src/css/hiddenVisibleSelectors.js return !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length); } function isNonEmptyString(s) { return typeof s == 'string' && s.length > 0; } function nativeFunc(dom, property, args) { dom.dispatchEvent(new CustomEvent("userscript-call-dom-func", { detail: { property, args } })) } function akAttr(cssElm, attrName, isNegative, flag) { let u = parseInt(cssElm.getAttribute(attrName) || 0) || 0; let ak = Math.abs(u); if (ak > 100 && isNegative && u < 0) { } else if (ak > 100 && !isNegative && u > 0) { } else { if (ak <= 100) { ak = 101; } else { ak++; if (ak >= 800) ak = 101; } // 101, 102, ... 799, 101 } let s = '' + (isNegative ? -ak : ak); flag = flag || ''; cssElm.setAttribute(attrName, s + flag) } let timeout_resize_for_layout_change = new Timeout(); function layoutStatusChanged(old_layoutStatus, new_layoutStatus) { if (old_layoutStatus === new_layoutStatus) return; const cssElm = kRef(ytdFlexy); if (!cssElm) return; const new_isExpandedChat = !(new_layoutStatus & LAYOUT_CHATROOM_COLLASPED) && (new_layoutStatus & LAYOUT_CHATROOM) const new_isCollaspedChat = (new_layoutStatus & LAYOUT_CHATROOM_COLLASPED) && (new_layoutStatus & LAYOUT_CHATROOM) const new_isTwoColumns = new_layoutStatus & LAYOUT_TWO_COLUMNS; const new_isTheater = new_layoutStatus & LAYOUT_THEATER; const new_isTabExpanded = new_layoutStatus & LAYOUT_TAB_EXPANDED; const new_isFullScreen = new_layoutStatus & LAYOUT_FULLSCREEN; const new_isExpandEPanel = new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPAND; function showTabOrChat() { layoutStatusMutex.lockWith(unlock => { if (lastShowTab == '#chatroom') { if (new_isTabExpanded) switchTabActivity(null) if (!new_isExpandedChat) ytBtnExpandChat(); } else if (lastShowTab && lastShowTab.indexOf('#engagement-panel-') == 0) { if (new_isTabExpanded) switchTabActivity(null) if (!new_isExpandEPanel) ytBtnOpenEngagementPanel(lastShowTab); } else { if (new_isExpandedChat) ytBtnCollapseChat() if (!new_isTabExpanded) setToActiveTab(); } setTimeout(unlock, 40); }) } function hideTabAndChat() { layoutStatusMutex.lockWith(unlock => { if (new_isTabExpanded) switchTabActivity(null) if (new_isExpandedChat) ytBtnCollapseChat() if (new_isExpandEPanel) ytBtnCloseEngagementPanels(); setTimeout(unlock, 40); }) } if (new_isExpandedChat || new_isTabExpanded || new_isExpandEPanel) { if (statusCollasped !== 1) statusCollasped = 1; } else { if (statusCollasped === 1) statusCollasped = 2; } let changes = 0; if (old_layoutStatus !== null) changes = old_layoutStatus ^ new_layoutStatus; let chat_collasped_changed = !!(changes & LAYOUT_CHATROOM_COLLASPED) let tab_expanded_changed = !!(changes & LAYOUT_TAB_EXPANDED) let theater_mode_changed = !!(changes & LAYOUT_THEATER) let column_mode_changed = !!(changes & LAYOUT_TWO_COLUMNS) let fullscreen_mode_changed = !!(changes & LAYOUT_FULLSCREEN) let epanel_expanded_changed = !!(changes & LAYOUT_ENGAGEMENT_PANEL_EXPAND) let tab_change = (tab_expanded_changed ? 1 : 0) | (chat_collasped_changed ? 2 : 0) | (epanel_expanded_changed ? 4 : 0); let isChatOrTabExpandTriggering = tab_change == 0 ? false : ( (tab_expanded_changed && new_isTabExpanded) || (chat_collasped_changed && new_isExpandedChat) || (epanel_expanded_changed && new_isExpandEPanel) ); let isChatOrTabCollaspeTriggering = tab_change == 0 ? false : ( (tab_expanded_changed && !new_isTabExpanded) || (chat_collasped_changed && new_isCollaspedChat) || (epanel_expanded_changed && !new_isExpandEPanel) ); let moreThanOneShown = (new_isTabExpanded + new_isExpandedChat + new_isExpandEPanel) > 1 let requestVideoResize = false; if (fullscreen_mode_changed || new_isFullScreen) { } else if (tab_change == 0 && column_mode_changed && new_isTwoColumns && !new_isTheater && statusCollasped === 1 && moreThanOneShown) { showTabOrChat(); requestVideoResize = true; } else if (tab_change == 2 && new_isExpandedChat && new_isTwoColumns && !new_isTheater && statusCollasped === 1 && new_isTabExpanded && !column_mode_changed) { switchTabActivity(null); requestVideoResize = true; } else if (isChatOrTabExpandTriggering && new_isTwoColumns && new_isTheater && statusCollasped === 1 && !theater_mode_changed && !column_mode_changed) { ytBtnCancelTheater(); requestVideoResize = true; } else if (new_isTwoColumns && new_isTheater && statusCollasped === 1) { hideTabAndChat(); requestVideoResize = true; } else if (isChatOrTabCollaspeTriggering && new_isTwoColumns && !new_isTheater && statusCollasped === 2 && !column_mode_changed) { ytBtnSetTheater(); requestVideoResize = true; } else if (tab_change == 0 && (column_mode_changed || theater_mode_changed) && new_isTwoColumns && !new_isTheater && statusCollasped !== 1) { showTabOrChat(); requestVideoResize = true; } else if (!new_isFullScreen && new_isTwoColumns && !new_isTheater && !new_isTabExpanded && (new_isCollaspedChat || !new_isExpandedChat) && !new_isExpandEPanel ) { // bug fix for restoring from mini player layoutStatusMutex.lockWith(unlock => { if (new_isExpandedChat) ytBtnCollapseChat() setToActiveTab(); setTimeout(unlock, 40); }) requestVideoResize = true; } else if (tab_expanded_changed) { requestVideoResize = true; } if (column_mode_changed && !chat_collasped_changed && new_isExpandedChat) { runAfterExpandChat(); } if (requestVideoResize) { timeout_resize_for_layout_change.clear(); timeout_resize_for_layout_change.set(() => { window.dispatchEvent(new Event('resize')) }, 92) } else if (timeout_resize_for_layout_change.isEmpty() && (Date.now()) - lastResizeAt > 600) { timeout_resize_for_layout_change.set(() => { if ((Date.now()) - lastResizeAt > 600) window.dispatchEvent(new Event('resize')); }, 62) } } const $ws = { _layoutStatus: null, layoutStatus_pending: false } let wls = new class { get layoutStatus() { return this._layoutStatus; } set layoutStatus(nv) { if (nv === null) { this._layoutStatus = null; statusCollasped = 0; return; } if (nv === this._layoutStatus) return; if (!this.layoutStatus_pending) { this.layoutStatus_pending = true; const old_layoutStatus = this._layoutStatus; layoutStatusMutex.lockWith(unlock => { this.layoutStatus_pending = false; const new_layoutStatus = this._layoutStatus; layoutStatusChanged(old_layoutStatus, new_layoutStatus); setTimeout(unlock, 40) }) } this._layoutStatus = nv; } }; const svgElm = (w, h, vw, vh, p) => `<svg width="${w}" height="${h}" viewBox="0 0 ${vw} ${vh}" preserveAspectRatio="xMidYMid meet">${p}</svg>` let settings = { defaultTab: "#tab-videos" }; let mtoInterval = mtoInterval1; function isVideoPlaying(video) { return video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA; } function wAttr(elm, attr, kv) { if (!elm || kv === null) {} else if (kv === true) { elm.setAttribute(attr, '') } else if (kv === false) { elm.removeAttribute(attr) } else if (typeof kv == 'string') { elm.setAttribute(attr, kv) } } function hideTabBtn(tabBtn) { var isActiveBefore = tabBtn.classList.contains('active'); tabBtn.classList.add("tab-btn-hidden"); if (isActiveBefore) { setToActiveTab(); } } function hasAttribute(obj, key) { return obj && obj.hasAttribute(key); } function isTheater() { const cssElm = kRef(ytdFlexy); return (cssElm && cssElm.hasAttribute('theater')) } function isFullScreen() { const cssElm = kRef(ytdFlexy); return (cssElm && cssElm.hasAttribute('fullscreen')) } function isChatExpand() { const cssElm = kRef(ytdFlexy); return cssElm && cssElm.hasAttribute('userscript-chatblock') && !cssElm.hasAttribute('userscript-chat-collapsed') } function isWideScreenWithTwoColumns() { const cssElm = kRef(ytdFlexy); return (cssElm && cssElm.hasAttribute('is-two-columns_')) } function isAnyActiveTab() { return $('#right-tabs .tab-btn.active').length > 0 } function isEngagementPanelExpanded() { const cssElm = kRef(ytdFlexy); return (cssElm && +cssElm.getAttribute('userscript-engagement-panel') > 0) } function engagement_panels_() { let res = []; let v = 0, k = 1, count = 0; for (const ePanel of document.querySelectorAll('ytd-watch-flexy ytd-engagement-panel-section-list-renderer[tabview-attr-checked]')) { let visibility = ePanel.getAttribute('visibility') //ENGAGEMENT_PANEL_VISIBILITY_EXPANDED //ENGAGEMENT_PANEL_VISIBILITY_HIDDEN switch (visibility) { case 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED': v |= k; count++; res.push({ ePanel, k, visible: true }); break; case 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN': res.push({ ePanel, k, visible: false }); break; default: res.push({ ePanel, k, visible: false }); } k = k << 1; } return { list: res, value: v, count: count }; } function ytBtnOpenEngagementPanel(panel_id) { if (typeof panel_id == 'string') { panel_id = panel_id.replace('#engagement-panel-', ''); panel_id = parseInt(panel_id); } if (panel_id >= 0) {} else return false; let panels = engagement_panels_(); for (const { ePanel, k, visible } of panels.list) { if ((panel_id & k) === k) { if (!visible) ePanel.setAttribute('visibility', "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"); } else { if (visible) ytBtnCloseEngagementPanel(ePanel); } } } function ytBtnCloseEngagementPanel(s) { //ePanel.setAttribute('visibility',"ENGAGEMENT_PANEL_VISIBILITY_HIDDEN"); let btn = s.querySelector('ytd-watch-flexy ytd-engagement-panel-title-header-renderer #header>#visibility-button>ytd-button-renderer'); if (btn) { btn.click(); } } function ytBtnCloseEngagementPanels() { if (isEngagementPanelExpanded()) { for (const s of document.querySelectorAll('ytd-watch-flexy ytd-engagement-panel-section-list-renderer[tabview-attr-checked]')) { if (s.getAttribute('visibility') == "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED") ytBtnCloseEngagementPanel(s); } } } function ytBtnSetTheater() { if (!isTheater()) { const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button') if (sizeBtn) sizeBtn.click(); } } function ytBtnCancelTheater() { if (isTheater()) { const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button') if (sizeBtn) sizeBtn.click(); } } function ytBtnExpandChat() { let button = document.querySelector('ytd-live-chat-frame#chat[collapsed]>.ytd-live-chat-frame#show-hide-button') if (button) button.querySelector('ytd-toggle-button-renderer').click(); } function ytBtnCollapseChat() { let button = document.querySelector('ytd-live-chat-frame#chat:not([collapsed])>.ytd-live-chat-frame#show-hide-button') if (button) button.querySelector('ytd-toggle-button-renderer').click(); } function hackImgShadow(imgShadow) { // add to #columns and add back after loaded let img = imgShadow.querySelector('img') if (!img) return; let p = imgShadow.parentNode let z = $(imgShadow).clone()[0]; //to occupy the space p.replaceChild(z, imgShadow) $(imgShadow).prependTo('#columns'); // refer to css hack function onload(evt) { if (evt) this.removeEventListener('load', onload, false) p.replaceChild(imgShadow, z) p = null; z = null; imgShadow = null; } if (img.complete) onload(); else img.addEventListener('load', onload, false) } const Q = {} const FOnce = {} const $callOnceAsync = async function(key) { if (FOnce[key] && FOnce[key]() === false) FOnce[key] = null } function chatFrameContentDocument() { // non-null if iframe exist && contentDocument && readyState = complete let iframe = document.querySelector('ytd-live-chat-frame iframe#chatframe'); if (!iframe) return null; //iframe must be there let cDoc = null; try { cDoc = iframe.contentDocument; } catch (e) {} if (!cDoc) return null; if (cDoc.readyState != 'complete') return null; //we must wait for its completion return cDoc; } function chatFrameElement(cssSelector) { let cDoc = chatFrameContentDocument(); if (!cDoc) return null; let elm = null; try { elm = cDoc.querySelector(cssSelector) } catch (e) { console.log('iframe error', e) } return elm; } function fixTabs() { if (!scriptEnable) return; let queryElement = document.querySelector('*:not(#tab-videos)>#related:not([non-placeholder-videos]) > ytd-watch-next-secondary-results-renderer') let isRelocated = !!queryElement; if (isRelocated) { let relocatedRelated = queryElement.parentNode; // NOT NULL let right_tabs = document.querySelector('#right-tabs'); let tab_videos = right_tabs.querySelector("#tab-videos"); if (!right_tabs || !tab_videos) return; for (const s of relocatedRelated.querySelectorAll('#related')) { s.setAttribute('non-placeholder-videos', '') } let target_container = document.querySelector('ytd-watch-flexy:not([is-two-columns_]) #primary-inner, ytd-watch-flexy[is-two-columns_] #secondary-inner') if (target_container) target_container.append(right_tabs) // last-child let videos_related = relocatedRelated; // NOT NULL $('[placeholder-videos]').removeAttr('placeholder-videos'); $('[placeholder-for-youtube-play-next-queue]').removeAttr('placeholder-for-youtube-play-next-queue'); tab_videos.appendChild(videos_related); let videos_results_renderer = relocatedRelated.querySelector("ytd-watch-next-secondary-results-renderer"); if (videos_results_renderer) videos_results_renderer.setAttribute('data-dom-changed-by-tabview-youtube', scriptVersionForExternal); videos_related.setAttribute('placeholder-for-youtube-play-next-queue', '') videos_related.setAttribute('placeholder-videos', '') $('[placeholder-videos]').on("scroll", makeBodyScrollByEvt); } let chatroom = null; if (chatroom = document.querySelector('*:not([data-positioner="before|#chat"]) + ytd-live-chat-frame#chat, ytd-live-chat-frame#chat:first-child')) { let positioner = document.querySelector('tabview-youtube-positioner[data-positioner="before|#chat"]'); if (positioner) positioner.remove(); if (document.querySelector('.YouTubeLiveFilledUpView')) { // no relocation } else { $(chatroom).insertBefore('#right-tabs') } $(positioner ? positioner : document.createElement('tabview-youtube-positioner')).attr('data-positioner', 'before|#chat').insertBefore(chatroom) } } const injectionScript_fixAutoComplete = function() { // https://cdnjs.cloudflare.com/ajax/libs/JavaScript-autoComplete/1.0.4/auto-complete.min.js for (const s of document.querySelectorAll('[autocomplete="off"]:not([data-autocomplete-results-id])')) { let sc = s.sc; if (sc instanceof HTMLElement) { let id = Date.now(); s.setAttribute('data-autocomplete-results-id', id); sc.setAttribute('data-autocomplete-input-id', id); if (window.WeakRef) { s._sc = new WeakRef(sc); s.sc = null; delete s.sc; Object.defineProperty(s, 'sc', { get: function() { return s._sc.deref() || null; }, enumerable: true, configurable: true }) } if (sc.hasAttribute('autocomplete-disable-updatesc') && typeof s.updateSC == 'function') { window.removeEventListener('resize', s.updateSC); s.updateSC = function() {}; } sc.dispatchEvent(new CustomEvent('autocomplete-sc-exist')); } } }; function handlerAutoCompleteExist() { let autoComplete = this; autoComplete.removeEventListener('autocomplete-sc-exist', handlerAutoCompleteExist, false) let domId = autoComplete.getAttribute('data-autocomplete-input-id') let searchBox = autoComplete.ownerDocument.querySelector(`[data-autocomplete-results-id="${domId}"]`) if (!domId || !searchBox) return; let positioner = searchBox.nextSibling; if (positioner && positioner.nodeName.toLowerCase() == "autocomplete-positioner") {} else if (positioner && positioner.nodeName.toLowerCase() != "autocomplete-positioner") { $(positioner = document.createElement("autocomplete-positioner")).insertAfter(searchBox); } else { $(positioner = document.createElement("autocomplete-positioner")).prependTo(searchBox.parentNode); } $(autoComplete).prependTo(positioner); positioner.style.setProperty('--sb-margin-bottom', getComputedStyle(searchBox).marginBottom) positioner.style.setProperty('--height', searchBox.offsetHeight + 'px') } function mtf_fixAutoCompletePosition(elmAutoComplete) { elmAutoComplete.setAttribute('autocomplete-disable-updatesc', '') elmAutoComplete.addEventListener('autocomplete-sc-exist', handlerAutoCompleteExist, false) if (isMyScriptInChromeRuntime()) addScriptByURL(window.chrome.runtime.getURL('js/injectionScript_fixAutoComplete.js')); else addScript(`!!(${injectionScript_fixAutoComplete+''})()`); // addScriptByURL(`${githubURLBase}/${githubURLCommit}/js/injectionScript_fixAutoComplete.js`); } function mtf_AfterFixTabs() { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return; const rootElement = Q.mutationTarget || ytdFlexyElm; const autocomplete = rootElement.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search + autocomplete-positioner > .autocomplete-suggestions[data-autocomplete-input-id]:not([position-fixed-by-tabview-youtube])') if (autocomplete) { const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search') if (searchBox) { autocomplete.parentNode.setAttribute('position-fixed-by-tabview-youtube', ''); autocomplete.setAttribute('position-fixed-by-tabview-youtube', ''); autocomplete.setAttribute('userscript-scrollbar-render', '') if (!searchBox.hasAttribute('is-set-click-to-toggle')) { searchBox.setAttribute('is-set-click-to-toggle', '') searchBox.addEventListener('click', function() { setTimeout(() => { const autocomplete = document.querySelector(`.autocomplete-suggestions[data-autocomplete-input-id="${ this.getAttribute('data-autocomplete-results-id') }"]`) if (!autocomplete) return; const isNotEmpty = (autocomplete.textContent || '').length > 0 && (this.value || '').length > 0; if (isNotEmpty) { let elmVisible = isDOMVisible(autocomplete) if (elmVisible) $(autocomplete).hide(); else $(autocomplete).show(); } }, 20); }) let timeoutOnce_searchbox_keyup = new Timeout(); searchBox.addEventListener('keyup', function() { timeoutOnce_searchbox_keyup.set(() => { const autocomplete = document.querySelector(`.autocomplete-suggestions[data-autocomplete-input-id="${ this.getAttribute('data-autocomplete-results-id') }"]`) if (!autocomplete) return; const isNotEmpty = (autocomplete.textContent || '').length > 0 && (this.value || '').length > 0 if (isNotEmpty) { let elmVisible = isDOMVisible(autocomplete) if (!elmVisible) $(autocomplete).show(); } }, 20); }) } } } let currentLastVideo = rootElement.querySelector('[placeholder-videos] #items ytd-compact-video-renderer:last-of-type') let prevLastVideo = kRef(_cachedLastVideo); if (prevLastVideo !== currentLastVideo && currentLastVideo) { _cachedLastVideo = mWeakRef(currentLastVideo); } if (prevLastVideo !== currentLastVideo && currentLastVideo && prevLastVideo) { let isPrevRemoved = !prevLastVideo.parentNode function getVideoListHash() { let res = [...document.querySelectorAll('[placeholder-videos] #items ytd-compact-video-renderer')].map(renderer => { return renderer.querySelector('a[href*="watch"][href*="v="]').getAttribute('href') }).join('|') // /watch?v=XXXXX|/watch?v=XXXXXX|/watch?v=XXXXXX // alternative - DOM.data.videoId // let elms = document.querySelectorAll('[placeholder-videos] #items ytd-compact-video-renderer') // let res = [...elms].map(elm=>elm.data.videoId||'').join('|') ; if (res.indexOf('||') >= 0) { res = ''; } return res ? res : null; } if (isPrevRemoved) { // this is the replacement of videos instead of addition const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search') let currentPlayListHash = getVideoListHash() || null; if (!currentPlayListHash) { } else if (!videoListBeforeSearch && searchBox) { videoListBeforeSearch = currentPlayListHash; if (videoListBeforeSearch) { //console.log('fromSearch', videoListBeforeSearch) requestAnimationFrame(function() { let renderer = document.querySelector('[placeholder-videos] ytd-watch-next-secondary-results-renderer'); if (searchBox && searchBox.parentNode) searchBox.blur(); if (renderer) { let scrollParent = renderer.parentNode; if (scrollParent.scrollHeight > scrollParent.offsetHeight) { let targetTop = renderer.offsetTop; if (searchBox && searchBox.parentNode == scrollParent) targetTop -= searchBox.offsetHeight scrollParent.scrollTop = targetTop - scrollParent.firstChild.offsetTop; } } }); } } else if (videoListBeforeSearch) { if (currentPlayListHash != videoListBeforeSearch) { videoListBeforeSearch = null; //console.log('fromSearch', videoListBeforeSearch) } } } } } function base_ChatExist() { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return null; // no mutation triggering if the changes are inside the iframe // 1) Detection of #continuations inside iframe // iframe ownerDocument is accessible due to same origin // if the chatroom is collasped, no determination of live chat or replay (as no #continuations and somehow a blank iframe doc) // 2) Detection of meta tag // This is fastest but not reliable. It is somehow a bug that the navigation might not update the meta tag content // 3) Detection of HTMLElement inside video player for live video // (1)+(3) = solution let attr_chatblock = null let attr_chatcollapsed = null; const elmChat = document.querySelector('ytd-live-chat-frame#chat') let elmCont = null; if (elmChat) { elmCont = chatFrameElement('yt-live-chat-renderer #continuations') let s = 0; if (elmCont) { //not found if it is collasped. s |= elmCont.querySelector('yt-timed-continuation') ? 1 : 0; s |= elmCont.querySelector('yt-live-chat-replay-continuation, yt-player-seek-continuation') ? 2 : 0; //s |= elmCont.querySelector('yt-live-chat-restricted-participation-renderer')?4:0; if (s == 1) { //console.log(7005) attr_chatblock = 'chat-live'; //disableComments_LiveChat(); } if (s == 2) attr_chatblock = 'chat-playback'; //if(s==5) attr_chatblock='chat-live-paid'; if (s == 1) $("span#tab3-txt-loader").text(''); } //keep unknown as original } else { attr_chatblock = false; attr_chatcollapsed = false; } return { attr_chatblock, attr_chatcollapsed } } function mtf_ChatExist() { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return; const elmChat = document.querySelector('ytd-live-chat-frame#chat') let elmCont = null; if (elmChat) { elmCont = chatFrameElement('yt-live-chat-renderer #continuations') } const chatBlockR = (elmChat ? 1 : 0) + (elmCont ? 2 : 0) if (Q.mtf_chatBlockQ !== chatBlockR) { Q.mtf_chatBlockQ = chatBlockR let rChatExist = base_ChatExist(); if (rChatExist) { let { attr_chatblock, attr_chatcollapsed } = rChatExist; wAttr(ytdFlexyElm, 'userscript-chatblock', attr_chatblock) wAttr(ytdFlexyElm, 'userscript-chat-collapsed', attr_chatcollapsed) } } } let lastScrollAt1 = 0; function makeBodyScrollByEvt() { let ct = Date.now(); if (ct - lastScrollAt1 < 6) return; // avoid duplicate calling lastScrollAt1 = ct; window.dispatchEvent(new Event("scroll")); // dispatch Scroll Event to Window for content display } let lastScrollAt2 = 0; function makeBodyScroll() { let ct = Date.now(); if (ct - lastScrollAt2 < 30) return; // avoid over triggering lastScrollAt2 = ct; requestAnimationFrame(() => { window.dispatchEvent(new Event("scroll")); // ask youtube to display content }) } let requestingComments = null let scrollForComments_lastStart = 0; function scrollForComments_TF() { let comments = requestingComments; if (!comments) return; if (comments.hasAttribute('hidden')) { window.dispatchEvent(new Event("scroll")); } else requestingComments = null; } function scrollForComments() { scrollForComments_TF(); if (!requestingComments) return; requestAnimationFrame(scrollForComments_TF); let ct = Date.now(); if (ct - scrollForComments_lastStart < 60) return; scrollForComments_lastStart = ct; setTimeout(scrollForComments_TF, 80); setTimeout(scrollForComments_TF, 240); setTimeout(scrollForComments_TF, 870); } const mtoCs = { mtoNav: null, mtoBody: null }; const mtoVs = {} const mutation_target_id_list = ['ytp-caption-window-container', 'items', 'button', 'movie_player', 'player-ads', 'hover-overlays', 'replies']; const mutation_target_class_list = ['ytp-panel-menu', 'ytp-endscreen-content']; function isMtoOverallSkip(dTarget) { if (!dTarget || dTarget.nodeType !== 1) return true; if (mutation_target_id_list.includes(dTarget.id)) return true; let className = dTarget.className.toLowerCase(); let classNameSplit = className.split(' '); for (const c of classNameSplit) { if (mutation_target_class_list.includes(c)) return true; } return false; } const mutation_div_id_ignorelist = [ 'metadata-line', 'ytp-caption-window-container', 'top-level-buttons-computed', 'microformat', 'visibility-button', 'info-strings', 'action-menu', 'reply-button-end' ]; const mutation_div_class_ignorelist = [ 'badge', 'tp-yt-paper-tooltip', 'ytp-autonav-endscreen-upnext-header', 'ytp-bound-time-left', 'ytp-bound-time-right', 'ytp-share-icon', 'ytp-tooltip-title', 'annotation', 'ytp-copylink-icon', 'ytd-thumbnail', 'paper-ripple', //caption 'captions-text', 'caption-visual-line', 'ytp-caption-segment', 'ytp-caption-window-container', //menu 'ytp-playlist-menu-button-text', 'ytp-bezel-icon', 'ytp-bezel-text', 'dropdown-content', 'tp-yt-paper-menu-button', 'tp-yt-iron-dropdown', 'ytd-metadata-row-renderer', // #content.ytd-metadata-row-renderer inside each of ytd-metadata-row-renderer (ytd-expander) 'ytd-engagement-panel-section-list-renderer', // {div#content.style-scope.ytd-engagement-panel-section-list-renderer} inside each of ytd-engagement-panel-section-list-renderer 'autocomplete-suggestions' // autocomplete-suggestions ]; const mutation_target_tag_ignorelist = [ 'ytd-channel-name', 'tp-yt-iron-dropdown', 'tp-yt-paper-tooltip', 'tp-yt-paper-listbox', 'yt-img-shadow', 'ytd-thumbnail', 'ytd-video-meta-block', 'yt-icon-button', 'tp-yt-paper-button', 'yt-formatted-string', 'yt-icon', 'button', 'paper-ripple', 'ytd-player-microformat-renderer', 'ytd-engagement-panel-section-list-renderer', 'ytd-engagement-panel-title-header-renderer', 'ytd-comment-renderer', 'ytd-menu-renderer', 'ytd-badge-supported-renderer', 'ytd-subscribe-button-renderer', 'ytd-subscription-notification-toggle-button-renderer', 'ytd-button-renderer', 'ytd-toggle-button-renderer', 'yt-pdg-comment-chip-renderer', 'ytd-comment-action-buttons-renderer', 'ytd-comment-thread-renderer', 'ytd-compact-radio-renderer', 'ytd-compact-video-renderer', 'ytd-video-owner-renderer', 'ytd-metadata-row-renderer', //ytd-metadata-row-renderer is part of the #collapsible inside ytd-expander 'ytd-moving-thumbnail-renderer', 'ytd-thumbnail-overlay-toggle-button-renderer', 'ytd-thumbnail-overlay-bottom-panel-renderer', 'ytd-thumbnail-overlay-equalizer', 'ytd-thumbnail-overlay-now-playing-renderer', 'ytd-thumbnail-overlay-resume-playback-renderer', 'ytd-thumbnail-overlay-side-panel-renderer', 'ytd-thumbnail-overlay-time-status-renderer', 'ytd-thumbnail-overlay-hover-text-renderer', 'yt-interaction', 'tp-yt-paper-spinner-lite', 'tp-yt-paper-spinner', 'h1', 'h2', 'h3', 'h4', 'h5', 'span', 'a', 'meta', 'br', 'script', 'style', 'link', 'dom-module', 'template' ]; function isMtoTargetSkip(mutation) { //skip not important mutation tartget if (!mutation) return true; let { type, target } = mutation if (!target || target.nodeType !== 1 || type != 'childList') return true; let tagName = target.nodeName.toLowerCase(); let className; let classNameSplit; if (mutation_target_tag_ignorelist.includes(tagName)) return true; switch (tagName) { case 'ytd-expander': if (target.id == 'expander' && Q.comments_section_loaded == 1 && target.classList.contains('ytd-comment-renderer')) return true; // load comments return false; case 'div': if (target.id == 'contents') { return false; } if (mutation_div_id_ignorelist.includes(target.id)) return true; className = target.className.toLowerCase(); classNameSplit = className.split(' '); for (const c of classNameSplit) { if (mutation_div_class_ignorelist.includes(c)) return true; } return false; } return false; } // continuous check for element relocation function mtf_append_comments() { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return; const rootElement = Q.mutationTarget || ytdFlexyElm; let comments = rootElement.querySelector('#primary ytd-watch-metadata ~ ytd-comments#comments'); if (comments) $(comments).appendTo('#tab-comments').attr('data-dom-changed-by-tabview-youtube', scriptVersionForExternal) } // continuous check for element relocation function mtf_liveChatBtnF() { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return; const rootElement = Q.mutationTarget || ytdFlexyElm; let button = rootElement.querySelector('ytd-live-chat-frame#chat>.ytd-live-chat-frame#show-hide-button:nth-child(n+2)'); if (button) button.parentNode.insertBefore(button, button.parentNode.firstChild) } // continuous check for element relocation // fired at begining & window resize, etc function mtf_append_playlist() { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return; const rootElement = Q.mutationTarget || ytdFlexyElm; let ple1 = rootElement.querySelector("*:not(#ytd-userscript-playlist)>ytd-playlist-panel-renderer#playlist"); if (ple1) { let ct = Date.now(); let truePlaylist = null; let truePlaylist_items = document.querySelector('ytd-playlist-panel-renderer#playlist #items:not(:empty)'); if (truePlaylist_items) { let pElm = truePlaylist_items.parentNode; while (pElm && pElm.nodeType === 1) { if (pElm.id == 'playlist') { pElm.setAttribute('tabview-true-playlist', ct) truePlaylist = pElm; break; } pElm = pElm.parentNode; } } if (!truePlaylist) truePlaylist = ple1; // NOT NULL for (const s of document.querySelectorAll(`*:not(#ytd-userscript-playlist)>ytd-playlist-panel-renderer#playlist:not([tabview-true-playlist="${ct}"])`)) s.parentNode.removeChild(s); let $wrapper = getWrapper('ytd-userscript-playlist') $wrapper.append(truePlaylist).appendTo(document.querySelector("#tab-list")); truePlaylist.setAttribute('data-dom-changed-by-tabview-youtube', scriptVersionForExternal) setDisplayedPlaylist(); // relocation after re-layout requestAnimationFrame(() => { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return; if (!switchTabActivity_lastTab && (ytdFlexyElm.getAttribute('tabview-selection') + '').indexOf('#tab-') === 0 && /https\:\/\/www\.youtube\.com\/watch.*[\?\&]list=[\w\-\_]+/.test(location.href)) { if (setToActiveTab('#tab-list')) switchTabActivity_lastTab = '#tab-list'; } }) } } // content fix - info & playlist // fired at begining, and keep for in case any change function mtf_fix_details() { if (!scriptEnable) return; if (no_fix_contents_until < Date.now()) { const content = document.querySelector('#meta-contents ytd-expander>#content, #tab-info ytd-expander>#content') if (content) { no_fix_contents_until = Date.now() + 3000; setTimeout(function() { const expander = content.parentNode; if (expander.hasAttribute('collapsed')) wAttr(expander, 'collapsed', false); expander.style.setProperty('--ytd-expander-collapsed-height', ''); let btn1 = expander.querySelector('tp-yt-paper-button#less:not([hidden])'); let btn2 = expander.querySelector('tp-yt-paper-button#more:not([hidden])'); if (btn1) wAttr(btn1, 'hidden', true); if (btn2) wAttr(btn2, 'hidden', true); }, 40); } } if (no_fix_playlist_until < Date.now()) { // just in case the playlist is collapsed const playlist = document.querySelector('#tab-list ytd-playlist-panel-renderer#playlist') if (playlist) { no_fix_playlist_until = Date.now() + 3000; setTimeout(function() { if (playlist.hasAttribute('collapsed')) wAttr(playlist, 'collapsed', false); if (playlist.hasAttribute('collapsible')) wAttr(playlist, 'collapsible', false); }, 40) } } } function status_commentsHidden() { var comments = document.querySelector('ytd-comments#comments') if (!comments || (comments.childElementCount === 0 && comments.hasAttribute('hidden'))) return 2; if (comments.hasAttribute('hidden')) return 1; return 0; } function getFMT(ytdFlexyElm) { let fmt = ["ytd-comments#comments #count.ytd-comments-header-renderer", 'ytd-item-section-renderer#sections #header ~ #contents>ytd-message-renderer'].map(s => { let elm = ytdFlexyElm.querySelector(s) if (!elm) return null; return elm.querySelector('yt-formatted-string') || elm.closest('yt-formatted-string') || null }); return fmt } function toFST(elm) { if (!elm) return null; return elm.querySelector('yt-formatted-string') || elm.closest('yt-formatted-string') || null } function innerCommentsLoader() { const rootElement = Q.mutationTarget || kRef(ytdFlexy); if (!rootElement) return; let messageElm, messageStr, commentRenderer; if (commentRenderer = rootElement.querySelector("ytd-comments#comments #count.ytd-comments-header-renderer")) { return { status: 1, f: () => { let span = document.querySelector("span#tab3-txt-loader") Q.fetchedOnce = true; let r = '0'; let txt = commentRenderer.textContent if (typeof txt == 'string') { let m = txt.match(/[\d\,\s]+/) if (m) { r = m[0].trim() } } if (span) span.textContent = r; mtoInterval = mtoInterval2; setCommentSection(1); cmItem = mWeakRef(toFST(commentRenderer)) comments_section_loaded_elm = mWeakRef(document.querySelector('ytd-comments#comments div#contents')) } } } else if ((messageElm = rootElement.querySelector('ytd-item-section-renderer#sections #header ~ #contents>ytd-message-renderer')) && (messageStr = (messageElm.textContent || '').trim())) { //ytd-message-renderer // it is possible to get the message before the header generation. return { status: 2, f: () => { setTimeout(function() { if (Q.fetchedOnce) return; let span = document.querySelector("span#tab3-txt-loader") const mainMsg = messageElm.querySelector('#message, #submessage') if (mainMsg && mainMsg.textContent) { for (const msg of mainMsg.querySelectorAll('*:not(:empty)')) { if (msg.childElementCount === 0 && msg.textContent) { messageStr = msg.textContent.trim() break } } } if (span) span.textContent = messageStr; mtoInterval = mtoInterval2; setCommentSection(1); cmItem = mWeakRef(toFST(messageElm)) comments_section_loaded_elm = mWeakRef(document.querySelector('ytd-comments#comments div#contents')) }, 40); } } } } const FP = { mtoNav_delayedF: () => { let { addP, removeP, mutationTarget } = Q; Q.addP = 0; Q.removeP = 0; let isInvalidAdding = Q.mutationTarget && !Q.mutationTarget.parentNode let promisesForAddition = !scriptEnable ? [] : addP > 0 && !isInvalidAdding ? [ $callOnceAsync('mtf_checkFlexy'), $callOnceAsync('mtf_initalAttr_comments'), $callOnceAsync('mtf_initalAttr_playlist'), $callOnceAsync('mtf_initalAttr_chatroom'), $callOnceAsync('mtf_initalAttr_engagement_panel'), $callOnceAsync('mtf_advancedComments'), $callOnceAsync('mtf_checkDescriptionLoaded'), $callOnceAsync('mtf_fetchCommentsAvailable'), $callOnceAsync('mtf_forceCheckLiveVideo'), (async () => { mtf_append_comments(); })(), (async () => { mtf_liveChatBtnF(); })(), (async () => { fixTabs(); mtf_AfterFixTabs(); })(), (async () => { mtf_append_playlist(); })() ] : []; let promisesForEveryMutation = !scriptEnable ? [] : [ (async () => { mtf_fix_details(); })(), (async () => { mtf_ChatExist(); })() ]; Promise.all([...promisesForAddition, ...promisesForEveryMutation]).then(() => { Q.mutationTarget = null; Q.mtoNav_requestNo--; //console.log('motnav reduced to', mtoNav_requestNo) if (Q.mtoNav_requestNo > 0) { Q.mtoNav_requestNo = 1; setTimeout(FP.mtoNav_delayedF, mtoInterval); } }) }, mtoNavF: (mutations, observer) => { if (!scriptEnable) return; let ch = false; let reg = []; let dTarget = null; let wAddP = 0, wRemoveP = 0; let _last_mto_target = null; let _last_mto_target_valid = null; for (const mutation of mutations) { if (!mutation || !mutation.target || !mutation.target.parentNode) continue; let elementalMutation = false; let tAddP = 0, tRemoveP = 0; for (const addedNode of mutation.addedNodes) { //theoretically faster: only reading of states if (addedNode.nodeType === 1) { tAddP++; elementalMutation = true; } } for (const removedNode of mutation.removedNodes) { //theoretically faster: only reading of states if (removedNode.nodeType === 1) { tRemoveP++; elementalMutation = true; } } if (elementalMutation) { //skip all addition and removal operations without elemental changes (e.g. textNode modification) if (_last_mto_target === mutation.target) { // due to addition and removal operations to the same DOM if (_last_mto_target_valid) { // AddP & RemoveP is still valid wAddP += tAddP; wRemoveP += tRemoveP; } continue; } _last_mto_target = mutation.target; if (isMtoTargetSkip(mutation)) { _last_mto_target_valid = false; continue; //theoretically slower: creation of string variables } else { _last_mto_target_valid = true; wAddP += tAddP; wRemoveP += tRemoveP; } ch = true; reg.push(mutation); if (dTarget === null) dTarget = mutation.target; //first else if (dTarget === true) {} //ytdFlexy else if (dTarget.contains(mutation.target)) {} //first node is the container to all subsequential targets else { dTarget = true; } //the target is not the child of first node } } if (!ch) return; // dTarget must be true OR HTMLElement if (dTarget === true) dTarget = kRef(ytdFlexy); // major mutation occurance else if (dTarget === kRef(comments_section_loaded_elm) && wAddP > wRemoveP) return true; // ignore if comments are loaded (adding comments) else if (isMtoOverallSkip(dTarget)) return; // allow for multiple mutations at the same time - determinated after looping // 4 ~ 16 times per full page loading Q.addP += wAddP; Q.removeP += wRemoveP; if (Q.mutationTarget === null) Q.mutationTarget = dTarget; else if (Q.mutationTarget != dTarget) Q.mutationTarget = kRef(ytdFlexy); //console.log(prettyElm(dTarget), wAddP , wRemoveP, mtoInterval) //console.log(prettyElm(dTarget), reg.map(m=>prettyElm(m.target))) //console.log(7015, performance.now()) Q.mtoNav_requestNo++; if (Q.mtoNav_requestNo == 1) setTimeout(FP.mtoNav_delayedF, mtoInterval); }, mtoBodyF: function(mutations, observer) { if (!scriptEnable) return; for (const mutation of mutations) { for (const addedNode of mutation.addedNodes) if (addedNode.nodeType === 1) { if (addedNode.nodeName == "DIV" && addedNode.className.indexOf('autocomplete-suggestions') >= 0) { mtf_fixAutoCompletePosition(addedNode) } } } }, mtf_attrPlaylist: (mutations, observer) => { if (!scriptEnable) return; let cssElm = kRef(ytdFlexy); if (!cssElm) return; var playlist = document.querySelector('ytd-playlist-panel-renderer#playlist') const tabBtn = document.querySelector('[userscript-tab-content="#tab-list"]'); if (tabBtn) { //console.log('attr playlist changed') if (tabBtn.classList.contains('tab-btn-hidden') && !playlist.hasAttribute('hidden')) { //console.log('attr playlist changed - no hide') tabBtn.classList.remove("tab-btn-hidden"); } else if (!tabBtn.classList.contains('tab-btn-hidden') && playlist.hasAttribute('hidden')) { //console.log('attr playlist changed - add hide') hideTabBtn(tabBtn); } } /* visible layout for triggering hidden removal */ akAttr(cssElm, 'tabview-youtube-playlist', playlist.hasAttribute('hidden')); }, delayed_disableComments: function() { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return; if (status_commentsHidden() == 2) _disableComments(); }, mtf_attrComments: (mutations, observer) => { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return; var comments = document.querySelector('ytd-comments#comments') const tabBtn = document.querySelector('[userscript-tab-content="#tab-comments"]'); if (!comments || !tabBtn) return; let isCommentHidden = comments.hasAttribute('hidden') //console.log('attr comments changed') timeout_attrComments.clear(); $('span#tab3-txt-loader').text(''); mtoInterval = mtoInterval1; if (!isCommentHidden) { akAttr(ytdFlexyElm, 'tabview-youtube-comments', false, 'K'); //console.log('attr comments changed - no hide') tabBtn.classList.remove("tab-btn-hidden") //if contains makeBodyScroll(); Q.fetchedOnce = false FOnce.mtf_fetchCommentsAvailable = FP.mtf_fetchCommentsAvailable; if (Q.mutationTarget === null) $callOnceAsync('mtf_fetchCommentsAvailable'); window.requestAnimationFrame(() => { let innerCommentsLoaderRet = innerCommentsLoader(); if (innerCommentsLoaderRet) innerCommentsLoaderRet.f(); }) } else if (isCommentHidden) { //console.log('attr comments changed - add hide') akAttr(ytdFlexyElm, 'tabview-youtube-comments', true, 'K'); let t = status_commentsHidden() == 2 ? 1630 : 760; timeout_attrComments.set(FP.delayed_disableComments, t); } requestingComments = comments; scrollForComments(); setTimeout(() => nativeFunc(comments, "loadComments"), 20) }, mtf_attrChatroom: (mutations, observer) => { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return; layoutStatusMutex.lockWith(unlock => { const chatBlock = document.querySelector('ytd-live-chat-frame#chat') const cssElm = kRef(ytdFlexy) if (!chatBlock || !cssElm) { unlock(); return; } if (!cssElm.hasAttribute('userscript-chatblock')) wAttr(cssElm, 'userscript-chatblock', true); let isCollapsed = !!chatBlock.hasAttribute('collapsed'); wAttr(cssElm, 'userscript-chat-collapsed', isCollapsed); if (cssElm.hasAttribute('userscript-chatblock') && !isCollapsed) lastShowTab = '#chatroom'; if (!isCollapsed && document.querySelector('#right-tabs .tab-btn.active') && isWideScreenWithTwoColumns() && !isTheater()) { switchTabActivity(null); setTimeout(unlock, 40); } else { unlock(); } if (!isCollapsed) { runAfterExpandChat(); } else { chatBlock.removeAttribute('yt-userscript-iframe-loaded'); } }) }, mtf_attrEngagementPanel: (mutations, observer) => { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return; //multiple instance if (mutations) { let cPanels = null; for (const mutation of mutations) { let ePanel = mutation.target; if (ePanel.getAttribute('visibility') == 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED') { cPanels = cPanels || engagement_panels_(); for (const entry of cPanels.list) { if (entry.ePanel != ePanel && entry.visible) ytBtnCloseEngagementPanel(entry.ePanel); } } } } layoutStatusMutex.lockWith(unlock => { const ePanel = document.querySelector('ytd-watch-flexy ytd-engagement-panel-section-list-renderer') const cssElm = kRef(ytdFlexy) if (!ePanel || !cssElm) { unlock(); return; } let previousValue = +cssElm.getAttribute('userscript-engagement-panel') || 0; let { value, count } = engagement_panels_(); let nextValue = value; let nextCount = count; if (nextCount == 0 && cssElm.hasAttribute('userscript-engagement-panel')) { wAttr(cssElm, 'userscript-engagement-panel', false); unlock(); } else { if ((nextCount > 1) || (cssElm.hasAttribute('userscript-engagement-panel') && previousValue === nextValue)) { unlock(); return; } cssElm.setAttribute('userscript-engagement-panel', nextValue); let b = false; if (previousValue != nextValue && nextValue > 0) { lastShowTab = `#engagement-panel-${nextValue}`; b = true; } if (b && document.querySelector('#right-tabs .tab-btn.active') && isWideScreenWithTwoColumns() && !isTheater()) { switchTabActivity(null); setTimeout(unlock, 40); } else { unlock(); } } }) }, mtf_initalAttr_playlist: () => { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return true; const rootElement = Q.mutationTarget || ytdFlexyElm; var playlist = rootElement.querySelector('ytd-playlist-panel-renderer#playlist') if (!playlist) return true; initMutationObserver(mtoVs, 'mtoVisibility_Playlist', FP.mtf_attrPlaylist).observe(playlist, { attributes: true, attributeFilter: ['hidden'], attributeOldValue: true }) FP.mtf_attrPlaylist() return false; }, mtf_initalAttr_comments: () => { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return true; const rootElement = Q.mutationTarget || ytdFlexyElm; var comments = rootElement.querySelector('ytd-comments#comments') if (!comments) return true; initMutationObserver(mtoVs, 'mtoVisibility_Comments', FP.mtf_attrComments).observe(comments, { attributes: true, attributeFilter: ['hidden'], attributeOldValue: true }) FP.mtf_attrComments() requestingComments = comments; //scrollForComments() setTimeout(() => nativeFunc(comments, "loadComments"), 20) return false; }, mtf_initalAttr_chatroom: () => { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return true; const rootElement = Q.mutationTarget || ytdFlexyElm; var chatroom = rootElement.querySelector('ytd-live-chat-frame#chat') if (!chatroom) return true; initMutationObserver(mtoVs, 'mtoVisibility_Chatroom', FP.mtf_attrChatroom).observe(chatroom, { attributes: true, attributeFilter: ['collapsed'], attributeOldValue: true }) FP.mtf_attrChatroom() return false; }, mtf_initalAttr_engagement_panel: () => { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return true; const rootElement = Q.mutationTarget || ytdFlexyElm; let toCheck = false; for (const engagement_panel of rootElement.querySelectorAll('ytd-watch-flexy ytd-engagement-panel-section-list-renderer:not([tabview-attr-checked])')) { engagement_panel.setAttribute('tabview-attr-checked', ''); let mto = mtoVs.mtoVisibility_EngagementPanel; if (!mto) mto = initMutationObserver(mtoVs, 'mtoVisibility_EngagementPanel', FP.mtf_attrEngagementPanel); mto.observe(engagement_panel, { attributes: true, attributeFilter: ['visibility'], attributeOldValue: true }) toCheck = true; } if (toCheck) FP.mtf_attrEngagementPanel() return true; }, mtf_checkDescriptionLoaded: () => { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return true; const rootElement = Q.mutationTarget || ytdFlexyElm; const expander = document.querySelector("#meta-contents ytd-expander"); if (!expander) return true; $(expander).appendTo("#tab-info").attr('data-dom-changed-by-tabview-youtube', scriptVersionForExternal) const avatar = document.querySelector('#meta-contents yt-img-shadow#avatar'); if (avatar) hackImgShadow(avatar) return false; }, //live-chat / chat-replay fireOnce_forceCheckLiveVideo_tf: () => { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return; let elm = document.querySelector('#ytd-player .ytp-time-display'); if (elm && elm.classList.contains('ytp-live')) { //console.log(7006) ytdFlexyElm.setAttribute('userscript-chatblock', 'chat-live') //disableComments_LiveChat(); } }, mtf_forceCheckLiveVideo: () => { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return true; const rootElement = Q.mutationTarget || ytdFlexyElm; const playerLabel = document.querySelector('#ytd-player .ytp-time-display') && document.querySelector('ytd-live-chat-frame#chat') if (!playerLabel) return true; setTimeout(FP.fireOnce_forceCheckLiveVideo_tf, 170) return false; }, //comments mtf_advancedComments: () => { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return true; const rootElement = Q.mutationTarget || ytdFlexyElm; const continuations = document.querySelector("ytd-comments#comments #continuations"); if (!continuations) return true; requestingComments = document.querySelector('ytd-comments#comments'); scrollForComments(); return false; }, mtf_fetchCommentsAvailable: () => { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return true; let innerCommentsLoaderRet = innerCommentsLoader(); if (!innerCommentsLoaderRet) return true; innerCommentsLoaderRet.f(); fetchCommentsFinished(); return false; } } function initObserver() { Q.addP = 0; Q.removeP = 0; Q.mutationTarget = null; Q.mtoNav_requestNo = 0; initMutationObserver(mtoCs, 'mtoNav', FP.mtoNavF).observe(kRef(ytdFlexy), { subtree: true, childList: true, attributes: false }); - (async () => { Q.addP = 1; //force all checking Q.mtoNav_requestNo++; if (Q.mtoNav_requestNo == 1) FP.mtoNav_delayedF(); })(); // for automcomplete plugin initMutationObserver(mtoCs, 'mtoBody', FP.mtoBodyF).observe(document.querySelector('body'), { childList: true, subtree: false, attributes: false }) } let displayedPlaylist = null; let scrollingVideosList = null; let scriptEnable = false; let scriptEC = 0; let lastShowTab = null; let _cachedLastVideo = null; let videoListBeforeSearch = null; let no_fix_contents_until = 0; let no_fix_playlist_until = 0; let statusCollasped = 0; let ytdFlexy = null; let timeout_attrComments = new Timeout(); function pluginHook() { scriptEnable = true; scriptEC++; no_fix_contents_until = 0; no_fix_playlist_until = 0; ytdFlexy = mWeakRef(document.querySelector('ytd-watch-flexy')) } function pluginUnhook() { //console.log(8001) timeout_attrComments.clear(); videoListBeforeSearch = null; statusCollasped = 0; _cachedLastVideo = null; lastShowTab = null; displayedPlaylist = null; scrollingVideosList = null; scriptEnable = false; scriptEC++; if (scriptEC > 788888888) scriptEC = 188888888; ytdFlexy = null; wls.layoutStatus = null; clearMutationObserver(mtoVs, 'mtoVisibility_Playlist') clearMutationObserver(mtoVs, 'mtoVisibility_Comments') clearMutationObserver(mtoVs, 'mtoVisibility_Chatroom') clearMutationObserver(mtoVs, 'mtoFlexyAttr') clearMutationObserver(mtoCs, 'mtoBody') if (clearMutationObserver(mtoCs, 'mtoNav')) { FOnce.mtf_checkFlexy = null; FOnce.mtf_initalAttr_comments = null; FOnce.mtf_initalAttr_playlist = null; FOnce.mtf_initalAttr_chatroom = null; FOnce.mtf_initalAttr_engagement_panel = null; FOnce.mtf_advancedComments = null; FOnce.mtf_checkDescriptionLoaded = null; FOnce.mtf_fetchCommentsAvailable = null; FOnce.mtf_forceCheckLiveVideo = null; Q.mtf_chatBlockQ = null; } mtoInterval = mtoInterval1; } let comments_section_loaded_elm = null; function initializeForVideoChange() { pluginUnhook(); if (!document.querySelector('script#userscript-tabview-injection-1')) { if (isMyScriptInChromeRuntime()) addScriptByURL(window.chrome.runtime.getURL('js/injection_script_1.js')).id = 'userscript-tabview-injection-1'; else addScript(`!!(${injection_script_1+""})()`).id = 'userscript-tabview-injection-1' // addScriptByURL(`${githubURLBase}/${githubURLCommit}/js/injection_script_1.js`).id='userscript-tabview-injection-1'; } var prevComemnts = document.querySelector('ytd-comments#comments'); if (prevComemnts && prevComemnts.matches('[hidden]')) { // assumption: loading comments after executation of this function which removes the attribute [hidden] of #ytd-comments#comments var prevCommentsHeader = prevComemnts.querySelector('ytd-comments#comments ytd-comments-header-renderer'); var prevCommentsMsg = prevComemnts.querySelector('ytd-item-section-renderer#sections #header ~ #contents>ytd-message-renderer'); //removed any cache of #comments header (i.e. count message) if (prevCommentsHeader) prevCommentsHeader.parentNode.removeChild(prevCommentsHeader); //removed any cache of #comments message (i.e. 留言功能已停用。) if (prevCommentsMsg) prevCommentsMsg.parentNode.removeChild(prevCommentsMsg); } window.requestAnimationFrame(() => { // requestAnimationFrame is required to let youtube coding running first ! pluginHook(); _onNavigationEnd(); setTimeout(() => { //ensure comment tab text is updated - no matter there is change of video or not let innerCommentsLoaderRet = innerCommentsLoader(); if (innerCommentsLoaderRet) innerCommentsLoaderRet.f(); }, 40); }) return true; } function getTabsHTML() { const sTabBtnVideos = `${svgElm(16,16,298,298,svgVideos)}<span>Videos</span>` const sTabBtnInfo = `${svgElm(16,16,23.625,23.625,svgInfo)}<span>Info</span>` const sTabBtnPlayList = `${svgElm(16,16,426.667,426.667,svgPlayList)}<span>Playlist</span>` let str1 = ` <paper-ripple class="style-scope yt-icon-button"> <div id="background" class="style-scope paper-ripple" style="opacity:0;"></div> <div id="waves" class="style-scope paper-ripple"></div> </paper-ripple> `; const str_tabs = [ `<a id="tab-btn1" data-name="info" userscript-tab-content="#tab-info" class="tab-btn">${sTabBtnInfo}${str1}</a>`, `<a id="tab-btn2" userscript-tab-content="#tab-live" class="tab-btn tab-btn-hidden">Chat${str1}</a>`, `<a id="tab-btn3" userscript-tab-content="#tab-comments" data-name="comments" class="tab-btn">${svgElm(16,16,60,60,svgComments)}<span id="tab3-txt-loader"></span>${str1}</a>`, `<a id="tab-btn4" userscript-tab-content="#tab-videos" data-name="videos" class="tab-btn">${sTabBtnVideos}${str1}</a>`, `<a id="tab-btn5" userscript-tab-content="#tab-list" class="tab-btn">${sTabBtnPlayList}${str1}</a>` ].join('') var addHTML = ` <div id="right-tabs"> <header> <div id="material-tabs"> ${str_tabs} </div> </header> <div class="tab-content"> <div id="tab-info" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div> <div id="tab-live" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div> <div id="tab-comments" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div> <div id="tab-videos" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div> <div id="tab-list" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div> </div> </div> `; return addHTML } function injection_script_1() { document.addEventListener('userscript-call-dom-func', function(evt) { if (!evt || !evt.target || !evt.detail) return; let dom = evt.target; let { property, args } = evt.detail; if (!property) return; let f = dom[property]; if (typeof f != 'function') return; if (args && args.length > 0) f.apply(dom, args); else f.call(dom); }, true) } function _onNavigationEnd() { let timeoutR_findRelated = new Timeout(); timeoutR_findRelated.set(function() { let related = kRef(ytdFlexy).querySelector("#related"); if (!related) return true; foundRelated(related); }, 100, 10) function foundRelated(related) { let promise = Promise.resolve(); if (!document.querySelector("#right-tabs")) { promise = promise.then(() => { $(getTabsHTML()).insertBefore(related).attr('data-dom-created-by-tabview-youtube', scriptVersionForExternal); }) } promise.then(runAfterTabAppended).then(initObserver) } setTimeout(() => { for (const s of document.querySelectorAll('#right-tabs [userscript-scrollbar-render]')) { Promise.resolve(s).then(s => { if (s && s.scrollTop > 0) s.scrollTop = 0; let child = s.firstElementChild; if (child && child.scrollTop > 0) child.scrollTop = 0; }); } }, 90) } function onNavigationEnd(evt) { pluginUnhook(); // in case not triggered by popstate - say mini playing $('span#tab3-txt-loader').text(''); setCommentSection(0) if (!/^https?\:\/\/(\w+\.)*youtube\.com\/watch\?(\w+\=[^\/\?\&]+\&|\w+\=?\&)*v=[\w\-\_]+/.test(window.location.href)) return; let timeout = 4; // max. 4 animation frames let tf = () => { if (--timeout > 0 && !document.querySelector('#player video')) return requestAnimationFrame(tf); initializeForVideoChange(); pluginHook(); _onNavigationEnd(); } tf(); } function setToActiveTab(defaultTab) { if (isTheater() && isWideScreenWithTwoColumns()) return; const jElm = document.querySelector(`a[userscript-tab-content="${switchTabActivity_lastTab}"]:not(.tab-btn-hidden)`) || document.querySelector(`a[userscript-tab-content="${(defaultTab||settings.defaultTab)}"]:not(.tab-btn-hidden)`) || document.querySelector("a[userscript-tab-content]:not(.tab-btn-hidden)") || null; switchTabActivity(jElm); return !!jElm; } function getWrapper(wrapperId) { let $wrapper = $(`#${wrapperId}`); if (!$wrapper[0]) $wrapper = $(`<div id="${wrapperId}"></div>`) return $wrapper.first(); } function runAfterTabAppended() { document.documentElement.setAttribute('plugin-tabview-youtube', '') const cssElm = kRef(ytdFlexy) if (cssElm && !cssElm.hasAttribute('tabview-selection')) cssElm.setAttribute('tabview-selection', '') // append the next videos // it exists as "related" is already here fixTabs(); // just switch to the default tab setToActiveTab(); prepareTabBtn(); // append the detailed meta contents to the tab-info FOnce.mtf_checkDescriptionLoaded = FP.mtf_checkDescriptionLoaded; if (Q.mutationTarget === null) $callOnceAsync('mtf_checkDescriptionLoaded'); // force window scroll when #continuations is first detected and #comments still [hidden] FOnce.mtf_advancedComments = FP.mtf_advancedComments; if (Q.mutationTarget === null) $callOnceAsync('mtf_advancedComments'); // use video player's element to detect the live-chat situation (no commenting section) // this would be very useful if the live chat is collapsed, i.e. iframe has no indication on the where it is live or replay FOnce.mtf_forceCheckLiveVideo = FP.mtf_forceCheckLiveVideo; if (Q.mutationTarget === null) $callOnceAsync('mtf_forceCheckLiveVideo'); // Attr Mutation Observer - #playlist - hidden clearMutationObserver(mtoVs, 'mtoVisibility_Playlist') // Attr Mutation Observer callback - #playlist - hidden // pending for #playlist and set Attribute Observer FOnce.mtf_initalAttr_playlist = FP.mtf_initalAttr_playlist if (Q.mutationTarget === null) $callOnceAsync('mtf_initalAttr_playlist'); // Attr Mutation Observer - ytd-comments#comments - hidden clearMutationObserver(mtoVs, 'mtoVisibility_Comments') // Attr Mutation Observer callback - ytd-comments#comments - hidden // pending for #comments and set Attribute Observer FOnce.mtf_initalAttr_comments = FP.mtf_initalAttr_comments; if (Q.mutationTarget === null) $callOnceAsync('mtf_initalAttr_comments'); clearMutationObserver(mtoVs, 'mtoVisibility_Chatroom'); FOnce.mtf_initalAttr_chatroom = FP.mtf_initalAttr_chatroom if (Q.mutationTarget === null) $callOnceAsync('mtf_initalAttr_chatroom'); clearMutationObserver(mtoVs, 'mtoVisibility_EngagementPanel'); for (const engagement_panel of document.querySelectorAll('ytd-watch-flexy ytd-engagement-panel-section-list-renderer:not([tabview-attr-checked])')) { engagement_panel.removeAttribute('tabview-attr-checked'); } FOnce.mtf_initalAttr_engagement_panel = FP.mtf_initalAttr_engagement_panel if (Q.mutationTarget === null) $callOnceAsync('mtf_initalAttr_engagement_panel'); flexyAttrInit(); document.querySelector("#right-tabs .tab-content").addEventListener('scroll', makeBodyScrollByEvt, true); } function fetchCommentsFinished() { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return; timeout_attrComments.clear(); akAttr(ytdFlexyElm, 'tabview-youtube-comments', false, 'LS') } function setCommentSection(value) { Q.comments_section_loaded = value; if (value === 0) { comments_section_loaded_elm = null; } } function _disableComments() { if (!scriptEnable) return; let cssElm = kRef(ytdFlexy); if (!cssElm) return; mtoInterval = mtoInterval2; setCommentSection(2); comments_section_loaded_elm = mWeakRef(document.querySelector('ytd-comments#comments div#contents') || null) let tabBtn = document.querySelector('.tab-btn[userscript-tab-content="#tab-comments"]'); if (tabBtn && !tabBtn.classList.contains('tab-btn-hidden')) { hideTabBtn(tabBtn) } akAttr(cssElm, 'tabview-youtube-comments', true, 'D'); FOnce.mtf_fetchCommentsAvailable = null; } let layoutStatusMutex = new Mutex(); function forceDisplayChatReplay() { let items = chatFrameElement('yt-live-chat-item-list-renderer #items'); if (items && items.childElementCount !== 0) return; let ytd_player = document.querySelector('ytd-player#ytd-player'); if (!ytd_player) return; let videoElm = ytd_player.querySelector('video'); if (!videoElm) return; let video = videoElm; if (videoElm && video.currentTime > 0 && !video.ended && video.readyState > video.HAVE_CURRENT_DATA) { let chat = document.querySelector('ytd-live-chat-frame#chat'); if (chat) { nativeFunc(chat, "postToContentWindow", [{ "yt-player-video-progress": videoElm.currentTime }]) } } } function runAfterExpandChat() { new Promise(resolve => { let chatFrame_st = Date.now(); let cid_chatFrameCheck = 0; let sEF = new ScriptEF(); cid_chatFrameCheck = setInterval(() => { if (!sEF.isValid()) return cid_chatFrameCheck = clearInterval(cid_chatFrameCheck); let cDoc = chatFrameContentDocument(); if (cDoc) { cid_chatFrameCheck = clearInterval(cid_chatFrameCheck); resolve(); } else if (!scriptEnable || !isChatExpand() || Date.now() - chatFrame_st > 6750) { cid_chatFrameCheck = clearInterval(cid_chatFrameCheck); } }, 60); }).then(() => new Promise(resolve => { let timeoutR_ChatAppReady = new Timeout(); timeoutR_ChatAppReady.set(() => { if (!scriptEnable || !isChatExpand()) return false; let app = chatFrameElement('yt-live-chat-app'); if (!app) return true; setTimeout(() => resolve(app), 40) }, 40, 150); //40*150 = 6000ms = 6s; })).then(app => { let cDoc = app.ownerDocument; if (!scriptEnable || !isChatExpand()) return; addStyle(` body #input-panel.yt-live-chat-renderer::after { background: transparent; } #items.yt-live-chat-item-list-renderer{ contain: content; } yt-live-chat-text-message-renderer{ contain: content; } #item-offset.yt-live-chat-item-list-renderer{ contain: content; } #item-scroller.yt-live-chat-item-list-renderer{ contain: strict; } img[width][height]{ contain: strict; } #item-list>yt-live-chat-item-list-renderer, #item-list>yt-live-chat-item-list-renderer>#contents{ contain: strict; } yt-live-chat-app{ contain: content; } `, cDoc.documentElement) if (cDoc.querySelector('yt-live-chat-renderer #continuations')) { setTimeout(() => mtf_ChatExist(), 40); $(document.querySelector('ytd-live-chat-frame#chat')).attr('yt-userscript-iframe-loaded', '') } forceDisplayChatReplay(); }) } function flexyAttrInit() { clearMutationObserver(mtoVs, 'mtoFlexyAttr') function toggleFlag(mFlag, b, flag) { if (b) mFlag = mFlag | flag; else mFlag = mFlag & ~flag; return mFlag; } function toLayoutStatus(nls, attributeName) { let attrElm, b, v; switch (attributeName) { case 'theater': nls = toggleFlag(nls, isTheater(), LAYOUT_THEATER); break; case 'userscript-chat-collapsed': case 'userscript-chatblock': attrElm = kRef(ytdFlexy); if (hasAttribute(attrElm, 'userscript-chat-collapsed')) { nls = toggleFlag(nls, true, LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLASPED); } else { nls = toggleFlag(nls, hasAttribute(attrElm, 'userscript-chatblock'), LAYOUT_CHATROOM); nls = toggleFlag(nls, false, LAYOUT_CHATROOM_COLLASPED); } break; case 'is-two-columns_': nls = toggleFlag(nls, isWideScreenWithTwoColumns(), LAYOUT_TWO_COLUMNS); break; case 'tabview-selection': b = isNonEmptyString(kRef(ytdFlexy).getAttribute('tabview-selection')); nls = toggleFlag(nls, b, LAYOUT_TAB_EXPANDED); break; case 'fullscreen': b = isNonEmptyString(kRef(ytdFlexy).getAttribute('fullscreen')); nls = toggleFlag(nls, b, LAYOUT_FULLSCREEN); break; case 'userscript-engagement-panel': v = kRef(ytdFlexy).getAttribute('userscript-engagement-panel'); b = (+v > 0) nls = toggleFlag(nls, b, LAYOUT_ENGAGEMENT_PANEL_EXPAND); break; } return nls; } let mtf_attrFlexy = (mutations, observer) => { if (!scriptEnable) return; const cssElm = kRef(ytdFlexy) if (!cssElm) return; if (!mutations) return; const old_layoutStatus = wls.layoutStatus if (old_layoutStatus === null) return; let new_layoutStatus = old_layoutStatus; for (const mutation of mutations) { new_layoutStatus = toLayoutStatus(new_layoutStatus, mutation.attributeName); if (mutation.attributeName == 'userscript-chat-collapsed' || mutation.attributeName == 'userscript-chatblock') { if (cssElm.getAttribute('userscript-chatblock') === 'chat-live') { requestingComments = null; _disableComments(); } if (!cssElm.hasAttribute('userscript-chatblock')) { setTimeout(() => { if (!scriptEnable) return; if (!isAnyActiveTab() && !isChatExpand() && !isTheater() && isWideScreenWithTwoColumns() && !isFullScreen()) { setToActiveTab(); } }, 240); } } } if (new_layoutStatus !== old_layoutStatus) wls.layoutStatus = new_layoutStatus } FOnce.mtf_checkFlexy = () => { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return true; const rootElement = Q.mutationTarget || ytdFlexyElm; var flexy = kRef(ytdFlexy) if (!flexy) return true; wls.layoutStatus = null; let rChatExist = base_ChatExist(); if (rChatExist) { let { attr_chatblock, attr_chatcollapsed } = rChatExist; if (attr_chatblock === null) { //remove attribute if it is unknown attr_chatblock = false; attr_chatcollapsed = false; } wAttr(flexy, 'userscript-chatblock', attr_chatblock) wAttr(flexy, 'userscript-chat-collapsed', attr_chatcollapsed) } let rTabSelection = [...flexy.querySelectorAll('.tab-btn[userscript-tab-content]')] .map(elm => ({ elm, hidden: elm.classList.contains('tab-btn-hidden') })) .filter(entry => entry.hidden === true); if (rTabSelection.length === 0) wAttr(flexy, 'tabview-selection', false); else if (rTabSelection.length === 1) wAttr(flexy, 'tabview-selection', rTabSelection[0].elm.getAttribute('userscript-tab-content') || ''); let rEP = engagement_panels_(); if (rEP && rEP.count > 0) wAttr(flexy, 'userscript-engagement-panel', false); else wAttr(flexy, 'userscript-engagement-panel', rEP.value + ""); let ls = 0; ls = toLayoutStatus(ls, 'theater') ls = toLayoutStatus(ls, 'userscript-chat-collapsed') ls = toLayoutStatus(ls, 'userscript-chatblock') ls = toLayoutStatus(ls, 'is-two-columns_') ls = toLayoutStatus(ls, 'tabview-selection') ls = toLayoutStatus(ls, 'fullscreen') ls = toLayoutStatus(ls, 'userscript-engagement-panel') wls.layoutStatus = ls initMutationObserver(mtoVs, 'mtoFlexyAttr', mtf_attrFlexy).observe(flexy, { attributes: true, attributeFilter: ['userscript-chat-collapsed', 'userscript-chatblock', 'theater', 'is-two-columns_', 'tabview-selection', 'fullscreen', 'userscript-engagement-panel'], attributeOldValue: true }) let columns = document.querySelector('ytd-page-manager#page-manager #columns') if (columns) { wAttr(columns, 'userscript-scrollbar-render', true); } return false; } if (Q.mutationTarget === null) $callOnceAsync('mtf_checkFlexy') } let switchTabActivity_lastTab = null function setDisplayedPlaylist() { //override the default youtube coding event prevention let cssElm = kRef(ytdFlexy); if (!scriptEnable || !cssElm) return; displayedPlaylist = mWeakRef(document.querySelector('ytd-watch-flexy #tab-list:not(.tab-content-hidden) ytd-playlist-panel-renderer') || null); } function fixLineClampFn1(){ setTimeout(()=>requestAnimationFrame(()=>new Promise(fixLineClampFn2)),1) } function fixLineClampFn2(){ let contentElements = document.querySelectorAll('ytd-comments#comments ytd-expander[should-use-number-of-lines] #content') for(const elm of contentElements) elm.classList.toggle('tabview-fix-line-clamp'); contentElements = null; } function switchTabActivity(activeLink) { if (!scriptEnable) return; const ytdFlexyElm = kRef(ytdFlexy); if (!ytdFlexyElm) return; if (activeLink && activeLink.classList.contains('tab-btn-hidden')) return; // not allow to switch to hide tab if (isTheater() && isWideScreenWithTwoColumns()) activeLink = null; function runAtEnd() { if (activeLink) lastShowTab = activeLink.getAttribute('userscript-tab-content') displayedPlaylist = null; scrollingVideosList = null; if (activeLink && lastShowTab == '#tab-list') { setDisplayedPlaylist(); } else if (activeLink && lastShowTab == '#tab-videos') { scrollingVideosList = mWeakRef(document.querySelector('ytd-watch-flexy #tab-videos:not(.tab-content-hidden) [placeholder-videos]')); } ytdFlexyElm.setAttribute('tabview-selection', activeLink ? lastShowTab : '') if (lastShowTab == '#tab-comments' && (ytdFlexyElm.getAttribute('tabview-youtube-comments') || '').lastIndexOf('S') >= 0) { akAttr(ytdFlexyElm, 'tabview-youtube-comments', false, 'L'); requestAnimationFrame(() => { let comments_tab = document.querySelector('#tab-comments'); if (comments_tab && comments_tab.scrollTop > 0) comments_tab.scrollTop = 0; }); } if(lastShowTab=='#tab-comments'){ fixLineClampFn1(); } } const links = document.querySelectorAll('#material-tabs a[userscript-tab-content]'); for (const link of links) { let content = document.querySelector(link.getAttribute('userscript-tab-content')); if (link && content) { if (link !== activeLink) { link.classList.remove("active"); content.classList.add("tab-content-hidden"); } else { link.classList.add("active"); content.classList.remove("tab-content-hidden"); //setTimeout(()=>content.focus(),400); } } } runAtEnd(); } let tabsUiScript_setclick = false; function prepareTabBtn() { const materialTab = document.querySelector("#material-tabs") if (!materialTab) return; let noActiveTab = !!document.querySelector('ytd-watch-flexy[userscript-chatblock]:not([userscript-chat-collapsed])') const activeLink = materialTab.querySelector('a[userscript-tab-content].active:not(.tab-btn-hidden)') if (activeLink) switchTabActivity(noActiveTab ? null : activeLink) if (!tabsUiScript_setclick) { tabsUiScript_setclick = true; $(materialTab).on("click", "a", function(evt) { let ytdFlexyElm = kRef(ytdFlexy); if (!scriptEnable || !ytdFlexyElm) return null; if (!this.hasAttribute('userscript-tab-content')) return; evt.preventDefault(); if (this.getAttribute('userscript-tab-content') == '#tab-comments' && parseInt(ytdFlexyElm.getAttribute('tabview-youtube-comments') || '') < 0) { return; } new Promise(requestAnimationFrame).then(() => { layoutStatusMutex.lockWith(unlock => { switchTabActivity_lastTab = this.getAttribute('userscript-tab-content'); let isActiveAndVisible = this.classList.contains('tab-btn') && this.classList.contains('active') && !this.classList.contains('tab-btn-hidden') if (isWideScreenWithTwoColumns() && !isTheater() && isActiveAndVisible) { //optional setTimeout(unlock, 80); switchTabActivity(null); ytBtnSetTheater(); } else if (isActiveAndVisible) { setTimeout(unlock, 80); switchTabActivity(null); } else { let pInterval = 60; if (isChatExpand() && isWideScreenWithTwoColumns()) { ytBtnCollapseChat(); } else if (isEngagementPanelExpanded() && isWideScreenWithTwoColumns()) { ytBtnCloseEngagementPanels(); } else if (isWideScreenWithTwoColumns() && isTheater() && !isFullScreen()) { ytBtnCancelTheater(); } else { pInterval = 20; } setTimeout(() => { setTimeout(makeBodyScroll, 20); // this is to make the image render setTimeout(() => { let rightTabs = document.querySelector('#right-tabs'); if (!isWideScreenWithTwoColumns() && rightTabs && rightTabs.offsetTop > 0 && this.classList.contains('active')) { let tabButtonBar = document.querySelector('#material-tabs'); let tabButtonBarHeight = tabButtonBar ? tabButtonBar.offsetHeight : 0; window.scrollTo(0, rightTabs.offsetTop - tabButtonBarHeight); } }, 60) setTimeout(unlock, 80) switchTabActivity(this) }, pInterval); } }) }) }); } } // --------------------------------------------------------------------------------------------- window.addEventListener("yt-navigate-finish", onNavigationEnd) document.addEventListener("loadstart", (evt) => { if (!evt || !evt.target || evt.target.nodeName !== "VIDEO") return; let elm = evt.target; if (!elm.matches('#player video, #movie_player video, video[tabview-mainvideo]')) return; let src = elm.src; if (src !== lastVideoURL) { lastVideoURL = elm.src; elm.setAttribute('tabview-mainvideo', ''); // mainly for mini playing } }, true) // --------------------------------------------------------------------------------------------- var scrolling_lastD = 0; const singleColumnScrolling = function(scrolling_lastF) { let pageY = pageYOffset; if (pageY < 10 && scrolling_lastD === 0 && !scrolling_lastF) return; let targetElm, header, navElm; Promise.resolve().then(() => { targetElm = document.querySelector("#right-tabs"); if (!targetElm) return; header = targetElm.querySelector("header"); if (!header) return; navElm = document.querySelector('#masthead-container, #masthead') if (!navElm) return; let navHeight = navElm ? navElm.offsetHeight : 0 let elmY = targetElm.offsetTop let xyz = [elmY + navHeight, pageY, elmY - navHeight] let xyStatus = 0 if (xyz[1] < xyz[2] && xyz[2] < xyz[0]) { // 1 xyStatus = 1 } if (xyz[0] > xyz[1] && xyz[1] > xyz[2]) { //2 xyStatus = 2 } if (xyz[2] < xyz[0] && xyz[0] < xyz[1]) { // 3 xyStatus = 3 } return xyStatus; }).then((xyStatus) => { if ((xyStatus == 2 || xyStatus == 3) && (scrolling_lastD === 0 || scrolling_lastF)) { scrolling_lastD = 1; let { offsetHeight } = header let { offsetWidth } = targetElm targetElm.style.setProperty("--userscript-sticky-width", offsetWidth + 'px') targetElm.style.setProperty("--userscript-sticky", offsetHeight + 'px') wAttr(targetElm, 'userscript-sticky', true); } else if ((xyStatus == 1) && (scrolling_lastD === 1 || scrolling_lastF)) { scrolling_lastD = 0; wAttr(targetElm, 'userscript-sticky', false); } targetElm = null; header = null; navElm = null; }); }; window.addEventListener("scroll", function() { if (!scriptEnable) return; singleColumnScrolling(false) }, { capture: false, passive: true }) var lastResizeAt = 0; window.addEventListener('resize', function() { if (!scriptEnable) return; lastResizeAt = Date.now(); requestAnimationFrame(() => { if (!scriptEnable) return; singleColumnScrolling(true) }) }, { capture: false, passive: true }) window.addEventListener('beforeunload', function() { if (!scriptEnable) return; console.log('beforeunload') pluginUnhook(); //let video=document.querySelector('video'); //if(video && !video.paused) video.pause(); }, { capture: true }) window.addEventListener('hashchange', function() { if (!scriptEnable) return; console.log('hashchange') pluginUnhook(); }, { capture: true }) window.addEventListener('popstate', function() { if (!scriptEnable) return; console.log('popstate') pluginUnhook(); }, { capture: true }) function clearMutationObserver(o, key) { if (o[key]) { o[key].takeRecords(); o[key].disconnect(); o[key] = null; return true; } } function initMutationObserver(o, key, callback) { clearMutationObserver(o, key); const mto = new MutationObserver(callback); o[key] = mto; return mto; } document.addEventListener('wheel', function(evt) { if (!scriptEnable) return; const displayedPlaylist_element = kRef(displayedPlaylist); if (displayedPlaylist_element && displayedPlaylist_element.contains(evt.target)) { evt.stopPropagation(); evt.stopImmediatePropagation(); } }, { capture: true, passive: true }); function setVideosTwoColumns(flag, bool) { //two columns to one column /* [placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy is-two-columns ="" => no is-two-columns [placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer no hidden => hidden ="" [placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer hidden ="" => no hidden */ let cssSelector1 = '[placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy' let cssSelector2 = '[placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer' let cssSelector3 = '[placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer' let res = {} if (flag & 1) { res.m1 = document.querySelector(cssSelector1) if (res.m1) wAttr(res.m1, 'is-two-columns', bool ? '' : false); } if (flag & 2) { res.m2 = document.querySelector(cssSelector2) if (res.m2) wAttr(res.m2, 'hidden', bool ? false : ''); } if (flag & 4) { res.m3 = document.querySelector(cssSelector3) if (res.m3) wAttr(res.m3, 'hidden', bool ? '' : false); } return res } let lastScrollFetch = 0; // function isScrolledToEnd(){ // return (window.innerHeight + window.pageYOffset) >= document.scrollingElement.scrollHeight - 2; // } let lastOffsetTop = 0; window.addEventListener('scroll', function(evt) { //console.log(evt.target) if (!scriptEnable) return; if (!kRef(scrollingVideosList)) return; if (videoListBeforeSearch) return; let visibleHeight = document.scrollingElement.clientHeight; let totalHeight = document.scrollingElement.scrollHeight; if (totalHeight < visibleHeight * 1.5) return; // filter out two column view; let z = window.pageYOffset + visibleHeight; let h_advanced = totalHeight - (visibleHeight > 5 * 40 ? visibleHeight * 0.5 : 40); if (z > h_advanced && !isWideScreenWithTwoColumns()) { let ct = Date.now(); if (ct - lastScrollFetch < 500) return; //prevent continuous calling lastScrollFetch = ct; let res = setVideosTwoColumns(2 | 4, true) if (res.m3 && res.m2) { //wait for DOM change, just in case requestAnimationFrame(() => { let { offsetTop } = res.m2 // as visibility of m2 & m3 switched. if (offsetTop - lastOffsetTop < 40) return; // in case bug, or repeating calling. // the next button shall below the this button lastOffsetTop = offsetTop res.m2.parentNode.dispatchEvent(new Event('yt-service-request-sent-button-renderer')) res = null }) } else { res = null } } }, { passive: true }) /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */ /* fix bug for comment section - version 1.8.7 This issue is the bug in browser's rendering I guess, this is due to the lines clamp with display:-webkit-box use stupid coding to let it re-render when its content become visible /* ytd-expander[should-use-number-of-lines][collapsed] > #content.ytd-expander { color: var(--yt-spec-text-primary); display: -webkit-box; overflow: hidden; max-height: none; -webkit-box-orient: vertical; -webkit-line-clamp: var(--ytd-expander-max-lines, 4); } // v1.8.36 imposed a effective solution for fixing this bug */ /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */ })(); // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js } ;!(function $$() { 'use strict'; if(document.documentElement==null) return window.requestAnimationFrame($$) const cssTxt = GM_getResourceText("contentCSS"); function addStyle (styleText) { const styleNode = document.createElement('style'); styleNode.type = 'text/css'; styleNode.textContent = styleText; document.documentElement.appendChild(styleNode); return styleNode; } addStyle (cssTxt); main(window.$); // Your code here... })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址