YouTube Popup Window

Enhances YouTube with a popup window feature.

  1. // ==UserScript==
  2. // @name YouTube Popup Window
  3. // @name:zh-TW YouTube Popup Window
  4. // @name:ja YouTube Popup Window
  5. // @namespace http://tampermonkey.net/
  6. // @version 0.2.2
  7. // @description Enhances YouTube with a popup window feature.
  8. // @description:zh-TW 透過彈出視窗功能增強YouTube。
  9. // @description:ja YouTubeをポップアップウィンドウ機能で強化します。
  10. // @author CY Fung
  11. // @license MIT
  12. // @match https://www.youtube.com/*
  13. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  14. // @require https://cdn.jsdelivr.net/npm/@violentmonkey/shortcut@1.4.1
  15. // @grant GM_registerMenuCommand
  16. // @allFrames true
  17. // ==/UserScript==
  18.  
  19. (async function () {
  20. 'use strict';
  21. const shortcutKey = 'ctrlcmd-alt-keya';
  22.  
  23. const winName = 'x4tGg';
  24. const styleName = 'rCbM3';
  25.  
  26. const observablePromise = (proc, timeoutPromise) => {
  27. let promise = null;
  28. return {
  29. obtain() {
  30. if (!promise) {
  31. promise = new Promise(resolve => {
  32. let mo = null;
  33. const f = () => {
  34. let t = proc();
  35. if (t) {
  36. mo.disconnect();
  37. mo.takeRecords();
  38. mo = null;
  39. resolve(t);
  40. }
  41. }
  42. mo = new MutationObserver(f);
  43. mo.observe(document, { subtree: true, childList: true })
  44. f();
  45. timeoutPromise && timeoutPromise.then(() => {
  46. resolve(null)
  47. });
  48. });
  49. }
  50. return promise
  51. }
  52. }
  53. }
  54.  
  55. function getVideo() {
  56. return document.querySelector('.video-stream.html5-main-video');
  57. }
  58.  
  59. function registerKeyboard(o) {
  60.  
  61. const { openPopup } = o;
  62.  
  63. const { KeyboardService } = VM.shortcut;
  64.  
  65. const service = new KeyboardService();
  66.  
  67. service.setContext('activeOnInput', false);
  68.  
  69. async function updateActiveOnInput() {
  70. const elm = document.activeElement;
  71. service.setContext('activeOnInput', elm instanceof HTMLInputElement || elm instanceof HTMLTextAreaElement);
  72. }
  73.  
  74. document.addEventListener('focus', (e) => {
  75. updateActiveOnInput();
  76. }, true);
  77.  
  78. document.addEventListener('blur', (e) => {
  79. updateActiveOnInput();
  80. }, true);
  81.  
  82. service.register(shortcutKey, openPopup, {
  83. condition: '!activeOnInput',
  84. });
  85. service.enable();
  86.  
  87. }
  88.  
  89. if (window.name === winName && window === top) {
  90.  
  91. if (!document.head) await observablePromise(() => document.head).obtain();
  92.  
  93. let style = document.createElement('style');
  94. style.id = styleName;
  95.  
  96. style.textContent = `
  97. *[class][id].style-scope.ytd-watch-flexy {
  98. min-width: unset !important;
  99. min-height: unset !important;
  100. }
  101. `
  102.  
  103. document.head.appendChild(style);
  104.  
  105. } else if (window !== top && top.name === winName) {
  106.  
  107. if (!document.head) await observablePromise(() => document.head).obtain();
  108.  
  109. let style = document.createElement('style');
  110. style.id = styleName;
  111.  
  112. style.textContent = `
  113. * {
  114. min-width: unset !important;
  115. min-height: unset !important;
  116. }
  117. `
  118.  
  119. document.head.appendChild(style);
  120.  
  121.  
  122. } else if (window === top) {
  123.  
  124. function openPopup() {
  125.  
  126. const currentUrl = window.location.href;
  127. const ytdAppElm = document.querySelector('ytd-app');
  128. if (!ytdAppElm) return;
  129. const rect = ytdAppElm.getBoundingClientRect();
  130. const w = rect.width;
  131. const h = rect.height;
  132. const popupOptions = `toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=${w},height=${h}`;
  133.  
  134. let video = getVideo();
  135.  
  136. if (video) {
  137. video.pause();
  138. }
  139. const win = window.open(currentUrl, '', popupOptions);
  140. win.name = winName;
  141.  
  142. const b = document.querySelector('#x4tGg');
  143. if (b) b.remove();
  144.  
  145. }
  146.  
  147. GM_registerMenuCommand('Open Popup Window', function () {
  148.  
  149. if (document.querySelector('#x4tGg')) return;
  150.  
  151. const div = document.body.appendChild(document.createElement('div'));
  152. div.id = 'x4tGg';
  153. div.textContent = 'Click to Open Popup'
  154.  
  155. Object.assign(div.style, {
  156. 'position': 'fixed',
  157. 'left': '50vw',
  158. 'top': '50vh',
  159. 'padding': '28px',
  160. 'backgroundColor': 'rgb(56, 94, 131)',
  161. 'color': '#fff',
  162. 'borderRadius': '16px',
  163. 'fontSize': '18pt',
  164. 'zIndex': '9999',
  165. 'transform': 'translate(-50%, -50%)'
  166. })
  167.  
  168. div.onclick = function () {
  169. openPopup();
  170. }
  171.  
  172. });
  173.  
  174. registerKeyboard({ openPopup });
  175.  
  176. }
  177. // Your code here...
  178.  
  179. })();