< Summary

Information
Class: Chronicis.Client.Components.Articles.ExternalLinksPanel
Assembly: Chronicis.Client
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Client/Components/Articles/ExternalLinksPanel.razor
Line coverage
78%
Covered lines: 15
Uncovered lines: 4
Coverable lines: 19
Total lines: 159
Line coverage: 78.9%
Branch coverage
75%
Covered branches: 15
Total branches: 20
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
BuildRenderTree(...)100%1010100%
get_ExternalLinks()100%11100%
get_IsLoading()100%11100%
GetSourceDisplayName(...)50%191055.55%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Client/Components/Articles/ExternalLinksPanel.razor

#LineLine coverage
 1@using Chronicis.Shared.DTOs
 2
 3@* Refactored ExternalLinksPanel - Now accepts data as parameters instead of fetching directly *@
 4
 5<div class="links-panel">
 6    <div class="links-panel-header">
 7        <span class="links-panel-title">External Resources</span>
 88        @if (IsLoading)
 9        {
 10            <span class="links-panel-loading">...</span>
 11        }
 812        <span class="links-panel-count">@ExternalLinks.Count</span>
 13    </div>
 14
 815    @if (ExternalLinks.Any())
 16    {
 17        <div class="external-links-container">
 3918            @foreach (var group in ExternalLinks.GroupBy(el => el.Source))
 19            {
 20                <div class="external-links-source-group">
 921                    <div class="external-links-source-badge">@GetSourceDisplayName(group.Key)</div>
 22                    <div class="links-chip-container">
 4023                        @foreach (var link in group)
 24                        {
 25                            <span class="external-link-chip"
 26                                  title="@($"{link.DisplayTitle} ({link.Source})")">
 1127                                @link.DisplayTitle
 28                            </span>
 29                        }
 30                    </div>
 31                </div>
 32            }
 33        </div>
 34    }
 335    else if (!IsLoading)
 36    {
 37        <div class="links-panel-empty">No external resources</div>
 38    }
 39</div>
 40
 41@code {
 42    /// <summary>
 43    /// The list of external links to display.
 44    /// Parent component is responsible for loading this data.
 45    /// </summary>
 46    [Parameter, EditorRequired]
 3747    public List<ArticleExternalLinkDto> ExternalLinks { get; set; } = new();
 48
 49    /// <summary>
 50    /// Whether data is currently being loaded.
 51    /// Controls the loading indicator display.
 52    /// </summary>
 53    [Parameter]
 1354    public bool IsLoading { get; set; }
 55
 56    private static string GetSourceDisplayName(string source)
 57    {
 958        return source.ToUpperInvariant() switch
 959        {
 560            "SRD" => "SRD",
 061            "SRD14" => "SRD 1.4",
 062            "SRD24" => "SRD 2.4",
 463            "OPEN5E" => "Open5e",
 064            "ROS" => "Ruins of Symbaroum",
 065            _ => source.ToUpperInvariant()
 966        };
 67    }
 68}
 69
 70<style>
 71.links-panel {
 72    padding: 12px 16px;
 73}
 74
 75.links-panel-header {
 76    display: flex;
 77    align-items: center;
 78    gap: 8px;
 79    margin-bottom: 10px;
 80}
 81
 82.links-panel-title {
 83    font-size: 13px;
 84    font-weight: 600;
 85    color: var(--mud-palette-text-secondary);
 86    text-transform: uppercase;
 87    letter-spacing: 0.5px;
 88}
 89
 90.links-panel-loading {
 91    font-size: 12px;
 92    color: #C4AF8E;
 93}
 94
 95.links-panel-count {
 96    font-size: 11px;
 97    background: rgba(196, 175, 142, 0.2);
 98    color: var(--mud-palette-text-secondary);
 99    padding: 2px 6px;
 100    border-radius: 10px;
 101    margin-left: auto;
 102}
 103
 104.external-links-container {
 105    display: flex;
 106    flex-direction: column;
 107    gap: 12px;
 108}
 109
 110.external-links-source-group {
 111    display: flex;
 112    flex-direction: column;
 113    gap: 6px;
 114}
 115
 116.external-links-source-badge {
 117    font-size: 10px;
 118    font-weight: 600;
 119    color: var(--mud-palette-text-secondary);
 120    text-transform: uppercase;
 121    letter-spacing: 0.8px;
 122    padding: 2px 6px;
 123    background: rgba(196, 175, 142, 0.15);
 124    border-radius: 3px;
 125    width: fit-content;
 126}
 127
 128.links-chip-container {
 129    display: flex;
 130    flex-wrap: wrap;
 131    gap: 6px;
 132}
 133
 134.external-link-chip {
 135    display: inline-block;
 136    padding: 4px 10px;
 137    font-size: 12px;
 138    background: rgba(100, 150, 200, 0.1);
 139    border: 1px solid rgba(100, 150, 200, 0.25);
 140    border-radius: 12px;
 141    color: var(--mud-palette-text-primary);
 142    max-width: 180px;
 143    overflow: hidden;
 144    text-overflow: ellipsis;
 145    white-space: nowrap;
 146}
 147
 148.external-link-chip:hover {
 149    background: rgba(100, 150, 200, 0.2);
 150    border-color: rgba(100, 150, 200, 0.4);
 151}
 152
 153.links-panel-empty {
 154    font-size: 12px;
 155    color: var(--mud-palette-text-secondary);
 156    opacity: 0.6;
 157    font-style: italic;
 158}
 159</style>