From a4f07bad9529a6bebb6a0292c4ef0bfc6e29c4d2 Mon Sep 17 00:00:00 2001 From: ThibG Date: Wed, 16 Jan 2019 15:42:00 +0100 Subject: Reduce chances of race conditions when processing deleted toots (#9815) * Reduce chances of race conditions when processing deleted toots * Prevent race condition when processing deleted toots --- app/lib/activitypub/activity/create.rb | 4 +++- app/lib/activitypub/activity/delete.rb | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 2b238bc88..f4cf7ceb5 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -5,10 +5,12 @@ class ActivityPub::Activity::Create < ActivityPub::Activity CONVERTED_TYPES = %w(Image Video Article Page).freeze def perform - return if delete_arrived_first?(object_uri) || unsupported_object_type? || invalid_origin?(@object['id']) + return if unsupported_object_type? || invalid_origin?(@object['id']) RedisLock.acquire(lock_options) do |lock| if lock.acquired? + return if delete_arrived_first?(object_uri) + @status = find_existing_status if @status.nil? diff --git a/app/lib/activitypub/activity/delete.rb b/app/lib/activitypub/activity/delete.rb index 8270fed1b..ca3cf387e 100644 --- a/app/lib/activitypub/activity/delete.rb +++ b/app/lib/activitypub/activity/delete.rb @@ -21,11 +21,13 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity def delete_note return if object_uri.nil? + RedisLock.acquire(lock_options) do |_lock| + delete_later!(object_uri) + end + @status = Status.find_by(uri: object_uri, account: @account) @status ||= Status.find_by(uri: @object['atomUri'], account: @account) if @object.is_a?(Hash) && @object['atomUri'].present? - delete_later!(object_uri) - return if @status.nil? if @status.public_visibility? || @status.unlisted_visibility? @@ -68,4 +70,8 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity def payload @payload ||= Oj.dump(@json) end + + def lock_options + { redis: Redis.current, key: "create:#{object_uri}" } + end end -- cgit From 4ab42287c0bbcbd259bff229d66da8964a261aff Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 16 Jan 2019 18:36:17 +0100 Subject: Use summary as summary for converted ActivityPub objects (#9823) Fix #8609 --- app/lib/activitypub/activity/create.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index f4cf7ceb5..665a9fbdc 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -61,7 +61,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity account: @account, text: text_from_content || '', language: detected_language, - spoiler_text: text_from_summary || '', + spoiler_text: converted_object_type? ? '' : (text_from_summary || ''), created_at: @object['published'], override_timestamps: @options[:override_timestamps], reply: @object['inReplyTo'].present?, @@ -256,7 +256,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity end def text_from_content - return Formatter.instance.linkify([text_from_name, object_url || @object['id']].join(' ')) if converted_object_type? + return Formatter.instance.linkify([[text_from_name, text_from_summary.presence].compact.join("\n\n"), object_url || @object['id']].join(' ')) if converted_object_type? if @object['content'].present? @object['content'] -- cgit From bc642ac24b49c14dca382e7aabbc16130293d2f4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 16 Jan 2019 19:47:46 +0100 Subject: Redesign public hashtag page to use a masonry layout (#9822) --- app/controllers/tags_controller.rb | 2 + app/javascript/mastodon/components/display_name.js | 12 +- app/javascript/mastodon/components/status.js | 2 +- .../features/standalone/hashtag_timeline/index.js | 86 +++++++---- .../features/status/components/detailed_status.js | 107 +++++++++++-- .../status/containers/detailed_status_container.js | 172 +++++++++++++++++++++ app/javascript/styles/mastodon/widgets.scss | 27 ++++ app/views/tags/show.html.haml | 30 +--- package.json | 1 + spec/controllers/tags_controller_spec.rb | 2 +- yarn.lock | 28 ++++ 11 files changed, 392 insertions(+), 77 deletions(-) create mode 100644 app/javascript/mastodon/features/status/containers/detailed_status_container.js (limited to 'app') diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 8e4051834..4694c823a 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -3,6 +3,8 @@ class TagsController < ApplicationController PAGE_SIZE = 20 + layout 'public' + before_action :set_body_classes before_action :set_instance_presenter diff --git a/app/javascript/mastodon/components/display_name.js b/app/javascript/mastodon/components/display_name.js index c2c40cb3f..acddf77c5 100644 --- a/app/javascript/mastodon/components/display_name.js +++ b/app/javascript/mastodon/components/display_name.js @@ -1,15 +1,17 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; export default class DisplayName extends React.PureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, others: ImmutablePropTypes.list, + localDomain: PropTypes.string, }; render () { - const { account, others } = this.props; + const { account, others, localDomain } = this.props; const displayNameHtml = { __html: account.get('display_name_html') }; let suffix; @@ -17,7 +19,13 @@ export default class DisplayName extends React.PureComponent { if (others && others.size > 1) { suffix = `+${others.size}`; } else { - suffix = @{account.get('acct')}; + let acct = account.get('acct'); + + if (acct.indexOf('@') === -1 && localDomain) { + acct = `${acct}@${localDomain}`; + } + + suffix = @{acct}; } return ( diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index fd0780025..20d838500 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -77,7 +77,7 @@ class Status extends ImmutablePureComponent { 'account', 'muted', 'hidden', - ] + ]; handleClick = () => { if (this.props.onClick) { diff --git a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js index 759922638..4e09b1948 100644 --- a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js @@ -1,28 +1,32 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import StatusListContainer from '../../ui/containers/status_list_container'; +import ImmutablePropTypes from 'react-immutable-proptypes'; import { expandHashtagTimeline } from '../../../actions/timelines'; -import Column from '../../../components/column'; -import ColumnHeader from '../../../components/column_header'; import { connectHashtagStream } from '../../../actions/streaming'; +import Masonry from 'react-masonry-infinite'; +import { List as ImmutableList } from 'immutable'; +import DetailedStatusContainer from '../../status/containers/detailed_status_container'; +import { debounce } from 'lodash'; +import LoadingIndicator from '../../../components/loading_indicator'; -export default @connect() +const mapStateToProps = (state, { hashtag }) => ({ + statusIds: state.getIn(['timelines', `hashtag:${hashtag}`, 'items'], ImmutableList()), + isLoading: state.getIn(['timelines', `hashtag:${hashtag}`, 'isLoading'], false), + hasMore: state.getIn(['timelines', `hashtag:${hashtag}`, 'hasMore'], false), +}); + +export default @connect(mapStateToProps) class HashtagTimeline extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, + statusIds: ImmutablePropTypes.list.isRequired, + isLoading: PropTypes.bool.isRequired, + hasMore: PropTypes.bool.isRequired, hashtag: PropTypes.string.isRequired, }; - handleHeaderClick = () => { - this.column.scrollTop(); - } - - setRef = c => { - this.column = c; - } - componentDidMount () { const { dispatch, hashtag } = this.props; @@ -37,28 +41,52 @@ class HashtagTimeline extends React.PureComponent { } } - handleLoadMore = maxId => { - this.props.dispatch(expandHashtagTimeline(this.props.hashtag, { maxId })); + handleLoadMore = () => { + const maxId = this.props.statusIds.last(); + + if (maxId) { + this.props.dispatch(expandHashtagTimeline(this.props.hashtag, { maxId })); + } + } + + setRef = c => { + this.masonry = c; } + handleHeightChange = debounce(() => { + if (!this.masonry) { + return; + } + + this.masonry.forcePack(); + }, 50) + render () { - const { hashtag } = this.props; + const { statusIds, hasMore, isLoading } = this.props; + + const sizes = [ + { columns: 1, gutter: 0 }, + { mq: '415px', columns: 1, gutter: 10 }, + { mq: '640px', columns: 2, gutter: 10 }, + { mq: '960px', columns: 3, gutter: 10 }, + { mq: '1255px', columns: 3, gutter: 10 }, + ]; + + const loader = (isLoading && statusIds.isEmpty()) ? : undefined; return ( - - - - - + + {statusIds.map(statusId => ( +
+ +
+ )).toArray()} +
); } diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index b0dea8817..3ea8e9e74 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -11,6 +11,7 @@ import { FormattedDate, FormattedNumber } from 'react-intl'; import Card from './card'; import ImmutablePureComponent from 'react-immutable-pure-component'; import Video from '../../video'; +import scheduleIdleTask from '../../ui/util/schedule_idle_task'; export default class DetailedStatus extends ImmutablePureComponent { @@ -23,10 +24,17 @@ export default class DetailedStatus extends ImmutablePureComponent { onOpenMedia: PropTypes.func.isRequired, onOpenVideo: PropTypes.func.isRequired, onToggleHidden: PropTypes.func.isRequired, + measureHeight: PropTypes.bool, + onHeightChange: PropTypes.func, + domain: PropTypes.string.isRequired, + }; + + state = { + height: null, }; handleAccountClick = (e) => { - if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { + if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.context.router) { e.preventDefault(); this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); } @@ -42,13 +50,56 @@ export default class DetailedStatus extends ImmutablePureComponent { this.props.onToggleHidden(this.props.status); } + _measureHeight (heightJustChanged) { + if (this.props.measureHeight && this.node) { + scheduleIdleTask(() => this.node && this.setState({ height: this.node.offsetHeight })); + + if (this.props.onHeightChange && heightJustChanged) { + this.props.onHeightChange(); + } + } + } + + setRef = c => { + this.node = c; + this._measureHeight(); + } + + componentDidUpdate (prevProps, prevState) { + this._measureHeight(prevState.height !== this.state.height); + } + + handleModalLink = e => { + e.preventDefault(); + + let href; + + if (e.target.nodeName !== 'A') { + href = e.target.parentNode.href; + } else { + href = e.target.href; + } + + window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes'); + } + render () { const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status; + const outerStyle = { boxSizing: 'border-box' }; + + if (!status) { + return null; + } let media = ''; let applicationLink = ''; let reblogLink = ''; let reblogIcon = 'retweet'; + let favouriteLink = ''; + + if (this.props.measureHeight) { + outerStyle.height = `${this.state.height}px`; + } if (status.get('media_attachments').size > 0) { if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) { @@ -95,20 +146,51 @@ export default class DetailedStatus extends ImmutablePureComponent { if (status.get('visibility') === 'private') { reblogLink = ; + } else if (this.context.router) { + reblogLink = ( + + + + + + + ); + } else { + reblogLink = ( + + + + + + + ); + } + + if (this.context.router) { + favouriteLink = ( + + + + + + + ); } else { - reblogLink = ( - - - - - ); + favouriteLink = ( + + + + + + + ); } return ( -
+
- +
@@ -118,12 +200,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
- {applicationLink} · {reblogLink} · - - - - - + {applicationLink} · {reblogLink} · {favouriteLink}
); diff --git a/app/javascript/mastodon/features/status/containers/detailed_status_container.js b/app/javascript/mastodon/features/status/containers/detailed_status_container.js new file mode 100644 index 000000000..2c0db0a6b --- /dev/null +++ b/app/javascript/mastodon/features/status/containers/detailed_status_container.js @@ -0,0 +1,172 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import DetailedStatus from '../components/detailed_status'; +import { makeGetStatus } from '../../../selectors'; +import { + replyCompose, + mentionCompose, + directCompose, +} from '../../../actions/compose'; +import { + reblog, + favourite, + unreblog, + unfavourite, + pin, + unpin, +} from '../../../actions/interactions'; +import { blockAccount } from '../../../actions/accounts'; +import { + muteStatus, + unmuteStatus, + deleteStatus, + hideStatus, + revealStatus, +} from '../../../actions/statuses'; +import { initMuteModal } from '../../../actions/mutes'; +import { initReport } from '../../../actions/reports'; +import { openModal } from '../../../actions/modal'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { boostModal, deleteModal } from '../../../initial_state'; +import { showAlertForError } from '../../../actions/alerts'; + +const messages = defineMessages({ + deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, + deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, + redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' }, + redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' }, + blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' }, + replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, + replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, +}); + +const makeMapStateToProps = () => { + const getStatus = makeGetStatus(); + + const mapStateToProps = (state, props) => ({ + status: getStatus(state, props), + domain: state.getIn(['meta', 'domain']), + }); + + return mapStateToProps; +}; + +const mapDispatchToProps = (dispatch, { intl }) => ({ + + onReply (status, router) { + dispatch((_, getState) => { + let state = getState(); + if (state.getIn(['compose', 'text']).trim().length !== 0) { + dispatch(openModal('CONFIRM', { + message: intl.formatMessage(messages.replyMessage), + confirm: intl.formatMessage(messages.replyConfirm), + onConfirm: () => dispatch(replyCompose(status, router)), + })); + } else { + dispatch(replyCompose(status, router)); + } + }); + }, + + onModalReblog (status) { + dispatch(reblog(status)); + }, + + onReblog (status, e) { + if (status.get('reblogged')) { + dispatch(unreblog(status)); + } else { + if (e.shiftKey || !boostModal) { + this.onModalReblog(status); + } else { + dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog })); + } + } + }, + + onFavourite (status) { + if (status.get('favourited')) { + dispatch(unfavourite(status)); + } else { + dispatch(favourite(status)); + } + }, + + onPin (status) { + if (status.get('pinned')) { + dispatch(unpin(status)); + } else { + dispatch(pin(status)); + } + }, + + onEmbed (status) { + dispatch(openModal('EMBED', { + url: status.get('url'), + onError: error => dispatch(showAlertForError(error)), + })); + }, + + onDelete (status, history, withRedraft = false) { + if (!deleteModal) { + dispatch(deleteStatus(status.get('id'), history, withRedraft)); + } else { + dispatch(openModal('CONFIRM', { + message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), + confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), + onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)), + })); + } + }, + + onDirect (account, router) { + dispatch(directCompose(account, router)); + }, + + onMention (account, router) { + dispatch(mentionCompose(account, router)); + }, + + onOpenMedia (media, index) { + dispatch(openModal('MEDIA', { media, index })); + }, + + onOpenVideo (media, time) { + dispatch(openModal('VIDEO', { media, time })); + }, + + onBlock (account) { + dispatch(openModal('CONFIRM', { + message: @{account.get('acct')} }} />, + confirm: intl.formatMessage(messages.blockConfirm), + onConfirm: () => dispatch(blockAccount(account.get('id'))), + })); + }, + + onReport (status) { + dispatch(initReport(status.get('account'), status)); + }, + + onMute (account) { + dispatch(initMuteModal(account)); + }, + + onMuteConversation (status) { + if (status.get('muted')) { + dispatch(unmuteStatus(status.get('id'))); + } else { + dispatch(muteStatus(status.get('id'))); + } + }, + + onToggleHidden (status) { + if (status.get('hidden')) { + dispatch(revealStatus(status.get('id'))); + } else { + dispatch(hideStatus(status.get('id'))); + } + }, + +}); + +export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(DetailedStatus)); diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index 87e633c70..cabef807e 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -425,3 +425,30 @@ border-radius: 0; } } + +$maximum-width: 1235px; +$fluid-breakpoint: $maximum-width + 20px; + +.statuses-grid { + min-height: 600px; + + &__item { + width: (960px - 20px) / 3; + + @media screen and (max-width: $fluid-breakpoint) { + width: (940px - 20px) / 3; + } + + @media screen and (max-width: $no-gap-breakpoint) { + width: 100vw; + } + } + + .detailed-status { + border-radius: 4px; + + @media screen and (max-width: $no-gap-breakpoint) { + border-bottom: 1px solid lighten($ui-base-color, 12%); + } + } +} diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml index f6e452f18..229fe958b 100644 --- a/app/views/tags/show.html.haml +++ b/app/views/tags/show.html.haml @@ -8,33 +8,5 @@ = javascript_pack_tag 'about', integrity: true, crossorigin: 'anonymous' = render 'og' -.landing-page.tag-page.alternative - .features - .container - .grid - .column-1 - #mastodon-timeline{ data: { props: Oj.dump(default_props.merge(hashtag: @tag.name)) } } - - .column-2 - .about-mastodon - .about-hashtag.landing-page__information - .brand - = link_to root_url do - = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon' - - %p= t 'about.about_hashtag_html', hashtag: @tag.name - - .cta - - if user_signed_in? - = link_to t('settings.back'), root_path, class: 'button button-secondary' - - else - = link_to t('auth.login'), new_user_session_path, class: 'button button-secondary' - = link_to t('about.learn_more'), about_path, class: 'button button-alternative' - - .landing-page__features.landing-page__information - %h3= t 'about.what_is_mastodon' - %p= t 'about.about_mastodon_html' - - = render 'features' - +#mastodon-timeline{ data: { props: Oj.dump(default_props.merge(hashtag: @tag.name)) } } #modal-container diff --git a/package.json b/package.json index 517f52bd4..5fa6fa8b7 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "react-immutable-proptypes": "^2.1.0", "react-immutable-pure-component": "^1.1.1", "react-intl": "^2.7.2", + "react-masonry-infinite": "^1.2.2", "react-motion": "^0.5.2", "react-notification": "^6.8.4", "react-overlays": "^0.8.3", diff --git a/spec/controllers/tags_controller_spec.rb b/spec/controllers/tags_controller_spec.rb index 33ccaed61..69def90cf 100644 --- a/spec/controllers/tags_controller_spec.rb +++ b/spec/controllers/tags_controller_spec.rb @@ -17,7 +17,7 @@ RSpec.describe TagsController, type: :controller do it 'renders application layout' do get :show, params: { id: 'test', max_id: late.id } - expect(response).to render_template layout: 'application' + expect(response).to render_template layout: 'public' end end diff --git a/yarn.lock b/yarn.lock index 6f766f232..9ff12a712 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1681,6 +1681,13 @@ braces@^2.3.0, braces@^2.3.1: split-string "^3.0.2" to-regex "^3.0.1" +bricks.js@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/bricks.js/-/bricks.js-1.8.0.tgz#8fdeb3c0226af251f4d5727a7df7f9ac0092b4b2" + integrity sha1-j96zwCJq8lH01XJ6fff5rACStLI= + dependencies: + knot.js "^1.1.5" + brorand@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" @@ -5528,6 +5535,11 @@ kleur@^2.0.1: resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300" integrity sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ== +knot.js@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/knot.js/-/knot.js-1.1.5.tgz#28e72522f703f50fe98812fde224dd72728fef5d" + integrity sha1-KOclIvcD9Q/piBL94iTdcnKP710= + lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" @@ -7558,6 +7570,13 @@ react-immutable-pure-component@^1.1.1: optionalDependencies: "@types/react" "16.4.6" +react-infinite-scroller@^1.0.12: + version "1.2.4" + resolved "https://registry.yarnpkg.com/react-infinite-scroller/-/react-infinite-scroller-1.2.4.tgz#f67eaec4940a4ce6417bebdd6e3433bfc38826e9" + integrity sha512-/oOa0QhZjXPqaD6sictN2edFMsd3kkMiE19Vcz5JDgHpzEJVqYcmq+V3mkwO88087kvKGe1URNksHEOt839Ubw== + dependencies: + prop-types "^15.5.8" + react-input-autosize@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.1.tgz#ec428fa15b1592994fb5f9aa15bb1eb6baf420f8" @@ -7596,6 +7615,15 @@ react-lifecycles-compat@^3.0.2, react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-masonry-infinite@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/react-masonry-infinite/-/react-masonry-infinite-1.2.2.tgz#20c1386f9ccdda9747527c8f42bc2c02dd2e7951" + integrity sha1-IME4b5zN2pdHUnyPQrwsAt0ueVE= + dependencies: + bricks.js "^1.7.0" + prop-types "^15.5.10" + react-infinite-scroller "^1.0.12" + react-motion@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316" -- cgit From 3b3a4d8a1709b8f4a9ffe67d21707117c75f9fe8 Mon Sep 17 00:00:00 2001 From: ThibG Date: Wed, 16 Jan 2019 20:36:10 +0100 Subject: Fix public hashtag timeline width on mobile, fix scrollbar width compensation (#9824) * Fix hashtag timeline width being potentially larger than window width * Add automatic computation of scrollbar width --- app/javascript/packs/public.js | 8 ++++++++ app/javascript/styles/mastodon/about.scss | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index a86dc7831..4ab27c769 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -108,6 +108,14 @@ function main() { if (parallaxComponents.length > 0 ) { new Rellax('.parallax', { speed: -1 }); } + + if (document.body.classList.contains('with-modals')) { + const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; + const scrollbarWidthStyle = document.createElement('style'); + scrollbarWidthStyle.id = 'scrollbar-width'; + document.head.appendChild(scrollbarWidthStyle); + scrollbarWidthStyle.sheet.insertRule(`body.with-modals--active { margin-right: ${scrollbarWidth}px; }`, 0); + } }); delegate(document, '.webapp-btn', 'click', ({ target, button }) => { diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index 47ac9265b..c6f249fab 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -364,7 +364,7 @@ $small-breakpoint: 960px; @media screen and (max-width: $column-breakpoint) { .grid { - grid-template-columns: auto; + grid-template-columns: 100%; .column-0 { display: block; -- cgit From 30af4ee65ff43c17d6f7b1b64d6bf1d8699f37c8 Mon Sep 17 00:00:00 2001 From: tmm576 Date: Thu, 17 Jan 2019 03:22:12 -0500 Subject: Hide floating action button on search and getting started pages (#9826) --- app/javascript/mastodon/features/ui/components/columns_area.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index ed338c2eb..b7e350cbc 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -33,7 +33,7 @@ const messages = defineMessages({ publish: { id: 'compose_form.publish', defaultMessage: 'Toot' }, }); -const shouldHideFAB = path => path.match(/^\/statuses\//); +const shouldHideFAB = path => path.match(/^\/statuses\/|^\/search|^\/getting-started/); export default @(component => injectIntl(component, { withRef: true })) class ColumnsArea extends ImmutablePureComponent { -- cgit From 8b1990355974543542544e56d2046bc0c9c8716b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 17 Jan 2019 14:06:08 +0100 Subject: Improve the public hashtag page (#9831) - Fix height not updating when clicking show more on public hashtag page - Add header to the public hashtag page - Change text size and margins on the public hashtag page --- app/javascript/mastodon/common.js | 6 +++- .../features/standalone/hashtag_timeline/index.js | 2 +- .../features/status/components/detailed_status.js | 35 ++++++++++++---------- app/javascript/mastodon/features/status/index.js | 5 +++- app/javascript/styles/mastodon/widgets.scss | 28 +++++++++++++++++ app/views/tags/_features.html.haml | 25 ---------------- app/views/tags/show.html.haml | 6 +++- 7 files changed, 63 insertions(+), 44 deletions(-) delete mode 100644 app/views/tags/_features.html.haml (limited to 'app') diff --git a/app/javascript/mastodon/common.js b/app/javascript/mastodon/common.js index 2b10b8c30..fba21316a 100644 --- a/app/javascript/mastodon/common.js +++ b/app/javascript/mastodon/common.js @@ -4,5 +4,9 @@ export function start() { require('font-awesome/css/font-awesome.css'); require.context('../images/', true); - Rails.start(); + try { + Rails.start(); + } catch (e) { + // If called twice + } }; diff --git a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js index 4e09b1948..333726f94 100644 --- a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js @@ -80,7 +80,7 @@ class HashtagTimeline extends React.PureComponent {
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index 3ea8e9e74..2921a26f9 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -12,6 +12,7 @@ import Card from './card'; import ImmutablePureComponent from 'react-immutable-pure-component'; import Video from '../../video'; import scheduleIdleTask from '../../ui/util/schedule_idle_task'; +import classNames from 'classnames'; export default class DetailedStatus extends ImmutablePureComponent { @@ -27,6 +28,7 @@ export default class DetailedStatus extends ImmutablePureComponent { measureHeight: PropTypes.bool, onHeightChange: PropTypes.func, domain: PropTypes.string.isRequired, + compact: PropTypes.bool, }; state = { @@ -52,7 +54,7 @@ export default class DetailedStatus extends ImmutablePureComponent { _measureHeight (heightJustChanged) { if (this.props.measureHeight && this.node) { - scheduleIdleTask(() => this.node && this.setState({ height: this.node.offsetHeight })); + scheduleIdleTask(() => this.node && this.setState({ height: this.node.scrollHeight })); if (this.props.onHeightChange && heightJustChanged) { this.props.onHeightChange(); @@ -86,6 +88,7 @@ export default class DetailedStatus extends ImmutablePureComponent { render () { const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status; const outerStyle = { boxSizing: 'border-box' }; + const { compact } = this.props; if (!status) { return null; @@ -187,20 +190,22 @@ export default class DetailedStatus extends ImmutablePureComponent { } return ( -
- -
- -
- - - - {media} - -
- - - {applicationLink} · {reblogLink} · {favouriteLink} +
+
+ +
+ +
+ + + + {media} + +
+ + + {applicationLink} · {reblogLink} · {favouriteLink} +
); diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index a092f7bb1..d48b682eb 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -101,6 +101,7 @@ const makeMapStateToProps = () => { ancestorsIds, descendantsIds, askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0, + domain: state.getIn(['meta', 'domain']), }; }; @@ -123,6 +124,7 @@ class Status extends ImmutablePureComponent { descendantsIds: ImmutablePropTypes.list, intl: PropTypes.object.isRequired, askReplyConfirmation: PropTypes.bool, + domain: PropTypes.string.isRequired, }; state = { @@ -387,7 +389,7 @@ class Status extends ImmutablePureComponent { render () { let ancestors, descendants; - const { shouldUpdateScroll, status, ancestorsIds, descendantsIds, intl } = this.props; + const { shouldUpdateScroll, status, ancestorsIds, descendantsIds, intl, domain } = this.props; const { fullscreen } = this.state; if (status === null) { @@ -438,6 +440,7 @@ class Status extends ImmutablePureComponent { onOpenVideo={this.handleOpenVideo} onOpenMedia={this.handleOpenMedia} onToggleHidden={this.handleToggleHidden} + domain={domain} /> Date: Thu, 17 Jan 2019 17:27:51 -0500 Subject: Allow event defaults on index for text data transfer (#9840) --- app/javascript/mastodon/features/ui/index.js | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'app') diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index a59c0a257..f01c2bf24 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -243,6 +243,7 @@ class UI extends React.PureComponent { } handleDragOver = (e) => { + if (this.dataTransferIsText(e.dataTransfer)) return false; e.preventDefault(); e.stopPropagation(); @@ -256,6 +257,7 @@ class UI extends React.PureComponent { } handleDrop = (e) => { + if (this.dataTransferIsText(e.dataTransfer)) return; e.preventDefault(); this.setState({ draggingOver: false }); @@ -279,6 +281,10 @@ class UI extends React.PureComponent { this.setState({ draggingOver: false }); } + dataTransferIsText = (dataTransfer) => { + return (dataTransfer && Array.from(dataTransfer.types).includes('text/plain') && dataTransfer.items.length === 1); + } + closeUploadModal = () => { this.setState({ draggingOver: false }); } -- cgit From b8894c429ae294801ffd59b2d1ee534eeb80dc24 Mon Sep 17 00:00:00 2001 From: "Mélanie Chauvel (ariasuni)" Date: Thu, 17 Jan 2019 23:28:30 +0100 Subject: Fix slightly cropped font on settings page dropdowns when using system font (#9839) --- app/javascript/styles/mastodon/forms.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 6132dd1ae..bab982706 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -419,7 +419,7 @@ code { background: darken($ui-base-color, 10%) url("data:image/svg+xml;utf8,") no-repeat right 8px center / auto 16px; border: 1px solid darken($ui-base-color, 14%); border-radius: 4px; - padding: 10px; + padding-left: 10px; padding-right: 30px; height: 41px; } -- cgit From 90ff2e7608321389e968f512e709a1e0a7bf2657 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 18 Jan 2019 00:48:09 +0100 Subject: Weblate translations (2019-01-17) (#9844) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Welsh) Currently translated at 94.7% (337 of 356 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/cy/ * Translated using Weblate (Czech) Currently translated at 99.9% (751 of 752 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/cs/ * Translated using Weblate (Romanian) Currently translated at 99.2% (353 of 356 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ro/ * Translated using Weblate (Romanian) Currently translated at 11.8% (89 of 752 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ro/ * Translated using Weblate (German) Currently translated at 99.9% (751 of 752 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/de/ * Translated using Weblate (German) Currently translated at 100.0% (107 of 107 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/de/ * Translated using Weblate (German) Currently translated at 99.9% (751 of 752 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/de/ * Translated using Weblate (Serbian) Currently translated at 96.8% (61 of 63 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/sr/ * Translated using Weblate (Slovak) Currently translated at 100.0% (356 of 356 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/ * Translated using Weblate (Serbian) Currently translated at 74.8% (80 of 107 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sr/ * Translated using Weblate (Serbian) Currently translated at 98.4% (62 of 63 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/sr/ нисам сигуран за ово "и не направите нову". слијед ријечи ми је мало чудан? * Translated using Weblate (Serbian) Currently translated at 75.7% (81 of 107 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sr/ преподешавања за ПРЕСЕТ * Translated using Weblate (Catalan) Currently translated at 100.0% (356 of 356 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ca/ * Translated using Weblate (Serbian) Currently translated at 80.4% (86 of 107 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sr/ * i18n-tasks normalize * yarn manage:translations --- app/javascript/mastodon/locales/ca.json | 2 +- app/javascript/mastodon/locales/cy.json | 22 +++---- .../mastodon/locales/defaultMessages.json | 37 +++++++++++ app/javascript/mastodon/locales/ro.json | 2 +- app/javascript/mastodon/locales/sk.json | 2 +- config/locales/de.yml | 72 +++++++++++----------- config/locales/devise.sr.yml | 22 +++++++ config/locales/ro.yml | 4 +- config/locales/simple_form.de.yml | 26 ++++---- config/locales/simple_form.sr.yml | 14 +++++ 10 files changed, 139 insertions(+), 64 deletions(-) (limited to 'app') diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 302ff2573..11c31877c 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -8,7 +8,7 @@ "account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.", "account.domain_blocked": "Domini ocult", "account.edit_profile": "Editar el perfil", - "account.endorse": "Característica del perfil", + "account.endorse": "Recomanar en el teu perfil", "account.follow": "Segueix", "account.followers": "Seguidors", "account.followers.empty": "Encara ningú no segueix aquest usuari.", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index 471b70439..f35b96244 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -84,7 +84,7 @@ "confirmations.block.confirm": "Blocio", "confirmations.block.message": "Ydych chi'n sicr eich bod eisiau blocio {name}?", "confirmations.delete.confirm": "Dileu", - "confirmations.delete.message": "Ydych chi'n sicr eich bod eisiau dileu y statws hwn?", + "confirmations.delete.message": "Ydych chi'n sicr eich bod eisiau dileu y tŵt hwn?", "confirmations.delete_list.confirm": "Dileu", "confirmations.delete_list.message": "Ydych chi'n sicr eich bod eisiau dileu y rhestr hwn am byth?", "confirmations.domain_block.confirm": "Cuddio parth cyfan", @@ -92,12 +92,12 @@ "confirmations.mute.confirm": "Tawelu", "confirmations.mute.message": "Ydych chi'n sicr eich bod am ddistewi {name}?", "confirmations.redraft.confirm": "Dileu & ailddrafftio", - "confirmations.redraft.message": "Ydych chi'n siwr eich bod eisiau dileu y statws hwn a'i ailddrafftio? Bydd ffefrynnau a bwstiau'n cael ei colli, a bydd ymatebion i'r statws gwreiddiol yn cael eu hamddifadu.", + "confirmations.redraft.message": "Ydych chi'n siwr eich bod eisiau dileu y tŵt hwn a'i ailddrafftio? Bydd ffefrynnau a bwstiau'n cael ei colli, a bydd ymatebion i'r tŵt gwreiddiol yn cael eu hamddifadu.", "confirmations.reply.confirm": "Ateb", "confirmations.reply.message": "Bydd ateb nawr yn cymryd lle y neges yr ydych yn cyfansoddi ar hyn o bryd. Ydych chi'n sicr yr ydych am barhau?", "confirmations.unfollow.confirm": "Dad-ddilynwch", "confirmations.unfollow.message": "Ydych chi'n sicr eich bod am ddad-ddilyn {name}?", - "embed.instructions": "Mewnblannwch y statws hwn ar eich gwefan drwy gopïo'r côd isod.", + "embed.instructions": "Mewnblannwch y tŵt hwn ar eich gwefan drwy gopïo'r côd isod.", "embed.preview": "Dyma sut olwg fydd arno:", "emoji_button.activity": "Gweithgarwch", "emoji_button.custom": "Unigryw", @@ -169,12 +169,12 @@ "keyboard_shortcuts.back": "i lywio nôl", "keyboard_shortcuts.blocked": "i agor rhestr defnyddwyr a flociwyd", "keyboard_shortcuts.boost": "i fŵstio", - "keyboard_shortcuts.column": "i ffocysu statws yn un o'r colofnau", + "keyboard_shortcuts.column": "i ffocysu tŵt yn un o'r colofnau", "keyboard_shortcuts.compose": "i ffocysu yr ardal cyfansoddi testun", "keyboard_shortcuts.description": "Disgrifiad", "keyboard_shortcuts.direct": "i agor colofn negeseuon preifat", "keyboard_shortcuts.down": "i symud lawr yn y rhestr", - "keyboard_shortcuts.enter": "i agor statws", + "keyboard_shortcuts.enter": "i agor tŵt", "keyboard_shortcuts.favourite": "i hoffi", "keyboard_shortcuts.favourites": "i agor rhestr hoffi", "keyboard_shortcuts.federated": "i agor ffrwd y ffederasiwn", @@ -234,10 +234,10 @@ "navigation_bar.preferences": "Dewisiadau", "navigation_bar.public_timeline": "Ffrwd y ffederasiwn", "navigation_bar.security": "Diogelwch", - "notification.favourite": "hoffodd {name} eich statws", + "notification.favourite": "hoffodd {name} eich tŵt", "notification.follow": "dilynodd {name} chi", "notification.mention": "Soniodd {name} amdanoch chi", - "notification.reblog": "{name} boosted your status", + "notification.reblog": "Hysbysebodd {name} eich tŵt", "notifications.clear": "Clirio hysbysiadau", "notifications.clear_confirmation": "Ydych chi'n sicr eich bod am glirio'ch holl hysbysiadau am byth?", "notifications.column_settings.alert": "Hysbysiadau bwrdd gwaith", @@ -257,7 +257,7 @@ "notifications.filter.follows": "Yn dilyn", "notifications.filter.mentions": "Mentions", "notifications.group": "{count} o hysbysiadau", - "privacy.change": "Addasu preifatrwdd y statws", + "privacy.change": "Addasu preifatrwdd y tŵt", "privacy.direct.long": "Cyhoeddi i'r defnyddwyr sy'n cael eu crybwyll yn unig", "privacy.direct.short": "Uniongyrchol", "privacy.private.long": "Cyhoeddi i ddilynwyr yn unig", @@ -284,7 +284,7 @@ "search_popout.search_format": "Fformat chwilio uwch", "search_popout.tips.full_text": "Mae testun syml yn dychwelyd tŵtiau yr ydych wedi ysgrifennu, hoffi, wedi'u bŵstio, neu wedi'ch crybwyll ynddynt, ynghyd a chyfateb a enwau defnyddwyr, enwau arddangos ac hashnodau.", "search_popout.tips.hashtag": "hashnod", - "search_popout.tips.status": "statws", + "search_popout.tips.status": "tŵt", "search_popout.tips.text": "Mae testun syml yn dychwelyd enwau arddangos, enwau defnyddwyr a hashnodau sy'n cyfateb", "search_popout.tips.user": "defnyddiwr", "search_results.accounts": "Pobl", @@ -293,7 +293,7 @@ "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "Golwg tu fewn...", "status.admin_account": "Open moderation interface for @{name}", - "status.admin_status": "Open this status in the moderation interface", + "status.admin_status": "Open this tŵt in the moderation interface", "status.block": "Blocio @{name}", "status.cancel_reblog_private": "Dadfŵstio", "status.cannot_reblog": "Ni ellir sbarduno'r tŵt hwn", @@ -309,7 +309,7 @@ "status.more": "Mwy", "status.mute": "Tawelu @{name}", "status.mute_conversation": "Tawelu sgwrs", - "status.open": "Ehangu'r statws hwn", + "status.open": "Ehangu'r tŵt hwn", "status.pin": "Pinio ar y proffil", "status.pinned": "Pinio tŵt", "status.read_more": "Darllen mwy", diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 951081f8d..6ac8160ba 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -1950,6 +1950,43 @@ ], "path": "app/javascript/mastodon/features/status/components/action_bar.json" }, + { + "descriptors": [ + { + "defaultMessage": "Delete", + "id": "confirmations.delete.confirm" + }, + { + "defaultMessage": "Are you sure you want to delete this status?", + "id": "confirmations.delete.message" + }, + { + "defaultMessage": "Delete & redraft", + "id": "confirmations.redraft.confirm" + }, + { + "defaultMessage": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.", + "id": "confirmations.redraft.message" + }, + { + "defaultMessage": "Block", + "id": "confirmations.block.confirm" + }, + { + "defaultMessage": "Reply", + "id": "confirmations.reply.confirm" + }, + { + "defaultMessage": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?", + "id": "confirmations.reply.message" + }, + { + "defaultMessage": "Are you sure you want to block {name}?", + "id": "confirmations.block.message" + } + ], + "path": "app/javascript/mastodon/features/status/containers/detailed_status_container.json" + }, { "descriptors": [ { diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json index f213f8ea3..a1a514f49 100644 --- a/app/javascript/mastodon/locales/ro.json +++ b/app/javascript/mastodon/locales/ro.json @@ -132,7 +132,7 @@ "follow_request.authorize": "Autorizează", "follow_request.reject": "Respinge", "getting_started.developers": "Dezvoltatori", - "getting_started.directory": "Directorul profilului", + "getting_started.directory": "Explorează", "getting_started.documentation": "Documentație", "getting_started.heading": "Începe", "getting_started.invite": "Invită prieteni", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 91ecbbce7..a3467785a 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -70,7 +70,7 @@ "compose_form.direct_message_warning": "Tento príspevok bude videný výhradne iba spomenutými užívateľmi. Ber ale na vedomie že správci tvojej a všetkých iných zahrnutých instancií majú možnosť skontrolovať túto správu.", "compose_form.direct_message_warning_learn_more": "Zistiť viac", "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": "Váš účet nie je {locked}. Ktokoľvek ťa môže nasledovať a vidieť tvoje správy pre sledujúcich.", "compose_form.lock_disclaimer.lock": "zamknutý", "compose_form.placeholder": "Čo máš na mysli?", "compose_form.publish": "Pošli", diff --git a/config/locales/de.yml b/config/locales/de.yml index a5315f9d3..e68448abd 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1,7 +1,7 @@ --- de: about: - about_hashtag_html: Dies sind öffentliche Beiträge, die mit #%{hashtag} getaggt wurden. Wenn du ein Konto irgendwo im Fediversum besitzt, kannst du mit ihnen interagieren. + about_hashtag_html: Dies sind öffentliche Beiträge, die mit #%{hashtag} getaggt wurden. Wenn du irgendwo im Fediversum ein Konto besitzt, kannst du mit ihnen interagieren. about_mastodon_html: Mastodon ist ein soziales Netzwerk. Es basiert auf offenen Web-Protokollen und freier, quelloffener Software. Es ist dezentral (so wie E-Mail!). about_this: Über diese Instanz administered_by: 'Administriert von:' @@ -22,7 +22,7 @@ de: not_a_product_title: Du bist ein Mensch und keine Ware real_conversation_body: Mit 500 Zeichen pro Beitrag und Features wie Inhalts- und Bilderwarnungen kannst du dich so ausdrücken, wie du es möchtest. real_conversation_title: Geschaffen für echte Gespräche - within_reach_body: Verschiedene Apps für iOS, Android und andere Plattformen erlauben dir, dank unseres blühenden API-Ökosystems, dich von überall auf dem Laufenden zu halten. + within_reach_body: Verschiedene Apps für iOS, Android und andere Plattformen erlauben es dir, dank unseres blühenden API-Ökosystems, dich von überall auf dem Laufenden zu halten. within_reach_title: Immer für dich da generic_description: "%{domain} ist ein Server im Netzwerk" hosted_on: Mastodon, beherbergt auf %{domain} @@ -44,8 +44,8 @@ de: choices_html: "%{name} empfiehlt:" follow: Folgen followers: - one: Folgende - other: Follower + one: Folgender + other: Folgende following: Folgt joined: Beigetreten am %{date} last_active: zuletzt aktiv @@ -81,7 +81,7 @@ de: accounts: are_you_sure: Bist du sicher? avatar: Profilbild - by_domain: Domäne + by_domain: Domain change_email: changed_msg: E-Mail-Adresse des Kontos erfolgreich geändert! current_email: Aktuelle E-Mail-Adresse @@ -105,7 +105,7 @@ de: enable: Freischalten enabled: Freigegeben feed_url: Feed-URL - followers: Folger + followers: Folgende followers_local: "(%{local} lokal)" followers_url: URL des Folgenden follows: Folgt @@ -194,17 +194,17 @@ de: disable_user: "%{name} hat den Login für Benutzer:in %{target} deaktiviert" enable_custom_emoji: "%{name} hat das %{target} Emoji aktiviert" enable_user: "%{name} hat die Anmeldung für di:en Benutzer:in %{target} aktiviert" - memorialize_account: "%{name} hat %{target}s Profil in eine Gedenkseite umgewandelt" + memorialize_account: "%{name} hat %{target}s Konto in eine Gedenkseite umgewandelt" promote_user: "%{name} hat %{target} befördert" remove_avatar_user: "%{name} hat das Profilbild von %{target} entfernt" reopen_report: "%{name} hat die Meldung %{target} wieder geöffnet" reset_password_user: "%{name} hat das Passwort für di:en Benutzer:in %{target} zurückgesetzt" resolve_report: "%{name} hat die Meldung %{target} bearbeitet" - silence_account: "%{name} hat %{target}s Account stummgeschaltet" - suspend_account: "%{name} hat %{target}s Account gesperrt" + silence_account: "%{name} hat %{target}s Konto stummgeschaltet" + suspend_account: "%{name} hat %{target}s Konto gesperrt" unassigned_report: "%{name} hat die Zuweisung der Meldung %{target} entfernt" - unsilence_account: "%{name} hat die Stummschaltung von %{target}s Account aufgehoben" - unsuspend_account: "%{name} hat die Sperrung von %{target}s Account aufgehoben" + unsilence_account: "%{name} hat die Stummschaltung von %{target}s Konto aufgehoben" + unsuspend_account: "%{name} hat die Sperrung von %{target}s Konto aufgehoben" update_custom_emoji: "%{name} hat das %{target} Emoji aktualisiert" update_status: "%{name} hat den Status von %{target} aktualisiert" deleted_status: "(gelöschter Beitrag)" @@ -300,12 +300,12 @@ de: title: Neue E-Mail-Domain-Blockade title: E-Mail-Domain-Blockade followers: - back_to_account: Zurück zum Account + back_to_account: Zurück zum Konto title: "%{acct}'s Follower" instances: delivery_available: Zustellung ist verfügbar known_accounts: - one: "%{count} bekannter Account" + one: "%{count} bekanntes Konto" other: "%{count} bekannte Accounts" moderation: all: Alle @@ -506,8 +506,8 @@ de: invalid_reset_password_token: Das Token zum Zurücksetzen des Passworts ist ungültig oder abgelaufen. Bitte fordere ein neues an. login: Anmelden logout: Abmelden - migrate_account: Ziehe zu einem anderen Account um - migrate_account_html: Wenn du es wünschst diesen Account zu einem anderen umzuziehen, dann kannst du es hier einstellen. + migrate_account: Ziehe zu einem anderen Konto um + migrate_account_html: Wenn du wünschst, dieses Konto zu einem anderen umzuziehen, kannst du dies hier einstellen. or: oder or_log_in_with: Oder anmelden mit providers: @@ -521,7 +521,7 @@ de: set_new_password: Neues Passwort setzen authorize_follow: already_following: Du folgst diesem Konto bereits - error: Das Profil konnte nicht geladen werden + error: Das Remote-Konto konnte nicht geladen werden follow: Folgen follow_request: 'Du hast eine Folgeanfrage gesendet an:' following: 'Erfolg! Du folgst nun:' @@ -655,7 +655,7 @@ de: table: expires_at: Läuft ab uses: Verwendungen - title: Leute Einladen + title: Leute einladen lists: errors: limit: Du hast die maximale Anzahl an Listen erreicht @@ -664,10 +664,10 @@ de: images_and_video: Es kann kein Video an einen Beitrag, der bereits Bilder enthält, angehängt werden too_many: Es können nicht mehr als 4 Bilder angehängt werden migrations: - acct: benutzername@domain des neuen Accounts + acct: benutzername@domain des neuen Kontos currently_redirecting: 'Deine Profilweiterleitung wurde gesetzt auf:' proceed: Speichern - updated_msg: Deine Account-Migrationseinstellungen wurden erfolgreich aktualisiert! + updated_msg: Deine Konto-Migrationseinstellungen wurden erfolgreich aktualisiert! moderation: title: Moderation notification_mailer: @@ -729,7 +729,7 @@ de: remote_follow: acct: Profilname@Domain, von wo aus du dieser Person folgen möchtest missing_resource: Die erforderliche Weiterleitungs-URL für dein Konto konnte nicht gefunden werden - no_account_html: Noch keinen Account? Du kannst dich hier anmelden + no_account_html: Noch kein Konto? Du kannst dich hier anmelden proceed: Weiter prompt: 'Du wirst dieser Person folgen:' reason_html: "Warum ist dieser Schritt erforderlich?%{instance} ist möglicherweise nicht der Server auf dem du registriert bist, also müssen wir dich erst auf deinen Heimserver weiterleiten." @@ -774,7 +774,7 @@ de: weibo: Weibo current_session: Aktuelle Sitzung description: "%{browser} auf %{platform}" - explanation: Dies sind die Webbrowser, die derzeit in dein Mastodon-Konto eingeloggt sind. + explanation: Dies sind die Webbrowser, die derzeit in deinem Mastodon-Konto eingeloggt sind. ip: IP-Adresse platforms: adobe_air: Adobe Air @@ -801,7 +801,7 @@ de: export: Datenexport followers: Autorisierte Folgende import: Datenimport - migrate: Account-Umzug + migrate: Konto-Umzug notifications: Benachrichtigungen preferences: Einstellungen settings: Einstellungen @@ -842,7 +842,7 @@ de: stream_entries: pinned: Angehefteter Beitrag reblogged: teilte - sensitive_content: Heikle Inhalte + sensitive_content: Sensible Inhalte terms: body_html: |

Datenschutzerklärung

@@ -949,33 +949,33 @@ de: manual_instructions: 'Wenn du den QR-Code nicht einlesen kannst und ihn manuell eingeben musst, ist hier das Klartext-Geheimnis:' recovery_codes: Wiederherstellungs-Codes sichern recovery_codes_regenerated: Wiederherstellungscodes erfolgreich neu generiert - recovery_instructions_html: Wenn du den Zugang zu deinem Telefon verlieren solltest, kannst du einen untenstehenden Wiederherstellungscodes benutzen, um wieder auf dein Konto zugreifen zu können. Bewahre die Wiederherstellungscodes gut auf. Du könntest sie beispielsweise ausdrucken und bei deinen restlichen wichtigen Dokumenten aufbewahren. + recovery_instructions_html: Wenn du den Zugang zu deinem Telefon verlieren solltest, kannst du einen untenstehenden Wiederherstellungscode benutzen, um wieder auf dein Konto zugreifen zu können. Bewahre die Wiederherstellungscodes gut auf. Du könntest sie beispielsweise ausdrucken und bei deinen restlichen wichtigen Dokumenten aufbewahren. setup: Einrichten wrong_code: Der eingegebene Code war ungültig! Stimmen Serverzeit und Gerätezeit? user_mailer: backup_ready: - explanation: Du hast ein vollständiges Backup von deinem Mastodon-Account angefragt. Es kann jetzt heruntergeladen werden! + explanation: Du hast ein vollständiges Backup von deinem Mastodon-Konto angefragt. Es kann jetzt heruntergeladen werden! subject: Dein Archiv ist bereit zum Download title: Archiv-Download warning: explanation: - disable: Solange dein Account eingefroren ist sind deine Benutzerdaten intakt, aber du kannst nichts tun bis dein Account entsperrt wurde. - silence: Solange dein Account limitiert ist können nur Leute, die dir bereits folgen deine Beiträge auf dem Server sehen und es könnte sein, dass du von verschiedenen öffentlichen Listungen ausgeschlossen wirst. Andererseits können andere dir manuell folgen. - suspend: Dein Account wurde gesperrt und alle deine Beiträge und hochgeladenen Medien wurden unwiderruflich vom Server und anderen Servern wo du Follower hattest gelöscht. + disable: Solange dein Konto eingefroren ist, sind deine Benutzerdaten intakt; aber du kannst nichts tun, bis dein Konto entsperrt wurde. + silence: Solange dein Konto limitiert ist, können nur die Leute, die dir bereits folgen, deine Beiträge auf dem Server sehen und es könnte sein, dass du von verschiedenen öffentlichen Listungen ausgeschlossen wirst. Andererseits können andere dir manuell folgen. + suspend: Dein Konto wurde gesperrt und alle deine Beiträge und hochgeladenen Medien wurden unwiderruflich vom Server und anderen Servern, bei denen du Folgende hattest, gelöscht. review_server_policies: Serverrichtlinien ansehen subject: - disable: Dein Account %{acct} wurde eingefroren + disable: Dein Konto %{acct} wurde eingefroren none: Warnung für %{acct} - silence: Dein Account %{acct} wurde limitiert - suspend: Dein Account %{acct} wurde gesperrt + silence: Dein Konto %{acct} wurde limitiert + suspend: Dein Konto %{acct} wurde gesperrt title: - disable: Account eingefroren + disable: Konto eingefroren none: Warnung - silence: Account limitiert - suspend: Account gesperrt + silence: Konto limitiert + suspend: Konto gesperrt welcome: edit_profile_action: Profil einstellen - edit_profile_step: Du kannst dein Profil anpassen, indem du einen Avatar oder ein Titelbild hochlädst oder deinen Anzeigenamen änderst und mehr. Wenn du deine Follower vorher überprüfen möchtest, bevor sie dir folgen können, dann kannst du dein Profil sperren. + edit_profile_step: Du kannst dein Profil anpassen, indem du einen Avatar oder ein Titelbild hochlädst oder deinen Anzeigenamen änderst und mehr. Wenn du deine Folgenden vorher überprüfen möchtest, bevor sie dir folgen können, dann kannst du dein Profil sperren. explanation: Hier sind ein paar Tipps, um loszulegen final_action: Fang an zu posten final_step: 'Fang an zu posten! Selbst ohne Follower werden deine öffentlichen Beitrage von anderen gesehen, zum Beispiel auf der lokalen Zeitleiste oder in Hashtags. Vielleicht möchtest du dich vorstellen mit dem #introductions-Hashtag.' @@ -998,5 +998,5 @@ de: seamless_external_login: Du bist angemeldet über einen Drittanbieter-Dienst, weswegen Passwort- und E-Maileinstellungen nicht verfügbar sind. signed_in_as: 'Angemeldet als:' verification: - explanation_html: 'Du kannst bestätigen, dass die Links in deinen Profil-Metadaten dir gehören. Dafür muss die verlinkte Website einen Link zurück auf dein Mastodon-Profil enthalten. Dieser Link muss ein rel="me"-Attribut enthalten. Der Linktext is dabei egal. Hier ist ein Beispiel:' + explanation_html: 'Du kannst bestätigen, dass die Links in deinen Profil-Metadaten dir gehören. Dafür muss die verlinkte Website einen Link zurück auf dein Mastodon-Profil enthalten. Dieser Link muss ein rel="me"-Attribut enthalten. Der Linktext ist dabei egal. Hier ist ein Beispiel:' verification: Verifizierung diff --git a/config/locales/devise.sr.yml b/config/locales/devise.sr.yml index 9d1359695..9061e01d4 100644 --- a/config/locales/devise.sr.yml +++ b/config/locales/devise.sr.yml @@ -17,11 +17,33 @@ sr: unconfirmed: Пре наставка морате потврдити свој налог. mailer: confirmation_instructions: + action: Потврдите адресу е-поште + action_with_app: Потврди и врати се на %{app} + explanation: Направили сте налог на %{host} са адресом ове е-поште. На један клик сте удаљени од активирања. Ако ово нисте ви, молимо игноришите ову е-пошту. + extra_html: Молимо да такође проверите правила ове инстанце и наше услове коришћења. subject: 'Мастодонт: Упутство за потврду корисничког налога на инстанци %{instance}' + title: Потврдите адресу е-поште + email_changed: + explanation: 'Адреса ове е-поште за ваш налог ће бити промењена у:' + extra: Ако нисте променили вашу е-пошту, сасвим је могуће да је неко други добио приступ вашем налогу. Молимо промените лозинку одмах или контактирајте администратора инстанце ако сте закључани изван вашег налога. + subject: 'Мастодон: Е-пошта промењена' + title: Нова адреса е-поште password_change: + explanation: Лозинка вашег налога је промењена. + extra: Ако нисте променили вашу е-пошту, сасвим је могуће да је неко други добио приступ вашем налогу. Молимо промените лозинку одмах или контактирајте администратора инстанце ако сте закључани изван вашег налога. subject: 'Мастодонт: Лозинка промењена' + title: Лозинка промењена + reconfirmation_instructions: + explanation: Потврдите нову адресу да бисте променили е-пошту. + extra: Ако ова промена није иницирана са ваше стране, молимо игноришите ову е-пошту. Адреса е-пошта за овај Мастодон налог неће бити промењена док не приступите повезници/линку изнад. + subject: 'Мастодон: Потврдите е-пошту за %{instance}' + title: Потврдите адресу е-поште reset_password_instructions: + action: Лозинка промењена + explanation: Затражили сте нову лозинку за ваш налог. + extra: Ако нисте затражили ово, молимо игноришите ову е-пошту. Ваша лозинка неће бити промењена док не приступите повезници/линку изнад и не направите нову. subject: 'Мастодонт: Упутство за ресетовање лозинке' + title: Лозинка ресетована unlock_instructions: subject: 'Мастодонт: Упутство за откључавање корисничког налога' omniauth_callbacks: diff --git a/config/locales/ro.yml b/config/locales/ro.yml index 60a38b7c6..aa4d3c967 100644 --- a/config/locales/ro.yml +++ b/config/locales/ro.yml @@ -1,6 +1,8 @@ --- ro: about: + features: + not_a_product_title: Ești o persoană, nu un produs hosted_on: Mastodon găzduit de %{domain} accounts: posts: @@ -64,7 +66,7 @@ ro: success_msg: Contul tău a fost șterg. Nu mai poate fi recuperat :D warning_html: Doar ștergerea conținutului de pe acest server este garantată. Conținutul tău care a fost redistribuit în alte instațe e posibil să lase urme. Serverele deconecate sau care nu mai sunt abonate la actualizările contului tău nu își vor mai actualiza baza de date. directories: - explanation: Descoperă utilizatori în funcție de interesele lor + explanation: Descoperă oameni și companii în funcție de interesele lor explore_mastodon: Explorează %{title} people: few: "%{count} persoană" diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index d6e7942b3..3958e315f 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -10,14 +10,14 @@ de: type_html: Wähle aus, was du mit %{acct} machen möchtest warning_preset_id: Optional. Du kannst immer noch eigenen Text an das Ende der Vorlage hinzufügen defaults: - autofollow: Leute die sich über deine Einladung registrieren werden dir automatisch folgen + autofollow: Leute, die sich über deine Einladung registrieren, werden dir automatisch folgen avatar: PNG, GIF oder JPG. Maximal %{size}. Wird auf %{dimensions} px herunterskaliert bot: Dieses Konto führt lediglich automatisierte Aktionen durch und wird möglicherweise nicht überwacht context: Ein oder mehrere Aspekte, wo der Filter greifen soll digest: Wenn du lange Zeit inaktiv bist, wird dir eine Zusammenfassung von Erwähnungen in deiner Abwesenheit zugeschickt - discoverable_html: Das Verzeichnis lässt dich neue Benutzerkonten finden basierend auf Interessen und Aktivitäten. Dies benötigt mindestens %{min_followers} Follower - email: Du wirst ein Bestätigungs-E-Mail erhalten - fields: Du kannst bis zu 4 Elemente als Tabelle dargestellt auf deinem Profil anzeigen lassen + discoverable_html: Das Verzeichnis lässt dich basierend auf Interessen und Aktivitäten neue Benutzerkonten finden. Dies benötigt mindestens %{min_followers} Follower + email: Du wirst eine Bestätigungs-E-Mail erhalten + fields: Du kannst bis zu 4 Elemente auf deinem Profil anzeigen lassen, die als Tabelle dargestellt werden header: PNG, GIF oder JPG. Maximal %{size}. Wird auf %{dimensions} px herunterskaliert inbox_url: Kopiere die URL von der Startseite des gewünschten Relays irreversible: Gefilterte Beiträge werden unwiderruflich gefiltert, selbst wenn der Filter später entfernt wurde @@ -31,11 +31,11 @@ de: setting_display_media_default: Verstecke Medien, die als sensibel markiert sind setting_display_media_hide_all: Alle Medien immer verstecken setting_display_media_show_all: Medien, die als sensibel markiert sind, immer anzeigen - setting_hide_network: Wem du folgst und wer dir folgt wird in deinem Profil nicht angezeigt + setting_hide_network: Wem du folgst und wer dir folgt, wird in deinem Profil nicht angezeigt setting_noindex: Betrifft dein öffentliches Profil und deine Beiträge setting_theme: Wirkt sich darauf aus, wie Mastodon aussieht, egal auf welchem Gerät du eingeloggt bist. username: Dein Benutzer:innen-Name wird auf %{domain} nur einmal vorkommen - whole_word: Wenn das Schlüsselwort oder -phrase nur Buchstaben und Zahlen enthält, wird es nur angewendet werden, wenn es dem ganzen Wort entspricht + whole_word: Wenn das Schlagwort oder die Phrase nur Buchstaben und Zahlen enthält, wird es nur angewendet, wenn es dem ganzen Wort entspricht imports: data: CSV-Datei, die aus einer anderen Mastodon-Instanz exportiert wurde sessions: @@ -60,7 +60,7 @@ de: suspend: Deaktivieren und unwiderruflich Benutzerdaten löschen warning_preset_id: Benutze eine Warnungsvorlage defaults: - autofollow: Einladen, um deinen Account zu folgen + autofollow: Einladen, um deinem Account zu folgen avatar: Profilbild bot: Dieser Benutzer ist ein Bot chosen_languages: Sprachen filtern @@ -76,7 +76,7 @@ de: fields: Profil-Metadaten header: Kopfbild inbox_url: Inbox-URL des Relays - irreversible: Fallen lassen anstatt es zu verstecken + irreversible: Verwerfen statt verstecken locale: Sprache der Benutzeroberfläche locked: Gesperrtes Profil max_uses: Maximale Verwendungen @@ -90,28 +90,28 @@ de: setting_boost_modal: Bestätigungsdialog anzeigen, bevor ein Beitrag geteilt wird setting_default_language: Beitragssprache setting_default_privacy: Beitragssichtbarkeit - setting_default_sensitive: Medien immer als heikel markieren + setting_default_sensitive: Medien immer als sensibel markieren setting_delete_modal: Bestätigungsdialog anzeigen, bevor ein Beitrag gelöscht wird setting_display_media: Medien-Anzeige setting_display_media_default: Standard setting_display_media_hide_all: Alle verstecken setting_display_media_show_all: Alle anzeigen - setting_expand_spoilers: Toots mit Inhaltswarnungen immer ausklappen + setting_expand_spoilers: Beiträge mit Inhaltswarnungen immer ausklappen setting_hide_network: Blende dein Netzwerk aus setting_noindex: Suchmaschinen-Indexierung verhindern setting_reduce_motion: Bewegung in Animationen verringern setting_system_font_ui: Standardschriftart des Systems verwenden setting_theme: Theme der Website - setting_unfollow_modal: Bestätigungsdialog anzeigen, bevor jemand entfolgt wird + setting_unfollow_modal: Bestätigungsdialog anzeigen, bevor jemandem entfolgt wird severity: Schweregrad type: Importtyp username: Profilname - username_or_email: Profilname oder Email + username_or_email: Profilname oder E-Mail whole_word: Ganzes Wort interactions: must_be_follower: Benachrichtigungen von Nicht-Folgenden blockieren must_be_following: Benachrichtigungen von Profilen blockieren, denen ich nicht folge - must_be_following_dm: Private Nachrichten von Profilen denen ich nicht folge blockieren + must_be_following_dm: Private Nachrichten von Profilen, denen ich nicht folge, blockieren notification_emails: digest: Schicke Übersichts-E-Mails favourite: E-Mail senden, wenn jemand meinen Beitrag favorisiert diff --git a/config/locales/simple_form.sr.yml b/config/locales/simple_form.sr.yml index c6294d4ba..d88c40323 100644 --- a/config/locales/simple_form.sr.yml +++ b/config/locales/simple_form.sr.yml @@ -2,12 +2,21 @@ sr: simple_form: hints: + account_warning_preset: + text: Можете користити синтаксу труба, као што су нпр. УРЛ-ова, тарабе и помињања + admin_account_action: + send_email_notification: Корисник ће добити објашњење тога шта му се десило са налога + text_html: Опционално. Можете користити синтаксу труба. Можете додати упозоравајућа преподешавање да сачувате време + type_html: Изаберите шта да радите са %{acct} + warning_preset_id: Опционално. Можете и даље додати прилагођени текст на крај пресета defaults: autofollow: Особе које се пријаве кроз позивнице ће вас аутоматски запратити avatar: PNG, GIF или JPG. Највише %{size}. Биће смањена на %{dimensions}px bot: Овај налог углавном врши аутоматизоване радње и можда се не надгледа context: Један или више контекста у којима треба да се примени филтер digest: Послато после дужег периода неактивности са прегледом свих битних ствари које сте добили док сте били одсутни + discoverable_html: Директоријум омогућава људима да пронађу налоге засноване на интересима и активности. Захтева бар %{min_followers} пратиоца + email: Биће вам послата е-пошта са потврдом fields: Можете имати до 4 ставке приказане као табела на вашем профилу header: PNG, GIF или JPG. Највише %{size}. Биће смањена на %{dimensions}px inbox_url: Копирајте URL са насловне стране релеја који желите користити @@ -17,6 +26,7 @@ sr: password: Користите најмање 8 знакова phrase: Биће упарена без обзира на велико или мало слово у тексту или упозорења о садржају трубе scopes: Којим API-јима ће апликација дозволити приступ. Ако изаберете опсег највишег нивоа, не морате одабрати појединачне. + setting_aggregate_reblogs: Не показуј нова дељења за трубе које су недавно подељене (утиче само на недавно примљена дељења) setting_default_language: Језик ваших труба може бити аутоматски откривен, али није увек прецизан setting_hide_network: Кога пратите и ко вас прати неће бити приказано на вашем профилу setting_noindex: Утиче на Ваш јавни профил и статусне стране @@ -33,6 +43,10 @@ sr: fields: name: Етикета value: Садржај + account_warning_preset: + text: Текст пресета + admin_account_action: + warning_preset_id: Користи упозоравајући пресет defaults: autofollow: Позовите да прати ваш налог avatar: Аватар -- cgit From 69f782b54d035789a6386ed979940dd9719af1a1 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 18 Jan 2019 01:02:51 +0100 Subject: Fix code style of regeneration-related code (#9843) --- app/models/user.rb | 3 ++- app/services/precompute_feed_service.rb | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/models/user.rb b/app/models/user.rb index 8b374c182..5aa5c2b15 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -362,7 +362,8 @@ class User < ApplicationRecord end def regenerate_feed! - Redis.current.setnx("account:#{account_id}:regeneration", true) && Redis.current.expire("account:#{account_id}:regeneration", 1.day.seconds) + return unless Redis.current.setnx("account:#{account_id}:regeneration", true) + Redis.current.expire("account:#{account_id}:regeneration", 1.day.seconds) RegenerationWorker.perform_async(account_id) end diff --git a/app/services/precompute_feed_service.rb b/app/services/precompute_feed_service.rb index 4f771ff72..076dedaca 100644 --- a/app/services/precompute_feed_service.rb +++ b/app/services/precompute_feed_service.rb @@ -3,6 +3,7 @@ class PrecomputeFeedService < BaseService def call(account) FeedManager.instance.populate_feed(account) + ensure Redis.current.del("account:#{account.id}:regeneration") end end -- cgit From a492a9bcd355d4f0998990905177ac4f9699cc3c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 18 Jan 2019 10:25:44 +0100 Subject: Add information about how to opt-in to the directory on the directory (#9834) Fix #9833 --- app/javascript/styles/mastodon/widgets.scss | 27 +++++++++++++++++++++++++++ app/views/directories/index.html.haml | 18 ++++++++++++++++-- config/locales/en.yml | 3 +++ 3 files changed, 46 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index d44a1ef06..0699900dc 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -480,3 +480,30 @@ $fluid-breakpoint: $maximum-width + 20px; } } } + +.notice-widget { + margin-bottom: 10px; + color: $darker-text-color; + + p { + margin-bottom: 10px; + + &:last-child { + margin-bottom: 0; + } + } + + a { + font-size: 14px; + line-height: 20px; + text-decoration: none; + font-weight: 500; + color: $ui-highlight-color; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } +} diff --git a/app/views/directories/index.html.haml b/app/views/directories/index.html.haml index 88706def7..a8aa68cc4 100644 --- a/app/views/directories/index.html.haml +++ b/app/views/directories/index.html.haml @@ -41,8 +41,22 @@ = paginate @accounts .column-1 - - if @tags.empty? - .nothing-here.nothing-here--flexible + - if user_signed_in? + .box-widget.notice-widget + - if current_account.discoverable? + - if current_account.followers_count < Account::MIN_FOLLOWERS_DISCOVERY + %p= t('directories.enabled_but_waiting', min_followers: Account::MIN_FOLLOWERS_DISCOVERY) + - else + %p= t('directories.enabled') + - else + %p= t('directories.how_to_enable') + + = link_to settings_profile_path do + = t('settings.edit_profile') + = fa_icon 'chevron-right fw' + + - if @tags.empty? && !user_signed_in? + .nothing-here - else - @tags.each do |tag| .directory__tag{ class: tag.id == @tag&.id ? 'active' : nil } diff --git a/config/locales/en.yml b/config/locales/en.yml index 8ad5ecb06..10749b21b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -553,8 +553,11 @@ en: warning_title: Disseminated content availability directories: directory: Profile directory + enabled: You are currently listed in the directory. + enabled_but_waiting: You have opted-in to be listed in the directory, but you do not have the minimum number of followers (%{min_followers}) to be listed yet. explanation: Discover users based on their interests explore_mastodon: Explore %{title} + how_to_enable: You are not currently opted-in to the directory. You can opt-in below. Use hashtags in your bio text to be listed under specific hashtags! people: one: "%{count} person" other: "%{count} people" -- cgit From 31f396b57dea684685d0affc3727a75eed2f38c9 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 18 Jan 2019 15:56:21 +0100 Subject: Add support for non-public reblogs from ActivityPub (#9841) Fix #9838 --- app/lib/activitypub/activity/announce.rb | 14 +++++++++++++- app/models/status.rb | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb index 1147a4481..34d1b7cbd 100644 --- a/app/lib/activitypub/activity/announce.rb +++ b/app/lib/activitypub/activity/announce.rb @@ -17,7 +17,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity uri: @json['id'], created_at: @json['published'], override_timestamps: @options[:override_timestamps], - visibility: original_status.visibility + visibility: visibility_from_audience ) distribute(status) @@ -26,6 +26,18 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity private + def visibility_from_audience + if equals_or_includes?(@json['to'], ActivityPub::TagManager::COLLECTIONS[:public]) + :public + elsif equals_or_includes?(@json['cc'], ActivityPub::TagManager::COLLECTIONS[:public]) + :unlisted + elsif equals_or_includes?(@json['to'], @account.followers_url) + :private + else + :direct + end + end + def announceable?(status) status.account_id == @account.id || status.public_visibility? || status.unlisted_visibility? end diff --git a/app/models/status.rb b/app/models/status.rb index 0705ba4c1..035423b40 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -478,7 +478,7 @@ class Status < ApplicationRecord return if direct_visibility? account&.increment_count!(:statuses_count) - reblog&.increment_count!(:reblogs_count) if reblog? + reblog&.increment_count!(:reblogs_count) if reblog? && (public_visibility? || unlisted_visibility?) thread&.increment_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?) end @@ -486,7 +486,7 @@ class Status < ApplicationRecord return if direct_visibility? || marked_for_mass_destruction? account&.decrement_count!(:statuses_count) - reblog&.decrement_count!(:reblogs_count) if reblog? + reblog&.decrement_count!(:reblogs_count) if reblog? && (public_visibility? || unlisted_visibility?) thread&.decrement_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?) end -- cgit From 75b1488cf4dfe54260deff8df20e5e9b9fd90aea Mon Sep 17 00:00:00 2001 From: ThibG Date: Fri, 18 Jan 2019 15:56:55 +0100 Subject: Add tombstones for remote statuses (#9830) * Add Tombstone model to remember object deletion * Do not recreate a status if it has been deleted * Record Tombstone for remote deleted items Also, only record deleted items from same-host actors * Clear an user's tombstones when their key change --- app/lib/activitypub/activity/create.rb | 1 + app/lib/activitypub/activity/delete.rb | 14 ++++++++++++-- app/models/tombstone.rb | 15 +++++++++++++++ app/services/activitypub/process_account_service.rb | 6 ++++++ db/migrate/20190117114553_create_tombstones.rb | 12 ++++++++++++ db/schema.rb | 12 +++++++++++- 6 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 app/models/tombstone.rb create mode 100644 db/migrate/20190117114553_create_tombstones.rb (limited to 'app') diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 665a9fbdc..b49657d4b 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -6,6 +6,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity def perform return if unsupported_object_type? || invalid_origin?(@object['id']) + return if Tombstone.exists?(uri: @object['id']) RedisLock.acquire(lock_options) do |lock| if lock.acquired? diff --git a/app/lib/activitypub/activity/delete.rb b/app/lib/activitypub/activity/delete.rb index ca3cf387e..dc76dd3e2 100644 --- a/app/lib/activitypub/activity/delete.rb +++ b/app/lib/activitypub/activity/delete.rb @@ -21,8 +21,9 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity def delete_note return if object_uri.nil? - RedisLock.acquire(lock_options) do |_lock| - delete_later!(object_uri) + unless invalid_origin?(object_uri) + RedisLock.acquire(lock_options) { |_lock| delete_later!(object_uri) } + Tombstone.find_or_create_by(uri: object_uri, account: @account) end @status = Status.find_by(uri: object_uri, account: @account) @@ -74,4 +75,13 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity def lock_options { redis: Redis.current, key: "create:#{object_uri}" } end + + def invalid_origin?(url) + return true if unsupported_uri_scheme?(url) + + needle = Addressable::URI.parse(url).host + haystack = Addressable::URI.parse(@account.uri).host + + !haystack.casecmp(needle).zero? + end end diff --git a/app/models/tombstone.rb b/app/models/tombstone.rb new file mode 100644 index 000000000..35b7337ff --- /dev/null +++ b/app/models/tombstone.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: tombstones +# +# id :bigint(8) not null, primary key +# account_id :bigint(8) +# uri :string not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class Tombstone < ApplicationRecord +end diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index d6c791b44..487456f3a 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -33,6 +33,8 @@ class ActivityPub::ProcessAccountService < BaseService after_protocol_change! if protocol_changed? after_key_change! if key_changed? && !@options[:signed_with_known_key] + clear_tombstones! if key_changed? + unless @options[:only_key] check_featured_collection! if @account.featured_collection_url.present? check_links! unless @account.fields.empty? @@ -209,6 +211,10 @@ class ActivityPub::ProcessAccountService < BaseService !@old_public_key.nil? && @old_public_key != @account.public_key end + def clear_tombstones! + Tombstone.delete_all(account_id: @account.id) + end + def protocol_changed? !@old_protocol.nil? && @old_protocol != @account.protocol end diff --git a/db/migrate/20190117114553_create_tombstones.rb b/db/migrate/20190117114553_create_tombstones.rb new file mode 100644 index 000000000..06d6d8c5a --- /dev/null +++ b/db/migrate/20190117114553_create_tombstones.rb @@ -0,0 +1,12 @@ +class CreateTombstones < ActiveRecord::Migration[5.2] + def change + create_table :tombstones do |t| + t.belongs_to :account, foreign_key: { on_delete: :cascade } + t.string :uri, null: false + + t.timestamps + end + + add_index :tombstones, :uri + end +end diff --git a/db/schema.rb b/db/schema.rb index 9380362e1..3487adf08 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_01_03_124754) do +ActiveRecord::Schema.define(version: 2019_01_17_114553) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -615,6 +615,15 @@ ActiveRecord::Schema.define(version: 2019_01_03_124754) do t.index ["name"], name: "index_tags_on_name", unique: true end + create_table "tombstones", force: :cascade do |t| + t.bigint "account_id" + t.string "uri", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id"], name: "index_tombstones_on_account_id" + t.index ["uri"], name: "index_tombstones_on_uri" + end + create_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.datetime "created_at", null: false @@ -743,6 +752,7 @@ ActiveRecord::Schema.define(version: 2019_01_03_124754) do add_foreign_key "statuses_tags", "tags", name: "fk_3081861e21", on_delete: :cascade add_foreign_key "stream_entries", "accounts", name: "fk_5659b17554", on_delete: :cascade add_foreign_key "subscriptions", "accounts", name: "fk_9847d1cbb5", on_delete: :cascade + add_foreign_key "tombstones", "accounts", on_delete: :cascade add_foreign_key "users", "accounts", name: "fk_50500f500d", on_delete: :cascade add_foreign_key "users", "invites", on_delete: :nullify add_foreign_key "users", "oauth_applications", column: "created_by_application_id", on_delete: :nullify -- cgit From aeb124491d169c2a75ee6c20520859dc865511ed Mon Sep 17 00:00:00 2001 From: ThibG Date: Fri, 18 Jan 2019 15:57:19 +0100 Subject: Reject existing Follow in addition to sending a Block (#9811) Mastodon expects remote servers to remove follow relationships upon receiving a Block. However, the spec only evokes Block activities in a C2S context, never in a S2S context. This PR, in addition to federating the Block, explicitly sends a Reject for any affected follow relationship, which makes a bit more sense with regards to the spec. --- app/services/unfollow_service.rb | 15 +++++++++++++++ spec/services/unfollow_service_spec.rb | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+) (limited to 'app') diff --git a/app/services/unfollow_service.rb b/app/services/unfollow_service.rb index 03e45912d..95da2a667 100644 --- a/app/services/unfollow_service.rb +++ b/app/services/unfollow_service.rb @@ -20,6 +20,7 @@ class UnfollowService < BaseService follow.destroy! create_notification(follow) unless @target_account.local? + create_reject_notification(follow) if @target_account.local? && !@source_account.local? UnmergeWorker.perform_async(@target_account.id, @source_account.id) follow end @@ -42,6 +43,12 @@ class UnfollowService < BaseService end end + def create_reject_notification(follow) + # Rejecting an already-existing follow request + return unless follow.account.activitypub? + ActivityPub::DeliveryWorker.perform_async(build_reject_json(follow), follow.target_account_id, follow.account.inbox_url) + end + def build_json(follow) ActiveModelSerializers::SerializableResource.new( follow, @@ -50,6 +57,14 @@ class UnfollowService < BaseService ).to_json end + def build_reject_json(follow) + ActiveModelSerializers::SerializableResource.new( + follow, + serializer: ActivityPub::RejectFollowSerializer, + adapter: ActivityPub::Adapter + ).to_json + end + def build_xml(follow) OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.unfollow_salmon(follow)) end diff --git a/spec/services/unfollow_service_spec.rb b/spec/services/unfollow_service_spec.rb index c5914c818..8a2881ab1 100644 --- a/spec/services/unfollow_service_spec.rb +++ b/spec/services/unfollow_service_spec.rb @@ -56,4 +56,22 @@ RSpec.describe UnfollowService, type: :service do expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once end end + + describe 'remote ActivityPub (reverse)' do + let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox')).account } + + before do + bob.follow!(sender) + stub_request(:post, 'http://example.com/inbox').to_return(status: 200) + subject.call(bob, sender) + end + + it 'destroys the following relation' do + expect(bob.following?(sender)).to be false + end + + it 'sends a reject activity' do + expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once + end + end end -- cgit From 32daecffefa5e633bf104ad4fac33621f6ea8201 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 18 Jan 2019 20:58:00 +0100 Subject: Fix REST API showing non-public reblogs for a given status (#9850) --- app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb index 4315b0283..6851099f6 100644 --- a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb @@ -25,7 +25,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController end def paginated_statuses - Status.where(reblog_of_id: @status.id).paginate_by_max_id( + Status.where(reblog_of_id: @status.id).where(visibility: [:public, :unlisted]).paginate_by_max_id( limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id] -- cgit From b506ce119766bb3308f934e2d3de143b3ac6f5ad Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 18 Jan 2019 20:58:11 +0100 Subject: Fix new hashtag page's items not being full-width on mobile (#9852) Fix #9845 --- .../mastodon/features/status/components/detailed_status.js | 2 +- app/javascript/styles/mastodon/containers.scss | 2 +- app/javascript/styles/mastodon/widgets.scss | 10 +++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index 2921a26f9..0630387d2 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -54,7 +54,7 @@ export default class DetailedStatus extends ImmutablePureComponent { _measureHeight (heightJustChanged) { if (this.props.measureHeight && this.node) { - scheduleIdleTask(() => this.node && this.setState({ height: this.node.scrollHeight })); + scheduleIdleTask(() => this.node && this.setState({ height: Math.ceil(this.node.scrollHeight) + 1 })); if (this.props.onHeightChange && heightJustChanged) { this.props.onHeightChange(); diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index 8de53ca98..a98fa52c4 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -295,7 +295,7 @@ color: $primary-text-color; } - @media screen and (max-width: $no-gap-breakpoint) { + @media screen and (max-width: 550px) { &.optional { display: none; } diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index 0699900dc..c97337e4e 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -432,6 +432,10 @@ $fluid-breakpoint: $maximum-width + 20px; .statuses-grid { min-height: 600px; + @media screen and (max-width: 640px) { + width: 100% !important; // Masonry layout is unnecessary at this width + } + &__item { width: (960px - 20px) / 3; @@ -439,6 +443,10 @@ $fluid-breakpoint: $maximum-width + 20px; width: (940px - 20px) / 3; } + @media screen and (max-width: 640px) { + width: 100%; + } + @media screen and (max-width: $no-gap-breakpoint) { width: 100vw; } @@ -448,7 +456,7 @@ $fluid-breakpoint: $maximum-width + 20px; border-radius: 4px; @media screen and (max-width: $no-gap-breakpoint) { - border-bottom: 1px solid lighten($ui-base-color, 12%); + border-top: 1px solid lighten($ui-base-color, 16%); } &.compact { -- cgit From 80768e2840d1dc412882343c035de114bd732532 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 18 Jan 2019 23:52:09 +0100 Subject: Fix missing account association in tombstone model (#9857) --- app/models/tombstone.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'app') diff --git a/app/models/tombstone.rb b/app/models/tombstone.rb index 35b7337ff..997bb65fd 100644 --- a/app/models/tombstone.rb +++ b/app/models/tombstone.rb @@ -12,4 +12,5 @@ # class Tombstone < ApplicationRecord + belongs_to :account end -- cgit