Tabview Youtube

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

当前为 2022-12-04 提交的版本,查看 最新版本

  1. /*
  2.  
  3. MIT License
  4.  
  5. Copyright (c) 2021 cyfung1031
  6.  
  7. Permission is hereby granted, free of charge, to any person obtaining a copy
  8. of this software and associated documentation files (the "Software"), to deal
  9. in the Software without restriction, including without limitation the rights
  10. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. copies of the Software, and to permit persons to whom the Software is
  12. furnished to do so, subject to the following conditions:
  13.  
  14. The above copyright notice and this permission notice shall be included in all
  15. copies or substantial portions of the Software.
  16.  
  17. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23. SOFTWARE.
  24.  
  25. */
  26. // ==UserScript==
  27. // @name Tabview Youtube
  28. // @name:en Tabview Youtube
  29. // @name:ja Tabview Youtube
  30. // @name:zh-TW Tabview Youtube
  31. // @name:zh-CN Tabview Youtube
  32. // @namespace http://tampermonkey.net/
  33. // @version 3.11.2
  34. // @license MIT
  35. // @description Make comments and lists into tabs for YouTube Videos
  36. // @description:en Make comments and lists into tabs for YouTube Videos
  37. // @description:ja YouTube動画のコメントやリストなどをタブに作成します
  38. // @description:zh-TW 把Youtube Videos中的評論及影片清單製作成Tabs
  39. // @description:zh-CN 把Youtube Videos中的评论及视频列表制作成Tabs
  40. // @author CY Fung
  41. // @match https://www.youtube.com/*
  42. // @icon https://github.com/cyfung1031/Tabview-Youtube/raw/main/images/icon128p.png
  43. // @run-at document-start
  44. // @grant GM_getResourceText
  45. // @resource contentCSS https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/36b87cfa6c4aee03c0899174568c210abb793ac1/css/style_content.css
  46. // @resource injectionJS1 https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/ed2bdc58a209e34d36f396528f7d982ae742f9e1/js/injection_script_1.js
  47. // @require https://cdnjs.cloudflare.com/ajax/libs/cash/8.1.1/cash.min.js
  48. // @noframes
  49. // ==/UserScript==
  50.  
  51. /* jshint esversion:8 */
  52.  
  53. function main($){
  54. // MIT License
  55. // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js
  56.  
  57.  
  58.  
  59.  
  60.  
  61.  
  62.  
  63.  
  64.  
  65.  
  66.  
  67.  
  68.  
  69.  
  70.  
  71.  
  72.  
  73. -(function mainBody() {
  74. 'use strict';
  75.  
  76. function inIframe() {
  77. try {
  78. return window.self !== window.top;
  79. } catch (e) {
  80. return true;
  81. }
  82. }
  83.  
  84. if (inIframe()) return;
  85.  
  86. if (!$) return;
  87.  
  88. /**
  89. * SVG resources:
  90. * <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>
  91. */
  92.  
  93. const scriptVersionForExternal = '2022/12/01';
  94.  
  95. const isMyScriptInChromeRuntime = () => typeof GM === 'undefined' && typeof ((((window || 0).chrome || 0).runtime || 0).getURL) === 'function'
  96.  
  97. // https://yqnn.github.io/svg-path-editor/
  98. // https://vecta.io/nano
  99.  
  100. const svgComments = `<path d="M80 27H12A12 12 90 0 0 0 39v42a12 12 90 0 0 12 12h12v20a2 2 90 0 0 3.4 2L47 93h33a12
  101. 12 90 0 0 12-12V39a12 12 90 0 0-12-12zM20 47h26a2 2 90 1 1 0 4H20a2 2 90 1 1 0-4zm52 28H20a2 2 90 1 1 0-4h52a2 2 90
  102. 1 1 0 4zm0-12H20a2 2 90 1 1 0-4h52a2 2 90 1 1 0 4zm36-58H40a12 12 90 0 0-12 12v6h52c9 0 16 7 16 16v42h0v4l7 7a2 2 90
  103. 0 0 3-1V71h2a12 12 90 0 0 12-12V17a12 12 90 0 0-12-12z"/>`.trim();
  104.  
  105. const svgVideos = `<path d="M89 10c0-4-3-7-7-7H7c-4 0-7 3-7 7v70c0 4 3 7 7 7h75c4 0 7-3 7-7V10zm-62 2h13v10H27V12zm-9
  106. 66H9V68h9v10zm0-56H9V12h9v10zm22 56H27V68h13v10zm-3-25V36c0-2 2-3 4-2l12 8c2 1 2 4 0 5l-12 8c-2 1-4 0-4-2zm25
  107. 25H49V68h13v10zm0-56H49V12h13v10zm18 56h-9V68h9v10zm0-56h-9V12h9v10z"/>`.trim();
  108.  
  109. const svgInfo = `<path d="M30 0C13.3 0 0 13.3 0 30s13.3 30 30 30 30-13.3 30-30S46.7 0 30 0zm6.2 46.6c-1.5.5-2.6
  110. 1-3.6 1.3a10.9 10.9 0 0 1-3.3.5c-1.7 0-3.3-.5-4.3-1.4a4.68 4.68 0 0 1-1.6-3.6c0-.4.2-1 .2-1.5a20.9 20.9 90 0 1
  111. .3-2l2-6.8c.1-.7.3-1.3.4-1.9a8.2 8.2 90 0 0 .3-1.6c0-.8-.3-1.4-.7-1.8s-1-.5-2-.5a4.53 4.53 0 0 0-1.6.3c-.5.2-1
  112. .2-1.3.4l.6-2.1c1.2-.5 2.4-1 3.5-1.3s2.3-.6 3.3-.6c1.9 0 3.3.6 4.3 1.3s1.5 2.1 1.5 3.5c0 .3 0 .9-.1 1.6a10.4 10.4
  113. 90 0 1-.4 2.2l-1.9 6.7c-.2.5-.2 1.1-.4 1.8s-.2 1.3-.2 1.6c0 .9.2 1.6.6 1.9s1.1.5 2.1.5a6.1 6.1 90 0 0 1.5-.3 9 9 90
  114. 0 0 1.4-.4l-.6 2.2zm-3.8-35.2a1 1 0 010 8.6 1 1 0 010-8.6z"/>`.trim();
  115.  
  116. const svgPlayList = `<path d="M0 3h12v2H0zm0 4h12v2H0zm0 4h8v2H0zm16 0V7h-2v4h-4v2h4v4h2v-4h4v-2z"/>`.trim();
  117. const svgDiag1 = `<svg stroke="currentColor" fill="none"><path d="M8 2h2v2M7 5l3-3m-6 8H2V8m0 2l3-3"/></svg>`;
  118. const svgDiag2 = `<svg stroke="currentColor" fill="none"><path d="M7 3v2h2M7 5l3-3M5 9V7H3m-1 3l3-3"/></svg>`;
  119.  
  120. const REMOVE_DUPLICATE_INFO = true
  121. const DEBUG_LOG = false
  122.  
  123. const LAYOUT_VAILD = 1;
  124.  
  125. const LAYOUT_TWO_COLUMNS = 2;
  126. const LAYOUT_THEATER = 4;
  127. const LAYOUT_FULLSCREEN = 8;
  128. const LAYOUT_CHATROOM = 16;
  129. const LAYOUT_CHATROOM_COLLAPSED = 32;
  130. const LAYOUT_TAB_EXPANDED = 64;
  131. const LAYOUT_ENGAGEMENT_PANEL_EXPAND = 128;
  132. const LAYOUT_CHATROOM_EXPANDED = 256;
  133.  
  134. const nonCryptoRandStr_base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
  135.  
  136. const nullFunc = function () { };
  137.  
  138. const iframeCSS = (()=>{
  139. return `
  140. body {
  141. --tabview-msg-cursor: default;
  142. --tabview-msg-pointer-events: none;
  143. --tabview-img-pointer-events: auto;
  144. }
  145. body.tabview-allow-pointer-events {
  146. --tabview-msg-cursor: '-NULL-';
  147. --tabview-msg-pointer-events: '-NULL-';
  148. --tabview-img-pointer-events: '-NULL-';
  149. }
  150. body #input-panel.yt-live-chat-renderer::after {
  151. background: transparent;
  152. }
  153. .style-scope.yt-live-chat-item-list-renderer {
  154. box-sizing: border-box;
  155. }
  156. yt-live-chat-text-message-renderer:nth-last-child(-n+30):hover #menu.yt-live-chat-text-message-renderer {
  157. transition-delay: 87ms;
  158. }
  159. yt-live-chat-text-message-renderer #menu.yt-live-chat-text-message-renderer {
  160. transition-delay: 1ms;
  161. }
  162. #item.style-scope.yt-live-chat-item-list-renderer,
  163. #item-scroller.style-scope.yt-live-chat-item-list-renderer {
  164. transition-delay: 42ms;
  165. }
  166. yt-live-chat-item-list-renderer img[alt] {
  167. pointer-events: auto;
  168. }
  169. body yt-live-chat-item-list-renderer img[alt]~tp-yt-paper-tooltip,
  170. body yt-live-chat-item-list-renderer #image~tp-yt-paper-tooltip {
  171. --paper-tooltip-delay-in: 120ms !important;
  172. white-space: nowrap;
  173. }
  174. #items.style-scope.yt-live-chat-item-list-renderer>yt-live-chat-text-message-renderer.yt-live-chat-item-list-renderer {
  175. --tabview-chat-message-display: block;
  176. --tabview-chat-message-mt: 2px;
  177. --tabview-chat-message-mb: 4px;
  178. }
  179. #message.yt-live-chat-text-message-renderer {
  180. display: var(--tabview-chat-message-display);
  181. margin-top: var(--tabview-chat-message-mt);
  182. margin-bottom: var(--tabview-chat-message-mb);
  183. }
  184. [collapsed] #message.yt-live-chat-text-message-renderer {
  185. --tabview-chat-message-display: 'VOID';
  186. --tabview-chat-message-mt: 'VOID';
  187. --tabview-chat-message-mb: 'VOID';
  188. }
  189. @supports (contain: layout paint style) {
  190. body yt-live-chat-app {
  191. contain: size layout paint style;
  192. content-visibility: auto;
  193. transform: translate3d(0, 0, 0);
  194. overflow: hidden;
  195. }
  196. #items.style-scope.yt-live-chat-item-list-renderer,
  197. #item-offset.style-scope.yt-live-chat-item-list-renderer {
  198. contain: layout paint style;
  199. }
  200. #item-scroller.style-scope.yt-live-chat-item-list-renderer {
  201. contain: size style;
  202. }
  203. #contents.style-scope.yt-live-chat-item-list-renderer,
  204. #chat.style-scope.yt-live-chat-renderer,
  205. img.style-scope.yt-img-shadow[width][height] {
  206. contain: size layout paint style;
  207. }
  208. .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label],
  209. .style-scope.yt-live-chat-ticker-renderer[role="button"][aria-label]>#container {
  210. contain: layout paint style;
  211. }
  212. yt-img-shadow#author-photo.style-scope {
  213. contain: layout paint style;
  214. content-visibility: auto;
  215. contain-intrinsic-size: 24px 24px;
  216. }
  217. yt-live-chat-text-message-renderer:not([author-is-owner]) #author-photo.style-scope.yt-live-chat-text-message-renderer,
  218. yt-live-chat-text-message-renderer:not([author-is-owner]) yt-live-chat-author-chip.style-scope.yt-live-chat-text-message-renderer {
  219. pointer-events: var(--tabview-msg-pointer-events);
  220. }
  221. yt-live-chat-text-message-renderer:not([author-is-owner]) span#message.style-scope.yt-live-chat-text-message-renderer>img.emoji.yt-formatted-string.style-scope.yt-live-chat-text-message-renderer {
  222. cursor: var(--tabview-msg-cursor);
  223. }
  224. yt-live-chat-text-message-renderer:not([author-is-owner]) span#message.style-scope.yt-live-chat-text-message-renderer,
  225. yt-live-chat-paid-message-renderer #message.yt-live-chat-paid-message-renderer,
  226. yt-live-chat-text-message-renderer:not([author-is-owner]) #timestamp.style-scope.yt-live-chat-text-message-renderer,
  227. yt-live-chat-membership-item-renderer #header-content.style-scope.yt-live-chat-membership-item-renderer,
  228. yt-live-chat-membership-item-renderer #timestamp.style-scope.yt-live-chat-membership-item-renderer,
  229. yt-live-chat-paid-message-renderer #header-content.yt-live-chat-paid-message-renderer,
  230. yt-live-chat-paid-message-renderer #timestamp.style-scope.yt-live-chat-paid-message-renderer,
  231. yt-live-chat-paid-sticker-renderer #content.style-scope.yt-live-chat-paid-sticker-renderer,
  232. yt-live-chat-paid-sticker-renderer #timestamp.style-scope.yt-live-chat-paid-sticker-renderer {
  233. cursor: var(--tabview-msg-cursor);
  234. pointer-events: var(--tabview-msg-pointer-events);
  235. }
  236. yt-live-chat-text-message-renderer.style-scope.yt-live-chat-item-list-renderer,
  237. yt-live-chat-membership-item-renderer.style-scope.yt-live-chat-item-list-renderer,
  238. yt-live-chat-paid-message-renderer.style-scope.yt-live-chat-item-list-renderer,
  239. yt-live-chat-banner-manager.style-scope.yt-live-chat-item-list-renderer {
  240. contain: layout style;
  241. }
  242. tp-yt-paper-tooltip[style*="inset"][role="tooltip"] {
  243. contain: layout paint style;
  244. }
  245. }
  246. #chat-messages tp-yt-iron-dropdown#dropdown.style-scope.tp-yt-paper-menu-button {
  247. margin-right: var(--ytd-margin-12x);
  248. }
  249. `.trim();
  250. });
  251.  
  252.  
  253. let iframePointEventsAllow = false; // default to discard unnecessary mouse events for iframe
  254.  
  255. let scriptEnable = false;
  256.  
  257. let comments_loader = 0; // for comment count (might omit)
  258.  
  259. let cmTime = 0;
  260. //let t_heated_BodyScroll = 0;
  261. const mTime = Date.now() - 152000000;
  262.  
  263. //let lastScrollFetch = 0;
  264. //let lastOffsetTop = 0;
  265. let mtf_forceCheckLiveVideo_disable = 0;
  266.  
  267. let tabsUiScript_setclick = false;
  268. let pageFetchedData = null;
  269. let pageType = null;
  270. let chatroomDetails = null;
  271. let switchTabActivity_lastTab = null
  272.  
  273. let lstTab = null;
  274.  
  275. let _cachedLastVideo = null;
  276. let videoListBeforeSearch = null;
  277.  
  278. let storeLastPanel = null;
  279.  
  280. let mtf_chatBlockQ = null; // for chat layout status change
  281.  
  282. let enableHoverSliderDetection = false; // for hover slider
  283.  
  284. let firstLoadStatus = 2|8; // for page init
  285.  
  286. let m_last_count = ''; // for comment count
  287.  
  288.  
  289. let sVideosITO = null;
  290.  
  291. /** @type {WeakRef | null} */
  292. //let displayedPlaylist = null;
  293.  
  294. /** @type {WeakRef | null} */
  295. let ytdFlexy = null;
  296.  
  297. const Q = {}
  298. const settings = {
  299. defaultTab: "#tab-videos"
  300. };
  301.  
  302. const STORE_VERSION = 1;
  303. const STORE_key = 'userscript-tabview-settings';
  304.  
  305. let fetchCounts = {
  306. base: null,
  307. new: null,
  308. fetched: false,
  309. count: null,
  310. }
  311.  
  312. let pageLang = 'en';
  313. const langWords = {
  314. 'en': {
  315. //'share':'Share',
  316. 'info': 'Info',
  317. 'videos': 'Videos',
  318. 'playlist': 'Playlist'
  319. },
  320. 'jp': {
  321. //'share':'共有',
  322. 'info': '情報',
  323. 'videos': '動画',
  324. 'playlist': '再生リスト'
  325. },
  326. 'tw': {
  327. //'share':'分享',
  328. 'info': '資訊',
  329. 'videos': '影片',
  330. 'playlist': '播放清單'
  331. },
  332. 'cn': {
  333. //'share':'分享',
  334. 'info': '资讯',
  335. 'videos': '视频',
  336. 'playlist': '播放列表'
  337. },
  338. 'du': {
  339. //'share':'Teilen',
  340. 'info': 'Info',
  341. 'videos': 'Videos',
  342. 'playlist': 'Playlist'
  343. },
  344. 'fr': {
  345. //'share':'Partager',
  346. 'info': 'Info',
  347. 'videos': 'Vidéos',
  348. 'playlist': 'Playlist'
  349. },
  350. 'kr': {
  351. //'share':'공유',
  352. 'info': '정보',
  353. 'videos': '동영상',
  354. 'playlist': '재생목록'
  355. }
  356. };
  357. const S_GENERAL_RENDERERS = ['YTD-TOGGLE-BUTTON-RENDERER', 'YTD-MENU-RENDERER']
  358.  
  359. let globalHook_symbols = [];
  360. let globalHook_hashs = {};
  361.  
  362. let singleColumnScrolling_dt = 0;
  363.  
  364. let isStickyHeaderEnabled = false;
  365. let theater_mode_changed_dt = 0;
  366. let detailsTriggerReset = false;
  367.  
  368. let isMakeHeaderFloatCalled = false;
  369.  
  370. /** @type {WeakSet<HTMLIFrameElement>} */
  371. let iframe_set = new WeakSet();
  372.  
  373.  
  374. /** @type {WeakMap<HTMLElement>} */
  375. let loadedCommentsDT = new WeakMap();
  376.  
  377.  
  378.  
  379.  
  380. const _console = new Proxy(console, {
  381. get(target, prop, receiver) {
  382. if (!DEBUG_LOG && prop === 'log') {
  383. return nullFunc
  384. }
  385. return Reflect.get(...arguments)
  386. }
  387. });
  388.  
  389. const isPassiveArgSupport = (typeof IntersectionObserver === 'function');
  390. // https://caniuse.com/?search=observer
  391. // https://caniuse.com/?search=addEventListener%20passive
  392.  
  393. const bubblePassive = isPassiveArgSupport ? { capture: false, passive: true } : false;
  394. const capturePassive = isPassiveArgSupport ? { capture: true, passive: true } : true;
  395.  
  396.  
  397. const querySelectorFromAnchor = HTMLElement.prototype.querySelector; // nodeType==1 // since 2022/07/12
  398. const querySelectorAllFromAnchor = HTMLElement.prototype.querySelectorAll; // nodeType==1 // since 2022/07/12
  399. const closestDOM = HTMLElement.prototype.closest;
  400. //const elementRemove = HTMLElement.prototype.remove;
  401. const elementInsertBefore = HTMLElement.prototype.insertBefore; // since 2022/07/12
  402. const elementContains = HTMLElement.prototype.contains; // since 2022/07/12
  403.  
  404.  
  405. const querySelectorFromAnchorFizzy = (root, id, selector) => {
  406. if (!root) return null;
  407. if (root.id === id && root.matches(selector)) return root;
  408. return querySelectorFromAnchor.call(root, selector) || null;
  409. }
  410.  
  411. // function maxUInt(s, d) {
  412. // let t = (d > s ? d : s);
  413. // return t > 0 ? t : 0;
  414. // }
  415.  
  416.  
  417. /* globals WeakRef:false */
  418.  
  419. /** @type {(o: Object | null) => WeakRef | null} */
  420. const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); // typeof InvalidVar == 'undefined'
  421.  
  422. /** @type {(wr: Object | null) => Object | null} */
  423. const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);
  424.  
  425.  
  426.  
  427.  
  428. const mgChatFrame = {
  429. setVar(elm) {
  430. mgChatFrame.kVar = mWeakRef(elm)
  431. },
  432. getVar() {
  433. return kRef(mgChatFrame.kVar)
  434. },
  435. inPage() {
  436. let elm = mgChatFrame.getVar();
  437. if (!elm) return false;
  438. let ytdFlexyElm = kRef(ytdFlexy);
  439. if (!ytdFlexyElm) return false;
  440. return elementContains.call(ytdFlexyElm, elm)
  441. }
  442. };
  443.  
  444. const timeline = {
  445. // after initialized (initObserver)
  446. cn1: {},
  447. cn2: {},
  448. setTimeout( /** @type {TimerHandler} */ f,/** @type {number} */ d) {
  449. let cid = setTimeout(f, d)
  450. timeline.cn1[cid] = true
  451. return cid;
  452. },
  453. clearTimeout(/** @type {number} */ cid) {
  454. timeline.cn1[cid] = false; return clearTimeout(cid)
  455. },
  456. setInterval(/** @type {TimerHandler} */ f,/** @type {number} */ d) {
  457. let cid = setInterval(f, d);
  458. timeline.cn2[cid] = true
  459. return cid;
  460. },
  461. clearInterval(/** @type {number} */ cid) {
  462. timeline.cn2[cid] = false; return clearInterval(cid)
  463. },
  464. reset() {
  465. for (let cid in timeline.cn1) timeline.cn1[cid] && clearTimeout(cid)
  466. for (let cid in timeline.cn2) timeline.cn2[cid] && clearInterval(cid)
  467. timeline.cn1 = {}
  468. timeline.cn2 = {}
  469. }
  470. }
  471.  
  472.  
  473. // function prettyElm(/** @type {Element} */ elm) {
  474. // if (!elm || !elm.nodeName) return null;
  475. // const eId = elm.id || null;
  476. // const eClsName = elm.className || null;
  477. // return [elm.nodeName.toLowerCase(), typeof eId == 'string' ? "#" + eId : '', typeof eClsName == 'string' ? '.' + eClsName.replace(/\s+/g, '.') : ''].join('').trim();
  478. // }
  479.  
  480. // function extractTextContent(/** @type {Node} */ elm) {
  481. // 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, '')
  482. // }
  483.  
  484. function addScript(/** @type {string} */ scriptText) {
  485. const scriptNode = document.createElement('script');
  486. scriptNode.type = 'text/javascript';
  487. scriptNode.textContent = scriptText;
  488. try {
  489. document.documentElement.appendChild(scriptNode);
  490. } catch (e) {
  491. console.log('addScript Error', e)
  492. }
  493. return scriptNode;
  494. }
  495.  
  496. function addScriptByURL(/** @type {string} */ scriptURL) {
  497. const scriptNode = document.createElement('script');
  498. scriptNode.type = 'text/javascript';
  499. scriptNode.src = scriptURL;
  500. try {
  501. document.documentElement.appendChild(scriptNode);
  502. } catch (e) {
  503. console.log('addScriptByURL Error', e)
  504. }
  505. return scriptNode;
  506. }
  507.  
  508. function addStyle(/** @type {string} */ styleText, /** @type {HTMLElement | Document} */ container) {
  509. const styleNode = document.createElement('style');
  510. //styleNode.type = 'text/css';
  511. styleNode.textContent = styleText;
  512. (container || document.documentElement).appendChild(styleNode);
  513. return styleNode;
  514. }
  515.  
  516.  
  517. // obsolete? keep it right now. to be removed.
  518. const stopIframePropagation = function (/** @type {Event} */ evt) {
  519. //if (iframePointEventsAllow) return;
  520. if (scriptEnable && ((evt || 0).target || 0).nodeName === 'IFRAME') {
  521. evt.stopImmediatePropagation();
  522. evt.stopPropagation();
  523. }
  524. }
  525.  
  526.  
  527.  
  528. /** @type {Map<string, Function>} */
  529. let handleDOMAppearFN = new Map();
  530. function handleDOMAppear( /** @type {string} */ fn, /** @type { listener: (this: Document, ev: AnimationEvent ) => any } */ func) {
  531.  
  532. if (handleDOMAppearFN.size === 0) {
  533. document.addEventListener('animationstart', (evt) => {
  534.  
  535. let func = handleDOMAppearFN.get(evt.animationName);
  536. if (func) func(evt);
  537. }, capturePassive)
  538. } else {
  539.  
  540. if (handleDOMAppearFN.has(fn)) return;
  541. }
  542.  
  543. handleDOMAppearFN.set(fn, func);
  544.  
  545. }
  546.  
  547. /*
  548.  
  549. yt-action yt-add-element-to-app yt-autonav-pause-blur yt-autonav-pause-focus
  550. yt-autonav-pause-guide-closed yt-autonav-pause-guide-opened yt-autonav-pause-player
  551. yt-autonav-pause-player-ended yt-autonav-pause-scroll yt-autoplay-on-changed
  552. yt-close-tou-form yt-consent-bump-display-changed yt-focus-searchbox
  553. yt-get-context-provider yt-guide-close yt-guide-hover yt-guide-toggle
  554. yt-history-load yt-history-pop yt-load-invalidation-continuation
  555. yt-load-next-continuation yt-load-reload-continuation yt-load-tou-form
  556. yt-masthead-height-changed yt-navigate yt-navigate-cache yt-navigate-error
  557. yt-navigate-finish yt-navigate-redirect yt-navigate-set-page-offset
  558. yt-navigate-start yt-next-continuation-data-updated yt-open-hotkey-dialog
  559. yt-open-tou-form-loading-state yt-page-data-fetched yt-page-data-updated
  560. yt-page-data-will-update yt-page-manager-navigate-start yt-page-navigate-start
  561. yt-page-type-changed yt-player-attached yt-player-detached yt-player-released
  562. yt-player-requested yt-player-updated yt-popup-canceled yt-popup-closed
  563. yt-popup-opened yt-preconnect-urls yt-register-action yt-report-form-closed
  564. yt-report-form-opened yt-request-panel-mode-change yt-retrieve-location
  565. yt-service-request-completed yt-service-request-error yt-service-request-sent
  566. yt-set-theater-mode-enabled yt-show-survey yt-subscription-changed
  567. yt-swatch-changed yt-theater-mode-allowed yt-unregister-action yt-update-title
  568. yt-update-unseen-notification-count yt-viewport-scanned yt-visibility-refresh
  569.  
  570. */
  571.  
  572.  
  573.  
  574. _console.log(38489)
  575.  
  576. class Session {
  577. constructor(initValue) {
  578. this.sid = initValue;
  579. }
  580. session() {
  581. let pageSession = this;
  582. let s = pageSession.sid; // inaccessible from external
  583. return {
  584. get isValid() {
  585. return s === pageSession.sid;
  586. }
  587. };
  588. }
  589. set(newValue) {
  590. this.sid = newValue;
  591. }
  592. inc() {
  593. this.sid++;
  594. }
  595. }
  596.  
  597. class Deferred {
  598. constructor() {
  599. this.reset();
  600. }
  601. debounce(f) {
  602. return Promise.race([this.promise]).then(f).catch(console.warn); // avoid promise.then.then.then ...
  603. }
  604. reset() {
  605. this.resolved = false;
  606. this.promise = new Promise((resolve, reject) => {
  607. //this.reject = reject
  608. this._resolve = resolve
  609. })
  610. }
  611. resolve() {
  612. this.resolved = true;
  613. this._resolve(...arguments);
  614. }
  615. }
  616.  
  617.  
  618. class Timeout {
  619.  
  620. set(f, d, repeatCount) {
  621. if (this.cid > 0) return;
  622. let sess = pageSession.session();
  623. let immediate = repeatCount > 0;
  624. let rc = immediate ? repeatCount : 1;
  625. const g = () => {
  626. this.cid = 0;
  627. if (sess.isValid) {
  628. if (f(rc--) === true && rc > 0) this.cid = setTimeout(g, d);
  629. }
  630. };
  631. immediate ? g() : (this.cid = setTimeout(g, d));
  632. }
  633.  
  634. clear() {
  635. if (this.cid > 0) {
  636. clearTimeout(this.cid);
  637. this.cid = 0;
  638. }
  639. }
  640.  
  641. get isEmpty() {
  642. return !this.cid
  643. }
  644.  
  645.  
  646. }
  647.  
  648. function timeout(f, d, repeatCount) {
  649. let res = new Timeout();
  650. res.set(f, d, repeatCount);
  651. return res;
  652. }
  653.  
  654.  
  655. class Mutex {
  656.  
  657. constructor() {
  658. this.p = Promise.resolve()
  659. }
  660.  
  661. lockWith(f) {
  662.  
  663. this.p = this.p.then(() => {
  664. return new Promise(f)
  665. }).catch(console.warn)
  666. }
  667.  
  668. }
  669.  
  670.  
  671. class AttributeMutationObserver extends MutationObserver {
  672. constructor(flist) {
  673. super((mutations, observer) => {
  674. for (const mutation of mutations) {
  675. if (mutation.type === 'attributes') {
  676. this.checker(mutation.target, mutation.attributeName)
  677. }
  678. }
  679. })
  680. this.flist = flist;
  681. this.res = {}
  682. }
  683.  
  684. takeRecords() {
  685. super.takeRecords();
  686. }
  687. disconnect() {
  688. this._target = null;
  689. super.disconnect();
  690. }
  691. observe(/** @type {Node} */ target) {
  692. if (this._target) return;
  693. //console.log(123124, target)
  694. this._target = mWeakRef(target);
  695.  
  696. //console.log(123125, kRef(this._target))
  697. const options = {
  698. attributes: true,
  699. attributeFilter: Object.keys(this.flist),
  700. //attributeFilter: [ "status", "username" ],
  701. attributeOldValue: true
  702. }
  703. super.observe(target, options)
  704. }
  705. checker(/** @type {Node} */ target,/** @type {string} */ attributeName) {
  706. let nv = target.getAttribute(attributeName);
  707. if (this.res[attributeName] !== nv) {
  708. this.res[attributeName] = nv
  709. let f = this.flist[attributeName];
  710. if (f) f(attributeName, nv);
  711.  
  712. }
  713. }
  714. check(delay = 0) {
  715. setTimeout(() => {
  716. let target = kRef(this._target)
  717. if (target !== null) {
  718. for (const key of Object.keys(this.flist)) {
  719. this.checker(target, key)
  720. }
  721. } else {
  722. console.log('target is null') //disconnected??
  723. }
  724. target = null;
  725. }, delay)
  726. }
  727. }
  728.  
  729. let pageSession = new Session(0);
  730. let tabsDeferred = new Deferred();
  731. tabsDeferred.resolve();
  732. let timeout_resize_for_layout_change = new Timeout();
  733.  
  734. let layoutStatusMutex = new Mutex();
  735. let sliderMutex = new Mutex();
  736. let renderDeferred = new Deferred(); //pageRendered
  737. let pageRendered = 0;
  738. let renderIdentifier = 0;
  739.  
  740.  
  741. function scriptInjector(script_id, url_chrome, response_id) {
  742.  
  743. let res = {
  744. script_id: script_id,
  745. inject: function () {
  746.  
  747. let res = this, script_id = this.script_id;
  748.  
  749. if (!document.querySelector(`script#${script_id}`)) {
  750. if (res.runtime_url) {
  751. addScriptByURL(res.runtime_url).id = script_id;
  752. } else {
  753. addScript(`${res.injection_script}`).id = script_id;
  754. }
  755. }
  756.  
  757. }
  758. }
  759. res.script_id = script_id;
  760.  
  761. if (isMyScriptInChromeRuntime()) {
  762. res.runtime_url = window.chrome.runtime.getURL(url_chrome)
  763. } else {
  764. res.injection_script = GM_getResourceText(response_id);
  765. }
  766.  
  767. return res;
  768.  
  769.  
  770.  
  771. }
  772.  
  773. const script_inject_js1 = scriptInjector(
  774. 'userscript-tabview-injection-1',
  775. 'js/injection_script_1.js',
  776. "injectionJS1");
  777.  
  778.  
  779.  
  780. function nonCryptoRandStr(/** @type {number} */ n) {
  781. const result = new Array(n);
  782. const baseStr = nonCryptoRandStr_base;
  783. const bLen = baseStr.length;
  784. for (let i = 0; i < n; i++) {
  785. let t = null
  786. do {
  787. t = baseStr.charAt(Math.floor(Math.random() * bLen));
  788. } while (i === 0 && 10 - t > 0)
  789. result[i] = t;
  790. }
  791. return result.join('');
  792. }
  793.  
  794. const uidMAP = new Map();
  795.  
  796. function uidGEN(s) {
  797. let uid = uidMAP.get(s);
  798. if (!uid) {
  799. const uidStore = ObserverRegister.uidStore;
  800. do {
  801. uid = nonCryptoRandStr(5);
  802. } while (uidStore[uid])
  803. uidMAP.set(s, uid);
  804. }
  805. return uid;
  806. }
  807.  
  808. /**
  809. * Class definition
  810. * @property {string} propName - propriety description
  811. * ...
  812. */
  813. class ObserverRegister {
  814.  
  815. constructor(/** @type {()=>MutationObserver | IntersectionObserver} */ observerCreator) {
  816. let uid = null;
  817. const uidStore = ObserverRegister.uidStore;
  818. do {
  819. uid = nonCryptoRandStr(5);
  820. } while (uidStore[uid])
  821. uidStore[uid] = true;
  822.  
  823. /**
  824. * uid is the unique string for each observer
  825. * @type {string}
  826. * @public
  827. */
  828. this.uid = uid;
  829.  
  830. /**
  831. * observerCreator is a function to create the observer
  832. * @type {Function}
  833. * @public
  834. */
  835. this.observerCreator = observerCreator
  836.  
  837. /**
  838. * observer is the actual observer object
  839. * @type {MutationObserver | IntersectionObserver}
  840. * @public
  841. */
  842. this.observer = null;
  843. }
  844. bindElement(/** @type {HTMLElement} */ elm, ...args) {
  845. if (elm.hasAttribute(`o3r-${this.uid}`)) return false;
  846. elm.setAttribute(`o3r-${this.uid}`, '')
  847. if (this.observer === null) {
  848. this.observer = this.observerCreator();
  849. }
  850. this.observer.observe(elm, ...args)
  851. return true
  852. }
  853. clear(/** @type {boolean} */ flag) {
  854. if (this.observer !== null) {
  855. //const uidStore = ObserverRegister.uidStore;
  856. if (flag === true) {
  857. this.observer.takeRecords();
  858. this.observer.disconnect();
  859. }
  860. this.observer = null;
  861. for (const s of document.querySelectorAll(`[o3r-${this.uid}]`)) s.removeAttribute(`o3r-${this.uid}`)
  862. //uidStore[this.uid]=false;
  863. //this.uid = null;
  864. }
  865. }
  866. }
  867.  
  868. /**
  869. * 'uidStore' is the static store of strings used.
  870. * @static
  871. */
  872. ObserverRegister.uidStore = {}; //backward compatible with FireFox 55.
  873.  
  874. const mtoObservationDetails = new ObserverRegister(() => {
  875. return new IntersectionObserver(iof_details, {
  876. root: null,
  877. rootMargin: "0px"
  878. })
  879. });
  880.  
  881. const mtoMutation_body = new ObserverRegister(() => {
  882. return new MutationObserver(FP.mtoBodyF)
  883. });
  884.  
  885. const mtoFlexyAttr = new ObserverRegister(() => {
  886. return new MutationObserver(mtf_attrFlexy)
  887. });
  888.  
  889. const mtoVisibility_EngagementPanel = new ObserverRegister(() => {
  890. return new MutationObserver(FP.mtf_attrEngagementPanel)
  891. });
  892. const sa_epanel = mtoVisibility_EngagementPanel.uid;
  893.  
  894. const mtoVisibility_Playlist = new ObserverRegister(() => {
  895. return new AttributeMutationObserver({
  896. "hidden": FP.mtf_attrPlaylist
  897. })
  898. })
  899. const sa_playlist = mtoVisibility_Playlist.uid;
  900.  
  901. const mtoVisibility_Comments = new ObserverRegister(() => {
  902. return new AttributeMutationObserver({
  903. "hidden": FP.mtf_attrComments
  904. })
  905. })
  906. const sa_comments = mtoVisibility_Comments.uid;
  907.  
  908.  
  909. const mtoVisibility_Chatroom = new ObserverRegister(() => {
  910. return new AttributeMutationObserver({
  911. "collapsed": FP.mtf_attrChatroom
  912. })
  913. })
  914. const sa_chatroom = mtoVisibility_Chatroom.uid;
  915.  
  916.  
  917.  
  918. function isDOMVisible(/** @type {HTMLElement} */ elem) {
  919. // jQuery version : https://github.com/jquery/jquery/blob/a684e6ba836f7c553968d7d026ed7941e1a612d8/src/css/hiddenVisibleSelectors.js
  920. return !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length);
  921. }
  922.  
  923. function isNonEmptyString(s) {
  924. return typeof s == 'string' && s.length > 0;
  925. }
  926.  
  927.  
  928. async function nativeCall(/** @type {EventTarget} */ dom, /** @type {any[]} */ detail) {
  929. //console.log(1231)
  930. dom.dispatchEvent(new CustomEvent("userscript-call-dom", { detail: detail }))
  931. //console.log(1232)
  932. }
  933.  
  934. async function nativeFunc(/** @type {EventTarget} */ dom, /** @type {string} */ property, /** @type {any} */ args) {
  935. dom.dispatchEvent(new CustomEvent("userscript-call-dom-func", { detail: { property, args } }))
  936. }
  937.  
  938. // async function nativeValue(dom, property, args) {
  939. // dom.dispatchEvent(new CustomEvent("userscript-call-dom-value", { detail: { property, args } }))
  940. // }
  941. // async function nativeFuncStacked(/** @type {string} */ selector, /** @type {string} */ property, /** @type {any} */ args){
  942. // document.dispatchEvent(new CustomEvent("userscript-call-dom-func-stacked", { detail: { selector, property, args } }))
  943. // }
  944. // async function nativeValueStacked(selector, property, args){
  945. // document.dispatchEvent(new CustomEvent("userscript-call-dom-value-stacked", { detail: { selector, property, args } }))
  946. // }
  947. // async function nativeConstStacked(selector, property, args){
  948. // document.dispatchEvent(new CustomEvent("userscript-call-dom-const-stacked", { detail: { selector, property, args } }))
  949. // }
  950.  
  951. async function dispatchWindowResize() {
  952. // for youtube to detect layout resize for adjusting Player tools
  953. return window.dispatchEvent(new Event('resize'));
  954. }
  955.  
  956. async function dispatchCommentRowResize(){
  957.  
  958. if(pageType!=="watch") return;
  959.  
  960. const ytdFlexyElm = kRef(ytdFlexy)
  961. if (!ytdFlexyElm) return;
  962. if (ytdFlexyElm.getAttribute('tyt-tab') !== '#tab-comments') return;
  963.  
  964. do {
  965. let b = !document.documentElement.getAttribute('tabview-unwrapjs');
  966. if (!b) break;
  967. await new Promise(r => setTimeout(r, 80));
  968. } while (true);
  969. document.dispatchEvent(new CustomEvent('tabview-resize-comments-rows'));
  970.  
  971. }
  972.  
  973. function setToggleBtnTxt() {
  974.  
  975. if (chatroomDetails) {
  976. _console.log(124234, 'c=== ')
  977.  
  978. let chat = document.querySelector('ytd-live-chat-frame#chat');
  979. if (!chat) return;
  980. let txt = querySelectorFromAnchor.call(chat, 'span.yt-core-attributed-string[role="text"]');
  981. let c = (txt || 0).textContent;
  982.  
  983. if (typeof c === 'string' && c.length > 2) {
  984. if (chat.hasAttribute('collapsed')) {
  985. _console.log(124234, 'collapsed show expand ', chatroomDetails.txt_expand)
  986. if (c !== chatroomDetails.txt_expand) {
  987. txt.textContent = chatroomDetails.txt_expand;
  988. }
  989. } else {
  990. _console.log(124234, 'not collapsed show collapse ', chatroomDetails.txt_collapse)
  991. if (c !== chatroomDetails.txt_collapse) {
  992. txt.textContent = chatroomDetails.txt_collapse;
  993. }
  994. }
  995. }
  996. }
  997. }
  998.  
  999. function handlerTabExpanderClick() {
  1000.  
  1001. async function b(){
  1002. let h1 = document.documentElement.clientHeight;
  1003. let h2 = (document.querySelector('#right-tabs')||0).clientHeight;
  1004.  
  1005. await Promise.resolve(0);
  1006. if(h1>300 && h2>300){
  1007. let ratio = h2/h1; // positive below 1.0
  1008.  
  1009. return ratio;
  1010. }
  1011. return 0;
  1012. }
  1013.  
  1014. async function a(){
  1015.  
  1016.  
  1017. let secondary = document.querySelector('#secondary.ytd-watch-flexy');
  1018. if (secondary) {
  1019.  
  1020. if (!secondary.classList.contains('tabview-hover-slider-enable')) {
  1021.  
  1022. let secondaryInner = querySelectorFromAnchor.call(secondary, '#secondary-inner.ytd-watch-flexy');
  1023.  
  1024. if(secondaryInner){
  1025.  
  1026. if (!secondary.classList.contains('tabview-hover-slider')) {
  1027. // without hover
  1028.  
  1029. let rect = secondary.getBoundingClientRect();
  1030. let rectI = secondaryInner.getBoundingClientRect();
  1031.  
  1032. secondaryInner.style.setProperty('--tabview-slider-right', `${rect.right - rectI.right}px`)
  1033.  
  1034. }
  1035. let ratio = await b();
  1036. if(ratio > 0.0 && ratio <= 1.0){
  1037.  
  1038. secondaryInner.style.setProperty('--ytd-watch-flexy-sidebar-width-d', `${ Math.round(100*ratio*10)/10 }vw`);
  1039. secondary.classList.add('tabview-hover-slider')
  1040. secondary.classList.add('tabview-hover-slider-enable')
  1041.  
  1042.  
  1043. let video = document.querySelector('#player video')
  1044. if(video && typeof video.requestPictureInPicture ==='function' && isVideoPlaying(video)){
  1045. video.requestPictureInPicture().then(res=>{
  1046.  
  1047. }).catch(console.warn)
  1048. }
  1049.  
  1050. }
  1051.  
  1052. }
  1053.  
  1054.  
  1055.  
  1056.  
  1057. } else {
  1058.  
  1059.  
  1060. secondary.dispatchEvent(new CustomEvent("tabview-hover-slider-restore"));
  1061. //console.log(1994)
  1062.  
  1063. }
  1064. // no animation event triggered for hover -> enable
  1065. dispatchCommentRowResize();
  1066.  
  1067. }
  1068.  
  1069.  
  1070. }
  1071.  
  1072.  
  1073. a();
  1074.  
  1075.  
  1076. }
  1077.  
  1078. function setupHoverSlider(secondary, columns){
  1079.  
  1080. if (secondary && columns) {
  1081. let attrName = `o4r-${uidGEN('tabview-hover-slider-restore')}`;
  1082.  
  1083. if (!secondary.hasAttribute(attrName)) {
  1084.  
  1085. secondary.setAttribute(attrName, '');
  1086.  
  1087. let elmA = document.createElement('tabview-column-pos')
  1088. let itoA = new IntersectionObserver((entries) => {
  1089. let t = null;
  1090. let w = enableHoverSliderDetection
  1091. for (const entry of entries) {
  1092. if (entry.rootBounds === null) continue;
  1093. t = !entry.isIntersecting; // if entries.length>1 (unlikely); take the last intersecting
  1094. }
  1095. if (w !== t && t !== null) {
  1096. // t can be true when the layout enters single column mode
  1097. enableHoverSliderDetection = t;
  1098. }
  1099. //console.log(entries, enableHoverSliderDetection, t)
  1100. })
  1101. columns.appendChild(elmA); // append to dom first before observe
  1102. itoA.observe(elmA);
  1103.  
  1104.  
  1105. let _tabviewSiderAnimated = false;
  1106. handleDOMAppear('tabviewSiderAnimation', (evt)=>{
  1107.  
  1108. if(!_tabviewSiderAnimated){
  1109. _tabviewSiderAnimated = true;
  1110. dispatchCommentRowResize();
  1111.  
  1112. }
  1113.  
  1114. })
  1115.  
  1116. handleDOMAppear('tabviewSiderAnimationNone', (evt)=>{
  1117.  
  1118. if(_tabviewSiderAnimated){
  1119. _tabviewSiderAnimated=false;
  1120. dispatchCommentRowResize();
  1121. }
  1122.  
  1123.  
  1124. })
  1125.  
  1126.  
  1127. secondary.addEventListener('tabview-hover-slider-restore', function (evt) {
  1128.  
  1129. let secondary = evt.target;
  1130.  
  1131. if (secondary.classList.contains('tabview-hover-slider-enable')) {
  1132.  
  1133. function exitPIP(){
  1134. if(document.pictureInPictureElement !== null && typeof document.exitPictureInPicture =='function'){
  1135. document.exitPictureInPicture().then(res=>{
  1136.  
  1137. }).catch(console.warn)
  1138. }
  1139. }
  1140.  
  1141. let secondaryInner = querySelectorFromAnchor.call(secondary,'#secondary-inner.ytd-watch-flexy')
  1142.  
  1143. if (secondary.classList.contains('tabview-hover-slider-hover')) {
  1144.  
  1145. if (secondaryInner) {
  1146.  
  1147. Promise.resolve(0).then(() => {
  1148. secondaryInner.style.removeProperty('--ytd-watch-flexy-sidebar-width-d');
  1149. }).then(() => {
  1150. secondary.classList.remove('tabview-hover-slider-enable')
  1151.  
  1152. exitPIP();
  1153.  
  1154. // triggered by tabviewSiderAnimationNone event
  1155. // document.dispatchEvent(new CustomEvent('tabview-resize-comments-rows'));
  1156.  
  1157. })
  1158.  
  1159. }
  1160.  
  1161. } else {
  1162.  
  1163. let secondary = evt.target;
  1164. secondary.classList.remove('tabview-hover-slider')
  1165. secondary.classList.remove('tabview-hover-slider-enable')
  1166.  
  1167. if (secondaryInner) {
  1168. secondaryInner.style.removeProperty('--ytd-watch-flexy-sidebar-width-d');
  1169. secondaryInner.style.removeProperty('--tabview-slider-right')
  1170. }
  1171.  
  1172.  
  1173. exitPIP();
  1174.  
  1175. // triggered by tabviewSiderAnimationNone event
  1176. // document.dispatchEvent(new CustomEvent('tabview-resize-comments-rows'));
  1177.  
  1178. }
  1179.  
  1180. setTimeout(() => {
  1181. updateFloatingSlider()
  1182. }, 30);
  1183.  
  1184.  
  1185. }
  1186.  
  1187.  
  1188. }, false)
  1189.  
  1190. }
  1191.  
  1192. }
  1193.  
  1194. }
  1195. function addTabExpander(tabContent){
  1196.  
  1197. if (!tabContent) return null;
  1198. let id = tabContent.id;
  1199. if (!id || typeof id !== 'string') return null;
  1200.  
  1201. if (querySelectorFromAnchor.call(tabContent, `#${id}>tabview-tab-expander`)) return false;
  1202.  
  1203. let elm = document.createElement('tabview-tab-expander')
  1204. tabContent.insertBefore(elm, tabContent.firstChild);
  1205. elm.innerHTML = `<div>${svgElm(16, 16, 12, 12, svgDiag1, 'svg-expand')}${svgElm(16, 16, 12, 12, svgDiag2, 'svg-collapse')}</div>`
  1206. elm.addEventListener('click', handlerTabExpanderClick, false);
  1207. return true;
  1208.  
  1209. }
  1210.  
  1211. async function updateFloatingSlider_A(secondaryInner) {
  1212.  
  1213. // [is-extra-wide-video_]
  1214.  
  1215. let secondary = secondaryInner.parentNode;
  1216. if (!secondary) return;
  1217.  
  1218.  
  1219. if (secondary.classList.contains('tabview-hover-slider-enable')) {
  1220. return;
  1221. }
  1222.  
  1223.  
  1224. await new Promise(r => setTimeout(r), 30); // time allowed for dom changes and value change of enableHoverSliderDetection
  1225. let bool = enableHoverSliderDetection;
  1226.  
  1227. let previousHasClass = secondary.classList.contains('tabview-hover-slider-hover');
  1228.  
  1229. if (bool || previousHasClass) {
  1230. }else{
  1231. return;
  1232. }
  1233.  
  1234. let columns = secondary.parentNode;
  1235. if (!columns) return;
  1236.  
  1237.  
  1238. let primary = querySelectorAllFromAnchor.call(columns, '#primary.ytd-watch-flexy');
  1239. if (!primary) return;
  1240.  
  1241.  
  1242. let res = await Promise.all([
  1243. new Promise(f => f(columns.getBoundingClientRect())),
  1244. new Promise(f => f(document.documentElement.getBoundingClientRect().width)),
  1245. new Promise(f => f(secondary.getBoundingClientRect())),
  1246. new Promise(f => f(secondaryInner.getBoundingClientRect()))
  1247. ]);
  1248.  
  1249. const [rectC, screenWidth, rect, rectI] = res;
  1250.  
  1251.  
  1252.  
  1253.  
  1254. const setOffset = () => {
  1255.  
  1256. let offset = rectC.width - screenWidth;
  1257.  
  1258. let k = 1.0
  1259. if (offset >= 125) {
  1260. k = 1.0
  1261. } else if (offset >= 75) {
  1262. k = 1.0;
  1263. } else if (offset >= 25) {
  1264. k = 0.25;
  1265. } else {
  1266. k = 0.0
  1267. }
  1268. secondaryInner.style.setProperty('--tabview-slider-offset-k2', `${k}`);
  1269. secondaryInner.style.setProperty('--tabview-slider-offset', `${offset}px`)
  1270.  
  1271. }
  1272.  
  1273.  
  1274.  
  1275.  
  1276. await Promise.resolve(0);
  1277.  
  1278. secondary.classList.add('tabview-hover-final')
  1279.  
  1280. if (previousHasClass && !bool) {
  1281. secondaryInner.style.removeProperty('--tabview-slider-right')
  1282. secondaryInner.style.removeProperty('--tabview-slider-offset')
  1283. } else {
  1284.  
  1285. if (!previousHasClass) { //secondary.classList.contains('tabview-hover-slider');
  1286. secondaryInner.style.setProperty('--tabview-slider-right', `${rect.right - rectI.right}px`)
  1287. }
  1288.  
  1289. setOffset();
  1290. }
  1291.  
  1292. if (bool && previousHasClass) { }
  1293. else if (!bool && !previousHasClass) { }
  1294. else {
  1295. secondary.classList.toggle('tabview-hover-slider', bool)
  1296. secondary.classList.toggle('tabview-hover-slider-hover', bool)
  1297. }
  1298.  
  1299. await Promise.resolve(0);
  1300.  
  1301.  
  1302. setTimeout(() => {
  1303. secondary.classList.remove('tabview-hover-final')
  1304. }, 350)
  1305.  
  1306.  
  1307. }
  1308.  
  1309.  
  1310. function updateFloatingSlider(){
  1311.  
  1312. let secondaryInner = document.querySelector('ytd-watch-flexy[flexy][is-two-columns_] #secondary-inner.ytd-watch-flexy')
  1313.  
  1314. if(!secondaryInner) return;
  1315. let secondary = secondaryInner.parentNode;
  1316. if(!secondary) return;
  1317.  
  1318. if(secondary.classList.contains('tabview-hover-slider-enable')){
  1319. return;
  1320. }
  1321.  
  1322. let t = document.documentElement.clientWidth; //integer
  1323.  
  1324. sliderMutex.lockWith(unlock=>{
  1325.  
  1326. let v = document.documentElement.clientWidth; //integer
  1327.  
  1328. if(t===v && secondaryInner.matches('body ytd-watch-flexy[flexy][is-two-columns_] #secondary-inner.ytd-watch-flexy')){
  1329. updateFloatingSlider_A(secondaryInner ).then(unlock);
  1330. }else{
  1331. unlock();
  1332. }
  1333.  
  1334. })
  1335.  
  1336. }
  1337.  
  1338.  
  1339. function setToActiveTab(defaultTab) {
  1340. if (isTheater() && isWideScreenWithTwoColumns()) return;
  1341. const jElm = document.querySelector(`a[userscript-tab-content="${switchTabActivity_lastTab}"]:not(.tab-btn-hidden)`) ||
  1342. document.querySelector(`a[userscript-tab-content="${(defaultTab || settings.defaultTab)}"]:not(.tab-btn-hidden)`) ||
  1343. document.querySelector("a[userscript-tab-content]:not(.tab-btn-hidden)") ||
  1344. null;
  1345.  
  1346. switchTabActivity(jElm);
  1347. return !!jElm;
  1348. }
  1349.  
  1350. function layoutStatusChanged(/** @type {number} */ old_layoutStatus, /** @type {number} */ new_layoutStatus) {
  1351.  
  1352. if((new_layoutStatus & LAYOUT_TWO_COLUMNS) === 0 ) makeHeaderFloat();
  1353.  
  1354. //if (old_layoutStatus === new_layoutStatus) return;
  1355.  
  1356. const cssElm = kRef(ytdFlexy);
  1357.  
  1358. if (!cssElm) return;
  1359.  
  1360.  
  1361. const BF_TWOCOL_N_THEATER = LAYOUT_TWO_COLUMNS | LAYOUT_THEATER
  1362.  
  1363. let new_isExpandedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_EXPANDED)
  1364. let new_isCollapsedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_COLLAPSED) && !!(new_layoutStatus & LAYOUT_CHATROOM)
  1365.  
  1366. let new_isTabExpanded = !!(new_layoutStatus & LAYOUT_TAB_EXPANDED);
  1367. let new_isFullScreen = !!(new_layoutStatus & LAYOUT_FULLSCREEN);
  1368. let new_isExpandEPanel = !!(new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPAND);
  1369.  
  1370.  
  1371.  
  1372.  
  1373. function showTabOrChat() {
  1374.  
  1375. layoutStatusMutex.lockWith(unlock => {
  1376.  
  1377. if (lstTab.lastPanel == '#chatroom') {
  1378.  
  1379. if (new_isTabExpanded) switchTabActivity(null)
  1380. if (!new_isExpandedChat) ytBtnExpandChat();
  1381.  
  1382. } else if (lstTab.lastPanel && lstTab.lastPanel.indexOf('#engagement-panel-') == 0) {
  1383.  
  1384. if (new_isTabExpanded) switchTabActivity(null)
  1385. if (!new_isExpandEPanel) ytBtnOpenEngagementPanel(lstTab.lastPanel);
  1386.  
  1387. } else {
  1388.  
  1389. if (new_isExpandedChat) ytBtnCollapseChat()
  1390. if (!new_isTabExpanded) { setToActiveTab(); }
  1391.  
  1392. }
  1393.  
  1394. timeline.setTimeout(unlock, 40);
  1395.  
  1396. })
  1397. }
  1398.  
  1399. function hideTabAndChat() {
  1400.  
  1401. layoutStatusMutex.lockWith(unlock => {
  1402.  
  1403. if (new_isTabExpanded) switchTabActivity(null)
  1404. if (new_isExpandedChat) ytBtnCollapseChat()
  1405. if (new_isExpandEPanel) ytBtnCloseEngagementPanels();
  1406.  
  1407.  
  1408. timeline.setTimeout(unlock, 40);
  1409.  
  1410. })
  1411.  
  1412. }
  1413.  
  1414. const statusCollapsedFalse = !!(new_layoutStatus & (LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPAND | LAYOUT_CHATROOM_EXPANDED))
  1415. const statusCollapsedTrue = !statusCollapsedFalse
  1416.  
  1417.  
  1418. let changes = (old_layoutStatus & LAYOUT_VAILD) ? old_layoutStatus ^ new_layoutStatus : 0;
  1419.  
  1420. let chat_collapsed_changed = !!(changes & LAYOUT_CHATROOM_COLLAPSED)
  1421. let chat_expanded_changed = !!(changes & LAYOUT_CHATROOM_EXPANDED)
  1422. let tab_expanded_changed = !!(changes & LAYOUT_TAB_EXPANDED)
  1423. let theater_mode_changed = !!(changes & LAYOUT_THEATER)
  1424. let column_mode_changed = !!(changes & LAYOUT_TWO_COLUMNS)
  1425. let fullscreen_mode_changed = !!(changes & LAYOUT_FULLSCREEN)
  1426. let epanel_expanded_changed = !!(changes & LAYOUT_ENGAGEMENT_PANEL_EXPAND)
  1427.  
  1428. _console.log(8221, 1, chat_collapsed_changed,chat_expanded_changed,tab_expanded_changed,theater_mode_changed,column_mode_changed,fullscreen_mode_changed,epanel_expanded_changed )
  1429.  
  1430.  
  1431. //console.log(169, 1, chat_collapsed_changed, tab_expanded_changed)
  1432. //console.log(169, 2, new_isExpandedChat, new_isCollapsedChat, new_isTabExpanded)
  1433.  
  1434. let BF_LayoutCh_Panel = (changes & (LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPAND))
  1435. let tab_change = BF_LayoutCh_Panel;
  1436. let isChatOrTabExpandTriggering = !!((new_layoutStatus) & BF_LayoutCh_Panel);
  1437. let isChatOrTabCollaspeTriggering = !!((~new_layoutStatus) & BF_LayoutCh_Panel);
  1438.  
  1439.  
  1440.  
  1441. let moreThanOneShown = (new_isTabExpanded + new_isExpandedChat + new_isExpandEPanel) > 1
  1442.  
  1443. let requestVideoResize = false;
  1444.  
  1445. // two column; not theater; tab collapse; chat expand; ep expand
  1446. const IF_01a = LAYOUT_TWO_COLUMNS | LAYOUT_THEATER | LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLAPSED | LAYOUT_ENGAGEMENT_PANEL_EXPAND;
  1447. const IF_01b = LAYOUT_TWO_COLUMNS | 0 | 0 | LAYOUT_CHATROOM | 0 | LAYOUT_ENGAGEMENT_PANEL_EXPAND;
  1448.  
  1449.  
  1450. // two column; not theater;
  1451. const IF_02a = BF_TWOCOL_N_THEATER;
  1452. const IF_02b = LAYOUT_TWO_COLUMNS;
  1453.  
  1454. // two column; not theater; tab expand; chat expand;
  1455. const IF_03a = LAYOUT_TWO_COLUMNS | LAYOUT_THEATER | LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLAPSED;
  1456. const IF_03b = LAYOUT_TWO_COLUMNS | 0 | LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM | 0;
  1457.  
  1458.  
  1459. // two column; tab expand; chat expand;
  1460. const IF_06a = LAYOUT_TWO_COLUMNS | LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLAPSED;
  1461. const IF_06b = LAYOUT_TWO_COLUMNS | LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM | 0;
  1462.  
  1463.  
  1464. // two column; theater;
  1465. const IF_04a = BF_TWOCOL_N_THEATER;
  1466. const IF_04b = BF_TWOCOL_N_THEATER;
  1467.  
  1468. // not fullscreen; two column; not theater; not tab expand; not EP expand; not expand chat
  1469. const IF_05a = LAYOUT_FULLSCREEN | LAYOUT_TWO_COLUMNS | LAYOUT_THEATER | LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPAND | LAYOUT_CHATROOM_EXPANDED;
  1470. const IF_05b = 0 | LAYOUT_TWO_COLUMNS | 0 | 0 | 0 | 0;
  1471.  
  1472.  
  1473.  
  1474. if (new_isFullScreen) {
  1475.  
  1476.  
  1477. if (tab_change == LAYOUT_CHATROOM_EXPANDED && (new_layoutStatus & IF_06a) === IF_06b && statusCollapsedFalse && !column_mode_changed) {
  1478.  
  1479. // two column; tab expand; chat expand;
  1480.  
  1481. switchTabActivity(null);
  1482.  
  1483.  
  1484.  
  1485. }
  1486.  
  1487. if (!!(tab_change & LAYOUT_CHATROOM_EXPANDED) && new_isExpandedChat) {
  1488. //tab_change = LAYOUT_CHATROOM_EXPANDED
  1489. //tab_change = LAYOUT_CHATROOM_EXPANDED|LAYOUT_TAB_EXPANDED
  1490.  
  1491. timeline.setTimeout(() => {
  1492. let scrollElement = document.querySelector('ytd-app[scrolling]')
  1493. if (!scrollElement) return;
  1494. // single column view; click button; scroll to tab content area 100%
  1495. let chatFrame = document.querySelector('ytd-live-chat-frame#chat');
  1496. if (chatFrame && isChatExpand()) {
  1497. _console.log(7290, 1)
  1498. chatFrame.scrollIntoView(true);
  1499. }
  1500. }, 60)
  1501.  
  1502. }
  1503.  
  1504.  
  1505.  
  1506.  
  1507.  
  1508. if (!!(tab_change & LAYOUT_ENGAGEMENT_PANEL_EXPAND) && new_isExpandEPanel) {
  1509.  
  1510. timeline.setTimeout(() => {
  1511. let scrollElement = document.querySelector('ytd-app[scrolling]')
  1512. if (!scrollElement) return;
  1513. // single column view; click button; scroll to tab content area 100%
  1514. let epPanel = document.querySelector('ytd-engagement-panel-section-list-renderer[visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]');
  1515. if (epPanel) {
  1516. _console.log(7290, 2)
  1517.  
  1518. let pi = 50;
  1519. let cid = setInterval(() => {
  1520. if (--pi) epPanel.scrollIntoView(true); else clearInterval(cid)
  1521. }, 17)
  1522. //
  1523. }
  1524. }, 60)
  1525.  
  1526. }
  1527.  
  1528.  
  1529.  
  1530. } else if (fullscreen_mode_changed) {
  1531.  
  1532.  
  1533.  
  1534.  
  1535. if (!new_isFullScreen && statusCollapsedTrue && isWideScreenWithTwoColumns() && !isTheater()) {
  1536. showTabOrChat();
  1537. requestVideoResize = true;
  1538. } else if (!new_isFullScreen && statusCollapsedFalse && isWideScreenWithTwoColumns() && isTheater()) {
  1539. ytBtnCancelTheater();
  1540. requestVideoResize = true;
  1541. }
  1542.  
  1543. }/*else if ( !fullscreen_mode_changed && !epanel_expanded_changed && !column_mode_changed && !theater_mode_changed && (chat_collapsed_changed||tab_expanded_changed) && !new_isExpandedChat && !new_isTabExpanded && new_isCollapsedChat ){
  1544.  
  1545. // switch between live play video and replay video
  1546. // force chat room display
  1547.  
  1548. //if(lstTab){
  1549. //if(lstTab.lastPanel!='#chatroom')
  1550. //lstTab.lastPanel = '#chatroom';
  1551. showTabOrChat();
  1552.  
  1553. //}
  1554.  
  1555. }*/ else if ((new_layoutStatus & IF_01a) === IF_01b && !column_mode_changed && (tab_change == LAYOUT_CHATROOM_EXPANDED || tab_change == LAYOUT_ENGAGEMENT_PANEL_EXPAND)) {
  1556.  
  1557. // two column; not theater; tab collapse; chat expand; ep expand
  1558.  
  1559.  
  1560.  
  1561. if (epanel_expanded_changed) {
  1562. layoutStatusMutex.lockWith(unlock => {
  1563. ytBtnCollapseChat();
  1564. setTimeout(unlock, 13)
  1565. })
  1566. } else if (chat_collapsed_changed) {
  1567. layoutStatusMutex.lockWith(unlock => {
  1568. ytBtnCloseEngagementPanels();
  1569. setTimeout(unlock, 13)
  1570. })
  1571.  
  1572. }
  1573.  
  1574. } else if (!tab_change && column_mode_changed && (new_layoutStatus & IF_02a) === IF_02b && moreThanOneShown) {
  1575.  
  1576. // two column; not theater;
  1577. // moreThanOneShown
  1578.  
  1579. showTabOrChat();
  1580. requestVideoResize = true;
  1581.  
  1582. } else if (tab_change == LAYOUT_CHATROOM_EXPANDED && (new_layoutStatus & IF_03a) === IF_03b && statusCollapsedFalse && !column_mode_changed) {
  1583.  
  1584. // two column; not theater; tab expand; chat expand;
  1585.  
  1586. switchTabActivity(null);
  1587. requestVideoResize = true;
  1588.  
  1589. } else if (isChatOrTabExpandTriggering && (new_layoutStatus & IF_04a) === IF_04b && statusCollapsedFalse && (changes & BF_TWOCOL_N_THEATER) === 0) {
  1590.  
  1591. ytBtnCancelTheater();
  1592. requestVideoResize = true;
  1593.  
  1594. } else if ((new_layoutStatus & IF_04a) === IF_04b && statusCollapsedFalse) {
  1595.  
  1596. hideTabAndChat();
  1597. requestVideoResize = true;
  1598.  
  1599. } else if (isChatOrTabCollaspeTriggering && (new_layoutStatus & IF_02a) === IF_02b && statusCollapsedTrue && !column_mode_changed) {
  1600.  
  1601. if (tab_change == LAYOUT_ENGAGEMENT_PANEL_EXPAND) {
  1602.  
  1603. lstTab.lastPanel = null;
  1604.  
  1605. if (new_isFullScreen) {
  1606.  
  1607. } else {
  1608. showTabOrChat();
  1609. }
  1610. } else if (tab_change == LAYOUT_CHATROOM_EXPANDED) {
  1611.  
  1612. lstTab.lastPanel = null;
  1613.  
  1614. if (new_isFullScreen) {
  1615.  
  1616. } else {
  1617. showTabOrChat();
  1618. }
  1619. } else {
  1620.  
  1621.  
  1622. if (new_isFullScreen) {
  1623.  
  1624. } else {
  1625.  
  1626. ytBtnSetTheater();
  1627.  
  1628. }
  1629.  
  1630. }
  1631.  
  1632. requestVideoResize = true;
  1633.  
  1634. } else if (!tab_change && !!(changes & BF_TWOCOL_N_THEATER) && (new_layoutStatus & IF_02a) === IF_02b && statusCollapsedTrue) {
  1635.  
  1636. showTabOrChat();
  1637. requestVideoResize = true;
  1638.  
  1639. } else if ((new_layoutStatus & IF_05a) === IF_05b) {
  1640. // bug fix for restoring from mini player
  1641.  
  1642. layoutStatusMutex.lockWith(unlock => {
  1643. setToActiveTab();
  1644. timeline.setTimeout(unlock, 40);
  1645. })
  1646.  
  1647. requestVideoResize = true;
  1648.  
  1649. } else if (tab_expanded_changed) {
  1650.  
  1651. requestVideoResize = true;
  1652.  
  1653. }
  1654.  
  1655. if (requestVideoResize) {
  1656. timeout_resize_for_layout_change.clear();
  1657. timeout_resize_for_layout_change.set(() => {
  1658. //dispatchWindowResize(); //try to omit
  1659. }, 92)
  1660. }
  1661.  
  1662. if(theater_mode_changed){
  1663. let tdt =Date.now();
  1664. theater_mode_changed_dt = tdt
  1665. setTimeout(()=>{
  1666. if(theater_mode_changed_dt!==tdt)return;
  1667. updateFloatingSlider();
  1668. },130)
  1669. }
  1670.  
  1671. let secondary = null;
  1672. if(secondary=document.querySelector('.tabview-hover-slider-enable')){
  1673. secondary.dispatchEvent(new CustomEvent('tabview-hover-slider-restore'))
  1674. //console.log(1996)
  1675. }
  1676.  
  1677. if (((old_layoutStatus ^ new_layoutStatus) & LAYOUT_FULLSCREEN) === LAYOUT_FULLSCREEN) {
  1678. detailsTriggerReset = true;
  1679. }
  1680.  
  1681. // resize => is-two-columns_
  1682. if (((new_layoutStatus ^ old_layoutStatus) & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) {
  1683. Promise.resolve(0).then(()=>{
  1684. pageCheck();
  1685. requestAnimationFrame(() => {
  1686. singleColumnScrolling(true); //initalize sticky
  1687. });
  1688. })
  1689. }
  1690. /*
  1691. if(fullscreen_mode_changed && new_isFullScreen){
  1692. requestAnimationFrame(()=>{
  1693. try{
  1694. document.querySelector('#player-theater-container').focus();
  1695. }catch(e){}
  1696. })
  1697. }*/
  1698.  
  1699. }
  1700.  
  1701. function fixLayoutStatus(x) {
  1702. const new_isExpandedChat = !(x & LAYOUT_CHATROOM_COLLAPSED) && (x & LAYOUT_CHATROOM)
  1703. return new_isExpandedChat ? (x | LAYOUT_CHATROOM_EXPANDED) : (x & ~LAYOUT_CHATROOM_EXPANDED);
  1704. }
  1705.  
  1706. const wls = new Proxy({
  1707. /** @type {number | null} */
  1708. layoutStatus: undefined
  1709. }, {
  1710. get: function (target, prop) {
  1711. return target[prop];
  1712. },
  1713. set: function (target, prop, value) {
  1714. if (prop == 'layoutStatus') {
  1715.  
  1716. if (value === 0) {
  1717. target[prop] = value;
  1718. return true;
  1719. } else if (target[prop] === value) {
  1720. return true;
  1721. } else {
  1722. if (!target.layoutStatus_pending) {
  1723. target.layoutStatus_pending = true;
  1724. const old_layoutStatus = target[prop];
  1725. target[prop] = value;
  1726. layoutStatusMutex.lockWith(unlock => {
  1727. target.layoutStatus_pending = false;
  1728. let new_layoutStatus = target[prop];
  1729. if (old_layoutStatus !== new_layoutStatus) {
  1730. layoutStatusChanged(old_layoutStatus, new_layoutStatus);
  1731. timeline.setTimeout(unlock, 40)
  1732. } else {
  1733. unlock();
  1734. }
  1735. })
  1736. return true;
  1737. }
  1738. }
  1739. }
  1740. target[prop] = value;
  1741. return true;
  1742. },
  1743. has: function (target, prop) {
  1744. return (prop in target);
  1745. }
  1746. });
  1747.  
  1748. const svgElm = (w, h, vw, vh, p, m) => `<svg${m?` class=${m}`:''} width="${w}" height="${h}" viewBox="0 0 ${vw} ${vh}" preserveAspectRatio="xMidYMid meet">${p}</svg>`
  1749.  
  1750. function isVideoPlaying(video) {
  1751. return video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA;
  1752. }
  1753.  
  1754. function wAttr(elm, attr, kv) {
  1755. if (elm) {
  1756. if (kv === true) {
  1757. elm.setAttribute(attr, '');
  1758. } else if (kv === false) {
  1759. elm.removeAttribute(attr);
  1760. } else if (kv === null) {
  1761. //;
  1762. } else if (typeof kv == 'string') {
  1763. elm.setAttribute(attr, kv);
  1764. }
  1765. }
  1766. }
  1767.  
  1768. function hideTabBtn(tabBtn) {
  1769. //console.log('hideTabBtn', tabBtn)
  1770. let isActiveBefore = tabBtn.classList.contains('active');
  1771. tabBtn.classList.add("tab-btn-hidden");
  1772. if (isActiveBefore) {
  1773. setToActiveTab();
  1774. }
  1775. }
  1776.  
  1777. function hasAttribute(obj, key) {
  1778. return obj && obj.hasAttribute(key);
  1779. }
  1780.  
  1781. function isTheater() {
  1782. const cssElm = kRef(ytdFlexy);
  1783. return (cssElm && cssElm.hasAttribute('theater'))
  1784. }
  1785.  
  1786. function isFullScreen() {
  1787. const cssElm = kRef(ytdFlexy);
  1788. return (cssElm && cssElm.hasAttribute('fullscreen'))
  1789. }
  1790.  
  1791. function isChatExpand() {
  1792. const cssElm = kRef(ytdFlexy);
  1793. return cssElm && (cssElm.getAttribute('tyt-chat')||'').charAt(0)==='+'
  1794. }
  1795.  
  1796. function isWideScreenWithTwoColumns() {
  1797. const cssElm = kRef(ytdFlexy);
  1798. return (cssElm && cssElm.hasAttribute('is-two-columns_'))
  1799. }
  1800.  
  1801. function isAnyActiveTab() {
  1802. return $('#right-tabs .tab-btn.active').length > 0
  1803. }
  1804.  
  1805. function isEngagementPanelExpanded() { //note: not checking the visual elements
  1806. const cssElm = kRef(ytdFlexy);
  1807. return (cssElm && +cssElm.getAttribute('tyt-ep-visible') > 0)
  1808. }
  1809.  
  1810. function engagement_panels_() {
  1811.  
  1812. let res = [];
  1813. let shownRes = [];
  1814.  
  1815. let v = 0,
  1816. k = 1,
  1817. count = 0;
  1818.  
  1819. for (const ePanel of document.querySelectorAll(
  1820. `ytd-watch-flexy ytd-engagement-panel-section-list-renderer[o3r-${sa_epanel}]`
  1821. )) {
  1822.  
  1823. let visibility = ePanel.getAttribute('visibility') //ENGAGEMENT_PANEL_VISIBILITY_EXPANDED //ENGAGEMENT_PANEL_VISIBILITY_HIDDEN
  1824.  
  1825. switch (visibility) {
  1826. case 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED':
  1827. v |= k;
  1828. count++;
  1829. shownRes.push(ePanel)
  1830. res.push({ ePanel, k, visible: true });
  1831. break;
  1832. case 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN':
  1833. res.push({ ePanel, k, visible: false });
  1834. break;
  1835. default:
  1836. res.push({ ePanel, k, visible: false });
  1837. }
  1838.  
  1839. k = k << 1;
  1840.  
  1841. }
  1842. return { list: res, value: v, count: count, shownRes };
  1843. }
  1844.  
  1845.  
  1846. function ytBtnOpenEngagementPanel(/** @type {number | string} */ panel_id) {
  1847.  
  1848. if (typeof panel_id == 'string') {
  1849. panel_id = panel_id.replace('#engagement-panel-', '');
  1850. panel_id = parseInt(panel_id);
  1851. }
  1852. if (panel_id >= 0) { } else return false;
  1853.  
  1854. let panels = engagement_panels_();
  1855.  
  1856. for (const { ePanel, k, visible } of panels.list) {
  1857. if ((panel_id & k) === k) {
  1858. if (!visible) ePanel.setAttribute('visibility', "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED");
  1859. } else {
  1860. if (visible) ytBtnCloseEngagementPanel(ePanel);
  1861. }
  1862. }
  1863.  
  1864. }
  1865.  
  1866. function ytBtnCloseEngagementPanel(/** @type {HTMLElement} */ s) {
  1867. //ePanel.setAttribute('visibility',"ENGAGEMENT_PANEL_VISIBILITY_HIDDEN");
  1868. let button = querySelectorFromAnchor.call(s, 'ytd-watch-flexy ytd-engagement-panel-title-header-renderer #header > #visibility-button');
  1869.  
  1870. if (button) {
  1871. button =
  1872. querySelectorFromAnchor.call(button, 'div.yt-spec-touch-feedback-shape') ||
  1873. querySelectorFromAnchor.call(button, 'ytd-button-renderer');
  1874. if (button) button.click();
  1875. }
  1876.  
  1877. }
  1878.  
  1879. function ytBtnCloseEngagementPanels() {
  1880. if (isEngagementPanelExpanded()) {
  1881. for (const s of document.querySelectorAll(
  1882. `ytd-watch-flexy ytd-engagement-panel-section-list-renderer[o3r-${sa_epanel}]`
  1883. )) {
  1884. if (s.getAttribute('visibility') == "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED") ytBtnCloseEngagementPanel(s);
  1885. }
  1886. }
  1887. }
  1888.  
  1889. function ytBtnSetTheater() {
  1890. if (!isTheater()) {
  1891. const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
  1892. if (sizeBtn) sizeBtn.click();
  1893. }
  1894. }
  1895.  
  1896. function ytBtnCancelTheater() {
  1897. if (isTheater()) {
  1898. const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
  1899. if (sizeBtn) sizeBtn.click();
  1900. }
  1901. }
  1902.  
  1903. function ytBtnExpandChat() {
  1904. let button = document.querySelector('ytd-live-chat-frame#chat[collapsed] > .ytd-live-chat-frame#show-hide-button')
  1905. if (button) {
  1906. button =
  1907. querySelectorFromAnchor.call(button, 'div.yt-spec-touch-feedback-shape') ||
  1908. querySelectorFromAnchor.call(button, 'ytd-toggle-button-renderer');
  1909. if (button) button.click();
  1910. }
  1911. }
  1912.  
  1913. function ytBtnCollapseChat() {
  1914. let button = document.querySelector('ytd-live-chat-frame#chat:not([collapsed]) > .ytd-live-chat-frame#show-hide-button')
  1915. if (button) {
  1916. button =
  1917. querySelectorFromAnchor.call(button, 'div.yt-spec-touch-feedback-shape') ||
  1918. querySelectorFromAnchor.call(button, 'ytd-toggle-button-renderer');
  1919. if (button) button.click();
  1920. }
  1921. }
  1922.  
  1923.  
  1924.  
  1925.  
  1926.  
  1927.  
  1928. async function makeVideosAutoLoad2() {
  1929. let sVideosList = document.querySelector('ytd-watch-flexy #tab-videos [placeholder-videos]');
  1930.  
  1931. if (!sVideosList) return null;
  1932.  
  1933. //let ab = sVideosList.getAttribute('tabview-videos-autoload')
  1934. await Promise.resolve(0);
  1935.  
  1936. let endPosDOM = document.querySelector('tabview-videos-end-pos')
  1937. if (endPosDOM) endPosDOM.remove(); // just in case
  1938. endPosDOM = document.createElement('tabview-videos-end-pos')
  1939. sVideosList.parentNode.insertBefore(endPosDOM, sVideosList.nextSibling);
  1940.  
  1941. await Promise.resolve(0);
  1942.  
  1943.  
  1944. //sVideosList.setAttribute('tabview-videos-autoload', '1')
  1945.  
  1946. _console.log(9333)
  1947. if (!sVideosITO) {
  1948.  
  1949. sVideosITO = new IntersectionObserver((entries) => {
  1950.  
  1951. _console.log(9334, entries)
  1952. if (entries.length !== 1) return;
  1953. if (entries[0].isIntersecting !== true) return;
  1954. let elm = ((entries[0] || 0).target || 0);
  1955. if (!elm) return;
  1956.  
  1957. setTimeout(() => {
  1958.  
  1959. let res = setVideosTwoColumns(2 | 4, true)
  1960.  
  1961. _console.log(9335, res)
  1962.  
  1963. if (res.m2 && res.m3) {
  1964. let m4 = res.m2.closest('ytd-continuation-item-renderer');
  1965. let m5, m6;
  1966.  
  1967. _console.log(9336, m4)
  1968. if (m4) {
  1969. m5 = querySelectorFromAnchor.call(m4, 'ytd-button-renderer.style-scope.ytd-continuation-item-renderer, yt-button-renderer.style-scope.ytd-continuation-item-renderer');
  1970.  
  1971. // YouTube coding bug - correct is 'ytd-button-renderer'. If the page is redirected under single column mode, the tag become 'yt-button-renderer'
  1972. // under 'yt-button-renderer', the
  1973.  
  1974. if (m5)
  1975. m6 = querySelectorFromAnchor.call(m5, 'button.yt-spec-button-shape-next--call-to-action'); // main
  1976.  
  1977. _console.log(9337, m4, m5, m6)
  1978.  
  1979. if (m6) {
  1980. m6.click() // generic solution
  1981. } else if (m5) {
  1982. m5.click(); // not sure
  1983. } else {
  1984. m4.dispatchEvent(new Event('yt-service-request-sent-button-renderer')); // only for correct YouTube coding
  1985. }
  1986. }
  1987. }
  1988.  
  1989. }, 30); // delay required to allow YouTube generate the continuation elements
  1990.  
  1991. /*
  1992. if (elm && entries[0].isIntersecting === true) {
  1993. elm.dispatchEvent(new Event('yt-service-request-sent-button-renderer'))
  1994. }*/
  1995. }, {
  1996. rootMargin: `0px`, // refer to css margin-top:-30vh
  1997. threshold: [0]
  1998. })
  1999. sVideosITO.observe(endPosDOM);
  2000. } else {
  2001. sVideosITO.disconnect();
  2002. sVideosITO.observe(endPosDOM);
  2003. }
  2004.  
  2005.  
  2006. }
  2007.  
  2008.  
  2009. function fixTabs() {
  2010.  
  2011.  
  2012. if (!scriptEnable) return;
  2013.  
  2014.  
  2015. let queryElement = document.querySelector('*:not(#tab-videos) > #related:not([tyt-null]) > ytd-watch-next-secondary-results-renderer')
  2016.  
  2017. let isRelocated = !!queryElement;
  2018.  
  2019.  
  2020.  
  2021. if (isRelocated) {
  2022.  
  2023. _console.log(3202,2)
  2024.  
  2025. let relocatedRelated = closestDOM.call(queryElement, '#related'); // NOT NULL
  2026.  
  2027. let right_tabs = document.querySelector('#right-tabs'); // can be NULL
  2028.  
  2029. let tab_videos = right_tabs ? querySelectorFromAnchor.call(right_tabs, "#tab-videos") : null; // can be NULL
  2030.  
  2031. if (tab_videos !== null) {
  2032. _console.log(3202,4)
  2033.  
  2034. for (const s of querySelectorAllFromAnchor.call(relocatedRelated, '#related')) {
  2035. s.setAttribute('tyt-null', '')
  2036. }
  2037.  
  2038. let target_container = document.querySelector('ytd-watch-flexy:not([is-two-columns_]) #primary-inner.ytd-watch-flexy, ytd-watch-flexy[is-two-columns_] #secondary-inner.ytd-watch-flexy')
  2039.  
  2040. if (target_container) target_container.append(right_tabs) // last-child
  2041.  
  2042.  
  2043. let videos_related = relocatedRelated; // NOT NULL
  2044. $('[placeholder-videos]').removeAttr('placeholder-videos');
  2045. $('[placeholder-for-youtube-play-next-queue]').removeAttr('placeholder-for-youtube-play-next-queue');
  2046.  
  2047. tab_videos.appendChild(videos_related);
  2048. videos_related.setAttribute('placeholder-for-youtube-play-next-queue', '')
  2049. videos_related.setAttribute('placeholder-videos', '')
  2050. makeVideosAutoLoad2();
  2051.  
  2052. }
  2053.  
  2054. }
  2055.  
  2056.  
  2057.  
  2058. /** @type {HTMLElement | null} */
  2059. let chatroom = null;
  2060. if (chatroom = document.querySelector('*:not([data-positioner="before|#chat"]) + ytd-live-chat-frame#chat, ytd-live-chat-frame#chat:first-child')) {
  2061.  
  2062. let positioner = document.querySelector('tabview-youtube-positioner[data-positioner="before|#chat"]');
  2063. if (positioner) positioner.remove();
  2064.  
  2065.  
  2066. if (document.querySelector('.YouTubeLiveFilledUpView')) {
  2067. // no relocation
  2068. } else {
  2069.  
  2070. $(chatroom).insertBefore('#right-tabs')
  2071.  
  2072. }
  2073.  
  2074.  
  2075. $(positioner ? positioner : document.createElement('tabview-youtube-positioner')).attr('data-positioner', 'before|#chat').insertBefore(chatroom)
  2076.  
  2077.  
  2078.  
  2079. }
  2080.  
  2081.  
  2082. }
  2083.  
  2084. function handlerAutoCompleteExist() {
  2085.  
  2086.  
  2087. /** @type {HTMLElement} */
  2088. let autoComplete = this;
  2089.  
  2090. autoComplete.removeEventListener('autocomplete-sc-exist', handlerAutoCompleteExist, false)
  2091.  
  2092. let domId = autoComplete.getAttribute('data-autocomplete-input-id')
  2093. let searchBox = autoComplete.ownerDocument.querySelector(`[data-autocomplete-results-id="${domId}"]`)
  2094.  
  2095. if (!domId || !searchBox) return;
  2096.  
  2097. let positioner = searchBox.nextSibling;
  2098. if (positioner && positioner.nodeName.toLowerCase() == "autocomplete-positioner") { } else if (positioner && positioner.nodeName.toLowerCase() != "autocomplete-positioner") {
  2099. $(positioner = document.createElement("autocomplete-positioner")).insertAfter(searchBox);
  2100. } else {
  2101. $(positioner = document.createElement("autocomplete-positioner")).prependTo(searchBox.parentNode);
  2102. }
  2103. $(autoComplete).prependTo(positioner);
  2104.  
  2105. positioner.style.setProperty('--sb-margin-bottom', getComputedStyle(searchBox).marginBottom)
  2106. positioner.style.setProperty('--height', searchBox.offsetHeight + 'px')
  2107.  
  2108. }
  2109.  
  2110. function mtf_fixAutoCompletePosition(/** @type {HTMLElement} */ elmAutoComplete) {
  2111.  
  2112.  
  2113. elmAutoComplete.setAttribute('autocomplete-disable-updatesc', '')
  2114. elmAutoComplete.addEventListener('autocomplete-sc-exist', handlerAutoCompleteExist, false)
  2115.  
  2116. let tf = () => {
  2117. if (!document.documentElement.getAttribute('tabview-unwrapjs')) return setTimeout(tf, 300);
  2118. document.dispatchEvent(new CustomEvent('tabview-fix-autocomplete'))
  2119. }
  2120. tf();
  2121. //script_inject_facp.inject();
  2122.  
  2123. }
  2124.  
  2125. function mtf_AfterFixTabs() {
  2126.  
  2127.  
  2128. /** @type {HTMLElement | null} */
  2129. let ytdFlexyElm = kRef(ytdFlexy);
  2130. if (!scriptEnable || !ytdFlexyElm) return;
  2131.  
  2132. /** @type {HTMLElement | null} */
  2133. const rootElement = ytdFlexyElm;
  2134.  
  2135.  
  2136.  
  2137. const autocomplete = querySelectorFromAnchor.call(rootElement, '[placeholder-for-youtube-play-next-queue] input#suggestions-search + autocomplete-positioner > .autocomplete-suggestions[data-autocomplete-input-id]:not([position-fixed-by-tabview-youtube])')
  2138.  
  2139. if (autocomplete) {
  2140.  
  2141. const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')
  2142.  
  2143.  
  2144. if (searchBox) {
  2145.  
  2146.  
  2147. autocomplete.parentNode.setAttribute('position-fixed-by-tabview-youtube', '');
  2148. autocomplete.setAttribute('position-fixed-by-tabview-youtube', '');
  2149. autocomplete.setAttribute('userscript-scrollbar-render', '')
  2150.  
  2151. if (!searchBox.hasAttribute('is-set-click-to-toggle')) {
  2152. searchBox.setAttribute('is-set-click-to-toggle', '')
  2153. searchBox.addEventListener('click', function () {
  2154.  
  2155.  
  2156. setTimeout(() => {
  2157. const autocomplete = document.querySelector(`.autocomplete-suggestions[data-autocomplete-input-id="${this.getAttribute('data-autocomplete-results-id')}"]`)
  2158.  
  2159. if (!autocomplete) return;
  2160.  
  2161. const isNotEmpty = (autocomplete.textContent || '').length > 0 && (this.value || '').length > 0;
  2162.  
  2163. if (isNotEmpty) {
  2164.  
  2165. let elmVisible = isDOMVisible(autocomplete)
  2166.  
  2167. if (elmVisible) $(autocomplete).hide();
  2168. else $(autocomplete).show();
  2169.  
  2170. }
  2171.  
  2172. }, 20);
  2173.  
  2174. })
  2175.  
  2176. let timeoutOnce_searchbox_keyup = new Timeout();
  2177. searchBox.addEventListener('keyup', function () {
  2178.  
  2179. timeoutOnce_searchbox_keyup.set(() => {
  2180.  
  2181. const autocomplete = document.querySelector(`.autocomplete-suggestions[data-autocomplete-input-id="${this.getAttribute('data-autocomplete-results-id')}"]`)
  2182.  
  2183. if (!autocomplete) return;
  2184.  
  2185.  
  2186. const isNotEmpty = (autocomplete.textContent || '').length > 0 && (this.value || '').length > 0
  2187.  
  2188. if (isNotEmpty) {
  2189.  
  2190. let elmVisible = isDOMVisible(autocomplete)
  2191.  
  2192. if (!elmVisible) $(autocomplete).show();
  2193.  
  2194. }
  2195.  
  2196. }, 20);
  2197.  
  2198. })
  2199.  
  2200. }
  2201.  
  2202.  
  2203.  
  2204. }
  2205.  
  2206. }
  2207.  
  2208.  
  2209.  
  2210.  
  2211. let currentLastVideo = querySelectorFromAnchor.call(rootElement, '[placeholder-videos] #items ytd-compact-video-renderer:last-of-type')
  2212. let prevLastVideo = kRef(_cachedLastVideo);
  2213.  
  2214. if (prevLastVideo !== currentLastVideo && currentLastVideo) {
  2215. _cachedLastVideo = mWeakRef(currentLastVideo);
  2216. }
  2217.  
  2218. if (prevLastVideo !== currentLastVideo && currentLastVideo && prevLastVideo) {
  2219.  
  2220. let isPrevRemoved = !prevLastVideo.parentNode
  2221.  
  2222.  
  2223. function getVideoListHash() {
  2224.  
  2225. let res = [...document.querySelectorAll('[placeholder-videos] #items ytd-compact-video-renderer')].map(renderer => {
  2226. let a = querySelectorFromAnchor.call(renderer, 'a[href*="watch"][href*="v="]')
  2227. if (!a) return ''; //???
  2228. return a.getAttribute('href') || ''
  2229.  
  2230. }).join('|')
  2231. // /watch?v=XXXXX|/watch?v=XXXXXX|/watch?v=XXXXXX
  2232.  
  2233. // alternative - DOM.data.videoId
  2234. // let elms = document.querySelectorAll('[placeholder-videos] #items ytd-compact-video-renderer')
  2235. // let res = [...elms].map(elm=>elm.data.videoId||'').join('|') ;
  2236.  
  2237. if (res.indexOf('||') >= 0) {
  2238. res = '';
  2239. }
  2240.  
  2241. return res ? res : null;
  2242. }
  2243.  
  2244. if (isPrevRemoved) {
  2245.  
  2246. // this is the replacement of videos instead of addition
  2247.  
  2248. const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')
  2249.  
  2250. let currentPlayListHash = getVideoListHash() || null;
  2251.  
  2252. if (!currentPlayListHash) {
  2253.  
  2254. } else if (!videoListBeforeSearch && searchBox) {
  2255.  
  2256. videoListBeforeSearch = currentPlayListHash;
  2257. if (videoListBeforeSearch) {
  2258. //console.log('fromSearch', videoListBeforeSearch)
  2259.  
  2260. requestAnimationFrame(function () { // the action shall be done by user input at foreground
  2261.  
  2262. let renderer = document.querySelector('[placeholder-videos] ytd-watch-next-secondary-results-renderer');
  2263. if (searchBox && searchBox.parentNode) searchBox.blur();
  2264.  
  2265. if (renderer) {
  2266. let scrollParent = renderer.parentNode;
  2267. if (scrollParent.scrollHeight > scrollParent.offsetHeight) {
  2268. let targetTop = renderer.offsetTop;
  2269. if (searchBox && searchBox.parentNode == scrollParent) targetTop -= searchBox.offsetHeight
  2270. scrollParent.scrollTop = targetTop - scrollParent.firstChild.offsetTop;
  2271. }
  2272. }
  2273.  
  2274. });
  2275.  
  2276. }
  2277.  
  2278. } else if (videoListBeforeSearch) {
  2279.  
  2280. if (currentPlayListHash != videoListBeforeSearch) {
  2281.  
  2282. videoListBeforeSearch = null;
  2283. //console.log('fromSearch', videoListBeforeSearch)
  2284.  
  2285.  
  2286. }
  2287.  
  2288. }
  2289.  
  2290.  
  2291. }
  2292.  
  2293.  
  2294. }
  2295.  
  2296.  
  2297.  
  2298.  
  2299. }
  2300.  
  2301.  
  2302.  
  2303. /*
  2304. function windowScroll() {
  2305. let ct = Date.now();
  2306. if (ct - t_heated_BodyScroll < 6) return; // avoid duplicate calling
  2307. t_heated_BodyScroll = ct;
  2308. window.dispatchEvent(new Event("scroll")); // dispatch Scroll Event to Window for content display
  2309. }
  2310. */
  2311.  
  2312. /* items - > special case (2022/11/09) */
  2313.  
  2314.  
  2315.  
  2316. // continuous check for element relocation
  2317. function mtf_append_comments() {
  2318.  
  2319. /** @type {HTMLElement | null} */
  2320. let ytdFlexyElm = kRef(ytdFlexy);
  2321. if (!scriptEnable || !ytdFlexyElm) return;
  2322.  
  2323. /** @type {HTMLElement | null} */
  2324. const rootElement = ytdFlexyElm;
  2325.  
  2326. let comments = querySelectorFromAnchorFizzy(rootElement, 'comments', '#primary.ytd-watch-flexy ytd-watch-metadata ~ ytd-comments#comments');
  2327. if (comments) {
  2328. $(comments).appendTo('#tab-comments')
  2329. }
  2330. }
  2331.  
  2332. function prependTo(elm, target){
  2333.  
  2334. if(!target || !elm) return null;
  2335.  
  2336. if (HTMLElement.prototype.prepend) {
  2337. // using prepend
  2338. HTMLElement.prototype.prepend.call(target, elm);
  2339. return true;
  2340. } else {
  2341. // using insertBefore
  2342. try {
  2343. elementInsertBefore.call(target, elm, target.firstChild);
  2344. return true;
  2345. } catch (e) {
  2346. console.log('element insert failed in old browser CE')
  2347. }
  2348. }
  2349.  
  2350. return false;
  2351.  
  2352. }
  2353.  
  2354. // css animation check for element relocation
  2355. function mtf_liveChatBtnF(node) {
  2356.  
  2357. if (!node || node.nodeType !== 1) return;
  2358.  
  2359. /** @type {HTMLElement | null} */
  2360. let ytdFlexyElm = kRef(ytdFlexy);
  2361. if (!scriptEnable || !ytdFlexyElm) return;
  2362.  
  2363. /** @type {HTMLElement | null} */
  2364. const rootElement = ytdFlexyElm;
  2365.  
  2366.  
  2367. let button = node;
  2368. if (button) {
  2369. prependTo(button, button.parentNode);
  2370. }
  2371.  
  2372.  
  2373. }
  2374.  
  2375.  
  2376. handleDOMAppear('chatFrameToggleBtnAppended1', (evt)=>{
  2377.  
  2378. _console.log(5099, 'chatFrameToggleBtnAppended', evt)
  2379.  
  2380. Promise.resolve(0).then(() => { // avoid immediately dom change
  2381.  
  2382. tabsDeferred.debounce(() => {
  2383.  
  2384. mtf_liveChatBtnF(evt.target);
  2385.  
  2386. })
  2387.  
  2388. })
  2389.  
  2390. });
  2391.  
  2392. DEBUG_LOG && handleDOMAppear('chatFrameToggleBtnAppended2', (evt)=>{
  2393.  
  2394. _console.log(5099, 'chatFrameToggleBtnAppended', evt)
  2395.  
  2396. });
  2397.  
  2398.  
  2399.  
  2400.  
  2401.  
  2402. // continuous check for element relocation
  2403. // fired at begining & window resize, etc
  2404. // might moved to #primary
  2405. function mtf_append_playlist(/** @type {HTMLElement | null} */ playlist) {
  2406.  
  2407.  
  2408. if(playlist === null){
  2409. playlist = document.querySelector('ytd-watch-flexy[playlist] *:not(#ytd-userscript-playlist) > ytd-playlist-panel-renderer.style-scope.ytd-watch-flexy#playlist:not(.ytd-miniplayer)');
  2410. // this playlist is highly possible to have '#items'
  2411. if(!playlist) return;
  2412. }
  2413.  
  2414. /** @type {HTMLElement | null} */
  2415. let ytdFlexyElm = kRef(ytdFlexy);
  2416. if (!scriptEnable || !ytdFlexyElm) return;
  2417.  
  2418. let items = querySelectorFromAnchor.call(playlist, "*:not(#ytd-userscript-playlist) > ytd-playlist-panel-renderer#playlist:not(.ytd-miniplayer) #items.ytd-playlist-panel-renderer:not(:empty)");
  2419.  
  2420. if (items) {
  2421.  
  2422. if ((playlist.nodeName||0).toUpperCase() === 'YTD-PLAYLIST-PANEL-RENDERER') {
  2423. let tab_list = document.querySelector("#tab-list");
  2424.  
  2425. if (!tab_list) return;
  2426.  
  2427. let $wrapper = getWrapper('ytd-userscript-playlist')
  2428. $wrapper.append(playlist).appendTo(tab_list);
  2429.  
  2430.  
  2431. }
  2432.  
  2433.  
  2434.  
  2435. }
  2436. }
  2437.  
  2438.  
  2439. function getCountHText(elm) {
  2440. return `${((((pageFetchedData || 0).pageData || 0).playerResponse || 0).videoDetails || 0).videoId || 0}...${elm.textContent}`
  2441. }
  2442.  
  2443. // content fix - info & playlist
  2444. // fired at begining, and keep for in case any change
  2445. function mtf_fix_details() {
  2446.  
  2447. if (!scriptEnable) return Promise.resolve(0); // in case
  2448.  
  2449. return Promise.all([
  2450. new Promise(resolve => {
  2451.  
  2452.  
  2453.  
  2454. let contentToggleBtn = document.querySelector('ytd-watch-flexy #tab-info ytd-expander tp-yt-paper-button#less.ytd-expander:not([hidden]), #tab-info ytd-expander tp-yt-paper-button#more.ytd-expander:not([hidden])');
  2455.  
  2456. if (contentToggleBtn) {
  2457.  
  2458. (() => {
  2459. const domElement = contentToggleBtn;
  2460. contentToggleBtn = null;
  2461. // if(!domElement.parentElement) return; // not working in pseudo custom element - parentNode = documentFragment
  2462. const expander = closestDOM.call(domElement, 'ytd-watch-flexy #tab-info ytd-expander')
  2463.  
  2464. if (!expander || expander.nodeType !== 1) return; // checking whether it is still on the page
  2465.  
  2466. if (expander.style.getPropertyValue('--ytd-expander-collapsed-height')) {
  2467. expander.style.setProperty('--ytd-expander-collapsed-height', '')
  2468. }
  2469. nativeCall(expander, [
  2470. { 'property': 'canToggleJobId', 'value': 1 }, // false disable calculateCanCollapse in childrenChanged
  2471. { 'property': 'alwaysToggleable', 'value': false }, // this is checked in childrenChanged
  2472. { 'property': 'recomputeOnResize', 'value': false }, // no need to check toggleable
  2473. { 'property': 'isToggled', 'value': true }, // show full content
  2474. { 'property': 'canToggle', 'value': false }, // hide show more or less btn
  2475. { 'property': 'collapsedHeight', 'value': 999999 } // disable collapsed height check
  2476. ])
  2477.  
  2478. })();
  2479. }
  2480.  
  2481. resolve();
  2482.  
  2483.  
  2484.  
  2485. }),
  2486.  
  2487. new Promise(resolve => {
  2488.  
  2489.  
  2490. let strcturedInfo = document.querySelector('ytd-watch-flexy #tab-info ytd-structured-description-content-renderer.style-scope.ytd-video-secondary-info-renderer[hidden]')
  2491. if (strcturedInfo) {
  2492.  
  2493. (() => {
  2494.  
  2495. strcturedInfo.removeAttribute('hidden');
  2496.  
  2497.  
  2498. setTimeout(() => {
  2499.  
  2500.  
  2501. let e = strcturedInfo.closest('ytd-watch-flexy #tab-info ytd-expander');
  2502.  
  2503. if (!e) return;
  2504. let s = querySelectorAllFromAnchor.call(e, '#tab-info .more-button.style-scope.ytd-video-secondary-info-renderer[role="button"]');
  2505. if (s.length === 1) {
  2506. let sp = s[0].parentNode
  2507. if (sp.nodeName.toUpperCase() === 'TP-YT-PAPER-BUTTON') {
  2508.  
  2509. sp.click();
  2510. }
  2511. }
  2512.  
  2513. }, 300)
  2514.  
  2515. })();
  2516. }
  2517.  
  2518.  
  2519. resolve();
  2520.  
  2521. }),
  2522.  
  2523. new Promise(resolve => {
  2524.  
  2525.  
  2526. // just in case the playlist is collapsed
  2527. let playlist = document.querySelector('#tab-list ytd-playlist-panel-renderer#playlist')
  2528. if (playlist && playlist.matches('[collapsed], [collapsible]')) {
  2529.  
  2530. (() => {
  2531.  
  2532. const domElement = playlist;
  2533. playlist = null;
  2534. // if(!domElement.parentElement || domElement.nodeType!==1) return; // not working in pseudo custom element - parentNode = documentFragment
  2535. const tablist = closestDOM.call(domElement, 'ytd-watch-flexy #tab-list')
  2536.  
  2537. if (!tablist || tablist.nodeType !== 1) return; // checking whether it is still on the page
  2538.  
  2539. if (domElement.hasAttribute('collapsed')) wAttr(domElement, 'collapsed', false);
  2540. if (domElement.hasAttribute('collapsible')) wAttr(domElement, 'collapsible', false);
  2541. })();
  2542. }
  2543.  
  2544. resolve();
  2545.  
  2546.  
  2547. }),
  2548.  
  2549. new Promise(resolve => {
  2550.  
  2551.  
  2552. let subscribersCount = document.querySelector('#primary.ytd-watch-flexy #below ytd-watch-metadata #owner #owner-sub-count')
  2553.  
  2554. if (subscribersCount) {
  2555. if (!subscribersCount.hasAttribute('title')) {
  2556. // assume YouTube native coding would not implement [title]
  2557.  
  2558. let ytdWatchMetaDataElm = closestDOM.call(subscribersCount, 'body #primary.ytd-watch-flexy #below ytd-watch-metadata[modern-metapanel-order]:not([tabview-uploader-hover])');
  2559. if (ytdWatchMetaDataElm) {
  2560. ytdWatchMetaDataElm.setAttribute('tabview-uploader-hover', '')
  2561. let _h = 0;
  2562. ytdWatchMetaDataElm.addEventListener('transitionend', function (evt) {
  2563. // no css selector rule required; no delay js function call required
  2564.  
  2565. if (evt.propertyName === 'background-position-y') { // string comparision only
  2566.  
  2567. // If the cursor initially stayed at the owner info area,
  2568. // the mechanism will be broken
  2569. // so implement the true leave detection at their parent
  2570.  
  2571. let isHover = evt.elapsedTime < 0.03; // 50ms @normal; 10ms @hover;
  2572.  
  2573. let cssRoot = this; // no querySelector is required
  2574. if (!isHover) {
  2575. // 50ms is slowest than sub element leave effect
  2576. if (_h > 0) cssRoot.classList.toggle('tabview-uploader-hover', false) // even the order is incorrect, removal of class is safe.
  2577. _h = 0; // in case
  2578. } else if (isHover) {
  2579. // 10ms is faster than sub element hover effect
  2580. // no removal of class in case browser's transition implemention order is incorrect
  2581. _h = 0; // in case
  2582. }
  2583.  
  2584. } else if (evt.propertyName === 'background-position-x') { // string comparision only
  2585.  
  2586. //from one element to another element; hover effect of 2nd element transition end first.
  2587. // _h: 0 -> 1 -> 2 -> 1
  2588.  
  2589. let isHover = evt.elapsedTime < 0.03; // 40ms @normal; 20ms @hover;
  2590. if (isHover) _h++; else _h--;
  2591. let cssRoot = this; // no querySelector is required
  2592. if (_h <= 0) {
  2593. cssRoot.classList.toggle('tabview-uploader-hover', false)
  2594. _h = 0; // in case
  2595. } else if (_h === 1) {
  2596. cssRoot.classList.toggle('tabview-uploader-hover', true)
  2597. }
  2598.  
  2599. }
  2600.  
  2601. }, capturePassive) // capture the hover effect inside the cssRoot
  2602. }
  2603.  
  2604. }
  2605. subscribersCount.setAttribute('title', subscribersCount.textContent); // set at every page update
  2606.  
  2607. }
  2608.  
  2609. resolve();
  2610.  
  2611. })
  2612.  
  2613.  
  2614. ]);
  2615.  
  2616.  
  2617.  
  2618. }
  2619.  
  2620.  
  2621.  
  2622. document.addEventListener('mouseover', stopIframePropagation, true)
  2623. document.addEventListener('mouseout', stopIframePropagation, true)
  2624. document.addEventListener('mousedown', stopIframePropagation, true)
  2625. document.addEventListener('mouseup', stopIframePropagation, true)
  2626. document.addEventListener('keydown', stopIframePropagation, true)
  2627. document.addEventListener('keyup', stopIframePropagation, true)
  2628. document.addEventListener('mouseenter', stopIframePropagation, true)
  2629. document.addEventListener('mouseleave', stopIframePropagation, true)
  2630.  
  2631.  
  2632.  
  2633. const innerCommentsFuncs = [
  2634. // comments
  2635. function () {
  2636.  
  2637. let elm = kRef(this.elm);
  2638. _console.log(2907, 1,!!elm)
  2639. if(!elm) return;
  2640.  
  2641. let span = document.querySelector("span#tyt-cm-count")
  2642. let r = '0';
  2643. let txt = elm.textContent
  2644. if (typeof txt == 'string') {
  2645. let m = txt.match(/[\d\,\s]+/)
  2646. if (m) {
  2647. r = m[0].trim()
  2648. }
  2649. }
  2650.  
  2651. if (span) {
  2652. let tab_btn = closestDOM.call(span, '.tab-btn[userscript-tab-content="#tab-comments"]')
  2653. if (tab_btn) tab_btn.setAttribute('loaded-comment', 'normal')
  2654. span.textContent = r;
  2655. }
  2656.  
  2657. setCommentSection(1);
  2658. m_last_count = getCountHText(elm);
  2659. _console.log(2907,2, m_last_count)
  2660. return true;
  2661. },
  2662. // message
  2663. function () {
  2664.  
  2665. let elm = kRef(this.elm);
  2666. _console.log(2907, 2,!!elm)
  2667. if(!elm) return;
  2668.  
  2669. let span = document.querySelector("span#tyt-cm-count")
  2670. if (span) {
  2671. let tab_btn = closestDOM.call(span, '.tab-btn[userscript-tab-content="#tab-comments"]')
  2672. if (tab_btn) tab_btn.setAttribute('loaded-comment', 'message')
  2673. span.textContent = '\u200B';
  2674. }
  2675.  
  2676. setCommentSection(1);
  2677. m_last_count = getCountHText(elm);
  2678. _console.log(2907,2, m_last_count)
  2679. return true;
  2680. }
  2681. ]
  2682.  
  2683.  
  2684. let innerDOMCommentsCountTextCache = null;
  2685. function innerDOMCommentsCountLoader() {
  2686. // independent of tabs initialization
  2687. // f() is executed after tabs being ready
  2688.  
  2689. /** @type {HTMLElement | null} */
  2690. let ytdFlexyElm = kRef(ytdFlexy);
  2691. if (!scriptEnable || !ytdFlexyElm) return;
  2692.  
  2693. _console.log(3434, pageType)
  2694. if (pageType !== 'watch') return;
  2695.  
  2696.  
  2697.  
  2698. //console.log(823100,rootElement)
  2699.  
  2700. /** @type {Array<HTMLElement>} */
  2701. let qmElms = [...document.querySelectorAll('ytd-comments#comments #count.ytd-comments-header-renderer, ytd-comments#comments ytd-item-section-renderer.ytd-comments#sections #header ~ #contents > ytd-message-renderer.ytd-item-section-renderer')]
  2702.  
  2703.  
  2704. const eTime = +`${Date.now() - mTime}00`;
  2705.  
  2706. let res = new Array(qmElms.length);
  2707. res.newFound = false;
  2708.  
  2709.  
  2710.  
  2711. let ci = 0;
  2712. let latest = -1;
  2713.  
  2714. let retrival = cmTime;
  2715. cmTime = eTime;
  2716.  
  2717. for (const qmElm of qmElms) {
  2718.  
  2719. let mgz = 0
  2720. if (qmElm.id === 'count') {
  2721. //#count.ytd-comments-header-renderer
  2722. mgz = 1;
  2723.  
  2724. } else if ((qmElm.textContent || '').trim()) {
  2725. //ytd-message-renderer.ytd-item-section-renderer
  2726. mgz = 2;
  2727. // it is possible to get the message before the header generation.
  2728. // sample link - https://www.youtube.com/watch?v=PVUZ8Nvr1ic
  2729. // sample link - https://www.youtube.com/watch?v=yz8AiQc1Bk8
  2730. }
  2731.  
  2732. if (mgz > 0) {
  2733.  
  2734.  
  2735. let lastUpdate = loadedCommentsDT.get(qmElm) || 0;
  2736. let diff = retrival - lastUpdate
  2737. _console.log(2907, diff)
  2738. let isNew = (diff > 4 || diff < -4);
  2739. if (!isNew) {
  2740. loadedCommentsDT.set(qmElm, eTime);
  2741. } else {
  2742. loadedCommentsDT.set(qmElm, eTime + 1);
  2743. res.newFound = true;
  2744. latest = ci;
  2745. }
  2746.  
  2747. res[ci] = {
  2748. elm: mWeakRef(qmElm),
  2749. isNew: isNew,
  2750. isLatest: false, //set afterwards
  2751. f: innerCommentsFuncs[mgz - 1]
  2752. }
  2753.  
  2754. if(DEBUG_LOG){
  2755. res[ci].status = mgz;
  2756. res[ci].text = qmElm.textContent;
  2757. }
  2758.  
  2759. ci++;
  2760.  
  2761. }
  2762.  
  2763. }
  2764. if (res.length > ci) res.length = ci;
  2765. if(latest>=0){
  2766.  
  2767. res[latest].isLatest = true;
  2768.  
  2769.  
  2770. let elm = kRef(res[latest].elm);
  2771. if (elm)
  2772. innerDOMCommentsCountTextCache = elm.textContent;
  2773.  
  2774. }else if(res.length ===1){
  2775.  
  2776. let qmElm = kRef(res[0].elm);
  2777. let t = null;
  2778. if (qmElm) {
  2779.  
  2780. let t = qmElm.textContent;
  2781. if (t !== innerDOMCommentsCountTextCache) {
  2782.  
  2783.  
  2784. loadedCommentsDT.set(qmElm, eTime + 1);
  2785. res.newFound = true;
  2786.  
  2787. res[0].isNew = true;
  2788. latest = 0;
  2789.  
  2790. res[latest].isLatest = true;
  2791.  
  2792. }
  2793.  
  2794. innerDOMCommentsCountTextCache = t;
  2795.  
  2796.  
  2797. }
  2798.  
  2799.  
  2800. }
  2801.  
  2802.  
  2803. _console.log(2908, res, Q.comments_section_loaded)
  2804.  
  2805. _console.log(696, res.map(e=>({
  2806.  
  2807. text: kRef(e.elm).textContent,
  2808. isNew: e.isNew,
  2809. isLatest: e.isLatest
  2810.  
  2811. })))
  2812.  
  2813. return res;
  2814.  
  2815. }
  2816.  
  2817. function restoreFetching() {
  2818.  
  2819.  
  2820. if (mtf_forceCheckLiveVideo_disable === 2) return;
  2821.  
  2822.  
  2823. let ytdFlexyElm = kRef(ytdFlexy);
  2824. if (!ytdFlexyElm) return;
  2825.  
  2826. _console.log(2901)
  2827.  
  2828. if ( (ytdFlexyElm.getAttribute('tyt-comments') || '').indexOf('K') >= 0 ) return;
  2829.  
  2830. _console.log(2902)
  2831.  
  2832. let visibleComments = querySelectorFromAnchor.call(ytdFlexyElm, 'ytd-comments#comments:not([hidden])')
  2833. if (!visibleComments) return;
  2834.  
  2835. _console.log(2903)
  2836.  
  2837.  
  2838. ytdFlexyElm.setAttribute('tyt-comments','Kz');
  2839.  
  2840. const tabBtn = document.querySelector('[userscript-tab-content="#tab-comments"]');
  2841. let span = querySelectorFromAnchor.call(tabBtn, 'span#tyt-cm-count');
  2842. tabBtn.removeAttribute('loaded-comment')
  2843. span.innerHTML = '';
  2844.  
  2845. if (tabBtn) {
  2846. tabBtn.classList.remove("tab-btn-hidden")
  2847. }
  2848.  
  2849. _console.log(2905)
  2850.  
  2851.  
  2852.  
  2853. }
  2854.  
  2855. const resultCommentsCountCaching = (res) => {
  2856. // update fetchCounts by res
  2857. // indepedent of previous state of fetchCounts
  2858. _console.log(2908, 10, res)
  2859. if (!res) return;
  2860. fetchCounts.count = res.length;
  2861. //if(fetchCounts.new && !document.documentElement.contains(fetchCounts.new.elm)) fetchCounts.new = null;
  2862. //if(fetchCounts.base && !document.documentElement.contains(fetchCounts.base.elm)) fetchCounts.base = null;
  2863. if (fetchCounts.new) return;
  2864. let newFound = res.newFound;
  2865. if (!newFound) {
  2866. if (res.length === 1) {
  2867. fetchCounts.base = res[0];
  2868. return false;
  2869. }
  2870. } else if (res.length === 1) {
  2871. fetchCounts.new = res[0];
  2872. return true;
  2873. } else if (res.length > 1) {
  2874. for (const entry of res) {
  2875. if (entry.isLatest === true && entry.isNew) {
  2876. fetchCounts.new = entry;
  2877. return true;
  2878. }
  2879. }
  2880. }
  2881. }
  2882.  
  2883. const domInit_comments = () => {
  2884.  
  2885.  
  2886. let comments = document.querySelector(`ytd-comments#comments:not([o3r-${sa_comments}])`);
  2887. if (!comments) return;
  2888.  
  2889. // once per {ytd-comments#comments} detection
  2890.  
  2891. let ytdFlexyElm = kRef(ytdFlexy);
  2892. if (!scriptEnable || !ytdFlexyElm) return;
  2893.  
  2894. _console.log(3901)
  2895.  
  2896. if (mtoVisibility_Comments.bindElement(comments)) {
  2897. mtoVisibility_Comments.observer.check(9);
  2898. }
  2899.  
  2900.  
  2901. };
  2902.  
  2903. const domInit_teaserInfo = () => {
  2904. //obsolete?
  2905.  
  2906. let teaserInfo = document.querySelector('#description-and-actions.style-scope.ytd-watch-metadata > #description ytd-text-inline-expander:not([tabview-removed-duplicate])');
  2907.  
  2908. if (!teaserInfo) return;
  2909.  
  2910. // for Teaser UI
  2911. // once per {#description-and-actions.style-scope.ytd-watch-metadata > #description > ytd-text-inline-expander} detection
  2912.  
  2913. let ytdFlexyElm = kRef(ytdFlexy);
  2914. if (!scriptEnable || !ytdFlexyElm) return;
  2915. let addedInfo = document.querySelector('#tab-info ytd-expander[tabview-info-expander]');
  2916.  
  2917. if (!addedInfo) return;
  2918.  
  2919. if (!document.documentElement.getAttribute('tabview-unwrapjs')) return;
  2920.  
  2921. _console.log(3903)
  2922.  
  2923. teaserInfo.setAttribute('tabview-removed-duplicate', '')
  2924.  
  2925.  
  2926. teaserInfo.dispatchEvent(new CustomEvent('tabview-no-duplicate-info'))
  2927. //console.log(56)
  2928.  
  2929.  
  2930.  
  2931. }
  2932.  
  2933. function setOnlyOneEPanel(ePanel) {
  2934.  
  2935. layoutStatusMutex.lockWith(unlock => {
  2936.  
  2937. let cPanels = engagement_panels_();
  2938. for (const entry of cPanels.list) {
  2939. if (entry.ePanel != ePanel && entry.visible) ytBtnCloseEngagementPanel(entry.ePanel);
  2940. }
  2941. setTimeout(unlock, 30)
  2942.  
  2943. })
  2944.  
  2945. }
  2946.  
  2947.  
  2948. function goYoutubeGeniusLyrics() {
  2949.  
  2950. setTimeout(function $f() {
  2951.  
  2952. if (!document.documentElement.getAttribute('tabview-unwrapjs')) return setTimeout($f, 100);
  2953. document.documentElement.dispatchEvent(new CustomEvent('engagement-panel-genius-lyrics'));
  2954.  
  2955. }, 100);
  2956.  
  2957. }
  2958.  
  2959. const FP = {
  2960.  
  2961. mtoBodyF: ( /** @type {MutationRecord[]} */ mutations, /** @type {MutationObserver} */ observer) => {
  2962. //subtree DOM mutation checker - {body} \single \subtree
  2963.  
  2964. if (!scriptEnable) return;
  2965. if (pageType !== 'watch') return;
  2966.  
  2967. for (const mutation of mutations) {
  2968. for (const addedNode of mutation.addedNodes) {
  2969. if (addedNode.nodeType === 1) {
  2970. if (addedNode.nodeName == "DIV" && addedNode.matches('.autocomplete-suggestion:not([autocomplete-disable-updatesc])')) {
  2971. mtf_fixAutoCompletePosition(addedNode)
  2972. } else if (addedNode.nodeName == "DIV" && (addedNode.id === 'lyricscontainer' || addedNode.id === 'showlyricsbutton')) {
  2973.  
  2974. goYoutubeGeniusLyrics();
  2975.  
  2976. }
  2977. }
  2978. }
  2979. }
  2980.  
  2981. },
  2982.  
  2983. mtf_attrPlaylist: (attrName, newValue) => {
  2984. //attr mutation checker - {ytd-playlist-panel-renderer#playlist} \single
  2985. //::attr ~ hidden
  2986. //console.log(1210)
  2987.  
  2988. _console.log(21311)
  2989. if (!scriptEnable) return;
  2990. if (pageType !== 'watch') return;
  2991. /** @type {HTMLElement|null} */
  2992. let cssElm = kRef(ytdFlexy);
  2993. if (!cssElm) return;
  2994.  
  2995. _console.log(21312)
  2996.  
  2997. let playlist = document.querySelector('#tab-list ytd-playlist-panel-renderer#playlist'); // can be null if it is manually triggered
  2998. let isAnyPlaylistExist = playlist && !playlist.hasAttribute('hidden');
  2999. const tabBtn = document.querySelector('[userscript-tab-content="#tab-list"]');
  3000. //console.log(1212.2, isPlaylistHidden, playlist.getAttribute('hidden'))
  3001. if (tabBtn) {
  3002. //console.log('attr playlist changed')
  3003. let isPlaylistTabHidden = tabBtn.classList.contains('tab-btn-hidden')
  3004. if (isPlaylistTabHidden && isAnyPlaylistExist) {
  3005. //console.log('attr playlist changed - no hide')
  3006. tabBtn.classList.remove("tab-btn-hidden");
  3007. } else if (!isPlaylistTabHidden && !isAnyPlaylistExist) {
  3008. //console.log('attr playlist changed - add hide')
  3009. hideTabBtn(tabBtn);
  3010. }
  3011. }
  3012. /* visible layout for triggering hidden removal */
  3013. },
  3014. mtf_attrComments: (attrName, newValue) => {
  3015. //attr mutation checker - {ytd-comments#comments} \single
  3016. //::attr ~ hidden
  3017.  
  3018. // *** consider this can happen immediately after pop state. timeout / interval might clear out.
  3019.  
  3020. renderDeferred.resolved && resultCommentsCountCaching(innerDOMCommentsCountLoader());
  3021. // this is triggered by mutationobserver, the comment count update might have ouccred
  3022.  
  3023. if (pageType !== 'watch') return;
  3024.  
  3025. let comments = document.querySelector('ytd-comments#comments')
  3026. const tabBtn = document.querySelector('[userscript-tab-content="#tab-comments"]');
  3027. if (!comments || !tabBtn) return;
  3028. let isCommentHidden = comments.hasAttribute('hidden')
  3029. //console.log('attr comments changed')
  3030.  
  3031.  
  3032. let ytdFlexyElm = kRef(ytdFlexy);
  3033. if (!scriptEnable || !ytdFlexyElm) return;
  3034.  
  3035. if (mtf_forceCheckLiveVideo_disable === 2) {
  3036.  
  3037. } else if (!isCommentHidden) {
  3038.  
  3039. ytdFlexyElm.setAttribute('tyt-comments', 'Kv');
  3040. if(!fetchCounts.fetched){
  3041. emptyCommentSection();
  3042. }
  3043. //_console.log(9360, 71);
  3044. tabBtn.classList.remove("tab-btn-hidden") //if contains
  3045.  
  3046. } else if (isCommentHidden) {
  3047.  
  3048. ytdFlexyElm.setAttribute('tyt-comments', 'Kh');
  3049. if (pageType === 'watch' && Q.comments_section_loaded === 1) {
  3050. emptyCommentSection();
  3051. _console.log(9360, 72);
  3052. }
  3053.  
  3054. }
  3055.  
  3056.  
  3057. },
  3058.  
  3059.  
  3060. mtf_attrChatroom: (attrName, newValue) => {
  3061. //attr mutation checker - {ytd-live-chat-frame#chat} \single
  3062. //::attr ~ collapsed
  3063.  
  3064.  
  3065.  
  3066. let ytdFlexyElm = kRef(ytdFlexy);
  3067. if (!scriptEnable || !ytdFlexyElm) return;
  3068. if (pageType !== 'watch') return;
  3069.  
  3070.  
  3071. setToggleBtnTxt();
  3072.  
  3073.  
  3074. layoutStatusMutex.lockWith(unlock => {
  3075.  
  3076. const chatBlock = document.querySelector('ytd-live-chat-frame#chat')
  3077. /** @type {HTMLElement | null} */
  3078. const cssElm = kRef(ytdFlexy)
  3079.  
  3080. if (!chatBlock || !cssElm) {
  3081. unlock();
  3082. return;
  3083. }
  3084.  
  3085. if (pageType !== 'watch') {
  3086. unlock();
  3087. return;
  3088. }
  3089.  
  3090. let newAttrV = '';
  3091. //mtf_attrChatroom => chat exist => tyt-chat non-null
  3092. let isCollapsed = !!chatBlock.hasAttribute('collapsed');
  3093.  
  3094. let currentAttr = cssElm.getAttribute('tyt-chat');
  3095.  
  3096. if (currentAttr !== null) { //string // [+-]?[az]+[az\$]+
  3097. let isPlusMinus = currentAttr.charCodeAt(0) < 46; // 43 OR 45
  3098. if (isPlusMinus) newAttrV = currentAttr.substring(1);
  3099. }
  3100.  
  3101. if(isCollapsed) newAttrV = `-${newAttrV}`;
  3102. if(!isCollapsed) newAttrV = `+${newAttrV}`;
  3103.  
  3104. wAttr(cssElm, 'tyt-chat', newAttrV);
  3105.  
  3106.  
  3107.  
  3108. if (typeof newAttrV === 'string' && !isCollapsed) lstTab.lastPanel = '#chatroom';
  3109.  
  3110. if (!isCollapsed && document.querySelector('#right-tabs .tab-btn.active') && isWideScreenWithTwoColumns() && !isTheater()) {
  3111. switchTabActivity(null);
  3112. timeline.setTimeout(unlock, 40);
  3113. } else {
  3114. unlock();
  3115. }
  3116.  
  3117. if (isCollapsed) {
  3118. chatBlock.removeAttribute('yt-userscript-iframe-loaded');
  3119. }
  3120.  
  3121. })
  3122.  
  3123.  
  3124.  
  3125.  
  3126. },
  3127.  
  3128. mtf_attrEngagementPanel: ( /** @type {MutationRecord[]} */ mutations, /** @type {MutationObserver} */ observer) => {
  3129. //attr mutation checker - {ytd-engagement-panel-section-list-renderer} \mutiple
  3130. //::attr ~ visibility
  3131.  
  3132. let ytdFlexyElm = kRef(ytdFlexy);
  3133. if (!scriptEnable || !ytdFlexyElm) return;
  3134.  
  3135. //multiple instance
  3136. if (mutations) {
  3137. for (const mutation of mutations) {
  3138. let ePanel = mutation.target;
  3139. if (ePanel.getAttribute('visibility') == 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED') {
  3140. setOnlyOneEPanel(ePanel);
  3141. break;
  3142. }
  3143. }
  3144. }
  3145. if (pageType !== 'watch') return;
  3146.  
  3147. layoutStatusMutex.lockWith(unlock => {
  3148.  
  3149. const ePanel = document.querySelector('ytd-watch-flexy ytd-engagement-panel-section-list-renderer')
  3150. const cssElm = kRef(ytdFlexy)
  3151.  
  3152. if (!ePanel || !cssElm) {
  3153. unlock();
  3154. return;
  3155. }
  3156. let previousValue = +cssElm.getAttribute('tyt-ep-visible') || 0;
  3157.  
  3158. let { shownRes, value, count } = engagement_panels_();
  3159. let nextValue = value;
  3160. let nextCount = count;
  3161.  
  3162.  
  3163. if (nextCount == 0 && cssElm.hasAttribute('tyt-ep-visible')) {
  3164. storeLastPanel = null;
  3165. wAttr(cssElm, 'tyt-ep-visible', false);
  3166. unlock();
  3167. } else {
  3168.  
  3169. if ((nextCount > 1) || (cssElm.hasAttribute('tyt-ep-visible') && previousValue === nextValue)) {
  3170. unlock();
  3171. return;
  3172. }
  3173.  
  3174. cssElm.setAttribute('tyt-ep-visible', nextValue);
  3175.  
  3176. let b = false;
  3177. if (previousValue != nextValue && nextValue > 0) {
  3178. lstTab.lastPanel = `#engagement-panel-${nextValue}`;
  3179. b = true;
  3180. storeLastPanel = mWeakRef(shownRes[0])
  3181. //console.log(9999, shownRes[0])
  3182. }
  3183.  
  3184. if (b && document.querySelector('#right-tabs .tab-btn.active') && isWideScreenWithTwoColumns() && !isTheater()) {
  3185. switchTabActivity(null);
  3186. timeline.setTimeout(unlock, 40);
  3187. } else {
  3188. unlock();
  3189. }
  3190. }
  3191.  
  3192. })
  3193.  
  3194.  
  3195.  
  3196.  
  3197. },
  3198.  
  3199. mtf_initalAttr_chatroom: () => {
  3200. // every per [new] {ytd-live-chat-frame#chat} detection - reset after mini-playview
  3201.  
  3202. let ytdFlexyElm = kRef(ytdFlexy);
  3203. if (!scriptEnable || !ytdFlexyElm) return true;
  3204.  
  3205. const rootElement = ytdFlexyElm;
  3206.  
  3207.  
  3208. if (!mgChatFrame.inPage()) {
  3209.  
  3210. mtoVisibility_Chatroom.clear(true);
  3211.  
  3212. let chatroom = querySelectorFromAnchor.call(rootElement, `ytd-live-chat-frame#chat:not([${sa_chatroom}]`)
  3213. if (chatroom) {
  3214. mgChatFrame.setVar(chatroom);
  3215.  
  3216. //console.log(124,chatroom)
  3217. if (mtoVisibility_Chatroom.bindElement(chatroom)) {
  3218. mtoVisibility_Chatroom.observer.check(9)
  3219.  
  3220. }
  3221.  
  3222. chatroom = null
  3223. }
  3224. }
  3225. return true;
  3226.  
  3227. },
  3228.  
  3229.  
  3230.  
  3231.  
  3232.  
  3233.  
  3234. }
  3235.  
  3236.  
  3237.  
  3238. async function isChildElementNodesEqual(pNodeA, pNodeB){
  3239.  
  3240. let cNodeA = pNodeA.firstElementChild;
  3241. let cNodeB = pNodeB.firstElementChild;
  3242.  
  3243. do {
  3244. if (cNodeA !== null && cNodeB !== null) {
  3245. await Promise.resolve(0);
  3246. let isEqual = cNodeA.isEqualNode(cNodeB)
  3247. if (!isEqual) return ({ res: false, pNodeA, pNodeB });
  3248. cNodeA = cNodeA.nextElementSibling;
  3249. cNodeB = cNodeB.nextElementSibling;
  3250. } else {
  3251. let res = (cNodeA === null && cNodeB === null);
  3252. return ({ res, pNodeA, pNodeB });
  3253. }
  3254. } while (true);
  3255.  
  3256.  
  3257. }
  3258.  
  3259. async function isChildTextNodesEqual(pNodeA, pNodeB){
  3260.  
  3261. let cNodeA = pNodeA.firstChild;
  3262. let cNodeB = pNodeB.firstChild;
  3263.  
  3264. do {
  3265. if (cNodeA !== null && cNodeB !== null) {
  3266. let isEqual;
  3267. if(cNodeA.nodeType===1 && cNodeB.nodeType===1){
  3268. let m = await isChildElementNodesEqual(cNodeA, cNodeB);
  3269. isEqual = m.res;
  3270. }else{
  3271. await Promise.resolve(0);
  3272. isEqual = cNodeA.isEqualNode(cNodeB)
  3273. }
  3274. if (!isEqual) return ({ res: false, pNodeA, pNodeB });
  3275. cNodeA = cNodeA.nextSibling;
  3276. cNodeB = cNodeB.nextSibling;
  3277. } else {
  3278. let res = (cNodeA === null && cNodeB === null);
  3279. return ({ res, pNodeA, pNodeB });
  3280. }
  3281. } while (true);
  3282.  
  3283.  
  3284. }
  3285.  
  3286. function variableResets() {
  3287.  
  3288. videoListBeforeSearch = null;
  3289. _cachedLastVideo = null;
  3290.  
  3291. lstTab =
  3292. {
  3293. lastTab: null, //tab-xxx
  3294. lastPanel: null,
  3295. last: null
  3296. };
  3297.  
  3298. //displayedPlaylist = null;
  3299. scriptEnable = false;
  3300. ytdFlexy = null;
  3301. wls.layoutStatus = 0;
  3302.  
  3303. mtoVisibility_EngagementPanel.clear(true)
  3304. mtoVisibility_Playlist.clear(true)
  3305. mtoVisibility_Comments.clear(true)
  3306.  
  3307. mgChatFrame.kVar = null;
  3308. mtoVisibility_Chatroom.clear(true)
  3309. mtoFlexyAttr.clear(true)
  3310.  
  3311.  
  3312. for (const elem of document.querySelectorAll('ytd-expander[tabview-info-expander]')) {
  3313. elem.removeAttribute('tabview-info-expander');
  3314. }
  3315.  
  3316. mtoMutation_body.clear(true)
  3317. mtf_chatBlockQ = null;
  3318.  
  3319. }
  3320.  
  3321.  
  3322.  
  3323.  
  3324.  
  3325. function getWord(tag) {
  3326. return langWords[pageLang][tag] || langWords['en'][tag] || '';
  3327. }
  3328.  
  3329.  
  3330. function getTabsHTML() {
  3331.  
  3332. const sTabBtnVideos = `${svgElm(16, 16, 90, 90, svgVideos)}<span>${getWord('videos')}</span>`;
  3333. const sTabBtnInfo = `${svgElm(16, 16, 60, 60, svgInfo)}<span>${getWord('info')}</span>`;
  3334. const sTabBtnPlayList = `${svgElm(16, 16, 20, 20, svgPlayList)}<span>${getWord('playlist')}</span>`;
  3335.  
  3336. let str1 = `
  3337. <paper-ripple class="style-scope yt-icon-button">
  3338. <div id="background" class="style-scope paper-ripple" style="opacity:0;"></div>
  3339. <div id="waves" class="style-scope paper-ripple"></div>
  3340. </paper-ripple>
  3341. `;
  3342.  
  3343. let str_fbtns = `
  3344. <div class="font-size-right">
  3345. <div class="font-size-btn font-size-plus">
  3346. <svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet"
  3347. stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">
  3348. <path d="M12 25H38M25 12V38"/>
  3349. </svg>
  3350. </div><div class="font-size-btn font-size-minus">
  3351. <svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet"
  3352. stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">
  3353. <path d="M12 25h26"/>
  3354. </svg>
  3355. </div>
  3356. </div>
  3357. `.replace(/[\r\n]+/g, '');
  3358.  
  3359. const str_tabs = [
  3360. `<a id="tab-btn1" data-name="info" userscript-tab-content="#tab-info" class="tab-btn">${sTabBtnInfo}${str1}${str_fbtns}</a>`,
  3361. `<a id="tab-btn3" userscript-tab-content="#tab-comments" data-name="comments" class="tab-btn">${svgElm(16, 16, 120, 120, svgComments)}<span id="tyt-cm-count"></span>${str1}${str_fbtns}</a>`,
  3362. `<a id="tab-btn4" userscript-tab-content="#tab-videos" data-name="videos" class="tab-btn">${sTabBtnVideos}${str1}${str_fbtns}</a>`,
  3363. `<a id="tab-btn5" userscript-tab-content="#tab-list" class="tab-btn tab-btn-hidden">${sTabBtnPlayList}${str1}${str_fbtns}</a>`
  3364. ].join('');
  3365.  
  3366. let addHTML = `
  3367. <div id="right-tabs">
  3368. <tabview-tabs-header-position></tabview-tabs-header-position>
  3369. <header>
  3370. <div id="material-tabs">
  3371. ${str_tabs}
  3372. </div>
  3373. </header>
  3374. <div class="tab-content">
  3375. <div id="tab-info" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  3376. <div id="tab-comments" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  3377. <div id="tab-videos" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  3378. <div id="tab-list" class="tab-content-cld tab-content-hidden" userscript-scrollbar-render></div>
  3379. </div>
  3380. </div>
  3381. `;
  3382.  
  3383. return addHTML;
  3384.  
  3385. }
  3386.  
  3387. function getLang() {
  3388.  
  3389. let lang = 'en';
  3390. let htmlLang = ((document || 0).documentElement || 0).lang || '';
  3391. switch (htmlLang) {
  3392. case 'en':
  3393. case 'en-GB':
  3394. lang = 'en';
  3395. break;
  3396. case 'de':
  3397. case 'de-DE':
  3398. lang = 'du';
  3399. break;
  3400. case 'fr':
  3401. case 'fr-CA':
  3402. case 'fr-FR':
  3403. lang = 'fr';
  3404. break;
  3405. case 'zh-Hant':
  3406. case 'zh-Hant-HK':
  3407. case 'zh-Hant-TW':
  3408. lang = 'tw';
  3409. break;
  3410. case 'zh-Hans':
  3411. case 'zh-Hans-CN':
  3412. lang = 'cn';
  3413. break;
  3414. case 'ja':
  3415. case 'ja-JP':
  3416. lang = 'jp';
  3417. break;
  3418. case 'ko':
  3419. case 'ko-KR':
  3420. lang = 'kr';
  3421. break;
  3422. default:
  3423. lang = 'en';
  3424. }
  3425.  
  3426. if (langWords[lang]) pageLang = lang; else pageLang = 'en';
  3427.  
  3428. }
  3429.  
  3430. // function checkEvtTarget(evt, nodeNames) {
  3431. // return nodeNames.includes((((evt || 0).target || 0).nodeName || 0));
  3432. // }
  3433.  
  3434. function pageCheck() {
  3435. // yt-player-updated
  3436. // yt-page-data-updated
  3437. // yt-watch-comments-ready - omitted
  3438. // [is-two-columns_] attr changed => layout changed
  3439.  
  3440. domInit_teaserInfo() // YouTube obsoleted feature?
  3441.  
  3442. mtf_append_comments();
  3443.  
  3444. mtf_append_playlist(null); // playlist relocated after layout changed
  3445. fixTabs();
  3446. mtf_AfterFixTabs();
  3447.  
  3448. }
  3449.  
  3450. function globalHook(eventType, func) {
  3451. if (!func) return;
  3452.  
  3453. const count = (globalHook_hashs[eventType] || 0) + 1;
  3454.  
  3455. globalHook_hashs[eventType] = count;
  3456.  
  3457. const s = globalHook_symbols[count - 1] || (globalHook_symbols[count - 1] = Symbol());
  3458.  
  3459. document.addEventListener(eventType, function (evt) {
  3460. if (evt[s]) return;
  3461. evt[s] = true;
  3462. new Promise(() => {
  3463. func(evt);
  3464. })
  3465.  
  3466. }, capturePassive)
  3467.  
  3468. }
  3469.  
  3470. async function makeHeaderFloat(){
  3471. if(isMakeHeaderFloatCalled) return;
  3472. isMakeHeaderFloatCalled = true;
  3473. await Promise.resolve(0);
  3474.  
  3475. const [header, headerP, navElm] = await Promise.all([
  3476. new Promise(f=>f(document.querySelector("#right-tabs header"))),
  3477. new Promise(f=>f(document.querySelector("#right-tabs tabview-tabs-header-position"))),
  3478. new Promise(f=>f(document.querySelector('#masthead-container, #masthead')))
  3479. ]);
  3480.  
  3481. let ito_dt = 0;
  3482. let ito = new IntersectionObserver((entries) => {
  3483.  
  3484. let xyStatus = null;
  3485.  
  3486. //console.log(entries);
  3487. let xRect = null;
  3488. let rRect = null;
  3489.  
  3490. for (const entry of entries) {
  3491. if (!entry.boundingClientRect || !entry.rootBounds) continue; // disconnected from DOM tree
  3492. if (!entry.isIntersecting && entry.boundingClientRect.y <= entry.rootBounds.top && entry.boundingClientRect.y < entry.rootBounds.bottom) {
  3493. xyStatus = 2;
  3494. xRect= entry.boundingClientRect;
  3495. rRect=entry.rootBounds;
  3496. } else if (entry.isIntersecting && entry.boundingClientRect.y >= entry.rootBounds.top && entry.boundingClientRect.y < entry.rootBounds.bottom) {
  3497. xyStatus = 1;
  3498. xRect= entry.boundingClientRect;
  3499. rRect=entry.rootBounds;
  3500. }
  3501. }
  3502. let p = wls.layoutStatus;
  3503. //console.log(document.documentElement.clientWidth)
  3504. if (xyStatus !== null) {
  3505.  
  3506. if (xyStatus === 2 && isStickyHeaderEnabled === true) {
  3507.  
  3508. } else if (xyStatus === 1 && isStickyHeaderEnabled === false) {
  3509.  
  3510. } else {
  3511. singleColumnScrolling2(xyStatus, xRect.width, {
  3512. left: xRect.left,
  3513. right: rRect.width - xRect.right
  3514. });
  3515. }
  3516.  
  3517. }
  3518.  
  3519. let tdt = Date.now();
  3520. ito_dt= tdt;
  3521. setTimeout(() => {
  3522. if(ito_dt !==tdt ) return;
  3523. if (p !== wls.layoutStatus) singleColumnScrolling();
  3524. }, 300)
  3525.  
  3526. },
  3527. {
  3528. rootMargin:`0px 0px 0px 0px`,
  3529. threshold: [0]
  3530. })
  3531.  
  3532. ito.observe(headerP)
  3533.  
  3534. }
  3535. function checkPlaylistForInitialization(){
  3536. // if the page url is with playlist; renderer event might not occur.
  3537.  
  3538. // playlist already added to dom; this is to set the visibility event and change hidden status
  3539.  
  3540. let m_playlist = document.querySelector(`#tab-list ytd-playlist-panel-renderer#playlist:not([o3r-${sa_playlist}])`)
  3541.  
  3542. // once per {ytd-playlist-panel-renderer#playlist} detection
  3543.  
  3544. _console.log(3902, !!m_playlist)
  3545.  
  3546. let ytdFlexyElm = kRef(ytdFlexy);
  3547. if (!scriptEnable || !ytdFlexyElm) { }
  3548. else if (m_playlist) {
  3549.  
  3550. if (mtoVisibility_Playlist.bindElement(m_playlist)) {
  3551. mtoVisibility_Playlist.observer.check(9); //delay check required for browser bug - hidden changed not triggered
  3552. }
  3553. m_playlist = null;
  3554.  
  3555. }
  3556.  
  3557. FP.mtf_attrPlaylist();
  3558. Promise.resolve(0).then(()=>{
  3559. // ['tab-btn', 'tab-btn', 'tab-btn active', 'tab-btn tab-btn-hidden']
  3560. // bug
  3561. let ytdFlexyElm = kRef(ytdFlexy);
  3562. if (!scriptEnable || !ytdFlexyElm) return;
  3563. if (!switchTabActivity_lastTab && (ytdFlexyElm.getAttribute('tyt-tab') + '').indexOf('#tab-') === 0 && /https\:\/\/www\.youtube\.com\/watch.*[\?\&]list=[\w\-\_]+/.test(location.href)) {
  3564. if (setToActiveTab('#tab-list')) switchTabActivity_lastTab = '#tab-list';
  3565. }
  3566. })
  3567.  
  3568. }
  3569.  
  3570.  
  3571.  
  3572.  
  3573.  
  3574.  
  3575.  
  3576. let generalLog901 = !DEBUG_LOG ? 0 : (evt) => {
  3577. _console.log(901, evt.type)
  3578. }
  3579.  
  3580.  
  3581.  
  3582. const _pageBeingInit = function () {
  3583.  
  3584. pageSession.inc();
  3585. if(pageSession.sid>9e9) pageSession.sid=9;
  3586. fetchCounts = {
  3587. base: null,
  3588. new: null,
  3589. fetched: false,
  3590. count: null
  3591. }
  3592. pageFetchedData = null;
  3593. chatroomDetails = null;
  3594. }
  3595.  
  3596. const pageBeingInit = function () {
  3597. // call regardless pageType
  3598. if (tabsDeferred.resolved) {
  3599. comments_loader = 1;
  3600. tabsDeferred.reset();
  3601. renderIdentifier++;
  3602. if (renderIdentifier > 88) renderIdentifier = 9;
  3603. renderDeferred.reset();
  3604. if(!(firstLoadStatus&8)){
  3605. //console.log(3446)
  3606. innerDOMCommentsCountLoader(); //ensure the previous record is saved
  3607. // no need to cache to the rendering state
  3608. //console.log(3447)
  3609. _pageBeingInit();
  3610. }else if(firstLoadStatus&2){
  3611. firstLoadStatus-=2;
  3612. script_inject_js1.inject();
  3613. }
  3614. _console.log('pageBeingInit', firstLoadStatus)
  3615. }else if(renderDeferred.resolved){
  3616. // in case , rarely, tabsDeferred not yet resolved but animateLoadDeferred resolved
  3617.  
  3618. renderIdentifier++;
  3619. if (renderIdentifier > 88) renderIdentifier = 9;
  3620. renderDeferred.reset();
  3621. }
  3622.  
  3623.  
  3624. if(pageRendered===2){
  3625. let elmPL = document.querySelector('tabview-page-loader');
  3626. elmPL.remove();
  3627. pageRendered=0;
  3628. }
  3629.  
  3630. };
  3631.  
  3632. const advanceFetch = async function () {
  3633. if (pageType === 'watch' && !fetchCounts.new && !fetchCounts.fetched) {
  3634. renderDeferred.resolved && resultCommentsCountCaching(innerDOMCommentsCountLoader());
  3635. if (renderDeferred.resolved && !fetchCounts.new) {
  3636. window.dispatchEvent(new Event("scroll"));
  3637. }
  3638. }
  3639. };
  3640. /*
  3641.  
  3642. let _prevComments = null;
  3643. handleDOMAppear('commentsSectionAppended', ()=>{
  3644. console.log('comments section appended')
  3645. advanceFetch();
  3646. })
  3647.  
  3648. handleDOMAppear('commentsHeaderAppended1',(evt)=>{
  3649. console.log(434)
  3650. let tmp = kRef(_prevComments);
  3651. if(tmp) tmp.classList.remove('tyt-ani')
  3652. evt.target.classList.add('tyt-ani')
  3653. _prevComments=mWeakRef(evt.target);
  3654. console.log(3434,evt.target)
  3655. })
  3656. */
  3657.  
  3658.  
  3659. function getFinalComments() {
  3660.  
  3661. if ((comments_loader & 3) === 3) { } else return;
  3662. comments_loader = 0;
  3663.  
  3664. let ei = 0;
  3665.  
  3666. function execute(){
  3667. //sync -> animateLoadDeferred.resolved always true
  3668.  
  3669. if(!renderDeferred.resolved) return;
  3670.  
  3671. _console.log(2323)
  3672.  
  3673. if (Q.comments_section_loaded !== 0) return;
  3674. if (fetchCounts.fetched) return;
  3675.  
  3676.  
  3677. let ret = innerDOMCommentsCountLoader();
  3678. resultCommentsCountCaching(ret);
  3679. if (fetchCounts.new && !fetchCounts.fetched) {
  3680. _console.log(4512, 4, Q.comments_section_loaded, fetchCounts.new, !fetchCounts.fetched)
  3681. if(fetchCounts.new.f()){
  3682. fetchCounts.fetched = true;
  3683. _console.log(9972, 'fetched = true')
  3684. fetchCommentsFinished();
  3685. }
  3686.  
  3687. return;
  3688. }
  3689.  
  3690.  
  3691.  
  3692. ei++;
  3693.  
  3694. if (fetchCounts.base && !fetchCounts.new && !fetchCounts.fetched && fetchCounts.count === 1) {
  3695.  
  3696.  
  3697. let elm = kRef(fetchCounts.base.elm);
  3698. let txt = elm ? getCountHText(elm) : null;
  3699. let condi1 = ei > 7;
  3700. let condi2 = txt === m_last_count;
  3701. if (condi1 || condi2) {
  3702.  
  3703. if (fetchCounts.base.f()) {
  3704. fetchCounts.fetched = true;
  3705. _console.log(9972, 'fetched = true')
  3706. //return true;
  3707. fetchCommentsFinished();
  3708. }
  3709.  
  3710. }
  3711.  
  3712. }
  3713.  
  3714. if (!fetchCounts.fetched) {
  3715. if (ei > 7) {
  3716. let elm = ret.length === 1 ? kRef(ret[0].elm) : null;
  3717. let txt = elm ? getCountHText(elm) : null;
  3718. if (elm && txt !== m_last_count) {
  3719. fetchCounts.base = null;
  3720. fetchCounts.new = ret[0];
  3721. fetchCounts.new.f();
  3722. fetchCounts.fetched = true;
  3723. _console.log(9979, 'fetched = true')
  3724. fetchCommentsFinished();
  3725. }
  3726. return;
  3727. }
  3728. return true;
  3729. }
  3730.  
  3731.  
  3732.  
  3733. }
  3734.  
  3735. async function alCheckFn (ks){
  3736. let alCheckCount = 9;
  3737. let alCheckInterval = 420;
  3738. do {
  3739.  
  3740. if (renderIdentifier !== ks) break;
  3741. if (alCheckCount === 0) break;
  3742. if(execute()!==true) break;
  3743. --alCheckCount;
  3744.  
  3745. await new Promise(r => setTimeout(r, alCheckInterval));
  3746.  
  3747. } while (true)
  3748.  
  3749. }
  3750. let ks = renderIdentifier;
  3751. renderDeferred.debounce(()=>{
  3752.  
  3753. alCheckFn(ks);
  3754.  
  3755. });
  3756.  
  3757.  
  3758.  
  3759. }
  3760.  
  3761.  
  3762.  
  3763.  
  3764. let g_check_detail_A=0;
  3765. let checkDuplicateRes = null;
  3766. function checkDuplicatedInfo_then(isCheck){
  3767.  
  3768. const ytdFlexyElm = kRef(ytdFlexy)
  3769. if (!ytdFlexyElm) return; //unlikely
  3770.  
  3771. let cssbool_c1 = false, cssbool_c2= false, cssbool_c3= false;
  3772. if(isCheck === 5 && ytdFlexyElm.matches('.tabview-info-duplicated[flexy]')){
  3773. cssbool_c1 = !!querySelectorFromAnchor.call(ytdFlexyElm, '#description.style-scope.ytd-watch-metadata > #description-inner:only-child');
  3774. cssbool_c2 = !!querySelectorFromAnchor.call(ytdFlexyElm, '#tab-info ytd-expander #description.ytd-video-secondary-info-renderer');
  3775. cssbool_c2 = !!querySelectorFromAnchor.call(ytdFlexyElm, '#tab-info ytd-expander ytd-rich-metadata-renderer.ytd-rich-metadata-row-renderer');
  3776. }
  3777.  
  3778. ytdFlexyElm.setAttribute('tyt-has', `${cssbool_c1?'A':'a'}${cssbool_c2?'B':'b'}${cssbool_c3?'C':'c'}`);
  3779.  
  3780.  
  3781. }
  3782. function checkDuplicatedInfo(){
  3783.  
  3784.  
  3785. async function checkEqual_bb(desc1, desc2){
  3786. // basically desc1 and desc2 are content identical
  3787. // however, class name order could be different
  3788. let txt1 = new Promise(r=>r(desc1.textContent))
  3789.  
  3790. let txt2 = new Promise(r=>r(desc2.textContent))
  3791.  
  3792.  
  3793. let [res1, res2] = await Promise.all([txt1, txt2]);
  3794.  
  3795. return {res: res1===res2}
  3796. }
  3797. async function checkEqual_B(){
  3798.  
  3799. const ytdFlexyElm = kRef(ytdFlexy)
  3800. if (!ytdFlexyElm) return; //unlikely
  3801.  
  3802. let t = Date.now();
  3803. g_check_detail_A=t;
  3804.  
  3805. ytdFlexyElm.classList.toggle('tabview-info-duplicated', true) // hide first;
  3806.  
  3807. await new Promise(resolve=>setTimeout(resolve,1)); // mcrcr might be not yet initalized
  3808.  
  3809.  
  3810. if(g_check_detail_A!==t) return;
  3811. // the class added before can be removed from the external coding
  3812.  
  3813. function mrcrf(mrcr) {
  3814. let tmp;
  3815. if (mrcr) {
  3816. if (tmp = querySelectorFromAnchor.call(mrcr, '#always-shown[hidden]:empty')) tmp.removeAttribute('hidden')
  3817. if (tmp = querySelectorFromAnchor.call(mrcr, '#collapsible[hidden]:empty')) tmp.removeAttribute('hidden')
  3818. }
  3819. }
  3820.  
  3821. let mrcr1 = document.querySelector('ytd-watch-metadata.ytd-watch-flexy[modern-metapanel] > ytd-metadata-row-container-renderer.style-scope.ytd-watch-metadata')
  3822. mrcrf(mrcr1);
  3823. await Promise.resolve(0);
  3824. let mrcr2 = document.querySelector('ytd-expander.ytd-video-secondary-info-renderer ytd-metadata-row-container-renderer.style-scope.ytd-video-secondary-info-renderer')
  3825. mrcrf(mrcr2);
  3826. await Promise.resolve(0);
  3827. let desc1 = null;
  3828. let desc2 = document.querySelector('ytd-expander.ytd-video-secondary-info-renderer #description.style-scope.ytd-video-secondary-info-renderer > yt-formatted-string.content.style-scope.ytd-video-secondary-info-renderer[split-lines]:not(:empty)');
  3829. await Promise.resolve(0);
  3830.  
  3831. let plainText = false;
  3832. if(desc2 && desc2.firstElementChild === null){
  3833. plainText = true;
  3834. desc1 = document.querySelector('ytd-text-inline-expander#description-inline-expander.style-scope.ytd-watch-metadata #plain-snippet-text.ytd-text-inline-expander');
  3835. }
  3836. if(!desc1) desc1 = document.querySelector('ytd-text-inline-expander#description-inline-expander.style-scope.ytd-watch-metadata yt-formatted-string#formatted-snippet-text.style-scope.ytd-text-inline-expander:not(:empty)');
  3837. await Promise.resolve(0);
  3838.  
  3839. if(desc1){
  3840. let parentContainer = desc1.closest('ytd-text-inline-expander#description-inline-expander.ytd-watch-metadata');
  3841. //console.log(3434,querySelectorFromAnchor.call(parentContainer, 'ytd-text-inline-expander#description-inline-expander.style-scope.ytd-watch-metadata yt-formatted-string[split-lines].ytd-text-inline-expander'))
  3842. // hidden
  3843.  
  3844. // example video
  3845. // https://www.youtube.com/watch?v=R65uouhSYJ0
  3846.  
  3847. if (parentContainer) {
  3848.  
  3849. let m = querySelectorFromAnchor.call(parentContainer, 'ytd-text-inline-expander#description-inline-expander.style-scope.ytd-watch-metadata yt-formatted-string[split-lines].ytd-text-inline-expander');
  3850.  
  3851. if (m.hasAttribute('hidden')) {
  3852.  
  3853. let expandBtn = querySelectorFromAnchor.call(parentContainer, 'tp-yt-paper-button#expand.ytd-text-inline-expander:not([hidden])'); if (expandBtn) {
  3854.  
  3855. expandBtn.click();
  3856. await new Promise(r => setTimeout(r, 30));
  3857. if (!m.hasAttribute('hidden')) desc1 = m;
  3858. }
  3859.  
  3860.  
  3861. }
  3862.  
  3863. }
  3864. }
  3865.  
  3866. let infoDuplicated = true;
  3867.  
  3868.  
  3869. let mb1=null, mb2=null;
  3870.  
  3871. if((desc1 === null) ^ (desc2===null) ){
  3872. infoDuplicated = false;
  3873. }else if((mrcr1 === null) ^ (mrcr2===null) ){
  3874. infoDuplicated = false;
  3875. }else{
  3876.  
  3877.  
  3878. await Promise.all([
  3879.  
  3880. (mrcr1 !== mrcr2 && mrcr1 !== null && mrcr2 !== null) ?
  3881. checkEqual_bb(mrcr1,mrcr2).then((o) => {
  3882. //console.log('mrcr', o.res)
  3883. let { res, pNodeA, pNodeB } = o;
  3884. mb1 = res;
  3885.  
  3886. if (res !== true) infoDuplicated = false;
  3887. }) : null,
  3888.  
  3889. (desc1 !== desc2 && desc1 !== null && desc2 !== null) ?
  3890. checkEqual_bb(desc1,desc2).then((o) => {
  3891. //console.log('desc', o.res)
  3892. let { res, pNodeA, pNodeB } = o;
  3893. mb2=res;
  3894.  
  3895. if (res !== true) infoDuplicated = false;
  3896.  
  3897. }) : null
  3898.  
  3899. ]);
  3900.  
  3901. }
  3902.  
  3903. console.log('check-info-duplicate', infoDuplicated, mb1,mb2 )
  3904. //console.log(desc1, desc2, infoDuplicated)
  3905. if(desc1){
  3906. Promise.resolve(desc1).then(desc1=>{
  3907. let snippet = closestDOM.call(desc1, '#snippet.style-scope.ytd-text-inline-expander');
  3908. let dom = snippet || desc1; //desc1 might be hidden and thus cannot trigger interception change
  3909. if(dom) mtoObservationDetails.bindElement(dom);
  3910. })
  3911. }
  3912.  
  3913. if(g_check_detail_A!==t) return;
  3914.  
  3915. //ytdFlexyElm.classList.toggle('tabview-info-duplicated', infoDuplicated)
  3916. checkDuplicateRes= infoDuplicated;
  3917.  
  3918.  
  3919. return 5; // other than 5, duplicated check = false
  3920.  
  3921.  
  3922. };
  3923.  
  3924.  
  3925. return checkEqual_B();
  3926.  
  3927. }
  3928.  
  3929.  
  3930.  
  3931. function findChatFrameDOM() {
  3932.  
  3933. let liveChatFrame = document.querySelector('ytd-live-chat-frame#chat')
  3934. if (liveChatFrame) {
  3935.  
  3936. FP.mtf_initalAttr_chatroom();
  3937.  
  3938. let incorrectChat = document.querySelector('ytd-watch-flexy[theater] ytd-live-chat-frame#chat:not([collapsed])')
  3939. if (incorrectChat) {
  3940. incorrectChat.setAttribute('collapsed', '')
  3941. }
  3942.  
  3943. setToggleBtnTxt() //button might not yet be rendered
  3944. requestAnimationFrame(setToggleBtnTxt)
  3945. }
  3946.  
  3947. }
  3948.  
  3949. function ytMicroEventsInit() {
  3950.  
  3951. _console.log(902)
  3952.  
  3953. //let rendererPageCheck_id = 0;
  3954. globalHook('yt-rendererstamper-finished', (evt) => {
  3955.  
  3956. if (!scriptEnable && tabsDeferred.resolved) { return }
  3957. // might occur before initialization
  3958.  
  3959. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  3960.  
  3961.  
  3962.  
  3963. const nodeName = evt.target.nodeName.toUpperCase();
  3964.  
  3965. // const S_GENERAL_RENDERERS = ['YTD-TOGGLE-BUTTON-RENDERER','YTD-MENU-RENDERER']
  3966. if (S_GENERAL_RENDERERS.includes(nodeName)) {
  3967.  
  3968. //_console.log(evt.target.nodeName, 908, evt.type, document.querySelector('ytd-live-chat-frame#chat'));
  3969.  
  3970. // if (scriptEnable && tabsDeferred.resolved) {
  3971.  
  3972. // //_console.log(782, 1, nodeName)
  3973.  
  3974.  
  3975. // rendererPageCheck_id++;
  3976. // if (rendererPageCheck_id > 999) rendererPageCheck_id = 333;
  3977. // let tmp_id = rendererPageCheck_id;
  3978.  
  3979. // requestAnimationFrame(() => {
  3980. // if (tmp_id !== rendererPageCheck_id) return;
  3981. // pageCheck();
  3982. // })
  3983.  
  3984. // }
  3985.  
  3986. return;
  3987. }
  3988.  
  3989.  
  3990.  
  3991. _console.log(evt.target.nodeName, 904, evt.type, evt.detail);
  3992.  
  3993. let tabsDeferredSess = pageSession.session();
  3994. if (!scriptEnable && tabsDeferred.resolved) { }
  3995. else tabsDeferred.debounce(() => {
  3996.  
  3997. if (!tabsDeferredSess.isValid) return;
  3998.  
  3999. const nodeName = evt.target.nodeName.toUpperCase();
  4000. const node = evt.target
  4001. _console.log(evt.target.nodeName, 905, evt.type, document.querySelector('ytd-live-chat-frame#chat'));
  4002.  
  4003.  
  4004. if (nodeName === 'YTD-PLAYLIST-PANEL-RENDERER') {
  4005. mtf_append_playlist(node);
  4006. // the true playlist is appended to the #tab-list
  4007.  
  4008. checkPlaylistForInitialization();
  4009.  
  4010.  
  4011. } else if (nodeName === 'YTD-WATCH-METADATA') {
  4012.  
  4013.  
  4014. } else if (nodeName === 'YTD-COMMENTS-HEADER-RENDERER') {
  4015.  
  4016. comments_loader = comments_loader | 4;
  4017.  
  4018. getFinalComments();
  4019.  
  4020.  
  4021. } else if (nodeName === 'YTD-ENGAGEMENT-PANEL-SECTION-LIST-RENDERER') {
  4022.  
  4023.  
  4024. let ytdFlexyElm = kRef(ytdFlexy);
  4025. if (scriptEnable && ytdFlexyElm) {
  4026.  
  4027. let match = node.matches(`ytd-watch-flexy ytd-engagement-panel-section-list-renderer:not([o3r-${sa_epanel}])`);
  4028. if (match) {
  4029.  
  4030. mtoVisibility_EngagementPanel.bindElement(node, {
  4031. attributes: true,
  4032. attributeFilter: ['visibility'],
  4033. attributeOldValue: true
  4034. })
  4035.  
  4036. }
  4037.  
  4038. }
  4039.  
  4040. }
  4041.  
  4042. });
  4043.  
  4044.  
  4045. });
  4046.  
  4047.  
  4048.  
  4049. handleDOMAppear('liveChatFrameDOMAppended', (evt)=>{
  4050.  
  4051.  
  4052. Promise.resolve(0).then(() => { // avoid immediately dom change
  4053. // time delay to avoid attribute set after dom appended.
  4054.  
  4055. tabsDeferred.debounce(() => {
  4056. findChatFrameDOM();
  4057. })
  4058. })
  4059.  
  4060. });
  4061.  
  4062. globalHook('yt-page-data-updated', (evt) => {
  4063.  
  4064. if (!scriptEnable && tabsDeferred.resolved) { return }
  4065. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4066.  
  4067. _console.log(evt.target.nodeName, 904, evt.type);
  4068.  
  4069. advanceFetch();
  4070.  
  4071. let tabsDeferredSess = pageSession.session();
  4072. if (!scriptEnable && tabsDeferred.resolved) { }
  4073. else tabsDeferred.debounce(() => {
  4074.  
  4075. if (!tabsDeferredSess.isValid) return;
  4076.  
  4077. if(!scriptEnable) return;
  4078.  
  4079. if (renderDeferred.resolved && Q.comments_section_loaded === 0 && fetchCounts.new && !fetchCounts.fetched) {
  4080. fetchCounts.new.f();
  4081. fetchCounts.fetched = true;
  4082.  
  4083. fetchCommentsFinished();
  4084. _console.log(9972, 'fetched = true')
  4085. }
  4086.  
  4087.  
  4088. FP.mtf_attrEngagementPanel();
  4089.  
  4090. _console.log(2178,3)
  4091. pageCheck();
  4092. //fixChatFrameToggleButton();
  4093.  
  4094. //if(document.querySelector('ytd-live-chat-frame#chat')){
  4095.  
  4096. findChatFrameDOM();
  4097.  
  4098.  
  4099. //}
  4100.  
  4101. let expander = document.querySelector('#meta-contents ytd-expander:not([tabview-info-expander])')
  4102. if (expander) {
  4103.  
  4104. // once per $$native-info-description$$ {#meta-contents ytd-expander} detection
  4105. // append the detailed meta contents to the tab-info
  4106.  
  4107. expander.setAttribute('tabview-info-expander', '')
  4108. $(expander).appendTo("#tab-info")
  4109.  
  4110. }
  4111.  
  4112.  
  4113. if (REMOVE_DUPLICATE_INFO) {
  4114.  
  4115. checkDuplicateRes = null;
  4116. async function alCheckFn (ks){
  4117. let alCheckCount = 4;
  4118. let alCheckInterval = 270;
  4119.  
  4120. checkDuplicateRes = null;
  4121. do {
  4122.  
  4123. if (renderIdentifier !== ks) break;
  4124. if (alCheckCount === 0) break;
  4125. if (checkDuplicateRes === true) break;
  4126. checkDuplicateRes = null;
  4127. let res = await checkDuplicatedInfo(); //async
  4128. if (res === 5) {
  4129.  
  4130. let ytdFlexyElm = document.querySelector('ytd-watch-flexy');
  4131. if (checkDuplicateRes === true) {
  4132. ytdFlexyElm.classList.toggle('tabview-info-duplicated', checkDuplicateRes)
  4133. checkDuplicatedInfo_then(res);
  4134. } else if (checkDuplicateRes === false && alCheckCount === 1) {
  4135. ytdFlexyElm.classList.toggle('tabview-info-duplicated', checkDuplicateRes)
  4136. checkDuplicatedInfo_then(res);
  4137. }
  4138. }
  4139. --alCheckCount;
  4140.  
  4141. await new Promise(r => setTimeout(r, alCheckInterval));
  4142.  
  4143. } while (true)
  4144.  
  4145. }
  4146. let ks = renderIdentifier;
  4147. renderDeferred.debounce(()=>{
  4148.  
  4149. alCheckFn(ks);
  4150.  
  4151. });
  4152.  
  4153. } else {
  4154.  
  4155. checkDuplicatedInfo_then(0);
  4156.  
  4157. }
  4158.  
  4159.  
  4160. checkPlaylistForInitialization();
  4161.  
  4162. mtf_fix_details().then(() => {
  4163. // setKeywords();
  4164. setToggleInfo();
  4165. setTimeout(() => {
  4166. //dispatchWindowResize(); //try to omit
  4167. dispatchWindowResize(); //add once for safe
  4168. manualResize(true);
  4169. }, 420)
  4170.  
  4171.  
  4172. let secondary = document.querySelector('#columns #secondary.ytd-watch-flexy');
  4173.  
  4174. let columns = secondary ? closestDOM.call(secondary, '#columns') : null;
  4175.  
  4176. setupHoverSlider(secondary, columns)
  4177.  
  4178. let tabInfo = document.querySelector('#tab-info');
  4179. addTabExpander(tabInfo);
  4180.  
  4181. let tabComments = document.querySelector('#tab-comments');
  4182. addTabExpander(tabComments);
  4183.  
  4184.  
  4185. });
  4186.  
  4187.  
  4188.  
  4189. });
  4190.  
  4191. });
  4192.  
  4193.  
  4194.  
  4195. globalHook('yt-player-updated', (evt) => {
  4196.  
  4197.  
  4198.  
  4199. if (!scriptEnable && tabsDeferred.resolved) { return }
  4200. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4201.  
  4202.  
  4203. _console.log(evt.target.nodeName, 904, evt.type);
  4204.  
  4205.  
  4206. advanceFetch();
  4207.  
  4208. let tabsDeferredSess = pageSession.session();
  4209. if (!scriptEnable && tabsDeferred.resolved) { }
  4210. else tabsDeferred.debounce(() => {
  4211.  
  4212. if (!tabsDeferredSess.isValid) return;
  4213.  
  4214.  
  4215. if (renderDeferred.resolved && Q.comments_section_loaded === 0 && fetchCounts.new && !fetchCounts.fetched) {
  4216. fetchCounts.new.f();
  4217. fetchCounts.fetched = true;
  4218.  
  4219. fetchCommentsFinished();
  4220. _console.log(9972, 'fetched = true')
  4221. }
  4222.  
  4223. //restoreFetching(); //yt-player-updated might happen after comments ready
  4224.  
  4225. _console.log(2178,4)
  4226. pageCheck();
  4227. //fixChatFrameToggleButton();
  4228.  
  4229.  
  4230. let nodeName = evt.target.nodeName.toUpperCase();
  4231.  
  4232. _console.log(nodeName, 905, evt.type);
  4233.  
  4234. _console.log(2344, evt.type, document.querySelector('ytd-live-chat-frame#chat') ? 5 : 2);
  4235.  
  4236.  
  4237. if (nodeName === 'YTD-PLAYER') {
  4238.  
  4239. _console.log(2554, nodeName, evt.type, evt.detail);
  4240.  
  4241. domInit_comments();
  4242.  
  4243. findChatFrameDOM();
  4244.  
  4245. let video = document.querySelector('#ytd-player video[src]');
  4246. if (video && !video.hasAttribute('tabview-video-events')) {
  4247. video.setAttribute('tabview-video-events', '')
  4248.  
  4249. let _viTimeNum = 0;
  4250. async function isDocumentInFullScreenMode() {
  4251. return document.fullscreenElement !== null;
  4252. }
  4253. async function videoAsyncTimeUpdate(){
  4254. const isFullscreen = await isDocumentInFullScreenMode();
  4255. // force browser to load the videostream during playing (primarily for music videos)
  4256. // both background and foreground
  4257. if (isFullscreen) return;
  4258. const x = Math.round((Date.now() - mTime) / 3400);
  4259. if (x !== _viTimeNum) {
  4260. const t = await Promise.resolve(x);
  4261.  
  4262. if (_viTimeNum !== t) {
  4263. document.head.dataset.viTime = `${t}`;
  4264. await Promise.resolve(0)
  4265. _viTimeNum = +document.head.dataset.viTime || 0;
  4266. }
  4267.  
  4268. }
  4269.  
  4270. }
  4271. video.addEventListener('timeupdate', (evt) => {
  4272. videoAsyncTimeUpdate();
  4273. }, bubblePassive);
  4274.  
  4275. video.addEventListener('ended',(evt)=>{
  4276. // scrollIntoView => auto start next video
  4277. // otherwise it cannot auto paly next
  4278.  
  4279. let elm =evt.target;
  4280. Promise.resolve(elm).then((elm)=>{
  4281. let scrollElm = closestDOM.call(elm,'#player') || closestDOM.call(elm,'#ytd-player') || elm;
  4282. // requestAnimationFrame
  4283. scrollElm.scrollIntoView(false);
  4284. }).then(()=>{
  4285. // requestAnimationFrame OR setTimeout 100ms
  4286. window.dispatchEvent(new Event("scroll"));
  4287. });
  4288.  
  4289. /*
  4290. if(document.visibilityState==='hidden'){
  4291. scrollElm.scrollIntoView(false);
  4292. setTimeout(()=>{
  4293. window.dispatchEvent(new Event("scroll"));
  4294. },100)
  4295.  
  4296. }else{
  4297. requestAnimationFrame(()=>{
  4298. scrollElm.scrollIntoView(false);
  4299. requestAnimationFrame(()=>{
  4300. window.dispatchEvent(new Event("scroll"));
  4301. })
  4302. })
  4303. }
  4304. */
  4305.  
  4306. /*
  4307. #player
  4308. position: fixed;
  4309. top: 20vh;
  4310. left: 20vh;
  4311. height: 80vh;
  4312. width: 80vh;
  4313. z-index: 99999;
  4314. */
  4315. })
  4316.  
  4317. }
  4318.  
  4319.  
  4320. }
  4321.  
  4322. });
  4323.  
  4324.  
  4325.  
  4326.  
  4327. });
  4328.  
  4329. globalHook('yt-watch-comments-ready', (evt) => {
  4330.  
  4331.  
  4332. if (!scriptEnable && tabsDeferred.resolved) { return }
  4333. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4334.  
  4335.  
  4336. let nodeName = evt.target.nodeName.toUpperCase()
  4337. _console.log(nodeName, 904, evt.type, evt.detail, document.querySelector('ytd-live-chat-frame#chat'));
  4338.  
  4339. advanceFetch();
  4340.  
  4341.  
  4342. comments_loader = comments_loader | 2;
  4343.  
  4344. let tabsDeferredSess = pageSession.session();
  4345. if (!scriptEnable && tabsDeferred.resolved) { }
  4346. else tabsDeferred.debounce(() => {
  4347.  
  4348. if (!tabsDeferredSess.isValid) return;
  4349.  
  4350.  
  4351. if (renderDeferred.resolved && Q.comments_section_loaded === 0 && fetchCounts.new && !fetchCounts.fetched) {
  4352. fetchCounts.new.f();
  4353. fetchCounts.fetched = true;
  4354.  
  4355. fetchCommentsFinished();
  4356. _console.log(9972, 'fetched = true')
  4357. }
  4358.  
  4359. _console.log(2178,5)
  4360. //pageCheck();
  4361.  
  4362. _console.log(nodeName, 905, evt.type, evt.detail, document.querySelector('ytd-live-chat-frame#chat'));
  4363.  
  4364.  
  4365. if (nodeName === 'YTD-WATCH-FLEXY') {
  4366.  
  4367. domInit_comments();
  4368.  
  4369.  
  4370.  
  4371. if (mtf_forceCheckLiveVideo_disable === 2) {
  4372.  
  4373. } else {
  4374.  
  4375.  
  4376.  
  4377. _console.log(3713, Q.comments_section_loaded, fetchCounts.fetched, 'fetch comments')
  4378.  
  4379.  
  4380.  
  4381. if (document.querySelector(`ytd-comments#comments`).hasAttribute('hidden')) {
  4382.  
  4383. //unavailable apart from live chat
  4384.  
  4385. _disableComments();
  4386.  
  4387. _console.log(3713, 3, 'comments hidden')
  4388.  
  4389.  
  4390.  
  4391. } else if (Q.comments_section_loaded === 0) {
  4392.  
  4393.  
  4394. getFinalComments();
  4395.  
  4396.  
  4397. }
  4398.  
  4399. }
  4400.  
  4401. }
  4402.  
  4403. });
  4404.  
  4405. })
  4406.  
  4407.  
  4408.  
  4409.  
  4410. window.addEventListener("message", (evt) => {
  4411.  
  4412. if (!scriptEnable && tabsDeferred.resolved) { return }
  4413. if (evt.origin === location.origin && evt.data.tabview) {
  4414.  
  4415. let data = evt.data.tabview;
  4416.  
  4417. if (data.eventType === "yt-page-type-changed") {
  4418. let detail = data.eventDetail
  4419. let { newPageType, oldPageType } = detail;
  4420. if (newPageType && oldPageType) {
  4421.  
  4422. let bool = false;
  4423. if (newPageType == 'ytd-watch-flexy') {
  4424. bool = true;
  4425.  
  4426. pageType = 'watch';
  4427. //dispatchWindowResize(); // player control positioning //try to omit
  4428. } else if (newPageType == 'ytd-browse') {
  4429.  
  4430. pageType = 'browse';
  4431. }
  4432.  
  4433. document.documentElement.classList.toggle('tabview-normal-player', bool)
  4434.  
  4435.  
  4436. }
  4437. }
  4438.  
  4439. _console.log(37192, evt)
  4440.  
  4441. }
  4442. }, bubblePassive);
  4443.  
  4444.  
  4445. globalHook('data-changed',(evt)=>{
  4446.  
  4447.  
  4448. // ytd-app>ytd-popup-container>tp-yt-iron-dropdown.style-scope.ytd-popup-container[horizontal-align="auto"]:not([aria-disabled=""]) > #contentWrapper > ytd-modal-with-title-and-button-renderer[style*="0px"]{
  4449.  
  4450. // /* fix issue mentioned in https://greasyfork.org/en/scripts/428651-tabview-youtube/discussions/157029 */
  4451. // /* reproduction: click watch later without login */
  4452. // /* without this, the layout coordinates and size (height) of container will become incorrect. */
  4453.  
  4454.  
  4455. // max-height: initial !important;
  4456.  
  4457.  
  4458.  
  4459. // }
  4460.  
  4461. // ytd-app>ytd-popup-container>tp-yt-iron-dropdown.style-scope.ytd-popup-container[horizontal-align="auto"][style*="fixed"]:not([aria-disabled=""]){
  4462.  
  4463. // /* fix issue mentioned in https://greasyfork.org/en/scripts/428651-tabview-youtube/discussions/157029 */
  4464. // /* reproduction: click watch later without login */
  4465. // /* without this, the layout coordinates and size (height) of container will become incorrect. */
  4466.  
  4467.  
  4468. // top:0 !important;
  4469. // left:0 !important;
  4470.  
  4471.  
  4472.  
  4473. // }
  4474.  
  4475.  
  4476.  
  4477. if (!scriptEnable && tabsDeferred.resolved) { return }
  4478.  
  4479. let nodeName = (((evt||0).target||0).nodeName||'').toUpperCase()
  4480.  
  4481. if(nodeName !== 'YTD-THUMBNAIL-OVERLAY-TOGGLE-BUTTON-RENDERER') return;
  4482.  
  4483. // setTimeout(()=>{
  4484.  
  4485. // let checkElm = document.querySelector(`ytd-app>ytd-popup-container>tp-yt-iron-dropdown.style-scope.ytd-popup-container[horizontal-align="auto"]:not([aria-disabled=""]) > #contentWrapper > ytd-modal-with-title-and-button-renderer[style*="0px"]`);
  4486. // console.log(123);
  4487. // if(checkElm){
  4488. // // this is incorrect layout with fixed position and wrong height
  4489.  
  4490. // let positionContainer = closestDOM.call(checkElm,'tp-yt-iron-dropdown.style-scope.ytd-popup-container');
  4491. // console.log(456);
  4492. // console.log(positionContainer.getBoundingClientRect().top, screen.height);
  4493. // if(positionContainer && positionContainer.getBoundingClientRect().top>screen.height ){
  4494. // checkElm.style.maxHeight = 'initial';
  4495. // positionContainer.top = '0';
  4496. // positionContainer.left = '0';
  4497. // }
  4498.  
  4499. // }
  4500.  
  4501. // },300)
  4502.  
  4503. document.dispatchEvent(new CustomEvent("tabview-fix-popup-refit"));
  4504.  
  4505.  
  4506. })
  4507.  
  4508.  
  4509. // document.addEventListener('yt-page-type-changed',(evt)=>{
  4510. // _console.log(3553,evt,evt.detail,evt.target.data)
  4511. // },false);
  4512.  
  4513. DEBUG_LOG && globalHook('data-changed', (evt) => {
  4514.  
  4515. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4516.  
  4517. let nodeName = evt.target.nodeName.toUpperCase()
  4518. _console.log(nodeName, evt.type)
  4519.  
  4520. if (nodeName === 'YTD-ITEM-SECTION-RENDERER' || nodeName === 'YTD-COMMENTS') {
  4521.  
  4522. _console.log(344)
  4523.  
  4524. }
  4525.  
  4526. })
  4527.  
  4528. DEBUG_LOG && globalHook('yt-navigate', (evt) => {
  4529.  
  4530. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4531. _console.log(evt.target.nodeName, evt.type)
  4532.  
  4533. })
  4534.  
  4535. DEBUG_LOG && globalHook('ytd-playlist-lockup-now-playing-active', (evt) => {
  4536.  
  4537. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4538. _console.log(evt.target.nodeName, evt.type)
  4539.  
  4540.  
  4541. })
  4542.  
  4543. DEBUG_LOG && globalHook('yt-service-request-completed', (evt) => {
  4544.  
  4545. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4546. _console.log(evt.target.nodeName, evt.type)
  4547.  
  4548.  
  4549. })
  4550.  
  4551. DEBUG_LOG && globalHook('yt-commerce-action-done', (evt) => {
  4552.  
  4553. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4554. _console.log(evt.target.nodeName, evt.type)
  4555.  
  4556.  
  4557. })
  4558.  
  4559. DEBUG_LOG && globalHook('yt-navigate', (evt) => {
  4560.  
  4561. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4562. _console.log(evt.target.nodeName, evt.type)
  4563.  
  4564. })
  4565.  
  4566.  
  4567. DEBUG_LOG && globalHook('yt-execute-service-endpoint', (evt) => {
  4568.  
  4569. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4570. _console.log(evt.target.nodeName, evt.type)
  4571.  
  4572. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  4573.  
  4574.  
  4575. })
  4576.  
  4577.  
  4578.  
  4579. DEBUG_LOG && globalHook('yt-request-panel-mode-change', (evt) => {
  4580.  
  4581. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4582. _console.log(evt.target.nodeName, evt.type)
  4583.  
  4584.  
  4585. })
  4586.  
  4587.  
  4588.  
  4589.  
  4590. DEBUG_LOG && globalHook('yt-visibility-refresh', (evt) => {
  4591.  
  4592. if (!evt || !evt.target /*|| evt.target.nodeType !== 1*/) return;
  4593. _console.log(evt.target.nodeName || '', evt.type)
  4594.  
  4595. _console.log(2784, evt.type, kRef(ytdFlexy).hasAttribute('hidden'), evt.detail)
  4596. _console.log(evt.detail)
  4597.  
  4598.  
  4599. })
  4600.  
  4601. DEBUG_LOG && globalHook('yt-request-panel-mode-change', (evt) => {
  4602.  
  4603. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4604. _console.log(evt.target.nodeName, evt.type)
  4605.  
  4606. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  4607.  
  4608.  
  4609. })
  4610.  
  4611. DEBUG_LOG && globalHook('app-reset-layout', (evt) => {
  4612.  
  4613. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4614. _console.log(evt.target.nodeName, evt.type)
  4615.  
  4616. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  4617.  
  4618.  
  4619. })
  4620. DEBUG_LOG && globalHook('yt-guide-close', (evt) => {
  4621.  
  4622. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4623. _console.log(evt.target.nodeName, evt.type)
  4624.  
  4625. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  4626.  
  4627.  
  4628. })
  4629. DEBUG_LOG && globalHook('yt-page-data-will-change', (evt) => {
  4630.  
  4631. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4632. _console.log(evt.target.nodeName, evt.type)
  4633.  
  4634.  
  4635. })
  4636.  
  4637. DEBUG_LOG && globalHook('yt-retrieve-location', (evt) => {
  4638.  
  4639. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4640. _console.log(evt.target.nodeName, evt.type)
  4641.  
  4642.  
  4643. })
  4644.  
  4645. DEBUG_LOG && globalHook('yt-refit', (evt) => {
  4646.  
  4647. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4648. _console.log(evt.target.nodeName, evt.type)
  4649.  
  4650. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  4651. })
  4652.  
  4653. DEBUG_LOG && globalHook('addon-attached', (evt) => {
  4654.  
  4655. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4656. _console.log(evt.target.nodeName, evt.type)
  4657.  
  4658. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  4659. })
  4660.  
  4661. DEBUG_LOG && globalHook('yt-live-chat-context-menu-opened', (evt) => {
  4662.  
  4663. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4664. _console.log(evt.target.nodeName, evt.type)
  4665.  
  4666. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  4667. })
  4668.  
  4669. DEBUG_LOG && globalHook('yt-live-chat-context-menu-closed', (evt) => {
  4670.  
  4671. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4672. _console.log(evt.target.nodeName, evt.type)
  4673.  
  4674. _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  4675. })
  4676.  
  4677. DEBUG_LOG && globalHook('yt-commentbox-resize', (evt) => {
  4678.  
  4679. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4680. _console.log(evt.target.nodeName, evt.type)
  4681. })
  4682.  
  4683. DEBUG_LOG && globalHook('yt-rich-grid-layout-refreshed', (evt) => {
  4684.  
  4685. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4686. _console.log(2327, evt.target.nodeName, evt.type)
  4687. })
  4688.  
  4689. DEBUG_LOG && globalHook('animationend', (evt) => {
  4690.  
  4691. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4692. _console.log(evt.target.nodeName, evt.type)
  4693.  
  4694.  
  4695. })
  4696.  
  4697. DEBUG_LOG && globalHook('yt-dismissible-item-dismissed', (evt) => {
  4698.  
  4699. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4700. _console.log(evt.target.nodeName, evt.type)
  4701.  
  4702.  
  4703. })
  4704.  
  4705. DEBUG_LOG && globalHook('yt-dismissible-item-undismissed', function (evt) {
  4706.  
  4707. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4708. _console.log(evt.target.nodeName, evt.type)
  4709.  
  4710.  
  4711. })
  4712.  
  4713.  
  4714. DEBUG_LOG && globalHook('yt-load-next-continuation', function (evt) {
  4715.  
  4716. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4717. _console.log(evt.target.nodeName, evt.type)
  4718.  
  4719.  
  4720. })
  4721.  
  4722.  
  4723. DEBUG_LOG && globalHook('yt-load-reload-continuation', function (evt) {
  4724.  
  4725. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4726. _console.log(evt.target.nodeName, evt.type)
  4727.  
  4728.  
  4729. })
  4730.  
  4731. DEBUG_LOG && globalHook('yt-toggle-button', function (evt) {
  4732.  
  4733. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4734. _console.log(evt.target.nodeName, evt.type)
  4735.  
  4736.  
  4737. })
  4738.  
  4739.  
  4740. }
  4741.  
  4742.  
  4743.  
  4744.  
  4745. function addIframeStyle(cDoc) {
  4746. if (cDoc.querySelector('#userscript-tabview-chatroom-css')) return false;
  4747. addStyle((iframeCSS()||''), cDoc.documentElement).id = 'userscript-tabview-chatroom-css'
  4748. return true;
  4749. }
  4750.  
  4751. function chatFrameContentDocument() {
  4752. // non-null if iframe exist && contentDocument && readyState = complete
  4753. /** @type {HTMLIFrameElement | null} */
  4754. let iframe = document.querySelector('ytd-live-chat-frame iframe#chatframe');
  4755. if (!iframe) return null; //iframe must be there
  4756. /** @type {Document | null} */
  4757. let cDoc = null;
  4758. try {
  4759. cDoc = iframe.contentDocument;
  4760. } catch (e) { }
  4761. if (!cDoc) return null;
  4762. if (cDoc.readyState != 'complete') return null; //we must wait for its completion
  4763.  
  4764. return cDoc;
  4765.  
  4766. }
  4767.  
  4768. function chatFrameElement(/** @type {string} */ cssSelector) {
  4769. let cDoc = chatFrameContentDocument();
  4770. if (!cDoc) return null;
  4771. /** @type {HTMLElement | null} */
  4772. let elm = null;
  4773. try {
  4774. elm = cDoc.querySelector(cssSelector)
  4775. } catch (e) {
  4776. console.log('iframe error', e)
  4777. }
  4778. return elm;
  4779. }
  4780.  
  4781. function forceDisplayChatReplay() {
  4782. let items = chatFrameElement('yt-live-chat-item-list-renderer #items');
  4783. if (items && items.childElementCount !== 0) return;
  4784.  
  4785. let ytd_player = document.querySelector('ytd-player#ytd-player');
  4786. if (!ytd_player) return;
  4787. let videoElm = querySelectorFromAnchor.call(ytd_player, 'video');
  4788. if (!videoElm) return;
  4789.  
  4790. let ct = videoElm.currentTime;
  4791. if (ct >= 0 && !videoElm.ended && videoElm.readyState > videoElm.HAVE_CURRENT_DATA) {
  4792. let chat = document.querySelector('ytd-live-chat-frame#chat');
  4793. if (chat) {
  4794. nativeFunc(chat, "postToContentWindow", [{ "yt-player-video-progress": ct }])
  4795. }
  4796. }
  4797.  
  4798. }
  4799. function checkIframeDblClick(){
  4800. setTimeout(()=>{
  4801. let Itemslist = chatFrameElement('#contents.yt-live-chat-renderer');
  4802. if(Itemslist && typeof Itemslist.ondblclick ==='function') iframePointEventsAllow=true;
  4803.  
  4804. if(iframePointEventsAllow){
  4805. chatFrameElement('body').classList.add('tabview-allow-pointer-events');
  4806. }
  4807.  
  4808. },300)
  4809. }
  4810.  
  4811. function iFrameContentReady(cDoc) {
  4812.  
  4813. if (!cDoc) return;
  4814.  
  4815. if (addIframeStyle(cDoc) === false) return;
  4816.  
  4817. let frc = 0;
  4818. let cid = 0;
  4819.  
  4820. let fullReady = () => {
  4821.  
  4822. if (!cDoc.documentElement.hasAttribute('style') && ++frc < 900) return;
  4823. clearInterval(cid);
  4824.  
  4825. if (!scriptEnable || !isChatExpand()) return;
  4826.  
  4827. let iframe = document.querySelector('body ytd-watch-flexy ytd-live-chat-frame iframe#chatframe');
  4828.  
  4829. if (!iframe) return; //prevent iframe is detached from the page
  4830.  
  4831. if (cDoc.querySelector('yt-live-chat-renderer #continuations')) {
  4832. $(document.querySelector('ytd-live-chat-frame#chat')).attr('yt-userscript-iframe-loaded', '')
  4833. }
  4834.  
  4835. forceDisplayChatReplay();
  4836. checkIframeDblClick(); //user request for compatible with https://greasyfork.org/en/scripts/452335
  4837. iframe.dispatchEvent(new CustomEvent("tabview-chatroom-ready"))
  4838.  
  4839. }
  4840. cid = setInterval(fullReady, 10)
  4841. fullReady();
  4842.  
  4843.  
  4844.  
  4845.  
  4846.  
  4847. }
  4848. const iframeLoadHookA = function (evt) {
  4849.  
  4850. let isIframe = (((evt || 0).target || 0).nodeName === 'IFRAME');
  4851.  
  4852. if (isIframe && evt.target.matches('body iframe.style-scope.ytd-live-chat-frame#chatframe')) {
  4853.  
  4854. let iframe = evt.target;
  4855. new Promise(resolve => {
  4856.  
  4857. let k = 270
  4858. let cid = setInterval(() => {
  4859.  
  4860. if (k-- < 1) {
  4861. clearInterval(cid);
  4862. resolve(false);
  4863. }
  4864.  
  4865. let cDoc = iframe.contentDocument;
  4866. if (!cDoc) return null;
  4867. if (cDoc.readyState != 'complete') return;
  4868. if (!cDoc.querySelector('body')) {
  4869. clearInterval(cid);
  4870. resolve(false);
  4871. }
  4872.  
  4873. if (!cDoc.querySelector('yt-live-chat-app')) return;
  4874.  
  4875. clearInterval(cid);
  4876.  
  4877. if (!document.contains(iframe)) return resolve(false);
  4878.  
  4879. resolve(cDoc);
  4880.  
  4881.  
  4882.  
  4883. }, 17)
  4884.  
  4885.  
  4886. }).then((res) => {
  4887.  
  4888. if (res) {
  4889. iFrameContentReady(res)
  4890. }
  4891.  
  4892. })
  4893.  
  4894.  
  4895.  
  4896. }
  4897. }
  4898.  
  4899.  
  4900. function onNavigationEnd(evt) {
  4901. console.log('yt-navigate-finish')
  4902.  
  4903. //globalHook('yt-navigate-finish',(evt)=>{
  4904.  
  4905. // if(!evt || !evt.target || evt.target.nodeType !== 1) return;
  4906. // _console.log(evt.target.nodeName, evt.type)
  4907.  
  4908. // _console.log(2774, evt.type, !!document.querySelector('ytd-live-chat-frame#chat'))
  4909.  
  4910. //})
  4911.  
  4912.  
  4913.  
  4914. // forceConfig();
  4915. /*
  4916. console.log(window.ytcfg)
  4917. try{
  4918. window.ytcfg.set({
  4919. "EXPERIMENT_FLAGS": {"kevlar_watch_metadata_refresh":false}})
  4920. }catch(e){}
  4921. // ytcfg.set({EXPERIMENT_FLAGS:{"kevlar_watch_metadata_refresh":true}})
  4922. */
  4923.  
  4924. if (pageType === 'watch') {
  4925.  
  4926. let isFirstLoad = firstLoadStatus&8;
  4927.  
  4928. if (isFirstLoad) {
  4929. firstLoadStatus -= 8;
  4930. document.addEventListener('load', iframeLoadHookA, capturePassive)
  4931. ytMicroEventsInit();
  4932. }
  4933. variableResets();
  4934.  
  4935. if(isFirstLoad){
  4936. let docElement = document.documentElement
  4937. if(docElement.hasAttribute('tabview-loaded')){
  4938. throw 'Tabview Youtube Duplicated';
  4939. }
  4940. docElement.setAttribute('tabview-loaded', '')
  4941. docElement.dispatchEvent(new CustomEvent('tabview-ce-hack'))
  4942. }
  4943.  
  4944.  
  4945. let ytdFlexyElm = document.querySelector('ytd-watch-flexy')
  4946.  
  4947. if (!ytdFlexyElm) return;
  4948.  
  4949. scriptEnable = true;
  4950.  
  4951. ytdFlexy = mWeakRef(ytdFlexyElm)
  4952.  
  4953. new Promise(frResolve=>{
  4954.  
  4955. let timeoutR_findRelated = new Timeout();
  4956. timeoutR_findRelated.set(function () {
  4957. let ytdFlexyElm = kRef(ytdFlexy);
  4958. if (!ytdFlexyElm) return true;
  4959. let related = querySelectorFromAnchor.call(ytdFlexyElm, "#related");
  4960. if (!related) return true;
  4961. frResolve(related);
  4962. }, 100, 10)
  4963.  
  4964. }).then(related=>{
  4965.  
  4966. if (!document.querySelector("#right-tabs")) {
  4967. getLang();
  4968. $(getTabsHTML()).insertBefore(related).attr('data-dom-created-by-tabview-youtube', scriptVersionForExternal);
  4969. console.log('#right-tabs inserted')
  4970. }
  4971.  
  4972. const ytdFlexyElm = kRef(ytdFlexy)
  4973. if (!ytdFlexyElm) return;
  4974. if (!ytdFlexyElm.hasAttribute('tyt-tab')) ytdFlexyElm.setAttribute('tyt-tab', '')
  4975.  
  4976. // append the next videos
  4977. // it exists as "related" is already here
  4978. fixTabs();
  4979.  
  4980. setToActiveTab(); // just switch to the default tab
  4981. prepareTabBtn();
  4982.  
  4983. mtoFlexyAttr.clear(true)
  4984. mtf_checkFlexy()
  4985.  
  4986. //document.querySelector("#right-tabs .tab-content").addEventListener('scroll', windowScroll, true);
  4987.  
  4988. // for automcomplete plugin or other userscript plugins
  4989. // document.body for Firefox >= 60
  4990. mtoMutation_body.bindElement(document.querySelector('body'), {
  4991. childList: true,
  4992. subtree: false,
  4993. attributes: false
  4994. })
  4995.  
  4996. tabsDeferred.resolve();
  4997.  
  4998. })
  4999. } else {
  5000.  
  5001. variableResets();
  5002. emptyCommentSection();
  5003. _console.log(9360, 75);
  5004. tabsDeferred.reset();
  5005. _pageBeingInit();
  5006. tabsDeferred.resolve(); // for page initialization
  5007.  
  5008. }
  5009.  
  5010.  
  5011. }
  5012.  
  5013.  
  5014.  
  5015. function getWrapper(wrapperId) {
  5016. let $wrapper = $(`#${wrapperId}`);
  5017. if (!$wrapper[0]) $wrapper = $(`<div id="${wrapperId}"></div>`)
  5018. return $wrapper.first();
  5019. }
  5020.  
  5021. function fetchCommentsFinished() {
  5022. let ytdFlexyElm = kRef(ytdFlexy);
  5023. if (!scriptEnable || !ytdFlexyElm) return;
  5024. if (mtf_forceCheckLiveVideo_disable === 2) return;
  5025. ytdFlexyElm.setAttribute('tyt-comments', 'L');
  5026. _console.log(2909, 1)
  5027. }
  5028.  
  5029. function setCommentSection( /** @type {number} */ value) {
  5030.  
  5031. Q.comments_section_loaded = value;
  5032. if (value === 0 && fetchCounts) {
  5033. fetchCounts.fetched = false; // unknown bug
  5034. }
  5035.  
  5036. }
  5037.  
  5038.  
  5039. function emptyCommentSection() {
  5040. let tab_btn = document.querySelector('.tab-btn[userscript-tab-content="#tab-comments"]')
  5041. if (tab_btn) {
  5042. let span = querySelectorFromAnchor.call(tab_btn, 'span#tyt-cm-count');
  5043. tab_btn.removeAttribute('loaded-comment')
  5044. if (span) {
  5045. span.textContent = '';
  5046. }
  5047. }
  5048. setCommentSection(0);
  5049. _console.log(7233, 'comments_section_loaded = 0')
  5050. }
  5051.  
  5052.  
  5053.  
  5054. function _disableComments() {
  5055.  
  5056.  
  5057. _console.log(2909, 1)
  5058. if (!scriptEnable) return;
  5059. let cssElm = kRef(ytdFlexy);
  5060. if (!cssElm) return;
  5061.  
  5062. _console.log(2909, 2)
  5063.  
  5064.  
  5065. let comments = document.querySelector('ytd-comments#comments')
  5066. if (mtf_forceCheckLiveVideo_disable === 2) {
  5067. // earlier than DOM change
  5068. } else {
  5069. if (comments && !comments.hasAttribute('hidden')) return; // visible comments content)
  5070. }
  5071.  
  5072. _console.log(2909, 4)
  5073. if (Q.comments_section_loaded === 2) return; //already disabled
  5074.  
  5075. setCommentSection(2);
  5076.  
  5077. _console.log(2909, 5)
  5078.  
  5079. let tabBtn = document.querySelector('.tab-btn[userscript-tab-content="#tab-comments"]');
  5080. if (tabBtn) {
  5081. let span = querySelectorFromAnchor.call(tabBtn, 'span#tyt-cm-count');
  5082. tabBtn.removeAttribute('loaded-comment')
  5083. if (!tabBtn.classList.contains('tab-btn-hidden')) {
  5084. //console.log('hide', comments, comments && comments.hasAttribute('hidden'))
  5085. hideTabBtn(tabBtn)
  5086. }
  5087. if (span) {
  5088. span.textContent = '';
  5089. }
  5090. }
  5091.  
  5092. cssElm.setAttribute('tyt-comments','D');
  5093.  
  5094. _console.log(2909, 10)
  5095.  
  5096.  
  5097. }
  5098.  
  5099.  
  5100.  
  5101. function setKeywords(){
  5102.  
  5103. return;
  5104.  
  5105. if(typeof String.prototype.replacei !== 'function'){
  5106. // reference: https://stackoverflow.com/questions/7313395/case-insensitive-replace-all
  5107. String.replacei = String.prototype.replacei = function (rep, rby) {
  5108. var pos = this.toLowerCase().indexOf(rep.toLowerCase());
  5109. return pos == -1 ? this : this.substring(0, pos) + rby(this.substring(pos, pos + rep.length)) + this.substring(pos + rep.length);
  5110. };
  5111. }
  5112.  
  5113. let data = pageFetchedData;
  5114. console.log(data)
  5115.  
  5116. let keywords = ((((data || 0).pageData || 0).playerResponse || 0).videoDetails || 0).keywords;
  5117. console.log(keywords)
  5118. if(keywords&&keywords.length>0){
  5119.  
  5120. let title = '';
  5121.  
  5122. try {
  5123. title = ((((data || 0).pageData || 0).response || 0).contents || 0).twoColumnWatchNextResults.results.results.contents[0].videoPrimaryInfoRenderer.title.runs[0].text;
  5124. if(typeof title !=='string') title='';
  5125. } catch (e) { }
  5126.  
  5127. let strText= title;
  5128. let tabsDeferredSess = pageSession.session();
  5129. if (!scriptEnable && tabsDeferred.resolved) { }
  5130. else tabsDeferred.debounce(() => {
  5131.  
  5132. if (!tabsDeferredSess.isValid) return;
  5133.  
  5134. let res = [];
  5135. for(const keyword of keywords){
  5136. if(strText.toUpperCase().includes(keyword.toUpperCase())){
  5137. res.push(keyword);
  5138. }
  5139. }
  5140. if(res.length>0){
  5141. console.log('tabview video keywords', res)
  5142. window.postMessage({
  5143. tabview: {
  5144. eventType: 0x3700,
  5145. eventDetail: {
  5146. keywords: res
  5147. }
  5148. }
  5149. }, location.origin);
  5150. }
  5151.  
  5152.  
  5153. let strElms = document.querySelectorAll('#title.ytd-watch-metadata yt-formatted-string.style-scope.ytd-watch-metadata');
  5154. //console.log(keywords,strElms)
  5155. for(const strElm of strElms){
  5156. if(strElm.id=='super-title' || strElm.id=='original-info'){
  5157.  
  5158. }else{
  5159. if(strElm.querySelector('*')){
  5160.  
  5161. }else{
  5162. /** @type{string} */
  5163. let strText = strElm.textContent;
  5164. if(strText){
  5165.  
  5166. let res = [];
  5167. for(const keyword of keywords){
  5168. if(strText.toUpperCase().includes(keyword.toUpperCase())){
  5169. res.push(keyword);
  5170. }
  5171. }
  5172. if(res.length>0){
  5173. console.log('tabview video keywords', res)
  5174. if(res.length>1) res.sort((a,b)=>{return b.length-a.length});
  5175. let usedKeywords = {};
  5176. for(const s of res){
  5177. strText=strText.replacei(s,((s)=>{
  5178. usedKeywords[s]=true;
  5179. return `\n${s}\n`
  5180. }))
  5181. }
  5182. strText=strText.replace(`\n\n+`,'\n')
  5183. let retElms = strText.split('\n').map(w=>{
  5184. let elm = document.createElement('tabview-txt')
  5185. if(usedKeywords[w]) elm.classList.add('tabview-title-keyword')
  5186. elm.textContent= w;
  5187. return elm
  5188. })
  5189.  
  5190. let p=querySelectorFromAnchor.call(strElm.parentNode,'.tabview-txt');
  5191.  
  5192. if(!p){
  5193. p = strElm.cloneNode(false)
  5194. p.classList.add('tabview-txt')
  5195. strElm.after(p);
  5196. }else{
  5197. strElm.after(p);
  5198. }
  5199.  
  5200. requestAnimationFrame(()=>{
  5201.  
  5202. p.textContent='x';
  5203. p.firstChild.replaceWith(...retElms);
  5204. p.removeAttribute('is-empty')
  5205. strElm.setAttribute('is-empty','')
  5206. })
  5207.  
  5208. }
  5209. break;
  5210. }
  5211. }
  5212. }
  5213. }
  5214.  
  5215. })
  5216.  
  5217. }
  5218.  
  5219.  
  5220. }
  5221.  
  5222. function setToggleInfo() {
  5223. new Promise(resolve => {
  5224.  
  5225. let tf = () => {
  5226. let t = document.documentElement.getAttribute('tabview-unwrapjs')
  5227. if (!t) return setTimeout(tf, 300);
  5228. resolve();
  5229. }
  5230. tf();
  5231.  
  5232. }).then(() => {
  5233.  
  5234. let elem = document.querySelector('#primary.ytd-watch-flexy #below ytd-watch-metadata #info-container.ytd-watch-metadata:first-child:not([tabview-info-toggler])')
  5235. if (elem) {
  5236.  
  5237. elem.setAttribute('tabview-info-toggler', '')
  5238. elem.dispatchEvent(new CustomEvent('tabview-info-toggler'))
  5239.  
  5240. }
  5241.  
  5242. });
  5243. }
  5244.  
  5245.  
  5246.  
  5247.  
  5248.  
  5249.  
  5250.  
  5251.  
  5252.  
  5253. function flexyAttr_toggleFlag(mFlag, b, flag) {
  5254. return b ? (mFlag | flag) : (mFlag & ~flag);
  5255. }
  5256.  
  5257. function flexAttr_toLayoutStatus(nls, attributeName) {
  5258.  
  5259. let attrElm, b, v;
  5260. switch (attributeName) {
  5261. case 'theater':
  5262. b = isTheater();
  5263. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_THEATER);
  5264. break;
  5265. case 'tyt-chat':
  5266. attrElm = kRef(ytdFlexy);
  5267. v = attrElm.getAttribute('tyt-chat');
  5268.  
  5269. if (v !== null && v.charAt(0) === '-') {
  5270. nls = flexyAttr_toggleFlag(nls, true, LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLAPSED);
  5271. } else {
  5272. nls = flexyAttr_toggleFlag(nls, v !== null, LAYOUT_CHATROOM);
  5273. nls = flexyAttr_toggleFlag(nls, false, LAYOUT_CHATROOM_COLLAPSED);
  5274. }
  5275.  
  5276. break;
  5277. case 'is-two-columns_':
  5278. b = isWideScreenWithTwoColumns();
  5279. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_TWO_COLUMNS);
  5280. break;
  5281.  
  5282. case 'tyt-tab':
  5283. attrElm = kRef(ytdFlexy);
  5284. b = isNonEmptyString(attrElm.getAttribute('tyt-tab'));
  5285. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_TAB_EXPANDED);
  5286. break;
  5287.  
  5288. case 'fullscreen':
  5289. attrElm = kRef(ytdFlexy);
  5290. b = attrElm.hasAttribute('fullscreen');
  5291. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_FULLSCREEN);
  5292. break;
  5293.  
  5294. case 'tyt-ep-visible':
  5295. attrElm = kRef(ytdFlexy);
  5296. v = attrElm.getAttribute('tyt-ep-visible');
  5297. b = (+v > 0)
  5298. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_ENGAGEMENT_PANEL_EXPAND);
  5299. break;
  5300.  
  5301. }
  5302.  
  5303. return nls;
  5304.  
  5305.  
  5306. }
  5307.  
  5308.  
  5309. const mtf_attrFlexy_functions = {
  5310. 'tyt-chat': () => {
  5311. if (!scriptEnable) return;
  5312. //delayed call => check with the "no active focus" condition with chatroom status
  5313. if (!isAnyActiveTab() && !isChatExpand() && !isTheater() && isWideScreenWithTwoColumns() && !isFullScreen()) {
  5314. setToActiveTab();
  5315. }
  5316. },
  5317. 'tyt-ep-visible': () => {
  5318. if (!scriptEnable) return;
  5319. //delayed call => check with the "no active focus" condition with engagement panel status
  5320. if (!isAnyActiveTab() && !isEngagementPanelExpanded() && !isTheater() && isWideScreenWithTwoColumns() && !isFullScreen() && !isChatExpand()) {
  5321. setToActiveTab();
  5322. }
  5323. }
  5324. }
  5325.  
  5326. function iof_details(entries, observer){
  5327. if(!detailsTriggerReset) return;
  5328. if(!entries || entries.length!==1) return; // unlikely
  5329. let entry = entries[0];
  5330. //console.log(entries)
  5331. if(entry.isIntersecting === true){
  5332.  
  5333. if ((wls.layoutStatus & (LAYOUT_TWO_COLUMNS | LAYOUT_FULLSCREEN) ) !== (LAYOUT_TWO_COLUMNS | LAYOUT_FULLSCREEN))
  5334. return;
  5335.  
  5336. let dom = entry.target;
  5337. if(!dom) return; //unlikely
  5338.  
  5339. let descClickable = dom.closest('#description.item.style-scope.ytd-watch-metadata')
  5340. if(descClickable){
  5341. detailsTriggerReset = false;
  5342. descClickable.click();
  5343. }
  5344.  
  5345.  
  5346. }
  5347.  
  5348. }
  5349.  
  5350. const mtf_attrFlexy = (mutations, observer) => {
  5351.  
  5352. //attr mutation checker - $$ytdFlexyElm$$ {ytd-watch-flexy} \single
  5353. //::attr
  5354. // ~ 'tyt-chat', 'theater', 'is-two-columns_',
  5355. // ~ 'tyt-tab', 'fullscreen', 'tyt-ep-visible',
  5356. // ~ 'hidden', 'is-extra-wide-video_'
  5357.  
  5358. //console.log(15330, scriptEnable, kRef(ytdFlexy), mutations)
  5359.  
  5360. if (!scriptEnable) return;
  5361.  
  5362. const cssElm = kRef(ytdFlexy)
  5363. if (!cssElm) return;
  5364.  
  5365. if (!mutations) return;
  5366.  
  5367. const old_layoutStatus = wls.layoutStatus
  5368. if (old_layoutStatus === 0) return;
  5369. let new_layoutStatus = old_layoutStatus;
  5370.  
  5371. let checkedChat = false;
  5372.  
  5373. for (const mutation of mutations) {
  5374. new_layoutStatus = flexAttr_toLayoutStatus(new_layoutStatus, mutation.attributeName);
  5375. _console.log(8221,18, mutation.attributeName )
  5376. if (mutation.attributeName === 'tyt-chat') {
  5377.  
  5378. if (!checkedChat) {
  5379. checkedChat = true; // avoid double call
  5380.  
  5381. if ((cssElm.getAttribute('tyt-chat') || '').indexOf('chat$live') >= 0) {
  5382. // assigned new attribute - "chat$live" => disable comments section
  5383.  
  5384. //console.log(3712,2)
  5385. _disableComments();
  5386. }
  5387.  
  5388. if (!cssElm.hasAttribute('tyt-chat')) {
  5389. // might or might not collapsed before
  5390. timeline.setTimeout(mtf_attrFlexy_functions['tyt-chat'], 240);
  5391. }
  5392. }
  5393.  
  5394. } else if (mutation.attributeName === 'tyt-ep-visible') {
  5395. // assume any other active component such as tab content and chatroom
  5396.  
  5397. if (+(cssElm.getAttribute('tyt-ep-visible') || 0) === 0 && +mutation.oldValue > 0) {
  5398. timeline.setTimeout(mtf_attrFlexy_functions['tyt-ep-visible'], 240);
  5399. }
  5400. } else if (mutation.attributeName === 'is-extra-wide-video_') {
  5401. setTimeout(() => {
  5402. updateFloatingSlider(); //required for hover slider // eg video after ads
  5403. }, 1);
  5404. }
  5405. }
  5406.  
  5407. new_layoutStatus = fixLayoutStatus(new_layoutStatus);
  5408.  
  5409. if (new_layoutStatus !== old_layoutStatus) {
  5410. wls.layoutStatus = new_layoutStatus
  5411.  
  5412.  
  5413. }
  5414.  
  5415. }
  5416.  
  5417. function setupChatFrameDisplayState1(chatBlockR, initialDisplayState) {
  5418.  
  5419.  
  5420. let ytdFlexyElm = kRef(ytdFlexy);
  5421. if (!scriptEnable || !ytdFlexyElm) return;
  5422.  
  5423. let chatTypeChanged = mtf_chatBlockQ !== chatBlockR;
  5424.  
  5425. let attr_chatblock = chatBlockR === 1 ? 'chat$live' : chatBlockR === 3 ? 'chat$playback' : false;
  5426. let attr_chatcollapsed = false;
  5427.  
  5428.  
  5429. if (attr_chatblock) {
  5430. let chatFrame = document.querySelector('ytd-live-chat-frame#chat')
  5431. if (chatFrame) {
  5432. attr_chatcollapsed = chatFrame.hasAttribute('collapsed');
  5433. if (!attr_chatcollapsed) {
  5434.  
  5435. //nativeFunc(p,'setupPlayerProgressRelay')
  5436. //if(!p.isFrameReady)
  5437. //nativeFunc(p, "urlChanged")
  5438. //console.log(12399,1)
  5439. chatFrame.dispatchEvent(new CustomEvent("tabview-chatroom-newpage")); //possible empty iframe is shown
  5440.  
  5441. }
  5442. } else {
  5443. attr_chatcollapsed = initialDisplayState === 'LIVE_CHAT_DISPLAY_STATE_COLLAPSED' ? true : false;
  5444. }
  5445. }
  5446.  
  5447. if (chatTypeChanged) {
  5448. mtf_chatBlockQ = chatBlockR
  5449.  
  5450. _console.log(932, 2, attr_chatblock, attr_chatcollapsed)
  5451.  
  5452. //LIVE_CHAT_DISPLAY_STATE_COLLAPSED
  5453. //LIVE_CHAT_DISPLAY_STATE_EXPANDED
  5454. let v = attr_chatblock
  5455. if (typeof attr_chatblock == 'string') {
  5456.  
  5457. if (attr_chatcollapsed === true) v = '-' + attr_chatblock
  5458. if (attr_chatcollapsed === false) v = '+' + attr_chatblock;
  5459. }
  5460. wAttr(ytdFlexyElm, 'tyt-chat', v)
  5461.  
  5462. _console.log(932, 3, ytdFlexyElm.hasAttribute('tyt-chat'))
  5463.  
  5464.  
  5465. }
  5466.  
  5467. return { attr_chatblock, attr_chatcollapsed, chatTypeChanged }
  5468. }
  5469. function setupChatFrameDisplayState2() {
  5470.  
  5471. let ytdFlexyElm = kRef(ytdFlexy);
  5472. if (!scriptEnable || !ytdFlexyElm) return null;
  5473.  
  5474. // this is a backup solution only; should be abandoned
  5475.  
  5476. let attr_chatblock = null
  5477. let attr_chatcollapsed = null;
  5478.  
  5479. const elmChat = document.querySelector('ytd-live-chat-frame#chat')
  5480. let elmCont = null;
  5481. if (elmChat) {
  5482. elmCont = chatFrameElement('yt-live-chat-renderer #continuations')
  5483.  
  5484.  
  5485. let s = 0;
  5486. if (elmCont) {
  5487. //not found if it is collapsed.
  5488. s |= querySelectorFromAnchor.call(elmCont, 'yt-timed-continuation') ? 1 : 0;
  5489. s |= querySelectorFromAnchor.call(elmCont, 'yt-live-chat-replay-continuation, yt-player-seek-continuation') ? 2 : 0;
  5490. //s |= elmCont.querySelector('yt-live-chat-restricted-participation-renderer')?4:0;
  5491. if (s == 1) {
  5492. attr_chatblock = 'chat$live';
  5493. } else if (s == 2) attr_chatblock = 'chat$playback';
  5494.  
  5495. if (s == 1) $("span#tyt-cm-count").text('');
  5496.  
  5497. } else if (!ytdFlexyElm.hasAttribute('tyt-chat')) {
  5498. // live chat frame but type not known
  5499.  
  5500. attr_chatblock = '';
  5501.  
  5502. }
  5503. //keep unknown as original
  5504.  
  5505.  
  5506. let isCollapsed = !!elmChat.hasAttribute('collapsed');
  5507. attr_chatcollapsed = isCollapsed;
  5508.  
  5509. } else {
  5510. attr_chatblock = false;
  5511. attr_chatcollapsed = false;
  5512.  
  5513. }
  5514.  
  5515. return { attr_chatblock, attr_chatcollapsed }
  5516.  
  5517. }
  5518.  
  5519.  
  5520. const mtf_checkFlexy = () => {
  5521. // once per $$native-ytd-watch-flexy$$ {ytd-watch-flexy} detection
  5522.  
  5523. let ytdFlexyElm = kRef(ytdFlexy);
  5524. if (!scriptEnable || !ytdFlexyElm) return true;
  5525.  
  5526.  
  5527. wls.layoutStatus = 0;
  5528.  
  5529. let isFlexyHidden = (ytdFlexyElm.hasAttribute('hidden'));
  5530.  
  5531. if (!isFlexyHidden) {
  5532. let rChatExist = setupChatFrameDisplayState2();
  5533. if (rChatExist) {
  5534. let { attr_chatblock, attr_chatcollapsed } = rChatExist;
  5535. if (attr_chatblock === null) {
  5536. //remove attribute if it is unknown
  5537. attr_chatblock = false;
  5538. attr_chatcollapsed = false;
  5539. }
  5540. let v = attr_chatblock;
  5541. if(typeof v ==='string'){
  5542. if(attr_chatcollapsed===true) v='-'+v;
  5543. if(attr_chatcollapsed===false) v='+'+v;
  5544. }
  5545. wAttr(ytdFlexyElm, 'tyt-chat', v)
  5546.  
  5547. }
  5548. }
  5549.  
  5550. let rTabSelection = [...querySelectorAllFromAnchor.call(ytdFlexyElm, '.tab-btn[userscript-tab-content]')]
  5551. .map(elm => ({ elm, hidden: elm.classList.contains('tab-btn-hidden') }));
  5552.  
  5553. if (rTabSelection.length === 0) {
  5554. wAttr(ytdFlexyElm, 'tyt-tab', false);
  5555. } else {
  5556. rTabSelection = rTabSelection.filter(entry => entry.hidden !== true); // all available tabs
  5557. if (rTabSelection.length === 0) wAttr(ytdFlexyElm, 'tyt-tab', '');
  5558. }
  5559. rTabSelection = null;
  5560.  
  5561. let rEP = engagement_panels_();
  5562. if (rEP && rEP.count > 0) wAttr(ytdFlexyElm, 'tyt-ep-visible', false);
  5563. //else wAttr(ytdFlexyElm, 'tyt-ep-visible', rEP.value + ""); // can be 0
  5564. else if (rEP.value > 0) wAttr(ytdFlexyElm, 'tyt-ep-visible', rEP.value + ""); // can be 0
  5565.  
  5566. let ls = LAYOUT_VAILD;
  5567. ls = flexAttr_toLayoutStatus(ls, 'theater')
  5568. ls = flexAttr_toLayoutStatus(ls, 'tyt-chat')
  5569. ls = flexAttr_toLayoutStatus(ls, 'is-two-columns_')
  5570. ls = flexAttr_toLayoutStatus(ls, 'tyt-tab')
  5571. ls = flexAttr_toLayoutStatus(ls, 'fullscreen')
  5572. ls = flexAttr_toLayoutStatus(ls, 'tyt-ep-visible')
  5573.  
  5574. fixLayoutStatus(ls)
  5575.  
  5576. wls.layoutStatus = ls
  5577.  
  5578. mtoFlexyAttr.bindElement(ytdFlexyElm, {
  5579. attributes: true,
  5580. attributeFilter: [ 'tyt-chat', 'theater', 'is-two-columns_', 'tyt-tab', 'fullscreen', 'tyt-ep-visible', 'hidden', 'is-extra-wide-video_'],
  5581. attributeOldValue: true
  5582. })
  5583.  
  5584. let columns = document.querySelector('ytd-page-manager#page-manager #columns')
  5585. if (columns) {
  5586. wAttr(columns, 'userscript-scrollbar-render', true);
  5587. }
  5588.  
  5589. return false;
  5590. }
  5591.  
  5592.  
  5593. function checkVisibleEngagementPanel() {
  5594.  
  5595. if (storeLastPanel) {
  5596.  
  5597. let elm_storeLastPanel = kRef(storeLastPanel);
  5598.  
  5599. if (elm_storeLastPanel && !isDOMVisible(elm_storeLastPanel)) {
  5600. storeLastPanel = null;
  5601. ytBtnCloseEngagementPanels();
  5602. }
  5603.  
  5604. }
  5605.  
  5606. }
  5607.  
  5608.  
  5609.  
  5610.  
  5611.  
  5612.  
  5613. function switchTabActivity(activeLink) {
  5614. if (!scriptEnable) return;
  5615.  
  5616. const ytdFlexyElm = kRef(ytdFlexy);
  5617.  
  5618. if (!ytdFlexyElm) return;
  5619.  
  5620. if (activeLink && activeLink.classList.contains('tab-btn-hidden')) return; // not allow to switch to hide tab
  5621.  
  5622. //if (isTheater() && isWideScreenWithTwoColumns()) activeLink = null;
  5623.  
  5624.  
  5625. function runAtEnd() {
  5626.  
  5627.  
  5628. //console.log(12312)
  5629.  
  5630.  
  5631. if (activeLink) {
  5632. lstTab.lastTab = activeLink.getAttribute('userscript-tab-content')
  5633. lstTab.lastPanel = null;
  5634.  
  5635. if(!document.querySelector(`${lstTab.lastTab}.tab-content-cld tabview-tab-expander`)){
  5636.  
  5637. let secondary = document.querySelector('#secondary.ytd-watch-flexy');
  5638. if (secondary) {
  5639. secondary.dispatchEvent(new CustomEvent('tabview-hover-slider-restore'))
  5640. //console.log(1995)
  5641. }
  5642.  
  5643.  
  5644. }
  5645. }
  5646.  
  5647. //displayedPlaylist = null;
  5648.  
  5649. // if (activeLink && lstTab.lastTab == '#tab-list') {
  5650. // //setDisplayedPlaylist();
  5651. // } else if (activeLink && lstTab.lastTab == '#tab-videos') {
  5652. // }
  5653.  
  5654.  
  5655. ytdFlexyElm.setAttribute('tyt-tab', activeLink ? lstTab.lastTab : '')
  5656.  
  5657.  
  5658.  
  5659.  
  5660.  
  5661.  
  5662. }
  5663.  
  5664. const links = document.querySelectorAll('#material-tabs a[userscript-tab-content]');
  5665.  
  5666. //console.log(701, activeLink)
  5667.  
  5668. for (const link of links) {
  5669. let content = document.querySelector(link.getAttribute('userscript-tab-content'));
  5670. if (link && content) {
  5671. if (link !== activeLink) {
  5672. link.classList.remove("active");
  5673. content.classList.add("tab-content-hidden");
  5674. } else {
  5675. //console.log(3343)
  5676. link.classList.add("active");
  5677. content.classList.remove("tab-content-hidden");
  5678. //timeline.setTimeout(()=>content.focus(),400);
  5679.  
  5680. }
  5681. }
  5682. }
  5683.  
  5684. runAtEnd();
  5685.  
  5686.  
  5687. }
  5688.  
  5689.  
  5690.  
  5691. function getStore() {
  5692. let s = localStorage[STORE_key];
  5693. function resetStore() {
  5694. let ret = {
  5695. version: 1,
  5696. };
  5697. localStorage[STORE_key] = JSON.stringify(ret);
  5698. return ret;
  5699. }
  5700. if (!s) return resetStore();
  5701. let obj = null;
  5702. try {
  5703. obj = JSON.parse(s);
  5704. } catch (e) { }
  5705. return obj && obj.version === STORE_VERSION ? obj : resetStore();
  5706. }
  5707.  
  5708. function setStore(obj) {
  5709. if (!obj || typeof obj !== 'object') return false;
  5710. if (obj.version !== STORE_VERSION) return false;
  5711. localStorage[STORE_key] = JSON.stringify(obj);
  5712. return true;
  5713. }
  5714.  
  5715.  
  5716. async function handlerMaterialTabClickInner(tabBtn){
  5717.  
  5718. await Promise.resolve(0);
  5719.  
  5720. const layoutStatusMutexUnlock = await new Promise(resolve => {
  5721. layoutStatusMutex.lockWith(unlock => {
  5722. resolve(unlock)
  5723. })
  5724. });
  5725.  
  5726. //console.log(8514)
  5727. let unlock = layoutStatusMutexUnlock;
  5728.  
  5729. //console.log(8515)
  5730. switchTabActivity_lastTab = tabBtn.getAttribute('userscript-tab-content');
  5731.  
  5732. let isActiveAndVisible = tabBtn.classList.contains('tab-btn') && tabBtn.classList.contains('active') && !tabBtn.classList.contains('tab-btn-hidden')
  5733.  
  5734. _console.log(8221, 15, isActiveAndVisible)
  5735.  
  5736. if (isFullScreen()) {
  5737.  
  5738.  
  5739. const fullScreenTabScrollIntoView=() => {
  5740. let scrollElement = document.querySelector('ytd-app[scrolling]')
  5741. if (!scrollElement) return;
  5742. // single column view; click button; scroll to tab content area 100%
  5743. let rightTabs = document.querySelector('#right-tabs');
  5744. let pTop = rightTabs.getBoundingClientRect().top - scrollElement.getBoundingClientRect().top
  5745. if (rightTabs && pTop > 0 && tabBtn.classList.contains('active')) {
  5746. rightTabs.scrollIntoView(false);
  5747. }
  5748. }
  5749.  
  5750. _console.log(8221, 16, 1)
  5751.  
  5752. if (isActiveAndVisible) {
  5753. timeline.setTimeout(unlock, 80);
  5754. switchTabActivity(null);
  5755. } else {
  5756.  
  5757. if (isChatExpand() && isWideScreenWithTwoColumns()) {
  5758. ytBtnCollapseChat();
  5759. } else if (isEngagementPanelExpanded() && isWideScreenWithTwoColumns()) {
  5760. ytBtnCloseEngagementPanels();
  5761. }
  5762.  
  5763. timeline.setTimeout(fullScreenTabScrollIntoView, 60)
  5764.  
  5765. timeline.setTimeout(unlock, 80);
  5766. switchTabActivity(tabBtn)
  5767. }
  5768.  
  5769.  
  5770. } else if (isWideScreenWithTwoColumns() && !isTheater() && isActiveAndVisible) {
  5771.  
  5772. _console.log(8221, 16, 2)
  5773. //optional
  5774. timeline.setTimeout(unlock, 80);
  5775. switchTabActivity(null);
  5776. ytBtnSetTheater();
  5777. } else if (isActiveAndVisible) {
  5778.  
  5779. _console.log(8221, 16, 3)
  5780. timeline.setTimeout(unlock, 80);
  5781. switchTabActivity(null);
  5782. } else {
  5783.  
  5784. _console.log(8221, 16, 4)
  5785.  
  5786. if (isChatExpand() && isWideScreenWithTwoColumns()) {
  5787. ytBtnCollapseChat();
  5788. } else if (isEngagementPanelExpanded() && isWideScreenWithTwoColumns()) {
  5789. ytBtnCloseEngagementPanels();
  5790. } else if (isWideScreenWithTwoColumns() && isTheater() && !isFullScreen()) {
  5791. ytBtnCancelTheater(); //ytd-watch-flexy attr [theater]
  5792. }
  5793.  
  5794. timeline.setTimeout(() => {
  5795. // single column view; click button; scroll to tab content area 100%
  5796. let rightTabs = document.querySelector('#right-tabs');
  5797. if (!isWideScreenWithTwoColumns() && rightTabs && rightTabs.offsetTop > 0 && tabBtn.classList.contains('active')) {
  5798. let tabButtonBar = document.querySelector('#material-tabs');
  5799. let tabButtonBarHeight = tabButtonBar ? tabButtonBar.offsetHeight : 0;
  5800. window.scrollTo(0, rightTabs.offsetTop - tabButtonBarHeight);
  5801.  
  5802.  
  5803. //singleColumnScrolling(true); //necessary
  5804.  
  5805. }
  5806. }, 60)
  5807. // _console.log(8519)
  5808.  
  5809. timeline.setTimeout(unlock, 80)
  5810. switchTabActivity(tabBtn)
  5811.  
  5812.  
  5813.  
  5814. }
  5815.  
  5816.  
  5817.  
  5818.  
  5819. }
  5820.  
  5821. function handlerMaterialTabClick(/** @type {MouseEvent} */ evt) {
  5822.  
  5823. //console.log(8510)
  5824. let ytdFlexyElm = kRef(ytdFlexy);
  5825. if (!scriptEnable || !ytdFlexyElm) return null;
  5826.  
  5827. let tabBtn = this;
  5828.  
  5829. if (!tabBtn.hasAttribute('userscript-tab-content')) return;
  5830.  
  5831. /** @type {HTMLElement | null} */
  5832. let dom = evt.target;
  5833. if (!dom) return;
  5834.  
  5835. if (dom.classList.contains('font-size-btn')) return;
  5836.  
  5837.  
  5838. evt.preventDefault();
  5839.  
  5840. handlerMaterialTabClickInner(tabBtn);
  5841.  
  5842.  
  5843.  
  5844.  
  5845. }
  5846.  
  5847. function prepareTabBtn() {
  5848.  
  5849. const materialTab = document.querySelector("#material-tabs")
  5850. if (!materialTab) return;
  5851.  
  5852. let noActiveTab = !!document.querySelector('ytd-watch-flexy[tyt-chat^="+"]')
  5853.  
  5854. const activeLink = querySelectorFromAnchor.call(materialTab, 'a[userscript-tab-content].active:not(.tab-btn-hidden)')
  5855. if (activeLink) switchTabActivity(noActiveTab ? null : activeLink)
  5856.  
  5857. if (!tabsUiScript_setclick) {
  5858. tabsUiScript_setclick = true;
  5859. //let _last_click =null;
  5860.  
  5861. $(materialTab).on("click", "a", handlerMaterialTabClick);
  5862.  
  5863. $(materialTab).on("dblclick", "a", function(evt){
  5864.  
  5865. evt.preventDefault();
  5866.  
  5867. });
  5868.  
  5869. function updateCSS_fontsize() {
  5870.  
  5871. let store = getStore();
  5872.  
  5873. let ytdFlexyElm = kRef(ytdFlexy);
  5874. if (ytdFlexyElm) {
  5875. if (store['font-size-#tab-info']) ytdFlexyElm.style.setProperty('--ut2257-info', store['font-size-#tab-info'])
  5876. if (store['font-size-#tab-comments']) ytdFlexyElm.style.setProperty('--ut2257-comments', store['font-size-#tab-comments'])
  5877. if (store['font-size-#tab-videos']) ytdFlexyElm.style.setProperty('--ut2257-videos', store['font-size-#tab-videos'])
  5878. if (store['font-size-#tab-list']) ytdFlexyElm.style.setProperty('--ut2257-list', store['font-size-#tab-list'])
  5879. }
  5880.  
  5881. }
  5882.  
  5883. $(materialTab).on("click", ".font-size-btn", function (evt) {
  5884.  
  5885. evt.preventDefault();
  5886. evt.stopPropagation();
  5887. evt.stopImmediatePropagation();
  5888.  
  5889. /** @type {HTMLElement | null} */
  5890. let dom = evt.target;
  5891. if(!dom) return;
  5892.  
  5893.  
  5894. let value = dom.classList.contains('font-size-plus') ? 1 : dom.classList.contains('font-size-minus') ? -1 : 0;
  5895.  
  5896. let active_tab_content = closestDOM.call(dom, '[userscript-tab-content]').getAttribute('userscript-tab-content');
  5897.  
  5898. let store = getStore();
  5899. let settingKey = `font-size-${active_tab_content}`
  5900. if (!store[settingKey]) store[settingKey] = 1.0;
  5901. if (value < 0) store[settingKey] -= 0.05;
  5902. else if (value > 0) store[settingKey] += 0.05;
  5903. if (store[settingKey] < 0.1) store[settingKey] = 0.1;
  5904. else if (store[settingKey] > 10) store[settingKey] = 10.0;
  5905. setStore(store);
  5906.  
  5907.  
  5908. updateCSS_fontsize();
  5909.  
  5910.  
  5911. //console.log(this.textContent)
  5912.  
  5913.  
  5914. });
  5915.  
  5916. updateCSS_fontsize();
  5917.  
  5918.  
  5919.  
  5920. }
  5921.  
  5922. }
  5923.  
  5924. function setStickyHeader(targetElm, bool, getWidthHeight, getLeftRight) {
  5925.  
  5926. //if(isStickyHeaderEnabled===bool) return; // no update
  5927.  
  5928. if (bool === true) {
  5929. const { width, height } = getWidthHeight();
  5930. targetElm.style.setProperty("--tyt-stickybar-w", width + 'px')
  5931. targetElm.style.setProperty("--tyt-stickybar-h", height + 'px')
  5932. const res = getLeftRight();
  5933. if (res) {
  5934.  
  5935. targetElm.style.setProperty("--tyt-stickybar-l", (res.left) + 'px')
  5936. targetElm.style.setProperty("--tyt-stickybar-r", (res.right) + 'px')
  5937.  
  5938. }
  5939. wAttr(targetElm, 'tyt-stickybar', true);
  5940. isStickyHeaderEnabled = true;
  5941.  
  5942. } else if (bool === false) {
  5943.  
  5944. wAttr(targetElm, 'tyt-stickybar', false);
  5945. isStickyHeaderEnabled = false;
  5946. }
  5947.  
  5948.  
  5949. }
  5950.  
  5951. const singleColumnScrolling = async function () {
  5952. //makeHeaderFloat
  5953. // required for 1) init 2) layout change 3) resizing
  5954.  
  5955. if (!scriptEnable || pageType !== 'watch') return;
  5956.  
  5957. let isTwoCol = (wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS;
  5958. if(isTwoCol){
  5959.  
  5960. if(isStickyHeaderEnabled){
  5961.  
  5962. let targetElm = document.querySelector("#right-tabs");
  5963. setStickyHeader(targetElm, false, null, null);
  5964. }
  5965. return;
  5966. }
  5967. let pageY = scrollY;
  5968.  
  5969.  
  5970. let tdt = Date.now();
  5971. singleColumnScrolling_dt = tdt;
  5972.  
  5973. let targetElm, header, navElm;
  5974.  
  5975.  
  5976. _console.log(7891, 'scrolling')
  5977.  
  5978. function getXYStatus(res){
  5979. const [navHeight, elmY] = res;
  5980.  
  5981. let xyz = [elmY + navHeight, pageY, elmY - navHeight]
  5982.  
  5983. let xyStatus = 0
  5984. if (xyz[1] < xyz[2] && xyz[2] < xyz[0]) {
  5985. // 1
  5986. xyStatus = 1
  5987. }
  5988.  
  5989. if (xyz[0] > xyz[1] && xyz[1] > xyz[2]) {
  5990.  
  5991. //2
  5992. xyStatus = 2
  5993.  
  5994. }
  5995.  
  5996. if (xyz[2] < xyz[0] && xyz[0] < xyz[1]) {
  5997. // 3
  5998.  
  5999. xyStatus = 3
  6000.  
  6001.  
  6002. }
  6003.  
  6004. return xyStatus;
  6005. }
  6006.  
  6007. Promise.all([
  6008. new Promise(f => f(document.querySelector("#right-tabs"))),
  6009.  
  6010. new Promise(f => f(document.querySelector("#right-tabs header"))),
  6011.  
  6012. new Promise(f => f(document.querySelector('#masthead-container, #masthead'))),
  6013.  
  6014. ]).then(res => {
  6015. const [_targetElm, _header, _navElm] = res;
  6016. targetElm = _targetElm;
  6017. header = _header;
  6018. navElm = _navElm;
  6019. if (!targetElm || !header) {
  6020. return null;
  6021. }
  6022. if (singleColumnScrolling_dt !== tdt) return null;
  6023. return Promise.all([
  6024. new Promise(f => f(navElm ? navElm.offsetHeight : 0)),
  6025. new Promise(f => f(targetElm.offsetTop))
  6026. ])
  6027. }).then(res => {
  6028.  
  6029. if (res === null) return;
  6030.  
  6031. if (singleColumnScrolling_dt !== tdt) return null;
  6032.  
  6033.  
  6034. const xyStatus = getXYStatus(res);
  6035.  
  6036.  
  6037. function getLeftRight() {
  6038.  
  6039. let thp = document.querySelector('tabview-tabs-header-position');
  6040. if (thp) {
  6041.  
  6042. let rect = thp.getBoundingClientRect()
  6043. if (rect) {
  6044. return {
  6045. left: rect.left,
  6046. right: document.documentElement.clientWidth - rect.right
  6047. };
  6048. }
  6049. }
  6050. return null;
  6051. }
  6052.  
  6053. let bool = (xyStatus == 2 || xyStatus == 3) ? true : ((xyStatus == 1) ? false : null);
  6054.  
  6055. function getWidthHeight() {
  6056. return { width: targetElm.offsetWidth, height: header.offsetHeight };
  6057. }
  6058.  
  6059. setStickyHeader(targetElm, bool, getWidthHeight, getLeftRight);
  6060.  
  6061.  
  6062.  
  6063. }).then(() => {
  6064.  
  6065. targetElm = null;
  6066. header = null;
  6067. navElm = null;
  6068.  
  6069. });
  6070.  
  6071. };
  6072.  
  6073.  
  6074.  
  6075. const singleColumnScrolling2 = async function (xyStatus, width, xRect) {
  6076. //makeHeaderFloat
  6077.  
  6078. if (!scriptEnable || pageType !== 'watch') return;
  6079.  
  6080.  
  6081. if ((wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) {
  6082. return;
  6083. }
  6084.  
  6085. Promise.all([
  6086. new Promise(f => f(document.querySelector("#right-tabs"))),
  6087.  
  6088. new Promise(f => f(document.querySelector("#right-tabs header")))
  6089.  
  6090. ]).then(res => {
  6091.  
  6092. const [targetElm, header] = res;
  6093. if (!targetElm || !header) {
  6094. return null;
  6095. }
  6096.  
  6097. function getLeftRight() {
  6098. return xRect;
  6099. }
  6100.  
  6101. let bool = (xyStatus == 2 || xyStatus == 3) ? true : ((xyStatus == 1) ? false : null);
  6102.  
  6103. function getWidthHeight() {
  6104. return { width: (width || targetElm.offsetWidth), height: header.offsetHeight };
  6105. }
  6106.  
  6107. setStickyHeader(targetElm, bool, getWidthHeight, getLeftRight);
  6108.  
  6109.  
  6110.  
  6111. })
  6112.  
  6113. };
  6114.  
  6115.  
  6116.  
  6117. function resetBuggyLayoutForNewVideoPage() {
  6118.  
  6119.  
  6120. let ytdFlexyElm = kRef(ytdFlexy);
  6121. if (!ytdFlexyElm) return;
  6122.  
  6123. //(flexy is visible and watch video page)
  6124.  
  6125. scriptEnable = true;
  6126.  
  6127. _console.log(27056)
  6128.  
  6129.  
  6130. let new_layoutStatus = wls.layoutStatus
  6131.  
  6132. new_layoutStatus & (LAYOUT_CHATROOM_COLLAPSED | LAYOUT_CHATROOM)
  6133.  
  6134. const new_isExpandedChat = !(new_layoutStatus & LAYOUT_CHATROOM_COLLAPSED) && !!(new_layoutStatus & LAYOUT_CHATROOM)
  6135. const new_isCollapsedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_COLLAPSED) && !!(new_layoutStatus & LAYOUT_CHATROOM)
  6136.  
  6137. const new_isTwoColumns = new_layoutStatus & LAYOUT_TWO_COLUMNS;
  6138. const new_isTheater = new_layoutStatus & LAYOUT_THEATER;
  6139. const new_isTabExpanded = new_layoutStatus & LAYOUT_TAB_EXPANDED;
  6140. const new_isFullScreen = new_layoutStatus & LAYOUT_FULLSCREEN;
  6141. const new_isExpandEPanel = new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPAND;
  6142.  
  6143. if (ytdFlexyElm.getAttribute('tyt-tab') === '' && new_isTwoColumns && !new_isTheater && !new_isTabExpanded && !new_isFullScreen && !new_isExpandEPanel && !new_isExpandedChat) {
  6144. // e.g. engage panel removed after miniview and change video
  6145. setToActiveTab();
  6146. } else if (new_isExpandEPanel && querySelectorAllFromAnchor.call(ytdFlexyElm, 'ytd-engagement-panel-section-list-renderer[visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]').length === 0) {
  6147. wls.layoutStatus = new_layoutStatus & (~LAYOUT_ENGAGEMENT_PANEL_EXPAND)
  6148. }
  6149.  
  6150.  
  6151.  
  6152.  
  6153. }
  6154.  
  6155.  
  6156. function extractInfoFromLiveChatRenderer(liveChatRenderer) {
  6157.  
  6158. let lcr = liveChatRenderer
  6159.  
  6160. let data_shb = ((lcr || 0).showHideButton || 0).toggleButtonRenderer
  6161.  
  6162. let default_display_state = null, txt_collapse = null, txt_expand = null;
  6163.  
  6164.  
  6165. if (data_shb && data_shb.defaultText && data_shb.toggledText && data_shb.defaultText.runs && data_shb.toggledText.runs) {
  6166.  
  6167. if (data_shb.defaultText.runs.length === 1 && data_shb.toggledText.runs.length === 1) {
  6168.  
  6169. if (lcr.initialDisplayState == "LIVE_CHAT_DISPLAY_STATE_EXPANDED") {
  6170.  
  6171. default_display_state = lcr.initialDisplayState
  6172.  
  6173. txt_collapse = (data_shb.defaultText.runs[0] || 0).text // COLLAPSE the area
  6174.  
  6175. txt_expand = (data_shb.toggledText.runs[0] || 0).text // expand the area
  6176.  
  6177. } else if (lcr.initialDisplayState == "LIVE_CHAT_DISPLAY_STATE_COLLAPSED") {
  6178. default_display_state = lcr.initialDisplayState
  6179.  
  6180. txt_expand = (data_shb.defaultText.runs[0] || 0).text // expand the area
  6181.  
  6182. txt_collapse = (data_shb.toggledText.runs[0] || 0).text // COLLAPSE the area
  6183. }
  6184.  
  6185.  
  6186.  
  6187. if (typeof txt_expand == 'string' && typeof txt_collapse == 'string' && txt_expand.length > 0 && txt_collapse.length > 0) {
  6188.  
  6189. } else {
  6190. txt_expand = null;
  6191. txt_collapse = null;
  6192. }
  6193. }
  6194.  
  6195. }
  6196.  
  6197. return { default_display_state, txt_collapse, txt_expand }
  6198.  
  6199. }
  6200.  
  6201.  
  6202. function newVideoPage(evt_detail) {
  6203.  
  6204. //toggleBtnDC = 1;
  6205.  
  6206. console.log('newVideoPage')
  6207.  
  6208.  
  6209.  
  6210. //console.log('newVideoPage-', 150, location.href)
  6211.  
  6212. let ytdFlexyElm = kRef(ytdFlexy);
  6213. if (!ytdFlexyElm) return;
  6214.  
  6215.  
  6216. timeline.reset();
  6217. layoutStatusMutex = new Mutex();
  6218.  
  6219. //console.log('newVideoPage-', 350, location.href)
  6220.  
  6221. let liveChatRenderer = null;
  6222. let isReplay = null;
  6223.  
  6224. if (pageType !== 'watch') return; // scriptEnable -> pageType shall be always 'watch'
  6225. resetBuggyLayoutForNewVideoPage();
  6226. advanceFetch();
  6227.  
  6228. try {
  6229. liveChatRenderer = evt_detail.pageData.response.contents.twoColumnWatchNextResults.conversationBar.liveChatRenderer
  6230. } catch (e) { }
  6231. if (liveChatRenderer) {
  6232. if (liveChatRenderer.isReplay === true) isReplay = true;
  6233. }
  6234.  
  6235.  
  6236. const chatBlockR = liveChatRenderer ? (isReplay ? 3 : 1) : 0
  6237. const initialDisplayState = liveChatRenderer ? liveChatRenderer.initialDisplayState : null;
  6238.  
  6239. let f = () => {
  6240.  
  6241. _console.log(932, 1, 1)
  6242. let ytdFlexyElm = kRef(ytdFlexy);
  6243. if (!scriptEnable || !ytdFlexyElm) return;
  6244.  
  6245. _console.log(932, 1, 2)
  6246. if (pageType !== 'watch') return;
  6247.  
  6248. _console.log(932, 1, 3)
  6249.  
  6250.  
  6251. let displayState = setupChatFrameDisplayState1(chatBlockR, initialDisplayState);
  6252.  
  6253. let {attr_chatblock, attr_chatcollapsed, chatTypeChanged} = displayState;
  6254.  
  6255.  
  6256.  
  6257.  
  6258.  
  6259. if (pageType === 'watch') { // reset info when hidden
  6260. checkVisibleEngagementPanel();
  6261. }
  6262.  
  6263. if (chatTypeChanged) {
  6264.  
  6265. if (attr_chatblock == 'chat$live') {
  6266.  
  6267. _console.log(932, 4)
  6268.  
  6269. mtf_forceCheckLiveVideo_disable = 2;
  6270.  
  6271. //console.log(3712,1)
  6272.  
  6273. _disableComments();
  6274.  
  6275.  
  6276. } else {
  6277.  
  6278.  
  6279.  
  6280. const tabBtn = document.querySelector('[userscript-tab-content="#tab-comments"].tab-btn-hidden')
  6281. if (tabBtn) {
  6282. emptyCommentSection();
  6283. _console.log(9360, 74);
  6284. tabBtn.classList.remove("tab-btn-hidden")
  6285. }else{
  6286. setCommentSection(0);
  6287. }
  6288.  
  6289. mtf_forceCheckLiveVideo_disable = 0;
  6290.  
  6291. _console.log(7234, 'comments_section_loaded = 0')
  6292. restoreFetching();
  6293.  
  6294.  
  6295. }
  6296.  
  6297.  
  6298. } else {
  6299.  
  6300. // restore Fetching only
  6301.  
  6302.  
  6303. if (mtf_forceCheckLiveVideo_disable !== 2 && (attr_chatblock === false || attr_chatblock === 'chat$playback')) {
  6304.  
  6305. emptyCommentSection();
  6306. _console.log(9360, 77);
  6307. mtf_forceCheckLiveVideo_disable = 0;
  6308. _console.log(7235, 'comments_section_loaded = 0')
  6309. restoreFetching();
  6310.  
  6311. }
  6312.  
  6313.  
  6314.  
  6315. }
  6316.  
  6317.  
  6318. if ( renderDeferred.resolved && Q.comments_section_loaded === 0 && fetchCounts.new && !fetchCounts.fetched) {
  6319. fetchCounts.new.f();
  6320. fetchCounts.fetched = true;
  6321. _console.log(9972, 'fetched = true')
  6322.  
  6323. fetchCommentsFinished();
  6324. }
  6325.  
  6326. }
  6327.  
  6328. f();
  6329.  
  6330.  
  6331.  
  6332.  
  6333.  
  6334. }
  6335.  
  6336.  
  6337. function setVideosTwoColumns(/** @type {number} */ flag, /** @type {boolean} */ bool) {
  6338.  
  6339. //two columns to one column
  6340.  
  6341. /*
  6342. [placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy
  6343.  
  6344. is-two-columns ="" => no is-two-columns
  6345. [placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer
  6346. no hidden => hidden =""
  6347. [placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer
  6348. hidden ="" => no hidden
  6349.  
  6350. */
  6351.  
  6352. let cssSelector1 = '[placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy'
  6353.  
  6354. let cssSelector2 = '[placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer'
  6355.  
  6356. let cssSelector3 = '[placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer'
  6357.  
  6358. let res = {}
  6359. if (flag & 1) {
  6360. res.m1 = document.querySelector(cssSelector1)
  6361. if (res.m1) wAttr(res.m1, 'is-two-columns', bool ? '' : false);
  6362. }
  6363.  
  6364. if (flag & 2) {
  6365. res.m2 = document.querySelector(cssSelector2)
  6366. if (res.m2) wAttr(res.m2, 'hidden', bool ? false : '');
  6367. }
  6368.  
  6369. if (flag & 4) {
  6370. res.m3 = document.querySelector(cssSelector3)
  6371. if (res.m3) wAttr(res.m3, 'hidden', bool ? '' : false);
  6372. }
  6373.  
  6374.  
  6375. return res
  6376.  
  6377.  
  6378.  
  6379.  
  6380. }
  6381.  
  6382. // ---------------------------------------------------------------------------------------------
  6383.  
  6384. // ---- EVENTS ----
  6385.  
  6386.  
  6387.  
  6388. let pageStartLoad = (evt) => {
  6389. pageBeingInit();
  6390. }
  6391.  
  6392.  
  6393. globalHook('yt-page-data-fetched', generalLog901)
  6394.  
  6395. //globalHook('yt-rendererstamper-finished',generalLog901)
  6396.  
  6397. globalHook('yt-page-data-updated', generalLog901)
  6398.  
  6399. globalHook('yt-player-updated', generalLog901)
  6400.  
  6401. globalHook('yt-watch-comments-ready', generalLog901)
  6402.  
  6403. globalHook('yt-page-type-changed', generalLog901)
  6404.  
  6405. globalHook('data-changed', generalLog901)
  6406.  
  6407. globalHook('yt-navigate-finish', generalLog901)
  6408.  
  6409. globalHook('yt-navigate-redirect', generalLog901)
  6410.  
  6411. globalHook('yt-navigate-error', generalLog901)
  6412.  
  6413. globalHook('yt-navigate-start', generalLog901)
  6414.  
  6415. globalHook('yt-page-manager-navigate-start', generalLog901)
  6416.  
  6417. globalHook('yt-navigate', generalLog901)
  6418. globalHook('yt-navigate-cache', generalLog901)
  6419.  
  6420.  
  6421.  
  6422. globalHook('yt-navigate',pageStartLoad);
  6423. globalHook('yt-navigate-start', pageStartLoad);
  6424. globalHook('yt-navigate-cache',pageStartLoad);
  6425. globalHook('yt-navigate-redirect', pageStartLoad);
  6426.  
  6427.  
  6428. globalHook('yt-page-data-fetched', (evt) => {
  6429.  
  6430. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  6431. let nodeName = evt.target.nodeName.toUpperCase()
  6432. if (nodeName !== 'YTD-APP') return;
  6433.  
  6434. let d_page = ((evt.detail || 0).pageData || 0).page;
  6435. if (!d_page) return;
  6436.  
  6437. _console.log(nodeName, 904, evt.type);
  6438.  
  6439. //in case yt-navigate-xxx not called.
  6440. pageBeingInit();
  6441.  
  6442. pageType = d_page;
  6443.  
  6444. advanceFetch();
  6445.  
  6446. document.documentElement.classList.toggle('tabview-normal-player', d_page === 'watch');
  6447. _console.log(601, evt.detail);
  6448.  
  6449. if(d_page === 'watch'){
  6450. //dispatchWindowResize(); // player control positioning //try to omit
  6451. pageFetchedData = evt.detail;
  6452. /*
  6453. console.log(pageFetchedData)
  6454. try{
  6455.  
  6456. let desc1=pageFetchedData.pageData.response.contents.twoColumnWatchNextResults.results.results.contents[1].videoSecondaryInfoRenderer.description;
  6457.  
  6458.  
  6459. let desc2=pageFetchedData.pageData.response.engagementPanels[2].engagementPanelSectionListRenderer.content.structuredDescriptionContentRenderer.items[1].expandableVideoDescriptionBodyRenderer.descriptionBodyText;
  6460.  
  6461. console.log(desc1===desc2, JSON.stringify(desc1),JSON.stringify(desc2), desc1, desc2)
  6462.  
  6463.  
  6464. console.log(pageFetchedData.pageData)
  6465.  
  6466. }catch(e){}
  6467. */
  6468. }else{
  6469. pageFetchedData = null;
  6470. }
  6471.  
  6472. new Promise(resolve=>{
  6473.  
  6474.  
  6475. let liveChatRenderer = null;
  6476. try {
  6477. liveChatRenderer = evt.detail.pageData.response.contents.twoColumnWatchNextResults.conversationBar.liveChatRenderer
  6478. } catch (e) { }
  6479. chatroomDetails = liveChatRenderer ? extractInfoFromLiveChatRenderer(liveChatRenderer) : null;
  6480. let ytdFlexyElm = document.querySelector('ytd-watch-flexy');
  6481. if (ytdFlexyElm) {
  6482. ytdFlexyElm.classList.toggle('tyt-chat-toggleable', !!chatroomDetails);
  6483. }
  6484.  
  6485. resolve(0)
  6486.  
  6487. }).then(()=>{
  6488.  
  6489.  
  6490. let tabsDeferredSess = pageSession.session();
  6491. if (!scriptEnable && tabsDeferred.resolved) { }
  6492. else tabsDeferred.debounce(() => {
  6493. if (!tabsDeferredSess.isValid) return;
  6494. domInit_comments();
  6495. if (pageFetchedData !== null) {
  6496. newVideoPage(pageFetchedData);
  6497. let ytdFlexyElm = document.querySelector('ytd-watch-flexy');
  6498. if(ytdFlexyElm){
  6499. if(pageRendered===0){
  6500. handleDOMAppear('pageLoaderAnimation',(evt)=>{
  6501. //console.log(3434)
  6502. pageRendered = 2;
  6503. renderDeferred.resolve();
  6504. console.log('pageLoaded(front)')
  6505. });
  6506. let elmPL = document.createElement('tabview-page-loader');
  6507. pageRendered = 1;
  6508. ytdFlexyElm.appendChild(elmPL);
  6509. // pageRendered keeps at 1 if the video is continuously playing at the background
  6510. // pageRendered would not be resolve but will reset for each change of video
  6511. }
  6512. }
  6513. }
  6514. });
  6515.  
  6516. })
  6517.  
  6518.  
  6519.  
  6520.  
  6521.  
  6522.  
  6523.  
  6524. })
  6525.  
  6526. document.addEventListener("yt-navigate-finish", onNavigationEnd, bubblePassive)
  6527. //yt-navigate-redirect
  6528. //"yt-page-data-fetched"
  6529. //yt-navigate-error
  6530. //yt-navigate-start
  6531. //yt-page-manager-navigate-start
  6532. //"yt-navigate"
  6533. //"yt-navigate-cache
  6534.  
  6535. document.addEventListener("yt-page-manager-navigate-start", () => {
  6536. console.log('yt-page-manager-navigate-start')
  6537. // forceConfig();
  6538. }, bubblePassive)
  6539.  
  6540.  
  6541.  
  6542. // ---------------------------------------------------------------------------------------------
  6543.  
  6544.  
  6545. function manualResize(isTrusted){
  6546.  
  6547. if (!scriptEnable) return;
  6548. if (pageType !== 'watch') return;
  6549. //lastResizeAt = Date.now();
  6550.  
  6551. _console.log(2233, isTrusted)
  6552.  
  6553. if ((wls.layoutStatus & LAYOUT_TWO_COLUMNS) === 0 ) {
  6554.  
  6555. requestAnimationFrame(() => {
  6556. singleColumnScrolling(true)
  6557. })
  6558.  
  6559. }else if(isTrusted) {
  6560.  
  6561. updateFloatingSlider();
  6562.  
  6563. }
  6564.  
  6565.  
  6566. }
  6567. //let lastResizeAt = 0;
  6568. window.addEventListener('resize', function (evt) {
  6569.  
  6570. if (evt.isTrusted === true) {
  6571. //console.log(evt)
  6572. let tcw = document.documentElement.clientWidth;
  6573. setTimeout(() => {
  6574. // avoid duplicate calling during resizing
  6575. if (tcw !== document.documentElement.clientWidth) return;
  6576. manualResize(true);
  6577. dispatchCommentRowResize();
  6578. }, 160)
  6579. }
  6580.  
  6581. }, bubblePassive)
  6582.  
  6583.  
  6584.  
  6585. /*
  6586. document.addEventListener('wheel', function (evt) {
  6587.  
  6588. if (!scriptEnable) return;
  6589. const displayedPlaylist_element = kRef(displayedPlaylist);
  6590. if (displayedPlaylist_element && elementContains.call(displayedPlaylist_element, evt.target)) {
  6591. evt.stopPropagation();
  6592. evt.stopImmediatePropagation();
  6593. }
  6594. }, capturePassive);
  6595. */
  6596.  
  6597.  
  6598.  
  6599.  
  6600.  
  6601. document.documentElement.setAttribute('plugin-tabview-youtube', `${scriptVersionForExternal}`)
  6602.  
  6603.  
  6604. function nestedObjectFlatten(prefix, obj) {
  6605. let ret = {};
  6606. let _prefix = prefix ? `${prefix}.` : '';
  6607. let isObject = (obj && typeof obj == 'object' && obj.constructor.name == 'Object');
  6608. let isArray = (obj && typeof obj == 'object' && obj.constructor.name == 'Array');
  6609. const f = (k, v) => {
  6610. let isObject = (v && typeof v == 'object' && v.constructor.name == 'Object');
  6611. let isArray = (v && typeof v == 'object' && v.constructor.name == 'Array');
  6612.  
  6613. if (isObject || isArray) {
  6614.  
  6615. let r = nestedObjectFlatten(k, v)
  6616. for (const w in r) {
  6617. ret[`${_prefix}${w}`] = r[w];
  6618. }
  6619. } else {
  6620. ret[`${_prefix}${k}`] = v;
  6621. }
  6622. }
  6623. if (isObject) {
  6624.  
  6625. for (const k in obj) {
  6626. let v = obj[k];
  6627. f(k, v);
  6628. }
  6629.  
  6630. } else if (isArray) {
  6631.  
  6632. let idx = 0;
  6633.  
  6634. for (const v of obj) {
  6635. let k = `[${idx}]`;
  6636. f(k, v);
  6637.  
  6638.  
  6639. idx++;
  6640. }
  6641. }
  6642. return ret;
  6643. }
  6644.  
  6645. /*
  6646.  
  6647. for(const p of document.querySelectorAll('ytd-watch-flexy *')){ let m = p.data; if(!m)continue; console.log(m)}
  6648.  
  6649. function objec
  6650.  
  6651. */
  6652.  
  6653.  
  6654. //Object.keys($0).filter(key=>!(key in $0.constructor.prototype))
  6655.  
  6656. //Object.getOwnPropertyNames(window).filter(k=>k.startsWith('HTML'))
  6657. //Object.getOwnPropertyNames(window).filter(k=>k.startsWith('HTML')).filter(k=>$0 instanceof window[k])
  6658.  
  6659.  
  6660. /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */
  6661.  
  6662. /*
  6663. fix bug for comment section - version 1.8.7
  6664. This issue is the bug in browser's rendering
  6665. I guess, this is due to the lines clamp with display:-webkit-box
  6666. use stupid coding to let it re-render when its content become visible
  6667. /*
  6668.  
  6669. ytd-expander[should-use-number-of-lines][collapsed] > #content.ytd-expander {
  6670. color: var(--yt-spec-text-primary);
  6671. display: -webkit-box;
  6672. overflow: hidden;
  6673. max-height: none;
  6674. -webkit-box-orient: vertical;
  6675. -webkit-line-clamp: var(--ytd-expander-max-lines, 4);
  6676. }
  6677.  
  6678. // v1.8.36 imposed a effective solution for fixing this bug
  6679.  
  6680. */
  6681.  
  6682. /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */
  6683.  
  6684.  
  6685. /**
  6686. *
  6687.  
  6688.  
  6689. f.initChildrenObserver=function(){var a=this;this.observer=new MutationObserver(function(){return a.childrenChanged()});
  6690. this.observer.observe(this.$.content,{subtree:!0,childList:!0,attributes:!0});this.childrenChanged()};
  6691. f.childrenChanged=function(){var a=this;this.alwaysToggleable?this.canToggle=this.alwaysToggleable:this.canToggleJobId||(this.canToggleJobId=window.requestAnimationFrame(function(){$h(function(){a.canToggleJobId=0;a.calculateCanCollapse()})}))};
  6692.  
  6693.  
  6694. f.onIronResize=function(){this.recomputeOnResize&&this.childrenChanged()};
  6695.  
  6696.  
  6697. onButtonClick_:function(){this.fire("yt-close-upsell-dialog")},
  6698. computeHasHeader_:function(a){return!!a.headerBackgroundImage}});var geb;var heb;var ieb;var jeb;var xI=function(){var a=L.apply(this,arguments)||this;a.alignAuto=!1;a.collapsed=!0;a.isToggled=!1;a.alwaysCollapsed=!1;a.canToggle=!0;a.collapsedHeight=80;a.disableToggle=!1;a.alwaysToggleable=!1;a.reversed=!1;a.shouldUseNumberOfLines=!1;a.recomputeOnResize=!1;a.canToggleJobId=0;return a};
  6699. n(xI,L);f=xI.prototype;f.alwaysToggleableChanged=function(){this.alwaysToggleable&&(this.canToggle=!0)};
  6700.  
  6701.  
  6702. f.calculateCanCollapse=function(){this.canToggle=this.shouldUseNumberOfLines?this.alwaysToggleable||this.$.content.offsetHeight<this.$.content.scrollHeight:this.alwaysToggleable||this.$.content.scrollHeight>this.collapsedHeight};
  6703. f.detachObserver=function(){this.observer&&this.observer.disconnect()};
  6704.  
  6705. *
  6706. *
  6707. *
  6708. */
  6709.  
  6710.  
  6711.  
  6712.  
  6713. })();
  6714.  
  6715.  
  6716.  
  6717.  
  6718.  
  6719.  
  6720.  
  6721.  
  6722.  
  6723.  
  6724.  
  6725.  
  6726.  
  6727.  
  6728.  
  6729.  
  6730.  
  6731.  
  6732.  
  6733.  
  6734.  
  6735.  
  6736.  
  6737.  
  6738.  
  6739.  
  6740.  
  6741.  
  6742.  
  6743.  
  6744.  
  6745.  
  6746.  
  6747.  
  6748.  
  6749.  
  6750.  
  6751.  
  6752.  
  6753. // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js
  6754.  
  6755. }
  6756.  
  6757.  
  6758. ;!(function $$() {
  6759. 'use strict';
  6760.  
  6761. if(document.documentElement==null) return window.requestAnimationFrame($$)
  6762.  
  6763. const cssTxt = GM_getResourceText("contentCSS");
  6764.  
  6765. function addStyle (styleText) {
  6766. const styleNode = document.createElement('style');
  6767. styleNode.type = 'text/css';
  6768. styleNode.textContent = styleText;
  6769. document.documentElement.appendChild(styleNode);
  6770. return styleNode;
  6771. }
  6772.  
  6773. addStyle (cssTxt);
  6774.  
  6775. main(window.$);
  6776.  
  6777.  
  6778. // Your code here...
  6779. })();