Add GET /api/v2/search which returns rich tag objects, adjust web UI (#7661)
parent
90b64c0069
commit
8bb74e50be
|
@ -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
|
|
@ -33,7 +33,7 @@ export function submitSearch() {
|
|||
|
||||
dispatch(fetchSearchRequest());
|
||||
|
||||
api(getState).get('/api/v1/search', {
|
||||
api(getState).get('/api/v2/search', {
|
||||
params: {
|
||||
q: value,
|
||||
resolve: true,
|
||||
|
|
|
@ -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 {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -44,27 +66,7 @@ export default class SearchResults extends ImmutablePureComponent {
|
|||
<FormattedMessage id='trends.header' defaultMessage='Trending now' />
|
||||
</div>
|
||||
|
||||
{trends && trends.map(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>
|
||||
))}
|
||||
{trends && trends.map(hashtag => renderHashtag(hashtag))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -74,7 +76,7 @@ export default class SearchResults extends ImmutablePureComponent {
|
|||
count += results.get('accounts').size;
|
||||
accounts = (
|
||||
<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} />)}
|
||||
</div>
|
||||
|
@ -85,7 +87,7 @@ export default class SearchResults extends ImmutablePureComponent {
|
|||
count += results.get('statuses').size;
|
||||
statuses = (
|
||||
<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} />)}
|
||||
</div>
|
||||
|
@ -96,13 +98,9 @@ export default class SearchResults extends ImmutablePureComponent {
|
|||
count += results.get('hashtags').size;
|
||||
hashtags = (
|
||||
<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 => (
|
||||
<Link key={hashtag} className='search-results__hashtag' to={`/timelines/tag/${hashtag}`}>
|
||||
{hashtag}
|
||||
</Link>
|
||||
))}
|
||||
{results.get('hashtags').map(hashtag => renderHashtag(hashtag))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
COMPOSE_REPLY,
|
||||
COMPOSE_DIRECT,
|
||||
} 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({
|
||||
value: '',
|
||||
|
@ -39,7 +39,7 @@ export default function search(state = initialState, action) {
|
|||
return state.set('results', ImmutableMap({
|
||||
accounts: ImmutableList(action.results.accounts.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);
|
||||
default:
|
||||
return state;
|
||||
|
|
|
@ -3284,6 +3284,15 @@ a.status-card {
|
|||
}
|
||||
|
||||
.search__icon {
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&::-moz-focus-inner,
|
||||
&:focus {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
.fa {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
|
@ -3333,7 +3342,6 @@ a.status-card {
|
|||
.search-results__header {
|
||||
color: $dark-text-color;
|
||||
background: lighten($ui-base-color, 2%);
|
||||
border-bottom: 1px solid darken($ui-base-color, 4%);
|
||||
padding: 15px;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
|
@ -3346,33 +3354,21 @@ a.status-card {
|
|||
}
|
||||
|
||||
.search-results__section {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
h5 {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
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;
|
||||
background: $ui-base-color;
|
||||
color: $darker-text-color;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
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;
|
||||
|
||||
.fa {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -315,6 +315,10 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
namespace :v2 do
|
||||
get '/search', to: 'search#index', as: :search
|
||||
end
|
||||
|
||||
namespace :web do
|
||||
resource :settings, only: [:update]
|
||||
resource :embed, only: [:create]
|
||||
|
|
Reference in New Issue