Compare commits
3 Commits
51eac1c2ef
...
0e6f611455
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e6f611455 | ||
|
|
99c41d537f | ||
|
|
551877780f |
@@ -64,8 +64,8 @@ const sortedPosts = posts
|
|||||||
|
|
||||||
const currentIndex = sortedPosts.findIndex((post) => post.slug === currentSlug);
|
const currentIndex = sortedPosts.findIndex((post) => post.slug === currentSlug);
|
||||||
|
|
||||||
const nextPost = currentIndex > 0 ? sortedPosts[currentIndex - 1] : null;
|
const nextPost = currentIndex < sortedPosts.length - 1 ? sortedPosts[currentIndex + 1] : null;
|
||||||
const prevPost = currentIndex < sortedPosts.length - 1 ? sortedPosts[currentIndex + 1] : null;
|
const prevPost = currentIndex > 0 ? sortedPosts[currentIndex - 1] : null;
|
||||||
|
|
||||||
const prevText = lang === 'zh' ? '上一篇' : 'Previous Post';
|
const prevText = lang === 'zh' ? '上一篇' : 'Previous Post';
|
||||||
const nextText = lang === 'zh' ? '下一篇' : 'Next Post';
|
const nextText = lang === 'zh' ? '下一篇' : 'Next Post';
|
||||||
|
|||||||
46
src/components/ui/back-to-top.tsx
Normal file
46
src/components/ui/back-to-top.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { ArrowUp } from "lucide-react";
|
||||||
|
import { Button } from "./button";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export default function BackToTop() {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const toggleVisibility = () => {
|
||||||
|
if (window.scrollY > 300) {
|
||||||
|
setIsVisible(true);
|
||||||
|
} else {
|
||||||
|
setIsVisible(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("scroll", toggleVisibility);
|
||||||
|
return () => window.removeEventListener("scroll", toggleVisibility);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const scrollToTop = () => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`fixed bottom-8 right-8 z-50 transition-all duration-300 ${
|
||||||
|
isVisible
|
||||||
|
? "translate-y-0 opacity-100"
|
||||||
|
: "translate-y-4 opacity-0 pointer-events-none"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
onClick={scrollToTop}
|
||||||
|
className="rounded-full shadow-lg bg-primary/90 backdrop-blur-sm border border-border/50 hover:bg-primary hover:shadow-xl hover:-translate-y-1 transition-all h-10 w-10"
|
||||||
|
>
|
||||||
|
<ArrowUp className="h-5 w-5 text-primary-foreground" />
|
||||||
|
<span className="sr-only">Back to top</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -49,6 +49,7 @@ export const translations = {
|
|||||||
},
|
},
|
||||||
blog: {
|
blog: {
|
||||||
slogan: 'This is where innovative thinking meets complex problems.',
|
slogan: 'This is where innovative thinking meets complex problems.',
|
||||||
|
backToList: 'Back to Blog',
|
||||||
},
|
},
|
||||||
services: {
|
services: {
|
||||||
title: 'What I Do',
|
title: 'What I Do',
|
||||||
@@ -105,6 +106,7 @@ export const translations = {
|
|||||||
},
|
},
|
||||||
blog: {
|
blog: {
|
||||||
slogan: '这里是创新思维与复杂问题相遇的地方。',
|
slogan: '这里是创新思维与复杂问题相遇的地方。',
|
||||||
|
backToList: '返回博客列表',
|
||||||
},
|
},
|
||||||
services: {
|
services: {
|
||||||
title: '我能做什么',
|
title: '我能做什么',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { MarkdownLayoutProps } from 'astro';
|
|||||||
import { type Lang } from '@/types/i18n';
|
import { type Lang } from '@/types/i18n';
|
||||||
import { type FrontmatterProps } from '@/types';
|
import { type FrontmatterProps } from '@/types';
|
||||||
import { defaultLang } from '@/i18n/ui';
|
import { defaultLang } from '@/i18n/ui';
|
||||||
|
import { useTranslations } from '@/i18n/utils';
|
||||||
import GlassHeader from '@/components/GlassHeader';
|
import GlassHeader from '@/components/GlassHeader';
|
||||||
import Footer from '@/components/Footer';
|
import Footer from '@/components/Footer';
|
||||||
import AuthorCard from '@/components/AuthorCard';
|
import AuthorCard from '@/components/AuthorCard';
|
||||||
@@ -31,6 +32,9 @@ const {
|
|||||||
} = frontmatter;
|
} = frontmatter;
|
||||||
|
|
||||||
const lang = Astro.currentLocale as Lang || defaultLang;
|
const lang = Astro.currentLocale as Lang || defaultLang;
|
||||||
|
const t = useTranslations(lang);
|
||||||
|
|
||||||
|
const blogListUrl = lang === 'zh' ? '/zh/blog' : '/blog';
|
||||||
|
|
||||||
// Handle different field names for backward compatibility
|
// Handle different field names for backward compatibility
|
||||||
const finalPublishDate = publishDate || date;
|
const finalPublishDate = publishDate || date;
|
||||||
@@ -51,6 +55,23 @@ const finalReadingTime = readTime ? parseInt(readTime.replace(/\D/g, '')) : unde
|
|||||||
|
|
||||||
<!-- Blog post header -->
|
<!-- Blog post header -->
|
||||||
<header class="mb-10">
|
<header class="mb-10">
|
||||||
|
<a
|
||||||
|
href={blogListUrl}
|
||||||
|
class="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-primary transition-colors duration-200 mb-6 group"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 15 15"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="group-hover:-translate-x-1 transition-transform duration-200"
|
||||||
|
>
|
||||||
|
<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.85355Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
{t('blog.backToList')}
|
||||||
|
</a>
|
||||||
|
|
||||||
<PostMeta
|
<PostMeta
|
||||||
lang={lang}
|
lang={lang}
|
||||||
publishDate={finalPublishDate}
|
publishDate={finalPublishDate}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
import BackToTop from "@/components/ui/back-to-top";
|
||||||
import { useTranslations } from "@/i18n/utils";
|
import { useTranslations } from "@/i18n/utils";
|
||||||
import type { Lang } from "@/types/i18n";
|
import type { Lang } from "@/types/i18n";
|
||||||
import { defaultLang } from "@/i18n/ui";
|
import { defaultLang } from "@/i18n/ui";
|
||||||
@@ -44,6 +45,7 @@ const t = useTranslations(lang);
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<slot />
|
<slot />
|
||||||
|
<BackToTop client:load />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user