Add Pinned toot column (#4817)
* Add Pinned_toot_section * Fix add frozen_string_literal * Fix delete no need controller and tests * Fix replace query strings to axios params * Fix change value to accountId and disabling more button
This commit is contained in:
parent
8185f98872
commit
85c7c42098
39
app/javascript/mastodon/actions/pin_statuses.js
Normal file
39
app/javascript/mastodon/actions/pin_statuses.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import api from '../api';
|
||||||
|
|
||||||
|
export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST';
|
||||||
|
export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS';
|
||||||
|
export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL';
|
||||||
|
|
||||||
|
export function fetchPinnedStatuses() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch(fetchPinnedStatusesRequest());
|
||||||
|
|
||||||
|
const accountId = getState().getIn(['meta', 'me']);
|
||||||
|
api(getState).get(`/api/v1/accounts/${accountId}/statuses`, { params: { pinned: true } }).then(response => {
|
||||||
|
dispatch(fetchPinnedStatusesSuccess(response.data, null));
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(fetchPinnedStatusesFail(error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function fetchPinnedStatusesRequest() {
|
||||||
|
return {
|
||||||
|
type: PINNED_STATUSES_FETCH_REQUEST,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function fetchPinnedStatusesSuccess(statuses, next) {
|
||||||
|
return {
|
||||||
|
type: PINNED_STATUSES_FETCH_SUCCESS,
|
||||||
|
statuses,
|
||||||
|
next,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function fetchPinnedStatusesFail(error) {
|
||||||
|
return {
|
||||||
|
type: PINNED_STATUSES_FETCH_FAIL,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
};
|
|
@ -23,6 +23,7 @@ const messages = defineMessages({
|
||||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||||
info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' },
|
info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' },
|
||||||
|
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
@ -66,15 +67,16 @@ export default class GettingStarted extends ImmutablePureComponent {
|
||||||
|
|
||||||
navItems = navItems.concat([
|
navItems = navItems.concat([
|
||||||
<ColumnLink key='4' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
|
<ColumnLink key='4' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
|
||||||
|
<ColumnLink key='5' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (me.get('locked')) {
|
if (me.get('locked')) {
|
||||||
navItems.push(<ColumnLink key='5' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />);
|
navItems.push(<ColumnLink key='6' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />);
|
||||||
}
|
}
|
||||||
|
|
||||||
navItems = navItems.concat([
|
navItems = navItems.concat([
|
||||||
<ColumnLink key='6' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />,
|
<ColumnLink key='7' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />,
|
||||||
<ColumnLink key='7' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />,
|
<ColumnLink key='8' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
59
app/javascript/mastodon/features/pinned_statuses/index.js
Normal file
59
app/javascript/mastodon/features/pinned_statuses/index.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import { fetchPinnedStatuses } from '../../actions/pin_statuses';
|
||||||
|
import Column from '../ui/components/column';
|
||||||
|
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
||||||
|
import StatusList from '../../components/status_list';
|
||||||
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
heading: { id: 'column.pins', defaultMessage: 'Pinned toot' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
statusIds: state.getIn(['status_lists', 'pins', 'items']),
|
||||||
|
hasMore: !!state.getIn(['status_lists', 'pins', 'next']),
|
||||||
|
});
|
||||||
|
|
||||||
|
@connect(mapStateToProps)
|
||||||
|
@injectIntl
|
||||||
|
export default class PinnedStatuses extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
dispatch: PropTypes.func.isRequired,
|
||||||
|
statusIds: ImmutablePropTypes.list.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
hasMore: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
this.props.dispatch(fetchPinnedStatuses());
|
||||||
|
}
|
||||||
|
|
||||||
|
handleHeaderClick = () => {
|
||||||
|
this.column.scrollTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
setRef = c => {
|
||||||
|
this.column = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { intl, statusIds, hasMore } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column icon='thumb-tack' heading={intl.formatMessage(messages.heading)} ref={this.setRef}>
|
||||||
|
<ColumnBackButtonSlim />
|
||||||
|
<StatusList
|
||||||
|
statusIds={statusIds}
|
||||||
|
scrollKey='pinned_statuses'
|
||||||
|
hasMore={hasMore}
|
||||||
|
/>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -35,6 +35,7 @@ import {
|
||||||
FavouritedStatuses,
|
FavouritedStatuses,
|
||||||
Blocks,
|
Blocks,
|
||||||
Mutes,
|
Mutes,
|
||||||
|
PinnedStatuses,
|
||||||
} from './util/async-components';
|
} from './util/async-components';
|
||||||
|
|
||||||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
||||||
|
@ -208,6 +209,7 @@ export default class UI extends React.PureComponent {
|
||||||
|
|
||||||
<WrappedRoute path='/notifications' component={Notifications} content={children} />
|
<WrappedRoute path='/notifications' component={Notifications} content={children} />
|
||||||
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
||||||
|
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
|
||||||
|
|
||||||
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
|
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
|
||||||
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
|
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
|
||||||
|
|
|
@ -34,6 +34,10 @@ export function GettingStarted () {
|
||||||
return import(/* webpackChunkName: "features/getting_started" */'../../getting_started');
|
return import(/* webpackChunkName: "features/getting_started" */'../../getting_started');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function PinnedStatuses () {
|
||||||
|
return import(/* webpackChunkName: "features/pinned_statuses" */'../../pinned_statuses');
|
||||||
|
}
|
||||||
|
|
||||||
export function AccountTimeline () {
|
export function AccountTimeline () {
|
||||||
return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline');
|
return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline');
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
"column.mutes": "Muted users",
|
"column.mutes": "Muted users",
|
||||||
"column.notifications": "Notifications",
|
"column.notifications": "Notifications",
|
||||||
"column.public": "Federated timeline",
|
"column.public": "Federated timeline",
|
||||||
|
"column.pins": "Pinned toots",
|
||||||
"column_back_button.label": "Back",
|
"column_back_button.label": "Back",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"column_header.hide_settings": "Hide settings",
|
||||||
"column_header.moveLeft_settings": "Move column to the left",
|
"column_header.moveLeft_settings": "Move column to the left",
|
||||||
|
@ -111,6 +112,7 @@
|
||||||
"navigation_bar.mutes": "Muted users",
|
"navigation_bar.mutes": "Muted users",
|
||||||
"navigation_bar.preferences": "Preferences",
|
"navigation_bar.preferences": "Preferences",
|
||||||
"navigation_bar.public_timeline": "Federated timeline",
|
"navigation_bar.public_timeline": "Federated timeline",
|
||||||
|
"navigation_bar.pins": "Pinned toots",
|
||||||
"notification.favourite": "{name} favourited your status",
|
"notification.favourite": "{name} favourited your status",
|
||||||
"notification.follow": "{name} followed you",
|
"notification.follow": "{name} followed you",
|
||||||
"notification.mention": "{name} mentioned you",
|
"notification.mention": "{name} mentioned you",
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
"column.mutes": "ミュートしたユーザー",
|
"column.mutes": "ミュートしたユーザー",
|
||||||
"column.notifications": "通知",
|
"column.notifications": "通知",
|
||||||
"column.public": "連合タイムライン",
|
"column.public": "連合タイムライン",
|
||||||
|
"column.pins": "固定されたトゥート",
|
||||||
"column_back_button.label": "戻る",
|
"column_back_button.label": "戻る",
|
||||||
"column_header.hide_settings": "設定を隠す",
|
"column_header.hide_settings": "設定を隠す",
|
||||||
"column_header.moveLeft_settings": "カラムを左に移動する",
|
"column_header.moveLeft_settings": "カラムを左に移動する",
|
||||||
|
@ -111,6 +112,7 @@
|
||||||
"navigation_bar.mutes": "ミュートしたユーザー",
|
"navigation_bar.mutes": "ミュートしたユーザー",
|
||||||
"navigation_bar.preferences": "ユーザー設定",
|
"navigation_bar.preferences": "ユーザー設定",
|
||||||
"navigation_bar.public_timeline": "連合タイムライン",
|
"navigation_bar.public_timeline": "連合タイムライン",
|
||||||
|
"navigation_bar.pins": "固定されたトゥート",
|
||||||
"notification.favourite": "{name}さんがあなたのトゥートをお気に入りに登録しました",
|
"notification.favourite": "{name}さんがあなたのトゥートをお気に入りに登録しました",
|
||||||
"notification.follow": "{name}さんにフォローされました",
|
"notification.follow": "{name}さんにフォローされました",
|
||||||
"notification.mention": "{name}さんがあなたに返信しました",
|
"notification.mention": "{name}さんがあなたに返信しました",
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
"column.mutes": "뮤트 중인 사용자",
|
"column.mutes": "뮤트 중인 사용자",
|
||||||
"column.notifications": "알림",
|
"column.notifications": "알림",
|
||||||
"column.public": "연합 타임라인",
|
"column.public": "연합 타임라인",
|
||||||
|
"column.pins": "고정된 Toot",
|
||||||
"column_back_button.label": "돌아가기",
|
"column_back_button.label": "돌아가기",
|
||||||
"column_header.hide_settings": "Hide settings",
|
"column_header.hide_settings": "Hide settings",
|
||||||
"column_header.moveLeft_settings": "Move column to the left",
|
"column_header.moveLeft_settings": "Move column to the left",
|
||||||
|
@ -111,6 +112,7 @@
|
||||||
"navigation_bar.mutes": "뮤트 중인 사용자",
|
"navigation_bar.mutes": "뮤트 중인 사용자",
|
||||||
"navigation_bar.preferences": "사용자 설정",
|
"navigation_bar.preferences": "사용자 설정",
|
||||||
"navigation_bar.public_timeline": "연합 타임라인",
|
"navigation_bar.public_timeline": "연합 타임라인",
|
||||||
|
"navigation_bar.pins": "고정된 Toot",
|
||||||
"notification.favourite": "{name}님이 즐겨찾기 했습니다",
|
"notification.favourite": "{name}님이 즐겨찾기 했습니다",
|
||||||
"notification.follow": "{name}님이 나를 팔로우 했습니다",
|
"notification.follow": "{name}님이 나를 팔로우 했습니다",
|
||||||
"notification.mention": "{name}님이 답글을 보냈습니다",
|
"notification.mention": "{name}님이 답글을 보냈습니다",
|
||||||
|
|
|
@ -2,10 +2,15 @@ import {
|
||||||
FAVOURITED_STATUSES_FETCH_SUCCESS,
|
FAVOURITED_STATUSES_FETCH_SUCCESS,
|
||||||
FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
||||||
} from '../actions/favourites';
|
} from '../actions/favourites';
|
||||||
|
import {
|
||||||
|
PINNED_STATUSES_FETCH_SUCCESS,
|
||||||
|
} from '../actions/pin_statuses';
|
||||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||||
import {
|
import {
|
||||||
FAVOURITE_SUCCESS,
|
FAVOURITE_SUCCESS,
|
||||||
UNFAVOURITE_SUCCESS,
|
UNFAVOURITE_SUCCESS,
|
||||||
|
PIN_SUCCESS,
|
||||||
|
UNPIN_SUCCESS,
|
||||||
} from '../actions/interactions';
|
} from '../actions/interactions';
|
||||||
|
|
||||||
const initialState = ImmutableMap({
|
const initialState = ImmutableMap({
|
||||||
|
@ -14,6 +19,11 @@ const initialState = ImmutableMap({
|
||||||
loaded: false,
|
loaded: false,
|
||||||
items: ImmutableList(),
|
items: ImmutableList(),
|
||||||
}),
|
}),
|
||||||
|
pins: ImmutableMap({
|
||||||
|
next: null,
|
||||||
|
loaded: false,
|
||||||
|
items: ImmutableList(),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const normalizeList = (state, listType, statuses, next) => {
|
const normalizeList = (state, listType, statuses, next) => {
|
||||||
|
@ -53,6 +63,12 @@ export default function statusLists(state = initialState, action) {
|
||||||
return prependOneToList(state, 'favourites', action.status);
|
return prependOneToList(state, 'favourites', action.status);
|
||||||
case UNFAVOURITE_SUCCESS:
|
case UNFAVOURITE_SUCCESS:
|
||||||
return removeOneFromList(state, 'favourites', action.status);
|
return removeOneFromList(state, 'favourites', action.status);
|
||||||
|
case PINNED_STATUSES_FETCH_SUCCESS:
|
||||||
|
return normalizeList(state, 'pins', action.statuses, action.next);
|
||||||
|
case PIN_SUCCESS:
|
||||||
|
return prependOneToList(state, 'pins', action.status);
|
||||||
|
case UNPIN_SUCCESS:
|
||||||
|
return removeOneFromList(state, 'pins', action.status);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,9 @@ import {
|
||||||
FAVOURITED_STATUSES_FETCH_SUCCESS,
|
FAVOURITED_STATUSES_FETCH_SUCCESS,
|
||||||
FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
||||||
} from '../actions/favourites';
|
} from '../actions/favourites';
|
||||||
|
import {
|
||||||
|
PINNED_STATUSES_FETCH_SUCCESS,
|
||||||
|
} from '../actions/pin_statuses';
|
||||||
import { SEARCH_FETCH_SUCCESS } from '../actions/search';
|
import { SEARCH_FETCH_SUCCESS } from '../actions/search';
|
||||||
import emojify from '../emoji';
|
import emojify from '../emoji';
|
||||||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||||
|
@ -138,6 +141,7 @@ export default function statuses(state = initialState, action) {
|
||||||
case NOTIFICATIONS_EXPAND_SUCCESS:
|
case NOTIFICATIONS_EXPAND_SUCCESS:
|
||||||
case FAVOURITED_STATUSES_FETCH_SUCCESS:
|
case FAVOURITED_STATUSES_FETCH_SUCCESS:
|
||||||
case FAVOURITED_STATUSES_EXPAND_SUCCESS:
|
case FAVOURITED_STATUSES_EXPAND_SUCCESS:
|
||||||
|
case PINNED_STATUSES_FETCH_SUCCESS:
|
||||||
case SEARCH_FETCH_SUCCESS:
|
case SEARCH_FETCH_SUCCESS:
|
||||||
return normalizeStatuses(state, action.statuses);
|
return normalizeStatuses(state, action.statuses);
|
||||||
case TIMELINE_DELETE:
|
case TIMELINE_DELETE:
|
||||||
|
|
Loading…
Reference in a new issue