Add icon on media that lacks alt description (#1261)
A warning icon now appears on media that lacks an alt description. Also, for admins who want to add custom CSS rules to media that is missing descriptions, there is now a `.media-missing-description` rule that can be added to the custom CSS settings so you can do stuff like this if you want: Fixes #1165
This commit is contained in:
parent
5de8a60c1a
commit
f295633a6e
|
@ -12,6 +12,7 @@ import Blurhash from 'mastodon/components/blurhash';
|
|||
|
||||
const messages = defineMessages({
|
||||
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: '{number, plural, one {Hide image} other {Hide images}}' },
|
||||
no_descriptive_text: { id: 'media.no_descriptive_text', defaultMessage: 'No descriptive text was provided for this media.' },
|
||||
});
|
||||
|
||||
class Item extends React.PureComponent {
|
||||
|
@ -25,6 +26,7 @@ class Item extends React.PureComponent {
|
|||
displayWidth: PropTypes.number,
|
||||
visible: PropTypes.bool.isRequired,
|
||||
autoplay: PropTypes.bool,
|
||||
noDescriptionTitle: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -135,7 +137,7 @@ class Item extends React.PureComponent {
|
|||
if (attachment.get('type') === 'unknown') {
|
||||
return (
|
||||
<div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
|
||||
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={attachment.get('description')} target='_blank' rel='noopener noreferrer'>
|
||||
<a className={`media-gallery__item-thumbnail ${!attachment.get('description') && 'media-missing-description'}`} href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={attachment.get('description')} target='_blank' rel='noopener noreferrer'>
|
||||
<Blurhash
|
||||
hash={attachment.get('blurhash')}
|
||||
className='media-gallery__preview'
|
||||
|
@ -163,12 +165,13 @@ class Item extends React.PureComponent {
|
|||
|
||||
thumbnail = (
|
||||
<a
|
||||
className='media-gallery__item-thumbnail'
|
||||
className={`media-gallery__item-thumbnail ${!attachment.get('description') && 'media-missing-description'}`}
|
||||
href={attachment.get('remote_url') || originalUrl}
|
||||
onClick={this.handleClick}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
{ !attachment.get('description') && <IconButton className='media-gallery__item-no-alt' title={this.props.noDescriptionTitle} icon='exclamation-triangle' overlay /> }
|
||||
<img
|
||||
src={previewUrl}
|
||||
srcSet={srcSet}
|
||||
|
@ -186,7 +189,7 @@ class Item extends React.PureComponent {
|
|||
thumbnail = (
|
||||
<div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
|
||||
<video
|
||||
className='media-gallery__item-gifv-thumbnail'
|
||||
className={`media-gallery__item-gifv-thumbnail ${!attachment.get('description') && 'media-missing-description'}`}
|
||||
aria-label={attachment.get('description')}
|
||||
title={attachment.get('description')}
|
||||
role='application'
|
||||
|
@ -199,7 +202,10 @@ class Item extends React.PureComponent {
|
|||
muted
|
||||
/>
|
||||
|
||||
<div className='media-gallery__gifv__label__container'>
|
||||
<span className='media-gallery__gifv__label'>GIF</span>
|
||||
{ !attachment.get('description') && <span className='media-gallery__gifv__label__no-description'><IconButton title={this.props.noDescriptionTitle} icon='exclamation-triangle' overlay /></span> }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -331,11 +337,12 @@ class MediaGallery extends React.PureComponent {
|
|||
|
||||
const size = media.take(4).size;
|
||||
const uncached = media.every(attachment => attachment.get('type') === 'unknown');
|
||||
const noDescriptionTitle = intl.formatMessage(messages.no_descriptive_text);
|
||||
|
||||
if (standalone && this.isFullSizeEligible()) {
|
||||
children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} displayWidth={width} visible={visible} />;
|
||||
children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} displayWidth={width} visible={visible} noDescriptionTitle={noDescriptionTitle} />;
|
||||
} else {
|
||||
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} size={size} displayWidth={width} visible={visible || uncached} />);
|
||||
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} size={size} displayWidth={width} visible={visible || uncached} noDescriptionTitle={noDescriptionTitle} />);
|
||||
}
|
||||
|
||||
if (uncached) {
|
||||
|
|
|
@ -19,6 +19,7 @@ const messages = defineMessages({
|
|||
unmute: { id: 'video.unmute', defaultMessage: 'Unmute sound' },
|
||||
download: { id: 'video.download', defaultMessage: 'Download file' },
|
||||
hide: { id: 'audio.hide', defaultMessage: 'Hide audio' },
|
||||
no_descriptive_text: { id: 'media.no_descriptive_text', defaultMessage: 'No descriptive text was provided for this media.' },
|
||||
});
|
||||
|
||||
const TICK_SIZE = 10;
|
||||
|
@ -470,7 +471,7 @@ class Audio extends React.PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('audio-player', { editable, inactive: !revealed })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), width: '100%', height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex='0' onKeyDown={this.handleKeyDown}>
|
||||
<div className={classNames('audio-player', { editable, inactive: !revealed, 'media-missing-description': !alt })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), width: '100%', height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex='0' onKeyDown={this.handleKeyDown}>
|
||||
|
||||
<Blurhash
|
||||
hash={blurhash}
|
||||
|
@ -555,6 +556,7 @@ class Audio extends React.PureComponent {
|
|||
</div>
|
||||
|
||||
<div className='video-player__buttons right'>
|
||||
{!alt && <button type='button' title={intl.formatMessage(messages.no_descriptive_text)} aria-label={intl.formatMessage(messages.no_descriptive_text)} className='player-button no-action' ><Icon id='exclamation-triangle' fixedWidth /></button>}
|
||||
{!editable && <button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} className='player-button' onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
|
||||
<a title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)} className='video-player__download__icon player-button' href={this.props.src} download>
|
||||
<Icon id={'download'} fixedWidth />
|
||||
|
|
|
@ -19,6 +19,7 @@ const messages = defineMessages({
|
|||
close: { id: 'video.close', defaultMessage: 'Close video' },
|
||||
fullscreen: { id: 'video.fullscreen', defaultMessage: 'Full screen' },
|
||||
exit_fullscreen: { id: 'video.exit_fullscreen', defaultMessage: 'Exit full screen' },
|
||||
no_descriptive_text: { id: 'media.no_descriptive_text', defaultMessage: 'No descriptive text was provided for this media.' },
|
||||
});
|
||||
|
||||
export const formatTime = secondsNum => {
|
||||
|
@ -558,7 +559,7 @@ class Video extends React.PureComponent {
|
|||
return (
|
||||
<div
|
||||
role='menuitem'
|
||||
className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen, editable })}
|
||||
className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen, editable, 'media-missing-description': !alt })}
|
||||
style={playerStyle}
|
||||
ref={this.setPlayerRef}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
|
@ -640,6 +641,7 @@ class Video extends React.PureComponent {
|
|||
</div>
|
||||
|
||||
<div className='video-player__buttons right'>
|
||||
{!alt && <button type='button' title={intl.formatMessage(messages.no_descriptive_text)} aria-label={intl.formatMessage(messages.no_descriptive_text)} className='player-button no-action' ><Icon id='exclamation-triangle' fixedWidth /></button>}
|
||||
{(!onCloseVideo && !editable && !fullscreen && !this.props.alwaysVisible) && <button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} className='player-button' onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
|
||||
{(!fullscreen && onOpenVideo) && <button type='button' title={intl.formatMessage(messages.expand)} aria-label={intl.formatMessage(messages.expand)} className='player-button' onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
|
||||
{onCloseVideo && <button type='button' title={intl.formatMessage(messages.close)} aria-label={intl.formatMessage(messages.close)} className='player-button' onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
|
||||
|
|
|
@ -363,6 +363,7 @@
|
|||
"load_pending": "{count, plural, one {# new item} other {# new items}}",
|
||||
"loading_indicator.label": "Loading...",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Hide image} other {Hide images}}",
|
||||
"media.no_descriptive_text": "No descriptive text was provided for this media.",
|
||||
"missing_indicator.label": "Not found",
|
||||
"missing_indicator.sublabel": "This resource could not be found",
|
||||
"moved_to_account_banner.text": "Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.",
|
||||
|
|
|
@ -5885,22 +5885,40 @@ a.status-card.compact:hover {
|
|||
z-index: 9999;
|
||||
}
|
||||
|
||||
.media-gallery__gifv__label {
|
||||
.media-gallery__gifv__label__container {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
left: 4px;
|
||||
z-index: 1;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.media-gallery__gifv__label {
|
||||
color: $primary-text-color;
|
||||
background: rgba($base-overlay-background, 0.5);
|
||||
bottom: 6px;
|
||||
left: 6px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 2px;
|
||||
padding: 3px 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
opacity: 0.9;
|
||||
transition: opacity 0.1s ease;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.media-gallery__gifv__label__no-description {
|
||||
color: $primary-text-color;
|
||||
border-radius: 2px;
|
||||
padding: 2px 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
z-index: 1;
|
||||
opacity: 0.9;
|
||||
transition: opacity 0.1s ease;
|
||||
|
||||
button {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.media-gallery__gifv {
|
||||
|
@ -6002,6 +6020,12 @@ a.status-card.compact:hover {
|
|||
}
|
||||
}
|
||||
|
||||
.media-gallery__item-no-alt {
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
.media-gallery__item-thumbnail {
|
||||
cursor: zoom-in;
|
||||
display: block;
|
||||
|
@ -6289,6 +6313,10 @@ a.status-card.compact:hover {
|
|||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.no-action {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
&__time {
|
||||
|
|
Loading…
Reference in New Issue