diff --git a/app/soapbox/actions/scheduled_statuses.js b/app/soapbox/actions/scheduled_statuses.js
index 4e54846b8..5d5b493f1 100644
--- a/app/soapbox/actions/scheduled_statuses.js
+++ b/app/soapbox/actions/scheduled_statuses.js
@@ -8,6 +8,10 @@ export const SCHEDULED_STATUSES_EXPAND_REQUEST = 'SCHEDULED_STATUSES_EXPAND_REQU
export const SCHEDULED_STATUSES_EXPAND_SUCCESS = 'SCHEDULED_STATUSES_EXPAND_SUCCESS';
export const SCHEDULED_STATUSES_EXPAND_FAIL = 'SCHEDULED_STATUSES_EXPAND_FAIL';
+export const SCHEDULED_STATUS_CANCEL_REQUEST = 'SCHEDULED_STATUS_CANCEL_REQUEST';
+export const SCHEDULED_STATUS_CANCEL_SUCCESS = 'SCHEDULED_STATUS_CANCEL_SUCCESS';
+export const SCHEDULED_STATUS_CANCEL_FAIL = 'SCHEDULED_STATUS_CANCEL_FAIL';
+
export function fetchScheduledStatuses() {
return (dispatch, getState) => {
if (getState().getIn(['status_lists', 'scheduled_statuses', 'isLoading'])) {
@@ -25,6 +29,17 @@ export function fetchScheduledStatuses() {
};
};
+export function cancelScheduledStatus(id) {
+ return (dispatch, getState) => {
+ dispatch({ type: SCHEDULED_STATUS_CANCEL_REQUEST, id });
+ api(getState).delete(`/api/v1/scheduled_statuses/${id}`).then(({ data }) => {
+ dispatch({ type: SCHEDULED_STATUS_CANCEL_SUCCESS, id, data });
+ }).catch(error => {
+ dispatch({ type: SCHEDULED_STATUS_CANCEL_FAIL, id, error });
+ });
+ };
+}
+
export function fetchScheduledStatusesRequest() {
return {
type: SCHEDULED_STATUSES_FETCH_REQUEST,
diff --git a/app/soapbox/features/scheduled_statuses/builder.js b/app/soapbox/features/scheduled_statuses/builder.js
new file mode 100644
index 000000000..fd0772fe3
--- /dev/null
+++ b/app/soapbox/features/scheduled_statuses/builder.js
@@ -0,0 +1,45 @@
+import { fromJS } from 'immutable';
+import { normalizeStatus } from 'soapbox/actions/importer/normalizer';
+import { makeGetAccount } from 'soapbox/selectors';
+
+export const buildStatus = (state, scheduledStatus) => {
+ const getAccount = makeGetAccount();
+
+ const me = state.get('me');
+ const params = scheduledStatus.get('params');
+ const account = getAccount(state, me);
+
+ const status = normalizeStatus({
+ account,
+ application: null,
+ bookmarked: false,
+ card: null,
+ content: params.get('text'),
+ created_at: params.get('scheduled_at'),
+ emojis: [],
+ favourited: false,
+ favourites_count: 0,
+ id: scheduledStatus.get('id'),
+ in_reply_to_account_id: null,
+ in_reply_to_id: params.get('in_reply_to_id'),
+ language: null,
+ media_attachments: scheduledStatus.get('media_attachments'),
+ mentions: [],
+ muted: false,
+ pinned: false,
+ poll: params.get('poll'),
+ reblog: null,
+ reblogged: false,
+ reblogs_count: 0,
+ replies_count: 0,
+ sensitive: params.get('sensitive'),
+ spoiler_text: '',
+ tags: [],
+ text: null,
+ uri: `/scheduled_statuses/${scheduledStatus.get('id')}`,
+ url: `/scheduled_statuses/${scheduledStatus.get('id')}`,
+ visibility: params.get('visibility'),
+ });
+
+ return fromJS(status).set('account', account);
+};
diff --git a/app/soapbox/features/scheduled_statuses/components/scheduled_status.js b/app/soapbox/features/scheduled_statuses/components/scheduled_status.js
new file mode 100644
index 000000000..c0fd0ffd8
--- /dev/null
+++ b/app/soapbox/features/scheduled_statuses/components/scheduled_status.js
@@ -0,0 +1,90 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { FormattedMessage } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import StatusContent from 'soapbox/components/status_content';
+import { buildStatus } from '../builder';
+import classNames from 'classnames';
+import RelativeTimestamp from 'soapbox/components/relative_timestamp';
+import { Link, NavLink } from 'react-router-dom';
+import { getDomain } from 'soapbox/utils/accounts';
+import Avatar from 'soapbox/components/avatar';
+import DisplayName from 'soapbox/components/display_name';
+import AttachmentList from 'soapbox/components/attachment_list';
+import PollContainer from 'soapbox/containers/poll_container';
+import ScheduledStatusActionBar from './scheduled_status_action_bar';
+
+const mapStateToProps = (state, props) => {
+ const scheduledStatus = state.getIn(['scheduled_statuses', props.statusId]);
+ return {
+ status: buildStatus(state, scheduledStatus),
+ };
+};
+
+export default @connect(mapStateToProps)
+class ScheduledStatus extends ImmutablePureComponent {
+
+ render() {
+ const { status, showThread, account, ...other } = this.props;
+ if (!status.get('account')) return null;
+
+ const statusUrl = `/scheduled_statuses/${status.get('id')}`;
+ const favicon = status.getIn(['account', 'pleroma', 'favicon']);
+ const domain = getDomain(status.get('account'));
+
+ return (
+
+
+
+
+
+
+
+
+
+ {favicon &&
+
+
+

+
+
}
+
+
+
+
+
+
+
+
+ {status.get('poll') &&
}
+
+ {showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) && (
+
+ )}
+
+
+
+
+
+ );
+ }
+
+};
diff --git a/app/soapbox/features/scheduled_statuses/components/scheduled_status_action_bar.js b/app/soapbox/features/scheduled_statuses/components/scheduled_status_action_bar.js
new file mode 100644
index 000000000..509f01520
--- /dev/null
+++ b/app/soapbox/features/scheduled_statuses/components/scheduled_status_action_bar.js
@@ -0,0 +1,58 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types';
+import IconButton from 'soapbox/components/icon_button';
+import { defineMessages, injectIntl } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { cancelScheduledStatus } from 'soapbox/actions/scheduled_statuses';
+
+const messages = defineMessages({
+ cancel: { id: 'schedled_status.cancel', defaultMessage: 'Cancel' },
+});
+
+const mapStateToProps = state => {
+ const me = state.get('me');
+ return {
+ me,
+ };
+};
+
+export default @connect(mapStateToProps, null, null, { forwardRef: true })
+@injectIntl
+class ScheduledStatusActionBar extends ImmutablePureComponent {
+
+ static contextTypes = {
+ router: PropTypes.object,
+ };
+
+ static propTypes = {
+ status: ImmutablePropTypes.map.isRequired,
+ intl: PropTypes.object.isRequired,
+ me: SoapboxPropTypes.me,
+ };
+
+ handleCancelClick = e => {
+ const { status, dispatch } = this.props;
+ dispatch(cancelScheduledStatus(status.get('id')));
+ }
+
+ render() {
+ const { intl } = this.props;
+
+ return (
+
+ );
+ }
+
+}
diff --git a/app/soapbox/features/scheduled_statuses/index.js b/app/soapbox/features/scheduled_statuses/index.js
index ace5684bd..0d293e7e0 100644
--- a/app/soapbox/features/scheduled_statuses/index.js
+++ b/app/soapbox/features/scheduled_statuses/index.js
@@ -5,8 +5,9 @@ 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 ScrollableList from 'soapbox/components/scrollable_list';
import { fetchScheduledStatuses, expandScheduledStatuses } from '../../actions/scheduled_statuses';
+import ScheduledStatus from './components/scheduled_status';
import { debounce } from 'lodash';
const messages = defineMessages({
@@ -29,11 +30,8 @@ class ScheduledStatuses extends ImmutablePureComponent {
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,
};
@@ -49,24 +47,19 @@ class ScheduledStatuses extends ImmutablePureComponent {
render() {
- const { intl, shouldUpdateScroll, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
- const pinned = !!columnId;
-
+ const { intl, statusIds, hasMore, isLoading } = this.props;
const emptyMessage = ;
return (
-
+ isLoading={isLoading}
+ hasMore={hasMore}
+ >
+ {statusIds.map(id => )}
+
);
}
diff --git a/app/soapbox/reducers/scheduled_statuses.js b/app/soapbox/reducers/scheduled_statuses.js
index e2ab142e4..2e304a8eb 100644
--- a/app/soapbox/reducers/scheduled_statuses.js
+++ b/app/soapbox/reducers/scheduled_statuses.js
@@ -1,4 +1,9 @@
import { STATUS_CREATE_SUCCESS } from 'soapbox/actions/statuses';
+import {
+ SCHEDULED_STATUSES_FETCH_SUCCESS,
+ SCHEDULED_STATUS_CANCEL_REQUEST,
+ SCHEDULED_STATUS_CANCEL_SUCCESS,
+} from 'soapbox/actions/scheduled_statuses';
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
import { Map as ImmutableMap, fromJS } from 'immutable';
@@ -10,6 +15,8 @@ const importStatus = (state, status) => {
const importStatuses = (state, statuses) =>
state.withMutations(mutable => statuses.forEach(status => importStatus(mutable, status)));
+const deleteStatus = (state, id) => state.delete(id);
+
const initialState = ImmutableMap();
export default function statuses(state = initialState, action) {
@@ -18,7 +25,11 @@ export default function statuses(state = initialState, action) {
case STATUS_CREATE_SUCCESS:
return importStatus(state, action.status);
case STATUSES_IMPORT:
+ case SCHEDULED_STATUSES_FETCH_SUCCESS:
return importStatuses(state, action.statuses);
+ case SCHEDULED_STATUS_CANCEL_REQUEST:
+ case SCHEDULED_STATUS_CANCEL_SUCCESS:
+ return deleteStatus(state, action.id);
default:
return state;
}
diff --git a/app/soapbox/reducers/status_lists.js b/app/soapbox/reducers/status_lists.js
index b5a96abe5..a9e5079b2 100644
--- a/app/soapbox/reducers/status_lists.js
+++ b/app/soapbox/reducers/status_lists.js
@@ -33,6 +33,8 @@ import {
SCHEDULED_STATUSES_EXPAND_REQUEST,
SCHEDULED_STATUSES_EXPAND_SUCCESS,
SCHEDULED_STATUSES_EXPAND_FAIL,
+ SCHEDULED_STATUS_CANCEL_REQUEST,
+ SCHEDULED_STATUS_CANCEL_SUCCESS,
} from '../actions/scheduled_statuses';
const initialState = ImmutableMap({
@@ -81,9 +83,9 @@ const prependOneToList = (state, listType, status) => {
}));
};
-const removeOneFromList = (state, listType, status) => {
+const removeOneFromList = (state, listType, statusId) => {
return state.update(listType, listMap => listMap.withMutations(map => {
- map.set('items', map.get('items').filter(item => item !== status.get('id')));
+ map.set('items', map.get('items').filter(item => item !== statusId));
}));
};
@@ -133,6 +135,9 @@ export default function statusLists(state = initialState, action) {
return normalizeList(state, 'scheduled_statuses', action.statuses, action.next);
case SCHEDULED_STATUSES_EXPAND_SUCCESS:
return appendToList(state, 'scheduled_statuses', action.statuses, action.next);
+ case SCHEDULED_STATUS_CANCEL_REQUEST:
+ case SCHEDULED_STATUS_CANCEL_SUCCESS:
+ return removeOneFromList(state, 'scheduled_statuses', action.id || action.status.get('id'));
default:
return state;
}
diff --git a/app/styles/components/detailed-status.scss b/app/styles/components/detailed-status.scss
index 5bf63db28..4cbaad0ed 100644
--- a/app/styles/components/detailed-status.scss
+++ b/app/styles/components/detailed-status.scss
@@ -73,7 +73,10 @@
.detailed-status__button {
padding: 10px 0;
+}
+.status__button,
+.detailed-status__button {
.icon-button {
display: inline-flex;
align-items: center;