New: Optionally display authors as LastName, FirstName in index

Fixes #1062
pull/1141/head
ta264 3 years ago
parent 332997aefe
commit 7f8dc3d2b4

@ -94,13 +94,13 @@ class AuthorIndex extends Component {
} = this.props;
// Reset if not sorting by sortName
if (sortKey !== 'sortName') {
if (sortKey !== 'sortName' && sortKey !== 'sortNameLastFirst') {
this.setState({ jumpBarItems: { order: [] } });
return;
}
const characters = _.reduce(items, (acc, item) => {
let char = item.sortName.charAt(0);
let char = item[sortKey].charAt(0);
if (!isNaN(char)) {
char = '#';

@ -34,16 +34,16 @@ function AuthorIndexSortMenu(props) {
sortDirection={sortDirection}
onPress={onSortSelect}
>
Name
First Name
</SortMenuItem>
<SortMenuItem
name="authorType"
name="sortNameLastFirst"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
Type
Last Name
</SortMenuItem>
<SortMenuItem

@ -74,6 +74,7 @@ class AuthorIndexOverview extends Component {
const {
id,
authorName,
authorNameLastFirst,
overview,
monitored,
status,
@ -167,7 +168,7 @@ class AuthorIndexOverview extends Component {
className={styles.title}
to={link}
>
{authorName}
{overviewOptions.showTitle === 'firstLast' ? authorName : authorNameLastFirst}
</Link>
<div className={styles.actions}>
@ -247,7 +248,8 @@ class AuthorIndexOverview extends Component {
AuthorIndexOverview.propTypes = {
id: PropTypes.number.isRequired,
authorName: PropTypes.string.isRequired,
overview: PropTypes.string.isRequired,
authorNameLastFirst: PropTypes.string.isRequired,
overview: PropTypes.string,
monitored: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired,

@ -90,7 +90,8 @@ class AuthorIndexOverviews extends Component {
if (this._grid &&
(prevState.width !== width ||
prevState.rowHeight !== rowHeight ||
hasDifferentItemsOrOrder(prevProps.items, items))) {
hasDifferentItemsOrOrder(prevProps.items, items) ||
prevProps.overviewOptions.showTitle !== overviewOptions.showTitle)) {
// recomputeGridSize also forces Grid to discard its cache of rendered cells
this._grid.recomputeGridSize();
}
@ -101,7 +102,7 @@ class AuthorIndexOverviews extends Component {
}
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
const index = getIndexOfFirstCharacter(items, sortKey, jumpToCharacter);
if (this._grid && index != null) {

@ -13,6 +13,11 @@ import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
const nameOptions = [
{ key: 'firstLast', value: translate('NameFirstLast') },
{ key: 'lastFirst', value: translate('NameLastFirst') }
];
const posterSizeOptions = [
{ key: 'small', value: 'Small' },
{ key: 'medium', value: 'Medium' },
@ -28,6 +33,7 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
super(props, context);
this.state = {
showTitle: props.showTitle,
detailedProgressBar: props.detailedProgressBar,
size: props.size,
showMonitored: props.showMonitored,
@ -43,6 +49,7 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
componentDidUpdate(prevProps) {
const {
showTitle,
detailedProgressBar,
size,
showMonitored,
@ -57,6 +64,10 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
const state = {};
if (showTitle !== prevProps.showTitle) {
state.showTitle = showTitle;
}
if (detailedProgressBar !== prevProps.detailedProgressBar) {
state.detailedProgressBar = detailedProgressBar;
}
@ -122,6 +133,7 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
} = this.props;
const {
showTitle,
detailedProgressBar,
size,
showMonitored,
@ -142,6 +154,20 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
<ModalBody>
<Form>
<FormGroup>
<FormLabel>
{translate('NameStyle')}
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="showTitle"
value={showTitle}
values={nameOptions}
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('PosterSize')}
@ -291,6 +317,7 @@ class AuthorIndexOverviewOptionsModalContent extends Component {
}
AuthorIndexOverviewOptionsModalContent.propTypes = {
showTitle: PropTypes.string.isRequired,
size: PropTypes.string.isRequired,
detailedProgressBar: PropTypes.bool.isRequired,
showMonitored: PropTypes.bool.isRequired,

@ -70,6 +70,7 @@ class AuthorIndexPoster extends Component {
const {
id,
authorName,
authorNameLastFirst,
monitored,
titleSlug,
status,
@ -193,9 +194,9 @@ class AuthorIndexPoster extends Component {
/>
{
showTitle &&
showTitle !== 'no' &&
<div className={styles.title}>
{authorName}
{showTitle === 'firstLast' ? authorName : authorNameLastFirst}
</div>
}
@ -260,6 +261,7 @@ class AuthorIndexPoster extends Component {
AuthorIndexPoster.propTypes = {
id: PropTypes.number.isRequired,
authorName: PropTypes.string.isRequired,
authorNameLastFirst: PropTypes.string.isRequired,
monitored: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired,
@ -269,7 +271,7 @@ AuthorIndexPoster.propTypes = {
posterWidth: PropTypes.number.isRequired,
posterHeight: PropTypes.number.isRequired,
detailedProgressBar: PropTypes.bool.isRequired,
showTitle: PropTypes.bool.isRequired,
showTitle: PropTypes.string.isRequired,
showMonitored: PropTypes.bool.isRequired,
showQualityProfile: PropTypes.bool.isRequired,
qualityProfile: PropTypes.object.isRequired,

@ -50,7 +50,7 @@ function calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions)
isSmallScreen ? columnPaddingSmallScreen : columnPadding
];
if (showTitle) {
if (showTitle !== 'no') {
heights.push(19);
}
@ -137,7 +137,8 @@ class AuthorIndexPosters extends Component {
prevState.columnWidth !== columnWidth ||
prevState.columnCount !== columnCount ||
prevState.rowHeight !== rowHeight ||
hasDifferentItemsOrOrder(prevProps.items, items))) {
hasDifferentItemsOrOrder(prevProps.items, items)) ||
prevProps.posterOptions.showTitle !== posterOptions.showTitle) {
// recomputeGridSize also forces Grid to discard its cache of rendered cells
this._grid.recomputeGridSize();
}
@ -148,7 +149,7 @@ class AuthorIndexPosters extends Component {
}
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
const index = getIndexOfFirstCharacter(items, sortKey, jumpToCharacter);
if (this._grid && index != null) {
const row = Math.floor(index / columnCount);

@ -19,6 +19,12 @@ const posterSizeOptions = [
{ key: 'large', value: 'Large' }
];
const nameOptions = [
{ key: 'no', value: translate('NoName') },
{ key: 'firstLast', value: translate('NameFirstLast') },
{ key: 'lastFirst', value: translate('NameLastFirst') }
];
class AuthorIndexPosterOptionsModalContent extends Component {
//
@ -148,9 +154,10 @@ class AuthorIndexPosterOptionsModalContent extends Component {
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
type={inputTypes.SELECT}
name="showTitle"
value={showTitle}
values={nameOptions}
helpText={translate('ShowTitleHelpText')}
onChange={this.onChangePosterOption}
/>
@ -214,7 +221,7 @@ class AuthorIndexPosterOptionsModalContent extends Component {
AuthorIndexPosterOptionsModalContent.propTypes = {
size: PropTypes.string.isRequired,
showTitle: PropTypes.bool.isRequired,
showTitle: PropTypes.string.isRequired,
showMonitored: PropTypes.bool.isRequired,
showQualityProfile: PropTypes.bool.isRequired,
detailedProgressBar: PropTypes.bool.isRequired,

@ -82,6 +82,7 @@ class AuthorIndexRow extends Component {
monitored,
status,
authorName,
authorNameLastFirst,
titleSlug,
qualityProfile,
metadataProfile,
@ -95,6 +96,7 @@ class AuthorIndexRow extends Component {
tags,
images,
showBanners,
showTitle,
showSearchAction,
columns,
isRefreshingAuthor,
@ -169,14 +171,14 @@ class AuthorIndexRow extends Component {
{
hasBannerError &&
<div className={styles.overlayTitle}>
{authorName}
{showTitle === 'firstLast' ? authorName : authorNameLastFirst}
</div>
}
</Link> :
<AuthorNameLink
titleSlug={titleSlug}
authorName={authorName}
authorName={showTitle === 'firstLast' ? authorName : authorNameLastFirst}
/>
}
</VirtualTableRowCell>
@ -408,6 +410,7 @@ AuthorIndexRow.propTypes = {
monitored: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
authorName: PropTypes.string.isRequired,
authorNameLastFirst: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired,
qualityProfile: PropTypes.object.isRequired,
metadataProfile: PropTypes.object.isRequired,
@ -422,6 +425,7 @@ AuthorIndexRow.propTypes = {
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
showBanners: PropTypes.bool.isRequired,
showTitle: PropTypes.string.isRequired,
showSearchAction: PropTypes.bool.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
isRefreshingAuthor: PropTypes.bool.isRequired,

@ -25,12 +25,13 @@ class AuthorIndexTable extends Component {
componentDidUpdate(prevProps) {
const {
items,
sortKey,
jumpToCharacter
} = this.props;
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
const scrollIndex = getIndexOfFirstCharacter(items, jumpToCharacter);
const scrollIndex = getIndexOfFirstCharacter(items, sortKey, jumpToCharacter);
if (scrollIndex != null) {
this.setState({ scrollIndex });
@ -47,7 +48,8 @@ class AuthorIndexTable extends Component {
const {
items,
columns,
showBanners
showBanners,
showTitle
} = this.props;
const author = items[rowIndex];
@ -66,6 +68,7 @@ class AuthorIndexTable extends Component {
qualityProfileId={author.qualityProfileId}
metadataProfileId={author.metadataProfileId}
showBanners={showBanners}
showTitle={showTitle}
/>
</VirtualTableRow>
);
@ -118,9 +121,10 @@ class AuthorIndexTable extends Component {
AuthorIndexTable.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
sortKey: PropTypes.string,
sortKey: PropTypes.string.isRequired,
sortDirection: PropTypes.oneOf(sortDirections.all),
showBanners: PropTypes.bool.isRequired,
showTitle: PropTypes.string.isRequired,
jumpToCharacter: PropTypes.string,
scrollTop: PropTypes.number,
scroller: PropTypes.instanceOf(Element).isRequired,

@ -12,6 +12,7 @@ function createMapStateToProps() {
return {
isSmallScreen: dimensions.isSmallScreen,
showBanners: tableOptions.showBanners,
showTitle: tableOptions.showTitle,
columns
};
}

@ -6,6 +6,11 @@ import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
const nameOptions = [
{ key: 'firstLast', value: translate('NameFirstLast') },
{ key: 'lastFirst', value: translate('NameLastFirst') }
];
class AuthorIndexTableOptions extends Component {
//
@ -16,23 +21,27 @@ class AuthorIndexTableOptions extends Component {
this.state = {
showBanners: props.showBanners,
showSearchAction: props.showSearchAction
showSearchAction: props.showSearchAction,
showTitle: props.showTitle
};
}
componentDidUpdate(prevProps) {
const {
showBanners,
showSearchAction
showSearchAction,
showTitle
} = this.props;
if (
showBanners !== prevProps.showBanners ||
showSearchAction !== prevProps.showSearchAction
showSearchAction !== prevProps.showSearchAction ||
showTitle !== prevProps.showTitle
) {
this.setState({
showBanners,
showSearchAction
showSearchAction,
showTitle
});
}
}
@ -59,11 +68,26 @@ class AuthorIndexTableOptions extends Component {
render() {
const {
showBanners,
showSearchAction
showSearchAction,
showTitle
} = this.state;
return (
<Fragment>
<FormGroup>
<FormLabel>
{translate('NameStyle')}
</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="showTitle"
value={showTitle}
values={nameOptions}
onChange={this.onTableOptionChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('ShowBanners')}
@ -97,6 +121,7 @@ class AuthorIndexTableOptions extends Component {
}
AuthorIndexTableOptions.propTypes = {
showTitle: PropTypes.string.isRequired,
showBanners: PropTypes.bool.isRequired,
showSearchAction: PropTypes.bool.isRequired,
onTableOptionChange: PropTypes.func.isRequired

@ -16,31 +16,23 @@ export const section = 'authorIndex';
// State
export const defaultState = {
sortKey: 'sortName',
sortKey: 'sortNameLastFirst',
sortDirection: sortDirections.ASCENDING,
secondarySortKey: 'sortName',
secondarySortKey: 'sortNameLastFirst',
secondarySortDirection: sortDirections.ASCENDING,
view: 'posters',
posterOptions: {
detailedProgressBar: false,
size: 'large',
showTitle: true,
showMonitored: true,
showQualityProfile: true,
showSearchAction: false
},
bannerOptions: {
detailedProgressBar: false,
size: 'large',
showTitle: false,
showTitle: 'lastFirst',
showMonitored: true,
showQualityProfile: true,
showSearchAction: false
},
overviewOptions: {
showTitle: 'lastFirst',
detailedProgressBar: false,
size: 'medium',
showMonitored: true,
@ -54,6 +46,7 @@ export const defaultState = {
},
tableOptions: {
showTitle: 'lastFirst',
showBanners: false,
showSearchAction: false
},

@ -9,12 +9,14 @@ function createUnoptimizedSelector(uiSection) {
const items = authors.items.map((s) => {
const {
id,
sortName
sortName,
sortNameLastFirst
} = s;
return {
id,
sortName
sortName,
sortNameLastFirst
};
});

@ -1,8 +1,8 @@
import _ from 'lodash';
export default function getIndexOfFirstCharacter(items, character) {
export default function getIndexOfFirstCharacter(items, sortKey, character) {
return _.findIndex(items, (item) => {
const firstCharacter = item.sortName.charAt(0);
const firstCharacter = item[sortKey].charAt(0);
if (character === '#') {
return !isNaN(firstCharacter);

@ -36,7 +36,7 @@ namespace NzbDrone.Common.Test.ExtensionTests.StringExtensionTests
[TestCase("John [x]von Neumann (III)", "von Neumann, John")]
public void should_get_sort_name(string input, string expected)
{
input.ToSortName().Should().Be(expected);
input.ToLastFirst().Should().Be(expected);
}
}
}

@ -275,7 +275,7 @@ namespace NzbDrone.Common.Extensions
return new string(buf.ToArray());
}
public static string ToSortName(this string author)
public static string ToLastFirst(this string author)
{
// ported from https://github.com/kovidgoyal/calibre/blob/master/src/calibre/ebooks/metadata/__init__.py
if (author == null)

@ -20,6 +20,8 @@ namespace NzbDrone.Core.Books
public string TitleSlug { get; set; }
public string Name { get; set; }
public string SortName { get; set; }
public string NameLastFirst { get; set; }
public string SortNameLastFirst { get; set; }
public List<string> Aliases { get; set; }
public string Overview { get; set; }
public string Disambiguation { get; set; }
@ -43,7 +45,9 @@ namespace NzbDrone.Core.Books
ForeignAuthorId = other.ForeignAuthorId;
TitleSlug = other.TitleSlug;
Name = other.Name;
NameLastFirst = other.NameLastFirst;
SortName = other.SortName;
SortNameLastFirst = other.SortNameLastFirst;
Aliases = other.Aliases;
Overview = other.Overview.IsNullOrWhiteSpace() ? Overview : other.Overview;
Disambiguation = other.Disambiguation;

@ -101,7 +101,7 @@ namespace NzbDrone.Core.Books
{
tc((a, t) => a.CleanName.FuzzyMatch(t), cleanTitle),
tc((a, t) => a.Name.FuzzyMatch(t), title),
tc((a, t) => a.Name.ToSortName().FuzzyMatch(t), title),
tc((a, t) => a.Name.ToLastFirst().FuzzyMatch(t), title),
tc((a, t) => a.Metadata.Value.Aliases.Concat(new List<string> { a.Name }).Max(x => x.CleanAuthorName().FuzzyMatch(t)), cleanTitle),
};
@ -153,7 +153,7 @@ namespace NzbDrone.Core.Books
{
tc((a, t) => t.FuzzyContains(a.CleanName), cleanReportTitle),
tc((a, t) => t.FuzzyContains(a.Metadata.Value.Name), reportTitle),
tc((a, t) => t.FuzzyContains(a.Metadata.Value.Name.ToSortName()), reportTitle)
tc((a, t) => t.FuzzyContains(a.Metadata.Value.Name.ToLastFirst()), reportTitle)
};
return scoringFunctions;

@ -25,7 +25,7 @@ namespace NzbDrone.Core.Datastore.Migration
foreach (var row in rows)
{
row.SortName = row.Name.ToSortName().ToLower();
row.SortName = row.Name.ToLastFirst().ToLower();
}
var sql = "UPDATE AuthorMetadata SET SortName = @SortName WHERE Id = @Id";

@ -0,0 +1,45 @@
using System.Data;
using System.Linq;
using Dapper;
using FluentMigrator;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(013)]
public class update_author_sort_name_again : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("AuthorMetadata").AddColumn("NameLastFirst").AsString().Nullable();
Alter.Table("AuthorMetadata").AddColumn("SortNameLastFirst").AsString().Nullable();
Execute.WithConnection(MigrateAuthorSortName);
Alter.Table("AuthorMetadata").AlterColumn("NameLastFirst").AsString().NotNullable();
Alter.Table("AuthorMetadata").AlterColumn("SortNameLastFirst").AsString().NotNullable();
}
private void MigrateAuthorSortName(IDbConnection conn, IDbTransaction tran)
{
var rows = conn.Query<AuthorName>("SELECT AuthorMetadata.Id, AuthorMetadata.Name FROM AuthorMetadata", transaction: tran);
foreach (var row in rows)
{
row.NameLastFirst = row.Name.ToLastFirst();
row.SortName = row.Name.ToLower();
row.SortNameLastFirst = row.Name.ToLastFirst().ToLower();
}
var sql = "UPDATE AuthorMetadata SET NameLastFirst = @NameLastFirst, SortName = @SortName, SortNameLastFirst = @SortNameLastFirst WHERE Id = @Id";
conn.Execute(sql, rows, transaction: tran);
}
private class AuthorName : ModelBase
{
public string Name { get; set; }
public string NameLastFirst { get; set; }
public string SortName { get; set; }
public string SortNameLastFirst { get; set; }
}
}
}

@ -381,6 +381,9 @@
"MustContain": "Must Contain",
"MustNotContain": "Must Not Contain",
"Name": "Name",
"NameFirstLast": "First Name Last Name",
"NameLastFirst": "Last Name, First Name",
"NameStyle": "Author Name Style",
"NamingSettings": "Naming Settings",
"NETCore": ".NET Core",
"New": "New",
@ -390,6 +393,7 @@
"NoLimitForAnyRuntime": "No limit for any runtime",
"NoLogFiles": "No log files",
"NoMinimumForAnyRuntime": "No minimum for any runtime",
"NoName": "Do not show name",
"None": "None",
"NoTagsHaveBeenAddedYet": "No tags have been added yet. Add tags to link authors with delay profiles, restrictions, or notifications. Click {0} to find out more about tags in Readarr.",
"NotificationTriggers": "Notification Triggers",

@ -531,7 +531,9 @@ namespace NzbDrone.Core.MetadataSource.Goodreads
Status = resource.DiedOnDate < DateTime.UtcNow ? AuthorStatusType.Ended : AuthorStatusType.Continuing
};
author.SortName = author.Name.ToSortName().ToLower();
author.SortName = author.Name.ToLower();
author.NameLastFirst = author.Name.ToLastFirst();
author.SortNameLastFirst = author.NameLastFirst.ToLower();
if (!NoPhotoRegex.IsMatch(resource.LargeImageUrl))
{
@ -556,7 +558,9 @@ namespace NzbDrone.Core.MetadataSource.Goodreads
TitleSlug = resource.Id.ToString()
};
author.SortName = author.Name.ToSortName().ToLower();
author.SortName = author.Name.ToLower();
author.NameLastFirst = author.Name.ToLastFirst();
author.SortNameLastFirst = author.NameLastFirst.ToLower();
if (resource.RatingsCount.HasValue)
{
@ -707,7 +711,9 @@ namespace NzbDrone.Core.MetadataSource.Goodreads
{
ForeignAuthorId = resource.BestBook.AuthorId.ToString(),
Name = resource.BestBook.AuthorName,
SortName = resource.BestBook.AuthorName.ToSortName().ToLower(),
NameLastFirst = resource.BestBook.AuthorName.ToLastFirst(),
SortName = resource.BestBook.AuthorName.ToLower(),
SortNameLastFirst = resource.BestBook.AuthorName.ToLastFirst().ToLower(),
TitleSlug = resource.BestBook.AuthorId.ToString()
}
};

@ -315,7 +315,9 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
Ratings = new Ratings { Votes = resource.RatingsCount, Value = (decimal)resource.AverageRating }
};
author.SortName = author.Name.ToSortName().ToLower();
author.NameLastFirst = author.Name.ToLastFirst();
author.SortName = author.Name.ToLower();
author.SortNameLastFirst = author.Name.ToLastFirst().ToLower();
if (resource.ImageUrl.IsNotNullOrWhiteSpace())
{

@ -221,7 +221,7 @@ namespace NzbDrone.Core.Organizer
tokenHandlers["{Author Name}"] = m => author.Name;
tokenHandlers["{Author CleanName}"] = m => CleanTitle(author.Name);
tokenHandlers["{Author NameThe}"] = m => TitleThe(author.Name);
tokenHandlers["{Author SortName}"] = m => author.Name.ToSortName();
tokenHandlers["{Author SortName}"] = m => author.Metadata.Value.NameLastFirst;
if (author.Metadata.Value.Disambiguation != null)
{

@ -357,7 +357,7 @@ namespace NzbDrone.Core.Parser
if (foundAuthor == null)
{
foundAuthor = GetTitleFuzzy(simpleTitle, authorName.ToSortName(), out remainder);
foundAuthor = GetTitleFuzzy(simpleTitle, authorName.ToLastFirst(), out remainder);
}
var foundBook = GetTitleFuzzy(remainder, bestBook.Title, out _);

@ -22,6 +22,7 @@ namespace Readarr.Api.V1.Author
public bool Ended => Status == AuthorStatusType.Ended;
public string AuthorName { get; set; }
public string AuthorNameLastFirst { get; set; }
public string ForeignAuthorId { get; set; }
public string TitleSlug { get; set; }
public string Overview { get; set; }
@ -47,6 +48,8 @@ namespace Readarr.Api.V1.Author
public List<string> Genres { get; set; }
public string CleanName { get; set; }
public string SortName { get; set; }
public string SortNameLastFirst { get; set; }
public HashSet<int> Tags { get; set; }
public DateTime Added { get; set; }
public AddAuthorOptions AddOptions { get; set; }
@ -70,9 +73,11 @@ namespace Readarr.Api.V1.Author
AuthorMetadataId = model.AuthorMetadataId,
AuthorName = model.Name,
AuthorNameLastFirst = model.Metadata.Value.NameLastFirst,
//AlternateTitles
SortName = model.Metadata.Value.SortName,
SortNameLastFirst = model.Metadata.Value.SortNameLastFirst,
Status = model.Metadata.Value.Status,
Overview = model.Metadata.Value.Overview,
@ -119,7 +124,9 @@ namespace Readarr.Api.V1.Author
ForeignAuthorId = resource.ForeignAuthorId,
TitleSlug = resource.TitleSlug,
Name = resource.AuthorName,
NameLastFirst = resource.AuthorNameLastFirst,
SortName = resource.SortName,
SortNameLastFirst = resource.SortNameLastFirst,
Status = resource.Status,
Overview = resource.Overview,
Links = resource.Links,

@ -173,6 +173,8 @@ namespace Readarr.Api.V1.Queue
return q => q.Status;
case "authors.sortName":
return q => q.Author?.Metadata.Value.SortName ?? string.Empty;
case "authors.sortNameLastFirst":
return q => q.Author?.Metadata.Value.SortNameLastFirst ?? string.Empty;
case "title":
return q => q.Title;
case "book":

Loading…
Cancel
Save