Skip to content

Commit

Permalink
Merge pull request #148 from HabitPay/fix/add_markdownEditor_and_code…
Browse files Browse the repository at this point in the history
…_highlight#143

Fix/add markdown editor and code highlight#143
  • Loading branch information
404yonara authored Dec 26, 2024
2 parents ffe2bee + 55527b3 commit ff53fd6
Show file tree
Hide file tree
Showing 9 changed files with 8,614 additions and 5,080 deletions.
3 changes: 3 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ const nextConfig = {
};

module.exports = nextConfig;

// const removeImports = require("next-remove-imports")();
// module.exports = removeImports({});
13,470 changes: 8,446 additions & 5,024 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@
"@next/third-parties": "^14.2.13",
"@types/react-date-range": "^1.4.9",
"@types/react-slick": "^0.23.13",
"@types/react-syntax-highlighter": "^15.5.13",
"@uiw/react-md-editor": "^3.6.0",
"ajv": "^8.17.1",
"axios": "^1.6.5",
"date-fns": "^3.6.0",
"date-fns-tz": "^3.1.3",
"http-status-codes": "^2.3.0",
"jwt-decode": "^4.0.0",
"next": "14.0.4",
"next-remove-imports": "^1.0.12",
"react": "^18.2.0",
"react-calendar": "^4.8.0",
"react-date-range": "^2.0.1",
Expand All @@ -29,7 +32,9 @@
"react-number-format": "^5.3.4",
"react-query": "^3.39.3",
"react-slick": "^0.30.2",
"react-syntax-highlighter": "^15.6.1",
"recoil": "^0.7.7",
"remark-gfm": "^4.0.0",
"slick-carousel": "^1.8.1",
"sst": "ion"
},
Expand Down
74 changes: 51 additions & 23 deletions src/app/challenges/[challengeId]/post/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@

import { usePathname, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import dynamic from "next/dynamic";

import { useForm } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
import { useSetRecoilState } from "recoil";

import apiManager from "@/api/apiManager";
import Frame from "@/app/components/frame";
import {
MAX_FILE_SIZE,
MB,
SUPPORTED_IMAGE_EXTENSIONS,
} from "@/libs/constants";
import { SUPPORTED_IMAGE_EXTENSIONS } from "@/libs/constants";
import PreviewList from "./components/previewList";
import { toastPopupAtom } from "@/hooks/atoms";
import { ICreatePostDTO } from "@/types/post";
Expand All @@ -26,6 +23,10 @@ import {
} from "@/libs/imageUploadUtils";
import { PopupErrorMessage } from "@/types/enums";

import "@uiw/react-md-editor/markdown-editor.css";
import "@uiw/react-markdown-preview/markdown.css";
import "@/styles/CustomMdEditor.css";

export interface imageInfo {
file?: File;
postPhotoId?: number;
Expand All @@ -42,13 +43,18 @@ const Page = ({
}: {
params: { challengeId: string };
}) => {
const { register, handleSubmit, setError, setValue } = useForm<IForm>();
const { register, handleSubmit, setError, setValue, control } =
useForm<IForm>();
const [imageList, setImageList] = useState<imageInfo[]>([]);
const [isAnnouncement, setIsAnnouncement] = useState(false);
const [isManager, setIsManager] = useState(false);
const setToastPopup = useSetRecoilState(toastPopupAtom);
const router = useRouter();
const currentPath = usePathname();
const MDEditor = dynamic(
() => import("@uiw/react-md-editor").then((mod) => mod.default),
{ ssr: false }
);
useEffect(() => {
document.title = "Challenge Post | HabitPay";
const getChallengeInfo = async () => {
Expand Down Expand Up @@ -158,24 +164,46 @@ const Page = ({
onSubmit={handleSubmit(onSubmitWithValidation)}
className="flex flex-col h-full"
>
<textarea
{...register("content", {
<Controller
name="content"
control={control}
rules={{
required: { value: true, message: "내용을 입력해주세요." },
})}
className="w-full h-screen px-4 pt-4 border-gray-300"
placeholder="오늘의 챌린지 내용에 대해서 작성해주세요."
}}
render={({ field, fieldState }) => (
<div className=" relative">
<MDEditor
value={field.value}
onChange={field.onChange}
preview="edit"
enableScroll={false}
height={window.innerHeight - 100}
textareaProps={{
placeholder: "내용을 입력해주세요",
}}
autoFocus
style={{
whiteSpace: "normal",
wordWrap: "break-word",
wordBreak: "break-word",
}}
/>
{fieldState.error && (
<p className="text-red-500 text-sm mt-1 absolute top-20 left-4">
{fieldState.error.message}
</p>
)}
</div>
)}
/>
{
// nav와 imageList의 높이 112px, 95px
imageList.length ? (
<>
<div className="h-[207px]"></div>
<PreviewList imageList={imageList} setImageList={setImageList} />
</>
) : (
<div className="h-[95px]"></div>
)
}
{imageList.length ? (
<>
<div className="h-[207px]"></div>
<PreviewList imageList={imageList} setImageList={setImageList} />
</>
) : (
<div className="h-[95px]"></div>
)}
<nav className="fixed bottom-0 flex justify-between w-full max-w-xl px-6 py-4 space-x-12 text-xs text-gray-700 bg-white border-t">
<div className="flex items-center space-x-8 ">
<div
Expand Down
69 changes: 49 additions & 20 deletions src/app/challenges/[challengeId]/posts/[postId]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import { useRouter, usePathname } from "next/navigation";
import { useEffect, useState } from "react";
import dynamic from "next/dynamic";

import { useForm } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
import { useSetRecoilState } from "recoil";

import apiManager from "@/api/apiManager";
import Frame from "@/app/components/frame";
import { SUPPORTED_IMAGE_EXTENSIONS } from "@/libs/constants";
import PreviewList from "../../../post/components/previewList";
import { toastPopupAtom } from "@/hooks/atoms";
import { IPatchPostDTO } from "@/types/post";
import { ContentDTO } from "@/types/challenge";
Expand All @@ -19,9 +21,12 @@ import {
isValidImageExtension,
uploadImagesToS3,
} from "@/libs/imageUploadUtils";
import PreviewList from "../../../post/components/previewList";
import { imageInfo } from "../../../post/page";
import { PopupErrorMessage } from "@/types/enums";
import { imageInfo } from "../../../post/page";

import "@uiw/react-md-editor/markdown-editor.css";
import "@uiw/react-markdown-preview/markdown.css";
import "@/styles/CustomMdEditor.css";

interface IForm {
content: string;
Expand All @@ -33,17 +38,22 @@ const Page = ({
}: {
params: { challengeId: string; postId: string };
}) => {
const { register, handleSubmit, setValue, setError } = useForm<IForm>();
const [imageList, setImageList] = useState<imageInfo[]>([]);
const [deletedImageList, setDeletedImageList] = useState<number[]>([]);
const [isAnnouncement, setIsAnnouncement] = useState(false);
const [challengeContent, setChallengeContent] = useState<ContentDTO>();
const [textAreaContent, setTextAreaContent] = useState(
challengeContent?.content || ""
);
const { register, handleSubmit, setValue, setError, control } =
useForm<IForm>({ defaultValues: { content: textAreaContent } });
const [imageList, setImageList] = useState<imageInfo[]>([]);
const [deletedImageList, setDeletedImageList] = useState<number[]>([]);
const [isAnnouncement] = useState(false);
const setToastPopup = useSetRecoilState(toastPopupAtom);
const router = useRouter();
const currentPath = usePathname();
const MDEditor = dynamic(
() => import("@uiw/react-md-editor").then((mod) => mod.default),
{ ssr: false }
);

useEffect(() => {
document.title = "Challenge Post Edit | HabitPay";
Expand Down Expand Up @@ -185,25 +195,44 @@ const Page = ({
setValue("content", textAreaContent); // Sync with form state
}, [setValue, textAreaContent]);

useEffect(() => {}, [setValue, imageList]);

return (
<Frame canGoBack title="게시물 수정" isWhiteTitle isBorder>
<form
onSubmit={handleSubmit(onSubmitWithValidation)}
className="flex flex-col"
>
<div className="flex-col">
<textarea
{...register("content", {
onChange: onTextAreaChange,
required: { value: true, message: "내용을 입력해주세요." },
})}
className="w-full h-screen px-4 pt-4 border-gray-300"
placeholder="오늘의 챌린지 내용에 대해서 작성해주세요."
value={textAreaContent}
/>
</div>
<Controller
name="content"
control={control}
rules={{
required: { value: true, message: "내용을 입력해주세요." },
}}
render={({ field, fieldState }) => (
<div className=" relative">
<MDEditor
value={field.value}
onChange={field.onChange}
preview="edit"
enableScroll={false}
height={window.innerHeight - 100}
textareaProps={{
placeholder: "내용을 입력해주세요",
}}
autoFocus
style={{
whiteSpace: "normal",
wordWrap: "break-word",
wordBreak: "break-word",
}}
/>
{fieldState.error && (
<p className="text-red-500 text-sm mt-1 absolute top-20 left-4">
{fieldState.error.message}
</p>
)}
</div>
)}
/>
{imageList.length ? (
<>
<div className="h-[207px]"></div>
Expand Down
38 changes: 38 additions & 0 deletions src/app/components/markdwonRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Markdown from "react-markdown";
import ReactMarkdown from "react-markdown";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { materialOceanic } from "react-syntax-highlighter/dist/esm/styles/prism";

interface MarkdownRendererProps {
content: string; // 마크다운 텍스트를 prop으로 받음
}

const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content }) => {
return (
<ReactMarkdown
components={{
code({ node, className, style, children, ref, ...props }) {
const match = /language-(\w+)/.exec(className || "");
return match ? (
<SyntaxHighlighter
style={materialOceanic}
language={match[1]}
PreTag="div"
{...props}
>
{String(children).replace(/\n$/, "")}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{content}
</ReactMarkdown>
);
};

export default MarkdownRenderer;
8 changes: 7 additions & 1 deletion src/app/components/postItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import { useRouter } from "next/navigation";
import { IPostDetailsDto } from "@/types/post";
import Markdown from "react-markdown";

import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import MarkdownRenderer from "./markdwonRenderer";

interface PostsFeedProps {
challengeId: string;
contentDTO: ContentDTO;
Expand Down Expand Up @@ -155,7 +159,9 @@ const PostItem = ({ challengeId, contentDTO }: PostsFeedProps) => {
</div>
))}
</Slider>
<Markdown>{contentDTO.content}</Markdown>
<div className=" overflow-auto">
<MarkdownRenderer content={contentDTO.content} />
</div>
</div>
</div>
);
Expand Down
18 changes: 6 additions & 12 deletions src/app/test_page/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,14 @@ import { NextPage } from "next";
import { useEffect, useState } from "react";
import ConfirmModal from "../components/confirmModal";
import Calendar from "react-calendar";
import MDEditor from "@uiw/react-md-editor";
import "@uiw/react-md-editor/markdown-editor.css";
import "@uiw/react-markdown-preview/markdown.css";
import "@/styles/CustomMdEditor.css";

const Page: NextPage = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
useEffect(() => {
setIsModalOpen(true);
}, []);
const onClick = () => {
console.log("clicked!");
};
return (
<div>
<Calendar />
</div>
);
const [value, setValue] = useState("**Hello world!!!**");
return <div className="container w-96"></div>;
};

export default Page;
9 changes: 9 additions & 0 deletions src/styles/CustomMdEditor.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@




.w-md-editor-text-pre > code,
.w-md-editor-text-input {
font-size: 16px !important;
line-height: 24px !important;
}

0 comments on commit ff53fd6

Please sign in to comment.