PDF Metadata Support (#3552)

Co-authored-by: Matthias Neeracher <microtherion@gmail.com>
This commit is contained in:
Joe Milazzo 2025-02-16 15:10:15 -06:00 committed by GitHub
parent 56108eb373
commit f76de42b28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 1949 additions and 57 deletions

View file

@ -17,13 +17,13 @@
@if (settingsForm.get('hostName'); as formControl) {
<app-setting-item [title]="t('host-name-label')" [subtitle]="t('host-name-tooltip')">
<ng-template #view>
{{formControl.value}}
{{formControl.value | defaultValue}}
</ng-template>
<ng-template #edit>
<input id="settings-hostname" aria-describedby="settings-hostname-help" class="form-control" formControlName="hostName" type="text"
[class.is-invalid]="formControl.invalid && formControl.touched">
[class.is-invalid]="formControl.invalid && !formControl.untouched">
@if(settingsForm.dirty || settingsForm.touched) {
@if(settingsForm.dirty || !settingsForm.untouched) {
<div id="hostname-validations" class="invalid-feedback">
@if (formControl.errors?.pattern) {
<div>{{t('host-name-validation')}}</div>
@ -44,11 +44,11 @@
<ng-template #edit>
<div class="input-group">
<input id="settings-baseurl" aria-describedby="settings-baseurl-help" class="form-control" formControlName="baseUrl" type="text"
[class.is-invalid]="formControl.invalid && formControl.touched">
[class.is-invalid]="formControl.invalid && !formControl.untouched">
<button type="button" class="btn btn-outline-secondary" (click)="resetBaseUrl()">{{t('reset')}}</button>
</div>
@if(settingsForm.dirty || settingsForm.touched) {
@if(settingsForm.dirty || !settingsForm.untouched) {
<div id="baseurl-validations" class="invalid-feedback">
@if (formControl.errors?.pattern) {
<div>{{t('base-url-validation')}}</div>
@ -69,11 +69,11 @@
<ng-template #edit>
<div class="input-group">
<input id="settings-ipaddresses" aria-describedby="settings-ipaddresses-help" class="form-control" formControlName="ipAddresses" type="text"
[class.is-invalid]="formControl.invalid && formControl.touched">
[class.is-invalid]="formControl.invalid && !formControl.untouched">
<button type="button" class="btn btn-outline-secondary" (click)="resetIPAddresses()">{{t('reset')}}</button>
</div>
@if(settingsForm.dirty || settingsForm.touched) {
@if(settingsForm.dirty || !settingsForm.untouched) {
<div id="ipaddresses-validations" class="invalid-feedback">
@if (formControl.errors?.pattern) {
<div>{{t('ip-address-validation')}}</div>
@ -116,9 +116,9 @@
<input id="settings-backup" aria-describedby="total-backups-validations" class="form-control"
formControlName="totalBackups" type="number" inputmode="numeric" step="1" min="1" max="30"
onkeypress="return event.charCode >= 48 && event.charCode <= 57"
[class.is-invalid]="formControl.invalid && formControl.touched">
[class.is-invalid]="formControl.invalid && !formControl.untouched">
@if(settingsForm.dirty || settingsForm.touched) {
@if(settingsForm.dirty || !settingsForm.untouched) {
<div id="total-backups-validations" class="invalid-feedback">
@if (formControl.errors?.required) {
<div>{{t('field-required')}}</div>
@ -146,9 +146,9 @@
<input id="settings-logs" aria-describedby="total-logs-validations" class="form-control"
formControlName="totalLogs" type="number" inputmode="numeric" step="1" min="1" max="30"
onkeypress="return event.charCode >= 48 && event.charCode <= 57"
[class.is-invalid]="formControl.invalid && formControl.touched">
[class.is-invalid]="formControl.invalid && !formControl.untouched">
@if(settingsForm.dirty || settingsForm.touched) {
@if(settingsForm.dirty || !settingsForm.untouched) {
<div id="total-logs-validations" class="invalid-feedback">
@if (formControl.errors?.required) {
<div>{{t('field-required')}}</div>
@ -175,13 +175,13 @@
<ng-template #edit>
<select id="logging-level" aria-describedby="logging-level-help" class="form-select" formControlName="loggingLevel"
[class.is-invalid]="formControl.invalid && formControl.touched">
[class.is-invalid]="formControl.invalid && !formControl.untouched">
@for(level of logLevels; track level) {
<option [value]="level">{{level | titlecase}}</option>
}
</select>
@if(settingsForm.dirty || settingsForm.touched) {
@if(settingsForm.dirty || !settingsForm.untouched) {
<div id="logging-level-validations" class="invalid-feedback">
@if (formControl.errors?.pattern) {
<div>{{t('host-name-validation')}}</div>
@ -202,9 +202,9 @@
<ng-template #edit>
<input id="setting-cache-size" aria-describedby="cache-size-help" class="form-control" formControlName="cacheSize"
type="number" inputmode="numeric" step="5" min="50" onkeypress="return event.charCode >= 48 && event.charCode <= 57"
[class.is-invalid]="formControl.invalid && formControl.touched">
[class.is-invalid]="formControl.invalid && !formControl.untouched">
@if(settingsForm.dirty || settingsForm.touched) {
@if(settingsForm.dirty || !settingsForm.untouched) {
<div id="cache-size-validations" class="invalid-feedback">
@if (formControl.errors?.required) {
<div>{{t('field-required')}}</div>
@ -271,9 +271,9 @@
<input id="setting-on-deck-progress-days" aria-describedby="on-deck-progress-days-validations" class="form-control" formControlName="onDeckProgressDays"
type="number" inputmode="numeric" step="1" min="1"
onkeypress="return event.charCode >= 48 && event.charCode <= 57"
[class.is-invalid]="formControl.invalid && formControl.touched">
[class.is-invalid]="formControl.invalid && !formControl.untouched">
@if(settingsForm.dirty || settingsForm.touched) {
@if(settingsForm.dirty || !settingsForm.untouched) {
<div id="on-deck-last-progress-validations" class="invalid-feedback">
@if (formControl.errors?.required) {
<div>{{t('field-required')}}</div>
@ -298,9 +298,9 @@
<input id="on-deck-last-chapter-add" aria-describedby="on-deck-last-chapter-add-validations" class="form-control" formControlName="onDeckUpdateDays"
type="number" inputmode="numeric" step="1" min="1"
onkeypress="return event.charCode >= 48 && event.charCode <= 57"
[class.is-invalid]="formControl.invalid && formControl.touched">
[class.is-invalid]="formControl.invalid && !formControl.untouched">
@if(settingsForm.dirty || settingsForm.touched) {
@if(settingsForm.dirty || !settingsForm.untouched) {
<div id="on-deck-last-chapter-add-validations" class="invalid-feedback">
@if (formControl.errors?.required) {
<div>{{t('field-required')}}</div>

View file

@ -5,17 +5,15 @@ import {take} from 'rxjs/operators';
import {ServerService} from 'src/app/_services/server.service';
import {SettingsService} from '../settings.service';
import {ServerSettings} from '../_models/server-settings';
import {NgbTooltip} from '@ng-bootstrap/ng-bootstrap';
import {NgTemplateOutlet, TitleCasePipe} from '@angular/common';
import {TitleCasePipe} from '@angular/common';
import {translate, TranslocoModule, TranslocoService} from "@jsverse/transloco";
import {WikiLink} from "../../_models/wiki";
import {PageLayoutModePipe} from "../../_pipes/page-layout-mode.pipe";
import {SettingItemComponent} from "../../settings/_components/setting-item/setting-item.component";
import {SettingSwitchComponent} from "../../settings/_components/setting-switch/setting-switch.component";
import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe";
import {ConfirmService} from "../../shared/confirm.service";
import {debounceTime, distinctUntilChanged, filter, of, switchMap, tap} from "rxjs";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
const ValidIpAddress = /^(\s*((([12]?\d{1,2}\.){3}[12]?\d{1,2})|(([\da-f]{0,4}\:){0,7}([\da-f]{0,4})))\s*\,)*\s*((([12]?\d{1,2}\.){3}[12]?\d{1,2})|(([\da-f]{0,4}\:){0,7}([\da-f]{0,4})))\s*$/i;
@ -25,7 +23,7 @@ const ValidIpAddress = /^(\s*((([12]?\d{1,2}\.){3}[12]?\d{1,2})|(([\da-f]{0,4}\:
styleUrls: ['./manage-settings.component.scss'],
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ReactiveFormsModule, TitleCasePipe, TranslocoModule, SettingItemComponent, SettingSwitchComponent]
imports: [ReactiveFormsModule, TitleCasePipe, TranslocoModule, SettingItemComponent, SettingSwitchComponent, DefaultValuePipe]
})
export class ManageSettingsComponent implements OnInit {
@ -81,7 +79,7 @@ export class ManageSettingsComponent implements OnInit {
// Automatically save settings as we edit them
this.settingsForm.valueChanges.pipe(
distinctUntilChanged(),
debounceTime(100),
debounceTime(300),
filter(_ => this.settingsForm.valid),
takeUntilDestroyed(this.destroyRef),
switchMap(_ => {

View file

@ -95,7 +95,7 @@
<div>
<app-badge-expander [items]="chapter.writers" [allowToggle]="false" (toggle)="switchTabsToDetail()">
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openPerson(FilterField.Writers, item.id)">{{item.name}}</a>
<a routerLink="/person/{{encodeURIComponent(item.name)}}/" class="dark-exempt btn-icon">{{item.name}}</a>
</ng-template>
</app-badge-expander>
</div>
@ -111,7 +111,7 @@
<div>
<app-badge-expander [items]="chapter.coverArtists" [allowToggle]="false" (toggle)="switchTabsToDetail()">
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openPerson(FilterField.CoverArtist, item.id)">{{item.name}}</a>
<a routerLink="/person/{{encodeURIComponent(item.name)}}/" class="dark-exempt btn-icon">{{item.name}}</a>
</ng-template>
</app-badge-expander>
</div>

View file

@ -364,4 +364,5 @@ export class ChapterDetailComponent implements OnInit {
}
protected readonly LibraryType = LibraryType;
protected readonly encodeURIComponent = encodeURIComponent;
}

View file

@ -788,7 +788,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (this.mangaReaderService.shouldBeWebtoonMode()) {
this.readerMode = ReaderMode.Webtoon;
this.toastr.info(translate('manga-reader.webtoon-override'));
this.toastr.info(translate('toasts.webtoon-override'));
this.readerModeSubject.next(this.readerMode);
this.cdRef.markForCheck();
}

View file

@ -26,6 +26,7 @@
<div class="person-badge">
<app-image
objectFit="cover"
[styles]="{'object-position': 'top'}"
height="200px"
width="200px"
[imageUrl]="imageService.getPersonImage(person.id)"

View file

@ -6,13 +6,14 @@
@if (HasCoverImage) {
<app-image
objectFit="cover"
[styles]="{'object-position': 'top'}"
height="96px"
width="96px"
[imageUrl]="ImageUrl"
[errorImage]="imageService.noPersonImage">
</app-image>
} @else {
<i class="fas fa-user" aria-hidden="true"></i>
<i class="fas fa-user" aria-hidden="true"></i>
}
</div>

View file

@ -31,7 +31,7 @@
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column prop="validUntilUtc" [sortable]="true" [draggable]="false" [resizeable]="false">
<ngx-datatable-column prop="validUntilUtc" [sortable]="false" [draggable]="false" [resizeable]="false">
<ng-template let-column="column" ngx-datatable-header-template>
</ng-template>

View file

@ -99,7 +99,7 @@
<div>
<app-badge-expander [items]="volumeCast.writers" [allowToggle]="false" (toggle)="switchTabsToDetail()">
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openPerson(FilterField.Writers, item.id)">{{item.name}}</a>
<a routerLink="/person/{{encodeURIComponent(item.name)}}/" class="dark-exempt btn-icon">{{item.name}}</a>
</ng-template>
</app-badge-expander>
</div>
@ -109,7 +109,7 @@
<div>
<app-badge-expander [items]="volumeCast.coverArtists" [allowToggle]="false" (toggle)="switchTabsToDetail()">
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openPerson(FilterField.CoverArtist, item.id)">{{item.name}}</a>
<a routerLink="/person/{{encodeURIComponent(item.name)}}/" class="dark-exempt btn-icon">{{item.name}}</a>
</ng-template>
</app-badge-expander>
</div>

View file

@ -666,4 +666,6 @@ export class VolumeDetailComponent implements OnInit {
this.currentlyReadingChapter = undefined;
}
}
protected readonly encodeURIComponent = encodeURIComponent;
}