Синхронизация моделей, Docker настройки и исправление Angular шаблонов
- V1__init.sql: синхронизированы таблицы с Java моделями (users, folders, messages, contacts) - docker-compose.yml: обновлён контекст сборки, переменные R2DBC/Flyway, порты (5433, 3026, 1443) - backend/Dockerfile: обновлены пути COPY для нового контекста сборки - settings.gradle: удалён frontend проект из Gradle - contacts.component.html: исправлен ternary оператор с HTML на Angular @if/@else - Фронтенд компоненты: обновления compose, inbox, layout, login, message-detail, register - index.html, styles.css: обновления стилей и мета-данных Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
@@ -12,7 +12,7 @@ import { Router, ActivatedRoute } from '@angular/router';
|
||||
template: `
|
||||
<div class="glass-panel p-6">
|
||||
<h2 class="text-lg font-orbitron font-bold text-cyan-500 mb-6 flex items-center gap-2">
|
||||
<span class="text-pink-500">✉️</span> COMPOSE MESSAGE
|
||||
<i class="fa-solid fa-pen-to-square text-pink-500"></i> COMPOSE MESSAGE
|
||||
</h2>
|
||||
|
||||
@if (success) {
|
||||
@@ -53,14 +53,14 @@ import { Router, ActivatedRoute } from '@angular/router';
|
||||
class="w-full bg-slate-950/80 border border-cyan-500/40 rounded-xl px-4 py-3 text-sm text-slate-200 font-mono focus:outline-none focus:ring-2 focus:ring-pink-500 focus:border-cyan-500 transition-all resize-none"
|
||||
[placeholder]="isHtml ? 'Write HTML or Markdown...' : 'Write your message...'"></textarea>
|
||||
@if (isHtml) {
|
||||
<p class="text-xs text-slate-500 font-mono mt-1">💡 Supports Markdown: **bold**, *italic*, - lists, \`\`\`code\`\`\`</p>
|
||||
<p class="text-xs text-slate-500 font-mono mt-1"><i class="fa-solid fa-lightbulb mr-1"></i>Supports Markdown: **bold**, *italic*, - lists, code </p>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3 pt-4">
|
||||
<button type="submit" [disabled]="sending"
|
||||
class="cyber-btn px-8 py-3 rounded-xl text-sm font-bold">
|
||||
{{ sending ? 'SENDING...' : 'SEND' }}
|
||||
<i class="fa-solid fa-paper-plane mr-1"></i> {{ sending ? 'SENDING...' : 'SEND' }}
|
||||
</button>
|
||||
<a routerLink="/inbox" class="px-6 py-3 rounded-xl text-xs font-mono text-slate-400 border border-slate-600 hover:text-slate-200 hover:border-slate-400 transition-all text-center">
|
||||
CANCEL
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<div class="space-y-4">
|
||||
<h2 class="text-lg font-orbitron font-bold text-cyan-300 flex items-center gap-2">
|
||||
<span class="text-pink-500">👥</span> CONTACTS
|
||||
<i class="fa-solid fa-users text-pink-500"></i> CONTACTS
|
||||
<span class="text-sm font-mono text-slate-400 ml-auto">{{ contacts().length }} contacts</span>
|
||||
</h2>
|
||||
|
||||
<!-- Форма добавления -->
|
||||
<div class="glass-panel p-5">
|
||||
<h3 class="text-sm font-orbitron font-bold text-yellow-400 mb-3 flex items-center gap-2">
|
||||
<span class="text-pink-500">✏️</span> {{ editingContact() ? 'EDIT CONTACT' : 'ADD NEW CONTACT' }}
|
||||
<i class="fa-solid fa-pen-to-square text-pink-500"></i> {{ editingContact() ? 'EDIT CONTACT' : 'ADD NEW CONTACT' }}
|
||||
</h3>
|
||||
<form (ngSubmit)="saveContact()" class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<div>
|
||||
@@ -37,12 +37,16 @@
|
||||
<div class="md:col-span-2 flex gap-2">
|
||||
<button type="submit"
|
||||
class="cyber-btn px-6 py-2 rounded-xl text-xs font-bold">
|
||||
{{ editingContact() ? '💾 UPDATE' : '➕ ADD' }}
|
||||
@if (editingContact()) {
|
||||
<i class="fa-solid fa-save mr-1"></i> UPDATE
|
||||
} @else {
|
||||
<i class="fa-solid fa-plus mr-1"></i> ADD
|
||||
}
|
||||
</button>
|
||||
@if (editingContact()) {
|
||||
<button type="button" (click)="cancelEdit()"
|
||||
class="px-4 py-2 rounded-xl text-xs font-mono text-slate-400 border border-slate-600/40 hover:text-slate-200 hover:border-slate-400 transition-all">
|
||||
✖ CANCEL
|
||||
<i class="fa-solid fa-xmark mr-1"></i> CANCEL
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@@ -53,7 +57,7 @@
|
||||
<div class="glass-panel p-5">
|
||||
@if (contacts().length === 0) {
|
||||
<div class="text-center py-12 text-slate-400 font-mono">
|
||||
<p class="text-4xl mb-4">📇</p>
|
||||
<i class="fa-solid fa-address-card text-4xl mb-4 text-slate-600"></i>
|
||||
<p>No contacts yet</p>
|
||||
</div>
|
||||
} @else {
|
||||
@@ -83,11 +87,11 @@
|
||||
<div class="flex gap-1">
|
||||
<button (click)="editContact(contact)"
|
||||
class="px-2 py-1 text-xs font-mono text-cyan-400 hover:bg-cyan-500/10 rounded-lg transition-all">
|
||||
✏️
|
||||
<i class="fa-solid fa-pen"></i>
|
||||
</button>
|
||||
<button (click)="deleteContact(contact.id!)"
|
||||
class="px-2 py-1 text-xs font-mono text-pink-400 hover:bg-pink-500/10 rounded-lg transition-all">
|
||||
🗑️
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,9 +19,9 @@ import { of } from 'rxjs';
|
||||
<div class="flex gap-3">
|
||||
<input [(ngModel)]="searchQuery" (keyup.enter)="loadMessages()"
|
||||
class="flex-1 bg-slate-950/80 border border-cyan-500/40 rounded-xl px-4 py-2 text-sm text-slate-200 font-mono focus:outline-none focus:ring-2 focus:ring-pink-500 focus:border-cyan-400 transition-all"
|
||||
placeholder="🔍 Search messages...">
|
||||
placeholder="Search messages...">
|
||||
<button (click)="loadMessages()" class="cyber-btn px-6 py-2 rounded-xl text-xs font-bold">
|
||||
SEARCH
|
||||
<i class="fa-solid fa-magnifying-glass mr-1"></i> SEARCH
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,13 +29,13 @@ import { of } from 'rxjs';
|
||||
<!-- Список писем -->
|
||||
<div class="glass-panel p-4">
|
||||
<h2 class="text-lg font-orbitron font-bold text-cyan-300 mb-4 flex items-center gap-2">
|
||||
<span class="text-pink-500">📬</span> {{ folderName() }}
|
||||
<i class="fa-solid fa-inbox text-pink-500"></i> {{ folderName() }}
|
||||
<span class="text-sm font-mono text-slate-400 ml-auto">{{ messages()?.length || 0 }} messages</span>
|
||||
</h2>
|
||||
|
||||
@if (!messages() || messages().length === 0) {
|
||||
<div class="text-center py-12 text-slate-400 font-mono">
|
||||
<p class="text-4xl mb-4">📭</p>
|
||||
<i class="fa-solid fa-envelope-open-text text-4xl mb-4 text-slate-600"></i>
|
||||
<p>No messages yet</p>
|
||||
</div>
|
||||
} @else {
|
||||
@@ -64,7 +64,7 @@ import { of } from 'rxjs';
|
||||
</span>
|
||||
}
|
||||
@if (msg.flagged) {
|
||||
<span class="text-yellow-400 text-xs">★</span>
|
||||
<i class="fa-solid fa-star text-yellow-400 text-xs"></i>
|
||||
}
|
||||
</div>
|
||||
<div class="text-sm text-cyan-400 font-rajdhani truncate group-hover:text-pink-400 transition-colors">
|
||||
@@ -80,7 +80,7 @@ import { of } from 'rxjs';
|
||||
{{ msg.sentAt | date:'MMM d, HH:mm' }}
|
||||
</div>
|
||||
@if (msg.hasAttachments) {
|
||||
<span class="text-xs text-purple-400 font-mono">📎 {{ msg.attachmentCount }}</span>
|
||||
<span class="text-xs text-purple-400 font-mono"><i class="fa-solid fa-paperclip mr-1"></i>{{ msg.attachmentCount }}</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,7 +95,7 @@ import { of } from 'rxjs';
|
||||
<div class="glass-panel p-4 flex items-center justify-center gap-2">
|
||||
<button (click)="goToPage(page() - 1)" [disabled]="page() === 0"
|
||||
class="px-3 py-1 rounded-lg text-xs font-mono text-cyan-400 border border-cyan-500/30 hover:bg-cyan-500/10 disabled:opacity-30 disabled:cursor-not-allowed transition-all">
|
||||
← Prev
|
||||
<i class="fa-solid fa-chevron-left mr-1"></i> Prev
|
||||
</button>
|
||||
@for (p of pageNumbers; track p) {
|
||||
<button (click)="goToPage(p)"
|
||||
@@ -107,7 +107,7 @@ import { of } from 'rxjs';
|
||||
}
|
||||
<button (click)="goToPage(page() + 1)" [disabled]="page() >= totalPages() - 1"
|
||||
class="px-3 py-1 rounded-lg text-xs font-mono text-cyan-400 border border-cyan-500/30 hover:bg-cyan-500/10 disabled:opacity-30 disabled:cursor-not-allowed transition-all">
|
||||
Next →
|
||||
Next <i class="fa-solid fa-chevron-right ml-1"></i>
|
||||
</button>
|
||||
<span class="text-xs font-mono text-slate-500 ml-2">
|
||||
Page {{ page() + 1 }} of {{ totalPages() }}
|
||||
|
||||
@@ -76,7 +76,7 @@ const THEMES = [
|
||||
[attr.aria-pressed]="isHighContrast()"
|
||||
title="Toggle High Contrast Mode"
|
||||
class="px-3 py-1.5 rounded-lg border border-cyan-500/40 bg-slate-900/80 text-xs font-mono hover:bg-cyan-500/20 focus:outline-none focus:ring-2 focus:ring-pink-500 transition-all flex items-center gap-1.5">
|
||||
<span class="text-yellow-400">◐</span>
|
||||
<i class="fa-solid fa-circle-half-stroke text-yellow-400"></i>
|
||||
<span>Contrast</span>
|
||||
</button>
|
||||
|
||||
@@ -87,7 +87,7 @@ const THEMES = [
|
||||
|
||||
<!-- Logout -->
|
||||
<button (click)="onLogout()" class="cyber-btn px-4 py-1.5 rounded-lg text-xs font-bold">
|
||||
⏻ Logout
|
||||
<i class="fa-solid fa-power-off mr-1"></i> Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -99,7 +99,7 @@ const THEMES = [
|
||||
<aside class="w-64 flex-shrink-0">
|
||||
<div class="glass-panel p-4">
|
||||
<h2 class="text-sm font-orbitron font-bold text-cyan-300 mb-3 flex items-center gap-2">
|
||||
<span class="text-pink-500">📁</span> FOLDERS
|
||||
<i class="fa-solid fa-folder-tree text-pink-500"></i> FOLDERS
|
||||
</h2>
|
||||
<nav class="space-y-1">
|
||||
@for (folder of folders(); track folder.id) {
|
||||
@@ -123,14 +123,14 @@ const THEMES = [
|
||||
<a routerLink="/contacts"
|
||||
routerLinkActive="bg-cyan-500/20 text-cyan-300 border-cyan-500"
|
||||
class="block px-3 py-2 rounded-lg text-sm font-rajdhani text-slate-300 hover:bg-cyan-500/10 hover:text-cyan-300 border border-transparent transition-all">
|
||||
👥 Contacts
|
||||
<i class="fa-solid fa-address-book mr-2"></i> Contacts
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Кнопка написать -->
|
||||
<div class="glass-panel p-4 mt-4">
|
||||
<a routerLink="/compose" class="cyber-btn w-full py-3 rounded-xl text-xs font-bold block text-center">
|
||||
✉️ NEW MESSAGE
|
||||
<i class="fa-solid fa-pen-to-square mr-2"></i> NEW MESSAGE
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -146,8 +146,8 @@ const THEMES = [
|
||||
<div class="max-w-7xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-2">
|
||||
<div>MAILSERVER SYNTHWAVE © 2026</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="text-cyan-400">◉ A11Y READY</span>
|
||||
<span class="text-pink-400">⚡ HIGH PERF CSS3D</span>
|
||||
<span class="text-cyan-400"><i class="fa-solid fa-universal-access mr-1"></i> A11Y READY</span>
|
||||
<span class="text-pink-400"><i class="fa-solid fa-bolt mr-1"></i> HIGH PERF CSS3D</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -55,7 +55,11 @@ import { Router } from '@angular/router';
|
||||
|
||||
<button type="submit" [disabled]="loading"
|
||||
class="cyber-btn w-full py-3 rounded-xl text-sm font-bold mt-6">
|
||||
{{ loading ? '⏳ CONNECTING...' : '⚡ LOGIN' }}
|
||||
@if (loading) {
|
||||
<i class="fa-solid fa-spinner fa-spin mr-1"></i> CONNECTING...
|
||||
} @else {
|
||||
<i class="fa-solid fa-bolt mr-1"></i> LOGIN
|
||||
}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ import { Message } from '../../models/mail.models';
|
||||
|
||||
@if (message.spam) {
|
||||
<div class="mt-3 px-3 py-1.5 inline-block rounded-full bg-yellow-500/20 border border-yellow-500/40 text-yellow-500 text-xs font-mono">
|
||||
⚠️ MARKED AS SPAM
|
||||
<i class="fa-solid fa-triangle-exclamation mr-1"></i> MARKED AS SPAM
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -47,7 +47,7 @@ import { Message } from '../../models/mail.models';
|
||||
@if (message.hasAttachments) {
|
||||
<div class="mb-6 p-4 rounded-xl bg-slate-950/60 border border-purple-500/30">
|
||||
<h3 class="text-sm font-orbitron font-bold text-purple-500 mb-3">
|
||||
📎 ATTACHMENTS ({{ message.attachmentCount }})
|
||||
<i class="fa-solid fa-paperclip mr-1"></i> ATTACHMENTS ({{ message.attachmentCount }})
|
||||
</h3>
|
||||
<p class="text-xs text-slate-400 font-mono">
|
||||
Attachments are available via the API
|
||||
@@ -58,24 +58,24 @@ import { Message } from '../../models/mail.models';
|
||||
<!-- Действия -->
|
||||
<div class="flex gap-3 pt-4 border-t border-cyan-500/20">
|
||||
<a routerLink="/inbox" class="cyber-btn px-6 py-2 rounded-xl text-xs font-bold">
|
||||
← BACK TO INBOX
|
||||
<i class="fa-solid fa-chevron-left mr-1"></i> BACK TO INBOX
|
||||
</a>
|
||||
<a [routerLink]="['/compose']" [queryParams]="{replyTo: message.sender, subject: 'Re: ' + (message.subject || '')}"
|
||||
class="px-6 py-2 rounded-xl text-xs font-mono text-cyan-500 border border-cyan-500/40 hover:bg-cyan-500/20 transition-all">
|
||||
↩️ REPLY
|
||||
<i class="fa-solid fa-reply mr-1"></i> REPLY
|
||||
</a>
|
||||
<a [routerLink]="['/compose']" [queryParams]="{forwardTo: '', forwardSubject: 'Fwd: ' + (message.subject || ''), forwardBody: message.body}"
|
||||
class="px-6 py-2 rounded-xl text-xs font-mono text-purple-500 border border-purple-500/40 hover:bg-purple-500/20 transition-all">
|
||||
➡️ FORWARD
|
||||
<i class="fa-solid fa-share mr-1"></i> FORWARD
|
||||
</a>
|
||||
<button (click)="onDelete()" class="px-6 py-2 rounded-xl text-xs font-mono text-pink-500 border border-pink-500/40 hover:bg-pink-500/20 transition-all">
|
||||
🗑️ DELETE
|
||||
<i class="fa-solid fa-trash mr-1"></i> DELETE
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="glass-panel p-6 text-center py-12">
|
||||
<p class="text-4xl mb-4">📭</p>
|
||||
<i class="fa-solid fa-envelope-open-text text-4xl mb-4 text-slate-600"></i>
|
||||
<p class="text-slate-400 font-mono">Message not found</p>
|
||||
<a routerLink="/inbox" class="cyber-btn px-6 py-2 rounded-xl text-xs font-bold mt-4 inline-block">
|
||||
← BACK TO INBOX
|
||||
|
||||
@@ -67,7 +67,11 @@ import { Router } from '@angular/router';
|
||||
|
||||
<button type="submit" [disabled]="loading"
|
||||
class="cyber-btn w-full py-3 rounded-xl text-sm font-bold mt-6">
|
||||
{{ loading ? '⏳ CREATING...' : '⚡ REGISTER' }}
|
||||
@if (loading) {
|
||||
<i class="fa-solid fa-spinner fa-spin mr-1"></i> CREATING...
|
||||
} @else {
|
||||
<i class="fa-solid fa-bolt mr-1"></i> REGISTER
|
||||
}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<!-- Google Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;800;900&family=Rajdhani:wght@400;500;600;700&family=Fira+Code:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<!-- FontAwesome Icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
</head>
|
||||
<body class="bg-synth-dark">
|
||||
<app-root></app-root>
|
||||
|
||||
@@ -253,6 +253,18 @@ body {
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
/* ARIA Inspector Focus Highlight */
|
||||
.aria-inspect-active {
|
||||
outline: 2px dashed var(--highlight-color) !important;
|
||||
outline-offset: 4px;
|
||||
animation: pulseAria 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulseAria {
|
||||
0%, 100% { outline-color: var(--highlight-color); }
|
||||
50% { outline-color: var(--primary-color); }
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
|
||||
Reference in New Issue
Block a user