mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-15 00:38:27 +00:00
Merge commit 'bd575a1dd69d87ca0f69873f7badf28d38e8b9ed' into glitch-soc/merge-upstream
This commit is contained in:
@@ -661,3 +661,18 @@ export function unpinAccountFail(error) {
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export const updateAccount = ({ displayName, note, avatar, header, discoverable, indexable }) => (dispatch, getState) => {
|
||||
const data = new FormData();
|
||||
|
||||
data.append('display_name', displayName);
|
||||
data.append('note', note);
|
||||
if (avatar) data.append('avatar', avatar);
|
||||
if (header) data.append('header', header);
|
||||
data.append('discoverable', discoverable);
|
||||
data.append('indexable', indexable);
|
||||
|
||||
return api(getState).patch('/api/v1/accounts/update_credentials', data).then(response => {
|
||||
dispatch(importFetchedAccount(response.data));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface ApiAccountJSON {
|
||||
bot: boolean;
|
||||
created_at: string;
|
||||
discoverable: boolean;
|
||||
indexable: boolean;
|
||||
display_name: string;
|
||||
emojis: ApiCustomEmojiJSON[];
|
||||
fields: ApiAccountFieldJSON[];
|
||||
|
||||
@@ -51,7 +51,7 @@ export default class Retention extends PureComponent {
|
||||
let content;
|
||||
|
||||
if (loading) {
|
||||
content = <FormattedMessage id='loading_indicator.label' defaultMessage='Loading...' />;
|
||||
content = <FormattedMessage id='loading_indicator.label' defaultMessage='Loading…' />;
|
||||
} else {
|
||||
content = (
|
||||
<table className='retention__table'>
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import { CircularProgress } from './circular_progress';
|
||||
|
||||
export const LoadingIndicator: React.FC = () => (
|
||||
<div className='loading-indicator'>
|
||||
<CircularProgress size={50} strokeWidth={6} />
|
||||
</div>
|
||||
);
|
||||
const messages = defineMessages({
|
||||
loading: { id: 'loading_indicator.label', defaultMessage: 'Loading…' },
|
||||
});
|
||||
|
||||
export const LoadingIndicator: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<div
|
||||
className='loading-indicator'
|
||||
role='progressbar'
|
||||
aria-busy
|
||||
aria-live='polite'
|
||||
aria-label={intl.formatMessage(messages.loading)}
|
||||
>
|
||||
<CircularProgress size={50} strokeWidth={6} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { Fragment } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/done.svg';
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
|
||||
const ProgressIndicator = ({ steps, completed }) => (
|
||||
<div className='onboarding__progress-indicator'>
|
||||
{(new Array(steps)).fill().map((_, i) => (
|
||||
<Fragment key={i}>
|
||||
{i > 0 && <div className={classNames('onboarding__progress-indicator__line', { active: completed > i })} />}
|
||||
|
||||
<div className={classNames('onboarding__progress-indicator__step', { active: completed > i })}>
|
||||
{completed > i && <Icon icon={CheckIcon} />}
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
ProgressIndicator.propTypes = {
|
||||
steps: PropTypes.number.isRequired,
|
||||
completed: PropTypes.number,
|
||||
};
|
||||
|
||||
export default ProgressIndicator;
|
||||
@@ -1,11 +1,13 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { ReactComponent as ArrowRightAltIcon } from '@material-symbols/svg-600/outlined/arrow_right_alt.svg';
|
||||
import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/done.svg';
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
|
||||
const Step = ({ label, description, icon, iconComponent, completed, onClick, href }) => {
|
||||
export const Step = ({ label, description, icon, iconComponent, completed, onClick, href, to }) => {
|
||||
const content = (
|
||||
<>
|
||||
<div className='onboarding__steps__item__icon'>
|
||||
@@ -29,6 +31,12 @@ const Step = ({ label, description, icon, iconComponent, completed, onClick, hre
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
} else if (to) {
|
||||
return (
|
||||
<Link to={to} className='onboarding__steps__item'>
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -45,7 +53,6 @@ Step.propTypes = {
|
||||
iconComponent: PropTypes.func,
|
||||
completed: PropTypes.bool,
|
||||
href: PropTypes.string,
|
||||
to: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Step;
|
||||
|
||||
@@ -1,79 +1,62 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
|
||||
import { fetchSuggestions } from 'mastodon/actions/suggestions';
|
||||
import { markAsPartial } from 'mastodon/actions/timelines';
|
||||
import Column from 'mastodon/components/column';
|
||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
||||
import { EmptyAccount } from 'mastodon/components/empty_account';
|
||||
import Account from 'mastodon/containers/account_container';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
suggestions: state.getIn(['suggestions', 'items']),
|
||||
isLoading: state.getIn(['suggestions', 'isLoading']),
|
||||
});
|
||||
export const Follows = () => {
|
||||
const dispatch = useDispatch();
|
||||
const isLoading = useAppSelector(state => state.getIn(['suggestions', 'isLoading']));
|
||||
const suggestions = useAppSelector(state => state.getIn(['suggestions', 'items']));
|
||||
|
||||
class Follows extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
onBack: PropTypes.func,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
suggestions: ImmutablePropTypes.list,
|
||||
isLoading: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
const { dispatch } = this.props;
|
||||
useEffect(() => {
|
||||
dispatch(fetchSuggestions(true));
|
||||
|
||||
return () => {
|
||||
dispatch(markAsPartial('home'));
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
let loadedContent;
|
||||
|
||||
if (isLoading) {
|
||||
loadedContent = (new Array(8)).fill().map((_, i) => <EmptyAccount key={i} />);
|
||||
} else if (suggestions.isEmpty()) {
|
||||
loadedContent = <div className='follow-recommendations__empty'><FormattedMessage id='onboarding.follows.empty' defaultMessage='Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.' /></div>;
|
||||
} else {
|
||||
loadedContent = suggestions.map(suggestion => <Account id={suggestion.get('account')} key={suggestion.get('account')} withBio />);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(markAsPartial('home'));
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<ColumnBackButton />
|
||||
|
||||
render () {
|
||||
const { onBack, isLoading, suggestions } = this.props;
|
||||
|
||||
let loadedContent;
|
||||
|
||||
if (isLoading) {
|
||||
loadedContent = (new Array(8)).fill().map((_, i) => <EmptyAccount key={i} />);
|
||||
} else if (suggestions.isEmpty()) {
|
||||
loadedContent = <div className='follow-recommendations__empty'><FormattedMessage id='onboarding.follows.empty' defaultMessage='Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.' /></div>;
|
||||
} else {
|
||||
loadedContent = suggestions.map(suggestion => <Account id={suggestion.get('account')} key={suggestion.get('account')} withBio />);
|
||||
}
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton onClick={onBack} />
|
||||
|
||||
<div className='scrollable privacy-policy'>
|
||||
<div className='column-title'>
|
||||
<h3><FormattedMessage id='onboarding.follows.title' defaultMessage='Popular on Mastodon' /></h3>
|
||||
<p><FormattedMessage id='onboarding.follows.lead' defaultMessage='You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!' /></p>
|
||||
</div>
|
||||
|
||||
<div className='follow-recommendations'>
|
||||
{loadedContent}
|
||||
</div>
|
||||
|
||||
<p className='onboarding__lead'><FormattedMessage id='onboarding.tips.accounts_from_other_servers' defaultMessage='<strong>Did you know?</strong> Since Mastodon is decentralized, some profiles you come across will be hosted on servers other than yours. And yet you can interact with them seamlessly! Their server is in the second half of their username!' values={{ strong: chunks => <strong>{chunks}</strong> }} /></p>
|
||||
|
||||
<div className='onboarding__footer'>
|
||||
<button className='link-button' onClick={onBack}><FormattedMessage id='onboarding.actions.back' defaultMessage='Take me back' /></button>
|
||||
</div>
|
||||
<div className='scrollable privacy-policy'>
|
||||
<div className='column-title'>
|
||||
<h3><FormattedMessage id='onboarding.follows.title' defaultMessage='Popular on Mastodon' /></h3>
|
||||
<p><FormattedMessage id='onboarding.follows.lead' defaultMessage='You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!' /></p>
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
<div className='follow-recommendations'>
|
||||
{loadedContent}
|
||||
</div>
|
||||
|
||||
export default connect(mapStateToProps)(Follows);
|
||||
<p className='onboarding__lead'><FormattedMessage id='onboarding.tips.accounts_from_other_servers' defaultMessage='<strong>Did you know?</strong> Since Mastodon is decentralized, some profiles you come across will be hosted on servers other than yours. And yet you can interact with them seamlessly! Their server is in the second half of their username!' values={{ strong: chunks => <strong>{chunks}</strong> }} /></p>
|
||||
|
||||
<div className='onboarding__footer'>
|
||||
<Link className='link-button' to='/start'><FormattedMessage id='onboarding.actions.back' defaultMessage='Take me back' /></Link>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,152 +1,90 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
||||
import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
import { Link, Switch, Route, useHistory } from 'react-router-dom';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { ReactComponent as AccountCircleIcon } from '@material-symbols/svg-600/outlined/account_circle.svg';
|
||||
import { ReactComponent as ArrowRightAltIcon } from '@material-symbols/svg-600/outlined/arrow_right_alt.svg';
|
||||
import { ReactComponent as ContentCopyIcon } from '@material-symbols/svg-600/outlined/content_copy.svg';
|
||||
import { ReactComponent as EditNoteIcon } from '@material-symbols/svg-600/outlined/edit_note.svg';
|
||||
import { ReactComponent as PersonAddIcon } from '@material-symbols/svg-600/outlined/person_add.svg';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import illustration from 'mastodon/../images/elephant_ui_conversation.svg';
|
||||
import { fetchAccount } from 'mastodon/actions/accounts';
|
||||
import { focusCompose } from 'mastodon/actions/compose';
|
||||
import { closeOnboarding } from 'mastodon/actions/onboarding';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import Column from 'mastodon/features/ui/components/column';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { makeGetAccount } from 'mastodon/selectors';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
import { assetHost } from 'mastodon/utils/config';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import Step from './components/step';
|
||||
import Follows from './follows';
|
||||
import Share from './share';
|
||||
import { Step } from './components/step';
|
||||
import { Follows } from './follows';
|
||||
import { Profile } from './profile';
|
||||
import { Share } from './share';
|
||||
|
||||
const messages = defineMessages({
|
||||
template: { id: 'onboarding.compose.template', defaultMessage: 'Hello #Mastodon!' },
|
||||
});
|
||||
|
||||
const mapStateToProps = () => {
|
||||
const getAccount = makeGetAccount();
|
||||
const Onboarding = () => {
|
||||
const account = useAppSelector(state => state.getIn(['accounts', me]));
|
||||
const dispatch = useDispatch();
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
|
||||
return state => ({
|
||||
account: getAccount(state, me),
|
||||
});
|
||||
const handleComposeClick = useCallback(() => {
|
||||
dispatch(focusCompose(history, intl.formatMessage(messages.template)));
|
||||
}, [dispatch, intl, history]);
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<Switch>
|
||||
<Route path='/start' exact>
|
||||
<div className='scrollable privacy-policy'>
|
||||
<div className='column-title'>
|
||||
<img src={illustration} alt='' className='onboarding__illustration' />
|
||||
<h3><FormattedMessage id='onboarding.start.title' defaultMessage="You've made it!" /></h3>
|
||||
<p><FormattedMessage id='onboarding.start.lead' defaultMessage="Your new Mastodon account is ready to go. Here's how you can make the most of it:" /></p>
|
||||
</div>
|
||||
|
||||
<div className='onboarding__steps'>
|
||||
<Step to='/start/profile' completed={(!account.get('avatar').endsWith('missing.png')) || (account.get('display_name').length > 0 && account.get('note').length > 0)} icon='address-book-o' iconComponent={AccountCircleIcon} label={<FormattedMessage id='onboarding.steps.setup_profile.title' defaultMessage='Customize your profile' />} description={<FormattedMessage id='onboarding.steps.setup_profile.body' defaultMessage='Others are more likely to interact with you with a filled out profile.' />} />
|
||||
<Step to='/start/follows' completed={(account.get('following_count') * 1) >= 1} icon='user-plus' iconComponent={PersonAddIcon} label={<FormattedMessage id='onboarding.steps.follow_people.title' defaultMessage='Find at least {count, plural, one {one person} other {# people}} to follow' values={{ count: 7 }} />} description={<FormattedMessage id='onboarding.steps.follow_people.body' defaultMessage="You curate your own home feed. Let's fill it with interesting people." />} />
|
||||
<Step onClick={handleComposeClick} completed={(account.get('statuses_count') * 1) >= 1} icon='pencil-square-o' iconComponent={EditNoteIcon} label={<FormattedMessage id='onboarding.steps.publish_status.title' defaultMessage='Make your first post' />} description={<FormattedMessage id='onboarding.steps.publish_status.body' defaultMessage='Say hello to the world.' values={{ emoji: <img className='emojione' alt='🐘' src={`${assetHost}/emoji/1f418.svg`} /> }} />} />
|
||||
<Step to='/start/share' icon='copy' iconComponent={ContentCopyIcon} label={<FormattedMessage id='onboarding.steps.share_profile.title' defaultMessage='Share your profile' />} description={<FormattedMessage id='onboarding.steps.share_profile.body' defaultMessage='Let your friends know how to find you on Mastodon!' />} />
|
||||
</div>
|
||||
|
||||
<p className='onboarding__lead'><FormattedMessage id='onboarding.start.skip' defaultMessage="Don't need help getting started?" /></p>
|
||||
|
||||
<div className='onboarding__links'>
|
||||
<Link to='/explore' className='onboarding__link'>
|
||||
<FormattedMessage id='onboarding.actions.go_to_explore' defaultMessage='Take me to trending' />
|
||||
<Icon icon={ArrowRightAltIcon} />
|
||||
</Link>
|
||||
|
||||
<Link to='/home' className='onboarding__link'>
|
||||
<FormattedMessage id='onboarding.actions.go_to_home' defaultMessage='Take me to my home feed' />
|
||||
<Icon icon={ArrowRightAltIcon} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Route>
|
||||
|
||||
<Route path='/start/profile' component={Profile} />
|
||||
<Route path='/start/follows' component={Follows} />
|
||||
<Route path='/start/share' component={Share} />
|
||||
</Switch>
|
||||
|
||||
<Helmet>
|
||||
<meta name='robots' content='noindex' />
|
||||
</Helmet>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
class Onboarding extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
account: ImmutablePropTypes.record,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
||||
state = {
|
||||
step: null,
|
||||
profileClicked: false,
|
||||
shareClicked: false,
|
||||
};
|
||||
|
||||
handleClose = () => {
|
||||
const { dispatch, history } = this.props;
|
||||
|
||||
dispatch(closeOnboarding());
|
||||
history.push('/home');
|
||||
};
|
||||
|
||||
handleProfileClick = () => {
|
||||
this.setState({ profileClicked: true });
|
||||
};
|
||||
|
||||
handleFollowClick = () => {
|
||||
this.setState({ step: 'follows' });
|
||||
};
|
||||
|
||||
handleComposeClick = () => {
|
||||
const { dispatch, intl, history } = this.props;
|
||||
|
||||
dispatch(focusCompose(history, intl.formatMessage(messages.template)));
|
||||
};
|
||||
|
||||
handleShareClick = () => {
|
||||
this.setState({ step: 'share', shareClicked: true });
|
||||
};
|
||||
|
||||
handleBackClick = () => {
|
||||
this.setState({ step: null });
|
||||
};
|
||||
|
||||
handleWindowFocus = debounce(() => {
|
||||
const { dispatch, account } = this.props;
|
||||
dispatch(fetchAccount(account.get('id')));
|
||||
}, 1000, { trailing: true });
|
||||
|
||||
componentDidMount () {
|
||||
window.addEventListener('focus', this.handleWindowFocus, false);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('focus', this.handleWindowFocus);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account } = this.props;
|
||||
const { step, shareClicked } = this.state;
|
||||
|
||||
switch(step) {
|
||||
case 'follows':
|
||||
return <Follows onBack={this.handleBackClick} />;
|
||||
case 'share':
|
||||
return <Share onBack={this.handleBackClick} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<div className='scrollable privacy-policy'>
|
||||
<div className='column-title'>
|
||||
<img src={illustration} alt='' className='onboarding__illustration' />
|
||||
<h3><FormattedMessage id='onboarding.start.title' defaultMessage="You've made it!" /></h3>
|
||||
<p><FormattedMessage id='onboarding.start.lead' defaultMessage="Your new Mastodon account is ready to go. Here's how you can make the most of it:" /></p>
|
||||
</div>
|
||||
|
||||
<div className='onboarding__steps'>
|
||||
<Step onClick={this.handleProfileClick} href='/settings/profile' completed={(!account.get('avatar').endsWith('missing.png')) || (account.get('display_name').length > 0 && account.get('note').length > 0)} icon='address-book-o' iconComponent={AccountCircleIcon} label={<FormattedMessage id='onboarding.steps.setup_profile.title' defaultMessage='Customize your profile' />} description={<FormattedMessage id='onboarding.steps.setup_profile.body' defaultMessage='Others are more likely to interact with you with a filled out profile.' />} />
|
||||
<Step onClick={this.handleFollowClick} completed={(account.get('following_count') * 1) >= 7} icon='user-plus' iconComponent={PersonAddIcon} label={<FormattedMessage id='onboarding.steps.follow_people.title' defaultMessage='Find at least {count, plural, one {one person} other {# people}} to follow' values={{ count: 7 }} />} description={<FormattedMessage id='onboarding.steps.follow_people.body' defaultMessage="You curate your own home feed. Let's fill it with interesting people." />} />
|
||||
<Step onClick={this.handleComposeClick} completed={(account.get('statuses_count') * 1) >= 1} icon='pencil-square-o' iconComponent={EditNoteIcon} label={<FormattedMessage id='onboarding.steps.publish_status.title' defaultMessage='Make your first post' />} description={<FormattedMessage id='onboarding.steps.publish_status.body' defaultMessage='Say hello to the world.' values={{ emoji: <img className='emojione' alt='🐘' src={`${assetHost}/emoji/1f418.svg`} /> }} />} />
|
||||
<Step onClick={this.handleShareClick} completed={shareClicked} icon='copy' iconComponent={ContentCopyIcon} label={<FormattedMessage id='onboarding.steps.share_profile.title' defaultMessage='Share your profile' />} description={<FormattedMessage id='onboarding.steps.share_profile.body' defaultMessage='Let your friends know how to find you on Mastodon!' />} />
|
||||
</div>
|
||||
|
||||
<p className='onboarding__lead'><FormattedMessage id='onboarding.start.skip' defaultMessage="Don't need help getting started?" /></p>
|
||||
|
||||
<div className='onboarding__links'>
|
||||
<Link to='/explore' className='onboarding__link'>
|
||||
<FormattedMessage id='onboarding.actions.go_to_explore' defaultMessage='Take me to trending' />
|
||||
<Icon icon={ArrowRightAltIcon} />
|
||||
</Link>
|
||||
|
||||
<Link to='/home' className='onboarding__link'>
|
||||
<FormattedMessage id='onboarding.actions.go_to_home' defaultMessage='Take me to my home feed' />
|
||||
<Icon icon={ArrowRightAltIcon} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Helmet>
|
||||
<meta name='robots' content='noindex' />
|
||||
</Helmet>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withRouter(connect(mapStateToProps)(injectIntl(Onboarding)));
|
||||
export default Onboarding;
|
||||
|
||||
162
app/javascript/mastodon/features/onboarding/profile.jsx
Normal file
162
app/javascript/mastodon/features/onboarding/profile.jsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import { useState, useMemo, useCallback, createRef } from 'react';
|
||||
|
||||
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
|
||||
import { ReactComponent as AddPhotoAlternateIcon } from '@material-symbols/svg-600/outlined/add_photo_alternate.svg';
|
||||
import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg';
|
||||
import Toggle from 'react-toggle';
|
||||
|
||||
import { updateAccount } from 'mastodon/actions/accounts';
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
import { unescapeHTML } from 'mastodon/utils/html';
|
||||
|
||||
const messages = defineMessages({
|
||||
uploadHeader: { id: 'onboarding.profile.upload_header', defaultMessage: 'Upload profile header' },
|
||||
uploadAvatar: { id: 'onboarding.profile.upload_avatar', defaultMessage: 'Upload profile picture' },
|
||||
});
|
||||
|
||||
export const Profile = () => {
|
||||
const account = useAppSelector(state => state.getIn(['accounts', me]));
|
||||
const [displayName, setDisplayName] = useState(account.get('display_name'));
|
||||
const [note, setNote] = useState(unescapeHTML(account.get('note')));
|
||||
const [avatar, setAvatar] = useState(null);
|
||||
const [header, setHeader] = useState(null);
|
||||
const [discoverable, setDiscoverable] = useState(account.get('discoverable'));
|
||||
const [indexable, setIndexable] = useState(account.get('indexable'));
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [errors, setErrors] = useState();
|
||||
const avatarFileRef = createRef();
|
||||
const headerFileRef = createRef();
|
||||
const dispatch = useDispatch();
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
|
||||
const handleDisplayNameChange = useCallback(e => {
|
||||
setDisplayName(e.target.value);
|
||||
}, [setDisplayName]);
|
||||
|
||||
const handleNoteChange = useCallback(e => {
|
||||
setNote(e.target.value);
|
||||
}, [setNote]);
|
||||
|
||||
const handleDiscoverableChange = useCallback(e => {
|
||||
setDiscoverable(e.target.checked);
|
||||
}, [setDiscoverable]);
|
||||
|
||||
const handleIndexableChange = useCallback(e => {
|
||||
setIndexable(e.target.checked);
|
||||
}, [setIndexable]);
|
||||
|
||||
const handleAvatarChange = useCallback(e => {
|
||||
setAvatar(e.target?.files?.[0]);
|
||||
}, [setAvatar]);
|
||||
|
||||
const handleHeaderChange = useCallback(e => {
|
||||
setHeader(e.target?.files?.[0]);
|
||||
}, [setHeader]);
|
||||
|
||||
const avatarPreview = useMemo(() => avatar ? URL.createObjectURL(avatar) : account.get('avatar'), [avatar, account]);
|
||||
const headerPreview = useMemo(() => header ? URL.createObjectURL(header) : account.get('header'), [header, account]);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
setIsSaving(true);
|
||||
|
||||
dispatch(updateAccount({
|
||||
displayName,
|
||||
note,
|
||||
avatar,
|
||||
header,
|
||||
discoverable,
|
||||
indexable,
|
||||
})).then(() => history.push('/start/follows')).catch(err => {
|
||||
setIsSaving(false);
|
||||
setErrors(err.response.data.details);
|
||||
});
|
||||
}, [dispatch, displayName, note, avatar, header, discoverable, indexable, history]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ColumnBackButton />
|
||||
|
||||
<div className='scrollable privacy-policy'>
|
||||
<div className='column-title'>
|
||||
<h3><FormattedMessage id='onboarding.profile.title' defaultMessage='Profile setup' /></h3>
|
||||
<p><FormattedMessage id='onboarding.profile.lead' defaultMessage='You can always complete this later in the settings, where even more customization options are available.' /></p>
|
||||
</div>
|
||||
|
||||
<div className='simple_form'>
|
||||
<div className='onboarding__profile'>
|
||||
<label className={classNames('app-form__header-input', { selected: !!headerPreview, invalid: !!errors?.header })} title={intl.formatMessage(messages.uploadHeader)}>
|
||||
<input
|
||||
type='file'
|
||||
hidden
|
||||
ref={headerFileRef}
|
||||
accept='image/*'
|
||||
onChange={handleHeaderChange}
|
||||
/>
|
||||
|
||||
{headerPreview && <img src={headerPreview} alt='' />}
|
||||
|
||||
<Icon icon={headerPreview ? EditIcon : AddPhotoAlternateIcon} />
|
||||
</label>
|
||||
|
||||
<label className={classNames('app-form__avatar-input', { selected: !!avatarPreview, invalid: !!errors?.avatar })} title={intl.formatMessage(messages.uploadAvatar)}>
|
||||
<input
|
||||
type='file'
|
||||
hidden
|
||||
ref={avatarFileRef}
|
||||
accept='image/*'
|
||||
onChange={handleAvatarChange}
|
||||
/>
|
||||
|
||||
{avatarPreview && <img src={avatarPreview} alt='' />}
|
||||
|
||||
<Icon icon={avatarPreview ? EditIcon : AddPhotoAlternateIcon} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className={classNames('input with_block_label', { field_with_errors: !!errors?.display_name })}>
|
||||
<label htmlFor='display_name'><FormattedMessage id='onboarding.profile.display_name' defaultMessage='Display name' /></label>
|
||||
<span className='hint'><FormattedMessage id='onboarding.profile.display_name_hint' defaultMessage='Your full name or your fun name…' /></span>
|
||||
<div className='label_input'>
|
||||
<input id='display_name' type='text' value={displayName} onChange={handleDisplayNameChange} maxLength={30} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classNames('input with_block_label', { field_with_errors: !!errors?.note })}>
|
||||
<label htmlFor='note'><FormattedMessage id='onboarding.profile.note' defaultMessage='Bio' /></label>
|
||||
<span className='hint'><FormattedMessage id='onboarding.profile.note_hint' defaultMessage='You can @mention other people or #hashtags…' /></span>
|
||||
<div className='label_input'>
|
||||
<textarea id='note' value={note} onChange={handleNoteChange} maxLength={500} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label className='report-dialog-modal__toggle'>
|
||||
<Toggle checked={discoverable} onChange={handleDiscoverableChange} />
|
||||
<FormattedMessage id='onboarding.profile.discoverable' defaultMessage='Feature profile and posts in discovery algorithms' />
|
||||
</label>
|
||||
|
||||
<label className='report-dialog-modal__toggle'>
|
||||
<Toggle checked={indexable} onChange={handleIndexableChange} />
|
||||
<FormattedMessage id='onboarding.profile.indexable' defaultMessage='Include public posts in search results' />
|
||||
</label>
|
||||
|
||||
<div className='onboarding__footer'>
|
||||
<Button block onClick={handleSubmit} disabled={isSaving}>{isSaving ? <LoadingIndicator /> : <FormattedMessage id='onboarding.profile.save_and_continue' defaultMessage='Save and continue' />}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,31 +1,25 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { ReactComponent as ArrowRightAltIcon } from '@material-symbols/svg-600/outlined/arrow_right_alt.svg';
|
||||
import { ReactComponent as ContentCopyIcon } from '@material-symbols/svg-600/outlined/content_copy.svg';
|
||||
import SwipeableViews from 'react-swipeable-views';
|
||||
|
||||
import Column from 'mastodon/components/column';
|
||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { me, domain } from 'mastodon/initial_state';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
const messages = defineMessages({
|
||||
shareableMessage: { id: 'onboarding.share.message', defaultMessage: 'I\'m {username} on #Mastodon! Come follow me at {url}' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
account: state.getIn(['accounts', me]),
|
||||
});
|
||||
|
||||
class CopyPasteText extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
@@ -141,59 +135,47 @@ class TipCarousel extends PureComponent {
|
||||
|
||||
}
|
||||
|
||||
class Share extends PureComponent {
|
||||
export const Share = () => {
|
||||
const account = useAppSelector(state => state.getIn(['accounts', me]));
|
||||
const intl = useIntl();
|
||||
const url = (new URL(`/@${account.get('username')}`, document.baseURI)).href;
|
||||
|
||||
static propTypes = {
|
||||
onBack: PropTypes.func,
|
||||
account: ImmutablePropTypes.record,
|
||||
intl: PropTypes.object,
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<ColumnBackButton />
|
||||
|
||||
render () {
|
||||
const { onBack, account, intl } = this.props;
|
||||
|
||||
const url = (new URL(`/@${account.get('username')}`, document.baseURI)).href;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton onClick={onBack} />
|
||||
|
||||
<div className='scrollable privacy-policy'>
|
||||
<div className='column-title'>
|
||||
<h3><FormattedMessage id='onboarding.share.title' defaultMessage='Share your profile' /></h3>
|
||||
<p><FormattedMessage id='onboarding.share.lead' defaultMessage='Let people know how they can find you on Mastodon!' /></p>
|
||||
</div>
|
||||
|
||||
<CopyPasteText value={intl.formatMessage(messages.shareableMessage, { username: `@${account.get('username')}@${domain}`, url })} />
|
||||
|
||||
<TipCarousel>
|
||||
<div><p className='onboarding__lead'><FormattedMessage id='onboarding.tips.verification' defaultMessage='<strong>Did you know?</strong> You can verify your account by putting a link to your Mastodon profile on your own website and adding the website to your profile. No fees or documents necessary!' values={{ strong: chunks => <strong>{chunks}</strong> }} /></p></div>
|
||||
<div><p className='onboarding__lead'><FormattedMessage id='onboarding.tips.migration' defaultMessage='<strong>Did you know?</strong> If you feel like {domain} is not a great server choice for you in the future, you can move to another Mastodon server without losing your followers. You can even host your own server!' values={{ domain, strong: chunks => <strong>{chunks}</strong> }} /></p></div>
|
||||
<div><p className='onboarding__lead'><FormattedMessage id='onboarding.tips.2fa' defaultMessage='<strong>Did you know?</strong> You can secure your account by setting up two-factor authentication in your account settings. It works with any TOTP app of your choice, no phone number necessary!' values={{ strong: chunks => <strong>{chunks}</strong> }} /></p></div>
|
||||
</TipCarousel>
|
||||
|
||||
<p className='onboarding__lead'><FormattedMessage id='onboarding.share.next_steps' defaultMessage='Possible next steps:' /></p>
|
||||
|
||||
<div className='onboarding__links'>
|
||||
<Link to='/home' className='onboarding__link'>
|
||||
<FormattedMessage id='onboarding.actions.go_to_home' defaultMessage='Take me to my home feed' />
|
||||
<Icon icon={ArrowRightAltIcon} />
|
||||
</Link>
|
||||
|
||||
<Link to='/explore' className='onboarding__link'>
|
||||
<FormattedMessage id='onboarding.actions.go_to_explore' defaultMessage='Take me to trending' />
|
||||
<Icon icon={ArrowRightAltIcon} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className='onboarding__footer'>
|
||||
<button className='link-button' onClick={onBack}><FormattedMessage id='onboarding.action.back' defaultMessage='Take me back' /></button>
|
||||
</div>
|
||||
<div className='scrollable privacy-policy'>
|
||||
<div className='column-title'>
|
||||
<h3><FormattedMessage id='onboarding.share.title' defaultMessage='Share your profile' /></h3>
|
||||
<p><FormattedMessage id='onboarding.share.lead' defaultMessage='Let people know how they can find you on Mastodon!' /></p>
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
<CopyPasteText value={intl.formatMessage(messages.shareableMessage, { username: `@${account.get('username')}@${domain}`, url })} />
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(Share));
|
||||
<TipCarousel>
|
||||
<div><p className='onboarding__lead'><FormattedMessage id='onboarding.tips.verification' defaultMessage='<strong>Did you know?</strong> You can verify your account by putting a link to your Mastodon profile on your own website and adding the website to your profile. No fees or documents necessary!' values={{ strong: chunks => <strong>{chunks}</strong> }} /></p></div>
|
||||
<div><p className='onboarding__lead'><FormattedMessage id='onboarding.tips.migration' defaultMessage='<strong>Did you know?</strong> If you feel like {domain} is not a great server choice for you in the future, you can move to another Mastodon server without losing your followers. You can even host your own server!' values={{ domain, strong: chunks => <strong>{chunks}</strong> }} /></p></div>
|
||||
<div><p className='onboarding__lead'><FormattedMessage id='onboarding.tips.2fa' defaultMessage='<strong>Did you know?</strong> You can secure your account by setting up two-factor authentication in your account settings. It works with any TOTP app of your choice, no phone number necessary!' values={{ strong: chunks => <strong>{chunks}</strong> }} /></p></div>
|
||||
</TipCarousel>
|
||||
|
||||
<p className='onboarding__lead'><FormattedMessage id='onboarding.share.next_steps' defaultMessage='Possible next steps:' /></p>
|
||||
|
||||
<div className='onboarding__links'>
|
||||
<Link to='/home' className='onboarding__link'>
|
||||
<FormattedMessage id='onboarding.actions.go_to_home' defaultMessage='Take me to my home feed' />
|
||||
<Icon icon={ArrowRightAltIcon} />
|
||||
</Link>
|
||||
|
||||
<Link to='/explore' className='onboarding__link'>
|
||||
<FormattedMessage id='onboarding.actions.go_to_explore' defaultMessage='Take me to trending' />
|
||||
<Icon icon={ArrowRightAltIcon} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className='onboarding__footer'>
|
||||
<Link className='link-button' to='/start'><FormattedMessage id='onboarding.action.back' defaultMessage='Take me back' /></Link>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -166,7 +166,7 @@ class Footer extends ImmutablePureComponent {
|
||||
onClose();
|
||||
}
|
||||
|
||||
history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
||||
this.props.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
||||
};
|
||||
|
||||
render () {
|
||||
|
||||
@@ -210,7 +210,7 @@ class SwitchingColumnsArea extends PureComponent {
|
||||
<WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} />
|
||||
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
|
||||
|
||||
<WrappedRoute path='/start' exact component={Onboarding} content={children} />
|
||||
<WrappedRoute path='/start' component={Onboarding} content={children} />
|
||||
<WrappedRoute path='/directory' component={Directory} content={children} />
|
||||
<WrappedRoute path={['/explore', '/search']} component={Explore} content={children} />
|
||||
<WrappedRoute path={['/publish', '/statuses/new']} component={Compose} content={children} />
|
||||
|
||||
@@ -222,6 +222,7 @@
|
||||
"emoji_button.search_results": "Výsledky hledání",
|
||||
"emoji_button.symbols": "Symboly",
|
||||
"emoji_button.travel": "Cestování a místa",
|
||||
"empty_column.account_hides_collections": "Tento uživatel se rozhodl nezveřejňovat tuto informaci",
|
||||
"empty_column.account_suspended": "Účet je pozastaven",
|
||||
"empty_column.account_timeline": "Nejsou tu žádné příspěvky!",
|
||||
"empty_column.account_unavailable": "Profil není dostupný",
|
||||
|
||||
@@ -390,7 +390,7 @@
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.subheading": "Your lists",
|
||||
"load_pending": "{count, plural, one {# new item} other {# new items}}",
|
||||
"loading_indicator.label": "Loading...",
|
||||
"loading_indicator.label": "Loading…",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Hide image} other {Hide images}}",
|
||||
"moved_to_account_banner.text": "Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.",
|
||||
"mute_modal.duration": "Duration",
|
||||
@@ -479,6 +479,17 @@
|
||||
"onboarding.follows.empty": "Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.",
|
||||
"onboarding.follows.lead": "Your home feed is the primary way to experience Mastodon. The more people you follow, the more active and interesting it will be. To get you started, here are some suggestions:",
|
||||
"onboarding.follows.title": "Personalize your home feed",
|
||||
"onboarding.profile.discoverable": "Feature profile and posts in discovery algorithms",
|
||||
"onboarding.profile.display_name": "Display name",
|
||||
"onboarding.profile.display_name_hint": "Your full name or your fun name…",
|
||||
"onboarding.profile.indexable": "Include public posts in search results",
|
||||
"onboarding.profile.lead": "You can always complete this later in the settings, where even more customization options are available.",
|
||||
"onboarding.profile.note": "Bio",
|
||||
"onboarding.profile.note_hint": "You can @mention other people or #hashtags…",
|
||||
"onboarding.profile.save_and_continue": "Save and continue",
|
||||
"onboarding.profile.title": "Profile setup",
|
||||
"onboarding.profile.upload_avatar": "Upload profile picture",
|
||||
"onboarding.profile.upload_header": "Upload profile header",
|
||||
"onboarding.share.lead": "Let people know how they can find you on Mastodon!",
|
||||
"onboarding.share.message": "I'm {username} on #Mastodon! Come follow me at {url}",
|
||||
"onboarding.share.next_steps": "Possible next steps:",
|
||||
|
||||
@@ -389,7 +389,7 @@
|
||||
"lists.replies_policy.title": "Erakutsi erantzunak:",
|
||||
"lists.search": "Bilatu jarraitzen dituzun pertsonen artean",
|
||||
"lists.subheading": "Zure zerrendak",
|
||||
"load_pending": "{count, plural, one {eleentuberri #} other {# elementu berri}}",
|
||||
"load_pending": "{count, plural, one {elementu berri #} other {# elementu berri}}",
|
||||
"loading_indicator.label": "Kargatzen...",
|
||||
"media_gallery.toggle_visible": "Txandakatu ikusgaitasuna",
|
||||
"moved_to_account_banner.text": "Zure {disabledAccount} kontua desgaituta dago une honetan, {movedToAccount} kontura aldatu zinelako.",
|
||||
|
||||
@@ -252,7 +252,7 @@
|
||||
"explore.search_results": "Risultati della ricerca",
|
||||
"explore.suggested_follows": "Persone",
|
||||
"explore.title": "Esplora",
|
||||
"explore.trending_links": "Novità",
|
||||
"explore.trending_links": "Notizie",
|
||||
"explore.trending_statuses": "Post",
|
||||
"explore.trending_tags": "Hashtag",
|
||||
"filter_modal.added.context_mismatch_explanation": "La categoria di questo filtro non si applica al contesto in cui hai acceduto a questo post. Se desideri che il post sia filtrato anche in questo contesto, dovrai modificare il filtro.",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"about.blocks": "Prižiūrimi serveriai",
|
||||
"about.contact": "Kontaktuoti:",
|
||||
"about.disclaimer": "Mastodon – nemokama atvirojo šaltinio programa ir Mastodon gGmbH prekės ženklas.",
|
||||
"about.disclaimer": "Mastodon – nemokama atvirojo kodo programa ir Mastodon gGmbH prekės ženklas.",
|
||||
"about.domain_blocks.no_reason_available": "Priežastis nežinoma",
|
||||
"about.domain_blocks.preamble": "Mastodon paprastai leidžia peržiūrėti turinį ir bendrauti su naudotojais iš bet kurio kito fediverse esančio serverio. Šios yra išimtys, kurios buvo padarytos šiame konkrečiame serveryje.",
|
||||
"about.domain_blocks.silenced.explanation": "Paprastai nematysi profilių ir turinio iš šio serverio, nebent jį aiškiai ieškosi arba pasirinksi jį sekdamas (-a).",
|
||||
@@ -33,28 +33,46 @@
|
||||
"account.followers.empty": "Šio naudotojo dar niekas neseka.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} sekėjas (-a)} few {{counter} sekėjai} many {{counter} sekėjo} other {{counter} sekėjų}}",
|
||||
"account.following": "Seka",
|
||||
"account.follows.empty": "Šis naudotojas (-a) dar nieko neseka.",
|
||||
"account.follows.empty": "Šis (-i) naudotojas (-a) dar nieko neseka.",
|
||||
"account.follows_you": "Seka tave",
|
||||
"account.go_to_profile": "Eiti į profilį",
|
||||
"account.in_memoriam": "Atminimui.",
|
||||
"account.joined_short": "Prisijungė",
|
||||
"account.languages": "Keisti prenumeruojamas kalbas",
|
||||
"account.link_verified_on": "Šios nuorodos nuosavybė buvo patikrinta {date}",
|
||||
"account.locked_info": "Šios paskyros privatumo būsena nustatyta kaip užrakinta. Savininkas (-ė) rankiniu būdu peržiūri, kas gali sekti.",
|
||||
"account.media": "Medija",
|
||||
"account.mention": "Paminėti @{name}",
|
||||
"account.moved_to": "{name} nurodė, kad dabar jų nauja paskyra yra:",
|
||||
"account.mute": "Užtildyti @{name}",
|
||||
"account.mute_notifications_short": "Nutildyti pranešimus",
|
||||
"account.mute_short": "Nutildyti",
|
||||
"account.muted": "Užtildytas",
|
||||
"account.posts": "Toots",
|
||||
"account.posts_with_replies": "Toots and replies",
|
||||
"account.report": "Pranešti apie @{name}",
|
||||
"account.requested": "Awaiting approval",
|
||||
"account.no_bio": "Nėra pateikto aprašymo.",
|
||||
"account.open_original_page": "Atidaryti originalinį tinklalapį",
|
||||
"account.posts": "Įrašai",
|
||||
"account.posts_with_replies": "Įrašai ir atsakymai",
|
||||
"account.report": "Pranešti @{name}",
|
||||
"account.requested": "Laukiama patvirtinimo. Spausk, kad atšaukti sekimo užklausą.",
|
||||
"account.requested_follow": "{name} paprašė tave sekti",
|
||||
"account.share": "Bendrinti @{name} profilį",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unblock_short": "Atblokuoti",
|
||||
"account.unfollow": "Nebesekti",
|
||||
"account.unmute": "Atitildyti @{name}",
|
||||
"account.unmute_notifications_short": "Atitildyti pranešimus",
|
||||
"account.unmute_short": "Atitildyti",
|
||||
"account_note.placeholder": "Click to add a note",
|
||||
"account_note.placeholder": "Spausk norėdamas (-a) pridėti pastabą",
|
||||
"admin.dashboard.retention.average": "Vidurkis",
|
||||
"admin.dashboard.retention.cohort": "Registravimo mėnuo",
|
||||
"admin.dashboard.retention.cohort_size": "Nauji naudotojai",
|
||||
"admin.impact_report.instance_accounts": "Paskyrų profiliai, kuriuos tai ištrintų",
|
||||
"admin.impact_report.instance_followers": "Sekėjai, kuriuos prarastų mūsų naudotojai",
|
||||
"admin.impact_report.instance_follows": "Sekėjai, kuriuos prarastų jų naudotojai",
|
||||
"admin.impact_report.title": "Poveikio apibendrinimas",
|
||||
"alert.rate_limited.message": "Pabandyk vėliau po {retry_time, time, medium}.",
|
||||
"alert.rate_limited.title": "Spartos ribojimas",
|
||||
"alert.unexpected.message": "Įvyko netikėta klaida.",
|
||||
"alert.unexpected.title": "Ups!",
|
||||
"announcement.announcement": "Skelbimas",
|
||||
@@ -65,6 +83,14 @@
|
||||
"bundle_column_error.copy_stacktrace": "Kopijuoti klaidos ataskaitą",
|
||||
"bundle_column_error.error.body": "Užklausos puslapio nepavyko atvaizduoti. Tai gali būti dėl mūsų kodo klaidos arba naršyklės suderinamumo problemos.",
|
||||
"bundle_column_error.error.title": "O, ne!",
|
||||
"bundle_column_error.network.body": "Bandant užkrauti šį puslapį įvyko klaida. Tai galėjo atsitikti dėl laikinos tavo interneto ryšio arba šio serverio problemos.",
|
||||
"bundle_column_error.network.title": "Tinklo klaida",
|
||||
"bundle_column_error.retry": "Bandyti dar kartą",
|
||||
"bundle_column_error.return": "Grįžti į pradžią",
|
||||
"bundle_column_error.routing.body": "Prašyto puslapio nepavyko rasti. Ar esi tikras (-a), kad adreso juostoje nurodytas URL adresas yra teisingas?",
|
||||
"bundle_column_error.routing.title": "404",
|
||||
"bundle_modal_error.close": "Uždaryti",
|
||||
"closed_registrations_modal.find_another_server": "Rasti kitą serverį",
|
||||
"column.domain_blocks": "Hidden domains",
|
||||
"column.lists": "Sąrašai",
|
||||
"column.mutes": "Užtildyti vartotojai",
|
||||
@@ -81,18 +107,32 @@
|
||||
"compose.published.body": "Įrašas paskelbtas.",
|
||||
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
|
||||
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
|
||||
"compose_form.placeholder": "What is on your mind?",
|
||||
"compose_form.placeholder": "Kas tavo mintyse?",
|
||||
"compose_form.poll.add_option": "Pridėti pasirinkimą",
|
||||
"compose_form.poll.duration": "Apklausos trukmė",
|
||||
"compose_form.poll.option_placeholder": "Pasirinkimas {number}",
|
||||
"compose_form.poll.remove_option": "Pašalinti šį pasirinkimą",
|
||||
"compose_form.poll.switch_to_multiple": "Keisti apklausą, kad būtų galima pasirinkti kelis pasirinkimus",
|
||||
"compose_form.publish_form": "Publish",
|
||||
"compose_form.sensitive.hide": "{count, plural, one {Žymėti mediją kaip jautrią} few {Žymėti medijas kaip jautrias} many {Žymėti medijos kaip jautrios} other {Žymėti medijų kaip jautrių}}",
|
||||
"compose_form.sensitive.marked": "{count, plural, one {Medija pažymėta kaip jautri} few {Medijos pažymėtos kaip jautrios} many {Medijos pažymėta kaip jautrios} other {Medijų pažymėtos kaip jautrios}}",
|
||||
"compose_form.sensitive.unmarked": "{count, plural, one {Medija nepažymėta kaip jautri} few {Medijos nepažymėtos kaip jautrios} many {Medijos nepažymėta kaip jautri} other {Medijų nepažymėta kaip jautrios}}",
|
||||
"compose_form.spoiler.marked": "Text is hidden behind warning",
|
||||
"compose_form.spoiler.unmarked": "Text is not hidden",
|
||||
"compose_form.spoiler.unmarked": "Pridėti turinio įspėjimą",
|
||||
"compose_form.spoiler_placeholder": "Rašyk savo įspėjimą čia",
|
||||
"confirmation_modal.cancel": "Atšaukti",
|
||||
"confirmations.block.block_and_report": "Blokuoti ir pranešti",
|
||||
"confirmations.block.confirm": "Blokuoti",
|
||||
"confirmations.block.message": "Ar tikrai nori užblokuoti {name}?",
|
||||
"confirmations.delete.confirm": "Ištrinti",
|
||||
"confirmations.delete.message": "Are you sure you want to delete this status?",
|
||||
"confirmations.discard_edit_media.confirm": "Atmesti",
|
||||
"confirmations.discard_edit_media.message": "Turi neišsaugotų medijos aprašymo ar peržiūros pakeitimų, vis tiek juos atmesti?",
|
||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
||||
"confirmations.logout.confirm": "Atsijungti",
|
||||
"confirmations.logout.message": "Ar tikrai nori atsijungti?",
|
||||
"confirmations.mute.confirm": "Nutildyti",
|
||||
"confirmations.mute.explanation": "Tai paslėps jų įrašus ir įrašus, kuriuose jie menėmi, tačiau jie vis tiek galės matyti tavo įrašus ir sekti.",
|
||||
"confirmations.reply.confirm": "Atsakyti",
|
||||
"confirmations.reply.message": "Atsakydamas (-a) dabar perrašysi šiuo metu rašomą žinutę. Ar tikrai nori tęsti?",
|
||||
"confirmations.unfollow.confirm": "Nebesekti",
|
||||
@@ -219,6 +259,8 @@
|
||||
"trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
|
||||
"upload_form.audio_description": "Describe for people with hearing loss",
|
||||
"upload_form.description": "Describe for the visually impaired",
|
||||
"upload_form.description_missing": "Nėra pridėto aprašymo",
|
||||
"upload_form.edit": "Redaguoti",
|
||||
"upload_form.video_description": "Describe for people with hearing loss or visual impairment",
|
||||
"upload_modal.edit_media": "Redaguoti mediją",
|
||||
"upload_progress.label": "Uploading…"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"about.powered_by": "由 {mastodon} 提供的去中心化社群媒體",
|
||||
"about.rules": "伺服器規則",
|
||||
"account.account_note_header": "備註",
|
||||
"account.add_or_remove_from_list": "從列表中新增或移除",
|
||||
"account.add_or_remove_from_list": "自列表中新增或移除",
|
||||
"account.badges.bot": "機器人",
|
||||
"account.badges.group": "群組",
|
||||
"account.block": "封鎖 @{name}",
|
||||
@@ -26,7 +26,7 @@
|
||||
"account.domain_blocked": "已封鎖網域",
|
||||
"account.edit_profile": "編輯個人檔案",
|
||||
"account.enable_notifications": "當 @{name} 嘟文時通知我",
|
||||
"account.endorse": "在個人檔案推薦對方",
|
||||
"account.endorse": "於個人檔案推薦對方",
|
||||
"account.featured_tags.last_status_at": "上次嘟文於 {date}",
|
||||
"account.featured_tags.last_status_never": "沒有嘟文",
|
||||
"account.featured_tags.title": "{name} 的推薦主題標籤",
|
||||
@@ -65,7 +65,7 @@
|
||||
"account.unblock": "解除封鎖 @{name}",
|
||||
"account.unblock_domain": "解除封鎖網域 {domain}",
|
||||
"account.unblock_short": "解除封鎖",
|
||||
"account.unendorse": "取消在個人檔案推薦對方",
|
||||
"account.unendorse": "取消於個人檔案推薦對方",
|
||||
"account.unfollow": "取消跟隨",
|
||||
"account.unmute": "解除靜音 @{name}",
|
||||
"account.unmute_notifications_short": "取消靜音推播通知",
|
||||
@@ -102,7 +102,7 @@
|
||||
"bundle_modal_error.message": "載入此元件時發生錯誤。",
|
||||
"bundle_modal_error.retry": "重試",
|
||||
"closed_registrations.other_server_instructions": "因為 Mastodon 是去中心化的,所以您也能於其他伺服器上建立帳號,並仍然與這個伺服器互動。",
|
||||
"closed_registrations_modal.description": "目前無法在 {domain} 建立新帳號,但也請別忘了,您並不一定需要有 {domain} 伺服器的帳號,也能使用 Mastodon 。",
|
||||
"closed_registrations_modal.description": "目前無法於 {domain} 建立新帳號,但也請別忘了,您並不一定需要有 {domain} 伺服器的帳號,也能使用 Mastodon 。",
|
||||
"closed_registrations_modal.find_another_server": "尋找另一個伺服器",
|
||||
"closed_registrations_modal.preamble": "Mastodon 是去中心化的,所以無論您於哪個伺服器新增帳號,都可以與此伺服器上的任何人跟隨及互動。您甚至能自行架一個自己的伺服器!",
|
||||
"closed_registrations_modal.title": "註冊 Mastodon",
|
||||
@@ -171,7 +171,7 @@
|
||||
"confirmations.delete_list.confirm": "刪除",
|
||||
"confirmations.delete_list.message": "您確定要永久刪除此列表嗎?",
|
||||
"confirmations.discard_edit_media.confirm": "捨棄",
|
||||
"confirmations.discard_edit_media.message": "您在媒體描述或預覽區塊有未儲存的變更。是否要捨棄這些變更?",
|
||||
"confirmations.discard_edit_media.message": "您於媒體描述或預覽區塊有未儲存的變更。是否要捨棄這些變更?",
|
||||
"confirmations.domain_block.confirm": "封鎖整個網域",
|
||||
"confirmations.domain_block.message": "您真的非常確定要封鎖整個 {domain} 網域嗎?大部分情況下,封鎖或靜音少數特定的帳號就能滿足需求了。您將不能在任何公開的時間軸及通知中看到來自此網域的內容。您來自該網域的跟隨者也將被移除。",
|
||||
"confirmations.edit.confirm": "編輯",
|
||||
@@ -205,7 +205,7 @@
|
||||
"dismissable_banner.explore_statuses": "這些於此伺服器以及去中心化網路中其他伺服器發出的嘟文正在被此伺服器上的人們熱烈討論著。越多不同人轉嘟及最愛排名更高。",
|
||||
"dismissable_banner.explore_tags": "這些主題標籤正在被此伺服器以及去中心化網路上的人們熱烈討論著。越多不同人所嘟出的主題標籤排名更高。",
|
||||
"dismissable_banner.public_timeline": "這些是來自 {domain} 使用者們跟隨中帳號所發表之最新公開嘟文。",
|
||||
"embed.instructions": "要在您的網站嵌入此嘟文,請複製以下程式碼。",
|
||||
"embed.instructions": "若您欲於您的網站嵌入此嘟文,請複製以下程式碼。",
|
||||
"embed.preview": "它將顯示成這樣:",
|
||||
"emoji_button.activity": "活動",
|
||||
"emoji_button.clear": "清除",
|
||||
@@ -218,7 +218,7 @@
|
||||
"emoji_button.objects": "物件",
|
||||
"emoji_button.people": "人物",
|
||||
"emoji_button.recent": "最常使用",
|
||||
"emoji_button.search": "搜尋…",
|
||||
"emoji_button.search": "搜尋...",
|
||||
"emoji_button.search_results": "搜尋結果",
|
||||
"emoji_button.symbols": "符號",
|
||||
"emoji_button.travel": "旅遊與地點",
|
||||
@@ -259,7 +259,7 @@
|
||||
"filter_modal.added.context_mismatch_title": "不符合情境!",
|
||||
"filter_modal.added.expired_explanation": "此過濾器類別已失效,您需要更新過期日期以套用。",
|
||||
"filter_modal.added.expired_title": "過期的過濾器!",
|
||||
"filter_modal.added.review_and_configure": "要檢視和進一步設定此過濾器類別,請至 {settings_link}。",
|
||||
"filter_modal.added.review_and_configure": "要檢視與進一步設定此過濾器類別,請至 {settings_link}。",
|
||||
"filter_modal.added.review_and_configure_title": "過濾器設定",
|
||||
"filter_modal.added.settings_link": "設定頁面",
|
||||
"filter_modal.added.short_explanation": "此嘟文已被新增至以下過濾器類別:{title}。",
|
||||
@@ -362,7 +362,7 @@
|
||||
"keyboard_shortcuts.search": "將游標移至搜尋框",
|
||||
"keyboard_shortcuts.spoilers": "顯示或隱藏內容警告之嘟文",
|
||||
"keyboard_shortcuts.start": "開啟「開始使用」欄位",
|
||||
"keyboard_shortcuts.toggle_hidden": "顯示或隱藏在內容警告之後的嘟文",
|
||||
"keyboard_shortcuts.toggle_hidden": "顯示或隱藏於內容警告之後的嘟文",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "顯示或隱藏媒體",
|
||||
"keyboard_shortcuts.toot": "發個新嘟文",
|
||||
"keyboard_shortcuts.unfocus": "跳離文字撰寫區塊或搜尋框",
|
||||
@@ -376,7 +376,7 @@
|
||||
"limited_account_hint.title": "此個人檔案已被 {domain} 的管理員隱藏。",
|
||||
"link_preview.author": "由 {name} 提供",
|
||||
"lists.account.add": "新增至列表",
|
||||
"lists.account.remove": "從列表中移除",
|
||||
"lists.account.remove": "自列表中移除",
|
||||
"lists.delete": "刪除列表",
|
||||
"lists.edit": "編輯列表",
|
||||
"lists.edit.submit": "變更標題",
|
||||
@@ -469,7 +469,7 @@
|
||||
"notifications.permission_denied_alert": "由於之前瀏覽器權限被拒絕,無法啟用桌面通知",
|
||||
"notifications.permission_required": "由於尚未授予所需的權限,因此無法使用桌面通知。",
|
||||
"notifications_permission_banner.enable": "啟用桌面通知",
|
||||
"notifications_permission_banner.how_to_control": "啟用桌面通知以在 Mastodon 沒有開啟的時候接收通知。在已經啟用桌面通知的時候,您可以透過上面的 {icon} 按鈕準確的控制哪些類型的互動會產生桌面通知。",
|
||||
"notifications_permission_banner.how_to_control": "啟用桌面通知以於 Mastodon 沒有開啟的時候接收通知。啟用桌面通知後,您可以透過上面的 {icon} 按鈕準確的控制哪些類型的互動會產生桌面通知。",
|
||||
"notifications_permission_banner.title": "不要錯過任何東西!",
|
||||
"onboarding.action.back": "返回",
|
||||
"onboarding.actions.back": "返回",
|
||||
@@ -490,7 +490,7 @@
|
||||
"onboarding.steps.follow_people.title": "客製化您的首頁時間軸",
|
||||
"onboarding.steps.publish_status.body": "向新世界打聲招呼吧。",
|
||||
"onboarding.steps.publish_status.title": "撰寫您第一則嘟文",
|
||||
"onboarding.steps.setup_profile.body": "若您完整填寫個人檔案,其他人比較願意和您互動。",
|
||||
"onboarding.steps.setup_profile.body": "若您完整填寫個人檔案,其他人比較願意與您互動。",
|
||||
"onboarding.steps.setup_profile.title": "客製化您的個人檔案",
|
||||
"onboarding.steps.share_profile.body": "讓您的朋友們知道如何於 Mastodon 找到您!",
|
||||
"onboarding.steps.share_profile.title": "分享您的 Mastodon 個人檔案",
|
||||
@@ -614,10 +614,10 @@
|
||||
"sign_in_banner.create_account": "新增帳號",
|
||||
"sign_in_banner.sign_in": "登入",
|
||||
"sign_in_banner.sso_redirect": "登入或註冊",
|
||||
"sign_in_banner.text": "登入以跟隨個人檔案和主題標籤,或收藏、分享和回覆嘟文。您也可以使用您的帳號在其他伺服器上進行互動。",
|
||||
"sign_in_banner.text": "登入以跟隨個人檔案與主題標籤,或收藏、分享及回覆嘟文。您也可以使用您的帳號於其他伺服器進行互動。",
|
||||
"status.admin_account": "開啟 @{name} 的管理介面",
|
||||
"status.admin_domain": "開啟 {domain} 的管理介面",
|
||||
"status.admin_status": "在管理介面開啟此嘟文",
|
||||
"status.admin_status": "於管理介面開啟此嘟文",
|
||||
"status.block": "封鎖 @{name}",
|
||||
"status.bookmark": "書籤",
|
||||
"status.cancel_reblog_private": "取消轉嘟",
|
||||
@@ -672,8 +672,8 @@
|
||||
"status.translated_from_with": "透過 {provider} 翻譯 {lang}",
|
||||
"status.uncached_media_warning": "無法預覽",
|
||||
"status.unmute_conversation": "解除此對話的靜音",
|
||||
"status.unpin": "從個人檔案頁面取消釘選",
|
||||
"subscribed_languages.lead": "僅選定語言的嘟文才會出現在您的首頁上,並在變更後列出時間軸。選取「無」以接收所有語言的嘟文。",
|
||||
"status.unpin": "自個人檔案頁面取消釘選",
|
||||
"subscribed_languages.lead": "僅選定語言的嘟文才會出現於您的首頁上,並於變更後列出時間軸。選取「無」以接收所有語言的嘟文。",
|
||||
"subscribed_languages.save": "儲存變更",
|
||||
"subscribed_languages.target": "變更 {target} 的訂閱語言",
|
||||
"tabs_bar.home": "首頁",
|
||||
@@ -696,7 +696,7 @@
|
||||
"upload_area.title": "拖放來上傳",
|
||||
"upload_button.label": "上傳圖片、影片、或者音樂檔案",
|
||||
"upload_error.limit": "已達到檔案上傳限制。",
|
||||
"upload_error.poll": "不允許在投票中上傳檔案。",
|
||||
"upload_error.poll": "不允許於投票時上傳檔案。",
|
||||
"upload_form.audio_description": "為聽障人士增加文字說明",
|
||||
"upload_form.description": "為視障人士增加文字說明",
|
||||
"upload_form.description_missing": "沒有任何描述",
|
||||
@@ -706,7 +706,7 @@
|
||||
"upload_form.video_description": "為聽障或視障人士增加文字說明",
|
||||
"upload_modal.analyzing_picture": "正在分析圖片…",
|
||||
"upload_modal.apply": "套用",
|
||||
"upload_modal.applying": "正在套用⋯⋯",
|
||||
"upload_modal.applying": "正在套用...",
|
||||
"upload_modal.choose_image": "選擇圖片",
|
||||
"upload_modal.description_placeholder": "我能吞下玻璃而不傷身體",
|
||||
"upload_modal.detect_text": "從圖片中偵測文字",
|
||||
|
||||
@@ -67,6 +67,7 @@ export const accountDefaultValues: AccountShape = {
|
||||
bot: false,
|
||||
created_at: '',
|
||||
discoverable: false,
|
||||
indexable: false,
|
||||
display_name: '',
|
||||
display_name_html: '',
|
||||
emojis: List<CustomEmoji>(),
|
||||
|
||||
@@ -2552,7 +2552,7 @@ $ui-header-height: 55px;
|
||||
|
||||
.column-title {
|
||||
text-align: center;
|
||||
padding-bottom: 40px;
|
||||
padding-bottom: 32px;
|
||||
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
@@ -2743,58 +2743,6 @@ $ui-header-height: 55px;
|
||||
}
|
||||
}
|
||||
|
||||
.onboarding__progress-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
position: sticky;
|
||||
background: $ui-base-color;
|
||||
|
||||
@media screen and (width >= 600) {
|
||||
padding: 0 40px;
|
||||
}
|
||||
|
||||
&__line {
|
||||
height: 4px;
|
||||
flex: 1 1 auto;
|
||||
background: lighten($ui-base-color, 4%);
|
||||
}
|
||||
|
||||
&__step {
|
||||
flex: 0 0 auto;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: lighten($ui-base-color, 4%);
|
||||
border-radius: 50%;
|
||||
color: $primary-text-color;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
svg {
|
||||
width: 15px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: $valid-value-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__step.active,
|
||||
&__line.active {
|
||||
background: $valid-value-color;
|
||||
background-image: linear-gradient(
|
||||
90deg,
|
||||
$valid-value-color,
|
||||
lighten($valid-value-color, 8%),
|
||||
$valid-value-color
|
||||
);
|
||||
background-size: 200px 100%;
|
||||
animation: skeleton 1.2s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.follow-recommendations {
|
||||
background: darken($ui-base-color, 4%);
|
||||
border-radius: 8px;
|
||||
@@ -2871,6 +2819,28 @@ $ui-header-height: 55px;
|
||||
}
|
||||
}
|
||||
|
||||
.onboarding__profile {
|
||||
position: relative;
|
||||
margin-bottom: 40px + 20px;
|
||||
|
||||
.app-form__avatar-input {
|
||||
border: 2px solid $ui-base-color;
|
||||
position: absolute;
|
||||
inset-inline-start: -2px;
|
||||
bottom: -40px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.app-form__header-input {
|
||||
margin: 0 -20px;
|
||||
border-radius: 0;
|
||||
|
||||
img {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.compose-form__highlightable {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -3145,6 +3115,7 @@ $ui-header-height: 55px;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
padding: 0;
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: rgba($base-overlay-background, 0);
|
||||
@@ -3169,81 +3140,41 @@ $ui-header-height: 55px;
|
||||
}
|
||||
|
||||
.react-toggle-track {
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
width: 32px;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
border-radius: 30px;
|
||||
background-color: $ui-base-color;
|
||||
transition: background-color 0.2s ease;
|
||||
border-radius: 10px;
|
||||
background-color: #626982;
|
||||
}
|
||||
|
||||
.react-toggle:is(:hover, :focus-within):not(.react-toggle--disabled)
|
||||
.react-toggle-track {
|
||||
background-color: darken($ui-base-color, 10%);
|
||||
.react-toggle--focus {
|
||||
outline: $ui-button-focus-outline;
|
||||
}
|
||||
|
||||
.react-toggle--checked .react-toggle-track {
|
||||
background-color: darken($ui-highlight-color, 2%);
|
||||
}
|
||||
|
||||
.react-toggle--checked:is(:hover, :focus-within):not(.react-toggle--disabled)
|
||||
.react-toggle-track {
|
||||
background-color: $ui-highlight-color;
|
||||
}
|
||||
|
||||
.react-toggle-track-check {
|
||||
position: absolute;
|
||||
width: 14px;
|
||||
height: 10px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
line-height: 0;
|
||||
inset-inline-start: 8px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.react-toggle--checked .react-toggle-track-check {
|
||||
opacity: 1;
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.react-toggle-track-check,
|
||||
.react-toggle-track-x {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
line-height: 0;
|
||||
inset-inline-end: 10px;
|
||||
opacity: 1;
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.react-toggle--checked .react-toggle-track-x {
|
||||
opacity: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.react-toggle-thumb {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
inset-inline-start: 1px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: 1px solid $ui-base-color;
|
||||
top: 2px;
|
||||
inset-inline-start: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background-color: darken($simple-background-color, 2%);
|
||||
background-color: $primary-text-color;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.25s ease;
|
||||
transition-property: border-color, left;
|
||||
}
|
||||
|
||||
.react-toggle--checked .react-toggle-thumb {
|
||||
inset-inline-start: 27px;
|
||||
inset-inline-start: 32px - 16px - 2px;
|
||||
border-color: $ui-highlight-color;
|
||||
}
|
||||
|
||||
@@ -4066,6 +3997,17 @@ a.status-card {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button .loading-indicator {
|
||||
position: static;
|
||||
transform: none;
|
||||
|
||||
.circular-progress {
|
||||
color: $primary-text-color;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.circular-progress {
|
||||
color: lighten($ui-base-color, 26%);
|
||||
animation: 1.4s linear 0s infinite normal none running simple-rotate;
|
||||
@@ -5799,12 +5741,14 @@ a.status-card {
|
||||
&__toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 16px;
|
||||
gap: 8px;
|
||||
|
||||
& > span {
|
||||
font-size: 17px;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-inline-start: 10px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ code {
|
||||
}
|
||||
|
||||
.input {
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 16px;
|
||||
overflow: hidden;
|
||||
|
||||
&.hidden {
|
||||
@@ -266,12 +266,13 @@ code {
|
||||
font-size: 14px;
|
||||
color: $primary-text-color;
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
padding-top: 5px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin-bottom: 15px;
|
||||
line-height: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
ul {
|
||||
@@ -427,7 +428,8 @@ code {
|
||||
input[type='datetime-local'],
|
||||
textarea {
|
||||
box-sizing: border-box;
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: $primary-text-color;
|
||||
display: block;
|
||||
width: 100%;
|
||||
@@ -435,9 +437,9 @@ code {
|
||||
font-family: inherit;
|
||||
resize: vertical;
|
||||
background: darken($ui-base-color, 10%);
|
||||
border: 1px solid darken($ui-base-color, 14%);
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
border: 1px solid darken($ui-base-color, 10%);
|
||||
border-radius: 8px;
|
||||
padding: 10px 16px;
|
||||
|
||||
&::placeholder {
|
||||
color: lighten($darker-text-color, 4%);
|
||||
@@ -451,14 +453,13 @@ code {
|
||||
border-color: $valid-value-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: darken($ui-base-color, 20%);
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
border-color: $highlight-text-color;
|
||||
background: darken($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
@media screen and (width <= 600px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,12 +525,11 @@ code {
|
||||
border-radius: 4px;
|
||||
background: $ui-button-background-color;
|
||||
color: $ui-button-color;
|
||||
font-size: 18px;
|
||||
line-height: inherit;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
height: auto;
|
||||
padding: 10px;
|
||||
padding: 7px 18px;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
@@ -1220,3 +1220,74 @@ code {
|
||||
background: $highlight-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.app-form {
|
||||
& > * {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__avatar-input,
|
||||
&__header-input {
|
||||
display: block;
|
||||
border-radius: 8px;
|
||||
background: var(--dropdown-background-color);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
inset-inline-start: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: $darker-text-color;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
&.selected .icon {
|
||||
color: $primary-text-color;
|
||||
transform: none;
|
||||
inset-inline-start: auto;
|
||||
inset-inline-end: 8px;
|
||||
top: auto;
|
||||
bottom: 8px;
|
||||
}
|
||||
|
||||
&.invalid img {
|
||||
outline: 1px solid $error-value-color;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
&.invalid::before {
|
||||
display: block;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
background: rgba($error-value-color, 0.25);
|
||||
z-index: 2;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--dropdown-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
&__avatar-input {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
&__header-input {
|
||||
aspect-ratio: 580/193;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user