diff --git a/.bolt/config.json b/.bolt/config.json new file mode 100644 index 0000000..6b6787d --- /dev/null +++ b/.bolt/config.json @@ -0,0 +1,3 @@ +{ + "template": "bolt-vite-react-ts" +} diff --git a/.bolt/prompt b/.bolt/prompt new file mode 100644 index 0000000..ce91b43 --- /dev/null +++ b/.bolt/prompt @@ -0,0 +1,5 @@ +For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production. + +By default, this template supports JSX syntax with Tailwind CSS classes, React hooks, and Lucide React for icons. Do not install other packages for UI themes, icons, etc unless absolutely necessary or I request them. + +Use icons from lucide-react for logos. diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml new file mode 100644 index 0000000..839048c --- /dev/null +++ b/.github/workflows/build-desktop.yml @@ -0,0 +1,204 @@ +name: Build Desktop App + +on: + push: + branches: [ main, master ] + tags: [ 'v*' ] + pull_request: + branches: [ main, master ] + workflow_dispatch: + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build web app + run: npm run build + + - name: Install Electron dependencies + run: | + npm install --save-dev electron electron-builder + + - name: Create Electron main process + run: | + mkdir -p electron + cat > electron/main.js << 'EOF' + const { app, BrowserWindow, Menu } = require('electron'); + const path = require('path'); + const isDev = process.env.NODE_ENV === 'development'; + + function createWindow() { + const mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + minWidth: 800, + minHeight: 600, + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + enableRemoteModule: false, + webSecurity: true + }, + icon: path.join(__dirname, '../dist/vite.svg'), + titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default', + show: false + }); + + // Load the app + if (isDev) { + mainWindow.loadURL('http://localhost:5173'); + mainWindow.webContents.openDevTools(); + } else { + mainWindow.loadFile(path.join(__dirname, '../dist/index.html')); + } + + mainWindow.once('ready-to-show', () => { + mainWindow.show(); + }); + + // Handle window closed + mainWindow.on('closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } + }); + + return mainWindow; + } + + // App event handlers + app.whenReady().then(() => { + createWindow(); + + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } + }); + }); + + app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } + }); + + // Security: Prevent new window creation + app.on('web-contents-created', (event, contents) => { + contents.on('new-window', (event, navigationUrl) => { + event.preventDefault(); + require('electron').shell.openExternal(navigationUrl); + }); + }); + EOF + + - name: Create Electron package.json + run: | + cat > electron/package.json << 'EOF' + { + "name": "github-stars-manager-desktop", + "version": "1.0.0", + "description": "GitHub Stars Manager Desktop App", + "main": "main.js", + "author": "GitHub Stars Manager", + "license": "MIT" + } + EOF + + - name: Update main package.json for Electron + run: | + npm pkg set main="electron/main.js" + npm pkg set homepage="./" + npm pkg set scripts.electron="electron ." + npm pkg set scripts.electron-dev="NODE_ENV=development electron ." + npm pkg set scripts.dist="electron-builder" + npm pkg set build.appId="com.github-stars-manager.app" + npm pkg set build.productName="GitHub Stars Manager" + npm pkg set build.directories.output="release" + npm pkg set build.files[0]="dist/**/*" + npm pkg set build.files[1]="electron/**/*" + npm pkg set build.files[2]="node_modules/**/*" + npm pkg set build.files[3]="package.json" + + - name: Configure platform-specific build settings + shell: bash + run: | + if [[ "${{ matrix.os }}" == "windows-latest" ]]; then + npm pkg set build.win.target="nsis" + npm pkg set build.win.icon="dist/vite.svg" + npm pkg set build.nsis.oneClick=false + npm pkg set build.nsis.allowToChangeInstallationDirectory=true + elif [[ "${{ matrix.os }}" == "macos-latest" ]]; then + npm pkg set build.mac.target="dmg" + npm pkg set build.mac.icon="dist/vite.svg" + npm pkg set build.mac.category="public.app-category.productivity" + else + npm pkg set build.linux.target="AppImage" + npm pkg set build.linux.icon="dist/vite.svg" + npm pkg set build.linux.category="Office" + fi + + - name: Build Electron app + run: npm run dist + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload artifacts (Windows) + if: matrix.os == 'windows-latest' + uses: actions/upload-artifact@v4 + with: + name: windows-app + path: release/*.exe + + - name: Upload artifacts (macOS) + if: matrix.os == 'macos-latest' + uses: actions/upload-artifact@v4 + with: + name: macos-app + path: release/*.dmg + + - name: Upload artifacts (Linux) + if: matrix.os == 'ubuntu-latest' + uses: actions/upload-artifact@v4 + with: + name: linux-app + path: release/*.AppImage + + release: + needs: build + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: | + windows-app/* + macos-app/* + linux-app/* + draft: false + prerelease: false + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml new file mode 100644 index 0000000..847cea3 --- /dev/null +++ b/.github/workflows/build-web.yml @@ -0,0 +1,42 @@ +name: Build and Deploy Web App + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + workflow_dispatch: + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build web app + run: npm run build + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: web-build + path: dist/ + + - name: Deploy to GitHub Pages + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./dist + cname: your-domain.com # 可选:如果你有自定义域名 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ceb59f --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +.env diff --git a/electron-builder.yml b/electron-builder.yml new file mode 100644 index 0000000..316618b --- /dev/null +++ b/electron-builder.yml @@ -0,0 +1,86 @@ +# Electron Builder配置文件 +appId: com.github-stars-manager.app +productName: GitHub Stars Manager +copyright: Copyright © 2024 GitHub Stars Manager + +directories: + output: release + buildResources: build + +files: + - dist/**/* + - electron/**/* + - node_modules/**/* + - package.json + +extraMetadata: + main: electron/main.js + +# Windows配置 +win: + target: + - target: nsis + arch: + - x64 + - ia32 + icon: dist/vite.svg + +nsis: + oneClick: false + allowToChangeInstallationDirectory: true + createDesktopShortcut: true + createStartMenuShortcut: true + shortcutName: GitHub Stars Manager + +# macOS配置 +mac: + target: + - target: dmg + arch: + - x64 + - arm64 + icon: dist/vite.svg + category: public.app-category.productivity + hardenedRuntime: true + entitlements: build/entitlements.mac.plist + entitlementsInherit: build/entitlements.mac.inherit.plist + +dmg: + title: GitHub Stars Manager + icon: dist/vite.svg + window: + width: 540 + height: 380 + contents: + - x: 410 + y: 230 + type: link + path: /Applications + - x: 130 + y: 230 + type: file + +# Linux配置 +linux: + target: + - target: AppImage + arch: + - x64 + - target: deb + arch: + - x64 + - target: rpm + arch: + - x64 + icon: dist/vite.svg + category: Office + synopsis: AI-powered GitHub starred repositories management tool + description: > + GitHub Stars Manager is an intelligent tool for organizing and managing + your GitHub starred repositories with AI-powered analysis and release tracking. + +# 发布配置 +publish: + provider: github + owner: your-username + repo: your-repo-name \ No newline at end of file diff --git a/public/icon.svg b/public/icon.svg new file mode 100644 index 0000000..fd3fcda --- /dev/null +++ b/public/icon.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GitHub Stars + + + Manager + + \ No newline at end of file diff --git a/scripts/build-desktop.js b/scripts/build-desktop.js new file mode 100644 index 0000000..3e23174 --- /dev/null +++ b/scripts/build-desktop.js @@ -0,0 +1,180 @@ +#!/usr/bin/env node + +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +console.log('🚀 开始构建桌面应用...'); + +// 1. 构建Web应用 +console.log('📦 构建Web应用...'); +execSync('npm run build', { stdio: 'inherit' }); + +// 2. 创建Electron目录和文件 +console.log('⚡ 设置Electron环境...'); +const electronDir = path.join(__dirname, '../electron'); +if (!fs.existsSync(electronDir)) { + fs.mkdirSync(electronDir, { recursive: true }); +} + +// 3. 创建主进程文件 +const mainJs = ` +const { app, BrowserWindow, Menu, shell } = require('electron'); +const path = require('path'); +const isDev = process.env.NODE_ENV === 'development'; + +let mainWindow; + +function createWindow() { + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + minWidth: 800, + minHeight: 600, + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + enableRemoteModule: false, + webSecurity: true + }, + icon: path.join(__dirname, '../dist/vite.svg'), + titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default', + show: false + }); + + // 加载应用 + if (isDev) { + mainWindow.loadURL('http://localhost:5173'); + mainWindow.webContents.openDevTools(); + } else { + mainWindow.loadFile(path.join(__dirname, '../dist/index.html')); + } + + mainWindow.once('ready-to-show', () => { + mainWindow.show(); + + // 设置应用菜单 + if (process.platform === 'darwin') { + const template = [ + { + label: 'GitHub Stars Manager', + submenu: [ + { role: 'about' }, + { type: 'separator' }, + { role: 'services' }, + { type: 'separator' }, + { role: 'hide' }, + { role: 'hideothers' }, + { role: 'unhide' }, + { type: 'separator' }, + { role: 'quit' } + ] + }, + { + label: 'Edit', + submenu: [ + { role: 'undo' }, + { role: 'redo' }, + { type: 'separator' }, + { role: 'cut' }, + { role: 'copy' }, + { role: 'paste' }, + { role: 'selectall' } + ] + }, + { + label: 'View', + submenu: [ + { role: 'reload' }, + { role: 'forceReload' }, + { role: 'toggleDevTools' }, + { type: 'separator' }, + { role: 'resetZoom' }, + { role: 'zoomIn' }, + { role: 'zoomOut' }, + { type: 'separator' }, + { role: 'togglefullscreen' } + ] + }, + { + label: 'Window', + submenu: [ + { role: 'minimize' }, + { role: 'close' } + ] + } + ]; + Menu.setApplicationMenu(Menu.buildFromTemplate(template)); + } + }); + + // 处理外部链接 + mainWindow.webContents.setWindowOpenHandler(({ url }) => { + shell.openExternal(url); + return { action: 'deny' }; + }); + + mainWindow.on('closed', () => { + mainWindow = null; + }); +} + +app.whenReady().then(createWindow); + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } +}); + +// 安全设置 +app.on('web-contents-created', (event, contents) => { + contents.on('new-window', (event, navigationUrl) => { + event.preventDefault(); + shell.openExternal(navigationUrl); + }); +}); +`; + +fs.writeFileSync(path.join(electronDir, 'main.js'), mainJs); + +// 4. 创建Electron package.json +const electronPackageJson = { + name: 'github-stars-manager-desktop', + version: '1.0.0', + description: 'GitHub Stars Manager Desktop App', + main: 'main.js', + author: 'GitHub Stars Manager', + license: 'MIT' +}; + +fs.writeFileSync( + path.join(electronDir, 'package.json'), + JSON.stringify(electronPackageJson, null, 2) +); + +// 5. 安装Electron依赖 +console.log('📥 安装Electron依赖...'); +try { + execSync('npm install --save-dev electron electron-builder', { stdio: 'inherit' }); +} catch (error) { + console.error('安装依赖失败:', error.message); + process.exit(1); +} + +// 6. 构建应用 +console.log('🔨 构建桌面应用...'); +try { + execSync('npx electron-builder', { stdio: 'inherit' }); + console.log('✅ 桌面应用构建完成!'); + console.log('📁 构建文件位于 release/ 目录'); +} catch (error) { + console.error('构建失败:', error.message); + process.exit(1); +} \ No newline at end of file diff --git a/src/components/CategoryEditModal.tsx b/src/components/CategoryEditModal.tsx new file mode 100644 index 0000000..d0e7984 --- /dev/null +++ b/src/components/CategoryEditModal.tsx @@ -0,0 +1,1014 @@ +import React, { useState, useEffect } from 'react'; +import { Save, X, Plus } from 'lucide-react'; +import { Modal } from './Modal'; +import { Category } from '../types'; +import { useAppStore } from '../store/useAppStore'; + +// Complete emoji collection for categories +const availableIcons = [ + // 笑脸和人物 + { name: '😀', icon: '😀' }, + { name: '😃', icon: '😃' }, + { name: '😄', icon: '😄' }, + { name: '😁', icon: '😁' }, + { name: '😆', icon: '😆' }, + { name: '😅', icon: '😅' }, + { name: '🤣', icon: '🤣' }, + { name: '😂', icon: '😂' }, + { name: '🙂', icon: '🙂' }, + { name: '🙃', icon: '🙃' }, + { name: '😉', icon: '😉' }, + { name: '😊', icon: '😊' }, + { name: '😇', icon: '😇' }, + { name: '🥰', icon: '🥰' }, + { name: '😍', icon: '😍' }, + { name: '🤩', icon: '🤩' }, + { name: '😘', icon: '😘' }, + { name: '😗', icon: '😗' }, + { name: '😚', icon: '😚' }, + { name: '😙', icon: '😙' }, + { name: '🥲', icon: '🥲' }, + { name: '😋', icon: '😋' }, + { name: '😛', icon: '😛' }, + { name: '😜', icon: '😜' }, + { name: '🤪', icon: '🤪' }, + { name: '😝', icon: '😝' }, + { name: '🤑', icon: '🤑' }, + { name: '🤗', icon: '🤗' }, + { name: '🤭', icon: '🤭' }, + { name: '🤫', icon: '🤫' }, + { name: '🤔', icon: '🤔' }, + { name: '🤐', icon: '🤐' }, + { name: '🤨', icon: '🤨' }, + { name: '😐', icon: '😐' }, + { name: '😑', icon: '😑' }, + { name: '😶', icon: '😶' }, + { name: '😏', icon: '😏' }, + { name: '😒', icon: '😒' }, + { name: '🙄', icon: '🙄' }, + { name: '😬', icon: '😬' }, + { name: '🤥', icon: '🤥' }, + { name: '😔', icon: '😔' }, + { name: '😪', icon: '😪' }, + { name: '🤤', icon: '🤤' }, + { name: '😴', icon: '😴' }, + { name: '😷', icon: '😷' }, + { name: '🤒', icon: '🤒' }, + { name: '🤕', icon: '🤕' }, + { name: '🤢', icon: '🤢' }, + { name: '🤮', icon: '🤮' }, + { name: '🤧', icon: '🤧' }, + { name: '🥵', icon: '🥵' }, + { name: '🥶', icon: '🥶' }, + { name: '🥴', icon: '🥴' }, + { name: '😵', icon: '😵' }, + { name: '🤯', icon: '🤯' }, + { name: '🤠', icon: '🤠' }, + { name: '🥳', icon: '🥳' }, + { name: '🥸', icon: '🥸' }, + { name: '😎', icon: '😎' }, + { name: '🤓', icon: '🤓' }, + { name: '🧐', icon: '🧐' }, + { name: '😕', icon: '😕' }, + { name: '😟', icon: '😟' }, + { name: '🙁', icon: '🙁' }, + { name: '😮', icon: '😮' }, + { name: '😯', icon: '😯' }, + { name: '😲', icon: '😲' }, + { name: '😳', icon: '😳' }, + { name: '🥺', icon: '🥺' }, + { name: '😦', icon: '😦' }, + { name: '😧', icon: '😧' }, + { name: '😨', icon: '😨' }, + { name: '😰', icon: '😰' }, + { name: '😥', icon: '😥' }, + { name: '😢', icon: '😢' }, + { name: '😭', icon: '😭' }, + { name: '😱', icon: '😱' }, + { name: '😖', icon: '😖' }, + { name: '😣', icon: '😣' }, + { name: '😞', icon: '😞' }, + { name: '😓', icon: '😓' }, + { name: '😩', icon: '😩' }, + { name: '😫', icon: '😫' }, + { name: '🥱', icon: '🥱' }, + { name: '😤', icon: '😤' }, + { name: '😡', icon: '😡' }, + { name: '😠', icon: '😠' }, + { name: '🤬', icon: '🤬' }, + { name: '😈', icon: '😈' }, + { name: '👿', icon: '👿' }, + { name: '💀', icon: '💀' }, + { name: '☠️', icon: '☠️' }, + { name: '💩', icon: '💩' }, + { name: '🤡', icon: '🤡' }, + { name: '👹', icon: '👹' }, + { name: '👺', icon: '👺' }, + { name: '👻', icon: '👻' }, + { name: '👽', icon: '👽' }, + { name: '👾', icon: '👾' }, + { name: '🤖', icon: '🤖' }, + + // 手势和身体部位 + { name: '👋', icon: '👋' }, + { name: '🤚', icon: '🤚' }, + { name: '🖐️', icon: '🖐️' }, + { name: '✋', icon: '✋' }, + { name: '🖖', icon: '🖖' }, + { name: '👌', icon: '👌' }, + { name: '🤌', icon: '🤌' }, + { name: '🤏', icon: '🤏' }, + { name: '✌️', icon: '✌️' }, + { name: '🤞', icon: '🤞' }, + { name: '🤟', icon: '🤟' }, + { name: '🤘', icon: '🤘' }, + { name: '🤙', icon: '🤙' }, + { name: '👈', icon: '👈' }, + { name: '👉', icon: '👉' }, + { name: '👆', icon: '👆' }, + { name: '🖕', icon: '🖕' }, + { name: '👇', icon: '👇' }, + { name: '☝️', icon: '☝️' }, + { name: '👍', icon: '👍' }, + { name: '👎', icon: '👎' }, + { name: '✊', icon: '✊' }, + { name: '👊', icon: '👊' }, + { name: '🤛', icon: '🤛' }, + { name: '🤜', icon: '🤜' }, + { name: '👏', icon: '👏' }, + { name: '🙌', icon: '🙌' }, + { name: '👐', icon: '👐' }, + { name: '🤲', icon: '🤲' }, + { name: '🤝', icon: '🤝' }, + { name: '🙏', icon: '🙏' }, + { name: '✍️', icon: '✍️' }, + { name: '💅', icon: '💅' }, + { name: '🤳', icon: '🤳' }, + { name: '💪', icon: '💪' }, + { name: '🦾', icon: '🦾' }, + { name: '🦿', icon: '🦿' }, + { name: '🦵', icon: '🦵' }, + { name: '🦶', icon: '🦶' }, + { name: '👂', icon: '👂' }, + { name: '🦻', icon: '🦻' }, + { name: '👃', icon: '👃' }, + { name: '🧠', icon: '🧠' }, + { name: '🫀', icon: '🫀' }, + { name: '🫁', icon: '🫁' }, + { name: '🦷', icon: '🦷' }, + { name: '🦴', icon: '🦴' }, + { name: '👀', icon: '👀' }, + { name: '👁️', icon: '👁️' }, + { name: '👅', icon: '👅' }, + { name: '👄', icon: '👄' }, + + // 人物和职业 + { name: '👶', icon: '👶' }, + { name: '🧒', icon: '🧒' }, + { name: '👦', icon: '👦' }, + { name: '👧', icon: '👧' }, + { name: '🧑', icon: '🧑' }, + { name: '👱', icon: '👱' }, + { name: '👨', icon: '👨' }, + { name: '🧔', icon: '🧔' }, + { name: '👩', icon: '👩' }, + { name: '🧓', icon: '🧓' }, + { name: '👴', icon: '👴' }, + { name: '👵', icon: '👵' }, + { name: '🙍', icon: '🙍' }, + { name: '🙎', icon: '🙎' }, + { name: '🙅', icon: '🙅' }, + { name: '🙆', icon: '🙆' }, + { name: '💁', icon: '💁' }, + { name: '🙋', icon: '🙋' }, + { name: '🧏', icon: '🧏' }, + { name: '🙇', icon: '🙇' }, + { name: '🤦', icon: '🤦' }, + { name: '🤷', icon: '🤷' }, + { name: '👨‍⚕️', icon: '👨‍⚕️' }, + { name: '👩‍⚕️', icon: '👩‍⚕️' }, + { name: '👨‍🌾', icon: '👨‍🌾' }, + { name: '👩‍🌾', icon: '👩‍🌾' }, + { name: '👨‍🍳', icon: '👨‍🍳' }, + { name: '👩‍🍳', icon: '👩‍🍳' }, + { name: '👨‍🎓', icon: '👨‍🎓' }, + { name: '👩‍🎓', icon: '👩‍🎓' }, + { name: '👨‍🎤', icon: '👨‍🎤' }, + { name: '👩‍🎤', icon: '👩‍🎤' }, + { name: '👨‍🏫', icon: '👨‍🏫' }, + { name: '👩‍🏫', icon: '👩‍🏫' }, + { name: '👨‍🏭', icon: '👨‍🏭' }, + { name: '👩‍🏭', icon: '👩‍🏭' }, + { name: '👨‍💻', icon: '👨‍💻' }, + { name: '👩‍💻', icon: '👩‍💻' }, + { name: '👨‍💼', icon: '👨‍💼' }, + { name: '👩‍💼', icon: '👩‍💼' }, + { name: '👨‍🔧', icon: '👨‍🔧' }, + { name: '👩‍🔧', icon: '👩‍🔧' }, + { name: '👨‍🔬', icon: '👨‍🔬' }, + { name: '👩‍🔬', icon: '👩‍🔬' }, + { name: '👨‍🎨', icon: '👨‍🎨' }, + { name: '👩‍🎨', icon: '👩‍🎨' }, + { name: '👨‍🚒', icon: '👨‍🚒' }, + { name: '👩‍🚒', icon: '👩‍🚒' }, + { name: '👨‍✈️', icon: '👨‍✈️' }, + { name: '👩‍✈️', icon: '👩‍✈️' }, + { name: '👨‍🚀', icon: '👨‍🚀' }, + { name: '👩‍🚀', icon: '👩‍🚀' }, + { name: '👨‍⚖️', icon: '👨‍⚖️' }, + { name: '👩‍⚖️', icon: '👩‍⚖️' }, + { name: '👰', icon: '👰' }, + { name: '🤵', icon: '🤵' }, + { name: '👸', icon: '👸' }, + { name: '🤴', icon: '🤴' }, + { name: '🥷', icon: '🥷' }, + { name: '🦸', icon: '🦸' }, + { name: '🦹', icon: '🦹' }, + { name: '🧙', icon: '🧙' }, + { name: '🧚', icon: '🧚' }, + { name: '🧛', icon: '🧛' }, + { name: '🧜', icon: '🧜' }, + { name: '🧝', icon: '🧝' }, + { name: '🧞', icon: '🧞' }, + { name: '🧟', icon: '🧟' }, + { name: '💆', icon: '💆' }, + { name: '💇', icon: '💇' }, + { name: '🚶', icon: '🚶' }, + { name: '🧍', icon: '🧍' }, + { name: '🧎', icon: '🧎' }, + { name: '🏃', icon: '🏃' }, + { name: '💃', icon: '💃' }, + { name: '🕺', icon: '🕺' }, + { name: '🕴️', icon: '🕴️' }, + { name: '👯', icon: '👯' }, + { name: '🧖', icon: '🧖' }, + { name: '🧗', icon: '🧗' }, + { name: '🤺', icon: '🤺' }, + { name: '🏇', icon: '🏇' }, + { name: '⛷️', icon: '⛷️' }, + { name: '🏂', icon: '🏂' }, + { name: '🏌️', icon: '🏌️' }, + { name: '🏄', icon: '🏄' }, + { name: '🚣', icon: '🚣' }, + { name: '🏊', icon: '🏊' }, + { name: '⛹️', icon: '⛹️' }, + { name: '🏋️', icon: '🏋️' }, + { name: '🚴', icon: '🚴' }, + { name: '🚵', icon: '🚵' }, + { name: '🤸', icon: '🤸' }, + { name: '🤼', icon: '🤼' }, + { name: '🤽', icon: '🤽' }, + { name: '🤾', icon: '🤾' }, + { name: '🤹', icon: '🤹' }, + { name: '🧘', icon: '🧘' }, + { name: '🛀', icon: '🛀' }, + { name: '🛌', icon: '🛌' }, + + // 文件和文档 + { name: '📁', icon: '📁' }, + { name: '📂', icon: '📂' }, + { name: '📄', icon: '📄' }, + { name: '📋', icon: '📋' }, + { name: '📊', icon: '📊' }, + { name: '📈', icon: '📈' }, + { name: '📉', icon: '📉' }, + { name: '📝', icon: '📝' }, + { name: '📚', icon: '📚' }, + { name: '📖', icon: '📖' }, + { name: '📑', icon: '📑' }, + { name: '🗂️', icon: '🗂️' }, + { name: '🗃️', icon: '🗃️' }, + { name: '🗄️', icon: '🗄️' }, + { name: '📇', icon: '📇' }, + + // 技术和开发 + { name: '💻', icon: '💻' }, + { name: '🖥️', icon: '🖥️' }, + { name: '⌨️', icon: '⌨️' }, + { name: '🖱️', icon: '🖱️' }, + { name: '💾', icon: '💾' }, + { name: '💿', icon: '💿' }, + { name: '📀', icon: '📀' }, + { name: '🔧', icon: '🔧' }, + { name: '🔨', icon: '🔨' }, + { name: '⚙️', icon: '⚙️' }, + { name: '🛠️', icon: '🛠️' }, + { name: '🔩', icon: '🔩' }, + { name: '⚡', icon: '⚡' }, + { name: '🔌', icon: '🔌' }, + { name: '🔋', icon: '🔋' }, + { name: '🖨️', icon: '🖨️' }, + { name: '⌨️', icon: '⌨️' }, + { name: '🖱️', icon: '🖱️' }, + { name: '🖲️', icon: '🖲️' }, + + // 网络和通信 + { name: '🌐', icon: '🌐' }, + { name: '🌍', icon: '🌍' }, + { name: '🌎', icon: '🌎' }, + { name: '🌏', icon: '🌏' }, + { name: '📡', icon: '📡' }, + { name: '📶', icon: '📶' }, + { name: '📱', icon: '📱' }, + { name: '📞', icon: '📞' }, + { name: '☎️', icon: '☎️' }, + { name: '📧', icon: '📧' }, + { name: '📨', icon: '📨' }, + { name: '📩', icon: '📩' }, + { name: '📬', icon: '📬' }, + { name: '📭', icon: '📭' }, + { name: '📮', icon: '📮' }, + { name: '📪', icon: '📪' }, + { name: '📫', icon: '📫' }, + { name: '📯', icon: '📯' }, + { name: '📢', icon: '📢' }, + { name: '📣', icon: '📣' }, + + // 多媒体 + { name: '🎵', icon: '🎵' }, + { name: '🎶', icon: '🎶' }, + { name: '🎤', icon: '🎤' }, + { name: '🎧', icon: '🎧' }, + { name: '📻', icon: '📻' }, + { name: '📺', icon: '📺' }, + { name: '📹', icon: '📹' }, + { name: '📷', icon: '📷' }, + { name: '📸', icon: '📸' }, + { name: '🎥', icon: '🎥' }, + { name: '🎬', icon: '🎬' }, + { name: '🎭', icon: '🎭' }, + { name: '🎨', icon: '🎨' }, + { name: '🖌️', icon: '🖌️' }, + { name: '🖍️', icon: '🖍️' }, + { name: '✏️', icon: '✏️' }, + { name: '✒️', icon: '✒️' }, + { name: '🖊️', icon: '🖊️' }, + { name: '🖋️', icon: '🖋️' }, + { name: '🖍️', icon: '🖍️' }, + { name: '📐', icon: '📐' }, + { name: '📏', icon: '📏' }, + { name: '📌', icon: '📌' }, + { name: '📍', icon: '📍' }, + { name: '🖋️', icon: '🖋️' }, + + // 游戏和娱乐 + { name: '🎮', icon: '🎮' }, + { name: '🕹️', icon: '🕹️' }, + { name: '🎯', icon: '🎯' }, + { name: '🎲', icon: '🎲' }, + { name: '🃏', icon: '🃏' }, + { name: '🎰', icon: '🎰' }, + { name: '🎪', icon: '🎪' }, + { name: '🎨', icon: '🎨' }, + { name: '🎭', icon: '🎭' }, + { name: '🎪', icon: '🎪' }, + { name: '🎨', icon: '🎨' }, + + // 安全和保护 + // 安全和保护 + { name: '🔒', icon: '🔒' }, + { name: '🔓', icon: '🔓' }, + { name: '🔐', icon: '🔐' }, + { name: '🔑', icon: '🔑' }, + { name: '🗝️', icon: '🗝️' }, + { name: '🛡️', icon: '🛡️' }, + { name: '🔰', icon: '🔰' }, + { name: '⚔️', icon: '⚔️' }, + + // 搜索和导航 + { name: '🔍', icon: '🔍' }, + { name: '🔎', icon: '🔎' }, + { name: '🧭', icon: '🧭' }, + { name: '🗺️', icon: '🗺️' }, + { name: '📍', icon: '📍' }, + { name: '📌', icon: '📌' }, + { name: '📎', icon: '📎' }, + { name: '🔗', icon: '🔗' }, + { name: '⛓️', icon: '⛓️' }, + { name: '🧭', icon: '🧭' }, + + // 云和存储 + { name: '☁️', icon: '☁️' }, + { name: '⛅', icon: '⛅' }, + { name: '🌤️', icon: '🌤️' }, + { name: '📦', icon: '📦' }, + { name: '📫', icon: '📫' }, + { name: '🗳️', icon: '🗳️' }, + { name: '🗂️', icon: '🗂️' }, + { name: '🗃️', icon: '🗃️' }, + { name: '🗄️', icon: '🗄️' }, + { name: '🗑️', icon: '🗑️' }, + + // 人物和社交 + { name: '👤', icon: '👤' }, + { name: '👥', icon: '👥' }, + { name: '👨‍💻', icon: '👨‍💻' }, + { name: '👩‍💻', icon: '👩‍💻' }, + { name: '🤖', icon: '🤖' }, + { name: '👾', icon: '👾' }, + { name: '👥', icon: '👥' }, + { name: '👪', icon: '👪' }, + { name: '👫', icon: '👫' }, + { name: '👬', icon: '👬' }, + + // 符号和标记 + { name: '⭐', icon: '⭐' }, + { name: '🌟', icon: '🌟' }, + { name: '✨', icon: '✨' }, + { name: '💫', icon: '💫' }, + { name: '❤️', icon: '❤️' }, + { name: '💙', icon: '💙' }, + { name: '💚', icon: '💚' }, + { name: '💛', icon: '💛' }, + { name: '🧡', icon: '🧡' }, + { name: '💜', icon: '💜' }, + { name: '🖤', icon: '🖤' }, + { name: '🤍', icon: '🤍' }, + { name: '💯', icon: '💯' }, + { name: '✅', icon: '✅' }, + { name: '❌', icon: '❌' }, + { name: '⚠️', icon: '⚠️' }, + { name: '🚀', icon: '🚀' }, + { name: '🎉', icon: '🎉' }, + { name: '🎊', icon: '🎊' }, + { name: '🔥', icon: '🔥' }, + { name: '💎', icon: '💎' }, + { name: '🏆', icon: '🏆' }, + { name: '🥇', icon: '🥇' }, + { name: '🥈', icon: '🥈' }, + { name: '🥉', icon: '🥉' }, + { name: '🏅', icon: '🏅' }, + + // 箭头和方向 + { name: '⬆️', icon: '⬆️' }, + { name: '⬇️', icon: '⬇️' }, + { name: '⬅️', icon: '⬅️' }, + { name: '➡️', icon: '➡️' }, + { name: '↗️', icon: '↗️' }, + { name: '↘️', icon: '↘️' }, + { name: '↙️', icon: '↙️' }, + { name: '↖️', icon: '↖️' }, + { name: '🔄', icon: '🔄' }, + { name: '🔃', icon: '🔃' }, + { name: '🔁', icon: '🔁' }, + { name: '🔂', icon: '🔂' }, + { name: '⤴️', icon: '⤴️' }, + { name: '⤵️', icon: '⤵️' }, + { name: '🔀', icon: '🔀' }, + { name: '🔄', icon: '🔄' }, + { name: '🔃', icon: '🔃' }, + { name: '🔁', icon: '🔁' }, + { name: '🔂', icon: '🔂' }, + { name: '▶️', icon: '▶️' }, + + // 其他常用 + { name: '📅', icon: '📅' }, + { name: '📆', icon: '📆' }, + { name: '🗓️', icon: '🗓️' }, + { name: '⏰', icon: '⏰' }, + { name: '⏱️', icon: '⏱️' }, + { name: '⏲️', icon: '⏲️' }, + { name: '🕐', icon: '🕐' }, + { name: '📐', icon: '📐' }, + { name: '📏', icon: '📏' }, + { name: '♻️', icon: '♻️' }, + { name: '🔄', icon: '🔄' }, + { name: '➕', icon: '➕' }, + { name: '➖', icon: '➖' }, + { name: '✖️', icon: '✖️' }, + { name: '➗', icon: '➗' }, + { name: '🟢', icon: '🟢' }, + { name: '🔴', icon: '🔴' }, + { name: '🟡', icon: '🟡' }, + { name: '🔵', icon: '🔵' }, + { name: '🟣', icon: '🟣' }, + { name: '🟠', icon: '🟠' }, + { name: '⚫', icon: '⚫' }, + { name: '⚪', icon: '⚪' }, + + // 动物和自然 + { name: '🐶', icon: '🐶' }, + { name: '🐱', icon: '🐱' }, + { name: '🐭', icon: '🐭' }, + { name: '🐹', icon: '🐹' }, + { name: '🐰', icon: '🐰' }, + { name: '🦊', icon: '🦊' }, + { name: '🐻', icon: '🐻' }, + { name: '🐼', icon: '🐼' }, + { name: '🐨', icon: '🐨' }, + { name: '🐯', icon: '🐯' }, + { name: '🦁', icon: '🦁' }, + { name: '🐮', icon: '🐮' }, + { name: '🐷', icon: '🐷' }, + { name: '🐽', icon: '🐽' }, + { name: '🐸', icon: '🐸' }, + { name: '🐵', icon: '🐵' }, + { name: '🙈', icon: '🙈' }, + { name: '🙉', icon: '🙉' }, + { name: '🙊', icon: '🙊' }, + { name: '🐒', icon: '🐒' }, + { name: '🐔', icon: '🐔' }, + { name: '🐧', icon: '🐧' }, + { name: '🐦', icon: '🐦' }, + { name: '🐤', icon: '🐤' }, + { name: '🐣', icon: '🐣' }, + { name: '🐥', icon: '🐥' }, + { name: '🦆', icon: '🦆' }, + { name: '🦅', icon: '🦅' }, + { name: '🦉', icon: '🦉' }, + { name: '🦇', icon: '🦇' }, + { name: '🐺', icon: '🐺' }, + { name: '🐗', icon: '🐗' }, + { name: '🐴', icon: '🐴' }, + { name: '🦄', icon: '🦄' }, + { name: '🐝', icon: '🐝' }, + { name: '🐛', icon: '🐛' }, + { name: '🦋', icon: '🦋' }, + { name: '🐌', icon: '🐌' }, + { name: '🐞', icon: '🐞' }, + { name: '🐜', icon: '🐜' }, + { name: '🦟', icon: '🦟' }, + { name: '🦗', icon: '🦗' }, + { name: '🕷️', icon: '🕷️' }, + { name: '🕸️', icon: '🕸️' }, + { name: '🦂', icon: '🦂' }, + { name: '🐢', icon: '🐢' }, + { name: '🐍', icon: '🐍' }, + { name: '🦎', icon: '🦎' }, + { name: '🦖', icon: '🦖' }, + { name: '🦕', icon: '🦕' }, + { name: '🐙', icon: '🐙' }, + { name: '🦑', icon: '🦑' }, + { name: '🦐', icon: '🦐' }, + { name: '🦞', icon: '🦞' }, + { name: '🦀', icon: '🦀' }, + { name: '🐡', icon: '🐡' }, + { name: '🐠', icon: '🐠' }, + { name: '🐟', icon: '🐟' }, + { name: '🐬', icon: '🐬' }, + { name: '🐳', icon: '🐳' }, + { name: '🐋', icon: '🐋' }, + { name: '🦈', icon: '🦈' }, + { name: '🐊', icon: '🐊' }, + { name: '🐅', icon: '🐅' }, + { name: '🐆', icon: '🐆' }, + { name: '🦓', icon: '🦓' }, + { name: '🦍', icon: '🦍' }, + { name: '🦧', icon: '🦧' }, + { name: '🐘', icon: '🐘' }, + { name: '🦛', icon: '🦛' }, + { name: '🦏', icon: '🦏' }, + { name: '🐪', icon: '🐪' }, + { name: '🐫', icon: '🐫' }, + { name: '🦒', icon: '🦒' }, + { name: '🦘', icon: '🦘' }, + { name: '🐃', icon: '🐃' }, + { name: '🐂', icon: '🐂' }, + { name: '🐄', icon: '🐄' }, + { name: '🐎', icon: '🐎' }, + { name: '🐖', icon: '🐖' }, + { name: '🐏', icon: '🐏' }, + { name: '🐑', icon: '🐑' }, + { name: '🦙', icon: '🦙' }, + { name: '🐐', icon: '🐐' }, + { name: '🦌', icon: '🦌' }, + { name: '🐕', icon: '🐕' }, + { name: '🐩', icon: '🐩' }, + { name: '🦮', icon: '🦮' }, + { name: '🐕‍🦺', icon: '🐕‍🦺' }, + { name: '🐈', icon: '🐈' }, + { name: '🐈‍⬛', icon: '🐈‍⬛' }, + { name: '🐓', icon: '🐓' }, + { name: '🦃', icon: '🦃' }, + { name: '🦚', icon: '🦚' }, + { name: '🦜', icon: '🦜' }, + { name: '🦢', icon: '🦢' }, + { name: '🦩', icon: '🦩' }, + { name: '🕊️', icon: '🕊️' }, + { name: '🐇', icon: '🐇' }, + { name: '🦝', icon: '🦝' }, + { name: '🦨', icon: '🦨' }, + { name: '🦡', icon: '🦡' }, + { name: '🦦', icon: '🦦' }, + { name: '🦥', icon: '🦥' }, + { name: '🐁', icon: '🐁' }, + { name: '🐀', icon: '🐀' }, + { name: '🐿️', icon: '🐿️' }, + { name: '🦔', icon: '🦔' }, + + // 植物和食物 + { name: '🌲', icon: '🌲' }, + { name: '🌳', icon: '🌳' }, + { name: '🌴', icon: '🌴' }, + { name: '🌵', icon: '🌵' }, + { name: '🌶️', icon: '🌶️' }, + { name: '🍄', icon: '🍄' }, + { name: '🌰', icon: '🌰' }, + { name: '🌱', icon: '🌱' }, + { name: '🌿', icon: '🌿' }, + { name: '☘️', icon: '☘️' }, + { name: '🍀', icon: '🍀' }, + { name: '🎋', icon: '🎋' }, + { name: '🎍', icon: '🎍' }, + { name: '🍎', icon: '🍎' }, + { name: '🍊', icon: '🍊' }, + { name: '🍋', icon: '🍋' }, + { name: '🍌', icon: '🍌' }, + { name: '🍉', icon: '🍉' }, + { name: '🍇', icon: '🍇' }, + { name: '🍓', icon: '🍓' }, + { name: '🫐', icon: '🫐' }, + { name: '🍈', icon: '🍈' }, + { name: '🍒', icon: '🍒' }, + { name: '🍑', icon: '🍑' }, + { name: '🥭', icon: '🥭' }, + { name: '🍍', icon: '🍍' }, + { name: '🥥', icon: '🥥' }, + { name: '🥝', icon: '🥝' }, + { name: '🍅', icon: '🍅' }, + { name: '🍆', icon: '🍆' }, + { name: '🥑', icon: '🥑' }, + { name: '🥦', icon: '🥦' }, + { name: '🥬', icon: '🥬' }, + { name: '🥒', icon: '🥒' }, + { name: '🌶️', icon: '🌶️' }, + { name: '🫑', icon: '🫑' }, + { name: '🌽', icon: '🌽' }, + { name: '🥕', icon: '🥕' }, + { name: '🫒', icon: '🫒' }, + { name: '🧄', icon: '🧄' }, + { name: '🧅', icon: '🧅' }, + { name: '🥔', icon: '🥔' }, + { name: '🍠', icon: '🍠' }, + { name: '🥐', icon: '🥐' }, + { name: '🥖', icon: '🥖' }, + { name: '🍞', icon: '🍞' }, + { name: '🥨', icon: '🥨' }, + { name: '🥯', icon: '🥯' }, + { name: '🥞', icon: '🥞' }, + { name: '🧇', icon: '🧇' }, + { name: '🧀', icon: '🧀' }, + { name: '🍖', icon: '🍖' }, + { name: '🍗', icon: '🍗' }, + { name: '🥩', icon: '🥩' }, + { name: '🥓', icon: '🥓' }, + { name: '🍔', icon: '🍔' }, + { name: '🍟', icon: '🍟' }, + { name: '🍕', icon: '🍕' }, + { name: '🌭', icon: '🌭' }, + { name: '🥪', icon: '🥪' }, + { name: '🌮', icon: '🌮' }, + { name: '🌯', icon: '🌯' }, + { name: '🫔', icon: '🫔' }, + { name: '🥙', icon: '🥙' }, + { name: '🧆', icon: '🧆' }, + { name: '🥚', icon: '🥚' }, + { name: '🍳', icon: '🍳' }, + { name: '🥘', icon: '🥘' }, + { name: '🍲', icon: '🍲' }, + { name: '🫕', icon: '🫕' }, + { name: '🥣', icon: '🥣' }, + { name: '🥗', icon: '🥗' }, + { name: '🍿', icon: '🍿' }, + { name: '🧈', icon: '🧈' }, + { name: '🧂', icon: '🧂' }, + { name: '🥫', icon: '🥫' }, + { name: '🍱', icon: '🍱' }, + { name: '🍘', icon: '🍘' }, + { name: '🍙', icon: '🍙' }, + { name: '🍚', icon: '🍚' }, + { name: '🍛', icon: '🍛' }, + { name: '🍜', icon: '🍜' }, + { name: '🍝', icon: '🍝' }, + { name: '🍠', icon: '🍠' }, + { name: '🍢', icon: '🍢' }, + { name: '🍣', icon: '🍣' }, + { name: '🍤', icon: '🍤' }, + { name: '🍥', icon: '🍥' }, + { name: '🥮', icon: '🥮' }, + { name: '🍡', icon: '🍡' }, + { name: '🥟', icon: '🥟' }, + { name: '🥠', icon: '🥠' }, + { name: '🥡', icon: '🥡' }, + + // 交通工具 + { name: '🚗', icon: '🚗' }, + { name: '🚕', icon: '🚕' }, + { name: '🚙', icon: '🚙' }, + { name: '🚌', icon: '🚌' }, + { name: '🚎', icon: '🚎' }, + { name: '🏎️', icon: '🏎️' }, + { name: '🚓', icon: '🚓' }, + { name: '🚑', icon: '🚑' }, + { name: '🚒', icon: '🚒' }, + { name: '🚐', icon: '🚐' }, + { name: '🛻', icon: '🛻' }, + { name: '🚚', icon: '🚚' }, + { name: '🚛', icon: '🚛' }, + { name: '🚜', icon: '🚜' }, + { name: '🏍️', icon: '🏍️' }, + { name: '🛵', icon: '🛵' }, + { name: '🚲', icon: '🚲' }, + { name: '🛴', icon: '🛴' }, + { name: '🛹', icon: '🛹' }, + { name: '🛼', icon: '🛼' }, + { name: '🚁', icon: '🚁' }, + { name: '🛸', icon: '🛸' }, + { name: '✈️', icon: '✈️' }, + { name: '🛩️', icon: '🛩️' }, + { name: '🛫', icon: '🛫' }, + { name: '🛬', icon: '🛬' }, + { name: '🪂', icon: '🪂' }, + { name: '💺', icon: '💺' }, + { name: '🚀', icon: '🚀' }, + { name: '🛰️', icon: '🛰️' }, + { name: '🚉', icon: '🚉' }, + { name: '🚞', icon: '🚞' }, + { name: '🚝', icon: '🚝' }, + { name: '🚄', icon: '🚄' }, + { name: '🚅', icon: '🚅' }, + { name: '🚈', icon: '🚈' }, + { name: '🚂', icon: '🚂' }, + { name: '🚆', icon: '🚆' }, + { name: '🚇', icon: '🚇' }, + { name: '🚊', icon: '🚊' }, + { name: '🚋', icon: '🚋' }, + { name: '🚃', icon: '🚃' }, + { name: '🚋', icon: '🚋' }, + { name: '🚎', icon: '🚎' }, + { name: '🚐', icon: '🚐' }, + { name: '🚑', icon: '🚑' }, + { name: '🚒', icon: '🚒' }, + { name: '🚓', icon: '🚓' }, + { name: '🚔', icon: '🚔' }, + { name: '🚕', icon: '🚕' }, + { name: '🚖', icon: '🚖' }, + { name: '🚗', icon: '🚗' }, + { name: '🚘', icon: '🚘' }, + { name: '🚙', icon: '🚙' }, + { name: '🛻', icon: '🛻' }, + { name: '🚚', icon: '🚚' }, + { name: '🚛', icon: '🚛' }, + { name: '🚜', icon: '🚜' }, + { name: '🏎️', icon: '🏎️' }, + { name: '🏍️', icon: '🏍️' }, + { name: '🛵', icon: '🛵' }, + { name: '🦽', icon: '🦽' }, + { name: '🦼', icon: '🦼' }, + { name: '🛺', icon: '🛺' }, + { name: '🚲', icon: '🚲' }, + { name: '🛴', icon: '🛴' }, + { name: '🛹', icon: '🛹' }, + { name: '🛼', icon: '🛼' }, + { name: '🚏', icon: '🚏' }, + { name: '🛣️', icon: '🛣️' }, + { name: '🛤️', icon: '🛤️' }, + { name: '🛢️', icon: '🛢️' }, + { name: '⛽', icon: '⛽' }, + { name: '🚨', icon: '🚨' }, + { name: '🚥', icon: '🚥' }, + { name: '🚦', icon: '🚦' }, + { name: '🛑', icon: '🛑' }, + { name: '🚧', icon: '🚧' }, + { name: '⚓', icon: '⚓' }, + { name: '⛵', icon: '⛵' }, + { name: '🛶', icon: '🛶' }, + { name: '🚤', icon: '🚤' }, + { name: '🛳️', icon: '🛳️' }, + { name: '⛴️', icon: '⛴️' }, + { name: '🚢', icon: '🚢' }, +]; + +interface CategoryEditModalProps { + isOpen: boolean; + onClose: () => void; + category: Category | null; + isCreating?: boolean; +} + +export const CategoryEditModal: React.FC = ({ + isOpen, + onClose, + category, + isCreating = false +}) => { + const { addCustomCategory, updateCustomCategory, language } = useAppStore(); + + const [formData, setFormData] = useState({ + name: '', + icon: 'Folder', + keywords: '' + }); + const [customIcon, setCustomIcon] = useState(''); + const [showCustomInput, setShowCustomInput] = useState(false); + + useEffect(() => { + if (category && !isCreating) { + setFormData({ + name: category.name, + icon: category.icon, + keywords: category.keywords.join(', ') + }); + } else if (isCreating) { + setFormData({ + name: '', + icon: '📁', + keywords: '' + }); + } + }, [category, isCreating, isOpen]); + + const handleSave = () => { + if (!formData.name.trim()) { + alert(language === 'zh' ? '请输入分类名称' : 'Please enter category name'); + return; + } + + const categoryData: Category = { + id: category?.id || Date.now().toString(), + name: formData.name.trim(), + icon: formData.icon, + keywords: formData.keywords.split(',').map(k => k.trim()).filter(k => k), + isCustom: true + }; + + if (isCreating) { + addCustomCategory(categoryData); + } else if (category) { + updateCustomCategory(category.id, { + name: categoryData.name, + icon: categoryData.icon, + keywords: categoryData.keywords + }); + } + + onClose(); + }; + + const handleIconSelect = (iconValue: string) => { + setFormData(prev => ({ ...prev, icon: iconValue })); + setShowCustomInput(false); + setCustomIcon(''); + }; + + const handleCustomIconSubmit = () => { + if (customIcon.trim()) { + setFormData(prev => ({ ...prev, icon: customIcon.trim() })); + setShowCustomInput(false); + setCustomIcon(''); + } + }; + + const handleClose = () => { + setFormData({ + name: '', + icon: 'Folder', + keywords: '' + }); + setCustomIcon(''); + setShowCustomInput(false); + onClose(); + }; + + const t = (zh: string, en: string) => language === 'zh' ? zh : en; + + return ( + +
+ {/* Category Name */} +
+ + setFormData(prev => ({ ...prev, name: e.target.value }))} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder={t('输入分类名称', 'Enter category name')} + autoFocus + /> +
+ + {/* Icon Selection */} +
+ + + {/* Custom Icon Input */} + {showCustomInput && ( +
+
+ setCustomIcon(e.target.value)} + placeholder={t('输入任意emoji...', 'Enter any emoji...')} + className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-center text-lg" + maxLength={4} + autoFocus + /> + + +
+

+ {t('提示:可以输入任何emoji表情,如 🎯 🎨 🎪 等', 'Tip: You can enter any emoji, like 🎯 🎨 🎪 etc.')} +

+
+ )} + +
+ {availableIcons.map((iconItem) => ( + + ))} +
+

+ {t('当前选择:', 'Selected:')} {formData.icon} + +

+

+ {t( + '包含所有常用emoji分类:笑脸、人物、手势、动物、食物、交通、符号等', + 'Includes all common emoji categories: smileys, people, gestures, animals, food, transport, symbols, etc.' + )} +

+
+ + {/* Keywords */} +
+ + setFormData(prev => ({ ...prev, keywords: e.target.value }))} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder={t('用逗号分隔关键词', 'Comma-separated keywords')} + /> +

+ {t('用于自动匹配仓库到此分类', 'Used to automatically match repositories to this category')} +

+
+ + {/* Action Buttons */} +
+ + +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx new file mode 100644 index 0000000..70eb066 --- /dev/null +++ b/src/components/Modal.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { X } from 'lucide-react'; + +interface ModalProps { + isOpen: boolean; + onClose: () => void; + title: string; + children: React.ReactNode; + maxWidth?: string; +} + +export const Modal: React.FC = ({ + isOpen, + onClose, + title, + children, + maxWidth = 'max-w-md' +}) => { + if (!isOpen) return null; + + return ( +
+ {/* Backdrop */} +
+ + {/* Modal */} +
+
+ {/* Header */} +
+

+ {title} +

+ +
+ + {/* Content */} +
+ {children} +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/components/RepositoryEditModal.tsx b/src/components/RepositoryEditModal.tsx new file mode 100644 index 0000000..5a78769 --- /dev/null +++ b/src/components/RepositoryEditModal.tsx @@ -0,0 +1,273 @@ +import React, { useState, useEffect } from 'react'; +import { Save, X, Plus } from 'lucide-react'; +import { Modal } from './Modal'; +import { Repository } from '../types'; +import { useAppStore, getAllCategories } from '../store/useAppStore'; + +interface RepositoryEditModalProps { + isOpen: boolean; + onClose: () => void; + repository: Repository | null; +} + +export const RepositoryEditModal: React.FC = ({ + isOpen, + onClose, + repository +}) => { + const { updateRepository, language, customCategories, repositories } = useAppStore(); + + const [formData, setFormData] = useState({ + description: '', + tags: [] as string[], + category: '' + }); + const [newTag, setNewTag] = useState(''); + + const allCategories = getAllCategories(customCategories, language); + + // 获取仓库当前所属的分类 + const getCurrentCategory = (repo: Repository) => { + // 如果有自定义分类,直接返回 + if (repo.custom_category) { + return repo.custom_category; + } + + // 否则根据AI标签或其他信息推断当前分类 + for (const category of allCategories) { + if (category.id === 'all') continue; + + // 检查AI标签匹配 + if (repo.ai_tags && repo.ai_tags.length > 0) { + const hasMatch = repo.ai_tags.some(tag => + category.keywords.some(keyword => + tag.toLowerCase().includes(keyword.toLowerCase()) || + keyword.toLowerCase().includes(tag.toLowerCase()) + ) + ); + if (hasMatch) { + return category.name; + } + } + + // 检查传统匹配方式 + const repoText = [ + repo.name, + repo.description || '', + repo.language || '', + ...(repo.topics || []), + repo.ai_summary || '' + ].join(' ').toLowerCase(); + + const hasKeywordMatch = category.keywords.some(keyword => + repoText.includes(keyword.toLowerCase()) + ); + + if (hasKeywordMatch) { + return category.name; + } + } + + return ''; + }; + useEffect(() => { + if (repository && isOpen) { + const currentCategory = getCurrentCategory(repository); + setFormData({ + description: repository.custom_description || repository.description || '', + tags: repository.custom_tags || repository.ai_tags || repository.topics || [], + category: currentCategory + }); + } + }, [repository, isOpen]); + + const handleSave = () => { + if (!repository) return; + + const updatedRepo = { + ...repository, + custom_description: formData.description !== repository.description ? formData.description : undefined, + custom_tags: formData.tags.length > 0 ? formData.tags : undefined, + custom_category: formData.category ? formData.category : undefined, + last_edited: new Date().toISOString() + }; + + updateRepository(updatedRepo); + onClose(); + }; + + const handleClose = () => { + setFormData({ + description: '', + tags: [], + category: '' + }); + setNewTag(''); + onClose(); + }; + + const handleAddTag = () => { + if (newTag.trim() && !formData.tags.includes(newTag.trim())) { + setFormData(prev => ({ + ...prev, + tags: [...prev.tags, newTag.trim()] + })); + setNewTag(''); + } + }; + + const handleRemoveTag = (tagToRemove: string) => { + setFormData(prev => ({ + ...prev, + tags: prev.tags.filter(tag => tag !== tagToRemove) + })); + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + handleAddTag(); + } + }; + + const t = (zh: string, en: string) => language === 'zh' ? zh : en; + + if (!repository) return null; + + return ( + +
+ {/* Repository Info */} +
+
+ {repository.owner.login} +
+

+ {repository.name} +

+

+ {repository.owner.login} +

+
+
+ {repository.description && ( +

+ {t('原始描述:', 'Original description:')} {repository.description} +

+ )} +
+ + {/* Description */} +
+ +