[Glitch] Add interaction modal to logged-out web UI
Port 7fb738c837 to glitch-soc
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
			
			
This commit is contained in:
		
							parent
							
								
									90c6b0aed6
								
							
						
					
					
						commit
						c9fadb3cae
					
				
					 12 changed files with 397 additions and 55 deletions
				
			
		| 
						 | 
				
			
			@ -83,6 +83,7 @@ class Status extends ImmutablePureComponent {
 | 
			
		|||
    onEmbed: PropTypes.func,
 | 
			
		||||
    onHeightChange: PropTypes.func,
 | 
			
		||||
    onToggleHidden: PropTypes.func,
 | 
			
		||||
    onInteractionModal: PropTypes.func,
 | 
			
		||||
    muted: PropTypes.bool,
 | 
			
		||||
    hidden: PropTypes.bool,
 | 
			
		||||
    unread: PropTypes.bool,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -69,6 +69,7 @@ class StatusActionBar extends ImmutablePureComponent {
 | 
			
		|||
    onBookmark: PropTypes.func,
 | 
			
		||||
    onFilter: PropTypes.func,
 | 
			
		||||
    onAddFilter: PropTypes.func,
 | 
			
		||||
    onInteractionModal: PropTypes.func,
 | 
			
		||||
    withDismiss: PropTypes.bool,
 | 
			
		||||
    withCounters: PropTypes.bool,
 | 
			
		||||
    showReplyCount: PropTypes.bool,
 | 
			
		||||
| 
						 | 
				
			
			@ -86,10 +87,12 @@ class StatusActionBar extends ImmutablePureComponent {
 | 
			
		|||
  ]
 | 
			
		||||
 | 
			
		||||
  handleReplyClick = () => {
 | 
			
		||||
    if (me) {
 | 
			
		||||
    const { signedIn } = this.context.identity;
 | 
			
		||||
 | 
			
		||||
    if (signedIn) {
 | 
			
		||||
      this.props.onReply(this.props.status, this.context.router.history);
 | 
			
		||||
    } else {
 | 
			
		||||
      this._openInteractionDialog('reply');
 | 
			
		||||
      this.props.onInteractionModal('reply', this.props.status);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -101,10 +104,22 @@ class StatusActionBar extends ImmutablePureComponent {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  handleFavouriteClick = (e) => {
 | 
			
		||||
    if (me) {
 | 
			
		||||
    const { signedIn } = this.context.identity;
 | 
			
		||||
 | 
			
		||||
    if (signedIn) {
 | 
			
		||||
      this.props.onFavourite(this.props.status, e);
 | 
			
		||||
    } else {
 | 
			
		||||
      this._openInteractionDialog('favourite');
 | 
			
		||||
      this.props.onInteractionModal('favourite', this.props.status);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleReblogClick = e => {
 | 
			
		||||
    const { signedIn } = this.context.identity;
 | 
			
		||||
 | 
			
		||||
    if (signedIn) {
 | 
			
		||||
      this.props.onReblog(this.props.status, e);
 | 
			
		||||
    } else {
 | 
			
		||||
      this.props.onInteractionModal('reblog', this.props.status);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -112,18 +127,6 @@ class StatusActionBar extends ImmutablePureComponent {
 | 
			
		|||
    this.props.onBookmark(this.props.status, e);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleReblogClick = e => {
 | 
			
		||||
    if (me) {
 | 
			
		||||
      this.props.onReblog(this.props.status, e);
 | 
			
		||||
    } else {
 | 
			
		||||
      this._openInteractionDialog('reblog');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _openInteractionDialog = type => {
 | 
			
		||||
    window.open(`/interact/${this.props.status.get('id')}?type=${type}`, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
  handleDeleteClick = () => {
 | 
			
		||||
    this.props.onDelete(this.props.status, this.context.router.history);
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -244,6 +244,14 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
 | 
			
		|||
    });
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  onInteractionModal (type, status) {
 | 
			
		||||
    dispatch(openModal('INTERACTION', {
 | 
			
		||||
      type,
 | 
			
		||||
      accountId: status.getIn(['account', 'id']),
 | 
			
		||||
      url: status.get('url'),
 | 
			
		||||
    }));
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -96,6 +96,7 @@ class Header extends ImmutablePureComponent {
 | 
			
		|||
    onAddToList: PropTypes.func.isRequired,
 | 
			
		||||
    onEditAccountNote: PropTypes.func.isRequired,
 | 
			
		||||
    onChangeLanguages: PropTypes.func.isRequired,
 | 
			
		||||
    onInteractionModal: PropTypes.func.isRequired,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
    domain: PropTypes.string.isRequired,
 | 
			
		||||
    hidden: PropTypes.bool,
 | 
			
		||||
| 
						 | 
				
			
			@ -172,7 +173,7 @@ class Header extends ImmutablePureComponent {
 | 
			
		|||
      } else if (account.getIn(['relationship', 'requested'])) {
 | 
			
		||||
        actionBtn = <Button className={classNames('logo-button', { 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
 | 
			
		||||
      } else if (!account.getIn(['relationship', 'blocking'])) {
 | 
			
		||||
        actionBtn = <Button className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']), 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={signedIn ? this.props.onFollow : undefined} />;
 | 
			
		||||
        actionBtn = <Button className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']), 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={signedIn ? this.props.onFollow : this.props.onInteractionModal} />;
 | 
			
		||||
      } else if (account.getIn(['relationship', 'blocking'])) {
 | 
			
		||||
        actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,7 @@ export default class Header extends ImmutablePureComponent {
 | 
			
		|||
    onEndorseToggle: PropTypes.func.isRequired,
 | 
			
		||||
    onAddToList: PropTypes.func.isRequired,
 | 
			
		||||
    onChangeLanguages: PropTypes.func.isRequired,
 | 
			
		||||
    onInteractionModal: PropTypes.func.isRequired,
 | 
			
		||||
    hideTabs: PropTypes.bool,
 | 
			
		||||
    domain: PropTypes.string.isRequired,
 | 
			
		||||
    hidden: PropTypes.bool,
 | 
			
		||||
| 
						 | 
				
			
			@ -97,6 +98,10 @@ export default class Header extends ImmutablePureComponent {
 | 
			
		|||
    this.props.onChangeLanguages(this.props.account);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleInteractionModal = () => {
 | 
			
		||||
    this.props.onInteractionModal(this.props.account);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { account, hidden, hideTabs } = this.props;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -124,6 +129,7 @@ export default class Header extends ImmutablePureComponent {
 | 
			
		|||
          onAddToList={this.handleAddToList}
 | 
			
		||||
          onEditAccountNote={this.handleEditAccountNote}
 | 
			
		||||
          onChangeLanguages={this.handleChangeLanguages}
 | 
			
		||||
          onInteractionModal={this.handleInteractionModal}
 | 
			
		||||
          domain={this.props.domain}
 | 
			
		||||
          hidden={hidden}
 | 
			
		||||
        />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -58,6 +58,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 | 
			
		|||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  onInteractionModal (account) {
 | 
			
		||||
    dispatch(openModal('INTERACTION', {
 | 
			
		||||
      type: 'follow',
 | 
			
		||||
      accountId: account.get('id'),
 | 
			
		||||
      url: account.get('url'),
 | 
			
		||||
    }));
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  onBlock (account) {
 | 
			
		||||
    if (account.getIn(['relationship', 'blocking'])) {
 | 
			
		||||
      dispatch(unblockAccount(account.get('id')));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,132 @@
 | 
			
		|||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import { FormattedMessage } from 'react-intl';
 | 
			
		||||
import { registrationsOpen } from 'flavours/glitch/util/initial_state';
 | 
			
		||||
import { connect } from 'react-redux';
 | 
			
		||||
import Icon from 'flavours/glitch/components/icon';
 | 
			
		||||
import classNames from 'classnames';
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = (state, { accountId }) => ({
 | 
			
		||||
  displayNameHtml: state.getIn(['accounts', accountId, 'display_name_html']),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
class Copypaste extends React.PureComponent {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    value: PropTypes.string,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  state = {
 | 
			
		||||
    copied: false,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  setRef = c => {
 | 
			
		||||
    this.input = c;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleInputClick = () => {
 | 
			
		||||
    this.setState({ copied: false });
 | 
			
		||||
    this.input.focus();
 | 
			
		||||
    this.input.select();
 | 
			
		||||
    this.input.setSelectionRange(0, this.input.value.length);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleButtonClick = () => {
 | 
			
		||||
    const { value } = this.props;
 | 
			
		||||
    navigator.clipboard.writeText(value);
 | 
			
		||||
    this.input.blur();
 | 
			
		||||
    this.setState({ copied: true });
 | 
			
		||||
    this.timeout = setTimeout(() => this.setState({ copied: false }), 700);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentWillUnmount () {
 | 
			
		||||
    if (this.timeout) clearTimeout(this.timeout);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { value } = this.props;
 | 
			
		||||
    const { copied } = this.state;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div className={classNames('copypaste', { copied })}>
 | 
			
		||||
        <input
 | 
			
		||||
          type='text'
 | 
			
		||||
          ref={this.setRef}
 | 
			
		||||
          value={value}
 | 
			
		||||
          readOnly
 | 
			
		||||
          onClick={this.handleInputClick}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <button className='button' onClick={this.handleButtonClick}>
 | 
			
		||||
          {copied ? <FormattedMessage id='copypaste.copied' defaultMessage='Copied' /> : <FormattedMessage id='copypaste.copy' defaultMessage='Copy' />}
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default @connect(mapStateToProps)
 | 
			
		||||
class InteractionModal extends React.PureComponent {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    displayNameHtml: PropTypes.string,
 | 
			
		||||
    url: PropTypes.string,
 | 
			
		||||
    type: PropTypes.oneOf(['reply', 'reblog', 'favourite', 'follow']),
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { url, type, displayNameHtml } = this.props;
 | 
			
		||||
 | 
			
		||||
    const name = <bdi dangerouslySetInnerHTML={{ __html: displayNameHtml }} />;
 | 
			
		||||
 | 
			
		||||
    let title, actionDescription, icon;
 | 
			
		||||
 | 
			
		||||
    switch(type) {
 | 
			
		||||
    case 'reply':
 | 
			
		||||
      icon = <Icon id='reply' />;
 | 
			
		||||
      title = <FormattedMessage id='interaction_modal.title.reply' defaultMessage="Reply to {name}'s post" values={{ name }} />;
 | 
			
		||||
      actionDescription = <FormattedMessage id='interaction_modal.description.reply' defaultMessage='With an account on Mastodon, you can respond to this post.' />;
 | 
			
		||||
      break;
 | 
			
		||||
    case 'reblog':
 | 
			
		||||
      icon = <Icon id='retweet' />;
 | 
			
		||||
      title = <FormattedMessage id='interaction_modal.title.reblog' defaultMessage="Boost {name}'s post" values={{ name }} />;
 | 
			
		||||
      actionDescription = <FormattedMessage id='interaction_modal.description.reblog' defaultMessage='With an account on Mastodon, you can boost this post to share it with your own followers.' />;
 | 
			
		||||
      break;
 | 
			
		||||
    case 'favourite':
 | 
			
		||||
      icon = <Icon id='star' />;
 | 
			
		||||
      title = <FormattedMessage id='interaction_modal.title.favourite' defaultMessage="Favourite {name}'s post" values={{ name }} />;
 | 
			
		||||
      actionDescription = <FormattedMessage id='interaction_modal.description.favourite' defaultMessage='With an account on Mastodon, you can favourite this post to let the author know you appreciate it and save it for later.' />;
 | 
			
		||||
      break;
 | 
			
		||||
    case 'follow':
 | 
			
		||||
      icon = <Icon id='user-plus' />;
 | 
			
		||||
      title = <FormattedMessage id='interaction_modal.title.follow' defaultMessage='Follow {name}' values={{ name }} />;
 | 
			
		||||
      actionDescription = <FormattedMessage id='interaction_modal.description.follow' defaultMessage='With an account on Mastodon, you can follow {name} to receive their posts in your home feed.' values={{ name }} />;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div className='modal-root__modal interaction-modal'>
 | 
			
		||||
        <div className='interaction-modal__lead'>
 | 
			
		||||
          <h3><span className='interaction-modal__icon'>{icon}</span> {title}</h3>
 | 
			
		||||
          <p>{actionDescription} <FormattedMessage id='interaction_modal.preamble' defaultMessage="Since Mastodon is decentralized, you can use your existing account hosted by another Mastodon server or compatible platform if you don't have an account on this one." /></p>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div className='interaction-modal__choices'>
 | 
			
		||||
          <div className='interaction-modal__choices__choice'>
 | 
			
		||||
            <h3><FormattedMessage id='interaction_modal.on_this_server' defaultMessage='On this server' /></h3>
 | 
			
		||||
            <a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
 | 
			
		||||
            <a href={registrationsOpen ? '/auth/sign_up' : 'https://joinmastodon.org/servers'} className='button button--block button-tertiary'><FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /></a>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div className='interaction-modal__choices__choice'>
 | 
			
		||||
            <h3><FormattedMessage id='interaction_modal.on_another_server' defaultMessage='On a different server' /></h3>
 | 
			
		||||
            <p><FormattedMessage id='interaction_modal.other_server_instructions' defaultMessage='Simply copy and paste this URL into the search bar of your favourite app or the web interface where you are signed in.' /></p>
 | 
			
		||||
            <Copypaste value={url} />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -44,6 +44,7 @@ class Footer extends ImmutablePureComponent {
 | 
			
		|||
 | 
			
		||||
  static contextTypes = {
 | 
			
		||||
    router: PropTypes.object,
 | 
			
		||||
    identity: PropTypes.object,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
| 
						 | 
				
			
			@ -69,26 +70,44 @@ class Footer extends ImmutablePureComponent {
 | 
			
		|||
  };
 | 
			
		||||
 | 
			
		||||
  handleReplyClick = () => {
 | 
			
		||||
    const { dispatch, askReplyConfirmation, intl } = this.props;
 | 
			
		||||
    const { dispatch, askReplyConfirmation, status, intl } = this.props;
 | 
			
		||||
    const { signedIn } = this.context.identity;
 | 
			
		||||
 | 
			
		||||
    if (askReplyConfirmation) {
 | 
			
		||||
      dispatch(openModal('CONFIRM', {
 | 
			
		||||
        message: intl.formatMessage(messages.replyMessage),
 | 
			
		||||
        confirm: intl.formatMessage(messages.replyConfirm),
 | 
			
		||||
        onConfirm: this._performReply,
 | 
			
		||||
      }));
 | 
			
		||||
    if (signedIn) {
 | 
			
		||||
      if (askReplyConfirmation) {
 | 
			
		||||
        dispatch(openModal('CONFIRM', {
 | 
			
		||||
          message: intl.formatMessage(messages.replyMessage),
 | 
			
		||||
          confirm: intl.formatMessage(messages.replyConfirm),
 | 
			
		||||
          onConfirm: this._performReply,
 | 
			
		||||
        }));
 | 
			
		||||
      } else {
 | 
			
		||||
        this._performReply();
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      this._performReply();
 | 
			
		||||
      dispatch(openModal('INTERACTION', {
 | 
			
		||||
        type: 'reply',
 | 
			
		||||
        accountId: status.getIn(['account', 'id']),
 | 
			
		||||
        url: status.get('url'),
 | 
			
		||||
      }));
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  handleFavouriteClick = () => {
 | 
			
		||||
    const { dispatch, status } = this.props;
 | 
			
		||||
    const { signedIn } = this.context.identity;
 | 
			
		||||
 | 
			
		||||
    if (status.get('favourited')) {
 | 
			
		||||
      dispatch(unfavourite(status));
 | 
			
		||||
    if (signedIn) {
 | 
			
		||||
      if (status.get('favourited')) {
 | 
			
		||||
        dispatch(unfavourite(status));
 | 
			
		||||
      } else {
 | 
			
		||||
        dispatch(favourite(status));
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      dispatch(favourite(status));
 | 
			
		||||
      dispatch(openModal('INTERACTION', {
 | 
			
		||||
        type: 'favourite',
 | 
			
		||||
        accountId: status.getIn(['account', 'id']),
 | 
			
		||||
        url: status.get('url'),
 | 
			
		||||
      }));
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -99,13 +118,22 @@ class Footer extends ImmutablePureComponent {
 | 
			
		|||
 | 
			
		||||
  handleReblogClick = e => {
 | 
			
		||||
    const { dispatch, status } = this.props;
 | 
			
		||||
    const { signedIn } = this.context.identity;
 | 
			
		||||
 | 
			
		||||
    if (status.get('reblogged')) {
 | 
			
		||||
      dispatch(unreblog(status));
 | 
			
		||||
    } else if ((e && e.shiftKey) || !boostModal) {
 | 
			
		||||
      this._performReblog();
 | 
			
		||||
    if (signedIn) {
 | 
			
		||||
      if (status.get('reblogged')) {
 | 
			
		||||
        dispatch(unreblog(status));
 | 
			
		||||
      } else if ((e && e.shiftKey) || !boostModal) {
 | 
			
		||||
        this._performReblog();
 | 
			
		||||
      } else {
 | 
			
		||||
        dispatch(initBoostModal({ status, onReblog: this._performReblog }));
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      dispatch(initBoostModal({ status, onReblog: this._performReblog }));
 | 
			
		||||
      dispatch(openModal('INTERACTION', {
 | 
			
		||||
        type: 'reblog',
 | 
			
		||||
        accountId: status.getIn(['account', 'id']),
 | 
			
		||||
        url: status.get('url'),
 | 
			
		||||
      }));
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -152,6 +152,7 @@ class ActionBar extends React.PureComponent {
 | 
			
		|||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { status, intl } = this.props;
 | 
			
		||||
    const { signedIn, permissions } = this.context.identity;
 | 
			
		||||
 | 
			
		||||
    const publicStatus       = ['public', 'unlisted'].includes(status.get('visibility'));
 | 
			
		||||
    const pinnableStatus     = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
 | 
			
		||||
| 
						 | 
				
			
			@ -184,7 +185,7 @@ class ActionBar extends React.PureComponent {
 | 
			
		|||
      menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
 | 
			
		||||
      menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
 | 
			
		||||
      if ((this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS && (accountAdminLink || statusAdminLink)) {
 | 
			
		||||
      if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS && (accountAdminLink || statusAdminLink)) {
 | 
			
		||||
        menu.push(null);
 | 
			
		||||
        if (accountAdminLink !== undefined) {
 | 
			
		||||
          menu.push({
 | 
			
		||||
| 
						 | 
				
			
			@ -224,7 +225,7 @@ class ActionBar extends React.PureComponent {
 | 
			
		|||
        <div className='detailed-status__button'><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} /></div>
 | 
			
		||||
        <div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
 | 
			
		||||
        {shareButton}
 | 
			
		||||
        <div className='detailed-status__button'><IconButton className='bookmark-icon' active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div>
 | 
			
		||||
        <div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div>
 | 
			
		||||
 | 
			
		||||
        <div className='detailed-status__action-bar-dropdown'>
 | 
			
		||||
          <DropdownMenuContainer size={18} icon='ellipsis-h' items={menu} direction='left' title={intl.formatMessage(messages.more)} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -171,6 +171,7 @@ class Status extends ImmutablePureComponent {
 | 
			
		|||
 | 
			
		||||
  static contextTypes = {
 | 
			
		||||
    router: PropTypes.object,
 | 
			
		||||
    identity: PropTypes.object,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
| 
						 | 
				
			
			@ -263,14 +264,25 @@ class Status extends ImmutablePureComponent {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  handleFavouriteClick = (status, e) => {
 | 
			
		||||
    if (status.get('favourited')) {
 | 
			
		||||
      this.props.dispatch(unfavourite(status));
 | 
			
		||||
    } else {
 | 
			
		||||
      if ((e && e.shiftKey) || !favouriteModal) {
 | 
			
		||||
        this.handleModalFavourite(status);
 | 
			
		||||
    const { dispatch } = this.props;
 | 
			
		||||
    const { signedIn } = this.context.identity;
 | 
			
		||||
 | 
			
		||||
    if (signedIn) {
 | 
			
		||||
      if (status.get('favourited')) {
 | 
			
		||||
        dispatch(unfavourite(status));
 | 
			
		||||
      } else {
 | 
			
		||||
        this.props.dispatch(openModal('FAVOURITE', { status, onFavourite: this.handleModalFavourite }));
 | 
			
		||||
        if ((e && e.shiftKey) || !favouriteModal) {
 | 
			
		||||
          this.handleModalFavourite(status);
 | 
			
		||||
        } else {
 | 
			
		||||
          dispatch(openModal('FAVOURITE', { status, onFavourite: this.handleModalFavourite }));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      dispatch(openModal('INTERACTION', {
 | 
			
		||||
        type: 'favourite',
 | 
			
		||||
        accountId: status.getIn(['account', 'id']),
 | 
			
		||||
        url: status.get('url'),
 | 
			
		||||
      }));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -283,16 +295,26 @@ class Status extends ImmutablePureComponent {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  handleReplyClick = (status) => {
 | 
			
		||||
    let { askReplyConfirmation, dispatch, intl } = this.props;
 | 
			
		||||
    if (askReplyConfirmation) {
 | 
			
		||||
      dispatch(openModal('CONFIRM', {
 | 
			
		||||
        message: intl.formatMessage(messages.replyMessage),
 | 
			
		||||
        confirm: intl.formatMessage(messages.replyConfirm),
 | 
			
		||||
        onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_before_clearing_draft'], false)),
 | 
			
		||||
        onConfirm: () => dispatch(replyCompose(status, this.context.router.history)),
 | 
			
		||||
      }));
 | 
			
		||||
    const { askReplyConfirmation, dispatch, intl } = this.props;
 | 
			
		||||
    const { signedIn } = this.context.identity;
 | 
			
		||||
 | 
			
		||||
    if (signedIn) {
 | 
			
		||||
      if (askReplyConfirmation) {
 | 
			
		||||
        dispatch(openModal('CONFIRM', {
 | 
			
		||||
          message: intl.formatMessage(messages.replyMessage),
 | 
			
		||||
          confirm: intl.formatMessage(messages.replyConfirm),
 | 
			
		||||
          onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_before_clearing_draft'], false)),
 | 
			
		||||
          onConfirm: () => dispatch(replyCompose(status, this.context.router.history)),
 | 
			
		||||
        }));
 | 
			
		||||
      } else {
 | 
			
		||||
        dispatch(replyCompose(status, this.context.router.history));
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      dispatch(replyCompose(status, this.context.router.history));
 | 
			
		||||
      dispatch(openModal('INTERACTION', {
 | 
			
		||||
        type: 'reply',
 | 
			
		||||
        accountId: status.getIn(['account', 'id']),
 | 
			
		||||
        url: status.get('url'),
 | 
			
		||||
      }));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -308,13 +330,22 @@ class Status extends ImmutablePureComponent {
 | 
			
		|||
 | 
			
		||||
  handleReblogClick = (status, e) => {
 | 
			
		||||
    const { settings, dispatch } = this.props;
 | 
			
		||||
    const { signedIn } = this.context.identity;
 | 
			
		||||
 | 
			
		||||
    if (settings.get('confirm_boost_missing_media_description') && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) {
 | 
			
		||||
      dispatch(initBoostModal({ status, onReblog: this.handleModalReblog, missingMediaDescription: true }));
 | 
			
		||||
    } else if ((e && e.shiftKey) || !boostModal) {
 | 
			
		||||
      this.handleModalReblog(status);
 | 
			
		||||
    if (signedIn) {
 | 
			
		||||
      if (settings.get('confirm_boost_missing_media_description') && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) {
 | 
			
		||||
        dispatch(initBoostModal({ status, onReblog: this.handleModalReblog, missingMediaDescription: true }));
 | 
			
		||||
      } else if ((e && e.shiftKey) || !boostModal) {
 | 
			
		||||
        this.handleModalReblog(status);
 | 
			
		||||
      } else {
 | 
			
		||||
        dispatch(initBoostModal({ status, onReblog: this.handleModalReblog }));
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      dispatch(initBoostModal({ status, onReblog: this.handleModalReblog }));
 | 
			
		||||
      dispatch(openModal('INTERACTION', {
 | 
			
		||||
        type: 'reblog',
 | 
			
		||||
        accountId: status.getIn(['account', 'id']),
 | 
			
		||||
        url: status.get('url'),
 | 
			
		||||
      }));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,7 @@ import ConfirmationModal from './confirmation_modal';
 | 
			
		|||
import SubscribedLanguagesModal from 'flavours/glitch/features/subscribed_languages_modal';
 | 
			
		||||
import FocalPointModal from './focal_point_modal';
 | 
			
		||||
import DeprecatedSettingsModal from './deprecated_settings_modal';
 | 
			
		||||
import InteractionModal from 'flavours/glitch/features/interaction_modal';
 | 
			
		||||
import {
 | 
			
		||||
  OnboardingModal,
 | 
			
		||||
  MuteModal,
 | 
			
		||||
| 
						 | 
				
			
			@ -53,6 +54,7 @@ const MODAL_COMPONENTS = {
 | 
			
		|||
  'COMPARE_HISTORY': CompareHistoryModal,
 | 
			
		||||
  'FILTER': FilterModal,
 | 
			
		||||
  'SUBSCRIBED_LANGUAGES': () => Promise.resolve({ default: SubscribedLanguagesModal }),
 | 
			
		||||
  'INTERACTION': () => Promise.resolve({ default: InteractionModal }),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default class ModalRoot extends React.PureComponent {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,7 @@
 | 
			
		|||
  left: 0;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
| 
						 | 
				
			
			@ -1304,3 +1305,123 @@ img.modal-warning {
 | 
			
		|||
  margin-bottom: 15px;
 | 
			
		||||
  width: 60px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.interaction-modal {
 | 
			
		||||
  max-width: 90vw;
 | 
			
		||||
  width: 600px;
 | 
			
		||||
  background: $ui-base-color;
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  display: block;
 | 
			
		||||
  padding: 20px;
 | 
			
		||||
 | 
			
		||||
  h3 {
 | 
			
		||||
    font-size: 22px;
 | 
			
		||||
    line-height: 33px;
 | 
			
		||||
    font-weight: 700;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &__icon {
 | 
			
		||||
    color: $highlight-text-color;
 | 
			
		||||
    margin: 0 5px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &__lead {
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
 | 
			
		||||
    h3 {
 | 
			
		||||
      margin-bottom: 15px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    p {
 | 
			
		||||
      font-size: 17px;
 | 
			
		||||
      line-height: 22px;
 | 
			
		||||
      color: $darker-text-color;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &__choices {
 | 
			
		||||
    display: flex;
 | 
			
		||||
 | 
			
		||||
    &__choice {
 | 
			
		||||
      flex: 0 0 auto;
 | 
			
		||||
      width: 50%;
 | 
			
		||||
      box-sizing: border-box;
 | 
			
		||||
      padding: 20px;
 | 
			
		||||
 | 
			
		||||
      h3 {
 | 
			
		||||
        margin-bottom: 20px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      p {
 | 
			
		||||
        color: $darker-text-color;
 | 
			
		||||
        margin-bottom: 20px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .button {
 | 
			
		||||
        margin-bottom: 10px;
 | 
			
		||||
 | 
			
		||||
        &:last-child {
 | 
			
		||||
          margin-bottom: 0;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @media screen and (max-width: $no-gap-breakpoint - 1px) {
 | 
			
		||||
    &__choices {
 | 
			
		||||
      display: block;
 | 
			
		||||
 | 
			
		||||
      &__choice {
 | 
			
		||||
        width: auto;
 | 
			
		||||
        margin-bottom: 20px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.copypaste {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  gap: 10px;
 | 
			
		||||
 | 
			
		||||
  input {
 | 
			
		||||
    display: block;
 | 
			
		||||
    font-family: inherit;
 | 
			
		||||
    background: darken($ui-base-color, 8%);
 | 
			
		||||
    border: 1px solid $highlight-text-color;
 | 
			
		||||
    color: $darker-text-color;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    padding: 6px 9px;
 | 
			
		||||
    line-height: 22px;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    transition: border-color 300ms linear;
 | 
			
		||||
    flex: 1 1 auto;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
 | 
			
		||||
    &:focus {
 | 
			
		||||
      outline: 0;
 | 
			
		||||
      background: darken($ui-base-color, 4%);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .button {
 | 
			
		||||
    flex: 0 0 auto;
 | 
			
		||||
    transition: background 300ms linear;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.copied {
 | 
			
		||||
    input {
 | 
			
		||||
      border: 1px solid $valid-value-color;
 | 
			
		||||
      transition: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .button {
 | 
			
		||||
      background: $valid-value-color;
 | 
			
		||||
      transition: none;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue