- // ==UserScript==
- // @name ProtonMail Dark Theme for Email Content
- // @namespace http://tampermonkey.net/
- // @version 1.5
- // @description Adds dark theme to ProtonMail email reading and composition areas
- // @match https://mail.proton.me/*
- // @match https://proton.me/mail/*
- // @grant GM_addStyle
- // @grant unsafeWindow
- // @license WTFPL
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- // Main window CSS
- GM_addStyle(`
- /* Composer window and borders */
- .composer, .composer .composer-content--rich-edition {
- background-color: #1a1a1a !important;
- border-color: #333 !important;
- }
-
- .composer-inner {
- background-color: #1a1a1a !important;
- border-color: #333 !important;
- }
-
- .composer-title {
- background-color: #1a1a1a !important;
- border-color: #333 !important;
- }
-
- /* Message containers */
- .message-container,
- .message-content,
- .message-content-wrapper,
- .scroll-horizontal-shadow {
- background-color: #1a1a1a !important;
- border-color: #333 !important;
- }
-
- /* General containers and borders */
- .rounded-lg,
- .border,
- [class*="border-"] {
- border-color: #333 !important;
- }
-
- /* Message header and footer areas */
- .message-header-wrapper,
- .message-footer-wrapper {
- background-color: #1a1a1a !important;
- }
-
- /* Toolbar and button backgrounds */
- .editor-toolbar,
- .composer-toolbar {
- background-color: #262626 !important;
- border-color: #333 !important;
- }
- `);
-
- // CSS to inject into iframes
- const darkThemeCSS = `
- body {
- background-color: #1a1a1a !important;
- color: #e0e0e0 !important;
- }
-
- p, div, span, h1, h2, h3, h4, h5, h6, td, th, li {
- color: #e0e0e0 !important;
- background-color: #1a1a1a !important;
- }
-
- [style*="background-color"],
- [bgcolor] {
- background-color: #1a1a1a !important;
- }
-
- [style*="color"],
- [color] {
- color: #e0e0e0 !important;
- }
-
- blockquote {
- border-left-color: #404040 !important;
- background-color: #262626 !important;
- color: #e0e0e0 !important;
- }
-
- a {
- color: #66b3ff !important;
- }
-
- /* Table backgrounds */
- table, tr, td, th {
- background-color: #1a1a1a !important;
- border-color: #333 !important;
- }
-
- /* Force color on common elements that might have inline styles */
- [style*="color: rgb(0, 0, 0)"],
- [style*="color: black"],
- [style*="color:#000"],
- [style*="color: #000"],
- font[color] {
- color: #e0e0e0 !important;
- }
-
- .composer {
- background-color: #1a1a1a !important;
- border-color: #333 !important;
- }
-
- .composer-inner {
- background-color: #1a1a1a !important;
- border-color: #333 !important;
- }
-
- .composer-title {
- background-color: #1a1a1a !important;
- border-color: #333 !important;
- }
-
- /* Message containers */
- .message-container,
- .message-content-wrapper,
- .scroll-horizontal-shadow {
- background-color: #1a1a1a !important;
- border-color: #333 !important;
- }
-
- /* General containers and borders */
- .rounded-lg,
- .border,
- [class*="border-"] {
- border-color: #333 !important;
- }
-
- /* Message header and footer areas */
- .message-header-wrapper,
- .message-footer-wrapper {
- background-color: #1a1a1a !important;
- }
-
- /* Toolbar and button backgrounds */
- .editor-toolbar,
- .composer-toolbar {
- background-color: #262626 !important;
- border-color: #333 !important;
- }
- `;
-
- // Function to inject styles into an iframe
- function injectIframeStyles(iframe) {
- try {
- const inject = () => {
- if (!iframe.contentDocument) return;
-
- // Create and inject stylesheet if it doesn't exist
- if (!iframe.contentDocument.querySelector('#dark-theme-style')) {
- const style = iframe.contentDocument.createElement('style');
- style.id = 'dark-theme-style';
- style.textContent = darkThemeCSS;
- iframe.contentDocument.head.appendChild(style);
- }
-
- // Force color on any elements with inline styles
- const elements = iframe.contentDocument.querySelectorAll('*');
- elements.forEach(el => {
- if (el.tagName !== 'IMG') {
- el.style.setProperty('background-color', '#1a1a1a', 'important');
- el.style.setProperty('color', '#e0e0e0', 'important');
- if (el.style.backgroundImage && el.style.backgroundImage !== 'none') {
- el.style.setProperty('background-image', 'none', 'important');
- }
- }
- });
-
- // Add mutation observer for dynamically added content within the iframe
- const observer = new MutationObserver((mutations) => {
- mutations.forEach((mutation) => {
- mutation.addedNodes.forEach((node) => {
- if (node.nodeType === 1 && node.tagName !== 'IMG') {
- node.style.setProperty('background-color', '#1a1a1a', 'important');
- node.style.setProperty('color', '#e0e0e0', 'important');
- }
- });
- });
- });
-
- observer.observe(iframe.contentDocument.body, {
- childList: true,
- subtree: true,
- attributes: true,
- attributeFilter: ['style', 'class']
- });
- };
-
- // If iframe is already loaded
- if (iframe.contentDocument && iframe.contentDocument.readyState === 'complete') {
- inject();
- }
-
- // Also listen for load event
- iframe.addEventListener('load', inject);
-
- } catch (e) {
- // Handle cross-origin errors silently
- }
- }
-
- // Function to watch a specific container for iframes
- function watchContainer(container) {
- if (!container) return;
-
- // Process any existing iframes
- container.querySelectorAll('iframe').forEach(iframe => {
- injectIframeStyles(iframe);
- });
-
- // Watch for new iframes
- const observer = new MutationObserver((mutations) => {
- mutations.forEach(mutation => {
- mutation.addedNodes.forEach(node => {
- // Direct iframe
- if (node.tagName === 'IFRAME') {
- injectIframeStyles(node);
- }
- // Iframe within added node
- if (node.querySelectorAll) {
- node.querySelectorAll('iframe').forEach(iframe => {
- injectIframeStyles(iframe);
- });
- }
- });
- });
- });
-
- observer.observe(container, {
- childList: true,
- subtree: true,
- attributes: true,
- attributeFilter: ['src', 'srcdoc']
- });
- }
-
- // Function to set up all observers
- function setupObservers() {
- // Watch for new messages and composers
- const bodyObserver = new MutationObserver((mutations) => {
- mutations.forEach(mutation => {
- mutation.addedNodes.forEach(node => {
- if (node.nodeType === 1) {
- // Watch message containers
- if (node.classList?.contains('message-container')) {
- watchContainer(node);
- }
- // Watch for reply composer
- if (node.classList?.contains('reply-wrapper')) {
- watchContainer(node);
- }
- // Watch any other potential containers
- node.querySelectorAll('.message-container, .reply-wrapper, .composer-body-container').forEach(container => {
- watchContainer(container);
- });
- }
- });
- });
- });
-
- bodyObserver.observe(document.body, {
- childList: true,
- subtree: true
- });
-
- // Process existing containers
- document.querySelectorAll('.message-container, .reply-wrapper, .composer-body-container').forEach(container => {
- watchContainer(container);
- });
- }
-
- // Initial setup
- setupObservers();
-
- // Handle route changes
- let lastUrl = location.href;
- new MutationObserver(() => {
- const url = location.href;
- if (url !== lastUrl) {
- lastUrl = url;
- setTimeout(setupObservers, 500);
- }
- }).observe(document, { subtree: true, childList: true });
-
- // Check periodically for new reply composers
- setInterval(() => {
- document.querySelectorAll('.reply-wrapper iframe').forEach(iframe => {
- if (!iframe.contentDocument?.querySelector('#dark-theme-style')) {
- injectIframeStyles(iframe);
- }
- });
- }, 1000);
-
- })();