Feature/enhancements and more (#1166)
* Moved libraryType into chapter info * Fixed a bug where you could not reset cover on a series * Patched in relevant changes from another polish branch * Refactored invite user setup to shift the checking for accessibility to the backend and always show the link. This will help with users who have some unique setups in docker. * Refactored invite user to always print the url to setup a new account. * Single page renderer uses canvasImage rather than re-requesting and relying on cache * Fixed a rendering issue where fit to split on single on a cover wouldn't force width scaling just for that image * Fixed a rendering bug with split image functionality * Added title to copy button * Fixed a bug in GetContinuePoint when a chapter is added to an already read volume and a new chapter is added loose leaf. The loose leaf would be prioritized over the volume chapter. Refactored 2 methods from controller into service and unit tested. * Fixed a bug on opening a volume in series detail that had a chapter added to it after the volume (0 chapter) was read would cause a loose leaf chapter to be opened. * Added mark as read/actionables on Files in volume detail modal. Fixed a bug where we were showing the wrong page count in a volume detail modal. * Removed OnDeck page and replaced it with a pre-filtered All-Series. Hooked up the ability to pass read state to the filter via query params. Fixed some spacing on filter post bootstrap update. * Fixed up some poor documentation on FilterDto. * Some string equals enhancements to reduce extra allocations * Fixed an issue when trying to download via a url, to remove query parameters to get the format * Made an optimization to Normalize method to reduce memory pressure by 100MB over the course of a scan (16k files) * Adjusted the styles on dashboard for first time setup and used a routerlink rather than href to avoid a fresh load. * Use framgment on router link * Hooked in the ability to search by release year (along with series optionally) and series will be returned back. * Fixed a bug in the filter format code where it was sending the wrong type * Only show clear all on typeahead when there are at least one selected item * Cleaned up the styles of the styles of the typeahead * Removed some dead code * Implemented the ability to filter against a series name. * Fixed filter top offset * Ensure that when we add or remove libraries, the side nav of users gets updated. * Tweaked the width on the mobile side nav * Close side nav on clicking overlay on mobile viewport * Don't show a pointer if the carousel section title is not actually selectable * Removed the User profile on the side nav so home is always first. Tweaked styles to match * Fixed up some poor documentation on FilterDto. * Fixed a bug where Latest read date wasn't being set due to an early short circuit. * When sending the chapter file, format the title of the FeedEntry more like Series Detail. * Removed dead code
This commit is contained in:
parent
67d8d3d808
commit
4a93b5c715
68 changed files with 663 additions and 451 deletions
|
|
@ -98,7 +98,7 @@ public class EmailService : IEmailService
|
|||
return await SendEmailWithPost(emailLink + "/api/email/email-password-reset", data);
|
||||
}
|
||||
|
||||
private static async Task<bool> SendEmailWithGet(string url)
|
||||
private static async Task<bool> SendEmailWithGet(string url, int timeoutSecs = 30)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -108,7 +108,7 @@ public class EmailService : IEmailService
|
|||
.WithHeader("x-api-key", "MsnvA2DfQqxSK5jh")
|
||||
.WithHeader("x-kavita-version", BuildInfo.Version)
|
||||
.WithHeader("Content-Type", "application/json")
|
||||
.WithTimeout(TimeSpan.FromSeconds(30))
|
||||
.WithTimeout(TimeSpan.FromSeconds(timeoutSecs))
|
||||
.GetStringAsync();
|
||||
|
||||
if (!string.IsNullOrEmpty(response) && bool.Parse(response))
|
||||
|
|
@ -124,7 +124,7 @@ public class EmailService : IEmailService
|
|||
}
|
||||
|
||||
|
||||
private static async Task<bool> SendEmailWithPost(string url, object data)
|
||||
private static async Task<bool> SendEmailWithPost(string url, object data, int timeoutSecs = 30)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -134,7 +134,7 @@ public class EmailService : IEmailService
|
|||
.WithHeader("x-api-key", "MsnvA2DfQqxSK5jh")
|
||||
.WithHeader("x-kavita-version", BuildInfo.Version)
|
||||
.WithHeader("Content-Type", "application/json")
|
||||
.WithTimeout(TimeSpan.FromSeconds(30))
|
||||
.WithTimeout(TimeSpan.FromSeconds(timeoutSecs))
|
||||
.PostJsonAsync(data);
|
||||
|
||||
if (response.StatusCode != StatusCodes.Status200OK)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ namespace API.Services;
|
|||
|
||||
public interface IReaderService
|
||||
{
|
||||
Task MarkSeriesAsRead(AppUser user, int seriesId);
|
||||
Task MarkSeriesAsUnread(AppUser user, int seriesId);
|
||||
void MarkChaptersAsRead(AppUser user, int seriesId, IEnumerable<Chapter> chapters);
|
||||
void MarkChaptersAsUnread(AppUser user, int seriesId, IEnumerable<Chapter> chapters);
|
||||
Task<bool> SaveReadingProgress(ProgressDto progressDto, int userId);
|
||||
|
|
@ -45,6 +47,40 @@ public class ReaderService : IReaderService
|
|||
return Parser.Parser.NormalizePath(Path.Join(baseDirectory, $"{userId}", $"{seriesId}", $"{chapterId}"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does not commit. Marks all entities under the series as read.
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="seriesId"></param>
|
||||
public async Task MarkSeriesAsRead(AppUser user, int seriesId)
|
||||
{
|
||||
var volumes = await _unitOfWork.VolumeRepository.GetVolumes(seriesId);
|
||||
user.Progresses ??= new List<AppUserProgress>();
|
||||
foreach (var volume in volumes)
|
||||
{
|
||||
MarkChaptersAsRead(user, seriesId, volume.Chapters);
|
||||
}
|
||||
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does not commit. Marks all entities under the series as unread.
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="seriesId"></param>
|
||||
public async Task MarkSeriesAsUnread(AppUser user, int seriesId)
|
||||
{
|
||||
var volumes = await _unitOfWork.VolumeRepository.GetVolumes(seriesId);
|
||||
user.Progresses ??= new List<AppUserProgress>();
|
||||
foreach (var volume in volumes)
|
||||
{
|
||||
MarkChaptersAsUnread(user, seriesId, volume.Chapters);
|
||||
}
|
||||
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks all Chapters as Read by creating or updating UserProgress rows. Does not commit.
|
||||
/// </summary>
|
||||
|
|
@ -367,7 +403,7 @@ public class ReaderService : IReaderService
|
|||
.ToList();
|
||||
|
||||
// If there are any volumes that have progress, return those. If not, move on.
|
||||
var currentlyReadingChapter = volumeChapters.FirstOrDefault(chapter => chapter.PagesRead < chapter.Pages && chapter.PagesRead > 0);
|
||||
var currentlyReadingChapter = volumeChapters.FirstOrDefault(chapter => chapter.PagesRead < chapter.Pages); // (removed for GetContinuePoint_ShouldReturnFirstVolumeChapter_WhenPreExistingProgress), not sure if needed && chapter.PagesRead > 0
|
||||
if (currentlyReadingChapter != null) return currentlyReadingChapter;
|
||||
|
||||
// Check loose leaf chapters (and specials). First check if there are any
|
||||
|
|
|
|||
|
|
@ -445,20 +445,7 @@ public class SeriesService : ISeriesService
|
|||
var firstChapter = volume.Chapters.First();
|
||||
// On Books, skip volumes that are specials, since these will be shown
|
||||
if (firstChapter.IsSpecial) continue;
|
||||
if (string.IsNullOrEmpty(firstChapter.TitleName))
|
||||
{
|
||||
if (!firstChapter.Range.Equals(Parser.Parser.DefaultVolume))
|
||||
{
|
||||
var title = Path.GetFileNameWithoutExtension(firstChapter.Range);
|
||||
if (string.IsNullOrEmpty(title)) continue;
|
||||
volume.Name += $" - {title}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
volume.Name += $" - {firstChapter.TitleName}";
|
||||
}
|
||||
|
||||
RenameVolumeName(firstChapter, volume, libraryType);
|
||||
processedVolumes.Add(volume);
|
||||
}
|
||||
}
|
||||
|
|
@ -517,48 +504,64 @@ public class SeriesService : ISeriesService
|
|||
return !c.IsSpecial && !c.Number.Equals(Parser.Parser.DefaultChapter);
|
||||
}
|
||||
|
||||
public static string FormatChapterTitle(ChapterDto chapter, LibraryType libraryType)
|
||||
public static void RenameVolumeName(ChapterDto firstChapter, VolumeDto volume, LibraryType libraryType)
|
||||
{
|
||||
if (chapter.IsSpecial)
|
||||
if (libraryType == LibraryType.Book)
|
||||
{
|
||||
return Parser.Parser.CleanSpecialTitle(chapter.Title);
|
||||
if (string.IsNullOrEmpty(firstChapter.TitleName))
|
||||
{
|
||||
if (firstChapter.Range.Equals(Parser.Parser.DefaultVolume)) return;
|
||||
var title = Path.GetFileNameWithoutExtension(firstChapter.Range);
|
||||
if (string.IsNullOrEmpty(title)) return;
|
||||
volume.Name += $" - {title}";
|
||||
}
|
||||
else
|
||||
{
|
||||
volume.Name += $" - {firstChapter.TitleName}";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
volume.Name = $"Volume {volume.Name}";
|
||||
}
|
||||
|
||||
|
||||
private static string FormatChapterTitle(bool isSpecial, LibraryType libraryType, string chapterTitle, bool withHash)
|
||||
{
|
||||
if (isSpecial)
|
||||
{
|
||||
return Parser.Parser.CleanSpecialTitle(chapterTitle);
|
||||
}
|
||||
|
||||
var hashSpot = withHash ? "#" : string.Empty;
|
||||
return libraryType switch
|
||||
{
|
||||
LibraryType.Book => $"Book {chapter.Title}",
|
||||
LibraryType.Comic => $"Issue #{chapter.Title}",
|
||||
LibraryType.Manga => $"Chapter {chapter.Title}",
|
||||
LibraryType.Book => $"Book {chapterTitle}",
|
||||
LibraryType.Comic => $"Issue {hashSpot}{chapterTitle}",
|
||||
LibraryType.Manga => $"Chapter {chapterTitle}",
|
||||
_ => "Chapter "
|
||||
};
|
||||
}
|
||||
|
||||
public static string FormatChapterTitle(Chapter chapter, LibraryType libraryType)
|
||||
public static string FormatChapterTitle(ChapterDto chapter, LibraryType libraryType, bool withHash = true)
|
||||
{
|
||||
if (chapter.IsSpecial)
|
||||
{
|
||||
return Parser.Parser.CleanSpecialTitle(chapter.Title);
|
||||
}
|
||||
return libraryType switch
|
||||
{
|
||||
LibraryType.Book => $"Book {chapter.Title}",
|
||||
LibraryType.Comic => $"Issue #{chapter.Title}",
|
||||
LibraryType.Manga => $"Chapter {chapter.Title}",
|
||||
_ => "Chapter "
|
||||
};
|
||||
return FormatChapterTitle(chapter.IsSpecial, libraryType, chapter.Title, withHash);
|
||||
}
|
||||
|
||||
public static string FormatChapterTitle(Chapter chapter, LibraryType libraryType, bool withHash = true)
|
||||
{
|
||||
return FormatChapterTitle(chapter.IsSpecial, libraryType, chapter.Title, withHash);
|
||||
}
|
||||
|
||||
public static string FormatChapterName(LibraryType libraryType, bool withHash = false)
|
||||
{
|
||||
switch (libraryType)
|
||||
return libraryType switch
|
||||
{
|
||||
case LibraryType.Manga:
|
||||
return "Chapter";
|
||||
case LibraryType.Comic:
|
||||
return withHash ? "Issue #" : "Issue";
|
||||
case LibraryType.Book:
|
||||
return "Book";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(libraryType), libraryType, null);
|
||||
}
|
||||
LibraryType.Manga => "Chapter",
|
||||
LibraryType.Comic => withHash ? "Issue #" : "Issue",
|
||||
LibraryType.Book => "Book",
|
||||
_ => "Chapter"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue