mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-24 00:52:23 -04:00
Swagger, Tachiyomi, and some new settings (#1331)
* Fixed up swagger generation * Updated Tachiyomi's latest-chapter to hopefully solve some sync issues. * Fixed #1279 with table of contents due to new EPubReader * When errors occur, show the event widget icon in red * Lots of documentation added and tweaked some wording around backups and swagger * For promidius * Return proper ChapterDTO * Hacks for Promidius * Cleanup code * No loose leaf, send max chapter * One more encode change * Implemented code per promiduius' requirements * Fixed a bug in the epub parsing where even if you had a series index and series group, but didn't have the series in the title, Kavita wouldn't group them properly. * Removed some extra comment * Implemented the ability to change a library's type after it's been setup. This displays a warning explaining the dangers of it. * Removed some whitespace * Blur descriptions based on read status for list item view to avoid spoilers * Tweaked placement of a tooltip due to new series detail styles * Hooked up a user preference for bluring unread summaries. Fixed a bug in refresh token where we would cause re-authentication when it shouldn't be needed.
This commit is contained in:
parent
f2c08092b8
commit
2ab0aedd22
@ -5,15 +5,17 @@
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<ApplicationIcon>../favicon.ico</ApplicationIcon>
|
||||
<DocumentationFile>bin\$(Configuration)\$(AssemblyName).xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DocumentationFile>bin\Debug\API.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\$(Configuration)\$(AssemblyName).xml</DocumentationFile>
|
||||
<NoWarn>1701;1702;1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
@ -42,37 +44,37 @@
|
||||
<PackageReference Include="ExCSS" Version="4.1.0" />
|
||||
<PackageReference Include="Flurl" Version="3.0.6" />
|
||||
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
||||
<PackageReference Include="Hangfire" Version="1.7.29" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.29" />
|
||||
<PackageReference Include="Hangfire" Version="1.7.30" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.30" />
|
||||
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
|
||||
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.43" />
|
||||
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.6">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.2.0" />
|
||||
<PackageReference Include="NetVips" Version="2.1.0" />
|
||||
<PackageReference Include="NetVips.Native" Version="8.12.2" />
|
||||
<PackageReference Include="NReco.Logging.File" Version="1.1.5" />
|
||||
<PackageReference Include="SharpCompress" Version="0.31.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.2" />
|
||||
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.40.0.48530">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.19.0" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="17.0.15" />
|
||||
<PackageReference Include="VersOne.Epub" Version="3.1.1" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.20.0" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="17.0.18" />
|
||||
<PackageReference Include="VersOne.Epub" Version="3.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -134,6 +136,9 @@
|
||||
</Content>
|
||||
<Content Remove="covers\**" />
|
||||
<Content Remove="config\covers\**" />
|
||||
<Content Update="bin\$(Configuration)\$(AssemblyName).xml">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -207,6 +207,11 @@ namespace API.Controllers
|
||||
return dto;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the user's JWT token
|
||||
/// </summary>
|
||||
/// <param name="tokenRequestDto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("refresh-token")]
|
||||
public async Task<ActionResult<TokenRequestDto>> RefreshToken([FromBody] TokenRequestDto tokenRequestDto)
|
||||
{
|
||||
|
@ -14,6 +14,10 @@ namespace API.Controllers
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an admin exists on the system. This is essentially a check to validate if the system has been setup.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("exists")]
|
||||
public async Task<ActionResult<bool>> AdminExists()
|
||||
{
|
||||
@ -21,4 +25,4 @@ namespace API.Controllers
|
||||
return users.Count > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,12 @@ namespace API.Controllers
|
||||
_cacheService = cacheService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves information for the PDF and Epub reader
|
||||
/// </summary>
|
||||
/// <remarks>This only applies to Epub or PDF files</remarks>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{chapterId}/book-info")]
|
||||
public async Task<ActionResult<BookInfoDto>> GetBookInfo(int chapterId)
|
||||
{
|
||||
@ -64,8 +70,6 @@ namespace API.Controllers
|
||||
break;
|
||||
case MangaFormat.Unknown:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
return Ok(new BookInfoDto()
|
||||
@ -83,6 +87,12 @@ namespace API.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is an entry point to fetch resources from within an epub chapter/book.
|
||||
/// </summary>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{chapterId}/book-resources")]
|
||||
public async Task<ActionResult> GetBookPageResources(int chapterId, [FromQuery] string file)
|
||||
{
|
||||
@ -102,7 +112,7 @@ namespace API.Controllers
|
||||
|
||||
/// <summary>
|
||||
/// This will return a list of mappings from ID -> page num. ID will be the xhtml key and page num will be the reading order
|
||||
/// this is used to rewrite anchors in the book text so that we always load properly in FE
|
||||
/// this is used to rewrite anchors in the book text so that we always load properly in our reader.
|
||||
/// </summary>
|
||||
/// <remarks>This is essentially building the table of contents</remarks>
|
||||
/// <param name="chapterId"></param>
|
||||
@ -229,6 +239,13 @@ namespace API.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This returns a single page within the epub book. All html will be rewritten to be scoped within our reader,
|
||||
/// all css is scoped, etc.
|
||||
/// </summary>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <param name="page"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{chapterId}/book-page")]
|
||||
public async Task<ActionResult<string>> GetBookPage(int chapterId, [FromQuery] int page)
|
||||
{
|
||||
|
@ -18,6 +18,9 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// All APIs related to downloading entities from the system. Requires Download Role or Admin Role.
|
||||
/// </summary>
|
||||
[Authorize(Policy="RequireDownloadRole")]
|
||||
public class DownloadController : BaseApiController
|
||||
{
|
||||
@ -42,6 +45,11 @@ namespace API.Controllers
|
||||
_bookmarkService = bookmarkService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For a given volume, return the size in bytes
|
||||
/// </summary>
|
||||
/// <param name="volumeId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("volume-size")]
|
||||
public async Task<ActionResult<long>> GetVolumeSize(int volumeId)
|
||||
{
|
||||
@ -49,6 +57,11 @@ namespace API.Controllers
|
||||
return Ok(_directoryService.GetTotalSize(files.Select(c => c.FilePath)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For a given chapter, return the size in bytes
|
||||
/// </summary>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("chapter-size")]
|
||||
public async Task<ActionResult<long>> GetChapterSize(int chapterId)
|
||||
{
|
||||
@ -56,6 +69,11 @@ namespace API.Controllers
|
||||
return Ok(_directoryService.GetTotalSize(files.Select(c => c.FilePath)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For a series, return the size in bytes
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("series-size")]
|
||||
public async Task<ActionResult<long>> GetSeriesSize(int seriesId)
|
||||
{
|
||||
@ -64,7 +82,11 @@ namespace API.Controllers
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Downloads all chapters within a volume.
|
||||
/// </summary>
|
||||
/// <param name="volumeId"></param>
|
||||
/// <returns></returns>
|
||||
[Authorize(Policy="RequireDownloadRole")]
|
||||
[HttpGet("volume")]
|
||||
public async Task<ActionResult> DownloadVolume(int volumeId)
|
||||
|
@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
namespace API.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Responsible for servicing up images stored in the DB
|
||||
/// Responsible for servicing up images stored in Kavita for entities
|
||||
/// </summary>
|
||||
public class ImageController : BaseApiController
|
||||
{
|
||||
|
@ -239,6 +239,12 @@ namespace API.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing Library with new name, folders, and/or type.
|
||||
/// </summary>
|
||||
/// <remarks>Any folder or type change will invoke a scan.</remarks>
|
||||
/// <param name="libraryForUserDto"></param>
|
||||
/// <returns></returns>
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost("update")]
|
||||
public async Task<ActionResult> UpdateLibrary(UpdateLibraryDto libraryForUserDto)
|
||||
@ -250,10 +256,13 @@ namespace API.Controllers
|
||||
library.Name = libraryForUserDto.Name;
|
||||
library.Folders = libraryForUserDto.Folders.Select(s => new FolderPath() {Path = s}).ToList();
|
||||
|
||||
var typeUpdate = library.Type != libraryForUserDto.Type;
|
||||
library.Type = libraryForUserDto.Type;
|
||||
|
||||
_unitOfWork.LibraryRepository.Update(library);
|
||||
|
||||
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was a critical issue updating the library.");
|
||||
if (originalFolders.Count != libraryForUserDto.Folders.Count())
|
||||
if (originalFolders.Count != libraryForUserDto.Folders.Count() || typeUpdate)
|
||||
{
|
||||
_taskScheduler.ScanLibrary(library.Id);
|
||||
}
|
||||
|
@ -191,6 +191,11 @@ namespace API.Controllers
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Marks a Series as read. All volumes and chapters will be marked as read during this process.
|
||||
/// </summary>
|
||||
/// <param name="markReadDto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("mark-read")]
|
||||
public async Task<ActionResult> MarkRead(MarkReadDto markReadDto)
|
||||
{
|
||||
@ -204,7 +209,7 @@ namespace API.Controllers
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Marks a Series as Unread (progress)
|
||||
/// Marks a Series as Unread. All volumes and chapters will be marked as unread during this process.
|
||||
/// </summary>
|
||||
/// <param name="markReadDto"></param>
|
||||
/// <returns></returns>
|
||||
|
@ -1,26 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Comparators;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using API.Services;
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// All APIs are for Tachiyomi extension and app. They have hacks for our implementation and should not be used for any
|
||||
/// other purposes.
|
||||
/// </summary>
|
||||
public class TachiyomiController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IReaderService _readerService;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public TachiyomiController(IUnitOfWork unitOfWork, IReaderService readerService)
|
||||
public TachiyomiController(IUnitOfWork unitOfWork, IReaderService readerService, IMapper mapper)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_readerService = readerService;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given the series Id, this should return the latest chapter that has been fully read.
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns>ChapterDTO of latest chapter. Only Chapter number is used by consuming app. All other fields may be missing.</returns>
|
||||
[HttpGet("latest-chapter")]
|
||||
public async Task<ActionResult<ChapterDto>> GetLatestChapter(int seriesId)
|
||||
{
|
||||
@ -31,10 +46,45 @@ public class TachiyomiController : BaseApiController
|
||||
var prevChapterId =
|
||||
await _readerService.GetPrevChapterIdAsync(seriesId, currentChapter.VolumeId, currentChapter.Id, userId);
|
||||
|
||||
if (prevChapterId == -1) return null;
|
||||
// If prevChapterId is -1, this means either nothing is read or everything is read.
|
||||
if (prevChapterId == -1)
|
||||
{
|
||||
var userWithProgress = await _unitOfWork.UserRepository.GetUserByIdAsync(userId, AppUserIncludes.Progress);
|
||||
var userHasProgress =
|
||||
userWithProgress.Progresses.Any(x => x.SeriesId == seriesId);
|
||||
|
||||
// If the user doesn't have progress, then return null, which the extension will catch as 204 (no content) and report nothing as read
|
||||
if (!userHasProgress) return null;
|
||||
|
||||
// Else return the max chapter to Tachiyomi so it can consider everything read
|
||||
var volumes = (await _unitOfWork.VolumeRepository.GetVolumes(seriesId)).ToImmutableList();
|
||||
var looseLeafChapterVolume = volumes.FirstOrDefault(v => v.Number == 0);
|
||||
if (looseLeafChapterVolume == null)
|
||||
{
|
||||
var volumeChapter = _mapper.Map<ChapterDto>(volumes.Last().Chapters.OrderBy(c => float.Parse(c.Number), new ChapterSortComparerZeroFirst()).Last());
|
||||
return Ok(new ChapterDto()
|
||||
{
|
||||
Number = $"{int.Parse(volumeChapter.Number) / 100f}"
|
||||
});
|
||||
}
|
||||
|
||||
var lastChapter = looseLeafChapterVolume.Chapters.OrderBy(c => float.Parse(c.Number), new ChapterSortComparer()).Last();
|
||||
return Ok(_mapper.Map<ChapterDto>(lastChapter));
|
||||
}
|
||||
|
||||
// There is progress, we now need to figure out the highest volume or chapter and return that.
|
||||
var prevChapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(prevChapterId);
|
||||
var volumeWithProgress = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(prevChapter.VolumeId, userId);
|
||||
if (volumeWithProgress.Number != 0)
|
||||
{
|
||||
// The progress is on a volume, encode it as a fake chapterDTO
|
||||
return Ok(new ChapterDto()
|
||||
{
|
||||
Number = $"{volumeWithProgress.Number / 100f}"
|
||||
});
|
||||
}
|
||||
|
||||
// Progress is just on a chapter, return as is
|
||||
return Ok(prevChapter);
|
||||
}
|
||||
|
||||
@ -51,8 +101,9 @@ public class TachiyomiController : BaseApiController
|
||||
|
||||
switch (chapterNumber)
|
||||
{
|
||||
// Tachiyomi sends chapter 0.0f when there's no chapters read.
|
||||
// Due to the encoding for volumes this marks all chapters in volume 0 (loose chapters) as read so we ignore it
|
||||
// When Tachiyomi sync's progress, if there is no current progress in Tachiyomi, 0.0f is sent.
|
||||
// Due to the encoding for volumes, this marks all chapters in volume 0 (loose chapters) as read.
|
||||
// Hence we catch and return early, so we ignore the request.
|
||||
case 0.0f:
|
||||
return true;
|
||||
case < 1.0f:
|
||||
|
@ -95,6 +95,7 @@ namespace API.Controllers
|
||||
existingPreferences.BookReaderLayoutMode = preferencesDto.BookReaderLayoutMode;
|
||||
existingPreferences.BookReaderImmersiveMode = preferencesDto.BookReaderImmersiveMode;
|
||||
existingPreferences.GlobalPageLayoutMode = preferencesDto.GlobalPageLayoutMode;
|
||||
existingPreferences.BlurUnreadSummaries = preferencesDto.BlurUnreadSummaries;
|
||||
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
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using API.Entities.Enums;
|
||||
|
||||
namespace API.DTOs
|
||||
{
|
||||
@ -6,6 +7,7 @@ namespace API.DTOs
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; }
|
||||
public LibraryType Type { get; set; }
|
||||
public IEnumerable<string> Folders { get; init; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,5 +88,10 @@ namespace API.DTOs
|
||||
/// </summary>
|
||||
/// <remarks>Defaults to Cards</remarks>
|
||||
public PageLayoutMode GlobalPageLayoutMode { get; set; } = PageLayoutMode.Cards;
|
||||
/// <summary>
|
||||
/// UI Site Global Setting: If unread summaries should be blurred until expanded or unless user has read it already
|
||||
/// </summary>
|
||||
/// <remarks>Defaults to false</remarks>
|
||||
public bool BlurUnreadSummaries { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
1573
API/Data/Migrations/20220625215526_BlurUnreadSummaries.Designer.cs
generated
Normal file
1573
API/Data/Migrations/20220625215526_BlurUnreadSummaries.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
API/Data/Migrations/20220625215526_BlurUnreadSummaries.cs
Normal file
26
API/Data/Migrations/20220625215526_BlurUnreadSummaries.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
public partial class BlurUnreadSummaries : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "BlurUnreadSummaries",
|
||||
table: "AppUserPreferences",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BlurUnreadSummaries",
|
||||
table: "AppUserPreferences");
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ namespace API.Data.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.5");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.6");
|
||||
|
||||
modelBuilder.Entity("API.Entities.AppRole", b =>
|
||||
{
|
||||
@ -170,6 +170,9 @@ namespace API.Data.Migrations
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValue("#000000");
|
||||
|
||||
b.Property<bool>("BlurUnreadSummaries")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("BookReaderFontFamily")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
@ -93,6 +93,11 @@ namespace API.Entities
|
||||
/// </summary>
|
||||
/// <remarks>Defaults to Cards</remarks>
|
||||
public PageLayoutMode GlobalPageLayoutMode { get; set; } = PageLayoutMode.Cards;
|
||||
/// <summary>
|
||||
/// UI Site Global Setting: If unread summaries should be blurred until expanded or unless user has read it already
|
||||
/// </summary>
|
||||
/// <remarks>Defaults to false</remarks>
|
||||
public bool BlurUnreadSummaries { get; set; } = false;
|
||||
|
||||
public AppUser AppUser { get; set; }
|
||||
public int AppUserId { get; set; }
|
||||
|
@ -585,8 +585,7 @@ namespace API.Services
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(series) && !string.IsNullOrEmpty(seriesIndex) &&
|
||||
(!string.IsNullOrEmpty(specialName) || groupPosition.Equals("series") || groupPosition.Equals("set")))
|
||||
if (!string.IsNullOrEmpty(series) && !string.IsNullOrEmpty(seriesIndex))
|
||||
{
|
||||
if (string.IsNullOrEmpty(specialName))
|
||||
{
|
||||
@ -606,7 +605,7 @@ namespace API.Services
|
||||
};
|
||||
|
||||
// Don't set titleSort if the book belongs to a group
|
||||
if (!string.IsNullOrEmpty(titleSort) && string.IsNullOrEmpty(seriesIndex))
|
||||
if (!string.IsNullOrEmpty(titleSort) && string.IsNullOrEmpty(seriesIndex) && (groupPosition.Equals("series") || groupPosition.Equals("set")))
|
||||
{
|
||||
info.SeriesSort = titleSort;
|
||||
}
|
||||
|
@ -74,18 +74,15 @@ public class TokenService : ITokenService
|
||||
var tokenContent = tokenHandler.ReadJwtToken(request.Token);
|
||||
var username = tokenContent.Claims.FirstOrDefault(q => q.Type == JwtRegisteredClaimNames.NameId)?.Value;
|
||||
var user = await _userManager.FindByNameAsync(username);
|
||||
if (user == null) return null; // This forces a logout
|
||||
var isValid = await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, "RefreshToken", request.RefreshToken);
|
||||
if (isValid)
|
||||
{
|
||||
return new TokenRequestDto()
|
||||
{
|
||||
Token = await CreateToken(user),
|
||||
RefreshToken = await CreateRefreshToken(user)
|
||||
};
|
||||
}
|
||||
|
||||
await _userManager.UpdateSecurityStampAsync(user);
|
||||
|
||||
return null;
|
||||
return new TokenRequestDto()
|
||||
{
|
||||
Token = await CreateToken(user),
|
||||
RefreshToken = await CreateRefreshToken(user)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -60,17 +60,16 @@ namespace API
|
||||
services.AddIdentityServices(_config);
|
||||
services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Kavita API", Version = "v1" });
|
||||
|
||||
c.SwaggerDoc("Kavita API", new OpenApiInfo()
|
||||
c.SwaggerDoc("v1", new OpenApiInfo()
|
||||
{
|
||||
Description = "Kavita provides a set of APIs that are authenticated by JWT. JWT token can be copied from local storage.",
|
||||
Title = "Kavita API",
|
||||
Version = "v1",
|
||||
});
|
||||
|
||||
|
||||
var filePath = Path.Combine(AppContext.BaseDirectory, "API.xml");
|
||||
c.IncludeXmlComments(filePath);
|
||||
c.IncludeXmlComments(filePath, true);
|
||||
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {
|
||||
In = ParameterLocation.Header,
|
||||
Description = "Please insert JWT with Bearer into field",
|
||||
@ -96,6 +95,19 @@ namespace API
|
||||
Description = "Local Server",
|
||||
Url = "http://localhost:5000/",
|
||||
});
|
||||
|
||||
c.AddServer(new OpenApiServer()
|
||||
{
|
||||
Url = "https://demo.kavitareader.com/",
|
||||
Description = "Kavita Demo"
|
||||
});
|
||||
|
||||
c.AddServer(new OpenApiServer()
|
||||
{
|
||||
Url = "http://" + GetLocalIpAddress() + ":5000/",
|
||||
Description = "Local IP"
|
||||
});
|
||||
|
||||
});
|
||||
services.AddResponseCompression(options =>
|
||||
{
|
||||
@ -229,9 +241,6 @@ namespace API
|
||||
ContentTypeProvider = new FileExtensionContentTypeProvider()
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
context.Response.GetTypedHeaders().CacheControl =
|
||||
|
@ -33,6 +33,7 @@ export interface Preferences {
|
||||
// Global
|
||||
theme: SiteTheme;
|
||||
globalPageLayoutMode: PageLayoutMode;
|
||||
blurUnreadSummaries: boolean;
|
||||
}
|
||||
|
||||
export const readingDirections = [{text: 'Left to Right', value: ReadingDirection.LeftToRight}, {text: 'Right to Left', value: ReadingDirection.RightToLeft}];
|
||||
|
@ -92,8 +92,9 @@ export class AccountService implements OnDestroy {
|
||||
this.themeService.setTheme(this.themeService.defaultTheme);
|
||||
}
|
||||
|
||||
this.currentUserSource.next(user);
|
||||
this.currentUser = user;
|
||||
this.currentUserSource.next(user);
|
||||
|
||||
if (this.currentUser !== undefined) {
|
||||
this.startRefreshTokenTimer();
|
||||
} else {
|
||||
|
@ -203,7 +203,6 @@ export class MessageHubService {
|
||||
});
|
||||
|
||||
this.hubConnection.on(EVENTS.UserUpdate, resp => {
|
||||
console.log('got UserUpdate', resp);
|
||||
this.messagesSource.next({
|
||||
event: EVENTS.UserUpdate,
|
||||
payload: resp.body as UserUpdateEvent
|
||||
|
@ -19,7 +19,7 @@
|
||||
<label for="library-type" class="form-label">Type</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="typeTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #typeTooltip>Library type determines how filenames are parsed and if the UI shows Chapters (Manga) vs Issues (Comics). Book work the same way as Manga but fall back to embedded data.</ng-template>
|
||||
<span class="visually-hidden" id="library-type-help">Library type determines how filenames are parsed and if the UI shows Chapters (Manga) vs Issues (Comics). Book work the same way as Manga but fall back to embedded data.</span>
|
||||
<select class="form-select" id="library-type" formControlName="type" [attr.disabled]="this.library" aria-describedby="library-type-help">
|
||||
<select class="form-select" id="library-type" formControlName="type" aria-describedby="library-type-help"> <!-- [attr.disabled]="this.library" -->
|
||||
<option [value]="i" *ngFor="let opt of libraryTypes; let i = index">{{opt}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -2,6 +2,7 @@ import { Component, Input, OnInit } from '@angular/core';
|
||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { ConfirmService } from 'src/app/shared/confirm.service';
|
||||
import { Library } from 'src/app/_models/library';
|
||||
import { LibraryService } from 'src/app/_services/library.service';
|
||||
import { SettingsService } from '../../settings.service';
|
||||
@ -28,7 +29,7 @@ export class LibraryEditorModalComponent implements OnInit {
|
||||
|
||||
|
||||
constructor(private modalService: NgbModal, private libraryService: LibraryService, public modal: NgbActiveModal, private settingService: SettingsService,
|
||||
private toastr: ToastrService) { }
|
||||
private toastr: ToastrService, private confirmService: ConfirmService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
@ -45,7 +46,7 @@ export class LibraryEditorModalComponent implements OnInit {
|
||||
this.madeChanges = true;
|
||||
}
|
||||
|
||||
submitLibrary() {
|
||||
async submitLibrary() {
|
||||
const model = this.libraryForm.value;
|
||||
model.folders = this.selectedFolders;
|
||||
|
||||
@ -57,6 +58,12 @@ export class LibraryEditorModalComponent implements OnInit {
|
||||
model.id = this.library.id;
|
||||
model.folders = model.folders.map((item: string) => item.startsWith('\\') ? item.substr(1, item.length) : item);
|
||||
model.type = parseInt(model.type, 10);
|
||||
|
||||
if (model.type !== this.library.type) {
|
||||
if (!await this.confirmService.confirm(`Changing library type will trigger a new scan with different parsing rules and may lead to
|
||||
series being re-created and hence you may loose progress and bookmarks. You should backup before you do this. Are you sure you want to continue?`)) return;
|
||||
}
|
||||
|
||||
this.libraryService.update(model).subscribe(() => {
|
||||
this.close(true);
|
||||
}, err => {
|
||||
|
@ -29,7 +29,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-sm-12 pe-2">
|
||||
<label for="backup-tasks" class="form-label">Backup Tasks</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="backupTasksTooltip" role="button" tabindex="0"></i>
|
||||
<label for="backup-tasks" class="form-label">Days of Backups</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="backupTasksTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #backupTasksTooltip>The number of backups to maintain. Default is 30, minumum is 1, maximum is 30.</ng-template>
|
||||
<span class="visually-hidden" id="backup-tasks-help">The number of backups to maintain. Default is 30, minumum is 1, maximum is 30.</span>
|
||||
<input id="backup-tasks" aria-describedby="backup-tasks-help" class="form-control" formControlName="totalBackups" type="number" step="1" min="1" max="30" onkeypress="return event.charCode >= 48 && event.charCode <= 57">
|
||||
@ -67,7 +67,7 @@
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="swagger-ui" class="form-label" aria-describedby="swaggerui-info">Expose Swagger UI</label>
|
||||
<p class="accent" id="swaggerui-info">Allows Swagger UI to be exposed via swagger/ on your server. Authentication is not required, but a valid JWT token is. Requires a restart to take effect.</p>
|
||||
<p class="accent" id="swaggerui-info">Allows Swagger UI to be exposed via swagger/ on your server. Authentication is not required, but a valid JWT token is. Requires a restart to take effect. Swagger is hosted on yourip:5000/swagger</p>
|
||||
<div class="form-check form-switch">
|
||||
<input id="swagger-ui" type="checkbox" class="form-check-input" formControlName="enableSwaggerUi" role="switch">
|
||||
<label for="swagger-ui" class="form-check-label">Enable Swagger UI</label>
|
||||
|
@ -28,7 +28,7 @@
|
||||
<h6 class="text-muted" [ngClass]="{'subtitle-with-actionables' : actions.length > 0}" style="font-size: 0.75rem" *ngIf="Title != '' && showTitle">{{Title}}</h6>
|
||||
<ng-container *ngIf="summary.length > 0">
|
||||
<div class="mt-2 ps-2">
|
||||
<app-read-more [text]="summary" [maxLength]="250"></app-read-more>
|
||||
<app-read-more [text]="summary" [blur]="pagesRead === 0 && blur" [maxLength]="250"></app-read-more>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="ps-2 d-none d-md-inline-block">
|
||||
|
@ -62,6 +62,10 @@ export class ListItemComponent implements OnInit {
|
||||
* Show's the title if avaible on entity
|
||||
*/
|
||||
@Input() showTitle: boolean = true;
|
||||
/**
|
||||
* Blur the summary for the list item
|
||||
*/
|
||||
@Input() blur: boolean = false;
|
||||
|
||||
@Output() read: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
<ng-container *ngIf="isAdmin">
|
||||
|
||||
<button type="button" class="btn btn-icon {{activeEvents > 0 ? 'colored' : ''}}"
|
||||
[ngbPopover]="popContent" title="Activity" placement="bottom" [popoverClass]="'nav-events'" [autoClose]="'outside'">
|
||||
<i aria-hidden="true" class="fa fa-wave-square nav"></i>
|
||||
</button>
|
||||
<ng-container *ngIf="errors$ | async as errors">
|
||||
<button type="button" class="btn btn-icon" [ngClass]="{'colored': activeEvents > 0, 'colored-error': errors.length > 0}"
|
||||
[ngbPopover]="popContent" title="Activity" placement="bottom" [popoverClass]="'nav-events'" [autoClose]="'outside'">
|
||||
<i aria-hidden="true" class="fa fa-wave-square nav"></i>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<ng-template #popContent>
|
||||
<ul class="list-group list-group-flush dark-menu">
|
||||
|
@ -66,6 +66,11 @@
|
||||
border-radius: 60px;
|
||||
}
|
||||
|
||||
.colored-error {
|
||||
background-color: var(--error-color) !important;
|
||||
border-radius: 60px;
|
||||
}
|
||||
|
||||
.update-available {
|
||||
cursor: pointer;
|
||||
|
||||
|
@ -96,7 +96,7 @@
|
||||
</div>
|
||||
<div class="col-auto ms-2">
|
||||
<ngb-rating class="rating-star" [(rate)]="series!.userRating" (rateChange)="updateRating($event)" (click)="promptToReview()"></ngb-rating>
|
||||
<button *ngIf="series?.userRating || series.userRating" class="btn btn-sm btn-icon" (click)="openReviewModal(true)" placement="top" ngbTooltip="Edit Review" attr.aria-label="Edit Review"><i class="fa fa-pen" aria-hidden="true"></i></button>
|
||||
<button *ngIf="series?.userRating || series.userRating" class="btn btn-sm btn-icon" (click)="openReviewModal(true)" placement="bottom" ngbTooltip="Edit Review" attr.aria-label="Edit Review"><i class="fa fa-pen" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-0">
|
||||
@ -142,7 +142,8 @@
|
||||
<app-list-item [imageUrl]="imageService.getVolumeCoverImage(item.volume.id)"
|
||||
[seriesName]="series.name" [entity]="item.volume" *ngIf="item.volume.number != 0"
|
||||
[actions]="volumeActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
|
||||
[pagesRead]="item.volume.pagesRead" [totalPages]="item.volume.pages" (read)="openVolume(item.volume)">
|
||||
[pagesRead]="item.volume.pagesRead" [totalPages]="item.volume.pages" (read)="openVolume(item.volume)"
|
||||
[blur]="user?.preferences?.blurUnreadSummaries || false">
|
||||
<ng-container title>
|
||||
<app-entity-title [libraryType]="libraryType" [entity]="item.volume" [seriesName]="series.name" [prioritizeTitleName]="false"></app-entity-title>
|
||||
</ng-container>
|
||||
@ -152,7 +153,8 @@
|
||||
<app-list-item [imageUrl]="imageService.getChapterCoverImage(item.chapter.id)"
|
||||
[seriesName]="series.name" [entity]="item.chapter" *ngIf="!item.chapter.isSpecial"
|
||||
[actions]="chapterActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
|
||||
[pagesRead]="item.chapter.pagesRead" [totalPages]="item.chapter.pages" (read)="openChapter(item.chapter)">
|
||||
[pagesRead]="item.chapter.pagesRead" [totalPages]="item.chapter.pages" (read)="openChapter(item.chapter)"
|
||||
[blur]="user?.preferences?.blurUnreadSummaries || false">
|
||||
<ng-container title>
|
||||
<app-entity-title [libraryType]="libraryType" [entity]="item.chapter" [seriesName]="series.name" [prioritizeTitleName]="false"></app-entity-title>
|
||||
</ng-container>
|
||||
@ -185,7 +187,8 @@
|
||||
<app-list-item [imageUrl]="imageService.getVolumeCoverImage(volume.id)"
|
||||
[seriesName]="series.name" [entity]="volume" *ngIf="volume.number != 0"
|
||||
[actions]="volumeActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
|
||||
[pagesRead]="volume.pagesRead" [totalPages]="volume.pages" (read)="openVolume(volume)">
|
||||
[pagesRead]="volume.pagesRead" [totalPages]="volume.pages" (read)="openVolume(volume)"
|
||||
[blur]="user?.preferences?.blurUnreadSummaries || false">
|
||||
<ng-container title>
|
||||
<app-entity-title [libraryType]="libraryType" [entity]="volume" [seriesName]="series.name"></app-entity-title>
|
||||
</ng-container>
|
||||
@ -222,7 +225,7 @@
|
||||
[seriesName]="series.name" [entity]="chapter" *ngIf="!chapter.isSpecial"
|
||||
[actions]="chapterActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
|
||||
[pagesRead]="chapter.pagesRead" [totalPages]="chapter.pages" (read)="openChapter(chapter)"
|
||||
[includeVolume]="true">
|
||||
[includeVolume]="true" [blur]="user?.preferences?.blurUnreadSummaries || false">
|
||||
<ng-container title>
|
||||
<app-entity-title [libraryType]="libraryType" [entity]="chapter" [seriesName]="series.name" [includeVolume]="true" [prioritizeTitleName]="false"></app-entity-title>
|
||||
</ng-container>
|
||||
@ -254,7 +257,8 @@
|
||||
<app-list-item [imageUrl]="imageService.getChapterCoverImage(chapter.id)"
|
||||
[seriesName]="series.name" [entity]="chapter"
|
||||
[actions]="chapterActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
|
||||
[pagesRead]="chapter.pagesRead" [totalPages]="chapter.pages" (read)="openChapter(chapter)">
|
||||
[pagesRead]="chapter.pagesRead" [totalPages]="chapter.pages" (read)="openChapter(chapter)"
|
||||
[blur]="user?.preferences?.blurUnreadSummaries || false">
|
||||
<ng-container title>
|
||||
{{chapter.title || chapter.range}}
|
||||
</ng-container>
|
||||
@ -287,4 +291,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -38,6 +38,7 @@ import { CardDetailDrawerComponent } from '../cards/card-detail-drawer/card-deta
|
||||
import { FormControl, FormGroup } from '@angular/forms';
|
||||
import { PageLayoutMode } from '../_models/page-layout-mode';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { User } from '../_models/user';
|
||||
|
||||
interface RelatedSeris {
|
||||
series: Series;
|
||||
@ -154,6 +155,7 @@ export class SeriesDetailComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
});
|
||||
|
||||
isAscendingSort: boolean = false; // TODO: Get this from User preferences
|
||||
user: User | undefined;
|
||||
|
||||
bulkActionCallback = (action: Action, data: any) => {
|
||||
if (this.series === undefined) {
|
||||
@ -252,6 +254,7 @@ export class SeriesDetailComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
|
||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||
if (user) {
|
||||
this.user = user;
|
||||
this.isAdmin = this.accountService.hasAdminRole(user);
|
||||
this.hasDownloadingRole = this.accountService.hasDownloadRole(user);
|
||||
this.renderMode = user.preferences.globalPageLayoutMode;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div>
|
||||
<span [innerHTML]="currentText | safeHtml"></span>
|
||||
<span [innerHTML]="currentText | safeHtml" [ngClass]="{'blur-text': blur && isCollapsed}"></span>
|
||||
<a [class.hidden]="hideToggle" *ngIf="text && text.length > maxLength" class="read-more-link" (click)="toggleView()">
|
||||
<i aria-hidden="true" class="fa fa-caret-{{isCollapsed ? 'down' : 'up'}}"></i> Read {{isCollapsed ? 'More' : 'Less'}}
|
||||
</a>
|
||||
|
@ -1,6 +1,4 @@
|
||||
// .read-more-link {
|
||||
// font-weight: bold;
|
||||
// text-decoration: none;
|
||||
// cursor: pointer;
|
||||
// color: var(--body-text-color) !important;
|
||||
// }
|
||||
.blur-text {
|
||||
color: transparent;
|
||||
text-shadow: 0 0 5px var(--body-text-color);
|
||||
}
|
@ -9,6 +9,10 @@ export class ReadMoreComponent implements OnChanges {
|
||||
|
||||
@Input() text!: string;
|
||||
@Input() maxLength: number = 250;
|
||||
/**
|
||||
* If the field is collapsed and blur true, text will not be readable
|
||||
*/
|
||||
@Input() blur: boolean = false;
|
||||
currentText!: string;
|
||||
hideToggle: boolean = true;
|
||||
|
||||
|
@ -34,6 +34,16 @@
|
||||
<option *ngFor="let opt of pageLayoutModes" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="auto-close" role="switch" formControlName="blurUnreadSummaries" class="form-check-input" aria-describedby="blurUnreadSummaries" [value]="true" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="auto-close">Blur Unread Summaries</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="blurUnreadSummariesTooltip" role="button" tabindex="0"></i>
|
||||
</div>
|
||||
|
||||
<ng-template #blurUnreadSummariesTooltip>Blurs summary text on volumes or chapters that have no read progress (to avoid spoilers)</ng-template>
|
||||
<span class="visually-hidden" id="settings-global-blurUnreadSummaries-help">Blurs summary text on volumes or chapters that have no read progress (to avoid spoilers)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end mb-3">
|
||||
|
@ -123,6 +123,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.settingsForm.addControl('theme', new FormControl(this.user.preferences.theme, []));
|
||||
this.settingsForm.addControl('globalPageLayoutMode', new FormControl(this.user.preferences.globalPageLayoutMode, []));
|
||||
this.settingsForm.addControl('blurUnreadSummaries', new FormControl(this.user.preferences.blurUnreadSummaries, []));
|
||||
});
|
||||
|
||||
this.passwordChangeForm.addControl('password', new FormControl('', [Validators.required]));
|
||||
@ -169,6 +170,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
||||
this.settingsForm.get('theme')?.setValue(this.user.preferences.theme);
|
||||
this.settingsForm.get('bookReaderImmersiveMode')?.setValue(this.user.preferences.bookReaderImmersiveMode);
|
||||
this.settingsForm.get('globalPageLayoutMode')?.setValue(this.user.preferences.globalPageLayoutMode);
|
||||
this.settingsForm.get('blurUnreadSummaries')?.setValue(this.user.preferences.blurUnreadSummaries);
|
||||
}
|
||||
|
||||
resetPasswordForm() {
|
||||
@ -200,6 +202,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
||||
theme: modelSettings.theme,
|
||||
bookReaderImmersiveMode: modelSettings.bookReaderImmersiveMode,
|
||||
globalPageLayoutMode: parseInt(modelSettings.globalPageLayoutMode, 10),
|
||||
blurUnreadSummaries: modelSettings.blurUnreadSummaries,
|
||||
};
|
||||
|
||||
this.obserableHandles.push(this.accountService.updatePreferences(data).subscribe((updatedPrefs) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user