Newer
Older
taehui / taehui-fe / src / app / [language] / forum / [forumID] / edit / [[...essayID]] / page.tsx
@taehui taehui on 17 Aug 10 KB v1.0.0
"use client";

import AutoEssayWindow from "@/app/[language]/forum/components/AutoEssayWindow";
import FileUploading from "@/app/[language]/forum/components/FileUploadingWindow";
import TextView from "@/app/[language]/forum/components/TextView";
import useGetEssay from "@/app/[language]/forum/query/useGetEssay";
import useGetForum from "@/app/[language]/forum/query/useGetForum";
import usePostAutoEssay from "@/app/[language]/forum/query/usePostAutoEssay";
import usePostEssay from "@/app/[language]/forum/query/usePostEssay";
import usePostFile from "@/app/[language]/forum/query/usePostFile";
import usePutAutoEssay from "@/app/[language]/forum/query/usePutAutoEssay";
import usePutEssay from "@/app/[language]/forum/query/usePutEssay";
import { useForumStore, useTaehuiStore } from "@/state/Stores";
import { useQueryClient } from "@tanstack/react-query";
import { observer } from "mobx-react-lite";
import { useTranslations } from "next-intl";
import { useParams, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { PencilFill } from "react-bootstrap-icons";
import Button from "react-bootstrap/Button";
import CloseButton from "react-bootstrap/CloseButton";
import Col from "react-bootstrap/Col";
import FormCheck from "react-bootstrap/FormCheck";
import FormControl from "react-bootstrap/FormControl";
import InputGroup from "react-bootstrap/InputGroup";
import InputGroupText from "react-bootstrap/InputGroupText";
import Row from "react-bootstrap/Row";
import Stack from "react-bootstrap/Stack";
import { toast } from "react-toastify";
import { useWindowArea } from "taehui-lib/fe-utilities";

export default observer(() => {
  const {
    isTitleTextFilled,
    title,
    text,
    setTitle,
    setText,
    autoEssayID,
    setAutoEssayID,
    setAutoEssayOpened,
  } = useForumStore();

  const inputView = useRef<HTMLButtonElement>(null);

  const { taehuiAvatarID, taehuiAvatarName } = useTaehuiStore();

  const t = useTranslations();
  const [isTestMode, setTestMode] = useState(false);
  const [testText, setTestText] = useState("");

  const { forumID, essayID: [essayID] = [""] } = useParams<{
    forumID: string;
    essayID: string[];
  }>();

  const textView = useRef<HTMLTextAreaElement>(null);
  const [textViewHeight, setTextViewHeight] = useState("");

  const { windowLength, windowHeight } = useWindowArea();

  useLayoutEffect(() => {
    const textViewArea = textView.current?.getBoundingClientRect();
    const inputViewArea = inputView.current?.getBoundingClientRect();
    setTextViewHeight(
      `${windowHeight - 8 * 4 - ((textViewArea?.top ?? 0) + (inputViewArea?.height ?? 0))}px`,
    );
  }, [windowLength, windowHeight]);

  const { mutateAsync: postAutoEssay } = usePostAutoEssay();
  const { mutateAsync: putAutoEssay } = usePutAutoEssay();
  const { mutateAsync: putEssay } = usePutEssay();
  const { mutateAsync: postFile, isPending: isPostFilePending } = usePostFile();
  const { mutateAsync: postEssay } = usePostEssay();

  const { data: essay, isFetched: isEssayLoaded } = useGetEssay(essayID);
  const searchParams = useSearchParams();

  const { push } = useRouter();

  useEffect(() => {
    if (isEssayLoaded) {
      setTitle(essay.title);
      setText(essay.text);
    }
  }, [essay, isEssayLoaded, setText, setTitle]);

  const setTag = (tag: string) => {
    const { current } = textView;
    if (current) {
      const { selectionStart, selectionEnd } = current;
      const t = text.substring(selectionStart, selectionEnd);
      const tag0 = `<${tag}>`;
      const tag1 = `</${tag}>`;
      if (t.startsWith(tag0) || t.endsWith(tag1)) {
        setText(
          text.substring(0, selectionStart) +
            text.substring(
              selectionStart + tag0.length,
              selectionStart + t.length - tag1.length,
            ) +
            text.substring(selectionEnd),
        );
        setTimeout(() => {
          current.selectionStart = selectionStart;
          current.selectionEnd = selectionEnd - tag0.length - tag1.length;
          current.focus();
        }, 0);
      } else {
        setText(
          text.substring(0, selectionStart) +
            tag0 +
            text.substring(selectionStart, selectionEnd) +
            tag1 +
            text.substring(selectionEnd),
        );
        setTimeout(() => {
          current.selectionStart = selectionStart;
          current.selectionEnd = selectionEnd + tag0.length + tag1.length;
          current.focus();
        }, 0);
      }
    }
  };

  useEffect(() => {
    if (!essayID) {
      setTitle("");
      setText("");
    }
  }, [essayID, setText, setTitle]);

  useEffect(() => {
    const postAutoEssaysID = setInterval(async () => {
      if (isTitleTextFilled) {
        if (typeof autoEssayID === "number") {
          await putAutoEssay({ autoEssayID, title, text });
        } else {
          const { autoEssayID } = await postAutoEssay({
            forumID,
            title,
            text,
          });
          setAutoEssayID(autoEssayID);
        }
      }
    }, 60000);

    return () => {
      clearInterval(postAutoEssaysID);
    };
  }, [
    autoEssayID,
    forumID,
    isTitleTextFilled,
    postAutoEssay,
    putAutoEssay,
    setAutoEssayID,
    t,
    text,
    title,
  ]);

  useLayoutEffect(() => {
    if (isTestMode) {
      setTestText(text);
    }
  }, [isTestMode, text]);

  const {
    data: { title: forumTitle },
  } = useGetForum(forumID, Number.parseInt(searchParams.get("page") ?? "1"));

  const onFileUpload = () => {
    const inputElement = document.createElement("input");
    inputElement.type = "file";
    inputElement.accept = "audio/*,image/*,video/*";
    inputElement.addEventListener("change", async ({ target }) => {
      const file = (target as HTMLInputElement).files?.[0];
      const { current } = textView;
      if (file && current) {
        const text = await postFile({
          file,
          textView: current,
        });
        if (text) {
          setText(text);
        }
      }
    });
    inputElement.click();
  };

  const { back } = useRouter();

  const queryClient = useQueryClient();

  return (
    <>
      <Stack gap={2}>
        <Row>
          <Col>
            <h4>{forumTitle}</h4>
          </Col>
          <Col xs="auto">
            <FormCheck
              id="isTestMode"
              label={t("setTestMode")}
              checked={isTestMode}
              onChange={() => {
                setTestMode((prevState) => !prevState);
              }}
            />
          </Col>
          <Col xs="auto">
            <CloseButton onClick={back} />
          </Col>
        </Row>
        <hr />
        {isTestMode ? (
          <>
            <TextView
              title={title}
              text={testText}
              avatarID={taehuiAvatarID}
              avatarName={taehuiAvatarName}
            />
            <hr />
          </>
        ) : (
          <>
            <InputGroup>
              <InputGroupText>{t("title")}</InputGroupText>
              <FormControl
                type="text"
                isInvalid={!title}
                isValid={!!title}
                placeholder={t("title")}
                value={title}
                onChange={({ target: { value } }) => {
                  setTitle(value);
                }}
              />
              <Button
                onClick={async () => {
                  await queryClient.invalidateQueries({
                    queryKey: ["autoEssay"],
                  });
                  setAutoEssayOpened(true);
                }}
              >
                {t("autoEssays")}
              </Button>
            </InputGroup>
            <Stack gap={2} direction="horizontal">
              <Button variant="outline-primary" onClick={onFileUpload}>
                {t("fileUpload")}
              </Button>
              <Button
                variant="outline-primary"
                onClick={() => {
                  setTag("strong");
                }}
              >
                <strong>{t("textTag")}</strong>
              </Button>
              <Button
                variant="outline-primary"
                onClick={() => {
                  setTag("i");
                }}
              >
                <i>{t("textTag")}</i>
              </Button>
              <Button
                variant="outline-primary"
                onClick={() => {
                  setTag("u");
                }}
              >
                <u>{t("textTag")}</u>
              </Button>
              <Button
                variant="outline-primary"
                onClick={() => {
                  setTag("s");
                }}
              >
                <s>{t("textTag")}</s>
              </Button>
            </Stack>
            <FormControl
              ref={textView}
              style={{ height: textViewHeight }}
              as="textarea"
              isInvalid={!text}
              isValid={!!text}
              placeholder={t("text")}
              value={text}
              onChange={({ target: { value } }) => {
                setText(value);
              }}
            />
          </>
        )}
        {essayID ? (
          <Button
            ref={inputView}
            variant="warning"
            onClick={async () => {
              if (title && text) {
                await putEssay({ essayID, title, text });
                push(`/forum/${forumID}/${essayID}`);
              } else {
                toast.error(t("failedValidation"));
              }
            }}
          >
            <PencilFill />
          </Button>
        ) : (
          <Button
            ref={inputView}
            variant="success"
            onClick={async () => {
              if (title && text) {
                const { essayID } = await postEssay({
                  forumID,
                  title,
                  text,
                });
                push(`/forum/${forumID}/${essayID}`);
              } else {
                toast.error(t("failedValidation"));
              }
            }}
          >
            <PencilFill />
          </Button>
        )}
      </Stack>
      <FileUploading isOpened={isPostFilePending} />
      <AutoEssayWindow />
    </>
  );
});