Initial commit

This commit is contained in:
Timothy DeHerrera
2022-11-01 15:18:00 -06:00
committed by GitHub
commit 14079f0511
90 changed files with 7473 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
---
import '@fontsource/fira-sans'
import { SITE } from '$/config'
import '../styles/global.css'
export type Props = {
title: string
description: string
permalink: string
image: string
}
const { title = SITE.title , description, permalink, image } = Astro.props as Props
---
<!-- Use Google Fonts, if you don't wanna prefer a self-hosted version -->
<!-- <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&display=swap" rel="stylesheet"> -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{title}</title>
<meta name="title" content={title}/>
{description &&
<meta name="description" content={description}/>
}
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="shortcut icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="msapplication-config" content="/browserconfig.xml"/>
<meta name="theme-color" content="#ffffff" />
<!-- Open Graph Tags (Facebook) -->
<meta property="og:type" content="website" />
<meta property="og:title" content={title} />
{permalink &&
<meta property="og:url" content={permalink} />
}
{description &&
<meta property="og:description" content={description} />
}
{image &&
<meta property="og:image" content={image} />
}
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:title" content={title} />
{permalink &&
<meta property="twitter:url" content={permalink} />
}
{description &&
<meta property="twitter:description" content={description} />
}
{image &&
<meta property="twitter:image" content={image} />
}
<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>

View File

@@ -0,0 +1,7 @@
<body class="font-sans antialiased min-h-screen bg-gray-100 dark:bg-gray-800">
<div class="transition-colors">
<main class="mx-auto max-w-4xl px-4 md:px-0">
<slot />
</main>
</div>
</body>

View File

@@ -0,0 +1,17 @@
---
import { SITE } from '$/config'
import ModeLabel from './ModeLabel.svelte'
import NetlifyIdentity from './NetlifyIdentity.svelte'
---
<footer class="footer">
<nav class="nav">
<div>2021 &copy; Copyright notice | <a href={ SITE.githubUrl } title={`${ SITE.name }'s Github URL'`}>{ SITE.name }</a>
<ModeLabel client:load/> theme on <a href="https://astro.build/">Astro</a></div>
<NetlifyIdentity client:load/>
</nav>
</footer>
<style>
.footer {
@apply py-6 border-t
}
</style>

View File

@@ -0,0 +1,69 @@
---
import { SITE } from '$/config'
import SvgIcon from './SvgIcon.astro'
import ModeSwitcherBtn from './ModeSwitcherBtn.svelte'
import SearchBtn from './SearchBtn.svelte'
---
<header class="header">
<div class="header__logo">
<a href="/" class="avatar">
<img class="header__logo-img" src="/assets/logo.svg" alt="Astro logo" />
</a>
</div>
<div class="header__meta flex-1">
<h3 class="header__title dark:text-theme-dark-secondary">
<a href="">{ SITE.name }</a>
</h3>
<div class="header__meta-more flex">
<p class="header__desc">
{ SITE.description }
</p>
<nav class="header__nav flex">
<ul class="header__ref-list">
<li>
<SearchBtn client:visible />
</li>
<li>
<a href={ SITE.githubUrl } title={`${ SITE.name }'s Github URL'`}>
<SvgIcon>
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
</SvgIcon>
</a>
</li>
<li>
<a href="/rss.xml" title="RSS">
<SvgIcon>
<path d="M4 11a9 9 0 0 1 9 9"></path>
<path d="M4 4a16 16 0 0 1 16 16"></path>
<circle cx="5" cy="19" r="1"></circle>
</SvgIcon>
</a>
</li>
<li>
<ModeSwitcherBtn client:visible />
</li>
</ul>
</nav>
</div>
</div>
</header>
<style>
.header {
@apply flex gap-4 border-b py-3 /* border-gray-200 dark:border-gray-700 - check styles/global.css */
}
.header__logo-img {
@apply w-16 h-16 rounded-full overflow-hidden
}
.header__title {
@apply text-4xl font-extrabold md:text-5xl text-theme-secondary dark:text-theme-dark-secondary
}
.header__desc {
@apply text-xl flex-1 dark:text-gray-200
}
.header__ref-list {
@apply flex gap-3 text-gray-400
}
</style>

View File

@@ -0,0 +1,10 @@
<img src="/assets/yay.svg" alt="Yay!" />
<style>
img {
@apply mx-auto w-2/3 mt-6
}
h1 {
@apply w-full justify-center text-center text-3xl font-bold text-purple-600 py-10
}
</style>

View File

@@ -0,0 +1,28 @@
---
import BaseLayout from './BaseLayout.astro';
import Header from './Header.astro';
import Footer from './Footer.astro';
import Nav from './Nav.astro';
import Portal from './Portal.astro';
import SearchModal from './SearchModal.svelte'
---
<BaseLayout>
<br class="my-4"/>
<Header/>
<Nav/>
<div class="content">
<slot />
</div>
<br class="my-4"/>
<Footer/>
<Portal>
<SearchModal client:load/>
</Portal>
</BaseLayout>
<style>
.content {
min-height: 580px
}
</style>

View File

@@ -0,0 +1,48 @@
---
import { getMonthName } from '$/utils'
const { post } = Astro.props
---
<div class="post-preview">
<div class="sm:w-20 md:w-32">
<div class="post-preview__date">
<span class="post-preview__date__day">{ new Date(post.date).getDate() }</span>
<span class="post-preview__date__month-n-year">{ `${getMonthName(post.date)} ${new Date(post.date).getFullYear()}` }</span>
</div>
</div>
<div class="flex-1">
<div class="flex flex-col mb-2">
<h4 class="post-preview__title">
<a href={post.url} title={post.title} target="_blank">{post.title}</a>
</h4>
<div>
<strong>{post.host}</strong>
{
post.participants.length > 0 && ` <em>with</em> ${post.participants.join(', ')}`
}
</div>
</div>
<p class="post-preview__desc">
{post.description}
</p>
</div>
</div>
<style>
.post-preview {
@apply flex gap-6
}
.post-preview__date {
@apply flex flex-col w-full text-center
}
.post-preview__date__day {
@apply text-6xl font-semibold text-gray-500 dark:text-gray-300
}
.post-preview__date__month-n-year {
@apply text-gray-400
}
.post-preview__title {
@apply text-2xl font-semibold text-theme-primary dark:text-theme-dark-primary hover:underline
}
.post-preview__desc {
@apply text-lg leading-6 dark:text-white line-clamp-2
}
</style>

View File

@@ -0,0 +1,14 @@
---
import MediaPreview from './MediaPreview.astro'
const { posts } = Astro.props
---
<section class="media-preview__list">
{posts.map((post) => (
<MediaPreview post={post}/>
))}
</section>
<style>
.media-preview__list {
@apply flex flex-col gap-12
}
</style>

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import ModeSensitive from './ModeSensitive.svelte'
</script>
<ModeSensitive>
<span slot="dark">(dark)</span>
<span slot="light">(light)</span>
</ModeSensitive>

View File

@@ -0,0 +1,8 @@
<script lang="ts">
import { theme } from '../store/theme'
</script>
{#if $theme === 'dark'}
<slot name="dark"/>
{:else}
<slot name="light"/>
{/if}

View File

@@ -0,0 +1,35 @@
<script lang="ts">
import { onMount } from 'svelte'
import { theme } from '../store/theme'
type ThemeType = 'dark' | 'light'
const THEME_DARK: ThemeType = 'dark'
const THEME_LIGHT: ThemeType = 'light'
let currTheme: ThemeType = THEME_DARK
function toggleTheme() {
window.document.documentElement.classList.toggle(THEME_DARK)
currTheme = localStorage.getItem('theme') === THEME_DARK ? THEME_LIGHT : THEME_DARK
// Update Storage
localStorage.setItem('theme', currTheme)
// Update Store
theme.set(currTheme)
}
onMount(() => {
if (localStorage.getItem('theme') === THEME_DARK || (!('theme' in localStorage) && window.matchMedia(`(prefers-color-scheme: ${THEME_DARK})`).matches)) {
window.document.documentElement.classList.add(THEME_DARK)
currTheme = THEME_DARK
} else {
window.document.documentElement.classList.remove(THEME_DARK)
currTheme = THEME_LIGHT
}
// Update Store
theme.set(currTheme)
})
</script>
<button on:click={toggleTheme}>
<slot theme={currTheme}/>
</button>

View File

@@ -0,0 +1,21 @@
<script lang="ts">
import ModeSwitcher from './ModeSwitcher.svelte'
import SvgIcon from './SvgIcon.svelte'
</script>
<ModeSwitcher let:theme>
<SvgIcon>
{#if theme === 'dark'}
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
{:else}
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path>
{/if}
</SvgIcon>
</ModeSwitcher>

18
src/components/Nav.astro Normal file
View File

@@ -0,0 +1,18 @@
---
import { toTitleCase } from '$/utils'
import { NAV_ITEMS } from '$/config'
---
<nav class="nav py-3">
<ul class="nav-list dark:text-theme-dark-secondary">
{
Object.keys(NAV_ITEMS).map(navItemKey => <li>
<a class="hover:underline" href={NAV_ITEMS[navItemKey].path} title={NAV_ITEMS[navItemKey].title}>{toTitleCase(NAV_ITEMS[navItemKey].title)}</a>
</li>)
}
</ul>
</nav>
<style>
.nav-list {
@apply inline-flex list-none gap-8 text-xl font-semibold text-theme-secondary dark:text-theme-dark-secondary py-2 flex-wrap
}
</style>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { onMount} from 'svelte'
onMount(() => {
if (window.netlifyIdentity) {
window.netlifyIdentity.on('init', (user) => {
if (!user) {
window.netlifyIdentity.on('login', () => {
document.location.href = '/admin/';
});
}
});
}
})
</script>

View File

@@ -0,0 +1,15 @@
---
const { page } = Astro.props
---
<div class="page__actions">
{page.url.prev && <a class="action__go-to-x" href={page.url.prev} title="Go to Previous">&larr; Prev</a>}
{page.url.next && <a class="action__go-to-x" href={page.url.next} title="Go to Next">Next &rarr;</a>}
</div>
<style>
.page__actions {
@apply flex justify-center md:justify-end py-6 gap-2
}
.action__go-to-x {
@apply text-base uppercase text-gray-500 dark:text-gray-400 hover:underline
}
</style>

View File

@@ -0,0 +1,3 @@
<div class="portal-root">
<slot/>
</div>

View File

@@ -0,0 +1,40 @@
---
import { getMonthName, getSlugFromPathname } from '$/utils'
const { frontmatter: post, file } = Astro.props.post
---
<div class="post-draft-preview">
<div class="sm:w-20 md:w-32">
<div class="post-draft-preview__date">
<span class="post-draft-preview__date__day">{ new Date(post.date).getDate() }</span>
<span class="post-draft-preview__date__month-n-year">{ `${getMonthName(post.date)} ${new Date(post.date).getFullYear()}` }</span>
</div>
</div>
<div class="flex-1">
<h4 class="post-draft-preview__title">
<a href={`/drafts/${getSlugFromPathname(file)}`} title={post.title}>{post.title}</a>
</h4>
<p class="post-draft-preview__desc">
{post.description}
</p>
</div>
</div>
<style>
.post-draft-preview {
@apply flex gap-6
}
.post-draft-preview__date {
@apply flex flex-col w-full text-center
}
.post-draft-preview__date__day {
@apply text-6xl font-semibold text-gray-500 dark:text-gray-300
}
.post-draft-preview__date__month-n-year {
@apply text-gray-400
}
.post-draft-preview__title {
@apply text-2xl font-semibold text-theme-primary dark:text-theme-dark-primary hover:underline mb-2
}
.post-draft-preview__desc {
@apply text-lg leading-6 dark:text-white line-clamp-2
}
</style>

View File

@@ -0,0 +1,14 @@
---
import PostDraftPreview from './PostDraftPreview.astro'
const { posts } = Astro.props
---
<section class="post-draft-preview__list">
{posts.map((post) => (
<PostDraftPreview post={post}/>
))}
</section>
<style>
.post-draft-preview__list {
@apply flex flex-col gap-12
}
</style>

View File

@@ -0,0 +1,40 @@
---
import { getMonthName } from '$/utils'
const { frontmatter: post, url } = Astro.props.post
---
<div class="post-preview">
<div class="sm:w-20 md:w-32">
<div class="post-preview__date">
<span class="post-preview__date__day">{ new Date(post.date).getDate() }</span>
<span class="post-preview__date__month-n-year">{ `${getMonthName(post.date)} ${new Date(post.date).getFullYear()}` }</span>
</div>
</div>
<div class="flex-1">
<h4 class="post-preview__title">
<a href={url} title={post.title}>{post.title}</a>
</h4>
<p class="post-preview__desc">
{post.description}
</p>
</div>
</div>
<style>
.post-preview {
@apply flex gap-6;
}
.post-preview__date {
@apply flex flex-col w-full text-center;
}
.post-preview__date__day {
@apply text-6xl font-semibold text-gray-500 dark:text-gray-300;
}
.post-preview__date__month-n-year {
@apply text-gray-400;
}
.post-preview__title {
@apply text-2xl font-semibold text-theme-primary dark:text-theme-dark-primary hover:underline mb-2;
}
.post-preview__desc {
@apply text-lg leading-6 line-clamp-2 dark:text-white;
}
</style>

View File

@@ -0,0 +1,15 @@
---
import PostPreview from './PostPreview.astro'
const { posts } = Astro.props
const sortedPosts = posts.sort((a, b) => new Date(b.date) - new Date(a.date));
---
<section class="post-preview__list">
{sortedPosts.map((post) => (
<PostPreview post={post}/>
))}
</section>
<style>
.post-preview__list {
@apply flex flex-col gap-12
}
</style>

View File

@@ -0,0 +1,46 @@
<script lang="ts">
type Props = {
slug: string
title: string
description: string
category: string,
tags: Array<string>
}
export let post: Props
export let isLast: boolean = false
</script>
<div class="post-preview hover:bg-theme-primary">
<div class="flex-1">
<h4 class="post-preview__title">
<a href={`/${post.category}/${post.slug}`} title={post.title}>{post.title} &rarr;</a>
</h4>
<p class="post-preview__desc">
{post.description}
</p>
<ul class="tag-list">
{#each post.tags as tag}
<a class="tag" href={`/tags/${tag}`} title={tag}>{tag}</a>
{/each}
</ul>
</div>
</div>
{#if !isLast}
<hr class="my-4 text-theme-dark-secondary"/>
{/if}
<style lang="postcss">
.post-preview {
@apply flex gap-6 text-left;
}
.post-preview__title {
@apply text-lg leading-tight font-semibold text-white mb-2;
}
.post-preview__desc {
@apply text-base text-theme-dark-primary leading-5 line-clamp-2;
}
.tag-list {
@apply list-none py-2 flex flex-wrap gap-2;
}
.tag {
@apply inline-block text-xs px-4 py-1 rounded-full text-theme-primary bg-theme-dark-primary;
}
</style>

View File

@@ -0,0 +1,11 @@
<article class="prose dark:prose-dark">
<slot />
</article>
<style>
.prose {
@apply max-w-none
/* Size Modifiers: https://github.com/tailwindlabs/tailwindcss-typography#size-modifiers */
/* Color Themes: https://github.com/tailwindlabs/tailwindcss-typography#color-modifiers */
}
</style>

View File

@@ -0,0 +1,96 @@
<script lang="ts">
import { onMount } from 'svelte'
import SearchIcon from './SearchIcon.svelte'
import PostSearchPreview from './PostSearchPreview.svelte'
let searchInput
let searchableDocs
let searchIndex
let searchQuery = ''
let searchResults = []
onMount(async() => {
const lunr = (await import('lunr')).default
const resp = await fetch('/search-index.json')
searchableDocs = await resp.json()
// Initialize indexing
searchIndex = lunr(function(){
// the match key...
this.ref('slug')
// indexable properties
this.field('title')
this.field('description')
this.field('tags')
// Omit, if you don't want to search on `body`
this.field('body')
// Index every document
searchableDocs.forEach(doc => {
this.add(doc)
}, this)
})
searchInput.focus()
})
$: {
if(searchQuery && searchQuery.length >= 3) {
const matches = searchIndex.search(searchQuery)
searchResults = []
matches.map(match => {
searchableDocs.filter(doc => {
if(match.ref === doc.slug) {
searchResults.push(doc)
}
})
})
}
}
</script>
<div class="search">
<div class="search__ctrl">
<label for="search"><SearchIcon found={searchResults.length > 0} /></label>
<input type="text" name="search" bind:this={searchInput} placeholder="What are you looking for?" bind:value={searchQuery} />
</div>
<div class="search__results">
{#if searchResults.length}
{#each searchResults as post, i }
<PostSearchPreview post={post} isLast={ i === searchResults.length - 1 } />
{/each}
{:else}
<div class="search__results--none">
{#if searchQuery.length}
No matching items found!
{:else}
Search something and let me find it for you! :-)
{/if}
</div>
{/if}
</div>
<div class="note"><small>click anywhere outside to close</small></div>
</div>
<style>
.search {
@apply w-full relative bg-theme-primary p-8 rounded-md shadow-lg;
}
input {
@apply w-full px-4 py-2 pl-10 text-xl font-semibold text-gray-600 border-0 shadow-inner rounded-md bg-gray-100 placeholder-theme-dark-secondary;
}
.search__ctrl {
@apply pb-4 relative;
}
.search__ctrl label {
@apply text-theme-primary absolute top-2 left-2;
}
.search__results {
@apply w-96 h-64 py-4 overflow-y-auto;
}
.search__results--none {
@apply text-center text-theme-dark-primary;
}
.note {
@apply w-full text-center text-white;
}
</style>

View File

@@ -0,0 +1,11 @@
<script lang="ts">
import SearchIcon from './SearchIcon.svelte'
import { isSearchVisible } from '../store/search'
function showSearchDialog() {
isSearchVisible.set(true)
}
</script>
<button on:click={showSearchDialog}>
<SearchIcon />
</button>

View File

@@ -0,0 +1,18 @@
<script lang="ts">
import SvgIcon from './SvgIcon.svelte'
export let found:boolean = false
</script>
<SvgIcon>
{#if found}
<path
d="M7.66542 10.2366L9.19751 8.951L10.4831 10.4831L13.5473 7.91194L14.8328 9.44402L10.2366 13.3007L7.66542 10.2366Z"
fill="currentColor"
/>
{/if}
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M16.2071 4.89344C19.0923 7.77862 19.3131 12.3193 16.8693 15.4578C16.8846 15.4713 16.8996 15.4854 16.9143 15.5L21.1569 19.7427C21.5474 20.1332 21.5474 20.7664 21.1569 21.1569C20.7664 21.5474 20.1332 21.5474 19.7427 21.1569L15.5 16.9143C15.4854 16.8996 15.4713 16.8846 15.4578 16.8693C12.3193 19.3131 7.77862 19.0923 4.89344 16.2071C1.76924 13.083 1.76924 8.01763 4.89344 4.89344C8.01763 1.76924 13.083 1.76924 16.2071 4.89344ZM14.7929 14.7929C17.1361 12.4498 17.1361 8.6508 14.7929 6.30765C12.4498 3.96451 8.6508 3.96451 6.30765 6.30765C3.96451 8.6508 3.96451 12.4498 6.30765 14.7929C8.6508 17.1361 12.4498 17.1361 14.7929 14.7929Z"
fill="currentColor"
/>
</SvgIcon>

View File

@@ -0,0 +1,32 @@
<script lang="ts">
import { fade, fly } from 'svelte/transition'
import { isSearchVisible } from '../store/search'
import Search from './Search.svelte'
const dismissModal = () => isSearchVisible.set(false)
const handleEsc = (event) => {
if (event.key === 'Escape') {
dismissModal()
}
}
</script>
{#if $isSearchVisible}
<div class="modal__backdrop" on:click={dismissModal} on:keydown={handleEsc} transition:fade></div>
<div class="modal">
<div class="modal__cnt" transition:fly="{{ y: 200, duration: 300 }}">
<Search />
</div>
</div>
{/if}
<style>
.modal {
@apply absolute top-0 left-0 w-full h-full grid justify-center content-center pointer-events-none;
}
.modal__backdrop {
@apply absolute top-0 left-0 w-full h-screen opacity-50 bg-gradient-to-tr from-fuchsia-600 to-fuchsia-900 z-0;
}
.modal__cnt {
@apply w-full z-10 pointer-events-auto;
}
</style>

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<slot />
</svg>

After

Width:  |  Height:  |  Size: 202 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<slot/>
</svg>

After

Width:  |  Height:  |  Size: 201 B

37
src/config.ts Normal file
View File

@@ -0,0 +1,37 @@
import type{ NavItems } from './types'
export const NAV_ITEMS: NavItems = {
home: {
path: '/',
title: 'home'
},
blog: {
path: '/blog',
title: 'blog'
},
tags: {
path: '/tags',
title: 'tags'
},
media: {
path: '/media',
title: 'media'
},
about: {
path: '/about',
title: 'about'
}
}
export const SITE = {
// Your site's detail?
name: 'Ink',
title: 'Astro - Ink',
description: 'Crisp, minimal, personal blog theme for Astro',
url: 'https://astro-ink.vercel.app',
githubUrl: 'https://github.com/one-aalam/astro-ink',
listDrafts: true
// description ?
}
export const PAGE_SIZE = 8

0
src/data/.gitkeep Normal file
View File

34
src/data/astro-media.json Normal file
View File

@@ -0,0 +1,34 @@
[
{
"title": "Ship Less JavaScript with Astro",
"description": "Astro is a way to build websites that ships zero JavaScript by default. Only add JS when you need it for maximum performance! Fred K. Schott will teach us how it works.",
"url": "https://youtu.be/z15YLsLMtu4?list=PLz8Iz-Fnk_eTpvd49Sa77NiF8Uqq5Iykx",
"host": "Jason Lengstorf",
"participants": ["Fred K. Schott"],
"date": "2021-05-08"
},
{
"title": "Astro: A New Architecture for the Modern Web",
"description": "JavaScript meetup for mad science, hacking, and experiments. Hang out virtually on Friday at 4pm Pacific Time each week.",
"url": "https://www.youtube.com/watch?v=mgkwZqVkrwo",
"host": "Feross",
"participants": ["Fred K. Schott"],
"date": "2021-06-08"
},
{
"title": "Astro in 100 Seconds",
"description": "Astro is an open-source tool that can build static HTML websites using popular frontend JavaScript frameworks (React, Vue, Svelte), while loading fully interactive components as needed https://github.com/snowpackjs/astro",
"url": "https://www.youtube.com/watch?v=dsTXcSeAZq8",
"host": "Jeff Delaney",
"participants": [],
"date": "2021-07-12"
},
{
"title": "Yapping About Astro",
"description": "Build a static-by-default site using JavaScript components and only load whatever JavaScript you need by opting in very carefully.",
"url": "https://www.youtube.com/watch?v=3jPaidbpUIA",
"host": "Chris Coyier",
"participants": [],
"date": "2021-08-07"
}
]

33
src/layouts/default.astro Normal file
View File

@@ -0,0 +1,33 @@
---
import { SITE } from '$/config'
import BaseHead from '$/components/BaseHead.astro';
import MainLayout from '$/components/MainLayout.astro';
const { content, showPageHeader = true } = Astro.props
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={ content.title ? `${ SITE.title } | ${content.title}` : SITE.title } description={ content.description }/>
</head>
<MainLayout>
{showPageHeader &&
<div class="page__header">
<h1 class="page__title">{content.title}</h1>
<h5 class="page__desc">{content.description}</h5>
</div>
}
<slot />
</MainLayout>
</html>
<style>
.page__header {
@apply py-4 mb-1
}
.page__title {
@apply text-5xl font-extrabold text-theme-primary dark:text-theme-dark-primary
}
.page__desc {
@apply text-gray-400
}
</style>

View File

@@ -0,0 +1,60 @@
---
import { SITE } from '$/config'
import MainLayout from '$/components/MainLayout.astro'
import BaseHead from '$/components/BaseHead.astro'
import Prose from '$/components/Prose.astro'
const { content } = Astro.props
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={ content.title ? `${ SITE.title } | ${content.title}` : SITE.title } description={ content.description }/>
</head>
<MainLayout>
<div class="post__header">
<div class="post__tags">
{ content.tags.length > 0 && content.tags.map(tag => <a class="post__tag" href={`/tags/${tag}`} title={tag}>{tag}</a>) }
</div>
<h1 class="post__title">{ content.title }</h1>
<h5 class="post__desc">
<a class="post__author" href={`https://twitter.com/${content.authorTwitter}`} title={`${content.author + "'s"} twitter`} target="_blank" rel="external">{ content.author }</a> |
<span class="post__date">{ new Intl.DateTimeFormat('en-US', { dateStyle: 'full' }).format(new Date(content.date))}</span>
</h5>
</div>
<div class="draft-message">
You're viewing a <strong>preview</strong> of <code>/blog/{content.slug}</code> which isn't published yet!
</div>
<!--<img src={content.image} alt={content.title} />-->
<Prose>
<slot />
</Prose>
</MainLayout>
</html>
<style>
.post__header {
@apply py-4 mb-1
}
.post__title {
@apply text-5xl font-extrabold text-theme-primary dark:text-theme-dark-primary
}
.post__desc {
@apply text-gray-500 dark:text-gray-100
}
.post__author {
@apply no-underline dark:text-white hover:text-theme-primary
}
.post__date {
@apply text-gray-400
}
.post__tags {
@apply inline-flex gap-2
}
.post__tag {
@apply text-gray-400 hover:text-theme-primary dark:hover:text-theme-dark-primary
}
.draft-message {
@apply bg-yellow-300 dark:bg-yellow-700 text-gray-700 dark:text-white px-2 py-1 my-2
}
</style>

53
src/layouts/post.astro Normal file
View File

@@ -0,0 +1,53 @@
---
import { SITE } from '$/config'
import MainLayout from '$/components/MainLayout.astro'
import BaseHead from '$/components/BaseHead.astro'
import Prose from '$/components/Prose.astro'
const { content } = Astro.props
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={ content.title ? `${ SITE.title } | ${content.title}` : SITE.title } description={ content.description }/>
</head>
<MainLayout>
<div class="post__header">
<div class="post__tags">
{ content.tags.length > 0 && content.tags.map(tag => <a class="post__tag" href={`/tags/${tag}`} title={tag}>{tag}</a>) }
</div>
<h1 class="post__title">{ content.title }</h1>
<h5 class="post__desc">
<a class="post__author" href={`https://twitter.com/${content.authorTwitter}`} title={`${content.author + "'s"} twitter`} target="_blank" rel="external">{ content.author }</a> |
<span class="post__date">{ new Intl.DateTimeFormat('en-US', { dateStyle: 'full' }).format(new Date(content.date))}</span>
</h5>
</div>
<!--<img src={content.image} alt={content.title} />-->
<Prose>
<slot />
</Prose>
</MainLayout>
</html>
<style>
.post__header {
@apply py-4 mb-1
}
.post__title {
@apply text-5xl font-extrabold text-theme-primary dark:text-theme-dark-primary
}
.post__desc {
@apply text-gray-500 dark:text-gray-100
}
.post__author {
@apply no-underline dark:text-white hover:text-theme-primary
}
.post__date {
@apply text-gray-400
}
.post__tags {
@apply inline-flex gap-2
}
.post__tag {
@apply text-gray-400 hover:text-theme-primary dark:hover:text-theme-dark-primary
}
</style>

18
src/pages/about.mdx Normal file
View File

@@ -0,0 +1,18 @@
---
title: 'About'
description: 'There is a simple secret to building a faster website — just ship less.'
---
import DefaultPageLayout from '$/layouts/default.astro'
import Prose from '$/components/Prose.astro'
<DefaultPageLayout content={{ title: frontmatter.title, description: frontmatter.description }}>
<Prose>
Astro-Ink is a crisp, minimal, personal blog theme for Astro, that shows the capability of statically built sites - offering all the goodness and DX of the modern JS ecosystem without actually shipping any JS by default. It's built by...
## Few Bots, Meta-humans & a Guy!
Aftab Alam // [@aftabbuddy](https://twitter.com/aftabbuddy) // [one-aalam](https://github.com/one-aalam)
<div class="author">
<img class="rounded-full" width="160" src="https://assets.website-files.com/5e51c674258ffe10d286d30a/5e5358878e2493fbea064dd9_peep-59.svg" title="Aalam" />
</div>
</Prose>
</DefaultPageLayout>

View File

@@ -0,0 +1,24 @@
---
import DefaultPageLayout from '$/layouts/default.astro'
import PostPreviewList from '$/components/PostPreviewList.astro'
import Paginator from '$/components/Paginator.astro'
import { PAGE_SIZE } from '$/config'
let title = 'Blog'
let description = 'All the articles posted so far...'
export async function getStaticPaths({ paginate }) {
const allPosts = await Astro.glob('./*.md');
const sortedPosts = allPosts.sort((a, b) => new Date(b.date) - new Date(a.date));
return paginate(sortedPosts, {
pageSize: PAGE_SIZE
})
}
const { page } = Astro.props
---
<DefaultPageLayout content={{ title, description }}>
<PostPreviewList posts={page.data} />
<Paginator page={page} />
</DefaultPageLayout>

View File

@@ -0,0 +1,11 @@
---
layout: $/layouts/post.astro
title: hihiiiiii
description: lol
tags:
- test
author: me
authorTwitter: me
date: 2022-10-30T11:25:18.276Z
---
hid

View File

@@ -0,0 +1,65 @@
---
layout: $/layouts/post.astro
title: Introducing Astro - Ship Less JavaScript1
description: There's a simple secret to building a faster website — just ship less.
tags:
- astro
- jam-stack
author: Fred K. Schott
authorTwitter: FredKSchott
date: 2022-09-28T10:23:31.210Z
image: https://images.unsplash.com/photo-1589409514187-c21d14df0d04?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1650&q=80
category: design
---
Unfortunately, modern web development has been trending in the opposite direction—towards more. More JavaScript, more features, more moving parts, and ultimately more complexity needed to keep it all running smoothly.
Today I'm excited to publicly share Astro: a new kind of static site builder that delivers lightning-fast performance with a modern developer experience. To design Astro, we borrowed the best parts of our favorite tools and then added a few innovations of our own, including:
- Bring Your Own Framework (BYOF): Build your site using React, Svelte, Vue, Preact, web components, or just plain ol' HTML + JavaScript.
- 100% Static HTML, No JS: Astro renders your entire page to static HTML, removing all JavaScript from your final build by default.
- On-Demand Components: Need some JS? Astro can automatically hydrate interactive components when they become visible on the page. If the user never sees it, they never load it.
- Fully-Featured: Astro supports TypeScript, Scoped CSS, CSS Modules, Sass, Tailwind, Markdown, MDX, and any of your favorite npm packages.
- SEO Enabled: Automatic sitemaps, RSS feeds, pagination and collections take the pain out of SEO and syndication.
## H1 is good
### H2 is good too
> links are better
[I know](they-are-better)
This post marks the first public beta release of Astro. Missing features and bugs are still to be expected at this early stage. There are still some months to go before an official 1.0 release, but there are already several fast sites built with Astro in production today. We would love your early feedback as we move towards a v1.0 release later this year.
> To learn more about Astro and start building your first site, check out the project README.
# Example - Syntax Highlighting
## Shell(Bash)
```bash
# make a new project directory and jump into it
mkdir my-astro-project && cd $_
# create a new project with npm
npm create astro@latest
# or yarn
yarn create astro
# or pnpm
pnpm create astro@latest
```
## Python
```python
print('hello world')
```
## Javascript
```js
const func = () => {alert("hello")}
```

View File

@@ -0,0 +1,65 @@
---
layout: $/layouts/post.astro
title: Introducing Astro - Ship Less JavaScript
description: There's a simple secret to building a faster website — just ship less.
tags:
- astro
- jam-stack
author: Fred K. Schott
authorTwitter: FredKSchott
date: 2022-09-18T13:10:23.402Z
image: https://images.unsplash.com/photo-1589409514187-c21d14df0d04?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1650&q=80
category: design
---
Unfortunately, modern web development has been trending in the opposite direction—towards more. More JavaScript, more features, more moving parts, and ultimately more complexity needed to keep it all running smoothly.
Today I'm excited to publicly share Astro: a new kind of static site builder that delivers lightning-fast performance with a modern developer experience. To design Astro, we borrowed the best parts of our favorite tools and then added a few innovations of our own, including:
- Bring Your Own Framework (BYOF): Build your site using React, Svelte, Vue, Preact, web components, or just plain ol' HTML + JavaScript.
- 100% Static HTML, No JS: Astro renders your entire page to static HTML, removing all JavaScript from your final build by default.
- On-Demand Components: Need some JS? Astro can automatically hydrate interactive components when they become visible on the page. If the user never sees it, they never load it.
- Fully-Featured: Astro supports TypeScript, Scoped CSS, CSS Modules, Sass, Tailwind, Markdown, MDX, and any of your favorite npm packages.
- SEO Enabled: Automatic sitemaps, RSS feeds, pagination and collections take the pain out of SEO and syndication.
## H1 is good
### H2 is good too
> links are better
[I know](they-are-better)
This post marks the first public beta release of Astro. Missing features and bugs are still to be expected at this early stage. There are still some months to go before an official 1.0 release, but there are already several fast sites built with Astro in production today. We would love your early feedback as we move towards a v1.0 release later this year.
> To learn more about Astro and start building your first site, check out the project README.
# Example - Syntax Highlighting
## Shell(Bash)
```bash
# make a new project directory and jump into it
mkdir my-astro-project && cd $_
# create a new project with npm
npm create astro@latest
# or yarn
yarn create astro
# or pnpm
pnpm create astro@latest
```
## Python
```python
print('hello world')
```
## Javascript
```js
const func = () => {alert("hello")}
```

View File

@@ -0,0 +1,35 @@
---
layout: $/layouts/post.astro
title: Introducing Astro - Ship Less JavaScript
date: 2021-06-08
image: https://images.unsplash.com/photo-1589409514187-c21d14df0d04?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1650&q=80
author: Fred K. Schott
authorTwitter: FredKSchott
category: design
tags:
- astro
- jam-stack
description: There's a simple secret to building a faster website — just ship less.
---
Unfortunately, modern web development has been trending in the opposite direction—towards more. More JavaScript, more features, more moving parts, and ultimately more complexity needed to keep it all running smoothly.
Today I'm excited to publicly share Astro: a new kind of static site builder that delivers lightning-fast performance with a modern developer experience. To design Astro, we borrowed the best parts of our favorite tools and then added a few innovations of our own, including:
- Bring Your Own Framework (BYOF): Build your site using React, Svelte, Vue, Preact, web components, or just plain ol' HTML + JavaScript.
- 100% Static HTML, No JS: Astro renders your entire page to static HTML, removing all JavaScript from your final build by default.
- On-Demand Components: Need some JS? Astro can automatically hydrate interactive components when they become visible on the page. If the user never sees it, they never load it.
- Fully-Featured: Astro supports TypeScript, Scoped CSS, CSS Modules, Sass, Tailwind, Markdown, MDX, and any of your favorite npm packages.
- SEO Enabled: Automatic sitemaps, RSS feeds, pagination and collections take the pain out of SEO and syndication.
## H1 is good
### H2 is good too
> links are better
[I know](they-are-better)
This post marks the first public beta release of Astro. Missing features and bugs are still to be expected at this early stage. There are still some months to go before an official 1.0 release, but there are already several fast sites built with Astro in production today. We would love your early feedback as we move towards a v1.0 release later this year.
> To learn more about Astro and start building your first site, check out the project README.

View File

@@ -0,0 +1,16 @@
---
layout: $/layouts/post.astro
title: Islands Architecture
date: 2021-05-08
image: https://images.unsplash.com/photo-1502085671122-2d218cd434e6?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1698&q=80
author: Jason Miller
authorTwitter: _developit
category: development
tags:
- astro
- jam-stack
- architecture
- front-end
description: Render HTML pages on the server, and inject placeholders or slots around highly dynamic regions.
---
https://jasonformat.com/islands-architecture/

View File

@@ -0,0 +1,15 @@
---
layout: $/layouts/post.astro
title: Second-guessing the modern web
date: 2021-04-10
image: https://images.unsplash.com/photo-1501772418-b33899635bca?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1650&q=80
author: Tom MacWright
authorTwitter: tmcw
category: design
tags:
- architecture
- front-end
- spa
description: There is a sweet spot of React - in moderately interactive interfaces..
---
https://macwright.com/2020/05/10/spa-fatigue.html

View File

@@ -0,0 +1,31 @@
---
import DefaultPageLayout from '$/layouts/default.astro'
import PostDraftPreviewList from '$/components/PostDraftPreviewList.astro'
import Paginator from '$/components/Paginator.astro'
import { SITE, PAGE_SIZE } from '$/config'
let title = 'Drafts'
let description = 'You\'re viewing a list of unpublished articles on the site. Accuracy or correctness isn\'t guranteed...'
export async function getStaticPaths({ paginate, rss }) {
let allPosts = []
try {
allPosts = await Astro.glob('../../drafts/*.md');
} catch(error) {
console.log('No draft posts found while generating the index page for the draft pages')
}
const sortedPosts = allPosts.sort((a, b) => new Date(b.date) - new Date(a.date));
return paginate(sortedPosts, {
pageSize: PAGE_SIZE
})
}
const { page } = Astro.props
---
<DefaultPageLayout content={{ title, description }}>
{
(SITE.listDrafts) ? <PostDraftPreviewList posts={page.data} /> : (<p class="text-gray-700 dark:text-gray-100">Looks like you have landed on a unpublished posts page. Please find all the published posts <a href="/blog">here</a>!</p>)
}
<Paginator page={page} />
</DefaultPageLayout>

View File

@@ -0,0 +1,47 @@
---
import { getSlugFromPathname } from '$/utils'
import PostDraftPageLayout from '$/layouts/post-draft.astro'
export async function getStaticPaths({ }) {
let allPosts = []
try {
allPosts = await Astro.glob('../../../drafts/*.md')
} catch(error) {
console.log('No draft posts found while generating the draft pages')
}
const allSlugs = new Set()
const allPostsWithSlug = allPosts.map(post => {
// @ts-ignore
const slug = getSlugFromPathname(post.file)
allSlugs.add(slug.toLowerCase())
return {
...post,
slug
}
})
return Array.from(allSlugs).map((slug) => {
const filteredPosts = allPostsWithSlug.filter((post) => post.slug === slug )
return {
params: { slug },
props: {
pages: filteredPosts
}
};
});
}
const { slug } = Astro.params
const { pages } = Astro.props
const [ post ] = pages
---
<div class="draft-message">
You're viewing a <strong>preview</strong> of <code>/blog/{slug}</code> which isn't published yet!
</div>
<post.Content/>
<style>
.draft-message {
@apply w-full bg-yellow-300 dark:bg-yellow-700 text-gray-700 dark:text-white px-2 py-1 text-center
}
</style>

25
src/pages/index.astro Normal file
View File

@@ -0,0 +1,25 @@
---
import DefaultPageLayout from '$/layouts/default.astro'
import PostPreviewList from '$/components/PostPreviewList.astro'
const title = 'Home'
const description = 'Astro-Ink is a crisp, minimal, personal blog theme for Astro'
const posts = await Astro.glob('./blog/*.md')
---
<DefaultPageLayout content={{ title, description }} showPageHeader={false}>
<PostPreviewList posts={posts} />
<div class="page__actions">
<a class="action__go-to-blog" href="/blog" title="All Posts">All Posts &rarr;</a>
</div>
</DefaultPageLayout>
<style>
.page__actions {
@apply flex justify-center md:justify-end py-6
}
.action__go-to-blog {
@apply text-base uppercase text-gray-500 dark:text-gray-400 hover:underline
}
</style>

15
src/pages/media.astro Normal file
View File

@@ -0,0 +1,15 @@
---
import DefaultPageLayout from '$/layouts/default.astro'
import MediaPreviewList from '$/components/MediaPreviewList.astro'
// import posts from '$/data/astro-media.json'
let title = 'Videos & Screencasts';
let description = 'All the great videos on Astro we could find for ya!'
const response = await fetch('https://raw.githubusercontent.com/one-aalam/astro-ink/main/src/data/astro-media.json')
const allPosts = await response.json()
const sortedPosts = allPosts.sort((a, b) => new Date(b.date) - new Date(a.date))
---
<DefaultPageLayout content={{ title, description }}>
<MediaPreviewList posts={sortedPosts} />
</DefaultPageLayout>

26
src/pages/rss.xml.js Normal file
View File

@@ -0,0 +1,26 @@
import rss from '@astrojs/rss';
import { SITE } from '../config'
const allPosts = import.meta.glob('./**/*.md', { eager: true })
const sortedPosts = Object.values(allPosts).sort((a, b) => new Date(b.date) - new Date(a.date));
export const get = () => rss({
// `<title>` field in output xml
title: `${SITE.name} | Blog`,
// `<description>` field in output xml
description: SITE.description,
// base URL for RSS <item> links
// SITE will use "site" from your project's astro.config.
site: import.meta.env.SITE,
// list of `<item>`s in output xml
// simple example: generate items for every md file in /src/pages
// see "Generating items" section for required frontmatter and advanced use cases
items: sortedPosts.map(item => ({
title: item.frontmatter.title,
description: item.frontmatter.description,
link: item.url,
pubDate: item.frontmatter.date,
})),
// (optional) inject custom xml
customData: `<language>en-us</language>`,
});

View File

@@ -0,0 +1,33 @@
---
import { PAGE_SIZE } from '$/config'
import DefaultPageLayout from '$/layouts/default.astro'
import PostPreviewList from '$/components/PostPreviewList.astro'
import Paginator from '$/components/Paginator.astro'
let title = 'Posts By Tags'
let description = 'All the articles posted so far...'
export async function getStaticPaths({ paginate }) {
const allPosts = await Astro.glob('../../blog/*.md')
const allTags = new Set()
allPosts.map(post => {
post.frontmatter.tags && post.frontmatter.tags.map(tag => allTags.add(tag.toLowerCase()))
})
return Array.from(allTags).map((tag) => {
const filteredPosts = allPosts.filter((post) => post.frontmatter.tags.includes(tag))
return paginate(filteredPosts, {
params: { tag },
pageSize: PAGE_SIZE
});
});
}
const { page } = Astro.props
const { tag } = Astro.params
---
<DefaultPageLayout content={{ title: `Posts by Tag: ${tag}`, description: `all of the articles we have posted and linked so far under the tag: ${tag}` }}>
<PostPreviewList posts={page.data} />
<Paginator page={page} />
</DefaultPageLayout>

View File

@@ -0,0 +1,29 @@
---
import DefaultPageLayout from '$/layouts/default.astro'
import PostPreviewList from '$/components/PostPreviewList.astro'
export async function getStaticPaths({ }) {
const allPosts = await Astro.glob('../../blog/*.md')
const allTags = new Set()
allPosts.map(post => {
post.frontmatter.tags && post.frontmatter.tags.map(tag => allTags.add(tag.toLowerCase()))
})
return Array.from(allTags).map((tag) => {
const filteredPosts = allPosts.filter((post) => post.frontmatter.tags.includes(tag))
return {
params: { tag },
props: {
pages: filteredPosts
}
};
});
}
const { pages } = Astro.props
const { tag } = Astro.params
---
<DefaultPageLayout content={{ title: `Posts by Tag: ${tag}`, description: `all of the articles we have posted and linked so far under the tag: ${tag}` }}>
<PostPreviewList posts={pages} />
</DefaultPageLayout>

View File

@@ -0,0 +1,27 @@
---
import DefaultPageLayout from '$/layouts/default.astro'
let title = 'All Tags'
let description = 'All the tags used so far...'
const allPosts = await Astro.glob('../blog/*.md');
const tags = [...new Set([].concat.apply([], allPosts.map(post => post.frontmatter.tags)))]
---
<DefaultPageLayout content={{ title, description }}>
<ul class="tag-list">
{tags.map((tag) => (
<li><a class="tag" href={`/tags/${tag}`} title={`View posts tagged under "${tag}"`}>{tag}</a></li>
))}
</ul>
</DefaultPageLayout>
<style>
.tag-list {
@apply list-none flex gap-2 flex-wrap
}
.tag {
@apply inline-block text-xl px-4 py-1 rounded-full text-theme-primary bg-theme-dark-primary dark:bg-theme-primary dark:text-theme-dark-primary hover:bg-theme-primary hover:text-theme-dark-primary dark:hover:bg-theme-dark-primary dark:hover:text-theme-primary
}
</style>

3
src/store/search.ts Normal file
View File

@@ -0,0 +1,3 @@
import { writable } from 'svelte/store'
export const isSearchVisible = writable<boolean>(false)

4
src/store/theme.ts Normal file
View File

@@ -0,0 +1,4 @@
import { writable } from 'svelte/store'
type ThemeType = 'dark' | 'light'
export const theme = writable<ThemeType>('dark')

14
src/styles/global.css Normal file
View File

@@ -0,0 +1,14 @@
@tailwind base;
/* https://github.com/tailwindlabs/tailwindcss/discussions/2917 */
@layer base {
body {
&.dark {
@apply text-gray-200;
header, footer {
@apply text-gray-400 border-gray-700;
}
}
}
}
@tailwind components;
@tailwind utilities;

8
src/types.ts Normal file
View File

@@ -0,0 +1,8 @@
export type NavItems = {
[key: string]: NavItem
}
export type NavItem = {
path: string
title: string
}

14
src/utils/index.ts Normal file
View File

@@ -0,0 +1,14 @@
import path from 'path'
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
export const toTitleCase = (str: string) => str.replace(
/\w\S*/g,
function(txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
}
)
export const getMonthName = (date: Date) => MONTHS[new Date(date).getMonth()]
export const getSlugFromPathname = (pathname: string) => path.basename(pathname, path.extname(pathname))