Tabview Youtube

Make comments and lists into tabs

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

  1. // ==UserScript==
  2. // @name Tabview Youtube
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.8.7
  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/ccaf34030b4053cbd1f41cfa09582f7ac7385dd5/css/style_content.css
  9. // @icon https://github.com/cyfung1031/Tabview-Youtube/raw/main/images/icon128p.png
  10. // @require https://cdnjs.cloudflare.com/ajax/libs/cash/8.1.0/cash.min.js
  11. // @grant GM_getResourceText
  12. // @run-at document-start
  13. // @license MIT https://github.com/cyfung1031/Tabview-Youtube/blob/main/LICENSE
  14. // @noframes
  15. // ==/UserScript==
  16. function main($){
  17. // MIT License
  18. // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js
  19.  
  20.  
  21.  
  22.  
  23.  
  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 = '2021/07/03';
  67.  
  68. const svgComments = `
  69. <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
  70. 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
  71. 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
  72. 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
  73. 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"/>
  74. <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
  75. 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
  76. c3.27,0,5.93-2.66,5.93-5.929V8.465C60,5.195,57.34,2.535,54.072,2.535z"/>
  77. `.trim();
  78.  
  79. 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
  80. z M91,39h43v34H91V39z M61,259H30v-34h31V259z M61,73H30V39h31V73z M134,259H91v-34h43V259z M123,176.708v-55.417
  81. 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
  82. C128.84,188.011,123,184.958,123,176.708z M207,259h-43v-34h43V259z M207,73h-43V39h43V73z M268,259h-31v-34h31V259z M268,73h-31V39
  83. h31V73z"/>`.trim();
  84.  
  85. 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
  86. 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
  87. 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
  88. 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
  89. 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
  90. 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
  91. 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
  92. 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
  93. 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
  94. 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
  95. 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
  96. c0.353,0.331,0.53,0.731,0.53,1.196C14.667,6.703,14.49,7.101,14.137,7.429z"/>`.trim();
  97.  
  98. const svgPlayList = `
  99. <rect x="0" y="64" width="256" height="42.667"/>
  100. <rect x="0" y="149.333" width="256" height="42.667"/>
  101. <rect x="0" y="234.667" width="170.667" height="42.667"/>
  102. <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
  103. 298.667,277.333 298.667,362.667 341.333,362.667 341.333,277.333 426.667,277.333 426.667,234.667"/>
  104. `.trim();
  105.  
  106. // --- Youtube Video Testing :
  107. // Square Video: https://www.youtube.com/watch?v=L0RXVnRbFg8
  108. // Square Video: https://www.youtube.com/watch?v=bK_rKhMIotU
  109. // ---
  110.  
  111.  
  112. const LAYOUT_TWO_COLUMNS=1;
  113. const LAYOUT_THEATER=2;
  114. const LAYOUT_FULLSCREEN=4;
  115. const LAYOUT_CHATROOM=8;
  116. const LAYOUT_CHATROOM_COLLASPED=16;
  117. const LAYOUT_TAB_EXPANDED = 32;
  118. const LAYOUT_ENGAGEMENT_PANEL_EXPAND = 64;
  119.  
  120. const mtoInterval1=40;
  121. const mtoInterval2=150;
  122.  
  123. let lastVideoURL = null;
  124.  
  125. const WeakRef = window.WeakRef;
  126. const mWeakRef=WeakRef?(o=>o?new WeakRef(o):null):(o=>o||null);
  127. const kRef = (wr=>(wr&&wr.deref)?wr.deref():wr);
  128.  
  129. let cmItem = null;
  130.  
  131. function tracer(key, cmp){
  132. if(cmp>0) return tracer[key]===cmp;
  133. return (tracer[key]=Date.now());
  134. }
  135.  
  136. function racer(key, f){
  137. let now = Date.now();
  138. const kTime = `${key}$$1`
  139. let t=racer[kTime]||0;
  140.  
  141. if(now<t){
  142. const kCount = `${key}$$2`;
  143. racer[kCount]=(racer[kCount]||0)+1;
  144. if(racer[kCount]===1){
  145. let g = f;
  146. requestAnimationFrame(()=>{
  147. racer[kCount]=0;
  148. g();
  149. g=null;
  150. })
  151. }
  152. }else{
  153. racer[kTime]=now+16;
  154. f();
  155. }
  156. }
  157.  
  158. class ScriptEF {
  159. constructor() {
  160. this._id = scriptEC;
  161. }
  162. isValid() {
  163. return this._id === scriptEC;
  164. }
  165. }
  166. class Timeout {
  167. set(f, d, repeatCount) {
  168. if (this.cid > 0) return;
  169. let sEF = new ScriptEF();
  170. if (repeatCount > 0) {
  171. let rc = repeatCount;
  172. const g = () => {
  173. this.cid = 0;
  174. if (!sEF.isValid()) return;
  175. let res = f();
  176. if (--rc <= 0) return;
  177. if (res === true) this.cid = setTimeout(g, d);
  178. }
  179. g();
  180.  
  181. } else {
  182. const g = () => {
  183. this.cid = 0;
  184. if (!sEF.isValid()) return;
  185. if (f() === true) this.cid = setTimeout(g, d);
  186. }
  187. this.cid = setTimeout(g, d);
  188. }
  189. }
  190. clear() {
  191. if (this.cid > 0) clearTimeout(this.cid);
  192. }
  193. isEmpty() {
  194. return !this.cid
  195. }
  196. }
  197. class Mutex{
  198. constructor(){
  199. this.p=Promise.resolve()
  200. }
  201. lockWith(f){
  202. this.p=this.p.then(()=>{
  203. return new Promise(f)
  204. }).catch(console.warn)
  205. }
  206.  
  207. }
  208.  
  209.  
  210.  
  211. function prettyElm(elm) {
  212. if (!elm || !elm.nodeName) return null;
  213. const eId = elm.id || null;
  214. const eClsName = elm.className || null;
  215. return [elm.nodeName.toLowerCase(), typeof eId == 'string' ? "#" + eId : '', typeof eClsName == 'string' ? '.' + eClsName.replace(/\s+/g, '.') : ''].join('').trim();
  216. }
  217. function extractTextContent(elm){
  218. 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,'')
  219. }
  220.  
  221. function addScript(scriptText) {
  222. const scriptNode = document.createElement('script');
  223. scriptNode.type = 'text/javascript';
  224. scriptNode.textContent = scriptText;
  225. document.documentElement.appendChild(scriptNode);
  226. return scriptNode;
  227. }
  228. function addStyle(styleText, container) {
  229. const styleNode = document.createElement('style');
  230. //styleNode.type = 'text/css';
  231. styleNode.textContent = styleText;
  232. (container||document.documentElement).appendChild(styleNode);
  233. return styleNode;
  234. }
  235.  
  236.  
  237.  
  238.  
  239.  
  240.  
  241. function isDOMVisible( elem ){
  242. // jQuery version : https://github.com/jquery/jquery/blob/a684e6ba836f7c553968d7d026ed7941e1a612d8/src/css/hiddenVisibleSelectors.js
  243. return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
  244. }
  245.  
  246. function isNonEmptyString(s){
  247. return typeof s=='string'&&s.length>0;
  248. }
  249.  
  250.  
  251. function nativeFunc(dom, property, args){
  252. dom.dispatchEvent(new CustomEvent("userscript-call-dom-func", {detail: {property, args}}))
  253. }
  254. function akAttr(cssElm, attrName, isNegative, flag){
  255. let u = parseInt(cssElm.getAttribute(attrName)||0)||0;
  256. let ak = Math.abs(u);
  257. if(ak>100 && isNegative && u<0){
  258.  
  259. }else if(ak>100 && !isNegative && u>0){
  260.  
  261. }else{
  262. if(ak<=100) {
  263. ak=101;
  264. } else {
  265. ak++;
  266. if(ak>=800) ak=101;
  267. }
  268. // 101, 102, ... 799, 101
  269. }
  270.  
  271. let s = ''+(isNegative?-ak:ak);
  272. flag = flag || '';
  273.  
  274. cssElm.setAttribute(attrName, s+flag)
  275. }
  276.  
  277.  
  278.  
  279. let timeout_resize_for_layout_change=new Timeout();
  280.  
  281.  
  282.  
  283. function layoutStatusChanged(old_layoutStatus, new_layoutStatus) {
  284.  
  285.  
  286. if(old_layoutStatus === new_layoutStatus) return;
  287.  
  288. const cssElm = kRef(ytdFlexy);
  289. if (!cssElm) return;
  290.  
  291. const new_isExpandedChat = !(new_layoutStatus & LAYOUT_CHATROOM_COLLASPED) && (new_layoutStatus & LAYOUT_CHATROOM)
  292. const new_isCollaspedChat = (new_layoutStatus & LAYOUT_CHATROOM_COLLASPED) && (new_layoutStatus & LAYOUT_CHATROOM)
  293. const new_isTwoColumns = new_layoutStatus & LAYOUT_TWO_COLUMNS;
  294. const new_isTheater = new_layoutStatus & LAYOUT_THEATER;
  295. const new_isTabExpanded = new_layoutStatus & LAYOUT_TAB_EXPANDED;
  296. const new_isFullScreen = new_layoutStatus & LAYOUT_FULLSCREEN;
  297. const new_isExpandEPanel = new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPAND;
  298.  
  299. function showTabOrChat() {
  300.  
  301. layoutStatusMutex.lockWith(unlock => {
  302.  
  303. if (lastShowTab == '#chatroom') {
  304.  
  305. if (new_isTabExpanded) switchTabActivity(null)
  306. if (!new_isExpandedChat) ytBtnExpandChat();
  307.  
  308. } else if(lastShowTab && lastShowTab.indexOf('#engagement-panel-')==0){
  309.  
  310. if (new_isTabExpanded) switchTabActivity(null)
  311. if (!new_isExpandEPanel) ytBtnOpenEngagementPanel(lastShowTab);
  312. }else {
  313.  
  314. if (new_isExpandedChat) ytBtnCollapseChat()
  315. if (!new_isTabExpanded) setToActiveTab();
  316.  
  317. }
  318.  
  319. setTimeout(unlock, 40);
  320.  
  321. })
  322. }
  323.  
  324. function hideTabAndChat() {
  325.  
  326. layoutStatusMutex.lockWith(unlock => {
  327.  
  328. if (new_isTabExpanded) switchTabActivity(null)
  329. if (new_isExpandedChat) ytBtnCollapseChat()
  330. if (new_isExpandEPanel) ytBtnCloseEngagementPanels();
  331.  
  332.  
  333. setTimeout(unlock, 40);
  334.  
  335. })
  336.  
  337. }
  338.  
  339.  
  340. if(new_isExpandedChat || new_isTabExpanded || new_isExpandEPanel){
  341. if(statusCollasped !== 1) statusCollasped = 1;
  342. }else{
  343. if(statusCollasped === 1) statusCollasped = 2;
  344. }
  345.  
  346. let changes = 0;
  347.  
  348. if(old_layoutStatus !== null ) changes = old_layoutStatus ^ new_layoutStatus;
  349.  
  350. let chat_collasped_changed = !!(changes & LAYOUT_CHATROOM_COLLASPED)
  351. let tab_expanded_changed = !!(changes & LAYOUT_TAB_EXPANDED)
  352. let theater_mode_changed = !!(changes & LAYOUT_THEATER)
  353. let column_mode_changed = !!(changes & LAYOUT_TWO_COLUMNS)
  354. let fullscreen_mode_changed = !!(changes & LAYOUT_FULLSCREEN)
  355. let epanel_expanded_changed = !!(changes & LAYOUT_ENGAGEMENT_PANEL_EXPAND)
  356. let tab_change = (tab_expanded_changed?1:0)|(chat_collasped_changed?2:0)|(epanel_expanded_changed?4:0);
  357. let isChatOrTabExpandTriggering = tab_change==0?false:(
  358. (tab_expanded_changed && new_isTabExpanded) ||
  359. (chat_collasped_changed && new_isExpandedChat) ||
  360. (epanel_expanded_changed && new_isExpandEPanel)
  361. );
  362. let isChatOrTabCollaspeTriggering = tab_change==0?false:(
  363. (tab_expanded_changed && !new_isTabExpanded) ||
  364. (chat_collasped_changed && new_isCollaspedChat) ||
  365. (epanel_expanded_changed && !new_isExpandEPanel)
  366. );
  367.  
  368. let moreThanOneShown = (new_isTabExpanded + new_isExpandedChat + new_isExpandEPanel)>1
  369.  
  370. let requestVideoResize=false;
  371.  
  372. if(fullscreen_mode_changed || new_isFullScreen){
  373.  
  374. }else if(tab_change==0 && column_mode_changed && new_isTwoColumns && !new_isTheater && statusCollasped === 1 && moreThanOneShown){
  375.  
  376. showTabOrChat();
  377. requestVideoResize=true;
  378.  
  379. }else if (tab_change==2 && new_isExpandedChat && new_isTwoColumns && !new_isTheater && statusCollasped === 1 && new_isTabExpanded && !column_mode_changed) {
  380. switchTabActivity(null);
  381. requestVideoResize=true;
  382. } else if ( isChatOrTabExpandTriggering && new_isTwoColumns && new_isTheater && statusCollasped === 1 && !theater_mode_changed && !column_mode_changed ) {
  383. ytBtnCancelTheater();
  384. requestVideoResize=true;
  385. } else if (new_isTwoColumns && new_isTheater && statusCollasped === 1) {
  386. hideTabAndChat();
  387. requestVideoResize=true;
  388. } else if (isChatOrTabCollaspeTriggering && new_isTwoColumns && !new_isTheater && statusCollasped === 2 && !column_mode_changed ) {
  389. ytBtnSetTheater();
  390. requestVideoResize=true;
  391. } else if(tab_change==0 && (column_mode_changed || theater_mode_changed) && new_isTwoColumns && !new_isTheater && statusCollasped !==1){
  392. showTabOrChat();
  393. requestVideoResize=true;
  394. } else if(!new_isFullScreen && new_isTwoColumns && !new_isTheater && (new_isCollaspedChat || !new_isExpandedChat) && !new_isTabExpanded){
  395. // bug fix for restoring from mini player
  396.  
  397. layoutStatusMutex.lockWith(unlock => {
  398.  
  399. if (new_isExpandedChat) ytBtnCollapseChat()
  400. setToActiveTab();
  401.  
  402. setTimeout(unlock, 40);
  403.  
  404. })
  405. requestVideoResize=true;
  406.  
  407. } else if(tab_expanded_changed){
  408.  
  409. requestVideoResize=true;
  410.  
  411. }
  412.  
  413.  
  414. if(column_mode_changed && !chat_collasped_changed && new_isExpandedChat){
  415.  
  416. runAfterExpandChat();
  417.  
  418. }
  419.  
  420.  
  421.  
  422. if(requestVideoResize){
  423.  
  424. timeout_resize_for_layout_change.clear();
  425. timeout_resize_for_layout_change.set(() => {
  426. window.dispatchEvent(new Event('resize'))
  427. } , 92)
  428. }else if (timeout_resize_for_layout_change.isEmpty() && (Date.now()) - lastResizeAt > 600){
  429. timeout_resize_for_layout_change.set(() => {
  430. if((Date.now()) - lastResizeAt > 600) window.dispatchEvent(new Event('resize'));
  431. }, 62)
  432. }
  433.  
  434. }
  435.  
  436.  
  437. const $ws={
  438. _layoutStatus:null,
  439. layoutStatus_pending:false
  440. }
  441.  
  442. let wls=new class {
  443. get layoutStatus(){
  444. return this._layoutStatus;
  445. }
  446. set layoutStatus(nv){
  447.  
  448. if(nv===null){
  449. this._layoutStatus=null;
  450. statusCollasped=0;
  451. return;
  452. }
  453. if(nv === this._layoutStatus) return;
  454.  
  455. if(!this.layoutStatus_pending) {
  456. this.layoutStatus_pending=true;
  457. const old_layoutStatus=this._layoutStatus;
  458. layoutStatusMutex.lockWith(unlock=>{
  459.  
  460. this.layoutStatus_pending=false;
  461. const new_layoutStatus = this._layoutStatus;
  462. layoutStatusChanged(old_layoutStatus, new_layoutStatus);
  463.  
  464. setTimeout(unlock,40)
  465.  
  466.  
  467. })
  468. }
  469.  
  470. this._layoutStatus=nv;
  471. }
  472. };
  473.  
  474.  
  475.  
  476.  
  477.  
  478. const svgElm = (w, h, vw, vh, p) => `<svg width="${w}" height="${h}" viewBox="0 0 ${vw} ${vh}" preserveAspectRatio="xMidYMid meet">${p}</svg>`
  479.  
  480. let settings = {
  481. defaultTab: "#tab-videos"
  482. };
  483.  
  484.  
  485. let mtoInterval = mtoInterval1;
  486.  
  487. function isVideoPlaying(video) {
  488. return video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA;
  489. }
  490.  
  491. function wAttr(elm, attr, kv){
  492. if(!elm || kv===null) {}
  493. else if(kv===true){ elm.setAttribute(attr,'') }
  494. else if(kv===false){ elm.removeAttribute(attr) }
  495. else if (typeof kv=='string'){ elm.setAttribute(attr, kv) }
  496. }
  497.  
  498. function hideTabBtn(tabBtn){
  499. var isActiveBefore = tabBtn.classList.contains('active');
  500. tabBtn.classList.add("tab-btn-hidden");
  501. if (isActiveBefore) {
  502. setToActiveTab();
  503. }
  504. }
  505.  
  506. function hasAttribute(obj, key){
  507. return obj && obj.hasAttribute(key);
  508. }
  509.  
  510. function isTheater(){
  511. const cssElm=kRef(ytdFlexy);
  512. return (cssElm && cssElm.hasAttribute('theater'))
  513. }
  514.  
  515. function isFullScreen(){
  516. const cssElm=kRef(ytdFlexy);
  517. return (cssElm && cssElm.hasAttribute('fullscreen'))
  518. }
  519.  
  520. function isChatExpand(){
  521. const cssElm=kRef(ytdFlexy);
  522. return cssElm && cssElm.hasAttribute('userscript-chatblock') && !cssElm.hasAttribute('userscript-chat-collapsed')
  523. }
  524. function isWideScreenWithTwoColumns(){
  525. const cssElm=kRef(ytdFlexy);
  526. return (cssElm && cssElm.hasAttribute('is-two-columns_'))
  527. }
  528.  
  529. function isAnyActiveTab(){
  530. return $('#right-tabs .tab-btn.active').length>0
  531. }
  532.  
  533. function isEngagementPanelExpanded(){
  534. const cssElm=kRef(ytdFlexy);
  535. return (cssElm && +cssElm.getAttribute('userscript-engagement-panel')>0)
  536. }
  537.  
  538. function engagement_panels_(){
  539.  
  540. let res = [];
  541.  
  542. let v = 0, k =1, count=0;
  543. for(const ePanel of document.querySelectorAll('ytd-watch-flexy ytd-engagement-panel-section-list-renderer[tabview-attr-checked]')){
  544.  
  545. let visibility = ePanel.getAttribute('visibility') //ENGAGEMENT_PANEL_VISIBILITY_EXPANDED //ENGAGEMENT_PANEL_VISIBILITY_HIDDEN
  546.  
  547. switch(visibility){
  548. case 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED': v|=k; count++; res.push({ePanel, k, visible: true}); break;
  549. case 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN': res.push({ePanel, k, visible: false}); break;
  550. default: res.push({ePanel, k, visible: false});
  551. }
  552.  
  553. k=k<<1;
  554.  
  555. }
  556. return {list:res, value: v, count: count};
  557. }
  558.  
  559. function ytBtnOpenEngagementPanel(panel_id){
  560.  
  561. if(typeof panel_id =='string') {
  562. panel_id = panel_id.replace('#engagement-panel-','');
  563. panel_id = parseInt(panel_id);
  564. }
  565. if(panel_id>=0){}else return false;
  566.  
  567. let panels = engagement_panels_();
  568.  
  569. for(const {ePanel, k, visible} of panels.list){
  570. if((panel_id & k) === k) {
  571. if(!visible) ePanel.setAttribute('visibility',"ENGAGEMENT_PANEL_VISIBILITY_EXPANDED");
  572. }else{
  573. if(visible) ytBtnCloseEngagementPanel(ePanel);
  574. }
  575. }
  576.  
  577. }
  578.  
  579. function ytBtnCloseEngagementPanel(s){
  580. //ePanel.setAttribute('visibility',"ENGAGEMENT_PANEL_VISIBILITY_HIDDEN");
  581. let btn = s.querySelector('ytd-watch-flexy ytd-engagement-panel-title-header-renderer #header>#visibility-button>ytd-button-renderer');
  582. if(btn){
  583. btn.click();
  584. }
  585. }
  586. function ytBtnCloseEngagementPanels(){
  587. if(isEngagementPanelExpanded()){
  588. for(const s of document.querySelectorAll('ytd-watch-flexy ytd-engagement-panel-section-list-renderer[tabview-attr-checked]')){
  589. if(s.getAttribute('visibility')=="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED") ytBtnCloseEngagementPanel(s);
  590. }
  591. }
  592. }
  593.  
  594. function ytBtnSetTheater(){
  595. if(!isTheater()){
  596. const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
  597. if(sizeBtn) sizeBtn.click();
  598. }
  599. }
  600. function ytBtnCancelTheater(){
  601. if(isTheater()){
  602. const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
  603. if(sizeBtn) sizeBtn.click();
  604. }
  605. }
  606.  
  607. function ytBtnExpandChat(){
  608. let button = document.querySelector('ytd-live-chat-frame#chat[collapsed]>.ytd-live-chat-frame#show-hide-button')
  609. if (button) button.querySelector('ytd-toggle-button-renderer').click();
  610. }
  611. function ytBtnCollapseChat(){
  612. let button = document.querySelector('ytd-live-chat-frame#chat:not([collapsed])>.ytd-live-chat-frame#show-hide-button')
  613. if (button) button.querySelector('ytd-toggle-button-renderer').click();
  614. }
  615.  
  616.  
  617. function hackImgShadow(imgShadow){
  618. // add to #columns and add back after loaded
  619. let img = imgShadow.querySelector('img')
  620. if(!img)return;
  621.  
  622. let p=imgShadow.parentNode
  623. let z=$(imgShadow).clone()[0]; //to occupy the space
  624. p.replaceChild(z, imgShadow)
  625. $(imgShadow).prependTo('#columns'); // refer to css hack
  626.  
  627. function onload(evt){
  628. if(evt) this.removeEventListener('load',onload,false)
  629. p.replaceChild(imgShadow, z)
  630. p=null;
  631. z=null;
  632. imgShadow=null;
  633. }
  634.  
  635. if (img.complete) onload();
  636. else img.addEventListener('load',onload,false)
  637. }
  638.  
  639.  
  640. const Q={}
  641. const FOnce={}
  642.  
  643. const $callOnceAsync=async function(key){
  644. if (FOnce[key] && FOnce[key]() === false) FOnce[key] = null
  645. }
  646.  
  647.  
  648. function chatFrameContentDocument(){
  649. // non-null if iframe exist && contentDocument && readyState = complete
  650.  
  651. let iframe = document.querySelector('ytd-live-chat-frame iframe#chatframe');
  652. if(!iframe) return null; //iframe must be there
  653. let cDoc = null;
  654. try{
  655. cDoc = iframe.contentDocument;
  656. }catch(e){}
  657. if(!cDoc) return null;
  658. if(cDoc.readyState != 'complete') return null; //we must wait for its completion
  659.  
  660. return cDoc;
  661.  
  662. }
  663.  
  664. function chatFrameElement(cssSelector){
  665. let cDoc = chatFrameContentDocument();
  666. if(!cDoc) return null;
  667. let elm = null;
  668. try{
  669. elm = cDoc.querySelector(cssSelector)
  670. }catch(e){
  671. console.log('iframe error', e)
  672. }
  673. return elm;
  674. }
  675.  
  676.  
  677.  
  678.  
  679.  
  680.  
  681.  
  682.  
  683.  
  684.  
  685.  
  686.  
  687.  
  688.  
  689.  
  690.  
  691.  
  692.  
  693. function fixTabs(){
  694.  
  695. if(!scriptEnable)return;
  696.  
  697.  
  698. let queryElement=document.querySelector('*:not(#tab-videos)>#related:not([non-placeholder-videos]) > ytd-watch-next-secondary-results-renderer')
  699.  
  700. let isRelocated = !!queryElement;
  701.  
  702.  
  703.  
  704. if(isRelocated){
  705.  
  706. let relocatedRelated = queryElement.parentNode; // NOT NULL
  707.  
  708. let right_tabs = document.querySelector('#right-tabs');
  709. let tab_videos = right_tabs.querySelector("#tab-videos");
  710.  
  711. if(!right_tabs || !tab_videos) return;
  712.  
  713. for(const s of relocatedRelated.querySelectorAll('#related')){
  714. s.setAttribute('non-placeholder-videos','')
  715. }
  716.  
  717. let target_container = document.querySelector('ytd-watch-flexy:not([is-two-columns_]) #primary-inner, ytd-watch-flexy[is-two-columns_] #secondary-inner')
  718.  
  719. if(target_container) target_container.append(right_tabs) // last-child
  720.  
  721.  
  722. let videos_related = relocatedRelated; // NOT NULL
  723. $('[placeholder-videos]').removeAttr('placeholder-videos');
  724. $('[placeholder-for-youtube-play-next-queue]').removeAttr('placeholder-for-youtube-play-next-queue');
  725.  
  726. tab_videos.appendChild(videos_related);
  727. let videos_results_renderer = relocatedRelated.querySelector("ytd-watch-next-secondary-results-renderer");
  728. if(videos_results_renderer) videos_results_renderer.setAttribute('data-dom-changed-by-tabview-youtube',scriptVersionForExternal);
  729. videos_related.setAttribute('placeholder-for-youtube-play-next-queue','')
  730. videos_related.setAttribute('placeholder-videos','')
  731.  
  732. $('[placeholder-videos]').on("scroll", makeBodyScrollByEvt);
  733.  
  734.  
  735.  
  736.  
  737. }
  738.  
  739.  
  740. let chatroom=null;
  741. if(chatroom=document.querySelector('*:not([data-positioner="before|#chat"]) + ytd-live-chat-frame#chat, ytd-live-chat-frame#chat:first-child')){
  742.  
  743. let positioner = document.querySelector('tabview-youtube-positioner[data-positioner="before|#chat"]');
  744. if(positioner) positioner.remove();
  745.  
  746.  
  747. if(document.querySelector('.YouTubeLiveFilledUpView')){
  748. // no relocation
  749. }else{
  750.  
  751. $(chatroom).insertBefore('#right-tabs')
  752.  
  753. }
  754.  
  755. $(positioner?positioner:document.createElement('tabview-youtube-positioner')).attr('data-positioner','before|#chat').insertBefore(chatroom)
  756.  
  757.  
  758. }
  759.  
  760.  
  761. }
  762.  
  763. const injectionScript_fixAutoComplete=function(){
  764.  
  765. // https://cdnjs.cloudflare.com/ajax/libs/JavaScript-autoComplete/1.0.4/auto-complete.min.js
  766.  
  767. for(const s of document.querySelectorAll('[autocomplete="off"]:not([data-autocomplete-results-id])')){
  768.  
  769.  
  770. let sc = s.sc;
  771. if(sc instanceof HTMLElement){
  772.  
  773. let id=Date.now();
  774. s.setAttribute('data-autocomplete-results-id',id);
  775. sc.setAttribute('data-autocomplete-input-id', id);
  776. if(window.WeakRef){
  777. s._sc=new WeakRef(sc);
  778. s.sc=null;
  779. delete s.sc;
  780. Object.defineProperty(s,'sc',{
  781. get: function() { return s._sc.deref()||null; },
  782. enumerable: true,
  783. configurable: true
  784. })
  785. }
  786.  
  787. if(sc.hasAttribute('autocomplete-disable-updatesc') && typeof s.updateSC =='function'){
  788.  
  789. window.removeEventListener('resize', s.updateSC);
  790. s.updateSC=function(){};
  791.  
  792. }
  793.  
  794. sc.dispatchEvent(new CustomEvent('autocomplete-sc-exist'));
  795.  
  796.  
  797. }
  798.  
  799. }
  800.  
  801. };
  802.  
  803. function handlerAutoCompleteExist(){
  804.  
  805.  
  806. let autoComplete = this;
  807.  
  808. autoComplete.removeEventListener('autocomplete-sc-exist', handlerAutoCompleteExist, false)
  809.  
  810. let domId= autoComplete.getAttribute('data-autocomplete-input-id')
  811. let searchBox = autoComplete.ownerDocument.querySelector(`[data-autocomplete-results-id="${domId}"]`)
  812.  
  813. if(!domId || !searchBox) return;
  814.  
  815. let positioner=searchBox.nextSibling;
  816. if(positioner && positioner.nodeName.toLowerCase()=="autocomplete-positioner"){
  817. }else if(positioner && positioner.nodeName.toLowerCase()!="autocomplete-positioner"){
  818. $(positioner=document.createElement("autocomplete-positioner")).insertAfter(searchBox);
  819. }else{
  820. $(positioner=document.createElement("autocomplete-positioner")).prependTo(searchBox.parentNode);
  821. }
  822. $(autoComplete).prependTo(positioner);
  823.  
  824. positioner.style.setProperty('--sb-margin-bottom',getComputedStyle(searchBox).marginBottom)
  825. positioner.style.setProperty('--height',searchBox.offsetHeight + 'px')
  826.  
  827. }
  828.  
  829. function mtf_fixAutoCompletePosition(elmAutoComplete){
  830.  
  831. elmAutoComplete.setAttribute('autocomplete-disable-updatesc','')
  832. elmAutoComplete.addEventListener('autocomplete-sc-exist',handlerAutoCompleteExist, false)
  833.  
  834. addScript(`!!(${injectionScript_fixAutoComplete+''})()`);
  835.  
  836. }
  837.  
  838. function mtf_AfterFixTabs(){
  839.  
  840.  
  841. let ytdFlexyElm = kRef(ytdFlexy);
  842. if(!scriptEnable || !ytdFlexyElm) return;
  843.  
  844. const rootElement = Q.mutationTarget || ytdFlexyElm;
  845.  
  846.  
  847.  
  848. 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])')
  849.  
  850. if(autocomplete){
  851.  
  852. const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')
  853.  
  854.  
  855. if(searchBox){
  856.  
  857. autocomplete.parentNode.setAttribute('position-fixed-by-tabview-youtube','');
  858. autocomplete.setAttribute('position-fixed-by-tabview-youtube','');
  859. autocomplete.setAttribute('userscript-scrollbar-render','')
  860.  
  861. if(!searchBox.hasAttribute('is-set-click-to-toggle')){
  862. searchBox.setAttribute('is-set-click-to-toggle','')
  863. searchBox.addEventListener('click',function(){
  864.  
  865.  
  866. setTimeout(()=>{
  867. const autocomplete=document.querySelector(`.autocomplete-suggestions[data-autocomplete-input-id="${ this.getAttribute('data-autocomplete-results-id') }"]`)
  868.  
  869. if(!autocomplete)return;
  870.  
  871. const isNotEmpty = (autocomplete.textContent||'').length>0 && (this.value||'').length>0;
  872. if( isNotEmpty ){
  873.  
  874. let elmVisible=isDOMVisible(autocomplete)
  875.  
  876. if(elmVisible) $(autocomplete).hide(); else $(autocomplete).show();
  877.  
  878. }
  879.  
  880. },20);
  881.  
  882. })
  883.  
  884. let timeoutOnce_searchbox_keyup=new Timeout();
  885. searchBox.addEventListener('keyup',function(){
  886.  
  887. timeoutOnce_searchbox_keyup.set(()=>{
  888. const autocomplete=document.querySelector(`.autocomplete-suggestions[data-autocomplete-input-id="${ this.getAttribute('data-autocomplete-results-id') }"]`)
  889.  
  890. if(!autocomplete)return;
  891.  
  892. const isNotEmpty = (autocomplete.textContent||'').length>0 && (this.value||'').length >0
  893. if( isNotEmpty ){
  894.  
  895. let elmVisible=isDOMVisible(autocomplete)
  896.  
  897. if(!elmVisible) $(autocomplete).show();
  898.  
  899. }
  900.  
  901. },20);
  902.  
  903. })
  904.  
  905. }
  906.  
  907.  
  908.  
  909. }
  910.  
  911. }
  912.  
  913.  
  914.  
  915.  
  916.  
  917. let currentLastVideo=rootElement.querySelector('[placeholder-videos] #items ytd-compact-video-renderer:last-of-type')
  918. let prevLastVideo=kRef(_cachedLastVideo);
  919.  
  920. if(prevLastVideo!==currentLastVideo && currentLastVideo){
  921. _cachedLastVideo=mWeakRef(currentLastVideo);
  922. }
  923. if(prevLastVideo!==currentLastVideo && currentLastVideo && prevLastVideo ){
  924.  
  925. let isPrevRemoved= !prevLastVideo.parentNode
  926.  
  927.  
  928. function getVideoListHash(){
  929.  
  930. let res = [...document.querySelectorAll('[placeholder-videos] #items ytd-compact-video-renderer')].map(renderer=>{
  931. return renderer.querySelector('a[href*="watch"][href*="v="]').getAttribute('href')
  932.  
  933. }).join('|')
  934. // /watch?v=XXXXX|/watch?v=XXXXXX|/watch?v=XXXXXX
  935.  
  936. // alternative - DOM.data.videoId
  937. // let elms = document.querySelectorAll('[placeholder-videos] #items ytd-compact-video-renderer')
  938. // let res = [...elms].map(elm=>elm.data.videoId||'').join('|') ;
  939. if(res.indexOf('||')>=0){
  940. res='';
  941. }
  942.  
  943. return res?res:null;
  944. }
  945.  
  946. if(isPrevRemoved){
  947.  
  948. // this is the replacement of videos instead of addition
  949. const searchBox=document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')
  950.  
  951. let currentPlayListHash= getVideoListHash() || null;
  952.  
  953. if(!currentPlayListHash){
  954.  
  955. }else if(!videoListBeforeSearch && searchBox){
  956.  
  957. videoListBeforeSearch= currentPlayListHash;
  958. if(videoListBeforeSearch){
  959. //console.log('fromSearch', videoListBeforeSearch)
  960.  
  961. requestAnimationFrame(function(){
  962.  
  963. let renderer = document.querySelector('[placeholder-videos] ytd-watch-next-secondary-results-renderer');
  964. if(searchBox && searchBox.parentNode) searchBox.blur();
  965.  
  966. if(renderer){
  967. let scrollParent = renderer.parentNode;
  968. if(scrollParent.scrollHeight>scrollParent.offsetHeight){
  969. let targetTop = renderer.offsetTop;
  970. if(searchBox && searchBox.parentNode==scrollParent ) targetTop-=searchBox.offsetHeight
  971. scrollParent.scrollTop= targetTop - scrollParent.firstChild.offsetTop;
  972. }
  973. }
  974. });
  975.  
  976. }
  977.  
  978. }else if(videoListBeforeSearch){
  979.  
  980. if(currentPlayListHash != videoListBeforeSearch){
  981.  
  982. videoListBeforeSearch=null;
  983. //console.log('fromSearch', videoListBeforeSearch)
  984. }
  985.  
  986. }
  987.  
  988.  
  989. }
  990.  
  991. }
  992.  
  993.  
  994.  
  995.  
  996. }
  997.  
  998. function base_ChatExist(){
  999. let ytdFlexyElm = kRef(ytdFlexy);
  1000. if(!scriptEnable || !ytdFlexyElm) return null;
  1001.  
  1002. // no mutation triggering if the changes are inside the iframe
  1003.  
  1004. // 1) Detection of #continuations inside iframe
  1005. // iframe ownerDocument is accessible due to same origin
  1006. // if the chatroom is collasped, no determination of live chat or replay (as no #continuations and somehow a blank iframe doc)
  1007.  
  1008. // 2) Detection of meta tag
  1009. // This is fastest but not reliable. It is somehow a bug that the navigation might not update the meta tag content
  1010. // 3) Detection of HTMLElement inside video player for live video
  1011. // (1)+(3) = solution
  1012.  
  1013. let attr_chatblock = null
  1014. let attr_chatcollapsed = null;
  1015. const elmChat = document.querySelector('ytd-live-chat-frame#chat')
  1016. let elmCont = null;
  1017. if(elmChat){
  1018. elmCont=chatFrameElement('yt-live-chat-renderer #continuations')
  1019.  
  1020. let s=0;
  1021. if(elmCont){
  1022. //not found if it is collasped.
  1023. s |= elmCont.querySelector('yt-timed-continuation')?1:0;
  1024. s |= elmCont.querySelector('yt-live-chat-replay-continuation, yt-player-seek-continuation')?2:0;
  1025. //s |= elmCont.querySelector('yt-live-chat-restricted-participation-renderer')?4:0;
  1026. if(s==1) {
  1027. //console.log(7005)
  1028. attr_chatblock='chat-live';
  1029. //disableComments_LiveChat();
  1030. }
  1031. if(s==2) attr_chatblock='chat-playback';
  1032. //if(s==5) attr_chatblock='chat-live-paid';
  1033.  
  1034. if(s==1) $("span#tab3-txt-loader").text('');
  1035.  
  1036. }
  1037. //keep unknown as original
  1038.  
  1039. }else{
  1040. attr_chatblock=false;
  1041. attr_chatcollapsed=false;
  1042.  
  1043. }
  1044.  
  1045. return {attr_chatblock, attr_chatcollapsed}
  1046. }
  1047.  
  1048.  
  1049. function mtf_ChatExist(){
  1050.  
  1051. let ytdFlexyElm = kRef(ytdFlexy);
  1052. if(!scriptEnable || !ytdFlexyElm) return;
  1053.  
  1054. const elmChat = document.querySelector('ytd-live-chat-frame#chat')
  1055. let elmCont = null;
  1056. if(elmChat){
  1057. elmCont=chatFrameElement('yt-live-chat-renderer #continuations')
  1058. }
  1059. const chatBlockR = (elmChat?1:0)+(elmCont?2:0)
  1060. if(Q.mtf_chatBlockQ!==chatBlockR){
  1061. Q.mtf_chatBlockQ=chatBlockR
  1062. let rChatExist = base_ChatExist();
  1063. if(rChatExist){
  1064. let {attr_chatblock, attr_chatcollapsed} = rChatExist;
  1065. wAttr(ytdFlexyElm, 'userscript-chatblock', attr_chatblock)
  1066. wAttr(ytdFlexyElm, 'userscript-chat-collapsed', attr_chatcollapsed)
  1067. }
  1068. }
  1069. }
  1070.  
  1071.  
  1072.  
  1073.  
  1074.  
  1075.  
  1076. let lastScrollAt1=0;
  1077. function makeBodyScrollByEvt(){
  1078. let ct = Date.now();
  1079. if(ct - lastScrollAt1 < 6) return; // avoid duplicate calling
  1080. lastScrollAt1 = ct;
  1081. window.dispatchEvent(new Event("scroll")); // dispatch Scroll Event to Window for content display
  1082. }
  1083.  
  1084. let lastScrollAt2 = 0;
  1085. function makeBodyScroll() {
  1086. let ct = Date.now();
  1087. if(ct - lastScrollAt2 < 30) return; // avoid over triggering
  1088. lastScrollAt2 = ct;
  1089. requestAnimationFrame(()=>{
  1090. window.dispatchEvent(new Event("scroll")); // ask youtube to display content
  1091. })
  1092. }
  1093.  
  1094. let requestingComments = null
  1095. let scrollForComments_lastStart = 0;
  1096. function scrollForComments_TF(){
  1097. let comments = requestingComments;
  1098. if(!comments) return;
  1099. if ( comments.hasAttribute('hidden')){
  1100. window.dispatchEvent(new Event("scroll"));
  1101. }
  1102. else requestingComments=null;
  1103. }
  1104. function scrollForComments() {
  1105. scrollForComments_TF();
  1106. if(!requestingComments) return;
  1107. requestAnimationFrame(scrollForComments_TF);
  1108. let ct = Date.now();
  1109. if(ct - scrollForComments_lastStart < 60) return;
  1110. scrollForComments_lastStart = ct;
  1111. setTimeout(scrollForComments_TF, 80);
  1112. setTimeout(scrollForComments_TF, 240);
  1113. setTimeout(scrollForComments_TF, 870);
  1114. }
  1115.  
  1116.  
  1117.  
  1118.  
  1119. const mtoCs = {mtoNav:null, mtoBody:null};
  1120.  
  1121.  
  1122. const mtoVs={}
  1123.  
  1124. const mutation_target_id_list=['ytp-caption-window-container', 'items', 'button', 'movie_player', 'player-ads', 'hover-overlays', 'replies'];
  1125. const mutation_target_class_list=['ytp-panel-menu', 'ytp-endscreen-content'];
  1126. function isMtoOverallSkip(dTarget) {
  1127.  
  1128. if(!dTarget || dTarget.nodeType!==1) return true;
  1129. if(mutation_target_id_list.includes(dTarget.id)) return true;
  1130.  
  1131. let className = dTarget.className.toLowerCase();
  1132. let classNameSplit = className.split(' ');
  1133. for (const c of classNameSplit) {
  1134. if(mutation_target_class_list.includes(c)) return true;
  1135. }
  1136.  
  1137. return false;
  1138. }
  1139.  
  1140.  
  1141. const mutation_div_id_ignorelist=[
  1142. 'metadata-line',
  1143. 'ytp-caption-window-container',
  1144. 'top-level-buttons-computed',
  1145. 'microformat',
  1146. 'visibility-button',
  1147. 'info-strings',
  1148. 'action-menu',
  1149. 'reply-button-end'
  1150. ];
  1151.  
  1152. const mutation_div_class_ignorelist=[
  1153. 'badge','tp-yt-paper-tooltip','ytp-autonav-endscreen-upnext-header',
  1154. 'ytp-bound-time-left','ytp-bound-time-right','ytp-share-icon',
  1155. 'ytp-tooltip-title','annotation','ytp-copylink-icon','ytd-thumbnail',
  1156. 'paper-ripple',
  1157. //caption
  1158. 'captions-text','caption-visual-line','ytp-caption-segment', 'ytp-caption-window-container',
  1159. //menu
  1160. 'ytp-playlist-menu-button-text',
  1161.  
  1162. 'ytp-bezel-icon','ytp-bezel-text',
  1163. 'dropdown-content',
  1164. 'tp-yt-paper-menu-button','tp-yt-iron-dropdown',
  1165.  
  1166. 'ytd-metadata-row-renderer', // #content.ytd-metadata-row-renderer inside each of ytd-metadata-row-renderer (ytd-expander)
  1167. '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
  1168.  
  1169. 'autocomplete-suggestions' // autocomplete-suggestions
  1170. ];
  1171.  
  1172. const mutation_target_tag_ignorelist=[
  1173. 'ytd-channel-name','tp-yt-iron-dropdown','tp-yt-paper-tooltip',
  1174. 'tp-yt-paper-listbox','yt-img-shadow','ytd-thumbnail','ytd-video-meta-block',
  1175.  
  1176. 'yt-icon-button','tp-yt-paper-button','yt-formatted-string','yt-icon','button','paper-ripple',
  1177.  
  1178. 'ytd-player-microformat-renderer',
  1179. 'ytd-engagement-panel-section-list-renderer','ytd-engagement-panel-title-header-renderer',
  1180. 'ytd-comment-renderer', 'ytd-menu-renderer', 'ytd-badge-supported-renderer',
  1181. 'ytd-subscribe-button-renderer', 'ytd-subscription-notification-toggle-button-renderer',
  1182. 'ytd-button-renderer','ytd-toggle-button-renderer',
  1183. 'yt-pdg-comment-chip-renderer','ytd-comment-action-buttons-renderer','ytd-comment-thread-renderer',
  1184. 'ytd-compact-radio-renderer','ytd-compact-video-renderer',
  1185. 'ytd-video-owner-renderer',
  1186. 'ytd-metadata-row-renderer', //ytd-metadata-row-renderer is part of the #collapsible inside ytd-expander
  1187.  
  1188. 'ytd-moving-thumbnail-renderer',
  1189. 'ytd-thumbnail-overlay-toggle-button-renderer',
  1190. 'ytd-thumbnail-overlay-bottom-panel-renderer','ytd-thumbnail-overlay-equalizer',
  1191. 'ytd-thumbnail-overlay-now-playing-renderer','ytd-thumbnail-overlay-resume-playback-renderer',
  1192. 'ytd-thumbnail-overlay-side-panel-renderer','ytd-thumbnail-overlay-time-status-renderer',
  1193. 'ytd-thumbnail-overlay-hover-text-renderer',
  1194.  
  1195. 'yt-interaction',
  1196. 'tp-yt-paper-spinner-lite','tp-yt-paper-spinner',
  1197.  
  1198. 'h1','h2','h3','h4','h5','span','a',
  1199.  
  1200. 'meta','br','script','style','link','dom-module','template'
  1201. ];
  1202.  
  1203. function isMtoTargetSkip(mutation) {
  1204. //skip not important mutation tartget
  1205.  
  1206. if (!mutation) return true;
  1207. let {type, target} = mutation
  1208.  
  1209. if (!target || target.nodeType !== 1 || type != 'childList') return true;
  1210.  
  1211. let tagName = target.nodeName.toLowerCase();
  1212. let className;
  1213. let classNameSplit;
  1214.  
  1215. if(mutation_target_tag_ignorelist.includes(tagName)) return true;
  1216.  
  1217. switch (tagName) {
  1218.  
  1219.  
  1220. case 'ytd-expander':
  1221. if (target.id == 'expander' && Q.comments_section_loaded==1 && target.classList.contains('ytd-comment-renderer') ) return true; // load comments
  1222. return false;
  1223.  
  1224. case 'div':
  1225.  
  1226. if (target.id == 'contents') {
  1227. return false;
  1228. }
  1229. if(mutation_div_id_ignorelist.includes(target.id)) return true;
  1230. className = target.className.toLowerCase();
  1231. classNameSplit = className.split(' ');
  1232. for (const c of classNameSplit) {
  1233. if(mutation_div_class_ignorelist.includes(c)) return true;
  1234. }
  1235.  
  1236. return false;
  1237.  
  1238. }
  1239.  
  1240. return false;
  1241.  
  1242. }
  1243.  
  1244.  
  1245.  
  1246. // continuous check for element relocation
  1247. function mtf_append_comments() {
  1248. let ytdFlexyElm = kRef(ytdFlexy);
  1249. if(!scriptEnable || !ytdFlexyElm) return;
  1250.  
  1251. const rootElement = Q.mutationTarget || ytdFlexyElm;
  1252.  
  1253. let comments = rootElement.querySelector('#primary ytd-watch-metadata ~ #info ~ ytd-comments#comments');
  1254. if (comments) $(comments).appendTo('#tab-comments').attr('data-dom-changed-by-tabview-youtube',scriptVersionForExternal)
  1255. }
  1256.  
  1257. // continuous check for element relocation
  1258. function mtf_liveChatBtnF() {
  1259. let ytdFlexyElm = kRef(ytdFlexy);
  1260. if(!scriptEnable || !ytdFlexyElm) return;
  1261.  
  1262. const rootElement = Q.mutationTarget || ytdFlexyElm;
  1263.  
  1264. let button = rootElement.querySelector('ytd-live-chat-frame#chat>.ytd-live-chat-frame#show-hide-button:nth-child(n+2)');
  1265. if (button) button.parentNode.insertBefore(button, button.parentNode.firstChild)
  1266. }
  1267.  
  1268.  
  1269. // continuous check for element relocation
  1270. // fired at begining & window resize, etc
  1271. function mtf_append_playlist(){
  1272. let ytdFlexyElm = kRef(ytdFlexy);
  1273. if(!scriptEnable || !ytdFlexyElm) return;
  1274.  
  1275. const rootElement = Q.mutationTarget || ytdFlexyElm;
  1276.  
  1277. let ple1 = rootElement.querySelector("*:not(#ytd-userscript-playlist)>ytd-playlist-panel-renderer#playlist");
  1278. if(ple1){
  1279. let ct = Date.now();
  1280. let truePlaylist=null;
  1281. let truePlaylist_items = document.querySelector('ytd-playlist-panel-renderer#playlist #items:not(:empty)');
  1282. if(truePlaylist_items){
  1283.  
  1284. let pElm = truePlaylist_items.parentNode;
  1285. while(pElm && pElm.nodeType===1){
  1286. if(pElm.id=='playlist'){
  1287. pElm.setAttribute('tabview-true-playlist',ct)
  1288. truePlaylist=pElm;
  1289. break;
  1290. }
  1291. pElm=pElm.parentNode;
  1292. }
  1293.  
  1294. }
  1295.  
  1296. if(!truePlaylist) truePlaylist = ple1; // NOT NULL
  1297.  
  1298. for(const s of document.querySelectorAll(`*:not(#ytd-userscript-playlist)>ytd-playlist-panel-renderer#playlist:not([tabview-true-playlist="${ct}"])`))
  1299. s.parentNode.removeChild(s);
  1300.  
  1301. let $wrapper = getWrapper('ytd-userscript-playlist')
  1302. $wrapper.append(truePlaylist).appendTo(document.querySelector("#tab-list"));
  1303. truePlaylist.setAttribute('data-dom-changed-by-tabview-youtube',scriptVersionForExternal)
  1304. setDisplayedPlaylist(); // relocation after re-layout
  1305.  
  1306. requestAnimationFrame(()=>{
  1307. let ytdFlexyElm = kRef(ytdFlexy);
  1308. if(!scriptEnable || !ytdFlexyElm) return;
  1309. if( !switchTabActivity_lastTab && (ytdFlexyElm.getAttribute('tabview-selection')+'').indexOf('#tab-')===0 && /https\:\/\/www\.youtube\.com\/watch.*[\?\&]list=[\w\-\_]+/.test(location.href) ){
  1310. if(setToActiveTab('#tab-list')) switchTabActivity_lastTab = '#tab-list';
  1311. }
  1312. })
  1313.  
  1314. }
  1315. }
  1316.  
  1317.  
  1318. // content fix - info & playlist
  1319. // fired at begining, and keep for in case any change
  1320. function mtf_fix_details() {
  1321.  
  1322. if(!scriptEnable)return ;
  1323.  
  1324. if(no_fix_contents_until<Date.now()){
  1325. const content = document.querySelector('#meta-contents ytd-expander>#content, #tab-info ytd-expander>#content')
  1326. if (content) {
  1327. no_fix_contents_until= Date.now() +3000;
  1328. setTimeout(function(){
  1329. const expander = content.parentNode;
  1330.  
  1331. if (expander.hasAttribute('collapsed')) wAttr(expander,'collapsed',false);
  1332. expander.style.setProperty('--ytd-expander-collapsed-height','');
  1333.  
  1334. let btn1 = expander.querySelector('tp-yt-paper-button#less:not([hidden])');
  1335. let btn2 = expander.querySelector('tp-yt-paper-button#more:not([hidden])');
  1336.  
  1337. if (btn1) wAttr(btn1,'hidden',true);
  1338. if (btn2) wAttr(btn2,'hidden',true);
  1339. },40);
  1340.  
  1341. }
  1342. }
  1343.  
  1344. if(no_fix_playlist_until<Date.now()){
  1345. // just in case the playlist is collapsed
  1346. const playlist = document.querySelector('#tab-list ytd-playlist-panel-renderer#playlist')
  1347. if(playlist){
  1348. no_fix_playlist_until= Date.now() +3000;
  1349. setTimeout(function(){
  1350. if(playlist.hasAttribute('collapsed')) wAttr(playlist,'collapsed',false);
  1351. if(playlist.hasAttribute('collapsible')) wAttr(playlist,'collapsible',false);
  1352. },40)
  1353. }
  1354. }
  1355.  
  1356.  
  1357. }
  1358.  
  1359.  
  1360.  
  1361. function status_commentsHidden(){
  1362. var comments=document.querySelector('ytd-comments#comments')
  1363. if(!comments || (comments.childElementCount === 0 && comments.hasAttribute('hidden'))) return 2;
  1364. if(comments.hasAttribute('hidden')) return 1;
  1365. return 0;
  1366.  
  1367. }
  1368.  
  1369.  
  1370. function getFMT(ytdFlexyElm){
  1371.  
  1372. let fmt = ["ytd-comments#comments #count.ytd-comments-header-renderer", 'ytd-item-section-renderer#sections #header ~ #contents>ytd-message-renderer'].map(s=>{
  1373. let elm = ytdFlexyElm.querySelector(s)
  1374. if(!elm) return null;
  1375. return elm.querySelector('yt-formatted-string') || elm.closest('yt-formatted-string') || null
  1376. });
  1377. return fmt
  1378. }
  1379. function toFST(elm){
  1380. if(!elm) return null;
  1381. return elm.querySelector('yt-formatted-string') || elm.closest('yt-formatted-string') || null
  1382. }
  1383.  
  1384. function innerCommentsLoader(){
  1385.  
  1386.  
  1387. const rootElement = Q.mutationTarget || kRef(ytdFlexy);
  1388. if(!rootElement)return;
  1389.  
  1390. let messageElm, messageStr, commentRenderer;
  1391. if (commentRenderer = rootElement.querySelector("ytd-comments#comments #count.ytd-comments-header-renderer")) {
  1392. return {
  1393. status: 1,
  1394. f: ()=>{
  1395.  
  1396. let span = document.querySelector("span#tab3-txt-loader")
  1397. Q.fetchedOnce=true;
  1398. let r = '0';
  1399. let txt = commentRenderer.textContent
  1400. if (typeof txt == 'string') {
  1401. let m = txt.match(/[\d\,\s]+/)
  1402. if (m) {
  1403. r = m[0].trim()
  1404. }
  1405. }
  1406. if(span) span.textContent = r;
  1407. mtoInterval=mtoInterval2;
  1408. setCommentSection(1);
  1409. cmItem=mWeakRef(toFST(commentRenderer))
  1410. comments_section_loaded_elm=mWeakRef(document.querySelector('ytd-comments#comments div#contents'))
  1411. }
  1412. }
  1413. }else if((messageElm = rootElement.querySelector('ytd-item-section-renderer#sections #header ~ #contents>ytd-message-renderer'))&&(messageStr=(messageElm.textContent||'').trim())){ //ytd-message-renderer
  1414. // it is possible to get the message before the header generation.
  1415. return {
  1416. status:2,
  1417. f:()=>{
  1418. setTimeout(function(){
  1419. if(Q.fetchedOnce)return;
  1420. let span = document.querySelector("span#tab3-txt-loader")
  1421. const mainMsg= messageElm.querySelector('#message, #submessage')
  1422. if(mainMsg && mainMsg.textContent){
  1423. for(const msg of mainMsg.querySelectorAll('*:not(:empty)')){
  1424. if(msg.childElementCount===0 && msg.textContent) {
  1425. messageStr=msg.textContent.trim()
  1426. break
  1427. }
  1428. }
  1429. }
  1430. if(span) span.textContent = messageStr;
  1431. mtoInterval=mtoInterval2;
  1432. setCommentSection(1);
  1433. cmItem=mWeakRef(toFST(messageElm))
  1434. comments_section_loaded_elm=mWeakRef(document.querySelector('ytd-comments#comments div#contents'))
  1435. },40);
  1436. }
  1437. }
  1438.  
  1439. }
  1440.  
  1441. }
  1442.  
  1443. const FP={
  1444.  
  1445. mtoNav_delayedF:() => {
  1446. let {addP, removeP, mutationTarget} = Q;
  1447. Q.addP = 0;
  1448. Q.removeP = 0;
  1449. let isInvalidAdding = Q.mutationTarget&& !Q.mutationTarget.parentNode
  1450.  
  1451. let promisesForAddition=!scriptEnable?[]:addP > 0 && !isInvalidAdding ?[
  1452. $callOnceAsync('mtf_checkFlexy'),
  1453. $callOnceAsync('mtf_initalAttr_comments'),
  1454. $callOnceAsync('mtf_initalAttr_playlist'),
  1455. $callOnceAsync('mtf_initalAttr_chatroom'),
  1456. $callOnceAsync('mtf_initalAttr_engagement_panel'),
  1457. $callOnceAsync('mtf_advancedComments'),
  1458. $callOnceAsync('mtf_checkDescriptionLoaded'),
  1459. $callOnceAsync('mtf_fetchCommentsAvailable'),
  1460. $callOnceAsync('mtf_forceCheckLiveVideo'),
  1461.  
  1462. (async () => {
  1463. mtf_append_comments();
  1464. })(),
  1465. (async () => {
  1466. mtf_liveChatBtnF();
  1467. })(),
  1468.  
  1469. (async ()=>{
  1470. fixTabs();
  1471. mtf_AfterFixTabs();
  1472. })(),
  1473. (async () => {
  1474. mtf_append_playlist();
  1475. })()
  1476. ]:[];
  1477.  
  1478.  
  1479. let promisesForEveryMutation=!scriptEnable?[]:[
  1480. (async () => {
  1481. mtf_fix_details();
  1482. })(),
  1483. (async () => {
  1484. mtf_ChatExist();
  1485. })()
  1486. ];
  1487.  
  1488.  
  1489. Promise.all([...promisesForAddition,...promisesForEveryMutation]).then(()=>{
  1490. Q.mutationTarget = null;
  1491.  
  1492. Q.mtoNav_requestNo--;
  1493. //console.log('motnav reduced to', mtoNav_requestNo)
  1494. if(Q.mtoNav_requestNo>0){
  1495. Q.mtoNav_requestNo=1;
  1496. setTimeout(FP.mtoNav_delayedF,mtoInterval);
  1497. }
  1498. })
  1499. },
  1500.  
  1501. mtoNavF: (mutations, observer) => {
  1502. if(!scriptEnable)return;
  1503.  
  1504. let ch = false;
  1505.  
  1506. let reg = [];
  1507. let dTarget =null;
  1508.  
  1509. let wAddP=0,wRemoveP=0;
  1510.  
  1511. let _last_mto_target = null;
  1512. let _last_mto_target_valid = null;
  1513.  
  1514. for (const mutation of mutations) {
  1515. if(!mutation || !mutation.target || !mutation.target.parentNode) continue;
  1516.  
  1517. let elementalMutation = false;
  1518. let tAddP=0, tRemoveP=0;
  1519.  
  1520. for (const addedNode of mutation.addedNodes){ //theoretically faster: only reading of states
  1521. if (addedNode.nodeType === 1) {
  1522. tAddP++;
  1523. elementalMutation = true;
  1524. }
  1525. }
  1526.  
  1527. for (const removedNode of mutation.removedNodes){ //theoretically faster: only reading of states
  1528. if (removedNode.nodeType === 1) {
  1529. tRemoveP++;
  1530. elementalMutation = true;
  1531. }
  1532. }
  1533.  
  1534. if(elementalMutation){ //skip all addition and removal operations without elemental changes (e.g. textNode modification)
  1535.  
  1536. if(_last_mto_target === mutation.target){
  1537. // due to addition and removal operations to the same DOM
  1538. if(_last_mto_target_valid){
  1539. // AddP & RemoveP is still valid
  1540. wAddP+=tAddP;
  1541. wRemoveP+=tRemoveP;
  1542. }
  1543. continue;
  1544. }
  1545. _last_mto_target = mutation.target;
  1546.  
  1547. if(isMtoTargetSkip(mutation)){
  1548. _last_mto_target_valid=false;
  1549. continue; //theoretically slower: creation of string variables
  1550. }else{
  1551. _last_mto_target_valid=true;
  1552. wAddP+=tAddP;
  1553. wRemoveP+=tRemoveP;
  1554. }
  1555.  
  1556.  
  1557. ch = true;
  1558.  
  1559. reg.push(mutation);
  1560.  
  1561. if(dTarget===null) dTarget=mutation.target; //first
  1562. else if(dTarget===true){} //ytdFlexy
  1563. else if(dTarget.contains(mutation.target)){} //first node is the container to all subsequential targets
  1564. else {dTarget=true;} //the target is not the child of first node
  1565.  
  1566. }
  1567.  
  1568. }
  1569. if (!ch) return; // dTarget must be true OR HTMLElement
  1570. if(dTarget===true) dTarget=kRef(ytdFlexy); // major mutation occurance
  1571. else if(dTarget === kRef(comments_section_loaded_elm) && wAddP>wRemoveP) return true; // ignore if comments are loaded (adding comments)
  1572. else if(isMtoOverallSkip(dTarget)) return; // allow for multiple mutations at the same time - determinated after looping
  1573.  
  1574.  
  1575.  
  1576. // 4 ~ 16 times per full page loading
  1577. Q.addP+=wAddP;
  1578. Q.removeP+=wRemoveP;
  1579.  
  1580. if(Q.mutationTarget===null) Q.mutationTarget=dTarget;
  1581. else if(Q.mutationTarget!=dTarget) Q.mutationTarget = kRef(ytdFlexy);
  1582.  
  1583. //console.log(prettyElm(dTarget), wAddP , wRemoveP, mtoInterval)
  1584. //console.log(prettyElm(dTarget), reg.map(m=>prettyElm(m.target)))
  1585. //console.log(7015, performance.now())
  1586.  
  1587. Q.mtoNav_requestNo++;
  1588. if(Q.mtoNav_requestNo==1) setTimeout(FP.mtoNav_delayedF,mtoInterval);
  1589.  
  1590. },
  1591.  
  1592.  
  1593. mtoBodyF: function (mutations, observer){
  1594. if(!scriptEnable)return;
  1595.  
  1596. for (const mutation of mutations) {
  1597. for (const addedNode of mutation.addedNodes)
  1598. if (addedNode.nodeType === 1) {
  1599. if(addedNode.nodeName=="DIV" && addedNode.className.indexOf('autocomplete-suggestions')>=0){
  1600. mtf_fixAutoCompletePosition(addedNode)
  1601. }
  1602. }
  1603. }
  1604.  
  1605. },
  1606.  
  1607. mtf_attrPlaylist:(mutations, observer)=>{
  1608.  
  1609. if(!scriptEnable)return;
  1610. let cssElm=kRef(ytdFlexy);
  1611. if(!cssElm)return;
  1612.  
  1613. var playlist=document.querySelector('ytd-playlist-panel-renderer#playlist')
  1614. const tabBtn = document.querySelector('[userscript-tab-content="#tab-list"]');
  1615. if(tabBtn){
  1616. //console.log('attr playlist changed')
  1617. if( tabBtn.classList.contains('tab-btn-hidden') && !playlist.hasAttribute('hidden') ){
  1618. //console.log('attr playlist changed - no hide')
  1619. tabBtn.classList.remove("tab-btn-hidden");
  1620. }else if( !tabBtn.classList.contains('tab-btn-hidden') && playlist.hasAttribute('hidden') ){
  1621. //console.log('attr playlist changed - add hide')
  1622. hideTabBtn(tabBtn);
  1623. }
  1624. }
  1625. /* visible layout for triggering hidden removal */
  1626. akAttr(cssElm, 'tabview-youtube-playlist', playlist.hasAttribute('hidden'));
  1627. },
  1628. delayed_disableComments:function(){
  1629.  
  1630. let ytdFlexyElm = kRef(ytdFlexy);
  1631. if(!scriptEnable || !ytdFlexyElm) return;
  1632.  
  1633. if(status_commentsHidden()==2) _disableComments();
  1634.  
  1635. },
  1636. mtf_attrComments:(mutations, observer)=>{
  1637.  
  1638. let ytdFlexyElm = kRef(ytdFlexy);
  1639. if(!scriptEnable || !ytdFlexyElm) return;
  1640.  
  1641. var comments=document.querySelector('ytd-comments#comments')
  1642. const tabBtn = document.querySelector('[userscript-tab-content="#tab-comments"]');
  1643. if(!comments || !tabBtn)return;
  1644. let isCommentHidden = comments.hasAttribute('hidden')
  1645. //console.log('attr comments changed')
  1646.  
  1647.  
  1648. timeout_attrComments.clear();
  1649. $('span#tab3-txt-loader').text('');
  1650. mtoInterval=mtoInterval1;
  1651.  
  1652. if( !isCommentHidden ){
  1653.  
  1654. akAttr(ytdFlexyElm,'tabview-youtube-comments', false, 'K');
  1655.  
  1656. //console.log('attr comments changed - no hide')
  1657. tabBtn.classList.remove("tab-btn-hidden") //if contains
  1658.  
  1659. makeBodyScroll();
  1660. Q.fetchedOnce = false
  1661. FOnce.mtf_fetchCommentsAvailable = FP.mtf_fetchCommentsAvailable;
  1662. if(Q.mutationTarget===null) $callOnceAsync('mtf_fetchCommentsAvailable');
  1663. window.requestAnimationFrame(()=>{
  1664. let innerCommentsLoaderRet = innerCommentsLoader();
  1665. if(innerCommentsLoaderRet) innerCommentsLoaderRet.f();
  1666. })
  1667. }else if( isCommentHidden ){
  1668.  
  1669.  
  1670. //console.log('attr comments changed - add hide')
  1671.  
  1672. akAttr(ytdFlexyElm,'tabview-youtube-comments',true, 'K');
  1673.  
  1674. let t = status_commentsHidden()==2 ? 1630 : 760;
  1675.  
  1676. timeout_attrComments.set(FP.delayed_disableComments,t);
  1677. }
  1678. requestingComments = comments;
  1679. scrollForComments();
  1680. setTimeout(()=>nativeFunc(comments, "loadComments"),20)
  1681.  
  1682. },
  1683. mtf_attrChatroom:(mutations, observer)=>{
  1684. let ytdFlexyElm = kRef(ytdFlexy);
  1685. if(!scriptEnable || !ytdFlexyElm) return ;
  1686.  
  1687. layoutStatusMutex.lockWith(unlock=>{
  1688.  
  1689. const chatBlock = document.querySelector('ytd-live-chat-frame#chat')
  1690. const cssElm = kRef(ytdFlexy)
  1691.  
  1692. if(!chatBlock || !cssElm){
  1693. unlock();
  1694. return;
  1695. }
  1696.  
  1697. if(!cssElm.hasAttribute('userscript-chatblock')) wAttr(cssElm, 'userscript-chatblock', true);
  1698. let isCollapsed=!!chatBlock.hasAttribute('collapsed');
  1699. wAttr(cssElm,'userscript-chat-collapsed',isCollapsed);
  1700.  
  1701. if(cssElm.hasAttribute('userscript-chatblock')&&!isCollapsed) lastShowTab='#chatroom';
  1702.  
  1703. if(!isCollapsed && document.querySelector('#right-tabs .tab-btn.active') && isWideScreenWithTwoColumns() && !isTheater() ){
  1704. switchTabActivity(null);
  1705. setTimeout(unlock,40);
  1706. } else{
  1707. unlock();
  1708. }
  1709.  
  1710. if(!isCollapsed){
  1711. runAfterExpandChat();
  1712. }else {
  1713. chatBlock.removeAttribute('yt-userscript-iframe-loaded');
  1714. }
  1715.  
  1716. })
  1717.  
  1718.  
  1719.  
  1720.  
  1721. },
  1722.  
  1723. mtf_attrEngagementPanel:(mutations, observer)=>{
  1724. let ytdFlexyElm = kRef(ytdFlexy);
  1725. if(!scriptEnable || !ytdFlexyElm) return ;
  1726.  
  1727. //multiple instance
  1728. if(mutations){
  1729. let cPanels = null;
  1730. for(const mutation of mutations){
  1731. let ePanel = mutation.target;
  1732. if(ePanel.getAttribute('visibility')=='ENGAGEMENT_PANEL_VISIBILITY_EXPANDED'){
  1733. cPanels = cPanels || engagement_panels_();
  1734. for(const entry of cPanels.list){
  1735. if(entry.ePanel!=ePanel && entry.visible) ytBtnCloseEngagementPanel(entry.ePanel);
  1736. }
  1737. }
  1738. }
  1739. }
  1740.  
  1741. layoutStatusMutex.lockWith(unlock=>{
  1742.  
  1743. const ePanel = document.querySelector('ytd-watch-flexy ytd-engagement-panel-section-list-renderer')
  1744. const cssElm = kRef(ytdFlexy)
  1745.  
  1746. if(!ePanel || !cssElm){
  1747. unlock();
  1748. return;
  1749. }
  1750. let previousValue = +cssElm.getAttribute('userscript-engagement-panel') || 0;
  1751. let {value, count} = engagement_panels_();
  1752. let nextValue = value;
  1753. let nextCount = count;
  1754.  
  1755.  
  1756. if(nextCount == 0 && cssElm.hasAttribute('userscript-engagement-panel')){
  1757. wAttr(cssElm, 'userscript-engagement-panel', false);
  1758. unlock();
  1759. }else{
  1760.  
  1761. if( ( nextCount > 1) || (cssElm.hasAttribute('userscript-engagement-panel') && previousValue===nextValue) ) {
  1762. unlock();
  1763. return;
  1764. }
  1765.  
  1766. cssElm.setAttribute('userscript-engagement-panel',nextValue);
  1767.  
  1768. let b = false;
  1769. if(previousValue!=nextValue && nextValue>0) {
  1770. lastShowTab = `#engagement-panel-${nextValue}`;
  1771. b=true;
  1772. }
  1773.  
  1774. if(b && document.querySelector('#right-tabs .tab-btn.active') && isWideScreenWithTwoColumns() && !isTheater() ){
  1775. switchTabActivity(null);
  1776. setTimeout(unlock,40);
  1777. } else{
  1778. unlock();
  1779. }
  1780. }
  1781.  
  1782. })
  1783.  
  1784.  
  1785.  
  1786.  
  1787. },
  1788.  
  1789. mtf_initalAttr_playlist:()=>{
  1790. let ytdFlexyElm = kRef(ytdFlexy);
  1791. if(!scriptEnable || !ytdFlexyElm) return true;
  1792.  
  1793. const rootElement = Q.mutationTarget || ytdFlexyElm;
  1794.  
  1795. var playlist=rootElement.querySelector('ytd-playlist-panel-renderer#playlist')
  1796. if(!playlist) return true;
  1797. initMutationObserver(mtoVs,'mtoVisibility_Playlist', FP.mtf_attrPlaylist).observe(playlist, {
  1798. attributes: true,
  1799. attributeFilter: ['hidden'],
  1800. attributeOldValue: true
  1801. })
  1802. FP.mtf_attrPlaylist()
  1803. return false;
  1804. },
  1805.  
  1806. mtf_initalAttr_comments:()=>{
  1807. let ytdFlexyElm = kRef(ytdFlexy);
  1808. if(!scriptEnable || !ytdFlexyElm) return true;
  1809.  
  1810. const rootElement = Q.mutationTarget || ytdFlexyElm;
  1811.  
  1812. var comments=rootElement.querySelector('ytd-comments#comments')
  1813. if(!comments) return true;
  1814. initMutationObserver(mtoVs,'mtoVisibility_Comments',FP.mtf_attrComments).observe(comments, {
  1815. attributes: true,
  1816. attributeFilter: ['hidden'],
  1817. attributeOldValue: true
  1818. })
  1819. FP.mtf_attrComments()
  1820. requestingComments = comments;
  1821. //scrollForComments()
  1822. setTimeout(()=>nativeFunc(comments, "loadComments"),20)
  1823. return false;
  1824. },
  1825.  
  1826. mtf_initalAttr_chatroom:()=>{
  1827. let ytdFlexyElm = kRef(ytdFlexy);
  1828. if(!scriptEnable || !ytdFlexyElm) return true;
  1829.  
  1830. const rootElement = Q.mutationTarget || ytdFlexyElm;
  1831.  
  1832. var chatroom=rootElement.querySelector('ytd-live-chat-frame#chat')
  1833. if(!chatroom) return true;
  1834. initMutationObserver(mtoVs,'mtoVisibility_Chatroom',FP.mtf_attrChatroom).observe(chatroom, {
  1835. attributes: true,
  1836. attributeFilter: ['collapsed'],
  1837. attributeOldValue: true
  1838. })
  1839. FP.mtf_attrChatroom()
  1840. return false;
  1841. },
  1842.  
  1843. mtf_initalAttr_engagement_panel:()=>{
  1844. let ytdFlexyElm = kRef(ytdFlexy);
  1845. if(!scriptEnable || !ytdFlexyElm) return true;
  1846.  
  1847. const rootElement = Q.mutationTarget || ytdFlexyElm;
  1848.  
  1849. let toCheck=false;
  1850. for(const engagement_panel of rootElement.querySelectorAll('ytd-watch-flexy ytd-engagement-panel-section-list-renderer:not([tabview-attr-checked])')){
  1851.  
  1852. engagement_panel.setAttribute('tabview-attr-checked','');
  1853. let mto = mtoVs.mtoVisibility_EngagementPanel;
  1854. if(!mto) mto=initMutationObserver(mtoVs,'mtoVisibility_EngagementPanel',FP.mtf_attrEngagementPanel);
  1855. mto.observe(engagement_panel, {
  1856. attributes: true,
  1857. attributeFilter: ['visibility'],
  1858. attributeOldValue: true
  1859. })
  1860. toCheck=true;
  1861. }
  1862.  
  1863. if(toCheck) FP.mtf_attrEngagementPanel()
  1864. return true;
  1865. },
  1866.  
  1867. mtf_checkDescriptionLoaded: () => {
  1868. let ytdFlexyElm = kRef(ytdFlexy);
  1869. if(!scriptEnable || !ytdFlexyElm) return true;
  1870.  
  1871. const rootElement = Q.mutationTarget || ytdFlexyElm;
  1872.  
  1873. const expander = document.querySelector("#meta-contents ytd-expander");
  1874. if (!expander) return true;
  1875. $(expander).appendTo("#tab-info").attr('data-dom-changed-by-tabview-youtube',scriptVersionForExternal)
  1876.  
  1877. const avatar = document.querySelector('#meta-contents yt-img-shadow#avatar');
  1878. if(avatar) hackImgShadow(avatar)
  1879. return false;
  1880.  
  1881. },
  1882.  
  1883.  
  1884. //live-chat / chat-replay
  1885.  
  1886. fireOnce_forceCheckLiveVideo_tf: ()=>{
  1887.  
  1888. let ytdFlexyElm = kRef(ytdFlexy);
  1889. if(!scriptEnable || !ytdFlexyElm) return;
  1890.  
  1891. let elm = document.querySelector('#ytd-player .ytp-time-display');
  1892. if(elm && elm.classList.contains('ytp-live')) {
  1893. //console.log(7006)
  1894. ytdFlexyElm.setAttribute('userscript-chatblock', 'chat-live')
  1895. //disableComments_LiveChat();
  1896. }
  1897. },
  1898.  
  1899. mtf_forceCheckLiveVideo:() => {
  1900. let ytdFlexyElm = kRef(ytdFlexy);
  1901. if(!scriptEnable || !ytdFlexyElm) return true;
  1902.  
  1903. const rootElement = Q.mutationTarget || ytdFlexyElm;
  1904. const playerLabel = document.querySelector('#ytd-player .ytp-time-display') && document.querySelector('ytd-live-chat-frame#chat')
  1905. if (!playerLabel) return true;
  1906. setTimeout(FP.fireOnce_forceCheckLiveVideo_tf,170)
  1907. return false;
  1908. },
  1909.  
  1910. //comments
  1911.  
  1912. mtf_advancedComments: () => {
  1913. let ytdFlexyElm = kRef(ytdFlexy);
  1914. if(!scriptEnable || !ytdFlexyElm) return true;
  1915.  
  1916. const rootElement = Q.mutationTarget || ytdFlexyElm;
  1917. const continuations = document.querySelector("ytd-comments#comments #continuations");
  1918. if (!continuations) return true;
  1919. requestingComments = document.querySelector('ytd-comments#comments');
  1920. scrollForComments();
  1921. return false;
  1922. },
  1923.  
  1924. mtf_fetchCommentsAvailable:() => {
  1925.  
  1926. let ytdFlexyElm = kRef(ytdFlexy);
  1927. if(!scriptEnable || !ytdFlexyElm) return true;
  1928.  
  1929. let innerCommentsLoaderRet = innerCommentsLoader();
  1930. if(!innerCommentsLoaderRet) return true;
  1931.  
  1932. innerCommentsLoaderRet.f();
  1933. fetchCommentsFinished();
  1934. return false;
  1935. }
  1936.  
  1937. }
  1938.  
  1939.  
  1940.  
  1941.  
  1942.  
  1943.  
  1944.  
  1945.  
  1946. function initObserver(){
  1947.  
  1948. Q.addP=0;
  1949. Q.removeP=0;
  1950. Q.mutationTarget = null;
  1951.  
  1952. Q.mtoNav_requestNo=0;
  1953.  
  1954. initMutationObserver(mtoCs, 'mtoNav', FP.mtoNavF).observe(kRef(ytdFlexy), {
  1955. subtree: true,
  1956. childList: true,
  1957. attributes: false
  1958. });
  1959.  
  1960. -(async()=>{
  1961. Q.addP=1; //force all checking
  1962. Q.mtoNav_requestNo++;
  1963. if(Q.mtoNav_requestNo==1) FP.mtoNav_delayedF();
  1964. })();
  1965.  
  1966. // for automcomplete plugin
  1967. initMutationObserver(mtoCs,'mtoBody', FP.mtoBodyF).observe(document.querySelector('body'), {
  1968. childList: true,
  1969. subtree: false,
  1970. attributes: false
  1971. })
  1972.  
  1973.  
  1974. }
  1975.  
  1976. let displayedPlaylist=null;
  1977. let scrollingVideosList=null;
  1978.  
  1979. let scriptEnable =false;
  1980. let scriptEC = 0;
  1981. let lastShowTab = null;
  1982.  
  1983. let _cachedLastVideo=null;
  1984. let videoListBeforeSearch=null;
  1985. let no_fix_contents_until = 0;
  1986. let no_fix_playlist_until = 0;
  1987. let statusCollasped = 0;
  1988. let ytdFlexy=null;
  1989. let timeout_attrComments=new Timeout();
  1990.  
  1991. function pluginHook(){
  1992.  
  1993. scriptEnable =true;
  1994. scriptEC++;
  1995.  
  1996. no_fix_contents_until = 0;
  1997. no_fix_playlist_until = 0;
  1998.  
  1999. ytdFlexy=mWeakRef(document.querySelector('ytd-watch-flexy'))
  2000.  
  2001. }
  2002.  
  2003. function pluginUnhook() {
  2004.  
  2005. //console.log(8001)
  2006. timeout_attrComments.clear();
  2007.  
  2008. videoListBeforeSearch=null;
  2009. statusCollasped=0;
  2010. _cachedLastVideo=null;
  2011. lastShowTab=null;
  2012. displayedPlaylist=null;
  2013. scrollingVideosList=null;
  2014. scriptEnable =false;
  2015. scriptEC++;
  2016. if(scriptEC>788888888) scriptEC=188888888;
  2017. ytdFlexy=null;
  2018. wls.layoutStatus=null;
  2019.  
  2020. clearMutationObserver(mtoVs,'mtoVisibility_Playlist')
  2021. clearMutationObserver(mtoVs,'mtoVisibility_Comments')
  2022. clearMutationObserver(mtoVs,'mtoVisibility_Chatroom')
  2023. clearMutationObserver(mtoVs,'mtoFlexyAttr')
  2024.  
  2025. clearMutationObserver(mtoCs,'mtoBody')
  2026. if (clearMutationObserver(mtoCs,'mtoNav')) {
  2027. FOnce.mtf_checkFlexy=null;
  2028. FOnce.mtf_initalAttr_comments = null;
  2029. FOnce.mtf_initalAttr_playlist = null;
  2030. FOnce.mtf_initalAttr_chatroom = null;
  2031. FOnce.mtf_initalAttr_engagement_panel = null;
  2032. FOnce.mtf_advancedComments=null;
  2033. FOnce.mtf_checkDescriptionLoaded = null;
  2034. FOnce.mtf_fetchCommentsAvailable = null;
  2035. FOnce.mtf_forceCheckLiveVideo=null;
  2036. Q.mtf_chatBlockQ = null;
  2037. }
  2038.  
  2039. mtoInterval = mtoInterval1;
  2040.  
  2041. }
  2042. let comments_section_loaded_elm = null;
  2043.  
  2044.  
  2045. function initializeForVideoChange(){
  2046.  
  2047.  
  2048.  
  2049.  
  2050.  
  2051. pluginUnhook();
  2052. if(!document.querySelector('script#userscript-tabview-injection-1')) {
  2053. addScript(`!!(${injection_script_1+""})()`).id='userscript-tabview-injection-1'
  2054. }
  2055.  
  2056. var prevComemnts = document.querySelector('ytd-comments#comments');
  2057.  
  2058. if(prevComemnts && prevComemnts.matches('[hidden]')){
  2059. // assumption: loading comments after executation of this function which removes the attribute [hidden] of #ytd-comments#comments
  2060.  
  2061. var prevCommentsHeader = prevComemnts.querySelector('ytd-comments#comments ytd-comments-header-renderer');
  2062. var prevCommentsMsg= prevComemnts.querySelector('ytd-item-section-renderer#sections #header ~ #contents>ytd-message-renderer');
  2063. //removed any cache of #comments header (i.e. count message)
  2064. if (prevCommentsHeader) prevCommentsHeader.parentNode.removeChild(prevCommentsHeader);
  2065.  
  2066. //removed any cache of #comments message (i.e. 留言功能已停用。)
  2067. if (prevCommentsMsg) prevCommentsMsg.parentNode.removeChild(prevCommentsMsg);
  2068.  
  2069. }
  2070.  
  2071.  
  2072. window.requestAnimationFrame(()=>{
  2073.  
  2074. // requestAnimationFrame is required to let youtube coding running first !
  2075. pluginHook();
  2076. _onNavigationEnd();
  2077.  
  2078. setTimeout(()=>{
  2079.  
  2080. //ensure comment tab text is updated - no matter there is change of video or not
  2081. let innerCommentsLoaderRet = innerCommentsLoader();
  2082. if(innerCommentsLoaderRet) innerCommentsLoaderRet.f();
  2083. },40);
  2084.  
  2085. })
  2086.  
  2087. return true;
  2088.  
  2089. }
  2090.  
  2091. function getTabsHTML(){
  2092.  
  2093. const sTabBtnVideos = `${svgElm(16,16,298,298,svgVideos)}<span>Videos</span>`
  2094. const sTabBtnInfo = `${svgElm(16,16,23.625,23.625,svgInfo)}<span>Info</span>`
  2095. const sTabBtnPlayList = `${svgElm(16,16,426.667,426.667,svgPlayList)}<span>Playlist</span>`
  2096.  
  2097. let str1 = `
  2098. <paper-ripple class="style-scope yt-icon-button">
  2099. <div id="background" class="style-scope paper-ripple" style="opacity:0;"></div>
  2100. <div id="waves" class="style-scope paper-ripple"></div>
  2101. </paper-ripple>
  2102. `;
  2103.  
  2104. const str_tabs = [
  2105. `<a id="tab-btn1" data-name="info" userscript-tab-content="#tab-info" class="tab-btn">${sTabBtnInfo}${str1}</a>`,
  2106. `<a id="tab-btn2" userscript-tab-content="#tab-live" class="tab-btn tab-btn-hidden">Chat${str1}</a>`,
  2107. `<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>`,
  2108. `<a id="tab-btn4" userscript-tab-content="#tab-videos" data-name="videos" class="tab-btn">${sTabBtnVideos}${str1}</a>`,
  2109. `<a id="tab-btn5" userscript-tab-content="#tab-list" class="tab-btn">${sTabBtnPlayList}${str1}</a>`
  2110. ].join('')
  2111.  
  2112. var addHTML = `
  2113. <div id="right-tabs">
  2114. <header>
  2115. <div id="material-tabs">
  2116. ${str_tabs}
  2117. </div>
  2118. </header>
  2119. <div class="tab-content">
  2120. <div id="tab-info" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2121. <div id="tab-live" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2122. <div id="tab-comments" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2123. <div id="tab-videos" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2124. <div id="tab-list" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  2125. </div>
  2126. </div>
  2127. `;
  2128.  
  2129. return addHTML
  2130.  
  2131. }
  2132.  
  2133. function injection_script_1(){
  2134.  
  2135. document.addEventListener('userscript-call-dom-func',function(evt){
  2136.  
  2137. if(!evt || !evt.target || !evt.detail) return;
  2138. let dom = evt.target;
  2139.  
  2140. let {property, args}=evt.detail;
  2141. if(!property) return;
  2142. let f=dom[property];
  2143. if(typeof f !='function') return;
  2144.  
  2145. if(args&&args.length>0) f.apply(dom,args);
  2146. else f.call(dom);
  2147.  
  2148. },true)
  2149.  
  2150. }
  2151.  
  2152. function _onNavigationEnd() {
  2153.  
  2154. let timeoutR_findRelated=new Timeout();
  2155. timeoutR_findRelated.set(function(){
  2156. let related = kRef(ytdFlexy).querySelector("#related");
  2157. if(!related) return true;
  2158. foundRelated(related);
  2159. },100,10)
  2160.  
  2161. function foundRelated(related){
  2162. let promise = Promise.resolve();
  2163. if (!document.querySelector("#right-tabs")) {
  2164. promise=promise.then(()=>{
  2165. $(getTabsHTML()).insertBefore(related).attr('data-dom-created-by-tabview-youtube',scriptVersionForExternal);
  2166. })
  2167. }
  2168. promise.then(runAfterTabAppended).then(initObserver)
  2169. }
  2170.  
  2171. setTimeout(()=>{
  2172. for(const s of document.querySelectorAll('#right-tabs [userscript-scrollbar-render]')){
  2173. Promise.resolve(s).then(s=>{
  2174. if(s && s.scrollTop>0) s.scrollTop=0;
  2175. let child =s.firstElementChild;
  2176. if(child && child.scrollTop>0) child.scrollTop =0;
  2177. });
  2178. }
  2179. },90)
  2180.  
  2181. }
  2182.  
  2183. function onNavigationEnd(evt) {
  2184. pluginUnhook(); // in case not triggered by popstate - say mini playing
  2185.  
  2186. $('span#tab3-txt-loader').text('');
  2187. setCommentSection(0)
  2188.  
  2189. if(!/^https?\:\/\/(\w+\.)*youtube\.com\/watch\?(\w+\=[^\/\?\&]+\&|\w+\=?\&)*v=[\w\-\_]+/.test(window.location.href))return;
  2190.  
  2191.  
  2192. let timeout = 4; // max. 4 animation frames
  2193.  
  2194. let tf = ()=>{
  2195. if(--timeout>0 && !document.querySelector('#player video')) return requestAnimationFrame(tf);
  2196. initializeForVideoChange();
  2197. pluginHook();
  2198. _onNavigationEnd();
  2199. }
  2200.  
  2201.  
  2202. tf();
  2203.  
  2204.  
  2205.  
  2206.  
  2207.  
  2208.  
  2209. }
  2210.  
  2211. function setToActiveTab(defaultTab) {
  2212. if(isTheater() && isWideScreenWithTwoColumns())return;
  2213. const jElm = document.querySelector(`a[userscript-tab-content="${switchTabActivity_lastTab}"]:not(.tab-btn-hidden)`) ||
  2214. document.querySelector(`a[userscript-tab-content="${(defaultTab||settings.defaultTab)}"]:not(.tab-btn-hidden)`) ||
  2215. document.querySelector("a[userscript-tab-content]:not(.tab-btn-hidden)") ||
  2216. null;
  2217. switchTabActivity(jElm);
  2218. return !!jElm;
  2219. }
  2220.  
  2221. function getWrapper(wrapperId){
  2222. let $wrapper = $(`#${wrapperId}`);
  2223. if(!$wrapper[0]) $wrapper=$(`<div id="${wrapperId}"></div>`)
  2224. return $wrapper.first();
  2225. }
  2226.  
  2227. function runAfterTabAppended() {
  2228.  
  2229. document.documentElement.setAttribute('plugin-tabview-youtube','')
  2230. const cssElm = kRef(ytdFlexy)
  2231. if(cssElm && !cssElm.hasAttribute('tabview-selection')) cssElm.setAttribute('tabview-selection','')
  2232.  
  2233.  
  2234. // append the next videos
  2235. // it exists as "related" is already here
  2236. fixTabs();
  2237.  
  2238. // just switch to the default tab
  2239. setToActiveTab();
  2240.  
  2241.  
  2242. prepareTabBtn();
  2243.  
  2244. // append the detailed meta contents to the tab-info
  2245. FOnce.mtf_checkDescriptionLoaded = FP.mtf_checkDescriptionLoaded;
  2246. if(Q.mutationTarget===null) $callOnceAsync('mtf_checkDescriptionLoaded');
  2247.  
  2248. // force window scroll when #continuations is first detected and #comments still [hidden]
  2249. FOnce.mtf_advancedComments = FP.mtf_advancedComments;
  2250. if(Q.mutationTarget===null) $callOnceAsync('mtf_advancedComments');
  2251.  
  2252. // use video player's element to detect the live-chat situation (no commenting section)
  2253. // 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
  2254. FOnce.mtf_forceCheckLiveVideo = FP.mtf_forceCheckLiveVideo;
  2255. if(Q.mutationTarget===null) $callOnceAsync('mtf_forceCheckLiveVideo');
  2256.  
  2257.  
  2258. // Attr Mutation Observer - #playlist - hidden
  2259. clearMutationObserver(mtoVs,'mtoVisibility_Playlist')
  2260. // Attr Mutation Observer callback - #playlist - hidden
  2261.  
  2262. // pending for #playlist and set Attribute Observer
  2263. FOnce.mtf_initalAttr_playlist=FP.mtf_initalAttr_playlist
  2264. if(Q.mutationTarget===null) $callOnceAsync('mtf_initalAttr_playlist');
  2265.  
  2266. // Attr Mutation Observer - ytd-comments#comments - hidden
  2267. clearMutationObserver(mtoVs,'mtoVisibility_Comments')
  2268. // Attr Mutation Observer callback - ytd-comments#comments - hidden
  2269.  
  2270. // pending for #comments and set Attribute Observer
  2271. FOnce.mtf_initalAttr_comments=FP.mtf_initalAttr_comments;
  2272. if(Q.mutationTarget===null) $callOnceAsync('mtf_initalAttr_comments');
  2273.  
  2274.  
  2275. clearMutationObserver(mtoVs,'mtoVisibility_Chatroom');
  2276. FOnce.mtf_initalAttr_chatroom=FP.mtf_initalAttr_chatroom
  2277. if(Q.mutationTarget===null) $callOnceAsync('mtf_initalAttr_chatroom');
  2278.  
  2279. clearMutationObserver(mtoVs,'mtoVisibility_EngagementPanel');
  2280. for(const engagement_panel of document.querySelectorAll('ytd-watch-flexy ytd-engagement-panel-section-list-renderer:not([tabview-attr-checked])')){
  2281. engagement_panel.removeAttribute('tabview-attr-checked');
  2282. }
  2283. FOnce.mtf_initalAttr_engagement_panel=FP.mtf_initalAttr_engagement_panel
  2284. if(Q.mutationTarget===null) $callOnceAsync('mtf_initalAttr_engagement_panel');
  2285.  
  2286.  
  2287. flexyAttrInit();
  2288.  
  2289. document.querySelector("#right-tabs .tab-content").addEventListener('scroll', makeBodyScrollByEvt, true);
  2290.  
  2291. }
  2292.  
  2293.  
  2294. function fetchCommentsFinished(){
  2295. let ytdFlexyElm = kRef(ytdFlexy);
  2296. if(!scriptEnable || !ytdFlexyElm) return;
  2297. timeout_attrComments.clear();
  2298. akAttr(ytdFlexyElm,'tabview-youtube-comments',false, 'LS')
  2299. }
  2300.  
  2301. function setCommentSection(value){
  2302. Q.comments_section_loaded =value;
  2303.  
  2304. if(value===0){
  2305. comments_section_loaded_elm=null;
  2306. }
  2307.  
  2308. }
  2309.  
  2310. function _disableComments(){
  2311.  
  2312. if(!scriptEnable)return;
  2313. let cssElm=kRef(ytdFlexy);
  2314. if(!cssElm)return;
  2315.  
  2316. mtoInterval=mtoInterval2;
  2317. setCommentSection(2);
  2318. comments_section_loaded_elm=mWeakRef(document.querySelector('ytd-comments#comments div#contents')||null)
  2319. let tabBtn = document.querySelector('.tab-btn[userscript-tab-content="#tab-comments"]');
  2320. if(tabBtn && !tabBtn.classList.contains('tab-btn-hidden')){
  2321. hideTabBtn(tabBtn)
  2322. }
  2323. akAttr(cssElm,'tabview-youtube-comments',true, 'D');
  2324. FOnce.mtf_fetchCommentsAvailable = null;
  2325.  
  2326.  
  2327. }
  2328.  
  2329.  
  2330.  
  2331.  
  2332.  
  2333. let layoutStatusMutex=new Mutex();
  2334.  
  2335. function forceDisplayChatReplay(){
  2336. let items=chatFrameElement('yt-live-chat-item-list-renderer #items');
  2337. if(items && items.childElementCount!==0)return;
  2338.  
  2339. let ytd_player = document.querySelector('ytd-player#ytd-player');
  2340. if(!ytd_player)return;
  2341. let videoElm = ytd_player.querySelector('video');
  2342. if(!videoElm)return;
  2343.  
  2344. let video=videoElm;
  2345. if(videoElm && video.currentTime > 0 && !video.ended && video.readyState > video.HAVE_CURRENT_DATA){
  2346. let chat = document.querySelector('ytd-live-chat-frame#chat');
  2347. if(chat){
  2348. nativeFunc(chat, "postToContentWindow", [{"yt-player-video-progress":videoElm.currentTime}])
  2349. }
  2350. }
  2351.  
  2352. }
  2353.  
  2354.  
  2355. function runAfterExpandChat(){
  2356.  
  2357.  
  2358.  
  2359. new Promise(resolve=>{
  2360.  
  2361.  
  2362. let chatFrame_st=Date.now();
  2363. let cid_chatFrameCheck = 0;
  2364. let sEF = new ScriptEF();
  2365. cid_chatFrameCheck=setInterval(()=>{
  2366. if(!sEF.isValid()) return cid_chatFrameCheck=clearInterval(cid_chatFrameCheck);
  2367. let cDoc = chatFrameContentDocument();
  2368. if(cDoc) {
  2369. cid_chatFrameCheck=clearInterval(cid_chatFrameCheck);
  2370. resolve();
  2371. }else if(!scriptEnable || !isChatExpand() || Date.now() - chatFrame_st > 6750){
  2372. cid_chatFrameCheck=clearInterval(cid_chatFrameCheck);
  2373. }
  2374. },60);
  2375.  
  2376.  
  2377. }).then(()=>new Promise(resolve=>{
  2378.  
  2379.  
  2380.  
  2381.  
  2382. let timeoutR_ChatAppReady=new Timeout();
  2383. timeoutR_ChatAppReady.set(()=>{
  2384. if(!scriptEnable || !isChatExpand())return false;
  2385. let app=chatFrameElement('yt-live-chat-app');
  2386. if(!app) return true;
  2387.  
  2388.  
  2389. setTimeout(()=>resolve(app),40)
  2390.  
  2391. },40,150); //40*150 = 6000ms = 6s;
  2392.  
  2393.  
  2394.  
  2395. })).then(app=>{
  2396.  
  2397. let cDoc = app.ownerDocument;
  2398.  
  2399. if(!scriptEnable || !isChatExpand())return;
  2400. addStyle(`
  2401. body #input-panel.yt-live-chat-renderer::after {
  2402. background: transparent;
  2403. }
  2404. #items.yt-live-chat-item-list-renderer{
  2405. contain: content;
  2406. }
  2407. yt-live-chat-text-message-renderer{
  2408. contain: content;
  2409. }
  2410. #item-offset.yt-live-chat-item-list-renderer{
  2411. contain: content;
  2412. }
  2413. #item-scroller.yt-live-chat-item-list-renderer{
  2414. contain: strict;
  2415. }
  2416. img[width][height]{
  2417. contain: strict;
  2418. }
  2419. #item-list>yt-live-chat-item-list-renderer, #item-list>yt-live-chat-item-list-renderer>#contents{
  2420. contain: strict;
  2421. }
  2422. yt-live-chat-app{
  2423. contain: content;
  2424. }
  2425. `, cDoc.documentElement)
  2426.  
  2427. if(cDoc.querySelector('yt-live-chat-renderer #continuations')){
  2428. setTimeout(()=>mtf_ChatExist(),40);
  2429. $(document.querySelector('ytd-live-chat-frame#chat')).attr('yt-userscript-iframe-loaded','')
  2430. }
  2431.  
  2432. forceDisplayChatReplay();
  2433.  
  2434.  
  2435.  
  2436.  
  2437.  
  2438. })
  2439.  
  2440. }
  2441.  
  2442.  
  2443. function flexyAttrInit(){
  2444. clearMutationObserver(mtoVs,'mtoFlexyAttr')
  2445.  
  2446. function toggleFlag(mFlag, b, flag){
  2447. if(b) mFlag = mFlag | flag;
  2448. else mFlag = mFlag & ~flag;
  2449. return mFlag;
  2450. }
  2451.  
  2452. function toLayoutStatus(nls, attributeName){
  2453.  
  2454. let attrElm, b, v;
  2455. switch (attributeName){
  2456. case 'theater':
  2457. nls = toggleFlag(nls, isTheater(), LAYOUT_THEATER);
  2458. break;
  2459. case 'userscript-chat-collapsed':
  2460. case 'userscript-chatblock':
  2461. attrElm=kRef(ytdFlexy);
  2462. if(hasAttribute(attrElm, 'userscript-chat-collapsed')) {
  2463. nls = toggleFlag(nls, true, LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLASPED);
  2464. }else{
  2465. nls = toggleFlag(nls, hasAttribute(attrElm, 'userscript-chatblock'), LAYOUT_CHATROOM);
  2466. nls = toggleFlag(nls, false, LAYOUT_CHATROOM_COLLASPED);
  2467. }
  2468. break;
  2469. case 'is-two-columns_':
  2470. nls = toggleFlag(nls, isWideScreenWithTwoColumns(), LAYOUT_TWO_COLUMNS);
  2471. break;
  2472.  
  2473. case 'tabview-selection':
  2474. b = isNonEmptyString( kRef(ytdFlexy).getAttribute('tabview-selection') );
  2475. nls = toggleFlag(nls, b, LAYOUT_TAB_EXPANDED);
  2476. break;
  2477.  
  2478. case 'fullscreen':
  2479. b = isNonEmptyString( kRef(ytdFlexy).getAttribute('fullscreen') );
  2480. nls = toggleFlag(nls, b, LAYOUT_FULLSCREEN);
  2481. break;
  2482.  
  2483. case 'userscript-engagement-panel':
  2484. v = kRef(ytdFlexy).getAttribute('userscript-engagement-panel');
  2485. b = (+v>0)
  2486. nls = toggleFlag(nls, b, LAYOUT_ENGAGEMENT_PANEL_EXPAND);
  2487. break;
  2488.  
  2489. }
  2490.  
  2491. return nls;
  2492.  
  2493.  
  2494. }
  2495.  
  2496. let mtf_attrFlexy=(mutations, observer)=>{
  2497. if(!scriptEnable)return;
  2498.  
  2499.  
  2500. const cssElm=kRef(ytdFlexy)
  2501. if(!cssElm)return;
  2502.  
  2503. if(!mutations)return;
  2504.  
  2505. const old_layoutStatus = wls.layoutStatus
  2506. if(old_layoutStatus === null) return;
  2507. let new_layoutStatus = old_layoutStatus;
  2508. for(const mutation of mutations) {
  2509. new_layoutStatus=toLayoutStatus(new_layoutStatus, mutation.attributeName);
  2510. if (mutation.attributeName == 'userscript-chat-collapsed' || mutation.attributeName == 'userscript-chatblock'){
  2511.  
  2512. if( cssElm.getAttribute('userscript-chatblock')==='chat-live' ){
  2513. requestingComments=null;
  2514. _disableComments();
  2515.  
  2516. }
  2517.  
  2518. if(!cssElm.hasAttribute('userscript-chatblock')){
  2519. setTimeout(()=>{
  2520. if(!scriptEnable)return;
  2521. if(!isAnyActiveTab() && !isChatExpand() && !isTheater() && isWideScreenWithTwoColumns() && !isFullScreen()){
  2522. setToActiveTab();
  2523. }
  2524. },240);
  2525. }
  2526.  
  2527. }
  2528. }
  2529.  
  2530.  
  2531. if(new_layoutStatus!==old_layoutStatus) wls.layoutStatus = new_layoutStatus
  2532.  
  2533.  
  2534.  
  2535.  
  2536. }
  2537.  
  2538.  
  2539. FOnce.mtf_checkFlexy=()=>{
  2540. let ytdFlexyElm = kRef(ytdFlexy);
  2541. if(!scriptEnable || !ytdFlexyElm) return true;
  2542.  
  2543. const rootElement = Q.mutationTarget || ytdFlexyElm;
  2544.  
  2545. var flexy=kRef(ytdFlexy)
  2546. if(!flexy) return true;
  2547.  
  2548. wls.layoutStatus=null;
  2549.  
  2550. let rChatExist = base_ChatExist();
  2551. if(rChatExist){
  2552. let {attr_chatblock, attr_chatcollapsed} = rChatExist;
  2553. if(attr_chatblock===null) {
  2554. //remove attribute if it is unknown
  2555. attr_chatblock=false;
  2556. attr_chatcollapsed=false;
  2557. }
  2558. wAttr(flexy, 'userscript-chatblock', attr_chatblock)
  2559. wAttr(flexy, 'userscript-chat-collapsed', attr_chatcollapsed)
  2560. }
  2561.  
  2562. let rTabSelection = [...flexy.querySelectorAll('.tab-btn[userscript-tab-content]')]
  2563. .map(elm=>({elm, hidden: elm.classList.contains('tab-btn-hidden') }))
  2564. .filter(entry=>entry.hidden===true);
  2565. if(rTabSelection.length===0) wAttr(flexy, 'tabview-selection', false);
  2566. else if(rTabSelection.length===1) wAttr(flexy, 'tabview-selection', rTabSelection[0].elm.getAttribute('userscript-tab-content')||'');
  2567.  
  2568. let rEP = engagement_panels_();
  2569. if(rEP && rEP.count>0) wAttr(flexy, 'userscript-engagement-panel', false);
  2570. else wAttr(flexy, 'userscript-engagement-panel', rEP.value+"");
  2571.  
  2572. let ls = 0;
  2573. ls=toLayoutStatus(ls,'theater')
  2574. ls=toLayoutStatus(ls,'userscript-chat-collapsed')
  2575. ls=toLayoutStatus(ls,'userscript-chatblock')
  2576. ls=toLayoutStatus(ls,'is-two-columns_')
  2577. ls=toLayoutStatus(ls,'tabview-selection')
  2578. ls=toLayoutStatus(ls,'fullscreen')
  2579. ls=toLayoutStatus(ls,'userscript-engagement-panel')
  2580.  
  2581. wls.layoutStatus=ls
  2582.  
  2583. initMutationObserver(mtoVs,'mtoFlexyAttr',mtf_attrFlexy).observe(flexy, {
  2584. attributes: true,
  2585. attributeFilter: ['userscript-chat-collapsed','userscript-chatblock','theater','is-two-columns_','tabview-selection','fullscreen','userscript-engagement-panel'],
  2586. attributeOldValue: true
  2587. })
  2588.  
  2589.  
  2590. let columns = document.querySelector('ytd-page-manager#page-manager #columns')
  2591. if(columns){
  2592. wAttr(columns, 'userscript-scrollbar-render', true);
  2593. }
  2594.  
  2595. return false;
  2596. }
  2597. if(Q.mutationTarget===null) $callOnceAsync('mtf_checkFlexy')
  2598.  
  2599.  
  2600.  
  2601. }
  2602.  
  2603.  
  2604.  
  2605.  
  2606. let switchTabActivity_lastTab = null
  2607.  
  2608. function setDisplayedPlaylist(){
  2609. //override the default youtube coding event prevention
  2610. let cssElm=kRef(ytdFlexy);
  2611. if(!scriptEnable || !cssElm)return;
  2612. displayedPlaylist=mWeakRef(document.querySelector('ytd-watch-flexy #tab-list:not(.tab-content-hidden) ytd-playlist-panel-renderer') || null);
  2613. }
  2614.  
  2615. function switchTabActivity(activeLink) {
  2616. if(!scriptEnable)return;
  2617. const ytdFlexyElm=kRef(ytdFlexy);
  2618. if(!ytdFlexyElm) return;
  2619. if (activeLink && activeLink.classList.contains('tab-btn-hidden')) return; // not allow to switch to hide tab
  2620. if(isTheater() && isWideScreenWithTwoColumns()) activeLink=null;
  2621. function runAtEnd(){
  2622. if(activeLink) lastShowTab=activeLink.getAttribute('userscript-tab-content')
  2623. displayedPlaylist=null;
  2624. scrollingVideosList=null;
  2625. if(activeLink && lastShowTab == '#tab-list'){
  2626. setDisplayedPlaylist();
  2627. }else if(activeLink && lastShowTab == '#tab-videos'){
  2628. scrollingVideosList=mWeakRef(document.querySelector('ytd-watch-flexy #tab-videos:not(.tab-content-hidden) [placeholder-videos]'));
  2629. }
  2630.  
  2631. ytdFlexyElm.setAttribute('tabview-selection',activeLink?lastShowTab:'')
  2632. if(lastShowTab == '#tab-comments' && (ytdFlexyElm.getAttribute('tabview-youtube-comments')||'').lastIndexOf('S')>=0){
  2633.  
  2634. akAttr(ytdFlexyElm, 'tabview-youtube-comments',false,'L');
  2635.  
  2636. requestAnimationFrame(()=>{
  2637. let comments_tab=document.querySelector('#tab-comments');
  2638. if(comments_tab && comments_tab.scrollTop>0) comments_tab.scrollTop=0;
  2639. });
  2640.  
  2641. }
  2642. }
  2643. const links = document.querySelectorAll('#material-tabs a[userscript-tab-content]');
  2644. for (const link of links) {
  2645. let content = document.querySelector(link.getAttribute('userscript-tab-content'));
  2646. if (link && content) {
  2647. if (link !== activeLink) {
  2648. link.classList.remove("active");
  2649. content.classList.add("tab-content-hidden");
  2650. } else {
  2651. link.classList.add("active");
  2652. content.classList.remove("tab-content-hidden");
  2653. //setTimeout(()=>content.focus(),400);
  2654. }
  2655. }
  2656. }
  2657. runAtEnd();
  2658. }
  2659.  
  2660. let tabsUiScript_setclick = false;
  2661.  
  2662. function prepareTabBtn() {
  2663.  
  2664. const materialTab = document.querySelector("#material-tabs")
  2665. if (!materialTab) return;
  2666.  
  2667. let noActiveTab = !!document.querySelector('ytd-watch-flexy[userscript-chatblock]:not([userscript-chat-collapsed])')
  2668.  
  2669. const activeLink = materialTab.querySelector('a[userscript-tab-content].active:not(.tab-btn-hidden)')
  2670. if (activeLink) switchTabActivity(noActiveTab ? null : activeLink)
  2671.  
  2672. if (!tabsUiScript_setclick) {
  2673. tabsUiScript_setclick = true;
  2674. $(materialTab).on("click", "a", function(evt) {
  2675.  
  2676. let ytdFlexyElm = kRef(ytdFlexy);
  2677. if(!scriptEnable || !ytdFlexyElm) return null;
  2678.  
  2679. if (!this.hasAttribute('userscript-tab-content')) return;
  2680.  
  2681.  
  2682. evt.preventDefault();
  2683.  
  2684.  
  2685. if(this.getAttribute('userscript-tab-content')=='#tab-comments' && parseInt(ytdFlexyElm.getAttribute('tabview-youtube-comments')||'')<0){
  2686. return;
  2687. }
  2688.  
  2689. new Promise(requestAnimationFrame).then(() => {
  2690.  
  2691.  
  2692. layoutStatusMutex.lockWith(unlock=>{
  2693.  
  2694. switchTabActivity_lastTab = this.getAttribute('userscript-tab-content');
  2695.  
  2696. let isActiveAndVisible = this.classList.contains('tab-btn') && this.classList.contains('active') && !this.classList.contains('tab-btn-hidden')
  2697. if( isWideScreenWithTwoColumns() && !isTheater() && isActiveAndVisible){
  2698. //optional
  2699. setTimeout(unlock,80);
  2700. switchTabActivity(null);
  2701. ytBtnSetTheater();
  2702. }else if(isActiveAndVisible){
  2703. setTimeout(unlock,80);
  2704. switchTabActivity(null);
  2705. }else{
  2706. let pInterval = 60;
  2707. if(isChatExpand() && isWideScreenWithTwoColumns()) {
  2708. ytBtnCollapseChat();
  2709. }else if(isEngagementPanelExpanded() && isWideScreenWithTwoColumns()){
  2710. ytBtnCloseEngagementPanels();
  2711. }else if(isWideScreenWithTwoColumns() && isTheater() && !isFullScreen() ){
  2712. ytBtnCancelTheater();
  2713. }else{
  2714. pInterval=20;
  2715. }
  2716. setTimeout(()=>{
  2717. setTimeout(makeBodyScroll,20); // this is to make the image render
  2718. setTimeout(()=>{
  2719. let rightTabs=document.querySelector('#right-tabs');
  2720. if(!isWideScreenWithTwoColumns() && rightTabs && rightTabs.offsetTop>0 && this.classList.contains('active')){
  2721. let tabButtonBar = document.querySelector('#material-tabs');
  2722. let tabButtonBarHeight = tabButtonBar?tabButtonBar.offsetHeight:0;
  2723. window.scrollTo(0, rightTabs.offsetTop-tabButtonBarHeight);
  2724. }
  2725. },60)
  2726. setTimeout(unlock,80)
  2727. switchTabActivity(this)
  2728. }, pInterval);
  2729. }
  2730. })
  2731.  
  2732. })
  2733.  
  2734.  
  2735. });
  2736.  
  2737. }
  2738.  
  2739. }
  2740.  
  2741.  
  2742. // ---------------------------------------------------------------------------------------------
  2743. window.addEventListener("yt-navigate-finish", onNavigationEnd)
  2744. document.addEventListener("loadstart",(evt)=>{
  2745. if(!evt || !evt.target || evt.target.nodeName!=="VIDEO")return;
  2746. let elm = evt.target;
  2747. if(!elm.matches('#player video, #movie_player video, video[tabview-mainvideo]'))return;
  2748. let src = elm.src;
  2749. if(src!==lastVideoURL){
  2750. lastVideoURL = elm.src;
  2751.  
  2752. elm.setAttribute('tabview-mainvideo', ''); // mainly for mini playing
  2753.  
  2754.  
  2755. }
  2756.  
  2757. }, true)
  2758.  
  2759. // ---------------------------------------------------------------------------------------------
  2760.  
  2761. var scrolling_lastD = 0;
  2762.  
  2763. const singleColumnScrolling = function(scrolling_lastF) {
  2764. let pageY = pageYOffset;
  2765. if (pageY < 10 && scrolling_lastD === 0 && !scrolling_lastF) return;
  2766.  
  2767. let targetElm, header, navElm;
  2768.  
  2769. Promise.resolve().then(() => {
  2770.  
  2771. targetElm = document.querySelector("#right-tabs");
  2772. if (!targetElm) return;
  2773. header = targetElm.querySelector("header");
  2774. if (!header) return;
  2775. navElm = document.querySelector('#masthead-container, #masthead')
  2776. if (!navElm) return;
  2777. let navHeight = navElm ? navElm.offsetHeight : 0
  2778.  
  2779. let elmY = targetElm.offsetTop
  2780.  
  2781. let xyz = [elmY + navHeight, pageY, elmY - navHeight]
  2782.  
  2783. let xyStatus = 0
  2784. if (xyz[1] < xyz[2] && xyz[2] < xyz[0]) {
  2785. // 1
  2786. xyStatus = 1
  2787. }
  2788.  
  2789. if (xyz[0] > xyz[1] && xyz[1] > xyz[2]) {
  2790.  
  2791. //2
  2792. xyStatus = 2
  2793.  
  2794. }
  2795.  
  2796. if (xyz[2] < xyz[0] && xyz[0] < xyz[1]) {
  2797. // 3
  2798.  
  2799. xyStatus = 3
  2800.  
  2801.  
  2802. }
  2803.  
  2804. return xyStatus;
  2805.  
  2806. }).then((xyStatus) => {
  2807.  
  2808. if ((xyStatus == 2 || xyStatus == 3) && (scrolling_lastD === 0 || scrolling_lastF)) {
  2809. scrolling_lastD = 1;
  2810. let {
  2811. offsetHeight
  2812. } = header
  2813. let {
  2814. offsetWidth
  2815. } = targetElm
  2816.  
  2817. targetElm.style.setProperty("--userscript-sticky-width", offsetWidth + 'px')
  2818. targetElm.style.setProperty("--userscript-sticky", offsetHeight + 'px')
  2819. wAttr(targetElm, 'userscript-sticky', true);
  2820.  
  2821. } else if ((xyStatus == 1) && (scrolling_lastD === 1 || scrolling_lastF)) {
  2822. scrolling_lastD = 0;
  2823.  
  2824. wAttr(targetElm, 'userscript-sticky', false);
  2825. }
  2826.  
  2827.  
  2828. targetElm = null;
  2829. header = null;
  2830. navElm = null;
  2831.  
  2832. });
  2833.  
  2834. };
  2835.  
  2836. window.addEventListener("scroll", function() {
  2837. if(!scriptEnable)return;
  2838. singleColumnScrolling(false)
  2839. }, {
  2840. capture: false,
  2841. passive: true
  2842. })
  2843.  
  2844. var lastResizeAt=0;
  2845. window.addEventListener('resize', function() {
  2846. if(!scriptEnable)return;
  2847. lastResizeAt= Date.now();
  2848.  
  2849. requestAnimationFrame(() => {
  2850. if(!scriptEnable)return;
  2851. singleColumnScrolling(true)
  2852. })
  2853.  
  2854. }, {
  2855. capture: false,
  2856. passive: true
  2857. })
  2858.  
  2859. window.addEventListener('beforeunload', function() {
  2860. if(!scriptEnable)return;
  2861. console.log('beforeunload')
  2862. pluginUnhook();
  2863. //let video=document.querySelector('video');
  2864. //if(video && !video.paused) video.pause();
  2865. }, {capture: true})
  2866.  
  2867. window.addEventListener('hashchange', function() {
  2868. if(!scriptEnable)return;
  2869. console.log('hashchange')
  2870. pluginUnhook();
  2871. }, {capture: true})
  2872. window.addEventListener('popstate', function() {
  2873. if(!scriptEnable)return;
  2874. console.log('popstate')
  2875. pluginUnhook();
  2876. }, {capture: true})
  2877.  
  2878.  
  2879. function clearMutationObserver(o, key){
  2880. if(o[key]) {
  2881. o[key].takeRecords();
  2882. o[key].disconnect();
  2883. o[key]=null;
  2884. return true;
  2885. }
  2886. }
  2887.  
  2888. function initMutationObserver(o, key, callback){
  2889. clearMutationObserver(o,key);
  2890. const mto = new MutationObserver(callback);
  2891. o[key] = mto;
  2892. return mto;
  2893. }
  2894.  
  2895. document.addEventListener('wheel',function(evt){
  2896. if(!scriptEnable)return;
  2897. const displayedPlaylist_element = kRef(displayedPlaylist);
  2898. if(displayedPlaylist_element && displayedPlaylist_element.contains(evt.target)){
  2899. evt.stopPropagation();
  2900. evt.stopImmediatePropagation();
  2901. }
  2902. },{capture:true,passive:true});
  2903.  
  2904.  
  2905. function setVideosTwoColumns(flag, bool){
  2906.  
  2907. //two columns to one column
  2908.  
  2909. /*
  2910. [placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy
  2911.  
  2912. is-two-columns ="" => no is-two-columns
  2913. [placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer
  2914. no hidden => hidden =""
  2915. [placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer
  2916. hidden ="" => no hidden
  2917.  
  2918. */
  2919.  
  2920. let cssSelector1='[placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy'
  2921.  
  2922. let cssSelector2='[placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer'
  2923.  
  2924. let cssSelector3='[placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer'
  2925.  
  2926. let res={}
  2927. if(flag&1){
  2928. res.m1=document.querySelector(cssSelector1)
  2929. if(res.m1) wAttr(res.m1, 'is-two-columns', bool?'':false);
  2930. }
  2931.  
  2932. if(flag&2){
  2933. res.m2=document.querySelector(cssSelector2)
  2934. if(res.m2) wAttr(res.m2, 'hidden', bool?false:'');
  2935. }
  2936.  
  2937. if(flag&4){
  2938. res.m3=document.querySelector(cssSelector3)
  2939. if(res.m3) wAttr(res.m3, 'hidden', bool?'':false);
  2940. }
  2941.  
  2942.  
  2943. return res
  2944.  
  2945.  
  2946. }
  2947.  
  2948. let lastScrollFetch=0;
  2949. // function isScrolledToEnd(){
  2950. // return (window.innerHeight + window.pageYOffset) >= document.scrollingElement.scrollHeight - 2;
  2951. // }
  2952. let lastOffsetTop = 0;
  2953. window.addEventListener('scroll',function(evt){
  2954.  
  2955. //console.log(evt.target)
  2956.  
  2957. if(!scriptEnable)return;
  2958.  
  2959.  
  2960. if( !kRef(scrollingVideosList) ) return;
  2961. if( videoListBeforeSearch ) return;
  2962.  
  2963.  
  2964.  
  2965. let visibleHeight = document.scrollingElement.clientHeight;
  2966. let totalHeight = document.scrollingElement.scrollHeight;
  2967.  
  2968. if(totalHeight<visibleHeight*1.5)return; // filter out two column view;
  2969.  
  2970. let z = window.pageYOffset + visibleHeight;
  2971. let h_advanced = totalHeight - ( visibleHeight > 5*40 ? visibleHeight*0.5 : 40 );
  2972.  
  2973.  
  2974.  
  2975. if(z>h_advanced && !isWideScreenWithTwoColumns() ){
  2976.  
  2977. let ct = Date.now();
  2978. if(ct - lastScrollFetch<500) return; //prevent continuous calling
  2979.  
  2980. lastScrollFetch=ct;
  2981. let res= setVideosTwoColumns(2|4, true)
  2982. if(res.m3 && res.m2){
  2983.  
  2984. //wait for DOM change, just in case
  2985. requestAnimationFrame(()=>{
  2986. let {offsetTop}=res.m2 // as visibility of m2 & m3 switched.
  2987.  
  2988. if(offsetTop-lastOffsetTop<40) return; // in case bug, or repeating calling. // the next button shall below the this button
  2989. lastOffsetTop= offsetTop
  2990.  
  2991. res.m2.parentNode.dispatchEvent(new Event('yt-service-request-sent-button-renderer'))
  2992. res= null
  2993. })
  2994.  
  2995. }else{
  2996. res= null
  2997. }
  2998.  
  2999.  
  3000. }
  3001.  
  3002.  
  3003.  
  3004.  
  3005. },{passive:true})
  3006.  
  3007.  
  3008. /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */
  3009. document.addEventListener('scroll',function(){
  3010. let css = `ytd-expander[should-use-number-of-lines][collapsed] > #content.ytd-expander:not([tabview-webkitbox-linefix])`;
  3011. setTimeout(function(){ // less priority
  3012. requestAnimationFrame(function(){ // only active tab
  3013. for(const elm of document.querySelectorAll(css)){
  3014. elm.setAttribute('tabview-webkitbox-linefix','') // force render to eliminate the browser bug
  3015. }
  3016. });
  3017. },40);
  3018. },true);
  3019. /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */
  3020.  
  3021.  
  3022.  
  3023.  
  3024. })();
  3025.  
  3026.  
  3027.  
  3028.  
  3029.  
  3030.  
  3031.  
  3032.  
  3033.  
  3034.  
  3035.  
  3036.  
  3037.  
  3038.  
  3039.  
  3040.  
  3041.  
  3042.  
  3043.  
  3044.  
  3045.  
  3046.  
  3047.  
  3048.  
  3049.  
  3050.  
  3051.  
  3052.  
  3053.  
  3054.  
  3055.  
  3056.  
  3057.  
  3058.  
  3059.  
  3060.  
  3061.  
  3062.  
  3063. // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js
  3064.  
  3065. }
  3066.  
  3067.  
  3068. ;!(function $$() {
  3069. 'use strict';
  3070.  
  3071. if(document.documentElement==null) return window.requestAnimationFrame($$)
  3072.  
  3073. var cssTxt = GM_getResourceText("contentCSS");
  3074.  
  3075. function addStyle (styleText) {
  3076. const styleNode = document.createElement('style');
  3077. styleNode.type = 'text/css';
  3078. styleNode.textContent = styleText;
  3079. document.documentElement.appendChild(styleNode);
  3080. return styleNode;
  3081. }
  3082.  
  3083.  
  3084. addStyle (cssTxt);
  3085.  
  3086. main(window.$);
  3087.  
  3088.  
  3089. // Your code here...
  3090. })();