diff options
66 files changed, 519 insertions, 103 deletions
diff --git a/.env.production.sample b/.env.production.sample index 059b314b7..0c158b06e 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -81,6 +81,10 @@ SMTP_FROM_ADDRESS=notifications@example.com # PAPERCLIP_ROOT_URL=/system # Optional asset host for multi-server setups +# The asset host must allow cross origin request from WEB_DOMAIN or LOCAL_DOMAIN +# if WEB_DOMAIN is not set. For example, the server may have the +# following header field: +# Access-Control-Allow-Origin: https://example.com/ # CDN_HOST=https://assets.example.com # S3 (optional) diff --git a/Gemfile b/Gemfile index 5033e290d..9ab7e046c 100644 --- a/Gemfile +++ b/Gemfile @@ -26,7 +26,7 @@ gem 'active_model_serializers', '~> 0.10' gem 'addressable', '~> 2.5' gem 'bootsnap' gem 'browser' -gem 'charlock_holmes', '~> 0.7.5' +gem 'charlock_holmes', '~> 0.7.6' gem 'iso-639' gem 'chewy', '~> 5.0' gem 'cld3', '~> 3.2.0' diff --git a/Gemfile.lock b/Gemfile.lock index 72f706295..be623923a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -113,7 +113,7 @@ GEM xpath (~> 2.0) case_transform (0.2) activesupport - charlock_holmes (0.7.5) + charlock_holmes (0.7.6) chewy (5.0.0) activesupport (>= 4.0) elasticsearch (>= 2.0.0) @@ -635,7 +635,7 @@ DEPENDENCIES capistrano-rbenv (~> 2.1) capistrano-yarn (~> 2.0) capybara (~> 2.15) - charlock_holmes (~> 0.7.5) + charlock_holmes (~> 0.7.6) chewy (~> 5.0) cld3 (~> 3.2.0) climate_control (~> 0.2) diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 5e7cdd270..2138f9426 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -15,6 +15,7 @@ export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS'; export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL'; export const COMPOSE_REPLY = 'COMPOSE_REPLY'; export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL'; +export const COMPOSE_DIRECT = 'COMPOSE_DIRECT'; export const COMPOSE_MENTION = 'COMPOSE_MENTION'; export const COMPOSE_RESET = 'COMPOSE_RESET'; export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST'; @@ -91,6 +92,19 @@ export function mentionCompose(account, router) { }; }; +export function directCompose(account, router) { + return (dispatch, getState) => { + dispatch({ + type: COMPOSE_DIRECT, + account: account, + }); + + if (!getState().getIn(['compose', 'mounted'])) { + router.push('/statuses/new'); + } + }; +}; + export function submitCompose() { return function (dispatch, getState) { const status = getState().getIn(['compose', 'text'], ''); diff --git a/app/javascript/mastodon/actions/domain_blocks.js b/app/javascript/mastodon/actions/domain_blocks.js index 44363697a..47e2df76b 100644 --- a/app/javascript/mastodon/actions/domain_blocks.js +++ b/app/javascript/mastodon/actions/domain_blocks.js @@ -12,12 +12,18 @@ export const DOMAIN_BLOCKS_FETCH_REQUEST = 'DOMAIN_BLOCKS_FETCH_REQUEST'; export const DOMAIN_BLOCKS_FETCH_SUCCESS = 'DOMAIN_BLOCKS_FETCH_SUCCESS'; export const DOMAIN_BLOCKS_FETCH_FAIL = 'DOMAIN_BLOCKS_FETCH_FAIL'; -export function blockDomain(domain, accountId) { +export const DOMAIN_BLOCKS_EXPAND_REQUEST = 'DOMAIN_BLOCKS_EXPAND_REQUEST'; +export const DOMAIN_BLOCKS_EXPAND_SUCCESS = 'DOMAIN_BLOCKS_EXPAND_SUCCESS'; +export const DOMAIN_BLOCKS_EXPAND_FAIL = 'DOMAIN_BLOCKS_EXPAND_FAIL'; + +export function blockDomain(domain) { return (dispatch, getState) => { dispatch(blockDomainRequest(domain)); api(getState).post('/api/v1/domain_blocks', { domain }).then(() => { - dispatch(blockDomainSuccess(domain, accountId)); + const at_domain = '@' + domain; + const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id')); + dispatch(blockDomainSuccess(domain, accounts)); }).catch(err => { dispatch(blockDomainFail(domain, err)); }); @@ -31,11 +37,11 @@ export function blockDomainRequest(domain) { }; }; -export function blockDomainSuccess(domain, accountId) { +export function blockDomainSuccess(domain, accounts) { return { type: DOMAIN_BLOCK_SUCCESS, domain, - accountId, + accounts, }; }; @@ -47,12 +53,14 @@ export function blockDomainFail(domain, error) { }; }; -export function unblockDomain(domain, accountId) { +export function unblockDomain(domain) { return (dispatch, getState) => { dispatch(unblockDomainRequest(domain)); api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(() => { - dispatch(unblockDomainSuccess(domain, accountId)); + const at_domain = '@' + domain; + const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id')); + dispatch(unblockDomainSuccess(domain, accounts)); }).catch(err => { dispatch(unblockDomainFail(domain, err)); }); @@ -66,11 +74,11 @@ export function unblockDomainRequest(domain) { }; }; -export function unblockDomainSuccess(domain, accountId) { +export function unblockDomainSuccess(domain, accounts) { return { type: DOMAIN_UNBLOCK_SUCCESS, domain, - accountId, + accounts, }; }; @@ -86,7 +94,7 @@ export function fetchDomainBlocks() { return (dispatch, getState) => { dispatch(fetchDomainBlocksRequest()); - api(getState).get().then(response => { + api(getState).get('/api/v1/domain_blocks').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(fetchDomainBlocksSuccess(response.data, next ? next.uri : null)); }).catch(err => { @@ -115,3 +123,43 @@ export function fetchDomainBlocksFail(error) { error, }; }; + +export function expandDomainBlocks() { + return (dispatch, getState) => { + const url = getState().getIn(['domain_lists', 'blocks', 'next']); + + if (url === null) { + return; + } + + dispatch(expandDomainBlocksRequest()); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(expandDomainBlocksSuccess(response.data, next ? next.uri : null)); + }).catch(err => { + dispatch(expandDomainBlocksFail(err)); + }); + }; +}; + +export function expandDomainBlocksRequest() { + return { + type: DOMAIN_BLOCKS_EXPAND_REQUEST, + }; +}; + +export function expandDomainBlocksSuccess(domains, next) { + return { + type: DOMAIN_BLOCKS_EXPAND_SUCCESS, + domains, + next, + }; +}; + +export function expandDomainBlocksFail(error) { + return { + type: DOMAIN_BLOCKS_EXPAND_FAIL, + error, + }; +}; diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index 7267b85bd..da77afbe0 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -43,7 +43,9 @@ export function updateNotifications(notification, intlMessages, intlLocale) { const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true); dispatch(importFetchedAccount(notification.account)); - dispatch(importFetchedStatus(notification.status)); + if (notification.status) { + dispatch(importFetchedStatus(notification.status)); + } dispatch({ type: NOTIFICATIONS_UPDATE, diff --git a/app/javascript/mastodon/components/domain.js b/app/javascript/mastodon/components/domain.js new file mode 100644 index 000000000..f657cb8d2 --- /dev/null +++ b/app/javascript/mastodon/components/domain.js @@ -0,0 +1,42 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import IconButton from './icon_button'; +import { defineMessages, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +const messages = defineMessages({ + unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, +}); + +@injectIntl +export default class Account extends ImmutablePureComponent { + + static propTypes = { + domain: PropTypes.string, + onUnblockDomain: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + handleDomainUnblock = () => { + this.props.onUnblockDomain(this.props.domain); + } + + render () { + const { domain, intl } = this.props; + + return ( + <div className='domain'> + <div className='domain__wrapper'> + <span className='domain__domain-name'> + <strong>{domain}</strong> + </span> + + <div className='domain__buttons'> + <IconButton active icon='unlock-alt' title={intl.formatMessage(messages.unblockDomain, { domain })} onClick={this.handleDomainUnblock} /> + </div> + </div> + </div> + ); + } + +} diff --git a/app/javascript/mastodon/containers/domain_container.js b/app/javascript/mastodon/containers/domain_container.js new file mode 100644 index 000000000..52d5c1613 --- /dev/null +++ b/app/javascript/mastodon/containers/domain_container.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { blockDomain, unblockDomain } from '../actions/domain_blocks'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import Domain from '../components/domain'; +import { openModal } from '../actions/modal'; + +const messages = defineMessages({ + blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' }, +}); + +const makeMapStateToProps = () => { + const mapStateToProps = (state, { }) => ({ + }); + + return mapStateToProps; +}; + +const mapDispatchToProps = (dispatch, { intl }) => ({ + onBlockDomain (domain) { + dispatch(openModal('CONFIRM', { + message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.' values={{ domain: <strong>{domain}</strong> }} />, + confirm: intl.formatMessage(messages.blockDomainConfirm), + onConfirm: () => dispatch(blockDomain(domain)), + })); + }, + + onUnblockDomain (domain) { + dispatch(unblockDomain(domain)); + }, +}); + +export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Domain)); diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js index b538fa5fc..23dbf32bc 100644 --- a/app/javascript/mastodon/features/account/components/action_bar.js +++ b/app/javascript/mastodon/features/account/components/action_bar.js @@ -8,6 +8,7 @@ import { me } from '../../../initial_state'; const messages = defineMessages({ mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' }, + direct: { id: 'account.direct', defaultMessage: 'Direct message @{name}' }, edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, @@ -32,6 +33,7 @@ export default class ActionBar extends React.PureComponent { onFollow: PropTypes.func, onBlock: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, + onDirect: PropTypes.func.isRequired, onReblogToggle: PropTypes.func.isRequired, onReport: PropTypes.func.isRequired, onMute: PropTypes.func.isRequired, @@ -53,6 +55,7 @@ export default class ActionBar extends React.PureComponent { let extraInfo = ''; menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention }); + menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect }); if ('share' in navigator) { menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare }); diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js index 6b88a7a0c..1ae5126e6 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.js +++ b/app/javascript/mastodon/features/account_timeline/components/header.js @@ -16,6 +16,7 @@ export default class Header extends ImmutablePureComponent { onFollow: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, + onDirect: PropTypes.func.isRequired, onReblogToggle: PropTypes.func.isRequired, onReport: PropTypes.func.isRequired, onMute: PropTypes.func.isRequired, @@ -40,6 +41,10 @@ export default class Header extends ImmutablePureComponent { this.props.onMention(this.props.account, this.context.router.history); } + handleDirect = () => { + this.props.onDirect(this.props.account, this.context.router.history); + } + handleReport = () => { this.props.onReport(this.props.account); } @@ -57,7 +62,7 @@ export default class Header extends ImmutablePureComponent { if (!domain) return; - this.props.onBlockDomain(domain, this.props.account.get('id')); + this.props.onBlockDomain(domain); } handleUnblockDomain = () => { @@ -65,7 +70,7 @@ export default class Header extends ImmutablePureComponent { if (!domain) return; - this.props.onUnblockDomain(domain, this.props.account.get('id')); + this.props.onUnblockDomain(domain); } render () { @@ -89,6 +94,7 @@ export default class Header extends ImmutablePureComponent { account={account} onBlock={this.handleBlock} onMention={this.handleMention} + onDirect={this.handleDirect} onReblogToggle={this.handleReblogToggle} onReport={this.handleReport} onMute={this.handleMute} diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js index b5e0e9a3f..4d5308219 100644 --- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js +++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js @@ -9,7 +9,10 @@ import { unblockAccount, unmuteAccount, } from '../../../actions/accounts'; -import { mentionCompose } from '../../../actions/compose'; +import { + mentionCompose, + directCompose, +} from '../../../actions/compose'; import { initMuteModal } from '../../../actions/mutes'; import { initReport } from '../../../actions/reports'; import { openModal } from '../../../actions/modal'; @@ -67,6 +70,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ dispatch(mentionCompose(account, router)); }, + onDirect (account, router) { + dispatch(directCompose(account, router)); + }, + onReblogToggle (account) { if (account.getIn(['relationship', 'showing_reblogs'])) { dispatch(followAccount(account.get('id'), false)); @@ -87,16 +94,16 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, - onBlockDomain (domain, accountId) { + onBlockDomain (domain) { dispatch(openModal('CONFIRM', { message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.' values={{ domain: <strong>{domain}</strong> }} />, confirm: intl.formatMessage(messages.blockDomainConfirm), - onConfirm: () => dispatch(blockDomain(domain, accountId)), + onConfirm: () => dispatch(blockDomain(domain)), })); }, - onUnblockDomain (domain, accountId) { - dispatch(unblockDomain(domain, accountId)); + onUnblockDomain (domain) { + dispatch(unblockDomain(domain)); }, }); diff --git a/app/javascript/mastodon/features/compose/containers/warning_container.js b/app/javascript/mastodon/features/compose/containers/warning_container.js index 8ee8ea190..efaa02e9e 100644 --- a/app/javascript/mastodon/features/compose/containers/warning_container.js +++ b/app/javascript/mastodon/features/compose/containers/warning_container.js @@ -10,15 +10,19 @@ const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\w*[a-zA-Z·]\w*)/i; const mapStateToProps = state => ({ needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']), hashtagWarning: state.getIn(['compose', 'privacy']) !== 'public' && APPROX_HASHTAG_RE.test(state.getIn(['compose', 'text'])), + directMessageWarning: state.getIn(['compose', 'privacy']) === 'direct', }); -const WarningWrapper = ({ needsLockWarning, hashtagWarning }) => { +const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning }) => { if (needsLockWarning) { return <Warning message={<FormattedMessage id='compose_form.lock_disclaimer' defaultMessage='Your account is not {locked}. Anyone can follow you to view your follower-only posts.' values={{ locked: <a href='/settings/profile'><FormattedMessage id='compose_form.lock_disclaimer.lock' defaultMessage='locked' /></a> }} />} />; } if (hashtagWarning) { return <Warning message={<FormattedMessage id='compose_form.hashtag_warning' defaultMessage="This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag." />} />; } + if (directMessageWarning) { + return <Warning message={<FormattedMessage id='compose_form.direct_message_warning' defaultMessage='This toot will only be visible to all the mentioned users.' />} />; + } return null; }; @@ -26,6 +30,7 @@ const WarningWrapper = ({ needsLockWarning, hashtagWarning }) => { WarningWrapper.propTypes = { needsLockWarning: PropTypes.bool, hashtagWarning: PropTypes.bool, + directMessageWarning: PropTypes.bool, }; export default connect(mapStateToProps)(WarningWrapper); diff --git a/app/javascript/mastodon/features/domain_blocks/index.js b/app/javascript/mastodon/features/domain_blocks/index.js new file mode 100644 index 000000000..b17c47e91 --- /dev/null +++ b/app/javascript/mastodon/features/domain_blocks/index.js @@ -0,0 +1,66 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import LoadingIndicator from '../../components/loading_indicator'; +import Column from '../ui/components/column'; +import ColumnBackButtonSlim from '../../components/column_back_button_slim'; +import DomainContainer from '../../containers/domain_container'; +import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks'; +import { defineMessages, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { debounce } from 'lodash'; +import ScrollableList from '../../components/scrollable_list'; + +const messages = defineMessages({ + heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' }, + unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, +}); + +const mapStateToProps = state => ({ + domains: state.getIn(['domain_lists', 'blocks', 'items']), +}); + +@connect(mapStateToProps) +@injectIntl +export default class Blocks extends ImmutablePureComponent { + + static propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + domains: ImmutablePropTypes.list, + intl: PropTypes.object.isRequired, + }; + + componentWillMount () { + this.props.dispatch(fetchDomainBlocks()); + } + + handleLoadMore = debounce(() => { + this.props.dispatch(expandDomainBlocks()); + }, 300, { leading: true }); + + render () { + const { intl, domains } = this.props; + + if (!domains) { + return ( + <Column> + <LoadingIndicator /> + </Column> + ); + } + + return ( + <Column icon='ban' heading={intl.formatMessage(messages.heading)}> + <ColumnBackButtonSlim /> + <ScrollableList scrollKey='domain_blocks' onLoadMore={this.handleLoadMore}> + {domains.map(domain => + <DomainContainer key={domain} domain={domain} /> + )} + </ScrollableList> + </Column> + ); + } + +} diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index 3a875169e..1a71cff94 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -24,6 +24,7 @@ const messages = defineMessages({ sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, + domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' }, mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' }, pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' }, @@ -121,6 +122,7 @@ export default class GettingStarted extends ImmutablePureComponent { <ColumnLink icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' /> <ColumnLink icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' /> <ColumnLink icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' /> + <ColumnLink icon='ban' text={intl.formatMessage(messages.domain_blocks)} to='/domain_blocks' /> <ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' /> <ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' /> </div> diff --git a/app/javascript/mastodon/features/ui/components/zoomable_image.js b/app/javascript/mastodon/features/ui/components/zoomable_image.js index 0a0a4d41a..0cae0862d 100644 --- a/app/javascript/mastodon/features/ui/components/zoomable_image.js +++ b/app/javascript/mastodon/features/ui/components/zoomable_image.js @@ -1,16 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; +import Hammer from 'hammerjs'; const MIN_SCALE = 1; const MAX_SCALE = 4; - -const getMidpoint = (p1, p2) => ({ - x: (p1.clientX + p2.clientX) / 2, - y: (p1.clientY + p2.clientY) / 2, -}); - -const getDistance = (p1, p2) => - Math.sqrt(Math.pow(p1.clientX - p2.clientX, 2) + Math.pow(p1.clientY - p2.clientY, 2)); +const DOUBLE_TAP_SCALE = 2; const clamp = (min, max, value) => Math.min(max, Math.max(min, value)); @@ -37,83 +31,97 @@ export default class ZoomableImage extends React.PureComponent { removers = []; container = null; image = null; - lastTouchEndTime = 0; - lastDistance = 0; + lastScale = null; + zoomCenter = null; componentDidMount () { - let handler = this.handleTouchStart; - this.container.addEventListener('touchstart', handler); - this.removers.push(() => this.container.removeEventListener('touchstart', handler)); - handler = this.handleTouchMove; - // on Chrome 56+, touch event listeners will default to passive - // https://www.chromestatus.com/features/5093566007214080 - this.container.addEventListener('touchmove', handler, { passive: false }); - this.removers.push(() => this.container.removeEventListener('touchend', handler)); + // register pinch event handlers to the container + let hammer = new Hammer.Manager(this.container, { + // required to make container scrollable by touch + touchAction: 'pan-x pan-y', + }); + hammer.add(new Hammer.Pinch()); + hammer.on('pinchstart', this.handlePinchStart); + hammer.on('pinchmove', this.handlePinchMove); + this.removers.push(() => hammer.off('pinchstart pinchmove')); + + // register tap event handlers + hammer = new Hammer.Manager(this.image); + // NOTE the order of adding is also the order of gesture recognition + hammer.add(new Hammer.Tap({ event: 'doubletap', taps: 2 })); + hammer.add(new Hammer.Tap()); + // prevent the 'tap' event handler be fired on double tap + hammer.get('tap').requireFailure('doubletap'); + // NOTE 'tap' and 'doubletap' events are fired by touch and *mouse* + hammer.on('tap', this.handleTap); + hammer.on('doubletap', this.handleDoubleTap); + this.removers.push(() => hammer.off('tap doubletap')); } componentWillUnmount () { this.removeEventListeners(); } - removeEventListeners () { - this.removers.forEach(listeners => listeners()); - this.removers = []; - } + componentDidUpdate (prevProps, prevState) { + if (!this.zoomCenter) return; - handleTouchStart = e => { - if (e.touches.length !== 2) return; + const { x: cx, y: cy } = this.zoomCenter; + const { scale: prevScale } = prevState; + const { scale: nextScale } = this.state; + const { scrollLeft, scrollTop } = this.container; - this.lastDistance = getDistance(...e.touches); + // math memo: + // x = (scrollLeft + cx) / scrollWidth + // x' = (nextScrollLeft + cx) / nextScrollWidth + // scrollWidth = clientWidth * prevScale + // scrollWidth' = clientWidth * nextScale + // Solve x = x' for nextScrollLeft + const nextScrollLeft = (scrollLeft + cx) * nextScale / prevScale - cx; + const nextScrollTop = (scrollTop + cy) * nextScale / prevScale - cy; + + this.container.scrollLeft = nextScrollLeft; + this.container.scrollTop = nextScrollTop; } - handleTouchMove = e => { - const { scrollTop, scrollHeight, clientHeight } = this.container; - if (e.touches.length === 1 && scrollTop !== scrollHeight - clientHeight) { - // prevent propagating event to MediaModal - e.stopPropagation(); - return; - } - if (e.touches.length !== 2) return; + removeEventListeners () { + this.removers.forEach(listeners => listeners()); + this.removers = []; + } - e.preventDefault(); + handleClick = e => { + // prevent the click event propagated to parent e.stopPropagation(); - const distance = getDistance(...e.touches); - const midpoint = getMidpoint(...e.touches); - const scale = clamp(MIN_SCALE, MAX_SCALE, this.state.scale * distance / this.lastDistance); - - this.zoom(scale, midpoint); - - this.lastMidpoint = midpoint; - this.lastDistance = distance; + // the tap event handler is executed at the same time by touch and mouse, + // so we don't need to execute the onClick handler here } - zoom(nextScale, midpoint) { - const { scale } = this.state; - const { scrollLeft, scrollTop } = this.container; - - // math memo: - // x = (scrollLeft + midpoint.x) / scrollWidth - // x' = (nextScrollLeft + midpoint.x) / nextScrollWidth - // scrollWidth = clientWidth * scale - // scrollWidth' = clientWidth * nextScale - // Solve x = x' for nextScrollLeft - const nextScrollLeft = (scrollLeft + midpoint.x) * nextScale / scale - midpoint.x; - const nextScrollTop = (scrollTop + midpoint.y) * nextScale / scale - midpoint.y; + handlePinchStart = () => { + this.lastScale = this.state.scale; + } - this.setState({ scale: nextScale }, () => { - this.container.scrollLeft = nextScrollLeft; - this.container.scrollTop = nextScrollTop; - }); + handlePinchMove = e => { + const scale = clamp(MIN_SCALE, MAX_SCALE, this.lastScale * e.scale); + this.zoom(scale, e.center); } - handleClick = e => { - // don't propagate event to MediaModal - e.stopPropagation(); + handleTap = () => { const handler = this.props.onClick; if (handler) handler(); } + handleDoubleTap = e => { + if (this.state.scale === MIN_SCALE) + this.zoom(DOUBLE_TAP_SCALE, e.center); + else + this.zoom(MIN_SCALE, e.center); + } + + zoom (scale, center) { + this.zoomCenter = center; + this.setState({ scale }); + } + setContainerRef = c => { this.container = c; } @@ -126,6 +134,18 @@ export default class ZoomableImage extends React.PureComponent { const { alt, src } = this.props; const { scale } = this.state; const overflow = scale === 1 ? 'hidden' : 'scroll'; + const marginStyle = { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + transform: `scale(${scale})`, + transformOrigin: '0 0', + }; return ( <div @@ -133,17 +153,18 @@ export default class ZoomableImage extends React.PureComponent { ref={this.setContainerRef} style={{ overflow }} > - <img - role='presentation' - ref={this.setImageRef} - alt={alt} - src={src} - style={{ - transform: `scale(${scale})`, - transformOrigin: '0 0', - }} - onClick={this.handleClick} - /> + <div + className='zoomable-image__margin' + style={marginStyle} + > + <img + ref={this.setImageRef} + role='presentation' + alt={alt} + src={src} + onClick={this.handleClick} + /> + </div> </div> ); } diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index b6a2a6cfc..8894eb4e6 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -37,6 +37,7 @@ import { FavouritedStatuses, ListTimeline, Blocks, + DomainBlocks, Mutes, PinnedStatuses, Lists, @@ -158,6 +159,7 @@ class SwitchingColumnsArea extends React.PureComponent { <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} /> <WrappedRoute path='/blocks' component={Blocks} content={children} /> + <WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} /> <WrappedRoute path='/mutes' component={Mutes} content={children} /> <WrappedRoute path='/lists' component={Lists} content={children} /> diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index d6586680b..19957208f 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -90,6 +90,10 @@ export function Blocks () { return import(/* webpackChunkName: "features/blocks" */'../../blocks'); } +export function DomainBlocks () { + return import(/* webpackChunkName: "features/domain_blocks" */'../../domain_blocks'); +} + export function Mutes () { return import(/* webpackChunkName: "features/mutes" */'../../mutes'); } diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 3d9620793..f9af062d0 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -2,6 +2,7 @@ "account.block": "حظر @{name}", "account.block_domain": "إخفاء كل شيئ قادم من إسم النطاق {domain}", "account.blocked": "محظور", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "قد لا تعكس المعلومات أدناه الملف الشخصي الكامل للمستخدم.", "account.domain_blocked": "النطاق مخفي", "account.edit_profile": "تعديل الملف الشخصي", @@ -56,6 +57,7 @@ "column_header.unpin": "فك التدبيس", "column_subheading.navigation": "التصفح", "column_subheading.settings": "الإعدادات", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "هذا التبويق لن يُدرَج تحت أي وسم كان بما أنه غير مُدرَج. لا يُسمح بالبحث إلّا عن التبويقات العمومية عن طريق الوسوم.", "compose_form.lock_disclaimer": "حسابك ليس {locked}. يمكن لأي شخص متابعتك و عرض المنشورات.", "compose_form.lock_disclaimer.lock": "مقفل", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 39eb05f2a..58795ca37 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -2,6 +2,7 @@ "account.block": "Блокирай", "account.block_domain": "Hide everything from {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Редактирай профила си", @@ -56,6 +57,7 @@ "column_header.unpin": "Unpin", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Settings", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", "compose_form.lock_disclaimer.lock": "locked", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 33545d86f..b0ce34c6b 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -2,6 +2,7 @@ "account.block": "Bloca @{name}", "account.block_domain": "Amaga-ho tot de {domain}", "account.blocked": "Bloquejat", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.", "account.domain_blocked": "Domini ocult", "account.edit_profile": "Edita el perfil", @@ -56,6 +57,7 @@ "column_header.unpin": "No fixis", "column_subheading.navigation": "Navegació", "column_subheading.settings": "Configuració", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Aquest toot no es mostrarà en cap etiqueta ja que no està llistat. Només els toots públics poden ser cercats per etiqueta.", "compose_form.lock_disclaimer": "El teu compte no està bloquejat {locked}. Tothom pot seguir-te i veure els teus missatges a seguidors.", "compose_form.lock_disclaimer.lock": "blocat", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 7bdb6a3c6..eb0c5056a 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -2,6 +2,7 @@ "account.block": "@{name} blocken", "account.block_domain": "Alles von {domain} verstecken", "account.blocked": "Blockiert", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Das Profil wird möglicherweise unvollständig wiedergegeben.", "account.domain_blocked": "Domain versteckt", "account.edit_profile": "Profil bearbeiten", @@ -56,6 +57,7 @@ "column_header.unpin": "Lösen", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Einstellungen", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Dieser Beitrag wird nicht unter einen dieser Hashtags sichtbar sein, solange er ungelistet ist. Bei einer Suche kann er nicht gefunden werden.", "compose_form.lock_disclaimer": "Dein Profil ist nicht {locked}. Wer dir folgen will, kann das jederzeit tun und dann auch deine privaten Beiträge sehen.", "compose_form.lock_disclaimer.lock": "gesperrt", diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 2120009ac..ac02c6af3 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -381,6 +381,10 @@ "id": "account.mention" }, { + "defaultMessage": "Direct message @{name}", + "id": "account.direct" + }, + { "defaultMessage": "Edit profile", "id": "account.edit_profile" }, @@ -804,6 +808,10 @@ { "defaultMessage": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "id": "compose_form.hashtag_warning" + }, + { + "defaultMessage": "This toot will only be visible to all the mentioned users.", + "id": "compose_form.direct_message_warning" } ], "path": "app/javascript/mastodon/features/compose/containers/warning_container.json" diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index d2133b1f6..da75f5fe2 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -2,6 +2,7 @@ "account.block": "Block @{name}", "account.block_domain": "Hide everything from {domain}", "account.blocked": "Blocked", + "account.direct": "Direct message @{name}", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Edit profile", @@ -60,6 +61,7 @@ "column_subheading.lists": "Lists", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Settings", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", "compose_form.lock_disclaimer.lock": "locked", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 35d9edf2b..9b00edb00 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -2,6 +2,7 @@ "account.block": "Bloki @{name}", "account.block_domain": "Kaŝi ĉion de {domain}", "account.blocked": "Blokita", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Subaj informoj povas reflekti la profilon de la uzanto nekomplete.", "account.domain_blocked": "Domajno kaŝita", "account.edit_profile": "Redakti profilon", @@ -56,6 +57,7 @@ "column_header.unpin": "Depingli", "column_subheading.navigation": "Navigado", "column_subheading.settings": "Agordado", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Ĉi tiu mesaĝo ne estos listigita per ajna kradvorto. Nur publikaj mesaĝoj estas serĉeblaj per kradvortoj.", "compose_form.lock_disclaimer": "Via konta ne estas {locked}. Iu ajn povas sekvi vin por vidi viajn mesaĝojn nur por sekvantoj.", "compose_form.lock_disclaimer.lock": "ŝlosita", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index e69938b0f..9f03b31c1 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -2,6 +2,7 @@ "account.block": "Bloquear", "account.block_domain": "Ocultar todo de {domain}", "account.blocked": "Bloqueado", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "La siguiente información del usuario puede estar incompleta.", "account.domain_blocked": "Dominio oculto", "account.edit_profile": "Editar perfil", @@ -56,6 +57,7 @@ "column_header.unpin": "Dejar de fijar", "column_subheading.navigation": "Navegación", "column_subheading.settings": "Ajustes", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Este toot no se mostrará bajo hashtags porque no es público. Sólo los toots públicos se pueden buscar por hashtag.", "compose_form.lock_disclaimer": "Tu cuenta no está bloqueada. Todos pueden seguirte para ver tus toots solo para seguidores.", "compose_form.lock_disclaimer.lock": "bloqueado", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index c9695d0a4..9421746b1 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -2,6 +2,7 @@ "account.block": "مسدودسازی @{name}", "account.block_domain": "پنهانسازی همه چیز از سرور {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "اطلاعات زیر ممکن است نمایهٔ این کاربر را به تمامی نشان ندهد.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "ویرایش نمایه", @@ -56,6 +57,7 @@ "column_header.unpin": "رهاکردن", "column_subheading.navigation": "گشت و گذار", "column_subheading.settings": "تنظیمات", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "حساب شما {locked} نیست. هر کسی میتواند پیگیر شما شود و نوشتههای ویژهٔ پیگیران شما را ببیند.", "compose_form.lock_disclaimer.lock": "قفل", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index cbdffec10..fce441df4 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -2,6 +2,7 @@ "account.block": "Estä @{name}", "account.block_domain": "Piilota kaikki sisältö verkkotunnuksesta {domain}", "account.blocked": "Estetty", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Alla olevat käyttäjän profiilitiedot saattavat olla epätäydellisiä.", "account.domain_blocked": "Verkko-osoite piilotettu", "account.edit_profile": "Muokkaa", @@ -56,6 +57,7 @@ "column_header.unpin": "Poista kiinnitys", "column_subheading.navigation": "Navigaatio", "column_subheading.settings": "Asetukset", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Tämä töötti ei tule näkymään hashtag-hauissa, koska se ei näy julkisilla aikajanoilla. Vain julkisia tööttejä voi hakea hashtageilla.", "compose_form.lock_disclaimer": "Tilisi ei ole {locked}. Kuka tahansa voi seurata tiliäsi ja nähdä vain seuraajille -postauksesi.", "compose_form.lock_disclaimer.lock": "lukittu", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 8c56a7558..6eb34e644 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -2,6 +2,7 @@ "account.block": "Bloquer @{name}", "account.block_domain": "Tout masquer venant de {domain}", "account.blocked": "Bloqué", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Les données ci-dessous peuvent ne pas refléter ce profil dans sa totalité.", "account.domain_blocked": "Domaine caché", "account.edit_profile": "Modifier le profil", @@ -56,6 +57,7 @@ "column_header.unpin": "Retirer", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Paramètres", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Ce pouet ne sera pas listé dans les recherches par hashtag car sa visibilité est réglée sur \"non-listé\". Seuls les pouets avec une visibilité \"publique\" peuvent être recherchés par hashtag.", "compose_form.lock_disclaimer": "Votre compte n’est pas {locked}. Tout le monde peut vous suivre et voir vos pouets privés.", "compose_form.lock_disclaimer.lock": "verrouillé", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index c5cedd60a..a0823b93f 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -2,6 +2,7 @@ "account.block": "Bloquear @{name}", "account.block_domain": "Ocultar calquer contido de {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "A información inferior podería mostrar un perfil incompleto da usuaria.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Editar perfil", @@ -56,6 +57,7 @@ "column_header.unpin": "Soltar", "column_subheading.navigation": "Navegación", "column_subheading.settings": "Axustes", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Esta mensaxe non será listada baixo ningunha etiqueta xa que está marcada como non listada. Só os toots públicos poden buscarse por etiquetas.", "compose_form.lock_disclaimer": "A súa conta non está {locked}. Calquera pode seguila para ver as súas mensaxes só-para-seguidoras.", "compose_form.lock_disclaimer.lock": "bloqueado", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index fe6f9bbb1..0e2ee8da4 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -2,6 +2,7 @@ "account.block": "חסימת @{name}", "account.block_domain": "להסתיר הכל מהקהילה {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "המידע להלן עשוי להיות לא עדכני או לא שלם.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "עריכת פרופיל", @@ -56,6 +57,7 @@ "column_header.unpin": "שחרור קיבוע", "column_subheading.navigation": "ניווט", "column_subheading.settings": "אפשרויות", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "חשבונך אינו {locked}. כל אחד יוכל לעקוב אחריך כדי לקרוא את הודעותיך המיועדות לעוקבים בלבד.", "compose_form.lock_disclaimer.lock": "נעול", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index 11cd1bff2..1e8ce8e29 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -2,6 +2,7 @@ "account.block": "Blokiraj @{name}", "account.block_domain": "Sakrij sve sa {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Ovaj korisnik je sa druge instance. Ovaj broj bi mogao biti veći.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Uredi profil", @@ -56,6 +57,7 @@ "column_header.unpin": "Unpin", "column_subheading.navigation": "Navigacija", "column_subheading.settings": "Postavke", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Tvoj račun nije {locked}. Svatko te može slijediti kako bi vidio postove namijenjene samo tvojim sljedbenicima.", "compose_form.lock_disclaimer.lock": "zaključan", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 1ea65768a..deb17c6f4 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -2,6 +2,7 @@ "account.block": "@{name} letiltása", "account.block_domain": "Minden elrejtése innen: {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Az alul található információk hiányosan mutathatják be a felhasználót.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Profil szerkesztése", @@ -56,6 +57,7 @@ "column_header.unpin": "Kitűzés eltávolítása", "column_subheading.navigation": "Navigáció", "column_subheading.settings": "Beállítások", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Ezen tülkölés nem fog megjelenni semmilyen hashtag alatt mivel listázatlan. Csak a publikus tülkölések kereshetőek hashtag-el.", "compose_form.lock_disclaimer": "Az ön fiókja nincs {locked}. Bárki követni tud, hogy megtekintse a kizárt követőknek szánt üzeneteid.", "compose_form.lock_disclaimer.lock": "lezárva", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json index e9638bf96..ee2055397 100644 --- a/app/javascript/mastodon/locales/hy.json +++ b/app/javascript/mastodon/locales/hy.json @@ -2,6 +2,7 @@ "account.block": "Արգելափակել @{name}֊ին", "account.block_domain": "Թաքցնել ամենը հետեւյալ տիրույթից՝ {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Ներքոհիշյալը կարող է ոչ ամբողջությամբ արտացոլել օգտատիրոջ էջի տվյալները։", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Խմբագրել անձնական էջը", @@ -56,6 +57,7 @@ "column_header.unpin": "Հանել", "column_subheading.navigation": "Նավարկություն", "column_subheading.settings": "Կարգավորումներ", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Այս թութը չի հաշվառվի որեւէ պիտակի տակ, քանզի այն ծածուկ է։ Միայն հրապարակային թթերը հնարավոր է որոնել պիտակներով։", "compose_form.lock_disclaimer": "Քո հաշիվը {locked} չէ։ Յուրաքանչյուր ոք կարող է հետեւել քեզ եւ տեսնել միայն հետեւողների համար նախատեսված գրառումները։", "compose_form.lock_disclaimer.lock": "փակ", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index c8d8ebe76..cae3211ee 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -2,6 +2,7 @@ "account.block": "Blokir @{name}", "account.block_domain": "Sembunyikan segalanya dari {domain}", "account.blocked": "Terblokir", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Informasi di bawah mungkin tidak mencerminkan profil user secara lengkap.", "account.domain_blocked": "Domain disembunyikan", "account.edit_profile": "Ubah profil", @@ -56,6 +57,7 @@ "column_header.unpin": "Lepaskan", "column_subheading.navigation": "Navigasi", "column_subheading.settings": "Pengaturan", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Toot ini tidak akan ada dalam daftar tagar manapun karena telah di set sebagai tidak terdaftar. Hanya postingan publik yang bisa dicari dengan tagar.", "compose_form.lock_disclaimer": "Akun anda tidak {locked}. Semua orang dapat mengikuti anda untuk melihat postingan khusus untuk pengikut anda.", "compose_form.lock_disclaimer.lock": "terkunci", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index a2e9af8ef..121d745ca 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -2,6 +2,7 @@ "account.block": "Blokusar @{name}", "account.block_domain": "Hide everything from {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Modifikar profilo", @@ -56,6 +57,7 @@ "column_header.unpin": "Unpin", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Settings", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", "compose_form.lock_disclaimer.lock": "locked", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 40ea9b26d..5e57143a9 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -2,6 +2,7 @@ "account.block": "Blocca @{name}", "account.block_domain": "Hide everything from {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Modifica profilo", @@ -56,6 +57,7 @@ "column_header.unpin": "Unpin", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Settings", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", "compose_form.lock_disclaimer.lock": "locked", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 2e55af510..e6f2bbcff 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -2,6 +2,7 @@ "account.block": "@{name}さんをブロック", "account.block_domain": "{domain}全体を非表示", "account.blocked": "ブロック済み", + "account.direct": "@{name}さんにダイレクトメッセージ", "account.disclaimer_full": "以下の情報は不正確な可能性があります。", "account.domain_blocked": "ドメイン非表示中", "account.edit_profile": "プロフィールを編集", @@ -60,6 +61,7 @@ "column_subheading.lists": "リスト", "column_subheading.navigation": "ナビゲーション", "column_subheading.settings": "設定", + "compose_form.direct_message_warning": "このトゥートはメンションされた人だけが見ることができます。", "compose_form.hashtag_warning": "このトゥートは未収載なのでハッシュタグの一覧に表示されません。公開トゥートだけがハッシュタグで検索できます。", "compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。", "compose_form.lock_disclaimer.lock": "非公開", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index bde4397f3..fa15214c9 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -2,6 +2,7 @@ "account.block": "@{name}을 차단", "account.block_domain": "{domain} 전체를 숨김", "account.blocked": "차단 됨", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "여기 있는 정보는 유저의 프로파일을 정확히 반영하지 못 할 수도 있습니다.", "account.domain_blocked": "도메인 숨겨짐", "account.edit_profile": "프로필 편집", @@ -56,6 +57,7 @@ "column_header.unpin": "고정 해제", "column_subheading.navigation": "내비게이션", "column_subheading.settings": "설정", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "이 툿은 어떤 해시태그로도 검색 되지 않습니다. 전체공개로 게시 된 툿만이 해시태그로 검색 될 수 있습니다.", "compose_form.lock_disclaimer": "이 계정은 {locked}로 설정 되어 있지 않습니다. 누구나 이 계정을 팔로우 할 수 있으며, 팔로워 공개의 포스팅을 볼 수 있습니다.", "compose_form.lock_disclaimer.lock": "비공개", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 140be0dca..ff827991d 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -2,6 +2,7 @@ "account.block": "Blokkeer @{name}", "account.block_domain": "Negeer alles van {domain}", "account.blocked": "Geblokkeerd", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "De informatie hieronder kan mogelijk een incompleet beeld geven van dit gebruikersprofiel.", "account.domain_blocked": "Domein verborgen", "account.edit_profile": "Profiel bewerken", @@ -56,6 +57,7 @@ "column_header.unpin": "Losmaken", "column_subheading.navigation": "Navigatie", "column_subheading.settings": "Instellingen", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Deze toot valt niet onder een hashtag te bekijken, omdat deze niet op openbare tijdlijnen wordt getoond. Alleen openbare toots kunnen via hashtags gevonden worden.", "compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en toots zien die je alleen aan volgers hebt gericht.", "compose_form.lock_disclaimer.lock": "besloten", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 4d6ac133e..d3bc75708 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -2,6 +2,7 @@ "account.block": "Blokkér @{name}", "account.block_domain": "Skjul alt fra {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Informasjonen nedenfor kan gi et ufullstendig bilde av brukerens profil.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Rediger profil", @@ -56,6 +57,7 @@ "column_header.unpin": "Løsne", "column_subheading.navigation": "Navigasjon", "column_subheading.settings": "Innstillinger", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Denne tuten blir ikke listet under noen emneknagger da den er ulistet. Kun offentlige tuter kan søktes etter med emneknagg.", "compose_form.lock_disclaimer": "Din konto er ikke {locked}. Hvem som helst kan følge deg og se dine private poster.", "compose_form.lock_disclaimer.lock": "låst", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 24dfa9375..39ba31de3 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -2,6 +2,7 @@ "account.block": "Blocar @{name}", "account.block_domain": "Tot amagar del domeni {domain}", "account.blocked": "Blocat", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Aquelas informacions de perfil pòdon èsser incomplètas.", "account.domain_blocked": "Domeni amagat", "account.edit_profile": "Modificar lo perfil", @@ -56,6 +57,7 @@ "column_header.unpin": "Despenjar", "column_subheading.navigation": "Navigacion", "column_subheading.settings": "Paramètres", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Aqueste tut serà pas ligat a cap etiqueta estant qu’es pas listat. Òm pas cercar que los tuts publics per etiqueta.", "compose_form.lock_disclaimer": "Vòstre compte es pas {locked}. Tot lo mond pòt vos sègre e veire los estatuts reservats als seguidors.", "compose_form.lock_disclaimer.lock": "clavat", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index dcd2d12b3..fa25192e6 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -2,6 +2,7 @@ "account.block": "Blokuj @{name}", "account.block_domain": "Blokuj wszystko z {domain}", "account.blocked": "Zablokowany", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Poniższe informacje mogą nie odwzorowywać bezbłędnie profilu użytkownika.", "account.domain_blocked": "Ukryto domenę", "account.edit_profile": "Edytuj profil", @@ -60,6 +61,7 @@ "column_subheading.lists": "Listy", "column_subheading.navigation": "Nawigacja", "column_subheading.settings": "Ustawienia", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Ten wpis nie będzie widoczny pod podanymi hashtagami, ponieważ jest oznaczony jako niewidoczny. Tylko publiczne wpisy mogą zostać znalezione z użyciem hashtagów.", "compose_form.lock_disclaimer": "Twoje konto nie jest {locked}. Każdy, kto Cię śledzi, może wyświetlać Twoje wpisy przeznaczone tylko dla śledzących.", "compose_form.lock_disclaimer.lock": "zablokowane", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index dcaeaced9..3d42eedb3 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -2,6 +2,7 @@ "account.block": "Bloquear @{name}", "account.block_domain": "Esconder tudo de {domain}", "account.blocked": "Bloqueado", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de maneira incompleta.", "account.domain_blocked": "Domínio escondido", "account.edit_profile": "Editar perfil", @@ -56,6 +57,7 @@ "column_header.unpin": "Desafixar", "column_subheading.navigation": "Navegação", "column_subheading.settings": "Configurações", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Esse toot não será listado em nenhuma hashtag por ser não listado. Somente toots públicos podem ser pesquisados por hashtag.", "compose_form.lock_disclaimer": "A sua conta não está {locked}. Qualquer pessoa pode te seguir e visualizar postagens direcionadas a apenas seguidores.", "compose_form.lock_disclaimer.lock": "trancada", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 4725a82da..5c93614a9 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -2,6 +2,7 @@ "account.block": "Bloquear @{name}", "account.block_domain": "Esconder tudo do domínio {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de forma incompleta.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Editar perfil", @@ -56,6 +57,7 @@ "column_header.unpin": "Desafixar", "column_subheading.navigation": "Navegação", "column_subheading.settings": "Preferências", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Esta pulbicacção não será listada em nenhuma hashtag por ser não listada. Somente publicações públicas podem ser pesquisadas por hashtag.", "compose_form.lock_disclaimer": "A tua conta não está {locked}. Qualquer pessoa pode seguir-te e ver as publicações direcionadas apenas a seguidores.", "compose_form.lock_disclaimer.lock": "bloqueada", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 8e7d36659..7dffbb210 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -2,6 +2,7 @@ "account.block": "Блокировать", "account.block_domain": "Блокировать все с {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Нижеуказанная информация может не полностью отражать профиль пользователя.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Изменить профиль", @@ -56,6 +57,7 @@ "column_header.unpin": "Открепить", "column_subheading.navigation": "Навигация", "column_subheading.settings": "Настройки", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Этот пост не будет показывается в поиске по хэштегу, т.к. он непубличный. Только публичные посты можно найти в поиске по хэштегу.", "compose_form.lock_disclaimer": "Ваш аккаунт не {locked}. Любой человек может подписаться на Вас и просматривать посты для подписчиков.", "compose_form.lock_disclaimer.lock": "закрыт", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index e3b323943..0a248d261 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -2,6 +2,7 @@ "account.block": "Blokovať @{name}", "account.block_domain": "Ukryť všetko z {domain}", "account.blocked": "Blokovaný/á", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Inofrmácie nižšie nemusia byť úplným odrazom uživateľovho účtu.", "account.domain_blocked": "Doména ukrytá", "account.edit_profile": "Upraviť profil", @@ -56,6 +57,7 @@ "column_header.unpin": "Odopnúť", "column_subheading.navigation": "Navigácia", "column_subheading.settings": "Nastavenia", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Tento toot nebude zobrazený pod žiadným haštagom lebo nieje listovaný. Iba verejné tooty môžu byť nájdené podľa haštagu.", "compose_form.lock_disclaimer": "Váš účet nie je zamknutý. Ktokoľvek ťa môže nasledovať a vidieť tvoje správy pre sledujúcich.", "compose_form.lock_disclaimer.lock": "zamknutý", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index d38e8e3af..b9effce96 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -2,6 +2,7 @@ "account.block": "Blokiraj korisnika @{name}", "account.block_domain": "Sakrij sve sa domena {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Navedene informacije možda ne odslikavaju korisnički profil u potpunosti.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Izmeni profil", @@ -56,6 +57,7 @@ "column_header.unpin": "Otkači", "column_subheading.navigation": "Navigacija", "column_subheading.settings": "Postavke", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Vaš nalog nije {locked}. Svako može da Vas zaprati i da vidi objave namenjene samo Vašim pratiocima.", "compose_form.lock_disclaimer.lock": "zaključan", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 3be0c89ee..a6c5f220e 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -2,6 +2,7 @@ "account.block": "Блокирај корисника @{name}", "account.block_domain": "Сакриј све са домена {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Наведене информације можда не одсликавају кориснички профил у потпуности.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Измени профил", @@ -56,6 +57,7 @@ "column_header.unpin": "Откачи", "column_subheading.navigation": "Навигација", "column_subheading.settings": "Поставке", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Ваш налог није {locked}. Свако може да Вас запрати и да види објаве намењене само Вашим пратиоцима.", "compose_form.lock_disclaimer.lock": "закључан", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index a13ba9847..6dc3d7a98 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -2,6 +2,7 @@ "account.block": "Blockera @{name}", "account.block_domain": "Dölj allt från {domain}", "account.blocked": "Blockerad", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Informationen nedan kan spegla användarens profil ofullständigt.", "account.domain_blocked": "Domän gömd", "account.edit_profile": "Redigera profil", @@ -56,6 +57,7 @@ "column_header.unpin": "Ångra fäst", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Inställningar", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Denna toot kommer inte att listas under någon hashtag eftersom den är onoterad. Endast offentliga toots kan sökas med hashtag.", "compose_form.lock_disclaimer": "Ditt konto är inte {locked}. Vemsomhelst kan följa dig och även se dina inlägg skrivna för endast dina följare.", "compose_form.lock_disclaimer.lock": "låst", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 59ff10b46..4de354007 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -2,6 +2,7 @@ "account.block": "Block @{name}", "account.block_domain": "Hide everything from {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Edit profile", @@ -56,6 +57,7 @@ "column_header.unpin": "Unpin", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Settings", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", "compose_form.lock_disclaimer.lock": "locked", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index e83af319e..9d0affea4 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -2,6 +2,7 @@ "account.block": "Engelle @{name}", "account.block_domain": "Hide everything from {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Profili düzenle", @@ -56,6 +57,7 @@ "column_header.unpin": "Unpin", "column_subheading.navigation": "Navigasyon", "column_subheading.settings": "Ayarlar", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Hesabınız {locked} değil. Sadece takipçilerle paylaştığınız gönderileri görebilmek için sizi herhangi bir kullanıcı takip edebilir.", "compose_form.lock_disclaimer.lock": "kilitli", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index accc2d027..c49d3c7ae 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -2,6 +2,7 @@ "account.block": "Заблокувати", "account.block_domain": "Заглушити {domain}", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Налаштування профілю", @@ -56,6 +57,7 @@ "column_header.unpin": "Unpin", "column_subheading.navigation": "Навігація", "column_subheading.settings": "Налаштування", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "Ваш акаунт не {locked}. Кожен може підписатися на Вас та бачити Ваші приватні пости.", "compose_form.lock_disclaimer.lock": "приватний", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index b9a912fb0..e95cf81f4 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -2,6 +2,7 @@ "account.block": "屏蔽 @{name}", "account.block_domain": "隐藏来自 {domain} 的内容", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "此处显示的信息可能不是全部内容。", "account.domain_blocked": "Domain hidden", "account.edit_profile": "修改个人资料", @@ -56,6 +57,7 @@ "column_header.unpin": "取消固定", "column_subheading.navigation": "导航", "column_subheading.settings": "设置", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "这条嘟文被设置为“不公开”,因此它不会出现在任何话题标签的列表下。只有公开的嘟文才能通过话题标签进行搜索。", "compose_form.lock_disclaimer": "你的帐户没有{locked}。任何人都可以在关注你后立即查看仅关注者可见的嘟文。", "compose_form.lock_disclaimer.lock": "开启保护", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index 91b1d00af..1801c838d 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -2,6 +2,7 @@ "account.block": "封鎖 @{name}", "account.block_domain": "隱藏來自 {domain} 的一切文章", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "下列資料不一定完整。", "account.domain_blocked": "Domain hidden", "account.edit_profile": "修改個人資料", @@ -56,6 +57,7 @@ "column_header.unpin": "取下", "column_subheading.navigation": "瀏覽", "column_subheading.settings": "設定", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "你的用戶狀態為「{locked}」,任何人都能立即關注你,然後看到「只有關注者能看」的文章。", "compose_form.lock_disclaimer.lock": "公共", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 7e845c650..acbe6eb8e 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -2,6 +2,7 @@ "account.block": "封鎖 @{name}", "account.block_domain": "隱藏來自 {domain} 的一切貼文", "account.blocked": "Blocked", + "account.direct": "Direct Message @{name}", "account.disclaimer_full": "下列資料不一定完整。", "account.domain_blocked": "Domain hidden", "account.edit_profile": "編輯用者資訊", @@ -56,6 +57,7 @@ "column_header.unpin": "取下", "column_subheading.navigation": "瀏覽", "column_subheading.settings": "設定", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", "compose_form.lock_disclaimer": "你的帳號沒有{locked}。任何人都可以關注你,看到發給關注者的貼文。", "compose_form.lock_disclaimer.lock": "上鎖", diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 5eadebb81..a48c46941 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -4,6 +4,7 @@ import { COMPOSE_CHANGE, COMPOSE_REPLY, COMPOSE_REPLY_CANCEL, + COMPOSE_DIRECT, COMPOSE_MENTION, COMPOSE_SUBMIT_REQUEST, COMPOSE_SUBMIT_SUCCESS, @@ -262,6 +263,12 @@ export default function compose(state = initialState, action) { .update('text', text => `${text}@${action.account.get('acct')} `) .set('focusDate', new Date()) .set('idempotencyKey', uuid()); + case COMPOSE_DIRECT: + return state + .update('text', text => `${text}@${action.account.get('acct')} `) + .set('privacy', 'direct') + .set('focusDate', new Date()) + .set('idempotencyKey', uuid()); case COMPOSE_SUGGESTIONS_CLEAR: return state.update('suggestions', ImmutableList(), list => list.clear()).set('suggestion_token', null); case COMPOSE_SUGGESTIONS_READY: diff --git a/app/javascript/mastodon/reducers/domain_lists.js b/app/javascript/mastodon/reducers/domain_lists.js new file mode 100644 index 000000000..a9e3519f3 --- /dev/null +++ b/app/javascript/mastodon/reducers/domain_lists.js @@ -0,0 +1,23 @@ +import { + DOMAIN_BLOCKS_FETCH_SUCCESS, + DOMAIN_BLOCKS_EXPAND_SUCCESS, + DOMAIN_UNBLOCK_SUCCESS, +} from '../actions/domain_blocks'; +import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable'; + +const initialState = ImmutableMap({ + blocks: ImmutableMap(), +}); + +export default function domainLists(state = initialState, action) { + switch(action.type) { + case DOMAIN_BLOCKS_FETCH_SUCCESS: + return state.setIn(['blocks', 'items'], ImmutableOrderedSet(action.domains)).setIn(['blocks', 'next'], action.next); + case DOMAIN_BLOCKS_EXPAND_SUCCESS: + return state.updateIn(['blocks', 'items'], set => set.union(action.domains)).setIn(['blocks', 'next'], action.next); + case DOMAIN_UNBLOCK_SUCCESS: + return state.updateIn(['blocks', 'items'], set => set.delete(action.domain)); + default: + return state; + } +}; diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js index b84b2d18a..3d9a6a132 100644 --- a/app/javascript/mastodon/reducers/index.js +++ b/app/javascript/mastodon/reducers/index.js @@ -6,6 +6,7 @@ import alerts from './alerts'; import { loadingBarReducer } from 'react-redux-loading-bar'; import modal from './modal'; import user_lists from './user_lists'; +import domain_lists from './domain_lists'; import accounts from './accounts'; import accounts_counters from './accounts_counters'; import statuses from './statuses'; @@ -34,6 +35,7 @@ const reducers = { loadingBar: loadingBarReducer, modal, user_lists, + domain_lists, status_lists, accounts, accounts_counters, diff --git a/app/javascript/mastodon/reducers/relationships.js b/app/javascript/mastodon/reducers/relationships.js index c7b04a668..d1caabc1c 100644 --- a/app/javascript/mastodon/reducers/relationships.js +++ b/app/javascript/mastodon/reducers/relationships.js @@ -23,6 +23,14 @@ const normalizeRelationships = (state, relationships) => { return state; }; +const setDomainBlocking = (state, accounts, blocking) => { + return state.withMutations(map => { + accounts.forEach(id => { + map.setIn([id, 'domain_blocking'], blocking); + }); + }); +}; + const initialState = ImmutableMap(); export default function relationships(state = initialState, action) { @@ -37,9 +45,9 @@ export default function relationships(state = initialState, action) { case RELATIONSHIPS_FETCH_SUCCESS: return normalizeRelationships(state, action.relationships); case DOMAIN_BLOCK_SUCCESS: - return state.setIn([action.accountId, 'domain_blocking'], true); + return setDomainBlocking(state, action.accounts, true); case DOMAIN_UNBLOCK_SUCCESS: - return state.setIn([action.accountId, 'domain_blocking'], false); + return setDomainBlocking(state, action.accounts, false); default: return state; } diff --git a/app/javascript/mastodon/storage/modifier.js b/app/javascript/mastodon/storage/modifier.js index 63e49fe6e..1bec04d0f 100644 --- a/app/javascript/mastodon/storage/modifier.js +++ b/app/javascript/mastodon/storage/modifier.js @@ -4,7 +4,10 @@ import { autoPlayGif } from '../initial_state'; const accountAssetKeys = ['avatar', 'avatar_static', 'header', 'header_static']; const avatarKey = autoPlayGif ? 'avatar' : 'avatar_static'; const limit = 1024; -const asyncCache = caches.open('mastodon-system'); + +// ServiceWorker and Cache API is not available on iOS 11 +// https://webkit.org/status/#specification-service-workers +const asyncCache = window.caches ? caches.open('mastodon-system') : Promise.reject(); function put(name, objects, onupdate, oncreate) { return asyncDB.then(db => new Promise((resolve, reject) => { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 042a84742..31089301c 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1001,6 +1001,30 @@ } } +.domain { + padding: 10px; + border-bottom: 1px solid lighten($ui-base-color, 8%); + + .domain__domain-name { + flex: 1 1 auto; + display: block; + color: $primary-text-color; + text-decoration: none; + font-size: 14px; + font-weight: 500; + } +} + +.domain__wrapper { + display: flex; +} + +.domain_buttons { + height: 18px; + padding: 10px; + white-space: nowrap; +} + .account { padding: 10px; border-bottom: 1px solid lighten($ui-base-color, 8%); @@ -1459,9 +1483,6 @@ position: relative; width: 100%; height: 100%; - display: flex; - align-items: center; - justify-content: center; img { max-width: $media-modal-media-max-width; diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 676e885c0..45c0e91cb 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -79,6 +79,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity hashtag = Tag.where(name: hashtag).first_or_initialize(name: hashtag) status.tags << hashtag + rescue ActiveRecord::RecordInvalid + nil end def process_mention(tag, status) @@ -113,13 +115,13 @@ class ActivityPub::Activity::Create < ActivityPub::Activity media_attachments = [] as_array(@object['attachment']).each do |attachment| - next if unsupported_media_type?(attachment['mediaType']) || attachment['url'].blank? + next if attachment['url'].blank? href = Addressable::URI.parse(attachment['url']).normalize.to_s media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence, focus: attachment['focalPoint']) media_attachments << media_attachment - next if skip_download? + next if unsupported_media_type?(attachment['mediaType']) || skip_download? media_attachment.file_remote_url = href media_attachment.save diff --git a/app/services/fetch_atom_service.rb b/app/services/fetch_atom_service.rb index 62dea8298..87076cc07 100644 --- a/app/services/fetch_atom_service.rb +++ b/app/services/fetch_atom_service.rb @@ -44,7 +44,7 @@ class FetchAtomService < BaseService json = body_to_json(body) if supported_context?(json) && json['type'] == 'Person' && json['inbox'].present? [json['id'], { prefetched_body: body, id: true }, :activitypub] - elsif supported_context?(json) && json['type'] == 'Note' + elsif supported_context?(json) && expected_type?(json) [json['id'], { prefetched_body: body, id: true }, :activitypub] else @unsupported_activity = true @@ -61,6 +61,10 @@ class FetchAtomService < BaseService end end + def expected_type?(json) + (ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES).include? json['type'] + end + def process_html(response) page = Nokogiri::HTML(response.body_with_limit) diff --git a/app/services/resolve_url_service.rb b/app/services/resolve_url_service.rb index 1f2b24524..9499dc286 100644 --- a/app/services/resolve_url_service.rb +++ b/app/services/resolve_url_service.rb @@ -19,7 +19,7 @@ class ResolveURLService < BaseService case type when 'Person' FetchRemoteAccountService.new.call(atom_url, body, protocol) - when 'Note' + when 'Note', 'Article', 'Image', 'Video' FetchRemoteStatusService.new.call(atom_url, body, protocol) end end diff --git a/package.json b/package.json index a3aaaf0c8..2bea79e4d 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "file-loader": "^0.11.2", "font-awesome": "^4.7.0", "glob": "^7.1.1", + "hammerjs": "^2.0.8", "http-link-header": "^0.8.0", "immutable": "^3.8.2", "imports-loader": "^0.8.0", diff --git a/yarn.lock b/yarn.lock index a4465c02e..8ae8efbd2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3096,6 +3096,10 @@ gzip-size@^3.0.0: dependencies: duplexer "^0.1.1" +hammerjs@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" + handle-thing@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" |