Files
HKSingleParty/99_references/voyager-main/src/features/auth/PageContext.tsx
2025-05-28 09:55:51 +08:00

342 lines
9.4 KiB
TypeScript

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<HTMLElement | undefined> | undefined;
/**
* @returns true if login dialog was presented
*/
presentLoginIfNeeded: () => boolean;
/**
* @returns private message payload if submitted
*/
presentPrivateMessageCompose: (
item: NewPrivateMessage,
) => Promise<PrivateMessageView | undefined>;
/**
* @returns comment payload if replied
*/
presentCommentEdit: (item: Comment) => Promise<CommentView | undefined>;
/**
* @returns comment payload if replied
*/
presentCommentReply: (
item: CommentReplyItem,
) => Promise<CommentView | undefined>;
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<IPageContext>({
pageRef: undefined,
presentLoginIfNeeded: () => false,
presentCommentEdit: async () => undefined,
presentCommentReply: async () => undefined,
presentPrivateMessageCompose: async () => undefined,
presentReport: () => {},
presentPostEditor: () => {},
presentSelectText: () => {},
presentShareAsImage: () => {},
presentAccountSwitcher: () => {},
presentBanUser: () => {},
presentCreateCrosspost: () => {},
});
interface PageContextProvider {
value: Pick<IPageContext, "pageRef">;
children: React.ReactNode;
}
export function PageContextProvider({ value, children }: PageContextProvider) {
const dispatch = useAppDispatch();
const jwt = useAppSelector(jwtSelector);
const reportRef = useRef<ReportHandle>(null);
const shareAsImageDataRef = useRef<ShareAsImageData | null>(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<MarkdownEditorData>();
const [isMarkdownEditorOpen, setIsMarkdownEditorOpen] = useState(false);
const presentMarkdownEditor = useCallback(
<T extends MarkdownEditorData>(data: Omit<T, "onSubmit">) =>
new Promise<Parameters<T["onSubmit"]>[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<IPageContext["presentPrivateMessageCompose"]>,
[presentMarkdownEditor],
);
const presentCommentEdit = useCallback<IPageContext["presentCommentEdit"]>(
(item) =>
presentMarkdownEditor({
type: "COMMENT_EDIT",
item,
}) as ReturnType<IPageContext["presentCommentEdit"]>,
[presentMarkdownEditor],
);
const presentCommentReply = useCallback<IPageContext["presentCommentReply"]>(
(item) =>
presentMarkdownEditor({
type: "COMMENT_REPLY",
item,
}) as ReturnType<IPageContext["presentCommentReply"]>,
[presentMarkdownEditor],
);
// Markdown editor end
// Edit/new post start
const postItem = useRef<PostView | string>();
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<string | string>();
const [isSelectTextOpen, setIsSelectTextOpen] = useState(false);
const presentSelectText = useCallback((text: string) => {
selectTextItem.current = text;
setIsSelectTextOpen(true);
}, []);
// Select text end
// Ban user start
const banItem = useRef<BanUserPayload>();
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<PostView | undefined>();
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 (
<PageContext.Provider value={currentValue}>
{children}
<LoginModal isOpen={isLoginOpen} setIsOpen={setIsLoginOpen} />
<GenericMarkdownEditorModal
{...markdownEditorData.current!}
isOpen={isMarkdownEditorOpen}
setIsOpen={setIsMarkdownEditorOpen}
/>
<Report ref={reportRef} />
<PostEditorModal
postOrCommunity={postItem.current!}
isOpen={isPostOpen}
setIsOpen={setIsPostOpen}
/>
<BanUserModal
item={banItem.current!}
isOpen={isBanUserOpen}
setIsOpen={setIsBanUserOpen}
/>
<SelectTextModal
text={selectTextItem.current!}
isOpen={isSelectTextOpen}
setIsOpen={setIsSelectTextOpen}
/>
</PageContext.Provider>
);
}