about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/api/v2/search_controller.rb8
-rw-r--r--app/javascript/mastodon/actions/search.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/search_results.js56
-rw-r--r--app/javascript/mastodon/reducers/search.js4
-rw-r--r--app/javascript/styles/mastodon/components.scss44
-rw-r--r--app/serializers/rest/v2/search_serializer.rb7
-rw-r--r--config/routes.rb4
7 files changed, 69 insertions, 56 deletions
diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb
new file mode 100644
index 000000000..2e91d68ee
--- /dev/null
+++ b/app/controllers/api/v2/search_controller.rb
@@ -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
diff --git a/app/javascript/mastodon/actions/search.js b/app/javascript/mastodon/actions/search.js
index 882c1709e..b670d25c3 100644
--- a/app/javascript/mastodon/actions/search.js
+++ b/app/javascript/mastodon/actions/search.js
@@ -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,
diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.js
index f2655c14d..445bf27bb 100644
--- a/app/javascript/mastodon/features/compose/components/search_results.js
+++ b/app/javascript/mastodon/features/compose/components/search_results.js
@@ -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>
       );
     }
diff --git a/app/javascript/mastodon/reducers/search.js b/app/javascript/mastodon/reducers/search.js
index 56fd7226b..4758defb1 100644
--- a/app/javascript/mastodon/reducers/search.js
+++ b/app/javascript/mastodon/reducers/search.js
@@ -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;
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index a2a18b5a0..c93d8e86a 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -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%);
-    }
+    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;
 
-    span {
+    .fa {
       display: inline-block;
-      background: $ui-base-color;
-      color: $darker-text-color;
-      font-size: 14px;
-      font-weight: 500;
-      padding: 10px;
-      position: relative;
-      z-index: 1;
-      cursor: default;
+      margin-right: 5px;
     }
   }
 
diff --git a/app/serializers/rest/v2/search_serializer.rb b/app/serializers/rest/v2/search_serializer.rb
new file mode 100644
index 000000000..cdb6b3a53
--- /dev/null
+++ b/app/serializers/rest/v2/search_serializer.rb
@@ -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
diff --git a/config/routes.rb b/config/routes.rb
index 2fcb885ed..31e90e2ff 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -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]