diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js
index 9e0b123704..8242ec6c84 100644
--- a/app/javascript/flavours/glitch/actions/compose.js
+++ b/app/javascript/flavours/glitch/actions/compose.js
@@ -84,6 +84,7 @@ export const COMPOSE_CHANGE_MEDIA_DESCRIPTION = 'COMPOSE_CHANGE_MEDIA_DESCRIPTIO
export const COMPOSE_CHANGE_MEDIA_FOCUS = 'COMPOSE_CHANGE_MEDIA_FOCUS';
export const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS';
+export const COMPOSE_FOCUS = 'COMPOSE_FOCUS';
const messages = defineMessages({
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
@@ -144,6 +145,15 @@ export function resetCompose() {
};
}
+export const focusCompose = (routerHistory, defaultText) => dispatch => {
+ dispatch({
+ type: COMPOSE_FOCUS,
+ defaultText,
+ });
+
+ ensureComposeIsVisible(routerHistory);
+};
+
export function mentionCompose(account, routerHistory) {
return (dispatch, getState) => {
dispatch({
diff --git a/app/javascript/flavours/glitch/actions/onboarding.js b/app/javascript/flavours/glitch/actions/onboarding.js
index a4a525c427..a1dd3a731e 100644
--- a/app/javascript/flavours/glitch/actions/onboarding.js
+++ b/app/javascript/flavours/glitch/actions/onboarding.js
@@ -1,16 +1,8 @@
-import { openModal } from './modal';
import { changeSetting, saveSettings } from './settings';
-export function showOnboardingOnce() {
- return (dispatch, getState) => {
- const alreadySeen = getState().getIn(['settings', 'onboarded']);
+export const INTRODUCTION_VERSION = 20181216044202;
- if (!alreadySeen) {
- dispatch(openModal({
- modalType: 'ONBOARDING',
- }));
- dispatch(changeSetting(['onboarded'], true));
- dispatch(saveSettings());
- }
- };
-}
+export const closeOnboarding = () => dispatch => {
+ dispatch(changeSetting(['introductionVersion'], INTRODUCTION_VERSION));
+ dispatch(saveSettings());
+};
diff --git a/app/javascript/flavours/glitch/components/check.jsx b/app/javascript/flavours/glitch/components/check.jsx
deleted file mode 100644
index d818480b7b..0000000000
--- a/app/javascript/flavours/glitch/components/check.jsx
+++ /dev/null
@@ -1,7 +0,0 @@
-const Check = () => (
-
-);
-
-export default Check;
diff --git a/app/javascript/flavours/glitch/components/check.tsx b/app/javascript/flavours/glitch/components/check.tsx
new file mode 100644
index 0000000000..901f89fc5b
--- /dev/null
+++ b/app/javascript/flavours/glitch/components/check.tsx
@@ -0,0 +1,13 @@
+export const Check: React.FC = () => (
+
+);
diff --git a/app/javascript/flavours/glitch/components/column_back_button.jsx b/app/javascript/flavours/glitch/components/column_back_button.jsx
index df623ab233..5e705e05d7 100644
--- a/app/javascript/flavours/glitch/components/column_back_button.jsx
+++ b/app/javascript/flavours/glitch/components/column_back_button.jsx
@@ -13,13 +13,16 @@ export class ColumnBackButton extends PureComponent {
static propTypes = {
multiColumn: PropTypes.bool,
+ onClick: PropTypes.func,
...WithRouterPropTypes,
};
handleClick = () => {
- const { history } = this.props;
+ const { onClick, history } = this.props;
- if (history.location?.state?.fromMastodon) {
+ if (onClick) {
+ onClick();
+ } else if (history.location?.state?.fromMastodon) {
history.goBack();
} else {
history.push('/');
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx b/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx
index 72fc4c4ab8..47cf124932 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx
@@ -2,6 +2,8 @@ import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
+import classNames from 'classnames';
+
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
@@ -84,6 +86,10 @@ class ComposeForm extends ImmutablePureComponent {
showSearch: false,
};
+ state = {
+ highlighted: false,
+ };
+
handleChange = (e) => {
this.props.onChange(e.target.value);
};
@@ -209,6 +215,10 @@ class ComposeForm extends ImmutablePureComponent {
this._updateFocusAndSelection({ });
}
+ componentWillUnmount () {
+ if (this.timeout) clearTimeout(this.timeout);
+ }
+
componentDidUpdate (prevProps) {
this._updateFocusAndSelection(prevProps);
}
@@ -257,6 +267,8 @@ class ComposeForm extends ImmutablePureComponent {
textarea.setSelectionRange(selectionStart, selectionEnd);
textarea.focus();
if (!singleColumn) textarea.scrollIntoView();
+ this.setState({ highlighted: true });
+ this.timeout = setTimeout(() => this.setState({ highlighted: false }), 700);
}).catch(console.error);
}
@@ -302,6 +314,7 @@ class ComposeForm extends ImmutablePureComponent {
spoilersAlwaysOn,
isEditing,
} = this.props;
+ const { highlighted } = this.state;
const countText = this.getFulltextForCharacterCounting();
@@ -332,42 +345,44 @@ class ComposeForm extends ImmutablePureComponent {
/>
-
-
-
-
-
-
-
-
+ 0)}
- spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
- />
-
-
+ value={this.props.text}
+ onChange={this.handleChange}
+ onKeyDown={this.handleKeyDown}
+ suggestions={suggestions}
+ onFocus={this.handleFocus}
+ onSuggestionsFetchRequested={onFetchSuggestions}
+ onSuggestionsClearRequested={onClearSuggestions}
+ onSuggestionSelected={this.handleSuggestionSelected}
+ onPaste={onPaste}
+ autoFocus={!showSearch && !isMobile(window.innerWidth, layout)}
+ lang={this.props.lang}
+ >
+
+
+
+
+
+
+
0)}
+ spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
+ />
+
+
+
diff --git a/app/javascript/flavours/glitch/features/follow_recommendations/components/account.jsx b/app/javascript/flavours/glitch/features/follow_recommendations/components/account.jsx
deleted file mode 100644
index 89a270d092..0000000000
--- a/app/javascript/flavours/glitch/features/follow_recommendations/components/account.jsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import PropTypes from 'prop-types';
-
-import { injectIntl, defineMessages } from 'react-intl';
-
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { connect } from 'react-redux';
-
-import { followAccount, unfollowAccount } from 'flavours/glitch/actions/accounts';
-import { Avatar } from 'flavours/glitch/components/avatar';
-import { DisplayName } from 'flavours/glitch/components/display_name';
-import { IconButton } from 'flavours/glitch/components/icon_button';
-import Permalink from 'flavours/glitch/components/permalink';
-import { makeGetAccount } from 'flavours/glitch/selectors';
-
-const messages = defineMessages({
- follow: { id: 'account.follow', defaultMessage: 'Follow' },
- unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
-});
-
-const makeMapStateToProps = () => {
- const getAccount = makeGetAccount();
-
- const mapStateToProps = (state, props) => ({
- account: getAccount(state, props.id),
- });
-
- return mapStateToProps;
-};
-
-const getFirstSentence = str => {
- const arr = str.split(/(([.?!]+\s)|[.。?!\n•])/);
-
- return arr[0];
-};
-
-class Account extends ImmutablePureComponent {
-
- static propTypes = {
- account: ImmutablePropTypes.map.isRequired,
- intl: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- };
-
- handleFollow = () => {
- const { account, dispatch } = this.props;
-
- if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
- dispatch(unfollowAccount(account.get('id')));
- } else {
- dispatch(followAccount(account.get('id')));
- }
- };
-
- render () {
- const { account, intl } = this.props;
-
- let button;
-
- if (account.getIn(['relationship', 'following'])) {
- button = ;
- } else {
- button = ;
- }
-
- return (
-
-
-
-
-
-
-
- {getFirstSentence(account.get('note_plain'))}
-
-
-
- {button}
-
-
-
- );
- }
-
-}
-
-export default connect(makeMapStateToProps)(injectIntl(Account));
diff --git a/app/javascript/flavours/glitch/features/follow_recommendations/index.jsx b/app/javascript/flavours/glitch/features/follow_recommendations/index.jsx
deleted file mode 100644
index 04fc2b06bc..0000000000
--- a/app/javascript/flavours/glitch/features/follow_recommendations/index.jsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import PropTypes from 'prop-types';
-
-import { FormattedMessage } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-import { withRouter } from 'react-router-dom';
-
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { connect } from 'react-redux';
-
-import { requestBrowserPermission } from 'flavours/glitch/actions/notifications';
-import { changeSetting, saveSettings } from 'flavours/glitch/actions/settings';
-import { fetchSuggestions } from 'flavours/glitch/actions/suggestions';
-import { markAsPartial } from 'flavours/glitch/actions/timelines';
-import { Button } from 'flavours/glitch/components/button';
-import Column from 'flavours/glitch/features/ui/components/column';
-import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
-import imageGreeting from 'mastodon/../images/elephant_ui_greeting.svg';
-
-import Account from './components/account';
-
-const mapStateToProps = state => ({
- suggestions: state.getIn(['suggestions', 'items']),
- isLoading: state.getIn(['suggestions', 'isLoading']),
-});
-
-class FollowRecommendations extends ImmutablePureComponent {
-
- static propTypes = {
- dispatch: PropTypes.func.isRequired,
- suggestions: ImmutablePropTypes.list,
- isLoading: PropTypes.bool,
- ...WithRouterPropTypes,
- };
-
- componentDidMount () {
- const { dispatch, suggestions } = this.props;
-
- // Don't re-fetch if we're e.g. navigating backwards to this page,
- // since we don't want followed accounts to disappear from the list
-
- if (suggestions.size === 0) {
- dispatch(fetchSuggestions(true));
- }
- }
-
- componentWillUnmount () {
- const { dispatch } = this.props;
-
- // Force the home timeline to be reloaded when the user navigates
- // to it; if the user is new, it would've been empty before
-
- dispatch(markAsPartial('home'));
- }
-
- handleDone = () => {
- const { history, dispatch } = this.props;
-
- dispatch(requestBrowserPermission((permission) => {
- if (permission === 'granted') {
- dispatch(changeSetting(['notifications', 'alerts', 'follow'], true));
- dispatch(changeSetting(['notifications', 'alerts', 'favourite'], true));
- dispatch(changeSetting(['notifications', 'alerts', 'reblog'], true));
- dispatch(changeSetting(['notifications', 'alerts', 'mention'], true));
- dispatch(changeSetting(['notifications', 'alerts', 'poll'], true));
- dispatch(changeSetting(['notifications', 'alerts', 'status'], true));
- dispatch(saveSettings());
- }
- }));
-
- history.push('/home');
- };
-
- render () {
- const { suggestions, isLoading } = this.props;
-
- return (
-
-
-
-
- {!isLoading && (
- <>
-
- {suggestions.size > 0 ? suggestions.map(suggestion => (
-
- )) : (
-
-
-
- )}
-
-
-
-
-
-
- >
- )}
-
-
-
-
-
-
- );
- }
-
-}
-
-export default withRouter(connect(mapStateToProps)(FollowRecommendations));
diff --git a/app/javascript/flavours/glitch/features/getting_started_misc/index.jsx b/app/javascript/flavours/glitch/features/getting_started_misc/index.jsx
index 2c2fbc05c0..60b710afca 100644
--- a/app/javascript/flavours/glitch/features/getting_started_misc/index.jsx
+++ b/app/javascript/flavours/glitch/features/getting_started_misc/index.jsx
@@ -19,7 +19,6 @@ const messages = defineMessages({
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
- show_me_around: { id: 'getting_started.onboarding', defaultMessage: 'Show me around' },
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
keyboard_shortcuts: { id: 'navigation_bar.keyboard_shortcuts', defaultMessage: 'Keyboard shortcuts' },
featured_users: { id: 'navigation_bar.featured_users', defaultMessage: 'Featured users' },
@@ -36,12 +35,6 @@ class GettingStartedMisc extends ImmutablePureComponent {
dispatch: PropTypes.func.isRequired,
};
- openOnboardingModal = () => {
- this.props.dispatch(openModal({
- modalType: 'ONBOARDING',
- }));
- };
-
openFeaturedAccountsModal = () => {
this.props.dispatch(openModal({
modalType: 'PINNED_ACCOUNTS_EDITOR',
@@ -65,7 +58,6 @@ class GettingStartedMisc extends ImmutablePureComponent {
{signedIn && ()}
{signedIn && ()}
- {signedIn && ()}
);
diff --git a/app/javascript/flavours/glitch/features/onboarding/components/arrow_small_right.jsx b/app/javascript/flavours/glitch/features/onboarding/components/arrow_small_right.jsx
new file mode 100644
index 0000000000..79b9db383f
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/onboarding/components/arrow_small_right.jsx
@@ -0,0 +1,7 @@
+const ArrowSmallRight = () => (
+
+);
+
+export default ArrowSmallRight;
\ No newline at end of file
diff --git a/app/javascript/flavours/glitch/features/onboarding/components/progress_indicator.jsx b/app/javascript/flavours/glitch/features/onboarding/components/progress_indicator.jsx
new file mode 100644
index 0000000000..f7d54ae37d
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/onboarding/components/progress_indicator.jsx
@@ -0,0 +1,28 @@
+import PropTypes from 'prop-types';
+import { Fragment } from 'react';
+
+import classNames from 'classnames';
+
+import { Check } from 'flavours/glitch/components/check';
+
+
+const ProgressIndicator = ({ steps, completed }) => (
+
+ {(new Array(steps)).fill().map((_, i) => (
+
+ {i > 0 && i })} />}
+
+
i })}>
+ {completed > i && }
+
+
+ ))}
+
+);
+
+ProgressIndicator.propTypes = {
+ steps: PropTypes.number.isRequired,
+ completed: PropTypes.number,
+};
+
+export default ProgressIndicator;
diff --git a/app/javascript/flavours/glitch/features/onboarding/components/step.jsx b/app/javascript/flavours/glitch/features/onboarding/components/step.jsx
new file mode 100644
index 0000000000..b5bb8e727f
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/onboarding/components/step.jsx
@@ -0,0 +1,50 @@
+import PropTypes from 'prop-types';
+
+import { Check } from 'flavours/glitch/components/check';
+import { Icon } from 'flavours/glitch/components/icon';
+
+import ArrowSmallRight from './arrow_small_right';
+
+const Step = ({ label, description, icon, completed, onClick, href }) => {
+ const content = (
+ <>
+
+
+
+
+
+
{label}
+
{description}
+
+
+
+ >
+ );
+
+ if (href) {
+ return (
+
+ {content}
+
+ );
+ }
+
+ return (
+
+ );
+};
+
+Step.propTypes = {
+ label: PropTypes.node,
+ description: PropTypes.node,
+ icon: PropTypes.string,
+ completed: PropTypes.bool,
+ href: PropTypes.string,
+ onClick: PropTypes.func,
+};
+
+export default Step;
diff --git a/app/javascript/flavours/glitch/features/onboarding/follows.jsx b/app/javascript/flavours/glitch/features/onboarding/follows.jsx
new file mode 100644
index 0000000000..76673cdb41
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/onboarding/follows.jsx
@@ -0,0 +1,80 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { FormattedMessage } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { connect } from 'react-redux';
+
+import { fetchSuggestions } from 'flavours/glitch/actions/suggestions';
+import { markAsPartial } from 'flavours/glitch/actions/timelines';
+import Column from 'flavours/glitch/components/column';
+import ColumnBackButton from 'flavours/glitch/components/column_back_button';
+import { EmptyAccount } from 'flavours/glitch/components/empty_account';
+import Account from 'flavours/glitch/containers/account_container';
+
+const mapStateToProps = state => ({
+ suggestions: state.getIn(['suggestions', 'items']),
+ isLoading: state.getIn(['suggestions', 'isLoading']),
+});
+
+class Follows extends PureComponent {
+
+ static propTypes = {
+ onBack: PropTypes.func,
+ dispatch: PropTypes.func.isRequired,
+ suggestions: ImmutablePropTypes.list,
+ isLoading: PropTypes.bool,
+ multiColumn: PropTypes.bool,
+ };
+
+ componentDidMount () {
+ const { dispatch } = this.props;
+ dispatch(fetchSuggestions(true));
+ }
+
+ componentWillUnmount () {
+ const { dispatch } = this.props;
+ dispatch(markAsPartial('home'));
+ }
+
+ render () {
+ const { onBack, isLoading, suggestions, multiColumn } = this.props;
+
+ let loadedContent;
+
+ if (isLoading) {
+ loadedContent = (new Array(8)).fill().map((_, i) => );
+ } else if (suggestions.isEmpty()) {
+ loadedContent =
;
+ } else {
+ loadedContent = suggestions.map(suggestion => );
+ }
+
+ return (
+
+
+
+
+
+
+
+ {loadedContent}
+
+
+
{chunks} }} />
+
+
+
+
+
+
+ );
+ }
+
+}
+
+export default connect(mapStateToProps)(Follows);
diff --git a/app/javascript/flavours/glitch/features/onboarding/index.jsx b/app/javascript/flavours/glitch/features/onboarding/index.jsx
new file mode 100644
index 0000000000..64cf42efac
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/onboarding/index.jsx
@@ -0,0 +1,149 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
+
+import { Helmet } from 'react-helmet';
+import { Link, withRouter } from 'react-router-dom';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import { debounce } from 'lodash';
+
+import { fetchAccount } from 'flavours/glitch/actions/accounts';
+import { focusCompose } from 'flavours/glitch/actions/compose';
+import { closeOnboarding } from 'flavours/glitch/actions/onboarding';
+import Column from 'flavours/glitch/features/ui/components/column';
+import { me } from 'flavours/glitch/initial_state';
+import { makeGetAccount } from 'flavours/glitch/selectors';
+import { assetHost } from 'flavours/glitch/utils/config';
+import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
+import illustration from 'mastodon/../images/elephant_ui_conversation.svg';
+
+import ArrowSmallRight from './components/arrow_small_right';
+import Step from './components/step';
+import Follows from './follows';
+import Share from './share';
+
+const messages = defineMessages({
+ template: { id: 'onboarding.compose.template', defaultMessage: 'Hello #Mastodon!' },
+});
+
+const mapStateToProps = () => {
+ const getAccount = makeGetAccount();
+
+ return state => ({
+ account: getAccount(state, me),
+ });
+};
+
+class Onboarding extends ImmutablePureComponent {
+ static propTypes = {
+ dispatch: PropTypes.func.isRequired,
+ account: ImmutablePropTypes.map,
+ multiColumn: PropTypes.bool,
+ ...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, multiColumn } = this.props;
+ const { step, shareClicked } = this.state;
+
+ switch(step) {
+ case 'follows':
+ return ;
+ case 'share':
+ return ;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ 0 && account.get('note').length > 0)} icon='address-book-o' label={} description={} />
+ = 7} icon='user-plus' label={} description={} />
+ = 1} icon='pencil-square-o' label={} description={ }} />} />
+ } description={} />
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+}
+
+export default withRouter(connect(mapStateToProps)(injectIntl(Onboarding)));
diff --git a/app/javascript/flavours/glitch/features/onboarding/share.jsx b/app/javascript/flavours/glitch/features/onboarding/share.jsx
new file mode 100644
index 0000000000..a313ee2e8d
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/onboarding/share.jsx
@@ -0,0 +1,200 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl, 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 SwipeableViews from 'react-swipeable-views';
+
+import Column from 'flavours/glitch/components/column';
+import ColumnBackButton from 'flavours/glitch/components/column_back_button';
+import { Icon } from 'flavours/glitch/components/icon';
+import { me, domain } from 'flavours/glitch/initial_state';
+
+import ArrowSmallRight from './components/arrow_small_right';
+
+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 = {
+ value: PropTypes.string,
+ };
+
+ state = {
+ copied: false,
+ focused: false,
+ };
+
+ setRef = c => {
+ this.input = c;
+ };
+
+ handleInputClick = () => {
+ this.setState({ copied: false });
+ this.input.focus();
+ this.input.select();
+ this.input.setSelectionRange(0, this.props.value.length);
+ };
+
+ handleButtonClick = e => {
+ e.stopPropagation();
+
+ const { value } = this.props;
+ navigator.clipboard.writeText(value);
+ this.input.blur();
+ this.setState({ copied: true });
+ this.timeout = setTimeout(() => this.setState({ copied: false }), 700);
+ };
+
+ handleFocus = () => {
+ this.setState({ focused: true });
+ };
+
+ handleBlur = () => {
+ this.setState({ focused: false });
+ };
+
+ componentWillUnmount () {
+ if (this.timeout) clearTimeout(this.timeout);
+ }
+
+ render () {
+ const { value } = this.props;
+ const { copied, focused } = this.state;
+
+ return (
+
+
+
+
+
+ );
+ }
+
+}
+
+class TipCarousel extends PureComponent {
+
+ static propTypes = {
+ children: PropTypes.node,
+ };
+
+ state = {
+ index: 0,
+ };
+
+ handleSwipe = index => {
+ this.setState({ index });
+ };
+
+ handleChangeIndex = e => {
+ this.setState({ index: Number(e.currentTarget.getAttribute('data-index')) });
+ };
+
+ handleKeyDown = e => {
+ switch(e.key) {
+ case 'ArrowLeft':
+ e.preventDefault();
+ this.setState(({ index }, { children }) => ({ index: Math.abs(index - 1) % children.length }));
+ break;
+ case 'ArrowRight':
+ e.preventDefault();
+ this.setState(({ index }, { children }) => ({ index: (index + 1) % children.length }));
+ break;
+ }
+ };
+
+ render () {
+ const { children } = this.props;
+ const { index } = this.state;
+
+ return (
+
+
+ {children}
+
+
+
+ {children.map((_, i) => (
+
+ ))}
+
+
+ );
+ }
+
+}
+
+class Share extends PureComponent {
+
+ static propTypes = {
+ onBack: PropTypes.func,
+ account: ImmutablePropTypes.map,
+ multiColumn: PropTypes.bool,
+ intl: PropTypes.object,
+ };
+
+ render () {
+ const { onBack, account, multiColumn, intl } = this.props;
+
+ const url = (new URL(`/@${account.get('username')}`, document.baseURI)).href;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+}
+
+export default connect(mapStateToProps)(injectIntl(Share));
diff --git a/app/javascript/flavours/glitch/features/report/components/option.jsx b/app/javascript/flavours/glitch/features/report/components/option.jsx
index da20fd0624..f78eb140d9 100644
--- a/app/javascript/flavours/glitch/features/report/components/option.jsx
+++ b/app/javascript/flavours/glitch/features/report/components/option.jsx
@@ -3,7 +3,7 @@ import { PureComponent } from 'react';
import classNames from 'classnames';
-import Check from 'flavours/glitch/components/check';
+import { Check } from 'flavours/glitch/components/check';
export default class Option extends PureComponent {
diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx b/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx
index 78a7ac2297..4ecf07b030 100644
--- a/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx
+++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx
@@ -5,7 +5,6 @@ import { Helmet } from 'react-helmet';
import Base from 'flavours/glitch/components/modal_root';
import {
- OnboardingModal,
MuteModal,
BlockModal,
ReportModal,
@@ -40,7 +39,6 @@ import VideoModal from './video_modal';
export const MODAL_COMPONENTS = {
'MEDIA': () => Promise.resolve({ default: MediaModal }),
- 'ONBOARDING': OnboardingModal,
'VIDEO': () => Promise.resolve({ default: VideoModal }),
'AUDIO': () => Promise.resolve({ default: AudioModal }),
'IMAGE': () => Promise.resolve({ default: ImageModal }),
diff --git a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.jsx
deleted file mode 100644
index 5f93cd2514..0000000000
--- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.jsx
+++ /dev/null
@@ -1,328 +0,0 @@
-import PropTypes from 'prop-types';
-import { PureComponent } from 'react';
-
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-
-import classNames from 'classnames';
-
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { connect } from 'react-redux';
-
-import ReactSwipeableViews from 'react-swipeable-views';
-
-import Permalink from 'flavours/glitch/components/permalink';
-import ComposeForm from 'flavours/glitch/features/compose/components/compose_form';
-import DrawerAccount from 'flavours/glitch/features/compose/components/navigation_bar';
-import Search from 'flavours/glitch/features/compose/components/search';
-import { me, source_url } from 'flavours/glitch/initial_state';
-
-import ColumnHeader from './column_header';
-
-const noop = () => { };
-
-const messages = defineMessages({
- home_title: { id: 'column.home', defaultMessage: 'Home' },
- notifications_title: { id: 'column.notifications', defaultMessage: 'Notifications' },
- local_title: { id: 'column.community', defaultMessage: 'Local timeline' },
- federated_title: { id: 'column.public', defaultMessage: 'Federated timeline' },
-});
-
-const PageOne = ({ acct, domain }) => (
-
-
-
-
-
-
-
@{acct}@{domain} }} />
-
-
-);
-
-PageOne.propTypes = {
- acct: PropTypes.string.isRequired,
- domain: PropTypes.string.isRequired,
-};
-
-const PageTwo = ({ myAccount }) => (
-
-);
-
-PageTwo.propTypes = {
- intl: PropTypes.object.isRequired,
- myAccount: ImmutablePropTypes.map.isRequired,
-};
-
-const PageThree = ({ myAccount }) => (
-
-
-
-
#illustration, introductions: #introductions }} />
-
-
-);
-
-PageThree.propTypes = {
- intl: PropTypes.object.isRequired,
- myAccount: ImmutablePropTypes.map.isRequired,
-};
-
-const PageFour = ({ domain, intl }) => (
-
-);
-
-PageFour.propTypes = {
- domain: PropTypes.string.isRequired,
- intl: PropTypes.object.isRequired,
-};
-
-const PageSix = ({ admin, domain }) => {
- let adminSection = '';
-
- if (admin) {
- adminSection = (
-
- @{admin.get('acct')} }} />
-
- }} />
-
- );
- }
-
- return (
-
-
- {adminSection}
-
- fork,
- Mastodon: Mastodon,
- github: GitHub,
- }}
- />
-
-
}} />
-
-
- );
-};
-
-PageSix.propTypes = {
- admin: ImmutablePropTypes.map,
- domain: PropTypes.string.isRequired,
-};
-
-const mapStateToProps = state => ({
- myAccount: state.getIn(['accounts', me]),
- admin: state.getIn(['accounts', state.getIn(['meta', 'admin'])]),
- domain: state.getIn(['meta', 'domain']),
-});
-
-class OnboardingModal extends PureComponent {
-
- static propTypes = {
- onClose: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- myAccount: ImmutablePropTypes.map.isRequired,
- domain: PropTypes.string.isRequired,
- admin: ImmutablePropTypes.map,
- };
-
- state = {
- currentIndex: 0,
- };
-
- UNSAFE_componentWillMount() {
- const { myAccount, admin, domain, intl } = this.props;
- this.pages = [
- ,
- ,
- ,
- ,
- ,
- ];
- }
-
- componentDidMount() {
- window.addEventListener('keyup', this.handleKeyUp);
- }
-
- componentWillUnmount() {
- window.addEventListener('keyup', this.handleKeyUp);
- }
-
- handleSkip = (e) => {
- e.preventDefault();
- this.props.onClose();
- };
-
- handleDot = (e) => {
- const i = Number(e.currentTarget.getAttribute('data-index'));
- e.preventDefault();
- this.setState({ currentIndex: i });
- };
-
- handlePrev = () => {
- this.setState(({ currentIndex }) => ({
- currentIndex: Math.max(0, currentIndex - 1),
- }));
- };
-
- handleNext = () => {
- const { pages } = this;
- this.setState(({ currentIndex }) => ({
- currentIndex: Math.min(currentIndex + 1, pages.length - 1),
- }));
- };
-
- handleSwipe = (index) => {
- this.setState({ currentIndex: index });
- };
-
- handleKeyUp = ({ key }) => {
- switch (key) {
- case 'ArrowLeft':
- this.handlePrev();
- break;
- case 'ArrowRight':
- this.handleNext();
- break;
- }
- };
-
- handleClose = () => {
- this.props.onClose();
- };
-
- render () {
- const { pages } = this;
- const { currentIndex } = this.state;
- const hasMore = currentIndex < pages.length - 1;
-
- const nextOrDoneBtn = hasMore ? (
-
- ) : (
-
- );
-
- return (
-
-
- {pages.map((page, i) => {
- const className = classNames('onboarding-modal__page__wrapper', {
- 'onboarding-modal__page__wrapper--active': i === currentIndex,
- });
- return (
- {page}
- );
- })}
-
-
-
-
-
-
-
-
- {pages.map((_, i) => {
- const className = classNames('onboarding-modal__dot', {
- active: i === currentIndex,
- });
- return (
-
- );
- })}
-
-
-
- {nextOrDoneBtn}
-
-
-
- );
- }
-
-}
-
-export default connect(mapStateToProps)(injectIntl(OnboardingModal));
diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx
index 566421e65c..0f5ad539f2 100644
--- a/app/javascript/flavours/glitch/features/ui/index.jsx
+++ b/app/javascript/flavours/glitch/features/ui/index.jsx
@@ -14,6 +14,7 @@ import { HotKeys } from 'react-hotkeys';
import { changeLayout } from 'flavours/glitch/actions/app';
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavours/glitch/actions/markers';
+import { INTRODUCTION_VERSION } from 'flavours/glitch/actions/onboarding';
import PermaLink from 'flavours/glitch/components/permalink';
import PictureInPicture from 'flavours/glitch/features/picture_in_picture';
import { layoutFromWindow } from 'flavours/glitch/is_mobile';
@@ -62,7 +63,7 @@ import {
GettingStartedMisc,
Directory,
Explore,
- FollowRecommendations,
+ Onboarding,
About,
PrivacyPolicy,
} from './util/async-components';
@@ -86,7 +87,7 @@ const mapStateToProps = state => ({
showFaviconBadge: state.getIn(['local_settings', 'notifications', 'favicon_badge']),
hicolorPrivacyIcons: state.getIn(['local_settings', 'hicolor_privacy_icons']),
moved: state.getIn(['accounts', me, 'moved']) && state.getIn(['accounts', state.getIn(['accounts', me, 'moved'])]),
- firstLaunch: false, // TODO: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION,
+ firstLaunch: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION,
username: state.getIn(['accounts', me, 'username']),
});
@@ -216,7 +217,7 @@ class SwitchingColumnsArea extends PureComponent {
-
+
@@ -417,7 +418,6 @@ class UI extends Component {
// On first launch, redirect to the follow recommendations page
if (signedIn && this.props.firstLaunch) {
this.props.history.replace('/start');
- // TODO: this.props.dispatch(closeOnboarding());
}
if (signedIn) {
diff --git a/app/javascript/flavours/glitch/features/ui/util/async-components.js b/app/javascript/flavours/glitch/features/ui/util/async-components.js
index 24e8a42a68..762df7ecf4 100644
--- a/app/javascript/flavours/glitch/features/ui/util/async-components.js
+++ b/app/javascript/flavours/glitch/features/ui/util/async-components.js
@@ -118,10 +118,6 @@ export function Mutes () {
return import(/* webpackChunkName: "flavours/glitch/async/mutes" */'flavours/glitch/features/mutes');
}
-export function OnboardingModal () {
- return import(/* webpackChunkName: "flavours/glitch/async/onboarding_modal" */'flavours/glitch/features/ui/components/onboarding_modal');
-}
-
export function MuteModal () {
return import(/* webpackChunkName: "flavours/glitch/async/mute_modal" */'flavours/glitch/features/ui/components/mute_modal');
}
@@ -170,8 +166,8 @@ export function Directory () {
return import(/* webpackChunkName: "features/glitch/async/directory" */'flavours/glitch/features/directory');
}
-export function FollowRecommendations () {
- return import(/* webpackChunkName: "features/glitch/async/follow_recommendations" */'flavours/glitch/features/follow_recommendations');
+export function Onboarding () {
+ return import(/* webpackChunkName: "features/glitch/async/onboarding" */'flavours/glitch/features/onboarding');
}
export function CompareHistoryModal () {
diff --git a/app/javascript/flavours/glitch/locales/en.json b/app/javascript/flavours/glitch/locales/en.json
index d0e50cbaf1..7d04470235 100644
--- a/app/javascript/flavours/glitch/locales/en.json
+++ b/app/javascript/flavours/glitch/locales/en.json
@@ -3,9 +3,7 @@
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.follows": "Follows",
"account.joined": "Joined {date}",
- "account.mute_notifications": "Mute notifications from @{name}",
"account.suspended_disclaimer_full": "This user has been suspended by a moderator.",
- "account.unmute_notifications": "Unmute notifications from @{name}",
"account.view_full_profile": "View full profile",
"advanced_options.icon_title": "Advanced options",
"advanced_options.local-only.long": "Do not post to other instances",
@@ -44,14 +42,9 @@
"confirmations.unfilter.filters": "Matching {count, plural, one {filter} other {filters}}",
"content-type.change": "Content type",
"direct.group_by_conversations": "Group by conversation",
- "empty_column.follow_recommendations": "Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.",
"endorsed_accounts_editor.endorsed_accounts": "Featured accounts",
"favourite_modal.combo": "You can press {combo} to skip this next time",
"firehose.column_settings.allow_local_only": "Show local-only posts in \"All\"",
- "follow_recommendations.done": "Done",
- "follow_recommendations.heading": "Follow people you'd like to see posts from! Here are some suggestions.",
- "follow_recommendations.lead": "Posts from people you follow will show up in chronological order on your home feed. Don't be afraid to make mistakes, you can unfollow people just as easily any time!",
- "getting_started.onboarding": "Show me around",
"home.column_settings.advanced": "Advanced",
"home.column_settings.filter_regex": "Filter out by regular expressions",
"home.column_settings.show_direct": "Show private mentions",
@@ -73,26 +66,6 @@
"notification_purge.start": "Enter notification cleaning mode",
"notifications.marked_clear": "Clear selected notifications",
"notifications.marked_clear_confirmation": "Are you sure you want to permanently clear all selected notifications?",
- "onboarding.done": "Done",
- "onboarding.next": "Next",
- "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
- "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
- "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
- "onboarding.page_one.federation": "{domain} is an 'instance' of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
- "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
- "onboarding.page_one.welcome": "Welcome to {domain}!",
- "onboarding.page_six.admin": "Your instance's admin is {admin}.",
- "onboarding.page_six.almost_done": "Almost done...",
- "onboarding.page_six.appetoot": "Bon Appetoot!",
- "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
- "onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}, and is compatible with any Mastodon instance or app. Glitchsoc is entirely free and open-source. You can report bugs, request features, or contribute to the code on {github}.",
- "onboarding.page_six.guidelines": "community guidelines",
- "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
- "onboarding.page_six.various_app": "mobile apps",
- "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
- "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
- "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
- "onboarding.skip": "Skip",
"settings.always_show_spoilers_field": "Always enable the Content Warning field",
"settings.auto_collapse": "Automatic collapsing",
"settings.auto_collapse_all": "Everything",
diff --git a/app/javascript/flavours/glitch/reducers/accounts_counters.js b/app/javascript/flavours/glitch/reducers/accounts_counters.js
index 16e7cfb761..4ef1128700 100644
--- a/app/javascript/flavours/glitch/reducers/accounts_counters.js
+++ b/app/javascript/flavours/glitch/reducers/accounts_counters.js
@@ -1,5 +1,7 @@
import { Map as ImmutableMap, fromJS } from 'immutable';
+import { me } from 'flavours/glitch/initial_state';
+
import {
ACCOUNT_FOLLOW_SUCCESS,
ACCOUNT_UNFOLLOW_SUCCESS,
@@ -20,6 +22,14 @@ const normalizeAccounts = (state, accounts) => {
return state;
};
+const incrementFollowers = (state, accountId) =>
+ state.updateIn([accountId, 'followers_count'], num => num + 1)
+ .updateIn([me, 'following_count'], num => num + 1);
+
+const decrementFollowers = (state, accountId) =>
+ state.updateIn([accountId, 'followers_count'], num => Math.max(0, num - 1))
+ .updateIn([me, 'following_count'], num => Math.max(0, num - 1));
+
const initialState = ImmutableMap();
export default function accountsCounters(state = initialState, action) {
@@ -30,9 +40,9 @@ export default function accountsCounters(state = initialState, action) {
return normalizeAccounts(state, action.accounts);
case ACCOUNT_FOLLOW_SUCCESS:
return action.alreadyFollowing ? state :
- state.updateIn([action.relationship.id, 'followers_count'], num => num + 1);
+ incrementFollowers(state, action.relationship.id);
case ACCOUNT_UNFOLLOW_SUCCESS:
- return state.updateIn([action.relationship.id, 'followers_count'], num => Math.max(0, num - 1));
+ return decrementFollowers(state, action.relationship.id);
default:
return state;
}
diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js
index d71148be28..96e5a2d7a3 100644
--- a/app/javascript/flavours/glitch/reducers/compose.js
+++ b/app/javascript/flavours/glitch/reducers/compose.js
@@ -51,6 +51,7 @@ import {
COMPOSE_CHANGE_MEDIA_DESCRIPTION,
COMPOSE_CHANGE_MEDIA_FOCUS,
COMPOSE_SET_STATUS,
+ COMPOSE_FOCUS,
} from '../actions/compose';
import { REDRAFT } from '../actions/statuses';
import { STORE_HYDRATE } from '../actions/store';
@@ -651,6 +652,8 @@ export default function compose(state = initialState, action) {
return state.update('poll', poll => poll.set('expires_in', action.expiresIn).set('multiple', action.isMultiple));
case COMPOSE_LANGUAGE_CHANGE:
return state.set('language', action.language);
+ case COMPOSE_FOCUS:
+ return state.set('focusDate', new Date()).update('text', text => text.length > 0 ? text : action.defaultText);
default:
return state;
}
diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss
index 03e0d579cb..fa17098234 100644
--- a/app/javascript/flavours/glitch/styles/components/accounts.scss
+++ b/app/javascript/flavours/glitch/styles/components/accounts.scss
@@ -36,21 +36,36 @@
}
&__note {
+ font-size: 14px;
+ font-weight: 400;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
- -webkit-line-clamp: 2;
+ -webkit-line-clamp: 1;
-webkit-box-orient: vertical;
- color: $ui-secondary-color;
- }
-}
+ margin-top: 10px;
+ color: $darker-text-color;
-.follow-recommendations-account {
- .icon-button {
- color: $ui-primary-color;
+ &--missing {
+ color: $dark-text-color;
+ }
- &.active {
- color: $valid-value-color;
+ p {
+ margin-bottom: 10px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ a {
+ color: inherit;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: none;
+ }
}
}
}
diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss
index 126c68c412..cffafffa78 100644
--- a/app/javascript/flavours/glitch/styles/components/columns.scss
+++ b/app/javascript/flavours/glitch/styles/components/columns.scss
@@ -914,13 +914,7 @@ $ui-header-height: 55px;
.column-title {
text-align: center;
- padding: 40px;
-
- .logo {
- width: 50px;
- margin: 0 auto;
- margin-bottom: 40px;
- }
+ padding-bottom: 40px;
h3 {
font-size: 24px;
@@ -935,45 +929,321 @@ $ui-header-height: 55px;
font-weight: 400;
color: $darker-text-color;
}
-}
-.follow-recommendations-container {
- display: flex;
- flex-direction: column;
-}
-
-.column-actions {
- display: flex;
- align-items: flex-start;
- justify-content: center;
- padding: 40px;
- padding-top: 40px;
- padding-bottom: 200px;
- flex-grow: 1;
- position: relative;
-
- &__background {
- position: absolute;
- inset-inline-start: 0;
- bottom: 0;
- height: 220px;
- width: auto;
+ @media screen and (width >= 600px) {
+ padding: 40px;
}
}
-.column-list {
- margin: 0 20px;
- border: 1px solid lighten($ui-base-color, 8%);
- background: darken($ui-base-color, 2%);
- border-radius: 4px;
+.onboarding__footer {
+ margin-top: 30px;
+ color: $dark-text-color;
+ text-align: center;
+ font-size: 14px;
- &__empty-message {
- padding: 40px;
+ .link-button {
+ display: inline-block;
+ color: inherit;
+ font-size: inherit;
+ }
+}
+
+.onboarding__link {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ color: $highlight-text-color;
+ background: lighten($ui-base-color, 4%);
+ border-radius: 8px;
+ padding: 10px 15px;
+ box-sizing: border-box;
+ font-size: 14px;
+ font-weight: 500;
+ height: 56px;
+ text-decoration: none;
+
+ svg {
+ height: 1.5em;
+ }
+
+ &:hover,
+ &:focus,
+ &:active {
+ background: lighten($ui-base-color, 8%);
+ }
+}
+
+.onboarding__illustration {
+ display: block;
+ margin: 0 auto;
+ margin-bottom: 10px;
+ max-height: 200px;
+ width: auto;
+}
+
+.onboarding__lead {
+ font-size: 16px;
+ line-height: 24px;
+ font-weight: 400;
+ color: $darker-text-color;
+ text-align: center;
+ margin-bottom: 30px;
+
+ strong {
+ font-weight: 700;
+ color: $secondary-text-color;
+ }
+}
+
+.onboarding__links {
+ margin-bottom: 30px;
+
+ & > * {
+ margin-bottom: 2px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+}
+
+.onboarding__steps {
+ margin-bottom: 30px;
+
+ &__item {
+ background: lighten($ui-base-color, 4%);
+ border: 0;
+ border-radius: 8px;
+ display: flex;
+ width: 100%;
+ box-sizing: border-box;
+ align-items: center;
+ gap: 10px;
+ padding: 10px;
+ padding-inline-end: 15px;
+ margin-bottom: 2px;
+ text-decoration: none;
+ text-align: start;
+
+ &:hover,
+ &:focus,
+ &:active {
+ background: lighten($ui-base-color, 8%);
+ }
+
+ &__icon {
+ flex: 0 0 auto;
+ border-radius: 50%;
+ display: none;
+ align-items: center;
+ justify-content: center;
+ width: 36px;
+ height: 36px;
+ color: $highlight-text-color;
+ font-size: 1.2rem;
+
+ @media screen and (width >= 600px) {
+ display: flex;
+ }
+ }
+
+ &__progress {
+ flex: 0 0 auto;
+ background: $valid-value-color;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 21px;
+ height: 21px;
+ color: $primary-text-color;
+
+ svg {
+ height: 14px;
+ width: auto;
+ }
+ }
+
+ &__go {
+ flex: 0 0 auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 21px;
+ height: 21px;
+ color: $highlight-text-color;
+ font-size: 17px;
+
+ svg {
+ height: 1.5em;
+ width: auto;
+ }
+ }
+
+ &__description {
+ flex: 1 1 auto;
+ line-height: 20px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+
+ h6 {
+ color: $highlight-text-color;
+ font-weight: 500;
+ font-size: 14px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ p {
+ color: $darker-text-color;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+}
+
+.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;
+ margin-bottom: 30px;
+
+ .account:last-child {
+ border-bottom: 0;
+ }
+
+ &__empty {
text-align: center;
- font-size: 16px;
- line-height: 24px;
- font-weight: 400;
color: $darker-text-color;
+ font-weight: 500;
+ padding: 40px;
+ }
+}
+
+.tip-carousel {
+ border: 1px solid transparent;
+ border-radius: 8px;
+ padding: 16px;
+ margin-bottom: 30px;
+
+ &:focus {
+ outline: 0;
+ border-color: $highlight-text-color;
+ }
+
+ .media-modal__pagination {
+ margin-bottom: 0;
+ }
+}
+
+.copy-paste-text {
+ background: lighten($ui-base-color, 4%);
+ border-radius: 8px;
+ border: 1px solid lighten($ui-base-color, 8%);
+ padding: 16px;
+ color: $primary-text-color;
+ font-size: 15px;
+ line-height: 22px;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ transition: border-color 300ms linear;
+ margin-bottom: 30px;
+
+ &:focus,
+ &.focused {
+ transition: none;
+ outline: 0;
+ border-color: $highlight-text-color;
+ }
+
+ &.copied {
+ border-color: $valid-value-color;
+ transition: none;
+ }
+
+ textarea {
+ width: 100%;
+ height: auto;
+ background: transparent;
+ color: inherit;
+ font: inherit;
+ border: 0;
+ padding: 0;
+ margin-bottom: 30px;
+ resize: none;
+
+ &:focus {
+ outline: 0;
+ }
+ }
+}
+
+.compose-form__highlightable {
+ display: flex;
+ flex-direction: column;
+ flex: 0 1 auto;
+ border-radius: 4px;
+ transition: box-shadow 300ms linear;
+ min-height: 0;
+
+ &.active {
+ transition: none;
+ box-shadow: 0 0 0 6px rgba(lighten($highlight-text-color, 8%), 0.7);
}
}
@@ -1096,4 +1366,9 @@ $ui-header-height: 55px;
font-weight: 700;
}
}
+
+ &:focus {
+ outline: 0;
+ background-color: $highlight-text-color;
+ }
}
diff --git a/app/javascript/flavours/glitch/styles/components/misc.scss b/app/javascript/flavours/glitch/styles/components/misc.scss
index 88e23eb43b..f16504c93e 100644
--- a/app/javascript/flavours/glitch/styles/components/misc.scss
+++ b/app/javascript/flavours/glitch/styles/components/misc.scss
@@ -406,6 +406,11 @@ body > [data-popper-placement] {
text-overflow: ellipsis;
white-space: nowrap;
+ &__account {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+
a {
color: inherit;
text-decoration: inherit;
diff --git a/app/javascript/flavours/glitch/styles/components/single_column.scss b/app/javascript/flavours/glitch/styles/components/single_column.scss
index df3bcdce22..87fdd170d3 100644
--- a/app/javascript/flavours/glitch/styles/components/single_column.scss
+++ b/app/javascript/flavours/glitch/styles/components/single_column.scss
@@ -43,7 +43,6 @@
.compose-form {
flex: 1;
- overflow-y: hidden;
display: flex;
flex-direction: column;
min-height: 310px;