pake-cli 3.11.5 → 3.11.7
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/dist/cli.js +39 -8
- package/package.json +3 -2
- package/src-tauri/Cargo.lock +7 -4
- package/src-tauri/Cargo.toml +6 -1
- package/src-tauri/build.rs +2 -0
- package/src-tauri/pake.json +1 -0
- package/src-tauri/src/app/config.rs +2 -0
- package/src-tauri/src/app/invoke.rs +85 -0
- package/src-tauri/src/app/menu.rs +56 -5
- package/src-tauri/src/app/window.rs +1 -0
- package/src-tauri/src/inject/event.js +98 -12
- package/src-tauri/src/inject/find.js +708 -0
- package/src-tauri/src/lib.rs +8 -3
- package/src-tauri/tauri.conf.json +1 -1
- package/src-tauri/gen/schemas/acl-manifests.json +0 -1
- package/src-tauri/gen/schemas/capabilities.json +0 -1
- package/src-tauri/gen/schemas/desktop-schema.json +0 -3331
- package/src-tauri/gen/schemas/macOS-schema.json +0 -3331
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ import { InvalidArgumentError, program as program$1, Option } from 'commander';
|
|
|
20
20
|
import fs$1 from 'fs';
|
|
21
21
|
|
|
22
22
|
var name = "pake-cli";
|
|
23
|
-
var version = "3.11.
|
|
23
|
+
var version = "3.11.7";
|
|
24
24
|
var description = "🤱🏻 Turn any webpage into a desktop app with one command. 🤱🏻 一键打包网页生成轻量桌面应用。";
|
|
25
25
|
var engines = {
|
|
26
26
|
node: ">=18.0.0"
|
|
@@ -31,7 +31,7 @@ var bin = {
|
|
|
31
31
|
};
|
|
32
32
|
var repository = {
|
|
33
33
|
type: "git",
|
|
34
|
-
url: "git+https://github.com/tw93/
|
|
34
|
+
url: "git+https://github.com/tw93/Pake.git"
|
|
35
35
|
};
|
|
36
36
|
var author = {
|
|
37
37
|
name: "Tw93",
|
|
@@ -62,6 +62,7 @@ var scripts = {
|
|
|
62
62
|
test: "pnpm run cli:build && cross-env PAKE_CREATE_APP=1 node tests/index.js",
|
|
63
63
|
format: "prettier --write . --ignore-unknown && find tests -name '*.js' -exec sed -i '' 's/[[:space:]]*$//' {} \\; && cd src-tauri && cargo fmt --verbose",
|
|
64
64
|
"format:check": "prettier --check . --ignore-unknown",
|
|
65
|
+
"release:check": "node scripts/check-release-version.mjs && pnpm run format:check && npx vitest run && pnpm run cli:build && npm pack --dry-run --ignore-scripts",
|
|
65
66
|
update: "pnpm update --verbose && cd src-tauri && cargo update",
|
|
66
67
|
prepublishOnly: "pnpm run cli:build"
|
|
67
68
|
};
|
|
@@ -464,6 +465,7 @@ function buildWindowConfigOverrides(options, platform = asSupportedPlatform(proc
|
|
|
464
465
|
start_to_tray: options.startToTray && options.showSystemTray,
|
|
465
466
|
force_internal_navigation: options.forceInternalNavigation,
|
|
466
467
|
internal_url_regex: options.internalUrlRegex,
|
|
468
|
+
enable_find: options.enableFind,
|
|
467
469
|
zoom: options.zoom,
|
|
468
470
|
min_width: options.minWidth,
|
|
469
471
|
min_height: options.minHeight,
|
|
@@ -805,6 +807,24 @@ function getBuildTimeout() {
|
|
|
805
807
|
return 900000;
|
|
806
808
|
}
|
|
807
809
|
let packageManagerCache = null;
|
|
810
|
+
function parseMajorVersion(version) {
|
|
811
|
+
const match = version.match(/^(\d+)/);
|
|
812
|
+
return match ? Number(match[1]) : null;
|
|
813
|
+
}
|
|
814
|
+
function getPinnedPnpmMajorVersion() {
|
|
815
|
+
const packageManager = packageJson.packageManager;
|
|
816
|
+
const match = packageManager?.match(/^pnpm@(\d+)/);
|
|
817
|
+
return match ? Number(match[1]) : null;
|
|
818
|
+
}
|
|
819
|
+
async function detectNpm(execa) {
|
|
820
|
+
try {
|
|
821
|
+
await execa('npm', ['--version'], { stdio: 'ignore' });
|
|
822
|
+
return true;
|
|
823
|
+
}
|
|
824
|
+
catch {
|
|
825
|
+
return false;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
808
828
|
/**
|
|
809
829
|
* Returns 'pnpm' when available, otherwise 'npm'. Throws if neither is found.
|
|
810
830
|
* Cached after the first successful detection so tests can call repeatedly.
|
|
@@ -815,21 +835,28 @@ async function detectPackageManager() {
|
|
|
815
835
|
}
|
|
816
836
|
const { execa } = await import('execa');
|
|
817
837
|
try {
|
|
818
|
-
await execa('pnpm', ['--version']
|
|
838
|
+
const { stdout } = await execa('pnpm', ['--version']);
|
|
839
|
+
const pnpmMajor = parseMajorVersion(stdout.trim());
|
|
840
|
+
const pinnedPnpmMajor = getPinnedPnpmMajorVersion();
|
|
841
|
+
if (pnpmMajor !== null &&
|
|
842
|
+
pinnedPnpmMajor !== null &&
|
|
843
|
+
pnpmMajor !== pinnedPnpmMajor &&
|
|
844
|
+
(await detectNpm(execa))) {
|
|
845
|
+
logger.warn(`✼ Detected pnpm v${stdout.trim()}, but Pake is pinned to ${packageJson.packageManager}; using npm for package installation instead.`);
|
|
846
|
+
packageManagerCache = 'npm';
|
|
847
|
+
return 'npm';
|
|
848
|
+
}
|
|
819
849
|
logger.info('✺ Using pnpm for package management.');
|
|
820
850
|
packageManagerCache = 'pnpm';
|
|
821
851
|
return 'pnpm';
|
|
822
852
|
}
|
|
823
853
|
catch {
|
|
824
|
-
|
|
825
|
-
await execa('npm', ['--version'], { stdio: 'ignore' });
|
|
854
|
+
if (await detectNpm(execa)) {
|
|
826
855
|
logger.info('✺ pnpm not available, using npm for package management.');
|
|
827
856
|
packageManagerCache = 'npm';
|
|
828
857
|
return 'npm';
|
|
829
858
|
}
|
|
830
|
-
|
|
831
|
-
throw new Error('Neither pnpm nor npm is available. Please install a package manager.');
|
|
832
|
-
}
|
|
859
|
+
throw new Error('Neither pnpm nor npm is available. Please install a package manager.');
|
|
833
860
|
}
|
|
834
861
|
}
|
|
835
862
|
function getInstallCommand(packageManager, useCnMirror) {
|
|
@@ -2402,6 +2429,7 @@ const DEFAULT_PAKE_OPTIONS = {
|
|
|
2402
2429
|
startToTray: false,
|
|
2403
2430
|
forceInternalNavigation: false,
|
|
2404
2431
|
internalUrlRegex: '',
|
|
2432
|
+
enableFind: false,
|
|
2405
2433
|
iterativeBuild: false,
|
|
2406
2434
|
zoom: 100,
|
|
2407
2435
|
minWidth: 0,
|
|
@@ -2541,6 +2569,9 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with
|
|
|
2541
2569
|
.addOption(new Option('--internal-url-regex <string>', 'Regex pattern to match URLs that should be considered internal')
|
|
2542
2570
|
.default(DEFAULT_PAKE_OPTIONS.internalUrlRegex)
|
|
2543
2571
|
.hideHelp())
|
|
2572
|
+
.addOption(new Option('--enable-find', 'Enable in-page Find UI with Cmd/Ctrl+F/G shortcuts')
|
|
2573
|
+
.default(DEFAULT_PAKE_OPTIONS.enableFind)
|
|
2574
|
+
.hideHelp())
|
|
2544
2575
|
.addOption(new Option('--installer-language <string>', 'Installer language')
|
|
2545
2576
|
.default(DEFAULT_PAKE_OPTIONS.installerLanguage)
|
|
2546
2577
|
.hideHelp())
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pake-cli",
|
|
3
|
-
"version": "3.11.
|
|
3
|
+
"version": "3.11.7",
|
|
4
4
|
"description": "🤱🏻 Turn any webpage into a desktop app with one command. 🤱🏻 一键打包网页生成轻量桌面应用。",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18.0.0"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
|
-
"url": "git+https://github.com/tw93/
|
|
14
|
+
"url": "git+https://github.com/tw93/Pake.git"
|
|
15
15
|
},
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "Tw93",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"test": "pnpm run cli:build && cross-env PAKE_CREATE_APP=1 node tests/index.js",
|
|
43
43
|
"format": "prettier --write . --ignore-unknown && find tests -name '*.js' -exec sed -i '' 's/[[:space:]]*$//' {} \\; && cd src-tauri && cargo fmt --verbose",
|
|
44
44
|
"format:check": "prettier --check . --ignore-unknown",
|
|
45
|
+
"release:check": "node scripts/check-release-version.mjs && pnpm run format:check && npx vitest run && pnpm run cli:build && npm pack --dry-run --ignore-scripts",
|
|
45
46
|
"update": "pnpm update --verbose && cd src-tauri && cargo update",
|
|
46
47
|
"prepublishOnly": "pnpm run cli:build"
|
|
47
48
|
},
|
package/src-tauri/Cargo.lock
CHANGED
|
@@ -2194,9 +2194,9 @@ dependencies = [
|
|
|
2194
2194
|
|
|
2195
2195
|
[[package]]
|
|
2196
2196
|
name = "muda"
|
|
2197
|
-
version = "0.17.
|
|
2197
|
+
version = "0.17.2"
|
|
2198
2198
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2199
|
-
checksum = "
|
|
2199
|
+
checksum = "7c9fec5a4e89860383d778d10563a605838f8f0b2f9303868937e5ff32e86177"
|
|
2200
2200
|
dependencies = [
|
|
2201
2201
|
"crossbeam-channel",
|
|
2202
2202
|
"dpi",
|
|
@@ -2564,8 +2564,11 @@ dependencies = [
|
|
|
2564
2564
|
|
|
2565
2565
|
[[package]]
|
|
2566
2566
|
name = "pake"
|
|
2567
|
-
version = "3.11.
|
|
2567
|
+
version = "3.11.7"
|
|
2568
2568
|
dependencies = [
|
|
2569
|
+
"objc2",
|
|
2570
|
+
"objc2-app-kit",
|
|
2571
|
+
"objc2-foundation",
|
|
2569
2572
|
"serde",
|
|
2570
2573
|
"serde_json",
|
|
2571
2574
|
"tauri",
|
|
@@ -3066,7 +3069,7 @@ dependencies = [
|
|
|
3066
3069
|
"once_cell",
|
|
3067
3070
|
"socket2",
|
|
3068
3071
|
"tracing",
|
|
3069
|
-
"windows-sys 0.
|
|
3072
|
+
"windows-sys 0.52.0",
|
|
3070
3073
|
]
|
|
3071
3074
|
|
|
3072
3075
|
[[package]]
|
package/src-tauri/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "pake"
|
|
3
|
-
version = "3.11.
|
|
3
|
+
version = "3.11.7"
|
|
4
4
|
description = "🤱🏻 Turn any webpage into a desktop app with Rust."
|
|
5
5
|
authors = ["Tw93"]
|
|
6
6
|
license = "MIT"
|
|
@@ -36,6 +36,11 @@ tauri-plugin-opener = { version = "2.5.3" }
|
|
|
36
36
|
tauri-plugin-single-instance = "2.4.0"
|
|
37
37
|
tauri-plugin-notification = "2.3.3"
|
|
38
38
|
|
|
39
|
+
[target.'cfg(target_os = "macos")'.dependencies]
|
|
40
|
+
objc2 = "0.6"
|
|
41
|
+
objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSDockTile"] }
|
|
42
|
+
objc2-foundation = { version = "0.3", features = ["NSString"] }
|
|
43
|
+
|
|
39
44
|
[features]
|
|
40
45
|
# this feature is used for development builds from development cli
|
|
41
46
|
cli-build = []
|
package/src-tauri/build.rs
CHANGED
package/src-tauri/pake.json
CHANGED
|
@@ -2,6 +2,7 @@ use crate::util::{check_file_or_append, get_download_message_with_lang, show_toa
|
|
|
2
2
|
use std::fs::{self, File};
|
|
3
3
|
use std::io::Write;
|
|
4
4
|
use std::str::FromStr;
|
|
5
|
+
use std::sync::atomic::{AtomicI64, Ordering};
|
|
5
6
|
use tauri::http::Method;
|
|
6
7
|
use tauri::{command, AppHandle, Manager, Url, WebviewWindow};
|
|
7
8
|
use tauri_plugin_http::reqwest::{ClientBuilder, Request};
|
|
@@ -9,6 +10,62 @@ use tauri_plugin_http::reqwest::{ClientBuilder, Request};
|
|
|
9
10
|
#[cfg(target_os = "macos")]
|
|
10
11
|
use tauri::Theme;
|
|
11
12
|
|
|
13
|
+
static BADGE_COUNT: AtomicI64 = AtomicI64::new(0);
|
|
14
|
+
const MAX_BADGE_COUNT: i64 = 99_999;
|
|
15
|
+
const MAX_BADGE_LABEL_CHARS: usize = 16;
|
|
16
|
+
|
|
17
|
+
fn normalize_badge_count(count: Option<i64>) -> Option<i64> {
|
|
18
|
+
count.filter(|n| (1..=MAX_BADGE_COUNT).contains(n))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fn normalize_badge_label(label: Option<&str>) -> Result<Option<String>, String> {
|
|
22
|
+
let Some(label) = label.map(str::trim).filter(|label| !label.is_empty()) else {
|
|
23
|
+
return Ok(None);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if label.chars().count() > MAX_BADGE_LABEL_CHARS {
|
|
27
|
+
return Err(format!(
|
|
28
|
+
"Badge label must be {MAX_BADGE_LABEL_CHARS} characters or fewer"
|
|
29
|
+
));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
Ok(Some(label.to_string()))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fn apply_badge(app: &AppHandle, count: Option<i64>) -> Result<(), String> {
|
|
36
|
+
let label = normalize_badge_count(count).map(|n| n.to_string());
|
|
37
|
+
apply_badge_label(app, label.as_deref())
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#[cfg(target_os = "macos")]
|
|
41
|
+
fn apply_badge_label(app: &AppHandle, label: Option<&str>) -> Result<(), String> {
|
|
42
|
+
use objc2::MainThreadMarker;
|
|
43
|
+
use objc2_app_kit::NSApplication;
|
|
44
|
+
use objc2_foundation::NSString;
|
|
45
|
+
|
|
46
|
+
let label = label.map(str::to_owned);
|
|
47
|
+
app.run_on_main_thread(move || {
|
|
48
|
+
let Some(mtm) = MainThreadMarker::new() else {
|
|
49
|
+
return;
|
|
50
|
+
};
|
|
51
|
+
let dock_tile = NSApplication::sharedApplication(mtm).dockTile();
|
|
52
|
+
let ns_label = label.as_deref().map(NSString::from_str);
|
|
53
|
+
dock_tile.setBadgeLabel(ns_label.as_deref());
|
|
54
|
+
})
|
|
55
|
+
.map_err(|e| format!("Failed to dispatch dock badge update: {e}"))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#[cfg(not(target_os = "macos"))]
|
|
59
|
+
fn apply_badge_label(app: &AppHandle, label: Option<&str>) -> Result<(), String> {
|
|
60
|
+
let window = app
|
|
61
|
+
.get_webview_window("pake")
|
|
62
|
+
.ok_or("Main window not found")?;
|
|
63
|
+
let count = label.and_then(|s| s.parse::<i64>().ok());
|
|
64
|
+
window
|
|
65
|
+
.set_badge_count(count)
|
|
66
|
+
.map_err(|e| format!("Failed to set badge count: {e}"))
|
|
67
|
+
}
|
|
68
|
+
|
|
12
69
|
#[derive(serde::Deserialize)]
|
|
13
70
|
pub struct DownloadFileParams {
|
|
14
71
|
url: String,
|
|
@@ -144,6 +201,34 @@ pub fn send_notification(app: AppHandle, params: NotificationParams) -> Result<(
|
|
|
144
201
|
Ok(())
|
|
145
202
|
}
|
|
146
203
|
|
|
204
|
+
#[command]
|
|
205
|
+
pub fn set_dock_badge(app: AppHandle, count: Option<i64>) -> Result<(), String> {
|
|
206
|
+
let normalized = normalize_badge_count(count);
|
|
207
|
+
BADGE_COUNT.store(normalized.unwrap_or(0), Ordering::SeqCst);
|
|
208
|
+
apply_badge(&app, normalized)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
#[command]
|
|
212
|
+
pub fn increment_dock_badge(app: AppHandle) -> Result<(), String> {
|
|
213
|
+
let current = BADGE_COUNT.load(Ordering::SeqCst);
|
|
214
|
+
let next = current.saturating_add(1).clamp(1, MAX_BADGE_COUNT);
|
|
215
|
+
BADGE_COUNT.store(next, Ordering::SeqCst);
|
|
216
|
+
apply_badge(&app, Some(next))
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#[command]
|
|
220
|
+
pub fn clear_dock_badge(app: AppHandle) -> Result<(), String> {
|
|
221
|
+
BADGE_COUNT.store(0, Ordering::SeqCst);
|
|
222
|
+
apply_badge(&app, None)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#[command]
|
|
226
|
+
pub fn set_dock_badge_label(app: AppHandle, label: Option<String>) -> Result<(), String> {
|
|
227
|
+
BADGE_COUNT.store(0, Ordering::SeqCst);
|
|
228
|
+
let label = normalize_badge_label(label.as_deref())?;
|
|
229
|
+
apply_badge_label(&app, label.as_deref())
|
|
230
|
+
}
|
|
231
|
+
|
|
147
232
|
#[command]
|
|
148
233
|
pub async fn update_theme_mode(app: AppHandle, mode: String) {
|
|
149
234
|
#[cfg(target_os = "macos")]
|
|
@@ -6,24 +6,36 @@ use tauri::menu::{AboutMetadata, Menu, MenuItem, PredefinedMenuItem, Submenu};
|
|
|
6
6
|
use tauri::{AppHandle, Manager, Wry};
|
|
7
7
|
use tauri_plugin_opener::OpenerExt;
|
|
8
8
|
|
|
9
|
-
pub fn
|
|
9
|
+
pub fn set_app_menu(
|
|
10
|
+
app: &AppHandle<Wry>,
|
|
11
|
+
allow_multi_window: bool,
|
|
12
|
+
enable_find: bool,
|
|
13
|
+
) -> tauri::Result<()> {
|
|
10
14
|
let pake_version = env!("CARGO_PKG_VERSION");
|
|
11
15
|
let pake_menu_item_title = format!("Built with Pake V{}", pake_version);
|
|
12
16
|
|
|
17
|
+
let window_submenu = window_menu(app)?;
|
|
18
|
+
|
|
13
19
|
let menu = Menu::with_items(
|
|
14
20
|
app,
|
|
15
21
|
&[
|
|
16
22
|
&app_menu(app)?,
|
|
17
23
|
&file_menu(app, allow_multi_window)?,
|
|
18
|
-
&edit_menu(app)?,
|
|
24
|
+
&edit_menu(app, enable_find)?,
|
|
19
25
|
&view_menu(app)?,
|
|
20
26
|
&navigation_menu(app)?,
|
|
21
|
-
&
|
|
27
|
+
&window_submenu,
|
|
22
28
|
&help_menu(app, &pake_menu_item_title)?,
|
|
23
29
|
],
|
|
24
30
|
)?;
|
|
25
31
|
|
|
26
|
-
|
|
32
|
+
app.set_menu(menu)?;
|
|
33
|
+
|
|
34
|
+
// AppKit injects Move & Resize, Fill, Center, Full Screen Tile, and
|
|
35
|
+
// window-cycling once the submenu is registered as the windows menu.
|
|
36
|
+
window_submenu.set_as_windows_menu_for_nsapp()?;
|
|
37
|
+
|
|
38
|
+
Ok(())
|
|
27
39
|
}
|
|
28
40
|
|
|
29
41
|
fn app_menu(app: &AppHandle<Wry>) -> tauri::Result<Submenu<Wry>> {
|
|
@@ -69,7 +81,7 @@ fn file_menu(app: &AppHandle<Wry>, allow_multi_window: bool) -> tauri::Result<Su
|
|
|
69
81
|
Ok(file_menu)
|
|
70
82
|
}
|
|
71
83
|
|
|
72
|
-
fn edit_menu(app: &AppHandle<Wry
|
|
84
|
+
fn edit_menu(app: &AppHandle<Wry>, enable_find: bool) -> tauri::Result<Submenu<Wry>> {
|
|
73
85
|
let edit_menu = Submenu::new(app, "Edit", true)?;
|
|
74
86
|
edit_menu.append(&PredefinedMenuItem::undo(app, None)?)?;
|
|
75
87
|
edit_menu.append(&PredefinedMenuItem::redo(app, None)?)?;
|
|
@@ -86,6 +98,30 @@ fn edit_menu(app: &AppHandle<Wry>) -> tauri::Result<Submenu<Wry>> {
|
|
|
86
98
|
)?)?;
|
|
87
99
|
edit_menu.append(&PredefinedMenuItem::select_all(app, None)?)?;
|
|
88
100
|
edit_menu.append(&PredefinedMenuItem::separator(app)?)?;
|
|
101
|
+
if enable_find {
|
|
102
|
+
edit_menu.append(&MenuItem::with_id(
|
|
103
|
+
app,
|
|
104
|
+
"find",
|
|
105
|
+
"Find",
|
|
106
|
+
true,
|
|
107
|
+
Some("CmdOrCtrl+F"),
|
|
108
|
+
)?)?;
|
|
109
|
+
edit_menu.append(&MenuItem::with_id(
|
|
110
|
+
app,
|
|
111
|
+
"find_next",
|
|
112
|
+
"Find Next",
|
|
113
|
+
true,
|
|
114
|
+
Some("CmdOrCtrl+G"),
|
|
115
|
+
)?)?;
|
|
116
|
+
edit_menu.append(&MenuItem::with_id(
|
|
117
|
+
app,
|
|
118
|
+
"find_previous",
|
|
119
|
+
"Find Previous",
|
|
120
|
+
true,
|
|
121
|
+
Some("CmdOrCtrl+Shift+G"),
|
|
122
|
+
)?)?;
|
|
123
|
+
edit_menu.append(&PredefinedMenuItem::separator(app)?)?;
|
|
124
|
+
}
|
|
89
125
|
edit_menu.append(&MenuItem::with_id(
|
|
90
126
|
app,
|
|
91
127
|
"copy_url",
|
|
@@ -255,6 +291,21 @@ pub fn handle_menu_click(app_handle: &AppHandle, id: &str) {
|
|
|
255
291
|
let _ = window.eval("triggerPasteAsPlainText()");
|
|
256
292
|
}
|
|
257
293
|
}
|
|
294
|
+
"find" => {
|
|
295
|
+
if let Some(window) = app_handle.get_webview_window("pake") {
|
|
296
|
+
let _ = window.eval("window.pakeFind?.open()");
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
"find_next" => {
|
|
300
|
+
if let Some(window) = app_handle.get_webview_window("pake") {
|
|
301
|
+
let _ = window.eval("window.pakeFind?.next()");
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
"find_previous" => {
|
|
305
|
+
if let Some(window) = app_handle.get_webview_window("pake") {
|
|
306
|
+
let _ = window.eval("window.pakeFind?.previous()");
|
|
307
|
+
}
|
|
308
|
+
}
|
|
258
309
|
"clear_cache_restart" => {
|
|
259
310
|
if let Some(window) = app_handle.get_webview_window("pake") {
|
|
260
311
|
if let Ok(_) = window.clear_all_browsing_data() {
|
|
@@ -287,6 +287,7 @@ fn build_window(
|
|
|
287
287
|
// calls show_toast().
|
|
288
288
|
window_builder = window_builder
|
|
289
289
|
.initialization_script(&config_script)
|
|
290
|
+
.initialization_script(include_str!("../inject/find.js"))
|
|
290
291
|
.initialization_script(include_str!("../inject/toast.js"))
|
|
291
292
|
.initialization_script(include_str!("../inject/fullscreen.js"))
|
|
292
293
|
.initialization_script(include_str!("../inject/event.js"))
|
|
@@ -1025,10 +1025,46 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
1025
1025
|
});
|
|
1026
1026
|
});
|
|
1027
1027
|
|
|
1028
|
-
|
|
1028
|
+
// Bridge the Web Notification + Web Badging APIs to Pake's Rust commands so
|
|
1029
|
+
// pages running inside the webview can drive the macOS dock badge (and
|
|
1030
|
+
// taskbar badge on Linux/Windows). Installs synchronously instead of waiting
|
|
1031
|
+
// for DOMContentLoaded so feature-detection on Notification/setAppBadge
|
|
1032
|
+
// returns the polyfill before site scripts run.
|
|
1033
|
+
(function () {
|
|
1034
|
+
const invoke = window.__TAURI__?.core?.invoke;
|
|
1035
|
+
if (!invoke) return;
|
|
1036
|
+
|
|
1029
1037
|
let permVal = "granted";
|
|
1030
1038
|
let lastNotifTime = 0;
|
|
1031
1039
|
let lastNotif = null;
|
|
1040
|
+
// Pages that drive the badge directly via setAppBadge own its lifecycle;
|
|
1041
|
+
// notifications-driven counts auto-clear on the next user interaction.
|
|
1042
|
+
let pageManagedBadge = false;
|
|
1043
|
+
let autoBadgeActive = false;
|
|
1044
|
+
|
|
1045
|
+
const normalizeBadgeCount = (count) => {
|
|
1046
|
+
if (typeof count !== "number" || !Number.isFinite(count)) {
|
|
1047
|
+
throw new TypeError("Badge count must be a finite number.");
|
|
1048
|
+
}
|
|
1049
|
+
const normalized = Math.floor(count);
|
|
1050
|
+
return normalized > 0 ? Math.min(normalized, 99999) : null;
|
|
1051
|
+
};
|
|
1052
|
+
const setBadge = (count) => {
|
|
1053
|
+
pageManagedBadge = true;
|
|
1054
|
+
autoBadgeActive = false;
|
|
1055
|
+
return invoke("set_dock_badge", { count }).catch(() => {});
|
|
1056
|
+
};
|
|
1057
|
+
const clearBadge = () => invoke("clear_dock_badge").catch(() => {});
|
|
1058
|
+
const setLabel = (label) => {
|
|
1059
|
+
pageManagedBadge = true;
|
|
1060
|
+
autoBadgeActive = false;
|
|
1061
|
+
return invoke("set_dock_badge_label", { label }).catch(() => {});
|
|
1062
|
+
};
|
|
1063
|
+
const incrementAutoBadge = () => {
|
|
1064
|
+
if (pageManagedBadge) return Promise.resolve();
|
|
1065
|
+
autoBadgeActive = true;
|
|
1066
|
+
return invoke("increment_dock_badge").catch(() => {});
|
|
1067
|
+
};
|
|
1032
1068
|
|
|
1033
1069
|
window.addEventListener("focus", () => {
|
|
1034
1070
|
if (lastNotif?.onclick && Date.now() - lastNotifTime < 5000) {
|
|
@@ -1037,11 +1073,17 @@ document.addEventListener("DOMContentLoaded", function () {
|
|
|
1037
1073
|
}
|
|
1038
1074
|
});
|
|
1039
1075
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1076
|
+
const clearAutoBadge = () => {
|
|
1077
|
+
if (pageManagedBadge || !autoBadgeActive) return;
|
|
1078
|
+
autoBadgeActive = false;
|
|
1079
|
+
clearBadge();
|
|
1080
|
+
};
|
|
1081
|
+
document.addEventListener("click", clearAutoBadge, true);
|
|
1082
|
+
document.addEventListener("keydown", clearAutoBadge, true);
|
|
1083
|
+
|
|
1084
|
+
const wrappedNotification = function (title, options) {
|
|
1042
1085
|
const body = options?.body || "";
|
|
1043
1086
|
let icon = options?.icon || "";
|
|
1044
|
-
|
|
1045
1087
|
if (icon.startsWith("/")) {
|
|
1046
1088
|
icon = window.location.origin + icon;
|
|
1047
1089
|
}
|
|
@@ -1056,24 +1098,68 @@ document.addEventListener("DOMContentLoaded", function () {
|
|
|
1056
1098
|
|
|
1057
1099
|
lastNotifTime = Date.now();
|
|
1058
1100
|
lastNotif = notif;
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1101
|
+
invoke("send_notification", { params: { title, body, icon } })
|
|
1102
|
+
.then(() => incrementAutoBadge())
|
|
1103
|
+
.then(() => {
|
|
1104
|
+
if (notif.onshow) notif.onshow(new Event("show"));
|
|
1105
|
+
});
|
|
1063
1106
|
|
|
1064
1107
|
return notif;
|
|
1065
1108
|
};
|
|
1066
1109
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
Object.defineProperty(window.Notification, "permission", {
|
|
1110
|
+
wrappedNotification.requestPermission = async () => "granted";
|
|
1111
|
+
Object.defineProperty(wrappedNotification, "permission", {
|
|
1070
1112
|
enumerable: true,
|
|
1071
1113
|
get: () => permVal,
|
|
1072
1114
|
set: (v) => {
|
|
1073
1115
|
permVal = v;
|
|
1074
1116
|
},
|
|
1075
1117
|
});
|
|
1076
|
-
|
|
1118
|
+
|
|
1119
|
+
try {
|
|
1120
|
+
Object.defineProperty(window, "Notification", {
|
|
1121
|
+
configurable: true,
|
|
1122
|
+
writable: true,
|
|
1123
|
+
value: wrappedNotification,
|
|
1124
|
+
});
|
|
1125
|
+
} catch (_) {}
|
|
1126
|
+
|
|
1127
|
+
// Web Badging API: https://wicg.github.io/badging/
|
|
1128
|
+
// setAppBadge() with no argument shows an indicator dot; with a number,
|
|
1129
|
+
// shows the count (0 clears). clearAppBadge() removes the badge entirely.
|
|
1130
|
+
const setAppBadge = (count) => {
|
|
1131
|
+
if (count === undefined) return setLabel("•");
|
|
1132
|
+
let normalized;
|
|
1133
|
+
try {
|
|
1134
|
+
normalized = normalizeBadgeCount(count);
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
return Promise.reject(error);
|
|
1137
|
+
}
|
|
1138
|
+
if (normalized === null) {
|
|
1139
|
+
pageManagedBadge = false;
|
|
1140
|
+
autoBadgeActive = false;
|
|
1141
|
+
return clearBadge();
|
|
1142
|
+
}
|
|
1143
|
+
return setBadge(normalized);
|
|
1144
|
+
};
|
|
1145
|
+
const clearAppBadge = () => {
|
|
1146
|
+
pageManagedBadge = false;
|
|
1147
|
+
autoBadgeActive = false;
|
|
1148
|
+
return clearBadge();
|
|
1149
|
+
};
|
|
1150
|
+
try {
|
|
1151
|
+
Object.defineProperty(navigator, "setAppBadge", {
|
|
1152
|
+
configurable: true,
|
|
1153
|
+
writable: true,
|
|
1154
|
+
value: setAppBadge,
|
|
1155
|
+
});
|
|
1156
|
+
Object.defineProperty(navigator, "clearAppBadge", {
|
|
1157
|
+
configurable: true,
|
|
1158
|
+
writable: true,
|
|
1159
|
+
value: clearAppBadge,
|
|
1160
|
+
});
|
|
1161
|
+
} catch (_) {}
|
|
1162
|
+
})();
|
|
1077
1163
|
|
|
1078
1164
|
function setDefaultZoom() {
|
|
1079
1165
|
const htmlZoom = window.localStorage.getItem("htmlZoom");
|