@page "/notes" @attribute [Authorize] @implements IAsyncDisposable @using Core.Entities @using WebApp.Services @using WebApp.Components.Shared.Components @inject INotesService NotesService @inject IDialogService DialogService @inject ISnackbar Snackbar Create Note @if (_isLoading) { } else if (!_notes.Any()) { No notes yet. Create your first note to get started! } else { @foreach (var note in _notes) { @if (!string.IsNullOrWhiteSpace(note.Content)) { @((MarkupString)MarkdownHelper.ToHtml(note.Content)) } Last updated: @note.UpdatedAt.ToString("g") by @(note.LastModifiedBy ?? "Unknown") History Edit Delete } } @code { private List _notes = []; private bool _isLoading = true; private CancellationTokenSource? _cancellationTokenSource; private bool _isDisposed = false; protected override void OnInitialized() { _cancellationTokenSource = new CancellationTokenSource(); } protected override async Task OnInitializedAsync() { await LoadNotes(); } private async Task LoadNotes() { if (_isDisposed) return; _isLoading = true; try { var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None; _notes = (await NotesService.GetNotesAsync()).ToList(); } catch (TaskCanceledException) { // Component was disposed, ignore } catch (JSDisconnectedException) { // JS connection lost, ignore } catch (Exception ex) { if (!_isDisposed) { Snackbar.Add($"Error loading notes: {ex.Message}", Severity.Error); } } finally { if (!_isDisposed) { _isLoading = false; StateHasChanged(); } } } private async Task OpenCreateDialog() { if (_isDisposed) return; var parameters = new DialogParameters { ["Note"] = new Note { Title = "", Content = "" }, ["IsEdit"] = false }; var options = new DialogOptions { MaxWidth = MaxWidth.Large, FullWidth = true, CloseButton = true }; var dialog = await DialogService.ShowAsync("Create Note", parameters, options); var result = await dialog.Result; if (!result.Canceled && !_isDisposed) { await LoadNotes(); } } private async Task OpenEditDialog(Note note) { if (_isDisposed) return; var parameters = new DialogParameters { ["Note"] = note, ["IsEdit"] = true }; var options = new DialogOptions { MaxWidth = MaxWidth.Large, FullWidth = true, CloseButton = true }; var dialog = await DialogService.ShowAsync("Edit Note", parameters, options); var result = await dialog.Result; if (!result.Canceled && !_isDisposed) { await LoadNotes(); } } private async Task OpenHistoryDialog(int noteId) { if (_isDisposed) return; var parameters = new DialogParameters { ["NoteId"] = noteId }; var options = new DialogOptions { MaxWidth = MaxWidth.Large, FullWidth = true, CloseButton = true }; await DialogService.ShowAsync("Note History", parameters, options); } private async Task DeleteNote(Note note) { if (_isDisposed) return; try { var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None; var result = await DialogService.ShowMessageBox( "Delete Note", (MarkupString)$"Are you sure you want to delete {note.Title}? This cannot be undone.", yesText: "Yes", noText: "Cancel"); if (_isDisposed) return; if (result == true) { await NotesService.DeleteNoteAsync(note.Id); if (!_isDisposed) { Snackbar.Add($"Note '{note.Title}' deleted", Severity.Info); await LoadNotes(); } } } catch (TaskCanceledException) { // Component was disposed, ignore } catch (JSDisconnectedException) { // JS connection lost, ignore } catch (Exception ex) { if (!_isDisposed) { Snackbar.Add($"Error deleting note: {ex.Message}", Severity.Error); } } } public async ValueTask DisposeAsync() { if (!_isDisposed) { _isDisposed = true; _cancellationTokenSource?.Cancel(); _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; } await ValueTask.CompletedTask; } }