diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/controllers/accounts_controller.rb | 2 | ||||
-rw-r--r-- | app/controllers/admin/tags_controller.rb | 2 | ||||
-rw-r--r-- | app/controllers/api/base_controller.rb | 2 | ||||
-rw-r--r-- | app/controllers/api/v1/accounts/statuses_controller.rb | 3 | ||||
-rw-r--r-- | app/controllers/statuses_controller.rb | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/components/button.js | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/components/status_content.js | 19 | ||||
-rw-r--r-- | app/javascript/mastodon/features/account/components/header.js | 3 | ||||
-rw-r--r-- | app/models/account.rb | 2 | ||||
-rw-r--r-- | app/models/account_domain_block.rb | 2 | ||||
-rw-r--r-- | app/models/concerns/domain_normalizable.rb | 2 | ||||
-rw-r--r-- | app/models/custom_emoji.rb | 4 | ||||
-rw-r--r-- | app/models/domain_allow.rb | 2 | ||||
-rw-r--r-- | app/models/domain_block.rb | 2 | ||||
-rw-r--r-- | app/models/email_domain_block.rb | 2 | ||||
-rw-r--r-- | app/models/trending_tags.rb | 3 | ||||
-rw-r--r-- | app/services/account_search_service.rb | 44 | ||||
-rw-r--r-- | app/validators/domain_validator.rb | 17 | ||||
-rw-r--r-- | app/views/admin/tags/show.html.haml | 2 |
19 files changed, 82 insertions, 35 deletions
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 1aed1af8d..1a876b831 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -9,6 +9,8 @@ class AccountsController < ApplicationController before_action :set_cache_headers before_action :set_body_classes + skip_around_action :set_locale, if: -> { request.format == :json } + def show respond_to do |format| format.html do diff --git a/app/controllers/admin/tags_controller.rb b/app/controllers/admin/tags_controller.rb index d62361eaa..39aca2a4b 100644 --- a/app/controllers/admin/tags_controller.rb +++ b/app/controllers/admin/tags_controller.rb @@ -71,7 +71,7 @@ module Admin now = Time.now.utc.beginning_of_day.to_date (Date.commercial(now.cwyear, now.cweek)..now).map do |date| - date.to_time.utc.beginning_of_day.to_i + date.to_time(:utc).beginning_of_day.to_i end end end diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 109e38ffa..de8fff30e 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -14,6 +14,8 @@ class Api::BaseController < ApplicationController protect_from_forgery with: :null_session + skip_around_action :set_locale + rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e| render json: { error: e.to_s }, status: 422 end diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index 8cd8f8e79..13cb4caf1 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -3,7 +3,8 @@ class Api::V1::Accounts::StatusesController < Api::BaseController before_action -> { authorize_if_got_token! :read, :'read:statuses' } before_action :set_account - after_action :insert_pagination_headers + + after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) } respond_to :json diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 3d7e61e77..83c412d5c 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -18,6 +18,8 @@ class StatusesController < ApplicationController before_action :set_body_classes before_action :set_autoplay, only: :embed + skip_around_action :set_locale, if: -> { request.format == :json } + content_security_policy only: :embed do |p| p.frame_ancestors(false) end diff --git a/app/javascript/mastodon/components/button.js b/app/javascript/mastodon/components/button.js index 51e2e6a7a..eb8dd7dc8 100644 --- a/app/javascript/mastodon/components/button.js +++ b/app/javascript/mastodon/components/button.js @@ -12,6 +12,7 @@ export default class Button extends React.PureComponent { secondary: PropTypes.bool, size: PropTypes.number, className: PropTypes.string, + title: PropTypes.string, style: PropTypes.object, children: PropTypes.node, }; @@ -54,6 +55,7 @@ export default class Button extends React.PureComponent { onClick={this.handleClick} ref={this.setRef} style={style} + title={this.props.title} > {this.props.text || this.props.children} </button> diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index 76117f1d9..6aa0bfcc2 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -166,11 +166,6 @@ export default class StatusContent extends React.PureComponent { } } - handleCollapsedClick = (e) => { - e.preventDefault(); - this.setState({ collapsed: !this.state.collapsed }); - } - setRef = (c) => { this.node = c; } @@ -234,15 +229,19 @@ export default class StatusContent extends React.PureComponent { </div> ); } else if (this.props.onClick) { - return ( + const output = [ <div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}> <div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} /> - {!!this.state.collapsed && readMoreButton} - {!!status.get('poll') && <PollContainer pollId={status.get('poll')} />} - </div> - ); + </div>, + ]; + + if (this.state.collapsed) { + output.push(readMoreButton); + } + + return output; } else { return ( <div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle}> diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index cab67c607..ac97bad71 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -15,6 +15,7 @@ import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; const messages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, follow: { id: 'account.follow', defaultMessage: 'Follow' }, + cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Cancel follow request' }, requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' }, unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, @@ -148,7 +149,7 @@ class Header extends ImmutablePureComponent { if (!account.get('relationship')) { // Wait until the relationship is loaded actionBtn = ''; } else if (account.getIn(['relationship', 'requested'])) { - actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />; + actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />; } else if (!account.getIn(['relationship', 'blocking'])) { actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />; } else if (account.getIn(['relationship', 'blocking'])) { diff --git a/app/models/account.rb b/app/models/account.rb index 92e60f747..38379d20e 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -508,7 +508,7 @@ class Account < ApplicationRecord end def emojifiable_text - [note, display_name, fields.map(&:value)].join(' ') + [note, display_name, fields.map(&:name), fields.map(&:value)].join(' ') end def clean_feed_manager diff --git a/app/models/account_domain_block.rb b/app/models/account_domain_block.rb index 7c0d60379..3aaffde9a 100644 --- a/app/models/account_domain_block.rb +++ b/app/models/account_domain_block.rb @@ -15,7 +15,7 @@ class AccountDomainBlock < ApplicationRecord include DomainNormalizable belongs_to :account - validates :domain, presence: true, uniqueness: { scope: :account_id } + validates :domain, presence: true, uniqueness: { scope: :account_id }, domain: true after_commit :remove_blocking_cache after_commit :remove_relationship_cache diff --git a/app/models/concerns/domain_normalizable.rb b/app/models/concerns/domain_normalizable.rb index fb84058fc..c00b3142f 100644 --- a/app/models/concerns/domain_normalizable.rb +++ b/app/models/concerns/domain_normalizable.rb @@ -4,7 +4,7 @@ module DomainNormalizable extend ActiveSupport::Concern included do - before_validation :normalize_domain + before_save :normalize_domain end private diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index 514cf4845..643a7e46a 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -28,6 +28,8 @@ class CustomEmoji < ApplicationRecord :(#{SHORTCODE_RE_FRAGMENT}): (?=[^[:alnum:]:]|$)/x + IMAGE_MIME_TYPES = %w(image/png image/gif image/webp).freeze + belongs_to :category, class_name: 'CustomEmojiCategory', optional: true has_one :local_counterpart, -> { where(domain: nil) }, class_name: 'CustomEmoji', primary_key: :shortcode, foreign_key: :shortcode @@ -35,7 +37,7 @@ class CustomEmoji < ApplicationRecord before_validation :downcase_domain - validates_attachment :image, content_type: { content_type: 'image/png' }, presence: true, size: { less_than: LIMIT } + validates_attachment :image, content_type: { content_type: IMAGE_MIME_TYPES }, presence: true, size: { less_than: LIMIT } validates :shortcode, uniqueness: { scope: :domain }, format: { with: /\A#{SHORTCODE_RE_FRAGMENT}\z/ }, length: { minimum: 2 } scope :local, -> { where(domain: nil) } diff --git a/app/models/domain_allow.rb b/app/models/domain_allow.rb index 85018b636..5fe0e3a29 100644 --- a/app/models/domain_allow.rb +++ b/app/models/domain_allow.rb @@ -13,7 +13,7 @@ class DomainAllow < ApplicationRecord include DomainNormalizable - validates :domain, presence: true, uniqueness: true + validates :domain, presence: true, uniqueness: true, domain: true scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb index 3f5b9f23e..37b8d98c6 100644 --- a/app/models/domain_block.rb +++ b/app/models/domain_block.rb @@ -19,7 +19,7 @@ class DomainBlock < ApplicationRecord enum severity: [:silence, :suspend, :noop] - validates :domain, presence: true, uniqueness: true + validates :domain, presence: true, uniqueness: true, domain: true has_many :accounts, foreign_key: :domain, primary_key: :domain delegate :count, to: :accounts, prefix: true diff --git a/app/models/email_domain_block.rb b/app/models/email_domain_block.rb index 0fcd36477..bc70dea25 100644 --- a/app/models/email_domain_block.rb +++ b/app/models/email_domain_block.rb @@ -12,7 +12,7 @@ class EmailDomainBlock < ApplicationRecord include DomainNormalizable - validates :domain, presence: true, uniqueness: true + validates :domain, presence: true, uniqueness: true, domain: true def self.block?(email) _, domain = email.split('@', 2) diff --git a/app/models/trending_tags.rb b/app/models/trending_tags.rb index 594ae9520..3d60a7fea 100644 --- a/app/models/trending_tags.rb +++ b/app/models/trending_tags.rb @@ -6,6 +6,7 @@ class TrendingTags EXPIRE_TRENDS_AFTER = 1.day.seconds THRESHOLD = 5 LIMIT = 10 + REVIEW_THRESHOLD = 3 class << self include Redisable @@ -60,7 +61,7 @@ class TrendingTags old_rank = redis.zrevrank(key, tag.id) redis.zadd(key, score, tag.id) - request_review!(tag) if (old_rank.nil? || old_rank > LIMIT) && redis.zrevrank(key, tag.id) <= LIMIT && !tag.trendable? && tag.requires_review? && !tag.requested_review? + request_review!(tag) if (old_rank.nil? || old_rank > REVIEW_THRESHOLD) && redis.zrevrank(key, tag.id) <= REVIEW_THRESHOLD && !tag.trendable? && tag.requires_review? && !tag.requested_review? end redis.expire(key, EXPIRE_TRENDS_AFTER) diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb index 7bdffbbd2..e1874d045 100644 --- a/app/services/account_search_service.rb +++ b/app/services/account_search_service.rb @@ -21,18 +21,22 @@ class AccountSearchService < BaseService if resolving_non_matching_remote_account? [ResolveAccountService.new.call("#{query_username}@#{query_domain}")].compact else - search_results_and_exact_match.compact.uniq.slice(0, limit) + search_results_and_exact_match.compact.uniq end end def resolving_non_matching_remote_account? - options[:resolve] && !exact_match && !domain_is_local? + offset.zero? && options[:resolve] && !exact_match? && !domain_is_local? end def search_results_and_exact_match - exact = [exact_match] - return exact if !exact[0].nil? && limit == 1 - exact + search_results.to_a + return search_results.to_a unless offset.zero? + + results = [exact_match] + + return results if exact_match? && limit == 1 + + results + search_results.to_a end def query_blank_or_hashtag? @@ -40,15 +44,15 @@ class AccountSearchService < BaseService end def split_query_string - @_split_query_string ||= query.gsub(/\A@/, '').split('@') + @split_query_string ||= query.gsub(/\A@/, '').split('@') end def query_username - @_query_username ||= split_query_string.first || '' + @query_username ||= split_query_string.first || '' end def query_domain - @_query_domain ||= query_without_split? ? nil : split_query_string.last + @query_domain ||= query_without_split? ? nil : split_query_string.last end def query_without_split? @@ -56,15 +60,21 @@ class AccountSearchService < BaseService end def domain_is_local? - @_domain_is_local ||= TagManager.instance.local_domain?(query_domain) + @domain_is_local ||= TagManager.instance.local_domain?(query_domain) end def search_from options[:following] && account ? account.following : Account end + def exact_match? + exact_match.present? + end + def exact_match - @_exact_match ||= begin + return @exact_match if defined?(@exact_match) + + @exact_match = begin if domain_is_local? search_from.without_suspended.find_local(query_username) else @@ -74,7 +84,7 @@ class AccountSearchService < BaseService end def search_results - @_search_results ||= begin + @search_results ||= begin if account advanced_search_results else @@ -84,11 +94,19 @@ class AccountSearchService < BaseService end def advanced_search_results - Account.advanced_search_for(terms_for_query, account, limit, options[:following], offset) + Account.advanced_search_for(terms_for_query, account, limit_for_non_exact_results, options[:following], offset) end def simple_search_results - Account.search_for(terms_for_query, limit, offset) + Account.search_for(terms_for_query, limit_for_non_exact_results, offset) + end + + def limit_for_non_exact_results + if offset.zero? && exact_match? + limit - 1 + else + limit + end end def terms_for_query diff --git a/app/validators/domain_validator.rb b/app/validators/domain_validator.rb new file mode 100644 index 000000000..ae07f1798 --- /dev/null +++ b/app/validators/domain_validator.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class DomainValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + return if value.blank? + + record.errors.add(attribute, I18n.t('domain_validator.invalid_domain')) unless compliant?(value) + end + + private + + def compliant?(value) + Addressable::URI.new.tap { |uri| uri.host = value } + rescue Addressable::URI::InvalidURIError + false + end +end diff --git a/app/views/admin/tags/show.html.haml b/app/views/admin/tags/show.html.haml index 6a1e03065..c3779d48c 100644 --- a/app/views/admin/tags/show.html.haml +++ b/app/views/admin/tags/show.html.haml @@ -41,5 +41,5 @@ - @usage_by_domain.each do |(domain, count)| %tr %th= domain || site_hostname - %td= "#{number_with_delimiter((count.to_f / @tag.history[0][:uses].to_f) * 100)}%" + %td= number_to_percentage((count / @tag.history[0][:uses].to_f) * 100) %td= number_with_delimiter count |