Compare commits
5 Commits
40
UPDATELOG.md
40
UPDATELOG.md
@@ -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
|
||||
|
||||
@@ -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
2
src-tauri/Cargo.lock
generated
@@ -784,7 +784,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clash-verge"
|
||||
version = "1.7.0"
|
||||
version = "1.7.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"auto-launch",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 }) => ({
|
||||
|
||||
@@ -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],
|
||||
]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user