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",
|
||||
NormalizedName = "Delores Casey".ToNormalized(),
|
||||
Description = "Hi, I'm Delores Casey!",
|
||||
Aliases = [new PersonAlias
|
||||
{
|
||||
Alias = "Casey, Delores",
|
||||
NormalizedAlias = "Casey, Delores".ToNormalized(),
|
||||
}],
|
||||
Aliases = [new PersonAliasBuilder("Casey, Delores").Build()],
|
||||
AniListId = 27,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using API.Data;
|
|||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Metadata;
|
||||
using API.DTOs.Person;
|
||||
using API.DTOs.SeriesDetail;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ using System.Threading.Tasks;
|
|||
using API.Constants;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Filtering;
|
||||
using API.DTOs.Metadata;
|
||||
using API.DTOs.Person;
|
||||
using API.DTOs.Recommendation;
|
||||
using API.DTOs.SeriesDetail;
|
||||
using API.Entities.Enums;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using API.DTOs.CollectionTags;
|
|||
using API.DTOs.Filtering;
|
||||
using API.DTOs.Filtering.v2;
|
||||
using API.DTOs.OPDS;
|
||||
using API.DTOs.Person;
|
||||
using API.DTOs.Progress;
|
||||
using API.DTOs.Search;
|
||||
using API.Entities;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
|||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Person;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ using System.Threading.Tasks;
|
|||
using API.Constants;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Person;
|
||||
using API.DTOs.ReadingLists;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ public class SearchController : BaseApiController
|
|||
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
if (user == null) return Unauthorized();
|
||||
|
||||
var libraries = _unitOfWork.LibraryRepository.GetLibraryIdsForUserIdAsync(user.Id, QueryContext.Search).ToList();
|
||||
if (libraries.Count == 0) return BadRequest(await _localizationService.Translate(User.GetUserId(), "libraries-restricted"));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using API.DTOs.Metadata;
|
||||
using API.DTOs.Person;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.Interfaces;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using API.DTOs.Person;
|
||||
using API.Entities.Enums;
|
||||
|
||||
namespace API.DTOs.Metadata;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
namespace API.DTOs;
|
||||
using API.DTOs.Person;
|
||||
|
||||
namespace API.DTOs;
|
||||
|
||||
/// <summary>
|
||||
/// Used to browse writers and click in to see their series
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace API.DTOs;
|
||||
namespace API.DTOs.Person;
|
||||
#nullable enable
|
||||
|
||||
public class PersonDto
|
||||
|
|
@ -13,6 +13,7 @@ public class PersonDto
|
|||
public string? SecondaryColor { get; set; }
|
||||
|
||||
public string? CoverImage { get; set; }
|
||||
public List<string> Aliases { get; set; } = [];
|
||||
|
||||
public string? Description { get; set; }
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using API.DTOs.Person;
|
||||
|
||||
namespace API.DTOs.ReadingLists;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using API.DTOs.Collection;
|
||||
using API.DTOs.CollectionTags;
|
||||
using API.DTOs.Metadata;
|
||||
using API.DTOs.Person;
|
||||
using API.DTOs.Reader;
|
||||
using API.DTOs.ReadingLists;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using API.DTOs.Metadata;
|
||||
using API.DTOs.Person;
|
||||
using API.Entities.Enums;
|
||||
|
||||
namespace API.DTOs;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using API.DTOs.Metadata;
|
||||
using API.DTOs.Person;
|
||||
using API.Entities.Enums;
|
||||
|
||||
namespace API.DTOs;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|||
namespace API.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20250504212806_PersonAliases")]
|
||||
[Migration("20250507221026_PersonAliases")]
|
||||
partial class PersonAliases
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
|
@ -1851,7 +1851,7 @@ namespace API.Data.Migrations
|
|||
b.Property<string>("NormalizedAlias")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("PersonId")
|
||||
b.Property<int>("PersonId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
|
@ -3109,9 +3109,13 @@ namespace API.Data.Migrations
|
|||
|
||||
modelBuilder.Entity("API.Entities.Person.PersonAlias", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.Person.Person", null)
|
||||
b.HasOne("API.Entities.Person.Person", "Person")
|
||||
.WithMany("Aliases")
|
||||
.HasForeignKey("PersonId");
|
||||
.HasForeignKey("PersonId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Person");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b =>
|
||||
|
|
@ -18,7 +18,7 @@ namespace API.Data.Migrations
|
|||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Alias = 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 =>
|
||||
{
|
||||
|
|
@ -27,7 +27,8 @@ namespace API.Data.Migrations
|
|||
name: "FK_PersonAlias_Person_PersonId",
|
||||
column: x => x.PersonId,
|
||||
principalTable: "Person",
|
||||
principalColumn: "Id");
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
|
|
@ -1848,7 +1848,7 @@ namespace API.Data.Migrations
|
|||
b.Property<string>("NormalizedAlias")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("PersonId")
|
||||
b.Property<int>("PersonId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
|
@ -3106,9 +3106,13 @@ namespace API.Data.Migrations
|
|||
|
||||
modelBuilder.Entity("API.Entities.Person.PersonAlias", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.Person.Person", null)
|
||||
b.HasOne("API.Entities.Person.Person", "Person")
|
||||
.WithMany("Aliases")
|
||||
.HasForeignKey("PersonId");
|
||||
.HasForeignKey("PersonId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Person");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b =>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Person;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.Person;
|
||||
using API.Extensions;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data.Misc;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Person;
|
||||
using API.DTOs.ReadingLists;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using API.DTOs.Filtering;
|
|||
using API.DTOs.Filtering.v2;
|
||||
using API.DTOs.KavitaPlus.Metadata;
|
||||
using API.DTOs.Metadata;
|
||||
using API.DTOs.Person;
|
||||
using API.DTOs.ReadingLists;
|
||||
using API.DTOs.Recommendation;
|
||||
using API.DTOs.Scrobbling;
|
||||
|
|
@ -455,11 +456,18 @@ public class SeriesRepository : ISeriesRepository
|
|||
.ProjectTo<AppUserCollectionDto>(_mapper.ConfigurationProvider)
|
||||
.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)
|
||||
.Take(maxRecords)
|
||||
.OrderBy(t => t.NormalizedName)
|
||||
.Select(p => p.Id)
|
||||
.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)
|
||||
.ToListAsync();
|
||||
|
||||
|
|
@ -475,8 +483,8 @@ public class SeriesRepository : ISeriesRepository
|
|||
.ProjectTo<TagDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
|
||||
result.Files = new List<MangaFileDto>();
|
||||
result.Chapters = new List<ChapterDto>();
|
||||
result.Files = [];
|
||||
result.Chapters = (List<ChapterDto>) [];
|
||||
|
||||
|
||||
if (includeChapterAndFiles)
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@ public class Person : IHasCoverImage
|
|||
//public long MetronId { get; set; } = 0;
|
||||
|
||||
// Relationships
|
||||
public ICollection<ChapterPeople> ChapterPeople { get; set; } = new List<ChapterPeople>();
|
||||
public ICollection<SeriesMetadataPeople> SeriesMetadataPeople { get; set; } = new List<SeriesMetadataPeople>();
|
||||
public ICollection<ChapterPeople> ChapterPeople { get; set; } = [];
|
||||
public ICollection<SeriesMetadataPeople> SeriesMetadataPeople { get; set; } = [];
|
||||
|
||||
|
||||
public void ResetColorScape()
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ namespace API.Entities.Person;
|
|||
public class PersonAlias
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public required string Alias { get; set; }
|
||||
public required string NormalizedAlias { get; set; }
|
||||
|
||||
public string Alias { get; set; }
|
||||
|
||||
public string NormalizedAlias { get; set; }
|
||||
public int PersonId { get; set; }
|
||||
public Person Person { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,27 +50,26 @@ public static class SearchQueryableExtensions
|
|||
// Get people from SeriesMetadata
|
||||
var peopleFromSeriesMetadata = queryable
|
||||
.Where(sm => seriesIds.Contains(sm.SeriesId))
|
||||
.SelectMany(sm => sm.People)
|
||||
.Include(sp => sp.Person.Aliases)
|
||||
.Where(p => (p.Person.Name != null && EF.Functions.Like(p.Person.Name, $"%{searchQuery}%"))
|
||||
|| p.Person.Aliases.Any(pa => EF.Functions.Like(pa.Alias, $"%{searchQuery}%")))
|
||||
.Select(p => p.Person);
|
||||
.SelectMany(sm => sm.People.Select(sp => sp.Person))
|
||||
.Where(p =>
|
||||
EF.Functions.Like(p.Name, $"%{searchQuery}%") ||
|
||||
p.Aliases.Any(pa => EF.Functions.Like(pa.Alias, $"%{searchQuery}%"))
|
||||
);
|
||||
|
||||
// Get people from ChapterPeople by navigating through Volume -> Series
|
||||
var peopleFromChapterPeople = queryable
|
||||
.Where(sm => seriesIds.Contains(sm.SeriesId))
|
||||
.SelectMany(sm => sm.Series.Volumes)
|
||||
.SelectMany(v => v.Chapters)
|
||||
.SelectMany(ch => ch.People)
|
||||
.Include(cp => cp.Person.Aliases)
|
||||
.Where(p => (p.Person.Name != null && EF.Functions.Like(p.Person.Name, $"%{searchQuery}%"))
|
||||
|| p.Person.Aliases.Any(pa => EF.Functions.Like(pa.Alias, $"%{searchQuery}%")))
|
||||
.Select(cp => cp.Person);
|
||||
.SelectMany(ch => ch.People.Select(cp => cp.Person))
|
||||
.Where(p =>
|
||||
EF.Functions.Like(p.Name, $"%{searchQuery}%") ||
|
||||
p.Aliases.Any(pa => EF.Functions.Like(pa.Alias, $"%{searchQuery}%"))
|
||||
);
|
||||
|
||||
// Combine both queries and ensure distinct results
|
||||
return peopleFromSeriesMetadata
|
||||
.Union(peopleFromChapterPeople)
|
||||
.Distinct()
|
||||
.Select(p => p)
|
||||
.OrderBy(p => p.NormalizedName);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using API.DTOs.KavitaPlus.Manage;
|
|||
using API.DTOs.KavitaPlus.Metadata;
|
||||
using API.DTOs.MediaErrors;
|
||||
using API.DTOs.Metadata;
|
||||
using API.DTOs.Person;
|
||||
using API.DTOs.Progress;
|
||||
using API.DTOs.Reader;
|
||||
using API.DTOs.ReadingLists;
|
||||
|
|
@ -68,7 +69,8 @@ public class AutoMapperProfiles : Profile
|
|||
CreateMap<AppUserCollection, AppUserCollectionDto>()
|
||||
.ForMember(dest => dest.Owner, opt => opt.MapFrom(src => src.AppUser.UserName))
|
||||
.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<Tag, TagDto>();
|
||||
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;
|
||||
}
|
||||
|
||||
_person.Aliases.Add(new PersonAlias()
|
||||
{
|
||||
Alias = alias,
|
||||
NormalizedAlias = alias.ToNormalized(),
|
||||
});
|
||||
_person.Aliases.Add(new PersonAliasBuilder(alias).Build());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
|||
using API.Data;
|
||||
using API.Entities.Person;
|
||||
using API.Extensions;
|
||||
using API.Helpers.Builders;
|
||||
|
||||
namespace API.Services;
|
||||
|
||||
|
|
@ -68,11 +69,7 @@ public class PersonService(IUnitOfWork unitOfWork): IPersonService
|
|||
MergeChapterPeople(dst, src);
|
||||
MergeSeriesMetadataPeople(dst, src);
|
||||
|
||||
dst.Aliases.Add(new PersonAlias
|
||||
{
|
||||
Alias = src.Name,
|
||||
NormalizedAlias = src.NormalizedName,
|
||||
});
|
||||
dst.Aliases.Add(new PersonAliasBuilder(src.Name).Build());
|
||||
|
||||
foreach (var alias in src.Aliases)
|
||||
{
|
||||
|
|
@ -84,12 +81,14 @@ public class PersonService(IUnitOfWork unitOfWork): IPersonService
|
|||
await unitOfWork.CommitAsync();
|
||||
}
|
||||
|
||||
private void MergeChapterPeople(Person dst, Person src)
|
||||
private static void MergeChapterPeople(Person dst, Person src)
|
||||
{
|
||||
|
||||
foreach (var chapter in src.ChapterPeople)
|
||||
{
|
||||
var alreadyPresent = dst.ChapterPeople
|
||||
.Any(x => x.ChapterId == chapter.ChapterId && x.Role == chapter.Role);
|
||||
|
||||
if (alreadyPresent) continue;
|
||||
|
||||
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)
|
||||
{
|
||||
var alreadyPresent = dst.SeriesMetadataPeople
|
||||
.Any(x => x.SeriesMetadataId == series.SeriesMetadataId && x.Role == series.Role);
|
||||
|
||||
if (alreadyPresent) continue;
|
||||
|
||||
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)
|
||||
{
|
||||
var normalizedAliases = aliases
|
||||
.Select(a => a.ToNormalized().Trim())
|
||||
.Where(a => !string.IsNullOrWhiteSpace(a))
|
||||
.Where(a => a != person.NormalizedName)
|
||||
.Select(a => a.ToNormalized())
|
||||
.Where(a => !string.IsNullOrEmpty(a) && a != person.NormalizedName)
|
||||
.ToList();
|
||||
|
||||
if (normalizedAliases.Count == 0)
|
||||
|
|
@ -141,11 +140,7 @@ public class PersonService(IUnitOfWork unitOfWork): IPersonService
|
|||
|
||||
if (others.Count != 0) return false;
|
||||
|
||||
person.Aliases = aliases.Select(a => new PersonAlias
|
||||
{
|
||||
Alias = a.Trim(),
|
||||
NormalizedAlias = a.Trim().ToNormalized()
|
||||
}).ToList();
|
||||
person.Aliases = aliases.Select(a => new PersonAliasBuilder(a).Build()).ToList();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using API.DTOs.Collection;
|
|||
using API.DTOs.KavitaPlus.ExternalMetadata;
|
||||
using API.DTOs.KavitaPlus.Metadata;
|
||||
using API.DTOs.Metadata.Matching;
|
||||
using API.DTOs.Person;
|
||||
using API.DTOs.Recommendation;
|
||||
using API.DTOs.Scrobbling;
|
||||
using API.DTOs.SeriesDetail;
|
||||
|
|
@ -20,6 +21,7 @@ using API.Entities.MetadataMatching;
|
|||
using API.Entities.Person;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Helpers.Builders;
|
||||
using API.Services.Tasks.Metadata;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using API.SignalR;
|
||||
|
|
@ -657,11 +659,7 @@ public class ExternalMetadataService : IExternalMetadataService
|
|||
if (existingPeopleDictionary.TryGetValue(mapping.AlternativeName.ToNormalized(), out var person))
|
||||
{
|
||||
modified = true;
|
||||
person.Aliases.Add(new PersonAlias
|
||||
{
|
||||
Alias = mapping.PreferredName,
|
||||
NormalizedAlias = mapping.PreferredName.ToNormalized(),
|
||||
});
|
||||
person.Aliases.Add(new PersonAliasBuilder(mapping.PreferredName).Build());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using API.Comparators;
|
|||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Person;
|
||||
using API.DTOs.SeriesDetail;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
}
|
||||
|
||||
.subtitle {
|
||||
color: lightgrey;
|
||||
color: var(--detail-subtitle-color);
|
||||
font-weight: bold;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export interface Person extends IHasCover {
|
|||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
aliases: Array<string>;
|
||||
coverImage?: string;
|
||||
coverImageLocked: boolean;
|
||||
malId?: number;
|
||||
|
|
|
|||
|
|
@ -119,6 +119,18 @@
|
|||
</div>
|
||||
<div class="ms-1">
|
||||
<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>
|
||||
</ng-template>
|
||||
|
|
@ -206,7 +218,7 @@
|
|||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
|
|
|
|||
|
|
@ -138,3 +138,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.small-text {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,6 @@
|
|||
}
|
||||
</h2>
|
||||
</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>
|
||||
</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="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) {
|
||||
<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>
|
||||
@if (roles.length > 0) {
|
||||
<span class="fw-bold mt-2">{{t('all-roles')}}</span>
|
||||
<div>
|
||||
<app-badge-expander [items]="roles"
|
||||
[itemsTillExpander]="6">
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import {CarouselReelComponent} from "../carousel/_components/carousel-reel/carou
|
|||
import {FilterComparison} from "../_models/metadata/v2/filter-comparison";
|
||||
import {FilterUtilitiesService} from "../shared/_services/filter-utilities.service";
|
||||
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 {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
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 {MergePersonModalComponent} from "./_modal/merge-person-modal/merge-person-modal.component";
|
||||
import {EVENTS, MessageHubService} from "../_services/message-hub.service";
|
||||
import {BadgeExpanderComponent} from "../shared/badge-expander/badge-expander.component";
|
||||
|
||||
interface PersonMergeEvent {
|
||||
srcId: number,
|
||||
|
|
@ -53,24 +54,25 @@ interface PersonMergeEvent {
|
|||
|
||||
|
||||
@Component({
|
||||
selector: 'app-person-detail',
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
ImageComponent,
|
||||
SideNavCompanionBarComponent,
|
||||
ReadMoreComponent,
|
||||
TagBadgeComponent,
|
||||
PersonRolePipe,
|
||||
CarouselReelComponent,
|
||||
CardItemComponent,
|
||||
CardActionablesComponent,
|
||||
TranslocoDirective,
|
||||
ChapterCardComponent,
|
||||
SafeUrlPipe
|
||||
],
|
||||
templateUrl: './person-detail.component.html',
|
||||
styleUrl: './person-detail.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
selector: 'app-person-detail',
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
ImageComponent,
|
||||
SideNavCompanionBarComponent,
|
||||
ReadMoreComponent,
|
||||
TagBadgeComponent,
|
||||
PersonRolePipe,
|
||||
CarouselReelComponent,
|
||||
CardItemComponent,
|
||||
CardActionablesComponent,
|
||||
TranslocoDirective,
|
||||
ChapterCardComponent,
|
||||
SafeUrlPipe,
|
||||
BadgeExpanderComponent
|
||||
],
|
||||
templateUrl: './person-detail.component.html',
|
||||
styleUrl: './person-detail.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class PersonDetailComponent implements OnInit {
|
||||
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));
|
||||
chaptersByRole: any = {};
|
||||
anilistUrl: string = '';
|
||||
|
||||
private readonly personSubject = new BehaviorSubject<Person | null>(null);
|
||||
protected readonly person$ = this.personSubject.asObservable().pipe(tap(p => {
|
||||
if (p?.aniListId) {
|
||||
|
|
@ -291,4 +294,6 @@ export class PersonDetailComponent implements OnInit {
|
|||
action.callback(action, this.person);
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly FilterField = FilterField;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@
|
|||
<span class="fw-bold">{{t('publication-status-title')}}</span>
|
||||
<div>
|
||||
@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);"
|
||||
[ngbTooltip]="t('publication-status-tooltip') + (seriesMetadata.totalCount === 0 ? '' : ' (' + seriesMetadata.maxCount + ' / ' + seriesMetadata.totalCount + ')')">
|
||||
{{pubStatus}}
|
||||
|
|
|
|||
|
|
@ -30,3 +30,7 @@
|
|||
:host ::ng-deep .card-actions.btn-actions .btn {
|
||||
padding: 0.375rem 0.75rem;
|
||||
}
|
||||
|
||||
.font-size {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,4 +5,8 @@
|
|||
.collapsed {
|
||||
height: 35px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .badge-expander .content a {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1103,7 +1103,7 @@
|
|||
},
|
||||
|
||||
"person-detail": {
|
||||
"aka": "Also known as ",
|
||||
"aka-title": "Also known as ",
|
||||
"known-for-title": "Known For",
|
||||
"individual-role-title": "As a {{role}}",
|
||||
"browse-person-title": "All Works of {{name}}",
|
||||
|
|
@ -1859,7 +1859,8 @@
|
|||
"logout": "Logout",
|
||||
"all-filters": "Smart Filters",
|
||||
"nav-link-header": "Navigation Options",
|
||||
"close": "{{common.close}}"
|
||||
"close": "{{common.close}}",
|
||||
"person-aka-label": "{{person-detail.aka-title}}:"
|
||||
},
|
||||
|
||||
"promoted-icon": {
|
||||
|
|
|
|||
|
|
@ -436,4 +436,7 @@
|
|||
--login-input-font-family: 'League Spartan', sans-serif;
|
||||
--login-input-placeholder-opacity: 0.5;
|
||||
--login-input-placeholder-color: #fff;
|
||||
|
||||
/** Series Detail **/
|
||||
--detail-subtitle-color: lightgrey;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue