merge-requests/539/merge
NEETzsche 2021-06-18 16:04:31 +00:00 zatwierdzone przez Alex Gleason
rodzic 969910481f
commit 3a209e2fea
13 zmienionych plików z 290 dodań i 3 usunięć

Wyświetl plik

@ -63,6 +63,10 @@ export const COMPOSE_POLL_OPTION_CHANGE = 'COMPOSE_POLL_OPTION_CHANGE';
export const COMPOSE_POLL_OPTION_REMOVE = 'COMPOSE_POLL_OPTION_REMOVE';
export const COMPOSE_POLL_SETTINGS_CHANGE = 'COMPOSE_POLL_SETTINGS_CHANGE';
export const COMPOSE_SCHEDULE_ADD = 'COMPOSE_SCHEDULE_ADD';
export const COMPOSE_SCHEDULE_SET = 'COMPOSE_SCHEDULE_SET';
export const COMPOSE_SCHEDULE_REMOVE = 'COMPOSE_SCHEDULE_REMOVE';
const messages = defineMessages({
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
@ -133,7 +137,7 @@ export function directCompose(account, routerHistory) {
export function handleComposeSubmit(dispatch, getState, response, status) {
if (!dispatch || !getState) return;
dispatch(insertIntoTagHistory(response.data.tags, status));
dispatch(insertIntoTagHistory(response.data.tags || [], status));
dispatch(submitComposeSuccess({ ...response.data }));
// To make the app more responsive, immediately push the status into the columns
@ -179,7 +183,7 @@ export function submitCompose(routerHistory, group) {
visibility: getState().getIn(['compose', 'privacy']),
content_type: getState().getIn(['compose', 'content_type']),
poll: getState().getIn(['compose', 'poll'], null),
group_id: group ? group.get('id') : null,
scheduled_at: getState().getIn(['compose', 'schedule'], null),
}, {
headers: {
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
@ -533,6 +537,25 @@ export function removePoll() {
};
};
export function addSchedule() {
return {
type: COMPOSE_SCHEDULE_ADD,
};
};
export function setSchedule(date) {
return {
type: COMPOSE_SCHEDULE_SET,
date: date,
};
};
export function removeSchedule() {
return {
type: COMPOSE_SCHEDULE_REMOVE,
};
};
export function addPollOption(title) {
return {
type: COMPOSE_POLL_OPTION_ADD,

Wyświetl plik

@ -12,6 +12,8 @@ import UploadButtonContainer from '../containers/upload_button_container';
import { defineMessages, injectIntl } from 'react-intl';
import SpoilerButtonContainer from '../containers/spoiler_button_container';
import MarkdownButtonContainer from '../containers/markdown_button_container';
import ScheduleFormContainer from '../containers/schedule_form_container';
import ScheduleButtonContainer from '../containers/schedule_button_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
import PollFormContainer from '../containers/poll_form_container';
@ -71,6 +73,7 @@ class ComposeForm extends ImmutablePureComponent {
group: ImmutablePropTypes.map,
isModalOpen: PropTypes.bool,
clickableAreaRef: PropTypes.object,
scheduledAt: PropTypes.instanceOf(Date),
};
static defaultProps = {
@ -310,6 +313,7 @@ class ComposeForm extends ImmutablePureComponent {
<div className='compose-form__modifiers'>
<UploadFormContainer />
<PollFormContainer />
<ScheduleFormContainer />
</div>
}
</AutosuggestTextarea>
@ -321,6 +325,7 @@ class ComposeForm extends ImmutablePureComponent {
<UploadButtonContainer />
<PollButtonContainer />
<PrivacyDropdownContainer />
<ScheduleButtonContainer />
<SpoilerButtonContainer />
<MarkdownButtonContainer />
</div>

Wyświetl plik

@ -0,0 +1,50 @@
import React from 'react';
import IconButton from '../../../components/icon_button';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({
add_schedule: { id: 'schedule_button.add_schedule', defaultMessage: 'Schedule post for later' },
remove_schedule: { id: 'schedule_button.remove_schedule', defaultMessage: 'Post immediately' },
});
const iconStyle = {
height: null,
lineHeight: '27px',
};
export default
@injectIntl
class ScheduleButton extends React.PureComponent {
static propTypes = {
disabled: PropTypes.bool,
active: PropTypes.bool,
onClick: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
handleClick = () => {
this.props.onClick();
}
render() {
const { intl, active, disabled } = this.props;
return (
<div className='compose-form__schedule-button'>
<IconButton
icon='calendar'
title={intl.formatMessage(active ? messages.remove_schedule : messages.add_schedule)}
disabled={disabled}
onClick={this.handleClick}
className={`compose-form__schedule-button-icon ${active ? 'active' : ''}`}
size={18}
inverted
style={iconStyle}
/>
</div>
);
}
}

Wyświetl plik

@ -0,0 +1,96 @@
'use strict';
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { defineMessages, injectIntl } from 'react-intl';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
const messages = defineMessages({
schedule: { id: 'schedule.post_time', defaultMessage: 'Post Date/Time' },
});
class ScheduleForm extends React.Component {
static propTypes = {
schedule: PropTypes.instanceOf(Date),
intl: PropTypes.object.isRequired,
onSchedule: PropTypes.func.isRequired,
active: PropTypes.bool,
};
setSchedule(date)
{
this.setState({ schedule: date });
this.props.onSchedule(date);
}
openDatePicker(datePicker)
{
if (!datePicker)
{
return;
}
datePicker.setOpen(true);
}
componentDidMount()
{
this.setState({ schedule: this.props.schedule });
}
constructor(props)
{
super(props);
this.setSchedule = this.setSchedule.bind(this);
}
isCurrentOrFutureDate(date)
{
return date && new Date().setHours(0, 0, 0, 0) <= date.setHours(0, 0, 0, 0);
}
isFiveMinutesFromNow(time)
{
const fiveMinutesFromNow = new Date(new Date().getTime() + 300000); // now, plus five minutes (Pleroma won't schedule posts )
const selectedDate = new Date(time);
return fiveMinutesFromNow.getTime() < selectedDate.getTime();
};
render() {
if (!this.props.active || !this.state)
{
return null;
}
const { schedule } = this.state;
return (
<DatePicker
selected={schedule}
showTimeSelect
dateFormat='MMMM d, yyyy h:mm aa'
timeIntervals={15}
timeFormat='HH:mm'
timeInputLabel='Time:'
wrapperClassName='react-datepicker-wrapper'
onChange={this.setSchedule}
placeholderText={this.props.intl.formatMessage(messages.schedule)}
filterDate={this.isCurrentOrFutureDate}
filterTime={this.isFiveMinutesFromNow}
ref={this.isCurrentOrFutureDate(schedule) ? null : this.openDatePicker}
/>
);
}
}
const mapStateToProps = (state, ownProps) => ({
schedule: state.getIn(['compose', 'schedule']),
});
export default injectIntl(connect(mapStateToProps)(ScheduleForm));

Wyświetl plik

@ -26,6 +26,7 @@ const mapStateToProps = state => ({
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
isModalOpen: state.get('modal').modalType === 'COMPOSE',
maxTootChars: state.getIn(['instance', 'max_toot_chars']),
schedule: state.getIn(['instance', 'schedule']),
});
const mapDispatchToProps = (dispatch) => ({

Wyświetl plik

@ -0,0 +1,23 @@
import { connect } from 'react-redux';
import ScheduleButton from '../components/schedule_button';
import { addSchedule, removeSchedule } from '../../../actions/compose';
const mapStateToProps = state => ({
active: state.getIn(['compose', 'schedule']) ? true : false,
});
const mapDispatchToProps = dispatch => ({
onClick() {
dispatch((_, getState) => {
if (getState().getIn(['compose', 'schedule'])) {
dispatch(removeSchedule());
} else {
dispatch(addSchedule());
}
});
},
});
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleButton);

Wyświetl plik

@ -0,0 +1,16 @@
import { connect } from 'react-redux';
import ScheduleForm from '../components/schedule_form';
import { setSchedule } from '../../../actions/compose';
const mapStateToProps = state => ({
schedule: state.getIn(['compose', 'schedule']),
active: state.getIn(['compose', 'schedule']) ? true : false,
});
const mapDispatchToProps = dispatch => ({
onSchedule(date) {
dispatch(setSchedule(date));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleForm);

Wyświetl plik

@ -189,6 +189,9 @@
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Write your warning here",
"schedule_button.add_schedule": "Schedule post for later",
"schedule_button.remove_schedule": "Post immediately",
"schedule.post_time": "Post Date/Time",
"confirmation_modal.cancel": "Cancel",
"confirmations.admin.deactivate_user.confirm": "Deactivate @{name}",
"confirmations.admin.deactivate_user.message": "You are about to deactivate @{acct}. Deactivating a user is a reversible action.",

Wyświetl plik

@ -32,6 +32,9 @@ import {
COMPOSE_RESET,
COMPOSE_POLL_ADD,
COMPOSE_POLL_REMOVE,
COMPOSE_SCHEDULE_ADD,
COMPOSE_SCHEDULE_SET,
COMPOSE_SCHEDULE_REMOVE,
COMPOSE_POLL_OPTION_ADD,
COMPOSE_POLL_OPTION_CHANGE,
COMPOSE_POLL_OPTION_REMOVE,
@ -81,6 +84,10 @@ const initialPoll = ImmutableMap({
multiple: false,
});
const initialSchedule = new Date();
initialSchedule.setDate(initialSchedule.getDate() - 1);
function statusToTextMentions(state, status, account) {
const author = status.getIn(['account', 'acct']);
const mentions = status.get('mentions', []).map(m => m.get('acct'));
@ -107,6 +114,7 @@ function clearAll(state) {
map.set('media_attachments', ImmutableList());
map.set('poll', null);
map.set('idempotencyKey', uuid());
map.set('schedule', null);
});
};
@ -398,6 +406,12 @@ export default function compose(state = initialState, action) {
return state.set('poll', initialPoll);
case COMPOSE_POLL_REMOVE:
return state.set('poll', null);
case COMPOSE_SCHEDULE_ADD:
return state.set('schedule', initialSchedule);
case COMPOSE_SCHEDULE_SET:
return state.set('schedule', action.date);
case COMPOSE_SCHEDULE_REMOVE:
return state.set('schedule', null);
case COMPOSE_POLL_OPTION_ADD:
return state.updateIn(['poll', 'options'], options => options.push(action.title));
case COMPOSE_POLL_OPTION_CHANGE:

Wyświetl plik

@ -90,7 +90,8 @@
}
.autosuggest-textarea__textarea,
.spoiler-input__input {
.spoiler-input__input,
.react-datepicker__input-container input {
display: block;
box-sizing: border-box;
width: 100%;
@ -376,6 +377,16 @@
}
} // 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 {
align-items: center;
background: rgba($base-overlay-background, 0.8);

Wyświetl plik

@ -129,6 +129,10 @@
}
}
.react-datepicker-popper {
z-index: 9999 !important;
}
.ellipsis::after { content: ""; }
.timeline-compose-block {

Wyświetl plik

@ -100,6 +100,7 @@
"qrcode.react": "^1.0.0",
"react": "^16.13.1",
"react-color": "^2.18.1",
"react-datepicker": "^4.1.1",
"react-dom": "^16.13.1",
"react-helmet": "^6.0.0",
"react-hotkeys": "^1.1.4",

Wyświetl plik

@ -1690,6 +1690,11 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.4.4.tgz#11d5db19bd178936ec89cd84519c4de439574398"
integrity sha512-1oO6+dN5kdIA3sKPZhRGJTfGVP4SWV6KqlMOwry4J3HfyD68sl/3KmG7DeYUzvN+RbhXDnv/D8vNNB8168tAMg==
"@popperjs/core@^2.9.2":
version "2.9.2"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353"
integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==
"@sinonjs/commons@^1.7.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d"
@ -3415,6 +3420,11 @@ classnames@^2.2.5:
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
classnames@^2.2.6:
version "2.3.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
clean-css@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78"
@ -4135,6 +4145,11 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
date-fns@^2.0.1:
version "2.22.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.22.1.tgz#1e5af959831ebb1d82992bf67b765052d8f0efc4"
integrity sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg==
date-now@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@ -9983,6 +9998,18 @@ react-color@^2.18.1:
reactcss "^1.2.0"
tinycolor2 "^1.4.1"
react-datepicker@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.1.1.tgz#5ecef49c672b2250fca26327c988464e6ba52b62"
integrity sha512-vtZIA7MbUrffRw1CHiyOGtmTO/tTdZGr5BYaiRucHMTb6rCqA8TkaQhzX6tTwMwP8vV38Khv4UWohrJbiX1rMw==
dependencies:
"@popperjs/core" "^2.9.2"
classnames "^2.2.6"
date-fns "^2.0.1"
prop-types "^15.7.2"
react-onclickoutside "^6.10.0"
react-popper "^2.2.5"
react-dom@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f"
@ -10102,6 +10129,11 @@ react-notification@^6.8.4:
dependencies:
prop-types "^15.6.2"
react-onclickoutside@^6.10.0:
version "6.11.2"
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.11.2.tgz#790e2100b9a3589eefca1404ecbf0476b81b7928"
integrity sha512-640486eSwU/t5iD6yeTlefma8dI3bxPXD93hM9JGKyYITAd0P1JFkkcDeyHZRqNpY/fv1YW0Fad9BXr44OY8wQ==
react-overlays@^0.9.0:
version "0.9.2"
resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-0.9.2.tgz#51ab1c62ded5af4d279bd3b943999531bbd648da"
@ -10122,6 +10154,14 @@ react-popper@^2.2.3:
react-fast-compare "^3.0.1"
warning "^4.0.2"
react-popper@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.2.5.tgz#1214ef3cec86330a171671a4fbcbeeb65ee58e96"
integrity sha512-kxGkS80eQGtLl18+uig1UIf9MKixFSyPxglsgLBxlYnyDf65BiY9B3nZSc6C9XUNDgStROB0fMQlTEz1KxGddw==
dependencies:
react-fast-compare "^3.0.1"
warning "^4.0.2"
react-redux-loading-bar@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/react-redux-loading-bar/-/react-redux-loading-bar-5.0.0.tgz#fffbc2b893c556b7b4c577743427507ee6dbc1f3"