-
-
Notifications
You must be signed in to change notification settings - Fork 88
feat: adding TagDiscovery panel feature (#494) #500
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| using System.Collections.Generic; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace LinkDotNet.Blog.Web.Features.Services.Tags; | ||
|
|
||
| public interface ITagQueryService | ||
| { | ||
| Task<IReadOnlyList<TagCount>> GetAllOrderedByUsageAsync(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| namespace LinkDotNet.Blog.Web.Features.Services.Tags; | ||
|
|
||
| public sealed record TagCount(string Name, int Count); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| using Azure.Storage.Blobs.Models; | ||
| using LinkDotNet.Blog.Domain; | ||
| using LinkDotNet.Blog.Infrastructure.Persistence; | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace LinkDotNet.Blog.Web.Features.Services.Tags; | ||
|
|
||
| public sealed class TagQueryService(IRepository<BlogPost> blogPostRepository) : ITagQueryService | ||
| { | ||
| public async Task<IReadOnlyList<TagCount>> GetAllOrderedByUsageAsync() | ||
| { | ||
| var posts = await blogPostRepository.GetAllAsync(); | ||
|
|
||
| var tagCounts = posts | ||
| // Flatten the collection of tag lists into a single sequence. | ||
| .SelectMany(p => p.Tags ?? Enumerable.Empty<string>()) | ||
|
|
||
| // Defensive guard against invalid tag values. | ||
| .Where(tag => !string.IsNullOrEmpty(tag)) | ||
|
|
||
| .GroupBy(tag => tag.Trim()) | ||
|
|
||
| // Transform each group into a TagCount DTO. | ||
| // group.Key = tag name | ||
| // group.Count() = number of occurrences | ||
| .Select(group => new TagCount( | ||
| group.Key, | ||
| group.Count())) | ||
|
|
||
| // Sort descending by usage count (most popular first). | ||
| .OrderByDescending(tc => tc.Count) | ||
| .ThenBy(tc => tc.Name) | ||
| .ToList(); | ||
|
|
||
| return tagCounts; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| @inject ITagQueryService TagQueryService | ||
| @inject IOptions<ApplicationConfiguration> AppConfiguration | ||
| @inject NavigationManager Navigation | ||
|
|
||
| @if (!AppConfiguration.Value.EnableTagDiscoveryPanel || !IsOpen) { return; } | ||
|
|
||
| <div class="tag-overlay" @onclick="Close"></div> | ||
|
|
||
| <div class="tag-panel"> | ||
| <div class="tag-discovery-container"> | ||
| @foreach (var tag in _tags) | ||
| { | ||
| <span class="tag-badge" @onclick="() => Navigate(tag.Name)"> | ||
| @tag.Name | ||
|
|
||
| @if (AppConfiguration.Value.ShowTagsWithCountInTagDiscovery) | ||
| { | ||
| <span class="tag-count">@tag.Count</span> | ||
| } | ||
| </span> | ||
| } | ||
| </div> | ||
| </div> | ||
|
|
||
| @code { | ||
| [Parameter] public bool IsOpen { get; set; } | ||
| [Parameter] public EventCallback OnClose { get; set; } | ||
|
|
||
| private IReadOnlyList<TagCount> _tags = []; | ||
|
|
||
| protected override async Task OnParametersSetAsync() | ||
| { | ||
| if (IsOpen && _tags.Count == 0) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Edge case: But that leads to stale updates as a creator. If I create an entry and open the dialog, it will show only the initially set version.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably we are better off with a cached version with timing or other means (like we do with the frontpage) |
||
| { | ||
| _tags = await TagQueryService.GetAllOrderedByUsageAsync(); | ||
| } | ||
| } | ||
|
|
||
| private async Task Close() | ||
| { | ||
| await OnClose.InvokeAsync(); | ||
| } | ||
|
|
||
| private async Task Navigate(string tag) | ||
| { | ||
| var encoded = Uri.EscapeDataString(tag); | ||
| Navigation.NavigateTo($"/searchByTag/{encoded}"); | ||
| await Close(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| .tag-overlay { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea is to have as little as possible custom css. Either by using bottstrap 5 itself (which to a big extend should be possible here) or at least move it into the central basic.css |
||
| position: fixed; | ||
| inset: 0; | ||
| background: rgba(0, 0, 0, 0.35); | ||
| backdrop-filter: blur(2px); | ||
| z-index: 1000; | ||
| } | ||
|
|
||
| .tag-panel { | ||
| position: fixed; | ||
| top: 50%; | ||
| left: 50%; | ||
| transform: translate(-50%, -50%); | ||
| width: min(340px, 92vw); | ||
| max-height: 70vh; | ||
| background: var(--background-color, #ffffff); | ||
| color: var(--text-color, #222); | ||
| border-radius: 14px; | ||
| padding: 1.2rem; | ||
| overflow-y: auto; | ||
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.25); | ||
| z-index: 1001; | ||
| } | ||
|
|
||
| .tag-discovery-container { | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| gap: 10px; | ||
| } | ||
|
|
||
| .tag-badge { | ||
| display: inline-flex; | ||
| align-items: center; | ||
| gap: 6px; | ||
| padding: 6px 12px; | ||
| border-radius: 999px; | ||
| font-size: 0.85rem; | ||
| font-weight: 500; | ||
| background-color: #4f83cc; | ||
| color: white; | ||
| cursor: pointer; | ||
| transition: transform 0.1s ease, background-color 0.1s ease, box-shadow 0.1s ease; | ||
| } | ||
|
|
||
| .tag-badge:hover { | ||
| background-color: #3c6fb3; | ||
| transform: translateY(-1px); | ||
| box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15); | ||
| } | ||
|
|
||
| .tag-count { | ||
| background: rgba(0, 0, 0, 0.25); | ||
| border-radius: 999px; | ||
| padding: 2px 7px; | ||
| font-size: 0.7rem; | ||
| font-weight: 600; | ||
| } | ||
|
|
||
| .tag-panel::-webkit-scrollbar { | ||
| width: 6px; | ||
| } | ||
|
|
||
| .tag-panel::-webkit-scrollbar-thumb { | ||
| background: rgba(0, 0, 0, 0.25); | ||
| border-radius: 6px; | ||
| } | ||
|
|
||
| .no-scroll { | ||
| overflow: hidden; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,7 +39,7 @@ | |
| "ProfilePictureUrl": "assets/profile-picture.webp" | ||
| }, | ||
| "ImageStorageProvider": "<Provider>", | ||
| "ImageStorage" : { | ||
| "ImageStorage": { | ||
| "AuthenticationMode": "Default", | ||
| "ConnectionString": "", | ||
| "ServiceUrl": "", | ||
|
|
@@ -49,5 +49,7 @@ | |
| "ShowReadingIndicator": true, | ||
| "ShowSimilarPosts": true, | ||
| "ShowBuildInformation": true, | ||
| "UseMultiAuthorMode": false | ||
| "UseMultiAuthorMode": false, | ||
| "EnableTagDiscoveryPanel": true, | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This also needs some documentation in the docs/ directory: There is a part where we describes all the settings |
||
| "ShowTagsWithCountInTagDiscovery": true | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would remove that - we can always show them |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those comments are so obvious they can be removed. I assume copilot/... did create them