mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-29 14:31:35 +08:00
更新35.2.0
This commit is contained in:
parent
8546219546
commit
e5c3e57430
7
.npmrc
7
.npmrc
@ -1 +1,6 @@
|
||||
electron_mirror=https://npmmirror.com/mirrors/electron/
|
||||
registry=https://registry.npmmirror.com/
|
||||
electron_mirror=https://npmmirror.com/mirrors/electron/
|
||||
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
|
||||
ELECTRON_CUSTOM_DIR="{{ version }}"
|
||||
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
|
||||
ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/
|
||||
|
||||
1
.yarnrc
Normal file
1
.yarnrc
Normal file
@ -0,0 +1 @@
|
||||
electron_mirror "https://npmmirror.com/mirrors/electron/"
|
||||
4
check-electron-version.js
Normal file
4
check-electron-version.js
Normal file
@ -0,0 +1,4 @@
|
||||
const { app } = require('electron');
|
||||
console.log(`Electron version: ${process.versions.electron}`);
|
||||
console.log(`Chrome version: ${process.versions.chrome}`);
|
||||
console.log(`Node version: ${process.versions.node}`);
|
||||
4
electron-builder-patched.js
Normal file
4
electron-builder-patched.js
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
// 这是一个启动脚本,用于加载补丁并运行 electron-builder
|
||||
require('J:\Cherry\cherry-studioTTS\node_modules\app-builder-lib\out\node-module-collector\nodeModulesCollector.patch.js')
|
||||
require('electron-builder/cli')
|
||||
28
package.json
28
package.json
@ -22,16 +22,19 @@
|
||||
"dev": "electron-vite dev",
|
||||
"build": "npm run typecheck && electron-vite build",
|
||||
"build:check": "yarn test && yarn typecheck && yarn check:i18n",
|
||||
"build:unpack": "dotenv npm run build && electron-builder --dir",
|
||||
"build:win": "dotenv npm run build && electron-builder --win",
|
||||
"build:win:x64": "dotenv npm run build && electron-builder --win --x64",
|
||||
"build:win:arm64": "dotenv npm run build && electron-builder --win --arm64",
|
||||
"build:mac": "dotenv electron-vite build && electron-builder --mac",
|
||||
"build:mac:arm64": "dotenv electron-vite build && electron-builder --mac --arm64",
|
||||
"build:mac:x64": "dotenv electron-vite build && electron-builder --mac --x64",
|
||||
"build:linux": "dotenv electron-vite build && electron-builder --linux",
|
||||
"build:linux:arm64": "dotenv electron-vite build && electron-builder --linux --arm64",
|
||||
"build:linux:x64": "dotenv electron-vite build && electron-builder --linux --x64",
|
||||
"fix:electron-builder": "node scripts/fix-electron-builder.js",
|
||||
"update:electron": "node scripts/update-electron.js",
|
||||
"update:electron:direct": "node scripts/update-electron-direct.js",
|
||||
"build:unpack": "dotenv npm run build && npm run fix:electron-builder && electron-builder --dir",
|
||||
"build:win": "dotenv npm run build && npm run fix:electron-builder && electron-builder --win",
|
||||
"build:win:x64": "dotenv npm run build && npm run fix:electron-builder && electron-builder --win --x64",
|
||||
"build:win:arm64": "dotenv npm run build && npm run fix:electron-builder && electron-builder --win --arm64",
|
||||
"build:mac": "dotenv electron-vite build && npm run fix:electron-builder && electron-builder --mac",
|
||||
"build:mac:arm64": "dotenv electron-vite build && npm run fix:electron-builder && electron-builder --mac --arm64",
|
||||
"build:mac:x64": "dotenv electron-vite build && npm run fix:electron-builder && electron-builder --mac --x64",
|
||||
"build:linux": "dotenv electron-vite build && npm run fix:electron-builder && electron-builder --linux",
|
||||
"build:linux:arm64": "dotenv electron-vite build && npm run fix:electron-builder && electron-builder --linux --arm64",
|
||||
"build:linux:x64": "dotenv electron-vite build && npm run fix:electron-builder && electron-builder --linux --x64",
|
||||
"build:npm": "node scripts/build-npm.js",
|
||||
"release": "node scripts/version.js",
|
||||
"publish": "yarn build:check && yarn release patch push",
|
||||
@ -52,7 +55,7 @@
|
||||
"test:renderer:coverage": "vitest run --coverage",
|
||||
"format": "prettier --write .",
|
||||
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"postinstall": "electron-builder install-app-deps && npm run fix:electron-builder",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -155,6 +158,7 @@
|
||||
"turndown-plugin-gfm": "^1.0.2",
|
||||
"undici": "^7.4.0",
|
||||
"webdav": "^5.8.0",
|
||||
"zipfile": "^0.5.12",
|
||||
"zipread": "^1.3.3",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
@ -213,7 +217,7 @@
|
||||
"dexie": "^4.0.8",
|
||||
"dexie-react-hooks": "^1.1.7",
|
||||
"dotenv-cli": "^7.4.2",
|
||||
"electron": "31.7.6",
|
||||
"electron": "35.2.0",
|
||||
"electron-builder": "26.0.13",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-icon-builder": "^2.0.1",
|
||||
|
||||
109
scripts/fix-electron-builder.js
Normal file
109
scripts/fix-electron-builder.js
Normal file
@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 修复 electron-builder 堆栈溢出问题的补丁脚本
|
||||
*
|
||||
* 这个脚本修复了 electron-builder 在处理循环依赖时导致的堆栈溢出问题。
|
||||
* 主要修改了以下文件:
|
||||
* 1. node_modules/app-builder-lib/out/node-module-collector/nodeModulesCollector.js
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 获取 nodeModulesCollector.js 文件的路径
|
||||
const nodeModulesCollectorPath = path.join(
|
||||
process.cwd(),
|
||||
'node_modules',
|
||||
'app-builder-lib',
|
||||
'out',
|
||||
'node-module-collector',
|
||||
'nodeModulesCollector.js'
|
||||
);
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!fs.existsSync(nodeModulesCollectorPath)) {
|
||||
console.error('找不到 nodeModulesCollector.js 文件,请确保已安装 electron-builder');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
let content = fs.readFileSync(nodeModulesCollectorPath, 'utf8');
|
||||
|
||||
// 修复 1: 修改 _getNodeModules 方法,添加环路检测
|
||||
const oldGetNodeModulesMethod = /(_getNodeModules\(dependencies, result\) \{[\s\S]*?result\.sort\(\(a, b\) => a\.name\.localeCompare\(b\.name\)\);\s*\})/;
|
||||
const newGetNodeModulesMethod = `_getNodeModules(dependencies, result, depth = 0, visited = new Set()) {
|
||||
// 添加递归深度限制
|
||||
if (depth > 10) {
|
||||
console.log("递归深度超过10,停止递归");
|
||||
return;
|
||||
}
|
||||
|
||||
if (dependencies.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const d of dependencies.values()) {
|
||||
const reference = [...d.references][0];
|
||||
const moduleId = \`\${d.name}@\${reference}\`;
|
||||
|
||||
// 环路检测:如果已经访问过这个模块,则跳过
|
||||
if (visited.has(moduleId)) {
|
||||
console.log(\`检测到循环依赖: \${moduleId}\`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 标记为已访问
|
||||
visited.add(moduleId);
|
||||
|
||||
const p = this.dependencyPathMap.get(moduleId);
|
||||
if (p === undefined) {
|
||||
builder_util_1.log.debug({ name: d.name, reference }, "cannot find path for dependency");
|
||||
continue;
|
||||
}
|
||||
const node = {
|
||||
name: d.name,
|
||||
version: reference,
|
||||
dir: p,
|
||||
};
|
||||
result.push(node);
|
||||
if (d.dependencies.size > 0) {
|
||||
node.dependencies = [];
|
||||
this._getNodeModules(d.dependencies, node.dependencies, depth + 1, visited);
|
||||
}
|
||||
|
||||
// 处理完成后,从已访问集合中移除,允许在其他路径中再次访问
|
||||
visited.delete(moduleId);
|
||||
}
|
||||
result.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}`;
|
||||
|
||||
content = content.replace(oldGetNodeModulesMethod, newGetNodeModulesMethod);
|
||||
|
||||
// 修复 2: 修改 getNodeModules 方法,传递 visited 集合
|
||||
const oldGetNodeModulesCall = /(this\._getNodeModules\(hoisterResult\.dependencies, this\.nodeModules\);)/;
|
||||
const newGetNodeModulesCall = `// 创建一个新的 visited 集合用于环路检测
|
||||
const visited = new Set();
|
||||
|
||||
this._getNodeModules(hoisterResult.dependencies, this.nodeModules, 0, visited);`;
|
||||
|
||||
content = content.replace(oldGetNodeModulesCall, newGetNodeModulesCall);
|
||||
|
||||
// 修复 3: 修改 convertToDependencyGraph 方法,跳过路径未定义的依赖
|
||||
const oldPathCheck = /(if \(!dependencies\.path\) \{[\s\S]*?throw new Error\("unable to parse `path` during `tree\.dependencies` reduce"\);[\s\S]*?\})/;
|
||||
const newPathCheck = `if (!dependencies.path) {
|
||||
builder_util_1.log.error({
|
||||
packageName,
|
||||
data: dependencies,
|
||||
parentModule: tree.name,
|
||||
parentVersion: tree.version,
|
||||
}, "dependency path is undefined");
|
||||
// 跳过这个依赖而不是抛出错误
|
||||
console.log(\`跳过路径未定义的依赖: \${packageName}\`);
|
||||
return acc;
|
||||
}`;
|
||||
|
||||
content = content.replace(oldPathCheck, newPathCheck);
|
||||
|
||||
// 写入修改后的内容
|
||||
fs.writeFileSync(nodeModulesCollectorPath, content, 'utf8');
|
||||
|
||||
console.log('成功应用 electron-builder 堆栈溢出修复补丁!');
|
||||
130
scripts/update-electron-direct.js
Normal file
130
scripts/update-electron-direct.js
Normal file
@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Electron 直接版本更新脚本
|
||||
*
|
||||
* 这个脚本帮助您直接更新到指定版本的 Electron
|
||||
* 使用方法: node scripts/update-electron-direct.js [version]
|
||||
*
|
||||
* 例如: node scripts/update-electron-direct.js 32.0.0
|
||||
*/
|
||||
|
||||
const { execSync } = require('child_process')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const readline = require('readline')
|
||||
|
||||
// 创建命令行交互界面
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
})
|
||||
|
||||
// 获取当前 package.json 中的 Electron 版本
|
||||
function getCurrentElectronVersion() {
|
||||
const packageJsonPath = path.join(process.cwd(), 'package.json')
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
||||
return packageJson.devDependencies.electron
|
||||
}
|
||||
|
||||
// 更新 package.json 中的 Electron 版本
|
||||
function updateElectronVersion(version) {
|
||||
const packageJsonPath = path.join(process.cwd(), 'package.json')
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
||||
|
||||
// 保存旧版本
|
||||
const oldVersion = packageJson.devDependencies.electron
|
||||
|
||||
// 更新版本
|
||||
packageJson.devDependencies.electron = version
|
||||
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8')
|
||||
|
||||
console.log(`已更新 package.json 中的 Electron 版本: ${oldVersion} -> ${version}`)
|
||||
return oldVersion
|
||||
}
|
||||
|
||||
// 安装依赖
|
||||
function installDependencies() {
|
||||
console.log('正在安装依赖...')
|
||||
try {
|
||||
execSync('yarn install', { stdio: 'inherit' })
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('安装依赖失败:', error.message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 测试应用
|
||||
function testApp() {
|
||||
console.log('正在启动开发模式测试应用...')
|
||||
try {
|
||||
execSync('npm run dev', { stdio: 'inherit' })
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('测试应用失败:', error.message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
const targetVersion = process.argv[2]
|
||||
|
||||
if (!targetVersion) {
|
||||
console.error('请指定目标 Electron 版本')
|
||||
console.log('使用方法: node scripts/update-electron-direct.js [version]')
|
||||
console.log('例如: node scripts/update-electron-direct.js 32.0.0')
|
||||
rl.close()
|
||||
return
|
||||
}
|
||||
|
||||
const currentVersion = getCurrentElectronVersion()
|
||||
console.log(`当前 Electron 版本: ${currentVersion}`)
|
||||
|
||||
rl.question(`确定要直接更新到 Electron ${targetVersion} 吗?(y/n) `, (answer) => {
|
||||
if (answer.toLowerCase() !== 'y') {
|
||||
console.log('操作已取消')
|
||||
rl.close()
|
||||
return
|
||||
}
|
||||
|
||||
const oldVersion = updateElectronVersion(targetVersion)
|
||||
|
||||
if (!installDependencies()) {
|
||||
console.log(`Electron ${targetVersion} 安装依赖失败,正在恢复到原版本 ${oldVersion}`)
|
||||
updateElectronVersion(oldVersion)
|
||||
installDependencies()
|
||||
rl.close()
|
||||
return
|
||||
}
|
||||
|
||||
rl.question('依赖安装成功,是否测试应用?(y/n) ', (answer) => {
|
||||
if (answer.toLowerCase() !== 'y') {
|
||||
console.log('跳过测试步骤')
|
||||
rl.close()
|
||||
return
|
||||
}
|
||||
|
||||
if (!testApp()) {
|
||||
console.log(`Electron ${targetVersion} 测试失败`)
|
||||
rl.question(`是否恢复到原版本 ${oldVersion}?(y/n) `, (answer) => {
|
||||
if (answer.toLowerCase() === 'y') {
|
||||
console.log(`正在恢复到原版本 ${oldVersion}`)
|
||||
updateElectronVersion(oldVersion)
|
||||
installDependencies()
|
||||
}
|
||||
rl.close()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`Electron ${targetVersion} 更新并测试成功!`)
|
||||
rl.close()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 运行主函数
|
||||
main().catch((error) => {
|
||||
console.error('发生错误:', error)
|
||||
rl.close()
|
||||
})
|
||||
266
scripts/update-electron.js
Normal file
266
scripts/update-electron.js
Normal file
@ -0,0 +1,266 @@
|
||||
/**
|
||||
* Electron 版本更新脚本
|
||||
*
|
||||
* 这个脚本帮助您逐步更新到更新版本的 Electron
|
||||
* 使用方法: node scripts/update-electron.js [target-version]
|
||||
*
|
||||
* 例如: node scripts/update-electron.js 32.0.0
|
||||
*/
|
||||
|
||||
const { execSync } = require('child_process')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const readline = require('readline')
|
||||
const https = require('https')
|
||||
|
||||
// 创建命令行交互界面
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
})
|
||||
|
||||
// 获取当前 package.json 中的 Electron 版本
|
||||
function getCurrentElectronVersion() {
|
||||
const packageJsonPath = path.join(process.cwd(), 'package.json')
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
||||
return packageJson.devDependencies.electron
|
||||
}
|
||||
|
||||
// 更新 package.json 中的 Electron 版本
|
||||
function updateElectronVersion(version) {
|
||||
const packageJsonPath = path.join(process.cwd(), 'package.json')
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
||||
|
||||
// 保存旧版本
|
||||
const oldVersion = packageJson.devDependencies.electron
|
||||
|
||||
// 更新版本
|
||||
packageJson.devDependencies.electron = version
|
||||
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8')
|
||||
|
||||
console.log(`已更新 package.json 中的 Electron 版本: ${oldVersion} -> ${version}`)
|
||||
return oldVersion
|
||||
}
|
||||
|
||||
// 安装依赖
|
||||
function installDependencies() {
|
||||
console.log('正在安装依赖...')
|
||||
try {
|
||||
execSync('yarn install', { stdio: 'inherit' })
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('安装依赖失败:', error.message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 测试应用
|
||||
function testApp() {
|
||||
console.log('正在启动开发模式测试应用...')
|
||||
try {
|
||||
execSync('npm run dev', { stdio: 'inherit' })
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('测试应用失败:', error.message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 从 npm 获取 Electron 的可用版本
|
||||
function getAvailableElectronVersions() {
|
||||
return new Promise((resolve, reject) => {
|
||||
https
|
||||
.get('https://registry.npmjs.org/electron', (res) => {
|
||||
let data = ''
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk
|
||||
})
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const json = JSON.parse(data)
|
||||
const versions = Object.keys(json.versions)
|
||||
.filter((v) => !v.includes('-')) // 过滤掉预发布版本
|
||||
.sort((a, b) => compareVersions(a, b))
|
||||
resolve(versions)
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
.on('error', (error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 比较版本号
|
||||
function compareVersions(a, b) {
|
||||
const partsA = a.split('.').map(Number)
|
||||
const partsB = b.split('.').map(Number)
|
||||
|
||||
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
||||
const partA = partsA[i] || 0
|
||||
const partB = partsB[i] || 0
|
||||
|
||||
if (partA < partB) return -1
|
||||
if (partA > partB) return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// 获取从当前版本到目标版本的升级路径
|
||||
function getUpgradePath(currentVersion, targetVersion, allVersions) {
|
||||
// 清理版本号,移除可能的前缀(如 ^、~)
|
||||
currentVersion = currentVersion.replace(/[^0-9.]/g, '')
|
||||
|
||||
// 过滤出在当前版本和目标版本之间的所有版本
|
||||
const relevantVersions = allVersions.filter(
|
||||
(v) => compareVersions(v, currentVersion) > 0 && compareVersions(v, targetVersion) <= 0
|
||||
)
|
||||
|
||||
// 按主要版本分组
|
||||
const versionsByMajor = {}
|
||||
relevantVersions.forEach((v) => {
|
||||
const major = v.split('.')[0]
|
||||
if (!versionsByMajor[major]) {
|
||||
versionsByMajor[major] = []
|
||||
}
|
||||
versionsByMajor[major].push(v)
|
||||
})
|
||||
|
||||
// 为每个主要版本选择最新的次要版本
|
||||
const upgradePath = []
|
||||
Object.keys(versionsByMajor)
|
||||
.sort((a, b) => Number(a) - Number(b))
|
||||
.forEach((major) => {
|
||||
const versions = versionsByMajor[major]
|
||||
// 添加该主要版本的最新版本
|
||||
upgradePath.push(versions[versions.length - 1])
|
||||
})
|
||||
|
||||
// 确保包含目标版本
|
||||
if (upgradePath.length === 0 || upgradePath[upgradePath.length - 1] !== targetVersion) {
|
||||
upgradePath.push(targetVersion)
|
||||
}
|
||||
|
||||
return upgradePath
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
try {
|
||||
const currentVersionFull = getCurrentElectronVersion()
|
||||
const currentVersion = currentVersionFull.replace(/[^0-9.]/g, '')
|
||||
console.log(`当前 Electron 版本: ${currentVersionFull} (${currentVersion})`)
|
||||
|
||||
// 获取可用的 Electron 版本
|
||||
console.log('正在获取可用的 Electron 版本...')
|
||||
const allVersions = await getAvailableElectronVersions()
|
||||
|
||||
// 获取最新版本
|
||||
const latestVersion = allVersions[allVersions.length - 1]
|
||||
console.log(`最新的 Electron 版本: ${latestVersion}`)
|
||||
|
||||
// 确定目标版本
|
||||
let targetVersion = process.argv[2] || latestVersion
|
||||
|
||||
if (compareVersions(targetVersion, currentVersion) <= 0) {
|
||||
console.log(`目标版本 ${targetVersion} 不高于当前版本 ${currentVersion},无需更新`)
|
||||
rl.close()
|
||||
return
|
||||
}
|
||||
|
||||
// 获取升级路径
|
||||
const upgradePath = getUpgradePath(currentVersion, targetVersion, allVersions)
|
||||
|
||||
console.log(`\n从 ${currentVersion} 到 ${targetVersion} 的推荐升级路径:`)
|
||||
upgradePath.forEach((v, i) => {
|
||||
console.log(`${i + 1}. ${v}`)
|
||||
})
|
||||
|
||||
rl.question('\n是否按照推荐路径逐步升级?(y/n) ', async (answer) => {
|
||||
if (answer.toLowerCase() !== 'y') {
|
||||
console.log('操作已取消')
|
||||
rl.close()
|
||||
return
|
||||
}
|
||||
|
||||
// 保存原始版本,以便在失败时恢复
|
||||
const originalVersion = currentVersionFull
|
||||
|
||||
// 逐步升级
|
||||
for (let i = 0; i < upgradePath.length; i++) {
|
||||
const version = upgradePath[i]
|
||||
console.log(`\n===== 正在升级到 Electron ${version} (${i + 1}/${upgradePath.length}) =====`)
|
||||
|
||||
updateElectronVersion(version)
|
||||
|
||||
if (!installDependencies()) {
|
||||
console.log(`Electron ${version} 安装依赖失败`)
|
||||
|
||||
const restoreAnswer = await new Promise((resolve) => {
|
||||
rl.question('是否恢复到原始版本?(y/n) ', resolve)
|
||||
})
|
||||
|
||||
if (restoreAnswer.toLowerCase() === 'y') {
|
||||
console.log(`正在恢复到原始版本 ${originalVersion}`)
|
||||
updateElectronVersion(originalVersion)
|
||||
installDependencies()
|
||||
}
|
||||
|
||||
rl.close()
|
||||
return
|
||||
}
|
||||
|
||||
const testAnswer = await new Promise((resolve) => {
|
||||
rl.question(`依赖安装成功,是否测试 Electron ${version}?(y/n) `, resolve)
|
||||
})
|
||||
|
||||
if (testAnswer.toLowerCase() === 'y') {
|
||||
if (!testApp()) {
|
||||
console.log(`Electron ${version} 测试失败`)
|
||||
|
||||
const restoreAnswer = await new Promise((resolve) => {
|
||||
rl.question('是否恢复到原始版本?(y/n) ', resolve)
|
||||
})
|
||||
|
||||
if (restoreAnswer.toLowerCase() === 'y') {
|
||||
console.log(`正在恢复到原始版本 ${originalVersion}`)
|
||||
updateElectronVersion(originalVersion)
|
||||
installDependencies()
|
||||
}
|
||||
|
||||
rl.close()
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`Electron ${version} 测试成功!`)
|
||||
}
|
||||
|
||||
if (i < upgradePath.length - 1) {
|
||||
const continueAnswer = await new Promise((resolve) => {
|
||||
rl.question('是否继续升级到下一个版本?(y/n) ', resolve)
|
||||
})
|
||||
|
||||
if (continueAnswer.toLowerCase() !== 'y') {
|
||||
console.log(`升级停止在 Electron ${version}`)
|
||||
rl.close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n成功升级到 Electron ${targetVersion}!`)
|
||||
rl.close()
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('发生错误:', error)
|
||||
rl.close()
|
||||
}
|
||||
}
|
||||
|
||||
// 运行主函数
|
||||
main()
|
||||
@ -7,7 +7,7 @@ import { isMac, isWin } from '@main/constant'
|
||||
import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process'
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
import { MCPServer, Shortcut, ThemeMode } from '@types' // Import MCPServer here
|
||||
import { BrowserWindow, ipcMain, session, shell } from 'electron'
|
||||
import { BrowserWindow, ipcMain, session, shell, webContents } from 'electron'
|
||||
import log from 'electron-log'
|
||||
|
||||
import { titleBarOverlayDark, titleBarOverlayLight } from './config'
|
||||
@ -193,6 +193,44 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
}
|
||||
})
|
||||
|
||||
// 销毁webContents
|
||||
ipcMain.handle('browser:destroy-webcontents', async (_, webContentsId: number) => {
|
||||
try {
|
||||
// 尝试通过ID获取webContents
|
||||
const allWebContents = webContents.getAllWebContents()
|
||||
const targetWebContents = allWebContents.find((wc) => wc.id === webContentsId)
|
||||
|
||||
if (targetWebContents) {
|
||||
// 如果找到了webContents,尝试销毁它
|
||||
if (!targetWebContents.isDestroyed()) {
|
||||
// 先停止加载
|
||||
targetWebContents.stop()
|
||||
|
||||
// 加载空白页面
|
||||
targetWebContents.loadURL('about:blank')
|
||||
|
||||
// 等待一小段时间,让空白页面加载完成
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
|
||||
// 销毁webContents - 使用close方法
|
||||
targetWebContents.close() // WebContents没有destroy方法,但有close方法
|
||||
|
||||
log.info(`Successfully destroyed webContents with ID: ${webContentsId}`)
|
||||
return { success: true }
|
||||
} else {
|
||||
log.info(`WebContents with ID ${webContentsId} is already destroyed`)
|
||||
return { success: true, alreadyDestroyed: true }
|
||||
}
|
||||
} else {
|
||||
log.warn(`WebContents with ID ${webContentsId} not found`)
|
||||
return { success: false, error: 'WebContents not found' }
|
||||
}
|
||||
} catch (error: any) {
|
||||
log.error(`Failed to destroy webContents with ID ${webContentsId}:`, error)
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
})
|
||||
|
||||
// check for update
|
||||
ipcMain.handle(IpcChannel.App_CheckForUpdate, async () => {
|
||||
const update = await appUpdater.autoUpdater.checkForUpdates()
|
||||
|
||||
@ -265,6 +265,10 @@ const api = {
|
||||
getSupportedLanguages: () => ipcRenderer.invoke(IpcChannel.CodeExecutor_GetSupportedLanguages),
|
||||
executeJS: (code: string) => ipcRenderer.invoke(IpcChannel.CodeExecutor_ExecuteJS, code),
|
||||
executePython: (code: string) => ipcRenderer.invoke(IpcChannel.CodeExecutor_ExecutePython, code)
|
||||
},
|
||||
browser: {
|
||||
clearData: () => ipcRenderer.invoke('browser:clear-data'),
|
||||
destroyWebContents: (webContentsId: number) => ipcRenderer.invoke('browser:destroy-webcontents', webContentsId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -82,16 +82,23 @@ const WebviewContainer = styled.div`
|
||||
.webview-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
visibility: hidden;
|
||||
z-index: 1;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
& webview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
`
|
||||
|
||||
@ -134,108 +141,300 @@ interface Tab {
|
||||
const Browser = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
// 选项卡状态管理
|
||||
const [tabs, setTabs] = useState<Tab[]>([
|
||||
{
|
||||
id: '1',
|
||||
title: 'Google',
|
||||
url: 'https://www.google.com',
|
||||
isLoading: false,
|
||||
canGoBack: false,
|
||||
canGoForward: false
|
||||
// 从本地存储加载选项卡状态
|
||||
const loadTabsFromStorage = (): { tabs: Tab[]; activeTabId: string } => {
|
||||
try {
|
||||
const savedTabs = localStorage.getItem('browser_tabs')
|
||||
const savedActiveTabId = localStorage.getItem('browser_active_tab_id')
|
||||
|
||||
if (savedTabs && savedActiveTabId) {
|
||||
// 解析保存的选项卡
|
||||
const parsedTabs = JSON.parse(savedTabs) as Tab[]
|
||||
|
||||
// 验证选项卡数据
|
||||
const validTabs = parsedTabs.filter(
|
||||
(tab) => tab && tab.id && tab.url && typeof tab.id === 'string' && typeof tab.url === 'string'
|
||||
)
|
||||
|
||||
// 确保至少有一个选项卡
|
||||
if (validTabs.length > 0) {
|
||||
// 验证活动选项卡ID
|
||||
const isActiveTabValid = validTabs.some((tab) => tab.id === savedActiveTabId)
|
||||
const finalActiveTabId = isActiveTabValid ? savedActiveTabId : validTabs[0].id
|
||||
|
||||
console.log('Loaded tabs from storage:', validTabs.length, 'tabs, active tab:', finalActiveTabId)
|
||||
|
||||
return {
|
||||
tabs: validTabs,
|
||||
activeTabId: finalActiveTabId
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load tabs from storage:', error)
|
||||
}
|
||||
])
|
||||
const [activeTabId, setActiveTabId] = useState('1')
|
||||
|
||||
// 默认选项卡
|
||||
const defaultTabs = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Google',
|
||||
url: 'https://www.google.com',
|
||||
isLoading: false,
|
||||
canGoBack: false,
|
||||
canGoForward: false
|
||||
}
|
||||
]
|
||||
|
||||
console.log('Using default tabs')
|
||||
|
||||
return {
|
||||
tabs: defaultTabs,
|
||||
activeTabId: '1'
|
||||
}
|
||||
}
|
||||
|
||||
// 保存选项卡状态到本地存储
|
||||
const saveTabsToStorage = (tabs: Tab[], activeTabId: string) => {
|
||||
try {
|
||||
// 确保只保存当前有效的选项卡
|
||||
const validTabs = tabs.filter((tab) => tab && tab.id && tab.url)
|
||||
|
||||
// 确保activeTabId是有效的
|
||||
const isActiveTabValid = validTabs.some((tab) => tab.id === activeTabId)
|
||||
const finalActiveTabId = isActiveTabValid ? activeTabId : validTabs.length > 0 ? validTabs[0].id : ''
|
||||
|
||||
// 保存到localStorage
|
||||
localStorage.setItem('browser_tabs', JSON.stringify(validTabs))
|
||||
localStorage.setItem('browser_active_tab_id', finalActiveTabId)
|
||||
|
||||
console.log('Saved tabs to storage:', validTabs.length, 'tabs, active tab:', finalActiveTabId)
|
||||
} catch (error) {
|
||||
console.error('Failed to save tabs to storage:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 选项卡状态管理
|
||||
const initialTabState = loadTabsFromStorage()
|
||||
const [tabs, setTabs] = useState<Tab[]>(initialTabState.tabs)
|
||||
const [activeTabId, setActiveTabId] = useState(initialTabState.activeTabId)
|
||||
|
||||
// 获取当前活动选项卡
|
||||
const activeTab = tabs.find((tab) => tab.id === activeTabId) || tabs[0]
|
||||
|
||||
// 兼容旧代码的状态
|
||||
const [url, setUrl] = useState(activeTab.url)
|
||||
// 兼容旧代码的状态,只使用setter
|
||||
const [, setUrl] = useState(activeTab.url)
|
||||
const [currentUrl, setCurrentUrl] = useState('')
|
||||
const [canGoBack, setCanGoBack] = useState(false)
|
||||
const [canGoForward, setCanGoForward] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
// 使用对象存储多个webview引用
|
||||
// 使用对象存储多个webview引用 - 使用useRef确保在组件重新渲染时保持引用
|
||||
const webviewRefs = useRef<Record<string, WebviewTag | null>>({})
|
||||
|
||||
// 使用useRef保存webview的会话状态
|
||||
const webviewSessionsRef = useRef<Record<string, boolean>>({})
|
||||
|
||||
// 使用useRef保存事件监听器清理函数
|
||||
const cleanupFunctionsRef = useRef<Record<string, () => void>>({})
|
||||
|
||||
// 获取当前活动的webview引用
|
||||
const webviewRef = {
|
||||
current: webviewRefs.current[activeTabId] || null
|
||||
} as React.RefObject<WebviewTag>
|
||||
|
||||
useEffect(() => {
|
||||
const webview = webviewRef.current
|
||||
if (!webview) return
|
||||
// 创建一个函数来设置webview的所有事件监听器
|
||||
const setupWebviewListeners = (webview: WebviewTag, tabId: string) => {
|
||||
console.log('Setting up event listeners for tab:', tabId)
|
||||
|
||||
// 处理加载开始事件
|
||||
const handleDidStartLoading = () => {
|
||||
setIsLoading(true)
|
||||
// 只更新当前活动标签页的UI状态
|
||||
if (tabId === activeTabId) {
|
||||
setIsLoading(true)
|
||||
}
|
||||
|
||||
// 更新选项卡状态
|
||||
updateTabInfo(activeTabId, { isLoading: true })
|
||||
updateTabInfo(tabId, { isLoading: true })
|
||||
}
|
||||
|
||||
// 处理加载结束事件
|
||||
const handleDidStopLoading = () => {
|
||||
const currentURL = webview.getURL()
|
||||
setIsLoading(false)
|
||||
setCurrentUrl(currentURL)
|
||||
|
||||
// 只更新当前活动标签页的UI状态
|
||||
if (tabId === activeTabId) {
|
||||
setIsLoading(false)
|
||||
setCurrentUrl(currentURL)
|
||||
}
|
||||
|
||||
// 更新选项卡状态
|
||||
updateTabInfo(activeTabId, {
|
||||
updateTabInfo(tabId, {
|
||||
isLoading: false,
|
||||
url: currentURL,
|
||||
title: webview.getTitle() || currentURL
|
||||
title: webview.getTitle() || currentURL,
|
||||
canGoBack: webview.canGoBack(),
|
||||
canGoForward: webview.canGoForward()
|
||||
})
|
||||
}
|
||||
|
||||
// 处理导航事件
|
||||
const handleDidNavigate = (e: any) => {
|
||||
const canGoBackStatus = webview.canGoBack()
|
||||
const canGoForwardStatus = webview.canGoForward()
|
||||
|
||||
setCurrentUrl(e.url)
|
||||
setCanGoBack(canGoBackStatus)
|
||||
setCanGoForward(canGoForwardStatus)
|
||||
// 只更新当前活动标签页的UI状态
|
||||
if (tabId === activeTabId) {
|
||||
setCurrentUrl(e.url)
|
||||
setCanGoBack(canGoBackStatus)
|
||||
setCanGoForward(canGoForwardStatus)
|
||||
}
|
||||
|
||||
// 更新选项卡状态
|
||||
updateTabInfo(activeTabId, {
|
||||
updateTabInfo(tabId, {
|
||||
url: e.url,
|
||||
canGoBack: canGoBackStatus,
|
||||
canGoForward: canGoForwardStatus
|
||||
})
|
||||
}
|
||||
|
||||
// 处理页内导航事件
|
||||
const handleDidNavigateInPage = (e: any) => {
|
||||
const canGoBackStatus = webview.canGoBack()
|
||||
const canGoForwardStatus = webview.canGoForward()
|
||||
|
||||
setCurrentUrl(e.url)
|
||||
setCanGoBack(canGoBackStatus)
|
||||
setCanGoForward(canGoForwardStatus)
|
||||
// 只更新当前活动标签页的UI状态
|
||||
if (tabId === activeTabId) {
|
||||
setCurrentUrl(e.url)
|
||||
setCanGoBack(canGoBackStatus)
|
||||
setCanGoForward(canGoForwardStatus)
|
||||
}
|
||||
|
||||
// 更新选项卡状态
|
||||
updateTabInfo(activeTabId, {
|
||||
updateTabInfo(tabId, {
|
||||
url: e.url,
|
||||
canGoBack: canGoBackStatus,
|
||||
canGoForward: canGoForwardStatus
|
||||
})
|
||||
}
|
||||
|
||||
// 处理页面标题变化
|
||||
// 处理页面标题更新事件
|
||||
const handlePageTitleUpdated = (e: any) => {
|
||||
// 更新选项卡标题
|
||||
updateTabInfo(activeTabId, { title: e.title })
|
||||
updateTabInfo(tabId, { title: e.title })
|
||||
}
|
||||
|
||||
// 处理网站图标更新
|
||||
// 处理网站图标更新事件
|
||||
const handlePageFaviconUpdated = (e: any) => {
|
||||
// 更新选项卡图标
|
||||
updateTabInfo(activeTabId, { favicon: e.favicons[0] })
|
||||
updateTabInfo(tabId, { favicon: e.favicons[0] })
|
||||
}
|
||||
|
||||
// 检测Cloudflare验证码
|
||||
// 处理DOM就绪事件
|
||||
const handleDomReady = () => {
|
||||
const captchaNotice = t('browser.captcha_notice')
|
||||
|
||||
// 注入链接点击拦截脚本
|
||||
webview.executeJavaScript(`
|
||||
(function() {
|
||||
// 已经注入过脚本,不再重复注入
|
||||
if (window.__linkInterceptorInjected) return;
|
||||
window.__linkInterceptorInjected = true;
|
||||
|
||||
// 创建一个全局函数,用于在控制台中调用以打开新标签页
|
||||
window.__openInNewTab = function(url, title) {
|
||||
console.log('OPEN_NEW_TAB:' + JSON.stringify({url: url, title: title || url}));
|
||||
};
|
||||
|
||||
// 拦截所有链接点击
|
||||
document.addEventListener('click', function(e) {
|
||||
// 查找被点击的链接元素
|
||||
let target = e.target;
|
||||
while (target && target.tagName !== 'A') {
|
||||
target = target.parentElement;
|
||||
if (!target) return; // 不是链接,直接返回
|
||||
}
|
||||
|
||||
// 找到了链接元素
|
||||
if (target.tagName === 'A' && target.href) {
|
||||
// 检查是否应该在新标签页中打开
|
||||
const inNewTab = e.ctrlKey || e.metaKey || target.target === '_blank';
|
||||
|
||||
// 阻止默认行为
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// 使用一个特殊的数据属性来标记这个链接
|
||||
const linkData = {
|
||||
url: target.href,
|
||||
title: target.textContent || target.title || target.href,
|
||||
inNewTab: inNewTab
|
||||
};
|
||||
|
||||
// 将数据转换为字符串并存储在自定义属性中
|
||||
document.body.setAttribute('data-last-clicked-link', JSON.stringify(linkData));
|
||||
|
||||
// 触发一个自定义事件
|
||||
const event = new CustomEvent('link-clicked', { detail: linkData });
|
||||
document.dispatchEvent(event);
|
||||
|
||||
// 使用控制台消息通知Electron
|
||||
console.log('LINK_CLICKED:' + JSON.stringify(linkData));
|
||||
|
||||
if (!inNewTab) {
|
||||
// 在当前标签页中打开链接
|
||||
window.location.href = target.href;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}, true);
|
||||
|
||||
// 打印一条消息,确认链接拦截脚本已经注入
|
||||
console.log('Link interceptor script injected successfully');
|
||||
|
||||
// 每5秒测试一次链接拦截功能
|
||||
setInterval(function() {
|
||||
console.log('Testing link interceptor...');
|
||||
|
||||
// 尝试调用全局函数
|
||||
if (window.__openInNewTab) {
|
||||
console.log('Link interceptor is working!');
|
||||
|
||||
// 创建一个测试链接
|
||||
const testLink = document.createElement('a');
|
||||
testLink.href = 'https://www.example.com';
|
||||
testLink.textContent = 'Test Link';
|
||||
testLink.target = '_blank'; // 在新标签页中打开
|
||||
|
||||
// 添加到DOM中
|
||||
if (!document.getElementById('test-link-container')) {
|
||||
const container = document.createElement('div');
|
||||
container.id = 'test-link-container';
|
||||
container.style.position = 'fixed';
|
||||
container.style.top = '10px';
|
||||
container.style.right = '10px';
|
||||
container.style.zIndex = '9999';
|
||||
container.style.background = 'white';
|
||||
container.style.padding = '10px';
|
||||
container.style.border = '1px solid black';
|
||||
container.appendChild(testLink);
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
// 模拟点击事件
|
||||
console.log('LINK_CLICKED:' + JSON.stringify({
|
||||
url: 'https://www.example.com',
|
||||
title: 'Test Link',
|
||||
inNewTab: true
|
||||
}));
|
||||
} else {
|
||||
console.log('Link interceptor is NOT working!');
|
||||
}
|
||||
}, 5000);
|
||||
})();
|
||||
`)
|
||||
|
||||
// 注入浏览器模拟脚本
|
||||
webview.executeJavaScript(`
|
||||
try {
|
||||
@ -431,6 +630,62 @@ const Browser = () => {
|
||||
webview.executeJavaScript(finalScript)
|
||||
}
|
||||
|
||||
// 处理新窗口打开请求
|
||||
const handleNewWindow = (e: any) => {
|
||||
e.preventDefault() // 阻止默认行为
|
||||
|
||||
// 始终在新标签页中打开
|
||||
openUrlInTab(e.url, true, e.frameName || 'New Tab')
|
||||
}
|
||||
|
||||
// 处理将要导航的事件
|
||||
const handleWillNavigate = (e: any) => {
|
||||
// 更新当前标签页的URL
|
||||
updateTabInfo(tabId, { url: e.url })
|
||||
}
|
||||
|
||||
// 处理控制台消息事件 - 用于链接点击拦截
|
||||
const handleConsoleMessage = (event: any) => {
|
||||
// 打印所有控制台消息,便于调试
|
||||
console.log(`[Tab ${tabId}] Console message:`, event.message)
|
||||
|
||||
// 处理新的链接点击消息
|
||||
if (event.message && event.message.startsWith('LINK_CLICKED:')) {
|
||||
try {
|
||||
const dataStr = event.message.replace('LINK_CLICKED:', '')
|
||||
const data = JSON.parse(dataStr)
|
||||
|
||||
console.log(`[Tab ${tabId}] Link clicked:`, data)
|
||||
|
||||
if (data.url && data.inNewTab) {
|
||||
// 在新标签页中打开链接
|
||||
console.log(`[Tab ${tabId}] Opening link in new tab:`, data.url)
|
||||
openUrlInTab(data.url, true, data.title || data.url)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to parse link data:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保留对旧消息格式的支持
|
||||
else if (event.message && event.message.startsWith('OPEN_NEW_TAB:')) {
|
||||
try {
|
||||
const dataStr = event.message.replace('OPEN_NEW_TAB:', '')
|
||||
const data = JSON.parse(dataStr)
|
||||
|
||||
console.log(`[Tab ${tabId}] Opening link in new tab (legacy format):`, data)
|
||||
|
||||
if (data.url) {
|
||||
// 在新标签页中打开链接
|
||||
openUrlInTab(data.url, true, data.title || data.url)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to parse link data:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加所有事件监听器
|
||||
webview.addEventListener('did-start-loading', handleDidStartLoading)
|
||||
webview.addEventListener('did-stop-loading', handleDidStopLoading)
|
||||
webview.addEventListener('did-navigate', handleDidNavigate)
|
||||
@ -438,11 +693,13 @@ const Browser = () => {
|
||||
webview.addEventListener('dom-ready', handleDomReady)
|
||||
webview.addEventListener('page-title-updated', handlePageTitleUpdated)
|
||||
webview.addEventListener('page-favicon-updated', handlePageFaviconUpdated)
|
||||
webview.addEventListener('new-window', handleNewWindow)
|
||||
webview.addEventListener('will-navigate', handleWillNavigate)
|
||||
webview.addEventListener('console-message', handleConsoleMessage)
|
||||
|
||||
// 初始加载URL
|
||||
webview.src = url
|
||||
|
||||
// 返回清理函数
|
||||
return () => {
|
||||
console.log('Cleaning up event listeners for tab:', tabId)
|
||||
webview.removeEventListener('did-start-loading', handleDidStartLoading)
|
||||
webview.removeEventListener('did-stop-loading', handleDidStopLoading)
|
||||
webview.removeEventListener('did-navigate', handleDidNavigate)
|
||||
@ -450,8 +707,73 @@ const Browser = () => {
|
||||
webview.removeEventListener('dom-ready', handleDomReady)
|
||||
webview.removeEventListener('page-title-updated', handlePageTitleUpdated)
|
||||
webview.removeEventListener('page-favicon-updated', handlePageFaviconUpdated)
|
||||
webview.removeEventListener('new-window', handleNewWindow)
|
||||
webview.removeEventListener('will-navigate', handleWillNavigate)
|
||||
webview.removeEventListener('console-message', handleConsoleMessage)
|
||||
}
|
||||
}, [url, t, activeTabId])
|
||||
}
|
||||
|
||||
// 通用的打开URL函数
|
||||
const openUrlInTab = (url: string, inNewTab: boolean = false, title: string = 'New Tab') => {
|
||||
if (inNewTab) {
|
||||
// 在新标签页中打开链接
|
||||
const newTabId = `tab-${Date.now()}`
|
||||
const newTab: Tab = {
|
||||
id: newTabId,
|
||||
title: title,
|
||||
url: url,
|
||||
isLoading: true,
|
||||
canGoBack: false,
|
||||
canGoForward: false
|
||||
}
|
||||
|
||||
// 创建新的选项卡数组,确保不修改原数组
|
||||
const newTabs = [...tabs, newTab]
|
||||
|
||||
// 更新状态
|
||||
setTabs(newTabs)
|
||||
setActiveTabId(newTabId)
|
||||
|
||||
// 保存到本地存储
|
||||
saveTabsToStorage(newTabs, newTabId)
|
||||
|
||||
console.log('Opened URL in new tab:', url, 'tab ID:', newTabId)
|
||||
} else {
|
||||
// 在当前标签页中打开链接
|
||||
setUrl(url)
|
||||
|
||||
// 更新当前选项卡的URL
|
||||
updateTabInfo(activeTabId, { url: url })
|
||||
}
|
||||
}
|
||||
|
||||
// 当activeTabId变化时,更新UI状态
|
||||
useEffect(() => {
|
||||
// 获取当前活动的webview
|
||||
const webview = webviewRefs.current[activeTabId]
|
||||
if (!webview) return
|
||||
|
||||
// 从webview获取最新状态
|
||||
try {
|
||||
const currentURL = webview.getURL()
|
||||
if (currentURL && currentURL !== 'about:blank') {
|
||||
setCurrentUrl(currentURL)
|
||||
} else {
|
||||
// 如果没有有效URL,使用存储的URL
|
||||
const tab = tabs.find((tab) => tab.id === activeTabId)
|
||||
if (tab) {
|
||||
setCurrentUrl(tab.url)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新导航状态
|
||||
setCanGoBack(webview.canGoBack())
|
||||
setCanGoForward(webview.canGoForward())
|
||||
setIsLoading(webview.isLoading())
|
||||
} catch (error) {
|
||||
console.error('Error updating UI state:', error)
|
||||
}
|
||||
}, [activeTabId, tabs])
|
||||
|
||||
const handleUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setCurrentUrl(e.target.value)
|
||||
@ -538,60 +860,192 @@ const Browser = () => {
|
||||
}
|
||||
|
||||
// 选项卡管理功能
|
||||
const handleAddTab = () => {
|
||||
const handleAddTab = (url: string = 'https://www.google.com', title: string = 'New Tab') => {
|
||||
const newTabId = `tab-${Date.now()}`
|
||||
const newTab: Tab = {
|
||||
id: newTabId,
|
||||
title: 'New Tab',
|
||||
url: 'https://www.google.com',
|
||||
title: title,
|
||||
url: url,
|
||||
isLoading: false,
|
||||
canGoBack: false,
|
||||
canGoForward: false
|
||||
}
|
||||
|
||||
setTabs([...tabs, newTab])
|
||||
const newTabs = [...tabs, newTab]
|
||||
setTabs(newTabs)
|
||||
setActiveTabId(newTabId)
|
||||
setUrl('https://www.google.com')
|
||||
setUrl(url)
|
||||
|
||||
// 保存到本地存储
|
||||
saveTabsToStorage(newTabs, newTabId)
|
||||
|
||||
return newTabId
|
||||
}
|
||||
|
||||
const handleCloseTab = (tabId: string, e: React.MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation() // 防止触发选项卡切换
|
||||
console.log('Closing tab:', tabId)
|
||||
|
||||
if (tabs.length === 1) {
|
||||
// 如果只有一个选项卡,创建一个新的空白选项卡
|
||||
handleAddTab()
|
||||
return // 已经在handleAddTab中保存了状态,这里直接返回
|
||||
}
|
||||
|
||||
// 如果关闭的是当前活动选项卡,切换到前一个选项卡
|
||||
// 计算新的活动选项卡ID
|
||||
let newActiveTabId = activeTabId
|
||||
if (tabId === activeTabId) {
|
||||
const currentIndex = tabs.findIndex((tab) => tab.id === tabId)
|
||||
const newActiveIndex = currentIndex === 0 ? 1 : currentIndex - 1
|
||||
setActiveTabId(tabs[newActiveIndex].id)
|
||||
newActiveTabId = tabs[newActiveIndex].id
|
||||
setActiveTabId(newActiveTabId)
|
||||
}
|
||||
|
||||
// 从选项卡列表中移除
|
||||
setTabs(tabs.filter((tab) => tab.id !== tabId))
|
||||
const newTabs = tabs.filter((tab) => tab.id !== tabId)
|
||||
setTabs(newTabs)
|
||||
|
||||
// 清理不再使用的webview引用和会话状态
|
||||
if (webviewRefs.current[tabId]) {
|
||||
// 停止加载并清理webview
|
||||
try {
|
||||
const webview = webviewRefs.current[tabId]
|
||||
if (webview) {
|
||||
// 停止加载
|
||||
webview.stop()
|
||||
|
||||
// 尝试获取webContentsId
|
||||
try {
|
||||
const webContentsId = webview.getWebContentsId()
|
||||
if (webContentsId && window.api && window.api.ipcRenderer) {
|
||||
// 通过IPC请求主进程销毁webContents
|
||||
window.api.ipcRenderer
|
||||
.invoke('browser:destroy-webcontents', webContentsId)
|
||||
.then(() => {
|
||||
console.log('Successfully requested destruction of webContents for tab:', tabId)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error requesting destruction of webContents:', error)
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error getting webContentsId:', e)
|
||||
}
|
||||
|
||||
// 加载空白页面,释放资源
|
||||
webview.src = 'about:blank'
|
||||
|
||||
// 使用保存的清理函数移除事件监听器
|
||||
if (cleanupFunctionsRef.current[tabId]) {
|
||||
console.log('Calling cleanup function for tab:', tabId)
|
||||
cleanupFunctionsRef.current[tabId]()
|
||||
delete cleanupFunctionsRef.current[tabId]
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error cleaning up webview:', error)
|
||||
}
|
||||
|
||||
// 删除引用
|
||||
delete webviewRefs.current[tabId]
|
||||
console.log('Removed webview reference for tab:', tabId)
|
||||
}
|
||||
|
||||
// 删除会话状态
|
||||
delete webviewSessionsRef.current[tabId]
|
||||
console.log('Removed session state for tab:', tabId)
|
||||
|
||||
// 保存到本地存储 - 确保不包含已关闭的选项卡
|
||||
saveTabsToStorage(newTabs, newActiveTabId)
|
||||
|
||||
console.log('Tab closed, remaining tabs:', newTabs.length)
|
||||
}
|
||||
|
||||
const handleTabChange = (newActiveTabId: string) => {
|
||||
console.log('Switching to tab:', newActiveTabId)
|
||||
|
||||
// 更新活动标签页ID
|
||||
setActiveTabId(newActiveTabId)
|
||||
|
||||
// 更新URL和其他状态
|
||||
const newActiveTab = tabs.find((tab) => tab.id === newActiveTabId)
|
||||
if (newActiveTab) {
|
||||
setUrl(newActiveTab.url)
|
||||
setCurrentUrl(newActiveTab.url)
|
||||
setCanGoBack(newActiveTab.canGoBack)
|
||||
setCanGoForward(newActiveTab.canGoForward)
|
||||
setIsLoading(newActiveTab.isLoading)
|
||||
// 获取新活动的webview
|
||||
const newWebview = webviewRefs.current[newActiveTabId]
|
||||
|
||||
// 如果webview存在,从webview获取最新状态
|
||||
if (newWebview) {
|
||||
try {
|
||||
// 获取当前URL
|
||||
const currentURL = newWebview.getURL()
|
||||
if (currentURL && currentURL !== 'about:blank') {
|
||||
// 使用webview的实际URL,而不是存储的URL
|
||||
setUrl(currentURL)
|
||||
setCurrentUrl(currentURL)
|
||||
|
||||
// 更新选项卡信息
|
||||
updateTabInfo(newActiveTabId, { url: currentURL })
|
||||
} else {
|
||||
// 如果没有有效URL,使用存储的URL
|
||||
setUrl(newActiveTab.url)
|
||||
setCurrentUrl(newActiveTab.url)
|
||||
}
|
||||
|
||||
// 更新导航状态
|
||||
setCanGoBack(newWebview.canGoBack())
|
||||
setCanGoForward(newWebview.canGoForward())
|
||||
setIsLoading(newWebview.isLoading())
|
||||
} catch (error) {
|
||||
console.error('Error getting webview state:', error)
|
||||
|
||||
// 出错时使用存储的状态
|
||||
setUrl(newActiveTab.url)
|
||||
setCurrentUrl(newActiveTab.url)
|
||||
setCanGoBack(newActiveTab.canGoBack)
|
||||
setCanGoForward(newActiveTab.canGoForward)
|
||||
setIsLoading(newActiveTab.isLoading)
|
||||
}
|
||||
} else {
|
||||
// 如果webview不存在,使用存储的状态
|
||||
setUrl(newActiveTab.url)
|
||||
setCurrentUrl(newActiveTab.url)
|
||||
setCanGoBack(newActiveTab.canGoBack)
|
||||
setCanGoForward(newActiveTab.canGoForward)
|
||||
setIsLoading(newActiveTab.isLoading)
|
||||
}
|
||||
|
||||
// 保存到本地存储
|
||||
saveTabsToStorage(tabs, newActiveTabId)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新选项卡信息
|
||||
const updateTabInfo = (tabId: string, updates: Partial<Tab>) => {
|
||||
setTabs((prevTabs) => prevTabs.map((tab) => (tab.id === tabId ? { ...tab, ...updates } : tab)))
|
||||
setTabs((prevTabs) => {
|
||||
const newTabs = prevTabs.map((tab) => (tab.id === tabId ? { ...tab, ...updates } : tab))
|
||||
|
||||
// 保存到本地存储
|
||||
saveTabsToStorage(newTabs, activeTabId)
|
||||
|
||||
return newTabs
|
||||
})
|
||||
}
|
||||
|
||||
// 在组件挂载和卸载时处理webview会话
|
||||
useEffect(() => {
|
||||
// 组件挂载时,确保webviewSessionsRef与tabs同步
|
||||
tabs.forEach((tab) => {
|
||||
if (!webviewSessionsRef.current[tab.id]) {
|
||||
webviewSessionsRef.current[tab.id] = false
|
||||
}
|
||||
})
|
||||
|
||||
// 组件卸载时保存状态
|
||||
return () => {
|
||||
saveTabsToStorage(tabs, activeTabId)
|
||||
}
|
||||
}, [tabs, activeTabId])
|
||||
|
||||
// 检测Google登录页面
|
||||
useEffect(() => {
|
||||
// 检测是否是Google登录页面
|
||||
@ -619,6 +1073,7 @@ const Browser = () => {
|
||||
} else {
|
||||
setShowGoogleLoginTip(false)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentUrl, activeTabId])
|
||||
|
||||
return (
|
||||
@ -667,7 +1122,7 @@ const Browser = () => {
|
||||
<Button
|
||||
className="add-tab-button"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleAddTab}
|
||||
onClick={() => handleAddTab()}
|
||||
title={t('browser.new_tab')}
|
||||
/>
|
||||
)
|
||||
@ -714,11 +1169,52 @@ const Browser = () => {
|
||||
<webview
|
||||
ref={(el: any) => {
|
||||
if (el) {
|
||||
webviewRefs.current[tab.id] = el as WebviewTag
|
||||
// 检查这个webview是否已经有引用
|
||||
const existingWebview = webviewRefs.current[tab.id]
|
||||
|
||||
// 如果是新创建的选项卡,加载初始URL
|
||||
if (!el.src) {
|
||||
// 只有在webview不存在或者是新创建的选项卡时才设置引用和加载URL
|
||||
if (!existingWebview) {
|
||||
console.log('Creating new webview for tab:', tab.id, 'URL:', tab.url)
|
||||
|
||||
// 保存webview引用
|
||||
webviewRefs.current[tab.id] = el as WebviewTag
|
||||
|
||||
// 标记为已初始化
|
||||
webviewSessionsRef.current[tab.id] = true
|
||||
|
||||
// 设置初始URL
|
||||
el.src = tab.url
|
||||
|
||||
// 设置事件监听器并保存清理函数
|
||||
const cleanup = setupWebviewListeners(el as WebviewTag, tab.id)
|
||||
cleanupFunctionsRef.current[tab.id] = cleanup
|
||||
} else if (existingWebview !== el) {
|
||||
// 如果引用变了(React重新创建了元素),保留原来的状态
|
||||
console.log('Webview reference changed for tab:', tab.id, 'preserving state')
|
||||
|
||||
// 先清理旧的事件监听器
|
||||
if (cleanupFunctionsRef.current[tab.id]) {
|
||||
cleanupFunctionsRef.current[tab.id]()
|
||||
}
|
||||
|
||||
// 更新webview引用
|
||||
webviewRefs.current[tab.id] = el as WebviewTag
|
||||
|
||||
// 不要重新设置src,这会导致页面刷新
|
||||
// 只有在URL明确改变时才设置src
|
||||
if (existingWebview.getURL() !== tab.url && tab.url !== '') {
|
||||
el.src = tab.url
|
||||
}
|
||||
|
||||
// 重新设置事件监听器
|
||||
const cleanup = setupWebviewListeners(el as WebviewTag, tab.id)
|
||||
cleanupFunctionsRef.current[tab.id] = cleanup
|
||||
}
|
||||
} else {
|
||||
// DOM元素被移除,清理事件监听器
|
||||
if (cleanupFunctionsRef.current[tab.id]) {
|
||||
cleanupFunctionsRef.current[tab.id]()
|
||||
delete cleanupFunctionsRef.current[tab.id]
|
||||
}
|
||||
}
|
||||
}}
|
||||
@ -726,7 +1222,7 @@ const Browser = () => {
|
||||
partition="persist:browser"
|
||||
useragent={userAgent}
|
||||
preload=""
|
||||
webpreferences="contextIsolation=no, javascript=yes, webgl=yes, webaudio=yes, allowRunningInsecureContent=yes"
|
||||
webpreferences="contextIsolation=no, javascript=yes, webgl=yes, webaudio=yes, allowRunningInsecureContent=yes, nodeIntegration=yes, enableRemoteModule=yes"
|
||||
disablewebsecurity={DISABLE_SECURITY}
|
||||
plugins={true}
|
||||
/>
|
||||
|
||||
8
temp.txt
8
temp.txt
@ -1,8 +0,0 @@
|
||||
// 不再自动清除回调函数,允许持续接收语音识别结果
|
||||
// setTimeout(() => {
|
||||
// // 发送重置命令,确保浏览器不会继续发送结果
|
||||
// ASRService.cancelRecording()
|
||||
//
|
||||
// // 清除ASRService中的回调函数,防止后续结果被处理
|
||||
// ASRService.resultCallback = null
|
||||
// }, 2000) // 2秒后强制取消,作为安全措施
|
||||
39
yarn.lock
39
yarn.lock
@ -6166,12 +6166,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:^20.9.0":
|
||||
version: 20.17.24
|
||||
resolution: "@types/node@npm:20.17.24"
|
||||
"@types/node@npm:^22.7.7":
|
||||
version: 22.15.2
|
||||
resolution: "@types/node@npm:22.15.2"
|
||||
dependencies:
|
||||
undici-types: "npm:~6.19.2"
|
||||
checksum: 10c0/2a39ce4c4cd4588a05b2a485cc0a1407cbea608dd1ab03e36add59d61712718d95c84b492ca5190753f0be2bce748aeeb0f2a1412e712775462befe3820b3ff9
|
||||
undici-types: "npm:~6.21.0"
|
||||
checksum: 10c0/39da31d5fc63b14fabd217bb8a921c4a7fc3d99f233440209f9fc2d5d736e8773f7efc65223e2fd0e8db8390b0baab9c0cd2e951c2ece8b237f07313ab3cf295
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -6847,7 +6847,7 @@ __metadata:
|
||||
docx: "npm:^9.0.2"
|
||||
dotenv-cli: "npm:^7.4.2"
|
||||
edge-tts-node: "npm:^1.5.7"
|
||||
electron: "npm:31.7.6"
|
||||
electron: "npm:35.2.0"
|
||||
electron-builder: "npm:26.0.13"
|
||||
electron-chrome-extensions: "npm:^4.7.0"
|
||||
electron-devtools-installer: "npm:^3.2.0"
|
||||
@ -6941,6 +6941,7 @@ __metadata:
|
||||
vite: "npm:^5.0.12"
|
||||
vitest: "npm:^3.1.1"
|
||||
webdav: "npm:^5.8.0"
|
||||
zipfile: "npm:^0.5.12"
|
||||
zipread: "npm:^1.3.3"
|
||||
zod: "npm:^3.24.2"
|
||||
languageName: unknown
|
||||
@ -10595,16 +10596,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"electron@npm:31.7.6":
|
||||
version: 31.7.6
|
||||
resolution: "electron@npm:31.7.6"
|
||||
"electron@npm:35.2.0":
|
||||
version: 35.2.0
|
||||
resolution: "electron@npm:35.2.0"
|
||||
dependencies:
|
||||
"@electron/get": "npm:^2.0.0"
|
||||
"@types/node": "npm:^20.9.0"
|
||||
"@types/node": "npm:^22.7.7"
|
||||
extract-zip: "npm:^2.0.1"
|
||||
bin:
|
||||
electron: cli.js
|
||||
checksum: 10c0/4b7ee31894eb3606d6a6047cd7af22d3b82331dacb96869c483bfd32ffc8581ef638ccfa027938d83d5242e7bf8b7856cad29a09fb80942a25ef3de0c888fb48
|
||||
checksum: 10c0/180c00ec8418ef79889c41b690fc206c51c12267420a85bee5ff7b0ca279a988eb63c66c979b6d0e241c2a821d9d2980cd8d0104063feba58a745333e0997e7f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -22947,13 +22948,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici-types@npm:~6.19.2":
|
||||
version: 6.19.8
|
||||
resolution: "undici-types@npm:6.19.8"
|
||||
checksum: 10c0/078afa5990fba110f6824823ace86073b4638f1d5112ee26e790155f481f2a868cc3e0615505b6f4282bdf74a3d8caad715fd809e870c2bb0704e3ea6082f344
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici-types@npm:~6.20.0":
|
||||
version: 6.20.0
|
||||
resolution: "undici-types@npm:6.20.0"
|
||||
@ -22961,6 +22955,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici-types@npm:~6.21.0":
|
||||
version: 6.21.0
|
||||
resolution: "undici-types@npm:6.21.0"
|
||||
checksum: 10c0/c01ed51829b10aa72fc3ce64b747f8e74ae9b60eafa19a7b46ef624403508a54c526ffab06a14a26b3120d055e1104d7abe7c9017e83ced038ea5cf52f8d5e04
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici@npm:>=6, undici@npm:^7.4.0":
|
||||
version: 7.5.0
|
||||
resolution: "undici@npm:7.5.0"
|
||||
@ -24164,7 +24165,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zipfile@npm:^0.5.11":
|
||||
"zipfile@npm:^0.5.11, zipfile@npm:^0.5.12":
|
||||
version: 0.5.12
|
||||
resolution: "zipfile@npm:0.5.12"
|
||||
dependencies:
|
||||
|
||||
@ -1 +0,0 @@
|
||||
我爱世界
|
||||
Loading…
Reference in New Issue
Block a user