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