-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2c32868
commit 254df35
Showing
17 changed files
with
2,673 additions
and
788 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
frontend/dashboard/src/components/dashboard/bubbleMenu.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import {BubbleMenu} from '@tiptap/react'; | ||
import { Editor } from '@tiptap/react' | ||
import { Bold, Heading1, Heading2, Italic, ListIcon } from 'lucide-react'; | ||
|
||
interface BubbleMenuProps { | ||
editor: Editor | null; | ||
} | ||
|
||
export const EditorBubbleMenu: React.FC<BubbleMenuProps> = ({ editor }) => { | ||
if (!editor) { | ||
return null | ||
} | ||
|
||
return ( | ||
<BubbleMenu className="flex items-center flex-row bg-gray-300 rounded-md space-x-1 p-2" editor={editor} tippyOptions={{ duration: 100 }}> | ||
<div className={editor.isActive('bold') ? 'bg-gray-400 rounded-md p-1.5' : 'p-1.5'} | ||
onClick={() => editor.chain().focus().toggleBold().run()}> | ||
<Bold size={20} /> | ||
</div> | ||
<div className={editor.isActive('italic') ? 'bg-gray-400 rounded-md p-1.5' : 'p-1.5'} | ||
onClick={() => editor.chain().focus().toggleItalic().run()}> | ||
<Italic size={20}/> | ||
</div> | ||
<div className={editor.isActive('heading', { level: 1 }) ? 'bg-gray-400 rounded-md p-1.5' : 'p-1.5'} | ||
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}> | ||
<Heading1 size={20} /> | ||
</div> | ||
<div className={editor.isActive('heading', { level: 2 }) ? 'bg-gray-400 rounded-md p-1.5' : 'p-1.5'} | ||
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}> | ||
<Heading2 size={20}/> | ||
</div> | ||
<div className={editor.isActive('bulletList') ? 'bg-gray-400 rounded-md p-1.5' : 'p-1.5'} | ||
onClick={() => editor.chain().focus().toggleBulletList().run()}> | ||
<ListIcon size={20}/> | ||
</div> | ||
</BubbleMenu> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export const Divider = () => { | ||
return ( | ||
<div className="bg-black h-5 w-0.5 mx-5" /> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
"use client" | ||
import { useEditor, EditorContent } from '@tiptap/react' | ||
import StarterKit from '@tiptap/starter-kit' | ||
import { Toolbar } from './toolbar' | ||
import Underline from '@tiptap/extension-underline'; | ||
import { Link } from '@tiptap/extension-link'; | ||
import { BubbleMenu } from '@tiptap/extension-bubble-menu'; | ||
import { EditorBubbleMenu } from './bubbleMenu'; | ||
import React from 'react'; | ||
import './styles.css'; | ||
|
||
const Tiptap = () => { | ||
const editor = useEditor({ | ||
autofocus: true, | ||
editable: false, | ||
extensions: [ | ||
StarterKit, Underline, | ||
BubbleMenu, | ||
Link.configure({ | ||
openOnClick: true, | ||
autolink: true, | ||
}), | ||
], | ||
content: {"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"type":"text","text":"Hello World"}]},{"type":"heading","attrs":{"level":2},"content":[{"type":"text","marks":[{"type":"bold"},{"type":"italic"},{"type":"strike"},{"type":"underline"}],"text":"This is generate"}]},{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Task 1"}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Task 2"}]}]}]},{"type":"orderedList","attrs":{"start":1},"content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Task 3"}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Task 4"}]}]}]},{"type":"paragraph","content":[{"type":"text","marks":[{"type":"link","attrs":{"href":"https://generatenu.com","target":"_blank","rel":"noopener noreferrer nofollow","class":null}}],"text":"link"}]}]}, | ||
}) | ||
|
||
return ( | ||
<> | ||
<Toolbar editor={editor}/> | ||
{editor && <EditorBubbleMenu editor={editor}/>} | ||
<EditorContent className="h-15 pl-10 p-5 rounded-md bg-slate-200" editor={editor} /> | ||
</> | ||
) | ||
} | ||
|
||
export default Tiptap; |
104 changes: 104 additions & 0 deletions
104
frontend/dashboard/src/components/dashboard/hyperlink.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { Button } from "../ui/button"; | ||
import { Input } from "../ui/input"; | ||
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; | ||
import { Editor } from "@tiptap/react"; | ||
import { Link } from "lucide-react"; | ||
import { useEffect } from "react"; | ||
import { z } from "zod"; | ||
import { useForm, Controller } from "react-hook-form"; | ||
import { zodResolver } from "@hookform/resolvers/zod"; | ||
|
||
interface HyperLinkProps { | ||
editor: Editor | null; | ||
disabled: boolean; | ||
} | ||
|
||
const linkSchema = z.object({ | ||
link: z.string().url("Invalid URL").optional(), | ||
}); | ||
|
||
type LinkData = z.infer<typeof linkSchema>; | ||
|
||
export const HyperlinkButton: React.FC<HyperLinkProps> = ({ editor, disabled }) => { | ||
const { | ||
control, | ||
handleSubmit, | ||
setValue, | ||
formState: { errors }, | ||
} = useForm<LinkData>({ | ||
resolver: zodResolver(linkSchema), | ||
defaultValues: { | ||
link: editor?.getAttributes("link").href || "", // Use optional chaining here | ||
}, | ||
}); | ||
|
||
useEffect(() => { | ||
if (!editor) return; | ||
|
||
const updateLink = () => { | ||
const currentLink = editor.getAttributes("link").href; | ||
setValue("link", currentLink || ""); | ||
}; | ||
|
||
editor.on("transaction", updateLink); | ||
return () => { | ||
editor.off("transaction", updateLink); | ||
}; | ||
}, [editor, setValue]); | ||
|
||
const setLinkInEditor = (data: LinkData) => { | ||
const url = data.link; | ||
if (!url) { | ||
editor?.chain().focus().unsetLink().run(); | ||
return; | ||
} | ||
editor?.chain().focus().extendMarkRange("link").setLink({ href: url }).run(); | ||
}; | ||
|
||
const onSubmit = handleSubmit((data) => { | ||
setLinkInEditor(data); | ||
}); | ||
|
||
return ( | ||
<Popover> | ||
<PopoverTrigger asChild> | ||
<Button | ||
variant="link" | ||
className={editor?.isActive("link") && editor?.isEditable ? "bg-slate-300 rounded-md p-1.5" : "p-1.5"} | ||
size="icon" | ||
disabled={disabled} | ||
> | ||
<Link /> | ||
</Button> | ||
</PopoverTrigger> | ||
<PopoverContent className="w-76"> | ||
<form onSubmit={onSubmit} className="flex flex-row items-center space-x-5"> | ||
<div className="flex flex-row space-x-2"> | ||
<div className="flex flex-col items-top"> | ||
<Controller | ||
name="link" | ||
control={control} | ||
render={({ field }) => ( | ||
<Input | ||
id="link" | ||
placeholder="Enter a link" | ||
{...field} | ||
onKeyDown={(e) => { | ||
if (e.key === "Enter") { | ||
onSubmit(); | ||
} | ||
}} | ||
/> | ||
)} | ||
/> | ||
{errors.link && ( | ||
<p className="text-sm pt-1 text-red-400">{errors.link.message}</p> | ||
)} | ||
</div> | ||
<Button type="submit">Submit</Button> | ||
</div> | ||
</form> | ||
</PopoverContent> | ||
</Popover> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
.tiptap { | ||
> * + * { | ||
margin-top: 0.75em; | ||
} | ||
|
||
a { | ||
color: revert; | ||
text-decoration: revert; | ||
} | ||
|
||
ul, ol { | ||
list-style: revert; | ||
padding-left: revert; | ||
} | ||
|
||
h1, h2 { | ||
font-size: revert; | ||
font-weight: revert; | ||
} | ||
} |
101 changes: 101 additions & 0 deletions
101
frontend/dashboard/src/components/dashboard/toolbar.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { Editor } from '@tiptap/react' | ||
import {Bold, Italic, Strikethrough, | ||
Heading1, Heading2, ListOrdered, | ||
Undo, Redo, Unlink, | ||
ListIcon, UnderlineIcon} from 'lucide-react'; | ||
import { useState } from 'react'; | ||
import { Divider } from './divider'; | ||
import { HyperlinkButton } from './hyperlink'; | ||
import { Button } from "../ui/button"; | ||
|
||
interface ToolbarProps { | ||
editor: Editor | null; | ||
} | ||
|
||
export const Toolbar: React.FC<ToolbarProps> = ({ editor }) => { | ||
const [, setJSON] = useState("") | ||
|
||
if (!editor) { | ||
return null | ||
} | ||
|
||
return ( | ||
<div className="flex flex-row bg-slate-200 p-2 rounded-md items-center mb-2 justify-between flex-wrap"> | ||
<div className="ml-1 flex flex-row items-center flex-wrap space-x-1"> | ||
<Button onClick={() => editor.chain().focus().toggleBold().run()} | ||
disabled={!editor.isEditable} | ||
variant="link" | ||
size="icon" | ||
className={editor.isActive('bold') && editor.isEditable ? 'bg-slate-300 rounded-md p-1.5' : 'p-1.5'}> | ||
<Bold /> | ||
</Button> | ||
<Button disabled={!editor.isEditable} | ||
variant="link" | ||
size="icon" | ||
onClick={() => editor.chain().focus().toggleItalic().run()} | ||
className={editor.isActive('italic') && editor.isEditable ? 'bg-slate-300 rounded-md p-1.5' : 'p-1.5'}> | ||
<Italic /> | ||
</Button> | ||
<Button variant="link" | ||
size="icon" disabled={!editor.isEditable} onClick={() => editor.chain().focus().toggleStrike().run()} | ||
className={editor.isActive('strike') && editor.isEditable ? 'bg-slate-300 rounded-md p-1.5' : 'p-1.5'}> | ||
<Strikethrough /> | ||
</Button> | ||
<Button variant="link" | ||
size="icon" disabled={!editor.isEditable} onClick={() => editor.chain().focus().toggleUnderline().run()} | ||
className={editor.isActive('underline') && editor.isEditable ? 'bg-slate-300 rounded-md p-1.5' : 'p-1.5'}> | ||
<UnderlineIcon /> | ||
</Button> | ||
<Divider /> | ||
<Button variant="link" | ||
size="icon" disabled={!editor.isEditable} onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} | ||
className={editor.isActive('heading', { level: 1 }) && editor.isEditable ? 'bg-slate-300 rounded-md p-1.5' : 'p-1.5'}> | ||
<Heading1 /> | ||
</Button> | ||
<Button variant="link" | ||
size="icon" disabled={!editor.isEditable} onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} | ||
className={editor.isActive('heading', { level: 2 }) && editor.isEditable ? 'bg-slate-300 rounded-md p-1.5' : 'p-1.5'}> | ||
<Heading2 /> | ||
</Button> | ||
<Divider /> | ||
<Button variant="link" | ||
size="icon" disabled={!editor.isEditable} onClick={() => editor.chain().focus().toggleBulletList().run()} | ||
className={editor.isActive('bulletList') && editor.isEditable ? 'bg-slate-300 rounded-md p-1.5' : 'p-1.5'}> | ||
<ListIcon /> | ||
</Button> | ||
<Button variant="link" | ||
size="icon" disabled={!editor.isEditable} onClick={() => editor.chain().focus().toggleOrderedList().run()} | ||
className={editor.isActive('orderedList') && editor.isEditable ? 'bg-slate-300 rounded-md p-1.5' : 'p-1.5'}> | ||
<ListOrdered /> | ||
</Button> | ||
<HyperlinkButton disabled={!editor.isEditable} editor={editor}/> | ||
<Button | ||
variant="link" | ||
size="icon" | ||
onClick={() => { | ||
editor.chain().focus().unsetLink().run(); | ||
}} | ||
disabled={!editor.isActive('link') || !editor.isEditable} | ||
className="p-1.5" | ||
> | ||
<Unlink /> | ||
</Button> | ||
<Divider /> | ||
<Button variant="link" | ||
size="icon" disabled={!editor.isEditable} className="p-1.5" onClick={() => editor.chain().focus().undo().run()}> | ||
<Undo /> | ||
</Button> | ||
<Button variant="link" | ||
size="icon" disabled={!editor.isEditable} className="p-1.5" onClick={() => editor.chain().focus().redo().run()}> | ||
<Redo /> | ||
</Button> | ||
</div> | ||
|
||
<Button onClick={() => { | ||
setJSON(JSON.stringify(editor.getJSON())); | ||
editor.setEditable(!editor.isEditable); | ||
}} | ||
className="px-4 py-1 bg-black rounded-md text-white text-sm">{editor.isEditable ? "Save" : "Edit"}</Button> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
"use client" | ||
|
||
import * as React from "react" | ||
import * as PopoverPrimitive from "@radix-ui/react-popover" | ||
|
||
import { cn } from "@/lib/utils" | ||
|
||
const Popover = PopoverPrimitive.Root | ||
|
||
const PopoverTrigger = PopoverPrimitive.Trigger | ||
|
||
const PopoverContent = React.forwardRef< | ||
React.ElementRef<typeof PopoverPrimitive.Content>, | ||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> | ||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( | ||
<PopoverPrimitive.Portal> | ||
<PopoverPrimitive.Content | ||
ref={ref} | ||
align={align} | ||
sideOffset={sideOffset} | ||
className={cn( | ||
"z-50 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", | ||
className | ||
)} | ||
{...props} | ||
/> | ||
</PopoverPrimitive.Portal> | ||
)) | ||
PopoverContent.displayName = PopoverPrimitive.Content.displayName | ||
|
||
export { Popover, PopoverTrigger, PopoverContent } |
Oops, something went wrong.