Chapter officers can merge a markdown note onto filtered students, teams, or events and save the recipe. Extra student-note columns are additional fields (any ## … fields heading) so they work as print tokens whether imported or typed by hand. Co-authored-by: Cursor <cursoragent@cursor.com>
168 lines
5.0 KiB
Plaintext
168 lines
5.0 KiB
Plaintext
@namespace WebApp.Components.Shared.Components
|
|
@inject INotesService NotesService
|
|
@inject IDialogService DialogService
|
|
@implements IAsyncDisposable
|
|
|
|
<div @onmouseenter="@(() => { if (_hasContent) _popoverOpen = true; })"
|
|
@onmouseleave="@(() => _popoverOpen = false)"
|
|
style="display: inline-block; position: relative;">
|
|
<MudTooltip Text="@TooltipText">
|
|
<MudButton StartIcon="@IconValue"
|
|
OnClick="OpenDialog"
|
|
Variant="@Variant"
|
|
Size="@Size"
|
|
Color="@ButtonColor"
|
|
Class="@MarkdownHelper.GetNoteColorClass(_noteId)">
|
|
@if (!string.IsNullOrEmpty(ButtonText))
|
|
{
|
|
@ButtonText
|
|
}
|
|
</MudButton>
|
|
</MudTooltip>
|
|
<MudPopover Open="_popoverOpen"
|
|
AnchorOrigin="Origin.BottomCenter"
|
|
TransformOrigin="Origin.TopCenter"
|
|
Elevation="8">
|
|
<ChildContent>
|
|
@if (_hasContent && !string.IsNullOrWhiteSpace(_noteContent))
|
|
{
|
|
<MudPaper Elevation="0"
|
|
Class="pa-3"
|
|
Style="max-width: 500px; max-height: 400px; overflow-y: auto;"
|
|
@onmouseenter="@(() => _popoverOpen = true)"
|
|
@onmouseleave="@(() => _popoverOpen = false)">
|
|
<MudText Typo="Typo.subtitle1" Class="mb-2">@PageIdentifier</MudText>
|
|
<MudDivider Class="my-2" />
|
|
<div class="markdown-content" style="max-height: 350px; overflow-y: auto;">
|
|
@((MarkupString)MarkdownHelper.ToHtml(_noteContent))
|
|
</div>
|
|
</MudPaper>
|
|
}
|
|
</ChildContent>
|
|
</MudPopover>
|
|
</div>
|
|
|
|
@code {
|
|
[Parameter]
|
|
public string PageIdentifier { get; set; } = null!;
|
|
|
|
[Parameter]
|
|
public string? Icon { get; set; }
|
|
|
|
[Parameter]
|
|
public Variant Variant { get; set; } = Variant.Outlined;
|
|
|
|
[Parameter]
|
|
public Size Size { get; set; } = Size.Medium;
|
|
|
|
[Parameter]
|
|
public string? ButtonText { get; set; }
|
|
|
|
[Parameter]
|
|
public string? Tooltip { get; set; }
|
|
|
|
private string IconValue => Icon ?? Icons.Material.Filled.Note;
|
|
private string TooltipText => Tooltip ?? $"Page notes for {PageIdentifier}";
|
|
private bool _hasContent = false;
|
|
private int _noteId = 0;
|
|
private string _noteContent = string.Empty;
|
|
private bool _popoverOpen = false;
|
|
private Color ButtonColor => _hasContent ? Color.Success : (Variant == Variant.Filled ? Color.Primary : Color.Default);
|
|
private CancellationTokenSource? _cancellationTokenSource;
|
|
private bool _isDisposed = false;
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|
}
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await CheckNoteContent();
|
|
}
|
|
|
|
private async Task CheckNoteContent()
|
|
{
|
|
if (_isDisposed) return;
|
|
|
|
try
|
|
{
|
|
var note = await NotesService.GetPageNoteAsync(PageIdentifier);
|
|
_hasContent = note != null && !string.IsNullOrWhiteSpace(note.Content);
|
|
_noteId = note?.Id ?? 0;
|
|
_noteContent = note?.Content ?? string.Empty;
|
|
|
|
if (!_isDisposed)
|
|
{
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
catch (TaskCanceledException)
|
|
{
|
|
// Component was disposed, ignore
|
|
}
|
|
catch (JSDisconnectedException)
|
|
{
|
|
// JS connection lost, ignore
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Error checking note - ignore
|
|
}
|
|
}
|
|
|
|
private async Task OpenDialog()
|
|
{
|
|
if (_isDisposed) return;
|
|
|
|
try
|
|
{
|
|
var parameters = new DialogParameters
|
|
{
|
|
["PageIdentifier"] = PageIdentifier
|
|
};
|
|
|
|
var options = new DialogOptions
|
|
{
|
|
MaxWidth = MaxWidth.Medium,
|
|
FullWidth = true,
|
|
CloseButton = true
|
|
};
|
|
|
|
var dialog = await DialogService.ShowAsync<PageNoteDialog>($"Page Notes: {PageIdentifier}", parameters, options);
|
|
var result = await dialog.Result;
|
|
|
|
// Always refresh content indicator after dialog closes
|
|
// (content may have been saved even if dialog was closed with Cancel)
|
|
if (!_isDisposed)
|
|
{
|
|
await CheckNoteContent();
|
|
}
|
|
}
|
|
catch (TaskCanceledException)
|
|
{
|
|
// Component was disposed, ignore
|
|
}
|
|
catch (JSDisconnectedException)
|
|
{
|
|
// JS connection lost, ignore
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Error opening dialog - could show snackbar if we had access
|
|
}
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
if (!_isDisposed)
|
|
{
|
|
_isDisposed = true;
|
|
_cancellationTokenSource?.Cancel();
|
|
_cancellationTokenSource?.Dispose();
|
|
_cancellationTokenSource = null;
|
|
}
|
|
await ValueTask.CompletedTask;
|
|
}
|
|
}
|