Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 38 additions & 9 deletions tauri-app/frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,23 @@
<link rel="stylesheet" href="styles/loading.css">
<link rel="stylesheet" href="styles/responsive.css">
<link rel="stylesheet" href="styles/dark-mode.css">
<link rel="stylesheet" href="styles/themes.css">
<link rel="stylesheet" href="styles/avatars.css">
<!-- Apply saved theme before first paint to avoid a flash of the wrong theme -->
<script>
(function () {
try {
var THEMES = ['light', 'dark', 'high-contrast', 'ocean', 'forest'];
var theme = localStorage.getItem('theme');
if (!theme) {
// Backward compatibility with the old binary dark-mode preference.
theme = localStorage.getItem('darkMode') === '1' ? 'dark' : 'light';
}
if (THEMES.indexOf(theme) === -1) theme = 'light';
document.documentElement.setAttribute('data-theme', theme);
} catch (e) {}
})();
</script>
<link rel="stylesheet" href="styles.css">
<link href="vendor/fontawesome/css/all.min.css" rel="stylesheet">
<link href="vendor/tom-select/css/tom-select.default.min.css" rel="stylesheet">
Expand Down Expand Up @@ -839,16 +855,29 @@ <h3><i class="fas fa-palette"></i> Appearance</h3>
<div class="settings-section-content">
<div class="form-group">
<label>Theme:</label>
<div class="input-with-button">
<div style="display: flex; align-items: center; gap: 12px;">
<span id="theme-label">Dark Mode</span>
<button id="dark-mode-toggle" class="btn btn-secondary" title="Toggle dark mode" style="display: flex; align-items: center; justify-content: center; gap: 8px;">
<i id="dark-mode-icon" class="fas fa-moon"></i>
<span id="dark-mode-text">Enable Dark Mode</span>
</button>
</div>
<div id="theme-picker" class="theme-picker" role="radiogroup" aria-label="Color theme">
<button type="button" class="theme-swatch" data-theme-value="light" role="radio" aria-checked="false" title="Light">
<span class="theme-swatch-preview" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);"><span class="theme-swatch-dot" style="background:#ffffff;"></span></span>
<span class="theme-swatch-name">Light</span>
</button>
<button type="button" class="theme-swatch" data-theme-value="dark" role="radio" aria-checked="false" title="Dark">
<span class="theme-swatch-preview" style="background: linear-gradient(135deg, #232946 0%, #121629 100%);"><span class="theme-swatch-dot" style="background:#667eea;"></span></span>
<span class="theme-swatch-name">Dark</span>
</button>
<button type="button" class="theme-swatch" data-theme-value="high-contrast" role="radio" aria-checked="false" title="High contrast">
<span class="theme-swatch-preview" style="background:#000000;"><span class="theme-swatch-dot" style="background:#ffd400;"></span></span>
<span class="theme-swatch-name">High contrast</span>
</button>
<button type="button" class="theme-swatch" data-theme-value="ocean" role="radio" aria-checked="false" title="Ocean">
<span class="theme-swatch-preview" style="background: linear-gradient(135deg, #12303f 0%, #071620 100%);"><span class="theme-swatch-dot" style="background:#2b9dc9;"></span></span>
<span class="theme-swatch-name">Ocean</span>
</button>
<button type="button" class="theme-swatch" data-theme-value="forest" role="radio" aria-checked="false" title="Forest">
<span class="theme-swatch-preview" style="background: linear-gradient(135deg, #1c3227 0%, #0c160f 100%);"><span class="theme-swatch-dot" style="background:#3fae6b;"></span></span>
<span class="theme-swatch-name">Forest</span>
</button>
</div>
<small class="form-text text-muted">Switch between light and dark theme</small>
<small class="form-text text-muted">Choose a color theme. Your choice is saved and applied on startup.</small>
</div>
</div>
</div>
Expand Down
88 changes: 62 additions & 26 deletions tauri-app/frontend/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1757,10 +1757,15 @@ NostrMailApp.prototype.setupEventListeners = function() {
// Profile switcher
this.setupProfileSwitcherListeners();

// Dark mode toggle
const darkToggle = document.getElementById('dark-mode-toggle');
if (darkToggle) {
darkToggle.addEventListener('click', () => this.toggleDarkMode());
// Theme picker
const themePicker = document.getElementById('theme-picker');
if (themePicker) {
themePicker.addEventListener('click', (e) => {
const swatch = e.target.closest('.theme-swatch');
if (swatch) {
this.setTheme(swatch.getAttribute('data-theme-value'));
}
});
}

// Profile form
Expand Down Expand Up @@ -5694,28 +5699,58 @@ NostrMailApp.prototype.updatePublicKeyDisplay = async function() {
}
}

// Dark mode management
NostrMailApp.prototype.setDarkMode = function(enabled) {
document.body.classList.toggle('dark-mode', enabled);
const icon = document.getElementById('dark-mode-icon');
if (icon) {
icon.className = enabled ? 'fas fa-sun' : 'fas fa-moon';
// Theme management
NostrMailApp.AVAILABLE_THEMES = ['light', 'dark', 'high-contrast', 'ocean', 'forest'];

// Resolve the active theme, honoring the legacy darkMode preference for
// users who set it before the multi-theme picker existed.
NostrMailApp.prototype.getSavedTheme = function() {
let theme = localStorage.getItem('theme');
if (!theme) {
theme = localStorage.getItem('darkMode') === '1' ? 'dark' : 'light';
}
const text = document.getElementById('dark-mode-text');
if (text) {
text.textContent = enabled ? 'Enable Light Mode' : 'Enable Dark Mode';
if (NostrMailApp.AVAILABLE_THEMES.indexOf(theme) === -1) {
theme = 'light';
}
const label = document.getElementById('theme-label');
if (label) {
label.textContent = enabled ? 'Dark Mode (Enabled)' : 'Light Mode (Enabled)';
return theme;
};

NostrMailApp.prototype.setTheme = function(theme) {
if (NostrMailApp.AVAILABLE_THEMES.indexOf(theme) === -1) {
theme = 'light';
}
localStorage.setItem('darkMode', enabled ? '1' : '0');
}

// Token sets live on :root[data-theme]; keep html and body in sync.
document.documentElement.setAttribute('data-theme', theme);
document.body.setAttribute('data-theme', theme);

// Dark keeps the legacy .dark-mode class (its visual overrides live in
// dark-mode.css); every other non-light theme uses the generic token
// layer driven by the .themed class.
document.body.classList.toggle('dark-mode', theme === 'dark');
document.body.classList.toggle('themed', theme !== 'light' && theme !== 'dark');

// Persist, and keep darkMode in sync for any older code paths.
localStorage.setItem('theme', theme);
localStorage.setItem('darkMode', theme === 'dark' ? '1' : '0');

// Reflect the selection in the Settings picker if it is rendered.
const swatches = document.querySelectorAll('#theme-picker .theme-swatch');
swatches.forEach((swatch) => {
const selected = swatch.getAttribute('data-theme-value') === theme;
swatch.setAttribute('aria-checked', selected ? 'true' : 'false');
});
};

// Backward-compatible shims for any remaining callers.
NostrMailApp.prototype.setDarkMode = function(enabled) {
this.setTheme(enabled ? 'dark' : 'light');
};

NostrMailApp.prototype.toggleDarkMode = function() {
const enabled = !document.body.classList.contains('dark-mode');
this.setDarkMode(enabled);
}
const isDark = this.getSavedTheme() === 'dark';
this.setTheme(isDark ? 'light' : 'dark');
};

// Add a method to render the email warning
NostrMailApp.prototype.renderProfileEmailWarning = function() {
Expand Down Expand Up @@ -6023,11 +6058,12 @@ document.addEventListener('DOMContentLoaded', () => {
}


// Set initial dark mode from localStorage
const darkPref = localStorage.getItem('darkMode');
window.app.setDarkMode(darkPref === '1');

console.log('🎨 Dark mode initialized:', darkPref === '1' ? 'enabled' : 'disabled');
// Apply the saved theme (the inline <head> script already set data-theme
// on <html> pre-paint; this syncs the body classes and picker state).
const initialTheme = window.app.getSavedTheme();
window.app.setTheme(initialTheme);

console.log('🎨 Theme initialized:', initialTheme);

// Initialize the application
window.app.init();
Expand Down
6 changes: 3 additions & 3 deletions tauri-app/frontend/js/contacts-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -1980,9 +1980,9 @@ class ContactsService {

// Update the UI to show it's an encrypted email
if (toAddressInput) {
const isDarkMode = document.body.classList.contains('dark-mode');
toAddressInput.style.borderColor = '#667eea';
toAddressInput.style.backgroundColor = isDarkMode ? '#1a1f3a' : '#f8f9ff';
const styles = getComputedStyle(document.body);
toAddressInput.style.borderColor = styles.getPropertyValue('--color-primary').trim() || '#667eea';
toAddressInput.style.backgroundColor = styles.getPropertyValue('--color-input-bg').trim() || '#f8f9ff';
}

window.notificationService.showSuccess(`Email address and recipient pubkey filled in for ${contact.name}`);
Expand Down
6 changes: 3 additions & 3 deletions tauri-app/frontend/js/email-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -392,10 +392,10 @@ class EmailService {
});
}

// Get appropriate background color for Nostr contact input based on dark mode
// Get appropriate background color for Nostr contact input from the active theme
getNostrContactInputBackgroundColor() {
const isDarkMode = document.body.classList.contains('dark-mode');
return isDarkMode ? '#1a1f3a' : '#f8f9ff';
const inputBg = getComputedStyle(document.body).getPropertyValue('--color-input-bg').trim();
return inputBg || '#f8f9ff';
}

/**
Expand Down
7 changes: 5 additions & 2 deletions tauri-app/frontend/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ class Utils {
iframe.style.minHeight = '100px';
container.appendChild(iframe);
const doc = iframe.contentDocument || iframe.contentWindow.document;
// Inject dark mode aware base styles + the email HTML
const isDark = document.body.classList.contains('dark-mode');
// Inject dark mode aware base styles + the email HTML.
// Every theme except light uses a dark surface, so apply the dark
// email overrides whenever the active theme is not light.
const activeTheme = document.documentElement.getAttribute('data-theme') || 'light';
const isDark = activeTheme !== 'light';
doc.open();
const darkOverrides = isDark ? `
h4 { color: #9ca3af !important; }
Expand Down
4 changes: 2 additions & 2 deletions tauri-app/frontend/styles/buttons.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
}

.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: var(--color-primary-gradient);
color: white;
}

Expand Down Expand Up @@ -205,7 +205,7 @@ select.btn-select:hover:not(:disabled) {

select.btn-select:focus {
outline: none;
border-color: #667eea;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}

Expand Down
30 changes: 15 additions & 15 deletions tauri-app/frontend/styles/contacts.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
}

.contact-item.active {
background-color: #667eea;
background-color: var(--color-primary);
color: white;
}

Expand Down Expand Up @@ -196,7 +196,7 @@ body.dark-mode .contact-section-content .section-empty-message {
border: none;
cursor: pointer;
padding: 4px;
color: #667eea;
color: var(--color-primary);
font-size: 14px;
opacity: 0.8;
transition: opacity 0.2s ease, color 0.2s ease;
Expand All @@ -208,7 +208,7 @@ body.dark-mode .contact-section-content .section-empty-message {

.contact-email-icon:hover {
opacity: 1;
color: #5568d3;
color: var(--color-primary-hover);
background-color: rgba(102, 126, 234, 0.1);
}

Expand Down Expand Up @@ -388,7 +388,7 @@ body.dark-mode .contact-section-content .section-empty-message {
}

.contact-detail-email {
color: #667eea;
color: var(--color-primary);
text-decoration: none;
font-weight: 500;
}
Expand All @@ -405,7 +405,7 @@ body.dark-mode .contact-section-content .section-empty-message {
margin: 0 0 15px 0;
font-size: 1.1rem;
color: #333;
border-bottom: 2px solid #667eea;
border-bottom: 2px solid var(--color-primary);
padding-bottom: 5px;
}

Expand Down Expand Up @@ -472,7 +472,7 @@ body.dark-mode .contact-section-content .section-empty-message {

.contacts-search-input:focus {
outline: none;
border-color: #667eea;
border-color: var(--color-primary);
background-color: white;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15);
}
Expand Down Expand Up @@ -513,7 +513,7 @@ body.dark-mode .contact-detail-info h3 {

body.dark-mode .contact-detail-section h4 {
color: #e0e0e0;
border-bottom-color: #667eea;
border-bottom-color: var(--color-primary);
}

body.dark-mode .contact-detail-field label {
Expand Down Expand Up @@ -551,7 +551,7 @@ body.dark-mode .contact-item:hover {
}

body.dark-mode .contact-item.active {
background-color: #667eea;
background-color: var(--color-primary);
color: white;
}

Expand All @@ -573,7 +573,7 @@ body.dark-mode .contact-detail-pubkey-code {
}

body.dark-mode .contact-email-icon {
color: #667eea;
color: var(--color-primary);
opacity: 0.8;
}

Expand All @@ -591,7 +591,7 @@ body.dark-mode .contacts-search-input {
}

body.dark-mode .contacts-search-input:focus {
border-color: #667eea;
border-color: var(--color-primary);
background-color: #232946;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15);
}
Expand Down Expand Up @@ -654,7 +654,7 @@ body.dark-mode .contact-item.active .contact-delete-icon:hover {

.privacy-toggle-icon {
font-size: 1.2rem;
color: #667eea;
color: var(--color-primary);
}

.privacy-toggle-title {
Expand Down Expand Up @@ -693,7 +693,7 @@ body.dark-mode .contact-item.active .contact-delete-icon:hover {
}

.privacy-toggle input[type="checkbox"]:checked + .privacy-toggle-slider {
background-color: #667eea;
background-color: var(--color-primary);
}

.privacy-toggle-slider::before {
Expand Down Expand Up @@ -740,7 +740,7 @@ body.dark-mode .contact-item.active .contact-delete-icon:hover {
z-index: 10;
padding: 0;
font-size: 0.95rem;
color: #667eea;
color: var(--color-primary);
font-weight: 600;
backdrop-filter: blur(2px);
}
Expand All @@ -755,7 +755,7 @@ body.dark-mode .contact-item.active .contact-delete-icon:hover {

body.dark-mode .privacy-toggle-loading {
background-color: rgba(24, 26, 32, 0.98);
color: #667eea;
color: var(--color-primary);
}

/* Dark mode support for privacy toggle */
Expand All @@ -777,7 +777,7 @@ body.dark-mode .privacy-toggle-slider {
}

body.dark-mode .privacy-toggle input[type="checkbox"]:checked + .privacy-toggle-slider {
background-color: #667eea;
background-color: var(--color-primary);
}

body.dark-mode .privacy-toggle-description {
Expand Down
4 changes: 2 additions & 2 deletions tauri-app/frontend/styles/dark-mode.css
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ body.dark-mode .email-item {

body.dark-mode .email-item.unread {
background-color: #232946;
border-left: 4px solid #667eea;
border-left: 4px solid var(--color-primary);
}

body.dark-mode .modal {
Expand All @@ -90,7 +90,7 @@ body.dark-mode .form-group textarea {

body.dark-mode .form-group input:focus,
body.dark-mode .form-group textarea:focus {
border-color: #667eea;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15);
}

Expand Down
Loading
Loading