diff --git a/app/chewy/accounts_index.rb b/app/chewy/accounts_index.rb
index ad3816e4d6..9503fb85a1 100644
--- a/app/chewy/accounts_index.rb
+++ b/app/chewy/accounts_index.rb
@@ -157,6 +157,6 @@ class AccountsIndex < Chewy::Index
field(:domain, type: 'keyword', value: ->(account) { account.domain || '' })
field(:display_name, type: 'text', analyzer: 'verbatim') { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
field(:username, type: 'text', analyzer: 'verbatim', value: ->(account) { [account.username, account.domain].compact.join('@') }) { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
- field(:text, type: 'text', analyzer: 'sudachi_analyzer', value: ->(account) { account.searchable_text }) { field :stemmed, type: 'text', analyzer: 'natural' }
+ field(:text, type: 'text', analyzer: 'sudachi_analyzer', value: ->(account) { account.searchable_text })
end
end
diff --git a/app/chewy/public_statuses_index.rb b/app/chewy/public_statuses_index.rb
index d152b52039..4bfe3f08ad 100644
--- a/app/chewy/public_statuses_index.rb
+++ b/app/chewy/public_statuses_index.rb
@@ -149,7 +149,7 @@ class PublicStatusesIndex < Chewy::Index
root date_detection: false do
field(:id, type: 'long')
field(:account_id, type: 'long')
- field(:text, type: 'text', analyzer: 'sudachi_analyzer', value: ->(status) { status.searchable_text }) { field(:stemmed, type: 'text', analyzer: 'sudachi_analyzer') }
+ field(:text, type: 'text', analyzer: 'sudachi_analyzer', value: ->(status) { status.searchable_text })
field(:tags, type: 'text', analyzer: 'hashtag', value: ->(status) { status.tags.map(&:display_name) })
field(:language, type: 'keyword')
field(:domain, type: 'keyword', value: ->(status) { status.account.domain || '' })
diff --git a/app/chewy/statuses_index.rb b/app/chewy/statuses_index.rb
index 3b83e2f31c..3b0a38a466 100644
--- a/app/chewy/statuses_index.rb
+++ b/app/chewy/statuses_index.rb
@@ -160,17 +160,26 @@ class StatusesIndex < Chewy::Index
if status.searchability == 'direct'
status.searchable_by.empty?
else
- status.searchability == 'limited' ? status.account.domain.present? : false
+ status.searchability == 'limited' ? !status.local? : false
end
}
root date_detection: false do
field(:id, type: 'long')
field(:account_id, type: 'long')
- field(:text, type: 'text', analyzer: 'sudachi_analyzer', value: ->(status) { status.searchable_text }) { field(:stemmed, type: 'text', analyzer: 'sudachi_analyzer') }
+ field(:text, type: 'text', analyzer: 'sudachi_analyzer', value: ->(status) { status.searchable_text })
field(:tags, type: 'text', analyzer: 'hashtag', value: ->(status) { status.tags.map(&:display_name) })
field(:searchable_by, type: 'long', value: ->(status) { status.searchable_by })
+ field(:mentioned_by, type: 'long', value: ->(status) { status.mentioned_by })
+ field(:favourited_by, type: 'long', value: ->(status) { status.favourited_by })
+ field(:reblogged_by, type: 'long', value: ->(status) { status.reblogged_by })
+ field(:bookmarked_by, type: 'long', value: ->(status) { status.bookmarked_by })
+ field(:bookmark_categoried_by, type: 'long', value: ->(status) { status.bookmark_categoried_by })
+ field(:emoji_reacted_by, type: 'long', value: ->(status) { status.emoji_reacted_by })
+ field(:referenced_by, type: 'long', value: ->(status) { status.referenced_by })
+ field(:voted_by, type: 'long', value: ->(status) { status.voted_by })
field(:searchability, type: 'keyword', value: ->(status) { status.compute_searchability })
+ field(:visibility, type: 'keyword', value: ->(status) { status.searchable_visibility })
field(:language, type: 'keyword')
field(:domain, type: 'keyword', value: ->(status) { status.account.domain || '' })
field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
diff --git a/app/javascript/mastodon/features/compose/components/search.jsx b/app/javascript/mastodon/features/compose/components/search.jsx
index 84d3306410..7584dbc819 100644
--- a/app/javascript/mastodon/features/compose/components/search.jsx
+++ b/app/javascript/mastodon/features/compose/components/search.jsx
@@ -59,13 +59,15 @@ class Search extends PureComponent {
defaultOptions = [
{ label: <>has: >, action: e => { e.preventDefault(); this._insertText('has:') } },
{ label: <>is: >, action: e => { e.preventDefault(); this._insertText('is:') } },
+ { label: <>my: >, action: e => { e.preventDefault(); this._insertText('my:') } },
{ label: <>language: >, action: e => { e.preventDefault(); this._insertText('language:') } },
{ label: <>from: >, action: e => { e.preventDefault(); this._insertText('from:') } },
{ label: <>domain: >, action: e => { e.preventDefault(); this._insertText('domain:') } },
{ label: <>before: >, action: e => { e.preventDefault(); this._insertText('before:') } },
{ label: <>during: >, action: e => { e.preventDefault(); this._insertText('during:') } },
{ label: <>after: >, action: e => { e.preventDefault(); this._insertText('after:') } },
- { label: <>in: >, action: e => { e.preventDefault(); this._insertText('in:') } }
+ { label: <>in: >, action: e => { e.preventDefault(); this._insertText('in:') } },
+ { label: <>order: >, action: e => { e.preventDefault(); this._insertText('order:') } },
];
setRef = c => {
diff --git a/app/lib/search_query_transformer.rb b/app/lib/search_query_transformer.rb
index 62dd378866..32d30a3b0e 100644
--- a/app/lib/search_query_transformer.rb
+++ b/app/lib/search_query_transformer.rb
@@ -4,6 +4,7 @@ class SearchQueryTransformer < Parslet::Transform
SUPPORTED_PREFIXES = %w(
has
is
+ my
language
from
before
@@ -11,6 +12,7 @@ class SearchQueryTransformer < Parslet::Transform
during
in
domain
+ order
).freeze
class Query
@@ -34,6 +36,18 @@ class SearchQueryTransformer < Parslet::Transform
search
end
+ def order_by
+ return @order_by if @order_by
+
+ @order_by = 'desc'
+ order_clauses.each { |clause| @order_by = clause.term }
+ @order_by
+ end
+
+ def valid
+ must_clauses.any? || must_not_clauses.any? || filter_clauses.any?
+ end
+
private
def clauses_by_operator
@@ -56,6 +70,10 @@ class SearchQueryTransformer < Parslet::Transform
clauses_by_operator.fetch(:filter, [])
end
+ def order_clauses
+ clauses_by_operator.fetch(:order, [])
+ end
+
def indexes
case @flags['in']
when 'library'
@@ -214,6 +232,7 @@ class SearchQueryTransformer < Parslet::Transform
if @term.start_with?('#')
{ match: { tags: { query: @term, operator: 'and' } } }
else
+ # Memo for checking when manually merge
# { multi_match: { type: 'most_fields', query: @term, fields: ['text', 'text.stemmed'], operator: 'and' } }
{ match_phrase: { text: { query: @term } } }
end
@@ -236,7 +255,7 @@ class SearchQueryTransformer < Parslet::Transform
class PrefixClause
attr_reader :operator, :prefix, :term
- def initialize(prefix, operator, term, options = {})
+ def initialize(prefix, operator, term, options = {}) # rubocop:disable Metrics/CyclomaticComplexity
@prefix = prefix
@negated = operator == '-'
@options = options
@@ -274,6 +293,39 @@ class SearchQueryTransformer < Parslet::Transform
when 'in'
@operator = :flag
@term = term
+ when 'my'
+ @type = :term
+ @term = @options[:current_account]&.id
+ case term
+ when 'favourited', 'favorited', 'fav'
+ @filter = :favourited_by
+ when 'boosted', 'bt'
+ @filter = :reblogged_by
+ when 'replied', 'mentioned', 're'
+ @filter = :mentioned_by
+ when 'referenced', 'ref'
+ @filter = :referenced_by
+ when 'emoji_reacted', 'stamped', 'stamp'
+ @filter = :emoji_reacted_by
+ when 'bookmarked', 'bm'
+ @filter = :bookmarked_by
+ when 'categoried', 'bmc'
+ @filter = :bookmark_categoried_by
+ when 'voted', 'vote'
+ @filter = :voted_by
+ when 'interacted', 'act'
+ @filter = :searchable_by
+ else
+ raise "Unknown prefix: my:#{term}"
+ end
+ when 'order'
+ @operator = :order
+ @term = case term
+ when 'asc'
+ term
+ else
+ 'desc'
+ end
else
raise "Unknown prefix: #{prefix}"
end
@@ -302,7 +354,7 @@ class SearchQueryTransformer < Parslet::Transform
end
def domain_from_term(term)
- return '' if %w(local me).include?(term)
+ return '' if ['local', 'me', Rails.configuration.x.local_domain].include?(term)
term
end
diff --git a/app/models/bookmark_category_status.rb b/app/models/bookmark_category_status.rb
index d932455ff5..8933d0e91f 100644
--- a/app/models/bookmark_category_status.rb
+++ b/app/models/bookmark_category_status.rb
@@ -13,6 +13,10 @@
#
class BookmarkCategoryStatus < ApplicationRecord
+ include Paginable
+
+ update_index('statuses', :status) if Chewy.enabled?
+
belongs_to :bookmark_category
belongs_to :status
belongs_to :bookmark
diff --git a/app/models/concerns/status_search_concern.rb b/app/models/concerns/status_search_concern.rb
index 69f1263f81..d1632b7b84 100644
--- a/app/models/concerns/status_search_concern.rb
+++ b/app/models/concerns/status_search_concern.rb
@@ -13,18 +13,48 @@ module StatusSearchConcern
ids << account_id if local?
- ids += local_mentioned.pluck(:id)
- ids += local_favorited.pluck(:id)
- ids += local_reblogged.pluck(:id)
- ids += local_bookmarked.pluck(:id)
- ids += local_emoji_reacted.pluck(:id)
- ids += local_referenced.pluck(:id)
- ids += preloadable_poll.local_voters.pluck(:id) if preloadable_poll.present?
+ ids += mentioned_by
+ ids += favourited_by
+ ids += reblogged_by
+ ids += bookmarked_by
+ ids += emoji_reacted_by
+ ids += referenced_by
+ ids += voted_by if preloadable_poll.present?
ids.uniq
end
end
+ def mentioned_by
+ @mentioned_by ||= local_mentioned.pluck(:id)
+ end
+
+ def favourited_by
+ @favourited_by ||= local_favorited.pluck(:id)
+ end
+
+ def reblogged_by
+ @reblogged_by ||= local_reblogged.pluck(:id)
+ end
+
+ def bookmarked_by
+ @bookmarked_by ||= local_bookmarked.pluck(:id)
+ end
+
+ def emoji_reacted_by
+ @emoji_reacted_by ||= local_emoji_reacted.pluck(:id)
+ end
+
+ def referenced_by
+ @referenced_by ||= local_referenced.pluck(:id)
+ end
+
+ def voted_by
+ return [] if preloadable_poll.blank?
+
+ @voted_by ||= preloadable_poll.local_voters.pluck(:id)
+ end
+
def searchable_text
[
spoiler_text,
diff --git a/app/models/status.rb b/app/models/status.rb
index d4a28220d1..f9587b3c71 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -86,6 +86,7 @@ class Status < ApplicationRecord
has_many :referenced_by_statuses, through: :referenced_by_status_objects, class_name: 'Status', source: :status
has_many :capability_tokens, class_name: 'StatusCapabilityToken', inverse_of: :status, dependent: :destroy
has_many :bookmark_category_relationships, class_name: 'BookmarkCategoryStatus', inverse_of: :status, dependent: :destroy
+ has_many :bookmark_categories, class_name: 'BookmarkCategory', through: :bookmark_category_relationships, source: :bookmark_category
has_many :joined_bookmark_categories, class_name: 'BookmarkCategory', through: :bookmark_category_relationships, source: :bookmark_category
# Those associations are used for the private search index
@@ -93,6 +94,7 @@ class Status < ApplicationRecord
has_many :local_favorited, -> { merge(Account.local) }, through: :favourites, source: :account
has_many :local_reblogged, -> { merge(Account.local) }, through: :reblogs, source: :account
has_many :local_bookmarked, -> { merge(Account.local) }, through: :bookmarks, source: :account
+ has_many :local_bookmark_categoried, -> { merge(Account.local) }, through: :bookmark_categories, source: :account
has_many :local_emoji_reacted, -> { merge(Account.local) }, through: :emoji_reactions, source: :account
has_many :local_referenced, -> { merge(Account.local) }, through: :referenced_by_statuses, source: :account
@@ -435,6 +437,12 @@ class Status < ApplicationRecord
compute_searchability
end
+ def searchable_visibility
+ return limited_scope if limited_visibility? && !none_limited?
+
+ visibility
+ end
+
class << self
def selectable_visibilities
visibilities.keys - %w(direct limited)
diff --git a/app/services/statuses_search_service.rb b/app/services/statuses_search_service.rb
index de5eda15c6..99921d1957 100644
--- a/app/services/statuses_search_service.rb
+++ b/app/services/statuses_search_service.rb
@@ -16,8 +16,11 @@ class StatusesSearchService < BaseService
private
def status_search_results
- request = parsed_query.request
- results = request.collapse(field: :id).order(id: { order: :desc }).limit(@limit).offset(@offset).objects.compact
+ query = parsed_query
+ request = query.request
+ return [] unless query.valid
+
+ results = request.collapse(field: :id).order(id: { order: query.order_by }).limit(@limit).offset(@offset).objects.compact
account_ids = results.map(&:account_id)
account_domains = results.map(&:account_domain)
preloaded_relations = @account.relations_map(account_ids, account_domains)