about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/api/v1/accounts/credentials_controller.rb2
-rw-r--r--app/javascript/mastodon/actions/search.js2
-rw-r--r--app/javascript/mastodon/components/media_gallery.js6
-rw-r--r--app/javascript/styles/mastodon/basics.scss1
-rw-r--r--app/javascript/styles/mastodon/components.scss20
-rw-r--r--app/javascript/styles/mastodon/stream_entries.scss20
-rw-r--r--app/lib/activitypub/activity.rb2
-rw-r--r--app/lib/ostatus/activity/creation.rb2
-rw-r--r--app/models/account_domain_block.rb8
-rw-r--r--app/models/block.rb4
-rw-r--r--app/models/concerns/account_avatar.rb12
-rw-r--r--app/models/concerns/account_header.rb12
-rw-r--r--app/models/concerns/relationship_cacheable.rb16
-rw-r--r--app/models/follow.rb1
-rw-r--r--app/models/follow_request.rb1
-rw-r--r--app/models/mute.rb4
-rw-r--r--app/models/status.rb6
-rw-r--r--app/presenters/account_relationships_presenter.rb68
18 files changed, 144 insertions, 43 deletions
diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb
index da534d960..68af22529 100644
--- a/app/controllers/api/v1/accounts/credentials_controller.rb
+++ b/app/controllers/api/v1/accounts/credentials_controller.rb
@@ -20,6 +20,6 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
   private
 
   def account_params
-    params.permit(:display_name, :note, :avatar, :header)
+    params.permit(:display_name, :note, :avatar, :header, :locked)
   end
 end
diff --git a/app/javascript/mastodon/actions/search.js b/app/javascript/mastodon/actions/search.js
index 78c6109f7..73cb106ec 100644
--- a/app/javascript/mastodon/actions/search.js
+++ b/app/javascript/mastodon/actions/search.js
@@ -1,4 +1,5 @@
 import api from '../api';
+import { fetchRelationships } from './accounts';
 
 export const SEARCH_CHANGE = 'SEARCH_CHANGE';
 export const SEARCH_CLEAR  = 'SEARCH_CLEAR';
@@ -38,6 +39,7 @@ export function submitSearch() {
       },
     }).then(response => {
       dispatch(fetchSearchSuccess(response.data));
+      dispatch(fetchRelationships(response.data.accounts.map(item => item.id)));
     }).catch(error => {
       dispatch(fetchSearchFail(error));
     });
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index 00943e205..a3ffc45ea 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -227,12 +227,8 @@ export default class MediaGallery extends React.PureComponent {
     const style = {};
 
     if (this.isStandaloneEligible()) {
-      if (!visible && width) {
-        // only need to forcibly set the height in "sensitive" mode
+      if (width) {
         style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']);
-      } else {
-        // layout automatically, using image's natural aspect ratio
-        style.height = '';
       }
     } else {
       // crop the image
diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss
index 0e3b9ad6b..bec0d4d91 100644
--- a/app/javascript/styles/mastodon/basics.scss
+++ b/app/javascript/styles/mastodon/basics.scss
@@ -122,5 +122,6 @@ button {
     height: 100%;
     align-items: center;
     justify-content: center;
+    outline: 0 !important;
   }
 }
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index d57481f97..66e4adc2b 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -686,12 +686,13 @@
   background: transparent;
   border: 0;
   color: lighten($ui-base-color, 8%);
-  font-weight: 500;
+  font-weight: 700;
   font-size: 11px;
   padding: 0 6px;
   text-transform: uppercase;
-  line-height: inherit;
+  line-height: 20px;
   cursor: pointer;
+  vertical-align: middle;
 }
 
 .status__prepend-icon-wrapper {
@@ -899,6 +900,11 @@
       height: 24px;
       margin: -1px 0 0;
     }
+
+    .status__content__spoiler-link {
+      line-height: 24px;
+      margin: -1px 0 0;
+    }
   }
 
   .video-player {
@@ -2667,12 +2673,16 @@ a.status-card {
   background: $base-overlay-background;
   color: $ui-primary-color;
   border: 0;
+  padding: 0;
   width: 100%;
   height: 100%;
+  border-radius: 4px;
+  appearance: none;
 
   &:hover,
   &:active,
   &:focus {
+    padding: 0;
     color: lighten($ui-primary-color, 8%);
   }
 }
@@ -2685,7 +2695,7 @@ a.status-card {
 .media-spoiler__trigger {
   display: block;
   font-size: 11px;
-  font-weight: 500;
+  font-weight: 700;
 }
 
 .spoiler-button {
@@ -4091,6 +4101,7 @@ a.status-card {
   box-sizing: border-box;
   margin-top: 8px;
   overflow: hidden;
+  border-radius: 4px;
   position: relative;
   width: 100%;
 }
@@ -4101,6 +4112,8 @@ a.status-card {
   display: block;
   float: left;
   position: relative;
+  border-radius: 4px;
+  overflow: hidden;
 
   &.standalone {
     .media-gallery__item-gifv-thumbnail {
@@ -4113,6 +4126,7 @@ a.status-card {
   cursor: zoom-in;
   display: block;
   text-decoration: none;
+  color: $ui-secondary-color;
   height: 100%;
   line-height: 0;
 
diff --git a/app/javascript/styles/mastodon/stream_entries.scss b/app/javascript/styles/mastodon/stream_entries.scss
index e11ca29d9..442b143a0 100644
--- a/app/javascript/styles/mastodon/stream_entries.scss
+++ b/app/javascript/styles/mastodon/stream_entries.scss
@@ -146,10 +146,10 @@
 
       a.status__content__spoiler-link {
         color: $primary-text-color;
-        background: $ui-primary-color;
+        background: $ui-base-color;
 
         &:hover {
-          background: lighten($ui-primary-color, 8%);
+          background: lighten($ui-base-color, 8%);
         }
       }
     }
@@ -214,10 +214,10 @@
 
       a.status__content__spoiler-link {
         color: $primary-text-color;
-        background: $ui-primary-color;
+        background: $ui-base-color;
 
         &:hover {
-          background: lighten($ui-primary-color, 8%);
+          background: lighten($ui-base-color, 8%);
         }
       }
     }
@@ -260,16 +260,8 @@
   }
 
   .media-spoiler {
-    background: $ui-primary-color;
-    color: $white;
-    transition: all 40ms linear;
-
-    &:hover,
-    &:active,
-    &:focus {
-      background: darken($ui-primary-color, 2%);
-      color: unset;
-    }
+    background: $ui-base-color;
+    color: $ui-primary-color;
   }
 
   .pre-header {
diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb
index 0f9e4f263..4617905c6 100644
--- a/app/lib/activitypub/activity.rb
+++ b/app/lib/activitypub/activity.rb
@@ -74,7 +74,7 @@ class ActivityPub::Activity
 
     # Only continue if the status is supposed to have
     # arrived in real-time
-    return unless @options[:override_timestamps]
+    return unless @options[:override_timestamps] || status.within_realtime_window?
 
     distribute_to_followers(status)
   end
diff --git a/app/lib/ostatus/activity/creation.rb b/app/lib/ostatus/activity/creation.rb
index b38407cd3..7cf2d90dc 100644
--- a/app/lib/ostatus/activity/creation.rb
+++ b/app/lib/ostatus/activity/creation.rb
@@ -61,7 +61,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
     Rails.logger.debug "Queuing remote status #{status.id} (#{id}) for distribution"
 
     LinkCrawlWorker.perform_async(status.id) unless status.spoiler_text?
-    DistributionWorker.perform_async(status.id) if @options[:override_timestamps]
+    DistributionWorker.perform_async(status.id) if @options[:override_timestamps] || status.within_realtime_window?
 
     status
   end
diff --git a/app/models/account_domain_block.rb b/app/models/account_domain_block.rb
index abcc923b3..bc00b4f32 100644
--- a/app/models/account_domain_block.rb
+++ b/app/models/account_domain_block.rb
@@ -16,12 +16,16 @@ class AccountDomainBlock < ApplicationRecord
   belongs_to :account
   validates :domain, presence: true, uniqueness: { scope: :account_id }
 
-  after_create  :remove_blocking_cache
-  after_destroy :remove_blocking_cache
+  after_commit :remove_blocking_cache
+  after_commit :remove_relationship_cache
 
   private
 
   def remove_blocking_cache
     Rails.cache.delete("exclude_domains_for:#{account_id}")
   end
+
+  def remove_relationship_cache
+    Rails.cache.delete_matched("relationship:#{account_id}:*")
+  end
 end
diff --git a/app/models/block.rb b/app/models/block.rb
index 441e6bca3..d6ecabd3b 100644
--- a/app/models/block.rb
+++ b/app/models/block.rb
@@ -12,14 +12,14 @@
 
 class Block < ApplicationRecord
   include Paginable
+  include RelationshipCacheable
 
   belongs_to :account
   belongs_to :target_account, class_name: 'Account'
 
   validates :account_id, uniqueness: { scope: :target_account_id }
 
-  after_create  :remove_blocking_cache
-  after_destroy :remove_blocking_cache
+  after_commit :remove_blocking_cache
 
   private
 
diff --git a/app/models/concerns/account_avatar.rb b/app/models/concerns/account_avatar.rb
index 8a5c9a22c..53d0d876f 100644
--- a/app/models/concerns/account_avatar.rb
+++ b/app/models/concerns/account_avatar.rb
@@ -7,9 +7,15 @@ module AccountAvatar
 
   class_methods do
     def avatar_styles(file)
-      styles = { original: '120x120#' }
-      styles[:static] = { format: 'png', convert_options: '-coalesce' } if file.content_type == 'image/gif'
+      styles   = {}
+      geometry = Paperclip::Geometry.from_file(file)
+
+      styles[:original] = '120x120#' if geometry.width != geometry.height || geometry.width > 120 || geometry.height > 120
+      styles[:static]   = { format: 'png', convert_options: '-coalesce' } if file.content_type == 'image/gif'
+
       styles
+    rescue Paperclip::Errors::NotIdentifiedByImageMagickError
+      {}
     end
 
     private :avatar_styles
@@ -17,7 +23,7 @@ module AccountAvatar
 
   included do
     # Avatar upload
-    has_attached_file :avatar, styles: ->(f) { avatar_styles(f) }, convert_options: { all: '-quality 80 -strip' }
+    has_attached_file :avatar, styles: ->(f) { avatar_styles(f) }, convert_options: { all: '-strip' }
     validates_attachment_content_type :avatar, content_type: IMAGE_MIME_TYPES
     validates_attachment_size :avatar, less_than: 2.megabytes
   end
diff --git a/app/models/concerns/account_header.rb b/app/models/concerns/account_header.rb
index aff2aa3f9..991473d8c 100644
--- a/app/models/concerns/account_header.rb
+++ b/app/models/concerns/account_header.rb
@@ -7,9 +7,15 @@ module AccountHeader
 
   class_methods do
     def header_styles(file)
-      styles = { original: '700x335#' }
-      styles[:static] = { format: 'png', convert_options: '-coalesce' } if file.content_type == 'image/gif'
+      styles   = {}
+      geometry = Paperclip::Geometry.from_file(file)
+
+      styles[:original] = '700x335#' unless geometry.width == 700 && geometry.height == 335
+      styles[:static]   = { format: 'png', convert_options: '-coalesce' } if file.content_type == 'image/gif'
+
       styles
+    rescue Paperclip::Errors::NotIdentifiedByImageMagickError
+      {}
     end
 
     private :header_styles
@@ -17,7 +23,7 @@ module AccountHeader
 
   included do
     # Header upload
-    has_attached_file :header, styles: ->(f) { header_styles(f) }, convert_options: { all: '-quality 80 -strip' }
+    has_attached_file :header, styles: ->(f) { header_styles(f) }, convert_options: { all: '-strip' }
     validates_attachment_content_type :header, content_type: IMAGE_MIME_TYPES
     validates_attachment_size :header, less_than: 2.megabytes
   end
diff --git a/app/models/concerns/relationship_cacheable.rb b/app/models/concerns/relationship_cacheable.rb
new file mode 100644
index 000000000..0d9359f7e
--- /dev/null
+++ b/app/models/concerns/relationship_cacheable.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module RelationshipCacheable
+  extend ActiveSupport::Concern
+
+  included do
+    after_commit :remove_relationship_cache
+  end
+
+  private
+
+  def remove_relationship_cache
+    Rails.cache.delete("relationship:#{account_id}:#{target_account_id}")
+    Rails.cache.delete("relationship:#{target_account_id}:#{account_id}")
+  end
+end
diff --git a/app/models/follow.rb b/app/models/follow.rb
index f953b8e3e..8e6fe537a 100644
--- a/app/models/follow.rb
+++ b/app/models/follow.rb
@@ -13,6 +13,7 @@
 
 class Follow < ApplicationRecord
   include Paginable
+  include RelationshipCacheable
 
   belongs_to :account, counter_cache: :following_count
 
diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb
index bd6c4a0b9..cde26ceed 100644
--- a/app/models/follow_request.rb
+++ b/app/models/follow_request.rb
@@ -13,6 +13,7 @@
 
 class FollowRequest < ApplicationRecord
   include Paginable
+  include RelationshipCacheable
 
   belongs_to :account
   belongs_to :target_account, class_name: 'Account'
diff --git a/app/models/mute.rb b/app/models/mute.rb
index da4787179..ebb3818c7 100644
--- a/app/models/mute.rb
+++ b/app/models/mute.rb
@@ -13,14 +13,14 @@
 
 class Mute < ApplicationRecord
   include Paginable
+  include RelationshipCacheable
 
   belongs_to :account
   belongs_to :target_account, class_name: 'Account'
 
   validates :account_id, uniqueness: { scope: :target_account_id }
 
-  after_create  :remove_blocking_cache
-  after_destroy :remove_blocking_cache
+  after_commit :remove_blocking_cache
 
   private
 
diff --git a/app/models/status.rb b/app/models/status.rb
index 6fb9b15dd..86bf3deba 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -83,6 +83,8 @@ class Status < ApplicationRecord
 
   delegate :domain, to: :account, prefix: true
 
+  REAL_TIME_WINDOW = 6.hours
+
   def searchable_by(preloaded = nil)
     ids = [account_id]
 
@@ -111,6 +113,10 @@ class Status < ApplicationRecord
     !reblog_of_id.nil?
   end
 
+  def within_realtime_window?
+    created_at >= REAL_TIME_WINDOW.ago
+  end
+
   def verb
     if destroyed?
       :delete
diff --git a/app/presenters/account_relationships_presenter.rb b/app/presenters/account_relationships_presenter.rb
index bf1ba3716..d27fb7b01 100644
--- a/app/presenters/account_relationships_presenter.rb
+++ b/app/presenters/account_relationships_presenter.rb
@@ -5,11 +5,67 @@ class AccountRelationshipsPresenter
               :muting, :requested, :domain_blocking
 
   def initialize(account_ids, current_account_id, **options)
-    @following       = Account.following_map(account_ids, current_account_id).merge(options[:following_map] || {})
-    @followed_by     = Account.followed_by_map(account_ids, current_account_id).merge(options[:followed_by_map] || {})
-    @blocking        = Account.blocking_map(account_ids, current_account_id).merge(options[:blocking_map] || {})
-    @muting          = Account.muting_map(account_ids, current_account_id).merge(options[:muting_map] || {})
-    @requested       = Account.requested_map(account_ids, current_account_id).merge(options[:requested_map] || {})
-    @domain_blocking = Account.domain_blocking_map(account_ids, current_account_id).merge(options[:domain_blocking_map] || {})
+    @account_ids        = account_ids.map { |a| a.is_a?(Account) ? a.id : a }
+    @current_account_id = current_account_id
+
+    @following       = cached[:following].merge(Account.following_map(@uncached_account_ids, @current_account_id))
+    @followed_by     = cached[:followed_by].merge(Account.followed_by_map(@uncached_account_ids, @current_account_id))
+    @blocking        = cached[:blocking].merge(Account.blocking_map(@uncached_account_ids, @current_account_id))
+    @muting          = cached[:muting].merge(Account.muting_map(@uncached_account_ids, @current_account_id))
+    @requested       = cached[:requested].merge(Account.requested_map(@uncached_account_ids, @current_account_id))
+    @domain_blocking = cached[:domain_blocking].merge(Account.domain_blocking_map(@uncached_account_ids, @current_account_id))
+
+    cache_uncached!
+
+    @following.merge!(options[:following_map] || {})
+    @followed_by.merge!(options[:followed_by_map] || {})
+    @blocking.merge!(options[:blocking_map] || {})
+    @muting.merge!(options[:muting_map] || {})
+    @requested.merge!(options[:requested_map] || {})
+    @domain_blocking.merge!(options[:domain_blocking_map] || {})
+  end
+
+  private
+
+  def cached
+    return @cached if defined?(@cached)
+
+    @cached = {
+      following: {},
+      followed_by: {},
+      blocking: {},
+      muting: {},
+      requested: {},
+      domain_blocking: {},
+    }
+
+    @uncached_account_ids = []
+
+    @account_ids.each do |account_id|
+      maps_for_account = Rails.cache.read("relationship:#{@current_account_id}:#{account_id}")
+
+      if maps_for_account.is_a?(Hash)
+        @cached.merge!(maps_for_account)
+      else
+        @uncached_account_ids << account_id
+      end
+    end
+
+    @cached
+  end
+
+  def cache_uncached!
+    @uncached_account_ids.each do |account_id|
+      maps_for_account = {
+        following:       { account_id => following[account_id] },
+        followed_by:     { account_id => followed_by[account_id] },
+        blocking:        { account_id => blocking[account_id] },
+        muting:          { account_id => muting[account_id] },
+        requested:       { account_id => requested[account_id] },
+        domain_blocking: { account_id => domain_blocking[account_id] },
+      }
+
+      Rails.cache.write("relationship:#{@current_account_id}:#{account_id}", maps_for_account, expires_in: 1.day)
+    end
   end
 end