New: Optionally display authors as LastName, FirstName in index

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -36,7 +36,7 @@ namespace NzbDrone.Common.Test.ExtensionTests.StringExtensionTests
[TestCase("John [x]von Neumann (III)", "von Neumann, John")] [TestCase("John [x]von Neumann (III)", "von Neumann, John")]
public void should_get_sort_name(string input, string expected) 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()); 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 // ported from https://github.com/kovidgoyal/calibre/blob/master/src/calibre/ebooks/metadata/__init__.py
if (author == null) if (author == null)

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

@ -101,7 +101,7 @@ namespace NzbDrone.Core.Books
{ {
tc((a, t) => a.CleanName.FuzzyMatch(t), cleanTitle), tc((a, t) => a.CleanName.FuzzyMatch(t), cleanTitle),
tc((a, t) => a.Name.FuzzyMatch(t), title), 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), 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.CleanName), cleanReportTitle),
tc((a, t) => t.FuzzyContains(a.Metadata.Value.Name), reportTitle), 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; return scoringFunctions;

@ -25,7 +25,7 @@ namespace NzbDrone.Core.Datastore.Migration
foreach (var row in rows) 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"; 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", "MustContain": "Must Contain",
"MustNotContain": "Must Not Contain", "MustNotContain": "Must Not Contain",
"Name": "Name", "Name": "Name",
"NameFirstLast": "First Name Last Name",
"NameLastFirst": "Last Name, First Name",
"NameStyle": "Author Name Style",
"NamingSettings": "Naming Settings", "NamingSettings": "Naming Settings",
"NETCore": ".NET Core", "NETCore": ".NET Core",
"New": "New", "New": "New",
@ -390,6 +393,7 @@
"NoLimitForAnyRuntime": "No limit for any runtime", "NoLimitForAnyRuntime": "No limit for any runtime",
"NoLogFiles": "No log files", "NoLogFiles": "No log files",
"NoMinimumForAnyRuntime": "No minimum for any runtime", "NoMinimumForAnyRuntime": "No minimum for any runtime",
"NoName": "Do not show name",
"None": "None", "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.", "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", "NotificationTriggers": "Notification Triggers",

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

@ -315,7 +315,9 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
Ratings = new Ratings { Votes = resource.RatingsCount, Value = (decimal)resource.AverageRating } 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()) if (resource.ImageUrl.IsNotNullOrWhiteSpace())
{ {

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

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

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

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

Loading…
Cancel
Save