YouTube: Floating Chat Window on Fullscreen

To make floating chat window on fullscreen

当前为 2024-03-04 提交的版本,查看 最新版本

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