Greasy Fork

Greasy Fork is available in English.

Tabview Youtube

Make comments and lists into tabs

当前为 2021-07-03 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Tabview Youtube
// @namespace    http://tampermonkey.net/
// @version      0.6
// @description  Make comments and lists into tabs
// @author       CY Fung
// @match        https://www.youtube.com/watch?v=*
// @resource     contentCSS https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/b57d5c149caf4b78df3eeb0b9a791af8347d97cb/css/style_content.css
// @icon         https://github.com/cyfung1031/Tabview-Youtube/raw/main/images/icon128p.png
// @require      https://code.jquery.com/jquery-3.6.0.slim.min.js
// @grant        GM_getResourceText
// @run-at       document-start
// @license      MIT https://github.com/cyfung1031/Tabview-Youtube/blob/main/LICENSE
// ==/UserScript==
function main($){
    // MIT License
    // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js





/**
 * 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 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"/>
 `

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"/>`

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"/>`

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"/>
     `





const svgElm = (w, h, vw, vh, p) => `<svg width="${w}" height="${h}" viewBox="0 0 ${vw} ${vh}" preserveAspectRatio="xMidYMid meet">${p}</svg>`

let settings = {
    toggleSettings: {
        tabs: 1,
        tInfo: 1,
        tComments: 1,
        tVideos: 1,
    },
    defaultTab: "videos"
};

const mtoInterval1=40;
const mtoInterval2=150;

const clickInterval1=100;
const clickInterval2=30;

let mtoInterval = mtoInterval1;
let clickInterval=clickInterval1;

function isVideoPlaying(video) {
    return video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA;
}

function setAttr(elm, attrName, b){

    if(!elm)return;
    if(b) elm.setAttribute(attrName,''); else elm.removeAttribute(attrName);
}

function hideTabBtn($tabBtn){
    
    var isActiveBefore = $tabBtn.is('.active')

    $tabBtn.addClass("tab-btn-hidden");
    if (isActiveBefore) {
        setToActiveTab();
    }
}

function isTheater(){
    
    const cssElm=document.querySelector('ytd-watch-flexy');
    return (cssElm && cssElm.hasAttribute('theater'))
}

function isChatExpand(){
    const cssElm=document.querySelector('ytd-watch-flexy');
    return cssElm && cssElm.hasAttribute('userscript-chatblock') && !cssElm.hasAttribute('userscript-chat-collapsed')
}
function isWideScreenWithTwoColumns(){
    const cssElm=document.querySelector('ytd-watch-flexy');
    return (cssElm && cssElm.hasAttribute('is-two-columns_'))
    
}

function isAnyActiveTab(){
    return $('#right-tabs .tab-btn.active').length>0
}

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 fixDisplayForTheaterModeChanged(){
    const cssElm = document.querySelector('ytd-watch-flexy')
    if(!cssElm) return;
    if(isTheater() && isWideScreenWithTwoColumns()){
        if( isAnyActiveTab()) switchTabActivity(null)
        if( isChatExpand() ) ytBtnCollapseChat()

    }else if( !isTheater() && !isChatExpand() && !isAnyActiveTab()){

        console.log('a112', lastShowTab)
        if(lastShowTab=='#chatroom') ytBtnExpandChat(); else setToActiveTab();

    }else if( !isWideScreenWithTwoColumns() && !isChatExpand() && !isAnyActiveTab() ){
        
        setToActiveTab();

    }
}

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={}

Q.$callOnceAsync=async function(key){
    if (Q[key] && Q[key]() === false) Q[key] = null
}

function chatFrameElement(cssSelector){
    let iframe = document.querySelector('iframe#chatframe');
    if(!iframe) return null;
    let cDoc = iframe.contentDocument;
    if(!cDoc) return null;
    if(cDoc.readyState  != 'complete') return null; //we must wait for its completion
    let elm = null;
    try{
        elm = cDoc.querySelector(cssSelector)
    }catch(e){
        console.log('iframe error', e)
    }
    return elm;
}


function fixRelated(){
    
    if(!document.querySelector("#tab-videos>[placeholder-videos]>ytd-watch-next-secondary-results-renderer[data-dom-changed-by-tabview-youtube]")){



        let relatedVideos = document.querySelector("#related>ytd-watch-next-secondary-results-renderer");
        if(relatedVideos){

            $('[placeholder-videos]').removeAttr('placeholder-videos')
            $('[placeholder-for-youtube-play-next-queue]').removeAttr('placeholder-for-youtube-play-next-queue')

            let $parentNode= $(relatedVideos.parentNode).appendTo(document.querySelector("#tab-videos"))
            $(relatedVideos).attr('data-dom-changed-by-tabview-youtube',scriptVersionForExternal)

            $parentNode.attr('placeholder-for-youtube-play-next-queue','').attr('placeholder-videos','')

            $('[placeholder-videos]').scroll(makeBodyScrollByEvt);

        }

        
    }
}

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 mtf_fixTabsAtTheEnd(){
    // if window resize, youtube coding will relocate the element
    // for example, chatroom move before #right-tabs
    // causing difference apperance after resize of window


    fixRelated();

    let nonLastRightTabs = document.querySelector('#secondary #right-tabs:not(:last-child)')

    if(nonLastRightTabs){
        nonLastRightTabs.parentNode.appendChild(nonLastRightTabs)
    }

    let chatroom = document.querySelector('#primary ytd-live-chat-frame#chat');
    if(chatroom){
        let right_tabs = document.querySelector('#secondary #right-tabs:last-child')
        if(right_tabs){


            right_tabs.parentNode.insertBefore(chatroom, right_tabs)

        }
        
    }



    const autocomplete=document.querySelector('body>.autocomplete-suggestions:not([position-fixed-by-tabview-youtube]):not(:empty)')
    if(autocomplete){
        const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')

        if(searchBox){


            autocomplete.setAttribute('position-fixed-by-tabview-youtube','');



            if(!searchBox.hasAttribute('is-set-click-to-toggle')){
                searchBox.setAttribute('is-set-click-to-toggle','')
                searchBox.addEventListener('click',function(){

                    setTimeout(function(){
                    let elm=document.querySelector('.autocomplete-suggestions[position-fixed-by-tabview-youtube]:not(:empty)')

                    $(elm).toggle()
                    //if(elm.style.display=='none') elm.style.display=''; else elm.style.display='none';
                },100);

                })
            }


            let aaa=searchBox.nextSibling;
            if(aaa && aaa.nodeName=="BFJQ"){
            }else if(aaa && aaa.nodeName!="BFJQ"){

                $(aaa=document.createElement("BFJQ")).insertAfter(searchBox);
                
                
            }else{

                $(aaa=document.createElement("BFJQ")).prependTo(searchBox.parentNode);
                


            }
            $(autocomplete).prependTo(aaa);


            aaa.style.setProperty('--sb-margin-bottom',getComputedStyle(searchBox).marginBottom)
            aaa.style.setProperty('--height',searchBox.offsetHeight + 'px')

            /*
            setInterval(function(){               

                autocomplete.style.setProperty('--ac-left',autocomplete.getBoundingClientRect().left + 'px')
                autocomplete.style.setProperty('--ac-top',autocomplete.getBoundingClientRect().top + 'px')
                autocomplete.style.setProperty('--sb-left',aaa.getBoundingClientRect().left + 'px')
                autocomplete.style.setProperty('--sb-top',aaa.getBoundingClientRect().top + 'px')

            },270)*/

        }

    }


    let zCache=document.querySelector('[placeholder-for-youtube-play-next-queue] #items ytd-compact-video-renderer:last-of-type')
    if(cachedLastVideo && zCache && cachedLastVideo!==zCache){

  //      let cachedLastVideoStr = cachedLastVideo.__userscript_prev_textcontent__;
   //     cachedLastVideo=zCache
//        cachedLastVideo.
 /*       $0.textContent.replace(/\s+/g,'').replace(/[^\da-zA-Z\u4E00-\u9FFF]/g,'')*/


 const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')

        if(!cachedLastVideo.parentNode/* || cachedLastVideoStr !== cachedLastVideo.*/){
            //removed
            fromSearch=true;

            requestAnimationFrame(function(){

                $('[placeholder-for-youtube-play-next-queue]')[0].scrollTop=0;
                const searchBox=document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')
                if(searchBox) searchBox.blur();

            });
        }else if(searchBox && !zCache.__clone_last_results__){

            let p=zCache.parentNode;
            //update from youtube loading
            setTimeout(function(){


                if(p&& p.parentNode) zCache.__clone_last_results__=$(p).clone();

                //$()
                //$('ytd-watch-next-secondary-results-renderer')

            },800)

        }
        cachedLastVideo=zCache


        setTimeout(function(){
            const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')
            let zCache=document.querySelector('[placeholder-for-youtube-play-next-queue] ytd-watch-next-secondary-results-renderer #items>ytd-compact-video-renderer:last-of-type')

            let items = document.querySelector('[placeholder-for-youtube-play-next-queue] ytd-watch-next-secondary-results-renderer #items');
            
            if(searchBox && $(searchBox).is(":visible") && (searchBox.value||"").length===0  && items.__clone_last_results__ && fromSearch ){


                if(items.__clone_last_results__){


                    for(const s of items.querySelectorAll('ytd-compact-video-renderer')){
                        try{
                        items.removeChild(s);
                        }catch(e){}
                    }
/*
                    let texts=[];

                    for(const s of items.querySelectorAll('[placeholder-for-youtube-play-next-queue] ytd-item-section-renderer ytd-compact-video-renderer')){
                        texts.push(extractTextContent(s))
                    }*/

                    for(const s of items.__clone_last_results__.querySelectorAll('ytd-compact-video-renderer')){
   /*                     const text = extractTextContent(s)
                        if(texts.indexOf(text)>0)continue;
    */                    $(items).append(s)
                    }

                    fromSearch=false;

                }


            }
        },300)

    }else if(!cachedLastVideo && zCache && cachedLastVideo!==zCache){
        
        cachedLastVideo=zCache
        

    }


}

function mtf_ChatExist(){

    // 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

    
    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){

        //console.log(897, Q.mtf_chatBlockQ, chatBlockR)
        Q.mtf_chatBlockQ=chatBlockR

        
        const cssElm = document.querySelector('ytd-watch-flexy')
        if(elmChat){

            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) {
                    cssElm.setAttribute('userscript-chatblock', 'chat-live')
                    requestingComments=null;
                }
                if(s==2) cssElm.setAttribute('userscript-chatblock', 'chat-playback')
                //if(s==5) cssElm.setAttribute('userscript-chatblock', 'chat-live-paid')

                if(s==1) $("span#tab3-txt-loader").text('');

            }
            //keep unknown as original
            if( !cssElm.hasAttribute) cssElm.setAttribute('userscript-chatblock', '')
           

        }else{
            cssElm.removeAttribute('userscript-chatblock')
            cssElm.removeAttribute('userscript-chat-collapsed')

        }
        

    }
}




let lastScrollAt = 0;

function makeBodyScrollByEvt(){
    // inside marco task (event)

    Promise.resolve().then(()=>window.dispatchEvent(new Event("scroll")))


}

function makeBodyScroll() {

    // avoid over triggering scroll event
    if (+new Date - lastScrollAt < 30) return;
    lastScrollAt = +new Date;

    //required for youtube content display

    requestAnimationFrame(()=>{

        window.dispatchEvent(new Event("scroll"));

    })
    


}

let requestingComments = null
function scrollForComments_TF(){
    let comments = requestingComments;
    if ( comments && comments.hasAttribute('hidden')) makeBodyScroll();
}
function scrollForComments() {
    setTimeout(scrollForComments_TF, 80);
    setTimeout(scrollForComments_TF, 240);
    setTimeout(scrollForComments_TF, 870);
}



let mtoNav = null;


const mtoVs={}


function initObserver(){




    // continuous check for element relocation
    function mtf_append_comments() {
        let comments = document.querySelector('#primary ytd-watch-metadata ~ #info ~ 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 button = document.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 ple1 = document.querySelector("*:not(#ytd-userscript-playlist)>ytd-playlist-panel-renderer#playlist");
        if(ple1){
            appendWithWrapper(
                ple1,
                'ytd-userscript-playlist',  
                document.querySelector("#tab-list")
            );
            $(ple1).attr('data-dom-changed-by-tabview-youtube',scriptVersionForExternal)

        }
    }


    // content fix - info & playlist
    // fired at begining, and keep for in case any change
    function mtf_fix_details() {

        const content = document.querySelector('#meta-contents ytd-expander>#content, #tab-info ytd-expander>#content')
        if (content) {
            const expander = content.parentNode;

            if (expander.hasAttribute('collapsed')) setAttr(expander,'collapsed',false);

            let btn1 = expander.querySelector('tp-yt-paper-button#less:not([hidden])');
            let btn2 = expander.querySelector('tp-yt-paper-button#more:not([hidden])');

            if (btn1) setAttr(btn1,'hidden',false);
            if (btn2) setAttr(btn2,'hidden',false);

        }

        // just in case the playlist is collapsed
        const playlist = document.querySelector('#tab-list ytd-playlist-panel-renderer#playlist')
        if(playlist){
            if(playlist.hasAttribute('collapsed')) setAttr(playlist,'collapsed',false);
            if(playlist.hasAttribute('collapsible')) setAttr(playlist,'collapsible',false);
        }        


    }



    let mtoNav_requestNo=0;

    let mtoNav_delayedF = () => {
    
        let {addP, removeP} = Q;
    
        Q.addP = 0;
        Q.removeP = 0;

        

        let promisesForAddition=!scriptEnable?[]:addP > 0?[
            Q.$callOnceAsync('mtf_advancedComments'),
            Q.$callOnceAsync('mtf_infoSectionHeight'),
            Q.$callOnceAsync('mtf_checkDescriptionLoaded'),
            Q.$callOnceAsync('mtf_checkPlayList'),
            Q.$callOnceAsync('mtf_fetchCommentsAvailable'),
            Q.$callOnceAsync('mtf_initalAttr_comments'),
            Q.$callOnceAsync('mtf_initalAttr_playlist'),
            Q.$callOnceAsync('mtf_checkStatus_chatroom'),
            Q.$callOnceAsync('mtf_checkFlexy'),
            Q.$callOnceAsync('mtf_forceCheckLiveVideo'),

            (async () => {
                mtf_append_comments();
            })(),
    
            (async () => {
                mtf_liveChatBtnF();
            })(),

            (async ()=>{
                mtf_fixTabsAtTheEnd();
            })(),
        
            (async () => {
                mtf_append_playlist();
            })()
        ]:[];
        


        let promisesForEveryMutation=!scriptEnable?[]:[
            (async () => {
                mtf_fix_details();
            })(),
            (async () => {
                mtf_ChatExist();
            })()
        ];


        Promise.all([...promisesForAddition,...promisesForEveryMutation]).then(()=>{
            mtoNav_requestNo--;
            //console.log('motnav reduced to', mtoNav_requestNo)
            if(mtoNav_requestNo>0){
                mtoNav_requestNo=1;
                setTimeout(mtoNav_delayedF,mtoInterval);
            }
        })
    
    
    }

    Q.addP=0;
    Q.removeP=0; 
    let hReqNo=0;
    const mtoNavF=(mutations, observer) => {

        let ch = false;
        for (const mutation of mutations) {
            for (const addedNode of mutation.addedNodes)
                if (addedNode.nodeType === 1) {
                    Q.addP++
                    ch = true;
                }
            for (const removedNode of mutation.removedNodes)
                if (removedNode.nodeType === 1) {
                    Q.removeP++;
                    ch = true;
                }
        }
        if (!ch) return;

        mtoNav_requestNo++;
        hReqNo++;
        if(hReqNo==36) {
             mtoInterval=mtoInterval2;
             clickInterval=clickInterval2;
        }
        //console.log('motnav added to', mtoNav_requestNo)

        if(mtoNav_requestNo==1) setTimeout(mtoNav_delayedF,mtoInterval);

    }
    mtoNav = new MutationObserver(mtoNavF);
    mtoNav.observe(document.querySelector('ytd-watch-flexy'), {
        subtree: true,
        childList: true
    })

    1;1&&(async()=>{
        Q.addP=1; //fake the function
        mtoNav_requestNo++;
        if(mtoNav_requestNo==1) mtoNav_delayedF();

    })();
}

let displayedPlaylist=null
let scrollingVideosList=null

let scriptEnable =false;
let lastShowTab = null;


let cachedLastVideo=null;
let fromSearch=false;
function resetBeforeNav() {
    fromSearch=true;
    cachedLastVideo=null;
    lastShowTab=null;
    displayedPlaylist=null
    scrollingVideosList=null
    scriptEnable =false;


    clearMutationObserver(mtoVs,'mtoVisibility_Playlist')
    clearMutationObserver(mtoVs,'mtoVisibility_Comments')
    clearMutationObserver(mtoVs,'mtoVisibility_Chatroom')
    clearMutationObserver(mtoVs,'mtoFlexyAttr')

    if (mtoNav) {
        mtoNav.takeRecords();
        mtoNav.disconnect();
        mtoNav = null;

        Q.mtf_advancedComments=null;
        Q.mtf_checkDescriptionLoaded = null;
        Q.mtf_checkPlayList = null;
        Q.mtf_fetchCommentsAvailable = null;
        Q.mtf_initalAttr_comments = null;
        Q.mtf_initalAttr_playlist = null;
        Q.mtf_checkStatus_chatroom = null;
        Q.mtf_forceCheckLiveVideo=null;
        Q.mtf_chatBlockQ = null;
    }

    mtoInterval = mtoInterval1;
    clickInterval = clickInterval1;

}

function resetAtNav() {

    scriptEnable =true;

    $("ytd-watch-flexy").removeAttr("userscript-chatblock").removeAttr("userscript-chat-collapsed");
    $('#tab-comments').attr('lazy-loading', '');
    $('span#tab3-txt-loader').text('');

    //removed any cache of #comments header (i.e. count message)
    var prevCommentsHeader = document.querySelector('ytd-comments#comments ytd-comments-header-renderer');
    if (prevCommentsHeader) prevCommentsHeader.parentNode.removeChild(prevCommentsHeader);

    var prevCommentsMsg= document.querySelector('ytd-item-section-renderer#sections #header ~ #contents>ytd-message-renderer:only-child');
    if (prevCommentsMsg) prevCommentsMsg.parentNode.removeChild(prevCommentsMsg);

    //force to [hidden]
    var prevComemnts = document.querySelector('ytd-comments#comments'); 
    if (prevComemnts) {
        setAttr(prevComemnts, 'hidden', true);
        requestingComments = prevComemnts;
        //scrollForComments();
    }

    
    //playlist bug
    /*
    var prevPlaylist = document.querySelector('ytd-watch-flexy #columns ytd-playlist-panel-renderer#playlist')
    var secondInner = document.querySelector('ytd-watch-flexy #secondary>#secondary-inner');
    if (prevPlaylist && secondInner){


        prevPlaylist.removeAttribute('hidden')
        secondInner.appendChild(prevPlaylist)

    }

    var prevRelated = document.querySelector('ytd-watch-flexy #columns #related')
    if (prevRelated && secondInner){

        secondInner.appendChild(prevRelated)
    }*/
    


}

function getTabsHTML(){


    let ts = settings.toggleSettings;

    if (!ts.tabs) return;

    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>`

    const 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 = [
        ts.tInfo ? `<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>`,
        ts.tComments ? `<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>` : '',
        ts.tVideos ? `<a id="tab-btn4" userscript-tab-content="#tab-videos" data-name="videos" class="active 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" 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" userscript-scrollbar-render></div>
             <div id="tab-videos" class="tab-content-cld" userscript-scrollbar-render></div>
             <div id="tab-list" class="tab-content-cld" userscript-scrollbar-render></div>
         </div>
     </div>
     `;

     return addHTML

}

function onNavigationEnd() {

    resetBeforeNav();
    if(!/https?\:\/\/(\w+\.)*youtube\.com\/watch\?(\w+\=[^\/\?\&]+\&)*v=[\w\-\_]+/.test(window.location.href))return;
    resetAtNav();


    let promise = Promise.resolve();

    if (!document.querySelector("#right-tabs")) {
        let targetElm = document.querySelector("ytd-watch-flexy #secondary>#secondary-inner")||document.querySelector("ytd-watch-flexy #secondary")||document.querySelector("ytd-watch-flexy #columns");
        if(!targetElm) throw 'Userscript: Two Column flexy layout not found'; // not flexy layout
        promise=promise.then(()=>{
            $(getTabsHTML()).appendTo(targetElm).attr('data-dom-created-by-tabview-youtube',scriptVersionForExternal);
            targetElm=null;
        })
    }

    promise.then(runAfterTabAppended).then(initObserver)

}

function setToActiveTab() {
    if(isTheater() && isWideScreenWithTwoColumns())return;
    const jElm = document.querySelector(`a[userscript-tab-content="${switchTabActivity_lastTab}"]:not(.tab-btn-hidden)`) ||
        document.querySelector(`a[userscript-tab-content="#tab-${settings.defaultTab}"]:not(.tab-btn-hidden)`) ||
        document.querySelector("a[userscript-tab-content]:not(.tab-btn-hidden)") ||
        null;
    switchTabActivity(jElm);
}

function insertBefore(elm, p) {
    if (elm && p && p.parentNode)
        p.parentNode.insertBefore(elm, p);
}

function appendWithWrapper(elm, wrapperId, toParent){
    if(!toParent||!elm)return;
    let $wrapper = $(`#${wrapperId}`);
    if(!$wrapper[0]) $wrapper=$(`<div id="${wrapperId}"></div>`)
    $wrapper.append(elm).appendTo(toParent);
}

function runAfterTabAppended() {

    // just switch to the default tab
    setToActiveTab();

    // append the next videos 
    // it exists as "related" is already here

    fixRelated();



    prepareTabBtn();

    
    // append the detailed meta contents to the tab-info
    Q.mtf_checkDescriptionLoaded = () => {
        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('ytd-watch-flexy #meta-contents yt-img-shadow#avatar');
        if(avatar) hackImgShadow(avatar)
        return false;
    }
    Q.$callOnceAsync('mtf_checkDescriptionLoaded')

    // force window scroll when #continuations is first detected and #comments still [hidden]
    Q.mtf_advancedComments = () => {
        const continuations = document.querySelector("ytd-comments#comments #continuations");
        if (!continuations) return true;
        requestingComments = document.querySelector('ytd-comments#comments');
        scrollForComments();
        return false;
    }
    Q.$callOnceAsync('mtf_advancedComments')

/*
    Q.mtf_infoSectionHeight=()=>{
        const infoSection = document.querySelector("#primary #player ~ #info>#info-contents");
        if (!infoSection) return true;
        if(mtoVs.rsoInfoSection) {
            mtoVs.rsoInfoSection.disconnect();
            mtoVs.rsoInfoSection=null;
        }
        mtoVs.rsoInfoSection=new ResizeObserver(()=>{
            const cssElm = document.querySelector('ytd-watch-flexy')
            if(!cssElm)return;
            cssElm.style.setProperty('--userscript-info-section-height', infoSection.offsetHeight);
        });
        return false;
    }
    Q.$callOnceAsync('mtf_infoSectionHeight')*/



    // make window scroll event from playlist scrolling
    // i guess it shall be not neccessary, just in case
    /*Q.mtf_checkPlayList = () => {
        const items= document.querySelector('ytd-playlist-panel-renderer>#container>#items');
        if(!items) return true;
        $(items).scroll(makeBodyScrollByEvt);
        return false;
    }
    Q.$callOnceAsync('mtf_checkPlayList')*/


    // 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
    
    Q.mtf_forceCheckLiveVideo_tf =()=>{
        const cssElm = document.querySelector('ytd-watch-flexy')
        if(!cssElm) return;
        if($('#ytd-player .ytp-time-display').is('.ytp-live')) {
            cssElm.setAttribute('userscript-chatblock', 'chat-live')
            requestingComments=null;
        }
    }
    Q.mtf_forceCheckLiveVideo = () => {
        const playerLabel = document.querySelector('#ytd-player .ytp-time-display') && document.querySelector('ytd-live-chat-frame#chat')
        if (!playerLabel) return true;
        setTimeout(Q.mtf_forceCheckLiveVideo_tf,170)
        return false;
    }
    Q.$callOnceAsync('mtf_forceCheckLiveVideo')



    createAttributeObservants();
    checkChatStatus();


    $("#right-tabs [userscript-scrollbar-render]").scroll(makeBodyScrollByEvt);

}


async function asyncFetchCommentsAvailable() {

    let span = document.querySelector("span#tab3-txt-loader")
    if (!span) return;

    makeBodyScroll();

    let fetchedOnce = false
    Q.mtf_fetchCommentsAvailable = () => {

        if(!scriptEnable)return;

        let messageElm, messageStr;
        const commentRenderer = document.querySelector("ytd-comments#comments #count.ytd-comments-header-renderer");
        if (commentRenderer) {
            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()
            }
            span.textContent = r;
            $('#tab-comments[lazy-loading]').removeAttr('lazy-loading')
            mtoInterval=mtoInterval2;
            clickInterval=clickInterval2;
        }else if((messageElm = document.querySelector('ytd-item-section-renderer#sections #header ~ #contents>ytd-message-renderer:only-child'))&&(messageStr=(messageElm.textContent||'').trim())){ //ytd-message-renderer
            // it is possible to get the message before the header generation.
            setTimeout(function(){
                if(fetchedOnce)return;
                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
                        }
                    } 
                }
                span.textContent = messageStr;
                $('#tab-comments[lazy-loading]').removeAttr('lazy-loading')
            },240);
        }
        return true;
    
    }
    Q.$callOnceAsync('mtf_fetchCommentsAvailable')


}




function createAttributeObservants() {


    // Attr Mutation Observer - #playlist - hidden
    clearMutationObserver(mtoVs,'mtoVisibility_Playlist')
    // Attr Mutation Observer callback - #playlist - hidden
    let mtf_attrPlaylist=(mutations, observer)=>{
        var playlist=document.querySelector('ytd-playlist-panel-renderer#playlist')
        const $tabBtn = $('[userscript-tab-content="#tab-list"]');
        //console.log(3712,$tabBtn)
        //console.log('attr playlist changed')
        if( $tabBtn.is('.tab-btn-hidden') && !playlist.hasAttribute('hidden') ){
            //console.log(3713)
            //console.log('attr playlist changed - no hide')
            $tabBtn.removeClass("tab-btn-hidden");
        }else if( !$tabBtn.is('.tab-btn-hidden') && playlist.hasAttribute('hidden') ){
            //console.log(3714)
            //console.log('attr playlist changed - add hide')
            hideTabBtn($tabBtn);
        }
    }

    // pending for #playlist and set Attribute Observer
    Q.mtf_initalAttr_playlist=()=>{
        var playlist=document.querySelector('ytd-playlist-panel-renderer#playlist')
        if(!playlist) return true;
        initMutationObserver(mtoVs,'mtoVisibility_Playlist', mtf_attrPlaylist)
        mtoVs.mtoVisibility_Playlist.observe(playlist, {          
            attributes: true,
            attributeFilter: ['hidden'],
            attributeOldValue: true
        })
        //console.log(3711)
        mtf_attrPlaylist()
        return false;
    }
    //console.log(3710)
    Q.$callOnceAsync('mtf_initalAttr_playlist')





    // Attr Mutation Observer - ytd-comments#comments - hidden
    
    clearMutationObserver(mtoVs,'mtoVisibility_Comments')
    // Attr Mutation Observer callback - ytd-comments#comments - hidden
    let mtf_attrComments=(mutations, observer)=>{
        var comments=document.querySelector('ytd-comments#comments')
        const $tabBtn = $('[userscript-tab-content="#tab-comments"]');
        if(!comments || !$tabBtn[0])return;
        //console.log('attr comments changed')
        if( $tabBtn.is('.tab-btn-hidden') && !comments.hasAttribute('hidden') ){
            //console.log('attr comments changed - no hide')
            $tabBtn.removeClass("tab-btn-hidden");
            asyncFetchCommentsAvailable();
        }else if( !$tabBtn.is('.tab-btn-hidden') && comments.hasAttribute('hidden') ){
            //console.log('attr comments changed - add hide')
            if(!document.querySelector('[userscript-chatblock="chat-live"]')){
                requestingComments=comments
                $('#tab-comments').attr('lazy-loading','')
            } 
            $('span#tab3-txt-loader').text('');
            hideTabBtn($tabBtn);
        }        
    }

    // pending for #comments and set Attribute Observer
    Q.mtf_initalAttr_comments=()=>{
        var comments=document.querySelector('ytd-comments#comments')
        if(!comments) return true;
        initMutationObserver(mtoVs,'mtoVisibility_Comments',mtf_attrComments)
        mtoVs.mtoVisibility_Comments.observe(comments, {          
            attributes: true,
            attributeFilter: ['hidden'],
            attributeOldValue: true
        })
        mtf_attrComments()
        requestingComments = document.querySelector('ytd-comments#comments');
        scrollForComments()
        return false;
    }
    Q.$callOnceAsync('mtf_initalAttr_comments')

}


function isEmptyBody(){
    //only deal with loaded document without body
    //other situation, case by case

    let iframe = document.querySelector('ytd-live-chat-frame iframe#chatframe');

    if(!iframe) return false; //iframe must be there

    if(iframe.readyState  != 'complete') return false; //we must wait for its completion

    let doc = null;
    try{

        doc=iframe.contentDocument

    }catch(e){}

    if(!doc) return false; //might be not loaded yet

    if(doc.body && doc.body.childElementCount===0){
        //empty body

        return true;
    }


}

function checkChatStatus(){
    
    
    clearMutationObserver(mtoVs,'mtoVisibility_Chatroom')

    let cid_chatFrameCheck=0;

    let mtf_attrChatroom=(mutations, observer)=>{

        const chatBlock = document.querySelector('ytd-live-chat-frame#chat')
        const cssElm = document.querySelector('ytd-watch-flexy')
        
        if(!cssElm.hasAttribute('userscript-chatblock')) setAttr(cssElm, 'userscript-chatblock', true);
        setAttr(cssElm,'userscript-chat-collapsed',!!chatBlock.hasAttribute('collapsed'));

        if(cssElm.hasAttribute('userscript-chatblock')&&!chatBlock.hasAttribute('collapsed')) lastShowTab='#chatroom'

        if( chatBlock && cssElm && cssElm.hasAttribute('userscript-chatblock') && !chatBlock.hasAttribute('collapsed') && !cid_chatFrameCheck){
            let dd=+new Date;
            cid_chatFrameCheck=setInterval(()=>{
                // mutation on iframe window would not trigger the observer
                // just check the first few seconds for this purpose.
                let chatFrameChecking, iframe;
                if(+new Date - dd>6750){
                    //
                }else if( isEmptyBody() ){

                    // bug. youtube iframe loaded with nothing

                    let button = document.querySelector('ytd-live-chat-frame#chat>.ytd-live-chat-frame#show-hide-button ytd-toggle-button-renderer')
                    if (button) {
                        setTimeout(function(){
                            if(button && button.parentNode && isChatExpand()){
                                button.click();
                                setTimeout(function(){
                                    if(button && button.parentNode && !isChatExpand()) button.click();
                                    button=null;
                                },80)
                            }else{
                                button=null;
                            }
                        },20)
                    } 

                
                }else if(chatFrameChecking=!!chatFrameElement('yt-live-chat-renderer #continuations')){
                    mtf_ChatExist();
                    $(document.querySelector('ytd-live-chat-frame#chat')).attr('yt-userscript-iframe-loaded','')
                }else{
                    return;
                }
                return (cid_chatFrameCheck=clearInterval(cid_chatFrameCheck));
            },270)
        }else if(chatBlock){
            chatBlock.removeAttribute('yt-userscript-iframe-loaded')

        }


    }

    Q.mtf_checkStatus_chatroom=()=>{
        var chatroom=document.querySelector('ytd-live-chat-frame#chat')
        if(!chatroom) return true;
        initMutationObserver(mtoVs,'mtoVisibility_Chatroom',mtf_attrChatroom)
        mtoVs.mtoVisibility_Chatroom.observe(chatroom, {          
            attributes: true,
            attributeFilter: ['collapsed'],
            attributeOldValue: true
        })
        mtf_attrChatroom()
        return false;
    }
    Q.$callOnceAsync('mtf_checkStatus_chatroom')





    clearMutationObserver(mtoVs,'mtoFlexyAttr')


    let mtf_attrFlexy=(mutations, observer)=>{
          


        const cssElm=document.querySelector('ytd-watch-flexy');
        if(!cssElm)return;

        let chatBlockStatusChanged = false
        let theaterStatusChanged = false;
        let twoColStatusChanged = false;
        let initalTriggering = !mutations

        if(!initalTriggering){
        
            for(const mutation of mutations) {
                if (mutation.attributeName == 'theater') {
                    theaterStatusChanged=true;
                }
                if (mutation.attributeName == 'userscript-chat-collapsed' || 'userscript-chatblock'){
                    chatBlockStatusChanged=true
                }
                if (mutation.attributeName == 'is-two-columns_'){
                
                    twoColStatusChanged=true;
                }
            }
        }
    

        if(theaterStatusChanged || chatBlockStatusChanged || twoColStatusChanged || initalTriggering  ){


            if(twoColStatusChanged || initalTriggering){

                fixDisplayForTheaterModeChanged();

            }else if(theaterStatusChanged){

                isChatExpandBeforeTheaterChange = isChatExpand()
                requestAnimationFrame(fixDisplayForTheaterModeChanged)
            }else if(chatBlockStatusChanged){
                //chatroom is shown or hidden

                let isOpenChatFrame =  !cssElm.hasAttribute('userscript-chat-collapsed') && cssElm.hasAttribute('userscript-chatblock') 
                

                new Promise(requestAnimationFrame).then(() => {
                    if (isOpenChatFrame && !isTheater() && isWideScreenWithTwoColumns()) {
                        switchTabActivity(null)
                    } else if(!isOpenChatFrame && !isTheater() && isWideScreenWithTwoColumns()){
                        setToActiveTab();
                    } else if(isTheater() && isWideScreenWithTwoColumns() && isOpenChatFrame && isWideScreenWithTwoColumns()){
                        ytBtnCancelTheater();
                    }
                })

            }

            

        }






    }


    Q.mtf_checkFlexy=()=>{
        var flexy=document.querySelector('ytd-watch-flexy')
        if(!flexy) return true;
        initMutationObserver(mtoVs,'mtoFlexyAttr',mtf_attrFlexy)
        mtoVs.mtoFlexyAttr.observe(flexy, {          
            attributes: true,
            attributeFilter: ['userscript-chat-collapsed','userscript-chatblock','theater','is-two-columns_'],
            attributeOldValue: true
        })
        mtf_attrFlexy()


        let columns = document.querySelector('ytd-page-manager#page-manager #columns')
        if(columns){
            setAttr(columns, 'userscript-scrollbar-render', true);
        }

        return false;
    }
    Q.$callOnceAsync('mtf_checkFlexy')



}




let switchTabActivity_lastTab = null

function switchTabActivity(activeLink) {


    if (activeLink && $(activeLink).is('.tab-btn-hidden')) return; // not allow to switch to hide tab

    if(isTheater() && isWideScreenWithTwoColumns()) activeLink=null;

    const links = document.querySelectorAll('#material-tabs a[userscript-tab-content]');



    function runAtEnd(){

        if(activeLink) lastShowTab=activeLink.getAttribute('userscript-tab-content')


        //override the default youtube coding event prevention

        //let elm=$('ytd-watch-flexy:not([is-two-columns_]) #tab-list:not(.tab-content-hidden) ytd-playlist-panel-renderer')[0];
        let elm=$('ytd-watch-flexy #tab-list:not(.tab-content-hidden) ytd-playlist-panel-renderer')[0];
        displayedPlaylist=elm;
        if(!!displayedPlaylist) $('ytd-watch-flexy').attr('userscript-auto-scroll-playlist',''); else $('ytd-watch-flexy').removeAttr('userscript-auto-scroll-playlist');
        
        scrollingVideosList=$('ytd-watch-flexy #tab-videos:not(.tab-content-hidden) [placeholder-videos]')[0]
        
    }

    for (const link of links) {
        let content = document.querySelector(link.getAttribute('userscript-tab-content'));
        if (link && content) {
            if (link !== activeLink) {
                $(link).removeClass("active");
                $(content).addClass("tab-content-hidden");
            } else {
                $(link).addClass("active");
                $(content).removeClass("tab-content-hidden");
                window.requestAnimationFrame(() => {
                 
                    content.focus()

                    runAtEnd()
                })
            }
        }
    }

    if(!activeLink){
        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) {

            if (!this.hasAttribute('userscript-tab-content')) return;

            switchTabActivity_lastTab = this.getAttribute('userscript-tab-content');

            if( isWideScreenWithTwoColumns() && !isTheater() && $(this).is(".tab-btn.active:not(.tab-btn-hidden)")){

                const sizeBtn=document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
                
                if(sizeBtn) sizeBtn.click();

            }else if($(this).is(".tab-btn.active:not(.tab-btn-hidden)")){
            

                switchTabActivity(null);


/*
                setTimeout(()=>{
                        window.scrollTo(0, 0);
                       
                },60)*/



            }else{

                new Promise(requestAnimationFrame).then(() => {
                    if(isChatExpand() && isWideScreenWithTwoColumns()) ytBtnCollapseChat();
                    else if(isWideScreenWithTwoColumns() && isTheater() ) ytBtnCancelTheater();
                }).then(() => {
                    setTimeout(()=>{
                        switchTabActivity(this)
                        
                        //setTimeout(makeBodyScroll,20);


                        setTimeout(()=>{
                            let rightTabs=document.querySelector('#right-tabs');
                            if(!isWideScreenWithTwoColumns() && rightTabs && rightTabs.offsetTop>0 && $(this).is('.active')){

                                window.scrollTo(0, rightTabs.offsetTop);
                               
                            }
                        },60)

                    }, clickInterval);
                })

            }

            

            evt.preventDefault();
        });

    }

}


// ---------------------------------------------------------------------------------------------
window.addEventListener("yt-navigate-finish", onNavigationEnd)

const singleColumnScrolling = (function() {
    var lastD = 0,
        lastF = 0;

    return function() {
        let pageY = pageYOffset;
        if (pageY < 10 && lastD === 0 && !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;
            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) && (lastD === 0 || lastF)) {
                lastD = 1;
                let {
                    offsetHeight
                } = header
                let {
                    offsetWidth
                } = targetElm

                targetElm.style.setProperty("--userscript-sticky-width", offsetWidth + 'px')
                targetElm.style.setProperty("--userscript-sticky", offsetHeight + 'px')
                
                setAttr(targetElm, 'userscript-sticky', true);

            } else if ((xyStatus == 1) && (lastD === 1 || lastF)) {
                lastD = 0;

                setAttr(targetElm, 'userscript-sticky', false);
            }


            targetElm = null;
            header = null;
            navElm = null;

        })

    }
})();

window.addEventListener("scroll", function() {
    if(!scriptEnable)return;
    singleColumnScrolling()
}, {
    capture: false,
    passive: true
})

var lastTheatreStatus = 0
window.addEventListener('resize', function() {
    if(!scriptEnable)return;

    requestAnimationFrame(() => {
        lastF = 1;
        singleColumnScrolling()
        lastF = 0;
    })

}, {
    capture: false,
    passive: true
})

window.addEventListener('beforeunload', function() {
    if(!scriptEnable)return;
    console.log('beforeunload')
    resetBeforeNav();
    let video=document.querySelector('video');
    if(video && !video.paused) video.pause(); 
}, {capture: true})

window.addEventListener('hashchange', function() { 
    if(!scriptEnable)return;
    console.log('hashchange')
    resetBeforeNav();
  }, {capture: true})
  
  window.addEventListener('popstate', function() { 
    if(!scriptEnable)return;
    console.log('popstate')
    resetBeforeNav();
  }, {capture: true})


function clearMutationObserver(o, key){
    if(o[key]) {
        o[key].takeRecords();
        o[key].disconnect();
        o[key]=null;
    }
}

function initMutationObserver(o, key, callback){


    clearMutationObserver(o,key)
    o[key]=new MutationObserver(callback)



}
/*

async function resizer(){

let full=true;
    (async ()=>{
        const primaryPlayer=$('ytd-watch-flexy #primary-inner>#player')[0]
        cssElm.style.setProperty('--userscript-resizing-primary-player-height', primaryPlayer?primaryPlayer.offsetHeight:'')
        if(!primaryPlayer)full=false;
})();


(async ()=>{
        const primaryInner=$('ytd-watch-flexy #primary-inner')[0]
        cssElm.style.setProperty('--userscript-resizing-primary-inner-height', primaryInner?primaryInner.offsetHeight:'')
        if(!primaryInner)full=false;
    })();
        
(async ()=>{
        const infoContents=$('ytd-watch-flexy #primary-inner>#info>#info-contents')[0]
        cssElm.style.setProperty('--userscript-resizing-primary-info-height', infoContents?infoContents.offsetHeight:'')
        if(!infoContents)full=false;
    })();

    
(async ()=>{
        const metaContents=$('ytd-watch-flexy #primary-inner>#meta>#meta-contents')[0]
        cssElm.style.setProperty('--userscript-resizing-primary-meta-height', metaContents?metaContents.offsetHeight:'')
        if(!metaContents)full=false;
    })();

    
(async ()=>{
    const secondaryInner=$('ytd-watch-flexy #secondary-inner')[0]
    cssElm.style.setProperty('--userscript-resizing-secondary-inner-height', secondaryInner?secondaryInner.offsetHeight:'')
    if(!secondaryInner)full=false;
})();

}


window.addEventListener('resize',function(){
    const cssElm=document.querySelector('ytd-watch-flexy');
    if(cssElm){


    }
    
    

   

}) */

document.addEventListener('wheel',function(evt){
    
    if(!scriptEnable)return;
    if(displayedPlaylist && displayedPlaylist.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=$(cssSelector1)[0]
    if(bool) $(res.m1).attr('is-two-columns',''); else $(res.m1).removeAttr('is-two-columns')
    }

    if(flag&2){
        res.m2=$(cssSelector2)[0]
    if(bool) $(res.m2).removeAttr('hidden'); else $(res.m2).attr('hidden','')
    }

    if(flag&4){
        res.m3=$(cssSelector3)[0]
    if(bool) $(res.m3).attr('hidden',''); else $(res.m3).removeAttr('hidden');
    }


    return res
    
    
    


}
/*
document.addEventListener('column1',function(evt){

    console.log(evt)
    document.aab=setVideosTwoColumns(1|2|4, false);
})
document.addEventListener('column2',function(evt){

    console.log(evt)
    document.aab=setVideosTwoColumns(1|2|4, true);
})
*/

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( !scrollingVideosList ) 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=totalHeight - 40;
    
    let h_advanced= h - ((visibleHeight>5*40)?visibleHeight*0.5:0);



    if(z>h_advanced && !isWideScreenWithTwoColumns() ){

        if(new Date -  lastScrollFetch<500) return; //prevent continuous calling

        lastScrollFetch=+new Date;
    
        let res= setVideosTwoColumns(2|4, true)
        if(res.m3 && res.m2){

            //wait for DOM change, just in case
            requestAnimationFrame(()=>{
                let {offsetTop}=res.m2  // as visiblity 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})












    // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js

}


;!(function $$() {
    'use strict';

    if(document.documentElement==null) return window.requestAnimationFrame($$)

var 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...
})();