diff options
Diffstat (limited to 'app/javascript')
-rw-r--r-- | app/javascript/mastodon/components/media_gallery.js | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/features/status/components/card.js | 175 | ||||
-rw-r--r-- | app/javascript/mastodon/features/video/index.js | 14 | ||||
-rw-r--r-- | app/javascript/mastodon/locales/pl.json | 13 | ||||
-rw-r--r-- | app/javascript/styles/mastodon/basics.scss | 4 | ||||
-rw-r--r-- | app/javascript/styles/mastodon/components.scss | 63 | ||||
-rw-r--r-- | app/javascript/styles/mastodon/stream_entries.scss | 8 |
7 files changed, 191 insertions, 88 deletions
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index a276b17d5..00943e205 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -249,7 +249,7 @@ export default class MediaGallery extends React.PureComponent { } children = ( - <button className='media-spoiler' onClick={this.handleOpen} style={style} ref={this.handleRef}> + <button type='button' className='media-spoiler' onClick={this.handleOpen} style={style} ref={this.handleRef}> <span className='media-spoiler__warning'>{warning}</span> <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> </button> diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js index 2f6a7831e..b52f3c4fa 100644 --- a/app/javascript/mastodon/features/status/components/card.js +++ b/app/javascript/mastodon/features/status/components/card.js @@ -20,6 +20,39 @@ const getHostname = url => { return parser.hostname; }; +const trim = (text, len) => { + const cut = text.indexOf(' ', len); + + if (cut === -1) { + return text; + } + + return text.substring(0, cut) + (text.length > len ? '…' : ''); +}; + +const domParser = new DOMParser(); + +const addAutoPlay = html => { + const document = domParser.parseFromString(html, 'text/html').documentElement; + const iframe = document.querySelector('iframe'); + + if (iframe) { + if (iframe.src.indexOf('?') !== -1) { + iframe.src += '&'; + } else { + iframe.src += '?'; + } + + iframe.src += 'autoplay=1&auto_play=1'; + + // DOM parser creates html/body elements around original HTML fragment, + // so we need to get innerHTML out of the body and not the entire document + return document.querySelector('body').innerHTML; + } + + return html; +}; + export default class Card extends React.PureComponent { static propTypes = { @@ -33,9 +66,16 @@ export default class Card extends React.PureComponent { }; state = { - width: 0, + width: 280, + embedded: false, }; + componentWillReceiveProps (nextProps) { + if (this.props.card !== nextProps.card) { + this.setState({ embedded: false }); + } + } + handlePhotoClick = () => { const { card, onOpenMedia } = this.props; @@ -57,56 +97,14 @@ export default class Card extends React.PureComponent { ); }; - renderLink () { - const { card, maxDescription } = this.props; - const { width } = this.state; - const horizontal = card.get('width') > card.get('height') && (card.get('width') + 100 >= width); - - let image = ''; - let provider = card.get('provider_name'); - - if (card.get('image')) { - image = ( - <div className='status-card__image'> - <img src={card.get('image')} alt={card.get('title')} className='status-card__image-image' width={card.get('width')} height={card.get('height')} /> - </div> - ); - } - - if (provider.length < 1) { - provider = decodeIDNA(getHostname(card.get('url'))); - } - - const className = classnames('status-card', { horizontal }); - - return ( - <a href={card.get('url')} className={className} target='_blank' rel='noopener' ref={this.setRef}> - {image} - - <div className='status-card__content'> - <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong> - {!horizontal && <p className='status-card__description'>{(card.get('description') || '').substring(0, maxDescription)}</p>} - <span className='status-card__host'>{provider}</span> - </div> - </a> - ); - } - - renderPhoto () { + handleEmbedClick = () => { const { card } = this.props; - return ( - <img - className='status-card-photo' - onClick={this.handlePhotoClick} - role='button' - tabIndex='0' - src={card.get('embed_url')} - alt={card.get('title')} - width={card.get('width')} - height={card.get('height')} - /> - ); + if (card.get('type') === 'photo') { + this.handlePhotoClick(); + } else { + this.setState({ embedded: true }); + } } setRef = c => { @@ -117,7 +115,7 @@ export default class Card extends React.PureComponent { renderVideo () { const { card } = this.props; - const content = { __html: card.get('html') }; + const content = { __html: addAutoPlay(card.get('html')) }; const { width } = this.state; const ratio = card.get('width') / card.get('height'); const height = card.get('width') > card.get('height') ? (width / ratio) : (width * ratio); @@ -125,7 +123,7 @@ export default class Card extends React.PureComponent { return ( <div ref={this.setRef} - className='status-card-video' + className='status-card__image status-card-video' dangerouslySetInnerHTML={content} style={{ height }} /> @@ -133,23 +131,76 @@ export default class Card extends React.PureComponent { } render () { - const { card } = this.props; + const { card, maxDescription } = this.props; + const { width, embedded } = this.state; if (card === null) { return null; } - switch(card.get('type')) { - case 'link': - return this.renderLink(); - case 'photo': - return this.renderPhoto(); - case 'video': - return this.renderVideo(); - case 'rich': - default: - return null; + const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name'); + const horizontal = card.get('width') > card.get('height') && (card.get('width') + 100 >= width) || card.get('type') !== 'link'; + const className = classnames('status-card', { horizontal }); + const interactive = card.get('type') !== 'link'; + const title = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>; + const ratio = card.get('width') / card.get('height'); + const height = card.get('width') > card.get('height') ? (width / ratio) : (width * ratio); + + const description = ( + <div className='status-card__content'> + {title} + {!horizontal && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>} + <span className='status-card__host'>{provider}</span> + </div> + ); + + let embed = ''; + let thumbnail = <div style={{ backgroundImage: `url(${card.get('image')})`, width: horizontal ? width : null, height: horizontal ? height : null }} className='status-card__image-image' />; + + if (interactive) { + if (embedded) { + embed = this.renderVideo(); + } else { + let iconVariant = 'play'; + + if (card.get('type') === 'photo') { + iconVariant = 'search-plus'; + } + + embed = ( + <div className='status-card__image'> + {thumbnail} + + <div className='status-card__actions'> + <div> + <button onClick={this.handleEmbedClick}><i className={`fa fa-${iconVariant}`} /></button> + <a href={card.get('url')} target='_blank' rel='noopener'><i className='fa fa-external-link' /></a> + </div> + </div> + </div> + ); + } + + return ( + <div className={className} ref={this.setRef}> + {embed} + {description} + </div> + ); + } else if (card.get('image')) { + embed = ( + <div className='status-card__image'> + {thumbnail} + </div> + ); } + + return ( + <a href={card.get('url')} className={className} target='_blank' rel='noopener' ref={this.setRef}> + {embed} + {description} + </a> + ); } } diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js index 7752fc057..6335d84b6 100644 --- a/app/javascript/mastodon/features/video/index.js +++ b/app/javascript/mastodon/features/video/index.js @@ -271,7 +271,7 @@ export default class Video extends React.PureComponent { onProgress={this.handleProgress} /> - <button className={classNames('video-player__spoiler', { active: !revealed })} onClick={this.toggleReveal}> + <button type='button' className={classNames('video-player__spoiler', { active: !revealed })} onClick={this.toggleReveal}> <span className='video-player__spoiler__title'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span> <span className='video-player__spoiler__subtitle'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> </button> @@ -290,10 +290,10 @@ export default class Video extends React.PureComponent { <div className='video-player__buttons-bar'> <div className='video-player__buttons left'> - <button aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button> - <button aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button> + <button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button> + <button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button> - {!onCloseVideo && <button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>} + {!onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>} {(detailed || fullscreen) && <span> @@ -305,9 +305,9 @@ export default class Video extends React.PureComponent { </div> <div className='video-player__buttons right'> - {(!fullscreen && onOpenVideo) && <button aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>} - {onCloseVideo && <button aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-compress' /></button>} - <button aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button> + {(!fullscreen && onOpenVideo) && <button type='button' aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>} + {onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-compress' /></button>} + <button type='button' aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button> </div> </div> </div> diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index b45ab5cee..bcc28b65a 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -8,7 +8,7 @@ "account.follows": "Śledzeni", "account.follows_you": "Śledzi Cię", "account.hide_reblogs": "Ukryj podbicia od @{name}", - "account.media": "Media", + "account.media": "Zawartość multimedialna", "account.mention": "Wspomnij o @{name}", "account.moved_to": "{name} przeniósł się do:", "account.mute": "Wycisz @{name}", @@ -134,7 +134,7 @@ "lightbox.next": "Następne", "lightbox.previous": "Poprzednie", "lists.account.add": "Dodaj do listy", - "lists.account.remove": "Remove from list", + "lists.account.remove": "Usuń z listy", "lists.delete": "Usuń listę", "lists.edit": "Edytuj listę", "lists.new.create": "Utwórz listę", @@ -144,7 +144,7 @@ "loading_indicator.label": "Ładowanie…", "media_gallery.toggle_visible": "Przełącz widoczność", "missing_indicator.label": "Nie znaleziono", - "missing_indicator.sublabel": "This resource could not be found", + "missing_indicator.sublabel": "Nie można odnaleźć tego zasobu", "mute_modal.hide_notifications": "Chcesz ukryć powiadomienia od tego użytkownika?", "navigation_bar.blocks": "Zablokowani użytkownicy", "navigation_bar.community_timeline": "Lokalna oś czasu", @@ -206,8 +206,8 @@ "privacy.public.short": "Publiczny", "privacy.unlisted.long": "Niewidoczny na publicznych osiach czasu", "privacy.unlisted.short": "Niewidoczny", - "regeneration_indicator.label": "Loading…", - "regeneration_indicator.sublabel": "Your home feed is being prepared!", + "regeneration_indicator.label": "Ładowanie…", + "regeneration_indicator.sublabel": "Twoja oś czasu jest przygotowywana!", "relative_time.days": "{number} dni", "relative_time.hours": "{number} godz.", "relative_time.just_now": "teraz", @@ -223,6 +223,9 @@ "search_popout.tips.status": "wpis", "search_popout.tips.text": "Proste wyszukiwanie pasujących pseudonimów, nazw użytkowników i hashtagów", "search_popout.tips.user": "użytkownik", + "search_results.accounts": "Ludzie", + "search_results.hashtags": "Hashtagi", + "search_results.statuses": "Wpisy", "search_results.total": "{count, number} {count, plural, one {wynik} few {wyniki} many {wyników} more {wyników}}", "standalone.public_title": "Spojrzenie w głąb…", "status.block": "Zablokuj @{name}", diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss index b5d77ff63..0e3b9ad6b 100644 --- a/app/javascript/styles/mastodon/basics.scss +++ b/app/javascript/styles/mastodon/basics.scss @@ -47,6 +47,10 @@ body { padding-bottom: 0; } + &.player { + text-align: center; + } + &.embed { background: transparent; margin: 0; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index b6244e3f9..d57481f97 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2208,7 +2208,6 @@ .status-card { display: flex; - cursor: pointer; font-size: 14px; border: 1px solid lighten($ui-base-color, 8%); border-radius: 4px; @@ -2217,20 +2216,58 @@ text-decoration: none; overflow: hidden; - &:hover { - background: lighten($ui-base-color, 8%); + &__actions { + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + display: flex; + justify-content: center; + align-items: center; + + & > div { + background: rgba($base-shadow-color, 0.6); + border-radius: 4px; + padding: 12px 9px; + flex: 0 0 auto; + display: flex; + justify-content: center; + align-items: center; + } + + button, + a { + display: inline; + color: $primary-text-color; + background: transparent; + border: 0; + padding: 0 5px; + text-decoration: none; + opacity: 0.6; + font-size: 18px; + line-height: 18px; + + &:hover, + &:active, + &:focus { + opacity: 1; + } + } + + a { + font-size: 19px; + position: relative; + bottom: -1px; + } } } -.status-card-video, -.status-card-rich, -.status-card-photo { - margin-top: 14px; - overflow: hidden; +a.status-card { + cursor: pointer; - iframe { - width: 100%; - height: auto; + &:hover { + background: lighten($ui-base-color, 8%); } } @@ -2258,6 +2295,7 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + text-decoration: none; } .status-card__content { @@ -2279,6 +2317,7 @@ .status-card__image { flex: 0 0 100px; background: lighten($ui-base-color, 8%); + position: relative; } .status-card.horizontal { @@ -2304,6 +2343,8 @@ width: 100%; height: 100%; object-fit: cover; + background-size: cover; + background-position: center center; } .load-more { diff --git a/app/javascript/styles/mastodon/stream_entries.scss b/app/javascript/styles/mastodon/stream_entries.scss index a35bbee79..e11ca29d9 100644 --- a/app/javascript/styles/mastodon/stream_entries.scss +++ b/app/javascript/styles/mastodon/stream_entries.scss @@ -65,6 +65,10 @@ } } + .media-gallery__gifv__label { + bottom: 9px; + } + .status.light { padding: 14px 14px 14px (48px + 14px * 2); position: relative; @@ -258,12 +262,12 @@ .media-spoiler { background: $ui-primary-color; color: $white; - transition: all 100ms linear; + transition: all 40ms linear; &:hover, &:active, &:focus { - background: darken($ui-primary-color, 5%); + background: darken($ui-primary-color, 2%); color: unset; } } |