about summary refs log tree commit diff
path: root/app/javascript
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript')
-rw-r--r--app/javascript/core/admin.js10
-rw-r--r--app/javascript/mastodon/components/hashtag.js4
-rw-r--r--app/javascript/mastodon/components/media_gallery.js17
-rw-r--r--app/javascript/mastodon/features/directory/components/account_card.js43
-rw-r--r--app/javascript/mastodon/features/ui/components/navigation_panel.js2
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json119
-rw-r--r--app/javascript/mastodon/locales/en.json13
-rw-r--r--app/javascript/mastodon/rtl.js1
-rw-r--r--app/javascript/styles/mastodon/components.scss15
-rw-r--r--app/javascript/styles/mastodon/dashboard.scss2
-rw-r--r--app/javascript/styles/mastodon/footer.scss2
-rw-r--r--app/javascript/styles/mastodon/forms.scss9
12 files changed, 220 insertions, 17 deletions
diff --git a/app/javascript/core/admin.js b/app/javascript/core/admin.js
index 3f6f187bc..ffdabe674 100644
--- a/app/javascript/core/admin.js
+++ b/app/javascript/core/admin.js
@@ -1,6 +1,7 @@
 //  This file will be loaded on admin pages, regardless of theme.
 
 import { delegate } from 'rails-ujs';
+import ready from '../mastodon/ready';
 
 const batchCheckboxClassName = '.batch-checkbox input[type="checkbox"]';
 
@@ -31,7 +32,7 @@ delegate(document, '.media-spoiler-hide-button', 'click', () => {
   });
 });
 
-delegate(document, '#domain_block_severity', 'change', ({ target }) => {
+const onDomainBlockSeverityChange = (target) => {
   const rejectMediaDiv   = document.querySelector('.input.with_label.domain_block_reject_media');
   const rejectReportsDiv = document.querySelector('.input.with_label.domain_block_reject_reports');
 
@@ -42,4 +43,11 @@ delegate(document, '#domain_block_severity', 'change', ({ target }) => {
   if (rejectReportsDiv) {
     rejectReportsDiv.style.display = (target.value === 'suspend') ? 'none' : 'block';
   }
+};
+
+delegate(document, '#domain_block_severity', 'change', ({ target }) => onDomainBlockSeverityChange(target));
+
+ready(() => {
+  const input = document.getElementById('domain_block_severity');
+  if (input) onDomainBlockSeverityChange(input);
 });
diff --git a/app/javascript/mastodon/components/hashtag.js b/app/javascript/mastodon/components/hashtag.js
index f091d7893..62d613262 100644
--- a/app/javascript/mastodon/components/hashtag.js
+++ b/app/javascript/mastodon/components/hashtag.js
@@ -12,11 +12,11 @@ const Hashtag = ({ hashtag }) => (
         #<span>{hashtag.get('name')}</span>
       </Permalink>
 
-      <FormattedMessage id='trends.count_by_accounts' defaultMessage='{count} {rawCount, plural, one {person} other {people}} talking' values={{ rawCount: hashtag.getIn(['history', 0, 'accounts']), count: <strong>{shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']))}</strong> }} />
+      <FormattedMessage id='trends.count_by_accounts' defaultMessage='{count} {rawCount, plural, one {person} other {people}} talking' values={{ rawCount: hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1, count: <strong>{shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1)}</strong> }} />
     </div>
 
     <div className='trends__item__current'>
-      {shortNumberFormat(hashtag.getIn(['history', 0, 'uses']))}
+      {shortNumberFormat(hashtag.getIn(['history', 0, 'uses']) * 1 + hashtag.getIn(['history', 1, 'uses']) * 1)}
     </div>
 
     <div className='trends__item__sparkline'>
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index 9cd71b7c9..e8dd79af9 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -159,7 +159,7 @@ class Item extends React.PureComponent {
     if (attachment.get('type') === 'unknown') {
       return (
         <div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
-          <a className='media-gallery__item-thumbnail' href={attachment.get('remote_url')} target='_blank' style={{ cursor: 'pointer' }} title={attachment.get('description')}>
+          <a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} target='_blank' style={{ cursor: 'pointer' }} title={attachment.get('description')}>
             <canvas width={32} height={32} ref={this.setCanvasRef} className='media-gallery__preview' />
           </a>
         </div>
@@ -315,15 +315,22 @@ class MediaGallery extends React.PureComponent {
       style.height = height;
     }
 
-    const size = media.take(4).size;
+    const size     = media.take(4).size;
+    const uncached = media.every(attachment => attachment.get('type') === 'unknown');
 
     if (this.isStandaloneEligible()) {
       children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} displayWidth={width} visible={visible} />;
     } else {
-      children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} displayWidth={width} visible={visible} />);
+      children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} displayWidth={width} visible={visible || uncached} />);
     }
 
-    if (visible) {
+    if (uncached) {
+      spoilerButton = (
+        <button type='button' disabled className='spoiler-button__overlay'>
+          <span className='spoiler-button__overlay__label'><FormattedMessage id='status.uncached_media_warning' defaultMessage='Not available' /></span>
+        </button>
+      );
+    } else if (visible) {
       spoilerButton = <IconButton title={intl.formatMessage(messages.toggle_visible)} icon='eye-slash' overlay onClick={this.handleOpen} />;
     } else {
       spoilerButton = (
@@ -335,7 +342,7 @@ class MediaGallery extends React.PureComponent {
 
     return (
       <div className='media-gallery' style={style} ref={this.handleRef}>
-        <div className={classNames('spoiler-button', { 'spoiler-button--minified': visible })}>
+        <div className={classNames('spoiler-button', { 'spoiler-button--minified': visible && !uncached, 'spoiler-button--click-thru': uncached })}>
           {spoilerButton}
         </div>
 
diff --git a/app/javascript/mastodon/features/directory/components/account_card.js b/app/javascript/mastodon/features/directory/components/account_card.js
index cb23a02ba..50ad74450 100644
--- a/app/javascript/mastodon/features/directory/components/account_card.js
+++ b/app/javascript/mastodon/features/directory/components/account_card.js
@@ -82,6 +82,43 @@ class AccountCard extends ImmutablePureComponent {
     onMute: PropTypes.func.isRequired,
   };
 
+  _updateEmojis () {
+    const node = this.node;
+
+    if (!node || autoPlayGif) {
+      return;
+    }
+
+    const emojis = node.querySelectorAll('.custom-emoji');
+
+    for (var i = 0; i < emojis.length; i++) {
+      let emoji = emojis[i];
+      if (emoji.classList.contains('status-emoji')) {
+        continue;
+      }
+      emoji.classList.add('status-emoji');
+
+      emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
+      emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
+    }
+  }
+
+  componentDidMount () {
+    this._updateEmojis();
+  }
+
+  componentDidUpdate () {
+    this._updateEmojis();
+  }
+
+  handleEmojiMouseEnter = ({ target }) => {
+    target.src = target.getAttribute('data-original');
+  }
+
+  handleEmojiMouseLeave = ({ target }) => {
+    target.src = target.getAttribute('data-static');
+  }
+
   handleFollow = () => {
     this.props.onFollow(this.props.account);
   }
@@ -94,6 +131,10 @@ class AccountCard extends ImmutablePureComponent {
     this.props.onMute(this.props.account);
   }
 
+  setRef = (c) => {
+    this.node = c;
+  }
+
   render () {
     const { account, intl } = this.props;
 
@@ -133,7 +174,7 @@ class AccountCard extends ImmutablePureComponent {
           </div>
         </div>
 
-        <div className='directory__card__extra'>
+        <div className='directory__card__extra' ref={this.setRef}>
           <div className='account__header__content' dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }} />
         </div>
 
diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.js b/app/javascript/mastodon/features/ui/components/navigation_panel.js
index 6f07778f2..51e3ec037 100644
--- a/app/javascript/mastodon/features/ui/components/navigation_panel.js
+++ b/app/javascript/mastodon/features/ui/components/navigation_panel.js
@@ -18,7 +18,7 @@ const NavigationPanel = () => (
     <NavLink className='column-link column-link--transparent' to='/timelines/direct'><Icon className='column-link__icon' id='envelope' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink>
     <NavLink className='column-link column-link--transparent' to='/favourites'><Icon className='column-link__icon' id='star' fixedWidth /><FormattedMessage id='navigation_bar.favourites' defaultMessage='Favourites' /></NavLink>
     <NavLink className='column-link column-link--transparent' to='/lists'><Icon className='column-link__icon' id='list-ul' fixedWidth /><FormattedMessage id='navigation_bar.lists' defaultMessage='Lists' /></NavLink>
-    {profile_directory && <NavLink className='column-link column-link--transparent' to='/directory'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='getting_started.profile_directory' defaultMessage='Profile directory' /></NavLink>}
+    {profile_directory && <NavLink className='column-link column-link--transparent' to='/directory'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></NavLink>}
 
     <ListPanel />
 
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 617328613..9cb4b74a7 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -8,6 +8,14 @@
       {
         "defaultMessage": "An unexpected error occurred.",
         "id": "alert.unexpected.message"
+      },
+      {
+        "defaultMessage": "Rate limited",
+        "id": "alert.rate_limited.title"
+      },
+      {
+        "defaultMessage": "Please retry after {retry_time, time, medium}.",
+        "id": "alert.rate_limited.message"
       }
     ],
     "path": "app/javascript/mastodon/actions/alerts.json"
@@ -192,6 +200,10 @@
         "id": "media_gallery.toggle_visible"
       },
       {
+        "defaultMessage": "Not available",
+        "id": "status.uncached_media_warning"
+      },
+      {
         "defaultMessage": "Sensitive content",
         "id": "status.sensitive_warning"
       },
@@ -1133,6 +1145,19 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Are you sure you want to log out?",
+        "id": "confirmations.logout.message"
+      },
+      {
+        "defaultMessage": "Log out",
+        "id": "confirmations.logout.confirm"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/compose/containers/navigation_container.json"
+  },
+  {
+    "descriptors": [
+      {
         "defaultMessage": "Media is marked as sensitive",
         "id": "compose_form.sensitive.marked"
       },
@@ -1218,6 +1243,14 @@
       {
         "defaultMessage": "Compose new toot",
         "id": "navigation_bar.compose"
+      },
+      {
+        "defaultMessage": "Are you sure you want to log out?",
+        "id": "confirmations.logout.message"
+      },
+      {
+        "defaultMessage": "Log out",
+        "id": "confirmations.logout.confirm"
       }
     ],
     "path": "app/javascript/mastodon/features/compose/index.json"
@@ -1238,6 +1271,76 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Follow",
+        "id": "account.follow"
+      },
+      {
+        "defaultMessage": "Unfollow",
+        "id": "account.unfollow"
+      },
+      {
+        "defaultMessage": "Awaiting approval",
+        "id": "account.requested"
+      },
+      {
+        "defaultMessage": "Unblock @{name}",
+        "id": "account.unblock"
+      },
+      {
+        "defaultMessage": "Unmute @{name}",
+        "id": "account.unmute"
+      },
+      {
+        "defaultMessage": "Are you sure you want to unfollow {name}?",
+        "id": "confirmations.unfollow.message"
+      },
+      {
+        "defaultMessage": "Toots",
+        "id": "account.posts"
+      },
+      {
+        "defaultMessage": "Followers",
+        "id": "account.followers"
+      },
+      {
+        "defaultMessage": "Never",
+        "id": "account.never_active"
+      },
+      {
+        "defaultMessage": "Last active",
+        "id": "account.last_status"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/directory/components/account_card.json"
+  },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "Browse profiles",
+        "id": "column.directory"
+      },
+      {
+        "defaultMessage": "Recently active",
+        "id": "directory.recently_active"
+      },
+      {
+        "defaultMessage": "New arrivals",
+        "id": "directory.new_arrivals"
+      },
+      {
+        "defaultMessage": "From {domain} only",
+        "id": "directory.local"
+      },
+      {
+        "defaultMessage": "From known fediverse",
+        "id": "directory.federated"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/directory/index.json"
+  },
+  {
+    "descriptors": [
+      {
         "defaultMessage": "Hidden domains",
         "id": "column.domain_blocks"
       },
@@ -2326,6 +2429,14 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Are you sure you want to log out?",
+        "id": "confirmations.logout.message"
+      },
+      {
+        "defaultMessage": "Log out",
+        "id": "confirmations.logout.confirm"
+      },
+      {
         "defaultMessage": "Invite people",
         "id": "getting_started.invite"
       },
@@ -2441,16 +2552,16 @@
         "id": "navigation_bar.lists"
       },
       {
+        "defaultMessage": "Profile directory",
+        "id": "getting_started.directory"
+      },
+      {
         "defaultMessage": "Preferences",
         "id": "navigation_bar.preferences"
       },
       {
         "defaultMessage": "Follows and followers",
         "id": "navigation_bar.follows_and_followers"
-      },
-      {
-        "defaultMessage": "Profile directory",
-        "id": "navigation_bar.profile_directory"
       }
     ],
     "path": "app/javascript/mastodon/features/ui/components/navigation_panel.json"
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 28ea713a3..260b43c53 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -16,6 +16,7 @@
   "account.follows.empty": "This user doesn't follow anyone yet.",
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.last_status": "Last active",
   "account.link_verified_on": "Ownership of this link was checked on {date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "Media",
@@ -24,6 +25,7 @@
   "account.mute": "Mute @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
   "account.muted": "Muted",
+  "account.never_active": "Never",
   "account.posts": "Toots",
   "account.posts_with_replies": "Toots and replies",
   "account.report": "Report @{name}",
@@ -36,6 +38,8 @@
   "account.unfollow": "Unfollow",
   "account.unmute": "Unmute @{name}",
   "account.unmute_notifications": "Unmute notifications from @{name}",
+  "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Rate limited",
   "alert.unexpected.message": "An unexpected error occurred.",
   "alert.unexpected.title": "Oops!",
   "autosuggest_hashtag.per_week": "{count} per week",
@@ -49,6 +53,7 @@
   "column.blocks": "Blocked users",
   "column.community": "Local timeline",
   "column.direct": "Direct messages",
+  "column.directory": "Browse profiles",
   "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favourites",
   "column.follow_requests": "Follow requests",
@@ -99,6 +104,8 @@
   "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
   "confirmations.domain_block.confirm": "Hide entire domain",
   "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
+  "confirmations.logout.confirm": "Log out",
+  "confirmations.logout.message": "Are you sure you want to log out?",
   "confirmations.mute.confirm": "Mute",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "confirmations.redraft.confirm": "Delete & redraft",
@@ -107,6 +114,10 @@
   "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
   "confirmations.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
+  "directory.federated": "From known fediverse",
+  "directory.local": "From {domain} only",
+  "directory.new_arrivals": "New arrivals",
+  "directory.recently_active": "Recently active",
   "embed.instructions": "Embed this status on your website by copying the code below.",
   "embed.preview": "Here is what it will look like:",
   "emoji_button.activity": "Activity",
@@ -254,7 +265,6 @@
   "navigation_bar.personal": "Personal",
   "navigation_bar.pins": "Pinned toots",
   "navigation_bar.preferences": "Preferences",
-  "navigation_bar.profile_directory": "Profile directory",
   "navigation_bar.public_timeline": "Federated timeline",
   "navigation_bar.security": "Security",
   "notification.favourite": "{name} favourited your status",
@@ -361,6 +371,7 @@
   "status.show_more": "Show more",
   "status.show_more_all": "Show more for all",
   "status.show_thread": "Show thread",
+  "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
   "suggestions.dismiss": "Dismiss suggestion",
diff --git a/app/javascript/mastodon/rtl.js b/app/javascript/mastodon/rtl.js
index 00870a15d..89bed6de8 100644
--- a/app/javascript/mastodon/rtl.js
+++ b/app/javascript/mastodon/rtl.js
@@ -20,6 +20,7 @@ export function isRtl(text) {
   text = text.replace(/(?:^|[^\/\w])@([a-z0-9_]+(@[a-z0-9\.\-]+)?)/ig, '');
   text = text.replace(/(?:^|[^\/\w])#([\S]+)/ig, '');
   text = text.replace(/\s+/g, '');
+  text = text.replace(/(\w\S+\.\w{2,}\S*)/g, '');
 
   const matches = text.match(rtlChars);
 
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index fd2180d6f..dee3c3439 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -507,6 +507,7 @@
       flex: 1 1 auto;
       overflow: hidden;
       text-overflow: ellipsis;
+      white-space: nowrap;
     }
 
     strong {
@@ -515,8 +516,10 @@
 
     &__uses {
       flex: 0 0 auto;
-      width: 80px;
       text-align: right;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
     }
   }
 
@@ -3449,6 +3452,10 @@ a.status-card.compact:hover {
     height: auto;
   }
 
+  &--click-thru {
+    pointer-events: none;
+  }
+
   &--hidden {
     display: none;
   }
@@ -3477,6 +3484,12 @@ a.status-card.compact:hover {
         background: rgba($base-overlay-background, 0.8);
       }
     }
+
+    &:disabled {
+      .spoiler-button__overlay__label {
+        background: rgba($base-overlay-background, 0.5);
+      }
+    }
   }
 }
 
diff --git a/app/javascript/styles/mastodon/dashboard.scss b/app/javascript/styles/mastodon/dashboard.scss
index e4564f062..c0944d417 100644
--- a/app/javascript/styles/mastodon/dashboard.scss
+++ b/app/javascript/styles/mastodon/dashboard.scss
@@ -15,6 +15,8 @@
       padding: 20px;
       background: lighten($ui-base-color, 4%);
       border-radius: 4px;
+      box-sizing: border-box;
+      height: 100%;
     }
 
     & > a {
diff --git a/app/javascript/styles/mastodon/footer.scss b/app/javascript/styles/mastodon/footer.scss
index f74c004e9..00d290883 100644
--- a/app/javascript/styles/mastodon/footer.scss
+++ b/app/javascript/styles/mastodon/footer.scss
@@ -128,7 +128,7 @@
       &:hover,
       &:focus,
       &:active {
-        svg path {
+        svg {
           fill: lighten($ui-base-color, 38%);
         }
       }
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index ac99124ea..16352340b 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -112,6 +112,15 @@ code {
       padding: 0.2em 0.4em;
       background: darken($ui-base-color, 12%);
     }
+
+    li {
+      list-style: disc;
+      margin-left: 18px;
+    }
+  }
+
+  ul.hint {
+    margin-bottom: 15px;
   }
 
   span.hint {