Greasy Fork 还支持 简体中文。

YouTube: Floating Chat Window on Fullscreen

To make floating chat window on fullscreen

目前為 2023-12-09 提交的版本,檢視 最新版本

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