Telegram 網頁全螢幕

為網頁Telegram添加視頻播放網頁全螢幕功能

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name:zh-CN  Telegram 网页全屏
// @name:zh-TW  Telegram 網頁全螢幕
// @name:ja     Telegram ウェブ全画面
// @name        Telegram in-page fullscree
// @namespace    http://tampermonkey.net/
// @version      1.3
// @icon         https://img.icons8.com/color/452/telegram-app--v5.png
// @description:zh-CN  为网页Telegram添加视频播放网页全屏功能
// @description:zh-TW  為網頁Telegram添加視頻播放網頁全螢幕功能
// @description:ja     ウェブ版Telegramに動画再生の全画面機能を追加
// @description  Add browser viewport full video playback feature for WebTelegram
// @author       zolay-poi
// @match        https://web.telegram.org/*
// @grant        none
// @run-at       document-idle
// @license MIT
// ==/UserScript==

(function () {
  "use strict";

  const i18n = {
    en: { enter: "Web Fullscreen", exit: "Exit Web Fullscreen" },
    "zh-CN": { enter: "网页全屏", exit: "退出网页全屏" },
    "zh-TW": { enter: "網頁全螢幕", exit: "退出網頁全螢幕" },
    ja: { enter: "ウェブ全画面", exit: "ウェブ全画面を終了" },
  };

  const language = navigator.language || navigator.userLanguage || "en";
  const baseLang = language.split("-")[0];
  const resolvedLang =
    (i18n[language] && language) ||
    (i18n[baseLang] && baseLang) ||
    (baseLang === "zh" ? "zh-CN" : "en");

  const h = (key) => i18n[resolvedLang][key];

  const doms = {
    btn: null,
    span: null,
  };

  const btnCls = "my-btn-dom-fullscreen";
  const fullCls = "tg-inner-fs";
  const styleId = "tg-inner-fs-style-tag";

  const CONTAINER_SELECTORS = [
    "body div.media-viewer-whole.active div.right-controls",
    "div.media-viewer-whole.active .right-controls",
    ".media-viewer-whole .right-controls",
    ".right-controls",
  ];

  const TARGET_SELECTORS = [
    "div.media-viewer-whole.active .media-viewer-mover.center.active",
    ".media-viewer-whole.active .media-viewer-mover.center.active",
    ".media-viewer-whole.active .media-viewer-mover.center",
    ".media-viewer-whole .media-viewer-mover.center.active",
    ".media-viewer-mover.center.active",
  ];

  function findTarget() {
    for (const sel of TARGET_SELECTORS) {
      const el = document.querySelector(sel);
      if (el) return el;
    }
    return null;
  }

  function findContainer() {
    for (const sel of CONTAINER_SELECTORS) {
      const el = document.querySelector(sel);
      if (el) return el;
    }
    return null;
  }

  const spanIcons = {
    enter: "\ue969",
    exit: "\ue948",
  };

  function ensureStyle() {
    if (document.getElementById(styleId)) return;
    const style = document.createElement("style");
    style.id = styleId;
    style.textContent = `
      .${fullCls} .media-viewer-topbar,
      .${fullCls} .media-viewer-caption {
        display: none !important;
      }
      .${fullCls} .media-viewer-mover.center {
        width: 100% !important;
        height: 100% !important;
        max-height: 100% !important;
      }
    `;
    document.head.appendChild(style);
  }

  function ensureDoms() {
    if (!doms.btn || !(doms.btn instanceof HTMLElement)) {
      const btn = document.createElement("div");
      btn.title = h("enter");
      btn.className = btnCls + " btn-icon default__button";

      const span = document.createElement("span");
      span.className = "tgico button-icon";
      span.innerText = spanIcons.enter;
      btn.appendChild(span);

      btn.addEventListener("click", function () {
        const target = findTarget();
        if (!target) return;

        const isFull = btn.dataset.fullscreen === "1";
        if (isFull) {
          document.body.classList.remove(fullCls);
          btn.dataset.fullscreen = "0";
          doms.span && (doms.span.innerText = spanIcons.enter);
          btn.title = h("enter");
        } else {
          document.body.classList.add(fullCls);
          btn.dataset.fullscreen = "1";
          doms.span && (doms.span.innerText = spanIcons.exit);
          btn.title = h("exit");
        }
      });

      doms.btn = btn;
      doms.span = span;
    } else if (!doms.span || !(doms.span instanceof HTMLElement)) {
      const existed = doms.btn.querySelector("span.tgico.button-icon");
      if (existed) doms.span = existed;
    }
    return doms;
  }

  function insertBeforeLastChild(container, node) {
    const last = container.lastElementChild;
    if (last) {
      container.insertBefore(node, last);
    } else {
      container.appendChild(node);
    }
  }

  function tryInsert() {
    const container = findContainer();
    if (!container) return false;
    ensureStyle();
    const existed = container.querySelector("." + btnCls);
    const { btn } = ensureDoms();
    if (existed || container.contains(btn)) return true;
    insertBeforeLastChild(container, btn);
    return true;
  }

  let pending = false;
  function scheduleTryInsert() {
    if (pending) return;
    pending = true;
    (window.requestAnimationFrame || window.setTimeout)(() => {
      pending = false;
      tryInsert();
    }, 16);
  }

  tryInsert();

  const observer = new MutationObserver((mutations) => {
    for (const m of mutations) {
      if (m.type === "childList" && (m.addedNodes.length || m.removedNodes.length)) {
        scheduleTryInsert();
        break;
      }
      if (m.type === "attributes" && m.attributeName === "class") {
        const t = m.target;
        if (t && t.nodeType === 1) {
          const el = t;
          if (el.matches && (el.matches(".media-viewer-whole") || el.matches(".right-controls"))) {
            scheduleTryInsert();
            break;
          }
        }
      }
    }
  });
  observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"] });

  window.addEventListener("beforeunload", () => observer.disconnect());
})();