mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-05-01 16:17:55 +02:00
feat: don't spam update notification, show more concise toasts, don't fetch unsupported browser updates
This commit is contained in:
+25
-25
@@ -29,30 +29,30 @@
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>HTML document</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Default</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.html</string>
|
||||
<string>public.xhtml</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>Web site URL</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>http</string>
|
||||
<string>https</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>HTML document</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Default</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.html</string>
|
||||
<string>public.xhtml</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>Web site URL</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>http</string>
|
||||
<string>https</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -54,23 +54,32 @@ impl AutoUpdater {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut notifications = Vec::new();
|
||||
let mut browser_versions: HashMap<String, Vec<BrowserVersionInfo>> = HashMap::new();
|
||||
|
||||
// Group profiles by browser
|
||||
let profiles = self
|
||||
.browser_runner
|
||||
.list_profiles()
|
||||
.map_err(|e| format!("Failed to list profiles: {e}"))?;
|
||||
let mut notifications = Vec::new();
|
||||
let mut browser_versions: HashMap<String, Vec<BrowserVersionInfo>> = HashMap::new();
|
||||
|
||||
// Group profiles by browser type
|
||||
let mut browser_profiles: HashMap<String, Vec<BrowserProfile>> = HashMap::new();
|
||||
|
||||
for profile in profiles {
|
||||
// Only check supported browsers
|
||||
if !self
|
||||
.version_service
|
||||
.is_browser_supported(&profile.browser)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
browser_profiles
|
||||
.entry(profile.browser.clone())
|
||||
.or_default()
|
||||
.push(profile);
|
||||
}
|
||||
|
||||
// Check each browser type
|
||||
for (browser, profiles) in browser_profiles {
|
||||
// Get cached versions first, then try to fetch if needed
|
||||
let versions = if let Some(cached) = self
|
||||
@@ -97,7 +106,23 @@ impl AutoUpdater {
|
||||
// Check each profile for updates
|
||||
for profile in profiles {
|
||||
if let Some(update) = self.check_profile_update(&profile, &versions)? {
|
||||
notifications.push(update);
|
||||
// Apply chromium threshold logic
|
||||
if browser == "chromium" {
|
||||
// For chromium, only show notifications if there are 100+ new versions
|
||||
// Count how many versions are newer than the current profile version
|
||||
let newer_versions_count = versions
|
||||
.iter()
|
||||
.filter(|v| self.is_version_newer(&v.version, &profile.version))
|
||||
.count();
|
||||
|
||||
if newer_versions_count >= 100 {
|
||||
notifications.push(update);
|
||||
} else {
|
||||
println!("Skipping chromium update notification: only {newer_versions_count} new versions (need 100+)");
|
||||
}
|
||||
} else {
|
||||
notifications.push(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,12 +224,12 @@ pub async fn clear_all_version_cache_and_refetch() -> Result<(), String> {
|
||||
.clear_all_cache()
|
||||
.map_err(|e| format!("Failed to clear version cache: {e}"))?;
|
||||
|
||||
// Trigger auto-fetch for all supported browsers
|
||||
// Trigger auto-fetch for only supported browsers
|
||||
let service = BrowserVersionService::new();
|
||||
let supported_browsers = service.get_supported_browsers();
|
||||
|
||||
for browser in supported_browsers {
|
||||
// Start background fetch for each browser (don't wait for completion)
|
||||
// Start background fetch for each supported browser (don't wait for completion)
|
||||
let service_clone = BrowserVersionService::new();
|
||||
let browser_clone = browser.clone();
|
||||
tokio::spawn(async move {
|
||||
|
||||
@@ -259,7 +259,7 @@ impl VersionUpdater {
|
||||
) -> Result<Vec<BackgroundUpdateResult>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
println!("Starting background version update for all browsers");
|
||||
|
||||
let browsers = [
|
||||
let all_browsers = [
|
||||
"firefox",
|
||||
"firefox-developer",
|
||||
"mullvad-browser",
|
||||
@@ -269,10 +269,28 @@ impl VersionUpdater {
|
||||
"tor-browser",
|
||||
];
|
||||
|
||||
// Filter browsers to only include those supported on the current platform
|
||||
let browsers: Vec<&str> = all_browsers
|
||||
.iter()
|
||||
.filter(|browser| {
|
||||
self
|
||||
.version_service
|
||||
.is_browser_supported(browser)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
let total_browsers = browsers.len();
|
||||
let mut results = Vec::new();
|
||||
let mut total_new_versions = 0;
|
||||
|
||||
println!(
|
||||
"Updating {} supported browsers (filtered from {} total)",
|
||||
browsers.len(),
|
||||
all_browsers.len()
|
||||
);
|
||||
|
||||
// Emit start event
|
||||
let progress = VersionUpdateProgress {
|
||||
current_browser: "".to_string(),
|
||||
|
||||
@@ -90,6 +90,7 @@ interface VersionUpdateToastProps extends BaseToastProps {
|
||||
current: number;
|
||||
total: number;
|
||||
found: number;
|
||||
current_browser?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -151,7 +152,7 @@ export function UnifiedToast(props: ToastProps) {
|
||||
const progress = "progress" in props ? props.progress : undefined;
|
||||
|
||||
return (
|
||||
<div className="flex items-start p-3 w-full bg-white rounded-lg border border-gray-200 shadow-lg dark:bg-gray-800 dark:border-gray-700">
|
||||
<div className="flex items-start p-3 w-96 bg-white rounded-lg border border-gray-200 shadow-lg dark:bg-gray-800 dark:border-gray-700">
|
||||
<div className="mr-3 mt-0.5">{getToastIcon(type, stage)}</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium leading-tight text-gray-900 dark:text-white">
|
||||
@@ -181,26 +182,30 @@ export function UnifiedToast(props: ToastProps) {
|
||||
)}
|
||||
|
||||
{/* Version update progress */}
|
||||
{type === "version-update" && progress && "found" in progress && (
|
||||
<div className="mt-2 space-y-1">
|
||||
<p className="text-xs text-gray-600 dark:text-gray-300">
|
||||
{progress.found} new versions found so far
|
||||
</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-1.5 min-w-0">
|
||||
<div
|
||||
className="bg-blue-500 h-1.5 rounded-full transition-all duration-300"
|
||||
style={{
|
||||
width: `${(progress.current / progress.total) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
{type === "version-update" &&
|
||||
progress &&
|
||||
"current_browser" in progress && (
|
||||
<div className="mt-2 space-y-1">
|
||||
<p className="text-xs text-gray-600 dark:text-gray-300">
|
||||
{progress.current_browser && (
|
||||
<>Looking for updates for {progress.current_browser}</>
|
||||
)}
|
||||
</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-1.5 min-w-0">
|
||||
<div
|
||||
className="bg-blue-500 h-1.5 rounded-full transition-all duration-300"
|
||||
style={{
|
||||
width: `${(progress.current / progress.total) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="w-8 text-xs text-right text-gray-500 whitespace-nowrap dark:text-gray-400 shrink-0">
|
||||
{progress.current}/{progress.total}
|
||||
</span>
|
||||
</div>
|
||||
<span className="w-8 text-xs text-right text-gray-500 whitespace-nowrap dark:text-gray-400 shrink-0">
|
||||
{progress.current}/{progress.total}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* Twilight update progress */}
|
||||
{type === "twilight-update" && (
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { usePermissions } from "@/hooks/use-permissions";
|
||||
import type { PermissionType } from "@/hooks/use-permissions";
|
||||
import { showSuccessToast } from "@/lib/toast-utils";
|
||||
import { showErrorToast, showSuccessToast } from "@/lib/toast-utils";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
@@ -210,11 +210,16 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
await invoke("clear_all_version_cache_and_refetch");
|
||||
showSuccessToast("Cache cleared successfully", {
|
||||
description:
|
||||
"All browser version cache has been cleared and browsers are being refreshed",
|
||||
"All browser version cache has been cleared and browsers are being refreshed.",
|
||||
duration: 4000,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to clear cache:", error);
|
||||
showErrorToast("Failed to clear cache", {
|
||||
description:
|
||||
error instanceof Error ? error.message : "Unknown error occurred",
|
||||
duration: 4000,
|
||||
});
|
||||
} finally {
|
||||
setIsClearingCache(false);
|
||||
}
|
||||
@@ -237,9 +242,9 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
const getPermissionIcon = (type: PermissionType) => {
|
||||
switch (type) {
|
||||
case "microphone":
|
||||
return <BsMic className="h-4 w-4" />;
|
||||
return <BsMic className="w-4 h-4" />;
|
||||
case "camera":
|
||||
return <BsCamera className="h-4 w-4" />;
|
||||
return <BsCamera className="w-4 h-4" />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -255,7 +260,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
const getStatusBadge = (isGranted: boolean) => {
|
||||
if (isGranted) {
|
||||
return (
|
||||
<Badge variant="default" className="bg-green-100 text-green-800">
|
||||
<Badge variant="default" className="text-green-800 bg-green-100">
|
||||
Granted
|
||||
</Badge>
|
||||
);
|
||||
@@ -436,7 +441,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
||||
{permissions.map((permission) => (
|
||||
<div
|
||||
key={permission.permission_type}
|
||||
className="flex items-center justify-between p-3 border rounded-lg"
|
||||
className="flex justify-between items-center p-3 rounded-lg border"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
{getPermissionIcon(permission.permission_type)}
|
||||
|
||||
@@ -31,8 +31,8 @@ export function VersionUpdateSettings() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<LuRefreshCw className="h-5 w-5" />
|
||||
<CardTitle className="flex gap-2 items-center">
|
||||
<LuRefreshCw className="w-5 h-5" />
|
||||
Background Version Updates
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
@@ -44,8 +44,8 @@ export function VersionUpdateSettings() {
|
||||
{/* Current Status */}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-sm font-medium">
|
||||
<LuClock className="h-4 w-4" />
|
||||
<div className="flex gap-2 items-center text-sm font-medium">
|
||||
<LuClock className="w-4 h-4" />
|
||||
Last Update
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
@@ -54,8 +54,8 @@ export function VersionUpdateSettings() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-sm font-medium">
|
||||
<LuCheckCheck className="h-4 w-4" />
|
||||
<div className="flex gap-2 items-center text-sm font-medium">
|
||||
<LuCheckCheck className="w-4 h-4" />
|
||||
Next Update
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
@@ -69,16 +69,14 @@ export function VersionUpdateSettings() {
|
||||
{/* Progress indicator */}
|
||||
{isUpdating && updateProgress && (
|
||||
<Alert>
|
||||
<LuRefreshCw className="h-4 w-4 animate-spin" />
|
||||
<LuRefreshCw className="w-4 h-4 animate-spin" />
|
||||
<AlertTitle>Updating Browser Versions</AlertTitle>
|
||||
<AlertDescription>
|
||||
{updateProgress.current_browser ? (
|
||||
<>
|
||||
Checking {updateProgress.current_browser} (
|
||||
Looking for updates for {updateProgress.current_browser} (
|
||||
{updateProgress.completed_browsers}/
|
||||
{updateProgress.total_browsers})
|
||||
<br />
|
||||
{updateProgress.new_versions_found} new versions found so far
|
||||
</>
|
||||
) : (
|
||||
"Starting version update..."
|
||||
@@ -88,7 +86,7 @@ export function VersionUpdateSettings() {
|
||||
)}
|
||||
|
||||
{/* Manual update button */}
|
||||
<div className="flex items-center justify-between pt-2 border-t">
|
||||
<div className="flex justify-between items-center pt-2 border-t">
|
||||
<div className="space-y-1">
|
||||
<div className="text-sm font-medium">Manual Update</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
@@ -104,14 +102,14 @@ export function VersionUpdateSettings() {
|
||||
size="sm"
|
||||
disabled={isUpdating}
|
||||
>
|
||||
<LuRefreshCw className="h-4 w-4 mr-2" />
|
||||
<LuRefreshCw className="mr-2 w-4 h-4" />
|
||||
{isUpdating ? "Updating..." : "Check Now"}
|
||||
</LoadingButton>
|
||||
</div>
|
||||
|
||||
{/* Information about background updates */}
|
||||
<Alert>
|
||||
<LuCircleAlert className="h-4 w-4" />
|
||||
<LuCircleAlert className="w-4 h-4" />
|
||||
<AlertTitle>How it works</AlertTitle>
|
||||
<AlertDescription className="text-xs">
|
||||
• Version information is checked automatically every 3 hours
|
||||
|
||||
@@ -5,11 +5,11 @@ import {
|
||||
showErrorToast,
|
||||
showFetchingToast,
|
||||
showSuccessToast,
|
||||
showUnifiedVersionUpdateToast,
|
||||
} from "@/lib/toast-utils";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface GithubRelease {
|
||||
tag_name: string;
|
||||
@@ -137,31 +137,50 @@ export function useBrowserDownload() {
|
||||
|
||||
if (progress.status === "updating") {
|
||||
setIsUpdatingVersions(true);
|
||||
if (progress.current_browser) {
|
||||
const browserName = getBrowserDisplayName(progress.current_browser);
|
||||
showFetchingToast(browserName, {
|
||||
id: `version-update-${progress.current_browser}`,
|
||||
description: "Fetching latest release information...",
|
||||
});
|
||||
}
|
||||
|
||||
// Show unified progress toast
|
||||
const currentBrowserName = progress.current_browser
|
||||
? getBrowserDisplayName(progress.current_browser)
|
||||
: undefined;
|
||||
|
||||
showUnifiedVersionUpdateToast("Checking for browser updates...", {
|
||||
description: currentBrowserName
|
||||
? `Fetching ${currentBrowserName} release information...`
|
||||
: "Initializing version check...",
|
||||
progress: {
|
||||
current: progress.completed_browsers,
|
||||
total: progress.total_browsers,
|
||||
found: progress.new_versions_found,
|
||||
current_browser: currentBrowserName,
|
||||
},
|
||||
});
|
||||
} else if (progress.status === "completed") {
|
||||
setIsUpdatingVersions(false);
|
||||
dismissToast("unified-version-update");
|
||||
|
||||
if (progress.new_versions_found > 0) {
|
||||
showSuccessToast(
|
||||
`Found ${progress.new_versions_found} new browser versions!`,
|
||||
{
|
||||
duration: 3000,
|
||||
duration: 4000,
|
||||
description:
|
||||
"Version information has been updated in the background",
|
||||
},
|
||||
);
|
||||
} else {
|
||||
showSuccessToast("No new browser versions found", {
|
||||
duration: 3000,
|
||||
description: "All browser versions are up to date",
|
||||
});
|
||||
}
|
||||
// Dismiss any update toasts
|
||||
toast.dismiss();
|
||||
} else if (progress.status === "error") {
|
||||
setIsUpdatingVersions(false);
|
||||
dismissToast("unified-version-update");
|
||||
|
||||
showErrorToast("Failed to check for new versions", {
|
||||
duration: 4000,
|
||||
description: "Check your internet connection and try again",
|
||||
});
|
||||
toast.dismiss();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getBrowserDisplayName } from "@/lib/browser-utils";
|
||||
import { showLoadingToast, showVersionUpdateToast } from "@/lib/toast-utils";
|
||||
import { dismissToast, showUnifiedVersionUpdateToast } from "@/lib/toast-utils";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
@@ -46,34 +46,31 @@ export function useVersionUpdater() {
|
||||
if (progress.status === "updating") {
|
||||
setIsUpdating(true);
|
||||
|
||||
if (progress.current_browser) {
|
||||
const browserName = getBrowserDisplayName(progress.current_browser);
|
||||
showVersionUpdateToast(
|
||||
`Downloading release information for ${browserName}`,
|
||||
{
|
||||
id: "version-update-progress",
|
||||
progress: {
|
||||
current: progress.completed_browsers + 1,
|
||||
total: progress.total_browsers,
|
||||
found: progress.new_versions_found,
|
||||
},
|
||||
},
|
||||
);
|
||||
} else {
|
||||
showLoadingToast("Starting version update check...", {
|
||||
id: "version-update-progress",
|
||||
description: "Initializing browser version check...",
|
||||
});
|
||||
}
|
||||
// Show unified progress toast
|
||||
const currentBrowserName = progress.current_browser
|
||||
? getBrowserDisplayName(progress.current_browser)
|
||||
: undefined;
|
||||
|
||||
showUnifiedVersionUpdateToast("Checking for browser updates...", {
|
||||
description: currentBrowserName
|
||||
? `Fetching ${currentBrowserName} release information...`
|
||||
: "Initializing version check...",
|
||||
progress: {
|
||||
current: progress.completed_browsers,
|
||||
total: progress.total_browsers,
|
||||
found: progress.new_versions_found,
|
||||
current_browser: currentBrowserName,
|
||||
},
|
||||
});
|
||||
} else if (progress.status === "completed") {
|
||||
setIsUpdating(false);
|
||||
setUpdateProgress(null);
|
||||
dismissToast("unified-version-update");
|
||||
|
||||
if (progress.new_versions_found > 0) {
|
||||
toast.success(
|
||||
`Found ${progress.new_versions_found} new browser versions!`,
|
||||
{
|
||||
id: "version-update-progress",
|
||||
duration: 4000,
|
||||
description:
|
||||
"Version information has been updated in the background",
|
||||
@@ -81,7 +78,6 @@ export function useVersionUpdater() {
|
||||
);
|
||||
} else {
|
||||
toast.success("No new browser versions found", {
|
||||
id: "version-update-progress",
|
||||
duration: 3000,
|
||||
description: "All browser versions are up to date",
|
||||
});
|
||||
@@ -92,9 +88,9 @@ export function useVersionUpdater() {
|
||||
} else if (progress.status === "error") {
|
||||
setIsUpdating(false);
|
||||
setUpdateProgress(null);
|
||||
dismissToast("unified-version-update");
|
||||
|
||||
toast.error("Failed to update browser versions", {
|
||||
id: "version-update-progress",
|
||||
duration: 4000,
|
||||
description: "Check your internet connection and try again",
|
||||
});
|
||||
@@ -159,7 +155,7 @@ export function useVersionUpdater() {
|
||||
duration: 5000,
|
||||
});
|
||||
} else if (totalNewVersions > 0) {
|
||||
toast.success(`Found ${totalNewVersions} new browser versions!`, {
|
||||
toast.success("Browser versions updated successfully", {
|
||||
description: `Updated ${successfulUpdates} browsers successfully`,
|
||||
duration: 4000,
|
||||
});
|
||||
|
||||
@@ -43,6 +43,7 @@ export interface VersionUpdateToastProps extends BaseToastProps {
|
||||
current: number;
|
||||
total: number;
|
||||
found: number;
|
||||
current_browser?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -214,6 +215,7 @@ export function showVersionUpdateToast(
|
||||
current: number;
|
||||
total: number;
|
||||
found: number;
|
||||
current_browser?: string;
|
||||
};
|
||||
duration?: number;
|
||||
},
|
||||
@@ -301,3 +303,27 @@ export function dismissToast(id: string) {
|
||||
export function dismissAllToasts() {
|
||||
sonnerToast.dismiss();
|
||||
}
|
||||
|
||||
// Add a specific function for unified version update progress
|
||||
export function showUnifiedVersionUpdateToast(
|
||||
title: string,
|
||||
options?: {
|
||||
id?: string;
|
||||
description?: string;
|
||||
progress?: {
|
||||
current: number;
|
||||
total: number;
|
||||
found: number;
|
||||
current_browser?: string;
|
||||
};
|
||||
duration?: number;
|
||||
},
|
||||
) {
|
||||
return showToast({
|
||||
type: "version-update",
|
||||
title,
|
||||
id: "unified-version-update",
|
||||
duration: Number.POSITIVE_INFINITY, // Keep showing until completed
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user