refactor: 清理无用资源并更新项目配置
删除大量未使用的图标、图片和组件文件 更新.gitignore、tsconfig.json和astro配置 添加新的工具函数和UI组件 修改项目元数据和依赖项
This commit is contained in:
@@ -1,47 +0,0 @@
|
||||
---
|
||||
import Social from "../ui/Social.astro";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { AstroError } from "astro/errors";
|
||||
import { getCollection} from "astro:content";
|
||||
|
||||
const [staticData] = await getCollection('staticData');
|
||||
|
||||
if (!staticData) {
|
||||
throw new AstroError("JSON data not found");
|
||||
}
|
||||
---
|
||||
|
||||
<footer
|
||||
class="relative bottom-0 w-full px-4 py-8 font-medium text-blacktext dark:bg-transparent dark:border-b-2 dark:border-zinc-800 dark:text-zinc-300 max-lg:mt-3"
|
||||
role="contentinfo"
|
||||
aria-label="Site footer"
|
||||
>
|
||||
<nav
|
||||
class="mx-auto flex max-w-7xl flex-row items-center justify-between gap-4 text-xl max-xl:px-6 max-sm:flex-col"
|
||||
aria-label="Footer navigation"
|
||||
>
|
||||
<div
|
||||
class="relative h-6 cursor-pointer before:absolute before:left-1/2 before:top-1/2 before:h-full before:w-[40%] before:-translate-x-1/2 before:-translate-y-1/2 before:rounded-full before:bg-[#50fd8f25] before:blur-3xl before:opacity-80 before:-z-1 hover:text-mint-500 transition-all [text-shadow:0_1px_2px_#000]"
|
||||
>
|
||||
<a href="/" aria-label="Return to homepage">
|
||||
<!-- This icon represents the logo -->
|
||||
<Icon name="logo" aria-hidden="true" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<a
|
||||
href={staticData.data.github}
|
||||
class="flex items-center justify-center gap-3 text-base font-normal italic max-sm:text-sm"
|
||||
aria-label="About the website development"
|
||||
><Icon name="code" aria-hidden="true" /> Developed by <strong>{staticData.data.alias}</strong>, with Astro</a
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center justify-center gap-5" role="list" aria-label="Social media links">
|
||||
<Social link={`mailto:${staticData.data.email}`} iconName={staticData.data.emailIconName} label={`Send email to ${staticData.data.email}`} />
|
||||
<Social link={staticData.data.instagram} iconName={staticData.data.instagramIconName} label={`Visit ${staticData.data.alias} on Instagram`} />
|
||||
<Social link={staticData.data.youtube} iconName={staticData.data.youtubeIconName} label={`Visit ${staticData.data.alias} on YouTube`} />
|
||||
<Social link={staticData.data.github} iconName={staticData.data.githubIconName} label={`Visit ${staticData.data.alias} on GitHub`} />
|
||||
<Social link={staticData.data.linkedin} iconName={staticData.data.linkedinIconName} label={`Visit ${staticData.data.alias} on LinkedIn`} />
|
||||
</div>
|
||||
</nav>
|
||||
</footer>
|
||||
@@ -1,63 +0,0 @@
|
||||
---
|
||||
import Navigation from "./Navigation.astro";
|
||||
import ThemeIcon from "../ui/ThemeIcon.astro";
|
||||
import Social from "../ui/Social.astro";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { AstroError } from "astro/errors";
|
||||
import { getCollection} from "astro:content";
|
||||
|
||||
const [staticData] = await getCollection('staticData');
|
||||
|
||||
if (!staticData) {
|
||||
throw new AstroError("JSON data not found");
|
||||
}
|
||||
|
||||
const currentPath = Astro.url.pathname;
|
||||
const routes = ["/", "/portfolio", "/about-me", ];
|
||||
|
||||
// Check if the current route is in the list of routes
|
||||
const isActiveRoute = routes.includes(currentPath);
|
||||
|
||||
const navItems = isActiveRoute
|
||||
? ["home", "experience", "projects", "about", "blog",]
|
||||
: ["home", "blog", "about"]; // Change the items
|
||||
---
|
||||
|
||||
<header
|
||||
role="banner"
|
||||
aria-label="Main navigation"
|
||||
class="sticky top-0 z-50 w-full p-4 font-medium text-blacktext dark:text-zinc-300 dark:bg-[#0E0E11]/80 dark:border-b dark:border-zinc-800 bg-white/90 backdrop-blur-xs dark:backdrop-blur-xs max-md:z-50 max-md:px-0 transition-all"
|
||||
>
|
||||
<div
|
||||
class="relative mx-auto flex max-w-7xl flex-row items-center justify-between max-xl:px-6"
|
||||
>
|
||||
<a href="/" aria-label="Go to home">
|
||||
<Icon
|
||||
name="logo"
|
||||
class="h-6 cursor-pointer transition-all hover:text-mint-300"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<Navigation items={navItems} />
|
||||
|
||||
<div class="flex items-center justify-between gap-5 text-xl">
|
||||
<div class="max-md:hidden flex items-center justify-center gap-5" role="list">
|
||||
<Social link={staticData.data.github} iconName={staticData.data.githubIconName} />
|
||||
<Social link={staticData.data.linkedin} iconName={staticData.data.linkedinIconName} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-5 text-xl md:pl-5">
|
||||
<ThemeIcon />
|
||||
<button
|
||||
class="hamburger"
|
||||
aria-label="Open menu"
|
||||
aria-expanded="false"
|
||||
aria-controls="mobile-menu"
|
||||
>
|
||||
<Icon name="bars" class="hamburger-icon bars-icon" aria-hidden="true" />
|
||||
<Icon name="xmark" class="hamburger-icon xmark-icon" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -1,92 +0,0 @@
|
||||
---
|
||||
const { title } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="max-xl:hidden ">
|
||||
<div id="nav-content" class="bg-white dark:bg-transparent sticky w-72 mt-8 rounded-2xl dark:border-0 border border-neutral-100 top-14 max-h-[calc(100svh-3.5rem)] overflow-x-hidden px-6 pt-8 pb-12">
|
||||
<div class="flex flex-col gap-4 pl-0">
|
||||
<div>
|
||||
<h3 class="dark:text-zinc-400 text-blacktext/90 font-black tracking-wide text-md uppercase">Table of Contents</h3>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 pr-6 text-neutral-500 dark:text-neutral-300 ">
|
||||
<ul id="toc-list" class="leading-loose text-base gap-2 border-l dark:border-neutral-500/20 border-blacktext/20">
|
||||
<li class="leading-loose">
|
||||
<a class="inline-block leading-5 pl-4 font-bold text-white border-l dark:border-white border-blacktext dark:hover:border-white hover:border-blacktext" href="#">{title}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const tocList = document.getElementById("toc-list");
|
||||
const content = document.getElementById("content");
|
||||
|
||||
if (!tocList || !content) return;
|
||||
|
||||
const headers = content.querySelectorAll("h2, h3");
|
||||
let currentUl = tocList;
|
||||
|
||||
headers.forEach((header, index) => {
|
||||
if (!header.id) {
|
||||
header.id = header.textContent?.trim().toLowerCase().replace(/\s+/g, "-") + "-" + index;
|
||||
// 👈 Add the class
|
||||
}
|
||||
|
||||
const li = document.createElement("li");
|
||||
const link = document.createElement("a");
|
||||
link.href = `#${header.id}`;
|
||||
link.textContent = header.textContent?.trim() || header.id;
|
||||
|
||||
// If the header is H2, keep pl-6, and if it's H3, use pl-12
|
||||
link.classList.add("inline-block","leading-5", "hover:text-mint-400", "py-2", "border-l", "border-transparent", "dark:hover:border-white","hover:border-blacktext");
|
||||
link.classList.add(header.tagName === "H2" ? "pl-6" : "pl-12");
|
||||
console.log("classes removed 2");
|
||||
li.appendChild(link);
|
||||
|
||||
if (header.tagName === "H2") {
|
||||
currentUl = document.createElement("ul");
|
||||
currentUl.classList.add("border-neutral-400","dark:hover:border-white","hover:border-blacktext", "pl-0");
|
||||
console.log("classes removed 3");
|
||||
const h2Li = document.createElement("li");
|
||||
h2Li.appendChild(link);
|
||||
h2Li.appendChild(currentUl);
|
||||
tocList.appendChild(h2Li);
|
||||
} else {
|
||||
currentUl.appendChild(li);
|
||||
}
|
||||
|
||||
// Smooth scroll when clicking on a link
|
||||
link.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
document.getElementById(header.id)?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
});
|
||||
});
|
||||
|
||||
// 👇 Detect the active header
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
const id = entry.target.getAttribute("id");
|
||||
const link = document.querySelector(`a[href="#${id}"]`);
|
||||
|
||||
if (entry.isIntersecting) {
|
||||
// Remove class from all and add only to the active one
|
||||
document.querySelectorAll("#toc-list a").forEach((el) => {
|
||||
el.classList.remove("font-semibold", "dark:text-mint-300!", "text-blacktext!", "dark:border-white!", "border-blacktext!" );
|
||||
el.classList.add("dark:text-neutral-300","text-neutral-500" );
|
||||
console.log("classes removed 4");
|
||||
});
|
||||
|
||||
link?.classList.add("font-semibold", "dark:text-mint-300!", "text-blacktext!", "border-l", "dark:border-white!", "border-blacktext!");
|
||||
}
|
||||
});
|
||||
},
|
||||
{ rootMargin: "-30% 0px -65% 0px", threshold: 0.1 } // Adjusted to improve visibility
|
||||
);
|
||||
|
||||
headers.forEach((header) => observer.observe(header));
|
||||
});
|
||||
</script>
|
||||
@@ -1,168 +0,0 @@
|
||||
---
|
||||
import Social from "../ui/Social.astro";
|
||||
import { AstroError } from "astro/errors";
|
||||
import { getCollection} from "astro:content";
|
||||
|
||||
const [staticData] = await getCollection('staticData');
|
||||
|
||||
if (!staticData) {
|
||||
throw new AstroError("JSON data not found");
|
||||
}
|
||||
|
||||
const { items = [] }: { items: (keyof typeof menu)[] } = Astro.props as {
|
||||
items: (keyof typeof menu)[];
|
||||
};
|
||||
|
||||
const menu = {
|
||||
about: { name: "About Me", path: "/about-me" },
|
||||
blog: {
|
||||
name: "Blog",
|
||||
path: "/blog/",
|
||||
dropdown: [
|
||||
{ name: "All Posts", path: "/blog/posts/" },
|
||||
]
|
||||
},
|
||||
home: { name: "Home", path: "/#home" },
|
||||
experience: { name: "Experience", path: "/#experience" },
|
||||
projects: { name: "Projects", path: "/#projects" },
|
||||
};
|
||||
|
||||
// Common base classes
|
||||
const baseClasses = {
|
||||
nav: "nav-links flex w-full justify-center gap-6 max-md:gap-3 max-md:py-6",
|
||||
link: "px-2 py-2 transition-all hover:text-mint-300 max-md:mx-auto max-md:w-full max-md:px-6 max-md:py-2 ",
|
||||
socialContainer: "flex items-center justify-center gap-5 md:hidden",
|
||||
dropdown: "relative group flex items-center",
|
||||
dropdownMenu: "absolute left-0 top-full hidden group-hover:block bg-white dark:bg-zinc-800 shadow-lg rounded-md py-2 min-w-[200px] z-50",
|
||||
dropdownItem: "block px-4 py-2 text-sm hover:bg-mint-100 dark:hover:bg-zinc-700 transition-colors"
|
||||
} as const;
|
||||
---
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const isHome = window.location.pathname === "/";
|
||||
|
||||
// Classes for link states
|
||||
const linkClasses = {
|
||||
active: ["text-mint-500","dark:text-mint-400", "font-bold", "[text-shadow:1px_1px_11px_rgba(208,251,229,0.7)]"],
|
||||
inactive: ["dark:text-zinc-300", "text-blacktext"]
|
||||
};
|
||||
|
||||
function toggleLinkClasses(link: Element, isActive: boolean) {
|
||||
if (isActive) {
|
||||
link.classList.add(...linkClasses.active);
|
||||
link.classList.remove(...linkClasses.inactive);
|
||||
link.setAttribute('aria-current', 'page');
|
||||
} else {
|
||||
link.classList.remove(...linkClasses.active);
|
||||
link.classList.add(...linkClasses.inactive);
|
||||
link.removeAttribute('aria-current');
|
||||
}
|
||||
}
|
||||
|
||||
function updateActiveLink() {
|
||||
const currentPath = window.location.pathname;
|
||||
const currentHash = window.location.hash ? `#${window.location.hash.substring(1)}` : "";
|
||||
|
||||
document.querySelectorAll("nav a").forEach((link) => {
|
||||
const path = link.getAttribute("data-path");
|
||||
toggleLinkClasses(link, path === currentPath || path === currentHash);
|
||||
});
|
||||
}
|
||||
|
||||
function setupScrollSpy() {
|
||||
if (!isHome) return;
|
||||
|
||||
const sections = document.querySelectorAll("section[id]");
|
||||
const navLinks = document.querySelectorAll("nav a");
|
||||
|
||||
const observerOptions = {
|
||||
root: null,
|
||||
rootMargin: "-50% 0px",
|
||||
threshold: 0
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const sectionId = entry.target.getAttribute("id");
|
||||
if (sectionId) {
|
||||
navLinks.forEach(link => {
|
||||
const path = link.getAttribute("data-path");
|
||||
toggleLinkClasses(link, path === `/#${sectionId}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
sections.forEach(section => observer.observe(section));
|
||||
}
|
||||
|
||||
updateActiveLink();
|
||||
setupScrollSpy();
|
||||
window.addEventListener("hashchange", updateActiveLink);
|
||||
});
|
||||
</script>
|
||||
|
||||
<nav
|
||||
class={baseClasses.nav}
|
||||
role="navigation"
|
||||
aria-label="Main Navigation"
|
||||
>
|
||||
{
|
||||
items.map((key: string) => {
|
||||
const item = menu[key as keyof typeof menu];
|
||||
if (!item) return null;
|
||||
|
||||
if ('dropdown' in item) {
|
||||
return (
|
||||
<div class={baseClasses.dropdown}>
|
||||
<a
|
||||
href={item.path}
|
||||
class={baseClasses.link}
|
||||
data-path={item.path}
|
||||
aria-label={item.name}
|
||||
aria-current={item.path === Astro.url.pathname ? 'page' : undefined}
|
||||
>
|
||||
{item.name}
|
||||
</a>
|
||||
<div class={baseClasses.dropdownMenu}>
|
||||
{item.dropdown.map((dropdownItem) => (
|
||||
<a
|
||||
href={dropdownItem.path}
|
||||
class={baseClasses.dropdownItem}
|
||||
data-path={dropdownItem.path}
|
||||
aria-label={dropdownItem.name}
|
||||
>
|
||||
{dropdownItem.name}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
href={item.path}
|
||||
class={baseClasses.link}
|
||||
data-path={item.path}
|
||||
aria-label={item.name}
|
||||
aria-current={item.path === Astro.url.pathname ? 'page' : undefined}
|
||||
>
|
||||
{item.name}
|
||||
</a>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
<div
|
||||
class={baseClasses.socialContainer}
|
||||
role="group"
|
||||
aria-label="Social Media Links"
|
||||
>
|
||||
<Social link={staticData.data.github} iconName={staticData.data.githubIconName} />
|
||||
<Social link={staticData.data.linkedin} iconName={staticData.data.linkedinIconName} />
|
||||
</div>
|
||||
</nav>
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
const allPosts = await Astro.glob("../../pages/blog/posts/*.md");
|
||||
|
||||
|
||||
// Ensure posts have a date before sorting them
|
||||
const sortedPosts = allPosts
|
||||
.filter(post => post.frontmatter.pubDate)
|
||||
.sort((a, b) => new Date(b.frontmatter.pubDate).getTime() - new Date(a.frontmatter.pubDate).getTime());
|
||||
|
||||
const currentSlug = Astro.url.pathname.split("/").filter(Boolean).pop();
|
||||
const currentIndex = sortedPosts.findIndex((post) =>
|
||||
post.url && post.url.includes(currentSlug || "")
|
||||
);
|
||||
const nextPost = currentIndex > 0 ? sortedPosts[currentIndex - 1] : null;
|
||||
const prevPost = currentIndex < sortedPosts.length - 1 ? sortedPosts[currentIndex + 1] : null;
|
||||
|
||||
console.log({ prevPost, nextPost });
|
||||
---
|
||||
|
||||
<nav class="mt-8 flex flex-row gap-2 w-full p-6 max-xl:p-3 max-lg:p-2">
|
||||
{prevPost && (
|
||||
<a
|
||||
href={prevPost.url}
|
||||
style="width: -webkit-fill-available;"
|
||||
class="relative flex min-w-1/2 items-center justify-start gap-2 font-semibold dark:text-white text-blacktext text-left text-pretty max-sm:text-xs max-md:text-sm max-md:leading-4 hover:text-mint-300 hover:[text-shadow:1px_1px_11px_rgba(208,251,229,0.7)] transition-all before:absolute before:-top-5 before:left-0 before:text-sm before:font-light before:content-['Previous_Post']"
|
||||
>
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6.85355 3.85355C7.04882 3.65829 7.04882 3.34171 6.85355 3.14645C6.65829 2.95118 6.34171 2.95118 6.14645 3.14645L2.14645 7.14645C1.95118 7.34171 1.95118 7.65829 2.14645 7.85355L6.14645 11.8536C6.34171 12.0488 6.65829 12.0488 6.85355 11.8536C7.04882 11.6583 7.04882 11.3417 6.85355 11.1464L3.20711 7.5L6.85355 3.85355ZM12.8536 3.85355C13.0488 3.65829 13.0488 3.34171 12.8536 3.14645C12.6583 2.95118 12.3417 2.95118 12.1464 3.14645L8.14645 7.14645C7.95118 7.34171 7.95118 7.65829 8.14645 7.85355L12.1464 11.8536C12.3417 12.0488 12.6583 12.0488 12.8536 11.8536C13.0488 11.6583 13.0488 11.3417 12.8536 11.1464L9.20711 7.5L12.8536 3.85355Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
|
||||
{prevPost.frontmatter.title}
|
||||
</a>
|
||||
)}
|
||||
|
||||
{nextPost && (
|
||||
<a
|
||||
href={nextPost.url}
|
||||
style="width: -webkit-fill-available;"
|
||||
class="relative flex min-w-1/2 items-center justify-end gap-2 font-semibold dark:text-white text-blacktext text-right text-pretty max-sm:text-xs max-md:text-sm max-md:leading-4 hover:text-mint-300 hover:[text-shadow:1px_1px_11px_rgba(208,251,229,0.7)] transition-all before:absolute before:-top-5 before:right-0 before:text-sm before:font-light before:content-['Next_Post']"
|
||||
>
|
||||
{nextPost.frontmatter.title}
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2.14645 11.1464C1.95118 11.3417 1.95118 11.6583 2.14645 11.8536C2.34171 12.0488 2.65829 12.0488 2.85355 11.8536L6.85355 7.85355C7.04882 7.65829 7.04882 7.34171 6.85355 7.14645L2.85355 3.14645C2.65829 2.95118 2.34171 2.95118 2.14645 3.14645C1.95118 3.34171 1.95118 3.65829 2.14645 3.85355L5.79289 7.5L2.14645 11.1464ZM8.14645 11.1464C7.95118 11.3417 7.95118 11.6583 8.14645 11.8536C8.34171 12.0488 8.65829 12.0488 8.85355 11.8536L12.8536 7.85355C13.0488 7.65829 13.0488 7.34171 12.8536 7.14645L8.85355 3.14645C8.65829 2.95118 8.34171 2.95118 8.14645 3.14645C7.95118 3.34171 7.95118 3.65829 8.14645 3.85355L11.7929 7.5L8.14645 11.1464Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
|
||||
</a>
|
||||
)}
|
||||
</nav>
|
||||
Reference in New Issue
Block a user