sforkowany z mirror/soapbox
				
			Merge branch 'account-aliases' into 'develop'
Account aliases See merge request soapbox-pub/soapbox-fe!663groups^2
						commit
						bde53541b7
					
				|  | @ -0,0 +1,128 @@ | |||
| import { defineMessages } from 'react-intl'; | ||||
| import api from '../api'; | ||||
| import { importFetchedAccount, importFetchedAccounts } from './importer'; | ||||
| import { showAlertForError } from './alerts'; | ||||
| import snackbar from './snackbar'; | ||||
| import { isLoggedIn } from 'soapbox/utils/auth'; | ||||
| import { ME_PATCH_SUCCESS } from './me'; | ||||
| 
 | ||||
| export const ALIASES_SUGGESTIONS_CHANGE = 'ALIASES_SUGGESTIONS_CHANGE'; | ||||
| export const ALIASES_SUGGESTIONS_READY  = 'ALIASES_SUGGESTIONS_READY'; | ||||
| export const ALIASES_SUGGESTIONS_CLEAR  = 'ALIASES_SUGGESTIONS_CLEAR'; | ||||
| 
 | ||||
| export const ALIASES_ADD_REQUEST = 'ALIASES_ADD_REQUEST'; | ||||
| export const ALIASES_ADD_SUCCESS = 'ALIASES_ADD_SUCCESS'; | ||||
| export const ALIASES_ADD_FAIL    = 'ALIASES_ADD_FAIL'; | ||||
| 
 | ||||
| export const ALIASES_REMOVE_REQUEST = 'ALIASES_REMOVE_REQUEST'; | ||||
| export const ALIASES_REMOVE_SUCCESS = 'ALIASES_REMOVE_SUCCESS'; | ||||
| export const ALIASES_REMOVE_FAIL    = 'ALIASES_REMOVE_FAIL'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   createSuccess: { id: 'aliases.success.add', defaultMessage: 'Account alias created successfully' }, | ||||
|   removeSuccess: { id: 'aliases.success.remove', defaultMessage: 'Account alias removed successfully' }, | ||||
| }); | ||||
| 
 | ||||
| export const fetchAliasesSuggestions = q => (dispatch, getState) => { | ||||
|   if (!isLoggedIn(getState)) return; | ||||
| 
 | ||||
|   const params = { | ||||
|     q, | ||||
|     resolve: true, | ||||
|     limit: 4, | ||||
|   }; | ||||
| 
 | ||||
|   api(getState).get('/api/v1/accounts/search', { params }).then(({ data }) => { | ||||
|     dispatch(importFetchedAccounts(data)); | ||||
|     dispatch(fetchAliasesSuggestionsReady(q, data)); | ||||
|   }).catch(error => dispatch(showAlertForError(error))); | ||||
| }; | ||||
| 
 | ||||
| export const fetchAliasesSuggestionsReady = (query, accounts) => ({ | ||||
|   type: ALIASES_SUGGESTIONS_READY, | ||||
|   query, | ||||
|   accounts, | ||||
| }); | ||||
| 
 | ||||
| export const clearAliasesSuggestions = () => ({ | ||||
|   type: ALIASES_SUGGESTIONS_CLEAR, | ||||
| }); | ||||
| 
 | ||||
| export const changeAliasesSuggestions = value => ({ | ||||
|   type: ALIASES_SUGGESTIONS_CHANGE, | ||||
|   value, | ||||
| }); | ||||
| 
 | ||||
| export const addToAliases = (intl, apId) => (dispatch, getState) => { | ||||
|   if (!isLoggedIn(getState)) return; | ||||
| 
 | ||||
|   const alsoKnownAs = getState().getIn(['meta', 'pleroma', 'also_known_as']); | ||||
| 
 | ||||
|   dispatch(addToAliasesRequest(apId)); | ||||
| 
 | ||||
|   api(getState).patch('/api/v1/accounts/update_credentials', { also_known_as: [...alsoKnownAs, apId] }) | ||||
|     .then((response => { | ||||
|       dispatch(snackbar.success(intl.formatMessage(messages.createSuccess))); | ||||
|       dispatch(addToAliasesSuccess(response.data)); | ||||
|     })) | ||||
|     .catch(err => dispatch(addToAliasesFail(err))); | ||||
| }; | ||||
| 
 | ||||
| export const addToAliasesRequest = (apId) => ({ | ||||
|   type: ALIASES_ADD_REQUEST, | ||||
|   apId, | ||||
| }); | ||||
| 
 | ||||
| export const addToAliasesSuccess = me => dispatch => { | ||||
|   dispatch(importFetchedAccount(me)); | ||||
|   dispatch({ | ||||
|     type: ME_PATCH_SUCCESS, | ||||
|     me, | ||||
|   }); | ||||
|   dispatch({ | ||||
|     type: ALIASES_ADD_SUCCESS, | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| export const addToAliasesFail = (apId, error) => ({ | ||||
|   type: ALIASES_ADD_FAIL, | ||||
|   apId, | ||||
|   error, | ||||
| }); | ||||
| 
 | ||||
| export const removeFromAliases = (intl, apId) => (dispatch, getState) => { | ||||
|   if (!isLoggedIn(getState)) return; | ||||
| 
 | ||||
|   const alsoKnownAs = getState().getIn(['meta', 'pleroma', 'also_known_as']); | ||||
| 
 | ||||
|   dispatch(removeFromAliasesRequest(apId)); | ||||
| 
 | ||||
|   api(getState).patch('/api/v1/accounts/update_credentials', { also_known_as: alsoKnownAs.filter(id => id !== apId) }) | ||||
|     .then(response => { | ||||
|       dispatch(snackbar.success(intl.formatMessage(messages.removeSuccess))); | ||||
|       dispatch(removeFromAliasesSuccess(response.data)); | ||||
|     }) | ||||
|     .catch(err => dispatch(removeFromAliasesFail(apId, err))); | ||||
| }; | ||||
| 
 | ||||
| export const removeFromAliasesRequest = (apId) => ({ | ||||
|   type: ALIASES_REMOVE_REQUEST, | ||||
|   apId, | ||||
| }); | ||||
| 
 | ||||
| export const removeFromAliasesSuccess = me => dispatch => { | ||||
|   dispatch(importFetchedAccount(me)); | ||||
|   dispatch({ | ||||
|     type: ME_PATCH_SUCCESS, | ||||
|     me, | ||||
|   }); | ||||
|   dispatch({ | ||||
|     type: ALIASES_REMOVE_SUCCESS, | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| export const removeFromAliasesFail = (apId, error) => ({ | ||||
|   type: ALIASES_REMOVE_FAIL, | ||||
|   apId, | ||||
|   error, | ||||
| }); | ||||
|  | @ -33,6 +33,7 @@ const messages = defineMessages({ | |||
|   admin_settings: { id: 'navigation_bar.admin_settings', defaultMessage: 'Admin settings' }, | ||||
|   soapbox_config: { id: 'navigation_bar.soapbox_config', defaultMessage: 'Soapbox config' }, | ||||
|   import_data: { id: 'navigation_bar.import_data', defaultMessage: 'Import data' }, | ||||
|   account_aliases: { id: 'navigation_bar.account_aliases', defaultMessage: 'Account aliases' }, | ||||
|   security: { id: 'navigation_bar.security', defaultMessage: 'Security' }, | ||||
|   logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, | ||||
|   lists: { id: 'column.lists', defaultMessage: 'Lists' }, | ||||
|  | @ -258,6 +259,10 @@ class SidebarMenu extends ImmutablePureComponent { | |||
|                 <Icon id='cloud-upload' /> | ||||
|                 <span className='sidebar-menu-item__title'>{intl.formatMessage(messages.import_data)}</span> | ||||
|               </NavLink> | ||||
|               <NavLink className='sidebar-menu-item' to='/settings/aliases' onClick={this.handleClose}> | ||||
|                 <Icon id='suitcase' /> | ||||
|                 <span className='sidebar-menu-item__title'>{intl.formatMessage(messages.account_aliases)}</span> | ||||
|               </NavLink> | ||||
|               <NavLink className='sidebar-menu-item' to='/auth/edit' onClick={this.handleClose}> | ||||
|                 <Icon id='lock' /> | ||||
|                 <span className='sidebar-menu-item__title'>{intl.formatMessage(messages.security)}</span> | ||||
|  |  | |||
|  | @ -0,0 +1,84 @@ | |||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { makeGetAccount } from '../../../selectors'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import Avatar from '../../../components/avatar'; | ||||
| import DisplayName from '../../../components/display_name'; | ||||
| import IconButton from '../../../components/icon_button'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import { addToAliases } from '../../../actions/aliases'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   add: { id: 'aliases.account.add', defaultMessage: 'Create alias' }, | ||||
| }); | ||||
| 
 | ||||
| const makeMapStateToProps = () => { | ||||
|   const getAccount = makeGetAccount(); | ||||
| 
 | ||||
|   const mapStateToProps = (state, { accountId, added }) => { | ||||
|     const account = getAccount(state, accountId); | ||||
|     const apId = account.getIn(['pleroma', 'ap_id']); | ||||
| 
 | ||||
|     return { | ||||
|       account, | ||||
|       apId, | ||||
|       added: typeof added === 'undefined' ? state.getIn(['meta', 'pleroma', 'also_known_as']).includes(apId) : added, | ||||
|       me: state.get('me'), | ||||
|     }; | ||||
|   }; | ||||
| 
 | ||||
|   return mapStateToProps; | ||||
| }; | ||||
| 
 | ||||
| const mapDispatchToProps = (dispatch) => ({ | ||||
|   onAdd: (intl, apId) => dispatch(addToAliases(intl, apId)), | ||||
| }); | ||||
| 
 | ||||
| export default @connect(makeMapStateToProps, mapDispatchToProps) | ||||
| @injectIntl | ||||
| class Account extends ImmutablePureComponent { | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     account: ImmutablePropTypes.map.isRequired, | ||||
|     apId: PropTypes.string.isRequired, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|     onAdd: PropTypes.func.isRequired, | ||||
|     added: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|   static defaultProps = { | ||||
|     added: false, | ||||
|   }; | ||||
| 
 | ||||
|   handleOnAdd = () => this.props.onAdd(this.props.intl, this.props.apId); | ||||
| 
 | ||||
|   render() { | ||||
|     const { account, accountId, intl, added, me } = this.props; | ||||
| 
 | ||||
|     let button; | ||||
| 
 | ||||
|     if (!added && accountId !== me) { | ||||
|       button = ( | ||||
|         <div className='account__relationship'> | ||||
|           <IconButton icon='plus' title={intl.formatMessage(messages.add)} onClick={this.handleOnAdd} /> | ||||
|         </div> | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div className='account'> | ||||
|         <div className='account__wrapper'> | ||||
|           <div className='account__display-name'> | ||||
|             <div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div> | ||||
|             <DisplayName account={account} /> | ||||
|           </div> | ||||
| 
 | ||||
|           {button} | ||||
|         </div> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,83 @@ | |||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import { fetchAliasesSuggestions, clearAliasesSuggestions, changeAliasesSuggestions } from '../../../actions/aliases'; | ||||
| import classNames from 'classnames'; | ||||
| import Icon from 'soapbox/components/icon'; | ||||
| import Button from 'soapbox/components/button'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   search: { id: 'aliases.search', defaultMessage: 'Search your old account' }, | ||||
|   searchTitle: { id: 'tabs_bar.search', defaultMessage: 'Search' }, | ||||
| }); | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|   value: state.getIn(['aliases', 'suggestions', 'value']), | ||||
| }); | ||||
| 
 | ||||
| const mapDispatchToProps = dispatch => ({ | ||||
|   onSubmit: value => dispatch(fetchAliasesSuggestions(value)), | ||||
|   onClear: () => dispatch(clearAliasesSuggestions()), | ||||
|   onChange: value => dispatch(changeAliasesSuggestions(value)), | ||||
| }); | ||||
| 
 | ||||
| export default @connect(mapStateToProps, mapDispatchToProps) | ||||
| @injectIntl | ||||
| class Search extends React.PureComponent { | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     intl: PropTypes.object.isRequired, | ||||
|     value: PropTypes.string.isRequired, | ||||
|     onChange: PropTypes.func.isRequired, | ||||
|     onSubmit: PropTypes.func.isRequired, | ||||
|     onClear: PropTypes.func.isRequired, | ||||
|   }; | ||||
| 
 | ||||
|   handleChange = e => { | ||||
|     this.props.onChange(e.target.value); | ||||
|   } | ||||
| 
 | ||||
|   handleKeyUp = e => { | ||||
|     if (e.keyCode === 13) { | ||||
|       this.props.onSubmit(this.props.value); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   handleSubmit = () => { | ||||
|     this.props.onSubmit(this.props.value); | ||||
|   } | ||||
| 
 | ||||
|   handleClear = () => { | ||||
|     this.props.onClear(); | ||||
|   } | ||||
| 
 | ||||
|   render() { | ||||
|     const { value, intl } = this.props; | ||||
|     const hasValue = value.length > 0; | ||||
| 
 | ||||
|     return ( | ||||
|       <div className='aliases_search search'> | ||||
|         <label> | ||||
|           <span style={{ display: 'none' }}>{intl.formatMessage(messages.search)}</span> | ||||
| 
 | ||||
|           <input | ||||
|             className='search__input' | ||||
|             type='text' | ||||
|             value={value} | ||||
|             onChange={this.handleChange} | ||||
|             onKeyUp={this.handleKeyUp} | ||||
|             placeholder={intl.formatMessage(messages.search)} | ||||
|           /> | ||||
|         </label> | ||||
| 
 | ||||
|         <div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}> | ||||
|           <Icon id='search' className={classNames({ active: !hasValue })} /> | ||||
|           <Icon id='times-circle' aria-label={intl.formatMessage(messages.search)} className={classNames({ active: hasValue })} /> | ||||
|         </div> | ||||
|         <Button onClick={this.handleSubmit}>{intl.formatMessage(messages.searchTitle)}</Button> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,81 @@ | |||
| import React from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import Column from '../ui/components/column'; | ||||
| import ColumnSubheading from '../ui/components/column_subheading'; | ||||
| import ScrollableList from '../../components/scrollable_list'; | ||||
| import Icon from 'soapbox/components/icon'; | ||||
| import Search from './components/search'; | ||||
| import Account from './components/account'; | ||||
| import { removeFromAliases } from '../../actions/aliases'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   heading: { id: 'column.aliases', defaultMessage: 'Account aliases' }, | ||||
|   subheading_add_new: { id: 'column.aliases.subheading_add_new', defaultMessage: 'Add New Alias' }, | ||||
|   create_error: { id: 'column.aliases.create_error', defaultMessage: 'Error creating alias' }, | ||||
|   delete_error: { id: 'column.aliases.delete_error', defaultMessage: 'Error deleting alias' }, | ||||
|   subheading_aliases: { id: 'column.aliases.subheading_aliases', defaultMessage: 'Current aliases' }, | ||||
|   delete: { id: 'column.aliases.delete', defaultMessage: 'Delete' }, | ||||
| }); | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|   aliases: state.getIn(['meta', 'pleroma', 'also_known_as']), | ||||
|   searchAccountIds: state.getIn(['aliases', 'suggestions', 'items']), | ||||
|   loaded: state.getIn(['aliases', 'suggestions', 'loaded']), | ||||
| }); | ||||
| 
 | ||||
| export default @connect(mapStateToProps) | ||||
| @injectIntl | ||||
| class Aliases extends ImmutablePureComponent { | ||||
| 
 | ||||
|   handleFilterDelete = e => { | ||||
|     const { dispatch, intl } = this.props; | ||||
|     dispatch(removeFromAliases(intl, e.currentTarget.dataset.value)); | ||||
|   } | ||||
| 
 | ||||
|   render() { | ||||
|     const { intl, aliases, searchAccountIds, loaded } = this.props; | ||||
| 
 | ||||
|     const emptyMessage = <FormattedMessage id='empty_column.aliases' defaultMessage="You haven't created any account alias yet." />; | ||||
| 
 | ||||
|     return ( | ||||
|       <Column className='aliases-settings-panel' icon='suitcase' heading={intl.formatMessage(messages.heading)} backBtnSlim> | ||||
|         <ColumnSubheading text={intl.formatMessage(messages.subheading_add_new)} /> | ||||
|         <Search /> | ||||
|         { | ||||
|           loaded && searchAccountIds.size === 0 ? ( | ||||
|             <div className='aliases__accounts empty-column-indicator'> | ||||
|               <FormattedMessage id='empty_column.aliases.suggestions' defaultMessage='There are no account suggestions available for the provided term.' /> | ||||
|             </div> | ||||
|           ) : ( | ||||
|             <div className='aliases__accounts'> | ||||
|               {searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)} | ||||
|             </div> | ||||
|           ) | ||||
|         } | ||||
|         <ColumnSubheading text={intl.formatMessage(messages.subheading_aliases)} /> | ||||
|         <div className='aliases-settings-panel'> | ||||
|           <ScrollableList | ||||
|             scrollKey='aliases' | ||||
|             emptyMessage={emptyMessage} | ||||
|           > | ||||
|             {aliases.map((alias, i) => ( | ||||
|               <div key={i} className='alias__container'> | ||||
|                 <div className='alias__details'> | ||||
|                   <span className='alias__list-label'><FormattedMessage id='aliases.account_label' defaultMessage='Old account:' /></span> | ||||
|                   <span className='alias__list-value'>{alias}</span> | ||||
|                 </div> | ||||
|                 <div className='alias__delete' role='button' tabIndex='0' onClick={this.handleFilterDelete} data-value={alias} aria-label={intl.formatMessage(messages.delete)}> | ||||
|                   <Icon className='alias__delete-icon' id='times' size={40} /> | ||||
|                   <span className='alias__delete-label'><FormattedMessage id='aliases.aliases_list_delete' defaultMessage='Unlink alias' /></span> | ||||
|                 </div> | ||||
|               </div> | ||||
|             ))} | ||||
|           </ScrollableList> | ||||
|         </div> | ||||
|       </Column> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  | @ -38,6 +38,7 @@ const LinkFooter = ({ onOpenHotkeys, account, onClickLogOut }) => ( | |||
|         {isAdmin(account) && <li><a href='/pleroma/admin'><FormattedMessage id='navigation_bar.admin_settings' defaultMessage='AdminFE' /></a></li>} | ||||
|         {isAdmin(account) && <li><Link to='/soapbox/config'><FormattedMessage id='navigation_bar.soapbox_config' defaultMessage='Soapbox config' /></Link></li>} | ||||
|         <li><Link to='/settings/import'><FormattedMessage id='navigation_bar.import_data' defaultMessage='Import data' /></Link></li> | ||||
|         <li><Link to='/settings/aliases'><FormattedMessage id='navigation_bar.account_aliases' defaultMessage='Account aliases' /></Link></li> | ||||
|         <li><a href='#' onClick={onOpenHotkeys}><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></a></li> | ||||
|       </>} | ||||
|       <li><Link to='/about'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></Link></li> | ||||
|  |  | |||
|  | @ -98,6 +98,7 @@ import { | |||
|   ScheduledStatuses, | ||||
|   UserIndex, | ||||
|   FederationRestrictions, | ||||
|   Aliases, | ||||
| } from './util/async-components'; | ||||
| 
 | ||||
| // Dummy import, to make sure that <Status /> ends up in the application bundle.
 | ||||
|  | @ -261,6 +262,7 @@ class SwitchingColumnsArea extends React.PureComponent { | |||
|         <WrappedRoute path='/settings/preferences' page={DefaultPage} component={Preferences} content={children} /> | ||||
|         <WrappedRoute path='/settings/profile' page={DefaultPage} component={EditProfile} content={children} /> | ||||
|         <WrappedRoute path='/settings/import' page={DefaultPage} component={ImportData} content={children} /> | ||||
|         <WrappedRoute path='/settings/aliases' page={DefaultPage} component={Aliases} content={children} /> | ||||
|         <WrappedRoute path='/backups' page={DefaultPage} component={Backups} content={children} /> | ||||
|         <WrappedRoute path='/soapbox/config' page={DefaultPage} component={SoapboxConfig} content={children} /> | ||||
| 
 | ||||
|  |  | |||
|  | @ -249,3 +249,7 @@ export function UserIndex() { | |||
| export function FederationRestrictions() { | ||||
|   return import(/* webpackChunkName: "features/federation_restrictions" */'../../federation_restrictions'); | ||||
| } | ||||
| 
 | ||||
| export function Aliases() { | ||||
|   return import(/* webpackChunkName: "features/aliases" */'../../aliases'); | ||||
| } | ||||
|  |  | |||
|  | @ -42,7 +42,8 @@ | |||
|   "account.share": "Udostępnij profil @{name}", | ||||
|   "account.show_reblogs": "Pokazuj podbicia od @{name}", | ||||
|   "account.subscribe": "Subskrybuj wpisy @{name}", | ||||
|   "account.unblock": "Odblokuj @{name}", | ||||
|   "account.subscribed": "Subscribed", | ||||
|   "account.unblock": "Zasubskrybowano", | ||||
|   "account.unblock_domain": "Odblokuj domenę {domain}", | ||||
|   "account.unendorse": "Przestań polecać", | ||||
|   "account.unfollow": "Przestań śledzić", | ||||
|  | @ -50,8 +51,6 @@ | |||
|   "account.unmute_notifications": "Cofnij wyciszenie powiadomień od @{name}", | ||||
|   "account.unsubscribe": "Przestań subskrybować wpisy @{name}", | ||||
|   "account_gallery.none": "Brak zawartości multimedialnej do wyświetlenia.", | ||||
|   "auth.invalid_credentials": "Nieprawidłowa nazwa użytkownika lub hasło", | ||||
|   "auth.logged_out": "Wylogowano.", | ||||
|   "admin.awaiting_approval.approved_message": "Przyjęto {acct}!", | ||||
|   "admin.awaiting_approval.empty_message": "Nikt nie oczekuje przyjęcia. Gdy zarejestruje się nowy użytkownik, możesz zatwierdzić go tutaj.", | ||||
|   "admin.awaiting_approval.rejected_message": "Odrzucono {acct}!", | ||||
|  | @ -95,7 +94,7 @@ | |||
|   "admin.users.actions.promote_to_admin": "Mianuj @{name} administratorem", | ||||
|   "admin.users.actions.promote_to_admin_message": "Mianowano @{acct} administratorem", | ||||
|   "admin.users.actions.promote_to_moderator": "Mianuj @{name} moderatorem", | ||||
|   "admin.users.actions.promote_to_moderatorem_message": "Mianowano @{acct} moderatorem", | ||||
|   "admin.users.actions.promote_to_moderator_message": "Mianowano @{acct} moderatorem", | ||||
|   "admin.users.actions.unverify_user": "Cofnij weryfikację @{name}", | ||||
|   "admin.users.actions.verify_user": "Weryfikuj @{name}", | ||||
|   "admin.users.user_deactivated_message": "Zdezaktywowano @{acct}", | ||||
|  | @ -110,13 +109,14 @@ | |||
|   "alert.unexpected.message": "Wystąpił nieoczekiwany błąd.", | ||||
|   "alert.unexpected.return_home": "Wróć na stronę główną", | ||||
|   "alert.unexpected.title": "O nie!", | ||||
|   "audio.close": "Zamknij dźwięk", | ||||
|   "audio.expand": "Rozwiń dźwięk", | ||||
|   "audio.hide": "Ukryj dźwięk", | ||||
|   "audio.mute": "Wycisz", | ||||
|   "audio.pause": "Pauzuj", | ||||
|   "audio.play": "Odtwórz", | ||||
|   "audio.unmute": "Cofnij wyciszenie", | ||||
|   "aliases.account.add": "Utwórz alias", | ||||
|   "aliases.account_label": "Stare konto:", | ||||
|   "aliases.aliases_list_delete": "Odłącz alias", | ||||
|   "aliases.search": "Szukaj swojego starego konta", | ||||
|   "aliases.success.add": "Pomyślnie utworzono alias konta", | ||||
|   "aliases.success.remove": "Pomyślnie usunięto alias konta", | ||||
|   "auth.invalid_credentials": "Nieprawidłowa nazwa użytkownika lub hasło", | ||||
|   "auth.logged_out": "Wylogowano.", | ||||
|   "backups.actions.create": "Utwórz kopię zapasową", | ||||
|   "backups.empty_message": "Nie znaleziono kopii zapasaowych. {action}", | ||||
|   "backups.empty_message.action": "Chcesz utworzyć?", | ||||
|  | @ -143,14 +143,20 @@ | |||
|   "column.admin.moderation_log": "Dziennik moderacyjny", | ||||
|   "column.admin.reports": "Zgłoszenia", | ||||
|   "column.admin.reports.menu.moderation_log": "Dziennik moderacji", | ||||
|   "column.aliases": "Aliasy kont", | ||||
|   "column.aliases.create_error": "Błąd tworzenia aliasu", | ||||
|   "column.aliases.delete": "Usuń", | ||||
|   "column.aliases.delete_error": "Błąd usuwania aliasu", | ||||
|   "column.aliases.subheading_add_new": "Dodaj nowy alias", | ||||
|   "column.aliases.subheading_aliases": "Istniejące aliasy", | ||||
|   "column.backups": "Kopie zapasowe", | ||||
|   "column.blocks": "Zablokowani użytkownicy", | ||||
|   "column.bookmarks": "Załadki", | ||||
|   "column.chats": "Rozmowy", | ||||
|   "column.community": "Lokalna oś czasu", | ||||
|   "column.crypto_donate": "Przekaż kryptowalutę", | ||||
|   "column.direct": "Wiadomości bezpośrednie", | ||||
|   "column.domain_blocks": "Ukryte domeny", | ||||
|   "column.crypto_donate": "Przekaż kryptowalutę", | ||||
|   "column.edit_profile": "Edytuj profil", | ||||
|   "column.federation_restrictions": "Ograniczenia federacji", | ||||
|   "column.filters": "Wyciszone słowa", | ||||
|  | @ -195,6 +201,7 @@ | |||
|   "column_header.hide_settings": "Ukryj ustawienia", | ||||
|   "column_header.show_settings": "Pokaż ustawienia", | ||||
|   "community.column_settings.media_only": "Tylko zawartość multimedialna", | ||||
|   "compose.invalid_schedule": "Musisz zaplanować wpis przynajmniej 5 minut wcześniej.", | ||||
|   "compose_form.direct_message_warning": "Ten wpis będzie widoczny tylko dla wszystkich wspomnianych użytkowników.", | ||||
|   "compose_form.direct_message_warning_learn_more": "Dowiedz się więcej", | ||||
|   "compose_form.hashtag_warning": "Ten wpis nie będzie widoczny pod podanymi hashtagami, ponieważ jest oznaczony jako niewidoczny. Tylko publiczne wpisy mogą zostać znalezione z użyciem hashtagów.", | ||||
|  | @ -250,10 +257,11 @@ | |||
|   "confirmations.reply.message": "W ten sposób utracisz wpis który obecnie tworzysz. Czy na pewno chcesz to zrobić?", | ||||
|   "confirmations.unfollow.confirm": "Przestań śledzić", | ||||
|   "confirmations.unfollow.message": "Czy na pewno zamierzasz przestać śledzić {name}?", | ||||
|   "crypto_donate_panel.actions.more": "Naciśnij, aby zobaczyć {count} more {count, plural, one {portfel} few {portfele} many {portfeli} other {portfeli}}", | ||||
|   "crypto_donate.explanation_box.message": "{siteTitle} przyjmuje darowizny w kryptowalutach. Możesz wysłać darowiznę na jeden z poniższych adresów. Dziękujemy za Wasze wsparcie!", | ||||
|   "crypto_donate_panel.intro.message": "{siteTitle} przyjmuje darowizny w kryptowalutach, aby utrzymać naszą usługę. Dziękujemy za Wasze wsparcie!", | ||||
|   "crypto_donate.explanation_box.title": "Przeaż darowiznę w kryptowalutach", | ||||
|   "crypto_donate_panel.actions.more": "Naciśnij, aby zobaczyć {count} more {count, plural, one {portfel} few {portfele} many {portfeli} other {portfeli}}", | ||||
|   "crypto_donate_panel.heading": "Przekaż kryptowalutę", | ||||
|   "crypto_donate_panel.intro.message": "{siteTitle} przyjmuje darowizny w kryptowalutach, aby utrzymać naszą usługę. Dziękujemy za Wasze wsparcie!", | ||||
|   "datepicker.hint": "Zaplanowano publikację na…", | ||||
|   "donate": "Przekaż darowiznę", | ||||
|   "donate_crypto": "Przekaż kryptowalutę", | ||||
|  | @ -277,7 +285,7 @@ | |||
|   "edit_profile.fields.meta_fields_label": "Metadane profilu", | ||||
|   "edit_profile.fields.stranger_notifications_label": "Blokuj powiadomienia od nieznajomych", | ||||
|   "edit_profile.fields.verified_display_name": "Zweryfikowani użytkownicy nie mogą zmieniać nazwy wyświetlanej", | ||||
|   "edit_profile.hints.accepts_email_list_label": "Otrzymuj wiadomości i nowości marketingowe", | ||||
|   "edit_profile.hints.accepts_email_list": "Otrzymuj wiadomości i nowości marketingowe.", | ||||
|   "edit_profile.hints.avatar": "PNG, GIF lub JPG. Maksymalnie 2 MB. Zostanie zmniejszony do 400x400px", | ||||
|   "edit_profile.hints.bot": "To konto podejmuje głównie zautomatyzowane działania i może nie być nadzorowane", | ||||
|   "edit_profile.hints.header": "PNG, GIF lub JPG. Maksymalnie 2 MB. Zostanie zmniejszony do 1500x500px", | ||||
|  | @ -306,6 +314,8 @@ | |||
|   "emoji_button.travel": "Podróże i miejsca", | ||||
|   "empty_column.account_timeline": "Brak wpisów tutaj!", | ||||
|   "empty_column.account_unavailable": "Profil niedostępny", | ||||
|   "empty_column.aliases": "Nie utworzyłeś(-aś) jeszcze żadnego aliasu konta.", | ||||
|   "empty_column.aliases.suggestions": "Brak propozycji kont dla podanej frazy.", | ||||
|   "empty_column.blocks": "Nie zablokowałeś(-aś) jeszcze żadnego użytkownika.", | ||||
|   "empty_column.bookmarks": "Nie masz jeszcze żadnej zakładki. Kiedy dodasz jakąś, pojawi się ona tutaj.", | ||||
|   "empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby zagaić!", | ||||
|  | @ -334,11 +344,14 @@ | |||
|   "federation_restriction.full_media_removal": "Pełne usunięcie mediów", | ||||
|   "federation_restriction.media_nsfw": "Załączniki oznaczone jako NSFW", | ||||
|   "federation_restriction.partial_media_removal": "Częściowe usunięcie mediów", | ||||
|   "federation_restrictions.empty_message": "{siteTitle} nie nakłada restrykcji na żadne instancje.", | ||||
|   "federation_restrictions.explanation_box.message": "Zwykle serwery w Fediwersum mogą swobodnie porozumiewać się. {siteTitle} nakłada pewne ograniczenia na następujące serwery.", | ||||
|   "federation_restrictions.explanation_box.title": "Zasady dla poszczególnych instancji", | ||||
|   "federation_restrictions.not_disclosed_message": "{siteTitle} nie udostępnia informacji o ograniczeniach federacji przez API.", | ||||
|   "fediverse_tab.explanation_box.dismiss": "Nie pokazuj ponownie", | ||||
|   "fediverse_tab.explanation_box.explanation": "{site_title} jest częścią Fediwersum, sieci społecznościowej na którą składają się tysiące niezależnie funkcjonujących stron (serwerów). Wpisy które tu widzisz pochodzą z serwerów podmiotów trzecich. Możesz do woli wchodzić z nimi w interakcje lub blokować serwery których nie lubisz. Zwracaj uwagę na pełną nazwę użytkownika po znaku @, aby wiedzieć z jakiego serwera pochodzi on. Aby widzieć tylko wpisy z {site_title}, odwiedź kartę {local}.", | ||||
|   "fediverse_tab.explanation_box.title": "Czym jest Fediverse?", | ||||
|   "filters.added": "Dodano filtr.", | ||||
|   "filters.context_header": "Konteksty filtru", | ||||
|   "filters.context_hint": "Jedno lub więcej miejsc, gdzie filtr powinien zostać zaaplikowany", | ||||
|   "filters.filters_list_context_label": "Konteksty filtru:", | ||||
|  | @ -348,12 +361,12 @@ | |||
|   "filters.filters_list_hide": "Ukrywaj", | ||||
|   "filters.filters_list_phrase_label": "Słowo kluczowe lub fraza:", | ||||
|   "filters.filters_list_whole-word": "Całe słowo", | ||||
|   "filters.added": "Dodano filtr.", | ||||
|   "filters.deleted": "Usunięto filtr.", | ||||
|   "filters.removed": "Usunięto filtr.", | ||||
|   "follow_request.authorize": "Autoryzuj", | ||||
|   "follow_request.reject": "Odrzuć", | ||||
|   "forms.copy": "Kopiuj", | ||||
|   "getting_started.open_source_notice": "{code_name} jest oprogramowaniem o otwartym źródle. Możesz pomóc w rozwoju lub zgłaszać błędy na GitLabie tutaj: {code_link} (v{code_version}).", | ||||
|   "group.detail.archived_group": "Zarchiwizowana grupa", | ||||
|   "group.members.empty": "Ta grupa nie ma żadnych członków.", | ||||
|   "group.removed_accounts.empty": "Ta grupa nie ma żadnych usuniętych kont.", | ||||
|   "groups.card.join": "Dołącz", | ||||
|  | @ -362,13 +375,21 @@ | |||
|   "groups.card.roles.member": "Jesteś członkiem", | ||||
|   "groups.card.view": "Zobacz", | ||||
|   "groups.create": "Utwórz grupę", | ||||
|   "groups.detail.role_admin": "Jesteś administratorem", | ||||
|   "groups.edit": "Edytuj", | ||||
|   "groups.form.coverImage": "Wyślij obraz baneru (nieobowiązkowe)", | ||||
|   "groups.form.coverImageChange": "Wybrano obraz baneru", | ||||
|   "groups.form.create": "Utwórz grupę", | ||||
|   "groups.form.description": "Opis", | ||||
|   "groups.form.title": "Tytuł", | ||||
|   "groups.form.update": "Aktualizuj grupę", | ||||
|   "groups.join": "Dołącz do grupy", | ||||
|   "groups.leave": "Opuść grupę", | ||||
|   "groups.removed_accounts": "Usunięte konta", | ||||
|   "groups.sidebar-panel.item.no_recent_activity": "Brak ostatniej aktywności", | ||||
|   "groups.sidebar-panel.item.view": "nowe wpisy", | ||||
|   "groups.sidebar-panel.show_all": "Pokaż wszystkie", | ||||
|   "groups.sidebar-panel.title": "Grupy do których należysz", | ||||
|   "groups.tab_admin": "Zarządzaj", | ||||
|   "groups.tab_featured": "Wyróżnione", | ||||
|   "groups.tab_member": "Członek", | ||||
|  | @ -407,9 +428,7 @@ | |||
|   "keyboard_shortcuts.back": "aby cofnąć się", | ||||
|   "keyboard_shortcuts.blocked": "aby przejść do listy zablokowanych użytkowników", | ||||
|   "keyboard_shortcuts.boost": "aby podbić wpis", | ||||
|   "keyboard_shortcuts.column": "aby przejść do wpisu z jednej z kolumn", | ||||
|   "keyboard_shortcuts.compose": "aby przejść do pola tworzenia wpisu", | ||||
|   "keyboard_shortcuts.direct": "aby otworzyć kolumnę wiadomości bezpośrednich", | ||||
|   "keyboard_shortcuts.down": "aby przejść na dół listy", | ||||
|   "keyboard_shortcuts.enter": "aby otworzyć wpis", | ||||
|   "keyboard_shortcuts.favourite": "aby dodać do ulubionych", | ||||
|  | @ -428,7 +447,6 @@ | |||
|   "keyboard_shortcuts.reply": "aby odpowiedzieć", | ||||
|   "keyboard_shortcuts.requests": "aby przejść do listy próśb o możliwość śledzenia", | ||||
|   "keyboard_shortcuts.search": "aby przejść do pola wyszukiwania", | ||||
|   "keyboard_shortcuts.start": "aby otworzyć kolumnę „Rozpocznij”", | ||||
|   "keyboard_shortcuts.toggle_hidden": "aby wyświetlić lub ukryć wpis spod CW", | ||||
|   "keyboard_shortcuts.toggle_sensitivity": "by pokazać/ukryć multimedia", | ||||
|   "keyboard_shortcuts.toot": "aby utworzyć nowy wpis", | ||||
|  | @ -484,8 +502,10 @@ | |||
|   "morefollows.followers_label": "…i {count} więcej {count, plural, one {obserwujący(-a)} few {obserwujących} many {obserwujących} other {obserwujących}} na zdalnych stronach.", | ||||
|   "morefollows.following_label": "…i {count} więcej {count, plural, one {obserwowany(-a)} few {obserwowanych} many {obserwowanych} other {obserwowanych}} na zdalnych stronach.", | ||||
|   "mute_modal.hide_notifications": "Chcesz ukryć powiadomienia od tego użytkownika?", | ||||
|   "navigation_bar.account_aliases": "Aliasy kont", | ||||
|   "navigation_bar.admin_settings": "Ustawienia administracyjne", | ||||
|   "navigation_bar.blocks": "Zablokowani użytkownicy", | ||||
|   "navigation_bar.bookmarks": "Zakładki", | ||||
|   "navigation_bar.compose": "Utwórz nowy wpis", | ||||
|   "navigation_bar.domain_blocks": "Ukryte domeny", | ||||
|   "navigation_bar.favourites": "Ulubione", | ||||
|  | @ -494,7 +514,9 @@ | |||
|   "navigation_bar.import_data": "Importuj dane", | ||||
|   "navigation_bar.info": "Szczegółowe informacje", | ||||
|   "navigation_bar.keyboard_shortcuts": "Skróty klawiszowe", | ||||
|   "navigation_bar.lists": "Lists", | ||||
|   "navigation_bar.logout": "Wyloguj", | ||||
|   "navigation_bar.messages": "Messages", | ||||
|   "navigation_bar.mutes": "Wyciszeni użytkownicy", | ||||
|   "navigation_bar.pins": "Przypięte wpisy", | ||||
|   "navigation_bar.preferences": "Preferencje", | ||||
|  | @ -613,6 +635,7 @@ | |||
|   "relative_time.minutes": "{number} min.", | ||||
|   "relative_time.seconds": "{number} s.", | ||||
|   "remote_instance.edit_federation": "Edytuj federację", | ||||
|   "remote_instance.federation_panel.heading": "Federation Restrictions", | ||||
|   "remote_instance.federation_panel.no_restrictions_message": "{siteTitle} nie nakłada ograniczeń na {host}.", | ||||
|   "remote_instance.federation_panel.restricted_message": "{siteTitle} blokuje wszystkie aktywności z {host}.", | ||||
|   "remote_instance.federation_panel.some_restrictions_message": "{siteTitle} nakłada pewne ograniczenia na {host}.", | ||||
|  | @ -641,8 +664,6 @@ | |||
|   "search_results.hashtags": "Hashtagi", | ||||
|   "search_results.statuses": "Wpisy", | ||||
|   "search_results.top": "Góra", | ||||
|   "search_results.total": "{count, number} {count, plural, one {wynik} few {wyniki} many {wyników} more {wyników}}", | ||||
|   "search_results.total.has_more": "{count, number} Ponad {count, plural, one {wynik} few {wyniki} many {wyników} more {wyników}}", | ||||
|   "security.codes.fail": "Nie udało się uzyskać zapasowych kodów", | ||||
|   "security.confirm.fail": "Nieprawidłowy kod lub hasło. Spróbuj ponownie.", | ||||
|   "security.delete_account.fail": "Nie udało się usunąć konta.", | ||||
|  | @ -788,7 +809,6 @@ | |||
|   "upload_error.limit": "Przekroczono limit plików do wysłania.", | ||||
|   "upload_error.poll": "Dołączanie plików nie dozwolone z głosowaniami.", | ||||
|   "upload_form.description": "Wprowadź opis dla niewidomych i niedowidzących", | ||||
|   "upload_form.focus": "Zmień podgląd", | ||||
|   "upload_form.preview": "Podgląd", | ||||
|   "upload_form.undo": "Usuń", | ||||
|   "upload_progress.label": "Wysyłanie…", | ||||
|  |  | |||
|  | @ -0,0 +1,35 @@ | |||
| import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; | ||||
| import { | ||||
|   ALIASES_SUGGESTIONS_READY, | ||||
|   ALIASES_SUGGESTIONS_CLEAR, | ||||
|   ALIASES_SUGGESTIONS_CHANGE, | ||||
| } from '../actions/aliases'; | ||||
| 
 | ||||
| const initialState = ImmutableMap({ | ||||
|   suggestions: ImmutableMap({ | ||||
|     value: '', | ||||
|     loaded: false, | ||||
|     items: ImmutableList(), | ||||
|   }), | ||||
| }); | ||||
| 
 | ||||
| export default function aliasesReducer(state = initialState, action) { | ||||
|   switch(action.type) { | ||||
|   case ALIASES_SUGGESTIONS_CHANGE: | ||||
|     return state | ||||
|       .setIn(['suggestions', 'value'], action.value) | ||||
|       .setIn(['suggestions', 'loaded'], false); | ||||
|   case ALIASES_SUGGESTIONS_READY: | ||||
|     return state | ||||
|       .setIn(['suggestions', 'items'], ImmutableList(action.accounts.map(item => item.id))) | ||||
|       .setIn(['suggestions', 'loaded'], true); | ||||
|   case ALIASES_SUGGESTIONS_CLEAR: | ||||
|     return state.update('suggestions', suggestions => suggestions.withMutations(map => { | ||||
|       map.set('items', ImmutableList()); | ||||
|       map.set('value', ''); | ||||
|       map.set('loaded', false); | ||||
|     })); | ||||
|   default: | ||||
|     return state; | ||||
|   } | ||||
| }; | ||||
|  | @ -53,6 +53,7 @@ import backups from './backups'; | |||
| import admin_log from './admin_log'; | ||||
| import security from './security'; | ||||
| import scheduled_statuses from './scheduled_statuses'; | ||||
| import aliases from './aliases'; | ||||
| 
 | ||||
| const appReducer = combineReducers({ | ||||
|   dropdown_menu, | ||||
|  | @ -107,6 +108,7 @@ const appReducer = combineReducers({ | |||
|   admin_log, | ||||
|   security, | ||||
|   scheduled_statuses, | ||||
|   aliases, | ||||
| }); | ||||
| 
 | ||||
| // Clear the state (mostly) when the user logs out
 | ||||
|  |  | |||
|  | @ -84,6 +84,7 @@ | |||
| @import 'components/datepicker'; | ||||
| @import 'components/remote-timeline'; | ||||
| @import 'components/federation-restrictions'; | ||||
| @import 'components/aliases'; | ||||
| 
 | ||||
| // Holiday | ||||
| @import 'holiday/halloween'; | ||||
|  |  | |||
|  | @ -0,0 +1,107 @@ | |||
| .aliases { | ||||
|   &__accounts { | ||||
|     overflow-y: auto; | ||||
| 
 | ||||
|     .account__display-name { | ||||
|       &:hover strong { | ||||
|         text-decoration: none; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .account__avatar { | ||||
|       cursor: default; | ||||
|     } | ||||
| 
 | ||||
|     &.empty-column-indicator { | ||||
|       min-height: unset; | ||||
|       overflow-y: unset; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &_search { | ||||
|     display: flex; | ||||
|     flex-direction: row; | ||||
|     margin: 10px; | ||||
| 
 | ||||
|     .search__input { | ||||
|       padding: 7px 30px 6px 10px; | ||||
|     } | ||||
| 
 | ||||
|     > label { | ||||
|       flex: 1 1; | ||||
|     } | ||||
| 
 | ||||
|     > .search__icon .fa { | ||||
|       top: 8px; | ||||
|       right: 102px !important; | ||||
|     } | ||||
| 
 | ||||
|     > .button { | ||||
|       width: 80px; | ||||
|       margin-left: 10px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .aliases-settings-panel { | ||||
|   flex: 1; | ||||
| 
 | ||||
|   .item-list article { | ||||
|     border-bottom: 1px solid var(--primary-text-color--faint); | ||||
| 
 | ||||
|     &:last-child { | ||||
|       border-bottom: 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .alias__container { | ||||
|     padding: 20px; | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     font-size: 14px; | ||||
| 
 | ||||
|     span.alias__list-label { | ||||
|       padding-right: 5px; | ||||
|       color: var(--primary-text-color--faint); | ||||
|     } | ||||
| 
 | ||||
|     span.alias__list-value span { | ||||
|       padding-right: 5px; | ||||
|       text-transform: capitalize; | ||||
| 
 | ||||
|       &::after { | ||||
|         content: ','; | ||||
|       } | ||||
| 
 | ||||
|       &:last-of-type { | ||||
|         &::after { | ||||
|           content: ''; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .alias__delete { | ||||
|       display: flex; | ||||
|       align-items: baseline; | ||||
|       cursor: pointer; | ||||
| 
 | ||||
|       span.alias__delete-label { | ||||
|         color: var(--primary-text-color--faint); | ||||
|         font-size: 14px; | ||||
|         font-weight: 800; | ||||
|       } | ||||
| 
 | ||||
|       .alias__delete-icon { | ||||
|         background: none; | ||||
|         color: var(--primary-text-color--faint); | ||||
|         padding: 0 5px; | ||||
|         margin: 0 auto; | ||||
|         font-size: 16px; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .slist--flex { | ||||
|     height: 100%; | ||||
|   } | ||||
| } | ||||
		Ładowanie…
	
		Reference in New Issue
	
	 Alex Gleason
						Alex Gleason