parent
9e010f10b9
commit
cbc53a66d8
7 changed files with 182 additions and 19 deletions
@ -0,0 +1,35 @@ |
||||
--- |
||||
import type { VideoFileType } from '../lib/video'; |
||||
import VideoListItem from './VideoListItem.astro'; |
||||
|
||||
export interface Props { |
||||
heading: string; |
||||
videos: VideoFileType[]; |
||||
} |
||||
|
||||
const { heading, videos } = Astro.props; |
||||
--- |
||||
|
||||
<div class='container'> |
||||
<h1 class='text-2xl sm:text-3xl font-bold block'>{heading}</h1> |
||||
|
||||
<div class='mt-3 sm:my-5'> |
||||
{videos.map((video) => <VideoListItem video={video} />)} |
||||
</div> |
||||
|
||||
<a |
||||
href='/videos' |
||||
class='hidden sm:inline transition-colors py-2 px-3 text-xs font-medium rounded-full bg-gradient-to-r from-slate-600 to-black hover:from-blue-600 hover:to-blue-800 text-white' |
||||
> |
||||
View All Videos → |
||||
</a> |
||||
|
||||
<div class='block sm:hidden mt-3'> |
||||
<a |
||||
href='/videos' |
||||
class='text-sm font-regular block p-2 border border-black text-black rounded-md text-center hover:bg-black hover:text-gray-50' |
||||
> |
||||
View All Videos → |
||||
</a> |
||||
</div> |
||||
</div> |
@ -0,0 +1,40 @@ |
||||
--- |
||||
import type { VideoFileType } from "../lib/video"; |
||||
|
||||
export interface Props { |
||||
video: VideoFileType; |
||||
} |
||||
|
||||
const { video } = Astro.props; |
||||
const { frontmatter, id } = video; |
||||
--- |
||||
|
||||
<a |
||||
class:list={[ |
||||
"block no-underline py-2 group text-md items-center text-gray-600 hover:text-blue-600 flex justify-between border-b", |
||||
]} |
||||
href={`/videos/${id}`} |
||||
> |
||||
<span class="group-hover:translate-x-2 transition-transform"> |
||||
{frontmatter.title} |
||||
|
||||
{ |
||||
frontmatter.isNew && ( |
||||
<span class="bg-green-300 text-green-900 text-xs font-medium px-1.5 py-0.5 rounded-sm uppercase ml-1.5"> |
||||
New |
||||
<span class="hidden sm:inline"> |
||||
· |
||||
{new Date(frontmatter.date).toLocaleString("default", { |
||||
month: "long", |
||||
})} |
||||
</span> |
||||
</span> |
||||
) |
||||
} |
||||
</span> |
||||
<span class="capitalize text-gray-500 text-xs hidden sm:block"> |
||||
{frontmatter.duration} |
||||
</span> |
||||
|
||||
<span class="text-gray-400 text-xs block sm:hidden"> »</span> |
||||
</a> |
@ -0,0 +1,61 @@ |
||||
import type { MarkdownFileType } from './file'; |
||||
|
||||
export interface VideoFrontmatter { |
||||
title: string; |
||||
description: string; |
||||
author: { |
||||
name: string; |
||||
url: string; |
||||
imageUrl: string; |
||||
}; |
||||
seo: { |
||||
title: string; |
||||
description: string; |
||||
}; |
||||
isNew: boolean; |
||||
duration: string; |
||||
date: string; |
||||
sitemap: { |
||||
priority: number; |
||||
changefreq: 'daily' | 'weekly' | 'monthly' | 'yealry'; |
||||
}; |
||||
tags: string[]; |
||||
} |
||||
|
||||
export type VideoFileType = MarkdownFileType<VideoFrontmatter> & { |
||||
id: string; |
||||
}; |
||||
|
||||
/** |
||||
* Generates id from the given video file |
||||
* @param filePath Markdown file path |
||||
* |
||||
* @returns unique video identifier |
||||
*/ |
||||
function videoPathToId(filePath: string): string { |
||||
const fileName = filePath.split('/').pop() || ''; |
||||
|
||||
return fileName.replace('.md', ''); |
||||
} |
||||
|
||||
/** |
||||
* Gets all the videos sorted by the publishing date |
||||
* @returns Promisifed video files |
||||
*/ |
||||
export async function getAllVideos(): Promise<VideoFileType[]> { |
||||
const videos = await import.meta.glob<VideoFileType>('/src/videos/*.md', { |
||||
eager: true, |
||||
}); |
||||
|
||||
const videoFiles = Object.values(videos); |
||||
const enrichedVideos = videoFiles.map((videoFile) => ({ |
||||
...videoFile, |
||||
id: videoPathToId(videoFile.file), |
||||
})); |
||||
|
||||
return enrichedVideos.sort( |
||||
(a, b) => |
||||
new Date(b.frontmatter.date).valueOf() - |
||||
new Date(a.frontmatter.date).valueOf() |
||||
); |
||||
} |
@ -0,0 +1,23 @@ |
||||
--- |
||||
import VideoListItem from '../../components/VideoListItem.astro'; |
||||
import SimplePageHeader from '../../components/SimplePageHeader.astro'; |
||||
import BaseLayout from '../../layouts/BaseLayout.astro'; |
||||
import { getAllVideos } from '../../lib/video'; |
||||
|
||||
const guides = await getAllGuides(); |
||||
--- |
||||
|
||||
<BaseLayout title='Guides'> |
||||
<SimplePageHeader |
||||
title='Guides' |
||||
description='Succinct graphical explanations to engineering topics.' |
||||
/> |
||||
|
||||
<div class='pb-20 pt-2 bg-gray-50'> |
||||
<div class='container'> |
||||
<div class='mt-3 sm:my-5'> |
||||
{guides.map((guide) => <GuideListItem guide={guide} />)} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</BaseLayout> |
Loading…
Reference in new issue