feat: implement useMarkAsRead hook for improved unread state management and update notification handling
This commit is contained in:
@@ -4,29 +4,42 @@
|
||||
*/
|
||||
export const openExternalUrl = async (url: string): Promise<void> => {
|
||||
console.log('[openExternalUrl] called with:', url);
|
||||
console.log('[openExternalUrl] __TAURI__ exists:', !!(window as any).__TAURI__);
|
||||
|
||||
// Only run this in Tauri builds
|
||||
if (typeof window !== 'undefined' && (window as any).__TAURI__) {
|
||||
// Use Electron's shell.openExternal if in Electron
|
||||
if (isElectron()) {
|
||||
try {
|
||||
const electron = (window as any).electron;
|
||||
if (electron?.shell?.openExternal) {
|
||||
await electron.shell.openExternal(url);
|
||||
console.log('[openExternalUrl] Electron shell.openExternal succeeded');
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[openExternalUrl] Electron shell.openExternal failed:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Use Tauri for actual Tauri builds
|
||||
if (isTauri() && !isElectron()) {
|
||||
try {
|
||||
// First, try the plugin directly (works on both desktop and mobile)
|
||||
console.log('[openExternalUrl] trying opener plugin...');
|
||||
console.log('[openExternalUrl] trying Tauri opener plugin...');
|
||||
const { openUrl } = await import('@tauri-apps/plugin-opener');
|
||||
await openUrl(url);
|
||||
console.log('[openExternalUrl] plugin succeeded');
|
||||
console.log('[openExternalUrl] Tauri plugin succeeded');
|
||||
return;
|
||||
} catch (pluginErr) {
|
||||
console.warn('[openExternalUrl] opener plugin failed:', pluginErr);
|
||||
console.warn('[openExternalUrl] Tauri opener plugin failed:', pluginErr);
|
||||
|
||||
// Fallback: try the custom command (useful if plugin fails due to ACL)
|
||||
try {
|
||||
console.log('[openExternalUrl] trying invoke command fallback...');
|
||||
console.log('[openExternalUrl] trying Tauri invoke command fallback...');
|
||||
const { invoke } = await import('@tauri-apps/api/core');
|
||||
await invoke('open_external_url', { url });
|
||||
console.log('[openExternalUrl] command fallback succeeded');
|
||||
console.log('[openExternalUrl] Tauri command fallback succeeded');
|
||||
return;
|
||||
} catch (invokeErr) {
|
||||
console.error('[openExternalUrl] command fallback also failed:', invokeErr);
|
||||
console.error('[openExternalUrl] Tauri command fallback also failed:', invokeErr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,15 +92,29 @@ let notificationTapCallback: ((path: string) => void) | null = null;
|
||||
* Bring the Tauri window to the front and focus it
|
||||
*/
|
||||
export const focusWindow = async (): Promise<void> => {
|
||||
if (!isTauri()) return;
|
||||
// Use Electron's window focus if in Electron
|
||||
if (isElectron()) {
|
||||
try {
|
||||
const electron = (window as any).electron;
|
||||
if (electron?.window?.focus) {
|
||||
await electron.window.focus();
|
||||
}
|
||||
return;
|
||||
} catch (err) {
|
||||
console.warn('Failed to focus Electron window:', err);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
||||
const window = getCurrentWindow();
|
||||
await window.unminimize();
|
||||
await window.setFocus();
|
||||
} catch (err) {
|
||||
console.warn('Failed to focus window:', err);
|
||||
// Use Tauri's window focus for actual Tauri builds
|
||||
if (isTauri() && !isElectron()) {
|
||||
try {
|
||||
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
||||
const window = getCurrentWindow();
|
||||
await window.unminimize();
|
||||
await window.setFocus();
|
||||
} catch (err) {
|
||||
console.warn('Failed to focus Tauri window:', err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -96,28 +123,43 @@ export const focusWindow = async (): Promise<void> => {
|
||||
* Call this once at app startup with a navigation callback
|
||||
*/
|
||||
export const setupNotificationTapListener = async (onTap: (path: string) => void): Promise<void> => {
|
||||
if (!isTauri()) return;
|
||||
|
||||
notificationTapCallback = onTap;
|
||||
|
||||
try {
|
||||
const { onAction } = await import('@tauri-apps/plugin-notification');
|
||||
|
||||
await onAction(async (notification) => {
|
||||
// Bring window to front first
|
||||
await focusWindow();
|
||||
|
||||
// Get the path from extra data and navigate
|
||||
const path = notification.notification?.extra?.path;
|
||||
if (path && typeof path === 'string' && notificationTapCallback) {
|
||||
notificationTapCallback(path);
|
||||
// Use Electron's native notification handler if in Electron
|
||||
if (isElectron()) {
|
||||
try {
|
||||
const electron = (window as any).electron;
|
||||
if (electron?.notification?.onNavigate) {
|
||||
electron.notification.onNavigate((data: { path?: string }) => {
|
||||
if (data.path && typeof data.path === 'string' && notificationTapCallback) {
|
||||
notificationTapCallback(data.path);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
// Silently ignore errors in Electron compatibility layer
|
||||
// Only warn if we're actually in a Tauri environment
|
||||
if (typeof window !== 'undefined' && '__TAURI__' in window) {
|
||||
console.warn('Failed to set up notification tap listener:', err);
|
||||
} catch (err) {
|
||||
console.warn('Failed to set up Electron notification listener:', err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Use Tauri plugin for actual Tauri builds (not Electron)
|
||||
if (isTauri() && !isElectron()) {
|
||||
try {
|
||||
const { onAction } = await import('@tauri-apps/plugin-notification');
|
||||
|
||||
await onAction(async (notification) => {
|
||||
// Bring window to front first
|
||||
await focusWindow();
|
||||
|
||||
// Get the path from extra data and navigate
|
||||
const path = notification.notification?.extra?.path;
|
||||
if (path && typeof path === 'string' && notificationTapCallback) {
|
||||
notificationTapCallback(path);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn('Failed to set up Tauri notification tap listener:', err);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -128,11 +170,17 @@ export const setupNotificationTapListener = async (onTap: (path: string) => void
|
||||
export const isTauri = (): boolean =>
|
||||
'__TAURI__' in window || '__TAURI_INTERNALS__' in window;
|
||||
|
||||
/**
|
||||
* Check if we're running inside Electron (not Tauri)
|
||||
*/
|
||||
export const isElectron = (): boolean =>
|
||||
typeof window !== 'undefined' && 'electron' in window;
|
||||
|
||||
/**
|
||||
* Check if we're running on a mobile platform (Android/iOS)
|
||||
*/
|
||||
export const isTauriMobile = (): boolean => {
|
||||
if (!isTauri()) return false;
|
||||
if (!isTauri() || isElectron()) return false;
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
return ua.includes('android') || ua.includes('iphone') || ua.includes('ipad');
|
||||
};
|
||||
@@ -242,7 +290,26 @@ export const sendNotification = async (options: {
|
||||
}): Promise<void> => {
|
||||
const { title, body, icon, path, onClick } = options;
|
||||
|
||||
if (isTauri()) {
|
||||
// Use Electron's native notification API if in Electron
|
||||
if (isElectron()) {
|
||||
try {
|
||||
const electron = (window as any).electron;
|
||||
if (electron?.notification?.show) {
|
||||
await electron.notification.show({
|
||||
title,
|
||||
body,
|
||||
icon,
|
||||
path,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Electron notification failed:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Use Tauri plugin for actual Tauri builds (not Electron)
|
||||
if (isTauri() && !isElectron()) {
|
||||
try {
|
||||
const {
|
||||
sendNotification: tauriSendNotification,
|
||||
@@ -269,14 +336,14 @@ export const sendNotification = async (options: {
|
||||
extra: path ? { path } : undefined,
|
||||
});
|
||||
}
|
||||
return;
|
||||
} catch (err) {
|
||||
console.warn('Tauri notification failed:', err);
|
||||
// Fallback to browser notification on error
|
||||
sendBrowserNotification({ title, body, icon, onClick });
|
||||
}
|
||||
} else {
|
||||
sendBrowserNotification({ title, body, icon, onClick });
|
||||
}
|
||||
|
||||
// Fallback to browser notification
|
||||
sendBrowserNotification({ title, body, icon, onClick });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -292,22 +359,44 @@ export const isLinux = (): boolean => {
|
||||
* Returns a data URL if an image is found, null otherwise
|
||||
*/
|
||||
export const readClipboardImage = async (): Promise<File | null> => {
|
||||
if (!isTauri() || !isLinux()) return null;
|
||||
|
||||
try {
|
||||
const { invoke } = await import('@tauri-apps/api/core');
|
||||
const dataUrl = await invoke<string | null>('read_clipboard_image');
|
||||
|
||||
if (!dataUrl) return null;
|
||||
|
||||
// Convert data URL to File
|
||||
const response = await fetch(dataUrl);
|
||||
const blob = await response.blob();
|
||||
return new File([blob], 'clipboard-image.png', { type: 'image/png' });
|
||||
} catch (err) {
|
||||
console.warn('Failed to read clipboard image:', err);
|
||||
return null;
|
||||
// Use Electron's clipboard API if in Electron
|
||||
if (isElectron()) {
|
||||
try {
|
||||
const electron = (window as any).electron;
|
||||
if (electron?.clipboard?.readImage) {
|
||||
const dataUrl = await electron.clipboard.readImage();
|
||||
if (!dataUrl) return null;
|
||||
|
||||
// Convert data URL to File
|
||||
const response = await fetch(dataUrl);
|
||||
const blob = await response.blob();
|
||||
return new File([blob], 'clipboard-image.png', { type: 'image/png' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed to read Electron clipboard image:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Use Tauri's invoke for actual Tauri builds on Linux
|
||||
if (isTauri() && !isElectron() && isLinux()) {
|
||||
try {
|
||||
const { invoke } = await import('@tauri-apps/api/core');
|
||||
const dataUrl = await invoke<string | null>('read_clipboard_image');
|
||||
|
||||
if (!dataUrl) return null;
|
||||
|
||||
// Convert data URL to File
|
||||
const response = await fetch(dataUrl);
|
||||
const blob = await response.blob();
|
||||
return new File([blob], 'clipboard-image.png', { type: 'image/png' });
|
||||
} catch (err) {
|
||||
console.warn('Failed to read Tauri clipboard image:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user