Merge branch 'develop' into 'captcha_reload_on_click'

# Conflicts:
#   app/soapbox/locales/defaultMessages.json
merge-requests/97/merge
Curtis 4 years ago
commit c28cc228a2

@ -1,4 +1,3 @@
NODE_ENV=development
# BACKEND_URL="https://example.com"
# PATRON_URL="https://patron.example.com"
# PROXY_HTTPS_INSECURE=false

@ -124,7 +124,7 @@ For https, be sure to also set `PROXY_HTTPS_INSECURE=true`.
Allows using an HTTPS backend if set to `true`.
This is needed if `BACKEND_URL` or `PATRON_URL` are set to an `https://` value.
This is needed if `BACKEND_URL` is set to an `https://` value.
[More info](https://stackoverflow.com/a/48624590/8811886).
**Default:** `false`

@ -5,7 +5,7 @@ export const PATRON_FUNDING_FETCH_FAIL = 'PATRON_FUNDING_FETCH_FAIL';
export function fetchFunding() {
return (dispatch, getState) => {
api(getState).get('/patron/v1/funding').then(response => {
api(getState).get('/api/patron/v1/instance').then(response => {
dispatch(importFetchedFunding(response.data));
}).catch(error => {
dispatch(fetchFundingFail(error));

@ -11,6 +11,7 @@ import { decode } from 'blurhash';
import { isPanoramic, isPortrait, isNonConformingRatio, minimumAspectRatio, maximumAspectRatio } from '../utils/media_aspect_ratio';
import { Map as ImmutableMap } from 'immutable';
import { getSettings } from 'soapbox/actions/settings';
import Icon from 'soapbox/components/icon';
import StillImage from 'soapbox/components/still_image';
const messages = defineMessages({
@ -192,6 +193,24 @@ class Item extends React.PureComponent {
<span className='media-gallery__gifv__label'>GIF</span>
</div>
);
} else if (attachment.get('type') === 'audio') {
const remoteURL = attachment.get('remote_url');
const originalUrl = attachment.get('url');
const fileExtensionLastIndex = remoteURL.lastIndexOf('.');
const fileExtension = remoteURL.substr(fileExtensionLastIndex + 1).toUpperCase();
thumbnail = (
<a
className={classNames('media-gallery__item-thumbnail')}
href={attachment.get('remote_url') || originalUrl}
onClick={this.handleClick}
target='_blank'
alt={attachment.get('description')}
title={attachment.get('description')}
>
<span className='media-gallery__item__icons'><Icon id='volume-up' /></span>
<span className='media-gallery__file-extension__label'>{fileExtension}</span>
</a>
);
}
return (

@ -39,11 +39,13 @@ const messages = defineMessages({
const mapStateToProps = state => {
const me = state.get('me');
const getAccount = makeGetAccount();
const patronEnabled = state.getIn(['soapbox', 'extensions', 'patron', 'enabled']);
const patronUrl = state.getIn(['soapbox', 'extensions', 'patron', 'baseUrl']);
return {
account: getAccount(state, me),
sidebarOpen: state.get('sidebar').sidebarOpen,
hasPatron: state.getIn(['soapbox', 'extensions', 'patron']),
patronUrl: patronEnabled && patronUrl,
isStaff: isStaff(state.getIn(['accounts', me])),
};
};
@ -75,7 +77,7 @@ class SidebarMenu extends ImmutablePureComponent {
}
render() {
const { sidebarOpen, onClose, intl, account, onClickLogOut, hasPatron, isStaff } = this.props;
const { sidebarOpen, onClose, intl, account, onClickLogOut, patronUrl, isStaff } = this.props;
if (!account) return null;
const acct = account.get('acct');
@ -127,11 +129,11 @@ class SidebarMenu extends ImmutablePureComponent {
<Icon id='envelope' />
<span className='sidebar-menu-item__title'>{intl.formatMessage(messages.messages)}</span>
</NavLink>
{hasPatron ?
<NavLink className='sidebar-menu-item' to='/donate' onClick={onClose}>
{patronUrl ?
<a className='sidebar-menu-item' href={patronUrl} onClick={onClose}>
<Icon id='dollar' />
<span className='sidebar-menu-item__title'>{intl.formatMessage(messages.donate)}</span>
</NavLink>
</a>
: ''}
<NavLink className='sidebar-menu-item' to='/lists' onClick={onClose}>
<Icon id='list' />

@ -12,7 +12,7 @@ import AttachmentList from './attachment_list';
import Card from '../features/status/components/card';
import { injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { MediaGallery, Video } from '../features/ui/util/async-components';
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
import { HotKeys } from 'react-hotkeys';
import classNames from 'classnames';
import Icon from 'soapbox/components/icon';
@ -73,6 +73,7 @@ class Status extends ImmutablePureComponent {
onPin: PropTypes.func,
onOpenMedia: PropTypes.func,
onOpenVideo: PropTypes.func,
onOpenAudio: PropTypes.func,
onBlock: PropTypes.func,
onEmbed: PropTypes.func,
onHeightChange: PropTypes.func,
@ -194,10 +195,18 @@ class Status extends ImmutablePureComponent {
return <div className='media-spoiler-video' style={{ height: '110px' }} />;
}
renderLoadingAudioPlayer() {
return <div className='media-spoiler-audio' style={{ height: '110px' }} />;
}
handleOpenVideo = (media, startTime) => {
this.props.onOpenVideo(media, startTime);
}
handleOpenAudio = (media, startTime) => {
this.props.OnOpenAudio(media, startTime);
}
handleHotkeyReply = e => {
e.preventDefault();
this.props.onReply(this._properStatus(), this.context.router.history);
@ -356,6 +365,24 @@ class Status extends ImmutablePureComponent {
)}
</Bundle>
);
} else if (status.getIn(['media_attachments', 0, 'type']) === 'audio' && status.get('media_attachments').size === 1) {
const audio = status.getIn(['media_attachments', 0]);
media = (
<Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
{Component => (
<Component
src={audio.get('url')}
alt={audio.get('description')}
inline
sensitive={status.get('sensitive')}
cacheWidth={this.props.cacheMediaWidth}
visible={this.state.showMedia}
onOpenAudio={this.handleOpenAudio}
/>
)}
</Bundle>
);
} else {
media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>

@ -146,6 +146,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch(openModal('VIDEO', { media, time }));
},
onOpenAudio(media, time) {
dispatch(openModal('AUDIO', { media, time }));
},
onBlock(status) {
const account = status.get('account');
dispatch(openModal('CONFIRM', {

@ -146,6 +146,16 @@ class MediaItem extends ImmutablePureComponent {
<span className='media-gallery__gifv__label'>GIF</span>
</div>
);
} else if (attachment.get('type') === 'audio') {
const remoteURL = attachment.get('remote_url');
const fileExtensionLastIndex = remoteURL.lastIndexOf('.');
const fileExtension = remoteURL.substr(fileExtensionLastIndex + 1).toUpperCase();
thumbnail = (
<div className='media-gallery__item-thumbnail'>
<span className='media-gallery__item__icons'><Icon id='volume-up' /></span>
<span className='media-gallery__file-extension__label'>{fileExtension}</span>
</div>
);
}
if (!visible) {

@ -0,0 +1,380 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { throttle } from 'lodash';
import classNames from 'classnames';
import Icon from 'soapbox/components/icon';
import { getSettings } from 'soapbox/actions/settings';
const messages = defineMessages({
play: { id: 'audio.play', defaultMessage: 'Play' },
pause: { id: 'audio.pause', defaultMessage: 'Pause' },
mute: { id: 'audio.mute', defaultMessage: 'Mute' },
unmute: { id: 'audio.unmute', defaultMessage: 'Unmute' },
hide: { id: 'audio.hide', defaultMessage: 'Hide audio' },
expand: { id: 'audio.expand', defaultMessage: 'Expand audio' },
close: { id: 'audio.close', defaultMessage: 'Close audio' },
});
const formatTime = secondsNum => {
let hours = Math.floor(secondsNum / 3600);
let minutes = Math.floor((secondsNum - (hours * 3600)) / 60);
let seconds = secondsNum - (hours * 3600) - (minutes * 60);
if (hours < 10) hours = '0' + hours;
if (minutes < 10 && hours >= 1) minutes = '0' + minutes;
if (seconds < 10) seconds = '0' + seconds;
return (hours === '00' ? '' : `${hours}:`) + `${minutes}:${seconds}`;
};
export const findElementPosition = el => {
let box;
if (el.getBoundingClientRect && el.parentNode) {
box = el.getBoundingClientRect();
}
if (!box) {
return {
left: 0,
top: 0,
};
}
const docEl = document.documentElement;
const body = document.body;
const clientLeft = docEl.clientLeft || body.clientLeft || 0;
const scrollLeft = window.pageXOffset || body.scrollLeft;
const left = (box.left + scrollLeft) - clientLeft;
const clientTop = docEl.clientTop || body.clientTop || 0;
const scrollTop = window.pageYOffset || body.scrollTop;
const top = (box.top + scrollTop) - clientTop;
return {
left: Math.round(left),
top: Math.round(top),
};
};
export const getPointerPosition = (el, event) => {
const position = {};
const box = findElementPosition(el);
const boxW = el.offsetWidth;
const boxH = el.offsetHeight;
const boxY = box.top;
const boxX = box.left;
let pageY = event.pageY;
let pageX = event.pageX;
if (event.changedTouches) {
pageX = event.changedTouches[0].pageX;
pageY = event.changedTouches[0].pageY;
}
position.y = Math.max(0, Math.min(1, (pageY - boxY) / boxH));
position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
return position;
};
const mapStateToProps = state => ({
displayMedia: getSettings(state).get('displayMedia'),
});
export default @connect(mapStateToProps)
@injectIntl
class Audio extends React.PureComponent {
static propTypes = {
src: PropTypes.string.isRequired,
alt: PropTypes.string,
sensitive: PropTypes.bool,
startTime: PropTypes.number,
detailed: PropTypes.bool,
inline: PropTypes.bool,
cacheWidth: PropTypes.func,
visible: PropTypes.bool,
onToggleVisibility: PropTypes.func,
intl: PropTypes.object.isRequired,
link: PropTypes.node,
displayMedia: PropTypes.string,
expandSpoilers: PropTypes.bool,
};
state = {
currentTime: 0,
duration: 0,
volume: 0.5,
paused: true,
dragging: false,
muted: false,
revealed: this.props.visible !== undefined ? this.props.visible : (this.props.displayMedia !== 'hide_all' && !this.props.sensitive || this.props.displayMedia === 'show_all'),
};
// hard coded in components.scss
// any way to get ::before values programatically?
volWidth = 50;
volOffset = 85;
volHandleOffset = v => {
const offset = v * this.volWidth + this.volOffset;
return (offset > 125) ? 125 : offset;
}
setPlayerRef = c => {
this.player = c;
if (c) {
if (this.props.cacheWidth) this.props.cacheWidth(this.player.offsetWidth);
this.setState({
containerWidth: c.offsetWidth,
});
}
}
setAudioRef = c => {
this.audio = c;
if (this.audio) {
this.setState({ volume: this.audio.volume, muted: this.audio.muted });
}
}
setSeekRef = c => {
this.seek = c;
}
setVolumeRef = c => {
this.volume = c;
}
handleClickRoot = e => e.stopPropagation();
handlePlay = () => {
this.setState({ paused: false });
}
handlePause = () => {
this.setState({ paused: true });
}
handleTimeUpdate = () => {
this.setState({
currentTime: Math.floor(this.audio.currentTime),
duration: Math.floor(this.audio.duration),
});
}
handleVolumeMouseDown = e => {
document.addEventListener('mousemove', this.handleMouseVolSlide, true);
document.addEventListener('mouseup', this.handleVolumeMouseUp, true);
document.addEventListener('touchmove', this.handleMouseVolSlide, true);
document.addEventListener('touchend', this.handleVolumeMouseUp, true);
this.handleMouseVolSlide(e);
e.preventDefault();
e.stopPropagation();
}
handleVolumeMouseUp = () => {
document.removeEventListener('mousemove', this.handleMouseVolSlide, true);
document.removeEventListener('mouseup', this.handleVolumeMouseUp, true);
document.removeEventListener('touchmove', this.handleMouseVolSlide, true);
document.removeEventListener('touchend', this.handleVolumeMouseUp, true);
}
handleMouseVolSlide = throttle(e => {
const rect = this.volume.getBoundingClientRect();
const x = (e.clientX - rect.left) / this.volWidth; //x position within the element.
if(!isNaN(x)) {
var slideamt = x;
if(x > 1) {
slideamt = 1;
} else if(x < 0) {
slideamt = 0;
}
this.audio.volume = slideamt;
this.setState({ volume: slideamt });
}
}, 60);
handleMouseDown = e => {
document.addEventListener('mousemove', this.handleMouseMove, true);
document.addEventListener('mouseup', this.handleMouseUp, true);
document.addEventListener('touchmove', this.handleMouseMove, true);
document.addEventListener('touchend', this.handleMouseUp, true);
this.setState({ dragging: true });
this.audio.pause();
this.handleMouseMove(e);
e.preventDefault();
e.stopPropagation();
}
handleMouseUp = () => {
document.removeEventListener('mousemove', this.handleMouseMove, true);
document.removeEventListener('mouseup', this.handleMouseUp, true);
document.removeEventListener('touchmove', this.handleMouseMove, true);
document.removeEventListener('touchend', this.handleMouseUp, true);
this.setState({ dragging: false });
this.audio.play();
}
handleMouseMove = throttle(e => {
const { x } = getPointerPosition(this.seek, e);
const currentTime = Math.floor(this.audio.duration * x);
if (!isNaN(currentTime)) {
this.audio.currentTime = currentTime;
this.setState({ currentTime });
}
}, 60);
togglePlay = () => {
if (this.state.paused) {
this.audio.play();
} else {
this.audio.pause();
}
}
toggleMute = () => {
this.audio.muted = !this.audio.muted;
this.setState({ muted: this.audio.muted });
}
toggleWarning = () => {
this.setState({ revealed: !this.state.revealed });
}
handleLoadedData = () => {
if (this.props.startTime) {
this.audio.currentTime = this.props.startTime;
this.audio.play();
}
}
handleProgress = () => {
if (this.audio.buffered.length > 0) {
this.setState({ buffer: this.audio.buffered.end(0) / this.audio.duration * 100 });
}
}
handleVolumeChange = () => {
this.setState({ volume: this.audio.volume, muted: this.audio.muted });
}
getPreload = () => {
const { startTime, detailed } = this.props;
const { dragging } = this.state;
if (startTime || dragging) {
return 'auto';
} else if (detailed) {
return 'metadata';
} else {
return 'none';
}
}
render() {
const { src, inline, intl, alt, detailed, sensitive, link } = this.props;
const { currentTime, duration, volume, buffer, dragging, paused, muted, revealed } = this.state;
const progress = (currentTime / duration) * 100;
const volumeWidth = (muted) ? 0 : volume * this.volWidth;
const volumeHandleLoc = (muted) ? this.volHandleOffset(0) : this.volHandleOffset(volume);
const playerStyle = {};
let warning;
if (sensitive) {
warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
} else {
warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
}
return (
<div
role='menuitem'
className={classNames('audio-player', { detailed: detailed, inline: inline, warning_visible: !revealed })}
style={playerStyle}
ref={this.setPlayerRef}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onClick={this.handleClickRoot}
tabIndex={0}
>
<audio
ref={this.setAudioRef}
src={src}
// preload={this.getPreload()}
role='button'
tabIndex='0'
aria-label={alt}
title={alt}
volume={volume}
onClick={this.togglePlay}
onPlay={this.handlePlay}
onPause={this.handlePause}
onTimeUpdate={this.handleTimeUpdate}
onLoadedData={this.handleLoadedData}
onProgress={this.handleProgress}
onVolumeChange={this.handleVolumeChange}
/>
<div className={classNames('audio-player__spoiler-warning', { 'spoiler-button--hidden': revealed })}>
<span className='audio-player__spoiler-warning__label'><Icon id='warning' fixedWidth /> {warning}</span>
<button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleWarning}><Icon id='times' fixedWidth /></button>
</div>
<div className={classNames('audio-player__controls')}>
<div className='audio-player__seek' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}>
<div className='audio-player__seek__buffer' style={{ width: `${buffer}%` }} />
<div className='audio-player__seek__progress' style={{ width: `${progress}%` }} />
<span
className={classNames('audio-player__seek__handle', { active: dragging })}
tabIndex='0'
style={{ left: `${progress}%` }}
/>
</div>
<div className='audio-player__buttons-bar'>
<div className='audio-player__buttons left'>
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
<div className='audio-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
<div className='audio-player__volume__current' style={{ width: `${volumeWidth}px` }} />
<span
className={classNames('audio-player__volume__handle')}
tabIndex='0'
style={{ left: `${volumeHandleLoc}px` }}
/>
</div>
<span>
<span className='audio-player__time-current'>{formatTime(currentTime)}</span>
<span className='audio-player__time-sep'>/</span>
<span className='audio-player__time-total'>{formatTime(duration)}</span>
</span>
{link && <span className='audio-player__link'>{link}</span>}
</div>
</div>
</div>
</div>
);
}
}

@ -7,7 +7,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
const messages = defineMessages({
upload: { id: 'upload_button.label', defaultMessage: 'Add media (JPEG, PNG, GIF, WebM, MP4, MOV)' },
upload: { id: 'upload_button.label', defaultMessage: 'Add media attachment' },
});
const makeMapStateToProps = () => {

@ -4,7 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import Toggle from 'react-toggle';
import noop from 'lodash/noop';
import StatusContent from '../../../components/status_content';
import { MediaGallery, Video } from '../../ui/util/async-components';
import { MediaGallery, Video, Audio } from '../../ui/util/async-components';
import Bundle from '../../ui/components/bundle';
export default class StatusCheckBox extends React.PureComponent {
@ -48,6 +48,22 @@ export default class StatusCheckBox extends React.PureComponent {
)}
</Bundle>
);
} else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
const audio = status.getIn(['media_attachments', 0]);
media = (
<Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
{Component => (
<Component
src={audio.get('url')}
alt={audio.get('description')}
inline
sensitive={status.get('sensitive')}
onOpenAudio={noop}
/>
)}
</Bundle>
);
} else {
media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >

@ -10,6 +10,7 @@ import { FormattedDate, FormattedNumber } from 'react-intl';
import Card from './card';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Video from '../../video';
import Audio from '../../audio';
import scheduleIdleTask from '../../ui/util/schedule_idle_task';
import classNames from 'classnames';
import Icon from 'soapbox/components/icon';
@ -120,6 +121,19 @@ export default class DetailedStatus extends ImmutablePureComponent {
onToggleVisibility={this.props.onToggleMediaVisibility}
/>
);
} else if (status.getIn(['media_attachments', 0, 'type']) === 'audio' && status.get('media_attachments').size === 1) {
const audio = status.getIn(['media_attachments', 0]);
media = (
<Audio
src={audio.get('url')}
alt={audio.get('description')}
inline
sensitive={status.get('sensitive')}
visible={this.props.showMedia}
onToggleVisibility={this.props.onToggleMediaVisibility}
/>
);
} else {
media = (
<MediaGallery

@ -5,6 +5,16 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import ProgressBar from '../../../components/progress_bar';
import { fetchFunding } from 'soapbox/actions/patron';
const moneyFormat = amount => (
new Intl
.NumberFormat('en-US', {
style: 'currency',
currency: 'usd',
notation: 'compact',
})
.format(amount/100)
);
class FundingPanel extends ImmutablePureComponent {
componentDidMount() {
@ -12,21 +22,22 @@ class FundingPanel extends ImmutablePureComponent {
}
render() {
const { funding } = this.props;
const { funding, patronUrl } = this.props;
if (!funding) {
return null;
}
const amount = funding.getIn(['funding', 'amount']);
const goal = funding.getIn(['goals', '0', 'amount']);
const goal_text = funding.getIn(['goals', '0', 'text']);
const goal_reached = funding.get('amount') >= goal;
const goal_reached = amount >= goal;
let ratio_text;
if (goal_reached) {
ratio_text = <><strong>${Math.floor(goal/100)}</strong> per month<span className='funding-panel__reached'>&mdash; reached!</span></>;
ratio_text = <><strong>{moneyFormat(goal)}</strong> per month<span className='funding-panel__reached'>&mdash; reached!</span></>;
} else {
ratio_text = <><strong>${Math.floor(funding.get('amount')/100)} out of ${Math.floor(goal/100)}</strong> per month</>;
ratio_text = <><strong>{moneyFormat(amount)} out of {moneyFormat(goal)}</strong> per month</>;
}
return (
@ -41,11 +52,11 @@ class FundingPanel extends ImmutablePureComponent {
<div className='funding-panel__ratio'>
{ratio_text}
</div>
<ProgressBar progress={funding.get('amount')/goal} />
<ProgressBar progress={amount/goal} />
<div className='funding-panel__description'>
{goal_text}
</div>
<a className='button' href='/donate'>Donate</a>
{patronUrl && <a className='button' href={patronUrl}>Donate</a>}
</div>
</div>
);
@ -56,6 +67,7 @@ class FundingPanel extends ImmutablePureComponent {
const mapStateToProps = state => {
return {
funding: state.getIn(['patron', 'funding']),
patronUrl: state.getIn(['soapbox', 'extensions', 'patron', 'baseUrl']),
};
};

@ -3,6 +3,7 @@ import ReactSwipeableViews from 'react-swipeable-views';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Video from 'soapbox/features/video';
import Audio from 'soapbox/features/audio';
import ExtendedVideoPlayer from 'soapbox/components/extended_video_player';
import classNames from 'classnames';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
@ -138,9 +139,18 @@ class MediaModal extends ImmutablePureComponent {
});
}
const isMultiMedia = media.map((image) => {
if (image.get('type') !== 'image') {
return true;
}
return false;
}).toArray();
const content = media.map((image) => {
const width = image.getIn(['meta', 'original', 'width']) || null;
const height = image.getIn(['meta', 'original', 'height']) || null;
const link = (status && <a href={status.get('url')} onClick={this.handleStatusClick}><FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>);
if (image.get('type') === 'image') {
return (
@ -167,6 +177,20 @@ class MediaModal extends ImmutablePureComponent {
startTime={time || 0}
onCloseVideo={onClose}
detailed
link={link}
alt={image.get('description')}
key={image.get('url')}
/>
);
} else if (image.get('type') === 'audio') {
const { time } = this.props;
return (
<Audio
src={image.get('url')}
startTime={time || 0}
detailed
link={link}
alt={image.get('description')}
key={image.get('url')}
/>
@ -178,6 +202,7 @@ class MediaModal extends ImmutablePureComponent {
muted
controls={false}
width={width}
link={link}
height={height}
key={image.get('preview_url')}
alt={image.get('description')}
@ -230,7 +255,7 @@ class MediaModal extends ImmutablePureComponent {
{leftNav}
{rightNav}
{status && (
{(status && !isMultiMedia[index]) && (
<div className={classNames('media-modal__meta', { 'media-modal__meta--shifted': media.size > 1 })}>
<a href={status.get('url')} onClick={this.handleStatusClick}><FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
</div>

@ -142,6 +142,10 @@ export function Video() {
return import(/* webpackChunkName: "features/video" */'../../video');
}
export function Audio() {
return import(/* webpackChunkName: "features/audio" */'../../audio');
}
export function EmbedModal() {
return import(/* webpackChunkName: "modals/embed_modal" */'../components/embed_modal');
}

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "لقد طرأ هناك خطأ غير متوقّع.",
"alert.unexpected.title": "المعذرة!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "يمكنك/ي ضغط {combo} لتخطّي هذه في المرّة القادمة",
"bundle_column_error.body": "لقد وقع هناك خطأ أثناء عملية تحميل هذا العنصر.",
"bundle_column_error.retry": "إعادة المحاولة",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Asocedió un fallu inesperáu.",
"alert.unexpected.title": "¡Ups!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Pues primir {combo} pa saltar esto la próxima vegada",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.retry": "Try again",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.retry": "Опитай отново",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "অপ্রত্যাশিত একটি সমস্যা হয়েছে।",
"alert.unexpected.title": "ওহো!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "পরেরবার আপনি {combo} চাপ দিলে এটার শেষে চলে যেতে পারবেন",
"bundle_column_error.body": "এই অংশটি দেখতে যেয়ে কোনো সমস্যা হয়েছে।",
"bundle_column_error.retry": "আবার চেষ্টা করুন",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Ur fazi dic'hortozet zo degouezhet.",
"alert.unexpected.title": "C'hem !",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.retry": "Klask endro",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "S'ha produït un error inesperat.",
"alert.unexpected.title": "Vaja!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop",
"bundle_column_error.body": "S'ha produït un error en carregar aquest component.",
"bundle_column_error.retry": "Torna-ho a provar",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Un prublemu inaspettatu hè accadutu.",
"alert.unexpected.title": "Uups!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Pudete appughjà nant'à {combo} per saltà quessa a prussima volta",
"bundle_column_error.body": "C'hè statu un prublemu caricandu st'elementu.",
"bundle_column_error.retry": "Pruvà torna",

@ -43,6 +43,13 @@
"account_gallery.none": "Žádná média k zobrazení.",
"alert.unexpected.message": "Objevila se neočekávaná chyba.",
"alert.unexpected.title": "Jejda!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Příště můžete pro přeskočení stisknout {combo}",
"bundle_column_error.body": "Při načítání tohoto komponentu se něco pokazilo.",
"bundle_column_error.retry": "Zkuste to znovu",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Digwyddodd gwall annisgwyl.",
"alert.unexpected.title": "Wps!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Mae modd gwasgu {combo} er mwyn sgipio hyn tro nesa",
"bundle_column_error.body": "Aeth rhywbeth o'i le tra'n llwytho'r elfen hon.",
"bundle_column_error.retry": "Ceisiwch eto",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Der opstod en uventet fejl.",
"alert.unexpected.title": "Ups!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Du kan trykke {combo} for at springe dette over næste gang",
"bundle_column_error.body": "Noget gik galt under indlæsningen af dette komponent.",
"bundle_column_error.retry": "Prøv igen",

@ -43,6 +43,13 @@
"account_gallery.none": "Keine Medien vorhanden.",
"alert.unexpected.message": "Ein unerwarteter Fehler ist aufgetreten.",
"alert.unexpected.title": "Hoppla!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Drücke {combo}, um dieses Fenster zu überspringen",
"bundle_column_error.body": "Etwas ist beim Laden schiefgelaufen.",
"bundle_column_error.retry": "Erneut versuchen",

@ -867,11 +867,43 @@
{
"descriptors": [
{
"defaultMessage": "Click the image to get a new captcha",
"id": "registration.captcha.hint"
"defaultMessage": "Play",
"id": "audio.play"
},
{
"defaultMessage": "Pause",
"id": "audio.pause"
},
{
"defaultMessage": "Mute",
"id": "audio.mute"
},
{
"defaultMessage": "Unmute",
"id": "audio.unmute"
},
{
"defaultMessage": "Hide audio",
"id": "audio.hide"
},
{
"defaultMessage": "Expand audio",
"id": "audio.expand"
},
{
"defaultMessage": "Close audio",
"id": "audio.close"
},
{
"defaultMessage": "Sensitive content",
"id": "status.sensitive_warning"
},
{
"defaultMessage": "Media hidden",
"id": "status.media_hidden"
}
],
"path": "app/soapbox/features/auth_login/components/captcha.json"
"path": "app/soapbox/features/audio/index.json"
},
{
"descriptors": [
@ -1226,7 +1258,7 @@
{
"descriptors": [
{
"defaultMessage": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
"defaultMessage": "Add media attachment",
"id": "upload_button.label"
}
],

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Προέκυψε απροσδόκητο σφάλμα.",
"alert.unexpected.title": "Εεπ!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Μπορείς να πατήσεις {combo} για να το προσπεράσεις αυτό την επόμενη φορά",
"bundle_column_error.body": "Κάτι πήγε στραβά ενώ φορτωνόταν αυτό το στοιχείο.",
"bundle_column_error.retry": "Δοκίμασε ξανά",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.retry": "Try again",
@ -451,7 +458,7 @@
"unauthorized_modal.text": "You need to be logged in to do that.",
"unauthorized_modal.title": "Sign up for {site_title}",
"upload_area.title": "Drag & drop to upload",
"upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
"upload_button.label": "Add media attachment",
"upload_error.limit": "File upload limit exceeded.",
"upload_error.poll": "File upload not allowed with polls.",
"upload_form.description": "Describe for the visually impaired",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Neatendita eraro okazis.",
"alert.unexpected.title": "Ups!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Vi povas premi {combo} por preterpasi sekvafoje",
"bundle_column_error.body": "Io misfunkciis en la ŝargado de ĉi tiu elemento.",
"bundle_column_error.retry": "Bonvolu reprovi",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Ocurrió un error inesperado.",
"alert.unexpected.title": "¡Epa!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Podés hacer clic en {combo} para saltar esto la próxima vez",
"bundle_column_error.body": "Algo salió mal al cargar este componente.",
"bundle_column_error.retry": "Intentá de nuevo",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Hubo un error inesperado.",
"alert.unexpected.title": "¡Ups!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Puedes hacer clic en {combo} para saltar este aviso la próxima vez",
"bundle_column_error.body": "Algo salió mal al cargar este componente.",
"bundle_column_error.retry": "Inténtalo de nuevo",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Tekkis ootamatu viga.",
"alert.unexpected.title": "Oih!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Saad vajutada {combo}, et see järgmine kord vahele jätta",
"bundle_column_error.body": "Mindagi läks valesti selle komponendi laadimisel.",
"bundle_column_error.retry": "Proovi uuesti",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Ustekabeko errore bat gertatu da.",
"alert.unexpected.title": "Ene!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "{combo} sakatu dezakezu hurrengoan hau saltatzeko",
"bundle_column_error.body": "Zerbait okerra gertatu da osagai hau kargatzean.",
"bundle_column_error.retry": "Saiatu berriro",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "خطای پیش‌بینی‌نشده‌ای رخ داد.",
"alert.unexpected.title": "ای وای!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید",
"bundle_column_error.body": "هنگام بازکردن این بخش خطایی رخ داد.",
"bundle_column_error.retry": "تلاش دوباره",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Tapahtui odottamaton virhe.",
"alert.unexpected.title": "Hups!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Ensi kerralla voit ohittaa tämän painamalla {combo}",
"bundle_column_error.body": "Jokin meni vikaan komponenttia ladattaessa.",
"bundle_column_error.retry": "Yritä uudestaan",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Une erreur inattendue sest produite.",
"alert.unexpected.title": "Oups!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Vous pouvez appuyer sur {combo} pour passer ceci, la prochaine fois",
"bundle_column_error.body": "Une erreur sest produite lors du chargement de ce composant.",
"bundle_column_error.retry": "Réessayer",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.retry": "Try again",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Aconteceu un fallo non agardado.",
"alert.unexpected.title": "Vaia!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Pulse {combo} para saltar esto a próxima vez",
"bundle_column_error.body": "Houbo un fallo mentras se cargaba este compoñente.",
"bundle_column_error.retry": "Inténteo de novo",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "אירעה שגיאה בלתי צפויה.",
"alert.unexpected.title": "אופס!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "ניתן להקיש {combo} כדי לדלג בפעם הבאה",
"bundle_column_error.body": "משהו השתבש בעת הצגת הרכיב הזה.",
"bundle_column_error.retry": "לנסות שוב",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.retry": "Try again",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Možeš pritisnuti {combo} kako bi ovo preskočio sljedeći put",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.retry": "Try again",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Váratlan hiba történt.",
"alert.unexpected.title": "Hoppá!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Hogy átugord ezt következő alkalommal, használd {combo}",
"bundle_column_error.body": "Hiba történt a komponens betöltése közben.",
"bundle_column_error.retry": "Próbáld újra",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Վա՜յ",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Կարող ես սեղմել {combo}՝ սա հաջորդ անգամ բաց թողնելու համար",
"bundle_column_error.body": "Այս բաղադրիչը բեռնելու ընթացքում ինչ֊որ բան խափանվեց։",
"bundle_column_error.retry": "Կրկին փորձել",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Terjadi kesalahan yang tidak terduga.",
"alert.unexpected.title": "Oops!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Anda dapat menekan {combo} untuk melewati ini",
"bundle_column_error.body": "Kesalahan terjadi saat memuat komponen ini.",
"bundle_column_error.retry": "Coba lagi",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Tu povas presar sur {combo} por omisar co en la venonta foyo",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.retry": "Try again",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Si è verificato un errore inatteso.",
"alert.unexpected.title": "Oops!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta",
"bundle_column_error.body": "E' avvenuto un errore durante il caricamento di questo componente.",
"bundle_column_error.retry": "Riprova",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "不明なエラーが発生しました。",
"alert.unexpected.title": "エラー!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "次からは{combo}を押せばスキップできます",
"bundle_column_error.body": "コンポーネントの読み込み中に問題が発生しました。",
"bundle_column_error.retry": "再試行",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "წარმოიშვა მოულოდნელი შეცდომა.",
"alert.unexpected.title": "უპს!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "შეგიძლიათ დააჭიროთ {combo}-ს რათა შემდეგ ჯერზე გამოტოვოთ ეს",
"bundle_column_error.body": "ამ კომპონენტის ჩატვირთვისას რაღაც აირია.",
"bundle_column_error.retry": "სცადეთ კიდევ ერთხელ",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Бір нәрсе дұрыс болмады.",
"alert.unexpected.title": "Өй!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Келесіде өткізіп жіберу үшін басыңыз {combo}",
"bundle_column_error.body": "Бұл компонентті жүктеген кезде бір қате пайда болды.",
"bundle_column_error.retry": "Қайтадан көріңіз",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "예측하지 못한 에러가 발생했습니다.",
"alert.unexpected.title": "앗!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "{combo}를 누르면 다음부터 이 과정을 건너뛸 수 있습니다",
"bundle_column_error.body": "컴포넌트를 불러오는 과정에서 문제가 발생했습니다.",
"bundle_column_error.retry": "다시 시도",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.retry": "Try again",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Negaidīta kļūda.",
"alert.unexpected.title": "Ups!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Nospied {combo} lai izlaistu šo nākamreiz",
"bundle_column_error.body": "Kaut kas nogāja greizi ielādējot šo komponenti.",
"bundle_column_error.retry": "Mēģini vēlreiz",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Неочекувана грешка.",
"alert.unexpected.title": "Упс!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Кликни {combo} за да го прескокниш ова нареден пат",
"bundle_column_error.body": "Се случи проблем при вчитувањето.",
"bundle_column_error.retry": "Обидете се повторно",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.retry": "Try again",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Er deed zich een onverwachte fout voor",
"alert.unexpected.title": "Oeps!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan",
"bundle_column_error.body": "Tijdens het laden van dit onderdeel is er iets fout gegaan.",
"bundle_column_error.retry": "Opnieuw proberen",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Eit uforventa problem har hendt.",
"alert.unexpected.title": "Oops!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Du kan trykke {combo} for å hoppe over dette neste gong",
"bundle_column_error.body": "Noko gikk gale mens komponent ble nedlasta.",
"bundle_column_error.retry": "Prøv igjen",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang",
"bundle_column_error.body": "Noe gikk galt mens denne komponenten lastet.",
"bundle_column_error.retry": "Prøv igjen",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Una error ses producha.",
"alert.unexpected.title": "Ops!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Podètz botar {combo} per passar aquò lo còp que ven",
"bundle_column_error.body": "Quicòm a fach mèuca pendent lo cargament daqueste compausant.",
"bundle_column_error.retry": "Tornar ensajar",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Wystąpił nieoczekiwany błąd.",
"alert.unexpected.title": "O nie!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem",
"bundle_column_error.body": "Coś poszło nie tak podczas ładowania tego składnika.",
"bundle_column_error.retry": "Spróbuj ponownie",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Um erro inesperado ocorreu.",
"alert.unexpected.title": "Eita!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Você pode pressionar {combo} para ignorar este diálogo na próxima vez",
"bundle_column_error.body": "Algo de errado aconteceu enquanto este componente era carregado.",
"bundle_column_error.retry": "Tente novamente",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Ocorreu um erro inesperado.",
"alert.unexpected.title": "Bolas!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
"bundle_column_error.body": "Algo de errado aconteceu enquanto este componente era carregado.",
"bundle_column_error.retry": "Tente de novo",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "A apărut o eroare neașteptată.",
"alert.unexpected.title": "Hopa!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Poți apăsa {combo} pentru a omite asta data viitoare",
"bundle_column_error.body": "Ceva nu a funcționat la încărcarea acestui component.",
"bundle_column_error.retry": "Încearcă din nou",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Что-то пошло не так.",
"alert.unexpected.title": "Ой!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз",
"bundle_column_error.body": "Что-то пошло не так при загрузке этого компонента.",
"bundle_column_error.retry": "Попробовать снова",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Vyskytla sa nečakaná chyba.",
"alert.unexpected.title": "Ups!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Nabudúce môžeš kliknúť {combo} pre preskočenie",
"bundle_column_error.body": "Pri načítaní tohto prvku nastala nejaká chyba.",
"bundle_column_error.retry": "Skús to znova",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Zgodila se je nepričakovana napaka.",
"alert.unexpected.title": "Uups!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Če želite preskočiti to, lahko pritisnete {combo}",
"bundle_column_error.body": "Med nalaganjem te komponente je prišlo do napake.",
"bundle_column_error.retry": "Poskusi ponovno",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Ndodhi një gabim të papritur.",
"alert.unexpected.title": "Hëm!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Mund të shtypni {combo}, që të anashkalohet kjo herës tjetër",
"bundle_column_error.body": "Diç shkoi ters teksa ngarkohej ky përbërës.",
"bundle_column_error.retry": "Riprovoni",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Možete pritisnuti {combo} da preskočite ovo sledeći put",
"bundle_column_error.body": "Nešto je pošlo po zlu prilikom učitavanja ove komponente.",
"bundle_column_error.retry": "Pokušajte ponovo",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Појавила се неочекивана грешка.",
"alert.unexpected.title": "Упс!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Можете притиснути {combo} да прескочите ово следећи пут",
"bundle_column_error.body": "Нешто је пошло по злу приликом учитавања ове компоненте.",
"bundle_column_error.retry": "Покушајте поново",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Ett oväntat fel uppstod.",
"alert.unexpected.title": "Hoppsan!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Du kan trycka {combo} för att slippa denna nästa gång",
"bundle_column_error.body": "Något gick fel när du laddade denna komponent.",
"bundle_column_error.retry": "Försök igen",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "எதிர் பாராத பிழை ஏற்பட்டு விட்டது.",
"alert.unexpected.title": "அச்சச்சோ!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "நீங்கள் அழுத்தவும் {combo} அடுத்த முறை தவிர்க்கவும்",
"bundle_column_error.body": "இந்த கூறுகளை ஏற்றும்போது ஏதோ தவறு ஏற்பட்டது.",
"bundle_column_error.retry": "மீண்டும் முயற்சி செய்",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "అనుకోని తప్పు జరిగినది.",
"alert.unexpected.title": "అయ్యో!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "మీరు తదుపరిసారి దీనిని దాటవేయడానికి {combo} నొక్కవచ్చు",
"bundle_column_error.body": "ఈ భాగం లోడ్ అవుతున్నప్పుడు ఏదో తప్పు జరిగింది.",
"bundle_column_error.retry": "మళ్ళీ ప్రయత్నించండి",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "เกิดข้อผิดพลาดที่ไม่คาดคิด",
"alert.unexpected.title": "อุปส์!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "คุณสามารถกด {combo} เพื่อข้ามสิ่งนี้ในครั้งถัดไป",
"bundle_column_error.body": "มีบางอย่างผิดพลาดขณะโหลดส่วนประกอบนี้",
"bundle_column_error.retry": "ลองอีกครั้ง",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Beklenmedik bir hata oluştu.",
"alert.unexpected.title": "Hay aksi!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Bir daha ki sefere {combo} tuşuna basabilirsiniz",
"bundle_column_error.body": "Bu bileşen yüklenirken bir şeyler ters gitti.",
"bundle_column_error.retry": "Tekrar deneyin",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "Трапилась неочікувана помилка.",
"alert.unexpected.title": "Ой!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "Ви можете натиснути {combo}, щоб пропустити це наступного разу",
"bundle_column_error.body": "Щось пішло не так під час завантаження компоненту.",
"bundle_column_error.retry": "Спробуйте ще раз",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "发生了意外错误。",
"alert.unexpected.title": "哎呀!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "下次按住 {combo} 即可跳过此提示",
"bundle_column_error.body": "载入这个组件时发生了错误。",
"bundle_column_error.retry": "重试",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "發生不可預期的錯誤。",
"alert.unexpected.title": "噢!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "如你想在下次路過這顯示,請按{combo}",
"bundle_column_error.body": "加載本組件出錯。",
"bundle_column_error.retry": "重試",

@ -43,6 +43,13 @@
"account_gallery.none": "No media to show.",
"alert.unexpected.message": "發生了非預期的錯誤。",
"alert.unexpected.title": "哎呀!",
"audio.close": "Close audio",
"audio.expand": "Expand audio",
"audio.hide": "Hide audio",
"audio.mute": "Mute",
"audio.pause": "Pause",
"audio.play": "Play",
"audio.unmute": "Unmute",
"boost_modal.combo": "下次您可以按 {combo} 跳過",
"bundle_column_error.body": "載入此元件時發生錯誤。",
"bundle_column_error.retry": "重試",

@ -16,7 +16,7 @@ const mapStateToProps = state => {
const me = state.get('me');
return {
account: state.getIn(['accounts', me]),
hasPatron: state.getIn(['soapbox', 'extensions', 'patron']),
hasPatron: state.getIn(['soapbox', 'extensions', 'patron', 'enabled']),
features: getFeatures(state.get('instance')),
};
};

@ -14,6 +14,8 @@ describe('media_attachments reducer', () => {
'.mp4',
'.m4v',
'.mov',
'.mp3',
'.ogg',
'image/jpeg',
'image/png',
'image/gif',
@ -21,6 +23,9 @@ describe('media_attachments reducer', () => {
'video/webm',
'video/mp4',
'video/quicktime',
'audio/mp3',
'audio/mpeg',
'audio/ogg',
]),
}));
});

@ -16,6 +16,8 @@ const initialState = ImmutableMap({
'.mp4',
'.m4v',
'.mov',
'.mp3',
'.ogg',
'image/jpeg',
'image/png',
'image/gif',
@ -23,6 +25,9 @@ const initialState = ImmutableMap({
'video/webm',
'video/mp4',
'video/quicktime',
'audio/mp3',
'audio/mpeg',
'audio/ogg',
]),
});

@ -399,6 +399,10 @@ a .account__avatar {
bottom: 0;
right: 0;
z-index: 1;
&.still-image {
position: absolute;
}
}
}

@ -69,3 +69,4 @@
@import 'components/media-spoiler';
@import 'components/error-boundary';
@import 'components/video-player';
@import 'components/audio-player';

@ -0,0 +1,397 @@
.audio-error-cover {
align-items: center;
background: var(--background-color);
color: var(--primary-text-color);
cursor: pointer;
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
margin-top: 8px;
position: relative;
text-align: center;
z-index: 100;
}
.status__audio-player {
background: var(--background-color);
box-sizing: border-box;
cursor: default; /* May not be needed */
margin-top: 8px;
overflow: hidden;
position: relative;
}
.status__audio-player-audio {
height: 100%;
object-fit: cover;
position: relative;
top: 50%;
transform: translateY(-50%);
width: 100%;
z-index: 1;
}
.status__audio-player-expand,
.status__audio-player-mute {
color: var(--primary-text-color);
opacity: 0.8;
position: absolute;
right: 4px;
text-shadow: 0 1px 1px $base-shadow-color, 1px 0 1px $base-shadow-color;
}
.status__audio-player-expand {
bottom: 4px;
z-index: 100;
}
.status__audio-player-mute {
top: 4px;
z-index: 5;
}
.detailed,
.fullscreen {
.audio-player__volume__current,
.audio-player__volume::before {
bottom: 27px;
}
.audio-player__volume__handle {
bottom: 23px;
}
}
.audio-player {
overflow: hidden;
position: relative;
background: $base-shadow-color;
max-width: 100%;
border-radius: 4px;
height: 57px;
&.warning_visible {
height: 92px;
}
&:focus {
outline: 0;
}
audio {
max-width: 100vw;
max-height: 80vh;
min-height: 120px;
object-fit: contain;
z-index: 1;
}
&.fullscreen {
width: 100% !important;
height: 100% !important;
margin: 0;
audio {
max-width: 100% !important;
max-height: 100% !important;
width: 100% !important;
height: 100% !important;
}
}
&.inline {
audio {
object-fit: contain;
position: relative;
}
}
&__controls {
position: absolute;
z-index: 2;
bottom: 0;
left: 0;
right: 0;
box-sizing: border-box;
background: linear-gradient(0deg, rgba($base-shadow-color, 0.85) 0, rgba($base-shadow-color, 0.45) 60%, transparent);
padding: 0 15px;
opacity: 1;
transition: opacity .1s ease;
}
&.inactive {
min-height: 57px;
audio,
.audio-player__controls {
visibility: hidden;
}
}
&__spoiler {
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 4;
border: 0;
background: var(--background-color);
color: var(--primary-text-color--faint);
transition: none;
pointer-events: auto;
&:hover,
&:active,
&:focus {
color: var(--primary-text-color);
}
&__title {
display: block;
font-size: 14px;
}
&__subtitle {
display: block;
font-size: 11px;
font-weight: 500;
}
}
&__buttons-bar {
display: flex;
justify-content: space-between;
padding-bottom: 10px;
}
&__spoiler-warning {
font-size: 16px;
white-space: nowrap;
overlow: hidden;
text-overflow: ellipsis;
background: hsl( var(--brand-color_h), var(--brand-color_s), 20% );
padding: 5px;
&__label {
color: white;
}
button {
background: transparent;
font-size: 16px;
border: 0;
color: rgba(#ffffff, 0.75);
position: absolute;
right: 0;
padding-right: 5px;
i {
margin-right: 0;
}
&:active,
&:hover,
&:focus {
color: #ffffff;
}
}
}
&__buttons {
font-size: 16px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.left {
button {
padding-left: 0;
}
}
button {
background: transparent;
padding: 2px 10px;
font-size: 16px;
border: 0;
color: rgba(#ffffff, 0.75);
&:active,
&:hover,
&:focus {
color: #ffffff;
}
}
}
&__time-sep,
&__time-total,
&__time-current {
font-size: 14px;
font-weight: 500;
}
&__time-current {
color: #ffffff;
margin-left: 60px;
}
&__time-sep {
display: inline-block;
margin: 0 6px;
}
&__time-sep,
&__time-total {
color: #ffffff;
}
&__volume {
cursor: pointer;
height: 24px;
display: inline;
&::before {
content: "";
width: 50px;
background: rgba(#ffffff, 0.35);
border-radius: 4px;
display: block;
position: absolute;
height: 4px;
left: 85px;
bottom: 20px;
}
&__current {
display: block;
position: absolute;
height: 4px;
border-radius: 4px;
left: 85px;
bottom: 20px;
background: var(--brand-color);
}
&__handle {
position: absolute;
z-index: 3;
border-radius: 50%;
width: 12px;
height: 12px;
bottom: 16px;
left: 70px;
transition: opacity .1s ease;
background: var(--brand-color);
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
pointer-events: none;
}
}
&__link {
padding: 2px 10px;
a {
text-decoration: none;
font-size: 14px;
font-weight: 500;
color: #ffffff;
&:hover,
&:active,
&:focus {
text-decoration: underline;
}
}
}
&__seek {
cursor: pointer;
height: 24px;
position: relative;
&::before {
content: "";
width: 100%;
background: rgba(#ffffff, 0.35);
border-radius: 4px;
display: block;
position: absolute;
height: 4px;
top: 10px;
}
&__progress,
&__buffer {
display: block;
position: absolute;
height: 4px;
border-radius: 4px;
top: 10px;
background: var(--brand-color);
}
&__buffer {
background: rgba(#ffffff, 0.2);
}
&__handle {
position: absolute;
z-index: 3;
opacity: 1;
border-radius: 50%;
width: 12px;
height: 12px;
top: 6px;
margin-left: -6px;
transition: opacity .1s ease;
background: var(--brand-color);
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
pointer-events: none;
}
&:hover {
.audio-player__seek__handle {
opacity: 1;
}
}
}
&.detailed {
width: 100vmin;
height: 80px;
.audio-player__buttons {
button {
padding-top: 10px;
padding-bottom: 10px;
}
}
}
}
.media-spoiler-audio {
background-size: cover;
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
margin-top: 8px;
position: relative;
border: 0;
display: block;
}
.media-spoiler-audio-play-icon {
border-radius: 100px;
color: var(--primary-text-color--faint);
font-size: 36px;
left: 50%;
padding: 5px;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
}

@ -284,7 +284,7 @@
.compose-form__upload-thumbnail {
border-radius: 4px;
background-position: center;
background-size: cover;
background-size: contain;
background-repeat: no-repeat;
height: 140px;
width: 100%;

@ -38,7 +38,8 @@
}
}
.video-player {
.video-player,
.audio-player {
margin-top: 8px;
}
}

@ -16,6 +16,14 @@
position: relative;
border-radius: 4px;
overflow: hidden;
&__icons {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 100px;
}
}
.media-gallery__item-thumbnail {
@ -31,6 +39,10 @@
.still-image {
height: 100%;
width: 100%;
img {
object-fit: contain;
}
}
.still-image--play-on-hover::before {
@ -108,7 +120,8 @@
position: absolute;
}
.media-gallery__gifv__label {
.media-gallery__gifv__label,
.media-gallery__file-extension__label {
display: block;
position: absolute;
color: var(--primary-text-color);

@ -38,7 +38,8 @@
overflow-y: hidden;
}
.video-modal {
.video-modal,
.audio-modal {
max-width: 100vw;
max-height: 100vh;
position: relative;
@ -49,12 +50,16 @@
height: 100%;
position: relative;
.audio-player.detailed,
.extended-video-player {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.extended-video-player {
width: 100%;
height: 100%;
video {
max-width: $media-modal-media-max-width;

@ -78,7 +78,8 @@
opacity: 1;
animation: fade 150ms linear;
.video-player {
.video-player,
.audio-player {
margin-top: 8px;
}
@ -176,7 +177,8 @@
white-space: normal;
}
.video-player {
.video-player,
.audio-player {
margin-top: 8px;
max-width: 250px;
}
@ -453,7 +455,8 @@ a.status-card {
margin: 0;
}
.status-card-video {
.status-card-video,
.status-card-audio {
iframe {
width: 100%;
height: 100%;

@ -11,6 +11,10 @@
width: 100%;
background: var(--brand-color--faint);
.still-image {
height: 100%;
}
img {
display: block;
height: 100%;

@ -1,6 +1,8 @@
# Customizing Soapbox
If you haven't already, [install Soapbox](https://soapbox.pub/).
If you haven't already, [install Soapbox](https://soapbox.pub/). But before you install soapbox, you should consider how Soapbox is installed, by default.
Soapbox, by default, is installed to replace the default Pleroma front end. By extension, the Pleroma Masto front end continues to be available at the `/web` sub-URL, which you can reference, if you'd like, in the `promoPanel` section of `soapbox.json`
There are two main places Soapbox gets its configuration:
@ -97,3 +99,9 @@ Simply rename `about.example` to `about`, or create your own.
The `soapbox.json` file navlinks section's default URL values are pointing to the above file location, when the `about.example` folder is renamed to `about`
These four template files have placeholders in them, e.g. "Your_Instance", that should be edited to match your Soapbox instance configuration, and will be meaningless to your users until you edit them.
## Alternate Soapbox URL Root Location
If you want to install Soapbox at an alternate URL, allowing you to potentially run more than 2 front ends on a Pleroma server, you can consider deploying the Nginx config created by @a1batross, available [here](https://git.mentality.rip/a1batross/soapbox-nginx-config/src/branch/master/soapbox.nginx)
Tech support is limited for this level of customization

@ -12,9 +12,6 @@
"url": "https://blog.example.com"
}]
},
"extensions": {
"patron": false
},
"defaultSettings": {
"autoPlayGif": false,
"themeMode": "light"

@ -9,7 +9,6 @@ const { settings, output } = require('./configuration');
const watchOptions = {};
const backendUrl = process.env.BACKEND_URL || 'http://localhost:4000';
const patronUrl = process.env.PATRON_URL || 'http://localhost:5000';
const secureProxy = !(process.env.PROXY_HTTPS_INSECURE === 'true');
const backendEndpoints = [
@ -22,7 +21,6 @@ const backendEndpoints = [
'/.well-known/webfinger',
'/static',
'/emoji',
'/patron',
];
const makeProxyConfig = () => {
@ -33,10 +31,6 @@ const makeProxyConfig = () => {
secure: secureProxy,
};
});
proxyConfig['/patron'] = {
target: patronUrl,
secure: secureProxy,
};
return proxyConfig;
};

Loading…
Cancel
Save