Markdown support in forums

This commit is contained in:
2024-08-13 14:16:02 +08:00
parent de3343e120
commit 9f5a6a94e8
4 changed files with 54 additions and 32 deletions

View File

@@ -22,10 +22,13 @@
"react-chartjs-2": "^5.2.0", "react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1", "react-hot-toast": "^2.4.1",
"react-markdown": "^9.0.1",
"react-router-dom": "^6.23.1", "react-router-dom": "^6.23.1",
"remark-gfm": "^4.0.0",
"yup": "^1.4.0" "yup": "^1.4.0"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/typography": "^0.5.14",
"@types/react": "^18.2.66", "@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22", "@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/eslint-plugin": "^7.2.0",

View File

@@ -30,7 +30,8 @@ import {
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { retrieveUserInformationById } from "../security/usersbyid"; import { retrieveUserInformationById } from "../security/usersbyid";
import { retrieveUserInformation } from "../security/users"; import { retrieveUserInformation } from "../security/users";
// import UserPostImage from "../components/UserPostImage"; import Markdown from "react-markdown";
import remarkGfm from "remark-gfm";
interface Post { interface Post {
title: string; title: string;
@@ -58,8 +59,12 @@ export default function CommunityPage() {
const [selectedPost, setSelectedPost] = useState<Post | null>(null); const [selectedPost, setSelectedPost] = useState<Post | null>(null);
const [communityList, setCommunityList] = useState<Post[]>([]); const [communityList, setCommunityList] = useState<Post[]>([]);
const [search, setSearch] = useState(""); // Search Function const [search, setSearch] = useState(""); // Search Function
const [userInformation, setUserInformation] = useState<Record<string, User>>({}); const [userInformation, setUserInformation] = useState<Record<string, User>>(
const [imageErrorFlags, setImageErrorFlags] = useState<Record<string, boolean>>({}); {}
);
const [imageErrorFlags, setImageErrorFlags] = useState<
Record<string, boolean>
>({});
const [tags, setTags] = useState<Tag[]>([]); const [tags, setTags] = useState<Tag[]>([]);
const [selectedTag, setSelectedTag] = useState<string | null>(null); const [selectedTag, setSelectedTag] = useState<string | null>(null);
const [currentUserId, setCurrentUserId] = useState<string | null>(null); const [currentUserId, setCurrentUserId] = useState<string | null>(null);
@@ -147,7 +152,7 @@ export default function CommunityPage() {
const getCurrentUserInformation = async () => { const getCurrentUserInformation = async () => {
try { try {
const user = await retrieveUserInformation(); // Get the user information const user = await retrieveUserInformation(); // Get the user information
console.log(user) console.log(user);
setCurrentUserId(user.id); // Store user ID setCurrentUserId(user.id); // Store user ID
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@@ -291,14 +296,17 @@ export default function CommunityPage() {
</div> </div>
</div> </div>
<div> <div>
<p>{post.content}</p> <Markdown className="prose" remarkPlugins={[remarkGfm]}>
{post.content}
</Markdown>
</div> </div>
{imageErrorFlags[post.id] ? null : ( {imageErrorFlags[post.id] ? null : (
<div> <div>
<img <img
src={`${config.serverAddress}/post/post-image/${post.id}`} src={`${config.serverAddress}/post/post-image/${post.id}`}
className="w-[300px] h-auto rounded-lg object-cover" className="w-[300px] h-auto rounded-lg object-cover"
onError={() => handleImageError(post.id)} /> onError={() => handleImageError(post.id)}
/>
</div> </div>
)} )}
</div> </div>
@@ -362,7 +370,9 @@ export default function CommunityPage() {
<Chip <Chip
key={tag.id} key={tag.id}
onClick={() => handleTagClick(tag.tag)} onClick={() => handleTagClick(tag.tag)}
className={selectedTag === tag.tag ? "bg-primary-500 text-white" : ""} className={
selectedTag === tag.tag ? "bg-primary-500 text-white" : ""
}
> >
{tag.tag} {tag.tag}
</Chip> </Chip>
@@ -370,10 +380,7 @@ export default function CommunityPage() {
</div> </div>
<div> <div>
{selectedTag && ( {selectedTag && (
<Button <Button className="mt-2" onPress={handleClearFilter}>
className="mt-2"
onPress={handleClearFilter}
>
Clear Filter Clear Filter
</Button> </Button>
)} )}

View File

@@ -28,6 +28,8 @@ import { retrieveUserInformationById } from "../security/usersbyid";
import CommentInputModule from "../components/CommentInputModule"; import CommentInputModule from "../components/CommentInputModule";
import CommentsModule from "../components/CommentsModule"; import CommentsModule from "../components/CommentsModule";
import { retrieveUserInformation } from "../security/users"; import { retrieveUserInformation } from "../security/users";
import remarkGfm from "remark-gfm";
import Markdown from "react-markdown";
interface Post { interface Post {
title: string; title: string;
@@ -55,14 +57,19 @@ const PostPage: React.FC = () => {
const [post, setPost] = useState<Post | null>(null); const [post, setPost] = useState<Post | null>(null);
const { isOpen, onOpen, onOpenChange } = useDisclosure(); const { isOpen, onOpen, onOpenChange } = useDisclosure();
const [selectedPost, setSelectedPost] = useState<Post | null>(null); const [selectedPost, setSelectedPost] = useState<Post | null>(null);
const [userInformation, setUserInformation] = useState<Record<string, User>>({}); const [userInformation, setUserInformation] = useState<Record<string, User>>(
const [imageErrorFlags, setImageErrorFlags] = useState<Record<string, boolean>>({}); {}
);
const [imageErrorFlags, setImageErrorFlags] = useState<
Record<string, boolean>
>({});
const [currentUserId, setCurrentUserId] = useState<string | null>(null); const [currentUserId, setCurrentUserId] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
if (id) { if (id) {
console.log("useEffect for fetching post called, id:", id); console.log("useEffect for fetching post called, id:", id);
instance.get(`${config.serverAddress}/post/${id}`) instance
.get(`${config.serverAddress}/post/${id}`)
.then((res) => { .then((res) => {
setPost(res.data); setPost(res.data);
console.log("Post fetch successfully"); console.log("Post fetch successfully");
@@ -97,7 +104,7 @@ const PostPage: React.FC = () => {
const getCurrentUserInformation = async () => { const getCurrentUserInformation = async () => {
try { try {
const user = await retrieveUserInformation(); // Get the user information const user = await retrieveUserInformation(); // Get the user information
console.log(user) console.log(user);
setCurrentUserId(user.id); // Store user ID setCurrentUserId(user.id); // Store user ID
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@@ -169,23 +176,22 @@ const PostPage: React.FC = () => {
key={post.id} key={post.id}
> >
<div> <div>
<Avatar <Avatar src={profilePictureUrl} size="lg" />
src={profilePictureUrl}
size="lg"
/>
</div> </div>
<div className="flex flex-col gap-8 w-full"> <div className="flex flex-col gap-8 w-full">
<div className="h-full flex flex-col gap-4"> <div className="h-full flex flex-col gap-4">
<div className="flex flex-row justify-between"> <div className="flex flex-row justify-between">
<div className="flex flex-col"> <div className="flex flex-col">
<p className="text-xl font-bold">{post.title}</p> <p className="text-xl font-bold">{post.title}</p>
<p className="text-md text-neutral-500">{userInformation[post.userId]?.firstName} {userInformation[post.userId]?.lastName}</p> <p className="text-md text-neutral-500">
{userInformation[post.userId]?.firstName}{" "}
{userInformation[post.userId]?.lastName}
</p>
</div> </div>
<div className="flex flex-row-reverse justify-center items-center"> <div className="flex flex-row-reverse justify-center items-center">
{currentUserId === post.userId && ( // Check if the current user is the post author {currentUserId === post.userId && ( // Check if the current user is the post author
<Dropdown> <Dropdown>
<DropdownTrigger <DropdownTrigger className="justify-center items-center">
className="justify-center items-center">
<Button isIconOnly variant="light"> <Button isIconOnly variant="light">
<EllipsisHorizontalIcon /> <EllipsisHorizontalIcon />
</Button> </Button>
@@ -215,17 +221,22 @@ const PostPage: React.FC = () => {
</div> </div>
</div> </div>
<div> <div>
<p>{post.content}</p> <Markdown className="prose" remarkPlugins={[remarkGfm]}>
{post.content}
</Markdown>
</div> </div>
{!imageErrorFlags[post.id] && post.postImage && post.postImage !== null && ( {!imageErrorFlags[post.id] &&
<div> post.postImage &&
<img post.postImage !== null && (
src={`${config.serverAddress}/post/post-image/${post.id}`} <div>
alt="PostImage" <img
className="w-[300px] h-auto rounded-lg object-cover" src={`${config.serverAddress}/post/post-image/${post.id}`}
onError={() => handleImageError(post.id)} /> alt="PostImage"
</div> className="w-[300px] h-auto rounded-lg object-cover"
)} onError={() => handleImageError(post.id)}
/>
</div>
)}
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">

View File

@@ -9,6 +9,7 @@ export default {
], ],
darkMode: "class", darkMode: "class",
plugins: [ plugins: [
require("@tailwindcss/typography"),
nextui({ nextui({
themes: { themes: {
"red-dark": { "red-dark": {