From 7a28dde7d5dbf283e4c6db1e7d3300a19b69feb0 Mon Sep 17 00:00:00 2001 From: Vishesh 'ironeagle' Bangotra Date: Sat, 15 Nov 2025 03:56:47 +0530 Subject: [PATCH] ArticleEditor.tsx for Editing and Creating Articles --- src/blog/Blog.tsx | 23 ++- src/blog/components/Article/ArticleEditor.tsx | 139 ++++++++++++++++++ src/blog/types/props.ts | 8 +- 3 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 src/blog/components/Article/ArticleEditor.tsx diff --git a/src/blog/Blog.tsx b/src/blog/Blog.tsx index 575aa4e..563ed96 100644 --- a/src/blog/Blog.tsx +++ b/src/blog/Blog.tsx @@ -6,6 +6,7 @@ import Button from '@mui/material/Button'; import AppTheme from '../shared-theme/AppTheme'; import MainContent from './components/MainContent'; import ArticleView from './components/Article/ArticleView'; +import ArticleEditor from './components/Article/ArticleEditor'; import Latest from './components/Latest'; import Footer from './components/Footer'; import Login from './components/Login'; @@ -14,7 +15,7 @@ import Profile from './components/Profile'; import { useArticles } from './providers/Article'; import { useAuth } from './providers/Author'; -type View = 'home' | 'login' | 'register' | 'article' | 'profile'; +type View = 'home' | 'login' | 'register' | 'article' | 'profile' | 'editor'; export default function Blog(props: { disableCustomTheme?: boolean }) { const { articles, loading, error } = useArticles(); @@ -24,6 +25,7 @@ export default function Blog(props: { disableCustomTheme?: boolean }) { const [showLogin, setShowLogin] = React.useState(false); const [showRegister, setShowRegister] = React.useState(false); const [showProfile, setShowProfile] = React.useState(false); + const [showEditor, setShowEditor] = React.useState(false); const handleSelectArticle = (index: number) => { setSelectedArticle(index); @@ -53,6 +55,13 @@ export default function Blog(props: { disableCustomTheme?: boolean }) { const handleHideProfile = () => { setShowProfile(false); }; + const handleShowEditor = () => { + console.log("handleShowEditor") + setShowEditor(true); + }; + const handleHideEditor = () => { + setShowEditor(false); + }; // derive a single source of truth for view const view: View = React.useMemo(() => { @@ -60,8 +69,9 @@ export default function Blog(props: { disableCustomTheme?: boolean }) { if (showRegister) return 'register'; if (showLogin) return 'login'; if (showProfile) return 'profile'; + if (showEditor) return 'editor'; return 'home'; - }, [selectedArticle, showLogin, showRegister, showProfile]); + }, [selectedArticle, showLogin, showRegister, showProfile, showEditor]); // render function keeps JSX tidy const renderView = () => { @@ -86,6 +96,8 @@ export default function Blog(props: { disableCustomTheme?: boolean }) { case 'article': if (selectedArticle == null || !articles[selectedArticle]) return null; return ; + case 'editor': + return case 'home': default: return ( @@ -110,6 +122,13 @@ export default function Blog(props: { disableCustomTheme?: boolean }) { > {currentUser.username} + )} diff --git a/src/blog/components/Article/ArticleEditor.tsx b/src/blog/components/Article/ArticleEditor.tsx new file mode 100644 index 0000000..a53cac4 --- /dev/null +++ b/src/blog/components/Article/ArticleEditor.tsx @@ -0,0 +1,139 @@ +import * as React from 'react'; +import { Box, Typography, Divider, IconButton, Chip, TextField, Button } from '@mui/material'; +import { styled } from '@mui/material/styles'; +import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded'; +import { ArticleMeta } from "../ArticleMeta"; +import { ArticleEditorProps } from '../../types/props'; +import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; + +const ArticleContainer = styled(Box)(({ theme }) => ({ + maxWidth: '800px', + margin: '0 auto', + padding: theme.spacing(4), + [theme.breakpoints.down('sm')]: { + padding: theme.spacing(2), + }, +})); + +const CoverImage = styled('img')({ + width: '100%', + height: 'auto', + borderRadius: '12px', + marginTop: '16px', + marginBottom: '24px', +}); + +export default function ArticleView({ + article, + onBack, + author, +}: ArticleEditorProps) { + + const [title, setTitle] = React.useState(article?.title ?? ""); + const [description, setDescription] = React.useState(article?.description ?? ""); + const [tag, setTag] = React.useState(article?.tag ?? ""); + const [img, setImg] = React.useState(article?.img ?? ""); + const [content, setContent] = React.useState(article?.content ?? ""); + + return ( + + {/* BACK BUTTON */} + + + + + {/* TAG */} + setTag(e.target.value)} + sx={{ mb: 2 }} + /> + + {/* TITLE */} + setTitle(e.target.value)} + sx={{ mb: 3 }} + /> + + + + {/* COVER IMAGE URL */} + setImg(e.target.value)} + sx={{ mb: 2 }} + /> + + {/* COVER IMAGE PREVIEW */} + {img && } + + {/* MARKDOWN EDITOR */} + + Content + + setContent(e.target.value)} + sx={{ + '& textarea': { + fontFamily: 'monospace', + lineHeight: 1.6, + }, + }} + /> + + {/* LIVE PREVIEW */} + + Preview + + + + {content} + + + + {/* ACTIONS */} + + + + + + ); +} diff --git a/src/blog/types/props.ts b/src/blog/types/props.ts index 5f3567a..2e43e3e 100644 --- a/src/blog/types/props.ts +++ b/src/blog/types/props.ts @@ -1,4 +1,4 @@ -import { ArticleModel } from "./models"; +import {ArticleModel, AuthorModel} from "./models"; import {styled} from "@mui/material/styles"; import Card from "@mui/material/Card"; @@ -13,6 +13,12 @@ export interface ArticleProps { onBack: () => void; } +export interface ArticleEditorProps { + article: ArticleModel; + onBack: () => void; + author: AuthorModel; +} + export interface ArticleMetaProps { article: ArticleModel; }