diff options
author | Eugen Rochko <eugen@zeonfederated.com> | 2017-09-28 15:31:31 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-09-28 15:31:31 +0200 |
commit | 4ec1771165ab8dd40e52804fd087eacfab25290b (patch) | |
tree | e356a5477ee1790367a9b8981fdf5f6419540f88 /app/javascript/mastodon/components | |
parent | 3d9b8847d21d886886baae483304288139669795 (diff) |
Add ability to specify alternative text for media attachments (#5123)
* Fix #117 - Add ability to specify alternative text for media attachments - POST /api/v1/media accepts `description` straight away - PUT /api/v1/media/:id to update `description` (only for unattached ones) - Serialized as `name` of Document object in ActivityPub - Uploads form adjusted for better performance and description input * Add tests * Change undo button blend mode to difference
Diffstat (limited to 'app/javascript/mastodon/components')
-rw-r--r-- | app/javascript/mastodon/components/extended_video_player.js | 14 | ||||
-rw-r--r-- | app/javascript/mastodon/components/media_gallery.js | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/components/video_player.js | 204 |
3 files changed, 12 insertions, 209 deletions
diff --git a/app/javascript/mastodon/components/extended_video_player.js b/app/javascript/mastodon/components/extended_video_player.js index 5ab5e9e58..f8bd067e8 100644 --- a/app/javascript/mastodon/components/extended_video_player.js +++ b/app/javascript/mastodon/components/extended_video_player.js @@ -5,6 +5,7 @@ export default class ExtendedVideoPlayer extends React.PureComponent { static propTypes = { src: PropTypes.string.isRequired, + alt: PropTypes.string, width: PropTypes.number, height: PropTypes.number, time: PropTypes.number, @@ -31,15 +32,20 @@ export default class ExtendedVideoPlayer extends React.PureComponent { } render () { + const { src, muted, controls, alt } = this.props; + return ( <div className='extended-video-player'> <video ref={this.setRef} - src={this.props.src} + src={src} autoPlay - muted={this.props.muted} - controls={this.props.controls} - loop={!this.props.controls} + role='button' + tabIndex='0' + aria-label={alt} + muted={muted} + controls={controls} + loop={!controls} /> </div> ); diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index a81409871..38b26b1fc 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -136,7 +136,7 @@ class Item extends React.PureComponent { onClick={this.handleClick} target='_blank' > - <img src={previewUrl} srcSet={srcSet} sizes={sizes} alt='' /> + <img src={previewUrl} srcSet={srcSet} sizes={sizes} alt={attachment.get('description')} /> </a> ); } else if (attachment.get('type') === 'gifv') { @@ -146,6 +146,7 @@ class Item extends React.PureComponent { <div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}> <video className='media-gallery__item-gifv-thumbnail' + aria-label={attachment.get('description')} role='application' src={attachment.get('url')} onClick={this.handleClick} diff --git a/app/javascript/mastodon/components/video_player.js b/app/javascript/mastodon/components/video_player.js deleted file mode 100644 index 2a2d91c33..000000000 --- a/app/javascript/mastodon/components/video_player.js +++ /dev/null @@ -1,204 +0,0 @@ -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import IconButton from './icon_button'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { isIOS } from '../is_mobile'; - -const messages = defineMessages({ - toggle_sound: { id: 'video_player.toggle_sound', defaultMessage: 'Toggle sound' }, - toggle_visible: { id: 'video_player.toggle_visible', defaultMessage: 'Toggle visibility' }, - expand_video: { id: 'video_player.expand', defaultMessage: 'Expand video' }, -}); - -@injectIntl -export default class VideoPlayer extends React.PureComponent { - - static contextTypes = { - router: PropTypes.object, - }; - - static propTypes = { - media: ImmutablePropTypes.map.isRequired, - width: PropTypes.number, - height: PropTypes.number, - sensitive: PropTypes.bool, - intl: PropTypes.object.isRequired, - autoplay: PropTypes.bool, - onOpenVideo: PropTypes.func.isRequired, - }; - - static defaultProps = { - width: 239, - height: 110, - }; - - state = { - visible: !this.props.sensitive, - preview: true, - muted: true, - hasAudio: true, - videoError: false, - }; - - handleClick = () => { - this.setState({ muted: !this.state.muted }); - } - - handleVideoClick = (e) => { - e.stopPropagation(); - - const node = this.video; - - if (node.paused) { - node.play(); - } else { - node.pause(); - } - } - - handleOpen = () => { - this.setState({ preview: !this.state.preview }); - } - - handleVisibility = () => { - this.setState({ - visible: !this.state.visible, - preview: true, - }); - } - - handleExpand = () => { - this.video.pause(); - this.props.onOpenVideo(this.props.media, this.video.currentTime); - } - - setRef = (c) => { - this.video = c; - } - - handleLoadedData = () => { - if (('WebkitAppearance' in document.documentElement.style && this.video.audioTracks.length === 0) || this.video.mozHasAudio === false) { - this.setState({ hasAudio: false }); - } - } - - handleVideoError = () => { - this.setState({ videoError: true }); - } - - componentDidMount () { - if (!this.video) { - return; - } - - this.video.addEventListener('loadeddata', this.handleLoadedData); - this.video.addEventListener('error', this.handleVideoError); - } - - componentDidUpdate () { - if (!this.video) { - return; - } - - this.video.addEventListener('loadeddata', this.handleLoadedData); - this.video.addEventListener('error', this.handleVideoError); - } - - componentWillUnmount () { - if (!this.video) { - return; - } - - this.video.removeEventListener('loadeddata', this.handleLoadedData); - this.video.removeEventListener('error', this.handleVideoError); - } - - render () { - const { media, intl, width, height, sensitive, autoplay } = this.props; - - let spoilerButton = ( - <div className={`status__video-player-spoiler ${this.state.visible ? 'status__video-player-spoiler--visible' : ''}`}> - <IconButton overlay title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleVisibility} /> - </div> - ); - - let expandButton = ''; - - if (this.context.router) { - expandButton = ( - <div className='status__video-player-expand'> - <IconButton overlay title={intl.formatMessage(messages.expand_video)} icon='expand' onClick={this.handleExpand} /> - </div> - ); - } - - let muteButton = ''; - - if (this.state.hasAudio) { - muteButton = ( - <div className='status__video-player-mute'> - <IconButton overlay title={intl.formatMessage(messages.toggle_sound)} icon={this.state.muted ? 'volume-off' : 'volume-up'} onClick={this.handleClick} /> - </div> - ); - } - - if (!this.state.visible) { - if (sensitive) { - return ( - <button style={{ width: `${width}px`, height: `${height}px`, marginTop: '8px' }} className='media-spoiler' onClick={this.handleVisibility}> - {spoilerButton} - <span className='media-spoiler__warning'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span> - <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> - </button> - ); - } else { - return ( - <button style={{ width: `${width}px`, height: `${height}px`, marginTop: '8px' }} className='media-spoiler' onClick={this.handleVisibility}> - {spoilerButton} - <span className='media-spoiler__warning'><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span> - <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> - </button> - ); - } - } - - if (this.state.preview && !autoplay) { - return ( - <button className='media-spoiler-video' style={{ width: `${width}px`, height: `${height}px`, backgroundImage: `url(${media.get('preview_url')})` }} onClick={this.handleOpen}> - {spoilerButton} - <div className='media-spoiler-video-play-icon'><i className='fa fa-play' /></div> - </button> - ); - } - - if (this.state.videoError) { - return ( - <div style={{ width: `${width}px`, height: `${height}px` }} className='video-error-cover' > - <span className='media-spoiler__warning'><FormattedMessage id='video_player.video_error' defaultMessage='Video could not be played' /></span> - </div> - ); - } - - return ( - <div className='status__video-player' style={{ width: `${width}px`, height: `${height}px` }}> - {spoilerButton} - {muteButton} - {expandButton} - - <video - className='status__video-player-video' - role='button' - tabIndex='0' - ref={this.setRef} - src={media.get('url')} - autoPlay={!isIOS()} - loop - muted={this.state.muted} - onClick={this.handleVideoClick} - /> - </div> - ); - } - -} |