Compare commits
	
		
			3 Commits
		
	
	
		
			35efde0db9
			...
			4d5b1692c4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4d5b1692c4 | |||
| 20da33d412 | |||
| f2df5c4f75 | 
| @@ -26,169 +26,201 @@ export default function PostSummary({ | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <div | ||||
|       key={metadata.slug} | ||||
|       style={{ | ||||
|         display: "flex", | ||||
|         flexDirection: "column", | ||||
|         borderColor: elementColor, | ||||
|         transition: "border-color 0.3s linear", | ||||
|         borderStyle: "solid", | ||||
|         marginTop: "1rem", | ||||
|       }} | ||||
|     > | ||||
|       <div | ||||
|         style={{ | ||||
|           width: "100%", | ||||
|           backgroundColor: elementColor, | ||||
|           color: "#111", | ||||
|           display: "flex", | ||||
|           alignItems: "center", | ||||
|           padding: "0.5rem", | ||||
|           boxSizing: "border-box", | ||||
|           fontSize: "1rem", | ||||
|           cursor: "pointer", | ||||
|           transition: "background-color 0.3s linear", | ||||
|         }} | ||||
|         onMouseEnter={hoverStart} | ||||
|         onMouseLeave={hoverEnd} | ||||
|         onClick={navigateToPost} | ||||
|       > | ||||
|         <span>{metadata.title}</span> | ||||
|       </div> | ||||
|       <div | ||||
|         style={{ | ||||
|           display: "flex", | ||||
|           flexDirection: "row", | ||||
|           justifyContent: "space-between", | ||||
|           fontSize: "0.9rem", | ||||
|           maxWidth: "100%", | ||||
|           flexWrap: "wrap", | ||||
|         }} | ||||
|       > | ||||
|         <div | ||||
|           style={{ | ||||
|             display: "flex", | ||||
|             justifyContent: "flex-start", | ||||
|             padding: "0.5rem", | ||||
|             gap: 4, | ||||
|           }} | ||||
|         > | ||||
|           tags: {metadata.tags.join(", ")} | ||||
|         </div> | ||||
|         <div | ||||
|           style={{ | ||||
|             display: "flex", | ||||
|             justifyContent: "flex-end", | ||||
|             padding: "0.5rem", | ||||
|             gap: 4, | ||||
|           }} | ||||
|         > | ||||
|           {metadata.is_draft && ( | ||||
|             <> | ||||
|               <span>draft - </span> | ||||
|               <div | ||||
|                 style={{ | ||||
|                   cursor: "pointer", | ||||
|                   color: "#999", | ||||
|                   transition: "color 0.3s linear", | ||||
|                 }} | ||||
|                 onMouseOver={(e) => { | ||||
|                   e.currentTarget.style.color = "#eee"; | ||||
|                 }} | ||||
|                 onMouseLeave={(e) => { | ||||
|                   e.currentTarget.style.color = "#999"; | ||||
|                 }} | ||||
|                 onClick={async () => { | ||||
|                   await publishArticle(metadata.slug); | ||||
|                   router.refresh(); | ||||
|                 }} | ||||
|               > | ||||
|                 move to published | ||||
|               </div> | ||||
|             </> | ||||
|           )} | ||||
|           {!metadata.is_draft && ( | ||||
|             <> | ||||
|               <div> | ||||
|                 <div> | ||||
|                   <span>published: </span> | ||||
|                   <span>{metadata.publishedDate.toLocaleDateString()}</span> | ||||
|                 </div> | ||||
|                 {loggedIn && ( | ||||
|                   <div | ||||
|                     style={{ | ||||
|                       cursor: "pointer", | ||||
|                       color: "#999", | ||||
|                       transition: "color 0.3s linear", | ||||
|                     }} | ||||
|                     onMouseOver={(e) => { | ||||
|                       e.currentTarget.style.color = "#eee"; | ||||
|                     }} | ||||
|                     onMouseLeave={(e) => { | ||||
|                       e.currentTarget.style.color = "#999"; | ||||
|                     }} | ||||
|                     onClick={async () => { | ||||
|                       await unpublishArticle(metadata.slug); | ||||
|                       router.refresh(); | ||||
|                     }} | ||||
|                   > | ||||
|                     move to drafts | ||||
|                   </div> | ||||
|                 )} | ||||
|               </div> | ||||
|             </> | ||||
|           )} | ||||
|         </div> | ||||
|       </div> | ||||
|       <div | ||||
|         style={{ | ||||
|           width: "100%", | ||||
|           height: "2px", | ||||
|           backgroundColor: elementColor, | ||||
|           transition: "background-color 0.3s linear", | ||||
|         }} | ||||
|       ></div> | ||||
|     <> | ||||
|       <div | ||||
|         key={metadata.slug} | ||||
|         style={{ | ||||
|           display: "flex", | ||||
|           flexDirection: "column", | ||||
|           padding: "1rem 0.5rem 0 0.5rem", | ||||
|           borderColor: elementColor, | ||||
|           transition: "border-color 0.3s linear", | ||||
|           borderStyle: "solid", | ||||
|           marginTop: "1rem", | ||||
|         }} | ||||
|       > | ||||
|         <div style={{ fontSize: "0.9rem" }}> | ||||
|           {metadata.blurb} | ||||
|           <span style={{ color: "#999" }}>…</span> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div | ||||
|         style={{ | ||||
|           display: "flex", | ||||
|           justifyContent: "flex-end", | ||||
|           marginTop: "0.5rem", | ||||
|         }} | ||||
|       > | ||||
|         <span | ||||
|         <div | ||||
|           style={{ | ||||
|             textDecoration: "none", | ||||
|             color: "#222", | ||||
|             transition: "background-color 0.3s linear", | ||||
|             padding: "0.5rem 1rem", | ||||
|             fontSize: "0.9rem", | ||||
|             fontWeight: "500", | ||||
|             display: "inline-flex", | ||||
|             alignItems: "center", | ||||
|             gap: "0.25rem", | ||||
|             cursor: "pointer", | ||||
|             width: "100%", | ||||
|             backgroundColor: elementColor, | ||||
|             color: "#111", | ||||
|             display: "flex", | ||||
|             alignItems: "center", | ||||
|             padding: "0.5rem", | ||||
|             boxSizing: "border-box", | ||||
|             fontSize: "1rem", | ||||
|             cursor: "pointer", | ||||
|             transition: "background-color 0.3s linear", | ||||
|           }} | ||||
|           onMouseEnter={hoverStart} | ||||
|           onMouseLeave={hoverEnd} | ||||
|           onClick={navigateToPost} | ||||
|         > | ||||
|           Read more <span style={{ fontSize: "1rem" }}>→</span> | ||||
|         </span> | ||||
|           <span>{metadata.title}</span> | ||||
|         </div> | ||||
|         <div | ||||
|           style={{ | ||||
|             display: "flex", | ||||
|             flexDirection: "row", | ||||
|             justifyContent: "space-between", | ||||
|             fontSize: "0.9rem", | ||||
|             maxWidth: "100%", | ||||
|             flexWrap: "wrap", | ||||
|           }} | ||||
|         > | ||||
|           <div | ||||
|             style={{ | ||||
|               display: "flex", | ||||
|               justifyContent: "flex-start", | ||||
|               padding: "0.5rem", | ||||
|               gap: 4, | ||||
|             }} | ||||
|           > | ||||
|             tags: {metadata.tags.join(", ")} | ||||
|           </div> | ||||
|           <div | ||||
|             style={{ | ||||
|               display: "flex", | ||||
|               justifyContent: "flex-end", | ||||
|               padding: "0.5rem", | ||||
|               gap: 4, | ||||
|             }} | ||||
|           > | ||||
|             {metadata.is_draft && ( | ||||
|               <> | ||||
|                 <span>draft - </span> | ||||
|                 <div | ||||
|                   style={{ | ||||
|                     cursor: "pointer", | ||||
|                     color: "#999", | ||||
|                     transition: "color 0.3s linear", | ||||
|                   }} | ||||
|                   onMouseOver={(e) => { | ||||
|                     e.currentTarget.style.color = "#eee"; | ||||
|                   }} | ||||
|                   onMouseLeave={(e) => { | ||||
|                     e.currentTarget.style.color = "#999"; | ||||
|                   }} | ||||
|                   onClick={async () => { | ||||
|                     await publishArticle(metadata.slug); | ||||
|                     router.refresh(); | ||||
|                   }} | ||||
|                 > | ||||
|                   move to published | ||||
|                 </div> | ||||
|               </> | ||||
|             )} | ||||
|             {!metadata.is_draft && ( | ||||
|               <> | ||||
|                 <div> | ||||
|                   <div> | ||||
|                     <span>published: </span> | ||||
|                     <span>{metadata.publishedDate.toLocaleDateString()}</span> | ||||
|                   </div> | ||||
|                   {loggedIn && ( | ||||
|                     <div | ||||
|                       style={{ | ||||
|                         cursor: "pointer", | ||||
|                         color: "#999", | ||||
|                         transition: "color 0.3s linear", | ||||
|                       }} | ||||
|                       onMouseOver={(e) => { | ||||
|                         e.currentTarget.style.color = "#eee"; | ||||
|                       }} | ||||
|                       onMouseLeave={(e) => { | ||||
|                         e.currentTarget.style.color = "#999"; | ||||
|                       }} | ||||
|                       onClick={async () => { | ||||
|                         await unpublishArticle(metadata.slug); | ||||
|                         router.refresh(); | ||||
|                       }} | ||||
|                     > | ||||
|                       move to drafts | ||||
|                     </div> | ||||
|                   )} | ||||
|                 </div> | ||||
|               </> | ||||
|             )} | ||||
|           </div> | ||||
|         </div> | ||||
|         <div | ||||
|           style={{ | ||||
|             width: "100%", | ||||
|             height: "2px", | ||||
|             backgroundColor: elementColor, | ||||
|             transition: "background-color 0.3s linear", | ||||
|           }} | ||||
|         ></div> | ||||
|         <div | ||||
|           style={{ | ||||
|             display: "flex", | ||||
|             flexDirection: "column", | ||||
|             padding: "1rem 0.5rem 0 0.5rem", | ||||
|           }} | ||||
|         > | ||||
|           <div style={{ fontSize: "0.9rem" }}> | ||||
|             {metadata.blurb} | ||||
|             <span style={{ color: "#999" }}>…</span> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div | ||||
|           style={{ | ||||
|             display: "flex", | ||||
|             justifyContent: "flex-end", | ||||
|             marginTop: "0.5rem", | ||||
|           }} | ||||
|         > | ||||
|           <span | ||||
|             style={{ | ||||
|               textDecoration: "none", | ||||
|               color: "#222", | ||||
|               transition: "background-color 0.3s linear", | ||||
|               padding: "0.5rem 1rem", | ||||
|               fontSize: "0.9rem", | ||||
|               fontWeight: "500", | ||||
|               display: "inline-flex", | ||||
|               alignItems: "center", | ||||
|               gap: "0.25rem", | ||||
|               cursor: "pointer", | ||||
|               backgroundColor: elementColor, | ||||
|             }} | ||||
|             onMouseEnter={hoverStart} | ||||
|             onMouseLeave={hoverEnd} | ||||
|             onClick={navigateToPost} | ||||
|           > | ||||
|             Read more <span style={{ fontSize: "1rem" }}>→</span> | ||||
|           </span> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|       {loggedIn ? ( | ||||
|         <div | ||||
|           style={{ | ||||
|             marginTop: "2rem", | ||||
|             padding: "0.5rem 1rem", | ||||
|             backgroundColor: "#333", | ||||
|             width: "fit-content", | ||||
|             userSelect: "none", | ||||
|             cursor: "pointer", | ||||
|           }} | ||||
|           onClick={() => router.push("/blog/write")} | ||||
|         > | ||||
|           write a post | ||||
|         </div> | ||||
|       ) : ( | ||||
|         <div | ||||
|           style={{ | ||||
|             marginTop: "2rem", | ||||
|             padding: "0.3rem 0.5rem", | ||||
|             backgroundColor: "#333", | ||||
|             width: "fit-content", | ||||
|             userSelect: "none", | ||||
|             cursor: "pointer", | ||||
|             fontSize: "0.8rem", | ||||
|           }} | ||||
|           onClick={() => router.push("/blog/login")} | ||||
|         > | ||||
|           login | ||||
|         </div> | ||||
|       )} | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
|   | ||||
| @@ -57,7 +57,7 @@ export async function getSummaries( | ||||
|       ...filter, | ||||
|       omit: { contentMarkdown: true, contentRendered: true }, | ||||
|       include: { tags: { select: { name: true } } }, | ||||
|       orderBy: { publishedDate: "asc" }, | ||||
|       orderBy: { publishedDate: "desc" }, | ||||
|       skip: PAGE_SIZE * (pageNumber - 1), | ||||
|       take: PAGE_SIZE, | ||||
|     }) | ||||
| @@ -79,3 +79,19 @@ export async function unpublishArticle(slug: string) { | ||||
|   const prisma = new PrismaClient(); | ||||
|   await prisma.post.update({ where: { slug }, data: { is_draft: true } }); | ||||
| } | ||||
|  | ||||
| export async function deleteArticle(slug: string) { | ||||
|   const prisma = new PrismaClient(); | ||||
|   const post = await prisma.post.delete({ | ||||
|     where: { slug }, | ||||
|     include: { tags: true }, | ||||
|   }); | ||||
|   for (const tag of post.tags) { | ||||
|     const postsWithTag = await prisma.post.count({ | ||||
|       where: { tags: { some: { id: tag.id } } }, | ||||
|     }); | ||||
|     if (postsWithTag == 0) { | ||||
|       await prisma.tag.delete({ where: { id: tag.id } }); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -3,7 +3,8 @@ | ||||
| import { useRouter } from "next/navigation"; | ||||
| import * as Types from "../../types"; | ||||
| import "highlight.js/styles/github-dark.css"; | ||||
| import { publishArticle, unpublishArticle } from "../../action"; | ||||
| import { deleteArticle, publishArticle, unpublishArticle } from "../../action"; | ||||
| import { useState } from "react"; | ||||
|  | ||||
| export default function PostDisplay({ | ||||
|   post, | ||||
| @@ -13,6 +14,9 @@ export default function PostDisplay({ | ||||
|   loggedIn: boolean; | ||||
| }) { | ||||
|   const router = useRouter(); | ||||
|  | ||||
|   const [confirmDelete, setConfirmDelete] = useState(false); | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <div | ||||
| @@ -151,25 +155,55 @@ export default function PostDisplay({ | ||||
|         }} | ||||
|       > | ||||
|         {loggedIn ? ( | ||||
|           <div | ||||
|             style={{ | ||||
|               backgroundColor: "#a66", | ||||
|               color: "#111", | ||||
|               fontSize: "0.8rem", | ||||
|               padding: "0.5rem", | ||||
|               transition: "background-color 0.3s linear", | ||||
|               userSelect: "none", | ||||
|               cursor: "pointer", | ||||
|             }} | ||||
|             onMouseOver={(e) => { | ||||
|               e.currentTarget.style.backgroundColor = "#c88"; | ||||
|             }} | ||||
|             onMouseOut={(e) => { | ||||
|               e.currentTarget.style.backgroundColor = "#a66"; | ||||
|             }} | ||||
|             onClick={() => router.push(`/blog/write/${post.slug}`)} | ||||
|           > | ||||
|             edit | ||||
|           <div style={{ display: "flex", gap: "1rem" }}> | ||||
|             <div | ||||
|               style={{ | ||||
|                 backgroundColor: "#6a6", | ||||
|                 color: "#111", | ||||
|                 fontSize: "0.8rem", | ||||
|                 padding: "0.5rem", | ||||
|                 transition: "background-color 0.3s linear", | ||||
|                 userSelect: "none", | ||||
|                 cursor: "pointer", | ||||
|               }} | ||||
|               onMouseOver={(e) => { | ||||
|                 e.currentTarget.style.backgroundColor = "#8c8"; | ||||
|               }} | ||||
|               onMouseOut={(e) => { | ||||
|                 e.currentTarget.style.backgroundColor = "#6a6"; | ||||
|               }} | ||||
|               onClick={() => router.push(`/blog/write/${post.slug}`)} | ||||
|             > | ||||
|               edit | ||||
|             </div> | ||||
|             <div | ||||
|               style={{ | ||||
|                 backgroundColor: "#a66", | ||||
|                 color: "#111", | ||||
|                 fontSize: "0.8rem", | ||||
|                 padding: "0.5rem", | ||||
|                 transition: "background-color 0.3s linear", | ||||
|                 userSelect: "none", | ||||
|                 cursor: "pointer", | ||||
|               }} | ||||
|               onMouseOver={(e) => { | ||||
|                 e.currentTarget.style.backgroundColor = "#c88"; | ||||
|               }} | ||||
|               onMouseOut={(e) => { | ||||
|                 e.currentTarget.style.backgroundColor = "#a66"; | ||||
|               }} | ||||
|               onClick={() => setConfirmDelete(true)} | ||||
|             > | ||||
|               delete | ||||
|             </div> | ||||
|             <DeleteModal | ||||
|               open={confirmDelete} | ||||
|               onCancel={() => setConfirmDelete(false)} | ||||
|               onConfirm={async () => { | ||||
|                 await deleteArticle(post.slug); | ||||
|                 router.push("/blog"); | ||||
|               }} | ||||
|             /> | ||||
|           </div> | ||||
|         ) : ( | ||||
|           <div></div> | ||||
| @@ -198,3 +232,96 @@ export default function PostDisplay({ | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| function DeleteModal({ | ||||
|   open, | ||||
|   onCancel, | ||||
|   onConfirm, | ||||
| }: { | ||||
|   open: boolean; | ||||
|   onCancel: () => void; | ||||
|   onConfirm: () => void; | ||||
| }) { | ||||
|   return ( | ||||
|     <div | ||||
|       id="deleteModal" | ||||
|       style={{ | ||||
|         display: open | ||||
|           ? "flex" | ||||
|           : "none" /* Will be changed to "flex" when opened */, | ||||
|         position: "fixed", | ||||
|         top: 0, | ||||
|         left: 0, | ||||
|         width: "100%", | ||||
|         height: "100%", | ||||
|         backgroundColor: "rgba(0, 0, 0, 0.7)", | ||||
|         zIndex: 1000, | ||||
|         justifyContent: "center", | ||||
|         alignItems: "center", | ||||
|       }} | ||||
|       onClick={(e) => { | ||||
|         if (e.target == e.currentTarget) { | ||||
|           onCancel(); | ||||
|         } | ||||
|       }} | ||||
|     > | ||||
|       <div | ||||
|         style={{ | ||||
|           backgroundColor: "#222", | ||||
|           padding: "1.5rem", | ||||
|           borderRadius: "4px", | ||||
|           maxWidth: "400px", | ||||
|           width: "90%", | ||||
|           boxShadow: "0 0 10px rgba(0, 0, 0, 0.5)", | ||||
|         }} | ||||
|         onClick={(e) => e.stopPropagation()} | ||||
|       > | ||||
|         <h3 style={{ margin: "0 0 1rem 0", color: "#eee" }}> | ||||
|           Confirm Deletion | ||||
|         </h3> | ||||
|         <p style={{ marginBottom: "1.5rem", color: "#ccc" }}> | ||||
|           Are you sure you want to delete this post? This action cannot be | ||||
|           undone. | ||||
|         </p> | ||||
|         <div | ||||
|           style={{ | ||||
|             display: "flex", | ||||
|             justifyContent: "flex-end", | ||||
|             gap: "0.75rem", | ||||
|           }} | ||||
|         > | ||||
|           <button | ||||
|             style={{ | ||||
|               backgroundColor: "#a66", | ||||
|               color: "#fff", | ||||
|               border: "none", | ||||
|               padding: "0.5rem 1rem", | ||||
|               cursor: "pointer", | ||||
|               borderRadius: "2px", | ||||
|             }} | ||||
|             onClick={() => { | ||||
|               onConfirm(); | ||||
|             }} | ||||
|           > | ||||
|             Yes, delete it | ||||
|           </button> | ||||
|           <button | ||||
|             style={{ | ||||
|               backgroundColor: "#555", | ||||
|               color: "#fff", | ||||
|               border: "none", | ||||
|               padding: "0.5rem 1rem", | ||||
|               cursor: "pointer", | ||||
|               borderRadius: "2px", | ||||
|             }} | ||||
|             onClick={() => { | ||||
|               onCancel(); | ||||
|             }} | ||||
|           > | ||||
|             Cancel | ||||
|           </button> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import "./globals.css"; | ||||
| import Title from "@/components/Title"; | ||||
| import Navbar from "@/components/Navbar"; | ||||
| import { bodyFont } from "@/components/fonts"; | ||||
| import Link from "next/link"; | ||||
|  | ||||
| export const metadata: Metadata = { | ||||
|   title: "nrx.sh", | ||||
| @@ -37,11 +38,28 @@ export default function RootLayout({ | ||||
|               maxWidth: "100vw", | ||||
|               padding: "2rem", | ||||
|               boxSizing: "border-box", | ||||
|               fontSize: "1.1rem", | ||||
|               fontSize: "0.9rem", | ||||
|             }} | ||||
|           > | ||||
|             {children} | ||||
|           </div> | ||||
|           <div | ||||
|             style={{ | ||||
|               fontSize: "0.7rem", | ||||
|               marginTop: "2rem", | ||||
|               textAlign: "center", | ||||
|               color: "#888", | ||||
|             }} | ||||
|             className={bodyFont.className} | ||||
|           > | ||||
|             this site is built from scratch using <b>next.js</b> - it is  | ||||
|             <Link | ||||
|               style={{ color: "#88f" }} | ||||
|               href={"https://git.nrx.sh/naresh/nrx.sh"} | ||||
|             > | ||||
|               completely open source | ||||
|             </Link> | ||||
|           </div> | ||||
|         </div> | ||||
|       </body> | ||||
|     </html> | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| "use client"; | ||||
|  | ||||
| import { SiGitea } from "@icons-pack/react-simple-icons"; | ||||
| import { NotebookText } from "lucide-react"; | ||||
| import { SocialIcon } from "react-social-icons"; | ||||
|  | ||||
| export default function Links() { | ||||
| @@ -17,9 +16,6 @@ export default function Links() { | ||||
|           gap: "1rem", | ||||
|         }} | ||||
|       > | ||||
|         <Link href="https://blog.nrx.sh"> | ||||
|           <NotebookText size={26} style={{ padding: 10 }} /> My Blog {"<3"} | ||||
|         </Link> | ||||
|         <Link href="https://github.com/naresh97"> | ||||
|           <SocialIcon network="github" bgColor="#111" /> GitHub | ||||
|         </Link> | ||||
| @@ -27,7 +23,7 @@ export default function Links() { | ||||
|           <SocialIcon network="linkedin" bgColor="#111" /> LinkedIn | ||||
|         </Link> | ||||
|         <Link href="https://git.nrx.sh"> | ||||
|           <SiGitea size={40} /> Private Git Repo | ||||
|           <SiGitea size={40} /> Self-Hosted Git Repos | ||||
|         </Link> | ||||
|       </div> | ||||
|     </> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user