Link every item and relic on /Warframe to the wiki
- author
- Marijn Besseling <njirambem@gmail.com> · 2026-09-14 18:35 UTC
- commit
- 0882c1477a0c0a2a49cbc75c6db18ece5857ec9d
- parent
- f4327415ca
- tree
- browse at this commit
4 files changed +122 -2
| Blog/Components/Pages/Warframe.razor | +9 -2 | |
| Blog/Components/Pages/Warframe.razor.cs | +89 -0 | |
| Blog/wwwroot/app.css | +15 -0 | |
| CLAUDE.md | +9 -0 |
Blog/Components/Pages/Warframe.razor +9 -2
| @@ -44,7 +44,10 @@ | ||
| 44 | 44 | |
| 45 | 45 | (Item is { } item) |
| 46 | 46 | { |
| 47 | - <h2 class="item-name"></h2> | |
| 47 | + <h2 class="item-name"> | |
| 48 | + | |
| 49 | + <a class="wiki" href="@WikiLink(item.Name)" rel="external noreferrer">[wiki]</a> | |
| 50 | + </h2> | |
| 48 | 51 | |
| 49 | 52 | (item.Sources.Count == 0 && !item.FromRelics) |
| 50 | 53 | { |
| @@ -64,6 +67,7 @@ | ||
| 64 | 67 | <div class="relic"> |
| 65 | 68 | <h3> |
| 66 | 69 | <a href="@ItemLink(line.Relic)"></a> |
| 70 | + <a class="wiki" href="@WikiLink(line.Relic)" rel="external noreferrer">[wiki]</a> | |
| 67 | 71 | <span class="muted"></span> |
| 68 | 72 | <span>(line.Intact) intact → (line.Radiant) radiant</span> |
| 69 | 73 | </h3> |
| @@ -149,7 +153,10 @@ | ||
| 149 | 153 | (var line in item.Contents) |
| 150 | 154 | { |
| 151 | 155 | <tr> |
| 152 | - <td><a href="@ItemLink(line.Item)"></a></td> | |
| 156 | + <td> | |
| 157 | + <a href="@ItemLink(line.Item)"></a> | |
| 158 | + <a class="wiki" href="@WikiLink(line.Item)" rel="external noreferrer">[wiki]</a> | |
| 159 | + </td> | |
| 153 | 160 | <td class="muted"></td> |
| 154 | 161 | <td class="chance">(line.Intact)</td> |
| 155 | 162 | <td class="chance">(line.Radiant)</td> |
Blog/Components/Pages/Warframe.razor.cs +89 -0
| @@ -1,4 +1,5 @@ | ||
| 1 | 1 | using System.Globalization; |
| 2 | +using System.Text.RegularExpressions; | |
| 2 | 3 | using Blog.Models; |
| 3 | 4 | using Blog.Services; |
| 4 | 5 | using Microsoft.AspNetCore.Components; |
| @@ -98,6 +99,94 @@ public partial class Warframe : ComponentBase | ||
| 98 | 99 | }; |
| 99 | 100 | |
| 100 | 101 | /// <summary> |
| 102 | + /// Components a non-prime item is built from. Only used to trim a part name back to the thing | |
| 103 | + /// the wiki has a page for, so it needs the common ones rather than all of them. | |
| 104 | + /// </summary> | |
| 105 | + private static readonly string[] Components = | |
| 106 | + [ | |
| 107 | + "Chassis", "Systems", "Neuroptics", "Harness", "Wings", "Barrel", "Receiver", "Stock", | |
| 108 | + "Blade", "Blades", "Handle", "Hilt", "Grip", "String", "Limb", "Link", "Head", "Guard", | |
| 109 | + "Ornament", "Boot", "Buckle", "Pouch", "Gauntlet", "Bracket", "Carapace", "Cerebrum", | |
| 110 | + "Fuselage", "Engines", "Avionics", "Nose Cone", "Wing" | |
| 111 | + ]; | |
| 112 | + | |
| 113 | + /// <summary> | |
| 114 | + /// A leading amount the wiki page's title does not carry: <c>100X Oxium</c>, <c>900 Endo</c>, | |
| 115 | + /// <c>3 Day Affinity Booster</c>. A bare number only counts as one in front of Endo or Credits, | |
| 116 | + /// because most of the time it is part of the name. | |
| 117 | + /// </summary> | |
| 118 | + | |
| 119 | + private static partial Regex AmountPrefix { get; } | |
| 120 | + | |
| 121 | + /// <summary> | |
| 122 | + /// A relic handed out already refined: <c>Axi S20 Relic (Radiant)</c> is a reward in its own | |
| 123 | + /// right, but the wiki documents the relic, not the state it arrives in. | |
| 124 | + /// </summary> | |
| 125 | + | |
| 126 | + private static partial Regex Refinement { get; } | |
| 127 | + | |
| 128 | + /// <summary> | |
| 129 | + /// The mark on a Railjack component. DE write <c>Lavan Plating Mk Ii</c>; the wiki documents | |
| 130 | + /// every mark of it on one page, at <c>Lavan Plating</c>. | |
| 131 | + /// </summary> | |
| 132 | + | |
| 133 | + private static partial Regex ComponentMark { get; } | |
| 134 | + | |
| 135 | + /// <summary> | |
| 136 | + /// The wiki page for an item, through MediaWiki's "go" search rather than a direct link. | |
| 137 | + /// </summary> | |
| 138 | + /// <remarks> | |
| 139 | + /// The wiki documents the thing, not the piece: there is a Gyre Prime page but no | |
| 140 | + /// "Gyre Prime Neuroptics Blueprint" one, and linking straight to the item name 404s. So the | |
| 141 | + /// name is trimmed back to what is likely to be a title, and handed to the search rather than | |
| 142 | + /// to /w/. An exact match redirects to the page, and anything the trimming gets wrong lands on | |
| 143 | + /// search results for it, which is still an answer. A direct link would be a dead end. | |
| 144 | + /// </remarks> | |
| 145 | + private static string WikiLink(string name) => | |
| 146 | + "https://wiki.warframe.com/index.php?search=" + Uri.EscapeDataString(WikiTitle(name)) + "&go=Go"; | |
| 147 | + | |
| 148 | + private static string WikiTitle(string name) | |
| 149 | + { | |
| 150 | + var title = name; | |
| 151 | + title = AmountPrefix.Replace(title, ""); | |
| 152 | + title = Refinement.Replace(title, ""); | |
| 153 | + title = ComponentMark.Replace(title, "").Trim(); | |
| 154 | + | |
| 155 | + // Every part of a prime lives on the prime's own page, so "Akstiletto Prime Barrel" and | |
| 156 | + // "Nikana Prime Blueprint" both want everything up to and including the word Prime. | |
| 157 | + var prime = title.IndexOf(" Prime", StringComparison.Ordinal); | |
| 158 | + if (prime >= 0) return title[..(prime + " Prime".Length)]; | |
| 159 | + | |
| 160 | + if (title.EndsWith(" Relic", StringComparison.Ordinal)) | |
| 161 | + { | |
| 162 | + return title[..^" Relic".Length]; | |
| 163 | + } | |
| 164 | + | |
| 165 | + // "2,000 Credits Cache" is the reward; Credits is the page. | |
| 166 | + if (title.EndsWith(" Cache", StringComparison.Ordinal)) | |
| 167 | + { | |
| 168 | + return title[..^" Cache".Length]; | |
| 169 | + } | |
| 170 | + | |
| 171 | + if (title.EndsWith(" Blueprint", StringComparison.Ordinal)) | |
| 172 | + { | |
| 173 | + title = title[..^" Blueprint".Length]; | |
| 174 | + | |
| 175 | + // "Gara Chassis Blueprint" is documented under Gara, but "Stahlta Blueprint" is already | |
| 176 | + // the title, so only a trailing component comes off. | |
| 177 | + foreach (var component in Components) | |
| 178 | + { | |
| 179 | + if (title.EndsWith(" " + component, StringComparison.Ordinal)) | |
| 180 | + { | |
| 181 | + return title[..^(component.Length + 1)]; | |
| 182 | + } | |
| 183 | + } | |
| 184 | + } | |
| 185 | + | |
| 186 | + return title; | |
| 187 | + } | |
| 188 | + | |
| 189 | + /// <summary> | |
| 101 | 190 | /// The size of the index. Invariant rather than the request's culture: the sentence around it |
| 102 | 191 | /// is English, and "3.481 items" reads as a different number. |
| 103 | 192 | /// </summary> |
Blog/wwwroot/app.css +15 -0
| @@ -770,6 +770,21 @@ progress { | ||
| 770 | 770 | margin-block: var() 0; |
| 771 | 771 | } |
| 772 | 772 | |
| 773 | +/* An offsite link sitting beside a name that already links within the site. | |
| 774 | + Dimmed and smaller so the two read as different kinds of thing, and it does | |
| 775 | + not compete with the name it follows. */ | |
| 776 | +.wiki { | |
| 777 | + font-size: 0.8em; | |
| 778 | + font-weight: 400; | |
| 779 | + opacity: 50%; | |
| 780 | + white-space: nowrap; | |
| 781 | +} | |
| 782 | + | |
| 783 | +.wiki:hover, | |
| 784 | +.wiki:focus { | |
| 785 | + opacity: 100%; | |
| 786 | +} | |
| 787 | + | |
| 773 | 788 | /* Each relic is a heading and its own small table. */ |
| 774 | 789 | .relic + .relic { |
| 775 | 790 | margin-block-start: var(); |
CLAUDE.md +9 -0
| @@ -145,6 +145,15 @@ is keyed on the file's last write time, so `dotnet watch` picks up a refresh wit | ||
| 145 | 145 | |
| 146 | 146 | Configuration lives under `Warframe` (`WarframeOptions`): `FilePath`. |
| 147 | 147 | |
| 148 | +Every item and relic name carries a `[wiki]` link beside it. It goes through MediaWiki's *go* | |
| 149 | +search (`wiki.warframe.com/index.php?search=...&go=Go`), **not** a direct `/w/Title` link: the wiki | |
| 150 | +documents the thing rather than the piece, so there is a `Gyre Prime` page but no | |
| 151 | +`Gyre Prime Neuroptics Blueprint` one, and a direct link to the item name 404s. `WikiTitle` trims a | |
| 152 | +name back to a likely title (everything up to `Prime`, minus relic/cache suffixes, amounts, and | |
| 153 | +Railjack marks) and the go-search does the rest: an exact match redirects to the page, anything the | |
| 154 | +trimming gets wrong lands on search results for it. That is about 96% direct hits on a sample of the | |
| 155 | +real names, and the remainder degrade to a search rather than a dead end. | |
| 156 | + | |
| 148 | 157 | The page is server-rendered and driven by the query string (`?q=` search, `?item=` selection), so |
| 149 | 158 | results are linkable and work without JS. `Warframe.razor.js` only fills the search box's |
| 150 | 159 | `<datalist>` from `/api/warframe/names` (mapped in `Program.cs`). It does *not* import |