Add GET /api/v2/search which returns rich tag objects, adjust web UI (#7661)

This commit is contained in:
Eugen Rochko 2018-05-29 02:01:24 +02:00 committed by GitHub
parent 90b64c0069
commit 8bb74e50be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 69 additions and 56 deletions

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
class Api::V2::SearchController < Api::V1::SearchController
def index
@search = Search.new(search)
render json: @search, serializer: REST::V2::SearchSerializer
end
end

View file

@ -33,7 +33,7 @@ export function submitSearch() {
dispatch(fetchSearchRequest()); dispatch(fetchSearchRequest());
api(getState).get('/api/v1/search', { api(getState).get('/api/v2/search', {
params: { params: {
q: value, q: value,
resolve: true, resolve: true,

View file

@ -16,6 +16,28 @@ const shortNumberFormat = number => {
} }
}; };
const renderHashtag = hashtag => (
<div className='trends__item' key={hashtag.get('name')}>
<div className='trends__item__name'>
<Link to={`/timelines/tag/${hashtag.get('name')}`}>
#<span>{hashtag.get('name')}</span>
</Link>
<FormattedMessage id='trends.count_by_accounts' defaultMessage='{count} {rawCount, plural, one {person} other {people}} talking' values={{ rawCount: hashtag.getIn(['history', 0, 'accounts']), count: <strong>{shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']))}</strong> }} />
</div>
<div className='trends__item__current'>
{shortNumberFormat(hashtag.getIn(['history', 0, 'uses']))}
</div>
<div className='trends__item__sparkline'>
<Sparklines width={50} height={28} data={hashtag.get('history').reverse().map(day => day.get('uses')).toArray()}>
<SparklinesCurve style={{ fill: 'none' }} />
</Sparklines>
</div>
</div>
);
export default class SearchResults extends ImmutablePureComponent { export default class SearchResults extends ImmutablePureComponent {
static propTypes = { static propTypes = {
@ -44,27 +66,7 @@ export default class SearchResults extends ImmutablePureComponent {
<FormattedMessage id='trends.header' defaultMessage='Trending now' /> <FormattedMessage id='trends.header' defaultMessage='Trending now' />
</div> </div>
{trends && trends.map(hashtag => ( {trends && trends.map(hashtag => renderHashtag(hashtag))}
<div className='trends__item' key={hashtag.get('name')}>
<div className='trends__item__name'>
<Link to={`/timelines/tag/${hashtag.get('name')}`}>
#<span>{hashtag.get('name')}</span>
</Link>
<FormattedMessage id='trends.count_by_accounts' defaultMessage='{count} {rawCount, plural, one {person} other {people}} talking' values={{ rawCount: hashtag.getIn(['history', 0, 'accounts']), count: <strong>{shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']))}</strong> }} />
</div>
<div className='trends__item__current'>
{shortNumberFormat(hashtag.getIn(['history', 0, 'uses']))}
</div>
<div className='trends__item__sparkline'>
<Sparklines width={50} height={28} data={hashtag.get('history').reverse().map(day => day.get('uses')).toArray()}>
<SparklinesCurve style={{ fill: 'none' }} />
</Sparklines>
</div>
</div>
))}
</div> </div>
</div> </div>
); );
@ -74,7 +76,7 @@ export default class SearchResults extends ImmutablePureComponent {
count += results.get('accounts').size; count += results.get('accounts').size;
accounts = ( accounts = (
<div className='search-results__section'> <div className='search-results__section'>
<h5><FormattedMessage id='search_results.accounts' defaultMessage='People' /></h5> <h5><i className='fa fa-fw fa-users' /><FormattedMessage id='search_results.accounts' defaultMessage='People' /></h5>
{results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)} {results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)}
</div> </div>
@ -85,7 +87,7 @@ export default class SearchResults extends ImmutablePureComponent {
count += results.get('statuses').size; count += results.get('statuses').size;
statuses = ( statuses = (
<div className='search-results__section'> <div className='search-results__section'>
<h5><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5> <h5><i className='fa fa-fw fa-quote-right' /><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
{results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)} {results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
</div> </div>
@ -96,13 +98,9 @@ export default class SearchResults extends ImmutablePureComponent {
count += results.get('hashtags').size; count += results.get('hashtags').size;
hashtags = ( hashtags = (
<div className='search-results__section'> <div className='search-results__section'>
<h5><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></h5> <h5><i className='fa fa-fw fa-hashtag' /><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></h5>
{results.get('hashtags').map(hashtag => ( {results.get('hashtags').map(hashtag => renderHashtag(hashtag))}
<Link key={hashtag} className='search-results__hashtag' to={`/timelines/tag/${hashtag}`}>
{hashtag}
</Link>
))}
</div> </div>
); );
} }

View file

@ -9,7 +9,7 @@ import {
COMPOSE_REPLY, COMPOSE_REPLY,
COMPOSE_DIRECT, COMPOSE_DIRECT,
} from '../actions/compose'; } from '../actions/compose';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
const initialState = ImmutableMap({ const initialState = ImmutableMap({
value: '', value: '',
@ -39,7 +39,7 @@ export default function search(state = initialState, action) {
return state.set('results', ImmutableMap({ return state.set('results', ImmutableMap({
accounts: ImmutableList(action.results.accounts.map(item => item.id)), accounts: ImmutableList(action.results.accounts.map(item => item.id)),
statuses: ImmutableList(action.results.statuses.map(item => item.id)), statuses: ImmutableList(action.results.statuses.map(item => item.id)),
hashtags: ImmutableList(action.results.hashtags), hashtags: fromJS(action.results.hashtags),
})).set('submitted', true); })).set('submitted', true);
default: default:
return state; return state;

View file

@ -3284,6 +3284,15 @@ a.status-card {
} }
.search__icon { .search__icon {
&::-moz-focus-inner {
border: 0;
}
&::-moz-focus-inner,
&:focus {
outline: 0 !important;
}
.fa { .fa {
position: absolute; position: absolute;
top: 10px; top: 10px;
@ -3333,7 +3342,6 @@ a.status-card {
.search-results__header { .search-results__header {
color: $dark-text-color; color: $dark-text-color;
background: lighten($ui-base-color, 2%); background: lighten($ui-base-color, 2%);
border-bottom: 1px solid darken($ui-base-color, 4%);
padding: 15px; padding: 15px;
font-weight: 500; font-weight: 500;
font-size: 16px; font-size: 16px;
@ -3346,33 +3354,21 @@ a.status-card {
} }
.search-results__section { .search-results__section {
margin-bottom: 20px; margin-bottom: 5px;
h5 { h5 {
position: relative; background: darken($ui-base-color, 4%);
border-bottom: 1px solid lighten($ui-base-color, 8%);
cursor: default;
display: flex;
padding: 15px;
font-weight: 500;
font-size: 16px;
color: $dark-text-color;
&::before { .fa {
content: "";
display: block;
position: absolute;
left: 0;
right: 0;
top: 50%;
width: 100%;
height: 0;
border-top: 1px solid lighten($ui-base-color, 8%);
}
span {
display: inline-block; display: inline-block;
background: $ui-base-color; margin-right: 5px;
color: $darker-text-color;
font-size: 14px;
font-weight: 500;
padding: 10px;
position: relative;
z-index: 1;
cursor: default;
} }
} }

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class REST::V2::SearchSerializer < ActiveModel::Serializer
has_many :accounts, serializer: REST::AccountSerializer
has_many :statuses, serializer: REST::StatusSerializer
has_many :hashtags, serializer: REST::TagSerializer
end

View file

@ -315,6 +315,10 @@ Rails.application.routes.draw do
end end
end end
namespace :v2 do
get '/search', to: 'search#index', as: :search
end
namespace :web do namespace :web do
resource :settings, only: [:update] resource :settings, only: [:update]
resource :embed, only: [:create] resource :embed, only: [:create]