mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-06-02 21:24:18 -04:00
Release Bugfixes (#2470)
This commit is contained in:
parent
d796d06fd1
commit
bdcd3965fd
@ -514,6 +514,72 @@ public class CleanupServiceTests : AbstractDbTest
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region EnsureChapterProgressIsCapped
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EnsureChapterProgressIsCapped_ShouldNormalizeProgress()
|
||||||
|
{
|
||||||
|
await ResetDb();
|
||||||
|
|
||||||
|
var s = new SeriesBuilder("Test CleanupWantToRead_ShouldRemoveFullyReadSeries")
|
||||||
|
.WithMetadata(new SeriesMetadataBuilder().WithPublicationStatus(PublicationStatus.Completed).Build())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
s.Library = new LibraryBuilder("Test LIb").Build();
|
||||||
|
var c = new ChapterBuilder("1").WithPages(2).Build();
|
||||||
|
c.UserProgress = new List<AppUserProgress>();
|
||||||
|
s.Volumes = new List<Volume>()
|
||||||
|
{
|
||||||
|
new VolumeBuilder("0").WithChapter(c).Build()
|
||||||
|
};
|
||||||
|
_context.Series.Add(s);
|
||||||
|
|
||||||
|
var user = new AppUser()
|
||||||
|
{
|
||||||
|
UserName = "EnsureChapterProgressIsCapped",
|
||||||
|
Progresses = new List<AppUserProgress>()
|
||||||
|
};
|
||||||
|
_context.AppUser.Add(user);
|
||||||
|
|
||||||
|
await _unitOfWork.CommitAsync();
|
||||||
|
|
||||||
|
await _readerService.MarkChaptersAsRead(user, s.Id, new List<Chapter>() {c});
|
||||||
|
await _unitOfWork.CommitAsync();
|
||||||
|
|
||||||
|
var chapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(c.Id);
|
||||||
|
await _unitOfWork.ChapterRepository.AddChapterModifiers(user.Id, chapter);
|
||||||
|
|
||||||
|
Assert.NotNull(chapter);
|
||||||
|
Assert.Equal(2, chapter.PagesRead);
|
||||||
|
|
||||||
|
// Update chapter to have 1 page
|
||||||
|
c.Pages = 1;
|
||||||
|
_unitOfWork.ChapterRepository.Update(c);
|
||||||
|
await _unitOfWork.CommitAsync();
|
||||||
|
|
||||||
|
chapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(c.Id);
|
||||||
|
await _unitOfWork.ChapterRepository.AddChapterModifiers(user.Id, chapter);
|
||||||
|
Assert.NotNull(chapter);
|
||||||
|
Assert.Equal(2, chapter.PagesRead);
|
||||||
|
Assert.Equal(1, chapter.Pages);
|
||||||
|
|
||||||
|
var cleanupService = new CleanupService(Substitute.For<ILogger<CleanupService>>(), _unitOfWork,
|
||||||
|
Substitute.For<IEventHub>(),
|
||||||
|
new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new MockFileSystem()));
|
||||||
|
|
||||||
|
await cleanupService.EnsureChapterProgressIsCapped();
|
||||||
|
chapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(c.Id);
|
||||||
|
await _unitOfWork.ChapterRepository.AddChapterModifiers(user.Id, chapter);
|
||||||
|
|
||||||
|
Assert.NotNull(chapter);
|
||||||
|
Assert.Equal(1, chapter.PagesRead);
|
||||||
|
|
||||||
|
_context.AppUser.Remove(user);
|
||||||
|
await _unitOfWork.CommitAsync();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
// #region CleanupBookmarks
|
// #region CleanupBookmarks
|
||||||
//
|
//
|
||||||
// [Fact]
|
// [Fact]
|
||||||
|
14
API.Tests/Services/ScrobblingServiceTests.cs
Normal file
14
API.Tests/Services/ScrobblingServiceTests.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using API.Services.Plus;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace API.Tests.Services;
|
||||||
|
|
||||||
|
public class ScrobblingServiceTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("https://anilist.co/manga/35851/Byeontaega-Doeja/", 35851)]
|
||||||
|
public void CanParseWeblink(string link, long expectedId)
|
||||||
|
{
|
||||||
|
Assert.Equal(ScrobblingService.ExtractId<long>(link, ScrobblingService.AniListWeblinkWebsite), expectedId);
|
||||||
|
}
|
||||||
|
}
|
@ -425,7 +425,7 @@ public class LibraryController : BaseApiController
|
|||||||
public async Task<ActionResult> UpdateLibrary(UpdateLibraryDto dto)
|
public async Task<ActionResult> UpdateLibrary(UpdateLibraryDto dto)
|
||||||
{
|
{
|
||||||
var userId = User.GetUserId();
|
var userId = User.GetUserId();
|
||||||
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(dto.Id, LibraryIncludes.Folders | LibraryIncludes.FileTypes);
|
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(dto.Id, LibraryIncludes.Folders | LibraryIncludes.FileTypes | LibraryIncludes.ExcludePatterns);
|
||||||
if (library == null) return BadRequest(await _localizationService.Translate(userId, "library-doesnt-exist"));
|
if (library == null) return BadRequest(await _localizationService.Translate(userId, "library-doesnt-exist"));
|
||||||
|
|
||||||
var newName = dto.Name.Trim();
|
var newName = dto.Name.Trim();
|
||||||
@ -453,8 +453,8 @@ public class LibraryController : BaseApiController
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
library.LibraryExcludePatterns = dto.ExcludePatterns
|
library.LibraryExcludePatterns = dto.ExcludePatterns
|
||||||
.Select(t => new LibraryExcludePattern() {Pattern = t, LibraryId = library.Id})
|
|
||||||
.Distinct()
|
.Distinct()
|
||||||
|
.Select(t => new LibraryExcludePattern() {Pattern = t, LibraryId = library.Id})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Override Scrobbling for Comic libraries since there are no providers to scrobble to
|
// Override Scrobbling for Comic libraries since there are no providers to scrobble to
|
||||||
|
@ -270,6 +270,8 @@ public class ServerController : BaseApiController
|
|||||||
await provider.FlushAsync();
|
await provider.FlushAsync();
|
||||||
provider = _cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusRatings);
|
provider = _cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusRatings);
|
||||||
await provider.FlushAsync();
|
await provider.FlushAsync();
|
||||||
|
provider = _cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusExternalSeries);
|
||||||
|
await provider.FlushAsync();
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ public interface IAppUserProgressRepository
|
|||||||
Task<int> GetHighestFullyReadVolumeForSeries(int seriesId, int userId);
|
Task<int> GetHighestFullyReadVolumeForSeries(int seriesId, int userId);
|
||||||
Task<DateTime?> GetLatestProgressForSeries(int seriesId, int userId);
|
Task<DateTime?> GetLatestProgressForSeries(int seriesId, int userId);
|
||||||
Task<DateTime?> GetFirstProgressForSeries(int seriesId, int userId);
|
Task<DateTime?> GetFirstProgressForSeries(int seriesId, int userId);
|
||||||
|
Task UpdateAllProgressThatAreMoreThanChapterPages();
|
||||||
}
|
}
|
||||||
#nullable disable
|
#nullable disable
|
||||||
public class AppUserProgressRepository : IAppUserProgressRepository
|
public class AppUserProgressRepository : IAppUserProgressRepository
|
||||||
@ -198,6 +199,36 @@ public class AppUserProgressRepository : IAppUserProgressRepository
|
|||||||
return list.Count == 0 ? null : list.DefaultIfEmpty().Min();
|
return list.Count == 0 ? null : list.DefaultIfEmpty().Min();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAllProgressThatAreMoreThanChapterPages()
|
||||||
|
{
|
||||||
|
var updates = _context.AppUserProgresses
|
||||||
|
.Join(_context.Chapter,
|
||||||
|
progress => progress.ChapterId,
|
||||||
|
chapter => chapter.Id,
|
||||||
|
(progress, chapter) => new
|
||||||
|
{
|
||||||
|
Progress = progress,
|
||||||
|
Chapter = chapter
|
||||||
|
})
|
||||||
|
.Where(joinResult => joinResult.Progress.PagesRead > joinResult.Chapter.Pages)
|
||||||
|
.Select(result => new
|
||||||
|
{
|
||||||
|
ProgressId = result.Progress.Id,
|
||||||
|
NewPagesRead = Math.Min(result.Progress.PagesRead, result.Chapter.Pages)
|
||||||
|
})
|
||||||
|
.AsEnumerable();
|
||||||
|
|
||||||
|
foreach (var update in updates)
|
||||||
|
{
|
||||||
|
_context.AppUserProgresses
|
||||||
|
.Where(p => p.Id == update.ProgressId)
|
||||||
|
.ToList() // Execute the query to ensure exclusive lock
|
||||||
|
.ForEach(p => p.PagesRead = update.NewPagesRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
public async Task<AppUserProgress?> GetUserProgressAsync(int chapterId, int userId)
|
public async Task<AppUserProgress?> GetUserProgressAsync(int chapterId, int userId)
|
||||||
{
|
{
|
||||||
|
@ -175,13 +175,13 @@ public class ScrobblingService : IScrobblingService
|
|||||||
private async Task<string> GetTokenForProvider(int userId, ScrobbleProvider provider)
|
private async Task<string> GetTokenForProvider(int userId, ScrobbleProvider provider)
|
||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||||
if (user == null) return null;
|
if (user == null) return string.Empty;
|
||||||
|
|
||||||
return provider switch
|
return provider switch
|
||||||
{
|
{
|
||||||
ScrobbleProvider.AniList => user.AniListAccessToken,
|
ScrobbleProvider.AniList => user.AniListAccessToken,
|
||||||
_ => string.Empty
|
_ => string.Empty
|
||||||
};
|
} ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ScrobbleReviewUpdate(int userId, int seriesId, string reviewTitle, string reviewBody)
|
public async Task ScrobbleReviewUpdate(int userId, int seriesId, string reviewTitle, string reviewBody)
|
||||||
@ -192,11 +192,9 @@ public class ScrobblingService : IScrobblingService
|
|||||||
if (series == null) throw new KavitaException(await _localizationService.Translate(userId, "series-doesnt-exist"));
|
if (series == null) throw new KavitaException(await _localizationService.Translate(userId, "series-doesnt-exist"));
|
||||||
|
|
||||||
_logger.LogInformation("Processing Scrobbling review event for {UserId} on {SeriesName}", userId, series.Name);
|
_logger.LogInformation("Processing Scrobbling review event for {UserId} on {SeriesName}", userId, series.Name);
|
||||||
if (await CheckIfCanScrobble(userId, seriesId, series)) return;
|
if (await CheckIfCannotScrobble(userId, seriesId, series)) return;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(reviewTitle) || string.IsNullOrEmpty(reviewBody) || (reviewTitle.Length < 2200 ||
|
if (IsAniListReviewValid(reviewTitle, reviewBody))
|
||||||
reviewTitle.Length > 120 ||
|
|
||||||
reviewTitle.Length < 20))
|
|
||||||
{
|
{
|
||||||
_logger.LogDebug(
|
_logger.LogDebug(
|
||||||
"Rejecting Scrobble event for {Series}. Review is not long enough to meet requirements", series.Name);
|
"Rejecting Scrobble event for {Series}. Review is not long enough to meet requirements", series.Name);
|
||||||
@ -232,6 +230,13 @@ public class ScrobblingService : IScrobblingService
|
|||||||
_logger.LogDebug("Added Scrobbling Review update on {SeriesName} with Userid {UserId} ", series.Name, userId);
|
_logger.LogDebug("Added Scrobbling Review update on {SeriesName} with Userid {UserId} ", series.Name, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsAniListReviewValid(string reviewTitle, string reviewBody)
|
||||||
|
{
|
||||||
|
return string.IsNullOrEmpty(reviewTitle) || string.IsNullOrEmpty(reviewBody) || (reviewTitle.Length < 2200 ||
|
||||||
|
reviewTitle.Length > 120 ||
|
||||||
|
reviewTitle.Length < 20);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task ScrobbleRatingUpdate(int userId, int seriesId, float rating)
|
public async Task ScrobbleRatingUpdate(int userId, int seriesId, float rating)
|
||||||
{
|
{
|
||||||
if (!await _licenseService.HasActiveLicense()) return;
|
if (!await _licenseService.HasActiveLicense()) return;
|
||||||
@ -240,7 +245,7 @@ public class ScrobblingService : IScrobblingService
|
|||||||
if (series == null) throw new KavitaException(await _localizationService.Translate(userId, "series-doesnt-exist"));
|
if (series == null) throw new KavitaException(await _localizationService.Translate(userId, "series-doesnt-exist"));
|
||||||
|
|
||||||
_logger.LogInformation("Processing Scrobbling rating event for {UserId} on {SeriesName}", userId, series.Name);
|
_logger.LogInformation("Processing Scrobbling rating event for {UserId} on {SeriesName}", userId, series.Name);
|
||||||
if (await CheckIfCanScrobble(userId, seriesId, series)) return;
|
if (await CheckIfCannotScrobble(userId, seriesId, series)) return;
|
||||||
|
|
||||||
var existingEvt = await _unitOfWork.ScrobbleRepository.GetEvent(userId, series.Id,
|
var existingEvt = await _unitOfWork.ScrobbleRepository.GetEvent(userId, series.Id,
|
||||||
ScrobbleEventType.ScoreUpdated);
|
ScrobbleEventType.ScoreUpdated);
|
||||||
@ -279,7 +284,7 @@ public class ScrobblingService : IScrobblingService
|
|||||||
if (series == null) throw new KavitaException(await _localizationService.Translate(userId, "series-doesnt-exist"));
|
if (series == null) throw new KavitaException(await _localizationService.Translate(userId, "series-doesnt-exist"));
|
||||||
|
|
||||||
_logger.LogInformation("Processing Scrobbling reading event for {UserId} on {SeriesName}", userId, series.Name);
|
_logger.LogInformation("Processing Scrobbling reading event for {UserId} on {SeriesName}", userId, series.Name);
|
||||||
if (await CheckIfCanScrobble(userId, seriesId, series)) return;
|
if (await CheckIfCannotScrobble(userId, seriesId, series)) return;
|
||||||
|
|
||||||
var existingEvt = await _unitOfWork.ScrobbleRepository.GetEvent(userId, series.Id,
|
var existingEvt = await _unitOfWork.ScrobbleRepository.GetEvent(userId, series.Id,
|
||||||
ScrobbleEventType.ChapterRead);
|
ScrobbleEventType.ChapterRead);
|
||||||
@ -334,11 +339,11 @@ public class ScrobblingService : IScrobblingService
|
|||||||
if (series == null) throw new KavitaException(await _localizationService.Translate(userId, "series-doesnt-exist"));
|
if (series == null) throw new KavitaException(await _localizationService.Translate(userId, "series-doesnt-exist"));
|
||||||
|
|
||||||
_logger.LogInformation("Processing Scrobbling want-to-read event for {UserId} on {SeriesName}", userId, series.Name);
|
_logger.LogInformation("Processing Scrobbling want-to-read event for {UserId} on {SeriesName}", userId, series.Name);
|
||||||
if (await CheckIfCanScrobble(userId, seriesId, series)) return;
|
if (await CheckIfCannotScrobble(userId, seriesId, series)) return;
|
||||||
|
|
||||||
var existing = await _unitOfWork.ScrobbleRepository.Exists(userId, series.Id,
|
var existing = await _unitOfWork.ScrobbleRepository.Exists(userId, series.Id,
|
||||||
onWantToRead ? ScrobbleEventType.AddWantToRead : ScrobbleEventType.RemoveWantToRead);
|
onWantToRead ? ScrobbleEventType.AddWantToRead : ScrobbleEventType.RemoveWantToRead);
|
||||||
if (existing) return;
|
if (existing) return; // BUG: If I take a series and add to remove from want to read, then add to want to read, Kavita rejects the second as a duplicate, when it's not
|
||||||
|
|
||||||
var evt = new ScrobbleEvent()
|
var evt = new ScrobbleEvent()
|
||||||
{
|
{
|
||||||
@ -355,7 +360,7 @@ public class ScrobblingService : IScrobblingService
|
|||||||
_logger.LogDebug("Added Scrobbling WantToRead update on {SeriesName} with Userid {UserId} ", series.Name, userId);
|
_logger.LogDebug("Added Scrobbling WantToRead update on {SeriesName} with Userid {UserId} ", series.Name, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> CheckIfCanScrobble(int userId, int seriesId, Series series)
|
private async Task<bool> CheckIfCannotScrobble(int userId, int seriesId, Series series)
|
||||||
{
|
{
|
||||||
if (await _unitOfWork.UserRepository.HasHoldOnSeries(userId, seriesId))
|
if (await _unitOfWork.UserRepository.HasHoldOnSeries(userId, seriesId))
|
||||||
{
|
{
|
||||||
@ -616,7 +621,7 @@ public class ScrobblingService : IScrobblingService
|
|||||||
await SetAndCheckRateLimit(userRateLimits, user, license.Value);
|
await SetAndCheckRateLimit(userRateLimits, user, license.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalProgress = readEvents.Count + addToWantToRead.Count + removeWantToRead.Count + ratingEvents.Count + decisions.Count + reviewEvents.Count;
|
var totalProgress = readEvents.Count + decisions.Count + ratingEvents.Count + decisions.Count + reviewEvents.Count;
|
||||||
|
|
||||||
_logger.LogInformation("Found {TotalEvents} Scrobble Events", totalProgress);
|
_logger.LogInformation("Found {TotalEvents} Scrobble Events", totalProgress);
|
||||||
try
|
try
|
||||||
@ -693,6 +698,24 @@ public class ScrobblingService : IScrobblingService
|
|||||||
LocalizedSeriesName = evt.Series.LocalizedName,
|
LocalizedSeriesName = evt.Series.LocalizedName,
|
||||||
Year = evt.Series.Metadata.ReleaseYear
|
Year = evt.Series.Metadata.ReleaseYear
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// After decisions, we need to mark all the want to read and remove from want to read as completed
|
||||||
|
if (decisions.All(d => d.IsProcessed))
|
||||||
|
{
|
||||||
|
foreach (var scrobbleEvent in addToWantToRead)
|
||||||
|
{
|
||||||
|
scrobbleEvent.IsProcessed = true;
|
||||||
|
scrobbleEvent.ProcessDateUtc = DateTime.UtcNow;
|
||||||
|
_unitOfWork.ScrobbleRepository.Update(scrobbleEvent);
|
||||||
|
}
|
||||||
|
foreach (var scrobbleEvent in removeWantToRead)
|
||||||
|
{
|
||||||
|
scrobbleEvent.IsProcessed = true;
|
||||||
|
scrobbleEvent.ProcessDateUtc = DateTime.UtcNow;
|
||||||
|
_unitOfWork.ScrobbleRepository.Update(scrobbleEvent);
|
||||||
|
}
|
||||||
|
await _unitOfWork.CommitAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (FlurlHttpException)
|
catch (FlurlHttpException)
|
||||||
{
|
{
|
||||||
|
@ -26,6 +26,7 @@ public interface ICleanupService
|
|||||||
Task CleanupBackups();
|
Task CleanupBackups();
|
||||||
Task CleanupLogs();
|
Task CleanupLogs();
|
||||||
void CleanupTemp();
|
void CleanupTemp();
|
||||||
|
Task EnsureChapterProgressIsCapped();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Responsible to remove Series from Want To Read when user's have fully read the series and the series has Publication Status of Completed or Cancelled.
|
/// Responsible to remove Series from Want To Read when user's have fully read the series and the series has Publication Status of Completed or Cancelled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -89,6 +90,8 @@ public class CleanupService : ICleanupService
|
|||||||
await DeleteReadingListCoverImages();
|
await DeleteReadingListCoverImages();
|
||||||
await SendProgress(0.8F, "Cleaning old logs");
|
await SendProgress(0.8F, "Cleaning old logs");
|
||||||
await CleanupLogs();
|
await CleanupLogs();
|
||||||
|
await SendProgress(0.9F, "Cleaning progress events that exceed 100%");
|
||||||
|
await EnsureChapterProgressIsCapped();
|
||||||
await SendProgress(1F, "Cleanup finished");
|
await SendProgress(1F, "Cleanup finished");
|
||||||
_logger.LogInformation("Cleanup finished");
|
_logger.LogInformation("Cleanup finished");
|
||||||
}
|
}
|
||||||
@ -243,6 +246,17 @@ public class CleanupService : ICleanupService
|
|||||||
_logger.LogInformation("Temp directory purged");
|
_logger.LogInformation("Temp directory purged");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that each chapter's progress (pages read) is capped at the total pages. This can get out of sync when a chapter is replaced after being read with one with lower page count.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task EnsureChapterProgressIsCapped()
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Cleaning up any progress rows that exceed chapter page count");
|
||||||
|
await _unitOfWork.AppUserProgressRepository.UpdateAllProgressThatAreMoreThanChapterPages();
|
||||||
|
_logger.LogInformation("Cleaning up any progress rows that exceed chapter page count - complete");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This does not cleanup any Series that are not Completed or Cancelled
|
/// This does not cleanup any Series that are not Completed or Cancelled
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -84,9 +84,11 @@ export class BookLineOverlayComponent implements OnInit {
|
|||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
if (!event.target) return;
|
if (!event.target) return;
|
||||||
|
|
||||||
if ((!selection || selection.toString().trim() === '' || selection.toString().trim() === this.selectedText)) {
|
if ((selection === null || selection === undefined || selection.toString().trim() === '' || selection.toString().trim() === this.selectedText)) {
|
||||||
event.preventDefault();
|
if (this.selectedText !== '') {
|
||||||
event.stopPropagation();
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
this.reset();
|
this.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1643,8 +1643,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
if (this.clickToPaginate) {
|
if (this.clickToPaginate) {
|
||||||
if (this.isCursorOverLeftPaginationArea(event)) {
|
if (this.isCursorOverLeftPaginationArea(event)) {
|
||||||
this.movePage(this.readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD);
|
this.movePage(this.readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD);
|
||||||
|
return;
|
||||||
} else if (this.isCursorOverRightPaginationArea(event)) {
|
} else if (this.isCursorOverRightPaginationArea(event)) {
|
||||||
this.movePage(this.readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)
|
this.movePage(this.readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
|
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy} from '@angular/core';
|
||||||
import { FormControl, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
import { FormControl, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
@ -9,6 +9,7 @@ import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
import { NgIf, NgFor, NgTemplateOutlet } from '@angular/common';
|
import { NgIf, NgFor, NgTemplateOutlet } from '@angular/common';
|
||||||
import { SplashContainerComponent } from '../splash-container/splash-container.component';
|
import { SplashContainerComponent } from '../splash-container/splash-container.component';
|
||||||
import {translate, TranslocoDirective} from "@ngneat/transloco";
|
import {translate, TranslocoDirective} from "@ngneat/transloco";
|
||||||
|
import {take} from "rxjs/operators";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-confirm-email',
|
selector: 'app-confirm-email',
|
||||||
@ -18,7 +19,7 @@ import {translate, TranslocoDirective} from "@ngneat/transloco";
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [SplashContainerComponent, NgIf, NgFor, ReactiveFormsModule, NgbTooltip, NgTemplateOutlet, TranslocoDirective]
|
imports: [SplashContainerComponent, NgIf, NgFor, ReactiveFormsModule, NgbTooltip, NgTemplateOutlet, TranslocoDirective]
|
||||||
})
|
})
|
||||||
export class ConfirmEmailComponent {
|
export class ConfirmEmailComponent implements OnDestroy {
|
||||||
/**
|
/**
|
||||||
* Email token used for validating
|
* Email token used for validating
|
||||||
*/
|
*/
|
||||||
@ -55,6 +56,14 @@ export class ConfirmEmailComponent {
|
|||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||||
|
if (user) {
|
||||||
|
this.navService.showSideNav();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
isNullOrEmpty(v: string | null | undefined) {
|
isNullOrEmpty(v: string | null | undefined) {
|
||||||
return v == undefined || v === '' || v === null;
|
return v == undefined || v === '' || v === null;
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,12 @@ export class EditListComponent implements OnInit {
|
|||||||
const tokenToRemove = tokens[index];
|
const tokenToRemove = tokens[index];
|
||||||
|
|
||||||
this.combinedItems = tokens.filter(t => t != tokenToRemove).join(',');
|
this.combinedItems = tokens.filter(t => t != tokenToRemove).join(',');
|
||||||
this.form.removeControl('link' + index, {emitEvent: true});
|
for (const [index, [key, value]] of Object.entries(Object.entries(this.form.controls))) {
|
||||||
|
if (key.startsWith('link') && this.form.get(key)?.value === tokenToRemove) {
|
||||||
|
this.form.removeControl('link' + index, {emitEvent: true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.emit();
|
this.emit();
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
@ -115,9 +115,10 @@
|
|||||||
<div ngbAccordionCollapse>
|
<div ngbAccordionCollapse>
|
||||||
<div ngbAccordionBody>
|
<div ngbAccordionBody>
|
||||||
<ng-template>
|
<ng-template>
|
||||||
<span class="mb-2">{{t('exclude-patterns-tooltip')}}</span>
|
<span >{{t('exclude-patterns-tooltip')}}<a class="ms-1" href="https://wiki.kavitareader.com/en/guides/managing-your-files/scanner/excluding-files-folders" rel="noopener noreferrer" target="_blank">{{t('help')}}<i class="fa fa-external-link-alt ms-1" aria-hidden="true"></i></a></span>
|
||||||
<a class="ms-1" href="https://wiki.kavitareader.com/en/guides/managing-your-files/scanner/excluding-files-folders" rel="noopener noreferrer" target="_blank">{{t('help')}}<i class="fa fa-external-link-alt ms-1" aria-hidden="true"></i></a>
|
<div class="mt-2">
|
||||||
<app-edit-list [items]="excludePatterns" [label]="t('exclude-patterns-label')" (updateItems)="updateGlobs($event)"></app-edit-list>
|
<app-edit-list [items]="excludePatterns" [label]="t('exclude-patterns-label')" (updateItems)="updateGlobs($event)"></app-edit-list>
|
||||||
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,7 +32,7 @@ $image-width: 160px;
|
|||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
width: 130px;
|
width: 140px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"name": "GPL-3.0",
|
"name": "GPL-3.0",
|
||||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||||
},
|
},
|
||||||
"version": "0.7.10.17"
|
"version": "0.7.10.20"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user