kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Merge branch 'scheduled-statuses-improvements' into 'develop'
Scheduled statuses improvements See merge request soapbox-pub/soapbox-fe!552groups
commit
7a86a4809e
|
@ -14,6 +14,7 @@ import { getSettings } from './settings';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
import { uploadMedia } from './media';
|
import { uploadMedia } from './media';
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
import { createStatus } from './statuses';
|
||||||
|
|
||||||
let cancelFetchComposeSuggestionsAccounts;
|
let cancelFetchComposeSuggestionsAccounts;
|
||||||
|
|
||||||
|
@ -134,11 +135,11 @@ export function directCompose(account, routerHistory) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function handleComposeSubmit(dispatch, getState, response, status) {
|
export function handleComposeSubmit(dispatch, getState, data, status) {
|
||||||
if (!dispatch || !getState) return;
|
if (!dispatch || !getState) return;
|
||||||
|
|
||||||
dispatch(insertIntoTagHistory(response.data.tags || [], status));
|
dispatch(insertIntoTagHistory(data.tags || [], status));
|
||||||
dispatch(submitComposeSuccess({ ...response.data }));
|
dispatch(submitComposeSuccess({ ...data }));
|
||||||
|
|
||||||
// To make the app more responsive, immediately push the status into the columns
|
// To make the app more responsive, immediately push the status into the columns
|
||||||
const insertIfOnline = timelineId => {
|
const insertIfOnline = timelineId => {
|
||||||
|
@ -148,64 +149,71 @@ export function handleComposeSubmit(dispatch, getState, response, status) {
|
||||||
let dequeueArgs = {};
|
let dequeueArgs = {};
|
||||||
if (timelineId === 'community') dequeueArgs.onlyMedia = getSettings(getState()).getIn(['community', 'other', 'onlyMedia']);
|
if (timelineId === 'community') dequeueArgs.onlyMedia = getSettings(getState()).getIn(['community', 'other', 'onlyMedia']);
|
||||||
dispatch(dequeueTimeline(timelineId, null, dequeueArgs));
|
dispatch(dequeueTimeline(timelineId, null, dequeueArgs));
|
||||||
dispatch(updateTimeline(timelineId, response.data.id));
|
dispatch(updateTimeline(timelineId, data.id));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (response.data.visibility !== 'direct') {
|
if (data.visibility !== 'direct') {
|
||||||
insertIfOnline('home');
|
insertIfOnline('home');
|
||||||
} else if (response.data.visibility === 'public') {
|
} else if (data.visibility === 'public') {
|
||||||
insertIfOnline('community');
|
insertIfOnline('community');
|
||||||
insertIfOnline('public');
|
insertIfOnline('public');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function submitCompose(routerHistory, group) {
|
const needsDescriptions = state => {
|
||||||
|
const media = state.getIn(['compose', 'media_attachments']);
|
||||||
|
const missingDescriptionModal = getSettings(state).get('missingDescriptionModal');
|
||||||
|
|
||||||
|
const hasMissing = media.filter(item => !item.get('description')).size > 0;
|
||||||
|
|
||||||
|
return missingDescriptionModal && hasMissing;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function submitCompose(routerHistory, force = false) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
if (!isLoggedIn(getState)) return;
|
if (!isLoggedIn(getState)) return;
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
function onModalSubmitCompose() {
|
const status = state.getIn(['compose', 'text'], '');
|
||||||
dispatch(submitComposeRequest());
|
const media = state.getIn(['compose', 'media_attachments']);
|
||||||
dispatch(closeModal());
|
|
||||||
|
|
||||||
api(getState).post('/api/v1/statuses', {
|
|
||||||
status,
|
|
||||||
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
|
|
||||||
media_ids: media.map(item => item.get('id')),
|
|
||||||
sensitive: getState().getIn(['compose', 'sensitive']),
|
|
||||||
spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''),
|
|
||||||
visibility: getState().getIn(['compose', 'privacy']),
|
|
||||||
content_type: getState().getIn(['compose', 'content_type']),
|
|
||||||
poll: getState().getIn(['compose', 'poll'], null),
|
|
||||||
scheduled_at: getState().getIn(['compose', 'schedule'], null),
|
|
||||||
}, {
|
|
||||||
headers: {
|
|
||||||
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
|
|
||||||
},
|
|
||||||
}).then(function(response) {
|
|
||||||
if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) {
|
|
||||||
routerHistory.push('/messages');
|
|
||||||
}
|
|
||||||
handleComposeSubmit(dispatch, getState, response, status);
|
|
||||||
}).catch(function(error) {
|
|
||||||
dispatch(submitComposeFail(error));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = getState().getIn(['compose', 'text'], '');
|
|
||||||
const media = getState().getIn(['compose', 'media_attachments']);
|
|
||||||
|
|
||||||
if ((!status || !status.length) && media.size === 0) {
|
if ((!status || !status.length) && media.size === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const missingDescriptionModal = getSettings(getState()).get('missingDescriptionModal');
|
if (!force && needsDescriptions(state)) {
|
||||||
|
|
||||||
if (missingDescriptionModal && media.filter(item => !item.get('description')).size) {
|
|
||||||
dispatch(openModal('MISSING_DESCRIPTION', {
|
dispatch(openModal('MISSING_DESCRIPTION', {
|
||||||
onContinue: () => onModalSubmitCompose(),
|
onContinue: () => dispatch(submitCompose(routerHistory, true)),
|
||||||
}));
|
}));
|
||||||
} else onModalSubmitCompose();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(submitComposeRequest());
|
||||||
|
dispatch(closeModal());
|
||||||
|
|
||||||
|
const idempotencyKey = state.getIn(['compose', 'idempotencyKey']);
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
status,
|
||||||
|
in_reply_to_id: state.getIn(['compose', 'in_reply_to'], null),
|
||||||
|
media_ids: media.map(item => item.get('id')),
|
||||||
|
sensitive: state.getIn(['compose', 'sensitive']),
|
||||||
|
spoiler_text: state.getIn(['compose', 'spoiler_text'], ''),
|
||||||
|
visibility: state.getIn(['compose', 'privacy']),
|
||||||
|
content_type: state.getIn(['compose', 'content_type']),
|
||||||
|
poll: state.getIn(['compose', 'poll'], null),
|
||||||
|
scheduled_at: state.getIn(['compose', 'schedule'], null),
|
||||||
|
};
|
||||||
|
|
||||||
|
dispatch(createStatus(params, idempotencyKey)).then(function(data) {
|
||||||
|
if (data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) {
|
||||||
|
routerHistory.push('/messages');
|
||||||
|
}
|
||||||
|
handleComposeSubmit(dispatch, getState, data, status);
|
||||||
|
}).catch(function(error) {
|
||||||
|
dispatch(submitComposeFail(error));
|
||||||
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
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 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'])) {
|
||||||
|
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 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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
|
@ -6,6 +6,10 @@ import { importFetchedStatus, importFetchedStatuses, importAccount, importStatus
|
||||||
import { openModal } from './modal';
|
import { openModal } from './modal';
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
export const STATUS_CREATE_REQUEST = 'STATUS_CREATE_REQUEST';
|
||||||
|
export const STATUS_CREATE_SUCCESS = 'STATUS_CREATE_SUCCESS';
|
||||||
|
export const STATUS_CREATE_FAIL = 'STATUS_CREATE_FAIL';
|
||||||
|
|
||||||
export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST';
|
export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST';
|
||||||
export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS';
|
export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS';
|
||||||
export const STATUS_FETCH_FAIL = 'STATUS_FETCH_FAIL';
|
export const STATUS_FETCH_FAIL = 'STATUS_FETCH_FAIL';
|
||||||
|
@ -39,6 +43,22 @@ export function fetchStatusRequest(id, skipLoading) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function createStatus(params, idempotencyKey) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch({ type: STATUS_CREATE_REQUEST, params, idempotencyKey });
|
||||||
|
|
||||||
|
return api(getState).post('/api/v1/statuses', params, {
|
||||||
|
headers: { 'Idempotency-Key': idempotencyKey },
|
||||||
|
}).then(({ data: status }) => {
|
||||||
|
dispatch({ type: STATUS_CREATE_SUCCESS, status, params, idempotencyKey });
|
||||||
|
return status;
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch({ type: STATUS_CREATE_FAIL, error, params, idempotencyKey });
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
function getFromDB(dispatch, getState, accountIndex, index, id) {
|
function getFromDB(dispatch, getState, accountIndex, index, id) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const request = index.get(id);
|
const request = index.get(id);
|
||||||
|
|
|
@ -4,12 +4,13 @@ import Button from '../../../components/button';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
|
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
|
||||||
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
|
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
|
||||||
import AutosuggestInput from '../../../components/autosuggest_input';
|
import AutosuggestInput from '../../../components/autosuggest_input';
|
||||||
import PollButtonContainer from '../containers/poll_button_container';
|
import PollButtonContainer from '../containers/poll_button_container';
|
||||||
import UploadButtonContainer from '../containers/upload_button_container';
|
import UploadButtonContainer from '../containers/upload_button_container';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import SpoilerButtonContainer from '../containers/spoiler_button_container';
|
import SpoilerButtonContainer from '../containers/spoiler_button_container';
|
||||||
import MarkdownButtonContainer from '../containers/markdown_button_container';
|
import MarkdownButtonContainer from '../containers/markdown_button_container';
|
||||||
import ScheduleFormContainer from '../containers/schedule_form_container';
|
import ScheduleFormContainer from '../containers/schedule_form_container';
|
||||||
|
@ -25,6 +26,7 @@ import { length } from 'stringz';
|
||||||
import { countableText } from '../util/counter';
|
import { countableText } from '../util/counter';
|
||||||
import Icon from 'soapbox/components/icon';
|
import Icon from 'soapbox/components/icon';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
|
import Warning from '../components/warning';
|
||||||
|
|
||||||
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
|
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
|
||||||
|
|
||||||
|
@ -245,7 +247,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { intl, onPaste, showSearch, anyMedia, shouldCondense, autoFocus, isModalOpen, maxTootChars } = this.props;
|
const { intl, onPaste, showSearch, anyMedia, shouldCondense, autoFocus, isModalOpen, maxTootChars, scheduledStatusCount } = this.props;
|
||||||
const condensed = shouldCondense && !this.state.composeFocused && this.isEmpty() && !this.props.isUploading;
|
const condensed = shouldCondense && !this.state.composeFocused && this.isEmpty() && !this.props.isUploading;
|
||||||
const disabled = this.props.isSubmitting;
|
const disabled = this.props.isSubmitting;
|
||||||
const text = [this.props.spoilerText, countableText(this.props.text)].join('');
|
const text = [this.props.spoilerText, countableText(this.props.text)].join('');
|
||||||
|
@ -267,6 +269,25 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={composeClassNames} ref={this.setForm} onClick={this.handleClick}>
|
<div className={composeClassNames} ref={this.setForm} onClick={this.handleClick}>
|
||||||
|
{scheduledStatusCount > 0 && (
|
||||||
|
<Warning
|
||||||
|
message={(
|
||||||
|
<FormattedMessage
|
||||||
|
id='compose_form.scheduled_statuses.message'
|
||||||
|
defaultMessage='You have scheduled statuses. {click_here} to see them.'
|
||||||
|
values={{ click_here: (
|
||||||
|
<Link to='/scheduled_statuses'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='compose_form.scheduled_statuses.click_here'
|
||||||
|
defaultMessage='Click here'
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
) }}
|
||||||
|
/>)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<WarningContainer />
|
<WarningContainer />
|
||||||
|
|
||||||
{ !shouldCondense && <ReplyIndicatorContainer /> }
|
{ !shouldCondense && <ReplyIndicatorContainer /> }
|
||||||
|
|
|
@ -27,6 +27,7 @@ const mapStateToProps = state => ({
|
||||||
isModalOpen: state.get('modal').modalType === 'COMPOSE',
|
isModalOpen: state.get('modal').modalType === 'COMPOSE',
|
||||||
maxTootChars: state.getIn(['instance', 'max_toot_chars']),
|
maxTootChars: state.getIn(['instance', 'max_toot_chars']),
|
||||||
schedule: state.getIn(['instance', 'schedule']),
|
schedule: state.getIn(['instance', 'schedule']),
|
||||||
|
scheduledStatusCount: state.get('scheduled_statuses').size,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
|
|
@ -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);
|
||||||
|
};
|
|
@ -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 (
|
||||||
|
<div className='scheduled-status'>
|
||||||
|
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id') })} tabIndex={this.props.muted ? null : 0}>
|
||||||
|
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted })} data-id={status.get('id')}>
|
||||||
|
<div className='status__expand' onClick={this.handleExpandClick} role='presentation' />
|
||||||
|
<div className='status__info'>
|
||||||
|
<NavLink to={statusUrl} className='status__relative-time'>
|
||||||
|
<RelativeTimestamp timestamp={status.get('created_at')} futureDate />
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
{favicon &&
|
||||||
|
<div className='status__favicon'>
|
||||||
|
<Link to={`/timeline/${domain}`}>
|
||||||
|
<img src={favicon} alt='' title={domain} />
|
||||||
|
</Link>
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
<div className='status__profile'>
|
||||||
|
<div className='status__avatar'>
|
||||||
|
<NavLink to={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])}>
|
||||||
|
<Avatar account={status.get('account')} size={48} />
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
<NavLink to={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} className='status__display-name'>
|
||||||
|
<DisplayName account={status.get('account')} />
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<StatusContent
|
||||||
|
status={status}
|
||||||
|
expanded
|
||||||
|
collapsable
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AttachmentList
|
||||||
|
compact
|
||||||
|
media={status.get('media_attachments')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{status.get('poll') && <PollContainer pollId={status.get('poll')} />}
|
||||||
|
|
||||||
|
{showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) && (
|
||||||
|
<button className='status__content__read-more-button' onClick={this.handleClick}>
|
||||||
|
<FormattedMessage id='status.show_thread' defaultMessage='Show thread' />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ScheduledStatusActionBar status={status} account={account} {...other} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
|
@ -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 (
|
||||||
|
<div className='status__action-bar'>
|
||||||
|
<div className='status__button'>
|
||||||
|
<IconButton
|
||||||
|
title={intl.formatMessage(messages.cancel)}
|
||||||
|
text={intl.formatMessage(messages.cancel)}
|
||||||
|
icon='close'
|
||||||
|
onClick={this.handleCancelClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
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 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({
|
||||||
|
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,
|
||||||
|
statusIds: ImmutablePropTypes.list.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
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, statusIds, hasMore, isLoading } = this.props;
|
||||||
|
const emptyMessage = <FormattedMessage id='empty_column.scheduled_statuses' defaultMessage="You don't have any scheduled statuses yet. When you add one, it will show up here." />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column icon='edit' heading={intl.formatMessage(messages.heading)} backBtnSlim>
|
||||||
|
<ScrollableList
|
||||||
|
scrollKey='scheduled_statuses'
|
||||||
|
emptyMessage={emptyMessage}
|
||||||
|
isLoading={isLoading}
|
||||||
|
hasMore={hasMore}
|
||||||
|
>
|
||||||
|
{statusIds.map(id => <ScheduledStatus key={id} statusId={id} />)}
|
||||||
|
</ScrollableList>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ import { fetchChats } from 'soapbox/actions/chats';
|
||||||
import { clearHeight } from '../../actions/height_cache';
|
import { clearHeight } from '../../actions/height_cache';
|
||||||
import { openModal } from '../../actions/modal';
|
import { openModal } from '../../actions/modal';
|
||||||
import { fetchFollowRequests } from '../../actions/accounts';
|
import { fetchFollowRequests } from '../../actions/accounts';
|
||||||
|
import { fetchScheduledStatuses } from '../../actions/scheduled_statuses';
|
||||||
import { WrappedRoute } from './util/react_router_helpers';
|
import { WrappedRoute } from './util/react_router_helpers';
|
||||||
import UploadArea from './components/upload_area';
|
import UploadArea from './components/upload_area';
|
||||||
import TabsBar from './components/tabs_bar';
|
import TabsBar from './components/tabs_bar';
|
||||||
|
@ -100,6 +101,7 @@ import {
|
||||||
Reports,
|
Reports,
|
||||||
ModerationLog,
|
ModerationLog,
|
||||||
CryptoDonate,
|
CryptoDonate,
|
||||||
|
ScheduledStatuses,
|
||||||
} 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.
|
||||||
|
@ -300,6 +302,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
||||||
<WrappedRoute path='/@:username/posts/:statusId/reblogs' layout={LAYOUT.DEFAULT} component={Reblogs} content={children} />
|
<WrappedRoute path='/@:username/posts/:statusId/reblogs' layout={LAYOUT.DEFAULT} component={Reblogs} content={children} />
|
||||||
|
|
||||||
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||||
|
<WrappedRoute path='/scheduled_statuses' layout={LAYOUT.DEFAULT} component={ScheduledStatuses} content={children} />
|
||||||
|
|
||||||
<Redirect exact from='/settings' to='/settings/preferences' />
|
<Redirect exact from='/settings' to='/settings/preferences' />
|
||||||
<WrappedRoute path='/settings/preferences' layout={LAYOUT.DEFAULT} component={Preferences} content={children} />
|
<WrappedRoute path='/settings/preferences' layout={LAYOUT.DEFAULT} component={Preferences} content={children} />
|
||||||
|
@ -501,6 +504,8 @@ class UI extends React.PureComponent {
|
||||||
if (account.get('locked')) {
|
if (account.get('locked')) {
|
||||||
setTimeout(() => this.props.dispatch(fetchFollowRequests()), 700);
|
setTimeout(() => this.props.dispatch(fetchFollowRequests()), 700);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTimeout(() => this.props.dispatch(fetchScheduledStatuses()), 900);
|
||||||
}
|
}
|
||||||
this.connectStreaming();
|
this.connectStreaming();
|
||||||
}
|
}
|
||||||
|
|
|
@ -233,3 +233,7 @@ export function ModerationLog() {
|
||||||
export function CryptoDonate() {
|
export function CryptoDonate() {
|
||||||
return import(/* webpackChunkName: "features/crypto_donate" */'../../crypto_donate');
|
return import(/* webpackChunkName: "features/crypto_donate" */'../../crypto_donate');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ScheduledStatuses() {
|
||||||
|
return import(/* webpackChunkName: "features/scheduled_statuses" */'../../scheduled_statuses');
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,11 @@ describe('status_lists reducer', () => {
|
||||||
loaded: false,
|
loaded: false,
|
||||||
items: ImmutableList(),
|
items: ImmutableList(),
|
||||||
}),
|
}),
|
||||||
|
scheduled_statuses: ImmutableMap({
|
||||||
|
next: null,
|
||||||
|
loaded: false,
|
||||||
|
items: ImmutableList(),
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -52,6 +52,7 @@ import profile_hover_card from './profile_hover_card';
|
||||||
import backups from './backups';
|
import backups from './backups';
|
||||||
import admin_log from './admin_log';
|
import admin_log from './admin_log';
|
||||||
import security from './security';
|
import security from './security';
|
||||||
|
import scheduled_statuses from './scheduled_statuses';
|
||||||
|
|
||||||
const appReducer = combineReducers({
|
const appReducer = combineReducers({
|
||||||
dropdown_menu,
|
dropdown_menu,
|
||||||
|
@ -105,6 +106,7 @@ const appReducer = combineReducers({
|
||||||
backups,
|
backups,
|
||||||
admin_log,
|
admin_log,
|
||||||
security,
|
security,
|
||||||
|
scheduled_statuses,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear the state (mostly) when the user logs out
|
// Clear the state (mostly) when the user logs out
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
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';
|
||||||
|
|
||||||
|
const importStatus = (state, status) => {
|
||||||
|
if (!status.scheduled_at) return state;
|
||||||
|
return state.set(status.id, fromJS(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) {
|
||||||
|
switch(action.type) {
|
||||||
|
case STATUS_IMPORT:
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
|
@ -26,6 +26,16 @@ import {
|
||||||
PIN_SUCCESS,
|
PIN_SUCCESS,
|
||||||
UNPIN_SUCCESS,
|
UNPIN_SUCCESS,
|
||||||
} from '../actions/interactions';
|
} 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,
|
||||||
|
SCHEDULED_STATUS_CANCEL_REQUEST,
|
||||||
|
SCHEDULED_STATUS_CANCEL_SUCCESS,
|
||||||
|
} from '../actions/scheduled_statuses';
|
||||||
|
|
||||||
const initialState = ImmutableMap({
|
const initialState = ImmutableMap({
|
||||||
favourites: ImmutableMap({
|
favourites: ImmutableMap({
|
||||||
|
@ -43,6 +53,11 @@ const initialState = ImmutableMap({
|
||||||
loaded: false,
|
loaded: false,
|
||||||
items: ImmutableList(),
|
items: ImmutableList(),
|
||||||
}),
|
}),
|
||||||
|
scheduled_statuses: ImmutableMap({
|
||||||
|
next: null,
|
||||||
|
loaded: false,
|
||||||
|
items: ImmutableList(),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const normalizeList = (state, listType, statuses, next) => {
|
const normalizeList = (state, listType, statuses, next) => {
|
||||||
|
@ -68,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 => {
|
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));
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -110,6 +125,19 @@ export default function statusLists(state = initialState, action) {
|
||||||
return prependOneToList(state, 'pins', action.status);
|
return prependOneToList(state, 'pins', action.status);
|
||||||
case UNPIN_SUCCESS:
|
case UNPIN_SUCCESS:
|
||||||
return removeOneFromList(state, 'pins', action.status);
|
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);
|
||||||
|
case SCHEDULED_STATUS_CANCEL_REQUEST:
|
||||||
|
case SCHEDULED_STATUS_CANCEL_SUCCESS:
|
||||||
|
return removeOneFromList(state, 'scheduled_statuses', action.id || action.status.get('id'));
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
@import 'components/admin';
|
@import 'components/admin';
|
||||||
@import 'components/backups';
|
@import 'components/backups';
|
||||||
@import 'components/crypto-donate';
|
@import 'components/crypto-donate';
|
||||||
|
@import 'components/datepicker';
|
||||||
|
|
||||||
// Holiday
|
// Holiday
|
||||||
@import 'holiday/halloween';
|
@import 'holiday/halloween';
|
||||||
|
|
|
@ -377,16 +377,6 @@
|
||||||
}
|
}
|
||||||
} // end .compose-form
|
} // end .compose-form
|
||||||
|
|
||||||
.react-datepicker-wrapper {
|
|
||||||
margin-left: 10px;
|
|
||||||
background: var(--background-color);
|
|
||||||
border: 2px solid var(--brand-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker-popper {
|
|
||||||
background: var(--background-color) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-area {
|
.upload-area {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: rgba($base-overlay-background, 0.8);
|
background: rgba($base-overlay-background, 0.8);
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
.ui .react-datepicker {
|
||||||
|
box-shadow: 0 0 6px 0 rgb(0 0 0 / 30%);
|
||||||
|
font-size: 12px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: var(--foreground-color);
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
|
||||||
|
&-wrapper {
|
||||||
|
margin-left: 10px;
|
||||||
|
background: var(--foreground-color);
|
||||||
|
border: 2px solid var(--brand-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__current-month,
|
||||||
|
&-time__header,
|
||||||
|
&-year-header {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__day--keyboard-selected,
|
||||||
|
&__month-text--keyboard-selected,
|
||||||
|
&__quarter-text--keyboard-selected,
|
||||||
|
&__year-text--keyboard-selected {
|
||||||
|
background-color: var(--brand-color);
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__day,
|
||||||
|
&__day-name,
|
||||||
|
&__time-name {
|
||||||
|
width: 22px;
|
||||||
|
line-height: 21px;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__day,
|
||||||
|
&__month-text,
|
||||||
|
&__quarter-text,
|
||||||
|
&__year-text {
|
||||||
|
transition: 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--background-color);
|
||||||
|
color: var(--primary-text-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
|
color: hsla(var(--primary-text-color_hsl), 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--selected {
|
||||||
|
background-color: var(--brand-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__day-names {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__time {
|
||||||
|
background-color: var(--foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
background-color: var(--background-color);
|
||||||
|
border: 0;
|
||||||
|
border-top-left-radius: 10px;
|
||||||
|
padding: 8px 0 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__triangle::before,
|
||||||
|
&__triangle::after {
|
||||||
|
border-bottom-color: var(--background-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__navigation-icon::before,
|
||||||
|
&__year-read-view--down-arrow,
|
||||||
|
&__month-read-view--down-arrow,
|
||||||
|
&__month-year-read-view--down-arrow {
|
||||||
|
border-color: hsla(var(--primary-text-color_hsl), 0.4);
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__navigation-icon:hover::before {
|
||||||
|
border-color: var(--primary-text-color--faint);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__time-list-item {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
transition: 0.2s !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--background-color) !important;
|
||||||
|
color: var(--primary-text-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
|
color: hsla(var(--primary-text-color_hsl), 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--selected {
|
||||||
|
background-color: var(--brand-color) !important;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header:not(.react-datepicker__header--has-time-select) {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__month {
|
||||||
|
margin: 8px 14px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__time-container {
|
||||||
|
border-color: var(--background-color);
|
||||||
|
}
|
||||||
|
}
|
|
@ -73,7 +73,10 @@
|
||||||
|
|
||||||
.detailed-status__button {
|
.detailed-status__button {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status__button,
|
||||||
|
.detailed-status__button {
|
||||||
.icon-button {
|
.icon-button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
Ładowanie…
Reference in New Issue