Tabview Youtube

把Youtube Videos中的评论及视频列表制作成Tabs

目前为 2022-11-15 提交的版本。查看 最新版本

  1. /*
  2.  
  3. MIT License
  4.  
  5. Copyright (c) 2021 cyfung1031
  6.  
  7. Permission is hereby granted, free of charge, to any person obtaining a copy
  8. of this software and associated documentation files (the "Software"), to deal
  9. in the Software without restriction, including without limitation the rights
  10. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. copies of the Software, and to permit persons to whom the Software is
  12. furnished to do so, subject to the following conditions:
  13.  
  14. The above copyright notice and this permission notice shall be included in all
  15. copies or substantial portions of the Software.
  16.  
  17. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23. SOFTWARE.
  24.  
  25. */
  26. // ==UserScript==
  27. // @name Tabview Youtube
  28. // @name:en Tabview Youtube
  29. // @name:ja Tabview Youtube
  30. // @name:zh-TW Tabview Youtube
  31. // @name:zh-CN Tabview Youtube
  32. // @namespace http://tampermonkey.net/
  33. // @version 3.2.1
  34. // @license MIT
  35. // @description Make comments and lists into tabs for YouTube Videos
  36. // @description:en Make comments and lists into tabs for YouTube Videos
  37. // @description:ja YouTube動画のコメントやリストなどをタブに作成します
  38. // @description:zh-TW 把Youtube Videos中的評論及影片清單製作成Tabs
  39. // @description:zh-CN 把Youtube Videos中的评论及视频列表制作成Tabs
  40. // @author CY Fung
  41. // @match https://www.youtube.com/*
  42. // @icon https://github.com/cyfung1031/Tabview-Youtube/raw/main/images/icon128p.png
  43. // @run-at document-start
  44. // @grant GM_getResourceText
  45. // @resource contentCSS https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/954e19d6956945841e925b1f1d00ecf855fa8aac/css/style_content.css
  46. // @resource injectionJS1 https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/954e19d6956945841e925b1f1d00ecf855fa8aac/js/injection_script_1.js
  47. // @require https://cdnjs.cloudflare.com/ajax/libs/cash/8.1.1/cash.min.js
  48. // @noframes
  49. // ==/UserScript==
  50.  
  51. /* jshint esversion:8 */
  52.  
  53. function main($){
  54. // MIT License
  55. // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js
  56.  
  57.  
  58.  
  59.  
  60.  
  61.  
  62.  
  63.  
  64.  
  65.  
  66.  
  67.  
  68.  
  69.  
  70.  
  71.  
  72.  
  73.  
  74. -(function() {
  75. 'use strict';
  76.  
  77. function inIframe() {
  78. try {
  79. return window.self !== window.top;
  80. } catch (e) {
  81. return true;
  82. }
  83. }
  84.  
  85. if (inIframe()) return;
  86.  
  87. if (!$) return;
  88.  
  89. /**
  90. * SVG resources:
  91. * <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>
  92. */
  93.  
  94. const scriptVersionForExternal = '2022/05/07';
  95.  
  96. const isMyScriptInChromeRuntime = () => typeof GM === 'undefined' && typeof((((window || 0).chrome || 0).runtime || 0).getURL) == 'function'
  97.  
  98. const svgComments = `<path d="M40.068 13.465l-34.138.07A5.94 5.94 0 0 0 0 19.465v21.141a5.94 5.94 0 0 0 5.93 5.929H12v10a1
  99. 1 0 0 0 1.74.673l9.704-10.675 16.626-.068A5.94 5.94 0 0 0 46 40.536V19.395a5.94 5.94 0 0 0-5.932-5.93zM10 23.465h13a1
  100. 1 0 1 1 0 2H10a1 1 0 1 1 0-2zm26 14H10a1 1 0 1 1 0-2h26a1 1 0 1 1 0 2zm0-6H10a1 1 0 1 1 0-2h26a1 1 0 1 1 0
  101. 2zm18.072-28.93l-34.142-.07A5.94 5.94 0 0 0 14 8.395v3.124l26.064-.054c4.377 0 7.936 3.557 7.936 7.93v21.07.071
  102. 2.087l3.26 3.586a1 1 0 0 0 1.74-.673v-10h1.07A5.94 5.94 0 0 0 60 29.607V8.465a5.94 5.94 0 0 0-5.928-5.93z"/>`.trim();
  103.  
  104. 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
  105. 24h250c13.255 0 24-10.745 24-24V33zM91 39h43v34H91V39zM61 259H30v-34h31v34zm0-186H30V39h31v34zm73
  106. 186H91v-34h43v34zm-11-82.292v-55.417c0-8.25 5.868-11.302 12.77-6.783l40.237 26.272c6.902 4.519 6.958 11.914.056
  107. 16.434l-40.321 26.277c-6.902 4.52-12.742 1.467-12.742-6.783zM207 259h-43v-34h43v34zm0-186h-43V39h43v34zm61
  108. 186h-31v-34h31v34zm0-186h-31V39h31v34z"/>`.trim();
  109.  
  110. const svgInfo = `<path d="M11.812 0C5.289 0 0 5.289 0 11.812s5.289 11.813 11.812 11.813 11.813-5.29 11.813-11.813S18.335
  111. 0 11.812 0zm2.459 18.307c-.608.24-1.092.422-1.455.548s-.783.189-1.262.189c-.736
  112. 0-1.309-.18-1.717-.539s-.611-.814-.611-1.367c0-.215.015-.435.045-.659a8.23 8.23 0 0 1
  113. .147-.759l.761-2.688c.067-.258.125-.503.171-.731a3.24 3.24 0 0 0
  114. .068-.633c0-.342-.071-.582-.212-.717s-.412-.201-.813-.201c-.196
  115. 0-.398.029-.605.09s-.383.12-.529.176l.201-.828c.498-.203.975-.377 1.43-.521s.885-.218 1.29-.218c.731 0 1.295.178
  116. 1.692.53s.594.812.594 1.376c0 .117-.014.323-.041.617a4.13 4.13 0 0 1-.152.811l-.757
  117. 2.68c-.062.215-.117.461-.167.736s-.073.485-.073.626c0 .356.079.599.239.728s.435.194.827.194a2.4 2.4 0 0 0 .626-.097
  118. 3.56 3.56 0 0 0 .506-.17l-.203.827zm-.134-10.878c-.353.328-.778.492-1.275.492s-.924-.164-1.28-.492a1.57 1.57 0
  119. 0 1-.533-1.193c0-.465.18-.865.533-1.196a1.81 1.81 0 0 1 1.28-.497 1.79 1.79 0 0 1 1.275.497c.353.331.53.731.53
  120. 1.196s-.177.865-.53 1.193z"/>`.trim();
  121.  
  122. const svgPlayList = `<path d="M0 64h256v42.667H0zm0 85.333h256V192H0zm0 85.334h170.667v42.667H0zm341.333
  123. 0v-85.334h-42.666v85.334h-85.334v42.666h85.334v85.334h42.666v-85.334h85.334v-42.666z"/>`.trim();
  124.  
  125.  
  126. const DEBUG_LOG = false
  127.  
  128. const LAYOUT_VAILD = 1;
  129.  
  130. const LAYOUT_TWO_COLUMNS = 2;
  131. const LAYOUT_THEATER = 4;
  132. const LAYOUT_FULLSCREEN = 8;
  133. const LAYOUT_CHATROOM = 16;
  134. const LAYOUT_CHATROOM_COLLAPSED = 32;
  135. const LAYOUT_TAB_EXPANDED = 64;
  136. const LAYOUT_ENGAGEMENT_PANEL_EXPAND = 128;
  137. const LAYOUT_CHATROOM_EXPANDED = 256;
  138.  
  139. let pageType = null;
  140. //let toggleBtnDC = null; //DOM or number
  141. let chatroomDetails = null;
  142.  
  143.  
  144. /*
  145.  
  146. yt-action yt-add-element-to-app yt-autonav-pause-blur yt-autonav-pause-focus
  147. yt-autonav-pause-guide-closed yt-autonav-pause-guide-opened yt-autonav-pause-player
  148. yt-autonav-pause-player-ended yt-autonav-pause-scroll yt-autoplay-on-changed
  149. yt-close-tou-form yt-consent-bump-display-changed yt-focus-searchbox
  150. yt-get-context-provider yt-guide-close yt-guide-hover yt-guide-toggle
  151. yt-history-load yt-history-pop yt-load-invalidation-continuation
  152. yt-load-next-continuation yt-load-reload-continuation yt-load-tou-form
  153. yt-masthead-height-changed yt-navigate yt-navigate-cache yt-navigate-error
  154. yt-navigate-finish yt-navigate-redirect yt-navigate-set-page-offset
  155. yt-navigate-start yt-next-continuation-data-updated yt-open-hotkey-dialog
  156. yt-open-tou-form-loading-state yt-page-data-fetched yt-page-data-updated
  157. yt-page-data-will-update yt-page-manager-navigate-start yt-page-navigate-start
  158. yt-page-type-changed yt-player-attached yt-player-detached yt-player-released
  159. yt-player-requested yt-player-updated yt-popup-canceled yt-popup-closed
  160. yt-popup-opened yt-preconnect-urls yt-register-action yt-report-form-closed
  161. yt-report-form-opened yt-request-panel-mode-change yt-retrieve-location
  162. yt-service-request-completed yt-service-request-error yt-service-request-sent
  163. yt-set-theater-mode-enabled yt-show-survey yt-subscription-changed
  164. yt-swatch-changed yt-theater-mode-allowed yt-unregister-action yt-update-title
  165. yt-update-unseen-notification-count yt-viewport-scanned yt-visibility-refresh
  166.  
  167. */
  168.  
  169.  
  170.  
  171. const nullFunc = function(){}
  172. const _console = new Proxy(console, {
  173. get(target, prop, receiver){
  174. if(!DEBUG_LOG && prop==='log'){
  175. return nullFunc
  176. }
  177. return Reflect.get(...arguments)
  178. }
  179. });
  180.  
  181. const isPassiveArgSupport = (typeof IntersectionObserver==='function');
  182. // https://caniuse.com/?search=observer
  183. // https://caniuse.com/?search=addEventListener%20passive
  184.  
  185. const bubblePassive = isPassiveArgSupport?{capture:false, passive:true}:false;
  186. const capturePassive = isPassiveArgSupport?{capture:true, passive:true}:true;
  187.  
  188.  
  189. _console.log(38489)
  190.  
  191.  
  192. const querySelectorFromAnchor = HTMLElement.prototype.querySelector; // nodeType==1 // since 2022/07/12
  193. const querySelectorAllFromAnchor = HTMLElement.prototype.querySelectorAll; // nodeType==1 // since 2022/07/12
  194. const closestDOM = HTMLElement.prototype.closest;
  195. const elementRemove = HTMLElement.prototype.remove;
  196. const elementInsertBefore = HTMLElement.prototype.insertBefore; // since 2022/07/12
  197. const elementContains = HTMLElement.prototype.contains; // since 2022/07/12
  198.  
  199. const querySelectorFromAnchorFizzy = (root, id, selector)=>{
  200. if(!root) return null;
  201. if(root.id===id && root.matches(selector))return root;
  202. return querySelectorFromAnchor.call(root, selector) || null;
  203. }
  204.  
  205.  
  206. function maxUInt(s, d){
  207. let t = (d>s?d:s);
  208. return t>0?t:0;
  209. }
  210.  
  211. function scriptInjector(script_id, url_chrome, response_id){
  212.  
  213. let res={
  214. script_id: script_id,
  215. inject: function(){
  216.  
  217. let res = this, script_id = this.script_id;
  218.  
  219. if(!document.querySelector(`script#${script_id}`)){
  220. if (res.runtime_url){
  221. addScriptByURL(res.runtime_url).id = script_id;
  222. } else {
  223. addScript(`${res.injection_script}`).id = script_id;
  224. }
  225. }
  226.  
  227. }
  228. }
  229. res.script_id = script_id;
  230. if (isMyScriptInChromeRuntime()){
  231. res.runtime_url = window.chrome.runtime.getURL(url_chrome)
  232. } else {
  233. res.injection_script = GM_getResourceText(response_id);
  234. }
  235.  
  236. return res;
  237.  
  238.  
  239.  
  240. }
  241.  
  242. /*
  243. const script_inject_facp = scriptInjector(
  244. 'userscript-tabview-injection-facp',
  245. 'js/injectionScript_fixAutoComplete.js',
  246. "injectionFixAutoComplete");
  247. */
  248.  
  249. const script_inject_js1 = scriptInjector(
  250. 'userscript-tabview-injection-1',
  251. 'js/injection_script_1.js',
  252. "injectionJS1");
  253.  
  254. /** @type {(o: Object | null) => WeakRef | null} */
  255. const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); // typeof InvalidVar == 'undefined'
  256.  
  257. /** @type {(wr: Object | null) => Object | null} */
  258. const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);
  259.  
  260.  
  261.  
  262. const nonCryptoRandStr_base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
  263. function nonCryptoRandStr(/** @type {number} */ n){
  264. const result = new Array(n);
  265. const baseStr = nonCryptoRandStr_base;
  266. const bLen = baseStr.length;
  267. for ( let i = 0; i < n; i++ ) {
  268. let t = null
  269. do {
  270. t = baseStr.charAt(Math.floor(Math.random() * bLen));
  271. }while(i===0 && 10-t>0)
  272. result[i]=t;
  273. }
  274. return result.join('');
  275. }
  276.  
  277. /**
  278. * Class definition
  279. * @property {string} propName - propriety description
  280. * ...
  281. */
  282. class ObserverRegister{
  283. constructor(/** @type {()=>MutationObserver | IntersectionObserver} */ observerCreator){
  284. let uid = null;
  285. const uidStore = ObserverRegister.uidStore;
  286. do{
  287. uid = nonCryptoRandStr(5);
  288. }while(uidStore[uid])
  289. uidStore[uid] = true;
  290. /**
  291. * uid is the unique string for each observer
  292. * @type {string}
  293. * @public
  294. */
  295. this.uid = uid;
  296. /**
  297. * observerCreator is a function to create the observer
  298. * @type {Function}
  299. * @public
  300. */
  301. this.observerCreator = observerCreator
  302. /**
  303. * observer is the actual observer object
  304. * @type {MutationObserver | IntersectionObserver}
  305. * @public
  306. */
  307. this.observer = null;
  308. }
  309. bindElement(/** @type {HTMLElement} */ elm, ...args){
  310. if(elm.hasAttribute(`o3r-${this.uid}`))return false;
  311. elm.setAttribute(`o3r-${this.uid}`,'')
  312. if(this.observer===null){
  313. this.observer=this.observerCreator();
  314. }
  315. this.observer.observe(elm, ...args)
  316. return true
  317. }
  318. clear(/** @type {boolean} */ flag){
  319. if(this.observer !== null){
  320. //const uidStore = ObserverRegister.uidStore;
  321. if(flag === true){
  322. this.observer.takeRecords();
  323. this.observer.disconnect();
  324. }
  325. this.observer = null;
  326. for(const s of document.querySelectorAll(`[o3r-${this.uid}]`)) s.removeAttribute(`o3r-${this.uid}`)
  327. //uidStore[this.uid]=false;
  328. //this.uid = null;
  329. }
  330. }
  331. }
  332.  
  333. /**
  334. * 'uidStore' is the static store of strings used.
  335. * @static
  336. */
  337. ObserverRegister.uidStore = {}; //backward compatible with FireFox 55.
  338.  
  339.  
  340. const mtoMutation_body = new ObserverRegister(()=>{
  341. return new MutationObserver(FP.mtoBodyF)
  342. });
  343.  
  344. const mtoFlexyAttr = new ObserverRegister(()=>{
  345. return new MutationObserver(mtf_attrFlexy)
  346. });
  347.  
  348. const mtoVisibility_EngagementPanel = new ObserverRegister(()=>{
  349. return new MutationObserver(FP.mtf_attrEngagementPanel)
  350. });
  351. const sa_epanel = mtoVisibility_EngagementPanel.uid;
  352.  
  353. const mtoVisibility_Playlist = new ObserverRegister(()=>{
  354. return new AttributeMutationObserver({
  355. "hidden": FP.mtf_attrPlaylist
  356. })
  357. })
  358. const sa_playlist = mtoVisibility_Playlist.uid;
  359.  
  360. const mtoVisibility_Comments = new ObserverRegister(()=>{
  361. return new AttributeMutationObserver({
  362. "hidden": FP.mtf_attrComments
  363. })
  364. })
  365. const sa_comments = mtoVisibility_Comments.uid;
  366.  
  367.  
  368. const mtoVisibility_Chatroom = new ObserverRegister(()=>{
  369. return new AttributeMutationObserver({
  370. "collapsed": FP.mtf_attrChatroom
  371. })
  372. })
  373. const sa_chatroom = mtoVisibility_Chatroom.uid;
  374.  
  375.  
  376.  
  377.  
  378.  
  379. class ScriptEF {
  380. constructor() {
  381. this._id = scriptEC;
  382. }
  383. isValid() {
  384. return this._id === scriptEC;
  385. }
  386. }
  387.  
  388. class Timeout {
  389.  
  390. set(f, d, repeatCount) {
  391. if (this.cid > 0) return;
  392. let sEF = new ScriptEF();
  393. if (repeatCount > 0) {
  394.  
  395. let rc = repeatCount;
  396. const g = () => {
  397. this.cid = 0;
  398. if (!sEF.isValid()) return;
  399. let res = f();
  400. if (--rc <= 0) return;
  401. if (res === true) this.cid = timeline.setTimeout(g, d);
  402. }
  403. g();
  404.  
  405. } else {
  406.  
  407. const g = () => {
  408. this.cid = 0;
  409. if (!sEF.isValid()) return;
  410. if (f() === true) this.cid = timeline.setTimeout(g, d);
  411. }
  412. this.cid = timeline.setTimeout(g, d);
  413. }
  414. }
  415.  
  416. clear() {
  417. if (this.cid > 0) timeline.clearTimeout(this.cid);
  418. }
  419.  
  420. isEmpty() {
  421. return !this.cid
  422. }
  423.  
  424.  
  425. }
  426.  
  427. class Mutex {
  428.  
  429. constructor() {
  430. this.p = Promise.resolve()
  431. }
  432.  
  433. lockWith(f) {
  434.  
  435. this.p = this.p.then(() => {
  436. return new Promise(f)
  437. }).catch(console.warn)
  438. }
  439.  
  440. }
  441.  
  442.  
  443.  
  444. function prettyElm(/** @type {Element} */ elm) {
  445. if (!elm || !elm.nodeName) return null;
  446. const eId = elm.id || null;
  447. const eClsName = elm.className || null;
  448. return [elm.nodeName.toLowerCase(), typeof eId == 'string' ? "#" + eId : '', typeof eClsName == 'string' ? '.' + eClsName.replace(/\s+/g, '.') : ''].join('').trim();
  449. }
  450.  
  451. function extractTextContent(/** @type {Node} */ elm) {
  452. 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, '')
  453. }
  454.  
  455. function addScript(/** @type {string} */ scriptText) {
  456. const scriptNode = document.createElement('script');
  457. scriptNode.type = 'text/javascript';
  458. scriptNode.textContent = scriptText;
  459. try {
  460. document.documentElement.appendChild(scriptNode);
  461. } catch (e) {
  462. console.log('addScript Error', e)
  463. }
  464. return scriptNode;
  465. }
  466.  
  467. function addScriptByURL(/** @type {string} */ scriptURL) {
  468. const scriptNode = document.createElement('script');
  469. scriptNode.type = 'text/javascript';
  470. scriptNode.src = scriptURL;
  471. try {
  472. document.documentElement.appendChild(scriptNode);
  473. } catch (e) {
  474. console.log('addScriptByURL Error', e)
  475. }
  476. return scriptNode;
  477. }
  478.  
  479. function addStyle(/** @type {string} */ styleText, /** @type {HTMLElement | Document} */ container) {
  480. const styleNode = document.createElement('style');
  481. //styleNode.type = 'text/css';
  482. styleNode.textContent = styleText;
  483. (container || document.documentElement).appendChild(styleNode);
  484. return styleNode;
  485. }
  486.  
  487.  
  488. const stopIframePropagation = function(/** @type {Event} */ evt){
  489. if(scriptEnable && ((evt||0).target||0).nodeName === 'IFRAME'){
  490. evt.stopImmediatePropagation();
  491. evt.stopPropagation();
  492. }
  493. }
  494. document.addEventListener('mouseover', stopIframePropagation, true)
  495. document.addEventListener('mouseout', stopIframePropagation, true)
  496. document.addEventListener('mousedown', stopIframePropagation, true)
  497. document.addEventListener('mouseup', stopIframePropagation, true)
  498. document.addEventListener('keydown', stopIframePropagation, true)
  499. document.addEventListener('keyup', stopIframePropagation, true)
  500. document.addEventListener('mouseenter', stopIframePropagation, true)
  501. document.addEventListener('mouseleave', stopIframePropagation, true)
  502.  
  503.  
  504.  
  505. function isDOMVisible(/** @type {HTMLElement} */ elem) {
  506. // jQuery version : https://github.com/jquery/jquery/blob/a684e6ba836f7c553968d7d026ed7941e1a612d8/src/css/hiddenVisibleSelectors.js
  507. return !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length);
  508. }
  509.  
  510. function isNonEmptyString(s) {
  511. return typeof s == 'string' && s.length > 0;
  512. }
  513.  
  514.  
  515. async function nativeCall(/** @type {EventTarget} */ dom, /** @type {any[]} */ detail) {
  516. //console.log(1231)
  517. dom.dispatchEvent(new CustomEvent("userscript-call-dom", { detail: detail }))
  518. //console.log(1232)
  519. }
  520.  
  521. async function nativeFunc(/** @type {EventTarget} */ dom, /** @type {string} */ property, /** @type {any} */ args) {
  522. dom.dispatchEvent(new CustomEvent("userscript-call-dom-func", { detail: { property, args } }))
  523. }
  524.  
  525. // async function nativeValue(dom, property, args) {
  526. // dom.dispatchEvent(new CustomEvent("userscript-call-dom-value", { detail: { property, args } }))
  527. // }
  528. // async function nativeFuncStacked(/** @type {string} */ selector, /** @type {string} */ property, /** @type {any} */ args){
  529. // document.dispatchEvent(new CustomEvent("userscript-call-dom-func-stacked", { detail: { selector, property, args } }))
  530. // }
  531. // async function nativeValueStacked(selector, property, args){
  532. // document.dispatchEvent(new CustomEvent("userscript-call-dom-value-stacked", { detail: { selector, property, args } }))
  533. // }
  534. // async function nativeConstStacked(selector, property, args){
  535. // document.dispatchEvent(new CustomEvent("userscript-call-dom-const-stacked", { detail: { selector, property, args } }))
  536. // }
  537.  
  538. function isCommentsK(ytdFlexyElm){
  539. return (ytdFlexyElm.getAttribute('tabview-youtube-comments')||'').indexOf('K')>=0
  540.  
  541. }
  542.  
  543. function akAttr(/** @type {HTMLElement} */ cssElm, /** @type {String} */ attrName, /** @type {boolean} */ isNegative, /** @type {string | any} */ flag) {
  544. // isNegative => incomplete loading
  545.  
  546. let u = parseInt(cssElm.getAttribute(attrName) || 0) || 0;
  547. let ak = Math.abs(u);
  548.  
  549. if (ak > 100 && isNegative && u < 0) {
  550.  
  551. } else if (ak > 100 && !isNegative && u > 0) {
  552.  
  553. } else {
  554. if (ak <= 100) {
  555. ak = 101;
  556. } else {
  557. ak++;
  558. if (ak >= 800) ak = 101;
  559. }
  560. // 101, 102, ... 799, 101
  561. }
  562.  
  563. cssElm.setAttribute(attrName, `${ isNegative ? -ak : ak }${ flag || '' }`)
  564. }
  565.  
  566.  
  567.  
  568. let timeout_resize_for_layout_change = new Timeout();
  569.  
  570. function dispatchWindowResize(){
  571. // for youtube to detect layout resize for adjusting Player tools
  572. return window.dispatchEvent(new Event('resize'));
  573. }
  574.  
  575. function setToggleBtnTxt(){
  576. if(chatroomDetails){
  577. _console.log(124234,'c=== ')
  578.  
  579. let chat = document.querySelector('ytd-live-chat-frame#chat');
  580. if(!chat) return;
  581. let txt = querySelectorFromAnchor.call(chat, 'span.yt-core-attributed-string[role="text"]');
  582. let c = txt.textContent;
  583. if(typeof c ==='string' && c.length>2){
  584.  
  585. if(chat.hasAttribute('collapsed')){
  586. _console.log(124234,'collapsed show expand ',chatroomDetails.txt_expand)
  587. if(c!==chatroomDetails.txt_expand)
  588. txt.textContent= chatroomDetails.txt_expand
  589. }else{
  590. _console.log(124234,'not collapsed show collapse ',chatroomDetails.txt_collapse)
  591. if(c!==chatroomDetails.txt_collapse)
  592. txt.textContent= chatroomDetails.txt_collapse
  593. }
  594. }
  595. }
  596. }
  597.  
  598.  
  599.  
  600. function layoutStatusChanged(/** @type {number} */ old_layoutStatus, /** @type {number} */ new_layoutStatus) {
  601.  
  602.  
  603. if (old_layoutStatus === new_layoutStatus) return;
  604.  
  605. const cssElm = kRef(ytdFlexy);
  606.  
  607. if (!cssElm) return;
  608.  
  609. const BF_TWOCOL_N_THEATER = LAYOUT_TWO_COLUMNS|LAYOUT_THEATER
  610.  
  611. let new_isExpandedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_EXPANDED)
  612. let new_isCollapsedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_COLLAPSED) && !!(new_layoutStatus & LAYOUT_CHATROOM)
  613.  
  614. let new_isTabExpanded = !!(new_layoutStatus & LAYOUT_TAB_EXPANDED);
  615. let new_isFullScreen = !!(new_layoutStatus & LAYOUT_FULLSCREEN);
  616. let new_isExpandEPanel = !!(new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPAND);
  617.  
  618.  
  619.  
  620. function showTabOrChat() {
  621.  
  622. layoutStatusMutex.lockWith(unlock => {
  623.  
  624. if (lstTab.lastPanel == '#chatroom') {
  625.  
  626. if (new_isTabExpanded) switchTabActivity(null)
  627. if (!new_isExpandedChat) ytBtnExpandChat();
  628.  
  629. } else if (lstTab.lastPanel && lstTab.lastPanel.indexOf('#engagement-panel-') == 0) {
  630.  
  631. if (new_isTabExpanded) switchTabActivity(null)
  632. if (!new_isExpandEPanel) ytBtnOpenEngagementPanel(lstTab.lastPanel);
  633.  
  634. } else {
  635.  
  636. if (new_isExpandedChat) ytBtnCollapseChat()
  637. if (!new_isTabExpanded) {setToActiveTab();}
  638.  
  639. }
  640.  
  641. timeline.setTimeout(unlock, 40);
  642.  
  643. })
  644. }
  645.  
  646. function hideTabAndChat() {
  647.  
  648. layoutStatusMutex.lockWith(unlock => {
  649.  
  650. if (new_isTabExpanded) switchTabActivity(null)
  651. if (new_isExpandedChat) ytBtnCollapseChat()
  652. if (new_isExpandEPanel) ytBtnCloseEngagementPanels();
  653.  
  654.  
  655. timeline.setTimeout(unlock, 40);
  656.  
  657. })
  658.  
  659. }
  660.  
  661. const statusCollapsedFalse = !!(new_layoutStatus & (LAYOUT_TAB_EXPANDED|LAYOUT_ENGAGEMENT_PANEL_EXPAND|LAYOUT_CHATROOM_EXPANDED))
  662. const statusCollapsedTrue = !statusCollapsedFalse
  663.  
  664. let changes = (old_layoutStatus & LAYOUT_VAILD) ? old_layoutStatus ^ new_layoutStatus : 0;
  665.  
  666. let chat_collapsed_changed = !!(changes & LAYOUT_CHATROOM_COLLAPSED)
  667. let chat_expanded_changed = !!(changes & LAYOUT_CHATROOM_EXPANDED)
  668. let tab_expanded_changed = !!(changes & LAYOUT_TAB_EXPANDED)
  669. let theater_mode_changed = !!(changes & LAYOUT_THEATER)
  670. let column_mode_changed = !!(changes & LAYOUT_TWO_COLUMNS)
  671. let fullscreen_mode_changed = !!(changes & LAYOUT_FULLSCREEN)
  672. let epanel_expanded_changed = !!(changes & LAYOUT_ENGAGEMENT_PANEL_EXPAND)
  673.  
  674. //console.log(169, 1, chat_collapsed_changed, tab_expanded_changed)
  675. //console.log(169, 2, new_isExpandedChat, new_isCollapsedChat, new_isTabExpanded)
  676. let BF_LayoutCh_Panel = (changes & (LAYOUT_TAB_EXPANDED|LAYOUT_CHATROOM_EXPANDED|LAYOUT_ENGAGEMENT_PANEL_EXPAND))
  677. let tab_change = BF_LayoutCh_Panel;
  678. let isChatOrTabExpandTriggering = !!((new_layoutStatus) & BF_LayoutCh_Panel);
  679. let isChatOrTabCollaspeTriggering = !!((~new_layoutStatus) & BF_LayoutCh_Panel);
  680.  
  681.  
  682.  
  683. let moreThanOneShown = (new_isTabExpanded + new_isExpandedChat + new_isExpandEPanel) > 1
  684.  
  685. let requestVideoResize = false;
  686.  
  687. // two column; not theater; tab collapse; chat expand; ep expand
  688. const IF_01a = LAYOUT_TWO_COLUMNS|LAYOUT_THEATER|LAYOUT_TAB_EXPANDED|LAYOUT_CHATROOM|LAYOUT_CHATROOM_COLLAPSED|LAYOUT_ENGAGEMENT_PANEL_EXPAND;
  689. const IF_01b = LAYOUT_TWO_COLUMNS|0|0|LAYOUT_CHATROOM|0|LAYOUT_ENGAGEMENT_PANEL_EXPAND;
  690.  
  691. // two column; not theater;
  692. const IF_02a = BF_TWOCOL_N_THEATER;
  693. const IF_02b = LAYOUT_TWO_COLUMNS;
  694.  
  695. // two column; not theater; tab expand; chat expand;
  696. const IF_03a = LAYOUT_TWO_COLUMNS|LAYOUT_THEATER|LAYOUT_TAB_EXPANDED|LAYOUT_CHATROOM|LAYOUT_CHATROOM_COLLAPSED;
  697. const IF_03b = LAYOUT_TWO_COLUMNS|0|LAYOUT_TAB_EXPANDED|LAYOUT_CHATROOM|0;
  698.  
  699. // two column; tab expand; chat expand;
  700. const IF_06a = LAYOUT_TWO_COLUMNS|LAYOUT_TAB_EXPANDED|LAYOUT_CHATROOM|LAYOUT_CHATROOM_COLLAPSED;
  701. const IF_06b = LAYOUT_TWO_COLUMNS|LAYOUT_TAB_EXPANDED|LAYOUT_CHATROOM|0;
  702.  
  703. // two column; theater;
  704. const IF_04a = BF_TWOCOL_N_THEATER;
  705. const IF_04b = BF_TWOCOL_N_THEATER;
  706.  
  707. // not fullscreen; two column; not theater; not tab expand; not EP expand; not expand chat
  708. const IF_05a = LAYOUT_FULLSCREEN|LAYOUT_TWO_COLUMNS|LAYOUT_THEATER|LAYOUT_TAB_EXPANDED|LAYOUT_ENGAGEMENT_PANEL_EXPAND|LAYOUT_CHATROOM_EXPANDED;
  709. const IF_05b = 0|LAYOUT_TWO_COLUMNS|0|0|0|0;
  710.  
  711.  
  712.  
  713. if(new_isFullScreen){
  714.  
  715.  
  716. if (tab_change == LAYOUT_CHATROOM_EXPANDED && (new_layoutStatus & IF_06a) === IF_06b && statusCollapsedFalse && !column_mode_changed) {
  717.  
  718. // two column; tab expand; chat expand;
  719. switchTabActivity(null);
  720.  
  721.  
  722. }
  723. if( !!(tab_change & LAYOUT_CHATROOM_EXPANDED) && new_isExpandedChat ){
  724. //tab_change = LAYOUT_CHATROOM_EXPANDED
  725. //tab_change = LAYOUT_CHATROOM_EXPANDED|LAYOUT_TAB_EXPANDED
  726. timeline.setTimeout(() => {
  727. let scrollElement = document.querySelector('ytd-app[scrolling]')
  728. if(!scrollElement) return;
  729. // single column view; click button; scroll to tab content area 100%
  730. let chatFrame = document.querySelector('ytd-live-chat-frame#chat');
  731. if (chatFrame && isChatExpand()) {
  732. _console.log(7290,1)
  733. chatFrame.scrollIntoView(true);
  734. }
  735. }, 60)
  736.  
  737. }
  738.  
  739.  
  740.  
  741. if (!!(tab_change & LAYOUT_ENGAGEMENT_PANEL_EXPAND) && new_isExpandEPanel) {
  742. timeline.setTimeout(() => {
  743. let scrollElement = document.querySelector('ytd-app[scrolling]')
  744. if(!scrollElement) return;
  745. // single column view; click button; scroll to tab content area 100%
  746. let epPanel = document.querySelector('ytd-engagement-panel-section-list-renderer[visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]');
  747. if (epPanel ) {
  748. _console.log(7290,2)
  749.  
  750. let pi=50;
  751. let cid = setInterval(()=>{
  752. if(--pi) epPanel.scrollIntoView(true); else clearInterval(cid)
  753. },17)
  754. //
  755. }
  756. }, 60)
  757.  
  758. }
  759.  
  760.  
  761.  
  762. }else if (fullscreen_mode_changed) {
  763.  
  764.  
  765.  
  766. if( !new_isFullScreen && statusCollapsedTrue && isWideScreenWithTwoColumns() && !isTheater()){
  767. showTabOrChat();
  768. requestVideoResize = true;
  769. } else if( !new_isFullScreen && statusCollapsedFalse && isWideScreenWithTwoColumns() && isTheater()){
  770. ytBtnCancelTheater();
  771. requestVideoResize = true;
  772. }
  773.  
  774. }/*else if ( !fullscreen_mode_changed && !epanel_expanded_changed && !column_mode_changed && !theater_mode_changed && (chat_collapsed_changed||tab_expanded_changed) && !new_isExpandedChat && !new_isTabExpanded && new_isCollapsedChat ){
  775.  
  776. // switch between live play video and replay video
  777. // force chat room display
  778.  
  779. //if(lstTab){
  780. //if(lstTab.lastPanel!='#chatroom')
  781. //lstTab.lastPanel = '#chatroom';
  782. showTabOrChat();
  783.  
  784. //}
  785.  
  786. }*/ else if((new_layoutStatus & IF_01a) === IF_01b && !column_mode_changed && (tab_change==LAYOUT_CHATROOM_EXPANDED || tab_change==LAYOUT_ENGAGEMENT_PANEL_EXPAND) ){
  787.  
  788. // two column; not theater; tab collapse; chat expand; ep expand
  789.  
  790.  
  791.  
  792. if(epanel_expanded_changed){
  793. layoutStatusMutex.lockWith(unlock => {
  794. ytBtnCollapseChat();
  795. setTimeout(unlock,13)
  796. })
  797. }else if(chat_collapsed_changed){
  798. layoutStatusMutex.lockWith(unlock => {
  799. ytBtnCloseEngagementPanels();
  800. setTimeout(unlock,13)
  801. })
  802.  
  803. }
  804.  
  805. } else if (!tab_change && column_mode_changed && (new_layoutStatus & IF_02a) === IF_02b && moreThanOneShown) {
  806.  
  807. // two column; not theater;
  808. // moreThanOneShown
  809.  
  810. showTabOrChat();
  811. requestVideoResize = true;
  812.  
  813. } else if (tab_change == LAYOUT_CHATROOM_EXPANDED && (new_layoutStatus & IF_03a) === IF_03b && statusCollapsedFalse && !column_mode_changed) {
  814.  
  815. // two column; not theater; tab expand; chat expand;
  816.  
  817. switchTabActivity(null);
  818. requestVideoResize = true;
  819.  
  820. } else if (isChatOrTabExpandTriggering && (new_layoutStatus & IF_04a) === IF_04b && statusCollapsedFalse && (changes & BF_TWOCOL_N_THEATER) === 0 ) {
  821.  
  822. ytBtnCancelTheater();
  823. requestVideoResize = true;
  824.  
  825. } else if ((new_layoutStatus & IF_04a) === IF_04b && statusCollapsedFalse) {
  826.  
  827. hideTabAndChat();
  828. requestVideoResize = true;
  829.  
  830. } else if (isChatOrTabCollaspeTriggering && (new_layoutStatus & IF_02a) === IF_02b && statusCollapsedTrue && !column_mode_changed) {
  831.  
  832. if(tab_change==LAYOUT_ENGAGEMENT_PANEL_EXPAND){
  833.  
  834. lstTab.lastPanel = null;
  835.  
  836. if(new_isFullScreen){
  837.  
  838. }else{
  839. showTabOrChat();
  840. }
  841. }else if(tab_change==LAYOUT_CHATROOM_EXPANDED){
  842.  
  843. lstTab.lastPanel = null;
  844.  
  845. if(new_isFullScreen){
  846.  
  847. }else{
  848. showTabOrChat();
  849. }
  850. }else{
  851.  
  852. if(new_isFullScreen){
  853.  
  854. }else{
  855.  
  856. ytBtnSetTheater();
  857.  
  858. }
  859.  
  860. }
  861.  
  862. requestVideoResize = true;
  863.  
  864. } else if (!tab_change && !!(changes & BF_TWOCOL_N_THEATER) && (new_layoutStatus & IF_02a) === IF_02b && statusCollapsedTrue) {
  865.  
  866. showTabOrChat();
  867. requestVideoResize = true;
  868.  
  869. } else if ( (new_layoutStatus & IF_05a) === IF_05b ) {
  870. // bug fix for restoring from mini player
  871.  
  872. layoutStatusMutex.lockWith(unlock => {
  873. setToActiveTab();
  874. timeline.setTimeout(unlock, 40);
  875. })
  876.  
  877. requestVideoResize = true;
  878.  
  879. } else if (tab_expanded_changed) {
  880.  
  881. requestVideoResize = true;
  882.  
  883. }
  884.  
  885.  
  886.  
  887.  
  888.  
  889. if (requestVideoResize) {
  890.  
  891. timeout_resize_for_layout_change.clear();
  892. timeout_resize_for_layout_change.set(() => {
  893. dispatchWindowResize();
  894. }, 92)
  895.  
  896. }
  897.  
  898.  
  899.  
  900.  
  901. }
  902.  
  903. function fixLayoutStatus(x){
  904.  
  905.  
  906. const new_isExpandedChat = !(x & LAYOUT_CHATROOM_COLLAPSED) && (x & LAYOUT_CHATROOM)
  907.  
  908. return new_isExpandedChat ? (x | LAYOUT_CHATROOM_EXPANDED) : (x & ~LAYOUT_CHATROOM_EXPANDED);
  909.  
  910. }
  911.  
  912. const wls = new Proxy({
  913. /** @type {number | null} */
  914. layoutStatus:undefined
  915. }, {
  916. get: function(target, prop) {
  917. return target[prop];
  918. },
  919. set: function(target, prop, value) {
  920. if(prop=='layoutStatus'){
  921. if (value === 0) {
  922. target[prop] = value;
  923. return true;
  924. }else if(target[prop]===value){
  925. return true;
  926. }else{
  927. if (!target.layoutStatus_pending) {
  928. target.layoutStatus_pending = true;
  929. const old_layoutStatus = target[prop];
  930. target[prop] = value;
  931. layoutStatusMutex.lockWith(unlock => {
  932. target.layoutStatus_pending = false;
  933. layoutStatusChanged((old_layoutStatus), (target[prop]));
  934. timeline.setTimeout(unlock, 40)
  935. })
  936. return true;
  937. }
  938. }
  939. }
  940. target[prop] = value;
  941. return true;
  942. },
  943. has: function(target, prop) {
  944. return (prop in target);
  945. }
  946. });
  947.  
  948.  
  949.  
  950.  
  951.  
  952.  
  953. const svgElm = (w, h, vw, vh, p) => `<svg width="${w}" height="${h}" viewBox="0 0 ${vw} ${vh}" preserveAspectRatio="xMidYMid meet">${p}</svg>`
  954.  
  955. let settings = {
  956. defaultTab: "#tab-videos"
  957. };
  958.  
  959.  
  960.  
  961. function isVideoPlaying(video) {
  962. return video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA;
  963. }
  964.  
  965. function wAttr(elm, attr, kv) {
  966. 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) }
  967. }
  968.  
  969. function hideTabBtn(tabBtn) {
  970. //console.log('hideTabBtn', tabBtn)
  971. let isActiveBefore = tabBtn.classList.contains('active');
  972. tabBtn.classList.add("tab-btn-hidden");
  973. if (isActiveBefore) {
  974. setToActiveTab();
  975. }
  976. }
  977.  
  978. function hasAttribute(obj, key) {
  979. return obj && obj.hasAttribute(key);
  980. }
  981.  
  982. function isTheater() {
  983. const cssElm = kRef(ytdFlexy);
  984. return (cssElm && cssElm.hasAttribute('theater'))
  985. }
  986.  
  987. function isFullScreen() {
  988. const cssElm = kRef(ytdFlexy);
  989. return (cssElm && cssElm.hasAttribute('fullscreen'))
  990. }
  991.  
  992. function isChatExpand() {
  993. const cssElm = kRef(ytdFlexy);
  994. return cssElm && cssElm.hasAttribute('userscript-chatblock') && !cssElm.hasAttribute('userscript-chat-collapsed')
  995. }
  996.  
  997. function isWideScreenWithTwoColumns() {
  998. const cssElm = kRef(ytdFlexy);
  999. return (cssElm && cssElm.hasAttribute('is-two-columns_'))
  1000. }
  1001.  
  1002. function isAnyActiveTab() {
  1003. return $('#right-tabs .tab-btn.active').length > 0
  1004. }
  1005.  
  1006. function isEngagementPanelExpanded() { //note: not checking the visual elements
  1007. const cssElm = kRef(ytdFlexy);
  1008. return (cssElm && +cssElm.getAttribute('userscript-engagement-panel') > 0)
  1009. }
  1010.  
  1011. function engagement_panels_() {
  1012.  
  1013. let res = [];
  1014. let shownRes = [];
  1015.  
  1016. let v = 0,
  1017. k = 1,
  1018. count = 0;
  1019. for (const ePanel of document.querySelectorAll(
  1020. `ytd-watch-flexy ytd-engagement-panel-section-list-renderer[o3r-${sa_epanel}]`
  1021. )) {
  1022.  
  1023. let visibility = ePanel.getAttribute('visibility') //ENGAGEMENT_PANEL_VISIBILITY_EXPANDED //ENGAGEMENT_PANEL_VISIBILITY_HIDDEN
  1024.  
  1025. switch (visibility) {
  1026. case 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED':
  1027. v |= k;
  1028. count++;
  1029. shownRes.push(ePanel)
  1030. res.push({ ePanel, k, visible: true });
  1031. break;
  1032. case 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN':
  1033. res.push({ ePanel, k, visible: false });
  1034. break;
  1035. default:
  1036. res.push({ ePanel, k, visible: false });
  1037. }
  1038.  
  1039. k = k << 1;
  1040.  
  1041. }
  1042. return { list: res, value: v, count: count, shownRes };
  1043. }
  1044.  
  1045.  
  1046. function ytBtnOpenEngagementPanel(/** @type {number | string} */ panel_id) {
  1047.  
  1048. if (typeof panel_id == 'string') {
  1049. panel_id = panel_id.replace('#engagement-panel-', '');
  1050. panel_id = parseInt(panel_id);
  1051. }
  1052. if (panel_id >= 0) {} else return false;
  1053.  
  1054. let panels = engagement_panels_();
  1055.  
  1056. for (const { ePanel, k, visible } of panels.list) {
  1057. if ((panel_id & k) === k) {
  1058. if (!visible) ePanel.setAttribute('visibility', "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED");
  1059. } else {
  1060. if (visible) ytBtnCloseEngagementPanel(ePanel);
  1061. }
  1062. }
  1063.  
  1064. }
  1065.  
  1066. function ytBtnCloseEngagementPanel(/** @type {HTMLElement} */ s) {
  1067. //ePanel.setAttribute('visibility',"ENGAGEMENT_PANEL_VISIBILITY_HIDDEN");
  1068. let button = querySelectorFromAnchor.call(s,'ytd-watch-flexy ytd-engagement-panel-title-header-renderer #header > #visibility-button');
  1069.  
  1070. if (button){
  1071. button =
  1072. querySelectorFromAnchor.call(button, 'div.yt-spec-touch-feedback-shape') ||
  1073. querySelectorFromAnchor.call(button, 'ytd-button-renderer');
  1074. if(button) button.click();
  1075. }
  1076.  
  1077. }
  1078.  
  1079. function ytBtnCloseEngagementPanels() {
  1080. if (isEngagementPanelExpanded()) {
  1081. for (const s of document.querySelectorAll(
  1082. `ytd-watch-flexy ytd-engagement-panel-section-list-renderer[o3r-${sa_epanel}]`
  1083. )) {
  1084. if (s.getAttribute('visibility') == "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED") ytBtnCloseEngagementPanel(s);
  1085. }
  1086. }
  1087. }
  1088.  
  1089. function ytBtnSetTheater() {
  1090. if (!isTheater()) {
  1091. const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
  1092. if (sizeBtn) sizeBtn.click();
  1093. }
  1094. }
  1095.  
  1096. function ytBtnCancelTheater() {
  1097. if (isTheater()) {
  1098. const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
  1099. if (sizeBtn) sizeBtn.click();
  1100. }
  1101. }
  1102.  
  1103. function ytBtnExpandChat() {
  1104. let button = document.querySelector('ytd-live-chat-frame#chat[collapsed] > .ytd-live-chat-frame#show-hide-button')
  1105. if (button){
  1106. button =
  1107. querySelectorFromAnchor.call(button, 'div.yt-spec-touch-feedback-shape') ||
  1108. querySelectorFromAnchor.call(button, 'ytd-toggle-button-renderer');
  1109. if(button) button.click();
  1110. }
  1111. }
  1112.  
  1113. function ytBtnCollapseChat() {
  1114. let button = document.querySelector('ytd-live-chat-frame#chat:not([collapsed]) > .ytd-live-chat-frame#show-hide-button')
  1115. if (button){
  1116. button =
  1117. querySelectorFromAnchor.call(button, 'div.yt-spec-touch-feedback-shape') ||
  1118. querySelectorFromAnchor.call(button, 'ytd-toggle-button-renderer');
  1119. if(button) button.click();
  1120. }
  1121. }
  1122.  
  1123.  
  1124. const Q = {}
  1125.  
  1126.  
  1127.  
  1128. function chatFrameContentDocument() {
  1129. // non-null if iframe exist && contentDocument && readyState = complete
  1130. /** @type {HTMLIFrameElement | null} */
  1131. let iframe = document.querySelector('ytd-live-chat-frame iframe#chatframe');
  1132. if (!iframe) return null; //iframe must be there
  1133. /** @type {Document | null} */
  1134. let cDoc = null;
  1135. try {
  1136. cDoc = iframe.contentDocument;
  1137. } catch (e) {}
  1138. if (!cDoc) return null;
  1139. if (cDoc.readyState != 'complete') return null; //we must wait for its completion
  1140.  
  1141. return cDoc;
  1142.  
  1143. }
  1144.  
  1145. function chatFrameElement(/** @type {string} */ cssSelector) {
  1146. let cDoc = chatFrameContentDocument();
  1147. if (!cDoc) return null;
  1148. /** @type {HTMLElement | null} */
  1149. let elm = null;
  1150. try {
  1151. elm = cDoc.querySelector(cssSelector)
  1152. } catch (e) {
  1153. console.log('iframe error', e)
  1154. }
  1155. return elm;
  1156. }
  1157.  
  1158.  
  1159.  
  1160.  
  1161. function fixTabs() {
  1162.  
  1163.  
  1164. if (!scriptEnable) return;
  1165.  
  1166.  
  1167. let queryElement = document.querySelector('*:not(#tab-videos) > #related:not([non-placeholder-videos]) > ytd-watch-next-secondary-results-renderer')
  1168.  
  1169. let isRelocated = !!queryElement;
  1170.  
  1171.  
  1172.  
  1173. if (isRelocated) {
  1174.  
  1175. let relocatedRelated = closestDOM.call(queryElement, '#related'); // NOT NULL
  1176.  
  1177. let right_tabs = document.querySelector('#right-tabs');
  1178. let tab_videos = querySelectorFromAnchor.call(right_tabs,"#tab-videos");
  1179.  
  1180. if (!right_tabs || !tab_videos) return;
  1181.  
  1182. for (const s of querySelectorAllFromAnchor.call(relocatedRelated,'#related')) {
  1183. s.setAttribute('non-placeholder-videos', '')
  1184. }
  1185.  
  1186. let target_container = document.querySelector('ytd-watch-flexy:not([is-two-columns_]) #primary-inner, ytd-watch-flexy[is-two-columns_] #secondary-inner')
  1187.  
  1188. if (target_container) target_container.append(right_tabs) // last-child
  1189.  
  1190.  
  1191. let videos_related = relocatedRelated; // NOT NULL
  1192. $('[placeholder-videos]').removeAttr('placeholder-videos');
  1193. $('[placeholder-for-youtube-play-next-queue]').removeAttr('placeholder-for-youtube-play-next-queue');
  1194.  
  1195. tab_videos.appendChild(videos_related);
  1196. let videos_results_renderer = querySelectorFromAnchor.call(relocatedRelated,"ytd-watch-next-secondary-results-renderer");
  1197. if (videos_results_renderer) videos_results_renderer.setAttribute('data-dom-changed-by-tabview-youtube', scriptVersionForExternal);
  1198. videos_related.setAttribute('placeholder-for-youtube-play-next-queue', '')
  1199. videos_related.setAttribute('placeholder-videos', '')
  1200.  
  1201. $('[placeholder-videos]').on("scroll", windowScroll);
  1202.  
  1203.  
  1204.  
  1205.  
  1206. }
  1207.  
  1208.  
  1209.  
  1210. /** @type {HTMLElement | null} */
  1211. let chatroom = null;
  1212. if (chatroom = document.querySelector('*:not([data-positioner="before|#chat"]) + ytd-live-chat-frame#chat, ytd-live-chat-frame#chat:first-child')) {
  1213.  
  1214. let positioner = document.querySelector('tabview-youtube-positioner[data-positioner="before|#chat"]');
  1215. if (positioner) positioner.remove();
  1216.  
  1217.  
  1218. if (document.querySelector('.YouTubeLiveFilledUpView')) {
  1219. // no relocation
  1220. } else {
  1221.  
  1222. $(chatroom).insertBefore('#right-tabs')
  1223.  
  1224. }
  1225.  
  1226.  
  1227. $(positioner ? positioner : document.createElement('tabview-youtube-positioner')).attr('data-positioner', 'before|#chat').insertBefore(chatroom)
  1228.  
  1229.  
  1230.  
  1231. }
  1232.  
  1233.  
  1234. }
  1235.  
  1236. function handlerAutoCompleteExist() {
  1237.  
  1238.  
  1239. /** @type {HTMLElement} */
  1240. let autoComplete = this;
  1241.  
  1242. autoComplete.removeEventListener('autocomplete-sc-exist', handlerAutoCompleteExist, false)
  1243.  
  1244. let domId = autoComplete.getAttribute('data-autocomplete-input-id')
  1245. let searchBox = autoComplete.ownerDocument.querySelector(`[data-autocomplete-results-id="${domId}"]`)
  1246.  
  1247. if (!domId || !searchBox) return;
  1248.  
  1249. let positioner = searchBox.nextSibling;
  1250. if (positioner && positioner.nodeName.toLowerCase() == "autocomplete-positioner") {} else if (positioner && positioner.nodeName.toLowerCase() != "autocomplete-positioner") {
  1251. $(positioner = document.createElement("autocomplete-positioner")).insertAfter(searchBox);
  1252. } else {
  1253. $(positioner = document.createElement("autocomplete-positioner")).prependTo(searchBox.parentNode);
  1254. }
  1255. $(autoComplete).prependTo(positioner);
  1256.  
  1257. positioner.style.setProperty('--sb-margin-bottom', getComputedStyle(searchBox).marginBottom)
  1258. positioner.style.setProperty('--height', searchBox.offsetHeight + 'px')
  1259.  
  1260. }
  1261.  
  1262. function mtf_fixAutoCompletePosition(/** @type {HTMLElement} */ elmAutoComplete) {
  1263.  
  1264.  
  1265. elmAutoComplete.setAttribute('autocomplete-disable-updatesc', '')
  1266. elmAutoComplete.addEventListener('autocomplete-sc-exist', handlerAutoCompleteExist, false)
  1267.  
  1268. let tf = ()=>{
  1269. if(!document.documentElement.hasAttribute('tabview-injection-js-1-ready')) return setTimeout(tf, 300);
  1270. document.dispatchEvent(new CustomEvent('tabview-fix-autocomplete'))
  1271. }
  1272. tf();
  1273. //script_inject_facp.inject();
  1274.  
  1275. }
  1276.  
  1277. function mtf_AfterFixTabs() {
  1278.  
  1279.  
  1280. /** @type {HTMLElement | null} */
  1281. let ytdFlexyElm = kRef(ytdFlexy);
  1282. if (!scriptEnable || !ytdFlexyElm) return;
  1283.  
  1284. /** @type {HTMLElement | null} */
  1285. const rootElement = ytdFlexyElm;
  1286.  
  1287.  
  1288.  
  1289. const autocomplete = querySelectorFromAnchor.call(rootElement,'[placeholder-for-youtube-play-next-queue] input#suggestions-search + autocomplete-positioner > .autocomplete-suggestions[data-autocomplete-input-id]:not([position-fixed-by-tabview-youtube])')
  1290.  
  1291. if (autocomplete) {
  1292.  
  1293. const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')
  1294.  
  1295.  
  1296. if (searchBox) {
  1297.  
  1298.  
  1299. autocomplete.parentNode.setAttribute('position-fixed-by-tabview-youtube', '');
  1300. autocomplete.setAttribute('position-fixed-by-tabview-youtube', '');
  1301. autocomplete.setAttribute('userscript-scrollbar-render', '')
  1302.  
  1303. if (!searchBox.hasAttribute('is-set-click-to-toggle')) {
  1304. searchBox.setAttribute('is-set-click-to-toggle', '')
  1305. searchBox.addEventListener('click', function() {
  1306.  
  1307.  
  1308. setTimeout(() => {
  1309. const autocomplete = document.querySelector(`.autocomplete-suggestions[data-autocomplete-input-id="${ this.getAttribute('data-autocomplete-results-id') }"]`)
  1310.  
  1311. if (!autocomplete) return;
  1312.  
  1313. const isNotEmpty = (autocomplete.textContent || '').length > 0 && (this.value || '').length > 0;
  1314.  
  1315. if (isNotEmpty) {
  1316.  
  1317. let elmVisible = isDOMVisible(autocomplete)
  1318.  
  1319. if (elmVisible) $(autocomplete).hide();
  1320. else $(autocomplete).show();
  1321.  
  1322. }
  1323.  
  1324. }, 20);
  1325.  
  1326. })
  1327.  
  1328. let timeoutOnce_searchbox_keyup = new Timeout();
  1329. searchBox.addEventListener('keyup', function() {
  1330.  
  1331. timeoutOnce_searchbox_keyup.set(() => {
  1332.  
  1333. const autocomplete = document.querySelector(`.autocomplete-suggestions[data-autocomplete-input-id="${ this.getAttribute('data-autocomplete-results-id') }"]`)
  1334.  
  1335. if (!autocomplete) return;
  1336.  
  1337.  
  1338. const isNotEmpty = (autocomplete.textContent || '').length > 0 && (this.value || '').length > 0
  1339.  
  1340. if (isNotEmpty) {
  1341.  
  1342. let elmVisible = isDOMVisible(autocomplete)
  1343.  
  1344. if (!elmVisible) $(autocomplete).show();
  1345.  
  1346. }
  1347.  
  1348. }, 20);
  1349.  
  1350. })
  1351.  
  1352. }
  1353.  
  1354.  
  1355.  
  1356. }
  1357.  
  1358. }
  1359.  
  1360.  
  1361.  
  1362.  
  1363. let currentLastVideo = querySelectorFromAnchor.call(rootElement,'[placeholder-videos] #items ytd-compact-video-renderer:last-of-type')
  1364. let prevLastVideo = kRef(_cachedLastVideo);
  1365.  
  1366. if (prevLastVideo !== currentLastVideo && currentLastVideo) {
  1367. _cachedLastVideo = mWeakRef(currentLastVideo);
  1368. }
  1369.  
  1370. if (prevLastVideo !== currentLastVideo && currentLastVideo && prevLastVideo) {
  1371.  
  1372. let isPrevRemoved = !prevLastVideo.parentNode
  1373.  
  1374.  
  1375. function getVideoListHash() {
  1376.  
  1377. let res = [...document.querySelectorAll('[placeholder-videos] #items ytd-compact-video-renderer')].map(renderer => {
  1378. let a = querySelectorFromAnchor.call(renderer,'a[href*="watch"][href*="v="]')
  1379. if(!a) return ''; //???
  1380. return a.getAttribute('href')||''
  1381.  
  1382. }).join('|')
  1383. // /watch?v=XXXXX|/watch?v=XXXXXX|/watch?v=XXXXXX
  1384.  
  1385. // alternative - DOM.data.videoId
  1386. // let elms = document.querySelectorAll('[placeholder-videos] #items ytd-compact-video-renderer')
  1387. // let res = [...elms].map(elm=>elm.data.videoId||'').join('|') ;
  1388.  
  1389. if (res.indexOf('||') >= 0) {
  1390. res = '';
  1391. }
  1392.  
  1393. return res ? res : null;
  1394. }
  1395.  
  1396. if (isPrevRemoved) {
  1397.  
  1398. // this is the replacement of videos instead of addition
  1399.  
  1400. const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')
  1401.  
  1402. let currentPlayListHash = getVideoListHash() || null;
  1403.  
  1404. if (!currentPlayListHash) {
  1405.  
  1406. } else if (!videoListBeforeSearch && searchBox) {
  1407.  
  1408. videoListBeforeSearch = currentPlayListHash;
  1409. if (videoListBeforeSearch) {
  1410. //console.log('fromSearch', videoListBeforeSearch)
  1411.  
  1412. requestAnimationFrame(function() {
  1413.  
  1414. let renderer = document.querySelector('[placeholder-videos] ytd-watch-next-secondary-results-renderer');
  1415. if (searchBox && searchBox.parentNode) searchBox.blur();
  1416.  
  1417. if (renderer) {
  1418. let scrollParent = renderer.parentNode;
  1419. if (scrollParent.scrollHeight > scrollParent.offsetHeight) {
  1420. let targetTop = renderer.offsetTop;
  1421. if (searchBox && searchBox.parentNode == scrollParent) targetTop -= searchBox.offsetHeight
  1422. scrollParent.scrollTop = targetTop - scrollParent.firstChild.offsetTop;
  1423. }
  1424. }
  1425.  
  1426. });
  1427.  
  1428. }
  1429.  
  1430. } else if (videoListBeforeSearch) {
  1431.  
  1432. if (currentPlayListHash != videoListBeforeSearch) {
  1433.  
  1434. videoListBeforeSearch = null;
  1435. //console.log('fromSearch', videoListBeforeSearch)
  1436.  
  1437.  
  1438. }
  1439.  
  1440. }
  1441.  
  1442.  
  1443. }
  1444.  
  1445.  
  1446. }
  1447.  
  1448.  
  1449.  
  1450.  
  1451. }
  1452.  
  1453. function base_ChatExist() {
  1454.  
  1455. let ytdFlexyElm = kRef(ytdFlexy);
  1456. if (!scriptEnable || !ytdFlexyElm) return null;
  1457.  
  1458. // no mutation triggering if the changes are inside the iframe
  1459.  
  1460. // 1) Detection of #continuations inside iframe
  1461. // iframe ownerDocument is accessible due to same origin
  1462. // if the chatroom is collapsed, no determination of live chat or replay (as no #continuations and somehow a blank iframe doc)
  1463.  
  1464. // 2) Detection of meta tag
  1465. // This is fastest but not reliable. It is somehow a bug that the navigation might not update the meta tag content
  1466.  
  1467. // 3) Detection of HTMLElement inside video player for live video
  1468.  
  1469. // (1)+(3) = solution
  1470.  
  1471. let attr_chatblock = null
  1472. let attr_chatcollapsed = null;
  1473.  
  1474. const elmChat = document.querySelector('ytd-live-chat-frame#chat')
  1475. let elmCont = null;
  1476. if (elmChat) {
  1477. elmCont = chatFrameElement('yt-live-chat-renderer #continuations')
  1478.  
  1479.  
  1480. let s = 0;
  1481. if (elmCont) {
  1482. //not found if it is collapsed.
  1483. s |= querySelectorFromAnchor.call(elmCont,'yt-timed-continuation') ? 1 : 0;
  1484. s |= querySelectorFromAnchor.call(elmCont,'yt-live-chat-replay-continuation, yt-player-seek-continuation') ? 2 : 0;
  1485. //s |= elmCont.querySelector('yt-live-chat-restricted-participation-renderer')?4:0;
  1486. if (s == 1) {
  1487. attr_chatblock = 'chat-live';
  1488. }else if (s == 2) attr_chatblock = 'chat-playback';
  1489.  
  1490. if (s == 1) $("span#tab3-txt-loader").text('');
  1491.  
  1492. } else if (!ytdFlexyElm.hasAttribute('userscript-chatblock')) {
  1493. // live chat frame but type not known
  1494. attr_chatblock = '';
  1495.  
  1496. }
  1497. //keep unknown as original
  1498. let isCollapsed = !!elmChat.hasAttribute('collapsed');
  1499. attr_chatcollapsed = isCollapsed;
  1500.  
  1501. } else {
  1502. attr_chatblock = false;
  1503. attr_chatcollapsed = false;
  1504.  
  1505. }
  1506.  
  1507. return { attr_chatblock, attr_chatcollapsed }
  1508.  
  1509. }
  1510.  
  1511.  
  1512.  
  1513.  
  1514.  
  1515.  
  1516. let t_heated_BodyScroll = 0;
  1517.  
  1518. function windowScroll() {
  1519. let ct = Date.now();
  1520. if (ct - t_heated_BodyScroll < 6) return; // avoid duplicate calling
  1521. t_heated_BodyScroll = ct;
  1522. window.dispatchEvent(new Event("scroll")); // dispatch Scroll Event to Window for content display
  1523. }
  1524.  
  1525. /* items - > special case (2022/11/09) */
  1526.  
  1527.  
  1528. // continuous check for element relocation
  1529. function mtf_append_comments() {
  1530. /** @type {HTMLElement | null} */
  1531. let ytdFlexyElm = kRef(ytdFlexy);
  1532. if (!scriptEnable || !ytdFlexyElm) return;
  1533.  
  1534. /** @type {HTMLElement | null} */
  1535. const rootElement = ytdFlexyElm;
  1536.  
  1537. let comments = querySelectorFromAnchorFizzy(rootElement,'comments', '#primary ytd-watch-metadata ~ ytd-comments#comments');
  1538. if (comments) {
  1539. _console.log(3202)
  1540. $(comments).appendTo('#tab-comments').attr('data-dom-changed-by-tabview-youtube', scriptVersionForExternal)
  1541. }
  1542. }
  1543.  
  1544. // continuous check for element relocation
  1545. function mtf_liveChatBtnF() {
  1546. /** @type {HTMLElement | null} */
  1547. let ytdFlexyElm = kRef(ytdFlexy);
  1548. if (!scriptEnable || !ytdFlexyElm) return;
  1549.  
  1550. /** @type {HTMLElement | null} */
  1551. const rootElement = ytdFlexyElm;
  1552.  
  1553. let button = querySelectorFromAnchor.call(rootElement,'ytd-live-chat-frame#chat > .ytd-live-chat-frame#show-hide-button:nth-child(n+2)');
  1554. if (button){
  1555. let parentNode = closestDOM.call(button, 'ytd-live-chat-frame#chat');
  1556. if (!parentNode){
  1557. console.log('parentNode failed')
  1558. } else if( HTMLElement.prototype.prepend ){
  1559. // using prepend
  1560. HTMLElement.prototype.prepend.call(parentNode, button)
  1561.  
  1562. } else{
  1563. // using insertBefore
  1564. try{
  1565. elementInsertBefore.call(parentNode, button, parentNode.firstChild);
  1566. }catch(e){
  1567. console.log('element insert failed in old browser CE')
  1568. }
  1569. }
  1570. }
  1571. //if (button) button.parentNode.insertBefore(button, button.parentNode.firstChild)
  1572. }
  1573.  
  1574.  
  1575.  
  1576. // continuous check for element relocation
  1577. // fired at begining & window resize, etc
  1578. function mtf_append_playlist(playlist) {
  1579.  
  1580. /** @type {HTMLElement | null} */
  1581. let ytdFlexyElm = kRef(ytdFlexy);
  1582. if (!scriptEnable || !ytdFlexyElm || !playlist) return;
  1583.  
  1584. let ple1 = querySelectorFromAnchor.call(playlist, "*:not(#ytd-userscript-playlist) > ytd-playlist-panel-renderer#playlist:not(.ytd-miniplayer) #items.ytd-playlist-panel-renderer:not(:empty)");
  1585.  
  1586. if (ple1) {
  1587.  
  1588. let ct = Date.now();
  1589.  
  1590. let truePlaylist = closestDOM.call(ple1, 'ytd-playlist-panel-renderer#playlist');
  1591. if(!truePlaylist || truePlaylist.nodeType!==1) truePlaylist = null;
  1592. else {
  1593.  
  1594. let tab_list = document.querySelector("#tab-list");
  1595.  
  1596. if(!tab_list) return;
  1597.  
  1598. truePlaylist.setAttribute('tabview-true-playlist', ct)
  1599.  
  1600.  
  1601. for (const s of document.querySelectorAll(`ytd-playlist-panel-renderer#playlist:not([tabview-true-playlist="${ct}"])`)){
  1602. s.removeAttribute('tabview-true-playlist')
  1603. }
  1604. let $wrapper = getWrapper('ytd-userscript-playlist')
  1605. $wrapper.append(truePlaylist).appendTo(tab_list);
  1606. truePlaylist.setAttribute('data-dom-changed-by-tabview-youtube', scriptVersionForExternal)
  1607. setDisplayedPlaylist(); // relocation after re-layout
  1608. requestAnimationFrame(() => {
  1609. let ytdFlexyElm = kRef(ytdFlexy);
  1610. if (!scriptEnable || !ytdFlexyElm) return;
  1611. if (!switchTabActivity_lastTab && (ytdFlexyElm.getAttribute('tabview-selection') + '').indexOf('#tab-') === 0 && /https\:\/\/www\.youtube\.com\/watch.*[\?\&]list=[\w\-\_]+/.test(location.href)) {
  1612. if (setToActiveTab('#tab-list')) switchTabActivity_lastTab = '#tab-list';
  1613. }
  1614. })
  1615.  
  1616. }
  1617.  
  1618.  
  1619.  
  1620. }
  1621. }
  1622.  
  1623.  
  1624. // content fix - info & playlist
  1625. // fired at begining, and keep for in case any change
  1626. function mtf_fix_details() {
  1627.  
  1628. if (!scriptEnable) return;
  1629.  
  1630.  
  1631. let contentToggleBtn = document.querySelector('ytd-watch-flexy #tab-info ytd-expander tp-yt-paper-button#less.ytd-expander:not([hidden]), #tab-info ytd-expander tp-yt-paper-button#more.ytd-expander:not([hidden])')
  1632.  
  1633. if(contentToggleBtn){
  1634.  
  1635. (()=> {
  1636. const domElement = contentToggleBtn;
  1637. contentToggleBtn = null;
  1638. // if(!domElement.parentElement) return; // not working in pseudo custom element - parentNode = documentFragment
  1639. const expander = closestDOM.call(domElement, 'ytd-watch-flexy #tab-info ytd-expander')
  1640.  
  1641. if(!expander || expander.nodeType!==1) return; // checking whether it is still on the page
  1642. if(expander.style.getPropertyValue('--ytd-expander-collapsed-height')){
  1643. expander.style.setProperty('--ytd-expander-collapsed-height','')
  1644. }
  1645. nativeCall(expander, [
  1646. {'property':'canToggleJobId','value':1}, // false disable calculateCanCollapse in childrenChanged
  1647. {'property':'alwaysToggleable','value':false}, // this is checked in childrenChanged
  1648. {'property':'recomputeOnResize','value':false}, // no need to check toggleable
  1649. {'property':'isToggled','value':true}, // show full content
  1650. {'property':'canToggle','value':false}, // hide show more or less btn
  1651. {'property':'collapsedHeight','value':999999} // disable collapsed height check
  1652. ])
  1653.  
  1654. })();
  1655. }
  1656.  
  1657. let strcturedInfo=document.querySelector('ytd-watch-flexy #tab-info ytd-structured-description-content-renderer.style-scope.ytd-video-secondary-info-renderer[hidden]')
  1658. if(strcturedInfo){
  1659.  
  1660. (()=>{
  1661.  
  1662. strcturedInfo.removeAttribute('hidden');
  1663. })();
  1664. }
  1665.  
  1666. /*
  1667. let inlineInfoExpander = document.querySelector('ytd-watch-flexy #description.ytd-watch-metadata ytd-text-inline-expander#description-inline-expander.ytd-watch-metadata yt-formatted-string[split-lines].ytd-text-inline-expander');
  1668. let vsInfoExpander = document.querySelector('#tab-info ytd-expander.ytd-video-secondary-info-renderer #description.ytd-video-secondary-info-renderer yt-formatted-string[split-lines].ytd-video-secondary-info-renderer')
  1669. console.log(23234, inlineInfoExpander, vsInfoExpander, inlineInfoExpander.textContent, vsInfoExpander.textContent)
  1670. if(inlineInfoExpander && vsInfoExpander && inlineInfoExpander.textContent===vsInfoExpander.textContent){
  1671.  
  1672. inlineInfoExpander.classList.add('tabview-hidden-info')
  1673.  
  1674. }*/
  1675.  
  1676.  
  1677.  
  1678.  
  1679. // just in case the playlist is collapsed
  1680. let playlist = document.querySelector('#tab-list ytd-playlist-panel-renderer#playlist')
  1681. if(playlist && playlist.matches('[collapsed], [collapsible]')) {
  1682. (()=> {
  1683. const domElement = playlist;
  1684. playlist = null;
  1685. // if(!domElement.parentElement || domElement.nodeType!==1) return; // not working in pseudo custom element - parentNode = documentFragment
  1686. const tablist = closestDOM.call(domElement, 'ytd-watch-flexy #tab-list')
  1687.  
  1688. if(!tablist || tablist.nodeType!==1) return; // checking whether it is still on the page
  1689.  
  1690. if (domElement.hasAttribute('collapsed')) wAttr(domElement, 'collapsed', false);
  1691. if (domElement.hasAttribute('collapsible')) wAttr(domElement, 'collapsible', false);
  1692. })();
  1693. }
  1694.  
  1695.  
  1696.  
  1697. }
  1698.  
  1699.  
  1700.  
  1701. let mTime = Date.now()-152000000;
  1702. //let loadedComments = [];
  1703.  
  1704. const innerCommentsFuncs=[
  1705. // comments
  1706. function(){
  1707. _console.log(2907,1)
  1708.  
  1709. let span = document.querySelector("span#tab3-txt-loader")
  1710. let r = '0';
  1711. let txt = this.elm.textContent
  1712. if (typeof txt == 'string') {
  1713. let m = txt.match(/[\d\,\s]+/)
  1714. if (m) {
  1715. r = m[0].trim()
  1716. }
  1717. }
  1718. if (span){
  1719. let tab_btn = closestDOM.call(span,'.tab-btn[userscript-tab-content="#tab-comments"]')
  1720. if(tab_btn)tab_btn.setAttribute('loaded-comment','normal')
  1721. span.textContent = r;
  1722. }
  1723. setCommentSection(1);
  1724. },
  1725. // message
  1726. function(){
  1727. _console.log(2907,2)
  1728. let span = document.querySelector("span#tab3-txt-loader")
  1729. if (span){
  1730. let tab_btn = closestDOM.call(span,'.tab-btn[userscript-tab-content="#tab-comments"]')
  1731. if(tab_btn)tab_btn.setAttribute('loaded-comment','message')
  1732. span.textContent ='\u200B';
  1733. }
  1734. setCommentSection(1);
  1735. }
  1736. ]
  1737.  
  1738. /** @type {WeakMap<HTMLElement>} */
  1739. let loadedCommentsDT = new WeakMap();
  1740.  
  1741. let cmTime = 0;
  1742.  
  1743.  
  1744.  
  1745.  
  1746. function _innerCommentsLoader() {
  1747.  
  1748. /** @type {HTMLElement | null} */
  1749. let ytdFlexyElm = kRef(ytdFlexy);
  1750. if (!scriptEnable || !ytdFlexyElm) return;
  1751.  
  1752. _console.log(3434, pageType)
  1753. if (pageType!=='watch') return;
  1754.  
  1755.  
  1756.  
  1757. //console.log(823100,rootElement)
  1758.  
  1759. /** @type {Array<HTMLElement>} */
  1760. let qmElms = [...document.querySelectorAll('ytd-comments#comments #count.ytd-comments-header-renderer, ytd-comments#comments ytd-item-section-renderer.ytd-comments#sections #header ~ #contents > ytd-message-renderer.ytd-item-section-renderer')]
  1761.  
  1762.  
  1763. let eTime = +`${Date.now()-mTime}00`;
  1764.  
  1765. let res = new Array(qmElms.length);
  1766. res.newFound = false;
  1767.  
  1768.  
  1769. let ci = 0;
  1770. let latest = -1;
  1771.  
  1772. let retrival = cmTime;
  1773. cmTime = eTime;
  1774. for(const qmElm of qmElms){
  1775.  
  1776. let mgz = 0
  1777. if (qmElm.id === 'count') {
  1778. //#count.ytd-comments-header-renderer
  1779. mgz = 1;
  1780. } else if ((qmElm.textContent || '').trim()) {
  1781. //ytd-message-renderer.ytd-item-section-renderer
  1782. mgz = 2;
  1783. // it is possible to get the message before the header generation.
  1784. // sample link - https://www.youtube.com/watch?v=PVUZ8Nvr1ic
  1785. // sample link - https://www.youtube.com/watch?v=yz8AiQc1Bk8
  1786. }
  1787.  
  1788. if(mgz>0){
  1789.  
  1790.  
  1791. let lastUpdate = loadedCommentsDT.get(qmElm)||0;
  1792. let diff = retrival-lastUpdate
  1793. _console.log(2907, diff)
  1794. let isNew = (diff>4 || diff<-4);
  1795. if(!isNew){
  1796. loadedCommentsDT.set(qmElm,eTime);
  1797. }else{
  1798. loadedCommentsDT.set(qmElm,eTime+1);
  1799. res.newFound = true;
  1800. latest = ci;
  1801. }
  1802. res[ci]={
  1803. status: mgz,
  1804. text: qmElm.textContent,
  1805. elm: qmElm,
  1806. isNew: isNew,
  1807. isLatest: false, //set afterwards
  1808. f: innerCommentsFuncs[mgz-1]
  1809. }
  1810.  
  1811. ci++;
  1812. }
  1813.  
  1814.  
  1815. }
  1816. if(res.length>ci) res.length=ci;
  1817. if(latest>=0){
  1818. res[latest].isLatest = true;
  1819. }
  1820.  
  1821.  
  1822.  
  1823.  
  1824.  
  1825. _console.log(2908, res)
  1826.  
  1827. return res;
  1828.  
  1829.  
  1830. }
  1831.  
  1832. // function _innerCommentsLoader() {
  1833.  
  1834. // /** @type {HTMLElement | null} */
  1835. // let ytdFlexyElm = kRef(ytdFlexy);
  1836. // if (!scriptEnable || !ytdFlexyElm) return;
  1837.  
  1838. // if (deferredVarYTDHidden) return;
  1839.  
  1840.  
  1841.  
  1842. // //console.log(823100,rootElement)
  1843.  
  1844. // /** @type {Array<HTMLElement>} */
  1845. // let qmElms = [...document.querySelectorAll('ytd-comments#comments #count.ytd-comments-header-renderer, ytd-comments#comments ytd-item-section-renderer.ytd-comments#sections #header ~ #contents > ytd-message-renderer.ytd-item-section-renderer')]
  1846.  
  1847.  
  1848. // let di = 0;
  1849. // let dz = false;
  1850. // /** @type {WeakSet<HTMLElement>} */
  1851. // let dS = new WeakSet()
  1852. // for (const loadedComment of loadedComments) {
  1853. // let loadedCommentElm = loadedComment ? kRef(loadedComment) : null;
  1854.  
  1855. // if (!loadedCommentElm || !qmElms.includes(loadedCommentElm)) {
  1856. // loadedComments[di] = null;
  1857. // dz = true;
  1858. // } else {
  1859. // dS.add(loadedCommentElm)
  1860. // }
  1861. // di++;
  1862. // }
  1863. // if (dz) {
  1864. // loadedComments = loadedComments.filter(e => !!e)
  1865. // }
  1866.  
  1867. // let res = new Array(qmElms.length);
  1868. // res.newFound = false;
  1869.  
  1870. // let ci = 0;
  1871. // for (const qmElm of qmElms) {
  1872.  
  1873. // let mgz = 0
  1874. // if (qmElm.id === 'count') {
  1875. // //#count.ytd-comments-header-renderer
  1876. // mgz = 1;
  1877. // } else if ((qmElm.textContent || '').trim()) {
  1878. // //ytd-message-renderer.ytd-item-section-renderer
  1879. // mgz = 2;
  1880. // // it is possible to get the message before the header generation.
  1881. // // sample link - https://www.youtube.com/watch?v=PVUZ8Nvr1ic
  1882. // // sample link - https://www.youtube.com/watch?v=yz8AiQc1Bk8
  1883. // }
  1884.  
  1885. // if (mgz > 0) {
  1886. // let isNew = !dS.has(qmElm)
  1887. // if (isNew) {
  1888. // loadedComments.push(mWeakRef(qmElm))
  1889. // res.newFound = true;
  1890. // }
  1891.  
  1892. // res[ci] = {
  1893. // status: mgz,
  1894. // text: qmElm.textContent,
  1895. // elm: qmElm,
  1896. // isNew: isNew,
  1897. // f: innerCommentsFuncs[mgz-1]
  1898. // }
  1899.  
  1900. // }
  1901. // ci++;
  1902.  
  1903. // }
  1904.  
  1905. // let lastElm = kRef(loadedComments[loadedComments.length - 1])
  1906.  
  1907. // for (const entry of res) {
  1908. // entry.isLatest = entry.elm === lastElm;
  1909. // }
  1910.  
  1911. // dS = null
  1912.  
  1913.  
  1914.  
  1915. // _console.log(2908, res)
  1916.  
  1917. // return res;
  1918.  
  1919.  
  1920. // }
  1921.  
  1922.  
  1923. function restoreFetching(){
  1924.  
  1925.  
  1926. if(mtf_forceCheckLiveVideo_disable===2) return;
  1927.  
  1928.  
  1929. let ytdFlexyElm = kRef(ytdFlexy);
  1930. if(!ytdFlexyElm) return;
  1931.  
  1932. _console.log(2901)
  1933.  
  1934. if(isCommentsK(ytdFlexyElm))return;
  1935.  
  1936. _console.log(2902)
  1937.  
  1938. let visibleComments = querySelectorFromAnchor.call(ytdFlexyElm, 'ytd-comments#comments:not([hidden])')
  1939. if(!visibleComments) return;
  1940.  
  1941. _console.log(2903)
  1942.  
  1943. fetchPendings.length= 0 ;
  1944. fetched=false;
  1945.  
  1946. akAttr(ytdFlexyElm, 'tabview-youtube-comments', false, 'K');
  1947.  
  1948. const tabBtn = document.querySelector('[userscript-tab-content="#tab-comments"]');
  1949. let span = querySelectorFromAnchor.call(tabBtn,'span#tab3-txt-loader');
  1950. tabBtn.removeAttribute('loaded-comment')
  1951. span.innerHTML='';
  1952.  
  1953. if(tabBtn){
  1954. tabBtn.classList.remove("tab-btn-hidden")
  1955. }
  1956. _console.log(2905)
  1957.  
  1958.  
  1959. }
  1960.  
  1961. let fetched = false;
  1962. let pendingFetch = null;
  1963. const comments_ulfx = (innerCommentsLoaderRet)=> {
  1964.  
  1965. if (!scriptEnable || !ytdFlexy || !kRef(ytdFlexy)) return;
  1966.  
  1967. if (pageType!=='watch') return;
  1968.  
  1969. _console.log(980, 1)
  1970.  
  1971.  
  1972. let newFound = innerCommentsLoaderRet.newFound;
  1973.  
  1974. let b = innerCommentsLoaderRet
  1975.  
  1976. _console.log(980, 2, !newFound)
  1977.  
  1978. if (!newFound) {
  1979. if (b.length === 1) {
  1980. _console.log(980, 3, 1)
  1981. let entry = b[0]
  1982. pendingFetch = entry;
  1983. }
  1984. return;
  1985. }
  1986.  
  1987. pendingFetch=null;
  1988.  
  1989. if (b.length === 1) {
  1990.  
  1991. _console.log(980, 3, 3)
  1992. let entry = b[0]
  1993. entry.f();
  1994. fetched = true;
  1995.  
  1996. } else if (b.length > 1) {
  1997.  
  1998. _console.log(980, 3, 5)
  1999. let yy = null;
  2000. for (const entry of b) {
  2001. if (entry.isLatest === true && entry.isNew) {
  2002. yy = entry;
  2003. break;
  2004. }
  2005. }
  2006.  
  2007. _console.log(980, 3, 6, yy)
  2008. if (yy) {
  2009. yy.f();
  2010. fetched = true;
  2011. }
  2012.  
  2013. }
  2014. _console.log(2907, innerCommentsLoaderRet)
  2015.  
  2016. };
  2017.  
  2018.  
  2019. let mtc_nr_comments=0;
  2020. const cssOnceFunc_comments = (comments)=>{
  2021. // once per {ytd-comments#comments} detection
  2022.  
  2023. let ytdFlexyElm = kRef(ytdFlexy);
  2024. if (!scriptEnable || !ytdFlexyElm) return;
  2025.  
  2026. if (!comments) return;
  2027. _console.log(3901)
  2028.  
  2029. if(mtoVisibility_Comments.bindElement(comments)){
  2030. mtoVisibility_Comments.observer.check(9);
  2031. }
  2032.  
  2033. };
  2034.  
  2035. const cssOnceFunc_teaserInfo = (teaserInfo)=>{
  2036. //obsolete?
  2037.  
  2038. // for Teaser UI
  2039. // once per {#description-and-actions.style-scope.ytd-watch-metadata > #description > ytd-text-inline-expander} detection
  2040.  
  2041. let ytdFlexyElm = kRef(ytdFlexy);
  2042. if (!scriptEnable || !ytdFlexyElm) return ;
  2043. let addedInfo = document.querySelector('#tab-info ytd-expander[tabview-info-expander]');
  2044.  
  2045. if(!addedInfo) return;
  2046.  
  2047. if(!document.documentElement.hasAttribute('tabview-injection-js-1-ready')) return;
  2048.  
  2049. _console.log(3903)
  2050.  
  2051. teaserInfo.setAttribute('tabview-removed-duplicate','')
  2052.  
  2053.  
  2054. teaserInfo.dispatchEvent(new CustomEvent('tabview-no-duplicate-info'))
  2055. //console.log(56)
  2056.  
  2057.  
  2058.  
  2059. }
  2060.  
  2061. function setOnlyOneEPanel(ePanel){
  2062.  
  2063. layoutStatusMutex.lockWith(unlock => {
  2064.  
  2065. let cPanels = engagement_panels_();
  2066. for (const entry of cPanels.list) {
  2067. if (entry.ePanel != ePanel && entry.visible) ytBtnCloseEngagementPanel(entry.ePanel);
  2068. }
  2069. setTimeout(unlock, 30)
  2070.  
  2071. })
  2072.  
  2073. }
  2074.  
  2075. function immHidden(){
  2076. //if(t_preheat_ImmHidden>Date.now()) return setTimeout(immHidden,400); // avoid timeline reset due to change of video
  2077. let comments = document.querySelector('ytd-comments#comments')
  2078.  
  2079. if(!comments.hasAttribute('hidden')) return; // visible comments content
  2080.  
  2081.  
  2082. if(pageType==='watch' && Q.comments_section_loaded===1){
  2083. emptyCommentSection();
  2084.  
  2085. }
  2086.  
  2087. }
  2088.  
  2089. const FP = {
  2090.  
  2091. mtoBodyF: ( /** @type {MutationRecord[]} */ mutations, /** @type {MutationObserver} */ observer) => {
  2092. //subtree DOM mutation checker - {body} \single \subtree
  2093.  
  2094. if (!scriptEnable) return;
  2095. if (pageType!=='watch') return;
  2096.  
  2097. for (const mutation of mutations) {
  2098. for (const addedNode of mutation.addedNodes){
  2099. if (addedNode.nodeType === 1) {
  2100. if (addedNode.nodeName == "DIV" && addedNode.matches('.autocomplete-suggestion:not([autocomplete-disable-updatesc])') ) {
  2101. mtf_fixAutoCompletePosition(addedNode)
  2102. }else if(addedNode.nodeName == "DIV" && (addedNode.id==='lyricscontainer' || addedNode.id==='showlyricsbutton')){
  2103.  
  2104. goYoutubeGeniusLyrics();
  2105.  
  2106. }
  2107. }
  2108. }
  2109. }
  2110.  
  2111. },
  2112.  
  2113. mtf_attrPlaylist: (attrName, newValue) => {
  2114. //attr mutation checker - {ytd-playlist-panel-renderer#playlist} \single
  2115. //::attr ~ hidden
  2116. //console.log(1210)
  2117.  
  2118. _console.log(21311)
  2119. if (!scriptEnable) return;
  2120. if (pageType!=='watch') return;
  2121. let cssElm = kRef(ytdFlexy);
  2122. if (!cssElm) return;
  2123.  
  2124. _console.log(21312)
  2125.  
  2126. let playlist = document.querySelector('ytd-playlist-panel-renderer#playlist[tabview-true-playlist]')
  2127. let isAnyPlaylistExist = playlist && !playlist.hasAttribute('hidden');
  2128. const tabBtn = document.querySelector('[userscript-tab-content="#tab-list"]');
  2129. //console.log(1212.2, isPlaylistHidden, playlist.getAttribute('hidden'))
  2130. if (tabBtn) {
  2131. //console.log('attr playlist changed')
  2132. let isPlaylistTabHidden = tabBtn.classList.contains('tab-btn-hidden')
  2133. if (isPlaylistTabHidden && isAnyPlaylistExist) {
  2134. //console.log('attr playlist changed - no hide')
  2135. tabBtn.classList.remove("tab-btn-hidden");
  2136. } else if (!isPlaylistTabHidden && !isAnyPlaylistExist) {
  2137. //console.log('attr playlist changed - add hide')
  2138. hideTabBtn(tabBtn);
  2139. }
  2140. }
  2141. /* visible layout for triggering hidden removal */
  2142. akAttr(cssElm, 'tabview-youtube-playlist', !isAnyPlaylistExist);
  2143. },
  2144. mtf_attrComments: (attrName, newValue) => {
  2145. //attr mutation checker - {ytd-comments#comments} \single
  2146. //::attr ~ hidden
  2147.  
  2148. // *** consider this can happen immediately after pop state. timeout / interval might clear out.
  2149.  
  2150. if (pageType!=='watch') return;
  2151.  
  2152. let comments = document.querySelector('ytd-comments#comments')
  2153. const tabBtn = document.querySelector('[userscript-tab-content="#tab-comments"]');
  2154. if (!comments || !tabBtn) return;
  2155. let isCommentHidden = comments.hasAttribute('hidden')
  2156. //console.log('attr comments changed')
  2157.  
  2158.  
  2159. let ytdFlexyElm = kRef(ytdFlexy);
  2160. if (!scriptEnable || !ytdFlexyElm) return;
  2161.  
  2162. if( mtf_forceCheckLiveVideo_disable === 2 ){
  2163.  
  2164. }else if (!isCommentHidden) {
  2165.  
  2166.  
  2167. akAttr(ytdFlexyElm, 'tabview-youtube-comments', false, 'K');
  2168. emptyCommentSection();
  2169. tabBtn.classList.remove("tab-btn-hidden") //if contains
  2170.  
  2171.  
  2172. } else if (isCommentHidden) {
  2173.  
  2174. akAttr(ytdFlexyElm, 'tabview-youtube-comments', true, 'K');
  2175. immHidden();
  2176.  
  2177. }
  2178.  
  2179.  
  2180. },
  2181.  
  2182.  
  2183. mtf_attrChatroom: (attrName, newValue) => {
  2184. //attr mutation checker - {ytd-live-chat-frame#chat} \single
  2185. //::attr ~ collapsed
  2186.  
  2187.  
  2188.  
  2189. let ytdFlexyElm = kRef(ytdFlexy);
  2190. if (!scriptEnable || !ytdFlexyElm) return;
  2191. if (pageType!=='watch') return;
  2192.  
  2193.  
  2194. setToggleBtnTxt();
  2195.  
  2196.  
  2197. layoutStatusMutex.lockWith(unlock => {
  2198.  
  2199. const chatBlock = document.querySelector('ytd-live-chat-frame#chat')
  2200. const cssElm = kRef(ytdFlexy)
  2201.  
  2202. if (!chatBlock || !cssElm) {
  2203. unlock();
  2204. return;
  2205. }
  2206.  
  2207. if (pageType!=='watch') {
  2208. unlock();
  2209. return;
  2210. }
  2211.  
  2212. if (!cssElm.hasAttribute('userscript-chatblock')) wAttr(cssElm, 'userscript-chatblock', true);
  2213. _console.log(332,ytdFlexyElm.hasAttribute('userscript-chatblock'))
  2214. let isCollapsed = !!chatBlock.hasAttribute('collapsed');
  2215. wAttr(cssElm, 'userscript-chat-collapsed', isCollapsed);
  2216.  
  2217. if (cssElm.hasAttribute('userscript-chatblock') && !isCollapsed) lstTab.lastPanel = '#chatroom';
  2218.  
  2219. if (!isCollapsed && document.querySelector('#right-tabs .tab-btn.active') && isWideScreenWithTwoColumns() && !isTheater()) {
  2220. switchTabActivity(null);
  2221. timeline.setTimeout(unlock, 40);
  2222. } else {
  2223. unlock();
  2224. }
  2225.  
  2226. if (isCollapsed) {
  2227. chatBlock.removeAttribute('yt-userscript-iframe-loaded');
  2228. }
  2229.  
  2230. })
  2231.  
  2232.  
  2233.  
  2234.  
  2235. },
  2236.  
  2237. mtf_attrEngagementPanel: ( /** @type {MutationRecord[]} */ mutations, /** @type {MutationObserver} */ observer) => {
  2238. //attr mutation checker - {ytd-engagement-panel-section-list-renderer} \mutiple
  2239. //::attr ~ visibility
  2240.  
  2241. let ytdFlexyElm = kRef(ytdFlexy);
  2242. if (!scriptEnable || !ytdFlexyElm) return;
  2243.  
  2244. //multiple instance
  2245. if (mutations) {
  2246. for (const mutation of mutations) {
  2247. let ePanel = mutation.target;
  2248. if (ePanel.getAttribute('visibility') == 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED') {
  2249. setOnlyOneEPanel(ePanel);
  2250. break;
  2251. }
  2252. }
  2253. }
  2254. if (pageType!=='watch') return;
  2255.  
  2256. layoutStatusMutex.lockWith(unlock => {
  2257.  
  2258. const ePanel = document.querySelector('ytd-watch-flexy ytd-engagement-panel-section-list-renderer')
  2259. const cssElm = kRef(ytdFlexy)
  2260.  
  2261. if (!ePanel || !cssElm) {
  2262. unlock();
  2263. return;
  2264. }
  2265. let previousValue = +cssElm.getAttribute('userscript-engagement-panel') || 0;
  2266.  
  2267. let { shownRes, value, count } = engagement_panels_();
  2268. let nextValue = value;
  2269. let nextCount = count;
  2270.  
  2271.  
  2272. if (nextCount == 0 && cssElm.hasAttribute('userscript-engagement-panel')) {
  2273. storeLastPanel=null;
  2274. wAttr(cssElm, 'userscript-engagement-panel', false);
  2275. unlock();
  2276. } else {
  2277.  
  2278. if ((nextCount > 1) || (cssElm.hasAttribute('userscript-engagement-panel') && previousValue === nextValue)) {
  2279. unlock();
  2280. return;
  2281. }
  2282.  
  2283. cssElm.setAttribute('userscript-engagement-panel', nextValue);
  2284.  
  2285. let b = false;
  2286. if (previousValue != nextValue && nextValue > 0) {
  2287. lstTab.lastPanel = `#engagement-panel-${nextValue}`;
  2288. b = true;
  2289. storeLastPanel = mWeakRef( shownRes[0])
  2290. //console.log(9999, shownRes[0])
  2291. }
  2292.  
  2293. if (b && document.querySelector('#right-tabs .tab-btn.active') && isWideScreenWithTwoColumns() && !isTheater()) {
  2294. switchTabActivity(null);
  2295. timeline.setTimeout(unlock, 40);
  2296. } else {
  2297. unlock();
  2298. }
  2299. }
  2300.  
  2301. })
  2302.  
  2303.  
  2304.  
  2305.  
  2306. },
  2307.  
  2308. mtf_initalAttr_chatroom: () => {
  2309. // every per [new] {ytd-live-chat-frame#chat} detection - reset after mini-playview
  2310. let ytdFlexyElm = kRef(ytdFlexy);
  2311. if (!scriptEnable || !ytdFlexyElm) return true;
  2312.  
  2313. const rootElement = ytdFlexyElm;
  2314.  
  2315.  
  2316. if (!mgChatFrame.inPage()) {
  2317.  
  2318. mtoVisibility_Chatroom.clear(true);
  2319.  
  2320. let chatroom = querySelectorFromAnchor.call(rootElement,`ytd-live-chat-frame#chat:not([${sa_chatroom}]`)
  2321. if (chatroom) {
  2322. mgChatFrame.setVar(chatroom);
  2323.  
  2324. //console.log(124,chatroom)
  2325. if(mtoVisibility_Chatroom.bindElement(chatroom)){
  2326. mtoVisibility_Chatroom.observer.check(9)
  2327. }
  2328.  
  2329. chatroom = null
  2330. }
  2331. }
  2332. return true;
  2333.  
  2334. },
  2335.  
  2336.  
  2337.  
  2338.  
  2339.  
  2340. }
  2341.  
  2342.  
  2343.  
  2344.  
  2345.  
  2346. /** @type {WeakRef | null} */
  2347. let displayedPlaylist = null;
  2348. /** @type {WeakRef | null} */
  2349. let scrollingVideosList = null;
  2350.  
  2351. let scriptEnable = false;
  2352. let scriptEC = 0;
  2353. let lstTab = null;
  2354. function lstTabInit(){
  2355. lstTab =
  2356. {
  2357. lastTab: null,
  2358. lastPanel: null,
  2359. last: null
  2360. };
  2361. }
  2362.  
  2363. let _cachedLastVideo = null;
  2364. let videoListBeforeSearch = null;
  2365.  
  2366. /** @type {WeakRef | null} */
  2367. let ytdFlexy = null;
  2368.  
  2369. function pluginUnhook() {
  2370. _pluginUnhook();
  2371. emptyCommentSection();
  2372. }
  2373.  
  2374. function _pluginUnhook() {
  2375.  
  2376. //console.log(8001)
  2377.  
  2378. videoListBeforeSearch = null;
  2379. _cachedLastVideo = null;
  2380. lstTabInit()
  2381. displayedPlaylist = null;
  2382. scrollingVideosList = null;
  2383. scriptEnable = false;
  2384. scriptEC++;
  2385. if (scriptEC > 788888888) scriptEC = 188888888;
  2386. ytdFlexy = null;
  2387. wls.layoutStatus = 0;
  2388.  
  2389. //console.log('unc01')
  2390.  
  2391. mtoVisibility_EngagementPanel.clear(true)
  2392. mtoVisibility_Playlist.clear(true)
  2393. mtoVisibility_Comments.clear(true)
  2394.  
  2395. mgChatFrame.kVar = null;
  2396. mtoVisibility_Chatroom.clear(true)
  2397. mtoFlexyAttr.clear(true)
  2398.  
  2399.  
  2400. for (const elem of document.querySelectorAll(
  2401. ['ytd-expander[tabview-info-expander]'].join(', ')
  2402. )) {
  2403. elem.removeAttribute('tabview-info-expander');
  2404. }
  2405.  
  2406. mtoMutation_body.clear(true)
  2407. Q.mtf_chatBlockQ = null;
  2408.  
  2409.  
  2410.  
  2411. }
  2412.  
  2413.  
  2414.  
  2415. let pageLang = 'en';
  2416. const langWords ={
  2417. 'en':{
  2418. //'share':'Share',
  2419. 'info':'Info',
  2420. 'videos':'Videos',
  2421. 'playlist':'Playlist'
  2422. },
  2423. 'jp':{
  2424. //'share':'共有',
  2425. 'info':'情報',
  2426. 'videos':'動画',
  2427. 'playlist':'再生リスト'
  2428. },
  2429. 'tw':{
  2430. //'share':'分享',
  2431. 'info':'資訊',
  2432. 'videos':'影片',
  2433. 'playlist':'播放清單'
  2434. },
  2435. 'cn':{
  2436. //'share':'分享',
  2437. 'info':'资讯',
  2438. 'videos':'视频',
  2439. 'playlist':'播放列表'
  2440. },
  2441. 'du':{
  2442. //'share':'Teilen',
  2443. 'info':'Info',
  2444. 'videos':'Videos',
  2445. 'playlist':'Playlist'
  2446. },
  2447. 'fr':{
  2448. //'share':'Partager',
  2449. 'info':'Info',
  2450. 'videos':'Vidéos',
  2451. 'playlist':'Playlist'
  2452. },
  2453. 'kr':{
  2454. //'share':'공유',
  2455. 'info':'정보',
  2456. 'videos':'동영상',
  2457. 'playlist':'재생목록'
  2458. }
  2459. };
  2460.  
  2461. function getWord(tag){
  2462. return langWords[pageLang][tag]||langWords['en'][tag]||'';
  2463. }
  2464.  
  2465.  
  2466. function getTabsHTML() {
  2467.  
  2468. const sTabBtnVideos = `${svgElm(16,16,298,298,svgVideos)}<span>${getWord('videos')}</span>`;
  2469. const sTabBtnInfo = `${svgElm(16,16,23.625,23.625,svgInfo)}<span>${getWord('info')}</span>`;
  2470. const sTabBtnPlayList = `${svgElm(16,16,426.667,426.667,svgPlayList)}<span>${getWord('playlist')}</span>`;
  2471.  
  2472. let str1 = `
  2473. <paper-ripple class="style-scope yt-icon-button">
  2474. <div id="background" class="style-scope paper-ripple" style="opacity:0;"></div>
  2475. <div id="waves" class="style-scope paper-ripple"></div>
  2476. </paper-ripple>
  2477. `;
  2478.  
  2479. let str_fbtns = `
  2480. <div class="font-size-right">
  2481. <div class="font-size-btn font-size-plus">
  2482. <svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet"
  2483. stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">
  2484. <path d="M12 25h26"/><path d="M25 12v26"/>
  2485. </svg>
  2486. </div><div class="font-size-btn font-size-minus">
  2487. <svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet"
  2488. stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">
  2489. <path d="M12 25h26"/>
  2490. </svg>
  2491. </div>
  2492. </div>
  2493. `.replace(/[\r\n]+/g,'');
  2494.  
  2495. const str_tabs = [
  2496. `<a id="tab-btn1" data-name="info" userscript-tab-content="#tab-info" class="tab-btn">${sTabBtnInfo}${str1}${str_fbtns}</a>`,
  2497. `<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}${str_fbtns}</a>`,
  2498. `<a id="tab-btn4" userscript-tab-content="#tab-videos" data-name="videos" class="tab-btn">${sTabBtnVideos}${str1}${str_fbtns}</a>`,
  2499. `<a id="tab-btn5" userscript-tab-content="#tab-list" class="tab-btn tab-btn-hidden">${sTabBtnPlayList}${str1}${str_fbtns}</a>`
  2500. ].join('');
  2501.  
  2502. let addHTML = `
  2503. <div id="right-tabs">
  2504. <header>
  2505. <div id="material-tabs">
  2506. ${str_tabs}
  2507. </div>
  2508. </header>
  2509. <div class="tab-content">
  2510. <div id="tab-info" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2511. <div id="tab-comments" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2512. <div id="tab-videos" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2513. <div id="tab-list" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2514. </div>
  2515. </div>
  2516. `;
  2517.  
  2518. return addHTML;
  2519.  
  2520. }
  2521.  
  2522. function getLang(){
  2523.  
  2524. let lang = 'en';
  2525. let htmlLang = ((document||0).documentElement||0).lang||'';
  2526. switch (htmlLang) {
  2527. case 'en':
  2528. case 'en-GB':
  2529. lang = 'en';
  2530. break;
  2531. case 'de':
  2532. case 'de-DE':
  2533. lang = 'du';
  2534. break;
  2535. case 'fr':
  2536. case 'fr-CA':
  2537. case 'fr-FR':
  2538. lang = 'fr';
  2539. break;
  2540. case 'zh-Hant':
  2541. case 'zh-Hant-HK':
  2542. case 'zh-Hant-TW':
  2543. lang = 'tw';
  2544. break;
  2545. case 'zh-Hans':
  2546. case 'zh-Hans-CN':
  2547. lang = 'cn';
  2548. break;
  2549. case 'ja':
  2550. case 'ja-JP':
  2551. lang = 'jp';
  2552. break;
  2553. case 'ko':
  2554. case 'ko-KR':
  2555. lang = 'kr';
  2556. break;
  2557. default:
  2558. lang = 'en';
  2559. }
  2560. if(langWords[lang]) pageLang = lang; else pageLang = 'en';
  2561.  
  2562. }
  2563.  
  2564. function checkEvtTarget(evt, nodeNames){
  2565. return nodeNames.includes((((evt||0).target||0).nodeName||0));
  2566. }
  2567.  
  2568.  
  2569.  
  2570. let cid_nav_end = 0;
  2571. let rc_nav_end = 0;
  2572.  
  2573. const symbol_noRepeat = Symbol();
  2574.  
  2575. function globalHook(eventType, func){
  2576.  
  2577. document.addEventListener(eventType,function(evt){
  2578. if(evt[symbol_noRepeat])return;
  2579. evt[symbol_noRepeat]=true;
  2580.  
  2581. new Promise(()=>{
  2582.  
  2583. func(evt);
  2584.  
  2585. })
  2586.  
  2587. },capturePassive)
  2588.  
  2589. }
  2590.  
  2591.  
  2592. let initializerResolve = null;
  2593.  
  2594. let initializerLock = new Promise(resolve=>{initializerResolve=resolve});
  2595.  
  2596. const S_GENERAL_RENDERERS = ['YTD-TOGGLE-BUTTON-RENDERER','YTD-MENU-RENDERER']
  2597.  
  2598.  
  2599.  
  2600.  
  2601. function pageCheck(){
  2602. // yt-rendererstamper-finished
  2603. // yt-player-updated
  2604. // yt-page-data-updated
  2605. // yt-watch-comments-ready
  2606. // [is-two-columns_] attr changed
  2607. let m_teaser_info = document.querySelector('#description-and-actions.style-scope.ytd-watch-metadata > #description ytd-text-inline-expander:not([tabview-removed-duplicate])')
  2608. if(m_teaser_info) cssOnceFunc_teaserInfo(m_teaser_info)
  2609. mtf_append_comments();
  2610. mtf_liveChatBtnF();
  2611. fixTabs();
  2612. mtf_AfterFixTabs();
  2613.  
  2614. }
  2615.  
  2616. globalHook('yt-rendererstamper-finished',(evt)=>{
  2617.  
  2618. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2619.  
  2620. _console.log(evt.target.nodeName,904, evt.type, evt.detail);
  2621. (async ()=>{
  2622.  
  2623. await initializerLock;
  2624.  
  2625. const nodeName = evt.target.nodeName.toUpperCase();
  2626. const node = evt.target
  2627. _console.log(evt.target.nodeName,905, evt.type, document.querySelector('ytd-live-chat-frame#chat'));
  2628. /*
  2629. if(toggleBtnDC>0){
  2630.  
  2631. }else if(toggleBtnDC){
  2632.  
  2633. fixChatFrameToggleButton();
  2634. toggleBtnDC=2;
  2635. }*/
  2636. if(!S_GENERAL_RENDERERS.includes(nodeName)){
  2637.  
  2638. mtc_nr_comments = Date.now();
  2639.  
  2640. switch(nodeName){
  2641. case 'YTD-ENGAGEMENT-PANEL-SECTION-LIST-RENDERER':
  2642. // ignore; trigger by page-updated
  2643. break;
  2644. default:
  2645. _console.log(782,1, nodeName)
  2646. pageCheck();
  2647. }
  2648. }else if(nodeName=='YTD-TOGGLE-BUTTON-RENDERER'){
  2649. _console.log(933,0)
  2650. //fixChatFrameToggleButton();
  2651. }
  2652.  
  2653. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  2654. //console.log(2344, document.querySelector('ytd-live-chat-frame#chat')?5:2)
  2655. //console.log(555, document.querySelectorAll('#meta-contents ytd-expander').length)
  2656. if(nodeName === 'YTD-MENU-RENDERER'){
  2657. //console.log('YTD-MENU-RENDERER !! ', evt.target)
  2658. }
  2659.  
  2660. if(nodeName === 'YTD-PLAYLIST-PANEL-RENDERER'){
  2661. //("yt-playlist-data-updated");
  2662. //yt-playlist-reloading
  2663. mtf_append_playlist(node);
  2664. let m_playlist = document.querySelector(`ytd-playlist-panel-renderer#playlist[tabview-true-playlist]:not([o3r-${sa_playlist}])`)
  2665.  
  2666. // once per {ytd-playlist-panel-renderer#playlist} detection
  2667.  
  2668. _console.log(3902, !!m_playlist)
  2669.  
  2670. let ytdFlexyElm = kRef(ytdFlexy);
  2671. if (!scriptEnable || !ytdFlexyElm){}
  2672. else if (m_playlist){
  2673.  
  2674. if(mtoVisibility_Playlist.bindElement(m_playlist)){
  2675. mtoVisibility_Playlist.observer.check(9); //delay check required for browser bug - hidden changed not triggered
  2676. }
  2677. m_playlist = null;
  2678.  
  2679. }
  2680. FP.mtf_attrPlaylist();
  2681.  
  2682. }else if (nodeName ==='YTD-WATCH-METADATA'){
  2683.  
  2684. }else if (nodeName ==='YTD-COMMENTS-HEADER-RENDERER'){
  2685. _console.log(3205,1)
  2686.  
  2687.  
  2688. let innerCommentsLoaderRet = _innerCommentsLoader();
  2689. comments_ulfx(innerCommentsLoaderRet);
  2690. _console.log(3205,3)
  2691.  
  2692. if(!fetched && pendingFetch){
  2693. requestAnimationFrame(()=>{
  2694.  
  2695. pendingFetch.f();
  2696. fetched= true;
  2697. fetchCommentsFinished();
  2698. fetched=false;pendingFetch=null;
  2699. })
  2700. }
  2701.  
  2702. if(fetched){
  2703. fetchCommentsFinished();
  2704. fetched=false;pendingFetch=null;
  2705. }
  2706.  
  2707.  
  2708.  
  2709.  
  2710. }else if(nodeName==='YTD-ENGAGEMENT-PANEL-SECTION-LIST-RENDERER'){
  2711.  
  2712.  
  2713. let ytdFlexyElm = kRef(ytdFlexy);
  2714. if (scriptEnable && ytdFlexyElm) {
  2715.  
  2716.  
  2717. let match = node.matches(`ytd-watch-flexy ytd-engagement-panel-section-list-renderer:not([o3r-${sa_epanel}])`);
  2718. if(match){
  2719.  
  2720.  
  2721. mtoVisibility_EngagementPanel.bindElement(node, {
  2722. attributes: true,
  2723. attributeFilter: ['visibility'],
  2724. attributeOldValue: true
  2725. })
  2726.  
  2727.  
  2728. }
  2729.  
  2730.  
  2731.  
  2732. }
  2733.  
  2734. }
  2735.  
  2736.  
  2737.  
  2738. })()
  2739.  
  2740. })
  2741.  
  2742. globalHook('yt-player-updated',(evt)=>{
  2743.  
  2744.  
  2745. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2746.  
  2747. mtc_nr_comments = Date.now();
  2748.  
  2749. _console.log(evt.target.nodeName,904, evt.type);
  2750. _innerCommentsLoader();
  2751. //restoreFetching(); //yt-player-updated might happen after comments ready
  2752. if(!fetched){
  2753. window.dispatchEvent(new Event("scroll"));
  2754. }
  2755. pageCheck();
  2756. //fixChatFrameToggleButton();
  2757.  
  2758. (async ()=>{
  2759. await initializerLock;
  2760. let nodeName = evt.target.nodeName.toUpperCase()
  2761. _console.log(nodeName,905, evt.type)
  2762.  
  2763. _console.log(2344,evt.type, document.querySelector('ytd-live-chat-frame#chat')?5:2)
  2764.  
  2765.  
  2766. if(nodeName === 'YTD-PLAYER'){
  2767.  
  2768. _console.log(2554, nodeName, evt.type, evt.detail )
  2769.  
  2770. let m_comments = document.querySelector(`ytd-comments#comments:not([o3r-${sa_comments}])`);
  2771. if(m_comments) cssOnceFunc_comments(m_comments);
  2772. FP.mtf_initalAttr_chatroom();
  2773.  
  2774. }
  2775.  
  2776. })();
  2777.  
  2778.  
  2779.  
  2780. })
  2781.  
  2782.  
  2783. let foundIframe=()=>{
  2784. FP.mtf_initalAttr_chatroom();
  2785. if(document.querySelector('ytd-watch-flexy[theater]')&&document.querySelector('ytd-live-chat-frame#chat:not([collapsed])')){
  2786. document.querySelector('ytd-live-chat-frame#chat').setAttribute('collapsed','')
  2787. }
  2788.  
  2789. setToggleBtnTxt()
  2790. }
  2791.  
  2792. globalHook('yt-page-data-updated',(evt)=>{
  2793.  
  2794. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2795.  
  2796. _console.log(evt.target.nodeName,904, evt.type);
  2797.  
  2798.  
  2799.  
  2800. mtc_nr_comments = Date.now();
  2801.  
  2802.  
  2803. FP.mtf_attrEngagementPanel();
  2804. _innerCommentsLoader();
  2805. if(!fetched){
  2806. window.dispatchEvent(new Event("scroll"));
  2807. }
  2808.  
  2809. pageCheck();
  2810. //fixChatFrameToggleButton();
  2811.  
  2812. //if(document.querySelector('ytd-live-chat-frame#chat')){
  2813.  
  2814.  
  2815. let siw=10;
  2816. let sic=0;
  2817. let sif = ()=>{
  2818.  
  2819. let iframe = document.querySelector('ytd-live-chat-frame#chat')
  2820. if(iframe){
  2821. foundIframe(iframe);
  2822. clearInterval(sic)
  2823. sic=0;
  2824. return;
  2825. }
  2826.  
  2827. if(--siw===1){
  2828. clearInterval(sic)
  2829. sic=0;
  2830. }
  2831. };
  2832. sic= setInterval(sif,46);
  2833. sif();
  2834.  
  2835.  
  2836. //}
  2837.  
  2838. (async ()=>{
  2839.  
  2840. await initializerLock;
  2841. _console.log(evt.target.nodeName,905, evt.type)
  2842.  
  2843. _console.log(2774, !!document.querySelector('ytd-live-chat-frame#chat'))
  2844.  
  2845. _console.log(2344,evt.type, document.querySelector('ytd-live-chat-frame#chat')?5:2)
  2846. _console.log(544, document.querySelectorAll('#meta-contents'))
  2847. _console.log(546, document.querySelectorAll('#meta-contents ytd-expander'))
  2848. let expander = document.querySelector('#meta-contents ytd-expander:not([tabview-info-expander])')
  2849. if(expander){
  2850.  
  2851. // once per $$native-info-description$$ {#meta-contents ytd-expander} detection
  2852. // append the detailed meta contents to the tab-info
  2853.  
  2854. let ytdFlexyElm = kRef(ytdFlexy);
  2855. if (!scriptEnable || !ytdFlexyElm) return ;
  2856.  
  2857. if (!expander) return ;
  2858. expander.setAttribute('tabview-info-expander','')
  2859. $(expander).appendTo("#tab-info").attr('data-dom-changed-by-tabview-youtube', scriptVersionForExternal)
  2860.  
  2861.  
  2862.  
  2863. }
  2864. mtf_fix_details();
  2865.  
  2866.  
  2867.  
  2868. })();
  2869.  
  2870.  
  2871.  
  2872. })
  2873.  
  2874.  
  2875. globalHook('yt-page-data-fetched',(evt)=>{
  2876.  
  2877. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2878.  
  2879. mtc_nr_comments = Date.now();
  2880.  
  2881. let nodeName = evt.target.nodeName.toUpperCase()
  2882.  
  2883. _console.log(nodeName,904, evt.type);
  2884.  
  2885. let d_page = ((evt.detail||0).pageData||0).page;
  2886.  
  2887. pageType = d_page;
  2888. if(d_page=='watch'){
  2889. document.documentElement.classList.toggle('tabview-normal-player', true)
  2890. }else{
  2891. document.documentElement.classList.toggle('tabview-normal-player', false)
  2892.  
  2893. }
  2894.  
  2895. dispatchWindowResize(); // player control positioning
  2896.  
  2897. let liveChatRenderer = null;
  2898. try {
  2899.  
  2900. liveChatRenderer = evt.detail.pageData.response.contents.twoColumnWatchNextResults.conversationBar.liveChatRenderer
  2901. } catch (e) { }
  2902.  
  2903. chatroomDetails = liveChatRenderer?extractInfoFromLiveChatRenderer(liveChatRenderer):null;
  2904.  
  2905. document.querySelector('ytd-watch-flexy').classList.toggle( 'tv-chat-toggleable', !!chatroomDetails);
  2906.  
  2907.  
  2908.  
  2909. (async ()=>{
  2910. await initializerLock;
  2911. _console.log(nodeName,905, evt.type, evt.detail, document.querySelector('ytd-live-chat-frame#chat'));
  2912.  
  2913. _console.log(2344,evt.type, document.querySelector('ytd-live-chat-frame#chat')?5:2)
  2914.  
  2915. if(nodeName==='YTD-APP'){
  2916. _console.log(2554, nodeName, evt.type, evt.detail )
  2917. newVideoPage(evt.detail);
  2918. }
  2919.  
  2920. })();
  2921. //fixChatFrameToggleButton();
  2922.  
  2923.  
  2924. })
  2925.  
  2926. globalHook('yt-watch-comments-ready',(evt)=>{
  2927. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  2928.  
  2929. mtc_nr_comments = Date.now();
  2930.  
  2931. let nodeName = evt.target.nodeName.toUpperCase()
  2932. _console.log(nodeName,904, evt.type, evt.detail, document.querySelector('ytd-live-chat-frame#chat'));
  2933.  
  2934. //fetchCommentsFinished();
  2935. window.dispatchEvent(new Event("scroll"));
  2936.  
  2937. pageCheck();
  2938.  
  2939. (async ()=>{
  2940. await initializerLock;
  2941. _console.log(nodeName,905, evt.type, evt.detail, document.querySelector('ytd-live-chat-frame#chat'));
  2942.  
  2943. if(nodeName==='YTD-WATCH-FLEXY'){
  2944. let m_comments = document.querySelector(`ytd-comments#comments:not([o3r-${sa_comments}])`);
  2945. if(m_comments) cssOnceFunc_comments(m_comments);
  2946.  
  2947.  
  2948.  
  2949. if(mtf_forceCheckLiveVideo_disable===2){
  2950.  
  2951. }else{
  2952.  
  2953.  
  2954. _console.log(3713, fetched)
  2955.  
  2956. if(document.querySelector(`ytd-comments#comments`).hasAttribute('hidden')){
  2957.  
  2958. //unavailable apart from live chat
  2959.  
  2960. _disableComments();
  2961. _console.log(3713, 3)
  2962.  
  2963.  
  2964. }else{
  2965. let innerCommentsLoaderRet = _innerCommentsLoader();
  2966. comments_ulfx(innerCommentsLoaderRet);
  2967. _console.log(3713, 5)
  2968.  
  2969.  
  2970.  
  2971. if(fetched|| pendingFetch ){
  2972. _console.log(3713, 6)
  2973. // if there is only message, no rendering event occur after yt-watch-comments-ready
  2974. timeline.setTimeout(()=>{
  2975. if(!fetched && pendingFetch){
  2976. pendingFetch.f();
  2977. fetched= true;
  2978. }
  2979. if(fetched){
  2980. fetchCommentsFinished();
  2981. fetched=false;pendingFetch=null;
  2982. }
  2983. },800)
  2984. }
  2985.  
  2986. }
  2987. _console.log(3714, fetched, !!pendingFetch)
  2988.  
  2989.  
  2990.  
  2991. }
  2992.  
  2993.  
  2994. }
  2995.  
  2996. })();
  2997.  
  2998.  
  2999. })
  3000.  
  3001. function onNavigationEnd(evt) {
  3002. console.log('yt-navigate-finish')
  3003.  
  3004.  
  3005.  
  3006. globalHook('yt-navigate',(evt)=>{
  3007.  
  3008. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3009. _console.log(evt.target.nodeName, evt.type)
  3010.  
  3011.  
  3012.  
  3013. })
  3014.  
  3015.  
  3016. globalHook('ytd-playlist-lockup-now-playing-active',(evt)=>{
  3017.  
  3018. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3019. _console.log(evt.target.nodeName, evt.type)
  3020.  
  3021.  
  3022. })
  3023.  
  3024. globalHook('yt-service-request-completed',(evt)=>{
  3025.  
  3026. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3027. _console.log(evt.target.nodeName, evt.type)
  3028.  
  3029.  
  3030. })
  3031. globalHook('yt-commerce-action-done',(evt)=>{
  3032.  
  3033. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3034. _console.log(evt.target.nodeName, evt.type)
  3035.  
  3036.  
  3037. })
  3038. globalHook('yt-navigate',(evt)=>{
  3039.  
  3040. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3041. _console.log(evt.target.nodeName, evt.type)
  3042.  
  3043.  
  3044. })
  3045.  
  3046.  
  3047. globalHook('yt-execute-service-endpoint',(evt)=>{
  3048.  
  3049. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3050. _console.log(evt.target.nodeName, evt.type)
  3051.  
  3052. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3053.  
  3054.  
  3055. })
  3056.  
  3057.  
  3058.  
  3059. globalHook('yt-request-panel-mode-change',(evt)=>{
  3060.  
  3061. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3062. _console.log(evt.target.nodeName, evt.type)
  3063.  
  3064.  
  3065. })
  3066.  
  3067. window.addEventListener("message", (evt) => {
  3068. if(evt.origin === location.origin && evt.data.tabview){
  3069.  
  3070. let data = evt.data.tabview;
  3071.  
  3072. if(data.eventType=== "yt-page-type-changed"){
  3073. let detail = data.eventDetail
  3074. let {newPageType, oldPageType} = detail;
  3075. if(newPageType && oldPageType ){
  3076.  
  3077. let bool = false;
  3078. if(newPageType == 'ytd-watch-flexy'){
  3079. bool = true;
  3080.  
  3081. pageType = 'watch';
  3082. }else if(newPageType=='ytd-browse'){
  3083. pageType = 'browse';
  3084. }
  3085.  
  3086. //if(newPageType=='ytd-browse'){
  3087.  
  3088. //}
  3089. document.documentElement.classList.toggle('tabview-normal-player', bool)
  3090. dispatchWindowResize(); // player control positioning
  3091. }
  3092. }
  3093.  
  3094. console.log(37192,evt)
  3095.  
  3096. }
  3097. }, bubblePassive);
  3098. /*
  3099. document.addEventListener('yt-page-type-changed',(evt)=>{
  3100.  
  3101. console.log(3553,evt,evt.detail,evt.target.data)
  3102. },false);
  3103. */
  3104. globalHook('yt-visibility-refresh',(evt)=>{
  3105.  
  3106. if(!evt || !evt.target /*|| evt.target.nodeType !== 1*/) return;
  3107. _console.log(evt.target.nodeName||'', evt.type)
  3108.  
  3109. _console.log(2784, evt.type, kRef(ytdFlexy).hasAttribute('hidden'),evt.detail)
  3110. _console.log(evt.detail)
  3111.  
  3112.  
  3113. })
  3114.  
  3115.  
  3116. //document.addEventListener('')
  3117.  
  3118. globalHook('yt-request-panel-mode-change',(evt)=>{
  3119.  
  3120. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3121. _console.log(evt.target.nodeName, evt.type)
  3122.  
  3123. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3124.  
  3125.  
  3126. })
  3127. globalHook('yt-navigate-finish',(evt)=>{
  3128.  
  3129. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3130. _console.log(evt.target.nodeName, evt.type)
  3131.  
  3132. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3133.  
  3134. let m_comments = document.querySelector(`ytd-comments#comments:not([o3r-${sa_comments}])`);
  3135. if(m_comments) cssOnceFunc_comments(m_comments);
  3136.  
  3137.  
  3138. })
  3139.  
  3140. globalHook('app-reset-layout',(evt)=>{
  3141.  
  3142. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3143. _console.log(evt.target.nodeName, evt.type)
  3144.  
  3145. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3146.  
  3147.  
  3148. })
  3149. globalHook('yt-guide-close',(evt)=>{
  3150.  
  3151. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3152. _console.log(evt.target.nodeName, evt.type)
  3153.  
  3154. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3155.  
  3156.  
  3157. })
  3158. globalHook('yt-page-data-will-change',(evt)=>{
  3159.  
  3160. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3161. _console.log(evt.target.nodeName, evt.type)
  3162.  
  3163.  
  3164. })
  3165.  
  3166. globalHook('yt-retrieve-location',(evt)=>{
  3167.  
  3168. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3169. _console.log(evt.target.nodeName, evt.type)
  3170.  
  3171.  
  3172. })
  3173.  
  3174.  
  3175.  
  3176. globalHook('yt-refit',(evt)=>{
  3177. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3178. _console.log(evt.target.nodeName, evt.type)
  3179. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3180. })
  3181. globalHook('addon-attached',(evt)=>{
  3182. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3183. _console.log(evt.target.nodeName, evt.type)
  3184. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3185. })
  3186. globalHook('yt-live-chat-context-menu-opened',(evt)=>{
  3187. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3188. _console.log(evt.target.nodeName, evt.type)
  3189. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3190. })
  3191.  
  3192. globalHook('yt-live-chat-context-menu-closed',(evt)=>{
  3193. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3194. _console.log(evt.target.nodeName, evt.type)
  3195. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  3196. })
  3197.  
  3198. globalHook('yt-commentbox-resize',(evt)=>{
  3199. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3200. _console.log(evt.target.nodeName, evt.type)
  3201. })
  3202.  
  3203. globalHook('yt-rich-grid-layout-refreshed',(evt)=>{
  3204. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3205. _console.log(2327, evt.target.nodeName, evt.type)
  3206. })
  3207.  
  3208.  
  3209. globalHook('data-changed',(evt)=>{
  3210.  
  3211. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3212. let nodeName = evt.target.nodeName.toUpperCase()
  3213. _console.log(nodeName, evt.type)
  3214.  
  3215. if(nodeName==='YTD-ITEM-SECTION-RENDERER' || nodeName==='YTD-COMMENTS'){
  3216.  
  3217. _console.log(344)
  3218.  
  3219.  
  3220. }
  3221. /*
  3222. if(nodeName==='YTD-TOGGLE-BUTTON-RENDERER'){
  3223.  
  3224.  
  3225. if(toggleBtnDC===1){
  3226. toggleBtnDC = evt.target;
  3227.  
  3228. fixChatFrameToggleButton();
  3229.  
  3230. requestAnimationFrame(()=>{
  3231. fixChatFrameToggleButton();
  3232. })
  3233.  
  3234. }
  3235. }*/
  3236.  
  3237.  
  3238. })
  3239.  
  3240. globalHook('animationend',(evt)=>{
  3241.  
  3242. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3243. _console.log(evt.target.nodeName, evt.type)
  3244.  
  3245. })
  3246.  
  3247. globalHook('yt-dismissible-item-dismissed',(evt)=>{
  3248.  
  3249. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3250. _console.log(evt.target.nodeName, evt.type)
  3251.  
  3252.  
  3253. })
  3254. globalHook('yt-dismissible-item-undismissed',function(evt){
  3255.  
  3256. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3257. _console.log(evt.target.nodeName, evt.type)
  3258.  
  3259.  
  3260. })
  3261.  
  3262. globalHook('yt-load-next-continuation',function(evt){
  3263.  
  3264. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3265. _console.log(evt.target.nodeName, evt.type)
  3266.  
  3267.  
  3268. })
  3269.  
  3270. globalHook('yt-load-reload-continuation',function(evt){
  3271.  
  3272. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3273. _console.log(evt.target.nodeName, evt.type)
  3274.  
  3275.  
  3276. })
  3277.  
  3278. globalHook('yt-toggle-button',function(evt){
  3279.  
  3280. if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  3281. _console.log(evt.target.nodeName, evt.type)
  3282.  
  3283.  
  3284. })
  3285.  
  3286. document.dispatchEvent(new CustomEvent('tabview-v-change')) //possible duplicated
  3287. document.documentElement.setAttribute('youtube-ready','')
  3288. script_inject_js1.inject();
  3289. document.documentElement.dispatchEvent(new CustomEvent('tabview-ce-hack'))
  3290.  
  3291. //document.dispatchEvent(new CustomEvent('tabview-youtube-comments-check'))
  3292.  
  3293. forceConfig();
  3294. /*
  3295. console.log(window.ytcfg)
  3296. try{
  3297. window.ytcfg.set({
  3298. "EXPERIMENT_FLAGS": {"kevlar_watch_metadata_refresh":false}})
  3299. }catch(e){}
  3300. // ytcfg.set({EXPERIMENT_FLAGS:{"kevlar_watch_metadata_refresh":true}})
  3301. */
  3302.  
  3303.  
  3304. let mRet =
  3305. (/^https?\:\/\/(\w+\.)*youtube\.com\/watch\?(\w+\=[^\/\?\&]+\&|\w+\=?\&)*v=[\w\-\_]+/.test(window.location.href) ? 1 : 0) +
  3306. (document.querySelector('ytd-watch-flexy[tabview-selection]') ? 2 : 0);
  3307.  
  3308. if (mRet === 1) {
  3309.  
  3310.  
  3311. document.addEventListener('load',function(evt){
  3312.  
  3313. if( checkEvtTarget(evt, ['IFRAME']) && evt.target.matches('body iframe.style-scope.ytd-live-chat-frame#chatframe')){
  3314.  
  3315. let iframe = evt.target;
  3316. new Promise(resolve=>{
  3317.  
  3318. let k = 270
  3319. let cid = setInterval(()=>{
  3320. if(k--<1) {
  3321. clearInterval(cid);
  3322. resolve(false);
  3323. }
  3324.  
  3325. let cDoc = iframe.contentDocument;
  3326. if (!cDoc) return null;
  3327. if (cDoc.readyState != 'complete') return;
  3328. if (!cDoc.querySelector('body')) {
  3329. clearInterval(cid);
  3330. resolve(false);
  3331. }
  3332.  
  3333. if(!cDoc.querySelector('yt-live-chat-app')) return;
  3334.  
  3335. clearInterval(cid);
  3336.  
  3337. if(!document.contains(iframe)) return resolve(false);
  3338.  
  3339. resolve(cDoc);
  3340.  
  3341.  
  3342. },17)
  3343.  
  3344. }).then((res)=>{
  3345.  
  3346. if(res){
  3347. callFind(res)
  3348. }
  3349.  
  3350. })
  3351.  
  3352.  
  3353.  
  3354. }
  3355. }, capturePassive)
  3356.  
  3357. pluginUnhook(); // in case not triggered by popstate - say mini playing
  3358.  
  3359. let timeout = 4; // max. 4 animation frames
  3360.  
  3361. let tf = () => {
  3362.  
  3363. if (--timeout > 0 && !document.querySelector('#player video')) return requestAnimationFrame(tf);
  3364.  
  3365. let ytdFlexyElm = document.querySelector('ytd-watch-flexy')
  3366.  
  3367. if(!ytdFlexyElm) return;
  3368.  
  3369.  
  3370. scriptEnable = true;
  3371. scriptEC++;
  3372.  
  3373.  
  3374. ytdFlexy = mWeakRef(ytdFlexyElm)
  3375.  
  3376. let timeoutR_findRelated = new Timeout();
  3377. timeoutR_findRelated.set(function() {
  3378. let ytdFlexyElm = kRef(ytdFlexy);
  3379. if(!ytdFlexyElm) return true;
  3380. let related = querySelectorFromAnchor.call(ytdFlexyElm,"#related");
  3381. if (!related) return true;
  3382. foundRelated(related);
  3383. }, 100, 10)
  3384.  
  3385. function foundRelated(related) {
  3386. let promise = Promise.resolve();
  3387. if (!document.querySelector("#right-tabs")) {
  3388. promise = promise.then(() => {
  3389. getLang();
  3390. $(getTabsHTML()).insertBefore(related).attr('data-dom-created-by-tabview-youtube', scriptVersionForExternal);
  3391. })
  3392. }
  3393. promise.then(runAfterTabAppended)
  3394. }
  3395.  
  3396. }
  3397.  
  3398. tf();
  3399.  
  3400. } else if (mRet === 3) {
  3401.  
  3402. if(!scriptEnable) return;
  3403.  
  3404. let elmComments = document.querySelector('ytd-comments#comments')
  3405.  
  3406. if (elmComments && querySelectorFromAnchor.call(elmComments,'ytd-item-section-renderer#sections.style-scope.ytd-comments')){
  3407. nativeFunc(elmComments, "loadComments")
  3408. }
  3409.  
  3410. } else if (mRet === 0) {
  3411.  
  3412. pluginUnhook(); // in case not triggered by popstate - say mini playing
  3413.  
  3414. }
  3415.  
  3416.  
  3417.  
  3418.  
  3419. if(scriptEnable) {
  3420.  
  3421.  
  3422. let m_comments = document.querySelector(`ytd-comments#comments:not([o3r-${sa_comments}])`);
  3423. if(m_comments) cssOnceFunc_comments(m_comments);
  3424.  
  3425. if(cid_nav_end>0) {
  3426. clearInterval(cid_nav_end)
  3427. cid_nav_end = 0;
  3428. }
  3429.  
  3430. rc_nav_end = 0;
  3431. cid_nav_end=setInterval(()=>{
  3432.  
  3433. if(rc_nav_end<1000) rc_nav_end++;
  3434.  
  3435. // regular check for different issues with UI display status
  3436. // until the initialization of the page finished + 4700ms
  3437.  
  3438.  
  3439. let clearTimer = false;
  3440. if(rc_nav_end<80){ // <=63s
  3441. let cTime = Date.now();
  3442.  
  3443. if(cTime - mtc_nr_comments<1270) return;
  3444. if(!mtc_nr_comments || cTime - mtc_nr_comments>2870){
  3445. clearTimer = true; // last check
  3446. }
  3447.  
  3448.  
  3449. }else{
  3450. clearTimer = true;
  3451. }
  3452. if(clearTimer && cid_nav_end>0) {
  3453. clearInterval(cid_nav_end)
  3454. cid_nav_end = 0;
  3455. }
  3456.  
  3457. },800)
  3458.  
  3459.  
  3460. initializerResolve();
  3461.  
  3462. }
  3463.  
  3464. }
  3465.  
  3466.  
  3467. function setToActiveTab(defaultTab) {
  3468. if (isTheater() && isWideScreenWithTwoColumns()) return;
  3469. const jElm = document.querySelector(`a[userscript-tab-content="${switchTabActivity_lastTab}"]:not(.tab-btn-hidden)`) ||
  3470. document.querySelector(`a[userscript-tab-content="${(defaultTab||settings.defaultTab)}"]:not(.tab-btn-hidden)`) ||
  3471. document.querySelector("a[userscript-tab-content]:not(.tab-btn-hidden)") ||
  3472. null;
  3473. switchTabActivity(jElm);
  3474. return !!jElm;
  3475. }
  3476.  
  3477. function getWrapper(wrapperId) {
  3478. let $wrapper = $(`#${wrapperId}`);
  3479. if (!$wrapper[0]) $wrapper = $(`<div id="${wrapperId}"></div>`)
  3480. return $wrapper.first();
  3481. }
  3482.  
  3483.  
  3484. function runAfterTabAppended() {
  3485.  
  3486. document.documentElement.setAttribute('plugin-tabview-youtube', '')
  3487.  
  3488. const ytdFlexyElm = kRef(ytdFlexy)
  3489. if(!ytdFlexyElm) return;
  3490. if (!ytdFlexyElm.hasAttribute('tabview-selection')) ytdFlexyElm.setAttribute('tabview-selection', '')
  3491.  
  3492. // append the next videos
  3493. // it exists as "related" is already here
  3494. fixTabs();
  3495.  
  3496. setToActiveTab(); // just switch to the default tab
  3497. prepareTabBtn();
  3498.  
  3499.  
  3500. mtoFlexyAttr.clear(true)
  3501. mtf_checkFlexy()
  3502.  
  3503.  
  3504. document.querySelector("#right-tabs .tab-content").addEventListener('scroll', windowScroll, true);
  3505.  
  3506.  
  3507. // for automcomplete plugin or other userscript plugins
  3508. // document.body for Firefox >= 60
  3509. mtoMutation_body.bindElement(document.querySelector('body'), {
  3510. childList: true,
  3511. subtree: false,
  3512. attributes: false
  3513. })
  3514.  
  3515.  
  3516.  
  3517.  
  3518.  
  3519.  
  3520. }
  3521.  
  3522.  
  3523. function fetchCommentsFinished() {
  3524. let ytdFlexyElm = kRef(ytdFlexy);
  3525. if (!scriptEnable || !ytdFlexyElm) return;
  3526. if(mtf_forceCheckLiveVideo_disable===2) return;
  3527. akAttr(ytdFlexyElm, 'tabview-youtube-comments', false, 'LS')
  3528. _console.log(2909,1)
  3529. }
  3530.  
  3531. function setCommentSection( /** @type {number} */ value) {
  3532.  
  3533. Q.comments_section_loaded = value;
  3534.  
  3535. }
  3536.  
  3537.  
  3538. function emptyCommentSection(){
  3539.  
  3540. let tab_btn = document.querySelector('.tab-btn[userscript-tab-content="#tab-comments"]')
  3541.  
  3542.  
  3543. if(tab_btn){
  3544.  
  3545. let span = querySelectorFromAnchor.call(tab_btn,'span#tab3-txt-loader');
  3546.  
  3547. tab_btn.removeAttribute('loaded-comment')
  3548. if(span){
  3549. span.textContent='';
  3550. }
  3551. }
  3552.  
  3553.  
  3554. setCommentSection(0);
  3555.  
  3556.  
  3557. }
  3558.  
  3559.  
  3560.  
  3561. function _disableComments() {
  3562.  
  3563. _console.log(2909,1)
  3564. if (!scriptEnable) return;
  3565. let cssElm = kRef(ytdFlexy);
  3566. if (!cssElm) return;
  3567.  
  3568. _console.log(2909,2)
  3569.  
  3570. let comments = document.querySelector('ytd-comments#comments')
  3571. if(mtf_forceCheckLiveVideo_disable===2){
  3572. // earlier than DOM change
  3573. }else{
  3574. if(comments && !comments.hasAttribute('hidden')) return; // visible comments content)
  3575. }
  3576. if(Q.comments_section_loaded===2) return; //already disabled
  3577.  
  3578.  
  3579. _console.log(2909,3)
  3580.  
  3581. _console.log(2909,4)
  3582. setCommentSection(2);
  3583.  
  3584. _console.log(2909,5)
  3585.  
  3586. let tabBtn = document.querySelector('.tab-btn[userscript-tab-content="#tab-comments"]');
  3587. if(tabBtn) {
  3588. let span = querySelectorFromAnchor.call(tabBtn,'span#tab3-txt-loader');
  3589. tabBtn.removeAttribute('loaded-comment')
  3590. if (!tabBtn.classList.contains('tab-btn-hidden')) {
  3591. //console.log('hide', comments, comments && comments.hasAttribute('hidden'))
  3592. hideTabBtn(tabBtn)
  3593. }
  3594. if(span){
  3595. span.textContent='';
  3596. }
  3597. }
  3598.  
  3599. akAttr(cssElm, 'tabview-youtube-comments', true, 'D');
  3600. _console.log(2909,10)
  3601.  
  3602.  
  3603. }
  3604.  
  3605.  
  3606.  
  3607.  
  3608. let layoutStatusMutex = new Mutex();
  3609.  
  3610. function forceDisplayChatReplay() {
  3611. let items = chatFrameElement('yt-live-chat-item-list-renderer #items');
  3612. if (items && items.childElementCount !== 0) return;
  3613.  
  3614. let ytd_player = document.querySelector('ytd-player#ytd-player');
  3615. if (!ytd_player) return;
  3616. let videoElm = querySelectorFromAnchor.call(ytd_player,'video');
  3617. if (!videoElm) return;
  3618.  
  3619. let video = videoElm;
  3620. if (videoElm && video.currentTime > 0 && !video.ended && video.readyState > video.HAVE_CURRENT_DATA) {
  3621. let chat = document.querySelector('ytd-live-chat-frame#chat');
  3622. if (chat) {
  3623. nativeFunc(chat, "postToContentWindow", [{ "yt-player-video-progress": videoElm.currentTime }])
  3624. }
  3625. }
  3626.  
  3627. }
  3628.  
  3629.  
  3630.  
  3631. function addIframeStyle(cDoc){
  3632.  
  3633.  
  3634. if(cDoc.querySelector('#userscript-tabview-chatroom-css')) return false;
  3635.  
  3636. addStyle(`
  3637.  
  3638.  
  3639. body #input-panel.yt-live-chat-renderer::after {
  3640. background: transparent;
  3641. }
  3642. .style-scope.yt-live-chat-item-list-renderer{
  3643. box-sizing: border-box;
  3644. }
  3645. yt-live-chat-text-message-renderer:nth-last-child(-n+30):hover #menu.yt-live-chat-text-message-renderer{
  3646. transition-delay: 87ms;
  3647. }
  3648. yt-live-chat-text-message-renderer #menu.yt-live-chat-text-message-renderer{
  3649. transition-delay: 1ms;
  3650. }
  3651. #item.style-scope.yt-live-chat-item-list-renderer, #item-scroller.style-scope.yt-live-chat-item-list-renderer{
  3652. transition-delay: 42ms;
  3653. }
  3654. yt-live-chat-item-list-renderer img[alt] {
  3655. pointer-events: auto;
  3656. }
  3657. body yt-live-chat-item-list-renderer img[alt] ~ tp-yt-paper-tooltip, body yt-live-chat-item-list-renderer #image ~ tp-yt-paper-tooltip {
  3658. --paper-tooltip-delay-in: 120ms !important;
  3659. white-space: nowrap;
  3660. }
  3661. #items.style-scope.yt-live-chat-item-list-renderer > yt-live-chat-text-message-renderer.yt-live-chat-item-list-renderer{
  3662. --tabview-chat-message-display: block;
  3663. --tabview-chat-message-mt: 2px;
  3664. --tabview-chat-message-mb: 4px;
  3665. }
  3666. #message.yt-live-chat-text-message-renderer {
  3667. display: var(--tabview-chat-message-display);
  3668. margin-top: var(--tabview-chat-message-mt);
  3669. margin-bottom: var(--tabview-chat-message-mb);
  3670. }
  3671. [collapsed] #message.yt-live-chat-text-message-renderer{
  3672. --tabview-chat-message-display: 'VOID';
  3673. --tabview-chat-message-mt: 'VOID';
  3674. --tabview-chat-message-mb: 'VOID';
  3675. }
  3676. @supports (contain: layout paint style){
  3677. body yt-live-chat-app{
  3678. contain: size layout paint style;
  3679. content-visibility: auto;
  3680. transform: translate3d(0,0,0);
  3681. overflow: hidden;
  3682. }
  3683. #items.style-scope.yt-live-chat-item-list-renderer,
  3684. #item-offset.style-scope.yt-live-chat-item-list-renderer {
  3685. contain: layout paint style;
  3686. }
  3687. #item-scroller.style-scope.yt-live-chat-item-list-renderer{
  3688. contain: size style;
  3689. }
  3690. #contents.style-scope.yt-live-chat-item-list-renderer,
  3691. #chat.style-scope.yt-live-chat-renderer,
  3692. img.style-scope.yt-img-shadow[width][height]{
  3693. contain: size layout paint style;
  3694. }
  3695. .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label],
  3696. .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label] > #container {
  3697. contain: layout paint style;
  3698. }
  3699. yt-img-shadow#author-photo.style-scope{
  3700. contain: layout paint style;
  3701. content-visibility: auto;
  3702. contain-intrinsic-size: 24px 24px;
  3703. }
  3704. yt-live-chat-text-message-renderer:not([author-is-owner]) #author-photo.style-scope.yt-live-chat-text-message-renderer, yt-live-chat-text-message-renderer:not([author-is-owner]) yt-live-chat-author-chip.style-scope.yt-live-chat-text-message-renderer{
  3705. pointer-events: none;
  3706. }
  3707. yt-live-chat-text-message-renderer:not([author-is-owner]) span#message.style-scope.yt-live-chat-text-message-renderer > img.emoji.yt-formatted-string.style-scope.yt-live-chat-text-message-renderer {
  3708. cursor: default;
  3709. }
  3710. yt-live-chat-text-message-renderer:not([author-is-owner]) span#message.style-scope.yt-live-chat-text-message-renderer,
  3711. yt-live-chat-paid-message-renderer #message.yt-live-chat-paid-message-renderer,
  3712. yt-live-chat-text-message-renderer:not([author-is-owner]) #timestamp.style-scope.yt-live-chat-text-message-renderer,
  3713. yt-live-chat-membership-item-renderer #header-content.style-scope.yt-live-chat-membership-item-renderer,
  3714. yt-live-chat-membership-item-renderer #timestamp.style-scope.yt-live-chat-membership-item-renderer,
  3715. yt-live-chat-paid-message-renderer #header-content.yt-live-chat-paid-message-renderer,
  3716. yt-live-chat-paid-message-renderer #timestamp.style-scope.yt-live-chat-paid-message-renderer,
  3717. yt-live-chat-paid-sticker-renderer #content.style-scope.yt-live-chat-paid-sticker-renderer,
  3718. yt-live-chat-paid-sticker-renderer #timestamp.style-scope.yt-live-chat-paid-sticker-renderer {
  3719. cursor: default;
  3720. pointer-events: none;
  3721. }
  3722. yt-live-chat-text-message-renderer.style-scope.yt-live-chat-item-list-renderer,
  3723. yt-live-chat-membership-item-renderer.style-scope.yt-live-chat-item-list-renderer,
  3724. yt-live-chat-paid-message-renderer.style-scope.yt-live-chat-item-list-renderer,
  3725. yt-live-chat-banner-manager.style-scope.yt-live-chat-item-list-renderer {
  3726. contain: layout style;
  3727. }
  3728. tp-yt-paper-tooltip[style*="inset"][role="tooltip"] {
  3729. contain: layout paint style;
  3730. }
  3731. }
  3732. #chat-messages tp-yt-iron-dropdown#dropdown.style-scope.tp-yt-paper-menu-button{
  3733. margin-right: var(--ytd-margin-12x);
  3734. }
  3735. `, cDoc.documentElement).id='userscript-tabview-chatroom-css'
  3736.  
  3737. return true;
  3738.  
  3739. }
  3740.  
  3741. let _last_iframe = null;
  3742.  
  3743. function callFind(cDoc){
  3744.  
  3745. _last_iframe = cDoc;
  3746. if(!cDoc)return;
  3747.  
  3748. if(addIframeStyle(cDoc)===false)return;
  3749.  
  3750. let frc= 0;
  3751. let cid = 0;
  3752. let fullReady = ()=>{
  3753. if(!cDoc.documentElement.hasAttribute('style') && ++frc<900) return;
  3754. clearInterval(cid);
  3755. if (!scriptEnable || !isChatExpand()) return;
  3756.  
  3757. let iframe = document.querySelector('ytd-live-chat-frame iframe#chatframe');
  3758.  
  3759. if(!document.contains(iframe)) return;
  3760.  
  3761. if (cDoc.querySelector('yt-live-chat-renderer #continuations')) {
  3762. $(document.querySelector('ytd-live-chat-frame#chat')).attr('yt-userscript-iframe-loaded', '')
  3763. }
  3764.  
  3765. forceDisplayChatReplay();
  3766. iframe.dispatchEvent(new CustomEvent("tabview-chatroom-ready"))
  3767.  
  3768. }
  3769. cid = setInterval(fullReady,10)
  3770. fullReady();
  3771.  
  3772.  
  3773.  
  3774. }
  3775.  
  3776. function flexyAttr_toggleFlag(mFlag, b, flag) {
  3777. return b ? (mFlag | flag) : (mFlag & ~flag) ;
  3778. }
  3779.  
  3780. function flexAttr_toLayoutStatus(nls, attributeName) {
  3781.  
  3782. let attrElm, b, v;
  3783. switch (attributeName) {
  3784. case 'theater':
  3785. b = isTheater();
  3786. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_THEATER);
  3787. break;
  3788. case 'userscript-chat-collapsed':
  3789. case 'userscript-chatblock':
  3790. attrElm = kRef(ytdFlexy);
  3791. if (hasAttribute(attrElm, 'userscript-chat-collapsed')) {
  3792. nls = flexyAttr_toggleFlag(nls, true, LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLAPSED);
  3793. } else {
  3794. nls = flexyAttr_toggleFlag(nls, hasAttribute(attrElm, 'userscript-chatblock'), LAYOUT_CHATROOM);
  3795. nls = flexyAttr_toggleFlag(nls, false, LAYOUT_CHATROOM_COLLAPSED);
  3796. }
  3797. break;
  3798. case 'is-two-columns_':
  3799. b = isWideScreenWithTwoColumns();
  3800. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_TWO_COLUMNS);
  3801. break;
  3802.  
  3803. case 'tabview-selection':
  3804. attrElm = kRef(ytdFlexy);
  3805. b = isNonEmptyString(attrElm.getAttribute('tabview-selection'));
  3806. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_TAB_EXPANDED);
  3807. break;
  3808.  
  3809. case 'fullscreen':
  3810. attrElm = kRef(ytdFlexy);
  3811. b = attrElm.hasAttribute('fullscreen');
  3812. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_FULLSCREEN);
  3813. break;
  3814.  
  3815. case 'userscript-engagement-panel':
  3816. attrElm = kRef(ytdFlexy);
  3817. v = attrElm.getAttribute('userscript-engagement-panel');
  3818. b = (+v > 0)
  3819. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_ENGAGEMENT_PANEL_EXPAND);
  3820. break;
  3821.  
  3822. }
  3823.  
  3824. return nls;
  3825.  
  3826.  
  3827. }
  3828.  
  3829. let mtf_attrFlexy = (mutations, observer) => {
  3830.  
  3831. //attr mutation checker - $$ytdFlexyElm$$ {ytd-watch-flexy} \single
  3832. //::attr
  3833. // ~ 'userscript-chat-collapsed', 'userscript-chatblock', 'theater', 'is-two-columns_',
  3834. // ~ 'tabview-selection', 'fullscreen', 'userscript-engagement-panel',
  3835. // ~ 'hidden'
  3836.  
  3837. //console.log(15330, scriptEnable, kRef(ytdFlexy), mutations)
  3838.  
  3839. if (!scriptEnable) return;
  3840.  
  3841. const cssElm = kRef(ytdFlexy)
  3842. if (!cssElm) return;
  3843.  
  3844. if (!mutations) return;
  3845.  
  3846.  
  3847.  
  3848. const old_layoutStatus = wls.layoutStatus
  3849. if (old_layoutStatus === 0) return;
  3850. let new_layoutStatus = old_layoutStatus;
  3851.  
  3852. let checkedChat = false;
  3853.  
  3854. for (const mutation of mutations) {
  3855. new_layoutStatus = flexAttr_toLayoutStatus(new_layoutStatus, mutation.attributeName);
  3856. if (mutation.attributeName == 'userscript-chat-collapsed' || mutation.attributeName == 'userscript-chatblock') {
  3857.  
  3858. if(!checkedChat){
  3859. checkedChat = true; // avoid double call
  3860.  
  3861. if (cssElm.getAttribute('userscript-chatblock') === 'chat-live') {
  3862. // assigned new attribute - "chat-live" => disable comments section
  3863. _disableComments();
  3864. }
  3865.  
  3866. if (!cssElm.hasAttribute('userscript-chatblock')) {
  3867. // might or might not collapsed before
  3868. timeline.setTimeout(() => {
  3869. if (!scriptEnable) return;
  3870. //delayed call => check with the "no active focus" condition with chatroom status
  3871. if (!isAnyActiveTab() && !isChatExpand() && !isTheater() && isWideScreenWithTwoColumns() && !isFullScreen()) {
  3872. setToActiveTab();
  3873. }
  3874. }, 240);
  3875. }
  3876. }
  3877.  
  3878. }else if(mutation.attributeName == 'userscript-engagement-panel'){
  3879. // assume any other active component such as tab content and chatroom
  3880. if (+(cssElm.getAttribute('userscript-engagement-panel')||0)===0 && +mutation.oldValue>0) {
  3881. timeline.setTimeout(() => {
  3882. if (!scriptEnable) return;
  3883. //delayed call => check with the "no active focus" condition with engagement panel status
  3884. if (!isAnyActiveTab() && !isEngagementPanelExpanded() && !isTheater() && isWideScreenWithTwoColumns() && !isFullScreen() && !isChatExpand()) {
  3885. setToActiveTab();
  3886. }
  3887. }, 240);
  3888. }
  3889. }
  3890. }
  3891.  
  3892. new_layoutStatus = fixLayoutStatus(new_layoutStatus);
  3893.  
  3894.  
  3895. if (new_layoutStatus !== old_layoutStatus) {
  3896. wls.layoutStatus = new_layoutStatus
  3897.  
  3898. // resize => is-two-columns_
  3899. if (((new_layoutStatus ^ old_layoutStatus) & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) {
  3900. requestAnimationFrame(() => {
  3901. pageCheck();
  3902. singleColumnScrolling(true); //initalize sticky
  3903. })
  3904. }
  3905.  
  3906. }
  3907.  
  3908. }
  3909.  
  3910. const mtf_checkFlexy = () => {
  3911. // once per $$native-ytd-watch-flexy$$ {ytd-watch-flexy} detection
  3912.  
  3913. let ytdFlexyElm = kRef(ytdFlexy);
  3914. if (!scriptEnable || !ytdFlexyElm) return true;
  3915.  
  3916.  
  3917. wls.layoutStatus = 0;
  3918.  
  3919. let isFlexyHidden = (ytdFlexyElm.hasAttribute('hidden'));
  3920.  
  3921. if (!isFlexyHidden) {
  3922. let rChatExist = base_ChatExist();
  3923. if (rChatExist) {
  3924. let { attr_chatblock, attr_chatcollapsed } = rChatExist;
  3925. if (attr_chatblock === null) {
  3926. //remove attribute if it is unknown
  3927. attr_chatblock = false;
  3928. attr_chatcollapsed = false;
  3929. }
  3930. wAttr(ytdFlexyElm, 'userscript-chatblock', attr_chatblock)
  3931. _console.log(322,ytdFlexyElm.hasAttribute('userscript-chatblock'))
  3932. wAttr(ytdFlexyElm, 'userscript-chat-collapsed', attr_chatcollapsed)
  3933. }
  3934. }
  3935.  
  3936. let rTabSelection = [...querySelectorAllFromAnchor.call(ytdFlexyElm,'.tab-btn[userscript-tab-content]')]
  3937. .map(elm => ({ elm, hidden: elm.classList.contains('tab-btn-hidden') }));
  3938.  
  3939. if(rTabSelection.length === 0){
  3940. wAttr(ytdFlexyElm, 'tabview-selection', false);
  3941. }else{
  3942. rTabSelection=rTabSelection.filter(entry => entry.hidden !== true); // all available tabs
  3943. if (rTabSelection.length === 0) wAttr(ytdFlexyElm, 'tabview-selection', '');
  3944. }
  3945. rTabSelection = null;
  3946. let rEP = engagement_panels_();
  3947. if (rEP && rEP.count > 0) wAttr(ytdFlexyElm, 'userscript-engagement-panel', false);
  3948. //else wAttr(ytdFlexyElm, 'userscript-engagement-panel', rEP.value + ""); // can be 0
  3949. else if(rEP.value>0) wAttr(ytdFlexyElm, 'userscript-engagement-panel', rEP.value + ""); // can be 0
  3950.  
  3951. let ls = LAYOUT_VAILD;
  3952. ls = flexAttr_toLayoutStatus(ls, 'theater')
  3953. ls = flexAttr_toLayoutStatus(ls, 'userscript-chat-collapsed')
  3954. ls = flexAttr_toLayoutStatus(ls, 'userscript-chatblock')
  3955. ls = flexAttr_toLayoutStatus(ls, 'is-two-columns_')
  3956. ls = flexAttr_toLayoutStatus(ls, 'tabview-selection')
  3957. ls = flexAttr_toLayoutStatus(ls, 'fullscreen')
  3958. ls = flexAttr_toLayoutStatus(ls, 'userscript-engagement-panel')
  3959.  
  3960. fixLayoutStatus(ls)
  3961.  
  3962.  
  3963. wls.layoutStatus = ls
  3964.  
  3965. mtoFlexyAttr.bindElement(ytdFlexyElm,{
  3966. attributes: true,
  3967. attributeFilter: ['userscript-chat-collapsed', 'userscript-chatblock', 'theater', 'is-two-columns_', 'tabview-selection', 'fullscreen', 'userscript-engagement-panel', 'hidden'],
  3968. attributeOldValue: true
  3969. })
  3970.  
  3971.  
  3972.  
  3973. let columns = document.querySelector('ytd-page-manager#page-manager #columns')
  3974. if (columns) {
  3975. wAttr(columns, 'userscript-scrollbar-render', true);
  3976. }
  3977.  
  3978. return false;
  3979. }
  3980.  
  3981. function checkVisibleEngagementPanel(){
  3982. if(storeLastPanel){
  3983.  
  3984. let elm_storeLastPanel = kRef(storeLastPanel);
  3985.  
  3986. if(elm_storeLastPanel && !isDOMVisible(elm_storeLastPanel) ){
  3987. storeLastPanel=null;
  3988. ytBtnCloseEngagementPanels();
  3989. }
  3990.  
  3991. }
  3992.  
  3993. }
  3994.  
  3995.  
  3996.  
  3997.  
  3998. let switchTabActivity_lastTab = null
  3999.  
  4000. function setDisplayedPlaylist() {
  4001. //override the default youtube coding event prevention
  4002. let cssElm = kRef(ytdFlexy);
  4003. if (!scriptEnable || !cssElm) return;
  4004. displayedPlaylist = mWeakRef(document.querySelector('ytd-watch-flexy #tab-list:not(.tab-content-hidden) ytd-playlist-panel-renderer[tabview-true-playlist]') || null);
  4005. }
  4006.  
  4007.  
  4008.  
  4009. function switchTabActivity(activeLink) {
  4010. if (!scriptEnable) return;
  4011.  
  4012. const ytdFlexyElm = kRef(ytdFlexy);
  4013.  
  4014. if (!ytdFlexyElm) return;
  4015.  
  4016. if (activeLink && activeLink.classList.contains('tab-btn-hidden')) return; // not allow to switch to hide tab
  4017.  
  4018. //if (isTheater() && isWideScreenWithTwoColumns()) activeLink = null;
  4019.  
  4020.  
  4021. function runAtEnd() {
  4022.  
  4023. //console.log(12312)
  4024.  
  4025. if (activeLink) {
  4026. lstTab.lastTab = activeLink.getAttribute('userscript-tab-content')
  4027. lstTab.lastPanel = null;
  4028. }
  4029.  
  4030. displayedPlaylist = null;
  4031. scrollingVideosList = null;
  4032.  
  4033. if (activeLink && lstTab.lastTab == '#tab-list') {
  4034. setDisplayedPlaylist();
  4035. } else if (activeLink && lstTab.lastTab == '#tab-videos') {
  4036. scrollingVideosList = mWeakRef(document.querySelector('ytd-watch-flexy #tab-videos:not(.tab-content-hidden) [placeholder-videos]'));
  4037. }
  4038.  
  4039.  
  4040. ytdFlexyElm.setAttribute('tabview-selection', activeLink ? lstTab.lastTab : '')
  4041.  
  4042. if (activeLink && lstTab.lastTab == '#tab-comments' && (ytdFlexyElm.getAttribute('tabview-youtube-comments') || '').lastIndexOf('S') >= 0) {
  4043.  
  4044. if(mtf_forceCheckLiveVideo_disable===2) {}
  4045. else{
  4046. akAttr(ytdFlexyElm, 'tabview-youtube-comments', false, 'L');
  4047. _console.log(2909,3)
  4048.  
  4049. }
  4050.  
  4051. }
  4052.  
  4053.  
  4054.  
  4055. }
  4056.  
  4057. const links = document.querySelectorAll('#material-tabs a[userscript-tab-content]');
  4058.  
  4059. //console.log(701, activeLink)
  4060.  
  4061. for (const link of links) {
  4062. let content = document.querySelector(link.getAttribute('userscript-tab-content'));
  4063. if (link && content) {
  4064. if (link !== activeLink) {
  4065. link.classList.remove("active");
  4066. content.classList.add("tab-content-hidden");
  4067. } else {
  4068. //console.log(3343)
  4069. link.classList.add("active");
  4070. content.classList.remove("tab-content-hidden");
  4071. //timeline.setTimeout(()=>content.focus(),400);
  4072.  
  4073. }
  4074. }
  4075. }
  4076.  
  4077. runAtEnd();
  4078.  
  4079.  
  4080. }
  4081.  
  4082.  
  4083. const STORE_VERSION = 1;
  4084. const STORE_key = 'userscript-tabview-settings';
  4085. function getStore(){
  4086. let s = localStorage[STORE_key];
  4087. function resetStore(){
  4088. let ret = {
  4089. version: 1,
  4090. };
  4091. localStorage[STORE_key]=JSON.stringify(ret);
  4092. return ret;
  4093. }
  4094. if(!s) return resetStore();
  4095. let obj = null;
  4096. try{
  4097. obj = JSON.parse(s);
  4098. }catch(e){}
  4099. return obj && obj.version === STORE_VERSION ? obj : resetStore();
  4100. }
  4101.  
  4102. function setStore(obj){
  4103. if(!obj || typeof obj !=='object') return false;
  4104. if(obj.version !== STORE_VERSION) return false;
  4105. localStorage[STORE_key]=JSON.stringify(obj);
  4106. return true;
  4107. }
  4108.  
  4109. let tabsUiScript_setclick = false;
  4110.  
  4111. function prepareTabBtn() {
  4112.  
  4113. const materialTab = document.querySelector("#material-tabs")
  4114. if (!materialTab) return;
  4115.  
  4116. let noActiveTab = !!document.querySelector('ytd-watch-flexy[userscript-chatblock]:not([userscript-chat-collapsed])')
  4117.  
  4118. const activeLink = querySelectorFromAnchor.call(materialTab,'a[userscript-tab-content].active:not(.tab-btn-hidden)')
  4119. if (activeLink) switchTabActivity(noActiveTab ? null : activeLink)
  4120.  
  4121. if (!tabsUiScript_setclick) {
  4122. tabsUiScript_setclick = true;
  4123. $(materialTab).on("click", "a", function(evt) {
  4124.  
  4125. //console.log(8510)
  4126. let ytdFlexyElm = kRef(ytdFlexy);
  4127. if (!scriptEnable || !ytdFlexyElm) return null;
  4128.  
  4129. if (!this.hasAttribute('userscript-tab-content')) return;
  4130.  
  4131. if (evt.target.matches('.font-size-btn')) return;
  4132.  
  4133.  
  4134. evt.preventDefault();
  4135.  
  4136. //console.log(8511)
  4137.  
  4138. //console.log(8513)
  4139. new Promise(requestAnimationFrame).then(() => {
  4140.  
  4141.  
  4142. //console.log(8514)
  4143. layoutStatusMutex.lockWith(unlock => {
  4144.  
  4145. //console.log(8515)
  4146. switchTabActivity_lastTab = this.getAttribute('userscript-tab-content');
  4147.  
  4148. let isActiveAndVisible = this.classList.contains('tab-btn') && this.classList.contains('active') && !this.classList.contains('tab-btn-hidden')
  4149.  
  4150. if( isFullScreen() ){
  4151.  
  4152. console.log(isActiveAndVisible, this)
  4153. if(isActiveAndVisible){
  4154. timeline.setTimeout(unlock, 80);
  4155. switchTabActivity(null);
  4156. }else{
  4157.  
  4158. if (isChatExpand() && isWideScreenWithTwoColumns()) {
  4159. ytBtnCollapseChat();
  4160. } else if (isEngagementPanelExpanded() && isWideScreenWithTwoColumns()) {
  4161. ytBtnCloseEngagementPanels();
  4162. }
  4163.  
  4164.  
  4165. timeline.setTimeout(() => {
  4166. let scrollElement = document.querySelector('ytd-app[scrolling]')
  4167. if(!scrollElement) return;
  4168. // single column view; click button; scroll to tab content area 100%
  4169. let rightTabs = document.querySelector('#right-tabs');
  4170. let pTop = rightTabs.getBoundingClientRect().top - scrollElement.getBoundingClientRect().top
  4171. if (rightTabs && pTop > 0 && this.classList.contains('active')) {
  4172. rightTabs.scrollIntoView(true);
  4173. }
  4174. }, 60)
  4175.  
  4176. timeline.setTimeout(unlock, 80);
  4177. switchTabActivity(this)
  4178. }
  4179.  
  4180. } else if (isWideScreenWithTwoColumns() && !isTheater() && isActiveAndVisible) {
  4181. //optional
  4182. timeline.setTimeout(unlock, 80);
  4183. switchTabActivity(null);
  4184. ytBtnSetTheater();
  4185. } else if (isActiveAndVisible) {
  4186. timeline.setTimeout(unlock, 80);
  4187. switchTabActivity(null);
  4188. } else {
  4189.  
  4190. if (isChatExpand() && isWideScreenWithTwoColumns()) {
  4191. ytBtnCollapseChat();
  4192. } else if (isEngagementPanelExpanded() && isWideScreenWithTwoColumns()) {
  4193. ytBtnCloseEngagementPanels();
  4194. } else if (isWideScreenWithTwoColumns() && isTheater() && !isFullScreen()) {
  4195. ytBtnCancelTheater();
  4196. }
  4197.  
  4198. timeline.setTimeout(() => {
  4199. // single column view; click button; scroll to tab content area 100%
  4200. let rightTabs = document.querySelector('#right-tabs');
  4201. if (!isWideScreenWithTwoColumns() && rightTabs && rightTabs.offsetTop > 0 && this.classList.contains('active')) {
  4202. let tabButtonBar = document.querySelector('#material-tabs');
  4203. let tabButtonBarHeight = tabButtonBar ? tabButtonBar.offsetHeight : 0;
  4204. window.scrollTo(0, rightTabs.offsetTop - tabButtonBarHeight);
  4205. }
  4206. }, 60)
  4207. // _console.log(8519)
  4208.  
  4209. timeline.setTimeout(unlock, 80)
  4210. switchTabActivity(this)
  4211.  
  4212.  
  4213. }
  4214.  
  4215.  
  4216. })
  4217.  
  4218. })
  4219.  
  4220.  
  4221.  
  4222.  
  4223. });
  4224.  
  4225. function updateCSS_fontsize(){
  4226.  
  4227. let store = getStore();
  4228. let ytdFlexyElm = kRef(ytdFlexy);
  4229. if(ytdFlexyElm){
  4230. if(store['font-size-#tab-info']) ytdFlexyElm.style.setProperty('--ut2257-info', store['font-size-#tab-info'])
  4231. if(store['font-size-#tab-comments']) ytdFlexyElm.style.setProperty('--ut2257-comments', store['font-size-#tab-comments'])
  4232. if(store['font-size-#tab-videos']) ytdFlexyElm.style.setProperty('--ut2257-videos', store['font-size-#tab-videos'])
  4233. if(store['font-size-#tab-list']) ytdFlexyElm.style.setProperty('--ut2257-list', store['font-size-#tab-list'])
  4234. }
  4235.  
  4236. }
  4237.  
  4238. $(materialTab).on("click", ".font-size-btn", function(evt){
  4239.  
  4240. evt.preventDefault();
  4241. evt.stopPropagation();
  4242. evt.stopImmediatePropagation();
  4243.  
  4244. let value = evt.target.matches('.font-size-plus')?1: evt.target.matches('.font-size-minus')?-1 :0;
  4245.  
  4246. let active_tab_content = closestDOM.call(evt.target,'[userscript-tab-content]').getAttribute('userscript-tab-content');
  4247.  
  4248. let store = getStore();
  4249. let settingKey = `font-size-${active_tab_content}`
  4250. if(!store[settingKey]) store[settingKey] = 1.0;
  4251. if(value<0) store[settingKey] -= 0.05;
  4252. else if(value>0) store[settingKey] += 0.05;
  4253. if(store[settingKey]<0.1) store[settingKey] = 0.1;
  4254. else if(store[settingKey]>10) store[settingKey] = 10.0;
  4255. setStore(store);
  4256.  
  4257.  
  4258. updateCSS_fontsize();
  4259.  
  4260.  
  4261. //console.log(this.textContent)
  4262.  
  4263.  
  4264. });
  4265.  
  4266. updateCSS_fontsize();
  4267.  
  4268.  
  4269.  
  4270. }
  4271.  
  4272. }
  4273.  
  4274.  
  4275. // ---------------------------------------------------------------------------------------------
  4276. document.addEventListener("yt-navigate-finish", onNavigationEnd, bubblePassive)
  4277. //yt-navigate-redirect
  4278. //"yt-page-data-fetched"
  4279. //yt-navigate-error
  4280. //yt-navigate-start
  4281. //yt-page-manager-navigate-start
  4282. //"yt-navigate"
  4283. //"yt-navigate-cache
  4284.  
  4285. document.addEventListener("yt-navigate-cache",()=>{
  4286. console.log('yt-navigate-cache')
  4287. },bubblePassive)
  4288. document.addEventListener("yt-navigate-redirect",()=>{
  4289. console.log('yt-navigate-redirect')
  4290.  
  4291.  
  4292.  
  4293. script_inject_js1.inject();
  4294.  
  4295. },bubblePassive)
  4296.  
  4297. function onReady(){
  4298. //might be earlier than yt-navigation-finish
  4299. console.log('html-onReady')
  4300. if(location.pathname=='/watch') script_inject_js1.inject();
  4301. }
  4302.  
  4303. if(document.readyState!=='loading'){
  4304. onReady();
  4305. }else{
  4306. document.addEventListener('DOMContentLoaded', onReady, bubblePassive)
  4307. }
  4308.  
  4309. function forceConfig(){
  4310. }
  4311.  
  4312. document.addEventListener("yt-navigate-start",()=>{
  4313. console.log('yt-navigate-start') // not always trigger before navigate-end
  4314.  
  4315. script_inject_js1.inject();
  4316. forceConfig();
  4317. },bubblePassive)
  4318.  
  4319. document.addEventListener("yt-page-manager-navigate-start",()=>{
  4320. console.log('yt-page-manager-navigate-start')
  4321. forceConfig();
  4322. },bubblePassive)
  4323.  
  4324.  
  4325.  
  4326. // ---------------------------------------------------------------------------------------------
  4327.  
  4328. let scrolling_lastD = 0;
  4329.  
  4330. const singleColumnScrolling = function(/** @type {boolean} */ scrolling_lastF) {
  4331. if (!scriptEnable || pageType!=='watch') return;
  4332.  
  4333. let pageY = 0;
  4334.  
  4335. if(scrolling_lastD === 0){
  4336. if( (wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS){
  4337. return;
  4338. }
  4339. pageY = scrollY;
  4340. if (pageY < 10 && !scrolling_lastF) return;
  4341. }else{
  4342. pageY = scrollY;
  4343. }
  4344.  
  4345. let targetElm, header, navElm;
  4346.  
  4347. _console.log(7891,'scrolling')
  4348.  
  4349. Promise.resolve().then(() => {
  4350.  
  4351. targetElm = document.querySelector("#right-tabs");
  4352. if (!targetElm) return;
  4353. header = querySelectorFromAnchor.call(targetElm,"header");
  4354. if (!header) return;
  4355. navElm = document.querySelector('#masthead-container, #masthead')
  4356. if (!navElm) return;
  4357. let navHeight = navElm ? navElm.offsetHeight : 0
  4358.  
  4359. let elmY = targetElm.offsetTop
  4360.  
  4361. let xyz = [elmY + navHeight, pageY, elmY - navHeight]
  4362.  
  4363. let xyStatus = 0
  4364. if (xyz[1] < xyz[2] && xyz[2] < xyz[0]) {
  4365. // 1
  4366. xyStatus = 1
  4367. }
  4368.  
  4369. if (xyz[0] > xyz[1] && xyz[1] > xyz[2]) {
  4370.  
  4371. //2
  4372. xyStatus = 2
  4373.  
  4374. }
  4375.  
  4376. if (xyz[2] < xyz[0] && xyz[0] < xyz[1]) {
  4377. // 3
  4378.  
  4379. xyStatus = 3
  4380.  
  4381.  
  4382. }
  4383.  
  4384. return xyStatus;
  4385.  
  4386. }).then((xyStatus) => {
  4387.  
  4388. if ((xyStatus == 2 || xyStatus == 3) && (scrolling_lastD === 0 || scrolling_lastF)) {
  4389. scrolling_lastD = 1;
  4390. let {
  4391. offsetHeight
  4392. } = header
  4393. let {
  4394. offsetWidth
  4395. } = targetElm
  4396.  
  4397. targetElm.style.setProperty("--userscript-sticky-width", offsetWidth + 'px')
  4398. targetElm.style.setProperty("--userscript-sticky", offsetHeight + 'px')
  4399.  
  4400. wAttr(targetElm, 'userscript-sticky', true);
  4401.  
  4402. } else if ((xyStatus == 1) && (scrolling_lastD === 1 || scrolling_lastF)) {
  4403. scrolling_lastD = 0;
  4404.  
  4405. wAttr(targetElm, 'userscript-sticky', false);
  4406. }
  4407.  
  4408.  
  4409. targetElm = null;
  4410. header = null;
  4411. navElm = null;
  4412.  
  4413. });
  4414.  
  4415. };
  4416.  
  4417. window.addEventListener("scroll", function() {
  4418. singleColumnScrolling(false)
  4419. }, bubblePassive)
  4420.  
  4421. //let lastResizeAt = 0;
  4422. window.addEventListener('resize', function() {
  4423.  
  4424. if (!scriptEnable) return;
  4425. if (pageType!=='watch') return;
  4426. //lastResizeAt = Date.now();
  4427.  
  4428. if((wls.layoutStatus & LAYOUT_TWO_COLUMNS) !== LAYOUT_TWO_COLUMNS){
  4429.  
  4430. requestAnimationFrame(() => {
  4431. singleColumnScrolling(true)
  4432. })
  4433.  
  4434. }
  4435.  
  4436.  
  4437. }, bubblePassive)
  4438.  
  4439.  
  4440.  
  4441. function resetBuggyLayoutForNewVideoPage() {
  4442.  
  4443. let ytdFlexyElm = kRef(ytdFlexy);
  4444. if (!ytdFlexyElm) return;
  4445.  
  4446. //(flexy is visible and watch video page)
  4447.  
  4448. scriptEnable = true;
  4449.  
  4450. //mtf_forceCheckLiveVideo_disable = 0;
  4451. _console.log(27056)
  4452.  
  4453.  
  4454. let new_layoutStatus = wls.layoutStatus
  4455.  
  4456. new_layoutStatus & (LAYOUT_CHATROOM_COLLAPSED | LAYOUT_CHATROOM)
  4457.  
  4458. const new_isExpandedChat = !(new_layoutStatus & LAYOUT_CHATROOM_COLLAPSED) && !!(new_layoutStatus & LAYOUT_CHATROOM)
  4459. const new_isCollapsedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_COLLAPSED) && !!(new_layoutStatus & LAYOUT_CHATROOM)
  4460.  
  4461. const new_isTwoColumns = new_layoutStatus & LAYOUT_TWO_COLUMNS;
  4462. const new_isTheater = new_layoutStatus & LAYOUT_THEATER;
  4463. const new_isTabExpanded = new_layoutStatus & LAYOUT_TAB_EXPANDED;
  4464. const new_isFullScreen = new_layoutStatus & LAYOUT_FULLSCREEN;
  4465. const new_isExpandEPanel = new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPAND;
  4466.  
  4467. if (ytdFlexyElm.getAttribute('tabview-selection') === '' && new_isTwoColumns && !new_isTheater && !new_isTabExpanded && !new_isFullScreen && !new_isExpandEPanel && !new_isExpandedChat) {
  4468. // e.g. engage panel removed after miniview and change video
  4469. setToActiveTab();
  4470. } else if (new_isExpandEPanel && querySelectorAllFromAnchor.call(ytdFlexyElm, 'ytd-engagement-panel-section-list-renderer[visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]').length === 0) {
  4471. wls.layoutStatus = new_layoutStatus & (~LAYOUT_ENGAGEMENT_PANEL_EXPAND)
  4472. }
  4473.  
  4474.  
  4475.  
  4476.  
  4477. }
  4478.  
  4479. function extractInfoFromLiveChatRenderer(liveChatRenderer){
  4480.  
  4481. let lcr = liveChatRenderer
  4482.  
  4483. let data_shb = ((lcr||0).showHideButton||0).toggleButtonRenderer
  4484.  
  4485. let default_display_state=null, txt_collapse=null, txt_expand=null;
  4486.  
  4487. if(data_shb && data_shb.defaultText&&data_shb.toggledText&&data_shb.defaultText.runs&&data_shb.toggledText.runs){
  4488.  
  4489. if(data_shb.defaultText.runs.length===1&&data_shb.toggledText.runs.length===1){
  4490.  
  4491. if(lcr.initialDisplayState== "LIVE_CHAT_DISPLAY_STATE_EXPANDED"){
  4492.  
  4493. default_display_state = lcr.initialDisplayState
  4494. txt_collapse=(data_shb.defaultText.runs[0]||0).text // COLLAPSE the area
  4495.  
  4496. txt_expand=(data_shb.toggledText.runs[0]||0).text // expand the area
  4497.  
  4498. }else if(lcr.initialDisplayState =="LIVE_CHAT_DISPLAY_STATE_COLLAPSED"){
  4499. default_display_state = lcr.initialDisplayState
  4500.  
  4501. txt_expand=(data_shb.defaultText.runs[0]||0).text // expand the area
  4502.  
  4503. txt_collapse=(data_shb.toggledText.runs[0]||0).text // COLLAPSE the area
  4504. }
  4505.  
  4506.  
  4507.  
  4508. if(typeof txt_expand=='string' && typeof txt_collapse=='string' && txt_expand.length>0 && txt_collapse.length>0){
  4509.  
  4510. }else{
  4511. txt_expand=null;
  4512. txt_collapse=null;
  4513. }
  4514. }
  4515.  
  4516. }
  4517.  
  4518. return {default_display_state, txt_collapse, txt_expand}
  4519.  
  4520. }
  4521.  
  4522. // function fixChatFrameToggleButton(){
  4523. /*
  4524. return;
  4525. if(toggleBtnDC>0) return;
  4526. if(!toggleBtnDC) return;
  4527. if(!chatroomDetails) return;
  4528. let {
  4529. txt_collapse,
  4530. txt_expand
  4531. } = chatroomDetails;
  4532.  
  4533. _console.log(933,1)
  4534. */
  4535. /*
  4536.  
  4537. _console.log(933,1 );
  4538. // fix default display state
  4539. if(initialDisplayState==='LIVE_CHAT_DISPLAY_STATE_EXPANDED'){
  4540. let p = document.querySelector('ytd-live-chat-frame#chat[collapsed]');
  4541. if(p) p.removeAttribute('collapsed')
  4542.  
  4543. }else if(initialDisplayState==='LIVE_CHAT_DISPLAY_STATE_COLLAPSED'){
  4544. let p = document.querySelector('ytd-live-chat-frame#chat:not([collapsed])');
  4545. if(p) p.setAttribute('collapsed','')
  4546. }
  4547.  
  4548. _console.log(933,2);
  4549.  
  4550. let chatroomBtn = document.querySelector('ytd-live-chat-frame#chat #show-hide-button.ytd-live-chat-frame')
  4551.  
  4552. if(chatroomBtn){
  4553.  
  4554. _console.log(933,3 );
  4555. let textElm = querySelectorFromAnchor.call(chatroomBtn, 'ytd-toggle-button-renderer span.yt-core-attributed-string[role="text"]')
  4556.  
  4557. if(textElm){
  4558.  
  4559.  
  4560. _console.log(933,4, txt_expand, txt_collapse );
  4561. // fix default display state
  4562. if(initialDisplayState==='LIVE_CHAT_DISPLAY_STATE_EXPANDED'){
  4563. textElm.textContent = txt_collapse
  4564.  
  4565. }else if(initialDisplayState==='LIVE_CHAT_DISPLAY_STATE_COLLAPSED'){
  4566. textElm.textContent = txt_expand
  4567. }
  4568.  
  4569. }
  4570.  
  4571. }
  4572. */
  4573.  
  4574. /*
  4575.  
  4576. let chatroomBtn = document.querySelector('ytd-live-chat-frame#chat #show-hide-button.ytd-live-chat-frame')
  4577.  
  4578. if(chatroomBtn){
  4579. _console.log(933,2)
  4580. let textElm = querySelectorFromAnchor.call(chatroomBtn, 'ytd-toggle-button-renderer span.yt-core-attributed-string[role="text"]')
  4581.  
  4582. if(textElm){
  4583.  
  4584. _console.log(933,3)
  4585.  
  4586. //_console.log(933,4, textElm.textContent, txt_expand, txt_collapse, document.querySelector('ytd-live-chat-frame#chat').hasAttribute('collapsed') );
  4587. // fix default display state
  4588. let p = document.querySelector('ytd-live-chat-frame#chat')
  4589. if(p){
  4590.  
  4591. let iscollapsed = p.hasAttribute('collapsed')
  4592. if(!iscollapsed && textElm.textContent !== txt_collapse){
  4593. textElm.textContent = txt_collapse
  4594. toggleBtnDC.dispatchEvent(new CustomEvent("tabview-button-toggle"))
  4595. toggleBtnDC= 2;
  4596. }else if(iscollapsed && textElm.textContent !== txt_expand){
  4597. textElm.textContent = txt_expand
  4598. toggleBtnDC.dispatchEvent(new CustomEvent("tabview-button-toggle"))
  4599. toggleBtnDC=2;
  4600. }
  4601.  
  4602. }
  4603.  
  4604. }
  4605.  
  4606. }*/
  4607.  
  4608. // }
  4609.  
  4610. function newVideoPage(evt_detail) {
  4611.  
  4612. //toggleBtnDC = 1;
  4613.  
  4614. console.log('newVideoPage')
  4615. pendingFetch = null
  4616. fetched = false;
  4617.  
  4618. document.dispatchEvent(new CustomEvent('tabview-v-change')) //possible duplicated
  4619.  
  4620.  
  4621.  
  4622. //console.log('newVideoPage-', 150, location.href)
  4623.  
  4624. let ytdFlexyElm = kRef(ytdFlexy);
  4625. if (!ytdFlexyElm) return;
  4626.  
  4627.  
  4628. timeline.reset();
  4629. layoutStatusMutex = new Mutex();
  4630.  
  4631. //console.log('newVideoPage-', 350, location.href)
  4632.  
  4633.  
  4634. if (pageType === 'watch') {
  4635. resetBuggyLayoutForNewVideoPage();
  4636.  
  4637. }
  4638.  
  4639.  
  4640.  
  4641.  
  4642.  
  4643.  
  4644. pendingFetch = null;
  4645. _innerCommentsLoader();
  4646. if (!fetched) {
  4647. window.dispatchEvent(new Event("scroll"));
  4648. }
  4649.  
  4650. let liveChatRenderer = null;
  4651. let isReplay = null;
  4652. try {
  4653.  
  4654. liveChatRenderer = evt_detail.pageData.response.contents.twoColumnWatchNextResults.conversationBar.liveChatRenderer
  4655. } catch (e) { }
  4656. if (liveChatRenderer) {
  4657.  
  4658. if (liveChatRenderer.isReplay === true) isReplay = true;
  4659. }
  4660.  
  4661. const chatBlockR = liveChatRenderer ? (isReplay ? 3 : 1) : 0
  4662. const initialDisplayState = liveChatRenderer ? liveChatRenderer.initialDisplayState : null;
  4663.  
  4664.  
  4665.  
  4666.  
  4667.  
  4668.  
  4669. let f = () => {
  4670.  
  4671. _console.log(932, 1, 1)
  4672. let ytdFlexyElm = kRef(ytdFlexy);
  4673. if (!scriptEnable || !ytdFlexyElm) return;
  4674.  
  4675. _console.log(932, 1, 2)
  4676. if (pageType !== 'watch') return;
  4677.  
  4678. _console.log(932, 1, 3)
  4679.  
  4680.  
  4681.  
  4682.  
  4683. let attr_chatblock = chatBlockR === 1 ? 'chat-live' : chatBlockR === 3 ? 'chat-playback' : false;
  4684. let attr_chatcollapsed = false;
  4685.  
  4686. if(attr_chatblock){
  4687. let p = document.querySelector('ytd-live-chat-frame#chat')
  4688. if(p){
  4689. attr_chatcollapsed = p.hasAttribute('collapsed');
  4690. if(!attr_chatcollapsed){
  4691.  
  4692. //nativeFunc(p,'setupPlayerProgressRelay')
  4693. //if(!p.isFrameReady)
  4694. //nativeFunc(p, "urlChanged")
  4695. //console.log(12399,1)
  4696. p.dispatchEvent(new CustomEvent("tabview-chatroom-newpage")); //possible empty iframe is shown
  4697.  
  4698. }
  4699. }else{
  4700. attr_chatcollapsed = initialDisplayState === 'LIVE_CHAT_DISPLAY_STATE_COLLAPSED' ? true : false;
  4701. }
  4702. }
  4703.  
  4704.  
  4705.  
  4706. let chatTypeChanged = Q.mtf_chatBlockQ !== chatBlockR
  4707.  
  4708. if (chatTypeChanged) {
  4709. Q.mtf_chatBlockQ = chatBlockR
  4710.  
  4711.  
  4712.  
  4713. _console.log(932, 2, attr_chatblock, attr_chatcollapsed)
  4714.  
  4715. //LIVE_CHAT_DISPLAY_STATE_COLLAPSED
  4716. //LIVE_CHAT_DISPLAY_STATE_EXPANDED
  4717. wAttr(ytdFlexyElm, 'userscript-chatblock', attr_chatblock)
  4718. wAttr(ytdFlexyElm, 'userscript-chat-collapsed', attr_chatcollapsed)
  4719.  
  4720. _console.log(932, 3, ytdFlexyElm.hasAttribute('userscript-chatblock'))
  4721.  
  4722.  
  4723. }
  4724.  
  4725.  
  4726.  
  4727.  
  4728. if (pageType === 'watch') { // reset info when hidden
  4729. checkVisibleEngagementPanel();
  4730. }
  4731.  
  4732. if(chatTypeChanged){
  4733. if (attr_chatblock == 'chat-live') {
  4734.  
  4735. _console.log(932, 4)
  4736.  
  4737. mtf_forceCheckLiveVideo_disable = 2;
  4738.  
  4739. _disableComments();
  4740.  
  4741.  
  4742. } else {
  4743.  
  4744. _console.log(932, 5)
  4745. mtf_forceCheckLiveVideo_disable = 0;
  4746. setCommentSection(0);
  4747. restoreFetching();
  4748.  
  4749. _console.log(932, 6, mtf_forceCheckLiveVideo_disable)
  4750.  
  4751. if (mtf_forceCheckLiveVideo_disable !== 2) {
  4752.  
  4753. const tabBtn = document.querySelector('[userscript-tab-content="#tab-comments"].tab-btn-hidden')
  4754. if (tabBtn) {
  4755. emptyCommentSection();
  4756. tabBtn.classList.remove("tab-btn-hidden")
  4757. }
  4758.  
  4759. }
  4760.  
  4761.  
  4762.  
  4763. }
  4764.  
  4765.  
  4766. } else {
  4767.  
  4768. // restore Fetching only
  4769.  
  4770.  
  4771. if (mtf_forceCheckLiveVideo_disable !== 2 && (attr_chatblock === false || attr_chatblock === 'chat-playback')) {
  4772.  
  4773.  
  4774. setCommentSection(0);
  4775. restoreFetching();
  4776.  
  4777. }
  4778.  
  4779.  
  4780.  
  4781. }
  4782.  
  4783. }
  4784.  
  4785. f();
  4786.  
  4787.  
  4788.  
  4789.  
  4790.  
  4791. }
  4792.  
  4793.  
  4794. document.addEventListener('wheel', function(evt) {
  4795.  
  4796. if (!scriptEnable) return;
  4797. const displayedPlaylist_element = kRef(displayedPlaylist);
  4798. if (displayedPlaylist_element && elementContains.call(displayedPlaylist_element, evt.target)) {
  4799. evt.stopPropagation();
  4800. evt.stopImmediatePropagation();
  4801. }
  4802. }, capturePassive);
  4803.  
  4804.  
  4805. function setVideosTwoColumns(/** @type {number} */ flag, /** @type {boolean} */ bool) {
  4806.  
  4807. //two columns to one column
  4808.  
  4809. /*
  4810. [placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy
  4811.  
  4812. is-two-columns ="" => no is-two-columns
  4813. [placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer
  4814. no hidden => hidden =""
  4815. [placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer
  4816. hidden ="" => no hidden
  4817.  
  4818. */
  4819.  
  4820. let cssSelector1 = '[placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy'
  4821.  
  4822. let cssSelector2 = '[placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer'
  4823.  
  4824. let cssSelector3 = '[placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer'
  4825.  
  4826. let res = {}
  4827. if (flag & 1) {
  4828. res.m1 = document.querySelector(cssSelector1)
  4829. if (res.m1) wAttr(res.m1, 'is-two-columns', bool ? '' : false);
  4830. }
  4831.  
  4832. if (flag & 2) {
  4833. res.m2 = document.querySelector(cssSelector2)
  4834. if (res.m2) wAttr(res.m2, 'hidden', bool ? false : '');
  4835. }
  4836.  
  4837. if (flag & 4) {
  4838. res.m3 = document.querySelector(cssSelector3)
  4839. if (res.m3) wAttr(res.m3, 'hidden', bool ? '' : false);
  4840. }
  4841.  
  4842.  
  4843. return res
  4844.  
  4845.  
  4846.  
  4847.  
  4848. }
  4849.  
  4850. let lastScrollFetch = 0;
  4851. // function isScrolledToEnd(){
  4852. // return (window.innerHeight + window.pageYOffset) >= document.scrollingElement.scrollHeight - 2;
  4853. // }
  4854. let lastOffsetTop = 0;
  4855. window.addEventListener('scroll', function(evt) {
  4856.  
  4857. //console.log(evt.target)
  4858.  
  4859. if (!scriptEnable) return;
  4860.  
  4861. let isTwoCol = (wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS
  4862. if(isTwoCol) return;
  4863.  
  4864. if (!kRef(scrollingVideosList)) return;
  4865. if (videoListBeforeSearch) return;
  4866.  
  4867.  
  4868.  
  4869. let visibleHeight = document.scrollingElement.clientHeight;
  4870. let totalHeight = document.scrollingElement.scrollHeight;
  4871.  
  4872. if (totalHeight < visibleHeight * 1.5) return; // filter out two column view;
  4873.  
  4874. let z = window.pageYOffset + visibleHeight;
  4875. let h_advanced = totalHeight - (visibleHeight > 5 * 40 ? visibleHeight * 0.5 : 40);
  4876.  
  4877.  
  4878.  
  4879. if (z > h_advanced) {
  4880.  
  4881. let ct = Date.now();
  4882. if (ct - lastScrollFetch < 500) return; //prevent continuous calling
  4883.  
  4884. lastScrollFetch = ct;
  4885.  
  4886. let res = setVideosTwoColumns(2 | 4, true)
  4887. if (res.m3 && res.m2) {
  4888.  
  4889. //wait for DOM change, just in case
  4890. requestAnimationFrame(() => {
  4891. let { offsetTop } = res.m2 // as visibility of m2 & m3 switched.
  4892.  
  4893. if (offsetTop - lastOffsetTop < 40) return; // in case bug, or repeating calling. // the next button shall below the this button
  4894. lastOffsetTop = offsetTop
  4895.  
  4896. res.m2.parentNode.dispatchEvent(new Event('yt-service-request-sent-button-renderer'))
  4897.  
  4898. res = null
  4899. })
  4900.  
  4901. } else {
  4902.  
  4903. res = null
  4904. }
  4905.  
  4906.  
  4907. }
  4908.  
  4909.  
  4910.  
  4911.  
  4912. }, bubblePassive)
  4913.  
  4914. let fetchPendings = [];
  4915. let mtf_forceCheckLiveVideo_disable = 0;
  4916.  
  4917. let storeLastPanel = null;
  4918.  
  4919.  
  4920.  
  4921.  
  4922.  
  4923. let mgChatFrame = {
  4924. setVar(elm) {
  4925. mgChatFrame.kVar = mWeakRef(elm)
  4926. },
  4927. getVar() {
  4928. return kRef(mgChatFrame.kVar)
  4929. },
  4930. inPage() {
  4931. let elm = mgChatFrame.getVar();
  4932. if (!elm) return false;
  4933. let ytdFlexyElm = kRef(ytdFlexy);
  4934. if(!ytdFlexyElm) return false;
  4935. return elementContains.call(ytdFlexyElm, elm)
  4936. }
  4937. };
  4938.  
  4939. const timeline = {
  4940. // after initialized (initObserver)
  4941. cn1:{},
  4942. cn2:{},
  4943. setTimeout( /** @type {TimerHandler} */ f,/** @type {number} */ d){
  4944. let cid = setTimeout(f,d)
  4945. timeline.cn1[cid]=true
  4946. return cid;
  4947. },
  4948. clearTimeout(/** @type {number} */ cid){
  4949. timeline.cn1[cid]=false; return clearTimeout(cid)
  4950. },
  4951. setInterval(/** @type {TimerHandler} */ f,/** @type {number} */ d){
  4952. let cid = setInterval(f,d);
  4953. timeline.cn2[cid]=true
  4954. return cid;
  4955. },
  4956. clearInterval(/** @type {number} */ cid){
  4957. timeline.cn2[cid]=false; return clearInterval(cid)
  4958. },
  4959. reset(){
  4960. for(let cid in timeline.cn1) timeline.cn1[cid] && clearTimeout(cid)
  4961. for(let cid in timeline.cn2) timeline.cn2[cid] && clearInterval(cid)
  4962. timeline.cn1={}
  4963. timeline.cn2={}
  4964. }
  4965. }
  4966.  
  4967. class AttributeMutationObserver extends MutationObserver {
  4968. constructor(flist){
  4969. super((mutations, observer)=>{
  4970. for(const mutation of mutations){
  4971. if (mutation.type === 'attributes') {
  4972. this.checker(mutation.target, mutation.attributeName)
  4973. }
  4974. }
  4975. })
  4976. this.flist=flist;
  4977. this.res={}
  4978. }
  4979. takeRecords(){
  4980. super.takeRecords();
  4981. }
  4982. disconnect(){
  4983. this._target = null;
  4984. super.disconnect();
  4985. }
  4986. observe(/** @type {Node} */ target){
  4987. if(this._target) return;
  4988. //console.log(123124, target)
  4989. this._target = mWeakRef(target);
  4990. //console.log(123125, kRef(this._target))
  4991. const options = {
  4992. attributes: true,
  4993. attributeFilter: Object.keys(this.flist),
  4994. //attributeFilter: [ "status", "username" ],
  4995. attributeOldValue: true
  4996. }
  4997. super.observe(target, options)
  4998. }
  4999. checker(/** @type {Node} */ target,/** @type {string} */ attributeName){
  5000. let nv = target.getAttribute(attributeName);
  5001. if(this.res[attributeName]!==nv){
  5002. this.res[attributeName] = nv
  5003. let f = this.flist[attributeName];
  5004. if(f) f(attributeName, nv);
  5005.  
  5006. }
  5007. }
  5008. check(delay = 0){
  5009. setTimeout(()=>{
  5010. let target = kRef(this._target)
  5011. if(target!==null){
  5012. for(const key of Object.keys(this.flist)){
  5013. this.checker(target,key)
  5014. }
  5015. }else{
  5016. console.log('target is null') //disconnected??
  5017. }
  5018. target = null;
  5019. },delay)
  5020. }
  5021. }
  5022.  
  5023. function goYoutubeGeniusLyrics() {
  5024.  
  5025. setTimeout(function $f() {
  5026.  
  5027. if (!document.documentElement.hasAttribute('w-engagement-panel-genius-lyrics')) return setTimeout($f, 100)
  5028.  
  5029. document.documentElement.dispatchEvent(new CustomEvent('engagement-panel-genius-lyrics'))
  5030.  
  5031.  
  5032. }, 100)
  5033.  
  5034.  
  5035.  
  5036. }
  5037.  
  5038.  
  5039.  
  5040. //Object.keys($0).filter(key=>!(key in $0.constructor.prototype))
  5041.  
  5042. //Object.getOwnPropertyNames(window).filter(k=>k.startsWith('HTML'))
  5043. //Object.getOwnPropertyNames(window).filter(k=>k.startsWith('HTML')).filter(k=>$0 instanceof window[k])
  5044.  
  5045.  
  5046. /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */
  5047.  
  5048. /*
  5049. fix bug for comment section - version 1.8.7
  5050. This issue is the bug in browser's rendering
  5051. I guess, this is due to the lines clamp with display:-webkit-box
  5052. use stupid coding to let it re-render when its content become visible
  5053. /*
  5054.  
  5055. ytd-expander[should-use-number-of-lines][collapsed] > #content.ytd-expander {
  5056. color: var(--yt-spec-text-primary);
  5057. display: -webkit-box;
  5058. overflow: hidden;
  5059. max-height: none;
  5060. -webkit-box-orient: vertical;
  5061. -webkit-line-clamp: var(--ytd-expander-max-lines, 4);
  5062. }
  5063.  
  5064. // v1.8.36 imposed a effective solution for fixing this bug
  5065.  
  5066. */
  5067.  
  5068. /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */
  5069.  
  5070.  
  5071. /**
  5072. *
  5073.  
  5074.  
  5075. f.initChildrenObserver=function(){var a=this;this.observer=new MutationObserver(function(){return a.childrenChanged()});
  5076. this.observer.observe(this.$.content,{subtree:!0,childList:!0,attributes:!0});this.childrenChanged()};
  5077. f.childrenChanged=function(){var a=this;this.alwaysToggleable?this.canToggle=this.alwaysToggleable:this.canToggleJobId||(this.canToggleJobId=window.requestAnimationFrame(function(){$h(function(){a.canToggleJobId=0;a.calculateCanCollapse()})}))};
  5078.  
  5079.  
  5080. f.onIronResize=function(){this.recomputeOnResize&&this.childrenChanged()};
  5081.  
  5082.  
  5083. onButtonClick_:function(){this.fire("yt-close-upsell-dialog")},
  5084. computeHasHeader_:function(a){return!!a.headerBackgroundImage}});var geb;var heb;var ieb;var jeb;var xI=function(){var a=L.apply(this,arguments)||this;a.alignAuto=!1;a.collapsed=!0;a.isToggled=!1;a.alwaysCollapsed=!1;a.canToggle=!0;a.collapsedHeight=80;a.disableToggle=!1;a.alwaysToggleable=!1;a.reversed=!1;a.shouldUseNumberOfLines=!1;a.recomputeOnResize=!1;a.canToggleJobId=0;return a};
  5085. n(xI,L);f=xI.prototype;f.alwaysToggleableChanged=function(){this.alwaysToggleable&&(this.canToggle=!0)};
  5086.  
  5087.  
  5088. f.calculateCanCollapse=function(){this.canToggle=this.shouldUseNumberOfLines?this.alwaysToggleable||this.$.content.offsetHeight<this.$.content.scrollHeight:this.alwaysToggleable||this.$.content.scrollHeight>this.collapsedHeight};
  5089. f.detachObserver=function(){this.observer&&this.observer.disconnect()};
  5090.  
  5091. *
  5092. *
  5093. *
  5094. */
  5095.  
  5096.  
  5097.  
  5098.  
  5099. })();
  5100.  
  5101.  
  5102.  
  5103.  
  5104.  
  5105.  
  5106.  
  5107.  
  5108.  
  5109.  
  5110.  
  5111.  
  5112.  
  5113.  
  5114.  
  5115.  
  5116.  
  5117.  
  5118.  
  5119.  
  5120.  
  5121.  
  5122.  
  5123.  
  5124.  
  5125.  
  5126.  
  5127.  
  5128.  
  5129.  
  5130.  
  5131.  
  5132.  
  5133.  
  5134.  
  5135.  
  5136.  
  5137.  
  5138.  
  5139.  
  5140.  
  5141. // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js
  5142.  
  5143. }
  5144.  
  5145.  
  5146. ;!(function $$() {
  5147. 'use strict';
  5148.  
  5149. if(document.documentElement==null) return window.requestAnimationFrame($$)
  5150.  
  5151. const cssTxt = GM_getResourceText("contentCSS");
  5152.  
  5153. function addStyle (styleText) {
  5154. const styleNode = document.createElement('style');
  5155. styleNode.type = 'text/css';
  5156. styleNode.textContent = styleText;
  5157. document.documentElement.appendChild(styleNode);
  5158. return styleNode;
  5159. }
  5160.  
  5161. addStyle (cssTxt);
  5162.  
  5163. main(window.$);
  5164.  
  5165.  
  5166. // Your code here...
  5167. })();