about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/features/account/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch/features/account/components')
-rw-r--r--app/javascript/flavours/glitch/features/account/components/action_bar.js4
-rw-r--r--app/javascript/flavours/glitch/features/account/components/featured_tags.js53
-rw-r--r--app/javascript/flavours/glitch/features/account/components/header.js71
3 files changed, 109 insertions, 19 deletions
diff --git a/app/javascript/flavours/glitch/features/account/components/action_bar.js b/app/javascript/flavours/glitch/features/account/components/action_bar.js
index 23120d57e..ce0584124 100644
--- a/app/javascript/flavours/glitch/features/account/components/action_bar.js
+++ b/app/javascript/flavours/glitch/features/account/components/action_bar.js
@@ -4,8 +4,8 @@ import PropTypes from 'prop-types';
 import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
 import { NavLink } from 'react-router-dom';
 import { injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
-import { me, isStaff } from 'flavours/glitch/util/initial_state';
-import { profileLink, accountAdminLink } from 'flavours/glitch/util/backend_links';
+import { me, isStaff } from 'flavours/glitch/initial_state';
+import { profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links';
 import Icon from 'flavours/glitch/components/icon';
 
 export default @injectIntl
diff --git a/app/javascript/flavours/glitch/features/account/components/featured_tags.js b/app/javascript/flavours/glitch/features/account/components/featured_tags.js
new file mode 100644
index 000000000..d646b08b2
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/account/components/featured_tags.js
@@ -0,0 +1,53 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import Hashtag from 'flavours/glitch/components/hashtag';
+
+const messages = defineMessages({
+  lastStatusAt: { id: 'account.featured_tags.last_status_at', defaultMessage: 'Last post on {date}' },
+  empty: { id: 'account.featured_tags.last_status_never', defaultMessage: 'No posts' },
+});
+
+export default @injectIntl
+class FeaturedTags extends ImmutablePureComponent {
+
+  static contextTypes = {
+    router: PropTypes.object,
+  };
+
+  static propTypes = {
+    account: ImmutablePropTypes.map,
+    featuredTags: ImmutablePropTypes.list,
+    tagged: PropTypes.string,
+    intl: PropTypes.object.isRequired,
+  };
+
+  render () {
+    const { account, featuredTags, intl } = this.props;
+
+    if (!account || account.get('suspended') || featuredTags.isEmpty()) {
+      return null;
+    }
+
+    return (
+      <div className='getting-started__trends'>
+        <h4><FormattedMessage id='account.featured_tags.title' defaultMessage="{name}'s featured hashtags" values={{ name: <bdi dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /> }} /></h4>
+
+        {featuredTags.take(3).map(featuredTag => (
+          <Hashtag
+            key={featuredTag.get('name')}
+            name={featuredTag.get('name')}
+            href={featuredTag.get('url')}
+            to={`/@${account.get('acct')}/tagged/${featuredTag.get('name')}`}
+            uses={featuredTag.get('statuses_count')}
+            withGraph={false}
+            description={((featuredTag.get('statuses_count') * 1) > 0) ? intl.formatMessage(messages.lastStatusAt, { date: intl.formatDate(featuredTag.get('last_status_at'), { month: 'short', day: '2-digit' }) }) : intl.formatMessage(messages.empty)}
+          />
+        ))}
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js
index 53170b7a6..93831b3e7 100644
--- a/app/javascript/flavours/glitch/features/account/components/header.js
+++ b/app/javascript/flavours/glitch/features/account/components/header.js
@@ -3,8 +3,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
-import { autoPlayGif, me } from 'flavours/glitch/util/initial_state';
-import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/util/backend_links';
+import { autoPlayGif, me, domain } from 'flavours/glitch/initial_state';
+import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links';
 import classNames from 'classnames';
 import Icon from 'flavours/glitch/components/icon';
 import IconButton from 'flavours/glitch/components/icon_button';
@@ -14,11 +14,12 @@ import { NavLink } from 'react-router-dom';
 import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
 import AccountNoteContainer from '../containers/account_note_container';
 import { PERMISSION_MANAGE_USERS } from 'flavours/glitch/permissions';
+import { Helmet } from 'react-helmet';
 
 const messages = defineMessages({
   unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
   follow: { id: 'account.follow', defaultMessage: 'Follow' },
-  cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Cancel follow request' },
+  cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
   requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
   unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
   edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
@@ -51,8 +52,18 @@ const messages = defineMessages({
   add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
   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 => {
+  const displayName = account.get('display_name');
+  const acct = account.get('acct') === account.get('username') ? `${account.get('username')}@${domain}` : account.get('acct');
+  const prefix = displayName.trim().length === 0 ? account.get('username') : displayName;
+
+  return `${prefix} (@${acct})`;
+};
+
 const dateFormatOptions = {
   month: 'short',
   day: 'numeric',
@@ -85,6 +96,9 @@ class Header extends ImmutablePureComponent {
     onEndorseToggle: PropTypes.func.isRequired,
     onAddToList: PropTypes.func.isRequired,
     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,
@@ -120,8 +134,16 @@ 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;
 
     if (!account) {
       return null;
@@ -129,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   = '';
@@ -155,12 +179,12 @@ class Header extends ImmutablePureComponent {
     }
 
     if (me !== account.get('id')) {
-      if (!account.get('relationship')) { // Wait until the relationship is loaded
+      if (signedIn && !account.get('relationship')) { // Wait until the relationship is loaded
         actionBtn = '';
       } 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={this.props.onFollow} />;
+        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} />;
       }
@@ -180,12 +204,17 @@ class Header extends ImmutablePureComponent {
       lockedIcon = <Icon id='lock' title={intl.formatMessage(messages.account_locked)} />;
     }
 
-    if (account.get('id') !== me && !suspended) {
+    if (signedIn && account.get('id') !== me && !suspended) {
       menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
       menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
       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);
@@ -207,7 +236,7 @@ class Header extends ImmutablePureComponent {
       menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
       menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
       menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
-    } else {
+    } else if (signedIn) {
       if (account.getIn(['relationship', 'following'])) {
         if (!account.getIn(['relationship', 'muting'])) {
           if (account.getIn(['relationship', 'showing_reblogs'])) {
@@ -215,6 +244,9 @@ class Header extends ImmutablePureComponent {
           } else {
             menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
           }
+
+          menu.push({ text: intl.formatMessage(messages.languages), action: this.props.onChangeLanguages });
+          menu.push(null);
         }
 
         menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
@@ -237,15 +269,13 @@ class Header extends ImmutablePureComponent {
       menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport });
     }
 
-    if (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 });
       }
     }
 
@@ -257,7 +287,9 @@ class Header extends ImmutablePureComponent {
     const content          = { __html: account.get('note_emojified') };
     const displayNameHtml = { __html: account.get('display_name_html') };
     const fields          = account.get('fields');
-    const acct            = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
+    const isLocal         = account.get('acct').indexOf('@') === -1;
+    const acct            = isLocal && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
+    const isIndexable     = !account.get('noindex');
 
     let badge;
 
@@ -281,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>
 
@@ -296,7 +328,7 @@ class Header extends ImmutablePureComponent {
                   </React.Fragment>
                 )}
 
-                <DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />
+                <DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' size={24} direction='right' />
               </div>
             )}
           </div>
@@ -308,7 +340,7 @@ class Header extends ImmutablePureComponent {
             </h1>
           </div>
 
-          <AccountNoteContainer account={account} />
+          {signedIn && <AccountNoteContainer account={account} />}
 
           {!(suspended || hidden) && (
             <div className='account__header__extra'>
@@ -334,6 +366,11 @@ class Header extends ImmutablePureComponent {
             </div>
           )}
         </div>
+
+        <Helmet>
+          <title>{titleFromAccount(account)}</title>
+          <meta name='robots' content={(isLocal && isIndexable) ? 'all' : 'noindex'} />
+        </Helmet>
       </div>
     );
   }