feat!: update src dir
This commit is contained in:
parent
fd87c91ab3
commit
aa011faa54
37 changed files with 4433 additions and 3451 deletions
|
@ -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
6409
package-lock.json
generated
File diff suppressed because it is too large
Load diff
27
package.json
27
package.json
|
@ -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"
|
||||||
|
|
BIN
preview.jpg
BIN
preview.jpg
Binary file not shown.
Before Width: | Height: | Size: 71 KiB |
BIN
public/about.jpg
BIN
public/about.jpg
Binary file not shown.
Before Width: | Height: | Size: 38 KiB |
|
@ -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 |
BIN
public/hero.jpg
BIN
public/hero.jpg
Binary file not shown.
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 11 KiB |
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
{{ link.text }}
|
<a :aria-label="`${link.text}`" :target="getLinkTarget(link.href)" class="nav-link" :href="link.href">
|
||||||
</a>
|
{{ link.text }}
|
||||||
|
</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> © {{ new Date().getFullYear() }} {{ siteConfig.author }}.</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -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 v-for="link in navLinks" :key="link.text" nav-link :href="link.href">
|
<a href="/" mr-6 aria-label="Header Logo Image">
|
||||||
{{ link.text }}
|
<img width="32" height="32" :src="siteConfig.header.logo.src" :alt="siteConfig.header.logo.alt">
|
||||||
</a>
|
</a>
|
||||||
</nav>
|
<nav class="sm:flex hidden flex-wrap gap-x-6 position-initial flex-row">
|
||||||
<menu sm:hidden inline-block i-ri-menu-2-fill @click="toggleMenu" />
|
<a
|
||||||
<div flex gap-4 sm:gap-6>
|
v-for="link in navLinks" :key="link.text" :aria-label="`${link.text}`" :target="getLinkTarget(link.href)"
|
||||||
<a nav-link href="rss.xml" i-ri-rss-line />
|
nav-link :href="link.href"
|
||||||
|
>
|
||||||
|
{{ link.text }}
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
<div sm:hidden h-full flex items-center @click="toggleNavDrawer()">
|
||||||
|
<menu i-ri-menu-2-fill />
|
||||||
|
</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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
||||||
|
|
|
@ -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.
|
|
|
@ -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 }
|
|
||||||
|
|
|
@ -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
|
|
||||||
---
|
|
||||||
```
|
|
|
@ -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.
|
|
|
@ -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.
|
|
|
@ -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.
|
|
|
@ -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 && (
|
<Footer />
|
||||||
<div mt-8>
|
|
||||||
<PageOperate
|
|
||||||
client:load
|
|
||||||
showShare={head.pageType === 'article'}
|
|
||||||
url={Astro.url}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -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">
|
{
|
||||||
<Content />
|
image && (
|
||||||
</div>
|
<p>
|
||||||
|
<img
|
||||||
|
width="100%"
|
||||||
|
height="auto"
|
||||||
|
src={image.src}
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
alt={image.alt || ''}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<Content />
|
||||||
</article>
|
</article>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
|
@ -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>
|
|
|
@ -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
|
||||||
<img
|
by <a target="_blank" href="https://antfu.me">antfu.me</a>.
|
||||||
class="w-full"
|
</p>
|
||||||
src={hero.image.src}
|
<p>
|
||||||
loading="lazy"
|
<img
|
||||||
decoding="async"
|
width="640"
|
||||||
alt={hero.image.alt || 'Hero Image'}
|
height="360"
|
||||||
/>
|
src="/hero.jpg"
|
||||||
)}
|
loading="lazy"
|
||||||
{hero.text && <div set:html={marked.parse(hero.text)} />}
|
decoding="async"
|
||||||
{hero.socialLinks.length > 0 && (
|
alt="Hero Image"
|
||||||
<>
|
/>
|
||||||
<hr class="w-14 mx-auto my-8 border-t border-truegray-200 dark:border-truegray-800" />
|
</p>
|
||||||
<p>Find me on</p>
|
<p>
|
||||||
<p class="flex gap-x-4 gap-y-2 flex-wrap">
|
Based on first principles thinking, and you can also see from the picture
|
||||||
{hero.socialLinks.map((link) => (
|
<sup>👆</sup>, the goal of this theme is to
|
||||||
<a
|
<b>make complex things as simple as possible</b>
|
||||||
href={link.href}
|
. So, the core of this theme is the content, and the other elements are just
|
||||||
target="_blank"
|
auxiliary.
|
||||||
rel="noopener noreferrer"
|
</p>
|
||||||
class="prose-link flex items-center"
|
<p>Here are the features you don't need to worry about:</p>
|
||||||
>
|
<ul>
|
||||||
<i class:list={[link.icon, 'text-sm mr-1']} />
|
<li>Responsive</li>
|
||||||
<span>{link.text}</span>
|
<li>Light / Dark Theme</li>
|
||||||
</a>
|
<li>Markdown support</li>
|
||||||
))}
|
<li>
|
||||||
</p>
|
<a target="_blank" href="https://mdxjs.com/">MDX</a>
|
||||||
</>
|
(components in your markdown) support
|
||||||
)}
|
</li>
|
||||||
{siteConfig.email && (
|
<li>
|
||||||
<p>
|
<a target="_blank" href="https://vuejs.org/">Vue</a>
|
||||||
If you have any questions, please email me at
|
SFC component support
|
||||||
<a prose-link href={`mailto:${siteConfig.email}`}>
|
</li>
|
||||||
{siteConfig.email}
|
<li>Auto generated sitemap and RSS Feed</li>
|
||||||
</a>
|
<li>
|
||||||
.
|
<a target="_blank" href="https://vueuse.org/">VueUse</a>
|
||||||
</p>
|
&
|
||||||
)}
|
<a target="_blank" href="https://lodash.com/">Lodash</a>
|
||||||
</div>
|
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 class="flex gap-x-4 gap-y-2 flex-wrap">
|
||||||
|
{
|
||||||
|
siteConfig.socialLinks.map((link) => (
|
||||||
|
<a
|
||||||
|
aria-label={link.text}
|
||||||
|
href={link.href}
|
||||||
|
target="_blank"
|
||||||
|
class="prose-link"
|
||||||
|
>
|
||||||
|
<i class:list={[link.icon]} />
|
||||||
|
{link.text}
|
||||||
|
</a>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If you have any questions, please email me at
|
||||||
|
<a
|
||||||
|
prose-link
|
||||||
|
aria-label={siteConfig.email}
|
||||||
|
href={`mailto:${siteConfig.email}`}
|
||||||
|
>
|
||||||
|
{siteConfig.email}
|
||||||
|
</a>.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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}>`,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -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: 'Blog',
|
text: 'Twitter',
|
||||||
href: '/blog',
|
href: 'https://twitter.com/kaivanwong',
|
||||||
|
icon: 'i-simple-icons-x',
|
||||||
|
header: 'i-ri-twitter-x-line',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Projects',
|
text: 'Linkedin',
|
||||||
href: '/projects',
|
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',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
hero: {
|
header: {
|
||||||
title: 'Welcome to Vitesse Theme for Astro',
|
logo: {
|
||||||
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>
|
src: '/favicon.svg',
|
||||||
<p><b>Key Features:</b></p>
|
alt: 'Logo Image',
|
||||||
<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: [],
|
navLinks: [
|
||||||
|
{
|
||||||
|
text: 'Blog',
|
||||||
|
href: '/blog',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Note',
|
||||||
|
href: '/blog/note',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Projects',
|
||||||
|
href: '/projects',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
navLinks: [
|
||||||
|
{
|
||||||
|
text: 'Blog',
|
||||||
|
href: '/blog',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Note',
|
||||||
|
href: '/blog/note',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
navLinks: [
|
||||||
|
{
|
||||||
|
text: 'Sponsor to Me',
|
||||||
|
href: 'https://kaivanwong.me/sponsor/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Astro Website',
|
||||||
|
href: 'https://astro.build/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Theme page in Astro',
|
||||||
|
href: 'https://astro.build/themes/details/vitesse-theme-for-astro/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'GitHub Repository',
|
||||||
|
href: 'https://github.com/kaivanwong/vitesse-astro-theme',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
pageNavLinks: [
|
|
||||||
{
|
|
||||||
text: 'Blog',
|
|
||||||
href: '/blog',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Notes',
|
|
||||||
href: '/notes',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Framework',
|
|
||||||
projects: [
|
|
||||||
{
|
|
||||||
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',
|
|
||||||
description: 'Modern UI library designed to streamline frontend development with modular components.',
|
|
||||||
icon: 'i-carbon-mountain',
|
|
||||||
href: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Nimbus CSS',
|
|
||||||
description: 'Lightweight CSS framework for building responsive websites with a flexible grid system.',
|
|
||||||
icon: 'i-carbon-face-satisfied',
|
|
||||||
href: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Library',
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default siteConfig
|
export default siteConfig
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
14
src/types.ts
14
src/types.ts
|
@ -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
|
||||||
|
}>
|
||||||
|
}>
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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',
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue