Polish Round 4 (#2429)

This commit is contained in:
Joe Milazzo 2023-11-12 08:29:46 -06:00 committed by GitHub
parent cd7f876140
commit ee72727841
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 666 additions and 599 deletions

View File

@ -19,3 +19,6 @@ trim_trailing_whitespace = false
[*.yml]
indent_size = 2
[*.csproj]
indent_size = 2

View File

@ -62,7 +62,7 @@ public class DefaultParserTests
[Theory]
[InlineData("/manga/Btooom!/Vol.1/Chapter 1/1.cbz", "Btooom!")]
[InlineData("/manga/Btooom!/Vol.1 Chapter 2/1.cbz", "Btooom!")]
[InlineData("/manga/Monster #8 (Digital)/Ch. 001-016 [MangaPlus] [Digital] [amit34521]/Monster #8 Ch. 001 [MangaPlus] [Digital] [amit34521]/13.jpg", "Monster")]
[InlineData("/manga/Monster #8 (Digital)/Ch. 001-016 [MangaPlus] [Digital] [amit34521]/Monster #8 Ch. 001 [MangaPlus] [Digital] [amit34521]/13.jpg", "manga")]
[InlineData("/manga/Monster (Digital)/Ch. 001-016 [MangaPlus] [Digital] [amit34521]/Monster Ch. 001 [MangaPlus] [Digital] [amit34521]/13.jpg", "Monster")]
[InlineData("/manga/Foo 50/Specials/Foo 50 SP01.cbz", "Foo 50")]
[InlineData("/manga/Foo 50 (kiraa)/Specials/Foo 50 SP01.cbz", "Foo 50")]
@ -293,7 +293,7 @@ public class DefaultParserTests
var expectedInfo2 = new ParserInfo
{
Series = "Monster #8", Volumes = "0", Edition = "",
Chapters = "1", Filename = "13.jpg", Format = MangaFormat.Image,
Chapters = "8", Filename = "13.jpg", Format = MangaFormat.Image,
FullFilePath = filepath, IsSpecial = false
};
var actual2 = _defaultParser.Parse(filepath, @"E:\Manga\Monster #8");
@ -314,7 +314,7 @@ public class DefaultParserTests
Assert.Equal(expectedInfo2.FullFilePath, actual2.FullFilePath);
_testOutputHelper.WriteLine("FullFilePath ✓");
filepath = @"E:\Manga\Extra layer for no reason\Just Images the second\Vol19\ch186\Vol. 19 p106.gif";
filepath = @"E:\Manga\Extra layer for no reason\Just Images the second\Vol19\ch. 186\Vol. 19 p106.gif";
expectedInfo2 = new ParserInfo
{
Series = "Just Images the second", Volumes = "19", Edition = "",
@ -340,7 +340,7 @@ public class DefaultParserTests
Assert.Equal(expectedInfo2.FullFilePath, actual2.FullFilePath);
_testOutputHelper.WriteLine("FullFilePath ✓");
filepath = @"E:\Manga\Extra layer for no reason\Just Images the second\Blank Folder\Vol19\ch186\Vol. 19 p106.gif";
filepath = @"E:\Manga\Extra layer for no reason\Just Images the second\Blank Folder\Vol19\ch. 186\Vol. 19 p106.gif";
expectedInfo2 = new ParserInfo
{
Series = "Just Images the second", Volumes = "19", Edition = "",

View File

@ -657,7 +657,7 @@ public class SeriesServiceTests : AbstractDbTest
{
SeriesId = 1,
Publishers = new List<PersonDto>() {new () {Id = 0, Name = "Existing Person", Role = PersonRole.Publisher}},
PublishersLocked = true
PublisherLocked = true
},
CollectionTags = new List<CollectionTagDto>()
});

View File

@ -53,6 +53,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.13">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="Docnet.Core" Version="2.6.0" />
<PackageReference Include="EasyCaching.InMemory" Version="1.9.2" />
@ -60,28 +64,15 @@
<PackageReference Include="Flurl" Version="3.0.7" />
<PackageReference Include="Flurl.Http" Version="3.2.4" />
<PackageReference Include="Hangfire" Version="1.8.6" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.6" />
<PackageReference Include="Hangfire.InMemory" Version="0.6.0" />
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.3.4" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.54" />
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.13" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.13" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.13" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.13">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.13" />
<PackageReference Include="EasyCaching.InMemory" Version="1.9.2" />
<PackageReference Include="ExCSS" Version="4.2.4" />
<PackageReference Include="Hangfire" Version="1.8.6" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.6" />
<PackageReference Include="Hangfire.InMemory" Version="0.6.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.54" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.13" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.13" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.13" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.13" />

View File

@ -437,8 +437,7 @@ public class SeriesController : BaseApiController
[HttpGet("metadata")]
public async Task<ActionResult<SeriesMetadataDto>> GetSeriesMetadata(int seriesId)
{
var metadata = await _unitOfWork.SeriesRepository.GetSeriesMetadata(seriesId);
return Ok(metadata);
return Ok(await _unitOfWork.SeriesRepository.GetSeriesMetadata(seriesId));
}
/// <summary>

View File

@ -75,16 +75,16 @@ public class SeriesMetadataDto
public bool PublicationStatusLocked { get; set; }
public bool GenresLocked { get; set; }
public bool TagsLocked { get; set; }
public bool WritersLocked { get; set; }
public bool CharactersLocked { get; set; }
public bool ColoristsLocked { get; set; }
public bool EditorsLocked { get; set; }
public bool InkersLocked { get; set; }
public bool LetterersLocked { get; set; }
public bool PencillersLocked { get; set; }
public bool PublishersLocked { get; set; }
public bool TranslatorsLocked { get; set; }
public bool CoverArtistsLocked { get; set; }
public bool WriterLocked { get; set; }
public bool CharacterLocked { get; set; }
public bool ColoristLocked { get; set; }
public bool EditorLocked { get; set; }
public bool InkerLocked { get; set; }
public bool LettererLocked { get; set; }
public bool PencillerLocked { get; set; }
public bool PublisherLocked { get; set; }
public bool TranslatorLocked { get; set; }
public bool CoverArtistLocked { get; set; }
public bool ReleaseYearLocked { get; set; }

View File

@ -73,12 +73,14 @@ public class AccountService : IAccountService
basePart = serverSettings.HostName;
if (!serverSettings.BaseUrl.Equals(Configuration.DefaultBaseUrl))
{
basePart += serverSettings.BaseUrl.Substring(0, serverSettings.BaseUrl.Length - 1);
var removeCount = serverSettings.BaseUrl.EndsWith("/") ? 2 : 1;
basePart += serverSettings.BaseUrl.Substring(0, serverSettings.BaseUrl.Length - removeCount);
}
}
if (withHost) return $"{basePart}/registration/{routePart}?token={HttpUtility.UrlEncode(token)}&email={HttpUtility.UrlEncode(email)}".Replace("//", "/");
return $"registration/{routePart}?token={HttpUtility.UrlEncode(token)}&email={HttpUtility.UrlEncode(email)}".Replace("//", "/");
if (withHost) return $"{basePart}/registration/{routePart}?token={HttpUtility.UrlEncode(token)}&email={HttpUtility.UrlEncode(email)}";
return $"registration/{routePart}?token={HttpUtility.UrlEncode(token)}&email={HttpUtility.UrlEncode(email)}"
.Replace("//", "/");
}
public async Task<IEnumerable<ApiException>> ChangeUserPassword(AppUser user, string newPassword)

View File

@ -107,6 +107,7 @@ public class SeriesService : ISeriesService
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
if (series == null) return false;
var allCollectionTags = (await _unitOfWork.CollectionTagRepository.GetAllTagsAsync()).ToList();
// TODO: This is Diesel's performance problem with Komf. For some systems, this is too heavy of a call if komf is spamming updates.
var allGenres = (await _unitOfWork.GenreRepository.GetAllGenresAsync()).ToList();
var allPeople = (await _unitOfWork.PersonRepository.GetAllPeople()).ToList();
var allTags = (await _unitOfWork.TagRepository.GetAllTagsAsync()).ToList();
@ -219,16 +220,16 @@ public class SeriesService : ISeriesService
series.Metadata.LanguageLocked = updateSeriesMetadataDto.SeriesMetadata.LanguageLocked;
series.Metadata.GenresLocked = updateSeriesMetadataDto.SeriesMetadata.GenresLocked;
series.Metadata.TagsLocked = updateSeriesMetadataDto.SeriesMetadata.TagsLocked;
series.Metadata.CharacterLocked = updateSeriesMetadataDto.SeriesMetadata.CharactersLocked;
series.Metadata.ColoristLocked = updateSeriesMetadataDto.SeriesMetadata.ColoristsLocked;
series.Metadata.EditorLocked = updateSeriesMetadataDto.SeriesMetadata.EditorsLocked;
series.Metadata.InkerLocked = updateSeriesMetadataDto.SeriesMetadata.InkersLocked;
series.Metadata.LettererLocked = updateSeriesMetadataDto.SeriesMetadata.LetterersLocked;
series.Metadata.PencillerLocked = updateSeriesMetadataDto.SeriesMetadata.PencillersLocked;
series.Metadata.PublisherLocked = updateSeriesMetadataDto.SeriesMetadata.PublishersLocked;
series.Metadata.TranslatorLocked = updateSeriesMetadataDto.SeriesMetadata.TranslatorsLocked;
series.Metadata.CoverArtistLocked = updateSeriesMetadataDto.SeriesMetadata.CoverArtistsLocked;
series.Metadata.WriterLocked = updateSeriesMetadataDto.SeriesMetadata.WritersLocked;
series.Metadata.CharacterLocked = updateSeriesMetadataDto.SeriesMetadata.CharacterLocked;
series.Metadata.ColoristLocked = updateSeriesMetadataDto.SeriesMetadata.ColoristLocked;
series.Metadata.EditorLocked = updateSeriesMetadataDto.SeriesMetadata.EditorLocked;
series.Metadata.InkerLocked = updateSeriesMetadataDto.SeriesMetadata.InkerLocked;
series.Metadata.LettererLocked = updateSeriesMetadataDto.SeriesMetadata.LettererLocked;
series.Metadata.PencillerLocked = updateSeriesMetadataDto.SeriesMetadata.PencillerLocked;
series.Metadata.PublisherLocked = updateSeriesMetadataDto.SeriesMetadata.PublisherLocked;
series.Metadata.TranslatorLocked = updateSeriesMetadataDto.SeriesMetadata.TranslatorLocked;
series.Metadata.CoverArtistLocked = updateSeriesMetadataDto.SeriesMetadata.CoverArtistLocked;
series.Metadata.WriterLocked = updateSeriesMetadataDto.SeriesMetadata.WriterLocked;
series.Metadata.SummaryLocked = updateSeriesMetadataDto.SeriesMetadata.SummaryLocked;
series.Metadata.ReleaseYearLocked = updateSeriesMetadataDto.SeriesMetadata.ReleaseYearLocked;

View File

@ -119,19 +119,46 @@ public class DefaultParser : IDefaultParser
{
ret.Volumes = Parser.DefaultVolume;
ret.Chapters = Parser.DefaultChapter;
// Next we need to see if the image has a folder between rootPath and filePath.
// if so, take that folder as a volume 0 chapter 0 special and group everything under there (if we can't parse a volume/chapter)
var directoryName = _directoryService.FileSystem.DirectoryInfo.New(rootPath).Name;
ret.Series = directoryName;
ParseFromFallbackFolders(filePath, rootPath, LibraryType.Image, ref ret);
if ((string.IsNullOrEmpty(ret.Chapters) || ret.Chapters == Parser.DefaultChapter) &&
(string.IsNullOrEmpty(ret.Volumes) || ret.Volumes == Parser.DefaultVolume))
if (IsEmptyOrDefault(ret.Volumes, ret.Chapters))
{
ret.IsSpecial = true;
}
else
{
var parsedVolume = Parser.ParseVolume(ret.Filename);
var parsedChapter = Parser.ParseChapter(ret.Filename);
if (IsEmptyOrDefault(ret.Volumes, string.Empty) && !parsedVolume.Equals(Parser.DefaultVolume))
{
ret.Volumes = parsedVolume;
}
if (IsEmptyOrDefault(string.Empty, ret.Chapters) && !parsedChapter.Equals(Parser.DefaultChapter))
{
ret.Chapters = parsedChapter;
}
}
// Override the series name, as fallback folders needs it to try and parse folder name
if (string.IsNullOrEmpty(ret.Series) || ret.Series.Equals(directoryName))
{
ret.Series = Parser.CleanTitle(directoryName, replaceSpecials: false);
}
ret.Series = _directoryService.FileSystem.DirectoryInfo.New(rootPath).Name;
return ret;
}
private static bool IsEmptyOrDefault(string volumes, string chapters)
{
return (string.IsNullOrEmpty(chapters) || chapters == Parser.DefaultChapter) &&
(string.IsNullOrEmpty(volumes) || volumes == Parser.DefaultVolume);
}
/// <summary>
/// Fills out <see cref="ParserInfo"/> by trying to parse volume, chapters, and series from folders
/// </summary>
@ -193,7 +220,7 @@ public class DefaultParser : IDefaultParser
break;
}
if (!string.IsNullOrEmpty(series) && (string.IsNullOrEmpty(ret.Series) || !folder.Contains(ret.Series)))
if (!string.IsNullOrEmpty(series) && (string.IsNullOrEmpty(ret.Series) && !folder.Contains(ret.Series)))
{
ret.Series = series;
break;

View File

@ -843,13 +843,15 @@ public static class Parser
/// <param name="isComic"></param>
/// <returns></returns>
public static string CleanTitle(string title, bool isComic = false)
public static string CleanTitle(string title, bool isComic = false, bool replaceSpecials = true)
{
title = ReplaceUnderscores(title);
title = RemoveEditionTagHolders(title);
if (replaceSpecials)
{
if (isComic)
{
title = RemoveComicSpecialTags(title);
@ -859,6 +861,8 @@ public static class Parser
{
title = RemoveMangaSpecialTags(title);
}
}
title = title.Trim(SpacesAndSeparators);

View File

@ -293,6 +293,9 @@ public class ProcessSeries : IProcessSeries
if ((maxChapter == 0 || maxChapter > series.Metadata.TotalCount) && maxVolume <= series.Metadata.TotalCount)
{
series.Metadata.MaxCount = maxVolume;
} else if (maxVolume == series.Metadata.TotalCount)
{
series.Metadata.MaxCount = maxVolume;
}

170
UI/Web/package-lock.json generated
View File

@ -8,16 +8,16 @@
"name": "kavita-webui",
"version": "0.4.2",
"dependencies": {
"@angular/animations": "^17.0.1",
"@angular/animations": "^17.0.2",
"@angular/cdk": "^17.0.0",
"@angular/common": "^17.0.1",
"@angular/compiler": "^17.0.1",
"@angular/core": "^17.0.1",
"@angular/forms": "^17.0.1",
"@angular/localize": "^17.0.1",
"@angular/platform-browser": "^17.0.1",
"@angular/platform-browser-dynamic": "^17.0.1",
"@angular/router": "^17.0.1",
"@angular/common": "^17.0.2",
"@angular/compiler": "^17.0.2",
"@angular/core": "^17.0.2",
"@angular/forms": "^17.0.2",
"@angular/localize": "^17.0.2",
"@angular/platform-browser": "^17.0.2",
"@angular/platform-browser-dynamic": "^17.0.2",
"@angular/router": "^17.0.2",
"@fortawesome/fontawesome-free": "^6.4.2",
"@iharbeck/ngx-virtual-scroller": "^16.0.0",
"@iplab/ngx-file-upload": "^16.0.2",
@ -29,7 +29,7 @@
"@ngneat/transloco-persist-translations": "^5.0.0",
"@ngneat/transloco-preload-langs": "^5.0.0",
"@popperjs/core": "^2.11.7",
"@swimlane/ngx-charts": "^20.1.2",
"@swimlane/ngx-charts": "^20.5.0",
"@tweenjs/tween.js": "^21.0.0",
"bootstrap": "^5.3.2",
"charts.css": "^1.1.0",
@ -52,13 +52,13 @@
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.0.0",
"@angular-eslint/builder": "^17.0.0",
"@angular-eslint/builder": "^17.0.1",
"@angular-eslint/eslint-plugin": "^17.0.0",
"@angular-eslint/eslint-plugin-template": "^17.0.0",
"@angular-eslint/schematics": "^17.0.1",
"@angular-eslint/template-parser": "^17.0.1",
"@angular/cli": "^17.0.0",
"@angular/compiler-cli": "^17.0.1",
"@angular/compiler-cli": "^17.0.2",
"@types/d3": "^7.4.3",
"@types/file-saver": "^2.0.7",
"@types/luxon": "^3.3.4",
@ -744,9 +744,9 @@
}
},
"node_modules/@angular-eslint/builder": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-17.0.0.tgz",
"integrity": "sha512-cquqJH0R/IIh2PElcGXdo9FTcrkwO78H2MXk9ChGFBjQrYjihFLhFm12VuQsih7X6bJjA0cmr2PL1KbtgjMk1Q==",
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-17.0.1.tgz",
"integrity": "sha512-bNXi5tdqIFdNDHxphDRUUbzA+7v6emOX2B/PFLG2pe+K6/JpHS0auwY/nq7hCroH7pMS5HZ+Q4i90q0GN/DWPg==",
"dev": true,
"dependencies": {
"@nx/devkit": "17.0.3",
@ -842,9 +842,9 @@
}
},
"node_modules/@angular/animations": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.0.1.tgz",
"integrity": "sha512-Uee6E8zyU6XjDfKFozybcf+JZy0nUFQ1bUEmRwFP5HvYJSSJ5YiUDokNiVxyn9znwZ7zKHlM6Bq9ZY9cCmeKKQ==",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-17.0.2.tgz",
"integrity": "sha512-32RHWhTgFLMonI3kRdstACay/nvetfxXjdwcTtABjcvBoND7nD9GMhkISQdgS+hcR/IhgXxaPidq8f2UAY5DBw==",
"dependencies": {
"tslib": "^2.3.0"
},
@ -852,7 +852,7 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
"@angular/core": "17.0.1"
"@angular/core": "17.0.2"
}
},
"node_modules/@angular/cdk": {
@ -939,9 +939,9 @@
"dev": true
},
"node_modules/@angular/common": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-17.0.1.tgz",
"integrity": "sha512-AvvhZc+PhX5lVEW/Vorxe3Zf1rIEJJvfduRuRv+nsjijo3ZGjdgYjTYEx4ighZgH60RLIAuwyBE24gPkT2Pm7g==",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-17.0.2.tgz",
"integrity": "sha512-hCW0njHgrcwTWNoKZDwf02DnhYLVWNXM2FMw66MKpfxTp7McSyaXjGBU9/hchW3dZJ0xTwyxoyoqJFoHYvg0yg==",
"dependencies": {
"tslib": "^2.3.0"
},
@ -949,14 +949,14 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
"@angular/core": "17.0.1",
"@angular/core": "17.0.2",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/compiler": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.0.1.tgz",
"integrity": "sha512-qlKqCvjoxPHJ1e8+UMaBl/n9zzrmGXI5eWMVhULSvQnQvPWkwNlUh5XFeoSFcTEQxORjaO2/08Z31DmTJAqlPA==",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.0.2.tgz",
"integrity": "sha512-ewUFbKhMEhAmw2dGfk0ImhTlyrO2y4pJSKIZdFrkR1d0HiJX8bCHUdTiiR/2jeP7w2eamjXj15Rptb+iZZes2Q==",
"dependencies": {
"tslib": "^2.3.0"
},
@ -964,7 +964,7 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
"@angular/core": "17.0.1"
"@angular/core": "17.0.2"
},
"peerDependenciesMeta": {
"@angular/core": {
@ -973,9 +973,9 @@
}
},
"node_modules/@angular/compiler-cli": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.0.1.tgz",
"integrity": "sha512-Rnvh2V2CYhG7NR5VI4cESGKk9jyqLat0HoqXa06v3TtbjkiZyjjwh0SyZ8NYOBMkQeWiQTHGcgxGvjKD3L3qqA==",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.0.2.tgz",
"integrity": "sha512-IUYL3Yz5RbR0Z0/x7it4GK3sMb2qVihxu0tlgfUW53P1Vi6nU/Zda0bCJTu6Z64qEtS8zwCwF1Ekomuq6UaiKg==",
"dev": true,
"dependencies": {
"@babel/core": "7.23.2",
@ -996,14 +996,14 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
"@angular/compiler": "17.0.1",
"@angular/compiler": "17.0.2",
"typescript": ">=5.2 <5.3"
}
},
"node_modules/@angular/core": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-17.0.1.tgz",
"integrity": "sha512-yVwU+oz0G8g6Q5ORyOCpgqMPdSiCdfW+uQhjI37WROnXHja3jY843AqrYTKE6mMx1r6q9h1wbDy+x2E61OWP7A==",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-17.0.2.tgz",
"integrity": "sha512-MjDxWeyn3Txi0qo/V/I+B/gndh0uptQ0XWgBRwOx6Wcr5zRGeZIFlXBxPpyXnGTlJkeyErsTN7FfFCZ4C3kCPA==",
"dependencies": {
"tslib": "^2.3.0"
},
@ -1016,9 +1016,9 @@
}
},
"node_modules/@angular/forms": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.0.1.tgz",
"integrity": "sha512-FpmUf2kgzwZXVbFB4VrwbnrO0m88QLUBsDsbLfQVQQwb7KxwSaftUu/aIrjst1gFCdl9k0Vqtrq2gwLZKzdSGQ==",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.0.2.tgz",
"integrity": "sha512-w1QKifaVG4daxUktcBNZqBtOH1vn8t0YiwDR3woEdUYt0XYKMipfDzQfyIK+6fIVPOJUd42pRns1nbWJQHOInA==",
"dependencies": {
"tslib": "^2.3.0"
},
@ -1026,16 +1026,16 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
"@angular/common": "17.0.1",
"@angular/core": "17.0.1",
"@angular/platform-browser": "17.0.1",
"@angular/common": "17.0.2",
"@angular/core": "17.0.2",
"@angular/platform-browser": "17.0.2",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/localize": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/@angular/localize/-/localize-17.0.1.tgz",
"integrity": "sha512-pNLLnEbXjoW1agKwA4cBcM/HnqGuwQMpIhx9H46Y/oC2JkAvTCMVyXLbZUESeXmhysC9x2JDmF+Awhu7JzVVCA==",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/localize/-/localize-17.0.2.tgz",
"integrity": "sha512-ct8xEy8Xk+PRfjrHLu7uywSQDzozmzlz6ptUCuYkRHrS4rJabXn3c0Sz4w+mh9B58qrK6KM+JSmXEZngEMXMTw==",
"dependencies": {
"@babel/core": "7.23.2",
"fast-glob": "3.3.1",
@ -1050,14 +1050,14 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
"@angular/compiler": "17.0.1",
"@angular/compiler-cli": "17.0.1"
"@angular/compiler": "17.0.2",
"@angular/compiler-cli": "17.0.2"
}
},
"node_modules/@angular/platform-browser": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.0.1.tgz",
"integrity": "sha512-JpvU0YDEM5KYdHtxC0Kdzk/hdwvZPq5vju5lTmIjTVa2OOabApOrQ6cq1MpKlrvjv1rw8MClHIM0l5Y0g9KH5g==",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.0.2.tgz",
"integrity": "sha512-eTnPILEA/eAMkVUR/+g6fWhhMTmnmOzcZSGX/bBgQcvOhayZrDDxA6/Qf+jIB4RwC0wd3KA9zT5BCMmNojoUsg==",
"dependencies": {
"tslib": "^2.3.0"
},
@ -1065,9 +1065,9 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
"@angular/animations": "17.0.1",
"@angular/common": "17.0.1",
"@angular/core": "17.0.1"
"@angular/animations": "17.0.2",
"@angular/common": "17.0.2",
"@angular/core": "17.0.2"
},
"peerDependenciesMeta": {
"@angular/animations": {
@ -1076,9 +1076,9 @@
}
},
"node_modules/@angular/platform-browser-dynamic": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.0.1.tgz",
"integrity": "sha512-xEcbB/ukXc65LaX4JBQYEM7D5Z8LcUIZniSJFneY7deZt3wNiKgmPZrPoXUyDV26QULh7N0IADEzvbcMF60AFQ==",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.0.2.tgz",
"integrity": "sha512-clcHqHcfD00/TlTixDbJ3q4EQxpm0t2ZFG76rRFmGrmE5tKYUPfaofIa3hQCxy3q269MAYuF16wALhUtrEWyUA==",
"dependencies": {
"tslib": "^2.3.0"
},
@ -1086,16 +1086,16 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
"@angular/common": "17.0.1",
"@angular/compiler": "17.0.1",
"@angular/core": "17.0.1",
"@angular/platform-browser": "17.0.1"
"@angular/common": "17.0.2",
"@angular/compiler": "17.0.2",
"@angular/core": "17.0.2",
"@angular/platform-browser": "17.0.2"
}
},
"node_modules/@angular/router": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-17.0.1.tgz",
"integrity": "sha512-73PCDDsRAjemODMRndZhwEN6Tb9rVVbDfMWgLQ4HgfgKnjek8P9BoYf8rOf3qV5fXf3c1Sm9MmKtaPv+l5lU9Q==",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-17.0.2.tgz",
"integrity": "sha512-A1Ulv4qBAtJyK5g1yBlK1qZHe+KaaL5vMPAaPWUxICH8lHEodDkJlbYAUI2e4VL2BN7zBmdOep6tlBKPmHY3mw==",
"dependencies": {
"tslib": "^2.3.0"
},
@ -1103,9 +1103,9 @@
"node": "^18.13.0 || >=20.9.0"
},
"peerDependencies": {
"@angular/common": "17.0.1",
"@angular/core": "17.0.1",
"@angular/platform-browser": "17.0.1",
"@angular/common": "17.0.2",
"@angular/core": "17.0.2",
"@angular/platform-browser": "17.0.2",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
@ -4251,9 +4251,9 @@
"dev": true
},
"node_modules/@swimlane/ngx-charts": {
"version": "20.4.1",
"resolved": "https://registry.npmjs.org/@swimlane/ngx-charts/-/ngx-charts-20.4.1.tgz",
"integrity": "sha512-DyTQe0fcqLDoLEZca45gkdjxP8iLH7kh4pCkr+TCFIkmgEdfQ5DpavNBOOVO0qd5J5uV/tbtSnkYWSx8JkbFpg==",
"version": "20.5.0",
"resolved": "https://registry.npmjs.org/@swimlane/ngx-charts/-/ngx-charts-20.5.0.tgz",
"integrity": "sha512-PNBIHdu/R3ceD7jnw1uCBVOj4k3T6IxfdW6xsDsglGkZyoWMEEq4tLoEurjLEKzmDtRv9c35kVNOXy0lkOuXeA==",
"dependencies": {
"d3-array": "^3.1.1",
"d3-brush": "^3.0.0",
@ -4262,6 +4262,7 @@
"d3-format": "^3.1.0",
"d3-hierarchy": "^3.1.0",
"d3-interpolate": "^3.0.1",
"d3-sankey": "^0.12.3",
"d3-scale": "^4.0.2",
"d3-selection": "^3.0.0",
"d3-shape": "^3.2.0",
@ -7166,6 +7167,41 @@
"node": ">=12"
}
},
"node_modules/d3-sankey": {
"version": "0.12.3",
"resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz",
"integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==",
"dependencies": {
"d3-array": "1 - 2",
"d3-shape": "^1.2.0"
}
},
"node_modules/d3-sankey/node_modules/d3-array": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
"integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
"dependencies": {
"internmap": "^1.0.0"
}
},
"node_modules/d3-sankey/node_modules/d3-path": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
},
"node_modules/d3-sankey/node_modules/d3-shape": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
"integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
"dependencies": {
"d3-path": "1"
}
},
"node_modules/d3-sankey/node_modules/internmap": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
"integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
},
"node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",

View File

@ -13,16 +13,16 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^17.0.1",
"@angular/animations": "^17.0.2",
"@angular/cdk": "^17.0.0",
"@angular/common": "^17.0.1",
"@angular/compiler": "^17.0.1",
"@angular/core": "^17.0.1",
"@angular/forms": "^17.0.1",
"@angular/localize": "^17.0.1",
"@angular/platform-browser": "^17.0.1",
"@angular/platform-browser-dynamic": "^17.0.1",
"@angular/router": "^17.0.1",
"@angular/common": "^17.0.2",
"@angular/compiler": "^17.0.2",
"@angular/core": "^17.0.2",
"@angular/forms": "^17.0.2",
"@angular/localize": "^17.0.2",
"@angular/platform-browser": "^17.0.2",
"@angular/platform-browser-dynamic": "^17.0.2",
"@angular/router": "^17.0.2",
"@fortawesome/fontawesome-free": "^6.4.2",
"@iharbeck/ngx-virtual-scroller": "^16.0.0",
"@iplab/ngx-file-upload": "^16.0.2",
@ -34,7 +34,7 @@
"@ngneat/transloco-persist-translations": "^5.0.0",
"@ngneat/transloco-preload-langs": "^5.0.0",
"@popperjs/core": "^2.11.7",
"@swimlane/ngx-charts": "^20.1.2",
"@swimlane/ngx-charts": "^20.5.0",
"@tweenjs/tween.js": "^21.0.0",
"bootstrap": "^5.3.2",
"charts.css": "^1.1.0",
@ -57,13 +57,13 @@
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.0.0",
"@angular-eslint/builder": "^17.0.0",
"@angular-eslint/builder": "^17.0.1",
"@angular-eslint/eslint-plugin": "^17.0.0",
"@angular-eslint/eslint-plugin-template": "^17.0.0",
"@angular-eslint/schematics": "^17.0.1",
"@angular-eslint/template-parser": "^17.0.1",
"@angular/cli": "^17.0.0",
"@angular/compiler-cli": "^17.0.1",
"@angular/compiler-cli": "^17.0.2",
"@types/d3": "^7.4.3",
"@types/file-saver": "^2.0.7",
"@types/luxon": "^3.3.4",

View File

@ -34,16 +34,16 @@ export interface SeriesMetadata {
summaryLocked: boolean;
genresLocked: boolean;
tagsLocked: boolean;
writersLocked: boolean;
coverArtistsLocked: boolean;
publishersLocked: boolean;
charactersLocked: boolean;
pencillersLocked: boolean;
inkersLocked: boolean;
coloristsLocked: boolean;
letterersLocked: boolean;
editorsLocked: boolean;
translatorsLocked: boolean;
writerLocked: boolean;
coverArtistLocked: boolean;
publisherLocked: boolean;
characterLocked: boolean;
pencillerLocked: boolean;
inkerLocked: boolean;
coloristLocked: boolean;
lettererLocked: boolean;
editorLocked: boolean;
translatorLocked: boolean;
ageRatingLocked: boolean;
releaseYearLocked: boolean;
languageLocked: boolean;

View File

@ -23,7 +23,7 @@ export class UtcToLocalTimePipe implements PipeTransform {
case 'short':
return dateTime.toLocaleString(DateTime.DATETIME_SHORT);
case 'shortDate':
return dateTime.toLocaleString(DateTime.DATE_MED);
return dateTime.toLocaleString(DateTime.DATE_SHORT);
case 'shortTime':
return dateTime.toLocaleString(DateTime.TIME_SIMPLE);
case 'full':

View File

@ -8,7 +8,7 @@
<div class="mb-3">
<label for="typeahead-focus" class="form-label">{{t('path-label')}}</label>
<div class="input-group">
<input id="typeahead-focus" type="text" class="form-control" [(ngModel)]="path" [ngbTypeahead]="search"
<input id="typeahead-focus" type="text" class="form-control" style="width: 100%" [(ngModel)]="path" [ngbTypeahead]="search"
(focus)="focus$.next($any($event).target.value)" (click)="click$.next($any($event).target.value)"
(ngModelChange)="updateTable()" #instance="ngbTypeahead" [placeholder]="t('path-placeholder')"
[resultTemplate]="rt" />

View File

@ -37,7 +37,7 @@ export class DirectoryPickerComponent implements OnInit {
path: string = '';
@ViewChild('instance', {static: true}) instance!: NgbTypeahead;
@ViewChild('instance', {static: false}) instance!: NgbTypeahead;
focus$ = new Subject<string>();
click$ = new Subject<string>();
searching: boolean = false;

View File

@ -24,9 +24,6 @@
<ng-container *ngIf="tab.fragment === TabID.Libraries">
<app-manage-library></app-manage-library>
</ng-container>
<ng-container *ngIf="tab.fragment === TabID.Logs">
<app-manage-logs></app-manage-logs>
</ng-container>
<ng-container *ngIf="tab.fragment === TabID.System">
<app-manage-system></app-manage-system>
</ng-container>

View File

@ -38,7 +38,10 @@ enum TabID {
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'],
standalone: true,
imports: [SideNavCompanionBarComponent, NgbNav, NgFor, NgbNavItem, NgbNavItemRole, NgbNavLink, RouterLink, NgbNavContent, NgIf, ManageSettingsComponent, ManageEmailSettingsComponent, ManageMediaSettingsComponent, ManageUsersComponent, ManageLibraryComponent, ManageLogsComponent, ManageSystemComponent, ServerStatsComponent, ManageTasksSettingsComponent, LicenseComponent, NgbNavOutlet, SentenceCasePipe, TranslocoDirective],
imports: [SideNavCompanionBarComponent, NgbNav, NgFor, NgbNavItem, NgbNavItemRole, NgbNavLink, RouterLink,
NgbNavContent, NgIf, ManageSettingsComponent, ManageEmailSettingsComponent, ManageMediaSettingsComponent,
ManageUsersComponent, ManageLibraryComponent, ManageSystemComponent, ServerStatsComponent,
ManageTasksSettingsComponent, LicenseComponent, NgbNavOutlet, SentenceCasePipe, TranslocoDirective],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DashboardComponent implements OnInit {
@ -47,7 +50,6 @@ export class DashboardComponent implements OnInit {
{title: 'general-tab', fragment: TabID.General},
{title: 'users-tab', fragment: TabID.Users},
{title: 'libraries-tab', fragment: TabID.Libraries},
//{title: 'logs-tab', fragment: TabID.Logs},
{title: 'media-tab', fragment: TabID.Media},
{title: 'email-tab', fragment: TabID.Email},
{title: 'tasks-tab', fragment: TabID.Tasks},

View File

@ -44,7 +44,7 @@ import {SeriesFilterV2} from "../../../_models/metadata/v2/series-filter-v2";
})
export class AllSeriesComponent implements OnInit {
title!: string;
title: string = translate('side-nav.all-series');
series: Series[] = [];
loadingSeries = false;
pagination: Pagination = new Pagination();
@ -115,10 +115,8 @@ export class AllSeriesComponent implements OnInit {
this.filterUtilityService.filterPresetsFromUrl(this.route.snapshot).subscribe(filter => {
this.filter = filter;
this.title = this.route.snapshot.queryParamMap.get('title') || this.filter.name || this.title;
this.titleService.setTitle('Kavita - ' + this.title);
this.filterActiveCheck = this.filterUtilityService.createSeriesV2Filter();
this.filterActiveCheck!.statements.push(this.filterUtilityService.createSeriesV2DefaultStatement());
this.filterSettings.presetsV2 = this.filter;
@ -128,7 +126,6 @@ export class AllSeriesComponent implements OnInit {
}
ngOnInit(): void {
this.title = translate('all-series.title');
this.hubService.messages$.pipe(debounceTime(6000), takeUntilDestroyed(this.destroyRef)).subscribe((event: Message<any>) => {
if (event.event !== EVENTS.SeriesAdded) return;
this.loadPage();

View File

@ -81,6 +81,7 @@ const routes: Routes = [
},
]
},
{path: '', pathMatch: 'full', redirectTo: 'home'},
]
},
{
@ -91,9 +92,10 @@ const routes: Routes = [
path: 'login',
loadChildren: () => import('./_routes/registration.router.module').then(m => m.routes) // TODO: Refactor so we just use /registration/login going forward
},
{path: '**', pathMatch: 'full', redirectTo: 'home'},
{path: 'libraries', pathMatch: 'full', redirectTo: 'home'},
{path: '**', pathMatch: 'prefix', redirectTo: 'home'},
{path: '**', pathMatch: 'full', redirectTo: 'home'},
{path: '', pathMatch: 'full', redirectTo: 'home'},
];
@NgModule({

View File

@ -1617,9 +1617,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
// Responsible for handling pagination only
handleContainerClick(event: MouseEvent) {
console.log('target: ', event.target);
if (this.actionBarVisible || ['action-bar'].some(className => (event.target as Element).classList.contains(className))) {
//console.log('exiting early')
if (this.drawerOpen || ['action-bar'].some(className => (event.target as Element).classList.contains(className))) {
return;
}

View File

@ -187,8 +187,8 @@
<div class="mb-3">
<label for="writer" class="form-label">{{t('writer-label')}}</label>
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Writer)" [settings]="getPersonsSettings(PersonRole.Writer)"
[(locked)]="metadata.writersLocked" (onUnlock)="metadata.writersLocked = false"
(newItemAdded)="metadata.writersLocked = true" (selectedData)="metadata.writersLocked = true">
[(locked)]="metadata.writerLocked" (onUnlock)="metadata.writerLocked = false"
(newItemAdded)="metadata.writerLocked = true" (selectedData)="metadata.writerLocked = true">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -202,8 +202,8 @@
<div class="mb-3">
<label for="cover-artist" class="form-label">{{t('cover-artist-label')}}</label>
<app-typeahead (selectedData)="updatePerson($event, PersonRole.CoverArtist)" [settings]="getPersonsSettings(PersonRole.CoverArtist)"
[(locked)]="metadata.coverArtistsLocked" (onUnlock)="metadata.coverArtistsLocked = false"
(newItemAdded)="metadata.coverArtistsLocked = true" (selectedData)="metadata.coverArtistsLocked = true">
[(locked)]="metadata.coverArtistLocked" (onUnlock)="metadata.coverArtistLocked = false"
(newItemAdded)="metadata.coverArtistLocked = true" (selectedData)="metadata.coverArtistLocked = true">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -219,8 +219,8 @@
<div class="mb-3">
<label for="publisher" class="form-label">{{t('publisher-label')}}</label>
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Publisher)" [settings]="getPersonsSettings(PersonRole.Publisher)"
[(locked)]="metadata.publishersLocked" (onUnlock)="metadata.publishersLocked = false"
(newItemAdded)="metadata.publishersLocked = true" (selectedData)="metadata.publishersLocked = true">
[(locked)]="metadata.publisherLocked" (onUnlock)="metadata.publisherLocked = false"
(newItemAdded)="metadata.publisherLocked = true" (selectedData)="metadata.publisherLocked = true">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -234,8 +234,8 @@
<div class="mb-3">
<label for="penciller" class="form-label">{{t('penciller-label')}}</label>
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Penciller)" [settings]="getPersonsSettings(PersonRole.Penciller)"
[(locked)]="metadata.pencillersLocked" (onUnlock)="metadata.pencillersLocked = false"
(newItemAdded)="metadata.pencillersLocked = true" (selectedData)="metadata.pencillersLocked = true">
[(locked)]="metadata.pencillerLocked" (onUnlock)="metadata.pencillerLocked = false"
(newItemAdded)="metadata.pencillerLocked = true" (selectedData)="metadata.pencillerLocked = true">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -251,8 +251,8 @@
<div class="mb-3">
<label for="letterer" class="form-label">{{t('letterer-label')}}</label>
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Letterer)" [settings]="getPersonsSettings(PersonRole.Letterer)"
[(locked)]="metadata.letterersLocked" (onUnlock)="metadata.letterersLocked = false"
(newItemAdded)="metadata.letterersLocked = true" (selectedData)="metadata.letterersLocked = true">
[(locked)]="metadata.lettererLocked" (onUnlock)="metadata.lettererLocked = false"
(newItemAdded)="metadata.lettererLocked = true" (selectedData)="metadata.lettererLocked = true">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -266,8 +266,8 @@
<div class="mb-3">
<label for="inker" class="form-label">{{t('inker-label')}}</label>
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Inker)" [settings]="getPersonsSettings(PersonRole.Inker)"
[(locked)]="metadata.inkersLocked" (onUnlock)="metadata.inkersLocked = false"
(newItemAdded)="metadata.inkersLocked = true" (selectedData)="metadata.inkersLocked = true">
[(locked)]="metadata.inkerLocked" (onUnlock)="metadata.inkerLocked = false"
(newItemAdded)="metadata.inkerLocked = true" (selectedData)="metadata.inkerLocked = true">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -284,8 +284,8 @@
<div class="mb-3">
<label for="editor" class="form-label">{{t('editor-label')}}</label>
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Editor)" [settings]="getPersonsSettings(PersonRole.Editor)"
[(locked)]="metadata.editorsLocked" (onUnlock)="metadata.editorsLocked = false"
(newItemAdded)="metadata.editorsLocked = true" (selectedData)="metadata.editorsLocked = true">
[(locked)]="metadata.editorLocked" (onUnlock)="metadata.editorLocked = false"
(newItemAdded)="metadata.editorLocked = true" (selectedData)="metadata.editorLocked = true">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -299,8 +299,8 @@
<div class="mb-3">
<label for="colorist" class="form-label">{{t('colorist-label')}}</label>
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Colorist)" [settings]="getPersonsSettings(PersonRole.Colorist)"
[(locked)]="metadata.coloristsLocked" (onUnlock)="metadata.coloristsLocked = false"
(newItemAdded)="metadata.coloristsLocked = true" (selectedData)="metadata.coloristsLocked = true">
[(locked)]="metadata.coloristLocked" (onUnlock)="metadata.coloristLocked = false"
(newItemAdded)="metadata.coloristLocked = true" (selectedData)="metadata.coloristLocked = true">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -317,8 +317,8 @@
<div class="mb-3">
<label for="character" class="form-label">{{t('character-label')}}</label>
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Character)" [settings]="getPersonsSettings(PersonRole.Character)"
[(locked)]="metadata.charactersLocked" (onUnlock)="metadata.charactersLocked = false"
(newItemAdded)="metadata.charactersLocked = true" (selectedData)="metadata.charactersLocked = true">
[(locked)]="metadata.characterLocked" (onUnlock)="metadata.characterLocked = false"
(newItemAdded)="metadata.characterLocked = true" (selectedData)="metadata.characterLocked = true">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>
@ -332,8 +332,8 @@
<div class="mb-3">
<label for="translator" class="form-label">{{t('translator-label')}}</label>
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Translator)" [settings]="getPersonsSettings(PersonRole.Translator)"
[(locked)]="metadata.translatorsLocked" (onUnlock)="metadata.translatorsLocked = false"
(newItemAdded)="metadata.translatorsLocked = true" (selectedData)="metadata.translatorsLocked = true">
[(locked)]="metadata.translatorLocked" (onUnlock)="metadata.translatorLocked = false"
(newItemAdded)="metadata.translatorLocked = true" (selectedData)="metadata.translatorLocked = true">
<ng-template #badgeItem let-item let-position="idx">
{{item.name}}
</ng-template>

View File

@ -4,6 +4,7 @@ import {ImageComponent} from "../../shared/image/image.component";
import {NextExpectedChapter} from "../../_models/series-detail/next-expected-chapter";
import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe";
import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe";
import {translate} from "@ngneat/transloco";
@Component({
selector: 'app-next-expected-card',
@ -39,7 +40,7 @@ export class NextExpectedCardComponent {
if (this.entity.expectedDate) {
const utcPipe = new UtcToLocalTimePipe();
this.title = '~ ' + utcPipe.transform(this.entity.expectedDate, 'shortDate');
this.title = translate('next-expected-card.title', {date: utcPipe.transform(this.entity.expectedDate, 'shortDate')});
}
this.cdRef.markForCheck();
}

View File

@ -83,7 +83,7 @@ export class ReadingListDetailComponent implements OnInit {
this.router.navigateByUrl('/home');
return;
}
this.titleService.setTitle('Kavita - ' + translate('side-nav.reading-lists'));
this.listId = parseInt(listId, 10);
this.characters$ = this.readingListService.getCharacters(this.listId);
@ -94,6 +94,8 @@ export class ReadingListDetailComponent implements OnInit {
const libraries = results[0];
const readingList = results[1];
this.titleService.setTitle('Kavita - ' + readingList.title);
libraries.forEach(lib => {
this.libraryTypes[lib.id] = lib.type;
});

View File

@ -17,9 +17,9 @@ import { CardItemComponent } from '../../../cards/card-item/card-item.component'
import { CardDetailLayoutComponent } from '../../../cards/card-detail-layout/card-detail-layout.component';
import { NgIf, DecimalPipe } from '@angular/common';
import { SideNavCompanionBarComponent } from '../../../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component';
import {TranslocoDirective, TranslocoService} from "@ngneat/transloco";
import {translate, TranslocoDirective, TranslocoService} from "@ngneat/transloco";
import {CardActionablesComponent} from "../../../_single-module/card-actionables/card-actionables.component";
import {CollectionTag} from "../../../_models/collection-tag";
import {Title} from "@angular/platform-browser";
@Component({
selector: 'app-reading-lists',
@ -43,13 +43,14 @@ export class ReadingListsComponent implements OnInit {
translocoService = inject(TranslocoService);
constructor(private readingListService: ReadingListService, public imageService: ImageService, private actionFactoryService: ActionFactoryService,
private accountService: AccountService, private toastr: ToastrService, private router: Router, private actionService: ActionService,
private jumpbarService: JumpbarService, private readonly cdRef: ChangeDetectorRef, private ngbModal: NgbModal) { }
private jumpbarService: JumpbarService, private readonly cdRef: ChangeDetectorRef, private ngbModal: NgbModal, private titleService: Title) { }
ngOnInit(): void {
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
if (user) {
this.isAdmin = this.accountService.hasAdminRole(user);
this.loadPage();
this.titleService.setTitle('Kavita - ' + translate('side-nav.reading-lists'));
}
});
}

View File

@ -64,7 +64,7 @@ export class ImportCblModalComponent {
{title: this.translocoService.translate('import-cbl-modal.import-step'), index: Step.Import, active: true, icon: 'fa-solid fa-file-arrow-up'},
{title: this.translocoService.translate('import-cbl-modal.validate-cbl-step'), index: Step.Validate, active: false, icon: 'fa-solid fa-spell-check'},
{title: this.translocoService.translate('import-cbl-modal.dry-run-step'), index: Step.DryRun, active: false, icon: 'fa-solid fa-gears'},
{title: this.translocoService.translate('import-cbl-final-import.import-step'), index: Step.Finalize, active: false, icon: 'fa-solid fa-floppy-disk'},
{title: this.translocoService.translate('import-cbl-modal.final-import-step'), index: Step.Finalize, active: false, icon: 'fa-solid fa-floppy-disk'},
];
currentStepIndex = this.steps[0].index;

View File

@ -80,7 +80,7 @@
<p *ngIf="isAddLibrary" class="alert alert-secondary" role="alert">{{t('cover-description')}}</p>
<p>{{t('cover-description-extra')}}</p>
<app-cover-image-chooser [(imageUrls)]="imageUrls" (imageSelected)="updateCoverImageIndex($event)"
(selectedBase64Url)="applyCoverImage($event)" [showReset]="library.coverImage !== null"
(selectedBase64Url)="applyCoverImage($event)" [showReset]="library?.coverImage !== null"
(resetClicked)="resetCoverImage()"></app-cover-image-chooser>
</ng-template>
</li>

View File

@ -1,5 +1,5 @@
.confirm-icon {
color: var(--primary-color);
font-size: 14px;
margin-bottom: 10px;
vertical-align: middle;
}

View File

@ -9,12 +9,14 @@
<li *ngFor="let tab of tabs" [ngbNavItem]="tab">
<a ngbNavLink routerLink="." [fragment]="tab.fragment">{{ t(tab.title) }}</a>
<ng-template ngbNavContent>
<ng-container *ngIf="tab.fragment === FragmentID.Account">
@defer (when tab.fragment === FragmentID.Account; prefetch on idle) {
<app-change-email></app-change-email>
<app-change-password></app-change-password>
<app-change-age-restriction></app-change-age-restriction>
<app-anilist-key></app-anilist-key>
</ng-container>
}
@defer (when tab.fragment === FragmentID.Preferences; prefetch on idle) {
<ng-container *ngIf="tab.fragment === FragmentID.Preferences">
<p>
{{t('pref-description')}}
@ -411,34 +413,32 @@
</div>
</form>
</ng-container>
}
<ng-container *ngIf="tab.fragment === FragmentID.Clients">
@defer (when tab.fragment === FragmentID.Clients; prefetch on idle) {
<div class="alert alert-warning" role="alert" *ngIf="!opdsEnabled">{{t('clients-opds-alert')}}</div>
<p>{{t('clients-opds-description')}}</p>
<app-api-key [tooltipText]="t('clients-api-key-tooltip')" [hideData]="true"></app-api-key>
<app-api-key [title]="t('clients-opds-url-tooltip')" [hideData]="true" [showRefresh]="false" [transform]="makeUrl"></app-api-key>
</ng-container>
<!-- @defer (when tab.fragment === FragmentID.Theme; prefetch on idle) {-->
<!-- <app-theme-manager></app-theme-manager>-->
<!-- }-->
<!-- @placeholder {-->
<!-- <app-loading [loading]="true"></app-loading>-->
<!-- }-->
<ng-container *ngIf="tab.fragment === FragmentID.Theme">
}
@defer (when tab.fragment === FragmentID.Theme; prefetch on idle) {
<app-theme-manager></app-theme-manager>
</ng-container>
}
<ng-container *ngIf="tab.fragment === FragmentID.Devices">
@defer (when tab.fragment === FragmentID.Devices; prefetch on idle) {
<app-manage-devices></app-manage-devices>
</ng-container>
<ng-container *ngIf="tab.fragment === FragmentID.Stats">
}
@defer (when tab.fragment === FragmentID.Stats; prefetch on idle) {
<app-user-stats></app-user-stats>
</ng-container>
<ng-container *ngIf="tab.fragment === FragmentID.Scrobbling">
}
@defer (when tab.fragment === FragmentID.Scrobbling; prefetch on idle) {
<app-user-scrobble-history></app-user-scrobble-history>
<app-user-holds></app-user-holds>
</ng-container>
}
</ng-template>
</li>
</ul>

View File

@ -49,11 +49,6 @@ import { SideNavCompanionBarComponent } from '../../sidenav/_components/side-nav
import {LocalizationService} from "../../_services/localization.service";
import {Language} from "../../_models/metadata/language";
import {translate, TranslocoDirective} from "@ngneat/transloco";
import {
provideTranslocoPersistTranslations,
TranslocoPersistTranslations
} from "@ngneat/transloco-persist-translations";
import {HttpLoader} from "../../../httpLoader";
import {LoadingComponent} from "../../shared/loading/loading.component";
enum AccordionPanelID {
@ -333,4 +328,6 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
d.text = translate('preferences.' + o.text);
return d;
}
protected readonly undefined = undefined;
}

View File

@ -1707,6 +1707,10 @@
"add-to-want-to-read": "{{actionable.add-to-want-to-read}}"
},
"next-expected-card": {
"title": "~{{date}}"
},
"server-stats": {
"total-series-label": "Total Series",
"total-series-tooltip": "Total Series: {{count}}",

View File

@ -17860,34 +17860,34 @@
"tagsLocked": {
"type": "boolean"
},
"writersLocked": {
"writerLocked": {
"type": "boolean"
},
"charactersLocked": {
"characterLocked": {
"type": "boolean"
},
"coloristsLocked": {
"coloristLocked": {
"type": "boolean"
},
"editorsLocked": {
"editorLocked": {
"type": "boolean"
},
"inkersLocked": {
"inkerLocked": {
"type": "boolean"
},
"letterersLocked": {
"lettererLocked": {
"type": "boolean"
},
"pencillersLocked": {
"pencillerLocked": {
"type": "boolean"
},
"publishersLocked": {
"publisherLocked": {
"type": "boolean"
},
"translatorsLocked": {
"translatorLocked": {
"type": "boolean"
},
"coverArtistsLocked": {
"coverArtistLocked": {
"type": "boolean"
},
"releaseYearLocked": {