Tabview Youtube

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

目前为 2023-09-18 提交的版本。查看 最新版本

  1. /*
  2.  
  3. MIT License
  4.  
  5. Copyright (c) 2021-2023 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. // @description Make comments and lists into tabs for YouTube Videos
  30. // @description:en Make comments and lists into tabs for YouTube Videos
  31. // @name:ja Tabview Youtube
  32. // @description:ja YouTube動画のコメントやリストなどをタブに作成します
  33. // @name:ko Tabview Youtube
  34. // @description:ko YouTube 동영상의 댓글 및 목록을 탭으로 만듭니다
  35. // @name:zh-TW Tabview Youtube
  36. // @name:zh-HK Tabview Youtube
  37. // @name:zh-CN Tabview Youtube
  38. // @description:zh-TW 把Youtube Videos中的評論及影片清單製作成Tabs
  39. // @description:zh-HK 把Youtube Videos中的評論及影片清單製作成Tabs
  40. // @description:zh-CN 把Youtube Videos中的评论及视频列表制作成Tabs
  41. // @name:ru Tabview Youtube
  42. // @description:ru Сделайте описание, комментарии и список видео в виде вкладок для видео на YouTube
  43.  
  44. // @name:af Tabview Youtube
  45. // @name:az Tabview Youtube
  46. // @name:id Tabview Youtube
  47. // @name:ms Tabview Youtube
  48. // @name:bs Tabview Youtube
  49. // @name:ca Tabview Youtube
  50. // @name:cs Tabview Youtube
  51. // @name:da Tabview Youtube
  52. // @name:de Tabview Youtube
  53. // @name:et Tabview Youtube
  54. // @name:es Tabview Youtube
  55. // @name:eu Tabview Youtube
  56. // @name:fr Tabview Youtube
  57. // @name:gl Tabview Youtube
  58. // @name:hr Tabview Youtube
  59. // @name:zu Tabview Youtube
  60. // @name:is Tabview Youtube
  61. // @name:it Tabview Youtube
  62. // @name:sw Tabview Youtube
  63. // @name:lv Tabview Youtube
  64. // @name:lt Tabview Youtube
  65. // @name:hu Tabview Youtube
  66. // @name:nl Tabview Youtube
  67. // @name:uz Tabview Youtube
  68. // @name:pl Tabview Youtube
  69. // @name:pt Tabview Youtube
  70. // @name:pt-BR Tabview Youtube
  71. // @name:ro Tabview Youtube
  72. // @name:sq Tabview Youtube
  73. // @name:sk Tabview Youtube
  74. // @name:sl Tabview Youtube
  75. // @name:sr Tabview Youtube
  76. // @name:fi Tabview Youtube
  77. // @name:sv Tabview Youtube
  78. // @name:vi Tabview Youtube
  79. // @name:tr Tabview Youtube
  80. // @name:be Tabview Youtube
  81. // @name:bg Tabview Youtube
  82. // @name:ky Tabview Youtube
  83. // @name:kk Tabview Youtube
  84. // @name:mk Tabview Youtube
  85. // @name:mn Tabview Youtube
  86. // @name:uk Tabview Youtube
  87. // @name:el Tabview Youtube
  88. // @name:hy Tabview Youtube
  89. // @name:ur Tabview Youtube
  90. // @name:ar Tabview Youtube
  91. // @name:fa Tabview Youtube
  92. // @name:ne Tabview Youtube
  93. // @name:mr Tabview Youtube
  94. // @name:hi Tabview Youtube
  95. // @name:as Tabview Youtube
  96. // @name:bn Tabview Youtube
  97. // @name:pa Tabview Youtube
  98. // @name:gu Tabview Youtube
  99. // @name:or Tabview Youtube
  100. // @name:ta Tabview Youtube
  101. // @name:te Tabview Youtube
  102. // @name:kn Tabview Youtube
  103. // @name:ml Tabview Youtube
  104. // @name:si Tabview Youtube
  105. // @name:th Tabview Youtube
  106. // @name:lo Tabview Youtube
  107. // @name:my Tabview Youtube
  108. // @name:ka Tabview Youtube
  109. // @name:am Tabview Youtube
  110. // @name:km Tabview Youtube
  111.  
  112.  
  113. // @description:af Maak kommentaar en lyste as oortjies vir YouTube-video's
  114. // @description:az YouTube Videoları üçün şərhləri və siyahıları tablara çevirin
  115. // @description:id Ubah komentar dan daftar menjadi tab untuk Video YouTube
  116. // @description:ms Ubah komen dan senarai menjadi tab untuk Video YouTube
  117. // @description:bs Pretvorite komentare i liste u kartice za YouTube videozapise
  118. // @description:ca Converteix comentaris i llistes en pestanyes per a Vídeos de YouTube
  119. // @description:cs Převeďte komentáře a seznamy na karty pro YouTube videa
  120. // @description:da Lav kommentarer og lister til faner for YouTube-videoer
  121. // @description:de Machen Sie Kommentare und Listen zu Tabs für YouTube-Videos
  122. // @description:et Muutke kommentaarid ja loendid YouTube'i videote jaoks kaartideks
  123. // @description:es Convierte los comentarios y listas en pestañas para los Videos de YouTube
  124. // @description:eu Egin iruzkinak eta zerrendak YouTube Bideoetarako fitxetan
  125. // @description:fr Transformez les commentaires et les listes en onglets pour les vidéos YouTube
  126. // @description:gl Converte comentarios e listas en lapelas para Vídeos de YouTube
  127. // @description:hr Pretvorite komentare i popise u kartice za YouTube videe
  128. // @description:zu Yenza ukubhala phansi kanye nemingcele ukuba yiithebhu kuVidiyo ze-YouTube
  129. // @description:is Breyttu athugasemdum og listum í flipa fyrir YouTube myndbönd
  130. // @description:it Trasforma commenti e liste in schede per i Video di YouTube
  131. // @description:sw Geuza maoni na orodha kuwa vichupo kwa Video za YouTube
  132. // @description:lv Pārveidojiet komentārus un sarakstus cilnēs YouTube video
  133. // @description:lt Paverčia komentarus ir sąrašus skirtukais YouTube vaizdo įrašams
  134. // @description:hu Alakítsa át a megjegyzéseket és listákat fülekké a YouTube videókhoz
  135. // @description:nl Maak van reacties en lijsten tabs voor YouTube-video's
  136. // @description:uz YouTube videolar uchun sharhlar va ro'yxatlarni ichki oynalar qiling
  137. // @description:pl Przekształć komentarze i listy w karty dla filmów na YouTube
  138. // @description:pt Transforme comentários e listas em abas para Vídeos do YouTube
  139. // @description:pt-BR Transforme comentários e listas em abas para Vídeos do YouTube
  140. // @description:ro Transformă comentariile și listele în file pentru Videoclipuri YouTube
  141. // @description:sq Kthe komentet dhe listat në skeda për Videot në YouTube
  142. // @description:sk Premente komentáre a zoznamy na karty pre YouTube videá
  143. // @description:sl Pretvori komentarje in sezname v zavihke za YouTube videe
  144. // @description:sr Pretvorite komentare i liste u kartice za YouTube videe
  145. // @description:fi Muuta kommentit ja luettelot välilehdiksi YouTube-videoille
  146. // @description:sv Gör kommentarer och listor till flikar för YouTube-videor
  147. // @description:vi Chuyển đổi bình luận và danh sách thành tab cho Video YouTube
  148. // @description:tr Yorumları ve listeleri YouTube Videoları için sekmelere dönüştürün
  149. // @description:be Пераўтварыце каментарыі і спісы ў закладкі для відэа на YouTube
  150. // @description:bg Превърнете коментарите и списъците в раздели за видеоклипове в YouTube
  151. // @description:ky YouTube видеолору үчүн эскертүүлөрдү жана тизмелерди табдыктарга айлантырыңыз
  152. // @description:kk Пікірлер мен тізімдерді YouTube видеолары үшін қоймақтарға айналдырыңыз
  153. // @description:mk Претворете ги коментарите и листите во јазичиња за Видеа на YouTube
  154. // @description:mn YouTube видео дэх сэтгэгдлүүд болон жагсаалтыг табчууд болгоно уу
  155. // @description:uk Зробіть коментарі та списки у вкладки для відео на YouTube
  156. // @description:el Μετατρέψτε τα σχόλια και τις λίστες σε καρτέλες για τα βίντεο του YouTube
  157. // @description:hy Վերածեք մեկնաբանությունները և ցուցակները YouTube տեսանյութերի ներդիրների
  158. // @description:ur YouTube ویڈیوز کے لئے تبصرے اور فہرستوں کو ٹیب میں تبدیل کریں
  159. // @description:ar قم بتحويل التعليقات والقوائم إلى علامات تبويب لفيديوهات YouTube
  160. // @description:fa نظرات و فهرست ها را به زبانه ها برای ویدیوهای YouTube تبدیل کنید
  161. // @description:ne YouTube भिडियोहरूका लागि प्रतिक्रिया र सूचीहरूलाई ट्याबहरूमा परिवर्तन गर्नुहोस्
  162. // @description:mr YouTube व्हिडिओसाठी टिप्पण्या आणि यादीतबांमध्ये करा
  163. // @description:hi YouTube वीडियो के लिए टिप्पणियाँ और सूचियों को टैब में बदलें
  164. // @description:as YouTube ভিডিঅ'ৰ বাবে মন্তব্য আৰু তালিকাসমূহ টেবলত পৰিণত কৰক
  165. // @description:bn YouTube ভিডিওর জন্য মন্তব্য এবং তালিকা ট্যাবে পরিণত করুন
  166. // @description:pa ਯੂਟਿਊਬ ਵੀਡੀਓਜ਼ ਲਈ ਟਿੱਪਣੀਆਂ ਅਤੇ ਸੂਚੀਆਂ ਨੂੰ ਟੈਬਾਂ ਵਿੱਚ ਬਦਲੋ
  167. // @description:gu YouTube વિડિઓ માટે ટિપ્પણીઓ અને યાદીઓ ટૅબમાં બદલો
  168. // @description:or YouTube ଭିଡିଓ ପାଇଁ ମନ୍ତବ୍ୟ ଏବଂ ତାଲିକାଗୁଡ଼ିକ ଟ୍ୟାବମାନେ ପରିବର୍ତ୍ତନ କରନ୍ତୁ
  169. // @description:ta YouTube வீடியோக்கான கருத்துக்கள் மற்றும் பட்டியல்களை தாவல்களாக மாற்றவும்
  170. // @description:te YouTube వీడియోల కోసం వ్యాఖ్యలు మరియు జాబితాలను ట్యాబ్లలుగా మార్చండి
  171. // @description:kn YouTube ವೀಡಿಯೊಗಳಿಗಾಗಿ ಟಿಪ್ಪಣಿಗಳನ್ನು ಮತ್ತು ಪಟ್ಟಿಗಳನ್ನು ಟ್ಯಾಬ್‌ಗಳಾಗಿ ಮಾಡಿ
  172. // @description:ml YouTube വീഡിയോകൾക്കായി അഭിപ്രായങ്ങളും പട്ടികകളും ടാബുകളായി മാറ്റുക
  173. // @description:si YouTube වීඩියෝ සඳහා අදහස් සහ ලැයිස්තු ටැබ් කරන්න
  174. // @description:th ทำให้ความคิดเห็นและรายการเป็นแท็บสำหรับวิดีโอ YouTube
  175. // @description:lo ປ່ຽນຄວາມເຫັນຂອງຄົນເບິ່ງແລະລາຍການເປັນແຖບສໍາລັບວິດີໂອ YouTube
  176. // @description:my YouTube ဗီဒီယိုများအတွက် မှတ်ချက်များနှင့် စာရင်းများကို Tabs အဖြစ် ပြောင်းပါ
  177. // @description:ka გადაიყვანეთ კომენტარები და სიები ჩანართებში YouTube ვიდეოებისთვის
  178. // @description:am አስተያየቶችን እና ዝርዝሮችን YouTube ቪዲዮዎች ለትርጓሜዎች ውስጥ ያስተካክሉ
  179. // @description:km បង្កើតមតិយោបល់និងបញ្ជីទៅជាផ្ទាំងសម្រាប់វីដេអូ YouTube
  180.  
  181. // @version 4.33.12
  182. // @resource contentCSS https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/44f3d76938b3143ca236aac242059aada57530f4/css/style_content.css
  183. // @resource injectionJS1 https://raw.githubusercontent.com/cyfung1031/Tabview-Youtube/44f3d76938b3143ca236aac242059aada57530f4/js/injection_script_1.js
  184. // @require https://greasyfork.org/scripts/465421-vanilla-js-dialog/code/Vanilla%20JS%20Dialog.js?version=1188332
  185.  
  186. // @namespace http://tampermonkey.net/
  187. // @author CY Fung
  188. // @license MIT
  189. // @supportURL https://github.com/cyfung1031/Tabview-Youtube
  190. // @run-at document-start
  191. // @match https://www.youtube.com/*
  192. // @exclude /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
  193. // @icon https://github.com/cyfung1031/Tabview-Youtube/raw/main/images/icon128p.png
  194.  
  195. // @compatible edge Edge [Blink] >= 79; Tampermonkey (Beta) / Violentmonkey
  196. // @compatible chrome Chrome >= 54; Tampermonkey (Beta) / Violentmonkey
  197. // @compatible firefox FireFox / Waterfox (Classic) >= 55; Tampermonkey / Violentmonkey
  198. // @compatible opera Opera >= 41; Tampermonkey (Beta) / Violentmonkey
  199. // @compatible safari Safari >= 12.1
  200.  
  201. // @grant GM_getResourceText
  202. // @grant GM_registerMenuCommand
  203. // @noframes
  204. // ==/UserScript==
  205.  
  206. /* jshint esversion:8 */
  207.  
  208. function main(){
  209. 'use strict';
  210. // MIT License
  211. // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js
  212.  
  213. -(function (__CONTEXT__) {
  214. 'use strict';
  215.  
  216. let __Promise__;
  217. try {
  218. __Promise__ = (async () => { })().constructor; // due to YouTube's Promise Hack
  219. } catch (e) {
  220. throw 'Please update your browser to use Tabview Youtube.';
  221. }
  222.  
  223. const fxOperator = (proto, propertyName) => {
  224. let propertyDescriptorGetter = null;
  225. try {
  226. propertyDescriptorGetter = Object.getOwnPropertyDescriptor(proto, propertyName).get;
  227. } catch (e) { }
  228. return typeof propertyDescriptorGetter === 'function' ? (e) => propertyDescriptorGetter.call(e) : (e) => e[propertyName];
  229. };
  230.  
  231. const fxAPI = (proto, propertyName) => {
  232. const methodFunc = proto[propertyName];
  233. return typeof methodFunc === 'function' ? (e, ...args) => methodFunc.apply(e, args) : (e, ...args) => e[propertyName](...args);
  234. };
  235.  
  236. const nodeParent = fxOperator(Node.prototype, 'parentNode');
  237. const nodeFirstChild = fxOperator(Node.prototype, 'firstChild');
  238. const nodeNextSibling = fxOperator(Node.prototype, 'nextSibling');
  239.  
  240. // const elementQS = fxAPI(Element.prototype, 'querySelector');
  241. // const elementQSA = fxAPI(Element.prototype, 'querySelectorAll');
  242. const elementNextSibling = fxOperator(Element.prototype, 'nextElementSibling');
  243. // const elementPrevSibling = fxOperator(Element.prototype, 'previousElementSibling');
  244.  
  245.  
  246. /** @type {PromiseConstructor} */
  247. const Promise = __Promise__; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.
  248.  
  249. const { requestAnimationFrame, cancelAnimationFrame } = __CONTEXT__;
  250. let _rafPromise = null;
  251.  
  252. const getRAFPromise = () => _rafPromise || (_rafPromise = new Promise(resolve => {
  253. requestAnimationFrame(hRes => {
  254. _rafPromise = null;
  255. resolve(hRes);
  256. });
  257. }));
  258.  
  259. function inIframe() {
  260. try {
  261. return window.self !== window.top;
  262. } catch (e) {
  263. return true;
  264. }
  265. }
  266.  
  267. if (inIframe()) return;
  268.  
  269. if (document.documentElement && document.documentElement.hasAttribute('plugin-tabview-youtube')) {
  270. console.warn('Multiple instances of Tabview Youtube is attached. [0x7F01]')
  271. return;
  272. }
  273.  
  274. //if (!$) return;
  275.  
  276. /**
  277. * SVG resources:
  278. * <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>
  279. */
  280.  
  281. const scriptVersionForExternal = '2022/12/04';
  282.  
  283. const isMyScriptInChromeRuntime = () => typeof GM === 'undefined' && typeof ((((window || 0).chrome || 0).runtime || 0).getURL) === 'function';
  284. const isGMAvailable = () => typeof GM !== 'undefined' && !isMyScriptInChromeRuntime();
  285.  
  286. // https://yqnn.github.io/svg-path-editor/
  287. // https://vecta.io/nano
  288.  
  289. 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
  290. 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
  291. 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
  292. 0 0 3-1V71h2a12 12 90 0 0 12-12V17a12 12 90 0 0-12-12z"/>`.trim();
  293.  
  294. 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
  295. 66H9V68h9v10zm0-56H9V12h9v10zm22 56H27V68h13v10zm-3-25V36c0-2 2-3 4-2l12 8c2 1 2 4 0 5l-12 8c-2 1-4 0-4-2zm25
  296. 25H49V68h13v10zm0-56H49V12h13v10zm18 56h-9V68h9v10zm0-56h-9V12h9v10z"/>`.trim();
  297.  
  298. 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
  299. 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
  300. .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
  301. .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
  302. 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
  303. 0 0 1.4-.4l-.6 2.2zm-3.8-35.2a1 1 0 010 8.6 1 1 0 010-8.6z"/>`.trim();
  304.  
  305. const svgPlayList = `<path d="M0 3h12v2H0zm0 4h12v2H0zm0 4h8v2H0zm16 0V7h-2v4h-4v2h4v4h2v-4h4v-2z"/>`.trim();
  306.  
  307. const svgDiag1 = `<svg stroke="currentColor" fill="none"><path d="M8 2h2v2M7 5l3-3m-6 8H2V8m0 2l3-3"/></svg>`;
  308. const svgDiag2 = `<svg stroke="currentColor" fill="none"><path d="M7 3v2h2M7 5l3-3M5 9V7H3m-1 3l3-3"/></svg>`;
  309.  
  310. const REMOVE_DUPLICATE_INFO = true;
  311. const REMOVE_DUPLICATE_META_RECOMMENDATION = true; /* https://www.youtube.com/watch?v=kGihxscQCPE */
  312. const MINIVIEW_BROWSER_ENABLE = true;
  313. const DEBUG_LOG = false;
  314.  
  315. function dnsPrefetch() {
  316.  
  317.  
  318. function linker(link, rel, href, _as) {
  319. return new Promise(resolve => {
  320. if (!link) link = document.createElement('link');
  321. link.rel = rel;
  322. if (_as) link.setAttribute('as', _as);
  323. link.onload = function () {
  324. resolve({
  325. link: this,
  326. success: true
  327. })
  328. this.remove();
  329. };
  330. link.onerror = function () {
  331. resolve({
  332. link: this,
  333. success: false
  334. });
  335. this.remove();
  336. };
  337. link.href = href;
  338. (document.head || document.documentElement).appendChild(link);
  339. link = null;
  340. });
  341. }
  342.  
  343. new Promise(resolve => {
  344.  
  345. if (document.documentElement) {
  346. resolve();
  347. } else {
  348.  
  349. let mo = new MutationObserver(() => {
  350. if (mo !== null && document.documentElement) {
  351. mo.takeRecords();
  352. mo.disconnect();
  353. mo = null;
  354. resolve();
  355. }
  356. });
  357. mo.observe(document, { childList: true, subtree: false })
  358.  
  359. }
  360.  
  361. }).then(() => {
  362.  
  363. const hosts = [
  364. 'https://i.ytimg.com',
  365. 'https://www.youtube.com',
  366. 'https://rr2---sn-5n5ip-ioqd.googlevideo.com',
  367. 'https://googlevideo.com',
  368. 'https://accounts.youtube.com',
  369. 'https://jnn-pa.googleapis.com',
  370. 'https://googleapis.com',
  371. 'https://fonts.gstatic.com',
  372. 'https://www.gstatic.com',
  373. 'https://yt3.ggpht.com',
  374. 'https://yt4.ggpht.com'
  375. ]
  376. for (const h of hosts) {
  377.  
  378.  
  379. linker(null, 'preconnect', h);
  380.  
  381. }
  382.  
  383.  
  384.  
  385. });
  386. }
  387. dnsPrefetch();
  388.  
  389.  
  390. let _isPageFirstLoaded = true;
  391.  
  392. async function makeTytLock() {
  393. let c = 8;
  394. while (!document.documentElement) {
  395. if (--c === 0) return
  396. await getRAFPromise().then()
  397. }
  398. if (_isPageFirstLoaded) document.documentElement.setAttribute('tyt-lock', '')
  399. }
  400. if (location.pathname === '/watch') makeTytLock()
  401. /*
  402.  
  403. youtube page
  404.  
  405. = Init::browse
  406. yt-page-data-fetched
  407. data-changed...
  408. yt-page-data-updated
  409. yt-navigate-finish
  410. data-changed...
  411. yt-watch-comments-ready
  412. = browse -> watch
  413. yt-player-updated
  414. yt-navigate
  415. yt-navigate-start
  416. yt-page-type-changed
  417. yt-player-updated
  418. yt-page-data-fetched
  419. yt-navigate-finish
  420. data-changed...
  421. yt-page-data-updated
  422. data-changed...
  423. yt-watch-comments-ready
  424. data-changed...
  425.  
  426. = watch -> watch
  427. = click video on meta panel // https://www.youtube.com/watch?v=UY5bp5CNhak; https://www.youtube.com/watch?v=m0WtnU8NVTo
  428. yt-navigate
  429. yt-navigate-start
  430. data-changed
  431. yt-player-updated
  432. yt-page-data-fetched
  433. yt-navigate-finish
  434. data-changed...
  435. yt-page-data-updated
  436. data-changed...
  437. yt-watch-comments-ready
  438. data-changed...
  439.  
  440. = watch -> browse (miniview)
  441. yt-navigate-cache
  442. yt-page-data-fetched
  443. yt-page-type-changed
  444. yt-page-data-updated
  445. yt-navigate-finish
  446.  
  447. = browse (miniview) -> watch (Restore)
  448. yt-navigate-cache
  449. yt-page-data-fetched
  450. yt-navigate-finish
  451. yt-page-type-changed
  452. yt-page-data-updated
  453. data-changed...
  454. yt-watch-comments-ready
  455.  
  456. = watch -> search (miniview)
  457. yt-navigate
  458. yt-navigate-start
  459. data-changed
  460. yt-page-data-fetched
  461. yt-page-type-changed
  462. data-changed
  463. yt-page-data-updated
  464. yt-navigate-finish
  465. data-changed...
  466.  
  467. = Init::search
  468. yt-page-data-fetched
  469. data-changed
  470. yt-page-data-updated
  471. yt-navigate-finish
  472. data-changed...
  473. yt-watch-comments-ready
  474.  
  475. = Init::watch
  476. yt-page-data-fetched
  477. yt-navigate-finish
  478. data-changed...
  479. yt-page-data-updated
  480. data-changed...
  481. yt-watch-comments-ready
  482. yt-player-updated
  483. data-changed...
  484.  
  485. = watch -> watch (history back)
  486. yt-player-updated
  487. yt-page-data-fetched
  488. yt-navigate-finish
  489. data-changed...
  490. yt-page-data-updated
  491. data-changed...
  492. yt-watch-comments-ready
  493.  
  494. = watch -> click video time // https://www.youtube.com/watch?v=UY5bp5CNhak; https://www.youtube.com/watch?v=m0WtnU8NVTo
  495. yt-navigate
  496.  
  497. */
  498.  
  499.  
  500.  
  501. const LAYOUT_VAILD = 1;
  502.  
  503. const LAYOUT_TWO_COLUMNS = 2;
  504. const LAYOUT_THEATER = 4;
  505. const LAYOUT_FULLSCREEN = 8;
  506. const LAYOUT_CHATROOM = 16;
  507. const LAYOUT_CHATROOM_COLLAPSED = 32;
  508. const LAYOUT_TAB_EXPANDED = 64;
  509. const LAYOUT_ENGAGEMENT_PANEL_EXPANDED = 128;
  510. const LAYOUT_CHATROOM_EXPANDED = 256;
  511. const LAYOUT_DONATION_SHELF_EXPANDED = 512;
  512.  
  513. const nonCryptoRandStr_base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  514.  
  515. const nullFunc = function () { };
  516.  
  517.  
  518. let scriptEnable = false;
  519.  
  520. let comments_loader = 0; // for comment count (might omit)
  521.  
  522. let cmTime = 0;
  523. const mTime = Date.now() - 152000000;
  524.  
  525. //let lastScrollFetch = 0;
  526. //let lastOffsetTop = 0;
  527. let mtf_forceCheckLiveVideo_disable = 0;
  528.  
  529. let tabsUiScript_setclick = false;
  530. let pageFetchedDataVideoId = null; // integer; for comment checking
  531. let pageType = null; // pageType = 'watch', 'browse', 'playlist', ...
  532. let chatroomDetails = null;
  533. let switchTabActivity_lastTab = null
  534.  
  535. let lstTab = null;
  536.  
  537. let storeLastPanel = null; // WeakRef
  538.  
  539.  
  540. let mtf_chatBlockQ = null; // for chat layout status change
  541.  
  542. let enableHoverSliderDetection = false; // for hover slider
  543.  
  544.  
  545. let firstLoadStatus = 2 | 8; // for page init
  546.  
  547.  
  548. let m_last_count = ''; // for comment count
  549.  
  550.  
  551.  
  552. let sVideosITO = null;
  553.  
  554. /** @type {WeakRef | null} */
  555. let ytdFlexy = null; // WeakRef
  556.  
  557. const Q = {}
  558. const SETTING_DEFAULT_TAB_0 = "#tab-videos"
  559. const settings = {
  560. defaultTab: SETTING_DEFAULT_TAB_0
  561. };
  562.  
  563. const STORE_VERSION = 1;
  564. const STORE_key = 'userscript-tabview-settings';
  565. const key_default_tab = 'my-default-tab';
  566.  
  567. let hiddenTabsByUserCSS = 0;
  568. let defaultTabByUserCSS = 0;
  569. let setupDefaultTabBtnSetting = null;
  570. let isCommentsTabBtnHidden = false;
  571.  
  572. let fetchCounts = {
  573. base: null,
  574. new: null,
  575. fetched: false,
  576. count: null,
  577. }
  578.  
  579. let pageLang = 'en';
  580. const langWords = {
  581. 'en': {
  582. //'share':'Share',
  583. 'info': 'Info',
  584. 'videos': 'Videos',
  585. 'playlist': 'Playlist'
  586. },
  587. 'jp': {
  588. //'share':'共有',
  589. 'info': '情報',
  590. 'videos': '動画',
  591. 'playlist': '再生リスト'
  592. },
  593. 'tw': {
  594. //'share':'分享',
  595. 'info': '資訊',
  596. 'videos': '影片',
  597. 'playlist': '播放清單'
  598. },
  599. 'cn': {
  600. //'share':'分享',
  601. 'info': '资讯',
  602. 'videos': '视频',
  603. 'playlist': '播放列表'
  604. },
  605. 'du': {
  606. //'share':'Teilen',
  607. 'info': 'Info',
  608. 'videos': 'Videos',
  609. 'playlist': 'Playlist'
  610. },
  611. 'fr': {
  612. //'share':'Partager',
  613. 'info': 'Info',
  614. 'videos': 'Vidéos',
  615. 'playlist': 'Playlist'
  616. },
  617. 'kr': {
  618. //'share':'공유',
  619. 'info': '정보',
  620. 'videos': '동영상',
  621. 'playlist': '재생목록'
  622. },
  623. 'ru': {
  624. //'share':'Поделиться',
  625. 'info': 'Описание',
  626. 'videos': 'Видео',
  627. 'playlist': 'Плейлист'
  628. }
  629. };
  630.  
  631. const getGMT = () => {
  632. let m = new Date('2023-01-01T00:00:00Z');
  633. return m.getDate() === 1 ? `+${m.getHours()}` : `-${24 - m.getHours()}`;
  634. };
  635.  
  636. function durationInfoTS(durationInfo) {
  637. /**
  638. * @type {{ hrs: number, mins: number, seconds: number }}
  639. */
  640. let _durationInfo = durationInfo
  641. return _durationInfo
  642. }
  643.  
  644. function formatDateReqTS(req) {
  645. /**
  646. * @type {{ bd1: KDate | undefined, bd2: KDate | undefined, isSameDay: number | undefined, durationInfo: object | undefined, formatDates: object | undefined }}
  647. */
  648. let _req = req
  649. return _req
  650. }
  651.  
  652. function liveDurationLocaleEN(durationInfo) {
  653.  
  654. const { hrs, mins, seconds } = durationInfoTS(durationInfo)
  655. let ret = []
  656. ret.push(`Length:`)
  657. if (hrs > 0) ret.push(`${hrs} ${hrs === 1 ? 'hour' : 'hours'}`)
  658. if (mins > 0) ret.push(`${mins} ${mins === 1 ? 'minute' : 'minutes'}`)
  659. if (seconds !== null) ret.push(`${seconds} ${seconds === 1 ? 'second' : 'seconds'}`)
  660. return ret.join(' ')
  661. }
  662.  
  663. /* liveDurationLocaleEN by ChatGPT @ 2023.05.11 */
  664. function liveDurationLocaleJP(durationInfo) {
  665.  
  666. const { hrs, mins, seconds } = durationInfoTS(durationInfo);
  667. let ret = [];
  668. ret.push(`長さ:`);
  669. if (hrs > 0) ret.push(`${hrs}時間`);
  670. if (mins > 0) ret.push(`${mins}分`);
  671. if (seconds !== null) ret.push(`${seconds}秒`);
  672. return ret.join('');
  673.  
  674. }
  675.  
  676. function formatDateResultEN(type, req) {
  677.  
  678. const { bd1, bd2, durationInfo, formatDates } = formatDateReqTS(req)
  679.  
  680. switch (type) {
  681. case 0x200:
  682. return [
  683. `The livestream was in ${bd1.lokStringDateEN()} from ${bd1.lokStringTime()} to ${bd2.lokStringTime()}. [GMT${getGMT()}]`,
  684. liveDurationLocaleEN(durationInfo)
  685. ].join('\n');
  686. case 0x210:
  687. return [
  688. `The livestream was from ${bd1.lokStringDateEN()} ${bd1.lokStringTime()} to ${bd2.lokStringDateEN()} ${bd2.lokStringTime()}. [GMT${getGMT()}]`,
  689. liveDurationLocaleEN(durationInfo)
  690. ].join('\n');
  691. case 0x300:
  692. return `The livestream started at ${bd1.lokStringTime()} [GMT${getGMT()}] in ${bd1.lokStringDateEN()}.`;
  693. case 0x600:
  694. return `The video was uploaded in ${formatDates.uploadDate} and published in ${formatDates.publishDate}.`;
  695. case 0x610:
  696. return `The video was uploaded in ${formatDates.uploadDate}.`;
  697. case 0x700:
  698. return `The video was published in ${formatDates.publishDate}.`;
  699. }
  700. return '';
  701.  
  702. }
  703.  
  704. /* formatDateResultJP by ChatGPT @ 2023.05.11 */
  705.  
  706. function formatDateResultJP(type, req) {
  707.  
  708. const { bd1, bd2, durationInfo, formatDates } = formatDateReqTS(req);
  709.  
  710. switch (type) {
  711. case 0x200:
  712. return [
  713. `ライブ配信は${bd1.lokStringDateJP()}の${bd1.lokStringTime()}から開始し、${bd2.lokStringTime()}まで続きました。[GMT${getGMT()}]`,
  714. liveDurationLocaleJP(durationInfo)
  715. ].join('\n');
  716. case 0x210:
  717. return [
  718. `ライブ配信は${bd1.lokStringDateJP()}の${bd1.lokStringTime()}から${bd2.lokStringDateJP()}の${bd2.lokStringTime()}まで行われました。[GMT${getGMT()}]`,
  719. liveDurationLocaleJP(durationInfo)
  720. ].join('\n');
  721. case 0x300:
  722. return `ライブ配信は${bd1.lokStringDateJP()}の${bd1.lokStringTime()}から開始しました。[GMT${getGMT()}]`;
  723. case 0x600:
  724. return `この動画は${formatDates.uploadDate}にアップロードされ、${formatDates.publishDate}に公開されました。`;
  725. case 0x610:
  726. return `この動画は${formatDates.uploadDate}にアップロードされました。`;
  727. case 0x700:
  728. return `この動画は${formatDates.publishDate}に公開されました。`;
  729. }
  730. return '';
  731.  
  732. }
  733.  
  734. function getFormatDateResultFunc() {
  735. switch (getLang()) {
  736. case 'jp':
  737. return formatDateResultJP;
  738. case 'en':
  739. default:
  740. return formatDateResultEN;
  741. }
  742. }
  743.  
  744.  
  745. // const S_GENERAL_RENDERERS = ['YTD-TOGGLE-BUTTON-RENDERER', 'YTD-MENU-RENDERER']
  746.  
  747. let globalHook_symbols = [];
  748. let globalHook_hashs = {};
  749.  
  750.  
  751. let singleColumnScrolling_dt = 0;
  752.  
  753. let isStickyHeaderEnabled = false;
  754. let isMiniviewForStickyHeadEnabled = false;
  755.  
  756. let theater_mode_changed_dt = 0;
  757. let detailsTriggerReset = false;
  758.  
  759.  
  760. let isMakeHeaderFloatCalled = false;
  761.  
  762. let _viTimeNum = 200;
  763. let _updateTimeAccum = 0;
  764.  
  765. /** @type {WeakMap<HTMLElement>} */
  766. let loadedCommentsDT = new WeakMap();
  767.  
  768.  
  769.  
  770. // for weakref variable management
  771. const es = {
  772. get ytdFlexy() {
  773. /** @type { HTMLElement | null } */
  774. let res = kRef(ytdFlexy);
  775. return res;
  776. },
  777. get storeLastPanel() {
  778. /** @type { HTMLElement | null } */
  779. let res = kRef(storeLastPanel);
  780. return res;
  781. }
  782. }
  783.  
  784.  
  785. const _console = new Proxy(console, {
  786. get(target, prop, receiver) {
  787. if (!DEBUG_LOG && prop === 'log') {
  788. return nullFunc
  789. }
  790. return Reflect.get(...arguments)
  791. }
  792. });
  793.  
  794. let generalLog901 = !DEBUG_LOG ? 0 : (evt) => {
  795. _console.log(901, evt.type)
  796. }
  797. const isPassiveArgSupport = (typeof IntersectionObserver === 'function');
  798. // https://caniuse.com/?search=observer
  799. // https://caniuse.com/?search=addEventListener%20passive
  800.  
  801. const bubblePassive = isPassiveArgSupport ? { capture: false, passive: true } : false;
  802. const capturePassive = isPassiveArgSupport ? { capture: true, passive: true } : true;
  803.  
  804.  
  805. /** @type { (str: string) => (HTMLElement | null) } */
  806. const querySelectorFromAnchor = HTMLElement.prototype.querySelector; // nodeType==1 // since 2022/07/12
  807.  
  808. /** @type { (str: string) => (NodeList) } */
  809. const querySelectorAllFromAnchor = HTMLElement.prototype.querySelectorAll; // nodeType==1 // since 2022/07/12
  810. const closestDOM = HTMLElement.prototype.closest;
  811. //const elementRemove = HTMLElement.prototype.remove;
  812. //const elementContains = HTMLElement.prototype.contains; // since 2022/07/12
  813. const elementAppend = HTMLElement.prototype.appendChild; // necessary for yt custom elements; due to Waterfox classic and https://greasyfork.org/en/scripts/428651-tabview-youtube/discussions/174437
  814.  
  815. const closestDOMX = function (child, parentSelector) {
  816. if (!(child instanceof HTMLElement)) return null;
  817. return closestDOM.call(child, parentSelector) || null;
  818. }
  819.  
  820. /**
  821. *
  822. * @param {number} f bit flag
  823. * @param {number} w bit flag (with)
  824. * @param {number} wo bit flag (without)
  825. * @returns
  826. */
  827. const fT = function (f, w, wo) {
  828. return (f & (w | wo)) === w
  829. }
  830.  
  831. /* globals WeakRef:false */
  832.  
  833. /** @type {(o: Object | null) => WeakRef | null} */
  834. const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); // typeof InvalidVar == 'undefined'
  835.  
  836. /** @type {(wr: Object | null) => Object | null} */
  837. const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);
  838.  
  839.  
  840. function setTimeout3(f) {
  841. Promise.race([getRAFPromise().then(), new Promise(r => setTimeout(r, 300))]).then(f);
  842. }
  843.  
  844. const timeline = {
  845. // after initialized (initObserver)
  846. cn1: new Set(),
  847. cn2: new Set(),
  848. setTimeout( /** @type {TimerHandler} */ f,/** @type {number} */ d) {
  849. let cid = setTimeout(f, d);
  850. timeline.cn1.add(cid);
  851. return cid;
  852. },
  853. clearTimeout(/** @type {number} */ cid) {
  854. cid = +cid;
  855. timeline.cn1.remove(cid);
  856. return clearTimeout(cid);
  857. },
  858. setInterval(/** @type {TimerHandler} */ f,/** @type {number} */ d) {
  859. let cid = setInterval(f, d);
  860. timeline.cn2.add(cid);
  861. return cid;
  862. },
  863. clearInterval(/** @type {number} */ cid) {
  864. cid = +cid;
  865. timeline.cn2.remove(cid);
  866. return clearInterval(cid);
  867. },
  868. reset() {
  869. timeline.cn1.forEach(clearTimeout);
  870. timeline.cn2.forEach(clearInterval);
  871. timeline.cn1.clear();
  872. timeline.cn2.clear();
  873. }
  874. }
  875.  
  876.  
  877. // function prettyElm(/** @type {Element} */ elm) {
  878. // if (!elm || !elm.nodeName) return null;
  879. // const eId = elm.id || null;
  880. // const eClsName = elm.className || null;
  881. // return [elm.nodeName.toLowerCase(), typeof eId == 'string' ? "#" + eId : '', typeof eClsName == 'string' ? '.' + eClsName.replace(/\s+/g, '.') : ''].join('').trim();
  882. // }
  883.  
  884. // function extractTextContent(/** @type {Node} */ elm) {
  885. // 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, '')
  886. // }
  887.  
  888. function addScript(/** @type {string} */ scriptText) {
  889. const scriptNode = document.createElement('script');
  890. scriptNode.type = 'text/javascript';
  891. scriptNode.textContent = scriptText;
  892. try {
  893. document.documentElement.appendChild(scriptNode);
  894. } catch (e) {
  895. console.log('addScript Error', e)
  896. }
  897. return scriptNode;
  898. }
  899.  
  900. function addScriptByURL(/** @type {string} */ scriptURL) {
  901. const scriptNode = document.createElement('script');
  902. scriptNode.type = 'text/javascript';
  903. scriptNode.src = scriptURL;
  904. try {
  905. document.documentElement.appendChild(scriptNode);
  906. } catch (e) {
  907. console.log('addScriptByURL Error', e)
  908. }
  909. return scriptNode;
  910. }
  911.  
  912. // function addStyle(/** @type {string} */ styleText, /** @type {HTMLElement | Document} */ container) {
  913. // const styleNode = document.createElement('style');
  914. // //styleNode.type = 'text/css';
  915. // styleNode.textContent = styleText;
  916. // (container || document.documentElement).appendChild(styleNode);
  917. // return styleNode;
  918. // }
  919.  
  920.  
  921.  
  922. /*
  923.  
  924. yt-action yt-add-element-to-app yt-autonav-pause-blur yt-autonav-pause-focus
  925. yt-autonav-pause-guide-closed yt-autonav-pause-guide-opened yt-autonav-pause-player
  926. yt-autonav-pause-player-ended yt-autonav-pause-scroll yt-autoplay-on-changed
  927. yt-close-tou-form yt-consent-bump-display-changed yt-focus-searchbox
  928. yt-get-context-provider yt-guide-close yt-guide-hover yt-guide-toggle
  929. yt-history-load yt-history-pop yt-load-invalidation-continuation
  930. yt-load-next-continuation yt-load-reload-continuation yt-load-tou-form
  931. yt-masthead-height-changed yt-navigate yt-navigate-cache yt-navigate-error
  932. yt-navigate-finish yt-navigate-redirect yt-navigate-set-page-offset
  933. yt-navigate-start yt-next-continuation-data-updated yt-open-hotkey-dialog
  934. yt-open-tou-form-loading-state yt-page-data-fetched yt-page-data-updated
  935. yt-page-data-will-update yt-page-manager-navigate-start yt-page-navigate-start
  936. yt-page-type-changed yt-player-attached yt-player-detached yt-player-released
  937. yt-player-requested yt-player-updated yt-popup-canceled yt-popup-closed
  938. yt-popup-opened yt-preconnect-urls yt-register-action yt-report-form-closed
  939. yt-report-form-opened yt-request-panel-mode-change yt-retrieve-location
  940. yt-service-request-completed yt-service-request-error yt-service-request-sent
  941. yt-set-theater-mode-enabled yt-show-survey yt-subscription-changed
  942. yt-swatch-changed yt-theater-mode-allowed yt-unregister-action yt-update-title
  943. yt-update-unseen-notification-count yt-viewport-scanned yt-visibility-refresh
  944.  
  945. */
  946.  
  947.  
  948. // _console.log(38489)
  949.  
  950. class Session {
  951. constructor(initValue) {
  952. this.sid = initValue;
  953. }
  954. session() {
  955. let pageSession = this;
  956. let s = pageSession.sid; // inaccessible from external
  957. return {
  958. get isValid() {
  959. return s === pageSession.sid;
  960. }
  961. };
  962. }
  963. set(newValue) {
  964. this.sid = newValue;
  965. }
  966. inc() {
  967. this.sid++;
  968. }
  969. }
  970.  
  971. const PromiseExternal = ((resolve_, reject_) => {
  972. const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject };
  973. return class PromiseExternal extends Promise {
  974. constructor(cb = h) {
  975. super(cb);
  976. if (cb === h) {
  977. /** @type {(value: any) => void} */
  978. this.resolve = resolve_;
  979. /** @type {(reason?: any) => void} */
  980. this.reject = reject_;
  981. }
  982. }
  983. };
  984. })();
  985.  
  986. class Deferred {
  987. constructor() {
  988. this.reset();
  989. }
  990. debounce(f) {
  991. return this.promise.then().then(f).catch(console.warn); // avoid promise.then.then.then ...
  992. }
  993. d() {
  994. return this.promise.then().catch(console.warn);
  995. }
  996. reset() {
  997. this.resolved = false;
  998. this.promise = new PromiseExternal();
  999. }
  1000. resolve() {
  1001. if (this.resolved !== false) return false;
  1002. this.resolved = true;
  1003. this.promise.resolve(...arguments);
  1004. return true;
  1005. }
  1006. }
  1007.  
  1008. class Mutex {
  1009.  
  1010. constructor() {
  1011. this.p = Promise.resolve()
  1012. }
  1013.  
  1014. lockWith(f) {
  1015.  
  1016. this.p = this.p.then(() => {
  1017. return new Promise(f).catch(console.warn)
  1018. })
  1019. }
  1020.  
  1021. }
  1022.  
  1023.  
  1024.  
  1025. /* FireMonkey unable to extend MutationObserver correctly */
  1026. class AttributeMutationObserver extends MutationObserver {
  1027. constructor(flist) {
  1028. super((mutations, observer) => {
  1029. for (const mutation of mutations) {
  1030. if (mutation.type === 'attributes') {
  1031. this.checker(mutation.target, mutation.attributeName)
  1032. }
  1033. }
  1034. })
  1035. this.flist = flist;
  1036. this.res = {}
  1037. }
  1038.  
  1039. takeRecords() {
  1040. super.takeRecords();
  1041. }
  1042. disconnect() {
  1043. this._target = null;
  1044. super.disconnect();
  1045. }
  1046. observe(/** @type {Node} */ target) {
  1047. if (this._target) return;
  1048. //console.log(123124, target)
  1049. this._target = mWeakRef(target);
  1050.  
  1051. //console.log(123125, kRef(this._target))
  1052. const options = {
  1053. attributes: true,
  1054. attributeFilter: Object.keys(this.flist),
  1055. //attributeFilter: [ "status", "username" ],
  1056. attributeOldValue: true
  1057. }
  1058. super.observe(target, options)
  1059. }
  1060. checker(/** @type {Node} */ target,/** @type {string} */ attributeName) {
  1061. let nv = target.getAttribute(attributeName);
  1062. if (this.res[attributeName] !== nv) {
  1063. this.res[attributeName] = nv
  1064. let f = this.flist[attributeName];
  1065. if (f) f(attributeName, nv);
  1066.  
  1067. }
  1068. }
  1069. check(delay = 0) {
  1070. setTimeout(() => {
  1071. let target = kRef(this._target)
  1072. if (target !== null) {
  1073. for (const key of Object.keys(this.flist)) {
  1074. this.checker(target, key)
  1075. }
  1076. } else {
  1077. console.log('target is null') //disconnected??
  1078. }
  1079. target = null;
  1080. }, delay)
  1081. }
  1082. }
  1083.  
  1084. class KDate extends Date {
  1085.  
  1086. constructor(...args) {
  1087. super(...args)
  1088. this.dayBack = false
  1089. }
  1090.  
  1091. browserSupported() {
  1092.  
  1093. }
  1094.  
  1095.  
  1096. lokStringDateEN() {
  1097. const d = this
  1098.  
  1099. let y = d.getFullYear()
  1100. let m = d.getMonth() + 1
  1101. let date = d.getDate()
  1102.  
  1103. let sy = y < 1000 ? (`0000${y}`).slice(-4) : '' + y
  1104.  
  1105. let sm = m < 10 ? '0' + m : '' + m
  1106. let sd = date < 10 ? '0' + date : '' + date
  1107.  
  1108. return `${sy}.${sm}.${sd}`
  1109.  
  1110. }
  1111.  
  1112.  
  1113. lokStringDateJP() {
  1114. const d = this
  1115.  
  1116. let y = d.getFullYear()
  1117. let m = d.getMonth() + 1
  1118. let date = d.getDate()
  1119.  
  1120. let sy = y < 1000 ? (`0000${y}`).slice(-4) : '' + y
  1121.  
  1122. let sm = m < 10 ? '0' + m : '' + m
  1123. let sd = date < 10 ? '0' + date : '' + date
  1124.  
  1125. return `${sy}/${sm}/${sd}`
  1126.  
  1127. }
  1128.  
  1129. lokStringTime() {
  1130. const d = this
  1131.  
  1132. let h = d.getHours()
  1133. let m = d.getMinutes()
  1134.  
  1135. const k = this.dayBack
  1136.  
  1137. if (k) h += 24
  1138.  
  1139. let sh = h < 10 ? '0' + h : '' + h
  1140. let sm = m < 10 ? '0' + m : '' + m
  1141.  
  1142.  
  1143. return `${sh}:${sm}`
  1144.  
  1145. }
  1146.  
  1147.  
  1148.  
  1149.  
  1150. }
  1151.  
  1152. console.assert('browserSupported' in (new KDate()),
  1153. { error: "0x87FF", errorMsg: "Your userscript manager is not supported. FireMonkey is not recommended." }
  1154. );
  1155.  
  1156.  
  1157.  
  1158. let pageSession = new Session(0);
  1159. const tabsDeferred = new Deferred();
  1160. tabsDeferred.resolve();
  1161.  
  1162. let layoutStatusMutex = new Mutex();
  1163.  
  1164. let sliderMutex = new Mutex();
  1165. const renderDeferred = new Deferred(); //pageRendered
  1166. let pageRendered = 0;
  1167. let renderIdentifier = 0;
  1168.  
  1169. const scriptletDeferred = new Deferred();
  1170.  
  1171.  
  1172. function scriptInjector(script_id, url_chrome, response_id) {
  1173.  
  1174. let res = {
  1175. script_id: script_id,
  1176. inject: function () {
  1177.  
  1178. let res = this, script_id = this.script_id;
  1179.  
  1180. if (!document.querySelector(`script#${script_id}`)) {
  1181. if (res.runtime_url) {
  1182. addScriptByURL(res.runtime_url).id = script_id;
  1183. } else {
  1184. addScript(`${res.injection_script}`).id = script_id;
  1185. }
  1186. }
  1187.  
  1188. }
  1189. }
  1190. res.script_id = script_id;
  1191.  
  1192. if (isMyScriptInChromeRuntime()) {
  1193. res.runtime_url = window.chrome.runtime.getURL(url_chrome)
  1194. } else {
  1195. res.injection_script = GM_getResourceText(response_id);
  1196. }
  1197.  
  1198. return res;
  1199.  
  1200.  
  1201. }
  1202.  
  1203. const script_inject_js1 = scriptInjector(
  1204. 'userscript-tabview-injection-1',
  1205. 'js/injection_script_1.js',
  1206. "injectionJS1");
  1207.  
  1208.  
  1209. function nonCryptoRandStr(/** @type {number} */ n) {
  1210. const result = new Array(n);
  1211. const baseStr = nonCryptoRandStr_base;
  1212. const bLen = baseStr.length;
  1213. for (let i = 0; i < n; i++) {
  1214. let t = null
  1215. do {
  1216. t = baseStr.charAt(Math.floor(Math.random() * bLen));
  1217. } while (i === 0 && 10 - t > 0)
  1218. result[i] = t;
  1219. }
  1220. return result.join('');
  1221. }
  1222.  
  1223. const uidMAP = new Map();
  1224.  
  1225. function uidGEN(s) {
  1226. let uid = uidMAP.get(s);
  1227. if (!uid) {
  1228. const uidStore = ObserverRegister.uidStore;
  1229. do {
  1230. uid = nonCryptoRandStr(5);
  1231. } while (uidStore[uid])
  1232. uidMAP.set(s, uid);
  1233. }
  1234. return uid;
  1235. }
  1236.  
  1237. /**
  1238. * Class definition
  1239. * @property {string} propName - propriety description
  1240. * ...
  1241. */
  1242. class ObserverRegister {
  1243.  
  1244. constructor(/** @type {()=>MutationObserver | IntersectionObserver} */ observerCreator) {
  1245. let uid = null;
  1246. const uidStore = ObserverRegister.uidStore;
  1247. do {
  1248. uid = nonCryptoRandStr(5);
  1249. } while (uidStore[uid])
  1250. uidStore[uid] = true;
  1251.  
  1252. /**
  1253. * uid is the unique string for each observer
  1254. * @type {string}
  1255. * @public
  1256. */
  1257. this.uid = uid;
  1258.  
  1259. /**
  1260. * observerCreator is a function to create the observer
  1261. * @type {Function}
  1262. * @public
  1263. */
  1264. this.observerCreator = observerCreator
  1265.  
  1266. /**
  1267. * observer is the actual observer object
  1268. * @type {MutationObserver | IntersectionObserver}
  1269. * @public
  1270. */
  1271. this.observer = null;
  1272. this.bindCount = 0;
  1273. }
  1274. bindElement(/** @type {HTMLElement} */ elm, ...args) {
  1275. if (elm.hasAttribute(`o3r-${this.uid}`)) return false;
  1276. elm.setAttribute(`o3r-${this.uid}`, '')
  1277. this.bindCount++;
  1278. if (this.observer === null) {
  1279. this.observer = this.observerCreator();
  1280. }
  1281. this.observer.observe(elm, ...args)
  1282. return true
  1283. }
  1284. clear(/** @type {boolean} */ flag) {
  1285. if (this.observer !== null) {
  1286. //const uidStore = ObserverRegister.uidStore;
  1287. if (flag === true) {
  1288. this.observer.takeRecords();
  1289. this.observer.disconnect();
  1290. }
  1291. this.observer = null;
  1292. this.bindCount = 0;
  1293. for (const s of document.querySelectorAll(`[o3r-${this.uid}]`)) s.removeAttribute(`o3r-${this.uid}`)
  1294. //uidStore[this.uid]=false;
  1295. //this.uid = null;
  1296. }
  1297. }
  1298. }
  1299.  
  1300. /**
  1301. * 'uidStore' is the static store of strings used.
  1302. * @static
  1303. */
  1304. ObserverRegister.uidStore = {}; // backward compatible with FireFox 55.
  1305.  
  1306.  
  1307. const mtoObservationDetails = new ObserverRegister(() => {
  1308. return new IntersectionObserver(ito_details, {
  1309. root: null,
  1310. rootMargin: "0px"
  1311. })
  1312. });
  1313.  
  1314.  
  1315. const mtoFlexyAttr = new ObserverRegister(() => {
  1316. return new MutationObserver(mtf_attrFlexy)
  1317. });
  1318.  
  1319. const mtoVisibility_EngagementPanel = new ObserverRegister(() => {
  1320. return new MutationObserver(FP.mtf_attrEngagementPanel)
  1321. });
  1322.  
  1323. const mtoVisibility_Playlist = new ObserverRegister(() => {
  1324. return new AttributeMutationObserver({
  1325. "hidden": FP.mtf_attrPlaylist
  1326. })
  1327. })
  1328. const sa_playlist = mtoVisibility_Playlist.uid;
  1329.  
  1330. const mtoVisibility_Comments = new ObserverRegister(() => {
  1331. return new AttributeMutationObserver({
  1332. "hidden": FP.mtf_attrComments
  1333. })
  1334. })
  1335. const sa_comments = mtoVisibility_Comments.uid;
  1336.  
  1337.  
  1338. const mtoVisibility_Chatroom = new ObserverRegister(() => {
  1339. return new AttributeMutationObserver({
  1340. "collapsed": FP.mtf_attrChatroom
  1341. })
  1342. })
  1343. // const sa_chatroom = mtoVisibility_Chatroom.uid;
  1344.  
  1345.  
  1346.  
  1347.  
  1348. function isDOMVisible(/** @type {HTMLElement} */ elem) {
  1349. // jQuery version : https://github.com/jquery/jquery/blob/a684e6ba836f7c553968d7d026ed7941e1a612d8/src/css/hiddenVisibleSelectors.js
  1350. return !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length);
  1351. }
  1352.  
  1353. function isNonEmptyString(s) {
  1354. return typeof s == 'string' && s.length > 0;
  1355. }
  1356.  
  1357. async function dispatchWindowResize() {
  1358. // for youtube to detect layout resize for adjusting Player tools
  1359. return window.dispatchEvent(new Event('resize'));
  1360. }
  1361.  
  1362. async function dispatchCommentRowResize() {
  1363.  
  1364. if (pageType !== "watch") return;
  1365.  
  1366. const ytdFlexyElm = es.ytdFlexy;
  1367. if (!ytdFlexyElm) return;
  1368. if (ytdFlexyElm.getAttribute('tyt-tab') !== '#tab-comments') return;
  1369.  
  1370. scriptletDeferred.debounce(() => {
  1371. document.dispatchEvent(new CustomEvent('tabview-resize-comments-rows'));
  1372. })
  1373.  
  1374.  
  1375. }
  1376.  
  1377. function enterPIP(video, errorHandler) {
  1378. return new Promise(resolve => {
  1379. if (video && typeof video.requestPictureInPicture === 'function' && typeof document.exitPictureInPicture === 'function') {
  1380. if (isVideoPlaying(video) && document.pictureInPictureElement === null) {
  1381. video.requestPictureInPicture().then(res => {
  1382. resolve(true);
  1383. }).catch((e) => {
  1384. if (errorHandler === undefined) console.warn(e);
  1385. else if (typeof errorHandler == 'function') errorHandler(e);
  1386. resolve(false);
  1387. });
  1388. } else {
  1389. resolve(false);
  1390. }
  1391. } else {
  1392. resolve(null);
  1393. }
  1394. })
  1395. }
  1396.  
  1397. function exitPIP() {
  1398. if (document.pictureInPictureElement !== null && typeof document.exitPictureInPicture === 'function') {
  1399. document.exitPictureInPicture().then(res => {
  1400.  
  1401. }).catch(console.warn)
  1402. }
  1403. }
  1404.  
  1405. function setToggleBtnTxt() {
  1406.  
  1407. if (chatroomDetails) {
  1408. // _console.log(124234, 'c=== ')
  1409.  
  1410. let chat = document.querySelector('ytd-live-chat-frame#chat');
  1411. if (!chat) return;
  1412. let txt = querySelectorFromAnchor.call(chat, 'span.yt-core-attributed-string[role="text"]');
  1413. let c = (txt || 0).textContent;
  1414.  
  1415. if (typeof c === 'string' && c.length > 2) {
  1416. if (chat.hasAttribute('collapsed')) {
  1417. // _console.log(124234, 'collapsed show expand ', chatroomDetails.txt_expand)
  1418. if (c !== chatroomDetails.txt_expand) {
  1419. txt.textContent = chatroomDetails.txt_expand;
  1420. }
  1421. } else {
  1422. // _console.log(124234, 'not collapsed show collapse ', chatroomDetails.txt_collapse)
  1423. if (c !== chatroomDetails.txt_collapse) {
  1424. txt.textContent = chatroomDetails.txt_collapse;
  1425. }
  1426. }
  1427. }
  1428. }
  1429. }
  1430.  
  1431.  
  1432. function handlerTabExpanderClick() {
  1433.  
  1434. async function b() {
  1435.  
  1436. let h1 = document.documentElement.clientHeight;
  1437. let h2 = (document.querySelector('#right-tabs') || 0).clientHeight;
  1438.  
  1439. await Promise.resolve(0);
  1440. if (h1 > 300 && h2 > 300) {
  1441. let ratio = h2 / h1; // positive below 1.0
  1442.  
  1443. return ratio;
  1444. }
  1445. return 0;
  1446. }
  1447.  
  1448. async function a() {
  1449.  
  1450.  
  1451. let secondary = document.querySelector('#secondary.ytd-watch-flexy');
  1452. if (secondary) {
  1453.  
  1454.  
  1455. if (!secondary.classList.contains('tabview-hover-slider-enable')) {
  1456.  
  1457. let secondaryInner = querySelectorFromAnchor.call(secondary, '#secondary-inner.ytd-watch-flexy');
  1458.  
  1459. if (secondaryInner) {
  1460.  
  1461. if (!secondary.classList.contains('tabview-hover-slider')) {
  1462. // without hover
  1463.  
  1464. //let rect = secondary.getBoundingClientRect();
  1465. //let rectI = secondaryInner.getBoundingClientRect();
  1466.  
  1467. secondaryInner.style.setProperty('--tabview-slider-right', `${getSecondaryInnerRight()}px`)
  1468.  
  1469. }
  1470.  
  1471. let ratio = await b();
  1472. if (ratio > 0.0 && ratio <= 1.0) {
  1473.  
  1474. secondaryInner.style.setProperty('--ytd-watch-flexy-sidebar-width-d', `${Math.round(100 * ratio * 10) / 10}vw`);
  1475. secondary.classList.add('tabview-hover-slider');
  1476. secondary.classList.add('tabview-hover-slider-enable');
  1477.  
  1478. let video = document.querySelector('#player video');
  1479. enterPIP(video);
  1480.  
  1481. }
  1482.  
  1483. }
  1484.  
  1485.  
  1486. } else {
  1487.  
  1488.  
  1489. secondary.dispatchEvent(new CustomEvent("tabview-hover-slider-restore"));
  1490. //console.log(1994)
  1491.  
  1492. }
  1493.  
  1494. // no animation event triggered for hover -> enable
  1495. dispatchCommentRowResize();
  1496.  
  1497. }
  1498.  
  1499.  
  1500.  
  1501. }
  1502.  
  1503.  
  1504. a();
  1505.  
  1506.  
  1507. }
  1508.  
  1509. let global_columns_end_ito = null;
  1510.  
  1511. function setupHoverSlider(secondary, columns) {
  1512.  
  1513. if (!secondary || !columns) return;
  1514. let attrName = `o4r-${uidGEN('tabview-hover-slider-restore')}`;
  1515.  
  1516. if (secondary.hasAttribute(attrName)) return;
  1517. secondary.setAttribute(attrName, '');
  1518.  
  1519. let elmB = document.querySelector('tabview-view-secondary-xpander');
  1520. if (!elmB) {
  1521. elmB = document.createElement('tabview-view-secondary-xpander');
  1522. prependTo(elmB, secondary);
  1523. }
  1524.  
  1525. let elmA = document.querySelector('tabview-view-columns-endpos');
  1526. if (elmA) elmA.remove();
  1527. elmA = document.createElement('tabview-view-columns-endpos');
  1528.  
  1529. let itoA = new IntersectionObserver((entries) => {
  1530. let t = null;
  1531. let w = enableHoverSliderDetection
  1532. for (const entry of entries) {
  1533. if (entry.rootBounds === null) continue;
  1534. let bcr = entry.boundingClientRect;
  1535. let rb = entry.rootBounds;
  1536. t = !entry.isIntersecting && (bcr.left > rb.right) && (rb.left <= 0);
  1537. // if entries.length>1 (unlikely); take the last intersecting
  1538. // supplement cond 1. ensure the col element is in the right side
  1539. // supplement cond 2. ensure column is wide enough for overflow checking
  1540. // it can also avoid if the layout change happened but attribute not yet changed during the intersection observation
  1541. }
  1542.  
  1543. let columns = document.querySelector('#columns.style-scope.ytd-watch-flexy');
  1544. if (columns) columns.classList.toggle('tyt-column-overflow', t);
  1545.  
  1546. if (w !== t && t !== null) {
  1547. // t can be true when the layout enters single column mode
  1548. enableHoverSliderDetection = t;
  1549. }
  1550. //console.log(entries, enableHoverSliderDetection, t)
  1551. })
  1552. elementAppend.call(columns, elmA); // append to dom first before observe
  1553. if ((wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) {
  1554. //to trigger observation at the time layout being changed
  1555. itoA.observe(elmA);
  1556. }
  1557. global_columns_end_ito = itoA;
  1558.  
  1559.  
  1560. secondary.addEventListener('tabview-hover-slider-restore', function (evt) {
  1561.  
  1562. let secondary = evt.target;
  1563.  
  1564. if (!secondary.classList.contains('tabview-hover-slider-enable')) return;
  1565.  
  1566. let secondaryInner = querySelectorFromAnchor.call(secondary, '#secondary-inner.ytd-watch-flexy')
  1567.  
  1568. if (!secondaryInner) return;
  1569.  
  1570. if (secondary.classList.contains('tabview-hover-slider-hover')) {
  1571.  
  1572. Promise.resolve(0).then(() => {
  1573. secondaryInner.style.removeProperty('--ytd-watch-flexy-sidebar-width-d');
  1574. }).then(() => {
  1575. secondary.classList.remove('tabview-hover-slider-enable')
  1576. exitPIP();
  1577. })
  1578.  
  1579. } else {
  1580.  
  1581. let secondary = evt.target;
  1582. secondary.classList.remove('tabview-hover-slider')
  1583. secondary.classList.remove('tabview-hover-slider-enable')
  1584.  
  1585. secondaryInner.style.removeProperty('--ytd-watch-flexy-sidebar-width-d');
  1586. secondaryInner.style.removeProperty('--tabview-slider-right')
  1587.  
  1588. exitPIP();
  1589.  
  1590. }
  1591.  
  1592. setTimeout(() => {
  1593. updateFloatingSlider()
  1594. }, 30);
  1595.  
  1596. }, false);
  1597.  
  1598. }
  1599.  
  1600. function addTabExpander(tabContent) {
  1601.  
  1602. if (!tabContent) return null;
  1603. let id = tabContent.id;
  1604. if (!id || typeof id !== 'string') return null;
  1605.  
  1606. if (querySelectorFromAnchor.call(tabContent, `#${id} > tabview-view-tab-expander`)) return false;
  1607.  
  1608. let elm = document.createElement('tabview-view-tab-expander')
  1609. prependTo(elm, tabContent);
  1610. elm.innerHTML = `<div>${svgElm(16, 16, 12, 12, svgDiag1, 'svg-expand')}${svgElm(16, 16, 12, 12, svgDiag2, 'svg-collapse')}</div>`
  1611. elm.addEventListener('click', handlerTabExpanderClick, false);
  1612. return true;
  1613.  
  1614. }
  1615.  
  1616. function getColumnOverflowWidth() {
  1617.  
  1618. let screenWidth = document.documentElement.getBoundingClientRect().width;
  1619.  
  1620. let posElm1 = document.querySelector('#secondary.style-scope.ytd-watch-flexy + tabview-view-columns-endpos');
  1621.  
  1622. if (posElm1) {
  1623.  
  1624. let offset = posElm1.getBoundingClientRect().x - screenWidth;
  1625. return offset
  1626.  
  1627. }
  1628. return null
  1629. }
  1630.  
  1631. function getSecondaryInnerRight() {
  1632.  
  1633. let posElm1 = document.querySelector('#secondary.style-scope.ytd-watch-flexy + tabview-view-columns-endpos');
  1634.  
  1635. let posElm2 = document.querySelector('#secondary.style-scope.ytd-watch-flexy > tabview-view-secondary-xpander');
  1636.  
  1637. if (posElm1 && posElm2) {
  1638.  
  1639. let offset = posElm1.getBoundingClientRect().x - posElm2.getBoundingClientRect().right;
  1640. return offset
  1641.  
  1642. }
  1643. return null
  1644.  
  1645. }
  1646.  
  1647. const setFloatingSliderOffset = (secondaryInner) => {
  1648.  
  1649.  
  1650. let posElm1 = document.querySelector('#secondary.style-scope.ytd-watch-flexy + tabview-view-columns-endpos');
  1651.  
  1652. let posElm2 = document.querySelector('#secondary.style-scope.ytd-watch-flexy > tabview-view-secondary-xpander');
  1653.  
  1654. if (posElm1 && posElm2) {
  1655.  
  1656. let offset = getColumnOverflowWidth();
  1657.  
  1658. let k = 1.0
  1659. if (offset >= 125) {
  1660. k = 1.0
  1661. } else if (offset >= 75) {
  1662. k = 1.0;
  1663. } else if (offset >= 25) {
  1664. k = 0.25;
  1665. } else {
  1666. k = 0.0
  1667. }
  1668. secondaryInner.style.setProperty('--tabview-slider-offset-k2', `${k}`);
  1669. secondaryInner.style.setProperty('--tabview-slider-offset', `${offset}px`) // unnecessary
  1670.  
  1671. let oriWidth = posElm2.getBoundingClientRect().width;
  1672. secondaryInner.style.setProperty('--tabview-slider-ow', `${oriWidth}px`)
  1673.  
  1674. let s1 = 'var(--ytd-watch-flexy-sidebar-width-d)';
  1675. // new width
  1676.  
  1677. let s2 = `var(--tabview-slider-ow)`;
  1678. // ori width - youtube changing the code -> not reliable to use css prop.
  1679.  
  1680. let s3 = `${offset}px`;
  1681. // how many px wider than the page
  1682.  
  1683. secondaryInner.style.setProperty('--tabview-slider-offset-actual', `calc(${s1} - ${s2} + ${s3})`)
  1684.  
  1685. }
  1686.  
  1687. }
  1688.  
  1689. async function updateFloatingSlider_A(secondaryInner) {
  1690.  
  1691. // [is-extra-wide-video_]
  1692.  
  1693. await new Promise(r => setTimeout(r, 30)); // time allowed for dom changes and value change of enableHoverSliderDetection
  1694.  
  1695. let secondary = nodeParent(secondaryInner);
  1696. if (!secondary) return;
  1697.  
  1698. if (secondary.classList.contains('tabview-hover-slider-enable')) {
  1699. return;
  1700. }
  1701.  
  1702. if (!secondary.matches('#columns.ytd-watch-flexy #primary.ytd-watch-flexy ~ #secondary.ytd-watch-flexy')) {
  1703. return;
  1704. }
  1705.  
  1706. const bool = enableHoverSliderDetection === true;
  1707. const hasClassHover = secondary.classList.contains('tabview-hover-slider-hover') === true;
  1708.  
  1709. if (bool || hasClassHover) {
  1710. } else {
  1711. return;
  1712. }
  1713.  
  1714. await Promise.resolve(0);
  1715.  
  1716. secondary.classList.add('tabview-hover-final')
  1717.  
  1718. if (hasClassHover && !bool) {
  1719. secondaryInner.style.removeProperty('--tabview-slider-right')
  1720. secondaryInner.style.removeProperty('--tabview-slider-offset')
  1721. } else {
  1722.  
  1723. if (!hasClassHover) {
  1724. secondaryInner.style.setProperty('--tabview-slider-right', `${getSecondaryInnerRight()}px`)
  1725. }
  1726.  
  1727. setFloatingSliderOffset(secondaryInner);
  1728. }
  1729.  
  1730. if (bool ^ hasClassHover) {
  1731. secondary.classList.toggle('tabview-hover-slider', bool)
  1732. secondary.classList.toggle('tabview-hover-slider-hover', bool)
  1733. }
  1734.  
  1735. await Promise.resolve(0);
  1736.  
  1737.  
  1738. setTimeout(() => {
  1739. secondary.classList.remove('tabview-hover-final')
  1740. }, 350)
  1741.  
  1742.  
  1743. }
  1744.  
  1745.  
  1746. function updateFloatingSlider() {
  1747.  
  1748. let secondaryInner = document.querySelector('ytd-watch-flexy[flexy][is-two-columns_] #secondary-inner.ytd-watch-flexy')
  1749.  
  1750. if (!secondaryInner) return;
  1751.  
  1752. let secondary = nodeParent(secondaryInner);
  1753. if (!secondary) return;
  1754.  
  1755. if (secondary.classList.contains('tabview-hover-slider-enable')) {
  1756. return;
  1757. }
  1758.  
  1759. let t = document.documentElement.clientWidth; //integer
  1760.  
  1761. sliderMutex.lockWith(unlock => {
  1762.  
  1763. let v = document.documentElement.clientWidth; //integer
  1764.  
  1765. if (t === v && secondaryInner.matches('body ytd-watch-flexy[flexy][is-two-columns_] #secondary-inner.ytd-watch-flexy')) {
  1766.  
  1767. updateFloatingSlider_A(secondaryInner).then(unlock);
  1768. } else {
  1769. unlock();
  1770. }
  1771.  
  1772. })
  1773.  
  1774. }
  1775.  
  1776.  
  1777. function setToActiveTab(defaultTab) {
  1778. if (isTheater() && isWideScreenWithTwoColumns()) return;
  1779. const jElm = document.querySelector(`a[tyt-tab-content="${switchTabActivity_lastTab}"]:not(.tab-btn-hidden)`) ||
  1780. document.querySelector(`a[tyt-tab-content="${(defaultTab || settings.defaultTab)}"]:not(.tab-btn-hidden)`) ||
  1781. document.querySelector(`a[tyt-tab-content="${(SETTING_DEFAULT_TAB_0)}"]:not(.tab-btn-hidden)`) ||
  1782. document.querySelector("a[tyt-tab-content]:not(.tab-btn-hidden)") ||
  1783. null;
  1784.  
  1785. switchTabActivity(jElm);
  1786. return !!jElm;
  1787. }
  1788.  
  1789. let enableLivePopupCheck = false
  1790.  
  1791. function layoutStatusChanged(/** @type {number} */ old_layoutStatus, /** @type {number} */ new_layoutStatus) {
  1792.  
  1793.  
  1794. if ((new_layoutStatus & LAYOUT_TWO_COLUMNS) === 0) makeHeaderFloat();
  1795.  
  1796. //if (old_layoutStatus === new_layoutStatus) return;
  1797.  
  1798. const cssElm = es.ytdFlexy;
  1799.  
  1800. if (!cssElm) return;
  1801.  
  1802.  
  1803. const BF_TWOCOL_N_THEATER = LAYOUT_TWO_COLUMNS | LAYOUT_THEATER
  1804.  
  1805. let new_isExpandedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_EXPANDED)
  1806. let new_isCollapsedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_COLLAPSED) && !!(new_layoutStatus & LAYOUT_CHATROOM)
  1807.  
  1808. let new_isTabExpanded = !!(new_layoutStatus & LAYOUT_TAB_EXPANDED);
  1809. let new_isFullScreen = !!(new_layoutStatus & LAYOUT_FULLSCREEN);
  1810. let new_isExpandedEPanel = !!(new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPANDED);
  1811. let new_isExpandedDonationShelf = !!(new_layoutStatus & LAYOUT_DONATION_SHELF_EXPANDED);
  1812.  
  1813.  
  1814. function showTabOrChat() {
  1815.  
  1816. layoutStatusMutex.lockWith(unlock => {
  1817.  
  1818. if (lstTab.lastPanel == '#chatroom') {
  1819.  
  1820. if (new_isTabExpanded) switchTabActivity(null)
  1821. if (!new_isExpandedChat) ytBtnExpandChat();
  1822.  
  1823. } else if (lstTab.lastPanel && lstTab.lastPanel.indexOf('#engagement-panel-') == 0) {
  1824.  
  1825. if (new_isTabExpanded) switchTabActivity(null)
  1826. if (!new_isExpandedEPanel) ytBtnOpenEngagementPanel(lstTab.lastPanel);
  1827.  
  1828. } else if (lstTab.lastPanel == '#donation-shelf') {
  1829.  
  1830. if (new_isTabExpanded) switchTabActivity(null)
  1831. if (!new_isExpandedDonationShelf) openDonationShelf();
  1832.  
  1833. } else {
  1834.  
  1835. if (new_isExpandedChat) ytBtnCollapseChat()
  1836. if (!new_isTabExpanded) setToActiveTab();
  1837.  
  1838. }
  1839.  
  1840. timeline.setTimeout(unlock, 40);
  1841.  
  1842. })
  1843. }
  1844.  
  1845. function hideTabAndChat() {
  1846.  
  1847. layoutStatusMutex.lockWith(unlock => {
  1848.  
  1849. if (new_isTabExpanded) switchTabActivity(null)
  1850. if (new_isExpandedChat) ytBtnCollapseChat()
  1851. if (new_isExpandedEPanel) ytBtnCloseEngagementPanels();
  1852. if (new_isExpandedDonationShelf) closeDonationShelf();
  1853.  
  1854.  
  1855. timeline.setTimeout(unlock, 40);
  1856.  
  1857. })
  1858.  
  1859. }
  1860.  
  1861. const statusCollapsedFalse = !!(new_layoutStatus & (LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED | LAYOUT_CHATROOM_EXPANDED))
  1862. const statusCollapsedTrue = !statusCollapsedFalse
  1863.  
  1864. let changes = (old_layoutStatus & LAYOUT_VAILD) ? old_layoutStatus ^ new_layoutStatus : 0;
  1865.  
  1866. let chat_collapsed_changed = !!(changes & LAYOUT_CHATROOM_COLLAPSED)
  1867. let chat_expanded_changed = !!(changes & LAYOUT_CHATROOM_EXPANDED)
  1868. let tab_expanded_changed = !!(changes & LAYOUT_TAB_EXPANDED)
  1869. let theater_mode_changed = !!(changes & LAYOUT_THEATER)
  1870. let column_mode_changed = !!(changes & LAYOUT_TWO_COLUMNS)
  1871. let fullscreen_mode_changed = !!(changes & LAYOUT_FULLSCREEN)
  1872. let epanel_expanded_changed = !!(changes & LAYOUT_ENGAGEMENT_PANEL_EXPANDED)
  1873. let ds_expanded_changed = !!(changes & LAYOUT_DONATION_SHELF_EXPANDED)
  1874.  
  1875. // _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)
  1876.  
  1877.  
  1878. //console.log(169, 1, chat_collapsed_changed, tab_expanded_changed)
  1879. //console.log(169, 2, new_isExpandedChat, new_isCollapsedChat, new_isTabExpanded)
  1880.  
  1881. let BF_LayoutCh_Panel = (changes & (LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED))
  1882. let tab_change = BF_LayoutCh_Panel;
  1883. let isChatOrTabExpandTriggering = !!((new_layoutStatus) & BF_LayoutCh_Panel);
  1884. let isChatOrTabCollaspeTriggering = !!((~new_layoutStatus) & BF_LayoutCh_Panel);
  1885.  
  1886.  
  1887. const moreThanOneShown = (new_isTabExpanded + new_isExpandedChat + new_isExpandedEPanel + new_isExpandedDonationShelf) > 1
  1888.  
  1889.  
  1890. const base_twoCol_NoTheather_chatExpand_a = LAYOUT_TWO_COLUMNS | LAYOUT_THEATER | LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLAPSED
  1891. const base_twoCol_NoTheather_chatExpand_b = LAYOUT_TWO_COLUMNS | 0 | LAYOUT_CHATROOM | 0
  1892.  
  1893. // two column; not theater; tab collapse; chat expand; ep expand
  1894. const IF_01a = base_twoCol_NoTheather_chatExpand_a | LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED;
  1895. const IF_01b = base_twoCol_NoTheather_chatExpand_b | 0 | LAYOUT_ENGAGEMENT_PANEL_EXPANDED;
  1896.  
  1897.  
  1898. // two column; not theater; tab collapse; chat expand; ep expand
  1899. const IF_07a = base_twoCol_NoTheather_chatExpand_a | LAYOUT_TAB_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED;
  1900. const IF_07b = base_twoCol_NoTheather_chatExpand_b | 0 | LAYOUT_DONATION_SHELF_EXPANDED;
  1901.  
  1902.  
  1903. // two column; not theater;
  1904. const IF_02a = BF_TWOCOL_N_THEATER;
  1905. const IF_02b = LAYOUT_TWO_COLUMNS;
  1906.  
  1907. // two column; not theater; tab expand; chat expand;
  1908. const IF_03a = base_twoCol_NoTheather_chatExpand_a | LAYOUT_TAB_EXPANDED;
  1909. const IF_03b = base_twoCol_NoTheather_chatExpand_b | LAYOUT_TAB_EXPANDED;
  1910.  
  1911.  
  1912. // two column; tab expand; chat expand;
  1913. const IF_06a = LAYOUT_TWO_COLUMNS | LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLAPSED;
  1914. const IF_06b = LAYOUT_TWO_COLUMNS | LAYOUT_TAB_EXPANDED | LAYOUT_CHATROOM | 0;
  1915.  
  1916.  
  1917. // two column; theater;
  1918. const IF_04a = BF_TWOCOL_N_THEATER;
  1919. const IF_04b = BF_TWOCOL_N_THEATER;
  1920.  
  1921. // not fullscreen; two column; not theater; not tab expand; not EP expand; not expand chat; not donation shelf
  1922. const IF_05a = LAYOUT_FULLSCREEN | LAYOUT_TWO_COLUMNS | LAYOUT_THEATER | LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED | LAYOUT_CHATROOM_EXPANDED;
  1923. const IF_05b = 0 | LAYOUT_TWO_COLUMNS | 0 | 0 | 0 | 0 | 0;
  1924.  
  1925. let _isChatPopupedF = null
  1926. let isChatPopupedF = () => {
  1927. return _isChatPopupedF === null ? (_isChatPopupedF = cssElm.classList.contains('tyt-chat-popup')) : _isChatPopupedF
  1928. }
  1929.  
  1930. if (new_isFullScreen) {
  1931.  
  1932.  
  1933. if (tab_change == LAYOUT_CHATROOM_EXPANDED && (new_layoutStatus & IF_06a) === IF_06b && statusCollapsedFalse && !column_mode_changed) {
  1934.  
  1935. // two column; tab expand; chat expand;
  1936.  
  1937. switchTabActivity(null);
  1938.  
  1939. }
  1940.  
  1941. if (!!(tab_change & LAYOUT_CHATROOM_EXPANDED) && new_isExpandedChat) {
  1942. //tab_change = LAYOUT_CHATROOM_EXPANDED
  1943. //tab_change = LAYOUT_CHATROOM_EXPANDED|LAYOUT_TAB_EXPANDED
  1944.  
  1945.  
  1946. timeline.setTimeout(() => {
  1947. let scrollElement = document.querySelector('ytd-app[scrolling]')
  1948. if (!scrollElement) return;
  1949. // single column view; click button; scroll to tab content area 100%
  1950. let chatFrame = document.querySelector('ytd-live-chat-frame#chat');
  1951. if (chatFrame && isChatExpand()) {
  1952. // _console.log(7290, 1)
  1953. chatFrame.scrollIntoView(true);
  1954. }
  1955. }, 60)
  1956.  
  1957. }
  1958.  
  1959. if (!!(tab_change & LAYOUT_ENGAGEMENT_PANEL_EXPANDED) && new_isExpandedEPanel) {
  1960.  
  1961. timeline.setTimeout(() => {
  1962. let scrollElement = document.querySelector('ytd-app[scrolling]')
  1963. if (!scrollElement) return;
  1964. // single column view; click button; scroll to tab content area 100%
  1965. let epPanel = document.querySelector('ytd-watch-flexy[flexy][tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility][visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]:not([hidden])');
  1966. if (epPanel) {
  1967. // _console.log(7290, 2)
  1968.  
  1969. let pi = 50;
  1970. let cid = setInterval(() => {
  1971. if (--pi) epPanel.scrollIntoView(true); else clearInterval(cid)
  1972. }, 17)
  1973. //
  1974. }
  1975. }, 60)
  1976.  
  1977. }
  1978.  
  1979.  
  1980. } else if (fullscreen_mode_changed) {
  1981.  
  1982. // new_isFullScreen: false
  1983. // fullscreen_mode_changed: true
  1984.  
  1985.  
  1986. if (!new_isFullScreen && statusCollapsedTrue && isWideScreenWithTwoColumns() && !isTheater()) {
  1987.  
  1988. showTabOrChat();
  1989. } else if (!new_isFullScreen && statusCollapsedFalse && isWideScreenWithTwoColumns() && isTheater()) {
  1990.  
  1991.  
  1992. if (cisChatPopupedF()) {
  1993. } else {
  1994.  
  1995. ytBtnCancelTheater();
  1996.  
  1997. }
  1998. }
  1999.  
  2000. } else if ((new_layoutStatus & IF_01a) === IF_01b && !column_mode_changed && (tab_change == LAYOUT_CHATROOM_EXPANDED || tab_change == LAYOUT_ENGAGEMENT_PANEL_EXPANDED)) {
  2001.  
  2002. // new_isFullScreen: false
  2003. // fullscreen_mode_changed: false
  2004.  
  2005. // two column; not theater; tab collapse; chat expand; ep expand
  2006.  
  2007. if (epanel_expanded_changed) {
  2008. layoutStatusMutex.lockWith(unlock => {
  2009. ytBtnCollapseChat();
  2010. setTimeout(unlock, 13)
  2011. })
  2012. } else if (chat_collapsed_changed) {
  2013. layoutStatusMutex.lockWith(unlock => {
  2014. ytBtnCloseEngagementPanels();
  2015. setTimeout(unlock, 13)
  2016. })
  2017.  
  2018. }
  2019.  
  2020. } else if ((new_layoutStatus & IF_07a) === IF_07b && !column_mode_changed && (tab_change == LAYOUT_CHATROOM_EXPANDED || tab_change == LAYOUT_DONATION_SHELF_EXPANDED)) {
  2021.  
  2022. // new_isFullScreen: false
  2023. // fullscreen_mode_changed: false
  2024.  
  2025. // two column; not theater; tab collapse; chat expand; ds expand
  2026.  
  2027. if (ds_expanded_changed) {
  2028. layoutStatusMutex.lockWith(unlock => {
  2029. ytBtnCollapseChat();
  2030. setTimeout(unlock, 13)
  2031. })
  2032. } else if (chat_collapsed_changed) {
  2033. layoutStatusMutex.lockWith(unlock => {
  2034. closeDonationShelf();
  2035. setTimeout(unlock, 13)
  2036. })
  2037.  
  2038. }
  2039.  
  2040. } else if (!tab_change && column_mode_changed && (new_layoutStatus & IF_02a) === IF_02b && moreThanOneShown) {
  2041.  
  2042. // new_isFullScreen: false
  2043. // fullscreen_mode_changed: false
  2044.  
  2045. // two column; not theater;
  2046. // moreThanOneShown
  2047.  
  2048. showTabOrChat();
  2049.  
  2050. } else if (tab_change == LAYOUT_CHATROOM_EXPANDED && (new_layoutStatus & IF_03a) === IF_03b && statusCollapsedFalse && !column_mode_changed) {
  2051.  
  2052. // new_isFullScreen: false
  2053. // fullscreen_mode_changed: false
  2054.  
  2055. // two column; not theater; tab expand; chat expand;
  2056.  
  2057. switchTabActivity(null);
  2058.  
  2059. } else if (isChatOrTabExpandTriggering && (new_layoutStatus & IF_04a) === IF_04b && statusCollapsedFalse && (changes & BF_TWOCOL_N_THEATER) === 0) {
  2060.  
  2061. // new_isFullScreen: false
  2062. // fullscreen_mode_changed: false
  2063.  
  2064. ytBtnCancelTheater();
  2065.  
  2066. } else if ((new_layoutStatus & IF_04a) === IF_04b && statusCollapsedFalse) {
  2067.  
  2068. // new_isFullScreen: false
  2069. // fullscreen_mode_changed: false
  2070.  
  2071. if (isChatPopupedF()) {
  2072.  
  2073. } else {
  2074.  
  2075. hideTabAndChat();
  2076. }
  2077.  
  2078. } else if (isChatOrTabCollaspeTriggering && (new_layoutStatus & IF_02a) === IF_02b && statusCollapsedTrue && !column_mode_changed) {
  2079.  
  2080. // new_isFullScreen: false
  2081. // fullscreen_mode_changed: false
  2082.  
  2083. if (tab_change == LAYOUT_ENGAGEMENT_PANEL_EXPANDED) {
  2084.  
  2085. lstTab.lastPanel = null;
  2086.  
  2087. if (new_isFullScreen) {
  2088.  
  2089. } else {
  2090. showTabOrChat();
  2091. }
  2092. } else if (tab_change == LAYOUT_DONATION_SHELF_EXPANDED) {
  2093.  
  2094. lstTab.lastPanel = null;
  2095.  
  2096. if (new_isFullScreen) {
  2097.  
  2098. } else {
  2099. showTabOrChat();
  2100. }
  2101. } else if (tab_change == LAYOUT_CHATROOM_EXPANDED) {
  2102.  
  2103. lstTab.lastPanel = null;
  2104.  
  2105. if (new_isFullScreen) {
  2106.  
  2107. } else {
  2108. showTabOrChat();
  2109. }
  2110. } else {
  2111.  
  2112.  
  2113. if (new_isFullScreen) {
  2114.  
  2115. } else {
  2116.  
  2117. ytBtnSetTheater();
  2118.  
  2119. }
  2120.  
  2121. }
  2122.  
  2123. } else if (!tab_change && !!(changes & BF_TWOCOL_N_THEATER) && (new_layoutStatus & IF_02a) === IF_02b && statusCollapsedTrue) {
  2124.  
  2125. // new_isFullScreen: false
  2126. // fullscreen_mode_changed: false
  2127.  
  2128. showTabOrChat();
  2129.  
  2130. } else if ((new_layoutStatus & IF_05a) === IF_05b) {
  2131. // bug fix for restoring from mini player
  2132.  
  2133. layoutStatusMutex.lockWith(unlock => {
  2134. setToActiveTab();
  2135. timeline.setTimeout(unlock, 40);
  2136. });
  2137.  
  2138. }
  2139.  
  2140. if (theater_mode_changed) {
  2141. let tdt = Date.now();
  2142. theater_mode_changed_dt = tdt
  2143. setTimeout(() => {
  2144. if (theater_mode_changed_dt !== tdt) return;
  2145. updateFloatingSlider();
  2146. }, 130)
  2147. }
  2148.  
  2149. let secondary = null;
  2150. if (secondary = document.querySelector('.tabview-hover-slider-enable')) {
  2151. secondary.dispatchEvent(new CustomEvent('tabview-hover-slider-restore'))
  2152. //console.log(1996)
  2153. }
  2154.  
  2155.  
  2156. if (fullscreen_mode_changed) {
  2157. detailsTriggerReset = true;
  2158. setTimeout(() => {
  2159. setHiddenStateForDesc();
  2160. }, 80);
  2161. }
  2162.  
  2163. // resize => is-two-columns_
  2164. if (column_mode_changed) {
  2165.  
  2166. Promise.resolve(0).then(() => {
  2167.  
  2168. if (moreThanOneShown && (new_layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) {
  2169.  
  2170. layoutStatusMutex.lockWith(unlock => {
  2171. if (new_isTabExpanded && lstTab.lastPanel === null) {
  2172. if (new_isExpandedChat) ytBtnCollapseChat();
  2173. if (new_isExpandedEPanel) ytBtnCloseEngagementPanels();
  2174. if (new_isExpandedDonationShelf) closeDonationShelf();
  2175. } else if (lstTab.lastPanel) {
  2176. let lastPanel = lstTab.lastPanel || '';
  2177. if (typeof lastPanel !== 'string') lastPanel = '';
  2178. if (new_isExpandedChat && lastPanel !== '#chatroom') ytBtnCollapseChat();
  2179. if (new_isExpandedEPanel && lastPanel.indexOf('#engagement-panel-') < 0) ytBtnCloseEngagementPanels();
  2180. if (new_isExpandedDonationShelf && lastPanel !== '#donation-shelf') closeDonationShelf();
  2181. switchTabActivity(null);
  2182. }
  2183. timeline.setTimeout(unlock, 40);
  2184. });
  2185. }
  2186.  
  2187. pageCheck();
  2188. if (global_columns_end_ito !== null) {
  2189. //to trigger observation at the time layout being changed
  2190. if ((new_layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) {
  2191. let endpos = document.querySelector('tabview-view-columns-endpos')
  2192. if (endpos !== null) {
  2193. global_columns_end_ito.observe(endpos)
  2194. }
  2195. } else {
  2196. global_columns_end_ito.disconnect();
  2197. }
  2198. }
  2199. setTimeout3(() => {
  2200. singleColumnScrolling(true); //initalize sticky
  2201. });
  2202. })
  2203. }
  2204.  
  2205. if (enableLivePopupCheck === true) {
  2206.  
  2207. const new_isTwoColumnsTheater = fT(new_layoutStatus, LAYOUT_TWO_COLUMNS | LAYOUT_THEATER, 0)
  2208.  
  2209. let currentIsTheaterPopupChat = new_isTwoColumnsTheater && new_isExpandedChat && isChatPopupedF()
  2210. if (!currentIsTheaterPopupChat) {
  2211. enableLivePopupCheck = false
  2212. document.dispatchEvent(new CustomEvent("tyt-close-popup"))
  2213. }
  2214.  
  2215. }
  2216.  
  2217.  
  2218. }
  2219.  
  2220. function fixLayoutStatus(x) {
  2221. const new_isExpandedChat = !(x & LAYOUT_CHATROOM_COLLAPSED) && (x & LAYOUT_CHATROOM)
  2222. return new_isExpandedChat ? (x | LAYOUT_CHATROOM_EXPANDED) : (x & ~LAYOUT_CHATROOM_EXPANDED);
  2223. }
  2224.  
  2225. const wls = new Proxy({
  2226. /** @type {number | null} */
  2227. layoutStatus: undefined
  2228. }, {
  2229. get: function (target, prop) {
  2230. return target[prop];
  2231. },
  2232. set: function (target, prop, value) {
  2233. if (prop == 'layoutStatus') {
  2234.  
  2235. if (value === 0) {
  2236. target[prop] = value;
  2237. return true;
  2238. } else if (target[prop] === value) {
  2239. return true;
  2240. } else {
  2241. if (!target.layoutStatus_pending) {
  2242. target.layoutStatus_pending = true;
  2243. const old_layoutStatus = target[prop];
  2244. target[prop] = value;
  2245. layoutStatusMutex.lockWith(unlock => {
  2246. target.layoutStatus_pending = false;
  2247. let new_layoutStatus = target[prop];
  2248. if (old_layoutStatus !== new_layoutStatus) {
  2249. layoutStatusChanged(old_layoutStatus, new_layoutStatus);
  2250. timeline.setTimeout(unlock, 40)
  2251. } else {
  2252. unlock();
  2253. }
  2254. })
  2255. return true;
  2256. }
  2257. }
  2258. }
  2259. target[prop] = value;
  2260. return true;
  2261. },
  2262. has: function (target, prop) {
  2263. return (prop in target);
  2264. }
  2265. });
  2266.  
  2267. 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>`
  2268.  
  2269. function isVideoPlaying(video) {
  2270. return video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA;
  2271. }
  2272.  
  2273. function wAttr(elm, attr, kv) {
  2274. if (elm) {
  2275. if (kv === true) {
  2276. elm.setAttribute(attr, '');
  2277. } else if (kv === false) {
  2278. elm.removeAttribute(attr);
  2279. } else if (kv === null) {
  2280. //;
  2281. } else if (typeof kv == 'string') {
  2282. elm.setAttribute(attr, kv);
  2283. }
  2284. }
  2285. }
  2286.  
  2287. function setTabBtnVisible(tabBtn, toVisible) {
  2288. let doClassListChange = false;
  2289. if (tabBtn.getAttribute('tyt-tab-content') === '#tab-comments') {
  2290. isCommentsTabBtnHidden = !toVisible;
  2291. if ((hiddenTabsByUserCSS & 2) !== 2) {
  2292. doClassListChange = true;
  2293. }
  2294. } else {
  2295. doClassListChange = true;
  2296. }
  2297. if (doClassListChange) {
  2298. if (toVisible) {
  2299. tabBtn.classList.remove("tab-btn-hidden");
  2300. } else {
  2301. tabBtn.classList.add("tab-btn-hidden");
  2302. }
  2303. }
  2304. }
  2305.  
  2306. function hideTabBtn(tabBtn) {
  2307. //console.log('hideTabBtn', tabBtn)
  2308. let isActiveBefore = tabBtn.classList.contains('active');
  2309. setTabBtnVisible(tabBtn, false);
  2310. if (isActiveBefore) {
  2311. setToActiveTab();
  2312. }
  2313. }
  2314.  
  2315. // function hasAttribute(obj, key) {
  2316. // return obj && obj.hasAttribute(key);
  2317. // }
  2318.  
  2319. function isTheater() {
  2320. const cssElm = es.ytdFlexy;
  2321. return (cssElm && cssElm.hasAttribute('theater'))
  2322. }
  2323.  
  2324. function isFullScreen() {
  2325. const cssElm = es.ytdFlexy;
  2326. return (cssElm && cssElm.hasAttribute('fullscreen'))
  2327. }
  2328.  
  2329. function isChatExpand() {
  2330. const cssElm = es.ytdFlexy;
  2331. return cssElm && (cssElm.getAttribute('tyt-chat') || '').charAt(0) === '+'
  2332. }
  2333.  
  2334. function isWideScreenWithTwoColumns() {
  2335. const cssElm = es.ytdFlexy;
  2336. return (cssElm && cssElm.hasAttribute('is-two-columns_'))
  2337. }
  2338.  
  2339.  
  2340. function isAnyActiveTab() {
  2341. return document.querySelector('#right-tabs .tab-btn.active') !== null
  2342. }
  2343. // function isAnyActiveTab2() {
  2344. // return document.querySelectorAll('#right-tabs .tab-btn.active').length > 0
  2345. // }
  2346.  
  2347. function isEngagementPanelExpanded() { //note: not checking the visual elements
  2348. const cssElm = es.ytdFlexy;
  2349. return (cssElm && +cssElm.getAttribute('tyt-ep-visible') > 0)
  2350. }
  2351.  
  2352. function isDonationShelfExpanded() {
  2353. const cssElm = es.ytdFlexy;
  2354. return (cssElm && cssElm.hasAttribute('tyt-donation-shelf'))
  2355. }
  2356.  
  2357. const engagementIdMap = new Map();
  2358. let engagementIdNext = 1; // max 1 << 62
  2359.  
  2360. function engagement_panels_() {
  2361.  
  2362. let res = [];
  2363. // let shownRes = [];
  2364.  
  2365. let v = 0;
  2366. // let k = 1;
  2367. // let count = 0;
  2368.  
  2369. for (const ePanel of document.querySelectorAll(
  2370. `ytd-watch-flexy[flexy][tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility]:not([hidden])`
  2371. )) {
  2372. let targetId = ePanel.getAttribute('target-id')
  2373. if (typeof targetId !== 'string') continue;
  2374. let eId = engagementIdMap.get(targetId)
  2375. if (!eId) {
  2376. engagementIdMap.set(targetId, eId = engagementIdNext)
  2377. if (engagementIdNext === (1 << 62)) {
  2378. engagementIdNext = 1;
  2379. console.warn('engagementId reached 1 << 62')
  2380. } else {
  2381. engagementIdNext = engagementIdNext << 1;
  2382. }
  2383. }
  2384. // console.log(55,eId, targetId)
  2385.  
  2386. let visibility = ePanel.getAttribute('visibility') //ENGAGEMENT_PANEL_VISIBILITY_EXPANDED //ENGAGEMENT_PANEL_VISIBILITY_HIDDEN
  2387.  
  2388. let k = eId
  2389. switch (visibility) {
  2390. case 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED':
  2391. v |= k;
  2392. // count++;
  2393. // shownRes.push(ePanel)
  2394. res.push({ ePanel, k, visible: true });
  2395. break;
  2396. case 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN':
  2397. res.push({ ePanel, k, visible: false });
  2398. break;
  2399. default:
  2400. res.push({ ePanel, k, visible: false });
  2401. }
  2402.  
  2403. //k = k << 1;
  2404.  
  2405. }
  2406. return { list: res, value: v };
  2407. // return { list: res, value: v, count: count, shownRes };
  2408. }
  2409.  
  2410.  
  2411. function ytBtnOpenEngagementPanel(/** @type {number | string} */ panel_id) {
  2412.  
  2413. // console.log(panel_id)
  2414. if (typeof panel_id == 'string') {
  2415. panel_id = panel_id.replace('#engagement-panel-', '');
  2416. panel_id = parseInt(panel_id);
  2417. }
  2418. if (panel_id >= 0) { } else return false;
  2419.  
  2420. let panels = engagement_panels_();
  2421. // console.log(panels)
  2422.  
  2423. let actions = []
  2424. for (const { ePanel, k, visible } of panels.list) {
  2425. if ((panel_id & k) === k) {
  2426. if (!visible) {
  2427. actions.push({
  2428. panelId: ePanel.getAttribute('target-id'),
  2429. toShow: true
  2430. })
  2431. }
  2432. } else {
  2433. if (visible) {
  2434. actions.push({
  2435. panelId: ePanel.getAttribute('target-id'),
  2436. toHide: true
  2437. })
  2438. }
  2439. }
  2440. }
  2441.  
  2442. if (actions.length > 0) {
  2443. // console.log(4545,actions)
  2444. scriptletDeferred.debounce(() => {
  2445. document.dispatchEvent(new CustomEvent('tyt-engagement-panel-visibility-change', {
  2446. detail: actions
  2447. }))
  2448. actions = null
  2449. })
  2450. }
  2451.  
  2452. }
  2453.  
  2454. function ytBtnCloseEngagementPanel(/** @type {HTMLElement} */ s) {
  2455. //ePanel.setAttribute('visibility',"ENGAGEMENT_PANEL_VISIBILITY_HIDDEN");
  2456.  
  2457. let panelId = s.getAttribute('target-id')
  2458. scriptletDeferred.debounce(() => {
  2459. document.dispatchEvent(new CustomEvent('tyt-engagement-panel-visibility-change', {
  2460. detail: {
  2461. panelId,
  2462. toHide: true
  2463. }
  2464. }))
  2465. })
  2466.  
  2467. }
  2468.  
  2469. function ytBtnCloseEngagementPanels() {
  2470. if (isEngagementPanelExpanded()) {
  2471. for (const s of document.querySelectorAll(
  2472. `ytd-watch-flexy[flexy][tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility]:not([hidden])`
  2473. )) {
  2474. if (s.getAttribute('visibility') == "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED") ytBtnCloseEngagementPanel(s);
  2475. }
  2476. }
  2477. }
  2478.  
  2479. function openDonationShelf() {
  2480. if (!isDonationShelfExpanded()) {
  2481. let btn = document.querySelector('#tyt-donation-shelf-toggle-btn')
  2482. if (btn) {
  2483. btn.click();
  2484. return true;
  2485. }
  2486. }
  2487. return false;
  2488. }
  2489. function closeDonationShelf() {
  2490. if (isDonationShelfExpanded()) {
  2491. let btn = document.querySelector('#tyt-donation-shelf-toggle-btn')
  2492. if (btn) {
  2493. btn.click();
  2494. return true;
  2495. }
  2496. }
  2497. return false;
  2498. }
  2499.  
  2500. function ytBtnSetTheater() {
  2501. if (!isTheater()) {
  2502. const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
  2503. if (sizeBtn) sizeBtn.click();
  2504. }
  2505. }
  2506.  
  2507. function ytBtnCancelTheater() {
  2508. if (isTheater()) {
  2509. const sizeBtn = document.querySelector('ytd-watch-flexy #ytd-player button.ytp-size-button')
  2510. if (sizeBtn) sizeBtn.click();
  2511. }
  2512. }
  2513.  
  2514. function ytBtnExpandChat() {
  2515. let button = document.querySelector('ytd-live-chat-frame#chat[collapsed] > .ytd-live-chat-frame#show-hide-button')
  2516. if (button) {
  2517. button =
  2518. querySelectorFromAnchor.call(button, 'div.yt-spec-touch-feedback-shape') ||
  2519. querySelectorFromAnchor.call(button, 'ytd-toggle-button-renderer');
  2520. if (button) button.click();
  2521. }
  2522. }
  2523.  
  2524. function ytBtnCollapseChat() {
  2525. let button = document.querySelector('ytd-live-chat-frame#chat:not([collapsed]) > .ytd-live-chat-frame#show-hide-button')
  2526. if (button) {
  2527. button =
  2528. querySelectorFromAnchor.call(button, 'div.yt-spec-touch-feedback-shape') ||
  2529. querySelectorFromAnchor.call(button, 'ytd-toggle-button-renderer');
  2530. if (button) button.click();
  2531. }
  2532. }
  2533.  
  2534.  
  2535. async function makeVideosAutoLoad2() {
  2536. let sVideosList = document.querySelector('ytd-watch-flexy #tab-videos [placeholder-videos]');
  2537.  
  2538. if (!sVideosList) return null;
  2539.  
  2540. //let ab = sVideosList.getAttribute('tabview-videos-autoload')
  2541. await Promise.resolve(0);
  2542.  
  2543. let endPosDOM = document.querySelector('tabview-view-videos-endpos')
  2544. if (endPosDOM) endPosDOM.remove(); // just in case
  2545. endPosDOM = document.createElement('tabview-view-videos-endpos')
  2546. insertAfterTo(endPosDOM, sVideosList);
  2547.  
  2548. await Promise.resolve(0);
  2549.  
  2550.  
  2551. //sVideosList.setAttribute('tabview-videos-autoload', '1')
  2552.  
  2553. // _console.log(9333)
  2554. if (!sVideosITO) {
  2555.  
  2556. sVideosITO = new IntersectionObserver((entries) => {
  2557.  
  2558. if ((wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) return;
  2559.  
  2560. // _console.log(9334, entries)
  2561. if (entries.length !== 1) return;
  2562. if (entries[0].isIntersecting !== true) return;
  2563. let elm = ((entries[0] || 0).target || 0);
  2564. if (!elm) return;
  2565. elm = null;
  2566. entries = null;
  2567.  
  2568. new Promise(resolve => {
  2569.  
  2570. // compatibile with Search While Watching Video
  2571. let isSearchGeneratedWithHiddenContinuation = !!document.querySelector('#related.style-scope.ytd-watch-flexy ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy ytd-compact-video-renderer.yt-search-generated.style-scope.ytd-item-section-renderer ~ ytd-continuation-item-renderer.style-scope.ytd-item-section-renderer[hidden]');
  2572. if (isSearchGeneratedWithHiddenContinuation) return;
  2573.  
  2574. // native YouTube coding use different way to handle custom videos, unknown condition for the continutation loading.
  2575. let isOtherChipSelected = !!document.querySelector('ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy yt-chip-cloud-renderer.style-scope.yt-related-chip-cloud-renderer yt-chip-cloud-chip-renderer.style-scope.yt-chip-cloud-renderer[aria-selected="false"] ~ [aria-selected="true"]')
  2576. if (isOtherChipSelected) return;
  2577.  
  2578. setTimeout(resolve, 30); // delay required to allow YouTube generate the continuation elements
  2579.  
  2580.  
  2581. }).then(() => {
  2582.  
  2583. let res = setVideosTwoColumns(2 | 4, true)
  2584.  
  2585. // _console.log(9335, res)
  2586.  
  2587. if (res.m2 && res.m3) {
  2588. let m4 = closestDOM.call(res.m2, 'ytd-continuation-item-renderer');
  2589. let m5, m6;
  2590.  
  2591. // _console.log(9336, m4)
  2592. if (m4) {
  2593. m5 = querySelectorFromAnchor.call(m4, 'ytd-button-renderer.style-scope.ytd-continuation-item-renderer, yt-button-renderer.style-scope.ytd-continuation-item-renderer');
  2594.  
  2595. // YouTube coding bug - correct is 'ytd-button-renderer'. If the page is redirected under single column mode, the tag become 'yt-button-renderer'
  2596. // under 'yt-button-renderer', the
  2597.  
  2598. if (m5)
  2599. m6 = querySelectorFromAnchor.call(m5, 'button.yt-spec-button-shape-next--call-to-action'); // main
  2600.  
  2601. // _console.log(9337, m4, m5, m6)
  2602.  
  2603. if (m6) {
  2604. m6.click() // generic solution
  2605. } else if (m5) {
  2606. m5.click(); // not sure
  2607. } else {
  2608. m4.dispatchEvent(new Event('yt-service-request-sent-button-renderer')); // only for correct YouTube coding
  2609. }
  2610. }
  2611. m4 = null;
  2612. m5 = null;
  2613. m6 = null;
  2614. }
  2615. res = null;
  2616.  
  2617. });
  2618.  
  2619. }, {
  2620. rootMargin: `0px`, // refer to css margin-top:-30vh
  2621. threshold: [0]
  2622. })
  2623. sVideosITO.observe(endPosDOM);
  2624. } else {
  2625. sVideosITO.disconnect();
  2626. sVideosITO.observe(endPosDOM);
  2627. }
  2628.  
  2629.  
  2630. }
  2631.  
  2632.  
  2633. function fixTabs() {
  2634.  
  2635. if (!scriptEnable) return;
  2636.  
  2637. let queryElement = document.querySelector('*:not(#tab-videos) > #related.ytd-watch-flexy > ytd-watch-next-secondary-results-renderer');
  2638.  
  2639. let isRelocated = !!queryElement;
  2640.  
  2641. if (isRelocated) {
  2642.  
  2643. // _console.log(3202, 2)
  2644.  
  2645. let relatedElm = closestDOM.call(queryElement, '#related.ytd-watch-flexy'); // NOT NULL
  2646.  
  2647. let right_tabs = document.querySelector('#right-tabs'); // can be NULL
  2648.  
  2649. let tab_videos = right_tabs ? querySelectorFromAnchor.call(right_tabs, "#tab-videos") : null; // can be NULL
  2650.  
  2651. if (tab_videos !== null) {
  2652.  
  2653. // _console.log(3202, 4)
  2654.  
  2655. 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')
  2656. if (target_container) elementAppend.call(target_container, right_tabs) // last-child
  2657.  
  2658. elementAppend.call(tab_videos, relatedElm);
  2659. // no any other element set these attr. only init / relocation
  2660. relatedElm.setAttribute('placeholder-for-youtube-play-next-queue', '')
  2661. relatedElm.setAttribute('placeholder-videos', '')
  2662.  
  2663. makeVideosAutoLoad2();
  2664.  
  2665. }
  2666.  
  2667. }
  2668.  
  2669. /** @type {HTMLElement | null} */
  2670.  
  2671. let chatroom = null;
  2672. if (chatroom = document.querySelector('ytd-live-chat-frame#chat')) {
  2673.  
  2674. const container = chatroom.parentNode.id === 'chat-container' ? chatroom.parentNode : chatroom;
  2675.  
  2676. let pHolderElm = document.querySelector('tabview-view-pholder[data-positioner="before|#chat"]');
  2677.  
  2678. if (!pHolderElm || pHolderElm.nextElementSibling !== container) {
  2679.  
  2680. if (pHolderElm) pHolderElm.remove();
  2681.  
  2682. if (document.querySelector('.YouTubeLiveFilledUpView')) {
  2683. // no relocation
  2684. } else {
  2685.  
  2686. let rightTabs = document.querySelector('#right-tabs');
  2687. if (rightTabs) {
  2688. insertBeforeTo(container, rightTabs);
  2689. }
  2690.  
  2691. }
  2692.  
  2693. if (!pHolderElm) {
  2694. pHolderElm = document.createElement('tabview-view-pholder');
  2695. pHolderElm.setAttribute('data-positioner', 'before|#chat');
  2696. }
  2697.  
  2698. insertBeforeTo(pHolderElm, container)
  2699. }
  2700.  
  2701. }
  2702.  
  2703. }
  2704.  
  2705.  
  2706. async function isDocumentInFullScreenMode() {
  2707. return document.fullscreenElement !== null;
  2708. }
  2709. async function energizedByVideoTimeUpdate() {
  2710.  
  2711. const isFullscreen = await isDocumentInFullScreenMode();
  2712. if (isFullscreen) return;
  2713.  
  2714. // force browser to load the videostream during playing (primarily for music videos)
  2715. // both background and foreground
  2716.  
  2717. _updateTimeAccum++;
  2718.  
  2719. if ((_updateTimeAccum + _viTimeNum) % 11 === 0) {
  2720. // console.log(document.querySelector('video').currentTime) // 2.55, 2.64, 3.12, ...
  2721. // about 2.66s
  2722.  
  2723. if (_viTimeNum > 211) {
  2724. // around 30.9s ~ 31.9s
  2725. _viTimeNum = 200;
  2726. _updateTimeAccum = (_updateTimeAccum % 8) + 1; // reset to 1 ~ 8
  2727. postMessage({ tabviewEnergized: true }, 'https://www.youtube.com'); // post message to make alive
  2728. }
  2729.  
  2730. document.head.dataset.viTime = `${_viTimeNum + 1}`;
  2731. await Promise.resolve(0)
  2732. _viTimeNum = +document.head.dataset.viTime || 0;
  2733. }
  2734.  
  2735.  
  2736. }
  2737.  
  2738. function autoCompletePosCreate() {
  2739.  
  2740. let positioner = document.createElement("tabview-view-autocomplete-pos");
  2741. let oldPositioner = document.querySelector("tabview-view-autocomplete-pos");
  2742. if (oldPositioner) oldPositioner.remove();
  2743. return positioner
  2744. }
  2745.  
  2746. function handlerAutoCompleteExist() {
  2747. // Youtube - Search While Watching Video
  2748.  
  2749. /** @type {HTMLElement} */
  2750. let searchBox, autoComplete;
  2751. searchBox = this;
  2752. this.removeEventListener('autocomplete-sc-exist', handlerAutoCompleteExist, false)
  2753. let domId = this.getAttribute('data-autocomplete-results-id')
  2754.  
  2755. autoComplete = document.querySelector(`[data-autocomplete-input-id="${domId}"]`)
  2756.  
  2757. if (!domId || !searchBox) return;
  2758.  
  2759. let positioner = nodeNextSibling(searchBox);
  2760. if (positioner) {
  2761. if (positioner.nodeName.toLowerCase() !== "tabview-view-autocomplete-pos") {
  2762. positioner = autoCompletePosCreate();
  2763. insertAfterTo(positioner, searchBox);
  2764. }
  2765. } else {
  2766. positioner = autoCompletePosCreate();
  2767. prependTo(positioner, nodeParent(searchBox));
  2768. }
  2769. prependTo(autoComplete, positioner);
  2770.  
  2771. setupSearchBox(searchBox, positioner);
  2772.  
  2773.  
  2774. }
  2775.  
  2776. async function setupSearchBox(searchBox, positioner) {
  2777.  
  2778. let mb = getComputedStyle(searchBox).marginBottom
  2779. let h = searchBox.offsetHeight + 'px'
  2780.  
  2781. positioner.style.setProperty('--tyt-swwv-searchbox-mb', mb)
  2782. positioner.style.setProperty('--tyt-swwv-searchbox-h', h)
  2783.  
  2784. mtf_autocomplete_search()
  2785.  
  2786. }
  2787.  
  2788. function mtf_autocomplete_search() {
  2789. // Youtube - Search While Watching Video
  2790.  
  2791. /** @type {HTMLElement | null} */
  2792. const ytdFlexyElm = es.ytdFlexy;
  2793. if (!scriptEnable || !ytdFlexyElm) return;
  2794.  
  2795. const autocomplete = querySelectorFromAnchor.call(ytdFlexyElm, '[placeholder-for-youtube-play-next-queue] input#suggestions-search + tabview-view-autocomplete-pos > .autocomplete-suggestions[data-autocomplete-input-id]:not([position-fixed-by-tabview-youtube])')
  2796.  
  2797. if (autocomplete) {
  2798.  
  2799. const searchBox = document.querySelector('[placeholder-for-youtube-play-next-queue] input#suggestions-search')
  2800.  
  2801.  
  2802. if (searchBox) {
  2803.  
  2804. const rAutoComplete = mWeakRef(autocomplete);
  2805.  
  2806. function setVisible(autocomplete, b) {
  2807. autocomplete.style.display = (b ? 'block' : 'none');
  2808. }
  2809.  
  2810. function isContentNotEmpty(searchbox, autocomplete) {
  2811. return (searchbox.value || '').length > 0 && (autocomplete.textContent || '').length > 0;
  2812. }
  2813.  
  2814. nodeParent(autocomplete).setAttribute('position-fixed-by-tabview-youtube', '');
  2815. autocomplete.setAttribute('position-fixed-by-tabview-youtube', '');
  2816. autocomplete.setAttribute('userscript-scrollbar-render', '')
  2817.  
  2818. //let cancelClickToggle = false;
  2819.  
  2820. if (!searchBox.hasAttribute('is-set-click-to-toggle')) {
  2821. searchBox.setAttribute('is-set-click-to-toggle', '')
  2822.  
  2823. searchBox.addEventListener('click', function () {
  2824.  
  2825. Promise.resolve(0).then(() => {
  2826.  
  2827. const autocomplete = kRef(rAutoComplete);
  2828.  
  2829. if (!autocomplete) return;
  2830.  
  2831. const isNotEmpty = isContentNotEmpty(this, autocomplete);
  2832.  
  2833. if (isNotEmpty) {
  2834.  
  2835. let elmVisible = isDOMVisible(autocomplete);
  2836.  
  2837. if (elmVisible) {
  2838. setVisible(autocomplete, false)
  2839. }
  2840. else {
  2841. setVisible(autocomplete, true)
  2842. }
  2843.  
  2844. }
  2845.  
  2846. })
  2847.  
  2848. }, bubblePassive)
  2849.  
  2850. let cacheScrollIntoView = null;
  2851. let rafXC = 0;
  2852. searchBox.addEventListener('keydown', function (evt) {
  2853. //cancelClickToggle = true;
  2854. switch (evt.code) {
  2855. case 'ArrowUp':
  2856. case 'ArrowDown':
  2857.  
  2858. let t = Date.now();
  2859. if (rafXC === 0) {
  2860. getRAFPromise().then(() => {
  2861. rafXC = 0;
  2862. let d = Date.now();
  2863. if (d - t > 300) return;
  2864.  
  2865. const autocomplete = kRef(rAutoComplete);
  2866.  
  2867. let selected = querySelectorFromAnchor.call(autocomplete, '.autocomplete-suggestion.selected');
  2868. let bool = selected && selected !== cacheScrollIntoView;
  2869. cacheScrollIntoView = selected;
  2870. if (bool) {
  2871.  
  2872. try {
  2873. selected.scrollIntoView({ block: "nearest", inline: "nearest" });
  2874. } catch (e) { }
  2875.  
  2876. }
  2877.  
  2878. });
  2879. rafXC = 1;
  2880. }
  2881. default:
  2882. //
  2883. }
  2884.  
  2885.  
  2886. }, bubblePassive)
  2887.  
  2888. searchBox.addEventListener('tyt-autocomplete-suggestions-change', function (evt) {
  2889.  
  2890. //cancelClickToggle = true;
  2891. if (evt.target !== document.activeElement) return;
  2892. setTimeout(() => {
  2893. const autocomplete = document.querySelector(`.autocomplete-suggestions[data-autocomplete-input-id="${this.getAttribute('data-autocomplete-results-id')}"]`);
  2894. if (!autocomplete) return;
  2895. const isNotEmpty = isContentNotEmpty(this, autocomplete);
  2896. if (isNotEmpty) {
  2897. // dont detect visibility; just set to visible
  2898. setVisible(autocomplete, true);
  2899. }
  2900. }, 20);
  2901.  
  2902. }, bubblePassive)
  2903.  
  2904. }
  2905.  
  2906. }
  2907.  
  2908. }
  2909.  
  2910. }
  2911.  
  2912. const insertBeforeTo = HTMLElement.prototype.before ? (elm, target) => {
  2913. if (!target || !elm) return null;
  2914. // using before
  2915. HTMLElement.prototype.before.call(target, elm);
  2916. return true;
  2917. } : (elm, target) => {
  2918. if (!target || !elm) return null;
  2919. // using insertBefore
  2920. try {
  2921. HTMLElement.prototype.insertBefore.call(nodeParent(target), elm, target);
  2922. return true;
  2923. } catch (e) {
  2924. console.log('element insert failed in old browser CE')
  2925. }
  2926. return false;
  2927. }
  2928.  
  2929. const insertAfterTo = HTMLElement.prototype.after ? (elm, target) => {
  2930. if (!target || !elm) return null;
  2931. // using after
  2932. HTMLElement.prototype.after.call(target, elm);
  2933. return true;
  2934. } : (elm, target) => {
  2935. if (!target || !elm) return null;
  2936. // using insertBefore
  2937. try {
  2938. HTMLElement.prototype.insertBefore.call(nodeParent(target), elm, nodeNextSibling(target));
  2939. return true;
  2940. } catch (e) {
  2941. console.log('element insert failed in old browser CE')
  2942. }
  2943. return false;
  2944. }
  2945.  
  2946. const prependTo = HTMLElement.prototype.prepend ? (elm, target) => {
  2947. if (!target || !elm) return null;
  2948. // using prepend
  2949. HTMLElement.prototype.prepend.call(target, elm);
  2950. return true;
  2951. } : (elm, target) => {
  2952. if (!target || !elm) return null;
  2953. // using insertBefore
  2954. try {
  2955. HTMLElement.prototype.insertBefore.call(target, elm, nodeFirstChild(target));
  2956. return true;
  2957. } catch (e) {
  2958. console.log('element insert failed in old browser CE')
  2959. }
  2960. return false;
  2961. }
  2962.  
  2963. const appends = HTMLElement.prototype.append ? (target, ...args) => {
  2964. HTMLElement.prototype.append.call(target, ...args);
  2965. return true;
  2966. } : (target, ...args) => {
  2967. for (const s of args) {
  2968. target.appendChild(s)
  2969. }
  2970. return true;
  2971. }
  2972.  
  2973.  
  2974. // css animation check for element relocation
  2975. function mtf_liveChatBtnF(node) {
  2976.  
  2977. if (!node || node.nodeType !== 1) return;
  2978.  
  2979. /** @type {HTMLElement | null} */
  2980. const ytdFlexyElm = es.ytdFlexy;
  2981. if (!scriptEnable || !ytdFlexyElm) return;
  2982.  
  2983. let button = node;
  2984.  
  2985. if (button) {
  2986. prependTo(button, nodeParent(button));
  2987. }
  2988.  
  2989.  
  2990. }
  2991.  
  2992.  
  2993. function getWrapper(wrapperId) {
  2994. let wrapper = document.getElementById(wrapperId);
  2995. if (!wrapper) {
  2996. wrapper = document.createElement('div');
  2997. wrapper.id = wrapperId;
  2998. }
  2999. return wrapper;
  3000. }
  3001.  
  3002. // continuous check for element relocation
  3003. // fired at begining & window resize, etc
  3004. // might moved to #primary
  3005. function mtf_append_playlist(/** @type {HTMLElement | null} */ playlist) {
  3006.  
  3007. if (playlist === null) {
  3008. playlist = document.querySelector('ytd-watch-flexy[playlist] *:not(#tabview-playlist-wrapper) > ytd-playlist-panel-renderer.style-scope.ytd-watch-flexy#playlist:not(.ytd-miniplayer)');
  3009. // this playlist is highly possible to have '#items'
  3010. if (!playlist) return;
  3011. }
  3012.  
  3013. /** @type {HTMLElement | null} */
  3014. const ytdFlexyElm = es.ytdFlexy;
  3015. if (!scriptEnable || !ytdFlexyElm) return;
  3016.  
  3017. let items = querySelectorFromAnchor.call(playlist, "*:not(#tabview-playlist-wrapper) > ytd-playlist-panel-renderer#playlist:not(.ytd-miniplayer) #items.ytd-playlist-panel-renderer:not(:empty)");
  3018.  
  3019. if (items !== null && playlist.nodeName.toUpperCase() === 'YTD-PLAYLIST-PANEL-RENDERER') {
  3020.  
  3021. let tab_list = document.querySelector("#tab-list");
  3022.  
  3023. if (!tab_list) return;
  3024.  
  3025. let w = getWrapper("tabview-playlist-wrapper");
  3026. let docFrag = new DocumentFragment();
  3027. // avoid immediate reflow for append playlist before append to tab_list
  3028. elementAppend.call(docFrag, w);
  3029. elementAppend.call(w, playlist);
  3030. elementAppend.call(tab_list, docFrag);
  3031. docFrag = null;
  3032. w = null;
  3033.  
  3034. }
  3035. }
  3036.  
  3037.  
  3038. function getCountHText(elm) {
  3039. return `${pageFetchedDataVideoId || 0}...${elm.textContent}`
  3040. }
  3041.  
  3042. // content fix - info & playlist
  3043. // fired at begining, and keep for in case any change
  3044. function mtf_fix_details() {
  3045.  
  3046. if (!scriptEnable) return Promise.resolve(0); // in case
  3047.  
  3048. return Promise.all([
  3049. new Promise(resolve => {
  3050.  
  3051.  
  3052. 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])');
  3053.  
  3054. if (contentToggleBtn) {
  3055.  
  3056. (() => {
  3057. const domElement = contentToggleBtn;
  3058. contentToggleBtn = null;
  3059. // if(!domElement.parentElement) return; // not working in pseudo custom element - parentNode = documentFragment
  3060. const expander = closestDOM.call(domElement, 'ytd-watch-flexy #tab-info ytd-expander')
  3061.  
  3062. if (!expander || expander.nodeType !== 1) return; // checking whether it is still on the page
  3063.  
  3064. if (expander.style.getPropertyValue('--ytd-expander-collapsed-height')) {
  3065. expander.style.setProperty('--ytd-expander-collapsed-height', '')
  3066. }
  3067. /*
  3068. nativeCall(expander, [
  3069. { 'property': 'canToggleJobId', 'value': 1 }, // false disable calculateCanCollapse in childrenChanged
  3070. { 'property': 'alwaysToggleable', 'value': false }, // this is checked in childrenChanged
  3071. { 'property': 'recomputeOnResize', 'value': false }, // no need to check toggleable
  3072. { 'property': 'isToggled', 'value': true }, // show full content
  3073. { 'property': 'canToggle', 'value': false }, // hide show more or less btn
  3074. { 'property': 'collapsedHeight', 'value': 999999 } // disable collapsed height check
  3075. ])
  3076. */
  3077. scriptletDeferred.debounce(() => {
  3078. expander.dispatchEvent(new CustomEvent("tabview-expander-config"));
  3079. })
  3080. // console.log(23131)
  3081.  
  3082. })();
  3083. }
  3084.  
  3085. resolve();
  3086.  
  3087.  
  3088. }),
  3089.  
  3090. new Promise(resolve => {
  3091.  
  3092.  
  3093. let strcturedInfo = document.querySelector('ytd-watch-flexy #tab-info ytd-structured-description-content-renderer.style-scope.ytd-video-secondary-info-renderer[hidden]')
  3094. if (strcturedInfo) {
  3095.  
  3096. (() => {
  3097.  
  3098. strcturedInfo.removeAttribute('hidden');
  3099.  
  3100.  
  3101. setTimeout(() => {
  3102.  
  3103.  
  3104. let e = closestDOM.call(strcturedInfo, 'ytd-watch-flexy #tab-info ytd-expander');
  3105.  
  3106. if (!e) return;
  3107. let s = querySelectorAllFromAnchor.call(e, '#tab-info .more-button.style-scope.ytd-video-secondary-info-renderer[role="button"]');
  3108. if (s.length === 1) {
  3109. let sp = nodeParent(s[0]);
  3110. if (sp.nodeName.toUpperCase() === 'TP-YT-PAPER-BUTTON') {
  3111. sp.click();
  3112. }
  3113. }
  3114.  
  3115. }, 300)
  3116.  
  3117. })();
  3118. }
  3119.  
  3120.  
  3121. resolve();
  3122.  
  3123. }),
  3124.  
  3125. new Promise(resolve => {
  3126.  
  3127.  
  3128. // just in case the playlist is collapsed
  3129. let playlist = document.querySelector('#tab-list ytd-playlist-panel-renderer#playlist')
  3130. if (playlist && playlist.matches('[collapsed], [collapsible]')) {
  3131.  
  3132. (() => {
  3133.  
  3134. const domElement = playlist;
  3135. playlist = null;
  3136. // if(!domElement.parentElement || domElement.nodeType!==1) return; // not working in pseudo custom element - parentNode = documentFragment
  3137. const tablist = closestDOM.call(domElement, 'ytd-watch-flexy #tab-list')
  3138.  
  3139. if (!tablist || tablist.nodeType !== 1) return; // checking whether it is still on the page
  3140.  
  3141. if (domElement.hasAttribute('collapsed')) wAttr(domElement, 'collapsed', false);
  3142. if (domElement.hasAttribute('collapsible')) wAttr(domElement, 'collapsible', false);
  3143. })();
  3144. }
  3145.  
  3146. resolve();
  3147.  
  3148.  
  3149. }),
  3150.  
  3151. new Promise(resolve => {
  3152.  
  3153.  
  3154. let subscribersCount = document.querySelector('#primary.ytd-watch-flexy #below ytd-watch-metadata #owner #owner-sub-count');
  3155.  
  3156. if (subscribersCount) {
  3157. if (!subscribersCount.hasAttribute('title')) {
  3158. // assume YouTube native coding would not implement [title]
  3159.  
  3160. let ytdWatchMetaDataElm = closestDOM.call(subscribersCount, 'body #primary.ytd-watch-flexy #below ytd-watch-metadata:not([tabview-uploader-hover])');
  3161. if (ytdWatchMetaDataElm) {
  3162. ytdWatchMetaDataElm.setAttribute('tabview-uploader-hover', '')
  3163. let _h = 0;
  3164. ytdWatchMetaDataElm.addEventListener('transitionend', function (evt) {
  3165. // no css selector rule required; no delay js function call required
  3166.  
  3167. let selection = evt.propertyName === 'background-position-y' ? 1 : evt.propertyName === 'background-position-x' ? 2 : 0;
  3168.  
  3169. if (selection && evt.target) {
  3170. let cssRoot = this; // no querySelector is required
  3171. if (cssRoot.classList.contains('tabview-uploader-hover')) {
  3172. if (evt.target.id !== 'owner') return;
  3173. cssRoot.classList.toggle('tabview-uploader-hover', false);
  3174. }
  3175. }
  3176.  
  3177. if (selection === 1) { // string comparision only
  3178.  
  3179. // If the cursor initially stayed at the owner info area,
  3180. // the mechanism will be broken
  3181. // so implement the true leave detection at their parent
  3182.  
  3183. let isHover = evt.elapsedTime < 0.03; // 50ms @normal; 10ms @hover;
  3184.  
  3185. let cssRoot = this; // no querySelector is required
  3186. if (!isHover) {
  3187. // 50ms is slowest than sub element leave effect
  3188. if (_h > 0) cssRoot.classList.toggle('tabview-uploader-hover', false) // even the order is incorrect, removal of class is safe.
  3189. _h = 0; // in case
  3190. } else if (isHover) {
  3191. // 10ms is faster than sub element hover effect
  3192. // no removal of class in case browser's transition implemention order is incorrect
  3193. _h = 0; // in case
  3194. }
  3195.  
  3196. } else if (selection === 2) { // string comparision only
  3197.  
  3198. //from one element to another element; hover effect of 2nd element transition end first.
  3199. // _h: 0 -> 1 -> 2 -> 1
  3200.  
  3201. let isHover = evt.elapsedTime < 0.03; // 40ms @normal; 20ms @hover;
  3202. if (isHover) _h++; else _h--;
  3203. let cssRoot = this; // no querySelector is required
  3204. if (_h <= 0) {
  3205. cssRoot.classList.toggle('tabview-uploader-hover', false)
  3206. _h = 0; // in case
  3207. } else if (_h === 1) {
  3208. cssRoot.classList.toggle('tabview-uploader-hover', true)
  3209. }
  3210.  
  3211. }
  3212.  
  3213. }, capturePassive) // capture the hover effect inside the cssRoot
  3214. }
  3215.  
  3216. }
  3217. subscribersCount.setAttribute('title', subscribersCount.textContent); // set at every page update
  3218.  
  3219. }
  3220.  
  3221. resolve();
  3222.  
  3223. })
  3224.  
  3225.  
  3226. ]);
  3227.  
  3228.  
  3229. }
  3230.  
  3231.  
  3232. const innerCommentsFuncs = [
  3233. // comments
  3234. function () {
  3235.  
  3236. let elm = kRef(this.elm);
  3237. // _console.log(2907, 1, !!elm)
  3238. if (!elm) return;
  3239.  
  3240. let span = document.querySelector("span#tyt-cm-count")
  3241. let r = '0';
  3242. let txt = elm.textContent
  3243. if (typeof txt == 'string') {
  3244. let m = txt.match(/[\d\,\.\s]+/)
  3245. if (m) {
  3246. let d = +m[0].replace(/\D+/g, '');
  3247. let ds = d.toLocaleString(document.documentElement.lang);
  3248. let rtxt = txt.replace(ds, '')
  3249. if (rtxt !== txt && !/\d/.test(rtxt)) {
  3250. r = ds;
  3251. }
  3252. }
  3253. }
  3254.  
  3255. if (span) {
  3256. let tab_btn = closestDOM.call(span, '.tab-btn[tyt-tab-content="#tab-comments"]')
  3257. if (tab_btn) tab_btn.setAttribute('loaded-comment', 'normal')
  3258. span.textContent = r;
  3259. }
  3260.  
  3261. setCommentSection(1);
  3262. m_last_count = getCountHText(elm);
  3263. // _console.log(2907, 2, m_last_count)
  3264. return true;
  3265. },
  3266. // message
  3267. function () {
  3268.  
  3269. let elm = kRef(this.elm);
  3270. // _console.log(2907, 2, !!elm)
  3271. if (!elm) return;
  3272.  
  3273. let span = document.querySelector("span#tyt-cm-count")
  3274. if (span) {
  3275. let tab_btn = closestDOM.call(span, '.tab-btn[tyt-tab-content="#tab-comments"]')
  3276. if (tab_btn) tab_btn.setAttribute('loaded-comment', 'message')
  3277. span.textContent = '\u200B';
  3278. }
  3279.  
  3280. setCommentSection(1);
  3281. m_last_count = getCountHText(elm);
  3282. // _console.log(2907, 2, m_last_count)
  3283. return true;
  3284. }
  3285. ]
  3286.  
  3287.  
  3288. let innerDOMCommentsCountTextCache = null;
  3289. /**
  3290. *
  3291. * @param {boolean} [requireResultCaching]
  3292. * @returns
  3293. */
  3294. function innerDOMCommentsCountLoader(requireResultCaching) {
  3295. // independent of tabs initialization
  3296. // f() is executed after tabs being ready
  3297.  
  3298. /** @type {HTMLElement | null} */
  3299. const ytdFlexyElm = es.ytdFlexy;
  3300. if (!scriptEnable || !ytdFlexyElm) return;
  3301.  
  3302. // _console.log(3434, pageType)
  3303. if (pageType !== 'watch') return;
  3304.  
  3305.  
  3306. /** @type {Array<HTMLElement>} */
  3307. 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')]
  3308.  
  3309.  
  3310. const eTime = +`${Date.now() - mTime}00`;
  3311.  
  3312. let res = new Array(qmElms.length);
  3313. res.newFound = false;
  3314.  
  3315.  
  3316. let ci = 0;
  3317. let latest = -1;
  3318.  
  3319. let retrival = cmTime;
  3320. cmTime = eTime;
  3321.  
  3322. for (const qmElm of qmElms) {
  3323.  
  3324. let mgz = 0
  3325. if (qmElm.id === 'count') {
  3326. //#count.ytd-comments-header-renderer
  3327. mgz = 1;
  3328.  
  3329. } else if ((qmElm.textContent || '').trim()) {
  3330. //ytd-message-renderer.ytd-item-section-renderer
  3331. mgz = 2;
  3332. // it is possible to get the message before the header generation.
  3333. // sample link - https://www.youtube.com/watch?v=PVUZ8Nvr1ic
  3334. // sample link - https://www.youtube.com/watch?v=yz8AiQc1Bk8
  3335. }
  3336.  
  3337. if (mgz > 0) {
  3338.  
  3339.  
  3340. let lastUpdate = loadedCommentsDT.get(qmElm) || 0;
  3341. let diff = retrival - lastUpdate
  3342. // _console.log(2907, diff)
  3343. let isNew = (diff > 4 || diff < -4);
  3344. if (!isNew) {
  3345. loadedCommentsDT.set(qmElm, eTime);
  3346. } else {
  3347. loadedCommentsDT.set(qmElm, eTime + 1);
  3348. res.newFound = true;
  3349. latest = ci;
  3350. }
  3351.  
  3352. res[ci] = {
  3353. elm: mWeakRef(qmElm),
  3354. isNew: isNew,
  3355. isLatest: false, //set afterwards
  3356. f: innerCommentsFuncs[mgz - 1]
  3357. }
  3358.  
  3359. if (DEBUG_LOG) {
  3360. res[ci].status = mgz;
  3361. res[ci].text = qmElm.textContent;
  3362. }
  3363.  
  3364. ci++;
  3365.  
  3366. }
  3367.  
  3368. }
  3369. if (res.length > ci) res.length = ci;
  3370.  
  3371. if (latest >= 0) {
  3372.  
  3373. res[latest].isLatest = true;
  3374.  
  3375.  
  3376. let elm = kRef(res[latest].elm);
  3377. if (elm)
  3378. innerDOMCommentsCountTextCache = elm.textContent;
  3379.  
  3380. } else if (res.length === 1) {
  3381.  
  3382. let qmElm = kRef(res[0].elm);
  3383. let t = null;
  3384. if (qmElm) {
  3385.  
  3386. let t = qmElm.textContent;
  3387. if (t !== innerDOMCommentsCountTextCache) {
  3388.  
  3389.  
  3390. loadedCommentsDT.set(qmElm, eTime + 1);
  3391. res.newFound = true;
  3392.  
  3393. res[0].isNew = true;
  3394. latest = 0;
  3395.  
  3396. res[latest].isLatest = true;
  3397.  
  3398. }
  3399.  
  3400. innerDOMCommentsCountTextCache = t;
  3401.  
  3402.  
  3403. }
  3404.  
  3405.  
  3406. }
  3407.  
  3408.  
  3409. // _console.log(2908, res, Q.comments_section_loaded)
  3410.  
  3411. // _console.log(696, res.map(e => ({
  3412.  
  3413. // text: kRef(e.elm).textContent,
  3414. // isNew: e.isNew,
  3415. // isLatest: e.isLatest
  3416.  
  3417. // })))
  3418.  
  3419. if (requireResultCaching) {
  3420. resultCommentsCountCaching(res);
  3421. }
  3422.  
  3423. return res;
  3424.  
  3425. }
  3426.  
  3427. function restoreFetching() {
  3428.  
  3429.  
  3430. if (mtf_forceCheckLiveVideo_disable === 2) return;
  3431.  
  3432.  
  3433. const ytdFlexyElm = es.ytdFlexy;
  3434. if (!ytdFlexyElm) return;
  3435.  
  3436. // _console.log(2901)
  3437.  
  3438. if ((ytdFlexyElm.getAttribute('tyt-comments') || '').indexOf('K') >= 0) return;
  3439.  
  3440. // _console.log(2902)
  3441.  
  3442. let visibleComments = querySelectorFromAnchor.call(ytdFlexyElm, 'ytd-comments#comments:not([hidden])')
  3443. if (!visibleComments) return;
  3444.  
  3445. // _console.log(2903)
  3446.  
  3447.  
  3448. ytdFlexyElm.setAttribute('tyt-comments', 'Kz');
  3449.  
  3450. const tabBtn = document.querySelector('[tyt-tab-content="#tab-comments"]');
  3451. let span = querySelectorFromAnchor.call(tabBtn, 'span#tyt-cm-count');
  3452. tabBtn.removeAttribute('loaded-comment')
  3453. span.innerHTML = '';
  3454.  
  3455. if (tabBtn) {
  3456. setTabBtnVisible(tabBtn, true);
  3457. }
  3458.  
  3459. // _console.log(2905)
  3460.  
  3461.  
  3462. }
  3463.  
  3464. function checkAndMakeNewCommentFetch() {
  3465. if (renderDeferred.resolved && Q.comments_section_loaded === 0 && fetchCounts.new && !fetchCounts.fetched) {
  3466. fetchCounts.new.f();
  3467. fetchCounts.fetched = true;
  3468. fetchCommentsFinished();
  3469. // _console.log(9972, 'fetched = true')
  3470. }
  3471. }
  3472. function onCommentsReady() {
  3473. if (mtf_forceCheckLiveVideo_disable !== 2) {
  3474. if (document.querySelector(`ytd-comments#comments`).hasAttribute('hidden')) {
  3475. // unavailable but not due to live chat
  3476. _disableComments();
  3477. } else if (Q.comments_section_loaded === 0) {
  3478. getFinalComments();
  3479. }
  3480. }
  3481. }
  3482.  
  3483. const resultCommentsCountCaching = (res) => {
  3484. // update fetchCounts by res
  3485. // indepedent of previous state of fetchCounts
  3486. // _console.log(2908, 10, res)
  3487. if (!res) return;
  3488. fetchCounts.count = res.length;
  3489. //if(fetchCounts.new && !document.documentElement.contains(fetchCounts.new.elm)) fetchCounts.new = null;
  3490. //if(fetchCounts.base && !document.documentElement.contains(fetchCounts.base.elm)) fetchCounts.base = null;
  3491. if (fetchCounts.new) return;
  3492. let newFound = res.newFound;
  3493. if (!newFound) {
  3494. if (res.length === 1) {
  3495. fetchCounts.base = res[0];
  3496. return false;
  3497. }
  3498. } else if (res.length === 1) {
  3499. fetchCounts.new = res[0];
  3500. return true;
  3501. } else if (res.length > 1) {
  3502. for (const entry of res) {
  3503. if (entry.isLatest === true && entry.isNew) {
  3504. fetchCounts.new = entry;
  3505. return true;
  3506. }
  3507. }
  3508. }
  3509. }
  3510.  
  3511. const domInit_comments = () => {
  3512.  
  3513.  
  3514. let comments = document.querySelector(`ytd-comments#comments:not([o3r-${sa_comments}])`);
  3515. if (!comments) return;
  3516.  
  3517. // once per {ytd-comments#comments} detection
  3518.  
  3519. const ytdFlexyElm = es.ytdFlexy;
  3520. if (!scriptEnable || !ytdFlexyElm) return;
  3521.  
  3522. // _console.log(3901)
  3523.  
  3524. if (mtoVisibility_Comments.bindElement(comments)) {
  3525. mtoVisibility_Comments.observer.check(9);
  3526. }
  3527.  
  3528.  
  3529. };
  3530.  
  3531. let fixLiveChatToggleButtonDispatchEventRid = 0;
  3532. const fixLiveChatToggleButtonDispatchEvent = () => {
  3533.  
  3534. let tid = ++fixLiveChatToggleButtonDispatchEventRid;
  3535. scriptletDeferred.debounce(() => {
  3536. if (tid === fixLiveChatToggleButtonDispatchEventRid) {
  3537. document.dispatchEvent(new CustomEvent("tabview-fix-live-chat-toggle-btn"));
  3538. }
  3539. });
  3540. }
  3541.  
  3542.  
  3543.  
  3544. const eventDispatcher = (evtName) => {
  3545. let erid = 0;
  3546. return (evtNode) => {
  3547. let tid = ++erid;
  3548. scriptletDeferred.debounce(() => {
  3549. if (tid === erid) {
  3550. evtNode.dispatchEvent(new CustomEvent(evtName));
  3551. }
  3552. });
  3553. }
  3554. }
  3555.  
  3556. let chatroomAttrCollapseCount = 0;
  3557.  
  3558. const dpeForceChatRenderOnChatExpanded = eventDispatcher("tabview-force-chat-render-on-chat-expanded");
  3559.  
  3560. const dpeNewUrlChat = eventDispatcher("tabview-chat-fix-url-on-new-video-page");
  3561. const dpeFixUrlChatWhenOnloadWithEmptyBody = eventDispatcher("tabview-chat-fix-url-onload-with-empty-body");
  3562.  
  3563. const dpeIframeReady = eventDispatcher('tabview-chatroom-ready');
  3564.  
  3565. const FP = {
  3566.  
  3567. mtf_attrPlaylist: (attrName, newValue) => {
  3568. //attr mutation checker - {ytd-playlist-panel-renderer#playlist} \single
  3569. //::attr ~ hidden
  3570. //console.log(1210)
  3571.  
  3572. // _console.log(21311)
  3573. if (!scriptEnable) return;
  3574. if (pageType !== 'watch') return;
  3575. /** @type {HTMLElement|null} */
  3576. let cssElm = es.ytdFlexy;
  3577. if (!cssElm) return;
  3578.  
  3579. // _console.log(21312)
  3580.  
  3581. let playlist = document.querySelector('#tab-list ytd-playlist-panel-renderer#playlist'); // can be null if it is manually triggered
  3582. let isAnyPlaylistExist = playlist && !playlist.hasAttribute('hidden');
  3583. const tabBtn = document.querySelector('[tyt-tab-content="#tab-list"]');
  3584. //console.log(1212.2, isPlaylistHidden, playlist.getAttribute('hidden'))
  3585. if (tabBtn) {
  3586. //console.log('attr playlist changed')
  3587. let isPlaylistTabHidden = tabBtn.classList.contains('tab-btn-hidden')
  3588. if (isPlaylistTabHidden && isAnyPlaylistExist) {
  3589. //console.log('attr playlist changed - no hide')
  3590. tabBtn.classList.remove("tab-btn-hidden");
  3591. } else if (!isPlaylistTabHidden && !isAnyPlaylistExist) {
  3592. //console.log('attr playlist changed - add hide')
  3593. hideTabBtn(tabBtn);
  3594. }
  3595. }
  3596. /* visible layout for triggering hidden removal */
  3597.  
  3598. },
  3599. mtf_attrComments: (attrName, newValue) => {
  3600. //attr mutation checker - {ytd-comments#comments} \single
  3601. //::attr ~ hidden
  3602.  
  3603. // *** consider this can happen immediately after pop state. timeout / interval might clear out.
  3604.  
  3605. renderDeferred.resolved && innerDOMCommentsCountLoader(true);
  3606. // this is triggered by mutationobserver, the comment count update might have ouccred
  3607.  
  3608. if (pageType !== 'watch') return;
  3609.  
  3610. let comments = document.querySelector('ytd-comments#comments')
  3611. const tabBtn = document.querySelector('[tyt-tab-content="#tab-comments"]');
  3612. if (!comments || !tabBtn) return;
  3613. let isCommentHidden = comments.hasAttribute('hidden')
  3614. //console.log('attr comments changed')
  3615.  
  3616.  
  3617. const ytdFlexyElm = es.ytdFlexy;
  3618. if (!scriptEnable || !ytdFlexyElm) return;
  3619.  
  3620. if (mtf_forceCheckLiveVideo_disable === 2) {
  3621.  
  3622. } else if (!isCommentHidden) {
  3623.  
  3624. ytdFlexyElm.setAttribute('tyt-comments', 'Kv');
  3625. if (!fetchCounts.fetched) {
  3626. emptyCommentSection();
  3627. }
  3628. //_console.log(9360, 71);
  3629. setTabBtnVisible(tabBtn, true); //if contains
  3630.  
  3631. } else if (isCommentHidden) {
  3632.  
  3633. ytdFlexyElm.setAttribute('tyt-comments', 'Kh');
  3634. if (pageType === 'watch' && Q.comments_section_loaded === 1) {
  3635. emptyCommentSection();
  3636. // _console.log(9360, 72);
  3637. }
  3638.  
  3639. }
  3640.  
  3641.  
  3642. },
  3643.  
  3644. mtf_attrChatroom: () => {
  3645. //attr mutation checker - {ytd-live-chat-frame#chat} \single
  3646. //::attr ~ collapsed
  3647.  
  3648. const ytdFlexyElm = es.ytdFlexy;
  3649. if (!scriptEnable || !ytdFlexyElm) return;
  3650. if (pageType !== 'watch') return;
  3651.  
  3652. setToggleBtnTxt();
  3653.  
  3654. layoutStatusMutex.lockWith(unlock => {
  3655.  
  3656. const chatBlock = document.querySelector('ytd-live-chat-frame#chat')
  3657. /** @type {HTMLElement | null} */
  3658. const cssElm = es.ytdFlexy;
  3659.  
  3660. if (!chatBlock || !cssElm) {
  3661. unlock();
  3662. return;
  3663. }
  3664.  
  3665. if (pageType !== 'watch') {
  3666. unlock();
  3667. return;
  3668. }
  3669.  
  3670. let newAttrV = '';
  3671. //mtf_attrChatroom => chat exist => tyt-chat non-null
  3672.  
  3673. let isCollapsed = !!chatBlock.hasAttribute('collapsed');
  3674.  
  3675. let currentAttr = cssElm.getAttribute('tyt-chat');
  3676.  
  3677. if (currentAttr !== null) { //string // [+-]?[az]+[az\$]+
  3678. let isPlusMinus = currentAttr.charCodeAt(0) < 46; // 43 OR 45
  3679. if (isPlusMinus) newAttrV = currentAttr.substring(1);
  3680. }
  3681.  
  3682. if (isCollapsed) newAttrV = `-${newAttrV}`;
  3683. if (!isCollapsed) newAttrV = `+${newAttrV}`;
  3684.  
  3685. wAttr(cssElm, 'tyt-chat', newAttrV);
  3686.  
  3687.  
  3688. if (typeof newAttrV === 'string' && !isCollapsed) lstTab.lastPanel = '#chatroom';
  3689.  
  3690. if (!isCollapsed && isAnyActiveTab() && isWideScreenWithTwoColumns() && !isTheater()) {
  3691. switchTabActivity(null);
  3692. timeline.setTimeout(unlock, 40);
  3693. } else {
  3694. unlock();
  3695. }
  3696.  
  3697. if (isCollapsed) {
  3698. ++chatroomAttrCollapseCount;
  3699. chatBlock.removeAttribute('tyt-iframe-loaded');
  3700. // console.log(922,1)
  3701. // buggy; this section might not be correctly executed.
  3702. // guess no collaspe change but still iframe will distory and reload.
  3703. let btn = document.querySelector('tyt-iframe-popup-btn')
  3704. if (btn) btn.remove();
  3705. } else {
  3706.  
  3707. if (pageFetchedDataVideoId && proceedingChatFrameVideoID === pageFetchedDataVideoId && chatroomAttrCollapseCount !== newVideoPageCACC) {
  3708. console.debug('[tyt] forceChatRenderOnChatExpanded')
  3709. const _chatBlock = chatBlock;
  3710. const tyid = ++dpeChatRefreshCounter;
  3711. setTimeout(() => {
  3712. if (pageFetchedDataVideoId && proceedingChatFrameVideoID === pageFetchedDataVideoId && chatroomAttrCollapseCount !== newVideoPageCACC) {
  3713. if (tyid !== dpeChatRefreshCounter) return;
  3714. dpeChatRefreshCounter++;
  3715. const chat = document.querySelector('ytd-live-chat-frame#chat');
  3716. const chatBlock = _chatBlock;
  3717. if (chat === chatBlock && !chatBlock.hasAttribute('collapsed')) {
  3718. dpeForceChatRenderOnChatExpanded(chatBlock);
  3719. }
  3720. }
  3721. }, 67);
  3722. }
  3723. }
  3724.  
  3725. fixLiveChatToggleButtonDispatchEvent();
  3726.  
  3727.  
  3728.  
  3729. })
  3730.  
  3731.  
  3732. },
  3733.  
  3734. mtf_attrEngagementPanel: ( /** @type {MutationRecord[]} */ mutations, /** @type {MutationObserver} */ observer) => {
  3735. //attr mutation checker - {ytd-engagement-panel-section-list-renderer} \mutiple
  3736. //::attr ~ visibility
  3737.  
  3738. const cssElm = es.ytdFlexy;
  3739. if (!scriptEnable || !cssElm) return;
  3740. let found = null
  3741. if (mutations === 9) {
  3742. found = observer
  3743. } else {
  3744. if (document.querySelector('ytd-watch-flexy[flexy][tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]:not([hidden])')) {
  3745. // do nothing
  3746. } else {
  3747. mtoVisibility_EngagementPanel.clear(true)
  3748. storeLastPanel = null;
  3749. wAttr(cssElm, 'tyt-ep-visible', false);
  3750. }
  3751. return
  3752. }
  3753. let nextValue = engagement_panels_().value;
  3754. let previousValue = +cssElm.getAttribute('tyt-ep-visible') || 0;
  3755. if (nextValue === 0 || previousValue === nextValue) return
  3756. cssElm.setAttribute('tyt-ep-visible', nextValue);
  3757. lstTab.lastPanel = `#engagement-panel-${nextValue}`;
  3758. storeLastPanel = mWeakRef(found)
  3759. let tabsDeferredSess = pageSession.session();
  3760. if (!scriptEnable && tabsDeferred.resolved) { }
  3761. else tabsDeferred.debounce(() => {
  3762. if (!tabsDeferredSess.isValid) return;
  3763. tabsDeferredSess = null;
  3764. if (es.storeLastPanel !== found) return
  3765. layoutStatusMutex.lockWith(unlock => {
  3766. if (es.storeLastPanel === found && whenEngagemenetPanelVisible()) {
  3767. timeline.setTimeout(unlock, 40);
  3768. } else {
  3769. unlock();
  3770. }
  3771. })
  3772. })
  3773. }
  3774.  
  3775. }
  3776.  
  3777.  
  3778. function variableResets() {
  3779.  
  3780. // reset variables when it is confirmed a new page is loaded
  3781.  
  3782. lstTab =
  3783. {
  3784. lastTab: null, //tab-xxx
  3785. lastPanel: null,
  3786. last: null
  3787. };
  3788.  
  3789. scriptEnable = false;
  3790. ytdFlexy = null;
  3791. wls.layoutStatus = 0;
  3792.  
  3793. mtoVisibility_Playlist.clear(true)
  3794. mtoVisibility_Comments.clear(true)
  3795.  
  3796. mtoVisibility_Chatroom.clear(true)
  3797. mtoFlexyAttr.clear(true)
  3798.  
  3799.  
  3800. for (const elem of document.querySelectorAll('ytd-expander[tabview-expander-checked]')) {
  3801. elem.removeAttribute('tabview-expander-checked');
  3802. }
  3803.  
  3804. mtf_chatBlockQ = null;
  3805.  
  3806. }
  3807.  
  3808.  
  3809. function getWord(tag) {
  3810. return langWords[pageLang][tag] || langWords['en'][tag] || '';
  3811. }
  3812.  
  3813.  
  3814. function getTabsHTML() {
  3815.  
  3816. const sTabBtnVideos = `${svgElm(16, 16, 90, 90, svgVideos)}<span>${getWord('videos')}</span>`;
  3817. const sTabBtnInfo = `${svgElm(16, 16, 60, 60, svgInfo)}<span>${getWord('info')}</span>`;
  3818. const sTabBtnPlayList = `${svgElm(16, 16, 20, 20, svgPlayList)}<span>${getWord('playlist')}</span>`;
  3819.  
  3820. let str1 = `
  3821. <paper-ripple class="style-scope yt-icon-button">
  3822. <div id="background" class="style-scope paper-ripple" style="opacity:0;"></div>
  3823. <div id="waves" class="style-scope paper-ripple"></div>
  3824. </paper-ripple>
  3825. `;
  3826.  
  3827. let str_fbtns = `
  3828. <div class="font-size-right">
  3829. <div class="font-size-btn font-size-plus" tyt-di="8rdLQ">
  3830. <svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet"
  3831. stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">
  3832. <path d="M12 25H38M25 12V38"/>
  3833. </svg>
  3834. </div><div class="font-size-btn font-size-minus" tyt-di="8rdLQ">
  3835. <svg width="12" height="12" viewbox="0 0 50 50" preserveAspectRatio="xMidYMid meet"
  3836. stroke="currentColor" stroke-width="6" stroke-linecap="round" vector-effect="non-scaling-size">
  3837. <path d="M12 25h26"/>
  3838. </svg>
  3839. </div>
  3840. </div>
  3841. `.replace(/[\r\n]+/g, '');
  3842.  
  3843. const str_tabs = [
  3844. `<a id="tab-btn1" tyt-di="q9Kjc" tyt-tab-content="#tab-info" class="tab-btn${(hiddenTabsByUserCSS & 1) === 1 ? ' tab-btn-hidden' : ''}">${sTabBtnInfo}${str1}${str_fbtns}</a>`,
  3845. `<a id="tab-btn3" tyt-di="q9Kjc" tyt-tab-content="#tab-comments" class="tab-btn${(hiddenTabsByUserCSS & 2) === 2 ? ' tab-btn-hidden' : ''}">${svgElm(16, 16, 120, 120, svgComments)}<span id="tyt-cm-count"></span>${str1}${str_fbtns}</a>`,
  3846. `<a id="tab-btn4" tyt-di="q9Kjc" tyt-tab-content="#tab-videos" class="tab-btn${(hiddenTabsByUserCSS & 4) === 4 ? ' tab-btn-hidden' : ''}">${sTabBtnVideos}${str1}${str_fbtns}</a>`,
  3847. `<a id="tab-btn5" tyt-di="q9Kjc" tyt-tab-content="#tab-list" class="tab-btn tab-btn-hidden">${sTabBtnPlayList}${str1}${str_fbtns}</a>`
  3848. ].join('');
  3849.  
  3850. let addHTML = `
  3851. <div id="right-tabs">
  3852. <tabview-view-pos-thead></tabview-view-pos-thead>
  3853. <header>
  3854. <div id="material-tabs">
  3855. ${str_tabs}
  3856. </div>
  3857. </header>
  3858. <div class="tab-content">
  3859. <div id="tab-info" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>
  3860. <div id="tab-comments" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>
  3861. <div id="tab-videos" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>
  3862. <div id="tab-list" class="tab-content-cld tab-content-hidden" tyt-hidden userscript-scrollbar-render></div>
  3863. </div>
  3864. </div>
  3865. `;
  3866.  
  3867. return addHTML;
  3868.  
  3869. }
  3870.  
  3871.  
  3872. function getLang() {
  3873.  
  3874. let lang = 'en';
  3875. let htmlLang = ((document || 0).documentElement || 0).lang || '';
  3876. switch (htmlLang) {
  3877. case 'en':
  3878. case 'en-GB':
  3879. lang = 'en';
  3880. break;
  3881. case 'de':
  3882. case 'de-DE':
  3883. lang = 'du';
  3884. break;
  3885. case 'fr':
  3886. case 'fr-CA':
  3887. case 'fr-FR':
  3888. lang = 'fr';
  3889. break;
  3890. case 'zh-Hant':
  3891. case 'zh-Hant-HK':
  3892. case 'zh-Hant-TW':
  3893. lang = 'tw';
  3894. break;
  3895. case 'zh-Hans':
  3896. case 'zh-Hans-CN':
  3897. lang = 'cn';
  3898. break;
  3899. case 'ja':
  3900. case 'ja-JP':
  3901. lang = 'jp';
  3902. break;
  3903. case 'ko':
  3904. case 'ko-KR':
  3905. lang = 'kr';
  3906. break;
  3907. case 'ru':
  3908. case 'ru-RU':
  3909. lang = 'ru';
  3910. break;
  3911. default:
  3912. lang = 'en';
  3913. }
  3914.  
  3915. return lang;
  3916.  
  3917. }
  3918.  
  3919. function getLangForPage() {
  3920.  
  3921. let lang = getLang();
  3922.  
  3923. if (langWords[lang]) pageLang = lang; else pageLang = 'en';
  3924.  
  3925. }
  3926.  
  3927. // function checkEvtTarget(evt, nodeNames) {
  3928. // return nodeNames.includes((((evt || 0).target || 0).nodeName || 0));
  3929. // }
  3930.  
  3931. function pageCheck() {
  3932. // yt-player-updated
  3933. // yt-page-data-updated
  3934. // yt-watch-comments-ready - omitted
  3935. // [is-two-columns_] attr changed => layout changed
  3936.  
  3937. /** @type {HTMLElement | null} */
  3938. const ytdFlexyElm = es.ytdFlexy;
  3939. if (!scriptEnable || !ytdFlexyElm) return;
  3940.  
  3941. let comments = querySelectorFromAnchor.call(ytdFlexyElm, '#primary.ytd-watch-flexy ytd-watch-metadata ~ ytd-comments#comments');
  3942. if (comments) {
  3943. let tabComments = document.querySelector('#tab-comments');
  3944. if (tabComments) {
  3945. elementAppend.call(tabComments, comments);
  3946. }
  3947. }
  3948.  
  3949. mtf_append_playlist(null); // playlist relocated after layout changed
  3950.  
  3951. fixTabs();
  3952.  
  3953. mtf_autocomplete_search();
  3954.  
  3955. }
  3956.  
  3957. function globalHook(eventType, func) {
  3958. if (!func) return;
  3959.  
  3960. const count = (globalHook_hashs[eventType] || 0) + 1;
  3961.  
  3962. globalHook_hashs[eventType] = count;
  3963.  
  3964. const s = globalHook_symbols[count - 1] || (globalHook_symbols[count - 1] = Symbol());
  3965.  
  3966. document.addEventListener(eventType, function (evt) {
  3967. if (evt[s]) return;
  3968. evt[s] = true;
  3969. Promise.resolve().then(() => {
  3970. func(evt);
  3971. })
  3972.  
  3973. }, capturePassive)
  3974.  
  3975. }
  3976.  
  3977. async function makeHeaderFloat() {
  3978. if (isMakeHeaderFloatCalled) return;
  3979. isMakeHeaderFloatCalled = true;
  3980. await Promise.resolve(0);
  3981.  
  3982.  
  3983. const [header, headerP, navElm] = await Promise.all([
  3984. Promise.resolve().then(() => document.querySelector("#right-tabs header")),
  3985.  
  3986. Promise.resolve().then(() => document.querySelector("#right-tabs tabview-view-pos-thead")),
  3987.  
  3988. Promise.resolve().then(() => document.querySelector('#masthead-container, #masthead'))
  3989.  
  3990. ]);
  3991.  
  3992.  
  3993. let ito_dt = 0;
  3994. let ito = new IntersectionObserver((entries) => {
  3995.  
  3996. let xyStatus = null;
  3997.  
  3998. //console.log(entries);
  3999.  
  4000. let xRect = null;
  4001. let rRect = null;
  4002.  
  4003. for (const entry of entries) {
  4004. if (!entry.boundingClientRect || !entry.rootBounds) continue; // disconnected from DOM tree
  4005. if (!entry.isIntersecting && entry.boundingClientRect.y <= entry.rootBounds.top && entry.boundingClientRect.y < entry.rootBounds.bottom) {
  4006. xyStatus = 2;
  4007. xRect = entry.boundingClientRect;
  4008. rRect = entry.rootBounds;
  4009. } else if (entry.isIntersecting && entry.boundingClientRect.y >= entry.rootBounds.top && entry.boundingClientRect.y < entry.rootBounds.bottom) {
  4010. xyStatus = 1;
  4011. xRect = entry.boundingClientRect;
  4012. rRect = entry.rootBounds;
  4013. }
  4014. }
  4015. let p = wls.layoutStatus;
  4016. //console.log(document.documentElement.clientWidth)
  4017. if (xyStatus !== null) {
  4018.  
  4019. if (xyStatus === 2 && isStickyHeaderEnabled === true) {
  4020.  
  4021. } else if (xyStatus === 1 && isStickyHeaderEnabled === false) {
  4022.  
  4023. } else {
  4024. singleColumnScrolling2(xyStatus, xRect.width, {
  4025. left: xRect.left,
  4026. right: rRect.width - xRect.right
  4027. });
  4028. }
  4029.  
  4030. }
  4031.  
  4032. let tdt = Date.now();
  4033. ito_dt = tdt;
  4034. setTimeout(() => {
  4035. if (ito_dt !== tdt) return;
  4036. if (p !== wls.layoutStatus) singleColumnScrolling();
  4037. }, 300)
  4038.  
  4039. },
  4040. {
  4041. rootMargin: `0px 0px 0px 0px`,
  4042. threshold: [0]
  4043. })
  4044.  
  4045. ito.observe(headerP)
  4046.  
  4047. }
  4048.  
  4049. function checkPlaylistForInitialization() {
  4050. // if the page url is with playlist; renderer event might not occur.
  4051.  
  4052. // playlist already added to dom; this is to set the visibility event and change hidden status
  4053.  
  4054. let m_playlist = document.querySelector(`#tab-list ytd-playlist-panel-renderer#playlist:not([o3r-${sa_playlist}])`)
  4055.  
  4056. // once per {ytd-playlist-panel-renderer#playlist} detection
  4057.  
  4058. // _console.log(3902, !!m_playlist)
  4059.  
  4060. const ytdFlexyElm = es.ytdFlexy;
  4061. if (!scriptEnable || !ytdFlexyElm) { }
  4062. else if (m_playlist) {
  4063.  
  4064. if (mtoVisibility_Playlist.bindElement(m_playlist)) {
  4065. mtoVisibility_Playlist.observer.check(9); // delay check required for browser bug - hidden changed not triggered
  4066. }
  4067. let playlist_wr = mWeakRef(m_playlist);
  4068. scriptletDeferred.debounce(() => {
  4069. let m_playlist = kRef(playlist_wr);
  4070. playlist_wr = null;
  4071. if (m_playlist) {
  4072. m_playlist.dispatchEvent(new CustomEvent("tabview-playlist-data-re-assign"));
  4073. }
  4074. m_playlist = null;
  4075. })
  4076. m_playlist = null;
  4077.  
  4078. }
  4079.  
  4080. FP.mtf_attrPlaylist();
  4081.  
  4082. Promise.resolve(0).then(() => {
  4083. // ['tab-btn', 'tab-btn', 'tab-btn active', 'tab-btn tab-btn-hidden']
  4084. // bug
  4085. const ytdFlexyElm = es.ytdFlexy;
  4086. if (!scriptEnable || !ytdFlexyElm) return;
  4087. if (!switchTabActivity_lastTab && (ytdFlexyElm.getAttribute('tyt-tab') + '').indexOf('#tab-') === 0 && location.pathname === '/watch') {
  4088. if (/[\?\&]list=[\w\-\_]+/.test(location.search)) {
  4089. if (setToActiveTab('#tab-list')) switchTabActivity_lastTab = '#tab-list';
  4090. } else if (/[\?\&]lc=[\w\-\_]+/.test(location.search)) {
  4091. if (setToActiveTab('#tab-comments')) switchTabActivity_lastTab = '#tab-comments';
  4092. }
  4093. }
  4094. })
  4095.  
  4096. }
  4097.  
  4098.  
  4099. const _pageBeingInit = function () {
  4100.  
  4101. pageSession.inc();
  4102. if (pageSession.sid > 9e9) pageSession.sid = 9;
  4103.  
  4104. fetchCounts = {
  4105. base: null,
  4106. new: null,
  4107. fetched: false,
  4108. count: null
  4109. }
  4110. pageFetchedDataVideoId = null;
  4111. chatroomDetails = null;
  4112. }
  4113.  
  4114. const pageBeingInit = function () {
  4115.  
  4116. if (_isPageFirstLoaded && location.pathname === '/watch') document.documentElement.setAttribute('tyt-lock', '')
  4117.  
  4118. // call regardless pageType
  4119. // run once on / before pageSeq2
  4120.  
  4121. let action = 0;
  4122. if (tabsDeferred.resolved) {
  4123. action = 1;
  4124. } else if (renderDeferred.resolved) {
  4125. // in case , rarely, tabsDeferred not yet resolved but animateLoadDeferred resolved
  4126. action = 2;
  4127. }
  4128.  
  4129. renderIdentifier++;
  4130. if (renderIdentifier > 1e9) renderIdentifier = 9;
  4131. renderDeferred.reset();
  4132.  
  4133. if (action === 1) {
  4134. comments_loader = 1;
  4135. tabsDeferred.reset();
  4136. if ((firstLoadStatus & 8) === 0) {
  4137. innerDOMCommentsCountLoader(false); //ensure the previous record is saved
  4138. // no need to cache to the rendering state
  4139. _pageBeingInit();
  4140. } else if ((firstLoadStatus & 2) === 2) {
  4141. firstLoadStatus -= 2;
  4142. script_inject_js1.inject();
  4143. }
  4144. // _console.log('pageBeingInit', firstLoadStatus)
  4145. }
  4146.  
  4147. if (pageRendered === 2) {
  4148. pageRendered = 0;
  4149. let elmPL = document.querySelector('tabview-view-ploader');
  4150. if (elmPL) elmPL.remove();
  4151. pageRendered = 0;
  4152. }
  4153.  
  4154. if (!scriptletDeferred.resolved) {
  4155. // just in case, should not happen this.
  4156. // this is to clear the pending queue if scriptlet is not ready.
  4157. scriptletDeferred.reset();
  4158. }
  4159.  
  4160. };
  4161.  
  4162. const advanceFetch = async function () {
  4163. if (pageType === 'watch' && !fetchCounts.new && !fetchCounts.fetched) {
  4164. renderDeferred.resolved && innerDOMCommentsCountLoader(true);
  4165. if (renderDeferred.resolved && !fetchCounts.new) {
  4166. window.dispatchEvent(new Event("scroll"));
  4167. }
  4168. }
  4169. };
  4170.  
  4171. function getFinalComments() {
  4172.  
  4173. if ((comments_loader & 3) === 3) { } else return;
  4174. comments_loader = 0;
  4175.  
  4176. let ei = 0;
  4177.  
  4178. function execute() {
  4179. //sync -> animateLoadDeferred.resolved always true
  4180.  
  4181. if (!renderDeferred.resolved) return;
  4182.  
  4183. // _console.log(2323)
  4184.  
  4185. if (Q.comments_section_loaded !== 0) return;
  4186. if (fetchCounts.fetched) return;
  4187.  
  4188.  
  4189. let ret = innerDOMCommentsCountLoader(true);
  4190.  
  4191. if (fetchCounts.new && !fetchCounts.fetched) {
  4192. if (fetchCounts.new.f()) {
  4193. fetchCounts.fetched = true;
  4194. fetchCommentsFinished();
  4195. // _console.log(9972, 'fetched = true')
  4196. }
  4197. return;
  4198. }
  4199.  
  4200.  
  4201. ei++;
  4202.  
  4203. if (fetchCounts.base && !fetchCounts.new && !fetchCounts.fetched && fetchCounts.count === 1) {
  4204.  
  4205.  
  4206. let elm = kRef(fetchCounts.base.elm);
  4207. let txt = elm ? getCountHText(elm) : null;
  4208. let condi1 = ei > 7;
  4209. let condi2 = txt === m_last_count;
  4210. if (condi1 || condi2) {
  4211.  
  4212. if (fetchCounts.base.f()) {
  4213. fetchCounts.fetched = true;
  4214. fetchCommentsFinished();
  4215. // _console.log(9972, 'fetched = true')
  4216. }
  4217.  
  4218. }
  4219.  
  4220. }
  4221.  
  4222. if (!fetchCounts.fetched) {
  4223. if (ei > 7) {
  4224. let elm = ret.length === 1 ? kRef(ret[0].elm) : null;
  4225. let txt = elm ? getCountHText(elm) : null;
  4226. if (elm && txt !== m_last_count) {
  4227. fetchCounts.base = null;
  4228. fetchCounts.new = ret[0];
  4229. fetchCounts.new.f();
  4230. fetchCounts.fetched = true;
  4231. // _console.log(9979, 'fetched = true')
  4232. fetchCommentsFinished();
  4233. }
  4234. return;
  4235. }
  4236. return true;
  4237. }
  4238.  
  4239. }
  4240.  
  4241.  
  4242. async function alCheckFn(ks) {
  4243.  
  4244. let alCheckCount = 9;
  4245. let alCheckInterval = 420;
  4246.  
  4247. do {
  4248.  
  4249. if (renderIdentifier !== ks) break;
  4250. if (alCheckCount === 0) break;
  4251. if (execute() !== true) break;
  4252. --alCheckCount;
  4253.  
  4254. await new Promise(r => setTimeout(r, alCheckInterval));
  4255.  
  4256. } while (true)
  4257.  
  4258. }
  4259. let ks = renderIdentifier;
  4260. renderDeferred.debounce(() => {
  4261. if (ks !== renderIdentifier) return
  4262. alCheckFn(ks);
  4263.  
  4264. });
  4265.  
  4266.  
  4267. }
  4268.  
  4269. const { removeDuplicateInfoFn, setHiddenStateForDesc, checkDuplicatedInfo_then } = (() => {
  4270.  
  4271. let g_check_detail_A = 0;
  4272. let checkDuplicateRes = null;
  4273. function setHiddenStateForDesc() {
  4274. let ytdFlexyElm = es.ytdFlexy
  4275. if (!ytdFlexyElm) return
  4276. let hiddenBool = !document.fullscreenElement ? ytdFlexyElm.classList.contains('tabview-info-duplicated') : false
  4277. let elm
  4278. elm = document.querySelector('.tabview-info-duplicated-checked[flexy] ytd-text-inline-expander#description-inline-expander #plain-snippet-text')
  4279. if (elm) {
  4280. wAttr(elm, 'hidden', hiddenBool)
  4281. }
  4282. elm = document.querySelector('.tabview-info-duplicated-checked[flexy] ytd-text-inline-expander#description-inline-expander #formatted-snippet-text')
  4283. if (elm) {
  4284. wAttr(elm, 'hidden', hiddenBool)
  4285. }
  4286. }
  4287. function checkDuplicatedInfo_then(isCheck, checkDuplicateRes) {
  4288.  
  4289. const ytdFlexyElm = es.ytdFlexy;
  4290. if (!ytdFlexyElm) return; //unlikely
  4291.  
  4292. let cssbool_c1 = false, cssbool_c2 = false;
  4293. if (isCheck === 5) {
  4294.  
  4295. if (ytdFlexyElm.matches('.tabview-info-duplicated[flexy]')) {
  4296. cssbool_c1 = !!querySelectorFromAnchor.call(ytdFlexyElm, '#description.style-scope.ytd-watch-metadata > #description-inner:only-child');
  4297. cssbool_c2 = !!querySelectorFromAnchor.call(ytdFlexyElm, '#tab-info ytd-expander #description.ytd-video-secondary-info-renderer');
  4298. }
  4299.  
  4300. if (typeof checkDuplicateRes === 'boolean') {
  4301. setHiddenStateForDesc();
  4302. }
  4303. }
  4304.  
  4305. ytdFlexyElm.setAttribute('tyt-has', `${cssbool_c1 ? 'A' : 'a'}${cssbool_c2 ? 'B' : 'b'}`);
  4306.  
  4307. }
  4308.  
  4309. async function checkDuplicatedInfoAug2023() {
  4310. const firstElementSelector = "ytd-text-inline-expander#description-inline-expander";
  4311. const secondElementSelector = "#tab-info ytd-expander #description";
  4312. const firstElement = document.querySelector(firstElementSelector);
  4313. const secondElement = document.querySelector(secondElementSelector);
  4314. if (!firstElement || !secondElement) return false;
  4315. if (firstElement.hasAttribute('hidden') || secondElement.hasAttribute('hidden')) return false;
  4316. const asyncGetContent = async (n) => {
  4317. return n.textContent;
  4318. }
  4319. const getTextContentArr = async (element) => {
  4320. let contentArray = [];
  4321. for (let currentNode = nodeFirstChild(element); currentNode; currentNode = nodeNextSibling(currentNode)) {
  4322. if (currentNode.nodeType === Node.ELEMENT_NODE) {
  4323. if (currentNode.nodeName === "STYLE") {
  4324. // <style is-scoped>
  4325. continue;
  4326. }
  4327. if (currentNode.getAttribute('role') === 'button') { // .role is not working in Firefox
  4328. // tp-yt-paper-button#expand-sizer
  4329. // currentNode.matches('#collapse[role="button"]:not([hidden])')
  4330. continue;
  4331. }
  4332. if (currentNode.hasAttribute("hidden")) {
  4333. continue;
  4334. }
  4335. if (currentNode.id === "snippet") {
  4336. let allHidden = true;
  4337. for (let child = nodeFirstChild(currentNode); child; child = nodeNextSibling(child)) {
  4338. if (child.nodeName === "STYLE") {
  4339. // <style is-scoped>
  4340. continue;
  4341. }
  4342. if (child.hasAttribute("hidden")) {
  4343. continue;
  4344. }
  4345. let trimmedTextContent = await asyncGetContent(child);
  4346. trimmedTextContent = trimmedTextContent.trim();
  4347. if (trimmedTextContent.length === 0) continue;
  4348. allHidden = false;
  4349. break;
  4350. }
  4351. if (allHidden) {
  4352. continue;
  4353. }
  4354. }
  4355. } else if (currentNode.nodeType === Node.TEXT_NODE) {
  4356. } else {
  4357. continue;
  4358. }
  4359. let trimmedTextContent = await asyncGetContent(currentNode);
  4360. trimmedTextContent = trimmedTextContent.trim();
  4361. if (trimmedTextContent.length > 0) {
  4362. trimmedTextContent = trimmedTextContent.replace(/\n[\n\x20]+\n/g, '\n\n');
  4363. // "白州大根\n \n チャンネル登録者数 698人\n \n \n\n\n 動画\n \n\n\n \n \n概要"
  4364. // "白州大根\n \n チャンネル登録者数 698人\n \n \n\n\n 動画\n \n \n概要"
  4365. contentArray.push(trimmedTextContent);
  4366. }
  4367. }
  4368. return contentArray;
  4369. };
  4370. const [firstElementTextArr, secondElementTextArr] = await Promise.all([getTextContentArr(firstElement), getTextContentArr(secondElement)]);
  4371. function isSubset(arr1, arr2) {
  4372. const set = new Set(arr2);
  4373. const r = arr1.every(item => set.has(item));
  4374. set.clear();
  4375. return r;
  4376. }
  4377. return isSubset(firstElementTextArr, secondElementTextArr);
  4378. }
  4379.  
  4380. async function checkDuplicatedInfo() {
  4381.  
  4382. const ytdFlexyElm = es.ytdFlexy;
  4383. if (!ytdFlexyElm) return; //unlikely
  4384.  
  4385. const targetDuplicatedInfoPanel = document.querySelector('ytd-text-inline-expander#description-inline-expander:not([hidden])');
  4386. if (targetDuplicatedInfoPanel && !targetDuplicatedInfoPanel.closest('[hidden]')) { } else {
  4387. return; // the layout is not required to have this checking.
  4388. }
  4389.  
  4390. let t = Date.now();
  4391. g_check_detail_A = t;
  4392.  
  4393. ytdFlexyElm.classList.toggle('tabview-info-duplicated', true) // hide first;
  4394. let infoDuplicated = false;
  4395.  
  4396. try {
  4397. await new Promise(resolve => setTimeout(resolve, 1)); // mcrcr might be not yet initalized
  4398.  
  4399.  
  4400. if (g_check_detail_A !== t) return;
  4401.  
  4402.  
  4403. let clicked = false;
  4404. await Promise.all([...document.querySelectorAll('ytd-text-inline-expander#description-inline-expander #expand[role="button"]:not([hidden])')].map(button => {
  4405. return Promise.resolve().then(() => {
  4406. button.click();
  4407. clicked = true;
  4408. });
  4409. }));
  4410.  
  4411. await Promise.resolve(0);
  4412.  
  4413. infoDuplicated = await checkDuplicatedInfoAug2023();
  4414.  
  4415. if (infoDuplicated === false && clicked) {
  4416.  
  4417. await Promise.all([...document.querySelectorAll('ytd-text-inline-expander#description-inline-expander #collapse[role="button"]:not([hidden])')].map(button => {
  4418. return Promise.resolve().then(() => {
  4419. button.click();
  4420. });
  4421. }));
  4422.  
  4423. }
  4424.  
  4425. } catch (e) {
  4426.  
  4427. ytdFlexyElm.classList.toggle('tabview-info-duplicated', false) // error => unhide
  4428. }
  4429.  
  4430. console.debug('[tyt] Have any details with duplicated information been found?', (infoDuplicated ? 'Yes' : 'No'));
  4431. if (g_check_detail_A !== t) return;
  4432.  
  4433. //ytdFlexyElm.classList.toggle('tabview-info-duplicated', infoDuplicated)
  4434. checkDuplicateRes = infoDuplicated;
  4435.  
  4436. return 5; // other than 5, duplicated check = false
  4437.  
  4438. };
  4439.  
  4440. const removeDuplicateInfoFn = () => {
  4441.  
  4442. checkDuplicateRes = null;
  4443. async function alCheckFn(ks) {
  4444.  
  4445. let alCheckCount = 4;
  4446. let alCheckInterval = 270;
  4447.  
  4448. checkDuplicateRes = null;
  4449. let descExpandState = null;
  4450. let descMetaExpander = closestDOMX(document.querySelector('ytd-text-inline-expander#description-inline-expander'), 'ytd-watch-metadata');
  4451. let descToggleBtn = null;
  4452. let descMetaLines = null;
  4453. if (descMetaExpander) {
  4454.  
  4455. // ytd-text-inline-expander#description-inline-expander.style-scope.ytd-watch-metadata
  4456. descMetaLines = querySelectorFromAnchor.call(descMetaExpander, 'ytd-text-inline-expander#description-inline-expander.style-scope.ytd-watch-metadata')
  4457. if (descMetaLines) {
  4458.  
  4459. descToggleBtn = querySelectorFromAnchor.call(descMetaLines, '#collapse[role="button"]:not([hidden]), #expand[role="button"]:not([hidden])');
  4460. if (descToggleBtn) {
  4461. if (descMetaExpander.hasAttribute('description-collapsed') && descToggleBtn.id === 'expand') {
  4462. descExpandState = false;
  4463. } else if (!descMetaExpander.hasAttribute('description-collapsed') && descToggleBtn.id === 'collapse') {
  4464. descExpandState = true;
  4465. }
  4466. }
  4467. }
  4468.  
  4469. }
  4470. if (descMetaExpander) {
  4471. descMetaExpander.classList.add('tyt-tmp-hide-metainfo');
  4472. }
  4473.  
  4474. try {
  4475.  
  4476.  
  4477.  
  4478. do {
  4479.  
  4480. if (renderIdentifier !== ks) break;
  4481. if (alCheckCount === 0) break;
  4482. if (checkDuplicateRes === true) break;
  4483. checkDuplicateRes = null;
  4484.  
  4485. let res = await checkDuplicatedInfo(); //async
  4486. if (res === 5) {
  4487.  
  4488. const ytdFlexyElm = es.ytdFlexy;
  4489. if (ytdFlexyElm) {
  4490. if (checkDuplicateRes === true || (checkDuplicateRes === false && alCheckCount === 1)) {
  4491. ytdFlexyElm.classList.toggle('tabview-info-duplicated', checkDuplicateRes)
  4492. ytdFlexyElm.classList.toggle('tabview-info-duplicated-checked', true)
  4493. checkDuplicatedInfo_then(res, checkDuplicateRes);
  4494. scriptletDeferred.debounce(() => {
  4495. document.dispatchEvent(new CustomEvent('tabview-fix-info-box-tooltip'));
  4496. });
  4497. }
  4498. }
  4499.  
  4500. }
  4501. --alCheckCount;
  4502.  
  4503. if (checkDuplicateRes === true) break;
  4504.  
  4505. await new Promise(r => setTimeout(r, alCheckInterval));
  4506.  
  4507. } while (true)
  4508.  
  4509. await Promise.resolve(0)
  4510.  
  4511. descToggleBtn = descMetaLines ? querySelectorFromAnchor.call(descMetaLines, '#collapse[role="button"]:not([hidden]), #expand[role="button"]:not([hidden])') : null;
  4512. if (descToggleBtn) {
  4513.  
  4514. let isCollapsed = descMetaExpander.hasAttribute('description-collapsed')
  4515. let id = descToggleBtn.id
  4516. let b1 = descExpandState === true && isCollapsed && id === 'expand';
  4517. let b2 = descExpandState === false && !isCollapsed && id === 'collapse';
  4518.  
  4519. if (b1 || b2) {
  4520. descToggleBtn.click();
  4521. }
  4522.  
  4523. }
  4524.  
  4525.  
  4526.  
  4527.  
  4528. } catch (e) {
  4529.  
  4530. console.warn(e)
  4531.  
  4532. }
  4533.  
  4534. if (descMetaExpander) {
  4535. descMetaExpander.classList.remove('tyt-tmp-hide-metainfo');
  4536.  
  4537. await Promise.resolve(0)
  4538.  
  4539. let detailsIntersectioner = querySelectorFromAnchor.call(descMetaExpander, '#info-container.style-scope.ytd-watch-metadata');
  4540. if (detailsIntersectioner) {
  4541. Promise.resolve(detailsIntersectioner).then(detailsIntersectioner => {
  4542. let dom = detailsIntersectioner;
  4543. if (dom) mtoObservationDetails.bindElement(dom);
  4544. })
  4545. }
  4546.  
  4547. }
  4548.  
  4549.  
  4550.  
  4551. }
  4552. let ks = renderIdentifier;
  4553. renderDeferred.debounce(() => {
  4554. if (ks !== renderIdentifier) return
  4555. alCheckFn(ks);
  4556.  
  4557. });
  4558. }
  4559.  
  4560. return { removeDuplicateInfoFn, setHiddenStateForDesc, checkDuplicatedInfo_then };
  4561.  
  4562. })();
  4563.  
  4564.  
  4565. // setupChatFrameDOM (v1) - removed in 2023.07.06 since it is buggy for page changing. subject to further review
  4566. function setupChatFrameDOM(node) {
  4567. // this function calls 3 times per each new video page
  4568.  
  4569. // 'tyt-chat' is initialized in setupChatFrameDisplayState1()
  4570.  
  4571. if (!chatroomDetails) return;
  4572. let liveChatFrame = node || document.querySelector('ytd-live-chat-frame#chat')
  4573. if (liveChatFrame) {
  4574.  
  4575. // every per [new] {ytd-live-chat-frame#chat} detection - reset after mini-playview
  4576.  
  4577. let ytdFlexyElm = es.ytdFlexy;
  4578. if (scriptEnable && ytdFlexyElm) {
  4579. if (mtoVisibility_Chatroom.bindElement(liveChatFrame)) {
  4580. mtoVisibility_Chatroom.observer.check(9)
  4581. }
  4582. }
  4583.  
  4584. liveChatFrame = null;
  4585. ytdFlexyElm = null;
  4586.  
  4587. setToggleBtnTxt(); // immediate update when page changed
  4588.  
  4589. if (node !== null) {
  4590. // button might not yet be rendered
  4591. getRAFPromise().then(setToggleBtnTxt); // bool = true must be front page
  4592. } else {
  4593.  
  4594. // this is due to page change
  4595. let incorrectChat = document.querySelector('ytd-watch-flexy[is-two-columns_][theater] ytd-live-chat-frame#chat:not([collapsed])')
  4596. if (incorrectChat) {
  4597. incorrectChat.setAttribute('collapsed', '')
  4598. }
  4599.  
  4600. }
  4601.  
  4602. }
  4603.  
  4604. }
  4605.  
  4606. function whenEngagemenetPanelVisible() {
  4607.  
  4608. const layoutStatus = wls.layoutStatus;
  4609. if ((layoutStatus & (LAYOUT_TWO_COLUMNS | LAYOUT_THEATER)) === LAYOUT_TWO_COLUMNS) {
  4610.  
  4611. if (layoutStatus & LAYOUT_TAB_EXPANDED) {
  4612. switchTabActivity(null);
  4613. return true;
  4614. } else if (layoutStatus & LAYOUT_CHATROOM_EXPANDED) {
  4615. ytBtnCollapseChat();
  4616. return true;
  4617. }
  4618.  
  4619. }
  4620.  
  4621. return false;
  4622.  
  4623. }
  4624.  
  4625.  
  4626. function removeFocusOnLeave(evt) {
  4627. let node = (evt || 0).target || 0
  4628. let activeElement = document.activeElement || 0
  4629. if (node.nodeType === 1 && activeElement.nodeType === 1) {
  4630. Promise.resolve().then(() => {
  4631. if (node.contains(activeElement)) {
  4632. activeElement.blur();
  4633. }
  4634. })
  4635. }
  4636. }
  4637.  
  4638. async function setupVideo(node) {
  4639. // this can be fired even in background without tabs rendered
  4640. const attrKey = 'gM7Cp'
  4641. let video = querySelectorFromAnchor.call(node, `#movie_player video[src]:not([${attrKey}])`);
  4642. if (video) {
  4643. video.setAttribute(attrKey, '')
  4644.  
  4645. video.addEventListener('timeupdate', (evt) => {
  4646. energizedByVideoTimeUpdate();
  4647. }, bubblePassive);
  4648.  
  4649. video.addEventListener('ended', (evt) => {
  4650. // scrollIntoView => auto start next video
  4651. // otherwise it cannot auto paly next
  4652. if (pageType === 'watch') {
  4653. let elm = evt.target;
  4654. Promise.resolve(elm).then((elm) => {
  4655. if (pageType === 'watch') {
  4656. let scrollElm = closestDOM.call(elm, '#player') || closestDOM.call(elm, '#ytd-player') || elm;
  4657. // background applicable
  4658. scrollElm.scrollIntoView(false);
  4659. scrollElm = null
  4660. }
  4661. elm = null
  4662. });
  4663. }
  4664.  
  4665. }, bubblePassive)
  4666.  
  4667. }
  4668. }
  4669.  
  4670.  
  4671. globalHook('yt-player-updated', (evt) => {
  4672.  
  4673. const node = ((evt || 0).target) || 0
  4674.  
  4675. if (node.nodeType !== 1) return;
  4676.  
  4677. const nodeName = node.nodeName.toUpperCase();
  4678.  
  4679. // _console.log(evt.target.nodeName, 904, evt.type);
  4680.  
  4681. if (nodeName !== 'YTD-PLAYER') return
  4682.  
  4683. setupVideo(node)
  4684.  
  4685.  
  4686. let tabsDeferredSess = pageSession.session();
  4687. if (!scriptEnable && tabsDeferred.resolved) { }
  4688. else tabsDeferred.debounce(() => {
  4689.  
  4690. if (!tabsDeferredSess.isValid) return;
  4691. tabsDeferredSess = null;
  4692.  
  4693.  
  4694. if (!scriptEnable) return
  4695.  
  4696. checkAndMakeNewCommentFetch();
  4697.  
  4698. // _console.log(2178, 4)
  4699. pageCheck();
  4700.  
  4701. domInit_comments();
  4702. setupChatFrameDOM(null);
  4703.  
  4704.  
  4705. });
  4706.  
  4707.  
  4708. });
  4709.  
  4710. function tabviewControllerFn(controllerId, val) {
  4711. val = +val;
  4712. if (val > -1 && val >= 0) {
  4713. if (controllerId === 'tabviewTabsHideController') {
  4714. hiddenTabsByUserCSS = val;
  4715.  
  4716. let btn;
  4717. btn = document.querySelector('[tyt-tab-content="#tab-info"]')
  4718. if (btn) btn.classList.toggle('tab-btn-hidden', ((hiddenTabsByUserCSS & 1) === 1));
  4719.  
  4720. btn = document.querySelector('[tyt-tab-content="#tab-comments"]')
  4721. if (btn) {
  4722. if ((hiddenTabsByUserCSS & 2) === 2) {
  4723. btn.classList.toggle('tab-btn-hidden', true);
  4724. } else {
  4725. btn.classList.toggle('tab-btn-hidden', isCommentsTabBtnHidden);
  4726. }
  4727. }
  4728. btn = document.querySelector('[tyt-tab-content="#tab-videos"]');
  4729. if (btn) btn.classList.toggle('tab-btn-hidden', ((hiddenTabsByUserCSS & 4) === 4));
  4730.  
  4731. let activeHiddenBtn = document.querySelector('[tyt-tab-content^="#"].active.tab-btn-hidden');
  4732. if (activeHiddenBtn) {
  4733. setToActiveTab();
  4734. }
  4735.  
  4736. } else if (controllerId === 'tabviewDefaultTabController') {
  4737. defaultTabByUserCSS = val;
  4738. if (setupDefaultTabBtnSetting) setupDefaultTabBtnSetting();
  4739. }
  4740. }
  4741. }
  4742.  
  4743.  
  4744. /** @type {Map<string, Function>} */
  4745. let handleDOMAppearFN = new Map();
  4746. function handleDOMAppear( /** @type {string} */ fn, /** @type { listener: (this: Document, ev: AnimationEvent ) => any } */ func) {
  4747. if (handleDOMAppearFN.size === 0) {
  4748. document.addEventListener('animationstart', (evt) => {
  4749. const animationName = evt.animationName;
  4750. if (!animationName) return;
  4751. let idx = -1;
  4752. let func = handleDOMAppearFN.get(animationName);
  4753. if (func) func(evt);
  4754. else {
  4755. let idx = animationName.indexOf('Controller-');
  4756. if (idx > 0) {
  4757. let j = idx + 'Controller'.length;
  4758. tabviewControllerFn(animationName.substring(0, j), animationName.substring(j + 1));
  4759. }
  4760. }
  4761. }, capturePassive)
  4762. } else {
  4763. if (handleDOMAppearFN.has(fn)) return;
  4764. }
  4765. handleDOMAppearFN.set(fn, func);
  4766. }
  4767.  
  4768. function ytMicroEventsInit() {
  4769.  
  4770. // _console.log(902);
  4771.  
  4772. handleDOMAppear('videosDOMAppended', function (evt) {
  4773. videosDeferred.resolve();
  4774. })
  4775.  
  4776. handleDOMAppear('liveChatFrameDOMAppended', (evt) => {
  4777.  
  4778. let node = evt.target;
  4779. if (!node) return;
  4780.  
  4781. let tabsDeferredSess = pageSession.session();
  4782. if (!scriptEnable && tabsDeferred.resolved) { }
  4783. else tabsDeferred.debounce(() => {
  4784.  
  4785. // P.S. avoid immediately dom change
  4786. // time delay to avoid attribute set after dom appended.
  4787.  
  4788. if (!tabsDeferredSess.isValid) return;
  4789. tabsDeferredSess = null;
  4790.  
  4791. setupChatFrameDOM(node); // front page
  4792. node = null;
  4793.  
  4794. })
  4795.  
  4796. });
  4797.  
  4798. handleDOMAppear('pageLoaderAnimation', (evt) => {
  4799. pageRendered = 2;
  4800. renderDeferred.resolve();
  4801. console
  4802. console.debug('[tyt] pageRendered')
  4803.  
  4804. scriptletDeferred.debounce(() => {
  4805. document.dispatchEvent(new CustomEvent('tabview-page-rendered'))
  4806. })
  4807.  
  4808. });
  4809.  
  4810.  
  4811. handleDOMAppear('chatFrameToggleBtnAppended1', (evt) => {
  4812.  
  4813. // _console.log(5099, 'chatFrameToggleBtnAppended', evt)
  4814.  
  4815. Promise.resolve(0).then(() => { // avoid immediately dom change
  4816.  
  4817. let tabsDeferredSess = pageSession.session();
  4818. if (!scriptEnable && tabsDeferred.resolved) { }
  4819. else tabsDeferred.debounce(() => {
  4820.  
  4821. if (!tabsDeferredSess.isValid) return;
  4822. tabsDeferredSess = null;
  4823.  
  4824. mtf_liveChatBtnF(evt.target);
  4825.  
  4826. })
  4827.  
  4828. })
  4829.  
  4830. });
  4831.  
  4832.  
  4833. // DEBUG_LOG && handleDOMAppear('chatFrameToggleBtnAppended2', (evt) => {
  4834.  
  4835. // _console.log(5099, 'chatFrameToggleBtnAppended', evt)
  4836.  
  4837.  
  4838. // });
  4839.  
  4840.  
  4841. handleDOMAppear('epDOMAppended', async (evt) => {
  4842. try {
  4843. let node = evt.target;
  4844.  
  4845. let eps = document.querySelectorAll('ytd-watch-flexy[flexy][tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]:not([hidden])')
  4846.  
  4847. if (eps && eps.length > 0) {
  4848.  
  4849. if (eps.length > 1) {
  4850. let p = 0;
  4851. for (const ep of eps) {
  4852. if (ep !== node) {
  4853. ytBtnCloseEngagementPanel(ep)
  4854. p++
  4855. }
  4856. }
  4857. if (p > 0) {
  4858. await Promise.resolve(0)
  4859. }
  4860. }
  4861.  
  4862. FP.mtf_attrEngagementPanel(9, node);
  4863.  
  4864. Promise.resolve().then(() => {
  4865.  
  4866. mtoVisibility_EngagementPanel.bindElement(node, {
  4867. attributes: true,
  4868. attributeFilter: ['visibility'],
  4869. attributeOldValue: true
  4870. })
  4871.  
  4872. node.removeEventListener('mouseleave', removeFocusOnLeave, false)
  4873. node.addEventListener('mouseleave', removeFocusOnLeave, false)
  4874.  
  4875. })
  4876.  
  4877. }
  4878.  
  4879.  
  4880. } catch (e) { }
  4881.  
  4882. })
  4883.  
  4884. handleDOMAppear('playlistRowDOMSelected', (evt) => {
  4885. if (!evt) return;
  4886. let target = evt.target;
  4887. if (!target) return;
  4888. let items = nodeParent(target);
  4889. if (!items || items.id !== 'items') return;
  4890. let m = /\/watch\?v=[^\&]+\&/.exec(location.href || '')
  4891. if (!m) return;
  4892. let s = m[0] + "";
  4893. if (!s || s.length <= 10) return;
  4894. let correctAnchor = items.querySelector(`ytd-playlist-panel-video-renderer a[href*="${s}"]`);
  4895. if (!correctAnchor || target.contains(correctAnchor)) return;
  4896. let correctRow = correctAnchor.closest('ytd-playlist-panel-video-renderer');
  4897. if (!correctRow) return;
  4898. target.removeAttribute('selected');
  4899. correctRow.setAttribute('selected', '');
  4900. });
  4901.  
  4902. let _tabviewSiderAnimated = false;
  4903.  
  4904. handleDOMAppear('tabviewSiderAnimation', (evt) => {
  4905. if (!_tabviewSiderAnimated) {
  4906. _tabviewSiderAnimated = true;
  4907. dispatchCommentRowResize();
  4908. }
  4909. })
  4910.  
  4911. handleDOMAppear('tabviewSiderAnimationNone', (evt) => {
  4912. if (_tabviewSiderAnimated) {
  4913. _tabviewSiderAnimated = false;
  4914. dispatchCommentRowResize();
  4915. }
  4916. })
  4917.  
  4918. handleDOMAppear('SearchWhileWatchAutocomplete', (evt) => { // Youtube - Search While Watching Video
  4919. let elm = evt.target;
  4920. elm.addEventListener('autocomplete-sc-exist', handlerAutoCompleteExist, false)
  4921. scriptletDeferred.debounce(() => {
  4922. elm.dispatchEvent(new CustomEvent('tabview-fix-autocomplete'));
  4923. elm = null;
  4924. })
  4925. })
  4926.  
  4927. handleDOMAppear('oldYtIconPinAppeared', (evt) => {
  4928. /* added in May 2023 - 2023.05.19 */
  4929.  
  4930. /*
  4931. from
  4932. <svg style="pointer-events: none; display: block; width: 100%; height: 100%;" focusable="false" width="24" viewBox="0 0 24 24" height="24"><path d="M16 11V3h1V2H7v1h1v8l-2 2v2h5v6l1 1 1-1v-6h5v-2l-2-2zm1 3H7v-.59l1.71-1.71.29-.29V3h6v8.41l.29.29L17 13.41V14z"></path></svg>
  4933.  
  4934. to
  4935. <svg viewBox="0 0 12 12" preserveAspectRatio="xMidYMid meet" focusable="false" class="style-scope yt-icon" style="pointer-events: none; display: block; width: 100%; height: 100%;"><g class="style-scope yt-icon"><path d="M8,2V1H3v1h1v3.8L3,7h2v2.5L5.5,10L6,9.5V7h2L7,5.8V2H8z M6,6H5V2h1V6z" class="style-scope yt-icon"></path></g></svg>
  4936.  
  4937. */
  4938.  
  4939. let svg = evt.target;
  4940. let p = document.createElement('template');
  4941. p.innerHTML = '<svg viewBox="0 0 12 12" preserveAspectRatio="xMidYMid meet" focusable="false" class="style-scope yt-icon" style="pointer-events: none; display: block; width: 100%; height: 100%;"><g class="style-scope yt-icon"><path d="M8,2V1H3v1h1v3.8L3,7h2v2.5L5.5,10L6,9.5V7h2L7,5.8V2H8z M6,6H5V2h1V6z" class="style-scope yt-icon"></path></g></svg>';
  4942. svg.replaceWith(p.content.firstChild);
  4943.  
  4944.  
  4945. })
  4946.  
  4947. const renderStamperFunc = {
  4948. 'YTD-PLAYLIST-PANEL-RENDERER': (node) => {
  4949. mtf_append_playlist(node); // the true playlist is appended to the #tab-list
  4950. checkPlaylistForInitialization();
  4951. },
  4952. 'YTD-COMMENTS-HEADER-RENDERER': (node) => {
  4953. comments_loader = comments_loader | 4;
  4954. getFinalComments();
  4955. }
  4956. }
  4957.  
  4958. globalHook('yt-rendererstamper-finished', (evt) => {
  4959.  
  4960. if (!scriptEnable && tabsDeferred.resolved) { return }
  4961. // might occur before initialization
  4962.  
  4963. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  4964.  
  4965. let node = evt.target;
  4966. const nodeName = node.nodeName.toUpperCase();
  4967. const func = renderStamperFunc[nodeName];
  4968.  
  4969. if (typeof func !== 'function') {
  4970. return;
  4971. }
  4972.  
  4973.  
  4974. let tabsDeferredSess = pageSession.session();
  4975. if (!scriptEnable && tabsDeferred.resolved) { }
  4976. else tabsDeferred.debounce(() => {
  4977.  
  4978. if (!tabsDeferredSess.isValid) return;
  4979. tabsDeferredSess = null;
  4980.  
  4981. func(node);
  4982. node = null;
  4983.  
  4984. });
  4985.  
  4986.  
  4987. });
  4988.  
  4989. if (REMOVE_DUPLICATE_INFO) {
  4990.  
  4991. handleDOMAppear('deferredDuplicatedMetaChecker', (evt) => {
  4992.  
  4993. removeDuplicateInfoFn();
  4994.  
  4995. });
  4996.  
  4997. }
  4998.  
  4999. globalHook('yt-page-data-updated', (evt) => {
  5000.  
  5001. if (!scriptEnable && tabsDeferred.resolved) { return }
  5002. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5003.  
  5004. // _console.log(evt.target.nodeName, 904, evt.type);
  5005.  
  5006. advanceFetch();
  5007.  
  5008. let tabsDeferredSess = pageSession.session();
  5009. if (!scriptEnable && tabsDeferred.resolved) { }
  5010. else tabsDeferred.debounce(() => {
  5011.  
  5012. if (!tabsDeferredSess.isValid) return;
  5013. tabsDeferredSess = null;
  5014.  
  5015. if (!scriptEnable) return;
  5016.  
  5017. checkAndMakeNewCommentFetch();
  5018.  
  5019.  
  5020. // if the page is navigated by history back-and-forth, not all engagement panels can be catched in rendering event.
  5021.  
  5022.  
  5023.  
  5024. // _console.log(2178, 3)
  5025. pageCheck();
  5026. setupChatFrameDOM(null);
  5027.  
  5028. let expander = document.querySelector('#meta-contents ytd-expander:not([tabview-expander-checked])');
  5029. if (expander) {
  5030.  
  5031. // once per $$native-info-description$$ {#meta-contents ytd-expander} detection
  5032. // append the detailed meta contents to the tab-info
  5033.  
  5034. expander.setAttribute('tabview-expander-checked', '');
  5035. let tabInfo = document.querySelector("#tab-info");
  5036. if (tabInfo) {
  5037. elementAppend.call(tabInfo, expander);
  5038. }
  5039.  
  5040. }
  5041.  
  5042.  
  5043. if (REMOVE_DUPLICATE_INFO) {
  5044.  
  5045. // removeDuplicateInfoFn();
  5046.  
  5047. } else {
  5048.  
  5049. checkDuplicatedInfo_then(0, null);
  5050.  
  5051. }
  5052.  
  5053. function contentExtractor(elm) {
  5054. if (!(elm instanceof HTMLElement)) {
  5055. return null;
  5056. }
  5057. let m = elm.textContent;
  5058. let isEmpty = m.trim().length === 0;
  5059. if (isEmpty) return null;
  5060. let s = elm.nodeName;
  5061. let id = elm.id;
  5062. if (id) s += '#' + id;
  5063. return s + '\n' + m;
  5064. }
  5065.  
  5066. if (REMOVE_DUPLICATE_META_RECOMMENDATION) {
  5067.  
  5068. async function checkDuplicatedMetaRecommendation() {
  5069. let mainContent0 = document.querySelector('#primary.ytd-watch-flexy #above-the-fold.ytd-watch-metadata');
  5070. let mainContent1 = document.querySelector('#tab-info ytd-expander > #content');
  5071. if (mainContent0 && mainContent1) {
  5072. const hashedContents = new Set();
  5073. for (let s1 = elementNextSibling(mainContent1); s1 instanceof HTMLElement; s1 = elementNextSibling(s1)) {
  5074. let m = contentExtractor(s1);
  5075. if (m === null) continue;
  5076. hashedContents.add(m);
  5077. }
  5078.  
  5079. for (let s0 = elementNextSibling(mainContent0); s0 instanceof HTMLElement; s0 = elementNextSibling(s0)) {
  5080. let m = contentExtractor(s0);
  5081. if (m !== null && hashedContents.has(m)) {
  5082. s0.classList.add('tyt-hidden-duplicated-meta');
  5083. } else {
  5084. s0.classList.remove('tyt-hidden-duplicated-meta');
  5085. }
  5086. }
  5087. hashedContents.clear();
  5088. }
  5089. }
  5090.  
  5091. let ks = renderIdentifier;
  5092. renderDeferred.debounce(() => {
  5093. if (ks !== renderIdentifier) return
  5094. checkDuplicatedMetaRecommendation();
  5095. })
  5096.  
  5097. }
  5098.  
  5099.  
  5100.  
  5101. let renderId = renderIdentifier
  5102. renderDeferred.debounce(() => {
  5103. if (renderId !== renderIdentifier) return
  5104. // domInit_teaserInfo() // YouTube obsoleted feature?
  5105.  
  5106. let h1 = document.querySelector('#below h1.ytd-watch-metadata yt-formatted-string')
  5107. if (h1) {
  5108.  
  5109.  
  5110. let s = '';
  5111.  
  5112. try {
  5113. if (formatDates && Object.keys(formatDates).length > 0) {
  5114.  
  5115. function getDurationInfo(bd1, bd2) {
  5116.  
  5117. let bdd = bd2 - bd1
  5118. let hrs = Math.floor(bdd / 3600000)
  5119. bdd = bdd - hrs * 3600000
  5120. let mins = Math.round(bdd / 60000)
  5121. let seconds = null
  5122. if (mins < 10 && hrs === 0) {
  5123. mins = Math.floor(bdd / 60000)
  5124. bdd = bdd - mins * 60000
  5125. seconds = Math.round(bdd / 1000)
  5126. if (seconds === 0) seconds = null
  5127. }
  5128.  
  5129. return { hrs, mins, seconds }
  5130.  
  5131. }
  5132.  
  5133. const formatDateResult = getFormatDateResultFunc();
  5134.  
  5135. if (formatDates.broadcastBeginAt && formatDates.isLiveNow === false) {
  5136.  
  5137. let bd1 = new KDate(formatDates.broadcastBeginAt)
  5138. let bd2 = formatDates.broadcastEndAt ? new KDate(formatDates.broadcastEndAt) : null
  5139.  
  5140. let isSameDay = 0
  5141. if (bd2 && bd1.toLocaleDateString() === bd2.toLocaleDateString()) {
  5142. isSameDay = 1
  5143.  
  5144. } else if (bd2 && +bd2 > +bd1 && bd2 - bd1 < 86400000) {
  5145.  
  5146. if (bd1.getHours() >= 6 && bd2.getHours() < 6) {
  5147. isSameDay = 2
  5148. }
  5149.  
  5150. }
  5151.  
  5152. let durationInfo = getDurationInfo(bd1, bd2)
  5153. if (isSameDay > 0) {
  5154.  
  5155. bd2.dayBack = (isSameDay === 2)
  5156.  
  5157. s = formatDateResult(0x200, { bd1, bd2, isSameDay, durationInfo, formatDates })
  5158.  
  5159. } else if (bd2 && isSameDay === 0) {
  5160.  
  5161. s = formatDateResult(0x210, { bd1, bd2, isSameDay, durationInfo, formatDates })
  5162.  
  5163. }
  5164.  
  5165.  
  5166. } else if (formatDates.broadcastBeginAt && formatDates.isLiveNow === true) {
  5167.  
  5168. let bd1 = new KDate(formatDates.broadcastBeginAt)
  5169.  
  5170. s = formatDateResult(0x300, { bd1, formatDates })
  5171.  
  5172. } else {
  5173. if (formatDates.uploadDate) {
  5174.  
  5175. if (formatDates.publishDate && formatDates.publishDate !== formatDates.uploadDate) {
  5176.  
  5177. s = formatDateResult(0x600, { formatDates })
  5178. } else {
  5179. s = formatDateResult(0x610, { formatDates })
  5180.  
  5181. }
  5182. } else if (!formatDates.uploadDate && formatDates.publishDate) {
  5183.  
  5184. s = formatDateResult(0x700, { formatDates })
  5185.  
  5186.  
  5187. }
  5188. }
  5189.  
  5190.  
  5191. }
  5192. } catch (e) {
  5193. s = '';
  5194. }
  5195.  
  5196. if (s) {
  5197. h1.setAttribute('data-title-details', s)
  5198. } else {
  5199. h1.removeAttribute('data-title-details')
  5200. }
  5201.  
  5202. }
  5203.  
  5204. })
  5205.  
  5206.  
  5207. checkPlaylistForInitialization();
  5208.  
  5209. mtf_fix_details().then(() => {
  5210. setToggleInfo();
  5211. renderDeferred.debounce(() => {
  5212. if (renderId !== renderIdentifier) return
  5213. setTimeout(() => {
  5214. //dispatchWindowResize(); //try to omit
  5215. dispatchWindowResize(); //add once for safe
  5216. manualResizeT();
  5217. }, 420)
  5218. })
  5219.  
  5220.  
  5221. let secondary = document.querySelector('#columns.ytd-watch-flexy #secondary.ytd-watch-flexy');
  5222.  
  5223. let columns = secondary ? closestDOM.call(secondary, '#columns.ytd-watch-flexy') : null;
  5224.  
  5225. setupHoverSlider(secondary, columns)
  5226.  
  5227. let tabInfo = document.querySelector('#tab-info');
  5228. addTabExpander(tabInfo);
  5229.  
  5230. let tabComments = document.querySelector('#tab-comments');
  5231. addTabExpander(tabComments);
  5232.  
  5233.  
  5234. });
  5235.  
  5236.  
  5237. });
  5238.  
  5239. });
  5240.  
  5241.  
  5242. globalHook('yt-watch-comments-ready', (evt) => {
  5243.  
  5244. if (!scriptEnable && tabsDeferred.resolved) { return }
  5245. if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5246.  
  5247. let nodeName = evt.target.nodeName.toUpperCase()
  5248. advanceFetch();
  5249.  
  5250. comments_loader = comments_loader | 2;
  5251.  
  5252. let tabsDeferredSess = pageSession.session();
  5253. if (!scriptEnable && tabsDeferred.resolved) { }
  5254. else tabsDeferred.debounce(() => {
  5255.  
  5256. if (!tabsDeferredSess.isValid) return;
  5257. tabsDeferredSess = null;
  5258.  
  5259. checkAndMakeNewCommentFetch();
  5260.  
  5261. if (nodeName === 'YTD-WATCH-FLEXY') {
  5262. domInit_comments();
  5263. onCommentsReady();
  5264. }
  5265. });
  5266.  
  5267. })
  5268.  
  5269.  
  5270. window.addEventListener("message", (evt) => {
  5271. if (!scriptEnable && tabsDeferred.resolved) { return }
  5272. if (evt.origin === location.origin && evt.data.tabview) {
  5273. let data = evt.data.tabview;
  5274. if (data.eventType === "yt-page-type-changed") {
  5275. let detail = data.eventDetail
  5276. let { newPageType, oldPageType } = detail;
  5277. if (newPageType && oldPageType) {
  5278. let bool = false;
  5279. if (newPageType == 'ytd-watch-flexy') {
  5280. bool = true;
  5281. pageType = 'watch';
  5282. } else if (newPageType == 'ytd-browse') {
  5283. pageType = 'browse';
  5284. }
  5285. document.documentElement.classList.toggle('tabview-normal-player', bool)
  5286. }
  5287. }
  5288. }
  5289. }, bubblePassive);
  5290.  
  5291.  
  5292. globalHook('data-changed', (evt) => {
  5293.  
  5294. if (!scriptEnable && tabsDeferred.resolved) { return }
  5295.  
  5296. let nodeName = (((evt || 0).target || 0).nodeName || '').toUpperCase()
  5297.  
  5298. if (nodeName !== 'YTD-THUMBNAIL-OVERLAY-TOGGLE-BUTTON-RENDERER') return;
  5299.  
  5300. document.dispatchEvent(new CustomEvent("tabview-fix-popup-refit"));
  5301.  
  5302. });
  5303.  
  5304.  
  5305. // DEBUG_LOG && globalHook('yt-rendererstamper-finished', (evt) => {
  5306.  
  5307. // if (!scriptEnable && tabsDeferred.resolved) { return }
  5308. // // might occur before initialization
  5309.  
  5310. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5311.  
  5312. // const nodeName = evt.target.nodeName.toUpperCase();
  5313.  
  5314. // // const S_GENERAL_RENDERERS = ['YTD-TOGGLE-BUTTON-RENDERER','YTD-MENU-RENDERER']
  5315. // if (S_GENERAL_RENDERERS.includes(nodeName)) {
  5316. // return;
  5317. // }
  5318.  
  5319. // // _console.log(evt.target.nodeName, 904, evt.type, evt.detail);
  5320.  
  5321. // });
  5322.  
  5323. // DEBUG_LOG && globalHook('data-changed', (evt) => {
  5324.  
  5325. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5326.  
  5327. // let nodeName = evt.target.nodeName.toUpperCase()
  5328. // // _console.log(nodeName, evt.type)
  5329.  
  5330. // if (nodeName === 'YTD-ITEM-SECTION-RENDERER' || nodeName === 'YTD-COMMENTS') {
  5331.  
  5332. // _console.log(344)
  5333.  
  5334. // }
  5335.  
  5336. // })
  5337.  
  5338. // DEBUG_LOG && globalHook('yt-navigate', (evt) => {
  5339.  
  5340. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5341. // _console.log(evt.target.nodeName, evt.type)
  5342.  
  5343. // })
  5344.  
  5345. // DEBUG_LOG && globalHook('ytd-playlist-lockup-now-playing-active', (evt) => {
  5346.  
  5347. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5348. // _console.log(evt.target.nodeName, evt.type)
  5349.  
  5350.  
  5351. // })
  5352.  
  5353. // DEBUG_LOG && globalHook('yt-service-request-completed', (evt) => {
  5354.  
  5355. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5356. // _console.log(evt.target.nodeName, evt.type)
  5357.  
  5358.  
  5359. // })
  5360.  
  5361. // DEBUG_LOG && globalHook('yt-commerce-action-done', (evt) => {
  5362.  
  5363. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5364. // _console.log(evt.target.nodeName, evt.type)
  5365.  
  5366.  
  5367. // })
  5368.  
  5369. // DEBUG_LOG && globalHook('yt-execute-service-endpoint', (evt) => {
  5370.  
  5371. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5372. // _console.log(evt.target.nodeName, evt.type)
  5373.  
  5374.  
  5375. // })
  5376.  
  5377.  
  5378. // DEBUG_LOG && globalHook('yt-request-panel-mode-change', (evt) => {
  5379.  
  5380. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5381. // _console.log(evt.target.nodeName, evt.type)
  5382.  
  5383.  
  5384. // })
  5385.  
  5386.  
  5387. // DEBUG_LOG && globalHook('yt-visibility-refresh', (evt) => {
  5388.  
  5389. // if (!evt || !evt.target /*|| evt.target.nodeType !== 1*/) return;
  5390. // _console.log(evt.target.nodeName || '', evt.type)
  5391.  
  5392. // const ytdFlexyElm = es.ytdFlexy;
  5393. // _console.log(2784, evt.type, (ytdFlexyElm ? ytdFlexyElm.hasAttribute('hidden') : null), evt.detail)
  5394.  
  5395. // _console.log(evt.detail)
  5396.  
  5397.  
  5398. // })
  5399.  
  5400. // DEBUG_LOG && globalHook('yt-request-panel-mode-change', (evt) => {
  5401.  
  5402. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5403. // _console.log(evt.target.nodeName, evt.type)
  5404.  
  5405.  
  5406. // })
  5407.  
  5408. // DEBUG_LOG && globalHook('app-reset-layout', (evt) => {
  5409.  
  5410. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5411. // _console.log(evt.target.nodeName, evt.type)
  5412.  
  5413.  
  5414. // })
  5415. // DEBUG_LOG && globalHook('yt-guide-close', (evt) => {
  5416.  
  5417. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5418. // _console.log(evt.target.nodeName, evt.type)
  5419.  
  5420.  
  5421. // })
  5422. // DEBUG_LOG && globalHook('yt-page-data-will-change', (evt) => {
  5423.  
  5424. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5425. // _console.log(evt.target.nodeName, evt.type)
  5426.  
  5427.  
  5428. // })
  5429.  
  5430. // DEBUG_LOG && globalHook('yt-retrieve-location', (evt) => {
  5431.  
  5432. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5433. // _console.log(evt.target.nodeName, evt.type)
  5434.  
  5435.  
  5436. // })
  5437.  
  5438. // DEBUG_LOG && globalHook('yt-refit', (evt) => {
  5439.  
  5440. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5441. // _console.log(evt.target.nodeName, evt.type)
  5442.  
  5443. // })
  5444.  
  5445. // DEBUG_LOG && globalHook('addon-attached', (evt) => {
  5446.  
  5447. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5448. // _console.log(evt.target.nodeName, evt.type)
  5449.  
  5450. // })
  5451.  
  5452. // DEBUG_LOG && globalHook('yt-live-chat-context-menu-opened', (evt) => {
  5453.  
  5454. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5455. // _console.log(evt.target.nodeName, evt.type)
  5456.  
  5457. // })
  5458.  
  5459. // DEBUG_LOG && globalHook('yt-live-chat-context-menu-closed', (evt) => {
  5460.  
  5461. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5462. // _console.log(evt.target.nodeName, evt.type)
  5463.  
  5464. // })
  5465.  
  5466. // DEBUG_LOG && globalHook('yt-commentbox-resize', (evt) => {
  5467.  
  5468. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5469. // _console.log(evt.target.nodeName, evt.type)
  5470. // })
  5471.  
  5472. // DEBUG_LOG && globalHook('yt-rich-grid-layout-refreshed', (evt) => {
  5473.  
  5474. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5475. // _console.log(2327, evt.target.nodeName, evt.type)
  5476. // })
  5477.  
  5478. // DEBUG_LOG && globalHook('animationend', (evt) => {
  5479.  
  5480. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5481. // _console.log(evt.target.nodeName, evt.type)
  5482.  
  5483.  
  5484. // })
  5485.  
  5486. // DEBUG_LOG && globalHook('yt-dismissible-item-dismissed', (evt) => {
  5487.  
  5488. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5489. // _console.log(evt.target.nodeName, evt.type)
  5490.  
  5491.  
  5492. // })
  5493.  
  5494. // DEBUG_LOG && globalHook('yt-dismissible-item-undismissed', function (evt) {
  5495.  
  5496. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5497. // _console.log(evt.target.nodeName, evt.type)
  5498.  
  5499.  
  5500. // })
  5501.  
  5502.  
  5503. // DEBUG_LOG && globalHook('yt-load-next-continuation', function (evt) {
  5504.  
  5505. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5506. // _console.log(evt.target.nodeName, evt.type)
  5507.  
  5508.  
  5509. // })
  5510.  
  5511.  
  5512. // DEBUG_LOG && globalHook('yt-load-reload-continuation', function (evt) {
  5513.  
  5514. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5515. // _console.log(evt.target.nodeName, evt.type)
  5516.  
  5517.  
  5518. // })
  5519.  
  5520. // DEBUG_LOG && globalHook('yt-toggle-button', function (evt) {
  5521.  
  5522. // if (!evt || !evt.target || evt.target.nodeType !== 1) return;
  5523. // _console.log(evt.target.nodeName, evt.type)
  5524.  
  5525.  
  5526. // })
  5527.  
  5528.  
  5529. }
  5530.  
  5531.  
  5532. function chatFrameContentDocument() {
  5533. // non-null if iframe exist && contentDocument && readyState = complete
  5534. /** @type {HTMLIFrameElement | null} */
  5535. let iframe = document.querySelector('ytd-live-chat-frame iframe#chatframe');
  5536. if (!iframe) return null; //iframe must be there
  5537. /** @type {Document | null} */
  5538. let cDoc = null;
  5539. try {
  5540. cDoc = iframe.contentDocument;
  5541. } catch (e) { }
  5542. if (!cDoc) return null;
  5543. if (cDoc.readyState != 'complete') return null; //we must wait for its completion
  5544.  
  5545. return cDoc;
  5546.  
  5547. }
  5548.  
  5549. function chatFrameElement(/** @type {string} */ cssSelector) {
  5550. let cDoc = chatFrameContentDocument();
  5551. if (!cDoc) return null;
  5552. /** @type {HTMLElement | null} */
  5553. let elm = null;
  5554. try {
  5555. elm = cDoc.querySelector(cssSelector)
  5556. } catch (e) {
  5557. console.log('iframe error', e)
  5558. }
  5559. return elm;
  5560. }
  5561.  
  5562. let iframeLoadHookA_id = 0
  5563.  
  5564. const iframeLoadHookA = async function (evt) {
  5565.  
  5566. const isIframe = (((evt || 0).target || 0).nodeName === 'IFRAME');
  5567. const iframe = isIframe ? evt.target : null;
  5568.  
  5569. if (iframe && iframe.matches('body iframe.style-scope.ytd-live-chat-frame#chatframe')) {
  5570. } else {
  5571. return;
  5572. }
  5573.  
  5574.  
  5575. const chat = HTMLElement.prototype.closest.call(iframe, 'ytd-live-chat-frame#chat');
  5576.  
  5577. // console.log('chat', chat)
  5578. if (!chat) return;
  5579.  
  5580. if (iframe.isConnected !== true) return;
  5581.  
  5582. // dpeChatRefreshCounter++; // optional ?
  5583.  
  5584. if (chat.hasAttribute('collapsed')) return;
  5585. console.debug('[tyt.iframe] loaded 01')
  5586.  
  5587. const tid = ++iframeLoadHookA_id;
  5588.  
  5589. const cDoc = await new Promise((resolve) => {
  5590.  
  5591. let timeoutCount = 80;
  5592. let cid = 0;
  5593. const tf = () => {
  5594. if (tid !== iframeLoadHookA_id || --timeoutCount < 0) {
  5595. resolve && resolve(null);
  5596. resolve = null;
  5597. cid && clearInterval(cid);
  5598. cid = 0;
  5599. }
  5600. let cDoc = false;
  5601. try {
  5602. cDoc = iframe.contentDocument;
  5603. } catch (e) {
  5604. }
  5605. if (!cDoc) return;
  5606. if (cDoc.readyState === 'loading') return;
  5607. if (!cDoc.body) return;
  5608.  
  5609. resolve && resolve(cDoc);
  5610. resolve = null;
  5611. cid && clearInterval(cid);
  5612. cid = 0;
  5613.  
  5614. }
  5615. tf();
  5616. if (resolve !== null) {
  5617. cid = setInterval(tf, 17);
  5618. }
  5619.  
  5620.  
  5621. }).catch(console.warn);
  5622.  
  5623. // console.log('cDoc', cDoc)
  5624. if (tid !== iframeLoadHookA_id) return;
  5625. if (!cDoc) {
  5626. return;
  5627. }
  5628.  
  5629. if (cDoc.readyState === 'loading') return;
  5630.  
  5631.  
  5632. const contentElement = (cDoc.body || 0).firstElementChild;
  5633.  
  5634. // const currentPopupBtn = HTMLElement.prototype.querySelector.call(chat, 'tyt-iframe-popup-btn');
  5635. // if(currentPopupBtn) currentPopupBtn.classList.remove('tyt-btn-enabled');
  5636.  
  5637. if (contentElement && !chat.hasAttribute('collapsed') && iframe.isConnected === true) {
  5638.  
  5639. if (!scriptEnable || !isChatExpand()) return; // v4.13.19 - scriptEnable = true in background
  5640. if (!(iframe instanceof HTMLIFrameElement) || iframe.isConnected !== true) return; //prevent iframe is detached from the page
  5641. let isCorrectDoc = false;
  5642. try {
  5643. isCorrectDoc = iframe.contentDocument === cDoc;
  5644. } catch (e) { }
  5645. if (isCorrectDoc) {
  5646. let chatFrame = HTMLElement.prototype.closest.call(iframe, 'ytd-live-chat-frame#chat');
  5647. if (chatFrame) {
  5648. console.debug('[tyt.iframe] loaded 02')
  5649. chatFrame.setAttribute('tyt-iframe-loaded', '');
  5650. dpeIframeReady(chatFrame);
  5651. }
  5652. }
  5653.  
  5654. } else if (!contentElement && !chat.hasAttribute('collapsed') && iframe.isConnected === true) {
  5655.  
  5656. // e.g. when restore from mini view to watch page
  5657.  
  5658. dpeFixUrlChatWhenOnloadWithEmptyBody(chat);
  5659.  
  5660. }
  5661.  
  5662.  
  5663. }
  5664.  
  5665. async function restorePIPforStickyHead() {
  5666. // after a trusted user action, PIP can be cancelled.
  5667. // this is to ensure enterPIP can be re-excecuted
  5668.  
  5669. if (isMiniviewForStickyHeadEnabled && !isStickyHeaderEnabled && userActivation) {
  5670. userActivation = false;
  5671. exitPIP();
  5672. }
  5673. }
  5674.  
  5675. let videosDeferred = new Deferred();
  5676.  
  5677. let _navigateLoadDT = 0;
  5678.  
  5679. function delayedClickHandler() {
  5680.  
  5681. if (isMiniviewForStickyHeadEnabled && !isStickyHeaderEnabled) {
  5682. restorePIPforStickyHead();
  5683. } else if (!isMiniviewForStickyHeadEnabled && isStickyHeaderEnabled && typeof IntersectionObserver == 'function') {
  5684. enablePIPforStickyHead();
  5685. }
  5686.  
  5687. }
  5688.  
  5689. function convertDefaultTabFromTmpToFinal(myDefaultTab_tmp) {
  5690.  
  5691. let myDefaultTab_final = null
  5692. if (myDefaultTab_tmp && typeof myDefaultTab_tmp === 'string' && /^\#[a-zA-Z\_\-\+]+$/.test(myDefaultTab_tmp)) {
  5693. if (document.querySelector(`.tab-btn[tyt-tab-content="${myDefaultTab_tmp}"]`)) myDefaultTab_final = myDefaultTab_tmp;
  5694. }
  5695.  
  5696. return myDefaultTab_final;
  5697. }
  5698.  
  5699. function getConfiguredDefaultTab(store) {
  5700. let myDefaultTab;
  5701. if (defaultTabByUserCSS === 1) {
  5702. myDefaultTab = '#tab-info';
  5703. } else if (defaultTabByUserCSS === 2) {
  5704. myDefaultTab = '#tab-comments';
  5705. } else if (defaultTabByUserCSS === 3) {
  5706. myDefaultTab = '#tab-videos';
  5707. } else {
  5708. store = store || getStore();
  5709. myDefaultTab = store[key_default_tab];
  5710. }
  5711. return myDefaultTab;
  5712. }
  5713.  
  5714. function setupTabBtns() {
  5715.  
  5716. const materialTab = document.querySelector("#material-tabs")
  5717. if (!materialTab) return;
  5718.  
  5719. if (tabsUiScript_setclick) return;
  5720. tabsUiScript_setclick = true;
  5721.  
  5722. let fontSizeBtnClick = null;
  5723.  
  5724. materialTab.addEventListener('click', function (evt) {
  5725.  
  5726. if (!evt.isTrusted) return; // prevent call from background
  5727.  
  5728. if (isMiniviewForStickyHeadEnabled) {
  5729. setTimeout(delayedClickHandler, 80);
  5730. } else if (!isMiniviewForStickyHeadEnabled && isStickyHeaderEnabled && typeof IntersectionObserver == 'function') {
  5731. setTimeout(delayedClickHandler, 80);
  5732. }
  5733. let dom = evt.target;
  5734. if ((dom || 0).nodeType !== 1) return;
  5735.  
  5736. const domInteraction = dom.getAttribute('tyt-di');
  5737. if (domInteraction === 'q9Kjc') {
  5738. handlerMaterialTabClick.call(dom, evt)
  5739. } else if (domInteraction === '8rdLQ') {
  5740. fontSizeBtnClick.call(dom, evt)
  5741. }
  5742.  
  5743.  
  5744.  
  5745. }, true)
  5746.  
  5747. function updateCSS_fontsize(store) {
  5748.  
  5749. if (!store) return;
  5750.  
  5751. const ytdFlexyElm = es.ytdFlexy;
  5752. if (ytdFlexyElm) {
  5753. if (store['font-size-#tab-info']) ytdFlexyElm.style.setProperty('--ut2257-info', store['font-size-#tab-info'])
  5754. if (store['font-size-#tab-comments']) ytdFlexyElm.style.setProperty('--ut2257-comments', store['font-size-#tab-comments'])
  5755. if (store['font-size-#tab-videos']) ytdFlexyElm.style.setProperty('--ut2257-videos', store['font-size-#tab-videos'])
  5756. if (store['font-size-#tab-list']) ytdFlexyElm.style.setProperty('--ut2257-list', store['font-size-#tab-list'])
  5757. document.dispatchEvent(new CustomEvent("tabview-zoom-updated"));
  5758. }
  5759.  
  5760. }
  5761.  
  5762. fontSizeBtnClick = function (evt) {
  5763.  
  5764. evt.preventDefault();
  5765. evt.stopPropagation();
  5766. evt.stopImmediatePropagation();
  5767.  
  5768. /** @type {HTMLElement | null} */
  5769. let dom = evt.target;
  5770. if (!dom) return;
  5771.  
  5772. let value = dom.classList.contains('font-size-plus') ? 1 : dom.classList.contains('font-size-minus') ? -1 : 0;
  5773.  
  5774. let active_tab_content = closestDOM.call(dom, '[tyt-tab-content]').getAttribute('tyt-tab-content');
  5775.  
  5776. let store = getStore();
  5777. let settingKey = `font-size-${active_tab_content}`
  5778. if (!store[settingKey]) store[settingKey] = 1.0;
  5779. if (value < 0) store[settingKey] -= 0.05;
  5780. else if (value > 0) store[settingKey] += 0.05;
  5781. if (store[settingKey] < 0.1) store[settingKey] = 0.1;
  5782. else if (store[settingKey] > 10) store[settingKey] = 10.0;
  5783. setStore(store);
  5784.  
  5785. store = getStore();
  5786. updateCSS_fontsize(store);
  5787.  
  5788. }
  5789.  
  5790. function loadDefaultTabBtnSettingToMem(store) {
  5791. let myDefaultTab = getConfiguredDefaultTab(store);
  5792. myDefaultTab = myDefaultTab ? convertDefaultTabFromTmpToFinal(myDefaultTab) : null;
  5793. if (myDefaultTab) {
  5794. settings.defaultTab = myDefaultTab;
  5795. }
  5796. }
  5797.  
  5798. let store = getStore();
  5799. updateCSS_fontsize(store);
  5800. loadDefaultTabBtnSettingToMem(store);
  5801. setupDefaultTabBtnSetting = function () {
  5802. let myDefaultTab = getConfiguredDefaultTab();
  5803. myDefaultTab = myDefaultTab ? convertDefaultTabFromTmpToFinal(myDefaultTab) : null;
  5804. if (myDefaultTab) {
  5805. settings.defaultTab = myDefaultTab;
  5806. } else {
  5807. settings.defaultTab = SETTING_DEFAULT_TAB_0;
  5808. }
  5809. }
  5810.  
  5811. }
  5812.  
  5813. function setMyDefaultTab(myDefaultTab) {
  5814. myDefaultTab = convertDefaultTabFromTmpToFinal(myDefaultTab);
  5815. let store = getStore();
  5816. if (myDefaultTab) {
  5817. store[key_default_tab] = myDefaultTab;
  5818. settings.defaultTab = myDefaultTab;
  5819. } else {
  5820. delete store[key_default_tab];
  5821. settings.defaultTab = SETTING_DEFAULT_TAB_0;
  5822. }
  5823. setStore(store);
  5824. }
  5825.  
  5826. document.addEventListener('tabview-setMyDefaultTab', function (evt) {
  5827.  
  5828. let myDefaultTab_tmp = ((evt || 0).detail || 0).myDefaultTab
  5829.  
  5830. setMyDefaultTab(myDefaultTab_tmp)
  5831.  
  5832. }, false)
  5833.  
  5834. function loadFrameHandler(evt) {
  5835. let target = (evt || 0).target;
  5836. if (target instanceof HTMLIFrameElement && target.id === 'chatframe') {
  5837. fixLiveChatToggleButtonDispatchEvent();
  5838. }
  5839. }
  5840.  
  5841. async function onNavigationEndAsync(isPageFirstLoaded) {
  5842.  
  5843. if (pageType !== 'watch') return;
  5844.  
  5845. let tdt = Date.now();
  5846. _navigateLoadDT = tdt;
  5847.  
  5848. // avoid blocking the page when youtube is initializing the page
  5849. const promiseDelay = getRAFPromise().then();
  5850. const promiseVideoRendered = videosDeferred.d();
  5851.  
  5852. const verifyPageState = () => {
  5853. if (_navigateLoadDT !== tdt) {
  5854. return -400;
  5855. }
  5856. if (ytEventSequence !== 3) {
  5857. return -200;
  5858. }
  5859.  
  5860. const ytdFlexyElm = document.querySelector('ytd-watch-flexy')
  5861.  
  5862. if (!ytdFlexyElm) {
  5863. ytdFlexy = null;
  5864. return -100;
  5865. } else {
  5866. ytdFlexy = mWeakRef(ytdFlexyElm);
  5867. return 0;
  5868. }
  5869.  
  5870.  
  5871. }
  5872.  
  5873. let pgState = verifyPageState();
  5874. if (pgState < 0) return;
  5875.  
  5876. let scriptEnableTemp = scriptEnable;
  5877. scriptEnable = true; // avoid locking the other parts of script (see v4.13.19)
  5878. await Promise.all([promiseVideoRendered, promiseDelay]);
  5879. pgState = verifyPageState();
  5880. if (pgState < 0) {
  5881. scriptEnable = scriptEnableTemp;
  5882. if (!ytdFlexy) scriptEnable = false;
  5883. return;
  5884. }
  5885. pgState = null;
  5886. const ytdFlexyElm = kRef(ytdFlexy);
  5887.  
  5888. if (!ytdFlexyElm) {
  5889. ytdFlexy = null;
  5890. scriptEnable = false;
  5891. return;
  5892. }
  5893.  
  5894. fixLiveChatToggleButtonDispatchEvent();
  5895. document.removeEventListener('load', loadFrameHandler, true);
  5896. document.addEventListener('load', loadFrameHandler, true);
  5897.  
  5898. const related = querySelectorFromAnchor.call(ytdFlexyElm, "#related.ytd-watch-flexy");
  5899. if (!related) return;
  5900.  
  5901. // isPageFirstLoaded && console.time("Tabview Youtube Render")
  5902.  
  5903. if (!document.querySelector("#right-tabs")) {
  5904. getLangForPage();
  5905. let docTmp = document.createElement('template');
  5906. docTmp.innerHTML = getTabsHTML();
  5907. let newElm = docTmp.content.firstElementChild;
  5908. if (newElm !== null) {
  5909. insertBeforeTo(newElm, related);
  5910. querySelectorFromAnchor.call(newElm, '#material-tabs').addEventListener('mousemove', (evt) => {
  5911. evt.preventDefault();
  5912. evt.stopPropagation();
  5913. evt.stopImmediatePropagation();
  5914. }, true);
  5915. setupTabBtns();
  5916. console.debug('[tyt] #right-tabs inserted')
  5917. }
  5918. docTmp.textContent = '';
  5919. docTmp = null;
  5920. }
  5921.  
  5922. if (!ytdFlexyElm.hasAttribute('tyt-tab')) ytdFlexyElm.setAttribute('tyt-tab', '')
  5923.  
  5924. // append the next videos
  5925. // it exists as "related" is already here
  5926. fixTabs();
  5927.  
  5928. let switchToDefaultTabNotAllowed = false;
  5929.  
  5930. if (document.querySelector('ytd-watch-flexy[tyt-chat^="+"]')) {
  5931. switchToDefaultTabNotAllowed = true;
  5932. } else if (document.querySelector('ytd-watch-flexy[flexy][tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]:not([hidden])')) {
  5933. switchToDefaultTabNotAllowed = true;
  5934. } else if (document.querySelector('ytd-watch-flexy ytd-donation-shelf-renderer.ytd-watch-flexy:not([hidden]):not(:empty)')) {
  5935. switchToDefaultTabNotAllowed = true;
  5936. }
  5937.  
  5938. if (switchToDefaultTabNotAllowed) {
  5939. switchTabActivity(null);
  5940. } else {
  5941. setToActiveTab(); // just switch to the default tab
  5942. }
  5943.  
  5944.  
  5945. mtoFlexyAttr.clear(true)
  5946. mtf_checkFlexy()
  5947.  
  5948. tabsDeferred.resolve();
  5949. FP.mtf_attrEngagementPanel(); // check whether no visible panels
  5950.  
  5951. // isPageFirstLoaded && console.timeEnd("Tabview Youtube Render")
  5952.  
  5953. // let ks = renderIdentifier;
  5954. // renderDeferred.debounce(() => {
  5955. // if (ks !== renderIdentifier) return
  5956. // if (_navigateLoadDT !== tdt) return;
  5957.  
  5958. // let donationShelf = document.querySelector('ytd-watch-flexy:not([tyt-donation-shelf]) ytd-donation-shelf-renderer.ytd-watch-flexy:not([hidden]):not(:empty)');
  5959. // if (donationShelf) {
  5960. // // console.log(334)
  5961. // // ignored if event handler for tabview-donation-shelf-set-visibility is not yet hooked.
  5962. // setTimeout(()=>{
  5963. // if (ks !== renderIdentifier) return
  5964. // if (_navigateLoadDT !== tdt) return;
  5965. // // console.log(554)
  5966.  
  5967. // let donationShelf = document.querySelector('ytd-watch-flexy:not([tyt-donation-shelf]) ytd-donation-shelf-renderer.ytd-watch-flexy:not([hidden]):not(:empty)');
  5968. // if(!donationShelf) return;
  5969. // document.dispatchEvent(new CustomEvent('tabview-donation-shelf-set-visibility', {
  5970. // detail: {
  5971. // visibility: true,
  5972. // flushDOM: true
  5973. // }
  5974. // }));
  5975. // },450)
  5976. // }
  5977. // donationShelf = null;
  5978.  
  5979. // })
  5980.  
  5981. isPageFirstLoaded && document.documentElement.removeAttribute('tyt-lock');
  5982.  
  5983. }
  5984.  
  5985.  
  5986. function fetchCommentsFinished() {
  5987. const ytdFlexyElm = es.ytdFlexy;
  5988. if (!scriptEnable || !ytdFlexyElm) return;
  5989. if (mtf_forceCheckLiveVideo_disable === 2) return;
  5990. ytdFlexyElm.setAttribute('tyt-comments', 'L');
  5991. _console.log(2909, 1)
  5992. }
  5993.  
  5994. function setCommentSection( /** @type {number} */ value) {
  5995.  
  5996. Q.comments_section_loaded = value;
  5997. if (value === 0 && fetchCounts) {
  5998. fetchCounts.fetched = false; // unknown bug
  5999. }
  6000.  
  6001. }
  6002.  
  6003.  
  6004. function emptyCommentSection() {
  6005. let tab_btn = document.querySelector('.tab-btn[tyt-tab-content="#tab-comments"]')
  6006. if (tab_btn) {
  6007. let span = querySelectorFromAnchor.call(tab_btn, 'span#tyt-cm-count');
  6008. tab_btn.removeAttribute('loaded-comment')
  6009. if (span) {
  6010. span.textContent = '';
  6011. }
  6012. }
  6013. setCommentSection(0);
  6014. _console.log(7233, 'comments_section_loaded = 0')
  6015. }
  6016.  
  6017.  
  6018. function _disableComments() {
  6019.  
  6020.  
  6021. _console.log(2909, 1)
  6022. if (!scriptEnable) return;
  6023. let cssElm = es.ytdFlexy;
  6024. if (!cssElm) return;
  6025.  
  6026. _console.log(2909, 2)
  6027.  
  6028.  
  6029. let comments = document.querySelector('ytd-comments#comments')
  6030. if (mtf_forceCheckLiveVideo_disable === 2) {
  6031. // earlier than DOM change
  6032. } else {
  6033. if (comments && !comments.hasAttribute('hidden')) return; // visible comments content)
  6034. }
  6035.  
  6036. _console.log(2909, 4)
  6037. if (Q.comments_section_loaded === 2) return; //already disabled
  6038.  
  6039. setCommentSection(2);
  6040.  
  6041. _console.log(2909, 5)
  6042.  
  6043. let tabBtn = document.querySelector('.tab-btn[tyt-tab-content="#tab-comments"]');
  6044. if (tabBtn) {
  6045. let span = querySelectorFromAnchor.call(tabBtn, 'span#tyt-cm-count');
  6046. tabBtn.removeAttribute('loaded-comment')
  6047. if (!tabBtn.classList.contains('tab-btn-hidden')) {
  6048. //console.log('hide', comments, comments && comments.hasAttribute('hidden'))
  6049. hideTabBtn(tabBtn)
  6050. }
  6051. if (span) {
  6052. span.textContent = '';
  6053. }
  6054. }
  6055.  
  6056. cssElm.setAttribute('tyt-comments', 'D');
  6057.  
  6058. _console.log(2909, 10)
  6059.  
  6060.  
  6061. }
  6062.  
  6063. function setToggleInfo() {
  6064.  
  6065. scriptletDeferred.d().then(() => {
  6066.  
  6067. let elem = document.querySelector('#primary.ytd-watch-flexy #below ytd-watch-metadata #info-container.ytd-watch-metadata:first-child:not([tyt-info-toggler])')
  6068. if (elem) {
  6069.  
  6070. elem.setAttribute('tyt-info-toggler', '')
  6071. elem.dispatchEvent(new CustomEvent('tyt-info-toggler'))
  6072.  
  6073. }
  6074.  
  6075. });
  6076. }
  6077.  
  6078.  
  6079. function flexyAttr_toggleFlag(mFlag, b, flag) {
  6080. return b ? (mFlag | flag) : (mFlag & ~flag);
  6081. }
  6082.  
  6083. function flexAttr_toLayoutStatus(nls, attributeName) {
  6084.  
  6085. let attrElm, b, v;
  6086. switch (attributeName) {
  6087. case 'theater':
  6088. b = isTheater();
  6089. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_THEATER);
  6090. break;
  6091. case 'tyt-chat':
  6092. attrElm = es.ytdFlexy;
  6093. v = attrElm.getAttribute('tyt-chat');
  6094.  
  6095. if (v !== null && v.charAt(0) === '-') {
  6096. nls = flexyAttr_toggleFlag(nls, true, LAYOUT_CHATROOM | LAYOUT_CHATROOM_COLLAPSED);
  6097. } else {
  6098. nls = flexyAttr_toggleFlag(nls, v !== null, LAYOUT_CHATROOM);
  6099. nls = flexyAttr_toggleFlag(nls, false, LAYOUT_CHATROOM_COLLAPSED);
  6100. }
  6101.  
  6102. break;
  6103. case 'is-two-columns_':
  6104. b = isWideScreenWithTwoColumns();
  6105. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_TWO_COLUMNS);
  6106. break;
  6107.  
  6108. case 'tyt-tab':
  6109. attrElm = es.ytdFlexy;
  6110. b = isNonEmptyString(attrElm.getAttribute('tyt-tab'));
  6111. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_TAB_EXPANDED);
  6112. break;
  6113.  
  6114. case 'fullscreen':
  6115. attrElm = es.ytdFlexy;
  6116. b = attrElm.hasAttribute('fullscreen');
  6117. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_FULLSCREEN);
  6118. break;
  6119.  
  6120. case 'tyt-ep-visible':
  6121. attrElm = es.ytdFlexy;
  6122. v = attrElm.getAttribute('tyt-ep-visible');
  6123. b = (+v > 0)
  6124. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_ENGAGEMENT_PANEL_EXPANDED);
  6125. break;
  6126.  
  6127. case 'tyt-donation-shelf':
  6128. attrElm = es.ytdFlexy;
  6129. b = attrElm.hasAttribute('tyt-donation-shelf');
  6130. nls = flexyAttr_toggleFlag(nls, b, LAYOUT_DONATION_SHELF_EXPANDED);
  6131. break;
  6132.  
  6133. }
  6134.  
  6135. return nls;
  6136.  
  6137.  
  6138. }
  6139.  
  6140.  
  6141.  
  6142. function ito_details(entries, observer) {
  6143. if (!detailsTriggerReset) return;
  6144. if (!entries || entries.length !== 1) return; // unlikely
  6145. let entry = entries[0];
  6146. //console.log(entries)
  6147. if (entry.isIntersecting === true) {
  6148.  
  6149. if (fT(wls.layoutStatus, LAYOUT_TWO_COLUMNS | LAYOUT_FULLSCREEN, 0) === false) return;
  6150.  
  6151. let dom = entry.target;
  6152. if (!dom) return; //unlikely
  6153.  
  6154. let bool = false;
  6155. let descClickable = null;
  6156.  
  6157. if (fT(wls.layoutStatus, 0, LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED | LAYOUT_CHATROOM_EXPANDED | LAYOUT_TAB_EXPANDED) === false) {
  6158. descClickable = closestDOM.call(dom, '#description.item.style-scope.ytd-watch-metadata')
  6159. if (descClickable) {
  6160. detailsTriggerReset = false;
  6161. bool = true;
  6162. }
  6163. }
  6164.  
  6165. async function runAsync(dom, bool) {
  6166.  
  6167. if (bool) {
  6168. let descClickable = closestDOM.call(dom, '#description.item.style-scope.ytd-watch-metadata')
  6169. if (descClickable) {
  6170. descClickable.click();
  6171. }
  6172. }
  6173.  
  6174. await new Promise(r => setTimeout(r, 20));
  6175.  
  6176. let pInner, nw = null;
  6177. try {
  6178. let x = closestDOM.call(dom, '#description.item.style-scope.ytd-watch-metadata');
  6179. let h2 = x.offsetHeight;
  6180. pInner = closestDOM.call(x, '#primary-inner')
  6181. let h1 = pInner.offsetHeight;
  6182. x.setAttribute('userscript-scrollbar-render', '')
  6183. if (h1 > h2 && h2 > 0 && h1 > 0) nw = h1 - h2
  6184. } catch (e) { }
  6185. if (pInner) {
  6186. pInner.style.setProperty('--tyt-desc-top-h', `${nw ? nw : 0}px`)
  6187. }
  6188. }
  6189.  
  6190. runAsync(dom, bool);
  6191.  
  6192.  
  6193. }
  6194.  
  6195. }
  6196.  
  6197. function immediateCheck() {
  6198.  
  6199.  
  6200. if (!scriptEnable) return;
  6201.  
  6202. if (fT(wls.layoutStatus, LAYOUT_TWO_COLUMNS, LAYOUT_TAB_EXPANDED | LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED | LAYOUT_THEATER | LAYOUT_FULLSCREEN | LAYOUT_CHATROOM_EXPANDED)) {
  6203. setToActiveTab();
  6204. }
  6205.  
  6206. }
  6207.  
  6208.  
  6209. const mtf_attrFlexy = (mutations, observer) => {
  6210.  
  6211. //attr mutation checker - $$ytdFlexyElm$$ {ytd-watch-flexy} \single
  6212. //::attr
  6213. // ~ 'tyt-chat', 'theater', 'is-two-columns_',
  6214. // ~ 'tyt-tab', 'fullscreen', 'tyt-ep-visible',
  6215. // ~ 'hidden', 'is-extra-wide-video_'
  6216.  
  6217. //console.log(15330, scriptEnable, es.ytdFlexy, mutations)
  6218.  
  6219. if (!scriptEnable) return;
  6220.  
  6221. const cssElm = es.ytdFlexy;
  6222. if (!cssElm) return;
  6223.  
  6224. if (!mutations) return;
  6225.  
  6226. const old_layoutStatus = wls.layoutStatus
  6227. if (old_layoutStatus === 0) return;
  6228. let new_layoutStatus = old_layoutStatus;
  6229.  
  6230. let checkedChat = false;
  6231. let mss = 0;
  6232. let dcall = 0;
  6233.  
  6234. for (const mutation of mutations) {
  6235. new_layoutStatus = flexAttr_toLayoutStatus(new_layoutStatus, mutation.attributeName);
  6236. // _console.log(8221, 18, mutation.attributeName)
  6237. if (mutation.attributeName === 'tyt-chat') {
  6238.  
  6239. if (!checkedChat) {
  6240. checkedChat = true; // avoid double call
  6241.  
  6242. if ((cssElm.getAttribute('tyt-chat') || '').indexOf('chat$live') >= 0) {
  6243. // assigned new attribute - "chat$live" => disable comments section
  6244.  
  6245. //console.log(3712,2)
  6246. _disableComments();
  6247. }
  6248.  
  6249. if (!cssElm.hasAttribute('tyt-chat')) {
  6250. // might or might not collapsed before
  6251. dcall |= 1;
  6252. }
  6253. }
  6254.  
  6255. } else if (mutation.attributeName === 'tyt-ep-visible') {
  6256. // assume any other active component such as tab content and chatroom
  6257.  
  6258. if (+(cssElm.getAttribute('tyt-ep-visible') || 0) === 0 && +mutation.oldValue > 0) {
  6259. dcall |= 2;
  6260. }
  6261.  
  6262. if (mss === 0) mss = 1;
  6263. else mss = -1;
  6264.  
  6265. } else if (mutation.attributeName === 'tyt-donation-shelf') {
  6266. // assume any other active component such as tab content and chatroom
  6267.  
  6268. // console.log(4545, cssElm.hasAttribute('tyt-donation-shelf'))
  6269. if (!(cssElm.hasAttribute('tyt-donation-shelf'))) {
  6270. dcall |= 4;
  6271. } else {
  6272. lstTab.lastPanel = '#donation-shelf'
  6273. switchTabActivity(null);
  6274. // console.log(55)
  6275. }
  6276. if (mss === 0) mss = 2;
  6277. else mss = -1;
  6278.  
  6279. } else if (mutation.attributeName === 'is-extra-wide-video_') {
  6280. setTimeout(() => {
  6281. updateFloatingSlider(); //required for hover slider // eg video after ads
  6282. }, 1);
  6283. }
  6284. }
  6285.  
  6286. new_layoutStatus = fixLayoutStatus(new_layoutStatus);
  6287.  
  6288. if (new_layoutStatus !== old_layoutStatus) {
  6289.  
  6290. if (fT(new_layoutStatus, LAYOUT_TWO_COLUMNS, LAYOUT_TAB_EXPANDED | LAYOUT_THEATER | LAYOUT_CHATROOM_EXPANDED)) {
  6291. if (mss === 1) {
  6292. if (fT(new_layoutStatus, LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED, 0)) {
  6293. closeDonationShelf();
  6294. }
  6295. } else if (mss === 2) {
  6296. if (fT(new_layoutStatus, LAYOUT_ENGAGEMENT_PANEL_EXPANDED | LAYOUT_DONATION_SHELF_EXPANDED, 0)) {
  6297. ytBtnCloseEngagementPanels();
  6298. }
  6299. }
  6300. }
  6301. wls.layoutStatus = new_layoutStatus
  6302.  
  6303. }
  6304.  
  6305. let timeout240 = new Promise(r => timeline.setTimeout(r, 240));
  6306. timeout240.then(immediateCheck);
  6307.  
  6308. }
  6309.  
  6310.  
  6311. function setupChatFrameDisplayState1(chatBlockR, initialDisplayState) {
  6312.  
  6313.  
  6314. const ytdFlexyElm = es.ytdFlexy;
  6315. if (!scriptEnable || !ytdFlexyElm) return;
  6316.  
  6317. let chatTypeChanged = mtf_chatBlockQ !== chatBlockR;
  6318.  
  6319. let attr_chatblock = chatBlockR === 1 ? 'chat$live' : chatBlockR === 3 ? 'chat$playback' : false;
  6320. let attr_chatcollapsed = false;
  6321.  
  6322.  
  6323. if (attr_chatblock) {
  6324. let chatFrame = document.querySelector('ytd-live-chat-frame#chat')
  6325. if (chatFrame) {
  6326. attr_chatcollapsed = chatFrame.hasAttribute('collapsed');
  6327. if (!attr_chatcollapsed) {
  6328.  
  6329. //nativeFunc(p,'setupPlayerProgressRelay')
  6330. //if(!p.isFrameReady)
  6331. //nativeFunc(p, "urlChanged")
  6332. //console.log(12399,1)
  6333. chatFrame.dispatchEvent(new CustomEvent("tabview-chatroom-newpage")); //possible empty iframe is shown
  6334.  
  6335. }
  6336. } else {
  6337. attr_chatcollapsed = initialDisplayState === 'LIVE_CHAT_DISPLAY_STATE_COLLAPSED' ? true : false;
  6338. }
  6339. }
  6340.  
  6341. if (chatTypeChanged) {
  6342. mtf_chatBlockQ = chatBlockR
  6343.  
  6344. // _console.log(932, 2, attr_chatblock, attr_chatcollapsed)
  6345.  
  6346. //LIVE_CHAT_DISPLAY_STATE_COLLAPSED
  6347. //LIVE_CHAT_DISPLAY_STATE_EXPANDED
  6348. let v = attr_chatblock
  6349. if (typeof attr_chatblock == 'string') {
  6350.  
  6351. if (attr_chatcollapsed === true) v = '-' + attr_chatblock
  6352. if (attr_chatcollapsed === false) v = '+' + attr_chatblock;
  6353. }
  6354. wAttr(ytdFlexyElm, 'tyt-chat', v)
  6355.  
  6356. // _console.log(932, 3, ytdFlexyElm.hasAttribute('tyt-chat'))
  6357.  
  6358.  
  6359. }
  6360.  
  6361. return { attr_chatblock, attr_chatcollapsed, chatTypeChanged }
  6362. }
  6363.  
  6364. function setupChatFrameDisplayState2() {
  6365.  
  6366. const ytdFlexyElm = es.ytdFlexy;
  6367. if (!scriptEnable || !ytdFlexyElm) return null;
  6368.  
  6369. // this is a backup solution only; should be abandoned
  6370.  
  6371. let attr_chatblock = null
  6372. let attr_chatcollapsed = null;
  6373.  
  6374. const elmChat = document.querySelector('ytd-live-chat-frame#chat')
  6375. let elmCont = null;
  6376. if (elmChat) {
  6377. elmCont = chatFrameElement('yt-live-chat-renderer #continuations')
  6378.  
  6379.  
  6380. let s = 0;
  6381. if (elmCont) {
  6382. //not found if it is collapsed.
  6383. s |= querySelectorFromAnchor.call(elmCont, 'yt-timed-continuation') ? 1 : 0;
  6384. s |= querySelectorFromAnchor.call(elmCont, 'yt-live-chat-replay-continuation, yt-player-seek-continuation') ? 2 : 0;
  6385. //s |= elmCont.querySelector('yt-live-chat-restricted-participation-renderer')?4:0;
  6386. if (s == 1) {
  6387. attr_chatblock = 'chat$live';
  6388. } else if (s == 2) attr_chatblock = 'chat$playback';
  6389.  
  6390. if (s == 1) {
  6391. let cmCountElm = document.querySelector("span#tyt-cm-count")
  6392. if (cmCountElm) cmCountElm.textContent = '';
  6393. }
  6394.  
  6395. } else if (!ytdFlexyElm.hasAttribute('tyt-chat')) {
  6396. // live chat frame but type not known
  6397.  
  6398. attr_chatblock = '';
  6399.  
  6400. }
  6401. //keep unknown as original
  6402.  
  6403.  
  6404. let isCollapsed = !!elmChat.hasAttribute('collapsed');
  6405. attr_chatcollapsed = isCollapsed;
  6406.  
  6407. } else {
  6408. attr_chatblock = false;
  6409. attr_chatcollapsed = false;
  6410.  
  6411. }
  6412.  
  6413. return { attr_chatblock, attr_chatcollapsed }
  6414.  
  6415. }
  6416.  
  6417.  
  6418. const mtf_checkFlexy = () => {
  6419. // once per $$native-ytd-watch-flexy$$ {ytd-watch-flexy} detection
  6420.  
  6421. const ytdFlexyElm = es.ytdFlexy;
  6422. if (!scriptEnable || !ytdFlexyElm) return true;
  6423.  
  6424.  
  6425. wls.layoutStatus = 0;
  6426.  
  6427. let isFlexyHidden = (ytdFlexyElm.hasAttribute('hidden'));
  6428.  
  6429. if (!isFlexyHidden) {
  6430. let rChatExist = setupChatFrameDisplayState2();
  6431. if (rChatExist) {
  6432. let { attr_chatblock, attr_chatcollapsed } = rChatExist;
  6433. if (attr_chatblock === null) {
  6434. //remove attribute if it is unknown
  6435. attr_chatblock = false;
  6436. attr_chatcollapsed = false;
  6437. }
  6438. let v = attr_chatblock;
  6439. if (typeof v === 'string') {
  6440. if (attr_chatcollapsed === true) v = '-' + v;
  6441. if (attr_chatcollapsed === false) v = '+' + v;
  6442. }
  6443. wAttr(ytdFlexyElm, 'tyt-chat', v)
  6444.  
  6445. }
  6446. }
  6447.  
  6448. let rTabSelection = [...querySelectorAllFromAnchor.call(ytdFlexyElm, '.tab-btn[tyt-tab-content]')]
  6449. .map(elm => ({ elm, hidden: elm.classList.contains('tab-btn-hidden') }));
  6450.  
  6451. if (rTabSelection.length === 0) {
  6452. wAttr(ytdFlexyElm, 'tyt-tab', false);
  6453. } else {
  6454. rTabSelection = rTabSelection.filter(entry => entry.hidden !== true); // all available tabs
  6455. if (rTabSelection.length === 0) wAttr(ytdFlexyElm, 'tyt-tab', '');
  6456. }
  6457. rTabSelection = null;
  6458.  
  6459. let rEP = engagement_panels_();
  6460. if (rEP && rEP.list.length > 0) {
  6461. wAttr(ytdFlexyElm, 'tyt-ep-visible', `${rEP.value}`);
  6462. } else {
  6463. wAttr(ytdFlexyElm, 'tyt-ep-visible', false);
  6464. }
  6465.  
  6466. let ls = LAYOUT_VAILD;
  6467. ls = flexAttr_toLayoutStatus(ls, 'theater')
  6468. ls = flexAttr_toLayoutStatus(ls, 'tyt-chat')
  6469. ls = flexAttr_toLayoutStatus(ls, 'is-two-columns_')
  6470. ls = flexAttr_toLayoutStatus(ls, 'tyt-tab')
  6471. ls = flexAttr_toLayoutStatus(ls, 'fullscreen')
  6472. ls = flexAttr_toLayoutStatus(ls, 'tyt-ep-visible')
  6473. ls = flexAttr_toLayoutStatus(ls, 'tyt-donation-shelf')
  6474.  
  6475. fixLayoutStatus(ls)
  6476.  
  6477. wls.layoutStatus = ls
  6478.  
  6479. mtoFlexyAttr.bindElement(ytdFlexyElm, {
  6480. attributes: true,
  6481. attributeFilter: ['tyt-chat', 'theater', 'is-two-columns_', 'tyt-tab', 'fullscreen', 'tyt-ep-visible', 'tyt-donation-shelf', 'hidden', 'is-extra-wide-video_'],
  6482. attributeOldValue: true
  6483. })
  6484.  
  6485. let columns = document.querySelector('ytd-page-manager#page-manager #columns.ytd-watch-flexy')
  6486. if (columns) {
  6487. wAttr(columns, 'userscript-scrollbar-render', true);
  6488. }
  6489.  
  6490. immediateCheck()
  6491.  
  6492. return false;
  6493. }
  6494.  
  6495. document.addEventListener('tabview-fix-layout', () => {
  6496.  
  6497. immediateCheck()
  6498.  
  6499. }, false)
  6500.  
  6501.  
  6502. function switchTabActivity(activeLink) {
  6503.  
  6504. //console.log(4545, activeLink)
  6505. if (!scriptEnable) return;
  6506.  
  6507. const ytdFlexyElm = es.ytdFlexy;
  6508.  
  6509. if (!ytdFlexyElm) return;
  6510.  
  6511. if (activeLink && activeLink.classList.contains('tab-btn-hidden')) return; // not allow to switch to hide tab
  6512.  
  6513. //if (isTheater() && isWideScreenWithTwoColumns()) activeLink = null;
  6514.  
  6515.  
  6516. function runAtEnd() {
  6517.  
  6518. if (activeLink) {
  6519. lstTab.lastTab = activeLink.getAttribute('tyt-tab-content')
  6520. lstTab.lastPanel = null;
  6521.  
  6522. if (!document.querySelector(`${lstTab.lastTab}.tab-content-cld tabview-view-tab-expander`)) {
  6523.  
  6524. let secondary = document.querySelector('#secondary.ytd-watch-flexy');
  6525. if (secondary) {
  6526. secondary.dispatchEvent(new CustomEvent('tabview-hover-slider-restore'))
  6527. //console.log(1995)
  6528. }
  6529.  
  6530.  
  6531. }
  6532. }
  6533.  
  6534. const newTag = activeLink ? lstTab.lastTab : '';
  6535.  
  6536. ytdFlexyElm.setAttribute('tyt-tab', newTag)
  6537.  
  6538. if (newTag === '#tab-videos') {
  6539.  
  6540.  
  6541.  
  6542. let t = document.querySelector('#tab-videos yt-chip-cloud-renderer')
  6543. if (t) {
  6544. scriptletDeferred.debounce(() => {
  6545. t.dispatchEvent(new CustomEvent('tyt-resize-chip-cloud'))
  6546. })
  6547. }
  6548.  
  6549. }
  6550.  
  6551.  
  6552. }
  6553.  
  6554. const links = document.querySelectorAll('#material-tabs a[tyt-tab-content]');
  6555.  
  6556. //console.log(701, activeLink)
  6557.  
  6558. for (const link of links) {
  6559. let content = document.querySelector(link.getAttribute('tyt-tab-content'));
  6560. if (link && content) {
  6561. if (link !== activeLink) {
  6562. link.classList.remove("active");
  6563. content.classList.add("tab-content-hidden");
  6564. if (!content.hasAttribute("tyt-hidden")) {
  6565. content.setAttribute("tyt-hidden", ""); // for https://greasyfork.org/en/scripts/456108
  6566. }
  6567. } else {
  6568. link.classList.add("active");
  6569. if (content.hasAttribute("tyt-hidden")) {
  6570. content.removeAttribute("tyt-hidden"); // for https://greasyfork.org/en/scripts/456108
  6571. }
  6572. content.classList.remove("tab-content-hidden");
  6573. }
  6574. }
  6575. }
  6576.  
  6577. runAtEnd();
  6578.  
  6579.  
  6580. }
  6581.  
  6582.  
  6583. function getStore() {
  6584. let s = localStorage[STORE_key];
  6585. function resetStore() {
  6586. let ret = {
  6587. version: 1,
  6588. };
  6589. localStorage[STORE_key] = JSON.stringify(ret);
  6590. return ret;
  6591. }
  6592. if (!s) return resetStore();
  6593. let obj = null;
  6594. try {
  6595. obj = JSON.parse(s);
  6596. } catch (e) { }
  6597. return obj && obj.version === STORE_VERSION ? obj : resetStore();
  6598. }
  6599.  
  6600. function setStore(obj) {
  6601. if (!obj || typeof obj !== 'object') return false;
  6602. if (obj.version !== STORE_VERSION) return false;
  6603. localStorage[STORE_key] = JSON.stringify(obj);
  6604. return true;
  6605. }
  6606.  
  6607.  
  6608.  
  6609. async function handlerMaterialTabClickInner(tabBtn) {
  6610.  
  6611. await Promise.resolve(0);
  6612.  
  6613. const layoutStatusMutexUnlock = await new Promise(resolve => {
  6614. layoutStatusMutex.lockWith(unlock => {
  6615. resolve(unlock)
  6616. })
  6617. });
  6618.  
  6619. // locked
  6620. let unlock = layoutStatusMutexUnlock; // function of unlock inside layoutStatusMutex
  6621.  
  6622. //console.log(8515)
  6623. switchTabActivity_lastTab = tabBtn.getAttribute('tyt-tab-content');
  6624.  
  6625. let isActiveAndVisible = tabBtn.classList.contains('tab-btn') && tabBtn.classList.contains('active') && !tabBtn.classList.contains('tab-btn-hidden')
  6626.  
  6627. _console.log(8221, 15, isActiveAndVisible)
  6628.  
  6629. if (isFullScreen()) {
  6630.  
  6631.  
  6632. const fullScreenTabScrollIntoView = () => {
  6633. let scrollElement = document.querySelector('ytd-app[scrolling]')
  6634. if (!scrollElement) return;
  6635. // single column view; click button; scroll to tab content area 100%
  6636. let rightTabs = document.querySelector('#right-tabs');
  6637. let pTop = rightTabs.getBoundingClientRect().top - scrollElement.getBoundingClientRect().top
  6638. if (rightTabs && pTop > 0 && tabBtn.classList.contains('active')) {
  6639. rightTabs.scrollIntoView(false);
  6640. }
  6641. }
  6642.  
  6643. _console.log(8221, 16, 1)
  6644.  
  6645. if (isActiveAndVisible) {
  6646. timeline.setTimeout(unlock, 80);
  6647. switchTabActivity(null);
  6648. } else {
  6649.  
  6650. if (isChatExpand() && isWideScreenWithTwoColumns()) {
  6651. ytBtnCollapseChat();
  6652. } else if (isEngagementPanelExpanded() && isWideScreenWithTwoColumns()) {
  6653. ytBtnCloseEngagementPanels();
  6654. } else if (isDonationShelfExpanded() && isWideScreenWithTwoColumns()) {
  6655. closeDonationShelf();
  6656. }
  6657.  
  6658.  
  6659. timeline.setTimeout(fullScreenTabScrollIntoView, 60)
  6660.  
  6661. timeline.setTimeout(unlock, 80);
  6662. switchTabActivity(tabBtn)
  6663. }
  6664.  
  6665. } else if (isWideScreenWithTwoColumns() && !isTheater() && isActiveAndVisible) {
  6666.  
  6667. _console.log(8221, 16, 2)
  6668. //optional
  6669. timeline.setTimeout(unlock, 80);
  6670. switchTabActivity(null);
  6671. ytBtnSetTheater();
  6672. } else if (isActiveAndVisible) {
  6673.  
  6674. _console.log(8221, 16, 3)
  6675. timeline.setTimeout(unlock, 80);
  6676. switchTabActivity(null);
  6677. } else {
  6678.  
  6679. _console.log(8221, 16, 4)
  6680.  
  6681. if (isChatExpand() && isWideScreenWithTwoColumns()) {
  6682. ytBtnCollapseChat();
  6683. } else if (isEngagementPanelExpanded() && isWideScreenWithTwoColumns()) {
  6684. ytBtnCloseEngagementPanels();
  6685. } else if (isDonationShelfExpanded() && isWideScreenWithTwoColumns()) {
  6686. closeDonationShelf();
  6687. } else if (isWideScreenWithTwoColumns() && isTheater() && !isFullScreen()) {
  6688. ytBtnCancelTheater(); //ytd-watch-flexy attr [theater]
  6689. }
  6690.  
  6691. timeline.setTimeout(() => {
  6692. // single column view; click button; scroll to tab content area 100%
  6693. let rightTabs = document.querySelector('#right-tabs');
  6694. if (!isWideScreenWithTwoColumns() && rightTabs && rightTabs.offsetTop > 0 && tabBtn.classList.contains('active') && rightTabs.hasAttribute('tyt-stickybar')) {
  6695. let tabButtonBar = document.querySelector('#material-tabs');
  6696. let tabButtonBarHeight = tabButtonBar ? tabButtonBar.offsetHeight : 0;
  6697. window.scrollTo(0, rightTabs.offsetTop - tabButtonBarHeight); // scrollIntoView
  6698. }
  6699. }, 60)
  6700. // _console.log(8519)
  6701.  
  6702. timeline.setTimeout(unlock, 80)
  6703. switchTabActivity(tabBtn)
  6704.  
  6705. }
  6706.  
  6707.  
  6708. }
  6709.  
  6710. function handlerMaterialTabClick(/** @type {MouseEvent} */ evt) {
  6711.  
  6712. //console.log(8510)
  6713. const ytdFlexyElm = es.ytdFlexy;
  6714. if (!scriptEnable || !ytdFlexyElm) return null;
  6715.  
  6716. let tabBtn = this;
  6717.  
  6718. if (!tabBtn.hasAttribute('tyt-tab-content')) return;
  6719.  
  6720. /** @type {HTMLElement | null} */
  6721. let dom = evt.target;
  6722. if (!dom) return;
  6723.  
  6724. if (dom.classList.contains('font-size-btn')) return;
  6725.  
  6726.  
  6727. evt.preventDefault();
  6728.  
  6729. handlerMaterialTabClickInner(tabBtn);
  6730.  
  6731.  
  6732. }
  6733.  
  6734. function onVideoLeavePictureInPicuture() {
  6735. isMiniviewForStickyHeadEnabled = false;
  6736. }
  6737.  
  6738.  
  6739. let videoPlayerInsectObserver = null;
  6740. let videoInsected = false;
  6741.  
  6742.  
  6743. async function enablePIPforStickyHead() {
  6744. // use async & await to avoid handler took 60ms
  6745.  
  6746. if (!isMiniviewForStickyHeadEnabled && isStickyHeaderEnabled && userActivation && typeof IntersectionObserver == 'function') {
  6747. let video = document.querySelector('#player video');
  6748. if (!video) return;
  6749.  
  6750. await Promise.resolve(0)
  6751. const pageClientWidth = document.documentElement.clientWidth;
  6752. if (pageClientWidth + 320 < screen.width && pageClientWidth > 320 && !document.querySelector('#rCbM3')) {
  6753.  
  6754.  
  6755. await Promise.resolve(0)
  6756. // desktop or notebook can use this feature
  6757.  
  6758. // --------------------------------------------------------
  6759. // ignore user activation error
  6760. enterPIP(video, null).then(r => {
  6761. if (r === true) {
  6762.  
  6763. userActivation = false;
  6764. isMiniviewForStickyHeadEnabled = true;
  6765. }
  6766. });
  6767. // --------------------------------------------------------
  6768. video.removeEventListener('leavepictureinpicture', onVideoLeavePictureInPicuture, false);
  6769. video.addEventListener('leavepictureinpicture', onVideoLeavePictureInPicuture, false);
  6770.  
  6771. if (!video.hasAttribute('NOL4j')) {
  6772. video.setAttribute('NOL4j', "");
  6773.  
  6774.  
  6775. await Promise.resolve(0)
  6776.  
  6777. let callback = (entries) => {
  6778.  
  6779.  
  6780. let lastEntry = entries[entries.length - 1];
  6781. if (lastEntry && lastEntry.isIntersecting === true) {
  6782.  
  6783. videoInsected = true;
  6784.  
  6785. if (isMiniviewForStickyHeadEnabled && !isStickyHeaderEnabled && userActivation && videoInsected) {
  6786. restorePIPforStickyHead();
  6787. }
  6788.  
  6789. } else {
  6790. videoInsected = false;
  6791. }
  6792.  
  6793. };
  6794.  
  6795. if (!videoPlayerInsectObserver) {
  6796. videoPlayerInsectObserver = new IntersectionObserver(callback, {
  6797. root: null,
  6798. rootMargin: "0px",
  6799. threshold: 0.25
  6800. });
  6801. }
  6802.  
  6803. videoPlayerInsectObserver.takeRecords();
  6804. videoPlayerInsectObserver.disconnect();
  6805.  
  6806. videoPlayerInsectObserver.observe(video);
  6807.  
  6808.  
  6809. }
  6810.  
  6811. }
  6812. }
  6813. }
  6814.  
  6815. function setStickyHeader(targetElm, bool, getWidthHeight, getLeftRight) {
  6816.  
  6817. //if(isStickyHeaderEnabled===bool) return; // no update
  6818.  
  6819. if (bool === true) {
  6820. const { width, height } = getWidthHeight();
  6821. targetElm.style.setProperty("--tyt-stickybar-w", width + 'px')
  6822. targetElm.style.setProperty("--tyt-stickybar-h", height + 'px')
  6823. const res = getLeftRight();
  6824. if (res) {
  6825.  
  6826. targetElm.style.setProperty("--tyt-stickybar-l", (res.left) + 'px')
  6827. targetElm.style.setProperty("--tyt-stickybar-r", (res.right) + 'px')
  6828.  
  6829. }
  6830. wAttr(targetElm, 'tyt-stickybar', true);
  6831. isStickyHeaderEnabled = true;
  6832.  
  6833. if (!isMiniviewForStickyHeadEnabled && isStickyHeaderEnabled && userActivation && typeof IntersectionObserver == 'function') {
  6834. setTimeout(enablePIPforStickyHead, 0);
  6835. }
  6836.  
  6837. } else if (bool === false) {
  6838.  
  6839. wAttr(targetElm, 'tyt-stickybar', false);
  6840. isStickyHeaderEnabled = false;
  6841.  
  6842.  
  6843. }
  6844.  
  6845.  
  6846. }
  6847.  
  6848. const singleColumnScrolling = async function () {
  6849. //makeHeaderFloat
  6850. // required for 1) init 2) layout change 3) resizing
  6851.  
  6852. if (!scriptEnable || pageType !== 'watch') return;
  6853.  
  6854.  
  6855. let isTwoCol = (wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS;
  6856. if (isTwoCol) {
  6857.  
  6858. if (isStickyHeaderEnabled) {
  6859.  
  6860. let targetElm = document.querySelector("#right-tabs");
  6861. setStickyHeader(targetElm, false, null, null);
  6862. }
  6863. return;
  6864. }
  6865.  
  6866. let pageY = scrollY;
  6867.  
  6868.  
  6869. let tdt = Date.now();
  6870. singleColumnScrolling_dt = tdt;
  6871.  
  6872.  
  6873. _console.log(7891, 'scrolling')
  6874.  
  6875. function getXYStatus(res) {
  6876.  
  6877. const [navHeight, elmY] = res;
  6878.  
  6879. let xyz = [elmY + navHeight, pageY, elmY - navHeight]
  6880.  
  6881. let xyStatus = 0
  6882. if (xyz[1] < xyz[2] && xyz[2] < xyz[0]) {
  6883. // 1
  6884. xyStatus = 1
  6885. }
  6886.  
  6887. if (xyz[0] > xyz[1] && xyz[1] > xyz[2]) {
  6888.  
  6889. //2
  6890. xyStatus = 2
  6891.  
  6892. }
  6893.  
  6894. if (xyz[2] < xyz[0] && xyz[0] < xyz[1]) {
  6895. // 3
  6896.  
  6897. xyStatus = 3
  6898.  
  6899.  
  6900. }
  6901.  
  6902. return xyStatus;
  6903. }
  6904.  
  6905. let [targetElm, header, navElm] = await Promise.all([
  6906. Promise.resolve().then(() => document.querySelector("#right-tabs")),
  6907.  
  6908. Promise.resolve().then(() => document.querySelector("#right-tabs header")),
  6909.  
  6910. Promise.resolve().then(() => document.querySelector('#masthead-container, #masthead')),
  6911.  
  6912. ]);
  6913.  
  6914. function emptyForGC() {
  6915. targetElm = null;
  6916. header = null;
  6917. navElm = null;
  6918. }
  6919.  
  6920. if (!targetElm || !header) {
  6921. return emptyForGC();
  6922. }
  6923. if (singleColumnScrolling_dt !== tdt) return emptyForGC();
  6924.  
  6925. let res2 = await Promise.all([
  6926. Promise.resolve().then(() => navElm ? navElm.offsetHeight : 0),
  6927. Promise.resolve().then(() => targetElm.offsetTop)
  6928. ])
  6929.  
  6930. if (res2 === null) return emptyForGC();
  6931.  
  6932. if (singleColumnScrolling_dt !== tdt) return emptyForGC();
  6933.  
  6934.  
  6935. const xyStatus = getXYStatus(res2);
  6936.  
  6937.  
  6938. function getLeftRight() {
  6939.  
  6940. let thp = document.querySelector('tabview-view-pos-thead');
  6941. if (thp) {
  6942.  
  6943. let rect = thp.getBoundingClientRect()
  6944. if (rect) {
  6945. return {
  6946. left: rect.left,
  6947. right: document.documentElement.clientWidth - rect.right
  6948. };
  6949. }
  6950. }
  6951. return null;
  6952. }
  6953.  
  6954. let bool = (xyStatus == 2 || xyStatus == 3) ? true : ((xyStatus == 1) ? false : null);
  6955.  
  6956. function getWidthHeight() {
  6957. return { width: targetElm.offsetWidth, height: header.offsetHeight };
  6958. }
  6959.  
  6960. setStickyHeader(targetElm, bool, getWidthHeight, getLeftRight);
  6961.  
  6962.  
  6963. emptyForGC();
  6964.  
  6965. };
  6966.  
  6967.  
  6968. const singleColumnScrolling2 = async function (xyStatus, width, xRect) {
  6969. //makeHeaderFloat
  6970.  
  6971. if (!scriptEnable || pageType !== 'watch') return;
  6972.  
  6973.  
  6974. if ((wls.layoutStatus & LAYOUT_TWO_COLUMNS) === LAYOUT_TWO_COLUMNS) {
  6975. return;
  6976. }
  6977.  
  6978. let [targetElm, header] = await Promise.all([
  6979. Promise.resolve().then(() => document.querySelector("#right-tabs")),
  6980. Promise.resolve().then(() => document.querySelector("#right-tabs header"))
  6981. ]);
  6982.  
  6983. function emptyForGC() {
  6984. targetElm = null;
  6985. header = null;
  6986. }
  6987.  
  6988.  
  6989. if (!targetElm || !header) {
  6990. return emptyForGC();
  6991. }
  6992.  
  6993. function getLeftRight() {
  6994. return xRect;
  6995. }
  6996.  
  6997. let bool = (xyStatus == 2 || xyStatus == 3) ? true : ((xyStatus == 1) ? false : null);
  6998.  
  6999. function getWidthHeight() {
  7000. return { width: (width || targetElm.offsetWidth), height: header.offsetHeight };
  7001. }
  7002.  
  7003. setStickyHeader(targetElm, bool, getWidthHeight, getLeftRight);
  7004.  
  7005. emptyForGC();
  7006.  
  7007. };
  7008.  
  7009.  
  7010. function resetBuggyLayoutForNewVideoPage() {
  7011.  
  7012. const ytdFlexyElm = es.ytdFlexy;
  7013. if (!ytdFlexyElm) return;
  7014.  
  7015. //(flexy is visible and watch video page)
  7016.  
  7017. scriptEnable = true;
  7018.  
  7019. _console.log(27056)
  7020.  
  7021. let new_layoutStatus = wls.layoutStatus
  7022.  
  7023. new_layoutStatus & (LAYOUT_CHATROOM_COLLAPSED | LAYOUT_CHATROOM)
  7024.  
  7025. const new_isExpandedChat = !(new_layoutStatus & LAYOUT_CHATROOM_COLLAPSED) && !!(new_layoutStatus & LAYOUT_CHATROOM)
  7026. const new_isCollapsedChat = !!(new_layoutStatus & LAYOUT_CHATROOM_COLLAPSED) && !!(new_layoutStatus & LAYOUT_CHATROOM)
  7027.  
  7028. const new_isTwoColumns = new_layoutStatus & LAYOUT_TWO_COLUMNS;
  7029. const new_isTheater = new_layoutStatus & LAYOUT_THEATER;
  7030. const new_isTabExpanded = new_layoutStatus & LAYOUT_TAB_EXPANDED;
  7031. const new_isFullScreen = new_layoutStatus & LAYOUT_FULLSCREEN;
  7032. const new_isExpandedEPanel = new_layoutStatus & LAYOUT_ENGAGEMENT_PANEL_EXPANDED;
  7033. const new_isExpandedDonationShelf = new_layoutStatus & LAYOUT_DONATION_SHELF_EXPANDED;
  7034.  
  7035. const nothingShown = !new_isTabExpanded && !new_isExpandedChat && !new_isExpandedEPanel && !new_isExpandedDonationShelf
  7036.  
  7037. if (ytdFlexyElm.getAttribute('tyt-tab') === '' && new_isTwoColumns && !new_isTheater && nothingShown && !new_isFullScreen) {
  7038. // e.g. engage panel removed after miniview and change video
  7039. setToActiveTab();
  7040. } else if (new_isExpandedEPanel && querySelectorAllFromAnchor.call(ytdFlexyElm, 'ytd-watch-flexy[flexy][tyt-tab] #panels.ytd-watch-flexy ytd-engagement-panel-section-list-renderer[target-id][visibility="ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"]:not([hidden])').length === 0) {
  7041. wls.layoutStatus = new_layoutStatus & (~LAYOUT_ENGAGEMENT_PANEL_EXPANDED)
  7042. } else if (new_isExpandedDonationShelf && querySelectorAllFromAnchor.call(ytdFlexyElm, 'ytd-donation-shelf-renderer.ytd-watch-flexy:not([hidden])').length === 0) {
  7043. wls.layoutStatus = new_layoutStatus & (~LAYOUT_DONATION_SHELF_EXPANDED)
  7044. }
  7045.  
  7046. }
  7047.  
  7048.  
  7049. function extractInfoFromLiveChatRenderer(liveChatRenderer) {
  7050.  
  7051. let lcr = liveChatRenderer
  7052.  
  7053. let data_shb = ((lcr || 0).showHideButton || 0).toggleButtonRenderer
  7054.  
  7055. let default_display_state = null, txt_collapse = null, txt_expand = null;
  7056.  
  7057.  
  7058. if (data_shb && data_shb.defaultText && data_shb.toggledText && data_shb.defaultText.runs && data_shb.toggledText.runs) {
  7059.  
  7060. if (data_shb.defaultText.runs.length === 1 && data_shb.toggledText.runs.length === 1) {
  7061.  
  7062. if (lcr.initialDisplayState == "LIVE_CHAT_DISPLAY_STATE_EXPANDED") {
  7063.  
  7064. default_display_state = lcr.initialDisplayState
  7065.  
  7066. txt_collapse = (data_shb.defaultText.runs[0] || 0).text // COLLAPSE the area
  7067.  
  7068. txt_expand = (data_shb.toggledText.runs[0] || 0).text // expand the area
  7069.  
  7070. } else if (lcr.initialDisplayState == "LIVE_CHAT_DISPLAY_STATE_COLLAPSED") {
  7071. default_display_state = lcr.initialDisplayState
  7072.  
  7073. txt_expand = (data_shb.defaultText.runs[0] || 0).text // expand the area
  7074.  
  7075. txt_collapse = (data_shb.toggledText.runs[0] || 0).text // COLLAPSE the area
  7076. }
  7077.  
  7078.  
  7079. if (typeof txt_expand == 'string' && typeof txt_collapse == 'string' && txt_expand.length > 0 && txt_collapse.length > 0) {
  7080.  
  7081. } else {
  7082. txt_expand = null;
  7083. txt_collapse = null;
  7084. }
  7085. }
  7086.  
  7087. }
  7088.  
  7089. return { default_display_state, txt_collapse, txt_expand }
  7090.  
  7091. }
  7092.  
  7093. let dpeChatRefreshCounter = 0;
  7094. let proceedingChatFrameVideoID = '';
  7095. let newVideoPageCACC = -1;
  7096.  
  7097. function newVideoPage(evt_detail) {
  7098.  
  7099. //toggleBtnDC = 1;
  7100.  
  7101. console.debug('[tyt] newVideoPage')
  7102.  
  7103. const ytdFlexyElm = es.ytdFlexy;
  7104. if (!ytdFlexyElm) return;
  7105.  
  7106. timeline.reset();
  7107. layoutStatusMutex = new Mutex();
  7108.  
  7109. let liveChatRenderer = null;
  7110. let isReplay = null;
  7111.  
  7112. if (pageType !== 'watch') return; // scriptEnable -> pageType shall be always 'watch'
  7113. resetBuggyLayoutForNewVideoPage();
  7114.  
  7115. try {
  7116. liveChatRenderer = evt_detail.pageData.response.contents.twoColumnWatchNextResults.conversationBar.liveChatRenderer
  7117. } catch (e) { }
  7118. if (liveChatRenderer) {
  7119. if (liveChatRenderer.isReplay === true) isReplay = true;
  7120. }
  7121.  
  7122. pageFetchedDataVideoId = ((((evt_detail || 0).pageData || 0).playerResponse || 0).videoDetails || 0).videoId || 0;
  7123.  
  7124.  
  7125. const fvid = pageFetchedDataVideoId;
  7126. const tyid = ++dpeChatRefreshCounter;
  7127. proceedingChatFrameVideoID = '';
  7128. newVideoPageCACC = chatroomAttrCollapseCount;
  7129. setTimeout(() => {
  7130. if (fvid !== pageFetchedDataVideoId) return;
  7131. if (tyid !== dpeChatRefreshCounter) return;
  7132. ++dpeChatRefreshCounter;
  7133. const chat = document.querySelector('ytd-live-chat-frame#chat');
  7134. if (chat && !chat.hasAttribute('collapsed')) {
  7135. proceedingChatFrameVideoID = fvid;
  7136. dpeNewUrlChat(chat);
  7137. }
  7138. }, 67);
  7139.  
  7140.  
  7141. const chatBlockR = liveChatRenderer ? (isReplay ? 3 : 1) : 0
  7142. const initialDisplayState = liveChatRenderer ? liveChatRenderer.initialDisplayState : null;
  7143.  
  7144.  
  7145. liveChatRenderer = null; // release memory for GC, just in case
  7146.  
  7147. let f = () => {
  7148.  
  7149. _console.log(932, 1, 1)
  7150. const ytdFlexyElm = es.ytdFlexy;
  7151. if (!scriptEnable || !ytdFlexyElm) return;
  7152.  
  7153. _console.log(932, 1, 2)
  7154. if (pageType !== 'watch') return;
  7155.  
  7156. _console.log(932, 1, 3)
  7157.  
  7158.  
  7159. let displayState = setupChatFrameDisplayState1(chatBlockR, initialDisplayState);
  7160.  
  7161. let { attr_chatblock, attr_chatcollapsed, chatTypeChanged } = displayState;
  7162.  
  7163.  
  7164. if (pageType === 'watch') { // reset info when hidden
  7165.  
  7166. let elm_storeLastPanel = es.storeLastPanel;
  7167.  
  7168. if (!elm_storeLastPanel) storeLastPanel = null;
  7169. else if (!isDOMVisible(elm_storeLastPanel)) {
  7170. storeLastPanel = null;
  7171. ytBtnCloseEngagementPanels();
  7172. }
  7173.  
  7174. }
  7175.  
  7176. if (chatTypeChanged) {
  7177.  
  7178. if (attr_chatblock == 'chat$live') {
  7179.  
  7180. _console.log(932, 4)
  7181.  
  7182. mtf_forceCheckLiveVideo_disable = 2;
  7183.  
  7184. //console.log(3712,1)
  7185.  
  7186. _disableComments();
  7187.  
  7188.  
  7189. } else {
  7190.  
  7191. const tabBtn = document.querySelector('[tyt-tab-content="#tab-comments"].tab-btn-hidden')
  7192. if (tabBtn) {
  7193. emptyCommentSection();
  7194. _console.log(9360, 74);
  7195. setTabBtnVisible(tabBtn, true);
  7196. } else {
  7197. setCommentSection(0);
  7198. }
  7199.  
  7200. mtf_forceCheckLiveVideo_disable = 0;
  7201.  
  7202. _console.log(7234, 'comments_section_loaded = 0')
  7203. restoreFetching();
  7204.  
  7205.  
  7206. }
  7207.  
  7208.  
  7209. } else {
  7210.  
  7211. // restore Fetching only
  7212.  
  7213. if (mtf_forceCheckLiveVideo_disable !== 2 && (attr_chatblock === false || attr_chatblock === 'chat$playback')) {
  7214.  
  7215. emptyCommentSection();
  7216. _console.log(9360, 77);
  7217. mtf_forceCheckLiveVideo_disable = 0;
  7218. _console.log(7235, 'comments_section_loaded = 0')
  7219. restoreFetching();
  7220.  
  7221. }
  7222.  
  7223. }
  7224.  
  7225.  
  7226. checkAndMakeNewCommentFetch();
  7227.  
  7228. }
  7229.  
  7230. f();
  7231.  
  7232. }
  7233.  
  7234. function setVideosTwoColumns(/** @type {number} */ flag, /** @type {boolean} */ bool) {
  7235.  
  7236. //two columns to one column
  7237.  
  7238. /*
  7239. [placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy
  7240.  
  7241. is-two-columns ="" => no is-two-columns
  7242. [placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer
  7243. no hidden => hidden =""
  7244. [placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer
  7245. hidden ="" => no hidden
  7246.  
  7247. */
  7248.  
  7249. let cssSelector1 = '[placeholder-videos] ytd-watch-next-secondary-results-renderer.style-scope.ytd-watch-flexy'
  7250.  
  7251. let cssSelector2 = '[placeholder-videos] tp-yt-paper-spinner#spinner.style-scope.ytd-continuation-item-renderer'
  7252.  
  7253. let cssSelector3 = '[placeholder-videos] div#button.style-scope.ytd-continuation-item-renderer'
  7254.  
  7255. let res = {}
  7256. if (flag & 1) {
  7257. res.m1 = document.querySelector(cssSelector1)
  7258. if (res.m1) wAttr(res.m1, 'is-two-columns', bool ? '' : false);
  7259. }
  7260.  
  7261. if (flag & 2) {
  7262. res.m2 = document.querySelector(cssSelector2)
  7263. if (res.m2) wAttr(res.m2, 'hidden', bool ? false : '');
  7264. }
  7265.  
  7266. if (flag & 4) {
  7267. res.m3 = document.querySelector(cssSelector3)
  7268. if (res.m3) wAttr(res.m3, 'hidden', bool ? '' : false);
  7269. }
  7270.  
  7271. return res
  7272.  
  7273.  
  7274. }
  7275.  
  7276.  
  7277. // ---------------------------------------------------------------------------------------------
  7278.  
  7279. // ---- EVENTS ----
  7280.  
  7281. let ytEventSequence = 0
  7282. let formatDates = null
  7283.  
  7284.  
  7285. function pageBeingFetched(evt, isPageFirstLoaded) {
  7286.  
  7287. let nodeName = (((evt || 0).target || 0).nodeName || '').toUpperCase()
  7288. if (nodeName !== 'YTD-APP') return;
  7289.  
  7290. let pageFetchedDataLocal = evt.detail;
  7291.  
  7292. let d_page = ((pageFetchedDataLocal || 0).pageData || 0).page;
  7293. if (!d_page) return;
  7294.  
  7295. pageType = d_page;
  7296.  
  7297. if (pageType !== 'watch') return
  7298.  
  7299. let promiseChatDetails = null
  7300.  
  7301. let isFirstLoad = firstLoadStatus & 8;
  7302.  
  7303. if (isFirstLoad) {
  7304. firstLoadStatus -= 8;
  7305. document.addEventListener('load', iframeLoadHookA, capturePassive)
  7306. ytMicroEventsInit();
  7307. }
  7308. // ytMicroEventsInit set
  7309. variableResets();
  7310.  
  7311. if (isFirstLoad) {
  7312.  
  7313. if (ytEventSequence >= 2) {
  7314. let docElement = document.documentElement
  7315. if (docElement.hasAttribute('tabview-loaded')) {
  7316. throw 'Tabview Youtube Duplicated';
  7317. }
  7318. docElement.setAttribute('tabview-loaded', '')
  7319.  
  7320. Promise.resolve(docElement).then(docElement => {
  7321. if (ytEventSequence >= 2) {
  7322. docElement.dispatchEvent(new CustomEvent('tabview-ce-hack'))
  7323. docElement = null
  7324. }
  7325. })
  7326.  
  7327. }
  7328. }
  7329. // tabview-loaded delay set
  7330.  
  7331. formatDates = {}
  7332. try {
  7333. formatDates.publishDate = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.publishDate
  7334. } catch (e) { }
  7335. // 2022-12-30
  7336.  
  7337. try {
  7338. formatDates.uploadDate = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.uploadDate
  7339. } catch (e) { }
  7340. // 2022-12-30
  7341.  
  7342. try {
  7343. formatDates.publishDate2 = pageFetchedDataLocal.pageData.response.contents.twoColumnWatchNextResults.results.results.contents[0].videoPrimaryInfoRenderer.dateText.simpleText
  7344. } catch (e) { }
  7345. // 2022/12/31
  7346.  
  7347. if (typeof formatDates.publishDate2 === 'string' && formatDates.publishDate2 !== formatDates.publishDate) {
  7348. formatDates.publishDate = formatDates.publishDate2
  7349. formatDates.uploadDate = null
  7350. }
  7351.  
  7352. try {
  7353. formatDates.broadcastBeginAt = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.liveBroadcastDetails.startTimestamp
  7354. } catch (e) { }
  7355. try {
  7356. formatDates.broadcastEndAt = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.liveBroadcastDetails.endTimestamp
  7357. } catch (e) { }
  7358. try {
  7359. formatDates.isLiveNow = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.liveBroadcastDetails.isLiveNow
  7360. } catch (e) { }
  7361.  
  7362. promiseChatDetails = Promise.resolve().then(() => {
  7363. if (ytEventSequence >= 2) {
  7364. let liveChatRenderer = null;
  7365. try {
  7366. liveChatRenderer = pageFetchedDataLocal.pageData.response.contents.twoColumnWatchNextResults.conversationBar.liveChatRenderer
  7367. } catch (e) { }
  7368. chatroomDetails = liveChatRenderer ? extractInfoFromLiveChatRenderer(liveChatRenderer) : null;
  7369. liveChatRenderer = null; // release memory for GC, just in case
  7370. }
  7371. });
  7372.  
  7373. let ytdFlexyElm = document.querySelector('ytd-watch-flexy')
  7374.  
  7375. if (!ytdFlexyElm) return;
  7376.  
  7377.  
  7378. ytdFlexy = mWeakRef(ytdFlexyElm);
  7379.  
  7380. ytdFlexyElm = null;
  7381.  
  7382. Promise.resolve(0).then(() => {
  7383.  
  7384. if (ytEventSequence >= 2 && pageRendered === 0) {
  7385.  
  7386. const ytdFlexyElm = es.ytdFlexy; // shall be always non-null
  7387. if (ytdFlexyElm) {
  7388.  
  7389. let elmPL = document.createElement('tabview-view-ploader');
  7390. pageRendered = 1;
  7391. // ytdFlexyElm.appendChild(elmPL);
  7392. elementAppend.call(ytdFlexyElm, elmPL);
  7393. // pageRendered keeps at 1 if the video is continuously playing at the background
  7394. // pageRendered would not be resolve but will reset for each change of video
  7395.  
  7396. }
  7397.  
  7398. }
  7399.  
  7400. })
  7401.  
  7402. let renderId = renderIdentifier
  7403. renderDeferred.debounce(() => {
  7404. if (renderId !== renderIdentifier) return
  7405. if (ytEventSequence >= 2) {
  7406. advanceFetch(); // at least one triggering at yt-page-data-fetched
  7407. }
  7408. });
  7409.  
  7410. Promise.race([promiseChatDetails]).then(() => {
  7411.  
  7412. const ytdFlexyElm = es.ytdFlexy;
  7413. if (ytEventSequence >= 2 && ytdFlexyElm) {
  7414. ytdFlexyElm.classList.toggle('tyt-chat-toggleable', !!chatroomDetails);
  7415. }
  7416.  
  7417. }).then(() => {
  7418.  
  7419. if (ytEventSequence >= 2) {
  7420.  
  7421. let tabsDeferredSess = pageSession.session();
  7422. if (!scriptEnable && tabsDeferred.resolved) { }
  7423. else tabsDeferred.debounce(() => {
  7424.  
  7425. if (!tabsDeferredSess.isValid) return;
  7426. tabsDeferredSess = null;
  7427.  
  7428. if (ytEventSequence >= 2 && pageFetchedDataLocal !== null) {
  7429. domInit_comments();
  7430. newVideoPage(pageFetchedDataLocal);
  7431. pageFetchedDataLocal = null;
  7432. }
  7433.  
  7434. });
  7435.  
  7436. }
  7437.  
  7438. })
  7439.  
  7440. }
  7441.  
  7442. let pageSeqMutex = new Mutex()
  7443.  
  7444. function pageSeq1(evt) {
  7445. _navigateLoadDT = 0
  7446. if (ytEventSequence !== 1) {
  7447. ytEventSequence = 1
  7448. pageSeqMutex.lockWith(unlock => {
  7449. pageBeingInit();
  7450. unlock();
  7451. })
  7452. }
  7453. }
  7454.  
  7455. function pageSeq2(evt) {
  7456. _navigateLoadDT = 0
  7457.  
  7458. if (ytEventSequence !== 1) {
  7459. ytEventSequence = 1
  7460. pageSeqMutex.lockWith(unlock => {
  7461. pageBeingInit();
  7462. unlock();
  7463. })
  7464. }
  7465. if (ytEventSequence === 1) {
  7466. ytEventSequence = 2
  7467.  
  7468.  
  7469. pageType = null
  7470.  
  7471. pageSeqMutex.lockWith(unlock => {
  7472.  
  7473. let mIsPageFirstLoaded = _isPageFirstLoaded
  7474.  
  7475. pageType = null
  7476. // mIsPageFirstLoaded && console.time("Tabview Youtube Load")
  7477. pageBeingFetched(evt, mIsPageFirstLoaded)
  7478. // mIsPageFirstLoaded && console.timeEnd("Tabview Youtube Load")
  7479. // ytMicroEventsInit set + tabview-loaded delay set
  7480. Promise.resolve().then(() => {
  7481. if (ytEventSequence >= 2) {
  7482. document.documentElement.classList.toggle('tabview-normal-player', pageType === 'watch');
  7483. }
  7484. })
  7485. if (pageType !== 'watch') {
  7486. ytdFlexy = null
  7487. chatroomDetails = null
  7488. Promise.resolve(0).then(() => {
  7489. if (ytEventSequence >= 2) {
  7490. variableResets();
  7491. emptyCommentSection();
  7492. _console.log(9360, 75);
  7493. tabsDeferred.reset();
  7494. _pageBeingInit();
  7495. tabsDeferred.resolve(); // for page initialization
  7496. }
  7497. })
  7498. }
  7499.  
  7500. if (_updateTimeAccum > 0) {
  7501. let currentVideo = document.querySelector('#movie_player video[src]')
  7502. let keep_viTime = false
  7503. if (currentVideo && currentVideo.readyState > currentVideo.HAVE_CURRENT_DATA && currentVideo.currentTime > 2.2) {
  7504. // allow miniview browsing
  7505. keep_viTime = true
  7506. }
  7507. if (!keep_viTime) {
  7508. _viTimeNum = 200;
  7509. _updateTimeAccum = 0;
  7510. delete document.head.dataset.viTime;
  7511. }
  7512. }
  7513.  
  7514. unlock();
  7515. })
  7516. }
  7517.  
  7518. }
  7519.  
  7520. function pageSeq3(evt) {
  7521. _navigateLoadDT = 0
  7522.  
  7523. if (ytEventSequence === 2) {
  7524. ytEventSequence = 3
  7525.  
  7526. pageSeqMutex.lockWith(unlock => {
  7527. if (pageType === 'watch') {
  7528. let mIsPageFirstLoaded = _isPageFirstLoaded
  7529. // ytMicroEventsInit set + tabview-loaded delay set
  7530. onNavigationEndAsync(mIsPageFirstLoaded)
  7531. _isPageFirstLoaded = false
  7532. }
  7533.  
  7534.  
  7535. unlock();
  7536. })
  7537. }
  7538. }
  7539.  
  7540.  
  7541. document.addEventListener('yt-navigate-start', pageSeq1, bubblePassive)
  7542. document.addEventListener('yt-navigate-cache', pageSeq1, bubblePassive)
  7543. document.addEventListener('yt-navigate-redirect', pageSeq1, bubblePassive)
  7544. document.addEventListener('yt-page-data-fetched', pageSeq2, bubblePassive)
  7545. document.addEventListener("yt-navigate-finish", pageSeq3, bubblePassive)
  7546. //yt-navigate-redirect
  7547. //yt-page-data-fetched
  7548. //yt-navigate-error
  7549. //yt-navigate-start
  7550. //yt-page-manager-navigate-start
  7551. //yt-navigate
  7552. //yt-navigate-cache
  7553.  
  7554. globalHook('yt-page-data-fetched', generalLog901)
  7555. //globalHook('yt-rendererstamper-finished',generalLog901)
  7556. globalHook('yt-page-data-updated', generalLog901)
  7557. globalHook('yt-player-updated', generalLog901)
  7558. globalHook('yt-watch-comments-ready', generalLog901)
  7559. globalHook('yt-page-type-changed', generalLog901)
  7560. globalHook('data-changed', generalLog901)
  7561. globalHook('yt-navigate-finish', generalLog901)
  7562. globalHook('yt-navigate-redirect', generalLog901)
  7563. globalHook('yt-navigate-error', generalLog901)
  7564. globalHook('yt-navigate-start', generalLog901)
  7565. globalHook('yt-page-manager-navigate-start', generalLog901)
  7566. globalHook('yt-navigate', generalLog901)
  7567. globalHook('yt-navigate-cache', generalLog901)
  7568.  
  7569.  
  7570. // ---------------------------------------------------------------------------------------------
  7571.  
  7572.  
  7573. function manualResizeT() {
  7574.  
  7575. if (!scriptEnable) return;
  7576. if (pageType !== 'watch') return;
  7577. //lastResizeAt = Date.now();
  7578.  
  7579. if ((wls.layoutStatus & LAYOUT_FULLSCREEN) === LAYOUT_FULLSCREEN) {
  7580.  
  7581. } else if ((wls.layoutStatus & LAYOUT_TWO_COLUMNS) === 0) {
  7582. // single col
  7583.  
  7584. setTimeout3(() => {
  7585. singleColumnScrolling(true)
  7586. })
  7587.  
  7588. } else {
  7589. // two cols
  7590.  
  7591. updateFloatingSlider();
  7592.  
  7593. }
  7594.  
  7595.  
  7596. }
  7597. //let lastResizeAt = 0;
  7598. let resizeCount = 0;
  7599. window.addEventListener('resize', function (evt) {
  7600.  
  7601. if (evt.isTrusted === true) {
  7602. //console.log(evt)
  7603. let tcw = ++resizeCount;
  7604. Promise.resolve(0).then(() => {
  7605. if (tcw !== resizeCount) return;
  7606. setTimeout(() => {
  7607. // avoid duplicate calling during resizing
  7608. if (tcw !== resizeCount) return;
  7609.  
  7610. resizeCount = 0;
  7611. manualResizeT();
  7612. dispatchCommentRowResize();
  7613. }, 160);
  7614. });
  7615. }
  7616.  
  7617. }, bubblePassive)
  7618.  
  7619.  
  7620. document.addEventListener("tyt-chat-popup", (evt) => {
  7621.  
  7622. let detail = (evt || 0).detail
  7623. if (!detail) return
  7624. const { popuped } = detail
  7625. if (typeof popuped !== 'boolean') return;
  7626.  
  7627. let ytdFlexyElm = es.ytdFlexy
  7628. if (!ytdFlexyElm) return
  7629.  
  7630. ytdFlexyElm.classList.toggle('tyt-chat-popup', popuped)
  7631. if (popuped === true) {
  7632. enableLivePopupCheck = true;
  7633. ytBtnSetTheater()
  7634. } else {
  7635. enableLivePopupCheck = false;
  7636. ytBtnCancelTheater()
  7637. }
  7638.  
  7639. })
  7640.  
  7641.  
  7642.  
  7643. let doingSelectionChange = false;
  7644. document.addEventListener("keyup", (evt) => {
  7645. if (!evt || !evt.target || !evt.key) return;
  7646. if (doingSelectionChange) {
  7647. if (!evt.shiftKey || evt.key.indexOf("Shift") == 0) {
  7648. doingSelectionChange = false;
  7649. }
  7650. }
  7651. })
  7652.  
  7653. document.addEventListener("keydown", (evt) => {
  7654. if (!evt || !evt.target || !evt.key) return;
  7655. if (evt.shiftKey && evt.key.indexOf("Arrow") == 0) {
  7656. try {
  7657. if (doingSelectionChange || !window.getSelection().isCollapsed) {
  7658. evt.stopImmediatePropagation();
  7659. evt.stopPropagation();
  7660. doingSelectionChange = true;
  7661. }
  7662.  
  7663. } catch (e) {
  7664.  
  7665. }
  7666. }
  7667. }, true);
  7668.  
  7669. let userActivation = false;
  7670.  
  7671. document.addEventListener('click', function () {
  7672. userActivation = true;
  7673.  
  7674.  
  7675. if (isMiniviewForStickyHeadEnabled && !isStickyHeaderEnabled && userActivation && videoInsected) {
  7676. setTimeout(delayedClickHandler, 80);
  7677. }
  7678.  
  7679. // if(isMiniviewForStickyHeadEnabled && !isStickyHeaderEnabled && userActivation){
  7680. // setTimeout(restorePIPforStickyHead, 80);
  7681. // }else if(!isMiniviewForStickyHeadEnabled && isStickyHeaderEnabled && userActivation && typeof IntersectionObserver == 'function'){
  7682. // setTimeout(enablePIPforStickyHead, 80);
  7683. // }
  7684. });
  7685.  
  7686. // new comment count fetch way
  7687. document.addEventListener('ytd-comments-data-changed', function (evt) {
  7688. const hasData = (evt.detail || 0).hasData;
  7689. if (hasData === false) {
  7690. // this is much effective to clear the counting text
  7691. emptyCommentSection();
  7692. }
  7693. innerDOMCommentsCountLoader(true);
  7694. checkAndMakeNewCommentFetch();
  7695. }, true);
  7696.  
  7697. document.addEventListener('ytd-comments-header-changed', function () {
  7698. const res = innerDOMCommentsCountLoader(true);
  7699. if (res && res.newFound === true && res.length === 1 && res[0].isLatest && res[0].isNew) {
  7700. if (renderDeferred.resolved && fetchCounts.new) {
  7701. // force refresh count dom
  7702. fetchCounts.fetched = false;
  7703. Q.comments_section_loaded = 0;
  7704. }
  7705. }
  7706. checkAndMakeNewCommentFetch();
  7707. }, true);
  7708.  
  7709. document.addEventListener("tabview-plugin-loaded", () => {
  7710.  
  7711. scriptletDeferred.resolve();
  7712.  
  7713. if (MINIVIEW_BROWSER_ENABLE) {
  7714. document.dispatchEvent(new CustomEvent("tabview-miniview-browser-enable"));
  7715. }
  7716.  
  7717. }, false)
  7718.  
  7719. if (isGMAvailable() && typeof GM_registerMenuCommand === 'function') {
  7720.  
  7721. let dialog = null;
  7722. function createDialog() {
  7723.  
  7724. const _themeProps_ = {
  7725. dialogBackgroundColor: '#f6f6f6',
  7726. dialogBackgroundColorDark: '#23252a',
  7727. backdropColor: '#b5b5b568',
  7728. textColor: '#111111',
  7729. textColorDark: '#f0f3f4',
  7730. zIndex: 60000,
  7731. fontSize: '10pt',
  7732. dialogMinWidth: '32px',
  7733. dialogMinHeight: '24px',
  7734. };
  7735.  
  7736. class VJSD extends VanillaJSDialog {
  7737.  
  7738. get themeProps() {
  7739. return _themeProps_
  7740. }
  7741.  
  7742. isDarkTheme() {
  7743. return document.documentElement.hasAttribute('dark');
  7744. }
  7745.  
  7746. onBeforeShow() {
  7747. const es = this.es;
  7748. if ('checkboxSelectionDisplay' in es) {
  7749. es.checkboxSelectionDisplay.textContent = '';
  7750. }
  7751. const setDefaultTabTick = (myDefaultTab) => {
  7752. for (const checkbox of document.getElementsByName('tabview-tab-default')) {
  7753. checkbox.checked = checkbox.value === myDefaultTab;
  7754. }
  7755. }
  7756. function getDefaultTabBtnSetting(store) {
  7757. if (!store) { } else {
  7758. let myDefaultTab = store[key_default_tab];
  7759. if (!myDefaultTab || typeof myDefaultTab !== 'string' || !/^\#[a-zA-Z\_\-\+]+$/.test(myDefaultTab)) {
  7760. } else {
  7761. if (document.querySelector(`.tab-btn[tyt-tab-content="${myDefaultTab}"]`)) return setDefaultTabTick(myDefaultTab);
  7762. }
  7763. }
  7764. setDefaultTabTick(null);
  7765. }
  7766. let store = getStore();
  7767. getDefaultTabBtnSetting(store);
  7768. }
  7769.  
  7770. onFirstCreation() {
  7771.  
  7772. const S = this.S; /* this is the global method */
  7773.  
  7774. /* on top of the setup function, override the icon widget on global method */
  7775. S.widgets.icon = (iconTag) => {
  7776. return S.ce('i', { className: 'vjsd-icon fa-solid fa-' + iconTag });
  7777. }
  7778.  
  7779. /* you might also overide `S.importCSS` by the use of Userscript Manager's import */
  7780. S.importCSS(
  7781. 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/fontawesome.min.css#sha512=SgaqKKxJDQ/tAUAAXzvxZz33rmn7leYDYfBP+YoMRSENhf3zJyx3SBASt/OfeQwBHA1nxMis7mM3EV/oYT6Fdw==',
  7782. // 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/brands.min.css',
  7783. 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/solid.min.css#sha512=yDUXOUWwbHH4ggxueDnC5vJv4tmfySpVdIcN1LksGZi8W8EVZv4uKGrQc0pVf66zS7LDhFJM7Zdeow1sw1/8Jw=='
  7784. );
  7785.  
  7786. /* load CSS files, etc - You might overide the `getTheme()` in VanillaJSDialog */
  7787. this.themeSetup();
  7788. }
  7789.  
  7790. /* init is called after setup function is called */
  7791. init() {
  7792. const S = this.S; /* this is the global method */
  7793.  
  7794. const es = this.es; /* this is a store for HTMLElements binded to this dialog */
  7795.  
  7796. es.dialog = S.ce('div', {
  7797. className: 'vjsd-dialog'
  7798. }, {
  7799. '__vjsd__': ''
  7800. });
  7801.  
  7802. es.dialog.append(
  7803. es.header = S.ce('div', {
  7804. className: 'vjsd-dialog-header vjsd-hflex'
  7805. }),
  7806. es.body = S.ce('div', {
  7807. className: 'vjsd-dialog-body vjsd-gap-2 vjsd-overscroll-none vjsd-vflex'
  7808. }),
  7809. es.footer = S.ce('div', {
  7810. className: 'vjsd-dialog-footer vjsd-hflex'
  7811. }),
  7812.  
  7813. );
  7814.  
  7815. es.header.append(
  7816. S.widgets.icon('circle-info', (a) => {
  7817.  
  7818. }),
  7819. S.widgets.title('Tabview Youtube - Change Default Tab', {
  7820. className: 'vjsd-flex-fill'
  7821. }),
  7822. S.widgets.buttonIcon('square-xmark', {
  7823. 'vjsd-clickable': '#dialogXmark'
  7824. })
  7825. );
  7826.  
  7827. const checkBoxChanged = () => {
  7828. let elmChoice1 = [...document.getElementsByName('tabview-tab-default')].filter(e => e.checked).map(e => e.value);
  7829. console.assert(elmChoice1.length <= 1);
  7830. es.checkboxSelectionDisplay.textContent = elmChoice1.length === 1 ? `The default tab will be set to ${elmChoice1[0]}` : `The default tab will be reset.`;
  7831. }
  7832.  
  7833. es.body.append(
  7834. S.widgets.labeledRadio('vjsd-checkbox1 vjsd-checkbox-tick', 'Info', (elmLabel, elmInput) => {
  7835. elmInput.name = 'tabview-tab-default';
  7836. elmInput.value = '#tab-info';
  7837. es.checkbox1 = elmInput;
  7838. elmInput.addEventListener('change', checkBoxChanged)
  7839. elmLabel.style.fontSize = '200%';
  7840. }),
  7841. S.widgets.labeledRadio('vjsd-checkbox1 vjsd-checkbox-tick', 'Comment', (elmLabel, elmInput) => {
  7842. elmInput.name = 'tabview-tab-default';
  7843. elmInput.value = '#tab-comments';
  7844. es.checkbox2 = elmInput;
  7845. elmInput.addEventListener('change', checkBoxChanged)
  7846. elmLabel.style.fontSize = '200%';
  7847. }),
  7848. S.widgets.labeledRadio('vjsd-checkbox1 vjsd-checkbox-tick', 'Video', (elmLabel, elmInput) => {
  7849. elmInput.name = 'tabview-tab-default';
  7850. elmInput.value = '#tab-videos';
  7851. es.checkbox3 = elmInput;
  7852. elmInput.addEventListener('change', checkBoxChanged)
  7853. elmLabel.style.fontSize = '200%';
  7854. }),
  7855. es.checkboxSelectionDisplay = S.ce('div', { className: 'vjsd-custom-widget' })
  7856. );
  7857.  
  7858. const onXMarkClicked = () => {
  7859. this.dismiss();
  7860. }
  7861.  
  7862. const onClearClicked = () => {
  7863. es.checkbox1.checked = false;
  7864. es.checkbox2.checked = false;
  7865. es.checkbox3.checked = false;
  7866. checkBoxChanged();
  7867. }
  7868.  
  7869. const onConfirmClicked = () => {
  7870. let myDefaultTab = null;
  7871. for (const checkbox of document.getElementsByName('tabview-tab-default')) {
  7872. if (checkbox.checked) myDefaultTab = checkbox.value;
  7873. }
  7874. myDefaultTab = myDefaultTab || null;
  7875. console.log(myDefaultTab)
  7876. setMyDefaultTab(myDefaultTab);
  7877. this.dismiss();
  7878. }
  7879.  
  7880. const onCancelClicked = () => {
  7881. this.dismiss();
  7882. }
  7883.  
  7884. es.footer.append(
  7885. es.clearButton = S.widgets.button('Clear', {
  7886. 'vjsd-clickable': '#clear'
  7887. }),
  7888. S.widgets.space(),
  7889. S.widgets.button('Cancel', {
  7890. 'vjsd-clickable': '#cancel'
  7891. }),
  7892. S.widgets.button('Confirm', {
  7893. 'vjsd-clickable': '#confirm'
  7894. }),
  7895. )
  7896.  
  7897. this.clickable('#cancel', onCancelClicked)
  7898. this.clickable('#clear', onClearClicked)
  7899. this.clickable('#confirm', onConfirmClicked)
  7900. this.clickable('#dialogXmark', onXMarkClicked);
  7901.  
  7902. this.backdrop = 'dismiss';
  7903. document.body.appendChild(es.dialog)
  7904. }
  7905. }
  7906.  
  7907. VJSD.setup1();
  7908. return new VJSD();
  7909. }
  7910.  
  7911. GM_registerMenuCommand("Change Default Tab", function () {
  7912. dialog = dialog || createDialog();
  7913. dialog.show();
  7914. });
  7915.  
  7916. /*
  7917. GM_registerMenuCommand("Default Tab: NULL", function () {
  7918. setMyDefaultTab(null);
  7919. });
  7920. GM_registerMenuCommand("Default Tab: Info", function () {
  7921. setMyDefaultTab("#tab-info");
  7922. });
  7923. GM_registerMenuCommand("Default Tab: Comments", function () {
  7924. setMyDefaultTab("#tab-comments");
  7925. });
  7926. GM_registerMenuCommand("Default Tab: Video", function () {
  7927. setMyDefaultTab("#tab-videos");
  7928. });
  7929. */
  7930. }
  7931.  
  7932.  
  7933. handleDOMAppear('#tabview-controller', () => { }); // dummy
  7934. document.documentElement.appendChild(document.createElement('tabview-controller')).id = 'tabview-tabs-hide-controller';
  7935. document.documentElement.appendChild(document.createElement('tabview-controller')).id = 'tabview-default-tab-controller';
  7936.  
  7937. document.documentElement.setAttribute('plugin-tabview-youtube', `${scriptVersionForExternal}`)
  7938. if (document.documentElement.getAttribute('tabview-unwrapjs')) {
  7939. document.dispatchEvent(new CustomEvent("tabview-plugin-loaded"))
  7940. }
  7941.  
  7942.  
  7943. // function nestedObjectFlatten(prefix, obj) {
  7944. // let ret = {};
  7945. // let _prefix = prefix ? `${prefix}.` : '';
  7946. // let isObject = (obj && typeof obj == 'object' && obj.constructor.name == 'Object');
  7947. // let isArray = (obj && typeof obj == 'object' && obj.constructor.name == 'Array');
  7948. // const f = (k, v) => {
  7949. // let isObject = (v && typeof v == 'object' && v.constructor.name == 'Object');
  7950. // let isArray = (v && typeof v == 'object' && v.constructor.name == 'Array');
  7951. // if (isObject || isArray) {
  7952. // let r = nestedObjectFlatten(k, v)
  7953. // for (const w in r) {
  7954. // ret[`${_prefix}${w}`] = r[w];
  7955. // }
  7956. // } else {
  7957. // ret[`${_prefix}${k}`] = v;
  7958. // }
  7959. // }
  7960. // if (isObject) {
  7961. // for (const k in obj) {
  7962. // let v = obj[k];
  7963. // f(k, v);
  7964. // }
  7965. // } else if (isArray) {
  7966. // let idx = 0;
  7967. // for (const v of obj) {
  7968. // let k = `[${idx}]`;
  7969. // f(k, v);
  7970. // idx++;
  7971. // }
  7972. // }
  7973. // return ret;
  7974. // }
  7975.  
  7976. /*
  7977.  
  7978. for(const p of document.querySelectorAll('ytd-watch-flexy *')){ let m = p.data; if(!m)continue; console.log(m)}
  7979.  
  7980. function objec
  7981.  
  7982. */
  7983.  
  7984.  
  7985. //Object.keys($0).filter(key=>!(key in $0.constructor.prototype))
  7986.  
  7987. //Object.getOwnPropertyNames(window).filter(k=>k.startsWith('HTML'))
  7988. //Object.getOwnPropertyNames(window).filter(k=>k.startsWith('HTML')).filter(k=>$0 instanceof window[k])
  7989.  
  7990.  
  7991. /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */
  7992.  
  7993. /*
  7994. fix bug for comment section - version 1.8.7
  7995. This issue is the bug in browser's rendering
  7996. I guess, this is due to the lines clamp with display:-webkit-box
  7997. use stupid coding to let it re-render when its content become visible
  7998. /*
  7999.  
  8000. ytd-expander[should-use-number-of-lines][collapsed] > #content.ytd-expander {
  8001. color: var(--yt-spec-text-primary);
  8002. display: -webkit-box;
  8003. overflow: hidden;
  8004. max-height: none;
  8005. -webkit-box-orient: vertical;
  8006. -webkit-line-clamp: var(--ytd-expander-max-lines, 4);
  8007. }
  8008.  
  8009. // v1.8.36 imposed a effective solution for fixing this bug
  8010.  
  8011. */
  8012.  
  8013. /* --------------------------- browser's bug in -webkit-box ----------------------------------------- */
  8014.  
  8015.  
  8016. /**
  8017. *
  8018.  
  8019.  
  8020. f.initChildrenObserver=function(){var a=this;this.observer=new MutationObserver(function(){return a.childrenChanged()});
  8021. this.observer.observe(this.$.content,{subtree:!0,childList:!0,attributes:!0});this.childrenChanged()};
  8022. 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()})}))};
  8023.  
  8024.  
  8025. f.onIronResize=function(){this.recomputeOnResize&&this.childrenChanged()};
  8026.  
  8027.  
  8028. onButtonClick_:function(){this.fire("yt-close-upsell-dialog")},
  8029. 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};
  8030. n(xI,L);f=xI.prototype;f.alwaysToggleableChanged=function(){this.alwaysToggleable&&(this.canToggle=!0)};
  8031.  
  8032.  
  8033. f.calculateCanCollapse=function(){this.canToggle=this.shouldUseNumberOfLines?this.alwaysToggleable||this.$.content.offsetHeight<this.$.content.scrollHeight:this.alwaysToggleable||this.$.content.scrollHeight>this.collapsedHeight};
  8034. f.detachObserver=function(){this.observer&&this.observer.disconnect()};
  8035.  
  8036. *
  8037. *
  8038. *
  8039. */
  8040.  
  8041.  
  8042. })({
  8043. requestAnimationFrame: (typeof webkitRequestAnimationFrame === 'function' ? webkitRequestAnimationFrame : requestAnimationFrame),
  8044. cancelAnimationFrame: (typeof webkitRequestAnimationFrame === 'function' ? webkitRequestAnimationFrame : requestAnimationFrame)
  8045. });
  8046. // console.timeEnd("Tabview Youtube Init Script")
  8047.  
  8048. // https://github.com/cyfung1031/Tabview-Youtube/raw/main/js/content.js
  8049.  
  8050. }
  8051.  
  8052.  
  8053. ;!(function $$() {
  8054. 'use strict';
  8055.  
  8056. if(document.documentElement==null) return window.requestAnimationFrame($$)
  8057.  
  8058. const cssTxt = GM_getResourceText("contentCSS");
  8059.  
  8060. function addStyle (styleText) {
  8061. const styleNode = document.createElement('style');
  8062. styleNode.textContent = styleText;
  8063. document.documentElement.appendChild(styleNode);
  8064. return styleNode;
  8065. }
  8066.  
  8067. addStyle (cssTxt);
  8068.  
  8069. main();
  8070.  
  8071. })();