182 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			182 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| ---
 | |
| import { type CollectionEntry, getCollection } from 'astro:content';
 | |
| import getReadingTime from 'reading-time';
 | |
| import { readItems, readSingleton } from '@directus/sdk';
 | |
| 
 | |
| import directus from '@lib/directus';
 | |
| import { getDirectusImageURL } from '@lib/directusFunctions';
 | |
| import BaseLayout from '@layouts/BaseLayout.astro';
 | |
| import Image from '@components/ui/images/Image.astro';
 | |
| import { formatDateTime } from '@support/time';
 | |
| 
 | |
| export async function getStaticPaths() {
 | |
|   const posts = await directus.request(readItems('posts'));
 | |
|   return posts.map((post) => ({
 | |
|     params: { slug: post.slug },
 | |
|     props: post,
 | |
|   }));
 | |
| }
 | |
| const post = Astro.props;
 | |
| 
 | |
| const global = await directus.request(readSingleton('site_global'));
 | |
| const category: CollectionEntry<'categories'> = (await getCollection('categories'))
 | |
|   .filter((c) => c.slug === post.category)
 | |
|   .pop() as CollectionEntry<'categories'>;
 | |
| const readingTime = getReadingTime(post.content);
 | |
| ---
 | |
| 
 | |
| <BaseLayout
 | |
|   title={post.title}
 | |
|   description={post.description}
 | |
|   ogImage={getDirectusImageURL(post.image)}
 | |
|   structuredData={{
 | |
|     '@context': 'https://schema.org',
 | |
|     '@type': 'NewsArticle',
 | |
|     inLanguage: 'en-US',
 | |
|     '@id': Astro.url.href,
 | |
|     url: Astro.url.href,
 | |
|     description: post.description,
 | |
|     isPartOf: {
 | |
|       '@type': 'WebSite',
 | |
|       url: `${global.site_url}/blog`,
 | |
|       name: global.name,
 | |
|       description: global.about,
 | |
|     },
 | |
|     image: [
 | |
|       // post.data.banner,
 | |
|     ],
 | |
|     headline: post.title,
 | |
|     datePublished: post.published_date,
 | |
|     dateModified: post.updated_date,
 | |
|     author: [
 | |
|       {
 | |
|         '@type': 'Person',
 | |
|         name: `${global.name}`,
 | |
|         url: `${global.site_url}`,
 | |
|       },
 | |
|     ],
 | |
|   }}
 | |
| >
 | |
|   <section class="mx-auto max-w-6xl px-4 pt-8 pb-12 sm:px-6 lg:px-8 lg:pt-12">
 | |
|     <div class="smooth-reveal relative w-full">
 | |
|       <div class="mt-4 rounded-2xl shadow-none sm:mt-0 sm:shadow-sm">
 | |
|         <Image
 | |
|           class="max-h-[600px] w-full rounded-t-2xl object-cover"
 | |
|           src={getDirectusImageURL(post.image)}
 | |
|           alt={post.image_alt}
 | |
|           draggable="false"
 | |
|           format="webp"
 | |
|           loading="lazy"
 | |
|           inferSize={true}
 | |
|         />
 | |
|         <div
 | |
|           class="rounded-b-2xl px-0 py-6 sm:bg-neutral-100 sm:px-6 md:px-10 lg:px-14 sm:dark:bg-neutral-900/30"
 | |
|         >
 | |
|           <div class="mb-16">
 | |
|             <h2
 | |
|               class="mb-6 block text-3xl font-bold tracking-tight text-balance text-neutral-800 md:text-4xl lg:text-5xl dark:text-neutral-300"
 | |
|             >
 | |
|               {post.title}
 | |
|             </h2>
 | |
|             <ol class="mt-8 flex items-center whitespace-nowrap">
 | |
|               <li class="inline-flex items-center">
 | |
|                 <a
 | |
|                   class="flex items-center text-sm text-neutral-500 transition-all duration-300 hover:text-neutral-700 dark:text-neutral-400 dark:hover:text-neutral-200"
 | |
|                   href=`/categories/${category.slug}`
 | |
|                 >
 | |
|                   {category?.data?.title}
 | |
|                 </a>
 | |
|                 <svg
 | |
|                   class="mx-2 size-5 flex-shrink-0 text-neutral-500 dark:text-neutral-500"
 | |
|                   width="16"
 | |
|                   height="16"
 | |
|                   viewBox="0 0 16 16"
 | |
|                   fill="none"
 | |
|                   xmlns="http://www.w3.org/2000/svg"
 | |
|                   aria-hidden="true"
 | |
|                 >
 | |
|                   <path d="M6 13L10 3" stroke="currentColor" stroke-linecap="round"></path>
 | |
|                 </svg>
 | |
|               </li>
 | |
|               <li
 | |
|                 class="inline-flex items-center text-sm text-neutral-500 transition-all duration-300 hover:text-neutral-700 dark:text-neutral-400 dark:hover:text-neutral-200"
 | |
|               >
 | |
|                 {formatDateTime(post.published_date)}
 | |
|                 <svg
 | |
|                   class="mx-2 size-5 flex-shrink-0 text-neutral-500 dark:text-neutral-500"
 | |
|                   width="16"
 | |
|                   height="16"
 | |
|                   viewBox="0 0 16 16"
 | |
|                   fill="none"
 | |
|                   xmlns="http://www.w3.org/2000/svg"
 | |
|                   aria-hidden="true"
 | |
|                 >
 | |
|                   <path d="M6 13L10 3" stroke="currentColor" stroke-linecap="round"></path>
 | |
|                 </svg>
 | |
|               </li>
 | |
|               <li
 | |
|                 class="inline-flex items-center text-sm text-neutral-500 transition-all duration-300 hover:text-neutral-700 dark:text-neutral-400 dark:hover:text-neutral-200"
 | |
|                 aria-current="page"
 | |
|               >
 | |
|                 {readingTime.minutes.toPrecision(1)} minutes to read
 | |
|               </li>
 | |
|             </ol>
 | |
|           </div>
 | |
| 
 | |
|           <article
 | |
|             class="prose prose-blog sm:prose-lg dark:prose-invert max-w-none text-justify text-neutral-800 dark:text-neutral-200"
 | |
|           >
 | |
|             <div set:html={post.content} />
 | |
|           </article>
 | |
| 
 | |
|           <div
 | |
|             class="mx-auto mt-10 grid max-w-screen-lg gap-y-5 sm:flex sm:items-center sm:justify-between sm:gap-y-0 md:mt-14"
 | |
|           >
 | |
|             <div class="flex flex-wrap gap-x-2 gap-y-1 sm:flex-nowrap sm:items-center sm:gap-y-0">
 | |
|               {
 | |
|                 post.tags.map((tag: string) => (
 | |
|                   <span class="bg-steel/30 dark:bg-bermuda/60 inline-flex items-center gap-x-1.5 rounded-lg px-3 py-1.5 text-xs font-medium text-neutral-700 outline-none focus:outline-none focus-visible:ring focus-visible:outline-none dark:text-neutral-200">
 | |
|                     {tag}
 | |
|                   </span>
 | |
|                 ))
 | |
|               }
 | |
|             </div>
 | |
|           </div>
 | |
|         </div>
 | |
|       </div>
 | |
|     </div>
 | |
|   </section>
 | |
|   <style is:inline>
 | |
|     code[data-theme*=' '],
 | |
|     code[data-theme*=' '] span {
 | |
|       color: var(--shiki-light);
 | |
|     }
 | |
| 
 | |
|     html.dark {
 | |
|       code[data-theme*=' '],
 | |
|       code[data-theme*=' '] span {
 | |
|         color: var(--shiki-dark);
 | |
|       }
 | |
|     }
 | |
|   </style>
 | |
| </BaseLayout>
 | |
| 
 | |
| <script>
 | |
|   // Add smooth reveal animations for content after loading
 | |
|   document.addEventListener('astro:page-load', () => {
 | |
|     const animateContent = () => {
 | |
|       const smoothReveal = document.querySelectorAll('.smooth-reveal');
 | |
|       smoothReveal.forEach((el, index) => {
 | |
|         setTimeout(
 | |
|           () => {
 | |
|             el.classList.add('animate-reveal');
 | |
|           },
 | |
|           100 + index * 100
 | |
|         );
 | |
|       });
 | |
|     };
 | |
| 
 | |
|     animateContent();
 | |
|   });
 | |
| </script>
 |