better markdown support, minor layout fixes

This commit is contained in:
2025-06-17 03:21:41 +02:00
parent d176f66865
commit 9f58cc3207
10 changed files with 674 additions and 30 deletions

View File

@@ -0,0 +1,15 @@
'use client'
import { useTheme } from "next-themes";
export function CodeHighlightStyle() {
const { theme } = useTheme()
if (theme == 'dark') {
return (
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.3.2/build/styles/atelier-lakeside-dark.min.css"/>
)
} else {
return (
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.3.2/build/styles/atelier-lakeside-light.min.min.css"/>
)
}
}

View File

@@ -41,7 +41,7 @@ export default function CvPage() {
cat.cvEntry.length > 0 ? (
<>
{cat.cvEntry.map((entry) => (
<CollapsibleCvEntryForm key={entry.id} entry={entry} />
<CollapsibleCvEntryForm key={entry.id} entry={entry} categoryId={undefined} />
))}
</>
) : (<></>)

View File

@@ -16,12 +16,15 @@ import { Calendar } from "~/components/ui/calendar";
import { cn } from "~/lib/utils";
import { useEffect, useState } from "react"
import { Checkbox } from "~/components/ui/checkbox"
import MDEditor from '@uiw/react-md-editor'
import { useTheme } from "next-themes"
export default function CreateCvEntryForm(params:{className:string|undefined, categoryId:string|undefined}) {
const { theme } = useTheme()
const categories = trpc.cv.category.list.useQuery()
console.log(params.categoryId)
const [categorySelectPlaceHolder,setCategorySelectPlaceHolder] = useState("Select category")
const [categorySelectDefaultValue,setCategorySelectDefaultValue] = useState("")
const [mdeTab,setMdeTab] = useState<"write" | "preview" | undefined>("write");
useEffect(() => {
console.log('category success effect')
if (categories.data !== undefined) {
@@ -38,6 +41,8 @@ export default function CreateCvEntryForm(params:{className:string|undefined, ca
}
}
},[categories.isSuccess])
const now = new Date();
now.setTime(Date.now());
const form = useForm<z.infer<typeof insertSchemaForm>>({
resolver: zodResolver(insertSchemaForm),
defaultValues: {
@@ -45,6 +50,8 @@ export default function CreateCvEntryForm(params:{className:string|undefined, ca
title: "",
description: "",
categoryId: params.categoryId ? params.categoryId : "",
fromTime: now,
toTime: now,
hideDates: false,
}
@@ -53,7 +60,7 @@ export default function CreateCvEntryForm(params:{className:string|undefined, ca
onSuccess: (data) => { form.setValue("id", data[0] ? data[0].id : "") }
})
function onSubmit(values: z.infer<typeof insertSchemaForm>) {
let { id, title, categoryId, description } = values
let { id, title, categoryId, description, hideDates } = values
let v: z.infer<typeof insertSchema> = {
id: id,
categoryId: categoryId,
@@ -61,6 +68,7 @@ export default function CreateCvEntryForm(params:{className:string|undefined, ca
title: title,
fromTime: format(values.fromTime,'yyyy-MM-dd'),
toTime: format(values.toTime,'yyyy-MM-dd'),
hideDates: hideDates
}
mutation.mutate(v)
form.setValue("id", crypto.randomUUID())
@@ -128,7 +136,11 @@ export default function CreateCvEntryForm(params:{className:string|undefined, ca
Description
</FormLabel>
<FormControl>
<Input placeholder="description" onChange={field.onChange} value={field.value == null ? undefined : field.value} />
<MDEditor
value={field.value ? field.value : ""}
onChange={field.onChange}
data-color-mode={theme ? theme : "dark"}
/>
</FormControl>
</FormItem>
)}

View File

@@ -11,14 +11,18 @@ import { Button } from "~/components/ui/button";
import * as Card from '~/components/ui/card'
import { format } from 'date-fns'
import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover";
import { cn } from "~/lib/utils";
import { cn, type Element } from "~/lib/utils";
import { CalendarIcon, Delete } from "lucide-react";
import { Calendar } from "~/components/ui/calendar";
import { Textarea } from "~/components/ui/textarea";
import { usePathname, useRouter } from "next/navigation";
import { Checkbox } from "~/components/ui/checkbox";
import MDEditor from '@uiw/react-md-editor'
import { useTheme } from "next-themes"
import type { EntryRouterOutputs } from "~/server/routers/cv/entry";
export default function UpdateCvEntryForm(params : { id: string, className: string | undefined }) {
console.log(params)
const { theme } = useTheme()
const id = params.id
const categories = trpc.cv.category.list.useQuery()
const entry = trpc.cv.entry.get.useQuery({ id: id })
@@ -35,25 +39,29 @@ export default function UpdateCvEntryForm(params : { id: string, className: stri
}
}
})
entry.promise.then((v) => {
const updateFormValues = (v:EntryRouterOutputs['get']|Element<EntryRouterOutputs['update']>) => {
form.setValue('update.title', v?.title)
form.setValue('update.description', v?.description);
form.setValue('update.fromTime', new Date(Date.parse(v ? v.fromTime : "")))
form.setValue('update.toTime', new Date(Date.parse(v ? v.toTime : "")))
form.setValue('update.categoryId', v?.categoryId)
form.setValue('update.hideDates', v?.hideDates)
}
entry.promise.then((v) => {
updateFormValues(v)
})
const updateMutation = trpc.cv.entry.update.useMutation({
onSuccess: () => {
if (pathname.includes('list')) {
router.refresh()
} else {
router.back()
onSuccess: (v) => {
if (v[0] !== undefined) {
updateFormValues(v[0])
}
}
})
const deleteMutation = trpc.cv.entry.delete.useMutation({
onSuccess: () => {
if (!pathname.includes('list')) {
router.back()
}
}
})
const deleteEntry = (id: string) => {
@@ -137,7 +145,12 @@ export default function UpdateCvEntryForm(params : { id: string, className: stri
Description
</FormLabel>
<FormControl>
<Textarea placeholder="description" onChange={field.onChange} value={field.value == null ? undefined : field.value} />
<MDEditor
value={field.value ? field.value : ""}
onChange={field.onChange}
data-color-mode={theme ? theme : "dark"}
previewOptions={{skipHtml:false}}
/>
</FormControl>
</FormItem>
)}

View File

@@ -22,9 +22,9 @@ export default function CvCategory(props:CvCategoryProps) {
</CardTitle>
</CardHeader>
{(query.data?.cvEntry.length ? query.data?.cvEntry.length : 0 ) > 0 ?
<CardContent className={cn(props.layout == "row" ? "flex flex-row" : "flex flex-col","gap-[1rem]")}>
<CardContent className={cn(props.layout == "row" ? "flex flex-row flex-wrap justify-center lg:justify-between" : "flex flex-col","gap-[1rem]","overflow-scroll")}>
{query.data?.cvEntry.map((entry) => (
<CvEntry key={entry.id} initialData={entry}/>
<CvEntry className={props.layout == "row" ? "w-full lg:w-fit" : undefined} key={entry.id} initialData={entry}/>
))}
</CardContent>
:

View File

@@ -1,15 +1,16 @@
import { trpc } from "~/app/_trpc/Client"
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "~/components/ui/card"
import { Skeleton } from "~/components/ui/skeleton"
import type { Element } from "~/lib/utils"
import { cn, type Element } from "~/lib/utils"
import type { CategoryRouterOutputs } from "~/server/routers/cv/category"
import type { EntryRouterOutputs } from "~/server/routers/cv/entry"
import Markdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
import { format } from 'date-fns'
import rehypeHighlight from 'rehype-highlight'
import rehypeRaw from 'rehype-raw'
export default function CvEntry(params: {
initialData: EntryRouterOutputs['get'] | Element<Element<CategoryRouterOutputs['list']>['cvEntry']>
className?: string
}) {
const query = trpc.cv.entry.get.useQuery({ id: params.initialData?.id ? params.initialData.id : "" })
const { data } = query
@@ -18,7 +19,7 @@ export default function CvEntry(params: {
{
data ?
<>
<Card className="w-fit">
<Card className={params.className ? cn("w-fit",params.className) : "w-fit"}>
{
data.title ?
<CardHeader>
@@ -28,9 +29,9 @@ export default function CvEntry(params: {
}
{
data.description ?
<CardContent>
<CardContent className="text-sm lg:text-base">
<div>
<Markdown rehypePlugins={[rehypeRaw]}>{data.description}</Markdown>
<Markdown rehypePlugins={[rehypeHighlight,rehypeRaw]}>{data.description}</Markdown>
</div>
</CardContent> :
<></>

View File

@@ -61,8 +61,8 @@ export default function CvPage() {
{categories.data.filter((cat) => cat.layoutPosition == 'sidebar').length > 0 ?
<>
<SidebarTriggerDisappearsOnMobile />
<Sidebar className="z-[51] gsapan">
<SidebarContent className="p-2">
<Sidebar className="gsapan ">
<SidebarContent className="p-2 lg:pt-[3.2rem]">
{sidebarCategories.map((cat) => {
return (
<CvCategory layout="col" initialData={cat} key={cat.id} />
@@ -74,17 +74,17 @@ export default function CvPage() {
<></>
}
<div className="h-full w-full flex flex-wrap flex-row p-[1rem] pt-[2rem] ">
<div id="mainwrap" className="flex w-full flex-col gap-[1rem]">
<div id="header" className="flex w-full h-fit flex-row gap-[1rem]">
<div id="mainwrap" className="flex w-full flex-col gap-[1rem] lg:px-[15vw]">
<div id="header" className="flex w-full h-fit flex-row gap-[1rem] flex-wrap">
{headerCategories.map((cat) => {
return (
<CvCategory layout="row" initialData={cat} key={cat.id} />
)
})}
</div>
<div id="colwrapper" className="flex flex-col md:flex-row w-full h-3/4 gap-[1rem]">
<div id="colwrapper" className="flex flex-col lg:flex-row w-full h-3/4 gap-[1rem]">
{col1Categories.length > 0 ?
<div id="col1" className={`flex flex-col w-full ${col2Categories.length > 0 ? "md:w-1/2" : ""} h-full gap-[1rem]`}>
<div id="col1" className={`flex flex-col w-full ${col2Categories.length > 0 ? "lg:w-1/2" : ""} h-full gap-[1rem]`}>
{col1Categories.map((cat) => {
return (
<CvCategory layout="col" initialData={cat} key={cat.id} />
@@ -94,7 +94,7 @@ export default function CvPage() {
<></>
}
{col2Categories.length > 0 ?
<div id="col2" className={`flex flex-col w-full ${col1Categories.length > 0 ? "md:w-1/2" : ""} h-full gap-[1rem]`}>
<div id="col2" className={`flex flex-col w-full ${col1Categories.length > 0 ? "lg:w-1/2" : ""} h-full gap-[1rem]`}>
{col2Categories.map((cat) => {
return (
<CvCategory layout="col" initialData={cat} key={cat.id} />

View File

@@ -10,6 +10,7 @@ import TrpcProvider from "./_trpc/TrpcProvider";
// const ThemeProvider = dynamic(() => import("./_providers/ThemeProvider"),{ssr:true})
import ThemeProvider from './_providers/ThemeProvider'
import GsapProvider from "./_providers/GsapProvicer";
import { CodeHighlightStyle } from "./_components/CodeHighlightSyle";
config.autoAddCss = false;
export const metadata: Metadata = {
title: "Gregor Lohaus",
@@ -34,7 +35,9 @@ export default async function RootLayout({
<ThemeProvider>
<GsapProvider>
<html lang="en" className={`${geist.variable}`} suppressHydrationWarning>
<head />
<head>
<CodeHighlightStyle/>
</head>
<body className="flex flex-col bg-background text-foreground">
<TopNav />
<main className="absolute lg:top-10 h-[100vh] w-[100vw]">