Skip to content

Commit 829788b

Browse files
feat: added markdown (#224)
* feat: added markdown * fix: resolved issue * fix: merge changes * fix: working changes merge conflicts * feat: markdown implemented
1 parent 2251354 commit 829788b

File tree

12 files changed

+1376
-531
lines changed

12 files changed

+1376
-531
lines changed

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
"eslint": "8.47.0",
4545
"eslint-config-next": "13.4.13",
4646
"framer-motion": "^10.18.0",
47+
"html-react-parser": "^5.1.1",
48+
"lucide-react": "^0.316.0",
4749
"next": "13.4.19",
4850
"next-themes": "^0.2.1",
4951
"nookies": "^2.5.2",
@@ -62,6 +64,10 @@
6264
"devDependencies": {
6365
"@commitlint/cli": "^18.4.4",
6466
"@commitlint/config-conventional": "^18.4.4",
67+
"@tailwindcss/typography": "^0.5.10",
68+
"@tiptap/pm": "^2.1.16",
69+
"@tiptap/react": "^2.1.16",
70+
"@tiptap/starter-kit": "^2.1.16",
6571
"encoding": "^0.1.13",
6672
"husky": "^8.0.3",
6773
"lint-staged": "^15.2.0",
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { Editor } from "@tiptap/react";
2+
import {
3+
Bold,
4+
Code,
5+
Heading1,
6+
Heading2,
7+
Heading3,
8+
Heading4,
9+
Heading5,
10+
Heading6,
11+
Italic,
12+
List,
13+
ListOrdered,
14+
Quote,
15+
Redo,
16+
Strikethrough,
17+
Undo,
18+
} from "lucide-react";
19+
import { Codepen } from "react-feather";
20+
21+
const EditorMenubar = ({ editor }: { editor: Editor }) => {
22+
const MenuBarFunctions = [
23+
{
24+
title: "bold",
25+
onFunction: () => editor.chain().focus().toggleBold().run(),
26+
offFunction: () => !editor.can().chain().focus().toggleBold().run(),
27+
icon: Bold,
28+
},
29+
{
30+
title: "italic",
31+
onFunction: () => editor.chain().focus().toggleItalic().run(),
32+
offFunction: () => !editor.can().chain().focus().toggleItalic().run(),
33+
icon: Italic,
34+
},
35+
{
36+
title: "strike",
37+
onFunction: () => editor.chain().focus().toggleStrike().run(),
38+
offFunction: () => !editor.can().chain().focus().toggleStrike().run(),
39+
icon: Strikethrough,
40+
},
41+
{
42+
title: "code",
43+
onFunction: () => editor.chain().focus().toggleCode().run(),
44+
offFunction: () => !editor.can().chain().focus().toggleCode().run(),
45+
icon: Code,
46+
},
47+
{
48+
title: `heading`,
49+
attribute: { level: 1 },
50+
onFunction: () => editor.chain().focus().toggleHeading({ level: 1 }).run(),
51+
offFunction: () => !editor.can().chain().focus().toggleHeading({ level: 1 }).run(),
52+
icon: Heading1,
53+
},
54+
{
55+
title: "heading",
56+
attribute: { level: 2 } || null,
57+
onFunction: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
58+
offFunction: () => !editor.can().chain().focus().toggleHeading({ level: 2 }).run(),
59+
icon: Heading2,
60+
},
61+
{
62+
title: "heading",
63+
attribute: { level: 3 } || null,
64+
onFunction: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
65+
offFunction: () => !editor.can().chain().focus().toggleHeading({ level: 3 }).run(),
66+
icon: Heading3,
67+
},
68+
{
69+
title: "heading",
70+
attribute: { level: 4 } || null,
71+
onFunction: () => editor.chain().focus().toggleHeading({ level: 4 }).run(),
72+
offFunction: () => !editor.can().chain().focus().toggleHeading({ level: 4 }).run(),
73+
icon: Heading4,
74+
},
75+
{
76+
title: "heading",
77+
attribute: { level: 5 } || null,
78+
onFunction: () => editor.chain().focus().toggleHeading({ level: 5 }).run(),
79+
offFunction: () => !editor.can().chain().focus().toggleHeading({ level: 5 }).run(),
80+
icon: Heading5,
81+
},
82+
{
83+
title: "heading",
84+
attribute: { level: 6 } || null,
85+
onFunction: () => editor.chain().focus().toggleHeading({ level: 6 }).run(),
86+
offFunction: () => !editor.can().chain().focus().toggleHeading({ level: 6 }).run(),
87+
icon: Heading6,
88+
},
89+
{
90+
title: "bulletList",
91+
onFunction: () => editor.chain().focus().toggleBulletList().run(),
92+
offFunction: () => !editor.can().chain().focus().toggleBulletList().run(),
93+
icon: List,
94+
},
95+
{
96+
title: "orderedList",
97+
onFunction: () => editor.chain().focus().toggleOrderedList().run(),
98+
offFunction: () => !editor.can().chain().focus().toggleOrderedList().run(),
99+
icon: ListOrdered,
100+
},
101+
{
102+
title: "codeBlock",
103+
onFunction: () => editor.chain().focus().toggleCodeBlock().run(),
104+
offFunction: () => !editor.can().chain().focus().toggleCodeBlock().run(),
105+
icon: Codepen,
106+
},
107+
{
108+
title: "blockquote",
109+
onFunction: () => editor.chain().focus().toggleBlockquote().run(),
110+
offFunction: () => !editor.can().chain().focus().toggleBlockquote().run(),
111+
icon: Quote,
112+
},
113+
{
114+
title: "undo",
115+
onFunction: () => editor.chain().focus().undo().run(),
116+
offFunction: () => !editor.can().chain().focus().undo().run(),
117+
icon: Undo,
118+
},
119+
{
120+
title: "redo",
121+
onFunction: () => editor.chain().focus().redo().run(),
122+
offFunction: () => !editor.can().chain().focus().redo().run(),
123+
icon: Redo,
124+
},
125+
];
126+
127+
return (
128+
<>
129+
<div className="flex flex-wrap gap-2">
130+
{MenuBarFunctions.length > 0 &&
131+
MenuBarFunctions.map((menu) => (
132+
<button
133+
key={menu.title}
134+
role="button"
135+
onClick={(e) => {
136+
e.preventDefault();
137+
menu.onFunction();
138+
console.log("cick");
139+
}}
140+
disabled={!menu.offFunction}
141+
className={
142+
editor.isActive(menu.title, menu.attribute)
143+
? "is-active rounded-sm p-0.5 hover:bg-white/80 hover:text-black"
144+
: "rounded-sm p-0.5 hover:bg-white/80 hover:text-black"
145+
}
146+
>
147+
<menu.icon className="w-4 h-4" />
148+
</button>
149+
))}
150+
</div>
151+
</>
152+
);
153+
};
154+
155+
export default EditorMenubar;

src/components/Editor/Markdown.tsx

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
"use client";
2+
import React, { useEffect } from "react";
3+
import { BubbleMenu, EditorContent, FloatingMenu, useEditor } from "@tiptap/react";
4+
import { HardBreak } from "@tiptap/extension-hard-break";
5+
import StarterKit from "@tiptap/starter-kit";
6+
import EditorMenubar from "./EditorMenuBar";
7+
import { Codepen } from "react-feather";
8+
import {
9+
BoldIcon,
10+
Italic,
11+
Code,
12+
Heading1,
13+
Heading2,
14+
List,
15+
ListOrdered,
16+
StrikethroughIcon,
17+
} from "lucide-react";
18+
19+
interface EditorProps {
20+
editorState: string;
21+
setEditorState: (params: string) => void;
22+
}
23+
const Markdown = ({ editorState, setEditorState }: EditorProps) => {
24+
const CustomHardBreak = HardBreak.extend({
25+
addKeyboardShortcuts() {
26+
return {
27+
"Mod-Enter": () => true,
28+
};
29+
},
30+
});
31+
const editor = useEditor({
32+
extensions: [StarterKit, CustomHardBreak],
33+
content: editorState,
34+
onUpdate: ({ editor }) => {
35+
setEditorState(editor.getHTML());
36+
},
37+
});
38+
useEffect(() => {
39+
if (editorState === null || editorState === "") {
40+
editor?.commands.setContent("");
41+
}
42+
}, [editorState]);
43+
44+
const BubbleMenuFunctions = [
45+
{
46+
title: "bold",
47+
onFunction: () => editor?.chain().focus().toggleBold().run(),
48+
icon: <BoldIcon size={16} className="font-bold " />,
49+
},
50+
{
51+
title: "italic",
52+
onFunction: () => editor?.chain().focus().toggleItalic().run(),
53+
icon: <Italic size={16} className="font-bold " />,
54+
},
55+
{
56+
title: "strike",
57+
onFunction: () => editor?.chain().focus().toggleStrike().run(),
58+
icon: <StrikethroughIcon size={18} className="font-bold " />,
59+
},
60+
{
61+
title: "code",
62+
onFunction: () => editor?.chain().focus().toggleCode().run(),
63+
icon: <Code size={18} className="font-bold " />,
64+
},
65+
{
66+
title: "codeBlck",
67+
onFunction: () => editor?.chain().focus().toggleCodeBlock().run(),
68+
icon: <Codepen size={16} className="font-bold " />,
69+
},
70+
];
71+
72+
const FloatingMenuFunctions = [
73+
{
74+
title: `heading`,
75+
attribute: { level: 1 },
76+
onFunction: () => editor?.chain().focus().toggleHeading({ level: 1 }).run(),
77+
icon: <Heading1 className="w-4 h-4" />,
78+
},
79+
{
80+
title: `heading`,
81+
attribute: { level: 2 },
82+
onFunction: () => editor?.chain().focus().toggleHeading({ level: 2 }).run(),
83+
icon: <Heading2 className="w-4 h-4" />,
84+
},
85+
{
86+
title: `codeBlock`,
87+
attribute: { level: 1 },
88+
onFunction: () => editor?.chain().focus().toggleCodeBlock().run(),
89+
icon: <Codepen className="w-4 h-4" />,
90+
},
91+
{
92+
title: `bulletList`,
93+
onFunction: () => editor?.chain().focus().toggleBulletList().run(),
94+
icon: <List className="w-4 h-4" />,
95+
},
96+
{
97+
title: `orderedList`,
98+
onFunction: () => editor?.chain().focus().toggleOrderedList().run(),
99+
icon: <ListOrdered className="w-4 h-4" />,
100+
},
101+
];
102+
103+
return (
104+
<>
105+
<main className="dark:bg-secondary-light outline-none focus:ring rounded-lg p-3 text-white dark:text-white placholder:text-gray-400 text-lg w-full mb-2 max-w-full ">
106+
{editor && (
107+
<BubbleMenu className="bubble-menu" tippyOptions={{ duration: 100 }} editor={editor}>
108+
<div className="flex gap-1 bg-white px-4 py-1 text-black rounded-sm">
109+
{BubbleMenuFunctions.length > 0 &&
110+
BubbleMenuFunctions.map((func) => (
111+
<button
112+
key={func.title}
113+
role="button"
114+
onClick={(e) => {
115+
e.preventDefault();
116+
func.onFunction();
117+
}}
118+
className={
119+
editor.isActive(func.title)
120+
? "is-active rounded-sm p-0.5"
121+
: " rounded-sm p-0.5"
122+
}
123+
>
124+
{func.icon}
125+
</button>
126+
))}
127+
</div>
128+
</BubbleMenu>
129+
)}
130+
131+
{editor && (
132+
<FloatingMenu className="floating-menu" tippyOptions={{ duration: 100 }} editor={editor}>
133+
<div className="flex gap-1 bg-white px-4 py-1 text-black rounded-sm">
134+
{FloatingMenuFunctions.length > 0 &&
135+
FloatingMenuFunctions.map((func) => (
136+
<button
137+
role="button"
138+
onClick={(e) => {
139+
e.preventDefault();
140+
func.onFunction();
141+
}}
142+
className={
143+
editor.isActive(func.title, func.attribute)
144+
? "is-active rounded-sm p-0.5"
145+
: "rounded-sm p-0.5"
146+
}
147+
>
148+
{func.icon}
149+
</button>
150+
))}
151+
</div>
152+
</FloatingMenu>
153+
)}
154+
155+
{editor && <EditorMenubar editor={editor} />}
156+
157+
<EditorContent className="prose dark:prose-invert mt-4" editor={editor} />
158+
</main>
159+
</>
160+
);
161+
};
162+
export default Markdown;

0 commit comments

Comments
 (0)