1
+ 'use client'
2
+
3
+ import { useState , useEffect } from 'react'
4
+ import { Button } from "@/components/ui/button"
5
+ import { Input } from "@/components/ui/input"
6
+ import { Textarea } from "@/components/ui/textarea"
7
+ import { Label } from "@/components/ui/label"
8
+ import { Card , CardContent , CardFooter , CardHeader , CardTitle } from "@/components/ui/card"
9
+ import { Tabs , TabsContent , TabsList , TabsTrigger } from "@/components/ui/tabs"
10
+ import { Copy , Check } from 'lucide-react'
11
+ import { useToast } from "../hooks/use-toast"
12
+
13
+ export default function BlogMarkdownGenerator ( ) {
14
+ const [ mounted , setMounted ] = useState ( false )
15
+ const [ blogData , setBlogData ] = useState ( {
16
+ title : '' ,
17
+ author : '' ,
18
+ coverImage : '' ,
19
+ introduction : '' ,
20
+ sections : [ { title : '' , content : '' } ] ,
21
+ conclusion : '' ,
22
+ authorBio : '' ,
23
+ twitterHandle : '' ,
24
+ linkedinProfile : '' ,
25
+ githubUsername : '' ,
26
+ } )
27
+ const [ generatedMarkdown , setGeneratedMarkdown ] = useState ( '' )
28
+ const [ isCopied , setIsCopied ] = useState ( false )
29
+ const { toast } = useToast ( )
30
+
31
+ useEffect ( ( ) => {
32
+ setMounted ( true )
33
+ } , [ ] )
34
+
35
+ if ( ! mounted ) {
36
+ return (
37
+ < Card className = "w-full max-w-4xl mx-auto" >
38
+ < CardHeader >
39
+ < CardTitle > Loading...</ CardTitle >
40
+ </ CardHeader >
41
+ < CardContent >
42
+ < div className = "h-96 flex items-center justify-center" >
43
+ < div className = "animate-pulse" > Loading editor...</ div >
44
+ </ div >
45
+ </ CardContent >
46
+ </ Card >
47
+ )
48
+ }
49
+
50
+ const handleInputChange = ( e : React . ChangeEvent < HTMLInputElement | HTMLTextAreaElement > ) => {
51
+ const { name, value } = e . target
52
+ setBlogData ( prev => ( { ...prev , [ name ] : value } ) )
53
+ }
54
+
55
+ const handleSectionChange = ( index : number , field : 'title' | 'content' , value : string ) => {
56
+ setBlogData ( prev => ( {
57
+ ...prev ,
58
+ sections : prev . sections . map ( ( section , i ) =>
59
+ i === index ? { ...section , [ field ] : value } : section
60
+ )
61
+ } ) )
62
+ }
63
+
64
+ const addSection = ( ) => {
65
+ setBlogData ( prev => ( {
66
+ ...prev ,
67
+ sections : [ ...prev . sections , { title : '' , content : '' } ]
68
+ } ) )
69
+ }
70
+
71
+ const generateMarkdown = ( ) => {
72
+ let markdown = `# ${ blogData . title } \n\n`
73
+ markdown += `*By [${ blogData . author } ](https://your-website.com)*\n\n`
74
+ markdown += blogData . coverImage ? `\n\n` : ''
75
+
76
+ markdown += `## Table of Contents\n`
77
+ markdown += `- [Introduction](#introduction)\n`
78
+ blogData . sections . forEach ( ( section , index ) => {
79
+ if ( section . title ) {
80
+ markdown += `- [${ section . title } ](#section-${ index + 1 } -${ section . title . toLowerCase ( ) . replace ( / \s + / g, '-' ) } )\n`
81
+ }
82
+ } )
83
+ markdown += `- [Conclusion](#conclusion)\n\n`
84
+
85
+ markdown += `## Introduction\n\n${ blogData . introduction } \n\n`
86
+
87
+ blogData . sections . forEach ( ( section , index ) => {
88
+ if ( section . title ) {
89
+ markdown += `## Section ${ index + 1 } : ${ section . title } \n\n${ section . content } \n\n`
90
+ }
91
+ } )
92
+
93
+ markdown += `## Conclusion\n\n${ blogData . conclusion } \n\n---\n\n`
94
+
95
+ if ( blogData . author || blogData . authorBio ) {
96
+ markdown += `### About the Author\n\n`
97
+ markdown += `**${ blogData . author } ** ${ blogData . authorBio } \n\n`
98
+ }
99
+
100
+ if ( blogData . twitterHandle || blogData . linkedinProfile || blogData . githubUsername ) {
101
+ markdown += `Connect with me:\n`
102
+ if ( blogData . twitterHandle ) markdown += `- [Twitter](https://twitter.com/${ blogData . twitterHandle } )\n`
103
+ if ( blogData . linkedinProfile ) markdown += `- [LinkedIn](${ blogData . linkedinProfile } )\n`
104
+ if ( blogData . githubUsername ) markdown += `- [GitHub](https://github.com/${ blogData . githubUsername } )\n`
105
+ markdown += '\n'
106
+ }
107
+
108
+ markdown += `---\n\n`
109
+ if ( blogData . twitterHandle ) {
110
+ markdown += `*Did you find this blog post helpful? [Share it on Twitter](https://twitter.com/intent/tweet?text=Check%20out%20this%20amazing%20blog%20post%20by%20@${ blogData . twitterHandle } :%20https://gist.github.com/your-gist-url)*\n\n`
111
+ }
112
+ markdown += `*For more content like this, [subscribe to my newsletter](https://your-newsletter-url.com).*`
113
+
114
+ setGeneratedMarkdown ( markdown )
115
+ }
116
+
117
+ const copyToClipboard = async ( ) => {
118
+ try {
119
+ await navigator . clipboard . writeText ( generatedMarkdown )
120
+ setIsCopied ( true )
121
+ toast ( {
122
+ title : "Copied!" ,
123
+ description : "Markdown has been copied to clipboard." ,
124
+ } )
125
+ setTimeout ( ( ) => setIsCopied ( false ) , 2000 )
126
+ } catch ( err ) {
127
+ console . error ( 'Failed to copy text: ' , err )
128
+ toast ( {
129
+ title : "Error" ,
130
+ description : "Failed to copy. Please try again." ,
131
+ variant : "destructive" ,
132
+ } )
133
+ }
134
+ }
135
+
136
+ return (
137
+ < Card className = "w-full max-w-4xl mx-auto" >
138
+ < CardHeader >
139
+ < CardTitle > Blog Markdown Generator</ CardTitle >
140
+ </ CardHeader >
141
+ < CardContent >
142
+ < Tabs defaultValue = "input" className = "space-y-4" >
143
+ < TabsList className = "grid w-full grid-cols-2" >
144
+ < TabsTrigger value = "input" > Input</ TabsTrigger >
145
+ < TabsTrigger value = "preview" > Preview</ TabsTrigger >
146
+ </ TabsList >
147
+ < TabsContent value = "input" >
148
+ < form className = "space-y-4" >
149
+ < div >
150
+ < Label htmlFor = "title" > Blog Title</ Label >
151
+ < Input id = "title" name = "title" value = { blogData . title } onChange = { handleInputChange } />
152
+ </ div >
153
+ < div >
154
+ < Label htmlFor = "author" > Author Name</ Label >
155
+ < Input id = "author" name = "author" value = { blogData . author } onChange = { handleInputChange } />
156
+ </ div >
157
+ < div >
158
+ < Label htmlFor = "coverImage" > Cover Image URL</ Label >
159
+ < Input id = "coverImage" name = "coverImage" value = { blogData . coverImage } onChange = { handleInputChange } />
160
+ </ div >
161
+ < div >
162
+ < Label htmlFor = "introduction" > Introduction</ Label >
163
+ < Textarea id = "introduction" name = "introduction" value = { blogData . introduction } onChange = { handleInputChange } />
164
+ </ div >
165
+ { blogData . sections . map ( ( section , index ) => (
166
+ < div key = { index } className = "space-y-2" >
167
+ < Label htmlFor = { `section-${ index } -title` } > Section { index + 1 } Title</ Label >
168
+ < Input
169
+ id = { `section-${ index } -title` }
170
+ value = { section . title }
171
+ onChange = { ( e ) => handleSectionChange ( index , 'title' , e . target . value ) }
172
+ />
173
+ < Label htmlFor = { `section-${ index } -content` } > Section { index + 1 } Content</ Label >
174
+ < Textarea
175
+ id = { `section-${ index } -content` }
176
+ value = { section . content }
177
+ onChange = { ( e ) => handleSectionChange ( index , 'content' , e . target . value ) }
178
+ />
179
+ </ div >
180
+ ) ) }
181
+ < Button type = "button" onClick = { addSection } > Add Section</ Button >
182
+ < div >
183
+ < Label htmlFor = "conclusion" > Conclusion</ Label >
184
+ < Textarea id = "conclusion" name = "conclusion" value = { blogData . conclusion } onChange = { handleInputChange } />
185
+ </ div >
186
+ < div >
187
+ < Label htmlFor = "authorBio" > Author Bio</ Label >
188
+ < Textarea id = "authorBio" name = "authorBio" value = { blogData . authorBio } onChange = { handleInputChange } />
189
+ </ div >
190
+ < div >
191
+ < Label htmlFor = "twitterHandle" > Twitter Handle</ Label >
192
+ < Input id = "twitterHandle" name = "twitterHandle" value = { blogData . twitterHandle } onChange = { handleInputChange } />
193
+ </ div >
194
+ < div >
195
+ < Label htmlFor = "linkedinProfile" > LinkedIn Profile URL</ Label >
196
+ < Input id = "linkedinProfile" name = "linkedinProfile" value = { blogData . linkedinProfile } onChange = { handleInputChange } />
197
+ </ div >
198
+ < div >
199
+ < Label htmlFor = "githubUsername" > GitHub Username</ Label >
200
+ < Input id = "githubUsername" name = "githubUsername" value = { blogData . githubUsername } onChange = { handleInputChange } />
201
+ </ div >
202
+ </ form >
203
+ </ TabsContent >
204
+ < TabsContent value = "preview" >
205
+ < div className = "bg-muted p-4 rounded-md relative" >
206
+ < pre className = "whitespace-pre-wrap" > { generatedMarkdown } </ pre >
207
+ { generatedMarkdown && (
208
+ < Button
209
+ className = "absolute top-2 right-2"
210
+ size = "icon"
211
+ onClick = { copyToClipboard }
212
+ aria-label = "Copy to clipboard"
213
+ >
214
+ { isCopied ? < Check className = "h-4 w-4" /> : < Copy className = "h-4 w-4" /> }
215
+ </ Button >
216
+ ) }
217
+ </ div >
218
+ </ TabsContent >
219
+ </ Tabs >
220
+ </ CardContent >
221
+ < CardFooter className = "flex justify-between" >
222
+ < Button onClick = { generateMarkdown } > Generate Markdown</ Button >
223
+ < Button
224
+ onClick = { copyToClipboard }
225
+ disabled = { ! generatedMarkdown }
226
+ className = "ml-2"
227
+ >
228
+ { isCopied ? 'Copied!' : 'Copy Markdown' }
229
+ </ Button >
230
+ </ CardFooter >
231
+ </ Card >
232
+ )
233
+ }
0 commit comments