mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Release Shakeout (#1266)
* Fixed an issue with fit to screen where spread images would fail to generate a paging area long enough. * Fixed pagination placement on original scaling * Fixed an issue with webtoon reader not reporting scroll events due to a fix from manga reader. * Fixing select on black book-reader theme * Fixing canvas split centering * Fixed a bug with white mode in book reader not rendering correctly. When bookmarking new pages after previously have viewing bookmarks for a series, ensure we clear out the temp cache else your new files wont be visible till next day. * Use grid on related tab * Clear bookmarks was not hooked up. Bulk add to collection didn't have label hidden * Fixed bug where filter might stay open between pages * Fixed typo on relationship for Adaptation * Contains was missing from series relation modal * Tweaked some methods and wording on reading list page * Cleaned up the phrasing when we abort a scan. * Fixed issue where typeahead wasn't reopening and it wasn't filtering selected options * Fixed some typeahead bugs and decreased interval for docker health check * Cleaned up and fixed some logic with receiving cover image update events Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
This commit is contained in:
parent
49d8a7c6ca
commit
a062341564
@ -1,25 +0,0 @@
|
|||||||
**/.dockerignore
|
|
||||||
**/.env
|
|
||||||
**/.git
|
|
||||||
**/.gitignore
|
|
||||||
**/.project
|
|
||||||
**/.settings
|
|
||||||
**/.toolstarget
|
|
||||||
**/.vs
|
|
||||||
**/.vscode
|
|
||||||
**/.idea
|
|
||||||
**/*.*proj.user
|
|
||||||
**/*.dbmdl
|
|
||||||
**/*.jfm
|
|
||||||
**/azds.yaml
|
|
||||||
**/bin
|
|
||||||
**/charts
|
|
||||||
**/docker-compose*
|
|
||||||
**/Dockerfile*
|
|
||||||
**/node_modules
|
|
||||||
**/npm-debug.log
|
|
||||||
**/obj
|
|
||||||
**/secrets.dev.yaml
|
|
||||||
**/values.dev.yaml
|
|
||||||
LICENSE
|
|
||||||
README.md
|
|
@ -150,8 +150,7 @@ namespace API.Controllers
|
|||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||||
var totalPages = await _cacheService.CacheBookmarkForSeries(user.Id, seriesId);
|
var totalPages = await _cacheService.CacheBookmarkForSeries(user.Id, seriesId);
|
||||||
// TODO: Change Includes to None from LinkedSeries branch
|
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId, SeriesIncludes.None);
|
||||||
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
|
|
||||||
|
|
||||||
return Ok(new BookmarkInfoDto()
|
return Ok(new BookmarkInfoDto()
|
||||||
{
|
{
|
||||||
@ -172,11 +171,6 @@ namespace API.Controllers
|
|||||||
|
|
||||||
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was an issue saving progress");
|
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was an issue saving progress");
|
||||||
|
|
||||||
// var series = new List<SeriesDto>()
|
|
||||||
// {await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(markReadDto.SeriesId, user.Id)};
|
|
||||||
// await _unitOfWork.SeriesRepository.AddSeriesModifiers(user.Id, series);
|
|
||||||
// await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate,
|
|
||||||
// MessageFactory.UserProgressUpdateEvent(user.Id, user.UserName, markReadDto.SeriesId, series[0], series[0].Pages));
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,16 +188,6 @@ namespace API.Controllers
|
|||||||
|
|
||||||
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was an issue saving progress");
|
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was an issue saving progress");
|
||||||
|
|
||||||
// Should I do this for every chapter? Maybe in a background task?
|
|
||||||
// foreach (var chapterId in await
|
|
||||||
// _unitOfWork.SeriesRepository.GetChapterIdsForSeriesAsync(new List<int>() {markReadDto.SeriesId}))
|
|
||||||
// {
|
|
||||||
// await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate,
|
|
||||||
// MessageFactory.UserProgressUpdateEvent(user.Id, user.UserName, chapterId, MessageFactoryEntityTypes.Chapter, 0));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate,
|
|
||||||
// MessageFactory.UserProgressUpdateEvent(user.Id, user.UserName, markReadDto.SeriesId, MessageFactoryEntityTypes.Series, 0));
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -580,6 +564,7 @@ namespace API.Controllers
|
|||||||
|
|
||||||
if (await _bookmarkService.BookmarkPage(user, bookmarkDto, path))
|
if (await _bookmarkService.BookmarkPage(user, bookmarkDto, path))
|
||||||
{
|
{
|
||||||
|
BackgroundJob.Enqueue(() => _cacheService.CleanupBookmarkCache(bookmarkDto.SeriesId));
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,6 +584,7 @@ namespace API.Controllers
|
|||||||
|
|
||||||
if (await _bookmarkService.RemoveBookmarkPage(user, bookmarkDto))
|
if (await _bookmarkService.RemoveBookmarkPage(user, bookmarkDto))
|
||||||
{
|
{
|
||||||
|
BackgroundJob.Enqueue(() => _cacheService.CleanupBookmarkCache(bookmarkDto.SeriesId));
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +102,8 @@ namespace API.Controllers
|
|||||||
|
|
||||||
if (_unitOfWork.HasChanges())
|
if (_unitOfWork.HasChanges())
|
||||||
{
|
{
|
||||||
|
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate,
|
||||||
|
MessageFactory.CoverUpdateEvent(series.Id, MessageFactoryEntityTypes.Series), false);
|
||||||
await _unitOfWork.CommitAsync();
|
await _unitOfWork.CommitAsync();
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
@ -245,6 +247,10 @@ namespace API.Controllers
|
|||||||
if (_unitOfWork.HasChanges())
|
if (_unitOfWork.HasChanges())
|
||||||
{
|
{
|
||||||
await _unitOfWork.CommitAsync();
|
await _unitOfWork.CommitAsync();
|
||||||
|
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate,
|
||||||
|
MessageFactory.CoverUpdateEvent(chapter.VolumeId, MessageFactoryEntityTypes.Volume), false);
|
||||||
|
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate,
|
||||||
|
MessageFactory.CoverUpdateEvent(chapter.Id, MessageFactoryEntityTypes.Chapter), false);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
#This Dockerfile pulls the latest git commit and builds Kavita from source
|
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:6.0-focal AS builder
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
ARG TARGETPLATFORM
|
|
||||||
|
|
||||||
#Installs nodejs and npm
|
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \
|
|
||||||
&& apt-get install -y nodejs \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
#Builds app based on platform
|
|
||||||
COPY build_target.sh /build_target.sh
|
|
||||||
RUN /build_target.sh
|
|
||||||
|
|
||||||
#Production image
|
|
||||||
FROM ubuntu:focal
|
|
||||||
|
|
||||||
#Move the output files to where they need to be
|
|
||||||
COPY --from=builder /Projects/Kavita/_output/build/Kavita /kavita
|
|
||||||
|
|
||||||
#Installs program dependencies
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install -y libicu-dev libssl1.1 pwgen \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
#Creates the manga storage directory
|
|
||||||
RUN mkdir /manga /kavita/data
|
|
||||||
|
|
||||||
RUN cp /kavita/appsettings.Development.json /kavita/appsettings.json \
|
|
||||||
&& sed -i 's/Data source=kavita.db/Data source=data\/kavita.db/g' /kavita/appsettings.json
|
|
||||||
|
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
|
||||||
|
|
||||||
EXPOSE 5000
|
|
||||||
|
|
||||||
WORKDIR /kavita
|
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/bash"]
|
|
||||||
CMD ["/entrypoint.sh"]
|
|
@ -32,6 +32,7 @@ namespace API.Services
|
|||||||
string GetCachedEpubFile(int chapterId, Chapter chapter);
|
string GetCachedEpubFile(int chapterId, Chapter chapter);
|
||||||
public void ExtractChapterFiles(string extractPath, IReadOnlyList<MangaFile> files);
|
public void ExtractChapterFiles(string extractPath, IReadOnlyList<MangaFile> files);
|
||||||
Task<int> CacheBookmarkForSeries(int userId, int seriesId);
|
Task<int> CacheBookmarkForSeries(int userId, int seriesId);
|
||||||
|
void CleanupBookmarkCache(int bookmarkDtoSeriesId);
|
||||||
}
|
}
|
||||||
public class CacheService : ICacheService
|
public class CacheService : ICacheService
|
||||||
{
|
{
|
||||||
@ -240,5 +241,17 @@ namespace API.Services
|
|||||||
_directoryService.Flatten(destDirectory);
|
_directoryService.Flatten(destDirectory);
|
||||||
return files.Count;
|
return files.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears a cached bookmarks for a series id folder
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seriesId"></param>
|
||||||
|
public void CleanupBookmarkCache(int seriesId)
|
||||||
|
{
|
||||||
|
var destDirectory = _directoryService.FileSystem.Path.Join(_directoryService.CacheDirectory, seriesId + "_bookmarks");
|
||||||
|
if (!_directoryService.Exists(destDirectory)) return;
|
||||||
|
|
||||||
|
_directoryService.ClearAndDeleteDirectory(destDirectory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,7 +182,7 @@ namespace API.Services.Tasks
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Task CleanupBookmarks()
|
public Task CleanupBookmarks()
|
||||||
{
|
{
|
||||||
// This is disabled for now while we test and validate a new method of deleting bookmarks
|
// TODO: This is disabled for now while we test and validate a new method of deleting bookmarks
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
// Search all files in bookmarks/ except bookmark files and delete those
|
// Search all files in bookmarks/ except bookmark files and delete those
|
||||||
// var bookmarkDirectory =
|
// var bookmarkDirectory =
|
||||||
|
@ -215,12 +215,12 @@ public class ScannerService : IScannerService
|
|||||||
// That way logging and UI informing is all in one place with full context
|
// That way logging and UI informing is all in one place with full context
|
||||||
_logger.LogError("Some of the root folders for the library are empty. " +
|
_logger.LogError("Some of the root folders for the library are empty. " +
|
||||||
"Either your mount has been disconnected or you are trying to delete all series in the library. " +
|
"Either your mount has been disconnected or you are trying to delete all series in the library. " +
|
||||||
"Scan will be aborted. " +
|
"Scan has be aborted. " +
|
||||||
"Check that your mount is connected or change the library's root folder and rescan");
|
"Check that your mount is connected or change the library's root folder and rescan");
|
||||||
|
|
||||||
await _eventHub.SendMessageAsync(MessageFactory.Error, MessageFactory.ErrorEvent( $"Some of the root folders for the library, {libraryName}, are empty.",
|
await _eventHub.SendMessageAsync(MessageFactory.Error, MessageFactory.ErrorEvent( $"Some of the root folders for the library, {libraryName}, are empty.",
|
||||||
"Either your mount has been disconnected or you are trying to delete all series in the library. " +
|
"Either your mount has been disconnected or you are trying to delete all series in the library. " +
|
||||||
"Scan will be aborted. " +
|
"Scan has be aborted. " +
|
||||||
"Check that your mount is connected or change the library's root folder and rescan"));
|
"Check that your mount is connected or change the library's root folder and rescan"));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -29,7 +29,7 @@ EXPOSE 5000
|
|||||||
|
|
||||||
WORKDIR /kavita
|
WORKDIR /kavita
|
||||||
|
|
||||||
HEALTHCHECK --interval=300s --timeout=15s --start-period=30s --retries=3 CMD curl --fail http://localhost:5000 || exit 1
|
HEALTHCHECK --interval=30s --timeout=15s --start-period=30s --retries=3 CMD curl --fail http://localhost:5000 || exit 1
|
||||||
|
|
||||||
ENTRYPOINT [ "/bin/bash" ]
|
ENTRYPOINT [ "/bin/bash" ]
|
||||||
CMD ["/entrypoint.sh"]
|
CMD ["/entrypoint.sh"]
|
||||||
|
@ -2,6 +2,7 @@ import { HttpClient, HttpParams } from '@angular/common/http';
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { environment } from 'src/environments/environment';
|
import { environment } from 'src/environments/environment';
|
||||||
|
import { UtilityService } from '../shared/_services/utility.service';
|
||||||
import { PaginatedResult } from '../_models/pagination';
|
import { PaginatedResult } from '../_models/pagination';
|
||||||
import { ReadingList, ReadingListItem } from '../_models/reading-list';
|
import { ReadingList, ReadingListItem } from '../_models/reading-list';
|
||||||
import { ActionItem } from './action-factory.service';
|
import { ActionItem } from './action-factory.service';
|
||||||
@ -13,7 +14,7 @@ export class ReadingListService {
|
|||||||
|
|
||||||
baseUrl = environment.apiUrl;
|
baseUrl = environment.apiUrl;
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient) { }
|
constructor(private httpClient: HttpClient, private utilityService: UtilityService) { }
|
||||||
|
|
||||||
getReadingList(readingListId: number) {
|
getReadingList(readingListId: number) {
|
||||||
return this.httpClient.get<ReadingList>(this.baseUrl + 'readinglist?readingListId=' + readingListId);
|
return this.httpClient.get<ReadingList>(this.baseUrl + 'readinglist?readingListId=' + readingListId);
|
||||||
@ -21,11 +22,11 @@ export class ReadingListService {
|
|||||||
|
|
||||||
getReadingLists(includePromoted: boolean = true, pageNum?: number, itemsPerPage?: number) {
|
getReadingLists(includePromoted: boolean = true, pageNum?: number, itemsPerPage?: number) {
|
||||||
let params = new HttpParams();
|
let params = new HttpParams();
|
||||||
params = this._addPaginationIfExists(params, pageNum, itemsPerPage);
|
params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage);
|
||||||
|
|
||||||
return this.httpClient.post<PaginatedResult<ReadingList[]>>(this.baseUrl + 'readinglist/lists?includePromoted=' + includePromoted, {}, {observe: 'response', params}).pipe(
|
return this.httpClient.post<PaginatedResult<ReadingList[]>>(this.baseUrl + 'readinglist/lists?includePromoted=' + includePromoted, {}, {observe: 'response', params}).pipe(
|
||||||
map((response: any) => {
|
map((response: any) => {
|
||||||
return this._cachePaginatedResults(response, new PaginatedResult<ReadingList[]>());
|
return this.utilityService.createPaginatedResult(response, new PaginatedResult<ReadingList[]>());
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -86,29 +87,4 @@ export class ReadingListService {
|
|||||||
if (readingList?.promoted && !isAdmin) return false;
|
if (readingList?.promoted && !isAdmin) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_addPaginationIfExists(params: HttpParams, pageNum?: number, itemsPerPage?: number) {
|
|
||||||
// TODO: Move to utility service
|
|
||||||
if (pageNum !== null && pageNum !== undefined && itemsPerPage !== null && itemsPerPage !== undefined) {
|
|
||||||
params = params.append('pageNumber', pageNum + '');
|
|
||||||
params = params.append('pageSize', itemsPerPage + '');
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
_cachePaginatedResults(response: any, paginatedVariable: PaginatedResult<any[]>) {
|
|
||||||
// TODO: Move to utility service
|
|
||||||
if (response.body === null) {
|
|
||||||
paginatedVariable.result = [];
|
|
||||||
} else {
|
|
||||||
paginatedVariable.result = response.body;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageHeader = response.headers.get('Pagination');
|
|
||||||
if (pageHeader !== null) {
|
|
||||||
paginatedVariable.pagination = JSON.parse(pageHeader);
|
|
||||||
}
|
|
||||||
|
|
||||||
return paginatedVariable;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ export class ToggleService {
|
|||||||
.pipe(filter(event => event instanceof NavigationStart))
|
.pipe(filter(event => event instanceof NavigationStart))
|
||||||
.subscribe((event) => {
|
.subscribe((event) => {
|
||||||
this.toggleState = false;
|
this.toggleState = false;
|
||||||
|
this.toggleStateSource.next(this.toggleState);
|
||||||
});
|
});
|
||||||
this.toggleStateSource.next(false);
|
this.toggleStateSource.next(false);
|
||||||
}
|
}
|
||||||
@ -26,7 +27,6 @@ export class ToggleService {
|
|||||||
this.toggleState = !this.toggleState;
|
this.toggleState = !this.toggleState;
|
||||||
this.toggleStateSource.pipe(take(1)).subscribe(state => {
|
this.toggleStateSource.pipe(take(1)).subscribe(state => {
|
||||||
this.toggleState = !state;
|
this.toggleState = !state;
|
||||||
console.log('Toggle: ', this.toggleState)
|
|
||||||
this.toggleStateSource.next(this.toggleState);
|
this.toggleStateSource.next(this.toggleState);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -47,6 +47,15 @@ export const BookBlackTheme = `
|
|||||||
--btn-disabled-text-color: white;
|
--btn-disabled-text-color: white;
|
||||||
--btn-disabled-border-color: #6c757d;
|
--btn-disabled-border-color: #6c757d;
|
||||||
|
|
||||||
|
/* Inputs */
|
||||||
|
--input-bg-color: #343a40;
|
||||||
|
--input-bg-readonly-color: #434648;
|
||||||
|
--input-focused-border-color: #ccc;
|
||||||
|
--input-text-color: #fff;
|
||||||
|
--input-placeholder-color: #aeaeae;
|
||||||
|
--input-border-color: #ccc;
|
||||||
|
--input-focus-boxshadow-color: rgb(255 255 255 / 50%);
|
||||||
|
|
||||||
/* Nav (Tabs) */
|
/* Nav (Tabs) */
|
||||||
--nav-tab-border-color: rgba(44, 118, 88, 0.7);
|
--nav-tab-border-color: rgba(44, 118, 88, 0.7);
|
||||||
--nav-tab-text-color: var(--body-text-color);
|
--nav-tab-text-color: var(--body-text-color);
|
||||||
|
@ -94,7 +94,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- TODO: move this inline style into a class -->
|
<!-- TODO: move this inline style into a class -->
|
||||||
<div class="controls" style="display:flex; justify-content:space-between; align-items:center;">
|
<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>
|
<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>
|
<ng-template #fullscreenTooltip>Put reader in fullscreen mode</ng-template>
|
||||||
<span class="visually-hidden" id="fullscreen-help">
|
<span class="visually-hidden" id="fullscreen-help">
|
||||||
<ng-container [ngTemplateOutlet]="fullscreenTooltip"></ng-container>
|
<ng-container [ngTemplateOutlet]="fullscreenTooltip"></ng-container>
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
.controls {
|
.controls {
|
||||||
margin: 0.25rem 0 0.25rem;
|
margin: 0.25rem 0 0.25rem;
|
||||||
|
|
||||||
|
.form-select {
|
||||||
|
option{
|
||||||
|
background-color: var(--input-bg-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.form-label {
|
.form-label {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
@ -284,6 +284,7 @@ export class ReaderSettingsComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleFullscreen() {
|
toggleFullscreen() {
|
||||||
|
this.isFullscreen = !this.isFullscreen;
|
||||||
this.fullscreen.emit();
|
this.fullscreen.emit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,9 +64,7 @@ export class BookmarksComponent implements OnInit, OnDestroy {
|
|||||||
async handleAction(action: Action, series: Series) {
|
async handleAction(action: Action, series: Series) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case(Action.Delete):
|
case(Action.Delete):
|
||||||
if (!await this.confirmService.confirm('Are you sure you want to clear all bookmarks for ' + series.name + '? This cannot be undone.')) {
|
this.clearBookmarks(series);
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case(Action.DownloadBookmark):
|
case(Action.DownloadBookmark):
|
||||||
this.downloadBookmarks(series);
|
this.downloadBookmarks(series);
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
<div style="width: 100%;">
|
<div style="width: 100%;">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<div class="col-9 col-lg-10">
|
<div class="col-9 col-lg-10">
|
||||||
<label class="visually-hidden" class="form-label" for="add-rlist">Collection</label>
|
<label class="form-label visually-hidden" for="add-rlist">Collection</label>
|
||||||
<input width="100%" #title ngbAutofocus type="text" class="form-control mb-2" id="add-rlist" formControlName="title">
|
<input width="100%" #title ngbAutofocus type="text" class="form-control mb-2" id="add-rlist" formControlName="title">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
|
@ -11,7 +11,3 @@
|
|||||||
.highlight {
|
.highlight {
|
||||||
color: var(--bulk-selection-highlight-text-color) !important;
|
color: var(--bulk-selection-highlight-text-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep button i.fa {
|
|
||||||
color: var(--bulk-selection-text-color);
|
|
||||||
}
|
|
@ -55,6 +55,8 @@ export class EditSeriesRelationComponent implements OnInit, OnDestroy {
|
|||||||
this.setupRelationRows(relations.alternativeSettings, RelationKind.AlternativeSetting);
|
this.setupRelationRows(relations.alternativeSettings, RelationKind.AlternativeSetting);
|
||||||
this.setupRelationRows(relations.alternativeVersions, RelationKind.AlternativeVersion);
|
this.setupRelationRows(relations.alternativeVersions, RelationKind.AlternativeVersion);
|
||||||
this.setupRelationRows(relations.doujinshis, RelationKind.Doujinshi);
|
this.setupRelationRows(relations.doujinshis, RelationKind.Doujinshi);
|
||||||
|
this.setupRelationRows(relations.contains, RelationKind.Contains);
|
||||||
|
this.setupRelationRows(relations.parent, RelationKind.Parent);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.libraryService.getLibraryNames().subscribe(names => {
|
this.libraryService.getLibraryNames().subscribe(names => {
|
||||||
|
@ -31,9 +31,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<div (click)="toggleMenu()" class="reading-area" [ngStyle]="{'background-color': backgroundColor}" #readingArea>
|
<div (click)="toggleMenu()" class="reading-area" [ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : 'calc(var(--vh)*100)'}" #readingArea>
|
||||||
<ng-container *ngIf="readerMode !== ReaderMode.Webtoon; else webtoon">
|
<ng-container *ngIf="readerMode !== ReaderMode.Webtoon; else webtoon">
|
||||||
<div [ngClass]="{'d-none': !renderWithCanvas }">
|
<div class="image-container" [ngClass]="{'d-none': !renderWithCanvas }">
|
||||||
<canvas #content class="{{getFittingOptionClass()}}"
|
<canvas #content class="{{getFittingOptionClass()}}"
|
||||||
ondragstart="return false;" onselectstart="return false;">
|
ondragstart="return false;" onselectstart="return false;">
|
||||||
</canvas>
|
</canvas>
|
||||||
@ -47,7 +47,7 @@
|
|||||||
title="Previous Page" aria-hidden="true"></i>
|
title="Previous Page" aria-hidden="true"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="{{readerMode === ReaderMode.LeftRight ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, 'right')" [ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? ImageHeight: 25 + '%')}">
|
<div class="{{readerMode === ReaderMode.LeftRight ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, 'right')" [ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? ImageHeight: 25 + '%'), 'left': (readerMode === ReaderMode.LeftRight && (this.generalSettingsForm.get('fittingOption')?.value === FITTING_OPTION.ORIGINAL) ? ImageWidth: 'inherit')}">
|
||||||
<div *ngIf="showClickOverlay">
|
<div *ngIf="showClickOverlay">
|
||||||
<i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'right' : 'down'}}"
|
<i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'right' : 'down'}}"
|
||||||
title="Next Page" aria-hidden="true"></i>
|
title="Next Page" aria-hidden="true"></i>
|
||||||
|
@ -20,7 +20,7 @@ img {
|
|||||||
.reading-area {
|
.reading-area {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
height: calc(var(--vh)*100);
|
//height: calc(var(--vh)*100); // this needs to be applied on the DOM because it breaks infinite scroller
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-container {
|
.image-container {
|
||||||
@ -248,6 +248,9 @@ img {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//$pagination-bg: rgba(0, 0, 0, 0);
|
||||||
|
$pagination-bg: rgba(0, 0, 255, 0.4);
|
||||||
|
|
||||||
.pagination-area {
|
.pagination-area {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
@ -262,7 +265,7 @@ img {
|
|||||||
right: 0px;
|
right: 0px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
width: $side-width;
|
width: $side-width;
|
||||||
background: rgba(0, 0, 0, 0);
|
background: $pagination-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top {
|
.top {
|
||||||
@ -270,7 +273,7 @@ img {
|
|||||||
right: 0px;
|
right: 0px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: rgba(0, 0, 0, 0);
|
background: $pagination-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
@ -278,7 +281,7 @@ img {
|
|||||||
left: 0px;
|
left: 0px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
width: $side-width;
|
width: $side-width;
|
||||||
background: rgba(0, 0, 0, 0);
|
background: $pagination-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom {
|
.bottom {
|
||||||
@ -286,7 +289,7 @@ img {
|
|||||||
left: 0px;
|
left: 0px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: rgba(0, 0, 0, 0);
|
background: $pagination-bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,11 +283,23 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
return this.bookmarks.hasOwnProperty(this.pageNum);
|
return this.bookmarks.hasOwnProperty(this.pageNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get WindowWidth() {
|
||||||
|
return this.readingArea?.nativeElement.scrollWidth + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
get WindowHeight() {
|
get WindowHeight() {
|
||||||
return this.readingArea?.nativeElement.scrollHeight + 'px';
|
return this.readingArea?.nativeElement.scrollHeight + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get ImageWidth() {
|
||||||
|
return this.image?.nativeElement.width + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
get ImageHeight() {
|
get ImageHeight() {
|
||||||
|
// If we are a cover image and implied fit to screen, then we need to take screen height rather than image height
|
||||||
|
if (this.isCoverImage()) {
|
||||||
|
return this.WindowHeight;
|
||||||
|
}
|
||||||
return this.image?.nativeElement.height + 'px';
|
return this.image?.nativeElement.height + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1018,7 +1030,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
if (!this.ctx || !this.canvas) { return; }
|
if (!this.ctx || !this.canvas) { return; }
|
||||||
|
|
||||||
this.canvasImage.onload = null;
|
this.canvasImage.onload = null;
|
||||||
console.log('canvasImage: ', this.canvasImage?.height);
|
|
||||||
|
|
||||||
this.setCanvasSize();
|
this.setCanvasSize();
|
||||||
|
|
||||||
@ -1055,8 +1066,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
|| document.documentElement.clientHeight
|
|| document.documentElement.clientHeight
|
||||||
|| document.body.clientHeight;
|
|| document.body.clientHeight;
|
||||||
|
|
||||||
console.log(windowHeight);
|
|
||||||
|
|
||||||
const needsSplitting = this.isCoverImage();
|
const needsSplitting = this.isCoverImage();
|
||||||
let newScale = this.generalSettingsForm.get('fittingOption')?.value;
|
let newScale = this.generalSettingsForm.get('fittingOption')?.value;
|
||||||
const widthRatio = windowWidth / (this.canvasImage.width / (needsSplitting ? 2 : 1));
|
const widthRatio = windowWidth / (this.canvasImage.width / (needsSplitting ? 2 : 1));
|
||||||
|
@ -10,7 +10,7 @@ export class RelationshipPipe implements PipeTransform {
|
|||||||
if (relationship === undefined) return '';
|
if (relationship === undefined) return '';
|
||||||
switch (relationship) {
|
switch (relationship) {
|
||||||
case RelationKind.Adaptation:
|
case RelationKind.Adaptation:
|
||||||
return 'Adaptaion';
|
return 'Adaptation';
|
||||||
case RelationKind.AlternativeSetting:
|
case RelationKind.AlternativeSetting:
|
||||||
return 'Alternative Setting';
|
return 'Alternative Setting';
|
||||||
case RelationKind.AlternativeVersion:
|
case RelationKind.AlternativeVersion:
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
<button class="btn btn-primary" title="Read" (click)="read()">
|
<button class="btn btn-primary" title="Read" (click)="read()">
|
||||||
<span>
|
<span>
|
||||||
<i class="fa fa-book-open" aria-hidden="true"></i>
|
<i class="fa fa-book-open" aria-hidden="true"></i>
|
||||||
<span class="read-btn--text"> Read</span> <!-- IDEA: We can provide them the ability to read/continue like we do with a series -->
|
<span class="read-btn--text"> Read</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -47,7 +47,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<div *ngIf="items.length === 0">
|
<div *ngIf="items.length === 0">
|
||||||
No chapters added
|
Nothing added
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-dragable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" (itemRemove)="itemRemoved($event)" [accessibilityMode]="accessibilityMode">
|
<app-dragable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" (itemRemove)="itemRemoved($event)" [accessibilityMode]="accessibilityMode">
|
||||||
|
@ -132,9 +132,9 @@
|
|||||||
<li [ngbNavItem]="TabID.Related" *ngIf="hasRelations">
|
<li [ngbNavItem]="TabID.Related" *ngIf="hasRelations">
|
||||||
<a ngbNavLink>Related</a>
|
<a ngbNavLink>Related</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
<div class="row g-0">
|
<div class="card-container row g-0">
|
||||||
<ng-container *ngFor="let item of relations; let idx = index; trackBy: trackByRelatedSeriesIdentiy">
|
<ng-container *ngFor="let item of relations; let idx = index; trackBy: trackByRelatedSeriesIdentiy">
|
||||||
<app-series-card class="col-auto" [data]="item.series" [libraryId]="item.series.libraryId" [relation]="item.relation"></app-series-card>
|
<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>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -51,10 +51,13 @@ export class ImageComponent implements OnChanges, OnDestroy {
|
|||||||
if (this.imageUrl === undefined || this.imageUrl === null || this.imageUrl === '') return;
|
if (this.imageUrl === undefined || this.imageUrl === null || this.imageUrl === '') return;
|
||||||
const enityType = this.imageService.getEntityTypeFromUrl(this.imageUrl);
|
const enityType = this.imageService.getEntityTypeFromUrl(this.imageUrl);
|
||||||
if (enityType === updateEvent.entityType) {
|
if (enityType === updateEvent.entityType) {
|
||||||
const tokens = this.imageUrl.split('?')[1].split('&random=');
|
const tokens = this.imageUrl.split('?')[1].split('&');
|
||||||
|
|
||||||
//...seriesId=123&random=
|
//...seriesId=123&random=
|
||||||
const id = tokens[0].replace(enityType + 'Id=', '');
|
let id = tokens[0].replace(enityType + 'Id=', '');
|
||||||
|
if (id.includes('&')) {
|
||||||
|
id = id.split('&')[0];
|
||||||
|
}
|
||||||
if (id === (updateEvent.id + '')) {
|
if (id === (updateEvent.id + '')) {
|
||||||
this.imageUrl = this.imageService.randomize(this.imageUrl);
|
this.imageUrl = this.imageService.randomize(this.imageUrl);
|
||||||
}
|
}
|
||||||
|
@ -217,7 +217,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
}),
|
}),
|
||||||
map(val => val.trim()),
|
map(val => val.trim()),
|
||||||
auditTime(this.settings.debounce),
|
auditTime(this.settings.debounce),
|
||||||
distinctUntilChanged(), // ?!: BUG Doesn't trigger the search to run when filtered array changes
|
//distinctUntilChanged(), // ?!: BUG Doesn't trigger the search to run when filtered array changes
|
||||||
filter(val => {
|
filter(val => {
|
||||||
// If minimum filter characters not met, do not filter
|
// If minimum filter characters not met, do not filter
|
||||||
if (this.settings.minCharacters === 0) return true;
|
if (this.settings.minCharacters === 0) return true;
|
||||||
@ -405,6 +405,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
openDropdown() {
|
openDropdown() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.typeaheadControl.setValue(this.typeaheadControl.value);
|
this.typeaheadControl.setValue(this.typeaheadControl.value);
|
||||||
|
this.hasFocus = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -454,6 +455,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateShowAddItem(options: any[]) {
|
updateShowAddItem(options: any[]) {
|
||||||
|
// ?! BUG This will still technicially allow you to add the same thing as a previously added item. (Code will just toggle it though)
|
||||||
this.showAddItem = this.settings.addIfNonExisting && this.typeaheadControl.value.trim()
|
this.showAddItem = this.settings.addIfNonExisting && this.typeaheadControl.value.trim()
|
||||||
&& this.typeaheadControl.value.trim().length >= Math.max(this.settings.minCharacters, 1)
|
&& this.typeaheadControl.value.trim().length >= Math.max(this.settings.minCharacters, 1)
|
||||||
&& this.typeaheadControl.dirty
|
&& this.typeaheadControl.dirty
|
||||||
|
Loading…
x
Reference in New Issue
Block a user