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",