Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ package-lock.json
.astro/
*.local
*.backup
*.draft.*
353 changes: 353 additions & 0 deletions src/components/blocks.content.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,353 @@
---
import { type ContentBlock } from "../types.d";

export interface Props {
content?: ContentBlock[];
className?: string;
}

const { content, className = "" } = Astro.props;
---

{
content && content.length > 0 && (
<div class={`blocks-content ${className}`}>
{content.map((block) => (
<>
{/* Paragraph blocks */}
{block.type === "paragraph" && (
<p class="mb-4 leading-relaxed">
{block.children.map((child) => (
<Fragment>
{child.code && (
<code class="bg-gray-100 text-gray-800 px-2 py-1 rounded text-sm font-mono">
{child.text}
</code>
)}
{child.type === "link" && !child.code && (
<a
href={child.url}
target={child.url?.startsWith("/") ? "_self" : "_blank"}
rel="noopener noreferrer"
class="text-blue-600 hover:text-pink-600 transition-colors underline"
>
{child.children?.[0]?.text || child.text}
</a>
)}
{!child.code &&
child.type !== "link" &&
(child.bold ? (
<strong>{child.text}</strong>
) : (
<span>{child.text}</span>
))}
</Fragment>
))}
</p>
)}

{/* Heading blocks */}
{block.type === "heading" && block.level === 1 && (
<h1 class="text-4xl font-bold my-6" style="color: var(--text);">
{block.children.map((child) => (
<Fragment>
{child.code ? (
<code class="bg-gray-100 text-gray-800 px-2 py-1 rounded text-sm font-mono">
{child.text}
</code>
) : child.type === "link" ? (
<a
href={child.url}
target={child.url?.startsWith("/") ? "_self" : "_blank"}
rel="noopener noreferrer"
class="text-blue-600 hover:text-pink-600 transition-colors underline"
>
{child.children?.[0]?.text || child.text}
</a>
) : (
<span>{child.text}</span>
)}
</Fragment>
))}
</h1>
)}

{block.type === "heading" && block.level === 2 && (
<h2 class="text-3xl font-bold my-5" style="color: var(--text);">
{block.children.map((child) => (
<Fragment>
{child.code ? (
<code class="bg-gray-100 text-gray-800 px-2 py-1 rounded text-sm font-mono">
{child.text}
</code>
) : child.type === "link" ? (
<a
href={child.url}
target={child.url?.startsWith("/") ? "_self" : "_blank"}
rel="noopener noreferrer"
class="text-blue-600 hover:text-pink-600 transition-colors underline"
>
{child.children?.[0]?.text || child.text}
</a>
) : (
<span>{child.text}</span>
)}
</Fragment>
))}
</h2>
)}

{block.type === "heading" && block.level === 3 && (
<h3 class="text-2xl font-bold my-4" style="color: var(--text);">
{block.children.map((child) => (
<Fragment>
{child.code ? (
<code class="bg-gray-100 text-gray-800 px-2 py-1 rounded text-sm font-mono">
{child.text}
</code>
) : child.type === "link" ? (
<a
href={child.url}
target={child.url?.startsWith("/") ? "_self" : "_blank"}
rel="noopener noreferrer"
class="text-blue-600 hover:text-pink-600 transition-colors underline"
>
{child.children?.[0]?.text || child.text}
</a>
) : (
<span>{child.text}</span>
)}
</Fragment>
))}
</h3>
)}

{block.type === "heading" && (block.level === 4 || !block.level) && (
<h4 class="text-xl font-bold my-3 ">
{block.children.map((child) => (
<Fragment>
{child.code ? (
<code class="bg-gray-100 text-gray-800 px-2 py-1 rounded text-sm font-mono">
{child.text}
</code>
) : child.type === "link" ? (
<a
href={child.url}
target={child.url?.startsWith("/") ? "_self" : "_blank"}
rel="noopener noreferrer"
class="text-blue-600 hover:text-pink-600 transition-colors underline"
>
{child.children?.[0]?.text || child.text}
</a>
) : (
<span>{child.text}</span>
)}
</Fragment>
))}
</h4>
)}

{block.type === "heading" && block.level === 5 && (
<h5 class="text-lg font-bold my-2" style="color: var(--text);">
{block.children.map((child) => (
<Fragment>
{child.code ? (
<code class="bg-gray-100 text-gray-800 px-2 py-1 rounded text-sm font-mono">
{child.text}
</code>
) : child.type === "link" ? (
<a
href={child.url}
target={child.url?.startsWith("/") ? "_self" : "_blank"}
rel="noopener noreferrer"
class="text-blue-600 hover:text-pink-600 transition-colors underline"
>
{child.children?.[0]?.text || child.text}
</a>
) : (
<span>{child.text}</span>
)}
</Fragment>
))}
</h5>
)}

{block.type === "heading" && block.level === 6 && (
<h6 class="text-base font-bold my-2" style="color: var(--text);">
{block.children.map((child) => (
<Fragment>
{child.code ? (
<code class="bg-gray-100 text-gray-800 px-2 py-1 rounded text-sm font-mono">
{child.text}
</code>
) : child.type === "link" ? (
<a
href={child.url}
target={child.url?.startsWith("/") ? "_self" : "_blank"}
rel="noopener noreferrer"
class="text-blue-600 hover:text-pink-600 transition-colors underline"
>
{child.children?.[0]?.text || child.text}
</a>
) : (
<span>{child.text}</span>
)}
</Fragment>
))}
</h6>
)}

{/* Code blocks */}
{block.type === "code" && (
<div class="my-6">
<pre class="bg-gray-900 text-green-400 p-4 rounded-lg overflow-x-auto">
<code class="font-mono text-sm leading-relaxed">
{block.children
.map((child) => (child.code ? child.text : ""))
.filter(Boolean)
.join("\n")}
</code>
</pre>
</div>
)}

{/* List blocks */}
{block.type === "list" && (
<ul class="list-disc pl-6 my-4 space-y-2">
{block.children
.filter((child) => child.type === "list-item")
.map((child) => (
<li class="leading-relaxed">
{child.children?.map((grandChild: any) => (
<Fragment>
{grandChild.type === "link" ? (
<a
href={grandChild.url}
target={
(grandChild.url || "").startsWith("/")
? "_self"
: "_blank"
}
rel="noopener noreferrer"
class="text-blue-600 hover:text-pink-600 transition-colors underline"
>
{grandChild.text}
</a>
) : (
<span>{grandChild.text}</span>
)}
</Fragment>
))}
</li>
))}
</ul>
)}

{/* Quote blocks */}
{block.type === "quote" && (
<blockquote class="border-l-4 border-blue-500 pl-6 my-6 italic text-gray-700 bg-gray-50 py-4 rounded-r-lg">
<div class="leading-relaxed">
{block.children.map((child) => (
<Fragment>
{child.code ? (
<code class="bg-gray-200 text-gray-800 px-2 py-1 rounded text-sm font-mono not-italic">
{child.text}
</code>
) : child.type === "link" ? (
<a
href={child.url}
target={child.url?.startsWith("/") ? "_self" : "_blank"}
rel="noopener noreferrer"
class="text-blue-600 hover:text-pink-600 transition-colors underline"
>
{child.children?.[0]?.text || child.text}
</a>
) : (
<span>{child.text}</span>
)}
</Fragment>
))}
</div>
</blockquote>
)}

{/* Image blocks */}
{block.type === "image" && block.image?.url && (
<figure class="my-8">
<div class="rounded-lg overflow-hidden shadow-lg">
<img
src={block.image.url}
alt={
(block.image as any).alternativeText ||
(block.image as any).name ||
""
}
class="w-full h-auto object-cover"
width={block.image.width}
height={block.image.height}
loading="lazy"
/>
</div>
{((block.image as any).caption ||
(block.image as any).alternativeText ||
(block.image as any).name) && (
<figcaption class="text-center text-sm text-muted mt-3 italic">
{(block.image as any).caption ||
(block.image as any).alternativeText ||
(block.image as any).name}
</figcaption>
)}
</figure>
)}
</>
))}
</div>
)
}

<style>
.blocks-content {
max-width: none;
line-height: 1.7;
}

.blocks-content p {
margin-bottom: 1rem;
}

.blocks-content pre {
font-size: 0.875rem;
line-height: 1.6;
}

.blocks-content code {
font-family: "Fira Code", "Consolas", "Monaco", "Courier New", monospace;
}

.blocks-content img {
transition: transform 0.3s ease;
}

.blocks-content img:hover {
transform: scale(1.02);
}

.blocks-content a {
text-decoration-thickness: 2px;
text-underline-offset: 2px;
}

.blocks-content blockquote {
position: relative;
}

.blocks-content blockquote::before {
content: '"';
position: absolute;
left: -0.5rem;
top: -0.5rem;
font-size: 3rem;
color: #3b82f6;
opacity: 0.3;
}
</style>
Loading