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