YouTube: Floating Chat Window on Fullscreen

To make floating chat window on fullscreen

目前为 2023-08-19 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube: Floating Chat Window on Fullscreen
  3. // @namespace UserScript
  4. // @match https://www.youtube.com/*
  5. // @exclude /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
  6. // @version 0.3.4
  7. // @license MIT License
  8. // @author CY Fung
  9. // @description To make floating chat window on fullscreen
  10. // @require https://greasyfork.org/scripts/465819-api-for-customelements-in-youtube/code/API%20for%20CustomElements%20in%20YouTube.js?version=1215161
  11. // @run-at document-start
  12. // @grant none
  13. // @unwrap
  14. // @allFrames true
  15. // @inject-into page
  16. // ==/UserScript==
  17.  
  18.  
  19. ((__CONTEXT__) => {
  20.  
  21.  
  22. let c27 = 0;
  23. let mouseDownActiveElement = null;
  24. document.addEventListener('click', function (evt) {
  25.  
  26. if(!document.fullscreenElement) return;
  27.  
  28. let byPass = false;
  29.  
  30. if (Date.now() - c27 < 40) byPass = true;
  31. else {
  32. return;
  33. }
  34.  
  35.  
  36. if (evt.target && evt.target.id === 'chat' && evt.target.nodeName.toLowerCase() === 'ytd-live-chat-frame') byPass = false;
  37. else if (evt.target && evt.target.nodeName.toLowerCase() === 'iframe') byPass = false;
  38.  
  39. if (byPass) {
  40.  
  41. evt.stopPropagation();
  42. evt.stopImmediatePropagation();
  43. c27 = Date.now();
  44. }
  45. c27 = 0;
  46.  
  47. }, { capture: true, passive: false });
  48. document.addEventListener('mousedown', function (evt) {
  49.  
  50. if(!document.fullscreenElement) return;
  51. let byPass = false;
  52. const activeElement = document.activeElement || 0;
  53. mouseDownActiveElement = null;
  54. if (activeElement.nodeName === 'IFRAME') {
  55. if (activeElement.matches('[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) iframe')) {
  56. byPass = true;
  57. mouseDownActiveElement = activeElement;
  58. }
  59. }
  60.  
  61. if (evt.target && evt.target.id === 'chat' && evt.target.nodeName.toLowerCase() === 'ytd-live-chat-frame') byPass = false;
  62. else if (evt.target && evt.target.nodeName.toLowerCase() === 'iframe') byPass = false;
  63.  
  64.  
  65. if (byPass) {
  66.  
  67. evt.stopPropagation();
  68. evt.stopImmediatePropagation();
  69. c27 = Date.now();
  70. } else {
  71. mouseDownActiveElement = null;
  72. }
  73. c27 = 0;
  74.  
  75.  
  76.  
  77. }, { capture: true, passive: false });
  78. document.addEventListener('mouseup', function (evt) {
  79.  
  80. if(!document.fullscreenElement) return;
  81. let mde = mouseDownActiveElement;
  82. mouseDownActiveElement = null;
  83.  
  84. if (!mde) return;
  85.  
  86. let byPass = false;
  87. const activeElement = mde || 0;
  88. if (activeElement.nodeName === 'IFRAME') {
  89. if (activeElement.matches('[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) iframe')) {
  90. byPass = true;
  91. }
  92. }
  93.  
  94. // if(Date.now()-c27 < 40 ) byPass = true;
  95. c27 = 0;
  96.  
  97. if (evt.target && evt.target.id === 'chat' && evt.target.nodeName.toLowerCase() === 'ytd-live-chat-frame') byPass = false;
  98. else if (evt.target && evt.target.nodeName.toLowerCase() === 'iframe') byPass = false;
  99.  
  100. if (byPass) {
  101.  
  102. evt.stopPropagation();
  103. evt.stopImmediatePropagation();
  104. c27 = Date.now();
  105. }
  106.  
  107.  
  108. }, { capture: true, passive: false });
  109.  
  110.  
  111. const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : (this instanceof Window ? this : window);
  112.  
  113. const hkey_script = 'vdnvorrwsksy';
  114. if (win[hkey_script]) throw new Error('Duplicated Userscript Calling'); // avoid duplicated scripting
  115. win[hkey_script] = true;
  116.  
  117. /** @type {globalThis.PromiseConstructor} */
  118. const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.
  119.  
  120.  
  121. const createStyleTextForTopWin = () => `
  122.  
  123.  
  124. [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) {
  125. position:fixed !important;
  126. top: var(--f3-top, 5px) !important;
  127. left: var(--f3-left, calc(60vw + 100px)) !important;
  128. height: var(--f3-h, 60vh) !important;
  129. width: var(--f3-w, 320px) !important;
  130. display:flex !important;
  131. flex-direction: column !important;
  132. padding: 4px;
  133. cursor: all-scroll;
  134. z-index:9999;
  135. box-sizing: border-box !important;
  136. margin:0 !important;
  137. opacity: var(--floating-window-opacity, 1.0) !important;
  138. background: transparent;
  139. background-color: rgba(0, 0, 0, 0.5);
  140. transition: background-color 300ms;
  141. }
  142.  
  143. [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]):hover {
  144. background-color: rgba(0, 0, 0, 0.85);
  145.  
  146. }
  147.  
  148. .no-floating[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) {
  149.  
  150. top: -300vh !important;
  151. left: -300vh !important;
  152. }
  153.  
  154.  
  155. [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) #show-hide-button[class]{
  156. flex-grow: 0;
  157. flex-shrink:0;
  158. position:static;
  159. cursor: all-scroll;
  160. }
  161. [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) #show-hide-button[class] *[class]{
  162. cursor: inherit;
  163. }
  164. [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) iframe[class]{
  165. flex-grow: 100;
  166. flex-shrink:0;
  167. height: 0;
  168. position:static;
  169. }
  170.  
  171.  
  172. html{
  173. --fc7-handle-color: #0cb8da;
  174. }
  175. html[dark]{
  176. --fc7-handle-color: #0c74e4;
  177. }
  178.  
  179. :fullscreen .resize-handle {
  180.  
  181. position: absolute !important;
  182. top: 0;
  183. left: 0;
  184. bottom: 0;
  185. background: transparent;
  186. right: 0;
  187. z-index: 999 !important;
  188. border-radius: inherit !important;
  189. box-sizing: border-box !important;
  190. pointer-events:none !important;
  191. visibility: collapse;
  192. border: 4px solid transparent;
  193. border-color: transparent;
  194. transition: border-color 300ms;
  195. }
  196.  
  197. [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]):hover .resize-handle {
  198.  
  199. visibility: visible;
  200.  
  201. border-color: var(--fc7-handle-color);
  202. }
  203.  
  204. [moving] {
  205. cursor: all-scroll;
  206. --pointer-events:initial;
  207. }
  208.  
  209. [moving] body {
  210. --pointer-events:none;
  211. }
  212.  
  213. [moving] ytd-live-chat-frame#chat{
  214.  
  215. --pointer-events:initial;
  216. }
  217.  
  218.  
  219. [moving] ytd-live-chat-frame#chat iframe {
  220.  
  221. --pointer-events:none;
  222. }
  223.  
  224.  
  225. [moving="move"] ytd-live-chat-frame#chat {
  226. background-color: var(--yt-spec-general-background-a);
  227.  
  228. }
  229.  
  230.  
  231. [moving="move"] ytd-live-chat-frame#chat iframe {
  232.  
  233. visibility: collapse;
  234. }
  235.  
  236. [moving] * {
  237. pointer-events:var(--pointer-events) !important;
  238.  
  239. }
  240. [moving] *, [moving] [class] {
  241. user-select: none !important;
  242. }
  243.  
  244. :fullscreen tyt-iframe-popup-btn{
  245. display: none !important;
  246. }
  247.  
  248. [moving] tyt-iframe-popup-btn{
  249. display: none !important;
  250. }
  251.  
  252. [floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed]) #show-hide-button.ytd-live-chat-frame>ytd-toggle-button-renderer.ytd-live-chat-frame {
  253.  
  254. background: transparent;
  255.  
  256. }
  257.  
  258.  
  259.  
  260. `;
  261.  
  262. const createStyleTextForIframe = () => `
  263.  
  264.  
  265. .youtube-floating-chat-iframe yt-live-chat-docked-message#docked-messages.style-scope.yt-live-chat-item-list-renderer {
  266. margin-top:var(--fc7-top-banner-mt);
  267. transition: margin-top 180ms;
  268. }
  269.  
  270. .youtube-floating-chat-iframe yt-live-chat-banner-manager#live-chat-banner.style-scope.yt-live-chat-item-list-renderer {
  271. margin-top:var(--fc7-top-banner-mt);
  272. transition: margin-top 180ms;
  273. }
  274.  
  275. .youtube-floating-chat-iframe #action-panel.style-scope.yt-live-chat-renderer {
  276. position: fixed;
  277. top: 50%;
  278. transform: translateY(-50%);
  279. }
  280.  
  281. .youtube-floating-chat-iframe yt-live-chat-header-renderer.style-scope.yt-live-chat-renderer {
  282. position: relative;
  283. z-index: 8;
  284. background: rgb(0,0,0);
  285. visibility: var(--fc7-panel-visibility);
  286. }
  287.  
  288.  
  289.  
  290. .youtube-floating-chat-iframe #chat-messages.style-scope.yt-live-chat-renderer.iron-selected > #contents.style-scope.yt-live-chat-renderer{
  291. position: fixed;
  292. z-index: 4;
  293. top: 0;
  294. bottom: 0;
  295. left: 0;
  296. right: 0;
  297. }
  298.  
  299. .youtube-floating-chat-iframe #right-arrow-container.yt-live-chat-ticker-renderer,
  300. .youtube-floating-chat-iframe #left-arrow-container.yt-live-chat-ticker-renderer
  301.  
  302. {
  303. background: transparent;
  304. }
  305.  
  306. .youtube-floating-chat-iframe yt-live-chat-renderer.yt-live-chat-app {
  307.  
  308. --yt-live-chat-background-color: transparent;
  309. --yt-live-chat-action-panel-background-color: rgba(0, 0, 0, 0.08);
  310. --yt-live-chat-header-background-color: rgba(0, 0, 0, 0.18);
  311. --yt-spec-static-overlay-background-medium: rgba(0, 0, 0, 0.08);
  312. --yt-live-chat-banner-gradient-scrim: transparent;
  313.  
  314. }
  315.  
  316. .youtube-floating-chat-iframe{
  317. --fc7-top-banner-mt: 0px;
  318. --fc7-banner-opacity: 0.86;
  319. --fc7-system-message-opacity: 0.66;
  320. --fc7-system-message-opacity2: 0.66;
  321. --fc7-panel-display: none;
  322. --fc7-panel-visibility: collapse;
  323. --fc7-panel-position: absolute;
  324. }
  325. .youtube-floating-chat-iframe:focus-within,
  326. html:focus-within,
  327. body:focus-within,
  328. yt-live-chat-app:focus-within,
  329. yt-live-chat-renderer.yt-live-chat-app:focus-within
  330. {
  331. --fc7-top-banner-mt: 56px;
  332. --fc7-banner-opacity: 1.0;
  333. --fc7-system-message-opacity: 1.0;
  334. --fc7-system-message-opacity2: 1.00;
  335. --fc7-panel-display: invalid;
  336. --fc7-panel-visibility: invalid;
  337. --fc7-panel-position: absolute;
  338. }
  339. .youtube-floating-chat-iframe yt-live-chat-app:hover {
  340. --fc7-top-banner-mt: 56px;
  341. --fc7-banner-opacity: 1.0;
  342. --fc7-system-message-opacity: 1.0;
  343. --fc7-system-message-opacity2: 1.00;
  344. --fc7-panel-display: invalid;
  345. --fc7-panel-visibility: invalid;
  346. --fc7-panel-position: absolute;
  347. }
  348.  
  349.  
  350. .youtube-floating-chat-iframe yt-live-chat-renderer.yt-live-chat-app #visible-banners > yt-live-chat-banner-renderer {
  351. opacity: var(--fc7-banner-opacity) !important;
  352. }
  353.  
  354.  
  355. .youtube-floating-chat-iframe yt-live-chat-renderer.yt-live-chat-app yt-live-chat-viewer-engagement-message-renderer {
  356. opacity: var(--fc7-system-message-opacity) !important;
  357. }
  358.  
  359.  
  360. .youtube-floating-chat-iframe yt-live-chat-app yt-live-chat-renderer.yt-live-chat-app yt-live-chat-message-input-renderer {
  361. visibility: var(--fc7-panel-visibility);
  362. position: var(--fc7-panel-position);
  363.  
  364. transform: translateY(-100%);
  365. left: 0;
  366. right: 0;
  367. opacity: 1;
  368. background: rgba(0,0,0,0.86);
  369. }
  370.  
  371.  
  372. /* hide message with input panel hidden */
  373. .youtube-floating-chat-iframe yt-live-chat-app > tp-yt-iron-dropdown.yt-live-chat-app yt-tooltip-renderer[slot="dropdown-content"][position-type="OPEN_POPUP_POSITION_TOP"].yt-live-chat-app {
  374. visibility: var(--fc7-panel-visibility);
  375. }
  376.  
  377.  
  378.  
  379.  
  380. [dark].youtube-floating-chat-iframe yt-live-chat-app ::-webkit-scrollbar-track,
  381. [dark].youtube-floating-chat-iframe yt-live-chat-kevlar-container ::-webkit-scrollbar-track {
  382. background-color: var(--ytd-searchbox-legacy-button-color);
  383. }
  384.  
  385. .youtube-floating-chat-iframe yt-live-chat-app ::-webkit-scrollbar-track,
  386. .youtube-floating-chat-iframe yt-live-chat-kevlar-container ::-webkit-scrollbar-track {
  387. background-color: #fcfcfc;
  388. }
  389.  
  390.  
  391. [dark].youtube-floating-chat-iframe yt-live-chat-app ::-webkit-scrollbar-thumb,
  392. [dark].youtube-floating-chat-iframe yt-live-chat-kevlar-container ::-webkit-scrollbar-thumb{
  393.  
  394. background-color: var(--ytd-searchbox-legacy-button-color);
  395. border: 2px solid var(--ytd-searchbox-legacy-button-color);
  396.  
  397. }
  398.  
  399.  
  400.  
  401. .youtube-floating-chat-iframe yt-live-chat-renderer[has-action-panel-renderer] #action-panel.yt-live-chat-renderer {
  402. --yt-live-chat-action-panel-gradient-scrim: transparent;
  403. }
  404.  
  405.  
  406. .youtube-floating-chat-iframe yt-live-chat-renderer[has-action-panel-renderer] #action-panel.yt-live-chat-renderer yt-live-chat-action-panel-renderer {
  407. opacity: var(--fc7-system-message-opacity2) !important;
  408. }
  409.  
  410. `;
  411.  
  412. const { isIframe, isTopFrame } = (() => {
  413.  
  414. let isIframe = false, isTopFrame = false;
  415. try {
  416. isIframe = window.document !== top.document
  417. } catch (e) { }
  418.  
  419. try {
  420. isTopFrame = window.document === top.document
  421. } catch (e) { }
  422.  
  423. return { isIframe, isTopFrame };
  424.  
  425. })();
  426.  
  427. if (isIframe ^ isTopFrame) { } else return;
  428.  
  429. if (isTopFrame) {
  430.  
  431.  
  432.  
  433.  
  434. const addCSS = (createStyleText) => {
  435. let text = createStyleText();
  436. let style = document.createElement('style');
  437. style.id = 'rvZ0t';
  438. style.textContent = text;
  439. document.head.appendChild(style);
  440. }
  441.  
  442.  
  443.  
  444. const cleanContext = async (win) => {
  445. const waitFn = requestAnimationFrame; // shall have been binded to window
  446. try {
  447. let mx = 16; // MAX TRIAL
  448. const frameId = 'vanillajs-iframe-v1'
  449. let frame = document.getElementById(frameId);
  450. let removeIframeFn = null;
  451. if (!frame) {
  452. frame = document.createElement('iframe');
  453. frame.id = 'vanillajs-iframe-v1';
  454. frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe
  455. let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting)
  456. n.appendChild(frame);
  457. while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine
  458. const root = document.documentElement;
  459. root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL
  460. removeIframeFn = (setTimeout) => {
  461. const removeIframeOnDocumentReady = (e) => {
  462. e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  463. win = null;
  464. setTimeout(() => {
  465. n.remove();
  466. n = null;
  467. }, 200);
  468. }
  469. if (document.readyState !== 'loading') {
  470. removeIframeOnDocumentReady();
  471. } else {
  472. win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  473. }
  474. }
  475. }
  476. while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn);
  477. const fc = frame.contentWindow;
  478. if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL
  479. const { requestAnimationFrame, setTimeout } = fc;
  480. const res = { requestAnimationFrame, setTimeout };
  481. for (let k in res) res[k] = res[k].bind(win); // necessary
  482. if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn);
  483. return res;
  484. } catch (e) {
  485. console.warn(e);
  486. return null;
  487. }
  488. };
  489.  
  490. cleanContext(win).then(__CONTEXT__ => {
  491. if (!__CONTEXT__) return null;
  492.  
  493. const { requestAnimationFrame } = __CONTEXT__;
  494.  
  495. let chatWindowWR = null;
  496. let showHideButtonWR = null;
  497.  
  498. /* globals WeakRef:false */
  499.  
  500. /** @type {(o: Object | null) => WeakRef | null} */
  501. const mWeakRef = typeof WeakRef === 'function' ? (o => o ? new WeakRef(o) : null) : (o => o || null); // typeof InvalidVar == 'undefined'
  502.  
  503. /** @type {(wr: Object | null) => Object | null} */
  504. const kRef = (wr => (wr && wr.deref) ? wr.deref() : wr);
  505.  
  506. let startX;
  507. let startY;
  508. let startWidth;
  509. let startHeight;
  510.  
  511.  
  512. let edge = 0;
  513.  
  514.  
  515. let initialLeft;
  516. let initialTop;
  517.  
  518. let stopResize;
  519. let stopMove;
  520.  
  521.  
  522. const getXY = (e) => {
  523. let rect = e.target.getBoundingClientRect();
  524. let x = e.clientX - rect.left; //x position within the element.
  525. let y = e.clientY - rect.top; //y position within the element.
  526. return { x, y }
  527. }
  528.  
  529. let beforeEvent = null;
  530.  
  531. function resizeWindow(e) {
  532.  
  533.  
  534. const chatWindow = kRef(chatWindowWR);
  535. if (chatWindow) {
  536.  
  537. const mEdge = edge;
  538.  
  539. if (mEdge == 4 || mEdge == 1) {
  540.  
  541. } else if (mEdge == 8 || mEdge == 16) {
  542. } else {
  543. return;
  544. }
  545.  
  546.  
  547. Promise.resolve(chatWindow).then(chatWindow => {
  548. let rect;
  549.  
  550. if (mEdge == 4 || mEdge == 1 || mEdge == 16) {
  551.  
  552. let newWidth = startWidth + (startX - e.pageX);
  553.  
  554. let newLeft = initialLeft + startWidth - newWidth;
  555. chatWindow.style.setProperty('--f3-w', newWidth + "px");
  556. chatWindow.style.setProperty('--f3-left', newLeft + "px");
  557.  
  558.  
  559.  
  560. let newHeight = startHeight + (startY - e.pageY);
  561.  
  562. let newTop = initialTop + startHeight - newHeight;
  563. chatWindow.style.setProperty('--f3-h', newHeight + "px");
  564. chatWindow.style.setProperty('--f3-top', newTop + "px");
  565.  
  566. rect = {
  567. x: newLeft,
  568. y: newTop,
  569. w: newWidth,
  570.  
  571. h: newHeight,
  572.  
  573.  
  574. };
  575.  
  576.  
  577.  
  578. } else if (mEdge == 8) {
  579.  
  580. let newWidth = startWidth + e.pageX - startX;
  581. let newHeight = startHeight + e.pageY - startY;
  582.  
  583. chatWindow.style.setProperty('--f3-w', newWidth + "px");
  584. chatWindow.style.setProperty('--f3-h', newHeight + "px");
  585.  
  586.  
  587. rect = {
  588. x: initialLeft,
  589. y: initialTop,
  590. w: newWidth,
  591.  
  592. h: newHeight,
  593.  
  594.  
  595. };
  596.  
  597. }
  598.  
  599.  
  600.  
  601. updateOpacity(chatWindow, rect, screen);
  602.  
  603. })
  604.  
  605.  
  606. e.stopImmediatePropagation();
  607. e.stopPropagation();
  608. e.preventDefault();
  609.  
  610.  
  611. }
  612.  
  613. }
  614.  
  615. let isMoved = false;
  616.  
  617. function moveWindow(e) {
  618.  
  619.  
  620.  
  621. const chatWindow = kRef(chatWindowWR);
  622. if (chatWindow) {
  623.  
  624. Promise.resolve(chatWindow).then(chatWindow => {
  625.  
  626.  
  627. let newX = initialLeft + e.pageX - startX;
  628. let newY = initialTop + e.pageY - startY;
  629.  
  630. if (Math.abs(e.pageX - startX) > 10 || Math.abs(e.pageY - startY) > 10) isMoved = true;
  631.  
  632. chatWindow.style.setProperty('--f3-left', newX + "px");
  633. chatWindow.style.setProperty('--f3-top', newY + "px");
  634.  
  635.  
  636.  
  637. updateOpacity(chatWindow, {
  638. x: newX,
  639. y: newY,
  640. w: startWidth,
  641.  
  642. h: startHeight,
  643.  
  644.  
  645. }, screen);
  646.  
  647. });
  648.  
  649.  
  650.  
  651. e.stopImmediatePropagation();
  652. e.stopPropagation();
  653. e.preventDefault();
  654.  
  655. }
  656. }
  657.  
  658.  
  659.  
  660.  
  661. function initializeResize(e) {
  662.  
  663. if (!document.fullscreenElement) return;
  664.  
  665. if (!document.querySelector('[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed])')) return;
  666.  
  667. if (e.target.id !== 'chat') return;
  668.  
  669.  
  670.  
  671. const { x, y } = getXY(e);
  672. edge = 0;
  673. if (x < 16 && y < 16) { edge = 16; }
  674. else if (x < 16) edge = 4;
  675. else if (y < 16) edge = 1;
  676. else edge = 8;
  677.  
  678. if (edge <= 0) return;
  679.  
  680. startX = e.pageX;
  681. startY = e.pageY;
  682.  
  683. const chatWindow = kRef(chatWindowWR);
  684. if (chatWindow) {
  685.  
  686. Promise.resolve(chatWindow).then(chatWindow => {
  687.  
  688. let rect = chatWindow.getBoundingClientRect();
  689. initialLeft = rect.x;
  690. initialTop = rect.y;
  691.  
  692.  
  693.  
  694. startWidth = rect.width;
  695. startHeight = rect.height;
  696.  
  697.  
  698. chatWindow.style.setProperty('--f3-left', initialLeft + "px");
  699. chatWindow.style.setProperty('--f3-top', initialTop + "px");
  700. chatWindow.style.setProperty('--f3-w', startWidth + "px");
  701. chatWindow.style.setProperty('--f3-h', startHeight + "px");
  702.  
  703. });
  704.  
  705. }
  706.  
  707.  
  708.  
  709.  
  710. document.documentElement.setAttribute('moving', 'resize');
  711.  
  712. document.documentElement.removeEventListener("mousemove", resizeWindow, false);
  713. document.documentElement.removeEventListener("mousemove", moveWindow, false);
  714. document.documentElement.removeEventListener("mouseup", stopResize, false);
  715. document.documentElement.removeEventListener("mouseup", stopMove, false);
  716.  
  717. isMoved = false;
  718. document.documentElement.addEventListener("mousemove", resizeWindow);
  719. document.documentElement.addEventListener("mouseup", stopResize);
  720.  
  721. }
  722.  
  723.  
  724. let updateOpacityRid = 0;
  725.  
  726. function updateOpacity(chatWindow, rect, screen) {
  727.  
  728. let tid = ++updateOpacityRid;
  729.  
  730. requestAnimationFrame(() => {
  731.  
  732.  
  733. if (tid !== updateOpacityRid) return;
  734.  
  735. let { x, y, w, h } = rect;
  736. let [left, top, right, bottom] = [x, y, x + w, y + h];
  737.  
  738.  
  739. let opacityW = (Math.min(right, screen.width) - Math.max(0, left)) / w;
  740. let opacityH = (Math.min(bottom, screen.height) - Math.max(0, top)) / h;
  741.  
  742. let opacity = Math.min(opacityW, opacityH);
  743.  
  744. chatWindow.style.setProperty('--floating-window-opacity', Math.round(opacity * 100 * 5, 0) / 5 / 100);
  745.  
  746.  
  747. })
  748.  
  749.  
  750.  
  751.  
  752.  
  753. }
  754.  
  755. function initializeMove(e) {
  756.  
  757. if (!document.fullscreenElement) return;
  758. if (!document.querySelector('[floating-chat-window]:fullscreen ytd-live-chat-frame#chat:not([collapsed])')) return;
  759.  
  760.  
  761.  
  762. const chatWindow = kRef(chatWindowWR);
  763.  
  764.  
  765.  
  766. startX = e.pageX;
  767. startY = e.pageY;
  768.  
  769.  
  770. if (chatWindow) {
  771.  
  772. Promise.resolve(chatWindow).then(chatWindow => {
  773.  
  774.  
  775. let rect = chatWindow.getBoundingClientRect();
  776. initialLeft = rect.x;
  777. initialTop = rect.y;
  778.  
  779.  
  780.  
  781. startWidth = rect.width;
  782. startHeight = rect.height;
  783.  
  784.  
  785. chatWindow.style.setProperty('--f3-left', initialLeft + "px");
  786. chatWindow.style.setProperty('--f3-top', initialTop + "px");
  787. chatWindow.style.setProperty('--f3-w', startWidth + "px");
  788. chatWindow.style.setProperty('--f3-h', startHeight + "px");
  789.  
  790. })
  791.  
  792.  
  793. }
  794.  
  795.  
  796.  
  797. document.documentElement.setAttribute('moving', 'move');
  798.  
  799. document.documentElement.removeEventListener("mousemove", resizeWindow, false);
  800. document.documentElement.removeEventListener("mousemove", moveWindow, false);
  801. document.documentElement.removeEventListener("mouseup", stopResize, false);
  802. document.documentElement.removeEventListener("mouseup", stopMove, false);
  803. isMoved = false;
  804.  
  805. document.documentElement.addEventListener("mousemove", moveWindow, false);
  806. document.documentElement.addEventListener("mouseup", stopMove, false);
  807.  
  808. e.stopImmediatePropagation();
  809. e.stopPropagation();
  810. e.preventDefault();
  811.  
  812. beforeEvent = e;
  813.  
  814. }
  815.  
  816.  
  817. function checkClick(beforeEvent, currentEvent) {
  818.  
  819. const d = currentEvent.timeStamp - beforeEvent.timeStamp;
  820. if (d < 300 && d > 30 && !isMoved) {
  821.  
  822. document.documentElement.classList.add('no-floating');
  823.  
  824. }
  825.  
  826. }
  827.  
  828.  
  829. stopResize = (e) => {
  830.  
  831. document.documentElement.removeEventListener("mousemove", resizeWindow, false);
  832. document.documentElement.removeEventListener("mousemove", moveWindow, false);
  833. document.documentElement.removeEventListener("mouseup", stopResize, false);
  834. document.documentElement.removeEventListener("mouseup", stopMove, false);
  835.  
  836. document.documentElement.removeAttribute('moving');
  837.  
  838. e.stopImmediatePropagation();
  839. e.stopPropagation();
  840.  
  841. }
  842.  
  843. stopMove = (e) => {
  844.  
  845. document.documentElement.removeEventListener("mousemove", resizeWindow, false);
  846. document.documentElement.removeEventListener("mousemove", moveWindow, false);
  847. document.documentElement.removeEventListener("mouseup", stopResize, false);
  848. document.documentElement.removeEventListener("mouseup", stopMove, false);
  849.  
  850. document.documentElement.removeAttribute('moving');
  851. beforeEvent && checkClick(beforeEvent, e);
  852. beforeEvent = null;
  853.  
  854. e.stopImmediatePropagation();
  855. e.stopPropagation();
  856.  
  857. }
  858.  
  859.  
  860. function reset() {
  861.  
  862. document.documentElement.removeAttribute('moving');
  863. document.documentElement.removeEventListener("mousemove", resizeWindow, false);
  864. document.documentElement.removeEventListener("mousemove", moveWindow, false);
  865. document.documentElement.removeEventListener("mouseup", stopResize, false);
  866. document.documentElement.removeEventListener("mouseup", stopMove, false);
  867.  
  868.  
  869. startX = 0;
  870. startY = 0;
  871. startWidth = 0;
  872. startHeight = 0;
  873.  
  874.  
  875. edge = 0;
  876.  
  877.  
  878. initialLeft = 0;
  879. initialTop = 0;
  880.  
  881. beforeEvent = null;
  882.  
  883.  
  884. }
  885.  
  886. function iframeLoaded() {
  887.  
  888. }
  889.  
  890. function iframeFullscreenChanged() {
  891. const iframeDoc = this;
  892.  
  893.  
  894. if (!document.fullscreenElement) {
  895. iframeDoc.documentElement.classList.remove('youtube-floating-chat-iframe');
  896. } else {
  897. iframeDoc.documentElement.classList.add('youtube-floating-chat-iframe');
  898.  
  899. }
  900.  
  901.  
  902. }
  903.  
  904. let iframeFullscreenChangedBinded = null;
  905.  
  906.  
  907.  
  908. function onMessage(evt) {
  909. if (evt.data === hkey_script) {
  910.  
  911. const iframeWin = evt.source;
  912. if (!iframeWin) return;
  913. const iframeDoc = iframeWin.document;
  914.  
  915. function onReady() {
  916.  
  917. iframeDoc.head.appendChild(document.createElement('style')).textContent = createStyleTextForIframe();
  918.  
  919. if (iframeFullscreenChangedBinded) document.removeEventListener('fullscreenchange', iframeFullscreenChangedBinded, false);
  920. iframeFullscreenChangedBinded = iframeFullscreenChanged.bind(iframeDoc);
  921. document.addEventListener('fullscreenchange', iframeFullscreenChangedBinded, false);
  922.  
  923. iframeFullscreenChangedBinded();
  924.  
  925. }
  926.  
  927. Promise.resolve().then(() => {
  928.  
  929. if (iframeDoc.readyState !== 'loading') {
  930. onReady();
  931. } else {
  932. iframeWin.addEventListener("DOMContentLoaded", onReady, false);
  933. }
  934.  
  935. });
  936.  
  937.  
  938. }
  939.  
  940. }
  941.  
  942.  
  943. function setChat(chat) {
  944.  
  945. let resizeHandle = HTMLElement.prototype.querySelector.call(chat, '.resize-handle')
  946. if (resizeHandle) return;
  947.  
  948.  
  949.  
  950. let cw = (() => {
  951. try {
  952. const { head, body } = chat.$.chatframe.contentWindow.document;
  953. return { head, body }
  954.  
  955. } catch (e) { return null; }
  956. })();
  957.  
  958. if (!cw) return;
  959.  
  960. window.removeEventListener('message', onMessage, false);
  961. window.addEventListener('message', onMessage, false);
  962.  
  963.  
  964.  
  965. let script = document.getElementById('rvZ0t') || (document.evaluate("//div[contains(text(), 'userscript-control[enable-customized-floating-window]')]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null) || 0).singleNodeValue;
  966. if (!script) addCSS(createStyleTextForTopWin);
  967.  
  968. if (!document.documentElement.hasAttribute('floating-chat-window')) document.documentElement.setAttribute('floating-chat-window', '');
  969.  
  970.  
  971. chat.setAttribute('allowtransparency', 'true');
  972.  
  973.  
  974.  
  975. resizeHandle = document.createElement("div");
  976. resizeHandle.className = "resize-handle";
  977. chat.appendChild(resizeHandle);
  978. resizeHandle = null;
  979.  
  980. let chatWindow;
  981. let showHideButton;
  982.  
  983. chatWindow = kRef(chatWindowWR);
  984. showHideButton = kRef(showHideButtonWR);
  985.  
  986.  
  987.  
  988. if (chatWindow) chatWindow.removeEventListener("mousedown", initializeResize, false);
  989. if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false);
  990.  
  991.  
  992. chatWindow = chat;
  993. showHideButton = HTMLElement.prototype.querySelector.call(chat, '#show-hide-button');
  994. chatWindowWR = mWeakRef(chat)
  995. showHideButtonWR = mWeakRef(showHideButton);
  996.  
  997.  
  998.  
  999. chatWindow.addEventListener("mousedown", initializeResize, false);
  1000. showHideButton.addEventListener("mousedown", initializeMove, false);
  1001.  
  1002. reset();
  1003.  
  1004. }
  1005.  
  1006.  
  1007. const fullscreenchangePageFn = () => {
  1008. if (!document.fullscreenElement) {
  1009. document.documentElement.classList.remove('no-floating')
  1010. }
  1011. };
  1012.  
  1013. function noChat(chat) {
  1014.  
  1015. let chatWindow;
  1016. let showHideButton;
  1017.  
  1018. chatWindow = kRef(chatWindowWR);
  1019. showHideButton = kRef(showHideButtonWR);
  1020.  
  1021.  
  1022.  
  1023. if (chatWindow) chatWindow.removeEventListener("mousedown", initializeResize, false);
  1024. if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false);
  1025.  
  1026.  
  1027. let resizeHandle = HTMLElement.prototype.querySelector.call(chat, '.resize-handle')
  1028. if (resizeHandle) {
  1029. resizeHandle.remove();
  1030. }
  1031.  
  1032. chat.removeEventListener("mousedown", initializeResize, false);
  1033.  
  1034.  
  1035. showHideButton = HTMLElement.prototype.querySelector.call(chat, '#show-hide-button');
  1036.  
  1037. if (showHideButton) showHideButton.removeEventListener("mousedown", initializeMove, false);
  1038.  
  1039.  
  1040. reset();
  1041. }
  1042.  
  1043.  
  1044. document.removeEventListener('fullscreenchange', fullscreenchangePageFn, false);
  1045. document.addEventListener('fullscreenchange', fullscreenchangePageFn, false);
  1046. fullscreenchangePageFn();
  1047.  
  1048. customYtElements.whenRegistered('ytd-live-chat-frame', (proto) => {
  1049.  
  1050.  
  1051. proto.attached = ((attached) => (function () { Promise.resolve(this).then(setChat); return attached.apply(this, arguments) }))(proto.attached);
  1052.  
  1053. proto.detached = ((detached) => (function () { Promise.resolve(this).then(noChat); return detached.apply(this, arguments) }))(proto.detached);
  1054.  
  1055. let chat = document.querySelector('ytd-live-chat-frame');
  1056. if (chat) Promise.resolve(chat).then(setChat);
  1057.  
  1058. })
  1059.  
  1060.  
  1061. });
  1062.  
  1063.  
  1064. } else if (isIframe && top === parent) {
  1065.  
  1066.  
  1067.  
  1068. top.postMessage(hkey_script, `${location.protocol}//${location.hostname}`);
  1069.  
  1070.  
  1071.  
  1072.  
  1073. }
  1074.  
  1075.  
  1076.  
  1077.  
  1078.  
  1079. })({ requestAnimationFrame });