added mails page + reworked button placement
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Aspose.Email" Version="23.3.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||||
<PackageReference Include="WebDav.Client" Version="2.8.0" />
|
<PackageReference Include="WebDav.Client" Version="2.8.0" />
|
||||||
|
|||||||
105
BetterIServ.Backend/Controllers/MailController.cs
Normal file
105
BetterIServ.Backend/Controllers/MailController.cs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Mail;
|
||||||
|
using System.Text;
|
||||||
|
using Aspose.Email.Clients.Imap;
|
||||||
|
using BetterIServ.Backend.Entities;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace BetterIServ.Backend.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("mail")]
|
||||||
|
public class MailController : ControllerBase {
|
||||||
|
|
||||||
|
[HttpPost("send")]
|
||||||
|
public async Task<IActionResult> SendMail([FromBody] MailData data) {
|
||||||
|
using var client = new SmtpClient($"smpt.{data.Domain}");
|
||||||
|
var sender = new MailAddress($"{data.Username}@{data.Domain}", data.Username);
|
||||||
|
var reciever = new MailAddress(data.Receiver ?? $"{data.Username}@{data.Domain}");
|
||||||
|
|
||||||
|
using var message = new MailMessage(sender, reciever);
|
||||||
|
message.Body = data.MailBody;
|
||||||
|
message.Subject = data.Subject;
|
||||||
|
message.BodyEncoding = Encoding.UTF8;
|
||||||
|
message.SubjectEncoding = Encoding.UTF8;
|
||||||
|
|
||||||
|
var result = new TaskCompletionSource<IActionResult>();
|
||||||
|
client.SendCompleted += (o, args) => {
|
||||||
|
if (args is { Cancelled: false, Error: null })
|
||||||
|
result.SetResult(Ok());
|
||||||
|
else result.SetResult(BadRequest(args.Error?.Message));
|
||||||
|
};
|
||||||
|
|
||||||
|
client.Credentials = new NetworkCredential(data.Username, data.Password);
|
||||||
|
client.EnableSsl = true;
|
||||||
|
await client.SendMailAsync(message);
|
||||||
|
return await result.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("list/{page}")]
|
||||||
|
public async Task<ActionResult<MailContent[]>> GetMails([FromBody] Credentials credentials, [FromQuery] string folder, [FromRoute] int page) {
|
||||||
|
using var client = new ImapClient($"imap.{credentials.Domain}", credentials.Username, credentials.Password);
|
||||||
|
await client.SelectFolderAsync(folder);
|
||||||
|
|
||||||
|
var messages = await client.ListMessagesByPageAsync(20, page, new PageSettingsAsync());
|
||||||
|
var contents = new List<MailContent>();
|
||||||
|
foreach (var message in messages.Items) {
|
||||||
|
var content = new MailContent {
|
||||||
|
Id = message.SequenceNumber,
|
||||||
|
Sender = message.Sender,
|
||||||
|
Subject = message.Subject,
|
||||||
|
Time = message.Date,
|
||||||
|
Read = message.IsRead
|
||||||
|
};
|
||||||
|
contents.Add(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
return contents.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("content/{id}")]
|
||||||
|
public async Task<ActionResult<MailContent>> GetMail([FromBody] Credentials credentials, [FromRoute] int id) {
|
||||||
|
using var client = new ImapClient($"imap.{credentials.Domain}", credentials.Username, credentials.Password);
|
||||||
|
var message = await client.FetchMessageAsync(id);
|
||||||
|
|
||||||
|
var content = new MailContent {
|
||||||
|
Id = id,
|
||||||
|
Sender = message.Sender,
|
||||||
|
Subject = message.Subject.Replace("(Aspose.Email Evaluation)", ""),
|
||||||
|
Time = message.Date,
|
||||||
|
Read = true,
|
||||||
|
Message = message.Body.Replace("EVALUATION ONLY. CREATED WITH ASPOSE.EMAIL FOR .NET. COPYRIGHT 2002-2022 ASPOSE PTY LTD. \r\n http://www.aspose.com/corporate/purchase/end-user-license-agreement.aspx: View EULA Online\r\n", ""),
|
||||||
|
Attachments = message.Attachments.Select(a => a.Name).ToArray()
|
||||||
|
};
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("folder")]
|
||||||
|
public async Task<ActionResult<SingleResult<ImapFolderInfo[]>>> GetFolder([FromBody] Credentials credentials) {
|
||||||
|
using var client = new ImapClient($"imap.{credentials.Domain}", credentials.Username, credentials.Password);
|
||||||
|
var folders = await client.ListFoldersAsync();
|
||||||
|
var results = new List<ImapFolderInfo>();
|
||||||
|
|
||||||
|
foreach (var folder in folders) {
|
||||||
|
results.Add(folder);
|
||||||
|
if (folder.HasChildren) {
|
||||||
|
var children = await client.ListFoldersAsync(folder.Name);
|
||||||
|
results.AddRange(children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SingleResult<ImapFolderInfo[]> { Value = results.ToArray() };
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("download/{id}/{attachment}")]
|
||||||
|
public async Task<FileStreamResult> DownloadAttachment([FromBody] Credentials credentials, [FromRoute] int id, [FromRoute] string attachment) {
|
||||||
|
using var client = new ImapClient($"imap.{credentials.Domain}", credentials.Username, credentials.Password);
|
||||||
|
var data = await client.FetchAttachmentAsync(id, attachment);
|
||||||
|
|
||||||
|
return new FileStreamResult(data.ContentStream, "application/octet-stream") {
|
||||||
|
FileDownloadName = attachment
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
namespace BetterIServ.Backend.Entities;
|
namespace BetterIServ.Backend.Entities;
|
||||||
|
|
||||||
public struct Credentials {
|
public class Credentials {
|
||||||
public string Domain { get; set; }
|
public string? Domain { get; set; }
|
||||||
public string Username { get; set; }
|
public string? Username { get; set; }
|
||||||
public string Password { get; set; }
|
public string? Password { get; set; }
|
||||||
|
public string? Token { get; set; }
|
||||||
}
|
}
|
||||||
13
BetterIServ.Backend/Entities/MailContent.cs
Normal file
13
BetterIServ.Backend/Entities/MailContent.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Aspose.Email;
|
||||||
|
|
||||||
|
namespace BetterIServ.Backend.Entities;
|
||||||
|
|
||||||
|
public struct MailContent {
|
||||||
|
public int Id { get; set; }
|
||||||
|
public MailAddress Sender { get; set; }
|
||||||
|
public string Subject { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public DateTime Time { get; set; }
|
||||||
|
public bool Read { get; set; }
|
||||||
|
public string[] Attachments { get; set; }
|
||||||
|
}
|
||||||
9
BetterIServ.Backend/Entities/MailData.cs
Normal file
9
BetterIServ.Backend/Entities/MailData.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace BetterIServ.Backend.Entities;
|
||||||
|
|
||||||
|
public sealed class MailData : Credentials {
|
||||||
|
|
||||||
|
public string? MailBody { get; set; }
|
||||||
|
public string? Receiver { get; set; }
|
||||||
|
public string? Subject { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
5
BetterIServ.Backend/Entities/SingleResult.cs
Normal file
5
BetterIServ.Backend/Entities/SingleResult.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace BetterIServ.Backend.Entities;
|
||||||
|
|
||||||
|
public class SingleResult<TValue> {
|
||||||
|
public TValue? Value { get; set; }
|
||||||
|
}
|
||||||
@@ -37,7 +37,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"styles": ["src/theme/variables.scss", "src/global.scss"],
|
"styles": ["src/theme/variables.scss", "src/global.scss"],
|
||||||
"scripts": []
|
"scripts": [],
|
||||||
|
"allowedCommonJsDependencies": ["file-saver"]
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
|
|||||||
19
BetterIServ.Mobile/package-lock.json
generated
19
BetterIServ.Mobile/package-lock.json
generated
@@ -23,6 +23,7 @@
|
|||||||
"@ionic/angular": "^7.0.0",
|
"@ionic/angular": "^7.0.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"ionicons": "^7.0.0",
|
"ionicons": "^7.0.0",
|
||||||
|
"marked": "^4.3.0",
|
||||||
"rxjs": "~7.5.0",
|
"rxjs": "~7.5.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.11.4"
|
"zone.js": "~0.11.4"
|
||||||
@@ -42,6 +43,7 @@
|
|||||||
"@ionic/angular-toolkit": "^9.0.0",
|
"@ionic/angular-toolkit": "^9.0.0",
|
||||||
"@types/file-saver": "^2.0.5",
|
"@types/file-saver": "^2.0.5",
|
||||||
"@types/jasmine": "~4.0.0",
|
"@types/jasmine": "~4.0.0",
|
||||||
|
"@types/marked": "^4.0.8",
|
||||||
"@types/node": "^12.11.1",
|
"@types/node": "^12.11.1",
|
||||||
"@typescript-eslint/eslint-plugin": "5.3.0",
|
"@typescript-eslint/eslint-plugin": "5.3.0",
|
||||||
"@typescript-eslint/parser": "5.3.0",
|
"@typescript-eslint/parser": "5.3.0",
|
||||||
@@ -3730,6 +3732,12 @@
|
|||||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/marked": {
|
||||||
|
"version": "4.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.8.tgz",
|
||||||
|
"integrity": "sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/mime": {
|
"node_modules/@types/mime": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
|
||||||
@@ -9998,6 +10006,17 @@
|
|||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/marked": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
|
||||||
|
"bin": {
|
||||||
|
"marked": "bin/marked.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/media-typer": {
|
"node_modules/media-typer": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
"@ionic/angular": "^7.0.0",
|
"@ionic/angular": "^7.0.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"ionicons": "^7.0.0",
|
"ionicons": "^7.0.0",
|
||||||
|
"marked": "^4.3.0",
|
||||||
"rxjs": "~7.5.0",
|
"rxjs": "~7.5.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.11.4"
|
"zone.js": "~0.11.4"
|
||||||
@@ -46,9 +47,10 @@
|
|||||||
"@angular/language-service": "^15.0.0",
|
"@angular/language-service": "^15.0.0",
|
||||||
"@capacitor/cli": "4.7.3",
|
"@capacitor/cli": "4.7.3",
|
||||||
"@ionic/angular-toolkit": "^9.0.0",
|
"@ionic/angular-toolkit": "^9.0.0",
|
||||||
"@types/jasmine": "~4.0.0",
|
|
||||||
"@types/node": "^12.11.1",
|
|
||||||
"@types/file-saver": "^2.0.5",
|
"@types/file-saver": "^2.0.5",
|
||||||
|
"@types/jasmine": "~4.0.0",
|
||||||
|
"@types/marked": "^4.0.8",
|
||||||
|
"@types/node": "^12.11.1",
|
||||||
"@typescript-eslint/eslint-plugin": "5.3.0",
|
"@typescript-eslint/eslint-plugin": "5.3.0",
|
||||||
"@typescript-eslint/parser": "5.3.0",
|
"@typescript-eslint/parser": "5.3.0",
|
||||||
"eslint": "^7.6.0",
|
"eslint": "^7.6.0",
|
||||||
|
|||||||
50
BetterIServ.Mobile/src/app/api/mail.service.ts
Normal file
50
BetterIServ.Mobile/src/app/api/mail.service.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import {IServService} from "./iserv.service";
|
||||||
|
import {HttpClient, HttpEvent} from "@angular/common/http";
|
||||||
|
import {MailContent, MailData, MailFolder} from "../entities/mail";
|
||||||
|
import {firstValueFrom, Observable} from "rxjs";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class MailService {
|
||||||
|
|
||||||
|
constructor(private iserv: IServService, private client: HttpClient) { }
|
||||||
|
|
||||||
|
public async getMails(folder: string, page: number): Promise<MailContent[]> {
|
||||||
|
const mails = await firstValueFrom(this.client.post<MailContent[]>(this.iserv.backend + `/mail/list/${page}?folder=${folder}`, this.iserv.userdata));
|
||||||
|
|
||||||
|
for (let mail of mails) {
|
||||||
|
mail.time = new Date(mail.time);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mails;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getMail(id: number): Promise<MailContent> {
|
||||||
|
const mail = await firstValueFrom(this.client.post<MailContent>(this.iserv.backend + "/mail/content/" + id, this.iserv.userdata));
|
||||||
|
|
||||||
|
mail.time = new Date(mail.time);
|
||||||
|
return mail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getFolders(): Promise<MailFolder[]> {
|
||||||
|
return (await firstValueFrom(this.client.post<{value: MailFolder[]}>(this.iserv.backend + "/mail/folder", this.iserv.userdata))).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async sendMail(subject: string, receiver: string, mailBody: string) {
|
||||||
|
const data: MailData = {
|
||||||
|
domain: this.iserv.userdata.domain,
|
||||||
|
username: this.iserv.userdata.username,
|
||||||
|
password: this.iserv.userdata.password,
|
||||||
|
token: this.iserv.userdata.token,
|
||||||
|
subject, receiver, mailBody
|
||||||
|
};
|
||||||
|
await firstValueFrom(this.client.post(this.iserv.backend + "/mail/send", data));
|
||||||
|
}
|
||||||
|
|
||||||
|
public downloadAttachment(mailId: number, attachment: string): Observable<HttpEvent<Blob>> {
|
||||||
|
return this.client.post(this.iserv.backend + `/mail/download/${mailId}/${attachment}`, this.iserv.userdata, {responseType: "blob", reportProgress: true, observe: "events"});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ export class AppComponent {
|
|||||||
|
|
||||||
public appPages = [
|
public appPages = [
|
||||||
{ title: 'Übersicht', url: '/home', icon: 'home' },
|
{ title: 'Übersicht', url: '/home', icon: 'home' },
|
||||||
{ title: 'E-Mail', url: '/email', icon: 'mail' },
|
{ title: 'E-Mail', url: '/mails', icon: 'mail' },
|
||||||
{ title: 'Dateien', url: '/files', icon: 'folder' },
|
{ title: 'Dateien', url: '/files', icon: 'folder' },
|
||||||
{ title: 'Aufgaben', url: '/tasks', icon: 'clipboard' },
|
{ title: 'Aufgaben', url: '/tasks', icon: 'clipboard' },
|
||||||
{ title: 'Stundenplan', url: '/schedule', icon: 'grid' },
|
{ title: 'Stundenplan', url: '/schedule', icon: 'grid' },
|
||||||
|
|||||||
@@ -18,4 +18,8 @@ export const routes: Routes = [
|
|||||||
path: 'files',
|
path: 'files',
|
||||||
loadComponent: () => import('./pages/files/files.page').then( m => m.FilesPage)
|
loadComponent: () => import('./pages/files/files.page').then( m => m.FilesPage)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'mails',
|
||||||
|
loadComponent: () => import('./pages/mails/mails.page').then( m => m.MailsPage)
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
28
BetterIServ.Mobile/src/app/entities/mail.ts
Normal file
28
BetterIServ.Mobile/src/app/entities/mail.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import {Userdata} from "./userdata";
|
||||||
|
|
||||||
|
export interface MailFolder {
|
||||||
|
name: string;
|
||||||
|
newMessageCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MailData extends Userdata {
|
||||||
|
mailBody: string;
|
||||||
|
receiver: string;
|
||||||
|
subject: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MailAddress {
|
||||||
|
displayName: string;
|
||||||
|
user: string;
|
||||||
|
address: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MailContent {
|
||||||
|
id: number;
|
||||||
|
sender: MailAddress;
|
||||||
|
subject: string;
|
||||||
|
message: string;
|
||||||
|
time: Date;
|
||||||
|
read: boolean;
|
||||||
|
attachments: string[];
|
||||||
|
}
|
||||||
@@ -4,21 +4,8 @@
|
|||||||
<ion-menu-button></ion-menu-button>
|
<ion-menu-button></ion-menu-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>Dateien</ion-title>
|
<ion-title>Dateien</ion-title>
|
||||||
<ion-progress-bar type="indeterminate" *ngIf="loading" />
|
<ion-buttons slot="end">
|
||||||
<ion-progress-bar [value]="progress" *ngIf="progress != -1" />
|
<ion-button (click)="onMove()" *ngIf="clipboard != undefined"><ion-icon ios="checkmark-circle-outline" md="checkmark-circle-sharp" ></ion-icon></ion-button>
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content [fullscreen]="true">
|
|
||||||
<ion-header collapse="condense">
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-title size="large">Dateien</ion-title>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-grid>
|
|
||||||
<ion-row>
|
|
||||||
<ion-col>
|
|
||||||
<ion-button (click)="upload.click()"><ion-icon ios="arrow-up-circle-outline" md="arrow-up-circle-sharp" /></ion-button>
|
<ion-button (click)="upload.click()"><ion-icon ios="arrow-up-circle-outline" md="arrow-up-circle-sharp" /></ion-button>
|
||||||
<ion-button id="create-folder"><ion-icon ios="add-circle-outline" md="add-circle-sharp" ></ion-icon></ion-button>
|
<ion-button id="create-folder"><ion-icon ios="add-circle-outline" md="add-circle-sharp" ></ion-icon></ion-button>
|
||||||
<form #uploadForm>
|
<form #uploadForm>
|
||||||
@@ -46,12 +33,18 @@
|
|||||||
</ion-content>
|
</ion-content>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ion-modal>
|
</ion-modal>
|
||||||
</ion-col>
|
</ion-buttons>
|
||||||
<ion-col *ngIf="clipboard">
|
<ion-progress-bar type="indeterminate" *ngIf="loading" />
|
||||||
<ion-button (click)="onMove()">Verschieben</ion-button>
|
<ion-progress-bar [value]="progress" *ngIf="progress != -1" />
|
||||||
</ion-col>
|
</ion-toolbar>
|
||||||
</ion-row>
|
</ion-header>
|
||||||
</ion-grid>
|
|
||||||
|
<ion-content [fullscreen]="true">
|
||||||
|
<ion-header collapse="condense">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title size="large">Dateien</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
<section class="container">
|
<section class="container">
|
||||||
<ion-list>
|
<ion-list>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {FormsModule} from '@angular/forms';
|
import {FormsModule} from '@angular/forms';
|
||||||
import {ActionSheetController, AlertController, IonicModule, Platform} from '@ionic/angular';
|
import {ActionSheetController, AlertController, IonicModule, Platform, ToastController} from '@ionic/angular';
|
||||||
import {WebdavService} from "../../api/webdav.service";
|
import {WebdavService} from "../../api/webdav.service";
|
||||||
import {DirectoryContent} from "../../entities/directoryContent";
|
import {DirectoryContent} from "../../entities/directoryContent";
|
||||||
import {File} from "@awesome-cordova-plugins/file/ngx";
|
import {File} from "@awesome-cordova-plugins/file/ngx";
|
||||||
@@ -24,7 +24,7 @@ export class FilesPage implements OnInit {
|
|||||||
|
|
||||||
public progress: number = -1;
|
public progress: number = -1;
|
||||||
|
|
||||||
constructor(private webdav: WebdavService, private platform: Platform, private menus: ActionSheetController, private alerts: AlertController) { }
|
constructor(private webdav: WebdavService, private platform: Platform, private menus: ActionSheetController, private alerts: AlertController, private toasts: ToastController) { }
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.directoryContent = await this.webdav.getDirectory(this.currentDirectory);
|
this.directoryContent = await this.webdav.getDirectory(this.currentDirectory);
|
||||||
@@ -122,9 +122,10 @@ export class FilesPage implements OnInit {
|
|||||||
await this.switchDirectory(this.currentDirectory);
|
await this.switchDirectory(this.currentDirectory);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
||||||
await (await this.alerts.create({
|
await (await this.toasts.create({
|
||||||
header: "Element gelöscht!",
|
message: "Element gelöscht!",
|
||||||
buttons: ["Ok"]
|
position: "bottom",
|
||||||
|
duration: 2000
|
||||||
})).present();
|
})).present();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,6 +153,12 @@ export class FilesPage implements OnInit {
|
|||||||
await this.switchDirectory(this.currentDirectory);
|
await this.switchDirectory(this.currentDirectory);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
form.reset();
|
form.reset();
|
||||||
|
|
||||||
|
await (await this.toasts.create({
|
||||||
|
message: "Element hochgeladen!",
|
||||||
|
position: "bottom",
|
||||||
|
duration: 2000
|
||||||
|
})).present();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createFolder(event: any) {
|
public async createFolder(event: any) {
|
||||||
@@ -160,6 +167,12 @@ export class FilesPage implements OnInit {
|
|||||||
await this.webdav.createFolder(this.currentDirectory + event.detail.data);
|
await this.webdav.createFolder(this.currentDirectory + event.detail.data);
|
||||||
await this.switchDirectory(this.currentDirectory);
|
await this.switchDirectory(this.currentDirectory);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
||||||
|
await (await this.toasts.create({
|
||||||
|
message: "Ordner erstellt!",
|
||||||
|
position: "bottom",
|
||||||
|
duration: 2000
|
||||||
|
})).present();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onMove() {
|
public async onMove() {
|
||||||
@@ -168,6 +181,12 @@ export class FilesPage implements OnInit {
|
|||||||
await this.switchDirectory(this.currentDirectory);
|
await this.switchDirectory(this.currentDirectory);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
delete this.clipboard;
|
delete this.clipboard;
|
||||||
|
|
||||||
|
await (await this.toasts.create({
|
||||||
|
message: "Element verschoben!",
|
||||||
|
position: "bottom",
|
||||||
|
duration: 2000
|
||||||
|
})).present();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
96
BetterIServ.Mobile/src/app/pages/mails/mails.page.html
Normal file
96
BetterIServ.Mobile/src/app/pages/mails/mails.page.html
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<ion-header [translucent]="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-menu-button></ion-menu-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>E-Mails</ion-title>
|
||||||
|
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button id="new-email"><ion-icon ios="add-circle-outline" md="add-circle-sharp" /></ion-button>
|
||||||
|
|
||||||
|
<ion-modal trigger="new-email" #newEmail>
|
||||||
|
<ng-template>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-button (click)="newEmail.dismiss()">Zurück</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>Neue E-Mail</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button (click)="sendMail(receiver.value.toString(), subject.value.toString(), message.value, newEmail)">Senden</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-progress-bar type="indeterminate" *ngIf="showLoading" />
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content class="ion-padding current-mail">
|
||||||
|
<form class="new-email">
|
||||||
|
<ion-input type="email" label="An" labelPlacement="floating" fill="outline" #receiver />
|
||||||
|
<ion-input type="text" label="Betreff" labelPlacement="floating" fill="outline" #subject />
|
||||||
|
<ion-textarea label="Nachricht" labelPlacement="floating" fill="outline" auto-grow="true" #message />
|
||||||
|
</form>
|
||||||
|
</ion-content>
|
||||||
|
</ng-template>
|
||||||
|
</ion-modal>
|
||||||
|
</ion-buttons>
|
||||||
|
|
||||||
|
<ion-progress-bar type="indeterminate" *ngIf="showLoading" />
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content [fullscreen]="true">
|
||||||
|
<ion-header collapse="condense">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title size="large">E-Mails</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-select label="Ordner" [value]="folders[0]" interface="action-sheet" (ionChange)="changeFolder(select.value)" #select>
|
||||||
|
<ion-select-option *ngFor="let folder of folders" [value]="folder">
|
||||||
|
{{folder.name}}
|
||||||
|
</ion-select-option>
|
||||||
|
</ion-select>
|
||||||
|
|
||||||
|
<ion-list>
|
||||||
|
<ion-item *ngFor="let message of mails" class="mail pointer" (click)="selectMail(message, mailModal)">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<ion-icon ios="mail-unread-outline" md="mail-unread-sharp" *ngIf="!message.read" />
|
||||||
|
<ion-label>{{message.sender.displayName}}</ion-label>
|
||||||
|
<ion-label class="date">{{message.time.toLocaleDateString()}}</ion-label>
|
||||||
|
</div>
|
||||||
|
<ion-label class="subject">{{message.subject}}</ion-label>
|
||||||
|
</div>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
<ion-infinite-scroll (ionInfinite)="loadMore($event)">
|
||||||
|
<ion-infinite-scroll-content></ion-infinite-scroll-content>
|
||||||
|
</ion-infinite-scroll>
|
||||||
|
|
||||||
|
<ion-modal #mailModal>
|
||||||
|
<ng-template>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-button (click)="mailModal.dismiss()">Zurück</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>{{currentMail?.sender.displayName}}</ion-title>
|
||||||
|
<ion-progress-bar type="indeterminate" *ngIf="showLoading" />
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content class="ion-padding current-mail">
|
||||||
|
<div class="header">
|
||||||
|
<ion-label class="subject">{{currentMail?.subject}}</ion-label>
|
||||||
|
<ion-label class="time">{{currentMail?.time.toLocaleDateString()}}</ion-label>
|
||||||
|
<ion-list *ngIf="currentMail?.attachments?.length > 0">
|
||||||
|
<ion-item *ngFor="let attachment of currentMail?.attachments" class="pointer" (click)="downloadAttachment(attachment, currentMail?.id)">
|
||||||
|
<ion-label>{{attachment}}</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</div>
|
||||||
|
<ion-item-divider />
|
||||||
|
<div class="message" [innerHtml]="currentMail?.message"></div>
|
||||||
|
</ion-content>
|
||||||
|
</ng-template>
|
||||||
|
</ion-modal>
|
||||||
|
|
||||||
|
</ion-content>
|
||||||
51
BetterIServ.Mobile/src/app/pages/mails/mails.page.scss
Normal file
51
BetterIServ.Mobile/src/app/pages/mails/mails.page.scss
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
ion-select {
|
||||||
|
padding-inline: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail > div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 7px;
|
||||||
|
margin-block: 15px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.subject, .date {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
ion-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
color: var(--ion-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-mail {
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.subject {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-email {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
109
BetterIServ.Mobile/src/app/pages/mails/mails.page.ts
Normal file
109
BetterIServ.Mobile/src/app/pages/mails/mails.page.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {FormsModule} from '@angular/forms';
|
||||||
|
import {InfiniteScrollCustomEvent, IonicModule, IonModal, Platform, ToastController} from '@ionic/angular';
|
||||||
|
import {MailService} from "../../api/mail.service";
|
||||||
|
import {MailContent, MailFolder} from "../../entities/mail";
|
||||||
|
import {marked} from "marked";
|
||||||
|
import {HttpDownloadProgressEvent, HttpEventType} from "@angular/common/http";
|
||||||
|
import {File} from "@awesome-cordova-plugins/file/ngx";
|
||||||
|
import {saveAs} from "file-saver";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-mails',
|
||||||
|
templateUrl: './mails.page.html',
|
||||||
|
styleUrls: ['./mails.page.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [IonicModule, CommonModule, FormsModule]
|
||||||
|
})
|
||||||
|
export class MailsPage implements OnInit {
|
||||||
|
|
||||||
|
public showLoading = false;
|
||||||
|
public mails: MailContent[] = [];
|
||||||
|
public folders: MailFolder[] = [];
|
||||||
|
public currentMail: MailContent;
|
||||||
|
private currentPage = 0;
|
||||||
|
private currentFolder: MailFolder;
|
||||||
|
|
||||||
|
constructor(private mail: MailService, private platform: Platform, private toasts: ToastController) { }
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
this.showLoading = true;
|
||||||
|
const folderResponse = this.mail.getFolders();
|
||||||
|
const mailResponse = this.mail.getMails("INBOX", this.currentPage);
|
||||||
|
await Promise.all([mailResponse, folderResponse]);
|
||||||
|
|
||||||
|
this.folders = await folderResponse;
|
||||||
|
this.mails = await mailResponse;
|
||||||
|
|
||||||
|
this.currentFolder = this.folders.filter(folder => folder.name == "INBOX")[0];
|
||||||
|
this.showLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async changeFolder(folder: MailFolder) {
|
||||||
|
this.showLoading = true;
|
||||||
|
this.currentFolder = folder;
|
||||||
|
this.currentPage = 0;
|
||||||
|
this.mails = await this.mail.getMails(this.currentFolder.name, this.currentPage);
|
||||||
|
this.showLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async loadMore(event: any) {
|
||||||
|
this.showLoading = true;
|
||||||
|
this.currentPage++;
|
||||||
|
const newMails = await this.mail.getMails(this.currentFolder.name, this.currentPage);
|
||||||
|
this.mails.push(...newMails);
|
||||||
|
this.showLoading = false;
|
||||||
|
await event.target.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async selectMail(message: MailContent, modal: IonModal) {
|
||||||
|
this.showLoading = true;
|
||||||
|
message.read = true;
|
||||||
|
this.currentMail = message;
|
||||||
|
|
||||||
|
const body = await this.mail.getMail(message.id);
|
||||||
|
this.currentMail.message = marked(body.message);
|
||||||
|
this.currentMail.attachments = body.attachments;
|
||||||
|
|
||||||
|
this.showLoading = false;
|
||||||
|
await modal.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async downloadAttachment(attachment: string, mailId: number) {
|
||||||
|
this.showLoading = true;
|
||||||
|
this.mail.downloadAttachment(mailId, attachment).subscribe(async event => {
|
||||||
|
if (event.type == HttpEventType.Response) {
|
||||||
|
const blob = event.body;
|
||||||
|
const file = new File();
|
||||||
|
|
||||||
|
if (this.platform.is('desktop')) {
|
||||||
|
saveAs(blob, attachment);
|
||||||
|
this.showLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadPath = (
|
||||||
|
this.platform.is('android')
|
||||||
|
) ? file.externalDataDirectory : file.documentsDirectory;
|
||||||
|
await file.writeFile(downloadPath, attachment, blob, {replace: true});
|
||||||
|
this.showLoading = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async sendMail(receiver: string, subject: string, message: string, modal: IonModal) {
|
||||||
|
this.showLoading = true;
|
||||||
|
await this.mail.sendMail(subject, receiver, message);
|
||||||
|
await modal.dismiss();
|
||||||
|
this.showLoading = false;
|
||||||
|
|
||||||
|
const toast = await this.toasts.create({
|
||||||
|
message: "E-Mail gesendet!",
|
||||||
|
duration: 2000,
|
||||||
|
position: "bottom"
|
||||||
|
});
|
||||||
|
await toast.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -28,3 +28,7 @@
|
|||||||
ion-menu-button {
|
ion-menu-button {
|
||||||
color: var(--ion-color-primary);
|
color: var(--ion-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user