< 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
100%
Covered lines: 18
Uncovered lines: 0
Coverable lines: 18
Total lines: 160
Line coverage: 100%
Branch coverage
100%
Covered branches: 22
Total branches: 22
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
BuildRenderTree(...)100%1010100%
GetSourceDisplayName(...)100%1212100%

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>
 138        @if (IsLoading)
 9        {
 10            <span class="links-panel-loading">...</span>
 11        }
 1312        <span class="links-panel-count">@ExternalLinks.Count</span>
 13    </div>
 14
 1315    @if (ExternalLinks.Any())
 16    {
 17        <div class="external-links-container">
 4818            @foreach (var group in ExternalLinks.GroupBy(el => el.Source))
 19            {
 20                <div class="external-links-source-group">
 1421                    <div class="external-links-source-badge">@GetSourceDisplayName(group.Key)</div>
 22                    <div class="links-chip-container">
 6023                        @foreach (var link in group)
 24                        {
 25                            <span class="external-link-chip"
 26                                  title="@($"{link.DisplayTitle} ({link.Source})")">
 1627                                @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]
 47    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]
 54    public bool IsLoading { get; set; }
 55
 56    private static string GetSourceDisplayName(string source)
 57    {
 1458        return source.ToUpperInvariant() switch
 1459        {
 560            "SRD" => "SRD",
 161            "SRD14" => "SRD 1.4",
 162            "SRD24" => "SRD 2.4",
 463            "OPEN5E" => "Open5e",
 164            "ROS" => "Ruins of Symbaroum",
 165            "CAC" => "Castles & Crusades",
 166            _ => source.ToUpperInvariant()
 1467        };
 68    }
 69}
 70
 71<style>
 72.links-panel {
 73    padding: 12px 16px;
 74}
 75
 76.links-panel-header {
 77    display: flex;
 78    align-items: center;
 79    gap: 8px;
 80    margin-bottom: 10px;
 81}
 82
 83.links-panel-title {
 84    font-size: 13px;
 85    font-weight: 600;
 86    color: var(--mud-palette-text-secondary);
 87    text-transform: uppercase;
 88    letter-spacing: 0.5px;
 89}
 90
 91.links-panel-loading {
 92    font-size: 12px;
 93    color: #C4AF8E;
 94}
 95
 96.links-panel-count {
 97    font-size: 11px;
 98    background: rgba(196, 175, 142, 0.2);
 99    color: var(--mud-palette-text-secondary);
 100    padding: 2px 6px;
 101    border-radius: 10px;
 102    margin-left: auto;
 103}
 104
 105.external-links-container {
 106    display: flex;
 107    flex-direction: column;
 108    gap: 12px;
 109}
 110
 111.external-links-source-group {
 112    display: flex;
 113    flex-direction: column;
 114    gap: 6px;
 115}
 116
 117.external-links-source-badge {
 118    font-size: 10px;
 119    font-weight: 600;
 120    color: var(--mud-palette-text-secondary);
 121    text-transform: uppercase;
 122    letter-spacing: 0.8px;
 123    padding: 2px 6px;
 124    background: rgba(196, 175, 142, 0.15);
 125    border-radius: 3px;
 126    width: fit-content;
 127}
 128
 129.links-chip-container {
 130    display: flex;
 131    flex-wrap: wrap;
 132    gap: 6px;
 133}
 134
 135.external-link-chip {
 136    display: inline-block;
 137    padding: 4px 10px;
 138    font-size: 12px;
 139    background: rgba(100, 150, 200, 0.1);
 140    border: 1px solid rgba(100, 150, 200, 0.25);
 141    border-radius: 12px;
 142    color: var(--mud-palette-text-primary);
 143    max-width: 180px;
 144    overflow: hidden;
 145    text-overflow: ellipsis;
 146    white-space: nowrap;
 147}
 148
 149.external-link-chip:hover {
 150    background: rgba(100, 150, 200, 0.2);
 151    border-color: rgba(100, 150, 200, 0.4);
 152}
 153
 154.links-panel-empty {
 155    font-size: 12px;
 156    color: var(--mud-palette-text-secondary);
 157    opacity: 0.6;
 158    font-style: italic;
 159}
 160</style>