mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Release Testing Day 2 (#1937)
* Removed unneded ngModel on password field * Fixed some bad validation messages on Edit Reading List modal and disabled save button * Added a lot of trace code to help debug a foreign constraint issue. * Fixed a bug where after a series is scanned, generate covers for series didn't respect webp cover generation. * Fixed library last scan being stored in Utc, but expected to be server time. * Fixed up some of that trace logging being way too verbose. Fixed a case where when a missing storyarc number, the whole pair was dropped. Now, it will default that item to the end of the reading list. Fixed a bug where Start and End dates weren't being calculated after generating a reading list. * Fixed a bug where the default admin user for reading list creation from files was selecting the wrong user. Changed so that when there is a bad pair (aka number missing) and only 1 pair, then we wont constantly reorder the item. * Fixed unit test
This commit is contained in:
parent
66f84a0ee3
commit
c70154f428
@ -1203,10 +1203,67 @@ public class ReadingListServiceTests
|
|||||||
|
|
||||||
#region CreateReadingListsFromSeries
|
#region CreateReadingListsFromSeries
|
||||||
|
|
||||||
|
private async Task<Tuple<Series, Series>> SetupData()
|
||||||
|
{
|
||||||
|
// Setup 2 series, only do this once tho
|
||||||
|
if (await _unitOfWork.SeriesRepository.DoesSeriesNameExistInLibrary("Series 1", 1, MangaFormat.Archive))
|
||||||
|
{
|
||||||
|
return new Tuple<Series, Series>(await _unitOfWork.SeriesRepository.GetFullSeriesForSeriesIdAsync(1),
|
||||||
|
await _unitOfWork.SeriesRepository.GetFullSeriesForSeriesIdAsync(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
var library =
|
||||||
|
await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1,
|
||||||
|
LibraryIncludes.Series | LibraryIncludes.AppUser);
|
||||||
|
var user = new AppUserBuilder("majora2007", "majora2007@fake.com").Build();
|
||||||
|
library!.AppUsers.Add(user);
|
||||||
|
library.ManageReadingLists = true;
|
||||||
|
|
||||||
|
// Setup the series for CreateReadingListsFromSeries
|
||||||
|
var series1 = new SeriesBuilder("Series 1")
|
||||||
|
.WithFormat(MangaFormat.Archive)
|
||||||
|
.WithVolume(new VolumeBuilder("1")
|
||||||
|
.WithChapter(new ChapterBuilder("1")
|
||||||
|
.WithStoryArc("CreateReadingListsFromSeries")
|
||||||
|
.WithStoryArcNumber("1")
|
||||||
|
.Build())
|
||||||
|
.WithChapter(new ChapterBuilder("2").Build())
|
||||||
|
.Build())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var series2 = new SeriesBuilder("Series 2")
|
||||||
|
.WithFormat(MangaFormat.Archive)
|
||||||
|
.WithVolume(new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultVolume)
|
||||||
|
.WithChapter(new ChapterBuilder("1").Build())
|
||||||
|
.WithChapter(new ChapterBuilder("2").Build())
|
||||||
|
.Build())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
library!.Series.Add(series1);
|
||||||
|
library!.Series.Add(series2);
|
||||||
|
|
||||||
|
await _unitOfWork.CommitAsync();
|
||||||
|
|
||||||
|
return new Tuple<Series, Series>(series1, series2);
|
||||||
|
}
|
||||||
|
|
||||||
// [Fact]
|
// [Fact]
|
||||||
// public async Task CreateReadingListsFromSeries_ShouldCreateFromSinglePair()
|
// public async Task CreateReadingListsFromSeries_ShouldCreateFromSinglePair()
|
||||||
// {
|
// {
|
||||||
|
// //await SetupData();
|
||||||
//
|
//
|
||||||
|
// var series1 = new SeriesBuilder("Series 1")
|
||||||
|
// .WithFormat(MangaFormat.Archive)
|
||||||
|
// .WithVolume(new VolumeBuilder("1")
|
||||||
|
// .WithChapter(new ChapterBuilder("1")
|
||||||
|
// .WithStoryArc("CreateReadingListsFromSeries")
|
||||||
|
// .WithStoryArcNumber("1")
|
||||||
|
// .Build())
|
||||||
|
// .WithChapter(new ChapterBuilder("2").Build())
|
||||||
|
// .Build())
|
||||||
|
// .Build();
|
||||||
|
//
|
||||||
|
// _readingListService.CreateReadingListsFromSeries(series.Item1)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -228,7 +228,7 @@ public class UserRepository : IUserRepository
|
|||||||
public async Task<AppUser> GetDefaultAdminUser()
|
public async Task<AppUser> GetDefaultAdminUser()
|
||||||
{
|
{
|
||||||
return (await _userManager.GetUsersInRoleAsync(PolicyConstants.AdminRole))
|
return (await _userManager.GetUsersInRoleAsync(PolicyConstants.AdminRole))
|
||||||
.OrderByDescending(u => u.Created)
|
.OrderBy(u => u.Created)
|
||||||
.First();
|
.First();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ public class SeriesMetadata : IHasConcurrencyToken
|
|||||||
|
|
||||||
public string Summary { get; set; } = string.Empty;
|
public string Summary { get; set; } = string.Empty;
|
||||||
|
|
||||||
public ICollection<CollectionTag> CollectionTags { get; set; } = null!;
|
public ICollection<CollectionTag> CollectionTags { get; set; } = new List<CollectionTag>();
|
||||||
|
|
||||||
public ICollection<Genre> Genres { get; set; } = new List<Genre>();
|
public ICollection<Genre> Genres { get; set; } = new List<Genre>();
|
||||||
public ICollection<Tag> Tags { get; set; } = new List<Tag>();
|
public ICollection<Tag> Tags { get; set; } = new List<Tag>();
|
||||||
|
@ -17,7 +17,7 @@ public class ChapterBuilder : IEntityBuilder<Chapter>
|
|||||||
{
|
{
|
||||||
Range = string.IsNullOrEmpty(range) ? number : range,
|
Range = string.IsNullOrEmpty(range) ? number : range,
|
||||||
Title = string.IsNullOrEmpty(range) ? number : range,
|
Title = string.IsNullOrEmpty(range) ? number : range,
|
||||||
Number = Services.Tasks.Scanner.Parser.Parser.MinNumberFromRange(number) + string.Empty,
|
Number = Parser.MinNumberFromRange(number) + string.Empty,
|
||||||
Files = new List<MangaFile>(),
|
Files = new List<MangaFile>(),
|
||||||
Pages = 1
|
Pages = 1
|
||||||
};
|
};
|
||||||
@ -42,12 +42,24 @@ public class ChapterBuilder : IEntityBuilder<Chapter>
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChapterBuilder WithNumber(string number)
|
public ChapterBuilder WithNumber(string number)
|
||||||
{
|
{
|
||||||
_chapter.Number = number;
|
_chapter.Number = number;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ChapterBuilder WithStoryArc(string arc)
|
||||||
|
{
|
||||||
|
_chapter.StoryArc = arc;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChapterBuilder WithStoryArcNumber(string number)
|
||||||
|
{
|
||||||
|
_chapter.StoryArcNumber = number;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
private ChapterBuilder WithRange(string range)
|
private ChapterBuilder WithRange(string range)
|
||||||
{
|
{
|
||||||
_chapter.Range = range;
|
_chapter.Range = range;
|
||||||
|
@ -103,6 +103,7 @@ public static class PersonHelper
|
|||||||
if (string.IsNullOrEmpty(person.Name)) return;
|
if (string.IsNullOrEmpty(person.Name)) return;
|
||||||
var existingPerson = metadataPeople.FirstOrDefault(p =>
|
var existingPerson = metadataPeople.FirstOrDefault(p =>
|
||||||
p.NormalizedName == person.Name.ToNormalized() && p.Role == person.Role);
|
p.NormalizedName == person.Name.ToNormalized() && p.Role == person.Role);
|
||||||
|
|
||||||
if (existingPerson == null)
|
if (existingPerson == null)
|
||||||
{
|
{
|
||||||
metadataPeople.Add(person);
|
metadataPeople.Add(person);
|
||||||
|
@ -13,9 +13,11 @@ using API.Entities;
|
|||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
using API.Helpers;
|
using API.Helpers;
|
||||||
using API.Helpers.Builders;
|
using API.Helpers.Builders;
|
||||||
|
using API.Services.Tasks.Scanner.Parser;
|
||||||
using API.SignalR;
|
using API.SignalR;
|
||||||
using Kavita.Common;
|
using Kavita.Common;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
namespace API.Services;
|
namespace API.Services;
|
||||||
|
|
||||||
@ -447,7 +449,7 @@ public class ReadingListService : IReadingListService
|
|||||||
series.Metadata ??= new SeriesMetadataBuilder().Build();
|
series.Metadata ??= new SeriesMetadataBuilder().Build();
|
||||||
foreach (var chapter in series.Volumes.SelectMany(v => v.Chapters))
|
foreach (var chapter in series.Volumes.SelectMany(v => v.Chapters))
|
||||||
{
|
{
|
||||||
List<Tuple<string, string>> pairs = new List<Tuple<string, string>>();
|
var pairs = new List<Tuple<string, string>>();
|
||||||
if (!string.IsNullOrEmpty(chapter.StoryArc))
|
if (!string.IsNullOrEmpty(chapter.StoryArc))
|
||||||
{
|
{
|
||||||
pairs.AddRange(GeneratePairs(chapter.Files.FirstOrDefault()!.FilePath, chapter.StoryArc, chapter.StoryArcNumber));
|
pairs.AddRange(GeneratePairs(chapter.Files.FirstOrDefault()!.FilePath, chapter.StoryArc, chapter.StoryArcNumber));
|
||||||
@ -459,7 +461,6 @@ public class ReadingListService : IReadingListService
|
|||||||
|
|
||||||
foreach (var arcPair in pairs)
|
foreach (var arcPair in pairs)
|
||||||
{
|
{
|
||||||
var order = int.Parse(arcPair.Item2);
|
|
||||||
var readingList = await _unitOfWork.ReadingListRepository.GetReadingListByTitleAsync(arcPair.Item1, user.Id);
|
var readingList = await _unitOfWork.ReadingListRepository.GetReadingListByTitleAsync(arcPair.Item1, user.Id);
|
||||||
if (readingList == null)
|
if (readingList == null)
|
||||||
{
|
{
|
||||||
@ -471,19 +472,36 @@ public class ReadingListService : IReadingListService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var items = readingList.Items.ToList();
|
var items = readingList.Items.ToList();
|
||||||
var readingListItem = items.FirstOrDefault(item => item.Order == order);
|
var order = int.Parse(arcPair.Item2);
|
||||||
|
var readingListItem = items.FirstOrDefault(item => item.Order == order || item.ChapterId == chapter.Id);
|
||||||
if (readingListItem == null)
|
if (readingListItem == null)
|
||||||
{
|
{
|
||||||
|
// If no number was provided in the reading list, we default to MaxValue and hence we should insert the item at the end of the list
|
||||||
|
if (order == int.MaxValue)
|
||||||
|
{
|
||||||
|
order = items.Count > 0 ? items.Max(item => item.Order) + 1 : 0;
|
||||||
|
}
|
||||||
items.Add(new ReadingListItemBuilder(order, series.Id, chapter.VolumeId, chapter.Id).Build());
|
items.Add(new ReadingListItemBuilder(order, series.Id, chapter.VolumeId, chapter.Id).Build());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ReorderItems(items, readingListItem.Id, order);
|
if (order == int.MaxValue)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("{Filename} has a missing StoryArcNumber/AlternativeNumber but list already exists with this item. Skipping item", chapter.Files.FirstOrDefault()?.FilePath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ReorderItems(items, readingListItem.Id, order);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readingList.Items = items;
|
readingList.Items = items;
|
||||||
await CalculateReadingListAgeRating(readingList);
|
await CalculateReadingListAgeRating(readingList);
|
||||||
await _unitOfWork.CommitAsync();
|
await CalculateStartAndEndDates(readingList);
|
||||||
|
if (_unitOfWork.HasChanges())
|
||||||
|
{
|
||||||
|
await _unitOfWork.CommitAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -495,14 +513,19 @@ public class ReadingListService : IReadingListService
|
|||||||
|
|
||||||
var arcs = storyArc.Split(",");
|
var arcs = storyArc.Split(",");
|
||||||
var arcNumbers = storyArcNumbers.Split(",");
|
var arcNumbers = storyArcNumbers.Split(",");
|
||||||
if (arcNumbers.Length != arcs.Length)
|
if (arcNumbers.Count(s => !string.IsNullOrEmpty(s)) != arcs.Length)
|
||||||
{
|
{
|
||||||
_logger.LogError("There is a mismatch on StoryArc and StoryArcNumber for {FileName}", filename);
|
_logger.LogWarning("There is a mismatch on StoryArc and StoryArcNumber for {FileName}. Def", filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxPairs = Math.Min(arcs.Length, arcNumbers.Length);
|
var maxPairs = Math.Min(arcs.Length, arcNumbers.Length);
|
||||||
for (var i = 0; i < maxPairs; i++)
|
for (var i = 0; i < maxPairs; i++)
|
||||||
{
|
{
|
||||||
|
// When there is a mismatch on arcs and arc numbers, then we should default to a high number
|
||||||
|
if (string.IsNullOrEmpty(arcNumbers[i]) && !string.IsNullOrEmpty(arcs[i]))
|
||||||
|
{
|
||||||
|
arcNumbers[i] = int.MaxValue.ToString();
|
||||||
|
}
|
||||||
if (string.IsNullOrEmpty(arcs[i]) || !int.TryParse(arcNumbers[i], out _)) continue;
|
if (string.IsNullOrEmpty(arcs[i]) || !int.TryParse(arcNumbers[i], out _)) continue;
|
||||||
data.Add(new Tuple<string, string>(arcs[i], arcNumbers[i]));
|
data.Add(new Tuple<string, string>(arcs[i], arcNumbers[i]));
|
||||||
}
|
}
|
||||||
|
@ -196,8 +196,9 @@ public class ProcessSeries : IProcessSeries
|
|||||||
{
|
{
|
||||||
await _unitOfWork.RollbackAsync();
|
await _unitOfWork.RollbackAsync();
|
||||||
_logger.LogCritical(ex,
|
_logger.LogCritical(ex,
|
||||||
"[ScannerService] There was an issue writing to the database for series {@SeriesName}",
|
"[ScannerService] There was an issue writing to the database for series {SeriesName}",
|
||||||
series.Name);
|
series.Name);
|
||||||
|
_logger.LogTrace("[ScannerService] Full Series Dump: {@Series}", series);
|
||||||
|
|
||||||
await _eventHub.SendMessageAsync(MessageFactory.Error,
|
await _eventHub.SendMessageAsync(MessageFactory.Error,
|
||||||
MessageFactory.ErrorEvent($"There was an issue writing to the DB for Series {series}",
|
MessageFactory.ErrorEvent($"There was an issue writing to the DB for Series {series}",
|
||||||
@ -222,7 +223,7 @@ public class ProcessSeries : IProcessSeries
|
|||||||
_logger.LogError(ex, "[ScannerService] There was an exception updating series for {SeriesName}", series.Name);
|
_logger.LogError(ex, "[ScannerService] There was an exception updating series for {SeriesName}", series.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _metadataService.GenerateCoversForSeries(series, false);
|
await _metadataService.GenerateCoversForSeries(series, (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).ConvertCoverToWebP);
|
||||||
EnqueuePostSeriesProcessTasks(series.LibraryId, series.Id);
|
EnqueuePostSeriesProcessTasks(series.LibraryId, series.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -726,7 +727,18 @@ public class ProcessSeries : IProcessSeries
|
|||||||
|
|
||||||
void AddPerson(Person person)
|
void AddPerson(Person person)
|
||||||
{
|
{
|
||||||
PersonHelper.AddPersonIfNotExists(chapter.People, person);
|
// TODO: Temp have code inlined to help debug foreign key constraint issue
|
||||||
|
//PersonHelper.AddPersonIfNotExists(chapter.People, person);
|
||||||
|
if (string.IsNullOrEmpty(person.Name)) return;
|
||||||
|
var existingPerson = chapter.People.FirstOrDefault(p =>
|
||||||
|
p.NormalizedName == person.Name.ToNormalized() && p.Role == person.Role);
|
||||||
|
_logger.LogTrace("[PersonHelper] Attempting to add {@Person} to {FileName} with ChapterID {ChapterId}, adding if not null: {@ExistingPerson}",
|
||||||
|
person, chapter.Files.FirstOrDefault()?.FilePath, chapter.Id, existingPerson);
|
||||||
|
|
||||||
|
if (existingPerson == null)
|
||||||
|
{
|
||||||
|
chapter.People.Add(person);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddGenre(Genre genre, bool newTag)
|
void AddGenre(Genre genre, bool newTag)
|
||||||
@ -823,15 +835,19 @@ public class ProcessSeries : IProcessSeries
|
|||||||
lock (_peopleLock)
|
lock (_peopleLock)
|
||||||
{
|
{
|
||||||
var allPeopleTypeRole = _people.Where(p => p.Role == role).ToList();
|
var allPeopleTypeRole = _people.Where(p => p.Role == role).ToList();
|
||||||
|
_logger.LogTrace("[UpdatePeople] for {Role} and Names of {Names}", role, names);
|
||||||
|
_logger.LogTrace("[UpdatePeople] for {Role} found {@People}", role, allPeopleTypeRole.Select(p => new {p.Id, p.Name, SeriesMetadataIds = p.SeriesMetadatas?.Select(m => m.Id).ToList()}));
|
||||||
|
|
||||||
foreach (var name in names)
|
foreach (var name in names)
|
||||||
{
|
{
|
||||||
var normalizedName = name.ToNormalized();
|
var normalizedName = name.ToNormalized();
|
||||||
var person = allPeopleTypeRole.FirstOrDefault(p =>
|
var person = allPeopleTypeRole.FirstOrDefault(p =>
|
||||||
p.NormalizedName != null && p.NormalizedName.Equals(normalizedName));
|
p.NormalizedName != null && p.NormalizedName.Equals(normalizedName));
|
||||||
|
|
||||||
if (person == null)
|
if (person == null)
|
||||||
{
|
{
|
||||||
person = new PersonBuilder(name, role).Build();
|
person = new PersonBuilder(name, role).Build();
|
||||||
|
_logger.LogTrace("[UpdatePeople] for {Role} no one found, adding to _people", role);
|
||||||
_people.Add(person);
|
_people.Add(person);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -531,7 +531,7 @@ public class ScannerService : IScannerService
|
|||||||
|
|
||||||
_logger.LogInformation("[ScannerService] Finished file scan in {ScanAndUpdateTime} milliseconds. Updating database", scanElapsedTime);
|
_logger.LogInformation("[ScannerService] Finished file scan in {ScanAndUpdateTime} milliseconds. Updating database", scanElapsedTime);
|
||||||
|
|
||||||
var time = DateTime.UtcNow;
|
var time = DateTime.Now;
|
||||||
foreach (var folderPath in library.Folders)
|
foreach (var folderPath in library.Folders)
|
||||||
{
|
{
|
||||||
folderPath.UpdateLastScanned(time);
|
folderPath.UpdateLastScanned(time);
|
||||||
|
@ -33,11 +33,12 @@ and modified
|
|||||||
})
|
})
|
||||||
export class TimeAgoPipe implements PipeTransform, OnDestroy {
|
export class TimeAgoPipe implements PipeTransform, OnDestroy {
|
||||||
|
|
||||||
private timer: number | null = null;
|
private timer: number | null = null;
|
||||||
constructor(private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone) {}
|
constructor(private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone) {}
|
||||||
transform(value: string) {
|
transform(value: string) {
|
||||||
this.removeTimer();
|
this.removeTimer();
|
||||||
const d = new Date(value);
|
const d = new Date(value);
|
||||||
|
console.log('date: ', d);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const seconds = Math.round(Math.abs((now.getTime() - d.getTime()) / 1000));
|
const seconds = Math.round(Math.abs((now.getTime() - d.getTime()) / 1000));
|
||||||
const timeToUpdate = (Number.isNaN(seconds)) ? 1000 : this.getSecondsUntilUpdate(seconds) * 1000;
|
const timeToUpdate = (Number.isNaN(seconds)) ? 1000 : this.getSecondsUntilUpdate(seconds) * 1000;
|
||||||
@ -61,37 +62,37 @@ export class TimeAgoPipe implements PipeTransform, OnDestroy {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seconds <= 45) {
|
if (seconds <= 45) {
|
||||||
return 'just now';
|
return 'just now';
|
||||||
}
|
}
|
||||||
if (seconds <= 90) {
|
if (seconds <= 90) {
|
||||||
return 'a minute ago';
|
return 'a minute ago';
|
||||||
}
|
}
|
||||||
if (minutes <= 45) {
|
if (minutes <= 45) {
|
||||||
return minutes + ' minutes ago';
|
return minutes + ' minutes ago';
|
||||||
}
|
}
|
||||||
if (minutes <= 90) {
|
if (minutes <= 90) {
|
||||||
return 'an hour ago';
|
return 'an hour ago';
|
||||||
}
|
}
|
||||||
if (hours <= 22) {
|
if (hours <= 22) {
|
||||||
return hours + ' hours ago';
|
return hours + ' hours ago';
|
||||||
}
|
}
|
||||||
if (hours <= 36) {
|
if (hours <= 36) {
|
||||||
return 'a day ago';
|
return 'a day ago';
|
||||||
}
|
}
|
||||||
if (days <= 25) {
|
if (days <= 25) {
|
||||||
return days + ' days ago';
|
return days + ' days ago';
|
||||||
}
|
}
|
||||||
if (days <= 45) {
|
if (days <= 45) {
|
||||||
return 'a month ago';
|
return 'a month ago';
|
||||||
}
|
}
|
||||||
if (days <= 345) {
|
if (days <= 345) {
|
||||||
return months + ' months ago';
|
return months + ' months ago';
|
||||||
}
|
}
|
||||||
if (days <= 545) {
|
if (days <= 545) {
|
||||||
return 'a year ago';
|
return 'a year ago';
|
||||||
}
|
}
|
||||||
return years + ' years ago';
|
return years + ' years ago';
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
aria-describedby="starting-year-header">
|
aria-describedby="starting-year-header">
|
||||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="reviewGroup.dirty || reviewGroup.touched">
|
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="reviewGroup.dirty || reviewGroup.touched">
|
||||||
<div *ngIf="formControl.errors?.min || formControl.errors?.max">
|
<div *ngIf="formControl.errors?.min || formControl.errors?.max">
|
||||||
Must be between 1 and 12 or blank
|
Must be greater than 1000, 0 or blank
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -84,7 +84,7 @@
|
|||||||
aria-describedby="ending-year-header">
|
aria-describedby="ending-year-header">
|
||||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="reviewGroup.dirty || reviewGroup.touched">
|
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="reviewGroup.dirty || reviewGroup.touched">
|
||||||
<div *ngIf="formControl.errors?.min || formControl.errors?.max">
|
<div *ngIf="formControl.errors?.min || formControl.errors?.max">
|
||||||
Must be between 1 and 12 or blank
|
Must be greater than 1000, 0 or blank
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -109,7 +109,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" (click)="close()">Close</button>
|
<button type="button" class="btn btn-secondary" (click)="close()">Close</button>
|
||||||
<button type="submit" class="btn btn-primary" [disabled]="!reviewGroup.valid" (click)="save()">Save</button>
|
<button type="submit" class="btn btn-primary" (click)="save()">Save</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,6 +116,8 @@ export class EditReadingListModalComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
updateSelectedIndex(index: number) {
|
updateSelectedIndex(index: number) {
|
||||||
this.coverImageIndex = index;
|
this.coverImageIndex = index;
|
||||||
|
console.log(this.coverImageIndex)
|
||||||
|
this.cdRef.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelectedImage(url: string) {
|
updateSelectedImage(url: string) {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label for="password" class="form-label visually-hidden">Password</label>
|
<label for="password" class="form-label visually-hidden">Password</label>
|
||||||
<input class="form-control custom-input" formControlName="password" name="password"
|
<input class="form-control custom-input" formControlName="password" name="password"
|
||||||
id="password" type="password" ngModel pattern="^.{6,32}$" placeholder="Password">
|
id="password" type="password" pattern="^.{6,32}$" placeholder="Password">
|
||||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="loginForm.get('password')?.errors?.pattern" >
|
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="loginForm.get('password')?.errors?.pattern" >
|
||||||
<div class="" *ngIf="loginForm.get('password')?.errors?.pattern">
|
<div class="" *ngIf="loginForm.get('password')?.errors?.pattern">
|
||||||
Password must be between 6 and 32 characters in length
|
Password must be between 6 and 32 characters in length
|
||||||
|
Loading…
x
Reference in New Issue
Block a user