Почтовый кабинет: регистрация, папки, черновики, вложения, грид @jambotron/aria-grid, интерфейс по synthwave-шаблону

Бэкенд: регистрация со входом сразу и /api/config, профиль и смена пароля, свои папки, PATCH письма
(прочитано, флажок, перенос; Спам ставит отметку), корзина и очистка, черновики, несколько адресатов и копия,
вложения черновика уходят с письмом, вложения и заголовки To/Cc входящих сохраняются, письма заблокированных —
в Спам. Исправлено: загрузка файла (415), колонка content_id (500 на любых вложениях), SPA-маршрут с точкой.
Фронтенд переписан: все таблицы — грид, оформление — шаблон synthwave-design-showcase.html.

α
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-05 16:41:29 +01:00
parent f605fbfd85
commit 29b1beabe0
70 changed files with 3468 additions and 1737 deletions
+36
View File
@@ -0,0 +1,36 @@
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 */ }
}