From 40afe232410befc78c8229b9a49fbb68c21e8bd6 Mon Sep 17 00:00:00 2001 From: Edi Wang Date: Wed, 9 Oct 2024 15:17:32 +0800 Subject: [PATCH 01/47] Add server-side handling for color scheme preference Enhanced user experience by dynamically setting the theme based on the `sec-ch-prefers-color-scheme` header to prevent screen blinking during page transitions. - `_Layout.cshtml`: Added logic to check the header and set `data-bs-theme` attribute. - `_LayoutAdmin.cshtml`: Applied similar changes as `_Layout.cshtml`. - `Program.cs`: Removed outdated code and comments, now reads client's preference directly. --- src/Moonglade.Web/Pages/Shared/_Layout.cshtml | 11 ++++++++++- src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml | 11 ++++++++++- src/Moonglade.Web/Program.cs | 2 -- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Moonglade.Web/Pages/Shared/_Layout.cshtml b/src/Moonglade.Web/Pages/Shared/_Layout.cshtml index a540bd6db..1eda57c4c 100644 --- a/src/Moonglade.Web/Pages/Shared/_Layout.cshtml +++ b/src/Moonglade.Web/Pages/Shared/_Layout.cshtml @@ -24,9 +24,18 @@ langCode = CultureInfo.CurrentUICulture.ToString().ToLower(); } } + + // Get `sec-ch-prefers-color-scheme` header value + // This is to enhance user experience by stopping the screen from blinking when switching pages + bool useServerSideDarkMode = false; + var prefersColorScheme = Context.Request.Headers["sec-ch-prefers-color-scheme"]; + if (prefersColorScheme == "dark") + { + useServerSideDarkMode = true; + } } - + diff --git a/src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml b/src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml index ba182e4cc..28b1903e5 100644 --- a/src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml +++ b/src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml @@ -4,9 +4,18 @@ { BlogConfig.GeneralSettings.AvatarUrl = Url.Action("Avatar", "Assets"); } + + // Get `sec-ch-prefers-color-scheme` header value + // This is to enhance user experience by stopping the screen from blinking when switching pages + bool useServerSideDarkMode = false; + var prefersColorScheme = Context.Request.Headers["sec-ch-prefers-color-scheme"]; + if (prefersColorScheme == "dark") + { + useServerSideDarkMode = true; + } } - + diff --git a/src/Moonglade.Web/Program.cs b/src/Moonglade.Web/Program.cs index ffb85f579..dda560dbf 100644 --- a/src/Moonglade.Web/Program.cs +++ b/src/Moonglade.Web/Program.cs @@ -267,8 +267,6 @@ private static void ConfigureMiddleware(WebApplication app, List<CultureInfo> cu options.IconFilePath = "/favicon-16x16.png"; }); - // In v14.11.0, just send the header to the client in order to observe browser behaviour - // We will read client's preference in future versions bool usePrefersColorSchemeHeader = app.Configuration.GetSection("Experimental:UsePrefersColorSchemeHeader").Get<bool>(); if (usePrefersColorSchemeHeader) app.UseMiddleware<PrefersColorSchemeMiddleware>(); From 5b4b99e37039f86bdee7d7ea414a0218ef04a592 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Wed, 9 Oct 2024 15:17:57 +0800 Subject: [PATCH 02/47] Update version numbers in Directory.Build.props Updated AssemblyVersion and FileVersion from 14.11.0.0 to 14.12.0.0. Updated Version from 14.11.0 to 14.12.0-preview.1. --- src/Directory.Build.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index d06c5a8fd..2abd583df 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,8 +3,8 @@ <Authors>Edi Wang</Authors> <Company>edi.wang</Company> <Copyright>(C) 2024 edi.wang@outlook.com</Copyright> - <AssemblyVersion>14.11.0.0</AssemblyVersion> - <FileVersion>14.11.0.0</FileVersion> - <Version>14.11.0</Version> + <AssemblyVersion>14.12.0.0</AssemblyVersion> + <FileVersion>14.12.0.0</FileVersion> + <Version>14.12.0-preview.1</Version> </PropertyGroup> </Project> \ No newline at end of file From 46566be1e9ab1e9a1a0cc6bbc6ad7612ebabe249 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Wed, 9 Oct 2024 15:21:54 +0800 Subject: [PATCH 03/47] Enable config-based dark mode and improve code readability Added `@inject IConfiguration Configuration` to `_Layout.cshtml` and `_LayoutAdmin.cshtml` for configuration access. Introduced `usePrefersColorSchemeHeader` from config to control dark mode based on `sec-ch-prefers-color-scheme` header. Improved code readability by adding spaces around `@if` statements and in self-closing `<img>` tags. --- src/Moonglade.Web/Pages/Shared/_Layout.cshtml | 12 +++++++----- src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml | 5 ++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Moonglade.Web/Pages/Shared/_Layout.cshtml b/src/Moonglade.Web/Pages/Shared/_Layout.cshtml index 1eda57c4c..f3f4057a0 100644 --- a/src/Moonglade.Web/Pages/Shared/_Layout.cshtml +++ b/src/Moonglade.Web/Pages/Shared/_Layout.cshtml @@ -1,6 +1,7 @@ @using Moonglade.Utils @using Microsoft.AspNetCore.Localization @using System.Globalization +@inject IConfiguration Configuration @{ if (string.IsNullOrEmpty(BlogConfig.GeneralSettings.AvatarUrl)) @@ -28,8 +29,9 @@ // Get `sec-ch-prefers-color-scheme` header value // This is to enhance user experience by stopping the screen from blinking when switching pages bool useServerSideDarkMode = false; + bool usePrefersColorSchemeHeader = Configuration.GetSection("Experimental:UsePrefersColorSchemeHeader").Get<bool>(); var prefersColorScheme = Context.Request.Headers["sec-ch-prefers-color-scheme"]; - if (prefersColorScheme == "dark") + if (usePrefersColorSchemeHeader && prefersColorScheme == "dark") { useServerSideDarkMode = true; } @@ -180,13 +182,13 @@ { <section class="profile-mobile d-block d-sm-none d-print-none"> <div class="card-background-container"> - @if(BlogConfig.GeneralSettings.UseGravatarAsProfilePicture) + @if (BlogConfig.GeneralSettings.UseGravatarAsProfilePicture) { <gravatar class="card-bkimg" email="@BlogConfig.GeneralSettings.OwnerEmail" size="256" /> } else { - <img class="card-bkimg" alt="" aria-hidden="true" src="@BlogConfig.GeneralSettings.AvatarUrl"/> + <img class="card-bkimg" alt="" aria-hidden="true" src="@BlogConfig.GeneralSettings.AvatarUrl" /> } </div> <div class="blogger-intro-content"> @@ -196,14 +198,14 @@ { <gravatar class="rounded-circle blogger-head-pic" size="256" - alt="@BlogConfig.GeneralSettings.OwnerName" + alt="@BlogConfig.GeneralSettings.OwnerName" email="@BlogConfig.GeneralSettings.OwnerEmail" /> } else { <img src="@BlogConfig.GeneralSettings.AvatarUrl" alt="@BlogConfig.GeneralSettings.OwnerName" - class="rounded-circle blogger-head-pic"/> + class="rounded-circle blogger-head-pic" /> } </div> <div class="col-9 position-relative"> diff --git a/src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml b/src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml index 28b1903e5..3b15e798f 100644 --- a/src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml +++ b/src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml @@ -1,4 +1,6 @@ @inject IOptions<AuthenticationSettings> AuthOptions +@inject IConfiguration Configuration + @{ if (string.IsNullOrEmpty(BlogConfig.GeneralSettings.AvatarUrl)) { @@ -8,8 +10,9 @@ // Get `sec-ch-prefers-color-scheme` header value // This is to enhance user experience by stopping the screen from blinking when switching pages bool useServerSideDarkMode = false; + bool usePrefersColorSchemeHeader = Configuration.GetSection("Experimental:UsePrefersColorSchemeHeader").Get<bool>(); var prefersColorScheme = Context.Request.Headers["sec-ch-prefers-color-scheme"]; - if (prefersColorScheme == "dark") + if (usePrefersColorSchemeHeader && prefersColorScheme == "dark") { useServerSideDarkMode = true; } From b151bc2d03dbb1130478befeb5131d955b85625a Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Wed, 9 Oct 2024 15:30:37 +0800 Subject: [PATCH 04/47] Update PrefersColorScheme feature and configuration Updated PrefersColorSchemeMiddleware to accept IConfiguration, allowing it to read settings from the configuration. The middleware now dynamically sets response headers based on the configured header name. Updated _Layout.cshtml, _LayoutAdmin.cshtml, and Program.cs to use the new PrefersColorSchemeHeader settings. Removed the Experimental section from appsettings.json and replaced it with PrefersColorSchemeHeader, which includes Enabled and HeaderName settings. --- .../Middleware/PrefersColorSchemeMiddleware.cs | 14 ++++++++++---- src/Moonglade.Web/Pages/Shared/_Layout.cshtml | 4 ++-- src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml | 4 ++-- src/Moonglade.Web/Program.cs | 2 +- src/Moonglade.Web/appsettings.json | 5 +++-- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Moonglade.Web/Middleware/PrefersColorSchemeMiddleware.cs b/src/Moonglade.Web/Middleware/PrefersColorSchemeMiddleware.cs index 593eafd29..2ebbf7b2f 100644 --- a/src/Moonglade.Web/Middleware/PrefersColorSchemeMiddleware.cs +++ b/src/Moonglade.Web/Middleware/PrefersColorSchemeMiddleware.cs @@ -2,13 +2,19 @@ public class PrefersColorSchemeMiddleware(RequestDelegate next) { - public async Task InvokeAsync(HttpContext context) + public async Task InvokeAsync(HttpContext context, IConfiguration configuration) { + var headerName = configuration["PrefersColorScheme:HeaderName"]; + if (string.IsNullOrWhiteSpace(headerName)) + { + await next(context); + } + context.Response.OnStarting(() => { - context.Response.Headers["Accept-CH"] = "Sec-CH-Prefers-Color-Scheme"; - context.Response.Headers["Vary"] = "Sec-CH-Prefers-Color-Scheme"; - context.Response.Headers["Critical-CH"] = "Sec-CH-Prefers-Color-Scheme"; + context.Response.Headers["Accept-CH"] = headerName; + context.Response.Headers["Vary"] = headerName; + context.Response.Headers["Critical-CH"] = headerName; return Task.CompletedTask; }); diff --git a/src/Moonglade.Web/Pages/Shared/_Layout.cshtml b/src/Moonglade.Web/Pages/Shared/_Layout.cshtml index f3f4057a0..8b44f6ac6 100644 --- a/src/Moonglade.Web/Pages/Shared/_Layout.cshtml +++ b/src/Moonglade.Web/Pages/Shared/_Layout.cshtml @@ -29,8 +29,8 @@ // Get `sec-ch-prefers-color-scheme` header value // This is to enhance user experience by stopping the screen from blinking when switching pages bool useServerSideDarkMode = false; - bool usePrefersColorSchemeHeader = Configuration.GetSection("Experimental:UsePrefersColorSchemeHeader").Get<bool>(); - var prefersColorScheme = Context.Request.Headers["sec-ch-prefers-color-scheme"]; + bool usePrefersColorSchemeHeader = Configuration.GetSection("PrefersColorSchemeHeader:Enabled").Get<bool>(); + var prefersColorScheme = Context.Request.Headers[Configuration["PrefersColorScheme:HeaderName"]!]; if (usePrefersColorSchemeHeader && prefersColorScheme == "dark") { useServerSideDarkMode = true; diff --git a/src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml b/src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml index 3b15e798f..9e7f9e475 100644 --- a/src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml +++ b/src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml @@ -10,8 +10,8 @@ // Get `sec-ch-prefers-color-scheme` header value // This is to enhance user experience by stopping the screen from blinking when switching pages bool useServerSideDarkMode = false; - bool usePrefersColorSchemeHeader = Configuration.GetSection("Experimental:UsePrefersColorSchemeHeader").Get<bool>(); - var prefersColorScheme = Context.Request.Headers["sec-ch-prefers-color-scheme"]; + bool usePrefersColorSchemeHeader = Configuration.GetSection("PrefersColorSchemeHeader:Enabled").Get<bool>(); + var prefersColorScheme = Context.Request.Headers[Configuration["PrefersColorScheme:HeaderName"]!]; if (usePrefersColorSchemeHeader && prefersColorScheme == "dark") { useServerSideDarkMode = true; diff --git a/src/Moonglade.Web/Program.cs b/src/Moonglade.Web/Program.cs index dda560dbf..552193ebc 100644 --- a/src/Moonglade.Web/Program.cs +++ b/src/Moonglade.Web/Program.cs @@ -267,7 +267,7 @@ private static void ConfigureMiddleware(WebApplication app, List<CultureInfo> cu options.IconFilePath = "/favicon-16x16.png"; }); - bool usePrefersColorSchemeHeader = app.Configuration.GetSection("Experimental:UsePrefersColorSchemeHeader").Get<bool>(); + bool usePrefersColorSchemeHeader = app.Configuration.GetSection("PrefersColorSchemeHeader:Enabled").Get<bool>(); if (usePrefersColorSchemeHeader) app.UseMiddleware<PrefersColorSchemeMiddleware>(); app.UseMiddleware<PoweredByMiddleware>(); diff --git a/src/Moonglade.Web/appsettings.json b/src/Moonglade.Web/appsettings.json index 8ae2501be..99835229c 100644 --- a/src/Moonglade.Web/appsettings.json +++ b/src/Moonglade.Web/appsettings.json @@ -73,8 +73,9 @@ "SetLastModifiedHeader": true, "CacheMinutes": 20 }, - "Experimental": { - "UsePrefersColorSchemeHeader": true + "PrefersColorSchemeHeader": { + "Enabled": true, + "HeaderName": "Sec-CH-Prefers-Color-Scheme" }, "Logging": { "LogLevel": { From 23b78ac950ee84aad4d7b57e036e99cac924e207 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Wed, 9 Oct 2024 15:34:07 +0800 Subject: [PATCH 05/47] Update config key for PrefersColorScheme header name Updated the configuration key from `PrefersColorScheme:HeaderName` to `PrefersColorSchemeHeader:HeaderName` in the following files: - PrefersColorSchemeMiddleware.cs - _Layout.cshtml - _LayoutAdmin.cshtml This change ensures the application reads the header name from the updated configuration section. --- src/Moonglade.Web/Middleware/PrefersColorSchemeMiddleware.cs | 2 +- src/Moonglade.Web/Pages/Shared/_Layout.cshtml | 2 +- src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Moonglade.Web/Middleware/PrefersColorSchemeMiddleware.cs b/src/Moonglade.Web/Middleware/PrefersColorSchemeMiddleware.cs index 2ebbf7b2f..ca2fbb833 100644 --- a/src/Moonglade.Web/Middleware/PrefersColorSchemeMiddleware.cs +++ b/src/Moonglade.Web/Middleware/PrefersColorSchemeMiddleware.cs @@ -4,7 +4,7 @@ public class PrefersColorSchemeMiddleware(RequestDelegate next) { public async Task InvokeAsync(HttpContext context, IConfiguration configuration) { - var headerName = configuration["PrefersColorScheme:HeaderName"]; + var headerName = configuration["PrefersColorSchemeHeader:HeaderName"]; if (string.IsNullOrWhiteSpace(headerName)) { await next(context); diff --git a/src/Moonglade.Web/Pages/Shared/_Layout.cshtml b/src/Moonglade.Web/Pages/Shared/_Layout.cshtml index 8b44f6ac6..8d5a26eab 100644 --- a/src/Moonglade.Web/Pages/Shared/_Layout.cshtml +++ b/src/Moonglade.Web/Pages/Shared/_Layout.cshtml @@ -30,7 +30,7 @@ // This is to enhance user experience by stopping the screen from blinking when switching pages bool useServerSideDarkMode = false; bool usePrefersColorSchemeHeader = Configuration.GetSection("PrefersColorSchemeHeader:Enabled").Get<bool>(); - var prefersColorScheme = Context.Request.Headers[Configuration["PrefersColorScheme:HeaderName"]!]; + var prefersColorScheme = Context.Request.Headers[Configuration["PrefersColorSchemeHeader:HeaderName"]!]; if (usePrefersColorSchemeHeader && prefersColorScheme == "dark") { useServerSideDarkMode = true; diff --git a/src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml b/src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml index 9e7f9e475..9993d13cc 100644 --- a/src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml +++ b/src/Moonglade.Web/Pages/Shared/_LayoutAdmin.cshtml @@ -11,7 +11,7 @@ // This is to enhance user experience by stopping the screen from blinking when switching pages bool useServerSideDarkMode = false; bool usePrefersColorSchemeHeader = Configuration.GetSection("PrefersColorSchemeHeader:Enabled").Get<bool>(); - var prefersColorScheme = Context.Request.Headers[Configuration["PrefersColorScheme:HeaderName"]!]; + var prefersColorScheme = Context.Request.Headers[Configuration["PrefersColorSchemeHeader:HeaderName"]!]; if (usePrefersColorSchemeHeader && prefersColorScheme == "dark") { useServerSideDarkMode = true; From 967dd52bd9051b92004d61aa5d49831bacb006d6 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Thu, 10 Oct 2024 09:24:46 +0800 Subject: [PATCH 06/47] Add DI, anti-forgery token, and "Index Now" settings Injected IConfiguration for accessing configuration settings. Added anti-forgery token for enhanced security. Introduced "Index Now" settings section with: - Icon and label for "Index Now" feature. - Conditional display of "Index Now" API key. --- .../Pages/Settings/Advanced.cshtml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Moonglade.Web/Pages/Settings/Advanced.cshtml b/src/Moonglade.Web/Pages/Settings/Advanced.cshtml index 9f6a7656d..4d499fcb5 100644 --- a/src/Moonglade.Web/Pages/Settings/Advanced.cshtml +++ b/src/Moonglade.Web/Pages/Settings/Advanced.cshtml @@ -1,5 +1,7 @@ @page "/admin/settings/advanced" @using System.Reflection +@inject IConfiguration Configuration + @Html.AntiForgeryToken() @{ var settings = BlogConfig.AdvancedSettings; @@ -144,6 +146,25 @@ </div> </div> </div> + + <div class="settings-entry row align-items-center py-3 px-2 rounded-3 shadow-sm border mb-2"> + <div class="col-auto"> + <i class="bi-file-earmark-check settings-entry-icon"></i> + </div> + <div class="col"> + <label class="form-check-label">Index Now</label> + </div> + <div class="col-md-4 text-end"> + @if (string.IsNullOrWhiteSpace(Configuration["IndexNow:ApiKey"])) + { + <text>Not configured</text> + } + else + { + <code>@Configuration["IndexNow:ApiKey"]</code> + } + </div> + </div> <div class="settings-entry row align-items-center py-3 px-2 rounded-3 shadow-sm border mb-2"> <div class="col-auto"> From 487b9332c948811fb2af6897ce550218bce668ea Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Thu, 10 Oct 2024 09:30:58 +0800 Subject: [PATCH 07/47] Update icons for EnableWebmention and EnablePingback settings Changed the icon for the "EnableWebmention" setting from `bi-arrow-right-circle` to `bi-bell` and the icon for the "EnablePingback" setting from `bi-arrow-right-circle` to `bi-signpost` in the `Advanced.cshtml` file. These updates aim to enhance the visual representation and clarity of these settings. --- src/Moonglade.Web/Pages/Settings/Advanced.cshtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Moonglade.Web/Pages/Settings/Advanced.cshtml b/src/Moonglade.Web/Pages/Settings/Advanced.cshtml index 4d499fcb5..6ad2d1951 100644 --- a/src/Moonglade.Web/Pages/Settings/Advanced.cshtml +++ b/src/Moonglade.Web/Pages/Settings/Advanced.cshtml @@ -104,7 +104,7 @@ <div class="settings-entry row align-items-center py-3 px-2 rounded-3 shadow-sm border mb-2"> <div class="col-auto"> - <i class="bi-arrow-right-circle settings-entry-icon"></i> + <i class="bi-bell settings-entry-icon"></i> </div> <div class="col"> <label asp-for="@settings.EnableWebmention" class="form-check-label"></label> @@ -119,7 +119,7 @@ <div class="settings-entry row align-items-center py-3 px-2 rounded-3 shadow-sm border mb-2"> <div class="col-auto"> - <i class="bi-arrow-right-circle settings-entry-icon"></i> + <i class="bi-signpost settings-entry-icon"></i> </div> <div class="col"> <label asp-for="@settings.EnablePingback" class="form-check-label"></label> From a45b1509355f9649e1f9acba7ce3221241397a26 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Fri, 11 Oct 2024 12:06:50 +0800 Subject: [PATCH 08/47] Update webapp-prod.yml --- .github/workflows/webapp-prod.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/webapp-prod.yml b/.github/workflows/webapp-prod.yml index db9ad3e48..fd04c0ee8 100644 --- a/.github/workflows/webapp-prod.yml +++ b/.github/workflows/webapp-prod.yml @@ -1,7 +1,7 @@ name: Azure Web App (PROD) env: - AZURE_WEBAPP_NAME: moonglade-ediwang-us + AZURE_WEBAPP_NAME: ediwang AZURE_WEBAPP_PACKAGE_PATH: '.' DOTNET_VERSION: '8' @@ -66,5 +66,5 @@ jobs: with: app-name: ${{ env.AZURE_WEBAPP_NAME }} clean: true - publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} + publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE2 }} package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} From 7d062b797b187d557b0664e11adbd8c1b1ecdc1c Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Sun, 13 Oct 2024 14:16:24 +0800 Subject: [PATCH 09/47] Update appsettings.json --- src/Moonglade.Web/appsettings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Moonglade.Web/appsettings.json b/src/Moonglade.Web/appsettings.json index 99835229c..479a5357d 100644 --- a/src/Moonglade.Web/appsettings.json +++ b/src/Moonglade.Web/appsettings.json @@ -30,7 +30,8 @@ "www.bing.com", "search.seznam.cz", "yandex.com" - ] + ], + "MinimalIntervalMinutes": 10 }, "ForwardedHeaders": { "Enabled": false, From 5aaee31919d038f04ac62b3ddb3c83423492ac17 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Sun, 13 Oct 2024 14:27:05 +0800 Subject: [PATCH 10/47] Ensure consistent timestamps in post commands Introduced a `utcNow` variable in `CreatePostCommand.cs` and `UpdatePostCommand.cs` to store the current UTC time using `DateTime.UtcNow`. This variable is used to set `CreateTimeUtc`, `LastModifiedUtc`, and `PubDateUtc` fields, ensuring all timestamps within the same operation are consistent. --- src/Moonglade.Core/PostFeature/CreatePostCommand.cs | 7 ++++--- src/Moonglade.Core/PostFeature/UpdatePostCommand.cs | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Moonglade.Core/PostFeature/CreatePostCommand.cs b/src/Moonglade.Core/PostFeature/CreatePostCommand.cs index 164b12f30..c47ffb63f 100644 --- a/src/Moonglade.Core/PostFeature/CreatePostCommand.cs +++ b/src/Moonglade.Core/PostFeature/CreatePostCommand.cs @@ -33,20 +33,21 @@ public async Task<PostEntity> Handle(CreatePostCommand request, CancellationToke abs = request.Payload.Abstract.Trim(); } + var utcNow = DateTime.UtcNow; var post = new PostEntity { CommentEnabled = request.Payload.EnableComment, Id = Guid.NewGuid(), PostContent = request.Payload.EditorContent, ContentAbstract = abs, - CreateTimeUtc = DateTime.UtcNow, - LastModifiedUtc = DateTime.UtcNow, // Fix draft orders + CreateTimeUtc = utcNow, + LastModifiedUtc = utcNow, // Fix draft orders Slug = request.Payload.Slug.ToLower().Trim(), Author = request.Payload.Author?.Trim(), Title = request.Payload.Title.Trim(), ContentLanguageCode = request.Payload.LanguageCode, IsFeedIncluded = request.Payload.FeedIncluded, - PubDateUtc = request.Payload.IsPublished ? DateTime.UtcNow : null, + PubDateUtc = request.Payload.IsPublished ? utcNow : null, IsDeleted = false, IsPublished = request.Payload.IsPublished, IsFeatured = request.Payload.Featured, diff --git a/src/Moonglade.Core/PostFeature/UpdatePostCommand.cs b/src/Moonglade.Core/PostFeature/UpdatePostCommand.cs index 1a388b48a..cb81ee2d2 100644 --- a/src/Moonglade.Core/PostFeature/UpdatePostCommand.cs +++ b/src/Moonglade.Core/PostFeature/UpdatePostCommand.cs @@ -48,6 +48,7 @@ public UpdatePostCommandHandler( public async Task<PostEntity> Handle(UpdatePostCommand request, CancellationToken ct) { + var utcNow = DateTime.UtcNow; var (guid, postEditModel) = request; var post = await _postRepo.GetByIdAsync(guid, ct); if (null == post) @@ -73,7 +74,7 @@ public async Task<PostEntity> Handle(UpdatePostCommand request, CancellationToke if (postEditModel.IsPublished && !post.IsPublished) { post.IsPublished = true; - post.PubDateUtc = DateTime.UtcNow; + post.PubDateUtc = utcNow; } // #325: Allow changing publish date for published posts @@ -87,7 +88,7 @@ public async Task<PostEntity> Handle(UpdatePostCommand request, CancellationToke post.Author = postEditModel.Author?.Trim(); post.Slug = postEditModel.Slug.ToLower().Trim(); post.Title = postEditModel.Title.Trim(); - post.LastModifiedUtc = DateTime.UtcNow; + post.LastModifiedUtc = utcNow; post.IsFeedIncluded = postEditModel.FeedIncluded; post.ContentLanguageCode = postEditModel.LanguageCode; post.IsFeatured = postEditModel.Featured; From e0b6472d8d99adc246f6292b6a9062837da6eceb Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Sun, 13 Oct 2024 14:43:57 +0800 Subject: [PATCH 11/47] Add LastModifiedUtc property and cooldown for IndexNow Introduced LastModifiedUtc property to track post modifications. Updated PostController to include IConfiguration and added a cooldown mechanism for firing IIndexNowClient based on LastModifiedUtc and a configurable interval. Updated EditPost view and model to handle LastModifiedUtc. --- src/Moonglade.Core/PostFeature/PostEditModel.cs | 3 +++ src/Moonglade.Web/Controllers/PostController.cs | 16 +++++++++++++++- src/Moonglade.Web/Pages/Admin/EditPost.cshtml | 1 + src/Moonglade.Web/Pages/Admin/EditPost.cshtml.cs | 3 ++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Moonglade.Core/PostFeature/PostEditModel.cs b/src/Moonglade.Core/PostFeature/PostEditModel.cs index 6451f5ada..ab9e5588d 100644 --- a/src/Moonglade.Core/PostFeature/PostEditModel.cs +++ b/src/Moonglade.Core/PostFeature/PostEditModel.cs @@ -67,4 +67,7 @@ public class PostEditModel public bool IsOutdated { get; set; } public bool WarnSlugModification => PublishDate.HasValue && (DateTime.UtcNow - PublishDate.Value).Days > 3; + + [HiddenInput] + public string LastModifiedUtc { get; set; } } \ No newline at end of file diff --git a/src/Moonglade.Web/Controllers/PostController.cs b/src/Moonglade.Web/Controllers/PostController.cs index 1ea6385da..3c2818177 100644 --- a/src/Moonglade.Web/Controllers/PostController.cs +++ b/src/Moonglade.Web/Controllers/PostController.cs @@ -11,6 +11,7 @@ namespace Moonglade.Web.Controllers; [ApiController] [Route("api/[controller]")] public class PostController( + IConfiguration configuration, IMediator mediator, IBlogConfig blogConfig, ITimeZoneResolver timeZoneResolver, @@ -61,7 +62,20 @@ await mediator.Send(new CreatePostCommand(model)) : cannonService.FireAsync<IWebmentionSender>(async sender => await sender.SendWebmentionAsync(link.ToString(), postEntity.PostContent)); } - cannonService.FireAsync<IIndexNowClient>(async sender => await sender.SendRequestAsync(link)); + var isNewPublish = postEntity.LastModifiedUtc == postEntity.PubDateUtc; + + bool indexCoolDown = true; + var minimalIntervalMinutes = int.Parse(configuration["IndexNow:MinimalIntervalMinutes"]!); + if (!string.IsNullOrWhiteSpace(model.LastModifiedUtc)) + { + var lastSavedInterval = DateTime.Parse(model.LastModifiedUtc) - DateTime.UtcNow; + indexCoolDown = lastSavedInterval.TotalMinutes > minimalIntervalMinutes; + } + + if (isNewPublish || indexCoolDown) + { + cannonService.FireAsync<IIndexNowClient>(async sender => await sender.SendRequestAsync(link)); + } } return Ok(new { PostId = postEntity.Id }); diff --git a/src/Moonglade.Web/Pages/Admin/EditPost.cshtml b/src/Moonglade.Web/Pages/Admin/EditPost.cshtml index 1541f6ea9..246d2cfb9 100644 --- a/src/Moonglade.Web/Pages/Admin/EditPost.cshtml +++ b/src/Moonglade.Web/Pages/Admin/EditPost.cshtml @@ -194,6 +194,7 @@ <form class="post-edit-form" asp-controller="Post" asp-action="CreateOrEdit"> <input type="hidden" asp-for="ViewModel.PostId" /> <input type="hidden" asp-for="ViewModel.IsPublished" /> + <input type="hidden" asp-for="ViewModel.LastModifiedUtc" /> <div class="row g-2"> <div class="col-md-9 col-xl-10"> <input type="text" asp-for="ViewModel.Title" class="form-control form-control-lg" placeholder="@SharedLocalizer["Title"]" required maxlength="128" /> diff --git a/src/Moonglade.Web/Pages/Admin/EditPost.cshtml.cs b/src/Moonglade.Web/Pages/Admin/EditPost.cshtml.cs index 2b65e00df..3758f9f5d 100644 --- a/src/Moonglade.Web/Pages/Admin/EditPost.cshtml.cs +++ b/src/Moonglade.Web/Pages/Admin/EditPost.cshtml.cs @@ -58,7 +58,8 @@ public async Task<IActionResult> OnGetAsync(Guid? id) Abstract = post.ContentAbstract.Replace("\u00A0\u2026", string.Empty), Featured = post.IsFeatured, HeroImageUrl = post.HeroImageUrl, - IsOutdated = post.IsOutdated + IsOutdated = post.IsOutdated, + LastModifiedUtc = post.LastModifiedUtc?.ToString("u") }; if (post.PubDateUtc is not null) From d093ac5b17baf671541af7e84696a1f821563b98 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 14 Oct 2024 12:05:14 +0800 Subject: [PATCH 12/47] Update package versions in project files Updated Moonglade.ImageStorage.csproj to use Azure.Storage.Blobs 12.22.2. Updated Moonglade.Web.csproj to use TinyMCE 7.4.1. --- src/Moonglade.ImageStorage/Moonglade.ImageStorage.csproj | 2 +- src/Moonglade.Web/Moonglade.Web.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Moonglade.ImageStorage/Moonglade.ImageStorage.csproj b/src/Moonglade.ImageStorage/Moonglade.ImageStorage.csproj index bdc8dd966..12cf10599 100644 --- a/src/Moonglade.ImageStorage/Moonglade.ImageStorage.csproj +++ b/src/Moonglade.ImageStorage/Moonglade.ImageStorage.csproj @@ -10,7 +10,7 @@ </PropertyGroup> <ItemGroup> <FrameworkReference Include="Microsoft.AspNetCore.App" /> - <PackageReference Include="Azure.Storage.Blobs" Version="12.22.1" /> + <PackageReference Include="Azure.Storage.Blobs" Version="12.22.2" /> <PackageReference Include="Minio" Version="6.0.3" /> </ItemGroup> </Project> \ No newline at end of file diff --git a/src/Moonglade.Web/Moonglade.Web.csproj b/src/Moonglade.Web/Moonglade.Web.csproj index 14ec8b136..87b9f87d5 100644 --- a/src/Moonglade.Web/Moonglade.Web.csproj +++ b/src/Moonglade.Web/Moonglade.Web.csproj @@ -43,7 +43,7 @@ <PackageReference Include="Edi.PasswordGenerator" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="8.0.10" /> - <PackageReference Include="TinyMCE" Version="7.3.0" /> + <PackageReference Include="TinyMCE" Version="7.4.1" /> <PackageReference Include="Moonglade.MonacoEditor" Version="0.50.0.1002" /> </ItemGroup> <ItemGroup> From b8f3ba5b9d6b704ba9d556fcb6f9e7ff81237715 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 14 Oct 2024 12:15:44 +0800 Subject: [PATCH 13/47] Fix checkbox attribute and add unpublish button Corrected the placement of the `checked` attribute in `EditPost.cshtml` to ensure it is properly applied based on the `IsChecked` property of the `cat` object. Added a conditional block to display an "Unpublish this post" button if the post is published, enhancing the UI for post management. --- src/Moonglade.Web/Pages/Admin/EditPost.cshtml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Moonglade.Web/Pages/Admin/EditPost.cshtml b/src/Moonglade.Web/Pages/Admin/EditPost.cshtml index 246d2cfb9..af2add345 100644 --- a/src/Moonglade.Web/Pages/Admin/EditPost.cshtml +++ b/src/Moonglade.Web/Pages/Admin/EditPost.cshtml @@ -327,7 +327,7 @@ name="SelectedCatIds" value="@cat.Id" class="form-check-input" - @(cat.IsChecked ? "checked" : null)> + @(cat.IsChecked ? "checked" : null)> <label for="category-@cat.Id" class="form-check-label"> @cat.DisplayText @@ -340,6 +340,13 @@ } </div> </div> + + @if (Model.ViewModel.IsPublished) + { + <div class="text-center"> + <a class="unpublish-link btn btn-sm btn-outline-secondary" href="#">@SharedLocalizer["Unpublish this post"]</a> + </div> + } </div> </div> From c05f9b66a965b3a9bd778286aabdeb5e4134e1a3 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 14 Oct 2024 12:17:27 +0800 Subject: [PATCH 14/47] Add localization for "Unpublish this post" in multiple languages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a new localization entry for the phrase "Unpublish this post" in the following resource files: * `Program.de-DE.resx` with the value "Veröffentlichung dieses Artikels aufheben" * `Program.ja-JP.resx` with the value "この記事を非公開にする" * `Program.zh-Hans.resx` with the value "取消发布这篇文章" * `Program.zh-Hant.resx` with the value "取消發佈這篇文章" --- src/Moonglade.Web/Resources/Program.de-DE.resx | 3 +++ src/Moonglade.Web/Resources/Program.ja-JP.resx | 3 +++ src/Moonglade.Web/Resources/Program.zh-Hans.resx | 3 +++ src/Moonglade.Web/Resources/Program.zh-Hant.resx | 3 +++ 4 files changed, 12 insertions(+) diff --git a/src/Moonglade.Web/Resources/Program.de-DE.resx b/src/Moonglade.Web/Resources/Program.de-DE.resx index 84c79537c..2983b8fbd 100644 --- a/src/Moonglade.Web/Resources/Program.de-DE.resx +++ b/src/Moonglade.Web/Resources/Program.de-DE.resx @@ -1026,4 +1026,7 @@ <data name="Change Password" xml:space="preserve"> <value>Ändern Sie Ihr Passwort</value> </data> + <data name="Unpublish this post" xml:space="preserve"> + <value>Veröffentlichung dieses Artikels aufheben</value> + </data> </root> \ No newline at end of file diff --git a/src/Moonglade.Web/Resources/Program.ja-JP.resx b/src/Moonglade.Web/Resources/Program.ja-JP.resx index 60056360e..927274f7e 100644 --- a/src/Moonglade.Web/Resources/Program.ja-JP.resx +++ b/src/Moonglade.Web/Resources/Program.ja-JP.resx @@ -1026,4 +1026,7 @@ <data name="Change Password" xml:space="preserve"> <value>パスワードを変更する</value> </data> + <data name="Unpublish this post" xml:space="preserve"> + <value>この記事を非公開にする</value> + </data> </root> \ No newline at end of file diff --git a/src/Moonglade.Web/Resources/Program.zh-Hans.resx b/src/Moonglade.Web/Resources/Program.zh-Hans.resx index 1527a16e5..fd7361fab 100644 --- a/src/Moonglade.Web/Resources/Program.zh-Hans.resx +++ b/src/Moonglade.Web/Resources/Program.zh-Hans.resx @@ -1026,4 +1026,7 @@ <data name="Change Password" xml:space="preserve"> <value>修改密码</value> </data> + <data name="Unpublish this post" xml:space="preserve"> + <value>取消发布这篇文章</value> + </data> </root> \ No newline at end of file diff --git a/src/Moonglade.Web/Resources/Program.zh-Hant.resx b/src/Moonglade.Web/Resources/Program.zh-Hant.resx index 23527387c..e7b6ed8fe 100644 --- a/src/Moonglade.Web/Resources/Program.zh-Hant.resx +++ b/src/Moonglade.Web/Resources/Program.zh-Hant.resx @@ -1029,4 +1029,7 @@ <data name="Change Password" xml:space="preserve"> <value>修改密碼</value> </data> + <data name="Unpublish this post" xml:space="preserve"> + <value>取消發佈這篇文章</value> + </data> </root> \ No newline at end of file From cbb38328c7d6c749046f96249c4d14adbd4f9b61 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 14 Oct 2024 12:25:32 +0800 Subject: [PATCH 15/47] Add modal confirmation for unpublishing posts Updated `EditPost.cshtml` to trigger a Bootstrap modal for the "Unpublish this post" action, enhancing user experience by requiring confirmation. Introduced `_UnpublishPostModal.cshtml` to define the modal's structure, including a warning message, post title, and "Cancel" and "Confirm" buttons. --- src/Moonglade.Web/Pages/Admin/EditPost.cshtml | 3 ++- .../Pages/Admin/_UnpublishPostModal.cshtml | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 src/Moonglade.Web/Pages/Admin/_UnpublishPostModal.cshtml diff --git a/src/Moonglade.Web/Pages/Admin/EditPost.cshtml b/src/Moonglade.Web/Pages/Admin/EditPost.cshtml index af2add345..d6f86b9ed 100644 --- a/src/Moonglade.Web/Pages/Admin/EditPost.cshtml +++ b/src/Moonglade.Web/Pages/Admin/EditPost.cshtml @@ -344,7 +344,7 @@ @if (Model.ViewModel.IsPublished) { <div class="text-center"> - <a class="unpublish-link btn btn-sm btn-outline-secondary" href="#">@SharedLocalizer["Unpublish this post"]</a> + <a class="unpublish-link btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#unpublishPostModal">@SharedLocalizer["Unpublish this post"]</a> </div> } </div> @@ -370,4 +370,5 @@ </form> <partial name="_EditPostHeroImage" /> + <partial name="_UnpublishPostModal" /> </div> \ No newline at end of file diff --git a/src/Moonglade.Web/Pages/Admin/_UnpublishPostModal.cshtml b/src/Moonglade.Web/Pages/Admin/_UnpublishPostModal.cshtml new file mode 100644 index 000000000..243471a23 --- /dev/null +++ b/src/Moonglade.Web/Pages/Admin/_UnpublishPostModal.cshtml @@ -0,0 +1,24 @@ +@model Moonglade.Web.Pages.Admin.EditPostModel + +<div class="modal fade" id="unpublishPostModal" tabindex="-1" role="dialog" aria-labelledby="unpublishPostModalLabel" aria-hidden="true"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="unpublishPostModalLabel">@SharedLocalizer["Unpublish Post"]</h5> + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"> + </button> + </div> + <div class="modal-body"> + <div class="alert alert-warning">@SharedLocalizer["Unpublishing this post will remove it from the public site and turn it into a draft. This will have impact on SEO. Please confirm."]</div> + + <p> + @Model.ViewModel.Title + </p> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">@SharedLocalizer["Cancel"]</button> + <a class="btn btn-danger" href="#">@SharedLocalizer["Confirm"]</a> + </div> + </div> + </div> +</div> \ No newline at end of file From 26edf7b5c8de1b92873e7d377ae3ea66bce0d49b Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 14 Oct 2024 12:27:27 +0800 Subject: [PATCH 16/47] Add new localization entries for multiple languages Added new localization entries to resource files for German, Japanese, Simplified Chinese, and Traditional Chinese. The new entries include: - "Unpublish Post" with translations for each language. - A detailed message explaining the impact of unpublishing a post on SEO, with translations for each language. - The word "Confirm" with translations for each language. --- src/Moonglade.Web/Resources/Program.de-DE.resx | 9 +++++++++ src/Moonglade.Web/Resources/Program.ja-JP.resx | 9 +++++++++ src/Moonglade.Web/Resources/Program.zh-Hans.resx | 9 +++++++++ src/Moonglade.Web/Resources/Program.zh-Hant.resx | 9 +++++++++ 4 files changed, 36 insertions(+) diff --git a/src/Moonglade.Web/Resources/Program.de-DE.resx b/src/Moonglade.Web/Resources/Program.de-DE.resx index 2983b8fbd..94c122251 100644 --- a/src/Moonglade.Web/Resources/Program.de-DE.resx +++ b/src/Moonglade.Web/Resources/Program.de-DE.resx @@ -1029,4 +1029,13 @@ <data name="Unpublish this post" xml:space="preserve"> <value>Veröffentlichung dieses Artikels aufheben</value> </data> + <data name="Unpublish Post" xml:space="preserve"> + <value>Veröffentlichung des Artikels aufheben</value> + </data> + <data name="Unpublishing this post will remove it from the public site and turn it into a draft. This will have impact on SEO. Please confirm." xml:space="preserve"> + <value>Wenn Sie die Veröffentlichung dieses Artikels aufheben, wird er von der öffentlichen Website entfernt und in einen Entwurf umgewandelt. Dies wird sich auf die Suchmaschinenoptimierung auswirken. Bitte bestätigen Sie.</value> + </data> + <data name="Confirm" xml:space="preserve"> + <value>Bestätigen</value> + </data> </root> \ No newline at end of file diff --git a/src/Moonglade.Web/Resources/Program.ja-JP.resx b/src/Moonglade.Web/Resources/Program.ja-JP.resx index 927274f7e..c1207f7fa 100644 --- a/src/Moonglade.Web/Resources/Program.ja-JP.resx +++ b/src/Moonglade.Web/Resources/Program.ja-JP.resx @@ -1029,4 +1029,13 @@ <data name="Unpublish this post" xml:space="preserve"> <value>この記事を非公開にする</value> </data> + <data name="Unpublish Post" xml:space="preserve"> + <value>記事を非公開にする</value> + </data> + <data name="Unpublishing this post will remove it from the public site and turn it into a draft. This will have impact on SEO. Please confirm." xml:space="preserve"> + <value>この記事を非公開にすると、公開サイトから削除され、下書きに変換されます。 これはSEOに影響を与えます。 ご確認ください。</value> + </data> + <data name="Confirm" xml:space="preserve"> + <value>確認する</value> + </data> </root> \ No newline at end of file diff --git a/src/Moonglade.Web/Resources/Program.zh-Hans.resx b/src/Moonglade.Web/Resources/Program.zh-Hans.resx index fd7361fab..6f5a4299c 100644 --- a/src/Moonglade.Web/Resources/Program.zh-Hans.resx +++ b/src/Moonglade.Web/Resources/Program.zh-Hans.resx @@ -1029,4 +1029,13 @@ <data name="Unpublish this post" xml:space="preserve"> <value>取消发布这篇文章</value> </data> + <data name="Unpublish Post" xml:space="preserve"> + <value>取消发布文章</value> + </data> + <data name="Unpublishing this post will remove it from the public site and turn it into a draft. This will have impact on SEO. Please confirm." xml:space="preserve"> + <value>取消发布此文章会将其从公共站点中删除,并将其转换为草稿。这将对 SEO 产生影响。请确认。</value> + </data> + <data name="Confirm" xml:space="preserve"> + <value>确认</value> + </data> </root> \ No newline at end of file diff --git a/src/Moonglade.Web/Resources/Program.zh-Hant.resx b/src/Moonglade.Web/Resources/Program.zh-Hant.resx index e7b6ed8fe..25ac8b908 100644 --- a/src/Moonglade.Web/Resources/Program.zh-Hant.resx +++ b/src/Moonglade.Web/Resources/Program.zh-Hant.resx @@ -1032,4 +1032,13 @@ <data name="Unpublish this post" xml:space="preserve"> <value>取消發佈這篇文章</value> </data> + <data name="Unpublish Post" xml:space="preserve"> + <value>取消發佈文章</value> + </data> + <data name="Unpublishing this post will remove it from the public site and turn it into a draft. This will have impact on SEO. Please confirm." xml:space="preserve"> + <value>取消發佈此文章會將其從公共網站中刪除,並將其轉換為草稿。 這將對 SEO 產生影響。 請確認。</value> + </data> + <data name="Confirm" xml:space="preserve"> + <value>確認</value> + </data> </root> \ No newline at end of file From cf36989a6a4f7bf16023ab975694a4858d6fb834 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 14 Oct 2024 12:36:14 +0800 Subject: [PATCH 17/47] Add feature to unpublish posts in Moonglade app Introduced `UnpublishPostCommand` and handler in `UnpublishPostCommand.cs` to unpublish posts by setting `IsPublished` to false, clearing `PubDateUtc` and `RouteLink`, updating `LastModifiedUtc`, and removing the post from the cache. Added a new endpoint in `PostController.cs` to handle HTTP PUT requests at `"{postId:guid}/unpublish"` and return a `204 No Content` status. --- .../PostFeature/UnpublishPostCommand.cs | 24 +++++++++++++++++++ .../Controllers/PostController.cs | 9 +++++++ 2 files changed, 33 insertions(+) create mode 100644 src/Moonglade.Core/PostFeature/UnpublishPostCommand.cs diff --git a/src/Moonglade.Core/PostFeature/UnpublishPostCommand.cs b/src/Moonglade.Core/PostFeature/UnpublishPostCommand.cs new file mode 100644 index 000000000..46cb6efd2 --- /dev/null +++ b/src/Moonglade.Core/PostFeature/UnpublishPostCommand.cs @@ -0,0 +1,24 @@ +using Edi.CacheAside.InMemory; +using Moonglade.Data; + +namespace Moonglade.Core.PostFeature; + +public record UnpublishPostCommand(Guid Id) : IRequest; + +public class UnpublishPostCommandHandler(MoongladeRepository<PostEntity> repo, ICacheAside cache) : IRequestHandler<UnpublishPostCommand> +{ + public async Task Handle(UnpublishPostCommand request, CancellationToken ct) + { + var post = await repo.GetByIdAsync(request.Id, ct); + if (null == post) return; + + post.IsPublished = false; + post.PubDateUtc = null; + post.RouteLink = null; + post.LastModifiedUtc = DateTime.UtcNow; + + await repo.UpdateAsync(post, ct); + + cache.Remove(BlogCachePartition.Post.ToString(), request.Id.ToString()); + } +} \ No newline at end of file diff --git a/src/Moonglade.Web/Controllers/PostController.cs b/src/Moonglade.Web/Controllers/PostController.cs index 3c2818177..78c48f13f 100644 --- a/src/Moonglade.Web/Controllers/PostController.cs +++ b/src/Moonglade.Web/Controllers/PostController.cs @@ -131,6 +131,15 @@ public async Task<IActionResult> EmptyRecycleBin() return NoContent(); } + [TypeFilter(typeof(ClearBlogCache), Arguments = [BlogCacheType.Subscription | BlogCacheType.SiteMap])] + [HttpPut("{postId:guid}/unpublish")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<IActionResult> Unpublish([NotEmpty] Guid postId) + { + await mediator.Send(new UnpublishPostCommand(postId)); + return NoContent(); + } + [IgnoreAntiforgeryToken] [HttpPost("keep-alive")] [ProducesResponseType(StatusCodes.Status200OK)] From 9b7cd371e779094f3b5259d3939b337b0bfd5f54 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 14 Oct 2024 12:42:31 +0800 Subject: [PATCH 18/47] Enhance "Unpublish" modal functionality - Remove post title from modal body. - Update "Confirm" button to call `UnpublishPost` function with post ID. - Add `UnpublishPost` function to make API call, show success toast, and reload page. --- .../Pages/Admin/_UnpublishPostModal.cshtml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Moonglade.Web/Pages/Admin/_UnpublishPostModal.cshtml b/src/Moonglade.Web/Pages/Admin/_UnpublishPostModal.cshtml index 243471a23..2758e288a 100644 --- a/src/Moonglade.Web/Pages/Admin/_UnpublishPostModal.cshtml +++ b/src/Moonglade.Web/Pages/Admin/_UnpublishPostModal.cshtml @@ -10,15 +10,27 @@ </div> <div class="modal-body"> <div class="alert alert-warning">@SharedLocalizer["Unpublishing this post will remove it from the public site and turn it into a draft. This will have impact on SEO. Please confirm."]</div> - <p> @Model.ViewModel.Title </p> </div> <div class="modal-footer"> <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">@SharedLocalizer["Cancel"]</button> - <a class="btn btn-danger" href="#">@SharedLocalizer["Confirm"]</a> + <a class="unpublish-post-confirm btn btn-danger" href="javascript:UnpublishPost('@Model.ViewModel.PostId');">@SharedLocalizer["Confirm"]</a> </div> </div> </div> -</div> \ No newline at end of file +</div> + +<script> + function UnpublishPost(postId) { + callApi( + `/api/post/${postId}/unpublish`, + 'PUT', + {}, + (resp) => { + blogToast.success('Post unpublished'); + location.reload(); + }); + } +</script> \ No newline at end of file From 286f3c9ecaf914f44cfc2735cf50c6bfa4853dbe Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 14 Oct 2024 12:49:33 +0800 Subject: [PATCH 19/47] Add Japanese reading time calculation to calculateReadingTime Updated `calculateReadingTime` to include Japanese characters. Added `japaneseCharactersPerMinute` constant for average reading speed. Extracted Japanese characters using regex and calculated their reading time, stored in `japaneseReadingTime`. Included Japanese reading time in total reading time, which is now rounded to the nearest minute and displayed in the `reading-time` element. --- src/Moonglade.Web/wwwroot/js/app/viewpost.module.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Moonglade.Web/wwwroot/js/app/viewpost.module.js b/src/Moonglade.Web/wwwroot/js/app/viewpost.module.js index 13a6760bd..459718700 100644 --- a/src/Moonglade.Web/wwwroot/js/app/viewpost.module.js +++ b/src/Moonglade.Web/wwwroot/js/app/viewpost.module.js @@ -145,22 +145,25 @@ export function calculateReadingTime() { const englishWordsPerMinute = 225; // Average reading speed for English const chineseCharactersPerMinute = 450; // Average reading speed for Chinese const germanWordsPerMinute = 225; // Average reading speed for German + const japaneseCharactersPerMinute = 400; // Average reading speed for Japanese // Get the content of the blog post const blogContent = document.querySelector('.post-content').innerText; const englishAndGermanWords = blogContent.match(/\b\w+\b/g) || []; const chineseCharacters = blogContent.match(/[\u4e00-\u9fa5]/g) || []; + const japaneseCharacters = blogContent.match(/[\u3040-\u30FF\u31F0-\u31FF\uFF66-\uFF9F\u4E00-\u9FAF]/g) || []; - // Calculate reading time for English and German (combined) and Chinese + // Calculate reading time for English and German (combined), Chinese, and Japanese const englishAndGermanReadingTime = englishAndGermanWords.length / englishWordsPerMinute; const chineseReadingTime = chineseCharacters.length / chineseCharactersPerMinute; + const japaneseReadingTime = japaneseCharacters.length / japaneseCharactersPerMinute; // Total reading time in minutes - const totalReadingTime = englishAndGermanReadingTime + chineseReadingTime; + const totalReadingTime = englishAndGermanReadingTime + chineseReadingTime + japaneseReadingTime; // Round to nearest minute const roundedReadingTime = Math.ceil(totalReadingTime); document.getElementById('reading-time').innerText = `Estimated Reading Time: ${roundedReadingTime} minute(s)`; -} \ No newline at end of file +} From 37dbea7da88b81a557ec9b7ffd3748234caf2106 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 14 Oct 2024 12:51:27 +0800 Subject: [PATCH 20/47] Update version to 14.12.0-preview.2 Updated the version number in `Directory.Build.props` from `14.12.0-preview.1` to `14.12.0-preview.2`, indicating a new iteration or minor update in the preview phase of the project. --- src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 2abd583df..54010abae 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,6 +5,6 @@ <Copyright>(C) 2024 edi.wang@outlook.com</Copyright> <AssemblyVersion>14.12.0.0</AssemblyVersion> <FileVersion>14.12.0.0</FileVersion> - <Version>14.12.0-preview.1</Version> + <Version>14.12.0-preview.2</Version> </PropertyGroup> </Project> \ No newline at end of file From 12241c88109d83dbf331febd3d3cc262816e5ebc Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Thu, 17 Oct 2024 14:56:05 +0800 Subject: [PATCH 21/47] lazy load sign in page captcha code #830 --- src/Moonglade.Web/Pages/SignIn.cshtml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Moonglade.Web/Pages/SignIn.cshtml b/src/Moonglade.Web/Pages/SignIn.cshtml index 8f2a04362..dad79c28c 100644 --- a/src/Moonglade.Web/Pages/SignIn.cshtml +++ b/src/Moonglade.Web/Pages/SignIn.cshtml @@ -51,11 +51,11 @@ <label asp-for="Username"></label> </div> <div class="form-floating mb-3"> - <input asp-for="Password" class="form-control" required pattern="^(?=.*[a-zA-Z])(?=.*[0-9])[A-Za-z0-9._~!@@#$^&*]{8,}$" placeholder="@Html.DisplayNameFor(m => m.Password)" minlength="8" maxlength="32" /> + <input asp-for="Password" class="form-control" required pattern="^(?=.*[a-zA-Z])(?=.*[0-9])[A-Za-z0-9._~!@@#$^&*]{8,}$" placeholder="@Html.DisplayNameFor(m => m.Password)" minlength="8" maxlength="32" onfocus="showCaptcha()" /> <label asp-for="Password"></label> </div> - <div class="mb-3"> + <div class="mb-3" id="captcha-container" style="display: none;"> <div class="input-group"> <input asp-for="CaptchaCode" minlength="4" @@ -63,7 +63,7 @@ placeholder="@SharedLocalizer["Captcha code"]" autocomplete="off" class="form-control" required /> - <img id="img-captcha" onclick="getNewCaptcha()" src="~/captcha-image" data-bs-toggle="tooltip" data-placement="top" title="@SharedLocalizer["Can't read? Click to change another image."]" /> + <img id="img-captcha" onclick="getNewCaptcha()" data-bs-toggle="tooltip" data-placement="top" title="@SharedLocalizer["Can't read? Click to change another image."]" /> </div> </div> @@ -88,6 +88,14 @@ d = new Date(); document.querySelector('#img-captcha').src = `/captcha-image?${d.getTime()}`; }; + + function showCaptcha() { + var captchaContainer = document.getElementById('captcha-container'); + if (captchaContainer.style.display === 'none') { + captchaContainer.style.display = 'block'; + getNewCaptcha(); + } + } </script> </body> </html> \ No newline at end of file From 85df6aed184777beebaf3f4e5327536c04a69e87 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Thu, 17 Oct 2024 15:35:07 +0800 Subject: [PATCH 22/47] lazy load comment captcha #830 --- src/Moonglade.Web/Pages/_CommentForm.cshtml | 7 ++++--- src/Moonglade.Web/wwwroot/js/app/viewpost.module.js | 8 ++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Moonglade.Web/Pages/_CommentForm.cshtml b/src/Moonglade.Web/Pages/_CommentForm.cshtml index 9af070785..de6a74fe1 100644 --- a/src/Moonglade.Web/Pages/_CommentForm.cshtml +++ b/src/Moonglade.Web/Pages/_CommentForm.cshtml @@ -29,12 +29,13 @@ aria-label="@SharedLocalizer["Your comments (Markdown supported)"]" placeholder="@SharedLocalizer["Your comments (Markdown supported)"]" maxlength="1024" - required></textarea> + required + onfocus="viewpost.showCaptcha()"></textarea> </div> <div class="row"> <div class="col-9"> - <div class="input-group"> - <img id="img-captcha" onclick="viewpost.resetCaptchaImage()" src="~/captcha-image" data-bs-toggle="tooltip" data-placement="top" title="@SharedLocalizer["Can't read? Click to change another image."]" alt="@SharedLocalizer["Captcha code"]" /> + <div class="input-group" id="captcha-container" style="display: none;"> + <img id="img-captcha" onclick="viewpost.resetCaptchaImage()" data-bs-toggle="tooltip" data-placement="top" title="@SharedLocalizer["Can't read? Click to change another image."]" alt="@SharedLocalizer["Captcha code"]" /> <input type="text" aria-label="@SharedLocalizer["Captcha code"]" id="input-comment-captcha" diff --git a/src/Moonglade.Web/wwwroot/js/app/viewpost.module.js b/src/Moonglade.Web/wwwroot/js/app/viewpost.module.js index 459718700..eafd9132c 100644 --- a/src/Moonglade.Web/wwwroot/js/app/viewpost.module.js +++ b/src/Moonglade.Web/wwwroot/js/app/viewpost.module.js @@ -105,6 +105,14 @@ export function resetCaptchaImage() { document.querySelector('#img-captcha').src = `/captcha-image?${d.getTime()}`; } +export function showCaptcha() { + var captchaContainer = document.getElementById('captcha-container'); + if (captchaContainer.style.display === 'none') { + captchaContainer.style.display = 'flex'; + resetCaptchaImage(); + } +} + var btnSubmitComment = '#btn-submit-comment'; export function submitComment(pid) { From d0bfbbb47d8a94771236f8b642670df8e6d54e5b Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Sat, 19 Oct 2024 14:30:36 +0800 Subject: [PATCH 23/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 027478d64..aaa9f8c37 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ A personal blog system that optimized for [**Microsoft Azure**](https://azure.mi This is the way https://edi.wang is deployed, by taking advantage of as many Azure services as possible, the blog can run very fast and secure. There is no automated script to deploy it, you need to manually create all the resources. -![image](https://cdn.edi.wang/web-assets/ediwang-azure-arch-visio-nov2022.png) +![image](https://cdn.edi.wang/web-assets/ediwang-azure-arch-visio-oct2024.png) ### Quick Deploy on Azure (App Service on Linux) From f9d09e885c93c63653813ec491293e33e7dcdfff Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 21 Oct 2024 10:12:21 +0800 Subject: [PATCH 24/47] Add "Experimental" section to appsettings.json Introduced a new "Experimental" section in the `appsettings.json` file. This section includes an empty array for "SocialLinks", intended for future experimental features or settings related to social links. --- src/Moonglade.Web/appsettings.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Moonglade.Web/appsettings.json b/src/Moonglade.Web/appsettings.json index 479a5357d..f5c60583a 100644 --- a/src/Moonglade.Web/appsettings.json +++ b/src/Moonglade.Web/appsettings.json @@ -78,6 +78,9 @@ "Enabled": true, "HeaderName": "Sec-CH-Prefers-Color-Scheme" }, + "Experimental": { + "SocialLinks": [] + }, "Logging": { "LogLevel": { "Default": "Warning", From f7934faf907ad82f7788b026fe58d2ad055912cc Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 21 Oct 2024 10:13:37 +0800 Subject: [PATCH 25/47] Create SocialLink.cs --- src/Moonglade.Configuration/SocialLink.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/Moonglade.Configuration/SocialLink.cs diff --git a/src/Moonglade.Configuration/SocialLink.cs b/src/Moonglade.Configuration/SocialLink.cs new file mode 100644 index 000000000..4269825cc --- /dev/null +++ b/src/Moonglade.Configuration/SocialLink.cs @@ -0,0 +1,10 @@ +namespace Moonglade.Configuration; + +public class SocialLink +{ + public string Name { get; set; } + + public string Icon { get; set; } + + public string Url { get; set; } +} \ No newline at end of file From 358ed45665a0d6f309342fc88ff2cbd7222c2a55 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 21 Oct 2024 10:18:33 +0800 Subject: [PATCH 26/47] Create GetAllSocialLinksQuery.cs --- src/Moonglade.Core/GetAllSocialLinksQuery.cs | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/Moonglade.Core/GetAllSocialLinksQuery.cs diff --git a/src/Moonglade.Core/GetAllSocialLinksQuery.cs b/src/Moonglade.Core/GetAllSocialLinksQuery.cs new file mode 100644 index 000000000..a5c7b49c7 --- /dev/null +++ b/src/Moonglade.Core/GetAllSocialLinksQuery.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Configuration; +using Moonglade.Configuration; + +namespace Moonglade.Core; + +public record GetAllSocialLinksQuery : IRequest<List<SocialLink>>; + +public class GetAllSocialLinksQueryHandler(IConfiguration configuration) : IRequestHandler<GetAllSocialLinksQuery, List<SocialLink>> +{ + public Task<List<SocialLink>> Handle(GetAllSocialLinksQuery request, CancellationToken cancellationToken) + { + var section = configuration.GetSection("Experimental:SocialLinks"); + + if (!section.Exists()) + { + return Task.FromResult(new List<SocialLink>()); + } + + var links = section.Get<List<SocialLink>>(); + return Task.FromResult(links ?? new List<SocialLink>()); + } +} \ No newline at end of file From 1fe159a3395ebc2a3aef0e773397a285929743e2 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 21 Oct 2024 10:19:00 +0800 Subject: [PATCH 27/47] Update GetAllSocialLinksQuery.cs --- src/Moonglade.Core/GetAllSocialLinksQuery.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Moonglade.Core/GetAllSocialLinksQuery.cs b/src/Moonglade.Core/GetAllSocialLinksQuery.cs index a5c7b49c7..77cdd0fb2 100644 --- a/src/Moonglade.Core/GetAllSocialLinksQuery.cs +++ b/src/Moonglade.Core/GetAllSocialLinksQuery.cs @@ -7,7 +7,7 @@ public record GetAllSocialLinksQuery : IRequest<List<SocialLink>>; public class GetAllSocialLinksQueryHandler(IConfiguration configuration) : IRequestHandler<GetAllSocialLinksQuery, List<SocialLink>> { - public Task<List<SocialLink>> Handle(GetAllSocialLinksQuery request, CancellationToken cancellationToken) + public Task<List<SocialLink>> Handle(GetAllSocialLinksQuery request, CancellationToken ct) { var section = configuration.GetSection("Experimental:SocialLinks"); From dbcb7f89897638eb03bbea502338c217338c9c3c Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 21 Oct 2024 10:21:45 +0800 Subject: [PATCH 28/47] Create SocialLinkViewComponent.cs --- .../ViewComponents/SocialLinkViewComponent.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/Moonglade.Web/ViewComponents/SocialLinkViewComponent.cs diff --git a/src/Moonglade.Web/ViewComponents/SocialLinkViewComponent.cs b/src/Moonglade.Web/ViewComponents/SocialLinkViewComponent.cs new file mode 100644 index 000000000..f44ff1b86 --- /dev/null +++ b/src/Moonglade.Web/ViewComponents/SocialLinkViewComponent.cs @@ -0,0 +1,18 @@ +namespace Moonglade.Web.ViewComponents; + +public class SocialLinkViewComponent(ILogger<SocialLinkViewComponent> logger, IMediator mediator) : ViewComponent +{ + public async Task<IViewComponentResult> InvokeAsync() + { + try + { + var links = await mediator.Send(new GetAllSocialLinksQuery()); + return View(links ?? []); + } + catch (Exception e) + { + logger.LogError(e, "Error Reading SocialLink."); + return Content("ERROR"); + } + } +} \ No newline at end of file From 2996dd52043da8407d5a95c2185ac6c99794bd8b Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 21 Oct 2024 10:21:46 +0800 Subject: [PATCH 29/47] Create Default.cshtml --- .../Components/SocialLink/Default.cshtml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/Moonglade.Web/Pages/Components/SocialLink/Default.cshtml diff --git a/src/Moonglade.Web/Pages/Components/SocialLink/Default.cshtml b/src/Moonglade.Web/Pages/Components/SocialLink/Default.cshtml new file mode 100644 index 000000000..2ab2d3d27 --- /dev/null +++ b/src/Moonglade.Web/Pages/Components/SocialLink/Default.cshtml @@ -0,0 +1,19 @@ +@using Moonglade.Utils +@model List<SocialLink> + +@if (Model.Any()) +{ + <div class="aside-widget p-3 rounded-3 shadow-sm border"> + <h6 class="card-subtitle mb-3 text-secondary">@SharedLocalizer["Social Links"]</h6> + + <div role="list"> + @foreach (var item in Model.OrderBy(c => c.Name)) + { + <a href="@Helper.SterilizeLink(item.Url)" target="_blank" class="d-block mb-3 mt-2" role="listitem"> + <i class="@item.Icon me-1"></i> + @item.Name + </a> + } + </div> + </div> +} \ No newline at end of file From ba26d64288eeb32fd6212749c13ae1ee676dc0dc Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 21 Oct 2024 10:23:59 +0800 Subject: [PATCH 30/47] Update _Aside.cshtml --- src/Moonglade.Web/Pages/Shared/_Aside.cshtml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Moonglade.Web/Pages/Shared/_Aside.cshtml b/src/Moonglade.Web/Pages/Shared/_Aside.cshtml index 6a47ad07d..7ca2db302 100644 --- a/src/Moonglade.Web/Pages/Shared/_Aside.cshtml +++ b/src/Moonglade.Web/Pages/Shared/_Aside.cshtml @@ -40,6 +40,10 @@ </section> } + <section id="aside-sociallink" class="mb-4"> + @await Component.InvokeAsync("SocialLink") + </section> + @if (BlogConfig.GeneralSettings.WidgetsFriendLink) { <section id="aside-friendlink" class="mb-4"> From 673c31ae0aa880a39d0332d98ab2c1421767bd86 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 21 Oct 2024 10:24:44 +0800 Subject: [PATCH 31/47] Update _Aside.cshtml --- src/Moonglade.Web/Pages/Shared/_Aside.cshtml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Moonglade.Web/Pages/Shared/_Aside.cshtml b/src/Moonglade.Web/Pages/Shared/_Aside.cshtml index 7ca2db302..e73ed8ebb 100644 --- a/src/Moonglade.Web/Pages/Shared/_Aside.cshtml +++ b/src/Moonglade.Web/Pages/Shared/_Aside.cshtml @@ -40,9 +40,12 @@ </section> } - <section id="aside-sociallink" class="mb-4"> - @await Component.InvokeAsync("SocialLink") - </section> + @if (Configuration.GetSection("Experimental:SocialLinks").Exists()) + { + <section id="aside-sociallink" class="mb-4"> + @await Component.InvokeAsync("SocialLink") + </section> + } @if (BlogConfig.GeneralSettings.WidgetsFriendLink) { From 3a9930ee09f55407e5cd594aabdf4122c455613b Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 21 Oct 2024 13:38:24 +0800 Subject: [PATCH 32/47] Fix umlaut display issue by decoding HTML entities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added `using System.Web;` and `using System.Xml.Linq;` to `ContentProcessor.cs`. Modified the `ContentProcessor` class to decode HTML entities in plain text before ellipsizing it. Introduced a new method `HtmlDecode` to address issue #833, ensuring umlauts like (ä, ö, ü) are displayed correctly in the abstract. --- src/Moonglade.Utils/ContentProcessor.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Moonglade.Utils/ContentProcessor.cs b/src/Moonglade.Utils/ContentProcessor.cs index 883184db1..54ebee947 100644 --- a/src/Moonglade.Utils/ContentProcessor.cs +++ b/src/Moonglade.Utils/ContentProcessor.cs @@ -1,5 +1,6 @@ using Markdig; using System.Text.RegularExpressions; +using System.Web; using System.Xml.Linq; namespace Moonglade.Utils; @@ -26,10 +27,14 @@ public static string GetPostAbstract(string content, int wordCount, bool useMark MarkdownToContent(content, MarkdownConvertType.Text) : RemoveTags(content); - var result = plainText.Ellipsize(wordCount); + var decodedText = HtmlDecode(plainText); + var result = decodedText.Ellipsize(wordCount); return result; } + // Fix #833 - umlauts like (ä,ö,ü). are not displayed correctly in the abstract + public static string HtmlDecode(string content) => HttpUtility.HtmlDecode(content); + public static string RemoveTags(string html) { if (string.IsNullOrEmpty(html)) From 6e3201cf5c2f6e6ccf8be636588b6d19a6a95cce Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 21 Oct 2024 13:39:24 +0800 Subject: [PATCH 33/47] Decode ContentAbstract to fix old data --- src/Moonglade.Web/Pages/Shared/_PostListEntry.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Moonglade.Web/Pages/Shared/_PostListEntry.cshtml b/src/Moonglade.Web/Pages/Shared/_PostListEntry.cshtml index af03d0477..b250aceb1 100644 --- a/src/Moonglade.Web/Pages/Shared/_PostListEntry.cshtml +++ b/src/Moonglade.Web/Pages/Shared/_PostListEntry.cshtml @@ -30,7 +30,7 @@ } </h3> - <abbr class="post-summary-text d-block mb-3">@(Model.ContentAbstract)</abbr> + <abbr class="post-summary-text d-block mb-3">@(ContentProcessor.HtmlDecode(Model.ContentAbstract))</abbr> @if (null != Model.Tags) { From 125a6e334629b1f2c01ad4acb154c48e2e05e740 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 21 Oct 2024 13:43:43 +0800 Subject: [PATCH 34/47] code clean up --- src/Moonglade.Web/Program.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Moonglade.Web/Program.cs b/src/Moonglade.Web/Program.cs index 552193ebc..0430e1e62 100644 --- a/src/Moonglade.Web/Program.cs +++ b/src/Moonglade.Web/Program.cs @@ -1,6 +1,3 @@ -using System.Globalization; -using System.Net; -using System.Text.Json.Serialization; using Edi.Captcha; using Edi.PasswordGenerator; using Microsoft.AspNetCore.Rewrite; @@ -17,6 +14,9 @@ using Moonglade.Web.Handlers; using Moonglade.Webmention; using SixLabors.Fonts; +using System.Globalization; +using System.Net; +using System.Text.Json.Serialization; using Encoder = Moonglade.Web.Configuration.Encoder; namespace Moonglade.Web; From 8861e863b8ce7807764d1a29665b6a82d724f716 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Mon, 21 Oct 2024 13:43:56 +0800 Subject: [PATCH 35/47] bump version --- src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 54010abae..7dbd12ab3 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,6 +5,6 @@ <Copyright>(C) 2024 edi.wang@outlook.com</Copyright> <AssemblyVersion>14.12.0.0</AssemblyVersion> <FileVersion>14.12.0.0</FileVersion> - <Version>14.12.0-preview.2</Version> + <Version>14.12.0-beta.1</Version> </PropertyGroup> </Project> \ No newline at end of file From ccb84be072af05568a49a151593e791a946ae53f Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Tue, 22 Oct 2024 08:26:28 +0800 Subject: [PATCH 36/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aaa9f8c37..72ae29f33 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ A personal blog system that optimized for [**Microsoft Azure**](https://azure.mi This is the way https://edi.wang is deployed, by taking advantage of as many Azure services as possible, the blog can run very fast and secure. There is no automated script to deploy it, you need to manually create all the resources. -![image](https://cdn.edi.wang/web-assets/ediwang-azure-arch-visio-oct2024.png) +![image](https://cdn.edi.wang/web-assets/ediwang-azure-arch-visio-oct2024.svg) ### Quick Deploy on Azure (App Service on Linux) From 356e1652bd4d061fbd7e10bca02fc4f5dffbe0a7 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Tue, 22 Oct 2024 08:28:39 +0800 Subject: [PATCH 37/47] Update Npgsql.EntityFrameworkCore.PostgreSQL to 8.0.10 Upgraded the Npgsql.EntityFrameworkCore.PostgreSQL package from version 8.0.8 to 8.0.10 in the Moonglade.Data.PostgreSql.csproj file. This update includes potential bug fixes, performance improvements, and new features available in the newer version. --- src/Moonglade.Data.PostgreSql/Moonglade.Data.PostgreSql.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Moonglade.Data.PostgreSql/Moonglade.Data.PostgreSql.csproj b/src/Moonglade.Data.PostgreSql/Moonglade.Data.PostgreSql.csproj index 25fbfcdd5..775db1296 100644 --- a/src/Moonglade.Data.PostgreSql/Moonglade.Data.PostgreSql.csproj +++ b/src/Moonglade.Data.PostgreSql/Moonglade.Data.PostgreSql.csproj @@ -10,7 +10,7 @@ <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> - <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.8" /> + <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.10" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\Moonglade.Data\Moonglade.Data.csproj" /> From 5ba5b5b8e83ab293b13bd6a133b891d43ab78377 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Tue, 22 Oct 2024 12:22:10 +0800 Subject: [PATCH 38/47] Add SocialLinkSettings to BlogConfig and initialization Added a new property `SocialLinkSettings` to the `IBlogConfig` interface and implemented it in the `BlogConfig` class. Updated the `LoadFromConfig` method to initialize `SocialLinkSettings` from the configuration dictionary. Introduced a new `SocialLinkSettings` class with properties `IsEnabled` and `Links`, and provided a static `DefaultValue`. Updated `BlogConfigInitializer` to handle `SocialLinkSettings` initialization using the mediator pattern. --- src/Moonglade.Configuration/BlogConfig.cs | 4 ++++ src/Moonglade.Configuration/SocialLink.cs | 14 ++++++++++++++ src/Moonglade.Setup/BlogConfigInitializer.cs | 4 ++++ 3 files changed, 22 insertions(+) diff --git a/src/Moonglade.Configuration/BlogConfig.cs b/src/Moonglade.Configuration/BlogConfig.cs index e76004eb8..6ae3a5d40 100644 --- a/src/Moonglade.Configuration/BlogConfig.cs +++ b/src/Moonglade.Configuration/BlogConfig.cs @@ -14,6 +14,7 @@ public interface IBlogConfig CustomStyleSheetSettings CustomStyleSheetSettings { get; set; } CustomMenuSettings CustomMenuSettings { get; set; } LocalAccountSettings LocalAccountSettings { get; set; } + SocialLinkSettings SocialLinkSettings { get; set; } SystemManifestSettings SystemManifestSettings { get; set; } IEnumerable<int> LoadFromConfig(IDictionary<string, string> config); @@ -40,6 +41,8 @@ public class BlogConfig : IBlogConfig public LocalAccountSettings LocalAccountSettings { get; set; } + public SocialLinkSettings SocialLinkSettings { get; set; } + public SystemManifestSettings SystemManifestSettings { get; set; } public IEnumerable<int> LoadFromConfig(IDictionary<string, string> config) @@ -53,6 +56,7 @@ public IEnumerable<int> LoadFromConfig(IDictionary<string, string> config) CustomStyleSheetSettings = AssignValueForConfigItem(7, CustomStyleSheetSettings.DefaultValue, config); CustomMenuSettings = AssignValueForConfigItem(10, CustomMenuSettings.DefaultValue, config); LocalAccountSettings = AssignValueForConfigItem(11, LocalAccountSettings.DefaultValue, config); + SocialLinkSettings = AssignValueForConfigItem(12, SocialLinkSettings.DefaultValue, config); // Special case SystemManifestSettings = AssignValueForConfigItem(99, SystemManifestSettings.DefaultValue, config); diff --git a/src/Moonglade.Configuration/SocialLink.cs b/src/Moonglade.Configuration/SocialLink.cs index 4269825cc..792169df9 100644 --- a/src/Moonglade.Configuration/SocialLink.cs +++ b/src/Moonglade.Configuration/SocialLink.cs @@ -1,5 +1,19 @@ namespace Moonglade.Configuration; +public class SocialLinkSettings : IBlogSettings +{ + public bool IsEnabled { get; set; } + + public List<SocialLink> Links { get; set; } + + public static SocialLinkSettings DefaultValue => + new() + { + IsEnabled = false, + Links = new() + }; +} + public class SocialLink { public string Name { get; set; } diff --git a/src/Moonglade.Setup/BlogConfigInitializer.cs b/src/Moonglade.Setup/BlogConfigInitializer.cs index 70945dcd5..fb7e225a6 100644 --- a/src/Moonglade.Setup/BlogConfigInitializer.cs +++ b/src/Moonglade.Setup/BlogConfigInitializer.cs @@ -59,6 +59,10 @@ await mediator.Send(new AddDefaultConfigurationCommand(key, nameof(CustomMenuSet await mediator.Send(new AddDefaultConfigurationCommand(key, nameof(LocalAccountSettings), LocalAccountSettings.DefaultValue.ToJson())); break; + case 12: + await mediator.Send(new AddDefaultConfigurationCommand(key, nameof(SocialLinkSettings), + SocialLinkSettings.DefaultValue.ToJson())); + break; case 99: await mediator.Send(new AddDefaultConfigurationCommand(key, nameof(SystemManifestSettings), isNew ? From 0074982631f0f43fec7ada0366de4e76f991b3c6 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Tue, 22 Oct 2024 12:23:59 +0800 Subject: [PATCH 39/47] Add POST endpoint for updating social link settings Introduced a new POST endpoint in SettingsController mapped to "social-link". This endpoint updates the SocialLinkSettings in blogConfig with the provided model, saves the configuration asynchronously, and returns a 204 No Content response. --- src/Moonglade.Web/Controllers/SettingsController.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Moonglade.Web/Controllers/SettingsController.cs b/src/Moonglade.Web/Controllers/SettingsController.cs index 2e35972ae..fea25fda0 100644 --- a/src/Moonglade.Web/Controllers/SettingsController.cs +++ b/src/Moonglade.Web/Controllers/SettingsController.cs @@ -153,6 +153,16 @@ public async Task<IActionResult> Advanced(AdvancedSettings model) return NoContent(); } + [HttpPost("social-link")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task<IActionResult> SocialLink(SocialLinkSettings model) + { + blogConfig.SocialLinkSettings = model; + + await SaveConfigAsync(blogConfig.SocialLinkSettings); + return NoContent(); + } + [HttpPost("reset")] [ProducesResponseType(StatusCodes.Status202Accepted)] public async Task<IActionResult> Reset(BlogDbContext context, IHostApplicationLifetime applicationLifetime) From ec4a6b3bbc1cd64a02429a8db6c6cba8be13852c Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Tue, 22 Oct 2024 12:27:02 +0800 Subject: [PATCH 40/47] Refactor social links config to use IBlogConfig Refactored `GetAllSocialLinksQueryHandler` to use `IBlogConfig` instead of `IConfiguration` for configuration management. Updated the handler to access social link settings directly from `blogConfig.SocialLinkSettings` and check if the feature is enabled via `IsEnabled`. Modified `_Aside.cshtml` to reflect the new configuration structure. Removed the `Experimental` section from `appsettings.json` as social links are now managed through `IBlogConfig`. These changes improve code readability, maintainability, and simplify the logic for managing social links. --- src/Moonglade.Core/GetAllSocialLinksQuery.cs | 11 +++++------ src/Moonglade.Web/Pages/Shared/_Aside.cshtml | 2 +- src/Moonglade.Web/appsettings.json | 3 --- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Moonglade.Core/GetAllSocialLinksQuery.cs b/src/Moonglade.Core/GetAllSocialLinksQuery.cs index 77cdd0fb2..276793df7 100644 --- a/src/Moonglade.Core/GetAllSocialLinksQuery.cs +++ b/src/Moonglade.Core/GetAllSocialLinksQuery.cs @@ -1,22 +1,21 @@ -using Microsoft.Extensions.Configuration; -using Moonglade.Configuration; +using Moonglade.Configuration; namespace Moonglade.Core; public record GetAllSocialLinksQuery : IRequest<List<SocialLink>>; -public class GetAllSocialLinksQueryHandler(IConfiguration configuration) : IRequestHandler<GetAllSocialLinksQuery, List<SocialLink>> +public class GetAllSocialLinksQueryHandler(IBlogConfig blogConfig) : IRequestHandler<GetAllSocialLinksQuery, List<SocialLink>> { public Task<List<SocialLink>> Handle(GetAllSocialLinksQuery request, CancellationToken ct) { - var section = configuration.GetSection("Experimental:SocialLinks"); + var section = blogConfig.SocialLinkSettings; - if (!section.Exists()) + if (!section.IsEnabled) { return Task.FromResult(new List<SocialLink>()); } - var links = section.Get<List<SocialLink>>(); + var links = blogConfig.SocialLinkSettings.Links; return Task.FromResult(links ?? new List<SocialLink>()); } } \ No newline at end of file diff --git a/src/Moonglade.Web/Pages/Shared/_Aside.cshtml b/src/Moonglade.Web/Pages/Shared/_Aside.cshtml index e73ed8ebb..18c2c2726 100644 --- a/src/Moonglade.Web/Pages/Shared/_Aside.cshtml +++ b/src/Moonglade.Web/Pages/Shared/_Aside.cshtml @@ -40,7 +40,7 @@ </section> } - @if (Configuration.GetSection("Experimental:SocialLinks").Exists()) + @if (BlogConfig.SocialLinkSettings.IsEnabled) { <section id="aside-sociallink" class="mb-4"> @await Component.InvokeAsync("SocialLink") diff --git a/src/Moonglade.Web/appsettings.json b/src/Moonglade.Web/appsettings.json index f5c60583a..479a5357d 100644 --- a/src/Moonglade.Web/appsettings.json +++ b/src/Moonglade.Web/appsettings.json @@ -78,9 +78,6 @@ "Enabled": true, "HeaderName": "Sec-CH-Prefers-Color-Scheme" }, - "Experimental": { - "SocialLinks": [] - }, "Logging": { "LogLevel": { "Default": "Warning", From 2e0ded02003c5e426d4a372aa6ae13e2927edea3 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Tue, 22 Oct 2024 12:38:08 +0800 Subject: [PATCH 41/47] Add Social Links management page and settings model Added a `using` directive for `System.ComponentModel.DataAnnotations` in `SocialLink.cs`. Introduced a new class `SocialLinkSettingsJsonModel` in `SocialLink.cs` with properties `IsEnabled` and `JsonData`, where `JsonData` has a `MaxLength` attribute of 1024. Added a new navigation link for "Social Links" in `_SettingsHeader.cshtml`. Created a new Razor page `SocialLinks.cshtml` for managing social link settings, including an anti-forgery token for security, initialization of `SocialLinkSettingsJsonModel` with current settings, script sections for loading Monaco editor and handling form submission, styling for the Monaco editor container, a warning alert indicating the feature is under development, and a form for enabling/disabling social links and editing JSON data using the Monaco editor. --- src/Moonglade.Configuration/SocialLink.cs | 12 ++- .../Pages/Settings/SocialLinks.cshtml | 77 +++++++++++++++++++ .../Pages/Settings/_SettingsHeader.cshtml | 3 + 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/Moonglade.Web/Pages/Settings/SocialLinks.cshtml diff --git a/src/Moonglade.Configuration/SocialLink.cs b/src/Moonglade.Configuration/SocialLink.cs index 792169df9..108386f30 100644 --- a/src/Moonglade.Configuration/SocialLink.cs +++ b/src/Moonglade.Configuration/SocialLink.cs @@ -1,4 +1,6 @@ -namespace Moonglade.Configuration; +using System.ComponentModel.DataAnnotations; + +namespace Moonglade.Configuration; public class SocialLinkSettings : IBlogSettings { @@ -21,4 +23,12 @@ public class SocialLink public string Icon { get; set; } public string Url { get; set; } +} + +public class SocialLinkSettingsJsonModel +{ + public bool IsEnabled { get; set; } + + [MaxLength(1024)] + public string JsonData { get; set; } } \ No newline at end of file diff --git a/src/Moonglade.Web/Pages/Settings/SocialLinks.cshtml b/src/Moonglade.Web/Pages/Settings/SocialLinks.cshtml new file mode 100644 index 000000000..357091873 --- /dev/null +++ b/src/Moonglade.Web/Pages/Settings/SocialLinks.cshtml @@ -0,0 +1,77 @@ +@page "/admin/settings/social-links" +@Html.AntiForgeryToken() +@{ + var bc = BlogConfig.SocialLinkSettings; + var settings = new SocialLinkSettingsJsonModel + { + IsEnabled = bc.IsEnabled, + JsonData = bc.Links.ToJson(true) + }; +} + +@section scripts { + <partial name="_MonacoLoaderScript" /> + <script> + var jsonContentEditor = null; + + require(['vs/editor/editor.main'], function () { + jsonContentEditor = initEditor('JsonContentEditor', "#settings_JsonData", 'json'); + }); + </script> + <script type="module"> + import * as settings from '/js/app/admin.settings.module.js'; + + function handleSubmit(event) { + assignEditorValues(jsonContentEditor, "#settings_JsonData"); + settings.handleSettingsSubmit(event); + } + + const form = document.querySelector('#form-settings'); + form.addEventListener('submit', handleSubmit); + </script> +} + +@section head { + <style> + .monaco-target { + border: 1px solid var(--bs-border-color); + width: 100%; + min-height: calc(100vh - 370px); + } + </style> +} + +<div class="alert alert-warning"> + <i class="bi-exclamation-triangle"></i> This feature is under development. It is currently in preview. A GUI editor will be available in the future. +</div> + +<form id="form-settings" asp-controller="Settings" asp-action="SocialLink"> + <div class="admin-settings-entry-container"> + <div class="settings-entry row align-items-center py-3 px-2 rounded-3 shadow-sm border mb-2"> + <div class="col-auto"> + <i class="bi-menu-app settings-entry-icon"></i> + </div> + <div class="col"> + <label asp-for="@settings.IsEnabled" class="form-check-label"></label> + </div> + <div class="col-md-5 text-end"> + <div class="form-check form-switch form-control-lg"> + <input type="hidden" name="IsEnabled" value="false"> + <input type="checkbox" name="IsEnabled" value="true" class="form-check-input" @(@settings.IsEnabled + ? "checked" : null) /> + </div> + </div> + </div> + + <div id="JsonContentEditor" class="monaco-target p-3 rounded-3 shadow-sm border"> + </div> + </div> + + <textarea asp-for="@settings.JsonData" class="settings-jsoncontent-textarea d-none"></textarea> + + <div class="admin-settings-action-container border-top pt-2 mt-2"> + <button type="submit" class="btn btn-outline-accent" id="btn-save-settings"> + @SharedLocalizer["Save"] + </button> + </div> +</form> \ No newline at end of file diff --git a/src/Moonglade.Web/Pages/Settings/_SettingsHeader.cshtml b/src/Moonglade.Web/Pages/Settings/_SettingsHeader.cshtml index ba7999249..1c01cdb6b 100644 --- a/src/Moonglade.Web/Pages/Settings/_SettingsHeader.cshtml +++ b/src/Moonglade.Web/Pages/Settings/_SettingsHeader.cshtml @@ -22,6 +22,9 @@ <li role="presentation" class="nav-item nav-item-non-margin"> <a class="nav-link @(currentPage == "/Settings/Notification" ? "active" : null)" asp-page="./Notification">@SharedLocalizer["Notification"]</a> </li> + <li role="presentation" class="nav-item nav-item-non-margin"> + <a class="nav-link @(currentPage == "/Settings/SocialLinks" ? "active" : null)" asp-page="./SocialLinks">@SharedLocalizer["Social Links"]</a> + </li> <li role="presentation" class="nav-item nav-item-non-margin"> <a class="nav-link @(currentPage == "/Settings/Advanced" ? "active" : null)" asp-page="./Advanced">@SharedLocalizer["Advanced"]</a> </li> From ace486eca3b1cef41238d5ef203126b10ca09b24 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Tue, 22 Oct 2024 12:44:38 +0800 Subject: [PATCH 42/47] Refactor SocialLink handling to use arrays instead of lists Updated SocialLinkSettings, GetAllSocialLinksQuery, and SettingsController to use SocialLink[] instead of List<SocialLink>. Adjusted default value initializations, return types, and added validation for JsonData in SettingsController. Updated Default.cshtml view to reflect these changes. --- src/Moonglade.Configuration/SocialLink.cs | 4 ++-- src/Moonglade.Core/GetAllSocialLinksQuery.cs | 10 +++++----- .../Controllers/SettingsController.cs | 14 ++++++++++++-- .../Pages/Components/SocialLink/Default.cshtml | 2 +- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Moonglade.Configuration/SocialLink.cs b/src/Moonglade.Configuration/SocialLink.cs index 108386f30..100bb152b 100644 --- a/src/Moonglade.Configuration/SocialLink.cs +++ b/src/Moonglade.Configuration/SocialLink.cs @@ -6,13 +6,13 @@ public class SocialLinkSettings : IBlogSettings { public bool IsEnabled { get; set; } - public List<SocialLink> Links { get; set; } + public SocialLink[] Links { get; set; } = []; public static SocialLinkSettings DefaultValue => new() { IsEnabled = false, - Links = new() + Links = [] }; } diff --git a/src/Moonglade.Core/GetAllSocialLinksQuery.cs b/src/Moonglade.Core/GetAllSocialLinksQuery.cs index 276793df7..2302d6c71 100644 --- a/src/Moonglade.Core/GetAllSocialLinksQuery.cs +++ b/src/Moonglade.Core/GetAllSocialLinksQuery.cs @@ -2,20 +2,20 @@ namespace Moonglade.Core; -public record GetAllSocialLinksQuery : IRequest<List<SocialLink>>; +public record GetAllSocialLinksQuery : IRequest<SocialLink[]>; -public class GetAllSocialLinksQueryHandler(IBlogConfig blogConfig) : IRequestHandler<GetAllSocialLinksQuery, List<SocialLink>> +public class GetAllSocialLinksQueryHandler(IBlogConfig blogConfig) : IRequestHandler<GetAllSocialLinksQuery, SocialLink[]> { - public Task<List<SocialLink>> Handle(GetAllSocialLinksQuery request, CancellationToken ct) + public Task<SocialLink[]> Handle(GetAllSocialLinksQuery request, CancellationToken ct) { var section = blogConfig.SocialLinkSettings; if (!section.IsEnabled) { - return Task.FromResult(new List<SocialLink>()); + return Task.FromResult(Array.Empty<SocialLink>()); } var links = blogConfig.SocialLinkSettings.Links; - return Task.FromResult(links ?? new List<SocialLink>()); + return Task.FromResult(links); } } \ No newline at end of file diff --git a/src/Moonglade.Web/Controllers/SettingsController.cs b/src/Moonglade.Web/Controllers/SettingsController.cs index fea25fda0..2241a6a85 100644 --- a/src/Moonglade.Web/Controllers/SettingsController.cs +++ b/src/Moonglade.Web/Controllers/SettingsController.cs @@ -155,9 +155,19 @@ public async Task<IActionResult> Advanced(AdvancedSettings model) [HttpPost("social-link")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public async Task<IActionResult> SocialLink(SocialLinkSettings model) + public async Task<IActionResult> SocialLink(SocialLinkSettingsJsonModel model) { - blogConfig.SocialLinkSettings = model; + if (model.IsEnabled && string.IsNullOrWhiteSpace(model.JsonData)) + { + ModelState.AddModelError(nameof(SocialLinkSettingsJsonModel.JsonData), "JsonData is required"); + return BadRequest(ModelState.CombineErrorMessages()); + } + + blogConfig.SocialLinkSettings = new() + { + IsEnabled = model.IsEnabled, + Links = model.JsonData.FromJson<SocialLink[]>() + }; await SaveConfigAsync(blogConfig.SocialLinkSettings); return NoContent(); diff --git a/src/Moonglade.Web/Pages/Components/SocialLink/Default.cshtml b/src/Moonglade.Web/Pages/Components/SocialLink/Default.cshtml index 2ab2d3d27..26ce18ab1 100644 --- a/src/Moonglade.Web/Pages/Components/SocialLink/Default.cshtml +++ b/src/Moonglade.Web/Pages/Components/SocialLink/Default.cshtml @@ -1,5 +1,5 @@ @using Moonglade.Utils -@model List<SocialLink> +@model SocialLink[] @if (Model.Any()) { From ed533d1fe71747ba0e816959e054a8681c8b9a49 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Tue, 22 Oct 2024 12:49:02 +0800 Subject: [PATCH 43/47] Enhance UI layout and admin toolbar - Increase min-height of `.monaco-target` element for better content space. - Reposition alert message within form and wrap in a new `div`. - Add `@section admintoolbar` with `_SettingsHeader` partial view. - Reformat `checked` attribute logic in `form-check-input` for readability. --- .../Pages/Settings/SocialLinks.cshtml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Moonglade.Web/Pages/Settings/SocialLinks.cshtml b/src/Moonglade.Web/Pages/Settings/SocialLinks.cshtml index 357091873..a74cef2f6 100644 --- a/src/Moonglade.Web/Pages/Settings/SocialLinks.cshtml +++ b/src/Moonglade.Web/Pages/Settings/SocialLinks.cshtml @@ -36,17 +36,22 @@ .monaco-target { border: 1px solid var(--bs-border-color); width: 100%; - min-height: calc(100vh - 370px); + min-height: calc(100vh - 440px); } </style> } -<div class="alert alert-warning"> - <i class="bi-exclamation-triangle"></i> This feature is under development. It is currently in preview. A GUI editor will be available in the future. -</div> +@section admintoolbar { + <partial name="_SettingsHeader" /> +} <form id="form-settings" asp-controller="Settings" asp-action="SocialLink"> <div class="admin-settings-entry-container"> + + <div class="alert alert-warning"> + <i class="bi-exclamation-triangle"></i> This feature is under development. It is currently in preview. A GUI editor will be available in the future. + </div> + <div class="settings-entry row align-items-center py-3 px-2 rounded-3 shadow-sm border mb-2"> <div class="col-auto"> <i class="bi-menu-app settings-entry-icon"></i> @@ -58,7 +63,8 @@ <div class="form-check form-switch form-control-lg"> <input type="hidden" name="IsEnabled" value="false"> <input type="checkbox" name="IsEnabled" value="true" class="form-check-input" @(@settings.IsEnabled - ? "checked" : null) /> + ? "checked" + : null)/> </div> </div> </div> From 8b974468a6b610d298cec64de834ee3f9991d044 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Tue, 22 Oct 2024 12:51:23 +0800 Subject: [PATCH 44/47] Add new properties and update translations Added `IsEnabled` and `JsonData` properties to `SocialLinkSettingsJsonModel` in `SocialLink.cs`. Updated `Program.de-DE.resx`, `Program.ja-JP.resx`, `Program.zh-Hans.resx`, and `Program.zh-Hant.resx` with translations for "Enable Social Links" and "Social Links". --- src/Moonglade.Configuration/SocialLink.cs | 1 + src/Moonglade.Web/Resources/Program.de-DE.resx | 6 ++++++ src/Moonglade.Web/Resources/Program.ja-JP.resx | 6 ++++++ src/Moonglade.Web/Resources/Program.zh-Hans.resx | 6 ++++++ src/Moonglade.Web/Resources/Program.zh-Hant.resx | 6 ++++++ 5 files changed, 25 insertions(+) diff --git a/src/Moonglade.Configuration/SocialLink.cs b/src/Moonglade.Configuration/SocialLink.cs index 100bb152b..984bbce6d 100644 --- a/src/Moonglade.Configuration/SocialLink.cs +++ b/src/Moonglade.Configuration/SocialLink.cs @@ -27,6 +27,7 @@ public class SocialLink public class SocialLinkSettingsJsonModel { + [Display(Name = "Enable Social Links")] public bool IsEnabled { get; set; } [MaxLength(1024)] diff --git a/src/Moonglade.Web/Resources/Program.de-DE.resx b/src/Moonglade.Web/Resources/Program.de-DE.resx index 94c122251..9507bab84 100644 --- a/src/Moonglade.Web/Resources/Program.de-DE.resx +++ b/src/Moonglade.Web/Resources/Program.de-DE.resx @@ -1038,4 +1038,10 @@ <data name="Confirm" xml:space="preserve"> <value>Bestätigen</value> </data> + <data name="Enable Social Links" xml:space="preserve"> + <value>Social Linking aktivieren</value> + </data> + <data name="Social Links" xml:space="preserve"> + <value>Soziale Verknüpfungen</value> + </data> </root> \ No newline at end of file diff --git a/src/Moonglade.Web/Resources/Program.ja-JP.resx b/src/Moonglade.Web/Resources/Program.ja-JP.resx index c1207f7fa..34915a0a5 100644 --- a/src/Moonglade.Web/Resources/Program.ja-JP.resx +++ b/src/Moonglade.Web/Resources/Program.ja-JP.resx @@ -1038,4 +1038,10 @@ <data name="Confirm" xml:space="preserve"> <value>確認する</value> </data> + <data name="Enable Social Links" xml:space="preserve"> + <value>ソーシャルリンクを有効にする</value> + </data> + <data name="Social Links" xml:space="preserve"> + <value>ソーシャルリンク</value> + </data> </root> \ No newline at end of file diff --git a/src/Moonglade.Web/Resources/Program.zh-Hans.resx b/src/Moonglade.Web/Resources/Program.zh-Hans.resx index 6f5a4299c..4cc9f2abd 100644 --- a/src/Moonglade.Web/Resources/Program.zh-Hans.resx +++ b/src/Moonglade.Web/Resources/Program.zh-Hans.resx @@ -1038,4 +1038,10 @@ <data name="Confirm" xml:space="preserve"> <value>确认</value> </data> + <data name="Enable Social Links" xml:space="preserve"> + <value>启用社交链接</value> + </data> + <data name="Social Links" xml:space="preserve"> + <value>社交链接</value> + </data> </root> \ No newline at end of file diff --git a/src/Moonglade.Web/Resources/Program.zh-Hant.resx b/src/Moonglade.Web/Resources/Program.zh-Hant.resx index 25ac8b908..24d3bf86a 100644 --- a/src/Moonglade.Web/Resources/Program.zh-Hant.resx +++ b/src/Moonglade.Web/Resources/Program.zh-Hant.resx @@ -1041,4 +1041,10 @@ <data name="Confirm" xml:space="preserve"> <value>確認</value> </data> + <data name="Enable Social Links" xml:space="preserve"> + <value>啟用社交連結</value> + </data> + <data name="Social Links" xml:space="preserve"> + <value>社交連結</value> + </data> </root> \ No newline at end of file From 2eeea55aa20e28fd620f26df7ebec5f3ccde23b7 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Thu, 24 Oct 2024 12:24:47 +0800 Subject: [PATCH 45/47] Update version tag to 14.12.0-rc.1 in Directory.Build.props Transitioned the version tag in the `Directory.Build.props` file from `14.12.0-beta.1` to `14.12.0-rc.1`, indicating a move from beta to release candidate. This change reflects increased stability and feature completeness as we approach the final release. --- src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 7dbd12ab3..7fdcdd8fd 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,6 +5,6 @@ <Copyright>(C) 2024 edi.wang@outlook.com</Copyright> <AssemblyVersion>14.12.0.0</AssemblyVersion> <FileVersion>14.12.0.0</FileVersion> - <Version>14.12.0-beta.1</Version> + <Version>14.12.0-rc.1</Version> </PropertyGroup> </Project> \ No newline at end of file From 0246e6141a21cda3226258ca30c2568f6b4167cf Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Fri, 25 Oct 2024 10:17:17 +0800 Subject: [PATCH 46/47] Remove DotNetVersion property from WriteResponse method The DotNetVersion property, previously set to Environment.Version.ToString(), has been removed from the anonymous object in the WriteResponse method of the ConfigureEndpoints class. This change simplifies the object by excluding the .NET version information. --- src/Moonglade.Web/Configuration/ConfigureEndpoints.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Moonglade.Web/Configuration/ConfigureEndpoints.cs b/src/Moonglade.Web/Configuration/ConfigureEndpoints.cs index a4af2d815..5c75d1ed6 100644 --- a/src/Moonglade.Web/Configuration/ConfigureEndpoints.cs +++ b/src/Moonglade.Web/Configuration/ConfigureEndpoints.cs @@ -9,7 +9,6 @@ public static Task WriteResponse(HttpContext context, HealthReport result) var obj = new { Helper.AppVersion, - DotNetVersion = Environment.Version.ToString(), EnvironmentTags = Helper.GetEnvironmentTags(), GeoMatch = context.Request.Headers["x-geo-match"] }; From 1cc20e26f71edea66e61e0057fd5a0453fe3c670 Mon Sep 17 00:00:00 2001 From: Edi Wang <Edi.Wang@outlook.com> Date: Fri, 25 Oct 2024 10:17:52 +0800 Subject: [PATCH 47/47] Update version to 14.12.0 final release The <Version> element in the Directory.Build.props file has been updated from 14.12.0-rc.1 to 14.12.0. This change signifies that the software has moved from a release candidate to a final release version, indicating it is now stable and ready for general availability. --- src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 7fdcdd8fd..d2b87bf72 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,6 +5,6 @@ <Copyright>(C) 2024 edi.wang@outlook.com</Copyright> <AssemblyVersion>14.12.0.0</AssemblyVersion> <FileVersion>14.12.0.0</FileVersion> - <Version>14.12.0-rc.1</Version> + <Version>14.12.0</Version> </PropertyGroup> </Project> \ No newline at end of file