Removing vanilla encodeURIComponent, doing it manually for better control

This commit is contained in:
James Barnsley
2021-01-23 16:24:07 +13:00
parent 1bfc48e193
commit 4f561de7c5
22 changed files with 589 additions and 417 deletions

View File

@ -28,6 +28,7 @@ import Button from '../components/Button';
import { makeLoadingSelector, makeItemSelector } from '../util/selectors';
import { applyFilter, sortItems } from '../util/arrays';
import { trackEvent } from '../components/Trackable';
import { encodeUri } from '../util/format';
class Album extends React.Component {
constructor(props) {
@ -218,7 +219,7 @@ class Album extends React.Component {
return (
<ErrorMessage type="not-found" title="Not found">
<p>
{i18n('errors.uri_not_found', { uri: encodeURIComponent(uri) })}
{i18n('errors.uri_not_found', { uri })}
</p>
</ErrorMessage>
);

View File

@ -25,7 +25,7 @@ import {
sourceIcon,
titleCase,
} from '../util/helpers';
import { collate } from '../util/format';
import { collate, encodeUri } from '../util/format';
import { sortItems, applyFilter, arrayOf } from '../util/arrays';
import { i18n, I18n } from '../locale';
import Button from '../components/Button';
@ -265,7 +265,7 @@ class Artist extends React.Component {
/>
</div>
<Button
to={`/artist/${encodeURIComponent(uri)}/related-artists`}
to={`/artist/${encodeUri(uri)}/related-artists`}
scrollTo="#sub-views-menu"
>
<I18n path="artist.overview.related_artists.more" />
@ -492,7 +492,7 @@ class Artist extends React.Component {
return (
<ErrorMessage type="not-found" title="Not found">
<p>
<I18n path="errors.uri_not_found" uri={encodeURIComponent(uri)} />
<I18n path="errors.uri_not_found" uri={uri} />
</p>
</ErrorMessage>
);
@ -539,7 +539,7 @@ class Artist extends React.Component {
history={history}
activeClassName="sub-views__option--active"
className="sub-views__option"
to={`/artist/${encodeURIComponent(uri)}`}
to={`/artist/${encodeUri(uri)}`}
scrollTo="#sub-views-menu"
>
<h4><I18n path="artist.overview.title" /></h4>
@ -550,7 +550,7 @@ class Artist extends React.Component {
history={history}
activeClassName="sub-views__option--active"
className="sub-views__option"
to={`/artist/${encodeURIComponent(uri)}/tracks`}
to={`/artist/${encodeUri(uri)}/tracks`}
scrollTo="#sub-views-menu"
>
<h4><I18n path="artist.tracks.title" /></h4>
@ -562,7 +562,7 @@ class Artist extends React.Component {
history={history}
activeClassName="sub-views__option--active"
className="sub-views__option"
to={`/artist/${encodeURIComponent(uri)}/related-artists`}
to={`/artist/${encodeUri(uri)}/related-artists`}
scrollTo="#sub-views-menu"
>
<h4><I18n path="artist.related_artists.title" /></h4>
@ -573,7 +573,7 @@ class Artist extends React.Component {
history={history}
activeClassName="sub-views__option--active"
className="sub-views__option"
to={`/artist/${encodeURIComponent(uri)}/about`}
to={`/artist/${encodeUri(uri)}/about`}
scrollTo="#sub-views-menu"
>
<h4><I18n path="artist.about.title" /></h4>

View File

@ -30,6 +30,7 @@ import { trackEvent } from '../components/Trackable';
import { i18n, I18n } from '../locale';
import { makeItemSelector, makeLoadingSelector } from '../util/selectors';
import { sortItems, applyFilter } from '../util/arrays';
import { decodeUri, encodeUri } from '../util/format';
class Playlist extends React.Component {
constructor(props) {
@ -45,7 +46,7 @@ class Playlist extends React.Component {
// We accept the old format, and redirect to the new one
if (uri.includes('spotify:user:')) {
uri = uri.replace(/spotify:user:([^:]*?):/i, 'spotify:');
props.history.push(`/playlist/${encodeURIComponent(uri)}`);
props.history.push(`/playlist/${encodeUri(uri)}`);
}
}
@ -76,7 +77,7 @@ class Playlist extends React.Component {
} = this.props;
if (prevPlaylist && playlist && prevPlaylist.moved_to !== playlist.moved_to) {
push(`/playlist/${encodeURIComponent(playlist.moved_to)}`);
push(`/playlist/${encodeUri(playlist.moved_to)}`);
}
if (uri !== prevUri) {
@ -258,7 +259,7 @@ class Playlist extends React.Component {
<I18n path="actions.play" />
</Button>
<Button
to={`/playlist/${encodeURIComponent(uri)}/edit`}
to={`/playlist/${encodeUri(uri)}/edit`}
tracking={{ category: 'Playlist', action: 'Edit' }}
>
<I18n path="actions.edit" />
@ -280,7 +281,7 @@ class Playlist extends React.Component {
<I18n path="actions.play" />
</Button>
<Button
to={`/playlist/${encodeURIComponent(uri)}/edit`}
to={`/playlist/${encodeUri(uri)}/edit`}
tracking={{ category: 'Playlist', action: 'Edit' }}
>
<I18n path="actions.edit" />
@ -495,7 +496,7 @@ const mapStateToProps = (state, ownProps) => {
} = {},
} = state;
const uri = decodeMopidyUri(ownProps.match.params.uri);
const uri = decodeUri(ownProps.match.params.uri);
const playlistId = getFromUri('playlistid', uri);
const itemSelector = makeItemSelector(uri);
const loadingSelector = makeLoadingSelector([`(.*)${playlistId}(?!.*(following))(.*)`]);

View File

@ -18,15 +18,121 @@ import * as mopidyActions from '../services/mopidy/actions';
import * as spotifyActions from '../services/spotify/actions';
import * as lastfmActions from '../services/lastfm/actions';
import * as geniusActions from '../services/genius/actions';
import {
isLoading,
sourceIcon,
} from '../util/helpers';
import { sourceIcon } from '../util/helpers';
import { i18n, I18n } from '../locale';
import Button from '../components/Button';
import { makeLoadingSelector, makeItemSelector } from '../util/selectors';
import { decodeUri } from '../util/format';
const LyricsSelector = ({
track: {
uri,
lyrics_results,
lyrics_path,
} = {},
getTrackLyrics,
}) => {
if (lyrics_results === undefined || lyrics_results === null) return null;
if (lyrics_results.length <= 0) {
return (
<div className="field lyrics-selector">
<div className="input">
<input type="text" disabled="disabled" value="No results" />
<div className="description">
<I18n path="services.genius.switch_lyrics_result" />
</div>
</div>
</div>
);
}
return (
<div className="field lyrics-selector">
<div className="input">
<SelectField
onChange={(value) => getTrackLyrics(uri, value)}
options={
lyrics_results.map((result) => ({
value: result.path,
label: result.title,
defaultValue: (result.path === lyrics_path),
}))
}
/>
<div className="description">
<I18n path="services.genius.switch_lyrics_result" />
</div>
</div>
</div>
);
}
const LyricsContent = ({
authorized,
loading,
track: {
lyrics,
lyrics_path,
} = {},
}) => {
if (loading) return <Loader mini />;
if (!lyrics && !authorized) {
return (
<p className="no-results">
<I18n path="track.want_lyrics" />
<Link to="/settings/services/genius" scrollTo="#services-menu">
<I18n path="settings.title" />
</Link>
.
</p>
);
};
if (!lyrics) return null;
return (
<div className="lyrics">
<div className="content" dangerouslySetInnerHTML={{ __html: lyrics }} />
<div className="origin mid_grey-text">
<I18n path="track.lyrics_origin" />
<a
href={`https://genius.com${lyrics_path}`}
target="_blank"
rel="noreferrer noopener"
>
{`https://genius.com${lyrics_path}`}
</a>
</div>
</div>
);
}
const Lyrics = ({
loading,
authorized,
track,
getTrackLyrics,
}) => {
return (
<>
<h4>
<I18n path="track.lyrics" />
{loading && <Loader loading mini />}
</h4>
<LyricsSelector
getTrackLyrics={getTrackLyrics}
authorized={authorized}
track={track}
/>
<LyricsContent
track={track}
authorized={authorized}
/>
</>
);
}
class Track extends React.Component {
componentDidMount() {
const {
@ -85,11 +191,13 @@ class Track extends React.Component {
if (track) {
let artists = '';
for (let i = 0; i < track.artists.length; i++) {
if (artists != '') {
artists += ', ';
if (artists) {
for (let i = 0; i < track.artists.length; i++) {
if (artists != '') {
artists += ', ';
}
artists += track.artists[i].name;
}
artists += track.artists[i].name;
}
setWindowTitle(i18n('track.title_window', { name: track.name, artists }));
} else {
@ -121,80 +229,6 @@ class Track extends React.Component {
playURIs([uri], uri);
}
renderLyricsSelector = () => {
const {
track,
geniusActions: {
getTrackLyrics,
},
genius_authorized,
} = this.props;
if (!genius_authorized || track.lyrics_results === undefined || track.lyrics_results === null) {
return null;
} if (track.lyrics_results.length <= 0) {
return (
<div className="field lyrics-selector">
<div className="input">
<input type="text" disabled="disabled" value="No results" />
<div className="description">
<I18n path="services.genius.switch_lyrics_result" />
</div>
</div>
</div>
);
}
return (
<div className="field lyrics-selector">
<div className="input">
<SelectField
onChange={(value) => getTrackLyrics(track.uri, value)}
options={
track.lyrics_results.map((result) => ({
value: result.path,
label: result.title,
defaultValue: (result.path === track.lyrics_path),
}))
}
/>
<div className="description">
<I18n path="services.genius.switch_lyrics_result" />
</div>
</div>
</div>
);
}
renderLyrics = () => {
const {
track: {
lyrics,
lyrics_path,
} = {},
genius_authorized,
loadingLyrics,
} = this.props;
if (!lyrics || !genius_authorized || loadingLyrics) return null;
return (
<div className="lyrics">
<div className="content" dangerouslySetInnerHTML={{ __html: lyrics }} />
<div className="origin mid_grey-text">
<I18n path="track.lyrics_origin" />
<a
href={`https://genius.com${lyrics_path}`}
target="_blank"
rel="noreferrer noopener"
>
{`https://genius.com${lyrics_path}`}
</a>
</div>
</div>
);
}
render = () => {
const {
uri,
@ -203,6 +237,9 @@ class Track extends React.Component {
slim_mode,
uiActions,
genius_authorized,
geniusActions: {
getTrackLyrics
},
loadingLyrics,
} = this.props;
@ -288,22 +325,12 @@ class Track extends React.Component {
<ContextMenuTrigger onTrigger={this.handleContextMenu} />
</div>
<h4>
<I18n path="track.lyrics" />
{loadingLyrics && <Loader loading mini />}
</h4>
{!genius_authorized && (
<p className="no-results">
<I18n path="track.want_lyrics" />
<Link to="/settings/services/genius" scrollTo="#services-menu">
<I18n path="settings.title" />
</Link>
.
</p>
)}
{this.renderLyricsSelector()}
{this.renderLyrics()}
<Lyrics
loading={loadingLyrics}
authorized={genius_authorized}
getTrackLyrics={getTrackLyrics}
track={track}
/>
</div>
);

View File

@ -8,7 +8,7 @@ import Icon from '../../components/Icon';
import ErrorBoundary from '../../components/ErrorBoundary';
import * as uiActions from '../../services/ui/actions';
import * as mopidyActions from '../../services/mopidy/actions';
import { formatImages } from '../../util/format';
import { formatImages, encodeUri } from '../../util/format';
import { I18n, i18n } from '../../locale';
class LibraryBrowse extends React.Component {
@ -91,7 +91,7 @@ class LibraryBrowse extends React.Component {
grid_items.push({
name: subdirectory.name,
link: `/library/browse/${encodeURIComponent(subdirectory.uri)}`,
link: `/library/browse/${encodeUri(subdirectory.uri)}`,
icons: formatImages(subdirectory.icons),
});
}

View File

@ -1,4 +1,3 @@
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
@ -8,6 +7,7 @@ import List from '../../components/List';
import TrackList from '../../components/TrackList';
import GridItem from '../../components/GridItem';
import DropdownField from '../../components/Fields/DropdownField';
import FilterField from '../../components/Fields/FilterField';
import Icon from '../../components/Icon';
import URILink from '../../components/URILink';
import ErrorBoundary from '../../components/ErrorBoundary';
@ -18,9 +18,10 @@ import * as spotifyActions from '../../services/spotify/actions';
import {
isLoading,
} from '../../util/helpers';
import { arrayOf, sortItems } from '../../util/arrays';
import { arrayOf, sortItems, applyFilter } from '../../util/arrays';
import { i18n, I18n } from '../../locale';
import Button from '../../components/Button';
import { encodeUri } from '../../util/format';
class LibraryBrowseDirectory extends React.Component {
constructor(props) {
@ -153,7 +154,7 @@ class LibraryBrowseDirectory extends React.Component {
<GridItem
key={subdirectory.uri}
type="directory"
link={`/library/browse/${encodeURIComponent(subdirectory.uri)}`}
link={`/library/browse/${encodeUri(subdirectory.uri)}`}
item={subdirectory}
nocontext
/>
@ -172,6 +173,8 @@ class LibraryBrowseDirectory extends React.Component {
view,
} = this.props;
const {
filter,
per_page,
limit,
} = this.state;
@ -198,6 +201,9 @@ class LibraryBrowseDirectory extends React.Component {
let subdirectories = (directory.subdirectories && directory.subdirectories.length > 0 ? directory.subdirectories : null);
subdirectories = sortItems(subdirectories, 'name');
if (filter && filter !== '') {
subdirectories = applyFilter('name', filter, subdirectories);
}
const total_items = (directory.tracks ? directory.tracks.length : 0) + (subdirectories ? subdirectories.length : 0);
subdirectories = subdirectories.slice(0, limit);
@ -207,6 +213,9 @@ class LibraryBrowseDirectory extends React.Component {
if (limit_remaining > 0 && directory.tracks && directory.tracks.length) {
all_tracks = directory.tracks;
all_tracks = sortItems(all_tracks, 'name');
if (filter && filter !== '') {
tracks = applyFilter('name', filter, tracks);
}
tracks = all_tracks.slice(0, limit_remaining);
}
@ -223,6 +232,11 @@ class LibraryBrowseDirectory extends React.Component {
const options = (
<>
<FilterField
initialValue={filter}
handleChange={(value) => this.setState({ filter: value, limit: per_page })}
onSubmit={() => uiActions.hideContextMenu()}
/>
<DropdownField
icon="visibility"
name="View"