您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Make theater mode fill the entire page view with a hidden navbar and auto theater mode (Support new UI)
当前为
// ==UserScript== // @name YouTube Fullpage Theater // @version 2.0.6 // @description Make theater mode fill the entire page view with a hidden navbar and auto theater mode (Support new UI) // @run-at document-body // @inject-into content // @match https://www.youtube.com/* // @exclude https://*.youtube.com/live_chat* // @exclude https://*.youtube.com/embed* // @exclude https://*.youtube.com/tv* // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com // @grant GM.getValue // @grant GM.setValue // @grant unsafeWindow // @author Fznhq // @namespace https://github.com/fznhq // @homepageURL https://github.com/fznhq/userscript-collection // @license GNU GPLv3 // ==/UserScript== // Icons provided by https://iconmonstr.com/ (async function () { "use strict"; const body = document.body; let theater = false; /** * @param {Event} event */ function dispatchEvent(event) { const context = window.dispatchEvent ? window : unsafeWindow; context.dispatchEvent(event); } /** * Options must be changed via popup menu, * just press (v) to open the menu */ const options = { auto_theater_mode: { icon: `{"svg":{"fill-rule":"evenodd","clip-rule":"evenodd"},"path":{"d":"M24 22H0V2h24zm-7-1V6H1v15zm1 0h5V3H1v2h17zm-6-6h-1v-3l-7 7-1-1 7-7H7v-1h5z"}}`, label: "Auto Open Theater", value: false, onUpdate() { if (this.value && !theater) toggleTheater(); }, }, hide_scrollbar: { icon: `{"path":{"d":"M14 12a2 2 0 1 1-4 0 2 2 0 0 1 4 0m-3-4h2V6h4l-5-6-5 6h4zm2 8h-2v2H7l5 6 5-6h-4z"}}`, label: "Theater Hide Scrollbar", value: true, onUpdate() { if (theater) { setHtmlAttr(attr.no_scroll, this.value); dispatchEvent(new Event("resize")); } }, }, close_theater_with_esc: { icon: `{"svg":{"clip-rule":"evenodd","fill-rule":"evenodd","stroke-linejoin":"round","stroke-miterlimit":2},"path":{"d":"M21 4a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1zm-16.5.5h15v15h-15zm7.5 6.43 2.7-2.72a.75.75 0 0 1 1.07 1.06L13.05 12l2.73 2.73a.75.75 0 1 1-1.06 1.06l-2.73-2.73-2.73 2.73a.75.75 0 0 1-1.06-1.06L10.93 12 8.21 9.28A.75.75 0 0 1 9.27 8.2z","fill-rule":"nonzero"}}`, label: "Close Theater With Esc", value: true, }, hide_card: { icon: `{"path":{"d":"M22 6v16H6V6zm2-2H4v20h20zM0 0v20h2V2h18V0zm14 11.22c-3.15 0-5 2.6-5 2.6s2.02 2.95 5 2.95c3.23 0 5-2.95 5-2.95s-1.79-2.6-5-2.6m.05 4.72a1.94 1.94 0 1 1 0-3.88 1.94 1.94 0 0 1 0 3.88m1.1-1.94a1.1 1.1 0 1 1-2.2 0l.03-.21a.68.68 0 0 0 .87-.86l.2-.02c.6 0 1.1.49 1.1 1.09"}}`, label: "Hide Card Outside Theater Mode", value: false, onUpdate() { if (!theater) setHtmlAttr(attr.hide_card, this.value); }, }, show_header_near: { icon: `{"path":{"d":"m5 4 10 9H9l-4 5zM3 0v24l7-9h11z"}}`, label: "Show Header When Mouse is Near", value: false, }, }; /** * @param {string} name * @param {boolean} value * @returns {boolean} */ function saveOption(name, value) { GM.setValue(name, value); return (options[name].value = value); } /** * @param {string} name * @param {object} attributes * @param {Array} append * @returns {SVGElement} */ function createNS(name, attributes = {}, append = []) { const el = document.createElementNS("http://www.w3.org/2000/svg", name); for (const k in attributes) el.setAttributeNS(null, k, attributes[k]); return el.append(...append), el; } for (const name in options) { const saved_option = await GM.getValue(name); const icon = JSON.parse(options[name].icon); if (saved_option === undefined) { saveOption(name, options[name].value); } else { options[name].value = saved_option; } options[name].icon = createNS("svg", icon.svg, [ createNS("path", icon.path), ]); } /** * @param {string} className * @param {Array} append * @returns {HTMLDivElement} */ function createDiv(className, append = []) { const el = document.createElement("div"); el.className = "ytp-menuitem" + (className ? "-" + className : ""); return el.append(...append), el; } const popup = { show: false, menu: (() => { const menu = createDiv(" ytc-menu ytp-panel-menu"); const container = createDiv(" ytc-popup-container", [menu]); for (const name in options) { const option = options[name]; const item = createDiv("", [ createDiv("icon", [option.icon]), createDiv("label", [option.label]), createDiv("content", [createDiv("toggle-checkbox")]), ]); menu.append(item); item.setAttribute("aria-checked", option.value); item.addEventListener("click", () => { const newValue = saveOption(name, !option.value); item.setAttribute("aria-checked", newValue); if (option.onUpdate) option.onUpdate(); }); } window.addEventListener("click", (ev) => { if (popup.show && !menu.contains(ev.target)) { popup.show = !!container.remove(); } }); return container; })(), }; window.addEventListener("keydown", (ev) => { const isV = ev.key.toLowerCase() == "v" || ev.code == "KeyV"; if ( (isV && !ev.ctrlKey && !isActiveEditable()) || (ev.code == "Escape" && popup.show) ) { popup.show = popup.show ? !!popup.menu.remove() : !body.append(popup.menu); } }); /** * @param {string} query * @returns {() => HTMLElement | null} */ function $(query) { let element = null; return () => element || (element = document.querySelector(query)); } const style = document.head.appendChild(document.createElement("style")); style.textContent = /*css*/ ` html[no-scroll], html[no-scroll] body { scrollbar-width: none !important; } html[no-scroll]::-webkit-scrollbar, html[no-scroll] body::-webkit-scrollbar, html[hide-card] ytd-player .ytp-paid-content-overlay, html[hide-card] ytd-player .iv-branding, html[hide-card] ytd-player .ytp-ce-element, html[hide-card] ytd-player .ytp-chrome-top, html[hide-card] ytd-player .ytp-suggested-action { display: none !important; } html[theater][masthead-hidden] #masthead-container { transform: translateY(-100%) !important; } html[theater][masthead-hidden] ytd-watch-flexy[fixed-panels] #chat { top: 0 !important; } html[theater] #page-manager { margin: 0 !important; } html[theater] #content #page-manager ytd-watch-flexy #full-bleed-container, html[theater] #content #page-manager ytd-watch-grid #player-full-bleed-container { height: 100vh; min-height: auto; max-height: none; } .ytc-popup-container { position: fixed; inset: 0; z-index: 9000; background: rgba(0, 0, 0, .5); display: flex; align-items: center; justify-content: center; } .ytc-menu.ytp-panel-menu { background: #000; width: 400px; font-size: 120%; padding: 10px; fill: #eee; } `; const attrId = "-" + Date.now().toString(36); const attr = { video_id: "video-id", role: "role", theater: "theater", fullscreen: "fullscreen", hidden_header: "masthead-hidden", no_scroll: "no-scroll", hide_card: "hide-card", }; for (const key in attr) { style.textContent = style.textContent.replaceAll( "[" + attr[key] + "]", "[" + attr[key] + attrId + "]" ); } const element = { watch: $("ytd-watch-flexy, ytd-watch-grid"), // ytd-watch-grid == trash search: $("input#search"), }; const keyToggleTheater = new KeyboardEvent("keydown", { key: "t", code: "KeyT", which: 84, keyCode: 84, bubbles: true, cancelable: true, }); /** * @param {string} attr * @param {boolean} state */ function setHtmlAttr(attr, state) { document.documentElement.toggleAttribute(attr + attrId, state); } /** * @param {MutationCallback} callback * @param {Node} target * @param {MutationObserverInit | undefined} options */ function observer(callback, target, options) { const mutation = new MutationObserver(callback); mutation.observe(target, options || { subtree: true, childList: true }); } /** * @returns {boolean} */ function isTheater() { const watch = element.watch(); return ( watch.getAttribute(attr.role) == "main" && watch.hasAttribute(attr.theater) && !watch.hasAttribute(attr.fullscreen) ); } /** * @returns {boolean} */ function isActiveEditable() { /** @type {HTMLElement} */ const active = document.activeElement; return ( active.tagName == "TEXTAREA" || active.tagName == "INPUT" || active.isContentEditable ); } /** * @param {boolean} state * @param {number} timeout * @returns {number | boolean} */ function toggleHeader(state, timeout) { const toggle = () => { if (state || document.activeElement != element.search()) { const scroll = !options.show_header_near.value && window.scrollY; setHtmlAttr(attr.hidden_header, !(state || scroll)); } }; return theater && setTimeout(toggle, timeout || 1); } let showHeaderTimerId = 0; /** * @param {MouseEvent} ev */ function mouseShowHeader(ev) { if (options.show_header_near.value && theater) { const state = ev.clientY < 200; if (state) { clearTimeout(showHeaderTimerId); showHeaderTimerId = toggleHeader(false, 1500); } toggleHeader(!popup.show && state); } } function toggleTheater() { document.dispatchEvent(keyToggleTheater); } /** * @param {KeyboardEvent} ev */ function onEscapePress(ev) { if (ev.code != "Escape" || !theater || popup.show) return; if (options.close_theater_with_esc.value) { toggleTheater(); } else { const input = element.search(); if (document.activeElement != input) input.focus(); else input.blur(); } } /** * @param {MutationRecord[]} mutations */ function autoOpenTheater(mutations) { const attrs = [attr.role, attr.video_id]; const watch = element.watch(); if ( options.auto_theater_mode.value && !watch.hasAttribute(attr.theater) && !watch.hasAttribute(attr.fullscreen) && mutations.some((m) => attrs.includes(m.attributeName)) ) { setTimeout(toggleTheater, 1); } } function registerEventListener() { element.search().addEventListener("focus", () => toggleHeader(true)); element.search().addEventListener("blur", () => toggleHeader(false)); window.addEventListener("scroll", () => { if (!options.show_header_near.value) toggleHeader(); }); window.addEventListener("mousemove", mouseShowHeader); window.addEventListener("keydown", onEscapePress, true); } function applyTheaterMode() { const state = isTheater(); if (theater == state) return; theater = state; setHtmlAttr(attr.theater, state); setHtmlAttr(attr.hidden_header, state); setHtmlAttr(attr.no_scroll, state && options.hide_scrollbar.value); setHtmlAttr(attr.hide_card, state || options.hide_card.value); } observer((_, observe) => { const watch = element.watch(); if (!watch) return; observer( (mutations) => { applyTheaterMode(); autoOpenTheater(mutations); }, watch, { attributes: true } ); registerEventListener(); observe.disconnect(); }, body); })();