Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.

Commit 3ae8cc8

Browse files
Merge pull request #184 from Cloud-Code-AI/159-add-more-block-types
159 add more block types
2 parents 5112db4 + 35e61ea commit 3ae8cc8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3142
-503
lines changed

packages/akiradocs/src/app/[locale]/[type]/[...slug]/page.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,17 +134,19 @@ export default async function ContentPage({ params }: Props) {
134134
</div>
135135
<MainTitle>{t(post.title)}</MainTitle>
136136
<SubTitle>{t(post.description)}</SubTitle>
137-
{post.blocks.map((block) => (
138-
block.content !== post.title && (
137+
<div className="mt-6">
138+
{post.blocks.map((block) => (
139+
block.content !== post.title && (
139140
<BlockRenderer
140141
key={block.id}
141142
block={{
142143
...block,
143144
content: block.content
144-
}}
145-
/>
146-
)
147-
))}
145+
}}
146+
/>
147+
)
148+
))}
149+
</div>
148150
<PageNavigation prev={prev} next={next} locale={locale} />
149151
</div>
150152
</PostContainer>

packages/akiradocs/src/components/blocks/ArticleHeaders.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ export function ArticleHeaders({ title, setTitle, subtitle, setSubtitle, showPre
2626
value={title}
2727
onChange={(e) => setTitle(e.target.value)}
2828
placeholder="Article title"
29-
className="text-4xl font-bold mb-4 border-none px-0"
29+
className="text-4xl font-bold mb-4 border-none px-0 max-w-4xl break-words"
3030
/>
3131
<Input
3232
value={subtitle}
3333
onChange={(e) => setSubtitle(e.target.value)}
3434
placeholder="Add a subtitle"
35-
className="text-xl mb-4 border-none px-0"
35+
className="text-xl mb-4 border-none px-0 max-w-4xl break-words"
3636
/>
3737
</>
3838
)
Lines changed: 195 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,198 @@
1-
import React from 'react';
2-
import { cn } from "@/lib/utils";
3-
4-
interface AudioProps {
5-
id?: string;
6-
src: string;
7-
caption?: string;
8-
align?: 'left' | 'center' | 'right';
9-
styles?: {
10-
bold?: boolean;
11-
italic?: boolean;
12-
underline?: boolean;
13-
};
1+
"use client"
2+
3+
import { cn } from '@/lib/utils'
4+
import { Button } from '@/components/ui/button'
5+
import { Music, Pause, Play } from 'lucide-react'
6+
import { useState, useRef, useEffect } from 'react'
7+
8+
interface AudioBlockProps {
9+
content: string
10+
id: string
11+
onUpdate?: (content: string) => void
12+
isEditing?: boolean
13+
metadata?: {
14+
caption?: string
15+
alignment?: 'left' | 'center' | 'right'
16+
styles?: {
17+
bold?: boolean
18+
italic?: boolean
19+
underline?: boolean
20+
}
21+
}
1422
}
1523

16-
export function Audio({ id, src, caption, align = 'left', styles }: AudioProps) {
17-
const alignClass = align === 'center' ? 'mx-auto' : align === 'right' ? 'ml-auto' : '';
18-
return (
19-
<div id={id} className={`mb-6 py-1 ${alignClass}`}>
20-
<audio controls className="w-full bg-background border border-border rounded-md">
21-
<source src={src} type="audio/mpeg" />
22-
<span className="text-muted-foreground">Your browser does not support the audio element.</span>
23-
</audio>
24-
{caption && <p className={cn(
25-
"mt-2 text-sm text-muted-foreground",
26-
styles?.bold && 'font-bold',
27-
styles?.italic && 'italic',
28-
styles?.underline && 'underline'
29-
)}>{caption}</p>}
30-
</div>
31-
);
24+
interface AudioBlockContent {
25+
url: string
26+
caption?: string
27+
alignment?: 'left' | 'center' | 'right'
3228
}
29+
30+
export function AudioBlock({ content, id, onUpdate, isEditing, metadata }: AudioBlockProps) {
31+
const [isHovered, setIsHovered] = useState(false)
32+
const [isPlaying, setIsPlaying] = useState(false)
33+
const audioRef = useRef<HTMLAudioElement>(null)
34+
35+
const handlePlayPause = (e: React.MouseEvent) => {
36+
e.stopPropagation()
37+
if (audioRef.current) {
38+
if (isPlaying) {
39+
audioRef.current.pause()
40+
} else {
41+
audioRef.current.play()
42+
}
43+
setIsPlaying(!isPlaying)
44+
}
45+
}
46+
47+
// Add event listeners to sync the isPlaying state with audio events
48+
useEffect(() => {
49+
const audio = audioRef.current
50+
if (!audio) return
51+
52+
const handlePlay = () => setIsPlaying(true)
53+
const handlePause = () => setIsPlaying(false)
54+
const handleEnded = () => setIsPlaying(false)
55+
56+
audio.addEventListener('play', handlePlay)
57+
audio.addEventListener('pause', handlePause)
58+
audio.addEventListener('ended', handleEnded)
59+
60+
return () => {
61+
audio.removeEventListener('play', handlePlay)
62+
audio.removeEventListener('pause', handlePause)
63+
audio.removeEventListener('ended', handleEnded)
64+
}
65+
}, [])
66+
67+
const parseAudioContent = (content: string): AudioBlockContent => {
68+
try {
69+
return typeof content === 'string' ? JSON.parse(content) : content
70+
} catch {
71+
return {
72+
url: content,
73+
caption: metadata?.caption || '',
74+
alignment: metadata?.alignment || 'center'
75+
}
76+
}
77+
}
78+
79+
const UploadButton = () => (
80+
<div className="text-center">
81+
<input
82+
type="file"
83+
id={`audio-upload-${id}`}
84+
className="hidden"
85+
accept="audio/*"
86+
/>
87+
<div className="flex flex-col items-center gap-2">
88+
<Button
89+
variant="ghost"
90+
className="w-full justify-start h-auto py-6 px-8 hover:bg-accent flex flex-col items-center gap-2"
91+
onClick={(e) => {
92+
e.stopPropagation()
93+
document.getElementById(`audio-upload-${id}`)?.click()
94+
}}
95+
>
96+
<Music className="h-8 w-8 text-muted-foreground" />
97+
<span className="font-medium">Upload Audio</span>
98+
<p className="text-sm text-muted-foreground">
99+
Click to upload or drag and drop
100+
</p>
101+
</Button>
102+
</div>
103+
</div>
104+
)
105+
106+
if (!content && isEditing) {
107+
return (
108+
<div className="flex items-center justify-center border-2 border-dashed border-muted-foreground/25 rounded-lg my-4">
109+
<UploadButton />
110+
</div>
111+
)
112+
}
113+
114+
const audioContent = parseAudioContent(content)
115+
const alignment = audioContent.alignment || metadata?.alignment || 'center'
116+
117+
return (
118+
<figure
119+
className={cn(
120+
"py-1 mb-6 relative group",
121+
"flex flex-col",
122+
alignment === 'left' && "items-start",
123+
alignment === 'center' && "items-center",
124+
alignment === 'right' && "items-end",
125+
)}
126+
onMouseEnter={() => setIsHovered(true)}
127+
onMouseLeave={() => setIsHovered(false)}
128+
>
129+
<div className="relative w-full max-w-[500px]">
130+
<div className="bg-muted rounded-lg p-4 flex items-center gap-4">
131+
<div className="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center">
132+
<Music className="h-6 w-6 text-primary" />
133+
</div>
134+
<audio
135+
ref={audioRef}
136+
src={audioContent.url}
137+
controls
138+
className="w-full"
139+
/>
140+
</div>
141+
{isEditing && isHovered && (
142+
<div
143+
className="absolute inset-0 bg-black/50 flex items-center justify-center gap-2 rounded-lg transition-opacity"
144+
onClick={(e) => e.stopPropagation()}
145+
>
146+
<input
147+
type="file"
148+
id={`audio-change-${id}`}
149+
className="hidden"
150+
accept="audio/*"
151+
/>
152+
<Button
153+
variant="secondary"
154+
className="gap-2"
155+
onClick={(e) => {
156+
e.stopPropagation()
157+
document.getElementById(`audio-change-${id}`)?.click()
158+
}}
159+
>
160+
<Music className="h-4 w-4" />
161+
Change Audio
162+
</Button>
163+
<Button
164+
variant="secondary"
165+
className="gap-2"
166+
onClick={handlePlayPause}
167+
>
168+
{isPlaying ? (
169+
<>
170+
<Pause className="h-4 w-4" />
171+
Pause
172+
</>
173+
) : (
174+
<>
175+
<Play className="h-4 w-4" />
176+
Play
177+
</>
178+
)}
179+
</Button>
180+
</div>
181+
)}
182+
</div>
183+
{audioContent.caption && (
184+
<figcaption className={cn(
185+
"mt-2 text-sm text-muted-foreground italic",
186+
alignment === 'left' && "text-left",
187+
alignment === 'center' && "text-center",
188+
alignment === 'right' && "text-right",
189+
metadata?.styles?.bold && "font-bold",
190+
metadata?.styles?.italic && "italic",
191+
metadata?.styles?.underline && "underline"
192+
)}>
193+
{audioContent.caption}
194+
</figcaption>
195+
)}
196+
</figure>
197+
)
198+
}

packages/akiradocs/src/components/blocks/BlockquoteBlock.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function Blockquote({ id, children, align = 'left', styles, isEditing, on
4040

4141
if (isEditing) {
4242
return (
43-
<div className={cn("border-l-4 border-border pl-4 text-muted-foreground", alignClass)}>
43+
<div className={cn("border-l-4 border-border pl-4 py-1 mb-6 text-muted-foreground", alignClass)}>
4444
<textarea
4545
ref={textareaRef}
4646
value={children as string}
@@ -63,7 +63,7 @@ export function Blockquote({ id, children, align = 'left', styles, isEditing, on
6363
<blockquote
6464
id={id}
6565
className={cn(
66-
"border-l-4 border-border pl-4 text-muted-foreground",
66+
"border-l-4 border-border pl-4 py-1 mb-6 text-muted-foreground",
6767
alignClass,
6868
commonStyles
6969
)}

packages/akiradocs/src/components/blocks/CalloutBlock.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export function Callout({ id, type, title, children, align = 'left', styles, isE
4747

4848
return (
4949
<Alert className={cn(
50-
'flex flex-col sm:flex-row items-start gap-4 p-4 my-4 rounded-lg border-2',
50+
'flex flex-col sm:flex-row items-start gap-4 p-4 mb-7 mt-1 rounded-lg border-2',
5151
className,
5252
{
5353
'text-left': align === 'left',

0 commit comments

Comments
 (0)