From 67215692fc1764cb4311e4bbd1211d0cf38438bd Mon Sep 17 00:00:00 2001
From: ThibG <thib@sitedethib.com>
Date: Tue, 12 Feb 2019 22:24:14 +0100
Subject: [PATCH 01/24] Save IP address used for sign-up, not only sign-in
 (#10026)

Fixes #9995
---
 app/controllers/auth/registrations_controller.rb | 1 +
 1 file changed, 1 insertion(+)

diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index f2a832542fe..ad7b1859f68 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -28,6 +28,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
     resource.invite_code = params[:invite_code] if resource.invite_code.blank?
     resource.agreement   = true
 
+    resource.current_sign_in_ip = request.remote_ip if resource.current_sign_in_ip.nil?
     resource.build_account if resource.account.nil?
   end
 

From fbe527ccfc85f9547fd807b3d1e2a7fcf3fc81ae Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <dependabot[bot]@users.noreply.github.com>
Date: Wed, 13 Feb 2019 02:45:01 +0100
Subject: [PATCH 02/24] Bump sidekiq-unique-jobs from 6.0.8 to 6.0.9 (#10019)

Bumps [sidekiq-unique-jobs](https://github.com/mhenrixon/sidekiq-unique-jobs) from 6.0.8 to 6.0.9.
- [Release notes](https://github.com/mhenrixon/sidekiq-unique-jobs/releases)
- [Changelog](https://github.com/mhenrixon/sidekiq-unique-jobs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mhenrixon/sidekiq-unique-jobs/compare/v6.0.8...v6.0.9)

Signed-off-by: dependabot[bot] <support@dependabot.com>
---
 Gemfile.lock | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index c47f4e9307f..9d4252f1639 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -563,7 +563,7 @@ GEM
       rufus-scheduler (~> 3.2)
       sidekiq (>= 3)
       tilt (>= 1.4.0)
-    sidekiq-unique-jobs (6.0.8)
+    sidekiq-unique-jobs (6.0.9)
       concurrent-ruby (~> 1.0, >= 1.0.5)
       sidekiq (>= 4.0, < 6.0)
       thor (~> 0)

From 3a2e44b62c408f2665f4faf6245f6a41e3a2c541 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <dependabot[bot]@users.noreply.github.com>
Date: Wed, 13 Feb 2019 02:54:27 +0100
Subject: [PATCH 03/24] Bump pkg-config from 1.3.2 to 1.3.3 (#10023)

Bumps [pkg-config](https://github.com/ruby-gnome2/pkg-config) from 1.3.2 to 1.3.3.
- [Release notes](https://github.com/ruby-gnome2/pkg-config/releases)
- [Changelog](https://github.com/ruby-gnome2/pkg-config/blob/master/NEWS)
- [Commits](https://github.com/ruby-gnome2/pkg-config/compare/1.3.2...1.3.3)

Signed-off-by: dependabot[bot] <support@dependabot.com>
---
 Gemfile.lock | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index 9d4252f1639..30c7d31d957 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -400,7 +400,7 @@ GEM
     pg (1.1.4)
     pghero (2.2.0)
       activerecord
-    pkg-config (1.3.2)
+    pkg-config (1.3.3)
     powerpack (0.1.2)
     premailer (1.11.1)
       addressable

From 5977e6af322fd597c7e9a28d4686870a7e0ed539 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <dependabot[bot]@users.noreply.github.com>
Date: Wed, 13 Feb 2019 02:54:48 +0100
Subject: [PATCH 04/24] Bump faker from 1.9.1 to 1.9.2 (#10020)

Bumps [faker](https://github.com/stympy/faker) from 1.9.1 to 1.9.2.
- [Release notes](https://github.com/stympy/faker/releases)
- [Changelog](https://github.com/stympy/faker/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stympy/faker/compare/v1.9.1...v1.9.2)

Signed-off-by: dependabot[bot] <support@dependabot.com>
---
 Gemfile.lock | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index 30c7d31d957..d60eb1c141e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -205,7 +205,7 @@ GEM
       tzinfo
     excon (0.62.0)
     fabrication (2.20.1)
-    faker (1.9.1)
+    faker (1.9.2)
       i18n (>= 0.7)
     faraday (0.15.0)
       multipart-post (>= 1.2, < 3)

From 3d374ed18be3efc78e48ccd512f7e44f38d2bc7c Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <dependabot[bot]@users.noreply.github.com>
Date: Wed, 13 Feb 2019 02:55:06 +0100
Subject: [PATCH 05/24] Bump bootsnap from 1.3.2 to 1.4.0 (#10022)

Bumps [bootsnap](https://github.com/Shopify/bootsnap) from 1.3.2 to 1.4.0.
- [Release notes](https://github.com/Shopify/bootsnap/releases)
- [Changelog](https://github.com/Shopify/bootsnap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Shopify/bootsnap/compare/v1.3.2...v1.4.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
---
 Gemfile      | 2 +-
 Gemfile.lock | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/Gemfile b/Gemfile
index d924a546ba1..e01d168b635 100644
--- a/Gemfile
+++ b/Gemfile
@@ -24,7 +24,7 @@ gem 'streamio-ffmpeg', '~> 3.0'
 
 gem 'active_model_serializers', '~> 0.10'
 gem 'addressable', '~> 2.6'
-gem 'bootsnap', '~> 1.3', require: false
+gem 'bootsnap', '~> 1.4', require: false
 gem 'browser'
 gem 'charlock_holmes', '~> 0.7.6'
 gem 'iso-639'
diff --git a/Gemfile.lock b/Gemfile.lock
index d60eb1c141e..05622340b79 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -98,7 +98,7 @@ GEM
       rack (>= 0.9.0)
     binding_of_caller (0.8.0)
       debug_inspector (>= 0.0.1)
-    bootsnap (1.3.2)
+    bootsnap (1.4.0)
       msgpack (~> 1.0)
     brakeman (4.4.0)
     browser (2.5.3)
@@ -345,7 +345,7 @@ GEM
     mini_mime (1.0.1)
     mini_portile2 (2.4.0)
     minitest (5.11.3)
-    msgpack (1.2.4)
+    msgpack (1.2.6)
     multi_json (1.13.1)
     multipart-post (2.0.0)
     necromancer (0.4.0)
@@ -660,7 +660,7 @@ DEPENDENCIES
   aws-sdk-s3 (~> 1.30)
   better_errors (~> 2.5)
   binding_of_caller (~> 0.7)
-  bootsnap (~> 1.3)
+  bootsnap (~> 1.4)
   brakeman (~> 4.4)
   browser
   bullet (~> 5.9)

From c6e7b97baa37c23a4853619ad3ff706f41a15237 Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Wed, 13 Feb 2019 05:30:49 +0100
Subject: [PATCH 06/24] Fix color of static page links in high contrast theme
 (#10028)

---
 app/javascript/styles/contrast/diff.scss | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/app/javascript/styles/contrast/diff.scss b/app/javascript/styles/contrast/diff.scss
index 7d8993a5080..8429103b8af 100644
--- a/app/javascript/styles/contrast/diff.scss
+++ b/app/javascript/styles/contrast/diff.scss
@@ -13,6 +13,10 @@
   }
 }
 
+.rich-formatting a,
+.rich-formatting p a,
+.rich-formatting li a,
+.landing-page__short-description p a,
 .status__content a,
 .reply-indicator__content a {
   color: lighten($ui-highlight-color, 12%);

From 98d1a1f117452a5e12d73eaf43a10f36e187402e Mon Sep 17 00:00:00 2001
From: ThibG <thib@sitedethib.com>
Date: Wed, 13 Feb 2019 18:33:03 +0100
Subject: [PATCH 07/24] Disable box shadows for featured hashtags in light
 theme (#10034)

Fixes #9990
---
 app/javascript/styles/mastodon-light/diff.scss | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss
index 78bc2dbb6d5..de03cf1a667 100644
--- a/app/javascript/styles/mastodon-light/diff.scss
+++ b/app/javascript/styles/mastodon-light/diff.scss
@@ -352,6 +352,8 @@
 .moved-account-widget,
 .memoriam-widget,
 .activity-stream,
-.nothing-here {
+.nothing-here,
+.directory__tag > a,
+.directory__tag > div {
   box-shadow: none;
 }

From 169b9d4428d8e54d7bee365fd76be9a6e2a92da5 Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Wed, 13 Feb 2019 18:34:58 +0100
Subject: [PATCH 08/24] Fix hashtags select styling in default and high
 contrast themes (#10029)

---
 .../components/column_settings.js             | 55 ++++++-----
 app/javascript/styles/mastodon/_mixins.scss   | 31 ++++++
 .../styles/mastodon/components.scss           | 95 +++++++++++++------
 3 files changed, 128 insertions(+), 53 deletions(-)

diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js b/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js
index 9c9f62d8211..cdc138c8bf3 100644
--- a/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js
+++ b/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js
@@ -1,10 +1,15 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import { injectIntl, FormattedMessage } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import Toggle from 'react-toggle';
 import AsyncSelect from 'react-select/lib/Async';
 
+const messages = defineMessages({
+  placeholder: { id: 'hashtag.column_settings.select.placeholder', defaultMessage: 'Enter hashtags…' },
+  noOptions: { id: 'hashtag.column_settings.select.no_options_message', defaultMessage: 'No suggestions found' },
+});
+
 export default @injectIntl
 class ColumnSettings extends React.PureComponent {
 
@@ -25,6 +30,7 @@ class ColumnSettings extends React.PureComponent {
 
   tags (mode) {
     let tags = this.props.settings.getIn(['tags', mode]) || [];
+
     if (tags.toJSON) {
       return tags.toJSON();
     } else {
@@ -32,33 +38,36 @@ class ColumnSettings extends React.PureComponent {
     }
   };
 
-  onSelect = (mode) => {
-    return (value) => {
-      this.props.onChange(['tags', mode], value);
-    };
-  };
+  onSelect = mode => value => this.props.onChange(['tags', mode], value);
 
   onToggle = () => {
     if (this.state.open && this.hasTags()) {
       this.props.onChange('tags', {});
     }
+
     this.setState({ open: !this.state.open });
   };
 
+  noOptionsMessage = () => this.props.intl.formatMessage(messages.noOptions);
+
   modeSelect (mode) {
     return (
-      <div className='column-settings__section'>
-        {this.modeLabel(mode)}
+      <div className='column-settings__row'>
+        <span className='column-settings__section'>
+          {this.modeLabel(mode)}
+        </span>
+
         <AsyncSelect
           isMulti
           autoFocus
           value={this.tags(mode)}
-          settings={this.props.settings}
-          settingPath={['tags', mode]}
           onChange={this.onSelect(mode)}
           loadOptions={this.props.onLoad}
-          classNamePrefix='column-settings__hashtag-select'
+          className='column-select__container'
+          classNamePrefix='column-select'
           name='tags'
+          placeholder={this.props.intl.formatMessage(messages.placeholder)}
+          noOptionsMessage={this.noOptionsMessage}
         />
       </div>
     );
@@ -66,11 +75,15 @@ class ColumnSettings extends React.PureComponent {
 
   modeLabel (mode) {
     switch(mode) {
-    case 'any':  return <FormattedMessage id='hashtag.column_settings.tag_mode.any' defaultMessage='Any of these' />;
-    case 'all':  return <FormattedMessage id='hashtag.column_settings.tag_mode.all' defaultMessage='All of these' />;
-    case 'none': return <FormattedMessage id='hashtag.column_settings.tag_mode.none' defaultMessage='None of these' />;
+    case 'any':
+      return <FormattedMessage id='hashtag.column_settings.tag_mode.any' defaultMessage='Any of these' />;
+    case 'all':
+      return <FormattedMessage id='hashtag.column_settings.tag_mode.all' defaultMessage='All of these' />;
+    case 'none':
+      return <FormattedMessage id='hashtag.column_settings.tag_mode.none' defaultMessage='None of these' />;
+    default:
+      return '';
     }
-    return '';
   };
 
   render () {
@@ -78,23 +91,21 @@ class ColumnSettings extends React.PureComponent {
       <div>
         <div className='column-settings__row'>
           <div className='setting-toggle'>
-            <Toggle
-              id='hashtag.column_settings.tag_toggle'
-              onChange={this.onToggle}
-              checked={this.state.open}
-            />
+            <Toggle id='hashtag.column_settings.tag_toggle' onChange={this.onToggle} checked={this.state.open} />
+
             <span className='setting-toggle__label'>
               <FormattedMessage id='hashtag.column_settings.tag_toggle' defaultMessage='Include additional tags in this column' />
             </span>
           </div>
         </div>
-        {this.state.open &&
+
+        {this.state.open && (
           <div className='column-settings__hashtags'>
             {this.modeSelect('any')}
             {this.modeSelect('all')}
             {this.modeSelect('none')}
           </div>
-        }
+        )}
       </div>
     );
   }
diff --git a/app/javascript/styles/mastodon/_mixins.scss b/app/javascript/styles/mastodon/_mixins.scss
index d5bafe6b6a2..08806599e8f 100644
--- a/app/javascript/styles/mastodon/_mixins.scss
+++ b/app/javascript/styles/mastodon/_mixins.scss
@@ -41,3 +41,34 @@
     font-size: 16px;
   }
 }
+
+@mixin search-popout() {
+  background: $simple-background-color;
+  border-radius: 4px;
+  padding: 10px 14px;
+  padding-bottom: 14px;
+  margin-top: 10px;
+  color: $light-text-color;
+  box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
+
+  h4 {
+    text-transform: uppercase;
+    color: $light-text-color;
+    font-size: 13px;
+    font-weight: 500;
+    margin-bottom: 10px;
+  }
+
+  li {
+    padding: 4px 0;
+  }
+
+  ul {
+    margin-bottom: 10px;
+  }
+
+  em {
+    font-weight: 500;
+    color: $inverted-text-color;
+  }
+}
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 32fd773859e..02bbd345a7b 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -3056,14 +3056,41 @@ a.status-card.compact:hover {
   display: block;
   font-weight: 500;
   margin-bottom: 10px;
+}
 
-  .column-settings__hashtag-select {
+.column-settings__hashtags {
+  .column-settings__row {
+    margin-bottom: 15px;
+  }
+
+  .column-select {
     &__control {
       @include search-input();
     }
 
+    &__placeholder {
+      color: $dark-text-color;
+      padding-left: 2px;
+      font-size: 12px;
+    }
+
+    &__value-container {
+      padding-left: 6px;
+    }
+
     &__multi-value {
       background: lighten($ui-base-color, 8%);
+
+      &__remove {
+        cursor: pointer;
+
+        &:hover,
+        &:active,
+        &:focus {
+          background: lighten($ui-base-color, 12%);
+          color: lighten($darker-text-color, 4%);
+        }
+      }
     }
 
     &__multi-value__label,
@@ -3071,9 +3098,42 @@ a.status-card.compact:hover {
       color: $darker-text-color;
     }
 
-    &__indicator-separator,
+    &__clear-indicator,
     &__dropdown-indicator {
-      display: none;
+      cursor: pointer;
+      transition: none;
+      color: $dark-text-color;
+
+      &:hover,
+      &:active,
+      &:focus {
+        color: lighten($dark-text-color, 4%);
+      }
+    }
+
+    &__indicator-separator {
+      background-color: lighten($ui-base-color, 8%);
+    }
+
+    &__menu {
+      @include search-popout();
+      padding: 0;
+      background: $ui-secondary-color;
+    }
+
+    &__menu-list {
+      padding: 6px;
+    }
+
+    &__option {
+      color: $inverted-text-color;
+      border-radius: 4px;
+      font-size: 14px;
+
+      &--is-focused,
+      &--is-selected {
+        background: darken($ui-secondary-color, 10%);
+      }
     }
   }
 }
@@ -4867,34 +4927,7 @@ a.status-card.compact:hover {
 }
 
 .search-popout {
-  background: $simple-background-color;
-  border-radius: 4px;
-  padding: 10px 14px;
-  padding-bottom: 14px;
-  margin-top: 10px;
-  color: $light-text-color;
-  box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
-
-  h4 {
-    text-transform: uppercase;
-    color: $light-text-color;
-    font-size: 13px;
-    font-weight: 500;
-    margin-bottom: 10px;
-  }
-
-  li {
-    padding: 4px 0;
-  }
-
-  ul {
-    margin-bottom: 10px;
-  }
-
-  em {
-    font-weight: 500;
-    color: $inverted-text-color;
-  }
+  @include search-popout();
 }
 
 noscript {

From 6a5307a5733e7872e7827f32b27111434e0307c4 Mon Sep 17 00:00:00 2001
From: ThibG <thib@sitedethib.com>
Date: Wed, 13 Feb 2019 18:36:23 +0100
Subject: [PATCH 09/24] Alternative handling of private self-boosts (#9998)

* When self-boosting, embed original toot into Announce serialization

* Process unknown self-boosts from Announce object if it is more than an URI

* Add some self-boost specs

* Only serialize private toots in self-Announces
---
 app/lib/activitypub/activity.rb               | 32 +++++++++++
 app/lib/activitypub/activity/announce.rb      |  4 +-
 app/lib/activitypub/activity/create.rb        | 15 ------
 .../activitypub/activity_serializer.rb        |  8 ++-
 .../lib/activitypub/activity/announce_spec.rb | 53 ++++++++++++++++---
 5 files changed, 86 insertions(+), 26 deletions(-)

diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb
index 919678618a3..7e4e195313a 100644
--- a/app/lib/activitypub/activity.rb
+++ b/app/lib/activitypub/activity.rb
@@ -4,6 +4,9 @@ class ActivityPub::Activity
   include JsonLdHelper
   include Redisable
 
+  SUPPORTED_TYPES = %w(Note).freeze
+  CONVERTED_TYPES = %w(Image Video Article Page).freeze
+
   def initialize(json, account, **options)
     @json    = json
     @account = account
@@ -71,6 +74,18 @@ class ActivityPub::Activity
     @object_uri ||= value_or_id(@object)
   end
 
+  def unsupported_object_type?
+    @object.is_a?(String) || !(supported_object_type? || converted_object_type?)
+  end
+
+  def supported_object_type?
+    equals_or_includes_any?(@object['type'], SUPPORTED_TYPES)
+  end
+
+  def converted_object_type?
+    equals_or_includes_any?(@object['type'], CONVERTED_TYPES)
+  end
+
   def distribute(status)
     crawl_links(status)
 
@@ -120,6 +135,23 @@ class ActivityPub::Activity
     redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, uri)
   end
 
+  def status_from_object
+    # If the status is already known, return it
+    status = status_from_uri(object_uri)
+    return status unless status.nil?
+
+    # If the boosted toot is embedded and it is a self-boost, handle it like a Create
+    unless unsupported_object_type?
+      actor_id = value_or_id(first_of_value(@object['attributedTo'])) || @account.uri
+      if actor_id == @account.uri
+        return ActivityPub::Activity.factory({ 'type' => 'Create', 'actor' => actor_id, 'object' => @object }, @account).perform
+      end
+    end
+
+    # If the status is not from the actor, try to fetch it
+    return fetch_remote_original_status if value_or_id(first_of_value(@json['attributedTo'])) == @account.uri
+  end
+
   def fetch_remote_original_status
     if object_uri.start_with?('http')
       return if ActivityPub::TagManager.instance.local_uri?(object_uri)
diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb
index 34d1b7cbd0f..04afeea202c 100644
--- a/app/lib/activitypub/activity/announce.rb
+++ b/app/lib/activitypub/activity/announce.rb
@@ -2,9 +2,7 @@
 
 class ActivityPub::Activity::Announce < ActivityPub::Activity
   def perform
-    original_status   = status_from_uri(object_uri)
-    original_status ||= fetch_remote_original_status
-
+    original_status = status_from_object
     return if original_status.nil? || delete_arrived_first?(@json['id']) || !announceable?(original_status)
 
     status = Status.find_by(account: @account, reblog: original_status)
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index b49657d4b14..9a3db51dd9e 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -1,9 +1,6 @@
 # frozen_string_literal: true
 
 class ActivityPub::Activity::Create < ActivityPub::Activity
-  SUPPORTED_TYPES = %w(Note).freeze
-  CONVERTED_TYPES = %w(Image Video Article Page).freeze
-
   def perform
     return if unsupported_object_type? || invalid_origin?(@object['id'])
     return if Tombstone.exists?(uri: @object['id'])
@@ -318,22 +315,10 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     @object['nameMap'].is_a?(Hash) && !@object['nameMap'].empty?
   end
 
-  def unsupported_object_type?
-    @object.is_a?(String) || !(supported_object_type? || converted_object_type?)
-  end
-
   def unsupported_media_type?(mime_type)
     mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_type)
   end
 
-  def supported_object_type?
-    equals_or_includes_any?(@object['type'], SUPPORTED_TYPES)
-  end
-
-  def converted_object_type?
-    equals_or_includes_any?(@object['type'], CONVERTED_TYPES)
-  end
-
   def skip_download?
     return @skip_download if defined?(@skip_download)
     @skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media?
diff --git a/app/serializers/activitypub/activity_serializer.rb b/app/serializers/activitypub/activity_serializer.rb
index 50c4f6a049a..b51e8c54429 100644
--- a/app/serializers/activitypub/activity_serializer.rb
+++ b/app/serializers/activitypub/activity_serializer.rb
@@ -3,8 +3,8 @@
 class ActivityPub::ActivitySerializer < ActiveModel::Serializer
   attributes :id, :type, :actor, :published, :to, :cc
 
-  has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, unless: :announce?
-  attribute :proper_uri, key: :object, if: :announce?
+  has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, unless: :owned_announce?
+  attribute :proper_uri, key: :object, if: :owned_announce?
   attribute :atom_uri, if: :announce?
 
   def id
@@ -42,4 +42,8 @@ class ActivityPub::ActivitySerializer < ActiveModel::Serializer
   def announce?
     object.reblog?
   end
+
+  def owned_announce?
+    announce? && object.account == object.proper.account && object.proper.private_visibility?
+  end
 end
diff --git a/spec/lib/activitypub/activity/announce_spec.rb b/spec/lib/activitypub/activity/announce_spec.rb
index 54dd52a6049..1725c2843bb 100644
--- a/spec/lib/activitypub/activity/announce_spec.rb
+++ b/spec/lib/activitypub/activity/announce_spec.rb
@@ -1,7 +1,7 @@
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Announce do
-  let(:sender)    { Fabricate(:account) }
+  let(:sender)    { Fabricate(:account, followers_url: 'http://example.com/followers') }
   let(:recipient) { Fabricate(:account) }
   let(:status)    { Fabricate(:status, account: recipient) }
 
@@ -11,19 +11,60 @@ RSpec.describe ActivityPub::Activity::Announce do
       id: 'foo',
       type: 'Announce',
       actor: ActivityPub::TagManager.instance.uri_for(sender),
-      object: ActivityPub::TagManager.instance.uri_for(status),
+      object: object_json,
     }.with_indifferent_access
   end
 
-  describe '#perform' do
-    subject { described_class.new(json, sender) }
+  subject { described_class.new(json, sender) }
 
+  before do
+    sender.update(uri: ActivityPub::TagManager.instance.uri_for(sender))
+  end
+
+  describe '#perform' do
     before do
       subject.perform
     end
 
-    it 'creates a reblog by sender of status' do
-      expect(sender.reblogged?(status)).to be true
+    context 'a known status' do
+      let(:object_json) do
+        ActivityPub::TagManager.instance.uri_for(status)
+      end
+
+      it 'creates a reblog by sender of status' do
+        expect(sender.reblogged?(status)).to be true
+      end
+    end
+
+    context 'self-boost of a previously unknown status with missing attributedTo' do
+      let(:object_json) do
+        {
+          id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
+          type: 'Note',
+          content: 'Lorem ipsum',
+          to: 'http://example.com/followers',
+        }
+      end
+
+      it 'creates a reblog by sender of status' do
+        expect(sender.reblogged?(sender.statuses.first)).to be true
+      end
+    end
+
+    context 'self-boost of a previously unknown status with correct attributedTo' do
+      let(:object_json) do
+        {
+          id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
+          type: 'Note',
+          content: 'Lorem ipsum',
+          attributedTo: ActivityPub::TagManager.instance.uri_for(sender),
+          to: 'http://example.com/followers',
+        }
+      end
+
+      it 'creates a reblog by sender of status' do
+        expect(sender.reblogged?(sender.statuses.first)).to be true
+      end
     end
   end
 end

From 114cdc36aad7247ffa886674ae40021516d53420 Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Wed, 13 Feb 2019 18:36:40 +0100
Subject: [PATCH 10/24] Fix style regressions on landing page (#10030)

---
 app/javascript/styles/mastodon/about.scss | 19 +++----------------
 1 file changed, 3 insertions(+), 16 deletions(-)

diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss
index b6c92a09e7d..b078d4d24d5 100644
--- a/app/javascript/styles/mastodon/about.scss
+++ b/app/javascript/styles/mastodon/about.scss
@@ -49,15 +49,9 @@ $small-breakpoint: 960px;
     }
   }
 
+  strong,
   em {
-    display: inline;
-    margin: 0;
-    padding: 0;
     font-weight: 700;
-    background: transparent;
-    font-family: inherit;
-    font-size: inherit;
-    line-height: inherit;
     color: lighten($darker-text-color, 10%);
   }
 
@@ -796,7 +790,7 @@ $small-breakpoint: 960px;
       width: 100%;
       display: flex;
       flex-direction: row-reverse;
-      flex-wrap: wrap;
+      flex-wrap: nowrap;
       justify-content: space-between;
       align-items: center;
     }
@@ -846,14 +840,7 @@ $small-breakpoint: 960px;
     }
 
     strong {
-      display: inline;
-      margin: 0;
-      padding: 0;
-      font-weight: 700;
-      background: transparent;
-      font-family: inherit;
-      font-size: inherit;
-      line-height: inherit;
+      font-weight: 500;
       color: lighten($darker-text-color, 10%);
     }
 

From 011b476d38663656988ae21ca29689aae01cb7c2 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <dependabot[bot]@users.noreply.github.com>
Date: Wed, 13 Feb 2019 18:37:27 +0100
Subject: [PATCH 11/24] Bump faker from 1.9.2 to 1.9.3 (#10031)

Bumps [faker](https://github.com/stympy/faker) from 1.9.2 to 1.9.3.
- [Release notes](https://github.com/stympy/faker/releases)
- [Changelog](https://github.com/stympy/faker/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stympy/faker/compare/v1.9.2...v1.9.3)

Signed-off-by: dependabot[bot] <support@dependabot.com>
---
 Gemfile.lock | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index 05622340b79..ee536f2ab63 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -205,7 +205,7 @@ GEM
       tzinfo
     excon (0.62.0)
     fabrication (2.20.1)
-    faker (1.9.2)
+    faker (1.9.3)
       i18n (>= 0.7)
     faraday (0.15.0)
       multipart-post (>= 1.2, < 3)

From dad339da6d19679f0361bfd49d9bcf3a3517af0c Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Wed, 13 Feb 2019 18:42:47 +0100
Subject: [PATCH 12/24] Filter incoming Create activities by relation to local
 activity (#10005)

Reject those from accounts with no local followers, from relays
that are not enabled, which do not address local accounts and are
not replies to accounts that do have local followers
---
 app/lib/activitypub/activity/create.rb        | 34 +++++++++++++++++--
 .../activitypub/process_collection_service.rb |  1 +
 app/workers/activitypub/processing_worker.rb  |  2 +-
 3 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index 9a3db51dd9e..1b31768d964 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -2,8 +2,7 @@
 
 class ActivityPub::Activity::Create < ActivityPub::Activity
   def perform
-    return if unsupported_object_type? || invalid_origin?(@object['id'])
-    return if Tombstone.exists?(uri: @object['id'])
+    return if unsupported_object_type? || invalid_origin?(@object['id']) || Tombstone.exists?(uri: @object['id']) || !related_to_local_activity?
 
     RedisLock.acquire(lock_options) do |lock|
       if lock.acquired?
@@ -337,6 +336,37 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     !replied_to_status.nil? && replied_to_status.account.local?
   end
 
+  def related_to_local_activity?
+    fetch? || followed_by_local_accounts? || requested_through_relay? ||
+      responds_to_followed_account? || addresses_local_accounts?
+  end
+
+  def fetch?
+    !@options[:delivery]
+  end
+
+  def followed_by_local_accounts?
+    @account.passive_relationships.exists?
+  end
+
+  def requested_through_relay?
+    @options[:relayed_through_account] && Relay.find_by(inbox_url: @options[:relayed_through_account].inbox_url)&.enabled?
+  end
+
+  def responds_to_followed_account?
+    !replied_to_status.nil? && (replied_to_status.account.local? || replied_to_status.account.passive_relationships.exists?)
+  end
+
+  def addresses_local_accounts?
+    return true if @options[:delivered_to_account_id]
+
+    local_usernames = (as_array(@object['to']) + as_array(@object['cc'])).uniq.select { |uri| ActivityPub::TagManager.instance.local_uri?(uri) }.map { |uri| ActivityPub::TagManager.instance.uri_to_local_id(uri, :username) }
+
+    return false if local_usernames.empty?
+
+    Account.local.where(username: local_usernames).exists?
+  end
+
   def forward_for_reply
     return unless @json['signature'].present? && reply_to_local?
     ActivityPub::RawDistributionWorker.perform_async(Oj.dump(@json), replied_to_status.account_id, [@account.preferred_inbox_url])
diff --git a/app/services/activitypub/process_collection_service.rb b/app/services/activitypub/process_collection_service.rb
index 5c54aad89f1..881df478bf5 100644
--- a/app/services/activitypub/process_collection_service.rb
+++ b/app/services/activitypub/process_collection_service.rb
@@ -44,6 +44,7 @@ class ActivityPub::ProcessCollectionService < BaseService
   end
 
   def verify_account!
+    @options[:relayed_through_account] = @account
     @account = ActivityPub::LinkedDataSignature.new(@json).verify_account!
   rescue JSON::LD::JsonLdError => e
     Rails.logger.debug "Could not verify LD-Signature for #{value_or_id(@json['actor'])}: #{e.message}"
diff --git a/app/workers/activitypub/processing_worker.rb b/app/workers/activitypub/processing_worker.rb
index a8a3ebf0f56..a3abe72cf66 100644
--- a/app/workers/activitypub/processing_worker.rb
+++ b/app/workers/activitypub/processing_worker.rb
@@ -6,6 +6,6 @@ class ActivityPub::ProcessingWorker
   sidekiq_options backtrace: true
 
   def perform(account_id, body, delivered_to_account_id = nil)
-    ActivityPub::ProcessCollectionService.new.call(body, Account.find(account_id), override_timestamps: true, delivered_to_account_id: delivered_to_account_id)
+    ActivityPub::ProcessCollectionService.new.call(body, Account.find(account_id), override_timestamps: true, delivered_to_account_id: delivered_to_account_id, delivery: true)
   end
 end

From 188f1c7c89d6fc7349302a9680bac9a82515cb90 Mon Sep 17 00:00:00 2001
From: ThibG <thib@sitedethib.com>
Date: Wed, 13 Feb 2019 18:52:02 +0100
Subject: [PATCH 13/24] Add list title editing (#9748)

* Add list title editing

Port changes made by ash for glitch-soc

* Code style fixes
---
 .../list_editor/components/edit_list_form.js  | 70 +++++++++++++++++++
 .../mastodon/features/list_editor/index.js    |  7 +-
 .../mastodon/reducers/list_editor.js          | 11 ++-
 .../styles/mastodon/components.scss           |  2 +-
 4 files changed, 83 insertions(+), 7 deletions(-)
 create mode 100644 app/javascript/mastodon/features/list_editor/components/edit_list_form.js

diff --git a/app/javascript/mastodon/features/list_editor/components/edit_list_form.js b/app/javascript/mastodon/features/list_editor/components/edit_list_form.js
new file mode 100644
index 00000000000..3dc59c12e76
--- /dev/null
+++ b/app/javascript/mastodon/features/list_editor/components/edit_list_form.js
@@ -0,0 +1,70 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import { changeListEditorTitle, submitListEditor } from '../../../actions/lists';
+import IconButton from '../../../components/icon_button';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+  title: { id: 'lists.edit.submit', defaultMessage: 'Change title' },
+});
+
+const mapStateToProps = state => ({
+  value: state.getIn(['listEditor', 'title']),
+  disabled: !state.getIn(['listEditor', 'isChanged']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onChange: value => dispatch(changeListEditorTitle(value)),
+  onSubmit: () => dispatch(submitListEditor(false)),
+});
+
+export default @connect(mapStateToProps, mapDispatchToProps)
+@injectIntl
+class ListForm extends React.PureComponent {
+
+  static propTypes = {
+    value: PropTypes.string.isRequired,
+    disabled: PropTypes.bool,
+    intl: PropTypes.object.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+  };
+
+  handleChange = e => {
+    this.props.onChange(e.target.value);
+  }
+
+  handleSubmit = e => {
+    e.preventDefault();
+    this.props.onSubmit();
+  }
+
+  handleClick = () => {
+    this.props.onSubmit();
+  }
+
+  render () {
+    const { value, disabled, intl } = this.props;
+
+    const title = intl.formatMessage(messages.title);
+
+    return (
+      <form className='column-inline-form' onSubmit={this.handleSubmit}>
+        <input
+          className='setting-text'
+          value={value}
+          onChange={this.handleChange}
+        />
+
+        <IconButton
+          disabled={disabled}
+          icon='check'
+          title={title}
+          onClick={this.handleClick}
+        />
+      </form>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/list_editor/index.js b/app/javascript/mastodon/features/list_editor/index.js
index aab0cdd0c0c..48466604a7d 100644
--- a/app/javascript/mastodon/features/list_editor/index.js
+++ b/app/javascript/mastodon/features/list_editor/index.js
@@ -7,11 +7,11 @@ import { injectIntl } from 'react-intl';
 import { setupListEditor, clearListSuggestions, resetListEditor } from '../../actions/lists';
 import Account from './components/account';
 import Search from './components/search';
+import EditListForm from './components/edit_list_form';
 import Motion from '../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 
 const mapStateToProps = state => ({
-  title: state.getIn(['listEditor', 'title']),
   accountIds: state.getIn(['listEditor', 'accounts', 'items']),
   searchAccountIds: state.getIn(['listEditor', 'suggestions', 'items']),
 });
@@ -33,7 +33,6 @@ class ListEditor extends ImmutablePureComponent {
     onInitialize: PropTypes.func.isRequired,
     onClear: PropTypes.func.isRequired,
     onReset: PropTypes.func.isRequired,
-    title: PropTypes.string.isRequired,
     accountIds: ImmutablePropTypes.list.isRequired,
     searchAccountIds: ImmutablePropTypes.list.isRequired,
   };
@@ -49,12 +48,12 @@ class ListEditor extends ImmutablePureComponent {
   }
 
   render () {
-    const { title, accountIds, searchAccountIds, onClear } = this.props;
+    const { accountIds, searchAccountIds, onClear } = this.props;
     const showSearch = searchAccountIds.size > 0;
 
     return (
       <div className='modal-root__modal list-editor'>
-        <h4>{title}</h4>
+        <EditListForm />
 
         <Search />
 
diff --git a/app/javascript/mastodon/reducers/list_editor.js b/app/javascript/mastodon/reducers/list_editor.js
index 02a0dabb147..91e524dd550 100644
--- a/app/javascript/mastodon/reducers/list_editor.js
+++ b/app/javascript/mastodon/reducers/list_editor.js
@@ -22,6 +22,7 @@ import {
 const initialState = ImmutableMap({
   listId: null,
   isSubmitting: false,
+  isChanged: false,
   title: '',
 
   accounts: ImmutableMap({
@@ -47,10 +48,16 @@ export default function listEditorReducer(state = initialState, action) {
       map.set('isSubmitting', false);
     });
   case LIST_EDITOR_TITLE_CHANGE:
-    return state.set('title', action.value);
+    return state.withMutations(map => {
+      map.set('title', action.value);
+      map.set('isChanged', true);
+    });
   case LIST_CREATE_REQUEST:
   case LIST_UPDATE_REQUEST:
-    return state.set('isSubmitting', true);
+    return state.withMutations(map => {
+      map.set('isSubmitting', true);
+      map.set('isChanged', false);
+    });
   case LIST_CREATE_FAIL:
   case LIST_UPDATE_FAIL:
     return state.set('isSubmitting', false);
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 02bbd345a7b..8533e4eb5b8 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -5163,7 +5163,7 @@ noscript {
 
   .icon-button {
     flex: 0 0 auto;
-    margin-left: 5px;
+    margin: 0 5px;
   }
 }
 

From 658b4621a622364d8daf699a7e46c65f3f63486b Mon Sep 17 00:00:00 2001
From: Nolan Lawson <nolan@nolanlawson.com>
Date: Wed, 13 Feb 2019 09:52:36 -0800
Subject: [PATCH 14/24] perf: run node directly when streaming (#10032)

---
 dist/mastodon-streaming.service | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/dist/mastodon-streaming.service b/dist/mastodon-streaming.service
index 5d7c129dfbb..c324fccf467 100644
--- a/dist/mastodon-streaming.service
+++ b/dist/mastodon-streaming.service
@@ -9,7 +9,7 @@ WorkingDirectory=/home/mastodon/live
 Environment="NODE_ENV=production"
 Environment="PORT=4000"
 Environment="STREAMING_CLUSTER_NUM=1"
-ExecStart=/usr/bin/npm run start
+ExecStart=/usr/bin/node ./streaming
 TimeoutSec=15
 Restart=always
 

From 7750416597e4d172b1d4751215aac2a50cf528e3 Mon Sep 17 00:00:00 2001
From: Aditoo17 <42938951+Aditoo17@users.noreply.github.com>
Date: Wed, 13 Feb 2019 17:53:01 +0000
Subject: [PATCH 15/24] I18n: Update Czech pluralization and fix some language
 names (#10015)

* I18n: Update Czech pluralization

* I18n: Fix some language names

* I18n: Fix some language names
---
 app/helpers/settings_helper.rb | 12 ++++++------
 config/locales/cs.yml          |  6 +++---
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb
index 7a3ceca786a..e868b45c019 100644
--- a/app/helpers/settings_helper.rb
+++ b/app/helpers/settings_helper.rb
@@ -4,7 +4,7 @@ module SettingsHelper
   HUMAN_LOCALES = {
     en: 'English',
     ar: 'العربية',
-    ast: 'l\'asturianu',
+    ast: 'Asturianu',
     bg: 'Български',
     ca: 'Català',
     co: 'Corsu',
@@ -30,16 +30,16 @@ module SettingsHelper
     ja: '日本語',
     ka: 'ქართული',
     ko: '한국어',
-    lv: 'Latviešu valoda',
+    lv: 'Latviešu',
     ml: 'മലയാളം',
-    ms: 'بهاس ملايو',
+    ms: 'Bahasa Melayu',
     nl: 'Nederlands',
     no: 'Norsk',
     oc: 'Occitan',
-    pl: 'Polszczyzna',
+    pl: 'Polski',
     pt: 'Português',
     'pt-BR': 'Português do Brasil',
-    ro: 'Limba română',
+    ro: 'Română',
     ru: 'Русский',
     sk: 'Slovenčina',
     sl: 'Slovenščina',
@@ -49,7 +49,7 @@ module SettingsHelper
     sv: 'Svenska',
     ta: 'தமிழ்',
     te: 'తెలుగు',
-    th: 'ภาษาไทย',
+    th: 'ไทย',
     tr: 'Türkçe',
     uk: 'Українська',
     zh: '中文',
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index d1f11261cc0..c75d0b643c9 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -46,7 +46,7 @@ cs:
     choices_html: 'Volby uživatele %{name}:'
     follow: Sledovat
     followers:
-      few: Sledovatelé
+      few: Sledující
       one: Sledující
       other: Sledujících
     following: Sledovaných
@@ -618,7 +618,7 @@ cs:
     lock_link: Zamkněte svůj účet
     purge: Odstranit ze sledujících
     success:
-      few: V průběhu blokování sledovatelů ze %{count} domén...
+      few: V průběhu blokování sledujících ze %{count} domén...
       one: V průběhu blokování sledujících z jedné domény...
       other: V průběhu blokování sledujících z %{count} domén...
     true_privacy_html: Berte prosím na vědomí, že <strong>skutečného soukromí se dá dosáhnout pouze za pomoci end-to-end šifrování</strong>.
@@ -688,7 +688,7 @@ cs:
       body: Zde najdete stručný souhrn zpráv, které jste zmeškal/a od vaší poslední návštěvy %{since}
       mention: "%{name} vás zmínil/a v:"
       new_followers_summary:
-        few: Navíc jste získal/a %{count} nové sledovatele, zatímco jste byl/a pryč! Skvělé!
+        few: Navíc jste získal/a %{count} nové sledující, zatímco jste byl/a pryč! Skvělé!
         one: Navíc jste získal/a jednoho nového sledujícího, zatímco jste byl/a pryč! Hurá!
         other: Navíc jste získal/a %{count} nových sledujících, zatímco jste byl/a pryč! Úžasné!
       subject:

From 80161f43510ad9316c60c9b50dd5c09c2dae4d54 Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Wed, 13 Feb 2019 21:28:18 +0100
Subject: [PATCH 16/24] Change robots.txt to exclude some URLs (#10037)

- Exclude static assets
- Exclude uploaded files
- Exclude alternate versions of the profile page
- Exclude media proxy URLs
---
 public/robots.txt | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/public/robots.txt b/public/robots.txt
index 3c9c7c01f30..36afc85eff5 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -1,5 +1,13 @@
-# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
-#
-# To ban all spiders from the entire site uncomment the next two lines:
-# User-agent: *
-# Disallow: /
+User-Agent: *
+Disallow: /users/*/followers
+Disallow: /users/*/following
+Disallow: /@*/media
+Disallow: /@*/with_replies
+Disallow: /@*/tagged/*
+Disallow: /media_proxy/*
+Disallow: /emoji/*
+Disallow: /packs/*
+Disallow: /sounds/*
+Disallow: /system/*
+Disallow: /avatars/*
+Disallow: /headers/*

From 309043b158f5a4187b6f603f346ed17ee6ddb190 Mon Sep 17 00:00:00 2001
From: Ben Lubar <ben.lubar+github@gmail.com>
Date: Wed, 13 Feb 2019 18:04:43 -0600
Subject: [PATCH 17/24] Improve image description user experience (#10036)

* Add image descriptions to searchable post content.

* Allow multi-line image descriptions.

* Request image descriptions in the same query as posts when creating the search index.

(see https://github.com/tootsuite/mastodon/pull/10036#discussion_r256551624)
---
 app/chewy/statuses_index.rb                                   | 4 ++--
 app/javascript/mastodon/features/compose/components/upload.js | 3 +--
 app/javascript/styles/mastodon/components.scss                | 2 +-
 3 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/app/chewy/statuses_index.rb b/app/chewy/statuses_index.rb
index d3104172c48..eafc1818b8f 100644
--- a/app/chewy/statuses_index.rb
+++ b/app/chewy/statuses_index.rb
@@ -31,7 +31,7 @@ class StatusesIndex < Chewy::Index
     },
   }
 
-  define_type ::Status.unscoped.without_reblogs do
+  define_type ::Status.unscoped.without_reblogs.includes(:media_attachments) do
     crutch :mentions do |collection|
       data = ::Mention.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
       data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
@@ -50,7 +50,7 @@ class StatusesIndex < Chewy::Index
     root date_detection: false do
       field :account_id, type: 'long'
 
-      field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].join("\n\n") } do
+      field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.media_attachments.map(&:description)).join("\n\n") } do
         field :stemmed, type: 'text', analyzer: 'content'
       end
 
diff --git a/app/javascript/mastodon/features/compose/components/upload.js b/app/javascript/mastodon/features/compose/components/upload.js
index 038d7ee28af..629cbc36ac3 100644
--- a/app/javascript/mastodon/features/compose/components/upload.js
+++ b/app/javascript/mastodon/features/compose/components/upload.js
@@ -108,9 +108,8 @@ class Upload extends ImmutablePureComponent {
                 <label>
                   <span style={{ display: 'none' }}>{intl.formatMessage(messages.description)}</span>
 
-                  <input
+                  <textarea
                     placeholder={intl.formatMessage(messages.description)}
-                    type='text'
                     value={description}
                     maxLength={420}
                     onFocus={this.handleInputFocus}
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 8533e4eb5b8..d88557559e4 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -476,7 +476,7 @@
         opacity: 0;
         transition: opacity .1s ease;
 
-        input {
+        textarea {
           background: transparent;
           color: $secondary-text-color;
           border: 0;

From a5992e58832fdc0199f70ed2fa791a619fcad258 Mon Sep 17 00:00:00 2001
From: nightpool <nightpool@users.noreply.github.com>
Date: Wed, 13 Feb 2019 21:11:47 -0500
Subject: [PATCH 18/24] Change robots.txt to exclude only media proxy URLs
 (#10038)

* Revert "Change robots.txt to exclude some URLs (#10037)"

This reverts commit 80161f43510ad9316c60c9b50dd5c09c2dae4d54.

* Let's block media_proxy

/media_proxy/ is a dynamic route used for requesting uncached media, so it's
probably bad to let crawlers use it

* misleading comment
---
 public/robots.txt | 17 ++++-------------
 1 file changed, 4 insertions(+), 13 deletions(-)

diff --git a/public/robots.txt b/public/robots.txt
index 36afc85eff5..d93648beee2 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -1,13 +1,4 @@
-User-Agent: *
-Disallow: /users/*/followers
-Disallow: /users/*/following
-Disallow: /@*/media
-Disallow: /@*/with_replies
-Disallow: /@*/tagged/*
-Disallow: /media_proxy/*
-Disallow: /emoji/*
-Disallow: /packs/*
-Disallow: /sounds/*
-Disallow: /system/*
-Disallow: /avatars/*
-Disallow: /headers/*
+# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
+
+User-agent: *
+Disallow: /media_proxy/

From 99fa1ce93d02ef4ee8bde9311f4cc56a64fe35f4 Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Thu, 14 Feb 2019 06:27:54 +0100
Subject: [PATCH 19/24] Add tight rate-limit for API deletions (#10042)

Deletions take a lot of resources to execute and cause a lot of
federation traffic, so it makes sense to decrease the number
someone can queue up through the API.

30 per 30 minutes
---
 config/initializers/rack_attack.rb | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb
index 35302e37b1b..28201cc64c8 100644
--- a/config/initializers/rack_attack.rb
+++ b/config/initializers/rack_attack.rb
@@ -46,14 +46,14 @@ class Rack::Attack
   end
 
   throttle('throttle_authenticated_api', limit: 300, period: 5.minutes) do |req|
-    req.api_request? && req.authenticated_user_id
+    req.authenticated_user_id if req.api_request?
   end
 
   throttle('throttle_unauthenticated_api', limit: 7_500, period: 5.minutes) do |req|
     req.ip if req.api_request?
   end
 
-  throttle('throttle_media', limit: 30, period: 30.minutes) do |req|
+  throttle('throttle_api_media', limit: 30, period: 30.minutes) do |req|
     req.authenticated_user_id if req.post? && req.path.start_with?('/api/v1/media')
   end
 
@@ -61,6 +61,13 @@ class Rack::Attack
     req.ip if req.post? && req.path == '/api/v1/accounts'
   end
 
+  API_DELETE_REBLOG_REGEX = /\A\/api\/v1\/statuses\/[\d]+\/unreblog/.freeze
+  API_DELETE_STATUS_REGEX = /\A\/api\/v1\/statuses\/[\d]+/.freeze
+
+  throttle('throttle_api_delete', limit: 30, period: 30.minutes) do |req|
+    req.authenticated_user_id if (req.post? && req.path =~ API_DELETE_REBLOG_REGEX) || (req.delete? && req.path =~ API_DELETE_STATUS_REGEX)
+  end
+
   throttle('protected_paths', limit: 25, period: 5.minutes) do |req|
     req.ip if req.post? && req.path =~ PROTECTED_PATHS_REGEX
   end

From aa832198750f99303b1555f412e5405954863a63 Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Thu, 14 Feb 2019 15:46:42 +0100
Subject: [PATCH 20/24] Fix hashtag column not subscribing to stream on mount
 (#10040)

Fix #9895
---
 .../mastodon/features/hashtag_timeline/index.js | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js
index c2e026d1361..0d3c97a6489 100644
--- a/app/javascript/mastodon/features/hashtag_timeline/index.js
+++ b/app/javascript/mastodon/features/hashtag_timeline/index.js
@@ -41,15 +41,19 @@ class HashtagTimeline extends React.PureComponent {
 
   title = () => {
     let title = [this.props.params.id];
+
     if (this.additionalFor('any')) {
-      title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.any'  values={{ additional: this.additionalFor('any') }} defaultMessage='or {additional}' />);
+      title.push(' ', <FormattedMessage key='any' id='hashtag.column_header.tag_mode.any'  values={{ additional: this.additionalFor('any') }} defaultMessage='or {additional}' />);
     }
+
     if (this.additionalFor('all')) {
-      title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.all'  values={{ additional: this.additionalFor('all') }} defaultMessage='and {additional}' />);
+      title.push(' ', <FormattedMessage key='all' id='hashtag.column_header.tag_mode.all'  values={{ additional: this.additionalFor('all') }} defaultMessage='and {additional}' />);
     }
+
     if (this.additionalFor('none')) {
-      title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.none' values={{ additional: this.additionalFor('none') }} defaultMessage='without {additional}' />);
+      title.push(' ', <FormattedMessage key='none' id='hashtag.column_header.tag_mode.none' values={{ additional: this.additionalFor('none') }} defaultMessage='without {additional}' />);
     }
+
     return title;
   }
 
@@ -77,9 +81,10 @@ class HashtagTimeline extends React.PureComponent {
     let all  = (tags.all || []).map(tag => tag.value);
     let none = (tags.none || []).map(tag => tag.value);
 
-    [id, ...any].map((tag) => {
-      this.disconnects.push(dispatch(connectHashtagStream(id, tag, (status) => {
+    [id, ...any].map(tag => {
+      this.disconnects.push(dispatch(connectHashtagStream(id, tag, status => {
         let tags = status.tags.map(tag => tag.name);
+
         return all.filter(tag => tags.includes(tag)).length === all.length &&
                none.filter(tag => tags.includes(tag)).length === 0;
       })));
@@ -95,12 +100,14 @@ class HashtagTimeline extends React.PureComponent {
     const { dispatch } = this.props;
     const { id, tags } = this.props.params;
 
+    this._subscribe(dispatch, id, tags);
     dispatch(expandHashtagTimeline(id, { tags }));
   }
 
   componentWillReceiveProps (nextProps) {
     const { dispatch, params } = this.props;
     const { id, tags } = nextProps.params;
+
     if (id !== params.id || !isEqual(tags, params.tags)) {
       this._unsubscribe();
       this._subscribe(dispatch, id, tags);

From f9a338b473e181dd725f9185d09394624088efac Mon Sep 17 00:00:00 2001
From: rinsuki <428rinsuki+git@gmail.com>
Date: Fri, 15 Feb 2019 01:03:01 +0900
Subject: [PATCH 21/24] Fix breaks when opening a reply tree in WebUI (#10046)

fix #10045
---
 app/javascript/mastodon/components/status.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index 386404b574b..3e98d374b83 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -86,7 +86,7 @@ class Status extends ImmutablePureComponent {
 
   // Track height changes we know about to compensate scrolling
   componentDidMount () {
-    this.didShowCard = !this.props.muted && !this.props.hidden && this.props.status.get('card');
+    this.didShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card');
   }
 
   getSnapshotBeforeUpdate () {
@@ -99,7 +99,7 @@ class Status extends ImmutablePureComponent {
 
   // Compensate height changes
   componentDidUpdate (prevProps, prevState, snapshot) {
-    const doShowCard  = !this.props.muted && !this.props.hidden && this.props.status.get('card');
+    const doShowCard  = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card');
     if (doShowCard && !this.didShowCard) {
       this.didShowCard = true;
       if (snapshot !== null && this.props.updateScrollBottom) {

From 57c2fc8454b84de3b9be49e5f614c9d0df48cbdc Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <dependabot[bot]@users.noreply.github.com>
Date: Fri, 15 Feb 2019 15:38:29 +0100
Subject: [PATCH 22/24] Bump better_errors from 2.5.0 to 2.5.1 (#10050)

Bumps [better_errors](https://github.com/BetterErrors/better_errors) from 2.5.0 to 2.5.1.
- [Release notes](https://github.com/BetterErrors/better_errors/releases)
- [Changelog](https://github.com/BetterErrors/better_errors/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BetterErrors/better_errors/compare/v2.5.0...v2.5.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
---
 Gemfile.lock | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index ee536f2ab63..1e4f085a9ad 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -92,7 +92,7 @@ GEM
     aws-sigv4 (1.0.3)
     bcrypt (3.1.12)
     benchmark-ips (2.7.2)
-    better_errors (2.5.0)
+    better_errors (2.5.1)
       coderay (>= 1.0.0)
       erubi (>= 1.0.0)
       rack (>= 0.9.0)

From b01f26ffbd7cf1ca42a6a16798c1f10e56a8e82e Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Fri, 15 Feb 2019 16:08:48 +0100
Subject: [PATCH 23/24] Change conversations to always show names of other
 participants (#10047)

Fix #9190
---
 .../mastodon/components/display_name.js       | 22 ++++++++++++++-----
 1 file changed, 16 insertions(+), 6 deletions(-)

diff --git a/app/javascript/mastodon/components/display_name.js b/app/javascript/mastodon/components/display_name.js
index acddf77c543..32809778ae7 100644
--- a/app/javascript/mastodon/components/display_name.js
+++ b/app/javascript/mastodon/components/display_name.js
@@ -11,26 +11,36 @@ export default class DisplayName extends React.PureComponent {
   };
 
   render () {
-    const { account, others, localDomain } = this.props;
-    const displayNameHtml = { __html: account.get('display_name_html') };
+    const { others, localDomain } = this.props;
 
-    let suffix;
+    let displayName, suffix, account;
 
     if (others && others.size > 1) {
-      suffix = `+${others.size}`;
+      displayName = others.take(2).map(a => <bdi key={a.get('id')}><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi>).reduce((prev, cur) => [prev, ', ', cur]);
+
+      if (others.size - 2 > 0) {
+        suffix = `+${others.size - 2}`;
+      }
     } else {
+      if (others) {
+        account = others.first();
+      } else {
+        account = this.props.account;
+      }
+
       let acct = account.get('acct');
 
       if (acct.indexOf('@') === -1 && localDomain) {
         acct = `${acct}@${localDomain}`;
       }
 
-      suffix = <span className='display-name__account'>@{acct}</span>;
+      displayName = <bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>;
+      suffix      = <span className='display-name__account'>@{acct}</span>;
     }
 
     return (
       <span className='display-name'>
-        <bdi><strong className='display-name__html' dangerouslySetInnerHTML={displayNameHtml} /></bdi> {suffix}
+        {displayName} {suffix}
       </span>
     );
   }

From 8ef50706a11e115e8b4aa31b30de93738bc7e754 Mon Sep 17 00:00:00 2001
From: Eugen Rochko <eugen@zeonfederated.com>
Date: Fri, 15 Feb 2019 16:08:59 +0100
Subject: [PATCH 24/24] Fix relay enabling/disabling not resetting inbox
 availability status (#10048)

Fix #10033
---
 app/models/relay.rb | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/app/models/relay.rb b/app/models/relay.rb
index 7478c110d45..6934a5c6287 100644
--- a/app/models/relay.rb
+++ b/app/models/relay.rb
@@ -29,6 +29,7 @@ class Relay < ApplicationRecord
     payload     = Oj.dump(follow_activity(activity_id))
 
     update!(state: :pending, follow_activity_id: activity_id)
+    DeliveryFailureTracker.new(inbox_url).track_success!
     ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url)
   end
 
@@ -37,6 +38,7 @@ class Relay < ApplicationRecord
     payload     = Oj.dump(unfollow_activity(activity_id))
 
     update!(state: :idle, follow_activity_id: nil)
+    DeliveryFailureTracker.new(inbox_url).track_success!
     ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url)
   end