Merge branch 'refs/heads/glitch-soc' into develop

# Conflicts:
#	app/javascript/flavours/glitch/initial_state.js
#	app/javascript/mastodon/initial_state.js
This commit is contained in:
Jeremy Kescher 2024-05-20 23:57:04 +02:00
commit fae98d1deb
No known key found for this signature in database
GPG key ID: 80A419A7A613DFA4
134 changed files with 1557 additions and 675 deletions

View file

@ -4,7 +4,8 @@ NODE_ENV=production
LOCAL_DOMAIN=cb6e6126.ngrok.io
LOCAL_HTTPS=true
# Required by ActiveRecord encryption feature
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=fkSxKD2bF396kdQbrP1EJ7WbU7ZgNokR
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=r0hvVmzBVsjxC7AMlwhOzmtc36ZCOS1E
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=PhdFyyfy5xJ7WVd2lWBpcPScRQHzRTNr
# Secret values required by ActiveRecord encryption feature
# Use `bin/rails db:encryption:init` to generate fresh secrets
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=test_determinist_key_DO_NOT_USE_IN_PRODUCTION
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=test_salt_DO_NOT_USE_IN_PRODUCTION
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=test_primary_key_DO_NOT_USE_IN_PRODUCTION

View file

@ -10,35 +10,35 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (7.1.3.2)
actionpack (= 7.1.3.2)
activesupport (= 7.1.3.2)
actioncable (7.1.3.3)
actionpack (= 7.1.3.3)
activesupport (= 7.1.3.3)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (7.1.3.2)
actionpack (= 7.1.3.2)
activejob (= 7.1.3.2)
activerecord (= 7.1.3.2)
activestorage (= 7.1.3.2)
activesupport (= 7.1.3.2)
actionmailbox (7.1.3.3)
actionpack (= 7.1.3.3)
activejob (= 7.1.3.3)
activerecord (= 7.1.3.3)
activestorage (= 7.1.3.3)
activesupport (= 7.1.3.3)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.1.3.2)
actionpack (= 7.1.3.2)
actionview (= 7.1.3.2)
activejob (= 7.1.3.2)
activesupport (= 7.1.3.2)
actionmailer (7.1.3.3)
actionpack (= 7.1.3.3)
actionview (= 7.1.3.3)
activejob (= 7.1.3.3)
activesupport (= 7.1.3.3)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.2)
actionpack (7.1.3.2)
actionview (= 7.1.3.2)
activesupport (= 7.1.3.2)
actionpack (7.1.3.3)
actionview (= 7.1.3.3)
activesupport (= 7.1.3.3)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4)
@ -46,15 +46,15 @@ GEM
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
actiontext (7.1.3.2)
actionpack (= 7.1.3.2)
activerecord (= 7.1.3.2)
activestorage (= 7.1.3.2)
activesupport (= 7.1.3.2)
actiontext (7.1.3.3)
actionpack (= 7.1.3.3)
activerecord (= 7.1.3.3)
activestorage (= 7.1.3.3)
activesupport (= 7.1.3.3)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.1.3.2)
activesupport (= 7.1.3.2)
actionview (7.1.3.3)
activesupport (= 7.1.3.3)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
@ -64,22 +64,22 @@ GEM
activemodel (>= 4.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.1.3.2)
activesupport (= 7.1.3.2)
activejob (7.1.3.3)
activesupport (= 7.1.3.3)
globalid (>= 0.3.6)
activemodel (7.1.3.2)
activesupport (= 7.1.3.2)
activerecord (7.1.3.2)
activemodel (= 7.1.3.2)
activesupport (= 7.1.3.2)
activemodel (7.1.3.3)
activesupport (= 7.1.3.3)
activerecord (7.1.3.3)
activemodel (= 7.1.3.3)
activesupport (= 7.1.3.3)
timeout (>= 0.4.0)
activestorage (7.1.3.2)
actionpack (= 7.1.3.2)
activejob (= 7.1.3.2)
activerecord (= 7.1.3.2)
activesupport (= 7.1.3.2)
activestorage (7.1.3.3)
actionpack (= 7.1.3.3)
activejob (= 7.1.3.3)
activerecord (= 7.1.3.3)
activesupport (= 7.1.3.3)
marcel (~> 1.0)
activesupport (7.1.3.2)
activesupport (7.1.3.3)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
@ -444,7 +444,7 @@ GEM
timeout
net-smtp (0.5.0)
net-protocol
nio4r (2.7.1)
nio4r (2.7.3)
nokogiri (1.16.5)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
@ -634,20 +634,20 @@ GEM
rackup (1.0.0)
rack (< 3)
webrick
rails (7.1.3.2)
actioncable (= 7.1.3.2)
actionmailbox (= 7.1.3.2)
actionmailer (= 7.1.3.2)
actionpack (= 7.1.3.2)
actiontext (= 7.1.3.2)
actionview (= 7.1.3.2)
activejob (= 7.1.3.2)
activemodel (= 7.1.3.2)
activerecord (= 7.1.3.2)
activestorage (= 7.1.3.2)
activesupport (= 7.1.3.2)
rails (7.1.3.3)
actioncable (= 7.1.3.3)
actionmailbox (= 7.1.3.3)
actionmailer (= 7.1.3.3)
actionpack (= 7.1.3.3)
actiontext (= 7.1.3.3)
actionview (= 7.1.3.3)
activejob (= 7.1.3.3)
activemodel (= 7.1.3.3)
activerecord (= 7.1.3.3)
activestorage (= 7.1.3.3)
activesupport (= 7.1.3.3)
bundler (>= 1.15.0)
railties (= 7.1.3.2)
railties (= 7.1.3.3)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
@ -662,9 +662,9 @@ GEM
rails-i18n (7.0.9)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
railties (7.1.3.2)
actionpack (= 7.1.3.2)
activesupport (= 7.1.3.2)
railties (7.1.3.3)
actionpack (= 7.1.3.3)
activesupport (= 7.1.3.3)
irb
rackup (>= 1.0.0)
rake (>= 12.2)
@ -775,7 +775,7 @@ GEM
scenic (1.8.0)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
selenium-webdriver (4.21.0)
selenium-webdriver (4.21.1)
base64 (~> 0.2)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)

View file

@ -4,6 +4,6 @@ class Api::V1::Apps::CredentialsController < Api::BaseController
def show
return doorkeeper_render_error unless valid_doorkeeper_token?
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key client_id scopes)
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer
end
end

View file

@ -5,7 +5,7 @@ class Api::V1::AppsController < Api::BaseController
def create
@app = Doorkeeper::Application.create!(application_options)
render json: @app, serializer: REST::ApplicationSerializer
render json: @app, serializer: REST::CredentialApplicationSerializer
end
private
@ -24,6 +24,6 @@ class Api::V1::AppsController < Api::BaseController
end
def app_params
params.permit(:client_name, :redirect_uris, :scopes, :website)
params.permit(:client_name, :scopes, :website, :redirect_uris, redirect_uris: [])
end
end

View file

@ -241,6 +241,10 @@ module ApplicationHelper
EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s
end
def mascot_url
full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg'))
end
def instance_presenter
@instance_presenter ||= InstancePresenter.new
end

View file

@ -1,11 +0,0 @@
# frozen_string_literal: true
module MascotHelper
def mascot_url
full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg'))
end
def instance_presenter
@instance_presenter ||= InstancePresenter.new
end
end

View file

@ -14,8 +14,10 @@ import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
import { Icon } from 'flavours/glitch/components/icon';
import { ButtonInTabsBar } from 'flavours/glitch/features/ui/util/columns_context';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import { useAppHistory } from './router';
const messages = defineMessages({
@ -51,12 +53,8 @@ BackButton.propTypes = {
};
class ColumnHeader extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
title: PropTypes.node,
icon: PropTypes.string,
@ -171,7 +169,7 @@ class ColumnHeader extends PureComponent {
);
}
if (this.context.identity.signedIn && (children || (multiColumn && this.props.onPin))) {
if (this.props.identity.signedIn && (children || (multiColumn && this.props.onPin))) {
collapseButton = (
<button
className={collapsibleButtonClassName}
@ -232,4 +230,4 @@ class ColumnHeader extends PureComponent {
}
export default injectIntl(withRouter(ColumnHeader));
export default injectIntl(withIdentity(withRouter(ColumnHeader)));

View file

@ -14,6 +14,7 @@ import CheckIcon from '@/material-icons/400-24px/check.svg?react';
import { Icon } from 'flavours/glitch/components/icon';
import emojify from 'flavours/glitch/features/emoji/emoji';
import Motion from 'flavours/glitch/features/ui/util/optional_motion';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { RelativeTimestamp } from './relative_timestamp';
@ -38,12 +39,8 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
}, {});
class Poll extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
poll: ImmutablePropTypes.map,
lang: PropTypes.string,
intl: PropTypes.object.isRequired,
@ -235,7 +232,7 @@ class Poll extends ImmutablePureComponent {
</ul>
<div className='poll__footer'>
{!showResults && <button className='button button-secondary' disabled={disabled || !this.context.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
{!showResults && <button className='button button-secondary' disabled={disabled || !this.props.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
{!showResults && <><button className='poll__link' onClick={this.handleReveal}><FormattedMessage id='poll.reveal' defaultMessage='See results' /></button> · </>}
{showResults && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>}
{votesCount}
@ -247,4 +244,4 @@ class Poll extends ImmutablePureComponent {
}
export default injectIntl(Poll);
export default injectIntl(withIdentity(Poll));

View file

@ -22,6 +22,7 @@ import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg';
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -70,12 +71,8 @@ const messages = defineMessages({
});
class StatusActionBar extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
onReply: PropTypes.func,
onFavourite: PropTypes.func,
@ -112,7 +109,7 @@ class StatusActionBar extends ImmutablePureComponent {
];
handleReplyClick = () => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onReply(this.props.status, this.props.history);
@ -128,7 +125,7 @@ class StatusActionBar extends ImmutablePureComponent {
};
handleFavouriteClick = (e) => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onFavourite(this.props.status, e);
@ -142,7 +139,7 @@ class StatusActionBar extends ImmutablePureComponent {
};
handleReblogClick = e => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onReblog(this.props.status, e);
@ -218,7 +215,7 @@ class StatusActionBar extends ImmutablePureComponent {
render () {
const { status, intl, withDismiss, withCounters, showReplyCount, scrollKey } = this.props;
const { permissions, signedIn } = this.context.identity;
const { permissions, signedIn } = this.props.identity;
const mutingConversation = status.get('muted');
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
@ -370,4 +367,4 @@ class StatusActionBar extends ImmutablePureComponent {
}
export default withRouter(injectIntl(StatusActionBar));
export default withRouter(withIdentity(injectIntl(StatusActionBar)));

View file

@ -15,6 +15,7 @@ import LinkIcon from '@/material-icons/400-24px/link.svg?react';
import MovieIcon from '@/material-icons/400-24px/movie.svg?react';
import MusicNoteIcon from '@/material-icons/400-24px/music_note.svg?react';
import { Icon } from 'flavours/glitch/components/icon';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state';
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
@ -126,12 +127,8 @@ const mapStateToProps = state => ({
});
class StatusContent extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
statusContent: PropTypes.string,
expanded: PropTypes.bool,
@ -349,7 +346,7 @@ class StatusContent extends PureComponent {
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
const contentLocale = intl.locale.replace(/[_-].*/, '');
const targetLanguages = this.props.languages?.get(status.get('language') || 'und');
const renderTranslate = this.props.onTranslate && this.context.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale);
const renderTranslate = this.props.onTranslate && this.props.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale);
const content = { __html: statusContent ?? getStatusContent(status) };
const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') };
@ -503,4 +500,4 @@ class StatusContent extends PureComponent {
}
export default withRouter(connect(mapStateToProps)(injectIntl(StatusContent)));
export default withRouter(withIdentity(connect(mapStateToProps)(injectIntl(StatusContent))));

View file

@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { Helmet } from 'react-helmet';
@ -15,6 +14,7 @@ import { connectUserStream } from 'flavours/glitch/actions/streaming';
import ErrorBoundary from 'flavours/glitch/components/error_boundary';
import { Router } from 'flavours/glitch/components/router';
import UI from 'flavours/glitch/features/ui';
import { IdentityContext, createIdentityContext } from 'flavours/glitch/identity_context';
import initialState, { title as siteTitle } from 'flavours/glitch/initial_state';
import { IntlProvider } from 'flavours/glitch/locales';
import { store } from 'flavours/glitch/store';
@ -33,33 +33,9 @@ if (initialState.meta.me) {
store.dispatch(fetchCustomEmojis());
}
const createIdentityContext = state => ({
signedIn: !!state.meta.me,
accountId: state.meta.me,
disabledAccountId: state.meta.disabled_account_id,
accessToken: state.meta.access_token,
permissions: state.role ? state.role.permissions : 0,
});
export default class Mastodon extends PureComponent {
static childContextTypes = {
identity: PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired,
};
identity = createIdentityContext(initialState);
getChildContext() {
return {
identity: this.identity,
};
}
componentDidMount() {
if (this.identity.signedIn) {
this.disconnect = store.dispatch(connectUserStream());
@ -79,19 +55,21 @@ export default class Mastodon extends PureComponent {
render () {
return (
<IntlProvider>
<ReduxProvider store={store}>
<ErrorBoundary>
<Router>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Route path='/' component={UI} />
</ScrollContext>
</Router>
<IdentityContext.Provider value={this.identity}>
<IntlProvider>
<ReduxProvider store={store}>
<ErrorBoundary>
<Router>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Route path='/' component={UI} />
</ScrollContext>
</Router>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
</ErrorBoundary>
</ReduxProvider>
</IntlProvider>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
</ErrorBoundary>
</ReduxProvider>
</IntlProvider>
</IdentityContext.Provider>
);
}

View file

@ -23,6 +23,7 @@ import { Icon } from 'flavours/glitch/components/icon';
import { IconButton } from 'flavours/glitch/components/icon_button';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { autoPlayGif, me, domain as localDomain } from 'flavours/glitch/initial_state';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links';
@ -94,6 +95,7 @@ const dateFormatOptions = {
class Header extends ImmutablePureComponent {
static propTypes = {
identity: identityContextPropShape,
account: ImmutablePropTypes.record,
identity_props: ImmutablePropTypes.list,
onFollow: PropTypes.func.isRequired,
@ -117,10 +119,6 @@ class Header extends ImmutablePureComponent {
...WithRouterPropTypes,
};
static contextTypes = {
identity: PropTypes.object,
};
openEditProfile = () => {
window.open(profileLink, '_blank');
};
@ -170,7 +168,7 @@ class Header extends ImmutablePureComponent {
render () {
const { account, hidden, intl } = this.props;
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
if (!account) {
return null;
@ -414,4 +412,4 @@ class Header extends ImmutablePureComponent {
}
export default withRouter(injectIntl(Header));
export default withRouter(withIdentity(injectIntl(Header)));

View file

@ -9,6 +9,7 @@ import { connect } from 'react-redux';
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { domain } from 'flavours/glitch/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -40,16 +41,12 @@ const mapStateToProps = (state, { columnId }) => {
};
class CommunityTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = {
onlyMedia: false,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string,
intl: PropTypes.object.isRequired,
@ -80,7 +77,7 @@ class CommunityTimeline extends PureComponent {
componentDidMount () {
const { dispatch, onlyMedia } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
dispatch(expandCommunityTimeline({ onlyMedia }));
@ -90,7 +87,7 @@ class CommunityTimeline extends PureComponent {
}
componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia) {
const { dispatch, onlyMedia } = this.props;
@ -165,4 +162,4 @@ class CommunityTimeline extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(CommunityTimeline));
export default withIdentity(connect(mapStateToProps)(injectIntl(CommunityTimeline)));

View file

@ -12,6 +12,7 @@ import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import { Icon } from 'flavours/glitch/components/icon';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { domain, searchEnabled } from 'flavours/glitch/initial_state';
import { HASHTAG_REGEX } from 'flavours/glitch/utils/hashtags';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -33,12 +34,8 @@ const labelForRecentSearch = search => {
};
class Search extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
value: PropTypes.string.isRequired,
recent: ImmutablePropTypes.orderedSet,
submitted: PropTypes.bool,
@ -276,7 +273,7 @@ class Search extends PureComponent {
}
_calculateOptions (value) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const trimmedValue = value.trim();
const options = [];
@ -318,7 +315,7 @@ class Search extends PureComponent {
render () {
const { intl, value, submitted, recent } = this.props;
const { expanded, options, selectedOption } = this.state;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const hasValue = value.length > 0 || submitted;
@ -402,4 +399,4 @@ class Search extends PureComponent {
}
export default withRouter(injectIntl(Search));
export default withRouter(withIdentity(injectIntl(Search)));

View file

@ -13,6 +13,7 @@ import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header';
import Search from 'flavours/glitch/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { trendsEnabled } from 'flavours/glitch/initial_state';
import Links from './links';
@ -32,12 +33,8 @@ const mapStateToProps = state => ({
});
class Explore extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
isSearching: PropTypes.bool,
@ -53,7 +50,7 @@ class Explore extends PureComponent {
render() {
const { intl, multiColumn, isSearching } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
@ -114,4 +111,4 @@ class Explore extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(Explore));
export default withIdentity(connect(mapStateToProps)(injectIntl(Explore)));

View file

@ -6,6 +6,7 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet';
import { NavLink } from 'react-router-dom';
import { useIdentity } from '@/flavours/glitch/identity_context';
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { addColumn } from 'flavours/glitch/actions/columns';
import { changeSetting } from 'flavours/glitch/actions/settings';
@ -13,7 +14,7 @@ import { connectPublicStream, connectCommunityStream } from 'flavours/glitch/act
import { expandPublicTimeline, expandCommunityTimeline } from 'flavours/glitch/actions/timelines';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import SettingText from 'flavours/glitch/components/setting_text';
import initialState, { domain } from 'flavours/glitch/initial_state';
import { domain } from 'flavours/glitch/initial_state';
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
import Column from '../../components/column';
@ -26,15 +27,6 @@ const messages = defineMessages({
filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' },
});
// TODO: use a proper React context later on
const useIdentity = () => ({
signedIn: !!initialState.meta.me,
accountId: initialState.meta.me,
disabledAccountId: initialState.meta.disabled_account_id,
accessToken: initialState.meta.access_token,
permissions: initialState.role ? initialState.role.permissions : 0,
});
const ColumnSettings = () => {
const intl = useIntl();
const dispatch = useAppDispatch();

View file

@ -28,6 +28,7 @@ import { fetchLists } from 'flavours/glitch/actions/lists';
import { openModal } from 'flavours/glitch/actions/modal';
import Column from 'flavours/glitch/features/ui/components/column';
import LinkFooter from 'flavours/glitch/features/ui/components/link_footer';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { preferencesLink } from 'flavours/glitch/utils/backend_links';
@ -99,12 +100,8 @@ const badgeDisplay = (number, limit) => {
};
class GettingStarted extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
myAccount: ImmutablePropTypes.record,
columns: ImmutablePropTypes.list,
@ -123,7 +120,7 @@ class GettingStarted extends ImmutablePureComponent {
componentDidMount () {
const { fetchFollowRequests } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -134,7 +131,7 @@ class GettingStarted extends ImmutablePureComponent {
render () {
const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications, lists, openSettings } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const navItems = [];
let listItems = [];
@ -219,4 +216,4 @@ class GettingStarted extends ImmutablePureComponent {
}
export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(GettingStarted));
export default withIdentity(connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(GettingStarted)));

View file

@ -16,7 +16,7 @@ import { openModal } from 'flavours/glitch/actions/modal';
import Column from 'flavours/glitch/features/ui/components/column';
import ColumnLink from 'flavours/glitch/features/ui/components/column_link';
import ColumnSubheading from 'flavours/glitch/features/ui/components/column_subheading';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
const messages = defineMessages({
heading: { id: 'column.heading', defaultMessage: 'Misc' },
@ -32,11 +32,8 @@ const messages = defineMessages({
class GettingStartedMisc extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
};
@ -49,7 +46,7 @@ class GettingStartedMisc extends ImmutablePureComponent {
render () {
const { intl } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<Column icon='ellipsis-h' iconComponent={MoreHorizIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
@ -69,4 +66,4 @@ class GettingStartedMisc extends ImmutablePureComponent {
}
export default connect()(injectIntl(GettingStartedMisc));
export default connect()(withIdentity(injectIntl(GettingStartedMisc)));

View file

@ -17,6 +17,7 @@ import { fetchHashtag, followHashtag, unfollowHashtag } from 'flavours/glitch/ac
import { expandHashtagTimeline, clearTimeline } from 'flavours/glitch/actions/timelines';
import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import StatusListContainer from '../ui/containers/status_list_container';
@ -29,14 +30,10 @@ const mapStateToProps = (state, props) => ({
});
class HashtagTimeline extends PureComponent {
disconnects = [];
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired,
columnId: PropTypes.string,
dispatch: PropTypes.func.isRequired,
@ -94,7 +91,7 @@ class HashtagTimeline extends PureComponent {
};
_subscribe (dispatch, id, tags = {}, local) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -168,7 +165,7 @@ class HashtagTimeline extends PureComponent {
handleFollow = () => {
const { dispatch, params, tag } = this.props;
const { id } = params;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -185,7 +182,7 @@ class HashtagTimeline extends PureComponent {
const { hasUnread, columnId, multiColumn, tag } = this.props;
const { id, local } = this.props.params;
const pinned = !!columnId;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}>
@ -225,4 +222,4 @@ class HashtagTimeline extends PureComponent {
}
export default connect(mapStateToProps)(HashtagTimeline);
export default connect(mapStateToProps)(withIdentity(HashtagTimeline));

View file

@ -14,6 +14,7 @@ import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/act
import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge';
import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator';
import AnnouncementsContainer from 'flavours/glitch/features/getting_started/containers/announcements_container';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { criticalUpdatesPending } from 'flavours/glitch/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -41,12 +42,8 @@ const mapStateToProps = state => ({
});
class HomeTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
@ -128,7 +125,7 @@ class HomeTimeline extends PureComponent {
render () {
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
const pinned = !!columnId;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const banners = [];
let announcementsButton;
@ -192,4 +189,4 @@ class HomeTimeline extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(HomeTimeline));
export default connect(mapStateToProps)(withIdentity(injectIntl(HomeTimeline)));

View file

@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'flavours/glitch/permissions';
import { CheckboxWithLabel } from './checkbox_with_label';
@ -13,13 +14,9 @@ import GrantPermissionButton from './grant_permission_button';
import PillBarButton from './pill_bar_button';
import SettingToggle from './setting_toggle';
export default class ColumnSettings extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
class ColumnSettings extends PureComponent {
static propTypes = {
identity: identityContextPropShape,
settings: ImmutablePropTypes.map.isRequired,
pushSettings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
@ -227,7 +224,7 @@ export default class ColumnSettings extends PureComponent {
</div>
</section>
{((this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && (
{((this.props.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && (
<section role='group' aria-labelledby='notifications-admin-sign-up'>
<h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></h3>
@ -240,7 +237,7 @@ export default class ColumnSettings extends PureComponent {
</section>
)}
{((this.context.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && (
{((this.props.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && (
<section role='group' aria-labelledby='notifications-admin-report'>
<h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.report' defaultMessage='New reports:' /></h3>
@ -257,3 +254,5 @@ export default class ColumnSettings extends PureComponent {
}
}
export default withIdentity(ColumnSettings);

View file

@ -19,6 +19,7 @@ import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?
import { compareId } from 'flavours/glitch/compare_id';
import { Icon } from 'flavours/glitch/components/icon';
import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { submitMarkers } from '../../actions/markers';
@ -93,12 +94,8 @@ const mapDispatchToProps = dispatch => ({
});
class Notifications extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
columnId: PropTypes.string,
notifications: ImmutablePropTypes.list.isRequired,
showFilterBar: PropTypes.bool.isRequired,
@ -225,7 +222,7 @@ class Notifications extends PureComponent {
const { animatingNCD } = this.state;
const pinned = !!columnId;
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
let scrollableContent = null;
@ -373,4 +370,4 @@ class Notifications extends PureComponent {
}
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(Notifications));
export default connect(mapStateToProps, mapDispatchToProps)(withIdentity(injectIntl(Notifications)));

View file

@ -18,6 +18,7 @@ import { replyCompose } from 'flavours/glitch/actions/compose';
import { reblog, favourite, unreblog, unfavourite } from 'flavours/glitch/actions/interactions';
import { openModal } from 'flavours/glitch/actions/modal';
import { IconButton } from 'flavours/glitch/components/icon_button';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { me, boostModal } from 'flavours/glitch/initial_state';
import { makeGetStatus } from 'flavours/glitch/selectors';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -48,12 +49,8 @@ const makeMapStateToProps = () => {
};
class Footer extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
statusId: PropTypes.string.isRequired,
status: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired,
@ -77,7 +74,7 @@ class Footer extends ImmutablePureComponent {
handleReplyClick = () => {
const { dispatch, askReplyConfirmation, status, intl } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (askReplyConfirmation) {
@ -106,7 +103,7 @@ class Footer extends ImmutablePureComponent {
handleFavouriteClick = () => {
const { dispatch, status } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('favourited')) {
@ -133,7 +130,7 @@ class Footer extends ImmutablePureComponent {
handleReblogClick = e => {
const { dispatch, status } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('reblogged')) {
@ -236,4 +233,4 @@ class Footer extends ImmutablePureComponent {
}
export default connect(makeMapStateToProps)(withRouter(injectIntl(Footer)));
export default connect(makeMapStateToProps)(withIdentity(withRouter(injectIntl(Footer))));

View file

@ -9,6 +9,7 @@ import { connect } from 'react-redux';
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { domain } from 'flavours/glitch/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -44,16 +45,12 @@ const mapStateToProps = (state, { columnId }) => {
};
class PublicTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = {
onlyMedia: false,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
@ -86,7 +83,7 @@ class PublicTimeline extends PureComponent {
componentDidMount () {
const { dispatch, onlyMedia, onlyRemote, allowLocalOnly } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote, allowLocalOnly }));
if (signedIn) {
@ -95,7 +92,7 @@ class PublicTimeline extends PureComponent {
}
componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote || prevProps.allowLocalOnly !== this.props.allowLocalOnly) {
const { dispatch, onlyMedia, onlyRemote, allowLocalOnly } = this.props;
@ -170,4 +167,4 @@ class PublicTimeline extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(PublicTimeline));
export default connect(mapStateToProps)(withIdentity(injectIntl(PublicTimeline)));

View file

@ -21,6 +21,7 @@ import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -62,12 +63,8 @@ const messages = defineMessages({
});
class ActionBar extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
onReply: PropTypes.func.isRequired,
onReblog: PropTypes.func.isRequired,
@ -165,7 +162,7 @@ class ActionBar extends PureComponent {
render () {
const { status, intl } = this.props;
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
@ -276,4 +273,4 @@ class ActionBar extends PureComponent {
}
export default withRouter(injectIntl(ActionBar));
export default withRouter(withIdentity(injectIntl(ActionBar)));

View file

@ -21,6 +21,7 @@ import { Icon } from 'flavours/glitch/components/icon';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import ScrollContainer from 'flavours/glitch/containers/scroll_container';
import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -187,12 +188,8 @@ const titleFromStatus = (intl, status) => {
};
class Status extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
status: ImmutablePropTypes.map,
@ -279,7 +276,7 @@ class Status extends ImmutablePureComponent {
handleFavouriteClick = (status, e) => {
const { dispatch } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('favourited')) {
@ -332,7 +329,7 @@ class Status extends ImmutablePureComponent {
handleReplyClick = (status) => {
const { askReplyConfirmation, dispatch, intl } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (askReplyConfirmation) {
@ -372,7 +369,7 @@ class Status extends ImmutablePureComponent {
handleReblogClick = (status, e) => {
const { settings, dispatch } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (settings.get('confirm_boost_missing_media_description') && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) {
@ -810,4 +807,4 @@ class Status extends ImmutablePureComponent {
}
export default withRouter(injectIntl(connect(makeMapStateToProps)(Status)));
export default withRouter(injectIntl(connect(makeMapStateToProps)(withIdentity(Status))));

View file

@ -7,16 +7,13 @@ import { mountCompose, unmountCompose } from 'flavours/glitch/actions/compose';
import ServerBanner from 'flavours/glitch/components/server_banner';
import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container';
import SearchContainer from 'flavours/glitch/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import LinkFooter from './link_footer';
class ComposePanel extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
};
@ -31,7 +28,7 @@ class ComposePanel extends PureComponent {
}
render() {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<div className='compose-panel'>
@ -55,4 +52,4 @@ class ComposePanel extends PureComponent {
}
export default connect()(ComposePanel);
export default connect()(withIdentity(ComposePanel));

View file

@ -14,6 +14,7 @@ import { Avatar } from 'flavours/glitch/components/avatar';
import { Icon } from 'flavours/glitch/components/icon';
import { WordmarkLogo, SymbolLogo } from 'flavours/glitch/components/logo';
import { Permalink } from 'flavours/glitch/components/permalink';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { registrationsOpen, me, sso_redirect } from 'flavours/glitch/initial_state';
const Account = connect(state => ({
@ -42,12 +43,8 @@ const mapDispatchToProps = (dispatch) => ({
});
class Header extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
openClosedRegistrationsModal: PropTypes.func,
location: PropTypes.object,
signupUrl: PropTypes.string.isRequired,
@ -61,7 +58,7 @@ class Header extends PureComponent {
}
render () {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props;
let content;
@ -122,4 +119,4 @@ class Header extends PureComponent {
}
export default injectIntl(withRouter(connect(mapStateToProps, mapDispatchToProps)(Header)));
export default injectIntl(withRouter(withIdentity(connect(mapStateToProps, mapDispatchToProps)(Header))));

View file

@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { openModal } from 'flavours/glitch/actions/modal';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'flavours/glitch/initial_state';
import { PERMISSION_INVITE_USERS } from 'flavours/glitch/permissions';
import { logOut } from 'flavours/glitch/utils/log_out';
@ -32,12 +33,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
});
class LinkFooter extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
multiColumn: PropTypes.bool,
onLogout: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
@ -53,7 +50,7 @@ class LinkFooter extends PureComponent {
};
render () {
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
const { multiColumn } = this.props;
const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS);
@ -106,4 +103,4 @@ class LinkFooter extends PureComponent {
}
export default injectIntl(connect(null, mapDispatchToProps)(LinkFooter));
export default injectIntl(withIdentity(connect(null, mapDispatchToProps)(LinkFooter)));

View file

@ -30,6 +30,7 @@ import StarIcon from '@/material-icons/400-24px/star.svg?react';
import { fetchFollowRequests } from 'flavours/glitch/actions/accounts';
import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge';
import { NavigationPortal } from 'flavours/glitch/components/navigation_portal';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { timelinePreview, trendsEnabled } from 'flavours/glitch/initial_state';
import { transientSingleColumn } from 'flavours/glitch/is_mobile';
import { preferencesLink } from 'flavours/glitch/utils/backend_links';
@ -98,12 +99,8 @@ const FollowRequestsLink = () => {
};
class NavigationPanel extends Component {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
onOpenSettings: PropTypes.func,
};
@ -114,7 +111,7 @@ class NavigationPanel extends Component {
render () {
const { intl, onOpenSettings } = this.props;
const { signedIn, disabledAccountId } = this.context.identity;
const { signedIn, disabledAccountId } = this.props.identity;
let banner = undefined;
@ -188,4 +185,4 @@ class NavigationPanel extends Component {
}
export default injectIntl(NavigationPanel);
export default injectIntl(withIdentity(NavigationPanel));

View file

@ -17,6 +17,7 @@ import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavour
import { INTRODUCTION_VERSION } from 'flavours/glitch/actions/onboarding';
import { Permalink } from 'flavours/glitch/components/permalink';
import { PictureInPicture } from 'flavours/glitch/features/picture_in_picture';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { layoutFromWindow } from 'flavours/glitch/is_mobile';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -129,12 +130,8 @@ const keyMap = {
};
class SwitchingColumnsArea extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
children: PropTypes.node,
location: PropTypes.object,
singleColumn: PropTypes.bool,
@ -169,7 +166,7 @@ class SwitchingColumnsArea extends PureComponent {
render () {
const { children, singleColumn } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const pathName = this.props.location.pathname;
let redirect;
@ -262,12 +259,8 @@ class SwitchingColumnsArea extends PureComponent {
}
class UI extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
children: PropTypes.node,
isWide: PropTypes.bool,
@ -323,7 +316,7 @@ class UI extends PureComponent {
this.dragTargets.push(e.target);
}
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.context.identity.signedIn) {
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.props.identity.signedIn) {
this.setState({ draggingOver: true });
}
};
@ -351,7 +344,7 @@ class UI extends PureComponent {
this.setState({ draggingOver: false });
this.dragTargets = [];
if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.context.identity.signedIn) {
if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.props.identity.signedIn) {
this.props.dispatch(uploadCompose(e.dataTransfer.files));
}
};
@ -403,7 +396,7 @@ class UI extends PureComponent {
};
componentDidMount () {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
window.addEventListener('resize', this.handleResize, { passive: true });
@ -649,7 +642,7 @@ class UI extends PureComponent {
<Header />
<SwitchingColumnsArea location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
<SwitchingColumnsArea identity={this.props.identity} location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
{children}
</SwitchingColumnsArea>
@ -665,4 +658,4 @@ class UI extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(withRouter(UI)));
export default connect(mapStateToProps)(injectIntl(withRouter(withIdentity(UI))));

View file

@ -0,0 +1,74 @@
import PropTypes from 'prop-types';
import { createContext, useContext } from 'react';
import hoistStatics from 'hoist-non-react-statics';
import type { InitialState } from 'flavours/glitch/initial_state';
export interface IdentityContextType {
signedIn: boolean;
accountId: string | undefined;
disabledAccountId: string | undefined;
accessToken: string | undefined;
permissions: number;
}
export const identityContextPropShape = PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired;
export const createIdentityContext = (state: InitialState) => ({
signedIn: !!state.meta.me,
accountId: state.meta.me,
disabledAccountId: state.meta.disabled_account_id,
accessToken: state.meta.access_token,
permissions: state.role?.permissions ?? 0,
});
export const IdentityContext = createContext<IdentityContextType>({
signedIn: false,
permissions: 0,
accountId: undefined,
disabledAccountId: undefined,
accessToken: undefined,
});
export const useIdentity = () => useContext(IdentityContext);
export interface IdentityProps {
ref?: unknown;
wrappedComponentRef?: unknown;
}
/* Injects an `identity` props into the wrapped component to be able to use the new context in class components */
export function withIdentity<
ComponentType extends React.ComponentType<IdentityProps>,
>(Component: ComponentType) {
const displayName = `withIdentity(${Component.displayName ?? Component.name})`;
const C = (props: React.ComponentProps<ComponentType>) => {
const { wrappedComponentRef, ...remainingProps } = props;
return (
<IdentityContext.Consumer>
{(context) => {
return (
// @ts-expect-error - Dynamic covariant generic components are tough to type.
<Component
{...remainingProps}
identity={context}
ref={wrappedComponentRef}
/>
);
}}
</IdentityContext.Consumer>
);
};
C.displayName = displayName;
C.WrappedComponent = Component;
return hoistStatics(C, Component);
}

View file

@ -52,6 +52,15 @@
* @property {string} default_content_type
*/
/**
* @typedef Role
* @property {string} id
* @property {string} name
* @property {string} permissions
* @property {string} color
* @property {boolean} highlighted
*/
/**
* @typedef PollLimits
* @property {number} min_options
@ -67,6 +76,7 @@
* @property {InitialStateLanguage[]} languages
* @property {boolean=} critical_updates_pending
* @property {InitialStateMeta} meta
* @property {Role?} role
* @property {object} local_settings
* @property {number} max_feed_hashtags
* @property {PollLimits} poll_limits

View file

@ -14,8 +14,10 @@ import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
import { Icon } from 'mastodon/components/icon';
import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import { useAppHistory } from './router';
const messages = defineMessages({
@ -51,12 +53,8 @@ BackButton.propTypes = {
};
class ColumnHeader extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
title: PropTypes.node,
icon: PropTypes.string,
@ -171,7 +169,7 @@ class ColumnHeader extends PureComponent {
);
}
if (this.context.identity.signedIn && (children || (multiColumn && this.props.onPin))) {
if (this.props.identity.signedIn && (children || (multiColumn && this.props.onPin))) {
collapseButton = (
<button
className={collapsibleButtonClassName}
@ -232,4 +230,4 @@ class ColumnHeader extends PureComponent {
}
export default injectIntl(withRouter(ColumnHeader));
export default injectIntl(withIdentity(withRouter(ColumnHeader)));

View file

@ -14,6 +14,7 @@ import CheckIcon from '@/material-icons/400-24px/check.svg?react';
import { Icon } from 'mastodon/components/icon';
import emojify from 'mastodon/features/emoji/emoji';
import Motion from 'mastodon/features/ui/util/optional_motion';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { RelativeTimestamp } from './relative_timestamp';
@ -38,12 +39,8 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
}, {});
class Poll extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
poll: ImmutablePropTypes.map,
lang: PropTypes.string,
intl: PropTypes.object.isRequired,
@ -235,7 +232,7 @@ class Poll extends ImmutablePureComponent {
</ul>
<div className='poll__footer'>
{!showResults && <button className='button button-secondary' disabled={disabled || !this.context.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
{!showResults && <button className='button button-secondary' disabled={disabled || !this.props.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
{!showResults && <><button className='poll__link' onClick={this.handleReveal}><FormattedMessage id='poll.reveal' defaultMessage='See results' /></button> · </>}
{showResults && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>}
{votesCount}
@ -247,4 +244,4 @@ class Poll extends ImmutablePureComponent {
}
export default injectIntl(Poll);
export default injectIntl(withIdentity(Poll));

View file

@ -22,6 +22,7 @@ import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -74,12 +75,8 @@ const mapStateToProps = (state, { status }) => ({
});
class StatusActionBar extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.record,
onReply: PropTypes.func,
@ -118,7 +115,7 @@ class StatusActionBar extends ImmutablePureComponent {
];
handleReplyClick = () => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onReply(this.props.status, this.props.history);
@ -136,7 +133,7 @@ class StatusActionBar extends ImmutablePureComponent {
};
handleFavouriteClick = () => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onFavourite(this.props.status);
@ -146,7 +143,7 @@ class StatusActionBar extends ImmutablePureComponent {
};
handleReblogClick = e => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onReblog(this.props.status, e);
@ -250,7 +247,7 @@ class StatusActionBar extends ImmutablePureComponent {
render () {
const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props;
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
@ -410,4 +407,4 @@ class StatusActionBar extends ImmutablePureComponent {
}
export default withRouter(connect(mapStateToProps)(injectIntl(StatusActionBar)));
export default withRouter(withIdentity(connect(mapStateToProps)(injectIntl(StatusActionBar))));

View file

@ -12,8 +12,10 @@ import { connect } from 'react-redux';
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
import { Icon } from 'mastodon/components/icon';
import PollContainer from 'mastodon/containers/poll_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state';
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
/**
@ -67,12 +69,8 @@ const mapStateToProps = state => ({
});
class StatusContent extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
statusContent: PropTypes.string,
expanded: PropTypes.bool,
@ -245,7 +243,7 @@ class StatusContent extends PureComponent {
const renderReadMore = this.props.onClick && status.get('collapsed');
const contentLocale = intl.locale.replace(/[_-].*/, '');
const targetLanguages = this.props.languages?.get(status.get('language') || 'und');
const renderTranslate = this.props.onTranslate && this.context.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale);
const renderTranslate = this.props.onTranslate && this.props.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale);
const content = { __html: statusContent ?? getStatusContent(status) };
const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') };
@ -328,4 +326,4 @@ class StatusContent extends PureComponent {
}
export default withRouter(connect(mapStateToProps)(injectIntl(StatusContent)));
export default withRouter(withIdentity(connect(mapStateToProps)(injectIntl(StatusContent))));

View file

@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { Helmet } from 'react-helmet';
@ -14,6 +13,7 @@ import { connectUserStream } from 'mastodon/actions/streaming';
import ErrorBoundary from 'mastodon/components/error_boundary';
import { Router } from 'mastodon/components/router';
import UI from 'mastodon/features/ui';
import { IdentityContext, createIdentityContext } from 'mastodon/identity_context';
import initialState, { title as siteTitle } from 'mastodon/initial_state';
import { IntlProvider } from 'mastodon/locales';
import { store } from 'mastodon/store';
@ -28,33 +28,9 @@ if (initialState.meta.me) {
store.dispatch(fetchCustomEmojis());
}
const createIdentityContext = state => ({
signedIn: !!state.meta.me,
accountId: state.meta.me,
disabledAccountId: state.meta.disabled_account_id,
accessToken: state.meta.access_token,
permissions: state.role ? state.role.permissions : 0,
});
export default class Mastodon extends PureComponent {
static childContextTypes = {
identity: PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired,
};
identity = createIdentityContext(initialState);
getChildContext() {
return {
identity: this.identity,
};
}
componentDidMount() {
if (this.identity.signedIn) {
this.disconnect = store.dispatch(connectUserStream());
@ -74,19 +50,21 @@ export default class Mastodon extends PureComponent {
render () {
return (
<IntlProvider>
<ReduxProvider store={store}>
<ErrorBoundary>
<Router>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Route path='/' component={UI} />
</ScrollContext>
</Router>
<IdentityContext.Provider value={this.identity}>
<IntlProvider>
<ReduxProvider store={store}>
<ErrorBoundary>
<Router>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Route path='/' component={UI} />
</ScrollContext>
</Router>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
</ErrorBoundary>
</ReduxProvider>
</IntlProvider>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
</ErrorBoundary>
</ReduxProvider>
</IntlProvider>
</IdentityContext.Provider>
);
}

View file

@ -25,6 +25,7 @@ import { IconButton } from 'mastodon/components/icon_button';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { ShortNumber } from 'mastodon/components/short_number';
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -111,6 +112,7 @@ const dateFormatOptions = {
class Header extends ImmutablePureComponent {
static propTypes = {
identity: identityContextPropShape,
account: ImmutablePropTypes.record,
identity_props: ImmutablePropTypes.list,
onFollow: PropTypes.func.isRequired,
@ -136,10 +138,6 @@ class Header extends ImmutablePureComponent {
...WithRouterPropTypes,
};
static contextTypes = {
identity: PropTypes.object,
};
setRef = c => {
this.node = c;
};
@ -255,7 +253,7 @@ class Header extends ImmutablePureComponent {
render () {
const { account, hidden, intl } = this.props;
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
if (!account) {
return null;
@ -516,4 +514,4 @@ class Header extends ImmutablePureComponent {
}
export default withRouter(injectIntl(Header));
export default withRouter(withIdentity(injectIntl(Header)));

View file

@ -9,6 +9,7 @@ import { connect } from 'react-redux';
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -38,16 +39,12 @@ const mapStateToProps = (state, { columnId }) => {
};
class CommunityTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = {
onlyMedia: false,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string,
intl: PropTypes.object.isRequired,
@ -77,7 +74,7 @@ class CommunityTimeline extends PureComponent {
componentDidMount () {
const { dispatch, onlyMedia } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
dispatch(expandCommunityTimeline({ onlyMedia }));
@ -87,7 +84,7 @@ class CommunityTimeline extends PureComponent {
}
componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia) {
const { dispatch, onlyMedia } = this.props;
@ -161,4 +158,4 @@ class CommunityTimeline extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(CommunityTimeline));
export default withIdentity(connect(mapStateToProps)(injectIntl(CommunityTimeline)));

View file

@ -12,6 +12,7 @@ import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import { Icon } from 'mastodon/components/icon';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain, searchEnabled } from 'mastodon/initial_state';
import { HASHTAG_REGEX } from 'mastodon/utils/hashtags';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -33,12 +34,8 @@ const labelForRecentSearch = search => {
};
class Search extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
value: PropTypes.string.isRequired,
recent: ImmutablePropTypes.orderedSet,
submitted: PropTypes.bool,
@ -276,7 +273,7 @@ class Search extends PureComponent {
}
_calculateOptions (value) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const trimmedValue = value.trim();
const options = [];
@ -318,7 +315,7 @@ class Search extends PureComponent {
render () {
const { intl, value, submitted, recent } = this.props;
const { expanded, options, selectedOption } = this.state;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const hasValue = value.length > 0 || submitted;
@ -402,4 +399,4 @@ class Search extends PureComponent {
}
export default withRouter(injectIntl(Search));
export default withRouter(withIdentity(injectIntl(Search)));

View file

@ -13,6 +13,7 @@ import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import Search from 'mastodon/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { trendsEnabled } from 'mastodon/initial_state';
import Links from './links';
@ -32,12 +33,8 @@ const mapStateToProps = state => ({
});
class Explore extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
isSearching: PropTypes.bool,
@ -53,7 +50,7 @@ class Explore extends PureComponent {
render() {
const { intl, multiColumn, isSearching } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
@ -114,4 +111,4 @@ class Explore extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(Explore));
export default withIdentity(connect(mapStateToProps)(injectIntl(Explore)));

View file

@ -6,13 +6,14 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet';
import { NavLink } from 'react-router-dom';
import { useIdentity } from '@/mastodon/identity_context';
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { addColumn } from 'mastodon/actions/columns';
import { changeSetting } from 'mastodon/actions/settings';
import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming';
import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import initialState, { domain } from 'mastodon/initial_state';
import { domain } from 'mastodon/initial_state';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import Column from '../../components/column';
@ -24,15 +25,6 @@ const messages = defineMessages({
title: { id: 'column.firehose', defaultMessage: 'Live feeds' },
});
// TODO: use a proper React context later on
const useIdentity = () => ({
signedIn: !!initialState.meta.me,
accountId: initialState.meta.me,
disabledAccountId: initialState.meta.disabled_account_id,
accessToken: initialState.meta.access_token,
permissions: initialState.role ? initialState.role.permissions : 0,
});
const ColumnSettings = () => {
const dispatch = useAppDispatch();
const settings = useAppSelector((state) => state.getIn(['settings', 'firehose']));

View file

@ -24,6 +24,7 @@ import { fetchFollowRequests } from 'mastodon/actions/accounts';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import LinkFooter from 'mastodon/features/ui/components/link_footer';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { me, showTrends } from '../../initial_state';
import { NavigationBar } from '../compose/components/navigation_bar';
@ -75,12 +76,8 @@ const badgeDisplay = (number, limit) => {
};
class GettingStarted extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
myAccount: ImmutablePropTypes.record,
multiColumn: PropTypes.bool,
@ -91,7 +88,7 @@ class GettingStarted extends ImmutablePureComponent {
componentDidMount () {
const { fetchFollowRequests } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -102,7 +99,7 @@ class GettingStarted extends ImmutablePureComponent {
render () {
const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const navItems = [];
@ -167,4 +164,4 @@ class GettingStarted extends ImmutablePureComponent {
}
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(GettingStarted));
export default withIdentity(connect(mapStateToProps, mapDispatchToProps)(injectIntl(GettingStarted)));

View file

@ -17,6 +17,7 @@ import { fetchHashtag, followHashtag, unfollowHashtag } from 'mastodon/actions/t
import { expandHashtagTimeline, clearTimeline } from 'mastodon/actions/timelines';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import StatusListContainer from '../ui/containers/status_list_container';
@ -29,14 +30,10 @@ const mapStateToProps = (state, props) => ({
});
class HashtagTimeline extends PureComponent {
disconnects = [];
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired,
columnId: PropTypes.string,
dispatch: PropTypes.func.isRequired,
@ -94,7 +91,7 @@ class HashtagTimeline extends PureComponent {
};
_subscribe (dispatch, id, tags = {}, local) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -168,7 +165,7 @@ class HashtagTimeline extends PureComponent {
handleFollow = () => {
const { dispatch, params, tag } = this.props;
const { id } = params;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -185,7 +182,7 @@ class HashtagTimeline extends PureComponent {
const { hasUnread, columnId, multiColumn, tag } = this.props;
const { id, local } = this.props.params;
const pinned = !!columnId;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}>
@ -225,4 +222,4 @@ class HashtagTimeline extends PureComponent {
}
export default connect(mapStateToProps)(HashtagTimeline);
export default connect(mapStateToProps)(withIdentity(HashtagTimeline));

View file

@ -14,6 +14,7 @@ import { fetchAnnouncements, toggleShowAnnouncements } from 'mastodon/actions/an
import { IconWithBadge } from 'mastodon/components/icon_with_badge';
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { criticalUpdatesPending } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -40,12 +41,8 @@ const mapStateToProps = state => ({
});
class HomeTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
@ -126,7 +123,7 @@ class HomeTimeline extends PureComponent {
render () {
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
const pinned = !!columnId;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const banners = [];
let announcementsButton;
@ -190,4 +187,4 @@ class HomeTimeline extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(HomeTimeline));
export default connect(mapStateToProps)(withIdentity(injectIntl(HomeTimeline)));

View file

@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'mastodon/permissions';
import { CheckboxWithLabel } from './checkbox_with_label';
@ -12,13 +13,9 @@ import ClearColumnButton from './clear_column_button';
import GrantPermissionButton from './grant_permission_button';
import SettingToggle from './setting_toggle';
export default class ColumnSettings extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
class ColumnSettings extends PureComponent {
static propTypes = {
identity: identityContextPropShape,
settings: ImmutablePropTypes.map.isRequired,
pushSettings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
@ -215,7 +212,7 @@ export default class ColumnSettings extends PureComponent {
</div>
</section>
{((this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && (
{((this.props.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && (
<section role='group' aria-labelledby='notifications-admin-sign-up'>
<h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></h3>
@ -228,7 +225,7 @@ export default class ColumnSettings extends PureComponent {
</section>
)}
{((this.context.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && (
{((this.props.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && (
<section role='group' aria-labelledby='notifications-admin-report'>
<h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.report' defaultMessage='New reports:' /></h3>
@ -245,3 +242,5 @@ export default class ColumnSettings extends PureComponent {
}
}
export default withIdentity(ColumnSettings);

View file

@ -17,6 +17,7 @@ import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?
import { compareId } from 'mastodon/compare_id';
import { Icon } from 'mastodon/components/icon';
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { submitMarkers } from '../../actions/markers';
@ -77,12 +78,8 @@ const mapStateToProps = state => ({
});
class Notifications extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
columnId: PropTypes.string,
notifications: ImmutablePropTypes.list.isRequired,
dispatch: PropTypes.func.isRequired,
@ -190,7 +187,7 @@ class Notifications extends PureComponent {
const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props;
const pinned = !!columnId;
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
let scrollableContent = null;
@ -299,4 +296,4 @@ class Notifications extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(Notifications));
export default connect(mapStateToProps)(withIdentity(injectIntl(Notifications)));

View file

@ -18,6 +18,7 @@ import { replyCompose } from 'mastodon/actions/compose';
import { reblog, favourite, unreblog, unfavourite } from 'mastodon/actions/interactions';
import { openModal } from 'mastodon/actions/modal';
import { IconButton } from 'mastodon/components/icon_button';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { me, boostModal } from 'mastodon/initial_state';
import { makeGetStatus } from 'mastodon/selectors';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -47,12 +48,8 @@ const makeMapStateToProps = () => {
};
class Footer extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
statusId: PropTypes.string.isRequired,
status: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired,
@ -75,7 +72,7 @@ class Footer extends ImmutablePureComponent {
handleReplyClick = () => {
const { dispatch, askReplyConfirmation, status, intl } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (askReplyConfirmation) {
@ -104,7 +101,7 @@ class Footer extends ImmutablePureComponent {
handleFavouriteClick = () => {
const { dispatch, status } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('favourited')) {
@ -131,7 +128,7 @@ class Footer extends ImmutablePureComponent {
handleReblogClick = e => {
const { dispatch, status } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('reblogged')) {
@ -209,4 +206,4 @@ class Footer extends ImmutablePureComponent {
}
export default connect(makeMapStateToProps)(withRouter(injectIntl(Footer)));
export default connect(makeMapStateToProps)(withIdentity(withRouter(injectIntl(Footer))));

View file

@ -9,6 +9,7 @@ import { connect } from 'react-redux';
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -40,16 +41,12 @@ const mapStateToProps = (state, { columnId }) => {
};
class PublicTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = {
onlyMedia: false,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
@ -80,7 +77,7 @@ class PublicTimeline extends PureComponent {
componentDidMount () {
const { dispatch, onlyMedia, onlyRemote } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
@ -90,7 +87,7 @@ class PublicTimeline extends PureComponent {
}
componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) {
const { dispatch, onlyMedia, onlyRemote } = this.props;
@ -164,4 +161,4 @@ class PublicTimeline extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(PublicTimeline));
export default connect(mapStateToProps)(withIdentity(injectIntl(PublicTimeline)));

View file

@ -21,6 +21,7 @@ import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -67,12 +68,8 @@ const mapStateToProps = (state, { status }) => ({
});
class ActionBar extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.record,
onReply: PropTypes.func.isRequired,
@ -198,7 +195,7 @@ class ActionBar extends PureComponent {
render () {
const { status, relationship, intl } = this.props;
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
@ -326,4 +323,4 @@ class ActionBar extends PureComponent {
}
export default withRouter(connect(mapStateToProps)(injectIntl(ActionBar)));
export default withRouter(connect(mapStateToProps)(withIdentity(injectIntl(ActionBar))));

View file

@ -20,6 +20,7 @@ import { Icon } from 'mastodon/components/icon';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import ScrollContainer from 'mastodon/containers/scroll_container';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import {
@ -189,12 +190,8 @@ const titleFromStatus = (intl, status) => {
};
class Status extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
status: ImmutablePropTypes.map,
@ -244,7 +241,7 @@ class Status extends ImmutablePureComponent {
handleFavouriteClick = (status) => {
const { dispatch } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('favourited')) {
@ -274,7 +271,7 @@ class Status extends ImmutablePureComponent {
handleReplyClick = (status) => {
const { askReplyConfirmation, dispatch, intl } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (askReplyConfirmation) {
@ -307,7 +304,7 @@ class Status extends ImmutablePureComponent {
handleReblogClick = (status, e) => {
const { dispatch } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('reblogged')) {
@ -745,4 +742,4 @@ class Status extends ImmutablePureComponent {
}
export default withRouter(injectIntl(connect(makeMapStateToProps)(Status)));
export default withRouter(injectIntl(connect(makeMapStateToProps)(withIdentity(Status))));

View file

@ -7,16 +7,13 @@ import { changeComposing, mountCompose, unmountCompose } from 'mastodon/actions/
import ServerBanner from 'mastodon/components/server_banner';
import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
import SearchContainer from 'mastodon/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import LinkFooter from './link_footer';
class ComposePanel extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
};
@ -41,7 +38,7 @@ class ComposePanel extends PureComponent {
}
render() {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<div className='compose-panel' onFocus={this.onFocus}>
@ -65,4 +62,4 @@ class ComposePanel extends PureComponent {
}
export default connect()(ComposePanel);
export default connect()(withIdentity(ComposePanel));

View file

@ -13,6 +13,7 @@ import { fetchServer } from 'mastodon/actions/server';
import { Avatar } from 'mastodon/components/avatar';
import { Icon } from 'mastodon/components/icon';
import { WordmarkLogo, SymbolLogo } from 'mastodon/components/logo';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { registrationsOpen, me, sso_redirect } from 'mastodon/initial_state';
const Account = connect(state => ({
@ -41,12 +42,8 @@ const mapDispatchToProps = (dispatch) => ({
});
class Header extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
openClosedRegistrationsModal: PropTypes.func,
location: PropTypes.object,
signupUrl: PropTypes.string.isRequired,
@ -60,7 +57,7 @@ class Header extends PureComponent {
}
render () {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props;
let content;
@ -121,4 +118,4 @@ class Header extends PureComponent {
}
export default injectIntl(withRouter(connect(mapStateToProps, mapDispatchToProps)(Header)));
export default injectIntl(withRouter(withIdentity(connect(mapStateToProps, mapDispatchToProps)(Header))));

View file

@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { openModal } from 'mastodon/actions/modal';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'mastodon/initial_state';
import { PERMISSION_INVITE_USERS } from 'mastodon/permissions';
import { logOut } from 'mastodon/utils/log_out';
@ -32,12 +33,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
});
class LinkFooter extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
multiColumn: PropTypes.bool,
onLogout: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
@ -53,7 +50,7 @@ class LinkFooter extends PureComponent {
};
render () {
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
const { multiColumn } = this.props;
const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS);
@ -108,4 +105,4 @@ class LinkFooter extends PureComponent {
}
export default injectIntl(connect(null, mapDispatchToProps)(LinkFooter));
export default injectIntl(withIdentity(connect(null, mapDispatchToProps)(LinkFooter)));

View file

@ -31,6 +31,7 @@ import { fetchFollowRequests } from 'mastodon/actions/accounts';
import { IconWithBadge } from 'mastodon/components/icon_with_badge';
import { WordmarkLogo } from 'mastodon/components/logo';
import { NavigationPortal } from 'mastodon/components/navigation_portal';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { timelinePreview, trendsEnabled } from 'mastodon/initial_state';
import { transientSingleColumn } from 'mastodon/is_mobile';
@ -97,12 +98,8 @@ const FollowRequestsLink = () => {
};
class NavigationPanel extends Component {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
};
@ -112,7 +109,7 @@ class NavigationPanel extends Component {
render () {
const { intl } = this.props;
const { signedIn, disabledAccountId } = this.context.identity;
const { signedIn, disabledAccountId } = this.props.identity;
let banner = undefined;
@ -189,4 +186,4 @@ class NavigationPanel extends Component {
}
export default injectIntl(NavigationPanel);
export default injectIntl(withIdentity(NavigationPanel));

View file

@ -15,6 +15,7 @@ import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
import { PictureInPicture } from 'mastodon/features/picture_in_picture';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { layoutFromWindow } from 'mastodon/is_mobile';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -120,12 +121,8 @@ const keyMap = {
};
class SwitchingColumnsArea extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
children: PropTypes.node,
location: PropTypes.object,
singleColumn: PropTypes.bool,
@ -160,7 +157,7 @@ class SwitchingColumnsArea extends PureComponent {
render () {
const { children, singleColumn } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const pathName = this.props.location.pathname;
let redirect;
@ -252,12 +249,8 @@ class SwitchingColumnsArea extends PureComponent {
}
class UI extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
children: PropTypes.node,
isComposing: PropTypes.bool,
@ -309,7 +302,7 @@ class UI extends PureComponent {
this.dragTargets.push(e.target);
}
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.context.identity.signedIn) {
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.props.identity.signedIn) {
this.setState({ draggingOver: true });
}
};
@ -337,7 +330,7 @@ class UI extends PureComponent {
this.setState({ draggingOver: false });
this.dragTargets = [];
if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.context.identity.signedIn) {
if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.props.identity.signedIn) {
this.props.dispatch(uploadCompose(e.dataTransfer.files));
}
};
@ -389,7 +382,7 @@ class UI extends PureComponent {
};
componentDidMount () {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
window.addEventListener('focus', this.handleWindowFocus, false);
window.addEventListener('blur', this.handleWindowBlur, false);
@ -586,7 +579,7 @@ class UI extends PureComponent {
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef}>
<Header />
<SwitchingColumnsArea location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
<SwitchingColumnsArea identity={this.props.identity} location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
{children}
</SwitchingColumnsArea>
@ -602,4 +595,4 @@ class UI extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(withRouter(UI)));
export default connect(mapStateToProps)(injectIntl(withRouter(withIdentity(UI))));

View file

@ -0,0 +1,74 @@
import PropTypes from 'prop-types';
import { createContext, useContext } from 'react';
import hoistStatics from 'hoist-non-react-statics';
import type { InitialState } from 'mastodon/initial_state';
export interface IdentityContextType {
signedIn: boolean;
accountId: string | undefined;
disabledAccountId: string | undefined;
accessToken: string | undefined;
permissions: number;
}
export const identityContextPropShape = PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired;
export const createIdentityContext = (state: InitialState) => ({
signedIn: !!state.meta.me,
accountId: state.meta.me,
disabledAccountId: state.meta.disabled_account_id,
accessToken: state.meta.access_token,
permissions: state.role?.permissions ?? 0,
});
export const IdentityContext = createContext<IdentityContextType>({
signedIn: false,
permissions: 0,
accountId: undefined,
disabledAccountId: undefined,
accessToken: undefined,
});
export const useIdentity = () => useContext(IdentityContext);
export interface IdentityProps {
ref?: unknown;
wrappedComponentRef?: unknown;
}
/* Injects an `identity` props into the wrapped component to be able to use the new context in class components */
export function withIdentity<
ComponentType extends React.ComponentType<IdentityProps>,
>(Component: ComponentType) {
const displayName = `withIdentity(${Component.displayName ?? Component.name})`;
const C = (props: React.ComponentProps<ComponentType>) => {
const { wrappedComponentRef, ...remainingProps } = props;
return (
<IdentityContext.Consumer>
{(context) => {
return (
// @ts-expect-error - Dynamic covariant generic components are tough to type.
<Component
{...remainingProps}
identity={context}
ref={wrappedComponentRef}
/>
);
}}
</IdentityContext.Consumer>
);
};
C.displayName = displayName;
C.WrappedComponent = Component;
return hoistStatics(C, Component);
}

View file

@ -44,6 +44,15 @@
* @property {string} sso_redirect
*/
/**
* @typedef Role
* @property {string} id
* @property {string} name
* @property {string} permissions
* @property {string} color
* @property {boolean} highlighted
*/
/**
* @typedef PollLimits
* @property {number} min_options
@ -59,6 +68,7 @@
* @property {InitialStateLanguage[]} languages
* @property {boolean=} critical_updates_pending
* @property {InitialStateMeta} meta
* @property {Role?} role
* @property {PollLimits} poll_limits
*/

View file

@ -19,7 +19,7 @@
"account.block_domain": "Blocar dominio {domain}",
"account.block_short": "Blocar",
"account.blocked": "Blocate",
"account.browse_more_on_origin_server": "Navigar plus sur le profilo original",
"account.browse_more_on_origin_server": "Percurrer plus sur le profilo original",
"account.cancel_follow_request": "Cancellar sequimento",
"account.copy": "Copiar ligamine a profilo",
"account.direct": "Mentionar privatemente @{name}",
@ -122,7 +122,7 @@
"column.direct": "Mentiones private",
"column.directory": "Navigar profilos",
"column.domain_blocks": "Dominios blocate",
"column.favourites": "Favoritos",
"column.favourites": "Favorites",
"column.firehose": "Fluxos in directo",
"column.follow_requests": "Requestas de sequimento",
"column.home": "Initio",
@ -204,7 +204,7 @@
"disabled_account_banner.account_settings": "Parametros de conto",
"disabled_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate.",
"dismissable_banner.community_timeline": "Ecce le messages public le plus recente del personas con contos sur {domain}.",
"dismissable_banner.dismiss": "Dimitter",
"dismissable_banner.dismiss": "Clauder",
"dismissable_banner.explore_links": "Istes es le articulos de novas que se condivide le plus sur le rete social hodie. Le articulos de novas le plus recente, publicate per plus personas differente, se classifica plus in alto.",
"dismissable_banner.explore_statuses": "Ecce le messages de tote le rete social que gania popularitate hodie. Le messages plus nove con plus impulsos e favorites se classifica plus in alto.",
"dismissable_banner.explore_tags": "Ecce le hashtags que gania popularitate sur le rete social hodie. Le hashtags usate per plus personas differente se classifica plus in alto.",
@ -212,8 +212,8 @@
"domain_block_modal.block": "Blocar le servitor",
"domain_block_modal.block_account_instead": "Blocar @{name} in su loco",
"domain_block_modal.they_can_interact_with_old_posts": "Le personas de iste servitor pote interager con tu messages ancian.",
"domain_block_modal.they_cant_follow": "Nulle persona ab iste servitor pote sequer te.",
"domain_block_modal.they_wont_know": "Illes non sapera que illes ha essite blocate.",
"domain_block_modal.they_cant_follow": "Necuno de iste servitor pote sequer te.",
"domain_block_modal.they_wont_know": "Ille non sapera que ille ha essite blocate.",
"domain_block_modal.title": "Blocar dominio?",
"domain_block_modal.you_will_lose_followers": "Omne sequitores ab iste servitor essera removite.",
"domain_block_modal.you_wont_see_posts": "Tu non videra messages e notificationes ab usatores sur iste servitor.",
@ -307,7 +307,7 @@
"follow_request.reject": "Rejectar",
"follow_requests.unlocked_explanation": "Benque tu conto non es serrate, le personal de {domain} pensa que es un bon idea que tu revide manualmente le sequente requestas de iste contos.",
"follow_suggestions.curated_suggestion": "Selection del equipa",
"follow_suggestions.dismiss": "Non monstrar novemente",
"follow_suggestions.dismiss": "Non monstrar de novo",
"follow_suggestions.featured_longer": "Seligite con cura per le equipa de {domain}",
"follow_suggestions.friends_of_friends_longer": "Popular inter le gente que tu seque",
"follow_suggestions.hints.featured": "Iste profilo ha essite seligite manualmente per le equipa de {domain}.",
@ -412,7 +412,7 @@
"lightbox.next": "Sequente",
"lightbox.previous": "Precedente",
"limited_account_hint.action": "Monstrar profilo in omne caso",
"limited_account_hint.title": "Iste profilo esseva celate per le moderatores de {domain}.",
"limited_account_hint.title": "Iste profilo ha essite celate per le moderatores de {domain}.",
"link_preview.author": "Per {name}",
"lists.account.add": "Adder al lista",
"lists.account.remove": "Remover del lista",
@ -432,12 +432,12 @@
"loading_indicator.label": "Cargante…",
"media_gallery.toggle_visible": "{number, plural, one {Celar imagine} other {Celar imagines}}",
"moved_to_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate perque tu ha cambiate de conto a {movedToAccount}.",
"mute_modal.hide_from_notifications": "Celar ab notificationes",
"mute_modal.hide_from_notifications": "Celar in notificationes",
"mute_modal.hide_options": "Celar optiones",
"mute_modal.indefinite": "Usque io dissilentia iste persona",
"mute_modal.show_options": "Monstrar optiones",
"mute_modal.they_can_mention_and_follow": "Illes pote mentionar te e sequer te, ma tu non potera vider los.",
"mute_modal.they_wont_know": "Illes non sapera que illes ha essite silentiate.",
"mute_modal.they_can_mention_and_follow": "Ille pote mentionar te e sequer te, ma tu non potera vider le.",
"mute_modal.they_wont_know": "Ille non sapera que ille ha essite silentiate.",
"mute_modal.title": "Silentiar le usator?",
"mute_modal.you_wont_see_mentions": "Tu non videra le messages que mentiona iste persona.",
"mute_modal.you_wont_see_posts": "Iste persona pote totevia vider tu messages, ma tu non videra le sues.",
@ -451,13 +451,13 @@
"navigation_bar.discover": "Discoperir",
"navigation_bar.domain_blocks": "Dominios blocate",
"navigation_bar.explore": "Explorar",
"navigation_bar.favourites": "Favoritos",
"navigation_bar.favourites": "Favorites",
"navigation_bar.filters": "Parolas silentiate",
"navigation_bar.follow_requests": "Requestas de sequimento",
"navigation_bar.followed_tags": "Hashtags sequite",
"navigation_bar.follows_and_followers": "Sequites e sequitores",
"navigation_bar.lists": "Listas",
"navigation_bar.logout": "Clauder le session",
"navigation_bar.logout": "Clauder session",
"navigation_bar.mutes": "Usatores silentiate",
"navigation_bar.opened_in_classic_interface": "Messages, contos e altere paginas specific es aperite per predefinition in le interfacie web classic.",
"navigation_bar.personal": "Personal",
@ -501,7 +501,7 @@
"notifications.column_settings.admin.report": "Nove signalationes:",
"notifications.column_settings.admin.sign_up": "Nove inscriptiones:",
"notifications.column_settings.alert": "Notificationes de scriptorio",
"notifications.column_settings.favourite": "Favoritos:",
"notifications.column_settings.favourite": "Favorites:",
"notifications.column_settings.filter_bar.advanced": "Monstrar tote le categorias",
"notifications.column_settings.filter_bar.category": "Barra de filtro rapide",
"notifications.column_settings.follow": "Nove sequitores:",
@ -518,7 +518,7 @@
"notifications.column_settings.update": "Modificationes:",
"notifications.filter.all": "Toto",
"notifications.filter.boosts": "Impulsos",
"notifications.filter.favourites": "Favoritos",
"notifications.filter.favourites": "Favorites",
"notifications.filter.follows": "Sequites",
"notifications.filter.mentions": "Mentiones",
"notifications.filter.polls": "Resultatos del sondage",
@ -717,7 +717,7 @@
"status.edited": "Ultime modification le {date}",
"status.edited_x_times": "Modificate {count, plural, one {{count} vice} other {{count} vices}}",
"status.embed": "Incastrar",
"status.favourite": "Adder al favoritos",
"status.favourite": "Adder al favorites",
"status.favourites": "{count, plural, one {favorite} other {favorites}}",
"status.filter": "Filtrar iste message",
"status.filtered": "Filtrate",

View file

@ -468,6 +468,7 @@
"notification.follow": "{name} подписался (-лась) на вас",
"notification.follow_request": "{name} отправил запрос на подписку",
"notification.mention": "{name} упомянул(а) вас",
"notification.moderation_warning.action_delete_statuses": "Некоторые из ваших публикаций были удалены.",
"notification.own_poll": "Ваш опрос закончился",
"notification.poll": "Опрос, в котором вы приняли участие, завершился",
"notification.reblog": "{name} продвинул(а) ваш пост",

View file

@ -474,6 +474,7 @@
"notification.follow_request": "{name} vam želi slediti",
"notification.mention": "{name} vas je omenil/a",
"notification.moderation-warning.learn_more": "Več o tem",
"notification.moderation_warning": "Prejeli ste opozorilo moderatorjev",
"notification.moderation_warning.action_delete_statuses": "Nekatere vaše objave so odstranjene.",
"notification.moderation_warning.action_disable": "Vaš račun je bil onemogočen.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Nekatere vaše objave so bile označene kot občutljive.",

View file

@ -158,7 +158,7 @@
"compose_form.poll.option_placeholder": "ตัวเลือก {number}",
"compose_form.poll.single": "เลือกอย่างใดอย่างหนึ่ง",
"compose_form.poll.switch_to_multiple": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตหลายตัวเลือก",
"compose_form.poll.switch_to_single": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตตัวเลือกเดียว",
"compose_form.poll.switch_to_single": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตตัวเลือกเดียว",
"compose_form.poll.type": "ลักษณะ",
"compose_form.publish": "โพสต์",
"compose_form.publish_form": "โพสต์ใหม่",

View file

@ -1,7 +1,3 @@
import PropTypes from 'prop-types';
import type { PropsWithChildren } from 'react';
import { Component } from 'react';
import { IntlProvider } from 'react-intl';
import { MemoryRouter } from 'react-router';
@ -9,44 +5,27 @@ import { MemoryRouter } from 'react-router';
// eslint-disable-next-line import/no-extraneous-dependencies
import { render as rtlRender } from '@testing-library/react';
class FakeIdentityWrapper extends Component<
PropsWithChildren<{ signedIn: boolean }>
> {
static childContextTypes = {
identity: PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired,
};
getChildContext() {
return {
identity: {
signedIn: this.props.signedIn,
accountId: '123',
accessToken: 'test-access-token',
},
};
}
render() {
return this.props.children;
}
}
import { IdentityContext } from './identity_context';
function render(
ui: React.ReactElement,
{ locale = 'en', signedIn = true, ...renderOptions } = {},
) {
const fakeIdentity = {
signedIn: signedIn,
accountId: '123',
accessToken: 'test-access-token',
disabledAccountId: undefined,
permissions: 0,
};
const Wrapper = (props: { children: React.ReactNode }) => {
return (
<MemoryRouter>
<IntlProvider locale={locale}>
<FakeIdentityWrapper signedIn={signedIn}>
<IdentityContext.Provider value={fakeIdentity}>
{props.children}
</FakeIdentityWrapper>
</IdentityContext.Provider>
</IntlProvider>
</MemoryRouter>
);

View file

@ -23,6 +23,12 @@ module ApplicationExtension
redirect_uri.lines.first.strip
end
def redirect_uris
# Doorkeeper stores the redirect_uri value as a newline delimeted list in
# the database:
redirect_uri.split
end
def push_to_streaming_api
# TODO: #28793 Combine into a single topic
payload = Oj.dump(event: :kill)

View file

@ -9,10 +9,10 @@ class Vacuum::ImportsVacuum
private
def clean_unconfirmed_imports!
BulkImport.state_unconfirmed.where('created_at <= ?', 10.minutes.ago).reorder(nil).in_batches.delete_all
BulkImport.state_unconfirmed.where(created_at: ..10.minutes.ago).reorder(nil).in_batches.delete_all
end
def clean_old_imports!
BulkImport.where('created_at <= ?', 1.week.ago).reorder(nil).in_batches.delete_all
BulkImport.where(created_at: ..1.week.ago).reorder(nil).in_batches.delete_all
end
end

View file

@ -34,7 +34,7 @@ class Vacuum::StatusesVacuum
def statuses_scope
Status.unscoped.kept
.joins(:account).merge(Account.remote)
.where('statuses.id < ?', retention_period_as_id)
.where(statuses: { id: ...retention_period_as_id })
end
def retention_period_as_id

View file

@ -5,7 +5,6 @@ class UserMailer < Devise::Mailer
helper :accounts
helper :application
helper :mascot
helper :formatting
helper :instance
helper :routing

View file

@ -59,6 +59,7 @@ class Admin::ActionLogFilter
unsuspend_account: { target_type: 'Account', action: 'unsuspend' }.freeze,
update_announcement: { target_type: 'Announcement', action: 'update' }.freeze,
update_custom_emoji: { target_type: 'CustomEmoji', action: 'update' }.freeze,
update_report: { target_type: 'Report', action: 'update' }.freeze,
update_status: { target_type: 'Status', action: 'update' }.freeze,
update_user_role: { target_type: 'UserRole', action: 'update' }.freeze,
update_ip_block: { target_type: 'IpBlock', action: 'update' }.freeze,

View file

@ -4,7 +4,7 @@ module Expireable
extend ActiveSupport::Concern
included do
scope :expired, -> { where.not(expires_at: nil).where('expires_at < ?', Time.now.utc) }
scope :expired, -> { where.not(expires_at: nil).where(expires_at: ...Time.now.utc) }
def expires_in
return @expires_in if defined?(@expires_in)

View file

@ -24,7 +24,7 @@ class Invite < ApplicationRecord
belongs_to :user, inverse_of: :invites
has_many :users, inverse_of: :invite, dependent: nil
scope :available, -> { where(expires_at: nil).or(where('expires_at >= ?', Time.now.utc)) }
scope :available, -> { where(expires_at: nil).or(where(expires_at: Time.now.utc..)) }
validates :comment, length: { maximum: COMMENT_SIZE_LIMIT }

View file

@ -4,6 +4,6 @@ class BackupPolicy < ApplicationPolicy
MIN_AGE = 6.days
def create?
user_signed_in? && current_user.backups.where('created_at >= ?', MIN_AGE.ago).count.zero?
user_signed_in? && current_user.backups.where(created_at: MIN_AGE.ago..).count.zero?
end
end

View file

@ -1,24 +1,18 @@
# frozen_string_literal: true
class REST::ApplicationSerializer < ActiveModel::Serializer
attributes :id, :name, :website, :scopes, :redirect_uri,
:client_id, :client_secret
attributes :id, :name, :website, :scopes, :redirect_uris
# NOTE: Deprecated in 4.3.0, needs to be removed in 5.0.0
attribute :vapid_key
# We should consider this property deprecated for 4.3.0
attribute :redirect_uri
def id
object.id.to_s
end
def client_id
object.uid
end
def client_secret
object.secret
end
def website
object.website.presence
end

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
class REST::CredentialApplicationSerializer < REST::ApplicationSerializer
attributes :client_id, :client_secret
def client_id
object.uid
end
def client_secret
object.secret
end
end

View file

@ -56,7 +56,7 @@ class FetchLinkCardService < BaseService
@html_charset = res.charset
res.body_with_limit
res.truncated_body
end
end

View file

@ -16,11 +16,11 @@ class Scheduler::IpCleanupScheduler
private
def clean_ip_columns!
SessionActivation.where('updated_at < ?', SESSION_RETENTION_PERIOD.ago).in_batches.destroy_all
SessionActivation.where('updated_at < ?', IP_RETENTION_PERIOD.ago).in_batches.update_all(ip: nil)
User.where('current_sign_in_at < ?', IP_RETENTION_PERIOD.ago).in_batches.update_all(sign_up_ip: nil)
LoginActivity.where('created_at < ?', IP_RETENTION_PERIOD.ago).in_batches.destroy_all
Doorkeeper::AccessToken.where('last_used_at < ?', IP_RETENTION_PERIOD.ago).in_batches.update_all(last_used_ip: nil)
SessionActivation.where(updated_at: ...SESSION_RETENTION_PERIOD.ago).in_batches.destroy_all
SessionActivation.where(updated_at: ...IP_RETENTION_PERIOD.ago).in_batches.update_all(ip: nil)
User.where(current_sign_in_at: ...IP_RETENTION_PERIOD.ago).in_batches.update_all(sign_up_ip: nil)
LoginActivity.where(created_at: ...IP_RETENTION_PERIOD.ago).in_batches.destroy_all
Doorkeeper::AccessToken.where(last_used_at: ...IP_RETENTION_PERIOD.ago).in_batches.update_all(last_used_ip: nil)
end
def clean_expired_ip_blocks!

View file

@ -20,7 +20,7 @@ class Scheduler::ScheduledStatusesScheduler
end
def due_statuses
ScheduledStatus.where('scheduled_at <= ?', Time.now.utc + PostStatusService::MIN_SCHEDULE_OFFSET)
ScheduledStatus.where(scheduled_at: ..Time.now.utc + PostStatusService::MIN_SCHEDULE_OFFSET)
end
def publish_scheduled_announcements!

View file

@ -25,7 +25,7 @@ class Scheduler::UserCleanupScheduler
end
def clean_discarded_statuses!
Status.unscoped.discarded.where('deleted_at <= ?', DISCARDED_STATUSES_MAX_AGE_DAYS.days.ago).find_in_batches do |statuses|
Status.unscoped.discarded.where(deleted_at: ..DISCARDED_STATUSES_MAX_AGE_DAYS.days.ago).find_in_batches do |statuses|
RemovalWorker.push_bulk(statuses) do |status|
[status.id, { 'immediate' => true, 'skip_streaming' => true }]
end

View file

@ -5,7 +5,7 @@
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY
).each do |key|
ENV.fetch(key) do
value = ENV.fetch(key) do
abort <<~MESSAGE
Catstodon now requires that these variables are set:
@ -14,9 +14,18 @@
- ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT
- ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY
Run `bin/rails db:encryption:init` to generate values and then assign the environment variables.
Run `bin/rails db:encryption:init` to generate new secrets and then assign the environment variables.
MESSAGE
end
next unless Rails.env.production? && value.end_with?('DO_NOT_USE_IN_PRODUCTION')
abort <<~MESSAGE
It looks like you are trying to run Mastodon in production with a #{key} value from the test environment.
Please generate fresh secrets using `bin/rails db:encryption:init` and use them instead.
MESSAGE
end
Rails.application.configure do

View file

@ -51,6 +51,9 @@ if ENV.keys.any? { |name| name.match?(/OTEL_.*_ENDPOINT/) }
use_rack_events: false, # instead of events, use middleware; allows for untraced_endpoints to ignore child spans
untraced_endpoints: ['/health'],
},
'OpenTelemetry::Instrumentation::Sidekiq' => {
span_naming: :job_class, # Use the job class as the span name, otherwise this is the queue name and not very helpful
},
})
prefix = ENV.fetch('OTEL_SERVICE_NAME_PREFIX', 'mastodon')

View file

@ -285,6 +285,7 @@ bg:
update_custom_emoji_html: "%{name} обнови емоджито %{target}"
update_domain_block_html: "%{name} обнови блокирането на домейна за %{target}"
update_ip_block_html: "%{name} промени правило за IP на %{target}"
update_report_html: "%{name} осъвремени доклад %{target}"
update_status_html: "%{name} обнови публикация от %{target}"
update_user_role_html: "%{name} промени ролята %{target}"
deleted_account: изтрит акаунт

View file

@ -285,6 +285,7 @@ ca:
update_custom_emoji_html: "%{name} ha actualitzat l'emoji %{target}"
update_domain_block_html: "%{name} ha actualitzat el bloqueig de domini per a %{target}"
update_ip_block_html: "%{name} ha canviat la norma per la IP %{target}"
update_report_html: "%{name} ha actualitzat l'informe %{target}"
update_status_html: "%{name} ha actualitzat l'estat de %{target}"
update_user_role_html: "%{name} ha canviat el rol %{target}"
deleted_account: compte eliminat

View file

@ -285,6 +285,7 @@ da:
update_custom_emoji_html: "%{name} opdaterede emoji %{target}"
update_domain_block_html: "%{name} opdaterede domæneblokeringen for %{target}"
update_ip_block_html: "%{name} ændrede reglen for IP'en %{target}"
update_report_html: "%{name} opdaterede rapporten %{target}"
update_status_html: "%{name} opdaterede indlægget fra %{target}"
update_user_role_html: "%{name} ændrede %{target}-rolle"
deleted_account: slettet konto

View file

@ -285,6 +285,7 @@ de:
update_custom_emoji_html: "%{name} bearbeitete das Emoji %{target}"
update_domain_block_html: "%{name} aktualisierte die Domain-Sperre für %{target}"
update_ip_block_html: "%{name} änderte die Regel für die IP-Adresse %{target}"
update_report_html: "%{name} überarbeitete die Meldung %{target}"
update_status_html: "%{name} überarbeitete einen Beitrag von %{target}"
update_user_role_html: "%{name} änderte die Rolle von %{target}"
deleted_account: gelöschtes Konto

View file

@ -6,11 +6,11 @@ ia:
send_instructions: Tu recipera un e-mail con instructiones pro confirmar tu adresse de e-mail in poc minutas. Per favor verifica tu dossier de spam si tu non lo recipe.
send_paranoid_instructions: Si tu adresse de e-mail existe in nostre base de datos, tu recipera un e-mail con instructiones pro confirmar tu adresse de e-mail in poc minutas. Per favor verifica tu dossier de spam si tu non lo recipe.
failure:
already_authenticated: Tu jam initiava le session.
inactive: Tu conto ancora non es activate.
already_authenticated: Tu ha jam aperite session.
inactive: Tu conto non es ancora activate.
invalid: "%{authentication_keys} o contrasigno non valide."
last_attempt: Tu ha solmente un altere tentativa ante que tu conto es serrate.
locked: Tu conto es blocate.
locked: Tu conto es serrate.
not_found_in_database: "%{authentication_keys} o contrasigno non valide."
omniauth_user_creation_failure: Error creante un conto pro iste identitate.
pending: Tu conto es ancora sub revision.
@ -51,12 +51,12 @@ ia:
explanation: Ora es possibile aperir session con solmente le adresse de e-mail e contrasigno.
subject: 'Mastodon: Authentication bifactorial disactivate'
subtitle: Le authentication bifactorial ha essite disactivate pro tu conto.
title: 2FA disactivate
title: A2F disactivate
two_factor_enabled:
explanation: Pro le apertura de session essera necessari un token generate per le application TOTP accopulate.
subject: 'Mastodon: Authentication bifactorial activate'
subtitle: Le authentication bifactorial ha essite activate pro tu conto.
title: 2FA activate
title: A2F activate
two_factor_recovery_codes_changed:
explanation: Le ancian codices de recuperation ha essite invalidate e nove codices ha essite generate.
subject: 'Mastodon: Codices de recuperation regenerate'
@ -66,11 +66,11 @@ ia:
subject: 'Mastodon: Instructiones pro disblocar'
webauthn_credential:
added:
explanation: Le sequente clave de securitate esseva addite a tu conto
explanation: Le sequente clave de securitate ha essite addite a tu conto
subject: 'Mastodon: Nove clave de securitate'
title: Un nove clave de securitate esseva addite
title: Un nove clave de securitate ha essite addite
deleted:
explanation: Le sequente clave de securitate esseva delite de tu conto
explanation: Le sequente clave de securitate ha essite delite de tu conto
subject: 'Mastodon: Clave de securitate delite'
title: Un de tu claves de securitate ha essite delite
webauthn_disabled:
@ -81,18 +81,41 @@ ia:
webauthn_enabled:
explanation: Le authentication con claves de securitate ha essite activate pro tu conto.
extra: Tu clave de securitate pote ora esser usate pro aperir session.
subject: 'Mastodon: authentication de clave de securitate activate'
title: Claves de securitate activate
omniauth_callbacks:
failure: Impossibile authenticar te ab %{kind} perque “%{reason}”.
success: Authenticate con successo ab conto %{kind}.
passwords:
no_token: Tu non pote acceder iste pagina sin venir ab un email de redefinition de contrasigno. Si tu veni ab un email de redefinition de contrasigno, verifica que tu usava le integre URL fornite.
send_instructions: Si tu adresse de e-mail existe in nostre base de datos, tu recipera un ligamine de recuperation de contrasigno in tu adresse de e-mail in poc minutas. Per favor verifica tu dossier de spam si tu non lo recipe.
send_paranoid_instructions: Si tu adresse de e-mail existe in nostre base de datos, tu recipera un ligamine de recuperation de contrasigno in tu adresse de e-mail in poc minutas. Per favor verifica tu dossier de spam si tu non lo recipe.
updated: Tu contrasigno ha essite cambiate. Tu ha ora aperite session.
updated_not_active: Tu contrasigno ha essite cambiate.
registrations:
destroyed: A revider! Tu conto esseva cancellate con successo. Nos spera vider te novemente tosto.
signed_up_but_pending: Un message con un ligamine de confirmation esseva inviate a tu conto de email. Post que tu clicca le ligamine, nos revidera tu application. Tu essera notificate si illo es approbate.
destroyed: A revider! Tu conto ha essite cancellate. Nos spera vider te de novo tosto.
signed_up: Benvenite! Tu te ha inscribite con successo.
signed_up_but_inactive: Tu te ha inscribite con successo. Nonobstante, nos non poteva aperir tu session perque tu conto non es ancora activate.
signed_up_but_locked: Tu te ha inscribite con successo. Nonobstante, nos non poteva aperir tu session perque tu conto es serrate.
signed_up_but_pending: Un message con un ligamine de confirmation ha essite inviate a tu adresse de email. Post que tu clicca sur le ligamine, nos revidera tu demanda. Tu essera notificate si illo es approbate.
signed_up_but_unconfirmed: Un message con un ligamine de confirmation ha essite inviate a tu adresse de e-mail. Per favor seque le ligamine pro activar tu conto. Verifica tu dossier de spam si tu non recipe iste e-mail.
update_needs_confirmation: Tu ha actualisate tu conto con successo, ma nos debe verificar tu nove adresse de e-mail. Accede a tu e-mail e seque le ligamine de confirmation pro confirmar tu nove adresse de e-mail. Verifica tu dossier de spam si tu non recipe iste e-mail.
updated: Tu conto ha essite actualisate con successo.
sessions:
signed_in: Connexe con successo.
signed_out: Disconnexe con successo.
already_signed_out: Session claudite con successo.
signed_in: Session aperite con successo.
signed_out: Session claudite con successo.
unlocks:
unlocked: Tu conto ha essite disblocate con successo. Initia session a continuar.
send_instructions: Tu recipera un e-mail con instructiones explicante como disserrar tu conto in alcun minutas. Verifica tu dossier de spam si tu non recipe iste e-mail.
send_paranoid_instructions: Si tu conto existe, tu recipera un email con instructiones explicante como disserrar lo in alcun minutas. Verifica tu dossier de spam si tu non recipe iste e-mail.
unlocked: Tu conto ha essite disserrate con successo. Aperi session pro continuar.
errors:
messages:
already_confirmed: jam esseva confirmate, tenta initiar session
already_confirmed: jam esseva confirmate, tenta aperir session
confirmation_period_expired: debe esser confirmate in %{period}, per favor requesta un nove
expired: ha expirate, per favor requesta un nove
not_found: non trovate
not_locked: non era blocate
not_locked: non esseva serrate
not_saved:
one: '1 error ha impedite a iste %{resource} de esser salvate:'
other: "%{count} errores ha impedite a iste %{resource} de esser salvate:"

View file

@ -3,28 +3,40 @@ ia:
activerecord:
attributes:
doorkeeper/application:
name: Nomine de application
name: Nomine del application
redirect_uri: URI de redirection
scopes: Ambitos
website: Sito web de application
website: Sito web del application
errors:
models:
doorkeeper/application:
attributes:
redirect_uri:
fragment_present: non pote continer un fragmento.
invalid_uri: debe esser un URI valide.
relative_uri: debe esser un URI absolute.
secured_uri: debe esser un URI HTTPS/SSL.
doorkeeper:
applications:
buttons:
authorize: Autorisar
cancel: Cancellar
destroy: Destruer
edit: Modificar
submit: Submitter
confirmations:
destroy: Es tu secur?
edit:
title: Modificar application
form:
error: Oops! Verifica tu formulario pro possibile errores
help:
native_redirect_uri: Usar %{native_redirect_uri} pro tests local
redirect_uri: Usar un linea per URI
scopes: Separa ambitos con spatios. Lassa vacue pro usar le ambitos predefinite.
index:
application: Application
callback_url: URL de retorno
delete: Deler
empty: Tu non ha applicationes.
name: Nomine
@ -37,17 +49,22 @@ ia:
show:
actions: Actiones
application_id: Clave del cliente
callback_urls: URLs de retorno
scopes: Ambitos
secret: Secreto del application
title: 'Application: %{name}'
authorizations:
buttons:
authorize: Autorisar
deny: Negar
error:
title: Ocurreva un error
title: Un error ha occurrite
new:
prompt_html: "%{client_name} vole haber le permission de acceder a tu conto. Illo es un application tertie. <strong>Si tu non confide in illo, alora tu non deberea autorisar lo.</strong>"
review_permissions: Revisionar le permissos
title: Autorisation necessari
show:
title: Copia iste codice de autorisation e colla lo in le application.
authorized_applications:
buttons:
revoke: Revocar
@ -55,11 +72,35 @@ ia:
revoke: Es tu secur?
index:
authorized_at: Autorisate le %{date}
description_html: Ecce applicationes que pote acceder tu conto per le API. Si il ha applicationes que tu non recognosce ci, o un application que se comporta mal, tu pote revocar su accesso.
last_used_at: Ultime uso in %{date}
never_used: Nunquam usate
scopes: Permissiones
superapp: Interne
title: Tu applicationes autorisate
errors:
messages:
access_denied: Le proprietario del ressource o servitor de autorisation ha refusate le requesta.
credential_flow_not_configured: Le processo de credentiales de contrasigno del proprietario del ressource ha fallite perque Doorkeeper.configure.resource_owner_from_credentials non es configurate.
invalid_client: Le authentication del cliente ha fallite perque le cliente es incognite, necun authentication de cliente es includite, o le methodo de authentication non es supportate.
invalid_grant: Le concession de autorisation fornite es invalide, expirate, revocate, non corresponde al URI de redirection usate in le requesta de autorisation, o ha essite emittite a un altere cliente.
invalid_redirect_uri: Le URI de redirection includite non es valide.
invalid_request:
missing_param: 'Parametro requirite mancante: %{value}.'
request_not_authorized: Le requesta debe esser autorisate. Un parametro requirite pro autorisar le requesta manca o non es valide.
unknown: Le requesta non include un parametro requirite, include un valor de parametro non supportate, o es alteremente mal formate.
invalid_resource_owner: Le credentiales del proprietario del ressource fornite non es valide, o le proprietario del ressource non pote esser trovate
invalid_scope: Le ambito requirite es invalide, incognite, o mal formate.
invalid_token:
expired: Le token de accesso ha expirate
revoked: Le token de accesso ha essite revocate
unknown: Le token de accesso non es valide
resource_owner_authenticator_not_configured: Impossibile trovar le proprietario del ressource perque Doorkeeper.configure.resource_owner_authenticator non es configurate.
server_error: Le servitor de autorisation ha incontrate un condition impreviste que lo ha impedite de complir le requesta.
temporarily_unavailable: Le servitor de autorisation actualmente non pote gerer le requesta a causa de un supercarga temporari o de mantenentia del servitor.
unauthorized_client: Le application non es autorisate a exequer iste requesta usante iste methodo.
unsupported_grant_type: Le typo de concession de autorisation non es supportate per le servitor de autorisation.
unsupported_response_type: Le servitor de autorisation non supporta iste typo de responsa.
flash:
applications:
create:
@ -73,20 +114,22 @@ ia:
notice: Application revocate.
grouped_scopes:
access:
read: Accesso de sol lectura
read: Accesso de lectura sol
read/write: Accesso de lectura e scriptura
write: Accesso de sol scriptura
write: Accesso de scriptura sol
title:
accounts: Contos
admin/accounts: Gestion de contos
admin/all: Tote le functiones administrative
admin/reports: Gestion de reportos
all: Accesso plen a tu conto de Mastodon
all: Accesso complete a tu conto de Mastodon
blocks: Blocadas
bookmarks: Marcapaginas
conversations: Conversationes
favourites: Favoritos
crypto: Cryptation de puncta a puncta
favourites: Favorites
filters: Filtros
follow: Sequites, silentiates e blocates
follows: Sequites
lists: Listas
media: Annexos multimedial
@ -101,21 +144,41 @@ ia:
nav:
applications: Applicationes
oauth2_provider: Fornitor OAuth2
application:
title: Autorisation OAuth necessari
scopes:
admin:read: leger tote le datos in le servitor
admin:read:accounts: leger information sensibile de tote le contos
admin:read:canonical_email_blocks: leger datos sensibile de tote le blocadas de email canonic
admin:read:domain_allows: leger informationes sensibile de tote le dominios permittite
admin:read:domain_blocks: leger informationes sensibile de tote le blocadas de dominio
admin:read:email_domain_blocks: leger informationes sensibile de tote le blocadas de dominio email
admin:read:ip_blocks: leger informationes sensibile de tote le blocadas de IP
admin:read:reports: leger information sensibile de tote le reportos e contos signalate
admin:write: modificar tote le datos in le servitor
admin:write:accounts: exequer action de moderation sur contos
admin:write:canonical_email_blocks: exequer actiones de moderation sur blocadas de email canonic
admin:write:domain_allows: exequer actiones de moderation sur dominios permittite
admin:write:domain_blocks: exequer actiones de moderation sur blocadas de dominio
admin:write:email_domain_blocks: exequer actiones de moderation sur blocadas de dominio email
admin:write:ip_blocks: exequer actiones de moderation sur blocadas de IP
admin:write:reports: exequer action de moderation sur reportos
crypto: usar cryptation de extremo-a-extremo
follow: modificar relationes del contos
push: reciper tu notificationes push
read: leger tote le datos de tu conto
read:accounts: vider informationes de conto
read:blocks: vider tu blocadas
read:bookmarks: vider tu marcapaginas
read:favourites: vider tu favoritos
read:filters: vider tu filtros
read:follows: vider tu sequites
read:lists: vider tu listas
read:me: leger solmente le information basic de tu conto
read:mutes: vider tu silentiates
read:notifications: vider tu notificationes
read:reports: vider tu reportos
read:search: cercar in tu nomine
read:statuses: vider tote le messages
write: modificar tote le datos de tu conto
write:accounts: modificar tu profilo

View file

@ -285,6 +285,7 @@ en:
update_custom_emoji_html: "%{name} updated emoji %{target}"
update_domain_block_html: "%{name} updated domain block for %{target}"
update_ip_block_html: "%{name} changed rule for IP %{target}"
update_report_html: "%{name} updated report %{target}"
update_status_html: "%{name} updated post by %{target}"
update_user_role_html: "%{name} changed %{target} role"
deleted_account: deleted account

View file

@ -285,6 +285,7 @@ es-AR:
update_custom_emoji_html: "%{name} actualizó el emoji %{target}"
update_domain_block_html: "%{name} actualizó el bloqueo de dominio para %{target}"
update_ip_block_html: "%{name} cambió la regla para la dirección IP %{target}"
update_report_html: "%{name} actualizó la denuncia %{target}"
update_status_html: "%{name} actualizó el mensaje de %{target}"
update_user_role_html: "%{name} cambió el rol %{target}"
deleted_account: cuenta eliminada

View file

@ -285,6 +285,7 @@ es-MX:
update_custom_emoji_html: "%{name} actualizó el emoji %{target}"
update_domain_block_html: "%{name} actualizó el bloqueo de dominio para %{target}"
update_ip_block_html: "%{name} cambió la regla para la IP %{target}"
update_report_html: "%{name} actualizó el informe %{target}"
update_status_html: "%{name} actualizó el estado de %{target}"
update_user_role_html: "%{name} cambió el rol %{target}"
deleted_account: cuenta eliminada

View file

@ -285,6 +285,7 @@ es:
update_custom_emoji_html: "%{name} actualizó el emoji %{target}"
update_domain_block_html: "%{name} actualizó el bloqueo de dominio para %{target}"
update_ip_block_html: "%{name} cambió la regla para la IP %{target}"
update_report_html: "%{name} actualizó el informe %{target}"
update_status_html: "%{name} actualizó la publicación de %{target}"
update_user_role_html: "%{name} cambió el rol %{target}"
deleted_account: cuenta eliminada

View file

@ -285,6 +285,7 @@ fi:
update_custom_emoji_html: "%{name} päivitti emojin %{target}"
update_domain_block_html: "%{name} päivitti verkkotunnuksen %{target} eston"
update_ip_block_html: "%{name} muutti sääntöä IP-osoitteelle %{target}"
update_report_html: "%{name} päivitti raportin %{target}"
update_status_html: "%{name} päivitti käyttäjän %{target} julkaisun"
update_user_role_html: "%{name} muutti roolia %{target}"
deleted_account: poisti tilin

View file

@ -285,6 +285,7 @@ fo:
update_custom_emoji_html: "%{name} dagførdi kensluteknið %{target}"
update_domain_block_html: "%{name} dagførdi navnaøkisblokeringina hjá %{target}"
update_ip_block_html: "%{name} broytti IP-reglurnar %{target}"
update_report_html: "%{name} dagførdi meldingina %{target}"
update_status_html: "%{name} dagførdi postin hjá %{target}"
update_user_role_html: "%{name} broyttir %{target} leiklutir"
deleted_account: strikað konta

View file

@ -285,6 +285,7 @@ gl:
update_custom_emoji_html: "%{name} actualizou o emoji %{target}"
update_domain_block_html: "%{name} actualizou o bloqueo do dominio para %{target}"
update_ip_block_html: "%{name} cambiou a regra para IP %{target}"
update_report_html: "%{name} actualizou a denuncia %{target}"
update_status_html: "%{name} actualizou a publicación de %{target}"
update_user_role_html: "%{name} cambiou o rol %{target}"
deleted_account: conta eliminada

View file

@ -291,6 +291,7 @@ he:
update_custom_emoji_html: "%{name} עדכן/ה אמוג'י %{target}"
update_domain_block_html: "%{name} עדכן/ה חסימת דומיין עבור %{target}"
update_ip_block_html: "%{name} שינה כלל עבור IP %{target}"
update_report_html: '%{name} עדכן/ה דו"ח %{target}'
update_status_html: "%{name} עדכן/ה הודעה של %{target}"
update_user_role_html: "%{name} שינה את התפקיד של %{target}"
deleted_account: חשבון מחוק

View file

@ -285,6 +285,7 @@ hu:
update_custom_emoji_html: "%{name} frissítette az emodzsit: %{target}"
update_domain_block_html: "%{name} frissítette a %{target} domain tiltását"
update_ip_block_html: "%{name} módosította a(z) %{target} IP-címre vonatkozó szabályt"
update_report_html: "%{name} frissítette a %{target} bejelentést"
update_status_html: "%{name} frissítette %{target} felhasználó bejegyzését"
update_user_role_html: "%{name} módosította a(z) %{target} szerepkört"
deleted_account: törölt fiók

View file

@ -285,6 +285,7 @@ ia:
update_custom_emoji_html: "%{name} actualisava le emoticone %{target}"
update_domain_block_html: "%{name} actualisava le blocada de dominio pro %{target}"
update_ip_block_html: "%{name} cambiava le regula pro IP %{target}"
update_report_html: "%{name} actualisava le reporto %{target}"
update_status_html: "%{name} actualisava le message per %{target}"
update_user_role_html: "%{name} cambiava le rolo de %{target}"
deleted_account: conto delite
@ -973,6 +974,7 @@ ia:
webhook: Crocs web
admin_mailer:
auto_close_registrations:
body: Per un carentia recente de activate de moderator, le registrationes sur %{instance} ha essite automaticamente mutate a besoniante revision manual, pro impedir %{instance} de esser usate como un platteforma pro potential mal actores. Tu pote mutar lo retro pro sempre aperir le registrationes.
subject: Le registrationes pro %{instance} ha essite automaticamente mutate a besoniante de approbation
new_appeal:
actions:
@ -1054,19 +1056,27 @@ ia:
clicking_this_link: cliccante iste ligamine
login_link: acceder
proceed_to_login_html: Ora tu pote continuar a %{login_link}.
redirect_to_app_html: Tu deberea haber essite re-dirigite al app <strong>%{app_name}</strong>. Si isto non eveni, tenta %{clicking_this_link} o manualmente retorna al app.
registration_complete: Tu registration sur %{domain} es ora complete!
welcome_title: Benvenite, %{name}!
wrong_email_hint: Si ille adresse email non es correcte, tu pote cambiar lo in parametros de conto.
delete_account: Deler le conto
delete_account_html: Si tu vole a dele tu conto, tu pote <a href="%{path}">continuar ci</a>. Te sera demandate confirmation.
description:
prefix_invited_by_user: "@%{name} te invita a junger te a iste servitor de Mastodon!"
prefix_sign_up: Inscribe te sur Mastodon hodie!
suffix: Con un conto, tu potera sequer personas, messages de actualisation e excambios de messages con usatores de ulle servitor de Mastodon e plus!
didnt_get_confirmation: Non recipeva tu un ligamine de confirmation?
dont_have_your_security_key: Non ha tu le clave de securitate?
forgot_password: Contrasigno oblidate?
invalid_reset_password_token: Pete un nove.
link_to_otp: Insere un codice a duo factores o un codice de recuperation ab tu telephono
link_to_webauth: Usa tu apparato clave de securitate
log_in_with: Accede con
login: Accede
logout: Clauder le session
migrate_account: Move a un conto differente
migrate_account_html: Si tu vole re-adressar iste conto a un altere, tu pote <a href="%{path}">configurar lo ci</a>.
or_log_in_with: O accede con
privacy_policy_agreement_html: Io ha legite e acceptar le <a href="<a href="%{privacy_policy_path}" target="_blank">politica de confidentialitate</a>
progress:
@ -1192,6 +1202,7 @@ ia:
invalid_domain: non es un nomine de dominio valide
edit_profile:
basic_information: Information basic
hint_html: "<strong>Personalisa lo que le personas vide sur tu profilo public e presso tu messages.</strong> Il es plus probabile que altere personas te seque e interage con te quando tu ha un profilo compilate e un photo de profilo."
other: Alteres
errors:
'400': Le requesta que tu inviava era non valide o mal formate.
@ -1230,6 +1241,7 @@ ia:
add_new: Adder nove
errors:
limit: Tu ha jam consiliate le maxime numero de hashtags
hint_html: "<strong>Consilia tu plus importante hashtags sur tu profilo.</strong> Un grande instrumento pro tener tracia de tu labores creative e projectos de longe-tempore, le hashtags consiliate es monstrate prominentemente sur tu profilo e permitte accesso rapide a tu proprie messages."
filters:
contexts:
account: Profilos
@ -1241,8 +1253,10 @@ ia:
add_keyword: Adder parola clave
keywords: Parolas clave
statuses: Messages individual
statuses_hint_html: Iste filtro se applica a seliger messages singule sin reguardo si illes concorda le parolas clave infra. <a href="%{path}">Revide o remove le messages ab le filtro</a>.
title: Modificar filtro
errors:
deprecated_api_multiple_keywords: Iste parametros non pote esser cambiate ab iste application perque illos se applica a plus que un sol parola clave del filtro. Usa un application plus recente o le interfacie web.
invalid_context: Nulle o non valide contexto supplite
index:
contexts: Filtros in %{contexts}
@ -1272,6 +1286,12 @@ ia:
title: Messages filtrate
generic:
all: Toto
all_items_on_page_selected_html:
one: "<strong>%{count}</strong> elemento sur iste pagina es seligite."
other: Tote le <strong>%{count}</strong> elementos sur iste pagina es seligite.
all_matching_items_selected_html:
one: "<strong>%{count}</strong> elemento concordante que tu cerca es seligite."
other: Tote le <strong>%{count}</strong> elementos concordante que tu cerca es seligite.
cancel: Cancellar
changes_saved_msg: Cambios salveguardate con successo!
confirm: Confirmar
@ -1299,13 +1319,24 @@ ia:
imported: Importate
mismatched_types_warning: Il appare que tu pote haber seligite le typo errate pro iste importation, controla duo vices.
modes:
merge: Funder
merge_long: Mantene le registrationes existente e adde illos nove
overwrite: Superscriber
overwrite_long: Reimplaciar registros actual con le noves
overwrite_preambles:
blocking_html: Tu es sur le puncto de <strong>reimplaciar tu lista de blocadas</strong> per usque a <strong>%{total_items} contos</strong> proveniente de <strong>%{filename}</strong>.
bookmarks_html: Tu va <strong>reimplaciar tu lista de blocadas</strong> per usque a <strong>%{total_items} contos</strong> proveniente de <strong>%{filename}</strong>.
domain_blocking_html: Tu es sur le puncto de <strong>reimplaciar tu lista de blocadas de dominio</strong> per usque a <strong>%{total_items} dominios</strong> proveniente de <strong>%{filename}</strong>.
following_html: Tu va <strong>sequer</strong> usque <strong>%{total_items} contos</strong> de <strong>%{filename}</strong> e <strong>cessar de sequer ulle altere</strong>.
lists_html: Tu va <strong>reimplaciar tu lista</strong> con contentos de <strong>%{filename}</strong>. Usque <strong>%{total_items} contos</strong> sera addite a nove listas.
muting_html: Tu va <strong>reimplaciar tu lista de contos silentiate</strong> con usque <strong>%{total_items} contos</strong> ab <strong>%{filename}</strong>.
preambles:
blocking_html: Tu es sur le puncto de <strong>blocar</strong> usque a <strong>%{total_items} contos</strong> a partir de <strong>%{filename}</strong>.
bookmarks_html: Tu va adder usque <strong>%{total_items} messages</strong> de <strong>%{filename}</strong> a tu <strong>marcapaginas</strong>.
domain_blocking_html: Tu es sur le puncto de <strong>blocar</strong> usque a <strong>%{total_items} dominios</strong> a partir de <strong>%{filename}</strong>.
following_html: Tu va <strong>blocar</strong> usque a <strong>%{total_items} dominios</strong> ab <strong>%{filename}</strong>.
lists_html: Tu va adder usque <strong>%{total_items} contos</strong> ab <strong>%{filename}</strong> a tu <strong>lista</strong>. Nove listas sera create si il non ha lista a adder.
muting_html: Tu va <strong>silentiar</strong> usque <strong>%{total_items} contos</strong> ab <strong>%{filename}</strong>.
preface: Tu pote importar datos que tu ha exportate de un altere servitor, como un lista de personas que tu seque o bloca.
recent_imports: Importationes recente
states:
@ -1348,16 +1379,30 @@ ia:
expires_in_prompt: Nunquam
generate: Generar ligamine de invitation
invalid: Iste invitation non es valide
invited_by: 'Tu ha essite invitate per:'
max_uses:
one: un uso
other: "%{count} usos"
max_uses_prompt: Nulle limite
prompt: Genera e comparti ligamines con alteres pro conceder accesso a iste servitor
table:
expires_at: Expira
uses: Usos
title: Invitar personas
lists:
errors:
limit: Tu ha attingite le maxime numero de listas
login_activities:
authentication_methods:
otp: app pro authentication a duo factores
password: contrasigno
sign_in_token: codice de securitate de e-mail
webauthn: claves de securitate
description_html: Si tu vide activitate que tu non recognosce, considera de cambiar tu contrasigno e activar le authentication a duo factores.
empty: Nulle chronologia de authentication disponibile
failed_sign_in_html: Tentativa de authentication fallite con %{method} ab %{ip} (%{browser})
successful_sign_in_html: Apertura de session con successo con %{method} ab %{ip} (%{browser})
title: Chronologia de authentication
mail_subscriptions:
unsubscribe:
action: Si, desubscriber
@ -1373,32 +1418,110 @@ ia:
resubscribe_html: Si tu ha cancellate le subscription in error, tu pote resubscriber te a partir del <a href="%{settings_path}">parametros de notification in e-mail</a>.
success_html: Tu non recipera plus %{type} pro Mastodon sur %{domain} a tu adresse de e-mail %{email}.
title: Desubcriber
media_attachments:
validations:
images_and_video: Impossibile annexar un video a un message que jam contine imagines
not_ready: Impossibile annexar un video a un message que jam contine imagines. Retenta post un momento!
too_many: Impossibile annexar plus que 4 files
migrations:
acct: Movite a
cancel: Cancellar redirection
cancel_explanation: Cancellar le redirection reactivara tu conto actual, ma non reportara sequaces que ha essite movite in ille conto.
cancelled_msg: Redirection cancellate con successo.
errors:
already_moved: is the same account you have already moved to
missing_also_known_as: non es un alias de iste conto
move_to_self: non pote esser le conto actual
not_found: non poterea esser trovate
on_cooldown: Tu es in pausa
followers_count: Sequaces a tempore de mover
incoming_migrations: Movente ab un conto differente
incoming_migrations_html: Pro mover ab un altere conto a isto, primo tu debe <a href="%{path}">crear un alias de conto</a>.
moved_msg: Tu conto ora es redirigite a %{acct} e tu sequaces es movite super.
not_redirecting: Tu conto actualmente non es redirigite a ulle altere conto.
on_cooldown: Tu recentemente ha migrate tu conto. Iste function de novo sera disponibile in %{count} dies.
past_migrations: Migrationes passate
proceed_with_move: Mover sequaces
redirected_msg: Tu conto es ora redirigite a %{acct}.
redirecting_to: Tu conto es redirigite a %{acct}.
set_redirect: Predefinir redirection
warning:
backreference_required: Le nove conto debe primo esser configurate pro referer se a isto
before: 'Ante de continuar, lege iste notas accuratemente:'
cooldown: Post le movimento il ha un periodo de pausa durante le qual tu non potera mover te ancora
disabled_account: Tu conto actual non sera plenmente usabile postea. Comocunque, tu habera accesso a exportation de datos e re-activation.
followers: Iste action movera tote le sequaces ab le conto actual al nove conto
only_redirect_html: In alternativa, tu pote <a href="%{path}">solo superponer un redirection sur tu profilo</a>.
other_data: Nulle altere datos sera movite automaticamente
redirect: Le profilo de tu conto actual sera actualisate con un aviso de redirection e sera excludite de recercas
moderation:
title: Moderation
move_handler:
carry_blocks_over_text: Iste usator ha cambiate de conto desde %{acct}, que tu habeva blocate.
carry_mutes_over_text: Iste usator moveva ab %{acct}, que tu habeva silentiate.
copy_account_note_text: 'Iste usator moveva ab %{acct}, ci era tu previe notas re ille:'
navigation:
toggle_menu: Mutar menu
notification_mailer:
admin:
report:
subject: "%{name} inviava un reporto"
sign_up:
subject: "%{name} se ha inscribite"
favourite:
body: 'Tu message era favorite per %{name}:'
subject: "%{name} favoriva tu message"
title: Nove preferito
follow:
body: "%{name} ora te seque!"
subject: "%{name} ora te seque"
title: Nove sequitor
follow_request:
action: Gere requestas de sequer
body: "%{name} ha demandate de sequer te"
subject: 'Sequace pendente: %{name}'
title: Nove requesta de sequimento
mention:
action: Responder
body: 'Tu era mentionate per %{name} in:'
subject: Tu ha essite mentionate per %{name}
title: Nove mention
poll:
subject: Un inquesta de %{name} ha finite
reblog:
body: 'Tu message ha essite impulsate per %{name}:'
subject: "%{name} ha impulsate tu message"
title: Nove impulso
status:
subject: "%{name} justo ha publicate"
update:
subject: "%{name} ha modificate un message"
notifications:
administration_emails: Avisos de email per administrator
email_events: Eventos pro avisos de email
email_events_hint: 'Selige eventos pro que tu vole reciper avisos:'
number:
human:
decimal_units:
format: "%n%u"
units:
billion: B
million: M
quadrillion: Q
thousand: K
trillion: T
otp_authentication:
code_hint: Insere le codice generate per tu app de authentication pro confirmar
description_html: Si tu activa <strong>le authentication a duo factores</strong> per un app de authentication, le authentication requirera que tu es in possession de tu telephono, que generara testimonios pro facer te entrar.
enable: Activar
instructions_html: "<strong>Scande iste codice QR in Google Authenticator o un simile app TOTP sur tu telephono</strong>. Desde ora in avante, ille app generara testimonios que tu debera inserer quando tu te authenticara."
manual_instructions: 'Si tu non pote scander le codice QR e besonia de inserer lo manualmente, ecce le texto-simple secrete:'
setup: Configurar
wrong_code: Le codice inserite non era valide! Es tempore de servitor e tempore de apparato correcte?
pagination:
newer: Plus recente
next: Sequente
older: Plus vetere
prev: Previe
truncate: "&hellip;"
polls:
@ -1418,9 +1541,13 @@ ia:
posting_defaults: Publicationes predefinite
public_timelines: Chronologias public
privacy:
hint_html: "<strong>Personalisa como tu vole que tu profilo e tu messages a es trovate.</strong> Un varietate de functiones in Mastodon pote adjutar te attinger un plus large auditorio si activate. Prende un momento pro revider iste parametros pro assecurar te que illos se adapta a tu caso de uso."
privacy: Confidentialitate
privacy_hint_html: Controla quanto tu vole divulgar pro le beneficio de alteres. Le gente discoperi profilos e applicationes interessante percurrente le profilos sequite per altere personas e vidente a partir de qual applicationes illos publica lor messages, ma tu pote preferer de mantener tal information private.
reach: Portata
reach_hint_html: Controla si tu vole esser discoperite e sequite per nove personas. Vole tu que tu messages appare sur le schermo Explorar? Vole tu que altere personas te vide in lor recommendationes de sequimento? Vole tu acceptar automaticamente tote le nove sequitores o prefere tu haber le controlo granular super cata un?
search: Cercar
search_hint_html: Controla como tu vole esser trovate. Vole tu que le gente te trova per medio del contento de tu messages public? Vole tu que personas foras de Mastodon trova tu profilo quando illes cerca in le web? Nota ben que non es possibile garantir le exclusion total de tu information public del motores de recerca.
title: Confidentialitate e portata
privacy_policy:
title: Politica de confidentialitate
@ -1521,35 +1648,96 @@ ia:
aliases: Aliases de conto
appearance: Apparentia
authorized_apps: Apps autorisate
back: Tornar a Mastodon
delete: Deletion de conto
development: Disveloppamento
edit_profile: Modificar profilo
export: Exportation de datos
featured_tags: Hashtags eminente
import: Importar
import_and_export: Importar e exportar
migrate: Migration de conto
notifications: Notificationes de e-mail
preferences: Preferentias
profile: Profilo public
relationships: Sequites e sequitores
severed_relationships: Relationes rupte
statuses_cleanup: Deletion de message automatic
strikes: Admonitiones de moderation
two_factor_authentication: Authentication a duo factores
webauthn_authentication: Claves de securitate
severed_relationships:
download: Discargar (%{count})
event_type:
account_suspension: Suspension del conto (%{target_name})
domain_block: Suspension del servitor (%{target_name})
user_domain_block: Tu ha blocate %{target_name}
lost_followers: Sequitores perdite
lost_follows: Sequites perdite
preamble: Tu pote perder sequites e sequitores quando tu bloca un dominio o quando tu moderatores decide suspender un servitor remote. Quando isto occurre, tu potera discargar listas de relationes rumpite, a inspectar e eventualmente importar in un altere servitor.
purged: Le information re iste servitor ha essite purgate per le administratores de tu servitor.
type: Evento
statuses:
attached:
audio:
one: "%{count} audio"
other: "%{count} audio"
description: 'Attachate: %{attached}'
image:
one: "%{count} imagine"
other: "%{count} imagines"
video:
one: "%{count} video"
other: "%{count} videos"
boosted_from_html: Impulsate desde %{acct_link}
content_warning: 'Advertimento de contento: %{warning}'
default_language: Mesme como lingua de interfacie
disallowed_hashtags:
one: 'contineva un hashtag non autorisate: %{tags}'
other: 'contineva le hashtags non autorisate: %{tags}'
edited_at_html: Modificate le %{date}
errors:
in_reply_not_found: Le message a que tu tenta responder non pare exister.
open_in_web: Aperir in le web
over_character_limit: limite de characteres de %{max} excedite
pin_errors:
direct: Messages que es solo visibile a usatores mentionate non pote esser appunctate
limit: Tu ha jam appunctate le maxime numero de messages
ownership: Le message de alcuno altere non pote esser appunctate
reblog: Un impulso non pote esser affixate
poll:
total_people:
one: "%{count} persona"
other: "%{count} personas"
total_votes:
one: "%{count} voto"
other: "%{count} votos"
vote: Votar
show_more: Monstrar plus
show_thread: Monstrar argumento
title: '%{name}: "%{quote}"'
visibilities:
direct: Directe
private: Solo-sequaces
private_long: Solmente monstrar a sequitores
public: Public
public_long: Omnes pote vider
unlisted: Non listate
unlisted_long: Omnes pote vider, ma non es listate in le chronologias public
statuses_cleanup:
enabled: Deler automaticamente le messages ancian
enabled_hint: Dele automaticamente tu messages un vice que illos attinge un limine de etate specificate, salvo que illes concorda un del exceptiones infra
exceptions: Exceptiones
explanation: Pois que deler messages es un operation costose, isto es facite lentemente in le tempore quando le servitor non es alteremente occupate. Pro iste ration, tu messages pote esser delite un poco post que illos attinge le limine de etate.
ignore_favs: Ignorar favoritos
ignore_reblogs: Ignorar impulsos
interaction_exceptions: Exceptiones basate super interactiones
interaction_exceptions_explanation: Nota que il non ha garantia que le messages essera delite si illos va sub le limine de favorites o impulsos post haber lo superate un vice.
keep_direct: Mantener le messages directe
keep_direct_hint: Non dele alcuno de tu messages directe
keep_media: Mantener messages con annexos de medios
keep_media_hint: Non dele alcuno de tu messages que ha annexos de medios
keep_pinned: Mantener messages appunctate
keep_pinned_hint: Non dele alcuno de tu messages appunctate
keep_polls: Mantener sondages
keep_polls_hint: Non dele ulle de tu sondages
@ -1567,32 +1755,94 @@ ia:
'63113904': 2 annos
'7889238': 3 menses
min_age_label: Limine de etate
min_favs: Mantener messages favorite al minus
min_favs_hint: Non deler alcuno de tu messages que ha recipite al minus iste numero de favoritos. Lassar blanc pro deler messages sin reguardo de lor numero de favoritos
min_reblogs: Mantener messages impulsate al minus
min_reblogs_hint: Non dele alcun de tu messages que ha essite impulsate al minus iste numero de vices. Lassar vacue pro deler messages independentemente de lor numero de impulsos
stream_entries:
sensitive_content: Contento sensibile
strikes:
errors:
too_late: Es troppo tarde pro facer appello contra iste admonition
tags:
does_not_match_previous_name: non concorda le nomine previe
themes:
contrast: Mastodon (Alte contrasto)
default: Mastodon (Obscur)
mastodon-light: Mastodon (Clar)
system: Automatic (usar thema del systema)
time:
formats:
default: "%d %b %Y, %H:%M"
month: "%b %Y"
time: "%H:%M"
with_time_zone: "%b %d, %Y, %H:%M %Z"
translation:
errors:
quota_exceeded: Le quota de utilisation del servitor pro le servicio de traduction ha essite excedite.
too_many_requests: Il ha habite troppe requestas al servicio de traduction recentemente.
two_factor_authentication:
add: Adder
disable: Disactivar 2FA
disabled_success: Authentication a duo factores disactivate con successo
edit: Modificar
enabled: Le authentication a duo factores es activate
enabled_success: Authentication a duo factores activate con successo
generate_recovery_codes: Generar codices de recuperation
lost_recovery_codes: Le codices de recuperation te permitte de reganiar accesso a tu conto si tu perde tu telephono. Si tu ha perdite tu codices de recuperation, tu pote regenerar los ci. Tu vetere codices de recuperation sera invalidate.
methods: Methodos a duo factores
otp: App de authenticator
recovery_codes: Salveguardar codices de recuperation
recovery_codes_regenerated: Codices de recuperation regenerate con successo
recovery_instructions_html: Si tu perde le accesso a tu telephono, tu pote usar un del codices de recuperation hic infra pro reganiar le accesso a tu conto. <strong>Mantene le codices de recuperation secur.</strong> Per exemplo, tu pote imprimer los e guardar los con altere documentos importante.
webauthn: Claves de securitate
user_mailer:
appeal_approved:
action: Parametros de conto
explanation: Le appello contra le admonition contra tu conto del %{strike_date}, que tu ha submittite le %{appeal_date}, ha essite approbate. Tu conto ha de novo un bon reputation.
subject: Tu appello ab %{date} ha essite approbate
subtitle: Tu conto es ancora un vice in regula.
title: Appello approbate
appeal_rejected:
explanation: Le appello contra le admonition contra tu conto del %{strike_date}, que tu ha submittite le %{appeal_date}, ha essite rejectate.
subject: Tu appello ab %{date} ha essite rejectate
subtitle: Tu appello ha essite rejectate.
title: Appello rejectate
backup_ready:
explanation: Tu ha requestate un copia de securitate complete de tu conto de Mastodon.
extra: Isto es preste pro discargar!
subject: Tu archivo es preste pro discargar
title: Discargar archivo
failed_2fa:
details: 'Hic es le detalios del tentativa de initio de session:'
explanation: Alcuno ha tentate aperir session a tu conto ma ha fornite un secunde factor de authentication non valide.
further_actions_html: Si non se tractava de te, nos recommenda %{action} immediatemente perque illo pote esser compromittite.
subject: Fallimento del authentication de duo factores
title: Falleva le authentication de duo factores
suspicious_sign_in:
change_password: cambiar tu contrasigno
details: 'Hic es le detalios del initio de session:'
explanation: Nos ha detegite un initio de session a tu conto ab un nove adresse IP.
further_actions_html: Si non se tractava de te, nos recommenda %{action} immediatemente e activar le authentication bifactorial pro mantener tu conto secur.
subject: Alcuno ha accedite a tu conto desde un nove adresse IP
title: Un nove initio de session
warning:
appeal: Submitter un appello
appeal_description: Si tu crede que se tracta de un error, tu pote presentar un appello al personal de %{instance}.
categories:
spam: Spam
violation: Le contento viola le sequente regulas del communitate
explanation:
delete_statuses: Alcunes de tu messages ha essite judicate contrari a un o plus directivas communitari e ha dunque essite removite per le moderatores de %{instance}.
disable: Tu non pote plus usar tu conto, ma tu profilo e altere datos remane intacte. Tu pote requestar un copia de reserva de tu datos, cambiar le parametros del conto o deler le conto.
mark_statuses_as_sensitive: Alcunes de tu messages ha essite marcate como sensibile per le moderatores de %{instance}. Isto vole dicer que le gente debe toccar le objectos multimedial in le messages ante que un previsualisation appare. Tu pote marcar objectos multimedial como sensibile tu mesme quando tu publica messages in futuro.
sensitive: A partir de iste momento, tote le files multimedial que tu incarga essera marcate como sensibile e le gente debera cliccar sur un advertimento ante de poter vider los.
silence: Tu pote ancora usar tu conto ma solmente le personas qui ja te seque videra tu messages sur iste servitor, e tu pote esser excludite de varie functiones de discoperta. Nonobstante, altere personas pote ancora sequer te manualmente.
suspend: Tu non pote plus usar tu conto, e tu profilo e altere datos non es plus accessibile. Tu pote ancora aperir session pro requestar un copia de reserva de tu datos usque lor elimination in circa 30 dies. Nos retenera certe datos de base pro impedir que tu evade le suspension.
reason: 'Ration:'
statuses: 'Message citate:'
subject:
delete_statuses: Tu messages sur %{acct} esseva removite
disable: Tu conto %{acct} ha essite gelate
mark_statuses_as_sensitive: Tu messages sur %{acct} ha essite marcate como sensibile
none: Advertimento pro %{acct}
@ -1612,20 +1862,71 @@ ia:
apps_ios_action: Discargar sur le App Store
apps_step: Discarga nostre applicationes official.
apps_title: Applicationes de Mastodon
checklist_subtitle: 'Comencia tu aventura sur le web social:'
checklist_title: Prime passos
edit_profile_action: Personalisar
edit_profile_step: Impulsa tu interactiones con un profilo comprehensive.
edit_profile_title: Personalisar tu profilo
explanation: Ecce alcun consilios pro initiar
feature_action: Apprender plus
feature_audience: Mastodon te presenta le possibilitate unic de gerer tu audientia sin intermediarios. Mastodon, installate sur tu proprie infrastructura, te permitte sequer, e esser sequite per, personas sur qualcunque altere servitor Mastodon in linea, e necuno lo controla salvo tu.
feature_audience_title: Crea tu auditorio in fiducia
feature_control: Tu sape melio lo que tu vole vider sur tu fluxo de initio. Nulle algorithmos o annuncios dissipa tu tempore. Seque quicinque sur qualcunque servitor Mastodon desde un sol conto, recipe lor messages in ordine chronologic, e face te un angulo del internet ubi tu te senti a casa.
feature_control_title: Mantene le controlo de tu proprie chronologia
feature_creativity: Mastodon supporta messages con audio, video e imagines, descriptiones de accessibilitate, sondages, advertimentos de contento, avatares con animation, emojis personalisate, controlo de retalio de miniaturas, e plus, pro adjutar te a exprimer te in linea. Que tu publica tu arte, tu musica o tu podcast, Mastodon existe pro te.
feature_creativity_title: Creativitate sin parallel
feature_moderation: Mastodon remitte le controlo in tu manos. Cata servitor crea su proprie regulas e directivas, applicate localmente e non de maniera vertical como le medios social corporative, rendente lo flexibile in responder al necessitates de differente gruppos de personas. Adhere a un servitor con regulas que te place, o alberga le tue.
feature_moderation_title: Moderation como deberea esser
follow_action: Sequer
follow_step: Sequer personas interessante es le ration de esser de Mastodon.
follow_title: Personalisa tu fluxo de initio
follows_subtitle: Seque contos popular
follows_title: Qui sequer
follows_view_more: Vider plus de personas a sequer
hashtags_recent_count:
one: "%{people} persona in le passate duo dies"
other: "%{people} personas in le passate duo diea"
hashtags_subtitle: Explora le tendentias del passate 2 dies
hashtags_title: Hashtags in tendentia
hashtags_view_more: Vider plus de hashtags in tendentia
post_action: Scriber
post_step: Saluta le mundo con texto, photos, videos o sondages.
post_title: Face tu prime message
share_action: Compartir
share_step: Face saper a tu amicos como trovar te sur Mastodon.
share_title: Compartir tu profilo de Mastodon
sign_in_action: Initiar session
subject: Benvenite in Mastodon
title: Benvenite a bordo, %{name}!
users:
follow_limit_reached: Tu non pote sequer plus de %{limit} personas
go_to_sso_account_settings: Vader al parametros de conto de tu fornitor de identitate
invalid_otp_token: Codice de duo factores non valide
otp_lost_help_html: Si tu ha perdite le accesso a ambes, tu pote contactar %{email}
rate_limited: Troppo de tentativas de authentication. Per favor reessaya plus tarde.
seamless_external_login: Tu ha aperite session per medio de un servicio externe. Le parametros de contrasigno e de e-mail es dunque indisponibile.
signed_in_as: 'Session aperite como:'
verification:
extra_instructions_html: <strong>Consilio:</strong> Le ligamine sur tu sito web pote esser invisibile. Le parte importante es <code>rel="me"</code> que impedi le usurpation de identitate sur sitos web con contento generate per usatores. Tu pote mesmo usar un etiquetta <code>link</code> in le capite del pagina in vice de <code>a</code>, ma le codice HTML debe esser accessibile sin executar JavaScript.
here_is_how: Ecce como
hint_html: "<strong>Omnes pote verificar lor identitate sur Mastodon.</strong> Isto es basate sur standards web aperte e es gratuite, ora e pro sempre. Tote lo que es necessari es un sito web personal que le gente recognosce como le tue. Quando tu liga a iste sito web desde tu profilo, le systema verificara que le sito web liga retro a tu profilo e monstrara un indicator visual de iste facto."
instructions_html: Copia e colla le codice hic infra in le HTML de tu sito web. Alora adde le adresse de tu sito web in un del campos supplementari sur tu profilo desde le scheda “Modificar profilo” e salva le cambiamentos.
verification: Verification
verified_links: Tu ligamines verificate
webauthn_credentials:
add: Adder un nove clave de securitate
create:
error: Il habeva un problema in adder tu clave de securitate. Tenta novemente.
success: Tu clave de securitate ha essite addite con successo.
delete: Deler
delete_confirmation: Es tu secur que tu vole deler iste clave de securitate?
description_html: Si tu activa le <strong>authentication per clave de securitate</strong>, le apertura de session requirera que tu usa un de tu claves de securitate.
destroy:
error: Il habeva un problema in deler tu clave de securitate. Tenta novemente.
success: Tu clave de securitate ha essite delite con successo.
invalid_credential: Clave de securitate non valide
nickname_hint: Insere le pseudonymo de tu nove clave de securitate
not_enabled: Tu ancora non ha activate WebAuthn
not_supported: Iste navigator non supporta claves de securitate
otp_required: Pro usar le claves de securitate activa prime le authentication de duo factores.
registered_on: Registrate le %{date}

View file

@ -285,6 +285,7 @@ it:
update_custom_emoji_html: "%{name} ha aggiornato emoji %{target}"
update_domain_block_html: "%{name} ha aggiornato il blocco dominio per %{target}"
update_ip_block_html: "%{name} ha cambiato la regola per l'IP %{target}"
update_report_html: "%{name} ha aggiornato la segnalazione %{target}"
update_status_html: "%{name} ha aggiornato lo status di %{target}"
update_user_role_html: "%{name} ha modificato il ruolo %{target}"
deleted_account: account eliminato

View file

@ -282,6 +282,7 @@ ko:
update_custom_emoji_html: "%{name} 님이 에모지 %{target}를 업데이트 했습니다"
update_domain_block_html: "%{name} 님이 %{target}에 대한 도메인 차단을 갱신했습니다"
update_ip_block_html: "%{name} 님이 IP 규칙 %{target}을 수정했습니다"
update_report_html: "%{name} 님이 신고 %{target}를 업데이트 했습니다"
update_status_html: "%{name} 님이 %{target}의 게시물을 업데이트했습니다"
update_user_role_html: "%{name} 님이 %{target} 역할을 수정했습니다"
deleted_account: 계정을 삭제했습니다

View file

@ -285,6 +285,7 @@ lad:
update_custom_emoji_html: "%{name} aktualizo el emoji %{target}"
update_domain_block_html: "%{name} aktualizo el bloko de domeno para %{target}"
update_ip_block_html: "\"%{name} troko la regla de IP %{target}"
update_report_html: "%{name} aktualizo el raporto %{target}"
update_status_html: "%{name} aktualizo la publikasyon de %{target}"
update_user_role_html: "%{name} troko el rolo %{target}"
deleted_account: kuento supremido

View file

@ -285,6 +285,7 @@ nl:
update_custom_emoji_html: Emoji %{target} is door %{name} bijgewerkt
update_domain_block_html: "%{name} heeft de domeinblokkade bijgewerkt voor %{target}"
update_ip_block_html: "%{name} wijzigde de IP-regel voor %{target}"
update_report_html: Rapportage %{target} is door %{name} bijgewerkt
update_status_html: "%{name} heeft de berichten van %{target} bijgewerkt"
update_user_role_html: "%{name} wijzigde de rol %{target}"
deleted_account: verwijderd account

Some files were not shown because too many files have changed in this diff Show more