* Updated dependencies

* Updated the default key to be 256 bits to meet security requirements.

* Added basic implementation of web link resolving favicon. Needs lots more work and testing on all OSes.

* Implemented ability to see links and click on them for an individual chapter.

* Hooked up the ability to set Series web links.

* Render out the web link

* Refactored out the favicon so there is a backup in case it fails. Refactored the baseline image placeholders to be dark mode since that is the default.

* Added Robbie's nice error weblink fallbacks.
This commit is contained in:
Joe Milazzo 2023-05-11 16:27:04 -05:00 committed by GitHub
parent 23fde65a7b
commit bd8a1821a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 4272 additions and 80 deletions

View file

@ -11,60 +11,61 @@
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-pills" orientation="{{utilityService.getActiveBreakpoint() === Breakpoint.Mobile ? 'horizontal' : 'vertical'}}" style="min-width: 135px;">
<li [ngbNavItem]="tabs[TabID.General]">
<a ngbNavLink>{{tabs[TabID.General]}}</a>
<ng-template ngbNavContent>
<div class="row g-0">
<div class="mb-3" style="width: 100%">
<label for="name" class="form-label">Name</label>
<div class="input-group">
<input id="name" class="form-control" formControlName="name" type="text" [class.is-invalid]="editSeriesForm.get('name')?.invalid && editSeriesForm.get('name')?.touched">
<ng-container *ngIf="editSeriesForm.get('name')?.errors as errors">
<div class="invalid-feedback" *ngIf="errors.required">
This field is required
</div>
</ng-container>
<a ngbNavLink>{{tabs[TabID.General]}}</a>
<ng-template ngbNavContent>
<div class="row g-0">
<div class="mb-3" style="width: 100%">
<label for="name" class="form-label">Name</label>
<div class="input-group">
<input id="name" class="form-control" formControlName="name" type="text" [class.is-invalid]="editSeriesForm.get('name')?.invalid && editSeriesForm.get('name')?.touched">
<ng-container *ngIf="editSeriesForm.get('name')?.errors as errors">
<div class="invalid-feedback" *ngIf="errors.required">
This field is required
</div>
</ng-container>
</div>
</div>
</div>
</div>
<div class="row g-0">
<div class="mb-3" style="width: 100%">
<label for="sort-name" class="form-label">Sort Name</label>
<div class="input-group {{series.sortNameLocked ? 'lock-active' : ''}}"
[class.is-invalid]="editSeriesForm.get('sortName')?.invalid && editSeriesForm.get('sortName')?.touched">
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: series, field: 'sortNameLocked' }"></ng-container>
<input id="sort-name" class="form-control" formControlName="sortName" type="text">
<ng-container *ngIf="editSeriesForm.get('sortName')?.errors as errors">
<div class="invalid-feedback" *ngIf="errors.required">
This field is required
</div>
</ng-container>
<div class="row g-0">
<div class="mb-3" style="width: 100%">
<label for="sort-name" class="form-label">Sort Name</label>
<div class="input-group {{series.sortNameLocked ? 'lock-active' : ''}}"
[class.is-invalid]="editSeriesForm.get('sortName')?.invalid && editSeriesForm.get('sortName')?.touched">
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: series, field: 'sortNameLocked' }"></ng-container>
<input id="sort-name" class="form-control" formControlName="sortName" type="text">
<ng-container *ngIf="editSeriesForm.get('sortName')?.errors as errors">
<div class="invalid-feedback" *ngIf="errors.required">
This field is required
</div>
</ng-container>
</div>
</div>
</div>
</div>
<div class="row g-0">
<div class="mb-3" style="width: 100%">
<label for="localized-name" class="form-label">Localized Name</label>
<div class="input-group {{series.localizedNameLocked ? 'lock-active' : ''}}">
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: series, field: 'localizedNameLocked' }"></ng-container>
<input id="localized-name" class="form-control" formControlName="localizedName" type="text">
<div class="row g-0">
<div class="mb-3" style="width: 100%">
<label for="localized-name" class="form-label">Localized Name</label>
<div class="input-group {{series.localizedNameLocked ? 'lock-active' : ''}}">
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: series, field: 'localizedNameLocked' }"></ng-container>
<input id="localized-name" class="form-control" formControlName="localizedName" type="text">
</div>
</div>
</div>
</div>
<div class="row g-0" *ngIf="metadata">
<div class="mb-3" style="width: 100%">
<label for="summary" class="form-label">Summary</label>
<div class="input-group {{metadata.summaryLocked ? 'lock-active' : ''}}">
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: metadata, field: 'summaryLocked' }"></ng-container>
<textarea id="summary" class="form-control" formControlName="summary" rows="4"></textarea>
<div class="row g-0" *ngIf="metadata">
<div class="mb-3" style="width: 100%">
<label for="summary" class="form-label">Summary</label>
<div class="input-group {{metadata.summaryLocked ? 'lock-active' : ''}}">
<ng-container [ngTemplateOutlet]="lock" [ngTemplateOutletContext]="{ item: metadata, field: 'summaryLocked' }"></ng-container>
<textarea id="summary" class="form-control" formControlName="summary" rows="4"></textarea>
</div>
</div>
</div>
</div>
</ng-template>
</ng-template>
</li>
<li [ngbNavItem]="tabs[TabID.Metadata]" *ngIf="metadata">
<a ngbNavLink>{{tabs[TabID.Metadata]}}</a>
<ng-template ngbNavContent>
@ -343,6 +344,27 @@
</ng-template>
</li>
<li [ngbNavItem]="tabs[TabID.WebLinks]" *ngIf="metadata">
<a ngbNavLink>{{tabs[TabID.WebLinks]}}</a>
<ng-template ngbNavContent>
<p>Here you can add many different links to external services.</p>
<div class="row g-0 mb-3" *ngFor="let link of WebLinks; let i = index;">
<div class="col-lg-8 col-md-12 pe-2">
<div class="mb-3">
<label for="web-link--{{i}}" class="visually-hidden">Web Link</label>
<input type="text" class="form-control" formControlName="link{{i}}" attr.id="web-link--{{i}}">
</div>
</div>
<div class="col-lg-2">
<button class="btn btn-secondary" (click)="addWebLink()">
<i class="fa-solid fa-plus" aria-hidden="true"></i>
<span class="visually-hidden">Add Link</span>
</button>
</div>
</div>
</ng-template>
</li>
<li [ngbNavItem]="tabs[TabID.CoverImage]">
<a ngbNavLink>{{tabs[TabID.CoverImage]}}</a>

View file

@ -26,9 +26,10 @@ enum TabID {
General = 0,
Metadata = 1,
People = 2,
CoverImage = 3,
Related = 4,
Info = 5,
WebLinks = 3,
CoverImage = 4,
Related = 5,
Info = 6,
}
@Component({
@ -49,7 +50,7 @@ export class EditSeriesModalComponent implements OnInit, OnDestroy {
isCollapsed = true;
volumeCollapsed: any = {};
tabs = ['General', 'Metadata', 'People', 'Cover Image', 'Related', 'Info'];
tabs = ['General', 'Metadata', 'People', 'Web Links', 'Cover Image', 'Related', 'Info'];
active = this.tabs[0];
activeTabId = TabID.General;
editSeriesForm!: FormGroup;
@ -100,6 +101,10 @@ export class EditSeriesModalComponent implements OnInit, OnDestroy {
return TabID;
}
get WebLinks() {
return this.metadata?.webLinks.split(',') || [''];
}
getPersonsSettings(role: PersonRole) {
return this.peopleSettings[role];
}
@ -168,6 +173,11 @@ export class EditSeriesModalComponent implements OnInit, OnDestroy {
this.editSeriesForm.get('publicationStatus')?.patchValue(this.metadata.publicationStatus);
this.editSeriesForm.get('language')?.patchValue(this.metadata.language);
this.editSeriesForm.get('releaseYear')?.patchValue(this.metadata.releaseYear);
this.WebLinks.forEach((link, index) => {
this.editSeriesForm.addControl('link' + index, new FormControl(link, [Validators.required]));
});
this.cdRef.markForCheck();
this.editSeriesForm.get('name')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(val => {
@ -474,6 +484,13 @@ export class EditSeriesModalComponent implements OnInit, OnDestroy {
save() {
const model = this.editSeriesForm.value;
const selectedIndex = this.editSeriesForm.get('coverImageIndex')?.value || 0;
this.metadata.webLinks = Object.keys(this.editSeriesForm.controls)
.filter(key => key.startsWith('link'))
.map(key => this.editSeriesForm.get(key)?.value)
.filter(v => v !== null && v !== '')
.join(',');
const apis = [
this.seriesService.updateMetadata(this.metadata, this.collectionTags)
@ -502,6 +519,12 @@ export class EditSeriesModalComponent implements OnInit, OnDestroy {
});
}
addWebLink() {
this.metadata.webLinks += ',';
this.editSeriesForm.addControl('link' + (this.WebLinks.length - 1), new FormControl('', [Validators.required]));
this.cdRef.markForCheck();
}
updateCollections(tags: CollectionTag[]) {
this.collectionTags = tags;
this.cdRef.markForCheck();

View file

@ -71,5 +71,19 @@
{{entity.id}}
</app-icon-and-title>
</div>
<ng-container *ngIf="WebLinks.length > 0">
<div class="vr d-none d-lg-block m-2"></div>
<div class="col-auto">
<app-icon-and-title label="Links" [clickable]="false" fontClasses="fa-solid fa-link" title="Links">
<a class="me-1" [href]="link | safeHtml" *ngFor="let link of WebLinks" target="_blank" rel="noopener noreferrer" [title]="link">
<img width="24px" height="24px" #img class="lazyload img-placeholder"
src="data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
[attr.data-src]="imageService.getWebLinkImage(link)"
(error)="imageService.updateErroredWebLinkImage($event)"
aria-hidden="true">
</a>
</app-icon-and-title>
</div>
</ng-container>
</ng-container>
</div>

View file

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, inject } from '@angular/core';
import { Subject } from 'rxjs';
import { UtilityService } from 'src/app/shared/_services/utility.service';
import { Chapter } from 'src/app/_models/chapter';
@ -9,6 +9,7 @@ import { MangaFormat } from 'src/app/_models/manga-format';
import { AgeRating } from 'src/app/_models/metadata/age-rating';
import { Volume } from 'src/app/_models/volume';
import { SeriesService } from 'src/app/_services/series.service';
import { ImageService } from 'src/app/_services/image.service';
@Component({
selector: 'app-entity-info-cards',
@ -40,6 +41,7 @@ export class EntityInfoCardsComponent implements OnInit, OnDestroy {
size: number = 0;
private readonly onDestroy: Subject<void> = new Subject();
imageService = inject(ImageService);
get LibraryType() {
return LibraryType;
@ -53,6 +55,10 @@ export class EntityInfoCardsComponent implements OnInit, OnDestroy {
return AgeRating;
}
get WebLinks() {
return this.chapter.webLinks.split(',');
}
constructor(private utilityService: UtilityService, private seriesService: SeriesService, private readonly cdRef: ChangeDetectorRef) {}
ngOnInit(): void {