diff --git a/bun.lock b/bun.lock index 522c683..4ce37f9 100644 --- a/bun.lock +++ b/bun.lock @@ -79,6 +79,7 @@ "recharts": "2.15.4", "rehype-highlight": "^7.0.2", "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.1", "server-only": "^0.0.1", "shadcn": "^4.0.2", "sonner": "^2.0.7", @@ -550,10 +551,10 @@ "@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="], - "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], - "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], + "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], "@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="], diff --git a/package.json b/package.json index e587fa5..a688453 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "recharts": "2.15.4", "rehype-highlight": "^7.0.2", "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.1", "server-only": "^0.0.1", "shadcn": "^4.0.2", "sonner": "^2.0.7", diff --git a/src/app/admin/_components/AdminSideBar.tsx b/src/app/admin/_components/AdminSideBar.tsx index 9e473a7..6133c7b 100644 --- a/src/app/admin/_components/AdminSideBar.tsx +++ b/src/app/admin/_components/AdminSideBar.tsx @@ -1,14 +1,15 @@ import Link from "next/link"; +import { ScrollArea } from "~/components/ui/scroll-area"; import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger } from "~/components/ui/sidebar"; import SimpleSidebarGroup from "~/components/ui/simple-sidebar-group"; export default function AdminSideBar() { return ( <> - - + + Create Category Create Entry @@ -29,9 +30,9 @@ export default function AdminSideBar() { System Prompt + - ) } diff --git a/src/app/admin/cv/category/list/page.tsx b/src/app/admin/cv/category/list/page.tsx index 1e3fd95..c5a67b2 100644 --- a/src/app/admin/cv/category/list/page.tsx +++ b/src/app/admin/cv/category/list/page.tsx @@ -11,13 +11,8 @@ import CreateUpdateCvCategoryForm from "../_components/CreateUpdateForm"; export default function CvPage() { const categories = trpc.category.select.useQuery({}, { refetchInterval: 1000 }); const entires = trpc.entry.select.useSuspenseQuery({}, { refetchInterval: 1000 }) - const gsap = useGsapContext() - const container = useRef(null); - useGSAP(() => { - gsap?.from('.gsapan', { x: -100, opacity: 0, duration: 0.5, stagger: { each: 0.3 } }); - }, { scope: container, dependencies: [categories.status], revertOnUpdate: true }); return ( -
+ <> {categories.data == undefined ?
: @@ -64,6 +59,6 @@ export default function CvPage() { } -
+ ) } diff --git a/src/app/admin/cv/entry/list/page.tsx b/src/app/admin/cv/entry/list/page.tsx index ed9622c..67a292e 100644 --- a/src/app/admin/cv/entry/list/page.tsx +++ b/src/app/admin/cv/entry/list/page.tsx @@ -8,13 +8,9 @@ import { useGsapContext } from "~/app/_providers/GsapProvicer"; export default function CvPage() { const entires = trpc.entry.select.useQuery({}); - const gsap = useGsapContext() const container = useRef(null); - useGSAP(() => { - gsap?.from('.gsapan', { x: -100, opacity: 0, duration: 0.5, stagger: { each: 0.3 } }) - }, { scope: container, dependencies: [entires.status], revertOnUpdate: true }); return ( -
+ <> {entires.data == undefined ?
: @@ -40,6 +36,6 @@ export default function CvPage() { })} } -
+ ) } diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx index d50ec93..cc40a7d 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/layout.tsx @@ -1,14 +1,18 @@ +import { SidebarProvider } from "~/components/ui/sidebar"; import AdminSideBar from "./_components/AdminSideBar"; +import { ScrollArea } from "~/components/ui/scroll-area"; export const dynamic = 'force-dynamic'; export default function Admin({children}: Readonly<{children: React.ReactNode}>) { return ( <> + -
+ {children} -
+ +
) } diff --git a/src/app/admin/project/list/page.tsx b/src/app/admin/project/list/page.tsx index d560f43..961608a 100644 --- a/src/app/admin/project/list/page.tsx +++ b/src/app/admin/project/list/page.tsx @@ -10,7 +10,7 @@ export default function ProjectList() { const projects = trpc.project.select.useQuery({}, { refetchInterval: 1000 }) const techStacks = trpc.techStack.select.useSuspenseQuery({}, { refetchInterval: 1000 }) return ( -
+ <> { projects.data == undefined ? <> : @@ -55,6 +55,6 @@ export default function ProjectList() { } -
+ ) } diff --git a/src/app/projects/page.tsx b/src/app/projects/page.tsx index 80616d1..eb5309d 100644 --- a/src/app/projects/page.tsx +++ b/src/app/projects/page.tsx @@ -11,6 +11,7 @@ import AnimateTextIn from "../_components/Animated/AnimateIn"; import { useTimeLine } from "../_providers/GsapProvicer"; import AnimatePopUp from "../_components/Animated/AnimatePopUp"; import { Button } from "~/components/ui/button"; +import remarkGfm from "remark-gfm" export default function ProjectsPage() { const { data: projects, isLoading } = trpc.projectv2.listWithStack.useQuery(); @@ -61,8 +62,9 @@ export default function ProjectsPage() { {project.description && (
- - {project.description} + + {project.description} +
)}
diff --git a/src/server/routers/project.ts b/src/server/routers/project.ts index 8f3604e..3bfaf15 100644 --- a/src/server/routers/project.ts +++ b/src/server/routers/project.ts @@ -1,12 +1,92 @@ import { publicProcedure, router } from "~/server/trpc"; import { db } from "~/server/db"; +type ReadmeRequest = { + url: string; +}; + +function getReadmeRequest(sourceLink: string): ReadmeRequest | null { + let url: URL; + + try { + url = new URL(sourceLink); + } catch { + return null; + } + + const pathParts = url.pathname.split("/").filter(Boolean); + const [owner, repo] = pathParts; + + if (!owner || !repo) { + return null; + } + + const repoName = repo.replace(/\.git$/, ""); + + if (url.hostname === "github.com" || url.hostname === "www.github.com") { + return { + url: `https://raw.githubusercontent.com/${owner}/${repoName}/main/README.md`, + }; + } + + if (url.hostname.includes("gitea.")) { + return { + url: `${url.origin}/${owner}/${repoName}/raw/branch/main/README.md`, + }; + } + + return null; +} + +async function fetchReadme(sourceLink: string) { + const readmeRequest = getReadmeRequest(sourceLink); + + if (!readmeRequest) { + return null; + } + + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 5000); + + try { + const response = await fetch(readmeRequest.url, { + headers: { + Accept: "text/plain", + }, + signal: controller.signal, + }); + + if (!response.ok) { + return null; + } + + return await response.text(); + } catch { + return null; + } finally { + clearTimeout(timeout); + } +} + export const projectRouter = router({ listWithStack: publicProcedure.query(async () => { - return db.query.project.findMany({ + const projects = await db.query.project.findMany({ with: { techStack: true, }, }); + + return Promise.all( + projects.map(async (project) => { + if (project.description?.length !== 0 || !project.sourceLink) { + return project; + } + + return { + ...project, + description: await fetchReadme(project.sourceLink), + }; + }), + ); }), });