Merge branch 'develop' into skip-redownload

This commit is contained in:
Mark McDowall 2023-07-18 09:57:31 -07:00 committed by GitHub
commit a968d0baf1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 460 additions and 434 deletions

View File

@ -268,7 +268,7 @@ dotnet_diagnostic.CA5397.severity = suggestion
dotnet_diagnostic.SYSLIB0006.severity = none
[*.{js,html,js,hbs,less,css}]
[*.{js,html,hbs,less,css,ts,tsx}]
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

View File

@ -36,7 +36,7 @@ module.exports = (env) => {
},
entry: {
index: 'index.js'
index: 'index.ts'
},
resolve: {
@ -67,23 +67,23 @@ module.exports = (env) => {
output: {
path: distFolder,
publicPath: '/',
filename: '[name].js',
filename: '[name]-[contenthash].js',
sourceMapFilename: '[file].map'
},
optimization: {
moduleIds: 'deterministic',
chunkIds: 'named',
splitChunks: {
chunks: 'initial',
name: 'vendors'
}
chunkIds: isProduction ? 'deterministic' : 'named'
},
performance: {
hints: false
},
experiments: {
topLevelAwait: true
},
plugins: [
new webpack.DefinePlugin({
__DEV__: !isProduction,
@ -97,7 +97,8 @@ module.exports = (env) => {
new HtmlWebpackPlugin({
template: 'frontend/src/index.ejs',
filename: 'index.html',
publicPath: '/'
publicPath: '/',
inject: false
}),
new FileManagerPlugin({

View File

@ -4,13 +4,14 @@ import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import Tooltip from 'Components/Tooltip/Tooltip';
import episodeEntities from 'Episode/episodeEntities';
import EpisodeFormats from 'Episode/EpisodeFormats';
import EpisodeLanguages from 'Episode/EpisodeLanguages';
import EpisodeQuality from 'Episode/EpisodeQuality';
import EpisodeTitleLink from 'Episode/EpisodeTitleLink';
import SeasonEpisodeNumber from 'Episode/SeasonEpisodeNumber';
import { icons } from 'Helpers/Props';
import { icons, tooltipPositions } from 'Helpers/Props';
import SeriesTitleLink from 'Series/SeriesTitleLink';
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
import HistoryDetailsModal from './Details/HistoryDetailsModal';
@ -210,7 +211,14 @@ class HistoryRow extends Component {
key={name}
className={styles.customFormatScore}
>
{formatPreferredWordScore(customFormatScore)}
<Tooltip
anchor={formatPreferredWordScore(
customFormatScore,
customFormats.length
)}
tooltip={<EpisodeFormats formats={customFormats} />}
position={tooltipPositions.BOTTOM}
/>
</TableRowCell>
);
}
@ -294,4 +302,8 @@ HistoryRow.propTypes = {
onMarkAsFailedPress: PropTypes.func.isRequired
};
HistoryRow.defaultProps = {
customFormats: []
};
export default HistoryRow;

View File

@ -8,12 +8,13 @@ import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellCo
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow';
import Tooltip from 'Components/Tooltip/Tooltip';
import EpisodeFormats from 'Episode/EpisodeFormats';
import EpisodeLanguages from 'Episode/EpisodeLanguages';
import EpisodeQuality from 'Episode/EpisodeQuality';
import EpisodeTitleLink from 'Episode/EpisodeTitleLink';
import SeasonEpisodeNumber from 'Episode/SeasonEpisodeNumber';
import { icons, kinds } from 'Helpers/Props';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
import SeriesTitleLink from 'Series/SeriesTitleLink';
import formatBytes from 'Utilities/Number/formatBytes';
@ -267,7 +268,14 @@ class QueueRow extends Component {
key={name}
className={styles.customFormatScore}
>
{formatPreferredWordScore(customFormatScore)}
<Tooltip
anchor={formatPreferredWordScore(
customFormatScore,
customFormats.length
)}
tooltip={<EpisodeFormats formats={customFormats} />}
position={tooltipPositions.BOTTOM}
/>
</TableRowCell>
);
}
@ -450,6 +458,7 @@ QueueRow.propTypes = {
};
QueueRow.defaultProps = {
customFormats: [],
isGrabbing: false,
isRemoving: false
};

View File

@ -7,13 +7,13 @@ import PageConnector from 'Components/Page/PageConnector';
import ApplyTheme from './ApplyTheme';
import AppRoutes from './AppRoutes';
function App({ store, history }) {
function App({ store, history, hasTranslationsError }) {
return (
<DocumentTitle title={window.Sonarr.instanceName}>
<Provider store={store}>
<ConnectedRouter history={history}>
<ApplyTheme>
<PageConnector>
<PageConnector hasTranslationsError={hasTranslationsError}>
<AppRoutes app={App} />
</PageConnector>
</ApplyTheme>
@ -25,7 +25,8 @@ function App({ store, history }) {
App.propTypes = {
store: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
history: PropTypes.object.isRequired,
hasTranslationsError: PropTypes.bool.isRequired
};
export default App;

View File

@ -69,7 +69,7 @@ class QualityProfileSelectInputConnector extends Component {
// Listeners
onChange = ({ name, value }) => {
this.props.onChange({ name, value: parseInt(value) });
this.props.onChange({ name, value: value === 'noChange' ? value : parseInt(value) });
};
//

View File

@ -7,6 +7,7 @@ function ErrorPage(props) {
const {
version,
isLocalStorageSupported,
hasTranslationsError,
seriesError,
customFiltersError,
tagsError,
@ -19,6 +20,8 @@ function ErrorPage(props) {
if (!isLocalStorageSupported) {
errorMessage = 'Local Storage is not supported or disabled. A plugin or private browsing may have disabled it.';
} else if (hasTranslationsError) {
errorMessage = 'Failed to load translations from API';
} else if (seriesError) {
errorMessage = getErrorMessage(seriesError, 'Failed to load series from API');
} else if (customFiltersError) {
@ -49,6 +52,7 @@ function ErrorPage(props) {
ErrorPage.propTypes = {
version: PropTypes.string.isRequired,
isLocalStorageSupported: PropTypes.bool.isRequired,
hasTranslationsError: PropTypes.bool.isRequired,
seriesError: PropTypes.object,
customFiltersError: PropTypes.object,
tagsError: PropTypes.object,

View File

@ -220,6 +220,7 @@ class PageConnector extends Component {
render() {
const {
hasTranslationsError,
isPopulated,
hasError,
dispatchFetchSeries,
@ -232,11 +233,12 @@ class PageConnector extends Component {
...otherProps
} = this.props;
if (hasError || !this.state.isLocalStorageSupported) {
if (hasTranslationsError || hasError || !this.state.isLocalStorageSupported) {
return (
<ErrorPage
{...this.state}
{...otherProps}
hasTranslationsError={hasTranslationsError}
/>
);
}
@ -257,6 +259,7 @@ class PageConnector extends Component {
}
PageConnector.propTypes = {
hasTranslationsError: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
hasError: PropTypes.bool.isRequired,
isSidebarVisible: PropTypes.bool.isRequired,

View File

@ -10,6 +10,7 @@ import { icons } from 'Helpers/Props';
import locationShape from 'Helpers/Props/Shapes/locationShape';
import dimensions from 'Styles/Variables/dimensions';
import HealthStatusConnector from 'System/Status/Health/HealthStatusConnector';
import translate from 'Utilities/String/translate';
import MessagesConnector from './Messages/MessagesConnector';
import PageSidebarItem from './PageSidebarItem';
import styles from './PageSidebar.css';
@ -20,16 +21,16 @@ const SIDEBAR_WIDTH = parseInt(dimensions.sidebarWidth);
const links = [
{
iconName: icons.SERIES_CONTINUING,
title: 'Series',
title: translate('Series'),
to: '/',
alias: '/series',
children: [
{
title: 'Add New',
title: translate('AddNew'),
to: '/add/new'
},
{
title: 'Library Import',
title: translate('LibraryImport'),
to: '/add/import'
}
]
@ -37,26 +38,26 @@ const links = [
{
iconName: icons.CALENDAR,
title: 'Calendar',
title: translate('Calendar'),
to: '/calendar'
},
{
iconName: icons.ACTIVITY,
title: 'Activity',
title: translate('Activity'),
to: '/activity/queue',
children: [
{
title: 'Queue',
title: translate('Queue'),
to: '/activity/queue',
statusComponent: QueueStatusConnector
},
{
title: 'History',
title: translate('History'),
to: '/activity/history'
},
{
title: 'Blocklist',
title: translate('Blocklist'),
to: '/activity/blocklist'
}
]
@ -64,15 +65,15 @@ const links = [
{
iconName: icons.WARNING,
title: 'Wanted',
title: translate('Wanted'),
to: '/wanted/missing',
children: [
{
title: 'Missing',
title: translate('Missing'),
to: '/wanted/missing'
},
{
title: 'Cutoff Unmet',
title: translate('CutoffUnmet'),
to: '/wanted/cutoffunmet'
}
]
@ -80,59 +81,59 @@ const links = [
{
iconName: icons.SETTINGS,
title: 'Settings',
title: translate('Settings'),
to: '/settings',
children: [
{
title: 'Media Management',
title: translate('MediaManagement'),
to: '/settings/mediamanagement'
},
{
title: 'Profiles',
title: translate('Profiles'),
to: '/settings/profiles'
},
{
title: 'Quality',
title: translate('Quality'),
to: '/settings/quality'
},
{
title: 'Custom Formats',
title: translate('CustomFormats'),
to: '/settings/customformats'
},
{
title: 'Indexers',
title: translate('Indexers'),
to: '/settings/indexers'
},
{
title: 'Download Clients',
title: translate('DownloadClients'),
to: '/settings/downloadclients'
},
{
title: 'Import Lists',
title: translate('ImportLists'),
to: '/settings/importlists'
},
{
title: 'Connect',
title: translate('Connect'),
to: '/settings/connect'
},
{
title: 'Metadata',
title: translate('Metadata'),
to: '/settings/metadata'
},
{
title: 'Metadata Source',
title: translate('MetadataSource'),
to: '/settings/metadatasource'
},
{
title: 'Tags',
title: translate('Tags'),
to: '/settings/tags'
},
{
title: 'General',
title: translate('General'),
to: '/settings/general'
},
{
title: 'UI',
title: translate('UI'),
to: '/settings/ui'
}
]
@ -140,32 +141,32 @@ const links = [
{
iconName: icons.SYSTEM,
title: 'System',
title: translate('System'),
to: '/system/status',
children: [
{
title: 'Status',
title: translate('Status'),
to: '/system/status',
statusComponent: HealthStatusConnector
},
{
title: 'Tasks',
title: translate('Tasks'),
to: '/system/tasks'
},
{
title: 'Backup',
title: translate('Backup'),
to: '/system/backup'
},
{
title: 'Updates',
title: translate('Updates'),
to: '/system/updates'
},
{
title: 'Events',
title: translate('Events'),
to: '/system/events'
},
{
title: 'Log Files',
title: translate('LogFiles'),
to: '/system/logs/files'
}
]

View File

@ -30,6 +30,8 @@ import {
import { SelectStateInputProps } from 'typings/props';
import Rejection from 'typings/Rejection';
import formatBytes from 'Utilities/Number/formatBytes';
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
import translate from 'Utilities/String/translate';
import InteractiveImportRowCellPlaceholder from './InteractiveImportRowCellPlaceholder';
import styles from './InteractiveImportRow.css';
@ -57,6 +59,7 @@ interface InteractiveImportRowProps {
languages?: Language[];
size: number;
customFormats?: object[];
customFormatScore?: number;
rejections: Rejection[];
columns: Column[];
episodeFileId?: number;
@ -80,6 +83,7 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
releaseGroup,
size,
customFormats,
customFormatScore,
rejections,
isReprocessing,
isSelected,
@ -427,8 +431,8 @@ function InteractiveImportRow(props: InteractiveImportRowProps) {
<TableRowCell>
{customFormats?.length ? (
<Popover
anchor={<Icon name={icons.INTERACTIVE} />}
title="Formats"
anchor={formatPreferredWordScore(customFormatScore)}
title={translate('CustomFormats')}
body={
<div className={styles.customFormatTooltip}>
<EpisodeFormats formats={customFormats} />

View File

@ -11,6 +11,7 @@ import ModalHeader from 'Components/Modal/ModalHeader';
import { icons } from 'Helpers/Props';
import { clear, fetch } from 'Store/Actions/parseActions';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import ParseResult from './ParseResult';
import parseStateSelector from './parseStateSelector';
import styles from './ParseModalContent.css';
@ -58,7 +59,7 @@ function ParseModalContent(props: ParseModalContentProps) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>Test Parsing</ModalHeader>
<ModalHeader>{translate('TestParsing')}</ModalHeader>
<ModalBody>
<div className={styles.inputContainer}>
@ -115,7 +116,7 @@ function ParseModalContent(props: ParseModalContentProps) {
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>Close</Button>
<Button onPress={onModalClose}>{translate('Close')}</Button>
</ModalFooter>
</ModalContent>
);

View File

@ -1,20 +1,8 @@
.item {
.container {
display: flex;
flex-wrap: wrap;
}
.title {
margin-right: 20px;
width: 250px;
text-align: right;
font-weight: bold;
}
.description {
/* composes: description from '~Components/DescriptionList/DescriptionListItemTitle.css'; */
}
@media (max-width: $breakpointSmall) {
.item {
display: block;
}
.column {
flex: 0 0 50%;
}

View File

@ -1,9 +1,8 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'description': string;
'item': string;
'title': string;
'column': string;
'container': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@ -5,6 +5,7 @@ import EpisodeFormats from 'Episode/EpisodeFormats';
import SeriesTitleLink from 'Series/SeriesTitleLink';
import translate from 'Utilities/String/translate';
import ParseResultItem from './ParseResultItem';
import styles from './ParseResult.css';
interface ParseResultProps {
item: ParseModel;
@ -45,11 +46,11 @@ function ParseResult(props: ParseResultProps) {
<div>
<FieldSet legend={translate('Release')}>
<ParseResultItem
title={translate('Release Title')}
title={translate('ReleaseTitle')}
data={releaseTitle}
/>
<ParseResultItem title={translate('Series Title')} data={seriesTitle} />
<ParseResultItem title={translate('SeriesTitle')} data={seriesTitle} />
<ParseResultItem
title={translate('Year')}
@ -57,7 +58,7 @@ function ParseResult(props: ParseResultProps) {
/>
<ParseResultItem
title={translate('All Titles')}
title={translate('AllTitles')}
data={
seriesTitleInfo.allTitles?.length > 0
? seriesTitleInfo.allTitles.join(', ')
@ -66,105 +67,113 @@ function ParseResult(props: ParseResultProps) {
/>
<ParseResultItem
title={translate('Release Group')}
title={translate('ReleaseGroup')}
data={releaseGroup ?? '-'}
/>
<ParseResultItem
title={translate('Release Hash')}
title={translate('ReleaseHash')}
data={releaseHash ? releaseHash : '-'}
/>
</FieldSet>
{/*
Year
Secondary titles
special episode
*/}
<FieldSet legend={translate('EpisodeInfo')}>
<div className={styles.container}>
<div className={styles.column}>
<ParseResultItem
title={translate('SeasonNumber')}
data={
seasonNumber === 0 && absoluteEpisodeNumbers.length
? '-'
: seasonNumber
}
/>
<FieldSet legend={translate('Episode Info')}>
<ParseResultItem
title={translate('Season Number')}
data={
seasonNumber === 0 && absoluteEpisodeNumbers.length
? '-'
: seasonNumber
}
/>
<ParseResultItem
title={translate('EpisodeNumbers')}
data={episodeNumbers.join(', ') || '-'}
/>
<ParseResultItem
title={translate('Episode Number(s)')}
data={episodeNumbers.join(', ') || '-'}
/>
<ParseResultItem
title={translate('AbsoluteEpisodeNumbers')}
data={
absoluteEpisodeNumbers.length
? absoluteEpisodeNumbers.join(', ')
: '-'
}
/>
<ParseResultItem
title={translate('Absolute Episode Number(s)')}
data={
absoluteEpisodeNumbers.length
? absoluteEpisodeNumbers.join(', ')
: '-'
}
/>
<ParseResultItem
title={translate('Daily')}
data={isDaily ? 'True' : 'False'}
/>
<ParseResultItem
title={translate('Special')}
data={special ? 'True' : 'False'}
/>
<ParseResultItem
title={translate('AirDate')}
data={airDate ?? '-'}
/>
</div>
<ParseResultItem
title={translate('Full Season')}
data={fullSeason ? 'True' : 'False'}
/>
<div className={styles.column}>
<ParseResultItem
title={translate('Special')}
data={special ? 'True' : 'False'}
/>
<ParseResultItem
title={translate('Multi-Season')}
data={isMultiSeason ? 'True' : 'False'}
/>
<ParseResultItem
title={translate('FullSeason')}
data={fullSeason ? 'True' : 'False'}
/>
<ParseResultItem
title={translate('Partial Season')}
data={isPartialSeason ? 'True' : 'False'}
/>
<ParseResultItem
title={translate('MultiSeason')}
data={isMultiSeason ? 'True' : 'False'}
/>
<ParseResultItem
title={translate('Daily')}
data={isDaily ? 'True' : 'False'}
/>
<ParseResultItem title={translate('Air Date')} data={airDate ?? '-'} />
<ParseResultItem
title={translate('PartialSeason')}
data={isPartialSeason ? 'True' : 'False'}
/>
</div>
</div>
</FieldSet>
<FieldSet legend={translate('Quality')}>
<ParseResultItem
title={translate('Quality')}
data={quality.quality.name}
/>
<div className={styles.container}>
<div className={styles.column}>
<ParseResultItem
title={translate('Quality')}
data={quality.quality.name}
/>
<ParseResultItem
title={translate('Proper')}
data={
quality.revision.version > 1 && !quality.revision.isRepack
? 'True'
: '-'
}
/>
<ParseResultItem
title={translate('Version')}
data={quality.revision.version > 1 ? quality.revision.version : '-'}
/>
<ParseResultItem
title={translate('Repack')}
data={quality.revision.isRepack ? 'True' : '-'}
/>
</div>
<ParseResultItem
title={translate('Real')}
data={quality.revision.real ? 'True' : '-'}
/>
<div className={styles.column}>
<ParseResultItem
title={translate('Version')}
data={
quality.revision.version > 1 ? quality.revision.version : '-'
}
/>
<ParseResultItem
title={translate('Proper')}
data={
quality.revision.version > 1 && !quality.revision.isRepack
? 'True'
: '-'
}
/>
<ParseResultItem
title={translate('Repack')}
data={quality.revision.isRepack ? 'True' : '-'}
/>
<ParseResultItem
title={translate('Real')}
data={quality.revision.real ? 'True' : '-'}
/>
</div>
</div>
</FieldSet>
<FieldSet legend={translate('Languages')}>
@ -176,7 +185,7 @@ function ParseResult(props: ParseResultProps) {
<FieldSet legend={translate('Details')}>
<ParseResultItem
title={translate('Matched to Series')}
title={translate('MatchedToSeries')}
data={
series ? (
<SeriesTitleLink
@ -190,12 +199,12 @@ function ParseResult(props: ParseResultProps) {
/>
<ParseResultItem
title={translate('Matched to Season')}
title={translate('MatchedToSeason')}
data={episodes.length ? episodes[0].seasonNumber : '-'}
/>
<ParseResultItem
title={translate('Matched to Episodes')}
title={translate('MatchedToEpisodes')}
data={
episodes.length ? (
<div>
@ -218,12 +227,12 @@ function ParseResult(props: ParseResultProps) {
/>
<ParseResultItem
title={translate('Custom Formats')}
title={translate('CustomFormats')}
data={<EpisodeFormats formats={customFormats} />}
/>
<ParseResultItem
title={translate('Custom Format Score')}
title={translate('CustomFormatScore')}
data={customFormatScore}
/>
</FieldSet>

View File

@ -2,6 +2,7 @@ import React, { Fragment, useCallback, useState } from 'react';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import { icons } from 'Helpers/Props';
import ParseModal from 'Parse/ParseModal';
import translate from 'Utilities/String/translate';
function ParseToolbarButton() {
const [isParseModalOpen, setIsParseModalOpen] = useState(false);
@ -17,7 +18,7 @@ function ParseToolbarButton() {
return (
<Fragment>
<PageToolbarButton
label="Test Parsing"
label={translate('TestParsing')}
iconName={icons.PARSE}
onPress={onOpenParseModalPress}
/>

View File

@ -56,3 +56,9 @@
width: 120px;
}
.customFormatScore {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 55px;
}

View File

@ -3,6 +3,7 @@
interface CssExports {
'audio': string;
'audioLanguages': string;
'customFormatScore': string;
'episodeNumber': string;
'episodeNumberAnime': string;
'languages': string;

View File

@ -4,6 +4,7 @@ import MonitorToggleButton from 'Components/MonitorToggleButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import Tooltip from 'Components/Tooltip/Tooltip';
import EpisodeFormats from 'Episode/EpisodeFormats';
import EpisodeNumber from 'Episode/EpisodeNumber';
import EpisodeSearchCellConnector from 'Episode/EpisodeSearchCellConnector';
@ -12,7 +13,9 @@ import EpisodeTitleLink from 'Episode/EpisodeTitleLink';
import EpisodeFileLanguageConnector from 'EpisodeFile/EpisodeFileLanguageConnector';
import MediaInfoConnector from 'EpisodeFile/MediaInfoConnector';
import * as mediaInfoTypes from 'EpisodeFile/mediaInfoTypes';
import { tooltipPositions } from 'Helpers/Props';
import formatBytes from 'Utilities/Number/formatBytes';
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
import formatRuntime from 'Utilities/Number/formatRuntime';
import styles from './EpisodeRow.css';
@ -72,6 +75,7 @@ class EpisodeRow extends Component {
episodeFileSize,
releaseGroup,
customFormats,
customFormatScore,
alternateTitles,
columns
} = this.props;
@ -193,6 +197,24 @@ class EpisodeRow extends Component {
);
}
if (name === 'customFormatScore') {
return (
<TableRowCell
key={name}
className={styles.customFormatScore}
>
<Tooltip
anchor={formatPreferredWordScore(
customFormatScore,
customFormats.length
)}
tooltip={<EpisodeFormats formats={customFormats} />}
position={tooltipPositions.BOTTOM}
/>
</TableRowCell>
);
}
if (name === 'languages') {
return (
<TableRowCell
@ -355,6 +377,7 @@ EpisodeRow.propTypes = {
episodeFileSize: PropTypes.number,
releaseGroup: PropTypes.string,
customFormats: PropTypes.arrayOf(PropTypes.object),
customFormatScore: PropTypes.number.isRequired,
mediaInfo: PropTypes.object,
alternateTitles: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,

View File

@ -19,6 +19,7 @@ function createMapStateToProps() {
episodeFileSize: episodeFile ? episodeFile.size : null,
releaseGroup: episodeFile ? episodeFile.releaseGroup : null,
customFormats: episodeFile ? episodeFile.customFormats : [],
customFormatScore: episodeFile ? episodeFile.customFormatScore : 0,
alternateTitles: series.alternateTitles
};
}

View File

@ -16,6 +16,7 @@ import { inputTypes, kinds, sizes } from 'Helpers/Props';
import Series from 'Series/Series';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import translate from 'Utilities/String/translate';
import styles from './TagsModalContent.css';
interface TagsModalContentProps {
@ -73,12 +74,12 @@ function TagsModalContent(props: TagsModalContentProps) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>Tags</ModalHeader>
<ModalHeader>{translate('Tags')}</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<FormLabel>Tags</FormLabel>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
@ -89,7 +90,7 @@ function TagsModalContent(props: TagsModalContentProps) {
</FormGroup>
<FormGroup>
<FormLabel>Apply Tags</FormLabel>
<FormLabel>{translate('ApplyTags')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
@ -97,17 +98,17 @@ function TagsModalContent(props: TagsModalContentProps) {
value={applyTags}
values={applyTagsOptions}
helpTexts={[
'How to apply tags to the selected series',
'Add: Add the tags the existing list of tags',
'Remove: Remove the entered tags',
'Replace: Replace the tags with the entered tags (enter no tags to clear all tags)',
translate('ApplyTagsHelpTextHowToApplySeries'),
translate('ApplyTagsHelpTextAdd'),
translate('ApplyTagsHelpTextRemove'),
translate('ApplyTagsHelpTextReplace'),
]}
onChange={onApplyTagsChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Result</FormLabel>
<FormLabel>{translate('Result')}</FormLabel>
<div className={styles.result}>
{seriesTags.map((id) => {
@ -124,7 +125,11 @@ function TagsModalContent(props: TagsModalContentProps) {
return (
<Label
key={tag.id}
title={removeTag ? 'Removing tag' : 'Existing tag'}
title={
removeTag
? translate('RemovingTag')
: translate('ExistingTag')
}
kind={removeTag ? kinds.INVERSE : kinds.INFO}
size={sizes.LARGE}
>
@ -148,7 +153,7 @@ function TagsModalContent(props: TagsModalContentProps) {
return (
<Label
key={tag.id}
title={'Adding tag'}
title={translate('AddingTag')}
kind={kinds.SUCCESS}
size={sizes.LARGE}
>
@ -162,10 +167,10 @@ function TagsModalContent(props: TagsModalContentProps) {
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>Cancel</Button>
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button kind={kinds.PRIMARY} onPress={onApplyPress}>
Apply
{translate('Apply')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -103,10 +103,10 @@ function TagsModalContent(props: TagsModalContentProps) {
value={applyTags}
values={applyTagsOptions}
helpTexts={[
translate('ApplyTagsHelpTexts1'),
translate('ApplyTagsHelpTexts2'),
translate('ApplyTagsHelpTexts3'),
translate('ApplyTagsHelpTexts4'),
translate('ApplyTagsHelpTextHowToApplyDownloadClients'),
translate('ApplyTagsHelpTextAdd'),
translate('ApplyTagsHelpTextRemove'),
translate('ApplyTagsHelpTextReplace'),
]}
onChange={onApplyTagsChange}
/>

View File

@ -101,10 +101,10 @@ function TagsModalContent(props: TagsModalContentProps) {
value={applyTags}
values={applyTagsOptions}
helpTexts={[
translate('ApplyTagsHelpTexts1'),
translate('ApplyTagsHelpTexts2'),
translate('ApplyTagsHelpTexts3'),
translate('ApplyTagsHelpTexts4'),
translate('ApplyTagsHelpTextHowToApplyImportLists'),
translate('ApplyTagsHelpTextAdd'),
translate('ApplyTagsHelpTextRemove'),
translate('ApplyTagsHelpTextReplace'),
]}
onChange={onApplyTagsChange}
/>

View File

@ -101,10 +101,10 @@ function TagsModalContent(props: TagsModalContentProps) {
value={applyTags}
values={applyTagsOptions}
helpTexts={[
translate('ApplyTagsHelpTexts1'),
translate('ApplyTagsHelpTexts2'),
translate('ApplyTagsHelpTexts3'),
translate('ApplyTagsHelpTexts4'),
translate('ApplyTagsHelpTextHowToApplyIndexers'),
translate('ApplyTagsHelpTextAdd'),
translate('ApplyTagsHelpTextRemove'),
translate('ApplyTagsHelpTextReplace'),
]}
onChange={onApplyTagsChange}
/>

View File

@ -1,10 +1,13 @@
import _ from 'lodash';
import React from 'react';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import Icon from 'Components/Icon';
import episodeEntities from 'Episode/episodeEntities';
import { sortDirections } from 'Helpers/Props';
import { icons, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import translate from 'Utilities/String/translate';
import { updateItem } from './baseActions';
import createFetchHandler from './Creators/createFetchHandler';
import createHandleActions from './Creators/createHandleActions';
@ -109,6 +112,15 @@ export const defaultState = {
label: 'Formats',
isVisible: false
},
{
name: 'customFormatScore',
columnLabel: translate('CustomFormatScore'),
label: React.createElement(Icon, {
name: icons.SCORE,
title: translate('CustomFormatScore')
}),
isVisible: false
},
{
name: 'status',
label: 'Status',

View File

@ -47,6 +47,10 @@ export const defaultState = {
quality: function(item, direction) {
return item.qualityWeight || 0;
},
customFormats: function(item, direction) {
return item.customFormatScore;
}
}
};

View File

@ -10,9 +10,18 @@ function getTranslations() {
let translations = {};
getTranslations().then((data) => {
translations = data.strings;
});
export function fetchTranslations() {
return new Promise(async(resolve) => {
try {
const data = await getTranslations();
translations = data.strings;
resolve(true);
} catch (error) {
resolve(false);
}
});
}
export default function translate(key, tokens) {
const translation = translations[key] || key;

View File

@ -0,0 +1,23 @@
import { createBrowserHistory } from 'history';
import React from 'react';
import { render } from 'react-dom';
import createAppStore from 'Store/createAppStore';
import { fetchTranslations } from 'Utilities/String/translate';
import App from './App/App';
import 'Diag/ConsoleApi';
export async function bootstrap() {
const history = createBrowserHistory();
const store = createAppStore(history);
const hasTranslationsError = !(await fetchTranslations());
render(
<App
store={store}
history={history}
hasTranslationsError={hasTranslationsError}
/>,
document.getElementById('root')
);
}

View File

@ -48,7 +48,15 @@
/>
<link rel="stylesheet" type="text/css" href="/Content/Fonts/fonts.css">
<!-- webpack bundles head -->
<script>
window.Sonarr = {
urlBase: '__URL_BASE__'
};
</script>
<% for (key in htmlWebpackPlugin.files.js) { %><script type="text/javascript" src="<%= htmlWebpackPlugin.files.js[key] %>" data-no-hash></script><% } %>
<% for (key in htmlWebpackPlugin.files.css) { %><link href="<%= htmlWebpackPlugin.files.css[key] %>" rel="stylesheet"></link><% } %>
<title>Sonarr</title>
@ -77,7 +85,4 @@
<div id="portal-root"></div>
<div id="root" class="root"></div>
</body>
<script src="/initialize.js" data-no-hash></script>
<!-- webpack bundles body -->
</html>

View File

@ -1,22 +0,0 @@
import { createBrowserHistory } from 'history';
import React from 'react';
import { render } from 'react-dom';
import createAppStore from 'Store/createAppStore';
import App from './App/App';
import './preload';
import './polyfills';
import 'Diag/ConsoleApi';
import 'Styles/globals.css';
import './index.css';
const history = createBrowserHistory();
const store = createAppStore(history);
render(
<App
store={store}
history={history}
/>,
document.getElementById('root')
);

19
frontend/src/index.ts Normal file
View File

@ -0,0 +1,19 @@
import './polyfills';
import 'Styles/globals.css';
import './index.css';
const initializeUrl = `${
window.Sonarr.urlBase
}/initialize.json?t=${Date.now()}`;
const response = await fetch(initializeUrl);
window.Sonarr = await response.json();
/* eslint-disable no-undef, @typescript-eslint/ban-ts-comment */
// @ts-ignore 2304
__webpack_public_path__ = `${window.Sonarr.urlBase}/`;
/* eslint-enable no-undef, @typescript-eslint/ban-ts-comment */
const { bootstrap } = await import('./bootstrap');
await bootstrap();

View File

@ -1,2 +0,0 @@
/* eslint no-undef: 0 */
__webpack_public_path__ = `${window.Sonarr.urlBase}/`;

View File

@ -1,11 +1,11 @@
{
"compilerOptions": {
"target": "es6",
"target": "esnext",
"allowJs": true,
"checkJs": false,
"baseUrl": "src",
"jsx": "react",
"module": "commonjs",
"module": "esnext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,

View File

@ -16,7 +16,7 @@
"author": "Team Sonarr",
"license": "GPL-3.0",
"readmeFilename": "readme.md",
"main": "index.js",
"main": "index.ts",
"browserslist": [
"defaults"
],

View File

@ -120,6 +120,8 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("[HatSubs] Anime Title 1004 [E63F2984].mkv", "Anime Title", 1004, 0, 0)]
[TestCase("Anime Title 100 S3 - 01 (1080p) [5A493522]", "Anime Title 100 S3", 1, 0, 0)]
[TestCase("[SubsPlease] Anime Title 100 S3 - 01 (1080p) [5A493522]", "Anime Title 100 S3", 1, 0, 0)]
[TestCase("[CameEsp] Another Anime 100 - Another 100 Anime - 01 [720p][ESP-ENG][mkv]", "Another Anime 100 - Another 100 Anime", 1, 0, 0)]
[TestCase("[SubsPlease] Another Anime 100 - Another 100 Anime - 01 (1080p) [4E6B4518].mkv", "Another Anime 100 - Another 100 Anime", 1, 0, 0)]
// [TestCase("", "", 0, 0, 0)]
public void should_parse_absolute_numbers(string postTitle, string title, int absoluteEpisodeNumber, int seasonNumber, int episodeNumber)

View File

@ -12,9 +12,9 @@ namespace NzbDrone.Core.Notifications.Discord
Release,
Poster,
Fanart,
Indexer,
CustomFormats,
CustomFormatScore,
Indexer
CustomFormatScore
}
public enum DiscordImportFieldType

View File

@ -106,6 +106,11 @@ namespace NzbDrone.Core.Parser
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>[^-]+?)(?:(?<![-_. ]|\b[0]\d+) - )(?:[-_. ]?(?<absoluteepisode>\d{2,3}(\.\d{1,2})?(?!\d+)))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
// TODO: WIP
// Anime - [SubGroup] Title with trailing 3-digit number and sub title - Absolute Episode Number
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>[^]]+?)(?:[-_. ]{3}?(?<absoluteepisode>\d{2}(\.\d{1,2})?(?!-?\d+|-[a-z]+)))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
// Anime - [SubGroup] Title with trailing number Absolute Episode Number
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>[^-]+?)(?:(?<![-_. ]|\b[0]\d+)[_ ]+)(?:[-_. ]?(?<absoluteepisode>\d{3}(\.\d{1,2})?(?!\d+|-[a-z]+)))+(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>\[\w{8}\])?(?:$|\.mkv)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),

View File

@ -24,6 +24,7 @@ namespace Sonarr.Api.V3.EpisodeFiles
public List<Language> Languages { get; set; }
public QualityModel Quality { get; set; }
public List<CustomFormatResource> CustomFormats { get; set; }
public int CustomFormatScore { get; set; }
public MediaInfoResource MediaInfo { get; set; }
public bool QualityCutoffNotMet { get; set; }
@ -67,6 +68,8 @@ namespace Sonarr.Api.V3.EpisodeFiles
}
model.Series = series;
var customFormats = formatCalculationService?.ParseCustomFormat(model, model.Series);
var customFormatScore = series?.QualityProfile?.Value?.CalculateCustomFormatScore(customFormats) ?? 0;
return new EpisodeFileResource
{
@ -84,7 +87,8 @@ namespace Sonarr.Api.V3.EpisodeFiles
Quality = model.Quality,
MediaInfo = model.MediaInfo.ToResource(model.SceneName),
QualityCutoffNotMet = upgradableSpecification.QualityCutoffNotMet(series.QualityProfile.Value, model.Quality),
CustomFormats = formatCalculationService.ParseCustomFormat(model).ToResource(false)
CustomFormats = customFormats.ToResource(false),
CustomFormatScore = customFormatScore
};
}
}

View File

@ -29,6 +29,7 @@ namespace Sonarr.Api.V3.ManualImport
public int QualityWeight { get; set; }
public string DownloadId { get; set; }
public List<CustomFormatResource> CustomFormats { get; set; }
public int CustomFormatScore { get; set; }
public IEnumerable<Rejection> Rejections { get; set; }
}
@ -41,6 +42,9 @@ namespace Sonarr.Api.V3.ManualImport
return null;
}
var customFormats = model.CustomFormats;
var customFormatScore = model.Series?.QualityProfile?.Value?.CalculateCustomFormatScore(customFormats) ?? 0;
return new ManualImportResource
{
Id = HashConverter.GetHashInt31(model.Path),
@ -56,7 +60,8 @@ namespace Sonarr.Api.V3.ManualImport
ReleaseGroup = model.ReleaseGroup,
Quality = model.Quality,
Languages = model.Languages,
CustomFormats = model.CustomFormats.ToResource(false),
CustomFormats = customFormats.ToResource(false),
CustomFormatScore = customFormatScore,
// QualityWeight
DownloadId = model.DownloadId,

View File

@ -4062,53 +4062,6 @@
}
}
},
"/api/v3/metadata/bulk": {
"put": {
"tags": [
"Metadata"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MetadataBulkResource"
}
}
}
},
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MetadataResource"
}
}
}
}
}
},
"delete": {
"tags": [
"Metadata"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MetadataBulkResource"
}
}
}
},
"responses": {
"200": {
"description": "Success"
}
}
}
},
"/api/v3/metadata/schema": {
"get": {
"tags": [
@ -4673,53 +4626,6 @@
}
}
},
"/api/v3/notification/bulk": {
"put": {
"tags": [
"Notification"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotificationBulkResource"
}
}
}
},
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotificationResource"
}
}
}
}
}
},
"delete": {
"tags": [
"Notification"
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotificationBulkResource"
}
}
}
},
"responses": {
"200": {
"description": "Success"
}
}
}
},
"/api/v3/notification/schema": {
"get": {
"tags": [
@ -7868,6 +7774,10 @@
},
"nullable": true
},
"customFormatScore": {
"type": "integer",
"format": "int32"
},
"mediaInfo": {
"$ref": "#/components/schemas/MediaInfoResource"
},
@ -9301,31 +9211,6 @@
},
"additionalProperties": false
},
"MetadataBulkResource": {
"type": "object",
"properties": {
"ids": {
"type": "array",
"items": {
"type": "integer",
"format": "int32"
},
"nullable": true
},
"tags": {
"type": "array",
"items": {
"type": "integer",
"format": "int32"
},
"nullable": true
},
"applyTags": {
"$ref": "#/components/schemas/ApplyTags"
}
},
"additionalProperties": false
},
"MetadataResource": {
"type": "object",
"properties": {
@ -9484,31 +9369,6 @@
},
"additionalProperties": false
},
"NotificationBulkResource": {
"type": "object",
"properties": {
"ids": {
"type": "array",
"items": {
"type": "integer",
"format": "int32"
},
"nullable": true
},
"tags": {
"type": "array",
"items": {
"type": "integer",
"format": "int32"
},
"nullable": true
},
"applyTags": {
"$ref": "#/components/schemas/ApplyTags"
}
},
"additionalProperties": false
},
"NotificationResource": {
"type": "object",
"properties": {
@ -9685,6 +9545,24 @@
"$ref": "#/components/schemas/EpisodeResource"
},
"nullable": true
},
"languages": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Language"
},
"nullable": true
},
"customFormats": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CustomFormatResource"
},
"nullable": true
},
"customFormatScore": {
"type": "integer",
"format": "int32"
}
},
"additionalProperties": false
@ -10089,6 +9967,10 @@
},
"nullable": true
},
"customFormatScore": {
"type": "integer",
"format": "int32"
},
"size": {
"type": "number",
"format": "double"
@ -11328,6 +11210,14 @@
},
"nullable": true
},
"downloadClientIds": {
"type": "array",
"items": {
"type": "integer",
"format": "int32"
},
"nullable": true
},
"autoTagIds": {
"type": "array",
"items": {

View File

@ -1,65 +0,0 @@
using System.Text;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Analytics;
using NzbDrone.Core.Configuration;
namespace Sonarr.Http.Frontend
{
[Authorize(Policy = "UI")]
[ApiController]
public class InitializeJsController : Controller
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IAnalyticsService _analyticsService;
private static string _apiKey;
private static string _urlBase;
private string _generatedContent;
public InitializeJsController(IConfigFileProvider configFileProvider,
IAnalyticsService analyticsService)
{
_configFileProvider = configFileProvider;
_analyticsService = analyticsService;
_apiKey = configFileProvider.ApiKey;
_urlBase = configFileProvider.UrlBase;
}
[HttpGet("/initialize.js")]
public IActionResult Index()
{
return Content(GetContent(), "application/javascript");
}
private string GetContent()
{
if (RuntimeInfo.IsProduction && _generatedContent != null)
{
return _generatedContent;
}
var builder = new StringBuilder();
builder.AppendLine("window.Sonarr = {");
builder.AppendLine($" apiRoot: '{_urlBase}/api/v3',");
builder.AppendLine($" apiKey: '{_apiKey}',");
builder.AppendLine($" release: '{BuildInfo.Release}',");
builder.AppendLine($" version: '{BuildInfo.Version.ToString()}',");
builder.AppendLine($" instanceName: '{_configFileProvider.InstanceName.ToString()}',");
builder.AppendLine($" theme: '{_configFileProvider.Theme.ToString()}',");
builder.AppendLine($" branch: '{_configFileProvider.Branch.ToLower()}',");
builder.AppendLine($" analytics: {_analyticsService.IsEnabled.ToString().ToLowerInvariant()},");
builder.AppendLine($" userHash: '{HashUtil.AnonymousToken()}',");
builder.AppendLine($" urlBase: '{_urlBase}',");
builder.AppendLine($" isProduction: {RuntimeInfo.IsProduction.ToString().ToLowerInvariant()}");
builder.AppendLine("};");
_generatedContent = builder.ToString();
return _generatedContent;
}
}
}

View File

@ -0,0 +1,66 @@
using System.Text;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Analytics;
using NzbDrone.Core.Configuration;
namespace Sonarr.Http.Frontend
{
[Authorize(Policy = "UI")]
[ApiController]
[ApiExplorerSettings(IgnoreApi = true)]
public class InitializeJsonController : Controller
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IAnalyticsService _analyticsService;
private static string _apiKey;
private static string _urlBase;
private string _generatedContent;
public InitializeJsonController(IConfigFileProvider configFileProvider,
IAnalyticsService analyticsService)
{
_configFileProvider = configFileProvider;
_analyticsService = analyticsService;
_apiKey = configFileProvider.ApiKey;
_urlBase = configFileProvider.UrlBase;
}
[HttpGet("/initialize.json")]
public IActionResult Index()
{
return Content(GetContent(), "application/json");
}
private string GetContent()
{
if (RuntimeInfo.IsProduction && _generatedContent != null)
{
return _generatedContent;
}
var builder = new StringBuilder();
builder.AppendLine("{");
builder.AppendLine($" \"apiRoot\": \"{_urlBase}/api/v3\",");
builder.AppendLine($" \"apiKey\": \"{_apiKey}\",");
builder.AppendLine($" \"release\": \"{BuildInfo.Release}\",");
builder.AppendLine($" \"version\": \"{BuildInfo.Version.ToString()}\",");
builder.AppendLine($" \"instanceName\": \"{_configFileProvider.InstanceName.ToString()}\",");
builder.AppendLine($" \"theme\": \"{_configFileProvider.Theme.ToString()}\",");
builder.AppendLine($" \"branch\": \"{_configFileProvider.Branch.ToLower()}\",");
builder.AppendLine($" \"analytics\": {_analyticsService.IsEnabled.ToString().ToLowerInvariant()},");
builder.AppendLine($" \"userHash\": \"{HashUtil.AnonymousToken()}\",");
builder.AppendLine($" \"urlBase\": \"{_urlBase}\",");
builder.AppendLine($" \"isProduction\": {RuntimeInfo.IsProduction.ToString().ToLowerInvariant()}");
builder.AppendLine("}");
_generatedContent = builder.ToString();
return _generatedContent;
}
}
}

View File

@ -62,9 +62,11 @@ namespace Sonarr.Http.Frontend.Mappers
url = cacheBreakProvider.AddCacheBreakerToPath(match.Groups["path"].Value);
}
return string.Format("{0}=\"{1}{2}\"", match.Groups["attribute"].Value, UrlBase, url);
return $"{match.Groups["attribute"].Value}=\"{UrlBase}{url}\"";
});
text = text.Replace("__URL_BASE__", UrlBase);
_generatedContent = text;
return _generatedContent;

View File

@ -37,7 +37,7 @@ namespace Sonarr.Http.Frontend.Mappers
}
return resourceUrl.StartsWith("/content") ||
(resourceUrl.EndsWith(".js") && !resourceUrl.EndsWith("initialize.js")) ||
resourceUrl.EndsWith(".js") ||
resourceUrl.EndsWith(".map") ||
resourceUrl.EndsWith(".css") ||
(resourceUrl.EndsWith(".ico") && !resourceUrl.Equals("/favicon.ico")) ||

View File

@ -46,7 +46,7 @@ namespace Sonarr.Http.Middleware
return false;
}
if (path.EndsWith("/initialize.js"))
if (path.EndsWith("/initialize.json"))
{
return false;
}