Compare commits

..

No commits in common. "5fbcc46013b8389fc5b0be5773c65129d6daaaee" and "a2929c60865de25b7d9dddd7787bde743668033c" have entirely different histories.

16 changed files with 120 additions and 329 deletions

View file

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>7.19.5</Version>
<Version>7.19.4</Version>
</PropertyGroup>
<PropertyGroup>

View file

@ -13,7 +13,7 @@
<PackageVersion Include="CliWrap" Version="3.10.0" />
<PackageVersion Include="Downloader" Version="5.1.0" />
<PackageVersion Include="H.NotifyIcon.Wpf" Version="2.4.1" />
<PackageVersion Include="MaterialDesignThemes" Version="5.3.1" />
<PackageVersion Include="MaterialDesignThemes" Version="5.3.0" />
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.1.1" />
<PackageVersion Include="QRCoder" Version="1.7.0" />
<PackageVersion Include="ReactiveUI" Version="23.1.8" />

View file

@ -924,42 +924,6 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Copy 的本地化字符串。
/// </summary>
public static string menuEditCopy {
get {
return ResourceManager.GetString("menuEditCopy", resourceCulture);
}
}
/// <summary>
/// 查找类似 Format 的本地化字符串。
/// </summary>
public static string menuEditFormat {
get {
return ResourceManager.GetString("menuEditFormat", resourceCulture);
}
}
/// <summary>
/// 查找类似 Paste 的本地化字符串。
/// </summary>
public static string menuEditPaste {
get {
return ResourceManager.GetString("menuEditPaste", resourceCulture);
}
}
/// <summary>
/// 查找类似 Select all 的本地化字符串。
/// </summary>
public static string menuEditSelectAll {
get {
return ResourceManager.GetString("menuEditSelectAll", resourceCulture);
}
}
/// <summary>
/// 查找类似 Edit 的本地化字符串。
/// </summary>

View file

@ -1668,16 +1668,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuGenRegionGroup" xml:space="preserve">
<value>Group by Region</value>
</data>
<data name="menuEditCopy" xml:space="preserve">
<value>کپی</value>
</data>
<data name="menuEditSelectAll" xml:space="preserve">
<value>انتخاب همه</value>
</data>
<data name="menuEditPaste" xml:space="preserve">
<value>Paste</value>
</data>
<data name="menuEditFormat" xml:space="preserve">
<value>Format</value>
</data>
</root>

View file

@ -1665,16 +1665,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuGenRegionGroup" xml:space="preserve">
<value>Group by Region</value>
</data>
<data name="menuEditCopy" xml:space="preserve">
<value>Copier</value>
</data>
<data name="menuEditSelectAll" xml:space="preserve">
<value>Tout sélect</value>
</data>
<data name="menuEditPaste" xml:space="preserve">
<value>Paste</value>
</data>
<data name="menuEditFormat" xml:space="preserve">
<value>Format</value>
</data>
</root>

View file

@ -1668,16 +1668,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuGenRegionGroup" xml:space="preserve">
<value>Group by Region</value>
</data>
<data name="menuEditCopy" xml:space="preserve">
<value>Másolás</value>
</data>
<data name="menuEditSelectAll" xml:space="preserve">
<value>Összes kijelölése</value>
</data>
<data name="menuEditPaste" xml:space="preserve">
<value>Paste</value>
</data>
<data name="menuEditFormat" xml:space="preserve">
<value>Format</value>
</data>
</root>

View file

@ -1668,16 +1668,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuGenRegionGroup" xml:space="preserve">
<value>Group by Region</value>
</data>
<data name="menuEditCopy" xml:space="preserve">
<value>Copy</value>
</data>
<data name="menuEditSelectAll" xml:space="preserve">
<value>Select all</value>
</data>
<data name="menuEditPaste" xml:space="preserve">
<value>Paste</value>
</data>
<data name="menuEditFormat" xml:space="preserve">
<value>Format</value>
</data>
</root>

View file

@ -1027,7 +1027,7 @@
<value>Протокол Mux для sing-box</value>
</data>
<data name="TbRoutingRuleProcess" xml:space="preserve">
<value>Процесс (Linux/Windows)</value>
<value>Process (Linux/Windows)</value>
</data>
<data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP-адрес или сеть CIDR</value>
@ -1393,10 +1393,10 @@
<value>Внутренний DNS</value>
</data>
<data name="TbDirectResolveStrategy" xml:space="preserve">
<value>Стратегия разрешения прямых соединений</value>
<value>Direct Target Resolution Strategy</value>
</data>
<data name="TbRemoteResolveStrategy" xml:space="preserve">
<value>Стратегия разрешения прокси-соединений</value>
<value>Proxy Target Resolution Strategy</value>
</data>
<data name="TbAddCommonDNSHosts" xml:space="preserve">
<value>Добавить стандартные записи hosts (DNS)</value>
@ -1429,7 +1429,7 @@
<value>Включён пользовательский DNS — настройки на этой странице не применяются</value>
</data>
<data name="TbBlockSVCBHTTPSQueriesTips" xml:space="preserve">
<value>При включении блокирует проверки доступности ECH и HTTP/3</value>
<value>Block ECH and HTTP/3 availability checks when enabled</value>
</data>
<data name="FillCorrectConfigTemplateText" xml:space="preserve">
<value>Пожалуйста, заполните корректный шаблон конфигурации</value>
@ -1462,127 +1462,127 @@
<value>Эта функция предназначена для продвинутых пользователей и особых случаев. После включения игнорируются базовые настройки ядра, DNS и маршрутизации. Вы должны самостоятельно корректно задать порт системного прокси, учёт трафика и другие связанные параметры — всё настраивается вручную.</value>
</data>
<data name="MsgStartParsingSubscription" xml:space="preserve">
<value>Начинается разбор и обработка содержимого подписки</value>
<value>Start parsing and processing subscription content</value>
</data>
<data name="TbSelectProfile" xml:space="preserve">
<value>Выбрать профиль</value>
<value>Select Profile</value>
</data>
<data name="TbFakeIPTips" xml:space="preserve">
<value>По умолчанию применяется глобально, со встроенной фильтрацией FakeIP (только sing-box).</value>
<value>Applies globally by default, with built-in FakeIP filtering (sing-box only).</value>
</data>
<data name="PleaseAddAtLeastOneServer" xml:space="preserve">
<value>Добавьте хотя бы одну конфигурацию</value>
<value>Please Add At Least One Configuration</value>
</data>
<data name="TbConfigTypePolicyGroup" xml:space="preserve">
<value>Группа политик</value>
<value>Policy Group</value>
</data>
<data name="TbConfigTypeProxyChain" xml:space="preserve">
<value>Цепочка прокси</value>
<value>Proxy Chain</value>
</data>
<data name="TbLeastPing" xml:space="preserve">
<value>Наименьшая задержка</value>
<value>Lowest Latency</value>
</data>
<data name="TbRandom" xml:space="preserve">
<value>Случайный</value>
<value>Random</value>
</data>
<data name="TbRoundRobin" xml:space="preserve">
<value>Циклический (Round Robin)</value>
<value>Round Robin</value>
</data>
<data name="TbLeastLoad" xml:space="preserve">
<value>Наиболее стабильный</value>
<value>Most Stable</value>
</data>
<data name="TbPolicyGroupType" xml:space="preserve">
<value>Тип группы политик</value>
<value>Policy Group Type</value>
</data>
<data name="menuAddPolicyGroupServer" xml:space="preserve">
<value>Добавить группу политик </value>
<value>Add Policy Group Configuration</value>
</data>
<data name="menuAddProxyChainServer" xml:space="preserve">
<value>Добавить цепочку прокси</value>
<value>Add Proxy Chain Configuration</value>
</data>
<data name="menuAddChildServer" xml:space="preserve">
<value>Добавить дочернюю конфигурацию </value>
<value>Add Child Configuration</value>
</data>
<data name="menuRemoveChildServer" xml:space="preserve">
<value>Удалить дочернюю конфигурацию </value>
<value>Remove Child Configuration</value>
</data>
<data name="menuServerList" xml:space="preserve">
<value>Конфигурация 1: автодобавление из группы подписки</value>
<value>Configuration item 1, Auto add from subscription group</value>
</data>
<data name="TbFallback" xml:space="preserve">
<value>Резервный (Fallback)</value>
<value>Fallback</value>
</data>
<data name="MsgCoreNotSupportNetwork" xml:space="preserve">
<value>Ядро «{0}» не поддерживает тип сети «{1}»</value>
<value>Core '{0}' does not support network type '{1}'</value>
</data>
<data name="MsgCoreNotSupportProtocolTransport" xml:space="preserve">
<value>Ядро «{0}» не поддерживает протокол «{1}» при транспорте «{2}»</value>
<value>Core '{0}' does not support protocol '{1}' when using transport '{2}'</value>
</data>
<data name="MsgCoreNotSupportProtocol" xml:space="preserve">
<value>Ядро «{0}» не поддерживает протокол «{1}»</value>
<value>Core '{0}' does not support protocol '{1}'</value>
</data>
<data name="MsgInvalidProperty" xml:space="preserve">
<value>Свойство {0} недопустимо, проверьте его</value>
<value>The {0} property is invalid, please check</value>
</data>
<data name="MsgNotSupportProtocol" xml:space="preserve">
<value>Протокол «{0}» не поддерживается</value>
<value>Not support protocol '{0}'</value>
</data>
<data name="TbSettingsHide2TrayWhenCloseTip" xml:space="preserve">
<value>Если в системе нет функции трея, не включайте эту опцию</value>
<value>If the system does not have a tray function, please do not enable it</value>
</data>
<data name="TbRuleTypeTips" xml:space="preserve">
<value>Можно задать отдельные правила для маршрутизации и DNS или выбрать «ALL» для применения к обоим</value>
<value>You can set separate rules for Routing and DNS, or select "ALL" to apply to both</value>
</data>
<data name="TbRuleType" xml:space="preserve">
<value>Тип правила</value>
<value>Rule Type</value>
</data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>Разрешает домены DNS-серверов, требуется IP-адрес</value>
<value>Resolve DNS server domains, requires IP</value>
</data>
<data name="menuFastRealPing" xml:space="preserve">
<value>Тест реальной задержки</value>
<value>Test real delay</value>
</data>
<data name="TbPolicyGroupSubChildTip" xml:space="preserve">
<value>Автодобавление отфильтрованных конфигураций из групп подписки</value>
<value>Auto add filtered configuration from subscription groups</value>
</data>
<data name="TbCertPinning" xml:space="preserve">
<value>Привязка сертификата</value>
<value>Certificate Pinning</value>
</data>
<data name="TbCertPinningTips" xml:space="preserve">
<value>Привязанный сертификат (заполните любое из полей)
При указании сертификат будет привязан, а «Разрешить небезопасные» отключится.
<value>Pinned certificate (fill in either one)
When specified, the certificate will be pinned, and "Allow Insecure" will be disabled.
Получение сертификата может завершиться неудачей при использовании самоподписанного сертификата или при наличии ненадёжного / вредоносного ЦС в системе.</value>
The "Get Certificate" action may fail if a self-signed certificate is used or if the system contains an untrusted or malicious CA.</value>
</data>
<data name="TbFetchCert" xml:space="preserve">
<value>Получить сертификат</value>
<value>Fetch Certificate</value>
</data>
<data name="TbFetchCertChain" xml:space="preserve">
<value>Получить цепочку сертификатов</value>
<value>Fetch Certificate Chain</value>
</data>
<data name="ServerNameMustBeValidDomain" xml:space="preserve">
<value>Укажите корректный домен</value>
<value>Please set a valid domain</value>
</data>
<data name="CertNotSet" xml:space="preserve">
<value>Сертификат не задан</value>
<value>Certificate not set</value>
</data>
<data name="CertSet" xml:space="preserve">
<value>Сертификат задан</value>
<value>Certificate set</value>
</data>
<data name="TbSettingsCustomSystemProxyPacPath" xml:space="preserve">
<value>Путь к пользовательскому PAC-файлу</value>
<value>Custom PAC file path</value>
</data>
<data name="TbSettingsCustomSystemProxyScriptPath" xml:space="preserve">
<value>Путь к скрипту системного прокси</value>
<value>Custom system proxy script file path</value>
</data>
<data name="TbSettingsMacOSShowInDock" xml:space="preserve">
<value>Отображать в Dock на macOS (требуется перезапуск)</value>
<value>macOS displays this in the Dock (requires restart)</value>
</data>
<data name="menuServerList2" xml:space="preserve">
<value>Конфигурация 2: выбор и добавление из собственных</value>
<value>Configuration Item 2, Select and add from self-built</value>
</data>
<data name="TbEchConfigList" xml:space="preserve">
<value>EchConfigList</value>
@ -1591,93 +1591,81 @@
<value>EchForceQuery</value>
</data>
<data name="TbFullCertTips" xml:space="preserve">
<value>Полный сертификат (цепочка) в формате PEM</value>
<value>Full certificate (chain), PEM format</value>
</data>
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Отпечаток сертификата (SHA-256)</value>
<value>Certificate fingerprint (SHA-256)</value>
</data>
<data name="TbServeStale" xml:space="preserve">
<value>Отдавать устаревшие записи (Serve Stale)</value>
<value>Serve Stale</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>Параллельные запросы</value>
<value>Parallel Query</value>
</data>
<data name="TbDomesticDNSTips" xml:space="preserve">
<value>По умолчанию используется только при разрешении имён в процессе маршрутизации</value>
<value>By default, invoked only during routing for resolution</value>
</data>
<data name="TbRemoteDNSTips" xml:space="preserve">
<value>По умолчанию используется только при разрешении имён в процессе маршрутизации; убедитесь, что удалённый сервер может достичь этого DNS</value>
<value>By default, invoked only during routing for resolution; ensure the remote server can reach this DNS</value>
</data>
<data name="TbDirectResolveStrategyTips" xml:space="preserve">
<value>Если не задано или «AsIs», используется системный DNS; иначе — встроенный DNS-модуль.</value>
<value>If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used.</value>
</data>
<data name="TbRemoteResolveStrategyTips" xml:space="preserve">
<value>Если не задано или «AsIs», разрешение DNS выполняется DNS удалённого сервера; иначе — встроенный DNS-модуль.</value>
<value>If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used.</value>
</data>
<data name="TbHopInt7" xml:space="preserve">
<value>Интервал смены портов (Port Hopping)</value>
<value>Port hopping interval</value>
</data>
<data name="menuServerListPreview" xml:space="preserve">
<value>Предпросмотр конфигурации</value>
<value>Configuration item preview</value>
</data>
<data name="TbFinalmask" xml:space="preserve">
<value>Finalmask</value>
</data>
<data name="MsgRoutingRuleOutboundNodeWarning" xml:space="preserve">
<value>Правило маршрутизации {0}, исходящий узел {1}, предупреждение: {2}</value>
<value>Routing rule {0} outbound node {1} warning: {2}</value>
</data>
<data name="MsgRoutingRuleOutboundNodeError" xml:space="preserve">
<value>Правило маршрутизации {0}, исходящий узел {1}, ошибка: {2}. Используется только прокси-узел.</value>
<value>Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only.</value>
</data>
<data name="MsgGroupCycleDependency" xml:space="preserve">
<value>Группа {0} имеет циклическую зависимость на дочерний узел {1}. Узел пропущен.</value>
<value>Group {0} has a cycle dependency on child node {1}. Skipping this node.</value>
</data>
<data name="MsgGroupChildNodeWarning" xml:space="preserve">
<value>Группа {0}: предупреждение дочернего узла {1}: {2}</value>
<value>Group {0} child node {1} warning: {2}</value>
</data>
<data name="MsgGroupChildNodeError" xml:space="preserve">
<value>Группа {0}: ошибка дочернего узла {1}: {2}. Узел пропущен.</value>
<value>Group {0} child node {1} error: {2}. Skipping this node.</value>
</data>
<data name="MsgGroupChildGroupNodeWarning" xml:space="preserve">
<value>Группа {0}: предупреждение дочернего узла группы {1}: {2}</value>
<value>Group {0} child group node {1} warning: {2}</value>
</data>
<data name="MsgGroupChildGroupNodeError" xml:space="preserve">
<value>Группа {0}: ошибка дочернего узла группы {1}: {2}. Узел пропущен.</value>
<value>Group {0} child group node {1} error: {2}. Skipping this node.</value>
</data>
<data name="MsgGroupNoValidChildNode" xml:space="preserve">
<value>У группы {0} нет допустимых дочерних узлов.</value>
<value>Group {0} has no valid child node.</value>
</data>
<data name="MsgRoutingRuleEmptyOutboundTag" xml:space="preserve">
<value>У правила маршрутизации {0} пустой исходящий тег. Используется только прокси-узел.</value>
<value>Routing rule {0} has an empty outbound tag. Fallback to proxy node only.</value>
</data>
<data name="MsgRoutingRuleOutboundNodeNotFound" xml:space="preserve">
<value>Правило маршрутизации {0}, исходящий узел {1} не найден. Используется только прокси-узел.</value>
<value>Routing rule {0} outbound node {1} not found. Fallback to proxy node only.</value>
</data>
<data name="MsgSubscriptionPrevProfileNotFound" xml:space="preserve">
<value>Предыдущий прокси подписки {0} не найден. Пропущено.</value>
<value>Subscription previous proxy {0} not found. Skipping.</value>
</data>
<data name="MsgSubscriptionNextProfileNotFound" xml:space="preserve">
<value>Следующий прокси подписки {0} не найден. Пропущено.</value>
<value>Subscription next proxy {0} not found. Skipping.</value>
</data>
<data name="menuGenGroupServer" xml:space="preserve">
<value>Сгенерировать группу политик</value>
<value>Generate Policy Group</value>
</data>
<data name="menuAllServers" xml:space="preserve">
<value>Все конфигурации</value>
<value>All configurations</value>
</data>
<data name="menuGenRegionGroup" xml:space="preserve">
<value>Группировка по регионам</value>
<value>Group by Region</value>
</data>
<data name="menuEditCopy" xml:space="preserve">
<value>Скопировать</value>
</data>
<data name="menuEditSelectAll" xml:space="preserve">
<value>Выбрать все</value>
</data>
<data name="menuEditPaste" xml:space="preserve">
<value>Paste</value>
</data>
<data name="menuEditFormat" xml:space="preserve">
<value>Format</value>
</data>
</root>
</root>

View file

@ -1665,16 +1665,4 @@
<data name="menuGenRegionGroup" xml:space="preserve">
<value>按地区分组</value>
</data>
<data name="menuEditCopy" xml:space="preserve">
<value>复制</value>
</data>
<data name="menuEditSelectAll" xml:space="preserve">
<value>全选</value>
</data>
<data name="menuEditPaste" xml:space="preserve">
<value>粘贴</value>
</data>
<data name="menuEditFormat" xml:space="preserve">
<value>格式化</value>
</data>
</root>

View file

@ -1665,16 +1665,4 @@
<data name="menuGenRegionGroup" xml:space="preserve">
<value>按區域分組</value>
</data>
<data name="menuEditCopy" xml:space="preserve">
<value>複製</value>
</data>
<data name="menuEditSelectAll" xml:space="preserve">
<value>全選</value>
</data>
<data name="menuEditPaste" xml:space="preserve">
<value>Paste</value>
</data>
<data name="menuEditFormat" xml:space="preserve">
<value>Format</value>
</data>
</root>

View file

@ -5,7 +5,6 @@
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"
@ -659,13 +658,16 @@
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TransportExtraTip}" />
<views:JsonEditor
<TextBox
x:Name="txtExtra"
Width="400"
MinHeight="100"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center" />
VerticalAlignment="Center"
Classes="TextArea"
MinLines="6"
TextWrapping="Wrap" />
</StackPanel>
</Flyout>
</Button.Flyout>
@ -747,13 +749,13 @@
<Button.Flyout>
<Flyout>
<StackPanel>
<views:JsonEditor
<TextBox
x:Name="txtFinalmask"
Width="400"
MinHeight="100"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center" />
AcceptsReturn="True"
Classes="TextArea"
TextWrapping="NoWrap" />
</StackPanel>
</Flyout>
</Button.Flyout>

View file

@ -3,7 +3,6 @@
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"
@ -400,7 +399,12 @@
BorderBrush="Gray"
BorderThickness="1"
Header="HTTP/SOCKS">
<local:JsonEditor Name="txtnormalDNSCompatible" VerticalAlignment="Stretch" />
<TextBox
Name="txtnormalDNSCompatible"
VerticalAlignment="Stretch"
Classes="TextArea"
MinLines="10"
TextWrapping="Wrap" />
</HeaderedContentControl>
</DockPanel>
</TabItem>
@ -469,7 +473,12 @@
BorderBrush="Gray"
BorderThickness="1"
Header="HTTP/SOCKS">
<local:JsonEditor Name="txtnormalDNS2Compatible" VerticalAlignment="Stretch" />
<TextBox
Name="txtnormalDNS2Compatible"
VerticalAlignment="Stretch"
Classes="TextArea"
MinLines="10"
TextWrapping="Wrap" />
</HeaderedContentControl>
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" />
@ -479,7 +488,12 @@
BorderBrush="Gray"
BorderThickness="1"
Header="{x:Static resx:ResUI.TbSettingsTunMode}">
<local:JsonEditor Name="txttunDNS2Compatible" VerticalAlignment="Stretch" />
<TextBox
Name="txttunDNS2Compatible"
VerticalAlignment="Stretch"
Classes="TextArea"
MinLines="10"
TextWrapping="Wrap" />
</HeaderedContentControl>
</Grid>

View file

@ -5,7 +5,6 @@
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"
@ -95,7 +94,12 @@
BorderBrush="Gray"
BorderThickness="1"
Header="xray config template json">
<views:JsonEditor x:Name="rayFullConfigTemplate" VerticalAlignment="Stretch" />
<TextBox
x:Name="rayFullConfigTemplate"
VerticalAlignment="Stretch"
Classes="TextArea"
MinLines="10"
TextWrapping="Wrap" />
</HeaderedContentControl>
</DockPanel>
</TabItem>
@ -162,7 +166,12 @@
BorderBrush="Gray"
BorderThickness="1"
Header="sing-box config template json">
<views:JsonEditor x:Name="sbFullConfigTemplate" VerticalAlignment="Stretch" />
<TextBox
x:Name="sbFullConfigTemplate"
VerticalAlignment="Stretch"
Classes="TextArea"
MinLines="10"
TextWrapping="Wrap" />
</HeaderedContentControl>
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" />
@ -172,7 +181,12 @@
BorderBrush="Gray"
BorderThickness="1"
Header="sing-box tun config template json">
<views:JsonEditor x:Name="sbFullTunConfigTemplate" VerticalAlignment="Stretch" />
<TextBox
x:Name="sbFullTunConfigTemplate"
VerticalAlignment="Stretch"
Classes="TextArea"
MinLines="10"
TextWrapping="Wrap" />
</HeaderedContentControl>
</Grid>
</DockPanel>

View file

@ -1,23 +0,0 @@
<UserControl
x:Class="v2rayN.Desktop.Views.JsonEditor"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ae="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib">
<ae:TextEditor
Name="Editor"
FontFamily="Cascadia Code,Consolas,Monospace"
FontSize="14"
ShowLineNumbers="True">
<ae:TextEditor.ContextMenu>
<ContextMenu>
<MenuItem Click="FormatJson_Click" Header="{x:Static resx:ResUI.menuEditFormat}" />
<Separator />
<MenuItem Click="Copy_Click" Header="{x:Static resx:ResUI.menuEditCopy}" />
<MenuItem Click="Paste_Click" Header="{x:Static resx:ResUI.menuEditPaste}" />
<MenuItem Click="SelectAll_Click" Header="{x:Static resx:ResUI.menuEditSelectAll}" />
</ContextMenu>
</ae:TextEditor.ContextMenu>
</ae:TextEditor>
</UserControl>

View file

@ -1,96 +0,0 @@
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<IHighlightingDefinition> SHighlightingDark =
new(() => BuildHighlighting(dark: true), isThreadSafe: true);
private static readonly Lazy<IHighlightingDefinition> SHighlightingLight =
new(() => BuildHighlighting(dark: false), isThreadSafe: true);
public static readonly StyledProperty<string> TextProperty =
AvaloniaProperty.Register<JsonEditor, string>(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 = $"""
<?xml version="1.0"?>
<SyntaxDefinition name="JSON" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
<Color name="Key" foreground="{keyColor}" />
<Color name="String" foreground="{strColor}" />
<Color name="Number" foreground="{numColor}" />
<Color name="Keyword" foreground="{kwColor}" fontWeight="bold" />
<RuleSet>
<Rule color="Key">"([^"\\]|\\.)*"(?=\s*:)</Rule>
<Rule color="String">"([^"\\]|\\.)*"</Rule>
<Rule color="Number">-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?</Rule>
<Keywords color="Keyword">
<Word>true</Word>
<Word>false</Word>
<Word>null</Word>
</Keywords>
</RuleSet>
</SyntaxDefinition>
""";
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();
}

View file

@ -79,8 +79,8 @@
<MenuItem
x:Name="menuMsgViewSelectAll"
Click="menuMsgViewSelectAll_Click"
Header="{x:Static resx:ResUI.menuMsgViewSelectAll}"
InputGesture="Ctrl+A" />
InputGesture="Ctrl+A"
Header="{x:Static resx:ResUI.menuMsgViewSelectAll}" />
<MenuItem
x:Name="menuMsgViewCopy"
Click="menuMsgViewCopy_Click"