Book Reader Redesign with e-ink focus (#1246)
* Refactored the drawer into offcanvas component. Had to write some hacks to emulate how bootstrap's javascript implementation works as ngBootstrap doesn't have a component yet. * Cleaned up some of the code * Rewrote drawer to align it with the new design * First pass, refactored table of content into it's own component * Refactored all of the settings logic into a separate component. Everything is broken. * More settings on on reactive form * More code cleanup on settings * Misc fixes around the drawer code. Fixed a bug where range sliders were inheriting background color of normal text inputs * Fixed dark mode with book reader. We now clear the theme from the main app so book reader is self-contained. Styles for dark mode are injected into the reading-section. Styles that were previously in scss are now only for the actual menu system. * Cleaned up drawer styling on header * Removed an ngIf statement for click to paginate * Tweaked the accent style to have smaller font size and adjusted style on light mode. Cleaned up some clearTimeout code in a further effort to streamline codebase. * Refactored Dark mode into a basic theme. Currently styles are hardcoded. * Patched book theme in from themes branch * Patched in the backend for Book Theme (not tested yet) * Fixed a bug in seeding code for book themes. Started integration of themes into the reader settings * Everything except managing themes is working. Themes are a bit shakey, having second thoughts if we should have them or not. * Reverted the ability to do custom user book themes. Code is stable with system themes. * Stablize the Styles (#1128) * Fixed a bug where adding multiple series to reading list would throw an error on UI, but it was successful. * When a series has a reading list, we now show the connection on Series detail. * Removed all baseurl code from UI and not-connected component since we no longer use it. * Fixed tag badges not showing a border. Added last read time to the series detail page * Fixed up error interceptor to remove no-connection code * Changed implementation for series detail. Book libraries will never send chapters back. Volume 0 volumes will not be sent in volumes ever. Fixed up more renaming logic on books to send more accurate representations to the UI. * Cleaned up the selected tab and tab display logic * Fixed a bad where statement in reading lists for series * Fixed up tab logic again * Fixed a small margin on search backdrop * Made badge expander button smaller to align with badges * Fixed a few UIs due to .form-group and .form-row being removed * Updated Theme component page to help with style testing * Added more components to theme tester * Cleaned up some styling * Fixed opacity on search item hover * Bump versions by dotnet-bump-version. * Tweaked the accordion styles for light mode * Set dark book theme as default. Refactored resetSettings to be much cleaner * Started the refactor to allow book themes to affect global css variables * Fixed some issues with my css variable declarations * Fixed a close model state update * Lots of work, but dark mode on the book reader is basically done. We have to code the themes much like the site themes * Some black theme enhancements * Started working on column layout in book reader. * Cleaned up the CSS on Reader Settings * Hooked up reading direction * Got column and double column layout working * Implemented some basic virtual paging and hooked in book color theme and layout mode into user preferences. * Migration wrote, can edit page layout and color theme on book reader. Removed book dark mode since no longer needed. Fixed a bug on login/register forms where when input is focused, text is white and not black. * When loading book reader, apply column layout. * Lots of work around 2 column layout, working on images not splitting. Still not working, committing so i can merge develop in and validate code with new manga reader. * Fixed images being split into 2 BUT regression on each page boundary, total reading height is smaller and smaller * Fixed some rendering bugs where toggling column layouts would shrink images on screen constantly. Fixed a bug where bottom bar wouldn't render on column layout in some conditions (this might need to be reworked) * Started progress on progress work * Updated .NET to 6.0.4 * Fixed a bug where DataContextModelSnapshot was being removed on build thus new migrations were broken. * Tweaked the code around progress saving so that we don't loose track of last scroll element on page load * Trying to restore progress, but stuck * Extra merge stuff * Fixed a bug where volumes that are a range fail to generate series detail * No gutters on whole app. Book reader backend now applies the image class automatically at the backend. * Added wiki documentation into invite user flow and register admin user to help users understand email isn't required and they can host their own service. * Removed bottom padding * Refactored the document height to be set and removed on nav service, so the book reader and manga reader aren't broken. * Fixed the height of the action bar to simplify logic and keep the code cleaner. Refactored book service image scoping to be much more streamlined and efficient * Fixed the height of action bar to 62px and adjusted code to use the hardcoded px. (code commented) * Removed commented out code from fixed action bar height * Progress restoration seems to be working * Code cleanup * Ensure the bottom action bar is at the bottom of the viewport on small pages * Fixed book fonts not setting properly and added OpenDyslexic font. * Fixed up some font issues * Updated drawer so all sections are open by default * Switched some LINQ to use MinBy * When navigating between pages and column layout, adjust the shift for the user. * Removed some debug code * Blacklist .qpkg folders and don't scan Recently-Snapshot or recycle folders. * Renamed the scale width to be scoped to kavita to avoid conflicts. * Refactored ngx-sliders out to use normal range instead. Changed up the preferences to separate image and book settinngs into own accordion. * updated user preferences for new migration options (not committed yet) * Removed some debug code * Remove console.logs * Migration committed, let's release this to users. * A lot of crazy code just to ensure that when you close drawer the toggle reflectst that state.
This commit is contained in:
parent
641e0a71e9
commit
2723a6cd10
79 changed files with 3708 additions and 1190 deletions
|
@ -94,16 +94,12 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Interfaces\IMetadataService.cs" />
|
||||
<Compile Remove="obj\**" />
|
||||
<Compile Remove="cache\**" />
|
||||
<Compile Remove="backups\**" />
|
||||
<Compile Remove="logs\**" />
|
||||
<Compile Remove="temp\**" />
|
||||
<Compile Remove="covers\**" />
|
||||
<Compile Remove="DTOs\Email\SmtpConfig.cs" />
|
||||
<Compile Remove="DTOs\Email\EmailOptionsDto.cs" />
|
||||
<Compile Remove="Helpers\Converters\SmtpConverter.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.DTOs.Theme;
|
||||
using API.Extensions;
|
||||
using API.Services;
|
||||
using API.Services.Tasks;
|
||||
using Kavita.Common;
|
||||
|
@ -13,13 +14,13 @@ namespace API.Controllers;
|
|||
public class ThemeController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly ISiteThemeService _siteThemeService;
|
||||
private readonly IThemeService _themeService;
|
||||
private readonly ITaskScheduler _taskScheduler;
|
||||
|
||||
public ThemeController(IUnitOfWork unitOfWork, ISiteThemeService siteThemeService, ITaskScheduler taskScheduler)
|
||||
public ThemeController(IUnitOfWork unitOfWork, IThemeService themeService, ITaskScheduler taskScheduler)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_siteThemeService = siteThemeService;
|
||||
_themeService = themeService;
|
||||
_taskScheduler = taskScheduler;
|
||||
}
|
||||
|
||||
|
@ -39,9 +40,9 @@ public class ThemeController : BaseApiController
|
|||
|
||||
[Authorize("RequireAdminRole")]
|
||||
[HttpPost("update-default")]
|
||||
public async Task<ActionResult> UpdateDefault(UpdateDefaultSiteThemeDto dto)
|
||||
public async Task<ActionResult> UpdateDefault(UpdateDefaultThemeDto dto)
|
||||
{
|
||||
await _siteThemeService.UpdateDefault(dto.ThemeId);
|
||||
await _themeService.UpdateDefault(dto.ThemeId);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
@ -54,7 +55,7 @@ public class ThemeController : BaseApiController
|
|||
{
|
||||
try
|
||||
{
|
||||
return Ok(await _siteThemeService.GetContent(themeId));
|
||||
return Ok(await _themeService.GetContent(themeId));
|
||||
}
|
||||
catch (KavitaException ex)
|
||||
{
|
||||
|
|
|
@ -82,11 +82,12 @@ namespace API.Controllers
|
|||
existingPreferences.BookReaderMargin = preferencesDto.BookReaderMargin;
|
||||
existingPreferences.BookReaderLineSpacing = preferencesDto.BookReaderLineSpacing;
|
||||
existingPreferences.BookReaderFontFamily = preferencesDto.BookReaderFontFamily;
|
||||
existingPreferences.BookReaderDarkMode = preferencesDto.BookReaderDarkMode;
|
||||
existingPreferences.BookReaderFontSize = preferencesDto.BookReaderFontSize;
|
||||
existingPreferences.BookReaderTapToPaginate = preferencesDto.BookReaderTapToPaginate;
|
||||
existingPreferences.BookReaderReadingDirection = preferencesDto.BookReaderReadingDirection;
|
||||
preferencesDto.Theme ??= await _unitOfWork.SiteThemeRepository.GetDefaultTheme();
|
||||
existingPreferences.BookThemeName = preferencesDto.BookReaderThemeName;
|
||||
existingPreferences.PageLayoutMode = preferencesDto.BookReaderLayoutMode;
|
||||
existingPreferences.Theme = await _unitOfWork.SiteThemeRepository.GetThemeById(preferencesDto.Theme.Id);
|
||||
|
||||
// TODO: Remove this code - this overrides layout mode to be single until the mode is released
|
||||
|
|
|
@ -4,6 +4,9 @@ using API.Services;
|
|||
|
||||
namespace API.DTOs.Theme;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a set of css overrides the user can upload to Kavita and will load into webui
|
||||
/// </summary>
|
||||
public class SiteThemeDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
namespace API.DTOs.Theme;
|
||||
|
||||
public class UpdateDefaultSiteThemeDto
|
||||
public class UpdateDefaultThemeDto
|
||||
{
|
||||
public int ThemeId { get; set; }
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using API.Entities;
|
||||
using API.DTOs.Theme;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
|
||||
namespace API.DTOs
|
||||
|
@ -74,5 +75,7 @@ namespace API.DTOs
|
|||
/// </summary>
|
||||
/// <remarks>Should default to Dark</remarks>
|
||||
public SiteTheme Theme { get; set; }
|
||||
public string BookReaderThemeName { get; set; }
|
||||
public BookPageLayoutMode BookReaderLayoutMode { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,33 +44,40 @@ namespace API.Data
|
|||
public DbSet<SeriesRelation> SeriesRelation { get; set; }
|
||||
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
|
||||
modelBuilder.Entity<AppUser>()
|
||||
builder.Entity<AppUser>()
|
||||
.HasMany(ur => ur.UserRoles)
|
||||
.WithOne(u => u.User)
|
||||
.HasForeignKey(ur => ur.UserId)
|
||||
.IsRequired();
|
||||
|
||||
modelBuilder.Entity<AppRole>()
|
||||
builder.Entity<AppRole>()
|
||||
.HasMany(ur => ur.UserRoles)
|
||||
.WithOne(u => u.Role)
|
||||
.HasForeignKey(ur => ur.RoleId)
|
||||
.IsRequired();
|
||||
|
||||
modelBuilder.Entity<SeriesRelation>()
|
||||
builder.Entity<SeriesRelation>()
|
||||
.HasOne(pt => pt.Series)
|
||||
.WithMany(p => p.Relations)
|
||||
.HasForeignKey(pt => pt.SeriesId)
|
||||
.OnDelete(DeleteBehavior.ClientCascade);
|
||||
|
||||
modelBuilder.Entity<SeriesRelation>()
|
||||
builder.Entity<SeriesRelation>()
|
||||
.HasOne(pt => pt.TargetSeries)
|
||||
.WithMany(t => t.RelationOf)
|
||||
.HasForeignKey(pt => pt.TargetSeriesId);
|
||||
|
||||
builder.Entity<AppUserPreferences>()
|
||||
.Property(b => b.BookThemeName)
|
||||
.HasDefaultValue("Dark");
|
||||
builder.Entity<AppUserPreferences>()
|
||||
.Property(b => b.BackgroundColor)
|
||||
.HasDefaultValue("#000000");
|
||||
}
|
||||
|
||||
|
||||
|
|
1523
API/Data/Migrations/20220508162841_BookReaderUpdate.Designer.cs
generated
Normal file
1523
API/Data/Migrations/20220508162841_BookReaderUpdate.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
56
API/Data/Migrations/20220508162841_BookReaderUpdate.cs
Normal file
56
API/Data/Migrations/20220508162841_BookReaderUpdate.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
public partial class BookReaderUpdate : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "BookReaderDarkMode",
|
||||
table: "AppUserPreferences",
|
||||
newName: "PageLayoutMode");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "BackgroundColor",
|
||||
table: "AppUserPreferences",
|
||||
type: "TEXT",
|
||||
nullable: true,
|
||||
defaultValue: "#000000",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "TEXT",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "BookThemeName",
|
||||
table: "AppUserPreferences",
|
||||
type: "TEXT",
|
||||
nullable: true,
|
||||
defaultValue: "Dark");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BookThemeName",
|
||||
table: "AppUserPreferences");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "PageLayoutMode",
|
||||
table: "AppUserPreferences",
|
||||
newName: "BookReaderDarkMode");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "BackgroundColor",
|
||||
table: "AppUserPreferences",
|
||||
type: "TEXT",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "TEXT",
|
||||
oldNullable: true,
|
||||
oldDefaultValue: "#000000");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ namespace API.Data.Migrations
|
|||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.3");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.4");
|
||||
|
||||
modelBuilder.Entity("API.Entities.AppRole", b =>
|
||||
{
|
||||
|
@ -166,10 +166,9 @@ namespace API.Data.Migrations
|
|||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("BackgroundColor")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("BookReaderDarkMode")
|
||||
.HasColumnType("INTEGER");
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValue("#000000");
|
||||
|
||||
b.Property<string>("BookReaderFontFamily")
|
||||
.HasColumnType("TEXT");
|
||||
|
@ -189,9 +188,17 @@ namespace API.Data.Migrations
|
|||
b.Property<bool>("BookReaderTapToPaginate")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("BookThemeName")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValue("Dark");
|
||||
|
||||
b.Property<int>("LayoutMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PageLayoutMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PageSplitOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ public interface ISiteThemeRepository
|
|||
Task<SiteThemeDto> GetThemeDtoByName(string themeName);
|
||||
Task<SiteTheme> GetDefaultTheme();
|
||||
Task<IEnumerable<SiteTheme>> GetThemes();
|
||||
|
||||
Task<SiteTheme> GetThemeById(int themeId);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace API.Entities
|
|||
/// </example>
|
||||
/// </summary>
|
||||
public ReaderMode ReaderMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Manga Reader Option: Allow the menu to close after 6 seconds without interaction
|
||||
/// </summary>
|
||||
|
@ -42,10 +43,6 @@ namespace API.Entities
|
|||
/// </summary>
|
||||
public string BackgroundColor { get; set; } = "#000000";
|
||||
/// <summary>
|
||||
/// Book Reader Option: Should the background color be dark
|
||||
/// </summary>
|
||||
public bool BookReaderDarkMode { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Book Reader Option: Override extra Margin
|
||||
/// </summary>
|
||||
public int BookReaderMargin { get; set; } = 15;
|
||||
|
@ -74,7 +71,17 @@ namespace API.Entities
|
|||
/// </summary>
|
||||
/// <remarks>Should default to Dark</remarks>
|
||||
public SiteTheme Theme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Book Reader Option: The color theme to decorate the book contents
|
||||
/// </summary>
|
||||
/// <remarks>Should default to Dark</remarks>
|
||||
public string BookThemeName { get; set; } = "Dark";
|
||||
/// <summary>
|
||||
/// Book Reader Option: The way a page from a book is rendered. Default is as book dictates, 1 column is fit to height,
|
||||
/// 2 column is fit to height, 2 columns
|
||||
/// </summary>
|
||||
/// <remarks>Defaults to Default</remarks>
|
||||
public BookPageLayoutMode PageLayoutMode { get; set; } = BookPageLayoutMode.Default;
|
||||
|
||||
|
||||
public AppUser AppUser { get; set; }
|
||||
|
|
13
API/Entities/Enums/BookPageLayoutMode.cs
Normal file
13
API/Entities/Enums/BookPageLayoutMode.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace API.Entities.Enums;
|
||||
|
||||
public enum BookPageLayoutMode
|
||||
{
|
||||
[Description("Default")]
|
||||
Default = 0,
|
||||
[Description("1 Column")]
|
||||
Column1 = 1,
|
||||
[Description("2 Column")]
|
||||
Column2 = 2
|
||||
}
|
15
API/Entities/Interfaces/ITheme.cs
Normal file
15
API/Entities/Interfaces/ITheme.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using API.Entities.Enums.Theme;
|
||||
|
||||
namespace API.Entities.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// A theme in some kind
|
||||
/// </summary>
|
||||
public interface ITheme
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string NormalizedName { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public bool IsDefault { get; set; }
|
||||
public ThemeProvider Provider { get; set; }
|
||||
}
|
|
@ -8,7 +8,7 @@ namespace API.Entities;
|
|||
/// <summary>
|
||||
/// Represents a set of css overrides the user can upload to Kavita and will load into webui
|
||||
/// </summary>
|
||||
public class SiteTheme : IEntityDate
|
||||
public class SiteTheme : IEntityDate, ITheme
|
||||
{
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
|
@ -23,6 +23,7 @@ public class SiteTheme : IEntityDate
|
|||
/// File path to the content. Stored under <see cref="DirectoryService.SiteThemeDirectory"/>.
|
||||
/// Must be a .css file
|
||||
/// </summary>
|
||||
/// <remarks>System provided themes use an alternative location as they are packaged with the app</remarks>
|
||||
public string FileName { get; set; }
|
||||
/// <summary>
|
||||
/// Only one theme can have this. Will auto-set this as default for new user accounts
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace API.Extensions
|
|||
services.AddScoped<IAccountService, AccountService>();
|
||||
services.AddScoped<IEmailService, EmailService>();
|
||||
services.AddScoped<IBookmarkService, BookmarkService>();
|
||||
services.AddScoped<ISiteThemeService, SiteThemeService>();
|
||||
services.AddScoped<IThemeService, ThemeService>();
|
||||
services.AddScoped<ISeriesService, SeriesService>();
|
||||
|
||||
|
||||
|
|
|
@ -107,7 +107,13 @@ namespace API.Helpers
|
|||
CreateMap<AppUserPreferences, UserPreferencesDto>()
|
||||
.ForMember(dest => dest.Theme,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.Theme));
|
||||
opt.MapFrom(src => src.Theme))
|
||||
.ForMember(dest => dest.BookReaderThemeName,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.BookThemeName))
|
||||
.ForMember(dest => dest.BookReaderLayoutMode,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.PageLayoutMode));
|
||||
|
||||
|
||||
CreateMap<AppUserBookmark, BookmarkDto>();
|
||||
|
|
|
@ -43,7 +43,7 @@ namespace API.Parser
|
|||
MatchOptions, RegexTimeout);
|
||||
|
||||
|
||||
private static readonly string XmlRegexExtensions = @"\.xml";
|
||||
private const string XmlRegexExtensions = @"\.xml";
|
||||
private static readonly Regex ImageRegex = new Regex(ImageFileExtensions,
|
||||
MatchOptions, RegexTimeout);
|
||||
private static readonly Regex ArchiveFileRegex = new Regex(ArchiveFileExtensions,
|
||||
|
@ -999,7 +999,7 @@ namespace API.Parser
|
|||
|
||||
public static bool HasBlacklistedFolderInPath(string path)
|
||||
{
|
||||
return path.Contains("__MACOSX") || path.StartsWith("@Recently-Snapshot") || path.StartsWith("@recycle") || path.StartsWith("._");
|
||||
return path.Contains("__MACOSX") || path.StartsWith("@Recently-Snapshot") || path.StartsWith("@recycle") || path.StartsWith("._") || path.Contains(".qpkg");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -183,6 +183,7 @@ namespace API.Services
|
|||
|
||||
EscapeFontFamilyReferences(ref stylesheetHtml, apiBase, prepend);
|
||||
|
||||
|
||||
// Check if there are any background images and rewrite those urls
|
||||
EscapeCssImageReferences(ref stylesheetHtml, apiBase, book);
|
||||
|
||||
|
@ -246,67 +247,62 @@ namespace API.Services
|
|||
private static void ScopeImages(HtmlDocument doc, EpubBookRef book, string apiBase)
|
||||
{
|
||||
var images = doc.DocumentNode.SelectNodes("//img");
|
||||
if (images != null)
|
||||
if (images == null) return;
|
||||
|
||||
foreach (var image in images)
|
||||
{
|
||||
foreach (var image in images)
|
||||
if (image.Name != "img") continue;
|
||||
|
||||
string key = null;
|
||||
if (image.Attributes["src"] != null)
|
||||
{
|
||||
if (image.Name != "img") continue;
|
||||
key = "src";
|
||||
}
|
||||
else if (image.Attributes["xlink:href"] != null)
|
||||
{
|
||||
key = "xlink:href";
|
||||
}
|
||||
|
||||
// Need to do for xlink:href
|
||||
if (image.Attributes["src"] != null)
|
||||
if (string.IsNullOrEmpty(key)) continue;
|
||||
|
||||
var imageFile = GetKeyForImage(book, image.Attributes[key].Value);
|
||||
image.Attributes.Remove(key);
|
||||
image.Attributes.Add(key, $"{apiBase}" + imageFile);
|
||||
|
||||
// Add a custom class that the reader uses to ensure images stay within reader
|
||||
image.AddClass("kavita-scale-width");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the image key associated with the file. Contains some basic fallback logic.
|
||||
/// </summary>
|
||||
/// <param name="book"></param>
|
||||
/// <param name="imageFile"></param>
|
||||
/// <returns></returns>
|
||||
private static string GetKeyForImage(EpubBookRef book, string imageFile)
|
||||
{
|
||||
if (!book.Content.Images.ContainsKey(imageFile))
|
||||
{
|
||||
var correctedKey = book.Content.Images.Keys.SingleOrDefault(s => s.EndsWith(imageFile));
|
||||
if (correctedKey != null)
|
||||
{
|
||||
imageFile = correctedKey;
|
||||
}
|
||||
else if (imageFile.StartsWith(".."))
|
||||
{
|
||||
// There are cases where the key is defined static like OEBPS/Images/1-4.jpg but reference is ../Images/1-4.jpg
|
||||
correctedKey =
|
||||
book.Content.Images.Keys.SingleOrDefault(s => s.EndsWith(imageFile.Replace("..", string.Empty)));
|
||||
if (correctedKey != null)
|
||||
{
|
||||
var imageFile = image.Attributes["src"].Value;
|
||||
if (!book.Content.Images.ContainsKey(imageFile))
|
||||
{
|
||||
// TODO: Refactor the Key code to a method to allow the hacks to be tested
|
||||
var correctedKey = book.Content.Images.Keys.SingleOrDefault(s => s.EndsWith(imageFile));
|
||||
if (correctedKey != null)
|
||||
{
|
||||
imageFile = correctedKey;
|
||||
} else if (imageFile.StartsWith(".."))
|
||||
{
|
||||
// There are cases where the key is defined static like OEBPS/Images/1-4.jpg but reference is ../Images/1-4.jpg
|
||||
correctedKey = book.Content.Images.Keys.SingleOrDefault(s => s.EndsWith(imageFile.Replace("..", string.Empty)));
|
||||
if (correctedKey != null)
|
||||
{
|
||||
imageFile = correctedKey;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
image.Attributes.Remove("src");
|
||||
image.Attributes.Add("src", $"{apiBase}" + imageFile);
|
||||
imageFile = correctedKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
images = doc.DocumentNode.SelectNodes("//image");
|
||||
if (images != null)
|
||||
{
|
||||
foreach (var image in images)
|
||||
{
|
||||
if (image.Name != "image") continue;
|
||||
|
||||
if (image.Attributes["xlink:href"] != null)
|
||||
{
|
||||
var imageFile = image.Attributes["xlink:href"].Value;
|
||||
if (!book.Content.Images.ContainsKey(imageFile))
|
||||
{
|
||||
var correctedKey = book.Content.Images.Keys.SingleOrDefault(s => s.EndsWith(imageFile));
|
||||
if (correctedKey != null)
|
||||
{
|
||||
imageFile = correctedKey;
|
||||
}
|
||||
}
|
||||
|
||||
image.Attributes.Remove("xlink:href");
|
||||
image.Attributes.Add("xlink:href", $"{apiBase}" + imageFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
return imageFile;
|
||||
}
|
||||
|
||||
private static string PrepareFinalHtml(HtmlDocument doc, HtmlNode body)
|
||||
|
|
|
@ -69,7 +69,7 @@ namespace API.Services
|
|||
private readonly ILogger<DirectoryService> _logger;
|
||||
|
||||
private static readonly Regex ExcludeDirectories = new Regex(
|
||||
@"@eaDir|\.DS_Store|\.qpkg",
|
||||
@"@eaDir|\.DS_Store|\.qpkg|__MACOSX|@Recently-Snapshot|@recycle",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex FileCopyAppend = new Regex(@"\(\d+\)",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
|
|
@ -62,7 +62,7 @@ public class MetadataService : IMetadataService
|
|||
/// <param name="forceUpdate">Force updating cover image even if underlying file has not been modified or chapter already has a cover image</param>
|
||||
private async Task<bool> UpdateChapterCoverImage(Chapter chapter, bool forceUpdate)
|
||||
{
|
||||
var firstFile = chapter.Files.OrderBy(x => x.Chapter).FirstOrDefault();
|
||||
var firstFile = chapter.Files.MinBy(x => x.Chapter);
|
||||
|
||||
if (!_cacheHelper.ShouldUpdateCoverImage(_directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, chapter.CoverImage), firstFile, chapter.Created, forceUpdate, chapter.CoverImageLocked))
|
||||
return false;
|
||||
|
@ -97,12 +97,13 @@ public class MetadataService : IMetadataService
|
|||
null, volume.Created, forceUpdate)) return false;
|
||||
|
||||
volume.Chapters ??= new List<Chapter>();
|
||||
var firstChapter = volume.Chapters.OrderBy(x => double.Parse(x.Number), _chapterSortComparerForInChapterSorting).FirstOrDefault();
|
||||
var firstChapter = volume.Chapters.MinBy(x => double.Parse(x.Number), _chapterSortComparerForInChapterSorting);
|
||||
if (firstChapter == null) return false;
|
||||
|
||||
volume.CoverImage = firstChapter.CoverImage;
|
||||
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate, MessageFactory.CoverUpdateEvent(volume.Id, MessageFactoryEntityTypes.Volume), false);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -133,8 +134,7 @@ public class MetadataService : IMetadataService
|
|||
|
||||
if (!_cacheHelper.CoverImageExists(coverImage))
|
||||
{
|
||||
coverImage = series.Volumes[0].Chapters.OrderBy(c => double.Parse(c.Number), _chapterSortComparerForInChapterSorting)
|
||||
.FirstOrDefault()?.CoverImage;
|
||||
coverImage = series.Volumes[0].Chapters.MinBy(c => double.Parse(c.Number), _chapterSortComparerForInChapterSorting)?.CoverImage;
|
||||
}
|
||||
}
|
||||
series.CoverImage = firstCover?.CoverImage ?? coverImage;
|
||||
|
|
|
@ -36,7 +36,7 @@ public class TaskScheduler : ITaskScheduler
|
|||
|
||||
private readonly IStatsService _statsService;
|
||||
private readonly IVersionUpdaterService _versionUpdaterService;
|
||||
private readonly ISiteThemeService _siteThemeService;
|
||||
private readonly IThemeService _themeService;
|
||||
|
||||
public static BackgroundJobServer Client => new BackgroundJobServer();
|
||||
private static readonly Random Rnd = new Random();
|
||||
|
@ -45,7 +45,7 @@ public class TaskScheduler : ITaskScheduler
|
|||
public TaskScheduler(ICacheService cacheService, ILogger<TaskScheduler> logger, IScannerService scannerService,
|
||||
IUnitOfWork unitOfWork, IMetadataService metadataService, IBackupService backupService,
|
||||
ICleanupService cleanupService, IStatsService statsService, IVersionUpdaterService versionUpdaterService,
|
||||
ISiteThemeService siteThemeService)
|
||||
IThemeService themeService)
|
||||
{
|
||||
_cacheService = cacheService;
|
||||
_logger = logger;
|
||||
|
@ -56,7 +56,7 @@ public class TaskScheduler : ITaskScheduler
|
|||
_cleanupService = cleanupService;
|
||||
_statsService = statsService;
|
||||
_versionUpdaterService = versionUpdaterService;
|
||||
_siteThemeService = siteThemeService;
|
||||
_themeService = themeService;
|
||||
}
|
||||
|
||||
public async Task ScheduleTasks()
|
||||
|
@ -131,7 +131,7 @@ public class TaskScheduler : ITaskScheduler
|
|||
public void ScanSiteThemes()
|
||||
{
|
||||
_logger.LogInformation("Starting Site Theme scan");
|
||||
BackgroundJob.Enqueue(() => _siteThemeService.Scan());
|
||||
BackgroundJob.Enqueue(() => _themeService.Scan());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -149,6 +149,7 @@ public class TaskScheduler : ITaskScheduler
|
|||
public void ScanLibrary(int libraryId)
|
||||
{
|
||||
_logger.LogInformation("Enqueuing library scan for: {LibraryId}", libraryId);
|
||||
// TODO: If a library scan is already queued up for libraryId, don't do anything
|
||||
BackgroundJob.Enqueue(() => _scannerService.ScanLibrary(libraryId));
|
||||
// When we do a scan, force cache to re-unpack in case page numbers change
|
||||
BackgroundJob.Enqueue(() => _cleanupService.CleanupCacheDirectory());
|
||||
|
|
|
@ -301,6 +301,9 @@ public class ScannerService : IScannerService
|
|||
|
||||
await CleanupDbEntities();
|
||||
|
||||
// await _eventHub.SendMessageAsync(SignalREvents.NotificationProgress,
|
||||
// MessageFactory.ScanLibraryProgressEvent(libraryId, 1F));
|
||||
|
||||
BackgroundJob.Enqueue(() => _metadataService.RefreshMetadata(libraryId, false));
|
||||
}
|
||||
|
||||
|
@ -712,7 +715,7 @@ public class ScannerService : IScannerService
|
|||
}
|
||||
}
|
||||
|
||||
// BUG: The issue here is that people is just from chapter, but series metadata might already have some people on it
|
||||
// NOTE: The issue here is that people is just from chapter, but series metadata might already have some people on it
|
||||
// I might be able to filter out people that are in locked fields?
|
||||
var people = chapters.SelectMany(c => c.People).ToList();
|
||||
PersonHelper.KeepOnlySamePeopleBetweenLists(series.Metadata.People,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
|
@ -7,24 +6,23 @@ using API.Entities;
|
|||
using API.Entities.Enums.Theme;
|
||||
using API.SignalR;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace API.Services.Tasks;
|
||||
|
||||
public interface ISiteThemeService
|
||||
public interface IThemeService
|
||||
{
|
||||
Task<string> GetContent(int themeId);
|
||||
Task Scan();
|
||||
Task UpdateDefault(int themeId);
|
||||
}
|
||||
|
||||
public class SiteThemeService : ISiteThemeService
|
||||
public class ThemeService : IThemeService
|
||||
{
|
||||
private readonly IDirectoryService _directoryService;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IEventHub _eventHub;
|
||||
|
||||
public SiteThemeService(IDirectoryService directoryService, IUnitOfWork unitOfWork, IEventHub eventHub)
|
||||
public ThemeService(IDirectoryService directoryService, IUnitOfWork unitOfWork, IEventHub eventHub)
|
||||
{
|
||||
_directoryService = directoryService;
|
||||
_unitOfWork = unitOfWork;
|
||||
|
@ -36,7 +34,6 @@ public class SiteThemeService : ISiteThemeService
|
|||
/// </summary>
|
||||
/// <param name="themeId"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="KavitaException"></exception>
|
||||
public async Task<string> GetContent(int themeId)
|
||||
{
|
||||
var theme = await _unitOfWork.SiteThemeRepository.GetThemeDto(themeId);
|
||||
|
@ -55,7 +52,8 @@ public class SiteThemeService : ISiteThemeService
|
|||
{
|
||||
_directoryService.ExistOrCreate(_directoryService.SiteThemeDirectory);
|
||||
var reservedNames = Seed.DefaultThemes.Select(t => t.NormalizedName).ToList();
|
||||
var themeFiles = _directoryService.GetFilesWithExtension(Parser.Parser.NormalizePath(_directoryService.SiteThemeDirectory), @"\.css")
|
||||
var themeFiles = _directoryService
|
||||
.GetFilesWithExtension(Parser.Parser.NormalizePath(_directoryService.SiteThemeDirectory), @"\.css")
|
||||
.Where(name => !reservedNames.Contains(Parser.Parser.Normalize(name))).ToList();
|
||||
|
||||
var allThemes = (await _unitOfWork.SiteThemeRepository.GetThemes()).ToList();
|
||||
|
@ -91,7 +89,8 @@ public class SiteThemeService : ISiteThemeService
|
|||
});
|
||||
|
||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||
MessageFactory.SiteThemeProgressEvent(_directoryService.FileSystem.Path.GetFileName(themeFile), themeName, ProgressEventType.Updated));
|
||||
MessageFactory.SiteThemeProgressEvent(_directoryService.FileSystem.Path.GetFileName(themeFile), themeName,
|
||||
ProgressEventType.Updated));
|
||||
}
|
||||
|
||||
|
||||
|
@ -116,10 +115,10 @@ public class SiteThemeService : ISiteThemeService
|
|||
}
|
||||
|
||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||
MessageFactory.SiteThemeProgressEvent("", "", ProgressEventType.Ended));
|
||||
|
||||
MessageFactory.SiteThemeProgressEvent("", "", ProgressEventType.Ended));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes the theme and any references to it from Pref and sets them to the default at the time.
|
||||
/// This commits to DB.
|
||||
|
|
|
@ -66,6 +66,10 @@ namespace API.SignalR
|
|||
/// </summary>
|
||||
private const string SiteThemeProgress = "SiteThemeProgress";
|
||||
/// <summary>
|
||||
/// A custom book theme was removed or added
|
||||
/// </summary>
|
||||
private const string BookThemeProgress = "BookThemeProgress";
|
||||
/// <summary>
|
||||
/// A type of event that has progress (determinate or indeterminate).
|
||||
/// The underlying event will have a name to give details on how to handle.
|
||||
/// </summary>
|
||||
|
@ -367,5 +371,21 @@ namespace API.SignalR
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage BookThemeProgressEvent(string subtitle, string themeName, string eventType)
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
Name = BookThemeProgress,
|
||||
Title = "Scanning Book Theme",
|
||||
SubTitle = subtitle,
|
||||
EventType = eventType,
|
||||
Progress = ProgressType.Indeterminate,
|
||||
Body = new
|
||||
{
|
||||
ThemeName = themeName,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue