diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs
index 5a1ea759..c75715ae 100644
--- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs
+++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs
@@ -924,6 +924,42 @@ namespace ServiceLib.Resx {
}
}
+ ///
+ /// 查找类似 Copy 的本地化字符串。
+ ///
+ public static string menuEditCopy {
+ get {
+ return ResourceManager.GetString("menuEditCopy", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 Format 的本地化字符串。
+ ///
+ public static string menuEditFormat {
+ get {
+ return ResourceManager.GetString("menuEditFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 Paste 的本地化字符串。
+ ///
+ public static string menuEditPaste {
+ get {
+ return ResourceManager.GetString("menuEditPaste", resourceCulture);
+ }
+ }
+
+ ///
+ /// 查找类似 Select all 的本地化字符串。
+ ///
+ public static string menuEditSelectAll {
+ get {
+ return ResourceManager.GetString("menuEditSelectAll", resourceCulture);
+ }
+ }
+
///
/// 查找类似 Edit 的本地化字符串。
///
diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx
index 1dd09e85..30f663e8 100644
--- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx
+++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx
@@ -1668,4 +1668,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
Group by Region
+
+ کپی
+
+
+ انتخاب همه
+
+
+ Paste
+
+
+ Format
+
\ No newline at end of file
diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx
index c873db43..061d6dad 100644
--- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx
+++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx
@@ -1665,4 +1665,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
Group by Region
+
+ Copier
+
+
+ Tout sélect
+
+
+ Paste
+
+
+ Format
+
\ No newline at end of file
diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx
index f59a652b..4d652587 100644
--- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx
+++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx
@@ -1668,4 +1668,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
Group by Region
+
+ Másolás
+
+
+ Összes kijelölése
+
+
+ Paste
+
+
+ Format
+
\ No newline at end of file
diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx
index f299a877..787e7bd9 100644
--- a/v2rayN/ServiceLib/Resx/ResUI.resx
+++ b/v2rayN/ServiceLib/Resx/ResUI.resx
@@ -1668,4 +1668,16 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
Group by Region
+
+ Copy
+
+
+ Select all
+
+
+ Paste
+
+
+ Format
+
\ No newline at end of file
diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx
index a20fa3b1..ba2ef074 100644
--- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx
+++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx
@@ -1668,4 +1668,16 @@
Группировка по регионам
+
+ Скопировать
+
+
+ Выбрать все
+
+
+ Paste
+
+
+ Format
+
diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx
index d655c30d..707609d0 100644
--- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx
+++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx
@@ -1665,4 +1665,16 @@
按地区分组
+
+ 复制
+
+
+ 全选
+
+
+ 粘贴
+
+
+ 格式化
+
\ No newline at end of file
diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx
index 65b1dbfb..4e417308 100644
--- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx
+++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx
@@ -1665,4 +1665,16 @@
按區域分組
+
+ 複製
+
+
+ 全選
+
+
+ Paste
+
+
+ Format
+
\ No newline at end of file
diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml
index 477eca5d..e9ded8aa 100644
--- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml
+++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml
@@ -5,6 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
+ xmlns:views="clr-namespace:v2rayN.Desktop.Views"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuServers}"
Width="900"
@@ -658,16 +659,13 @@
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TransportExtraTip}" />
-
+ VerticalAlignment="Center" />
@@ -749,13 +747,13 @@
-
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Center" />
diff --git a/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml
index 705e79e2..3335785b 100644
--- a/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml
+++ b/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml
@@ -3,6 +3,7 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:local="using:v2rayN.Desktop.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
@@ -399,12 +400,7 @@
BorderBrush="Gray"
BorderThickness="1"
Header="HTTP/SOCKS">
-
+
@@ -473,12 +469,7 @@
BorderBrush="Gray"
BorderThickness="1"
Header="HTTP/SOCKS">
-
+
@@ -488,12 +479,7 @@
BorderBrush="Gray"
BorderThickness="1"
Header="{x:Static resx:ResUI.TbSettingsTunMode}">
-
+
diff --git a/v2rayN/v2rayN.Desktop/Views/FullConfigTemplateWindow.axaml b/v2rayN/v2rayN.Desktop/Views/FullConfigTemplateWindow.axaml
index 63f29ed0..1a2b482c 100644
--- a/v2rayN/v2rayN.Desktop/Views/FullConfigTemplateWindow.axaml
+++ b/v2rayN/v2rayN.Desktop/Views/FullConfigTemplateWindow.axaml
@@ -5,6 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
+ xmlns:views="clr-namespace:v2rayN.Desktop.Views"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuFullConfigTemplate}"
Width="900"
@@ -94,12 +95,7 @@
BorderBrush="Gray"
BorderThickness="1"
Header="xray config template json">
-
+
@@ -166,12 +162,7 @@
BorderBrush="Gray"
BorderThickness="1"
Header="sing-box config template json">
-
+
@@ -181,12 +172,7 @@
BorderBrush="Gray"
BorderThickness="1"
Header="sing-box tun config template json">
-
+
diff --git a/v2rayN/v2rayN.Desktop/Views/JsonEditor.axaml b/v2rayN/v2rayN.Desktop/Views/JsonEditor.axaml
new file mode 100644
index 00000000..cad0021d
--- /dev/null
+++ b/v2rayN/v2rayN.Desktop/Views/JsonEditor.axaml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/v2rayN/v2rayN.Desktop/Views/JsonEditor.axaml.cs b/v2rayN/v2rayN.Desktop/Views/JsonEditor.axaml.cs
new file mode 100644
index 00000000..98620126
--- /dev/null
+++ b/v2rayN/v2rayN.Desktop/Views/JsonEditor.axaml.cs
@@ -0,0 +1,96 @@
+using System.Text.Json;
+using System.Xml;
+using AvaloniaEdit.Highlighting;
+using AvaloniaEdit.Highlighting.Xshd;
+
+namespace v2rayN.Desktop.Views;
+
+public partial class JsonEditor : UserControl
+{
+ private static readonly JsonSerializerOptions SIndentedOptions = new() { WriteIndented = true };
+
+ private static readonly Lazy SHighlightingDark =
+ new(() => BuildHighlighting(dark: true), isThreadSafe: true);
+
+ private static readonly Lazy SHighlightingLight =
+ new(() => BuildHighlighting(dark: false), isThreadSafe: true);
+
+ public static readonly StyledProperty TextProperty =
+ AvaloniaProperty.Register(nameof(Text), defaultValue: string.Empty);
+
+ public string Text
+ {
+ get => GetValue(TextProperty);
+ set => SetValue(TextProperty, value);
+ }
+
+ public JsonEditor()
+ {
+ InitializeComponent();
+ var isDark = Application.Current?.ActualThemeVariant != ThemeVariant.Light;
+ Editor.SyntaxHighlighting = isDark ? SHighlightingDark.Value : SHighlightingLight.Value;
+ Editor.TextArea.TextView.Options.EnableHyperlinks = false;
+
+ Editor.TextChanged += (_, _) =>
+ {
+ if (Text != Editor.Text)
+ {
+ SetCurrentValue(TextProperty, Editor.Text);
+ }
+ };
+
+ this.GetObservable(TextProperty).Subscribe(text =>
+ {
+ if (Editor.Text != text)
+ {
+ Editor.Text = text ?? string.Empty;
+ }
+ });
+ }
+
+ private static IHighlightingDefinition BuildHighlighting(bool dark)
+ {
+ var keyColor = dark ? "#9CDCFE" : "#0451A5";
+ var strColor = dark ? "#CE9178" : "#A31515";
+ var numColor = dark ? "#B5CEA8" : "#098658";
+ var kwColor = dark ? "#569CD6" : "#0000FF";
+ var xshd = $"""
+
+
+
+
+
+
+
+ "([^"\\]|\\.)*"(?=\s*:)
+ "([^"\\]|\\.)*"
+ -?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?
+
+ true
+ false
+ null
+
+
+
+ """;
+ using var reader = XmlReader.Create(new StringReader(xshd));
+ return HighlightingLoader.Load(reader, HighlightingManager.Instance);
+ }
+
+ private void FormatJson_Click(object? sender, RoutedEventArgs e)
+ {
+ try
+ {
+ var obj = JsonUtils.ParseJson(Editor.Text);
+ Editor.Text = JsonUtils.Serialize(obj, SIndentedOptions);
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ private void Copy_Click(object? sender, RoutedEventArgs e) => Editor.Copy();
+ private void Paste_Click(object? sender, RoutedEventArgs e) => Editor.Paste();
+ private void SelectAll_Click(object? sender, RoutedEventArgs e) => Editor.SelectAll();
+}
diff --git a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml
index 90dcfeb7..aabe80fa 100644
--- a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml
+++ b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml
@@ -79,8 +79,8 @@
+ Header="{x:Static resx:ResUI.menuMsgViewSelectAll}"
+ InputGesture="Ctrl+A" />