Tabview Youtube

Make comments and lists into tabs

当前为 2022-05-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Tabview Youtube
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.1.1
  5. // @description Make comments and lists into tabs
  6. // @author CY Fung
  7. // @match https://www.youtube.com/*
  8. // @resource contentCSS https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/4546739b1eb4224a62a431fe1b23a7b8201d4f17/css/style_content.css
  9. // @resource injectionJS1 https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/4546739b1eb4224a62a431fe1b23a7b8201d4f17/js/injection_script_1.js
  10. // @resource injectionFixAutoComplete https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/4546739b1eb4224a62a431fe1b23a7b8201d4f17/js/injectionScript_fixAutoComplete.js
  11. // @icon https://github.com/cyfung1031/Tabview-Youtube/raw/main/images/icon128p.png
  12. // @require https://cdnjs.cloudflare.com/ajax/libs/cash/8.1.0/cash.min.js
  13. // @grant GM_getResourceText
  14. // @run-at document-start
  15. // @license MIT https://github.com/cyfung1031/Tabview-Youtube/blob/main/LICENSE
  16. // @noframes
  17. // ==/UserScript==
  18.  
  19. /* jshint esversion:6 */
  20.  
  21. function main($){
  22. // MIT License
  23. // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js
  24.  
  25.  
  26.  
  27.  
  28.  
  29.  
  30.  
  31.  
  32.  
  33.  
  34.  
  35.  
  36.  
  37.  
  38.  
  39.  
  40.  
  41.  
  42.  
  43.  
  44.  
  45.  
  46.  
  47. -(function() {
  48.  
  49. function inIframe() {
  50. try {
  51. return window.self !== window.top;
  52. } catch (e) {
  53. return true;
  54. }
  55. }
  56.  
  57. if (inIframe()) return;
  58.  
  59. if (!$) return;
  60.  
  61. /**
  62. * SVG resources:
  63. * <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>
  64. */
  65.  
  66. const scriptVersionForExternal = '2022/05/07';
  67.  
  68. //const githubURLBase = "https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube";
  69. //const githubURLCommit = "bdf401045266e5224663f80b276bc7f56d122b8d";
  70.  
  71. const isMyScriptInChromeRuntime = () => typeof((((window || 0).chrome || 0).runtime || 0).getURL) == 'function'
  72.  
  73.  
  74. const svgComments = `
  75. <path d="M40.068,13.465L5.93,13.535c-3.27,0-5.93,2.66-5.93,5.93v21.141c0,3.27,2.66,5.929,5.93,5.929H12v10
  76. c0,0.413,0.254,0.784,0.64,0.933c0.117,0.045,0.239,0.067,0.36,0.067c0.276,0,0.547-0.115,0.74-0.327l9.704-10.675l16.626-0.068
  77. c3.27,0,5.93-2.66,5.93-5.929V19.395C46,16.125,43.34,13.465,40.068,13.465z M10,23.465h13c0.553,0,1,0.448,1,1s-0.447,1-1,1H10
  78. c-0.553,0-1-0.448-1-1S9.447,23.465,10,23.465z M36,37.465H10c-0.553,0-1-0.448-1-1s0.447-1,1-1h26c0.553,0,1,0.448,1,1
  79. S36.553,37.465,36,37.465z M36,31.465H10c-0.553,0-1-0.448-1-1s0.447-1,1-1h26c0.553,0,1,0.448,1,1S36.553,31.465,36,31.465z"/>
  80. <path d="M54.072,2.535L19.93,2.465c-3.27,0-5.93,2.66-5.93,5.93v3.124l26.064-0.054c4.377,0,7.936,3.557,7.936,7.93v21.07v0.071
  81. v2.087l3.26,3.586c0.193,0.212,0.464,0.327,0.74,0.327c0.121,0,0.243-0.022,0.36-0.067c0.386-0.149,0.64-0.52,0.64-0.933v-10h1.07
  82. c3.27,0,5.93-2.66,5.93-5.929V8.465C60,5.195,57.34,2.535,54.072,2.535z"/>
  83. `.trim();
  84.  
  85. const svgVideos = `<path d="M298,33c0-13.255-10.745-24-24-24H24C10.745,9,0,19.745,0,33v232c0,13.255,10.745,24,24,24h250c13.255,0,24-10.745,24-24V33
  86. z M91,39h43v34H91V39z M61,259H30v-34h31V259z M61,73H30V39h31V73z M134,259H91v-34h43V259z M123,176.708v-55.417
  87. c0-8.25,5.868-11.302,12.77-6.783l40.237,26.272c6.902,4.519,6.958,11.914,0.056,16.434l-40.321,26.277
  88. C128.84,188.011,123,184.958,123,176.708z M207,259h-43v-34h43V259z M207,73h-43V39h43V73z M268,259h-31v-34h31V259z M268,73h-31V39
  89. h31V73z"/>`.trim();
  90.  
  91. const svgInfo = `<path d="M11.812,0C5.289,0,0,5.289,0,11.812s5.289,11.813,11.812,11.813s11.813-5.29,11.813-11.813
  92. S18.335,0,11.812,0z M14.271,18.307c-0.608,0.24-1.092,0.422-1.455,0.548c-0.362,0.126-0.783,0.189-1.262,0.189
  93. c-0.736,0-1.309-0.18-1.717-0.539s-0.611-0.814-0.611-1.367c0-0.215,0.015-0.435,0.045-0.659c0.031-0.224,0.08-0.476,0.147-0.759
  94. l0.761-2.688c0.067-0.258,0.125-0.503,0.171-0.731c0.046-0.23,0.068-0.441,0.068-0.633c0-0.342-0.071-0.582-0.212-0.717
  95. c-0.143-0.135-0.412-0.201-0.813-0.201c-0.196,0-0.398,0.029-0.605,0.09c-0.205,0.063-0.383,0.12-0.529,0.176l0.201-0.828
  96. c0.498-0.203,0.975-0.377,1.43-0.521c0.455-0.146,0.885-0.218,1.29-0.218c0.731,0,1.295,0.178,1.692,0.53
  97. c0.395,0.353,0.594,0.812,0.594,1.376c0,0.117-0.014,0.323-0.041,0.617c-0.027,0.295-0.078,0.564-0.152,0.811l-0.757,2.68
  98. c-0.062,0.215-0.117,0.461-0.167,0.736c-0.049,0.275-0.073,0.485-0.073,0.626c0,0.356,0.079,0.599,0.239,0.728
  99. c0.158,0.129,0.435,0.194,0.827,0.194c0.185,0,0.392-0.033,0.626-0.097c0.232-0.064,0.4-0.121,0.506-0.17L14.271,18.307z
  100. M14.137,7.429c-0.353,0.328-0.778,0.492-1.275,0.492c-0.496,0-0.924-0.164-1.28-0.492c-0.354-0.328-0.533-0.727-0.533-1.193
  101. c0-0.465,0.18-0.865,0.533-1.196c0.356-0.332,0.784-0.497,1.28-0.497c0.497,0,0.923,0.165,1.275,0.497
  102. c0.353,0.331,0.53,0.731,0.53,1.196C14.667,6.703,14.49,7.101,14.137,7.429z"/>`.trim();
  103.  
  104. const svgPlayList = `
  105. <rect x="0" y="64" width="256" height="42.667"/>
  106. <rect x="0" y="149.333" width="256" height="42.667"/>
  107. <rect x="0" y="234.667" width="170.667" height="42.667"/>
  108. <polygon points="341.333,234.667 341.333,149.333 298.667,149.333 298.667,234.667 213.333,234.667 213.333,277.333
  109. 298.667,277.333 298.667,362.667 341.333,362.667 341.333,277.333 426.667,277.333 426.667,234.667"/>
  110. `.trim();
  111.  
  112. // --- Youtube Video Testing :
  113. // Square Video: https://www.youtube.com/watch?v=L0RXVnRbFg8
  114. // Square Video: https://www.youtube.com/watch?v=bK_rKhMIotU
  115. // ---
  116.  
  117.  
  118. const LAYOUT_TWO_COLUMNS = 1;
  119. const LAYOUT_THEATER = 2;
  120. const LAYOUT_FULLSCREEN = 4;
  121. const LAYOUT_CHATROOM = 8;
  122. const LAYOUT_CHATROOM_COLLASPED = 16;
  123. const LAYOUT_TAB_EXPANDED = 32;
  124. const LAYOUT_ENGAGEMENT_PANEL_EXPAND = 64;
  125.  
  126. const mtoInterval1 = 40;
  127. const mtoInterval2 = 150;
  128.  
  129. let lastVideoURL = null; // for less attribute set only
  130.  
  131.  
  132. const WeakRef = window.WeakRef;
  133. const mWeakRef = WeakRef ? (o => o ? new WeakRef(o) : null) : (o => o || null);
  134. const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);
  135.  
  136.  
  137.  
  138. const nonCryptoRandStr_base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
  139. function nonCryptoRandStr(n){
  140. const result = new Array(n);
  141. const baseStr = nonCryptoRandStr_base;
  142. const bLen = baseStr.length;
  143. for ( let i = 0; i < n; i++ ) {
  144. let t = null
  145. do {
  146. t = baseStr.charAt(Math.floor(Math.random() * bLen));
  147. }while(i===0 && 10-t>0)
  148. result[i]=t;
  149. }
  150. return result.join('');
  151. }
  152.  
  153. class ObserverRegister{
  154. static uidStore = {};
  155. /** @type {string} */
  156. uid;
  157.  
  158. /** @type {Function} */
  159. observerCreator;
  160.  
  161. /** @type {MutationObserver | IntersectionObserver} */
  162. observer;
  163. constructor(/** @type {Function} */ observerCreator){
  164. let uid = null;
  165. const uidStore = ObserverRegister.uidStore;
  166. do{
  167. uid = nonCryptoRandStr(5);
  168. }while(uidStore[uid])
  169. uidStore[uid] = true;
  170. this.uid = uid;
  171. this.observerCreator = observerCreator
  172. this.observer = null;
  173. }
  174. bindElement(/** @type {HTMLElement} */ elm, ...args){
  175. if(elm.hasAttribute(`o3r-${this.uid}`))return false;
  176. elm.setAttribute(`o3r-${this.uid}`,'')
  177. if(this.observer===null){
  178. this.observer=this.observerCreator();
  179. }
  180. this.observer.observe(elm, ...args)
  181. return true
  182. }
  183. clear(/** @type {boolean} */ flag){
  184. if(this.observer !== null){
  185. //const uidStore = ObserverRegister.uidStore;
  186. if(flag === true){
  187. this.observer.takeRecords();
  188. this.observer.disconnect();
  189. }
  190. this.observer = null;
  191. for(const s of document.querySelectorAll(`[o3r-${this.uid}]`)) s.removeAttribute(`o3r-${this.uid}`)
  192. //uidStore[this.uid]=false;
  193. //this.uid = null;
  194. }
  195. }
  196. }
  197.  
  198. const mtoMutation_watchFlexy = new ObserverRegister(()=>{
  199. return new MutationObserver(FP.mtoNavF)
  200. });
  201. const sa_wflexy = mtoMutation_watchFlexy.uid;
  202.  
  203. const mtoMutation_body = new ObserverRegister(()=>{
  204. return new MutationObserver(FP.mtoBodyF)
  205. });
  206. const sa_body = mtoMutation_body.uid;
  207.  
  208. const mtoFlexyAttr = new ObserverRegister(()=>{
  209. return new MutationObserver(mtf_attrFlexy)
  210. });
  211. const sa_flexyAttr = mtoFlexyAttr.uid;
  212.  
  213. const mtoVisibility_EngagementPanel = new ObserverRegister(()=>{
  214. return new MutationObserver(FP.mtf_attrEngagementPanel)
  215. });
  216. const sa_epanel = mtoVisibility_EngagementPanel.uid;
  217.  
  218. const mtoVisibility_Playlist = new ObserverRegister(()=>{
  219. return new AttributeMutationObserver({
  220. "hidden": FP.mtf_attrPlaylist
  221. })
  222. })
  223. const sa_playlist = mtoVisibility_Playlist.uid;
  224.  
  225. const mtoVisibility_Comments = new ObserverRegister(()=>{
  226. return new AttributeMutationObserver({
  227. "hidden": FP.mtf_attrComments
  228. })
  229. })
  230. const sa_comments = mtoVisibility_Comments.uid;
  231.  
  232.  
  233. const mtoVisibility_Chatroom = new ObserverRegister(()=>{
  234. return new AttributeMutationObserver({
  235. "collapsed": FP.mtf_attrChatroom
  236. })
  237. })
  238. const sa_chatroom = mtoVisibility_Chatroom.uid;
  239.  
  240.  
  241.  
  242.  
  243.  
  244. function tracer(key, cmp) {
  245. if (cmp > 0) return tracer[key] === cmp;
  246. return (tracer[key] = Date.now());
  247. }
  248.  
  249. function racer(key, f) {
  250. let now = Date.now();
  251. const kTime = `${key}$$1`
  252. let t = racer[kTime] || 0;
  253.  
  254. if (now < t) {
  255. const kCount = `${key}$$2`;
  256. racer[kCount] = (racer[kCount] || 0) + 1;
  257. if (racer[kCount] === 1) {
  258. let g = f;
  259. requestAnimationFrame(() => {
  260. racer[kCount] = 0;
  261. g();
  262. g = null;
  263. })
  264. }
  265. } else {
  266. racer[kTime] = now + 16;
  267. f();
  268. }
  269. }
  270.  
  271. class ScriptEF {
  272. constructor() {
  273. this._id = scriptEC;
  274. }
  275. isValid() {
  276. return this._id === scriptEC;
  277. }
  278. }
  279.  
  280. class Timeout {
  281.  
  282. set(f, d, repeatCount) {
  283. if (this.cid > 0) return;
  284. let sEF = new ScriptEF();
  285. if (repeatCount > 0) {
  286.  
  287. let rc = repeatCount;
  288. const g = () => {
  289. this.cid = 0;
  290. if (!sEF.isValid()) return;
  291. let res = f();
  292. if (--rc <= 0) return;
  293. if (res === true) this.cid = timeline.setTimeout(g, d);
  294. }
  295. g();
  296.  
  297. } else {
  298.  
  299. const g = () => {
  300. this.cid = 0;
  301. if (!sEF.isValid()) return;
  302. if (f() === true) this.cid = timeline.setTimeout(g, d);
  303. }
  304. this.cid = timeline.setTimeout(g, d);
  305. }
  306. }
  307.  
  308. clear() {
  309. if (this.cid > 0) timeline.clearTimeout(this.cid);
  310. }
  311.  
  312. isEmpty() {
  313. return !this.cid
  314. }
  315.  
  316.  
  317. }
  318.  
  319. class Mutex {
  320.  
  321. constructor() {
  322. this.p = Promise.resolve()
  323. }
  324.  
  325. lockWith(f) {
  326.  
  327. this.p = this.p.then(() => {
  328. return new Promise(f)
  329. }).catch(console.warn)
  330. }
  331.  
  332. }
  333.  
  334.  
  335.  
  336. function prettyElm(elm) {
  337. if (!elm || !elm.nodeName) return null;
  338. const eId = elm.id || null;
  339. const eClsName = elm.className || null;
  340. return [elm.nodeName.toLowerCase(), typeof eId == 'string' ? "#" + eId : '', typeof eClsName == 'string' ? '.' + eClsName.replace(/\s+/g, '.') : ''].join('').trim();
  341. }
  342.  
  343. function extractTextContent(elm) {
  344. 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, '')
  345. }
  346.  
  347. function addScript(scriptText) {
  348. const scriptNode = document.createElement('script');
  349. scriptNode.type = 'text/javascript';
  350. scriptNode.textContent = scriptText;
  351. try {
  352. document.documentElement.appendChild(scriptNode);
  353. } catch (e) {
  354. console.log('addScript Error', e)
  355. }
  356. return scriptNode;
  357. }
  358.  
  359. function addScriptByURL(scriptURL) {
  360. const scriptNode = document.createElement('script');
  361. scriptNode.type = 'text/javascript';
  362. scriptNode.src = scriptURL;
  363. try {
  364. document.documentElement.appendChild(scriptNode);
  365. } catch (e) {
  366. console.log('addScriptByURL Error', e)
  367. }
  368. return scriptNode;
  369. }
  370.  
  371. function addStyle(styleText, container) {
  372. const styleNode = document.createElement('style');
  373. //styleNode.type = 'text/css';
  374. styleNode.textContent = styleText;
  375. (container || document.documentElement).appendChild(styleNode);
  376. return styleNode;
  377. }
  378.  
  379.  
  380. const stopIframePropagation = function(evt){
  381. if(scriptEnable && evt && evt.target && evt.target.nodeName=='IFRAME'){
  382. evt.stopImmediatePropagation();
  383. evt.stopPropagation();
  384. }
  385. }
  386. document.addEventListener('mouseover', stopIframePropagation, true)
  387. document.addEventListener('mouseout', stopIframePropagation, true)
  388. document.addEventListener('mousedown', stopIframePropagation, true)
  389. document.addEventListener('mouseup', stopIframePropagation, true)
  390. document.addEventListener('keydown', stopIframePropagation, true)
  391. document.addEventListener('keyup', stopIframePropagation, true)
  392. document.addEventListener('mouseenter', stopIframePropagation, true)
  393. document.addEventListener('mouseleave', stopIframePropagation, true)
  394.  
  395.  
  396.  
  397. function isDOMVisible(elem) {
  398. // jQuery version : https://github.com/jquery/jquery/blob/a684e6ba836f7c553968d7d026ed7941e1a612d8/src/css/hiddenVisibleSelectors.js
  399. return !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length);
  400. }
  401.  
  402. function isNonEmptyString(s) {
  403. return typeof s == 'string' && s.length > 0;
  404. }
  405.  
  406.  
  407. async function nativeFunc(dom, property, args) {
  408. dom.dispatchEvent(new CustomEvent("userscript-call-dom-func", { detail: { property, args } }))
  409. }
  410.  
  411. /*
  412. async function nativeValue(dom, property, args) {
  413. dom.dispatchEvent(new CustomEvent("userscript-call-dom-value", { detail: { property, args } }))
  414. }
  415. */
  416. async function nativeFuncStacked(selector, property, args){
  417. document.dispatchEvent(new CustomEvent("userscript-call-dom-func-stacked", { detail: { selector, property, args } }))
  418. }
  419. /*
  420. async function nativeValueStacked(selector, property, args){
  421. document.dispatchEvent(new CustomEvent("userscript-call-dom-value-stacked", { detail: { selector, property, args } }))
  422. }
  423. async function nativeConstStacked(selector, property, args){
  424. document.dispatchEvent(new CustomEvent("userscript-call-dom-const-stacked", { detail: { selector, property, args } }))
  425. }
  426. */
  427.  
  428.  
  429. function akAttr(cssElm, attrName, isNegative, flag) {
  430. // isNegative => incomplete loading
  431.  
  432. let u = parseInt(cssElm.getAttribute(attrName) || 0) || 0;
  433. let ak = Math.abs(u);
  434.  
  435. if (ak > 100 && isNegative && u < 0) {
  436.  
  437. } else if (ak > 100 && !isNegative && u > 0) {
  438.  
  439. } else {
  440. if (ak <= 100) {
  441. ak = 101;
  442. } else {
  443. ak++;
  444. if (ak >= 800) ak = 101;
  445. }
  446. // 101, 102, ... 799, 101
  447. }
  448.  
  449. cssElm.setAttribute(attrName, `${ isNegative ? -ak : ak }${ flag || '' }`)
  450. }
  451.  
  452.  
  453.  
  454. let timeout_resize_for_layout_change = new Timeout();
  455.  
  456. function dispatchWindowResize(){
  457. // for youtube to detect layout resize for adjusting Player tools
  458. return window.dispatchEvent(new Event('resize'));
  459. }
  460.  
  461.  
  462.  
  463. function layoutStatusChanged(old_layoutStatus, new_layoutStatus) {
  464.  
  465.  
  466. if (old_layoutStatus === new_layoutStatus) return;
  467.  
  468. const cssElm = kRef(ytdFlexy);
  469.  
  470. if (!cssElm) return;
  471.  
  472.  
  473. const new_isExpandedChat = !(new_layoutStatus & LAYOUT_CHATROOM_COLLASPED) && (new_layoutStatus & LAYOUT_CHATROOM)
  474. const new_isCollaspedChat = (new_layoutStatus & LAYOUT_CHATROOM_COLLASPED) && (new_layoutStatus & LAYOUT_CHATROOM)
  475.  
  476. const new_isTwoColumns = new_layoutStatus & LAYOUT_TWO_COLUMNS;
  477. const new_isTheater = new_layoutStatus & LAYOUT_THEATER;
  478. const new_isTabExpanded = new_layoutStatus & LAYOUT_TAB_EXPANDED;
  479. const new_isFullScreen = new_layoutStatus & LAYOUT_FULLSCREEN;
  480. const new_isExpandEPanel = new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPAND;
  481.  
  482.  
  483. function showTabOrChat() {
  484.  
  485. layoutStatusMutex.lockWith(unlock => {
  486.  
  487. if (lastShowTab == '#chatroom') {
  488.  
  489. if (new_isTabExpanded) switchTabActivity(null)
  490. if (!new_isExpandedChat) ytBtnExpandChat();
  491.  
  492. } else if (lastShowTab && lastShowTab.indexOf('#engagement-panel-') == 0) {
  493.  
  494. if (new_isTabExpanded) switchTabActivity(null)
  495. if (!new_isExpandEPanel) ytBtnOpenEngagementPanel(lastShowTab);
  496.  
  497. } else {
  498.  
  499. if (new_isExpandedChat) ytBtnCollapseChat()
  500. if (!new_isTabExpanded) {setToActiveTab();}
  501.  
  502. }
  503.  
  504. timeline.setTimeout(unlock, 40);
  505.  
  506. })
  507. }
  508.  
  509. function hideTabAndChat() {
  510.  
  511. layoutStatusMutex.lockWith(unlock => {
  512.  
  513. if (new_isTabExpanded) switchTabActivity(null)
  514. if (new_isExpandedChat) ytBtnCollapseChat()
  515. if (new_isExpandEPanel) ytBtnCloseEngagementPanels();
  516.  
  517.  
  518. timeline.setTimeout(unlock, 40);
  519.  
  520. })
  521.  
  522. }
  523.  
  524.  
  525. if (new_isExpandedChat || new_isTabExpanded || new_isExpandEPanel) {
  526. if (statusCollasped !== 1) statusCollasped = 1;
  527. } else {
  528. if (statusCollasped === 1) statusCollasped = 2;
  529. }
  530.  
  531. let changes = 0;
  532.  
  533. if (old_layoutStatus !== null) changes = old_layoutStatus ^ new_layoutStatus;
  534.  
  535. let chat_collasped_changed = !!(changes & LAYOUT_CHATROOM_COLLASPED)
  536. let tab_expanded_changed = !!(changes & LAYOUT_TAB_EXPANDED)
  537. let theater_mode_changed = !!(changes & LAYOUT_THEATER)
  538. let column_mode_changed = !!(changes & LAYOUT_TWO_COLUMNS)
  539. let fullscreen_mode_changed = !!(changes & LAYOUT_FULLSCREEN)
  540. let epanel_expanded_changed = !!(changes & LAYOUT_ENGAGEMENT_PANEL_EXPAND)
  541.  
  542. let tab_change = (tab_expanded_changed ? 1 : 0) | (chat_collasped_changed ? 2 : 0) | (epanel_expanded_changed ? 4 : 0);
  543.  
  544. let isChatOrTabExpandTriggering = tab_change == 0 ? false : (
  545. (tab_expanded_changed && new_isTabExpanded) ||
  546. (chat_collasped_changed && new_isExpandedChat) ||
  547. (epanel_expanded_changed && new_isExpandEPanel)
  548. );
  549.  
  550. let isChatOrTabCollaspeTriggering = tab_change == 0 ? false : (
  551. (tab_expanded_changed && !new_isTabExpanded) ||
  552. (chat_collasped_changed && new_isCollaspedChat) ||
  553. (epanel_expanded_changed && !new_isExpandEPanel)
  554. );
  555.  
  556.  
  557.  
  558. let moreThanOneShown = (new_isTabExpanded + new_isExpandedChat + new_isExpandEPanel) > 1
  559.  
  560. let requestVideoResize = false;
  561.  
  562. if (fullscreen_mode_changed || new_isFullScreen) {
  563.  
  564. } else if (tab_change == 0 && column_mode_changed && new_isTwoColumns && !new_isTheater && statusCollasped === 1 && moreThanOneShown) {
  565.  
  566. showTabOrChat();
  567. requestVideoResize = true;
  568.  
  569. } else if (tab_change == 2 && new_isExpandedChat && new_isTwoColumns && !new_isTheater && statusCollasped === 1 && new_isTabExpanded && !column_mode_changed) {
  570.  
  571. switchTabActivity(null);
  572. requestVideoResize = true;
  573.  
  574. } else if (isChatOrTabExpandTriggering && new_isTwoColumns && new_isTheater && statusCollasped === 1 && !theater_mode_changed && !column_mode_changed) {
  575.  
  576. ytBtnCancelTheater();
  577. requestVideoResize = true;
  578.  
  579. } else if (new_isTwoColumns && new_isTheater && statusCollasped === 1) {
  580.  
  581. hideTabAndChat();
  582. requestVideoResize = true;
  583.  
  584. } else if (isChatOrTabCollaspeTriggering && new_isTwoColumns && !new_isTheater && statusCollasped === 2 && !column_mode_changed) {
  585.  
  586. ytBtnSetTheater();
  587. requestVideoResize = true;
  588.  
  589. } else if (tab_change == 0 && (column_mode_changed || theater_mode_changed) && new_isTwoColumns && !new_isTheater && statusCollasped !== 1) {
  590.  
  591. showTabOrChat();
  592. requestVideoResize = true;
  593.  
  594. } else if (!new_isFullScreen && new_isTwoColumns && !new_isTheater && !new_isTabExpanded &&
  595. (new_isCollaspedChat || !new_isExpandedChat) &&
  596. !new_isExpandEPanel
  597. ) {
  598. // bug fix for restoring from mini player
  599.  
  600. layoutStatusMutex.lockWith(unlock => {
  601.  
  602. if (new_isExpandedChat) ytBtnCollapseChat()
  603. setToActiveTab();
  604.  
  605. timeline.setTimeout(unlock, 40);
  606.  
  607. })
  608.  
  609. requestVideoResize = true;
  610.  
  611. } else if (tab_expanded_changed) {
  612.  
  613. requestVideoResize = true;
  614.  
  615. }
  616.  
  617.  
  618. if (column_mode_changed && !chat_collasped_changed && new_isExpandedChat) {
  619.  
  620. runAfterExpandChat();
  621.  
  622. }
  623.  
  624.  
  625.  
  626. if (requestVideoResize) {
  627.  
  628. timeout_resize_for_layout_change.clear();
  629. timeout_resize_for_layout_change.set(() => {
  630. dispatchWindowResize();
  631. }, 92)
  632.  
  633. } else if (timeout_resize_for_layout_change.isEmpty() && (Date.now()) - lastResizeAt > 600) {
  634. timeout_resize_for_layout_change.set(() => {
  635. if ((Date.now()) - lastResizeAt > 600) dispatchWindowResize();
  636. }, 62)
  637. }
  638.  
  639.  
  640.  
  641.  
  642. }
  643.  
  644.  
  645. const $ws = {
  646. _layoutStatus: null,
  647. layoutStatus_pending: false
  648. }
  649.  
  650. let wls = new class {
  651. get layoutStatus() {
  652. return this._layoutStatus;
  653. }
  654. set layoutStatus(nv) {
  655.  
  656. if (nv === null) {
  657. this._layoutStatus = null;
  658. statusCollasped = 0;
  659. return;
  660. }
  661. if (nv === this._layoutStatus) return;
  662.  
  663. if (!this.layoutStatus_pending) {
  664. this.layoutStatus_pending = true;
  665. const old_layoutStatus = this._layoutStatus;
  666.  
  667. layoutStatusMutex.lockWith(unlock => {
  668.  
  669. this.layoutStatus_pending = false;
  670. const new_layoutStatus = this._layoutStatus;
  671. layoutStatusChanged(old_layoutStatus, new_layoutStatus);
  672.  
  673. timeline.setTimeout(unlock, 40)
  674.  
  675.  
  676. })
  677. }
  678.  
  679. this._layoutStatus = nv;
  680. }
  681. };
  682.  
  683.  
  684.  
  685.  
  686. const svgElm = (w, h, vw, vh, p) => `<svg width="${w}" height="${h}" viewBox="0 0 ${vw} ${vh}" preserveAspectRatio="xMidYMid meet">${p}</svg>`
  687.  
  688. let settings = {
  689. defaultTab: "#tab-videos"
  690. };
  691.  
  692.  
  693. let mtoInterval = mtoInterval1;
  694.  
  695. function isVideoPlaying(video) {
  696. return video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA;
  697. }
  698.  
  699. function wAttr(elm, attr, kv) {
  700. 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) }
  701. }
  702.  
  703. function hideTabBtn(tabBtn) {
  704. let isActiveBefore = tabBtn.classList.contains('active');
  705. tabBtn.classList.add("tab-btn-hidden");
  706. if (isActiveBefore) {
  707. setToActiveTab();
  708. }
  709. }
  710.  
  711. function hasAttribute(obj, key) {
  712. return obj && obj.hasAttribute(key);
  713. }
  714.  
  715. function isTheater() {
  716. const cssElm = kRef(ytdFlexy);
  717. return (cssElm && cssElm.hasAttribute('theater'))
  718. }
  719.  
  720. function isFullScreen() {
  721. const cssElm = kRef(ytdFlexy);
  722. return (cssElm && cssElm.hasAttribute('fullscreen'))
  723. }
  724.  
  725. function isChatExpand() {
  726. const cssElm = kRef(ytdFlexy);
  727. return cssElm && cssElm.hasAttribute('userscript-chatblock') && !cssElm.hasAttribute('userscript-chat-collapsed')
  728. }
  729.  
  730. function isWideScreenWithTwoColumns() {
  731. const cssElm = kRef(ytdFlexy);
  732. return (cssElm && cssElm.hasAttribute('is-two-columns_'))
  733. }
  734.  
  735. function isAnyActiveTab() {
  736. return $('#right-tabs .tab-btn.active').length > 0
  737. }
  738.  
  739. function isEngagementPanelExpanded() { //note: not checking the visual elements
  740. const cssElm = kRef(ytdFlexy);
  741. return (cssElm && +cssElm.getAttribute('userscript-engagement-panel') > 0)
  742. }
  743.  
  744. function engagement_panels_() {
  745.  
  746. let res = [];
  747. let shownRes = [];
  748.  
  749. let v = 0,
  750. k = 1,
  751. count = 0;
  752. for (const ePanel of document.querySelectorAll(
  753. `ytd-watch-flexy ytd-engagement-panel-section-list-renderer[o3r-${sa_epanel}]`
  754. )) {
  755.  
  756. let visibility = ePanel.getAttribute('visibility') //ENGAGEMENT_PANEL_VISIBILITY_EXPANDED //ENGAGEMENT_PANEL_VISIBILITY_HIDDEN
  757.  
  758. switch (visibility) {
  759. case 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED':
  760. v |= k;
  761. count++;
  762. shownRes.push(ePanel)
  763. res.push({ ePanel, k, visible: true });
  764. break;
  765. case 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN':
  766. res.push({ ePanel, k, visible: false });
  767. break;
  768. default:
  769. res.push({ ePanel, k, visible: false });
  770. }
  771.  
  772. k = k << 1;
  773.  
  774. }
  775. return { list: res, value: v, count: count, shownRes };
  776. }
  777.  
  778.  
  779. function ytBtnOpenEngagementPanel(panel_id) {
  780.  
  781. if (typeof panel_id == 'string') {
  782. panel_id = panel_id.replace('#engagement-panel-', '');
  783. panel_id = parseInt(panel_id);
  784. }
  785. if (panel_id >= 0) {} else return false;
  786.  
  787. let panels = engagement_panels_();
  788.  
  789. for (const { ePanel, k, visible } of panels.list) {
  790. if ((panel_id & k) === k) {
  791. if (!visible) ePanel.setAttribute('visibility', "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED");
  792. } else {
  793. if (visible) ytBtnCloseEngagementPanel(ePanel);
  794. }
  795. }
  796.  
  797. }
  798.  
  799. function ytBtnCloseEngagementPanel(s) {
  800. //ePanel.setAttribute('visibility',"ENGAGEMENT_PANEL_VISIBILITY_HIDDEN");
  801. let btn = s.querySelector('ytd-watch-flexy ytd-engagement-panel-title-header-renderer #header>#visibility-button>ytd-button-renderer');
  802. if (btn) {
  803. btn.click();
  804. }
  805. }
  806.  
  807. function ytBtnCloseEngagementPanels() {
  808. if (isEngagementPanelExpanded()) {
  809. for (const s of document.querySelectorAll(
  810. `ytd-watch-flexy ytd-engagement-panel-section-list-renderer[o3r-${sa_epanel}]`
  811. )) {
  812. if (s.getAttribute('visibility') == "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED") ytBtnCloseEngagementPanel(s);
  813. }
  814. }
  815. }
  816.  
  817. function ytBtnSetTheater() {
  818. if (!isTheater()) {
  819. const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
  820. if (sizeBtn) sizeBtn.click();
  821. }
  822. }
  823.  
  824. function ytBtnCancelTheater() {
  825. if (isTheater()) {
  826. const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
  827. if (sizeBtn) sizeBtn.click();
  828. }
  829. }
  830.  
  831. function ytBtnExpandChat() {
  832. let button = document.querySelector('ytd-live-chat-frame#chat[collapsed]>.ytd-live-chat-frame#show-hide-button')
  833. if (button) button.querySelector('ytd-toggle-button-renderer').click();
  834. }
  835.  
  836. function ytBtnCollapseChat() {
  837. let button = document.querySelector('ytd-live-chat-frame#chat:not([collapsed])>.ytd-live-chat-frame#show-hide-button')
  838. if (button) button.querySelector('ytd-toggle-button-renderer').click();
  839. }
  840.  
  841. /*
  842. function hackImgShadow(imgShadow) {
  843. // add to #columns and add back after loaded
  844. let img = imgShadow.querySelector('img')
  845. if (!img) return;
  846.  
  847. let p = imgShadow.parentNode
  848. let z = $(imgShadow).clone()[0]; //to occupy the space
  849. p.replaceChild(z, imgShadow)
  850. $(imgShadow).prependTo('#columns'); // refer to css hack
  851.  
  852. function onload(evt) {
  853. if (evt) this.removeEventListener('load', onload, false)
  854. p.replaceChild(imgShadow, z)
  855. p = null;
  856. z = null;
  857. imgShadow = null;
  858. }
  859.  
  860. if (img.complete) onload();
  861. else img.addEventListener('load', onload, false)
  862. }
  863. */
  864.  
  865.  
  866. const Q = {}
  867. const FOnce = {}
  868.  
  869. const $callOnce = function(key) {
  870. if (FOnce[key] && FOnce[key]() === false) FOnce[key] = null
  871. }
  872. const $callOnceAsync = async function(key) {
  873. if (FOnce[key] && FOnce[key]() === false) FOnce[key] = null
  874. }
  875.  
  876.  
  877.  
  878. function chatFrameContentDocument() {
  879. // non-null if iframe exist && contentDocument && readyState = complete
  880.  
  881. let iframe = document.querySelector('ytd-live-chat-frame iframe#chatframe');
  882. if (!iframe) return null; //iframe must be there
  883. let cDoc = null;
  884. try {
  885. cDoc = iframe.contentDocument;
  886. } catch (e) {}
  887. if (!cDoc) return null;
  888. if (cDoc.readyState != 'complete') return null; //we must wait for its completion
  889.  
  890. return cDoc;
  891.  
  892. }
  893.  
  894. function chatFrameElement(cssSelector) {
  895. let cDoc = chatFrameContentDocument();
  896. if (!cDoc) return null;
  897. let elm = null;
  898. try {
  899. elm = cDoc.querySelector(cssSelector)
  900. } catch (e) {
  901. console.log('iframe error', e)
  902. }
  903. return elm;
  904. }
  905.  
  906.  
  907.  
  908.  
  909. function fixTabs() {
  910.  
  911.  
  912. if (!scriptEnable) return;
  913.  
  914.  
  915. let queryElement = document.querySelector('*:not(#tab-videos)>#related:not([non-placeholder-videos]) > ytd-watch-next-secondary-results-renderer')
  916.  
  917. let isRelocated = !!queryElement;
  918.  
  919.  
  920.  
  921. if (isRelocated) {
  922.  
  923. let relocatedRelated = queryElement.parentNode; // NOT NULL
  924.  
  925. let right_tabs = document.querySelector('#right-tabs');
  926. let tab_videos = right_tabs.querySelector("#tab-videos");
  927.  
  928. if (!right_tabs || !tab_videos) return;
  929.  
  930. for (const s of relocatedRelated.querySelectorAll('#related')) {
  931. s.setAttribute('non-placeholder-videos', '')
  932. }
  933.  
  934. let target_container = document.querySelector('ytd-watch-flexy:not([is-two-columns_]) #primary-inner, ytd-watch-flexy[is-two-columns_] #secondary-inner')
  935.  
  936. if (target_container) target_container.append(right_tabs) // last-child
  937.  
  938.  
  939. let videos_related = relocatedRelated; // NOT NULL
  940. $('[placeholder-videos]').removeAttr('placeholder-videos');
  941. $('[placeholder-for-youtube-play-next-queue]').removeAttr('placeholder-for-youtube-play-next-queue');
  942.  
  943. tab_videos.appendChild(videos_related);
  944. let videos_results_renderer = relocatedRelated.querySelector("ytd-watch-next-secondary-results-renderer");
  945. if (videos_results_renderer) videos_results_renderer.setAttribute('data-dom-changed-by-tabview-youtube', scriptVersionForExternal);
  946. videos_related.setAttribute('placeholder-for-youtube-play-next-queue', '')
  947. videos_related.setAttribute('placeholder-videos', '')
  948.  
  949. $('[placeholder-videos]').on("scroll", makeBodyScrollByEvt);
  950.  
  951.  
  952.  
  953.  
  954. }
  955.  
  956.  
  957.  
  958.  
  959. let chatroom = null;
  960. if (chatroom = document.querySelector('*:not([data-positioner="before|#chat"]) + ytd-live-chat-frame#chat, ytd-live-chat-frame#chat:first-child')) {
  961.  
  962. let positioner = document.querySelector('tabview-youtube-positioner[data-positioner="before|#chat"]');
  963. if (positioner) positioner.remove();
  964.  
  965.  
  966. if (document.querySelector('.YouTubeLiveFilledUpView')) {
  967. // no relocation
  968. } else {
  969.  
  970. $(chatroom).insertBefore('#right-tabs')
  971.  
  972. }
  973.  
  974.  
  975. $(positioner ? positioner : document.createElement('tabview-youtube-positioner')).attr('data-positioner', 'before|#chat').insertBefore(chatroom)
  976.  
  977.  
  978.  
  979. }
  980.  
  981.  
  982. }
  983.  
  984. const injectionScript_fixAutoComplete = function() {
  985.  
  986. // https://cdnjs.cloudflare.com/ajax/libs/JavaScript-autoComplete/1.0.4/auto-complete.min.js
  987.  
  988. for (const s of document.querySelectorAll('[autocomplete="off"]:not([data-autocomplete-results-id])')) {
  989.  
  990.  
  991. let sc = s.sc;
  992. if (sc instanceof HTMLElement) {
  993.  
  994. let id = Date.now();
  995. s.setAttribute('data-autocomplete-results-id', id);
  996. sc.setAttribute('data-autocomplete-input-id', id);
  997.  
  998. if (window.WeakRef) {
  999. s._sc = new WeakRef(sc);
  1000. s.sc = null;
  1001. delete s.sc;
  1002. Object.defineProperty(s, 'sc', {
  1003. get: function() { return s._sc.deref() || null; },
  1004. enumerable: true,
  1005. configurable: true
  1006. })
  1007. }
  1008.  
  1009. if (sc.hasAttribute('autocomplete-disable-updatesc') && typeof s.updateSC == 'function') {
  1010.  
  1011. window.removeEventListener('resize', s.updateSC);
  1012. s.updateSC = function() {};
  1013.  
  1014. }
  1015.  
  1016. sc.dispatchEvent(new CustomEvent('autocomplete-sc-exist'));
  1017.  
  1018.  
  1019. }
  1020.  
  1021. }
  1022.  
  1023. };
  1024.  
  1025.  
  1026. function handlerAutoCompleteExist() {
  1027.  
  1028.  
  1029. let autoComplete = this;
  1030.  
  1031. autoComplete.removeEventListener('autocomplete-sc-exist', handlerAutoCompleteExist, false)
  1032.  
  1033. let domId = autoComplete.getAttribute('data-autocomplete-input-id')
  1034. let searchBox = autoComplete.ownerDocument.querySelector(`[data-autocomplete-results-id="${domId}"]`)
  1035.  
  1036. if (!domId || !searchBox) return;
  1037.  
  1038. let positioner = searchBox.nextSibling;
  1039. if (positioner && positioner.nodeName.toLowerCase() == "autocomplete-positioner") {} else if (positioner && positioner.nodeName.toLowerCase() != "autocomplete-positioner") {
  1040. $(positioner = document.createElement("autocomplete-positioner")).insertAfter(searchBox);
  1041. } else {
  1042. $(positioner = document.createElement("autocomplete-positioner")).prependTo(searchBox.parentNode);
  1043. }
  1044. $(autoComplete).prependTo(positioner);
  1045.  
  1046. positioner.style.setProperty('--sb-margin-bottom', getComputedStyle(searchBox).marginBottom)
  1047. positioner.style.setProperty('--height', searchBox.offsetHeight + 'px')
  1048.  
  1049. }
  1050.  
  1051. function mtf_fixAutoCompletePosition(elmAutoComplete) {
  1052.  
  1053. elmAutoComplete.setAttribute('autocomplete-disable-updatesc', '')
  1054. elmAutoComplete.addEventListener('autocomplete-sc-exist', handlerAutoCompleteExist, false)
  1055.  
  1056. if (isMyScriptInChromeRuntime()){
  1057. addScriptByURL(window.chrome.runtime.getURL('js/injectionScript_fixAutoComplete.js'));
  1058. } else {
  1059. let injection_script = GM_getResourceText("injectionFixAutoComplete")
  1060. addScript(`${injection_script}`);
  1061. }
  1062.  
  1063. }
  1064.  
  1065. function mtf_AfterFixTabs() {
  1066.  
  1067.  
  1068. let ytdFlexyElm = kRef(ytdFlexy);
  1069. if (!scriptEnable || !ytdFlexyElm) return;
  1070.  
  1071. const rootElement = Q.mutationTarget || ytdFlexyElm;
  1072.  
  1073.  
  1074.  
  1075. const autocomplete = rootElement.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search + autocomplete-positioner > .autocomplete-suggestions[data-autocomplete-input-id]:not([position-fixed-by-tabview-youtube])')
  1076.  
  1077. if (autocomplete) {
  1078.  
  1079. const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')
  1080.  
  1081.  
  1082. if (searchBox) {
  1083.  
  1084.  
  1085. autocomplete.parentNode.setAttribute('position-fixed-by-tabview-youtube', '');
  1086. autocomplete.setAttribute('position-fixed-by-tabview-youtube', '');
  1087. autocomplete.setAttribute('userscript-scrollbar-render', '')
  1088.  
  1089. if (!searchBox.hasAttribute('is-set-click-to-toggle')) {
  1090. searchBox.setAttribute('is-set-click-to-toggle', '')
  1091. searchBox.addEventListener('click', function() {
  1092.  
  1093.  
  1094. setTimeout(() => {
  1095. const autocomplete = document.querySelector(`.autocomplete-suggestions[data-autocomplete-input-id="${ this.getAttribute('data-autocomplete-results-id') }"]`)
  1096.  
  1097. if (!autocomplete) return;
  1098.  
  1099. const isNotEmpty = (autocomplete.textContent || '').length > 0 && (this.value || '').length > 0;
  1100.  
  1101. if (isNotEmpty) {
  1102.  
  1103. let elmVisible = isDOMVisible(autocomplete)
  1104.  
  1105. if (elmVisible) $(autocomplete).hide();
  1106. else $(autocomplete).show();
  1107.  
  1108. }
  1109.  
  1110. }, 20);
  1111.  
  1112. })
  1113.  
  1114. let timeoutOnce_searchbox_keyup = new Timeout();
  1115. searchBox.addEventListener('keyup', function() {
  1116.  
  1117. timeoutOnce_searchbox_keyup.set(() => {
  1118.  
  1119. const autocomplete = document.querySelector(`.autocomplete-suggestions[data-autocomplete-input-id="${ this.getAttribute('data-autocomplete-results-id') }"]`)
  1120.  
  1121. if (!autocomplete) return;
  1122.  
  1123.  
  1124. const isNotEmpty = (autocomplete.textContent || '').length > 0 && (this.value || '').length > 0
  1125.  
  1126. if (isNotEmpty) {
  1127.  
  1128. let elmVisible = isDOMVisible(autocomplete)
  1129.  
  1130. if (!elmVisible) $(autocomplete).show();
  1131.  
  1132. }
  1133.  
  1134. }, 20);
  1135.  
  1136. })
  1137.  
  1138. }
  1139.  
  1140.  
  1141.  
  1142. }
  1143.  
  1144. }
  1145.  
  1146.  
  1147.  
  1148.  
  1149. let currentLastVideo = rootElement.querySelector('[placeholder-videos] #items ytd-compact-video-renderer:last-of-type')
  1150. let prevLastVideo = kRef(_cachedLastVideo);
  1151.  
  1152. if (prevLastVideo !== currentLastVideo && currentLastVideo) {
  1153. _cachedLastVideo = mWeakRef(currentLastVideo);
  1154. }
  1155.  
  1156. if (prevLastVideo !== currentLastVideo && currentLastVideo && prevLastVideo) {
  1157.  
  1158. let isPrevRemoved = !prevLastVideo.parentNode
  1159.  
  1160.  
  1161. function getVideoListHash() {
  1162.  
  1163. let res = [...document.querySelectorAll('[placeholder-videos] #items ytd-compact-video-renderer')].map(renderer => {
  1164. return renderer.querySelector('a[href*="watch"][href*="v="]').getAttribute('href')
  1165.  
  1166. }).join('|')
  1167. // /watch?v=XXXXX|/watch?v=XXXXXX|/watch?v=XXXXXX
  1168.  
  1169. // alternative - DOM.data.videoId
  1170. // let elms = document.querySelectorAll('[placeholder-videos] #items ytd-compact-video-renderer')
  1171. // let res = [...elms].map(elm=>elm.data.videoId||'').join('|') ;
  1172.  
  1173. if (res.indexOf('||') >= 0) {
  1174. res = '';
  1175. }
  1176.  
  1177. return res ? res : null;
  1178. }
  1179.  
  1180. if (isPrevRemoved) {
  1181.  
  1182. // this is the replacement of videos instead of addition
  1183.  
  1184. const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')
  1185.  
  1186. let currentPlayListHash = getVideoListHash() || null;
  1187.  
  1188. if (!currentPlayListHash) {
  1189.  
  1190. } else if (!videoListBeforeSearch && searchBox) {
  1191.  
  1192. videoListBeforeSearch = currentPlayListHash;
  1193. if (videoListBeforeSearch) {
  1194. //console.log('fromSearch', videoListBeforeSearch)
  1195.  
  1196. requestAnimationFrame(function() {
  1197.  
  1198. let renderer = document.querySelector('[placeholder-videos] ytd-watch-next-secondary-results-renderer');
  1199. if (searchBox && searchBox.parentNode) searchBox.blur();
  1200.  
  1201. if (renderer) {
  1202. let scrollParent = renderer.parentNode;
  1203. if (scrollParent.scrollHeight > scrollParent.offsetHeight) {
  1204. let targetTop = renderer.offsetTop;
  1205. if (searchBox && searchBox.parentNode == scrollParent) targetTop -= searchBox.offsetHeight
  1206. scrollParent.scrollTop = targetTop - scrollParent.firstChild.offsetTop;
  1207. }
  1208. }
  1209.  
  1210. });
  1211.  
  1212. }
  1213.  
  1214. } else if (videoListBeforeSearch) {
  1215.  
  1216. if (currentPlayListHash != videoListBeforeSearch) {
  1217.  
  1218. videoListBeforeSearch = null;
  1219. //console.log('fromSearch', videoListBeforeSearch)
  1220.  
  1221.  
  1222. }
  1223.  
  1224. }
  1225.  
  1226.  
  1227. }
  1228.  
  1229.  
  1230. }
  1231.  
  1232.  
  1233.  
  1234.  
  1235. }
  1236.  
  1237. function base_ChatExist() {
  1238.  
  1239. let ytdFlexyElm = kRef(ytdFlexy);
  1240. if (!scriptEnable || !ytdFlexyElm) return null;
  1241.  
  1242. // no mutation triggering if the changes are inside the iframe
  1243.  
  1244. // 1) Detection of #continuations inside iframe
  1245. // iframe ownerDocument is accessible due to same origin
  1246. // if the chatroom is collasped, no determination of live chat or replay (as no #continuations and somehow a blank iframe doc)
  1247.  
  1248. // 2) Detection of meta tag
  1249. // This is fastest but not reliable. It is somehow a bug that the navigation might not update the meta tag content
  1250.  
  1251. // 3) Detection of HTMLElement inside video player for live video
  1252.  
  1253. // (1)+(3) = solution
  1254.  
  1255. let attr_chatblock = null
  1256. let attr_chatcollapsed = null;
  1257.  
  1258. const elmChat = document.querySelector('ytd-live-chat-frame#chat')
  1259. let elmCont = null;
  1260. if (elmChat) {
  1261. elmCont = chatFrameElement('yt-live-chat-renderer #continuations')
  1262.  
  1263.  
  1264. let s = 0;
  1265. if (elmCont) {
  1266. //not found if it is collasped.
  1267. s |= elmCont.querySelector('yt-timed-continuation') ? 1 : 0;
  1268. s |= elmCont.querySelector('yt-live-chat-replay-continuation, yt-player-seek-continuation') ? 2 : 0;
  1269. //s |= elmCont.querySelector('yt-live-chat-restricted-participation-renderer')?4:0;
  1270. if (s == 1) {
  1271. attr_chatblock = 'chat-live';
  1272. }else if (s == 2) attr_chatblock = 'chat-playback';
  1273.  
  1274. if (s == 1) $("span#tab3-txt-loader").text('');
  1275.  
  1276. } else if (!ytdFlexyElm.hasAttribute('userscript-chatblock')) {
  1277. // live chat frame but type not known
  1278. attr_chatblock = '';
  1279.  
  1280. }
  1281. //keep unknown as original
  1282. let isCollapsed = !!elmChat.hasAttribute('collapsed');
  1283. attr_chatcollapsed = isCollapsed;
  1284.  
  1285. } else {
  1286. attr_chatblock = false;
  1287. attr_chatcollapsed = false;
  1288.  
  1289. }
  1290.  
  1291. return { attr_chatblock, attr_chatcollapsed }
  1292.  
  1293. }
  1294.  
  1295.  
  1296. function mtf_ChatExist() {
  1297.  
  1298. let ytdFlexyElm = kRef(ytdFlexy);
  1299. if (!scriptEnable || !ytdFlexyElm) return;
  1300.  
  1301. if (deferredVarYTDHidden) return;
  1302.  
  1303. const elmChat = document.querySelector('ytd-live-chat-frame#chat')
  1304. let elmCont = null;
  1305. if (elmChat) {
  1306. elmCont = chatFrameElement('yt-live-chat-renderer #continuations')
  1307. }
  1308.  
  1309. const chatBlockR = (elmChat ? 1 : 0) + (elmCont ? 2 : 0)
  1310. if (Q.mtf_chatBlockQ !== chatBlockR) {
  1311. Q.mtf_chatBlockQ = chatBlockR
  1312. let rChatExist = base_ChatExist();
  1313. //console.log(2446, rChatExist)
  1314. if (rChatExist) {
  1315. let { attr_chatblock, attr_chatcollapsed } = rChatExist;
  1316. wAttr(ytdFlexyElm, 'userscript-chatblock', attr_chatblock)
  1317. wAttr(ytdFlexyElm, 'userscript-chat-collapsed', attr_chatcollapsed)
  1318. if(attr_chatblock=='chat-live') _disableComments();
  1319. }
  1320. }
  1321.  
  1322. }
  1323.  
  1324.  
  1325.  
  1326.  
  1327. let lastScrollAt1 = 0;
  1328.  
  1329. function makeBodyScrollByEvt() {
  1330. let ct = Date.now();
  1331. if (ct - lastScrollAt1 < 6) return; // avoid duplicate calling
  1332. lastScrollAt1 = ct;
  1333. window.dispatchEvent(new Event("scroll")); // dispatch Scroll Event to Window for content display
  1334. }
  1335.  
  1336. let lastScrollAt2 = 0;
  1337.  
  1338. function makeBodyScroll() {
  1339. let ct = Date.now();
  1340. if (ct - lastScrollAt2 < 30) return; // avoid over triggering
  1341. lastScrollAt2 = ct;
  1342. requestAnimationFrame(() => {
  1343. window.dispatchEvent(new Event("scroll")); // ask youtube to display content
  1344. })
  1345. }
  1346.  
  1347. //let requestingComments = null
  1348. //let scrollForComments_lastStart = 0;
  1349. /*
  1350. function scrollForComments_TF() {
  1351. let comments = requestingComments;
  1352. if (!comments) return;
  1353. if (comments.hasAttribute('hidden')) {
  1354. window.dispatchEvent(new Event("scroll"));
  1355. } else requestingComments = null;
  1356.  
  1357. }
  1358. function scrollForComments() {
  1359. scrollForComments_TF();
  1360. if (!requestingComments) return;
  1361. requestAnimationFrame(scrollForComments_TF);
  1362. let ct = Date.now();
  1363. if (ct - scrollForComments_lastStart < 60) return;
  1364. scrollForComments_lastStart = ct;
  1365. timeline.setTimeout(scrollForComments_TF, 80);
  1366. timeline.setTimeout(scrollForComments_TF, 240);
  1367. timeline.setTimeout(scrollForComments_TF, 870);
  1368. }
  1369. */
  1370.  
  1371.  
  1372.  
  1373. const mtoCs = { mtoNav: null, mtoBody: null };
  1374.  
  1375.  
  1376. const mtoVs = {}
  1377.  
  1378. const mutation_target_id_list = ['ytp-caption-window-container', 'items', 'button', 'movie_player', 'player-ads', 'hover-overlays', 'replies'];
  1379. const mutation_target_class_list = ['ytp-panel-menu', 'ytp-endscreen-content'];
  1380.  
  1381. function isMtoOverallSkip(dTarget) {
  1382.  
  1383. if (!dTarget || dTarget.nodeType !== 1) return true;
  1384.  
  1385. if (mutation_target_id_list.includes(dTarget.id)) return true;
  1386.  
  1387. for (const c of dTarget.classList) {
  1388. if (mutation_target_class_list.includes(c)) return true;
  1389. }
  1390.  
  1391. return false;
  1392. }
  1393.  
  1394.  
  1395. const mutation_div_id_ignorelist = [
  1396. 'metadata-line',
  1397. 'ytp-caption-window-container',
  1398. 'top-level-buttons-computed',
  1399. 'microformat',
  1400. 'visibility-button',
  1401. 'info-strings',
  1402. 'action-menu',
  1403. 'reply-button-end'
  1404. ];
  1405.  
  1406. const mutation_div_class_ignorelist = [
  1407. 'badge', 'tp-yt-paper-tooltip', 'ytp-autonav-endscreen-upnext-header',
  1408. 'ytp-bound-time-left', 'ytp-bound-time-right', 'ytp-share-icon',
  1409. 'ytp-tooltip-title', 'annotation', 'ytp-copylink-icon', 'ytd-thumbnail',
  1410. 'paper-ripple',
  1411. //caption
  1412. 'captions-text', 'caption-visual-line', 'ytp-caption-segment', 'ytp-caption-window-container',
  1413. //menu
  1414. 'ytp-playlist-menu-button-text',
  1415.  
  1416. 'ytp-bezel-icon', 'ytp-bezel-text',
  1417. 'dropdown-content',
  1418. 'tp-yt-paper-menu-button', 'tp-yt-iron-dropdown',
  1419.  
  1420. 'ytd-metadata-row-renderer', // #content.ytd-metadata-row-renderer inside each of ytd-metadata-row-renderer (ytd-expander)
  1421. 'ytd-engagement-panel-section-list-renderer', // {div#content.style-scope.ytd-engagement-panel-section-list-renderer} inside each of ytd-engagement-panel-section-list-renderer
  1422.  
  1423. 'autocomplete-suggestions' // autocomplete-suggestions
  1424. ];
  1425.  
  1426. const mutation_target_tag_ignorelist = [
  1427. 'ytd-channel-name', 'tp-yt-iron-dropdown', 'tp-yt-paper-tooltip',
  1428. 'tp-yt-paper-listbox', 'yt-img-shadow', 'ytd-thumbnail', 'ytd-video-meta-block',
  1429.  
  1430. 'yt-icon-button', 'tp-yt-paper-button', 'yt-formatted-string', 'yt-icon', 'button', 'paper-ripple',
  1431.  
  1432. 'ytd-player-microformat-renderer',
  1433. 'ytd-engagement-panel-section-list-renderer', 'ytd-engagement-panel-title-header-renderer',
  1434. 'ytd-comment-renderer', 'ytd-menu-renderer', 'ytd-badge-supported-renderer',
  1435. 'ytd-subscribe-button-renderer', 'ytd-subscription-notification-toggle-button-renderer',
  1436. 'ytd-button-renderer', 'ytd-toggle-button-renderer',
  1437. 'yt-pdg-comment-chip-renderer', 'ytd-comment-action-buttons-renderer', 'ytd-comment-thread-renderer',
  1438. 'ytd-compact-radio-renderer', 'ytd-compact-video-renderer',
  1439. 'ytd-video-owner-renderer',
  1440. 'ytd-metadata-row-renderer', //ytd-metadata-row-renderer is part of the #collapsible inside ytd-expander
  1441.  
  1442. 'ytd-moving-thumbnail-renderer',
  1443. 'ytd-thumbnail-overlay-toggle-button-renderer',
  1444. 'ytd-thumbnail-overlay-bottom-panel-renderer', 'ytd-thumbnail-overlay-equalizer',
  1445. 'ytd-thumbnail-overlay-now-playing-renderer', 'ytd-thumbnail-overlay-resume-playback-renderer',
  1446. 'ytd-thumbnail-overlay-side-panel-renderer', 'ytd-thumbnail-overlay-time-status-renderer',
  1447. 'ytd-thumbnail-overlay-hover-text-renderer',
  1448.  
  1449. 'yt-interaction',
  1450. 'tp-yt-paper-spinner-lite', 'tp-yt-paper-spinner',
  1451.  
  1452. 'h1', 'h2', 'h3', 'h4', 'h5', 'span', 'a',
  1453.  
  1454. 'meta', 'br', 'script', 'style', 'link', 'dom-module', 'template'
  1455. ];
  1456.  
  1457. function isMtoTargetSkip(mutation) {
  1458. //skip not important mutation tartget
  1459.  
  1460. if (!mutation) return true;
  1461. let { type, target } = mutation
  1462.  
  1463. if (!target || target.nodeType !== 1 || type != 'childList') return true;
  1464.  
  1465. let tagName = target.nodeName.toLowerCase();
  1466.  
  1467. if (mutation_target_tag_ignorelist.includes(tagName)) return true;
  1468.  
  1469. switch (tagName) {
  1470.  
  1471.  
  1472. case 'ytd-expander':
  1473. if (target.id == 'expander' && Q.comments_section_loaded == 1 && target.classList.contains('ytd-comment-renderer')) return true; // load comments
  1474. return false;
  1475.  
  1476. case 'div':
  1477.  
  1478. if (target.id == 'contents') {
  1479. return false;
  1480. }
  1481. if (mutation_div_id_ignorelist.includes(target.id)) return true;
  1482.  
  1483. for (const c of target.classList) {
  1484. if (mutation_div_class_ignorelist.includes(c)) return true;
  1485. }
  1486.  
  1487. return false;
  1488.  
  1489. }
  1490.  
  1491. return false;
  1492.  
  1493. }
  1494.  
  1495.  
  1496. function mtf_forceCheckLiveVideo() {
  1497. // once per $$player-playback-timestamp$$ {#ytd-player .ytp-time-display} && $$chat-frame$$ {ytd-live-chat-frame#chat} detection
  1498. // reset after popstatechange / videochange
  1499.  
  1500. if(mtf_forceCheckLiveVideo_disable) return ;
  1501.  
  1502. let ytdFlexyElm = kRef(ytdFlexy);
  1503. if (!scriptEnable || !ytdFlexyElm) return ;
  1504.  
  1505. const rootElement = Q.mutationTarget || ytdFlexyElm;
  1506. const playerLabel = document.querySelector('ytd-watch-flexy:not([hidden]) #ytd-player .ytp-time-display') && document.querySelector('ytd-watch-flexy:not([hidden]) ytd-live-chat-frame#chat')
  1507. if (!playerLabel) return ;
  1508. mtf_forceCheckLiveVideo_disable = 1;
  1509. timeline.setTimeout(FP.fireOnce_forceCheckLiveVideo_tf, 170)
  1510. return ;
  1511. }
  1512.  
  1513. // continuous check for element relocation
  1514. function mtf_append_comments() {
  1515.  
  1516. let ytdFlexyElm = kRef(ytdFlexy);
  1517. if (!scriptEnable || !ytdFlexyElm) return;
  1518.  
  1519. const rootElement = Q.mutationTarget || ytdFlexyElm;
  1520.  
  1521. let comments = rootElement.querySelector('#primary ytd-watch-metadata ~ ytd-comments#comments');
  1522. if (comments) $(comments).appendTo('#tab-comments').attr('data-dom-changed-by-tabview-youtube', scriptVersionForExternal)
  1523. }
  1524.  
  1525. // continuous check for element relocation
  1526. function mtf_liveChatBtnF() {
  1527. let ytdFlexyElm = kRef(ytdFlexy);
  1528. if (!scriptEnable || !ytdFlexyElm) return;
  1529.  
  1530. const rootElement = Q.mutationTarget || ytdFlexyElm;
  1531.  
  1532. let button = rootElement.querySelector('ytd-live-chat-frame#chat>.ytd-live-chat-frame#show-hide-button:nth-child(n+2)');
  1533. if (button) button.parentNode.insertBefore(button, button.parentNode.firstChild)
  1534. }
  1535.  
  1536.  
  1537.  
  1538. // continuous check for element relocation
  1539. // fired at begining & window resize, etc
  1540. function mtf_append_playlist() {
  1541.  
  1542. let ytdFlexyElm = kRef(ytdFlexy);
  1543. if (!scriptEnable || !ytdFlexyElm) return;
  1544.  
  1545. const rootElement = Q.mutationTarget || ytdFlexyElm;
  1546.  
  1547. let ple1 = rootElement.querySelector("*:not(#ytd-userscript-playlist)>ytd-playlist-panel-renderer#playlist");
  1548. if (ple1) {
  1549. let ct = Date.now();
  1550. let truePlaylist = null;
  1551. let truePlaylist_items = document.querySelector('ytd-playlist-panel-renderer#playlist #items:not(:empty)');
  1552. if (truePlaylist_items) {
  1553.  
  1554. let pElm = truePlaylist_items.parentNode;
  1555. while (pElm && pElm.nodeType === 1) {
  1556. if (pElm.id == 'playlist') {
  1557. pElm.setAttribute('tabview-true-playlist', ct)
  1558. truePlaylist = pElm;
  1559. break;
  1560. }
  1561. pElm = pElm.parentNode;
  1562. }
  1563.  
  1564. }
  1565.  
  1566. if (!truePlaylist) truePlaylist = ple1; // NOT NULL
  1567.  
  1568. for (const s of document.querySelectorAll(`*:not(#ytd-userscript-playlist)>ytd-playlist-panel-renderer#playlist:not([tabview-true-playlist="${ct}"])`))
  1569. s.parentNode.removeChild(s);
  1570.  
  1571. let $wrapper = getWrapper('ytd-userscript-playlist')
  1572. $wrapper.append(truePlaylist).appendTo(document.querySelector("#tab-list"));
  1573. truePlaylist.setAttribute('data-dom-changed-by-tabview-youtube', scriptVersionForExternal)
  1574. setDisplayedPlaylist(); // relocation after re-layout
  1575.  
  1576. requestAnimationFrame(() => {
  1577. let ytdFlexyElm = kRef(ytdFlexy);
  1578. if (!scriptEnable || !ytdFlexyElm) return;
  1579. if (!switchTabActivity_lastTab && (ytdFlexyElm.getAttribute('tabview-selection') + '').indexOf('#tab-') === 0 && /https\:\/\/www\.youtube\.com\/watch.*[\?\&]list=[\w\-\_]+/.test(location.href)) {
  1580. if (setToActiveTab('#tab-list')) switchTabActivity_lastTab = '#tab-list';
  1581. }
  1582. })
  1583.  
  1584. }
  1585. }
  1586.  
  1587.  
  1588. // content fix - info & playlist
  1589. // fired at begining, and keep for in case any change
  1590. function mtf_fix_details() {
  1591.  
  1592. if (!scriptEnable) return;
  1593.  
  1594. if (no_fix_contents_until < Date.now()) {
  1595. const content = document.querySelector('#meta-contents ytd-expander>#content, #tab-info ytd-expander>#content')
  1596. if (content) {
  1597. no_fix_contents_until = Date.now() + 3000;
  1598. timeline.setTimeout(function() {
  1599. const expander = content.parentNode;
  1600.  
  1601. if (expander.hasAttribute('collapsed')) wAttr(expander, 'collapsed', false);
  1602. expander.style.setProperty('--ytd-expander-collapsed-height', '');
  1603.  
  1604. let btn1 = expander.querySelector('tp-yt-paper-button#less:not([hidden])');
  1605. let btn2 = expander.querySelector('tp-yt-paper-button#more:not([hidden])');
  1606.  
  1607. if (btn1) wAttr(btn1, 'hidden', true);
  1608. if (btn2) wAttr(btn2, 'hidden', true);
  1609. }, 40);
  1610.  
  1611. }
  1612. }
  1613.  
  1614. if (no_fix_playlist_until < Date.now()) {
  1615. // just in case the playlist is collapsed
  1616. const playlist = document.querySelector('#tab-list ytd-playlist-panel-renderer#playlist')
  1617. if (playlist) {
  1618. no_fix_playlist_until = Date.now() + 3000;
  1619. timeline.setTimeout(function() {
  1620. if (playlist.hasAttribute('collapsed')) wAttr(playlist, 'collapsed', false);
  1621. if (playlist.hasAttribute('collapsible')) wAttr(playlist, 'collapsible', false);
  1622. }, 40)
  1623. }
  1624. }
  1625.  
  1626.  
  1627. }
  1628.  
  1629.  
  1630.  
  1631. function isNullComments() {
  1632.  
  1633. let comments = document.querySelector('ytd-comments#comments')
  1634. if (!comments || comments.hasAttribute('hidden')) return true;
  1635.  
  1636. }
  1637.  
  1638.  
  1639. function getFMT(ytdFlexyElm) {
  1640.  
  1641.  
  1642. let fmt = ["ytd-comments#comments #count.ytd-comments-header-renderer", 'ytd-item-section-renderer#sections #header ~ #contents>ytd-message-renderer'].map(s => {
  1643.  
  1644. let elm = ytdFlexyElm.querySelector(s)
  1645. if (!elm) return null;
  1646. return elm.querySelector('yt-formatted-string') || elm.closest('yt-formatted-string') || null
  1647.  
  1648.  
  1649. });
  1650.  
  1651. return fmt
  1652. }
  1653.  
  1654. function toFST(elm) {
  1655. if (!elm) return null;
  1656. return elm.querySelector('yt-formatted-string') || elm.closest('yt-formatted-string') || null
  1657. }
  1658.  
  1659. function _innerCommentsLoader(rootElement) {
  1660.  
  1661. let ytdFlexyElm = kRef(ytdFlexy);
  1662. if (!scriptEnable || !ytdFlexyElm) return;
  1663. if (deferredVarYTDHidden) return;
  1664.  
  1665.  
  1666. let messageElm, messageStr, commentRenderer;
  1667. let diffCSS = `[tabview-cache-time="${sect_hTime}"]`
  1668.  
  1669. if (commentRenderer = rootElement.querySelector(`ytd-comments#comments #count.ytd-comments-header-renderer:not(${diffCSS})`)) {
  1670.  
  1671. scheduledCommentRefresh=false;
  1672. let eTime = Date.now();
  1673.  
  1674. sect_holder = mWeakRef(commentRenderer);
  1675. sect_hText = commentRenderer.textContent;
  1676. sect_hTime = eTime;
  1677. commentRenderer.setAttribute('tabview-cache-time', eTime);
  1678.  
  1679. return {
  1680. status: 1,
  1681. f: () => {
  1682.  
  1683. let span = document.querySelector("span#tab3-txt-loader")
  1684. let r = '0';
  1685. let txt = commentRenderer.textContent
  1686. if (typeof txt == 'string') {
  1687. let m = txt.match(/[\d\,\s]+/)
  1688. if (m) {
  1689. r = m[0].trim()
  1690.  
  1691.  
  1692. }
  1693. }
  1694. if (span){
  1695.  
  1696. let tab_btn = span.closest('.tab-btn[userscript-tab-content="#tab-comments"]')
  1697. if(tab_btn)tab_btn.setAttribute('loaded-comment','normal')
  1698. span.textContent = r;
  1699. }
  1700. //console.log(754)
  1701. mtoInterval = mtoInterval2;
  1702. scheduledCommentRefresh=false;
  1703. setCommentSection(1);
  1704. comments_section_loaded_elm = mWeakRef(document.querySelector('ytd-comments#comments div#contents'))
  1705. }
  1706. }
  1707.  
  1708. } else if ((messageElm = rootElement.querySelector(`ytd-item-section-renderer#sections #header ~ #contents > ytd-message-renderer:not(${diffCSS})`)) && (messageStr = (messageElm.textContent || '').trim())) { //ytd-message-renderer
  1709. // it is possible to get the message before the header generation.
  1710. // sample link - https://www.youtube.com/watch?v=PVUZ8Nvr1ic
  1711. // sample link - https://www.youtube.com/watch?v=yz8AiQc1Bk8
  1712.  
  1713. scheduledCommentRefresh=false;
  1714. let eTime = Date.now();
  1715.  
  1716. sect_holder = mWeakRef(messageElm);
  1717. sect_hText = messageElm.textContent;
  1718. sect_hTime = eTime;
  1719. messageElm.setAttribute('tabview-cache-time', eTime);
  1720.  
  1721. return {
  1722. status: 2,
  1723. f: () => {
  1724. timeline.setTimeout(function() {
  1725. let span = document.querySelector("span#tab3-txt-loader")
  1726. const mainMsg = messageElm.querySelector('#message, #submessage')
  1727. if (mainMsg && mainMsg.textContent) {
  1728. for (const msg of mainMsg.querySelectorAll('*:not(:empty)')) {
  1729. if (msg.childElementCount === 0 && msg.textContent) {
  1730. messageStr = msg.textContent.trim()
  1731. break
  1732. }
  1733. }
  1734. }
  1735. if (span){
  1736. let tab_btn = span.closest('.tab-btn[userscript-tab-content="#tab-comments"]')
  1737. if(tab_btn)tab_btn.setAttribute('loaded-comment','message')
  1738. span.textContent ='\u200B';
  1739. }
  1740. mtoInterval = mtoInterval2;
  1741. scheduledCommentRefresh=false;
  1742. setCommentSection(1);
  1743. comments_section_loaded_elm = mWeakRef(document.querySelector('ytd-comments#comments div#contents'))
  1744. }, 40);
  1745. }
  1746. }
  1747.  
  1748. }
  1749.  
  1750. }
  1751.  
  1752. const atChange = async ()=>{
  1753.  
  1754. // call when video change / popstate change / switch back from mini-view
  1755.  
  1756. let ytdFlexyElm = kRef(ytdFlexy);
  1757. if(!ytdFlexyElm) return;
  1758.  
  1759. mtf_forceCheckLiveVideo_disable = 0;
  1760. mtf_forceCheckLiveVideo();
  1761.  
  1762. if(Q.comments_section_loaded===0){
  1763.  
  1764. const tabBtn = document.querySelector('[userscript-tab-content="#tab-comments"].tab-btn-hidden')
  1765. if(tabBtn) {
  1766. tabBtn.classList.remove("tab-btn-hidden") //if contains
  1767. }
  1768.  
  1769. }
  1770. }
  1771.  
  1772.  
  1773.  
  1774. function hookSection(){
  1775.  
  1776.  
  1777. //console.log(3300)
  1778.  
  1779. let ytdFlexyElm = kRef(ytdFlexy);
  1780. if (!scriptEnable || !ytdFlexyElm) return ;
  1781.  
  1782. const comments = ytdFlexyElm.querySelector('ytd-comments#comments')
  1783. if (!comments) return ;
  1784.  
  1785. if(deferredVarYTDHidden) return;
  1786.  
  1787. //console.log(3310)
  1788.  
  1789. //new MutationObserver(function(){console.log(comments.querySelectorAll('*').length)}).observe(comments,{ attributes: true, childList: true, subtree: true })
  1790.  
  1791. function foundSection(sections){
  1792.  
  1793. //console.log(3320)
  1794.  
  1795. function ulfx() {
  1796. //console.log(1220)
  1797.  
  1798.  
  1799. const rootElement = comments.parentNode
  1800.  
  1801.  
  1802. if (pendingOne && comments.querySelectorAll('[tabview-cache-time]').length > 1) return;
  1803. pendingOne = false;
  1804. if(!rootElement) return; //prevent unknown condition
  1805.  
  1806.  
  1807. let innerCommentsLoaderRet = _innerCommentsLoader(rootElement);
  1808.  
  1809. //console.log(1222,_innerCommentsLoader)
  1810.  
  1811. if (!innerCommentsLoaderRet) {
  1812.  
  1813.  
  1814. let holder = kRef(sect_holder);
  1815. if (holder && comments.contains(holder)) {
  1816.  
  1817. if (sect_lastUpdate) return;
  1818. sect_lastUpdate = 1;
  1819.  
  1820. timeline.setTimeout(function() {
  1821. //console.log(2633)
  1822. if (holder.textContent !== sect_hText && sect_hTime > 0) {
  1823.  
  1824. if (comments.querySelectorAll('[tabview-cache-time]').length === 1) {
  1825.  
  1826. sect_hTime--;
  1827.  
  1828. let innerCommentsLoaderRet = _innerCommentsLoader(rootElement);
  1829.  
  1830. if (innerCommentsLoaderRet) {
  1831. //console.log(9442)
  1832. innerCommentsLoaderRet.f();
  1833. fetchCommentsFinished();
  1834. }
  1835.  
  1836. pendingOne = false;
  1837.  
  1838. }
  1839.  
  1840.  
  1841. }
  1842. sect_lastUpdate = 0
  1843. }, 400)
  1844.  
  1845. } else if (sect_holder) {
  1846. sect_holder = null
  1847. sect_hTime = 0;
  1848. sect_lastUpdate = 0;
  1849. pendingOne = false;
  1850. timeline.setTimeout(()=>{
  1851. if(!deferredVarYTDHidden && scheduledCommentRefresh && Q.comments_section_loaded>0 && comments.hasAttribute('hidden')){
  1852.  
  1853. scheduledCommentRefresh=false; // logic tbc
  1854. resetCommentSection();
  1855. }
  1856. },142);
  1857. Q.mutationTarget=null;
  1858. FP.mtf_attrComments(); // in case for popstate
  1859. }
  1860.  
  1861.  
  1862. } else {
  1863.  
  1864. //console.log(9443)
  1865. innerCommentsLoaderRet.f();
  1866. fetchCommentsFinished();
  1867.  
  1868. pendingOne = true;
  1869.  
  1870. }
  1871.  
  1872.  
  1873.  
  1874. }
  1875.  
  1876. function observerHook(mutationsList, observer){
  1877.  
  1878. let valid = false
  1879. for (const mutation of mutationsList) {
  1880.  
  1881. let target = mutation.target;
  1882. if (!target) break;
  1883. if (!target.id) break;
  1884. let classList = target.classList;
  1885.  
  1886.  
  1887. if (
  1888. classList.contains('ytd-item-section-renderer') ||
  1889. classList.contains('ytd-comments-header-renderer') ||
  1890. classList.contains('ytd-message-renderer')) {
  1891. valid = true
  1892. break;
  1893. }
  1894.  
  1895. break; // only outest mutation
  1896.  
  1897.  
  1898. }
  1899. if (valid) ulfx();
  1900. }
  1901.  
  1902. if(sections.hasAttribute('tabview-comment-section-checked')){
  1903.  
  1904. //console.log(3612)
  1905. ulfx();
  1906.  
  1907. }else{
  1908.  
  1909. pendingOne = false;
  1910. //console.log(3611)
  1911. const observer = new MutationObserver(observerHook)
  1912. sections.setAttribute('tabview-comment-section-checked','')
  1913. const config = { childList: true, subtree: true };
  1914. observer.observe(sections, config);
  1915. ulfx();
  1916.  
  1917.  
  1918. }
  1919.  
  1920.  
  1921.  
  1922. }
  1923. //console.log(3241, location.href)
  1924. let cid_render_section = timeline.setInterval(function() {
  1925.  
  1926. // console.log(3244, location.href)
  1927.  
  1928.  
  1929. const rootElement = comments.parentNode
  1930.  
  1931. let sections = rootElement.querySelector('ytd-comments#comments > ytd-item-section-renderer#sections');
  1932.  
  1933.  
  1934. if (sections) {
  1935.  
  1936. timeline.clearInterval(cid_render_section);
  1937. foundSection(sections);
  1938.  
  1939. }
  1940.  
  1941. }, 30)
  1942.  
  1943.  
  1944.  
  1945. }
  1946.  
  1947.  
  1948. let mtc_store=0;
  1949. let mtc_cid = 0;
  1950. const cssOnceFunc = {
  1951. [`ytd-comments#comments:not([o3r-${sa_comments}])`]:(comments)=>{
  1952. // once per {ytd-comments#comments} detection
  1953.  
  1954. let ytdFlexyElm = kRef(ytdFlexy);
  1955. if (!scriptEnable || !ytdFlexyElm) return;
  1956.  
  1957. if (!comments) return;
  1958.  
  1959.  
  1960.  
  1961.  
  1962. if(mtoVisibility_Comments.bindElement(comments)){
  1963. mtoVisibility_Comments.observer.check(9);
  1964. }
  1965. pageInit_attrComments = true
  1966. hookSection();
  1967.  
  1968. let dComments = comments
  1969. timeline.setTimeout(() => nativeFunc(dComments, "loadComments"), 20)
  1970. comments = null;
  1971.  
  1972. },
  1973. [`ytd-playlist-panel-renderer#playlist:not([o3r-${sa_playlist}])`]:(playlist)=>{
  1974.  
  1975. // once per {ytd-playlist-panel-renderer#playlist} detection
  1976.  
  1977. let ytdFlexyElm = kRef(ytdFlexy);
  1978. if (!scriptEnable || !ytdFlexyElm) return;
  1979.  
  1980. if (!playlist) return;
  1981.  
  1982. if(mtoVisibility_Playlist.bindElement(playlist)){
  1983. mtoVisibility_Playlist.observer.check(9) //delay check required for browser bug - hidden changed not triggered
  1984. }
  1985. playlist = null;
  1986.  
  1987. return;
  1988.  
  1989. },
  1990. '#meta-contents ytd-expander:not([tabview-info-expander])':(expander)=>{
  1991.  
  1992. // once per $$native-info-description$$ {#meta-contents ytd-expander} detection
  1993.  
  1994. let ytdFlexyElm = kRef(ytdFlexy);
  1995. if (!scriptEnable || !ytdFlexyElm) return ;
  1996.  
  1997. if (!expander) return ;
  1998. expander.setAttribute('tabview-info-expander','')
  1999. $(expander).appendTo("#tab-info").attr('data-dom-changed-by-tabview-youtube', scriptVersionForExternal)
  2000.  
  2001. },
  2002. }
  2003. const cssOnce = Object.keys(cssOnceFunc).join(', ')
  2004.  
  2005. const regularCheck = (addP, removeP, mutationTarget)=>{
  2006.  
  2007.  
  2008. let isInvalidAdding = mutationTarget && !mutationTarget.parentNode
  2009.  
  2010. let promisesForAddition = !scriptEnable ? [] : addP > 0 && !isInvalidAdding ? [
  2011.  
  2012. (async () => {
  2013.  
  2014. //$callOnce('mtf_checkFlexy');
  2015.  
  2016. const rootElement = Q.mutationTarget || kRef(ytdFlexy);
  2017.  
  2018. if(rootElement){
  2019.  
  2020. let fElms = rootElement.querySelectorAll(cssOnce)
  2021. if(fElms.length>0){
  2022.  
  2023. for(const fElm of fElms){
  2024. for(let cssSelctor in cssOnceFunc){
  2025. if(fElm.matches(cssSelctor)){
  2026. let f= cssOnceFunc[cssSelctor]
  2027. if(typeof f=='function') f(fElm);
  2028. break;
  2029. }
  2030.  
  2031. }
  2032. }
  2033.  
  2034. //$callOnce('mtf_initalAttr_comments');
  2035. //$callOnce('mtf_initalAttr_playlist');
  2036. // $callOnce('mtf_checkDescriptionLoaded');
  2037. }
  2038.  
  2039. }
  2040.  
  2041. })(),
  2042. (async () => {
  2043.  
  2044.  
  2045. FP.mtf_initalAttr_chatroom();
  2046. FP.mtf_initalAttr_engagement_panel();
  2047. //$callOnce('mtf_initalAttr_chatroom');
  2048. // $callOnce('mtf_initalAttr_engagement_panel');
  2049.  
  2050. mtf_forceCheckLiveVideo();
  2051. mtf_append_comments();
  2052. mtf_liveChatBtnF();
  2053. fixTabs();
  2054. mtf_AfterFixTabs();
  2055. mtf_append_playlist();
  2056.  
  2057. })()
  2058.  
  2059. ] : [];
  2060. let promisesForEveryMutation = !scriptEnable ? [] : [
  2061. (async () => {
  2062. mtf_fix_details();
  2063. mtf_ChatExist();
  2064. })()
  2065. ];
  2066.  
  2067. return [...promisesForAddition, ...promisesForEveryMutation]
  2068.  
  2069.  
  2070. }
  2071.  
  2072. function setOnlyOneEPanel(ePanel){
  2073.  
  2074. layoutStatusMutex.lockWith(unlock => {
  2075.  
  2076. let cPanels = engagement_panels_();
  2077. for (const entry of cPanels.list) {
  2078. if (entry.ePanel != ePanel && entry.visible) ytBtnCloseEngagementPanel(entry.ePanel);
  2079. }
  2080. setTimeout(unlock, 30)
  2081.  
  2082. })
  2083.  
  2084. }
  2085.  
  2086. const FP = {
  2087.  
  2088. mtoNav_delayedF: () => {
  2089.  
  2090. let { addP, removeP, mutationTarget } = Q;
  2091.  
  2092. Q.addP = 0;
  2093. Q.removeP = 0;
  2094. mtc_store=Date.now()+1870
  2095.  
  2096.  
  2097. let regularChecks = regularCheck(addP, removeP, mutationTarget);
  2098.  
  2099. Promise.all(regularChecks).then(() => {
  2100. regularChecks = null;
  2101. mtc_store= Date.now() + 3870 // pending Comments start to load
  2102. Q.mutationTarget = null;
  2103.  
  2104. Q.mtoNav_requestNo--;
  2105. //console.log('motnav reduced to', mtoNav_requestNo)
  2106. if (Q.mtoNav_requestNo > 0) {
  2107. Q.mtoNav_requestNo = 1;
  2108. setTimeout(FP.mtoNav_delayedF, mtoInterval);
  2109. }
  2110. })
  2111.  
  2112.  
  2113. },
  2114.  
  2115. mtoNavF: (mutations, observer) => {
  2116. //subtree DOM mutation checker - {ytd-watch-flexy} \single \subtree
  2117.  
  2118. newVideoPageCheck()
  2119.  
  2120. if (!scriptEnable) return;
  2121. if (deferredVarYTDHidden) return;
  2122.  
  2123. let ch = false;
  2124.  
  2125. let reg = [];
  2126. let dTarget = null;
  2127.  
  2128. let wAddP = 0,
  2129. wRemoveP = 0;
  2130.  
  2131. let _last_mto_target = null;
  2132. let _last_mto_target_valid = null;
  2133.  
  2134. for (const mutation of mutations) {
  2135. if (!mutation || !mutation.target || !mutation.target.parentNode) continue;
  2136.  
  2137. let elementalMutation = false;
  2138. let tAddP = 0,
  2139. tRemoveP = 0;
  2140.  
  2141. for (const addedNode of mutation.addedNodes) { //theoretically faster: only reading of states
  2142. if (addedNode.nodeType === 1) {
  2143. tAddP++;
  2144. elementalMutation = true;
  2145. }
  2146. }
  2147.  
  2148. for (const removedNode of mutation.removedNodes) { //theoretically faster: only reading of states
  2149. if (removedNode.nodeType === 1) {
  2150. tRemoveP++;
  2151. elementalMutation = true;
  2152. }
  2153. }
  2154.  
  2155. if (elementalMutation) { //skip all addition and removal operations without elemental changes (e.g. textNode modification)
  2156.  
  2157. if (_last_mto_target === mutation.target) {
  2158. // due to addition and removal operations to the same DOM
  2159. if (_last_mto_target_valid) {
  2160. // AddP & RemoveP is still valid
  2161. wAddP += tAddP;
  2162. wRemoveP += tRemoveP;
  2163. }
  2164. continue;
  2165. }
  2166. _last_mto_target = mutation.target;
  2167.  
  2168. if (isMtoTargetSkip(mutation)) {
  2169. _last_mto_target_valid = false;
  2170. continue; //theoretically slower: creation of string variables
  2171. } else {
  2172. _last_mto_target_valid = true;
  2173. wAddP += tAddP;
  2174. wRemoveP += tRemoveP;
  2175. }
  2176.  
  2177.  
  2178.  
  2179. ch = true;
  2180.  
  2181. reg.push(mutation);
  2182.  
  2183. if (dTarget === null) dTarget = mutation.target; //first
  2184. else if (dTarget === true) {} //ytdFlexy
  2185. else if (dTarget.contains(mutation.target)) {} //first node is the container to all subsequential targets
  2186. else { dTarget = true; } //the target is not the child of first node
  2187.  
  2188. }
  2189.  
  2190. }
  2191.  
  2192. if (!ch) return; // dTarget must be true OR HTMLElement
  2193.  
  2194. if (dTarget === true) dTarget = kRef(ytdFlexy); // major mutation occurance
  2195. else if (dTarget === kRef(comments_section_loaded_elm) && wAddP > wRemoveP) return true; // ignore if comments are loaded (adding comments)
  2196. else if (isMtoOverallSkip(dTarget)) return; // allow for multiple mutations at the same time - determinated after looping
  2197.  
  2198.  
  2199.  
  2200. // 4 ~ 16 times per full page loading
  2201.  
  2202. Q.addP += wAddP;
  2203. Q.removeP += wRemoveP;
  2204.  
  2205. if (Q.mutationTarget === null) Q.mutationTarget = dTarget;
  2206. else if (Q.mutationTarget != dTarget) Q.mutationTarget = kRef(ytdFlexy);
  2207.  
  2208. //console.log(prettyElm(dTarget), wAddP , wRemoveP, mtoInterval)
  2209. //console.log(prettyElm(dTarget), reg.map(m=>prettyElm(m.target)))
  2210. //console.log(7015, performance.now())
  2211.  
  2212.  
  2213.  
  2214. Q.mtoNav_requestNo++;
  2215. if (Q.mtoNav_requestNo == 1) setTimeout(FP.mtoNav_delayedF, mtoInterval);
  2216.  
  2217. },
  2218.  
  2219.  
  2220. mtoBodyF: function(mutations, observer) {
  2221. //subtree DOM mutation checker - {body} \single \subtree
  2222.  
  2223. if (!scriptEnable) return;
  2224. if (deferredVarYTDHidden) return;
  2225.  
  2226. for (const mutation of mutations) {
  2227. for (const addedNode of mutation.addedNodes)
  2228. if (addedNode.nodeType === 1) {
  2229. if (addedNode.nodeName == "DIV" && addedNode.className.indexOf('autocomplete-suggestions') >= 0) {
  2230. mtf_fixAutoCompletePosition(addedNode)
  2231. }else if(addedNode.nodeName == "DIV" && (addedNode.id==='lyricscontainer' || addedNode.id==='showlyricsbutton')){
  2232.  
  2233. goYoutubeGeniusLyrics();
  2234.  
  2235. }
  2236. }
  2237. }
  2238.  
  2239. },
  2240.  
  2241. mtf_attrPlaylist: (attrName, newValue) => {
  2242. //attr mutation checker - {ytd-playlist-panel-renderer#playlist} \single
  2243. //::attr ~ hidden
  2244. //console.log(1210)
  2245.  
  2246. if (!scriptEnable) return;
  2247. if (deferredVarYTDHidden) return;
  2248. let cssElm = kRef(ytdFlexy);
  2249. if (!cssElm) return;
  2250.  
  2251. let playlist = document.querySelector('ytd-playlist-panel-renderer#playlist')
  2252. const tabBtn = document.querySelector('[userscript-tab-content="#tab-list"]');
  2253. let isPlaylistHidden = playlist.hasAttribute('hidden')
  2254. //console.log(1212.2, isPlaylistHidden, playlist.getAttribute('hidden'))
  2255. if (tabBtn) {
  2256. //console.log('attr playlist changed')
  2257. if (tabBtn.classList.contains('tab-btn-hidden') && !isPlaylistHidden) {
  2258. //console.log('attr playlist changed - no hide')
  2259. tabBtn.classList.remove("tab-btn-hidden");
  2260. } else if (!tabBtn.classList.contains('tab-btn-hidden') && isPlaylistHidden) {
  2261. //console.log('attr playlist changed - add hide')
  2262. hideTabBtn(tabBtn);
  2263. }
  2264. }
  2265. /* visible layout for triggering hidden removal */
  2266. akAttr(cssElm, 'tabview-youtube-playlist', isPlaylistHidden);
  2267. },
  2268. mtf_attrComments: (attrName, newValue) => {
  2269. //attr mutation checker - {ytd-comments#comments} \single
  2270. //::attr ~ hidden
  2271.  
  2272. let ytdFlexyElm = kRef(ytdFlexy);
  2273. if (!scriptEnable || !ytdFlexyElm) return;
  2274. if (deferredVarYTDHidden) return;
  2275.  
  2276. let comments = document.querySelector('ytd-comments#comments')
  2277. const tabBtn = document.querySelector('[userscript-tab-content="#tab-comments"]');
  2278. if (!comments || !tabBtn) return;
  2279. let isCommentHidden = comments.hasAttribute('hidden')
  2280. //console.log('attr comments changed')
  2281.  
  2282. mtoInterval = mtoInterval1;
  2283. mtc_store=Date.now()+2870
  2284.  
  2285. if( mtf_forceCheckLiveVideo_disable === 2 ){
  2286.  
  2287. }else if (!isCommentHidden) {
  2288.  
  2289. akAttr(ytdFlexyElm, 'tabview-youtube-comments', false, 'K');
  2290.  
  2291.  
  2292.  
  2293. //console.log('attr comments changed - no hide')
  2294. tabBtn.classList.remove("tab-btn-hidden") //if contains
  2295.  
  2296. //console.log(703)
  2297.  
  2298. mtc_cid&&timeline.clearInterval(mtc_cid);
  2299.  
  2300. } else if (isCommentHidden) {
  2301.  
  2302. akAttr(ytdFlexyElm, 'tabview-youtube-comments', true, 'K');
  2303. timeline.setTimeout(function(){
  2304. if(!deferredVarYTDHidden && scheduledCommentRefresh && Q.comments_section_loaded>0 && comments.hasAttribute('hidden')){
  2305. //console.log(3434)
  2306. scheduledCommentRefresh=false; //logic tbc
  2307. resetCommentSection();
  2308. //console.log(8022)
  2309. }
  2310. },142)
  2311.  
  2312. if(!mtc_cid) mtc_cid=timeline.setInterval(()=>{
  2313. if(mtc_store>Date.now()) return;
  2314. timeline.clearInterval(mtc_cid)
  2315. mtc_cid=0;
  2316. if(mtf_forceCheckLiveVideo_disable===2)return;
  2317. if(isNullComments()) _disableComments();
  2318.  
  2319. },80)
  2320.  
  2321. }
  2322.  
  2323.  
  2324. },
  2325.  
  2326.  
  2327. mtf_attrChatroom: (attrName, newValue) => {
  2328. //attr mutation checker - {ytd-live-chat-frame#chat} \single
  2329. //::attr ~ collapsed
  2330.  
  2331. let ytdFlexyElm = kRef(ytdFlexy);
  2332. if (!scriptEnable || !ytdFlexyElm) return;
  2333. if (deferredVarYTDHidden) return;
  2334.  
  2335. layoutStatusMutex.lockWith(unlock => {
  2336.  
  2337. const chatBlock = document.querySelector('ytd-live-chat-frame#chat')
  2338. const cssElm = kRef(ytdFlexy)
  2339.  
  2340. if (!chatBlock || !cssElm) {
  2341. unlock();
  2342. return;
  2343. }
  2344.  
  2345. if (deferredVarYTDHidden) {
  2346. unlock();
  2347. return;
  2348. }
  2349.  
  2350. if (!cssElm.hasAttribute('userscript-chatblock')) wAttr(cssElm, 'userscript-chatblock', true);
  2351. let isCollapsed = !!chatBlock.hasAttribute('collapsed');
  2352. wAttr(cssElm, 'userscript-chat-collapsed', isCollapsed);
  2353.  
  2354. if (cssElm.hasAttribute('userscript-chatblock') && !isCollapsed) lastShowTab = '#chatroom';
  2355.  
  2356. if (!isCollapsed && document.querySelector('#right-tabs .tab-btn.active') && isWideScreenWithTwoColumns() && !isTheater()) {
  2357. switchTabActivity(null);
  2358. timeline.setTimeout(unlock, 40);
  2359. } else {
  2360. unlock();
  2361. }
  2362.  
  2363. if (!isCollapsed) {
  2364. runAfterExpandChat();
  2365. } else {
  2366. chatBlock.removeAttribute('yt-userscript-iframe-loaded');
  2367. }
  2368.  
  2369. })
  2370.  
  2371.  
  2372.  
  2373.  
  2374. },
  2375.  
  2376. mtf_attrEngagementPanel: (mutations, observer) => {
  2377. //attr mutation checker - {ytd-engagement-panel-section-list-renderer} \mutiple
  2378. //::attr ~ visibility
  2379.  
  2380. let ytdFlexyElm = kRef(ytdFlexy);
  2381. if (!scriptEnable || !ytdFlexyElm) return;
  2382.  
  2383. //multiple instance
  2384. if (mutations) {
  2385. for (const mutation of mutations) {
  2386. let ePanel = mutation.target;
  2387. if (ePanel.getAttribute('visibility') == 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED') {
  2388. setOnlyOneEPanel(ePanel);
  2389. break;
  2390. }
  2391. }
  2392. }
  2393. if (deferredVarYTDHidden) return;
  2394.  
  2395. layoutStatusMutex.lockWith(unlock => {
  2396.  
  2397. const ePanel = document.querySelector('ytd-watch-flexy ytd-engagement-panel-section-list-renderer')
  2398. const cssElm = kRef(ytdFlexy)
  2399.  
  2400. if (!ePanel || !cssElm) {
  2401. unlock();
  2402. return;
  2403. }
  2404. let previousValue = +cssElm.getAttribute('userscript-engagement-panel') || 0;
  2405.  
  2406. let { shownRes, value, count } = engagement_panels_();
  2407. let nextValue = value;
  2408. let nextCount = count;
  2409.  
  2410.  
  2411. if (nextCount == 0 && cssElm.hasAttribute('userscript-engagement-panel')) {
  2412. storeLastPanel=null;
  2413. wAttr(cssElm, 'userscript-engagement-panel', false);
  2414. unlock();
  2415. } else {
  2416.  
  2417. if ((nextCount > 1) || (cssElm.hasAttribute('userscript-engagement-panel') && previousValue === nextValue)) {
  2418. unlock();
  2419. return;
  2420. }
  2421.  
  2422. cssElm.setAttribute('userscript-engagement-panel', nextValue);
  2423.  
  2424. let b = false;
  2425. if (previousValue != nextValue && nextValue > 0) {
  2426. lastShowTab = `#engagement-panel-${nextValue}`;
  2427. b = true;
  2428. storeLastPanel = mWeakRef( shownRes[0])
  2429. //console.log(9999, shownRes[0])
  2430. }
  2431.  
  2432. if (b && document.querySelector('#right-tabs .tab-btn.active') && isWideScreenWithTwoColumns() && !isTheater()) {
  2433. switchTabActivity(null);
  2434. timeline.setTimeout(unlock, 40);
  2435. } else {
  2436. unlock();
  2437. }
  2438. }
  2439.  
  2440. })
  2441.  
  2442.  
  2443.  
  2444.  
  2445. },
  2446.  
  2447. mtf_initalAttr_playlist: () => {
  2448. },
  2449.  
  2450. mtf_initalAttr_comments: () => {
  2451. },
  2452.  
  2453. mtf_initalAttr_chatroom: () => {
  2454. // every per [new] {ytd-live-chat-frame#chat} detection - reset after mini-playview
  2455. let ytdFlexyElm = kRef(ytdFlexy);
  2456. if (!scriptEnable || !ytdFlexyElm) return true;
  2457.  
  2458. const rootElement = Q.mutationTarget || ytdFlexyElm;
  2459.  
  2460.  
  2461. if (!mgChatFrame.inPage()) {
  2462.  
  2463. mtoVisibility_Chatroom.clear(true);
  2464.  
  2465. let chatroom = rootElement.querySelector(`ytd-live-chat-frame#chat:not([${sa_chatroom}]`)
  2466. if (chatroom) {
  2467. mgChatFrame.setVar(chatroom);
  2468.  
  2469. if(mtoVisibility_Chatroom.bindElement(chatroom)){
  2470. mtoVisibility_Chatroom.observer.check(9)
  2471. }
  2472.  
  2473. chatroom = null
  2474. }
  2475. }
  2476. return true;
  2477.  
  2478. },
  2479.  
  2480. mtf_initalAttr_engagement_panel: () => {
  2481. // every per $$non-checked-section-list$$ {ytd-watch-flexy ytd-engagement-panel-section-list-renderer:not([o3r-XXXXX])} detection
  2482. let ytdFlexyElm = kRef(ytdFlexy);
  2483. if (!scriptEnable || !ytdFlexyElm) return true;
  2484.  
  2485. const rootElement = Q.mutationTarget || ytdFlexyElm;
  2486.  
  2487. let toCheck = false;
  2488. for (const engagement_panel of rootElement.querySelectorAll(
  2489. `ytd-watch-flexy ytd-engagement-panel-section-list-renderer:not([o3r-${sa_epanel}])`
  2490. )) {
  2491.  
  2492.  
  2493.  
  2494. let bindRes = mtoVisibility_EngagementPanel.bindElement(engagement_panel, {
  2495. attributes: true,
  2496. attributeFilter: ['visibility'],
  2497. attributeOldValue: true
  2498. })
  2499.  
  2500. if(bindRes) toCheck = true;
  2501. }
  2502.  
  2503. if (toCheck) FP.mtf_attrEngagementPanel()
  2504.  
  2505. return true;
  2506. },
  2507.  
  2508.  
  2509.  
  2510. //live-chat / chat-replay
  2511.  
  2512. fireOnce_forceCheckLiveVideo_tf: () => {
  2513. // once per $$player-playback-timestamp$$ {#ytd-player .ytp-time-display} && $$chat-frame$$ {ytd-live-chat-frame#chat} detection
  2514.  
  2515. let ytdFlexyElm = kRef(ytdFlexy);
  2516. if (!scriptEnable || !ytdFlexyElm) return;
  2517.  
  2518. let elm = document.querySelector('#ytd-player .ytp-time-display');
  2519. if (elm && elm.classList.contains('ytp-live')) {
  2520. //console.log(7006)
  2521. ytdFlexyElm.setAttribute('userscript-chatblock', 'chat-live')
  2522. //console.log(2441)
  2523. mtf_forceCheckLiveVideo_disable = 2;
  2524. _disableComments();
  2525. //console.log(701)
  2526. //disableComments_LiveChat();
  2527. }
  2528. },
  2529.  
  2530.  
  2531. //comments
  2532. /*
  2533. mtf_advancedComments: () => {
  2534. // once per {ytd-comments#comments #continuations} detection
  2535.  
  2536. //let ytdFlexyElm = kRef(ytdFlexy);
  2537. //if (!scriptEnable || !ytdFlexyElm) return true;
  2538.  
  2539. //const rootElement = Q.mutationTarget || ytdFlexyElm;
  2540. //const continuations = document.querySelector("ytd-comments#comments #continuations");
  2541. //if (!continuations) return true;
  2542. //requestingComments = document.querySelector('ytd-comments#comments');
  2543. //scrollForComments();
  2544. return false;
  2545. }
  2546. */
  2547.  
  2548.  
  2549. }
  2550.  
  2551.  
  2552.  
  2553.  
  2554.  
  2555. let displayedPlaylist = null;
  2556. let scrollingVideosList = null;
  2557.  
  2558. let scriptEnable = false;
  2559. let scriptEC = 0;
  2560. let lastShowTab = null;
  2561.  
  2562. let _cachedLastVideo = null;
  2563. let videoListBeforeSearch = null;
  2564. let no_fix_contents_until = 0;
  2565. let no_fix_playlist_until = 0;
  2566. let statusCollasped = 0;
  2567.  
  2568. let ytdFlexy = null;
  2569.  
  2570. function pluginUnhook() {
  2571. _pluginUnhook()
  2572.  
  2573. resetCommentSection();
  2574.  
  2575. }
  2576.  
  2577. function _pluginUnhook() {
  2578.  
  2579. sect_lastUpdate = 0;
  2580. sect_holder = null;
  2581. sect_hTime = 0;
  2582.  
  2583. //console.log(8001)
  2584.  
  2585. videoListBeforeSearch = null;
  2586. statusCollasped = 0;
  2587. _cachedLastVideo = null;
  2588. lastShowTab = null;
  2589. displayedPlaylist = null;
  2590. scrollingVideosList = null;
  2591. scriptEnable = false;
  2592. scriptEC++;
  2593. if (scriptEC > 788888888) scriptEC = 188888888;
  2594. ytdFlexy = null;
  2595. wls.layoutStatus = null;
  2596.  
  2597. //console.log('unc01')
  2598.  
  2599. mtoVisibility_EngagementPanel.clear(true)
  2600. mtoVisibility_Playlist.clear(true)
  2601. mtoVisibility_Comments.clear(true)
  2602.  
  2603. mgChatFrame.kVar = null;
  2604. mtoVisibility_Chatroom.clear(true)
  2605. mtoFlexyAttr.clear(true)
  2606.  
  2607.  
  2608. for (const elem of document.querySelectorAll(
  2609. ['ytd-expander[tabview-info-expander]'].join(', ')
  2610. )) {
  2611. elem.removeAttribute('tabview-info-expander');
  2612. }
  2613.  
  2614. mtoMutation_body.clear(true)
  2615. mtoMutation_watchFlexy.clear(true)
  2616. //FOnce.mtf_checkFlexy = null;
  2617. //FOnce.mtf_initalAttr_comments = null;
  2618. //FOnce.mtf_initalAttr_playlist = null;
  2619. //FOnce.mtf_initalAttr_chatroom = null;
  2620. //FOnce.mtf_initalAttr_engagement_panel = null;
  2621. //FOnce.mtf_advancedComments = null;
  2622. //FOnce.mtf_checkDescriptionLoaded = null;
  2623. //FOnce.mtf_forceCheckLiveVideo = null;
  2624. Q.mtf_chatBlockQ = null;
  2625.  
  2626.  
  2627. mtoInterval = mtoInterval1;
  2628.  
  2629. }
  2630.  
  2631. let comments_section_loaded_elm = null;
  2632.  
  2633.  
  2634.  
  2635. function getTabsHTML() {
  2636.  
  2637. const sTabBtnVideos = `${svgElm(16,16,298,298,svgVideos)}<span>Videos</span>`
  2638. const sTabBtnInfo = `${svgElm(16,16,23.625,23.625,svgInfo)}<span>Info</span>`
  2639. const sTabBtnPlayList = `${svgElm(16,16,426.667,426.667,svgPlayList)}<span>Playlist</span>`
  2640.  
  2641. let str1 = `
  2642. <paper-ripple class="style-scope yt-icon-button">
  2643. <div id="background" class="style-scope paper-ripple" style="opacity:0;"></div>
  2644. <div id="waves" class="style-scope paper-ripple"></div>
  2645. </paper-ripple>
  2646. `;
  2647.  
  2648. const str_tabs = [
  2649. `<a id="tab-btn1" data-name="info" userscript-tab-content="#tab-info" class="tab-btn">${sTabBtnInfo}${str1}</a>`,
  2650. `<a id="tab-btn3" userscript-tab-content="#tab-comments" data-name="comments" class="tab-btn">${svgElm(16,16,60,60,svgComments)}<span id="tab3-txt-loader"></span>${str1}</a>`,
  2651. `<a id="tab-btn4" userscript-tab-content="#tab-videos" data-name="videos" class="tab-btn">${sTabBtnVideos}${str1}</a>`,
  2652. `<a id="tab-btn5" userscript-tab-content="#tab-list" class="tab-btn">${sTabBtnPlayList}${str1}</a>`
  2653. ].join('')
  2654.  
  2655. let addHTML = `
  2656. <div id="right-tabs">
  2657. <header>
  2658. <div id="material-tabs">
  2659. ${str_tabs}
  2660. </div>
  2661. </header>
  2662. <div class="tab-content">
  2663. <div id="tab-info" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2664. <div id="tab-comments" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2665. <div id="tab-videos" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2666. <div id="tab-list" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2667. </div>
  2668. </div>
  2669. `;
  2670.  
  2671. return addHTML
  2672.  
  2673. }
  2674.  
  2675.  
  2676. function injection_script_1() {
  2677.  
  2678. document.addEventListener('userscript-call-dom-func', function (evt) {
  2679. if (!evt || !evt.target || !evt.detail) return;
  2680. let dom = evt.target;
  2681. let { property, args } = evt.detail;
  2682. if (!property) return;
  2683. let f = dom[property];
  2684. if (typeof f != 'function') return;
  2685. if (args && args.length > 0) f.apply(dom, args);
  2686. else f.call(dom);
  2687. }, true)
  2688. /*
  2689. document.addEventListener('userscript-call-dom-value', function(evt) {
  2690. if (!evt || !evt.target || !evt.detail) return;
  2691. let dom = evt.target;
  2692. let { property, args } = evt.detail;
  2693. if (!property) return;
  2694. dom[property] = args;
  2695. }, true)
  2696. */
  2697. document.addEventListener('userscript-call-dom-func-stacked', function (evt) {
  2698. if (!evt || !evt.target || !evt.detail) return;
  2699. let document = evt.target;
  2700. let { selector, property, args } = evt.detail;
  2701. if (!selector || !property) return;
  2702. let elements = document.querySelectorAll(selector);
  2703. for (const element of elements) {
  2704. let f = element[property];
  2705. if (typeof f != 'function') return;
  2706. if (args && args.length > 0) f.apply(element, args);
  2707. else f.call(element);
  2708. }
  2709. }, false)
  2710. /*
  2711. document.addEventListener('userscript-call-dom-value-stacked', function(evt) {
  2712. if (!evt || !evt.target || !evt.detail) return;
  2713. let document = evt.target;
  2714. let { selector, property, args } = evt.detail;
  2715. if (!selector||!property) return;
  2716. let elements = document.querySelectorAll(selector);
  2717. for(const element of elements){
  2718. element[property] = args;
  2719. }
  2720. }, false)
  2721. document.addEventListener('userscript-call-dom-const-stacked', function(evt) {
  2722. if (!evt || !evt.target || !evt.detail) return;
  2723. let document = evt.target;
  2724. let { selector, property, args } = evt.detail;
  2725. if (!selector||!property) return;
  2726. let elements = document.querySelectorAll(selector);
  2727. const singleConfig = {
  2728. get(){return args;},
  2729. set(nv){},
  2730. enumerable: true,
  2731. configurable: true
  2732. }
  2733. console.log(453,elements)
  2734. for(const element of elements){
  2735. Object.defineProperty(element, property, singleConfig );
  2736. }
  2737. }, false)
  2738. */
  2739. let s1 = Symbol();
  2740. //let s2 = Symbol();
  2741. // note: after content expanded, resizing window will casue the "collapse" feature disappears.
  2742. // this.$.content.scrollHeight>this.collapsedHeight is used for recomputeOnResize = true
  2743. //let f1= function(){this.canToggle=!this.recomputeOnResize&&this.shouldUseNumberOfLines?this.alwaysToggleable||this.$.content.offsetHeight<this.$.content.scrollHeight:this.alwaysToggleable||this.$.content.scrollHeight>this.collapsedHeight};
  2744. // new browser - 84>80 would not lead to line clamp [as using -webkit-line-clamp]
  2745. // this.$.content.offsetHeight<this.$.content.scrollHeight is not working for collapsed content
  2746. let f1 = function () { this.canToggle = this.shouldUseNumberOfLines && (this.alwaysCollapsed || this.collapsed) ? this.alwaysToggleable || this.$.content.offsetHeight < this.$.content.scrollHeight : this.alwaysToggleable || this.$.content.scrollHeight > this.collapsedHeight };
  2747. let insObserver = null;
  2748. if (window.IntersectionObserver) {
  2749. insObserver = new IntersectionObserver(function (entries, observer) {
  2750. let count = 0
  2751. for (const entry of entries) {
  2752. if (entry.isIntersecting && ++count) entry.target.calculateCanCollapse();
  2753. }
  2754. }, {
  2755. threshold: [0]
  2756. })
  2757. }
  2758. Object.defineProperty(HTMLElement.prototype, 'recomputeOnResize', {
  2759. get() {
  2760. if (this.calculateCanCollapse !== f1) {
  2761. this.calculateCanCollapse = f1
  2762. if (insObserver) insObserver.observe(this)
  2763. }
  2764. return this[s1];
  2765. },
  2766. set(nv) {
  2767. if (this.calculateCanCollapse !== f1) {
  2768. this.calculateCanCollapse = f1
  2769. if (insObserver) insObserver.observe(this)
  2770. }
  2771. if (nv === false) nv = true;
  2772. this[s1] = nv;
  2773. },
  2774. enumerable: false,
  2775. configurable: true
  2776. });
  2777. let calledOnce = false;
  2778. document.documentElement.addEventListener('engagement-panel-genius-lyrics', function () {
  2779. function getEPC(ep) {
  2780. if (!ep) return null;
  2781. let epc = ep.querySelector('#content');
  2782. if (!epc) return null;
  2783. let epc1 = epc.querySelector('ytd-ads-engagement-panel-content-renderer #content')
  2784. let epc2 = epc.querySelector('ytd-ads-engagement-panel-content-renderer')
  2785. return epc1 || epc2 || epc;
  2786. }
  2787. if (calledOnce) return;
  2788. calledOnce = true
  2789. // ENGAGEMENT_PANEL_VISIBILITY_EXPANDED
  2790.  
  2791. function createPanel(){
  2792.  
  2793.  
  2794.  
  2795. /** @type {HTMLElement} */
  2796. let newPanel = ytdFlexyElm.createComponent_({
  2797. "component": "ytd-engagement-panel-section-list-renderer",
  2798. "params": {
  2799. "isWatch": true
  2800. }
  2801. }, "ytd-engagement-panel-section-list-renderer", true);
  2802. newPanel.data = {
  2803. "panelIdentifier": "engagement-panel-genius-transcript",
  2804. "header": {
  2805. "engagementPanelTitleHeaderRenderer": {
  2806. "title": {
  2807. "runs": [
  2808. {
  2809. "text": "Genius Lyrics"
  2810. }
  2811. ]
  2812. },
  2813. "visibilityButton": {
  2814. "buttonRenderer": {
  2815. "icon": {
  2816. "iconType": "CLOSE"
  2817. },
  2818. "accessibility": {
  2819. "label": "Close Genius Lyrics"
  2820. },
  2821. "accessibilityData": {
  2822. "accessibilityData": {
  2823. "label": "Close Genius Lyrics"
  2824. }
  2825. },
  2826. "command": {
  2827. "changeEngagementPanelVisibilityAction": {
  2828. "targetId": "engagement-panel-genius-transcript",
  2829. "visibility": "ENGAGEMENT_PANEL_VISIBILITY_HIDDEN"
  2830. }
  2831. }
  2832. }
  2833. }
  2834. }
  2835. },
  2836. "content": {
  2837. "adsEngagementPanelContentRenderer": {
  2838. //"engagementPanelGeniusTranscriptRenderer":{
  2839. }
  2840. },
  2841. "targetId": "engagement-panel-genius-transcript",
  2842. "visibility": "ENGAGEMENT_PANEL_VISIBILITY_HIDDEN",
  2843. "loggingDirectives": {
  2844. }
  2845. }
  2846. newPanel.classList.add('style-scope','ytd-watch-flexy')
  2847. ytdFlexyElm.querySelector('#panels').appendChild(newPanel)
  2848.  
  2849. return newPanel;
  2850.  
  2851.  
  2852. }
  2853. let count = 0;
  2854. setTimeout(function $f() {
  2855. if (++count > 30) return;
  2856. let ytdFlexyElm = document.querySelector('ytd-watch-flexy[tabview-selection]');
  2857. if (!ytdFlexyElm) return setTimeout($f, 100);
  2858. function closeBtn() {
  2859. let hideBtns = null;
  2860. if (hideBtns = document.querySelectorAll('#lyricscontainer > .lyricsnavbar > a')) {
  2861. for (const hideBtn of hideBtns) {
  2862. if (/\bhide\b/i.test(hideBtn.textContent)) return hideBtn;
  2863. }
  2864. }
  2865. }
  2866.  
  2867. let visObserver = new MutationObserver(function (mutations, observer) {
  2868. if (!mutations || !mutations[0]) return;
  2869. let panel = mutations[0].target;
  2870. if (!panel) return;
  2871. //console.log(panel)
  2872. setTimeout(function () {
  2873. if (panel.getAttribute('visibility') === 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN') {
  2874. let hideBtn = closeBtn();
  2875. if (hideBtn) hideBtn.dispatchEvent(new Event("click"));
  2876. }
  2877. }, 22);
  2878. })
  2879. new MutationObserver(function () {
  2880. let panel = document.querySelector('ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-genius-transcript"]')
  2881. if (panel) {
  2882. let lyricscontainer = document.querySelector('body > #lyricscontainer')
  2883. if (!lyricscontainer && panel.getAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED')) {
  2884. panel.setAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN')
  2885. }
  2886. }
  2887. }).observe(document.body, {
  2888. childList: true
  2889. })
  2890. setInterval(function () {
  2891.  
  2892.  
  2893. if(!document.querySelector('ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-genius-transcript"]')){
  2894. let newPanel = createPanel();
  2895.  
  2896. visObserver.observe(newPanel, {
  2897. attributes: true
  2898. })
  2899. }
  2900.  
  2901.  
  2902. let elm = null;
  2903. if (elm = document.querySelector('body>#lyricscontainer>#lyricsiframe')) {
  2904.  
  2905. let panel = document.querySelector('ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-genius-transcript"]')
  2906. if(!panel) return;
  2907. let epc = getEPC(panel);
  2908. if(!epc) return;
  2909. epc.innerHTML = '';
  2910. epc.appendChild(elm)
  2911. panel.setAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED')
  2912. }
  2913. }, 400)
  2914. }, 100)
  2915. })
  2916. document.documentElement.setAttribute('w-engagement-panel-genius-lyrics', '')
  2917. function removeChildren(cssSelector, parentNode) {
  2918. let elements = parentNode.querySelectorAll(cssSelector);
  2919. let fragment = document.createDocumentFragment();
  2920. fragment.textContent = ' ';
  2921. fragment.firstChild.replaceWith(...elements);
  2922. }
  2923. function removeDOMs(elements) {
  2924. let fragment = document.createDocumentFragment();
  2925. fragment.textContent = ' ';
  2926. fragment.firstChild.replaceWith(...elements);
  2927. }
  2928. function chain(elm, ...args) {
  2929. if (!elm) return;
  2930. for (const s of args) {
  2931. let tmp = elm[s]
  2932. if (!tmp) return tmp || null
  2933. elm = tmp;
  2934. }
  2935. return elm;
  2936. }
  2937. //https://www.youtube.com/watch?v=Ud73fm4Uoq0
  2938. function _snippetText(str) {
  2939. return str.replace(/\u200b/g, '').replace(/[\xA0\x20]/g, ' ')
  2940. }
  2941. function snippetText(snippet) {
  2942. let res = [];
  2943. for (const s of snippet.runs) {
  2944. res.push(_snippetText(s.text))
  2945. }
  2946. return res.join('\n');
  2947. }
  2948. const s7 = Symbol();
  2949. const s8 = Symbol();
  2950. function translate(initialSegments) {
  2951. if (!initialSegments) return initialSegments;
  2952. let res = {};
  2953. let map = new WeakSet();
  2954. let subTexts = {};
  2955. for (const initialSegment of initialSegments) {
  2956. if (!initialSegment || !initialSegment.transcriptSegmentRenderer) continue;
  2957. let startMs = (+initialSegment.transcriptSegmentRenderer.startMs || 0)
  2958. let endMs = (+initialSegment.transcriptSegmentRenderer.endMs || 0)
  2959. if (startMs === endMs) {
  2960. // effect text
  2961. // https://www.youtube.com/watch?v=Ud73fm4Uoq0
  2962. map.add(initialSegment)
  2963. let text = snippetText(initialSegment.transcriptSegmentRenderer.snippet);
  2964. if (!subTexts[startMs]) subTexts[startMs] = [];
  2965. if (!subTexts[startMs].includes(text)) subTexts[startMs].push(text);
  2966. continue;
  2967. }
  2968. let text = snippetText(initialSegment.transcriptSegmentRenderer.snippet);
  2969. if (/^[\,\.\x60\x27\x22\u200b\xA0\x20\;\-]*$/.test(text)) {
  2970. initialSegment[s8] = true;
  2971. //effect only
  2972. // https://www.youtube.com/watch?v=zLak0dxBKpM
  2973. map.add(initialSegment)
  2974. continue;
  2975. }
  2976. if (!res[text]) res[text] = [];
  2977. res[text].push({
  2978. startMs,
  2979. endMs,
  2980. initialSegment
  2981. })
  2982. }
  2983. function trim2(str) {
  2984. return str.replace(/^[\xA0\u200b\s\n\t]+|[\xA0\u200b\s\n\t]+$/g, '');
  2985. }
  2986. function ns2(str) {
  2987. return str.replace(/[\xA0\u200b\s\n\t]+/g, '');
  2988. }
  2989. for (const text of Object.keys(res)) {
  2990. let lastEntry = null
  2991. for (const entry of res[text]) {
  2992. if (lastEntry && entry) {
  2993. if (entry.startMs - lastEntry.endMs < 5 && entry.startMs >= lastEntry.endMs) {
  2994. lastEntry.endMs = entry.endMs
  2995. lastEntry.initialSegment.transcriptSegmentRenderer.endMs = entry.initialSegment.transcriptSegmentRenderer.endMs
  2996. map.add(entry.initialSegment);
  2997. continue;
  2998. }
  2999. }
  3000. lastEntry = entry
  3001. }
  3002. }
  3003. let fRes = initialSegments.filter(m => !map.has(m));
  3004. for (const segment of fRes) {
  3005. let main_startMs = (+segment.transcriptSegmentRenderer.startMs || 0)
  3006. let main_endMs = (+segment.transcriptSegmentRenderer.endMs || 0)
  3007. segment.transcriptSegmentRenderer[s7] = {};
  3008. for (const initialSegment of initialSegments) {
  3009. if (initialSegment[s8]) continue;
  3010. let startMs = (+initialSegment.transcriptSegmentRenderer.startMs || 0)
  3011. let endMs = (+initialSegment.transcriptSegmentRenderer.endMs || 0)
  3012. if (startMs >= main_startMs && endMs <= main_endMs) {
  3013. let mt = snippetText(initialSegment.transcriptSegmentRenderer.snippet);
  3014. segment.transcriptSegmentRenderer[s7][mt] = (segment.transcriptSegmentRenderer[s7][mt] || 0) + 1 + Math.abs(endMs - startMs);
  3015. }
  3016. }
  3017. }
  3018. for (const segment of fRes) {
  3019. let snippet = segment.transcriptSegmentRenderer.snippet
  3020. let main_str = trim2(_snippetText(snippet.runs[0].text));
  3021. snippet.runs[0].text = main_str
  3022. if (snippet.runs.length > 1) continue; // skip multi lines
  3023. let obj = segment.transcriptSegmentRenderer[s7]
  3024. let rg = Object.keys(obj)
  3025. if (rg.length <= 1) continue; // no second line
  3026. rg = rg.map(k => ({ str: k, count: obj[k] }))
  3027. rg.sort((a, b) => b.count - a.count)
  3028. if (rg[1].count > 4 && ns2(rg[1].str) !== ns2(rg[0].str) && rg[1].count > rg.slice(2).map(e => e.count).reduce((a, b) => a + b, 0)) {
  3029. let a = trim2(rg[0].str)
  3030. let b = main_str
  3031. //console.log(452, a,b,a===b)
  3032. if (a === b) snippet.runs.push({ text: trim2(rg[1].str) })
  3033. }
  3034. }
  3035. //console.log(fRes)
  3036. //console.log(fRes)
  3037. return fRes;
  3038. }
  3039. const s6 = Symbol();
  3040. Object.defineProperty(customElements.get('ytd-transcript-search-panel-renderer').prototype, 'initialTranscriptsRenderer', {
  3041. get() {
  3042. return this.__$$initialTranscriptsRenderer$$__
  3043. },
  3044. set(nv) {
  3045. try {
  3046. if (nv && nv.initialSegments && !nv.initialSegments[s6]) {
  3047. nv[s6] = true;
  3048. //console.log(343,JSON.parse(JSON.stringify(nv)), nv.initialSegments.length)
  3049. nv.initialSegments = translate(nv.initialSegments)
  3050. //console.log(344,nv, nv.initialSegments.length)
  3051. }
  3052. } catch (e) {
  3053. console.log('Tabview Error', e)
  3054. }
  3055. this.__$$initialTranscriptsRenderer$$__ = nv;
  3056. }
  3057. })
  3058. let afm = 0;
  3059. let afArg = null;
  3060. const g_postToContentWindow = function () {
  3061. if (arguments.length === 1 && "yt-player-video-progress" in arguments[0]) {
  3062. afArg = [...arguments];
  3063. afm++;
  3064. if (afm === 1) {
  3065. requestAnimationFrame(() => {
  3066. afm = 0;
  3067. this.__$$postToContentWindow$$__.apply(this, afArg || arguments)
  3068. })
  3069. }
  3070. } else {
  3071. this.__$$postToContentWindow$$__.apply(this, arguments)
  3072. }
  3073. }
  3074. setInterval(function () {
  3075. let tmp;
  3076. tmp = document.querySelector('ytd-live-chat-frame#chat');
  3077. if (tmp && !tmp.__$$postToContentWindow$$__ && typeof tmp.postToContentWindow == 'function' && tmp.postToContentWindow !== g_postToContentWindow) {
  3078. tmp.__$$postToContentWindow$$__ = tmp.postToContentWindow
  3079. tmp.postToContentWindow = g_postToContentWindow
  3080. }
  3081. }, 150)
  3082. document.addEventListener('yt-expander-less-tapped', function (evt) {
  3083. // including reply of comments;
  3084. // fix scrollTop when "show less" is tapped
  3085. if (!evt || !evt.target) return;
  3086. /** @type {HTMLElement} */
  3087. let elm = evt.target;
  3088. if (elm.matches('#tab-comments #contents.style-scope.ytd-item-section-renderer > ytd-comment-thread-renderer.style-scope.ytd-item-section-renderer ytd-expander#expander[max-number-of-lines]')) {
  3089. //setTimeout(()=>{
  3090. let cmRender = elm.closest('ytd-comment-renderer')
  3091. let tabComments = cmRender.closest('#tab-comments')
  3092. let cmRenderRect = cmRender.getBoundingClientRect()
  3093. let tabCommentsRect = tabComments.getBoundingClientRect()
  3094. let eTop = cmRenderRect.top
  3095. let cTop = tabCommentsRect.top
  3096. if (cTop - eTop > 0) {
  3097. tabComments.scrollTop -= cTop - eTop + Math.max(cmRenderRect.left - tabCommentsRect.left, 0)
  3098. //note: {cmRenderRect.left - tabCommentsRect.left} is larger for reply of comments
  3099. }
  3100. //},30)
  3101. }
  3102. }, true)
  3103. }
  3104.  
  3105.  
  3106. function onVideoChange() {
  3107. // for popstate & next video
  3108.  
  3109. newVideoPageCheck();
  3110.  
  3111. scheduledCommentRefresh=true;
  3112. let comments = document.querySelector('ytd-comments#comments')
  3113. if(!deferredVarYTDHidden && scheduledCommentRefresh && Q.comments_section_loaded>0 && comments.hasAttribute('hidden')){
  3114.  
  3115. scheduledCommentRefresh=false;
  3116. // comment tab btn - hide by timer in attribute change
  3117. resetCommentSection();
  3118. }
  3119.  
  3120. mtf_forceCheckLiveVideo_disable = 0
  3121. mtc_store= Date.now()+3870
  3122.  
  3123. if (deferredVarYTDHidden) { // reset info when hidden
  3124. resetCommentSection();
  3125. }else{
  3126. checkVisibleEngagementPanel();
  3127. atChange(); // in case no mutation occurance
  3128. }
  3129.  
  3130.  
  3131.  
  3132. }
  3133.  
  3134. function onNavigationEnd(evt) {
  3135. newVideoPageCheck(); // required for init
  3136.  
  3137. let mRet =
  3138. (/^https?\:\/\/(\w+\.)*youtube\.com\/watch\?(\w+\=[^\/\?\&]+\&|\w+\=?\&)*v=[\w\-\_]+/.test(window.location.href) ? 1 : 0) +
  3139. (document.querySelector('ytd-watch-flexy[tabview-selection]') ? 2 : 0);
  3140.  
  3141. if (mRet === 1) {
  3142.  
  3143. document.addEventListener('loadedmetadata', function(evt) {
  3144. if (evt.target.matches('video[tabview-mainvideo]')) {
  3145. setTimeout(onVideoChange, 30)
  3146. }
  3147. }, true)
  3148.  
  3149. pluginUnhook(); // in case not triggered by popstate - say mini playing
  3150.  
  3151. let timeout = 4; // max. 4 animation frames
  3152.  
  3153. let tf = () => {
  3154.  
  3155. if (--timeout > 0 && !document.querySelector('#player video')) return requestAnimationFrame(tf);
  3156.  
  3157. if (!document.querySelector('script#userscript-tabview-injection-1')) {
  3158.  
  3159. if (isMyScriptInChromeRuntime()){
  3160.  
  3161. addScriptByURL(window.chrome.runtime.getURL('js/injection_script_1.js')).id = 'userscript-tabview-injection-1';
  3162. }
  3163. else{
  3164.  
  3165. let injection_script = GM_getResourceText("injectionJS1")
  3166. addScript(`${injection_script}`).id = 'userscript-tabview-injection-1';
  3167. }
  3168. }
  3169.  
  3170. let ytdFlexyElm = document.querySelector('ytd-watch-flexy')
  3171.  
  3172. if(!ytdFlexyElm) return;
  3173.  
  3174.  
  3175. scriptEnable = true;
  3176. scriptEC++;
  3177.  
  3178. no_fix_contents_until = 0;
  3179. no_fix_playlist_until = 0;
  3180.  
  3181. ytdFlexy = mWeakRef(ytdFlexyElm)
  3182.  
  3183. let timeoutR_findRelated = new Timeout();
  3184. timeoutR_findRelated.set(function() {
  3185. let ytdFlexyElm = kRef(ytdFlexy);
  3186. if(!ytdFlexyElm) return true;
  3187. let related = ytdFlexyElm.querySelector("#related");
  3188. if (!related) return true;
  3189. foundRelated(related);
  3190. }, 100, 10)
  3191.  
  3192. function foundRelated(related) {
  3193. let promise = Promise.resolve();
  3194. if (!document.querySelector("#right-tabs")) {
  3195. promise = promise.then(() => {
  3196. $(getTabsHTML()).insertBefore(related).attr('data-dom-created-by-tabview-youtube', scriptVersionForExternal);
  3197. })
  3198. }
  3199. promise.then(runAfterTabAppended)
  3200. }
  3201.  
  3202. /*
  3203. setTimeout(() => {
  3204. for (const s of document.querySelectorAll('#right-tabs [userscript-scrollbar-render]')) {
  3205. Promise.resolve(s).then(s => {
  3206. if (s && s.scrollTop > 0) s.scrollTop = 0;
  3207. let child = s.firstElementChild;
  3208. if (child && child.scrollTop > 0) child.scrollTop = 0;
  3209. });
  3210. }
  3211. }, 90)
  3212. */
  3213.  
  3214.  
  3215. }
  3216.  
  3217. tf();
  3218.  
  3219. } else if (mRet === 3) {
  3220.  
  3221. if(!scriptEnable) return;
  3222.  
  3223. let elmComments = document.querySelector('ytd-comments#comments')
  3224.  
  3225. if (elmComments) {
  3226. nativeFunc(elmComments, "loadComments")
  3227. }
  3228.  
  3229. } else if (mRet === 0) {
  3230.  
  3231. pluginUnhook(); // in case not triggered by popstate - say mini playing
  3232.  
  3233. }
  3234.  
  3235. if(mRet & 1){
  3236. setTimeout(()=>{
  3237. Q.mutationTarget=null;
  3238. atChange(); // in case no mutation occurance
  3239. FP.mtf_attrComments()
  3240. },160);
  3241. }
  3242.  
  3243. }
  3244.  
  3245.  
  3246. function setToActiveTab(defaultTab) {
  3247. if (isTheater() && isWideScreenWithTwoColumns()) return;
  3248. const jElm = document.querySelector(`a[userscript-tab-content="${switchTabActivity_lastTab}"]:not(.tab-btn-hidden)`) ||
  3249. document.querySelector(`a[userscript-tab-content="${(defaultTab||settings.defaultTab)}"]:not(.tab-btn-hidden)`) ||
  3250. document.querySelector("a[userscript-tab-content]:not(.tab-btn-hidden)") ||
  3251. null;
  3252. switchTabActivity(jElm);
  3253. return !!jElm;
  3254. }
  3255.  
  3256. function getWrapper(wrapperId) {
  3257. let $wrapper = $(`#${wrapperId}`);
  3258. if (!$wrapper[0]) $wrapper = $(`<div id="${wrapperId}"></div>`)
  3259. return $wrapper.first();
  3260. }
  3261.  
  3262. function runAfterTabAppended() {
  3263.  
  3264. document.documentElement.setAttribute('plugin-tabview-youtube', '')
  3265.  
  3266. const ytdFlexyElm = kRef(ytdFlexy)
  3267. if(!ytdFlexyElm) return;
  3268. if (!ytdFlexyElm.hasAttribute('tabview-selection')) ytdFlexyElm.setAttribute('tabview-selection', '')
  3269.  
  3270.  
  3271. //console.log('unc02')
  3272.  
  3273. // append the next videos
  3274. // it exists as "related" is already here
  3275. fixTabs();
  3276.  
  3277. // just switch to the default tab
  3278. setToActiveTab();
  3279.  
  3280.  
  3281. prepareTabBtn();
  3282.  
  3283.  
  3284. // append the detailed meta contents to the tab-info
  3285. // ** FOnce.mtf_checkDescriptionLoaded = FP.mtf_checkDescriptionLoaded;
  3286. // ** if (Q.mutationTarget === null) $callOnceAsync('mtf_checkDescriptionLoaded');
  3287.  
  3288. // force window scroll when #continuations is first detected and #comments still [hidden]
  3289. //FOnce.mtf_advancedComments = FP.mtf_advancedComments;
  3290. //if (Q.mutationTarget === null) $callOnceAsync('mtf_advancedComments');
  3291.  
  3292. // use video player's element to detect the live-chat situation (no commenting section)
  3293. // this would be very useful if the live chat is collapsed, i.e. iframe has no indication on the where it is live or replay
  3294. //FOnce.mtf_forceCheckLiveVideo = FP.mtf_forceCheckLiveVideo;
  3295. //if (Q.mutationTarget === null) $callOnceAsync('mtf_forceCheckLiveVideo');
  3296. //mtf_forceCheckLiveVideo();
  3297.  
  3298.  
  3299. // Attr Mutation Observer - #playlist - hidden
  3300. //clearMutationObserver(mtoVs, 'mtoVisibility_Playlist')
  3301. // Attr Mutation Observer callback - #playlist - hidden
  3302.  
  3303. // pending for #playlist and set Attribute Observer
  3304. // ** FOnce.mtf_initalAttr_playlist = FP.mtf_initalAttr_playlist
  3305. // ** if (Q.mutationTarget === null) $callOnceAsync('mtf_initalAttr_playlist');
  3306.  
  3307. // Attr Mutation Observer - ytd-comments#comments - hidden
  3308. //clearMutationObserver(mtoVs, 'mtoVisibility_Comments')
  3309. // Attr Mutation Observer callback - ytd-comments#comments - hidden
  3310.  
  3311. // pending for #comments and set Attribute Observer
  3312. // ** FOnce.mtf_initalAttr_comments = FP.mtf_initalAttr_comments;
  3313. // ** if (Q.mutationTarget === null) $callOnceAsync('mtf_initalAttr_comments');
  3314.  
  3315.  
  3316. //clearMutationObserver(mtoVs, 'mtoVisibility_Chatroom');
  3317. //FOnce.mtf_initalAttr_chatroom = FP.mtf_initalAttr_chatroom
  3318. //if (Q.mutationTarget === null) $callOnceAsync('mtf_initalAttr_chatroom');
  3319.  
  3320. // clearMutationObserver(mtoVs, 'mtoVisibility_EngagementPanel');
  3321. // for (const engagement_panel of document.querySelectorAll('ytd-watch-flexy ytd-engagement-panel-section-list-renderer:not([tabview-attr-checked])')) {
  3322. // engagement_panel.removeAttribute('tabview-attr-checked');
  3323. // }
  3324. //FOnce.mtf_initalAttr_engagement_panel = FP.mtf_initalAttr_engagement_panel
  3325. //if (Q.mutationTarget === null) $callOnceAsync('mtf_initalAttr_engagement_panel');
  3326.  
  3327.  
  3328. mtoFlexyAttr.clear(true)
  3329. mtf_checkFlexy()
  3330.  
  3331.  
  3332. document.querySelector("#right-tabs .tab-content").addEventListener('scroll', makeBodyScrollByEvt, true);
  3333.  
  3334.  
  3335. Q.addP = 0;
  3336. Q.removeP = 0;
  3337. Q.mutationTarget = null;
  3338.  
  3339. Q.mtoNav_requestNo = 0;
  3340.  
  3341.  
  3342. mtoMutation_watchFlexy.bindElement(ytdFlexyElm, {
  3343. childList: true,
  3344. subtree: true,
  3345. attributes: false
  3346. })
  3347.  
  3348. // for automcomplete plugin or other userscript plugins
  3349. // document.body for Firefox >= 60
  3350. mtoMutation_body.bindElement(document.querySelector('body'), {
  3351. childList: true,
  3352. subtree: false,
  3353. attributes: false
  3354. })
  3355.  
  3356.  
  3357. regularCheck(1, 0, null);
  3358.  
  3359.  
  3360.  
  3361.  
  3362. }
  3363.  
  3364.  
  3365. function fetchCommentsFinished() {
  3366. let ytdFlexyElm = kRef(ytdFlexy);
  3367. if (!scriptEnable || !ytdFlexyElm) return;
  3368. if(mtf_forceCheckLiveVideo_disable===2) return;
  3369. akAttr(ytdFlexyElm, 'tabview-youtube-comments', false, 'LS')
  3370. }
  3371.  
  3372. function setCommentSection(value) {
  3373.  
  3374. Q.comments_section_loaded = value;
  3375.  
  3376. if (value === 0) {
  3377. sect_lastUpdate = 0;
  3378. comments_section_loaded_elm = null;
  3379. }
  3380.  
  3381. }
  3382.  
  3383. function resetCommentSection(){
  3384.  
  3385. let tab_btn = document.querySelector('.tab-btn[userscript-tab-content="#tab-comments"]')
  3386.  
  3387.  
  3388. if(tab_btn){
  3389.  
  3390. let span = tab_btn.querySelector('span#tab3-txt-loader');
  3391.  
  3392. tab_btn.removeAttribute('loaded-comment')
  3393. tab_btn.classList.remove('tab-btn-hidden')
  3394. if(span){
  3395. span.textContent='';
  3396. }
  3397. }
  3398.  
  3399.  
  3400. setCommentSection(0);
  3401.  
  3402.  
  3403. }
  3404.  
  3405. function _disableComments() {
  3406. //requestingComments = null;
  3407. mtc_cid && timeline.clearInterval(mtc_cid)
  3408. mtc_cid=0;
  3409.  
  3410. if (!scriptEnable) return;
  3411. let cssElm = kRef(ytdFlexy);
  3412. if (!cssElm) return;
  3413.  
  3414. mtoInterval = mtoInterval2;
  3415.  
  3416. if(Q.comments_section_loaded>0) return; //already displayed / disabled
  3417.  
  3418. scheduledCommentRefresh=false;
  3419. setCommentSection(2);
  3420.  
  3421. comments_section_loaded_elm = mWeakRef(document.querySelector('ytd-comments#comments div#contents') || null)
  3422.  
  3423. let tabBtn = document.querySelector('.tab-btn[userscript-tab-content="#tab-comments"]');
  3424. if(tabBtn) {
  3425. tabBtn.removeAttribute('loaded-comment')
  3426. if (!tabBtn.classList.contains('tab-btn-hidden')) {
  3427. hideTabBtn(tabBtn)
  3428. }
  3429. }
  3430.  
  3431. akAttr(cssElm, 'tabview-youtube-comments', true, 'D');
  3432.  
  3433.  
  3434. }
  3435.  
  3436.  
  3437.  
  3438.  
  3439. let layoutStatusMutex = new Mutex();
  3440.  
  3441. function forceDisplayChatReplay() {
  3442. let items = chatFrameElement('yt-live-chat-item-list-renderer #items');
  3443. if (items && items.childElementCount !== 0) return;
  3444.  
  3445. let ytd_player = document.querySelector('ytd-player#ytd-player');
  3446. if (!ytd_player) return;
  3447. let videoElm = ytd_player.querySelector('video');
  3448. if (!videoElm) return;
  3449.  
  3450. let video = videoElm;
  3451. if (videoElm && video.currentTime > 0 && !video.ended && video.readyState > video.HAVE_CURRENT_DATA) {
  3452. let chat = document.querySelector('ytd-live-chat-frame#chat');
  3453. if (chat) {
  3454. nativeFunc(chat, "postToContentWindow", [{ "yt-player-video-progress": videoElm.currentTime }])
  3455. }
  3456. }
  3457.  
  3458. }
  3459.  
  3460.  
  3461.  
  3462. function runAfterExpandChat() {
  3463.  
  3464.  
  3465. new Promise(resolve => {
  3466.  
  3467. let chatFrame_st = Date.now();
  3468. let cid_chatFrameCheck = 0;
  3469.  
  3470. let sEF = new ScriptEF();
  3471. cid_chatFrameCheck = setInterval(() => {
  3472. if (!sEF.isValid()) return cid_chatFrameCheck = clearInterval(cid_chatFrameCheck);
  3473. let cDoc = chatFrameContentDocument();
  3474. if (cDoc) {
  3475. cid_chatFrameCheck = clearInterval(cid_chatFrameCheck);
  3476. resolve();
  3477. } else if (!scriptEnable || !isChatExpand() || Date.now() - chatFrame_st > 6750) {
  3478. cid_chatFrameCheck = clearInterval(cid_chatFrameCheck);
  3479. }
  3480. }, 60);
  3481.  
  3482.  
  3483. }).then(() => new Promise(resolve => {
  3484.  
  3485. let timeoutR_ChatAppReady = new Timeout();
  3486. timeoutR_ChatAppReady.set(() => {
  3487.  
  3488. if (!scriptEnable || !isChatExpand()) return false;
  3489. let app = chatFrameElement('yt-live-chat-app');
  3490. if (!app) return true;
  3491.  
  3492. timeline.setTimeout(() => resolve(app), 40)
  3493.  
  3494. }, 40, 150); //40*150 = 6000ms = 6s;
  3495.  
  3496. })).then(app => {
  3497.  
  3498.  
  3499. let cDoc = app.ownerDocument;
  3500.  
  3501. if (!scriptEnable || !isChatExpand()) return;
  3502. addStyle(`
  3503. body #input-panel.yt-live-chat-renderer::after {
  3504. background: transparent;
  3505. }
  3506. #items.yt-live-chat-item-list-renderer{
  3507. contain: content;
  3508. }
  3509. yt-live-chat-text-message-renderer{
  3510. contain: content;
  3511. }
  3512. #item-offset.yt-live-chat-item-list-renderer{
  3513. contain: content;
  3514. }
  3515. #item-scroller.yt-live-chat-item-list-renderer{
  3516. contain: strict;
  3517. }
  3518. img[width][height]{
  3519. contain: strict;
  3520. }
  3521. #item-list>yt-live-chat-item-list-renderer, #item-list>yt-live-chat-item-list-renderer>#contents{
  3522. contain: strict;
  3523. }
  3524.  
  3525.  
  3526. #chat.style-scope.yt-live-chat-renderer,
  3527. yt-live-chat-text-message-renderer.style-scope.yt-live-chat-item-list-renderer,
  3528. #item-scroller,
  3529. yt-live-chat-text-message-renderer.style-scope.yt-live-chat-item-list-renderer .style-scope.yt-live-chat-text-message-renderer,
  3530. yt-live-chat-ticker-paid-message-item-renderer
  3531. {
  3532. contain: layout paint style;
  3533. }
  3534. yt-img-shadow#author-photo.style-scope{
  3535. contain: layout paint style;
  3536. content-visibility: auto;
  3537. contain-intrinsic-size: 24px 24px;
  3538. }
  3539. #item-offset.style-scope.yt-live-chat-item-list-renderer,
  3540. #items.style-scope.yt-live-chat-item-list-renderer {
  3541. contain: layout paint;
  3542. }
  3543. .style-scope.yt-live-chat-text-message-renderer {
  3544. cursor: default;
  3545. }
  3546. #author-photo.style-scope.yt-live-chat-text-message-renderer,
  3547. yt-live-chat-author-chip.style-scope.yt-live-chat-text-message-renderer,
  3548. yt-live-chat-author-chip.style-scope.yt-live-chat-text-message-renderer ~ span#message.style-scope.yt-live-chat-text-message-renderer
  3549. {
  3550. pointer-events: none;
  3551. }
  3552. span#message.style-scope.yt-live-chat-text-message-renderer > img.emoji.yt-formatted-string.style-scope.yt-live-chat-text-message-renderer{
  3553. contain: layout paint style;
  3554. cursor: default;
  3555. pointer-events: none;
  3556. }
  3557. body yt-live-chat-app{
  3558. contain: size layout paint style;
  3559. content-visibility: auto;
  3560. transform: translate3d(0,0,0);
  3561. overflow: hidden;
  3562. }
  3563.  
  3564. `, cDoc.documentElement)
  3565.  
  3566. if (cDoc.querySelector('yt-live-chat-renderer #continuations')) {
  3567. timeline.setTimeout(() => mtf_ChatExist(), 40);
  3568. $(document.querySelector('ytd-live-chat-frame#chat')).attr('yt-userscript-iframe-loaded', '')
  3569. }
  3570.  
  3571. forceDisplayChatReplay();
  3572.  
  3573.  
  3574.  
  3575.  
  3576. })
  3577.  
  3578. }
  3579.  
  3580. function flexyAttr_toggleFlag(mFlag, b, flag) {
  3581. if (b) mFlag = mFlag | flag;
  3582. else mFlag = mFlag & ~flag;
  3583. return mFlag;
  3584. }
  3585.  
  3586. function flexAttr_toLayoutStatus(nls, attributeName) {
  3587.  
  3588. let attrElm, b, v;
  3589. switch (attributeName) {
  3590. case 'theater':
  3591. nls = flexyAttr_toggleFlag(nls, isTheater(), LAYOUT_THEATER);
  3592. break;
  3593. case 'userscript-chat-collapsed':
  3594. case 'userscript-chatblock':
  3595. attrElm = kRef(ytdFlexy);
  3596. if (hasAttribute(attrElm, 'userscript-chat-collapsed')) {
  3597. nls = flexyAttr_toggleFlag(nls, true, LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLASPED);
  3598. } else {
  3599. nls = flexyAttr_toggleFlag(nls, hasAttribute(attrElm, 'userscript-chatblock'), LAYOUT_CHATROOM);
  3600. nls = flexyAttr_toggleFlag(nls, false, LAYOUT_CHATROOM_COLLASPED);
  3601. }
  3602. break;
  3603. case 'is-two-columns_':
  3604. nls = flexyAttr_toggleFlag(nls, isWideScreenWithTwoColumns(), LAYOUT_TWO_COLUMNS);
  3605. break;
  3606.  
  3607. case 'tabview-selection':
  3608. b = isNonEmptyString(kRef(ytdFlexy).getAttribute('tabview-selection'));
  3609. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_TAB_EXPANDED);
  3610. break;
  3611.  
  3612. case 'fullscreen':
  3613. b = isNonEmptyString(kRef(ytdFlexy).getAttribute('fullscreen'));
  3614. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_FULLSCREEN);
  3615. break;
  3616.  
  3617. case 'userscript-engagement-panel':
  3618. v = kRef(ytdFlexy).getAttribute('userscript-engagement-panel');
  3619. b = (+v > 0)
  3620. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_ENGAGEMENT_PANEL_EXPAND);
  3621. break;
  3622.  
  3623. }
  3624.  
  3625. return nls;
  3626.  
  3627.  
  3628. }
  3629.  
  3630. let mtf_attrFlexy = (mutations, observer) => {
  3631.  
  3632. //attr mutation checker - $$ytdFlexyElm$$ {ytd-watch-flexy} \single
  3633. //::attr
  3634. // ~ 'userscript-chat-collapsed', 'userscript-chatblock', 'theater', 'is-two-columns_',
  3635. // ~ 'tabview-selection', 'fullscreen', 'userscript-engagement-panel',
  3636. // ~ 'hidden'
  3637.  
  3638. //console.log(15330, scriptEnable, kRef(ytdFlexy), mutations)
  3639.  
  3640. if (!scriptEnable) return;
  3641.  
  3642. const cssElm = kRef(ytdFlexy)
  3643. if (!cssElm) return;
  3644.  
  3645. if (!mutations) return;
  3646.  
  3647. //console.log(15340)
  3648. ytdFlexyHiddenCheck(mutations);
  3649.  
  3650. const old_layoutStatus = wls.layoutStatus
  3651. if (old_layoutStatus === null) return;
  3652. let new_layoutStatus = old_layoutStatus;
  3653.  
  3654. let checkedChat = false;
  3655.  
  3656. for (const mutation of mutations) {
  3657. new_layoutStatus = flexAttr_toLayoutStatus(new_layoutStatus, mutation.attributeName);
  3658. if (mutation.attributeName == 'userscript-chat-collapsed' || mutation.attributeName == 'userscript-chatblock') {
  3659.  
  3660. if(!checkedChat){
  3661. checkedChat = true; // avoid double call
  3662.  
  3663. if (cssElm.getAttribute('userscript-chatblock') === 'chat-live') {
  3664. // assigned new attribute - "chat-live" => disable comments section
  3665. _disableComments();
  3666. }
  3667.  
  3668. if (!cssElm.hasAttribute('userscript-chatblock')) {
  3669. // might or might not collapsed before
  3670. timeline.setTimeout(() => {
  3671. if (!scriptEnable) return;
  3672. //delayed call => check with the "no active focus" condition with chatroom status
  3673. if (!isAnyActiveTab() && !isChatExpand() && !isTheater() && isWideScreenWithTwoColumns() && !isFullScreen()) {
  3674. setToActiveTab();
  3675. }
  3676. }, 240);
  3677. }
  3678. }
  3679.  
  3680. }else if(mutation.attributeName == 'userscript-engagement-panel'){
  3681. // assume any other active component such as tab content and chatroom
  3682. if (+(cssElm.getAttribute('userscript-engagement-panel')||0)===0 && +mutation.oldValue>0) {
  3683. timeline.setTimeout(() => {
  3684. if (!scriptEnable) return;
  3685. //delayed call => check with the "no active focus" condition with engagement panel status
  3686. if (!isAnyActiveTab() && !isEngagementPanelExpanded() && !isTheater() && isWideScreenWithTwoColumns() && !isFullScreen()) {
  3687. setToActiveTab();
  3688. }
  3689. }, 240);
  3690. }
  3691. }
  3692. }
  3693.  
  3694. if (new_layoutStatus !== old_layoutStatus) wls.layoutStatus = new_layoutStatus
  3695.  
  3696. }
  3697.  
  3698. const mtf_checkFlexy = () => {
  3699. // once per $$native-ytd-watch-flexy$$ {ytd-watch-flexy} detection
  3700.  
  3701. let ytdFlexyElm = kRef(ytdFlexy);
  3702. if (!scriptEnable || !ytdFlexyElm) return true;
  3703.  
  3704.  
  3705. wls.layoutStatus = null;
  3706.  
  3707. let isFlexyHidden = (deferredVarYTDHidden = ytdFlexyElm.hasAttribute('hidden'));
  3708.  
  3709. if (!isFlexyHidden) {
  3710. let rChatExist = base_ChatExist();
  3711. if (rChatExist) {
  3712. let { attr_chatblock, attr_chatcollapsed } = rChatExist;
  3713. if (attr_chatblock === null) {
  3714. //remove attribute if it is unknown
  3715. attr_chatblock = false;
  3716. attr_chatcollapsed = false;
  3717. }
  3718. wAttr(ytdFlexyElm, 'userscript-chatblock', attr_chatblock)
  3719. wAttr(ytdFlexyElm, 'userscript-chat-collapsed', attr_chatcollapsed)
  3720. }
  3721. }
  3722.  
  3723. let rTabSelection = [...ytdFlexyElm.querySelectorAll('.tab-btn[userscript-tab-content]')]
  3724. .map(elm => ({ elm, hidden: elm.classList.contains('tab-btn-hidden') }));
  3725.  
  3726. if(rTabSelection.length === 0){
  3727. wAttr(ytdFlexyElm, 'tabview-selection', false);
  3728. }else{
  3729. rTabSelection=rTabSelection.filter(entry => entry.hidden !== true); // all available tabs
  3730. if (rTabSelection.length === 0) wAttr(ytdFlexyElm, 'tabview-selection', '');
  3731. }
  3732. rTabSelection = null;
  3733. let rEP = engagement_panels_();
  3734. if (rEP && rEP.count > 0) wAttr(ytdFlexyElm, 'userscript-engagement-panel', false);
  3735. //else wAttr(ytdFlexyElm, 'userscript-engagement-panel', rEP.value + ""); // can be 0
  3736. else if(rEP.value>0) wAttr(ytdFlexyElm, 'userscript-engagement-panel', rEP.value + ""); // can be 0
  3737.  
  3738. let ls = 0;
  3739. ls = flexAttr_toLayoutStatus(ls, 'theater')
  3740. ls = flexAttr_toLayoutStatus(ls, 'userscript-chat-collapsed')
  3741. ls = flexAttr_toLayoutStatus(ls, 'userscript-chatblock')
  3742. ls = flexAttr_toLayoutStatus(ls, 'is-two-columns_')
  3743. ls = flexAttr_toLayoutStatus(ls, 'tabview-selection')
  3744. ls = flexAttr_toLayoutStatus(ls, 'fullscreen')
  3745. ls = flexAttr_toLayoutStatus(ls, 'userscript-engagement-panel')
  3746.  
  3747. wls.layoutStatus = ls
  3748.  
  3749. mtoFlexyAttr.bindElement(ytdFlexyElm,{
  3750. attributes: true,
  3751. attributeFilter: ['userscript-chat-collapsed', 'userscript-chatblock', 'theater', 'is-two-columns_', 'tabview-selection', 'fullscreen', 'userscript-engagement-panel', 'hidden'],
  3752. attributeOldValue: true
  3753. })
  3754.  
  3755. ytdFlexyHiddenCheckBasic(ytdFlexyElm);
  3756.  
  3757.  
  3758. let columns = document.querySelector('ytd-page-manager#page-manager #columns')
  3759. if (columns) {
  3760. wAttr(columns, 'userscript-scrollbar-render', true);
  3761. }
  3762.  
  3763. return false;
  3764. }
  3765.  
  3766. function ytdFlexyHiddenCheckBasic(ytdFlexyElm){
  3767. // for both hidden and non-hidden ytd-watch-flexy
  3768.  
  3769. deferredVarYTDHidden = ytdFlexyElm.hasAttribute('hidden');
  3770.  
  3771. // console.log(15403)
  3772. document.documentElement.classList.toggle('tabview-normal-player',!deferredVarYTDHidden)
  3773.  
  3774. }
  3775.  
  3776. function checkVisibleEngagementPanel(){
  3777. if(storeLastPanel){
  3778.  
  3779. let elm_storeLastPanel = kRef(storeLastPanel);
  3780.  
  3781. if(elm_storeLastPanel && !isDOMVisible(elm_storeLastPanel) ){
  3782. storeLastPanel=null;
  3783. ytBtnCloseEngagementPanels();
  3784. }
  3785.  
  3786. }
  3787.  
  3788. }
  3789.  
  3790. function ytdFlexyHiddenCheck(mutations) {
  3791.  
  3792. //console.log(15350)
  3793.  
  3794. const ytdFlexyElm = kRef(ytdFlexy)
  3795. if (!ytdFlexyElm) return;
  3796. let muHidden = false
  3797.  
  3798. for (const mutation of mutations) {
  3799. if (mutation.attributeName === 'hidden') {
  3800. //console.log(343)
  3801. muHidden = true;
  3802. break;
  3803. }
  3804. }
  3805.  
  3806. if (!muHidden) return;
  3807.  
  3808. mtc_store= Date.now()+2870
  3809. //console.log(7004)
  3810. deferredVarYTDHidden = ytdFlexyElm.hasAttribute('hidden');
  3811.  
  3812. if(deferredVarYTDHidden && scheduledCommentRefresh && Q.comments_section_loaded>0){
  3813. scheduledCommentRefresh=false;
  3814. }
  3815.  
  3816. //console.log(15400)
  3817. if(!deferredVarYTDHidden){
  3818. mtf_forceCheckLiveVideo_disable = 0
  3819. timeline.setTimeout(function() {
  3820. mtf_ChatExist();
  3821. layoutStatusMutex.lockWith(unlock=>{
  3822. atChange(); // in case no mutation occurance
  3823. ytdFlexyHiddenCheckBasic(ytdFlexyElm);
  3824. dispatchWindowResize(); // player control positioning
  3825. timeline.setTimeout(unlock, 40);
  3826. })
  3827. }, 40);
  3828. }else{
  3829. //console.log(15401)
  3830. timeline.setTimeout(function() {
  3831. //console.log(15402)
  3832. ytdFlexyHiddenCheckBasic(ytdFlexyElm);
  3833. }, 40);
  3834. }
  3835.  
  3836. }
  3837.  
  3838.  
  3839.  
  3840.  
  3841. let switchTabActivity_lastTab = null
  3842.  
  3843. function setDisplayedPlaylist() {
  3844. //override the default youtube coding event prevention
  3845. let cssElm = kRef(ytdFlexy);
  3846. if (!scriptEnable || !cssElm) return;
  3847. displayedPlaylist = mWeakRef(document.querySelector('ytd-watch-flexy #tab-list:not(.tab-content-hidden) ytd-playlist-panel-renderer') || null);
  3848. }
  3849.  
  3850. function fixLineClampFn1() {
  3851. // use IntersectionObserver for new browser
  3852. if(!isIntersectionObserverAvailable)
  3853. timeline.setTimeout(() => requestAnimationFrame(() => new Promise(fixLineClampFn2)), 1)
  3854. }
  3855.  
  3856. //let lastFixLineClamp = 0
  3857. //let mConstructor = null
  3858. const isIntersectionObserverAvailable= !!window.IntersectionObserver;
  3859. function fixLineClampFn2() {
  3860. //let currentTime= Date.now();
  3861. //if(currentTime)
  3862. let contentElements = document.querySelectorAll('ytd-comments#comments ytd-expander[max-number-of-lines] > #content.ytd-expander:not(.tabview-fix-line-clamp)');
  3863. // backward compatible with Waterfox Classic
  3864. // let contentElements = document.querySelectorAll('ytd-comments#comments ytd-expander[should-use-number-of-lines] > #content')
  3865. for (const elm of contentElements){
  3866. //nativeValue(elm.parentNode,'recomputeOnResize',true)
  3867.  
  3868. // loading -> fetched -> loaded in non-visual mode
  3869. // require to trigger the update using mutation observer for line clamp
  3870. elm.classList.toggle('tabview-fix-line-clamp'); // this is to trigger mutation observer in dekstop_polymer.js
  3871. }
  3872. nativeFuncStacked('ytd-comments#comments ytd-expander[max-number-of-lines]', 'calculateCanCollapse');
  3873. //nativeConstStacked('ytd-comments#comments ytd-expander[max-number-of-lines]', 'recomputeOnResize',true);
  3874. contentElements = null;
  3875. }
  3876.  
  3877. function switchTabActivity(activeLink) {
  3878. if (!scriptEnable) return;
  3879.  
  3880. const ytdFlexyElm = kRef(ytdFlexy);
  3881.  
  3882. if (!ytdFlexyElm) return;
  3883.  
  3884. if (activeLink && activeLink.classList.contains('tab-btn-hidden')) return; // not allow to switch to hide tab
  3885.  
  3886. if (isTheater() && isWideScreenWithTwoColumns()) activeLink = null;
  3887.  
  3888.  
  3889. function runAtEnd() {
  3890.  
  3891. //console.log(12312)
  3892.  
  3893. if (activeLink) lastShowTab = activeLink.getAttribute('userscript-tab-content')
  3894.  
  3895. displayedPlaylist = null;
  3896. scrollingVideosList = null;
  3897.  
  3898. if (activeLink && lastShowTab == '#tab-list') {
  3899. setDisplayedPlaylist();
  3900. } else if (activeLink && lastShowTab == '#tab-videos') {
  3901. scrollingVideosList = mWeakRef(document.querySelector('ytd-watch-flexy #tab-videos:not(.tab-content-hidden) [placeholder-videos]'));
  3902. }
  3903.  
  3904.  
  3905. ytdFlexyElm.setAttribute('tabview-selection', activeLink ? lastShowTab : '')
  3906.  
  3907. if (lastShowTab == '#tab-comments' && (ytdFlexyElm.getAttribute('tabview-youtube-comments') || '').lastIndexOf('S') >= 0) {
  3908.  
  3909. if(mtf_forceCheckLiveVideo_disable===2) {}
  3910. else{
  3911. akAttr(ytdFlexyElm, 'tabview-youtube-comments', false, 'L');
  3912.  
  3913. requestAnimationFrame(() => {
  3914. let comments_tab = document.querySelector('#tab-comments');
  3915. if (comments_tab && comments_tab.scrollTop > 0) comments_tab.scrollTop = 0;
  3916. });
  3917. }
  3918.  
  3919. }
  3920.  
  3921. if (lastShowTab == '#tab-comments') {
  3922. fixLineClampFn1();
  3923. }
  3924.  
  3925. }
  3926.  
  3927. const links = document.querySelectorAll('#material-tabs a[userscript-tab-content]');
  3928.  
  3929.  
  3930.  
  3931. for (const link of links) {
  3932. let content = document.querySelector(link.getAttribute('userscript-tab-content'));
  3933. if (link && content) {
  3934. if (link !== activeLink) {
  3935. link.classList.remove("active");
  3936. content.classList.add("tab-content-hidden");
  3937. } else {
  3938. link.classList.add("active");
  3939. content.classList.remove("tab-content-hidden");
  3940. //timeline.setTimeout(()=>content.focus(),400);
  3941.  
  3942. }
  3943. }
  3944. }
  3945.  
  3946. runAtEnd();
  3947.  
  3948.  
  3949. }
  3950.  
  3951. let tabsUiScript_setclick = false;
  3952.  
  3953. function prepareTabBtn() {
  3954.  
  3955. const materialTab = document.querySelector("#material-tabs")
  3956. if (!materialTab) return;
  3957.  
  3958. let noActiveTab = !!document.querySelector('ytd-watch-flexy[userscript-chatblock]:not([userscript-chat-collapsed])')
  3959.  
  3960. const activeLink = materialTab.querySelector('a[userscript-tab-content].active:not(.tab-btn-hidden)')
  3961. if (activeLink) switchTabActivity(noActiveTab ? null : activeLink)
  3962.  
  3963. if (!tabsUiScript_setclick) {
  3964. tabsUiScript_setclick = true;
  3965. $(materialTab).on("click", "a", function(evt) {
  3966.  
  3967. //console.log(8510)
  3968. let ytdFlexyElm = kRef(ytdFlexy);
  3969. if (!scriptEnable || !ytdFlexyElm) return null;
  3970.  
  3971. if (!this.hasAttribute('userscript-tab-content')) return;
  3972.  
  3973.  
  3974. evt.preventDefault();
  3975.  
  3976. //console.log(8511)
  3977.  
  3978. /*
  3979. if (this.getAttribute('userscript-tab-content') == '#tab-comments' && parseInt(ytdFlexyElm.getAttribute('tabview-youtube-comments') || '') < 0) {
  3980. console.log(8512)
  3981. return;
  3982. }*/
  3983.  
  3984. //console.log(8513)
  3985. new Promise(requestAnimationFrame).then(() => {
  3986.  
  3987.  
  3988. //console.log(8514)
  3989. layoutStatusMutex.lockWith(unlock => {
  3990.  
  3991. //console.log(8515)
  3992. switchTabActivity_lastTab = this.getAttribute('userscript-tab-content');
  3993.  
  3994. let isActiveAndVisible = this.classList.contains('tab-btn') && this.classList.contains('active') && !this.classList.contains('tab-btn-hidden')
  3995.  
  3996. if (isWideScreenWithTwoColumns() && !isTheater() && isActiveAndVisible) {
  3997. //optional
  3998. timeline.setTimeout(unlock, 80);
  3999. switchTabActivity(null);
  4000. ytBtnSetTheater();
  4001. } else if (isActiveAndVisible) {
  4002. timeline.setTimeout(unlock, 80);
  4003. switchTabActivity(null);
  4004. } else {
  4005. let pInterval = 60;
  4006. if (isChatExpand() && isWideScreenWithTwoColumns()) {
  4007. ytBtnCollapseChat();
  4008. } else if (isEngagementPanelExpanded() && isWideScreenWithTwoColumns()) {
  4009. ytBtnCloseEngagementPanels();
  4010. } else if (isWideScreenWithTwoColumns() && isTheater() && !isFullScreen()) {
  4011. ytBtnCancelTheater();
  4012. } else {
  4013. pInterval = 20;
  4014. }
  4015. //console.log(8518)
  4016. timeline.setTimeout(() => {
  4017. timeline.setTimeout(makeBodyScroll, 20); // this is to make the image render
  4018.  
  4019. timeline.setTimeout(() => {
  4020. let rightTabs = document.querySelector('#right-tabs');
  4021. if (!isWideScreenWithTwoColumns() && rightTabs && rightTabs.offsetTop > 0 && this.classList.contains('active')) {
  4022. let tabButtonBar = document.querySelector('#material-tabs');
  4023. let tabButtonBarHeight = tabButtonBar ? tabButtonBar.offsetHeight : 0;
  4024. window.scrollTo(0, rightTabs.offsetTop - tabButtonBarHeight);
  4025. }
  4026. }, 60)
  4027. // console.log(8519)
  4028.  
  4029. timeline.setTimeout(unlock, 80)
  4030. switchTabActivity(this)
  4031.  
  4032. }, pInterval);
  4033. }
  4034.  
  4035.  
  4036. })
  4037.  
  4038. })
  4039.  
  4040.  
  4041.  
  4042.  
  4043. });
  4044.  
  4045. }
  4046.  
  4047. }
  4048.  
  4049.  
  4050. // ---------------------------------------------------------------------------------------------
  4051. window.addEventListener("yt-navigate-finish", onNavigationEnd)
  4052.  
  4053. document.addEventListener("loadstart", (evt) => {
  4054. if (!evt || !evt.target || evt.target.nodeName !== "VIDEO") return;
  4055. let elm = evt.target;
  4056. if (!elm.matches('#player video, #movie_player video, video[tabview-mainvideo]')) return;
  4057.  
  4058. mtc_store= Date.now()+2870
  4059.  
  4060. let src = elm.src;
  4061. if (src !== lastVideoURL) {
  4062. lastVideoURL = elm.src;
  4063. elm.setAttribute('tabview-mainvideo', ''); // mainly for mini playing
  4064. }
  4065.  
  4066. }, true)
  4067.  
  4068. // ---------------------------------------------------------------------------------------------
  4069.  
  4070. let scrolling_lastD = 0;
  4071.  
  4072. const singleColumnScrolling = function(scrolling_lastF) {
  4073. if (!scriptEnable || deferredVarYTDHidden) return;
  4074.  
  4075. let pageY = scrollY;
  4076. if (pageY < 10 && scrolling_lastD === 0 && !scrolling_lastF) return;
  4077.  
  4078. let targetElm, header, navElm;
  4079.  
  4080. Promise.resolve().then(() => {
  4081.  
  4082. targetElm = document.querySelector("#right-tabs");
  4083. if (!targetElm) return;
  4084. header = targetElm.querySelector("header");
  4085. if (!header) return;
  4086. navElm = document.querySelector('#masthead-container, #masthead')
  4087. if (!navElm) return;
  4088. let navHeight = navElm ? navElm.offsetHeight : 0
  4089.  
  4090. let elmY = targetElm.offsetTop
  4091.  
  4092. let xyz = [elmY + navHeight, pageY, elmY - navHeight]
  4093.  
  4094. let xyStatus = 0
  4095. if (xyz[1] < xyz[2] && xyz[2] < xyz[0]) {
  4096. // 1
  4097. xyStatus = 1
  4098. }
  4099.  
  4100. if (xyz[0] > xyz[1] && xyz[1] > xyz[2]) {
  4101.  
  4102. //2
  4103. xyStatus = 2
  4104.  
  4105. }
  4106.  
  4107. if (xyz[2] < xyz[0] && xyz[0] < xyz[1]) {
  4108. // 3
  4109.  
  4110. xyStatus = 3
  4111.  
  4112.  
  4113. }
  4114.  
  4115. return xyStatus;
  4116.  
  4117. }).then((xyStatus) => {
  4118.  
  4119. if ((xyStatus == 2 || xyStatus == 3) && (scrolling_lastD === 0 || scrolling_lastF)) {
  4120. scrolling_lastD = 1;
  4121. let {
  4122. offsetHeight
  4123. } = header
  4124. let {
  4125. offsetWidth
  4126. } = targetElm
  4127.  
  4128. targetElm.style.setProperty("--userscript-sticky-width", offsetWidth + 'px')
  4129. targetElm.style.setProperty("--userscript-sticky", offsetHeight + 'px')
  4130.  
  4131. wAttr(targetElm, 'userscript-sticky', true);
  4132.  
  4133. } else if ((xyStatus == 1) && (scrolling_lastD === 1 || scrolling_lastF)) {
  4134. scrolling_lastD = 0;
  4135.  
  4136. wAttr(targetElm, 'userscript-sticky', false);
  4137. }
  4138.  
  4139.  
  4140. targetElm = null;
  4141. header = null;
  4142. navElm = null;
  4143.  
  4144. });
  4145.  
  4146. };
  4147.  
  4148. window.addEventListener("scroll", function() {
  4149. singleColumnScrolling(false)
  4150. }, {
  4151. capture: false,
  4152. passive: true
  4153. })
  4154.  
  4155. let lastResizeAt = 0;
  4156. window.addEventListener('resize', function() {
  4157.  
  4158. if (!scriptEnable) return;
  4159. if (deferredVarYTDHidden) return;
  4160. lastResizeAt = Date.now();
  4161.  
  4162. requestAnimationFrame(() => {
  4163. singleColumnScrolling(true)
  4164. })
  4165.  
  4166. if(lastShowTab == '#tab-comments') fixLineClampFn1() // in case window resized leading the change of number of lines.
  4167.  
  4168. }, {
  4169. capture: false,
  4170. passive: true
  4171. })
  4172.  
  4173.  
  4174.  
  4175. let hist = null
  4176. const getHistState = (history)=>`${history.length}|${history.state?history.state.entryTime:null}`;
  4177. function newVideoPageCheck(){
  4178. let hState = getHistState(history)
  4179. if(hist!==hState) {
  4180. hist=hState;
  4181. if(scriptEnable){
  4182. //scriptEnable = false;
  4183. let callTimeout = !newVideoPage.entryTime
  4184. newVideoPage.entryTime = 1
  4185. if(callTimeout) setTimeout(newVideoPage,0)
  4186. }
  4187. return true;
  4188. }
  4189. }
  4190. function newVideoPage(){
  4191. newVideoPage.entryTime = 0;
  4192.  
  4193. console.log('newVideoPage')
  4194. //console.log('newVideoPage-', 150, location.href)
  4195.  
  4196. let ytdFlexyElm = kRef(ytdFlexy);
  4197. if(!ytdFlexyElm) return;
  4198.  
  4199. timeline.reset();
  4200. layoutStatusMutex = new Mutex();
  4201.  
  4202. //console.log('newVideoPage-', 350, location.href)
  4203. let flag1= /^https?\:\/\/(\w+\.)*youtube\.com\/watch\?(\w+\=[^\/\?\&]+\&|\w+\=?\&)*v=[\w\-\_]+/.test(location.href)
  4204. let tf = ()=>{
  4205.  
  4206. let isFlexyHidden = (deferredVarYTDHidden = ytdFlexyElm.hasAttribute('hidden'));
  4207.  
  4208. let flag2 = !isFlexyHidden;
  4209.  
  4210. if(flag1^flag2) return timeline.setTimeout(tf,7); else funcVisibleWatch();
  4211.  
  4212. tf= null;
  4213. funcVisibleWatch = null;
  4214. ytdFlexyElm=null
  4215.  
  4216. }
  4217.  
  4218. let funcVisibleWatch = ()=>{
  4219. //scriptEnable = true;
  4220.  
  4221. let isFlexyHidden = (deferredVarYTDHidden = ytdFlexyElm.hasAttribute('hidden'));
  4222. ytdFlexyHiddenCheckBasic(ytdFlexyElm)
  4223.  
  4224. if(!isFlexyHidden) {
  4225.  
  4226. mtf_forceCheckLiveVideo_disable = 0;
  4227. mtc_store= Date.now()+2870
  4228.  
  4229. sect_lastUpdate = 0;
  4230. if(pendingOne){
  4231. // reset pendingOne = false if the element is already removed.
  4232. pendingOne = document.querySelectorAll(`ytd-watch-flexy ytd-comments#comments [tabview-cache-time]`).length>1;
  4233. }
  4234.  
  4235. timeline.setTimeout(()=>{
  4236. Q.mutationTarget=null;
  4237. FP.mtf_attrComments()
  4238. },2870)
  4239. if(pageInit_attrComments) hookSection();
  4240.  
  4241.  
  4242.  
  4243.  
  4244.  
  4245. let new_layoutStatus = wls.layoutStatus
  4246.  
  4247. const new_isExpandedChat = !(new_layoutStatus & LAYOUT_CHATROOM_COLLASPED) && (new_layoutStatus & LAYOUT_CHATROOM)
  4248. const new_isCollaspedChat = (new_layoutStatus & LAYOUT_CHATROOM_COLLASPED) && (new_layoutStatus & LAYOUT_CHATROOM)
  4249. const new_isTwoColumns = new_layoutStatus & LAYOUT_TWO_COLUMNS;
  4250. const new_isTheater = new_layoutStatus & LAYOUT_THEATER;
  4251. const new_isTabExpanded = new_layoutStatus & LAYOUT_TAB_EXPANDED;
  4252. const new_isFullScreen = new_layoutStatus & LAYOUT_FULLSCREEN;
  4253. const new_isExpandEPanel = new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPAND;
  4254.  
  4255. /*
  4256. console.log('d67',
  4257. ytdFlexyElm.getAttribute('tabview-selection')==='' ,
  4258. new_isTwoColumns , !new_isTheater, !new_isTabExpanded, !new_isFullScreen , !new_isExpandEPanel , !new_isExpandedChat
  4259.  
  4260. )
  4261. */
  4262.  
  4263. if( ytdFlexyElm.getAttribute('tabview-selection')==='' && new_isTwoColumns && !new_isTheater && !new_isTabExpanded && !new_isFullScreen && !new_isExpandEPanel && !new_isExpandedChat ){
  4264. // e.g. engage panel removed after miniview and change video
  4265. setToActiveTab();
  4266. }else if(new_isExpandEPanel && ytdFlexyElm.querySelectorAll('ytd-engagement-panel-section-list-renderer[visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]').length===0){
  4267. wls.layoutStatus = new_layoutStatus & (~LAYOUT_ENGAGEMENT_PANEL_EXPAND)
  4268. }
  4269.  
  4270. //mtf_attrFlexy();
  4271.  
  4272. FP.mtf_attrEngagementPanel();
  4273. FP.mtf_attrPlaylist();
  4274. FP.mtf_attrComments();
  4275. FP.mtf_attrChatroom();
  4276.  
  4277.  
  4278. }
  4279.  
  4280. regularCheck(1,0,null); // mutation happens when the page is not ready; call again as the page is ready.
  4281.  
  4282.  
  4283.  
  4284.  
  4285. }
  4286.  
  4287. tf();
  4288.  
  4289.  
  4290. }
  4291.  
  4292.  
  4293.  
  4294. function browserHistoryState(){
  4295. let maxCount = 5
  4296.  
  4297. if (!scriptEnable) return;
  4298. let ytdFlexyElm = kRef(ytdFlexy);
  4299. if(!ytdFlexyElm) return;
  4300.  
  4301. let cid = timeline.setInterval(function(){
  4302. if(newVideoPageCheck()){
  4303. timeline.clearInterval(cid);
  4304. }
  4305. if(--maxCount===0) timeline.clearInterval(cid);
  4306. },7)
  4307.  
  4308. }
  4309.  
  4310.  
  4311. window.addEventListener('hashchange', function() {
  4312. if (!scriptEnable) return;
  4313. console.log('hashchange');
  4314. browserHistoryState();
  4315. }, { capture: true })
  4316.  
  4317. window.addEventListener('popstate', function() {
  4318. if (!scriptEnable) return;
  4319. console.log('popstate');
  4320. browserHistoryState();
  4321. }, { capture: true })
  4322.  
  4323.  
  4324. // function clearMutationObserver(o, key) {
  4325. // if (o[key]) {
  4326. // o[key].takeRecords();
  4327. // o[key].disconnect();
  4328. // o[key] = null;
  4329. // return true;
  4330. // }
  4331. // }
  4332.  
  4333. // function initMutationObserver(o, key, callback) {
  4334. // clearMutationObserver(o, key);
  4335. // const mto = new MutationObserver(callback);
  4336. // o[key] = mto;
  4337. // return mto;
  4338. // }
  4339. // function initMutationObserver2(o, key, mtoFunc) {
  4340. // clearMutationObserver(o, key);
  4341. // const mto = mtoFunc();
  4342. // o[key] = mto;
  4343. // return mto;
  4344. // }
  4345.  
  4346. document.addEventListener('wheel', function(evt) {
  4347.  
  4348. if (!scriptEnable) return;
  4349. const displayedPlaylist_element = kRef(displayedPlaylist);
  4350. if (displayedPlaylist_element && displayedPlaylist_element.contains(evt.target)) {
  4351. evt.stopPropagation();
  4352. evt.stopImmediatePropagation();
  4353. }
  4354. }, { capture: true, passive: true });
  4355.  
  4356.  
  4357. function setVideosTwoColumns(flag, bool) {
  4358.  
  4359. //two columns to one column
  4360.  
  4361. /*
  4362. [placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy
  4363.  
  4364. is-two-columns ="" => no is-two-columns
  4365. [placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer
  4366. no hidden => hidden =""
  4367. [placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer
  4368. hidden ="" => no hidden
  4369.  
  4370. */
  4371.  
  4372. let cssSelector1 = '[placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy'
  4373.  
  4374. let cssSelector2 = '[placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer'
  4375.  
  4376. let cssSelector3 = '[placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer'
  4377.  
  4378. let res = {}
  4379. if (flag & 1) {
  4380. res.m1 = document.querySelector(cssSelector1)
  4381. if (res.m1) wAttr(res.m1, 'is-two-columns', bool ? '' : false);
  4382. }
  4383.  
  4384. if (flag & 2) {
  4385. res.m2 = document.querySelector(cssSelector2)
  4386. if (res.m2) wAttr(res.m2, 'hidden', bool ? false : '');
  4387. }
  4388.  
  4389. if (flag & 4) {
  4390. res.m3 = document.querySelector(cssSelector3)
  4391. if (res.m3) wAttr(res.m3, 'hidden', bool ? '' : false);
  4392. }
  4393.  
  4394.  
  4395. return res
  4396.  
  4397.  
  4398.  
  4399.  
  4400. }
  4401.  
  4402. let lastScrollFetch = 0;
  4403. // function isScrolledToEnd(){
  4404. // return (window.innerHeight + window.pageYOffset) >= document.scrollingElement.scrollHeight - 2;
  4405. // }
  4406. let lastOffsetTop = 0;
  4407. window.addEventListener('scroll', function(evt) {
  4408.  
  4409. //console.log(evt.target)
  4410.  
  4411. if (!scriptEnable) return;
  4412.  
  4413.  
  4414. if (!kRef(scrollingVideosList)) return;
  4415. if (videoListBeforeSearch) return;
  4416.  
  4417.  
  4418.  
  4419. let visibleHeight = document.scrollingElement.clientHeight;
  4420. let totalHeight = document.scrollingElement.scrollHeight;
  4421.  
  4422. if (totalHeight < visibleHeight * 1.5) return; // filter out two column view;
  4423.  
  4424. let z = window.pageYOffset + visibleHeight;
  4425. let h_advanced = totalHeight - (visibleHeight > 5 * 40 ? visibleHeight * 0.5 : 40);
  4426.  
  4427.  
  4428.  
  4429. if (z > h_advanced && !isWideScreenWithTwoColumns()) {
  4430.  
  4431. let ct = Date.now();
  4432. if (ct - lastScrollFetch < 500) return; //prevent continuous calling
  4433.  
  4434. lastScrollFetch = ct;
  4435.  
  4436. let res = setVideosTwoColumns(2 | 4, true)
  4437. if (res.m3 && res.m2) {
  4438.  
  4439. //wait for DOM change, just in case
  4440. requestAnimationFrame(() => {
  4441. let { offsetTop } = res.m2 // as visibility of m2 & m3 switched.
  4442.  
  4443. if (offsetTop - lastOffsetTop < 40) return; // in case bug, or repeating calling. // the next button shall below the this button
  4444. lastOffsetTop = offsetTop
  4445.  
  4446. res.m2.parentNode.dispatchEvent(new Event('yt-service-request-sent-button-renderer'))
  4447.  
  4448. res = null
  4449. })
  4450.  
  4451. } else {
  4452.  
  4453. res = null
  4454. }
  4455.  
  4456.  
  4457. }
  4458.  
  4459.  
  4460.  
  4461.  
  4462. }, { passive: true })
  4463.  
  4464. let sect_lastUpdate = 0;
  4465.  
  4466. let sect_holder = null;
  4467. let sect_hText = null;
  4468. let sect_hTime = 0;
  4469.  
  4470. let pendingOne = false;
  4471. let mtf_forceCheckLiveVideo_disable = 0;
  4472.  
  4473. let storeLastPanel = null;
  4474.  
  4475. let deferredVarYTDHidden = null;
  4476. let scheduledCommentRefresh = false;
  4477.  
  4478. let pageInit_attrComments = false;
  4479.  
  4480.  
  4481.  
  4482. let mgChatFrame = {
  4483. setVar(elm) {
  4484. mgChatFrame.kVar = mWeakRef(elm)
  4485. },
  4486. getVar() {
  4487. return kRef(mgChatFrame.kVar)
  4488. },
  4489. inPage() {
  4490. let elm = mgChatFrame.getVar();
  4491. if (!elm) return false;
  4492. let ytdFlexyElm = kRef(ytdFlexy);
  4493. if(!ytdFlexyElm) return false;
  4494. return ytdFlexyElm.contains(elm)
  4495. }
  4496. };
  4497.  
  4498. const timeline = {
  4499. // after initialized (initObserver)
  4500. cn1:{},
  4501. cn2:{},
  4502. setTimeout(f,d){
  4503. return timeline.cn1[setTimeout(f,d)]=true
  4504. },
  4505. clearTimeout(cid){
  4506. timeline.cn1[cid]=false; return clearTimeout(cid)
  4507. },
  4508. setInterval(f,d){
  4509. return timeline.cn2[setInterval(f,d)]=true
  4510. },
  4511. clearInterval(cid){
  4512. timeline.cn2[cid]=false; return clearInterval(cid)
  4513. },
  4514. reset(){
  4515. for(let cid in timeline.cn1) timeline.cn1[cid] && clearTimeout(cid)
  4516. for(let cid in timeline.cn2) timeline.cn2[cid] && clearInterval(cid)
  4517. timeline.cn1={}
  4518. timeline.cn2={}
  4519. }
  4520. }
  4521.  
  4522. class AttributeMutationObserver extends MutationObserver {
  4523. constructor(flist){
  4524. super((mutations, observer)=>{
  4525. for(const mutation of mutations){
  4526. if (mutation.type === 'attributes') {
  4527. this.checker(mutation.target, mutation.attributeName)
  4528. }
  4529. }
  4530. })
  4531. this.flist=flist;
  4532. this.res={}
  4533. }
  4534. takeRecords(){
  4535. super.takeRecords();
  4536. }
  4537. disconnect(){
  4538. this._target = null;
  4539. super.disconnect();
  4540. }
  4541. observe(target){
  4542. if(this._target) return;
  4543. this._target = mWeakRef(target);
  4544. const options = {
  4545. attributes: true,
  4546. attributeFilter: Object.keys(this.flist),
  4547. //attributeFilter: [ "status", "username" ],
  4548. attributeOldValue: true
  4549. }
  4550. super.observe(target, options)
  4551. }
  4552. checker(target, attributeName){
  4553. let nv = target.getAttribute(attributeName);
  4554. if(this.res[attributeName]!==nv){
  4555. this.res[attributeName] = nv
  4556. let f = this.flist[attributeName];
  4557. if(f) f(attributeName, nv);
  4558.  
  4559. }
  4560. }
  4561. check(delay = 0){
  4562. setTimeout(()=>{
  4563. let target = kRef(this._target)
  4564. for(const key of Object.keys(this.flist)){
  4565. this.checker(target,key)
  4566. }
  4567. target = null;
  4568. },delay)
  4569. }
  4570. }
  4571.  
  4572. function goYoutubeGeniusLyrics(){
  4573.  
  4574. setTimeout(function $f(){
  4575.  
  4576. if(!document.documentElement.hasAttribute('w-engagement-panel-genius-lyrics')) return setTimeout($f,100)
  4577.  
  4578. document.documentElement.dispatchEvent(new CustomEvent('engagement-panel-genius-lyrics'))
  4579.  
  4580.  
  4581. },100)
  4582.  
  4583.  
  4584.  
  4585. }
  4586.  
  4587.  
  4588. /*
  4589.  
  4590. To be implement: timeline
  4591.  
  4592. timeline.setTimeout
  4593. timeline.clearTimeout
  4594. timeline.setInterval
  4595. timeline.clearInterval
  4596.  
  4597. */
  4598.  
  4599.  
  4600.  
  4601. //Object.keys($0).filter(key=>!(key in $0.constructor.prototype))
  4602.  
  4603. //Object.getOwnPropertyNames(window).filter(k=>k.startsWith('HTML'))
  4604. //Object.getOwnPropertyNames(window).filter(k=>k.startsWith('HTML')).filter(k=>$0 instanceof window[k])
  4605.  
  4606.  
  4607. /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */
  4608.  
  4609. /*
  4610. fix bug for comment section - version 1.8.7
  4611. This issue is the bug in browser's rendering
  4612. I guess, this is due to the lines clamp with display:-webkit-box
  4613. use stupid coding to let it re-render when its content become visible
  4614. /*
  4615.  
  4616. ytd-expander[should-use-number-of-lines][collapsed] > #content.ytd-expander {
  4617. color: var(--yt-spec-text-primary);
  4618. display: -webkit-box;
  4619. overflow: hidden;
  4620. max-height: none;
  4621. -webkit-box-orient: vertical;
  4622. -webkit-line-clamp: var(--ytd-expander-max-lines, 4);
  4623. }
  4624.  
  4625. // v1.8.36 imposed a effective solution for fixing this bug
  4626.  
  4627. */
  4628.  
  4629. /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */
  4630.  
  4631.  
  4632. /**
  4633. *
  4634.  
  4635.  
  4636. f.initChildrenObserver=function(){var a=this;this.observer=new MutationObserver(function(){return a.childrenChanged()});
  4637. this.observer.observe(this.$.content,{subtree:!0,childList:!0,attributes:!0});this.childrenChanged()};
  4638. 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()})}))};
  4639.  
  4640.  
  4641. f.onIronResize=function(){this.recomputeOnResize&&this.childrenChanged()};
  4642.  
  4643.  
  4644. onButtonClick_:function(){this.fire("yt-close-upsell-dialog")},
  4645. 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};
  4646. n(xI,L);f=xI.prototype;f.alwaysToggleableChanged=function(){this.alwaysToggleable&&(this.canToggle=!0)};
  4647.  
  4648.  
  4649. f.calculateCanCollapse=function(){this.canToggle=this.shouldUseNumberOfLines?this.alwaysToggleable||this.$.content.offsetHeight<this.$.content.scrollHeight:this.alwaysToggleable||this.$.content.scrollHeight>this.collapsedHeight};
  4650. f.detachObserver=function(){this.observer&&this.observer.disconnect()};
  4651.  
  4652. *
  4653. *
  4654. *
  4655. */
  4656.  
  4657.  
  4658.  
  4659.  
  4660. })();
  4661.  
  4662.  
  4663.  
  4664.  
  4665.  
  4666.  
  4667.  
  4668.  
  4669.  
  4670.  
  4671.  
  4672.  
  4673.  
  4674.  
  4675.  
  4676.  
  4677.  
  4678.  
  4679.  
  4680.  
  4681.  
  4682.  
  4683.  
  4684.  
  4685.  
  4686.  
  4687.  
  4688.  
  4689.  
  4690.  
  4691.  
  4692.  
  4693.  
  4694.  
  4695.  
  4696.  
  4697.  
  4698.  
  4699.  
  4700.  
  4701.  
  4702.  
  4703. // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js
  4704.  
  4705. }
  4706.  
  4707.  
  4708. ;!(function $$() {
  4709. 'use strict';
  4710.  
  4711. if(document.documentElement==null) return window.requestAnimationFrame($$)
  4712.  
  4713. const cssTxt = GM_getResourceText("contentCSS");
  4714.  
  4715. function addStyle (styleText) {
  4716. const styleNode = document.createElement('style');
  4717. styleNode.type = 'text/css';
  4718. styleNode.textContent = styleText;
  4719. document.documentElement.appendChild(styleNode);
  4720. return styleNode;
  4721. }
  4722.  
  4723. addStyle (cssTxt);
  4724.  
  4725. main(window.$);
  4726.  
  4727.  
  4728. // Your code here...
  4729. })();