commit: 85c7c42098b5a5b10033eb8de79f6cb5dce8d462
parent: 8185f988723dd95b814bcc5f03ccb3da94edbae4
Author: voidSatisfaction <lourie@naver.com>
Date: Thu, 7 Sep 2017 16:58:11 +0900
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
Diffstat:
10 files changed, 135 insertions(+), 3 deletions(-)
diff --git a/app/javascript/mastodon/actions/pin_statuses.js b/app/javascript/mastodon/actions/pin_statuses.js
@@ -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,
+ };
+};
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js
@@ -23,6 +23,7 @@ const messages = defineMessages({
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' },
+ pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
});
const mapStateToProps = state => ({
@@ -66,15 +67,16 @@ export default class GettingStarted extends ImmutablePureComponent {
navItems = navItems.concat([
<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')) {
- 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([
- <ColumnLink key='6' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />,
- <ColumnLink key='7' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />,
+ <ColumnLink key='7' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />,
+ <ColumnLink key='8' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />,
]);
return (
diff --git a/app/javascript/mastodon/features/pinned_statuses/index.js b/app/javascript/mastodon/features/pinned_statuses/index.js
@@ -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>
+ );
+ }
+
+}
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
@@ -35,6 +35,7 @@ import {
FavouritedStatuses,
Blocks,
Mutes,
+ PinnedStatuses,
} from './util/async-components';
// 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='/favourites' component={FavouritedStatuses} content={children} />
+ <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js
@@ -34,6 +34,10 @@ export function GettingStarted () {
return import(/* webpackChunkName: "features/getting_started" */'../../getting_started');
}
+export function PinnedStatuses () {
+ return import(/* webpackChunkName: "features/pinned_statuses" */'../../pinned_statuses');
+}
+
export function AccountTimeline () {
return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline');
}
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
@@ -34,6 +34,7 @@
"column.mutes": "Muted users",
"column.notifications": "Notifications",
"column.public": "Federated timeline",
+ "column.pins": "Pinned toots",
"column_back_button.label": "Back",
"column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Move column to the left",
@@ -111,6 +112,7 @@
"navigation_bar.mutes": "Muted users",
"navigation_bar.preferences": "Preferences",
"navigation_bar.public_timeline": "Federated timeline",
+ "navigation_bar.pins": "Pinned toots",
"notification.favourite": "{name} favourited your status",
"notification.follow": "{name} followed you",
"notification.mention": "{name} mentioned you",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
@@ -34,6 +34,7 @@
"column.mutes": "ミュートしたユーザー",
"column.notifications": "通知",
"column.public": "連合タイムライン",
+ "column.pins": "固定されたトゥート",
"column_back_button.label": "戻る",
"column_header.hide_settings": "設定を隠す",
"column_header.moveLeft_settings": "カラムを左に移動する",
@@ -111,6 +112,7 @@
"navigation_bar.mutes": "ミュートしたユーザー",
"navigation_bar.preferences": "ユーザー設定",
"navigation_bar.public_timeline": "連合タイムライン",
+ "navigation_bar.pins": "固定されたトゥート",
"notification.favourite": "{name}さんがあなたのトゥートをお気に入りに登録しました",
"notification.follow": "{name}さんにフォローされました",
"notification.mention": "{name}さんがあなたに返信しました",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
@@ -34,6 +34,7 @@
"column.mutes": "뮤트 중인 사용자",
"column.notifications": "알림",
"column.public": "연합 타임라인",
+ "column.pins": "고정된 Toot",
"column_back_button.label": "돌아가기",
"column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Move column to the left",
@@ -111,6 +112,7 @@
"navigation_bar.mutes": "뮤트 중인 사용자",
"navigation_bar.preferences": "사용자 설정",
"navigation_bar.public_timeline": "연합 타임라인",
+ "navigation_bar.pins": "고정된 Toot",
"notification.favourite": "{name}님이 즐겨찾기 했습니다",
"notification.follow": "{name}님이 나를 팔로우 했습니다",
"notification.mention": "{name}님이 답글을 보냈습니다",
diff --git a/app/javascript/mastodon/reducers/status_lists.js b/app/javascript/mastodon/reducers/status_lists.js
@@ -2,10 +2,15 @@ import {
FAVOURITED_STATUSES_FETCH_SUCCESS,
FAVOURITED_STATUSES_EXPAND_SUCCESS,
} from '../actions/favourites';
+import {
+ PINNED_STATUSES_FETCH_SUCCESS,
+} from '../actions/pin_statuses';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import {
FAVOURITE_SUCCESS,
UNFAVOURITE_SUCCESS,
+ PIN_SUCCESS,
+ UNPIN_SUCCESS,
} from '../actions/interactions';
const initialState = ImmutableMap({
@@ -14,6 +19,11 @@ const initialState = ImmutableMap({
loaded: false,
items: ImmutableList(),
}),
+ pins: ImmutableMap({
+ next: null,
+ loaded: false,
+ items: ImmutableList(),
+ }),
});
const normalizeList = (state, listType, statuses, next) => {
@@ -53,6 +63,12 @@ export default function statusLists(state = initialState, action) {
return prependOneToList(state, 'favourites', action.status);
case UNFAVOURITE_SUCCESS:
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:
return state;
}
diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js
@@ -36,6 +36,9 @@ import {
FAVOURITED_STATUSES_FETCH_SUCCESS,
FAVOURITED_STATUSES_EXPAND_SUCCESS,
} from '../actions/favourites';
+import {
+ PINNED_STATUSES_FETCH_SUCCESS,
+} from '../actions/pin_statuses';
import { SEARCH_FETCH_SUCCESS } from '../actions/search';
import emojify from '../emoji';
import { Map as ImmutableMap, fromJS } from 'immutable';
@@ -138,6 +141,7 @@ export default function statuses(state = initialState, action) {
case NOTIFICATIONS_EXPAND_SUCCESS:
case FAVOURITED_STATUSES_FETCH_SUCCESS:
case FAVOURITED_STATUSES_EXPAND_SUCCESS:
+ case PINNED_STATUSES_FETCH_SUCCESS:
case SEARCH_FETCH_SUCCESS:
return normalizeStatuses(state, action.statuses);
case TIMELINE_DELETE: