import { useIonModal } from "@ionic/react"; import React, { RefObject, createContext, useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { useAppDispatch, useAppSelector } from "../../store"; import { changeAccount } from "../auth/authSlice"; import { Comment, CommentView, Community, Person, PostView, PrivateMessageView, } from "lemmy-js-client"; import { Report, ReportHandle, ReportableItem } from "../report/Report"; import PostEditorModal from "../post/new/PostEditorModal"; import SelectTextModal from "../shared/SelectTextModal"; import ShareAsImageModal, { ShareAsImageData, } from "../share/asImage/ShareAsImageModal"; import AccountSwitcher from "./AccountSwitcher"; 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; community: Community; } interface IPageContext { // used for ion presentingElement pageRef: RefObject | undefined; /** * @returns true if login dialog was presented */ presentLoginIfNeeded: () => boolean; /** * @returns private message payload if submitted */ presentPrivateMessageCompose: ( item: NewPrivateMessage, ) => Promise; /** * @returns comment payload if replied */ presentCommentEdit: (item: Comment) => Promise; /** * @returns comment payload if replied */ presentCommentReply: ( item: CommentReplyItem, ) => Promise; presentReport: (item: ReportableItem) => void; /** * @param postOrCommunity An existing post to be edited, or the community handle * to submit the new post to */ presentPostEditor: (postOrCommunity: PostView | string) => void; presentSelectText: (text: string) => void; presentShareAsImage: ( post: PostView, comment?: CommentView, comments?: CommentView[], ) => void; presentAccountSwitcher: () => void; presentBanUser: (payload: BanUserPayload) => void; presentCreateCrosspost: (post: PostView) => void; } export const PageContext = createContext({ pageRef: undefined, presentLoginIfNeeded: () => false, presentCommentEdit: async () => undefined, presentCommentReply: async () => undefined, presentPrivateMessageCompose: async () => undefined, presentReport: () => {}, presentPostEditor: () => {}, presentSelectText: () => {}, presentShareAsImage: () => {}, presentAccountSwitcher: () => {}, presentBanUser: () => {}, presentCreateCrosspost: () => {}, }); interface PageContextProvider { value: Pick; children: React.ReactNode; } export function PageContextProvider({ value, children }: PageContextProvider) { const dispatch = useAppDispatch(); const jwt = useAppSelector(jwtSelector); const reportRef = useRef(null); const shareAsImageDataRef = useRef(null); const [presentShareAsImageModal, onDismissShareAsImageModal] = useIonModal( ShareAsImageModal, { dataRef: shareAsImageDataRef, onDismiss: (data?: string, role?: string) => onDismissShareAsImageModal(data, role), }, ); const [isLoginOpen, setIsLoginOpen] = useState(false); const presentLoginIfNeeded = useCallback(() => { if (jwt) return false; setIsLoginOpen(true); return true; }, [jwt]); const presentShareAsImage = useCallback( (post: PostView, comment?: CommentView, comments?: CommentView[]) => { shareAsImageDataRef.current = { post, }; if (comment && comments) { shareAsImageDataRef.current = { ...shareAsImageDataRef.current, comment, comments, }; } presentShareAsImageModal({ cssClass: "save-as-image-modal", initialBreakpoint: 1, breakpoints: [0, 1], handle: false, }); }, [presentShareAsImageModal], ); // 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 (isMarkdownEditorOpen) return; markdownEditorData.current?.onSubmit(undefined); markdownEditorData.current = undefined; return; }, [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(); const [isPostOpen, setIsPostOpen] = useState(false); const presentPostEditor = useCallback( (postOrCommunity: PostView | string) => { postItem.current = postOrCommunity; setIsPostOpen(true); }, [], ); // Edit/new post end // Select text start const selectTextItem = useRef(); const [isSelectTextOpen, setIsSelectTextOpen] = useState(false); const presentSelectText = useCallback((text: string) => { selectTextItem.current = text; setIsSelectTextOpen(true); }, []); // Select text end // Ban user start const banItem = useRef(); const [isBanUserOpen, setIsBanUserOpen] = useState(false); const presentBanUser = useCallback((banUserPayload: BanUserPayload) => { banItem.current = banUserPayload; setIsBanUserOpen(true); }, []); // Ban user end const presentReport = useCallback((item: ReportableItem) => { reportRef.current?.present(item); }, []); const [presentAccountSwitcherModal, onDismissAccountSwitcher] = useIonModal( AccountSwitcher, { onDismiss: (data?: string, role?: string) => onDismissAccountSwitcher(data, role), presentLogin: () => { onDismissAccountSwitcher(); setIsLoginOpen(true); }, onSelectAccount: (account: string) => dispatch(changeAccount(account)), }, ); const presentAccountSwitcher = useCallback(() => { presentAccountSwitcherModal({ cssClass: "small" }); }, [presentAccountSwitcherModal]); const crosspost = useRef(); const [presentCrosspost, onDismissCrosspost] = useIonModal( CreateCrosspostDialog, { onDismiss: (data?: string, role?: string) => onDismissCrosspost(data, role), post: crosspost.current!, }, ); const presentCreateCrosspost = useCallback( (post: PostView) => { crosspost.current = post; presentCrosspost({ cssClass: "transparent-scroll dark" }); }, [presentCrosspost], ); const currentValue = useMemo( () => ({ ...value, presentLoginIfNeeded, presentPrivateMessageCompose, presentCommentEdit, presentCommentReply, presentReport, presentPostEditor, presentSelectText, presentShareAsImage, presentAccountSwitcher, presentBanUser, presentCreateCrosspost, }), [ presentPrivateMessageCompose, presentCommentEdit, presentCommentReply, presentLoginIfNeeded, presentPostEditor, presentReport, presentSelectText, presentShareAsImage, presentAccountSwitcher, presentBanUser, presentCreateCrosspost, value, ], ); return ( {children} ); }