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