continuous-integration/drone/pr Build is passing
- MAILBOX: <select> со своими, общими и добавленными ящиками (непрочитанные в скобках), выбор — переключение; список и форма «добавить» из левой панели убраны. - Settings → MY MAILBOXES (у всех): список ящиков, «убрать» добавленный, «добавить» по паролю. - Выбранная папка, ссылка и палитра — nav-active в --primary-color темы (было фиксированное розовое — в Neon Cyan баклажановое). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
4.5 KiB
TypeScript
96 lines
4.5 KiB
TypeScript
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
|
import { FormsModule } from '@angular/forms';
|
|
import { MatIconModule } from '@angular/material/icon';
|
|
import { ApiService } from '../../core/api.service';
|
|
import { AuthService } from '../../core/auth.service';
|
|
import { MailStateService } from '../../core/mail-state.service';
|
|
import { MailboxEntry, errorText } from '../../core/models';
|
|
import { DialogService } from '../../ui/dialog.service';
|
|
import { ToastService } from '../../ui/toast.service';
|
|
|
|
/** Settings → MY MAILBOXES (everyone): mailboxes in the sidebar combobox — add one by its password, remove an added one. */
|
|
@Component({
|
|
selector: 'app-my-mailboxes-section',
|
|
imports: [FormsModule, MatIconModule],
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
host: { class: 'block' },
|
|
template: `
|
|
<section class="glass-panel static p-5 md:p-6 h-full" aria-labelledby="my-boxes-title">
|
|
<h2 id="my-boxes-title" class="text-lg font-orbitron font-bold text-cyan-300 flex items-center gap-2 mb-4 border-b border-cyan-500/20 pb-2">
|
|
<mat-icon class="text-pink-500">alternate_email</mat-icon> MY MAILBOXES
|
|
</h2>
|
|
<ul class="space-y-1 mb-4" aria-label="Your mailboxes">
|
|
@for (b of state.boxes(); track b.email) {
|
|
<li class="flex items-center gap-2 px-2 py-1 rounded-lg border border-cyan-500/15">
|
|
<mat-icon class="!text-lg text-slate-400 shrink-0">{{ b.shared ? 'groups' : b.kind === 'OWN' ? 'person' : 'link' }}</mat-icon>
|
|
<span class="flex-grow truncate font-mono text-sm text-slate-200">{{ b.email }}</span>
|
|
<span class="text-[11px] font-mono text-slate-500">{{ b.kind === 'OWN' ? 'own' : b.shared ? 'shared' : 'added' }}</span>
|
|
@if (b.kind !== 'OWN') {
|
|
<button type="button" class="ghost-btn !py-1 hover:text-rose-400" (click)="leave(b)"
|
|
[attr.aria-label]="'Remove ' + b.email + ' from my mailboxes'" title="Remove from my mailboxes">
|
|
<mat-icon>close</mat-icon></button>
|
|
}
|
|
</li>
|
|
}
|
|
</ul>
|
|
<form class="grid grid-cols-1 md:grid-cols-[1fr_1fr_auto] gap-3 items-end" (ngSubmit)="attach()" aria-label="Add a mailbox">
|
|
<div><label class="synth-label" for="att-email">Mailbox</label>
|
|
<input id="att-email" name="attEmail" class="synth-input" [(ngModel)]="email" autocomplete="off" placeholder="name@domain"></div>
|
|
<div><label class="synth-label" for="att-pw">Its password</label>
|
|
<input id="att-pw" name="attPw" type="password" class="synth-input" [(ngModel)]="password" autocomplete="off"></div>
|
|
<button type="submit" class="cyber-btn px-4 py-2 rounded-lg text-xs font-bold"><mat-icon>link</mat-icon> ADD</button>
|
|
</form>
|
|
</section>
|
|
`
|
|
})
|
|
export class MyMailboxesSection {
|
|
readonly state = inject(MailStateService);
|
|
private api = inject(ApiService);
|
|
private auth = inject(AuthService);
|
|
private dialogs = inject(DialogService);
|
|
private toasts = inject(ToastService);
|
|
|
|
email = '';
|
|
password = '';
|
|
|
|
constructor() {
|
|
this.state.refreshBoxes();
|
|
}
|
|
|
|
attach(): void {
|
|
const email = this.email.trim().toLowerCase();
|
|
if (!email || !this.password) { this.toasts.error('Mailbox and its password'); return; }
|
|
this.api.attachMailbox(email, this.password).subscribe({
|
|
next: b => {
|
|
this.email = this.password = '';
|
|
this.toasts.ok(b.email + ' added — pick it in the MAILBOX list on the left');
|
|
this.state.refreshBoxes();
|
|
},
|
|
error: e => this.toasts.error(errorText(e))
|
|
});
|
|
}
|
|
|
|
leave(b: MailboxEntry): void {
|
|
this.dialogs.confirm(`Remove ${b.email}?`, b.shared
|
|
? 'It disappears from your list. An administrator can give it back.'
|
|
: 'It disappears from your list. Add it again with its password.', 'Remove', true).subscribe(ok => {
|
|
if (ok === null) return;
|
|
this.api.leaveMailbox(b.email).subscribe({
|
|
next: () => {
|
|
this.toasts.ok(b.email + ' removed');
|
|
if (b.email === this.auth.email()) {
|
|
// removed the one we are in — back to the own mailbox
|
|
this.api.mailboxSession(this.state.boxes().find(x => x.kind === 'OWN')?.email ?? '').subscribe({
|
|
next: session => { this.state.reset(); this.auth.switchTo(session); location.assign('/'); },
|
|
error: () => location.assign('/')
|
|
});
|
|
return;
|
|
}
|
|
this.state.refreshBoxes();
|
|
},
|
|
error: e => this.toasts.error(errorText(e))
|
|
});
|
|
});
|
|
}
|
|
}
|