mirror of
https://github.com/AmintaCCCP/GithubStarsManager.git
synced 2025-11-24 18:32:51 +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