@page "/docs" @using System.Text.RegularExpressions Fellowship Docs

Fellowship Reference

@DocsData.All.Count entries across @Groups.Count() categories

@if (!FilteredGroups.Any()) {

No docs match "@searchTerm"

} @foreach (var group in FilteredGroups) { var groupId = GetGroupId(group.Key);

@group.Key

@group.Count()
@foreach (var doc in group) { var docId = GetDocId(doc); var typeName = doc.GetType().Name.Replace("Doc", "");

@GetDisplayName(doc)

@typeName
@if (doc is SkillDoc { Key: { } key } && !string.IsNullOrEmpty(key)) {
@key
}
@if (doc is SkillDoc { Description: { } desc } && !string.IsNullOrEmpty(desc)) {
@{ var lines = desc.Split('\n'); foreach (var line in lines) {

@RenderWikiLinks(line)

} }
}
@foreach (var field in GetOrderedFields(doc)) {
@field.Label @field.Value
}
@if (GetBody(doc) is { } body && !string.IsNullOrWhiteSpace(body) && (doc is not SkillDoc || string.IsNullOrWhiteSpace(((SkillDoc)doc).Description))) {
@body.Trim()
}
@if (doc is SkillDoc { Tags: { } tags } && tags.Count > 0) { }
}
}
@code { private string? searchTerm; private string? activeFilter; private List> Groups = []; private IEnumerable> FilteredGroups => Groups .Where(g => g.Any(d => MatchesFilter(d))) .OrderBy(g => SortOrder(g.Key)); protected override void OnInitialized() { Groups = DocsData.All .GroupBy(d => GetCharacterOrType(d)) .ToList(); } private bool MatchesFilter(DocEntry doc) { if (!string.IsNullOrEmpty(searchTerm)) { var term = searchTerm.Trim().ToLowerInvariant(); var name = GetDisplayName(doc).ToLowerInvariant(); if (!name.Contains(term)) return false; } if (activeFilter != null) { var type = doc.GetType().Name.Replace("Doc", ""); if (type != activeFilter) return false; } return true; } private static int SortOrder(string groupKey) => groupKey switch { "Xavian" => 0, "Rime" => 1, "Vigour" => 2, "Keys" => 3, _ => 99 }; private void OnSearchChanged() { StateHasChanged(); } private void ClearSearch() { searchTerm = null; activeFilter = null; } private void FilterAll() { activeFilter = null; } private void FilterSkills() { activeFilter = activeFilter == "Skill" ? null : "Skill"; } private void FilterDebuffs() { activeFilter = activeFilter == "Debuff" ? null : "Debuff"; } private void FilterBuffs() { activeFilter = activeFilter == "Buff" ? null : "Buff"; } private void FilterKeys() { activeFilter = activeFilter == "Key" ? null : "Key"; } private static string GetCharacterOrType(DocEntry doc) => doc switch { SkillDoc s => s.Character, DebuffDoc d => d.Character, BuffDoc b => b.Character, CharacterDoc c => c.Character, KeyDoc => "Keys", _ => "Other" }; private static string GetDisplayName(DocEntry doc) => Path.GetFileNameWithoutExtension(doc.FileName); private static string GetGroupId(string group) => $"group-{group.GetHashCode():x}"; private static string GetDocId(DocEntry doc) => $"doc-{doc.FileName.GetHashCode():x}"; private static string GetTypeClass(DocEntry doc) => doc switch { SkillDoc => "type-skill", DebuffDoc => "type-debuff", BuffDoc => "type-buff", KeyDoc => "type-key", CharacterDoc => "type-character", _ => "" }; private static MarkupString RenderWikiLinks(string line) { var result = Regex.Replace(line, @"\[\[([^\]]+)\]\]", m => { var name = m.Groups[1].Value; return $"{name}"; }); return new MarkupString(result); } private static readonly Dictionary FieldLabels = new() { ["Character"] = "Character", ["Cast"] = "Cast Time", ["Key"] = "Key Bind", ["Range"] = "Range", ["Damage"] = "Damage", ["DamageType"] = "Damage Type", ["Heal"] = "Healing", ["Shield"] = "Shield", ["Cooldown"] = "Cooldown", ["Mana"] = "Mana Cost", ["OffGlobalCooldown"] = "Off GCD", ["Gdc"] = "GDC", ["Duration"] = "Duration", ["DamageReduction"] = "Dmg Reduction", ["DamageTickTime"] = "Tick Interval", ["IsToggle"] = "Toggle", ["ManaUpkeepTick"] = "Mana Upkeep", ["ParryChance"] = "Parry Chance", ["DamageRedirection"] = "Dmg Redirection", ["SpiritCost"] = "Spirit Cost", ["SecondEffectDuration"] = "Effect 2 Duration", ["SecondEffectDamageReduction"] = "Effect 2 Dmg Reduction", ["HealingDuration"] = "Healing Duration", ["HealingTickTime"] = "Healing Tick", ["CostSwiftReprieval"] = "Cost: Swift Reprieval", ["GdcDuration"] = "GDC Duration", ["Effect"] = "Effect", ["GeneratesSpirit"] = "Generates Spirit", ["AreaDamagePercentage"] = "Cleave %", ["SwiftReprievalChance"] = "Swift Reprieval %", ["MaxStacks"] = "Max Stacks", ["Action"] = "Action", ["ParryChanceBonus"] = "Parry Bonus", ["ManaRestoreBase"] = "Mana Restore Base", ["ManaRestorePerStack"] = "Mana Per Stack", ["Order"] = "Order", ["Priority"] = "Priority", ["Completed"] = "Completed", ["Raw"] = "Raw", }; private static readonly Dictionary FieldOrder = new() { [typeof(SkillDoc)] = ["Character", "Cast", "Key", "Range", "Damage", "DamageType", "Heal", "Shield", "Cooldown", "Mana", "OffGlobalCooldown", "Gdc", "Duration", "DamageReduction", "DamageTickTime", "IsToggle", "ManaUpkeepTick", "ParryChance", "DamageRedirection", "GeneratesSpirit", "AreaDamagePercentage", "SwiftReprievalChance", "Effect", "SpiritCost", "SecondEffectDuration", "SecondEffectDamageReduction", "HealingDuration", "HealingTickTime", "CostSwiftReprieval", "GdcDuration"], [typeof(DebuffDoc)] = ["Character", "MaxStacks", "Duration", "ParryChanceBonus", "ManaRestoreBase", "ManaRestorePerStack"], [typeof(BuffDoc)] = ["Character", "MaxStacks"], [typeof(KeyDoc)] = ["Action"], [typeof(CharacterDoc)] = ["Character"], }; private static List GetOrderedFields(DocEntry doc) { var result = new List(); var type = doc.GetType(); var order = FieldOrder.GetValueOrDefault(type, []); var allProps = new Dictionary(); foreach (var prop in type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)) { var name = prop.Name; if (name is "FileName" or "FilePath" or "Body" or "Description") continue; var value = prop.GetValue(doc); if (value == null) continue; if (value is bool bVal && !bVal) continue; if (value is string s && string.IsNullOrEmpty(s)) continue; var display = value switch { List list => string.Join(", ", list), _ => value.ToString() }; if (string.IsNullOrEmpty(display)) continue; allProps[name] = display; } foreach (var key in order) { if (allProps.Remove(key, out var val)) { result.Add(new FieldEntry(FieldLabels.GetValueOrDefault(key, key), val)); } } foreach (var (key, val) in allProps) { result.Add(new FieldEntry(FieldLabels.GetValueOrDefault(key, key), val)); } return result; } private static string? GetBody(DocEntry doc) => doc.Body; private record FieldEntry(string Label, string Value); }