This commit is contained in:
dongzhimin-xz 2025-05-06 13:14:00 +00:00 committed by GitHub
commit dade625358
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 150 additions and 0 deletions

View File

@ -81,6 +81,9 @@ export const COMPOSE_CHANGE_MEDIA_ORDER = 'COMPOSE_CHANGE_MEDIA_ORDER';
export const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS';
export const COMPOSE_FOCUS = 'COMPOSE_FOCUS';
export const COMPOSE_CHANGE_IS_SCHEDULED = 'COMPOSE_CHANGE_IS_SCHEDULED';
export const COMPOSE_CHANGE_SCHEDULE_TIME = 'COMPOSE_CHANGE_SCHEDULE_TIME';
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.' },
@ -188,6 +191,7 @@ export function submitCompose() {
const status = getState().getIn(['compose', 'text'], '');
const media = getState().getIn(['compose', 'media_attachments']);
const statusId = getState().getIn(['compose', 'id'], null);
const is_scheduled = getState().getIn(['compose', 'is_scheduled']);
if ((!status || !status.length) && media.size === 0) {
return;
@ -228,6 +232,7 @@ export function submitCompose() {
visibility: getState().getIn(['compose', 'privacy']),
poll: getState().getIn(['compose', 'poll'], null),
language: getState().getIn(['compose', 'language']),
scheduled_at: is_scheduled ? getState().getIn(['compose', 'scheduled_at']) : null,
},
headers: {
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
@ -237,9 +242,22 @@ export function submitCompose() {
browserHistory.goBack();
}
if ('scheduled_at' in response.data) {
dispatch(showAlert({
message: messages.saved,
dismissAfter: 10000,
}));
dispatch(submitComposeSuccess({ ...response.data.params}));
return;
}
dispatch(insertIntoTagHistory(response.data.tags, status));
dispatch(submitComposeSuccess({ ...response.data }));
// To make the app more responsive, immediately push the status
// into the columns
const insertIfOnline = timelineId => {
@ -835,3 +853,16 @@ export const changeMediaOrder = (a, b) => ({
a,
b,
});
export function changeIsScheduled() {
return {
type: COMPOSE_CHANGE_IS_SCHEDULED,
};
}
export function changeScheduleTime(value) {
return {
type: COMPOSE_CHANGE_SCHEDULE_TIME,
value,
};
}

View File

@ -29,6 +29,9 @@ import { PollForm } from "./poll_form";
import { ReplyIndicator } from './reply_indicator';
import { UploadForm } from './upload_form';
import ScheduleButtonContainer from '../containers/schedule_button_container';
import { ScheduleForm } from './schedule_form';
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 messages = defineMessages({
@ -69,6 +72,11 @@ class ComposeForm extends ImmutablePureComponent {
singleColumn: PropTypes.bool,
lang: PropTypes.string,
maxChars: PropTypes.number,
schedule_time: PropTypes.string,
schedule_timezone: PropTypes.string,
is_scheduled: PropTypes.bool.isRequired,
scheduled_at: PropTypes.string,
};
static defaultProps = {
@ -295,6 +303,7 @@ class ComposeForm extends ImmutablePureComponent {
<PollButtonContainer />
<SpoilerButtonContainer />
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
<ScheduleButtonContainer />
<CharacterCounter max={maxChars} text={this.getFulltextForCharacterCounting()} />
</div>
@ -306,6 +315,7 @@ class ComposeForm extends ImmutablePureComponent {
/>
</div>
</div>
<ScheduleForm />
</div>
</div>
</form>

View File

@ -0,0 +1,41 @@
import PropTypes from 'prop-types';
import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import classNames from 'classnames';
import { useDispatch, useSelector} from 'react-redux';
import { changeScheduleTime } from 'mastodon/actions/compose';
const messages = defineMessages({
schedule_time: { id: 'compose_form.schedule_time', defaultMessage: 'This post is scheduled to be published at (UTC+8)' },
});
export const ScheduleForm = () => {
const is_scheduled = useSelector(state => state.getIn(['compose', 'is_scheduled']));
const schedule_time = useSelector(state => state.getIn(['compose', 'schedule_time']));
const dispatch = useDispatch();
const intl = useIntl();
const handleChange = useCallback(({ target: { value } }) => {
dispatch(changeScheduleTime(value));
}, [dispatch]);
if (!is_scheduled) {
return null;
}
return (
<div>
<label>{intl.formatMessage(messages.schedule_time)}</label>
<input
className='search__input'
type='datetime-local'
value={schedule_time}
onChange={handleChange}
/>
</div>
);
}

View File

@ -29,6 +29,10 @@ const mapStateToProps = state => ({
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
lang: state.getIn(['compose', 'language']),
maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500),
is_scheduled: state.getIn(['compose', 'is_scheduled']),
schedule_time: state.getIn(['compose', 'schedule_time']),
schedule_timezone: state.getIn(['compose', 'schedule_timezone']),
scheduled_at: state.getIn(['compose', 'scheduled_at']),
});
const mapDispatchToProps = (dispatch) => ({

View File

@ -0,0 +1,30 @@
import { injectIntl, defineMessages } from "react-intl";
import { connect } from 'react-redux';
import ScheduleIcon from '@/material-icons/400-20px/schedule.svg?react';
import { IconButton } from "@/mastodon/components/icon_button";
import { changeIsScheduled } from '../../../actions/compose';
const messages = defineMessages({
marked: { id: 'compose_form.schedule.marked', defaultMessage: 'This post will be published at the time chosen below'},
unmarked: { id: 'compose_form.schedule.unmarked', defaultMessage: 'This post will be published at once'},
})
const mapStateToProps = (state, { intl }) => ({
iconComponent: ScheduleIcon,
title: intl.formatMessage(state.getIn(['compose', 'is_scheduled']) ? messages.marked : messages.unmarked),
active: state.getIn(['compose', 'is_scheduled']),
ariaControls: 'schedule-publish',
size: 18,
inverted: true,
});
const mapDispatchToProps = dispatch => ({
onClick () {
dispatch(changeIsScheduled());
},
});
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(IconButton));

View File

@ -166,6 +166,9 @@
"compose_form.publish_form": "New post",
"compose_form.reply": "Reply",
"compose_form.save_changes": "Update",
"compose_form.schedule.marked": "This post will be published at the time chosen below",
"compose_form.schedule.unmarked": "This post will be published at once",
"compose_form.schedule_time": "This post is scheduled to be published at (UTC+8)",
"compose_form.spoiler.marked": "Remove content warning",
"compose_form.spoiler.unmarked": "Add content warning",
"compose_form.spoiler_placeholder": "Content warning (optional)",

View File

@ -166,6 +166,9 @@
"compose_form.publish_form": "发嘟",
"compose_form.reply": "回复",
"compose_form.save_changes": "更改",
"compose_form.schedule.marked": "本文将在以下时间发布",
"compose_form.schedule.unmarked": "本文将立即发布",
"compose_form.schedule_time": "计划发文时间(北京时间)",
"compose_form.spoiler.marked": "移除内容警告",
"compose_form.spoiler.unmarked": "添加内容警告",
"compose_form.spoiler_placeholder": "内容警告 (可选)",

View File

@ -50,6 +50,8 @@ import {
COMPOSE_CHANGE_MEDIA_ORDER,
COMPOSE_SET_STATUS,
COMPOSE_FOCUS,
COMPOSE_CHANGE_IS_SCHEDULED,
COMPOSE_CHANGE_SCHEDULE_TIME
} from '../actions/compose';
import { REDRAFT } from '../actions/statuses';
import { STORE_HYDRATE } from '../actions/store';
@ -94,6 +96,11 @@ const initialState = ImmutableMap({
focusY: 0,
dirty: false,
}),
schedule_time: null,
schedule_timezone: '+08:00',
is_scheduled: false,
scheduled_at: null,
});
const initialPoll = ImmutableMap({
@ -127,6 +134,9 @@ function clearAll(state) {
map.update('media_attachments', list => list.clear());
map.set('poll', null);
map.set('idempotencyKey', uuid());
map.set('schedule_time', null);
map.set('is_scheduled', false);
map.set('scheduled_at', null);
});
}
@ -560,6 +570,18 @@ export default function compose(state = initialState, action) {
return list.splice(indexA, 1).splice(indexB, 0, moveItem);
});
case COMPOSE_CHANGE_IS_SCHEDULED:
return state.withMutations(map => {
map.set('is_scheduled', !state.get('is_scheduled'));
map.set('scheduled_at', state.get('schedule_time') + ':00.0' + state.get('schedule_timezone'));
map.set('idempotencyKey', uuid());
});
case COMPOSE_CHANGE_SCHEDULE_TIME:
return state.withMutations(map => {
map.set('schedule_time', action.value);
map.set('scheduled_at', action.value + ':00.0' + state.get('schedule_timezone'));
map.set('idempotencyKey', uuid());
});
default:
return state;
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20px" height="20px" viewBox="0 0 20 20" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill-opacity:1;" d="M 13.476562 8.167969 C 15.507812 8.167969 17.382812 9.214844 18.398438 10.917969 C 19.417969 12.609375 19.417969 14.722656 18.398438 16.417969 C 17.382812 18.117188 15.507812 19.167969 13.476562 19.167969 C 10.332031 19.167969 7.785156 16.699219 7.785156 13.667969 C 7.785156 10.632812 10.332031 8.167969 13.476562 8.167969 Z M 7.160156 1.25 L 7.160156 2.058594 L 12.214844 2.058594 L 12.214844 1.25 C 12.214844 1.023438 12.398438 0.832031 12.632812 0.832031 L 13.066406 0.832031 C 13.292969 0.832031 13.484375 1.015625 13.484375 1.25 L 13.484375 2.058594 L 18.125 2.058594 C 18.359375 2.058594 18.542969 2.25 18.542969 2.476562 L 18.542969 8.367188 C 18.542969 8.589844 18.359375 8.785156 18.125 8.785156 L 17.691406 8.785156 C 17.457031 8.785156 17.273438 8.601562 17.273438 8.367188 L 17.273438 6.949219 L 2.101562 6.949219 L 2.101562 17.332031 L 7.375 17.332031 C 7.609375 17.332031 7.792969 17.515625 7.792969 17.75 L 7.792969 18.140625 C 7.792969 18.375 7.609375 18.558594 7.375 18.558594 L 0.832031 18.558594 L 0.832031 2.476562 C 0.832031 2.242188 1.015625 2.058594 1.25 2.058594 L 5.890625 2.058594 L 5.890625 1.25 C 5.890625 1.023438 6.074219 0.832031 6.308594 0.832031 L 6.742188 0.832031 C 6.964844 0.832031 7.160156 1.015625 7.160156 1.25 Z M 13.476562 9.390625 C 11.890625 9.390625 10.433594 10.207031 9.640625 11.535156 C 8.851562 12.859375 8.851562 14.492188 9.640625 15.808594 C 10.433594 17.125 11.890625 17.949219 13.476562 17.949219 C 15.917969 17.949219 17.898438 16.035156 17.898438 13.675781 C 17.898438 11.316406 15.917969 9.390625 13.476562 9.390625 Z M 12.523438 11.226562 L 13.375 11.226562 C 13.609375 11.226562 13.792969 11.410156 13.792969 11.640625 L 13.792969 13.941406 C 13.792969 14.089844 13.875 14.234375 14.007812 14.308594 L 14.976562 14.851562 C 15.074219 14.902344 15.144531 14.996094 15.175781 15.105469 C 15.207031 15.210938 15.191406 15.328125 15.132812 15.425781 L 14.726562 16.117188 L 12.75 15.015625 C 12.617188 14.941406 12.535156 14.800781 12.535156 14.648438 L 12.535156 11.226562 Z M 5.890625 3.273438 L 2.101562 3.273438 L 2.101562 5.714844 L 17.273438 5.714844 L 17.273438 3.273438 L 13.484375 3.273438 L 13.484375 4.082031 C 13.484375 4.316406 13.300781 4.5 13.066406 4.5 L 12.632812 4.5 C 12.398438 4.5 12.214844 4.316406 12.214844 4.082031 L 12.214844 3.273438 L 7.160156 3.273438 L 7.160156 4.082031 C 7.160156 4.316406 6.976562 4.5 6.742188 4.5 L 6.308594 4.5 C 6.074219 4.5 5.890625 4.316406 5.890625 4.082031 Z M 5.890625 3.273438 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB