| | | 1 | | @namespace Chronicis.Client.Components.Search |
| | | 2 | | @using Chronicis.Shared.DTOs |
| | | 3 | | @using System.Text.RegularExpressions |
| | | 4 | | |
| | | 5 | | <MudCard Class="mb-3 search-result-card" @onclick="OnClick" Style="cursor: pointer;" Elevation="2"> |
| | | 6 | | <MudCardContent> |
| | | 7 | | <div class="d-flex align-center mb-2"> |
| | | 8 | | <MudChip T="string" Size="Size.Small" |
| | | 9 | | Color="@GetMatchTypeColor()" |
| | | 10 | | Variant="Variant.Text" |
| | | 11 | | Style="font-weight: 500;"> |
| | 15 | 12 | | @GetMatchTypeDisplay() |
| | | 13 | | </MudChip> |
| | | 14 | | |
| | | 15 | | <MudSpacer /> |
| | | 16 | | |
| | | 17 | | <MudText Typo="Typo.caption" Color="Color.Secondary"> |
| | 15 | 18 | | @GetRelativeTime() |
| | | 19 | | </MudText> |
| | | 20 | | </div> |
| | | 21 | | |
| | | 22 | | <MudText Typo="Typo.h6" Class="mb-2"> |
| | 15 | 23 | | @((MarkupString)HighlightMatch(Result.Title, Query)) |
| | | 24 | | </MudText> |
| | | 25 | | |
| | 15 | 26 | | @if (Result.AncestorPath.Any()) |
| | | 27 | | { |
| | | 28 | | <MudBreadcrumbs Items="@GetBreadcrumbItems()" |
| | | 29 | | Class="mb-2" |
| | | 30 | | Style="font-size: 0.875rem; color: #666;" /> |
| | | 31 | | } |
| | | 32 | | |
| | 15 | 33 | | @if (!string.IsNullOrEmpty(Result.MatchSnippet)) |
| | | 34 | | { |
| | | 35 | | <MudText Typo="Typo.body2" Class="search-snippet mt-2"> |
| | 2 | 36 | | @((MarkupString)HighlightMatch(Result.MatchSnippet, Query)) |
| | | 37 | | </MudText> |
| | | 38 | | } |
| | | 39 | | </MudCardContent> |
| | | 40 | | </MudCard> |
| | | 41 | | |
| | | 42 | | @code { |
| | | 43 | | [Parameter, EditorRequired] |
| | 108 | 44 | | public ArticleSearchResultDto Result { get; set; } = null!; |
| | | 45 | | |
| | | 46 | | [Parameter, EditorRequired] |
| | 47 | 47 | | public string Query { get; set; } = string.Empty; |
| | | 48 | | |
| | | 49 | | [Parameter] |
| | 16 | 50 | | public EventCallback OnClick { get; set; } |
| | | 51 | | |
| | | 52 | | private string GetMatchTypeDisplay() |
| | | 53 | | { |
| | 15 | 54 | | return Result.MatchType switch |
| | 15 | 55 | | { |
| | 11 | 56 | | "title" => "Title Match", |
| | 2 | 57 | | "content" => "Content Match", |
| | 2 | 58 | | "hashtag" => "Hashtag Match", |
| | 0 | 59 | | _ => "Match" |
| | 15 | 60 | | }; |
| | | 61 | | } |
| | | 62 | | |
| | | 63 | | private Color GetMatchTypeColor() |
| | | 64 | | { |
| | 15 | 65 | | return Result.MatchType switch |
| | 15 | 66 | | { |
| | 11 | 67 | | "title" => Color.Primary, |
| | 2 | 68 | | "content" => Color.Info, |
| | 2 | 69 | | "hashtag" => Color.Success, |
| | 0 | 70 | | _ => Color.Default |
| | 15 | 71 | | }; |
| | | 72 | | } |
| | | 73 | | |
| | | 74 | | private string GetRelativeTime() |
| | | 75 | | { |
| | 15 | 76 | | var diff = DateTime.UtcNow - Result.LastModified; |
| | | 77 | | |
| | 15 | 78 | | if (diff.TotalMinutes < 1) |
| | 0 | 79 | | return "Just now"; |
| | 15 | 80 | | if (diff.TotalMinutes < 60) |
| | 0 | 81 | | return $"{(int)diff.TotalMinutes}m ago"; |
| | 15 | 82 | | if (diff.TotalHours < 24) |
| | 15 | 83 | | return $"{(int)diff.TotalHours}h ago"; |
| | 0 | 84 | | if (diff.TotalDays < 7) |
| | 0 | 85 | | return $"{(int)diff.TotalDays}d ago"; |
| | 0 | 86 | | if (diff.TotalDays < 30) |
| | 0 | 87 | | return $"{(int)(diff.TotalDays / 7)}w ago"; |
| | | 88 | | |
| | 0 | 89 | | return Result.LastModified.ToString("MMM d, yyyy"); |
| | | 90 | | } |
| | | 91 | | |
| | | 92 | | private List<BreadcrumbItem> GetBreadcrumbItems() |
| | | 93 | | { |
| | 1 | 94 | | return Result.AncestorPath.Select(b => |
| | 2 | 95 | | new BreadcrumbItem(b.Title, href: null, disabled: true) |
| | 1 | 96 | | ).ToList(); |
| | | 97 | | } |
| | | 98 | | |
| | | 99 | | private string HighlightMatch(string text, string query) |
| | | 100 | | { |
| | 17 | 101 | | if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(query)) |
| | 1 | 102 | | return text; |
| | | 103 | | |
| | | 104 | | try |
| | | 105 | | { |
| | 16 | 106 | | var regex = new Regex( |
| | 16 | 107 | | Regex.Escape(query), |
| | 16 | 108 | | RegexOptions.IgnoreCase); |
| | | 109 | | |
| | 16 | 110 | | return regex.Replace(text, match => |
| | 30 | 111 | | $"<mark class=\"search-highlight\">{match.Value}</mark>"); |
| | | 112 | | } |
| | 0 | 113 | | catch |
| | | 114 | | { |
| | 0 | 115 | | return text; |
| | | 116 | | } |
| | 16 | 117 | | } |
| | | 118 | | } |