Merge pull request 'dev' (#4) from dev into production
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 0s

Reviewed-on: #4
This commit is contained in:
pablo 2025-03-17 19:55:42 +00:00
commit 800bce807e
13 changed files with 177 additions and 161 deletions

View File

@ -7,4 +7,5 @@ jobs:
runs-on: linux runs-on: linux
steps: steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event." - run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🎉 Test job completed! 🚀" - run: ssh
- run: echo "🍏 This job's status is ${{ gitea.job_status }}."

View File

@ -2,6 +2,7 @@ import BreadNavigator from "@/components/bread-navigator";
import RenderContent from "@/components/editor/render-content"; import RenderContent from "@/components/editor/render-content";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { appRoutes } from "@/config"; import { appRoutes } from "@/config";
import { hasPermission, Role } from "@/lib/validation/permissions"; import { hasPermission, Role } from "@/lib/validation/permissions";
import { auth } from "@/server/auth"; import { auth } from "@/server/auth";
@ -20,8 +21,8 @@ async function Page({ params }: { params: Promise<{ slug: string }> }) {
? hasPermission(session.user.role, Role.EDITOR) ? hasPermission(session.user.role, Role.EDITOR)
: false; : false;
return ( return (
<div className="space-y-2"> <div className="">
<div className="flex w-full items-center justify-between"> {/* <div className="flex w-full items-center justify-between">
<div className="flex w-full items-center gap-4"> <div className="flex w-full items-center gap-4">
<BreadNavigator <BreadNavigator
className="w-full" className="w-full"
@ -39,6 +40,28 @@ async function Page({ params }: { params: Promise<{ slug: string }> }) {
]} ]}
/> />
</div> </div>
</div> */}
<div className="flex w-full flex-col gap-4 xl:flex-row">
<div className="w-full space-y-4">
<h1 className="text-4xl font-bold">{article.title}</h1>
<div className="w-full">
{article.content && <RenderContent content={article.content} />}
</div>
</div>
<Separator
orientation="vertical"
className="sticky top-0 hidden h-screen -translate-y-4 xl:block"
/>
<div
className={
"top-4 h-max w-full max-w-xl space-y-4 xl:sticky xl:max-w-sm"
}
>
<div className="relative min-h-96 w-full space-y-4 overflow-hidden rounded-md border bg-background p-4">
<p className="text-center text-muted-foreground">
Kommentare bald verfügbar
</p>
</div>
{isEditor && ( {isEditor && (
<div className="flex w-full items-center justify-end space-x-2"> <div className="flex w-full items-center justify-end space-x-2">
<Badge <Badge
@ -60,8 +83,7 @@ async function Page({ params }: { params: Promise<{ slug: string }> }) {
</div> </div>
)} )}
</div> </div>
<h1 className="text-4xl font-bold">{article.title}</h1> </div>
{article.content && <RenderContent content={article.content} />}
</div> </div>
); );
} }

View File

@ -24,6 +24,7 @@ async function Page({ params }: CategoryPageProps) {
articles: true, articles: true,
}, },
}); });
console.log(category);
if (!category) return notFound(); if (!category) return notFound();

View File

@ -23,6 +23,7 @@ function UserForm({ server_user, cb }: { server_user: User; cb?: () => void }) {
const form = useForm<z.infer<typeof userSchema>>({ const form = useForm<z.infer<typeof userSchema>>({
resolver: zodResolver(userSchema), resolver: zodResolver(userSchema),
defaultValues: { defaultValues: {
email: server_user.email ?? "",
name: server_user?.name ?? "", name: server_user?.name ?? "",
}, },
}); });

View File

@ -15,7 +15,7 @@ export default async function Home() {
return ( return (
<> <>
<Alert> <Alert>
<div className="flex gap-2"> <div className="flex flex-col gap-2 sm:flex-row">
<Icons.logo className="size-8" /> <Icons.logo className="size-8" />
<div> <div>
<AlertTitle className="font-bold"> <AlertTitle className="font-bold">
@ -25,7 +25,7 @@ export default async function Home() {
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Lorem ipsum, dolor sit amet consectetur adipisicing elit.
</AlertDescription> </AlertDescription>
</div> </div>
<Button asChild className="ml-auto"> <Button asChild className="sm:ml-auto">
<Link href={appRoutes.about}> <Link href={appRoutes.about}>
Erfahre mehr über {appConfig.name} Erfahre mehr über {appConfig.name}
</Link> </Link>

View File

@ -68,7 +68,7 @@ export default ({ server_article }: { server_article: Article }) => {
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="flex w-full flex-col-reverse gap-4 space-y-4 xl:flex-row" className="flex w-full flex-col-reverse gap-4 xl:flex-row"
> >
<div className="flex w-full max-w-3xl flex-col gap-2"> <div className="flex w-full max-w-3xl flex-col gap-2">
<FormField <FormField
@ -112,12 +112,7 @@ export default ({ server_article }: { server_article: Article }) => {
/> />
</div> </div>
<div <div className={"top-4 h-max w-full max-w-xl xl:sticky xl:max-w-md"}>
className={cn(
"top-4 h-max w-full max-w-xl xl:sticky xl:max-w-md",
// loading && "border-t-blue-600",
)}
>
<div className="relative w-full space-y-4 overflow-hidden rounded-md border bg-background p-4"> <div className="relative w-full space-y-4 overflow-hidden rounded-md border bg-background p-4">
<div <div
className={cn( className={cn(

View File

@ -6,18 +6,31 @@ import { Icons } from "../icons";
function CategorySelect(props: Partial<ComboboxProps>) { function CategorySelect(props: Partial<ComboboxProps>) {
const { data: categories } = api.category.getMany.useQuery(); const { data: categories } = api.category.getMany.useQuery();
const initialValue = categories?.find(
(c) => c.id === props?.initialValue,
)?.slug;
return ( return (
<Combobox <Combobox
{...(props as ComboboxProps)} {...(props as ComboboxProps)}
initialValue={initialValue}
messageUi={{ messageUi={{
select: "Kategorie auswählen...", select: "Kategorie auswählen...",
selectIcon: Icons.category, selectIcon: Icons.category,
placeholder: "Kategorie suchen...", placeholder: "Kategorie suchen...",
empty: "Keine Kategorien gefunden", empty: "Keine Kategorien gefunden",
}} }}
onSelect={(value) => {
const id = categories?.find((c) => {
return c.slug === value;
})?.id!;
props?.onSelect?.(id);
}}
data={ data={
categories?.map(({ name, slug }) => ({ label: name, value: slug })) ?? categories?.map(({ name, slug }) => ({
[] label: name,
value: slug,
})) ?? []
} }
/> />
); );

View File

@ -58,22 +58,18 @@ const updatedImage = UpdatedImage.configure({
}, },
}); });
const taskList = TaskList.configure({ const taskList = TaskList.configure({});
HTMLAttributes: {
class: cx("not-prose pl-2 "),
},
});
const taskItem = TaskItem.configure({ const taskItem = TaskItem.configure({
HTMLAttributes: { HTMLAttributes: {
class: cx("flex gap-2 items-start my-4"), class: cx("flex gap-2 items-start "),
}, },
nested: true, nested: true,
}); });
const horizontalRule = HorizontalRule.configure({ const horizontalRule = HorizontalRule.configure({
HTMLAttributes: { HTMLAttributes: {
class: cx("mt-4 mb-6 border-t border-muted-foreground"), class: cx("mt-4 mb-6 border border-border"),
}, },
}); });
@ -85,6 +81,21 @@ const starterKit = StarterKit.configure({
heading: { heading: {
levels: [2, 3, 4, 5, 6], levels: [2, 3, 4, 5, 6],
}, },
bulletList: {
HTMLAttributes: {
class: cx("list-disc ml-4 px-2 "),
},
},
orderedList: {
HTMLAttributes: {
class: cx("list-decimal ml-4 px-2 "),
},
},
blockquote: {
HTMLAttributes: {
class: cx("m-4 border-l-4 border-border pl-4"),
},
},
gapcursor: false, gapcursor: false,
horizontalRule: false, horizontalRule: false,
}); });

View File

@ -17,18 +17,38 @@
margin: 0.75rem 0; margin: 0.75rem 0;
} }
.tiptap ul, ul[data-type="taskList"] li > label input[type="checkbox"] {
.tiptap ol { @apply size-4 border border-border bg-muted;
margin-left: 1rem; -webkit-appearance: none;
padding: 0.25rem; appearance: none;
margin: 0;
margin-bottom: 0.75rem;
margin-top: 0.75rem;
cursor: pointer;
position: relative;
transform: translateY(0.17rem);
display: grid;
border-radius: 0.25rem;
place-content: center;
} }
.tiptap ul { ul[data-type="taskList"] li > label input[type="checkbox"]::before {
list-style-type: disc; content: "";
width: 0.65em;
height: 0.65em;
transform: scale(0);
transition: 120ms transform ease-in-out;
box-shadow: inset 1em 1em;
transform-origin: center;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
} }
ul[data-type="taskList"] li > label input[type="checkbox"]:checked::before {
.tiptap ol { transform: scale(1);
list-style-type: decimal; }
ul[data-type="taskList"] li[data-checked="true"] > div > p {
@apply text-foreground/75;
text-decoration: line-through;
text-decoration-thickness: 2px;
} }
/* Heading styles */ /* Heading styles */
@ -65,17 +85,9 @@
font-size: 1.125rem; /* Equivalent to text-lg */ font-size: 1.125rem; /* Equivalent to text-lg */
} }
.tiptap blockquote { /* .tiptap blockquote {
@apply m-4 border-l-4 border-border pl-4; @apply ;
/* border-left: 1rem solid var(--primary); } */
padding-left: 1rem; */
}
.tiptap hr {
@apply border-border;
/* border-color: var(--border); */
margin: 0.25rem 0;
}
::selection { ::selection {
background-color: #5abbf7; background-color: #5abbf7;
@ -89,52 +101,6 @@ img {
@apply rounded-md; @apply rounded-md;
} }
/* Task List */
ul[data-type="taskList"] li {
@apply m-0 my-3 flex items-center;
}
ul[data-type="taskList"] li p {
@apply m-0 my-0 flex items-center;
}
ul[data-type="taskList"] li > label input[type="checkbox"] {
@apply size-4 border border-border bg-muted;
-webkit-appearance: none;
appearance: none;
margin: 0;
cursor: pointer;
position: relative;
display: grid;
border-radius: 0.25rem;
place-content: center;
}
/* ul[data-type="taskList"] li > label input[type="checkbox"]:hover {
background-color: #000;
}
ul[data-type="taskList"] li > label input[type="checkbox"]:active {
background-color: #000;
} */
ul[data-type="taskList"] li > label input[type="checkbox"]::before {
content: "";
width: 0.65em;
height: 0.65em;
transform: scale(0);
transition: 120ms transform ease-in-out;
box-shadow: inset 1em 1em;
transform-origin: center;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
ul[data-type="taskList"] li > label input[type="checkbox"]:checked::before {
transform: scale(1);
}
ul[data-type="taskList"] li[data-checked="true"] > div > p {
@apply text-foreground/75;
text-decoration: line-through;
text-decoration-thickness: 2px;
}
.ProseMirror:not(.dragging) .ProseMirror-selectednode { .ProseMirror:not(.dragging) .ProseMirror-selectednode {
outline: none !important; outline: none !important;
background-color: var(--novel-highlight-blue); background-color: var(--novel-highlight-blue);

View File

@ -4,7 +4,7 @@ import { createCallerFactory, createTRPCRouter } from "@/server/api/trpc";
import { usersRouter } from "./routers/users"; import { usersRouter } from "./routers/users";
import { authorRouter } from "./routers/author"; import { authorRouter } from "./routers/author";
import { appRouter as globalRouter } from "./routers/app"; import { appRouter as globalRouter } from "./routers/app";
import { authRouter } from "./routers/auth"; // import { authRouter } from "./routers/auth";
/** /**
* This is the primary router for your server. * This is the primary router for your server.
* *
@ -16,7 +16,7 @@ export const appRouter = createTRPCRouter({
users: usersRouter, users: usersRouter,
author: authorRouter, author: authorRouter,
app: globalRouter, app: globalRouter,
auth: authRouter, // auth: authRouter,
}); });
// export type definition of API // export type definition of API

View File

@ -162,10 +162,15 @@ export const articleRouter = createTRPCRouter({
) )
.query(async ({ ctx, input }) => { .query(async ({ ctx, input }) => {
const limit = input?.limit ?? 50; const limit = input?.limit ?? 50;
return (await ctx.db.query.articles.findMany({ const where = and(
where: input?.categoryId input?.categoryId
? eq(articles.categoryId, input.categoryId) ? eq(articles.categoryId, input.categoryId)
: undefined, : undefined,
eq(articles.published, true),
);
return (await ctx.db.query.articles.findMany({
where,
limit: limit, limit: limit,
columns: { columns: {
title: true, title: true,

View File

@ -1,54 +1,54 @@
import { passwordSchema, userSchema } from "@/lib/validation/zod/user"; // import { passwordSchema, userSchema } from "@/lib/validation/zod/user";
import { createTRPCRouter, publicProcedure } from "../trpc"; // import { createTRPCRouter, publicProcedure } from "../trpc";
import { z } from "zod"; // import { z } from "zod";
import { eq } from "drizzle-orm"; // import { eq } from "drizzle-orm";
import { users } from "@/server/db/schema"; // import { users } from "@/server/db/schema";
import argon from "argon2"; // import argon from "argon2";
export const authRouter = createTRPCRouter({ // export const authRouter = createTRPCRouter({
register: publicProcedure // register: publicProcedure
.input( // .input(
z.object({ // z.object({
user: userSchema, // user: userSchema,
password: passwordSchema, // password: passwordSchema,
}), // }),
) // )
.mutation(async ({ ctx, input }) => { // .mutation(async ({ ctx, input }) => {
const { // const {
password, // password,
user: { email, name }, // user: { email, name },
} = input; // } = input;
// Check if user already exists // // Check if user already exists
try { // try {
const existingUser = await ctx.db.query.users.findFirst({ // const existingUser = await ctx.db.query.users.findFirst({
where: eq(users.email, email), // where: eq(users.email, email),
}); // });
if (existingUser) { // if (existingUser) {
return { success: false, message: "User already exists" }; // return { success: false, message: "User already exists" };
} // }
// Hash the password (12 is a good cost factor) // // Hash the password (12 is a good cost factor)
const hashedPassword = await argon.hash(password); // const hashedPassword = await argon.hash(password);
// Create user in database // // Create user in database
const [user] = await ctx.db // const [user] = await ctx.db
.insert(users) // .insert(users)
.values({ // .values({
name, // name,
email, // email,
password: hashedPassword, // password: hashedPassword,
}) // })
.returning({ id: users.id }); // .returning({ id: users.id });
console.log(user); // console.log(user);
if (user) { // if (user) {
return { success: true, message: "User created successfully" }; // return { success: true, message: "User created successfully" };
} // }
return { success: false, message: "Error creating user" }; // return { success: false, message: "Error creating user" };
} catch (e) { // } catch (e) {
console.error(e); // console.error(e);
return { success: false, message: "Error creating user" }; // return { success: false, message: "Error creating user" };
} // }
}), // }),
}); // });

View File

@ -85,6 +85,7 @@ export const categoryRouter = createTRPCRouter({
name: true, name: true,
slug: true, slug: true,
createdAt: true, createdAt: true,
id: true,
}, },
})) as Category[]; })) as Category[];
}), }),