Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbd1c55f44 | ||
|
|
9668a04a1a | ||
|
|
24af375a8e | ||
|
|
a32c973ab8 | ||
|
|
50beb913de | ||
|
|
05f1ec7b34 | ||
|
|
9271b107b6 | ||
|
|
e7208dd7d2 |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -424,7 +424,7 @@ jobs:
|
||||
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Clash.Verge.Rev.Lite_${{ env.VERSION }}_x64.dmg"><img src="https://img.shields.io/badge/DMG-default?style=flat&logo=apple&label=Intel"></a><br>
|
||||
> :warning: **Warning**
|
||||
If you get a notification that the application is corrupted when you run it on macOS, run this command:<br>
|
||||
`sudo xattr -r -c /Applications/Clash\ Verge\ Rev\ Lite.app`
|
||||
<code>sudo xattr -r -c /Applications/Clash\ Verge\ Rev\ Lite.app</code>
|
||||
|
||||
### Linux
|
||||
<a href="https://github.com/coolcoala/clash-verge-rev-lite/releases/download/v${{ env.VERSION }}/Clash.Verge.Rev.Lite_${{ env.VERSION }}_amd64.deb"><img src="https://img.shields.io/badge/x64-default?style=flat&logo=debian&label=DEB"> </a><br>
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
## v0.2.3
|
||||
|
||||
- fixed problem with profile inactivation after adding via deeplink on windows
|
||||
- corrected layout on the proxy page, now all cards are the same size
|
||||
- corrected announe transposition by \n
|
||||
- corrected side menu in compressed window
|
||||
- added check at the main toggle switch, now it cannot be enabled if there are no profiles.
|
||||
|
||||
|
||||
## v0.2.1
|
||||
|
||||
- added headers "announce-url", "update-always"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clash-verge",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.3",
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
"dev": "cross-env RUST_BACKTRACE=1 tauri dev -f verge-dev",
|
||||
|
||||
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
@@ -1061,7 +1061,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clash-verge"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"aes-gcm",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clash-verge"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
description = "clash verge"
|
||||
authors = ["zzzgydi", "wonfen", "MystiPanda", "coolcoala"]
|
||||
license = "GPL-3.0-only"
|
||||
|
||||
@@ -565,7 +565,7 @@ pub async fn resolve_scheme(param: String) -> Result<()> {
|
||||
Some(url) => {
|
||||
log::info!(target:"app", "decoded subscription url: {url}");
|
||||
|
||||
create_window(false);
|
||||
create_window(true);
|
||||
match PrfItem::from_url(url.as_ref(), name, None, None).await {
|
||||
Ok(item) => {
|
||||
let uid = item.uid.clone().unwrap();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.3",
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"bundle": {
|
||||
"active": true,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuItem, useSidebar,
|
||||
} from "@/components/ui/sidebar"
|
||||
import { t } from 'i18next';
|
||||
import { cn } from '@root/lib/utils';
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { UpdateButton } from "@/components/layout/update-button";
|
||||
import React from "react";
|
||||
import { SheetClose } from '@/components/ui/sheet';
|
||||
|
||||
const menuItems = [
|
||||
{ title: 'Home', url: '/home', icon: Home },
|
||||
@@ -35,6 +36,7 @@ const menuItems = [
|
||||
];
|
||||
|
||||
export function AppSidebar() {
|
||||
const { isMobile } = useSidebar();
|
||||
return (
|
||||
<Sidebar variant="floating" collapsible="icon">
|
||||
<SidebarHeader>
|
||||
@@ -51,13 +53,8 @@ export function AppSidebar() {
|
||||
<SidebarMenu className="gap-3">
|
||||
{menuItems.map((item) => {
|
||||
const isActive = location.pathname === item.url;
|
||||
return (
|
||||
<SidebarMenuItem key={item.title} className="my-1">
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
isActive={isActive}
|
||||
tooltip={t(item.title)}>
|
||||
<Link
|
||||
const linkElement = (
|
||||
<Link
|
||||
key={item.title}
|
||||
to={item.url}
|
||||
className={cn(
|
||||
@@ -68,6 +65,20 @@ export function AppSidebar() {
|
||||
<item.icon className="h-4 w-4 drop-shadow-md" />
|
||||
{t(item.title)}
|
||||
</Link>
|
||||
)
|
||||
return (
|
||||
<SidebarMenuItem key={item.title} className="my-1">
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
isActive={isActive}
|
||||
tooltip={t(item.title)}>
|
||||
{isMobile ? (
|
||||
<SheetClose asChild>
|
||||
{linkElement}
|
||||
</SheetClose>
|
||||
) : (
|
||||
linkElement
|
||||
)}
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
)
|
||||
|
||||
@@ -66,7 +66,7 @@ export const ProxyItemMini = (props: Props) => {
|
||||
title={`${proxy.name}\n${proxy.now ?? ""}`}
|
||||
className="group relative flex h-16 cursor-pointer items-center justify-between rounded-lg border bg-card p-3 shadow-sm transition-colors duration-200 hover:bg-accent data-[selected=true]:ring-2 data-[selected=true]:ring-primary"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex-1 min-w-0 w-0">
|
||||
<p className="truncate text-sm font-medium">{proxy.name}</p>
|
||||
|
||||
{showType && (
|
||||
|
||||
@@ -32,21 +32,21 @@ dayjs.extend(relativeTime);
|
||||
|
||||
const OS = getSystem();
|
||||
|
||||
// 通知处理函数
|
||||
// Notification Handler
|
||||
const handleNoticeMessage = (
|
||||
status: string,
|
||||
msg: string,
|
||||
t: (key: string) => string,
|
||||
navigate: (path: string, options?: any) => void,
|
||||
) => {
|
||||
console.log("[通知监听 V2] 收到消息:", status, msg);
|
||||
console.log("[Notification Listener V2] Receiving a message:", status, msg);
|
||||
|
||||
switch (status) {
|
||||
case "import_sub_url::ok":
|
||||
mutate("getProfiles");
|
||||
navigate("/", { state: { activateProfile: msg } });
|
||||
navigate("/");
|
||||
showNotice("success", t("Import Subscription Successful"));
|
||||
window.dispatchEvent(new CustomEvent('activate-profile', { detail: msg }));
|
||||
sessionStorage.setItem('activateProfile', msg);
|
||||
break;
|
||||
case "import_sub_url::error":
|
||||
showNotice("error", msg);
|
||||
@@ -136,7 +136,7 @@ const handleNoticeMessage = (
|
||||
showNotice("error", `${t("Failed to Change Core")}: ${msg}`);
|
||||
break;
|
||||
default: // Optional: Log unhandled statuses
|
||||
console.warn(`[通知监听 V2] 未处理的状态: ${status}`);
|
||||
console.warn(`[Notification Listener V2] Unprocessed state: ${status}`);
|
||||
break;
|
||||
}
|
||||
};
|
||||
@@ -163,14 +163,14 @@ const Layout = () => {
|
||||
try {
|
||||
handleNoticeMessage(status, msg, t, navigate);
|
||||
} catch (error) {
|
||||
console.error("[Layout] 处理通知消息失败:", error);
|
||||
console.error("[Layout] Failure to process a notification message:", error);
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
[t, navigate],
|
||||
);
|
||||
|
||||
// 初始化全局日志服务
|
||||
// Initialize the global logging service
|
||||
useEffect(() => {
|
||||
if (clashInfo) {
|
||||
const { server = "", secret = "" } = clashInfo;
|
||||
@@ -178,7 +178,7 @@ const Layout = () => {
|
||||
}
|
||||
}, [clashInfo, enableLog]);
|
||||
|
||||
// 设置监听器
|
||||
// Setting up a listener
|
||||
useEffect(() => {
|
||||
const listeners = [
|
||||
addListener("verge://refresh-clash-config", async () => {
|
||||
@@ -224,11 +224,11 @@ const Layout = () => {
|
||||
try {
|
||||
unlisten();
|
||||
} catch (error) {
|
||||
console.error("[Layout] 清理事件监听器失败:", error);
|
||||
console.error("[Layout] Failed to clear event listener:", error);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("[Layout] 获取unlisten函数失败:", error);
|
||||
console.error("[Layout] Failed to get unlisten function:", error);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -238,11 +238,11 @@ const Layout = () => {
|
||||
try {
|
||||
cleanup();
|
||||
} catch (error) {
|
||||
console.error("[Layout] 清理窗口监听器失败:", error);
|
||||
console.error("[Layout] Failed to clear window listener:", error);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("[Layout] 获取cleanup函数失败:", error);
|
||||
console.error("[Layout] Failed to get cleanup function:", error);
|
||||
});
|
||||
}, 0);
|
||||
};
|
||||
@@ -250,10 +250,10 @@ const Layout = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (initRef.current) {
|
||||
console.log("[Layout] 初始化代码已执行过,跳过");
|
||||
console.log("[Layout] Initialization code has already been executed, skip");
|
||||
return;
|
||||
}
|
||||
console.log("[Layout] 开始执行初始化代码");
|
||||
console.log("[Layout] Begin executing initialization code");
|
||||
initRef.current = true;
|
||||
|
||||
let isInitialized = false;
|
||||
@@ -263,27 +263,27 @@ const Layout = () => {
|
||||
const notifyBackend = async (action: string, stage?: string) => {
|
||||
try {
|
||||
if (stage) {
|
||||
console.log(`[Layout] 通知后端 ${action}: ${stage}`);
|
||||
console.log(`[Layout] Notification Backend ${action}: ${stage}`);
|
||||
await invoke("update_ui_stage", { stage });
|
||||
} else {
|
||||
console.log(`[Layout] 通知后端 ${action}`);
|
||||
console.log(`[Layout] Notification Backend ${action}`);
|
||||
await invoke("notify_ui_ready");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[Layout] 通知失败 ${action}:`, err);
|
||||
console.error(`[Layout] Notification failure ${action}:`, err);
|
||||
}
|
||||
};
|
||||
|
||||
const removeLoadingOverlay = () => {
|
||||
const initialOverlay = document.getElementById("initial-loading-overlay");
|
||||
if (initialOverlay) {
|
||||
console.log("[Layout] 移除加载指示器");
|
||||
console.log("[Layout] Remove loading indicator");
|
||||
initialOverlay.style.opacity = "0";
|
||||
setTimeout(() => {
|
||||
try {
|
||||
initialOverlay.remove();
|
||||
} catch (e) {
|
||||
console.log("[Layout] 加载指示器已被移除");
|
||||
console.log("[Layout] Load indicator has been removed");
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
@@ -291,23 +291,23 @@ const Layout = () => {
|
||||
|
||||
const performInitialization = async () => {
|
||||
if (isInitialized) {
|
||||
console.log("[Layout] 已经初始化过,跳过");
|
||||
console.log("[Layout] Already initialized, skip");
|
||||
return;
|
||||
}
|
||||
|
||||
initializationAttempts++;
|
||||
console.log(`[Layout] 开始第 ${initializationAttempts} 次初始化尝试`);
|
||||
console.log(`[Layout] Start ${initializationAttempts} for the first time`);
|
||||
|
||||
try {
|
||||
removeLoadingOverlay();
|
||||
|
||||
await notifyBackend("加载阶段", "Loading");
|
||||
await notifyBackend("Loading phase", "Loading");
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
const checkReactMount = () => {
|
||||
const rootElement = document.getElementById("root");
|
||||
if (rootElement && rootElement.children.length > 0) {
|
||||
console.log("[Layout] React组件已挂载");
|
||||
console.log("[Layout] React components are mounted");
|
||||
resolve();
|
||||
} else {
|
||||
setTimeout(checkReactMount, 50);
|
||||
@@ -317,43 +317,43 @@ const Layout = () => {
|
||||
checkReactMount();
|
||||
|
||||
setTimeout(() => {
|
||||
console.log("[Layout] React组件挂载检查超时,继续执行");
|
||||
console.log("[Layout] React components mount check timeout, continue execution");
|
||||
resolve();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
await notifyBackend("DOM就绪", "DomReady");
|
||||
await notifyBackend("DOM ready", "DomReady");
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
requestAnimationFrame(() => resolve());
|
||||
});
|
||||
|
||||
await notifyBackend("资源加载完成", "ResourcesLoaded");
|
||||
await notifyBackend("Resource loading completed", "ResourcesLoaded");
|
||||
|
||||
await notifyBackend("UI就绪");
|
||||
await notifyBackend("UI ready");
|
||||
|
||||
isInitialized = true;
|
||||
console.log(`[Layout] 第 ${initializationAttempts} 次初始化完成`);
|
||||
console.log(`[Layout] The ${initializationAttempts} initialization is complete`);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[Layout] 第 ${initializationAttempts} 次初始化失败:`,
|
||||
`[Layout] Initialization failure at ${initializationAttempts}:`,
|
||||
error,
|
||||
);
|
||||
|
||||
if (initializationAttempts < maxAttempts) {
|
||||
console.log(
|
||||
`[Layout] 将在500ms后进行第 ${initializationAttempts + 1} 次重试`,
|
||||
`[Layout] The first ${initializationAttempts + 1} retry will be made after 500ms`,
|
||||
);
|
||||
setTimeout(performInitialization, 500);
|
||||
} else {
|
||||
console.error("[Layout] 所有初始化尝试都失败,执行紧急初始化");
|
||||
console.error("[Layout] All initialization attempts fail, perform emergency initialization");
|
||||
|
||||
removeLoadingOverlay();
|
||||
try {
|
||||
await notifyBackend("UI就绪");
|
||||
await notifyBackend("UI ready");
|
||||
isInitialized = true;
|
||||
} catch (e) {
|
||||
console.error("[Layout] 紧急初始化也失败:", e);
|
||||
console.error("[Layout] Emergency initialization also failed:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,39 +363,39 @@ const Layout = () => {
|
||||
|
||||
const setupEventListener = async () => {
|
||||
try {
|
||||
console.log("[Layout] 开始监听启动完成事件");
|
||||
console.log("[Layout] Start listening for startup completion events");
|
||||
const unlisten = await listen("verge://startup-completed", () => {
|
||||
if (!hasEventTriggered) {
|
||||
console.log("[Layout] 收到启动完成事件,开始初始化");
|
||||
console.log("[Layout] Receive startup completion event, start initialization");
|
||||
hasEventTriggered = true;
|
||||
performInitialization();
|
||||
}
|
||||
});
|
||||
return unlisten;
|
||||
} catch (err) {
|
||||
console.error("[Layout] 监听启动完成事件失败:", err);
|
||||
console.error("[Layout] Failed to listen for startup completion event:", err);
|
||||
return () => {};
|
||||
}
|
||||
};
|
||||
|
||||
const checkImmediateInitialization = async () => {
|
||||
try {
|
||||
console.log("[Layout] 检查后端是否已就绪");
|
||||
console.log("[Layout] Check if the backend is ready");
|
||||
await invoke("update_ui_stage", { stage: "Loading" });
|
||||
|
||||
if (!hasEventTriggered && !isInitialized) {
|
||||
console.log("[Layout] 后端已就绪,立即开始初始化");
|
||||
console.log("[Layout] Backend is ready, start initialization immediately");
|
||||
hasEventTriggered = true;
|
||||
performInitialization();
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("[Layout] 后端尚未就绪,等待启动完成事件");
|
||||
console.log("[Layout] Backend not yet ready, waiting for startup completion event");
|
||||
}
|
||||
};
|
||||
|
||||
const backupInitialization = setTimeout(() => {
|
||||
if (!hasEventTriggered && !isInitialized) {
|
||||
console.warn("[Layout] 备用初始化触发:1.5秒内未开始初始化");
|
||||
console.warn("[Layout] Standby initialization trigger: initialization not started within 1.5 seconds");
|
||||
hasEventTriggered = true;
|
||||
performInitialization();
|
||||
}
|
||||
@@ -403,9 +403,9 @@ const Layout = () => {
|
||||
|
||||
const emergencyInitialization = setTimeout(() => {
|
||||
if (!isInitialized) {
|
||||
console.error("[Layout] 紧急初始化触发:5秒内未完成初始化");
|
||||
console.error("[Layout] Emergency initialization trigger: initialization not completed within 5 seconds");
|
||||
removeLoadingOverlay();
|
||||
notifyBackend("UI就绪").catch(() => {});
|
||||
notifyBackend("UI ready").catch(() => {});
|
||||
isInitialized = true;
|
||||
}
|
||||
}, 5000);
|
||||
@@ -421,10 +421,10 @@ const Layout = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 语言和起始页设置
|
||||
// Language and start page settings
|
||||
useEffect(() => {
|
||||
if (language) {
|
||||
dayjs.locale(language === "zh" ? "zh-cn" : language);
|
||||
dayjs.locale(language === "ru" ? "ru-ru" : language);
|
||||
i18next.changeLanguage(language);
|
||||
}
|
||||
}, [language]);
|
||||
|
||||
@@ -78,26 +78,13 @@ const MinimalHomePage: React.FC = () => {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleActivationEvent = (event: Event) => {
|
||||
const customEvent = event as CustomEvent<string>;
|
||||
const profileId = customEvent.detail;
|
||||
if (profileId) {
|
||||
setUidToActivate(profileId);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('activate-profile', handleActivationEvent);
|
||||
return () => {
|
||||
window.removeEventListener('activate-profile', handleActivationEvent);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const uidToActivate = sessionStorage.getItem('activateProfile');
|
||||
if (uidToActivate && profileItems.some(p => p.uid === uidToActivate)) {
|
||||
activateProfile(uidToActivate, false);
|
||||
setUidToActivate(null);
|
||||
sessionStorage.removeItem('activateProfile');
|
||||
}
|
||||
}, [uidToActivate, profileItems, activateProfile]);
|
||||
}, [profileItems, activateProfile]);
|
||||
|
||||
|
||||
const handleProfileChange = useLockFn(async (uid: string) => {
|
||||
if (profiles?.current === uid) return;
|
||||
@@ -240,14 +227,14 @@ const MinimalHomePage: React.FC = () => {
|
||||
href={currentProfile.announce_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 text-base font-semibold text-foreground hover:underline hover:opacity-80 transition-all"
|
||||
title={currentProfile.announce_url}
|
||||
className="inline-flex items-center gap-2 text-base font-semibold text-foreground hover:underline hover:opacity-80 transition-all whitespace-pre-wrap"
|
||||
title={currentProfile.announce_url.replace(/\\n/g, '\n')}
|
||||
>
|
||||
<span>{currentProfile.announce}</span>
|
||||
<span>{currentProfile.announce.replace(/\\n/g, '\n')}</span>
|
||||
<ExternalLink className="h-4 w-4 flex-shrink-0" />
|
||||
</a>
|
||||
) : (
|
||||
<p className="text-base font-semibold text-foreground">
|
||||
<p className="text-base font-semibold text-foreground whitespace-pre-wrap">
|
||||
{currentProfile.announce}
|
||||
</p>
|
||||
)}
|
||||
@@ -268,7 +255,7 @@ const MinimalHomePage: React.FC = () => {
|
||||
|
||||
<div className="scale-[7] my-16">
|
||||
<Switch
|
||||
disabled={showTunAlert || isToggling}
|
||||
disabled={showTunAlert || isToggling || profileItems.length === 0}
|
||||
checked={!!isProxyEnabled}
|
||||
onCheckedChange={handleToggleProxy}
|
||||
aria-label={t("Toggle Proxy")}
|
||||
|
||||
Reference in New Issue
Block a user