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 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.5";
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/pake.git"
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'], { stdio: 'ignore' });
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
- try {
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
- catch {
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.5",
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/pake.git"
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
  },
@@ -2194,9 +2194,9 @@ dependencies = [
2194
2194
 
2195
2195
  [[package]]
2196
2196
  name = "muda"
2197
- version = "0.17.1"
2197
+ version = "0.17.2"
2198
2198
  source = "registry+https://github.com/rust-lang/crates.io-index"
2199
- checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a"
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.5"
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.60.2",
3072
+ "windows-sys 0.52.0",
3070
3073
  ]
3071
3074
 
3072
3075
  [[package]]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "pake"
3
- version = "3.11.5"
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 = []
@@ -1,3 +1,5 @@
1
1
  fn main() {
2
+ println!("cargo:rerun-if-changed=.pake/pake.json");
3
+ println!("cargo:rerun-if-changed=.pake/tauri.conf.json");
2
4
  tauri_build::build()
3
5
  }
@@ -20,6 +20,7 @@
20
20
  "start_to_tray": false,
21
21
  "force_internal_navigation": false,
22
22
  "internal_url_regex": "",
23
+ "enable_find": false,
23
24
  "new_window": false
24
25
  }
25
26
  ],
@@ -26,6 +26,8 @@ pub struct WindowConfig {
26
26
  pub force_internal_navigation: bool,
27
27
  #[serde(default)]
28
28
  pub internal_url_regex: String,
29
+ #[serde(default)]
30
+ pub enable_find: bool,
29
31
  #[serde(default = "default_zoom")]
30
32
  pub zoom: u32,
31
33
  #[serde(default)]
@@ -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 get_menu(app: &AppHandle<Wry>, allow_multi_window: bool) -> tauri::Result<Menu<Wry>> {
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
- &window_menu(app)?,
27
+ &window_submenu,
22
28
  &help_menu(app, &pake_menu_item_title)?,
23
29
  ],
24
30
  )?;
25
31
 
26
- Ok(menu)
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>) -> tauri::Result<Submenu<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
- document.addEventListener("DOMContentLoaded", function () {
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
- window.Notification = function (title, options) {
1041
- const { invoke } = window.__TAURI__.core;
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
- invoke("send_notification", { params: { title, body, icon } }).then(() => {
1061
- if (notif.onshow) notif.onshow(new Event("show"));
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
- window.Notification.requestPermission = async () => "granted";
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");