From 144b7fcfc2801615d2ba364e37f14694492110bd Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 8 Jun 2025 17:18:23 +0800 Subject: [PATCH] :sparkles: Account contacts APIs :lipstick: Redesign emails --- DysonNetwork.Sphere/Account/Account.cs | 1 + .../Account/AccountController.cs | 3 +- .../Account/AccountCurrentController.cs | 104 +++++- DysonNetwork.Sphere/Account/AccountService.cs | 100 +++++- .../Account/MagicSpellService.cs | 37 +- DysonNetwork.Sphere/Email/EmailModels.cs | 6 + .../Pages/Emails/AccountDeletionEmail.razor | 79 ++--- .../Emails/ContactVerificationEmail.razor | 43 +++ .../Pages/Emails/EmailLayout.razor | 330 +++++++++++++++++- .../Pages/Emails/LandingEmail.razor | 78 ++--- .../Pages/Emails/PasswordResetEmail.razor | 77 ++-- .../Pages/Emails/VerificationEmail.razor | 57 +-- .../Resources/Localization/EmailResource.resx | 21 ++ .../Localization/EmailResource.zh-hans.resx | 21 ++ .../Localization/SharedResource.Designer.cs | 18 - .../Localization/SharedResource.resx | 9 - .../Localization/SharedResource.zh-hans.resx | 9 - DysonNetwork.sln.DotSettings.user | 2 +- 18 files changed, 727 insertions(+), 268 deletions(-) create mode 100644 DysonNetwork.Sphere/Pages/Emails/ContactVerificationEmail.razor diff --git a/DysonNetwork.Sphere/Account/Account.cs b/DysonNetwork.Sphere/Account/Account.cs index a3e2b4e..7f010ce 100644 --- a/DysonNetwork.Sphere/Account/Account.cs +++ b/DysonNetwork.Sphere/Account/Account.cs @@ -90,6 +90,7 @@ public class AccountContact : ModelBase public Guid Id { get; set; } public AccountContactType Type { get; set; } public Instant? VerifiedAt { get; set; } + public bool IsPrimary { get; set; } = false; [MaxLength(1024)] public string Content { get; set; } = string.Empty; public Guid AccountId { get; set; } diff --git a/DysonNetwork.Sphere/Account/AccountController.cs b/DysonNetwork.Sphere/Account/AccountController.cs index 7c1b2ad..8e47642 100644 --- a/DysonNetwork.Sphere/Account/AccountController.cs +++ b/DysonNetwork.Sphere/Account/AccountController.cs @@ -97,7 +97,8 @@ public class AccountController( new() { Type = AccountContactType.Email, - Content = request.Email + Content = request.Email, + IsPrimary = true } }, AuthFactors = new List diff --git a/DysonNetwork.Sphere/Account/AccountCurrentController.cs b/DysonNetwork.Sphere/Account/AccountCurrentController.cs index b0612d4..e8a921e 100644 --- a/DysonNetwork.Sphere/Account/AccountCurrentController.cs +++ b/DysonNetwork.Sphere/Account/AccountCurrentController.cs @@ -437,7 +437,7 @@ public class AccountCurrentController( { if (HttpContext.Items["CurrentUser"] is not Account currentUser || HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized(); - + Response.Headers.Append("X-Auth-Session", currentSession.Id.ToString()); // Group sessions by the related DeviceId, then create an AuthorizedDevice for each group. @@ -558,4 +558,106 @@ public class AccountCurrentController( return BadRequest(ex.Message); } } + + [HttpGet("contacts")] + [Authorize] + public async Task>> GetContacts() + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var contacts = await db.AccountContacts + .Where(c => c.AccountId == currentUser.Id) + .ToListAsync(); + + return Ok(contacts); + } + + public class AccountContactRequest + { + [Required] public AccountContactType Type { get; set; } + [Required] public string Content { get; set; } = null!; + } + + [HttpPost("contacts")] + [Authorize] + public async Task> CreateContact([FromBody] AccountContactRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + try + { + var contact = await accounts.CreateContactMethod(currentUser, request.Type, request.Content); + return Ok(contact); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPost("contacts/{id:guid}/verify")] + [Authorize] + public async Task> VerifyContact(Guid id) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var contact = await db.AccountContacts + .Where(c => c.AccountId == currentUser.Id && c.Id == id) + .FirstOrDefaultAsync(); + if (contact is null) return NotFound(); + + try + { + await accounts.VerifyContactMethod(currentUser, contact); + return Ok(contact); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPost("contacts/{id:guid}/primary")] + [Authorize] + public async Task> SetPrimaryContact(Guid id) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var contact = await db.AccountContacts + .Where(c => c.AccountId == currentUser.Id && c.Id == id) + .FirstOrDefaultAsync(); + if (contact is null) return NotFound(); + + try + { + contact = await accounts.SetContactMethodPrimary(currentUser, contact); + return Ok(contact); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpDelete("contacts/{id:guid}")] + [Authorize] + public async Task> DeleteContact(Guid id) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var contact = await db.AccountContacts + .Where(c => c.AccountId == currentUser.Id && c.Id == id) + .FirstOrDefaultAsync(); + if (contact is null) return NotFound(); + + try + { + await accounts.DeleteContactMethod(currentUser, contact); + return NoContent(); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/AccountService.cs b/DysonNetwork.Sphere/Account/AccountService.cs index 3d25bda..1286f77 100644 --- a/DysonNetwork.Sphere/Account/AccountService.cs +++ b/DysonNetwork.Sphere/Account/AccountService.cs @@ -293,7 +293,8 @@ public class AccountService( case AccountAuthFactorType.EmailCode: case AccountAuthFactorType.InAppCode: var correctCode = await _GetFactorCode(factor); - var isCorrect = correctCode is not null && string.Equals(correctCode, code, StringComparison.OrdinalIgnoreCase); + var isCorrect = correctCode is not null && + string.Equals(correctCode, code, StringComparison.OrdinalIgnoreCase); await cache.RemoveAsync($"{AuthFactorCachePrefix}{factor.Id}:code"); return isCorrect; case AccountAuthFactorType.Password: @@ -320,27 +321,28 @@ public class AccountService( $"{AuthFactorCachePrefix}{factor.Id}:code" ); } - + public async Task UpdateSessionLabel(Account account, Guid sessionId, string label) { - var session = await db.AuthSessions - .Include(s => s.Challenge) - .Where(s => s.Id == sessionId && s.AccountId == account.Id) - .FirstOrDefaultAsync(); - if (session is null) throw new InvalidOperationException("Session was not found."); + var session = await db.AuthSessions + .Include(s => s.Challenge) + .Where(s => s.Id == sessionId && s.AccountId == account.Id) + .FirstOrDefaultAsync(); + if (session is null) throw new InvalidOperationException("Session was not found."); - await db.AuthChallenges - .Where(s => s.DeviceId == session.Challenge.DeviceId) - .ExecuteUpdateAsync(p => p.SetProperty(s => s.DeviceId, label)); + await db.AuthSessions + .Include(s => s.Challenge) + .Where(s => s.Challenge.DeviceId == session.Challenge.DeviceId) + .ExecuteUpdateAsync(p => p.SetProperty(s => s.Label, label)); - var sessions = await db.AuthSessions - .Include(s => s.Challenge) - .Where(s => s.AccountId == session.Id && s.Challenge.DeviceId == session.Challenge.DeviceId) - .ToListAsync(); - foreach(var item in sessions) + var sessions = await db.AuthSessions + .Include(s => s.Challenge) + .Where(s => s.AccountId == session.Id && s.Challenge.DeviceId == session.Challenge.DeviceId) + .ToListAsync(); + foreach (var item in sessions) await cache.RemoveAsync($"{DysonTokenAuthHandler.AuthCachePrefix}{item.Id}"); - return session; + return session; } public async Task DeleteSession(Account account, Guid sessionId) @@ -369,6 +371,72 @@ public class AccountService( await cache.RemoveAsync($"{DysonTokenAuthHandler.AuthCachePrefix}{item.Id}"); } + public async Task CreateContactMethod(Account account, AccountContactType type, string content) + { + var contact = new AccountContact + { + Type = type, + Content = content + }; + + db.AccountContacts.Add(contact); + await db.SaveChangesAsync(); + + return contact; + } + + public async Task VerifyContactMethod(Account account, AccountContact contact) + { + var spell = await spells.CreateMagicSpell( + account, + MagicSpellType.ContactVerification, + new Dictionary { { "contact_method", contact.Content } }, + expiredAt: SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)), + preventRepeat: true + ); + await spells.NotifyMagicSpell(spell); + } + + public async Task SetContactMethodPrimary(Account account, AccountContact contact) + { + if (contact.AccountId != account.Id) + throw new InvalidOperationException("Contact method does not belong to this account."); + if (contact.VerifiedAt is null) + throw new InvalidOperationException("Cannot set unverified contact method as primary."); + + await using var transaction = await db.Database.BeginTransactionAsync(); + + try + { + await db.AccountContacts + .Where(c => c.AccountId == account.Id && c.Type == contact.Type) + .ExecuteUpdateAsync(s => s.SetProperty(x => x.IsPrimary, false)); + + contact.IsPrimary = true; + db.AccountContacts.Update(contact); + await db.SaveChangesAsync(); + + await transaction.CommitAsync(); + return contact; + } + catch + { + await transaction.RollbackAsync(); + throw; + } + } + + public async Task DeleteContactMethod(Account account, AccountContact contact) + { + if (contact.AccountId != account.Id) + throw new InvalidOperationException("Contact method does not belong to this account."); + if (contact.IsPrimary) + throw new InvalidOperationException("Cannot delete primary contact method."); + + db.AccountContacts.Remove(contact); + await db.SaveChangesAsync(); + } + /// Maintenance methods for server administrator public async Task EnsureAccountProfileCreated() { diff --git a/DysonNetwork.Sphere/Account/MagicSpellService.cs b/DysonNetwork.Sphere/Account/MagicSpellService.cs index ff2e4a9..383bb37 100644 --- a/DysonNetwork.Sphere/Account/MagicSpellService.cs +++ b/DysonNetwork.Sphere/Account/MagicSpellService.cs @@ -86,7 +86,7 @@ public class MagicSpellService( { case MagicSpellType.AccountActivation: await email.SendTemplatedEmailAsync( - contact.Account.Name, + contact.Account.Nick, contact.Content, localizer["EmailLandingTitle"], new LandingEmailModel @@ -98,7 +98,7 @@ public class MagicSpellService( break; case MagicSpellType.AccountRemoval: await email.SendTemplatedEmailAsync( - contact.Account.Name, + contact.Account.Nick, contact.Content, localizer["EmailAccountDeletionTitle"], new AccountDeletionEmailModel @@ -110,7 +110,7 @@ public class MagicSpellService( break; case MagicSpellType.AuthPasswordReset: await email.SendTemplatedEmailAsync( - contact.Account.Name, + contact.Account.Nick, contact.Content, localizer["EmailAccountDeletionTitle"], new PasswordResetEmailModel @@ -120,6 +120,20 @@ public class MagicSpellService( } ); break; + case MagicSpellType.ContactVerification: + if (spell.Meta["contact_method"] is not string contactMethod) + throw new InvalidOperationException("Contact method is not found."); + await email.SendTemplatedEmailAsync( + contact.Account.Nick, + contactMethod!, + localizer["EmailContactVerificationTitle"], + new ContactVerificationEmailModel + { + Name = contact.Account.Name, + Link = link + } + ); + break; default: throw new ArgumentOutOfRangeException(); } @@ -142,7 +156,6 @@ public class MagicSpellService( var account = await db.Accounts.FirstOrDefaultAsync(c => c.Id == spell.AccountId); if (account is null) break; db.Accounts.Remove(account); - await db.SaveChangesAsync(); break; case MagicSpellType.AccountActivation: var contactMethod = spell.Meta["contact_method"] as string; @@ -173,12 +186,24 @@ public class MagicSpellService( }); } - db.Remove(spell); - await db.SaveChangesAsync(); + break; + case MagicSpellType.ContactVerification: + var verifyContactMethod = spell.Meta["contact_method"] as string; + var verifyContact = await db.AccountContacts + .FirstOrDefaultAsync(c => c.Content == verifyContactMethod); + if (verifyContact is not null) + { + verifyContact.VerifiedAt = SystemClock.Instance.GetCurrentInstant(); + db.Update(verifyContact); + } + break; default: throw new ArgumentOutOfRangeException(); } + + db.Remove(spell); + await db.SaveChangesAsync(); } public async Task ApplyPasswordReset(MagicSpell spell, string newPassword) diff --git a/DysonNetwork.Sphere/Email/EmailModels.cs b/DysonNetwork.Sphere/Email/EmailModels.cs index 355f6d1..1a53142 100644 --- a/DysonNetwork.Sphere/Email/EmailModels.cs +++ b/DysonNetwork.Sphere/Email/EmailModels.cs @@ -22,4 +22,10 @@ public class VerificationEmailModel { public required string Name { get; set; } public required string Code { get; set; } +} + +public class ContactVerificationEmailModel +{ + public required string Name { get; set; } + public required string Link { get; set; } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Emails/AccountDeletionEmail.razor b/DysonNetwork.Sphere/Pages/Emails/AccountDeletionEmail.razor index 0abeafe..e1a590a 100644 --- a/DysonNetwork.Sphere/Pages/Emails/AccountDeletionEmail.razor +++ b/DysonNetwork.Sphere/Pages/Emails/AccountDeletionEmail.razor @@ -1,59 +1,37 @@ @using DysonNetwork.Sphere.Localization @using Microsoft.Extensions.Localization -@using EmailResource = DysonNetwork.Sphere.Localization.EmailResource - - - - + + - - +
-

- @(Localizer["AccountDeletionHeader"]) -

-
+

@(Localizer["AccountDeletionHeader"])

+

@(Localizer["AccountDeletionPara1"]) @@@Name,

+

@(Localizer["AccountDeletionPara2"])

+

@(Localizer["AccountDeletionPara3"])

-
-

- @(Localizer["AccountDeletionPara1"]) @@@Name, -

-

- @(Localizer["AccountDeletionPara2"]) -

-

- @(Localizer["AccountDeletionPara3"]) -

-
+ + + + + + - - - - - - - - -

- @(LocalizerShared["EmailLinkHint"]) -
- @Link -

-

- @(Localizer["AccountDeletionPara4"]) -

-

- @(LocalizerShared["EmailFooter1"])
- @(LocalizerShared["EmailFooter2"]) -

- - - +

@(Localizer["AccountDeletionPara4"])

+ +
@code { @@ -61,5 +39,4 @@ [Parameter] public required string Link { get; set; } [Inject] IStringLocalizer Localizer { get; set; } = null!; - [Inject] IStringLocalizer LocalizerShared { get; set; } = null!; } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Emails/ContactVerificationEmail.razor b/DysonNetwork.Sphere/Pages/Emails/ContactVerificationEmail.razor new file mode 100644 index 0000000..3319384 --- /dev/null +++ b/DysonNetwork.Sphere/Pages/Emails/ContactVerificationEmail.razor @@ -0,0 +1,43 @@ +@using DysonNetwork.Sphere.Localization +@using Microsoft.Extensions.Localization +@using EmailResource = DysonNetwork.Sphere.Localization.EmailResource + + + + +

@(Localizer["ContactVerificationHeader"])

+

@(Localizer["ContactVerificationPara1"]) @Name,

+

@(Localizer["ContactVerificationPara2"])

+ + + + + + + + + +

@(Localizer["ContactVerificationPara3"])

+

@(Localizer["ContactVerificationPara4"])

+ + +
+ +@code { + [Parameter] public required string Name { get; set; } + [Parameter] public required string Link { get; set; } + + [Inject] IStringLocalizer Localizer { get; set; } = null!; +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Emails/EmailLayout.razor b/DysonNetwork.Sphere/Pages/Emails/EmailLayout.razor index a76e6b4..c3f22ff 100644 --- a/DysonNetwork.Sphere/Pages/Emails/EmailLayout.razor +++ b/DysonNetwork.Sphere/Pages/Emails/EmailLayout.razor @@ -1,24 +1,332 @@ @inherits LayoutComponentBase - - + + - - - - - - +
- + + diff --git a/DysonNetwork.Sphere/Pages/Emails/LandingEmail.razor b/DysonNetwork.Sphere/Pages/Emails/LandingEmail.razor index 1009e4d..f7615cf 100644 --- a/DysonNetwork.Sphere/Pages/Emails/LandingEmail.razor +++ b/DysonNetwork.Sphere/Pages/Emails/LandingEmail.razor @@ -3,57 +3,36 @@ @using EmailResource = DysonNetwork.Sphere.Localization.EmailResource - - - - + + - - +
-

- @(Localizer["LandingHeader1"]) -

-
+

@(Localizer["LandingHeader1"])

+

@(Localizer["LandingPara1"]) @@@Name,

+

@(Localizer["LandingPara2"])

+

@(Localizer["LandingPara3"])

-
-

- @(Localizer["LandingPara1"]) @@@Name, -

-

- @(Localizer["LandingPara2"]) -

-

- @(Localizer["LandingPara3"]) -

-
+ + + + + + - - - - - - - - -

- @(LocalizerShared["EmailLinkHint"]) -
- @Link -

-

- @(Localizer["LandingPara4"]) -

-

- @(LocalizerShared["EmailFooter1"])
- @(LocalizerShared["EmailFooter2"]) -

- - - +

@(Localizer["LandingPara4"])

+ +
@code { @@ -61,5 +40,4 @@ [Parameter] public required string Link { get; set; } [Inject] IStringLocalizer Localizer { get; set; } = null!; - [Inject] IStringLocalizer LocalizerShared { get; set; } = null!; } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Pages/Emails/PasswordResetEmail.razor b/DysonNetwork.Sphere/Pages/Emails/PasswordResetEmail.razor index a4a452a..8a38b62 100644 --- a/DysonNetwork.Sphere/Pages/Emails/PasswordResetEmail.razor +++ b/DysonNetwork.Sphere/Pages/Emails/PasswordResetEmail.razor @@ -3,57 +3,36 @@ @using EmailResource = DysonNetwork.Sphere.Localization.EmailResource - - - - + + - - +
-

- @(Localizer["PasswordResetHeader"]) -

-
+

@(Localizer["PasswordResetHeader"])

+

@(Localizer["PasswordResetPara1"]) @@@Name,

+

@(Localizer["PasswordResetPara2"])

+

@(Localizer["PasswordResetPara3"])

-
-

- @(Localizer["PasswordResetPara1"]) @@@Name, -

-

- @(Localizer["PasswordResetPara2"]) -

-

- @(Localizer["PasswordResetPara3"]) -

-
+ + + + + + - - - - - - - - -

- @(LocalizerShared["EmailLinkHint"]) -
- @Link -

-

- @(Localizer["PasswordResetPara4"]) -

-

- @(LocalizerShared["EmailFooter1"])
- @(LocalizerShared["EmailFooter2"]) -

- - - +

@(Localizer["PasswordResetPara4"])

+ +
@code { diff --git a/DysonNetwork.Sphere/Pages/Emails/VerificationEmail.razor b/DysonNetwork.Sphere/Pages/Emails/VerificationEmail.razor index f7d2fdf..7c994bd 100644 --- a/DysonNetwork.Sphere/Pages/Emails/VerificationEmail.razor +++ b/DysonNetwork.Sphere/Pages/Emails/VerificationEmail.razor @@ -3,54 +3,19 @@ @using EmailResource = DysonNetwork.Sphere.Localization.EmailResource - - - - + + - - +

@Code

- - - - - - - -
-

- @(Localizer["VerificationHeader1"]) -

-
+

@(Localizer["VerificationHeader1"])

+

@(Localizer["VerificationPara1"]) @@@Name,

+

@(Localizer["VerificationPara2"])

+

@(Localizer["VerificationPara3"])

-
-

- @(Localizer["VerificationPara1"]) @Name, -

-

- @(Localizer["VerificationPara2"]) -

-

- @(Localizer["VerificationPara3"]) -

-
-
-
- @Code -
-
-
-

- @(Localizer["VerificationPara4"]) -

-

- @(Localizer["VerificationPara5"]) -

-

- @(LocalizerShared["EmailFooter1"])
- @(LocalizerShared["EmailFooter2"]) -

-
+

@(Localizer["VerificationPara4"])

+

@(Localizer["VerificationPara5"])

+ +
@code { diff --git a/DysonNetwork.Sphere/Resources/Localization/EmailResource.resx b/DysonNetwork.Sphere/Resources/Localization/EmailResource.resx index 889be82..89dd75c 100644 --- a/DysonNetwork.Sphere/Resources/Localization/EmailResource.resx +++ b/DysonNetwork.Sphere/Resources/Localization/EmailResource.resx @@ -102,4 +102,25 @@ Verify your email address + + Verify Your Contact Information + + + Dear, + + + Thank you for updating your contact information on the Solar Network. To ensure your account security, we need to verify this change. + + + Please click the button below to verify your contact information: + + + Verify Contact Information + + + If you didn't request this change, please contact our support team immediately. + + + Verify your contact information + \ No newline at end of file diff --git a/DysonNetwork.Sphere/Resources/Localization/EmailResource.zh-hans.resx b/DysonNetwork.Sphere/Resources/Localization/EmailResource.zh-hans.resx index d782a74..fc4ed68 100644 --- a/DysonNetwork.Sphere/Resources/Localization/EmailResource.zh-hans.resx +++ b/DysonNetwork.Sphere/Resources/Localization/EmailResource.zh-hans.resx @@ -95,4 +95,25 @@ 验证您的电子邮箱 + + 验证您的联系信息 + + + 尊敬的 + + + 感谢您更新 Solar Network 上的联系信息。为确保您的账户安全,我们需要验证此更改。 + + + 请点击下方按钮验证您的联系信息: + + + 验证联系信息 + + + 如果您没有请求此更改,请立即联系我们的支持团队。 + + + 验证您的联系信息 + \ No newline at end of file diff --git a/DysonNetwork.Sphere/Resources/Localization/SharedResource.Designer.cs b/DysonNetwork.Sphere/Resources/Localization/SharedResource.Designer.cs index 9498628..0793e32 100644 --- a/DysonNetwork.Sphere/Resources/Localization/SharedResource.Designer.cs +++ b/DysonNetwork.Sphere/Resources/Localization/SharedResource.Designer.cs @@ -44,23 +44,5 @@ namespace DysonNetwork.Sphere.Resources { resourceCulture = value; } } - - internal static string EmailLinkHint { - get { - return ResourceManager.GetString("EmailLinkHint", resourceCulture); - } - } - - internal static string EmailFooter1 { - get { - return ResourceManager.GetString("EmailFooter1", resourceCulture); - } - } - - internal static string EmailFooter2 { - get { - return ResourceManager.GetString("EmailFooter2", resourceCulture); - } - } } } diff --git a/DysonNetwork.Sphere/Resources/Localization/SharedResource.resx b/DysonNetwork.Sphere/Resources/Localization/SharedResource.resx index 9b403ff..a4c5284 100644 --- a/DysonNetwork.Sphere/Resources/Localization/SharedResource.resx +++ b/DysonNetwork.Sphere/Resources/Localization/SharedResource.resx @@ -18,13 +18,4 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - If the button doesn't work, you can also copy and paste this link into your browser: - - - Best regards, - - - The Solar Network Team - \ No newline at end of file diff --git a/DysonNetwork.Sphere/Resources/Localization/SharedResource.zh-hans.resx b/DysonNetwork.Sphere/Resources/Localization/SharedResource.zh-hans.resx index 23a16ed..0db1973 100644 --- a/DysonNetwork.Sphere/Resources/Localization/SharedResource.zh-hans.resx +++ b/DysonNetwork.Sphere/Resources/Localization/SharedResource.zh-hans.resx @@ -11,13 +11,4 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 此致 - - - Solar Network 团队 - - - 如果上方的按钮不起作用,你也可以复制下面的链接到浏览器。 - \ No newline at end of file diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index 365dc15..4e302f1 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -107,7 +107,7 @@ False True True - True + False False