Making cards in deck description clickable
This commit is contained in:
@@ -6,42 +6,4 @@ namespace Tests;
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class PlaywrightTests : PageTest
|
public class PlaywrightTests : PageTest
|
||||||
{
|
{
|
||||||
[Test]
|
|
||||||
public async Task CanWriteAndSaveNote()
|
|
||||||
{
|
|
||||||
// 1. Navigate to the cards gallery
|
|
||||||
await Page.GotoAsync("http://localhost:8080/cards");
|
|
||||||
|
|
||||||
// 2. Wait for cards to load - looking for at least one card-cell
|
|
||||||
await Expect(Page.Locator(".card-cell").First).ToBeVisibleAsync();
|
|
||||||
|
|
||||||
// 3. Find an Agent card and click it.
|
|
||||||
var agentCard = Page.Locator(".card-cell:has(.card-category-badge.agent)").First;
|
|
||||||
await agentCard.ClickAsync();
|
|
||||||
|
|
||||||
// 4. Wait for the detail view to show the note textarea
|
|
||||||
var noteInput = Page.Locator(".note-input");
|
|
||||||
await Expect(noteInput).ToBeVisibleAsync();
|
|
||||||
|
|
||||||
// 5. Type a unique note
|
|
||||||
var uniqueNote = "Test note " + Guid.NewGuid();
|
|
||||||
await noteInput.FillAsync(uniqueNote);
|
|
||||||
|
|
||||||
// 6. Blur to trigger save
|
|
||||||
await noteInput.BlurAsync();
|
|
||||||
|
|
||||||
// 7. Wait for saving indicator to disappear (if it appeared)
|
|
||||||
var savingIndicator = Page.Locator(".saving-indicator");
|
|
||||||
if (await savingIndicator.IsVisibleAsync()) await Expect(savingIndicator).Not.ToBeVisibleAsync();
|
|
||||||
|
|
||||||
// 8. Close the detail view by clicking the backdrop
|
|
||||||
await Page.Locator(".modal-backdrop").ClickAsync();
|
|
||||||
await Expect(noteInput).Not.ToBeVisibleAsync();
|
|
||||||
|
|
||||||
// 9. Re-open the same agent card
|
|
||||||
await agentCard.ClickAsync();
|
|
||||||
|
|
||||||
// 10. Verify the note is still there
|
|
||||||
await Expect(noteInput).ToHaveValueAsync(uniqueNote);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -111,10 +111,7 @@
|
|||||||
<section class="deck-section description">
|
<section class="deck-section description">
|
||||||
<h2><i class="bi bi-chat-quote-fill"></i> Description</h2>
|
<h2><i class="bi bi-chat-quote-fill"></i> Description</h2>
|
||||||
<div class="deck-description">
|
<div class="deck-description">
|
||||||
@foreach (var paragraph in deck.Description.Split('\n', StringSplitOptions.RemoveEmptyEntries))
|
@RenderDescription(deck.Description)
|
||||||
{
|
|
||||||
<p>@paragraph</p>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
@@ -238,4 +235,65 @@
|
|||||||
selectedCard = null;
|
selectedCard = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RenderFragment RenderDescription(string description)
|
||||||
|
{
|
||||||
|
return builder =>
|
||||||
|
{
|
||||||
|
var cardNames = CardDatabase.Cards
|
||||||
|
.Select(c => c.Name)
|
||||||
|
.Where(n => !string.IsNullOrWhiteSpace(n))
|
||||||
|
.OrderByDescending(n => n.Length)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
int sequence = 0;
|
||||||
|
var paragraphs = description.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
foreach (var paragraph in paragraphs)
|
||||||
|
{
|
||||||
|
builder.OpenElement(sequence++, "p");
|
||||||
|
|
||||||
|
var currentText = paragraph;
|
||||||
|
while (!string.IsNullOrEmpty(currentText))
|
||||||
|
{
|
||||||
|
int bestMatchIndex = -1;
|
||||||
|
string? bestMatchName = null;
|
||||||
|
|
||||||
|
foreach (var name in cardNames)
|
||||||
|
{
|
||||||
|
int index = currentText.IndexOf(name, StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (index != -1 && (bestMatchIndex == -1 || index < bestMatchIndex))
|
||||||
|
{
|
||||||
|
bestMatchIndex = index;
|
||||||
|
bestMatchName = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestMatchIndex != -1 && bestMatchName != null)
|
||||||
|
{
|
||||||
|
if (bestMatchIndex > 0)
|
||||||
|
{
|
||||||
|
builder.AddContent(sequence++, currentText.Substring(0, bestMatchIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
var card = LookupCard(bestMatchName);
|
||||||
|
builder.OpenElement(sequence++, "button");
|
||||||
|
builder.AddAttribute(sequence++, "class", "inline-card-btn");
|
||||||
|
builder.AddAttribute(sequence++, "onclick", EventCallback.Factory.Create(this, () => SelectCard(card)));
|
||||||
|
builder.AddContent(sequence++, bestMatchName);
|
||||||
|
builder.CloseElement();
|
||||||
|
|
||||||
|
currentText = currentText.Substring(bestMatchIndex + bestMatchName.Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AddContent(sequence++, currentText);
|
||||||
|
currentText = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.CloseElement();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,6 +179,7 @@
|
|||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.deck-description p {
|
.deck-description p {
|
||||||
margin: 0 0 1rem;
|
margin: 0 0 1rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
--text-secondary: #9898b8;
|
--text-secondary: #9898b8;
|
||||||
--text-muted: #686888;
|
--text-muted: #686888;
|
||||||
--accent: #6c63ff;
|
--accent: #6c63ff;
|
||||||
|
--accent-rgb: 108, 99, 255;
|
||||||
--accent-glow: rgba(108, 99, 255, 0.3);
|
--accent-glow: rgba(108, 99, 255, 0.3);
|
||||||
--gold: #ffd700;
|
--gold: #ffd700;
|
||||||
--gold-glow: rgba(255, 215, 0, 0.4);
|
--gold-glow: rgba(255, 215, 0, 0.4);
|
||||||
@@ -254,3 +255,29 @@ a, .btn-link {
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.inline-card-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0 2px;
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px dashed var(--accent);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition);
|
||||||
|
display: inline;
|
||||||
|
font-size: inherit;
|
||||||
|
font-family: inherit;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-card-btn:hover {
|
||||||
|
color: var(--accent-light, #60a5fa);
|
||||||
|
border-bottom-style: solid;
|
||||||
|
background-color: rgba(var(--accent-rgb, 59, 130, 246), 0.1);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user