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_DOMAIN=cb6e6126.ngrok.io
LOCAL_HTTPS=true LOCAL_HTTPS=true
# Required by ActiveRecord encryption feature # Secret values required by ActiveRecord encryption feature
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=fkSxKD2bF396kdQbrP1EJ7WbU7ZgNokR # Use `bin/rails db:encryption:init` to generate fresh secrets
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=r0hvVmzBVsjxC7AMlwhOzmtc36ZCOS1E ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=test_determinist_key_DO_NOT_USE_IN_PRODUCTION
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=PhdFyyfy5xJ7WVd2lWBpcPScRQHzRTNr 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 GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.1.3.2) actioncable (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (7.1.3.2) actionmailbox (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
activejob (= 7.1.3.2) activejob (= 7.1.3.3)
activerecord (= 7.1.3.2) activerecord (= 7.1.3.3)
activestorage (= 7.1.3.2) activestorage (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
mail (>= 2.7.1) mail (>= 2.7.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
actionmailer (7.1.3.2) actionmailer (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
actionview (= 7.1.3.2) actionview (= 7.1.3.3)
activejob (= 7.1.3.2) activejob (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (7.1.3.2) actionpack (7.1.3.3)
actionview (= 7.1.3.2) actionview (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
racc racc
rack (>= 2.2.4) rack (>= 2.2.4)
@ -46,15 +46,15 @@ GEM
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
actiontext (7.1.3.2) actiontext (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
activerecord (= 7.1.3.2) activerecord (= 7.1.3.3)
activestorage (= 7.1.3.2) activestorage (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.1.3.2) actionview (7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
@ -64,22 +64,22 @@ GEM
activemodel (>= 4.1) activemodel (>= 4.1)
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.1.3.2) activejob (7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.1.3.2) activemodel (7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
activerecord (7.1.3.2) activerecord (7.1.3.3)
activemodel (= 7.1.3.2) activemodel (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (7.1.3.2) activestorage (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
activejob (= 7.1.3.2) activejob (= 7.1.3.3)
activerecord (= 7.1.3.2) activerecord (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (7.1.3.2) activesupport (7.1.3.3)
base64 base64
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
@ -444,7 +444,7 @@ GEM
timeout timeout
net-smtp (0.5.0) net-smtp (0.5.0)
net-protocol net-protocol
nio4r (2.7.1) nio4r (2.7.3)
nokogiri (1.16.5) nokogiri (1.16.5)
mini_portile2 (~> 2.8.2) mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
@ -634,20 +634,20 @@ GEM
rackup (1.0.0) rackup (1.0.0)
rack (< 3) rack (< 3)
webrick webrick
rails (7.1.3.2) rails (7.1.3.3)
actioncable (= 7.1.3.2) actioncable (= 7.1.3.3)
actionmailbox (= 7.1.3.2) actionmailbox (= 7.1.3.3)
actionmailer (= 7.1.3.2) actionmailer (= 7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
actiontext (= 7.1.3.2) actiontext (= 7.1.3.3)
actionview (= 7.1.3.2) actionview (= 7.1.3.3)
activejob (= 7.1.3.2) activejob (= 7.1.3.3)
activemodel (= 7.1.3.2) activemodel (= 7.1.3.3)
activerecord (= 7.1.3.2) activerecord (= 7.1.3.3)
activestorage (= 7.1.3.2) activestorage (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.1.3.2) railties (= 7.1.3.3)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1)
@ -662,9 +662,9 @@ GEM
rails-i18n (7.0.9) rails-i18n (7.0.9)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8) railties (>= 6.0.0, < 8)
railties (7.1.3.2) railties (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
irb irb
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
@ -775,7 +775,7 @@ GEM
scenic (1.8.0) scenic (1.8.0)
activerecord (>= 4.0.0) activerecord (>= 4.0.0)
railties (>= 4.0.0) railties (>= 4.0.0)
selenium-webdriver (4.21.0) selenium-webdriver (4.21.1)
base64 (~> 0.2) base64 (~> 0.2)
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0) rubyzip (>= 1.2.2, < 3.0)

View file

@ -4,6 +4,6 @@ class Api::V1::Apps::CredentialsController < Api::BaseController
def show def show
return doorkeeper_render_error unless valid_doorkeeper_token? 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
end end

View file

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

View file

@ -241,6 +241,10 @@ module ApplicationHelper
EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s
end end
def mascot_url
full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg'))
end
def instance_presenter def instance_presenter
@instance_presenter ||= InstancePresenter.new @instance_presenter ||= InstancePresenter.new
end 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 SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { ButtonInTabsBar } from 'flavours/glitch/features/ui/util/columns_context'; 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 { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import { useAppHistory } from './router'; import { useAppHistory } from './router';
const messages = defineMessages({ const messages = defineMessages({
@ -51,12 +53,8 @@ BackButton.propTypes = {
}; };
class ColumnHeader extends PureComponent { class ColumnHeader extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
title: PropTypes.node, title: PropTypes.node,
icon: PropTypes.string, 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 = ( collapseButton = (
<button <button
className={collapsibleButtonClassName} 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 { Icon } from 'flavours/glitch/components/icon';
import emojify from 'flavours/glitch/features/emoji/emoji'; import emojify from 'flavours/glitch/features/emoji/emoji';
import Motion from 'flavours/glitch/features/ui/util/optional_motion'; import Motion from 'flavours/glitch/features/ui/util/optional_motion';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { RelativeTimestamp } from './relative_timestamp'; import { RelativeTimestamp } from './relative_timestamp';
@ -38,12 +39,8 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
}, {}); }, {});
class Poll extends ImmutablePureComponent { class Poll extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
poll: ImmutablePropTypes.map, poll: ImmutablePropTypes.map,
lang: PropTypes.string, lang: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -235,7 +232,7 @@ class Poll extends ImmutablePureComponent {
</ul> </ul>
<div className='poll__footer'> <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 && <><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> · </>} {showResults && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>}
{votesCount} {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 RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg'; import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg';
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.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 { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links'; import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -70,12 +71,8 @@ const messages = defineMessages({
}); });
class StatusActionBar extends ImmutablePureComponent { class StatusActionBar extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
onReply: PropTypes.func, onReply: PropTypes.func,
onFavourite: PropTypes.func, onFavourite: PropTypes.func,
@ -112,7 +109,7 @@ class StatusActionBar extends ImmutablePureComponent {
]; ];
handleReplyClick = () => { handleReplyClick = () => {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
this.props.onReply(this.props.status, this.props.history); this.props.onReply(this.props.status, this.props.history);
@ -128,7 +125,7 @@ class StatusActionBar extends ImmutablePureComponent {
}; };
handleFavouriteClick = (e) => { handleFavouriteClick = (e) => {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
this.props.onFavourite(this.props.status, e); this.props.onFavourite(this.props.status, e);
@ -142,7 +139,7 @@ class StatusActionBar extends ImmutablePureComponent {
}; };
handleReblogClick = e => { handleReblogClick = e => {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
this.props.onReblog(this.props.status, e); this.props.onReblog(this.props.status, e);
@ -218,7 +215,7 @@ class StatusActionBar extends ImmutablePureComponent {
render () { render () {
const { status, intl, withDismiss, withCounters, showReplyCount, scrollKey } = this.props; 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 mutingConversation = status.get('muted');
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); 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 MovieIcon from '@/material-icons/400-24px/movie.svg?react';
import MusicNoteIcon from '@/material-icons/400-24px/music_note.svg?react'; import MusicNoteIcon from '@/material-icons/400-24px/music_note.svg?react';
import { Icon } from 'flavours/glitch/components/icon'; 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 { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state';
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
@ -126,12 +127,8 @@ const mapStateToProps = state => ({
}); });
class StatusContent extends PureComponent { class StatusContent extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
statusContent: PropTypes.string, statusContent: PropTypes.string,
expanded: PropTypes.bool, expanded: PropTypes.bool,
@ -349,7 +346,7 @@ class StatusContent extends PureComponent {
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden; const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
const contentLocale = intl.locale.replace(/[_-].*/, ''); const contentLocale = intl.locale.replace(/[_-].*/, '');
const targetLanguages = this.props.languages?.get(status.get('language') || 'und'); 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 content = { __html: statusContent ?? getStatusContent(status) };
const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') }; 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 { PureComponent } from 'react';
import { Helmet } from 'react-helmet'; 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 ErrorBoundary from 'flavours/glitch/components/error_boundary';
import { Router } from 'flavours/glitch/components/router'; import { Router } from 'flavours/glitch/components/router';
import UI from 'flavours/glitch/features/ui'; 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 initialState, { title as siteTitle } from 'flavours/glitch/initial_state';
import { IntlProvider } from 'flavours/glitch/locales'; import { IntlProvider } from 'flavours/glitch/locales';
import { store } from 'flavours/glitch/store'; import { store } from 'flavours/glitch/store';
@ -33,33 +33,9 @@ if (initialState.meta.me) {
store.dispatch(fetchCustomEmojis()); 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 { 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); identity = createIdentityContext(initialState);
getChildContext() {
return {
identity: this.identity,
};
}
componentDidMount() { componentDidMount() {
if (this.identity.signedIn) { if (this.identity.signedIn) {
this.disconnect = store.dispatch(connectUserStream()); this.disconnect = store.dispatch(connectUserStream());
@ -79,19 +55,21 @@ export default class Mastodon extends PureComponent {
render () { render () {
return ( return (
<IntlProvider> <IdentityContext.Provider value={this.identity}>
<ReduxProvider store={store}> <IntlProvider>
<ErrorBoundary> <ReduxProvider store={store}>
<Router> <ErrorBoundary>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}> <Router>
<Route path='/' component={UI} /> <ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
</ScrollContext> <Route path='/' component={UI} />
</Router> </ScrollContext>
</Router>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} /> <Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
</ErrorBoundary> </ErrorBoundary>
</ReduxProvider> </ReduxProvider>
</IntlProvider> </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 { IconButton } from 'flavours/glitch/components/icon_button';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; 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 { autoPlayGif, me, domain as localDomain } from 'flavours/glitch/initial_state';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links'; import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links';
@ -94,6 +95,7 @@ const dateFormatOptions = {
class Header extends ImmutablePureComponent { class Header extends ImmutablePureComponent {
static propTypes = { static propTypes = {
identity: identityContextPropShape,
account: ImmutablePropTypes.record, account: ImmutablePropTypes.record,
identity_props: ImmutablePropTypes.list, identity_props: ImmutablePropTypes.list,
onFollow: PropTypes.func.isRequired, onFollow: PropTypes.func.isRequired,
@ -117,10 +119,6 @@ class Header extends ImmutablePureComponent {
...WithRouterPropTypes, ...WithRouterPropTypes,
}; };
static contextTypes = {
identity: PropTypes.object,
};
openEditProfile = () => { openEditProfile = () => {
window.open(profileLink, '_blank'); window.open(profileLink, '_blank');
}; };
@ -170,7 +168,7 @@ class Header extends ImmutablePureComponent {
render () { render () {
const { account, hidden, intl } = this.props; const { account, hidden, intl } = this.props;
const { signedIn, permissions } = this.context.identity; const { signedIn, permissions } = this.props.identity;
if (!account) { if (!account) {
return null; 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 PeopleIcon from '@/material-icons/400-24px/group.svg?react';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { domain } from 'flavours/glitch/initial_state'; import { domain } from 'flavours/glitch/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -40,16 +41,12 @@ const mapStateToProps = (state, { columnId }) => {
}; };
class CommunityTimeline extends PureComponent { class CommunityTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = { static defaultProps = {
onlyMedia: false, onlyMedia: false,
}; };
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -80,7 +77,7 @@ class CommunityTimeline extends PureComponent {
componentDidMount () { componentDidMount () {
const { dispatch, onlyMedia } = this.props; const { dispatch, onlyMedia } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
dispatch(expandCommunityTimeline({ onlyMedia })); dispatch(expandCommunityTimeline({ onlyMedia }));
@ -90,7 +87,7 @@ class CommunityTimeline extends PureComponent {
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia) { if (prevProps.onlyMedia !== this.props.onlyMedia) {
const { dispatch, onlyMedia } = this.props; 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 CloseIcon from '@/material-icons/400-24px/close.svg?react';
import SearchIcon from '@/material-icons/400-24px/search.svg?react'; import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { domain, searchEnabled } from 'flavours/glitch/initial_state'; import { domain, searchEnabled } from 'flavours/glitch/initial_state';
import { HASHTAG_REGEX } from 'flavours/glitch/utils/hashtags'; import { HASHTAG_REGEX } from 'flavours/glitch/utils/hashtags';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -33,12 +34,8 @@ const labelForRecentSearch = search => {
}; };
class Search extends PureComponent { class Search extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
value: PropTypes.string.isRequired, value: PropTypes.string.isRequired,
recent: ImmutablePropTypes.orderedSet, recent: ImmutablePropTypes.orderedSet,
submitted: PropTypes.bool, submitted: PropTypes.bool,
@ -276,7 +273,7 @@ class Search extends PureComponent {
} }
_calculateOptions (value) { _calculateOptions (value) {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const trimmedValue = value.trim(); const trimmedValue = value.trim();
const options = []; const options = [];
@ -318,7 +315,7 @@ class Search extends PureComponent {
render () { render () {
const { intl, value, submitted, recent } = this.props; const { intl, value, submitted, recent } = this.props;
const { expanded, options, selectedOption } = this.state; const { expanded, options, selectedOption } = this.state;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const hasValue = value.length > 0 || submitted; 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 Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header'; import ColumnHeader from 'flavours/glitch/components/column_header';
import Search from 'flavours/glitch/features/compose/containers/search_container'; 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 { trendsEnabled } from 'flavours/glitch/initial_state';
import Links from './links'; import Links from './links';
@ -32,12 +33,8 @@ const mapStateToProps = state => ({
}); });
class Explore extends PureComponent { class Explore extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
isSearching: PropTypes.bool, isSearching: PropTypes.bool,
@ -53,7 +50,7 @@ class Explore extends PureComponent {
render() { render() {
const { intl, multiColumn, isSearching } = this.props; const { intl, multiColumn, isSearching } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <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 { Helmet } from 'react-helmet';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { useIdentity } from '@/flavours/glitch/identity_context';
import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { addColumn } from 'flavours/glitch/actions/columns'; import { addColumn } from 'flavours/glitch/actions/columns';
import { changeSetting } from 'flavours/glitch/actions/settings'; 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 { expandPublicTimeline, expandCommunityTimeline } from 'flavours/glitch/actions/timelines';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import SettingText from 'flavours/glitch/components/setting_text'; 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 { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
import Column from '../../components/column'; 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' }, 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 ColumnSettings = () => {
const intl = useIntl(); const intl = useIntl();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();

View file

@ -28,6 +28,7 @@ import { fetchLists } from 'flavours/glitch/actions/lists';
import { openModal } from 'flavours/glitch/actions/modal'; import { openModal } from 'flavours/glitch/actions/modal';
import Column from 'flavours/glitch/features/ui/components/column'; import Column from 'flavours/glitch/features/ui/components/column';
import LinkFooter from 'flavours/glitch/features/ui/components/link_footer'; 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'; import { preferencesLink } from 'flavours/glitch/utils/backend_links';
@ -99,12 +100,8 @@ const badgeDisplay = (number, limit) => {
}; };
class GettingStarted extends ImmutablePureComponent { class GettingStarted extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
myAccount: ImmutablePropTypes.record, myAccount: ImmutablePropTypes.record,
columns: ImmutablePropTypes.list, columns: ImmutablePropTypes.list,
@ -123,7 +120,7 @@ class GettingStarted extends ImmutablePureComponent {
componentDidMount () { componentDidMount () {
const { fetchFollowRequests } = this.props; const { fetchFollowRequests } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (!signedIn) { if (!signedIn) {
return; return;
@ -134,7 +131,7 @@ class GettingStarted extends ImmutablePureComponent {
render () { render () {
const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications, lists, openSettings } = this.props; const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications, lists, openSettings } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const navItems = []; const navItems = [];
let listItems = []; 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 Column from 'flavours/glitch/features/ui/components/column';
import ColumnLink from 'flavours/glitch/features/ui/components/column_link'; import ColumnLink from 'flavours/glitch/features/ui/components/column_link';
import ColumnSubheading from 'flavours/glitch/features/ui/components/column_subheading'; import ColumnSubheading from 'flavours/glitch/features/ui/components/column_subheading';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.heading', defaultMessage: 'Misc' }, heading: { id: 'column.heading', defaultMessage: 'Misc' },
@ -32,11 +32,8 @@ const messages = defineMessages({
class GettingStartedMisc extends ImmutablePureComponent { class GettingStartedMisc extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
}; };
@ -49,7 +46,7 @@ class GettingStartedMisc extends ImmutablePureComponent {
render () { render () {
const { intl } = this.props; const { intl } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
return ( return (
<Column icon='ellipsis-h' iconComponent={MoreHorizIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton> <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 { expandHashtagTimeline, clearTimeline } from 'flavours/glitch/actions/timelines';
import Column from 'flavours/glitch/components/column'; import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header'; import ColumnHeader from 'flavours/glitch/components/column_header';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import StatusListContainer from '../ui/containers/status_list_container'; import StatusListContainer from '../ui/containers/status_list_container';
@ -29,14 +30,10 @@ const mapStateToProps = (state, props) => ({
}); });
class HashtagTimeline extends PureComponent { class HashtagTimeline extends PureComponent {
disconnects = []; disconnects = [];
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
@ -94,7 +91,7 @@ class HashtagTimeline extends PureComponent {
}; };
_subscribe (dispatch, id, tags = {}, local) { _subscribe (dispatch, id, tags = {}, local) {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (!signedIn) { if (!signedIn) {
return; return;
@ -168,7 +165,7 @@ class HashtagTimeline extends PureComponent {
handleFollow = () => { handleFollow = () => {
const { dispatch, params, tag } = this.props; const { dispatch, params, tag } = this.props;
const { id } = params; const { id } = params;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (!signedIn) { if (!signedIn) {
return; return;
@ -185,7 +182,7 @@ class HashtagTimeline extends PureComponent {
const { hasUnread, columnId, multiColumn, tag } = this.props; const { hasUnread, columnId, multiColumn, tag } = this.props;
const { id, local } = this.props.params; const { id, local } = this.props.params;
const pinned = !!columnId; const pinned = !!columnId;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}> <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 { IconWithBadge } from 'flavours/glitch/components/icon_with_badge';
import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator'; import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator';
import AnnouncementsContainer from 'flavours/glitch/features/getting_started/containers/announcements_container'; 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 { criticalUpdatesPending } from 'flavours/glitch/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -41,12 +42,8 @@ const mapStateToProps = state => ({
}); });
class HomeTimeline extends PureComponent { class HomeTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool, hasUnread: PropTypes.bool,
@ -128,7 +125,7 @@ class HomeTimeline extends PureComponent {
render () { render () {
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props; const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const banners = []; const banners = [];
let announcementsButton; 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 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 { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'flavours/glitch/permissions';
import { CheckboxWithLabel } from './checkbox_with_label'; import { CheckboxWithLabel } from './checkbox_with_label';
@ -13,13 +14,9 @@ import GrantPermissionButton from './grant_permission_button';
import PillBarButton from './pill_bar_button'; import PillBarButton from './pill_bar_button';
import SettingToggle from './setting_toggle'; import SettingToggle from './setting_toggle';
export default class ColumnSettings extends PureComponent { class ColumnSettings extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
settings: ImmutablePropTypes.map.isRequired, settings: ImmutablePropTypes.map.isRequired,
pushSettings: ImmutablePropTypes.map.isRequired, pushSettings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
@ -227,7 +224,7 @@ export default class ColumnSettings extends PureComponent {
</div> </div>
</section> </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'> <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> <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> </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'> <section role='group' aria-labelledby='notifications-admin-report'>
<h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.report' defaultMessage='New reports:' /></h3> <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 { compareId } from 'flavours/glitch/compare_id';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator'; 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 { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { submitMarkers } from '../../actions/markers'; import { submitMarkers } from '../../actions/markers';
@ -93,12 +94,8 @@ const mapDispatchToProps = dispatch => ({
}); });
class Notifications extends PureComponent { class Notifications extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
columnId: PropTypes.string, columnId: PropTypes.string,
notifications: ImmutablePropTypes.list.isRequired, notifications: ImmutablePropTypes.list.isRequired,
showFilterBar: PropTypes.bool.isRequired, showFilterBar: PropTypes.bool.isRequired,
@ -225,7 +222,7 @@ class Notifications extends PureComponent {
const { animatingNCD } = this.state; const { animatingNCD } = this.state;
const pinned = !!columnId; 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 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; 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 { reblog, favourite, unreblog, unfavourite } from 'flavours/glitch/actions/interactions';
import { openModal } from 'flavours/glitch/actions/modal'; import { openModal } from 'flavours/glitch/actions/modal';
import { IconButton } from 'flavours/glitch/components/icon_button'; 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 { me, boostModal } from 'flavours/glitch/initial_state';
import { makeGetStatus } from 'flavours/glitch/selectors'; import { makeGetStatus } from 'flavours/glitch/selectors';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -48,12 +49,8 @@ const makeMapStateToProps = () => {
}; };
class Footer extends ImmutablePureComponent { class Footer extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
statusId: PropTypes.string.isRequired, statusId: PropTypes.string.isRequired,
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -77,7 +74,7 @@ class Footer extends ImmutablePureComponent {
handleReplyClick = () => { handleReplyClick = () => {
const { dispatch, askReplyConfirmation, status, intl } = this.props; const { dispatch, askReplyConfirmation, status, intl } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (askReplyConfirmation) { if (askReplyConfirmation) {
@ -106,7 +103,7 @@ class Footer extends ImmutablePureComponent {
handleFavouriteClick = () => { handleFavouriteClick = () => {
const { dispatch, status } = this.props; const { dispatch, status } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (status.get('favourited')) { if (status.get('favourited')) {
@ -133,7 +130,7 @@ class Footer extends ImmutablePureComponent {
handleReblogClick = e => { handleReblogClick = e => {
const { dispatch, status } = this.props; const { dispatch, status } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (status.get('reblogged')) { 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 PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { domain } from 'flavours/glitch/initial_state'; import { domain } from 'flavours/glitch/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -44,16 +45,12 @@ const mapStateToProps = (state, { columnId }) => {
}; };
class PublicTimeline extends PureComponent { class PublicTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = { static defaultProps = {
onlyMedia: false, onlyMedia: false,
}; };
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
@ -86,7 +83,7 @@ class PublicTimeline extends PureComponent {
componentDidMount () { componentDidMount () {
const { dispatch, onlyMedia, onlyRemote, allowLocalOnly } = this.props; const { dispatch, onlyMedia, onlyRemote, allowLocalOnly } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote, allowLocalOnly })); dispatch(expandPublicTimeline({ onlyMedia, onlyRemote, allowLocalOnly }));
if (signedIn) { if (signedIn) {
@ -95,7 +92,7 @@ class PublicTimeline extends PureComponent {
} }
componentDidUpdate (prevProps) { 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) { if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote || prevProps.allowLocalOnly !== this.props.allowLocalOnly) {
const { dispatch, onlyMedia, onlyRemote, allowLocalOnly } = this.props; 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 RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.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 { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links'; import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -62,12 +63,8 @@ const messages = defineMessages({
}); });
class ActionBar extends PureComponent { class ActionBar extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
onReply: PropTypes.func.isRequired, onReply: PropTypes.func.isRequired,
onReblog: PropTypes.func.isRequired, onReblog: PropTypes.func.isRequired,
@ -165,7 +162,7 @@ class ActionBar extends PureComponent {
render () { render () {
const { status, intl } = this.props; 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 publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const pinnableStatus = ['public', 'unlisted', 'private'].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 { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import ScrollContainer from 'flavours/glitch/containers/scroll_container'; import ScrollContainer from 'flavours/glitch/containers/scroll_container';
import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error'; 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 { autoUnfoldCW } from 'flavours/glitch/utils/content_warning';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -187,12 +188,8 @@ const titleFromStatus = (intl, status) => {
}; };
class Status extends ImmutablePureComponent { class Status extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
status: ImmutablePropTypes.map, status: ImmutablePropTypes.map,
@ -279,7 +276,7 @@ class Status extends ImmutablePureComponent {
handleFavouriteClick = (status, e) => { handleFavouriteClick = (status, e) => {
const { dispatch } = this.props; const { dispatch } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (status.get('favourited')) { if (status.get('favourited')) {
@ -332,7 +329,7 @@ class Status extends ImmutablePureComponent {
handleReplyClick = (status) => { handleReplyClick = (status) => {
const { askReplyConfirmation, dispatch, intl } = this.props; const { askReplyConfirmation, dispatch, intl } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (askReplyConfirmation) { if (askReplyConfirmation) {
@ -372,7 +369,7 @@ class Status extends ImmutablePureComponent {
handleReblogClick = (status, e) => { handleReblogClick = (status, e) => {
const { settings, dispatch } = this.props; const { settings, dispatch } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (settings.get('confirm_boost_missing_media_description') && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) { 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 ServerBanner from 'flavours/glitch/components/server_banner';
import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container'; import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container';
import SearchContainer from 'flavours/glitch/features/compose/containers/search_container'; import SearchContainer from 'flavours/glitch/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import LinkFooter from './link_footer'; import LinkFooter from './link_footer';
class ComposePanel extends PureComponent { class ComposePanel extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
}; };
@ -31,7 +28,7 @@ class ComposePanel extends PureComponent {
} }
render() { render() {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
return ( return (
<div className='compose-panel'> <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 { Icon } from 'flavours/glitch/components/icon';
import { WordmarkLogo, SymbolLogo } from 'flavours/glitch/components/logo'; import { WordmarkLogo, SymbolLogo } from 'flavours/glitch/components/logo';
import { Permalink } from 'flavours/glitch/components/permalink'; 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'; import { registrationsOpen, me, sso_redirect } from 'flavours/glitch/initial_state';
const Account = connect(state => ({ const Account = connect(state => ({
@ -42,12 +43,8 @@ const mapDispatchToProps = (dispatch) => ({
}); });
class Header extends PureComponent { class Header extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
openClosedRegistrationsModal: PropTypes.func, openClosedRegistrationsModal: PropTypes.func,
location: PropTypes.object, location: PropTypes.object,
signupUrl: PropTypes.string.isRequired, signupUrl: PropTypes.string.isRequired,
@ -61,7 +58,7 @@ class Header extends PureComponent {
} }
render () { render () {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props; const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props;
let content; 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 { connect } from 'react-redux';
import { openModal } from 'flavours/glitch/actions/modal'; 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 { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'flavours/glitch/initial_state';
import { PERMISSION_INVITE_USERS } from 'flavours/glitch/permissions'; import { PERMISSION_INVITE_USERS } from 'flavours/glitch/permissions';
import { logOut } from 'flavours/glitch/utils/log_out'; import { logOut } from 'flavours/glitch/utils/log_out';
@ -32,12 +33,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}); });
class LinkFooter extends PureComponent { class LinkFooter extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
onLogout: PropTypes.func.isRequired, onLogout: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -53,7 +50,7 @@ class LinkFooter extends PureComponent {
}; };
render () { render () {
const { signedIn, permissions } = this.context.identity; const { signedIn, permissions } = this.props.identity;
const { multiColumn } = this.props; const { multiColumn } = this.props;
const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS); 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 { fetchFollowRequests } from 'flavours/glitch/actions/accounts';
import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge'; import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge';
import { NavigationPortal } from 'flavours/glitch/components/navigation_portal'; 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 { timelinePreview, trendsEnabled } from 'flavours/glitch/initial_state';
import { transientSingleColumn } from 'flavours/glitch/is_mobile'; import { transientSingleColumn } from 'flavours/glitch/is_mobile';
import { preferencesLink } from 'flavours/glitch/utils/backend_links'; import { preferencesLink } from 'flavours/glitch/utils/backend_links';
@ -98,12 +99,8 @@ const FollowRequestsLink = () => {
}; };
class NavigationPanel extends Component { class NavigationPanel extends Component {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
onOpenSettings: PropTypes.func, onOpenSettings: PropTypes.func,
}; };
@ -114,7 +111,7 @@ class NavigationPanel extends Component {
render () { render () {
const { intl, onOpenSettings } = this.props; const { intl, onOpenSettings } = this.props;
const { signedIn, disabledAccountId } = this.context.identity; const { signedIn, disabledAccountId } = this.props.identity;
let banner = undefined; 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 { INTRODUCTION_VERSION } from 'flavours/glitch/actions/onboarding';
import { Permalink } from 'flavours/glitch/components/permalink'; import { Permalink } from 'flavours/glitch/components/permalink';
import { PictureInPicture } from 'flavours/glitch/features/picture_in_picture'; 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 { layoutFromWindow } from 'flavours/glitch/is_mobile';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
@ -129,12 +130,8 @@ const keyMap = {
}; };
class SwitchingColumnsArea extends PureComponent { class SwitchingColumnsArea extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
children: PropTypes.node, children: PropTypes.node,
location: PropTypes.object, location: PropTypes.object,
singleColumn: PropTypes.bool, singleColumn: PropTypes.bool,
@ -169,7 +166,7 @@ class SwitchingColumnsArea extends PureComponent {
render () { render () {
const { children, singleColumn } = this.props; const { children, singleColumn } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const pathName = this.props.location.pathname; const pathName = this.props.location.pathname;
let redirect; let redirect;
@ -262,12 +259,8 @@ class SwitchingColumnsArea extends PureComponent {
} }
class UI extends PureComponent { class UI extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
children: PropTypes.node, children: PropTypes.node,
isWide: PropTypes.bool, isWide: PropTypes.bool,
@ -323,7 +316,7 @@ class UI extends PureComponent {
this.dragTargets.push(e.target); 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 }); this.setState({ draggingOver: true });
} }
}; };
@ -351,7 +344,7 @@ class UI extends PureComponent {
this.setState({ draggingOver: false }); this.setState({ draggingOver: false });
this.dragTargets = []; 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)); this.props.dispatch(uploadCompose(e.dataTransfer.files));
} }
}; };
@ -403,7 +396,7 @@ class UI extends PureComponent {
}; };
componentDidMount () { componentDidMount () {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
window.addEventListener('beforeunload', this.handleBeforeUnload, false); window.addEventListener('beforeunload', this.handleBeforeUnload, false);
window.addEventListener('resize', this.handleResize, { passive: true }); window.addEventListener('resize', this.handleResize, { passive: true });
@ -649,7 +642,7 @@ class UI extends PureComponent {
<Header /> <Header />
<SwitchingColumnsArea location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}> <SwitchingColumnsArea identity={this.props.identity} location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
{children} {children}
</SwitchingColumnsArea> </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 * @property {string} default_content_type
*/ */
/**
* @typedef Role
* @property {string} id
* @property {string} name
* @property {string} permissions
* @property {string} color
* @property {boolean} highlighted
*/
/** /**
* @typedef PollLimits * @typedef PollLimits
* @property {number} min_options * @property {number} min_options
@ -67,6 +76,7 @@
* @property {InitialStateLanguage[]} languages * @property {InitialStateLanguage[]} languages
* @property {boolean=} critical_updates_pending * @property {boolean=} critical_updates_pending
* @property {InitialStateMeta} meta * @property {InitialStateMeta} meta
* @property {Role?} role
* @property {object} local_settings * @property {object} local_settings
* @property {number} max_feed_hashtags * @property {number} max_feed_hashtags
* @property {PollLimits} poll_limits * @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 SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context'; import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import { useAppHistory } from './router'; import { useAppHistory } from './router';
const messages = defineMessages({ const messages = defineMessages({
@ -51,12 +53,8 @@ BackButton.propTypes = {
}; };
class ColumnHeader extends PureComponent { class ColumnHeader extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
title: PropTypes.node, title: PropTypes.node,
icon: PropTypes.string, 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 = ( collapseButton = (
<button <button
className={collapsibleButtonClassName} 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 { Icon } from 'mastodon/components/icon';
import emojify from 'mastodon/features/emoji/emoji'; import emojify from 'mastodon/features/emoji/emoji';
import Motion from 'mastodon/features/ui/util/optional_motion'; import Motion from 'mastodon/features/ui/util/optional_motion';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { RelativeTimestamp } from './relative_timestamp'; import { RelativeTimestamp } from './relative_timestamp';
@ -38,12 +39,8 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
}, {}); }, {});
class Poll extends ImmutablePureComponent { class Poll extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
poll: ImmutablePropTypes.map, poll: ImmutablePropTypes.map,
lang: PropTypes.string, lang: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -235,7 +232,7 @@ class Poll extends ImmutablePureComponent {
</ul> </ul>
<div className='poll__footer'> <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 && <><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> · </>} {showResults && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>}
{votesCount} {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 RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.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 { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -74,12 +75,8 @@ const mapStateToProps = (state, { status }) => ({
}); });
class StatusActionBar extends ImmutablePureComponent { class StatusActionBar extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.record, relationship: ImmutablePropTypes.record,
onReply: PropTypes.func, onReply: PropTypes.func,
@ -118,7 +115,7 @@ class StatusActionBar extends ImmutablePureComponent {
]; ];
handleReplyClick = () => { handleReplyClick = () => {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
this.props.onReply(this.props.status, this.props.history); this.props.onReply(this.props.status, this.props.history);
@ -136,7 +133,7 @@ class StatusActionBar extends ImmutablePureComponent {
}; };
handleFavouriteClick = () => { handleFavouriteClick = () => {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
this.props.onFavourite(this.props.status); this.props.onFavourite(this.props.status);
@ -146,7 +143,7 @@ class StatusActionBar extends ImmutablePureComponent {
}; };
handleReblogClick = e => { handleReblogClick = e => {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
this.props.onReblog(this.props.status, e); this.props.onReblog(this.props.status, e);
@ -250,7 +247,7 @@ class StatusActionBar extends ImmutablePureComponent {
render () { render () {
const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props; 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 publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const pinnableStatus = ['public', 'unlisted', 'private'].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 ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import PollContainer from 'mastodon/containers/poll_container'; import PollContainer from 'mastodon/containers/poll_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state'; import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state';
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
/** /**
@ -67,12 +69,8 @@ const mapStateToProps = state => ({
}); });
class StatusContent extends PureComponent { class StatusContent extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
statusContent: PropTypes.string, statusContent: PropTypes.string,
expanded: PropTypes.bool, expanded: PropTypes.bool,
@ -245,7 +243,7 @@ class StatusContent extends PureComponent {
const renderReadMore = this.props.onClick && status.get('collapsed'); const renderReadMore = this.props.onClick && status.get('collapsed');
const contentLocale = intl.locale.replace(/[_-].*/, ''); const contentLocale = intl.locale.replace(/[_-].*/, '');
const targetLanguages = this.props.languages?.get(status.get('language') || 'und'); 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 content = { __html: statusContent ?? getStatusContent(status) };
const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') }; 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 { PureComponent } from 'react';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
@ -14,6 +13,7 @@ import { connectUserStream } from 'mastodon/actions/streaming';
import ErrorBoundary from 'mastodon/components/error_boundary'; import ErrorBoundary from 'mastodon/components/error_boundary';
import { Router } from 'mastodon/components/router'; import { Router } from 'mastodon/components/router';
import UI from 'mastodon/features/ui'; import UI from 'mastodon/features/ui';
import { IdentityContext, createIdentityContext } from 'mastodon/identity_context';
import initialState, { title as siteTitle } from 'mastodon/initial_state'; import initialState, { title as siteTitle } from 'mastodon/initial_state';
import { IntlProvider } from 'mastodon/locales'; import { IntlProvider } from 'mastodon/locales';
import { store } from 'mastodon/store'; import { store } from 'mastodon/store';
@ -28,33 +28,9 @@ if (initialState.meta.me) {
store.dispatch(fetchCustomEmojis()); 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 { 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); identity = createIdentityContext(initialState);
getChildContext() {
return {
identity: this.identity,
};
}
componentDidMount() { componentDidMount() {
if (this.identity.signedIn) { if (this.identity.signedIn) {
this.disconnect = store.dispatch(connectUserStream()); this.disconnect = store.dispatch(connectUserStream());
@ -74,19 +50,21 @@ export default class Mastodon extends PureComponent {
render () { render () {
return ( return (
<IntlProvider> <IdentityContext.Provider value={this.identity}>
<ReduxProvider store={store}> <IntlProvider>
<ErrorBoundary> <ReduxProvider store={store}>
<Router> <ErrorBoundary>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}> <Router>
<Route path='/' component={UI} /> <ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
</ScrollContext> <Route path='/' component={UI} />
</Router> </ScrollContext>
</Router>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} /> <Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
</ErrorBoundary> </ErrorBoundary>
</ReduxProvider> </ReduxProvider>
</IntlProvider> </IntlProvider>
</IdentityContext.Provider>
); );
} }

View file

@ -25,6 +25,7 @@ import { IconButton } from 'mastodon/components/icon_button';
import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { ShortNumber } from 'mastodon/components/short_number'; import { ShortNumber } from 'mastodon/components/short_number';
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; 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 { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -111,6 +112,7 @@ const dateFormatOptions = {
class Header extends ImmutablePureComponent { class Header extends ImmutablePureComponent {
static propTypes = { static propTypes = {
identity: identityContextPropShape,
account: ImmutablePropTypes.record, account: ImmutablePropTypes.record,
identity_props: ImmutablePropTypes.list, identity_props: ImmutablePropTypes.list,
onFollow: PropTypes.func.isRequired, onFollow: PropTypes.func.isRequired,
@ -136,10 +138,6 @@ class Header extends ImmutablePureComponent {
...WithRouterPropTypes, ...WithRouterPropTypes,
}; };
static contextTypes = {
identity: PropTypes.object,
};
setRef = c => { setRef = c => {
this.node = c; this.node = c;
}; };
@ -255,7 +253,7 @@ class Header extends ImmutablePureComponent {
render () { render () {
const { account, hidden, intl } = this.props; const { account, hidden, intl } = this.props;
const { signedIn, permissions } = this.context.identity; const { signedIn, permissions } = this.props.identity;
if (!account) { if (!account) {
return null; 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 PeopleIcon from '@/material-icons/400-24px/group.svg?react';
import { DismissableBanner } from 'mastodon/components/dismissable_banner'; import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain } from 'mastodon/initial_state'; import { domain } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -38,16 +39,12 @@ const mapStateToProps = (state, { columnId }) => {
}; };
class CommunityTimeline extends PureComponent { class CommunityTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = { static defaultProps = {
onlyMedia: false, onlyMedia: false,
}; };
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -77,7 +74,7 @@ class CommunityTimeline extends PureComponent {
componentDidMount () { componentDidMount () {
const { dispatch, onlyMedia } = this.props; const { dispatch, onlyMedia } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
dispatch(expandCommunityTimeline({ onlyMedia })); dispatch(expandCommunityTimeline({ onlyMedia }));
@ -87,7 +84,7 @@ class CommunityTimeline extends PureComponent {
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia) { if (prevProps.onlyMedia !== this.props.onlyMedia) {
const { dispatch, onlyMedia } = this.props; 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 CloseIcon from '@/material-icons/400-24px/close.svg?react';
import SearchIcon from '@/material-icons/400-24px/search.svg?react'; import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain, searchEnabled } from 'mastodon/initial_state'; import { domain, searchEnabled } from 'mastodon/initial_state';
import { HASHTAG_REGEX } from 'mastodon/utils/hashtags'; import { HASHTAG_REGEX } from 'mastodon/utils/hashtags';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -33,12 +34,8 @@ const labelForRecentSearch = search => {
}; };
class Search extends PureComponent { class Search extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
value: PropTypes.string.isRequired, value: PropTypes.string.isRequired,
recent: ImmutablePropTypes.orderedSet, recent: ImmutablePropTypes.orderedSet,
submitted: PropTypes.bool, submitted: PropTypes.bool,
@ -276,7 +273,7 @@ class Search extends PureComponent {
} }
_calculateOptions (value) { _calculateOptions (value) {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const trimmedValue = value.trim(); const trimmedValue = value.trim();
const options = []; const options = [];
@ -318,7 +315,7 @@ class Search extends PureComponent {
render () { render () {
const { intl, value, submitted, recent } = this.props; const { intl, value, submitted, recent } = this.props;
const { expanded, options, selectedOption } = this.state; const { expanded, options, selectedOption } = this.state;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const hasValue = value.length > 0 || submitted; 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 Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header'; import ColumnHeader from 'mastodon/components/column_header';
import Search from 'mastodon/features/compose/containers/search_container'; import Search from 'mastodon/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { trendsEnabled } from 'mastodon/initial_state'; import { trendsEnabled } from 'mastodon/initial_state';
import Links from './links'; import Links from './links';
@ -32,12 +33,8 @@ const mapStateToProps = state => ({
}); });
class Explore extends PureComponent { class Explore extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
isSearching: PropTypes.bool, isSearching: PropTypes.bool,
@ -53,7 +50,7 @@ class Explore extends PureComponent {
render() { render() {
const { intl, multiColumn, isSearching } = this.props; const { intl, multiColumn, isSearching } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <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 { Helmet } from 'react-helmet';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { useIdentity } from '@/mastodon/identity_context';
import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { addColumn } from 'mastodon/actions/columns'; import { addColumn } from 'mastodon/actions/columns';
import { changeSetting } from 'mastodon/actions/settings'; import { changeSetting } from 'mastodon/actions/settings';
import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming'; import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming';
import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines'; import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
import { DismissableBanner } from 'mastodon/components/dismissable_banner'; 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 { useAppDispatch, useAppSelector } from 'mastodon/store';
import Column from '../../components/column'; import Column from '../../components/column';
@ -24,15 +25,6 @@ const messages = defineMessages({
title: { id: 'column.firehose', defaultMessage: 'Live feeds' }, 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 ColumnSettings = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const settings = useAppSelector((state) => state.getIn(['settings', 'firehose'])); 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 Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header'; import ColumnHeader from 'mastodon/components/column_header';
import LinkFooter from 'mastodon/features/ui/components/link_footer'; import LinkFooter from 'mastodon/features/ui/components/link_footer';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { me, showTrends } from '../../initial_state'; import { me, showTrends } from '../../initial_state';
import { NavigationBar } from '../compose/components/navigation_bar'; import { NavigationBar } from '../compose/components/navigation_bar';
@ -75,12 +76,8 @@ const badgeDisplay = (number, limit) => {
}; };
class GettingStarted extends ImmutablePureComponent { class GettingStarted extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
myAccount: ImmutablePropTypes.record, myAccount: ImmutablePropTypes.record,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
@ -91,7 +88,7 @@ class GettingStarted extends ImmutablePureComponent {
componentDidMount () { componentDidMount () {
const { fetchFollowRequests } = this.props; const { fetchFollowRequests } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (!signedIn) { if (!signedIn) {
return; return;
@ -102,7 +99,7 @@ class GettingStarted extends ImmutablePureComponent {
render () { render () {
const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props; const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const navItems = []; 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 { expandHashtagTimeline, clearTimeline } from 'mastodon/actions/timelines';
import Column from 'mastodon/components/column'; import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header'; import ColumnHeader from 'mastodon/components/column_header';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import StatusListContainer from '../ui/containers/status_list_container'; import StatusListContainer from '../ui/containers/status_list_container';
@ -29,14 +30,10 @@ const mapStateToProps = (state, props) => ({
}); });
class HashtagTimeline extends PureComponent { class HashtagTimeline extends PureComponent {
disconnects = []; disconnects = [];
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
@ -94,7 +91,7 @@ class HashtagTimeline extends PureComponent {
}; };
_subscribe (dispatch, id, tags = {}, local) { _subscribe (dispatch, id, tags = {}, local) {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (!signedIn) { if (!signedIn) {
return; return;
@ -168,7 +165,7 @@ class HashtagTimeline extends PureComponent {
handleFollow = () => { handleFollow = () => {
const { dispatch, params, tag } = this.props; const { dispatch, params, tag } = this.props;
const { id } = params; const { id } = params;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (!signedIn) { if (!signedIn) {
return; return;
@ -185,7 +182,7 @@ class HashtagTimeline extends PureComponent {
const { hasUnread, columnId, multiColumn, tag } = this.props; const { hasUnread, columnId, multiColumn, tag } = this.props;
const { id, local } = this.props.params; const { id, local } = this.props.params;
const pinned = !!columnId; const pinned = !!columnId;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}> <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 { IconWithBadge } from 'mastodon/components/icon_with_badge';
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator'; import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container'; import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { criticalUpdatesPending } from 'mastodon/initial_state'; import { criticalUpdatesPending } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -40,12 +41,8 @@ const mapStateToProps = state => ({
}); });
class HomeTimeline extends PureComponent { class HomeTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool, hasUnread: PropTypes.bool,
@ -126,7 +123,7 @@ class HomeTimeline extends PureComponent {
render () { render () {
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props; const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const banners = []; const banners = [];
let announcementsButton; 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 ImmutablePropTypes from 'react-immutable-proptypes';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'mastodon/permissions'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'mastodon/permissions';
import { CheckboxWithLabel } from './checkbox_with_label'; import { CheckboxWithLabel } from './checkbox_with_label';
@ -12,13 +13,9 @@ import ClearColumnButton from './clear_column_button';
import GrantPermissionButton from './grant_permission_button'; import GrantPermissionButton from './grant_permission_button';
import SettingToggle from './setting_toggle'; import SettingToggle from './setting_toggle';
export default class ColumnSettings extends PureComponent { class ColumnSettings extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
settings: ImmutablePropTypes.map.isRequired, settings: ImmutablePropTypes.map.isRequired,
pushSettings: ImmutablePropTypes.map.isRequired, pushSettings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
@ -215,7 +212,7 @@ export default class ColumnSettings extends PureComponent {
</div> </div>
</section> </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'> <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> <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> </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'> <section role='group' aria-labelledby='notifications-admin-report'>
<h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.report' defaultMessage='New reports:' /></h3> <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 { compareId } from 'mastodon/compare_id';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator'; import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { submitMarkers } from '../../actions/markers'; import { submitMarkers } from '../../actions/markers';
@ -77,12 +78,8 @@ const mapStateToProps = state => ({
}); });
class Notifications extends PureComponent { class Notifications extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
columnId: PropTypes.string, columnId: PropTypes.string,
notifications: ImmutablePropTypes.list.isRequired, notifications: ImmutablePropTypes.list.isRequired,
dispatch: PropTypes.func.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 { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props;
const pinned = !!columnId; 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 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; 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 { reblog, favourite, unreblog, unfavourite } from 'mastodon/actions/interactions';
import { openModal } from 'mastodon/actions/modal'; import { openModal } from 'mastodon/actions/modal';
import { IconButton } from 'mastodon/components/icon_button'; import { IconButton } from 'mastodon/components/icon_button';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { me, boostModal } from 'mastodon/initial_state'; import { me, boostModal } from 'mastodon/initial_state';
import { makeGetStatus } from 'mastodon/selectors'; import { makeGetStatus } from 'mastodon/selectors';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -47,12 +48,8 @@ const makeMapStateToProps = () => {
}; };
class Footer extends ImmutablePureComponent { class Footer extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
statusId: PropTypes.string.isRequired, statusId: PropTypes.string.isRequired,
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -75,7 +72,7 @@ class Footer extends ImmutablePureComponent {
handleReplyClick = () => { handleReplyClick = () => {
const { dispatch, askReplyConfirmation, status, intl } = this.props; const { dispatch, askReplyConfirmation, status, intl } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (askReplyConfirmation) { if (askReplyConfirmation) {
@ -104,7 +101,7 @@ class Footer extends ImmutablePureComponent {
handleFavouriteClick = () => { handleFavouriteClick = () => {
const { dispatch, status } = this.props; const { dispatch, status } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (status.get('favourited')) { if (status.get('favourited')) {
@ -131,7 +128,7 @@ class Footer extends ImmutablePureComponent {
handleReblogClick = e => { handleReblogClick = e => {
const { dispatch, status } = this.props; const { dispatch, status } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (status.get('reblogged')) { 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 PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { DismissableBanner } from 'mastodon/components/dismissable_banner'; import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain } from 'mastodon/initial_state'; import { domain } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -40,16 +41,12 @@ const mapStateToProps = (state, { columnId }) => {
}; };
class PublicTimeline extends PureComponent { class PublicTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = { static defaultProps = {
onlyMedia: false, onlyMedia: false,
}; };
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
@ -80,7 +77,7 @@ class PublicTimeline extends PureComponent {
componentDidMount () { componentDidMount () {
const { dispatch, onlyMedia, onlyRemote } = this.props; const { dispatch, onlyMedia, onlyRemote } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote })); dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
@ -90,7 +87,7 @@ class PublicTimeline extends PureComponent {
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) { if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) {
const { dispatch, onlyMedia, onlyRemote } = this.props; 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 RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.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 { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -67,12 +68,8 @@ const mapStateToProps = (state, { status }) => ({
}); });
class ActionBar extends PureComponent { class ActionBar extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.record, relationship: ImmutablePropTypes.record,
onReply: PropTypes.func.isRequired, onReply: PropTypes.func.isRequired,
@ -198,7 +195,7 @@ class ActionBar extends PureComponent {
render () { render () {
const { status, relationship, intl } = this.props; 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 publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const pinnableStatus = ['public', 'unlisted', 'private'].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 { LoadingIndicator } from 'mastodon/components/loading_indicator';
import ScrollContainer from 'mastodon/containers/scroll_container'; import ScrollContainer from 'mastodon/containers/scroll_container';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error'; 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 { WithRouterPropTypes } from 'mastodon/utils/react_router';
import { import {
@ -189,12 +190,8 @@ const titleFromStatus = (intl, status) => {
}; };
class Status extends ImmutablePureComponent { class Status extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
status: ImmutablePropTypes.map, status: ImmutablePropTypes.map,
@ -244,7 +241,7 @@ class Status extends ImmutablePureComponent {
handleFavouriteClick = (status) => { handleFavouriteClick = (status) => {
const { dispatch } = this.props; const { dispatch } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (status.get('favourited')) { if (status.get('favourited')) {
@ -274,7 +271,7 @@ class Status extends ImmutablePureComponent {
handleReplyClick = (status) => { handleReplyClick = (status) => {
const { askReplyConfirmation, dispatch, intl } = this.props; const { askReplyConfirmation, dispatch, intl } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (askReplyConfirmation) { if (askReplyConfirmation) {
@ -307,7 +304,7 @@ class Status extends ImmutablePureComponent {
handleReblogClick = (status, e) => { handleReblogClick = (status, e) => {
const { dispatch } = this.props; const { dispatch } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
if (signedIn) { if (signedIn) {
if (status.get('reblogged')) { 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 ServerBanner from 'mastodon/components/server_banner';
import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container'; import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
import SearchContainer from 'mastodon/features/compose/containers/search_container'; import SearchContainer from 'mastodon/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import LinkFooter from './link_footer'; import LinkFooter from './link_footer';
class ComposePanel extends PureComponent { class ComposePanel extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
}; };
@ -41,7 +38,7 @@ class ComposePanel extends PureComponent {
} }
render() { render() {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
return ( return (
<div className='compose-panel' onFocus={this.onFocus}> <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 { Avatar } from 'mastodon/components/avatar';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { WordmarkLogo, SymbolLogo } from 'mastodon/components/logo'; import { WordmarkLogo, SymbolLogo } from 'mastodon/components/logo';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { registrationsOpen, me, sso_redirect } from 'mastodon/initial_state'; import { registrationsOpen, me, sso_redirect } from 'mastodon/initial_state';
const Account = connect(state => ({ const Account = connect(state => ({
@ -41,12 +42,8 @@ const mapDispatchToProps = (dispatch) => ({
}); });
class Header extends PureComponent { class Header extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
openClosedRegistrationsModal: PropTypes.func, openClosedRegistrationsModal: PropTypes.func,
location: PropTypes.object, location: PropTypes.object,
signupUrl: PropTypes.string.isRequired, signupUrl: PropTypes.string.isRequired,
@ -60,7 +57,7 @@ class Header extends PureComponent {
} }
render () { render () {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props; const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props;
let content; 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 { connect } from 'react-redux';
import { openModal } from 'mastodon/actions/modal'; 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 { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'mastodon/initial_state';
import { PERMISSION_INVITE_USERS } from 'mastodon/permissions'; import { PERMISSION_INVITE_USERS } from 'mastodon/permissions';
import { logOut } from 'mastodon/utils/log_out'; import { logOut } from 'mastodon/utils/log_out';
@ -32,12 +33,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}); });
class LinkFooter extends PureComponent { class LinkFooter extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
onLogout: PropTypes.func.isRequired, onLogout: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -53,7 +50,7 @@ class LinkFooter extends PureComponent {
}; };
render () { render () {
const { signedIn, permissions } = this.context.identity; const { signedIn, permissions } = this.props.identity;
const { multiColumn } = this.props; const { multiColumn } = this.props;
const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS); 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 { IconWithBadge } from 'mastodon/components/icon_with_badge';
import { WordmarkLogo } from 'mastodon/components/logo'; import { WordmarkLogo } from 'mastodon/components/logo';
import { NavigationPortal } from 'mastodon/components/navigation_portal'; import { NavigationPortal } from 'mastodon/components/navigation_portal';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { timelinePreview, trendsEnabled } from 'mastodon/initial_state'; import { timelinePreview, trendsEnabled } from 'mastodon/initial_state';
import { transientSingleColumn } from 'mastodon/is_mobile'; import { transientSingleColumn } from 'mastodon/is_mobile';
@ -97,12 +98,8 @@ const FollowRequestsLink = () => {
}; };
class NavigationPanel extends Component { class NavigationPanel extends Component {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
@ -112,7 +109,7 @@ class NavigationPanel extends Component {
render () { render () {
const { intl } = this.props; const { intl } = this.props;
const { signedIn, disabledAccountId } = this.context.identity; const { signedIn, disabledAccountId } = this.props.identity;
let banner = undefined; 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 { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding'; import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
import { PictureInPicture } from 'mastodon/features/picture_in_picture'; import { PictureInPicture } from 'mastodon/features/picture_in_picture';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { layoutFromWindow } from 'mastodon/is_mobile'; import { layoutFromWindow } from 'mastodon/is_mobile';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -120,12 +121,8 @@ const keyMap = {
}; };
class SwitchingColumnsArea extends PureComponent { class SwitchingColumnsArea extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
children: PropTypes.node, children: PropTypes.node,
location: PropTypes.object, location: PropTypes.object,
singleColumn: PropTypes.bool, singleColumn: PropTypes.bool,
@ -160,7 +157,7 @@ class SwitchingColumnsArea extends PureComponent {
render () { render () {
const { children, singleColumn } = this.props; const { children, singleColumn } = this.props;
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
const pathName = this.props.location.pathname; const pathName = this.props.location.pathname;
let redirect; let redirect;
@ -252,12 +249,8 @@ class SwitchingColumnsArea extends PureComponent {
} }
class UI extends PureComponent { class UI extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
children: PropTypes.node, children: PropTypes.node,
isComposing: PropTypes.bool, isComposing: PropTypes.bool,
@ -309,7 +302,7 @@ class UI extends PureComponent {
this.dragTargets.push(e.target); 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 }); this.setState({ draggingOver: true });
} }
}; };
@ -337,7 +330,7 @@ class UI extends PureComponent {
this.setState({ draggingOver: false }); this.setState({ draggingOver: false });
this.dragTargets = []; 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)); this.props.dispatch(uploadCompose(e.dataTransfer.files));
} }
}; };
@ -389,7 +382,7 @@ class UI extends PureComponent {
}; };
componentDidMount () { componentDidMount () {
const { signedIn } = this.context.identity; const { signedIn } = this.props.identity;
window.addEventListener('focus', this.handleWindowFocus, false); window.addEventListener('focus', this.handleWindowFocus, false);
window.addEventListener('blur', this.handleWindowBlur, 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}> <div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef}>
<Header /> <Header />
<SwitchingColumnsArea location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}> <SwitchingColumnsArea identity={this.props.identity} location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
{children} {children}
</SwitchingColumnsArea> </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 * @property {string} sso_redirect
*/ */
/**
* @typedef Role
* @property {string} id
* @property {string} name
* @property {string} permissions
* @property {string} color
* @property {boolean} highlighted
*/
/** /**
* @typedef PollLimits * @typedef PollLimits
* @property {number} min_options * @property {number} min_options
@ -59,6 +68,7 @@
* @property {InitialStateLanguage[]} languages * @property {InitialStateLanguage[]} languages
* @property {boolean=} critical_updates_pending * @property {boolean=} critical_updates_pending
* @property {InitialStateMeta} meta * @property {InitialStateMeta} meta
* @property {Role?} role
* @property {PollLimits} poll_limits * @property {PollLimits} poll_limits
*/ */

View file

@ -19,7 +19,7 @@
"account.block_domain": "Blocar dominio {domain}", "account.block_domain": "Blocar dominio {domain}",
"account.block_short": "Blocar", "account.block_short": "Blocar",
"account.blocked": "Blocate", "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.cancel_follow_request": "Cancellar sequimento",
"account.copy": "Copiar ligamine a profilo", "account.copy": "Copiar ligamine a profilo",
"account.direct": "Mentionar privatemente @{name}", "account.direct": "Mentionar privatemente @{name}",
@ -122,7 +122,7 @@
"column.direct": "Mentiones private", "column.direct": "Mentiones private",
"column.directory": "Navigar profilos", "column.directory": "Navigar profilos",
"column.domain_blocks": "Dominios blocate", "column.domain_blocks": "Dominios blocate",
"column.favourites": "Favoritos", "column.favourites": "Favorites",
"column.firehose": "Fluxos in directo", "column.firehose": "Fluxos in directo",
"column.follow_requests": "Requestas de sequimento", "column.follow_requests": "Requestas de sequimento",
"column.home": "Initio", "column.home": "Initio",
@ -204,7 +204,7 @@
"disabled_account_banner.account_settings": "Parametros de conto", "disabled_account_banner.account_settings": "Parametros de conto",
"disabled_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate.", "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.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_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_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.", "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": "Blocar le servitor",
"domain_block_modal.block_account_instead": "Blocar @{name} in su loco", "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_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_cant_follow": "Necuno de iste servitor pote sequer te.",
"domain_block_modal.they_wont_know": "Illes non sapera que illes ha essite blocate.", "domain_block_modal.they_wont_know": "Ille non sapera que ille ha essite blocate.",
"domain_block_modal.title": "Blocar dominio?", "domain_block_modal.title": "Blocar dominio?",
"domain_block_modal.you_will_lose_followers": "Omne sequitores ab iste servitor essera removite.", "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.", "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_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_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.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.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.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}.", "follow_suggestions.hints.featured": "Iste profilo ha essite seligite manualmente per le equipa de {domain}.",
@ -412,7 +412,7 @@
"lightbox.next": "Sequente", "lightbox.next": "Sequente",
"lightbox.previous": "Precedente", "lightbox.previous": "Precedente",
"limited_account_hint.action": "Monstrar profilo in omne caso", "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}", "link_preview.author": "Per {name}",
"lists.account.add": "Adder al lista", "lists.account.add": "Adder al lista",
"lists.account.remove": "Remover del lista", "lists.account.remove": "Remover del lista",
@ -432,12 +432,12 @@
"loading_indicator.label": "Cargante…", "loading_indicator.label": "Cargante…",
"media_gallery.toggle_visible": "{number, plural, one {Celar imagine} other {Celar imagines}}", "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}.", "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.hide_options": "Celar optiones",
"mute_modal.indefinite": "Usque io dissilentia iste persona", "mute_modal.indefinite": "Usque io dissilentia iste persona",
"mute_modal.show_options": "Monstrar optiones", "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_can_mention_and_follow": "Ille pote mentionar te e sequer te, ma tu non potera vider le.",
"mute_modal.they_wont_know": "Illes non sapera que illes ha essite silentiate.", "mute_modal.they_wont_know": "Ille non sapera que ille ha essite silentiate.",
"mute_modal.title": "Silentiar le usator?", "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_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.", "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.discover": "Discoperir",
"navigation_bar.domain_blocks": "Dominios blocate", "navigation_bar.domain_blocks": "Dominios blocate",
"navigation_bar.explore": "Explorar", "navigation_bar.explore": "Explorar",
"navigation_bar.favourites": "Favoritos", "navigation_bar.favourites": "Favorites",
"navigation_bar.filters": "Parolas silentiate", "navigation_bar.filters": "Parolas silentiate",
"navigation_bar.follow_requests": "Requestas de sequimento", "navigation_bar.follow_requests": "Requestas de sequimento",
"navigation_bar.followed_tags": "Hashtags sequite", "navigation_bar.followed_tags": "Hashtags sequite",
"navigation_bar.follows_and_followers": "Sequites e sequitores", "navigation_bar.follows_and_followers": "Sequites e sequitores",
"navigation_bar.lists": "Listas", "navigation_bar.lists": "Listas",
"navigation_bar.logout": "Clauder le session", "navigation_bar.logout": "Clauder session",
"navigation_bar.mutes": "Usatores silentiate", "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.opened_in_classic_interface": "Messages, contos e altere paginas specific es aperite per predefinition in le interfacie web classic.",
"navigation_bar.personal": "Personal", "navigation_bar.personal": "Personal",
@ -501,7 +501,7 @@
"notifications.column_settings.admin.report": "Nove signalationes:", "notifications.column_settings.admin.report": "Nove signalationes:",
"notifications.column_settings.admin.sign_up": "Nove inscriptiones:", "notifications.column_settings.admin.sign_up": "Nove inscriptiones:",
"notifications.column_settings.alert": "Notificationes de scriptorio", "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.advanced": "Monstrar tote le categorias",
"notifications.column_settings.filter_bar.category": "Barra de filtro rapide", "notifications.column_settings.filter_bar.category": "Barra de filtro rapide",
"notifications.column_settings.follow": "Nove sequitores:", "notifications.column_settings.follow": "Nove sequitores:",
@ -518,7 +518,7 @@
"notifications.column_settings.update": "Modificationes:", "notifications.column_settings.update": "Modificationes:",
"notifications.filter.all": "Toto", "notifications.filter.all": "Toto",
"notifications.filter.boosts": "Impulsos", "notifications.filter.boosts": "Impulsos",
"notifications.filter.favourites": "Favoritos", "notifications.filter.favourites": "Favorites",
"notifications.filter.follows": "Sequites", "notifications.filter.follows": "Sequites",
"notifications.filter.mentions": "Mentiones", "notifications.filter.mentions": "Mentiones",
"notifications.filter.polls": "Resultatos del sondage", "notifications.filter.polls": "Resultatos del sondage",
@ -717,7 +717,7 @@
"status.edited": "Ultime modification le {date}", "status.edited": "Ultime modification le {date}",
"status.edited_x_times": "Modificate {count, plural, one {{count} vice} other {{count} vices}}", "status.edited_x_times": "Modificate {count, plural, one {{count} vice} other {{count} vices}}",
"status.embed": "Incastrar", "status.embed": "Incastrar",
"status.favourite": "Adder al favoritos", "status.favourite": "Adder al favorites",
"status.favourites": "{count, plural, one {favorite} other {favorites}}", "status.favourites": "{count, plural, one {favorite} other {favorites}}",
"status.filter": "Filtrar iste message", "status.filter": "Filtrar iste message",
"status.filtered": "Filtrate", "status.filtered": "Filtrate",

View file

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

View file

@ -474,6 +474,7 @@
"notification.follow_request": "{name} vam želi slediti", "notification.follow_request": "{name} vam želi slediti",
"notification.mention": "{name} vas je omenil/a", "notification.mention": "{name} vas je omenil/a",
"notification.moderation-warning.learn_more": "Več o tem", "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_delete_statuses": "Nekatere vaše objave so odstranjene.",
"notification.moderation_warning.action_disable": "Vaš račun je bil onemogočen.", "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.", "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.option_placeholder": "ตัวเลือก {number}",
"compose_form.poll.single": "เลือกอย่างใดอย่างหนึ่ง", "compose_form.poll.single": "เลือกอย่างใดอย่างหนึ่ง",
"compose_form.poll.switch_to_multiple": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตหลายตัวเลือก", "compose_form.poll.switch_to_multiple": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตหลายตัวเลือก",
"compose_form.poll.switch_to_single": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตตัวเลือกเดียว", "compose_form.poll.switch_to_single": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตตัวเลือกเดียว",
"compose_form.poll.type": "ลักษณะ", "compose_form.poll.type": "ลักษณะ",
"compose_form.publish": "โพสต์", "compose_form.publish": "โพสต์",
"compose_form.publish_form": "โพสต์ใหม่", "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 { IntlProvider } from 'react-intl';
import { MemoryRouter } from 'react-router'; import { MemoryRouter } from 'react-router';
@ -9,44 +5,27 @@ import { MemoryRouter } from 'react-router';
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
import { render as rtlRender } from '@testing-library/react'; import { render as rtlRender } from '@testing-library/react';
class FakeIdentityWrapper extends Component< import { IdentityContext } from './identity_context';
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;
}
}
function render( function render(
ui: React.ReactElement, ui: React.ReactElement,
{ locale = 'en', signedIn = true, ...renderOptions } = {}, { 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 }) => { const Wrapper = (props: { children: React.ReactNode }) => {
return ( return (
<MemoryRouter> <MemoryRouter>
<IntlProvider locale={locale}> <IntlProvider locale={locale}>
<FakeIdentityWrapper signedIn={signedIn}> <IdentityContext.Provider value={fakeIdentity}>
{props.children} {props.children}
</FakeIdentityWrapper> </IdentityContext.Provider>
</IntlProvider> </IntlProvider>
</MemoryRouter> </MemoryRouter>
); );

View file

@ -23,6 +23,12 @@ module ApplicationExtension
redirect_uri.lines.first.strip redirect_uri.lines.first.strip
end 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 def push_to_streaming_api
# TODO: #28793 Combine into a single topic # TODO: #28793 Combine into a single topic
payload = Oj.dump(event: :kill) payload = Oj.dump(event: :kill)

View file

@ -9,10 +9,10 @@ class Vacuum::ImportsVacuum
private private
def clean_unconfirmed_imports! 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 end
def clean_old_imports! 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
end end

View file

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

View file

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

View file

@ -59,6 +59,7 @@ class Admin::ActionLogFilter
unsuspend_account: { target_type: 'Account', action: 'unsuspend' }.freeze, unsuspend_account: { target_type: 'Account', action: 'unsuspend' }.freeze,
update_announcement: { target_type: 'Announcement', action: 'update' }.freeze, update_announcement: { target_type: 'Announcement', action: 'update' }.freeze,
update_custom_emoji: { target_type: 'CustomEmoji', 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_status: { target_type: 'Status', action: 'update' }.freeze,
update_user_role: { target_type: 'UserRole', action: 'update' }.freeze, update_user_role: { target_type: 'UserRole', action: 'update' }.freeze,
update_ip_block: { target_type: 'IpBlock', action: 'update' }.freeze, update_ip_block: { target_type: 'IpBlock', action: 'update' }.freeze,

View file

@ -4,7 +4,7 @@ module Expireable
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do 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 def expires_in
return @expires_in if defined?(@expires_in) return @expires_in if defined?(@expires_in)

View file

@ -24,7 +24,7 @@ class Invite < ApplicationRecord
belongs_to :user, inverse_of: :invites belongs_to :user, inverse_of: :invites
has_many :users, inverse_of: :invite, dependent: nil 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 } validates :comment, length: { maximum: COMMENT_SIZE_LIMIT }

View file

@ -4,6 +4,6 @@ class BackupPolicy < ApplicationPolicy
MIN_AGE = 6.days MIN_AGE = 6.days
def create? 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
end end

View file

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

View file

@ -16,11 +16,11 @@ class Scheduler::IpCleanupScheduler
private private
def clean_ip_columns! def clean_ip_columns!
SessionActivation.where('updated_at < ?', SESSION_RETENTION_PERIOD.ago).in_batches.destroy_all 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) 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) 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 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) Doorkeeper::AccessToken.where(last_used_at: ...IP_RETENTION_PERIOD.ago).in_batches.update_all(last_used_ip: nil)
end end
def clean_expired_ip_blocks! def clean_expired_ip_blocks!

View file

@ -20,7 +20,7 @@ class Scheduler::ScheduledStatusesScheduler
end end
def due_statuses 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 end
def publish_scheduled_announcements! def publish_scheduled_announcements!

View file

@ -25,7 +25,7 @@ class Scheduler::UserCleanupScheduler
end end
def clean_discarded_statuses! 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| RemovalWorker.push_bulk(statuses) do |status|
[status.id, { 'immediate' => true, 'skip_streaming' => true }] [status.id, { 'immediate' => true, 'skip_streaming' => true }]
end end

View file

@ -5,7 +5,7 @@
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY
).each do |key| ).each do |key|
ENV.fetch(key) do value = ENV.fetch(key) do
abort <<~MESSAGE abort <<~MESSAGE
Catstodon now requires that these variables are set: Catstodon now requires that these variables are set:
@ -14,9 +14,18 @@
- ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT - ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT
- ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY - 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 MESSAGE
end 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 end
Rails.application.configure do 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 use_rack_events: false, # instead of events, use middleware; allows for untraced_endpoints to ignore child spans
untraced_endpoints: ['/health'], 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') prefix = ENV.fetch('OTEL_SERVICE_NAME_PREFIX', 'mastodon')

View file

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

View file

@ -285,6 +285,7 @@ ca:
update_custom_emoji_html: "%{name} ha actualitzat l'emoji %{target}" 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_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_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_status_html: "%{name} ha actualitzat l'estat de %{target}"
update_user_role_html: "%{name} ha canviat el rol %{target}" update_user_role_html: "%{name} ha canviat el rol %{target}"
deleted_account: compte eliminat deleted_account: compte eliminat

View file

@ -285,6 +285,7 @@ da:
update_custom_emoji_html: "%{name} opdaterede emoji %{target}" update_custom_emoji_html: "%{name} opdaterede emoji %{target}"
update_domain_block_html: "%{name} opdaterede domæneblokeringen for %{target}" update_domain_block_html: "%{name} opdaterede domæneblokeringen for %{target}"
update_ip_block_html: "%{name} ændrede reglen for IP'en %{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_status_html: "%{name} opdaterede indlægget fra %{target}"
update_user_role_html: "%{name} ændrede %{target}-rolle" update_user_role_html: "%{name} ændrede %{target}-rolle"
deleted_account: slettet konto deleted_account: slettet konto

View file

@ -285,6 +285,7 @@ de:
update_custom_emoji_html: "%{name} bearbeitete das Emoji %{target}" update_custom_emoji_html: "%{name} bearbeitete das Emoji %{target}"
update_domain_block_html: "%{name} aktualisierte die Domain-Sperre für %{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_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_status_html: "%{name} überarbeitete einen Beitrag von %{target}"
update_user_role_html: "%{name} änderte die Rolle von %{target}" update_user_role_html: "%{name} änderte die Rolle von %{target}"
deleted_account: gelöschtes Konto 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_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. 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: failure:
already_authenticated: Tu jam initiava le session. already_authenticated: Tu ha jam aperite session.
inactive: Tu conto ancora non es activate. inactive: Tu conto non es ancora activate.
invalid: "%{authentication_keys} o contrasigno non valide." invalid: "%{authentication_keys} o contrasigno non valide."
last_attempt: Tu ha solmente un altere tentativa ante que tu conto es serrate. 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." not_found_in_database: "%{authentication_keys} o contrasigno non valide."
omniauth_user_creation_failure: Error creante un conto pro iste identitate. omniauth_user_creation_failure: Error creante un conto pro iste identitate.
pending: Tu conto es ancora sub revision. 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. explanation: Ora es possibile aperir session con solmente le adresse de e-mail e contrasigno.
subject: 'Mastodon: Authentication bifactorial disactivate' subject: 'Mastodon: Authentication bifactorial disactivate'
subtitle: Le authentication bifactorial ha essite disactivate pro tu conto. subtitle: Le authentication bifactorial ha essite disactivate pro tu conto.
title: 2FA disactivate title: A2F disactivate
two_factor_enabled: two_factor_enabled:
explanation: Pro le apertura de session essera necessari un token generate per le application TOTP accopulate. explanation: Pro le apertura de session essera necessari un token generate per le application TOTP accopulate.
subject: 'Mastodon: Authentication bifactorial activate' subject: 'Mastodon: Authentication bifactorial activate'
subtitle: Le authentication bifactorial ha essite activate pro tu conto. subtitle: Le authentication bifactorial ha essite activate pro tu conto.
title: 2FA activate title: A2F activate
two_factor_recovery_codes_changed: two_factor_recovery_codes_changed:
explanation: Le ancian codices de recuperation ha essite invalidate e nove codices ha essite generate. explanation: Le ancian codices de recuperation ha essite invalidate e nove codices ha essite generate.
subject: 'Mastodon: Codices de recuperation regenerate' subject: 'Mastodon: Codices de recuperation regenerate'
@ -66,11 +66,11 @@ ia:
subject: 'Mastodon: Instructiones pro disblocar' subject: 'Mastodon: Instructiones pro disblocar'
webauthn_credential: webauthn_credential:
added: 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' subject: 'Mastodon: Nove clave de securitate'
title: Un nove clave de securitate esseva addite title: Un nove clave de securitate ha essite addite
deleted: 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' subject: 'Mastodon: Clave de securitate delite'
title: Un de tu claves de securitate ha essite delite title: Un de tu claves de securitate ha essite delite
webauthn_disabled: webauthn_disabled:
@ -81,18 +81,41 @@ ia:
webauthn_enabled: webauthn_enabled:
explanation: Le authentication con claves de securitate ha essite activate pro tu conto. 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. 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 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: registrations:
destroyed: A revider! Tu conto esseva cancellate con successo. Nos spera vider te novemente tosto. destroyed: A revider! Tu conto ha essite cancellate. Nos spera vider te de novo 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. 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. updated: Tu conto ha essite actualisate con successo.
sessions: sessions:
signed_in: Connexe con successo. already_signed_out: Session claudite con successo.
signed_out: Disconnexe con successo. signed_in: Session aperite con successo.
signed_out: Session claudite con successo.
unlocks: 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: errors:
messages: 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_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: activerecord:
attributes: attributes:
doorkeeper/application: doorkeeper/application:
name: Nomine de application name: Nomine del application
redirect_uri: URI de redirection
scopes: Ambitos scopes: Ambitos
website: Sito web de application website: Sito web del application
errors: errors:
models: models:
doorkeeper/application: doorkeeper/application:
attributes: attributes:
redirect_uri: redirect_uri:
fragment_present: non pote continer un fragmento.
invalid_uri: debe esser un URI valide. invalid_uri: debe esser un URI valide.
relative_uri: debe esser un URI absolute.
secured_uri: debe esser un URI HTTPS/SSL.
doorkeeper: doorkeeper:
applications: applications:
buttons: buttons:
authorize: Autorisar authorize: Autorisar
cancel: Cancellar cancel: Cancellar
destroy: Destruer
edit: Modificar edit: Modificar
submit: Submitter submit: Submitter
confirmations: confirmations:
destroy: Es tu secur? destroy: Es tu secur?
edit: edit:
title: Modificar application 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: index:
application: Application application: Application
callback_url: URL de retorno
delete: Deler delete: Deler
empty: Tu non ha applicationes. empty: Tu non ha applicationes.
name: Nomine name: Nomine
@ -37,17 +49,22 @@ ia:
show: show:
actions: Actiones actions: Actiones
application_id: Clave del cliente application_id: Clave del cliente
callback_urls: URLs de retorno
scopes: Ambitos scopes: Ambitos
secret: Secreto del application
title: 'Application: %{name}' title: 'Application: %{name}'
authorizations: authorizations:
buttons: buttons:
authorize: Autorisar authorize: Autorisar
deny: Negar deny: Negar
error: error:
title: Ocurreva un error title: Un error ha occurrite
new: 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 review_permissions: Revisionar le permissos
title: Autorisation necessari title: Autorisation necessari
show:
title: Copia iste codice de autorisation e colla lo in le application.
authorized_applications: authorized_applications:
buttons: buttons:
revoke: Revocar revoke: Revocar
@ -55,11 +72,35 @@ ia:
revoke: Es tu secur? revoke: Es tu secur?
index: index:
authorized_at: Autorisate le %{date} 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} last_used_at: Ultime uso in %{date}
never_used: Nunquam usate never_used: Nunquam usate
scopes: Permissiones scopes: Permissiones
superapp: Interne superapp: Interne
title: Tu applicationes autorisate 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: flash:
applications: applications:
create: create:
@ -73,20 +114,22 @@ ia:
notice: Application revocate. notice: Application revocate.
grouped_scopes: grouped_scopes:
access: access:
read: Accesso de sol lectura read: Accesso de lectura sol
read/write: Accesso de lectura e scriptura read/write: Accesso de lectura e scriptura
write: Accesso de sol scriptura write: Accesso de scriptura sol
title: title:
accounts: Contos accounts: Contos
admin/accounts: Gestion de contos admin/accounts: Gestion de contos
admin/all: Tote le functiones administrative admin/all: Tote le functiones administrative
admin/reports: Gestion de reportos admin/reports: Gestion de reportos
all: Accesso plen a tu conto de Mastodon all: Accesso complete a tu conto de Mastodon
blocks: Blocadas blocks: Blocadas
bookmarks: Marcapaginas bookmarks: Marcapaginas
conversations: Conversationes conversations: Conversationes
favourites: Favoritos crypto: Cryptation de puncta a puncta
favourites: Favorites
filters: Filtros filters: Filtros
follow: Sequites, silentiates e blocates
follows: Sequites follows: Sequites
lists: Listas lists: Listas
media: Annexos multimedial media: Annexos multimedial
@ -101,21 +144,41 @@ ia:
nav: nav:
applications: Applicationes applications: Applicationes
oauth2_provider: Fornitor OAuth2 oauth2_provider: Fornitor OAuth2
application:
title: Autorisation OAuth necessari
scopes: scopes:
admin:read: leger tote le datos in le servitor admin:read: leger tote le datos in le servitor
admin:read:accounts: leger information sensibile de tote le contos 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: 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 follow: modificar relationes del contos
push: reciper tu notificationes push
read: leger tote le datos de tu conto read: leger tote le datos de tu conto
read:accounts: vider informationes de conto read:accounts: vider informationes de conto
read:blocks: vider tu blocadas
read:bookmarks: vider tu marcapaginas read:bookmarks: vider tu marcapaginas
read:favourites: vider tu favoritos read:favourites: vider tu favoritos
read:filters: vider tu filtros read:filters: vider tu filtros
read:follows: vider tu sequites read:follows: vider tu sequites
read:lists: vider tu listas read:lists: vider tu listas
read:me: leger solmente le information basic de tu conto read:me: leger solmente le information basic de tu conto
read:mutes: vider tu silentiates
read:notifications: vider tu notificationes read:notifications: vider tu notificationes
read:reports: vider tu reportos read:reports: vider tu reportos
read:search: cercar in tu nomine
read:statuses: vider tote le messages read:statuses: vider tote le messages
write: modificar tote le datos de tu conto write: modificar tote le datos de tu conto
write:accounts: modificar tu profilo write:accounts: modificar tu profilo

View file

@ -285,6 +285,7 @@ en:
update_custom_emoji_html: "%{name} updated emoji %{target}" update_custom_emoji_html: "%{name} updated emoji %{target}"
update_domain_block_html: "%{name} updated domain block for %{target}" update_domain_block_html: "%{name} updated domain block for %{target}"
update_ip_block_html: "%{name} changed rule for IP %{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_status_html: "%{name} updated post by %{target}"
update_user_role_html: "%{name} changed %{target} role" update_user_role_html: "%{name} changed %{target} role"
deleted_account: deleted account deleted_account: deleted account

View file

@ -285,6 +285,7 @@ es-AR:
update_custom_emoji_html: "%{name} actualizó el emoji %{target}" update_custom_emoji_html: "%{name} actualizó el emoji %{target}"
update_domain_block_html: "%{name} actualizó el bloqueo de dominio para %{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_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_status_html: "%{name} actualizó el mensaje de %{target}"
update_user_role_html: "%{name} cambió el rol %{target}" update_user_role_html: "%{name} cambió el rol %{target}"
deleted_account: cuenta eliminada deleted_account: cuenta eliminada

View file

@ -285,6 +285,7 @@ es-MX:
update_custom_emoji_html: "%{name} actualizó el emoji %{target}" update_custom_emoji_html: "%{name} actualizó el emoji %{target}"
update_domain_block_html: "%{name} actualizó el bloqueo de dominio para %{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_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_status_html: "%{name} actualizó el estado de %{target}"
update_user_role_html: "%{name} cambió el rol %{target}" update_user_role_html: "%{name} cambió el rol %{target}"
deleted_account: cuenta eliminada deleted_account: cuenta eliminada

View file

@ -285,6 +285,7 @@ es:
update_custom_emoji_html: "%{name} actualizó el emoji %{target}" update_custom_emoji_html: "%{name} actualizó el emoji %{target}"
update_domain_block_html: "%{name} actualizó el bloqueo de dominio para %{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_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_status_html: "%{name} actualizó la publicación de %{target}"
update_user_role_html: "%{name} cambió el rol %{target}" update_user_role_html: "%{name} cambió el rol %{target}"
deleted_account: cuenta eliminada deleted_account: cuenta eliminada

View file

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

View file

@ -285,6 +285,7 @@ fo:
update_custom_emoji_html: "%{name} dagførdi kensluteknið %{target}" update_custom_emoji_html: "%{name} dagførdi kensluteknið %{target}"
update_domain_block_html: "%{name} dagførdi navnaøkisblokeringina hjá %{target}" update_domain_block_html: "%{name} dagførdi navnaøkisblokeringina hjá %{target}"
update_ip_block_html: "%{name} broytti IP-reglurnar %{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_status_html: "%{name} dagførdi postin hjá %{target}"
update_user_role_html: "%{name} broyttir %{target} leiklutir" update_user_role_html: "%{name} broyttir %{target} leiklutir"
deleted_account: strikað konta deleted_account: strikað konta

View file

@ -285,6 +285,7 @@ gl:
update_custom_emoji_html: "%{name} actualizou o emoji %{target}" update_custom_emoji_html: "%{name} actualizou o emoji %{target}"
update_domain_block_html: "%{name} actualizou o bloqueo do dominio para %{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_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_status_html: "%{name} actualizou a publicación de %{target}"
update_user_role_html: "%{name} cambiou o rol %{target}" update_user_role_html: "%{name} cambiou o rol %{target}"
deleted_account: conta eliminada deleted_account: conta eliminada

View file

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

View file

@ -285,6 +285,7 @@ hu:
update_custom_emoji_html: "%{name} frissítette az emodzsit: %{target}" update_custom_emoji_html: "%{name} frissítette az emodzsit: %{target}"
update_domain_block_html: "%{name} frissítette a %{target} domain tiltását" 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_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_status_html: "%{name} frissítette %{target} felhasználó bejegyzését"
update_user_role_html: "%{name} módosította a(z) %{target} szerepkört" update_user_role_html: "%{name} módosította a(z) %{target} szerepkört"
deleted_account: törölt fiók deleted_account: törölt fiók

View file

@ -285,6 +285,7 @@ ia:
update_custom_emoji_html: "%{name} actualisava le emoticone %{target}" update_custom_emoji_html: "%{name} actualisava le emoticone %{target}"
update_domain_block_html: "%{name} actualisava le blocada de dominio pro %{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_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_status_html: "%{name} actualisava le message per %{target}"
update_user_role_html: "%{name} cambiava le rolo de %{target}" update_user_role_html: "%{name} cambiava le rolo de %{target}"
deleted_account: conto delite deleted_account: conto delite
@ -973,6 +974,7 @@ ia:
webhook: Crocs web webhook: Crocs web
admin_mailer: admin_mailer:
auto_close_registrations: 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 subject: Le registrationes pro %{instance} ha essite automaticamente mutate a besoniante de approbation
new_appeal: new_appeal:
actions: actions:
@ -1054,19 +1056,27 @@ ia:
clicking_this_link: cliccante iste ligamine clicking_this_link: cliccante iste ligamine
login_link: acceder login_link: acceder
proceed_to_login_html: Ora tu pote continuar a %{login_link}. 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}! 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: 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: description:
prefix_invited_by_user: "@%{name} te invita a junger te a iste servitor de Mastodon!"
prefix_sign_up: Inscribe te sur Mastodon hodie! 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? didnt_get_confirmation: Non recipeva tu un ligamine de confirmation?
dont_have_your_security_key: Non ha tu le clave de securitate? dont_have_your_security_key: Non ha tu le clave de securitate?
forgot_password: Contrasigno oblidate? forgot_password: Contrasigno oblidate?
invalid_reset_password_token: Pete un nove. 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 link_to_webauth: Usa tu apparato clave de securitate
log_in_with: Accede con log_in_with: Accede con
login: Accede login: Accede
logout: Clauder le session logout: Clauder le session
migrate_account: Move a un conto differente 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 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> privacy_policy_agreement_html: Io ha legite e acceptar le <a href="<a href="%{privacy_policy_path}" target="_blank">politica de confidentialitate</a>
progress: progress:
@ -1192,6 +1202,7 @@ ia:
invalid_domain: non es un nomine de dominio valide invalid_domain: non es un nomine de dominio valide
edit_profile: edit_profile:
basic_information: Information basic 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 other: Alteres
errors: errors:
'400': Le requesta que tu inviava era non valide o mal formate. '400': Le requesta que tu inviava era non valide o mal formate.
@ -1230,6 +1241,7 @@ ia:
add_new: Adder nove add_new: Adder nove
errors: errors:
limit: Tu ha jam consiliate le maxime numero de hashtags 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: filters:
contexts: contexts:
account: Profilos account: Profilos
@ -1241,8 +1253,10 @@ ia:
add_keyword: Adder parola clave add_keyword: Adder parola clave
keywords: Parolas clave keywords: Parolas clave
statuses: Messages individual 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 title: Modificar filtro
errors: 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 invalid_context: Nulle o non valide contexto supplite
index: index:
contexts: Filtros in %{contexts} contexts: Filtros in %{contexts}
@ -1272,6 +1286,12 @@ ia:
title: Messages filtrate title: Messages filtrate
generic: generic:
all: Toto 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 cancel: Cancellar
changes_saved_msg: Cambios salveguardate con successo! changes_saved_msg: Cambios salveguardate con successo!
confirm: Confirmar confirm: Confirmar
@ -1299,13 +1319,24 @@ ia:
imported: Importate imported: Importate
mismatched_types_warning: Il appare que tu pote haber seligite le typo errate pro iste importation, controla duo vices. mismatched_types_warning: Il appare que tu pote haber seligite le typo errate pro iste importation, controla duo vices.
modes: modes:
merge: Funder
merge_long: Mantene le registrationes existente e adde illos nove
overwrite: Superscriber
overwrite_long: Reimplaciar registros actual con le noves overwrite_long: Reimplaciar registros actual con le noves
overwrite_preambles: 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>. 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>. 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: 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>. 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>. 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. 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 recent_imports: Importationes recente
states: states:
@ -1348,16 +1379,30 @@ ia:
expires_in_prompt: Nunquam expires_in_prompt: Nunquam
generate: Generar ligamine de invitation generate: Generar ligamine de invitation
invalid: Iste invitation non es valide invalid: Iste invitation non es valide
invited_by: 'Tu ha essite invitate per:'
max_uses: max_uses:
one: un uso one: un uso
other: "%{count} usos" other: "%{count} usos"
max_uses_prompt: Nulle limite
prompt: Genera e comparti ligamines con alteres pro conceder accesso a iste servitor
table: table:
expires_at: Expira expires_at: Expira
uses: Usos
title: Invitar personas title: Invitar personas
lists:
errors:
limit: Tu ha attingite le maxime numero de listas
login_activities: login_activities:
authentication_methods: authentication_methods:
otp: app pro authentication a duo factores
password: contrasigno password: contrasigno
sign_in_token: codice de securitate de e-mail
webauthn: claves de securitate 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: mail_subscriptions:
unsubscribe: unsubscribe:
action: Si, desubscriber 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>. 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}. success_html: Tu non recipera plus %{type} pro Mastodon sur %{domain} a tu adresse de e-mail %{email}.
title: Desubcriber 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: 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: 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 move_to_self: non pote esser le conto actual
not_found: non poterea esser trovate 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: moderation:
title: Moderation title: Moderation
move_handler: move_handler:
carry_blocks_over_text: Iste usator ha cambiate de conto desde %{acct}, que tu habeva blocate. 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: notification_mailer:
admin: admin:
report:
subject: "%{name} inviava un reporto"
sign_up: sign_up:
subject: "%{name} se ha inscribite" subject: "%{name} se ha inscribite"
favourite:
body: 'Tu message era favorite per %{name}:'
subject: "%{name} favoriva tu message"
title: Nove preferito
follow: follow:
body: "%{name} ora te seque!"
subject: "%{name} ora te seque"
title: Nove sequitor title: Nove sequitor
follow_request: follow_request:
action: Gere requestas de sequer
body: "%{name} ha demandate de sequer te"
subject: 'Sequace pendente: %{name}'
title: Nove requesta de sequimento title: Nove requesta de sequimento
mention: mention:
action: Responder action: Responder
body: 'Tu era mentionate per %{name} in:'
subject: Tu ha essite mentionate per %{name}
title: Nove mention title: Nove mention
poll: poll:
subject: Un inquesta de %{name} ha finite 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: 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 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 setup: Configurar
wrong_code: Le codice inserite non era valide! Es tempore de servitor e tempore de apparato correcte?
pagination: pagination:
newer: Plus recente
next: Sequente next: Sequente
older: Plus vetere
prev: Previe prev: Previe
truncate: "&hellip;" truncate: "&hellip;"
polls: polls:
@ -1418,9 +1541,13 @@ ia:
posting_defaults: Publicationes predefinite posting_defaults: Publicationes predefinite
public_timelines: Chronologias public public_timelines: Chronologias public
privacy: 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: 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: 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: 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 title: Confidentialitate e portata
privacy_policy: privacy_policy:
title: Politica de confidentialitate title: Politica de confidentialitate
@ -1521,35 +1648,96 @@ ia:
aliases: Aliases de conto aliases: Aliases de conto
appearance: Apparentia appearance: Apparentia
authorized_apps: Apps autorisate authorized_apps: Apps autorisate
back: Tornar a Mastodon
delete: Deletion de conto delete: Deletion de conto
development: Disveloppamento development: Disveloppamento
edit_profile: Modificar profilo edit_profile: Modificar profilo
export: Exportation de datos
featured_tags: Hashtags eminente featured_tags: Hashtags eminente
import: Importar import: Importar
import_and_export: Importar e exportar
migrate: Migration de conto migrate: Migration de conto
notifications: Notificationes de e-mail notifications: Notificationes de e-mail
preferences: Preferentias preferences: Preferentias
profile: Profilo public profile: Profilo public
relationships: Sequites e sequitores relationships: Sequites e sequitores
severed_relationships: Relationes rupte
statuses_cleanup: Deletion de message automatic
strikes: Admonitiones de moderation strikes: Admonitiones de moderation
two_factor_authentication: Authentication a duo factores
webauthn_authentication: Claves de securitate
severed_relationships: severed_relationships:
download: Discargar (%{count}) download: Discargar (%{count})
event_type: event_type:
account_suspension: Suspension del conto (%{target_name}) account_suspension: Suspension del conto (%{target_name})
domain_block: Suspension del servitor (%{target_name}) domain_block: Suspension del servitor (%{target_name})
user_domain_block: Tu ha blocate %{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. 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 type: Evento
statuses: 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 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: poll:
total_people:
one: "%{count} persona"
other: "%{count} personas"
total_votes:
one: "%{count} voto"
other: "%{count} votos"
vote: Votar vote: Votar
show_more: Monstrar plus show_more: Monstrar plus
show_thread: Monstrar argumento
title: '%{name}: "%{quote}"'
visibilities: visibilities:
direct: Directe direct: Directe
private: Solo-sequaces
private_long: Solmente monstrar a sequitores private_long: Solmente monstrar a sequitores
public: Public 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: 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_pinned_hint: Non dele alcuno de tu messages appunctate
keep_polls: Mantener sondages keep_polls: Mantener sondages
keep_polls_hint: Non dele ulle de tu sondages keep_polls_hint: Non dele ulle de tu sondages
@ -1567,32 +1755,94 @@ ia:
'63113904': 2 annos '63113904': 2 annos
'7889238': 3 menses '7889238': 3 menses
min_age_label: Limine de etate 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: stream_entries:
sensitive_content: Contento sensibile sensitive_content: Contento sensibile
strikes: strikes:
errors: errors:
too_late: Es troppo tarde pro facer appello contra iste admonition too_late: Es troppo tarde pro facer appello contra iste admonition
tags:
does_not_match_previous_name: non concorda le nomine previe
themes: themes:
contrast: Mastodon (Alte contrasto) contrast: Mastodon (Alte contrasto)
default: Mastodon (Obscur) default: Mastodon (Obscur)
mastodon-light: Mastodon (Clar) mastodon-light: Mastodon (Clar)
system: Automatic (usar thema del systema) 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: two_factor_authentication:
add: Adder add: Adder
disable: Disactivar 2FA disable: Disactivar 2FA
disabled_success: Authentication a duo factores disactivate con successo
edit: Modificar 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 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: user_mailer:
appeal_approved: appeal_approved:
action: Parametros de conto 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. 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: appeal_rejected:
explanation: Le appello contra le admonition contra tu conto del %{strike_date}, que tu ha submittite le %{appeal_date}, ha essite rejectate. 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: warning:
appeal: Submitter un appello 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: categories:
spam: Spam 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: subject:
delete_statuses: Tu messages sur %{acct} esseva removite
disable: Tu conto %{acct} ha essite gelate disable: Tu conto %{acct} ha essite gelate
mark_statuses_as_sensitive: Tu messages sur %{acct} ha essite marcate como sensibile mark_statuses_as_sensitive: Tu messages sur %{acct} ha essite marcate como sensibile
none: Advertimento pro %{acct} none: Advertimento pro %{acct}
@ -1612,20 +1862,71 @@ ia:
apps_ios_action: Discargar sur le App Store apps_ios_action: Discargar sur le App Store
apps_step: Discarga nostre applicationes official. apps_step: Discarga nostre applicationes official.
apps_title: Applicationes de Mastodon apps_title: Applicationes de Mastodon
checklist_subtitle: 'Comencia tu aventura sur le web social:'
checklist_title: Prime passos
edit_profile_action: Personalisar edit_profile_action: Personalisar
edit_profile_step: Impulsa tu interactiones con un profilo comprehensive. edit_profile_step: Impulsa tu interactiones con un profilo comprehensive.
edit_profile_title: Personalisar tu profilo edit_profile_title: Personalisar tu profilo
explanation: Ecce alcun consilios pro initiar explanation: Ecce alcun consilios pro initiar
feature_action: Apprender plus 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_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 feature_moderation_title: Moderation como deberea esser
follow_action: Sequer 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 post_title: Face tu prime message
share_action: Compartir share_action: Compartir
share_step: Face saper a tu amicos como trovar te sur Mastodon.
share_title: Compartir tu profilo de Mastodon share_title: Compartir tu profilo de Mastodon
sign_in_action: Initiar session
subject: Benvenite in Mastodon 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: 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 verified_links: Tu ligamines verificate
webauthn_credentials: webauthn_credentials:
add: Adder un nove clave de securitate 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: 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_custom_emoji_html: "%{name} ha aggiornato emoji %{target}"
update_domain_block_html: "%{name} ha aggiornato il blocco dominio per %{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_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_status_html: "%{name} ha aggiornato lo status di %{target}"
update_user_role_html: "%{name} ha modificato il ruolo %{target}" update_user_role_html: "%{name} ha modificato il ruolo %{target}"
deleted_account: account eliminato deleted_account: account eliminato

View file

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

View file

@ -285,6 +285,7 @@ lad:
update_custom_emoji_html: "%{name} aktualizo el emoji %{target}" update_custom_emoji_html: "%{name} aktualizo el emoji %{target}"
update_domain_block_html: "%{name} aktualizo el bloko de domeno para %{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_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_status_html: "%{name} aktualizo la publikasyon de %{target}"
update_user_role_html: "%{name} troko el rolo %{target}" update_user_role_html: "%{name} troko el rolo %{target}"
deleted_account: kuento supremido deleted_account: kuento supremido

View file

@ -285,6 +285,7 @@ nl:
update_custom_emoji_html: Emoji %{target} is door %{name} bijgewerkt update_custom_emoji_html: Emoji %{target} is door %{name} bijgewerkt
update_domain_block_html: "%{name} heeft de domeinblokkade bijgewerkt voor %{target}" update_domain_block_html: "%{name} heeft de domeinblokkade bijgewerkt voor %{target}"
update_ip_block_html: "%{name} wijzigde de IP-regel 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_status_html: "%{name} heeft de berichten van %{target} bijgewerkt"
update_user_role_html: "%{name} wijzigde de rol %{target}" update_user_role_html: "%{name} wijzigde de rol %{target}"
deleted_account: verwijderd account deleted_account: verwijderd account

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