about summary refs log tree commit diff
path: root/app/javascript
diff options
context:
space:
mode:
authorThibaut Girka <thib@sitedethib.com>2018-08-20 11:29:42 +0200
committerThibG <thib@sitedethib.com>2018-08-22 19:27:06 +0200
commit801919fc9b7ab12ca6652b03cce027224df59718 (patch)
tree79b9dd4e50faa8efd4d974dd559049b73337d1f1 /app/javascript
parent360fbf1bd430ae43f28444b41e01d305c85c4935 (diff)
Add hashtag trendline support to glitch-soc flavour
Port Mastodon's hashtag stats thing to glitch-soc.
This doesn't change how hashtags are ordered, and doesn't add a trending
hashtags section, but it does change how hashtag searches are rendered,
displaying a trend line alongside each hashtag.
Diffstat (limited to 'app/javascript')
-rw-r--r--app/javascript/flavours/glitch/actions/search.js2
-rw-r--r--app/javascript/flavours/glitch/components/hashtag.js34
-rw-r--r--app/javascript/flavours/glitch/features/drawer/results/index.js11
-rw-r--r--app/javascript/flavours/glitch/reducers/search.js4
-rw-r--r--app/javascript/flavours/glitch/styles/components/search.scss84
-rw-r--r--app/javascript/flavours/glitch/util/numbers.js10
6 files changed, 123 insertions, 22 deletions
diff --git a/app/javascript/flavours/glitch/actions/search.js b/app/javascript/flavours/glitch/actions/search.js
index 13885c600..ec65bdf28 100644
--- a/app/javascript/flavours/glitch/actions/search.js
+++ b/app/javascript/flavours/glitch/actions/search.js
@@ -32,7 +32,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/flavours/glitch/components/hashtag.js b/app/javascript/flavours/glitch/components/hashtag.js
new file mode 100644
index 000000000..88689cc6c
--- /dev/null
+++ b/app/javascript/flavours/glitch/components/hashtag.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import { Sparklines, SparklinesCurve } from 'react-sparklines';
+import { Link } from 'react-router-dom';
+import { FormattedMessage } from 'react-intl';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { shortNumberFormat } from 'flavours/glitch/util/numbers';
+
+const Hashtag = ({ hashtag }) => (
+  <div className='trends__item'>
+    <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>
+);
+
+Hashtag.propTypes = {
+  hashtag: ImmutablePropTypes.map.isRequired,
+};
+
+export default Hashtag;
diff --git a/app/javascript/flavours/glitch/features/drawer/results/index.js b/app/javascript/flavours/glitch/features/drawer/results/index.js
index 23dc0e3cf..ac7a14ef4 100644
--- a/app/javascript/flavours/glitch/features/drawer/results/index.js
+++ b/app/javascript/flavours/glitch/features/drawer/results/index.js
@@ -12,6 +12,7 @@ import { Link } from 'react-router-dom';
 //  Components.
 import AccountContainer from 'flavours/glitch/containers/account_container';
 import StatusContainer from 'flavours/glitch/containers/status_container';
+import Hashtag from 'flavours/glitch/components/hashtag';
 
 //  Utils.
 import Motion from 'flavours/glitch/util/optional_motion';
@@ -98,15 +99,7 @@ export default function DrawerResults ({
             <section>
               <h5><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></h5>
 
-              {hashtags.map(
-                hashtag => (
-                  <Link
-                    className='hashtag'
-                    key={hashtag}
-                    to={`/timelines/tag/${hashtag}`}
-                  >#{hashtag}</Link>
-                )
-              )}
+              {hashtags.map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
             </section>
           ) : null}
         </div>
diff --git a/app/javascript/flavours/glitch/reducers/search.js b/app/javascript/flavours/glitch/reducers/search.js
index dc6be97e2..9a525bf47 100644
--- a/app/javascript/flavours/glitch/reducers/search.js
+++ b/app/javascript/flavours/glitch/reducers/search.js
@@ -9,7 +9,7 @@ import {
   COMPOSE_REPLY,
   COMPOSE_DIRECT,
 } from 'flavours/glitch/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/flavours/glitch/styles/components/search.scss b/app/javascript/flavours/glitch/styles/components/search.scss
index 91861ea19..f9e4b5883 100644
--- a/app/javascript/flavours/glitch/styles/components/search.scss
+++ b/app/javascript/flavours/glitch/styles/components/search.scss
@@ -90,16 +90,80 @@
   font-weight: 500;
 }
 
-.search-results__hashtag {
-  display: block;
-  padding: 10px;
-  color: $secondary-text-color;
-  text-decoration: none;
+.trends {
+  &__header {
+    color: $dark-text-color;
+    background: lighten($ui-base-color, 2%);
+    border-bottom: 1px solid darken($ui-base-color, 4%);
+    font-weight: 500;
+    padding: 15px;
+    font-size: 16px;
+    cursor: default;
 
-  &:hover,
-  &:active,
-  &:focus {
-    color: lighten($secondary-text-color, 4%);
-    text-decoration: underline;
+    .fa {
+      display: inline-block;
+      margin-right: 5px;
+    }
+  }
+
+  &__item {
+    display: flex;
+    align-items: center;
+    padding: 15px;
+    border-bottom: 1px solid lighten($ui-base-color, 8%);
+
+    &:last-child {
+      border-bottom: 0;
+    }
+
+    &__name {
+      flex: 1 1 auto;
+      color: $dark-text-color;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+
+      strong {
+        font-weight: 500;
+      }
+
+      a {
+        color: $darker-text-color;
+        text-decoration: none;
+        font-size: 14px;
+        font-weight: 500;
+        display: block;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+
+        &:hover,
+        &:focus,
+        &:active {
+          span {
+            text-decoration: underline;
+          }
+        }
+      }
+    }
+
+    &__current {
+      flex: 0 0 auto;
+      width: 100px;
+      font-size: 24px;
+      line-height: 36px;
+      font-weight: 500;
+      text-align: center;
+      color: $secondary-text-color;
+    }
+
+    &__sparkline {
+      flex: 0 0 auto;
+      width: 50px;
+
+      path {
+        stroke: lighten($highlight-text-color, 6%) !important;
+      }
+    }
   }
 }
diff --git a/app/javascript/flavours/glitch/util/numbers.js b/app/javascript/flavours/glitch/util/numbers.js
new file mode 100644
index 000000000..fdd8269ae
--- /dev/null
+++ b/app/javascript/flavours/glitch/util/numbers.js
@@ -0,0 +1,10 @@
+import React, { Fragment } from 'react';
+import { FormattedNumber } from 'react-intl';
+
+export const shortNumberFormat = number => {
+  if (number < 1000) {
+    return <FormattedNumber value={number} />;
+  } else {
+    return <Fragment><FormattedNumber value={number / 1000} maximumFractionDigits={1} />K</Fragment>;
+  }
+};