mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Security Hotfix (#1415)
* Updated ngx-extended-pdf-viewer to 14.5.2 + misc security vuln * Hooked up remove from want to read AND fixed a bug in the logic that was removing everything BUT what was passed. Allow for bookmarks to have date info for better ordering. * Implemented a quick way to set darkneses level on manga reader for when nightlight just isn't dark enough * Added Japanese Series name support in the Parser * Updated our security file with our Huntr. * Fixed a security vulnerability where through the API, an unauthorized user could delete/modify reading lists that did not belong to them. Fixed a bug where when creating a reading list with the name of another users, the API would throw an exception (but reading list would still get created) * Ensure all reading list apis are authorized * Ensured all APIs require authentication, except those that explicitly don't. All APIs are default requiring Authentication. Fixed a security vulnerability which would allow a user to take over an admin account. * Fixed a bug where cover-upload would accept filenames that were not expected. * Explicitly check that a user has access to the pdf file before we serve it back. * Enabled lock out when invalid user auth occurs. After 5 invalid auths, the user account will be locked out for 10 mins.
This commit is contained in:
parent
331e0d0ca9
commit
88b5ebeb69
@ -178,6 +178,8 @@ namespace API.Tests.Parser
|
||||
[InlineData("Zettai Karen Children v02 c003 - The Invisible Guardian (2) [JS Scans]", "Zettai Karen Children")]
|
||||
[InlineData("My Charms Are Wasted on Kuroiwa Medaka - Ch. 37.5 - Volume Extras", "My Charms Are Wasted on Kuroiwa Medaka")]
|
||||
[InlineData("Highschool of the Dead - Full Color Edition v02 [Uasaha] (Yen Press)", "Highschool of the Dead - Full Color Edition")]
|
||||
[InlineData("諌山創] 進撃の巨人 第23巻", "諌山創] 進撃の巨人")]
|
||||
[InlineData("(一般コミック) [奥浩哉] いぬやしき 第09巻", "いぬやしき")]
|
||||
public void ParseSeriesTest(string filename, string expected)
|
||||
{
|
||||
Assert.Equal(expected, API.Parser.Parser.ParseSeries(filename));
|
||||
|
@ -70,13 +70,21 @@ namespace API.Controllers
|
||||
/// </summary>
|
||||
/// <param name="resetPasswordDto"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost("reset-password")]
|
||||
public async Task<ActionResult> UpdatePassword(ResetPasswordDto resetPasswordDto)
|
||||
{
|
||||
// TODO: Log this request to Audit Table
|
||||
_logger.LogInformation("{UserName} is changing {ResetUser}'s password", User.GetUsername(), resetPasswordDto.UserName);
|
||||
var user = await _userManager.Users.SingleAsync(x => x.UserName == resetPasswordDto.UserName);
|
||||
|
||||
if (resetPasswordDto.UserName != User.GetUsername() && !(User.IsInRole(PolicyConstants.AdminRole) || User.IsInRole(PolicyConstants.ChangePasswordRole)))
|
||||
var user = await _userManager.Users.SingleOrDefaultAsync(x => x.UserName == resetPasswordDto.UserName);
|
||||
if (user == null) return Ok(); // Don't report BadRequest as that would allow brute forcing to find accounts on system
|
||||
|
||||
|
||||
if (resetPasswordDto.UserName == User.GetUsername() && !(User.IsInRole(PolicyConstants.ChangePasswordRole) || User.IsInRole(PolicyConstants.AdminRole)))
|
||||
return Unauthorized("You are not permitted to this operation.");
|
||||
|
||||
if (resetPasswordDto.UserName != User.GetUsername() && !User.IsInRole(PolicyConstants.AdminRole))
|
||||
return Unauthorized("You are not permitted to this operation.");
|
||||
|
||||
var errors = await _accountService.ChangeUserPassword(user, resetPasswordDto.Password);
|
||||
@ -94,6 +102,7 @@ namespace API.Controllers
|
||||
/// </summary>
|
||||
/// <param name="registerDto"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost("register")]
|
||||
public async Task<ActionResult<UserDto>> RegisterFirstUser(RegisterDto registerDto)
|
||||
{
|
||||
@ -158,6 +167,7 @@ namespace API.Controllers
|
||||
/// </summary>
|
||||
/// <param name="loginDto"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost("login")]
|
||||
public async Task<ActionResult<UserDto>> Login(LoginDto loginDto)
|
||||
{
|
||||
@ -176,13 +186,13 @@ namespace API.Controllers
|
||||
"You are missing an email on your account. Please wait while we migrate your account.");
|
||||
}
|
||||
|
||||
if (!validPassword)
|
||||
{
|
||||
return Unauthorized("Your credentials are not correct");
|
||||
}
|
||||
|
||||
var result = await _signInManager
|
||||
.CheckPasswordSignInAsync(user, loginDto.Password, false);
|
||||
.CheckPasswordSignInAsync(user, loginDto.Password, true);
|
||||
|
||||
if (result.IsLockedOut)
|
||||
{
|
||||
return Unauthorized("You've been locked out from too many authorization attempts. Please wait 10 minutes.");
|
||||
}
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
@ -215,6 +225,7 @@ namespace API.Controllers
|
||||
/// </summary>
|
||||
/// <param name="tokenRequestDto"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost("refresh-token")]
|
||||
public async Task<ActionResult<TokenRequestDto>> RefreshToken([FromBody] TokenRequestDto tokenRequestDto)
|
||||
{
|
||||
@ -486,6 +497,7 @@ namespace API.Controllers
|
||||
return BadRequest("There was an error setting up your account. Please check the logs");
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("confirm-email")]
|
||||
public async Task<ActionResult<UserDto>> ConfirmEmail(ConfirmEmailDto dto)
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using API.Entities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@ -18,6 +19,7 @@ namespace API.Controllers
|
||||
/// Checks if an admin exists on the system. This is essentially a check to validate if the system has been setup.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("exists")]
|
||||
public async Task<ActionResult<bool>> AdminExists()
|
||||
{
|
||||
|
@ -1,9 +1,11 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class BaseApiController : ControllerBase
|
||||
{
|
||||
}
|
||||
|
@ -1,24 +1,26 @@
|
||||
using System.IO;
|
||||
using API.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers
|
||||
namespace API.Controllers;
|
||||
|
||||
[AllowAnonymous]
|
||||
public class FallbackController : Controller
|
||||
{
|
||||
public class FallbackController : Controller
|
||||
// ReSharper disable once S4487
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private readonly ITaskScheduler _taskScheduler;
|
||||
|
||||
public FallbackController(ITaskScheduler taskScheduler)
|
||||
{
|
||||
// ReSharper disable once S4487
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private readonly ITaskScheduler _taskScheduler;
|
||||
// This is used to load TaskScheduler on startup without having to navigate to a Controller that uses.
|
||||
_taskScheduler = taskScheduler;
|
||||
}
|
||||
|
||||
public FallbackController(ITaskScheduler taskScheduler)
|
||||
{
|
||||
// This is used to load TaskScheduler on startup without having to navigate to a Controller that uses.
|
||||
_taskScheduler = taskScheduler;
|
||||
}
|
||||
|
||||
public ActionResult Index()
|
||||
{
|
||||
return PhysicalFile(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "index.html"), "text/HTML");
|
||||
}
|
||||
public ActionResult Index()
|
||||
{
|
||||
return PhysicalFile(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "index.html"), "text/HTML");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,6 +138,8 @@ namespace API.Controllers
|
||||
[ResponseCache(Duration = ImageCacheSeconds, Location = ResponseCacheLocation.Client, NoStore = false)]
|
||||
public ActionResult GetCoverUploadImage(string filename)
|
||||
{
|
||||
if (filename.Contains("..")) return BadRequest("Invalid Filename");
|
||||
|
||||
var path = Path.Join(_directoryService.TempDirectory, filename);
|
||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"File does not exist");
|
||||
var format = _directoryService.FileSystem.Path.GetExtension(path).Replace(".", "");
|
||||
|
@ -17,10 +17,12 @@ using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Services;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
[AllowAnonymous]
|
||||
public class OpdsController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
@ -53,6 +53,11 @@ namespace API.Controllers
|
||||
var chapter = await _cacheService.Ensure(chapterId);
|
||||
if (chapter == null) return BadRequest("There was an issue finding pdf file for reading");
|
||||
|
||||
// Validate the user has access to the PDF
|
||||
var series = await _unitOfWork.SeriesRepository.GetSeriesForChapter(chapter.Id,
|
||||
await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()));
|
||||
if (series == null) return BadRequest("Invalid Access");
|
||||
|
||||
try
|
||||
{
|
||||
var path = _cacheService.GetCachedFile(chapter);
|
||||
|
@ -3,15 +3,18 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Comparators;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs.ReadingLists;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.SignalR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
public class ReadingListController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
@ -75,6 +78,18 @@ namespace API.Controllers
|
||||
return Ok(items);
|
||||
}
|
||||
|
||||
private async Task<AppUser?> UserHasReadingListAccess(int readingListId)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(),
|
||||
AppUserIncludes.ReadingLists);
|
||||
if (user.ReadingLists.SingleOrDefault(rl => rl.Id == readingListId) == null && !await _unitOfWork.UserRepository.IsUserAdminAsync(user))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an items position
|
||||
/// </summary>
|
||||
@ -84,6 +99,11 @@ namespace API.Controllers
|
||||
public async Task<ActionResult> UpdateListItemPosition(UpdateReadingListPosition dto)
|
||||
{
|
||||
// Make sure UI buffers events
|
||||
var user = await UserHasReadingListAccess(dto.ReadingListId);
|
||||
if (user == null)
|
||||
{
|
||||
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
|
||||
}
|
||||
var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemsByIdAsync(dto.ReadingListId)).ToList();
|
||||
var item = items.Find(r => r.Id == dto.ReadingListItemId);
|
||||
items.Remove(item);
|
||||
@ -110,10 +130,15 @@ namespace API.Controllers
|
||||
[HttpPost("delete-item")]
|
||||
public async Task<ActionResult> DeleteListItem(UpdateReadingListPosition dto)
|
||||
{
|
||||
var user = await UserHasReadingListAccess(dto.ReadingListId);
|
||||
if (user == null)
|
||||
{
|
||||
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
|
||||
}
|
||||
|
||||
var readingList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(dto.ReadingListId);
|
||||
readingList.Items = readingList.Items.Where(r => r.Id != dto.ReadingListItemId).ToList();
|
||||
|
||||
|
||||
var index = 0;
|
||||
foreach (var readingListItem in readingList.Items)
|
||||
{
|
||||
@ -139,9 +164,14 @@ namespace API.Controllers
|
||||
[HttpPost("remove-read")]
|
||||
public async Task<ActionResult> DeleteReadFromList([FromQuery] int readingListId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var items = await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, userId);
|
||||
items = await _unitOfWork.ReadingListRepository.AddReadingProgressModifiers(userId, items.ToList());
|
||||
var user = await UserHasReadingListAccess(readingListId);
|
||||
if (user == null)
|
||||
{
|
||||
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
|
||||
}
|
||||
|
||||
var items = await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, user.Id);
|
||||
items = await _unitOfWork.ReadingListRepository.AddReadingProgressModifiers(user.Id, items.ToList());
|
||||
|
||||
// Collect all Ids to remove
|
||||
var itemIdsToRemove = items.Where(item => item.PagesRead == item.PagesTotal).Select(item => item.Id);
|
||||
@ -174,15 +204,13 @@ namespace API.Controllers
|
||||
[HttpDelete]
|
||||
public async Task<ActionResult> DeleteList([FromQuery] int readingListId)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
|
||||
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
|
||||
var readingList = user.ReadingLists.SingleOrDefault(r => r.Id == readingListId);
|
||||
if (readingList == null && !isAdmin)
|
||||
var user = await UserHasReadingListAccess(readingListId);
|
||||
if (user == null)
|
||||
{
|
||||
return BadRequest("User is not associated with this reading list");
|
||||
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
|
||||
}
|
||||
|
||||
readingList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(readingListId);
|
||||
var readingList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(readingListId);
|
||||
|
||||
user.ReadingLists.Remove(readingList);
|
||||
|
||||
@ -211,13 +239,14 @@ namespace API.Controllers
|
||||
return BadRequest("A list of this name already exists");
|
||||
}
|
||||
|
||||
user.ReadingLists.Add(DbFactory.ReadingList(dto.Title, string.Empty, false));
|
||||
var readingList = DbFactory.ReadingList(dto.Title, string.Empty, false);
|
||||
user.ReadingLists.Add(readingList);
|
||||
|
||||
if (!_unitOfWork.HasChanges()) return BadRequest("There was a problem creating list");
|
||||
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
return Ok(await _unitOfWork.ReadingListRepository.GetReadingListDtoByTitleAsync(dto.Title));
|
||||
return Ok(await _unitOfWork.ReadingListRepository.GetReadingListDtoByTitleAsync(user.Id, dto.Title));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -231,7 +260,11 @@ namespace API.Controllers
|
||||
var readingList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(dto.ReadingListId);
|
||||
if (readingList == null) return BadRequest("List does not exist");
|
||||
|
||||
|
||||
var user = await UserHasReadingListAccess(readingList.Id);
|
||||
if (user == null)
|
||||
{
|
||||
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(dto.Title))
|
||||
{
|
||||
@ -275,7 +308,12 @@ namespace API.Controllers
|
||||
[HttpPost("update-by-series")]
|
||||
public async Task<ActionResult> UpdateListBySeries(UpdateReadingListBySeriesDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
|
||||
var user = await UserHasReadingListAccess(dto.ReadingListId);
|
||||
if (user == null)
|
||||
{
|
||||
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
|
||||
}
|
||||
|
||||
var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
|
||||
if (readingList == null) return BadRequest("Reading List does not exist");
|
||||
var chapterIdsForSeries =
|
||||
@ -312,7 +350,11 @@ namespace API.Controllers
|
||||
[HttpPost("update-by-multiple")]
|
||||
public async Task<ActionResult> UpdateListByMultiple(UpdateReadingListByMultipleDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
|
||||
var user = await UserHasReadingListAccess(dto.ReadingListId);
|
||||
if (user == null)
|
||||
{
|
||||
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
|
||||
}
|
||||
var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
|
||||
if (readingList == null) return BadRequest("Reading List does not exist");
|
||||
|
||||
@ -352,7 +394,11 @@ namespace API.Controllers
|
||||
[HttpPost("update-by-multiple-series")]
|
||||
public async Task<ActionResult> UpdateListByMultipleSeries(UpdateReadingListByMultipleSeriesDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
|
||||
var user = await UserHasReadingListAccess(dto.ReadingListId);
|
||||
if (user == null)
|
||||
{
|
||||
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
|
||||
}
|
||||
var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
|
||||
if (readingList == null) return BadRequest("Reading List does not exist");
|
||||
|
||||
@ -386,9 +432,14 @@ namespace API.Controllers
|
||||
[HttpPost("update-by-volume")]
|
||||
public async Task<ActionResult> UpdateListByVolume(UpdateReadingListByVolumeDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
|
||||
var user = await UserHasReadingListAccess(dto.ReadingListId);
|
||||
if (user == null)
|
||||
{
|
||||
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
|
||||
}
|
||||
var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
|
||||
if (readingList == null) return BadRequest("Reading List does not exist");
|
||||
|
||||
var chapterIdsForVolume =
|
||||
(await _unitOfWork.ChapterRepository.GetChaptersAsync(dto.VolumeId)).Select(c => c.Id).ToList();
|
||||
|
||||
@ -417,7 +468,11 @@ namespace API.Controllers
|
||||
[HttpPost("update-by-chapter")]
|
||||
public async Task<ActionResult> UpdateListByChapter(UpdateReadingListByChapterDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
|
||||
var user = await UserHasReadingListAccess(dto.ReadingListId);
|
||||
if (user == null)
|
||||
{
|
||||
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
|
||||
}
|
||||
var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
|
||||
if (readingList == null) return BadRequest("Reading List does not exist");
|
||||
|
||||
|
@ -24,6 +24,7 @@ public class ThemeController : BaseApiController
|
||||
_taskScheduler = taskScheduler;
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IEnumerable<SiteThemeDto>>> GetThemes()
|
||||
{
|
||||
|
@ -59,6 +59,8 @@ namespace API.Controllers
|
||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path))
|
||||
return BadRequest($"Could not download file");
|
||||
|
||||
if (!await _imageService.IsImage(path)) return BadRequest("Url does not return a valid image");
|
||||
|
||||
return $"coverupload_{dateString}.{format}";
|
||||
}
|
||||
catch (FlurlHttpException ex)
|
||||
|
@ -80,7 +80,7 @@ public class WantToReadController : BaseApiController
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(),
|
||||
AppUserIncludes.WantToRead);
|
||||
|
||||
user.WantToRead = user.WantToRead.Where(s => @dto.SeriesIds.Contains(s.Id)).ToList();
|
||||
user.WantToRead = user.WantToRead.Where(s => !dto.SeriesIds.Contains(s.Id)).ToList();
|
||||
|
||||
if (!_unitOfWork.HasChanges()) return Ok();
|
||||
if (await _unitOfWork.CommitAsync()) return Ok();
|
||||
|
1596
API/Data/Migrations/20220802222910_BookmarkHasDate.Designer.cs
generated
Normal file
1596
API/Data/Migrations/20220802222910_BookmarkHasDate.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
API/Data/Migrations/20220802222910_BookmarkHasDate.cs
Normal file
38
API/Data/Migrations/20220802222910_BookmarkHasDate.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
public partial class BookmarkHasDate : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "Created",
|
||||
table: "AppUserBookmark",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastModified",
|
||||
table: "AppUserBookmark",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Created",
|
||||
table: "AppUserBookmark");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastModified",
|
||||
table: "AppUserBookmark");
|
||||
}
|
||||
}
|
||||
}
|
@ -137,9 +137,15 @@ namespace API.Data.Migrations
|
||||
b.Property<int>("ChapterId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Page")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
@ -17,7 +17,7 @@ public interface IReadingListRepository
|
||||
Task<IEnumerable<ReadingListItemDto>> GetReadingListItemDtosByIdAsync(int readingListId, int userId);
|
||||
Task<ReadingListDto> GetReadingListDtoByIdAsync(int readingListId, int userId);
|
||||
Task<IEnumerable<ReadingListItemDto>> AddReadingProgressModifiers(int userId, IList<ReadingListItemDto> items);
|
||||
Task<ReadingListDto> GetReadingListDtoByTitleAsync(string title);
|
||||
Task<ReadingListDto> GetReadingListDtoByTitleAsync(int userId, string title);
|
||||
Task<IEnumerable<ReadingListItem>> GetReadingListItemsByIdAsync(int readingListId);
|
||||
|
||||
Task<IEnumerable<ReadingListDto>> GetReadingListDtosForSeriesAndUserAsync(int userId, int seriesId,
|
||||
@ -215,10 +215,10 @@ public class ReadingListRepository : IReadingListRepository
|
||||
return items;
|
||||
}
|
||||
|
||||
public async Task<ReadingListDto> GetReadingListDtoByTitleAsync(string title)
|
||||
public async Task<ReadingListDto> GetReadingListDtoByTitleAsync(int userId, string title)
|
||||
{
|
||||
return await _context.ReadingList
|
||||
.Where(r => r.Title.Equals(title))
|
||||
.Where(r => r.Title.Equals(title) && r.AppUserId == userId)
|
||||
.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
@ -224,6 +224,7 @@ public class UserRepository : IUserRepository
|
||||
{
|
||||
return await _context.AppUserBookmark
|
||||
.Where(b => bookmarkIds.Contains(b.Id))
|
||||
.OrderBy(b => b.Created)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using API.Entities.Interfaces;
|
||||
|
||||
namespace API.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a saved page in a Chapter entity for a given user.
|
||||
/// </summary>
|
||||
public class AppUserBookmark
|
||||
public class AppUserBookmark : IEntityDate
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int Page { get; set; }
|
||||
@ -23,5 +25,7 @@ namespace API.Entities
|
||||
[JsonIgnore]
|
||||
public AppUser AppUser { get; set; }
|
||||
public int AppUserId { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Text;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using API.Constants;
|
||||
using API.Data;
|
||||
@ -32,6 +33,11 @@ namespace API.Extensions
|
||||
opt.Password.RequiredLength = 6;
|
||||
|
||||
opt.SignIn.RequireConfirmedEmail = true;
|
||||
|
||||
opt.Lockout.AllowedForNewUsers = true;
|
||||
opt.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
|
||||
opt.Lockout.MaxFailedAccessAttempts = 5;
|
||||
|
||||
})
|
||||
.AddTokenProvider<DataProtectorTokenProvider<AppUser>>(TokenOptions.DefaultProvider)
|
||||
.AddRoles<AppRole>()
|
||||
|
@ -276,6 +276,10 @@ namespace API.Parser
|
||||
new Regex(
|
||||
@"^(?!Vol)(?<Series>.*)( |_|-)(ch?)\d+",
|
||||
MatchOptions, RegexTimeout),
|
||||
// Japanese Volume: n巻 -> Volume n
|
||||
new Regex(
|
||||
@"(?<Series>.+?)第(?<Volume>\d+(?:(\-)\d+)?)巻",
|
||||
MatchOptions, RegexTimeout),
|
||||
};
|
||||
|
||||
private static readonly Regex[] ComicSeriesRegex = new[]
|
||||
|
@ -28,6 +28,8 @@ public interface IImageService
|
||||
/// <param name="outputPath">Where to output the file</param>
|
||||
/// <returns>File of written webp image</returns>
|
||||
Task<string> ConvertToWebP(string filePath, string outputPath);
|
||||
|
||||
Task<bool> IsImage(string filePath);
|
||||
}
|
||||
|
||||
public class ImageService : IImageService
|
||||
@ -117,6 +119,23 @@ public class ImageService : IImageService
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
public async Task<bool> IsImage(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var info = await SixLabors.ImageSharp.Image.IdentifyAsync(filePath);
|
||||
if (info == null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
/* Swallow Exception */
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public string CreateThumbnailFromBase64(string encodedImage, string fileName)
|
||||
|
@ -21,12 +21,12 @@ your reading collection with your friends and family!
|
||||
- [x] Serve up Manga/Webtoons/Comics (cbr, cbz, zip/rar, 7zip, raw images) and Books (epub, pdf)
|
||||
- [x] First class responsive readers that work great on any device (phone, tablet, desktop)
|
||||
- [x] Dark mode and customizable theming support
|
||||
- [ ] Provide hooks into metadata providers to fetch metadata for Comics, Manga, and Books
|
||||
- [ ] Provide a plugin system to allow external metadata integration and scrobbling for read status, ratings, and reviews
|
||||
- [x] Metadata should allow for collections, want to read integration from 3rd party services, genres.
|
||||
- [x] Ability to manage users, access, and ratings
|
||||
- [ ] Ability to sync ratings and reviews to external services
|
||||
- [x] Fully Accessible with active accessibility audits
|
||||
- [x] Dedicated webtoon reading mode
|
||||
- [ ] Full localization support
|
||||
- [ ] And so much [more...](https://github.com/Kareadita/Kavita/projects)
|
||||
|
||||
## Support
|
||||
@ -93,6 +93,9 @@ Thank you to [<img src="/Logo/jetbrains.svg" alt="" width="32"> JetBrains](http:
|
||||
## Palace-Designs
|
||||
We would like to extend a big thank you to [<img src="/Logo/hosting-sponsor.png" alt="" width="128">](https://www.palace-designs.com/) who hosts our infrastructure pro-bono.
|
||||
|
||||
## Huntr
|
||||
We would like to extend a big thank you to [Huntr](https://huntr.dev/repos/kareadita/kavita) who has worked with Kavita in reporting security vulnerabilities. If you are interested in
|
||||
being paid to help secure Kavita, please give them a try.
|
||||
|
||||
### License
|
||||
|
||||
|
@ -6,6 +6,5 @@ Security is maintained on latest stable version only.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Use this section to tell people how to report a vulnerability.
|
||||
|
||||
Please reach out via majora2007@users.noreply.github.com or via our discord (majora2007)
|
||||
Please reach out to majora2007 via our Discord or you can (and should) report your vulnerability via [Huntr](https://huntr.dev/repos/kareadita/kavita).
|
||||
|
395
UI/Web/package-lock.json
generated
395
UI/Web/package-lock.json
generated
@ -267,6 +267,25 @@
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"terser": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
|
||||
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.7.2",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
|
||||
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1787,23 +1806,23 @@
|
||||
}
|
||||
},
|
||||
"@angular-devkit/architect": {
|
||||
"version": "0.1303.7",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.7.tgz",
|
||||
"integrity": "sha512-xr35v7AuJygRdiaFhgoBSLN2ZMUri8x8Qx9jkmCkD3WLKz33TSFyAyqwdNNmOO9riK8ePXMH/QcSv0wY12pFBw==",
|
||||
"version": "0.1303.9",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.9.tgz",
|
||||
"integrity": "sha512-RMHqCGDxbLqT+250A0a8vagsoTdqGjAxjhrvTeq7PJmClI7uJ/uA1Fs18+t85toIqVKn2hovdY9sNf42nBDD2Q==",
|
||||
"requires": {
|
||||
"@angular-devkit/core": "13.3.7",
|
||||
"@angular-devkit/core": "13.3.9",
|
||||
"rxjs": "6.6.7"
|
||||
}
|
||||
},
|
||||
"@angular-devkit/build-angular": {
|
||||
"version": "13.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.7.tgz",
|
||||
"integrity": "sha512-XUmiq/3zpuna+r0UOqNSvA9kEcPwsLblEmNLUYyZXL9v/aGWUHOSH0nhGVrNRrSud4ryklEnxfkxkxlZlT4mjQ==",
|
||||
"version": "13.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.9.tgz",
|
||||
"integrity": "sha512-1LqcMizeabx3yOkx3tptCSAoEhG6nO6hPgI/B3EJ07G/ZcoxunMWSeN3P3zT10dZMEHhcxl+8cSStSXaXj9hfA==",
|
||||
"requires": {
|
||||
"@ampproject/remapping": "2.2.0",
|
||||
"@angular-devkit/architect": "0.1303.7",
|
||||
"@angular-devkit/build-webpack": "0.1303.7",
|
||||
"@angular-devkit/core": "13.3.7",
|
||||
"@angular-devkit/architect": "0.1303.9",
|
||||
"@angular-devkit/build-webpack": "0.1303.9",
|
||||
"@angular-devkit/core": "13.3.9",
|
||||
"@babel/core": "7.16.12",
|
||||
"@babel/generator": "7.16.8",
|
||||
"@babel/helper-annotate-as-pure": "7.16.7",
|
||||
@ -1814,7 +1833,7 @@
|
||||
"@babel/runtime": "7.16.7",
|
||||
"@babel/template": "7.16.7",
|
||||
"@discoveryjs/json-ext": "0.5.6",
|
||||
"@ngtools/webpack": "13.3.7",
|
||||
"@ngtools/webpack": "13.3.9",
|
||||
"ansi-colors": "4.1.1",
|
||||
"babel-loader": "8.2.5",
|
||||
"babel-plugin-istanbul": "6.1.1",
|
||||
@ -1856,7 +1875,7 @@
|
||||
"source-map-support": "0.5.21",
|
||||
"stylus": "0.56.0",
|
||||
"stylus-loader": "6.2.0",
|
||||
"terser": "5.11.0",
|
||||
"terser": "5.14.2",
|
||||
"text-table": "0.2.0",
|
||||
"tree-kill": "1.2.2",
|
||||
"tslib": "2.3.1",
|
||||
@ -1897,18 +1916,18 @@
|
||||
}
|
||||
},
|
||||
"@angular-devkit/build-webpack": {
|
||||
"version": "0.1303.7",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.7.tgz",
|
||||
"integrity": "sha512-5vF399cPdwuCbzbxS4yNGgChdAzEM0/By21P0uiqBcIe/Zxuz3IUPapjvcyhkAo5OTu+d7smY9eusLHqoq1WFQ==",
|
||||
"version": "0.1303.9",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.9.tgz",
|
||||
"integrity": "sha512-CdYXvAN1xAik8FyfdF1B8Nt1B/1aBvkZr65AUVFOmP6wuVzcdn78BMZmZD42srYbV2449sWi5Vyo/j0a/lfJww==",
|
||||
"requires": {
|
||||
"@angular-devkit/architect": "0.1303.7",
|
||||
"@angular-devkit/architect": "0.1303.9",
|
||||
"rxjs": "6.6.7"
|
||||
}
|
||||
},
|
||||
"@angular-devkit/core": {
|
||||
"version": "13.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.7.tgz",
|
||||
"integrity": "sha512-Ucy4bJmlgCoBenuVeGMdtW9dE8+cD+guWCgqexsFIG21KJ/l0ShZEZ/dGC1XibzaIs1HbKiTr/T1MOjInCV1rA==",
|
||||
"version": "13.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.9.tgz",
|
||||
"integrity": "sha512-XqCuIWyoqIsLABjV3GQL/+EiBCt3xVPPtNp3Mg4gjBsDLW7PEnvbb81yGkiZQmIsq4EIyQC/6fQa3VdjsCshGg==",
|
||||
"requires": {
|
||||
"ajv": "8.9.0",
|
||||
"ajv-formats": "2.1.1",
|
||||
@ -1919,11 +1938,11 @@
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
|
||||
"integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
|
||||
"integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.16.7"
|
||||
"@babel/highlight": "^7.18.6"
|
||||
}
|
||||
},
|
||||
"@babel/core": {
|
||||
@ -1978,33 +1997,33 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
|
||||
"integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw=="
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz",
|
||||
"integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g=="
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz",
|
||||
"integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==",
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
|
||||
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.16.7",
|
||||
"@babel/helper-validator-identifier": "^7.18.6",
|
||||
"chalk": "^2.0.0",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@jridgewell/trace-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==",
|
||||
"version": "0.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
|
||||
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
|
||||
"requires": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"@ngtools/webpack": {
|
||||
"version": "13.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.7.tgz",
|
||||
"integrity": "sha512-KtNMHOGZIU2oaNTzk97ZNwTnJLbvnSpwyG3/+VW9xN92b2yw8gG9tHPKW2fsFrfzF9Mz8kqJeF31ftvkYuKtuA=="
|
||||
"version": "13.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.9.tgz",
|
||||
"integrity": "sha512-wmgOI5sogAuilwBZJqCHVMjm2uhDxjdSmNLFx7eznwGDa6LjvjuATqCv2dVlftq0Y/5oZFVrg5NpyHt5kfZ8Cg=="
|
||||
},
|
||||
"@types/estree": {
|
||||
"version": "0.0.51",
|
||||
@ -2054,9 +2073,9 @@
|
||||
}
|
||||
},
|
||||
"enhanced-resolve": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz",
|
||||
"integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==",
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz",
|
||||
"integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.2.0"
|
||||
@ -2256,17 +2275,6 @@
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
|
||||
},
|
||||
"terser": {
|
||||
"version": "5.11.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.11.0.tgz",
|
||||
"integrity": "sha512-uCA9DLanzzWSsN1UirKwylhhRz3aKPInlfmpGfw8VN6jHsAtu8HJtIpeeHHK23rxnE/cDc+yvmq5wqkIC6Kn0A==",
|
||||
"requires": {
|
||||
"acorn": "^8.5.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.7.2",
|
||||
"source-map-support": "~0.5.20"
|
||||
}
|
||||
},
|
||||
"webpack": {
|
||||
"version": "5.70.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz",
|
||||
@ -2854,6 +2862,36 @@
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz",
|
||||
"integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ=="
|
||||
},
|
||||
"@jridgewell/source-map": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
|
||||
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
|
||||
"requires": {
|
||||
"@jridgewell/gen-mapping": "^0.3.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
|
||||
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
|
||||
"requires": {
|
||||
"@jridgewell/set-array": "^1.0.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
}
|
||||
},
|
||||
"@jridgewell/trace-mapping": {
|
||||
"version": "0.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
|
||||
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
|
||||
"requires": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz",
|
||||
@ -3408,16 +3446,6 @@
|
||||
"integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/yauzl": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz",
|
||||
"integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
|
||||
@ -4344,12 +4372,6 @@
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"buffer-crc32": {
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
|
||||
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
|
||||
"dev": true
|
||||
},
|
||||
"buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
@ -4659,12 +4681,6 @@
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz",
|
||||
"integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g=="
|
||||
},
|
||||
"colors": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
|
||||
"dev": true
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@ -5473,15 +5489,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"enhanced-resolve": {
|
||||
"version": "5.9.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.0.tgz",
|
||||
@ -5908,29 +5915,6 @@
|
||||
"tmp": "^0.0.33"
|
||||
}
|
||||
},
|
||||
"extract-zip": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
|
||||
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/yauzl": "^2.9.1",
|
||||
"debug": "^4.1.1",
|
||||
"get-stream": "^5.1.0",
|
||||
"yauzl": "^2.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"get-stream": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
|
||||
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"pump": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"extsprintf": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||
@ -6006,15 +5990,6 @@
|
||||
"bser": "2.1.1"
|
||||
}
|
||||
},
|
||||
"fd-slicer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||
"integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"pend": "~1.2.0"
|
||||
}
|
||||
},
|
||||
"fetch-cookie": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.11.0.tgz",
|
||||
@ -8590,12 +8565,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"jpeg-js": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.3.tgz",
|
||||
"integrity": "sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==",
|
||||
"dev": true
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@ -9407,9 +9376,9 @@
|
||||
}
|
||||
},
|
||||
"ngx-extended-pdf-viewer": {
|
||||
"version": "13.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ngx-extended-pdf-viewer/-/ngx-extended-pdf-viewer-13.5.2.tgz",
|
||||
"integrity": "sha512-dbGozWdfjHosHtJXRbM7zZQ8Zojdpv2/5e68767htvPRQ2JCUtRN+u6NwA59k+sNpNCliHhjaeFMXfWEWEHDMQ==",
|
||||
"version": "14.5.3",
|
||||
"resolved": "https://registry.npmjs.org/ngx-extended-pdf-viewer/-/ngx-extended-pdf-viewer-14.5.3.tgz",
|
||||
"integrity": "sha512-9pqnbonKcu/6SIwPe3yCfHzsO1fgO7qIwETHD7UuS2kAG5GM7VkEwrqMoF7qsZ0Lq/rkqFBcGsS4GYW5JK+oEQ==",
|
||||
"requires": {
|
||||
"lodash.deburr": "^4.1.0",
|
||||
"tslib": "^2.3.0"
|
||||
@ -10110,12 +10079,6 @@
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="
|
||||
},
|
||||
"pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
|
||||
"dev": true
|
||||
},
|
||||
"performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
@ -10170,23 +10133,6 @@
|
||||
"nice-napi": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"pixelmatch": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.2.1.tgz",
|
||||
"integrity": "sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"pngjs": "^4.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"pngjs": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-4.0.1.tgz",
|
||||
"integrity": "sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"pkg-dir": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
|
||||
@ -10196,85 +10142,22 @@
|
||||
}
|
||||
},
|
||||
"playwright": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.20.2.tgz",
|
||||
"integrity": "sha512-p6GE8A/f2G7t8FIk/AwQ94nT7R7tyPRJyKt1FwRjwBDf4WdpgoAr4hDfMgHy+CkClR22adFjopGwhxXAPsewhg==",
|
||||
"version": "1.24.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.24.2.tgz",
|
||||
"integrity": "sha512-iMWDLgaFRT+7dXsNeYwgl8nhLHsUrzFyaRVC+ftr++P1dVs70mPrFKBZrGp1fOKigHV9d1syC03IpPbqLKlPsg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"playwright-core": "1.20.2"
|
||||
"playwright-core": "1.24.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "4"
|
||||
}
|
||||
},
|
||||
"commander": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
||||
"dev": true
|
||||
},
|
||||
"https-proxy-agent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
|
||||
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
|
||||
"dev": true
|
||||
},
|
||||
"playwright-core": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.20.2.tgz",
|
||||
"integrity": "sha512-iV6+HftSPalynkq0CYJala1vaTOq7+gU9BRfKCdM9bAxNq/lFLrwbluug2Wt5OoUwbMABcnTThIEm3/qUhCdJQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"colors": "1.4.0",
|
||||
"commander": "8.3.0",
|
||||
"debug": "4.3.3",
|
||||
"extract-zip": "2.0.1",
|
||||
"https-proxy-agent": "5.0.0",
|
||||
"jpeg-js": "0.4.3",
|
||||
"mime": "3.0.0",
|
||||
"pixelmatch": "5.2.1",
|
||||
"pngjs": "6.0.0",
|
||||
"progress": "2.0.3",
|
||||
"proper-lockfile": "4.1.2",
|
||||
"proxy-from-env": "1.1.0",
|
||||
"rimraf": "3.0.2",
|
||||
"socks-proxy-agent": "6.1.1",
|
||||
"stack-utils": "2.0.5",
|
||||
"ws": "8.4.2",
|
||||
"yauzl": "2.10.0",
|
||||
"yazl": "2.5.1"
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz",
|
||||
"integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==",
|
||||
"version": "1.24.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.24.2.tgz",
|
||||
"integrity": "sha512-zfAoDoPY/0sDLsgSgLZwWmSCevIg1ym7CppBwllguVBNiHeixZkc1AdMuYUPZC6AdEYc4CxWEyLMBTw2YcmRrA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"pngjs": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",
|
||||
"integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",
|
||||
"dev": true
|
||||
},
|
||||
"portfinder": {
|
||||
"version": "1.0.28",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
|
||||
@ -10636,12 +10519,6 @@
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"progress": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
||||
"dev": true
|
||||
},
|
||||
"promise-inflight": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
|
||||
@ -10675,25 +10552,6 @@
|
||||
"sisteransi": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"proper-lockfile": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
|
||||
"integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"retry": "^0.12.0",
|
||||
"signal-exit": "^3.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"retry": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
||||
"integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"protractor": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/protractor/-/protractor-7.0.0.tgz",
|
||||
@ -10776,8 +10634,7 @@
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
|
||||
"resolved": "",
|
||||
"dev": true
|
||||
},
|
||||
"strip-ansi": {
|
||||
@ -10982,8 +10839,7 @@
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
|
||||
"resolved": "",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
@ -11053,12 +10909,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true
|
||||
},
|
||||
"prr": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||
@ -11070,16 +10920,6 @@
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
|
||||
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
|
||||
},
|
||||
"pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
@ -12123,20 +11963,14 @@
|
||||
}
|
||||
},
|
||||
"terser": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
|
||||
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
|
||||
"version": "5.14.2",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz",
|
||||
"integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==",
|
||||
"requires": {
|
||||
"@jridgewell/source-map": "^0.3.2",
|
||||
"acorn": "^8.5.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.7.2",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"terser-webpack-plugin": {
|
||||
@ -13133,25 +12967,6 @@
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz",
|
||||
"integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA=="
|
||||
},
|
||||
"yauzl": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
||||
"integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-crc32": "~0.2.3",
|
||||
"fd-slicer": "~1.1.0"
|
||||
}
|
||||
},
|
||||
"yazl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz",
|
||||
"integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-crc32": "~0.2.3"
|
||||
}
|
||||
},
|
||||
"yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
|
@ -39,7 +39,7 @@
|
||||
"lazysizes": "^5.3.2",
|
||||
"ng-circle-progress": "^1.6.0",
|
||||
"ngx-color-picker": "^12.0.0",
|
||||
"ngx-extended-pdf-viewer": "^13.5.2",
|
||||
"ngx-extended-pdf-viewer": "^14.5.2",
|
||||
"ngx-file-drop": "^13.0.0",
|
||||
"ngx-infinite-scroll": "^13.0.2",
|
||||
"ngx-toastr": "^14.2.1",
|
||||
@ -61,7 +61,7 @@
|
||||
"jest": "^27.5.1",
|
||||
"jest-preset-angular": "^11.1.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"playwright": "^1.20.2",
|
||||
"playwright": "^1.24.2",
|
||||
"protractor": "~7.0.0",
|
||||
"ts-node": "~10.5.0",
|
||||
"tslint": "^6.1.3",
|
||||
|
@ -290,6 +290,12 @@ export class ActionFactoryService {
|
||||
title: 'Add to Want To Read',
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
},
|
||||
{
|
||||
action: Action.RemoveFromWantToReadList,
|
||||
title: 'Remove from Want To Read',
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ChangeDetectorRef, Injectable } from '@angular/core';
|
||||
import { NavigationStart, Router } from '@angular/router';
|
||||
import { ActivatedRoute, NavigationStart, Router } from '@angular/router';
|
||||
import { ReplaySubject } from 'rxjs';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { Action, ActionFactoryService, ActionItem } from '../_services/action-factory.service';
|
||||
@ -23,6 +23,7 @@ export class BulkSelectionService {
|
||||
private selectedCards: { [key: string]: {[key: number]: boolean} } = {};
|
||||
private dataSourceMax: { [key: string]: number} = {};
|
||||
public isShiftDown: boolean = false;
|
||||
private activeRoute: string = '';
|
||||
|
||||
private actionsSource = new ReplaySubject<ActionItem<any>[]>(1);
|
||||
public actions$ = this.actionsSource.asObservable();
|
||||
@ -33,14 +34,16 @@ export class BulkSelectionService {
|
||||
*/
|
||||
public selections$ = this.selectionsSource.asObservable();
|
||||
|
||||
constructor(private router: Router, private actionFactory: ActionFactoryService) {
|
||||
constructor(private router: Router, private actionFactory: ActionFactoryService, private route: ActivatedRoute) {
|
||||
router.events
|
||||
.pipe(filter(event => event instanceof NavigationStart))
|
||||
.subscribe((event) => {
|
||||
this.deselectAll();
|
||||
this.dataSourceMax = {};
|
||||
this.prevIndex = 0;
|
||||
this.activeRoute = this.router.url;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
handleCardSelection(dataSource: DataSource, index: number, maxIndex: number, wasSelected: boolean) {
|
||||
@ -143,7 +146,14 @@ export class BulkSelectionService {
|
||||
// else returns volume/chapter items
|
||||
const allowedActions = [Action.AddToReadingList, Action.MarkAsRead, Action.MarkAsUnread, Action.AddToCollection, Action.Delete, Action.AddToWantToReadList, Action.RemoveFromWantToReadList];
|
||||
if (Object.keys(this.selectedCards).filter(item => item === 'series').length > 0) {
|
||||
return this.actionFactory.getSeriesActions(callback).filter(item => allowedActions.includes(item.action));
|
||||
let actions = this.actionFactory.getSeriesActions(callback).filter(item => allowedActions.includes(item.action));
|
||||
if (this.activeRoute.startsWith('/want-to-read')) {
|
||||
const removeFromWantToRead = {...actions[0]};
|
||||
removeFromWantToRead.action = Action.RemoveFromWantToReadList;
|
||||
removeFromWantToRead.title = 'Remove from Want to Read';
|
||||
actions.push(removeFromWantToRead);
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
if (Object.keys(this.selectedCards).filter(item => item === 'bookmark').length > 0) {
|
||||
|
@ -117,7 +117,6 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy, OnChanges,
|
||||
// }
|
||||
// this.hasResumedJumpKey = true;
|
||||
// });
|
||||
console.log(this.noDataTemplate);
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
|
@ -33,9 +33,10 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div (click)="toggleMenu()" class="reading-area" [ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : 'calc(var(--vh)*100)'}" #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">
|
||||
<div class="image-container" [ngClass]="{'d-none': !renderWithCanvas }">
|
||||
<div class="image-container" [ngClass]="{'d-none': !renderWithCanvas }" [style.filter]="'brightness(' + generalSettingsForm.get('darkness')?.value + '%)'">
|
||||
<canvas #content class="{{getFittingOptionClass()}}"
|
||||
ondragstart="return false;" onselectstart="return false;">
|
||||
</canvas>
|
||||
@ -64,7 +65,8 @@
|
||||
<div class="image-container {{getFittingOptionClass()}}" [ngClass]="{'d-none': renderWithCanvas, 'center-double': ShouldRenderDoublePage,
|
||||
'fit-to-width-double-offset' : FittingOption === FITTING_OPTION.WIDTH && ShouldRenderDoublePage,
|
||||
'fit-to-height-double-offset': FittingOption === FITTING_OPTION.HEIGHT && ShouldRenderDoublePage,
|
||||
'original-double-offset' : FittingOption === FITTING_OPTION.ORIGINAL && ShouldRenderDoublePage}">
|
||||
'original-double-offset' : FittingOption === FITTING_OPTION.ORIGINAL && ShouldRenderDoublePage}"
|
||||
[style.filter]="'brightness(' + generalSettingsForm.get('darkness')?.value + '%)' | safeStyle">
|
||||
<img #image [src]="canvasImage.src" id="image-1"
|
||||
class="{{getFittingOptionClass()}} {{readerMode === ReaderMode.LeftRight || readerMode === ReaderMode.UpDown ? '' : 'd-none'}} {{showClickOverlay ? 'blur' : ''}}">
|
||||
|
||||
@ -214,6 +216,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<label for="darkness" class="form-label range-label">Darkess</label>
|
||||
<input type="range" class="form-range" id="darkness"
|
||||
min="10" max="100" step="1" formControlName="darkness">
|
||||
<span class="range-text">{{generalSettingsForm.get('darkness')?.value + '%'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -480,11 +480,17 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
autoCloseMenu: this.autoCloseMenu,
|
||||
pageSplitOption: this.pageSplitOption,
|
||||
fittingOption: this.translateScalingOption(this.scalingOption),
|
||||
layoutMode: this.layoutMode
|
||||
layoutMode: this.layoutMode,
|
||||
darkness: 100
|
||||
});
|
||||
|
||||
this.updateForm();
|
||||
|
||||
this.generalSettingsForm.get('darkness')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(val => {
|
||||
console.log('brightness: ', val);
|
||||
//this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
this.generalSettingsForm.get('layoutMode')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(val => {
|
||||
|
||||
const changeOccurred = parseInt(val, 10) !== this.layoutMode;
|
||||
|
@ -9,6 +9,7 @@ import { NgxSliderModule } from '@angular-slider/ngx-slider';
|
||||
import { InfiniteScrollerComponent } from './infinite-scroller/infinite-scroller.component';
|
||||
import { ReaderSharedModule } from '../reader-shared/reader-shared.module';
|
||||
import { FullscreenIconPipe } from './fullscreen-icon.pipe';
|
||||
import { PipeModule } from '../pipe/pipe.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -20,6 +21,7 @@ import { FullscreenIconPipe } from './fullscreen-icon.pipe';
|
||||
CommonModule,
|
||||
MangaReaderRoutingModule,
|
||||
ReactiveFormsModule,
|
||||
PipeModule,
|
||||
|
||||
NgbDropdownModule,
|
||||
NgxSliderModule,
|
||||
|
@ -13,6 +13,7 @@ import { AgeRatingPipe } from './age-rating.pipe';
|
||||
import { MangaFormatPipe } from './manga-format.pipe';
|
||||
import { MangaFormatIconPipe } from './manga-format-icon.pipe';
|
||||
import { LibraryTypePipe } from './library-type.pipe';
|
||||
import { SafeStylePipe } from './safe-style.pipe';
|
||||
|
||||
|
||||
|
||||
@ -30,7 +31,8 @@ import { LibraryTypePipe } from './library-type.pipe';
|
||||
AgeRatingPipe,
|
||||
MangaFormatPipe,
|
||||
MangaFormatIconPipe,
|
||||
LibraryTypePipe
|
||||
LibraryTypePipe,
|
||||
SafeStylePipe
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -48,7 +50,8 @@ import { LibraryTypePipe } from './library-type.pipe';
|
||||
AgeRatingPipe,
|
||||
MangaFormatPipe,
|
||||
MangaFormatIconPipe,
|
||||
LibraryTypePipe
|
||||
LibraryTypePipe,
|
||||
SafeStylePipe
|
||||
]
|
||||
})
|
||||
export class PipeModule { }
|
||||
|
16
UI/Web/src/app/pipe/safe-style.pipe.ts
Normal file
16
UI/Web/src/app/pipe/safe-style.pipe.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
|
||||
@Pipe({
|
||||
name: 'safeStyle'
|
||||
})
|
||||
export class SafeStylePipe implements PipeTransform {
|
||||
|
||||
constructor(private sanitizer: DomSanitizer){
|
||||
}
|
||||
|
||||
transform(style: string) {
|
||||
return this.sanitizer.bypassSecurityTrustStyle(style);
|
||||
}
|
||||
|
||||
}
|
@ -49,7 +49,6 @@ export class SideNavComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@ -54,6 +54,7 @@ export class WantToReadComponent implements OnInit, OnDestroy {
|
||||
case Action.RemoveFromWantToReadList:
|
||||
this.actionService.removeMultipleSeriesFromWantToReadList(selectedSeries.map(s => s.id), () => {
|
||||
this.bulkSelectionService.deselectAll();
|
||||
this.loadPage();
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
break;
|
||||
|
Loading…
x
Reference in New Issue
Block a user