Localization - First Pass (#2174)
* Started designing the backend localization service * Worked in Transloco for initial PoC * Worked in Transloco for initial PoC * Translated the login screen * translated dashboard screen * Started work on the backend * Fixed a logic bug * translated edit-user screen * Hooked up the backend for having a locale property. * Hooked up the ability to view the available locales and switch to them. * Made the localization service languages be derived from what's in langs/ directory. * Fixed up localization switching * Switched when we check for a license on UI bootstrap * Tweaked some code * Fixed the bug where dashboard wasn't loading and made it so language switching is working. * Fixed a bug on dashboard with languagePath * Converted user-scrobble-history.component.html * Converted spoiler.component.html * Converted review-series-modal.component.html * Converted review-card-modal.component.html * Updated the readme * Translated using Weblate (English) Currently translated at 100.0% (54 of 54 strings) Translation: Kavita/ui Translate-URL: https://hosted.weblate.org/projects/kavita/ui/en/ * Converted review-card.component.html * Deleted dead component * Converted want-to-read.component.html * Added translation using Weblate (Korean) * Translated using Weblate (Spanish) Currently translated at 40.7% (22 of 54 strings) Translation: Kavita/ui Translate-URL: https://hosted.weblate.org/projects/kavita/ui/es/ * Translated using Weblate (Korean) Currently translated at 62.9% (34 of 54 strings) Translation: Kavita/ui Translate-URL: https://hosted.weblate.org/projects/kavita/ui/ko/ * Converted user-preferences.component.html * Translated using Weblate (Korean) Currently translated at 92.5% (50 of 54 strings) Translation: Kavita/ui Translate-URL: https://hosted.weblate.org/projects/kavita/ui/ko/ * Converted user-holds.component.html * Converted theme-manager.component.html * Converted restriction-selector.component.html * Converted manage-devices.component.html * Converted edit-device.component.html * Converted change-password.component.html * Converted change-email.component.html * Converted change-age-restriction.component.html * Converted api-key.component.html * Converted anilist-key.component.html * Converted typeahead.component.html * Converted user-stats-info-cards.component.html * Converted user-stats.component.html * Converted top-readers.component.html * Converted some pipes and ensure translation is loaded before the app. * Finished all but one pipe for localization * Converted directory-picker.component.html * Converted library-access-modal.component.html * Converted a few components * Converted a few components * Converted a few components * Converted a few components * Converted a few components * Merged weblate in * ... -> … update * Updated the readme * Updateded all fonts to be woff2 * Cleaned up some strings to increase re-use * Removed an old flow (that doesn't exist in backend any longer) from when we introduced emails on Kavita. * Converted Series detail * Lots more converted * Lots more converted & hooked up the ability to flatten during prod build the language files. * Lots more converted * Lots more converted & fixed a bunch of broken pipes due to inject() * Lots more converted * Lots more converted * Lots more converted & fixed some bad keys * Lots more converted * Fixed some bugs with admin dasbhoard nested tabs not rendering on first load due to not using onpush change detection * Fixed up some localization errors and fixed forgot password error when the user doesn't have change password permission * Fixed a stupid build issue again * Started adding errors for interceptor and backend. * Finished off manga-reader * More translations * Few fixes * Fixed a bug where character tag badges weren't showing the name on chapter info * All components are translated * All toasts are translated * All confirm/alerts are translated * Trying something new for the backend * Migrated the localization strings for the backend into a new file. * Updated the localization service to be able to do backend localization with fallback to english. * Cleaned up some external reviews code to reduce looping * Localized AccountController.cs * 60% done with controllers * All controllers are done * All KavitaExceptions are covered * Some shakeout fixes * Prep for initial merge * Everything is done except options and basic shakeout proves response times are good. Unit tests are broken. * Fixed up the unit tests * All unit tests are now working * Removed some quantifier * I'm not sure I can support localization for some Volume/Chapter/Book strings within the codebase. --------- Co-authored-by: Robbie Davis <robbie@therobbiedavis.com> Co-authored-by: majora2007 <kavitareader@gmail.com> Co-authored-by: expertjun <jtrobin@naver.com> Co-authored-by: ThePromidius <thepromidiusyt@gmail.com>
This commit is contained in:
parent
670bf82c38
commit
3b23d63234
389 changed files with 13652 additions and 7925 deletions
|
|
@ -1,364 +1,352 @@
|
|||
<div #companionBar>
|
||||
<app-side-nav-companion-bar *ngIf="series !== undefined" [hasExtras]="true" [extraDrawer]="extrasDrawer">
|
||||
<ng-container title>
|
||||
<h2 class="title text-break">
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="seriesActions" [labelBy]="series.name" iconClass="fa-ellipsis-v"></app-card-actionables>
|
||||
<span>{{series.name}}</span>
|
||||
</h2>
|
||||
</ng-container>
|
||||
<ng-container subtitle *ngIf="series.localizedName !== series.name">
|
||||
<h6 class="subtitle-with-actionables text-break" title="Localized Name">{{series.localizedName}}</h6>
|
||||
</ng-container>
|
||||
<ng-container *transloco="let t; read: 'series-detail'">
|
||||
<div #companionBar>
|
||||
<app-side-nav-companion-bar *ngIf="series !== undefined" [hasExtras]="true" [extraDrawer]="extrasDrawer">
|
||||
<ng-container title>
|
||||
<h2 class="title text-break">
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="seriesActions" [labelBy]="series.name" iconClass="fa-ellipsis-v"></app-card-actionables>
|
||||
<span>{{series.name}}</span>
|
||||
</h2>
|
||||
</ng-container>
|
||||
<ng-container subtitle *ngIf="series.localizedName !== series.name">
|
||||
<h6 class="subtitle-with-actionables text-break" title="Localized Name">{{series.localizedName}}</h6>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #extrasDrawer let-offcanvas>
|
||||
<div style="margin-top: 56px">
|
||||
<div class="offcanvas-header">
|
||||
<h4 class="offcanvas-title" id="offcanvas-basic-title">Page Settings</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="offcanvas.dismiss()"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<form [formGroup]="pageExtrasGroup">
|
||||
<!-- <div class="row g-0">
|
||||
<ng-template #extrasDrawer let-offcanvas>
|
||||
<div style="margin-top: 56px">
|
||||
<div class="offcanvas-header">
|
||||
<h4 class="offcanvas-title" id="offcanvas-basic-title">{{t('page-settings-title')}}</h4>
|
||||
<button type="button" class="btn-close" [attr.aria-label]="t('close')" (click)="offcanvas.dismiss()"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<form [formGroup]="pageExtrasGroup">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-12 col-sm-12 pe-2 mb-3">
|
||||
<label for="settings-book-reading-direction" class="form-label">Sort Order</label>
|
||||
<button class="btn btn-sm btn-secondary-outline" (click)="updateSortOrder()" style="height: 25px; padding-bottom: 0px;">
|
||||
<i class="fa fa-arrow-up" title="Ascending" *ngIf="isAscendingSort; else descSort"></i>
|
||||
<ng-template #descSort>
|
||||
<i class="fa fa-arrow-down" title="Descending"></i>
|
||||
</ng-template>
|
||||
</button>
|
||||
<label id="list-layout-mode-label" class="form-label">{{t('layout-mode-label')}}</label>
|
||||
<br/>
|
||||
<div class="btn-group d-flex justify-content-center" role="group" [attr.aria-label]="t('page-settings-title')">
|
||||
<input type="radio" formControlName="renderMode" [value]="PageLayoutMode.Cards" class="btn-check" id="layout-mode-default" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="layout-mode-default">{{t('layout-mode-option-card')}}</label>
|
||||
|
||||
<select class="form-select" aria-describedby="settings-reading-direction-help" formControlName="sortingOption">
|
||||
<option *ngFor="let opt of sortingOptions" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="row g-0">
|
||||
<div class="col-md-12 col-sm-12 pe-2 mb-3">
|
||||
<label id="list-layout-mode-label" class="form-label">Layout Mode</label>
|
||||
<br/>
|
||||
<div class="btn-group d-flex justify-content-center" role="group" aria-label="Layout Mode">
|
||||
<input type="radio" formControlName="renderMode" [value]="PageLayoutMode.Cards" class="btn-check" id="layout-mode-default" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="layout-mode-default">Card</label>
|
||||
|
||||
<input type="radio" formControlName="renderMode" [value]="PageLayoutMode.List" class="btn-check" id="layout-mode-col1" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="layout-mode-col1">List</label>
|
||||
<input type="radio" formControlName="renderMode" [value]="PageLayoutMode.List" class="btn-check" id="layout-mode-col1" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="layout-mode-col1">{{t('layout-mode-option-list')}}</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
||||
</app-side-nav-companion-bar>
|
||||
</div>
|
||||
|
||||
<div [ngStyle]="{'height': ScrollingBlockHeight}" class="main-container container-fluid pt-2" *ngIf="series !== undefined" #scrollingBlock>
|
||||
<div class="row mb-0 mb-xl-3 info-container">
|
||||
<div class="image-container col-4 col-sm-6 col-md-4 col-lg-4 col-xl-2 col-xxl-2 d-none d-sm-block mt-2">
|
||||
<div class="to-read-counter" *ngIf="unreadCount > 0 && unreadCount !== totalCount">
|
||||
<app-tag-badge [selectionMode]="TagBadgeCursor.NotAllowed" fillStyle="filled">{{unreadCount}}</app-tag-badge>
|
||||
</div>
|
||||
<app-image height="100%" maxHeight="400px" objectFit="contain" background="none" [imageUrl]="seriesImage"></app-image>
|
||||
<ng-container *ngIf="series.pagesRead < series.pages && hasReadingProgress && currentlyReadingChapter && !currentlyReadingChapter.isSpecial">
|
||||
<div class="progress-banner" ngbTooltip="{{(series.pagesRead / series.pages) | number:'1.0-1'}}% Read">
|
||||
<ngb-progressbar type="primary" height="5px" [value]="series.pagesRead" [max]="series.pages"></ngb-progressbar>
|
||||
</div>
|
||||
<div class="under-image">
|
||||
{{t('continue-from', {title: ContinuePointTitle})}}
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
||||
</app-side-nav-companion-bar>
|
||||
</div>
|
||||
|
||||
<div [ngStyle]="{'height': ScrollingBlockHeight}" class="main-container container-fluid pt-2" *ngIf="series !== undefined" #scrollingBlock>
|
||||
<div class="row mb-0 mb-xl-3 info-container">
|
||||
<div class="image-container col-4 col-sm-6 col-md-4 col-lg-4 col-xl-2 col-xxl-2 d-none d-sm-block mt-2">
|
||||
<div class="to-read-counter" *ngIf="unreadCount > 0 && unreadCount !== totalCount">
|
||||
<app-tag-badge [selectionMode]="TagBadgeCursor.NotAllowed" fillStyle="filled">{{unreadCount}}</app-tag-badge>
|
||||
</div>
|
||||
<app-image height="100%" maxHeight="400px" objectFit="contain" background="none" [imageUrl]="seriesImage"></app-image>
|
||||
<ng-container *ngIf="series.pagesRead < series.pages && hasReadingProgress && currentlyReadingChapter && !currentlyReadingChapter.isSpecial">
|
||||
<div class="progress-banner" ngbTooltip="{{(series.pagesRead / series.pages) | number:'1.0-1'}}% Read">
|
||||
<ngb-progressbar type="primary" height="5px" [value]="series.pagesRead" [max]="series.pages"></ngb-progressbar>
|
||||
</div>
|
||||
<div class="under-image">
|
||||
Continue {{ContinuePointTitle}}
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="col-xlg-10 col-lg-8 col-md-8 col-xs-8 col-sm-6 mt-2">
|
||||
<div class="row g-0">
|
||||
<div class="col-auto">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-primary" (click)="read()">
|
||||
<div class="col-xlg-10 col-lg-8 col-md-8 col-xs-8 col-sm-6 mt-2">
|
||||
<div class="row g-0">
|
||||
<div class="col-auto">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-primary" (click)="read()">
|
||||
<span>
|
||||
<i class="fa {{showBook ? 'fa-book-open' : 'fa-book'}}" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> {{(hasReadingProgress) ? 'Continue' : 'Read'}}</span>
|
||||
<span class="read-btn--text"> {{(hasReadingProgress) ? t('continue') : t('read')}}</span>
|
||||
</span>
|
||||
</button>
|
||||
<div class="btn-group" ngbDropdown role="group" display="dynamic" aria-label="Read options">
|
||||
<button type="button" class="btn btn-primary dropdown-toggle-split" ngbDropdownToggle></button>
|
||||
<div class="dropdown-menu" ngbDropdownMenu>
|
||||
<button ngbDropdownItem (click)="read(true)">
|
||||
</button>
|
||||
<div class="btn-group" ngbDropdown role="group" display="dynamic" [attr.aria-label]="t('read-options-alt')">
|
||||
<button type="button" class="btn btn-primary dropdown-toggle-split" ngbDropdownToggle></button>
|
||||
<div class="dropdown-menu" ngbDropdownMenu>
|
||||
<button ngbDropdownItem (click)="read(true)">
|
||||
<span>
|
||||
<i class="fa fa-glasses" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> {{(hasReadingProgress) ? 'Continue' : 'Read'}} Incognito</span>
|
||||
<span class="read-btn--text"> {{(hasReadingProgress) ? t('continue') : t('read')}} Incognito</span>
|
||||
</span>
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto ms-2">
|
||||
<button class="btn btn-secondary" (click)="toggleWantToRead()" title="{{isWantToRead ? 'Remove from' : 'Add to'}} Want to Read">
|
||||
<div class="col-auto ms-2">
|
||||
<button class="btn btn-secondary" (click)="toggleWantToRead()" title="{{isWantToRead ? t('remove-from-want-to-read') : t('add-to-want-to-read')}}">
|
||||
<span>
|
||||
<i class="{{isWantToRead ? 'fa-solid' : 'fa-regular'}} fa-star" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto ms-2" *ngIf="isAdmin">
|
||||
<button class="btn btn-secondary" (click)="openEditSeriesModal()" title="Edit Series information">
|
||||
<span><i class="fa fa-pen" aria-hidden="true"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto ms-2 d-none d-md-block">
|
||||
<div class="card-actions">
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="seriesActions" [labelBy]="series.name" iconClass="fa-ellipsis-h" btnClass="btn-secondary"></app-card-actionables>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto ms-2" *ngIf="isAdmin">
|
||||
<button class="btn btn-secondary" (click)="openEditSeriesModal()" [title]="t('edit-series-alt')">
|
||||
<span><i class="fa fa-pen" aria-hidden="true"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto ms-2 d-none d-md-block">
|
||||
<div class="card-actions">
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="seriesActions" [labelBy]="series.name" iconClass="fa-ellipsis-h" btnClass="btn-secondary"></app-card-actionables>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto ms-2 d-none d-md-block" *ngIf="isAdmin || hasDownloadingRole">
|
||||
<button class="btn btn-secondary" (click)="downloadSeries()" title="Download Series" [disabled]="downloadInProgress">
|
||||
<ng-container *ngIf="downloadInProgress; else notDownloading">
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">{{t('downloading-status')}}</span>
|
||||
</ng-container>
|
||||
<ng-template #notDownloading>
|
||||
<i class="fa fa-arrow-alt-circle-down" aria-hidden="true"></i>
|
||||
</ng-template>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto ms-2 d-none d-md-block" *ngIf="isAdmin || hasDownloadingRole">
|
||||
<button class="btn btn-secondary" (click)="downloadSeries()" title="Download Series" [disabled]="downloadInProgress">
|
||||
<ng-container *ngIf="downloadInProgress; else notDownloading">
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Downloading...</span>
|
||||
</ng-container>
|
||||
<ng-template #notDownloading>
|
||||
<i class="fa fa-arrow-alt-circle-down" aria-hidden="true"></i>
|
||||
</ng-template>
|
||||
</button>
|
||||
<div *ngIf="seriesMetadata" class="mt-2">
|
||||
<app-series-metadata-detail [seriesMetadata]="seriesMetadata" [readingLists]="readingLists" [series]="series"
|
||||
[libraryType]="libraryType"
|
||||
[hasReadingProgress]="hasReadingProgress"></app-series-metadata-detail>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="seriesMetadata" class="mt-2">
|
||||
<app-series-metadata-detail [seriesMetadata]="seriesMetadata" [readingLists]="readingLists" [series]="series"
|
||||
[libraryType]="libraryType"
|
||||
[hasReadingProgress]="hasReadingProgress"></app-series-metadata-detail>
|
||||
|
||||
<div class="row pt-4">
|
||||
<app-carousel-reel [items]="reviews" [alwaysShow]="true" [title]="t('user-reviews-alt')" iconClasses="fa-solid fa-{{getUserReview().length > 0 ? 'pen' : 'plus'}}" [clickableTitle]="true" (sectionClick)="openReviewModal()">
|
||||
<ng-template #carouselItem let-item let-position="idx">
|
||||
<app-review-card [review]="item"></app-review-card>
|
||||
</ng-template>
|
||||
</app-carousel-reel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row pt-4">
|
||||
<app-carousel-reel [items]="reviews" [alwaysShow]="true" title="User Reviews" iconClasses="fa-solid fa-{{getUserReview().length > 0 ? 'pen' : 'plus'}}" [clickableTitle]="true" (sectionClick)="openReviewModal()">
|
||||
<ng-template #carouselItem let-item let-position="idx">
|
||||
<app-review-card [review]="item"></app-review-card>
|
||||
</ng-template>
|
||||
</app-carousel-reel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="series">
|
||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTabId" class="nav nav-tabs mb-2" [destroyOnHide]="false" (navChange)="onNavChange($event)">
|
||||
<li [ngbNavItem]="TabID.Storyline" *ngIf="libraryType !== LibraryType.Book && (volumes.length > 0 || chapters.length > 0)">
|
||||
<a ngbNavLink>Storyline</a>
|
||||
<ng-template ngbNavContent>
|
||||
<virtual-scroller #scroll [items]="storylineItems" [bufferAmount]="1" [parentScroll]="scrollingBlock">
|
||||
<ng-container *ngIf="renderMode === PageLayoutMode.Cards; else storylineListLayout">
|
||||
<div class="card-container row g-0" #container>
|
||||
<ng-container *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackByStoryLineIdentity">
|
||||
<ng-container *ngIf="!item.isChapter; else chapterCardItem">
|
||||
<app-card-item class="col-auto mt-2 mb-2" *ngIf="item.volume.number !== 0" [entity]="item.volume" [title]="item.volume.name" (click)="openVolume(item.volume)"
|
||||
[imageUrl]="imageService.getVolumeCoverImage(item.volume.id)"
|
||||
[read]="item.volume.pagesRead" [total]="item.volume.pages" [actions]="volumeActions"
|
||||
[count]="item.volume.chapters[0].files.length"
|
||||
(selection)="bulkSelectionService.handleCardSelection('volume', scroll.viewPortInfo.startIndexWithBuffer + idx, volumes.length, $event)"
|
||||
[selected]="bulkSelectionService.isCardSelected('volume', scroll.viewPortInfo.startIndexWithBuffer + idx)" [allowSelection]="true"></app-card-item>
|
||||
</ng-container>
|
||||
<ng-template #chapterCardItem>
|
||||
<app-card-item class="col-auto mt-2 mb-2" *ngIf="!item.chapter.isSpecial" [entity]="item.chapter" [title]="item.chapter.title" (click)="openChapter(item.chapter)"
|
||||
[imageUrl]="imageService.getChapterCoverImage(item.chapter.id)"
|
||||
[read]="item.chapter.pagesRead" [total]="item.chapter.pages" [actions]="chapterActions"
|
||||
[count]="item.chapter.files.length"
|
||||
(selection)="bulkSelectionService.handleCardSelection('chapter', scroll.viewPortInfo.startIndexWithBuffer + idx, storyChapters.length, $event)"
|
||||
[selected]="bulkSelectionService.isCardSelected('chapter', scroll.viewPortInfo.startIndexWithBuffer + idx)" [allowSelection]="true"></app-card-item>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #storylineListLayout>
|
||||
<ng-container *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackByStoryLineIdentity">
|
||||
<ng-container *ngIf="!item.isChapter; else chapterListItem">
|
||||
<app-list-item [imageUrl]="imageService.getVolumeCoverImage(item.volume.id)" [libraryId]="libraryId"
|
||||
[seriesName]="series.name" [entity]="item.volume" *ngIf="item.volume.number !== 0"
|
||||
[actions]="volumeActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
|
||||
[pagesRead]="item.volume.pagesRead" [totalPages]="item.volume.pages" (read)="openVolume(item.volume)"
|
||||
[blur]="user?.preferences?.blurUnreadSummaries || false">
|
||||
<ng-container title>
|
||||
<app-entity-title [libraryType]="libraryType" [entity]="item.volume" [seriesName]="series.name" [prioritizeTitleName]="false"></app-entity-title>
|
||||
</ng-container>
|
||||
</app-list-item>
|
||||
</ng-container>
|
||||
<ng-template #chapterListItem>
|
||||
<app-list-item [imageUrl]="imageService.getChapterCoverImage(item.chapter.id)" [libraryId]="libraryId"
|
||||
[seriesName]="series.name" [entity]="item.chapter" *ngIf="!item.chapter.isSpecial"
|
||||
[actions]="chapterActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
|
||||
[pagesRead]="item.chapter.pagesRead" [totalPages]="item.chapter.pages" (read)="openChapter(item.chapter)"
|
||||
[blur]="user?.preferences?.blurUnreadSummaries || false">
|
||||
<ng-container title>
|
||||
<app-entity-title [libraryType]="libraryType" [entity]="item.chapter" [seriesName]="series.name" [prioritizeTitleName]="false"></app-entity-title>
|
||||
</ng-container>
|
||||
</app-list-item>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</virtual-scroller>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="TabID.Volumes" *ngIf="volumes.length > 0">
|
||||
<a ngbNavLink>{{libraryType === LibraryType.Book ? 'Books': 'Volumes'}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<virtual-scroller #scroll [items]="volumes" [parentScroll]="scrollingBlock">
|
||||
<ng-container *ngIf="renderMode === PageLayoutMode.Cards; else volumeListLayout">
|
||||
<div class="card-container row g-0" #container>
|
||||
<ng-container *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackByVolumeIdentity">
|
||||
<app-card-item class="col-auto mt-2 mb-2" [entity]="item" [title]="item.name" (click)="openVolume(item)"
|
||||
[imageUrl]="imageService.getVolumeCoverImage(item.id)"
|
||||
[read]="item.pagesRead" [total]="item.pages" [actions]="volumeActions"
|
||||
(selection)="bulkSelectionService.handleCardSelection('volume', scroll.viewPortInfo.startIndexWithBuffer + idx, volumes.length, $event)"
|
||||
[selected]="bulkSelectionService.isCardSelected('volume', scroll.viewPortInfo.startIndexWithBuffer + idx)" [allowSelection]="true">
|
||||
</app-card-item>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #volumeListLayout>
|
||||
<ng-container *ngFor="let volume of scroll.viewPortItems; let idx = index; trackBy: trackByVolumeIdentity">
|
||||
<app-list-item [imageUrl]="imageService.getVolumeCoverImage(volume.id)" [libraryId]="libraryId"
|
||||
[seriesName]="series.name" [entity]="volume"
|
||||
[actions]="volumeActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
|
||||
[pagesRead]="volume.pagesRead" [totalPages]="volume.pages" (read)="openVolume(volume)"
|
||||
[blur]="user?.preferences?.blurUnreadSummaries || false">
|
||||
<ng-container title>
|
||||
<app-entity-title [libraryType]="libraryType" [entity]="volume" [seriesName]="series.name"></app-entity-title>
|
||||
</ng-container>
|
||||
</app-list-item>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</virtual-scroller>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="TabID.Chapters" *ngIf="chapters.length > 0">
|
||||
<a ngbNavLink>{{utilityService.formatChapterName(libraryType) + 's'}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<virtual-scroller #scroll [items]="chapters" [parentScroll]="scrollingBlock">
|
||||
<ng-container *ngIf="renderMode === PageLayoutMode.Cards; else chapterListLayout">
|
||||
<div class="card-container row g-0" #container>
|
||||
<div *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackByChapterIdentity">
|
||||
<app-card-item class="col-auto mt-2 mb-2" *ngIf="!item.isSpecial" [entity]="item" [title]="item.title" (click)="openChapter(item)"
|
||||
[imageUrl]="imageService.getChapterCoverImage(item.id)"
|
||||
[read]="item.pagesRead" [total]="item.pages" [actions]="chapterActions"
|
||||
[count]="item.files.length"
|
||||
(selection)="bulkSelectionService.handleCardSelection('chapter', scroll.viewPortInfo.startIndexWithBuffer + idx, chapters.length, $event)"
|
||||
[selected]="bulkSelectionService.isCardSelected('chapter', scroll.viewPortInfo.startIndexWithBuffer + idx)" [allowSelection]="true">
|
||||
<ng-container title>
|
||||
<app-entity-title [libraryType]="libraryType" [entity]="item" [seriesName]="series.name" [includeVolume]="true"></app-entity-title>
|
||||
</ng-container>
|
||||
</app-card-item>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #chapterListLayout>
|
||||
<div *ngFor="let chapter of scroll.viewPortItems; let idx = index; trackBy: trackByChapterIdentity">
|
||||
<app-list-item [imageUrl]="imageService.getChapterCoverImage(chapter.id)" [libraryId]="libraryId"
|
||||
[seriesName]="series.name" [entity]="chapter" *ngIf="!chapter.isSpecial"
|
||||
[actions]="chapterActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
|
||||
[pagesRead]="chapter.pagesRead" [totalPages]="chapter.pages" (read)="openChapter(chapter)"
|
||||
[includeVolume]="true" [blur]="user?.preferences?.blurUnreadSummaries || false">
|
||||
<ng-container title>
|
||||
<app-entity-title [libraryType]="libraryType" [entity]="chapter" [seriesName]="series.name" [includeVolume]="true" [prioritizeTitleName]="false"></app-entity-title>
|
||||
</ng-container>
|
||||
</app-list-item>
|
||||
</div>
|
||||
</ng-template>
|
||||
</virtual-scroller>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="TabID.Specials" *ngIf="hasSpecials">
|
||||
<a ngbNavLink>Specials</a>
|
||||
<ng-template ngbNavContent>
|
||||
<virtual-scroller #scroll [items]="specials" [parentScroll]="scrollingBlock">
|
||||
<ng-container *ngIf="renderMode === PageLayoutMode.Cards; else specialListLayout">
|
||||
<div class="card-container row g-0" #container>
|
||||
<ng-container *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackByChapterIdentity">
|
||||
<app-card-item class="col-auto mt-2 mb-2" [entity]="item" [title]="item.title || item.range" (click)="openChapter(item)"
|
||||
[imageUrl]="imageService.getChapterCoverImage(item.id)"
|
||||
[read]="item.pagesRead" [total]="item.pages" [actions]="chapterActions"
|
||||
[count]="item.files.length"
|
||||
(selection)="bulkSelectionService.handleCardSelection('special', scroll.viewPortInfo.startIndexWithBuffer + idx, chapters.length, $event)"
|
||||
[selected]="bulkSelectionService.isCardSelected('special', scroll.viewPortInfo.startIndexWithBuffer + idx)" [allowSelection]="true">
|
||||
</app-card-item>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #specialListLayout>
|
||||
<ng-container *ngFor="let chapter of scroll.viewPortItems; let idx = index; trackBy: trackByChapterIdentity">
|
||||
<app-list-item [imageUrl]="imageService.getChapterCoverImage(chapter.id)" [libraryId]="libraryId"
|
||||
[seriesName]="series.name" [entity]="chapter"
|
||||
[actions]="chapterActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
|
||||
[pagesRead]="chapter.pagesRead" [totalPages]="chapter.pages" (read)="openChapter(chapter)"
|
||||
[blur]="user?.preferences?.blurUnreadSummaries || false">
|
||||
<ng-container title>
|
||||
{{chapter.title || chapter.range}}
|
||||
</ng-container>
|
||||
</app-list-item>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</virtual-scroller>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="TabID.Related" *ngIf="hasRelations">
|
||||
<a ngbNavLink>Related</a>
|
||||
<ng-template ngbNavContent>
|
||||
<virtual-scroller #scroll [items]="relations" [parentScroll]="scrollingBlock">
|
||||
<div class="card-container row g-0" #container>
|
||||
<ng-container *ngFor="let item of scroll.viewPortItems let idx = index; trackBy: trackByRelatedSeriesIdentify">
|
||||
<app-series-card class="col-auto mt-2 mb-2" [data]="item.series" [libraryId]="item.series.libraryId" [relation]="item.relation"></app-series-card>
|
||||
</ng-container>
|
||||
</div>
|
||||
</virtual-scroller>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="TabID.Recommendations" *ngIf="hasRecommendations">
|
||||
<a ngbNavLink>Recommendations</a>
|
||||
<ng-container *ngIf="series">
|
||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTabId" class="nav nav-tabs mb-2" [destroyOnHide]="false" (navChange)="onNavChange($event)">
|
||||
<li [ngbNavItem]="TabID.Storyline" *ngIf="libraryType !== LibraryType.Book && (volumes.length > 0 || chapters.length > 0)">
|
||||
<a ngbNavLink>{{t('storyline-tab')}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<virtual-scroller #scroll [items]="combinedRecs" [parentScroll]="scrollingBlock">
|
||||
<ng-container *ngIf="renderMode === PageLayoutMode.Cards; else recListLayout">
|
||||
<div class="card-container row g-0" #container>
|
||||
<ng-container *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackBySeriesIdentify">
|
||||
<ng-container *ngIf="!item.hasOwnProperty('coverUrl'); else externalRec">
|
||||
<app-series-card class="col-auto mt-2 mb-2" [data]="item" [libraryId]="item.libraryId"></app-series-card>
|
||||
</ng-container>
|
||||
<ng-template #externalRec>
|
||||
<app-external-series-card class="col-auto mt-2 mb-2" [data]="item"></app-external-series-card>
|
||||
</ng-template>
|
||||
<virtual-scroller #scroll [items]="storylineItems" [bufferAmount]="1" [parentScroll]="scrollingBlock">
|
||||
<ng-container *ngIf="renderMode === PageLayoutMode.Cards; else storylineListLayout">
|
||||
<div class="card-container row g-0" #container>
|
||||
<ng-container *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackByStoryLineIdentity">
|
||||
<ng-container *ngIf="!item.isChapter; else chapterCardItem">
|
||||
<app-card-item class="col-auto mt-2 mb-2" *ngIf="item.volume.number !== 0" [entity]="item.volume" [title]="item.volume.name" (click)="openVolume(item.volume)"
|
||||
[imageUrl]="imageService.getVolumeCoverImage(item.volume.id)"
|
||||
[read]="item.volume.pagesRead" [total]="item.volume.pages" [actions]="volumeActions"
|
||||
[count]="item.volume.chapters[0].files.length"
|
||||
(selection)="bulkSelectionService.handleCardSelection('volume', scroll.viewPortInfo.startIndexWithBuffer + idx, volumes.length, $event)"
|
||||
[selected]="bulkSelectionService.isCardSelected('volume', scroll.viewPortInfo.startIndexWithBuffer + idx)" [allowSelection]="true"></app-card-item>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #recListLayout>
|
||||
<ng-container *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackBySeriesIdentify">
|
||||
<ng-container *ngIf="!item.hasOwnProperty('coverUrl'); else externalRec">
|
||||
<app-external-list-item [imageUrl]="imageService.getSeriesCoverImage(item.id)" imageWidth="130px" imageHeight="" [summary]="item.summary">
|
||||
<ng-container title>
|
||||
<a href="/library/{{item.libraryId}}/series/{{item.id}}">{{item.name}}</a>
|
||||
</ng-container>
|
||||
</app-external-list-item>
|
||||
</ng-container>
|
||||
<ng-template #externalRec>
|
||||
<app-external-list-item [imageUrl]="item.coverUrl" imageWidth="130px" imageHeight="" [summary]="item.summary">
|
||||
<ng-container title>
|
||||
<a [href]="item.url" target="_blank" rel="noreferrer nofollow">{{item.name}}</a>
|
||||
</ng-container>
|
||||
</app-external-list-item>
|
||||
<ng-template #chapterCardItem>
|
||||
<app-card-item class="col-auto mt-2 mb-2" *ngIf="!item.chapter.isSpecial" [entity]="item.chapter" [title]="item.chapter.title" (click)="openChapter(item.chapter)"
|
||||
[imageUrl]="imageService.getChapterCoverImage(item.chapter.id)"
|
||||
[read]="item.chapter.pagesRead" [total]="item.chapter.pages" [actions]="chapterActions"
|
||||
[count]="item.chapter.files.length"
|
||||
(selection)="bulkSelectionService.handleCardSelection('chapter', scroll.viewPortInfo.startIndexWithBuffer + idx, storyChapters.length, $event)"
|
||||
[selected]="bulkSelectionService.isCardSelected('chapter', scroll.viewPortInfo.startIndexWithBuffer + idx)" [allowSelection]="true"></app-card-item>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
|
||||
</virtual-scroller>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #storylineListLayout>
|
||||
<ng-container *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackByStoryLineIdentity">
|
||||
<ng-container *ngIf="!item.isChapter; else chapterListItem">
|
||||
<app-list-item [imageUrl]="imageService.getVolumeCoverImage(item.volume.id)" [libraryId]="libraryId"
|
||||
[seriesName]="series.name" [entity]="item.volume" *ngIf="item.volume.number !== 0"
|
||||
[actions]="volumeActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
|
||||
[pagesRead]="item.volume.pagesRead" [totalPages]="item.volume.pages" (read)="openVolume(item.volume)"
|
||||
[blur]="user?.preferences?.blurUnreadSummaries || false">
|
||||
<ng-container title>
|
||||
<app-entity-title [libraryType]="libraryType" [entity]="item.volume" [seriesName]="series.name" [prioritizeTitleName]="false"></app-entity-title>
|
||||
</ng-container>
|
||||
</app-list-item>
|
||||
</ng-container>
|
||||
<ng-template #chapterListItem>
|
||||
<app-list-item [imageUrl]="imageService.getChapterCoverImage(item.chapter.id)" [libraryId]="libraryId"
|
||||
[seriesName]="series.name" [entity]="item.chapter" *ngIf="!item.chapter.isSpecial"
|
||||
[actions]="chapterActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
|
||||
[pagesRead]="item.chapter.pagesRead" [totalPages]="item.chapter.pages" (read)="openChapter(item.chapter)"
|
||||
[blur]="user?.preferences?.blurUnreadSummaries || false">
|
||||
<ng-container title>
|
||||
<app-entity-title [libraryType]="libraryType" [entity]="item.chapter" [seriesName]="series.name" [prioritizeTitleName]="false"></app-entity-title>
|
||||
</ng-container>
|
||||
</app-list-item>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</virtual-scroller>
|
||||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
<div [ngbNavOutlet]="nav"></div>
|
||||
</ng-container>
|
||||
</li>
|
||||
|
||||
<app-loading [loading]="isLoading"></app-loading>
|
||||
</div>
|
||||
<li [ngbNavItem]="TabID.Volumes" *ngIf="volumes.length > 0">
|
||||
<a ngbNavLink>{{libraryType === LibraryType.Book ? t('books-tab') : t('volumes-tab')}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<virtual-scroller #scroll [items]="volumes" [parentScroll]="scrollingBlock">
|
||||
<ng-container *ngIf="renderMode === PageLayoutMode.Cards; else volumeListLayout">
|
||||
<div class="card-container row g-0" #container>
|
||||
<ng-container *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackByVolumeIdentity">
|
||||
<app-card-item class="col-auto mt-2 mb-2" [entity]="item" [title]="item.name" (click)="openVolume(item)"
|
||||
[imageUrl]="imageService.getVolumeCoverImage(item.id)"
|
||||
[read]="item.pagesRead" [total]="item.pages" [actions]="volumeActions"
|
||||
(selection)="bulkSelectionService.handleCardSelection('volume', scroll.viewPortInfo.startIndexWithBuffer + idx, volumes.length, $event)"
|
||||
[selected]="bulkSelectionService.isCardSelected('volume', scroll.viewPortInfo.startIndexWithBuffer + idx)" [allowSelection]="true">
|
||||
</app-card-item>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #volumeListLayout>
|
||||
<ng-container *ngFor="let volume of scroll.viewPortItems; let idx = index; trackBy: trackByVolumeIdentity">
|
||||
<app-list-item [imageUrl]="imageService.getVolumeCoverImage(volume.id)" [libraryId]="libraryId"
|
||||
[seriesName]="series.name" [entity]="volume"
|
||||
[actions]="volumeActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
|
||||
[pagesRead]="volume.pagesRead" [totalPages]="volume.pages" (read)="openVolume(volume)"
|
||||
[blur]="user?.preferences?.blurUnreadSummaries || false">
|
||||
<ng-container title>
|
||||
<app-entity-title [libraryType]="libraryType" [entity]="volume" [seriesName]="series.name"></app-entity-title>
|
||||
</ng-container>
|
||||
</app-list-item>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</virtual-scroller>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="TabID.Chapters" *ngIf="chapters.length > 0">
|
||||
<a ngbNavLink>{{utilityService.formatChapterName(libraryType) + 's'}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<virtual-scroller #scroll [items]="chapters" [parentScroll]="scrollingBlock">
|
||||
<ng-container *ngIf="renderMode === PageLayoutMode.Cards; else chapterListLayout">
|
||||
<div class="card-container row g-0" #container>
|
||||
<div *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackByChapterIdentity">
|
||||
<app-card-item class="col-auto mt-2 mb-2" *ngIf="!item.isSpecial" [entity]="item" [title]="item.title" (click)="openChapter(item)"
|
||||
[imageUrl]="imageService.getChapterCoverImage(item.id)"
|
||||
[read]="item.pagesRead" [total]="item.pages" [actions]="chapterActions"
|
||||
[count]="item.files.length"
|
||||
(selection)="bulkSelectionService.handleCardSelection('chapter', scroll.viewPortInfo.startIndexWithBuffer + idx, chapters.length, $event)"
|
||||
[selected]="bulkSelectionService.isCardSelected('chapter', scroll.viewPortInfo.startIndexWithBuffer + idx)" [allowSelection]="true">
|
||||
<ng-container title>
|
||||
<app-entity-title [libraryType]="libraryType" [entity]="item" [seriesName]="series.name" [includeVolume]="true"></app-entity-title>
|
||||
</ng-container>
|
||||
</app-card-item>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #chapterListLayout>
|
||||
<div *ngFor="let chapter of scroll.viewPortItems; let idx = index; trackBy: trackByChapterIdentity">
|
||||
<app-list-item [imageUrl]="imageService.getChapterCoverImage(chapter.id)" [libraryId]="libraryId"
|
||||
[seriesName]="series.name" [entity]="chapter" *ngIf="!chapter.isSpecial"
|
||||
[actions]="chapterActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
|
||||
[pagesRead]="chapter.pagesRead" [totalPages]="chapter.pages" (read)="openChapter(chapter)"
|
||||
[includeVolume]="true" [blur]="user?.preferences?.blurUnreadSummaries || false">
|
||||
<ng-container title>
|
||||
<app-entity-title [libraryType]="libraryType" [entity]="chapter" [seriesName]="series.name" [includeVolume]="true" [prioritizeTitleName]="false"></app-entity-title>
|
||||
</ng-container>
|
||||
</app-list-item>
|
||||
</div>
|
||||
</ng-template>
|
||||
</virtual-scroller>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="TabID.Specials" *ngIf="hasSpecials">
|
||||
<a ngbNavLink>{{t('specials-tab')}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<virtual-scroller #scroll [items]="specials" [parentScroll]="scrollingBlock">
|
||||
<ng-container *ngIf="renderMode === PageLayoutMode.Cards; else specialListLayout">
|
||||
<div class="card-container row g-0" #container>
|
||||
<ng-container *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackByChapterIdentity">
|
||||
<app-card-item class="col-auto mt-2 mb-2" [entity]="item" [title]="item.title || item.range" (click)="openChapter(item)"
|
||||
[imageUrl]="imageService.getChapterCoverImage(item.id)"
|
||||
[read]="item.pagesRead" [total]="item.pages" [actions]="chapterActions"
|
||||
[count]="item.files.length"
|
||||
(selection)="bulkSelectionService.handleCardSelection('special', scroll.viewPortInfo.startIndexWithBuffer + idx, chapters.length, $event)"
|
||||
[selected]="bulkSelectionService.isCardSelected('special', scroll.viewPortInfo.startIndexWithBuffer + idx)" [allowSelection]="true">
|
||||
</app-card-item>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #specialListLayout>
|
||||
<ng-container *ngFor="let chapter of scroll.viewPortItems; let idx = index; trackBy: trackByChapterIdentity">
|
||||
<app-list-item [imageUrl]="imageService.getChapterCoverImage(chapter.id)" [libraryId]="libraryId"
|
||||
[seriesName]="series.name" [entity]="chapter"
|
||||
[actions]="chapterActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
|
||||
[pagesRead]="chapter.pagesRead" [totalPages]="chapter.pages" (read)="openChapter(chapter)"
|
||||
[blur]="user?.preferences?.blurUnreadSummaries || false">
|
||||
<ng-container title>
|
||||
{{chapter.title || chapter.range}}
|
||||
</ng-container>
|
||||
</app-list-item>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</virtual-scroller>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="TabID.Related" *ngIf="hasRelations">
|
||||
<a ngbNavLink>{{t('related-tab')}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<virtual-scroller #scroll [items]="relations" [parentScroll]="scrollingBlock">
|
||||
<div class="card-container row g-0" #container>
|
||||
<ng-container *ngFor="let item of scroll.viewPortItems let idx = index; trackBy: trackByRelatedSeriesIdentify">
|
||||
<app-series-card class="col-auto mt-2 mb-2" [data]="item.series" [libraryId]="item.series.libraryId" [relation]="item.relation"></app-series-card>
|
||||
</ng-container>
|
||||
</div>
|
||||
</virtual-scroller>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="TabID.Recommendations" *ngIf="hasRecommendations">
|
||||
<a ngbNavLink>{{t('recommendations-tab')}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<virtual-scroller #scroll [items]="combinedRecs" [parentScroll]="scrollingBlock">
|
||||
<ng-container *ngIf="renderMode === PageLayoutMode.Cards; else recListLayout">
|
||||
<div class="card-container row g-0" #container>
|
||||
<ng-container *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackBySeriesIdentify">
|
||||
<ng-container *ngIf="!item.hasOwnProperty('coverUrl'); else externalRec">
|
||||
<app-series-card class="col-auto mt-2 mb-2" [data]="item" [libraryId]="item.libraryId"></app-series-card>
|
||||
</ng-container>
|
||||
<ng-template #externalRec>
|
||||
<app-external-series-card class="col-auto mt-2 mb-2" [data]="item"></app-external-series-card>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #recListLayout>
|
||||
<ng-container *ngFor="let item of scroll.viewPortItems; let idx = index; trackBy: trackBySeriesIdentify">
|
||||
<ng-container *ngIf="!item.hasOwnProperty('coverUrl'); else externalRec">
|
||||
<app-external-list-item [imageUrl]="imageService.getSeriesCoverImage(item.id)" imageWidth="130px" imageHeight="" [summary]="item.summary">
|
||||
<ng-container title>
|
||||
<a href="/library/{{item.libraryId}}/series/{{item.id}}">{{item.name}}</a>
|
||||
</ng-container>
|
||||
</app-external-list-item>
|
||||
</ng-container>
|
||||
<ng-template #externalRec>
|
||||
<app-external-list-item [imageUrl]="item.coverUrl" imageWidth="130px" imageHeight="" [summary]="item.summary">
|
||||
<ng-container title>
|
||||
<a [href]="item.url" target="_blank" rel="noreferrer nofollow">{{item.name}}</a>
|
||||
</ng-container>
|
||||
</app-external-list-item>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
|
||||
</virtual-scroller>
|
||||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
<div [ngbNavOutlet]="nav"></div>
|
||||
</ng-container>
|
||||
|
||||
<app-loading [loading]="isLoading"></app-loading>
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ import { ImageComponent } from '../../../shared/image/image.component';
|
|||
import { TagBadgeComponent } from '../../../shared/tag-badge/tag-badge.component';
|
||||
import { CardActionablesComponent } from '../../../cards/card-item/card-actionables/card-actionables.component';
|
||||
import { SideNavCompanionBarComponent } from '../../../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component';
|
||||
import {TranslocoModule, TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
interface RelatedSeriesPair {
|
||||
series: Series;
|
||||
|
|
@ -97,7 +98,7 @@ interface StoryLineItem {
|
|||
styleUrls: ['./series-detail.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [NgIf, SideNavCompanionBarComponent, CardActionablesComponent, ReactiveFormsModule, NgStyle, TagBadgeComponent, ImageComponent, NgbTooltip, NgbProgressbar, NgbDropdown, NgbDropdownToggle, NgbDropdownMenu, NgbDropdownItem, SeriesMetadataDetailComponent, CarouselReelComponent, ReviewCardComponent, BulkOperationsComponent, NgbNav, NgbNavItem, NgbNavLink, NgbNavContent, VirtualScrollerModule, NgFor, CardItemComponent, ListItemComponent, EntityTitleComponent, SeriesCardComponent, ExternalSeriesCardComponent, ExternalListItemComponent, NgbNavOutlet, LoadingComponent, DecimalPipe]
|
||||
imports: [NgIf, SideNavCompanionBarComponent, CardActionablesComponent, ReactiveFormsModule, NgStyle, TagBadgeComponent, ImageComponent, NgbTooltip, NgbProgressbar, NgbDropdown, NgbDropdownToggle, NgbDropdownMenu, NgbDropdownItem, SeriesMetadataDetailComponent, CarouselReelComponent, ReviewCardComponent, BulkOperationsComponent, NgbNav, NgbNavItem, NgbNavLink, NgbNavContent, VirtualScrollerModule, NgFor, CardItemComponent, ListItemComponent, EntityTitleComponent, SeriesCardComponent, ExternalSeriesCardComponent, ExternalListItemComponent, NgbNavOutlet, LoadingComponent, DecimalPipe, TranslocoModule]
|
||||
})
|
||||
export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
||||
|
||||
|
|
@ -192,7 +193,6 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
'renderMode': new FormControl(this.renderMode, []),
|
||||
});
|
||||
|
||||
isAscendingSort: boolean = false; // TODO: Get this from User preferences
|
||||
user: User | undefined;
|
||||
|
||||
bulkActionCallback = (action: ActionItem<any>, data: any) => {
|
||||
|
|
@ -295,7 +295,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
public navService: NavService,
|
||||
private offcanvasService: NgbOffcanvas, @Inject(DOCUMENT) private document: Document,
|
||||
private cdRef: ChangeDetectorRef, private scrollService: ScrollService,
|
||||
private deviceService: DeviceService
|
||||
private deviceService: DeviceService, private translocoService: TranslocoService
|
||||
) {
|
||||
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
|
||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||
|
|
@ -327,7 +327,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
if (event.event === EVENTS.SeriesRemoved) {
|
||||
const seriesRemovedEvent = event.payload as SeriesRemovedEvent;
|
||||
if (seriesRemovedEvent.seriesId === this.seriesId) {
|
||||
this.toastr.info('This series no longer exists');
|
||||
this.toastr.info(this.translocoService.translate('errors.series-doesnt-exist'));
|
||||
this.router.navigateByUrl('/libraries');
|
||||
}
|
||||
} else if (event.event === EVENTS.ScanSeries) {
|
||||
|
|
@ -474,7 +474,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
{
|
||||
const device = (action._extra!.data as Device);
|
||||
this.deviceService.sendTo([chapter.id], device.id).subscribe(() => {
|
||||
this.toastr.success('File emailed to ' + device.name);
|
||||
this.toastr.success(this.translocoService.translate('series-detail.send-to', {deviceName: device.name}));
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
@ -704,7 +704,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
openChapter(chapter: Chapter, incognitoMode = false) {
|
||||
if (this.bulkSelectionService.hasSelections()) return;
|
||||
if (chapter.pages === 0) {
|
||||
this.toastr.error('There are no pages. Kavita was not able to read this archive.');
|
||||
this.toastr.error(this.translocoService.translate('series-detail.no-pages'));
|
||||
return;
|
||||
}
|
||||
this.router.navigate(this.readerService.getNavigationArray(this.libraryId, this.seriesId, chapter.id, chapter.files[0].format), {queryParams: {incognitoMode}});
|
||||
|
|
@ -713,7 +713,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
openVolume(volume: Volume) {
|
||||
if (this.bulkSelectionService.hasSelections()) return;
|
||||
if (volume.chapters === undefined || volume.chapters?.length === 0) {
|
||||
this.toastr.error('There are no chapters to this volume. Cannot read.');
|
||||
this.toastr.error(this.translocoService.translate('series-detail.no-chapters'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -733,10 +733,6 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
this.openChapter([...volume.chapters].sort(this.utilityService.sortChapters)[0]);
|
||||
}
|
||||
|
||||
isNullOrEmpty(val: string) {
|
||||
return val === null || val === undefined || val === ''; // TODO: Validate if this code is used
|
||||
}
|
||||
|
||||
openViewInfo(data: Volume | Chapter) {
|
||||
const drawerRef = this.offcanvasService.open(CardDetailDrawerComponent, {position: 'bottom'});
|
||||
drawerRef.componentInstance.data = data;
|
||||
|
|
@ -755,7 +751,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
}
|
||||
|
||||
if (closeResult.coverImageUpdate) {
|
||||
this.toastr.info('It can take up to a minute for your browser to refresh the image. Until then, the old image may be shown on some pages.');
|
||||
this.toastr.info(this.translocoService.translate('series-detail.cover-change'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -785,10 +781,6 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
});
|
||||
}
|
||||
|
||||
preventClick(event: any) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
performAction(action: ActionItem<any>) {
|
||||
if (typeof action.callback === 'function') {
|
||||
|
|
@ -807,18 +799,6 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||
});
|
||||
}
|
||||
|
||||
updateSortOrder() {
|
||||
this.isAscendingSort = !this.isAscendingSort;
|
||||
// if (this.filter.sortOptions === null) {
|
||||
// this.filter.sortOptions = {
|
||||
// isAscending: this.isAscendingSort,
|
||||
// sortField: SortField.SortName
|
||||
// }
|
||||
// }
|
||||
|
||||
// this.filter.sortOptions.isAscending = this.isAscendingSort;
|
||||
}
|
||||
|
||||
toggleWantToRead() {
|
||||
if (this.isWantToRead) {
|
||||
this.actionService.removeMultipleSeriesFromWantToReadList([this.series.id]);
|
||||
|
|
|
|||
|
|
@ -1,131 +1,134 @@
|
|||
<div class="row g-0 mt-2 mb-2">
|
||||
<ng-container *transloco="let t; read: 'series-metadata-detail'">
|
||||
<div class="row g-0 mt-2 mb-2">
|
||||
<app-read-more [text]="seriesSummary" [maxLength]="250"></app-read-more>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<app-metadata-detail [tags]="['']" [libraryId]="series.libraryId" heading="Ratings">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-external-rating [seriesId]="series.id" [userRating]="series.userRating" [hasUserRated]="series.hasUserRated" [libraryType]="libraryType"></app-external-rating>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
|
||||
<ng-container *ngIf="WebLinks as links">
|
||||
<app-metadata-detail [tags]="links" [libraryId]="series.libraryId" heading="Links">
|
||||
<app-metadata-detail [tags]="['']" [libraryId]="series.libraryId" heading="Ratings">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<a class="col me-1" [href]="item | safeHtml" target="_blank" rel="noopener noreferrer" [title]="item">
|
||||
<img width="24" height="24" class="lazyload img-placeholder"
|
||||
[src]="imageService.errorWebLinkImage"
|
||||
[attr.data-src]="imageService.getWebLinkImage(item)"
|
||||
(error)="imageService.updateErroredWebLinkImage($event)"
|
||||
aria-hidden="true" alt="">
|
||||
</a>
|
||||
<app-external-rating [seriesId]="series.id" [userRating]="series.userRating" [hasUserRated]="series.hasUserRated" [libraryType]="libraryType"></app-external-rating>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.genres" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Genres" heading="Genres">
|
||||
<ng-template #titleTemplate let-item>{{item.title}}</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.tags" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Tags" heading="Tags">
|
||||
<ng-template #titleTemplate let-item>{{item.title}}</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.collectionTags" [libraryId]="series.libraryId" heading="Collections">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-tag-badge a11y-click="13,32" class="col-auto" (click)="navigate('collections', item.id)" [selectionMode]="TagBadgeCursor.Clickable">
|
||||
{{item.title}}
|
||||
</app-tag-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
<ng-container *ngIf="WebLinks as links">
|
||||
<app-metadata-detail [tags]="links" [libraryId]="series.libraryId" [heading]="t('links-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<a class="col me-1" [href]="item | safeHtml" target="_blank" rel="noopener noreferrer" [title]="item">
|
||||
<img width="24" height="24" class="lazyload img-placeholder"
|
||||
[src]="imageService.errorWebLinkImage"
|
||||
[attr.data-src]="imageService.getWebLinkImage(item)"
|
||||
(error)="imageService.updateErroredWebLinkImage($event)"
|
||||
aria-hidden="true" alt="">
|
||||
</a>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<app-metadata-detail [tags]="readingLists" [libraryId]="series.libraryId" heading="Reading Lists">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-tag-badge a11y-click="13,32" class="col-auto" (click)="navigate('lists', item.id)" [selectionMode]="TagBadgeCursor.Clickable">
|
||||
<app-metadata-detail [tags]="seriesMetadata.genres" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Genres" [heading]="t('genres-title')">
|
||||
<ng-template #titleTemplate let-item>{{item.title}}</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.tags" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Tags" [heading]="t('tags-title')">
|
||||
<ng-template #titleTemplate let-item>{{item.title}}</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.collectionTags" [libraryId]="series.libraryId" [heading]="t('collections-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-tag-badge a11y-click="13,32" class="col-auto" (click)="navigate('collections', item.id)" [selectionMode]="TagBadgeCursor.Clickable">
|
||||
{{item.title}}
|
||||
</app-tag-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
|
||||
<app-metadata-detail [tags]="readingLists" [libraryId]="series.libraryId" [heading]="t('reading-lists-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-tag-badge a11y-click="13,32" class="col-auto" (click)="navigate('lists', item.id)" [selectionMode]="TagBadgeCursor.Clickable">
|
||||
<span *ngIf="item.promoted">
|
||||
<i class="fa fa-angle-double-up" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">(promoted)</span>
|
||||
<span class="visually-hidden">({{t('promoted')}})</span>
|
||||
</span>
|
||||
{{item.title}}
|
||||
</app-tag-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
{{item.title}}
|
||||
</app-tag-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.writers" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Writers" heading="Writers/Authors">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
|
||||
<div #collapse="ngbCollapse" [(ngbCollapse)]="isCollapsed" id="extended-series-metadata">
|
||||
<app-metadata-detail [tags]="seriesMetadata.coverArtists" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.CoverArtists" heading="Cover Artists">
|
||||
<app-metadata-detail [tags]="seriesMetadata.writers" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Writers" [heading]="t('writers-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.characters" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Character" heading="Characters">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.colorists" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Colorist" heading="Colorists">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
<div #collapse="ngbCollapse" [(ngbCollapse)]="isCollapsed" id="extended-series-metadata">
|
||||
<app-metadata-detail [tags]="seriesMetadata.coverArtists" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.CoverArtists" [heading]="t('cover-artists-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.editors" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Editor" heading="Editors">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
<app-metadata-detail [tags]="seriesMetadata.characters" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Character" [heading]="t('characters-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.inkers" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Inker" heading="Inkers">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
<app-metadata-detail [tags]="seriesMetadata.colorists" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Colorist" [heading]="t('colorists-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.letterers" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Letterer" heading="Letterers">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
<app-metadata-detail [tags]="seriesMetadata.editors" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Editor" [heading]="t('editors-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.translators" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Translator" heading="Translators">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
<app-metadata-detail [tags]="seriesMetadata.inkers" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Inker" [heading]="t('inkers-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.pencillers" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Penciller" heading="Pencillers">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
<app-metadata-detail [tags]="seriesMetadata.letterers" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Letterer" [heading]="t('letterers-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<app-metadata-detail [tags]="seriesMetadata.publishers" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Publisher" heading="Publishers">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
<app-metadata-detail [tags]="seriesMetadata.translators" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Translator" [heading]="t('translators-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
</div>
|
||||
<app-metadata-detail [tags]="seriesMetadata.pencillers" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Penciller" [heading]="t('pencillers-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
<div class="row g-0">
|
||||
<app-metadata-detail [tags]="seriesMetadata.publishers" [libraryId]="series.libraryId" [queryParam]="FilterQueryParam.Publisher" [heading]="t('publishers-title')">
|
||||
<ng-template #itemTemplate let-item>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-metadata-detail>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<hr class="col mt-3" *ngIf="hasExtendedProperties" >
|
||||
<a [class.hidden]="hasExtendedProperties" *ngIf="hasExtendedProperties"
|
||||
class="col col-md-auto align-self-end read-more-link" (click)="toggleView()">
|
||||
<i aria-hidden="true" class="fa fa-caret-{{isCollapsed ? 'down' : 'up'}} me-1" aria-controls="extended-series-metadata"></i>
|
||||
See {{isCollapsed ? 'More' : 'Less'}}
|
||||
<i aria-hidden="true" class="fa fa-caret-{{isCollapsed ? 'down' : 'up'}} me-1" aria-controls="extended-series-metadata"></i>
|
||||
{{isCollapsed ? t('see-more') : t('see-less')}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-series-info-cards [series]="series" [seriesMetadata]="seriesMetadata" (goTo)="handleGoTo($event)" [hasReadingProgress]="hasReadingProgress"></app-series-info-cards>
|
||||
<app-series-info-cards [series]="series" [seriesMetadata]="seriesMetadata" (goTo)="handleGoTo($event)" [hasReadingProgress]="hasReadingProgress"></app-series-info-cards>
|
||||
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {NgbCollapse} from "@ng-bootstrap/ng-bootstrap";
|
|||
import {SeriesInfoCardsComponent} from "../../../cards/series-info-cards/series-info-cards.component";
|
||||
import {LibraryType} from "../../../_models/library";
|
||||
import {MetadataDetailComponent} from "../metadata-detail/metadata-detail.component";
|
||||
import {TranslocoModule} from "@ngneat/transloco";
|
||||
|
||||
|
||||
@Component({
|
||||
|
|
@ -27,7 +28,7 @@ import {MetadataDetailComponent} from "../metadata-detail/metadata-detail.compon
|
|||
standalone: true,
|
||||
imports: [CommonModule, TagBadgeComponent, BadgeExpanderComponent, SafeHtmlPipe, ExternalRatingComponent,
|
||||
ReadMoreComponent, A11yClickDirective, PersonBadgeComponent, NgbCollapse, SeriesInfoCardsComponent,
|
||||
MetadataDetailComponent],
|
||||
MetadataDetailComponent, TranslocoModule],
|
||||
templateUrl: './series-metadata-detail.component.html',
|
||||
styleUrls: ['./series-metadata-detail.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue