about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/activitypub/inboxes_controller.rb2
-rw-r--r--app/controllers/admin/base_controller.rb7
-rw-r--r--app/controllers/filters_controller.rb5
-rw-r--r--app/controllers/invites_controller.rb5
-rw-r--r--app/controllers/settings/base_controller.rb5
-rw-r--r--app/controllers/settings/follower_domains_controller.rb2
-rw-r--r--app/controllers/settings/sessions_controller.rb5
-rw-r--r--app/javascript/core/settings.js16
-rw-r--r--app/javascript/mastodon/actions/compose.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/compose_form.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/upload.js12
-rw-r--r--app/javascript/mastodon/features/direct_timeline/components/conversation.js1
-rw-r--r--app/javascript/mastodon/features/direct_timeline/components/conversations_list.js18
-rw-r--r--app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js2
-rw-r--r--app/javascript/packs/public.js1
-rw-r--r--app/javascript/styles/mastodon/rtl.scss15
-rw-r--r--app/lib/activitypub/activity/create.rb13
-rw-r--r--app/models/account_conversation.rb3
-rw-r--r--app/services/fetch_link_card_service.rb12
-rw-r--r--app/services/verify_link_service.rb2
-rw-r--r--app/views/layouts/admin.html.haml2
-rw-r--r--app/views/settings/profiles/show.html.haml8
-rw-r--r--app/views/stream_entries/_simple_status.html.haml1
-rw-r--r--app/workers/activitypub/processing_worker.rb4
24 files changed, 89 insertions, 56 deletions
diff --git a/app/controllers/activitypub/inboxes_controller.rb b/app/controllers/activitypub/inboxes_controller.rb
index af51e32d5..8f5e1887e 100644
--- a/app/controllers/activitypub/inboxes_controller.rb
+++ b/app/controllers/activitypub/inboxes_controller.rb
@@ -36,6 +36,6 @@ class ActivityPub::InboxesController < Api::BaseController
   end
 
   def process_payload
-    ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body.force_encoding('UTF-8'))
+    ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body.force_encoding('UTF-8'), @account&.id)
   end
 end
diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb
index fc299f74c..f2190ddf9 100644
--- a/app/controllers/admin/base_controller.rb
+++ b/app/controllers/admin/base_controller.rb
@@ -9,6 +9,13 @@ module Admin
 
     before_action :require_staff!
     before_action :set_pack
+    before_action :set_body_classes
+
+    private
+
+    def set_body_classes
+      @body_classes = 'admin'
+    end
 
     def set_pack
       use_pack 'admin'
diff --git a/app/controllers/filters_controller.rb b/app/controllers/filters_controller.rb
index 0d1200fcc..f1e110d87 100644
--- a/app/controllers/filters_controller.rb
+++ b/app/controllers/filters_controller.rb
@@ -8,6 +8,7 @@ class FiltersController < ApplicationController
   before_action :set_filters, only: :index
   before_action :set_filter, only: [:edit, :update, :destroy]
   before_action :set_pack
+  before_action :set_body_classes
 
   def index
     @filters = current_account.custom_filters
@@ -59,4 +60,8 @@ class FiltersController < ApplicationController
   def resource_params
     params.require(:custom_filter).permit(:phrase, :expires_in, :irreversible, :whole_word, context: [])
   end
+
+  def set_body_classes
+    @body_classes = 'admin'
+  end
 end
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 3dc934761..52cddc404 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -7,6 +7,7 @@ class InvitesController < ApplicationController
 
   before_action :authenticate_user!
   before_action :set_pack
+  before_action :set_body_classes
 
   def index
     authorize :invite, :create?
@@ -49,4 +50,8 @@ class InvitesController < ApplicationController
   def resource_params
     params.require(:invite).permit(:max_uses, :expires_in, :autofollow)
   end
+
+  def set_body_classes
+    @body_classes = 'admin'
+  end
 end
diff --git a/app/controllers/settings/base_controller.rb b/app/controllers/settings/base_controller.rb
index 7322d461b..34ef16568 100644
--- a/app/controllers/settings/base_controller.rb
+++ b/app/controllers/settings/base_controller.rb
@@ -5,8 +5,13 @@ class Settings::BaseController < ApplicationController
 
   before_action :authenticate_user!
   before_action :set_pack
+  before_action :set_body_classes
 
   def set_pack
     use_pack 'settings'
   end
+
+  def set_body_classes
+    @body_classes = 'admin'
+  end
 end
diff --git a/app/controllers/settings/follower_domains_controller.rb b/app/controllers/settings/follower_domains_controller.rb
index 83945df52..8aae379aa 100644
--- a/app/controllers/settings/follower_domains_controller.rb
+++ b/app/controllers/settings/follower_domains_controller.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require 'sidekiq-bulk'
-
 class Settings::FollowerDomainsController < Settings::BaseController
   def show
     @account = current_account
diff --git a/app/controllers/settings/sessions_controller.rb b/app/controllers/settings/sessions_controller.rb
index 780ea64b4..f235dd477 100644
--- a/app/controllers/settings/sessions_controller.rb
+++ b/app/controllers/settings/sessions_controller.rb
@@ -3,6 +3,7 @@
 #  Intentionally does not inherit from BaseController
 class Settings::SessionsController < ApplicationController
   before_action :set_session, only: :destroy
+  before_action :set_body_classes
 
   def destroy
     @session.destroy!
@@ -15,4 +16,8 @@ class Settings::SessionsController < ApplicationController
   def set_session
     @session = current_user.session_activations.find(params[:id])
   end
+
+  def set_body_classes
+    @body_classes = 'admin'
+  end
 end
diff --git a/app/javascript/core/settings.js b/app/javascript/core/settings.js
index af97c84f9..23a303747 100644
--- a/app/javascript/core/settings.js
+++ b/app/javascript/core/settings.js
@@ -1,30 +1,16 @@
 //  This file will be loaded on settings pages, regardless of theme.
 
-const { length } = require('stringz');
 const { delegate } = require('rails-ujs');
 import emojify from '../mastodon/features/emoji/emoji';
 
 delegate(document, '#account_display_name', 'input', ({ target }) => {
-  const nameCounter = document.querySelector('.name-counter');
-  const name        = document.querySelector('.card .display-name strong');
-
-  if (nameCounter) {
-    nameCounter.textContent = 30 - length(target.value);
-  }
+  const name = document.querySelector('.card .display-name strong');
 
   if (name) {
     name.innerHTML = emojify(target.value);
   }
 });
 
-delegate(document, '#account_note', 'input', ({ target }) => {
-  const noteCounter = document.querySelector('.note-counter');
-
-  if (noteCounter) {
-    noteCounter.textContent = 500 - length(target.value);
-  }
-});
-
 delegate(document, '#account_avatar', 'change', ({ target }) => {
   const avatar = document.querySelector('.card .avatar img');
   const [file] = target.files || [];
diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index fac8d32a1..86d83122f 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -142,7 +142,7 @@ export function submitCompose(routerHistory) {
         }
       };
 
-      if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0) {
+      if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) {
         routerHistory.push('/timelines/direct');
       } else if (response.data.visibility !== 'direct') {
         insertIfOnline('home');
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js
index 1b1a4f8d4..4b56c7fdd 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.js
+++ b/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -89,7 +89,7 @@ class ComposeForm extends ImmutablePureComponent {
       return;
     }
 
-    this.props.onSubmit(this.context.router.history);
+    this.props.onSubmit(this.context.router ? this.context.router.history : null);
   }
 
   onSuggestionsClearRequested = () => {
diff --git a/app/javascript/mastodon/features/compose/components/upload.js b/app/javascript/mastodon/features/compose/components/upload.js
index 66c93452c..a1e99dcbb 100644
--- a/app/javascript/mastodon/features/compose/components/upload.js
+++ b/app/javascript/mastodon/features/compose/components/upload.js
@@ -44,11 +44,13 @@ class Upload extends ImmutablePureComponent {
     this.props.onSubmit(this.context.router.history);
   }
 
-  handleUndoClick = () => {
+  handleUndoClick = e => {
+    e.stopPropagation();
     this.props.onUndo(this.props.media.get('id'));
   }
 
-  handleFocalPointClick = () => {
+  handleFocalPointClick = e => {
+    e.stopPropagation();
     this.props.onOpenFocalPoint(this.props.media.get('id'));
   }
 
@@ -68,6 +70,10 @@ class Upload extends ImmutablePureComponent {
     this.setState({ focused: true });
   }
 
+  handleClick = () => {
+    this.setState({ focused: true });
+  }
+
   handleInputBlur = () => {
     const { dirtyDescription } = this.state;
 
@@ -88,7 +94,7 @@ class Upload extends ImmutablePureComponent {
     const y = ((focusY / -2) + .5) * 100;
 
     return (
-      <div className='compose-form__upload' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
+      <div className='compose-form__upload' tabIndex='0' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} onClick={this.handleClick} role='button'>
         <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
           {({ scale }) => (
             <div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.js b/app/javascript/mastodon/features/direct_timeline/components/conversation.js
index 7277b7f0f..ffcd6d281 100644
--- a/app/javascript/mastodon/features/direct_timeline/components/conversation.js
+++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.js
@@ -56,6 +56,7 @@ export default class Conversation extends ImmutablePureComponent {
         otherAccounts={accounts}
         onMoveUp={this.handleHotkeyMoveUp}
         onMoveDown={this.handleHotkeyMoveDown}
+        onClick={this.handleClick}
       />
     );
   }
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
index 4684548e0..635c03c1d 100644
--- a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
+++ b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
@@ -9,14 +9,14 @@ import { debounce } from 'lodash';
 export default class ConversationsList extends ImmutablePureComponent {
 
   static propTypes = {
-    conversationIds: ImmutablePropTypes.list.isRequired,
+    conversations: ImmutablePropTypes.list.isRequired,
     hasMore: PropTypes.bool,
     isLoading: PropTypes.bool,
     onLoadMore: PropTypes.func,
     shouldUpdateScroll: PropTypes.func,
   };
 
-  getCurrentIndex = id => this.props.conversationIds.indexOf(id)
+  getCurrentIndex = id => this.props.conversations.findIndex(x => x.get('id') === id)
 
   handleMoveUp = id => {
     const elementIndex = this.getCurrentIndex(id) - 1;
@@ -41,22 +41,22 @@ export default class ConversationsList extends ImmutablePureComponent {
   }
 
   handleLoadOlder = debounce(() => {
-    const last = this.props.conversationIds.last();
+    const last = this.props.conversations.last();
 
-    if (last) {
-      this.props.onLoadMore(last);
+    if (last && last.get('last_status')) {
+      this.props.onLoadMore(last.get('last_status'));
     }
   }, 300, { leading: true })
 
   render () {
-    const { conversationIds, onLoadMore, ...other } = this.props;
+    const { conversations, onLoadMore, ...other } = this.props;
 
     return (
       <ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} scrollKey='direct' ref={this.setRef}>
-        {conversationIds.map(item => (
+        {conversations.map(item => (
           <ConversationContainer
-            key={item}
-            conversationId={item}
+            key={item.get('id')}
+            conversationId={item.get('id')}
             onMoveUp={this.handleMoveUp}
             onMoveDown={this.handleMoveDown}
           />
diff --git a/app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js b/app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js
index 81ea812ad..57e17d96f 100644
--- a/app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js
+++ b/app/javascript/mastodon/features/direct_timeline/containers/conversations_list_container.js
@@ -3,7 +3,7 @@ import ConversationsList from '../components/conversations_list';
 import { expandConversations } from '../../../actions/conversations';
 
 const mapStateToProps = state => ({
-  conversationIds: state.getIn(['conversations', 'items']).map(x => x.get('id')),
+  conversations: state.getIn(['conversations', 'items']),
   isLoading: state.getIn(['conversations', 'isLoading'], true),
   hasMore: state.getIn(['conversations', 'hasMore'], false),
 });
diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js
index a62974ec0..9cf783c84 100644
--- a/app/javascript/packs/public.js
+++ b/app/javascript/packs/public.js
@@ -5,7 +5,6 @@ import { start } from '../mastodon/common';
 start();
 
 function main() {
-  const { length } = require('stringz');
   const IntlMessageFormat = require('intl-messageformat').default;
   const { timeAgoString } = require('../mastodon/components/relative_timestamp');
   const { delegate } = require('rails-ujs');
diff --git a/app/javascript/styles/mastodon/rtl.scss b/app/javascript/styles/mastodon/rtl.scss
index a86e3e632..ccb53a090 100644
--- a/app/javascript/styles/mastodon/rtl.scss
+++ b/app/javascript/styles/mastodon/rtl.scss
@@ -130,17 +130,6 @@ body.rtl {
     float: left;
   }
 
-  .activity-stream .detailed-status.light .detailed-status__display-name > div {
-    float: right;
-    margin-right: 0;
-    margin-left: 10px;
-  }
-
-  .activity-stream .detailed-status.light .detailed-status__meta span > span {
-    margin-left: 0;
-    margin-right: 6px;
-  }
-
   .status__action-bar {
 
     &__counter {
@@ -174,6 +163,10 @@ body.rtl {
     margin-right: 0;
   }
 
+  .detailed-status__display-name .display-name {
+    text-align: right;
+  }
+
   .detailed-status__display-avatar {
     margin-right: 0;
     margin-left: 10px;
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index 7e6702a63..ea9017b82 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -81,11 +81,22 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
       @mentions << Mention.new(account: account, silent: true)
 
       # If there is at least one silent mention, then the status can be considered
-      # as a limited-audience status, and not strictly a direct message
+      # as a limited-audience status, and not strictly a direct message, but only
+      # if we considered a direct message in the first place
       next unless @params[:visibility] == :direct
 
       @params[:visibility] = :limited
     end
+
+    # If the payload was delivered to a specific inbox, the inbox owner must have
+    # access to it, unless they already have access to it anyway
+    return if @options[:delivered_to_account_id].nil? || @mentions.any? { |mention| mention.account_id == @options[:delivered_to_account_id] }
+
+    @mentions << Mention.new(account_id: @options[:delivered_to_account_id], silent: true)
+
+    return unless @params[:visibility] == :direct
+
+    @params[:visibility] = :limited
   end
 
   def attach_tags(status)
diff --git a/app/models/account_conversation.rb b/app/models/account_conversation.rb
index b7447d805..cc6b39279 100644
--- a/app/models/account_conversation.rb
+++ b/app/models/account_conversation.rb
@@ -58,6 +58,9 @@ class AccountConversation < ApplicationRecord
 
     def add_status(recipient, status)
       conversation = find_or_initialize_by(account: recipient, conversation_id: status.conversation_id, participant_account_ids: participants_from_status(recipient, status))
+
+      return conversation if conversation.status_ids.include?(status.id)
+
       conversation.status_ids << status.id
       conversation.unread = status.account_id != recipient.id
       conversation.save
diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb
index 4169c685b..4551aa7e0 100644
--- a/app/services/fetch_link_card_service.rb
+++ b/app/services/fetch_link_card_service.rb
@@ -17,7 +17,8 @@ class FetchLinkCardService < BaseService
 
     return if @url.nil? || @status.preview_cards.any?
 
-    @url = @url.to_s
+    @mentions = status.mentions
+    @url      = @url.to_s
 
     RedisLock.acquire(lock_options) do |lock|
       if lock.acquired?
@@ -81,9 +82,16 @@ class FetchLinkCardService < BaseService
     uri.host.blank? || TagManager.instance.local_url?(uri.to_s) || !%w(http https).include?(uri.scheme)
   end
 
+  def mention_link?(a)
+    return false if @mentions.nil?
+    @mentions.any? do |mention|
+      a['href'] == TagManager.instance.url_for(mention.target)
+    end
+  end
+
   def skip_link?(a)
     # Avoid links for hashtags and mentions (microformats)
-    a['rel']&.include?('tag') || a['class']&.include?('u-url')
+    a['rel']&.include?('tag') || a['class']&.include?('u-url') || mention_link?(a)
   end
 
   def attempt_oembed
diff --git a/app/services/verify_link_service.rb b/app/services/verify_link_service.rb
index 3453b54c5..9f56249c7 100644
--- a/app/services/verify_link_service.rb
+++ b/app/services/verify_link_service.rb
@@ -25,7 +25,7 @@ class VerifyLinkService < BaseService
   end
 
   def link_back_present?
-    return false if @body.empty?
+    return false if @body.blank?
 
     links = Nokogiri::HTML(@body).xpath('//a[contains(concat(" ", normalize-space(@rel), " "), " me ")]|//link[contains(concat(" ", normalize-space(@rel), " "), " me ")]')
 
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index 66382db50..0e52702dc 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -14,4 +14,4 @@
 
         = yield
 
-= render template: 'layouts/application', locals: { body_classes: 'admin' }
+= render template: 'layouts/application'
diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml
index 6b61fa9c9..516851b04 100644
--- a/app/views/settings/profiles/show.html.haml
+++ b/app/views/settings/profiles/show.html.haml
@@ -6,8 +6,8 @@
 
   .fields-row
     .fields-row__column.fields-group.fields-row__column-6
-      = f.input :display_name, wrapper: :with_label, hint: t('simple_form.hints.defaults.display_name', count: 30 - @account.display_name.size).html_safe
-      = f.input :note, wrapper: :with_label, hint: t('simple_form.hints.defaults.note', count: 500 - @account.note.size).html_safe
+      = f.input :display_name, wrapper: :with_label, input_html: { maxlength: 30 }, hint: false
+      = f.input :note, wrapper: :with_label, input_html: { maxlength: 500 }, hint: false
 
   .fields-row
     .fields-row__column.fields-row__column-6
@@ -36,8 +36,8 @@
 
         = f.simple_fields_for :fields do |fields_f|
           .row
-            = fields_f.input :name, placeholder: t('simple_form.labels.account.fields.name')
-            = fields_f.input :value, placeholder: t('simple_form.labels.account.fields.value')
+            = fields_f.input :name, placeholder: t('simple_form.labels.account.fields.name'), input_html: { maxlength: 255 }
+            = fields_f.input :value, placeholder: t('simple_form.labels.account.fields.value'), input_html: { maxlength: 255 }
 
     .fields-row__column.fields-group.fields-row__column-6
       %h6= t('verification.verification')
diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml
index cd0531ba2..e996d5052 100644
--- a/app/views/stream_entries/_simple_status.html.haml
+++ b/app/views/stream_entries/_simple_status.html.haml
@@ -15,6 +15,7 @@
         %span.display-name
           %bdi
             %strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: autoplay)
+          &nbsp;
           %span.display-name__account
             = acct(status.account)
             = fa_icon('lock') if status.account.locked?
diff --git a/app/workers/activitypub/processing_worker.rb b/app/workers/activitypub/processing_worker.rb
index 0e2e0eddd..a8a3ebf0f 100644
--- a/app/workers/activitypub/processing_worker.rb
+++ b/app/workers/activitypub/processing_worker.rb
@@ -5,7 +5,7 @@ class ActivityPub::ProcessingWorker
 
   sidekiq_options backtrace: true
 
-  def perform(account_id, body)
-    ActivityPub::ProcessCollectionService.new.call(body, Account.find(account_id), override_timestamps: 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)
   end
 end