mirror of
https://github.com/AmintaCCCP/GithubStarsManager.git
synced 2025-11-25 02:34:54 +08:00
Several optimizations
This commit is contained in:
3
.bolt/config.json
Normal file
3
.bolt/config.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"template": "bolt-vite-react-ts"
|
||||||
|
}
|
||||||
5
.bolt/prompt
Normal file
5
.bolt/prompt
Normal file
@@ -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.
|
||||||
204
.github/workflows/build-desktop.yml
vendored
Normal file
204
.github/workflows/build-desktop.yml
vendored
Normal file
@@ -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 }}
|
||||||
42
.github/workflows/build-web.yml
vendored
Normal file
42
.github/workflows/build-web.yml
vendored
Normal file
@@ -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 # 可选:如果你有自定义域名
|
||||||
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@@ -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
|
||||||
86
electron-builder.yml
Normal file
86
electron-builder.yml
Normal file
@@ -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
|
||||||
97
public/icon.svg
Normal file
97
public/icon.svg
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!-- Background gradient -->
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#667eea;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="starGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#ffd700;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#ffb347;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="githubGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#f0f0f0;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
<!-- Shadow filter -->
|
||||||
|
<filter id="shadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||||
|
<feDropShadow dx="2" dy="4" stdDeviation="3" flood-color="#000000" flood-opacity="0.3"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Background circle -->
|
||||||
|
<circle cx="256" cy="256" r="240" fill="url(#bgGradient)" stroke="#4a5568" stroke-width="4"/>
|
||||||
|
|
||||||
|
<!-- GitHub logo background circle -->
|
||||||
|
<circle cx="256" cy="200" r="80" fill="url(#githubGradient)" filter="url(#shadow)"/>
|
||||||
|
|
||||||
|
<!-- GitHub cat icon (simplified) -->
|
||||||
|
<g transform="translate(256, 200)">
|
||||||
|
<!-- Cat body -->
|
||||||
|
<ellipse cx="0" cy="10" rx="35" ry="45" fill="#24292e"/>
|
||||||
|
<!-- Cat head -->
|
||||||
|
<circle cx="0" cy="-20" r="30" fill="#24292e"/>
|
||||||
|
<!-- Cat ears -->
|
||||||
|
<polygon points="-20,-35 -10,-50 -5,-35" fill="#24292e"/>
|
||||||
|
<polygon points="20,-35 10,-50 5,-35" fill="#24292e"/>
|
||||||
|
<!-- Cat eyes -->
|
||||||
|
<circle cx="-10" cy="-25" r="4" fill="#ffffff"/>
|
||||||
|
<circle cx="10" cy="-25" r="4" fill="#ffffff"/>
|
||||||
|
<circle cx="-10" cy="-25" r="2" fill="#24292e"/>
|
||||||
|
<circle cx="10" cy="-25" r="2" fill="#24292e"/>
|
||||||
|
<!-- Cat nose -->
|
||||||
|
<polygon points="0,-15 -3,-10 3,-10" fill="#ffffff"/>
|
||||||
|
<!-- Cat whiskers -->
|
||||||
|
<line x1="-25" y1="-20" x2="-35" y2="-18" stroke="#ffffff" stroke-width="2"/>
|
||||||
|
<line x1="-25" y1="-15" x2="-35" y2="-15" stroke="#ffffff" stroke-width="2"/>
|
||||||
|
<line x1="25" y1="-20" x2="35" y2="-18" stroke="#ffffff" stroke-width="2"/>
|
||||||
|
<line x1="25" y1="-15" x2="35" y2="-15" stroke="#ffffff" stroke-width="2"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Stars around GitHub logo -->
|
||||||
|
<g fill="url(#starGradient)" filter="url(#shadow)">
|
||||||
|
<!-- Main star -->
|
||||||
|
<g transform="translate(180, 140)">
|
||||||
|
<polygon points="0,-20 6,-6 20,-6 10,2 16,16 0,8 -16,16 -10,2 -20,-6 -6,-6" />
|
||||||
|
</g>
|
||||||
|
<!-- Secondary stars -->
|
||||||
|
<g transform="translate(330, 160) scale(0.7)">
|
||||||
|
<polygon points="0,-20 6,-6 20,-6 10,2 16,16 0,8 -16,16 -10,2 -20,-6 -6,-6" />
|
||||||
|
</g>
|
||||||
|
<g transform="translate(200, 280) scale(0.5)">
|
||||||
|
<polygon points="0,-20 6,-6 20,-6 10,2 16,16 0,8 -16,16 -10,2 -20,-6 -6,-6" />
|
||||||
|
</g>
|
||||||
|
<g transform="translate(320, 280) scale(0.6)">
|
||||||
|
<polygon points="0,-20 6,-6 20,-6 10,2 16,16 0,8 -16,16 -10,2 -20,-6 -6,-6" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Management/Organization symbols -->
|
||||||
|
<g transform="translate(256, 350)" fill="#ffffff" filter="url(#shadow)">
|
||||||
|
<!-- Folder icon -->
|
||||||
|
<rect x="-30" y="-10" width="60" height="40" rx="4" fill="#4299e1" opacity="0.9"/>
|
||||||
|
<rect x="-25" y="-15" width="20" height="8" rx="2" fill="#4299e1" opacity="0.9"/>
|
||||||
|
<!-- Files inside folder -->
|
||||||
|
<rect x="-20" y="-2" width="15" height="2" fill="#ffffff" opacity="0.8"/>
|
||||||
|
<rect x="-20" y="5" width="25" height="2" fill="#ffffff" opacity="0.8"/>
|
||||||
|
<rect x="-20" y="12" width="20" height="2" fill="#ffffff" opacity="0.8"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- AI/Smart features indicator -->
|
||||||
|
<g transform="translate(380, 320)" fill="#10b981" filter="url(#shadow)">
|
||||||
|
<circle cx="0" cy="0" r="15" fill="#10b981"/>
|
||||||
|
<!-- Brain/AI symbol -->
|
||||||
|
<path d="M-8,-5 Q-8,-10 -3,-10 Q2,-10 2,-5 Q7,-5 7,0 Q7,5 2,5 Q-3,5 -3,0 Q-8,0 -8,-5" fill="#ffffff"/>
|
||||||
|
<circle cx="-3" cy="-3" r="1.5" fill="#10b981"/>
|
||||||
|
<circle cx="2" cy="-3" r="1.5" fill="#10b981"/>
|
||||||
|
<circle cx="-1" cy="2" r="1" fill="#10b981"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- App title -->
|
||||||
|
<text x="256" y="450" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" font-weight="bold" fill="#ffffff" filter="url(#shadow)">
|
||||||
|
GitHub Stars
|
||||||
|
</text>
|
||||||
|
<text x="256" y="475" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" fill="#e2e8f0">
|
||||||
|
Manager
|
||||||
|
</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.4 KiB |
180
scripts/build-desktop.js
Normal file
180
scripts/build-desktop.js
Normal file
@@ -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);
|
||||||
|
}
|
||||||
1014
src/components/CategoryEditModal.tsx
Normal file
1014
src/components/CategoryEditModal.tsx
Normal file
File diff suppressed because it is too large
Load Diff
53
src/components/Modal.tsx
Normal file
53
src/components/Modal.tsx
Normal file
@@ -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<ModalProps> = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
maxWidth = 'max-w-md'
|
||||||
|
}) => {
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-50 overflow-y-auto">
|
||||||
|
{/* Backdrop */}
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black bg-opacity-50 transition-opacity"
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Modal */}
|
||||||
|
<div className="flex min-h-full items-center justify-center p-4">
|
||||||
|
<div className={`relative w-full ${maxWidth} bg-white dark:bg-gray-800 rounded-xl shadow-xl border border-gray-200 dark:border-gray-700`}>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="p-2 rounded-lg text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="p-6">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
273
src/components/RepositoryEditModal.tsx
Normal file
273
src/components/RepositoryEditModal.tsx
Normal file
@@ -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<RepositoryEditModalProps> = ({
|
||||||
|
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 (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={handleClose}
|
||||||
|
title={t('编辑仓库信息', 'Edit Repository Info')}
|
||||||
|
maxWidth="max-w-2xl"
|
||||||
|
>
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Repository Info */}
|
||||||
|
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
|
||||||
|
<div className="flex items-center space-x-3 mb-2">
|
||||||
|
<img
|
||||||
|
src={repository.owner.avatar_url}
|
||||||
|
alt={repository.owner.login}
|
||||||
|
className="w-8 h-8 rounded-full"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-gray-900 dark:text-white">
|
||||||
|
{repository.name}
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{repository.owner.login}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{repository.description && (
|
||||||
|
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||||
|
{t('原始描述:', 'Original description:')} {repository.description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
{t('自定义描述', 'Custom Description')}
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
value={formData.description}
|
||||||
|
onChange={(e) => setFormData(prev => ({ ...prev, description: 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 resize-none"
|
||||||
|
rows={3}
|
||||||
|
placeholder={t('输入自定义描述...', 'Enter custom description...')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Category */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
{t('分类', 'Category')}
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={formData.category}
|
||||||
|
onChange={(e) => setFormData(prev => ({ ...prev, category: 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"
|
||||||
|
>
|
||||||
|
<option value="">{t('选择分类...', 'Select category...')}</option>
|
||||||
|
{allCategories.filter(cat => cat.id !== 'all').map(category => (
|
||||||
|
<option key={category.id} value={category.name}>
|
||||||
|
{category.icon} {category.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{formData.category && (
|
||||||
|
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{t('当前分类:', 'Current category:')} {formData.category}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tags */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
{t('自定义标签', 'Custom Tags')}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{/* Existing Tags */}
|
||||||
|
{formData.tags.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-2 mb-3">
|
||||||
|
{formData.tags.map((tag, index) => (
|
||||||
|
<span
|
||||||
|
key={index}
|
||||||
|
className="inline-flex items-center px-3 py-1 bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300 rounded-full text-sm"
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
<button
|
||||||
|
onClick={() => handleRemoveTag(tag)}
|
||||||
|
className="ml-2 text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-200"
|
||||||
|
>
|
||||||
|
<X className="w-3 h-3" />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Add New Tag */}
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={newTag}
|
||||||
|
onChange={(e) => setNewTag(e.target.value)}
|
||||||
|
onKeyPress={handleKeyPress}
|
||||||
|
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 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder={t('添加标签...', 'Add tag...')}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleAddTag}
|
||||||
|
disabled={!newTag.trim()}
|
||||||
|
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<div className="flex justify-end space-x-3 pt-4 border-t border-gray-200 dark:border-gray-600">
|
||||||
|
<button
|
||||||
|
onClick={handleClose}
|
||||||
|
className="flex items-center space-x-2 px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
<span>{t('取消', 'Cancel')}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleSave}
|
||||||
|
className="flex items-center space-x-2 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
|
||||||
|
>
|
||||||
|
<Save className="w-4 h-4" />
|
||||||
|
<span>{t('保存', 'Save')}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user