Fixed up code comments for Amelia.
Fixed a bug where not all detail pages had the same size font. Fixed series detail page not having subtitle as a themeable variable (--detail-subtitle-color).
This commit is contained in:
parent
bbea28fd05
commit
9844503671
41 changed files with 200 additions and 106 deletions
|
|
@ -32,11 +32,7 @@ public class PersonServiceTests: AbstractDbTest
|
||||||
Name= "Delores Casey",
|
Name= "Delores Casey",
|
||||||
NormalizedName = "Delores Casey".ToNormalized(),
|
NormalizedName = "Delores Casey".ToNormalized(),
|
||||||
Description = "Hi, I'm Delores Casey!",
|
Description = "Hi, I'm Delores Casey!",
|
||||||
Aliases = [new PersonAlias
|
Aliases = [new PersonAliasBuilder("Casey, Delores").Build()],
|
||||||
{
|
|
||||||
Alias = "Casey, Delores",
|
|
||||||
NormalizedAlias = "Casey, Delores".ToNormalized(),
|
|
||||||
}],
|
|
||||||
AniListId = 27,
|
AniListId = 27,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using API.Data;
|
||||||
using API.Data.Repositories;
|
using API.Data.Repositories;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
using API.DTOs.Metadata;
|
using API.DTOs.Metadata;
|
||||||
|
using API.DTOs.Person;
|
||||||
using API.DTOs.SeriesDetail;
|
using API.DTOs.SeriesDetail;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ using System.Threading.Tasks;
|
||||||
using API.Constants;
|
using API.Constants;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.Data.Repositories;
|
using API.Data.Repositories;
|
||||||
using API.DTOs;
|
|
||||||
using API.DTOs.Filtering;
|
using API.DTOs.Filtering;
|
||||||
using API.DTOs.Metadata;
|
using API.DTOs.Metadata;
|
||||||
|
using API.DTOs.Person;
|
||||||
using API.DTOs.Recommendation;
|
using API.DTOs.Recommendation;
|
||||||
using API.DTOs.SeriesDetail;
|
using API.DTOs.SeriesDetail;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ using API.DTOs.CollectionTags;
|
||||||
using API.DTOs.Filtering;
|
using API.DTOs.Filtering;
|
||||||
using API.DTOs.Filtering.v2;
|
using API.DTOs.Filtering.v2;
|
||||||
using API.DTOs.OPDS;
|
using API.DTOs.OPDS;
|
||||||
|
using API.DTOs.Person;
|
||||||
using API.DTOs.Progress;
|
using API.DTOs.Progress;
|
||||||
using API.DTOs.Search;
|
using API.DTOs.Search;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.Data.Repositories;
|
using API.Data.Repositories;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
|
using API.DTOs.Person;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
using API.Helpers;
|
using API.Helpers;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ using System.Threading.Tasks;
|
||||||
using API.Constants;
|
using API.Constants;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.Data.Repositories;
|
using API.Data.Repositories;
|
||||||
using API.DTOs;
|
using API.DTOs.Person;
|
||||||
using API.DTOs.ReadingLists;
|
using API.DTOs.ReadingLists;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ public class SearchController : BaseApiController
|
||||||
|
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||||
if (user == null) return Unauthorized();
|
if (user == null) return Unauthorized();
|
||||||
|
|
||||||
var libraries = _unitOfWork.LibraryRepository.GetLibraryIdsForUserIdAsync(user.Id, QueryContext.Search).ToList();
|
var libraries = _unitOfWork.LibraryRepository.GetLibraryIdsForUserIdAsync(user.Id, QueryContext.Search).ToList();
|
||||||
if (libraries.Count == 0) return BadRequest(await _localizationService.Translate(User.GetUserId(), "libraries-restricted"));
|
if (libraries.Count == 0) return BadRequest(await _localizationService.Translate(User.GetUserId(), "libraries-restricted"));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using API.DTOs.Metadata;
|
using API.DTOs.Metadata;
|
||||||
|
using API.DTOs.Person;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
using API.Entities.Interfaces;
|
using API.Entities.Interfaces;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using API.DTOs.Person;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
|
|
||||||
namespace API.DTOs.Metadata;
|
namespace API.DTOs.Metadata;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
namespace API.DTOs;
|
using API.DTOs.Person;
|
||||||
|
|
||||||
|
namespace API.DTOs;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to browse writers and click in to see their series
|
/// Used to browse writers and click in to see their series
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace API.DTOs;
|
namespace API.DTOs.Person;
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
public class PersonDto
|
public class PersonDto
|
||||||
|
|
@ -13,6 +13,7 @@ public class PersonDto
|
||||||
public string? SecondaryColor { get; set; }
|
public string? SecondaryColor { get; set; }
|
||||||
|
|
||||||
public string? CoverImage { get; set; }
|
public string? CoverImage { get; set; }
|
||||||
|
public List<string> Aliases { get; set; } = [];
|
||||||
|
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using API.DTOs.Person;
|
||||||
|
|
||||||
namespace API.DTOs.ReadingLists;
|
namespace API.DTOs.ReadingLists;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using API.DTOs.Collection;
|
using API.DTOs.Collection;
|
||||||
using API.DTOs.CollectionTags;
|
using API.DTOs.CollectionTags;
|
||||||
using API.DTOs.Metadata;
|
using API.DTOs.Metadata;
|
||||||
|
using API.DTOs.Person;
|
||||||
using API.DTOs.Reader;
|
using API.DTOs.Reader;
|
||||||
using API.DTOs.ReadingLists;
|
using API.DTOs.ReadingLists;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using API.DTOs.Metadata;
|
using API.DTOs.Metadata;
|
||||||
|
using API.DTOs.Person;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
|
|
||||||
namespace API.DTOs;
|
namespace API.DTOs;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using API.DTOs.Metadata;
|
using API.DTOs.Metadata;
|
||||||
|
using API.DTOs.Person;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
|
|
||||||
namespace API.DTOs;
|
namespace API.DTOs;
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
namespace API.Data.Migrations
|
namespace API.Data.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(DataContext))]
|
[DbContext(typeof(DataContext))]
|
||||||
[Migration("20250504212806_PersonAliases")]
|
[Migration("20250507221026_PersonAliases")]
|
||||||
partial class PersonAliases
|
partial class PersonAliases
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -1851,7 +1851,7 @@ namespace API.Data.Migrations
|
||||||
b.Property<string>("NormalizedAlias")
|
b.Property<string>("NormalizedAlias")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<int?>("PersonId")
|
b.Property<int>("PersonId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
@ -3109,9 +3109,13 @@ namespace API.Data.Migrations
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.Person.PersonAlias", b =>
|
modelBuilder.Entity("API.Entities.Person.PersonAlias", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Entities.Person.Person", null)
|
b.HasOne("API.Entities.Person.Person", "Person")
|
||||||
.WithMany("Aliases")
|
.WithMany("Aliases")
|
||||||
.HasForeignKey("PersonId");
|
.HasForeignKey("PersonId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Person");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b =>
|
modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b =>
|
||||||
|
|
@ -18,7 +18,7 @@ namespace API.Data.Migrations
|
||||||
.Annotation("Sqlite:Autoincrement", true),
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
Alias = table.Column<string>(type: "TEXT", nullable: true),
|
Alias = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
NormalizedAlias = table.Column<string>(type: "TEXT", nullable: true),
|
NormalizedAlias = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
PersonId = table.Column<int>(type: "INTEGER", nullable: true)
|
PersonId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
|
|
@ -27,7 +27,8 @@ namespace API.Data.Migrations
|
||||||
name: "FK_PersonAlias_Person_PersonId",
|
name: "FK_PersonAlias_Person_PersonId",
|
||||||
column: x => x.PersonId,
|
column: x => x.PersonId,
|
||||||
principalTable: "Person",
|
principalTable: "Person",
|
||||||
principalColumn: "Id");
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
|
|
@ -1848,7 +1848,7 @@ namespace API.Data.Migrations
|
||||||
b.Property<string>("NormalizedAlias")
|
b.Property<string>("NormalizedAlias")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<int?>("PersonId")
|
b.Property<int>("PersonId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
@ -3106,9 +3106,13 @@ namespace API.Data.Migrations
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.Person.PersonAlias", b =>
|
modelBuilder.Entity("API.Entities.Person.PersonAlias", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Entities.Person.Person", null)
|
b.HasOne("API.Entities.Person.Person", "Person")
|
||||||
.WithMany("Aliases")
|
.WithMany("Aliases")
|
||||||
.HasForeignKey("PersonId");
|
.HasForeignKey("PersonId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Person");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b =>
|
modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b =>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
|
using API.DTOs.Person;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
using API.Entities.Person;
|
using API.Entities.Person;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.Data.Misc;
|
using API.Data.Misc;
|
||||||
using API.DTOs;
|
using API.DTOs.Person;
|
||||||
using API.DTOs.ReadingLists;
|
using API.DTOs.ReadingLists;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ using API.DTOs.Filtering;
|
||||||
using API.DTOs.Filtering.v2;
|
using API.DTOs.Filtering.v2;
|
||||||
using API.DTOs.KavitaPlus.Metadata;
|
using API.DTOs.KavitaPlus.Metadata;
|
||||||
using API.DTOs.Metadata;
|
using API.DTOs.Metadata;
|
||||||
|
using API.DTOs.Person;
|
||||||
using API.DTOs.ReadingLists;
|
using API.DTOs.ReadingLists;
|
||||||
using API.DTOs.Recommendation;
|
using API.DTOs.Recommendation;
|
||||||
using API.DTOs.Scrobbling;
|
using API.DTOs.Scrobbling;
|
||||||
|
|
@ -455,11 +456,18 @@ public class SeriesRepository : ISeriesRepository
|
||||||
.ProjectTo<AppUserCollectionDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<AppUserCollectionDto>(_mapper.ConfigurationProvider)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
result.Persons = await _context.SeriesMetadata
|
// I can't work out how to map people in DB layer
|
||||||
|
var personIds = await _context.SeriesMetadata
|
||||||
.SearchPeople(searchQuery, seriesIds)
|
.SearchPeople(searchQuery, seriesIds)
|
||||||
.Take(maxRecords)
|
.Select(p => p.Id)
|
||||||
.OrderBy(t => t.NormalizedName)
|
|
||||||
.Distinct()
|
.Distinct()
|
||||||
|
.OrderBy(id => id)
|
||||||
|
.Take(maxRecords)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
result.Persons = await _context.Person
|
||||||
|
.Where(p => personIds.Contains(p.Id))
|
||||||
|
.OrderBy(p => p.NormalizedName)
|
||||||
.ProjectTo<PersonDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<PersonDto>(_mapper.ConfigurationProvider)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
|
@ -475,8 +483,8 @@ public class SeriesRepository : ISeriesRepository
|
||||||
.ProjectTo<TagDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<TagDto>(_mapper.ConfigurationProvider)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
result.Files = new List<MangaFileDto>();
|
result.Files = [];
|
||||||
result.Chapters = new List<ChapterDto>();
|
result.Chapters = (List<ChapterDto>) [];
|
||||||
|
|
||||||
|
|
||||||
if (includeChapterAndFiles)
|
if (includeChapterAndFiles)
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ public class Person : IHasCoverImage
|
||||||
//public long MetronId { get; set; } = 0;
|
//public long MetronId { get; set; } = 0;
|
||||||
|
|
||||||
// Relationships
|
// Relationships
|
||||||
public ICollection<ChapterPeople> ChapterPeople { get; set; } = new List<ChapterPeople>();
|
public ICollection<ChapterPeople> ChapterPeople { get; set; } = [];
|
||||||
public ICollection<SeriesMetadataPeople> SeriesMetadataPeople { get; set; } = new List<SeriesMetadataPeople>();
|
public ICollection<SeriesMetadataPeople> SeriesMetadataPeople { get; set; } = [];
|
||||||
|
|
||||||
|
|
||||||
public void ResetColorScape()
|
public void ResetColorScape()
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ namespace API.Entities.Person;
|
||||||
public class PersonAlias
|
public class PersonAlias
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
public required string Alias { get; set; }
|
||||||
|
public required string NormalizedAlias { get; set; }
|
||||||
|
|
||||||
public string Alias { get; set; }
|
public int PersonId { get; set; }
|
||||||
|
public Person Person { get; set; }
|
||||||
public string NormalizedAlias { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,27 +50,26 @@ public static class SearchQueryableExtensions
|
||||||
// Get people from SeriesMetadata
|
// Get people from SeriesMetadata
|
||||||
var peopleFromSeriesMetadata = queryable
|
var peopleFromSeriesMetadata = queryable
|
||||||
.Where(sm => seriesIds.Contains(sm.SeriesId))
|
.Where(sm => seriesIds.Contains(sm.SeriesId))
|
||||||
.SelectMany(sm => sm.People)
|
.SelectMany(sm => sm.People.Select(sp => sp.Person))
|
||||||
.Include(sp => sp.Person.Aliases)
|
.Where(p =>
|
||||||
.Where(p => (p.Person.Name != null && EF.Functions.Like(p.Person.Name, $"%{searchQuery}%"))
|
EF.Functions.Like(p.Name, $"%{searchQuery}%") ||
|
||||||
|| p.Person.Aliases.Any(pa => EF.Functions.Like(pa.Alias, $"%{searchQuery}%")))
|
p.Aliases.Any(pa => EF.Functions.Like(pa.Alias, $"%{searchQuery}%"))
|
||||||
.Select(p => p.Person);
|
);
|
||||||
|
|
||||||
// Get people from ChapterPeople by navigating through Volume -> Series
|
|
||||||
var peopleFromChapterPeople = queryable
|
var peopleFromChapterPeople = queryable
|
||||||
.Where(sm => seriesIds.Contains(sm.SeriesId))
|
.Where(sm => seriesIds.Contains(sm.SeriesId))
|
||||||
.SelectMany(sm => sm.Series.Volumes)
|
.SelectMany(sm => sm.Series.Volumes)
|
||||||
.SelectMany(v => v.Chapters)
|
.SelectMany(v => v.Chapters)
|
||||||
.SelectMany(ch => ch.People)
|
.SelectMany(ch => ch.People.Select(cp => cp.Person))
|
||||||
.Include(cp => cp.Person.Aliases)
|
.Where(p =>
|
||||||
.Where(p => (p.Person.Name != null && EF.Functions.Like(p.Person.Name, $"%{searchQuery}%"))
|
EF.Functions.Like(p.Name, $"%{searchQuery}%") ||
|
||||||
|| p.Person.Aliases.Any(pa => EF.Functions.Like(pa.Alias, $"%{searchQuery}%")))
|
p.Aliases.Any(pa => EF.Functions.Like(pa.Alias, $"%{searchQuery}%"))
|
||||||
.Select(cp => cp.Person);
|
);
|
||||||
|
|
||||||
// Combine both queries and ensure distinct results
|
// Combine both queries and ensure distinct results
|
||||||
return peopleFromSeriesMetadata
|
return peopleFromSeriesMetadata
|
||||||
.Union(peopleFromChapterPeople)
|
.Union(peopleFromChapterPeople)
|
||||||
.Distinct()
|
.Select(p => p)
|
||||||
.OrderBy(p => p.NormalizedName);
|
.OrderBy(p => p.NormalizedName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ using API.DTOs.KavitaPlus.Manage;
|
||||||
using API.DTOs.KavitaPlus.Metadata;
|
using API.DTOs.KavitaPlus.Metadata;
|
||||||
using API.DTOs.MediaErrors;
|
using API.DTOs.MediaErrors;
|
||||||
using API.DTOs.Metadata;
|
using API.DTOs.Metadata;
|
||||||
|
using API.DTOs.Person;
|
||||||
using API.DTOs.Progress;
|
using API.DTOs.Progress;
|
||||||
using API.DTOs.Reader;
|
using API.DTOs.Reader;
|
||||||
using API.DTOs.ReadingLists;
|
using API.DTOs.ReadingLists;
|
||||||
|
|
@ -68,7 +69,8 @@ public class AutoMapperProfiles : Profile
|
||||||
CreateMap<AppUserCollection, AppUserCollectionDto>()
|
CreateMap<AppUserCollection, AppUserCollectionDto>()
|
||||||
.ForMember(dest => dest.Owner, opt => opt.MapFrom(src => src.AppUser.UserName))
|
.ForMember(dest => dest.Owner, opt => opt.MapFrom(src => src.AppUser.UserName))
|
||||||
.ForMember(dest => dest.ItemCount, opt => opt.MapFrom(src => src.Items.Count));
|
.ForMember(dest => dest.ItemCount, opt => opt.MapFrom(src => src.Items.Count));
|
||||||
CreateMap<Person, PersonDto>();
|
CreateMap<Person, PersonDto>()
|
||||||
|
.ForMember(dest => dest.Aliases, opt => opt.MapFrom(src => src.Aliases.Select(s => s.Alias)));
|
||||||
CreateMap<Genre, GenreTagDto>();
|
CreateMap<Genre, GenreTagDto>();
|
||||||
CreateMap<Tag, TagDto>();
|
CreateMap<Tag, TagDto>();
|
||||||
CreateMap<AgeRating, AgeRatingDto>();
|
CreateMap<AgeRating, AgeRatingDto>();
|
||||||
|
|
|
||||||
19
API/Helpers/Builders/PersonAliasBuilder.cs
Normal file
19
API/Helpers/Builders/PersonAliasBuilder.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
using API.Entities.Person;
|
||||||
|
using API.Extensions;
|
||||||
|
|
||||||
|
namespace API.Helpers.Builders;
|
||||||
|
|
||||||
|
public class PersonAliasBuilder : IEntityBuilder<PersonAlias>
|
||||||
|
{
|
||||||
|
private readonly PersonAlias _alias;
|
||||||
|
public PersonAlias Build() => _alias;
|
||||||
|
|
||||||
|
public PersonAliasBuilder(string name)
|
||||||
|
{
|
||||||
|
_alias = new PersonAlias()
|
||||||
|
{
|
||||||
|
Alias = name.Trim(),
|
||||||
|
NormalizedAlias = name.ToNormalized(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -39,11 +39,8 @@ public class PersonBuilder : IEntityBuilder<Person>
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
_person.Aliases.Add(new PersonAlias()
|
_person.Aliases.Add(new PersonAliasBuilder(alias).Build());
|
||||||
{
|
|
||||||
Alias = alias,
|
|
||||||
NormalizedAlias = alias.ToNormalized(),
|
|
||||||
});
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.Entities.Person;
|
using API.Entities.Person;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
|
using API.Helpers.Builders;
|
||||||
|
|
||||||
namespace API.Services;
|
namespace API.Services;
|
||||||
|
|
||||||
|
|
@ -68,11 +69,7 @@ public class PersonService(IUnitOfWork unitOfWork): IPersonService
|
||||||
MergeChapterPeople(dst, src);
|
MergeChapterPeople(dst, src);
|
||||||
MergeSeriesMetadataPeople(dst, src);
|
MergeSeriesMetadataPeople(dst, src);
|
||||||
|
|
||||||
dst.Aliases.Add(new PersonAlias
|
dst.Aliases.Add(new PersonAliasBuilder(src.Name).Build());
|
||||||
{
|
|
||||||
Alias = src.Name,
|
|
||||||
NormalizedAlias = src.NormalizedName,
|
|
||||||
});
|
|
||||||
|
|
||||||
foreach (var alias in src.Aliases)
|
foreach (var alias in src.Aliases)
|
||||||
{
|
{
|
||||||
|
|
@ -84,12 +81,14 @@ public class PersonService(IUnitOfWork unitOfWork): IPersonService
|
||||||
await unitOfWork.CommitAsync();
|
await unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MergeChapterPeople(Person dst, Person src)
|
private static void MergeChapterPeople(Person dst, Person src)
|
||||||
{
|
{
|
||||||
|
|
||||||
foreach (var chapter in src.ChapterPeople)
|
foreach (var chapter in src.ChapterPeople)
|
||||||
{
|
{
|
||||||
var alreadyPresent = dst.ChapterPeople
|
var alreadyPresent = dst.ChapterPeople
|
||||||
.Any(x => x.ChapterId == chapter.ChapterId && x.Role == chapter.Role);
|
.Any(x => x.ChapterId == chapter.ChapterId && x.Role == chapter.Role);
|
||||||
|
|
||||||
if (alreadyPresent) continue;
|
if (alreadyPresent) continue;
|
||||||
|
|
||||||
dst.ChapterPeople.Add(new ChapterPeople
|
dst.ChapterPeople.Add(new ChapterPeople
|
||||||
|
|
@ -103,12 +102,13 @@ public class PersonService(IUnitOfWork unitOfWork): IPersonService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MergeSeriesMetadataPeople(Person dst, Person src)
|
private static void MergeSeriesMetadataPeople(Person dst, Person src)
|
||||||
{
|
{
|
||||||
foreach (var series in src.SeriesMetadataPeople)
|
foreach (var series in src.SeriesMetadataPeople)
|
||||||
{
|
{
|
||||||
var alreadyPresent = dst.SeriesMetadataPeople
|
var alreadyPresent = dst.SeriesMetadataPeople
|
||||||
.Any(x => x.SeriesMetadataId == series.SeriesMetadataId && x.Role == series.Role);
|
.Any(x => x.SeriesMetadataId == series.SeriesMetadataId && x.Role == series.Role);
|
||||||
|
|
||||||
if (alreadyPresent) continue;
|
if (alreadyPresent) continue;
|
||||||
|
|
||||||
dst.SeriesMetadataPeople.Add(new SeriesMetadataPeople
|
dst.SeriesMetadataPeople.Add(new SeriesMetadataPeople
|
||||||
|
|
@ -125,9 +125,8 @@ public class PersonService(IUnitOfWork unitOfWork): IPersonService
|
||||||
public async Task<bool> UpdatePersonAliasesAsync(Person person, IList<string> aliases)
|
public async Task<bool> UpdatePersonAliasesAsync(Person person, IList<string> aliases)
|
||||||
{
|
{
|
||||||
var normalizedAliases = aliases
|
var normalizedAliases = aliases
|
||||||
.Select(a => a.ToNormalized().Trim())
|
.Select(a => a.ToNormalized())
|
||||||
.Where(a => !string.IsNullOrWhiteSpace(a))
|
.Where(a => !string.IsNullOrEmpty(a) && a != person.NormalizedName)
|
||||||
.Where(a => a != person.NormalizedName)
|
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (normalizedAliases.Count == 0)
|
if (normalizedAliases.Count == 0)
|
||||||
|
|
@ -141,11 +140,7 @@ public class PersonService(IUnitOfWork unitOfWork): IPersonService
|
||||||
|
|
||||||
if (others.Count != 0) return false;
|
if (others.Count != 0) return false;
|
||||||
|
|
||||||
person.Aliases = aliases.Select(a => new PersonAlias
|
person.Aliases = aliases.Select(a => new PersonAliasBuilder(a).Build()).ToList();
|
||||||
{
|
|
||||||
Alias = a.Trim(),
|
|
||||||
NormalizedAlias = a.Trim().ToNormalized()
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ using API.DTOs.Collection;
|
||||||
using API.DTOs.KavitaPlus.ExternalMetadata;
|
using API.DTOs.KavitaPlus.ExternalMetadata;
|
||||||
using API.DTOs.KavitaPlus.Metadata;
|
using API.DTOs.KavitaPlus.Metadata;
|
||||||
using API.DTOs.Metadata.Matching;
|
using API.DTOs.Metadata.Matching;
|
||||||
|
using API.DTOs.Person;
|
||||||
using API.DTOs.Recommendation;
|
using API.DTOs.Recommendation;
|
||||||
using API.DTOs.Scrobbling;
|
using API.DTOs.Scrobbling;
|
||||||
using API.DTOs.SeriesDetail;
|
using API.DTOs.SeriesDetail;
|
||||||
|
|
@ -20,6 +21,7 @@ using API.Entities.MetadataMatching;
|
||||||
using API.Entities.Person;
|
using API.Entities.Person;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
using API.Helpers;
|
using API.Helpers;
|
||||||
|
using API.Helpers.Builders;
|
||||||
using API.Services.Tasks.Metadata;
|
using API.Services.Tasks.Metadata;
|
||||||
using API.Services.Tasks.Scanner.Parser;
|
using API.Services.Tasks.Scanner.Parser;
|
||||||
using API.SignalR;
|
using API.SignalR;
|
||||||
|
|
@ -657,11 +659,7 @@ public class ExternalMetadataService : IExternalMetadataService
|
||||||
if (existingPeopleDictionary.TryGetValue(mapping.AlternativeName.ToNormalized(), out var person))
|
if (existingPeopleDictionary.TryGetValue(mapping.AlternativeName.ToNormalized(), out var person))
|
||||||
{
|
{
|
||||||
modified = true;
|
modified = true;
|
||||||
person.Aliases.Add(new PersonAlias
|
person.Aliases.Add(new PersonAliasBuilder(mapping.PreferredName).Build());
|
||||||
{
|
|
||||||
Alias = mapping.PreferredName,
|
|
||||||
NormalizedAlias = mapping.PreferredName.ToNormalized(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using API.Comparators;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.Data.Repositories;
|
using API.Data.Repositories;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
|
using API.DTOs.Person;
|
||||||
using API.DTOs.SeriesDetail;
|
using API.DTOs.SeriesDetail;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
color: lightgrey;
|
color: var(--detail-subtitle-color);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export interface Person extends IHasCover {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
aliases: Array<string>;
|
||||||
coverImage?: string;
|
coverImage?: string;
|
||||||
coverImageLocked: boolean;
|
coverImageLocked: boolean;
|
||||||
malId?: number;
|
malId?: number;
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,18 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="ms-1">
|
<div class="ms-1">
|
||||||
<div>{{item.name}}</div>
|
<div>{{item.name}}</div>
|
||||||
|
@if (item.aliases.length > 0) {
|
||||||
|
|
||||||
|
<span class="small-text">
|
||||||
|
{{t('person-aka-label')}}
|
||||||
|
@for(alias of item.aliases; track alias; let last = $last) {
|
||||||
|
<span>{{alias}}</span>
|
||||||
|
@if (!last) {
|
||||||
|
<span>, </span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
|
||||||
|
|
@ -138,3 +138,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.small-text {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,6 @@
|
||||||
}
|
}
|
||||||
</h2>
|
</h2>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container subtitle>
|
|
||||||
@if (aliases$ | async; as aliases) {
|
|
||||||
@if (aliases.length > 0) {
|
|
||||||
<span>{{t('aka')}} {{aliases.join(", ")}}</span>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</ng-container>
|
|
||||||
</app-side-nav-companion-bar>
|
</app-side-nav-companion-bar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -50,15 +43,43 @@
|
||||||
|
|
||||||
<div class="col-xl-10 col-lg-7 col-md-12 col-xs-12 col-sm-12 mt-2">
|
<div class="col-xl-10 col-lg-7 col-md-12 col-xs-12 col-sm-12 mt-2">
|
||||||
<div class="row g-0 mt-2">
|
<div class="row g-0 mt-2">
|
||||||
<app-read-more [text]="person.description || t('no-info')"></app-read-more>
|
<app-read-more [maxLength]="500" [text]="person.description || t('no-info')"></app-read-more>
|
||||||
|
|
||||||
|
|
||||||
|
@if (person.aliases.length > 0) {
|
||||||
|
<span class="fw-bold mt-2">{{t('aka-title')}}</span>
|
||||||
|
<div>
|
||||||
|
<app-badge-expander [items]="person.aliases"
|
||||||
|
[itemsTillExpander]="6">
|
||||||
|
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||||
|
<a href="javascript:void(0)" class="dark-exempt btn-icon">{{item}}</a>
|
||||||
|
</ng-template>
|
||||||
|
</app-badge-expander>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@if (roles$ | async; as roles) {
|
@if (roles$ | async; as roles) {
|
||||||
<div class="mt-1">
|
@if (roles.length > 0) {
|
||||||
<h5>{{t('all-roles')}}</h5>
|
<span class="fw-bold mt-2">{{t('all-roles')}}</span>
|
||||||
@for(role of roles; track role) {
|
<div>
|
||||||
<app-tag-badge [selectionMode]="TagBadgeCursor.Clickable" (click)="loadFilterByRole(role)">{{role | personRole}}</app-tag-badge>
|
<app-badge-expander [items]="roles"
|
||||||
}
|
[itemsTillExpander]="6">
|
||||||
</div>
|
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||||
|
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="loadFilterByRole(item)">{{item | personRole}}</a>
|
||||||
|
</ng-template>
|
||||||
|
</app-badge-expander>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
<!-- -->
|
||||||
|
<!-- <div class="mt-1">-->
|
||||||
|
<!-- <h5>{{t('all-roles')}}</h5>-->
|
||||||
|
<!-- @for(role of roles; track role) {-->
|
||||||
|
<!-- <app-tag-badge [selectionMode]="TagBadgeCursor.Clickable" (click)="loadFilterByRole(role)">{{role | personRole}}</app-tag-badge>-->
|
||||||
|
<!-- }-->
|
||||||
|
<!-- </div>-->
|
||||||
}
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import {CarouselReelComponent} from "../carousel/_components/carousel-reel/carou
|
||||||
import {FilterComparison} from "../_models/metadata/v2/filter-comparison";
|
import {FilterComparison} from "../_models/metadata/v2/filter-comparison";
|
||||||
import {FilterUtilitiesService} from "../shared/_services/filter-utilities.service";
|
import {FilterUtilitiesService} from "../shared/_services/filter-utilities.service";
|
||||||
import {SeriesFilterV2} from "../_models/metadata/v2/series-filter-v2";
|
import {SeriesFilterV2} from "../_models/metadata/v2/series-filter-v2";
|
||||||
import {allPeople, personRoleForFilterField} from "../_models/metadata/v2/filter-field";
|
import {allPeople, FilterField, personRoleForFilterField} from "../_models/metadata/v2/filter-field";
|
||||||
import {Series} from "../_models/series";
|
import {Series} from "../_models/series";
|
||||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||||
import {FilterCombination} from "../_models/metadata/v2/filter-combination";
|
import {FilterCombination} from "../_models/metadata/v2/filter-combination";
|
||||||
|
|
@ -44,6 +44,7 @@ import {LicenseService} from "../_services/license.service";
|
||||||
import {SafeUrlPipe} from "../_pipes/safe-url.pipe";
|
import {SafeUrlPipe} from "../_pipes/safe-url.pipe";
|
||||||
import {MergePersonModalComponent} from "./_modal/merge-person-modal/merge-person-modal.component";
|
import {MergePersonModalComponent} from "./_modal/merge-person-modal/merge-person-modal.component";
|
||||||
import {EVENTS, MessageHubService} from "../_services/message-hub.service";
|
import {EVENTS, MessageHubService} from "../_services/message-hub.service";
|
||||||
|
import {BadgeExpanderComponent} from "../shared/badge-expander/badge-expander.component";
|
||||||
|
|
||||||
interface PersonMergeEvent {
|
interface PersonMergeEvent {
|
||||||
srcId: number,
|
srcId: number,
|
||||||
|
|
@ -53,24 +54,25 @@ interface PersonMergeEvent {
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-person-detail',
|
selector: 'app-person-detail',
|
||||||
imports: [
|
imports: [
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
ImageComponent,
|
ImageComponent,
|
||||||
SideNavCompanionBarComponent,
|
SideNavCompanionBarComponent,
|
||||||
ReadMoreComponent,
|
ReadMoreComponent,
|
||||||
TagBadgeComponent,
|
TagBadgeComponent,
|
||||||
PersonRolePipe,
|
PersonRolePipe,
|
||||||
CarouselReelComponent,
|
CarouselReelComponent,
|
||||||
CardItemComponent,
|
CardItemComponent,
|
||||||
CardActionablesComponent,
|
CardActionablesComponent,
|
||||||
TranslocoDirective,
|
TranslocoDirective,
|
||||||
ChapterCardComponent,
|
ChapterCardComponent,
|
||||||
SafeUrlPipe
|
SafeUrlPipe,
|
||||||
],
|
BadgeExpanderComponent
|
||||||
templateUrl: './person-detail.component.html',
|
],
|
||||||
styleUrl: './person-detail.component.scss',
|
templateUrl: './person-detail.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
styleUrl: './person-detail.component.scss',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class PersonDetailComponent implements OnInit {
|
export class PersonDetailComponent implements OnInit {
|
||||||
private readonly route = inject(ActivatedRoute);
|
private readonly route = inject(ActivatedRoute);
|
||||||
|
|
@ -104,6 +106,7 @@ export class PersonDetailComponent implements OnInit {
|
||||||
personActions: Array<ActionItem<Person>> = this.actionService.getPersonActions(this.handleAction.bind(this));
|
personActions: Array<ActionItem<Person>> = this.actionService.getPersonActions(this.handleAction.bind(this));
|
||||||
chaptersByRole: any = {};
|
chaptersByRole: any = {};
|
||||||
anilistUrl: string = '';
|
anilistUrl: string = '';
|
||||||
|
|
||||||
private readonly personSubject = new BehaviorSubject<Person | null>(null);
|
private readonly personSubject = new BehaviorSubject<Person | null>(null);
|
||||||
protected readonly person$ = this.personSubject.asObservable().pipe(tap(p => {
|
protected readonly person$ = this.personSubject.asObservable().pipe(tap(p => {
|
||||||
if (p?.aniListId) {
|
if (p?.aniListId) {
|
||||||
|
|
@ -291,4 +294,6 @@ export class PersonDetailComponent implements OnInit {
|
||||||
action.callback(action, this.person);
|
action.callback(action, this.person);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected readonly FilterField = FilterField;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@
|
||||||
<span class="fw-bold">{{t('publication-status-title')}}</span>
|
<span class="fw-bold">{{t('publication-status-title')}}</span>
|
||||||
<div>
|
<div>
|
||||||
@if (seriesMetadata.publicationStatus | publicationStatus; as pubStatus) {
|
@if (seriesMetadata.publicationStatus | publicationStatus; as pubStatus) {
|
||||||
<a class="dark-exempt btn-icon" (click)="openFilter(FilterField.PublicationStatus, seriesMetadata.publicationStatus)"
|
<a class="dark-exempt btn-icon font-size" (click)="openFilter(FilterField.PublicationStatus, seriesMetadata!.publicationStatus)"
|
||||||
href="javascript:void(0);"
|
href="javascript:void(0);"
|
||||||
[ngbTooltip]="t('publication-status-tooltip') + (seriesMetadata.totalCount === 0 ? '' : ' (' + seriesMetadata.maxCount + ' / ' + seriesMetadata.totalCount + ')')">
|
[ngbTooltip]="t('publication-status-tooltip') + (seriesMetadata.totalCount === 0 ? '' : ' (' + seriesMetadata.maxCount + ' / ' + seriesMetadata.totalCount + ')')">
|
||||||
{{pubStatus}}
|
{{pubStatus}}
|
||||||
|
|
|
||||||
|
|
@ -30,3 +30,7 @@
|
||||||
:host ::ng-deep .card-actions.btn-actions .btn {
|
:host ::ng-deep .card-actions.btn-actions .btn {
|
||||||
padding: 0.375rem 0.75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.font-size {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,7 @@
|
||||||
height: 35px;
|
height: 35px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::ng-deep .badge-expander .content a {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1103,7 +1103,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"person-detail": {
|
"person-detail": {
|
||||||
"aka": "Also known as ",
|
"aka-title": "Also known as ",
|
||||||
"known-for-title": "Known For",
|
"known-for-title": "Known For",
|
||||||
"individual-role-title": "As a {{role}}",
|
"individual-role-title": "As a {{role}}",
|
||||||
"browse-person-title": "All Works of {{name}}",
|
"browse-person-title": "All Works of {{name}}",
|
||||||
|
|
@ -1859,7 +1859,8 @@
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"all-filters": "Smart Filters",
|
"all-filters": "Smart Filters",
|
||||||
"nav-link-header": "Navigation Options",
|
"nav-link-header": "Navigation Options",
|
||||||
"close": "{{common.close}}"
|
"close": "{{common.close}}",
|
||||||
|
"person-aka-label": "{{person-detail.aka-title}}:"
|
||||||
},
|
},
|
||||||
|
|
||||||
"promoted-icon": {
|
"promoted-icon": {
|
||||||
|
|
|
||||||
|
|
@ -436,4 +436,7 @@
|
||||||
--login-input-font-family: 'League Spartan', sans-serif;
|
--login-input-font-family: 'League Spartan', sans-serif;
|
||||||
--login-input-placeholder-opacity: 0.5;
|
--login-input-placeholder-opacity: 0.5;
|
||||||
--login-input-placeholder-color: #fff;
|
--login-input-placeholder-color: #fff;
|
||||||
|
|
||||||
|
/** Series Detail **/
|
||||||
|
--detail-subtitle-color: lightgrey;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue