Add trends UI with admin and user settings (#11502)
This commit is contained in:
parent
82d2069c75
commit
9072fe5ab6
21 changed files with 189 additions and 13 deletions
|
@ -56,6 +56,7 @@ class Settings::PreferencesController < Settings::BaseController
|
|||
:setting_advanced_layout,
|
||||
:setting_use_blurhash,
|
||||
:setting_use_pending_items,
|
||||
:setting_trends,
|
||||
notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account trending_tag),
|
||||
interactions: %i(must_be_follower must_be_following must_be_following_dm)
|
||||
)
|
||||
|
|
32
app/javascript/mastodon/actions/trends.js
Normal file
32
app/javascript/mastodon/actions/trends.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import api from '../api';
|
||||
|
||||
export const TRENDS_FETCH_REQUEST = 'TRENDS_FETCH_REQUEST';
|
||||
export const TRENDS_FETCH_SUCCESS = 'TRENDS_FETCH_SUCCESS';
|
||||
export const TRENDS_FETCH_FAIL = 'TRENDS_FETCH_FAIL';
|
||||
|
||||
export const fetchTrends = () => (dispatch, getState) => {
|
||||
dispatch(fetchTrendsRequest());
|
||||
|
||||
api(getState)
|
||||
.get('/api/v1/trends')
|
||||
.then(({ data }) => dispatch(fetchTrendsSuccess(data)))
|
||||
.catch(err => dispatch(fetchTrendsFail(err)));
|
||||
};
|
||||
|
||||
export const fetchTrendsRequest = () => ({
|
||||
type: TRENDS_FETCH_REQUEST,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
export const fetchTrendsSuccess = trends => ({
|
||||
type: TRENDS_FETCH_SUCCESS,
|
||||
trends,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
export const fetchTrendsFail = error => ({
|
||||
type: TRENDS_FETCH_FAIL,
|
||||
error,
|
||||
skipLoading: true,
|
||||
skipAlert: true,
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
import React from 'react';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Hashtag from 'mastodon/components/hashtag';
|
||||
|
||||
export default class Trends extends ImmutablePureComponent {
|
||||
|
||||
static defaultProps = {
|
||||
loading: false,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
trends: ImmutablePropTypes.list,
|
||||
fetchTrends: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
this.props.fetchTrends();
|
||||
this.refreshInterval = setInterval(() => this.props.fetchTrends(), 36000);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.refreshInterval) {
|
||||
clearInterval(this.refreshInterval);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { trends } = this.props;
|
||||
|
||||
if (!trends || trends.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='getting-started__trends'>
|
||||
{trends.take(3).map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { fetchTrends } from '../../../actions/trends';
|
||||
import Trends from '../components/trends';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
trends: state.getIn(['trends', 'items']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
fetchTrends: () => dispatch(fetchTrends()),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Trends);
|
|
@ -7,12 +7,13 @@ import { connect } from 'react-redux';
|
|||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { me, profile_directory } from '../../initial_state';
|
||||
import { me, profile_directory, showTrends } from '../../initial_state';
|
||||
import { fetchFollowRequests } from 'mastodon/actions/accounts';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import NavigationBar from '../compose/components/navigation_bar';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import LinkFooter from 'mastodon/features/ui/components/link_footer';
|
||||
import TrendsContainer from './containers/trends_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
|
||||
|
@ -168,6 +169,8 @@ class GettingStarted extends ImmutablePureComponent {
|
|||
|
||||
<LinkFooter withHotkeys={multiColumn} />
|
||||
</div>
|
||||
|
||||
{multiColumn && showTrends && <TrendsContainer />}
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,10 +2,11 @@ import React from 'react';
|
|||
import { NavLink, withRouter } from 'react-router-dom';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import { profile_directory } from 'mastodon/initial_state';
|
||||
import { profile_directory, showTrends } from 'mastodon/initial_state';
|
||||
import NotificationsCounterIcon from './notifications_counter_icon';
|
||||
import FollowRequestsNavLink from './follow_requests_nav_link';
|
||||
import ListPanel from './list_panel';
|
||||
import TrendsContainer from 'mastodon/features/getting_started/containers/trends_container';
|
||||
|
||||
const NavigationPanel = () => (
|
||||
<div className='navigation-panel'>
|
||||
|
@ -25,6 +26,9 @@ const NavigationPanel = () => (
|
|||
<a className='column-link column-link--transparent' href='/settings/preferences'><Icon className='column-link__icon' id='cog' fixedWidth /><FormattedMessage id='navigation_bar.preferences' defaultMessage='Preferences' /></a>
|
||||
<a className='column-link column-link--transparent' href='/relationships'><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='navigation_bar.follows_and_followers' defaultMessage='Follows and followers' /></a>
|
||||
{!!profile_directory && <a className='column-link column-link--transparent' href='/explore'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='navigation_bar.profile_directory' defaultMessage='Profile directory' /></a>}
|
||||
|
||||
{showTrends && <div className='flex-spacer' />}
|
||||
{showTrends && <TrendsContainer />}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
|
@ -22,5 +22,6 @@ export const isStaff = getMeta('is_staff');
|
|||
export const forceSingleColumn = !getMeta('advanced_layout');
|
||||
export const useBlurhash = getMeta('use_blurhash');
|
||||
export const usePendingItems = getMeta('use_pending_items');
|
||||
export const showTrends = getMeta('trends');
|
||||
|
||||
export default initialState;
|
||||
|
|
|
@ -31,6 +31,7 @@ import conversations from './conversations';
|
|||
import suggestions from './suggestions';
|
||||
import polls from './polls';
|
||||
import identity_proofs from './identity_proofs';
|
||||
import trends from './trends';
|
||||
|
||||
const reducers = {
|
||||
dropdown_menu,
|
||||
|
@ -65,6 +66,7 @@ const reducers = {
|
|||
conversations,
|
||||
suggestions,
|
||||
polls,
|
||||
trends,
|
||||
};
|
||||
|
||||
export default combineReducers(reducers);
|
||||
|
|
|
@ -12,6 +12,10 @@ const initialState = ImmutableMap({
|
|||
|
||||
skinTone: 1,
|
||||
|
||||
trends: ImmutableMap({
|
||||
show: true,
|
||||
}),
|
||||
|
||||
home: ImmutableMap({
|
||||
shows: ImmutableMap({
|
||||
reblog: true,
|
||||
|
|
23
app/javascript/mastodon/reducers/trends.js
Normal file
23
app/javascript/mastodon/reducers/trends.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { TRENDS_FETCH_REQUEST, TRENDS_FETCH_SUCCESS, TRENDS_FETCH_FAIL } from '../actions/trends';
|
||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
export default function trendsReducer(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case TRENDS_FETCH_REQUEST:
|
||||
return state.set('isLoading', true);
|
||||
case TRENDS_FETCH_SUCCESS:
|
||||
return state.withMutations(map => {
|
||||
map.set('items', fromJS(action.trends));
|
||||
map.set('isLoading', false);
|
||||
});
|
||||
case TRENDS_FETCH_FAIL:
|
||||
return state.set('isLoading', false);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
|
@ -2212,7 +2212,6 @@ a.account__display-name {
|
|||
}
|
||||
|
||||
.getting-started__wrapper,
|
||||
.getting-started__trends,
|
||||
.search {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
@ -2319,13 +2318,24 @@ a.account__display-name {
|
|||
margin-bottom: 10px;
|
||||
height: calc(100% - 20px);
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > a {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
hr {
|
||||
flex: 0 0 auto;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
border-top: 1px solid lighten($ui-base-color, 4%);
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.flex-spacer {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer__pager {
|
||||
|
@ -2717,8 +2727,10 @@ a.account__display-name {
|
|||
}
|
||||
|
||||
&__trends {
|
||||
background: $ui-base-color;
|
||||
flex: 0 1 auto;
|
||||
opacity: 1;
|
||||
animation: fade 150ms linear;
|
||||
margin-top: 10px;
|
||||
|
||||
@media screen and (max-height: 810px) {
|
||||
.trends__item:nth-child(3) {
|
||||
|
@ -2735,11 +2747,15 @@ a.account__display-name {
|
|||
@media screen and (max-height: 670px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__scrollable {
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
.trends__item {
|
||||
border-bottom: 0;
|
||||
padding: 10px;
|
||||
|
||||
&__current {
|
||||
color: $darker-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5968,7 +5984,8 @@ noscript {
|
|||
font-size: 24px;
|
||||
line-height: 36px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
text-align: right;
|
||||
padding-right: 15px;
|
||||
color: $secondary-text-color;
|
||||
}
|
||||
|
||||
|
@ -5976,7 +5993,12 @@ noscript {
|
|||
flex: 0 0 auto;
|
||||
width: 50px;
|
||||
|
||||
path {
|
||||
path:first-child {
|
||||
fill: rgba($highlight-text-color, 0.25) !important;
|
||||
fill-opacity: 1 !important;
|
||||
}
|
||||
|
||||
path:last-child {
|
||||
stroke: lighten($highlight-text-color, 6%) !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ class UserSettingsDecorator
|
|||
user.settings['advanced_layout'] = advanced_layout_preference if change?('setting_advanced_layout')
|
||||
user.settings['use_blurhash'] = use_blurhash_preference if change?('setting_use_blurhash')
|
||||
user.settings['use_pending_items'] = use_pending_items_preference if change?('setting_use_pending_items')
|
||||
user.settings['trends'] = trends_preference if change?('setting_trends')
|
||||
end
|
||||
|
||||
def merged_notification_emails
|
||||
|
@ -122,6 +123,10 @@ class UserSettingsDecorator
|
|||
boolean_cast_setting 'setting_use_pending_items'
|
||||
end
|
||||
|
||||
def trends_preference
|
||||
boolean_cast_setting 'setting_trends'
|
||||
end
|
||||
|
||||
def boolean_cast_setting(key)
|
||||
ActiveModel::Type::Boolean.new.cast(settings[key])
|
||||
end
|
||||
|
|
|
@ -29,6 +29,7 @@ class Form::AdminSettings
|
|||
hero
|
||||
mascot
|
||||
spam_check_enabled
|
||||
trends
|
||||
).freeze
|
||||
|
||||
BOOLEAN_KEYS = %i(
|
||||
|
@ -41,6 +42,7 @@ class Form::AdminSettings
|
|||
preview_sensitive_media
|
||||
profile_directory
|
||||
spam_check_enabled
|
||||
trends
|
||||
).freeze
|
||||
|
||||
UPLOAD_KEYS = %i(
|
||||
|
|
|
@ -66,6 +66,10 @@ class TrendingTags
|
|||
end
|
||||
|
||||
def request_review!(tag)
|
||||
return unless Setting.trends
|
||||
|
||||
tag.touch(:requested_review_at)
|
||||
|
||||
User.staff.includes(:account).find_each { |u| AdminMailer.new_trending_tag(u.account, tag).deliver_later! if u.allows_trending_tag_emails? }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -107,7 +107,8 @@ class User < ApplicationRecord
|
|||
delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal,
|
||||
:reduce_motion, :system_font_ui, :noindex, :theme, :display_media, :hide_network,
|
||||
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application,
|
||||
:advanced_layout, :use_blurhash, :use_pending_items, to: :settings, prefix: :setting, allow_nil: false
|
||||
:advanced_layout, :use_blurhash, :use_pending_items, :trends,
|
||||
to: :settings, prefix: :setting, allow_nil: false
|
||||
|
||||
attr_reader :invite_code
|
||||
attr_writer :external
|
||||
|
|
|
@ -20,6 +20,7 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||
invites_enabled: Setting.min_invite_role == 'user',
|
||||
mascot: instance_presenter.mascot&.file&.url,
|
||||
profile_directory: Setting.profile_directory,
|
||||
trends: Setting.trends,
|
||||
}
|
||||
|
||||
if object.current_account
|
||||
|
@ -35,6 +36,7 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||
store[:use_blurhash] = object.current_account.user.setting_use_blurhash
|
||||
store[:use_pending_items] = object.current_account.user.setting_use_pending_items
|
||||
store[:is_staff] = object.current_account.user.staff?
|
||||
store[:trends] = Setting.trends && object.current_account.user.setting_trends
|
||||
end
|
||||
|
||||
store
|
||||
|
|
|
@ -68,6 +68,9 @@
|
|||
.fields-group
|
||||
= f.input :profile_directory, as: :boolean, wrapper: :with_label, label: t('admin.settings.profile_directory.title'), hint: t('admin.settings.profile_directory.desc_html')
|
||||
|
||||
.fields-group
|
||||
= f.input :trends, as: :boolean, wrapper: :with_label, label: t('admin.settings.trends.title'), hint: t('admin.settings.trends.desc_html')
|
||||
|
||||
.fields-group
|
||||
= f.input :spam_check_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.spam_check_enabled.title'), hint: t('admin.settings.spam_check_enabled.desc_html')
|
||||
|
||||
|
|
|
@ -25,6 +25,11 @@
|
|||
= f.input :setting_reduce_motion, as: :boolean, wrapper: :with_label
|
||||
= f.input :setting_system_font_ui, as: :boolean, wrapper: :with_label
|
||||
|
||||
%h4= t 'appearance.discovery'
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_trends, as: :boolean, wrapper: :with_label
|
||||
|
||||
%h4= t 'appearance.confirmation_dialogs'
|
||||
|
||||
.fields-group
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue