Merge branch 'groups-toggle' into 'develop'

Groups toggle

See merge request soapbox-pub/soapbox!2391
environments/review-develop-3zknud/deployments/3019
Alex Gleason 2023-03-31 19:06:10 +00:00
commit 72b57d93da
17 zmienionych plików z 102 dodań i 159 usunięć

Wyświetl plik

@ -48,6 +48,7 @@ const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL';
const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS';
const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO';
const COMPOSE_GROUP_POST = 'COMPOSE_GROUP_POST';
const COMPOSE_SET_GROUP_TIMELINE_VISIBLE = 'COMPOSE_SET_GROUP_TIMELINE_VISIBLE';
const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR';
const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY';
@ -292,7 +293,10 @@ const submitCompose = (composeId: string, routerHistory?: History, force = false
to,
};
if (compose.privacy === 'group') params.group_id = compose.group_id;
if (compose.privacy === 'group') {
params.group_id = compose.group_id;
params.group_timeline_visible = compose.group_timeline_visible; // Truth Social
}
dispatch(createStatus(params, idempotencyKey, statusId)).then(function(data) {
if (!statusId && data.visibility === 'direct' && getState().conversations.mounted <= 0 && routerHistory) {
@ -483,6 +487,12 @@ const groupCompose = (composeId: string, groupId: string) =>
});
};
const setGroupTimelineVisible = (composeId: string, groupTimelineVisible: boolean) => ({
type: COMPOSE_SET_GROUP_TIMELINE_VISIBLE,
id: composeId,
groupTimelineVisible,
});
const clearComposeSuggestions = (composeId: string) => {
if (cancelFetchComposeSuggestionsAccounts) {
cancelFetchComposeSuggestionsAccounts();
@ -792,6 +802,7 @@ export {
COMPOSE_ADD_TO_MENTIONS,
COMPOSE_REMOVE_FROM_MENTIONS,
COMPOSE_SET_STATUS,
COMPOSE_SET_GROUP_TIMELINE_VISIBLE,
setComposeToStatus,
changeCompose,
replyCompose,
@ -818,6 +829,7 @@ export {
uploadComposeFail,
undoUploadCompose,
groupCompose,
setGroupTimelineVisible,
clearComposeSuggestions,
fetchComposeSuggestions,
readyComposeSuggestionsEmojis,

Wyświetl plik

@ -1,13 +1,52 @@
import React from 'react';
import ReactToggle, { ToggleProps } from 'react-toggle';
import clsx from 'clsx';
import React, { useRef } from 'react';
interface IToggle extends Pick<React.InputHTMLAttributes<HTMLInputElement>, 'id' | 'name' | 'checked' | 'onChange' | 'required' | 'disabled'> {
size?: 'sm' | 'md'
}
/** A glorified checkbox. */
const Toggle: React.FC<IToggle> = ({ id, size = 'md', name, checked, onChange, required, disabled }) => {
const input = useRef<HTMLInputElement>(null);
const handleClick: React.MouseEventHandler<HTMLButtonElement> = () => {
input.current?.focus();
input.current?.click();
};
/** A glorified checkbox. Wrapper around react-toggle. */
const Toggle: React.FC<ToggleProps> = ({ icons = false, ...rest }) => {
return (
<ReactToggle
icons={icons}
{...rest}
/>
<button
className={clsx('flex-none rounded-full', {
'bg-gray-500': !checked && !disabled,
'bg-primary-600': checked && !disabled,
'bg-gray-200': !checked && disabled,
'bg-primary-200': checked && disabled,
'w-9 p-0.5': size === 'sm',
'w-11 p-0.5': size === 'md',
'cursor-default': disabled,
})}
onClick={handleClick}
>
<div className={clsx('rounded-full bg-white transition-transform', {
'h-4.5 w-4.5': size === 'sm',
'translate-x-3.5': size === 'sm' && checked,
'h-6 w-6': size === 'md',
'translate-x-4': size === 'md' && checked,
})}
/>
<input
id={id}
ref={input}
name={name}
type='checkbox'
className='sr-only'
checked={checked}
onChange={onChange}
required={required}
disabled={disabled}
/>
</button>
);
};

Wyświetl plik

@ -63,9 +63,10 @@ interface IComposeForm<ID extends string> {
clickableAreaRef?: React.RefObject<HTMLDivElement>
event?: string
group?: string
extra?: React.ReactNode
}
const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickableAreaRef, event, group }: IComposeForm<ID>) => {
const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickableAreaRef, event, group, extra }: IComposeForm<ID>) => {
const history = useHistory();
const intl = useIntl();
const dispatch = useAppDispatch();
@ -333,6 +334,8 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
<QuotedStatusContainer composeId={id} />
{extra && <div className={clsx({ 'hidden': condensed })}>{extra}</div>}
<div
className={clsx('flex flex-wrap items-center justify-between', {
'hidden': condensed,

Wyświetl plik

@ -71,7 +71,6 @@ const FilterField: StreamfieldComponent<IFilterField> = ({ value, onChange }) =>
<Toggle
checked={value.whole_word}
onChange={handleChange('whole_word')}
icons={false}
/>
<Text tag='span' theme='muted'>
@ -212,28 +211,24 @@ const EditFilter: React.FC<IEditFilter> = ({ params }) => {
<List>
<ListItem label={intl.formatMessage(messages.home_timeline)}>
<Toggle
name='home_timeline'
checked={homeTimeline}
onChange={({ target }) => setHomeTimeline(target.checked)}
/>
</ListItem>
<ListItem label={intl.formatMessage(messages.public_timeline)}>
<Toggle
name='public_timeline'
checked={publicTimeline}
onChange={({ target }) => setPublicTimeline(target.checked)}
/>
</ListItem>
<ListItem label={intl.formatMessage(messages.notifications)}>
<Toggle
name='notifications'
checked={notifications}
onChange={({ target }) => setNotifications(target.checked)}
/>
</ListItem>
<ListItem label={intl.formatMessage(messages.conversations)}>
<Toggle
name='conversations'
checked={conversations}
onChange={({ target }) => setConversations(target.checked)}
/>
@ -241,7 +236,6 @@ const EditFilter: React.FC<IEditFilter> = ({ params }) => {
{features.filtersV2 && (
<ListItem label={intl.formatMessage(messages.accounts)}>
<Toggle
name='accounts'
checked={accounts}
onChange={({ target }) => setAccounts(target.checked)}
/>
@ -255,7 +249,6 @@ const EditFilter: React.FC<IEditFilter> = ({ params }) => {
hint={intl.formatMessage(features.filtersV2 ? messages.hide_hint : messages.drop_hint)}
>
<Toggle
name='hide'
checked={hide}
onChange={({ target }) => setHide(target.checked)}
/>

Wyświetl plik

@ -2,12 +2,12 @@ import React, { useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { groupCompose } from 'soapbox/actions/compose';
import { groupCompose, setGroupTimelineVisible } from 'soapbox/actions/compose';
import { connectGroupStream } from 'soapbox/actions/streaming';
import { expandGroupTimeline } from 'soapbox/actions/timelines';
import { Avatar, HStack, Icon, Stack, Text } from 'soapbox/components/ui';
import { Avatar, HStack, Icon, Stack, Text, Toggle } from 'soapbox/components/ui';
import ComposeForm from 'soapbox/features/compose/components/compose-form';
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useOwnAccount } from 'soapbox/hooks';
import { useGroup } from 'soapbox/hooks/api';
import Timeline from '../ui/components/timeline';
@ -26,16 +26,21 @@ const GroupTimeline: React.FC<IGroupTimeline> = (props) => {
const { group } = useGroup(groupId);
const composeId = `group:${groupId}`;
const canComposeGroupStatus = !!account && group?.relationship?.member;
const groupTimelineVisible = useAppSelector((state) => !!state.compose.get(composeId)?.group_timeline_visible);
const handleLoadMore = (maxId: string) => {
dispatch(expandGroupTimeline(groupId, { maxId }));
};
const handleToggleChange = () => {
dispatch(setGroupTimelineVisible(composeId, !groupTimelineVisible));
};
useEffect(() => {
dispatch(expandGroupTimeline(groupId));
dispatch(groupCompose(`group:${groupId}`, groupId));
dispatch(groupCompose(composeId, groupId));
const disconnect = dispatch(connectGroupStream(groupId));
@ -58,10 +63,25 @@ const GroupTimeline: React.FC<IGroupTimeline> = (props) => {
</Link>
<ComposeForm
id={`group:${groupId}`}
id={composeId}
shouldCondense
autoFocus={false}
group={groupId}
extra={!group.locked && (
<HStack alignItems='center' space={4}>
<label className='ml-auto cursor-pointer' htmlFor='group-timeline-visible'>
<Text theme='muted'>
<FormattedMessage id='compose_group.share_to_followers' defaultMessage='Share with my followers' />
</Text>
</label>
<Toggle
id='group-timeline-visible'
checked={groupTimelineVisible}
onChange={handleToggleChange}
size='sm'
/>
</HStack>
)}
/>
</HStack>
</div>
@ -69,7 +89,7 @@ const GroupTimeline: React.FC<IGroupTimeline> = (props) => {
<Timeline
scrollKey='group_timeline'
timelineId={`group:${groupId}`}
timelineId={composeId}
onLoadMore={handleLoadMore}
emptyMessage={
<Stack space={4} className='py-6' justifyContent='center' alignItems='center'>

Wyświetl plik

@ -1,9 +1,9 @@
import noop from 'lodash/noop';
import React from 'react';
import Toggle from 'react-toggle';
import { toggleStatusReport } from 'soapbox/actions/reports';
import StatusContent from 'soapbox/components/status-content';
import { Toggle } from 'soapbox/components/ui';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import Bundle from '../../ui/components/bundle';
@ -88,7 +88,7 @@ const StatusCheckBox: React.FC<IStatusCheckBox> = ({ id, disabled }) => {
</div>
<div className='status-check-box-toggle'>
<Toggle checked={checked} onChange={onToggle} disabled={disabled} icons={false} />
<Toggle checked={checked} onChange={onToggle} disabled={disabled} />
</div>
</div>
);

Wyświetl plik

@ -1,6 +1,5 @@
import React, { useEffect, useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import Toggle from 'react-toggle';
import {
changeEditEventApprovalRequired,
@ -22,7 +21,7 @@ import { closeModal, openModal } from 'soapbox/actions/modals';
import { ADDRESS_ICONS } from 'soapbox/components/autosuggest-location';
import LocationSearch from 'soapbox/components/location-search';
import { checkEventComposeContent } from 'soapbox/components/modal-root';
import { Button, Form, FormGroup, HStack, Icon, IconButton, Input, Modal, Spinner, Stack, Tabs, Text, Textarea } from 'soapbox/components/ui';
import { Button, Form, FormGroup, HStack, Icon, IconButton, Input, Modal, Spinner, Stack, Tabs, Text, Textarea, Toggle } from 'soapbox/components/ui';
import AccountContainer from 'soapbox/containers/account-container';
import { isCurrentOrFutureDate } from 'soapbox/features/compose/components/schedule-form';
import BundleContainer from 'soapbox/features/ui/containers/bundle-container';
@ -273,7 +272,6 @@ const ComposeEventModal: React.FC<IComposeEventModal> = ({ onClose }) => {
</FormGroup>
<HStack alignItems='center' space={2}>
<Toggle
icons={false}
checked={!!endTime}
onChange={onChangeHasEndTime}
/>
@ -302,7 +300,6 @@ const ComposeEventModal: React.FC<IComposeEventModal> = ({ onClose }) => {
{!id && (
<HStack alignItems='center' space={2}>
<Toggle
icons={false}
checked={approvalRequired}
onChange={onChangeApprovalRequired}
/>

Wyświetl plik

@ -98,7 +98,6 @@ const EditAnnouncementModal: React.FC<IEditAnnouncementModal> = ({ onClose }) =>
</FormGroup>
<HStack alignItems='center' space={2}>
<Toggle
icons={false}
checked={allDay}
onChange={onChangeAllDay}
/>

Wyświetl plik

@ -1,11 +1,10 @@
import { Map as ImmutableMap } from 'immutable';
import React, { useState, useEffect, useCallback } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import Toggle from 'react-toggle';
import { updateMrf } from 'soapbox/actions/mrf';
import List, { ListItem } from 'soapbox/components/list';
import { Modal } from 'soapbox/components/ui';
import { Modal, Toggle } from 'soapbox/components/ui';
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
import { makeGetRemoteInstance } from 'soapbox/selectors';
import toast from 'soapbox/toast';
@ -86,7 +85,6 @@ const EditFederationModal: React.FC<IEditFederationModal> = ({ host, onClose })
<Toggle
checked={reject}
onChange={handleDataChange('reject')}
icons={false}
id='reject'
/>
</ListItem>
@ -95,7 +93,6 @@ const EditFederationModal: React.FC<IEditFederationModal> = ({ host, onClose })
<Toggle
checked={fullMediaRemoval}
onChange={handleMediaRemoval}
icons={false}
id='media_removal'
disabled={reject}
/>
@ -105,7 +102,6 @@ const EditFederationModal: React.FC<IEditFederationModal> = ({ host, onClose })
<Toggle
checked={media_nsfw}
onChange={handleDataChange('media_nsfw')}
icons={false}
id='media_nsfw'
disabled={reject || media_removal}
/>
@ -115,7 +111,6 @@ const EditFederationModal: React.FC<IEditFederationModal> = ({ host, onClose })
<Toggle
checked={followers_only}
onChange={handleDataChange('followers_only')}
icons={false}
id='followers_only'
disabled={reject}
/>
@ -125,7 +120,6 @@ const EditFederationModal: React.FC<IEditFederationModal> = ({ host, onClose })
<Toggle
checked={federated_timeline_removal}
onChange={handleDataChange('federated_timeline_removal')}
icons={false}
id='federated_timeline_removal'
disabled={reject || followers_only}
/>

Wyświetl plik

@ -1,11 +1,10 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import Toggle from 'react-toggle';
import { muteAccount } from 'soapbox/actions/accounts';
import { closeModal } from 'soapbox/actions/modals';
import { toggleHideNotifications, changeMuteDuration } from 'soapbox/actions/mutes';
import { Modal, HStack, Stack, Text } from 'soapbox/components/ui';
import { Modal, HStack, Stack, Text, Toggle } from 'soapbox/components/ui';
import DurationSelector from 'soapbox/features/compose/components/polls/duration-selector';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import { makeGetAccount } from 'soapbox/selectors';
@ -74,7 +73,6 @@ const MuteModal = () => {
<Toggle
checked={notifications}
onChange={toggleNotifications}
icons={false}
/>
</HStack>
</label>
@ -90,7 +88,6 @@ const MuteModal = () => {
<Toggle
checked={duration !== 0}
onChange={toggleAutoExpire}
icons={false}
/>
</HStack>
</label>

Wyświetl plik

@ -1,11 +1,10 @@
import { OrderedSet } from 'immutable';
import React, { useEffect, useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import Toggle from 'react-toggle';
import { changeReportBlock, changeReportForward } from 'soapbox/actions/reports';
import { fetchRules } from 'soapbox/actions/rules';
import { Button, FormGroup, HStack, Stack, Text } from 'soapbox/components/ui';
import { Button, FormGroup, HStack, Stack, Text, Toggle } from 'soapbox/components/ui';
import StatusCheckBox from 'soapbox/features/report/components/status-check-box';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import { isRemote, getDomain } from 'soapbox/utils/accounts';
@ -101,7 +100,6 @@ const OtherActionsStep = ({ account }: IOtherActionsStep) => {
<Toggle
checked={isBlocked}
onChange={handleBlockChange}
icons={false}
id='report-block'
/>
@ -119,7 +117,6 @@ const OtherActionsStep = ({ account }: IOtherActionsStep) => {
<Toggle
checked={isForward}
onChange={handleForwardChange}
icons={false}
id='report-forward'
disabled={isSubmitting}
/>

Wyświetl plik

@ -443,6 +443,7 @@
"compose_form.spoiler_placeholder": "Write your warning here (optional)",
"compose_form.spoiler_remove": "Remove sensitive",
"compose_form.spoiler_title": "Sensitive content",
"compose_group.share_to_followers": "Share with my followers",
"confirmation_modal.cancel": "Cancel",
"confirmations.admin.deactivate_user.confirm": "Deactivate @{name}",
"confirmations.admin.deactivate_user.heading": "Deactivate @{acct}",

Wyświetl plik

@ -51,6 +51,7 @@ import {
COMPOSE_REMOVE_FROM_MENTIONS,
COMPOSE_SET_STATUS,
COMPOSE_EVENT_REPLY,
COMPOSE_SET_GROUP_TIMELINE_VISIBLE,
} from '../actions/compose';
import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS } from '../actions/me';
import { SETTING_CHANGE, FE_NAME } from '../actions/settings';
@ -103,6 +104,7 @@ export const ReducerCompose = ImmutableRecord({
tagHistory: ImmutableList<string>(),
text: '',
to: ImmutableOrderedSet<string>(),
group_timeline_visible: false, // TruthSocial
});
type State = ImmutableMap<string, Compose>;
@ -493,6 +495,8 @@ export default function compose(state = initialState, action: AnyAction) {
return updateCompose(state, action.id, compose => compose.update('to', mentions => mentions!.add(action.account)));
case COMPOSE_REMOVE_FROM_MENTIONS:
return updateCompose(state, action.id, compose => compose.update('to', mentions => mentions!.delete(action.account)));
case COMPOSE_SET_GROUP_TIMELINE_VISIBLE:
return updateCompose(state, action.id, compose => compose.set('group_timeline_visible', action.groupTimelineVisible));
case ME_FETCH_SUCCESS:
return updateCompose(state, 'default', compose => importAccount(compose, action.me));
case ME_PATCH_SUCCESS:

Wyświetl plik

@ -21,7 +21,6 @@
@import 'components/display-name';
@import 'components/columns';
@import 'components/search';
@import 'components/react-toggle';
@import 'components/video-player';
@import 'components/audio-player';
@import 'components/crypto-donate';

Wyświetl plik

@ -1,96 +0,0 @@
.react-toggle {
display: inline-block;
position: relative;
cursor: pointer;
background-color: transparent;
border: 0;
padding: 0;
user-select: none;
-webkit-tap-highlight-color: #0000;
-webkit-tap-highlight-color: transparent;
&:focus-within .react-toggle-track {
@apply ring-2 ring-offset-2 ring-primary-500;
}
}
.react-toggle-screenreader-only {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.react-toggle--disabled {
cursor: not-allowed;
opacity: 0.5;
transition: opacity 0.25s;
}
.react-toggle-track {
@apply bg-gray-500 dark:bg-gray-700 h-[30px] w-[50px] p-0 rounded-full transition-colors;
}
.react-toggle--checked .react-toggle-track {
@apply bg-primary-600 dark:bg-accent-blue;
}
.react-toggle-track-check {
position: absolute;
width: 14px;
height: 10px;
top: 0;
bottom: 0;
margin-top: auto;
margin-bottom: auto;
line-height: 0;
left: 8px;
opacity: 0;
transition: opacity 0.25s ease;
}
.react-toggle--checked .react-toggle-track-check {
opacity: 1;
transition: opacity 0.25s ease;
}
.react-toggle-track-x {
position: absolute;
width: 10px;
height: 10px;
top: 0;
bottom: 0;
margin-top: auto;
margin-bottom: auto;
line-height: 0;
right: 10px;
opacity: 1;
transition: opacity 0.25s ease;
}
.react-toggle--checked .react-toggle-track-x {
opacity: 0;
}
.react-toggle-thumb {
position: absolute;
top: 1px;
left: 1px;
width: 28px;
height: 28px;
border: 1px solid #fff;
border-radius: 50%;
background-color: #fff;
box-sizing: border-box;
transition: all 0.25s ease;
transition-property: border-color, left;
}
.react-toggle--checked .react-toggle-thumb {
@apply border-primary-600 dark:border-accent-blue;
left: 21px;
}

Wyświetl plik

@ -87,7 +87,6 @@
"@types/react-router-dom": "^5.3.3",
"@types/react-sparklines": "^1.7.2",
"@types/react-swipeable-views": "^0.13.1",
"@types/react-toggle": "^4.0.3",
"@types/redux-mock-store": "^1.0.3",
"@types/seedrandom": "^3.0.2",
"@types/semver": "^7.3.9",
@ -165,7 +164,6 @@
"react-sticky-box": "^2.0.0",
"react-swipeable-views": "^0.14.0",
"react-textarea-autosize": "^8.3.4",
"react-toggle": "^4.1.2",
"react-virtuoso": "^4.0.8",
"redux": "^4.1.1",
"redux-immutable": "^4.0.0",

Wyświetl plik

@ -4497,13 +4497,6 @@
dependencies:
"@types/react" "*"
"@types/react-toggle@^4.0.3":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@types/react-toggle/-/react-toggle-4.0.3.tgz#8db98ac8d2c5e8c03c2d3a42027555c1cd2289da"
integrity sha512-57QdMWeeQdRjM2/p+udgYerxUbSkmeUIW18kwUttcci6GHkgxoqCsDZfRtsCsAHcvvM5VBQdtDUEgLWo2e87mA==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@17", "@types/react@^18.0.26":
version "18.0.26"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.26.tgz#8ad59fc01fef8eaf5c74f4ea392621749f0b7917"
@ -14819,13 +14812,6 @@ react-textarea-autosize@^8.3.4:
use-composed-ref "^1.3.0"
use-latest "^1.2.1"
react-toggle@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/react-toggle/-/react-toggle-4.1.2.tgz#b00500832f925ad524356d909821821ae39f6c52"
integrity sha512-4Ohw31TuYQdhWfA6qlKafeXx3IOH7t4ZHhmRdwsm1fQREwOBGxJT+I22sgHqR/w8JRdk+AeMCJXPImEFSrNXow==
dependencies:
classnames "^2.2.5"
react-transition-group@^2.2.1:
version "2.9.0"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"