Compare commits

..

5 Commits

10 changed files with 131 additions and 71 deletions

View File

@@ -1,3 +1,43 @@
## v1.7.1
### Break Changes
- 更新后请务必重新导入所有订阅,包括 Remote 和 Local
- 此版本重构了 Merge/Script更新前请先备份好自定义 Merge 和 Script更新并不会删除配置文件但是旧版 Merge 和 Script 在更新后无法从前端访问,备份以防万一)
- Merge 改名为 `扩展配置`,分为 `全局扩展配置``订阅扩展配置`,全局扩展配置对所有订阅生效,订阅扩展配置只对关联的订阅生效
- Script 改名为 `扩展脚本`,同样分为 `全局扩展脚本``订阅扩展脚本`
- 订阅扩展配置在订阅右键菜单里进入
- 执行优先级为: 全局扩展配置 -> 全局扩展脚本 -> 订阅扩展配置 ->订阅扩展脚本
- 扩展配置删除了 `prepend/append` 能力,请使用 右键订阅 -> `编辑规则`/`编辑节点`/`编辑代理组` 来代替
- MacOS 用户更新后请重新安装服务模式
### Features
- 升级内核到 1.18.6
- 移除内核授权,改为服务模式实现
- 自动填充本地订阅名称
- 添加重大更新处理逻辑
- 订阅单独指定扩展配置/脚本(需要重新导入订阅)
- 添加可视化规则编辑器(需要重新导入订阅)
- 编辑器新增工具栏按钮(格式化、最大化/最小化)
- WEBUI 使用最新版 metacubex并解决无法自动登陆问问题
- 禁用部分 Webview2 快捷键
- 热键配置新增连接符 + 号
- 新增部分悬浮提示按钮,用于解释说明
- 当日志等级为`Debug`时(更改需重启软件生效),支持点击内存主动内存回收(绿色文字)
- 设置页面右上角新增 TG 频道链接
- 各种细节优化和界面性能优化
### Bugs Fixes
- 修复代理绕过格式检查
- 通过进程名称关闭进程
- 退出软件时恢复 DNS 设置
- 修复创建本地订阅时更新间隔无法保存
- 连接页面列宽无法调整
---
## v1.7.0
### Break Changes

View File

@@ -1,6 +1,6 @@
{
"name": "clash-verge",
"version": "1.7.0",
"version": "1.7.1",
"license": "GPL-3.0-only",
"scripts": {
"dev": "tauri dev",

2
src-tauri/Cargo.lock generated
View File

@@ -784,7 +784,7 @@ dependencies = [
[[package]]
name = "clash-verge"
version = "1.7.0"
version = "1.7.1"
dependencies = [
"anyhow",
"auto-launch",

View File

@@ -1,6 +1,6 @@
[package]
name = "clash-verge"
version = "1.7.0"
version = "1.7.1"
description = "clash verge"
authors = ["zzzgydi", "wonfen", "MystiPanda"]
license = "GPL-3.0-only"

View File

@@ -247,33 +247,6 @@ impl PrfItem {
let mut groups = opt_ref.and_then(|o| o.groups.clone());
let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
if merge.is_none() {
let merge_item = PrfItem::from_merge(None)?;
Config::profiles().data().append_item(merge_item.clone())?;
merge = merge_item.uid;
}
if script.is_none() {
let script_item = PrfItem::from_script(None)?;
Config::profiles().data().append_item(script_item.clone())?;
script = script_item.uid;
}
if rules.is_none() {
let rules_item = PrfItem::from_rules()?;
Config::profiles().data().append_item(rules_item.clone())?;
rules = rules_item.uid;
}
if proxies.is_none() {
let proxies_item = PrfItem::from_proxies()?;
Config::profiles()
.data()
.append_item(proxies_item.clone())?;
proxies = proxies_item.uid;
}
if groups.is_none() {
let groups_item = PrfItem::from_groups()?;
Config::profiles().data().append_item(groups_item.clone())?;
groups = groups_item.uid;
}
// 使用软件自己的代理
if self_proxy {
let port = Config::verge()
@@ -400,6 +373,34 @@ impl PrfItem {
bail!("profile does not contain `proxies` or `proxy-providers`");
}
if merge.is_none() {
let merge_item = PrfItem::from_merge(None)?;
Config::profiles().data().append_item(merge_item.clone())?;
merge = merge_item.uid;
}
if script.is_none() {
let script_item = PrfItem::from_script(None)?;
Config::profiles().data().append_item(script_item.clone())?;
script = script_item.uid;
}
if rules.is_none() {
let rules_item = PrfItem::from_rules()?;
Config::profiles().data().append_item(rules_item.clone())?;
rules = rules_item.uid;
}
if proxies.is_none() {
let proxies_item = PrfItem::from_proxies()?;
Config::profiles()
.data()
.append_item(proxies_item.clone())?;
proxies = proxies_item.uid;
}
if groups.is_none() {
let groups_item = PrfItem::from_groups()?;
Config::profiles().data().append_item(groups_item.clone())?;
groups = groups_item.uid;
}
Ok(PrfItem {
uid: Some(uid),
itype: Some("remote".into()),

View File

@@ -1,4 +1,4 @@
use super::prfitem::PrfItem;
use super::{prfitem::PrfItem, PrfOption};
use crate::utils::{dirs, help};
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
@@ -205,6 +205,7 @@ impl IProfiles {
each.extra = item.extra;
each.updated = item.updated;
each.home = item.home;
each.option = PrfOption::merge(each.option.clone(), item.option);
// save the file data
// move the field value after save
if let Some(file_data) = item.file_data.take() {

View File

@@ -2,7 +2,7 @@
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"package": {
"productName": "Clash Verge",
"version": "1.7.0"
"version": "1.7.1"
},
"build": {
"distDir": "../dist",

View File

@@ -71,7 +71,7 @@ export const ProfileItem = (props: Props) => {
const from = parseUrl(itemData.url);
const description = itemData.desc;
const expire = parseExpire(extra?.expire);
const progress = Math.round(((download + upload) * 100) / (total + 0.1));
const progress = Math.round(((download + upload) * 100) / (total + 0.01) + 1);
const loading = loadingCache[itemData.uid] ?? false;
@@ -211,27 +211,27 @@ export const ProfileItem = (props: Props) => {
{
label: "Edit Rules",
handler: onEditRules,
disabled: option?.rules === null,
disabled: !option?.rules,
},
{
label: "Edit Proxies",
handler: onEditProxies,
disabled: !option?.proxies,
},
{
label: "Edit Groups",
handler: onEditGroups,
disabled: !option?.groups,
},
// {
// label: "Edit Proxies",
// handler: onEditProxies,
// disabled: option?.proxies === null,
// },
// {
// label: "Edit Groups",
// handler: onEditGroups,
// disabled: option?.groups === null,
// },
{
label: "Extend Config",
handler: onEditMerge,
disabled: option?.merge === null,
disabled: !option?.merge,
},
{
label: "Extend Script",
handler: onEditScript,
disabled: option?.script === null,
disabled: !option?.script,
},
{ label: "Open File", handler: onOpenFile, disabled: false },
{ label: "Update", handler: () => onUpdate(0), disabled: false },
@@ -252,27 +252,27 @@ export const ProfileItem = (props: Props) => {
{
label: "Edit Rules",
handler: onEditRules,
disabled: option?.rules === null,
disabled: !option?.rules,
},
{
label: "Edit Proxies",
handler: onEditProxies,
disabled: !option?.proxies,
},
{
label: "Edit Groups",
handler: onEditGroups,
disabled: !option?.groups,
},
// {
// label: "Edit Proxies",
// handler: onEditProxies,
// disabled: option?.proxies === null,
// },
// {
// label: "Edit Groups",
// handler: onEditGroups,
// disabled: option?.groups === null,
// },
{
label: "Extend Config",
handler: onEditMerge,
disabled: option?.merge === null,
disabled: !option?.merge,
},
{
label: "Extend Script",
handler: onEditScript,
disabled: option?.script === null,
disabled: !option?.script,
},
{ label: "Open File", handler: onOpenFile, disabled: false },
{
@@ -429,7 +429,7 @@ export const ProfileItem = (props: Props) => {
<LinearProgress
variant="determinate"
value={progress}
style={{ opacity: progress > 0 ? 1 : 0 }}
style={{ opacity: total > 0 ? 1 : 0 }}
/>
</ProfileBox>

View File

@@ -18,9 +18,17 @@ interface Props {
export const RuleItem = (props: Props) => {
let { type, ruleRaw, onDelete } = props;
const sortable = type === "prepend" || type === "append";
const rule = ruleRaw.replace(",no-resolve", "").split(",");
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({ id: ruleRaw });
const { attributes, listeners, setNodeRef, transform, transition } = sortable
? useSortable({ id: ruleRaw })
: {
attributes: {},
listeners: {},
setNodeRef: null,
transform: null,
transition: null,
};
return (
<ListItem
sx={({ palette }) => ({

View File

@@ -1,4 +1,4 @@
import { ReactNode, useEffect, useState } from "react";
import { ReactNode, useEffect, useMemo, useState } from "react";
import { useLockFn } from "ahooks";
import yaml from "js-yaml";
import { useTranslation } from "react-i18next";
@@ -247,6 +247,11 @@ export const RulesEditorViewer = (props: Props) => {
const [appendSeq, setAppendSeq] = useState<string[]>([]);
const [deleteSeq, setDeleteSeq] = useState<string[]>([]);
const filteredRuleList = useMemo(
() => ruleList.filter((rule) => match(rule)),
[ruleList, match]
);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
@@ -482,7 +487,7 @@ export const RulesEditorViewer = (props: Props) => {
<Virtuoso
style={{ height: "calc(100% - 16px)", marginTop: "8px" }}
totalCount={
ruleList.length +
filteredRuleList.length +
(prependSeq.length > 0 ? 1 : 0) +
(appendSeq.length > 0 ? 1 : 0)
}
@@ -518,24 +523,29 @@ export const RulesEditorViewer = (props: Props) => {
</SortableContext>
</DndContext>
);
} else if (index < ruleList.length + shift) {
} else if (index < filteredRuleList.length + shift) {
let newIndex = index - shift;
return (
<RuleItem
key={`${ruleList[newIndex]}-${index}`}
key={`${filteredRuleList[newIndex]}-${index}`}
type={
deleteSeq.includes(ruleList[newIndex])
deleteSeq.includes(filteredRuleList[newIndex])
? "delete"
: "original"
}
ruleRaw={ruleList[newIndex]}
ruleRaw={filteredRuleList[newIndex]}
onDelete={() => {
if (deleteSeq.includes(ruleList[newIndex])) {
if (deleteSeq.includes(filteredRuleList[newIndex])) {
setDeleteSeq(
deleteSeq.filter((v) => v !== ruleList[newIndex])
deleteSeq.filter(
(v) => v !== filteredRuleList[newIndex]
)
);
} else {
setDeleteSeq((prev) => [...prev, ruleList[newIndex]]);
setDeleteSeq((prev) => [
...prev,
filteredRuleList[newIndex],
]);
}
}}
/>