pake-cli 3.11.4 → 3.11.6
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 +3 -3
- package/dist/cli.js +335 -161
- package/dist/pake-badge-test.html +1 -0
- package/dist/test-local.html +1 -0
- package/package.json +1 -1
- package/src-tauri/Cargo.lock +7 -4
- package/src-tauri/Cargo.toml +6 -1
- package/src-tauri/build.rs +2 -0
- package/src-tauri/gen/schemas/acl-manifests.json +1 -0
- package/src-tauri/gen/schemas/capabilities.json +1 -0
- package/src-tauri/gen/schemas/desktop-schema.json +3331 -0
- package/src-tauri/gen/schemas/macOS-schema.json +3331 -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/setup.rs +39 -34
- package/src-tauri/src/app/window.rs +64 -17
- package/src-tauri/src/inject/event.js +98 -12
- package/src-tauri/src/inject/find.js +708 -0
- package/src-tauri/src/inject/{component.js → fullscreen.js} +7 -41
- package/src-tauri/src/inject/toast.js +22 -0
- package/src-tauri/src/lib.rs +13 -5
- package/src-tauri/src/util.rs +131 -24
- package/src-tauri/tauri.conf.json +1 -1
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() {
|
|
@@ -119,49 +119,54 @@ pub fn set_global_shortcut(
|
|
|
119
119
|
let app_handle = app.clone();
|
|
120
120
|
let shortcut_hotkey = match Shortcut::from_str(&shortcut) {
|
|
121
121
|
Ok(s) => s,
|
|
122
|
-
Err(
|
|
122
|
+
Err(error) => {
|
|
123
|
+
eprintln!("[Pake] Invalid activation shortcut '{shortcut}': {error}");
|
|
124
|
+
return Ok(());
|
|
125
|
+
}
|
|
123
126
|
};
|
|
124
127
|
let last_triggered = Arc::new(Mutex::new(Instant::now()));
|
|
125
128
|
|
|
126
|
-
app_handle
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
*last_triggered = Instant::now();
|
|
129
|
+
if let Err(error) = app_handle.plugin(
|
|
130
|
+
tauri_plugin_global_shortcut::Builder::new()
|
|
131
|
+
.with_handler({
|
|
132
|
+
let last_triggered = Arc::clone(&last_triggered);
|
|
133
|
+
move |app, event, _shortcut| {
|
|
134
|
+
let Ok(mut last_triggered) = last_triggered.lock() else {
|
|
135
|
+
return;
|
|
136
|
+
};
|
|
137
|
+
if Instant::now().duration_since(*last_triggered) < Duration::from_millis(300) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
*last_triggered = Instant::now();
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
let _ = window.set_fullscreen(true);
|
|
154
|
-
}
|
|
142
|
+
if shortcut_hotkey.eq(event) {
|
|
143
|
+
if let Some(window) = app.get_webview_window("pake") {
|
|
144
|
+
let is_visible = window.is_visible().unwrap_or(false);
|
|
145
|
+
if is_visible {
|
|
146
|
+
let _ = window.hide();
|
|
147
|
+
} else {
|
|
148
|
+
let _ = window.show();
|
|
149
|
+
let _ = window.set_focus();
|
|
150
|
+
#[cfg(target_os = "linux")]
|
|
151
|
+
if _init_fullscreen && !window.is_fullscreen().unwrap_or(false) {
|
|
152
|
+
let _ = window.set_fullscreen(true);
|
|
155
153
|
}
|
|
156
154
|
}
|
|
157
155
|
}
|
|
158
156
|
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
.build(),
|
|
160
|
+
) {
|
|
161
|
+
eprintln!(
|
|
162
|
+
"[Pake] Failed to register global shortcut plugin '{shortcut}': {error}; continuing without it."
|
|
163
|
+
);
|
|
164
|
+
return Ok(());
|
|
165
|
+
}
|
|
163
166
|
|
|
164
|
-
let
|
|
167
|
+
if let Err(error) = app.global_shortcut().register(shortcut_hotkey) {
|
|
168
|
+
eprintln!("[Pake] Failed to bind global shortcut '{shortcut}': {error}");
|
|
169
|
+
}
|
|
165
170
|
|
|
166
171
|
Ok(())
|
|
167
172
|
}
|
|
@@ -50,8 +50,12 @@ impl MultiWindowState {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
pub fn set_window(
|
|
54
|
-
|
|
53
|
+
pub fn set_window(
|
|
54
|
+
app: &AppHandle,
|
|
55
|
+
config: &PakeConfig,
|
|
56
|
+
tauri_config: &Config,
|
|
57
|
+
) -> tauri::Result<WebviewWindow> {
|
|
58
|
+
build_window_with_label(app, config, tauri_config, "pake")
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
pub fn open_additional_window(app: &AppHandle) -> tauri::Result<WebviewWindow> {
|
|
@@ -122,10 +126,12 @@ fn build_window_with_label(
|
|
|
122
126
|
tauri_config: &Config,
|
|
123
127
|
label: &str,
|
|
124
128
|
) -> tauri::Result<WebviewWindow> {
|
|
125
|
-
let window_config = config
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
let window_config = config.windows.first().ok_or_else(|| {
|
|
130
|
+
tauri::Error::Io(std::io::Error::new(
|
|
131
|
+
std::io::ErrorKind::InvalidData,
|
|
132
|
+
"pake.json must define at least one window configuration",
|
|
133
|
+
))
|
|
134
|
+
})?;
|
|
129
135
|
let url = match window_config.url_type.as_str() {
|
|
130
136
|
"web" => {
|
|
131
137
|
let parsed = window_config.url.parse().map_err(|err| {
|
|
@@ -177,12 +183,14 @@ fn build_window(
|
|
|
177
183
|
.product_name
|
|
178
184
|
.clone()
|
|
179
185
|
.unwrap_or_else(|| "pake".to_string());
|
|
180
|
-
let _data_dir = get_data_dir(app, package_name)
|
|
186
|
+
let _data_dir = get_data_dir(app, package_name).map_err(tauri::Error::Io)?;
|
|
181
187
|
|
|
182
|
-
let window_config = config
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
188
|
+
let window_config = config.windows.first().ok_or_else(|| {
|
|
189
|
+
tauri::Error::Io(std::io::Error::new(
|
|
190
|
+
std::io::ErrorKind::InvalidData,
|
|
191
|
+
"pake.json must define at least one window configuration",
|
|
192
|
+
))
|
|
193
|
+
})?;
|
|
186
194
|
|
|
187
195
|
let user_agent = config.user_agent.get();
|
|
188
196
|
|
|
@@ -273,10 +281,15 @@ fn build_window(
|
|
|
273
281
|
});
|
|
274
282
|
}
|
|
275
283
|
|
|
276
|
-
// Add initialization scripts
|
|
284
|
+
// Add initialization scripts. Order matters: pakeConfig must land before
|
|
285
|
+
// any script that reads it (e.g. fullscreen polyfill checks for an opt-out
|
|
286
|
+
// flag), and toast must register `window.pakeToast` before Rust code
|
|
287
|
+
// calls show_toast().
|
|
277
288
|
window_builder = window_builder
|
|
278
289
|
.initialization_script(&config_script)
|
|
279
|
-
.initialization_script(include_str!("../inject/
|
|
290
|
+
.initialization_script(include_str!("../inject/find.js"))
|
|
291
|
+
.initialization_script(include_str!("../inject/toast.js"))
|
|
292
|
+
.initialization_script(include_str!("../inject/fullscreen.js"))
|
|
280
293
|
.initialization_script(include_str!("../inject/event.js"))
|
|
281
294
|
.initialization_script(include_str!("../inject/style.js"))
|
|
282
295
|
.initialization_script(include_str!("../inject/theme_refresh.js"))
|
|
@@ -391,7 +404,9 @@ fn build_window(
|
|
|
391
404
|
}
|
|
392
405
|
|
|
393
406
|
if let Some(features) = new_window_features {
|
|
394
|
-
//
|
|
407
|
+
// Reuse only opener-provided position/size on macOS; sharing the opener
|
|
408
|
+
// WKWebViewConfiguration triggers duplicate WKScriptMessageHandler
|
|
409
|
+
// registrations on macOS 26+ and crashes the app (issue #1194).
|
|
395
410
|
#[cfg(target_os = "macos")]
|
|
396
411
|
{
|
|
397
412
|
if let Some(position) = features.position() {
|
|
@@ -402,9 +417,7 @@ fn build_window(
|
|
|
402
417
|
window_builder = window_builder.inner_size(size.width, size.height);
|
|
403
418
|
}
|
|
404
419
|
|
|
405
|
-
window_builder = window_builder
|
|
406
|
-
.with_webview_configuration(features.opener().target_configuration.clone())
|
|
407
|
-
.focused(true);
|
|
420
|
+
window_builder = window_builder.focused(true);
|
|
408
421
|
}
|
|
409
422
|
|
|
410
423
|
#[cfg(not(target_os = "macos"))]
|
|
@@ -417,3 +430,37 @@ fn build_window(
|
|
|
417
430
|
|
|
418
431
|
window_builder.build()
|
|
419
432
|
}
|
|
433
|
+
|
|
434
|
+
#[cfg(all(test, target_os = "windows"))]
|
|
435
|
+
mod proxy_arg_tests {
|
|
436
|
+
use super::*;
|
|
437
|
+
|
|
438
|
+
fn parse(url: &str) -> Url {
|
|
439
|
+
Url::from_str(url).unwrap()
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
#[test]
|
|
443
|
+
fn http_url_with_explicit_port() {
|
|
444
|
+
let arg = build_proxy_browser_arg(&parse("http://127.0.0.1:7890")).unwrap();
|
|
445
|
+
assert_eq!(arg, "--proxy-server=http://127.0.0.1:7890");
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
#[test]
|
|
449
|
+
fn http_url_uses_default_port_when_missing() {
|
|
450
|
+
let arg = build_proxy_browser_arg(&parse("http://proxy.local")).unwrap();
|
|
451
|
+
assert_eq!(arg, "--proxy-server=http://proxy.local:80");
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
#[test]
|
|
455
|
+
fn socks5_url_uses_default_port_when_missing() {
|
|
456
|
+
let arg = build_proxy_browser_arg(&parse("socks5://proxy.local")).unwrap();
|
|
457
|
+
assert_eq!(arg, "--proxy-server=socks5://proxy.local:1080");
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
#[test]
|
|
461
|
+
fn https_scheme_is_not_supported_yet() {
|
|
462
|
+
// https proxies fall back to platform proxy_url; we only emit a CLI arg
|
|
463
|
+
// for http/socks5 today.
|
|
464
|
+
assert!(build_proxy_browser_arg(&parse("https://proxy.local:8443")).is_none());
|
|
465
|
+
}
|
|
466
|
+
}
|
|
@@ -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");
|