Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7646830194 | ||
|
|
59f998e8e3 | ||
|
|
55207beff5 | ||
|
|
e98a344b95 | ||
|
|
d553c3e04c | ||
|
|
e2da2758cc | ||
|
|
3ab70498e6 | ||
|
|
a5ef1a1e70 | ||
|
|
297000f208 | ||
|
|
04fbf12e07 | ||
| a9a6354b2d | |||
|
|
109139a42e | ||
|
|
b5a811e5ee | ||
|
|
b3adfe5c8f | ||
|
|
8eda2eae99 | ||
|
|
99673913a6 |
@@ -43,8 +43,8 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=registry,ref=${{ secrets.REGISTRY_ENDPOINT }}/${{ github.repository_owner }}/litek:buildcache
|
# cache-from: type=registry,ref=${{ secrets.REGISTRY_ENDPOINT }}/${{ github.repository_owner }}/litek:buildcache
|
||||||
cache-to: type=registry,ref=${{ secrets.REGISTRY_ENDPOINT }}/${{ github.repository_owner }}/litek:buildcache,mode=max
|
# cache-to: type=registry,ref=${{ secrets.REGISTRY_ENDPOINT }}/${{ github.repository_owner }}/litek:buildcache,mode=max
|
||||||
# platforms: linux/amd64,linux/arm64
|
# platforms: linux/amd64,linux/arm64
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
|
|
||||||
|
|||||||
171
SEO-README.md
Normal file
171
SEO-README.md
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
# SEO 优化说明
|
||||||
|
|
||||||
|
本项目已完成基础 SEO 优化,以下是已实施的改动。
|
||||||
|
|
||||||
|
## 已完成的优化项
|
||||||
|
|
||||||
|
### 1. HTML Meta 标签优化 ✅
|
||||||
|
|
||||||
|
在 `index.html` 中添加了完整的 SEO 元数据:
|
||||||
|
|
||||||
|
- **基础 Meta 标签**
|
||||||
|
- title, description, keywords
|
||||||
|
- author, theme-color
|
||||||
|
- canonical URL
|
||||||
|
|
||||||
|
- **Open Graph 标签**(社交媒体分享优化)
|
||||||
|
- og:type, og:url, og:title
|
||||||
|
- og:description, og:image, og:site_name
|
||||||
|
|
||||||
|
- **Twitter Card 标签**
|
||||||
|
- twitter:card, twitter:title
|
||||||
|
- twitter:description, twitter:image
|
||||||
|
|
||||||
|
- **结构化数据**(JSON-LD)
|
||||||
|
- Schema.org WebSite 类型标记
|
||||||
|
- 提升搜索引擎理解能力
|
||||||
|
|
||||||
|
### 2. SEO 配置文件 ✅
|
||||||
|
|
||||||
|
- **`public/robots.txt`** - 搜索引擎爬虫规则
|
||||||
|
- 允许所有爬虫访问
|
||||||
|
- 指定 sitemap 位置
|
||||||
|
|
||||||
|
- **`public/sitemap.xml`** - 站点地图(自动生成)
|
||||||
|
- 包含所有工具页面 URL
|
||||||
|
- 设置更新频率和优先级
|
||||||
|
- 通过 `scripts/generate-sitemap.ts` 自动生成
|
||||||
|
|
||||||
|
- **`public/manifest.json`** - PWA 配置
|
||||||
|
- 支持渐进式 Web 应用
|
||||||
|
- 改善移动端体验
|
||||||
|
|
||||||
|
### 3. 构建流程优化 ✅
|
||||||
|
|
||||||
|
- **`package.json`**
|
||||||
|
- 构建时自动生成 sitemap
|
||||||
|
- 添加 tsx 依赖用于运行生成脚本
|
||||||
|
|
||||||
|
## 文件清单
|
||||||
|
|
||||||
|
### 新增文件
|
||||||
|
```
|
||||||
|
public/
|
||||||
|
├── robots.txt # 爬虫规则
|
||||||
|
├── sitemap.xml # 站点地图(自动生成)
|
||||||
|
└── manifest.json # PWA 配置
|
||||||
|
|
||||||
|
scripts/
|
||||||
|
└── generate-sitemap.ts # Sitemap 生成脚本
|
||||||
|
```
|
||||||
|
|
||||||
|
### 修改文件
|
||||||
|
```
|
||||||
|
index.html # 添加 SEO meta 标签和结构化数据
|
||||||
|
package.json # 添加 sitemap 生成命令
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用说明
|
||||||
|
|
||||||
|
### 开发
|
||||||
|
```bash
|
||||||
|
pnpm install # 安装依赖
|
||||||
|
pnpm dev # 启动开发服务器
|
||||||
|
```
|
||||||
|
|
||||||
|
### 构建
|
||||||
|
```bash
|
||||||
|
pnpm run build # 构建项目(自动生成 sitemap)
|
||||||
|
pnpm run generate:sitemap # 单独生成 sitemap
|
||||||
|
```
|
||||||
|
|
||||||
|
### 部署前检查
|
||||||
|
|
||||||
|
⚠️ **重要:部署前必须更新配置文件中的域名!**
|
||||||
|
|
||||||
|
需要将以下文件中的 `https://litek.typist.cc` 替换为你的实际域名:
|
||||||
|
|
||||||
|
1. **`index.html`**
|
||||||
|
- canonical URL
|
||||||
|
- Open Graph URL 和 image
|
||||||
|
- Twitter Card image
|
||||||
|
- Structured Data URL
|
||||||
|
|
||||||
|
2. **`public/robots.txt`**
|
||||||
|
- Sitemap URL
|
||||||
|
|
||||||
|
3. **`scripts/generate-sitemap.ts`**
|
||||||
|
- BASE_URL 常量
|
||||||
|
|
||||||
|
更新后重新构建:
|
||||||
|
```bash
|
||||||
|
pnpm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## SEO 验证
|
||||||
|
|
||||||
|
### 部署后验证
|
||||||
|
|
||||||
|
1. **提交 sitemap 到 Google Search Console**
|
||||||
|
- 访问 https://search.google.com/search-console
|
||||||
|
- 添加网站资源
|
||||||
|
- 提交 sitemap: `https://你的域名/sitemap.xml`
|
||||||
|
|
||||||
|
2. **测试结构化数据**
|
||||||
|
- 访问 https://validator.schema.org/
|
||||||
|
- 输入网站 URL
|
||||||
|
- 检查是否有错误
|
||||||
|
|
||||||
|
3. **测试 Open Graph 预览**
|
||||||
|
- Facebook: https://developers.facebook.com/tools/debug/
|
||||||
|
- Twitter: https://cards-dev.twitter.com/validator
|
||||||
|
|
||||||
|
4. **性能测试**
|
||||||
|
- Google PageSpeed Insights: https://pagespeed.web.dev/
|
||||||
|
- 目标分数: SEO > 95
|
||||||
|
|
||||||
|
## 可访问的 URL
|
||||||
|
|
||||||
|
部署后,以下 URL 应该可以正常访问:
|
||||||
|
|
||||||
|
- `https://你的域名/` - 主页
|
||||||
|
- `https://你的域名/robots.txt` - 爬虫规则
|
||||||
|
- `https://你的域名/sitemap.xml` - 站点地图
|
||||||
|
- `https://你的域名/manifest.json` - PWA 配置
|
||||||
|
- `https://你的域名/tool/uuid` - UUID 工具
|
||||||
|
- `https://你的域名/tool/json` - JSON 工具
|
||||||
|
- `https://你的域名/tool/base64` - Base64 工具
|
||||||
|
- `https://你的域名/tool/network/dns` - DNS 工具
|
||||||
|
- ... 其他工具页面
|
||||||
|
|
||||||
|
## 添加新工具时更新 SEO
|
||||||
|
|
||||||
|
当添加新工具时,需要更新 `scripts/generate-sitemap.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const tools = [
|
||||||
|
// 现有工具...
|
||||||
|
{ path: '/tool/你的新工具', priority: '0.9' },
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
然后重新构建项目。
|
||||||
|
|
||||||
|
## SEO 效果预期
|
||||||
|
|
||||||
|
- **1-2 周**:搜索引擎开始索引页面
|
||||||
|
- **1-2 月**:主要关键词开始有排名
|
||||||
|
- **3-6 月**:稳定的搜索排名和自然流量增长
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- React 19 + TypeScript
|
||||||
|
- Vite (Rolldown)
|
||||||
|
- React Router v7
|
||||||
|
- Radix UI + Tailwind CSS 4
|
||||||
|
- Nginx
|
||||||
|
|
||||||
|
## 联系方式
|
||||||
|
|
||||||
|
需要更多工具或有建议?联系:litek@mail.typist.cc
|
||||||
|
|
||||||
54
index.html
54
index.html
@@ -2,9 +2,59 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/lite.svg" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Lite Kit</title>
|
|
||||||
|
<!-- Primary Meta Tags -->
|
||||||
|
<title>Lite Kit - Lightweight Online Tools</title>
|
||||||
|
<meta name="description" content="Free online tools including UUID generator, JSON formatter, Base64 encoder/decoder, network testing tools and more. Fast, secure, and easy to use." />
|
||||||
|
<meta name="keywords" content="online tools,UUID generator,JSON formatter,Base64 encoder,network tools,DNS lookup,Ping test,TCPing,speed test,IP query" />
|
||||||
|
<meta name="author" content="Lite Kit" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/lite.svg" />
|
||||||
|
|
||||||
|
<!-- DNS Prefetch & Preconnect for external APIs -->
|
||||||
|
<link rel="dns-prefetch" href="https://ipinfo.io">
|
||||||
|
<link rel="preconnect" href="https://ipinfo.io" crossorigin>
|
||||||
|
|
||||||
|
<!-- Open Graph / Facebook -->
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:url" content="https://litek.typist.cc/" />
|
||||||
|
<meta property="og:title" content="Lite Kit - Lightweight Online Tools" />
|
||||||
|
<meta property="og:description" content="Free online tools including UUID generator, JSON formatter, Base64 encoder/decoder, network testing tools and more." />
|
||||||
|
<meta property="og:image" content="https://litek.typist.cc/lite.svg" />
|
||||||
|
<meta property="og:site_name" content="Lite Kit" />
|
||||||
|
|
||||||
|
<!-- Twitter Card -->
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:title" content="Lite Kit - Lightweight Online Tools" />
|
||||||
|
<meta name="twitter:description" content="Free online tools including UUID generator, JSON formatter, Base64 encoder/decoder, network testing tools and more." />
|
||||||
|
<meta name="twitter:image" content="https://litek.typist.cc/lite.svg" />
|
||||||
|
|
||||||
|
<!-- PWA Manifest -->
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
|
||||||
|
<!-- Structured Data (JSON-LD) -->
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "WebSite",
|
||||||
|
"name": "Lite Kit",
|
||||||
|
"description": "Free online tools including UUID generator, JSON formatter, Base64 encoder/decoder, network testing tools and more",
|
||||||
|
"url": "https://litek.typist.cc/",
|
||||||
|
"author": {
|
||||||
|
"@type": "Organization",
|
||||||
|
"name": "Lite Kit"
|
||||||
|
},
|
||||||
|
"applicationCategory": "UtilitiesApplication",
|
||||||
|
"offers": {
|
||||||
|
"@type": "Offer",
|
||||||
|
"price": "0",
|
||||||
|
"priceCurrency": "USD"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "litek",
|
"name": "litek",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.12",
|
"version": "0.0.19",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
|
"generate:sitemap": "tsx scripts/generate-sitemap.ts",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"release:patch": "npm version patch && git push --follow-tags",
|
"release:patch": "npm version patch && git push --follow-tags",
|
||||||
@@ -43,6 +44,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.22",
|
"eslint-plugin-react-refresh": "^0.4.22",
|
||||||
"globals": "^16.4.0",
|
"globals": "^16.4.0",
|
||||||
|
"tsx": "^4.19.2",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"typescript-eslint": "^8.45.0",
|
"typescript-eslint": "^8.45.0",
|
||||||
@@ -53,7 +55,8 @@
|
|||||||
"vite": "npm:rolldown-vite@7.1.14"
|
"vite": "npm:rolldown-vite@7.1.14"
|
||||||
},
|
},
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
"@swc/core"
|
"@swc/core",
|
||||||
|
"esbuild"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
313
pnpm-lock.yaml
generated
313
pnpm-lock.yaml
generated
@@ -31,7 +31,7 @@ importers:
|
|||||||
version: 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
version: 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
specifier: ^4.1.16
|
specifier: ^4.1.16
|
||||||
version: 4.1.16(rolldown-vite@7.1.14(@types/node@24.9.1)(jiti@2.6.1))
|
version: 4.1.16(rolldown-vite@7.1.14(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(tsx@4.20.6))
|
||||||
class-variance-authority:
|
class-variance-authority:
|
||||||
specifier: ^0.7.1
|
specifier: ^0.7.1
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
@@ -83,7 +83,7 @@ importers:
|
|||||||
version: 19.2.2(@types/react@19.2.2)
|
version: 19.2.2(@types/react@19.2.2)
|
||||||
'@vitejs/plugin-react':
|
'@vitejs/plugin-react':
|
||||||
specifier: ^5.1.0
|
specifier: ^5.1.0
|
||||||
version: 5.1.0(rolldown-vite@7.1.14(@types/node@24.9.1)(jiti@2.6.1))
|
version: 5.1.0(rolldown-vite@7.1.14(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(tsx@4.20.6))
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.36.0
|
specifier: ^9.36.0
|
||||||
version: 9.38.0(jiti@2.6.1)
|
version: 9.38.0(jiti@2.6.1)
|
||||||
@@ -96,6 +96,9 @@ importers:
|
|||||||
globals:
|
globals:
|
||||||
specifier: ^16.4.0
|
specifier: ^16.4.0
|
||||||
version: 16.4.0
|
version: 16.4.0
|
||||||
|
tsx:
|
||||||
|
specifier: ^4.19.2
|
||||||
|
version: 4.20.6
|
||||||
tw-animate-css:
|
tw-animate-css:
|
||||||
specifier: ^1.4.0
|
specifier: ^1.4.0
|
||||||
version: 1.4.0
|
version: 1.4.0
|
||||||
@@ -107,7 +110,7 @@ importers:
|
|||||||
version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)
|
version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)
|
||||||
vite:
|
vite:
|
||||||
specifier: npm:rolldown-vite@7.1.14
|
specifier: npm:rolldown-vite@7.1.14
|
||||||
version: rolldown-vite@7.1.14(@types/node@24.9.1)(jiti@2.6.1)
|
version: rolldown-vite@7.1.14(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(tsx@4.20.6)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -203,6 +206,162 @@ packages:
|
|||||||
'@emnapi/wasi-threads@1.1.0':
|
'@emnapi/wasi-threads@1.1.0':
|
||||||
resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
|
resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [aix]
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.25.11':
|
||||||
|
resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.25.11':
|
||||||
|
resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.25.11':
|
||||||
|
resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.25.11':
|
||||||
|
resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [mips64el]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.25.11':
|
||||||
|
resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openharmony]
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [sunos]
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.25.11':
|
||||||
|
resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.25.11':
|
||||||
|
resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
'@eslint-community/eslint-utils@4.9.0':
|
'@eslint-community/eslint-utils@4.9.0':
|
||||||
resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
|
resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
@@ -1035,6 +1194,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
|
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
|
|
||||||
|
esbuild@0.25.11:
|
||||||
|
resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
escalade@3.2.0:
|
escalade@3.2.0:
|
||||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -1153,6 +1317,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
|
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
get-tsconfig@4.13.0:
|
||||||
|
resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==}
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
glob-parent@5.1.2:
|
||||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@@ -1489,6 +1656,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
resolve-pkg-maps@1.0.0:
|
||||||
|
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||||
|
|
||||||
reusify@1.1.0:
|
reusify@1.1.0:
|
||||||
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
||||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||||
@@ -1609,6 +1779,11 @@ packages:
|
|||||||
tslib@2.8.1:
|
tslib@2.8.1:
|
||||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||||
|
|
||||||
|
tsx@4.20.6:
|
||||||
|
resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
tw-animate-css@1.4.0:
|
tw-animate-css@1.4.0:
|
||||||
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
|
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
|
||||||
|
|
||||||
@@ -1810,6 +1985,84 @@ snapshots:
|
|||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.25.11':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@eslint-community/eslint-utils@4.9.0(eslint@9.38.0(jiti@2.6.1))':
|
'@eslint-community/eslint-utils@4.9.0(eslint@9.38.0(jiti@2.6.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.38.0(jiti@2.6.1)
|
eslint: 9.38.0(jiti@2.6.1)
|
||||||
@@ -2351,12 +2604,12 @@ snapshots:
|
|||||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.16
|
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.16
|
||||||
'@tailwindcss/oxide-win32-x64-msvc': 4.1.16
|
'@tailwindcss/oxide-win32-x64-msvc': 4.1.16
|
||||||
|
|
||||||
'@tailwindcss/vite@4.1.16(rolldown-vite@7.1.14(@types/node@24.9.1)(jiti@2.6.1))':
|
'@tailwindcss/vite@4.1.16(rolldown-vite@7.1.14(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(tsx@4.20.6))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tailwindcss/node': 4.1.16
|
'@tailwindcss/node': 4.1.16
|
||||||
'@tailwindcss/oxide': 4.1.16
|
'@tailwindcss/oxide': 4.1.16
|
||||||
tailwindcss: 4.1.16
|
tailwindcss: 4.1.16
|
||||||
vite: rolldown-vite@7.1.14(@types/node@24.9.1)(jiti@2.6.1)
|
vite: rolldown-vite@7.1.14(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(tsx@4.20.6)
|
||||||
|
|
||||||
'@tybys/wasm-util@0.10.1':
|
'@tybys/wasm-util@0.10.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2493,7 +2746,7 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.46.2
|
'@typescript-eslint/types': 8.46.2
|
||||||
eslint-visitor-keys: 4.2.1
|
eslint-visitor-keys: 4.2.1
|
||||||
|
|
||||||
'@vitejs/plugin-react@5.1.0(rolldown-vite@7.1.14(@types/node@24.9.1)(jiti@2.6.1))':
|
'@vitejs/plugin-react@5.1.0(rolldown-vite@7.1.14(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(tsx@4.20.6))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.28.5
|
'@babel/core': 7.28.5
|
||||||
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5)
|
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5)
|
||||||
@@ -2501,7 +2754,7 @@ snapshots:
|
|||||||
'@rolldown/pluginutils': 1.0.0-beta.43
|
'@rolldown/pluginutils': 1.0.0-beta.43
|
||||||
'@types/babel__core': 7.20.5
|
'@types/babel__core': 7.20.5
|
||||||
react-refresh: 0.18.0
|
react-refresh: 0.18.0
|
||||||
vite: rolldown-vite@7.1.14(@types/node@24.9.1)(jiti@2.6.1)
|
vite: rolldown-vite@7.1.14(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(tsx@4.20.6)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -2607,6 +2860,35 @@ snapshots:
|
|||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
tapable: 2.3.0
|
tapable: 2.3.0
|
||||||
|
|
||||||
|
esbuild@0.25.11:
|
||||||
|
optionalDependencies:
|
||||||
|
'@esbuild/aix-ppc64': 0.25.11
|
||||||
|
'@esbuild/android-arm': 0.25.11
|
||||||
|
'@esbuild/android-arm64': 0.25.11
|
||||||
|
'@esbuild/android-x64': 0.25.11
|
||||||
|
'@esbuild/darwin-arm64': 0.25.11
|
||||||
|
'@esbuild/darwin-x64': 0.25.11
|
||||||
|
'@esbuild/freebsd-arm64': 0.25.11
|
||||||
|
'@esbuild/freebsd-x64': 0.25.11
|
||||||
|
'@esbuild/linux-arm': 0.25.11
|
||||||
|
'@esbuild/linux-arm64': 0.25.11
|
||||||
|
'@esbuild/linux-ia32': 0.25.11
|
||||||
|
'@esbuild/linux-loong64': 0.25.11
|
||||||
|
'@esbuild/linux-mips64el': 0.25.11
|
||||||
|
'@esbuild/linux-ppc64': 0.25.11
|
||||||
|
'@esbuild/linux-riscv64': 0.25.11
|
||||||
|
'@esbuild/linux-s390x': 0.25.11
|
||||||
|
'@esbuild/linux-x64': 0.25.11
|
||||||
|
'@esbuild/netbsd-arm64': 0.25.11
|
||||||
|
'@esbuild/netbsd-x64': 0.25.11
|
||||||
|
'@esbuild/openbsd-arm64': 0.25.11
|
||||||
|
'@esbuild/openbsd-x64': 0.25.11
|
||||||
|
'@esbuild/openharmony-arm64': 0.25.11
|
||||||
|
'@esbuild/sunos-x64': 0.25.11
|
||||||
|
'@esbuild/win32-arm64': 0.25.11
|
||||||
|
'@esbuild/win32-ia32': 0.25.11
|
||||||
|
'@esbuild/win32-x64': 0.25.11
|
||||||
|
|
||||||
escalade@3.2.0: {}
|
escalade@3.2.0: {}
|
||||||
|
|
||||||
escape-string-regexp@4.0.0: {}
|
escape-string-regexp@4.0.0: {}
|
||||||
@@ -2736,6 +3018,10 @@ snapshots:
|
|||||||
|
|
||||||
get-nonce@1.0.1: {}
|
get-nonce@1.0.1: {}
|
||||||
|
|
||||||
|
get-tsconfig@4.13.0:
|
||||||
|
dependencies:
|
||||||
|
resolve-pkg-maps: 1.0.0
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
glob-parent@5.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
@@ -2994,9 +3280,11 @@ snapshots:
|
|||||||
|
|
||||||
resolve-from@4.0.0: {}
|
resolve-from@4.0.0: {}
|
||||||
|
|
||||||
|
resolve-pkg-maps@1.0.0: {}
|
||||||
|
|
||||||
reusify@1.1.0: {}
|
reusify@1.1.0: {}
|
||||||
|
|
||||||
rolldown-vite@7.1.14(@types/node@24.9.1)(jiti@2.6.1):
|
rolldown-vite@7.1.14(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(tsx@4.20.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@oxc-project/runtime': 0.92.0
|
'@oxc-project/runtime': 0.92.0
|
||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.3)
|
||||||
@@ -3007,8 +3295,10 @@ snapshots:
|
|||||||
tinyglobby: 0.2.15
|
tinyglobby: 0.2.15
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 24.9.1
|
'@types/node': 24.9.1
|
||||||
|
esbuild: 0.25.11
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
jiti: 2.6.1
|
jiti: 2.6.1
|
||||||
|
tsx: 4.20.6
|
||||||
|
|
||||||
rolldown@1.0.0-beta.41:
|
rolldown@1.0.0-beta.41:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3083,6 +3373,13 @@ snapshots:
|
|||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
|
tsx@4.20.6:
|
||||||
|
dependencies:
|
||||||
|
esbuild: 0.25.11
|
||||||
|
get-tsconfig: 4.13.0
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.3
|
||||||
|
|
||||||
tw-animate-css@1.4.0: {}
|
tw-animate-css@1.4.0: {}
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
|
|||||||
21
public/manifest.json
Normal file
21
public/manifest.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "Lite Kit - Lightweight Online Tools",
|
||||||
|
"short_name": "Lite Kit",
|
||||||
|
"description": "Free online tools including UUID generator, JSON formatter, Base64 encoder/decoder, network testing tools and more",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#000000",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"orientation": "portrait-primary",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/lite.svg",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"sizes": "any"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"categories": ["utilities", "productivity"],
|
||||||
|
"lang": "en",
|
||||||
|
"dir": "ltr"
|
||||||
|
}
|
||||||
|
|
||||||
21
public/robots.txt
Normal file
21
public/robots.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Allow all crawlers
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
# Sitemaps
|
||||||
|
Sitemap: https://litek.typist.cc/sitemap.xml
|
||||||
|
|
||||||
|
# Common bots
|
||||||
|
User-agent: Googlebot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: Bingbot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: Baiduspider
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
# Crawl-delay for less aggressive bots
|
||||||
|
User-agent: *
|
||||||
|
Crawl-delay: 1
|
||||||
|
|
||||||
63
public/sitemap.xml
Normal file
63
public/sitemap.xml
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
<url>
|
||||||
|
<loc>https://litek.typist.cc</loc>
|
||||||
|
<lastmod>2025-10-29</lastmod>
|
||||||
|
<changefreq>weekly</changefreq>
|
||||||
|
<priority>1.0</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://litek.typist.cc/tool</loc>
|
||||||
|
<lastmod>2025-10-29</lastmod>
|
||||||
|
<changefreq>weekly</changefreq>
|
||||||
|
<priority>0.9</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://litek.typist.cc/tool/uuid</loc>
|
||||||
|
<lastmod>2025-10-29</lastmod>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>0.9</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://litek.typist.cc/tool/json</loc>
|
||||||
|
<lastmod>2025-10-29</lastmod>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>0.9</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://litek.typist.cc/tool/base64</loc>
|
||||||
|
<lastmod>2025-10-29</lastmod>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>0.9</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://litek.typist.cc/tool/network/dns</loc>
|
||||||
|
<lastmod>2025-10-29</lastmod>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://litek.typist.cc/tool/network/ping</loc>
|
||||||
|
<lastmod>2025-10-29</lastmod>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://litek.typist.cc/tool/network/tcping</loc>
|
||||||
|
<lastmod>2025-10-29</lastmod>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://litek.typist.cc/tool/network/speedtest</loc>
|
||||||
|
<lastmod>2025-10-29</lastmod>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://litek.typist.cc/tool/network/ipquery</loc>
|
||||||
|
<lastmod>2025-10-29</lastmod>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>
|
||||||
|
</urlset>
|
||||||
83
scripts/generate-sitemap.ts
Normal file
83
scripts/generate-sitemap.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
interface SitemapUrl {
|
||||||
|
loc: string;
|
||||||
|
lastmod: string;
|
||||||
|
changefreq: string;
|
||||||
|
priority: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BASE_URL = 'https://litek.typist.cc';
|
||||||
|
const currentDate = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
|
const tools = [
|
||||||
|
{ path: '/tool/uuid', priority: '0.9' },
|
||||||
|
{ path: '/tool/json', priority: '0.9' },
|
||||||
|
{ path: '/tool/base64', priority: '0.9' },
|
||||||
|
{ path: '/tool/network/dns', priority: '0.8' },
|
||||||
|
{ path: '/tool/network/ping', priority: '0.8' },
|
||||||
|
{ path: '/tool/network/tcping', priority: '0.8' },
|
||||||
|
{ path: '/tool/network/speedtest', priority: '0.8' },
|
||||||
|
{ path: '/tool/network/ipquery', priority: '0.8' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const urls: SitemapUrl[] = [
|
||||||
|
{
|
||||||
|
loc: BASE_URL,
|
||||||
|
lastmod: currentDate,
|
||||||
|
changefreq: 'weekly',
|
||||||
|
priority: '1.0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loc: `${BASE_URL}/tool`,
|
||||||
|
lastmod: currentDate,
|
||||||
|
changefreq: 'weekly',
|
||||||
|
priority: '0.9',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add all tool pages
|
||||||
|
tools.forEach((tool) => {
|
||||||
|
urls.push({
|
||||||
|
loc: `${BASE_URL}${tool.path}`,
|
||||||
|
lastmod: currentDate,
|
||||||
|
changefreq: 'monthly',
|
||||||
|
priority: tool.priority,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function generateSitemap(): string {
|
||||||
|
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
||||||
|
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n';
|
||||||
|
|
||||||
|
urls.forEach((url) => {
|
||||||
|
xml += ' <url>\n';
|
||||||
|
xml += ` <loc>${url.loc}</loc>\n`;
|
||||||
|
xml += ` <lastmod>${url.lastmod}</lastmod>\n`;
|
||||||
|
xml += ` <changefreq>${url.changefreq}</changefreq>\n`;
|
||||||
|
xml += ` <priority>${url.priority}</priority>\n`;
|
||||||
|
xml += ' </url>\n';
|
||||||
|
});
|
||||||
|
|
||||||
|
xml += '</urlset>\n';
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate and write sitemap
|
||||||
|
const sitemap = generateSitemap();
|
||||||
|
const publicDir = path.resolve(__dirname, '../public');
|
||||||
|
const sitemapPath = path.join(publicDir, 'sitemap.xml');
|
||||||
|
|
||||||
|
// Ensure public directory exists
|
||||||
|
if (!fs.existsSync(publicDir)) {
|
||||||
|
fs.mkdirSync(publicDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(sitemapPath, sitemap, 'utf-8');
|
||||||
|
console.log(`✅ Sitemap generated successfully at ${sitemapPath}`);
|
||||||
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { ReactNode } from "react";
|
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 { 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 { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@/components/ui/collapsible";
|
||||||
import { tools, type Tool } from "@/components/tool";
|
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 = () => {
|
export const AppSidebar = () => {
|
||||||
// 递归构建完整路径
|
// 递归构建完整路径
|
||||||
@@ -13,6 +13,46 @@ export const AppSidebar = () => {
|
|||||||
return `/tool/${pathSegments.join("/")}`;
|
return `/tool/${pathSegments.join("/")}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 递归渲染子菜单内容
|
||||||
|
const renderSubMenuContent = (child: Tool, currentPaths: string[]): ReactNode => {
|
||||||
|
if (child.children) {
|
||||||
|
// 子菜单内的可折叠项
|
||||||
|
return (
|
||||||
|
<Collapsible defaultOpen={false} className="group/collapsible">
|
||||||
|
<CollapsibleTrigger asChild>
|
||||||
|
<SidebarMenuSubButton>
|
||||||
|
{child.icon}
|
||||||
|
<span>{child.name}</span>
|
||||||
|
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||||
|
</SidebarMenuSubButton>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent>
|
||||||
|
<SidebarMenuSub>
|
||||||
|
{child.children.map((subChild) => (
|
||||||
|
<SidebarMenuSubItem key={subChild.name}>
|
||||||
|
{renderSubMenuContent(subChild, [...currentPaths, child.path])}
|
||||||
|
</SidebarMenuSubItem>
|
||||||
|
))}
|
||||||
|
</SidebarMenuSub>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 叶子节点
|
||||||
|
return (
|
||||||
|
<SidebarMenuSubButton asChild>
|
||||||
|
<Link
|
||||||
|
to={buildFullPath([...currentPaths, child.path])}
|
||||||
|
title={child.description}
|
||||||
|
>
|
||||||
|
{child.icon}
|
||||||
|
<span>{child.name}</span>
|
||||||
|
</Link>
|
||||||
|
</SidebarMenuSubButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// 递归渲染菜单项
|
// 递归渲染菜单项
|
||||||
const renderMenuItem = (tool: Tool, parentPaths: string[] = []): ReactNode => {
|
const renderMenuItem = (tool: Tool, parentPaths: string[] = []): ReactNode => {
|
||||||
const currentPaths = [...parentPaths, tool.path];
|
const currentPaths = [...parentPaths, tool.path];
|
||||||
@@ -20,12 +60,11 @@ export const AppSidebar = () => {
|
|||||||
if (tool.children) {
|
if (tool.children) {
|
||||||
// 有子菜单的项目
|
// 有子菜单的项目
|
||||||
return (
|
return (
|
||||||
<Collapsible
|
<SidebarMenuItem key={tool.name}>
|
||||||
key={tool.name}
|
<Collapsible
|
||||||
defaultOpen={false}
|
defaultOpen={false}
|
||||||
className="group/collapsible"
|
className="group/collapsible"
|
||||||
>
|
>
|
||||||
<SidebarMenuItem>
|
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
<SidebarMenuButton tooltip={tool.description}>
|
<SidebarMenuButton tooltip={tool.description}>
|
||||||
{tool.icon}
|
{tool.icon}
|
||||||
@@ -37,25 +76,13 @@ export const AppSidebar = () => {
|
|||||||
<SidebarMenuSub>
|
<SidebarMenuSub>
|
||||||
{tool.children.map((child) => (
|
{tool.children.map((child) => (
|
||||||
<SidebarMenuSubItem key={child.name}>
|
<SidebarMenuSubItem key={child.name}>
|
||||||
{child.children ? (
|
{renderSubMenuContent(child, currentPaths)}
|
||||||
renderMenuItem(child, currentPaths)
|
|
||||||
) : (
|
|
||||||
<SidebarMenuSubButton asChild>
|
|
||||||
<Link
|
|
||||||
to={buildFullPath([...currentPaths, child.path])}
|
|
||||||
title={child.description}
|
|
||||||
>
|
|
||||||
{child.icon}
|
|
||||||
<span>{child.name}</span>
|
|
||||||
</Link>
|
|
||||||
</SidebarMenuSubButton>
|
|
||||||
)}
|
|
||||||
</SidebarMenuSubItem>
|
</SidebarMenuSubItem>
|
||||||
))}
|
))}
|
||||||
</SidebarMenuSub>
|
</SidebarMenuSub>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</SidebarMenuItem>
|
</Collapsible>
|
||||||
</Collapsible>
|
</SidebarMenuItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,10 +115,9 @@ export const AppSidebar = () => {
|
|||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter className="flex flex-row justify-between items-center gap-2">
|
<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>
|
<a href="mailto:litek@mail.typist.cc">need more tools?</a>
|
||||||
</Button>
|
</SidebarMenuButton>
|
||||||
<ModeToggle />
|
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
import type { ReactNode } from 'react';
|
import { lazy, type ReactNode, type ComponentType } from 'react';
|
||||||
import { FileJson, Hash, Binary, Network, Globe, Activity, Gauge, Wifi, MapPin } from 'lucide-react'
|
import { FileJson, Hash, Binary, Network, Globe, Activity, Gauge, Wifi, MapPin } from 'lucide-react'
|
||||||
|
|
||||||
import UUID from './uuid'
|
// 懒加载工具组件
|
||||||
import JSON from './json'
|
const UUID = lazy(() => import('./uuid'))
|
||||||
import Base64 from './base64'
|
const JSON = lazy(() => import('./json'))
|
||||||
import { DNS, Ping, TCPing, SpeedTest, IPQuery } from './network'
|
const Base64 = lazy(() => import('./base64'))
|
||||||
|
const DNS = lazy(() => import('./network/dns'))
|
||||||
|
const Ping = lazy(() => import('./network/ping'))
|
||||||
|
const TCPing = lazy(() => import('./network/tcping'))
|
||||||
|
const SpeedTest = lazy(() => import('./network/speedtest'))
|
||||||
|
const IPQuery = lazy(() => import('./network/ipquery'))
|
||||||
|
|
||||||
export interface Tool {
|
export interface Tool {
|
||||||
path: string;
|
path: string;
|
||||||
name: string;
|
name: string;
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
description: string;
|
description: string;
|
||||||
component?: ReactNode;
|
component?: ComponentType;
|
||||||
children?: Tool[];
|
children?: Tool[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,21 +26,21 @@ export const tools: Tool[] = [
|
|||||||
name: "UUID Generator",
|
name: "UUID Generator",
|
||||||
description: "Generate a UUID",
|
description: "Generate a UUID",
|
||||||
icon: <Hash />,
|
icon: <Hash />,
|
||||||
component: <UUID />,
|
component: UUID,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "json",
|
path: "json",
|
||||||
name: "JSON Formatter",
|
name: "JSON Formatter",
|
||||||
description: "Format and validate JSON",
|
description: "Format and validate JSON",
|
||||||
icon: <FileJson />,
|
icon: <FileJson />,
|
||||||
component: <JSON />,
|
component: JSON,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "base64",
|
path: "base64",
|
||||||
name: "Base64 Encoder/Decoder",
|
name: "Base64 Encoder/Decoder",
|
||||||
description: "Encode and decode Base64",
|
description: "Encode and decode Base64",
|
||||||
icon: <Binary />,
|
icon: <Binary />,
|
||||||
component: <Base64 />,
|
component: Base64,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "network",
|
path: "network",
|
||||||
@@ -48,35 +53,35 @@ export const tools: Tool[] = [
|
|||||||
name: "DNS Lookup",
|
name: "DNS Lookup",
|
||||||
description: "DNS query tool",
|
description: "DNS query tool",
|
||||||
icon: <Globe />,
|
icon: <Globe />,
|
||||||
component: <DNS />,
|
component: DNS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "ping",
|
path: "ping",
|
||||||
name: "Ping",
|
name: "Ping",
|
||||||
description: "Ping test tool",
|
description: "Ping test tool",
|
||||||
icon: <Activity />,
|
icon: <Activity />,
|
||||||
component: <Ping />,
|
component: Ping,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "tcping",
|
path: "tcping",
|
||||||
name: "TCPing",
|
name: "TCPing",
|
||||||
description: "TCP port connectivity test",
|
description: "TCP port connectivity test",
|
||||||
icon: <Wifi />,
|
icon: <Wifi />,
|
||||||
component: <TCPing />,
|
component: TCPing,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "speedtest",
|
path: "speedtest",
|
||||||
name: "Speed Test",
|
name: "Speed Test",
|
||||||
description: "Website speed test",
|
description: "Website speed test",
|
||||||
icon: <Gauge />,
|
icon: <Gauge />,
|
||||||
component: <SpeedTest />,
|
component: SpeedTest,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "ipquery",
|
path: "ipquery",
|
||||||
name: "IP Query",
|
name: "IP Query",
|
||||||
description: "Query IP location, quality and risk info",
|
description: "Query IP location, quality and risk info",
|
||||||
icon: <MapPin />,
|
icon: <MapPin />,
|
||||||
component: <IPQuery />,
|
component: IPQuery,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
export { default as DNS } from './dns';
|
|
||||||
export { default as Ping } from './ping';
|
|
||||||
export { default as TCPing } from './tcping';
|
|
||||||
export { default as SpeedTest } from './speedtest';
|
|
||||||
export { default as IPQuery } from './ipquery';
|
|
||||||
|
|
||||||
@@ -91,8 +91,8 @@ const Tool: FC = () => {
|
|||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 使用 ip-api.com (免费,功能较全)
|
// 使用 ipinfo.io (免费,稳定可靠)
|
||||||
const response = await fetch(`http://ip-api.com/json/${encodeURIComponent(ip.trim())}?fields=status,message,country,countryCode,region,city,lat,lon,timezone,isp,org,as,proxy,hosting,query`);
|
const response = await fetch(`https://ipinfo.io/${encodeURIComponent(ip.trim())}/json`);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
@@ -103,28 +103,8 @@ const Tool: FC = () => {
|
|||||||
|
|
||||||
setQueryTime(endTime - startTime);
|
setQueryTime(endTime - startTime);
|
||||||
|
|
||||||
if (data.status === "fail") {
|
// ipinfo.io 返回格式已经符合 IPInfo 接口
|
||||||
toast.error(data.message || "Query failed");
|
setIpInfo(data);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 转换为统一格式
|
|
||||||
const ipData: IPInfo = {
|
|
||||||
ip: data.query,
|
|
||||||
city: data.city,
|
|
||||||
region: data.region,
|
|
||||||
country: data.country,
|
|
||||||
countryCode: data.countryCode,
|
|
||||||
loc: data.lat && data.lon ? `${data.lat},${data.lon}` : undefined,
|
|
||||||
timezone: data.timezone,
|
|
||||||
isp: data.isp,
|
|
||||||
org: data.org,
|
|
||||||
as: data.as,
|
|
||||||
proxy: data.proxy,
|
|
||||||
hosting: data.hosting,
|
|
||||||
};
|
|
||||||
|
|
||||||
setIpInfo(ipData);
|
|
||||||
toast.success("Query successful");
|
toast.success("Query successful");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
@@ -146,14 +126,18 @@ const Tool: FC = () => {
|
|||||||
const getRiskLevel = () => {
|
const getRiskLevel = () => {
|
||||||
if (!ipInfo) return null;
|
if (!ipInfo) return null;
|
||||||
|
|
||||||
if (ipInfo.proxy || ipInfo.hosting) {
|
// ipinfo.io 通过 org 字段可以简单判断是否为托管IP
|
||||||
|
const orgLower = ipInfo.org?.toLowerCase() || "";
|
||||||
|
const isHosting = orgLower.includes("hosting") ||
|
||||||
|
orgLower.includes("datacenter") ||
|
||||||
|
orgLower.includes("cloud") ||
|
||||||
|
orgLower.includes("server");
|
||||||
|
|
||||||
|
if (isHosting) {
|
||||||
return {
|
return {
|
||||||
level: "High",
|
level: "Medium",
|
||||||
color: "text-red-500",
|
color: "text-yellow-500",
|
||||||
reasons: [
|
reasons: ["Possible Hosting/Datacenter IP"],
|
||||||
ipInfo.proxy && "Proxy/VPN detected",
|
|
||||||
ipInfo.hosting && "Hosting/Datacenter IP",
|
|
||||||
].filter(Boolean),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
96
src/hooks/use-seo.ts
Normal file
96
src/hooks/use-seo.ts
Normal 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]);
|
||||||
|
};
|
||||||
|
|
||||||
@@ -4,20 +4,28 @@ import { Outlet } from "react-router-dom";
|
|||||||
import { ThemeProvider } from "@/components/theme/provider"
|
import { ThemeProvider } from "@/components/theme/provider"
|
||||||
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
|
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
|
||||||
import { AppSidebar } from "@/components/sidebar";
|
import { AppSidebar } from "@/components/sidebar";
|
||||||
|
import { ModeToggle } from "@/components/theme/toggle";
|
||||||
|
|
||||||
export const Layout: FC = () => (
|
import { useSEO } from "@/hooks/use-seo";
|
||||||
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
|
||||||
<SidebarProvider>
|
export const Layout: FC = () => {
|
||||||
<AppSidebar />
|
// 使用 SEO hook 自动更新 canonical URL 和其他 SEO 元数据
|
||||||
<div className="p-4 flex flex-col w-full h-[100vh] overflow-hidden">
|
useSEO();
|
||||||
<nav className="flex items-center justify-between">
|
|
||||||
<SidebarTrigger className="size-10" />
|
return (
|
||||||
<div role="actions" />
|
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
||||||
</nav>
|
<SidebarProvider>
|
||||||
<main className="flex-1 overflow-auto p-4 overflow-hidden">
|
<AppSidebar />
|
||||||
<Outlet />
|
<div className="p-4 flex flex-col w-full h-[100vh] overflow-hidden">
|
||||||
</main>
|
<nav className="flex items-center justify-between">
|
||||||
</div>
|
<SidebarTrigger className="size-10" />
|
||||||
</SidebarProvider>
|
<ModeToggle />
|
||||||
</ThemeProvider>
|
</nav>
|
||||||
);
|
<main className="flex-1 overflow-auto p-4 overflow-hidden">
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</SidebarProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Suspense, createElement } from "react";
|
||||||
import {
|
import {
|
||||||
createBrowserRouter,
|
createBrowserRouter,
|
||||||
redirect,
|
redirect,
|
||||||
@@ -8,6 +9,19 @@ import {
|
|||||||
import { tools, type Tool } from "@/components/tool";
|
import { tools, type Tool } from "@/components/tool";
|
||||||
import { Layout } from "./layout";
|
import { Layout } from "./layout";
|
||||||
|
|
||||||
|
// 加载中的占位组件
|
||||||
|
const LoadingFallback = () => (
|
||||||
|
<div className="flex items-center justify-center h-full">
|
||||||
|
<div className="text-center flex flex-col items-center gap-3">
|
||||||
|
<div className="relative">
|
||||||
|
<div className="h-12 w-12 rounded-full border-4 border-muted"></div>
|
||||||
|
<div className="absolute top-0 left-0 h-12 w-12 rounded-full border-4 border-primary border-t-transparent animate-spin"></div>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground font-medium">Loading...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const buildToolRoutes = (tools: Tool[]): RouteObject[] => {
|
const buildToolRoutes = (tools: Tool[]): RouteObject[] => {
|
||||||
return tools.map((tool) => {
|
return tools.map((tool) => {
|
||||||
const route: RouteObject = {
|
const route: RouteObject = {
|
||||||
@@ -15,7 +29,12 @@ const buildToolRoutes = (tools: Tool[]): RouteObject[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (tool.component) {
|
if (tool.component) {
|
||||||
route.element = tool.component;
|
// 使用 Suspense 包裹懒加载组件
|
||||||
|
route.element = (
|
||||||
|
<Suspense fallback={<LoadingFallback />}>
|
||||||
|
{createElement(tool.component)}
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tool.children && tool.children.length > 0) {
|
if (tool.children && tool.children.length > 0) {
|
||||||
|
|||||||
@@ -11,4 +11,27 @@ export default defineConfig({
|
|||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: (id) => {
|
||||||
|
// React核心库
|
||||||
|
if (id.includes('node_modules/react') ||
|
||||||
|
id.includes('node_modules/react-dom') ||
|
||||||
|
id.includes('node_modules/react-router-dom')) {
|
||||||
|
return 'react-vendor';
|
||||||
|
}
|
||||||
|
// Radix UI组件
|
||||||
|
if (id.includes('node_modules/@radix-ui')) {
|
||||||
|
return 'ui-vendor';
|
||||||
|
}
|
||||||
|
// 图标库
|
||||||
|
if (id.includes('node_modules/lucide-react')) {
|
||||||
|
return 'icons';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
chunkSizeWarningLimit: 500,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user