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,44 +1,46 @@
|
|||
<div class="overlay" *ngIf="selectedText.length > 0 || mode !== BookLineOverlayMode.None">
|
||||
<ng-container *transloco="let t; read: 'book-line-overlay'">
|
||||
<div class="overlay" *ngIf="selectedText.length > 0 || mode !== BookLineOverlayMode.None">
|
||||
|
||||
<div class="row g-0 justify-content-between">
|
||||
<ng-container [ngSwitch]="mode">
|
||||
<ng-container *ngSwitchCase="BookLineOverlayMode.None">
|
||||
<div class="row g-0 justify-content-between">
|
||||
<ng-container [ngSwitch]="mode">
|
||||
<ng-container *ngSwitchCase="BookLineOverlayMode.None">
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-icon btn-sm" (click)="copy()">
|
||||
<i class="fa-solid fa-copy" aria-hidden="true"></i>
|
||||
<div>Copy</div>
|
||||
<div>{{t('copy')}}</div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-icon btn-sm" (click)="switchMode(BookLineOverlayMode.Bookmark)">
|
||||
<i class="fa-solid fa-book-bookmark" aria-hidden="true"></i>
|
||||
<div>Bookmark</div>
|
||||
<div>{{t('bookmark')}}</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-icon btn-sm" (click)="reset()">
|
||||
<i class="fa-solid fa-times-circle" aria-hidden="true"></i>
|
||||
<div>Close</div>
|
||||
<div>{{t('close')}}</div>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="BookLineOverlayMode.Bookmark">
|
||||
<form [formGroup]="bookmarkForm">
|
||||
<div class="input-group">
|
||||
<input id="bookmark-name" class="form-control" formControlName="name" type="text" placeholder="Bookmark Name"
|
||||
[class.is-invalid]="bookmarkForm.get('name')?.invalid && bookmarkForm.get('name')?.touched" aria-describedby="bookmark-name-btn">
|
||||
<button class="btn btn-outline-primary" id="bookmark-name-btn" (click)="createPTOC()">Save</button>
|
||||
<div id="bookmark-name-validations" class="invalid-feedback" *ngIf="bookmarkForm.dirty || bookmarkForm.touched">
|
||||
<div *ngIf="bookmarkForm.get('name')?.errors?.required" role="status">
|
||||
This field is required
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="BookLineOverlayMode.Bookmark">
|
||||
<form [formGroup]="bookmarkForm">
|
||||
<div class="input-group">
|
||||
<input id="bookmark-name" class="form-control" formControlName="name" type="text" [placeholder]="t('book-label')"
|
||||
[class.is-invalid]="bookmarkForm.get('name')?.invalid && bookmarkForm.get('name')?.touched" aria-describedby="bookmark-name-btn">
|
||||
<button class="btn btn-outline-primary" id="bookmark-name-btn" (click)="createPTOC()">{{t('save')}}</button>
|
||||
<div id="bookmark-name-validations" class="invalid-feedback" *ngIf="bookmarkForm.dirty || bookmarkForm.touched">
|
||||
<div *ngIf="bookmarkForm.get('name')?.errors?.required" role="status">
|
||||
{{t('required-field')}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ import {
|
|||
} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {fromEvent, merge, of} from "rxjs";
|
||||
import {catchError, filter, tap} from "rxjs/operators";
|
||||
import {catchError} from "rxjs/operators";
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import getBoundingClientRect from "@popperjs/core/lib/dom-utils/getBoundingClientRect";
|
||||
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms";
|
||||
import {ReaderService} from "../../../_services/reader.service";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {translate, TranslocoModule} from "@ngneat/transloco";
|
||||
|
||||
enum BookLineOverlayMode {
|
||||
None = 0,
|
||||
|
|
@ -24,7 +24,7 @@ enum BookLineOverlayMode {
|
|||
@Component({
|
||||
selector: 'app-book-line-overlay',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ReactiveFormsModule],
|
||||
imports: [CommonModule, ReactiveFormsModule, TranslocoModule],
|
||||
templateUrl: './book-line-overlay.component.html',
|
||||
styleUrls: ['./book-line-overlay.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
|
@ -132,7 +132,7 @@ export class BookLineOverlayComponent implements OnInit {
|
|||
const selection = window.getSelection();
|
||||
if (selection) {
|
||||
await navigator.clipboard.writeText(selection.toString());
|
||||
this.toastr.info('Copied to clipboard');
|
||||
this.toastr.info(translate('toasts.copied-to-clipboard'));
|
||||
}
|
||||
this.reset();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,155 +1,159 @@
|
|||
<div class="container-flex {{darkMode ? 'dark-mode' : ''}} reader-container {{ColumnLayout}} {{WritingStyleClass}}" tabindex="0" #reader>
|
||||
<ng-container *transloco="let t; read: 'book-reader'">
|
||||
<div class="container-flex {{darkMode ? 'dark-mode' : ''}} reader-container {{ColumnLayout}} {{WritingStyleClass}}" tabindex="0" #reader>
|
||||
<div class="fixed-top" #stickyTop>
|
||||
<a class="visually-hidden-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">Skip to main content</a>
|
||||
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
|
||||
<app-book-line-overlay [parent]="bookContainerElemRef" *ngIf="page !== undefined"
|
||||
[libraryId]="libraryId"
|
||||
[volumeId]="volumeId"
|
||||
[chapterId]="chapterId"
|
||||
[seriesId]="seriesId"
|
||||
[pageNumber]="pageNum"
|
||||
(refreshToC)="refreshPersonalToC()">
|
||||
<a class="visually-hidden-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">{{t('skip-header')}}</a>
|
||||
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
|
||||
<app-book-line-overlay [parent]="bookContainerElemRef" *ngIf="page !== undefined"
|
||||
[libraryId]="libraryId"
|
||||
[volumeId]="volumeId"
|
||||
[chapterId]="chapterId"
|
||||
[seriesId]="seriesId"
|
||||
[pageNumber]="pageNum"
|
||||
(refreshToC)="refreshPersonalToC()">
|
||||
</app-book-line-overlay>
|
||||
<app-drawer #commentDrawer="drawer" [(isOpen)]="drawerOpen" [options]="{topOffset: topOffset}">
|
||||
<h5 header>
|
||||
Book Settings
|
||||
</h5>
|
||||
<div subheader>
|
||||
<div class="pagination-cont">
|
||||
<ng-container *ngIf="layoutMode !== BookPageLayoutMode.Default">
|
||||
<div class="virt-pagination-cont">
|
||||
<div class="g-0 text-center">
|
||||
Page
|
||||
</div>
|
||||
<div class="d-flex align-items-center justify-content-between text-center row g-0" *ngIf="getVirtualPage() as vp" >
|
||||
<button class="btn btn-small btn-icon col-1" (click)="prevPage()" title="Prev Page">
|
||||
<i class="fa-solid fa-caret-left" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="col-1">{{vp[0]}}</div>
|
||||
<div class="col-8">
|
||||
<ngb-progressbar title="virtual pages" type="primary" height="5px" (click)="loadPage()" [value]="vp[0]" [max]="vp[1]"></ngb-progressbar>
|
||||
</div>
|
||||
<div class="col-1 btn-icon" (click)="loadPage()" title="Go to last page">{{vp[1]}}</div>
|
||||
<button class="btn btn-small btn-icon col-1" (click)="nextPage()" title="Next Page"><i class="fa-solid fa-caret-right" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="g-0 text-center">
|
||||
Section
|
||||
</div>
|
||||
<div class="d-flex align-items-center justify-content-between text-center row g-0">
|
||||
<button class="btn btn-small btn-icon col-1" [disabled]="prevChapterDisabled" (click)="loadPrevChapter()" title="Prev Chapter/Volume"><i class="fa fa-fast-backward" aria-hidden="true"></i></button>
|
||||
<div class="col-1" (click)="goToPage(0)">{{pageNum}}</div>
|
||||
<div class="col-8">
|
||||
<ngb-progressbar style="cursor: pointer" title="Go to page" (click)="goToPage()" type="primary" height="5px" [value]="pageNum" [max]="maxPages - 1"></ngb-progressbar>
|
||||
</div>
|
||||
<div class="col-1 btn-icon" (click)="goToPage(maxPages - 1)" title="Go to last page">{{maxPages - 1}}</div>
|
||||
<button class="btn btn-small btn-icon col-1" [disabled]="nextChapterDisabled" (click)="loadNextChapter()" title="Next Chapter/Volume"><i class="fa fa-fast-forward" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<app-drawer #commentDrawer="drawer" [(isOpen)]="drawerOpen" [options]="{topOffset: topOffset}">
|
||||
<h5 header>
|
||||
{{t('title')}}
|
||||
</h5>
|
||||
<div subheader>
|
||||
<div class="pagination-cont">
|
||||
<ng-container *ngIf="layoutMode !== BookPageLayoutMode.Default">
|
||||
<div class="virt-pagination-cont">
|
||||
<div class="g-0 text-center">
|
||||
{{t('page')}}
|
||||
</div>
|
||||
</div>
|
||||
<div body class="drawer-body">
|
||||
<nav role="navigation">
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTabId" class="reader-pills nav nav-pills mb-2" [destroyOnHide]="false">
|
||||
<li [ngbNavItem]="TabID.Settings">
|
||||
<a ngbNavLink>Settings</a>
|
||||
<ng-template ngbNavContent>
|
||||
<app-reader-settings
|
||||
(colorThemeUpdate)="updateColorTheme($event)"
|
||||
(styleUpdate)="updateReaderStyles($event)"
|
||||
(clickToPaginateChanged)="showPaginationOverlay($event)"
|
||||
(fullscreen)="toggleFullscreen()"
|
||||
(bookReaderWritingStyle)="updateWritingStyle($event)"
|
||||
(layoutModeUpdate)="updateLayoutMode($event)"
|
||||
(readingDirection)="updateReadingDirection($event)"
|
||||
(immersiveMode)="updateImmersiveMode($event)"
|
||||
></app-reader-settings>
|
||||
</ng-template>
|
||||
</li>
|
||||
<div class="d-flex align-items-center justify-content-between text-center row g-0" *ngIf="getVirtualPage() as vp" >
|
||||
<button class="btn btn-small btn-icon col-1" (click)="prevPage()" [title]="t('prev-page')">
|
||||
<i class="fa-solid fa-caret-left" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="col-1">{{vp[0]}}</div>
|
||||
<div class="col-8">
|
||||
<ngb-progressbar [title]="t('virtual-pages')" type="primary" height="5px" (click)="loadPage()" [value]="vp[0]" [max]="vp[1]"></ngb-progressbar>
|
||||
</div>
|
||||
<div class="col-1 btn-icon" (click)="loadPage()" [title]="t('go-to-last-page')">{{vp[1]}}</div>
|
||||
<button class="btn btn-small btn-icon col-1" (click)="nextPage()" [title]="t('next-page')"><i class="fa-solid fa-caret-right" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<li [ngbNavItem]="TabID.TableOfContents">
|
||||
<a ngbNavLink>Table of Contents</a>
|
||||
<ng-template ngbNavContent>
|
||||
<ul #subnav="ngbNav" ngbNav [(activeId)]="tocId" class="reader-pills nav nav-pills mb-2" [destroyOnHide]="false">
|
||||
<li [ngbNavItem]="TabID.TableOfContents">
|
||||
<a ngbNavLink>ToC</a>
|
||||
<ng-template ngbNavContent>
|
||||
<app-table-of-contents [chapters]="chapters" [chapterId]="chapterId" [pageNum]="pageNum"
|
||||
[currentPageAnchor]="currentPageAnchor" (loadChapter)="loadChapterPage($event)"></app-table-of-contents>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li [ngbNavItem]="TabID.PersonalTableOfContents">
|
||||
<a ngbNavLink>Bookmarks</a>
|
||||
<ng-template ngbNavContent>
|
||||
<app-personal-table-of-contents [chapterId]="chapterId" [pageNum]="pageNum" (loadChapter)="loadChapterPart($event)"
|
||||
[tocRefresh]="refreshPToC"></app-personal-table-of-contents>
|
||||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
<div [ngbNavOutlet]="subnav" class="mt-3"></div>
|
||||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div [ngbNavOutlet]="nav" class="mt-3"></div>
|
||||
<div class="g-0 text-center">
|
||||
{{t('pagination-header')}}
|
||||
</div>
|
||||
</app-drawer>
|
||||
<div class="d-flex align-items-center justify-content-between text-center row g-0">
|
||||
<button class="btn btn-small btn-icon col-1" [disabled]="prevChapterDisabled" (click)="loadPrevChapter()" [title]="t('prev-chapter')"><i class="fa fa-fast-backward" aria-hidden="true"></i></button>
|
||||
<div class="col-1" (click)="goToPage(0)">{{pageNum}}</div>
|
||||
<div class="col-8">
|
||||
<ngb-progressbar style="cursor: pointer" [title]="t('go-to-page')" (click)="goToPage()" type="primary" height="5px" [value]="pageNum" [max]="maxPages - 1"></ngb-progressbar>
|
||||
</div>
|
||||
<div class="col-1 btn-icon" (click)="goToPage(maxPages - 1)" [title]="t('go-to-last-page')">{{maxPages - 1}}</div>
|
||||
<button class="btn btn-small btn-icon col-1" [disabled]="nextChapterDisabled" (click)="loadNextChapter()" [title]="t('next-chapter')"><i class="fa fa-fast-forward" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div body class="drawer-body">
|
||||
<nav role="navigation">
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTabId" class="reader-pills nav nav-pills mb-2" [destroyOnHide]="false">
|
||||
<li [ngbNavItem]="TabID.Settings">
|
||||
<a ngbNavLink>{{t('settings-header')}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<app-reader-settings
|
||||
(colorThemeUpdate)="updateColorTheme($event)"
|
||||
(styleUpdate)="updateReaderStyles($event)"
|
||||
(clickToPaginateChanged)="showPaginationOverlay($event)"
|
||||
(fullscreen)="toggleFullscreen()"
|
||||
(bookReaderWritingStyle)="updateWritingStyle($event)"
|
||||
(layoutModeUpdate)="updateLayoutMode($event)"
|
||||
(readingDirection)="updateReadingDirection($event)"
|
||||
(immersiveMode)="updateImmersiveMode($event)"
|
||||
></app-reader-settings>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
||||
<li [ngbNavItem]="TabID.TableOfContents">
|
||||
<a ngbNavLink>{{t('table-of-contents-header')}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<ul #subnav="ngbNav" ngbNav [(activeId)]="tocId" class="reader-pills nav nav-pills mb-2" [destroyOnHide]="false">
|
||||
<li [ngbNavItem]="TabID.TableOfContents">
|
||||
<a ngbNavLink>{{t('toc-header')}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<app-table-of-contents [chapters]="chapters" [chapterId]="chapterId" [pageNum]="pageNum"
|
||||
[currentPageAnchor]="currentPageAnchor" (loadChapter)="loadChapterPage($event)"></app-table-of-contents>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li [ngbNavItem]="TabID.PersonalTableOfContents">
|
||||
<a ngbNavLink>{{t('bookmarks-header')}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<app-personal-table-of-contents [chapterId]="chapterId" [pageNum]="pageNum" (loadChapter)="loadChapterPart($event)"
|
||||
[tocRefresh]="refreshPToC"></app-personal-table-of-contents>
|
||||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
<div [ngbNavOutlet]="subnav" class="mt-3"></div>
|
||||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div [ngbNavOutlet]="nav" class="mt-3"></div>
|
||||
</div>
|
||||
</app-drawer>
|
||||
</div>
|
||||
|
||||
<div #readingSection class="reading-section {{ColumnLayout}} {{WritingStyleClass}}" [ngStyle]="{'width': PageWidthForPagination}" [ngClass]="{'immersive' : immersiveMode || !actionBarVisible}" [@isLoading]="isLoading">
|
||||
|
||||
<ng-container *ngIf="clickToPaginate">
|
||||
<div class="left {{clickOverlayClass('left')}} no-observe" [ngClass]="{'immersive' : immersiveMode}"
|
||||
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
|
||||
tabindex="-1" [ngStyle]="{height: PageHeightForPagination}"></div>
|
||||
<div class="{{scrollbarNeeded ? 'right-with-scrollbar' : 'right'}} {{clickOverlayClass('right')}} no-observe"
|
||||
[ngClass]="{'immersive' : immersiveMode}"
|
||||
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)"
|
||||
tabindex="-1" [ngStyle]="{height: PageHeightForPagination}"></div>
|
||||
</ng-container>
|
||||
<div #bookContainer class="book-container {{WritingStyleClass}}" [ngClass]="{'immersive' : immersiveMode}">
|
||||
<ng-container *ngIf="clickToPaginate">
|
||||
<div class="left {{clickOverlayClass('left')}} no-observe" [ngClass]="{'immersive' : immersiveMode}"
|
||||
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
|
||||
tabindex="-1" [ngStyle]="{height: PageHeightForPagination}"></div>
|
||||
<div class="{{scrollbarNeeded ? 'right-with-scrollbar' : 'right'}} {{clickOverlayClass('right')}} no-observe"
|
||||
[ngClass]="{'immersive' : immersiveMode}"
|
||||
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)"
|
||||
tabindex="-1" [ngStyle]="{height: PageHeightForPagination}"></div>
|
||||
</ng-container>
|
||||
<div #bookContainer class="book-container {{WritingStyleClass}}" [ngClass]="{'immersive' : immersiveMode}">
|
||||
|
||||
<div #readingHtml class="book-content {{ColumnLayout}} {{WritingStyleClass}}"
|
||||
[ngStyle]="{'max-height': ColumnHeight, 'max-width': VerticalBookContentWidth, 'width': VerticalBookContentWidth, 'column-width': ColumnWidth}"
|
||||
[ngClass]="{'immersive': immersiveMode && actionBarVisible}"
|
||||
[innerHtml]="page" *ngIf="page !== undefined" (click)="toggleMenu($event)" (mousedown)="mouseDown($event)" (wheel)="onWheel($event)"></div>
|
||||
<div *ngIf="page !== undefined && (scrollbarNeeded || layoutMode !== BookPageLayoutMode.Default) && !(writingStyle === WritingStyle.Vertical && layoutMode === BookPageLayoutMode.Default)"
|
||||
(click)="$event.stopPropagation();"
|
||||
[ngClass]="{'bottom-bar': layoutMode !== BookPageLayoutMode.Default}">
|
||||
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
|
||||
</div>
|
||||
<div #readingHtml class="book-content {{ColumnLayout}} {{WritingStyleClass}}"
|
||||
[ngStyle]="{'max-height': ColumnHeight, 'max-width': VerticalBookContentWidth, 'width': VerticalBookContentWidth, 'column-width': ColumnWidth}"
|
||||
[ngClass]="{'immersive': immersiveMode && actionBarVisible}"
|
||||
[innerHtml]="page" *ngIf="page !== undefined" (click)="toggleMenu($event)" (mousedown)="mouseDown($event)" (wheel)="onWheel($event)"></div>
|
||||
<div *ngIf="page !== undefined && (scrollbarNeeded || layoutMode !== BookPageLayoutMode.Default) && !(writingStyle === WritingStyle.Vertical && layoutMode === BookPageLayoutMode.Default)"
|
||||
(click)="$event.stopPropagation();"
|
||||
[ngClass]="{'bottom-bar': layoutMode !== BookPageLayoutMode.Default}">
|
||||
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #actionBar>
|
||||
<div class="action-bar row g-0 justify-content-between" *ngIf="!immersiveMode || drawerOpen || actionBarVisible">
|
||||
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1" (click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
|
||||
<div class="action-bar row g-0 justify-content-between" *ngIf="!immersiveMode || drawerOpen || actionBarVisible">
|
||||
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1" (click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
|
||||
[disabled]="readingDirection === ReadingDirection.LeftToRight ? IsPrevDisabled : IsNextDisabled"
|
||||
title="{{readingDirection === ReadingDirection.LeftToRight ? 'Previous' : 'Next'}} Page">
|
||||
<i class="fa {{(readingDirection === ReadingDirection.LeftToRight ? IsPrevChapter : IsNextChapter) ? 'fa-angle-double-left' : 'fa-angle-left'}} {{readingDirection === ReadingDirection.RightToLeft ? 'next-page-highlight' : ''}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button *ngIf="!this.adhocPageHistory.isEmpty()" class="btn btn-outline-secondary btn-icon col-2 col-xs-1" (click)="goBack()" title="Go Back">
|
||||
<i class="fa fa-reply" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button class="btn btn-secondary col-2 col-xs-1" (click)="toggleDrawer()">
|
||||
<i class="fa fa-bars" aria-hidden="true"></i></button>
|
||||
<div class="book-title col-2 d-none d-sm-block">
|
||||
<ng-container *ngIf="isLoading; else showTitle">
|
||||
<div class="spinner-border spinner-border-sm text-primary" style="border-radius: 50%;" role="status">
|
||||
<span class="visually-hidden">Loading book...</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #showTitle>
|
||||
<span *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" aria-label="Incognito mode is on. Toggle to turn off.">(<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">Incognito Mode</span>)</span>
|
||||
<span class="book-title-text ms-1" [ngbTooltip]="bookTitle">{{bookTitle}}</span>
|
||||
</ng-template>
|
||||
title="{{readingDirection === ReadingDirection.LeftToRight ? t('previous') : t('next')}} Page">
|
||||
<i class="fa {{(readingDirection === ReadingDirection.LeftToRight ? IsPrevChapter : IsNextChapter) ? 'fa-angle-double-left' : 'fa-angle-left'}} {{readingDirection === ReadingDirection.RightToLeft ? 'next-page-highlight' : ''}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button *ngIf="!this.adhocPageHistory.isEmpty()" class="btn btn-outline-secondary btn-icon col-2 col-xs-1" (click)="goBack()" [title]="t('go-back')">
|
||||
<i class="fa fa-reply" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button class="btn btn-secondary col-2 col-xs-1" (click)="toggleDrawer()">
|
||||
<i class="fa fa-bars" aria-hidden="true"></i></button>
|
||||
<div class="book-title col-2 d-none d-sm-block">
|
||||
<ng-container *ngIf="isLoading; else showTitle">
|
||||
<div class="spinner-border spinner-border-sm text-primary" style="border-radius: 50%;" role="status">
|
||||
<span class="visually-hidden">{{t('loading-book')}}</span>
|
||||
</div>
|
||||
<button class="btn btn-secondary col-2 col-xs-1" (click)="closeReader()"><i class="fa fa-times-circle" aria-hidden="true"></i></button>
|
||||
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1"
|
||||
[disabled]="readingDirection === ReadingDirection.LeftToRight ? IsNextDisabled : IsPrevDisabled"
|
||||
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)" title="{{readingDirection === ReadingDirection.LeftToRight ? 'Next' : 'Previous'}} Page">
|
||||
<i class="fa {{(readingDirection === ReadingDirection.LeftToRight ? IsNextChapter : IsPrevChapter) ? 'fa-angle-double-right' : 'fa-angle-right'}} {{readingDirection === ReadingDirection.LeftToRight ? 'next-page-highlight' : ''}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
</ng-container>
|
||||
<ng-template #showTitle>
|
||||
<span *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" [attr.aria-label]="t('incognito-mode-alt')">
|
||||
(<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">{{t('incognito-mode-label')}}</span>)</span>
|
||||
<span class="book-title-text ms-1" [ngbTooltip]="bookTitle">{{bookTitle}}</span>
|
||||
</ng-template>
|
||||
</div>
|
||||
<button class="btn btn-secondary col-2 col-xs-1" (click)="closeReader()"><i class="fa fa-times-circle" aria-hidden="true"></i></button>
|
||||
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1"
|
||||
[disabled]="readingDirection === ReadingDirection.LeftToRight ? IsNextDisabled : IsPrevDisabled"
|
||||
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)" title="{{readingDirection === ReadingDirection.LeftToRight ? t('next') : t('previous')}} Page">
|
||||
<i class="fa {{(readingDirection === ReadingDirection.LeftToRight ? IsNextChapter : IsPrevChapter) ? 'fa-angle-double-right' : 'fa-angle-right'}} {{readingDirection === ReadingDirection.LeftToRight ? 'next-page-highlight' : ''}}" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -1,42 +1,42 @@
|
|||
@font-face {
|
||||
font-family: "Fira_Sans";
|
||||
src: url(../../../../assets/fonts/Fira_Sans/FiraSans-Regular.ttf) format("truetype");
|
||||
src: url(../../../../assets/fonts/Fira_Sans/FiraSans-Regular.woff2) format("woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Lato";
|
||||
src: url(../../../../assets/fonts/Lato/Lato-Regular.ttf) format("truetype");
|
||||
src: url(../../../../assets/fonts/Lato/Lato-Regular.woff2) format("woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Libre_Baskerville";
|
||||
src: url(../../../../assets/fonts/Libre_Baskerville/LibreBaskerville-Regular.ttf) format("truetype");
|
||||
src: url(../../../../assets/fonts/Libre_Baskerville/LibreBaskerville-Regular.woff2) format("woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Merriweather";
|
||||
src: url(../../../../assets/fonts/Merriweather/Merriweather-Regular.ttf) format("truetype");
|
||||
src: url(../../../../assets/fonts/Merriweather/Merriweather-Regular.woff2) format("woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Nanum_Gothic";
|
||||
src: url(../../../../assets/fonts/Nanum_Gothic/NanumGothic-Regular.ttf) format("truetype");
|
||||
src: url(../../../../assets/fonts/Nanum_Gothic/NanumGothic-Regular.woff2) format("woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "RocknRoll_One";
|
||||
src: url(../../../../assets/fonts/RocknRoll_One/RocknRollOne-Regular.ttf) format("truetype");
|
||||
src: url(../../../../assets/fonts/RocknRoll_One/RocknRollOne-Regular.woff2) format("woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "OpenDyslexic2";
|
||||
src: url(../../../../assets/fonts/OpenDyslexic2/OpenDyslexic-Regular.otf) format("opentype");
|
||||
src: url(../../../../assets/fonts/OpenDyslexic2/OpenDyslexic-Regular.woff2) format("woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import {
|
|||
PersonalTableOfContentsComponent,
|
||||
PersonalToCEvent
|
||||
} from "../personal-table-of-contents/personal-table-of-contents.component";
|
||||
import {translate, TranslocoModule} from "@ngneat/transloco";
|
||||
|
||||
|
||||
enum TabID {
|
||||
|
|
@ -101,7 +102,7 @@ const elementLevelStyles = ['line-height', 'font-family'];
|
|||
])
|
||||
],
|
||||
standalone: true,
|
||||
imports: [NgTemplateOutlet, DrawerComponent, NgIf, NgbProgressbar, NgbNav, NgbNavItem, NgbNavItemRole, NgbNavLink, NgbNavContent, ReaderSettingsComponent, TableOfContentsComponent, NgbNavOutlet, NgStyle, NgClass, NgbTooltip, BookLineOverlayComponent, PersonalTableOfContentsComponent]
|
||||
imports: [NgTemplateOutlet, DrawerComponent, NgIf, NgbProgressbar, NgbNav, NgbNavItem, NgbNavItemRole, NgbNavLink, NgbNavContent, ReaderSettingsComponent, TableOfContentsComponent, NgbNavOutlet, NgStyle, NgClass, NgbTooltip, BookLineOverlayComponent, PersonalTableOfContentsComponent, TranslocoModule]
|
||||
})
|
||||
export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
|
|
@ -577,7 +578,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.memberService.hasReadingProgress(this.libraryId).pipe(take(1)).subscribe(hasProgress => {
|
||||
if (!hasProgress) {
|
||||
this.toggleDrawer();
|
||||
this.toastr.info('You can modify book settings, save those settings for all books, and view table of contents from the drawer.');
|
||||
this.toastr.info(translate('toasts.book-settings-info'));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -782,12 +783,14 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
// Load chapter Id onto route but don't reload
|
||||
const newRoute = this.readerService.getNextChapterUrl(this.router.url, this.chapterId, this.incognitoMode, this.readingListMode, this.readingListId);
|
||||
window.history.replaceState({}, '', newRoute);
|
||||
this.toastr.info(direction + ' ' + this.utilityService.formatChapterName(this.libraryType).toLowerCase() + ' loaded', '', {timeOut: 3000});
|
||||
const msg = translate(direction === 'Next' ? 'toasts.load-next-chapter' : 'toasts.load-prev-chapter', {entity: this.utilityService.formatChapterName(this.libraryType).toLowerCase()});
|
||||
this.toastr.info(msg, '', {timeOut: 3000});
|
||||
this.cdRef.markForCheck();
|
||||
this.init();
|
||||
} else {
|
||||
// This will only happen if no actual chapter can be found
|
||||
this.toastr.warning('Could not find ' + direction.toLowerCase() + ' ' + this.utilityService.formatChapterName(this.libraryType).toLowerCase());
|
||||
const msg = translate(direction === 'Next' ? 'toasts.no-next-chapter' : 'toasts.no-prev-chapter', {entity: this.utilityService.formatChapterName(this.libraryType).toLowerCase()});
|
||||
this.toastr.warning(msg);
|
||||
this.isLoading = false;
|
||||
if (direction === 'Prev') {
|
||||
this.prevPageDisabled = true;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,24 @@
|
|||
<div class="table-of-contents">
|
||||
<div *ngIf="Pages.length === 0">
|
||||
<em>Nothing Bookmarked yet</em>
|
||||
<ng-container *transloco="let t; read: 'personal-table-of-contents'">
|
||||
<div class="table-of-contents">
|
||||
<div *ngIf="Pages.length === 0">
|
||||
<em>{{t('no-data')}}}</em>
|
||||
</div>
|
||||
<ul>
|
||||
<li *ngFor="let page of Pages">
|
||||
<span (click)="loadChapterPage(page, '')">{{t('page', {value: page})}}</span>
|
||||
<ul class="chapter-title">
|
||||
<li class="ellipsis"
|
||||
[ngbTooltip]="bookmark.title"
|
||||
placement="right"
|
||||
*ngFor="let bookmark of bookmarks[page]" (click)="loadChapterPage(bookmark.pageNumber, bookmark.bookScrollId); $event.stopPropagation();">
|
||||
{{bookmark.title}}
|
||||
<button class="btn btn-icon ms-1" (click)="removeBookmark(bookmark); $event.stopPropagation();">
|
||||
<i class="fa-solid fa-trash" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">{{t('delete', {bookmarkName: bookmark.title})}}</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul>
|
||||
<li *ngFor="let page of Pages">
|
||||
<span (click)="loadChapterPage(page, '')">Page {{page}}</span>
|
||||
<ul class="chapter-title">
|
||||
<li class="ellipsis"
|
||||
[ngbTooltip]="bookmark.title"
|
||||
placement="right"
|
||||
*ngFor="let bookmark of bookmarks[page]" (click)="loadChapterPage(bookmark.pageNumber, bookmark.bookScrollId); $event.stopPropagation();">
|
||||
{{bookmark.title}}
|
||||
<button class="btn btn-icon ms-1" (click)="removeBookmark(bookmark); $event.stopPropagation();">
|
||||
<i class="fa-solid fa-trash" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">Delete {{bookmark.title}}</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {ReaderService} from "../../../_services/reader.service";
|
|||
import {PersonalToC} from "../../../_models/readers/personal-toc";
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {TranslocoModule} from "@ngneat/transloco";
|
||||
|
||||
export interface PersonalToCEvent {
|
||||
pageNum: number;
|
||||
|
|
@ -22,7 +23,7 @@ export interface PersonalToCEvent {
|
|||
@Component({
|
||||
selector: 'app-personal-table-of-contents',
|
||||
standalone: true,
|
||||
imports: [CommonModule, NgbTooltip],
|
||||
imports: [CommonModule, NgbTooltip, TranslocoModule],
|
||||
templateUrl: './personal-table-of-contents.component.html',
|
||||
styleUrls: ['./personal-table-of-contents.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
|
|
|||
|
|
@ -1,169 +1,172 @@
|
|||
<!-- IDEA: Move the whole reader drawer into this component and have it self contained -->
|
||||
<form [formGroup]="settingsForm">
|
||||
<ng-container *transloco="let t; read: 'reader-settings'">
|
||||
<!-- IDEA: Move the whole reader drawer into this component and have it self contained -->
|
||||
<form [formGroup]="settingsForm">
|
||||
<div ngbAccordion [closeOthers]="false" #acc="ngbAccordion">
|
||||
<div ngbAccordionItem id="general-panel" title="General Settings" [collapsed]="false">
|
||||
<h2 class="accordion-header" ngbAccordionHeader>
|
||||
<button ngbAccordionButton class="accordion-button" type="button" [attr.aria-expanded]="acc.isExpanded('general-panel')" aria-controls="collapseOne">
|
||||
General Settings
|
||||
</button>
|
||||
</h2>
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody>
|
||||
<ng-template>
|
||||
<div class="control-container" >
|
||||
<div class="controls">
|
||||
<div class="mb-3">
|
||||
<label for="library-type" class="form-label">Font Family</label>
|
||||
<select class="form-select" id="library-type" formControlName="bookReaderFontFamily">
|
||||
<option [value]="opt" *ngFor="let opt of fontOptions; let i = index">{{opt | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-0 controls">
|
||||
<label for="fontsize" class="form-label col-6">Font Size</label>
|
||||
<span class="col-6 float-end" style="display: inline-flex;">
|
||||
<div ngbAccordionItem id="general-panel" title="General Settings" [collapsed]="false">
|
||||
<h2 class="accordion-header" ngbAccordionHeader>
|
||||
<button ngbAccordionButton class="accordion-button" type="button" [attr.aria-expanded]="acc.isExpanded('general-panel')" aria-controls="collapseOne">
|
||||
{{t('general-settings-title')}}
|
||||
</button>
|
||||
</h2>
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody>
|
||||
<ng-template>
|
||||
<div class="control-container" >
|
||||
<div class="controls">
|
||||
<div class="mb-3">
|
||||
<label for="library-type" class="form-label">{{t('font-family-label')}}</label>
|
||||
<select class="form-select" id="library-type" formControlName="bookReaderFontFamily">
|
||||
<option [value]="opt" *ngFor="let opt of fontOptions; let i = index">{{opt | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-0 controls">
|
||||
<label for="fontsize" class="form-label col-6">{{t('font-size-label')}}</label>
|
||||
<span class="col-6 float-end" style="display: inline-flex;">
|
||||
<i class="fa-solid fa-font" style="font-size: 12px;"></i>
|
||||
<input type="range" class="form-range ms-2 me-2" id="fontsize" min="50" max="300" step="10" formControlName="bookReaderFontSize" [ngbTooltip]="settingsForm.get('bookReaderFontSize')?.value + '%'">
|
||||
<i class="fa-solid fa-font" style="font-size: 24px;"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 controls">
|
||||
<label for="linespacing" class="form-label col-6">Line Spacing</label>
|
||||
<span class="col-6 float-end" style="display: inline-flex;">
|
||||
<div class="row g-0 controls">
|
||||
<label for="linespacing" class="form-label col-6">{{t('line-spacing-label')}}</label>
|
||||
<span class="col-6 float-end" style="display: inline-flex;">
|
||||
1x
|
||||
<input type="range" class="form-range ms-2 me-2" id="linespacing" min="100" max="200" step="10" formControlName="bookReaderLineSpacing" [ngbTooltip]="settingsForm.get('bookReaderLineSpacing')?.value + '%'">
|
||||
2.5x
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 controls">
|
||||
<label for="margin" class="form-label col-6">Margin</label>
|
||||
<span class="col-6 float-end" style="display: inline-flex;">
|
||||
<div class="row g-0 controls">
|
||||
<label for="margin" class="form-label col-6">{{t('margin-label')}}</label>
|
||||
<span class="col-6 float-end" style="display: inline-flex;">
|
||||
<i class="fa-solid fa-outdent"></i>
|
||||
<input type="range" class="form-range ms-2 me-2" id="margin" min="0" max="30" step="5" formControlName="bookReaderMargin" [ngbTooltip]="settingsForm.get('bookReaderMargin')?.value + '%'">
|
||||
<i class="fa-solid fa-indent"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 justify-content-between mt-2">
|
||||
<button (click)="resetSettings()" class="btn btn-primary col">Reset to Defaults</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="row g-0 justify-content-between mt-2">
|
||||
<button (click)="resetSettings()" class="btn btn-primary col">{{t('reset-to-defaults')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div ngbAccordionItem id="reader-panel" title="Reader Settings" [collapsed]="false">
|
||||
<h2 class="accordion-header" ngbAccordionHeader>
|
||||
<button class="accordion-button" ngbAccordionButton type="button" [attr.aria-expanded]="acc.isExpanded('reader-panel')" aria-controls="collapseOne">
|
||||
Reader Settings
|
||||
</button>
|
||||
</h2>
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody>
|
||||
<ng-template>
|
||||
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<label id="readingdirection" class="form-label">Reading Direction</label>
|
||||
<button (click)="toggleReadingDirection()" class="btn btn-icon" aria-labelledby="readingdirection" title="{{readingDirectionModel === ReadingDirection.LeftToRight ? 'Left to Right' : 'Right to Left'}}">
|
||||
<i class="fa {{readingDirectionModel === ReadingDirection.LeftToRight ? 'fa-arrow-right' : 'fa-arrow-left'}} " aria-hidden="true"></i>
|
||||
<span class="phone-hidden"> {{readingDirectionModel === ReadingDirection.LeftToRight ? 'Left to Right' : 'Right to Left'}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="controls" style="display: flex; justify-content: space-between; align-items: center; ">
|
||||
<label for="writing-style" class="form-label">Writing Style <i class="fa fa-info-circle" aria-hidden="true" placement="top" [ngbTooltip]="writingStyleTooltip" role="button" tabindex="0" aria-describedby="writingStyle-help"></i></label>
|
||||
<ng-template #writingStyleTooltip>Changes the direction of the text. Horizontal is left to right, vertical is top to bottom.</ng-template>
|
||||
<span class="visually-hidden" id="writingStyle-help"><ng-container [ngTemplateOutlet]="writingStyleTooltip"></ng-container></span>
|
||||
<button (click)="toggleWritingStyle()" id="writing-style" class="btn btn-icon" aria-labelledby="writingStyle-help" title="{{writingStyleModel === WritingStyle.Horizontal ? 'Horizontal' : 'Vertical'}}">
|
||||
<i class="fa {{writingStyleModel === WritingStyle.Horizontal ? 'fa-arrows-left-right' : 'fa-arrows-up-down' }}" aria-hidden="true"></i>
|
||||
<span class="phone-hidden"> {{writingStyleModel === WritingStyle.Horizontal ? 'Horizontal' : 'Vertical' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div ngbAccordionItem id="reader-panel" title="Reader Settings" [collapsed]="false">
|
||||
<h2 class="accordion-header" ngbAccordionHeader>
|
||||
<button class="accordion-button" ngbAccordionButton type="button" [attr.aria-expanded]="acc.isExpanded('reader-panel')" aria-controls="collapseOne">
|
||||
{{t('reader-settings-title')}}
|
||||
</button>
|
||||
</h2>
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody>
|
||||
<ng-template>
|
||||
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<label id="readingdirection" class="form-label">{{t('reading-direction-label')}}</label>
|
||||
<button (click)="toggleReadingDirection()" class="btn btn-icon" aria-labelledby="readingdirection" title="{{readingDirectionModel === ReadingDirection.LeftToRight ? t('left-to-right') : t('right-to-left')}}">
|
||||
<i class="fa {{readingDirectionModel === ReadingDirection.LeftToRight ? 'fa-arrow-right' : 'fa-arrow-left'}} " aria-hidden="true"></i>
|
||||
<span class="phone-hidden"> {{readingDirectionModel === ReadingDirection.LeftToRight ? t('left-to-right') : t('right-to-left')}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="controls" style="display: flex; justify-content: space-between; align-items: center; ">
|
||||
<label for="writing-style" class="form-label">{{t('writing-style-label')}}<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top" [ngbTooltip]="writingStyleTooltip" role="button" tabindex="0" aria-describedby="writingStyle-help"></i></label>
|
||||
<ng-template #writingStyleTooltip>{{t('writing-style-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="writingStyle-help"><ng-container [ngTemplateOutlet]="writingStyleTooltip"></ng-container></span>
|
||||
<button (click)="toggleWritingStyle()" id="writing-style" class="btn btn-icon" aria-labelledby="writingStyle-help" title="{{writingStyleModel === WritingStyle.Horizontal ? t('horizontal') : t('vertical')}}">
|
||||
<i class="fa {{writingStyleModel === WritingStyle.Horizontal ? 'fa-arrows-left-right' : 'fa-arrows-up-down' }}" aria-hidden="true"></i>
|
||||
<span class="phone-hidden"> {{writingStyleModel === WritingStyle.Horizontal ? t('horizontal') : t('vertical') }}</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<label for="tap-pagination" class="form-label">{{t('tap-to-paginate-label')}}<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top" [ngbTooltip]="tapPaginationTooltip" role="button" tabindex="0" aria-describedby="tapPagination-help"></i></label>
|
||||
<ng-template #tapPaginationTooltip>{{t('tap-to-paginate-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="tapPagination-help">
|
||||
<ng-container [ngTemplateOutlet]="tapPaginationTooltip"></ng-container>
|
||||
</span>
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="tap-pagination" formControlName="bookReaderTapToPaginate" class="form-check-input" aria-labelledby="tapPagination-help">
|
||||
<label>{{settingsForm.get('bookReaderTapToPaginate')?.value ? t('on') : t('off')}} </label>
|
||||
</div>
|
||||
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<label for="tap-pagination" class="form-label">Tap Pagination <i class="fa fa-info-circle" aria-hidden="true" placement="top" [ngbTooltip]="tapPaginationTooltip" role="button" tabindex="0" aria-describedby="tapPagination-help"></i></label>
|
||||
<ng-template #tapPaginationTooltip>Click the edges of the screen to paginate</ng-template>
|
||||
<span class="visually-hidden" id="tapPagination-help">
|
||||
<ng-container [ngTemplateOutlet]="tapPaginationTooltip"></ng-container>
|
||||
</span>
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="tap-pagination" formControlName="bookReaderTapToPaginate" class="form-check-input" aria-labelledby="tapPagination-help">
|
||||
<label>{{settingsForm.get('bookReaderTapToPaginate')?.value ? 'On' : 'Off'}} </label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<label for="immersive-mode" class="form-label">Immersive Mode <i class="fa fa-info-circle" aria-hidden="true" placement="top" [ngbTooltip]="immersiveModeTooltip" role="button" tabindex="0" aria-describedby="immersiveMode-help"></i></label>
|
||||
<ng-template #immersiveModeTooltip>This will hide the menu behind a click on the reader document and turn tap to paginate on</ng-template>
|
||||
<span class="visually-hidden" id="immersiveMode-help">
|
||||
</div>
|
||||
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<label for="immersive-mode" class="form-label">{{t('immersive-mode-label')}}<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top" [ngbTooltip]="immersiveModeTooltip" role="button" tabindex="0" aria-describedby="immersiveMode-help"></i></label>
|
||||
<ng-template #immersiveModeTooltip>{{t('immersive-mode-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="immersiveMode-help">
|
||||
<ng-container [ngTemplateOutlet]="immersiveModeTooltip"></ng-container>
|
||||
</span>
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="immersive-mode" formControlName="bookReaderImmersiveMode" class="form-check-input" aria-labelledby="immersiveMode-help">
|
||||
<label>{{settingsForm.get('bookReaderImmersiveMode')?.value ? 'On' : 'Off'}} </label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="immersive-mode" formControlName="bookReaderImmersiveMode" class="form-check-input" aria-labelledby="immersiveMode-help">
|
||||
<label>{{settingsForm.get('bookReaderImmersiveMode')?.value ? t('on') : t('off')}} </label>
|
||||
</div>
|
||||
<!-- TODO: move this inline style into a class -->
|
||||
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<label id="fullscreen" class="form-label">Fullscreen <i class="fa fa-info-circle" aria-hidden="true" placement="top"
|
||||
[ngbTooltip]="fullscreenTooltip" role="button" tabindex="1" aria-describedby="fullscreen-help"></i></label>
|
||||
<ng-template #fullscreenTooltip>Put reader in fullscreen mode</ng-template>
|
||||
<span class="visually-hidden" id="fullscreen-help">
|
||||
<ng-container [ngTemplateOutlet]="fullscreenTooltip"></ng-container>
|
||||
</span>
|
||||
<button (click)="toggleFullscreen()" class="btn btn-icon" aria-labelledby="fullscreen">
|
||||
<i class="fa {{this.isFullscreen ? 'fa-compress-alt' : 'fa-expand-alt'}} {{isFullscreen ? 'icon-primary-color' : ''}}" aria-hidden="true"></i>
|
||||
<span *ngIf="activeTheme?.isDarkTheme"> {{isFullscreen ? 'Exit' : 'Enter'}}</span>
|
||||
</div>
|
||||
|
||||
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<label id="fullscreen" class="form-label">{{t('fullscreen-label')}}<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top"
|
||||
[ngbTooltip]="fullscreenTooltip" role="button" tabindex="1" aria-describedby="fullscreen-help"></i></label>
|
||||
<ng-template #fullscreenTooltip>{{t('fullscreen-tooltip')}}</ng-template>
|
||||
<span class="visually-hidden" id="fullscreen-help">
|
||||
<ng-container [ngTemplateOutlet]="fullscreenTooltip"></ng-container>
|
||||
</span>
|
||||
<button (click)="toggleFullscreen()" class="btn btn-icon" aria-labelledby="fullscreen">
|
||||
<i class="fa {{this.isFullscreen ? 'fa-compress-alt' : 'fa-expand-alt'}} {{isFullscreen ? 'icon-primary-color' : ''}}" aria-hidden="true"></i>
|
||||
<span *ngIf="activeTheme?.isDarkTheme"> {{isFullscreen ? t('exit') : t('enter')}}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<label id="layout-mode" class="form-label" style="margin-bottom:0.5rem">{{t('layout-mode-label')}}<i class="fa fa-info-circle ms-1" aria-hidden="true" placement="top" [ngbTooltip]="layoutTooltip" role="button" tabindex="1" aria-describedby="layout-help"></i></label>
|
||||
<ng-template #layoutTooltip><span [innerHTML]="t('layout-mode-tooltip')"></span></ng-template>
|
||||
<span class="visually-hidden" id="layout-help">
|
||||
<ng-container [ngTemplateOutlet]="layoutTooltip"></ng-container>
|
||||
</span>
|
||||
<br>
|
||||
<div class="btn-group d-flex justify-content-center" role="group" [attr.aria-label]="t('layout-mode-label')">
|
||||
<input type="radio" formControlName="layoutMode" [value]="BookPageLayoutMode.Default" class="btn-check" id="layout-mode-default" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="layout-mode-default">{{t('layout-mode-option-scroll')}}</label>
|
||||
|
||||
<input type="radio" formControlName="layoutMode" [value]="BookPageLayoutMode.Column1" class="btn-check" id="layout-mode-col1" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="layout-mode-col1">{{t('layout-mode-option-1col')}}</label>
|
||||
|
||||
<input type="radio" formControlName="layoutMode" [value]="BookPageLayoutMode.Column2" class="btn-check" id="layout-mode-col2" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="layout-mode-col2">{{t('layout-mode-option-2col')}}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ngbAccordionItem id="color-panel" [title]="t('color-theme-title')" [collapsed]="false">
|
||||
<h2 class="accordion-header" ngbAccordionHeader>
|
||||
<button class="accordion-button" ngbAccordionButton type="button" [attr.aria-expanded]="acc.isExpanded('color-panel')" aria-controls="collapseOne">
|
||||
{{t('color-theme-title')}}
|
||||
</button>
|
||||
</h2>
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody>
|
||||
<ng-template>
|
||||
<div class="controls">
|
||||
<ng-container *ngFor="let theme of themes">
|
||||
<button class="btn btn-icon color" (click)="setTheme(theme.name)" [ngClass]="{'active': activeTheme?.name === theme.name}">
|
||||
<div class="dot" [ngStyle]="{'background-color': theme.colorHash}"></div>
|
||||
{{t('theme.translationKey')}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<label id="layout-mode" class="form-label" style="margin-bottom:0.5rem">Layout Mode <i class="fa fa-info-circle" aria-hidden="true" placement="top" [ngbTooltip]="layoutTooltip" role="button" tabindex="1" aria-describedby="layout-help"></i></label>
|
||||
<ng-template #layoutTooltip>Scroll: Mirrors epub file (usually one long scrolling page per chapter).<br/>1 Column: Creates a single virtual page at a time.<br/>2 Column: Creates two virtual pages at a time laid out side-by-side.</ng-template>
|
||||
<span class="visually-hidden" id="layout-help">
|
||||
<ng-container [ngTemplateOutlet]="layoutTooltip"></ng-container>
|
||||
</span>
|
||||
<br>
|
||||
<div class="btn-group d-flex justify-content-center" role="group" aria-label="Layout Mode">
|
||||
<input type="radio" formControlName="layoutMode" [value]="BookPageLayoutMode.Default" class="btn-check" id="layout-mode-default" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="layout-mode-default">Scroll</label>
|
||||
|
||||
<input type="radio" formControlName="layoutMode" [value]="BookPageLayoutMode.Column1" class="btn-check" id="layout-mode-col1" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="layout-mode-col1">1 Column</label>
|
||||
|
||||
<input type="radio" formControlName="layoutMode" [value]="BookPageLayoutMode.Column2" class="btn-check" id="layout-mode-col2" autocomplete="off">
|
||||
<label class="btn btn-outline-primary" for="layout-mode-col2">2 Column</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ngbAccordionItem id="color-panel" title="Color Theme" [collapsed]="false">
|
||||
<h2 class="accordion-header" ngbAccordionHeader>
|
||||
<button class="accordion-button" ngbAccordionButton type="button" [attr.aria-expanded]="acc.isExpanded('color-panel')" aria-controls="collapseOne">
|
||||
Color Theme
|
||||
</button>
|
||||
</h2>
|
||||
<div ngbAccordionCollapse>
|
||||
<div ngbAccordionBody>
|
||||
<ng-template>
|
||||
<div class="controls">
|
||||
<ng-container *ngFor="let theme of themes">
|
||||
<button class="btn btn-icon color" (click)="setTheme(theme.name)" [ngClass]="{'active': activeTheme?.name === theme.name}">
|
||||
<div class="dot" [ngStyle]="{'background-color': theme.colorHash}"></div>
|
||||
{{theme.name}}
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import { BookWhiteTheme } from '../../_models/book-white-theme';
|
|||
import { BookPaperTheme } from '../../_models/book-paper-theme';
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import { NgbAccordionDirective, NgbAccordionItem, NgbAccordionHeader, NgbAccordionToggle, NgbAccordionButton, NgbCollapse, NgbAccordionCollapse, NgbAccordionBody, NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
|
||||
import {TranslocoModule} from "@ngneat/transloco";
|
||||
|
||||
/**
|
||||
* Used for book reader. Do not use for other components
|
||||
|
|
@ -46,7 +47,8 @@ export const bookColorThemes = [
|
|||
isDefault: true,
|
||||
provider: ThemeProvider.System,
|
||||
selector: 'brtheme-dark',
|
||||
content: BookDarkTheme
|
||||
content: BookDarkTheme,
|
||||
translationKey: 'theme-dark'
|
||||
},
|
||||
{
|
||||
name: 'Black',
|
||||
|
|
@ -55,7 +57,8 @@ export const bookColorThemes = [
|
|||
isDefault: false,
|
||||
provider: ThemeProvider.System,
|
||||
selector: 'brtheme-black',
|
||||
content: BookBlackTheme
|
||||
content: BookBlackTheme,
|
||||
translationKey: 'theme-black'
|
||||
},
|
||||
{
|
||||
name: 'White',
|
||||
|
|
@ -64,7 +67,8 @@ export const bookColorThemes = [
|
|||
isDefault: false,
|
||||
provider: ThemeProvider.System,
|
||||
selector: 'brtheme-white',
|
||||
content: BookWhiteTheme
|
||||
content: BookWhiteTheme,
|
||||
translationKey: 'theme-white'
|
||||
},
|
||||
{
|
||||
name: 'Paper',
|
||||
|
|
@ -73,7 +77,8 @@ export const bookColorThemes = [
|
|||
isDefault: false,
|
||||
provider: ThemeProvider.System,
|
||||
selector: 'brtheme-paper',
|
||||
content: BookPaperTheme
|
||||
content: BookPaperTheme,
|
||||
translationKey: 'theme-paper'
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -85,7 +90,7 @@ const mobileBreakpointMarginOverride = 700;
|
|||
styleUrls: ['./reader-settings.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [ReactiveFormsModule, NgbAccordionDirective, NgbAccordionItem, NgbAccordionHeader, NgbAccordionToggle, NgbAccordionButton, NgbCollapse, NgbAccordionCollapse, NgbAccordionBody, NgFor, NgbTooltip, NgTemplateOutlet, NgIf, NgClass, NgStyle, TitleCasePipe]
|
||||
imports: [ReactiveFormsModule, NgbAccordionDirective, NgbAccordionItem, NgbAccordionHeader, NgbAccordionToggle, NgbAccordionButton, NgbCollapse, NgbAccordionCollapse, NgbAccordionBody, NgFor, NgbTooltip, NgTemplateOutlet, NgIf, NgClass, NgStyle, TitleCasePipe, TranslocoModule]
|
||||
})
|
||||
export class ReaderSettingsComponent implements OnInit {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,24 +1,26 @@
|
|||
<div class="table-of-contents">
|
||||
<ng-container *transloco="let t; read: 'table-of-contents'">
|
||||
<div class="table-of-contents">
|
||||
<div *ngIf="chapters.length === 0">
|
||||
<em>This book does not have Table of Contents set in the metadata or a toc file</em>
|
||||
<em>{{t('no-data')}}}</em>
|
||||
</div>
|
||||
<div *ngIf="chapters.length === 1; else nestedChildren">
|
||||
<ul>
|
||||
<li *ngFor="let chapter of chapters[0].children">
|
||||
<a href="javascript:void(0);" (click)="loadChapterPage(chapter.page, chapter.part)">{{chapter.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li *ngFor="let chapter of chapters[0].children">
|
||||
<a href="javascript:void(0);" (click)="loadChapterPage(chapter.page, chapter.part)">{{chapter.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ng-template #nestedChildren>
|
||||
<ul *ngFor="let chapterGroup of chapters" class="chapter-title">
|
||||
<li class="{{chapterGroup.page === pageNum ? 'active': ''}}" (click)="loadChapterPage(chapterGroup.page, '')">
|
||||
{{chapterGroup.title}}
|
||||
</li>
|
||||
<ul *ngFor="let chapter of chapterGroup.children">
|
||||
<li class="{{cleanIdSelector(chapter.part) === currentPageAnchor ? 'active' : ''}}">
|
||||
<a href="javascript:void(0);" (click)="loadChapterPage(chapter.page, chapter.part)">{{chapter.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul *ngFor="let chapterGroup of chapters" class="chapter-title">
|
||||
<li class="{{chapterGroup.page === pageNum ? 'active': ''}}" (click)="loadChapterPage(chapterGroup.page, '')">
|
||||
{{chapterGroup.title}}
|
||||
</li>
|
||||
<ul *ngFor="let chapter of chapterGroup.children">
|
||||
<li class="{{cleanIdSelector(chapter.part) === currentPageAnchor ? 'active' : ''}}">
|
||||
<a href="javascript:void(0);" (click)="loadChapterPage(chapter.page, chapter.part)">{{chapter.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { BookChapterItem } from '../../_models/book-chapter-item';
|
||||
import { NgIf, NgFor } from '@angular/common';
|
||||
import {TranslocoModule} from "@ngneat/transloco";
|
||||
|
||||
@Component({
|
||||
selector: 'app-table-of-contents',
|
||||
|
|
@ -8,7 +9,7 @@ import { NgIf, NgFor } from '@angular/common';
|
|||
styleUrls: ['./table-of-contents.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.Default,
|
||||
standalone: true,
|
||||
imports: [NgIf, NgFor]
|
||||
imports: [NgIf, NgFor, TranslocoModule]
|
||||
})
|
||||
export class TableOfContentsComponent {
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue