mirror of
https://github.com/Readarr/Readarr
synced 2026-01-30 11:23:00 +01:00
New: Optionally display authors as LastName, FirstName in index
Fixes #1062
This commit is contained in:
parent
332997aefe
commit
7f8dc3d2b4
28 changed files with 193 additions and 54 deletions
|
|
@ -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 @@ public void should_remove_brackets(string input, string expected)
|
|||
[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 @@ public static string RemoveBracketedText(this string input)
|
|||
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 @@ public AuthorMetadata()
|
|||
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 @@ public override void UseMetadataFrom(AuthorMetadata other)
|
|||
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 @@ public List<Tuple<Func<Author, string, double>, string>> AuthorScoringFunctions(
|
|||
{
|
||||
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 @@ public List<Tuple<Func<Author, string, double>, string>> ReportAuthorScoringFunc
|
|||
{
|
||||
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 @@ private void MigrateAuthorSortName(IDbConnection conn, IDbTransaction tran)
|
|||
|
||||
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 @@ private static AuthorMetadata MapAuthor(AuthorResource resource)
|
|||
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 @@ private static AuthorMetadata MapAuthor(AuthorSummaryResource resource)
|
|||
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 @@ private Book MapSearchResult(WorkResource resource)
|
|||
{
|
||||
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 @@ private static AuthorMetadata MapAuthor(AuthorSummaryResource resource)
|
|||
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 @@ private void AddAuthorTokens(Dictionary<string, Func<TokenMatch, string>> tokenH
|
|||
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 @@ public static ParsedBookInfo ParseBookTitleWithSearchCriteria(string title, Auth
|
|||
|
||||
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 @@ public class AuthorResource : RestResource
|
|||
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 @@ public class AuthorResource : RestResource
|
|||
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 @@ public static AuthorResource ToResource(this NzbDrone.Core.Books.Author model)
|
|||
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 @@ public static NzbDrone.Core.Books.Author ToModel(this AuthorResource resource)
|
|||
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 @@ public PagingResource<QueueResource> GetQueue(bool includeUnknownAuthorItems = f
|
|||
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…
Reference in a new issue