about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/features
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch/features')
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/compose_form.js11
-rw-r--r--app/javascript/flavours/glitch/features/directory/components/account_card.js204
-rw-r--r--app/javascript/flavours/glitch/features/directory/index.js10
-rw-r--r--app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js4
-rw-r--r--app/javascript/flavours/glitch/features/report/category.js2
-rw-r--r--app/javascript/flavours/glitch/features/report/comment.js2
-rw-r--r--app/javascript/flavours/glitch/features/report/statuses.js7
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/modal_root.js9
-rw-r--r--app/javascript/flavours/glitch/features/ui/containers/modal_container.js11
-rw-r--r--app/javascript/flavours/glitch/features/video/index.js4
10 files changed, 116 insertions, 148 deletions
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index c75906ce7..b03bc34b8 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -246,9 +246,14 @@ class ComposeForm extends ImmutablePureComponent {
         selectionStart = selectionEnd = text.length;
       }
       if (textarea) {
-        textarea.setSelectionRange(selectionStart, selectionEnd);
-        textarea.focus();
-        if (!singleColumn) textarea.scrollIntoView();
+        // Because of the wicg-inert polyfill, the activeElement may not be
+        // immediately selectable, we have to wait for observers to run, as
+        // described in https://github.com/WICG/inert#performance-and-gotchas
+        Promise.resolve().then(() => {
+          textarea.setSelectionRange(selectionStart, selectionEnd);
+          textarea.focus();
+          if (!singleColumn) textarea.scrollIntoView();
+        }).catch(console.error);
       }
 
     //  Refocuses the textarea after submitting.
diff --git a/app/javascript/flavours/glitch/features/directory/components/account_card.js b/app/javascript/flavours/glitch/features/directory/components/account_card.js
index 2a3fd1ecf..c9ef5850c 100644
--- a/app/javascript/flavours/glitch/features/directory/components/account_card.js
+++ b/app/javascript/flavours/glitch/features/directory/components/account_card.js
@@ -7,31 +7,28 @@ import { makeGetAccount } from 'flavours/glitch/selectors';
 import Avatar from 'flavours/glitch/components/avatar';
 import DisplayName from 'flavours/glitch/components/display_name';
 import Permalink from 'flavours/glitch/components/permalink';
-import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
-import IconButton from 'flavours/glitch/components/icon_button';
+import Button from 'flavours/glitch/components/button';
 import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
 import { autoPlayGif, me, unfollowModal } from 'flavours/glitch/util/initial_state';
 import ShortNumber from 'flavours/glitch/components/short_number';
 import {
   followAccount,
   unfollowAccount,
-  blockAccount,
   unblockAccount,
   unmuteAccount,
 } from 'flavours/glitch/actions/accounts';
 import { openModal } from 'flavours/glitch/actions/modal';
-import { initMuteModal } from 'flavours/glitch/actions/mutes';
+import classNames from 'classnames';
 
 const messages = defineMessages({
-  follow: { id: 'account.follow', defaultMessage: 'Follow' },
   unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
-  requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
-  unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
-  unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
-  unfollowConfirm: {
-    id: 'confirmations.unfollow.confirm',
-    defaultMessage: 'Unfollow',
-  },
+  follow: { id: 'account.follow', defaultMessage: 'Follow' },
+  cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Cancel follow request' },
+  requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
+  unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
+  unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
+  unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
+  edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
 });
 
 const makeMapStateToProps = () => {
@@ -75,18 +72,15 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
   onBlock(account) {
     if (account.getIn(['relationship', 'blocking'])) {
       dispatch(unblockAccount(account.get('id')));
-    } else {
-      dispatch(blockAccount(account.get('id')));
     }
   },
 
   onMute(account) {
     if (account.getIn(['relationship', 'muting'])) {
       dispatch(unmuteAccount(account.get('id')));
-    } else {
-      dispatch(initMuteModal(account));
     }
   },
+
 });
 
 export default
@@ -138,130 +132,92 @@ class AccountCard extends ImmutablePureComponent {
 
   handleMute = () => {
     this.props.onMute(this.props.account);
-  };
+  }
+
+  handleEditProfile = () => {
+    window.open('/settings/profile', '_blank');
+  }
 
   render() {
     const { account, intl } = this.props;
 
-    let buttons;
-
-    if (
-      account.get('id') !== me &&
-      account.get('relationship', null) !== null
-    ) {
-      const following = account.getIn(['relationship', 'following']);
-      const requested = account.getIn(['relationship', 'requested']);
-      const blocking = account.getIn(['relationship', 'blocking']);
-      const muting = account.getIn(['relationship', 'muting']);
-
-      if (requested) {
-        buttons = (
-          <IconButton
-            disabled
-            icon='hourglass'
-            title={intl.formatMessage(messages.requested)}
-          />
-        );
-      } else if (blocking) {
-        buttons = (
-          <IconButton
-            active
-            icon='unlock'
-            title={intl.formatMessage(messages.unblock, {
-              name: account.get('username'),
-            })}
-            onClick={this.handleBlock}
-          />
-        );
-      } else if (muting) {
-        buttons = (
-          <IconButton
-            active
-            icon='volume-up'
-            title={intl.formatMessage(messages.unmute, {
-              name: account.get('username'),
-            })}
-            onClick={this.handleMute}
-          />
-        );
-      } else if (!account.get('moved') || following) {
-        buttons = (
-          <IconButton
-            icon={following ? 'user-times' : 'user-plus'}
-            title={intl.formatMessage(
-              following ? messages.unfollow : messages.follow,
-            )}
-            onClick={this.handleFollow}
-            active={following}
-          />
-        );
+    let actionBtn;
+
+    if (me !== account.get('id')) {
+      if (!account.get('relationship')) { // Wait until the relationship is loaded
+        actionBtn = '';
+      } else if (account.getIn(['relationship', 'requested'])) {
+        actionBtn = <Button className={classNames('logo-button')} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.handleFollow} />;
+      } else if (account.getIn(['relationship', 'muting'])) {
+        actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unmute)} onClick={this.handleMute} />;
+      } else if (!account.getIn(['relationship', 'blocking'])) {
+        actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
+      } else if (account.getIn(['relationship', 'blocking'])) {
+        actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock)} onClick={this.handleBlock} />;
       }
+    } else {
+      actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.edit_profile)} onClick={this.handleEditProfile} />;
     }
 
     return (
-      <div className='directory__card'>
-        <div className='directory__card__img'>
-          <img
-            src={
-              autoPlayGif ? account.get('header') : account.get('header_static')
-            }
-            alt=''
-          />
-        </div>
+      <div className='account-card'>
+        <Permalink href={account.get('url')} to={`/@${account.get('acct')}`} className='account-card__permalink'>
+          <div className='account-card__header'>
+            <img
+              src={
+                autoPlayGif ? account.get('header') : account.get('header_static')
+              }
+              alt=''
+            />
+          </div>
 
-        <div className='directory__card__bar'>
-          <Permalink
-            className='directory__card__bar__name'
-            href={account.get('url')}
-            to={`/@${account.get('acct')}`}
-          >
-            <Avatar account={account} size={48} />
+          <div className='account-card__title'>
+            <div className='account-card__title__avatar'><Avatar account={account} size={56} /></div>
             <DisplayName account={account} />
-          </Permalink>
-
-          <div className='directory__card__bar__relationship account__relationship'>
-            {buttons}
           </div>
-        </div>
+        </Permalink>
 
-        <div className='directory__card__extra' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
+        {account.get('note').length > 0 && (
           <div
-            className='account__header__content translate'
+            className='account-card__bio translate'
+            onMouseEnter={this.handleMouseEnter}
+            onMouseLeave={this.handleMouseLeave}
             dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
           />
-        </div>
-
-        <div className='directory__card__extra'>
-          <div className='accounts-table__count'>
-            <ShortNumber value={account.get('statuses_count')} />
-            <small>
-              <FormattedMessage id='account.posts' defaultMessage='Toots' />
-            </small>
+        )}
+
+        <div className='account-card__actions'>
+          <div className='account-card__counters'>
+            <div className='account-card__counters__item'>
+              <ShortNumber value={account.get('statuses_count')} />
+              <small>
+                <FormattedMessage id='account.posts' defaultMessage='Toots' />
+              </small>
+            </div>
+
+            <div className='account-card__counters__item'>
+              {account.get('followers_count') < 0 ? '-' : <ShortNumber value={account.get('followers_count')} />}{' '}
+              <small>
+                <FormattedMessage
+                  id='account.followers'
+                  defaultMessage='Followers'
+                />
+              </small>
+            </div>
+
+            <div className='account-card__counters__item'>
+              <ShortNumber value={account.get('following_count')} />{' '}
+              <small>
+                <FormattedMessage
+                  id='account.following'
+                  defaultMessage='Following'
+                />
+              </small>
+            </div>
           </div>
-          <div className='accounts-table__count'>
-            {account.get('followers_count') < 0 ? '-' : <ShortNumber value={account.get('followers_count')} />}{' '}
-            <small>
-              <FormattedMessage
-                id='account.followers'
-                defaultMessage='Followers'
-              />
-            </small>
-          </div>
-          <div className='accounts-table__count'>
-            {account.get('last_status_at') === null ? (
-              <FormattedMessage
-                id='account.never_active'
-                defaultMessage='Never'
-              />
-            ) : (
-              <RelativeTimestamp timestamp={account.get('last_status_at')} />
-            )}{' '}
-            <small>
-              <FormattedMessage
-                id='account.last_status'
-                defaultMessage='Last active'
-              />
-            </small>
+
+          <div className='account-card__actions__button'>
+            {actionBtn}
           </div>
         </div>
       </div>
diff --git a/app/javascript/flavours/glitch/features/directory/index.js b/app/javascript/flavours/glitch/features/directory/index.js
index cde5926e0..87d9b3625 100644
--- a/app/javascript/flavours/glitch/features/directory/index.js
+++ b/app/javascript/flavours/glitch/features/directory/index.js
@@ -10,9 +10,9 @@ import { fetchDirectory, expandDirectory } from 'flavours/glitch/actions/directo
 import { List as ImmutableList } from 'immutable';
 import AccountCard from './components/account_card';
 import RadioButton from 'flavours/glitch/components/radio_button';
-import classNames from 'classnames';
 import LoadMore from 'flavours/glitch/components/load_more';
 import ScrollContainer from 'flavours/glitch/containers/scroll_container';
+import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
 
 const messages = defineMessages({
   title: { id: 'column.directory', defaultMessage: 'Browse profiles' },
@@ -129,7 +129,7 @@ class Directory extends React.PureComponent {
     const pinned = !!columnId;
 
     const scrollableArea = (
-      <div className='scrollable' style={{ background: 'transparent' }}>
+      <div className='scrollable'>
         <div className='filter-form'>
           <div className='filter-form__column' role='group'>
             <RadioButton name='order' value='active' label={intl.formatMessage(messages.recentlyActive)} checked={order === 'active'} onChange={this.handleChangeOrder} />
@@ -142,8 +142,10 @@ class Directory extends React.PureComponent {
           </div>
         </div>
 
-        <div className={classNames('directory__list', { loading: isLoading })}>
-          {accountIds.map(accountId => <AccountCard id={accountId} key={accountId} />)}
+        <div className='directory__list'>
+          {isLoading ? <LoadingIndicator /> : accountIds.map(accountId => (
+            <AccountCard id={accountId} key={accountId} />
+          ))}
         </div>
 
         <LoadMore onClick={this.handleLoadMore} visible={!isLoading} />
diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js
index e01d277a1..0408105ae 100644
--- a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js
+++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js
@@ -62,7 +62,7 @@ class Footer extends ImmutablePureComponent {
     const { router } = this.context;
 
     if (onClose) {
-      onClose();
+      onClose(true);
     }
 
     dispatch(replyCompose(status, router.history));
@@ -181,7 +181,7 @@ class Footer extends ImmutablePureComponent {
         {replyButton}
         <IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate}  active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
         <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
-        {withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' onClick={this.handleOpenClick} />}
+        {withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' onClick={this.handleOpenClick} href={status.get('url')} />}
       </div>
     );
   }
diff --git a/app/javascript/flavours/glitch/features/report/category.js b/app/javascript/flavours/glitch/features/report/category.js
index ddbc82563..cf63533d0 100644
--- a/app/javascript/flavours/glitch/features/report/category.js
+++ b/app/javascript/flavours/glitch/features/report/category.js
@@ -8,7 +8,7 @@ const messages = defineMessages({
   dislike: { id: 'report.reasons.dislike', defaultMessage: 'I don\'t like it' },
   dislike_description: { id: 'report.reasons.dislike_description', defaultMessage: 'It is not something you want to see' },
   spam: { id: 'report.reasons.spam', defaultMessage: 'It\'s spam' },
-  spam_description: { id: 'report.reasons.spam_description', defaultMessage: 'Malicious links, fake engagement, or repetetive replies' },
+  spam_description: { id: 'report.reasons.spam_description', defaultMessage: 'Malicious links, fake engagement, or repetitive replies' },
   violation: { id: 'report.reasons.violation', defaultMessage: 'It violates server rules' },
   violation_description: { id: 'report.reasons.violation_description', defaultMessage: 'You are aware that it breaks specific rules' },
   other: { id: 'report.reasons.other', defaultMessage: 'It\'s something else' },
diff --git a/app/javascript/flavours/glitch/features/report/comment.js b/app/javascript/flavours/glitch/features/report/comment.js
index b2663bbf2..ec261afcb 100644
--- a/app/javascript/flavours/glitch/features/report/comment.js
+++ b/app/javascript/flavours/glitch/features/report/comment.js
@@ -74,7 +74,7 @@ class Comment extends React.PureComponent {
         <div className='flex-spacer' />
 
         <div className='report-dialog-modal__actions'>
-          <Button onClick={this.handleClick}><FormattedMessage id='report.submit' defaultMessage='Submit report' /></Button>
+          <Button onClick={this.handleClick} disabled={isSubmitting}><FormattedMessage id='report.submit' defaultMessage='Submit report' /></Button>
         </div>
       </React.Fragment>
     );
diff --git a/app/javascript/flavours/glitch/features/report/statuses.js b/app/javascript/flavours/glitch/features/report/statuses.js
index 69cfbb3e5..47d5ee863 100644
--- a/app/javascript/flavours/glitch/features/report/statuses.js
+++ b/app/javascript/flavours/glitch/features/report/statuses.js
@@ -6,9 +6,11 @@ import StatusCheckBox from 'flavours/glitch/features/report/containers/status_ch
 import { OrderedSet } from 'immutable';
 import { FormattedMessage } from 'react-intl';
 import Button from 'flavours/glitch/components/button';
+import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
 
 const mapStateToProps = (state, { accountId }) => ({
   availableStatusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])),
+  isLoading: state.getIn(['timelines', `account:${accountId}:with_replies`, 'isLoading']),
 });
 
 export default @connect(mapStateToProps)
@@ -19,6 +21,7 @@ class Statuses extends React.PureComponent {
     accountId: PropTypes.string.isRequired,
     availableStatusIds: ImmutablePropTypes.set.isRequired,
     selectedStatusIds: ImmutablePropTypes.set.isRequired,
+    isLoading: PropTypes.bool,
     onToggle: PropTypes.func.isRequired,
   };
 
@@ -28,7 +31,7 @@ class Statuses extends React.PureComponent {
   };
 
   render () {
-    const { availableStatusIds, selectedStatusIds, onToggle } = this.props;
+    const { availableStatusIds, selectedStatusIds, onToggle, isLoading } = this.props;
 
     return (
       <React.Fragment>
@@ -36,7 +39,7 @@ class Statuses extends React.PureComponent {
         <p className='report-dialog-modal__lead'><FormattedMessage id='report.statuses.subtitle' defaultMessage='Select all that apply' /></p>
 
         <div className='report-dialog-modal__statuses'>
-          {availableStatusIds.union(selectedStatusIds).map(statusId => (
+          {isLoading ? <LoadingIndicator /> : availableStatusIds.union(selectedStatusIds).map(statusId => (
             <StatusCheckBox
               id={statusId}
               key={statusId}
diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.js b/app/javascript/flavours/glitch/features/ui/components/modal_root.js
index 1e065c171..a975c4013 100644
--- a/app/javascript/flavours/glitch/features/ui/components/modal_root.js
+++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.js
@@ -55,6 +55,7 @@ export default class ModalRoot extends React.PureComponent {
     type: PropTypes.string,
     props: PropTypes.object,
     onClose: PropTypes.func.isRequired,
+    ignoreFocus: PropTypes.bool,
   };
 
   state = {
@@ -85,7 +86,7 @@ export default class ModalRoot extends React.PureComponent {
     return <BundleModalError {...props} onClose={onClose} />;
   }
 
-  handleClose = () => {
+  handleClose = (ignoreFocus = false) => {
     const { onClose } = this.props;
     let message = null;
     try {
@@ -95,7 +96,7 @@ export default class ModalRoot extends React.PureComponent {
       // isn't set.
       // This would be much smoother with react-intl 3+ and `forwardRef`.
     }
-    onClose(message);
+    onClose(message, ignoreFocus);
   }
 
   setModalRef = (c) => {
@@ -103,12 +104,12 @@ export default class ModalRoot extends React.PureComponent {
   }
 
   render () {
-    const { type, props } = this.props;
+    const { type, props, ignoreFocus } = this.props;
     const { backgroundColor } = this.state;
     const visible = !!type;
 
     return (
-      <Base backgroundColor={backgroundColor} onClose={this.handleClose} noEsc={props ? props.noEsc : false}>
+      <Base backgroundColor={backgroundColor} onClose={this.handleClose} noEsc={props ? props.noEsc : false} ignoreFocus={ignoreFocus}>
         {visible && (
           <BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}>
             {(SpecificComponent) => <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={this.setModalRef} />}
diff --git a/app/javascript/flavours/glitch/features/ui/containers/modal_container.js b/app/javascript/flavours/glitch/features/ui/containers/modal_container.js
index 039aabd8a..560c34f01 100644
--- a/app/javascript/flavours/glitch/features/ui/containers/modal_container.js
+++ b/app/javascript/flavours/glitch/features/ui/containers/modal_container.js
@@ -3,22 +3,23 @@ import { openModal, closeModal } from 'flavours/glitch/actions/modal';
 import ModalRoot from '../components/modal_root';
 
 const mapStateToProps = state => ({
-  type: state.getIn(['modal', 0, 'modalType'], null),
-  props: state.getIn(['modal', 0, 'modalProps'], {}),
+  ignoreFocus: state.getIn(['modal', 'ignoreFocus']),
+  type: state.getIn(['modal', 'stack', 0, 'modalType'], null),
+  props: state.getIn(['modal', 'stack', 0, 'modalProps'], {}),
 });
 
 const mapDispatchToProps = dispatch => ({
-  onClose (confirmationMessage) {
+  onClose (confirmationMessage, ignoreFocus = false) {
     if (confirmationMessage) {
       dispatch(
         openModal('CONFIRM', {
           message: confirmationMessage.message,
           confirm: confirmationMessage.confirm,
-          onConfirm: () => dispatch(closeModal()),
+          onConfirm: () => dispatch(closeModal(undefined, { ignoreFocus })),
         }),
       );
     } else {
-      dispatch(closeModal());
+      dispatch(closeModal(undefined, { ignoreFocus }));
     }
   },
 });
diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js
index fcbf07ce2..53e3dfda3 100644
--- a/app/javascript/flavours/glitch/features/video/index.js
+++ b/app/javascript/flavours/glitch/features/video/index.js
@@ -123,7 +123,7 @@ class Video extends React.PureComponent {
     autoPlay: PropTypes.bool,
     volume: PropTypes.number,
     muted: PropTypes.bool,
-    componetIndex: PropTypes.number,
+    componentIndex: PropTypes.number,
   };
 
   static defaultProps = {
@@ -516,7 +516,7 @@ class Video extends React.PureComponent {
       startTime: this.video.currentTime,
       autoPlay: !this.state.paused,
       defaultVolume: this.state.volume,
-      componetIndex: this.props.componetIndex,
+      componentIndex: this.props.componentIndex,
     });
   }