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