Skip to content

Commit

Permalink
🐮 Add markdown editor (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
nezouse authored Feb 21, 2024
1 parent 13d0245 commit 1565b3b
Show file tree
Hide file tree
Showing 9 changed files with 1,017 additions and 47 deletions.
104 changes: 61 additions & 43 deletions app/waves/[waveId]/applications/[applicationId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import { notFound } from "next/navigation";
import { db } from "@/drizzle/db";
import { applications } from "@/drizzle/schema";
import { eq } from "drizzle-orm";
import rehypeSanitize from "rehype-sanitize";
import rehypeStringify from "rehype-stringify";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import { unified } from "unified";

import { auth } from "@/lib/auth";
import { formatDate } from "@/lib/dates";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Card } from "@/components/ui/card";
Expand All @@ -29,6 +33,13 @@ export default async function Application({
return notFound();
}

const applicationHtml = await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeSanitize)
.use(rehypeStringify)
.process(application.description);

return (
<div className="flex w-full flex-col gap-4">
<h2 className="text-2xl">{application.name}</h2>
Expand All @@ -50,48 +61,55 @@ export default async function Application({
<div className="font-medium">{application.users.name}</div>
<div className="text-sm">{formatDate(application.createdAt)}</div>
</div>
<p className="text-justify">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Excepturi
exercitationem dicta aspernatur veniam doloremque tempora harum
officia quo fugit assumenda. Vero ad obcaecati optio ab iusto natus,
quam iure ullam. Odit laboriosam maxime numquam amet doloribus
velit, fugit eius doloremque deleniti non inventore soluta nobis
deserunt similique! Molestias commodi molestiae magni quisquam? Et
dolorum voluptatum quod id inventore quae sapiente! Temporibus cum
consectetur iure eius, eaque qui rerum neque quo, vitae reiciendis
distinctio quas obcaecati. Maxime dolorum obcaecati esse voluptatem
sapiente, repudiandae dolores beatae, aut autem debitis ab dolore
impedit! Alias inventore repellat illum! Doloribus facilis cum,
consequatur blanditiis officiis adipisci, a nostrum voluptatem sint
minus soluta unde illo voluptates, nesciunt fuga? Nostrum, quod.
Voluptatum sapiente molestiae id veritatis in. Ea, quae repellendus.
Quia, dolorem beatae natus non adipisci laudantium minus. Quos
tempore nostrum optio doloribus aut reprehenderit, vel beatae sequi
quia molestias expedita adipisci dolorum consectetur cum ducimus
commodi! Praesentium est rem molestias dolores sunt possimus dicta
laudantium sequi minus quidem saepe eos, soluta, ut dolore vero
libero quos recusandae cupiditate tenetur officia illum nam enim.
Praesentium, debitis molestias! Labore nihil optio deserunt
sapiente. Optio atque, sed et sequi totam rerum voluptates similique
est eum, soluta voluptas vero maxime excepturi vel inventore modi
libero tempora aperiam fuga. Error, reiciendis! Quis, reprehenderit
delectus amet eos quo vel minus exercitationem eum dolores possimus
culpa totam sint ad, molestiae cum, sunt qui natus tenetur nostrum
id quam sequi? Maxime quos totam rem? Earum dicta ullam maxime quod
dolores praesentium repellat magnam ex fugiat ea quidem, hic aliquam
atque vel blanditiis mollitia ratione. Commodi cum reiciendis vero,
neque aliquid eum magni qui libero. Porro, modi. Similique, dolorum
nulla accusantium perspiciatis aut accusamus rerum dolor. Totam
recusandae, exercitationem perferendis, veniam quod accusamus ipsam
enim atque deserunt, aliquam reiciendis non nobis laudantium maiores
fugiat eaque? Vitae quasi aliquam dolore magnam dicta neque deleniti
aliquid? Quibusdam suscipit, est autem similique soluta laborum
error inventore velit aut, at sed doloremque ipsum quam! Cumque
quasi iure labore error. Commodi nam quam nesciunt in repellendus
aliquid omnis, illo dolores nihil, nemo voluptas soluta sed
repudiandae a, ipsam esse. Consequatur, provident. Consequuntur,
eaque laborum? Maxime possimus culpa quae itaque iusto?
</p>
<div className="prose max-w-none">
<div
dangerouslySetInnerHTML={{ __html: applicationHtml.toString() }}
/>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
Excepturi exercitationem dicta aspernatur veniam doloremque
tempora harum officia quo fugit assumenda. Vero ad obcaecati optio
ab iusto natus, quam iure ullam. Odit laboriosam maxime numquam
amet doloribus velit, fugit eius doloremque deleniti non inventore
soluta nobis deserunt similique! Molestias commodi molestiae magni
quisquam? Et dolorum voluptatum quod id inventore quae sapiente!
Temporibus cum consectetur iure eius, eaque qui rerum neque quo,
vitae reiciendis distinctio quas obcaecati. Maxime dolorum
obcaecati esse voluptatem sapiente, repudiandae dolores beatae,
aut autem debitis ab dolore impedit! Alias inventore repellat
illum! Doloribus facilis cum, consequatur blanditiis officiis
adipisci, a nostrum voluptatem sint minus soluta unde illo
voluptates, nesciunt fuga? Nostrum, quod. Voluptatum sapiente
molestiae id veritatis in. Ea, quae repellendus. Quia, dolorem
beatae natus non adipisci laudantium minus. Quos tempore nostrum
optio doloribus aut reprehenderit, vel beatae sequi quia molestias
expedita adipisci dolorum consectetur cum ducimus commodi!
Praesentium est rem molestias dolores sunt possimus dicta
laudantium sequi minus quidem saepe eos, soluta, ut dolore vero
libero quos recusandae cupiditate tenetur officia illum nam enim.
Praesentium, debitis molestias! Labore nihil optio deserunt
sapiente. Optio atque, sed et sequi totam rerum voluptates
similique est eum, soluta voluptas vero maxime excepturi vel
inventore modi libero tempora aperiam fuga. Error, reiciendis!
Quis, reprehenderit delectus amet eos quo vel minus exercitationem
eum dolores possimus culpa totam sint ad, molestiae cum, sunt qui
natus tenetur nostrum id quam sequi? Maxime quos totam rem? Earum
dicta ullam maxime quod dolores praesentium repellat magnam ex
fugiat ea quidem, hic aliquam atque vel blanditiis mollitia
ratione. Commodi cum reiciendis vero, neque aliquid eum magni qui
libero. Porro, modi. Similique, dolorum nulla accusantium
perspiciatis aut accusamus rerum dolor. Totam recusandae,
exercitationem perferendis, veniam quod accusamus ipsam enim atque
deserunt, aliquam reiciendis non nobis laudantium maiores fugiat
eaque? Vitae quasi aliquam dolore magnam dicta neque deleniti
aliquid? Quibusdam suscipit, est autem similique soluta laborum
error inventore velit aut, at sed doloremque ipsum quam! Cumque
quasi iure labore error. Commodi nam quam nesciunt in repellendus
aliquid omnis, illo dolores nihil, nemo voluptas soluta sed
repudiandae a, ipsam esse. Consequatur, provident. Consequuntur,
eaque laborum? Maxime possimus culpa quae itaque iusto?
</p>
</div>
</div>
</Card>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export async function createApplicationAction(
.insert(applications)
.values({
name: data.projectName,
description: data.description,
waveId,
userId: session.user.id,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ import { z } from "zod";

export const createApplicationSchema = z.object({
projectName: z.string(),
description: z.string(),
});
export type createApplicationSchema = z.infer<typeof createApplicationSchema>;
17 changes: 16 additions & 1 deletion app/waves/[waveId]/applications/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";

import { Button } from "@/components/ui/button";
import { Editor } from "@/components/ui/editor";
import {
Form,
FormControl,
Expand All @@ -26,6 +27,7 @@ export default function CreateApplication({
resolver: zodResolver(createApplicationSchema),
defaultValues: {
projectName: "",
description: "",
},
});

Expand All @@ -44,7 +46,7 @@ export default function CreateApplication({
name="projectName"
render={({ field }) => (
<FormItem>
<FormLabel htmlFor="waveName">Project name</FormLabel>
<FormLabel>Project name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
Expand All @@ -53,6 +55,19 @@ export default function CreateApplication({
)}
/>

<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Editor onChange={field.onChange} />
</FormControl>
</FormItem>
)}
/>

<Button disabled={form.formState.isSubmitting}>Create</Button>
</form>
</Form>
Expand Down
65 changes: 65 additions & 0 deletions components/ui/editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"use client";

import { CodeNode } from "@lexical/code";
import { LinkNode } from "@lexical/link";
import { ListItemNode, ListNode } from "@lexical/list";
import { $convertToMarkdownString, TRANSFORMERS } from "@lexical/markdown";
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
import { HorizontalRuleNode } from "@lexical/react/LexicalHorizontalRuleNode";
import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
import { HeadingNode, QuoteNode } from "@lexical/rich-text";

interface EditorProps {
onChange: (value: string) => void;
}

export function Editor({ onChange }: EditorProps) {
return (
<LexicalComposer
initialConfig={{
namespace: "Editor",
onError(error) {
console.error(error);
},
nodes: [
HorizontalRuleNode,
CodeNode,
HeadingNode,
LinkNode,
ListNode,
ListItemNode,
QuoteNode,
],
}}
>
<div className="relative">
<RichTextPlugin
contentEditable={
<ContentEditable className="prose min-h-[60px] w-full max-w-none rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50" />
}
placeholder={
<div className="absolute left-3 top-2 text-sm">
Enter some text...
</div>
}
ErrorBoundary={LexicalErrorBoundary}
/>
</div>
<HistoryPlugin />
<MarkdownShortcutPlugin transformers={TRANSFORMERS} />
<OnChangePlugin
onChange={(editor) => {
editor.read(() => {
const markdown = $convertToMarkdownString(TRANSFORMERS);
onChange(markdown);
});
}}
/>
</LexicalComposer>
);
}
1 change: 1 addition & 0 deletions drizzle/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const waves = pgTable("wave", {
export const applications = pgTable("application", {
id: serial("id").primaryKey(),
name: text("name").notNull(),
description: text("description").notNull(),
waveId: integer("waveId")
.notNull()
.references(() => waves.id),
Expand Down
13 changes: 13 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@
"@auth/core": "^0.27.0",
"@auth/drizzle-adapter": "^0.7.0",
"@hookform/resolvers": "^3.3.4",
"@lexical/code": "^0.13.1",
"@lexical/link": "^0.13.1",
"@lexical/list": "^0.13.1",
"@lexical/markdown": "^0.13.1",
"@lexical/react": "^0.13.1",
"@lexical/rich-text": "^0.13.1",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-slot": "^1.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"drizzle-orm": "^0.29.3",
"lexical": "^0.13.1",
"lucide-react": "^0.331.0",
"next": "14.1.0",
"next-auth": "5.0.0-beta.11",
Expand All @@ -29,13 +36,19 @@
"react-day-picker": "^8.10.0",
"react-dom": "^18",
"react-hook-form": "^7.50.1",
"rehype-sanitize": "^6.0.0",
"rehype-stringify": "^10.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.0",
"tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7",
"unified": "^11.0.4",
"zod": "^3.22.4"
},
"devDependencies": {
"@ianvs/prettier-plugin-sort-imports": "^4.1.1",
"@playwright/test": "^1.41.2",
"@tailwindcss/typography": "^0.5.10",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
Loading

0 comments on commit 1565b3b

Please sign in to comment.