diff --git a/app/javascript/mastodon/actions/pin_statuses.js b/app/javascript/mastodon/actions/pin_statuses.js new file mode 100644 index 000000000..01bf8930b --- /dev/null +++ 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 index f8ea01024..973c8a4ae 100644 --- 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([ , + , ]); if (me.get('locked')) { - navItems.push(); + navItems.push(); } navItems = navItems.concat([ - , - , + , + , ]); return ( diff --git a/app/javascript/mastodon/features/pinned_statuses/index.js b/app/javascript/mastodon/features/pinned_statuses/index.js new file mode 100644 index 000000000..b4a6c1e52 --- /dev/null +++ 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 ( + + + + + ); + } + +} diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 8f971ae67..41ce0f8b5 100644 --- 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 ends up in the application bundle. @@ -208,6 +209,7 @@ export default class UI 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 0882beb7f..0b4e2df22 100644 --- 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 index 6d9b9c208..f42851f45 100644 --- 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 index 560d2b668..65838a3f8 100644 --- 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 index 7d573506c..8393e82e5 100644 --- 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 index 2ce27a454..c4aeb338f 100644 --- 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 index 38691dc43..eec2a5f16 100644 --- 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: