Merge branch 'master' into glitch-soc/merge-upstream

This commit is contained in:
Thibaut Girka
2019-08-12 16:24:22 +02:00
24 changed files with 214 additions and 120 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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}>

View File

@ -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'])) {

View File

@ -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

View File

@ -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

View File

@ -4,7 +4,7 @@ module DomainNormalizable
extend ActiveSupport::Concern
included do
before_validation :normalize_domain
before_save :normalize_domain
end
private

View File

@ -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) }

View File

@ -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}%")) }

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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