diff --git a/app/soapbox/actions/compose.ts b/app/soapbox/actions/compose.ts index 995f37c15..61303d15b 100644 --- a/app/soapbox/actions/compose.ts +++ b/app/soapbox/actions/compose.ts @@ -22,6 +22,7 @@ import { createStatus } from './statuses'; import type { AutoSuggestion } from 'soapbox/components/autosuggest-input'; import type { Emoji } from 'soapbox/features/emoji'; +import type { Group } from 'soapbox/schemas'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { Account, APIEntity, Status, Tag } from 'soapbox/types/entities'; import type { History } from 'soapbox/types/history'; @@ -168,6 +169,14 @@ const cancelQuoteCompose = () => ({ id: 'compose-modal', }); +const groupComposeModal = (group: Group) => + (dispatch: AppDispatch, getState: () => RootState) => { + const composeId = `group:${group.id}`; + + dispatch(groupCompose(composeId, group.id)); + dispatch(openModal('COMPOSE', { composeId })); + }; + const resetCompose = (composeId = 'compose-modal') => ({ type: COMPOSE_RESET, id: composeId, @@ -829,6 +838,7 @@ export { uploadComposeFail, undoUploadCompose, groupCompose, + groupComposeModal, setGroupTimelineVisible, clearComposeSuggestions, fetchComposeSuggestions, diff --git a/app/soapbox/features/ui/components/__tests__/compose-button.test.tsx b/app/soapbox/features/ui/components/__tests__/compose-button.test.tsx index c98b7aa50..11d9bdea1 100644 --- a/app/soapbox/features/ui/components/__tests__/compose-button.test.tsx +++ b/app/soapbox/features/ui/components/__tests__/compose-button.test.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { IntlProvider } from 'react-intl'; import { Provider } from 'react-redux'; import '@testing-library/jest-dom'; +import { MemoryRouter } from 'react-router-dom'; import { MODAL_OPEN } from 'soapbox/actions/modals'; import { mockStore, rootState } from 'soapbox/jest/test-helpers'; @@ -14,7 +15,9 @@ const renderComposeButton = () => { render( - + + + , ); diff --git a/app/soapbox/features/ui/components/compose-button.tsx b/app/soapbox/features/ui/components/compose-button.tsx index 155a7194e..a2944f699 100644 --- a/app/soapbox/features/ui/components/compose-button.tsx +++ b/app/soapbox/features/ui/components/compose-button.tsx @@ -1,11 +1,24 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import { useLocation, useRouteMatch } from 'react-router-dom'; +import { groupComposeModal } from 'soapbox/actions/compose'; import { openModal } from 'soapbox/actions/modals'; -import { Button } from 'soapbox/components/ui'; +import { Avatar, Button, HStack } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; +import { useGroupLookup } from 'soapbox/hooks/api/groups/useGroupLookup'; const ComposeButton = () => { + const location = useLocation(); + + if (location.pathname.startsWith('/group/')) { + return ; + } + + return ; +}; + +const HomeComposeButton = () => { const dispatch = useAppDispatch(); const onOpenCompose = () => dispatch(openModal('COMPOSE')); @@ -22,4 +35,32 @@ const ComposeButton = () => { ); }; +const GroupComposeButton = () => { + const dispatch = useAppDispatch(); + const match = useRouteMatch<{ groupSlug: string }>('/group/:groupSlug'); + const { entity: group } = useGroupLookup(match?.params.groupSlug || ''); + + if (!group) return null; + + const onOpenCompose = () => { + dispatch(groupComposeModal(group)); + }; + + return ( + + ); +}; + export default ComposeButton; diff --git a/app/soapbox/features/ui/components/floating-action-button.tsx b/app/soapbox/features/ui/components/floating-action-button.tsx index 8800cf444..0e972ef40 100644 --- a/app/soapbox/features/ui/components/floating-action-button.tsx +++ b/app/soapbox/features/ui/components/floating-action-button.tsx @@ -1,20 +1,30 @@ import clsx from 'clsx'; import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; +import { useLocation, useRouteMatch } from 'react-router-dom'; +import { groupComposeModal } from 'soapbox/actions/compose'; import { openModal } from 'soapbox/actions/modals'; -import { Icon } from 'soapbox/components/ui'; +import { Avatar, HStack, Icon } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; +import { useGroupLookup } from 'soapbox/hooks/api/groups/useGroupLookup'; const messages = defineMessages({ publish: { id: 'compose_form.publish', defaultMessage: 'Publish' }, }); -interface IFloatingActionButton { -} - /** FloatingActionButton (aka FAB), a composer button that floats in the corner on mobile. */ -const FloatingActionButton: React.FC = () => { +const FloatingActionButton: React.FC = () => { + const location = useLocation(); + + if (location.pathname.startsWith('/group/')) { + return ; + } + + return ; +}; + +const HomeFAB: React.FC = () => { const intl = useIntl(); const dispatch = useAppDispatch(); @@ -39,4 +49,37 @@ const FloatingActionButton: React.FC = () => { ); }; +const GroupFAB: React.FC = () => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const match = useRouteMatch<{ groupSlug: string }>('/group/:groupSlug'); + const { entity: group } = useGroupLookup(match?.params.groupSlug || ''); + + if (!group) return null; + + const handleOpenComposeModal = () => { + dispatch(groupComposeModal(group)); + }; + + return ( + + ); +}; + export default FloatingActionButton; diff --git a/app/soapbox/features/ui/components/modals/compose-modal.tsx b/app/soapbox/features/ui/components/modals/compose-modal.tsx index 72a06fb7c..358a6d648 100644 --- a/app/soapbox/features/ui/components/modals/compose-modal.tsx +++ b/app/soapbox/features/ui/components/modals/compose-modal.tsx @@ -2,11 +2,12 @@ import clsx from 'clsx'; import React, { useRef } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { cancelReplyCompose, uploadCompose } from 'soapbox/actions/compose'; +import { cancelReplyCompose, setGroupTimelineVisible, uploadCompose } from 'soapbox/actions/compose'; import { openModal, closeModal } from 'soapbox/actions/modals'; import { checkComposeContent } from 'soapbox/components/modal-root'; -import { Modal } from 'soapbox/components/ui'; -import { useAppDispatch, useCompose, useDraggedFiles } from 'soapbox/hooks'; +import { HStack, Modal, Text, Toggle } from 'soapbox/components/ui'; +import { useAppDispatch, useAppSelector, useCompose, useDraggedFiles } from 'soapbox/hooks'; +import { useGroup } from 'soapbox/hooks/api'; import ComposeForm from '../../../compose/components/compose-form'; @@ -18,17 +19,16 @@ const messages = defineMessages({ interface IComposeModal { onClose: (type?: string) => void + composeId?: string } -const ComposeModal: React.FC = ({ onClose }) => { +const ComposeModal: React.FC = ({ onClose, composeId = 'compose-modal' }) => { const intl = useIntl(); const dispatch = useAppDispatch(); const node = useRef(null); - - const composeId = 'compose-modal'; const compose = useCompose(composeId); - const { id: statusId, privacy, in_reply_to: inReplyTo, quote } = compose!; + const { id: statusId, privacy, in_reply_to: inReplyTo, quote, group_id: groupId } = compose!; const { isDragging, isDraggedOver } = useDraggedFiles(node, (files) => { dispatch(uploadCompose(composeId, files, intl)); @@ -60,6 +60,10 @@ const ComposeModal: React.FC = ({ onClose }) => { return ; } else if (privacy === 'direct') { return ; + } else if (inReplyTo && groupId) { + return ; + } else if (groupId) { + return ; } else if (inReplyTo) { return ; } else if (quote) { @@ -79,9 +83,49 @@ const ComposeModal: React.FC = ({ onClose }) => { 'ring-2 ring-offset-2 ring-primary-600': isDraggedOver, })} > - + } + /> ); }; +interface IComposeFormGroupToggle { + composeId: string + groupId: string | null +} + +const ComposeFormGroupToggle: React.FC = ({ composeId, groupId }) => { + const dispatch = useAppDispatch(); + const { group } = useGroup(groupId || '', false); + + const groupTimelineVisible = useAppSelector((state) => !!state.compose.get(composeId)?.group_timeline_visible); + + const handleToggleChange = () => { + dispatch(setGroupTimelineVisible(composeId, !groupTimelineVisible)); + }; + + const labelId = `group-timeline-visible+${composeId}`; + + if (!group) return null; + if (group.locked) return null; + + return ( + + + + + ); +}; + export default ComposeModal; diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index b9c54f32c..96efd8530 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -1042,6 +1042,7 @@ "navbar.login.username.placeholder": "Email or username", "navigation.chats": "Chats", "navigation.compose": "Compose", + "navigation.compose_group": "Compose to Group", "navigation.dashboard": "Dashboard", "navigation.developers": "Developers", "navigation.direct_messages": "Messages", @@ -1055,6 +1056,8 @@ "navigation_bar.compose_direct": "Direct message", "navigation_bar.compose_edit": "Edit post", "navigation_bar.compose_event": "Manage event", + "navigation_bar.compose_group": "Compose to group", + "navigation_bar.compose_group_reply": "Reply to group post", "navigation_bar.compose_quote": "Quote post", "navigation_bar.compose_reply": "Reply to post", "navigation_bar.create_event": "Create new event",