mirror of
https://github.com/anotherhadi/blog.git
synced 2026-04-04 04:12:11 +02:00
178 lines
5.3 KiB
Plaintext
178 lines
5.3 KiB
Plaintext
---
|
|
import Layout from "./Layout.astro";
|
|
import { Image } from "astro:assets";
|
|
import TagBadge from "../components/TagBadge.astro";
|
|
import BackToTop from "../components/BackToTop.astro";
|
|
import { ChevronLeft } from "@lucide/astro";
|
|
import { parse } from "node-html-parser";
|
|
|
|
interface Props {
|
|
title: string;
|
|
description: string;
|
|
publishDate: Date;
|
|
updatedDate?: Date;
|
|
image: any;
|
|
tags?: string[];
|
|
}
|
|
|
|
const { title, description, publishDate, updatedDate, image, tags } =
|
|
Astro.props;
|
|
|
|
function formatDate(date: Date) {
|
|
return date.toLocaleDateString("en-US", {
|
|
month: "long",
|
|
day: "numeric",
|
|
year: "numeric",
|
|
});
|
|
}
|
|
|
|
// Calculate reading time (rough estimate based on word count)
|
|
const content = await Astro.slots.render("default");
|
|
const wordCount = content.split(/\s+/).length;
|
|
const readingTime = Math.ceil(wordCount / 200); // Average reading speed: 200 words/min
|
|
|
|
const root = parse(content);
|
|
const headers = root.querySelectorAll("h1, h2, h3");
|
|
|
|
const toc = headers.map((header) => ({
|
|
depth: parseInt(header.tagName.replace("H", "")),
|
|
text: header.innerText.trim(),
|
|
slug: header.getAttribute("id"), // Astro génère l'id automatiquement
|
|
}));
|
|
---
|
|
|
|
<Layout title={`${title} - Another Hadi`} description={description}>
|
|
<article class="max-w-4xl mx-auto px-4 py-20">
|
|
<BackToTop />
|
|
<!-- Back button -->
|
|
<div class="mb-8">
|
|
<a href="/blog" class="btn btn-ghost btn-sm">
|
|
<ChevronLeft size={18} />
|
|
Back to Blog
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Featured Image -->
|
|
{
|
|
image && (
|
|
<figure class="mb-8 rounded-2xl overflow-hidden">
|
|
<Image
|
|
src={image}
|
|
alt={title}
|
|
class="w-full aspect-video object-cover"
|
|
width={1200}
|
|
height={630}
|
|
/>
|
|
</figure>
|
|
)
|
|
}
|
|
|
|
<!-- Post Header -->
|
|
<header class="mb-8">
|
|
<h1 class="text-5xl font-bold mb-4">{title}</h1>
|
|
<p class="text-xl text-base-content/70 mb-4">{description}</p>
|
|
|
|
<div
|
|
class="flex flex-wrap items-center gap-4 text-sm text-base-content/60"
|
|
>
|
|
<time datetime={publishDate.toISOString()}>
|
|
{formatDate(publishDate)}
|
|
</time>
|
|
<span>•</span>
|
|
<span>{readingTime} min read</span>
|
|
{
|
|
updatedDate && (
|
|
<>
|
|
<span>•</span>
|
|
<span>Updated: {formatDate(updatedDate)}</span>
|
|
</>
|
|
)
|
|
}
|
|
</div>
|
|
|
|
{
|
|
tags && tags.length > 0 && (
|
|
<div class="flex flex-wrap gap-2 mt-4">
|
|
{tags.map((tag) => (
|
|
<TagBadge tag={tag} />
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
</header>
|
|
|
|
<!-- Divider -->
|
|
<div class="divider"></div>
|
|
|
|
<!-- TOC -->
|
|
{
|
|
toc.length > 0 && (
|
|
<div class="collapse bg-base-200/50 rounded-xl mb-8 border border-base-300">
|
|
<input type="checkbox" />
|
|
|
|
<p class="collapse-title font-bold uppercase text-xs tracking-widest opacity-60">
|
|
Table of Contents
|
|
</p>
|
|
<div class="collapse-content text-sm">
|
|
<ul class="space-y-3">
|
|
{toc.map((item) => (
|
|
<li
|
|
class={`list-none ${item.depth === 3 ? "ml-6 text-sm" : "font-medium"}`}
|
|
>
|
|
<a
|
|
href={`#${item.slug}`}
|
|
class="hover:link transition-all flex items-center gap-2"
|
|
>
|
|
<span class="text-primary/40">{"#".repeat(item.depth)}</span>
|
|
{item.text}
|
|
</a>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<nav class="bg-base-200/50 ">
|
|
</nav>
|
|
)
|
|
}
|
|
|
|
<!-- Post Content -->
|
|
<div
|
|
class="max-w-none leading-7
|
|
[&_h1]:text-4xl [&_h1]:font-bold [&_h1]:mt-8 [&_h1]:mb-4
|
|
[&_h2]:text-3xl [&_h2]:font-bold [&_h2]:mt-8 [&_h2]:mb-4
|
|
[&_h3]:text-2xl [&_h3]:font-bold [&_h3]:mt-8 [&_h3]:mb-4
|
|
[&_h4]:text-xl [&_h4]:font-bold [&_h4]:mt-8 [&_h4]:mb-4
|
|
[&_h5]:text-lg [&_h5]:font-bold [&_h5]:mt-8 [&_h5]:mb-4
|
|
[&_h6]:text-base [&_h6]:font-bold [&_h6]:mt-8 [&_h6]:mb-4
|
|
[&_p]:mb-4
|
|
[&_a]:underline [&_a]:link [&_a]:break-words
|
|
[&_ul]:mb-4 [&_ul]:ml-6 [&_ul]:list-disc [&_ul]:list-outside
|
|
[&_ol]:mb-4 [&_ol]:ml-6 [&_ol]:list-decimal [&_ol]:list-outside
|
|
[&_li]:mb-2
|
|
[&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-sm [&_code]:font-mono [&_code]:bg-base-200 [&_code]:break-words [&_code]:whitespace-pre-wrap
|
|
[&_pre]:p-4 [&_pre]:rounded-lg [&_pre]:overflow-x-auto [&_pre]:mb-4 [&_pre]:bg-base-200
|
|
[&_pre_code]:bg-transparent [&_pre_code]:p-0
|
|
[&_blockquote]:border-l-4 [&_blockquote]:border-base-300 [&_blockquote]:pl-4 [&_blockquote]:italic [&_blockquote]:my-4
|
|
[&_img]:rounded-lg [&_img]:my-6
|
|
[&_hr]:divider [&_hr]:border-none"
|
|
>
|
|
<slot />
|
|
</div>
|
|
|
|
<!-- Divider -->
|
|
<div class="divider mt-12"></div>
|
|
|
|
<!-- Back to blog link -->
|
|
<div class="flex justify-center gap-2 mt-12">
|
|
<div class="flex gap-3 justify-center flex-wrap text-sm">
|
|
<a href="/blog" class="link link-hover">View All Posts</a>
|
|
<span class="text-base-content/30">•</span>
|
|
<a href="/#contact" class="link link-hover">Contact me</a>
|
|
<span class="text-base-content/30">•</span>
|
|
<a href="https://ko-fi.com/anotherhadi" class="link link-hover">Support me</a>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
</Layout>
|