Merge remote-tracking branch 'soapbox/develop' into glitch-soc
This commit is contained in:
commit
1f4d71aeb2
10 changed files with 123 additions and 154 deletions
|
@ -1 +0,0 @@
|
||||||
<svg fill="white" stroke="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="M8.21 4.175V5.86h1.685a.842.842 0 0 1 0 1.684H8.21v1.684a.842.842 0 1 1-1.685 0V7.544H4.842a.842.842 0 1 1 0-1.684h1.684V4.175a.842.842 0 1 1 1.685 0Zm12.87 3.523a.814.814 0 0 1 0 1.18l-1.43 1.6-3.2-3.2 1.515-1.517a.814.814 0 0 1 1.179 0l1.937 1.937ZM6.573 18.364a5 5 0 0 1 1.392-2.686l7.559-7.559 3.116 3.2-7.47 7.544a5 5 0 0 1-2.704 1.409l-2.29.395.397-2.303Z"/></svg>
|
|
Before Width: | Height: | Size: 487 B |
|
@ -1 +0,0 @@
|
||||||
<svg viewBox="0 0 24 24" stroke="currentColor" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M22 3H2a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1ZM1 15h22M1 21h22" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
Before Width: | Height: | Size: 264 B |
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 1 1-8 0 4 4 0 0 1 8 0zm-4 7a7 7 0 0 0-7 7h14a7 7 0 0 0-7-7z"/></svg>
|
|
Before Width: | Height: | Size: 247 B |
|
@ -137,7 +137,7 @@ const SidebarNavigation = () => {
|
||||||
<div className='flex flex-col space-y-2'>
|
<div className='flex flex-col space-y-2'>
|
||||||
<SidebarNavigationLink
|
<SidebarNavigationLink
|
||||||
to='/'
|
to='/'
|
||||||
icon={require('icons/feed.svg')}
|
icon={require('@tabler/icons/icons/home.svg')}
|
||||||
text={<FormattedMessage id='tabs_bar.home' defaultMessage='Home' />}
|
text={<FormattedMessage id='tabs_bar.home' defaultMessage='Home' />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ const SidebarNavigation = () => {
|
||||||
<>
|
<>
|
||||||
<SidebarNavigationLink
|
<SidebarNavigationLink
|
||||||
to={`/@${account.acct}`}
|
to={`/@${account.acct}`}
|
||||||
icon={require('icons/user.svg')}
|
icon={require('@tabler/icons/icons/user.svg')}
|
||||||
text={<FormattedMessage id='tabs_bar.profile' defaultMessage='Profile' />}
|
text={<FormattedMessage id='tabs_bar.profile' defaultMessage='Profile' />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ const ThumbNavigation: React.FC = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<div className='thumb-navigation'>
|
<div className='thumb-navigation'>
|
||||||
<ThumbNavigationLink
|
<ThumbNavigationLink
|
||||||
src={require('icons/feed.svg')}
|
src={require('@tabler/icons/icons/home.svg')}
|
||||||
text={<FormattedMessage id='navigation.home' defaultMessage='Home' />}
|
text={<FormattedMessage id='navigation.home' defaultMessage='Home' />}
|
||||||
to='/'
|
to='/'
|
||||||
exact
|
exact
|
||||||
|
|
|
@ -1,129 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import IconButton from 'soapbox/components/icon_button';
|
|
||||||
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
|
||||||
import { DatePicker } from 'soapbox/features/ui/util/async-components';
|
|
||||||
|
|
||||||
import { setSchedule, removeSchedule } from '../../../actions/compose';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
schedule: { id: 'schedule.post_time', defaultMessage: 'Post Date/Time' },
|
|
||||||
remove: { id: 'schedule.remove', defaultMessage: 'Remove schedule' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
active: state.getIn(['compose', 'schedule']) ? true : false,
|
|
||||||
scheduledAt: state.getIn(['compose', 'schedule']),
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
onSchedule(date) {
|
|
||||||
dispatch(setSchedule(date));
|
|
||||||
},
|
|
||||||
|
|
||||||
onRemoveSchedule(date) {
|
|
||||||
dispatch(removeSchedule());
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
|
||||||
@injectIntl
|
|
||||||
class ScheduleForm extends React.Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
scheduledAt: PropTypes.instanceOf(Date),
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
onSchedule: PropTypes.func.isRequired,
|
|
||||||
onRemoveSchedule: PropTypes.func.isRequired,
|
|
||||||
dispatch: PropTypes.func,
|
|
||||||
active: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
initialized: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
setSchedule = date => {
|
|
||||||
this.props.onSchedule(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
setRef = c => {
|
|
||||||
this.datePicker = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
openDatePicker = () => {
|
|
||||||
if (!this.datePicker) return;
|
|
||||||
this.datePicker.setOpen(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
isCurrentOrFutureDate(date) {
|
|
||||||
return date && new Date().setHours(0, 0, 0, 0) <= new Date(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();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleRemove = e => {
|
|
||||||
this.props.onRemoveSchedule();
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize = () => {
|
|
||||||
const { initialized } = this.state;
|
|
||||||
|
|
||||||
if (!initialized && this.datePicker) {
|
|
||||||
this.openDatePicker();
|
|
||||||
this.setState({ initialized: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (!this.props.active) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { intl, scheduledAt } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classNames('datepicker', { 'datepicker--error': !this.isFiveMinutesFromNow(scheduledAt) })}>
|
|
||||||
<div className='datepicker__hint'>
|
|
||||||
<FormattedMessage id='datepicker.hint' defaultMessage='Scheduled to post at…' />
|
|
||||||
</div>
|
|
||||||
<div className='datepicker__input'>
|
|
||||||
<BundleContainer fetchComponent={DatePicker}>
|
|
||||||
{Component => (<Component
|
|
||||||
selected={scheduledAt}
|
|
||||||
showTimeSelect
|
|
||||||
dateFormat='MMMM d, yyyy h:mm aa'
|
|
||||||
timeIntervals={15}
|
|
||||||
wrapperClassName='react-datepicker-wrapper'
|
|
||||||
onChange={this.setSchedule}
|
|
||||||
placeholderText={this.props.intl.formatMessage(messages.schedule)}
|
|
||||||
filterDate={this.isCurrentOrFutureDate}
|
|
||||||
filterTime={this.isFiveMinutesFromNow}
|
|
||||||
ref={this.setRef}
|
|
||||||
/>)}
|
|
||||||
</BundleContainer>
|
|
||||||
<div className='datepicker__cancel'>
|
|
||||||
<IconButton title={intl.formatMessage(messages.remove)} src={require('@tabler/icons/icons/x.svg')} onClick={this.handleRemove} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
84
app/soapbox/features/compose/components/schedule_form.tsx
Normal file
84
app/soapbox/features/compose/components/schedule_form.tsx
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { setSchedule, removeSchedule } from 'soapbox/actions/compose';
|
||||||
|
import IconButton from 'soapbox/components/icon_button';
|
||||||
|
import { HStack, Stack, Text } from 'soapbox/components/ui';
|
||||||
|
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
||||||
|
import { DatePicker } from 'soapbox/features/ui/util/async-components';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
const isCurrentOrFutureDate = (date: Date) => {
|
||||||
|
return date && new Date().setHours(0, 0, 0, 0) <= new Date(date).setHours(0, 0, 0, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFiveMinutesFromNow = (time: Date) => {
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
schedule: { id: 'schedule.post_time', defaultMessage: 'Post Date/Time' },
|
||||||
|
remove: { id: 'schedule.remove', defaultMessage: 'Remove schedule' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const ScheduleForm: React.FC = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const scheduledAt = useAppSelector((state) => state.compose.get('schedule'));
|
||||||
|
const active = !!scheduledAt;
|
||||||
|
|
||||||
|
const onSchedule = (date: Date) => {
|
||||||
|
dispatch(setSchedule(date));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemove = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
dispatch(removeSchedule());
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!active) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack className='mb-2' space={1}>
|
||||||
|
<Text theme='muted'>
|
||||||
|
<FormattedMessage id='datepicker.hint' defaultMessage='Scheduled to post at…' />
|
||||||
|
</Text>
|
||||||
|
<HStack space={2} alignItems='center'>
|
||||||
|
<BundleContainer fetchComponent={DatePicker}>
|
||||||
|
{Component => (<Component
|
||||||
|
selected={scheduledAt}
|
||||||
|
showTimeSelect
|
||||||
|
dateFormat='MMMM d, yyyy h:mm aa'
|
||||||
|
timeIntervals={15}
|
||||||
|
wrapperClassName='react-datepicker-wrapper'
|
||||||
|
onChange={onSchedule}
|
||||||
|
placeholderText={intl.formatMessage(messages.schedule)}
|
||||||
|
filterDate={isCurrentOrFutureDate}
|
||||||
|
filterTime={isFiveMinutesFromNow}
|
||||||
|
className={classNames({
|
||||||
|
'has-error': !isFiveMinutesFromNow(scheduledAt),
|
||||||
|
})}
|
||||||
|
/>)}
|
||||||
|
</BundleContainer>
|
||||||
|
<IconButton
|
||||||
|
iconClassName='w-4 h-4'
|
||||||
|
className='bg-transparent text-gray-400 hover:text-gray-600'
|
||||||
|
src={require('@tabler/icons/icons/x.svg')}
|
||||||
|
onClick={handleRemove}
|
||||||
|
title={intl.formatMessage(messages.remove)}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScheduleForm;
|
|
@ -48,7 +48,7 @@ const Developers = () => {
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link to='/developers/timeline' className='bg-gray-200 dark:bg-gray-600 p-4 rounded flex flex-col items-center justify-center space-y-2 hover:-translate-y-1 transition-transform'>
|
<Link to='/developers/timeline' className='bg-gray-200 dark:bg-gray-600 p-4 rounded flex flex-col items-center justify-center space-y-2 hover:-translate-y-1 transition-transform'>
|
||||||
<SvgIcon src={require('icons/feed.svg')} className='dark:text-gray-100' />
|
<SvgIcon src={require('@tabler/icons/icons/home.svg')} className='dark:text-gray-100' />
|
||||||
|
|
||||||
<Text>
|
<Text>
|
||||||
<FormattedMessage id='developers.navigation.test_timeline_label' defaultMessage='Test timeline' />
|
<FormattedMessage id='developers.navigation.test_timeline_label' defaultMessage='Test timeline' />
|
||||||
|
|
|
@ -11,7 +11,7 @@ const ComposeButton = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-4'>
|
||||||
<Button icon={require('icons/compose.svg')} block size='lg' onClick={onOpenCompose}>
|
<Button icon={require('icons/pen-plus.svg')} block size='lg' onClick={onOpenCompose}>
|
||||||
<span><FormattedMessage id='navigation.compose' defaultMessage='Compose' /></span>
|
<span><FormattedMessage id='navigation.compose' defaultMessage='Compose' /></span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,14 +1,22 @@
|
||||||
.react-datepicker {
|
.react-datepicker {
|
||||||
@apply p-4 font-sans text-xs text-gray-900 border border-solid border-gray-200 rounded-lg;
|
@apply dark:bg-slate-900 dark:border-slate-700 p-4 font-sans text-xs text-gray-900 dark:text-gray-300 border border-solid border-gray-200 rounded-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-datepicker__input-container > input {
|
||||||
|
@apply dark:bg-slate-800 dark:text-white block w-full sm:text-sm border-gray-300 dark:border-gray-600 rounded-md focus:ring-indigo-500 focus:border-indigo-500;
|
||||||
|
|
||||||
|
&.has-error {
|
||||||
|
@apply text-red-600 border-red-600;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::before,
|
.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::before,
|
||||||
.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::after {
|
.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::after {
|
||||||
@apply border-b-white;
|
@apply border-b-white dark:border-b-slate-900;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::before {
|
.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::before {
|
||||||
@apply border-b-gray-200;
|
@apply border-b-gray-200 dark:border-b-slate-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-datepicker__header:not(.react-datepicker__header--has-time-select) {
|
.react-datepicker__header:not(.react-datepicker__header--has-time-select) {
|
||||||
|
@ -16,14 +24,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-datepicker__header {
|
.react-datepicker__header {
|
||||||
@apply bg-white border-b-0 py-1 px-0;
|
@apply bg-white dark:bg-slate-900 border-b-0 py-1 px-0;
|
||||||
// border-top-left-radius: var(--border-radius-lg);
|
// border-top-left-radius: var(--border-radius-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-datepicker__current-month,
|
.react-datepicker__current-month,
|
||||||
.react-datepicker-time__header,
|
.react-datepicker-time__header,
|
||||||
.react-datepicker-year-header {
|
.react-datepicker-year-header {
|
||||||
@apply text-gray-900 font-bold text-sm;
|
@apply text-gray-900 dark:text-gray-300 font-bold text-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-datepicker__current-month {
|
.react-datepicker__current-month {
|
||||||
|
@ -31,7 +39,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-datepicker__navigation {
|
.react-datepicker__navigation {
|
||||||
@apply top-4 h-8 w-8 rounded hover:bg-gray-50;
|
@apply top-4 h-8 w-8 rounded hover:bg-gray-50 dark:hover:bg-slate-900/50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-datepicker__navigation-icon {
|
.react-datepicker__navigation-icon {
|
||||||
|
@ -70,24 +78,37 @@
|
||||||
|
|
||||||
.react-datepicker__day-names,
|
.react-datepicker__day-names,
|
||||||
.react-datepicker__week {
|
.react-datepicker__week {
|
||||||
display: flex;
|
@apply flex justify-between;
|
||||||
justify-content: space-between;
|
}
|
||||||
|
|
||||||
|
.react-datepicker__time {
|
||||||
|
@apply dark:bg-slate-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-datepicker__time-container {
|
||||||
|
@apply dark:border-slate-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-datepicker__day-name,
|
.react-datepicker__day-name,
|
||||||
.react-datepicker__day,
|
.react-datepicker__day,
|
||||||
.react-datepicker__time-name {
|
.react-datepicker__time-name {
|
||||||
@apply text-gray-900;
|
@apply text-gray-900 dark:text-gray-300;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.react-datepicker__time-list-item--disabled,
|
||||||
|
.react-datepicker__day--disabled {
|
||||||
|
@apply text-gray-400 dark:text-gray-500;
|
||||||
|
}
|
||||||
|
|
||||||
.react-datepicker__day:hover,
|
.react-datepicker__day:hover,
|
||||||
.react-datepicker__month-text:hover,
|
.react-datepicker__month-text:hover,
|
||||||
.react-datepicker__quarter-text:hover,
|
.react-datepicker__quarter-text:hover,
|
||||||
.react-datepicker__year-text:hover {
|
.react-datepicker__year-text:hover,
|
||||||
@apply bg-gray-100 rounded;
|
.react-datepicker__time-list-item:hover {
|
||||||
|
@apply bg-gray-100 dark:bg-slate-700 rounded;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-datepicker__day--selected,
|
.react-datepicker__day--selected,
|
||||||
|
@ -102,16 +123,12 @@
|
||||||
.react-datepicker__year-text--selected,
|
.react-datepicker__year-text--selected,
|
||||||
.react-datepicker__year-text--in-selecting-range,
|
.react-datepicker__year-text--in-selecting-range,
|
||||||
.react-datepicker__year-text--in-range {
|
.react-datepicker__year-text--in-range {
|
||||||
@apply bg-primary-600 hover:bg-primary-700 text-white rounded;
|
@apply bg-primary-600 hover:bg-primary-700 dark:bg-slate-300 dark:hover:bg-slate-200 text-white dark:text-black rounded;
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__day--disabled {
|
|
||||||
@apply text-gray-400;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-datepicker__day--keyboard-selected,
|
.react-datepicker__day--keyboard-selected,
|
||||||
.react-datepicker__month-text--keyboard-selected,
|
.react-datepicker__month-text--keyboard-selected,
|
||||||
.react-datepicker__quarter-text--keyboard-selected,
|
.react-datepicker__quarter-text--keyboard-selected,
|
||||||
.react-datepicker__year-text--keyboard-selected {
|
.react-datepicker__year-text--keyboard-selected {
|
||||||
@apply bg-primary-50 hover:bg-primary-100 text-primary-600 dark:text-primary-400;
|
@apply bg-primary-50 hover:bg-primary-100 dark:bg-slate-700 dark:hover:bg-slate-600 text-primary-600 dark:text-primary-400;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue