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/account/components/header.js28
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/components/header.js6
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js7
-rw-r--r--app/javascript/flavours/glitch/features/hashtag_timeline/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/home_timeline/index.js1
-rw-r--r--app/javascript/flavours/glitch/features/interaction_modal/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js4
-rw-r--r--app/javascript/flavours/glitch/features/status/components/action_bar.js24
-rw-r--r--app/javascript/flavours/glitch/features/status/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/image_modal.js59
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/modal_root.js2
11 files changed, 108 insertions, 29 deletions
diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js
index cf1494f05..93831b3e7 100644
--- a/app/javascript/flavours/glitch/features/account/components/header.js
+++ b/app/javascript/flavours/glitch/features/account/components/header.js
@@ -53,6 +53,7 @@ const messages = defineMessages({
   admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
   add_account_note: { id: 'account.add_account_note', defaultMessage: 'Add note for @{name}' },
   languages: { id: 'account.languages', defaultMessage: 'Change subscribed languages' },
+  openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
 });
 
 const titleFromAccount = account => {
@@ -97,6 +98,7 @@ class Header extends ImmutablePureComponent {
     onEditAccountNote: PropTypes.func.isRequired,
     onChangeLanguages: PropTypes.func.isRequired,
     onInteractionModal: PropTypes.func.isRequired,
+    onOpenAvatar: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
     domain: PropTypes.string.isRequired,
     hidden: PropTypes.bool,
@@ -132,6 +134,13 @@ class Header extends ImmutablePureComponent {
     }
   }
 
+  handleAvatarClick = e => {
+    if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+      e.preventDefault();
+      this.props.onOpenAvatar();
+    }
+  }
+
   render () {
     const { account, hidden, intl, domain } = this.props;
     const { signedIn } = this.context.identity;
@@ -142,7 +151,9 @@ class Header extends ImmutablePureComponent {
 
     const accountNote = account.getIn(['relationship', 'note']);
 
-    const suspended = account.get('suspended');
+    const suspended    = account.get('suspended');
+    const isRemote     = account.get('acct') !== account.get('username');
+    const remoteDomain = isRemote ? account.get('acct').split('@')[1] : null;
 
     let info        = [];
     let actionBtn   = '';
@@ -199,6 +210,11 @@ class Header extends ImmutablePureComponent {
       menu.push(null);
     }
 
+    if (isRemote) {
+      menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') });
+      menu.push(null);
+    }
+
     if ('share' in navigator && !suspended) {
       menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
       menu.push(null);
@@ -253,15 +269,13 @@ class Header extends ImmutablePureComponent {
       menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport });
     }
 
-    if (signedIn && account.get('acct') !== account.get('username')) {
-      const domain = account.get('acct').split('@')[1];
-
+    if (signedIn && isRemote) {
       menu.push(null);
 
       if (account.getIn(['relationship', 'domain_blocking'])) {
-        menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain });
+        menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain: remoteDomain }), action: this.props.onUnblockDomain });
       } else {
-        menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain });
+        menu.push({ text: intl.formatMessage(messages.blockDomain, { domain: remoteDomain }), action: this.props.onBlockDomain });
       }
     }
 
@@ -299,7 +313,7 @@ class Header extends ImmutablePureComponent {
 
         <div className='account__header__bar'>
           <div className='account__header__tabs'>
-            <a className='avatar' href={account.get('url')} rel='noopener noreferrer' target='_blank'>
+            <a className='avatar' href={account.get('avatar')} rel='noopener noreferrer' target='_blank' onClick={this.handleAvatarClick}>
               <Avatar account={suspended || hidden ? undefined : account} size={90} />
             </a>
 
diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.js b/app/javascript/flavours/glitch/features/account_timeline/components/header.js
index eb332e296..90c4c9d51 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/components/header.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/components/header.js
@@ -25,6 +25,7 @@ export default class Header extends ImmutablePureComponent {
     onAddToList: PropTypes.func.isRequired,
     onChangeLanguages: PropTypes.func.isRequired,
     onInteractionModal: PropTypes.func.isRequired,
+    onOpenAvatar: PropTypes.func.isRequired,
     hideTabs: PropTypes.bool,
     domain: PropTypes.string.isRequired,
     hidden: PropTypes.bool,
@@ -102,6 +103,10 @@ export default class Header extends ImmutablePureComponent {
     this.props.onInteractionModal(this.props.account);
   }
 
+  handleOpenAvatar = () => {
+    this.props.onOpenAvatar(this.props.account);
+  }
+
   render () {
     const { account, hidden, hideTabs } = this.props;
 
@@ -130,6 +135,7 @@ export default class Header extends ImmutablePureComponent {
           onEditAccountNote={this.handleEditAccountNote}
           onChangeLanguages={this.handleChangeLanguages}
           onInteractionModal={this.handleInteractionModal}
+          onOpenAvatar={this.handleOpenAvatar}
           domain={this.props.domain}
           hidden={hidden}
         />
diff --git a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js
index a65463243..25bcd0119 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js
@@ -161,6 +161,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
     }));
   },
 
+  onOpenAvatar (account) {
+    dispatch(openModal('IMAGE', {
+      src: account.get('avatar'),
+      alt: account.get('acct'),
+    }));
+  },
+
 });
 
 export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/index.js b/app/javascript/flavours/glitch/features/hashtag_timeline/index.js
index f1827789f..4428fdecf 100644
--- a/app/javascript/flavours/glitch/features/hashtag_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/hashtag_timeline/index.js
@@ -194,7 +194,7 @@ class HashtagTimeline extends React.PureComponent {
       const following = tag.get('following');
 
       followButton = (
-        <button className={classNames('column-header__button')} onClick={this.handleFollow} disabled={!signedIn} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-pressed={following ? 'true' : 'false'}>
+        <button className={classNames('column-header__button')} onClick={this.handleFollow} disabled={!signedIn} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)}>
           <Icon id={following ? 'user-times' : 'user-plus'} fixedWidth className='column-header__icon' />
         </button>
       );
diff --git a/app/javascript/flavours/glitch/features/home_timeline/index.js b/app/javascript/flavours/glitch/features/home_timeline/index.js
index 23d0440a9..5ed108ad2 100644
--- a/app/javascript/flavours/glitch/features/home_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/home_timeline/index.js
@@ -131,7 +131,6 @@ class HomeTimeline extends React.PureComponent {
           className={classNames('column-header__button', { 'active': showAnnouncements })}
           title={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}
           aria-label={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}
-          aria-pressed={showAnnouncements ? 'true' : 'false'}
           onClick={this.handleToggleAnnouncementsClick}
         >
           <IconWithBadge id='bullhorn' count={unreadAnnouncements} />
diff --git a/app/javascript/flavours/glitch/features/interaction_modal/index.js b/app/javascript/flavours/glitch/features/interaction_modal/index.js
index 4cd8e51de..b71c041c9 100644
--- a/app/javascript/flavours/glitch/features/interaction_modal/index.js
+++ b/app/javascript/flavours/glitch/features/interaction_modal/index.js
@@ -150,7 +150,7 @@ class InteractionModal extends React.PureComponent {
 
           <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>
+            <p><FormattedMessage id='interaction_modal.other_server_instructions' defaultMessage='Copy and paste this URL into the search field of your favourite Mastodon app or the web interface of your Mastodon server.' /></p>
             <Copypaste value={url} />
           </div>
         </div>
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 86e24e38f..f05a763e0 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
@@ -207,8 +207,8 @@ class Footer extends ImmutablePureComponent {
     return (
       <div className='picture-in-picture__footer'>
         {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')} />
+        <IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate}  active={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')} 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} href={status.get('url')} />}
       </div>
     );
diff --git a/app/javascript/flavours/glitch/features/status/components/action_bar.js b/app/javascript/flavours/glitch/features/status/components/action_bar.js
index 0e21ca5cc..b6f8a9877 100644
--- a/app/javascript/flavours/glitch/features/status/components/action_bar.js
+++ b/app/javascript/flavours/glitch/features/status/components/action_bar.js
@@ -35,6 +35,7 @@ const messages = defineMessages({
   admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
   admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' },
   copy: { id: 'status.copy', defaultMessage: 'Copy link to status' },
+  openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
 });
 
 export default @injectIntl
@@ -132,22 +133,8 @@ class ActionBar extends React.PureComponent {
   }
 
   handleCopy = () => {
-    const url      = this.props.status.get('url');
-    const textarea = document.createElement('textarea');
-
-    textarea.textContent    = url;
-    textarea.style.position = 'fixed';
-
-    document.body.appendChild(textarea);
-
-    try {
-      textarea.select();
-      document.execCommand('copy');
-    } catch (e) {
-
-    } finally {
-      document.body.removeChild(textarea);
-    }
+    const url = this.props.status.get('url');
+    navigator.clipboard.writeText(url);
   }
 
   render () {
@@ -158,10 +145,15 @@ class ActionBar extends React.PureComponent {
     const pinnableStatus     = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
     const mutingConversation = status.get('muted');
     const writtenByMe        = status.getIn(['account', 'id']) === me;
+    const isRemote           = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']);
 
     let menu = [];
 
     if (publicStatus) {
+      if (isRemote) {
+        menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
+      }
+
       menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
       menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
       menu.push(null);
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index f59e8c7f6..aaa9c7928 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -648,7 +648,7 @@ class Status extends ImmutablePureComponent {
           showBackButton
           multiColumn={multiColumn}
           extraButton={(
-            <button className='column-header__button' title={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={!isExpanded ? 'false' : 'true'}><Icon id={!isExpanded ? 'eye-slash' : 'eye'} /></button>
+            <button className='column-header__button' title={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll}><Icon id={!isExpanded ? 'eye-slash' : 'eye'} /></button>
           )}
         />
 
diff --git a/app/javascript/flavours/glitch/features/ui/components/image_modal.js b/app/javascript/flavours/glitch/features/ui/components/image_modal.js
new file mode 100644
index 000000000..a792b9be7
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/ui/components/image_modal.js
@@ -0,0 +1,59 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import { defineMessages, injectIntl } from 'react-intl';
+import IconButton from 'flavours/glitch/components/icon_button';
+import ImageLoader from './image_loader';
+
+const messages = defineMessages({
+  close: { id: 'lightbox.close', defaultMessage: 'Close' },
+});
+
+export default @injectIntl
+class ImageModal extends React.PureComponent {
+
+  static propTypes = {
+    src: PropTypes.string.isRequired,
+    alt: PropTypes.string.isRequired,
+    onClose: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  state = {
+    navigationHidden: false,
+  };
+
+  toggleNavigation = () => {
+    this.setState(prevState => ({
+      navigationHidden: !prevState.navigationHidden,
+    }));
+  };
+
+  render () {
+    const { intl, src, alt, onClose } = this.props;
+    const { navigationHidden } = this.state;
+
+    const navigationClassName = classNames('media-modal__navigation', {
+      'media-modal__navigation--hidden': navigationHidden,
+    });
+
+    return (
+      <div className='modal-root__modal media-modal'>
+        <div className='media-modal__closer' role='presentation' onClick={onClose} >
+          <ImageLoader
+            src={src}
+            width={400}
+            height={400}
+            alt={alt}
+            onClick={this.toggleNavigation}
+          />
+        </div>
+
+        <div className={navigationClassName}>
+          <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={40} />
+        </div>
+      </div>
+    );
+  }
+
+}
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 93834f60e..d2ee28948 100644
--- a/app/javascript/flavours/glitch/features/ui/components/modal_root.js
+++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.js
@@ -15,6 +15,7 @@ import DoodleModal from './doodle_modal';
 import ConfirmationModal from './confirmation_modal';
 import FocalPointModal from './focal_point_modal';
 import DeprecatedSettingsModal from './deprecated_settings_modal';
+import ImageModal from './image_modal';
 import {
   OnboardingModal,
   MuteModal,
@@ -38,6 +39,7 @@ const MODAL_COMPONENTS = {
   'ONBOARDING': OnboardingModal,
   'VIDEO': () => Promise.resolve({ default: VideoModal }),
   'AUDIO': () => Promise.resolve({ default: AudioModal }),
+  'IMAGE': () => Promise.resolve({ default: ImageModal }),
   'BOOST': () => Promise.resolve({ default: BoostModal }),
   'FAVOURITE': () => Promise.resolve({ default: FavouriteModal }),
   'DOODLE': () => Promise.resolve({ default: DoodleModal }),