diff --git a/app/soapbox/actions/scheduled_statuses.js b/app/soapbox/actions/scheduled_statuses.js
new file mode 100644
index 000000000..4e54846b8
--- /dev/null
+++ b/app/soapbox/actions/scheduled_statuses.js
@@ -0,0 +1,87 @@
+import api, { getLinks } from '../api';
+
+export const SCHEDULED_STATUSES_FETCH_REQUEST = 'SCHEDULED_STATUSES_FETCH_REQUEST';
+export const SCHEDULED_STATUSES_FETCH_SUCCESS = 'SCHEDULED_STATUSES_FETCH_SUCCESS';
+export const SCHEDULED_STATUSES_FETCH_FAIL = 'SCHEDULED_STATUSES_FETCH_FAIL';
+
+export const SCHEDULED_STATUSES_EXPAND_REQUEST = 'SCHEDULED_STATUSES_EXPAND_REQUEST';
+export const SCHEDULED_STATUSES_EXPAND_SUCCESS = 'SCHEDULED_STATUSES_EXPAND_SUCCESS';
+export const SCHEDULED_STATUSES_EXPAND_FAIL = 'SCHEDULED_STATUSES_EXPAND_FAIL';
+
+export function fetchScheduledStatuses() {
+ return (dispatch, getState) => {
+ if (getState().getIn(['status_lists', 'scheduled_statuses', 'isLoading'])) {
+ return;
+ }
+
+ dispatch(fetchScheduledStatusesRequest());
+
+ api(getState).get('/api/v1/scheduled_statuses').then(response => {
+ const next = getLinks(response).refs.find(link => link.rel === 'next');
+ dispatch(fetchScheduledStatusesSuccess(response.data, next ? next.uri : null));
+ }).catch(error => {
+ dispatch(fetchScheduledStatusesFail(error));
+ });
+ };
+};
+
+export function fetchScheduledStatusesRequest() {
+ return {
+ type: SCHEDULED_STATUSES_FETCH_REQUEST,
+ };
+};
+
+export function fetchScheduledStatusesSuccess(statuses, next) {
+ return {
+ type: SCHEDULED_STATUSES_FETCH_SUCCESS,
+ statuses,
+ next,
+ };
+};
+
+export function fetchScheduledStatusesFail(error) {
+ return {
+ type: SCHEDULED_STATUSES_FETCH_FAIL,
+ error,
+ };
+};
+
+export function expandScheduledStatuses() {
+ return (dispatch, getState) => {
+ const url = getState().getIn(['status_lists', 'scheduled_statuses', 'next'], null);
+
+ if (url === null || getState().getIn(['status_lists', 'scheduled_statuses', 'isLoading'])) {
+ return;
+ }
+
+ dispatch(expandScheduledStatusesRequest());
+
+ api(getState).get(url).then(response => {
+ const next = getLinks(response).refs.find(link => link.rel === 'next');
+ dispatch(expandScheduledStatusesSuccess(response.data, next ? next.uri : null));
+ }).catch(error => {
+ dispatch(expandScheduledStatusesFail(error));
+ });
+ };
+};
+
+export function expandScheduledStatusesRequest() {
+ return {
+ type: SCHEDULED_STATUSES_EXPAND_REQUEST,
+ };
+};
+
+export function expandScheduledStatusesSuccess(statuses, next) {
+ return {
+ type: SCHEDULED_STATUSES_EXPAND_SUCCESS,
+ statuses,
+ next,
+ };
+};
+
+export function expandScheduledStatusesFail(error) {
+ return {
+ type: SCHEDULED_STATUSES_EXPAND_FAIL,
+ error,
+ };
+};
diff --git a/app/soapbox/features/scheduled_statuses/index.js b/app/soapbox/features/scheduled_statuses/index.js
new file mode 100644
index 000000000..ace5684bd
--- /dev/null
+++ b/app/soapbox/features/scheduled_statuses/index.js
@@ -0,0 +1,74 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import Column from '../ui/components/column';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import StatusList from '../../components/status_list';
+import { fetchScheduledStatuses, expandScheduledStatuses } from '../../actions/scheduled_statuses';
+import { debounce } from 'lodash';
+
+const messages = defineMessages({
+ heading: { id: 'column.scheduled_statuses', defaultMessage: 'Scheduled Statuses' },
+});
+
+const mapStateToProps = state => ({
+ statusIds: state.getIn(['status_lists', 'scheduled_statuses', 'items']),
+ isLoading: state.getIn(['status_lists', 'scheduled_statuses', 'isLoading'], true),
+ hasMore: !!state.getIn(['status_lists', 'scheduled_statuses', 'next']),
+});
+
+export default @connect(mapStateToProps)
+@injectIntl
+class ScheduledStatuses extends ImmutablePureComponent {
+
+ static contextTypes = {
+ router: PropTypes.object,
+ };
+
+ static propTypes = {
+ dispatch: PropTypes.func.isRequired,
+ shouldUpdateScroll: PropTypes.func,
+ statusIds: ImmutablePropTypes.list.isRequired,
+ intl: PropTypes.object.isRequired,
+ columnId: PropTypes.string,
+ multiColumn: PropTypes.bool,
+ hasMore: PropTypes.bool,
+ isLoading: PropTypes.bool,
+ };
+
+ componentDidMount() {
+ const { dispatch } = this.props;
+ dispatch(fetchScheduledStatuses());
+ }
+
+ handleLoadMore = debounce(() => {
+ this.props.dispatch(expandScheduledStatuses());
+ }, 300, { leading: true })
+
+
+ render() {
+ const { intl, shouldUpdateScroll, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
+ const pinned = !!columnId;
+
+ const emptyMessage = ;
+
+ return (
+
+
+
+ );
+ }
+
+}
diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js
index 3dffbf238..cdd0652dc 100644
--- a/app/soapbox/features/ui/index.js
+++ b/app/soapbox/features/ui/index.js
@@ -100,6 +100,7 @@ import {
Reports,
ModerationLog,
CryptoDonate,
+ ScheduledStatuses,
} from './util/async-components';
// Dummy import, to make sure that ends up in the application bundle.
@@ -300,6 +301,7 @@ class SwitchingColumnsArea extends React.PureComponent {
+
diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js
index dc06d1677..c3c5dc9d7 100644
--- a/app/soapbox/features/ui/util/async-components.js
+++ b/app/soapbox/features/ui/util/async-components.js
@@ -233,3 +233,7 @@ export function ModerationLog() {
export function CryptoDonate() {
return import(/* webpackChunkName: "features/crypto_donate" */'../../crypto_donate');
}
+
+export function ScheduledStatuses() {
+ return import(/* webpackChunkName: "features/scheduled_statuses" */'../../scheduled_statuses');
+}
diff --git a/app/soapbox/reducers/scheduled_statuses.js b/app/soapbox/reducers/scheduled_statuses.js
new file mode 100644
index 000000000..9441406aa
--- /dev/null
+++ b/app/soapbox/reducers/scheduled_statuses.js
@@ -0,0 +1,45 @@
+import { Map as ImmutableMap, fromJS } from 'immutable';
+
+const importStatus = (state, status) => state.set(status.id, fromJS(status));
+
+const importStatuses = (state, statuses) =>
+ state.withMutations(mutable => statuses.forEach(status => importStatus(mutable, status)));
+
+const deleteStatus = (state, id, references) => {
+ references.forEach(ref => {
+ state = deleteStatus(state, ref[0], []);
+ });
+
+ return state.delete(id);
+};
+
+const initialState = ImmutableMap();
+
+export default function statuses(state = initialState, action) {
+ switch(action.type) {
+ case STATUS_IMPORT:
+ return importStatus(state, action.status);
+ case STATUSES_IMPORT:
+ return importStatuses(state, action.statuses);
+ case STATUS_REVEAL:
+ return state.withMutations(map => {
+ action.ids.forEach(id => {
+ if (!(state.get(id) === undefined)) {
+ map.setIn([id, 'hidden'], false);
+ }
+ });
+ });
+ case STATUS_HIDE:
+ return state.withMutations(map => {
+ action.ids.forEach(id => {
+ if (!(state.get(id) === undefined)) {
+ map.setIn([id, 'hidden'], true);
+ }
+ });
+ });
+ case TIMELINE_DELETE:
+ return deleteStatus(state, action.id, action.references);
+ default:
+ return state;
+ }
+};
diff --git a/app/soapbox/reducers/status_lists.js b/app/soapbox/reducers/status_lists.js
index 9f8f28dee..3471eca34 100644
--- a/app/soapbox/reducers/status_lists.js
+++ b/app/soapbox/reducers/status_lists.js
@@ -26,6 +26,14 @@ import {
PIN_SUCCESS,
UNPIN_SUCCESS,
} from '../actions/interactions';
+import {
+ SCHEDULED_STATUSES_FETCH_REQUEST,
+ SCHEDULED_STATUSES_FETCH_SUCCESS,
+ SCHEDULED_STATUSES_FETCH_FAIL,
+ SCHEDULED_STATUSES_EXPAND_REQUEST,
+ SCHEDULED_STATUSES_EXPAND_SUCCESS,
+ SCHEDULED_STATUSES_EXPAND_FAIL,
+} from '../actions/scheduled_statuses';
const initialState = ImmutableMap({
favourites: ImmutableMap({
@@ -110,6 +118,16 @@ export default function statusLists(state = initialState, action) {
return prependOneToList(state, 'pins', action.status);
case UNPIN_SUCCESS:
return removeOneFromList(state, 'pins', action.status);
+ case SCHEDULED_STATUSES_FETCH_REQUEST:
+ case SCHEDULED_STATUSES_EXPAND_REQUEST:
+ return state.setIn(['scheduled_statuses', 'isLoading'], true);
+ case SCHEDULED_STATUSES_FETCH_FAIL:
+ case SCHEDULED_STATUSES_EXPAND_FAIL:
+ return state.setIn(['scheduled_statuses', 'isLoading'], false);
+ case SCHEDULED_STATUSES_FETCH_SUCCESS:
+ return normalizeList(state, 'scheduled_statuses', action.statuses, action.next);
+ case SCHEDULED_STATUSES_EXPAND_SUCCESS:
+ return appendToList(state, 'scheduled_statuses', action.statuses, action.next);
default:
return state;
}