feat!: update src dir

This commit is contained in:
kaivanwong 2024-05-11 17:19:40 +08:00
parent fd87c91ab3
commit aa011faa54
37 changed files with 4433 additions and 3451 deletions

View file

@ -6,7 +6,7 @@ Vitesse theme for Astro blog, supports Vue and UnoCSS.
## Preview ## Preview
![Preview Image](./preview.jpg) ![Preview Image](https://astro-theme-vitesse.netlify.app/preview.jpg)
## Quick Start ## Quick Start

6409
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -12,27 +12,30 @@
"release": "bumpp" "release": "bumpp"
}, },
"dependencies": { "dependencies": {
"@astrojs/mdx": "^2.2.0", "@astrojs/mdx": "^2.3.1",
"@astrojs/rss": "^4.0.5", "@astrojs/rss": "^4.0.5",
"@astrojs/sitemap": "^3.1.1", "@astrojs/sitemap": "^3.1.4",
"@astrojs/vue": "^4.0.8", "@astrojs/vue": "^4.1.0",
"@unocss/reset": "^0.58.5", "@unocss/reset": "^0.58.9",
"astro": "^4.5.2", "astro": "^4.7.0",
"marked": "^11.2.0", "nprogress": "^0.2.0",
"unocss": "^0.58.5", "unocss": "^0.58.9",
"vue": "^3.4.21" "vue": "^3.4.25"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^2.8.1", "@antfu/eslint-config": "^2.16.0",
"@iconify/json": "^2.2.191", "@iconify/json": "^2.2.204",
"@types/lodash-es": "^4.17.12",
"@types/nprogress": "^0.2.3",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^10.9.0",
"bumpp": "^9.4.0", "bumpp": "^9.4.0",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-astro": "^0.31.4", "eslint-plugin-astro": "^0.31.4",
"eslint-plugin-format": "^0.1.0", "eslint-plugin-format": "^0.1.1",
"lint-staged": "^15.2.2", "lint-staged": "^15.2.2",
"lodash-es": "^4.17.21",
"prettier-plugin-astro": "^0.13.0", "prettier-plugin-astro": "^0.13.0",
"simple-git-hooks": "^2.10.0" "simple-git-hooks": "^2.11.1"
}, },
"simple-git-hooks": { "simple-git-hooks": {
"pre-commit": "npx lint-staged" "pre-commit": "npx lint-staged"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View file

@ -1,177 +1,29 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="300px" height="300px"><svg version="1.1" id="SvgjsSvg1001" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="300px" height="300px" viewBox="0 0 300 300" enable-background="new 0 0 300 300" xml:space="preserve"> <image id="SvgjsImage1000" width="300" height="300" x="0" y="0" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAAAAABcFtGpAAAABGdBTUEAALGPC/xhBQAAACBjSFJN <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElN <g>
RQfoAQgCCSuw7aR3AAAlfElEQVR42u2deZQdx3Xev1vV3W+dN/sMMDPAYBmsxMJFpAiS4iJRlkRS <path
mylRVrRYckhvOXZsxY68JF4kx3bi+MQnjBVHkn0c80iRSInaKJmkCIkiRQgkSALEvi8DzL7PW7u7 d="M47.7 107.1c-5.5-5-7.2-15.7-4.9-23.4 4 4.9 9.6 6.4 15.4 7.3 8.9 1.3 17.6.8 25.9-3.2l2.8-1.7a18 18 0 0 1-7.2 20l-5.5 3.8c-5.6 3.8-7.2 8.2-5 14.7l.2.7a14 14 0 0 1-6.6-5.6 15.8 15.8 0 0 1-2.6-8.6c0-1.5 0-3-.2-4.5-.5-3.7-2.2-5.3-5.5-5.4-3.3-.1-5.9 2-6.6 5.2l-.2.7Z" />
qm7+6DfAYJ9+7w1IQv3xHPAcYKb69e9V3bp169YtYkSaq8Tr/QHeTIpghVAEK4QiWCEUwQqhCFYI <path
RbBCKIIVQhGsEIpghVAEK4QiWCEUwQqhCFYIRbBCKIIVQhGsEIpghVAEK4QiWCEUwQqhCFYIRbBC d="M16 82.4s16.5-8 33-8l12.4-38.3c.5-2 1.8-3.2 3.3-3.2 1.6 0 3 1.3 3.4 3.2l12.4 38.3c19.6 0 33 8 33 8l-28-76c-.8-2.3-2.2-3.7-4-3.7H48c-1.8 0-3.1 1.4-4 3.7l-28 76Z" />
KIIVQhGsEIpghVAEK4QiWCEUwQqhCFYIRbBCKIIVQhGsEIpghVAEK4QiWCEUwQqhCFYIRbBCKIIV </g>
QhGsEIpghVAEK4QiWCEUwQqhCFYIRbBCKIIVQhGsEIpghVAEK4QiWCEUwQqhCFYIRbBCKIIVQhGs <path fill="url(#a)"
EIpghVAEK4QiWCEUwQqhCFYIRbBCKIIVQhGsEIpghVAEK4QiWCEUwQqhCFYIRbBCKIIVQhGsEIpg d="M47.7 107.1c-5.5-5-7.2-15.7-4.9-23.4 4 4.9 9.6 6.4 15.4 7.3 8.9 1.3 17.6.8 25.9-3.2l2.8-1.7a18 18 0 0 1-7.2 20l-5.5 3.8c-5.6 3.8-7.2 8.2-5 14.7l.2.7a14 14 0 0 1-6.6-5.6 15.8 15.8 0 0 1-2.6-8.6c0-1.5 0-3-.2-4.5-.5-3.7-2.2-5.3-5.5-5.4-3.3-.1-5.9 2-6.6 5.2l-.2.7Z" />
hVAEK4QiWCEUwQqhnydYjCpLlP+8wGIABKqOlvV6v8UVkhGKJJhlNY38nMDiMXfH5qb3rkxV1UqV
HfNNIZ+we/DFvyvZix++U1sMqrShn4eeJb3vbX1u0Mnz0S/0dADgSmn9HPQs79TWP+kvtd/37Um2 <defs>
Vj2ygbniSe3qnw3Z7P42hBj+p3FN8fzvHKvila9+WOTseV5RIiYBo4o/+37FFuvnwWbx9M7cBFsM <linearGradient id="a" x1="64.7" x2="77.4" y1="119.2" y2="77.4" gradientUnits="userSpaceOnUse">
ELwi7ywmGBXa+Ku/Z+W+8JxrWBkGLJ1N/OSoQaWe/NXfs+ipMSZNAIm4u9gZLmqr0i5y9fcsX5sG <stop stop-color="#D83333" />
wcxgo5ziiDpObExlXevqhzXaL5YkmZmBkspPeLuVrrSpq34YeiOsXtMCDCl1qSTdhAWqcEa82nsW <stop offset="1" stop-color="#F041FF" />
O1mfNIEIMpWW0gipK50Mr35YU9/tD+JYaTPpEmKN7bakChc8V/0wRA4AMZOrTBGwEkwVr6Sv+p4l </linearGradient>
0w6xYWYiAKQn6ip34a96WJNHHACgwBHl5NrlquLGrvZhmHv5iFIAmAQAsNdUV3GE5qqHZR+Y9AM4 </defs>
TAQQRLryV766hyHj1FaPBAGCGGDYpt2pvLmru2cR6icEgQCGZoGm4icfiFXe3BunZxljWGugwnXb
BcWm9yQxkADAgL2w7vkDlbrveCP1LBrL1Y2Z+maWsvIthXPbxG4/Z2YsOvuHvFU9VXwXbxBYDNLZ <style>
LV56sNTWs6axZs2q6UYfgHEBAGTSyyemqmjujQGL3UMm9spjp9pveXa4fvHnl+QS2ql8hj8jufUv g {
h8j2oIgJLOB7i390R+xN7Tpow3zk66PN208lXymaVd797172zoW1GIqmtIq6J3JSgQkgFvmR2345 fill: #000;
+eb2sySdGD6yOXVwKEPjDh8qTP3tNROfbqlBwyKxt3/shoFeiLL/zm/LxuIVh7MqgRV8M7UYJTNy }
/U/nS8oFcTxPZkAmvRfEA11GVvsMVnsnve3CQEoPAIw18OllRlTcbAWwaNafNRKnegvSUZOSjfBB
rjs6PPKxNVU/w1ACmWkmoXzJADUV6t4er6K9Cvws1zU86itGxbsk56KiR05IJ7NIkOEWG34DHC/7 @media (prefers-color-scheme: dark) {
/Je2FattmETGuiEhICEYIGk4NwJQxbvw4WEN/dd/HXvyf7nQqFX3MrktrqL8IcWGCwbIwXUHpvZ8 g {
C6bKhpnXdBxJkFHB50y6etlSquJjhx2G/MTWbz35se//8taR7usSNUEFyGeGtc9xlwAukEAevvFp fill: #FFF;
qP+dd1a7vjCNDYUpWY7JiO6RgaOqGjsYDpb2nv7Siynz6Gs7DS1573+wawNr6tmBoiM5kwMAYkJC }
WsrPW5/D2+JV2Xi5/ZB/ymICgwC9m0TGqaa9cN9d7pHfe23BqZO99X2j08ef+0GxNkZr9DASxi8V }
QcK2pWRwXbo4JRKvfO5FXd1AL3xtkIJMIQYJIjlWrOYTh4Glp/7oj4+M5jJewYFOJPduVbUwWowj </style>
vb6vPVbMTIAB1OSgEX4vdn+z163m5bSwxwXqJcrArLpjvVcKVuErP5qIe0MxW5kl1LMuvmiXX3mI </svg>
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"></image>
</svg><style>@media (prefers-color-scheme: light) { :root { filter: contrast(1) brightness(1); } }
@media (prefers-color-scheme: dark) { :root { filter: invert(100%); } }
</style></svg>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,8 +1,9 @@
--- ---
import siteConfig from '../site-config' import siteConfig from '@/site-config'
import '../styles/markdown.css' import '@/styles/global.css'
import '@/styles/dot.css'
export type Props = { interface Props {
title?: string title?: string
description?: string description?: string
image?: { src: string; alt?: string } image?: { src: string; alt?: string }
@ -14,25 +15,22 @@ const {
image = siteConfig.image, image = siteConfig.image,
pageType = 'website', pageType = 'website',
} = Astro.props } = Astro.props
const title = [Astro.props.title, siteConfig.title].filter(Boolean).join(' | ') const title = [Astro.props.title, siteConfig.title].filter(Boolean).join(' | ')
const resolvedImage = image?.src const resolvedImage = image?.src
? { ? {
src: new URL(image.src, Astro.site).toString(), src: new URL(image.src, Astro.site).toString(),
alt: image.alt, alt: image.alt,
} }
: undefined : undefined
const canonicalURL = new URL(Astro.request.url, Astro.site) const canonicalURL = new URL(Astro.request.url, Astro.site)
/**
* Enforce some standard canonical URL formatting across the site.
*/
function formatCanonicalURL(url: string | URL) { function formatCanonicalURL(url: string | URL) {
const path = url.toString() const path = url.toString()
const hasQueryParams = path.includes('?') const hasQueryParams = path.includes('?')
// If there are query params, make sure the URL has no trailing slash
if (hasQueryParams) path.replace(/\/?$/, '') if (hasQueryParams) path.replace(/\/?$/, '')
// otherwise, canonical URL always has a trailing slash
return path.replace(/\/?$/, hasQueryParams ? '' : '/') return path.replace(/\/?$/, hasQueryParams ? '' : '/')
} }
--- ---
@ -43,14 +41,6 @@ function formatCanonicalURL(url: string | URL) {
<title>{title}</title> <title>{title}</title>
<meta name="generator" content={Astro.generator} /> <meta name="generator" content={Astro.generator} />
<!-- Fonts -->
<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=Inter:wght@400..700&family=Newsreader:ital,opsz,wght@0,6..72,400..700;1,6..72,400..700&display=swap"
rel="stylesheet"
/>
<!-- Low Priority Global Metadata --> <!-- Low Priority Global Metadata -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="sitemap" href="/sitemap-index.xml" /> <link rel="sitemap" href="/sitemap-index.xml" />
@ -72,7 +62,7 @@ function formatCanonicalURL(url: string | URL) {
) )
} }
<!-- X/Twitter --> <!-- X / Twitter -->
<meta property="twitter:card" content="summary_large_image" /> <meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={formatCanonicalURL(canonicalURL)} /> <meta property="twitter:url" content={formatCanonicalURL(canonicalURL)} />
<meta property="twitter:title" content={title} /> <meta property="twitter:title" content={title} />
@ -87,3 +77,15 @@ function formatCanonicalURL(url: string | URL) {
<meta name="twitter:image:alt" content={resolvedImage?.alt} /> <meta name="twitter:image:alt" content={resolvedImage?.alt} />
) )
} }
<script>
import nprogress from 'nprogress'
document.addEventListener('astro:before-preparation', () => {
nprogress.start()
})
document.addEventListener('astro:page-load', () => {
nprogress.done()
})
</script>

View file

@ -1,17 +1,21 @@
<script lang="ts" setup> <script lang="ts" setup>
import siteConfig from '../site-config' import siteConfig from '@/site-config'
import { getLinkTarget } from '@/utils/link'
</script> </script>
<template> <template>
<footer class="w-full px-6 pt-4 pb-12 max-w-3xl mx-auto opacity-60"> <footer class="w-full mt-18 pt-6 pb-8 max-w-3xl text-sm flex flex-col gap-4 border-main border-t !border-op-50">
<div class="mb-6 flex flex-wrap justify-center gap-x-4 gap-y-2"> <div v-if="siteConfig.footer.navLinks && siteConfig.footer.navLinks.length > 0" class="flex flex-wrap gap-4">
<a v-for="link in siteConfig.footerNavLinks" :key="link.text" class="nav-link" :href="link.href"> <template v-for="(link, index) in siteConfig.footer.navLinks" :key="link.text">
<a :aria-label="`${link.text}`" :target="getLinkTarget(link.href)" class="nav-link" :href="link.href">
{{ link.text }} {{ link.text }}
</a> </a>
<span v-if="index < siteConfig.footer.navLinks.length - 1"> / </span>
</template>
</div> </div>
<div class="flex flex-wrap justify-center text-dark dark:text-white"> <div class="flex text-dark dark:text-white">
<span class="opacity-70">2023-PRESENT ©</span> <a nav-link href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a>
<a class="!nav-link opacity-100 ml-1" href="/">{{ siteConfig.author }}</a> <span op-70>&nbsp;&nbsp;&copy;&nbsp;&nbsp;{{ new Date().getFullYear() }}&nbsp;&nbsp;{{ siteConfig.author }}.</span>
</div> </div>
</footer> </footer>
</template> </template>

View file

@ -1,48 +1,150 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watchEffect } from 'vue' import { useWindowScroll } from '@vueuse/core'
import { onClickOutside, useWindowSize } from '@vueuse/core' import { computed, onMounted, ref, unref } from 'vue'
import siteConfig from '../site-config'
import ThemeToggle from './ThemeToggle.vue' import ThemeToggle from './ThemeToggle.vue'
import siteConfig from '@/site-config'
import { getLinkTarget } from '@/utils/link'
const navLinks = siteConfig.headerNavLinks || [] const navLinks = siteConfig.header.navLinks || []
const menuRef = ref(null) const socialLinks = computed(() => {
return siteConfig.socialLinks.filter((link: Record<string, any>) => {
if (link.header && typeof link.header === 'boolean') {
return link
}
else if (link.header && typeof link.header === 'string') {
link.icon = link.header.includes('i-') ? link.header : link.icon
return link
}
else {
return false
}
})
})
const menu = ref(false) const { y: scroll } = useWindowScroll()
function toggleMenu() { const oldScroll = ref(unref(scroll))
menu.value = !menu.value
onMounted(() => {
const navMask = document.querySelector('.nav-drawer-mask') as HTMLElement
navMask?.addEventListener('touchmove', (event) => {
event.preventDefault()
})
const headerEl = document.querySelector('#header') as HTMLElement
if (!headerEl)
return
if (document.documentElement.scrollTop > 100)
headerEl.classList.add('header-bg-blur')
window.addEventListener('scroll', () => {
if (scroll.value < 150) {
headerEl.classList.remove('header-hide')
return
}
if (scroll.value - oldScroll.value > 150) {
headerEl.classList.add('header-hide')
oldScroll.value = scroll.value
}
if (oldScroll.value - scroll.value > 150) {
headerEl.classList.remove('header-hide')
oldScroll.value = scroll.value
}
})
})
function toggleNavDrawer() {
const drawer = document.querySelector('.nav-drawer') as HTMLElement
const mask = document.querySelector('.nav-drawer-mask') as HTMLElement
if (!drawer || !mask)
return
if (drawer.style.transform === `translateX(0%)`) {
drawer.style.transform = `translateX(-100%)`
mask.style.display = `none`
}
else {
drawer.style.transform = `translateX(0%)`
mask.style.display = `block`
}
} }
const { width } = useWindowSize()
onClickOutside(menuRef, () => {
if (width.value < 640)
menu.value = false
})
watchEffect(() => {
if (width.value > 640)
menu.value = true
else
menu.value = false
})
</script> </script>
<template> <template>
<header text-lg max-w-3xl mx-auto h-18 px-6 flex justify-between items-center relative> <header
<nav id="header" :class="{ 'header-bg-blur': scroll > 20 }"
v-show="menu" ref="menuRef" flex flex-wrap gap-4 sm:gap-6 sm:position-initial absolute z-199 top-15 sm:flex-row class="!fixed bg-transparent z-899 w-screen h-20 px-6 flex justify-between items-center relative"
flex-col sm:p0 p-4 bg-main border-1 border-main sm:border-none >
<div class="flex items-center h-full">
<a href="/" mr-6 aria-label="Header Logo Image">
<img width="32" height="32" :src="siteConfig.header.logo.src" :alt="siteConfig.header.logo.alt">
</a>
<nav class="sm:flex hidden flex-wrap gap-x-6 position-initial flex-row">
<a
v-for="link in navLinks" :key="link.text" :aria-label="`${link.text}`" :target="getLinkTarget(link.href)"
nav-link :href="link.href"
> >
<a v-for="link in navLinks" :key="link.text" nav-link :href="link.href">
{{ link.text }} {{ link.text }}
</a> </a>
</nav> </nav>
<menu sm:hidden inline-block i-ri-menu-2-fill @click="toggleMenu" /> <div sm:hidden h-full flex items-center @click="toggleNavDrawer()">
<div flex gap-4 sm:gap-6> <menu i-ri-menu-2-fill />
<a nav-link href="rss.xml" i-ri-rss-line /> </div>
</div>
<div class="flex gap-x-6">
<a
v-for="link in socialLinks" :key="link.text" :aria-label="`${link.text}`" :class="link.icon" nav-link
:target="getLinkTarget(link.href)" :href="link.href"
/>
<a nav-link href="/rss.xml" i-ri-rss-line aria-label="RSS" />
<ThemeToggle /> <ThemeToggle />
</div> </div>
</header> </header>
<nav
class="nav-drawer sm:hidden"
>
<i i-ri-menu-2-fill />
<a
v-for="link in navLinks" :key="link.text" :aria-label="`${link.text}`" :target="getLinkTarget(link.href)"
nav-link :href="link.href" @click="toggleNavDrawer()"
>
{{ link.text }}
</a>
</nav>
<div class="nav-drawer-mask" @click="toggleNavDrawer()" />
</template> </template>
<style scoped>
.header-hide {
transform: translateY(-100%);
transition: transform 0.4s ease;
}
.header-bg-blur {
--at-apply: backdrop-blur-sm;
}
.nav-drawer {
transform: translateX(-100%);
--at-apply: box-border fixed h-screen z-999 left-0 top-0 min-w-32vw max-w-50vw
bg-main p-6 text-lg flex flex-col gap-5 transition-all;
}
.nav-drawer-mask {
display: none;
--at-apply: transition-all;
content: '';
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.5);
z-index: 998;
}
</style>

View file

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
interface Posts { interface Post {
id: string id: string
slug: string slug: string
body: string body: string
@ -9,7 +9,7 @@ interface Posts {
} }
withDefaults(defineProps<{ withDefaults(defineProps<{
list: Posts[] list: Post[]
}>(), { }>(), {
list: () => [], list: () => [],
}) })
@ -18,14 +18,14 @@ function getDate(date: string) {
return new Date(date).toISOString() return new Date(date).toISOString()
} }
function getHref(posts: Posts) { function getHref(post: Post) {
if (posts.data.redirect) if (post.data.redirect)
return posts.data.redirect return post.data.redirect
return `/posts/${posts.slug}` return `/posts/${post.slug}`
} }
function getTarget(posts: Posts) { function getTarget(post: Post) {
if (posts.data.redirect) if (post.data.redirect)
return '_blank' return '_blank'
return '_self' return '_self'
} }
@ -40,37 +40,36 @@ function getYear(date: Date | string | number) {
</script> </script>
<template> <template>
<ul> <ul sm:min-h-38 min-h-28 mb-18>
<template v-if="!list || list.length === 0"> <template v-if="!list || list.length === 0">
<div py2 opacity-50> <div my-12 opacity-50>
nothing here yet. nothing here yet.
</div> </div>
</template> </template>
<li v-for="(posts, index) in list " :key="posts.data.title" mb-6> <li v-for="(post, index) in list " :key="post.data.title" mb-8>
<div v-if="!isSameYear(posts.data.date, list[index - 1]?.data.date)" select-none relative h18 pointer-events-none> <div v-if="!isSameYear(post.data.date, list[index - 1]?.data.date)" select-none relative h18 pointer-events-none>
<span text-7em color-transparent font-bold text-stroke-2 text-stroke-hex-aaa op14 absolute top--0.2em> <span text-7em color-transparent font-bold text-stroke-2 text-stroke-hex-aaa op14 absolute top--0.2em>
{{ getYear(posts.data.date) }} {{ getYear(post.data.date) }}
</span> </span>
</div> </div>
<a text-lg lh-tight nav-link flex="~ col gap-2" :target="getTarget(posts)" :href="getHref(posts)"> <a text-lg lh-tight nav-link flex="~ col gap-2" :aria-label="post.data.title" :target="getTarget(post)" :href="getHref(post)">
<div flex="~ col md:row gap-2 md:items-center"> <div flex="~ col md:row gap-2 md:items-center">
<div flex="~ gap-2 items-center text-wrap"> <div flex="~ gap-2 items-center text-wrap">
<span lh-normal> <span lh-normal>
<i v-if="posts.data.draft" text-base vertical-mid i-ri-draft-line /> <i v-if="post.data.draft" text-base vertical-mid i-ri-draft-line />
{{ posts.data.title }} {{ post.data.title }}
</span> </span>
</div> </div>
<div opacity-50 text-sm ws-nowrap flex="~ gap-2 items-center"> <div opacity-50 text-sm ws-nowrap flex="~ gap-2 items-center">
<i v-if="posts.data.redirect" text-base i-ri-external-link-line /> <i v-if="post.data.redirect" text-base i-ri-external-link-line />
<i v-if="posts.data.recording || posts.data.video" text-base i-ri:film-line /> <i v-if="post.data.recording || post.data.video" text-base i-ri:film-line />
<time :datetime="getDate(posts.data.date)">{{ posts.data.date.split(',')[0] }}</time> <time v-if="post.data.date" :datetime="getDate(post.data.date)">{{ post.data.date.split(',')[0] }}</time>
<span v-if="posts.data.duration">· {{ posts.data.duration }}</span> <span v-if="post.data.duration">· {{ post.data.duration }}</span>
<span v-if="posts.data.tag">· {{ posts.data.tag }}</span> <span v-if="post.data.tag">· {{ post.data.tag }}</span>
<span v-if="posts.data.lang.includes('zh')">· 中文</span> <span v-if="post.data.lang && post.data.lang.includes('zh')">· 中文</span>
<span v-if="posts.data.link">· 中文</span>
</div> </div>
</div> </div>
<div opacity-50 text-sm>{{ posts.data.description }}</div> <div opacity-50 text-sm>{{ post.data.description }}</div>
</a> </a>
</li> </li>
</ul> </ul>

View file

@ -2,7 +2,7 @@
defineProps<{ defineProps<{
list: { list: {
text: string text: string
description: string description?: string
icon?: string icon?: string
href: string href: string
}[] }[]
@ -12,12 +12,12 @@ defineProps<{
<template> <template>
<ul grid="~ cols-1 sm:cols-2 gap-4"> <ul grid="~ cols-1 sm:cols-2 gap-4">
<template v-if="!list || list.length === 0"> <template v-if="!list || list.length === 0">
<div py2 opacity-50> <div py10 opacity-50 text-lg>
nothing here yet. nothing here yet.
</div> </div>
</template> </template>
<li v-for="project in list" :key="project.text" container-link w-full flex items-center> <li v-for="project in list" :key="project.text" container-link w-full flex items-center rd-2>
<a flex items-center target="_blank" :href="project.href"> <a flex items-center target="_blank" :href="project.href" :aria-label="project.text">
<div ml-2 mr-4 pt-2> <div ml-2 mr-4 pt-2>
<i text-4xl inline-block :class="project.icon || 'i-carbon-unknown'" /> <i text-4xl inline-block :class="project.icon || 'i-carbon-unknown'" />
</div> </div>

View file

@ -1,18 +0,0 @@
<script lang="ts" setup>
import siteConfig from '../site-config'
defineProps<{
pathname: string
}>()
</script>
<template>
<div flex="~ col gap-2 sm:row sm:gap-4 wrap" mb-8>
<a
v-for="nav in siteConfig.pageNavLinks" :key="nav.text" nav-link text-3xl font-bold
:class="pathname.includes(nav.href) ? 'opacity-80' : 'opacity-30 hover:opacity-50'" :href="nav.href"
>
{{ nav.text }}
</a>
</div>
</template>

View file

@ -1,55 +0,0 @@
<script lang="ts" setup>
import { useWindowScroll } from '@vueuse/core'
withDefaults(defineProps<{
showShare?: boolean
showBack?: boolean
url?: URL
}>(), {
showShare: false,
showBack: true,
})
const shareLinks = [
{
text: 'Twitter',
icon: 'i-simple-icons-x',
href: 'https://twitter.com/intent/tweet?url=',
},
{
text: 'Mail',
href: 'mailto:?subject=See%20this%20post&body=',
},
]
const { y: scroll } = useWindowScroll()
function toTop() {
window.scrollTo({
top: 0,
behavior: 'smooth',
})
}
</script>
<template>
<div sm:flex="~ flex-row items-start justify-between" w-full font-mono opacity-50 text-main>
<div>
<div v-if="showShare" flex="~ gap-2 items-center flex-wrap" mb-2>
<i i-ri-arrow-right-s-line />
<span>share to</span>
<a v-for="link in shareLinks" :key="link.text" prose-link lh-tight :href="link.href + url">
<i v-if="link.icon" text-2.2 :class="link.icon" mr-0.8 />{{ link.text }}
</a>
</div>
<div v-if="showBack" flex="~ gap-2 items-center">
<i i-ri-arrow-right-s-line />
<a prose-link href="javascript:history.back(-1)">cd ..</a>
</div>
</div>
<div v-show="scroll > 300" cursor-pointer sm:m-0 mt-6 flex="~ gap-2 items-center" sm:gap-2>
<i vertical-mid i-ri-arrow-up-line />
<span prose-link @click="toTop()">Scroll to top</span>
</div>
</div>
</template>

View file

@ -2,9 +2,10 @@
import { useDark, useToggle } from '@vueuse/core' import { useDark, useToggle } from '@vueuse/core'
const isDark = useDark() const isDark = useDark()
const toggleDark = useToggle(isDark) const toggleDark = useToggle(isDark)
</script> </script>
<template> <template>
<i nav-link dark:i-ri-moon-line i-ri-sun-line @click="toggleDark()" /> <button :aria-label="isDark ? 'Dark Theme' : 'Light Theme'" nav-link dark:i-ri-moon-line i-ri-sun-line @click="toggleDark()" />
</template> </template>

View file

@ -1,32 +0,0 @@
---
title: "Responsive User Interfaces with Vue 3"
description: Creating Dynamic and Responsive User Interfaces with Vue 3
duration: "12min"
date: "2023-08-11"
---
In the realm of modern web development, creating dynamic and responsive user interfaces is paramount to delivering engaging and immersive web experiences. With the advent of Vue 3, developers now have a powerful toolkit at their disposal to craft dynamic, reactive, and high-performing user interfaces like never before. In this article, we'll explore the key features of Vue 3 and demonstrate how to leverage its capabilities to create dynamic and responsive user interfaces.
## Understanding Vue 3: A Paradigm Shift
Vue 3 represents a significant evolution of the Vue.js framework, introducing several groundbreaking features and performance improvements. One of the most notable enhancements is the Composition API, which provides a more flexible and scalable way to organize and reuse code logic within Vue components. With the Composition API, developers can encapsulate related functionality into reusable composition functions, leading to cleaner and more maintainable codebases.
## Reactivity at its Core
At the heart of Vue 3 lies its robust reactivity system, which enables seamless and efficient data binding between the underlying data model and the user interface. Vue 3's reactivity system is powered by the Composition API and the new reactive and ref APIs, which allow developers to create reactive data objects and references that automatically update the user interface whenever their values change. This reactive paradigm simplifies state management and enables developers to build highly interactive and responsive user interfaces with minimal effort.
## Leveraging Vue 3's Composition API
The Composition API is a game-changer for Vue.js development, offering a more flexible and intuitive way to organize and reuse code logic within components. Unlike the Options API, which relies on fixed lifecycle hooks and options objects, the Composition API allows developers to define component logic in a more granular and composable manner using standalone functions called composition functions.
By breaking down component logic into smaller, reusable composition functions, developers can better encapsulate and manage complex functionality within their components. This promotes code reuse, improves readability, and facilitates collaboration among team members. Additionally, the Composition API enables better separation of concerns and allows developers to extract common logic into reusable mixins, further enhancing code maintainability and scalability.
## Building Dynamic User Interfaces
With Vue 3's reactivity system and Composition API in hand, developers can easily build dynamic user interfaces that respond to user interactions and changes in data. By leveraging reactive data objects and composition functions, developers can create reactive components that automatically update in response to changes in their underlying data, providing a seamless and intuitive user experience.
Vue 3's enhanced performance optimizations, including better tree shaking and optimized reactivity tracking, further contribute to the creation of fast and responsive user interfaces. With these improvements, Vue 3 enables developers to build web applications that not only look great but also perform exceptionally well across a wide range of devices and screen sizes.
## Conclusion
In conclusion, Vue 3 represents a significant milestone in the evolution of Vue.js, offering developers a powerful and intuitive framework for building dynamic and responsive user interfaces. By leveraging Vue 3's reactivity system and Composition API, developers can create highly interactive and performant web applications that push the boundaries of what's possible on the web. Whether you're a seasoned Vue.js developer or just getting started, Vue 3 provides the tools and capabilities you need to bring your web projects to life in exciting new ways.

View file

@ -1,31 +1,5 @@
import { defineCollection, z } from 'astro:content' import { defineCollection, z } from 'astro:content'
const postsSchema = z.object({
title: z.string(),
description: z.string().optional(),
duration: z.string().optional(),
image: z
.object({
src: z.string(),
alt: z.string().optional(),
})
.optional(),
date: z
.string()
.or(z.date())
.transform((val: string | number | Date) => new Date(val).toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric',
})),
draft: z.boolean().optional().default(false),
lang: z.string().optional().default('en-US'),
tag: z.string().optional(),
redirect: z.string().optional(),
video: z.boolean().optional(),
recording: z.boolean().optional(),
})
const pages = defineCollection({ const pages = defineCollection({
schema: z.object({ schema: z.object({
title: z.string(), title: z.string(),
@ -33,22 +7,35 @@ const pages = defineCollection({
image: z image: z
.object({ .object({
src: z.string(), src: z.string(),
alt: z.string().optional(), alt: z.string(),
}) }).optional(),
.optional(),
}), }),
}) })
const blog = defineCollection({ const blog = defineCollection({
schema: postsSchema, schema: z.object({
title: z.string(),
description: z.string().optional(),
duration: z.string().optional(),
image: z
.object({
src: z.string(),
alt: z.string(),
}).optional(),
date: z
.string()
.or(z.date())
.transform((val: string | number | Date) => new Date(val).toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric',
})),
draft: z.boolean().default(false).optional(),
lang: z.string().default('en-US').optional(),
tag: z.string().optional().optional(),
redirect: z.string().optional(),
video: z.boolean().default(false).optional(),
}),
}) })
const notes = defineCollection({ export const collections = { pages, blog }
schema: postsSchema,
})
const reading = defineCollection({
schema: postsSchema,
})
export const collections = { pages, blog, notes, reading }

View file

@ -1,75 +0,0 @@
---
title: Notes on Using Astro
description: A New Experience in Building Modern Static Websites.
duration: "5min"
date: "2024-03-02"
---
In the modern web development field, building fast, reliable, and easy-to-maintain static websites is one of the common challenges for developers. Traditional static site generators often face limitations such as lack of support for modern JavaScript frameworks, slow build times, or complex configuration files. However, with the emergence of Astro, these issues are becoming a thing of the past. Below are some of my experiences and notes from using Astro:
## Simplified Development Experience
Astro provides a simple yet powerful development experience, making it easy to build static websites. With its concise syntax and intuitive API, developers can quickly create highly optimized static websites without delving into complex configuration files or build pipelines. Astro's design philosophy is "out of the box," providing developers with a set of ready-to-use tools and best practices to accelerate the development process and reduce the learning curve.
Example Code:
```astro
---
import { React, Astro } from 'astro'
const Index = () => (
<Astro>
<h1>Hello, Astro!</h1>
<p>Welcome to my Astro-powered website.</p>
</Astro>
)
export default Index
---
```
## Support for Multiple Frontend Frameworks
One notable feature is that Astro supports multiple frontend frameworks, including React, Vue.js, Svelte, and more. This means developers can choose their preferred framework to build websites without worrying about compatibility or performance issues. Astro provides dedicated plugins for integration with each framework, ensuring developers can leverage the full capabilities of the framework while benefiting from the advantages of Astro.
Example Code:
```astro
---
import { Vue, Astro } from 'astro'
const Index = () => (
<Astro>
<Vue>
<template>
<h1>Hello, Astro!</h1>
<p>Welcome to my Astro-powered website.</p>
</template>
</Vue>
</Astro>
)
export default Index
---
```
## Instant Reload and Pre-rendering
Astro provides features like instant reload and pre-rendering, making the development process more efficient. Instant reload reflects code changes in real-time and updates them immediately in the browser, speeding up the development and debugging process. Pre-rendering generates static HTML during the build process, improving website performance and search engine optimization (SEO), resulting in better loading speed and user experience.
Example Code:
```astro
---
import { React, Astro } from 'astro'
const Index = () => (
<Astro>
<h1>Hello, Astro!</h1>
<p>Welcome to my Astro-powered website.</p>
</Astro>
)
export default Index
---
```

View file

@ -1,19 +0,0 @@
---
title: Unleash the Power of Web Development with Vitesse Theme for Astro
---
![About Image](/about.jpg)
In the ever-evolving landscape of web development, staying ahead requires harnessing cutting-edge tools and frameworks that streamline workflows and empower developers to unleash their creativity. Enter Vitesse Theme for Astro: a game-changing template that combines the best of Vue.js, Unocss, and modern design principles to revolutionize frontend development.
At its core, Vitesse Theme for Astro is all about efficiency without sacrificing aesthetics. Inspired by the sleek design of antfu.me, this template offers a visually stunning experience while ensuring seamless integration with Vue.js and Unocss. Whether you're building a personal blog, a portfolio, or a business website, Vitesse Theme for Astro provides the flexibility and versatility to bring your vision to life.
One of the standout features of Vitesse Theme for Astro is its robust support for Vue.js. With Vue.js, developers can build dynamic and interactive web applications with ease. From reactive components to state management, Vue.js empowers developers to create sophisticated user interfaces that delight users and elevate the overall user experience.
But Vitesse Theme for Astro doesn't stop there. It also leverages the power of Unocss, a utility-first CSS framework that simplifies styling and ensures consistency across your project. With Unocss, developers can write less code while achieving greater flexibility and scalability, resulting in cleaner, more maintainable codebases.
Beyond its technical prowess, Vitesse Theme for Astro stands out for its sleek and modern design. Every element is carefully crafted to ensure a seamless and intuitive user experience, from crisp typography to smooth animations. Whether you're a seasoned developer or just getting started, Vitesse Theme for Astro makes it easy to create stunning web applications that captivate and engage your audience.
But perhaps the most compelling aspect of Vitesse Theme for Astro is its commitment to community-driven development. Built on open-source principles, Vitesse Theme for Astro welcomes contributions from developers around the world, ensuring that it continues to evolve and improve over time. Whether it's submitting bug fixes, suggesting new features, or sharing best practices, the Vitesse Theme for Astro community is vibrant and inclusive, fostering collaboration and innovation at every turn.
In conclusion, Vitesse Theme for Astro is more than just a template—it's a catalyst for innovation in frontend development. By combining the power of Vue.js, Unocss, and modern design principles, Vitesse Theme for Astro empowers developers to build sleek, responsive, and visually stunning web applications that push the boundaries of what's possible on the web.

View file

@ -1,17 +0,0 @@
---
title: 'Unlocking Potential with Sponsorship: A Key Element of Vitesse Theme for Astro'
---
In the dynamic world of web development, sponsorship plays a crucial role in fueling innovation, fostering collaboration, and supporting the growth of open-source projects. At the heart of Vitesse Theme for Astro lies a commitment to community-driven development, and sponsorship stands as a cornerstone of this ethos.
Sponsorship is not merely a transactional relationship; it's a partnership built on mutual trust and shared goals. Through sponsorship, individuals and organizations have the opportunity to invest in the sustainability and advancement of projects like Vitesse Theme for Astro, ensuring their longevity and continued evolution.
For sponsors, Vitesse Theme for Astro offers a platform to showcase their commitment to the developer community while gaining visibility among a diverse audience of frontend enthusiasts, Vue.js practitioners, and Unocss aficionados. By supporting Vitesse Theme for Astro, sponsors align themselves with a project that embodies innovation, accessibility, and excellence in frontend development.
In return for their support, sponsors of Vitesse Theme for Astro gain access to a range of exclusive benefits tailored to their needs and objectives. These benefits may include prominent placement on the project's website, recognition in documentation and release notes, invitations to participate in development discussions, and priority support from the project maintainers.
Furthermore, sponsorship of Vitesse Theme for Astro is an investment in the future of web development. By contributing financial resources, sponsors enable the project maintainers to dedicate more time and resources to improving the framework, addressing bugs, implementing new features, and providing ongoing support to the community.
Sponsorship is also a means of giving back to the ecosystem that supports and sustains developers around the world. By sponsoring Vitesse Theme for Astro, individuals and organizations demonstrate their commitment to nurturing a thriving and inclusive community of developers, where knowledge-sharing, collaboration, and innovation thrive.
In conclusion, sponsorship is a vital component of the ecosystem surrounding Vitesse Theme for Astro. It empowers individuals and organizations to play an active role in shaping the future of frontend development while reaping the benefits of increased visibility, collaboration opportunities, and a stronger, more sustainable open-source community.

View file

@ -1,31 +0,0 @@
---
title: "The Moon and Sixpence"
duration: "12min"
date: "2023-09-22"
---
"The Moon and Sixpence" is a captivating novel written by W. Somerset Maugham, first published in 1919. This compelling narrative delves into the life of Charles Strickland, a middle-aged English stockbroker who abandons his family and comfortable life in London to pursue his passion for painting in Paris. Inspired by the life of Paul Gauguin, Maugham crafts a mesmerizing tale exploring the complexities of art, passion, and the pursuit of one's true calling.
## Plot Overview:
The story unfolds through the eyes of the narrator, who is fascinated by the enigmatic Strickland. Despite being outwardly unremarkable, Strickland possesses an inner fire that drives him to forsake societal norms and follow his artistic ambitions. As the narrator delves deeper into Strickland's past, he uncovers a tale of obsession, sacrifice, and ultimately, redemption.
## Themes and Analysis:
One of the central themes of "The Moon and Sixpence" is the conflict between artistic genius and societal conventions. Strickland's decision to abandon his family and pursue art shocks those around him, who view his actions as selfish and irresponsible. However, Maugham challenges the reader to question whether true artistic greatness can coexist with societal expectations.
The character of Strickland is portrayed as a complex and contradictory figure. On one hand, he is ruthless in his pursuit of artistic perfection, willing to sacrifice everything in its pursuit. Yet, he is also portrayed as a deeply flawed individual, capable of callousness and cruelty towards those who love him. Through Strickland's character, Maugham explores the price of artistic genius and the impact it can have on both the artist and those around them.
Another prominent theme in the novel is the clash between Western civilization and the exotic allure of the South Pacific. Like Gauguin, Strickland is drawn to the primitive beauty of Tahiti, where he seeks inspiration for his paintings. However, his idealized vision of the South Pacific is shattered by the harsh realities of life on the island, leading to a poignant exploration of cultural imperialism and the search for authenticity in art.
## Character Development:
Maugham's characterizations are rich and nuanced, particularly in his portrayal of Strickland. Despite his flaws, Strickland is depicted as a figure of singular vision and uncompromising integrity. His refusal to conform to societal expectations marks him as a rebel and a visionary, willing to sacrifice everything for his art. Similarly, the supporting characters in the novel are equally well-drawn, each contributing to the unfolding drama in their own unique way.
## Writing Style:
Maugham's prose is elegant and evocative, capturing the beauty and brutality of Strickland's world with equal precision. His descriptions of Parisian cafes, Tahitian landscapes, and the inner workings of the human psyche are rendered with a keen eye for detail and an unparalleled sense of atmosphere. The result is a novel that immerses the reader in its world from the very first page, inviting them to contemplate the mysteries of art and existence alongside its unforgettable characters.
## Conclusion:
In conclusion, "The Moon and Sixpence" is a masterpiece of modern literature that continues to resonate with readers over a century after its initial publication. Through its exploration of art, passion, and the human condition, Maugham crafts a timeless tale that challenges our assumptions about creativity, morality, and the nature of genius. Whether you're an art enthusiast, a lover of literary fiction, or simply in search of a compelling story, "The Moon and Sixpence" is a novel that will stay with you long after you've turned the final page.

View file

@ -1,12 +1,12 @@
--- ---
import { fade } from 'astro:transitions'
import { ViewTransitions } from 'astro:transitions' import { ViewTransitions } from 'astro:transitions'
import BaseHead from '../components/BaseHead.astro' import BaseHead from '@/components/BaseHead.astro'
import Header from '../components/Header.vue' import Header from '@/components/Header.vue'
import Footer from '../components/Footer.vue' import Footer from '@/components/Footer.vue'
import PageNav from '../components/PageNav.vue' import ScrollToTop from '@/components/ScrollToTop.vue'
import PageOperate from '../components/PageOperate.vue'
const { pageNav = false, pageOperate = false, ...head } = Astro.props const { pageOperate = false, ...head } = Astro.props
--- ---
<!doctype html> <!doctype html>
@ -15,23 +15,15 @@ const { pageNav = false, pageOperate = false, ...head } = Astro.props
<BaseHead {...head} /> <BaseHead {...head} />
<ViewTransitions /> <ViewTransitions />
</head> </head>
<body class="bg-main text-main min-h-screen font-sans w-full"> <body class="bg-main text-main min-h-screen font-sans w-full bg-dot">
<Header client:load /> <Header client:load />
<main class="grow max-w-3xl mx-auto py-10 px-6"> <main
{pageNav && <PageNav client:load pathname={Astro.url.pathname} />} class="grow max-w-3xl mx-auto sm:pt-36 pt-26 pb-16 px-6 relative"
transition:animate={fade({ duration: '0.4s' })}
>
<slot /> <slot />
{ <ScrollToTop client:load />
pageOperate && (
<div mt-8>
<PageOperate
client:load
showShare={head.pageType === 'article'}
url={Astro.url}
/>
</div>
)
}
</main>
<Footer /> <Footer />
</main>
</body> </body>
</html> </html>

View file

@ -1,7 +1,7 @@
--- ---
import { getCollection } from 'astro:content' import { getCollection } from 'astro:content'
import BaseLayout from '../layouts/BaseLayout.astro' import BaseLayout from '@/layouts/BaseLayout.astro'
import type { CollectionPages } from '../types' import type { CollectionPages } from '@/types'
export async function getStaticPaths() { export async function getStaticPaths() {
const pages = await getCollection('pages') const pages = await getCollection('pages')
@ -17,21 +17,29 @@ type Props = { page: CollectionPages }
const { page } = Astro.props const { page } = Astro.props
const { title, description, image } = page.data const { title, description, image } = page.data
const { Content } = await page.render() const { Content } = await page.render()
--- ---
<BaseLayout <BaseLayout title={title} description={description} pageOperate={true}>
title={title} <article class="mb-16 max-w-none prose">
description={description}
image={image}
pageOperate={true}
>
<article class="mb-16 sm:mb-24">
<h1 class="text-title"> <h1 class="text-title">
{title} {title}
</h1> </h1>
<div class="max-w-none prose"> {
image && (
<p>
<img
width="100%"
height="auto"
src={image.src}
loading="lazy"
decoding="async"
alt={image.alt || ''}
/>
</p>
)
}
<Content /> <Content />
</div>
</article> </article>
</BaseLayout> </BaseLayout>

View file

@ -1,16 +0,0 @@
---
import BaseLayout from '../layouts/BaseLayout.astro'
import ListPosts from '../components/ListPosts.vue'
import { getPosts } from '../utils/posts'
const posts = await getPosts('blog')
---
<BaseLayout
title="Blog"
description="List of all the blog posts."
pageNav={true}
pageOperate={true}
>
<ListPosts list={posts} />
</BaseLayout>

View file

@ -1,55 +1,102 @@
--- ---
import { marked } from 'marked' import BaseLayout from '@/layouts/BaseLayout.astro'
import BaseLayout from '../layouts/BaseLayout.astro' import siteConfig from '@/site-config'
import siteConfig from '../site-config'
const hero = siteConfig.hero
--- ---
<BaseLayout description={siteConfig.description} image={siteConfig.image}> <BaseLayout description={siteConfig.description}>
{ <article class="prose max-w-none w-full">
(hero.title || hero.image.src || hero.text || hero.socialLinks) && ( <h1 class="text-title">Vitesse theme</h1>
<div class="prose max-w-3xl w-full"> <p>Vitesse theme for Astro, supports Vue and UnoCSS.</p>
{hero.title && <h1 class="text-title">{hero.title}</h1>} <p>
{hero.image.src && ( A minimal, SEO-riendly portfolio and blog theme for Astro. It's inspired
by <a target="_blank" href="https://antfu.me">antfu.me</a>.
</p>
<p>
<img <img
class="w-full" width="640"
src={hero.image.src} height="360"
src="/hero.jpg"
loading="lazy" loading="lazy"
decoding="async" decoding="async"
alt={hero.image.alt || 'Hero Image'} alt="Hero Image"
/> />
)} </p>
{hero.text && <div set:html={marked.parse(hero.text)} />} <p>
{hero.socialLinks.length > 0 && ( Based on first principles thinking, and you can also see from the picture
<> <sup>👆</sup>, the goal of this theme is to
<hr class="w-14 mx-auto my-8 border-t border-truegray-200 dark:border-truegray-800" /> <b>make complex things as simple as possible</b>
. So, the core of this theme is the content, and the other elements are just
auxiliary.
</p>
<p>Here are the features you don't need to worry about:</p>
<ul>
<li>Responsive</li>
<li>Light / Dark Theme</li>
<li>Markdown support</li>
<li>
<a target="_blank" href="https://mdxjs.com/">MDX</a>
(components in your markdown) support
</li>
<li>
<a target="_blank" href="https://vuejs.org/">Vue</a>
SFC component support
</li>
<li>Auto generated sitemap and RSS Feed</li>
<li>
<a target="_blank" href="https://vueuse.org/">VueUse</a>
&
<a target="_blank" href="https://lodash.com/">Lodash</a>
support
</li>
</ul>
<p>
By the way, this theme use the <a
target="_blank"
href="https://unocss.dev/">Unocss</a
> for style, it's fast.
</p>
<p>
Visit
<a
target="_blank"
href="https://github.com/kaivanwong/astro-theme-vitesse"
>
it on GitHub
</a>
to fork the repository, read the docs or one-click deploy on Netlify.
</p>
<p>
If you like Vitesse theme for Astro, please
<a target="_blank" href="https://github.com/kaivanwong">
click follow for me
</a>.
</p>
<hr class="hr-line" />
<p>Find me on</p> <p>Find me on</p>
<p class="flex gap-x-4 gap-y-2 flex-wrap"> <p class="flex gap-x-4 gap-y-2 flex-wrap">
{hero.socialLinks.map((link) => ( {
siteConfig.socialLinks.map((link) => (
<a <a
aria-label={link.text}
href={link.href} href={link.href}
target="_blank" target="_blank"
rel="noopener noreferrer" class="prose-link"
class="prose-link flex items-center"
> >
<i class:list={[link.icon, 'text-sm mr-1']} /> <i class:list={[link.icon]} />
<span>{link.text}</span> {link.text}
</a> </a>
))} ))
}
</p> </p>
</>
)}
{siteConfig.email && (
<p> <p>
If you have any questions, please email me at If you have any questions, please email me at
<a prose-link href={`mailto:${siteConfig.email}`}> <a
prose-link
aria-label={siteConfig.email}
href={`mailto:${siteConfig.email}`}
>
{siteConfig.email} {siteConfig.email}
</a> </a>.
.
</p> </p>
)} </article>
</div>
)
}
</BaseLayout> </BaseLayout>

View file

@ -1,16 +0,0 @@
---
import BaseLayout from '../layouts/BaseLayout.astro'
import ListPosts from '../components/ListPosts.vue'
import { getPosts } from '../utils/posts'
const posts = await getPosts('notes')
---
<BaseLayout
title="Notes"
description="List of all the note posts."
pageNav={true}
pageOperate={true}
>
<ListPosts list={posts} />
</BaseLayout>

View file

@ -1,41 +0,0 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro'
import { type CollectionPosts } from '../../types'
import { getAllPosts } from '../../utils/posts'
export async function getStaticPaths() {
const posts = await getAllPosts()
return posts.map((post) => ({
params: { slug: post.slug },
props: {
post,
},
}))
}
type Props = { post: CollectionPosts }
const { post } = Astro.props
const { title, image, description } = post.data
const { Content } = await post.render()
---
<BaseLayout
title={title}
description={description}
image={image}
pageType="article"
pageOperate={true}
>
<article class="mb-16 sm:mb-24">
<header class="mb-8">
<h1 class="text-title">
{title}
</h1>
</header>
<div class="max-w-none prose">
<Content />
</div>
</article>
</BaseLayout>

View file

@ -1,27 +0,0 @@
---
import siteConfig from '../site-config'
import BaseLayout from '../layouts/BaseLayout.astro'
import ListProjects from '../components/ListProjects.vue'
---
<BaseLayout
title="Projects"
description="List of projects that I am proud of"
pageOperate={true}
>
<h1 class="mb-10 text-title">Projects</h1>
{
siteConfig.projects.length > 0 && (
<div>
{siteConfig.projects.map((group) => (
<div mb-10>
<h2 mb-4 font-bold>
{group.title}
</h2>
<ListProjects list={group.projects} />
</div>
))}
</div>
)
}
</BaseLayout>

View file

@ -1,16 +0,0 @@
---
import BaseLayout from '../layouts/BaseLayout.astro'
import ListPosts from '../components/ListPosts.vue'
import { getPosts } from '../utils/posts'
const posts = await getPosts('reading')
---
<BaseLayout
title="Reading"
description="List of all the reading posts."
pageNav={true}
pageOperate={true}
>
<ListPosts list={posts} />
</BaseLayout>

View file

@ -1,22 +0,0 @@
import rss from '@astrojs/rss'
import siteConfig from '../site-config'
import { getAllPosts } from '../utils/posts'
export async function GET(context) {
const posts = await getAllPosts()
return rss({
title: siteConfig.title,
description: siteConfig.description,
site: context.site,
items: posts.map((item) => {
return {
...item.data,
link: `${context.site}/posts/${item.slug}/`,
pubDate: new Date(item.data.date),
content: item.body,
author: `${siteConfig.author} <${siteConfig.email}>`,
}
}),
})
}

View file

@ -1,16 +0,0 @@
---
import BaseLayout from '../layouts/BaseLayout.astro'
import ListPosts from '../components/ListPosts.vue'
import { getPosts } from '../utils/posts'
const posts = await getPosts('talks')
---
<BaseLayout
title="Talks"
description="List of all the talk posts."
pageNav={true}
pageOperate={true}
>
<ListPosts list={posts} />
</BaseLayout>

View file

@ -1,131 +1,104 @@
export const siteConfig = { export const siteConfig = {
author: 'Kaivan Wong', author: 'Kaivan Wong',
title: 'Vitesse theme for Astro', title: 'Vitesse theme for Astro',
subtitle: 'Supports Vue and UnoCSS.', subtitle: 'Vitesse theme for Astro, supports Vue and UnoCSS.',
description: 'Vitesse theme for Astro blog, supports Vue and UnoCSS.', description: 'A Minimal, SEO-friendly portfolio and blog theme for Astro.',
image: { image: {
src: '/preview.jpg', src: '/hero.jpg',
alt: 'Vitesse theme for Astro - Supports Vue and UnoCSS.', alt: 'Website Main Image',
}, },
email: '', email: 'kaivanwong@outlook.com',
headerNavLinks: [ socialLinks: [
{ {
text: 'Home', text: 'GitHub',
href: '/', href: 'https://github.com/kaivanwong',
icon: 'i-simple-icons-github',
header: 'i-ri-github-line',
}, },
{
text: 'Twitter',
href: 'https://twitter.com/kaivanwong',
icon: 'i-simple-icons-x',
header: 'i-ri-twitter-x-line',
},
{
text: 'Linkedin',
href: 'https://www.linkedin.com/in/kaivan-wong-a42441291',
icon: 'i-simple-icons-linkedin',
},
{
text: 'Instagram',
href: 'https://www.instagram.com/hikaivanwong',
icon: 'i-simple-icons-instagram',
},
{
text: 'Youtube',
href: 'https://youtube.com/@kaivanwong',
icon: 'i-simple-icons-youtube',
},
{
text: 'Bilibili',
href: 'https://space.bilibili.com/19001420',
icon: 'i-simple-icons-bilibili',
},
{
text: '微博',
href: 'https://weibo.com/u/5605059021',
icon: 'i-simple-icons-sinaweibo',
},
],
header: {
logo: {
src: '/favicon.svg',
alt: 'Logo Image',
},
navLinks: [
{ {
text: 'Blog', text: 'Blog',
href: '/blog', href: '/blog',
}, },
{
text: 'Note',
href: '/blog/note',
},
{ {
text: 'Projects', text: 'Projects',
href: '/projects', href: '/projects',
}, },
], ],
hero: {
title: 'Welcome to Vitesse Theme for Astro',
text: `<p>Experience the perfect blend of efficiency and aesthetics with Vitesse Theme for Astro. Inspired by the sleek design of antfu.me, this template seamlessly integrates Vue and Unocss to provide you with a cutting-edge development experience.</p>
<p><b>Key Features:</b></p>
<ol>
<li><b>Vue Support:</b> Harness the power of Vue.js to build dynamic and interactive web applications. Vitesse Theme for Astro ensures smooth integration and efficient utilization of Vue components for enhanced functionality.</li>
<li><b>Unocss Integration:</b> Streamline your styling process with Unocss, a utility-first CSS framework. By utilizing only the styles you need, Unocss optimizes your codebase for performance without compromising on design flexibility.</li>
<li><b>Sleek Design:</b> Drawing inspiration from the modern aesthetic of antfu.me, Vitesse Theme for Astro offers a clean and visually appealing design. From crisp typography to intuitive layouts, every element is crafted with attention to detail to elevate your web presence.</li>
<li><b>Customizable Components:</b> Tailor your web applications to suit your unique requirements with Vitesse Theme's customizable components. Whether you're building a portfolio, blog, or e-commerce site, our flexible components adapt to your needs with ease.</li>
<li><b>Performance Optimization:</b> Deliver lightning-fast user experiences with Vitesse Theme for Astro's focus on performance optimization. By minimizing unnecessary bloat and prioritizing efficient code practices, your applications will load swiftly and operate seamlessly across devices.</li>
</ol>
<p>Elevate your web development journey with Vitesse Theme for Astro. Experience the perfect synergy of Vue, Unocss, and modern design principles to create stunning web applications that captivate and engage your audience.</p>
`,
image: {
src: 'hero.jpg',
alt: '',
}, },
socialLinks: [], page: {
}, navLinks: [
pageNavLinks: [
{ {
text: 'Blog', text: 'Blog',
href: '/blog', href: '/blog',
}, },
{ {
text: 'Notes', text: 'Note',
href: '/notes', href: '/blog/note',
},
{
text: 'Reading',
href: '/reading',
},
],
projects: [
{
title: 'Develop Templates',
projects: [
{
text: 'Frosty Web',
description: 'A clean and minimalist website template designed to showcase content with style.',
icon: 'i-carbon-webhook',
href: '',
}, },
], ],
}, },
footer: {
navLinks: [
{ {
title: 'Framework', text: 'Sponsor to Me',
projects: [ href: 'https://kaivanwong.me/sponsor/',
{
text: 'Pixel Craft',
description: 'Frontend framework for crafting pixel-perfect web applications with a responsive design.',
icon: 'i-carbon-pen-fountain',
href: '',
}, },
{ {
text: 'Aurora UI', text: 'Astro Website',
description: 'Modern UI library designed to streamline frontend development with modular components.', href: 'https://astro.build/',
icon: 'i-carbon-mountain',
href: '',
}, },
{ {
text: 'Nimbus CSS', text: 'Theme page in Astro',
description: 'Lightweight CSS framework for building responsive websites with a flexible grid system.', href: 'https://astro.build/themes/details/vitesse-theme-for-astro/',
icon: 'i-carbon-face-satisfied',
href: '',
},
],
}, },
{ {
title: 'Library', text: 'GitHub Repository',
projects: [
{
text: 'Zenith Scroll',
description: 'Smooth-scrolling JavaScript library for creating immersive scrolling experiences.',
icon: '',
href: '',
},
{
text: 'Polaris JS',
description: 'Lightweight JavaScript library for creating smooth animations and transitions.',
icon: 'i-carbon-tools-alt',
href: '',
},
],
},
],
footerNavLinks: [
{
text: 'About',
href: '/about',
},
{
text: 'Sponsor',
href: '/sponsor',
},
{
text: 'Contact Me',
href: 'mailto:kaivanwong@outlook.me',
},
{
text: 'Github Repo',
href: 'https://github.com/kaivanwong/vitesse-astro-theme', href: 'https://github.com/kaivanwong/vitesse-astro-theme',
}, },
], ],
},
} }
export default siteConfig export default siteConfig

View file

@ -1,12 +0,0 @@
html.dark .astro-code,
html.dark .astro-code span {
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}
.prose a {
--at-apply: prose-link;
}

View file

@ -1,9 +1,19 @@
import type { CollectionEntry } from 'astro:content' import type { CollectionEntry } from 'astro:content'
export type Posts = 'blog' | 'notes' | 'reading' export type PostKey = 'blog'
export type CollectionPosts = CollectionEntry<Posts> export type CollectionPosts = CollectionEntry<PostKey>
export type Pages = 'pages' export type Pages = 'pages'
export type CollectionPages = CollectionEntry<Pages> export type CollectionPages = CollectionEntry<Pages>
export type ProjectData = Array<{
title: string
projects: Array<{
text: string
description?: string
icon?: string
href: string
}>
}>

View file

@ -1,22 +1,12 @@
import { getCollection } from 'astro:content' import { getCollection } from 'astro:content'
import type { CollectionPosts, Posts } from '../types' import type { CollectionPosts, PostKey } from '@/types'
export function sortPostsByDate(itemA: CollectionPosts, itemB: CollectionPosts) { export function sortPostsByDate(itemA: CollectionPosts, itemB: CollectionPosts) {
return new Date(itemB.data.date).getTime() - new Date(itemA.data.date).getTime() return new Date(itemB.data.date).getTime() - new Date(itemA.data.date).getTime()
} }
export async function getPosts(type: Posts) { export async function getPosts(path?: string, collection: PostKey = 'blog') {
return (await getCollection(type, ({ data }) => { return (await getCollection(collection, (post) => {
return import.meta.env.PROD ? data.draft !== true : true return (import.meta.env.PROD ? post.data.draft !== true : true) && (path ? post.slug.includes(path) : true)
})).sort(sortPostsByDate) })).sort(sortPostsByDate)
} }
export async function getAllPosts() {
const posts = await Promise.all([
getPosts('blog'),
getPosts('notes'),
getPosts('reading'),
getPosts('talks'),
])
return posts.flat().sort(sortPostsByDate)
}

View file

@ -2,6 +2,10 @@
"extends": "astro/tsconfigs/strict", "extends": "astro/tsconfigs/strict",
"compilerOptions": { "compilerOptions": {
"jsx": "preserve", "jsx": "preserve",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"strictNullChecks": true "strictNullChecks": true
} }
} }

View file

@ -12,8 +12,8 @@ import {
export default defineConfig({ export default defineConfig({
shortcuts: [ shortcuts: [
{ {
'bg-main': 'bg-white dark:bg-black', 'bg-main': 'bg-hex-eef5fc dark:bg-hex-0d1117',
'text-main': 'text-hex-555 dark:text-hex-bbb', 'text-main': 'text-hex-555555 dark:text-hex-bbbbbb',
'text-link': 'text-dark dark:text-white ', 'text-link': 'text-dark dark:text-white ',
'border-main': 'border-truegray-300 dark:border-truegray-600', 'border-main': 'border-truegray-300 dark:border-truegray-600',
}, },
@ -23,6 +23,9 @@ export default defineConfig({
'prose-link': 'text-link text-nowrap cursor-pointer border-b-1 !border-opacity-30 hover:!border-opacity-100 border-neutral-500 hover:border-truegray-600 dark:border-neutral-500 hover:dark:border-truegray-400 transition-border-color duration-200 decoration-none', 'prose-link': 'text-link text-nowrap cursor-pointer border-b-1 !border-opacity-30 hover:!border-opacity-100 border-neutral-500 hover:border-truegray-600 dark:border-neutral-500 hover:dark:border-truegray-400 transition-border-color duration-200 decoration-none',
'container-link': 'p-2 opacity-60 hover:opacity-100 cursor-pointer hover:bg-truegray-500 !bg-opacity-10 transition-colors transition-opacity duration-200', 'container-link': 'p-2 opacity-60 hover:opacity-100 cursor-pointer hover:bg-truegray-500 !bg-opacity-10 transition-colors transition-opacity duration-200',
}, },
{
'hr-line': 'w-14 mx-auto my-8 border-solid border-1px !border-truegray-200 !dark:border-truegray-800',
},
], ],
presets: [ presets: [
presetUno(), presetUno(),
@ -44,10 +47,17 @@ export default defineConfig({
], ],
transformers: [transformerDirectives(), transformerVariantGroup()], transformers: [transformerDirectives(), transformerVariantGroup()],
safelist: [ safelist: [
'i-carbon-webhook', 'i-ri-file-list-2-line',
'i-carbon-mountain', 'i-carbon-campsite',
'i-carbon-pen-fountain', 'i-simple-icons-github',
'i-carbon-face-satisfied', 'i-simple-icons-x',
'i-carbon-tools-alt', 'i-simple-icons-linkedin',
'i-simple-icons-instagram',
'i-simple-icons-youtube',
'i-simple-icons-bilibili',
'i-simple-icons-zhihu',
'i-simple-icons-sinaweibo',
'i-ri-github-line',
'i-ri-twitter-x-line',
], ],
}) })