From cfce94fb24c33c51849399e6d33ef2a05f8605d0 Mon Sep 17 00:00:00 2001 From: noellabo Date: Wed, 24 Mar 2021 17:14:38 +0900 Subject: [PATCH] Add trends columns --- app/controllers/api/v1/trends_controller.rb | 2 +- app/javascript/mastodon/actions/trends.js | 2 +- .../features/getting_started/index.js | 13 ++ .../mastodon/features/trends/index.js | 122 ++++++++++++++++++ .../features/ui/components/columns_area.js | 2 + .../ui/components/navigation_panel.js | 1 + app/javascript/mastodon/features/ui/index.js | 2 + .../features/ui/util/async-components.js | 4 + app/javascript/mastodon/locales/en.json | 3 + app/javascript/mastodon/locales/ja.json | 3 + app/models/trending_tags.rb | 6 +- 11 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 app/javascript/mastodon/features/trends/index.js diff --git a/app/controllers/api/v1/trends_controller.rb b/app/controllers/api/v1/trends_controller.rb index c875e9041..a418da903 100644 --- a/app/controllers/api/v1/trends_controller.rb +++ b/app/controllers/api/v1/trends_controller.rb @@ -10,6 +10,6 @@ class Api::V1::TrendsController < Api::BaseController private def set_tags - @tags = TrendingTags.get(limit_param(10)) + @tags = TrendingTags.get(limit_param(TrendingTags::LIMIT)) end end diff --git a/app/javascript/mastodon/actions/trends.js b/app/javascript/mastodon/actions/trends.js index 853e4f60a..03ab76521 100644 --- a/app/javascript/mastodon/actions/trends.js +++ b/app/javascript/mastodon/actions/trends.js @@ -8,7 +8,7 @@ export const fetchTrends = () => (dispatch, getState) => { dispatch(fetchTrendsRequest()); api(getState) - .get('/api/v1/trends') + .get('/api/v1/trends', { params: { limit: 20 } }) .then(({ data }) => dispatch(fetchTrendsSuccess(data))) .catch(err => dispatch(fetchTrendsFail(err))); }; diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index 3a0478e79..4316328e2 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -37,6 +37,7 @@ const messages = defineMessages({ menu: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, group_directory: { id: 'getting_started.group_directory', defaultMessage: 'Group directory' }, profile_directory: { id: 'getting_started.directory', defaultMessage: 'Profile directory' }, + trends: { id: 'navigation_bar.trends', defaultMessage: 'Trends' }, information_acct: { id: 'navigation_bar.information_acct', defaultMessage: 'Fedibird info' }, hashtag_fedibird: { id: 'navigation_bar.hashtag_fedibird', defaultMessage: 'fedibird' }, }); @@ -120,6 +121,12 @@ class GettingStarted extends ImmutablePureComponent { height += 48; } + navItems.push( + , + ); + + height += 48; + navItems.push( , , @@ -147,6 +154,12 @@ class GettingStarted extends ImmutablePureComponent { height += 48; } + navItems.push( + , + ); + + height += 48; + navItems.push( , , diff --git a/app/javascript/mastodon/features/trends/index.js b/app/javascript/mastodon/features/trends/index.js new file mode 100644 index 000000000..7af077282 --- /dev/null +++ b/app/javascript/mastodon/features/trends/index.js @@ -0,0 +1,122 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { fetchTrends } from '../../actions/trends'; +import Column from '../ui/components/column'; +import ColumnHeader from '../../components/column_header'; +import ColumnSubheading from '../ui/components/column_subheading'; +import Icon from 'mastodon/components/icon'; +import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; +import ScrollableList from 'mastodon/components/scrollable_list'; +import Hashtag from 'mastodon/components/hashtag'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +const messages = defineMessages({ + heading: { id: 'trends.heading', defaultMessage: 'Trends' }, + subheading: { id: 'trends.trending_now', defaultMessage: 'Trending now' }, + refresh: { id: 'refresh', defaultMessage: 'Refresh' }, +}); + +const mapStateToProps = state => ({ + trends: state.getIn(['trends', 'items']), + isLoading: state.getIn(['trends', 'isLoading'], true), +}); + +export default @connect(mapStateToProps) +@injectIntl +class Trends extends ImmutablePureComponent { + + static propTypes = { + dispatch: PropTypes.func.isRequired, + trends: ImmutablePropTypes.list, + intl: PropTypes.object.isRequired, + columnId: PropTypes.string, + multiColumn: PropTypes.bool, + isLoading: PropTypes.bool, + }; + + componentDidMount () { + this.fetchTrends(); + this.refreshInterval = setInterval(() => this.fetchTrends(), 900 * 1000); + } + + componentWillUnmount () { + if (this.refreshInterval) { + clearInterval(this.refreshInterval); + } + } + + handleRefresh = () => { + this.fetchTrends(); + } + + fetchTrends = () => { + const { dispatch } = this.props; + + dispatch(fetchTrends()); + } + + handlePin = () => { + const { columnId, dispatch } = this.props; + + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + dispatch(addColumn('TRENDS', {})); + } + } + + handleMove = (dir) => { + const { columnId, dispatch } = this.props; + dispatch(moveColumn(columnId, dir)); + } + + handleHeaderClick = () => { + this.column.scrollTop(); + } + + setRef = c => { + this.column = c; + } + + render () { + const { intl, trends, columnId, multiColumn, isLoading } = this.props; + const pinned = !!columnId; + + const emptyMessage = ; + + return ( + + + )} + /> + + } + bindToDocument={!multiColumn} + > + {trends.map(hashtag => + , + )} + + + ); + } + +} diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index bbb04f36f..3c1f07af6 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -28,6 +28,7 @@ import { ListTimeline, GroupDirectory, Directory, + Trends, } from '../../ui/util/async-components'; import Icon from 'mastodon/components/icon'; import ComposePanel from './compose_panel'; @@ -54,6 +55,7 @@ const componentMap = { 'LIST': ListTimeline, 'GROUP_DIRECTORY': GroupDirectory, 'DIRECTORY': Directory, + 'TRENDS': Trends, }; const messages = defineMessages({ diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.js b/app/javascript/mastodon/features/ui/components/navigation_panel.js index 238d369f2..5e2adedc5 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.js +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.js @@ -25,6 +25,7 @@ const NavigationPanel = () => ( {profile_directory && } + diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 2327407dd..1629b0dbd 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -57,6 +57,7 @@ import { GroupDirectory, Directory, FollowRecommendations, + Trends, } from './util/async-components'; import { me } from '../../initial_state'; import { closeOnboarding, INTRODUCTION_VERSION } from 'mastodon/actions/onboarding'; @@ -173,6 +174,7 @@ class SwitchingColumnsArea extends React.PureComponent { + diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index 0230b37e2..c98e4a51a 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -185,3 +185,7 @@ export function Directory () { export function FollowRecommendations () { return import(/* webpackChunkName: "features/follow_recommendations" */'../../follow_recommendations'); } + +export function Trends () { + return import(/* webpackChunkName: "features/trends" */'../../trends'); +} diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 45eb2c64b..48b5ba56e 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -203,6 +203,7 @@ "empty_column.mutes": "You haven't muted any users yet.", "empty_column.notifications": "You don't have any notifications yet. When other people interact with you, you will see it here.", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up", + "empty_column.trends": "No one has trends yet.", "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.", "error.unexpected_crash.explanation_addons": "This page could not be displayed correctly. This error is likely caused by a browser add-on or automatic translation tools.", "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.", @@ -345,6 +346,7 @@ "navigation_bar.short.preferences": "Pref.", "navigation_bar.short.public_timeline": "FTL", "navigation_bar.short.search": "Search", + "navigation_bar.trends": "Trends", "notification.favourite": "{name} favourited your post", "notification.follow": "{name} followed you", "notification.follow_request": "{name} has requested to follow you", @@ -503,6 +505,7 @@ "timeline_hint.resources.follows": "Follows", "timeline_hint.resources.statuses": "Older posts", "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} talking", + "trends.heading": "Trends", "trends.trending_now": "Trending now", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "units.short.billion": "{count}B", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index b9e64c67a..bdc6bfaf5 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -202,6 +202,7 @@ "empty_column.lists": "まだリストがありません。リストを作るとここに表示されます。", "empty_column.mutes": "まだ誰もミュートしていません。", "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。", + "empty_column.trends": "まだ何もトレンドがありません。", "empty_column.public": "ここにはまだ何もありません! 公開で何かを投稿したり、他のサーバーのユーザーをフォローしたりしていっぱいにしましょう", "error.unexpected_crash.explanation": "不具合かブラウザの互換性問題のため、このページを正しく表示できませんでした。", "error.unexpected_crash.explanation_addons": "このページは正しく表示できませんでした。このエラーはブラウザのアドオンや自動翻訳ツールによって引き起こされることがあります。", @@ -345,6 +346,7 @@ "navigation_bar.short.preferences": "設定", "navigation_bar.short.public_timeline": "連合", "navigation_bar.short.search": "検索", + "navigation_bar.trends": "トレンド", "notification.favourite": "{name}さんがあなたの投稿をお気に入りに登録しました", "notification.follow": "{name}さんにフォローされました", "notification.follow_request": "{name} さんがあなたにフォローリクエストしました", @@ -503,6 +505,7 @@ "timeline_hint.resources.follows": "フォロー", "timeline_hint.resources.statuses": "以前の投稿", "trends.counter_by_accounts": "{counter} 人が投稿", + "trends.heading": "トレンド", "trends.trending_now": "トレンドタグ", "ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。", "units.short.billion": "{count}B", diff --git a/app/models/trending_tags.rb b/app/models/trending_tags.rb index 31890b082..64d689c21 100644 --- a/app/models/trending_tags.rb +++ b/app/models/trending_tags.rb @@ -86,12 +86,12 @@ class TrendingTags # Trim older items - redis.zremrangebyrank(KEY, 0, -(LIMIT + 1)) + redis.zremrangebyrank(KEY, 0, -(LIMIT*2 + 1)) redis.zremrangebyscore(KEY, '(0.3', '-inf') end def get(limit, filtered: true) - tag_ids = redis.zrevrange(KEY, 0, LIMIT - 1).map(&:to_i) + tag_ids = redis.zrevrange(KEY, 0, LIMIT*2 - 1).map(&:to_i) tags = Tag.where(id: tag_ids) tags = tags.trendable if filtered @@ -102,7 +102,7 @@ class TrendingTags def trending?(tag) rank = redis.zrevrank(KEY, tag.id) - rank.present? && rank < LIMIT + rank.present? && rank < LIMIT*2 end private