pake-cli 3.3.7-beta โ 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/cli.js +146 -29
- package/package.json +7 -7
- package/src-tauri/.pake/pake.json +4 -1
- package/src-tauri/Cargo.lock +18 -7
- package/src-tauri/Cargo.toml +5 -5
- package/src-tauri/pake.json +5 -2
- package/src-tauri/src/app/config.rs +4 -0
- package/src-tauri/src/app/setup.rs +26 -3
- package/src-tauri/src/app/window.rs +12 -9
- package/src-tauri/src/inject/event.js +53 -56
- package/src-tauri/src/lib.rs +21 -10
- package/src-tauri/.cargo/config.toml +0 -10
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
- **Beginners**: Download ready-made [Popular Packages](#popular-packages) or use [Online Building](docs/github-actions-usage.md) with no environment setup required
|
|
30
30
|
- **Developers**: Install [CLI Tool](docs/cli-usage.md) for one-command packaging of any website with customizable icons, window settings, and more
|
|
31
31
|
- **Advanced Users**: Clone the project locally for [Custom Development](#development), or check [Advanced Usage](docs/advanced-usage.md) for style customization and feature enhancement
|
|
32
|
+
- **Troubleshooting**: Check [FAQ](docs/faq.md) for common issues and solutions
|
|
32
33
|
|
|
33
34
|
## Popular Packages
|
|
34
35
|
|
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import path from 'path';
|
|
|
6
6
|
import fsExtra from 'fs-extra';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import prompts from 'prompts';
|
|
9
|
+
import os from 'os';
|
|
9
10
|
import { execa, execaSync } from 'execa';
|
|
10
11
|
import crypto from 'crypto';
|
|
11
12
|
import ora from 'ora';
|
|
@@ -22,7 +23,7 @@ import sharp from 'sharp';
|
|
|
22
23
|
import * as psl from 'psl';
|
|
23
24
|
|
|
24
25
|
var name = "pake-cli";
|
|
25
|
-
var version = "3.
|
|
26
|
+
var version = "3.4.1";
|
|
26
27
|
var description = "๐คฑ๐ป Turn any webpage into a desktop app with one command. ๐คฑ๐ป ไธ้ฎๆๅ
็ฝ้กต็ๆ่ฝป้ๆก้ขๅบ็จใ";
|
|
27
28
|
var engines = {
|
|
28
29
|
node: ">=18.0.0"
|
|
@@ -74,12 +75,12 @@ var license = "MIT";
|
|
|
74
75
|
var dependencies = {
|
|
75
76
|
"@tauri-apps/api": "^2.8.0",
|
|
76
77
|
"@tauri-apps/cli": "^2.8.4",
|
|
77
|
-
axios: "^1.
|
|
78
|
-
chalk: "^5.6.
|
|
78
|
+
axios: "^1.12.2",
|
|
79
|
+
chalk: "^5.6.2",
|
|
79
80
|
commander: "^12.1.0",
|
|
80
81
|
execa: "^9.6.0",
|
|
81
82
|
"file-type": "^18.7.0",
|
|
82
|
-
"fs-extra": "^11.3.
|
|
83
|
+
"fs-extra": "^11.3.2",
|
|
83
84
|
"icon-gen": "^5.0.0",
|
|
84
85
|
loglevel: "^1.9.2",
|
|
85
86
|
ora: "^8.2.0",
|
|
@@ -96,7 +97,7 @@ var devDependencies = {
|
|
|
96
97
|
"@rollup/plugin-replace": "^6.0.2",
|
|
97
98
|
"@rollup/plugin-terser": "^0.4.4",
|
|
98
99
|
"@types/fs-extra": "^11.0.4",
|
|
99
|
-
"@types/node": "^20.19.
|
|
100
|
+
"@types/node": "^20.19.21",
|
|
100
101
|
"@types/page-icon": "^0.3.6",
|
|
101
102
|
"@types/prompts": "^2.4.9",
|
|
102
103
|
"@types/tmp": "^0.2.6",
|
|
@@ -104,10 +105,10 @@ var devDependencies = {
|
|
|
104
105
|
"app-root-path": "^3.1.0",
|
|
105
106
|
"cross-env": "^7.0.3",
|
|
106
107
|
prettier: "^3.6.2",
|
|
107
|
-
rollup: "^4.
|
|
108
|
+
rollup: "^4.52.4",
|
|
108
109
|
"rollup-plugin-typescript2": "^0.36.0",
|
|
109
110
|
tslib: "^2.8.1",
|
|
110
|
-
typescript: "^5.9.
|
|
111
|
+
typescript: "^5.9.3"
|
|
111
112
|
};
|
|
112
113
|
var packageJson = {
|
|
113
114
|
name: name,
|
|
@@ -204,7 +205,9 @@ async function shellExec(command, timeout = 300000, env) {
|
|
|
204
205
|
try {
|
|
205
206
|
const { exitCode } = await execa(command, {
|
|
206
207
|
cwd: npmDirectory,
|
|
207
|
-
|
|
208
|
+
// Use 'inherit' to show all output directly to user in real-time.
|
|
209
|
+
// This ensures linuxdeploy and other tool outputs are visible during builds.
|
|
210
|
+
stdio: 'inherit',
|
|
208
211
|
shell: true,
|
|
209
212
|
timeout,
|
|
210
213
|
env: env ? { ...process.env, ...env } : process.env,
|
|
@@ -217,7 +220,28 @@ async function shellExec(command, timeout = 300000, env) {
|
|
|
217
220
|
if (error.timedOut) {
|
|
218
221
|
throw new Error(`Command timed out after ${timeout}ms: "${command}". Try increasing timeout or check network connectivity.`);
|
|
219
222
|
}
|
|
220
|
-
|
|
223
|
+
let errorMsg = `Error occurred while executing command "${command}". Exit code: ${exitCode}. Details: ${errorMessage}`;
|
|
224
|
+
// Provide helpful guidance for common Linux AppImage build failures
|
|
225
|
+
// caused by strip tool incompatibility with modern glibc (2.38+)
|
|
226
|
+
if (process.platform === 'linux' &&
|
|
227
|
+
(errorMessage.includes('linuxdeploy') ||
|
|
228
|
+
errorMessage.includes('appimage') ||
|
|
229
|
+
errorMessage.includes('strip'))) {
|
|
230
|
+
errorMsg +=
|
|
231
|
+
'\n\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n' +
|
|
232
|
+
'Linux AppImage Build Failed\n' +
|
|
233
|
+
'โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\n' +
|
|
234
|
+
'Cause: Strip tool incompatibility with glibc 2.38+\n' +
|
|
235
|
+
' (affects Debian Trixie, Arch Linux, and other modern distros)\n\n' +
|
|
236
|
+
'Quick fix:\n' +
|
|
237
|
+
' NO_STRIP=1 pake <url> --targets appimage --debug\n\n' +
|
|
238
|
+
'Alternatives:\n' +
|
|
239
|
+
' โข Use DEB format: pake <url> --targets deb\n' +
|
|
240
|
+
' โข Update binutils: sudo apt install binutils (or pacman -S binutils)\n' +
|
|
241
|
+
' โข Detailed guide: https://github.com/tw93/Pake/blob/main/docs/faq.md\n' +
|
|
242
|
+
'โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ';
|
|
243
|
+
}
|
|
244
|
+
throw new Error(errorMsg);
|
|
221
245
|
}
|
|
222
246
|
}
|
|
223
247
|
|
|
@@ -284,6 +308,52 @@ async function isChinaIP(ip, domain) {
|
|
|
284
308
|
}
|
|
285
309
|
}
|
|
286
310
|
|
|
311
|
+
function normalizePathForComparison(targetPath) {
|
|
312
|
+
const normalized = path.normalize(targetPath);
|
|
313
|
+
return IS_WIN ? normalized.toLowerCase() : normalized;
|
|
314
|
+
}
|
|
315
|
+
function getCargoHomeCandidates() {
|
|
316
|
+
const candidates = new Set();
|
|
317
|
+
if (process.env.CARGO_HOME) {
|
|
318
|
+
candidates.add(process.env.CARGO_HOME);
|
|
319
|
+
}
|
|
320
|
+
const homeDir = os.homedir();
|
|
321
|
+
if (homeDir) {
|
|
322
|
+
candidates.add(path.join(homeDir, '.cargo'));
|
|
323
|
+
}
|
|
324
|
+
if (IS_WIN && process.env.USERPROFILE) {
|
|
325
|
+
candidates.add(path.join(process.env.USERPROFILE, '.cargo'));
|
|
326
|
+
}
|
|
327
|
+
return Array.from(candidates).filter(Boolean);
|
|
328
|
+
}
|
|
329
|
+
function ensureCargoBinOnPath() {
|
|
330
|
+
const currentPath = process.env.PATH || '';
|
|
331
|
+
const segments = currentPath.split(path.delimiter).filter(Boolean);
|
|
332
|
+
const normalizedSegments = new Set(segments.map((segment) => normalizePathForComparison(segment)));
|
|
333
|
+
const additions = [];
|
|
334
|
+
let cargoHomeSet = Boolean(process.env.CARGO_HOME);
|
|
335
|
+
for (const cargoHome of getCargoHomeCandidates()) {
|
|
336
|
+
const binDir = path.join(cargoHome, 'bin');
|
|
337
|
+
if (fsExtra.pathExistsSync(binDir) &&
|
|
338
|
+
!normalizedSegments.has(normalizePathForComparison(binDir))) {
|
|
339
|
+
additions.push(binDir);
|
|
340
|
+
normalizedSegments.add(normalizePathForComparison(binDir));
|
|
341
|
+
}
|
|
342
|
+
if (!cargoHomeSet && fsExtra.pathExistsSync(cargoHome)) {
|
|
343
|
+
process.env.CARGO_HOME = cargoHome;
|
|
344
|
+
cargoHomeSet = true;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (additions.length) {
|
|
348
|
+
const prefix = additions.join(path.delimiter);
|
|
349
|
+
process.env.PATH = segments.length
|
|
350
|
+
? `${prefix}${path.delimiter}${segments.join(path.delimiter)}`
|
|
351
|
+
: prefix;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function ensureRustEnv() {
|
|
355
|
+
ensureCargoBinOnPath();
|
|
356
|
+
}
|
|
287
357
|
async function installRust() {
|
|
288
358
|
const isActions = process.env.GITHUB_ACTIONS;
|
|
289
359
|
const isInChina = await isChinaDomain('sh.rustup.rs');
|
|
@@ -293,8 +363,9 @@ async function installRust() {
|
|
|
293
363
|
const rustInstallScriptForWindows = 'winget install --id Rustlang.Rustup';
|
|
294
364
|
const spinner = getSpinner('Downloading Rust...');
|
|
295
365
|
try {
|
|
296
|
-
await shellExec(IS_WIN ? rustInstallScriptForWindows : rustInstallScriptForMac);
|
|
366
|
+
await shellExec(IS_WIN ? rustInstallScriptForWindows : rustInstallScriptForMac, 300000, undefined);
|
|
297
367
|
spinner.succeed(chalk.green('โ Rust installed successfully!'));
|
|
368
|
+
ensureRustEnv();
|
|
298
369
|
}
|
|
299
370
|
catch (error) {
|
|
300
371
|
spinner.fail(chalk.red('โ Rust installation failed!'));
|
|
@@ -303,6 +374,7 @@ async function installRust() {
|
|
|
303
374
|
}
|
|
304
375
|
}
|
|
305
376
|
function checkRustInstalled() {
|
|
377
|
+
ensureCargoBinOnPath();
|
|
306
378
|
try {
|
|
307
379
|
execaSync('rustc', ['--version']);
|
|
308
380
|
return true;
|
|
@@ -343,14 +415,14 @@ function generateLinuxPackageName(name) {
|
|
|
343
415
|
.replace(/-+/g, '-');
|
|
344
416
|
}
|
|
345
417
|
function generateIdentifierSafeName(name) {
|
|
346
|
-
const cleaned = name
|
|
347
|
-
.replace(/[^a-zA-Z0-9\u4e00-\u9fff]/g, '')
|
|
348
|
-
.toLowerCase();
|
|
418
|
+
const cleaned = name.replace(/[^a-zA-Z0-9\u4e00-\u9fff]/g, '').toLowerCase();
|
|
349
419
|
if (cleaned === '') {
|
|
350
420
|
const fallback = Array.from(name)
|
|
351
|
-
.map(char => {
|
|
421
|
+
.map((char) => {
|
|
352
422
|
const code = char.charCodeAt(0);
|
|
353
|
-
if ((code >= 48 && code <= 57) ||
|
|
423
|
+
if ((code >= 48 && code <= 57) ||
|
|
424
|
+
(code >= 65 && code <= 90) ||
|
|
425
|
+
(code >= 97 && code <= 122)) {
|
|
354
426
|
return char.toLowerCase();
|
|
355
427
|
}
|
|
356
428
|
return code.toString(16);
|
|
@@ -362,6 +434,12 @@ function generateIdentifierSafeName(name) {
|
|
|
362
434
|
return cleaned;
|
|
363
435
|
}
|
|
364
436
|
|
|
437
|
+
/**
|
|
438
|
+
* Helper function to generate safe lowercase app name for file paths
|
|
439
|
+
*/
|
|
440
|
+
function getSafeAppName(name) {
|
|
441
|
+
return generateSafeFilename(name).toLowerCase();
|
|
442
|
+
}
|
|
365
443
|
async function mergeConfig(url, options, tauriConf) {
|
|
366
444
|
// Ensure .pake directory exists and copy source templates if needed
|
|
367
445
|
const srcTauriDir = path.join(npmDirectory, 'src-tauri');
|
|
@@ -382,13 +460,14 @@ async function mergeConfig(url, options, tauriConf) {
|
|
|
382
460
|
await fsExtra.copy(sourcePath, destPath);
|
|
383
461
|
}
|
|
384
462
|
}));
|
|
385
|
-
const { width, height, fullscreen, hideTitleBar, alwaysOnTop, appVersion, darkMode, disabledWebShortcuts, activationShortcut, userAgent, showSystemTray, systemTrayIcon, useLocalFile, identifier, name, resizable = true, inject, proxyUrl, installerLanguage, hideOnClose, incognito, title, wasm, enableDragDrop, } = options;
|
|
463
|
+
const { width, height, fullscreen, maximize, hideTitleBar, alwaysOnTop, appVersion, darkMode, disabledWebShortcuts, activationShortcut, userAgent, showSystemTray, systemTrayIcon, useLocalFile, identifier, name, resizable = true, inject, proxyUrl, installerLanguage, hideOnClose, incognito, title, wasm, enableDragDrop, multiInstance, startToTray, } = options;
|
|
386
464
|
const { platform } = process;
|
|
387
465
|
const platformHideOnClose = hideOnClose ?? platform === 'darwin';
|
|
388
466
|
const tauriConfWindowOptions = {
|
|
389
467
|
width,
|
|
390
468
|
height,
|
|
391
469
|
fullscreen,
|
|
470
|
+
maximize,
|
|
392
471
|
resizable,
|
|
393
472
|
hide_title_bar: hideTitleBar,
|
|
394
473
|
activation_shortcut: activationShortcut,
|
|
@@ -400,6 +479,7 @@ async function mergeConfig(url, options, tauriConf) {
|
|
|
400
479
|
title: title || null,
|
|
401
480
|
enable_wasm: wasm,
|
|
402
481
|
enable_drag_drop: enableDragDrop,
|
|
482
|
+
start_to_tray: startToTray && showSystemTray,
|
|
403
483
|
};
|
|
404
484
|
Object.assign(tauriConf.pake.windows[0], { url, ...tauriConfWindowOptions });
|
|
405
485
|
tauriConf.productName = name;
|
|
@@ -452,7 +532,7 @@ async function mergeConfig(url, options, tauriConf) {
|
|
|
452
532
|
// Remove hardcoded desktop files and regenerate with correct app name
|
|
453
533
|
delete tauriConf.bundle.linux.deb.files;
|
|
454
534
|
// Generate correct desktop file configuration
|
|
455
|
-
const appNameSafe =
|
|
535
|
+
const appNameSafe = getSafeAppName(name);
|
|
456
536
|
const identifier = `com.pake.${appNameSafe}`;
|
|
457
537
|
const desktopFileName = `${identifier}.desktop`;
|
|
458
538
|
// Create desktop file content
|
|
@@ -503,22 +583,23 @@ StartupNotify=true
|
|
|
503
583
|
}
|
|
504
584
|
}
|
|
505
585
|
// Set icon.
|
|
586
|
+
const safeAppName = getSafeAppName(name);
|
|
506
587
|
const platformIconMap = {
|
|
507
588
|
win32: {
|
|
508
589
|
fileExt: '.ico',
|
|
509
|
-
path: `png/${
|
|
590
|
+
path: `png/${safeAppName}_256.ico`,
|
|
510
591
|
defaultIcon: 'png/icon_256.ico',
|
|
511
592
|
message: 'Windows icon must be .ico and 256x256px.',
|
|
512
593
|
},
|
|
513
594
|
linux: {
|
|
514
595
|
fileExt: '.png',
|
|
515
|
-
path: `png/${
|
|
596
|
+
path: `png/${safeAppName}_512.png`,
|
|
516
597
|
defaultIcon: 'png/icon_512.png',
|
|
517
598
|
message: 'Linux icon must be .png and 512x512px.',
|
|
518
599
|
},
|
|
519
600
|
darwin: {
|
|
520
601
|
fileExt: '.icns',
|
|
521
|
-
path: `icons/${
|
|
602
|
+
path: `icons/${safeAppName}.icns`,
|
|
522
603
|
defaultIcon: 'icons/icon.icns',
|
|
523
604
|
message: 'macOS icon must be .icns type.',
|
|
524
605
|
},
|
|
@@ -562,8 +643,8 @@ StartupNotify=true
|
|
|
562
643
|
// ้่ฆๅคๆญๅพๆ ๆ ผๅผ๏ผ้ป่ฎคๅชๆฏๆicoๅpngไธค็ง
|
|
563
644
|
let iconExt = path.extname(systemTrayIcon).toLowerCase();
|
|
564
645
|
if (iconExt == '.png' || iconExt == '.ico') {
|
|
565
|
-
const trayIcoPath = path.join(npmDirectory, `src-tauri/png/${
|
|
566
|
-
trayIconPath = `png/${
|
|
646
|
+
const trayIcoPath = path.join(npmDirectory, `src-tauri/png/${safeAppName}${iconExt}`);
|
|
647
|
+
trayIconPath = `png/${safeAppName}${iconExt}`;
|
|
567
648
|
await fsExtra.copy(systemTrayIcon, trayIcoPath);
|
|
568
649
|
}
|
|
569
650
|
else {
|
|
@@ -597,6 +678,7 @@ StartupNotify=true
|
|
|
597
678
|
await fsExtra.writeFile(injectFilePath, '');
|
|
598
679
|
}
|
|
599
680
|
tauriConf.pake.proxy_url = proxyUrl || '';
|
|
681
|
+
tauriConf.pake.multi_instance = multiInstance;
|
|
600
682
|
// Configure WASM support with required HTTP headers
|
|
601
683
|
if (wasm) {
|
|
602
684
|
tauriConf.app.security = {
|
|
@@ -673,6 +755,7 @@ class BaseBuilder {
|
|
|
673
755
|
logger.warn('โผ The first use requires installing system dependencies.');
|
|
674
756
|
logger.warn('โผ See more in https://tauri.app/start/prerequisites/.');
|
|
675
757
|
}
|
|
758
|
+
ensureRustEnv();
|
|
676
759
|
if (!checkRustInstalled()) {
|
|
677
760
|
const res = await prompts({
|
|
678
761
|
type: 'confirm',
|
|
@@ -705,10 +788,10 @@ class BaseBuilder {
|
|
|
705
788
|
logger.info(`โบ Located in China, using ${packageManager}/rsProxy CN mirror.`);
|
|
706
789
|
const projectCnConf = path.join(tauriSrcPath, 'rust_proxy.toml');
|
|
707
790
|
await fsExtra.copy(projectCnConf, projectConf);
|
|
708
|
-
await shellExec(`cd "${npmDirectory}" && ${packageManager} install${registryOption}${peerDepsOption}
|
|
791
|
+
await shellExec(`cd "${npmDirectory}" && ${packageManager} install${registryOption}${peerDepsOption}`, timeout, buildEnv);
|
|
709
792
|
}
|
|
710
793
|
else {
|
|
711
|
-
await shellExec(`cd "${npmDirectory}" && ${packageManager} install${peerDepsOption}
|
|
794
|
+
await shellExec(`cd "${npmDirectory}" && ${packageManager} install${peerDepsOption}`, timeout, buildEnv);
|
|
712
795
|
}
|
|
713
796
|
spinner.succeed(chalk.green('Package installed!'));
|
|
714
797
|
if (!tauriTargetPathExists) {
|
|
@@ -733,7 +816,19 @@ class BaseBuilder {
|
|
|
733
816
|
buildSpinner.stop();
|
|
734
817
|
// Show static message to keep the status visible
|
|
735
818
|
logger.warn('โธ Building app...');
|
|
736
|
-
const buildEnv =
|
|
819
|
+
const buildEnv = {
|
|
820
|
+
...this.getBuildEnvironment(),
|
|
821
|
+
...(process.env.NO_STRIP && { NO_STRIP: process.env.NO_STRIP }),
|
|
822
|
+
};
|
|
823
|
+
// Warn users about potential AppImage build failures on modern Linux systems.
|
|
824
|
+
// The linuxdeploy tool bundled in Tauri uses an older strip tool that doesn't
|
|
825
|
+
// recognize the .relr.dyn section introduced in glibc 2.38+.
|
|
826
|
+
if (process.platform === 'linux' && this.options.targets === 'appimage') {
|
|
827
|
+
if (!buildEnv.NO_STRIP) {
|
|
828
|
+
logger.warn('โ Building AppImage on Linux may fail due to strip incompatibility with glibc 2.38+');
|
|
829
|
+
logger.warn('โ If build fails, retry with: NO_STRIP=1 pake <url> --targets appimage');
|
|
830
|
+
}
|
|
831
|
+
}
|
|
737
832
|
await shellExec(`cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`, this.getBuildTimeout(), buildEnv);
|
|
738
833
|
// Copy app
|
|
739
834
|
const fileName = this.getFileName();
|
|
@@ -793,6 +888,11 @@ class BaseBuilder {
|
|
|
793
888
|
if (target) {
|
|
794
889
|
fullCommand += ` --target ${target}`;
|
|
795
890
|
}
|
|
891
|
+
// Enable verbose output in debug mode to help diagnose build issues.
|
|
892
|
+
// This provides detailed logs from Tauri CLI and bundler tools.
|
|
893
|
+
if (this.options.debug) {
|
|
894
|
+
fullCommand += ' --verbose';
|
|
895
|
+
}
|
|
796
896
|
return fullCommand;
|
|
797
897
|
}
|
|
798
898
|
/**
|
|
@@ -1106,6 +1206,12 @@ class LinuxBuilder extends BaseBuilder {
|
|
|
1106
1206
|
if (features.length > 0) {
|
|
1107
1207
|
fullCommand += ` --features ${features.join(',')}`;
|
|
1108
1208
|
}
|
|
1209
|
+
// Enable verbose output for AppImage builds when debugging or PAKE_VERBOSE is set.
|
|
1210
|
+
// AppImage builds often fail with minimal error messages from linuxdeploy,
|
|
1211
|
+
// so verbose mode helps diagnose issues like strip failures and missing dependencies.
|
|
1212
|
+
if (this.options.targets === 'appimage' && (this.options.debug || process.env.PAKE_VERBOSE)) {
|
|
1213
|
+
fullCommand += ' --verbose';
|
|
1214
|
+
}
|
|
1109
1215
|
return fullCommand;
|
|
1110
1216
|
}
|
|
1111
1217
|
getBasePath() {
|
|
@@ -1155,6 +1261,7 @@ const DEFAULT_PAKE_OPTIONS = {
|
|
|
1155
1261
|
height: 780,
|
|
1156
1262
|
width: 1200,
|
|
1157
1263
|
fullscreen: false,
|
|
1264
|
+
maximize: false,
|
|
1158
1265
|
hideTitleBar: false,
|
|
1159
1266
|
alwaysOnTop: false,
|
|
1160
1267
|
appVersion: '1.0.0',
|
|
@@ -1176,6 +1283,8 @@ const DEFAULT_PAKE_OPTIONS = {
|
|
|
1176
1283
|
wasm: false,
|
|
1177
1284
|
enableDragDrop: false,
|
|
1178
1285
|
keepBinary: false,
|
|
1286
|
+
multiInstance: false,
|
|
1287
|
+
startToTray: false,
|
|
1179
1288
|
};
|
|
1180
1289
|
|
|
1181
1290
|
async function checkUpdateTips() {
|
|
@@ -1619,8 +1728,7 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with
|
|
|
1619
1728
|
program
|
|
1620
1729
|
.addHelpText('beforeAll', logo)
|
|
1621
1730
|
.usage(`[url] [options]`)
|
|
1622
|
-
.showHelpAfterError()
|
|
1623
|
-
.helpOption(false);
|
|
1731
|
+
.showHelpAfterError();
|
|
1624
1732
|
program
|
|
1625
1733
|
.argument('[url]', 'The web URL you want to package', validateUrlInput)
|
|
1626
1734
|
// Refer to https://github.com/tj/commander.js#custom-option-processing, turn string array into a string connected with custom connectors.
|
|
@@ -1659,6 +1767,9 @@ program
|
|
|
1659
1767
|
.addOption(new Option('--always-on-top', 'Always on the top level')
|
|
1660
1768
|
.default(DEFAULT_PAKE_OPTIONS.alwaysOnTop)
|
|
1661
1769
|
.hideHelp())
|
|
1770
|
+
.addOption(new Option('--maximize', 'Start window maximized')
|
|
1771
|
+
.default(DEFAULT_PAKE_OPTIONS.maximize)
|
|
1772
|
+
.hideHelp())
|
|
1662
1773
|
.addOption(new Option('--dark-mode', 'Force Mac app to use dark mode')
|
|
1663
1774
|
.default(DEFAULT_PAKE_OPTIONS.darkMode)
|
|
1664
1775
|
.hideHelp())
|
|
@@ -1699,6 +1810,12 @@ program
|
|
|
1699
1810
|
.addOption(new Option('--keep-binary', 'Keep raw binary file alongside installer')
|
|
1700
1811
|
.default(DEFAULT_PAKE_OPTIONS.keepBinary)
|
|
1701
1812
|
.hideHelp())
|
|
1813
|
+
.addOption(new Option('--multi-instance', 'Allow multiple app instances')
|
|
1814
|
+
.default(DEFAULT_PAKE_OPTIONS.multiInstance)
|
|
1815
|
+
.hideHelp())
|
|
1816
|
+
.addOption(new Option('--start-to-tray', 'Start app minimized to tray')
|
|
1817
|
+
.default(DEFAULT_PAKE_OPTIONS.startToTray)
|
|
1818
|
+
.hideHelp())
|
|
1702
1819
|
.addOption(new Option('--installer-language <string>', 'Installer language')
|
|
1703
1820
|
.default(DEFAULT_PAKE_OPTIONS.installerLanguage)
|
|
1704
1821
|
.hideHelp())
|
|
@@ -1706,12 +1823,12 @@ program
|
|
|
1706
1823
|
.configureHelp({
|
|
1707
1824
|
sortSubcommands: true,
|
|
1708
1825
|
optionTerm: (option) => {
|
|
1709
|
-
if (option.flags === '-v, --version')
|
|
1826
|
+
if (option.flags === '-v, --version' || option.flags === '-h, --help')
|
|
1710
1827
|
return '';
|
|
1711
1828
|
return option.flags;
|
|
1712
1829
|
},
|
|
1713
1830
|
optionDescription: (option) => {
|
|
1714
|
-
if (option.flags === '-v, --version')
|
|
1831
|
+
if (option.flags === '-v, --version' || option.flags === '-h, --help')
|
|
1715
1832
|
return '';
|
|
1716
1833
|
return option.description;
|
|
1717
1834
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pake-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"description": "๐คฑ๐ป Turn any webpage into a desktop app with one command. ๐คฑ๐ป ไธ้ฎๆๅ
็ฝ้กต็ๆ่ฝป้ๆก้ขๅบ็จใ",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18.0.0"
|
|
@@ -52,12 +52,12 @@
|
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@tauri-apps/api": "^2.8.0",
|
|
54
54
|
"@tauri-apps/cli": "^2.8.4",
|
|
55
|
-
"axios": "^1.
|
|
56
|
-
"chalk": "^5.6.
|
|
55
|
+
"axios": "^1.12.2",
|
|
56
|
+
"chalk": "^5.6.2",
|
|
57
57
|
"commander": "^12.1.0",
|
|
58
58
|
"execa": "^9.6.0",
|
|
59
59
|
"file-type": "^18.7.0",
|
|
60
|
-
"fs-extra": "^11.3.
|
|
60
|
+
"fs-extra": "^11.3.2",
|
|
61
61
|
"icon-gen": "^5.0.0",
|
|
62
62
|
"loglevel": "^1.9.2",
|
|
63
63
|
"ora": "^8.2.0",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"@rollup/plugin-replace": "^6.0.2",
|
|
75
75
|
"@rollup/plugin-terser": "^0.4.4",
|
|
76
76
|
"@types/fs-extra": "^11.0.4",
|
|
77
|
-
"@types/node": "^20.19.
|
|
77
|
+
"@types/node": "^20.19.21",
|
|
78
78
|
"@types/page-icon": "^0.3.6",
|
|
79
79
|
"@types/prompts": "^2.4.9",
|
|
80
80
|
"@types/tmp": "^0.2.6",
|
|
@@ -82,9 +82,9 @@
|
|
|
82
82
|
"app-root-path": "^3.1.0",
|
|
83
83
|
"cross-env": "^7.0.3",
|
|
84
84
|
"prettier": "^3.6.2",
|
|
85
|
-
"rollup": "^4.
|
|
85
|
+
"rollup": "^4.52.4",
|
|
86
86
|
"rollup-plugin-typescript2": "^0.36.0",
|
|
87
87
|
"tslib": "^2.8.1",
|
|
88
|
-
"typescript": "^5.9.
|
|
88
|
+
"typescript": "^5.9.3"
|
|
89
89
|
}
|
|
90
90
|
}
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
"incognito": false,
|
|
17
17
|
"enable_wasm": false,
|
|
18
18
|
"enable_drag_drop": false,
|
|
19
|
+
"maximize": false,
|
|
20
|
+
"start_to_tray": false,
|
|
19
21
|
"title": null
|
|
20
22
|
}
|
|
21
23
|
],
|
|
@@ -31,5 +33,6 @@
|
|
|
31
33
|
},
|
|
32
34
|
"system_tray_path": "png/icon_512.png",
|
|
33
35
|
"inject": [],
|
|
34
|
-
"proxy_url": ""
|
|
36
|
+
"proxy_url": "",
|
|
37
|
+
"multi_instance": false
|
|
35
38
|
}
|
package/src-tauri/Cargo.lock
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# This file is automatically @generated by Cargo.
|
|
2
2
|
# It is not intended for manual editing.
|
|
3
|
-
version =
|
|
3
|
+
version = 4
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
6
6
|
name = "addr2line"
|
|
@@ -3546,10 +3546,11 @@ dependencies = [
|
|
|
3546
3546
|
|
|
3547
3547
|
[[package]]
|
|
3548
3548
|
name = "serde"
|
|
3549
|
-
version = "1.0.
|
|
3549
|
+
version = "1.0.228"
|
|
3550
3550
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3551
|
-
checksum = "
|
|
3551
|
+
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
|
3552
3552
|
dependencies = [
|
|
3553
|
+
"serde_core",
|
|
3553
3554
|
"serde_derive",
|
|
3554
3555
|
]
|
|
3555
3556
|
|
|
@@ -3564,11 +3565,20 @@ dependencies = [
|
|
|
3564
3565
|
"typeid",
|
|
3565
3566
|
]
|
|
3566
3567
|
|
|
3568
|
+
[[package]]
|
|
3569
|
+
name = "serde_core"
|
|
3570
|
+
version = "1.0.228"
|
|
3571
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3572
|
+
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
|
3573
|
+
dependencies = [
|
|
3574
|
+
"serde_derive",
|
|
3575
|
+
]
|
|
3576
|
+
|
|
3567
3577
|
[[package]]
|
|
3568
3578
|
name = "serde_derive"
|
|
3569
|
-
version = "1.0.
|
|
3579
|
+
version = "1.0.228"
|
|
3570
3580
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3571
|
-
checksum = "
|
|
3581
|
+
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
3572
3582
|
dependencies = [
|
|
3573
3583
|
"proc-macro2",
|
|
3574
3584
|
"quote",
|
|
@@ -3588,14 +3598,15 @@ dependencies = [
|
|
|
3588
3598
|
|
|
3589
3599
|
[[package]]
|
|
3590
3600
|
name = "serde_json"
|
|
3591
|
-
version = "1.0.
|
|
3601
|
+
version = "1.0.145"
|
|
3592
3602
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3593
|
-
checksum = "
|
|
3603
|
+
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
|
3594
3604
|
dependencies = [
|
|
3595
3605
|
"itoa",
|
|
3596
3606
|
"memchr",
|
|
3597
3607
|
"ryu",
|
|
3598
3608
|
"serde",
|
|
3609
|
+
"serde_core",
|
|
3599
3610
|
]
|
|
3600
3611
|
|
|
3601
3612
|
[[package]]
|
package/src-tauri/Cargo.toml
CHANGED
|
@@ -15,19 +15,19 @@ crate-type = ["staticlib", "cdylib", "lib"]
|
|
|
15
15
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
16
16
|
|
|
17
17
|
[build-dependencies]
|
|
18
|
-
tauri-build = { version = "2.4.
|
|
18
|
+
tauri-build = { version = "2.4.1", features = [] }
|
|
19
19
|
|
|
20
20
|
[dependencies]
|
|
21
|
-
serde_json = "1.0.
|
|
22
|
-
serde = { version = "1.0.
|
|
21
|
+
serde_json = "1.0.145"
|
|
22
|
+
serde = { version = "1.0.228", features = ["derive"] }
|
|
23
23
|
tokio = { version = "1.47.1", features = ["full"] }
|
|
24
|
-
tauri = { version = "2.8.
|
|
24
|
+
tauri = { version = "2.8.5", features = ["tray-icon", "image-ico", "image-png", "macos-proxy"] }
|
|
25
25
|
tauri-plugin-window-state = "2.4.0"
|
|
26
26
|
tauri-plugin-oauth = "2.0.0"
|
|
27
27
|
tauri-plugin-http = "2.5.2"
|
|
28
28
|
tauri-plugin-global-shortcut = { version = "2.3.0" }
|
|
29
29
|
tauri-plugin-shell = "2.3.1"
|
|
30
|
-
tauri-plugin-single-instance = "2.3.
|
|
30
|
+
tauri-plugin-single-instance = "2.3.4"
|
|
31
31
|
tauri-plugin-notification = "2.3.1"
|
|
32
32
|
|
|
33
33
|
[features]
|
package/src-tauri/pake.json
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
"hide_on_close": true,
|
|
16
16
|
"incognito": false,
|
|
17
17
|
"enable_wasm": false,
|
|
18
|
-
"enable_drag_drop": false
|
|
18
|
+
"enable_drag_drop": false,
|
|
19
|
+
"maximize": false,
|
|
20
|
+
"start_to_tray": false
|
|
19
21
|
}
|
|
20
22
|
],
|
|
21
23
|
"user_agent": {
|
|
@@ -30,5 +32,6 @@
|
|
|
30
32
|
},
|
|
31
33
|
"system_tray_path": "icons/icon.png",
|
|
32
34
|
"inject": [],
|
|
33
|
-
"proxy_url": ""
|
|
35
|
+
"proxy_url": "",
|
|
36
|
+
"multi_instance": false
|
|
34
37
|
}
|
|
@@ -5,6 +5,7 @@ pub struct WindowConfig {
|
|
|
5
5
|
pub url: String,
|
|
6
6
|
pub hide_title_bar: bool,
|
|
7
7
|
pub fullscreen: bool,
|
|
8
|
+
pub maximize: bool,
|
|
8
9
|
pub width: f64,
|
|
9
10
|
pub height: f64,
|
|
10
11
|
pub resizable: bool,
|
|
@@ -18,6 +19,7 @@ pub struct WindowConfig {
|
|
|
18
19
|
pub title: Option<String>,
|
|
19
20
|
pub enable_wasm: bool,
|
|
20
21
|
pub enable_drag_drop: bool,
|
|
22
|
+
pub start_to_tray: bool,
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
#[derive(Debug, Serialize, Deserialize)]
|
|
@@ -59,6 +61,8 @@ pub struct PakeConfig {
|
|
|
59
61
|
pub system_tray: FunctionON,
|
|
60
62
|
pub system_tray_path: String,
|
|
61
63
|
pub proxy_url: String,
|
|
64
|
+
#[serde(default)]
|
|
65
|
+
pub multi_instance: bool,
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
impl PakeConfig {
|
|
@@ -3,13 +3,13 @@ use std::sync::{Arc, Mutex};
|
|
|
3
3
|
use std::time::{Duration, Instant};
|
|
4
4
|
use tauri::{
|
|
5
5
|
menu::{MenuBuilder, MenuItemBuilder},
|
|
6
|
-
tray::TrayIconBuilder,
|
|
6
|
+
tray::{TrayIconBuilder, TrayIconEvent},
|
|
7
7
|
AppHandle, Manager,
|
|
8
8
|
};
|
|
9
9
|
use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut};
|
|
10
10
|
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
|
11
11
|
|
|
12
|
-
pub fn set_system_tray(app: &AppHandle, show_system_tray: bool) -> tauri::Result<()> {
|
|
12
|
+
pub fn set_system_tray(app: &AppHandle, show_system_tray: bool, tray_icon_path: &str) -> tauri::Result<()> {
|
|
13
13
|
if !show_system_tray {
|
|
14
14
|
app.remove_tray_by_id("pake-tray");
|
|
15
15
|
return Ok(());
|
|
@@ -44,7 +44,30 @@ pub fn set_system_tray(app: &AppHandle, show_system_tray: bool) -> tauri::Result
|
|
|
44
44
|
}
|
|
45
45
|
_ => (),
|
|
46
46
|
})
|
|
47
|
-
.
|
|
47
|
+
.on_tray_icon_event(|tray, event| match event {
|
|
48
|
+
TrayIconEvent::Click { button, .. } => {
|
|
49
|
+
if button == tauri::tray::MouseButton::Left {
|
|
50
|
+
if let Some(window) = tray.app_handle().get_webview_window("pake") {
|
|
51
|
+
let is_visible = window.is_visible().unwrap_or(false);
|
|
52
|
+
if is_visible {
|
|
53
|
+
window.hide().unwrap();
|
|
54
|
+
} else {
|
|
55
|
+
window.show().unwrap();
|
|
56
|
+
window.set_focus().unwrap();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
_ => {}
|
|
62
|
+
})
|
|
63
|
+
.icon(if tray_icon_path.is_empty() {
|
|
64
|
+
app.default_window_icon().unwrap_or_else(|| panic!("Failed to get default window icon")).clone()
|
|
65
|
+
} else {
|
|
66
|
+
tauri::image::Image::from_path(tray_icon_path).unwrap_or_else(|_| {
|
|
67
|
+
// If custom tray icon fails to load, fallback to default
|
|
68
|
+
app.default_window_icon().unwrap_or_else(|| panic!("Failed to get default window icon")).clone()
|
|
69
|
+
})
|
|
70
|
+
})
|
|
48
71
|
.build(app)?;
|
|
49
72
|
|
|
50
73
|
tray.set_icon_as_template(false)?;
|
|
@@ -43,6 +43,7 @@ pub fn set_window(app: &mut App, config: &PakeConfig, tauri_config: &Config) ->
|
|
|
43
43
|
.user_agent(user_agent)
|
|
44
44
|
.resizable(window_config.resizable)
|
|
45
45
|
.fullscreen(window_config.fullscreen)
|
|
46
|
+
.maximized(window_config.maximize)
|
|
46
47
|
.inner_size(window_config.width, window_config.height)
|
|
47
48
|
.always_on_top(window_config.always_on_top)
|
|
48
49
|
.incognito(window_config.incognito);
|
|
@@ -65,14 +66,7 @@ pub fn set_window(app: &mut App, config: &PakeConfig, tauri_config: &Config) ->
|
|
|
65
66
|
.additional_browser_args("--enable-unsafe-webgpu");
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
if let Ok(proxy_url) = Url::from_str(&config.proxy_url) {
|
|
70
|
-
window_builder = window_builder.proxy_url(proxy_url);
|
|
71
|
-
#[cfg(debug_assertions)]
|
|
72
|
-
println!("Proxy configured: {}", config.proxy_url);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
69
|
+
// Platform-specific configuration must be set before proxy on Windows/Linux
|
|
76
70
|
#[cfg(target_os = "macos")]
|
|
77
71
|
{
|
|
78
72
|
let title_bar_style = if window_config.hide_title_bar {
|
|
@@ -87,7 +81,7 @@ pub fn set_window(app: &mut App, config: &PakeConfig, tauri_config: &Config) ->
|
|
|
87
81
|
}
|
|
88
82
|
}
|
|
89
83
|
|
|
90
|
-
// Windows and Linux
|
|
84
|
+
// Windows and Linux: set data_directory before proxy_url
|
|
91
85
|
#[cfg(not(target_os = "macos"))]
|
|
92
86
|
{
|
|
93
87
|
window_builder = window_builder
|
|
@@ -96,5 +90,14 @@ pub fn set_window(app: &mut App, config: &PakeConfig, tauri_config: &Config) ->
|
|
|
96
90
|
.theme(None);
|
|
97
91
|
}
|
|
98
92
|
|
|
93
|
+
// Set proxy after platform-specific configs (required for Windows/Linux)
|
|
94
|
+
if !config.proxy_url.is_empty() {
|
|
95
|
+
if let Ok(proxy_url) = Url::from_str(&config.proxy_url) {
|
|
96
|
+
window_builder = window_builder.proxy_url(proxy_url);
|
|
97
|
+
#[cfg(debug_assertions)]
|
|
98
|
+
println!("Proxy configured: {}", config.proxy_url);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
99
102
|
window_builder.build().expect("Failed to build window")
|
|
100
103
|
}
|
|
@@ -100,21 +100,6 @@ const DOWNLOADABLE_FILE_EXTENSIONS = {
|
|
|
100
100
|
"scss",
|
|
101
101
|
"sass",
|
|
102
102
|
"less",
|
|
103
|
-
"html",
|
|
104
|
-
"htm",
|
|
105
|
-
"php",
|
|
106
|
-
"py",
|
|
107
|
-
"java",
|
|
108
|
-
"cpp",
|
|
109
|
-
"c",
|
|
110
|
-
"h",
|
|
111
|
-
"cs",
|
|
112
|
-
"rb",
|
|
113
|
-
"go",
|
|
114
|
-
"rs",
|
|
115
|
-
"swift",
|
|
116
|
-
"kt",
|
|
117
|
-
"scala",
|
|
118
103
|
"sh",
|
|
119
104
|
"bat",
|
|
120
105
|
"ps1",
|
|
@@ -163,6 +148,22 @@ function isChineseLanguage(language = getUserLanguage()) {
|
|
|
163
148
|
);
|
|
164
149
|
}
|
|
165
150
|
|
|
151
|
+
// User notification helper
|
|
152
|
+
function showDownloadError(filename) {
|
|
153
|
+
const isChinese = isChineseLanguage();
|
|
154
|
+
const message = isChinese
|
|
155
|
+
? `ไธ่ฝฝๅคฑ่ดฅ: ${filename}`
|
|
156
|
+
: `Download failed: ${filename}`;
|
|
157
|
+
|
|
158
|
+
if (window.Notification && Notification.permission === "granted") {
|
|
159
|
+
new Notification(isChinese ? "ไธ่ฝฝ้่ฏฏ" : "Download Error", {
|
|
160
|
+
body: message,
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
console.error(message);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
166
167
|
// Unified file detection - replaces both isDownloadLink and isFileLink
|
|
167
168
|
function isDownloadableFile(url) {
|
|
168
169
|
try {
|
|
@@ -266,7 +267,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
266
267
|
}
|
|
267
268
|
|
|
268
269
|
// write the ArrayBuffer to a binary, and you're done
|
|
269
|
-
const userLanguage =
|
|
270
|
+
const userLanguage = getUserLanguage();
|
|
270
271
|
invoke("download_file_by_binary", {
|
|
271
272
|
params: {
|
|
272
273
|
filename,
|
|
@@ -275,16 +276,18 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
275
276
|
},
|
|
276
277
|
}).catch((error) => {
|
|
277
278
|
console.error("Failed to download data URI file:", filename, error);
|
|
279
|
+
showDownloadError(filename);
|
|
278
280
|
});
|
|
279
281
|
} catch (error) {
|
|
280
282
|
console.error("Failed to process data URI:", dataURI, error);
|
|
283
|
+
showDownloadError(filename || "file");
|
|
281
284
|
}
|
|
282
285
|
}
|
|
283
286
|
|
|
284
287
|
function downloadFromBlobUrl(blobUrl, filename) {
|
|
285
288
|
convertBlobUrlToBinary(blobUrl)
|
|
286
289
|
.then((binary) => {
|
|
287
|
-
const userLanguage =
|
|
290
|
+
const userLanguage = getUserLanguage();
|
|
288
291
|
invoke("download_file_by_binary", {
|
|
289
292
|
params: {
|
|
290
293
|
filename,
|
|
@@ -293,10 +296,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
293
296
|
},
|
|
294
297
|
}).catch((error) => {
|
|
295
298
|
console.error("Failed to download blob file:", filename, error);
|
|
299
|
+
showDownloadError(filename);
|
|
296
300
|
});
|
|
297
301
|
})
|
|
298
302
|
.catch((error) => {
|
|
299
303
|
console.error("Failed to convert blob to binary:", blobUrl, error);
|
|
304
|
+
showDownloadError(filename);
|
|
300
305
|
});
|
|
301
306
|
}
|
|
302
307
|
|
|
@@ -385,20 +390,21 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
385
390
|
|
|
386
391
|
// Handle _blank links: same domain navigates in-app, cross-domain opens new window
|
|
387
392
|
if (target === "_blank") {
|
|
388
|
-
e.preventDefault();
|
|
389
|
-
e.stopImmediatePropagation();
|
|
390
|
-
|
|
391
393
|
if (isSameDomain(absoluteUrl)) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
window,
|
|
396
|
-
absoluteUrl,
|
|
397
|
-
"_blank",
|
|
398
|
-
"width=1200,height=800,scrollbars=yes,resizable=yes",
|
|
399
|
-
);
|
|
400
|
-
if (!newWindow) handleExternalLink(absoluteUrl);
|
|
394
|
+
// For same-domain links, let the browser/SPA handle it naturally
|
|
395
|
+
// This prevents full page reload in SPA apps like Discord
|
|
396
|
+
return;
|
|
401
397
|
}
|
|
398
|
+
|
|
399
|
+
e.preventDefault();
|
|
400
|
+
e.stopImmediatePropagation();
|
|
401
|
+
const newWindow = originalWindowOpen.call(
|
|
402
|
+
window,
|
|
403
|
+
absoluteUrl,
|
|
404
|
+
"_blank",
|
|
405
|
+
"width=1200,height=800,scrollbars=yes,resizable=yes",
|
|
406
|
+
);
|
|
407
|
+
if (!newWindow) handleExternalLink(absoluteUrl);
|
|
402
408
|
return;
|
|
403
409
|
}
|
|
404
410
|
|
|
@@ -448,39 +454,24 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
448
454
|
// Rewrite the window.open function.
|
|
449
455
|
const originalWindowOpen = window.open;
|
|
450
456
|
window.open = function (url, name, specs) {
|
|
451
|
-
// Apple login and google login
|
|
452
457
|
if (name === "AppleAuthentication") {
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
) {
|
|
458
|
-
location.href = url;
|
|
459
|
-
} else {
|
|
458
|
+
return originalWindowOpen.call(window, url, name, specs);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
try {
|
|
460
462
|
const baseUrl = window.location.origin + window.location.pathname;
|
|
461
463
|
const hrefUrl = new URL(url, baseUrl);
|
|
462
464
|
const absoluteUrl = hrefUrl.href;
|
|
463
465
|
|
|
464
|
-
|
|
465
|
-
if (isSameDomain(absoluteUrl)) {
|
|
466
|
-
// Same domain: navigate in app or open new window based on specs
|
|
467
|
-
if (name === "_blank" || !name) {
|
|
468
|
-
return originalWindowOpen.call(
|
|
469
|
-
window,
|
|
470
|
-
absoluteUrl,
|
|
471
|
-
"_blank",
|
|
472
|
-
"width=1200,height=800,scrollbars=yes,resizable=yes",
|
|
473
|
-
);
|
|
474
|
-
} else {
|
|
475
|
-
location.href = absoluteUrl;
|
|
476
|
-
}
|
|
477
|
-
} else {
|
|
478
|
-
// Cross domain: open in external browser
|
|
466
|
+
if (!isSameDomain(absoluteUrl)) {
|
|
479
467
|
handleExternalLink(absoluteUrl);
|
|
468
|
+
return null;
|
|
480
469
|
}
|
|
470
|
+
|
|
471
|
+
return originalWindowOpen.call(window, absoluteUrl, name, specs);
|
|
472
|
+
} catch (error) {
|
|
473
|
+
return originalWindowOpen.call(window, url, name, specs);
|
|
481
474
|
}
|
|
482
|
-
// Call the original window.open function to maintain its normal functionality.
|
|
483
|
-
return originalWindowOpen.call(window, url, name, specs);
|
|
484
475
|
};
|
|
485
476
|
|
|
486
477
|
// Set the default zoom, There are problems with Loop without using try-catch.
|
|
@@ -688,13 +679,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
688
679
|
}
|
|
689
680
|
} else {
|
|
690
681
|
// Regular HTTP(S) image
|
|
691
|
-
const userLanguage =
|
|
682
|
+
const userLanguage = getUserLanguage();
|
|
692
683
|
invoke("download_file", {
|
|
693
684
|
params: {
|
|
694
685
|
url: imageUrl,
|
|
695
686
|
filename: filename,
|
|
696
687
|
language: userLanguage,
|
|
697
688
|
},
|
|
689
|
+
}).catch((error) => {
|
|
690
|
+
console.error("Failed to download image:", filename, error);
|
|
691
|
+
showDownloadError(filename);
|
|
698
692
|
});
|
|
699
693
|
}
|
|
700
694
|
}
|
|
@@ -742,7 +736,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
742
736
|
|
|
743
737
|
// Simplified menu builder
|
|
744
738
|
function buildMenuItems(type, data) {
|
|
745
|
-
const userLanguage =
|
|
739
|
+
const userLanguage = getUserLanguage();
|
|
746
740
|
const items = [];
|
|
747
741
|
|
|
748
742
|
switch (type) {
|
|
@@ -769,6 +763,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
769
763
|
const filename = getFilenameFromUrl(data.url);
|
|
770
764
|
invoke("download_file", {
|
|
771
765
|
params: { url: data.url, filename, language: userLanguage },
|
|
766
|
+
}).catch((error) => {
|
|
767
|
+
console.error("Failed to download file:", filename, error);
|
|
768
|
+
showDownloadError(filename);
|
|
772
769
|
});
|
|
773
770
|
}),
|
|
774
771
|
);
|
package/src-tauri/src/lib.rs
CHANGED
|
@@ -24,6 +24,8 @@ pub fn run_app() {
|
|
|
24
24
|
let hide_on_close = pake_config.windows[0].hide_on_close;
|
|
25
25
|
let activation_shortcut = pake_config.windows[0].activation_shortcut.clone();
|
|
26
26
|
let init_fullscreen = pake_config.windows[0].fullscreen;
|
|
27
|
+
let start_to_tray = pake_config.windows[0].start_to_tray && show_system_tray; // Only valid when tray is enabled
|
|
28
|
+
let multi_instance = pake_config.multi_instance;
|
|
27
29
|
|
|
28
30
|
let window_state_plugin = WindowStatePlugin::default()
|
|
29
31
|
.with_state_flags(if init_fullscreen {
|
|
@@ -35,19 +37,25 @@ pub fn run_app() {
|
|
|
35
37
|
.build();
|
|
36
38
|
|
|
37
39
|
#[allow(deprecated)]
|
|
38
|
-
tauri_app
|
|
40
|
+
let mut app_builder = tauri_app
|
|
39
41
|
.plugin(window_state_plugin)
|
|
40
42
|
.plugin(tauri_plugin_oauth::init())
|
|
41
43
|
.plugin(tauri_plugin_http::init())
|
|
42
44
|
.plugin(tauri_plugin_shell::init())
|
|
43
|
-
.plugin(tauri_plugin_notification::init())
|
|
44
|
-
|
|
45
|
+
.plugin(tauri_plugin_notification::init());
|
|
46
|
+
|
|
47
|
+
// Only add single instance plugin if multiple instances are not allowed
|
|
48
|
+
if !multi_instance {
|
|
49
|
+
app_builder = app_builder.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
|
|
45
50
|
if let Some(window) = app.get_webview_window("pake") {
|
|
46
51
|
let _ = window.unminimize();
|
|
47
52
|
let _ = window.show();
|
|
48
53
|
let _ = window.set_focus();
|
|
49
54
|
}
|
|
50
|
-
}))
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
app_builder
|
|
51
59
|
.invoke_handler(tauri::generate_handler![
|
|
52
60
|
download_file,
|
|
53
61
|
download_file_by_binary,
|
|
@@ -55,15 +63,18 @@ pub fn run_app() {
|
|
|
55
63
|
])
|
|
56
64
|
.setup(move |app| {
|
|
57
65
|
let window = set_window(app, &pake_config, &tauri_config);
|
|
58
|
-
set_system_tray(app.app_handle(), show_system_tray).unwrap();
|
|
66
|
+
set_system_tray(app.app_handle(), show_system_tray, &pake_config.system_tray_path).unwrap();
|
|
59
67
|
set_global_shortcut(app.app_handle(), activation_shortcut).unwrap();
|
|
60
68
|
|
|
61
69
|
// Show window after state restoration to prevent position flashing
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
// Unless start_to_tray is enabled, then keep it hidden
|
|
71
|
+
if !start_to_tray {
|
|
72
|
+
let window_clone = window.clone();
|
|
73
|
+
tauri::async_runtime::spawn(async move {
|
|
74
|
+
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
|
|
75
|
+
window_clone.show().unwrap();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
67
78
|
|
|
68
79
|
Ok(())
|
|
69
80
|
})
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
[source.crates-io]
|
|
2
|
-
replace-with = 'rsproxy-sparse'
|
|
3
|
-
[source.rsproxy]
|
|
4
|
-
registry = "https://rsproxy.cn/crates.io-index"
|
|
5
|
-
[source.rsproxy-sparse]
|
|
6
|
-
registry = "sparse+https://rsproxy.cn/index/"
|
|
7
|
-
[registries.rsproxy]
|
|
8
|
-
index = "https://rsproxy.cn/crates.io-index"
|
|
9
|
-
[net]
|
|
10
|
-
git-fetch-with-cli = true
|