This commit is contained in:
2025-12-20 18:46:45 +08:00
parent 5163e756f0
commit 547fd99c23
73 changed files with 15635 additions and 1924 deletions

View File

@@ -0,0 +1,74 @@
<template>
<section class="flex flex-col gap-2.5">
<div class="flex flex-wrap gap-2.5">
<template v-for="link in links" :key="link.url">
<NuxtLink
:to="link.url"
class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-white/40 backdrop-blur-md border border-white/40 text-zinc-700 text-sm font-semibold transition-all duration-300 hover:bg-white/60 hover:border-white/60 hover:text-violet-600 hover:-translate-y-1 hover:shadow-lg dark:bg-slate-800/40 dark:border-white/10 dark:text-zinc-300 dark:hover:bg-slate-800/60 dark:hover:text-white">
<span v-if="iconFor(link)" class="inline-flex items-center justify-center w-5 h-5">
<Icon
v-if="iconFor(link).name"
:name="iconFor(link).name"
size="16"
class="text-current" />
<NuxtImg
v-else
:src="iconFor(link).src"
:alt="link.name"
loading="lazy"
class="w-full h-full" />
</span>
<span>{{ link.name }}</span>
</NuxtLink>
</template>
</div>
</section>
</template>
<script setup>
import siteConfig from "~/config";
import { onMounted } from "vue";
const links = siteConfig.socialLinks;
const iconMap = {
bilibili: "simple-icons:bilibili",
github: "fa-brands:github",
blog: "fa-solid:rss",
email: "fa-solid:envelope",
mail: "fa-solid:envelope",
telegram: "fa-brands:telegram",
twitter: "fa-brands:twitter",
x: "fa-brands:twitter",
linkedin: "fa-brands:linkedin",
youtube: "fa-brands:youtube",
facebook: "fa-brands:facebook",
instagram: "fa-brands:instagram",
reddit: "fa-brands:reddit",
discord: "fa-brands:discord",
weibo: "fa-brands:weibo",
zhihu: "fa-brands:zhihu",
wechat: "fa-brands:weixin",
weixin: "fa-brands:weixin",
qq: "fa-brands:qq",
};
const iconFor = (link) => {
const key = (link.name || "").toLowerCase();
if (iconMap[key]) return { name: iconMap[key] };
if (link.icon) return { src: link.icon };
return null;
};
onMounted(() => {
const id = "fa-cdn";
if (document.getElementById(id)) return;
const link = document.createElement("link");
link.id = id;
link.rel = "stylesheet";
link.href =
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css?font-display=swap";
link.crossOrigin = "anonymous";
link.referrerPolicy = "no-referrer";
document.head.appendChild(link);
});
</script>

View File

@@ -0,0 +1,104 @@
<script setup lang="ts">
import { ref } from "vue";
import siteConfig from "~/config";
const showMore = ref(false);
const { data: sysInfo } = await useFetch("/sys-info");
const techStack = computed(() => {
const platform = sysInfo.value?.platform || {
name: "Detecting...",
icon: "heroicons:arrow-path",
};
return [
{ label: "构建平台", value: platform.name, icon: platform.icon },
{ label: "图片存储", value: "去图图床", icon: "heroicons:photo" },
{ label: "软件协议", value: "MIT", icon: "heroicons:document-text" },
{ label: "文章许可", value: "CC BY-NC-SA 4.0", icon: "heroicons:creative-commons" },
{
label: "规范域名",
value: siteConfig.siteMeta.url.replace("https://", ""),
icon: "heroicons:globe-alt",
},
];
});
const versions = computed(() => {
if (!sysInfo.value) return [];
const v = sysInfo.value.versions;
return [
{ label: "Vue", value: v.vue },
{ label: "Nuxt", value: v.nuxt },
{ label: "Content", value: v.content },
{ label: "Node", value: v.node },
{ label: "OS", value: v.os },
{ label: "Arch", value: v.arch },
];
});
</script>
<template>
<section class="py-12 px-4">
<div class="max-w-xl mx-auto">
<div class="flex items-center gap-3 mb-6">
<div class="p-2 bg-violet-500/10 rounded-lg">
<Icon
name="heroicons:cpu-chip"
size="1.5em"
class="text-violet-600 dark:text-violet-400" />
</div>
<h2 class="text-2xl font-bold text-zinc-800 dark:text-zinc-100 tracking-tight">技术信息</h2>
</div>
<div
class="bg-white/40 dark:bg-slate-900/40 backdrop-blur-md border border-white/20 dark:border-white/5 rounded-3xl p-6 shadow-sm">
<!-- 基础信息列表 -->
<div class="space-y-4">
<div
v-for="item in techStack"
:key="item.label"
class="flex items-center justify-between group">
<span class="text-zinc-500 dark:text-zinc-400 text-sm font-medium">{{
item.label
}}</span>
<div class="flex items-center gap-2">
<Icon
v-if="item.icon"
:name="item.icon"
class="w-4 h-4 text-zinc-400 group-hover:text-violet-500 transition-colors" />
<span class="text-zinc-800 dark:text-zinc-200 text-sm font-bold">{{
item.value
}}</span>
</div>
</div>
</div>
<!-- 展开/收起按钮 -->
<button
class="w-full mt-8 py-2 flex items-center justify-center gap-2 text-xs font-bold text-zinc-400 hover:text-violet-500 transition-colors border-t border-white/10 dark:border-white/5 pt-6"
@click="showMore = !showMore">
<Icon
:name="showMore ? 'heroicons:chevron-double-up' : 'heroicons:chevron-double-down'"
class="w-3 h-3" />
{{ showMore ? "收起构建信息" : "展开构建信息" }}
</button>
<!-- 详细版本信息网格 -->
<div
v-show="showMore"
class="grid grid-cols-2 sm:grid-cols-4 gap-6 mt-6 animate-in fade-in slide-in-from-top-2 duration-500">
<div v-for="v in versions" :key="v.label" class="text-center">
<div
class="text-[10px] uppercase tracking-widest text-zinc-500 dark:text-zinc-500 font-bold mb-1">
{{ v.label }}
</div>
<div class="text-xs font-mono font-bold text-zinc-700 dark:text-zinc-300">
{{ v.value }}
</div>
</div>
</div>
</div>
</div>
</section>
</template>

View File

@@ -1,44 +1,146 @@
<script setup lang="ts">
const route = useRoute()
const path = computed(() => route.fullPath.replace('/', ''))
</script>
<template>
<div class="py-5 border-t dark:border-gray-800 mt-5 text-zinc-700 dark:text-zinc-300">
<div class="px-6 container max-w-5xl mx-auto">
<div class="grid grid-cols-1 md:grid-cols-3">
<FooterSite v-if="path === 'about'" />
<FooterDeveloper v-else />
<FooterLink />
<FooterConnect />
</div>
<footer
class="text-center mt-auto w-full flex flex-col gap-2 py-8 px-4 border-t border-white/10 dark:border-white/5">
<!-- 一言 -->
<p v-if="showHitokoto && quote" class="text-text-muted text-sm m-0 italic">
{{ quote }}<span v-if="from" class="ml-1.5"> {{ from }}</span>
</p>
<div class="border-t dark:border-gray-800 mt-5 text-center p-2">
© 2020-2024 No Right is reserved. Who cares 🤷? It's
<a href="https://github.com/nurriyad/blog" target="_blank" rel="nofollow" class="underline"
>open source</a
>
anyway.
<!-- 访问统计 -->
<p v-if="showStats && !statsError" class="text-text-muted text-xs m-0">
👁 {{ visitors }} · 📊 {{ pageviews }}
</p>
<a href="/rss.xml" aria-label="Website RSS Feed">
<span class="px-3"><Icon name="bi:rss-fill" class="-translate-y-[-20%]" /></span
></a>
</div>
</div>
</div>
<!-- 备案信息 -->
<p v-if="contact?.beian" class="text-text-muted text-xs m-0">
<NuxtLink
:to="contact.beianLink || '/'"
class="opacity-85 transition-all duration-200 hover:text-primary hover:opacity-100">
{{ contact.beian }}
</NuxtLink>
</p>
<!-- 框架与技术栈信息 -->
<p class="text-text-muted text-xs m-0">
Powered by
<a
href="https://nuxt.com"
target="_blank"
rel="noreferrer"
class="text-primary hover:text-accent transition-colors"
>Nuxt 4</a
>
·
<a
href="https://tailwindcss.com"
target="_blank"
rel="noreferrer"
class="text-primary hover:text-accent transition-colors"
>Tailwind CSS</a
>
·
<a
href="https://vuejs.org"
target="_blank"
rel="noreferrer"
class="text-primary hover:text-accent transition-colors"
>Vue 3</a
>
</p>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-if="contact?.customHtml" v-html="contact.customHtml" />
</footer>
</template>
<style>
/* we will explain what these classes do next! */
.v-enter-active,
.v-leave-active {
transition: all 0.4s;
}
<script setup>
import { onMounted, ref } from "vue";
import { useRuntimeConfig } from "#imports";
import siteConfig from "~/config";
const contact = siteConfig.footer || {};
const config = useRuntimeConfig();
const quote = ref("");
const from = ref("");
const pageviews = ref(0);
const visitors = ref(0);
const statsError = ref(true);
const showHitokoto = siteConfig.footer?.hitokoto?.enable;
const showStats = ref(siteConfig.umami?.enable);
.v-enter-from,
.v-leave-to {
opacity: 0;
filter: blur(1rem);
}
</style>
const buildHitokotoUrl = () => {
const type = siteConfig.footer?.hitokoto?.type;
const url = new URL("https://v1.hitokoto.cn/");
if (Array.isArray(type)) {
type.filter(Boolean).forEach((t) => url.searchParams.append("c", t));
} else if (typeof type === "string") {
type
.split("&")
.map((t) => t.trim())
.filter(Boolean)
.forEach((t) => url.searchParams.append("c", t));
}
return url.toString();
};
const fetchHitokoto = async () => {
try {
const resp = await fetch(buildHitokotoUrl());
const data = await resp.json();
quote.value = data.hitokoto || "";
from.value = data.from || "";
} catch (e) {
console.warn("Hitokoto fetch failed", e);
}
};
const fetchStats = async () => {
try {
if (!siteConfig.umami?.apiBase || !siteConfig.umami?.websiteId) {
return;
}
const apiBase = siteConfig.umami.apiBase;
const websiteId = siteConfig.umami.websiteId;
const apiKey = config.public.umamiApiKey;
if (!apiKey) return;
// 获取统计数据
const endAt = Date.now();
const startAt = new Date(siteConfig.siteMeta.startDate).getTime();
const resp = await fetch(
`${apiBase}/v1/websites/${websiteId}/stats?startAt=${startAt}&endAt=${endAt}`,
{
headers: {
Authorization: `Bearer ${apiKey}`,
},
},
);
if (!resp.ok) {
console.warn(`Stats API returned ${resp.status}`);
statsError.value = true;
return;
}
const data = await resp.json();
if (data) {
statsError.value = false;
pageviews.value = data.pageviews;
visitors.value = data.visitors;
}
if (pageviews.value === 0 && visitors.value === 0) {
showStats.value = false;
}
} catch (e) {
statsError.value = true;
console.debug("Stats fetch failed (this is normal if blocked by ad blocker):", e.message);
}
};
onMounted(() => {
if (showHitokoto) fetchHitokoto();
if (showStats.value) fetchStats();
});
</script>

View File

@@ -1,72 +1,111 @@
<script setup lang="ts">
import { navbarData } from '../../data'
import siteConfig from "~/config";
const colorMode = useColorMode()
const colorMode = useColorMode();
function onClick(val: string) {
colorMode.preference = val
colorMode.preference = val;
}
const route = useRoute()
const route = useRoute();
function isActive(path: string) {
return route.path.startsWith(path)
return route.path === path || route.path.startsWith(path + "/");
}
</script>
<template>
<div class="py-5 border-b dark:border-gray-800 font-semibold">
<div class="flex px-6 container max-w-5xl justify-between mx-auto items-baseline">
<ul class="flex items-baseline space-x-5">
<li class="text-base sm:text-2xl font-bold">
<NuxtLink to="/" :class="{ underline: $route.path === '/' }">
{{ navbarData.homeTitle }}
</NuxtLink>
</li>
</ul>
<ul class="flex items-center space-x-3 sm:space-x-6 text-sm sm:text-lg">
<li>
<NuxtLink to="/blogs" :class="{ underline: isActive('/blogs') }"> Blogs </NuxtLink>
</li>
<li>
<NuxtLink to="/categories" :class="{ underline: isActive('/categories') }">
Categories
</NuxtLink>
</li>
<li title="About Me">
<NuxtLink
to="/about"
aria-label="About me"
:class="{ underline: $route.path === '/about' }"
>
About
</NuxtLink>
</li>
<li>
<ClientOnly>
<button
v-if="colorMode.value === 'light'"
name="light-mode"
title="Light"
class="hover:scale-110 transition-all ease-out hover:cursor-pointer"
@click="onClick('dark')"
>
<Icon name="icon-park:moon" size="20" class="-translate-y-[-20%]" />
</button>
<button
v-if="colorMode.value === 'dark'"
name="dark-mode"
title="Dark"
class="hover:scale-110 transition-all ease-out hover:cursor-pointer"
@click="onClick('light')"
>
<Icon name="noto:sun" size="20" class="-translate-y-[-20%]" />
</button>
<template #fallback>
<!-- this will be rendered on server side -->
<Icon name="svg-spinners:180-ring" size="20" class="-translate-y-[-20%]" />
</template>
</ClientOnly>
</li>
</ul>
<header class="fixed top-0 left-0 right-0 z-50 h-16 backdrop-blur-sm font-semibold">
<div class="flex justify-center px-6 container max-w-5xl mx-auto items-center h-full">
<nav class="items-center">
<div
class="inline-flex items-center gap-3 bg-white/50 dark:bg-slate-900/50 backdrop-blur-xl shadow-[0_4px_20px_-2px_rgba(0,0,0,0.1)] border border-white/40 dark:border-white/5 rounded-full px-3 py-2 transition-all duration-300 hover:shadow-xl">
<div class="hidden sm:flex items-center justify-center h-8 w-8 rounded-full bg-white/0">
<NuxtLink
to="/"
class="relative rounded-full hover:bg-violet-400/40 dark:hover:bg-violet-200/40 p-1">
<Icon name="fa-solid:cat" size="16" class="text-zinc-700 dark:text-zinc-200" />
</NuxtLink>
</div>
<ul class="flex items-center space-x-3 sm:space-x-6 text-sm sm:text-lg">
<li v-for="link in siteConfig.navbar.links" :key="link.path">
<NuxtLink
:to="link.path"
class="relative px-2 py-1 rounded-full transition-all duration-200 flex items-center"
:class="{
'bg-gradient-to-r from-violet-500/80 to-fuchsia-500/80 text-white shadow-md scale-105':
isActive(link.path),
'hover:bg-white/50 dark:hover:bg-white/10': !isActive(link.path),
}">
<Icon
v-if="link.icon"
:name="link.icon"
size="16"
class="text-zinc-700 dark:text-zinc-200 mr-2" />
<span class="hidden sm:inline-block px-auto">{{ link.name }}</span>
<span class="sm:hidden">{{ link.name }}</span>
</NuxtLink>
</li>
</ul>
<div class="ml-2 flex items-center">
<ClientOnly>
<button
:title="colorMode.value === 'light' ? '切换到深色模式' : '切换到浅色模式'"
class="relative h-9 w-9 rounded-full bg-white/20 dark:bg-white/5 flex items-center justify-center transition-all duration-300 hover:scale-110 hover:bg-white/40 dark:hover:bg-white/10 focus:outline-none shadow-sm border border-white/20"
@click="onClick(colorMode.value === 'light' ? 'dark' : 'light')">
<Icon
name="fa-regular:sun"
size="18"
class="icon-svg transition-all duration-300 text-yellow-400"
:class="
colorMode.value === 'light'
? 'opacity-100 scale-100'
: 'opacity-0 scale-75 -translate-y-1'
" />
<Icon
name="fa-regular:moon"
size="18"
class="icon-svg absolute transition-all duration-300 text-indigo-200"
:class="
colorMode.value === 'dark'
? 'opacity-100 scale-100'
: 'opacity-0 scale-75 translate-y-1'
" />
</button>
<template #fallback>
<Icon name="fa-solid:spinner" size="18" class="icon-svg text-zinc-400" />
</template>
</ClientOnly>
</div>
</div>
</nav>
</div>
</div>
</header>
<!-- Spacer to prevent page content being hidden under fixed header -->
<div class="h-16" aria-hidden="true"></div>
</template>
<style scoped>
.icon-svg {
display: inline-block;
width: 1.125rem;
/* 18px */
height: 1.125rem;
}
.icon-svg svg {
width: 100%;
height: 100%;
transition:
transform 0.25s ease,
opacity 0.25s ease,
fill 0.25s ease;
}
.icon-svg.opacity-0 {
pointer-events: none;
}
</style>

View File

@@ -1,23 +1,57 @@
<script setup lang="ts">
import { homePage } from '~/data'
import siteConfig from "~/config";
import SocialLinks from "./SocialLinks.vue";
</script>
<template>
<div class="container mx-auto">
<div class="grid grid-cols-1 sm:grid-cols-2 items-center">
<div class="px-6">
<h1
class="text-black dark:text-zinc-300 font-semibold leading-tight text-4xl md:text-5xl my-5"
>
{{ homePage.title }}
</h1>
<p class="dark:text-zinc-300">
{{ homePage.description }}
</p>
</div>
<div class="px-6 justify-self-center">
<LogoDog />
<section class="relative w-full min-h-[calc(100vh-4rem)] overflow-hidden bg-transparent">
<div
class="container max-w-5xl mx-auto px-6 min-h-[calc(100vh-4rem)] flex items-center justify-center">
<div class="relative z-10 flex flex-col items-center text-center">
<div class="mb-8">
<div class="relative h-32 w-32 group">
<div
class="absolute inset-0 bg-gradient-to-tr from-violet-500 to-fuchsia-500 rounded-full blur-xl opacity-30 group-hover:opacity-50 transition-opacity duration-500"></div>
<NuxtImg
:src="siteConfig.profile.avatar"
alt="avatar"
class="relative h-full w-full object-cover rounded-full border-4 border-white/80 dark:border-slate-800/80 shadow-2xl transition-transform duration-500 group-hover:scale-105"
loading="eager" />
</div>
</div>
<div
class="group rounded-3xl bg-white/40 dark:bg-slate-900/40 backdrop-blur-2xl border border-white/30 dark:border-white/10 p-8 shadow-[0_8px_32px_0_rgba(31,38,135,0.07)] transition-all duration-500 hover:shadow-[0_8px_32px_0_rgba(31,38,135,0.15)] hover:-translate-y-1">
<h1
class="max-w-2xl text-4xl sm:text-5xl md:text-6xl font-extrabold leading-tight text-zinc-800 dark:text-white drop-shadow-sm relative tracking-tight">
{{ siteConfig.hero.title }}
</h1>
<div class="mt-4 flex justify-center">
<div
class="h-1 w-12 bg-gradient-to-r from-violet-500 to-fuchsia-500 rounded-full opacity-60"></div>
</div>
<p
class="mt-4 max-w-2xl text-base sm:text-lg text-zinc-600 dark:text-zinc-300 relative font-medium leading-relaxed">
{{ siteConfig.hero.description }}
</p>
</div>
<div class="mt-6 w-full flex items-center justify-center">
<SocialLinks />
</div>
</div>
</div>
</div>
<!-- Decorative wave -->
<!-- <div class="pointer-events-none absolute inset-x-0 bottom-0 -mb-1">
<svg viewBox="0 0 1440 120" class="w-full h-20" preserveAspectRatio="none">
<path
d="M0,32L48,42.7C96,53,192,75,288,90.7C384,107,480,117,576,117.3C672,117,768,107,864,101.3C960,96,1056,96,1152,80C1248,64,1344,32,1392,16L1440,0L1440,120L1392,120C1344,120,1248,120,1152,120C1056,120,960,120,864,120C768,120,672,120,576,120C480,120,384,120,288,120C192,120,96,120,48,120L0,120Z"
fill="rgba(255,255,255,0.14)"
/>
</svg>
</div> -->
</section>
</template>

View File

@@ -1,82 +1,101 @@
<script lang="ts" setup>
import type { BlogPost } from '~/types/blog'
import type { BlogPost } from "~/types/blog";
// Function to parse dates in the format "1st Mar 2023"
function parseCustomDate(dateStr: string): Date {
// Remove ordinal indicators (st, nd, rd, th)
const cleanDateStr = dateStr.replace(/(\d+)(st|nd|rd|th)/, '$1')
const cleanDateStr = dateStr.replace(/(\d+)(st|nd|rd|th)/, "$1");
// Parse the date
return new Date(cleanDateStr)
return new Date(cleanDateStr);
}
// Get Last 6 Publish Post from the content/blog directory
const { data } = await useAsyncData('recent-post', () =>
queryCollection('content')
const { data } = await useAsyncData("recent-post", () =>
queryCollection("content")
.all()
.then((data) => {
return data
.sort((a, b) => {
const aDate = parseCustomDate(a.meta.date as string)
const bDate = parseCustomDate(b.meta.date as string)
return bDate.getTime() - aDate.getTime()
const aDate = parseCustomDate(a.meta.date as string);
const bDate = parseCustomDate(b.meta.date as string);
return bDate.getTime() - aDate.getTime();
})
.slice(0, 3)
.slice(0, 3);
}),
)
);
const formattedData = computed(() => {
return data.value?.map((articles) => {
const meta = articles.meta as unknown as BlogPost
const meta = articles.meta as unknown as BlogPost;
return {
path: articles.path,
title: articles.title || 'no-title available',
description: articles.description || 'no-description available',
image: meta.image || '/not-found.jpg',
alt: meta.alt || 'no alter data available',
ogImage: meta.ogImage || '/not-found.jpg',
date: meta.date || 'not-date-available',
title: articles.title || "no-title available",
description: articles.description || "no-description available",
image: meta.image || "/not-found.jpg",
alt: meta.alt || "no alter data available",
ogImage: meta.ogImage || "/not-found.jpg",
date: meta.date || "not-date-available",
tags: meta.tags || [],
published: meta.published || false,
}
})
})
};
});
});
useHead({
title: 'Home',
title: "Home",
meta: [
{
name: 'description',
name: "description",
content:
'Welcome To My Blog Site. Get Web Development, Javascript, Typescript, NodeJs, Vue, and Nuxt, Related Articles, Tips, Learning resources and more.',
"Welcome To My Blog Site. Get Web Development, Javascript, Typescript, NodeJs, Vue, and Nuxt, Related Articles, Tips, Learning resources and more.",
},
],
})
});
</script>
<template>
<div class="pb-10 px-4">
<div class="flex flex-row items-center space-x-3 pt-5 pb-3">
<Icon name="mdi:star-three-points-outline" size="2em" class="text-black dark:text-zinc-300" />
<h2 class="text-4xl font-semibold text-black dark:text-zinc-300">Recent Post</h2>
<section class="pb-10 px-4">
<div class="flex flex-row items-center justify-between pt-10 pb-6">
<div class="flex items-center space-x-3">
<div class="p-2 bg-violet-500/10 rounded-lg">
<Icon
name="mdi:star-three-points-outline"
size="1.5em"
class="text-violet-600 dark:text-violet-400" />
</div>
<h2 class="text-3xl font-bold text-zinc-800 dark:text-zinc-100 tracking-tight">
Recent Posts
</h2>
</div>
<NuxtLink
to="/blogs"
class="group flex items-center gap-1 text-sm font-semibold text-violet-600 dark:text-violet-400 hover:text-violet-700 transition-colors">
查看全部文章
<Icon
name="heroicons:arrow-right-20-solid"
class="transition-transform group-hover:translate-x-1" />
</NuxtLink>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<template v-for="post in formattedData" :key="post.title">
<BlogCard
:path="post.path"
:title="post.title"
:date="post.date"
:description="post.description"
:image="post.image"
:alt="post.alt"
:og-image="post.ogImage"
:tags="post.tags"
:published="post.published"
/>
<div
class="transition-transform transform hover:scale-[1.02] hover:shadow-lg rounded-lg overflow-hidden">
<BlogCard
:path="post.path"
:title="post.title"
:date="post.date"
:description="post.description"
:image="post.image"
:alt="post.alt"
:og-image="post.ogImage"
:tags="post.tags"
:published="post.published" />
</div>
</template>
<template v-if="data?.length === 0">
<BlogEmpty />
</template>
</div>
</div>
</section>
</template>

View File

@@ -1,62 +0,0 @@
<script lang="ts" setup>
import type { BlogPost } from '~/types/blog'
const { data } = await useAsyncData('trending-post', () =>
queryCollection('content').limit(3).all(),
)
const formattedData = computed(() => {
return data.value?.map((articles) => {
const meta = articles.meta as unknown as BlogPost
return {
path: articles.path,
title: articles.title || 'no-title available',
description: articles.description || 'no-description available',
image: meta.image || '/not-found.jpg',
alt: meta.alt || 'no alter data available',
ogImage: meta.ogImage || '/not-found.jpg',
date: meta.date || 'not-date-available',
tags: meta.tags || [],
published: meta.published || false,
}
})
})
useHead({
title: 'Home',
meta: [
{
name: 'description',
content:
'Welcome To My Blog Site. Get Web Development, Javascript, Typescript, NodeJs, Vue, and Nuxt, Related Articles, Tips, Learning resources and more.',
},
],
})
</script>
<template>
<div class="px-4">
<div class="flex flex-row items-center space-x-3 pt-5 pb-3">
<Icon name="mdi:star-three-points-outline" size="2em" class="text-black dark:text-zinc-300" />
<h2 class="text-4xl font-semibold text-black dark:text-zinc-300">Trending Post</h2>
</div>
<div class="grid grid-cols-1">
<template v-for="post in formattedData" :key="post.title">
<ArchiveCard
:path="post.path"
:title="post.title"
:date="post.date"
:description="post.description"
:image="post.image"
:alt="post.alt"
:og-image="post.ogImage"
:tags="post.tags"
:published="post.published"
/>
</template>
<template v-if="data?.length === 0">
<BlogEmpty />
</template>
</div>
</div>
</template>