Skip to content

Commit 942ca72

Browse files
authored
add files
1 parent b6de19d commit 942ca72

File tree

13 files changed

+524
-13
lines changed

13 files changed

+524
-13
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
"astro-icon": "^1.1.0",
2424
"dayjs": "^1.11.13",
2525
"mdast-util-to-string": "^4.0.0",
26-
"reading-time": "^1.5.0",
2726
"rehype-figure-title": "^1.0.0",
2827
"tailwindcss": "^4.1.12",
2928
"typescript": "^5.3.2"

src/components/BaseHead.astro

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
import { SITE_BASE } from '@/consts'
3+
import '../styles/global.css'
4+
5+
interface Props {
6+
title: string
7+
description: string
8+
image?: string
9+
}
10+
11+
const canonicalURL = new URL(Astro.url.pathname, Astro.site)
12+
13+
const { title, description, image = `${SITE_BASE}/saral-og.jpg` } = Astro.props
14+
---
15+
16+
<!-- Global Metadata -->
17+
<meta charset="utf-8" />
18+
<meta name="viewport" content="width=device-width,initial-scale=1" />
19+
<meta name="generator" content={Astro.generator} />
20+
21+
<!-- Favicons -->
22+
<link
23+
rel="icon"
24+
type="image/png"
25+
href={`${SITE_BASE}/favicon-96x96.png`}
26+
sizes="96x96"
27+
/>
28+
<link rel="icon" type="image/svg+xml" href={`${SITE_BASE}/favicon.svg`} />
29+
<link rel="shortcut icon" href={`${SITE_BASE}/favicon.ico`} />
30+
<link
31+
rel="apple-touch-icon"
32+
sizes="180x180"
33+
href={`${SITE_BASE}/apple-touch-icon.png`}
34+
/>
35+
<meta name="apple-mobile-web-app-title" content="Apps For Linux" />
36+
<link rel="manifest" href={`${SITE_BASE}/site.webmanifest`} />
37+
38+
<!-- Canonical URL -->
39+
<link rel="canonical" href={canonicalURL} />
40+
41+
<!-- Primary Meta Tags -->
42+
<title>{title}</title>
43+
<meta name="title" content={title} />
44+
<meta name="description" content={description} />
45+
46+
<!-- Open Graph / Facebook -->
47+
<meta property="og:type" content="website" />
48+
<meta property="og:url" content={Astro.url} />
49+
<meta property="og:title" content={title} />
50+
<meta property="og:description" content={description} />
51+
<meta property="og:image" content={new URL(image, Astro.url)} />
52+
53+
<!-- Twitter -->
54+
<meta property="twitter:card" content="summary_large_image" />
55+
<meta property="twitter:url" content={Astro.url} />
56+
<meta property="twitter:title" content={title} />
57+
<meta property="twitter:description" content={description} />
58+
<meta property="twitter:image" content={new URL(image, Astro.url)} />
59+
60+
<!-- RSS Auto-discovery -->
61+
<link
62+
rel="alternate"
63+
type="application/rss+xml"
64+
title="Apps for Linux RSS Feed"
65+
href={new URL('rss.xml', Astro.site)}
66+
/>

src/components/BlogCard.astro

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
import { SITE_BASE } from '@/consts'
3+
import FormattedDate from './FormattedDate.astro'
4+
import type { ImageMetadata } from 'astro'
5+
import { Image } from 'astro:assets'
6+
7+
const { post } = Astro.props
8+
9+
const blogImages = import.meta.glob<{ default: ImageMetadata }>(
10+
'/src/assets/blogimages/**/*.{jpeg,jpg,png,gif}'
11+
)
12+
13+
const coverImagePath = `/src/assets/blogimages/${post.slug}/cover.jpg`
14+
---
15+
16+
<a
17+
href={`${SITE_BASE}/blog/${post.slug}/`}
18+
class="flex flex-col gap-4 pb-8 h-full group transition-colors"
19+
>
20+
{
21+
blogImages[coverImagePath] && (
22+
<Image
23+
src={blogImages[coverImagePath]()}
24+
alt={post.data.title}
25+
class="object-cover rounded-xl aspect-video"
26+
/>
27+
)
28+
}
29+
<h4
30+
class="text-2xl font-semibold group-hover:text-primary dark:group-hover:text-primary-dark group-hover:underline transition-colors"
31+
>
32+
{post.data.title}
33+
</h4>
34+
<p class="line-clamp-3 opacity-80">
35+
{post.data.description}
36+
</p>
37+
<p class="uppercase text-sm mt-auto tracking-tight">
38+
<FormattedDate date={post.data.pubDate} />
39+
</p>
40+
</a>

src/components/Card.astro

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
const { title } = Astro.props
3+
const { class: className } = Astro.props
4+
---
5+
6+
<div class:list={['rounded-lg p-6 py-12 pt-16 md:p-14', className]}>
7+
<h2 class="font-title text-[42px] leading-tight pb-5 z-10">{title}</h2>
8+
<div class="flex flex-col gap-4">
9+
<slot />
10+
</div>
11+
</div>

src/components/Footer.astro

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
import { SITE_BASE, SocialLinks, WebsiteLinks } from '../consts'
3+
import LogoSVG from '../assets/logo.svg'
4+
import { Icon } from 'astro-icon/components'
5+
6+
const today = new Date()
7+
---
8+
9+
<footer>
10+
<div
11+
class="app-container grid md:grid-cols-3 gap-12 py-16 border-t-2 border-foreground/50 dark:border-foreground-dark/50"
12+
>
13+
<div>
14+
<a href={`${SITE_BASE}/`} class="block w-fit" aria-label="Home">
15+
<LogoSVG height={36} width={36} />
16+
</a>
17+
</div>
18+
<div>
19+
<h3 class="font-semibold text-2xl">Links</h3>
20+
<div class="flex flex-wrap gap-4 mt-4">
21+
{
22+
WebsiteLinks.filter((link) => link.name !== 'Home').map((link) => (
23+
<a
24+
class="text-lg"
25+
href={link.url}
26+
target="_blank"
27+
rel="noopener noreferrer"
28+
>
29+
{link.name}
30+
</a>
31+
))
32+
}
33+
<a
34+
href="rss.xml"
35+
target="_blank"
36+
rel="noopener noreferrer"
37+
class="text-lg"
38+
aria-label="RSS Feed"
39+
>
40+
<Icon name="mdi:rss" class="w-6 h-6" />
41+
</a>
42+
</div>
43+
</div>
44+
<div>
45+
<h3 class="font-semibold text-2xl">Socials</h3>
46+
<div class="flex flex-wrap gap-4 mt-4">
47+
{
48+
SocialLinks.map((link) => (
49+
<a
50+
class="text-lg"
51+
href={link.url}
52+
target="_blank"
53+
rel="noopener noreferrer"
54+
>
55+
{link.name}
56+
</a>
57+
))
58+
}
59+
</div>
60+
</div>
61+
</div>
62+
<div class="app-container flex justify-between items-center pb-6 text-center">
63+
<p class="text-left md:text-center w-full">
64+
&copy; {today.getFullYear()} Source code available on <a
65+
class="underline"
66+
href="https://github.com/yashjawale/saral-theme-astro"
67+
target="_blank">GitHub</a
68+
>
69+
</p>
70+
</div>
71+
</footer>

src/components/FormattedDate.astro

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
interface Props {
3+
date: Date
4+
}
5+
6+
const { date } = Astro.props
7+
import dayjs from 'dayjs'
8+
---
9+
10+
<time datetime={date.toISOString()}>
11+
{dayjs(date).format('D MMM YYYY')}
12+
</time>

src/components/Navbar.astro

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
---
2+
import NavbarLink from './NavbarLink.astro'
3+
import { Icon } from 'astro-icon/components'
4+
import ThemeSelector from './ThemeSelector.astro'
5+
import { SITE_BASE, WebsiteLinks } from '../consts'
6+
import LogoSVG from '../assets/logo.svg'
7+
---
8+
9+
<header class="fixed top-0 w-full z-40 bg-background dark:bg-background-dark">
10+
<nav class="">
11+
<div class="app-container flex justify-between items-center py-5">
12+
<a href={`${SITE_BASE}/`} aria-label="Home">
13+
<LogoSVG height={36} width={36} />
14+
</a>
15+
<div class="gap-8 hidden md:flex items-center">
16+
{
17+
WebsiteLinks.map((link) => (
18+
<NavbarLink
19+
class="text-lg"
20+
href={`${SITE_BASE}/${link.url}`}
21+
target={link.url.charAt(0) === 'h' ? '_blank' : '_self'}
22+
>
23+
{link.name}
24+
</NavbarLink>
25+
))
26+
}
27+
<ThemeSelector />
28+
</div>
29+
<button
30+
class="md:hidden cursor-pointer"
31+
id="nav-open-btn"
32+
aria-label="Open navigation menu"
33+
title="Open navigation menu"
34+
>
35+
<Icon aria-hidden name="mdi:menu" size={32} />
36+
</button>
37+
38+
<!-- Mobile nav -->
39+
<div
40+
class="translate-x-full md:hidden fixed top-0 right-0 bg-background dark:bg-background-dark h-screen w-5/6 px-8 py-6 transition-transform z-40 border-l-[1px] border-background"
41+
id="mobile-menu"
42+
>
43+
<button
44+
id="nav-close-btn"
45+
class="ml-auto block cursor-pointer"
46+
aria-label="Close navigation menu"
47+
title="Close navigation menu"
48+
>
49+
<Icon aria-hidden name="mdi:close" size={32} />
50+
</button>
51+
<div class="flex flex-col h-full items-start gap-8 pt-16">
52+
{
53+
WebsiteLinks.map((link) => (
54+
<NavbarLink
55+
class="text-4xl font-light"
56+
href={`${SITE_BASE}/${link.url}`}
57+
>
58+
{link.name}
59+
</NavbarLink>
60+
))
61+
}
62+
<ThemeSelector class:list="mt-4" />
63+
</div>
64+
</div>
65+
</div>
66+
</nav>
67+
68+
<script>
69+
function toggleNav() {
70+
document
71+
.querySelector('#mobile-menu')
72+
?.classList.toggle('mobile-nav-open')
73+
}
74+
75+
document
76+
.querySelector('#nav-open-btn')
77+
?.addEventListener('click', toggleNav)
78+
document
79+
.querySelector('#nav-close-btn')
80+
?.addEventListener('click', toggleNav)
81+
</script>
82+
</header>
83+
84+
<script is:inline>
85+
function reComputeTheme() {
86+
if (
87+
localStorage.theme === 'dark' ||
88+
(!('theme' in localStorage) &&
89+
window.matchMedia('(prefers-color-scheme: dark)').matches)
90+
) {
91+
document.documentElement.classList.add('dark')
92+
} else {
93+
document.documentElement.classList.remove('dark')
94+
}
95+
96+
const theme = localStorage.theme || 'auto'
97+
document
98+
.querySelectorAll(`[data-theme="theme-${theme}"]`)
99+
.forEach((el) => el.classList.add('active'))
100+
}
101+
102+
function addThemeEventListeners() {
103+
themeButtons = document.querySelectorAll('.theme-button')
104+
themeButtons.forEach((button) => {
105+
button.addEventListener('click', () => {
106+
themeButtons.forEach((btn) => btn.classList.remove('active'))
107+
button.classList.add('active')
108+
if (button.dataset.theme === 'theme-auto') {
109+
localStorage.removeItem('theme')
110+
reComputeTheme()
111+
} else {
112+
localStorage.theme = button.dataset.theme.replace('theme-', '')
113+
document.documentElement.classList.toggle(
114+
'dark',
115+
button.dataset.theme === 'theme-dark'
116+
)
117+
}
118+
})
119+
})
120+
}
121+
122+
// Set active class on OS theme change
123+
window
124+
.matchMedia('(prefers-color-scheme: dark)')
125+
.addEventListener('change', (_e) => {
126+
reComputeTheme()
127+
})
128+
129+
// run recomputeTheme when screen crosses 768px
130+
window.matchMedia('(min-width: 768px)').addEventListener('change', (_e) => {
131+
addThemeEventListeners()
132+
reComputeTheme()
133+
})
134+
135+
reComputeTheme()
136+
137+
// Run on page load
138+
window.addEventListener('DOMContentLoaded', () => {
139+
addThemeEventListeners()
140+
reComputeTheme()
141+
})
142+
</script>

src/components/NavbarLink.astro

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
import type { HTMLAttributes } from 'astro/types'
3+
4+
type Props = HTMLAttributes<'a'>
5+
6+
const { href, class: className, ...props } = Astro.props
7+
8+
const { pathname } = Astro.url
9+
const isActive =
10+
href === pathname ||
11+
href === pathname.replace(/\/$/, '') ||
12+
(pathname.startsWith('/blog') && href === '/blog')
13+
---
14+
15+
<a href={href} class:list={[className, { active: isActive }]} {...props}>
16+
<slot />
17+
</a>
18+
<style>
19+
@reference "../styles/global.css";
20+
21+
a {
22+
@apply underline-offset-4 hover:underline relative decoration-2;
23+
}
24+
a.active {
25+
@apply underline text-primary dark:text-primary-dark;
26+
}
27+
</style>

0 commit comments

Comments
 (0)