Email is now Built-in! (#2635)
This commit is contained in:
parent
2a539da24c
commit
a85644fb6b
55 changed files with 5129 additions and 1047 deletions
|
|
@ -199,7 +199,7 @@ export class AccountService {
|
|||
}
|
||||
|
||||
resendConfirmationEmail(userId: number) {
|
||||
return this.httpClient.post<string>(this.baseUrl + 'account/resend-confirmation-email?userId=' + userId, {}, TextResonse);
|
||||
return this.httpClient.post<InviteUserResponse>(this.baseUrl + 'account/resend-confirmation-email?userId=' + userId, {});
|
||||
}
|
||||
|
||||
inviteUser(model: {email: string, roles: Array<string>, libraries: Array<number>, ageRestriction: AgeRestriction}) {
|
||||
|
|
@ -310,7 +310,7 @@ export class AccountService {
|
|||
}
|
||||
|
||||
|
||||
private refreshAccount() {
|
||||
refreshAccount() {
|
||||
if (this.currentUser === null || this.currentUser === undefined) return of();
|
||||
return this.httpClient.get<User>(this.baseUrl + 'account/refresh-account').pipe(map((user: User) => {
|
||||
if (user) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { UpdateVersionEvent } from '../_models/events/update-version-event';
|
|||
import { Job } from '../_models/job/job';
|
||||
import { KavitaMediaError } from '../admin/_models/media-error';
|
||||
import {TextResonse} from "../_types/text-response";
|
||||
import {map} from "rxjs/operators";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
|
@ -14,66 +15,62 @@ export class ServerService {
|
|||
|
||||
baseUrl = environment.apiUrl;
|
||||
|
||||
constructor(private httpClient: HttpClient) { }
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
|
||||
getServerInfo() {
|
||||
return this.httpClient.get<ServerInfoSlim>(this.baseUrl + 'server/server-info-slim');
|
||||
return this.http.get<ServerInfoSlim>(this.baseUrl + 'server/server-info-slim');
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
return this.httpClient.post(this.baseUrl + 'server/clear-cache', {});
|
||||
return this.http.post(this.baseUrl + 'server/clear-cache', {});
|
||||
}
|
||||
|
||||
cleanupWantToRead() {
|
||||
return this.httpClient.post(this.baseUrl + 'server/cleanup-want-to-read', {});
|
||||
return this.http.post(this.baseUrl + 'server/cleanup-want-to-read', {});
|
||||
}
|
||||
|
||||
backupDatabase() {
|
||||
return this.httpClient.post(this.baseUrl + 'server/backup-db', {});
|
||||
return this.http.post(this.baseUrl + 'server/backup-db', {});
|
||||
}
|
||||
|
||||
analyzeFiles() {
|
||||
return this.httpClient.post(this.baseUrl + 'server/analyze-files', {});
|
||||
return this.http.post(this.baseUrl + 'server/analyze-files', {});
|
||||
}
|
||||
|
||||
checkForUpdate() {
|
||||
return this.httpClient.get<UpdateVersionEvent>(this.baseUrl + 'server/check-update', {});
|
||||
return this.http.get<UpdateVersionEvent>(this.baseUrl + 'server/check-update', {});
|
||||
}
|
||||
|
||||
checkForUpdates() {
|
||||
return this.httpClient.get(this.baseUrl + 'server/check-for-updates', {});
|
||||
return this.http.get(this.baseUrl + 'server/check-for-updates', {});
|
||||
}
|
||||
|
||||
getChangelog() {
|
||||
return this.httpClient.get<UpdateVersionEvent[]>(this.baseUrl + 'server/changelog', {});
|
||||
return this.http.get<UpdateVersionEvent[]>(this.baseUrl + 'server/changelog', {});
|
||||
}
|
||||
|
||||
isServerAccessible() {
|
||||
return this.httpClient.get<boolean>(this.baseUrl + 'server/accessible');
|
||||
return this.http.get<boolean>(this.baseUrl + 'server/accessible');
|
||||
}
|
||||
|
||||
getRecurringJobs() {
|
||||
return this.httpClient.get<Job[]>(this.baseUrl + 'server/jobs');
|
||||
return this.http.get<Job[]>(this.baseUrl + 'server/jobs');
|
||||
}
|
||||
|
||||
convertMedia() {
|
||||
return this.httpClient.post(this.baseUrl + 'server/convert-media', {});
|
||||
return this.http.post(this.baseUrl + 'server/convert-media', {});
|
||||
}
|
||||
|
||||
bustCache() {
|
||||
return this.httpClient.post(this.baseUrl + 'server/bust-review-and-rec-cache', {});
|
||||
return this.http.post(this.baseUrl + 'server/bust-review-and-rec-cache', {});
|
||||
}
|
||||
|
||||
getMediaErrors() {
|
||||
return this.httpClient.get<Array<KavitaMediaError>>(this.baseUrl + 'server/media-errors', {});
|
||||
return this.http.get<Array<KavitaMediaError>>(this.baseUrl + 'server/media-errors', {});
|
||||
}
|
||||
|
||||
clearMediaAlerts() {
|
||||
return this.httpClient.post(this.baseUrl + 'server/clear-media-alerts', {});
|
||||
}
|
||||
|
||||
getEmailVersion() {
|
||||
return this.httpClient.get<string>(this.baseUrl + 'server/email-version', TextResonse);
|
||||
return this.http.post(this.baseUrl + 'server/clear-media-alerts', {});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { Component, Input } from '@angular/core';
|
||||
import {Component, inject, Input} from '@angular/core';
|
||||
import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { Member } from 'src/app/_models/auth/member';
|
||||
import { AccountService } from 'src/app/_services/account.service';
|
||||
import { SentenceCasePipe } from '../../../_pipes/sentence-case.pipe';
|
||||
import { NgIf } from '@angular/common';
|
||||
import {TranslocoDirective} from "@ngneat/transloco";
|
||||
import {translate, TranslocoDirective} from "@ngneat/transloco";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
|
||||
@Component({
|
||||
selector: 'app-reset-password-modal',
|
||||
|
|
@ -16,16 +17,21 @@ import {TranslocoDirective} from "@ngneat/transloco";
|
|||
})
|
||||
export class ResetPasswordModalComponent {
|
||||
|
||||
private readonly toastr = inject(ToastrService);
|
||||
private readonly accountService = inject(AccountService);
|
||||
public readonly modal = inject(NgbActiveModal);
|
||||
|
||||
@Input({required: true}) member!: Member;
|
||||
|
||||
errorMessage = '';
|
||||
resetPasswordForm: FormGroup = new FormGroup({
|
||||
password: new FormControl('', [Validators.required]),
|
||||
});
|
||||
|
||||
constructor(public modal: NgbActiveModal, private accountService: AccountService) { }
|
||||
|
||||
save() {
|
||||
this.accountService.resetPassword(this.member.username, this.resetPasswordForm.value.password,'').subscribe(() => {
|
||||
this.toastr.success(translate('toasts.password-updated'))
|
||||
this.modal.close();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { EncodeFormat } from "./encode-format";
|
||||
import {CoverImageSize} from "./cover-image-size";
|
||||
import {SmtpConfig} from "./smtp-config";
|
||||
|
||||
export interface ServerSettings {
|
||||
cacheDirectory: string;
|
||||
|
|
@ -22,4 +23,5 @@ export interface ServerSettings {
|
|||
onDeckProgressDays: number;
|
||||
onDeckUpdateDays: number;
|
||||
coverImageSize: CoverImageSize;
|
||||
smtpConfig: SmtpConfig;
|
||||
}
|
||||
|
|
|
|||
11
UI/Web/src/app/admin/_models/smtp-config.ts
Normal file
11
UI/Web/src/app/admin/_models/smtp-config.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export interface SmtpConfig {
|
||||
senderAddress: string;
|
||||
senderDisplayName: string;
|
||||
userName: string;
|
||||
password: string;
|
||||
host: string;
|
||||
port: number;
|
||||
enableSsl: boolean;
|
||||
sizeLimit: number;
|
||||
customizedTemplates: boolean;
|
||||
}
|
||||
|
|
@ -2,25 +2,10 @@
|
|||
<div class="container-fluid">
|
||||
<form [formGroup]="settingsForm" *ngIf="serverSettings !== undefined">
|
||||
<h4>{{t('title')}}</h4>
|
||||
<p [innerHTML]="t('description', {link: link}) | safeHtml">
|
||||
<span class="text-warning">{{t('send-to-warning')}}</span>
|
||||
</p>
|
||||
<div class="mb-3">
|
||||
<label for="settings-emailservice" class="form-label">{{t('email-url-label')}}</label><i class="ms-1 fa fa-info-circle" placement="right" [ngbTooltip]="emailServiceTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #emailServiceTooltip>{{t('email-url-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-emailservice-help"><ng-container [ngTemplateOutlet]="emailServiceTooltip"></ng-container></span>
|
||||
<div class="input-group">
|
||||
<input id="settings-emailservice" aria-describedby="settings-emailservice-help" class="form-control" formControlName="emailServiceUrl" type="url" autocapitalize="off" inputmode="url">
|
||||
<button class="btn btn-outline-secondary" (click)="resetEmailServiceUrl()">
|
||||
{{t('reset')}}
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" (click)="testEmailServiceUrl()">
|
||||
{{t('test')}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<p>You must fill out both Host Name and SMTP settings to use email-based functionality within Kavita.</p>
|
||||
|
||||
<div class="mb-3 pe-2 ps-2 ">
|
||||
<label for="settings-hostname" class="form-label">{{t('host-name-label')}}</label><i class="fa fa-info-circle ms-1" placement="right" [ngbTooltip]="hostNameTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #hostNameTooltip>{{t('host-name-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-hostname-help">
|
||||
|
|
@ -35,7 +20,93 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 ps-2 mb-2">
|
||||
<label for="settings-sender-address" class="form-label">{{t('sender-address-label')}}</label>
|
||||
<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="senderAddressTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #senderAddressTooltip>{{t('sender-address-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-sender-address-help"><ng-container [ngTemplateOutlet]="senderAddressTooltip"></ng-container></span>
|
||||
<input type="text" class="form-control" aria-describedby="manga-header" formControlName="senderAddress" id="settings-sender-address" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12 pe-2 ps-2 mb-2">
|
||||
<label for="settings-sender-displayname" class="form-label">{{t('sender-displayname-label')}}</label>
|
||||
<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="senderDisplayNameTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #senderDisplayNameTooltip>{{t('sender-displayname-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-sender-displayname-help"><ng-container [ngTemplateOutlet]="senderDisplayNameTooltip"></ng-container></span>
|
||||
<input type="text" class="form-control" aria-describedby="manga-header" formControlName="senderDisplayName" id="settings-sender-displayname" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-4 col-sm-12 pe-2 ps-2 mb-2">
|
||||
<label for="settings-sender-address" class="form-label">{{t('host-label')}}</label>
|
||||
<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="hostTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #hostTooltip>{{t('host-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-host-help"><ng-container [ngTemplateOutlet]="hostTooltip"></ng-container></span>
|
||||
<input type="text" class="form-control" aria-describedby="manga-header" formControlName="host" id="settings-host" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-sm-12 pe-2 ps-2 mb-2">
|
||||
<label for="settings-port" class="form-label">{{t('port-label')}}</label>
|
||||
<input type="number" min="1" class="form-control" aria-describedby="manga-header" formControlName="port" id="settings-port" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-sm-12 pe-2 ps-2 mb-2">
|
||||
<div class="form-check form-switch" style="margin-top: 36px">
|
||||
<input type="checkbox" id="settings-enable-ssl" role="switch" formControlName="enableSsl" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="settings-enable-ssl">{{t('enable-ssl-label')}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 ps-2 mb-2">
|
||||
<label for="settings-username" class="form-label">{{t('username-label')}}</label>
|
||||
<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="usernameTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #usernameTooltip>{{t('username-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-username-help"><ng-container [ngTemplateOutlet]="usernameTooltip"></ng-container></span>
|
||||
<input type="text" class="form-control" aria-describedby="manga-header" formControlName="userName" id="settings-username" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12 pe-2 ps-2 mb-2">
|
||||
<label for="settings-password" class="form-label">{{t('password-label')}}</label>
|
||||
<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="passwordTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #passwordTooltip>{{t('password-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-password-help"><ng-container [ngTemplateOutlet]="usernameTooltip"></ng-container></span>
|
||||
<input type="password" class="form-control" aria-describedby="manga-header" formControlName="password" id="settings-password" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 ps-2 mb-2">
|
||||
<label for="settings-size-limit" class="form-label">{{t('size-limit-label')}}</label>
|
||||
<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="sizeLimitTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #sizeLimitTooltip>{{t('size-limit-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-size-limit-help"><ng-container [ngTemplateOutlet]="sizeLimitTooltip"></ng-container></span>
|
||||
<input type="text" class="form-control" aria-describedby="manga-header" formControlName="sizeLimit" id="settings-size-limit" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12 pe-2 ps-2 mb-2">
|
||||
<div class="form-check form-switch" style="margin-top: 36px">
|
||||
<input type="checkbox" id="settings-customized-templates" role="switch" formControlName="customizedTemplates" class="form-check-input"
|
||||
aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="settings-customized-templates">{{t('customized-templates-label')}}</label>
|
||||
|
||||
<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="customizedTemplatesTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #customizedTemplatesTooltip>{{t('customized-templates-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-customized-templates-help"><ng-container [ngTemplateOutlet]="customizedTemplatesTooltip"></ng-container></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end">
|
||||
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="test()">{{t('test')}}</button>
|
||||
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetToDefaults()">{{t('reset-to-default')}}</button>
|
||||
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetForm()">{{t('reset')}}</button>
|
||||
<button type="submit" class="flex-fill btn btn-primary" (click)="saveSettings()" [disabled]="!settingsForm.dirty">{{t('save')}}</button>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,20 @@
|
|||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
|
||||
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
import {ToastrService} from 'ngx-toastr';
|
||||
import {forkJoin, take} from 'rxjs';
|
||||
import {EmailTestResult, SettingsService} from '../settings.service';
|
||||
import {take} from 'rxjs';
|
||||
import {SettingsService} from '../settings.service';
|
||||
import {ServerSettings} from '../_models/server-settings';
|
||||
import {NgbTooltip} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {NgIf, NgTemplateOutlet} from '@angular/common';
|
||||
import {translate, TranslocoModule, TranslocoService} from "@ngneat/transloco";
|
||||
import {
|
||||
NgbAccordionBody,
|
||||
NgbAccordionButton,
|
||||
NgbAccordionCollapse,
|
||||
NgbAccordionDirective, NgbAccordionHeader, NgbAccordionItem,
|
||||
NgbTooltip
|
||||
} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {NgForOf, NgIf, NgTemplateOutlet, TitleCasePipe} from '@angular/common';
|
||||
import {translate, TranslocoModule} from "@ngneat/transloco";
|
||||
import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe";
|
||||
import {ServerService} from "../../_services/server.service";
|
||||
import {ManageAlertsComponent} from "../manage-alerts/manage-alerts.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-manage-email-settings',
|
||||
|
|
@ -16,38 +22,47 @@ import {ServerService} from "../../_services/server.service";
|
|||
styleUrls: ['./manage-email-settings.component.scss'],
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [NgIf, ReactiveFormsModule, NgbTooltip, NgTemplateOutlet, TranslocoModule, SafeHtmlPipe]
|
||||
imports: [NgIf, ReactiveFormsModule, NgbTooltip, NgTemplateOutlet, TranslocoModule, SafeHtmlPipe, ManageAlertsComponent, NgbAccordionBody, NgbAccordionButton, NgbAccordionCollapse, NgbAccordionDirective, NgbAccordionHeader, NgbAccordionItem, NgForOf, TitleCasePipe]
|
||||
})
|
||||
export class ManageEmailSettingsComponent implements OnInit {
|
||||
|
||||
serverSettings!: ServerSettings;
|
||||
settingsForm: FormGroup = new FormGroup({});
|
||||
link = '<a href="https://github.com/Kareadita/KavitaEmail" target="_blank" rel="noopener noreferrer">Kavita Email</a>';
|
||||
emailVersion: string | null = null;
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly serverService = inject(ServerService);
|
||||
private readonly settingsService = inject(SettingsService);
|
||||
private readonly toastr = inject(ToastrService);
|
||||
|
||||
constructor() { }
|
||||
serverSettings!: ServerSettings;
|
||||
settingsForm: FormGroup = new FormGroup({});
|
||||
|
||||
ngOnInit(): void {
|
||||
this.settingsService.getServerSettings().pipe(take(1)).subscribe((settings: ServerSettings) => {
|
||||
this.serverSettings = settings;
|
||||
this.settingsForm.addControl('emailServiceUrl', new FormControl(this.serverSettings.emailServiceUrl, [Validators.required]));
|
||||
this.settingsForm.addControl('hostName', new FormControl(this.serverSettings.hostName, []));
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
this.serverService.getEmailVersion().subscribe(version => {
|
||||
this.emailVersion = version;
|
||||
this.settingsForm.addControl('host', new FormControl(this.serverSettings.smtpConfig.host, []));
|
||||
this.settingsForm.addControl('port', new FormControl(this.serverSettings.smtpConfig.port, []));
|
||||
this.settingsForm.addControl('userName', new FormControl(this.serverSettings.smtpConfig.userName, []));
|
||||
this.settingsForm.addControl('enableSsl', new FormControl(this.serverSettings.smtpConfig.enableSsl, []));
|
||||
this.settingsForm.addControl('password', new FormControl(this.serverSettings.smtpConfig.password, []));
|
||||
this.settingsForm.addControl('senderAddress', new FormControl(this.serverSettings.smtpConfig.senderAddress, []));
|
||||
this.settingsForm.addControl('senderDisplayName', new FormControl(this.serverSettings.smtpConfig.senderDisplayName, []));
|
||||
this.settingsForm.addControl('sizeLimit', new FormControl(this.serverSettings.smtpConfig.sizeLimit, [Validators.min(1)]));
|
||||
this.settingsForm.addControl('customizedTemplates', new FormControl(this.serverSettings.smtpConfig.customizedTemplates, [Validators.min(1)]));
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
resetForm() {
|
||||
this.settingsForm.get('emailServiceUrl')?.setValue(this.serverSettings.emailServiceUrl);
|
||||
this.settingsForm.get('hostName')?.setValue(this.serverSettings.hostName);
|
||||
|
||||
this.settingsForm.addControl('host', new FormControl(this.serverSettings.smtpConfig.host, []));
|
||||
this.settingsForm.addControl('port', new FormControl(this.serverSettings.smtpConfig.port, []));
|
||||
this.settingsForm.addControl('userName', new FormControl(this.serverSettings.smtpConfig.userName, []));
|
||||
this.settingsForm.addControl('enableSsl', new FormControl(this.serverSettings.smtpConfig.enableSsl, []));
|
||||
this.settingsForm.addControl('password', new FormControl(this.serverSettings.smtpConfig.password, []));
|
||||
this.settingsForm.addControl('senderAddress', new FormControl(this.serverSettings.smtpConfig.senderAddress, []));
|
||||
this.settingsForm.addControl('senderDisplayName', new FormControl(this.serverSettings.smtpConfig.senderDisplayName, []));
|
||||
this.settingsForm.addControl('sizeLimit', new FormControl(this.serverSettings.smtpConfig.sizeLimit, [Validators.min(1)]));
|
||||
this.settingsForm.addControl('customizedTemplates', new FormControl(this.serverSettings.smtpConfig.customizedTemplates, [Validators.min(1)]));
|
||||
this.settingsForm.markAsPristine();
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
|
@ -57,6 +72,15 @@ export class ManageEmailSettingsComponent implements OnInit {
|
|||
modelSettings.emailServiceUrl = this.settingsForm.get('emailServiceUrl')?.value;
|
||||
modelSettings.hostName = this.settingsForm.get('hostName')?.value;
|
||||
|
||||
modelSettings.smtpConfig.host = this.settingsForm.get('host')?.value;
|
||||
modelSettings.smtpConfig.port = this.settingsForm.get('port')?.value;
|
||||
modelSettings.smtpConfig.userName = this.settingsForm.get('userName')?.value;
|
||||
modelSettings.smtpConfig.enableSsl = this.settingsForm.get('enableSsl')?.value;
|
||||
modelSettings.smtpConfig.password = this.settingsForm.get('password')?.value;
|
||||
modelSettings.smtpConfig.senderAddress = this.settingsForm.get('senderAddress')?.value;
|
||||
modelSettings.smtpConfig.senderDisplayName = this.settingsForm.get('senderDisplayName')?.value;
|
||||
modelSettings.smtpConfig.sizeLimit = this.settingsForm.get('sizeLimit')?.value;
|
||||
modelSettings.smtpConfig.customizedTemplates = this.settingsForm.get('customizedTemplates')?.value;
|
||||
|
||||
this.settingsService.updateServerSettings(modelSettings).pipe(take(1)).subscribe((settings: ServerSettings) => {
|
||||
this.serverSettings = settings;
|
||||
|
|
@ -77,32 +101,13 @@ export class ManageEmailSettingsComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
resetEmailServiceUrl() {
|
||||
this.settingsService.resetEmailServerSettings().pipe(take(1)).subscribe((settings: ServerSettings) => {
|
||||
this.serverSettings.emailServiceUrl = settings.emailServiceUrl;
|
||||
this.resetForm();
|
||||
this.toastr.success(translate('toasts.email-service-reset'));
|
||||
}, (err: any) => {
|
||||
console.error('error: ', err);
|
||||
});
|
||||
}
|
||||
|
||||
testEmailServiceUrl() {
|
||||
if (this.settingsForm.get('emailServiceUrl')?.value === '') return;
|
||||
forkJoin([this.settingsService.testEmailServerSettings(this.settingsForm.get('emailServiceUrl')?.value), this.serverService.getEmailVersion()])
|
||||
.pipe(take(1)).subscribe(async (results) => {
|
||||
const result = results[0] as EmailTestResult;
|
||||
if (result.successful) {
|
||||
const version = ('. Kavita Email: ' + results[1] ? 'v' + results[1] : '');
|
||||
this.toastr.success(translate('toasts.email-service-reachable') + ' - ' + version);
|
||||
test() {
|
||||
this.settingsService.testEmailServerSettings().subscribe(res => {
|
||||
if (res.successful) {
|
||||
this.toastr.success(translate('toasts.email-sent', {email: res.emailAddress}));
|
||||
} else {
|
||||
this.toastr.error(translate('toasts.email-service-unresponsive') + result.errorMessage.split('(')[0]);
|
||||
this.toastr.error(translate('toasts.email-not-sent-test'))
|
||||
}
|
||||
|
||||
}, (err: any) => {
|
||||
console.error('error: ', err);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ export class ManageUsersComponent implements OnInit {
|
|||
setTimeout(() => {
|
||||
this.loadMembers();
|
||||
this.toastr.success(this.translocoService.translate('toasts.user-deleted', {user: member.username}));
|
||||
}, 30); // SetTimeout because I've noticed this can run superfast and not give enough time for data to flush
|
||||
}, 30); // SetTimeout because I've noticed this can run super fast and not give enough time for data to flush
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -112,15 +112,13 @@ export class ManageUsersComponent implements OnInit {
|
|||
}
|
||||
|
||||
resendEmail(member: Member) {
|
||||
this.serverService.isServerAccessible().subscribe(canAccess => {
|
||||
this.accountService.resendConfirmationEmail(member.id).subscribe(async (email) => {
|
||||
if (canAccess) {
|
||||
this.toastr.info(this.translocoService.translate('toasts.email-sent', {user: member.username}));
|
||||
return;
|
||||
}
|
||||
await this.confirmService.alert(
|
||||
this.translocoService.translate('toasts.click-email-link') + '<br/> <a href="' + email + '" target="_blank" rel="noopener noreferrer">' + email + '</a>');
|
||||
});
|
||||
this.accountService.resendConfirmationEmail(member.id).subscribe(async (response) => {
|
||||
if (response.emailSent) {
|
||||
this.toastr.info(this.translocoService.translate('toasts.email-sent', {email: member.username}));
|
||||
return;
|
||||
}
|
||||
await this.confirmService.alert(
|
||||
this.translocoService.translate('toasts.click-email-link') + '<br/> <a href="' + response.emailLink + '" target="_blank" rel="noopener noreferrer">' + response.emailLink + '</a>');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { ServerSettings } from './_models/server-settings';
|
|||
export interface EmailTestResult {
|
||||
successful: boolean;
|
||||
errorMessage: string;
|
||||
emailAddress: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
|
|
@ -46,12 +47,12 @@ export class SettingsService {
|
|||
return this.http.post<ServerSettings>(this.baseUrl + 'settings/reset-base-url', {});
|
||||
}
|
||||
|
||||
resetEmailServerSettings() {
|
||||
return this.http.post<ServerSettings>(this.baseUrl + 'settings/reset-email-url', {});
|
||||
testEmailServerSettings() {
|
||||
return this.http.post<EmailTestResult>(this.baseUrl + 'settings/test-email-url', {});
|
||||
}
|
||||
|
||||
testEmailServerSettings(emailUrl: string) {
|
||||
return this.http.post<EmailTestResult>(this.baseUrl + 'settings/test-email-url', {url: emailUrl});
|
||||
isEmailSetup() {
|
||||
return this.http.get<string>(this.baseUrl + 'server/is-email-setup', TextResonse).pipe(map(d => d == "true"));
|
||||
}
|
||||
|
||||
getTaskFrequencies() {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,9 @@ export class ConfirmEmailChangeComponent implements OnInit {
|
|||
this.accountService.confirmEmailUpdate({email: this.email, token: this.token}).subscribe((errors) => {
|
||||
this.confirmed = true;
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
// Once we are confirmed, we need to refresh our user information (in case the user is already authenticated)
|
||||
this.accountService.refreshAccount().subscribe();
|
||||
setTimeout(() => this.router.navigateByUrl('login'), 2000);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<div class="card-title">
|
||||
<div class="container-fluid row mb-2">
|
||||
<div class="col-10 col-sm-11">
|
||||
<h4 id="email-card">{{t('email-label')}}
|
||||
<h4 id="email-card">{{t('email-title')}}
|
||||
@if(emailConfirmed) {
|
||||
<i class="fa-solid fa-circle-check ms-1 confirm-icon" aria-hidden="true" [ngbTooltip]="t('email-confirmed')"></i>
|
||||
<span class="visually-hidden">{{t('email-confirmed')}}</span>
|
||||
|
|
|
|||
|
|
@ -74,7 +74,13 @@ export class ChangeEmailComponent implements OnInit {
|
|||
if (updateEmailResponse.emailSent) {
|
||||
this.toastr.success(translate('toasts.email-sent-to'));
|
||||
} else {
|
||||
this.toastr.success(translate('toasts.change-email-private'));
|
||||
this.toastr.success(translate('toasts.change-email-no-email'));
|
||||
this.accountService.refreshAccount().subscribe(user => {
|
||||
this.user = user;
|
||||
this.form.get('email')?.setValue(this.user?.email);
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
this.isViewMode = true;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@
|
|||
{{t('description')}}
|
||||
</p>
|
||||
|
||||
@if(hasEmailSetup) {
|
||||
<div class="alert alert-warning" role="alert">{{t('email-setup-alert')}}</div>
|
||||
}
|
||||
|
||||
<div #collapse="ngbCollapse" [(ngbCollapse)]="addDeviceIsCollapsed">
|
||||
<app-edit-device [device]="device" (deviceAdded)="loadDevices()" (deviceUpdated)="loadDevices()"></app-edit-device>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { Subject } from 'rxjs';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
inject,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { Device } from 'src/app/_models/device/device';
|
||||
import { DeviceService } from 'src/app/_services/device.service';
|
||||
import { DevicePlatformPipe } from '../../_pipes/device-platform.pipe';
|
||||
|
|
@ -9,6 +13,7 @@ import { NgIf, NgFor } from '@angular/common';
|
|||
import { EditDeviceComponent } from '../edit-device/edit-device.component';
|
||||
import { NgbCollapse } from '@ng-bootstrap/ng-bootstrap';
|
||||
import {TranslocoDirective} from "@ngneat/transloco";
|
||||
import {SettingsService} from "../../admin/settings.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-manage-devices',
|
||||
|
|
@ -18,26 +23,25 @@ import {TranslocoDirective} from "@ngneat/transloco";
|
|||
standalone: true,
|
||||
imports: [NgbCollapse, EditDeviceComponent, NgIf, NgFor, SentenceCasePipe, DevicePlatformPipe, TranslocoDirective]
|
||||
})
|
||||
export class ManageDevicesComponent implements OnInit, OnDestroy {
|
||||
export class ManageDevicesComponent implements OnInit {
|
||||
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly deviceService = inject(DeviceService);
|
||||
private readonly settingsService = inject(SettingsService);
|
||||
|
||||
devices: Array<Device> = [];
|
||||
addDeviceIsCollapsed: boolean = true;
|
||||
device: Device | undefined;
|
||||
|
||||
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
constructor(public deviceService: DeviceService, private toastr: ToastrService,
|
||||
private readonly cdRef: ChangeDetectorRef) { }
|
||||
hasEmailSetup = false;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.settingsService.isEmailSetup().subscribe(res => {
|
||||
this.hasEmailSetup = res;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
this.loadDevices();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy.next();
|
||||
this.onDestroy.complete();
|
||||
}
|
||||
|
||||
loadDevices() {
|
||||
this.addDeviceIsCollapsed = true;
|
||||
|
|
|
|||
|
|
@ -17,406 +17,404 @@
|
|||
}
|
||||
|
||||
@defer (when tab.fragment === FragmentID.Preferences; prefetch on idle) {
|
||||
<ng-container *ngIf="tab.fragment === FragmentID.Preferences">
|
||||
<p>
|
||||
{{t('pref-description')}}
|
||||
</p>
|
||||
<ng-container *ngIf="tab.fragment === FragmentID.Preferences">
|
||||
<p>
|
||||
{{t('pref-description')}}
|
||||
</p>
|
||||
|
||||
<form [formGroup]="settingsForm" *ngIf="user !== undefined">
|
||||
<div ngbAccordion [closeOthers]="true" #acc="ngbAccordion">
|
||||
<div ngbAccordionItem [id]="AccordionPanelID.GlobalSettings" [collapsed]="false">
|
||||
<h2 class="accordion-header" ngbAccordionHeader>
|
||||
<button class="accordion-button" ngbAccordionButton type="button" [attr.aria-expanded]="acc.isExpanded(AccordionPanelID.GlobalSettings)" aria-controls="collapseOne">
|
||||
{{t('global-settings-title')}}
|
||||
</button>
|
||||
</h2>
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody>
|
||||
<ng-template>
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-global-layoutmode" class="form-label">{{t('page-layout-mode-label')}}</label>
|
||||
<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="layoutModeTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #layoutModeTooltip>{{t('page-layout-mode-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-global-layoutmode-help">
|
||||
<ng-container [ngTemplateOutlet]="layoutModeTooltip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="manga-header" formControlName="globalPageLayoutMode" id="settings-global-layoutmode">
|
||||
<option *ngFor="let opt of pageLayoutModesTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-global-locale" class="form-label">{{t('locale-label')}}</label>
|
||||
<i class="fa fa-info-circle ms-1"
|
||||
aria-hidden="true" placement="right" [ngbTooltip]="localeTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #localeTooltip>{{t('locale-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-global-locale-help">
|
||||
<ng-container [ngTemplateOutlet]="localeTooltip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="manga-header" formControlName="locale" id="settings-global-locale">
|
||||
<option *ngFor="let opt of locales" [value]="opt.isoCode">{{opt.title | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="blur-unread-summaries" role="switch" formControlName="blurUnreadSummaries" class="form-check-input" aria-describedby="settings-global-blurUnreadSummaries-help" [value]="true" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="blur-unread-summaries">{{t('blur-unread-summaries-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="blurUnreadSummariesTooltip" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
|
||||
<ng-template #blurUnreadSummariesTooltip>{{t('blur-unread-summaries-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-global-blurUnreadSummaries-help">
|
||||
<ng-container [ngTemplateOutlet]="blurUnreadSummariesTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="prompt-download" role="switch" formControlName="promptForDownloadSize" class="form-check-input" aria-describedby="settings-global-promptForDownloadSize-help" [value]="true" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="prompt-download">{{t('prompt-on-download-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="promptForDownloadSizeTooltip" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
|
||||
<ng-template #promptForDownloadSizeTooltip>{{t('prompt-on-download-tooltip', {size: '100'})}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-global-promptForDownloadSize-help">
|
||||
<ng-container [ngTemplateOutlet]="promptForDownloadSizeTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="no-transitions" role="switch" formControlName="noTransitions" class="form-check-input"
|
||||
aria-describedby="settings-global-noTransitions-help" [value]="true" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="no-transitions">{{t('disable-animations-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="noTransitionsTooltip" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
|
||||
<ng-template #noTransitionsTooltip>{{t('disable-animations-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-global-noTransitions-help">
|
||||
<ng-container [ngTemplateOutlet]="noTransitionsTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-12 col-sm-12 pe-2 mb-2">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="collapse-relationships" role="switch" formControlName="collapseSeriesRelationships"
|
||||
aria-describedby="settings-collapse-relationships-help" class="form-check-input" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="collapse-relationships">{{t('collapse-series-relationships-label')}}</label>
|
||||
<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="collapseSeriesRelationshipsTooltip" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
<ng-template #collapseSeriesRelationshipsTooltip>{{t('collapse-series-relationships-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-collapse-relationships-help">
|
||||
<ng-container [ngTemplateOutlet]="collapseSeriesRelationshipsTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-12 col-sm-12 pe-2 mb-2">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="share-reviews" role="switch" formControlName="shareReviews"
|
||||
aria-describedby="settings-share-reviews-help" class="form-check-input" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="share-reviews">{{t('share-series-reviews-label')}}</label>
|
||||
<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="shareReviewsTooltip" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
<ng-template #shareReviewsTooltip>{{t('share-series-reviews-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-share-reviews-help">
|
||||
<ng-container [ngTemplateOutlet]="shareReviewsTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end mb-3">
|
||||
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetForm()" aria-describedby="reading-panel">{{t('reset')}}</button>
|
||||
<button type="submit" class="flex-fill btn btn-primary" (click)="save()" aria-describedby="reading-panel" [disabled]="!settingsForm.dirty">{{t('save')}}</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ngbAccordionItem [id]="AccordionPanelID.ImageReader">
|
||||
<form [formGroup]="settingsForm" *ngIf="user !== undefined">
|
||||
<div ngbAccordion [closeOthers]="true" #acc="ngbAccordion">
|
||||
<div ngbAccordionItem [id]="AccordionPanelID.GlobalSettings" [collapsed]="false">
|
||||
<h2 class="accordion-header" ngbAccordionHeader>
|
||||
<button class="accordion-button" ngbAccordionButton type="button" [attr.aria-expanded]="acc.isExpanded(AccordionPanelID.ImageReader)" aria-controls="collapseOne">
|
||||
{{t('image-reader-settings-title')}}
|
||||
</button>
|
||||
<button class="accordion-button" ngbAccordionButton type="button" [attr.aria-expanded]="acc.isExpanded(AccordionPanelID.GlobalSettings)" aria-controls="collapseOne">
|
||||
{{t('global-settings-title')}}
|
||||
</button>
|
||||
</h2>
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody>
|
||||
<ng-template>
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-reading-direction" class="form-label">{{t('reading-direction-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="readingDirectionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #readingDirectionTooltip>{{t('reading-direction-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-reading-direction-help">
|
||||
<ng-container [ngTemplateOutlet]="readingDirectionTooltip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="manga-header" formControlName="readingDirection" id="settings-reading-direction">
|
||||
<option *ngFor="let opt of readingDirectionsTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
<label for="settings-global-layoutmode" class="form-label">{{t('page-layout-mode-label')}}</label>
|
||||
<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="layoutModeTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #layoutModeTooltip>{{t('page-layout-mode-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-global-layoutmode-help">
|
||||
<ng-container [ngTemplateOutlet]="layoutModeTooltip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="manga-header" formControlName="globalPageLayoutMode" id="settings-global-layoutmode">
|
||||
<option *ngFor="let opt of pageLayoutModesTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-global-locale" class="form-label">{{t('locale-label')}}</label>
|
||||
<i class="fa fa-info-circle ms-1"
|
||||
aria-hidden="true" placement="right" [ngbTooltip]="localeTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #localeTooltip>{{t('locale-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-global-locale-help">
|
||||
<ng-container [ngTemplateOutlet]="localeTooltip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="manga-header" formControlName="locale" id="settings-global-locale">
|
||||
<option *ngFor="let opt of locales" [value]="opt.isoCode">{{opt.title | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-scaling-option" class="form-label">{{t('scaling-option-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="scalingOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #scalingOptionTooltip>{{t('scaling-option-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-scaling-option-help">
|
||||
<ng-container [ngTemplateOutlet]="scalingOptionTooltip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="manga-header" formControlName="scalingOption" id="settings-scaling-option">
|
||||
<option *ngFor="let opt of scalingOptionsTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-pagesplit-option" class="form-label">{{t('page-splitting-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="pageSplitOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #pageSplitOptionTooltip>{{t('page-splitting-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-pagesplit-option-help">
|
||||
<ng-container [ngTemplateOutlet]="pageSplitOptionTooltip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="manga-header" formControlName="pageSplitOption" id="settings-pagesplit-option">
|
||||
<option *ngFor="let opt of pageSplitOptionsTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-readingmode-option" class="form-label">{{t('reading-mode-label')}}</label>
|
||||
<select class="form-select" aria-describedby="manga-header" formControlName="readerMode" id="settings-readingmode-option">
|
||||
<option *ngFor="let opt of readingModesTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2" *ngIf="true">
|
||||
<label for="settings-layoutmode-option" class="form-label">{{t('layout-mode-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="layoutModeTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #layoutModeTooltip>{{t('layout-mode-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-layoutmode-option-help">
|
||||
<ng-container [ngTemplateOutlet]="layoutModeTooltip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="manga-header" formControlName="layoutMode" id="settings-layoutmode-option">
|
||||
<option *ngFor="let opt of layoutModesTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-background-color-option" class="form-label">{{t('background-color-label')}}</label>
|
||||
<input [value]="user!.preferences!.backgroundColor"
|
||||
class="form-control"
|
||||
id="settings-background-color-option"
|
||||
(colorPickerChange)="handleBackgroundColorChange()"
|
||||
[style.background]="user!.preferences!.backgroundColor"
|
||||
[cpAlphaChannel]="'disabled'"
|
||||
[(colorPicker)]="user!.preferences!.backgroundColor"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="mb-3 mt-1">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="auto-close" role="switch" formControlName="autoCloseMenu" class="form-check-input" [value]="true" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="auto-close">{{t('auto-close-menu-label')}}</label>
|
||||
<input type="checkbox" id="blur-unread-summaries" role="switch" formControlName="blurUnreadSummaries" class="form-check-input" aria-describedby="settings-global-blurUnreadSummaries-help" [value]="true" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="blur-unread-summaries">{{t('blur-unread-summaries-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="blurUnreadSummariesTooltip" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
|
||||
<ng-template #blurUnreadSummariesTooltip>{{t('blur-unread-summaries-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-global-blurUnreadSummaries-help">
|
||||
<ng-container [ngTemplateOutlet]="blurUnreadSummariesTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="mb-3 mt-1">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="show-screen-hints" role="switch" formControlName="showScreenHints" class="form-check-input" [value]="true" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="show-screen-hints">{{t('show-screen-hints-label')}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="mb-3 mt-1">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="emulate-book" role="switch" formControlName="emulateBook" class="form-check-input" [value]="true">
|
||||
<label class="form-check-label me-1" for="emulate-book">{{t('emulate-comic-book-label')}}</label><i class="fa fa-info-circle" aria-hidden="true" placement="top" ngbTooltip="Applies a shadow effect to emulate reading from a book" role="button" tabindex="0"></i>
|
||||
<input type="checkbox" id="prompt-download" role="switch" formControlName="promptForDownloadSize" class="form-check-input" aria-describedby="settings-global-promptForDownloadSize-help" [value]="true" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="prompt-download">{{t('prompt-on-download-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="promptForDownloadSizeTooltip" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="mb-3 mt-1">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="swipe-to-paginate" role="switch" formControlName="swipeToPaginate" class="form-check-input" [value]="true">
|
||||
<label class="form-check-label me-1" for="swipe-to-paginate">{{t('swipe-to-paginate-label')}}</label><i class="fa fa-info-circle" aria-hidden="true" placement="top" ngbTooltip="Should swiping on the screen cause the next or previous page to be triggered" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end mb-3">
|
||||
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetForm()" aria-describedby="reading-panel">{{t('reset')}}</button>
|
||||
<button type="submit" class="flex-fill btn btn-primary" (click)="save()" aria-describedby="reading-panel" [disabled]="!settingsForm.dirty">{{t('save')}}</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #promptForDownloadSizeTooltip>{{t('prompt-on-download-tooltip', {size: '100'})}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-global-promptForDownloadSize-help">
|
||||
<ng-container [ngTemplateOutlet]="promptForDownloadSizeTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="no-transitions" role="switch" formControlName="noTransitions" class="form-check-input"
|
||||
aria-describedby="settings-global-noTransitions-help" [value]="true" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="no-transitions">{{t('disable-animations-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="noTransitionsTooltip" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
|
||||
<ng-template #noTransitionsTooltip>{{t('disable-animations-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-global-noTransitions-help">
|
||||
<ng-container [ngTemplateOutlet]="noTransitionsTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-12 col-sm-12 pe-2 mb-2">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="collapse-relationships" role="switch" formControlName="collapseSeriesRelationships"
|
||||
aria-describedby="settings-collapse-relationships-help" class="form-check-input" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="collapse-relationships">{{t('collapse-series-relationships-label')}}</label>
|
||||
<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="collapseSeriesRelationshipsTooltip" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
<ng-template #collapseSeriesRelationshipsTooltip>{{t('collapse-series-relationships-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-collapse-relationships-help">
|
||||
<ng-container [ngTemplateOutlet]="collapseSeriesRelationshipsTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-12 col-sm-12 pe-2 mb-2">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="share-reviews" role="switch" formControlName="shareReviews"
|
||||
aria-describedby="settings-share-reviews-help" class="form-check-input" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="share-reviews">{{t('share-series-reviews-label')}}</label>
|
||||
<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="shareReviewsTooltip" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
<ng-template #shareReviewsTooltip>{{t('share-series-reviews-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-share-reviews-help">
|
||||
<ng-container [ngTemplateOutlet]="shareReviewsTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end mb-3">
|
||||
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetForm()" aria-describedby="reading-panel">{{t('reset')}}</button>
|
||||
<button type="submit" class="flex-fill btn btn-primary" (click)="save()" aria-describedby="reading-panel" [disabled]="!settingsForm.dirty">{{t('save')}}</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ngbAccordionItem [id]="AccordionPanelID.ImageReader">
|
||||
<h2 class="accordion-header" ngbAccordionHeader>
|
||||
<button class="accordion-button" ngbAccordionButton type="button" [attr.aria-expanded]="acc.isExpanded(AccordionPanelID.ImageReader)" aria-controls="collapseOne">
|
||||
{{t('image-reader-settings-title')}}
|
||||
</button>
|
||||
</h2>
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody>
|
||||
<ng-template>
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-reading-direction" class="form-label">{{t('reading-direction-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="readingDirectionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #readingDirectionTooltip>{{t('reading-direction-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-reading-direction-help">
|
||||
<ng-container [ngTemplateOutlet]="readingDirectionTooltip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="manga-header" formControlName="readingDirection" id="settings-reading-direction">
|
||||
<option *ngFor="let opt of readingDirectionsTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-scaling-option" class="form-label">{{t('scaling-option-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="scalingOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #scalingOptionTooltip>{{t('scaling-option-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-scaling-option-help">
|
||||
<ng-container [ngTemplateOutlet]="scalingOptionTooltip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="manga-header" formControlName="scalingOption" id="settings-scaling-option">
|
||||
<option *ngFor="let opt of scalingOptionsTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-pagesplit-option" class="form-label">{{t('page-splitting-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="pageSplitOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #pageSplitOptionTooltip>{{t('page-splitting-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-pagesplit-option-help">
|
||||
<ng-container [ngTemplateOutlet]="pageSplitOptionTooltip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="manga-header" formControlName="pageSplitOption" id="settings-pagesplit-option">
|
||||
<option *ngFor="let opt of pageSplitOptionsTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-readingmode-option" class="form-label">{{t('reading-mode-label')}}</label>
|
||||
<select class="form-select" aria-describedby="manga-header" formControlName="readerMode" id="settings-readingmode-option">
|
||||
<option *ngFor="let opt of readingModesTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2" *ngIf="true">
|
||||
<label for="settings-layoutmode-option" class="form-label">{{t('layout-mode-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="layoutModeTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #layoutModeTooltip>{{t('layout-mode-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-layoutmode-option-help">
|
||||
<ng-container [ngTemplateOutlet]="layoutModeTooltip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="manga-header" formControlName="layoutMode" id="settings-layoutmode-option">
|
||||
<option *ngFor="let opt of layoutModesTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-background-color-option" class="form-label">{{t('background-color-label')}}</label>
|
||||
<input [value]="user!.preferences!.backgroundColor"
|
||||
class="form-control"
|
||||
id="settings-background-color-option"
|
||||
(colorPickerChange)="handleBackgroundColorChange()"
|
||||
[style.background]="user!.preferences!.backgroundColor"
|
||||
[cpAlphaChannel]="'disabled'"
|
||||
[(colorPicker)]="user!.preferences!.backgroundColor"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="mb-3 mt-1">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="auto-close" role="switch" formControlName="autoCloseMenu" class="form-check-input" [value]="true" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="auto-close">{{t('auto-close-menu-label')}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ngbAccordionItem [id]="AccordionPanelID.BookReader">
|
||||
<h2 class="accordion-header" ngbAccordionHeader>
|
||||
<button class="accordion-button" ngbAccordionButton type="button" [attr.aria-expanded]="acc.isExpanded(AccordionPanelID.BookReader)" aria-controls="collapseOne">
|
||||
{{t('book-reader-settings-title')}}
|
||||
</button>
|
||||
</h2>
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody>
|
||||
<ng-template>
|
||||
<div class="row g-0">
|
||||
<div class="col-md-4 col-sm-12 pe-2 mb-3">
|
||||
<label id="taptopaginate-label" class="form-label"></label>
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" role="switch" id="taptopaginate" formControlName="bookReaderTapToPaginate" class="form-check-input" [value]="true" aria-labelledby="taptopaginate-label">
|
||||
<label for="taptopaginate" class="form-check-label">{{t('tap-to-paginate-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="tapToPaginateOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #tapToPaginateOptionTooltip>{{t('tap-to-paginate-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-taptopaginate-option-help">
|
||||
<ng-container [ngTemplateOutlet]="tapToPaginateOptionTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-12 pe-2 mb-3">
|
||||
<label id="immersivemode-label" class="form-label"></label>
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" role="switch" id="immersivemode" formControlName="bookReaderImmersiveMode" class="form-check-input" [value]="true" aria-labelledby="immersivemode-label">
|
||||
<label for="immersivemode" class="form-check-label">{{t('immersive-mode-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="immersivemodeOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #immersivemodeOptionTooltip>{{t('immersive-mode-label')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-immersivemode-option-help">
|
||||
<ng-container [ngTemplateOutlet]="immersivemodeOptionTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<label for="settings-book-reading-direction" class="form-label">{{t('reading-direction-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="bookReadingDirectionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookReadingDirectionTooltip>{{t('reading-direction-book-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-book-reading-direction-book-help">
|
||||
<ng-container [ngTemplateOutlet]="bookReadingDirectionTooltip"></ng-container>
|
||||
</span>
|
||||
<select id="settings-book-reading-direction" class="form-select" aria-describedby="settings-book-reading-direction-help" formControlName="bookReaderReadingDirection">
|
||||
<option *ngFor="let opt of readingDirectionsTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<label for="settings-fontfamily-option" class="form-label">{{t('font-family-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="fontFamilyOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #fontFamilyOptionTooltip>{{t('font-family-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-fontfamily-option-help">
|
||||
<ng-container [ngTemplateOutlet]="fontFamilyOptionTooltip"></ng-container>
|
||||
</span>
|
||||
<select id="settings-fontfamily-option" class="form-select" aria-describedby="settings-fontfamily-option-help" formControlName="bookReaderFontFamily">
|
||||
<option *ngFor="let opt of fontFamilies" [value]="opt">{{opt | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<label for="settings-book-writing-style" class="form-label me-1">{{t('writing-style-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" aria-describedby="settings-book-writing-style-help" placement="right" [ngbTooltip]="bookWritingStyleToolTip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookWritingStyleToolTip>{{t('writing-style-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-book-writing-style-help">
|
||||
<ng-container [ngTemplateOutlet]="bookWritingStyleToolTip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="settings-book-writing-style-help" formControlName="bookReaderWritingStyle" id="settings-book-writing-style" >
|
||||
<option *ngFor="let opt of bookWritingStylesTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<label for="settings-book-layout-mode" class="form-label">{{t('layout-mode-book-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="bookLayoutModeTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookLayoutModeTooltip>{{t('layout-mode-book-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-book-layout-mode-help">
|
||||
<ng-container [ngTemplateOutlet]="bookLayoutModeTooltip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="settings-book-layout-mode-help" formControlName="bookReaderLayoutMode" id="settings-book-layout-mode">
|
||||
<option *ngFor="let opt of bookLayoutModesTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<label for="settings-color-theme-option" class="form-label">{{t('color-theme-book-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="bookColorThemeTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookColorThemeTooltip>{{t('color-theme-book-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-color-theme-option-help">
|
||||
<ng-container [ngTemplateOutlet]="bookColorThemeTooltip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="settings-color-theme-option-help" formControlName="bookReaderThemeName" id="settings-color-theme-option">
|
||||
<option *ngFor="let opt of bookColorThemesTranslated" [value]="opt.name">{{opt.name | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-4 col-sm-12 pe-2 mb-3">
|
||||
<label for="fontsize" class="form-label range-label">{{t('font-size-book-label')}}</label>
|
||||
<input type="range" class="form-range" id="fontsize"
|
||||
min="50" max="300" step="10" formControlName="bookReaderFontSize">
|
||||
<span class="range-text">{{settingsForm.get('bookReaderFontSize')?.value + '%'}}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-4 col-sm-12 pe-2 mb-3">
|
||||
<div class="range-label">
|
||||
<label class="form-label" for="linespacing">{{t('line-height-book-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="bookLineHeightOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookLineHeightOptionTooltip>{{t('line-height-book-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-booklineheight-option-help">
|
||||
<ng-container [ngTemplateOutlet]="bookLineHeightOptionTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
<input type="range" class="form-range" id="linespacing" min="100" max="200" step="10"
|
||||
formControlName="bookReaderLineSpacing" aria-describedby="settings-booklineheight-option-help">
|
||||
<span class="range-text">{{settingsForm.get('bookReaderLineSpacing')?.value + '%'}}</span>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-sm-12 pe-2 mb-3">
|
||||
<div class="range-label">
|
||||
<label class="form-label">{{t('margin-book-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="bookReaderMarginOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookReaderMarginOptionTooltip>{{t('margin-book-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-bookmargin-option-help">
|
||||
<ng-container [ngTemplateOutlet]="bookReaderMarginOptionTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<input type="range" class="form-range" id="margin" min="0" max="30" step="5" formControlName="bookReaderMargin" aria-describedby="bookmargin">
|
||||
<span class="range-text">{{settingsForm.get('bookReaderMargin')?.value + '%'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end mb-3">
|
||||
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetForm()" aria-describedby="reading-panel">{{t('reset')}}</button>
|
||||
<button type="submit" class="flex-fill btn btn-primary" (click)="save()" aria-describedby="reading-panel" [disabled]="!settingsForm.dirty">{{t('save')}}</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="mb-3 mt-1">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="show-screen-hints" role="switch" formControlName="showScreenHints" class="form-check-input" [value]="true" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="show-screen-hints">{{t('show-screen-hints-label')}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</ng-container>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="mb-3 mt-1">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="emulate-book" role="switch" formControlName="emulateBook" class="form-check-input" [value]="true">
|
||||
<label class="form-check-label me-1" for="emulate-book">{{t('emulate-comic-book-label')}}</label><i class="fa fa-info-circle" aria-hidden="true" placement="top" ngbTooltip="Applies a shadow effect to emulate reading from a book" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="mb-3 mt-1">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="swipe-to-paginate" role="switch" formControlName="swipeToPaginate" class="form-check-input" [value]="true">
|
||||
<label class="form-check-label me-1" for="swipe-to-paginate">{{t('swipe-to-paginate-label')}}</label><i class="fa fa-info-circle" aria-hidden="true" placement="top" ngbTooltip="Should swiping on the screen cause the next or previous page to be triggered" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end mb-3">
|
||||
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetForm()" aria-describedby="reading-panel">{{t('reset')}}</button>
|
||||
<button type="submit" class="flex-fill btn btn-primary" (click)="save()" aria-describedby="reading-panel" [disabled]="!settingsForm.dirty">{{t('save')}}</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ngbAccordionItem [id]="AccordionPanelID.BookReader">
|
||||
<h2 class="accordion-header" ngbAccordionHeader>
|
||||
<button class="accordion-button" ngbAccordionButton type="button" [attr.aria-expanded]="acc.isExpanded(AccordionPanelID.BookReader)" aria-controls="collapseOne">
|
||||
{{t('book-reader-settings-title')}}
|
||||
</button>
|
||||
</h2>
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody>
|
||||
<ng-template>
|
||||
<div class="row g-0">
|
||||
<div class="col-md-4 col-sm-12 pe-2 mb-3">
|
||||
<label id="taptopaginate-label" class="form-label"></label>
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" role="switch" id="taptopaginate" formControlName="bookReaderTapToPaginate" class="form-check-input" [value]="true" aria-labelledby="taptopaginate-label">
|
||||
<label for="taptopaginate" class="form-check-label">{{t('tap-to-paginate-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="tapToPaginateOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #tapToPaginateOptionTooltip>{{t('tap-to-paginate-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-taptopaginate-option-help">
|
||||
<ng-container [ngTemplateOutlet]="tapToPaginateOptionTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-12 pe-2 mb-3">
|
||||
<label id="immersivemode-label" class="form-label"></label>
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" role="switch" id="immersivemode" formControlName="bookReaderImmersiveMode" class="form-check-input" [value]="true" aria-labelledby="immersivemode-label">
|
||||
<label for="immersivemode" class="form-check-label">{{t('immersive-mode-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="immersivemodeOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #immersivemodeOptionTooltip>{{t('immersive-mode-label')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-immersivemode-option-help">
|
||||
<ng-container [ngTemplateOutlet]="immersivemodeOptionTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<label for="settings-book-reading-direction" class="form-label">{{t('reading-direction-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="bookReadingDirectionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookReadingDirectionTooltip>{{t('reading-direction-book-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-book-reading-direction-book-help">
|
||||
<ng-container [ngTemplateOutlet]="bookReadingDirectionTooltip"></ng-container>
|
||||
</span>
|
||||
<select id="settings-book-reading-direction" class="form-select" aria-describedby="settings-book-reading-direction-help" formControlName="bookReaderReadingDirection">
|
||||
<option *ngFor="let opt of readingDirectionsTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<label for="settings-fontfamily-option" class="form-label">{{t('font-family-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="fontFamilyOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #fontFamilyOptionTooltip>{{t('font-family-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-fontfamily-option-help">
|
||||
<ng-container [ngTemplateOutlet]="fontFamilyOptionTooltip"></ng-container>
|
||||
</span>
|
||||
<select id="settings-fontfamily-option" class="form-select" aria-describedby="settings-fontfamily-option-help" formControlName="bookReaderFontFamily">
|
||||
<option *ngFor="let opt of fontFamilies" [value]="opt">{{opt | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<label for="settings-book-writing-style" class="form-label me-1">{{t('writing-style-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" aria-describedby="settings-book-writing-style-help" placement="right" [ngbTooltip]="bookWritingStyleToolTip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookWritingStyleToolTip>{{t('writing-style-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-book-writing-style-help">
|
||||
<ng-container [ngTemplateOutlet]="bookWritingStyleToolTip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="settings-book-writing-style-help" formControlName="bookReaderWritingStyle" id="settings-book-writing-style" >
|
||||
<option *ngFor="let opt of bookWritingStylesTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<label for="settings-book-layout-mode" class="form-label">{{t('layout-mode-book-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="bookLayoutModeTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookLayoutModeTooltip>{{t('layout-mode-book-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-book-layout-mode-help">
|
||||
<ng-container [ngTemplateOutlet]="bookLayoutModeTooltip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="settings-book-layout-mode-help" formControlName="bookReaderLayoutMode" id="settings-book-layout-mode">
|
||||
<option *ngFor="let opt of bookLayoutModesTranslated" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<label for="settings-color-theme-option" class="form-label">{{t('color-theme-book-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="bookColorThemeTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookColorThemeTooltip>{{t('color-theme-book-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-color-theme-option-help">
|
||||
<ng-container [ngTemplateOutlet]="bookColorThemeTooltip"></ng-container>
|
||||
</span>
|
||||
<select class="form-select" aria-describedby="settings-color-theme-option-help" formControlName="bookReaderThemeName" id="settings-color-theme-option">
|
||||
<option *ngFor="let opt of bookColorThemesTranslated" [value]="opt.name">{{opt.name | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-md-4 col-sm-12 pe-2 mb-3">
|
||||
<label for="fontsize" class="form-label range-label">{{t('font-size-book-label')}}</label>
|
||||
<input type="range" class="form-range" id="fontsize"
|
||||
min="50" max="300" step="10" formControlName="bookReaderFontSize">
|
||||
<span class="range-text">{{settingsForm.get('bookReaderFontSize')?.value + '%'}}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-4 col-sm-12 pe-2 mb-3">
|
||||
<div class="range-label">
|
||||
<label class="form-label" for="linespacing">{{t('line-height-book-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="bookLineHeightOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookLineHeightOptionTooltip>{{t('line-height-book-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-booklineheight-option-help">
|
||||
<ng-container [ngTemplateOutlet]="bookLineHeightOptionTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
<input type="range" class="form-range" id="linespacing" min="100" max="200" step="10"
|
||||
formControlName="bookReaderLineSpacing" aria-describedby="settings-booklineheight-option-help">
|
||||
<span class="range-text">{{settingsForm.get('bookReaderLineSpacing')?.value + '%'}}</span>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-sm-12 pe-2 mb-3">
|
||||
<div class="range-label">
|
||||
<label class="form-label">{{t('margin-book-label')}}</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="bookReaderMarginOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookReaderMarginOptionTooltip>{{t('margin-book-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="settings-bookmargin-option-help">
|
||||
<ng-container [ngTemplateOutlet]="bookReaderMarginOptionTooltip"></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<input type="range" class="form-range" id="margin" min="0" max="30" step="5" formControlName="bookReaderMargin" aria-describedby="bookmargin">
|
||||
<span class="range-text">{{settingsForm.get('bookReaderMargin')?.value + '%'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end mb-3">
|
||||
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetForm()" aria-describedby="reading-panel">{{t('reset')}}</button>
|
||||
<button type="submit" class="flex-fill btn btn-primary" (click)="save()" aria-describedby="reading-panel" [disabled]="!settingsForm.dirty">{{t('save')}}</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
|
||||
|
||||
@defer (when tab.fragment === FragmentID.Clients; prefetch on idle) {
|
||||
<div class="alert alert-warning" role="alert" *ngIf="!opdsEnabled">{{t('clients-opds-alert')}}</div>
|
||||
<p>{{t('clients-opds-description')}}</p>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue