5 Commits

Author SHA1 Message Date
typist
d553c3e04c 0.0.17
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m1s
2025-10-29 08:27:15 +08:00
typist
e2da2758cc chore: simplify build script in package.json
- Removed sitemap generation step from the build script for a more streamlined build process.
2025-10-29 08:27:00 +08:00
typist
3ab70498e6 feat: add ModeToggle component to layout and sidebar
- Integrated ModeToggle component into the layout for theme switching.
- Replaced the existing button in the sidebar footer with SidebarMenuButton for improved styling and functionality.
2025-10-29 08:26:26 +08:00
typist
a5ef1a1e70 0.0.16
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m7s
2025-10-29 08:16:17 +08:00
typist
297000f208 feat: integrate SEO hook for dynamic metadata management
- Removed hardcoded canonical link from index.html.
- Implemented useSEO hook in layout.tsx to dynamically update SEO metadata including title, description, and canonical URL based on the current route.
- Added new use-seo.ts file containing the SEO hook logic for improved search engine optimization.
2025-10-29 08:15:53 +08:00
5 changed files with 128 additions and 26 deletions

View File

@@ -13,7 +13,6 @@
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/lite.svg" />
<link rel="canonical" href="https://litek.typist.cc/" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />

View File

@@ -1,11 +1,11 @@
{
"name": "litek",
"private": true,
"version": "0.0.15",
"version": "0.0.17",
"type": "module",
"scripts": {
"dev": "vite",
"build": "npm run generate:sitemap && tsc -b && vite build",
"build": "tsc -b && vite build",
"generate:sitemap": "tsx scripts/generate-sitemap.ts",
"lint": "eslint .",
"preview": "vite preview",

View File

@@ -1,11 +1,11 @@
import type { ReactNode } from "react";
import { Link } from "react-router-dom";
import { ChevronRight } from "lucide-react";
import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarMenuButton, SidebarMenuItem, SidebarMenu, SidebarMenuSub, SidebarMenuSubItem, SidebarMenuSubButton } from "@/components/ui/sidebar";
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@/components/ui/collapsible";
import { tools, type Tool } from "@/components/tool";
import { Link } from "react-router-dom";
import { ModeToggle } from "@/components/theme/toggle";
import { Button } from "../ui/button";
import { ChevronRight } from "lucide-react";
export const AppSidebar = () => {
// 递归构建完整路径
@@ -88,10 +88,9 @@ export const AppSidebar = () => {
</SidebarGroup>
</SidebarContent>
<SidebarFooter className="flex flex-row justify-between items-center gap-2">
<Button variant="link">
<SidebarMenuButton variant="outline" asChild>
<a href="mailto:litek@mail.typist.cc">need more tools?</a>
</Button>
<ModeToggle />
</SidebarMenuButton>
</SidebarFooter>
</Sidebar>
);

96
src/hooks/use-seo.ts Normal file
View File

@@ -0,0 +1,96 @@
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
interface UseSEOOptions {
title?: string;
description?: string;
baseUrl?: string;
}
/**
* SEO Hook - 动态更新页面 SEO 元数据
*
* @param options - SEO 配置选项
* @param options.title - 页面标题(可选)
* @param options.description - 页面描述(可选)
* @param options.baseUrl - 网站基础 URL默认为 https://litek.typist.cc
*
* @example
* ```tsx
* // 在组件中使用
* useSEO({
* title: 'UUID Generator',
* description: 'Free online UUID generator tool'
* });
* ```
*/
export const useSEO = (options: UseSEOOptions = {}) => {
const location = useLocation();
const {
title,
description,
baseUrl = 'https://litek.typist.cc'
} = options;
useEffect(() => {
// 构建当前页面的完整 URL
const canonicalUrl = `${baseUrl}${location.pathname}`;
// 更新或创建 canonical 链接
let canonical = document.querySelector('link[rel="canonical"]') as HTMLLinkElement;
if (!canonical) {
canonical = document.createElement('link');
canonical.setAttribute('rel', 'canonical');
document.head.appendChild(canonical);
}
canonical.setAttribute('href', canonicalUrl);
// 更新页面标题
if (title) {
document.title = `${title} - Lite Kit`;
}
// 更新 meta description
if (description) {
let metaDescription = document.querySelector('meta[name="description"]') as HTMLMetaElement;
if (metaDescription) {
metaDescription.setAttribute('content', description);
}
}
// 更新 Open Graph URL
let ogUrl = document.querySelector('meta[property="og:url"]') as HTMLMetaElement;
if (ogUrl) {
ogUrl.setAttribute('content', canonicalUrl);
}
// 更新 Open Graph Title
if (title) {
let ogTitle = document.querySelector('meta[property="og:title"]') as HTMLMetaElement;
if (ogTitle) {
ogTitle.setAttribute('content', `${title} - Lite Kit`);
}
// 更新 Twitter Card Title
let twitterTitle = document.querySelector('meta[name="twitter:title"]') as HTMLMetaElement;
if (twitterTitle) {
twitterTitle.setAttribute('content', `${title} - Lite Kit`);
}
}
// 更新 Open Graph Description
if (description) {
let ogDescription = document.querySelector('meta[property="og:description"]') as HTMLMetaElement;
if (ogDescription) {
ogDescription.setAttribute('content', description);
}
// 更新 Twitter Card Description
let twitterDescription = document.querySelector('meta[name="twitter:description"]') as HTMLMetaElement;
if (twitterDescription) {
twitterDescription.setAttribute('content', description);
}
}
}, [location.pathname, title, description, baseUrl]);
};

View File

@@ -4,15 +4,22 @@ import { Outlet } from "react-router-dom";
import { ThemeProvider } from "@/components/theme/provider"
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
import { AppSidebar } from "@/components/sidebar";
import { ModeToggle } from "@/components/theme/toggle";
export const Layout: FC = () => (
import { useSEO } from "@/hooks/use-seo";
export const Layout: FC = () => {
// 使用 SEO hook 自动更新 canonical URL 和其他 SEO 元数据
useSEO();
return (
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
<SidebarProvider>
<AppSidebar />
<div className="p-4 flex flex-col w-full h-[100vh] overflow-hidden">
<nav className="flex items-center justify-between">
<SidebarTrigger className="size-10" />
<div role="actions" />
<ModeToggle />
</nav>
<main className="flex-1 overflow-auto p-4 overflow-hidden">
<Outlet />
@@ -20,4 +27,5 @@ export const Layout: FC = () => (
</div>
</SidebarProvider>
</ThemeProvider>
);
);
};