diff options
author | Reverite <github@reverite.sh> | 2019-08-13 15:45:40 -0700 |
---|---|---|
committer | Reverite <github@reverite.sh> | 2019-08-13 15:45:40 -0700 |
commit | 2e221bd5b6472d1eecb654b3518af7886d3dadaf (patch) | |
tree | afb9e3a86ba3a93b6b5f04cdec6070351992fed4 /app | |
parent | 9528d3eda280ffac20a18c6f21bfc51f594e2c86 (diff) | |
parent | b859eb001717dfc62aebb8eba47b84c75aebe4ef (diff) |
Merge branch 'glitch' into production
Diffstat (limited to 'app')
29 files changed, 107 insertions, 48 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/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index 418ea5d7a..f05dbe0ea 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -49,6 +49,7 @@ class Settings::PreferencesController < Settings::BaseController :setting_expand_spoilers, :setting_reduce_motion, :setting_system_font_ui, + :setting_system_emoji_font, :setting_noindex, :setting_hide_network, :setting_hide_followers_count, 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/flavours/glitch/components/button.js b/app/javascript/flavours/glitch/components/button.js index 16868010c..cd6528f58 100644 --- a/app/javascript/flavours/glitch/components/button.js +++ b/app/javascript/flavours/glitch/components/button.js @@ -12,9 +12,9 @@ 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, - title: PropTypes.string, }; static defaultProps = { diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js index e9437c0a9..b0072533c 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.js +++ b/app/javascript/flavours/glitch/features/account/components/header.js @@ -15,6 +15,7 @@ import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_cont 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' }, @@ -141,7 +142,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 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/javascript/flavours/glitch/features/emoji_picker/index.js b/app/javascript/flavours/glitch/features/emoji_picker/index.js index 9821502d3..b255cf858 100644 --- a/app/javascript/flavours/glitch/features/emoji_picker/index.js +++ b/app/javascript/flavours/glitch/features/emoji_picker/index.js @@ -12,6 +12,7 @@ import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import detectPassiveEvents from 'detect-passive-events'; import { buildCustomEmojis, categoriesFromEmojis } from 'flavours/glitch/util/emoji'; +import { useSystemEmojiFont } from 'flavours/glitch/util/initial_state'; const messages = defineMessages({ emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, @@ -159,12 +160,12 @@ class ModifierPickerMenu extends React.PureComponent { return ( <div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}> - <button onClick={this.handleClick} data-index={1}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button> - <button onClick={this.handleClick} data-index={2}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button> - <button onClick={this.handleClick} data-index={3}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button> - <button onClick={this.handleClick} data-index={4}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} /></button> - <button onClick={this.handleClick} data-index={5}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} /></button> - <button onClick={this.handleClick} data-index={6}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} /></button> + <button onClick={this.handleClick} data-index={1}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button> + <button onClick={this.handleClick} data-index={2}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button> + <button onClick={this.handleClick} data-index={3}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button> + <button onClick={this.handleClick} data-index={4}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button> + <button onClick={this.handleClick} data-index={5}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button> + <button onClick={this.handleClick} data-index={6}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /></button> </div> ); } @@ -199,7 +200,7 @@ class ModifierPicker extends React.PureComponent { return ( <div className='emoji-picker-dropdown__modifiers'> - <Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} /> + <Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} native={useSystemEmojiFont} /> <ModifierPickerMenu active={active} onSelect={this.handleSelect} onClose={this.props.onClose} /> </div> ); @@ -344,6 +345,7 @@ class EmojiPickerMenu extends React.PureComponent { backgroundImageFn={backgroundImageFn} autoFocus emojiTooltip + native={useSystemEmojiFont} /> <ModifierPicker diff --git a/app/javascript/flavours/glitch/util/emoji/index.js b/app/javascript/flavours/glitch/util/emoji/index.js index 2f40f9b08..b2d13cc95 100644 --- a/app/javascript/flavours/glitch/util/emoji/index.js +++ b/app/javascript/flavours/glitch/util/emoji/index.js @@ -1,4 +1,4 @@ -import { autoPlayGif } from 'flavours/glitch/util/initial_state'; +import { autoPlayGif, useSystemEmojiFont } from 'flavours/glitch/util/initial_state'; import unicodeMapping from './emoji_unicode_mapping_light'; import Trie from 'substring-trie'; @@ -12,7 +12,7 @@ const emojify = (str, customEmojis = {}) => { let rtn = '', tagChars = tagCharsWithEmojis, invisible = 0; for (;;) { let match, i = 0, tag; - while (i < str.length && (tag = tagChars.indexOf(str[i])) === -1 && (invisible || !(match = trie.search(str.slice(i))))) { + while (i < str.length && (tag = tagChars.indexOf(str[i])) === -1 && (invisible || useSystemEmojiFont || !(match = trie.search(str.slice(i))))) { i += str.codePointAt(i) < 65536 ? 1 : 2; } let rend, replacement = ''; @@ -57,7 +57,7 @@ const emojify = (str, customEmojis = {}) => { } } i = rend; - } else { // matched to unicode emoji + } else if (!useSystemEmojiFont) { // matched to unicode emoji const { filename, shortCode } = unicodeMapping[match]; const title = shortCode ? `:${shortCode}:` : ''; replacement = `<img draggable="false" class="emojione" alt="${match}" title="${title}" src="${assetHost}/emoji/${filename}.svg" />`; diff --git a/app/javascript/flavours/glitch/util/initial_state.js b/app/javascript/flavours/glitch/util/initial_state.js index caaa79bb3..4b6227cac 100644 --- a/app/javascript/flavours/glitch/util/initial_state.js +++ b/app/javascript/flavours/glitch/util/initial_state.js @@ -31,5 +31,6 @@ export const defaultContentType = getMeta('default_content_type'); export const forceSingleColumn = getMeta('advanced_layout') === false; export const useBlurhash = getMeta('use_blurhash'); export const usePendingItems = getMeta('use_pending_items'); +export const useSystemEmojiFont = getMeta('system_emoji_font'); export default initialState; 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/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb index a52172707..c822d54de 100644 --- a/app/lib/user_settings_decorator.rb +++ b/app/lib/user_settings_decorator.rb @@ -29,6 +29,7 @@ class UserSettingsDecorator user.settings['expand_spoilers'] = expand_spoilers_preference if change?('setting_expand_spoilers') user.settings['reduce_motion'] = reduce_motion_preference if change?('setting_reduce_motion') user.settings['system_font_ui'] = system_font_ui_preference if change?('setting_system_font_ui') + user.settings['system_emoji_font'] = system_emoji_font_preference if change?('setting_system_emoji_font') user.settings['noindex'] = noindex_preference if change?('setting_noindex') user.settings['hide_followers_count']= hide_followers_count_preference if change?('setting_hide_followers_count') user.settings['flavour'] = flavour_preference if change?('setting_flavour') @@ -79,6 +80,10 @@ class UserSettingsDecorator boolean_cast_setting 'setting_system_font_ui' end + def system_emoji_font_preference + boolean_cast_setting 'setting_system_emoji_font' + end + def auto_play_gif_preference boolean_cast_setting 'setting_auto_play_gif' end 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/models/user.rb b/app/models/user.rb index 45a4b8989..341227d3e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -108,7 +108,7 @@ class User < ApplicationRecord :reduce_motion, :system_font_ui, :noindex, :flavour, :skin, :display_media, :hide_network, :hide_followers_count, :expand_spoilers, :default_language, :aggregate_reblogs, :show_application, :advanced_layout, :use_blurhash, :use_pending_items, :trends, - :default_content_type, + :default_content_type, :system_emoji_font, to: :settings, prefix: :setting, allow_nil: false attr_reader :invite_code diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index c8da6e725..c8f6bec7a 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -53,6 +53,7 @@ class InitialStateSerializer < ActiveModel::Serializer store[:is_staff] = object.current_account.user.staff? store[:trends] = Setting.trends && object.current_account.user.setting_trends store[:default_content_type] = object.current_account.user.setting_default_content_type + store[:system_emoji_font] = object.current_account.user.setting_system_emoji_font end store 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 diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index 0bda49f44..900a7c6fb 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -21,6 +21,7 @@ = f.input :setting_auto_play_gif, as: :boolean, wrapper: :with_label, recommended: true = f.input :setting_reduce_motion, as: :boolean, wrapper: :with_label = f.input :setting_system_font_ui, as: :boolean, wrapper: :with_label + = f.input :setting_system_emoji_font, as: :boolean, wrapper: :with_label %h4= t 'appearance.discovery' |