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