From d73cfaf6762388ea63a31a394a9591882f78bf1d Mon Sep 17 00:00:00 2001 From: Alexander Harding <2166114+aeharding@users.noreply.github.com> Date: Thu, 8 Aug 2024 23:23:35 -0500 Subject: [PATCH] (chore) Refactor markdown modal code (#1562) --- src/features/auth/AccountSwitcher.tsx | 6 +- src/features/auth/PageContext.tsx | 129 ++++++++------- src/features/auth/login/LoginNav.tsx | 4 + src/features/comment/commentSlice.ts | 2 + .../comment/compose/edit/CommentEditModal.tsx | 32 ---- .../compose/reply/CommentReplyModal.tsx | 33 ---- src/features/comment/useCommentActions.ts | 3 +- .../inbox/PrivateMessageMoreActions.tsx | 6 +- src/features/inbox/SendMessageBox.tsx | 5 +- src/features/post/detail/PostHeader.tsx | 3 +- .../modal/GenericMarkdownEditorModal.tsx | 78 +++++++++ .../modal/contents/CommentEditPage.tsx} | 31 ++-- .../modal/contents}/CommentEditorContent.tsx | 6 +- .../modal/contents/CommentReplyPage.tsx} | 149 +++++------------- .../modal/contents}/ItemReplyingTo.tsx | 12 +- .../modal/contents/PrivateMessagePage.tsx | 141 +++++++++++++++++ .../sliding/internal/impl/DMActionsImpl.tsx | 4 +- .../internal/impl/VotableActionsImpl.tsx | 3 +- src/routes/pages/inbox/ConversationPage.tsx | 5 +- .../pages/settings/BlocksSettingsPage.tsx | 8 +- 20 files changed, 386 insertions(+), 274 deletions(-) delete mode 100644 src/features/comment/compose/edit/CommentEditModal.tsx delete mode 100644 src/features/comment/compose/reply/CommentReplyModal.tsx create mode 100644 src/features/shared/markdown/editing/modal/GenericMarkdownEditorModal.tsx rename src/features/{comment/compose/edit/CommentEdit.tsx => shared/markdown/editing/modal/contents/CommentEditPage.tsx} (72%) rename src/features/{comment/compose => shared/markdown/editing/modal/contents}/CommentEditorContent.tsx (55%) rename src/features/{comment/compose/reply/CommentReply.tsx => shared/markdown/editing/modal/contents/CommentReplyPage.tsx} (67%) rename src/features/{comment/compose/reply => shared/markdown/editing/modal/contents}/ItemReplyingTo.tsx (78%) create mode 100644 src/features/shared/markdown/editing/modal/contents/PrivateMessagePage.tsx diff --git a/src/features/auth/AccountSwitcher.tsx b/src/features/auth/AccountSwitcher.tsx index 1ec5cb90e8..33d7326287 100644 --- a/src/features/auth/AccountSwitcher.tsx +++ b/src/features/auth/AccountSwitcher.tsx @@ -40,15 +40,15 @@ type AccountSwitcherProps = { } ); -export default function AccountSwitcherContainer(props: AccountSwitcherProps) { +export default function AccountSwitcher(props: AccountSwitcherProps) { return ( - + ); } -function AccountSwitcher({ +function AccountSwitcherContents({ onDismiss, onSelectAccount, allowEdit = true, diff --git a/src/features/auth/PageContext.tsx b/src/features/auth/PageContext.tsx index 600d7c6c56..8f91ee1397 100644 --- a/src/features/auth/PageContext.tsx +++ b/src/features/auth/PageContext.tsx @@ -8,10 +8,8 @@ import React, { useRef, useState, } from "react"; -import { CommentReplyItem } from "../comment/compose/reply/CommentReply"; import { useAppDispatch, useAppSelector } from "../../store"; import { changeAccount } from "../auth/authSlice"; -import CommentReplyModal from "../comment/compose/reply/CommentReplyModal"; import { Comment, CommentView, @@ -20,7 +18,6 @@ import { PostView, PrivateMessageView, } from "lemmy-js-client"; -import CommentEditModal from "../comment/compose/edit/CommentEditModal"; import { Report, ReportHandle, ReportableItem } from "../report/Report"; import PostEditorModal from "../post/new/PostEditorModal"; import SelectTextModal from "../shared/SelectTextModal"; @@ -32,6 +29,11 @@ import { jwtSelector } from "./authSelectors"; import BanUserModal from "../moderation/ban/BanUserModal"; import CreateCrosspostDialog from "../post/crosspost/create/CreateCrosspostDialog"; import LoginModal from "./login/LoginModal"; +import GenericMarkdownEditorModal, { + MarkdownEditorData, +} from "../shared/markdown/editing/modal/GenericMarkdownEditorModal"; +import { NewPrivateMessage } from "../shared/markdown/editing/modal/contents/PrivateMessagePage"; +import { CommentReplyItem } from "../shared/markdown/editing/modal/contents/CommentReplyPage"; export interface BanUserPayload { user: Person; @@ -47,18 +49,24 @@ interface IPageContext { */ presentLoginIfNeeded: () => boolean; + /** + * @returns private message payload if submitted + */ + presentPrivateMessageCompose: ( + item: NewPrivateMessage, + ) => Promise; + /** * @returns comment payload if replied */ - presentCommentReply: ( - item: CommentReplyItem, - ) => Promise; + presentCommentEdit: (item: Comment) => Promise; /** - * Will mutate comment in store, which view should be linked to for updates - * That's why this does not return anything + * @returns comment payload if replied */ - presentCommentEdit: (item: Comment) => void; + presentCommentReply: ( + item: CommentReplyItem, + ) => Promise; presentReport: (item: ReportableItem) => void; @@ -86,8 +94,9 @@ interface IPageContext { export const PageContext = createContext({ pageRef: undefined, presentLoginIfNeeded: () => false, + presentCommentEdit: async () => undefined, presentCommentReply: async () => undefined, - presentCommentEdit: () => false, + presentPrivateMessageCompose: async () => undefined, presentReport: () => {}, presentPostEditor: () => {}, presentSelectText: () => {}, @@ -148,41 +157,58 @@ export function PageContextProvider({ value, children }: PageContextProvider) { [presentShareAsImageModal], ); - // Comment reply start - const commentReplyItem = useRef(); - const commentReplyCb = useRef< - | ((replied: CommentView | PrivateMessageView | undefined) => void) - | undefined - >(); - const [isReplyOpen, setIsReplyOpen] = useState(false); - const presentCommentReply = useCallback((item: CommentReplyItem) => { - const promise = new Promise( - (resolve) => (commentReplyCb.current = resolve), - ); - - commentReplyItem.current = item; - setIsReplyOpen(true); - - return promise; - }, []); + // Markdown editor start + const markdownEditorData = useRef(); + const [isMarkdownEditorOpen, setIsMarkdownEditorOpen] = useState(false); + const presentMarkdownEditor = useCallback( + (data: Omit) => + new Promise[0]>((resolve) => { + markdownEditorData.current = { + ...data, + onSubmit: resolve, + } as T; + setIsMarkdownEditorOpen(true); + }), + [], + ); useEffect(() => { - if (isReplyOpen) return; + if (isMarkdownEditorOpen) return; - commentReplyCb.current?.(undefined); - commentReplyCb.current = undefined; + markdownEditorData.current?.onSubmit(undefined); + markdownEditorData.current = undefined; return; - }, [isReplyOpen]); - // Comment reply end - - // Edit comment start - const commentEditItem = useRef(); - const [isEditCommentOpen, setIsEditCommentOpen] = useState(false); - const presentCommentEdit = useCallback((item: Comment) => { - commentEditItem.current = item; - setIsEditCommentOpen(true); - }, []); - // Edit comment end + }, [isMarkdownEditorOpen]); + + const presentPrivateMessageCompose = useCallback< + IPageContext["presentPrivateMessageCompose"] + >( + (item) => + presentMarkdownEditor({ + type: "PRIVATE_MESSAGE", + item, + }) as ReturnType, + [presentMarkdownEditor], + ); + + const presentCommentEdit = useCallback( + (item) => + presentMarkdownEditor({ + type: "COMMENT_EDIT", + item, + }) as ReturnType, + [presentMarkdownEditor], + ); + + const presentCommentReply = useCallback( + (item) => + presentMarkdownEditor({ + type: "COMMENT_REPLY", + item, + }) as ReturnType, + [presentMarkdownEditor], + ); + // Markdown editor end // Edit/new post start const postItem = useRef(); @@ -257,8 +283,9 @@ export function PageContextProvider({ value, children }: PageContextProvider) { () => ({ ...value, presentLoginIfNeeded, - presentCommentReply, + presentPrivateMessageCompose, presentCommentEdit, + presentCommentReply, presentReport, presentPostEditor, presentSelectText, @@ -268,6 +295,7 @@ export function PageContextProvider({ value, children }: PageContextProvider) { presentCreateCrosspost, }), [ + presentPrivateMessageCompose, presentCommentEdit, presentCommentReply, presentLoginIfNeeded, @@ -287,19 +315,10 @@ export function PageContextProvider({ value, children }: PageContextProvider) { {children} - { - commentReplyCb.current?.(reply); - commentReplyCb.current = undefined; - }} - /> - void; -} - -export default function CommentEditModal({ - item, - isOpen, - setIsOpen, -}: CommentEditModalProps) { - return ( - - {({ setCanDismiss, dismiss }) => ( - dismiss()} - /> - )} - - ); -} diff --git a/src/features/comment/compose/reply/CommentReplyModal.tsx b/src/features/comment/compose/reply/CommentReplyModal.tsx deleted file mode 100644 index 2a1d2992a8..0000000000 --- a/src/features/comment/compose/reply/CommentReplyModal.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import CommentReply, { CommentReplyItem } from "./CommentReply"; -import React from "react"; -import { DynamicDismissableModal } from "../../../shared/DynamicDismissableModal"; -import { CommentView, PrivateMessageView } from "lemmy-js-client"; - -interface CommentReplyModalProps { - item: CommentReplyItem; - isOpen: boolean; - setIsOpen: (isOpen: boolean) => void; - onReply: (reply: CommentView | PrivateMessageView | undefined) => void; -} - -export default function CommentReplyModal({ - item, - isOpen, - setIsOpen, - onReply, -}: CommentReplyModalProps) { - return ( - - {({ setCanDismiss, dismiss }) => ( - { - dismiss(); - onReply(replied); - }} - /> - )} - - ); -} diff --git a/src/features/comment/useCommentActions.ts b/src/features/comment/useCommentActions.ts index 411884cd0b..609b98d517 100644 --- a/src/features/comment/useCommentActions.ts +++ b/src/features/comment/useCommentActions.ts @@ -239,8 +239,7 @@ export default function useCommentActions({ const reply = await presentCommentReply(commentView); - if (reply && !("private_message" in reply)) - prependComments([reply]); + if (reply) prependComments([reply]); })(); }, }, diff --git a/src/features/inbox/PrivateMessageMoreActions.tsx b/src/features/inbox/PrivateMessageMoreActions.tsx index ca41c54543..601cb90864 100644 --- a/src/features/inbox/PrivateMessageMoreActions.tsx +++ b/src/features/inbox/PrivateMessageMoreActions.tsx @@ -41,7 +41,7 @@ export default forwardRef< >(function PrivateMessageMoreActions({ item, markReadAction }, ref) { const dispatch = useAppDispatch(); const [presentActionSheet] = useIonActionSheet(); - const { presentReport, presentSelectText, presentCommentReply } = + const { presentReport, presentSelectText, presentPrivateMessageCompose } = useContext(PageContext); const { navigateToUser } = useAppNavigation(); @@ -58,7 +58,7 @@ export default forwardRef< icon: arrowUndoOutline, handler: () => { (async () => { - await presentCommentReply({ + await presentPrivateMessageCompose({ private_message: { recipient: item.private_message.creator_id === @@ -113,7 +113,7 @@ export default forwardRef< markReadAction, item, isBlocked, - presentCommentReply, + presentPrivateMessageCompose, dispatch, presentSelectText, navigateToUser, diff --git a/src/features/inbox/SendMessageBox.tsx b/src/features/inbox/SendMessageBox.tsx index e1f2986fb8..6659965a50 100644 --- a/src/features/inbox/SendMessageBox.tsx +++ b/src/features/inbox/SendMessageBox.tsx @@ -88,7 +88,7 @@ export default function SendMessageBox({ const [value, setValue] = useState(""); const client = useClient(); const presentToast = useAppToast(); - const { presentCommentReply } = useContext(PageContext); + const { presentPrivateMessageCompose } = useContext(PageContext); const send = useCallback(async () => { setLoading(true); @@ -132,13 +132,12 @@ export default function SendMessageBox({ shape="round" fill="clear" onClick={async () => { - const message = await presentCommentReply({ + const message = await presentPrivateMessageCompose({ private_message: { recipient }, value, }); if (!message) return; - if (!("private_message" in message)) return; scrollToBottom?.(); setValue(""); diff --git a/src/features/post/detail/PostHeader.tsx b/src/features/post/detail/PostHeader.tsx index ab525dbffd..ecf9945947 100644 --- a/src/features/post/detail/PostHeader.tsx +++ b/src/features/post/detail/PostHeader.tsx @@ -294,8 +294,7 @@ function PostHeader({ const reply = await presentCommentReply(post); - if (reply && !("private_message" in reply)) - onPrependComment?.(reply); + if (reply) onPrependComment?.(reply); }} /> diff --git a/src/features/shared/markdown/editing/modal/GenericMarkdownEditorModal.tsx b/src/features/shared/markdown/editing/modal/GenericMarkdownEditorModal.tsx new file mode 100644 index 0000000000..8c5f85fead --- /dev/null +++ b/src/features/shared/markdown/editing/modal/GenericMarkdownEditorModal.tsx @@ -0,0 +1,78 @@ +import { Comment, CommentView, PrivateMessageView } from "lemmy-js-client"; +import { DynamicDismissableModal } from "../../../DynamicDismissableModal"; +import CommentReplyPage, { + CommentReplyItem, +} from "./contents/CommentReplyPage"; +import CommentEditPage from "./contents/CommentEditPage"; +import PrivateMessagePage, { + NewPrivateMessage, +} from "./contents/PrivateMessagePage"; + +interface Data { + type: Type; + item: Item; + onSubmit: (result: Result | undefined) => void; +} + +export type MarkdownEditorData = + | Data<"COMMENT_REPLY", CommentReplyItem> + | Data<"COMMENT_EDIT", Comment, CommentView> + | Data<"PRIVATE_MESSAGE", NewPrivateMessage, PrivateMessageView>; + +type GenericMarkdownEditorModalProps = MarkdownEditorData & { + isOpen: boolean; + setIsOpen: (isOpen: boolean) => void; +}; + +export default function GenericMarkdownEditorModal({ + type, + item, + isOpen, + setIsOpen, + onSubmit, +}: GenericMarkdownEditorModalProps) { + return ( + + {({ setCanDismiss, dismiss }) => { + switch (type) { + case "COMMENT_REPLY": { + return ( + { + dismiss(); + onSubmit(replied); + }} + /> + ); + } + case "COMMENT_EDIT": { + return ( + { + dismiss(); + onSubmit(replied); + }} + /> + ); + } + case "PRIVATE_MESSAGE": { + return ( + { + dismiss(); + onSubmit(replied); + }} + /> + ); + } + } + }} + + ); +} diff --git a/src/features/comment/compose/edit/CommentEdit.tsx b/src/features/shared/markdown/editing/modal/contents/CommentEditPage.tsx similarity index 72% rename from src/features/comment/compose/edit/CommentEdit.tsx rename to src/features/shared/markdown/editing/modal/contents/CommentEditPage.tsx index b0ecc9a697..4eb492662e 100644 --- a/src/features/comment/compose/edit/CommentEdit.tsx +++ b/src/features/shared/markdown/editing/modal/contents/CommentEditPage.tsx @@ -5,27 +5,28 @@ import { IonTitle, IonIcon, } from "@ionic/react"; -import { Comment } from "lemmy-js-client"; +import { Comment, CommentView } from "lemmy-js-client"; import { useEffect, useState } from "react"; -import { useAppDispatch } from "../../../../store"; -import { Centered, Spinner } from "../../../auth/login/LoginNav"; -import { editComment } from "../../commentSlice"; -import { DismissableProps } from "../../../shared/DynamicDismissableModal"; -import CommentEditorContent from "../CommentEditorContent"; -import useAppToast from "../../../../helpers/useAppToast"; -import AppHeader from "../../../shared/AppHeader"; -import { isIosTheme } from "../../../../helpers/device"; import { arrowBackSharp, send } from "ionicons/icons"; +import { useAppDispatch } from "../../../../../../store"; +import useAppToast from "../../../../../../helpers/useAppToast"; +import { editComment } from "../../../../../comment/commentSlice"; +import AppHeader from "../../../../AppHeader"; +import { isIosTheme } from "../../../../../../helpers/device"; +import { Centered, Spinner } from "../../../../../auth/login/LoginNav"; +import CommentEditorContent from "./CommentEditorContent"; +import { DismissableProps } from "../../../../DynamicDismissableModal"; -type CommentEditingProps = DismissableProps & { +type CommentEditPageProps = Omit & { + dismiss: (reply?: CommentView | undefined) => void; item: Comment; }; -export default function CommentEdit({ +export default function CommentEditPage({ item, setCanDismiss, dismiss, -}: CommentEditingProps) { +}: CommentEditPageProps) { const dispatch = useAppDispatch(); const [replyContent, setReplyContent] = useState(item.content); const presentToast = useAppToast(); @@ -42,8 +43,10 @@ export default function CommentEdit({ setLoading(true); + let comment; + try { - await dispatch(editComment(item.id, replyContent)); + comment = await dispatch(editComment(item.id, replyContent)); } catch (error) { presentToast({ message: "Problem saving your changes. Please try again.", @@ -65,7 +68,7 @@ export default function CommentEdit({ }); setCanDismiss(true); - dismiss(); + dismiss(comment); } return ( diff --git a/src/features/comment/compose/CommentEditorContent.tsx b/src/features/shared/markdown/editing/modal/contents/CommentEditorContent.tsx similarity index 55% rename from src/features/comment/compose/CommentEditorContent.tsx rename to src/features/shared/markdown/editing/modal/contents/CommentEditorContent.tsx index 9f124560e4..5917b8c6f8 100644 --- a/src/features/comment/compose/CommentEditorContent.tsx +++ b/src/features/shared/markdown/editing/modal/contents/CommentEditorContent.tsx @@ -1,7 +1,7 @@ -import { preventPhotoswipeGalleryFocusTrap } from "../../media/gallery/GalleryImg"; import { forwardRef } from "react"; -import Editor, { EditorProps } from "../../shared/markdown/editing/Editor"; -import { MarkdownEditorIonContent } from "../../shared/markdown/editing/MarkdownToolbar"; +import Editor, { EditorProps } from "../../Editor"; +import { MarkdownEditorIonContent } from "../../MarkdownToolbar"; +import { preventPhotoswipeGalleryFocusTrap } from "../../../../../media/gallery/GalleryImg"; export default forwardRef( function CommentEditorContent(props, ref) { diff --git a/src/features/comment/compose/reply/CommentReply.tsx b/src/features/shared/markdown/editing/modal/contents/CommentReplyPage.tsx similarity index 67% rename from src/features/comment/compose/reply/CommentReply.tsx rename to src/features/shared/markdown/editing/modal/contents/CommentReplyPage.tsx index 842c721d8c..5a24d255ee 100644 --- a/src/features/comment/compose/reply/CommentReply.tsx +++ b/src/features/shared/markdown/editing/modal/contents/CommentReplyPage.tsx @@ -11,33 +11,28 @@ import { import { CommentReplyView, CommentView, - Person, PersonMentionView, PostView, - PrivateMessageView, ResolveObjectResponse, } from "lemmy-js-client"; import { useEffect, useMemo, useRef, useState } from "react"; import ItemReplyingTo from "./ItemReplyingTo"; -import useClient from "../../../../helpers/useClient"; -import { useAppDispatch, useAppSelector } from "../../../../store"; -import { Centered, Spinner } from "../../../auth/login/LoginNav"; +import CommentEditorContent from "./CommentEditorContent"; +import { arrowBackSharp, send } from "ionicons/icons"; +import { useAppDispatch, useAppSelector } from "../../../../../../store"; +import useClient from "../../../../../../helpers/useClient"; +import useAppToast from "../../../../../../helpers/useAppToast"; import { loggedInAccountsSelector, userHandleSelector, -} from "../../../auth/authSelectors"; -import { receivedComments } from "../../commentSlice"; -import CommentEditorContent from "../CommentEditorContent"; -import useAppToast from "../../../../helpers/useAppToast"; -import { isLemmyError } from "../../../../helpers/lemmyErrors"; -import AccountSwitcher from "../../../auth/AccountSwitcher"; -import { getClient } from "../../../../services/lemmy"; -import AppHeader from "../../../shared/AppHeader"; -import { arrowBackSharp, send } from "ionicons/icons"; -import { isIosTheme } from "../../../../helpers/device"; -import { getHandle } from "../../../../helpers/lemmy"; -import { privateMessageSendFailed } from "../../../../helpers/toastMessages"; -import { receivedMessages } from "../../../inbox/inboxSlice"; +} from "../../../../../auth/authSelectors"; +import { getClient } from "../../../../../../services/lemmy"; +import AccountSwitcher from "../../../../../auth/AccountSwitcher"; +import { isLemmyError } from "../../../../../../helpers/lemmyErrors"; +import { receivedComments } from "../../../../../comment/commentSlice"; +import AppHeader from "../../../../AppHeader"; +import { isIosTheme } from "../../../../../../helpers/device"; +import { Centered, Spinner } from "../../../../../auth/login/LoginNav"; export const UsernameIonText = styled(IonText)` font-size: 0.7em; @@ -48,32 +43,14 @@ export const TitleContainer = styled.div` line-height: 1; `; -/** - * Special case to compose a private message - * (everything else has a Lemmy type for context- - * e.g. post or comment replying to, - * but not necessarily DMs) - */ -type PrivateMessage = { - private_message: { - recipient: Person; - }; - - /** - * Prefilled content - */ - value?: string; -}; - export type CommentReplyItem = | CommentView | PostView | PersonMentionView - | CommentReplyView - | PrivateMessage; + | CommentReplyView; -type CommentReplyProps = { - dismiss: (reply?: CommentView | PrivateMessageView | undefined) => void; +type CommentReplyPageProps = { + dismiss: (reply?: CommentView | undefined) => void; setCanDismiss: (canDismiss: boolean) => void; item: CommentReplyItem; }; @@ -81,17 +58,15 @@ type CommentReplyProps = { /** * New comment replying to something */ -export default function CommentReply({ +export default function CommentReplyPage({ dismiss, setCanDismiss, item, -}: CommentReplyProps) { +}: CommentReplyPageProps) { const comment = "comment" in item ? item.comment : undefined; const dispatch = useAppDispatch(); - const [replyContent, setReplyContent] = useState( - "private_message" in item ? (item.value ?? "") : "", - ); + const [replyContent, setReplyContent] = useState(""); const client = useClient(); const presentToast = useAppToast(); const [loading, setLoading] = useState(false); @@ -126,9 +101,6 @@ export default function CommentReply({ onDismiss: (data?: string, role?: string) => onDismissAccountSwitcher(data, role), onSelectAccount: async (account: string) => { - if ("private_message" in item) - throw new Error("Cannot switch account on private message reply"); - // Switching back to local account if (account === userHandle) { resolvedRef.current = undefined; @@ -176,36 +148,6 @@ export default function CommentReply({ setLoading(true); - let message; - - if ("private_message" in item) { - try { - message = await client.createPrivateMessage({ - content: replyContent, - recipient_id: item.private_message.recipient.id, - }); - } catch (error) { - presentToast(privateMessageSendFailed); - - throw error; - } finally { - setLoading(false); - } - - presentToast({ - message: "Message sent!", - color: "primary", - position: "top", - centerText: true, - fullscreen: true, - }); - - setCanDismiss(true); - dismiss(message.private_message_view); - dispatch(receivedMessages([message.private_message_view])); - return; - } - let reply; let silentError = false; @@ -305,35 +247,30 @@ export default function CommentReply({ - {"private_message" in item ? ( - <>To {getHandle(item.private_message.recipient)} - ) : ( - - { - if (accounts?.length === 1) return; - if ("private_message" in item) return; - - presentAccountSwitcher({ - cssClass: "small", - onDidDismiss: () => { - requestAnimationFrame(() => { - textareaRef.current?.focus(); - }); - }, - }); - }} - > - New Comment -
- - {selectedAccount} - -
-
{" "} - {loading && } -
- )} + + { + if (accounts?.length === 1) return; + + presentAccountSwitcher({ + cssClass: "small", + onDidDismiss: () => { + requestAnimationFrame(() => { + textareaRef.current?.focus(); + }); + }, + }); + }} + > + New Comment +
+ + {selectedAccount} + +
+
{" "} + {loading && } +
& { + dismiss: (reply?: PrivateMessageView | undefined) => void; + item: NewPrivateMessage; +}; + +export default function PrivateMessagePage({ + item, + setCanDismiss, + dismiss, +}: CommentEditingProps) { + const client = useClient(); + const dispatch = useAppDispatch(); + const [replyContent, setReplyContent] = useState(item.value ?? ""); + const presentToast = useAppToast(); + const [loading, setLoading] = useState(false); + const isSubmitDisabled = !replyContent.trim() || loading; + + useEffect(() => { + setCanDismiss(!replyContent); + }, [replyContent, setCanDismiss]); + + async function submit() { + if (isSubmitDisabled) return; + + setLoading(true); + + let message; + + try { + message = await client.createPrivateMessage({ + content: replyContent, + recipient_id: item.private_message.recipient.id, + }); + } catch (error) { + presentToast(privateMessageSendFailed); + + throw error; + } finally { + setLoading(false); + } + + presentToast({ + message: "Message sent!", + color: "primary", + position: "top", + centerText: true, + fullscreen: true, + }); + + setCanDismiss(true); + dismiss(message.private_message_view); + dispatch(receivedMessages([message.private_message_view])); + } + + return ( + <> + + + + dismiss()}> + {isIosTheme() ? ( + "Cancel" + ) : ( + + )} + + + + + To {getHandle(item.private_message.recipient)} + {loading && } + + + + + {isIosTheme() ? "Send" : } + + + + + + + + ); +} diff --git a/src/features/shared/sliding/internal/impl/DMActionsImpl.tsx b/src/features/shared/sliding/internal/impl/DMActionsImpl.tsx index 4405b6c8d5..d56d2a179e 100644 --- a/src/features/shared/sliding/internal/impl/DMActionsImpl.tsx +++ b/src/features/shared/sliding/internal/impl/DMActionsImpl.tsx @@ -11,7 +11,7 @@ export default function DMActionsImpl({ ...rest }: ComponentProps) { const dispatch = useAppDispatch(); - const { presentCommentReply } = useContext(PageContext); + const { presentPrivateMessageCompose } = useContext(PageContext); const shared = useSharedInboxActions(item); @@ -22,7 +22,7 @@ export default function DMActionsImpl({ }} currentVote={0} reply={async () => { - await presentCommentReply({ + await presentPrivateMessageCompose({ private_message: { recipient: item.private_message.creator_id === diff --git a/src/features/shared/sliding/internal/impl/VotableActionsImpl.tsx b/src/features/shared/sliding/internal/impl/VotableActionsImpl.tsx index 09a3d6bdc6..1a5a6b44a0 100644 --- a/src/features/shared/sliding/internal/impl/VotableActionsImpl.tsx +++ b/src/features/shared/sliding/internal/impl/VotableActionsImpl.tsx @@ -109,8 +109,7 @@ export function VotableActionsImpl({ } const reply = await presentCommentReply(item); - if (!isPost && reply && !("private_message" in reply)) - prependComments([reply]); + if (!isPost && reply) prependComments([reply]); }, [ item, isPost, diff --git a/src/routes/pages/inbox/ConversationPage.tsx b/src/routes/pages/inbox/ConversationPage.tsx index 0010b3a69b..0fe80646f3 100644 --- a/src/routes/pages/inbox/ConversationPage.tsx +++ b/src/routes/pages/inbox/ConversationPage.tsx @@ -132,10 +132,7 @@ export default function ConversationPage() { padding-inline-end: 120px; `} > - e.stopPropagation()} - > + {handle} diff --git a/src/routes/pages/settings/BlocksSettingsPage.tsx b/src/routes/pages/settings/BlocksSettingsPage.tsx index f9f4aed040..6f54995602 100644 --- a/src/routes/pages/settings/BlocksSettingsPage.tsx +++ b/src/routes/pages/settings/BlocksSettingsPage.tsx @@ -8,10 +8,6 @@ import { IonToolbar, } from "@ionic/react"; import AppContent from "../../../features/shared/AppContent"; -import { - TitleContainer, - UsernameIonText, -} from "../../../features/comment/compose/reply/CommentReply"; import { useAppSelector } from "../../../store"; import { userHandleSelector } from "../../../features/auth/authSelectors"; import FilterNsfw from "../../../features/settings/blocks/FilterNsfw"; @@ -28,6 +24,10 @@ import { ListEditorProvider, } from "../../../features/shared/ListEditor"; import { CenteredSpinner } from "../../../features/shared/CenteredSpinner"; +import { + TitleContainer, + UsernameIonText, +} from "../../../features/shared/markdown/editing/modal/contents/CommentReplyPage"; export default function BlocksSettingsPage() { const pageRef = useRef(null);