Бэкенд: регистрация со входом сразу и /api/config, профиль и смена пароля, свои папки, PATCH письма (прочитано, флажок, перенос; Спам ставит отметку), корзина и очистка, черновики, несколько адресатов и копия, вложения черновика уходят с письмом, вложения и заголовки To/Cc входящих сохраняются, письма заблокированных — в Спам. Исправлено: загрузка файла (415), колонка content_id (500 на любых вложениях), SPA-маршрут с точкой. Фронтенд переписан: все таблицы — грид, оформление — шаблон synthwave-design-showcase.html. α Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
37 lines
1.4 KiB
TypeScript
37 lines
1.4 KiB
TypeScript
import { Injectable, effect, signal } from '@angular/core';
|
|
|
|
export const THEMES = [
|
|
{ value: 'theme-cyber-magenta', label: 'Cyber Magenta' },
|
|
{ value: 'theme-neon-cyan', label: 'Neon Cyan' },
|
|
{ value: 'theme-sunset-orange', label: 'Sunset Orange' },
|
|
{ value: 'theme-matrix-green', label: 'Matrix Green' }
|
|
];
|
|
|
|
/** Palette, high contrast and background motion — on <html>, remembered in this browser. */
|
|
@Injectable({ providedIn: 'root' })
|
|
export class ThemeService {
|
|
readonly theme = signal(load('theme', THEMES[0].value));
|
|
readonly highContrast = signal(load('contrast', 'off') === 'on');
|
|
readonly animation = signal(load('animation', 'on') === 'on');
|
|
|
|
constructor() {
|
|
effect(() => {
|
|
const root = document.documentElement;
|
|
THEMES.forEach(t => root.classList.toggle(t.value, t.value === this.theme()));
|
|
root.classList.toggle('high-contrast', this.highContrast());
|
|
root.classList.toggle('no-anim', !this.animation());
|
|
save('theme', this.theme());
|
|
save('contrast', this.highContrast() ? 'on' : 'off');
|
|
save('animation', this.animation() ? 'on' : 'off');
|
|
});
|
|
}
|
|
}
|
|
|
|
function load(key: string, fallback: string): string {
|
|
try { return localStorage.getItem('ui.' + key) ?? fallback; } catch { return fallback; }
|
|
}
|
|
|
|
function save(key: string, value: string): void {
|
|
try { localStorage.setItem('ui.' + key, value); } catch { /* storage blocked */ }
|
|
}
|