feat(tauri-app): expand tray menu + open_external_url + Profile/Search items

Tray menu now has:
  Show maarcadetweet
  Home
  Compose
  Profile
  Search
  ----
  Quit

The Home/Profile/Search items emit 'app://navigate' events
which the frontend's listenTrayEvents translates to view
switches. The compose and show events continue to be
separate event types ('app://compose', 'app://show').

open_external_url Tauri command takes a URL, validates it's
http(s), and uses tauri-plugin-shell to open it in the user's
default browser. The frontend's openExternalUrl falls back
to window.open in the browser preview (no Tauri runtime).

svelte-check error fix: tauriCall<T>(cmd, fallback, args?)
had the second call argument as 'undefined' instead of 'null'
on the call site for session.load(). The TypeScript compiler
correctly noted that the fallback type 'T' (here Session |
null) couldn't be undefined. Replaced with 'null' and
dropped the trailing null args argument (it's optional).
This commit is contained in:
tomdebone
2026-07-07 17:31:09 +02:00
parent 6a85f44bab
commit 550b89673d
4 changed files with 190 additions and 5 deletions
+66 -1
View File
@@ -444,6 +444,28 @@ async fn pick_and_upload_image(
Ok(Some(resp))
}
/// Open an external URL in the user's default browser. Returns
/// silently on success. Only accepts `http://` and `https://` —
/// any other scheme (e.g. `file://`, `mailto:`, `javascript:`)
/// is rejected so the Rust side is the single source of truth for
/// which schemes are allowed.
#[tauri::command]
async fn open_external_url(
url: String,
app: tauri::AppHandle,
) -> Result<(), String> {
use tauri_plugin_shell::ShellExt;
let lower = url.to_ascii_lowercase();
if !(lower.starts_with("http://") || lower.starts_with("https://")) {
return Err(format!(
"open_external_url: refusing non-http(s) scheme in {url:?}"
));
}
app.shell()
.open(url, None)
.map_err(|e| e.to_string())
}
/// Map a file extension to a MIME type. Returns
/// `application/octet-stream` when the extension is unrecognised —
/// the PDS's magic-byte sniffer will take over from there.
@@ -529,6 +551,7 @@ pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_window_state::Builder::default().build())
.manage(state)
@@ -551,6 +574,14 @@ pub fn run() {
None::<&str>,
)
.map_err(|e| format!("failed to build show menu item: {e}"))?;
let home_item = tauri::menu::MenuItem::with_id(
app,
"tray_home",
"Home",
true,
None::<&str>,
)
.map_err(|e| format!("failed to build home menu item: {e}"))?;
let compose_item = tauri::menu::MenuItem::with_id(
app,
"tray_compose",
@@ -559,6 +590,22 @@ pub fn run() {
None::<&str>,
)
.map_err(|e| format!("failed to build compose menu item: {e}"))?;
let profile_item = tauri::menu::MenuItem::with_id(
app,
"tray_profile",
"Profile",
true,
None::<&str>,
)
.map_err(|e| format!("failed to build profile menu item: {e}"))?;
let search_item = tauri::menu::MenuItem::with_id(
app,
"tray_search",
"Search",
true,
None::<&str>,
)
.map_err(|e| format!("failed to build search menu item: {e}"))?;
let quit_item = tauri::menu::MenuItem::with_id(
app,
"tray_quit",
@@ -572,7 +619,15 @@ pub fn run() {
let tray_menu = tauri::menu::Menu::with_items(
app,
&[&show_item, &compose_item, &separator, &quit_item],
&[
&show_item,
&home_item,
&compose_item,
&profile_item,
&search_item,
&separator,
&quit_item,
],
)
.map_err(|e| format!("failed to build tray menu: {e}"))?;
@@ -586,9 +641,18 @@ pub fn run() {
"tray_show" => {
let _ = tauri::Emitter::emit(app, "app://show", ());
}
"tray_home" => {
let _ = tauri::Emitter::emit(app, "app://navigate", "home");
}
"tray_compose" => {
let _ = tauri::Emitter::emit(app, "app://compose", ());
}
"tray_profile" => {
let _ = tauri::Emitter::emit(app, "app://navigate", "profile");
}
"tray_search" => {
let _ = tauri::Emitter::emit(app, "app://navigate", "search");
}
"tray_quit" => {
app.exit(0);
}
@@ -634,6 +698,7 @@ pub fn run() {
fetch_blob,
pick_and_upload_image,
show_notification,
open_external_url,
])
.run(tauri::generate_context!())
.expect("error while running maarcadetweet");