Nextcloud Mail — Darkmode in mail.

Forces dark background/text inside the message HTML iframe on Nextcloud Mail

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Nextcloud Mail — Darkmode in mail.
// @namespace    https://greasyfork.org
// @author       r-hiland
// @version      1.2
// @description  Forces dark background/text inside the message HTML iframe on Nextcloud Mail
// @match        https://nextcloud.example.com/apps/mail/* // YOU NEED TO MODIFY THIS TO YOUR DOMAIN.
// @run-at       document-idle
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  const BG = "#171717";
  const FG = "#ebebeb";
  const FG_SOFT = "#d8d8d8";
  const BORDER = "#2a2a2a";
  const MUTED = "#b3b3b3";
  const LINK = "#8ab4f8";

  const STYLE_ID = "tm-nextcloud-mail-dark-style";

  const css = `
    :root, html, body {
      color-scheme: dark !important;
      background: ${BG} !important;
      color: ${FG} !important;
    }

    body, p, div, section, article, main, footer, header, aside,
    span, strong, em, small, blockquote, figure,
    ul, ol, li, dl, dt, dd {
      background: transparent !important;
      color: ${FG} !important;
    }

    table, thead, tbody, tfoot, tr, th, td {
      background: transparent !important;
      color: ${FG} !important;
      border-color: ${BORDER} !important;
    }
    table[bgcolor], td[bgcolor], tr[bgcolor] { background-color: ${BG} !important; }

    hr { border: 0 !important; border-top: 1px solid ${BORDER} !important; }

    a, a * { color: ${LINK} !important; }
    a:visited { opacity: 0.95 !important; }

    small, .muted, .subtle, [style*="color:#999"], [style*="color: #999"] {
      color: ${MUTED} !important;
    }

    input, textarea, select, button {
      background: #1f1f1f !important;
      color: ${FG} !important;
      border: 1px solid ${BORDER} !important;
    }

    /* Kill hard-coded white backgrounds */
    [style*="background:#fff"], [style*="background: #fff"],
    [style*="background:#ffffff"], [style*="background: #ffffff"],
    [bgcolor="white"], [bgcolor="#ffffff"], [bgcolor="#fff"] {
      background: ${BG} !important;
    }

    /* Kill hard-coded dark text */
    [style*="color:#000"], [style*="color: #000"],
    [style*="color:#111"], [style*="color: #111"],
    [style*="color:#222"], [style*="color: #222"] {
      color: ${FG} !important;
    }

    img, svg, video, canvas { filter: none !important; }

    pre, code, kbd, samp {
      background: #1f1f1f !important;
      color: ${FG_SOFT} !important;
    }
  `;

  function injectIntoFrame(doc) {
    try {
      if (!doc || doc.getElementById(STYLE_ID)) return;

      // Early paint guard
      doc.documentElement.style.background = BG;
      doc.documentElement.style.color = FG;

      const s = doc.createElement("style");
      s.id = STYLE_ID;
      s.textContent = css;
      (doc.head || doc.documentElement).appendChild(s);
    } catch (_) {
      /* ignore cross-origin or timing issues */
    }
  }

  function tryWireFrame(iframe) {
    if (!iframe || iframe.__tmDarkWired) return;
    iframe.__tmDarkWired = true;

    // Inject when it loads (or if already loaded)
    const onload = () => injectIntoFrame(iframe.contentDocument);
    iframe.addEventListener("load", onload, { once: false });

    // If it's already there and same-origin:
    if (iframe.contentDocument && iframe.contentDocument.readyState !== "loading") {
      injectIntoFrame(iframe.contentDocument);
    }
  }

  // Initial scan
  function scan() {
    document.querySelectorAll("iframe.message-frame").forEach(tryWireFrame);
  }
  scan();

  // Observe just the message container for newly inserted/replicated iframes
  const container = document.querySelector("#message-container") || document.body;
  const obs = new MutationObserver((muts) => {
    let needsScan = false;
    for (const m of muts) {
      if (m.addedNodes && m.addedNodes.length) {
        for (const n of m.addedNodes) {
          if (n.nodeType === 1 && (n.matches?.("iframe.message-frame") || n.querySelector?.("iframe.message-frame"))) {
            needsScan = true;
            break;
          }
        }
      }
      if (needsScan) break;
    }
    if (needsScan) {
      // throttle to next frame to avoid storms
      requestAnimationFrame(scan);
    }
  });
  obs.observe(container, { childList: true, subtree: true });
})();