commit
08faeedff7
@ -1,11 +1,16 @@ |
||||
import Avatar from '../../../components/avatar'; |
||||
import DisplayName from '../../../components/display_name'; |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
|
||||
const AutosuggestAccount = ({ account }) => ( |
||||
<div style={{ overflow: 'hidden' }}> |
||||
<div style={{ overflow: 'hidden' }} className='autosuggest-account'> |
||||
<div style={{ float: 'left', marginRight: '5px' }}><Avatar src={account.get('avatar')} size={18} /></div> |
||||
<DisplayName account={account} /> |
||||
</div> |
||||
); |
||||
|
||||
AutosuggestAccount.propTypes = { |
||||
account: ImmutablePropTypes.map.isRequired |
||||
}; |
||||
|
||||
export default AutosuggestAccount; |
||||
|
@ -0,0 +1,15 @@ |
||||
import { FormattedMessage } from 'react-intl'; |
||||
import DisplayName from '../../../components/display_name'; |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
|
||||
const AutosuggestStatus = ({ status }) => ( |
||||
<div style={{ overflow: 'hidden' }} className='autosuggest-status'> |
||||
<FormattedMessage id='search.status_by' defaultMessage='Status by {name}' values={{ name: <strong>@{status.getIn(['account', 'acct'])}</strong> }} /> |
||||
</div> |
||||
); |
||||
|
||||
AutosuggestStatus.propTypes = { |
||||
status: ImmutablePropTypes.map.isRequired |
||||
}; |
||||
|
||||
export default AutosuggestStatus; |
@ -0,0 +1,15 @@ |
||||
import { connect } from 'react-redux'; |
||||
import AutosuggestStatus from '../components/autosuggest_status'; |
||||
import { makeGetStatus } from '../../../selectors'; |
||||
|
||||
const makeMapStateToProps = () => { |
||||
const getStatus = makeGetStatus(); |
||||
|
||||
const mapStateToProps = (state, { id }) => ({ |
||||
status: getStatus(state, id) |
||||
}); |
||||
|
||||
return mapStateToProps; |
||||
}; |
||||
|
||||
export default connect(makeMapStateToProps)(AutosuggestStatus); |
@ -0,0 +1,9 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class Api::V1::SearchController < ApiController |
||||
respond_to :json |
||||
|
||||
def index |
||||
@search = OpenStruct.new(SearchService.new.call(params[:q], 5, params[:resolve] == 'true', current_account)) |
||||
end |
||||
end |
@ -0,0 +1,39 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class StatusesController < ApplicationController |
||||
layout 'public' |
||||
|
||||
before_action :set_account |
||||
before_action :set_status |
||||
before_action :set_link_headers |
||||
before_action :check_account_suspension |
||||
|
||||
def show |
||||
@ancestors = @status.reply? ? cache_collection(@status.ancestors(current_account), Status) : [] |
||||
@descendants = cache_collection(@status.descendants(current_account), Status) |
||||
|
||||
render 'stream_entries/show' |
||||
end |
||||
|
||||
private |
||||
|
||||
def set_account |
||||
@account = Account.find_local!(params[:account_username]) |
||||
end |
||||
|
||||
def set_link_headers |
||||
response.headers['Link'] = LinkHeader.new([[account_stream_entry_url(@account, @status.stream_entry, format: 'atom'), [%w(rel alternate), %w(type application/atom+xml)]]]) |
||||
end |
||||
|
||||
def set_status |
||||
@status = @account.statuses.find(params[:id]) |
||||
@stream_entry = @status.stream_entry |
||||
@type = @stream_entry.activity_type.downcase |
||||
|
||||
raise ActiveRecord::RecordNotFound unless @status.permitted?(current_account) |
||||
end |
||||
|
||||
def check_account_suspension |
||||
gone if @account.suspended? |
||||
end |
||||
end |
@ -0,0 +1,26 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class AccountSearchService < BaseService |
||||
def call(query, limit, resolve = false, account = nil) |
||||
return [] if query.blank? || query.start_with?('#') |
||||
|
||||
username, domain = query.gsub(/\A@/, '').split('@') |
||||
domain = nil if TagManager.instance.local_domain?(domain) |
||||
|
||||
if domain.nil? |
||||
exact_match = Account.find_local(username) |
||||
results = account.nil? ? Account.search_for(username, limit) : Account.advanced_search_for(username, account, limit) |
||||
else |
||||
exact_match = Account.find_remote(username, domain) |
||||
results = account.nil? ? Account.search_for("#{username} #{domain}", limit) : Account.advanced_search_for("#{username} #{domain}", account, limit) |
||||
end |
||||
|
||||
results = [exact_match] + results.reject { |a| a.id == exact_match.id } if exact_match |
||||
|
||||
if resolve && !exact_match && !domain.nil? |
||||
results = [FollowRemoteAccountService.new.call("#{username}@#{domain}")] |
||||
end |
||||
|
||||
results |
||||
end |
||||
end |
@ -0,0 +1,18 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class FetchRemoteResourceService < BaseService |
||||
def call(url) |
||||
atom_url, body = FetchAtomService.new.call(url) |
||||
|
||||
return nil if atom_url.nil? |
||||
|
||||
xml = Nokogiri::XML(body) |
||||
xml.encoding = 'utf-8' |
||||
|
||||
if xml.root.name == 'feed' |
||||
FetchRemoteAccountService.new.call(atom_url, body) |
||||
elsif xml.root.name == 'entry' |
||||
FetchRemoteStatusService.new.call(atom_url, body) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,13 @@ |
||||
object @search |
||||
|
||||
child :accounts, object_root: false do |
||||
extends 'api/v1/accounts/show' |
||||
end |
||||
|
||||
node(:hashtags) do |search| |
||||
search.hashtags.map(&:name) |
||||
end |
||||
|
||||
child :statuses, object_root: false do |
||||
extends 'api/v1/statuses/show' |
||||
end |
@ -0,0 +1,9 @@ |
||||
class AddSearchIndexToTags < ActiveRecord::Migration[5.0] |
||||
def up |
||||
execute 'CREATE INDEX hashtag_search_index ON tags USING gin(to_tsvector(\'simple\', tags.name));' |
||||
end |
||||
|
||||
def down |
||||
remove_index :tags, name: :hashtag_search_index |
||||
end |
||||
end |
Loading…
Reference in new issue