@page "/notes/{NoteId}" @attribute [Authorize] @inject ApiService ApiService @inject IJSRuntime Js @inject MessageService MessageService @inject StateService StateSvc @inject NavigationManager Navigation @inject IStringLocalizer Loc @inject ILogger Logger; @using Iceshrimp.Assets.PhosphorIcons @using Iceshrimp.Frontend.Components @using Iceshrimp.Frontend.Components.Note @using Iceshrimp.Frontend.Core.Miscellaneous @using Iceshrimp.Frontend.Core.Services @using Iceshrimp.Frontend.Core.Services.StateServicePatterns @using Iceshrimp.Frontend.Localization @using Iceshrimp.Shared.Schemas.Web @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Sections @using Microsoft.Extensions.Localization @implements IDisposable @if (_componentState == State.Loaded) { @if (RootNote is { User.DisplayName: not null } && RootNote.User.Emojis.Count != 0) { } else { @Loc["Note by {0}", (RootNote?.User.DisplayName ?? RootNote?.User.Username) ?? string.Empty] }
@if (Ascendants != null) {
@foreach (var note in Ascendants) { }
}
@Loc["Replies ({0})", RootNote!.Replies] @if (Descendants != null) {
@foreach (var element in Descendants) { }
}
@if (RootNote?.Likes > 0) { @Loc["Likes ({0})", RootNote!.Likes] } @if (RootNote?.Renotes > 0) { @Loc["Renotes ({0})", RootNote!.Renotes] } @if (RootNote?.Reactions.Count > 0) { @Loc["Reactions ({0})", RootNote!.Reactions.Count] } @Loc["Quotes"]
} @if (_componentState == State.Loading) {
Loading
} @if (_componentState == State.NotFound) {
This note does not exist!
} @if (_componentState == State.Empty) {
@Loc["This post has been deleted"]
} @if (_componentState == State.Error) {
An error occured loading the notes. Please inspect logs.
} @code { [Parameter] public string? NoteId { get; set; } public NoteResponse? RootNote { get; set; } private IList? Descendants { get; set; } private IList? Ascendants { get; set; } private IJSInProcessObjectReference? Module { get; set; } private ElementReference RootNoteRef { get; set; } private int _depth = 20; private IDisposable? _locationChangingHandlerDisposable; private IDisposable? _noteChangedHandler; private State _componentState; private bool _firstLoad = true; private async Task Load() { Logger.LogTrace($"Opening NoteID: {NoteId}"); _componentState = State.Loading; if (NoteId == null) { _componentState = State.NotFound; return; } try { var rootNoteTask = ApiService.Notes.GetNote(NoteId); var descendantsTask = ApiService.Notes.GetNoteDescendants(NoteId, _depth); var ascendantsTask = ApiService.Notes.GetNoteAscendants(NoteId, default); RootNote = await rootNoteTask; Descendants = await descendantsTask; Ascendants = await ascendantsTask; } catch (ApiException e) { Logger.LogWarning($"Failed to load ID '{NoteId}' due to API Exception: {e.Message}"); _componentState = State.Error; return; } if (RootNote == null) { _componentState = State.NotFound; return; } _componentState = State.Loaded; } protected override async Task OnInitializedAsync() { await Load(); } protected override async Task OnParametersSetAsync() { if (_firstLoad) { _firstLoad = false; return; } await Load(); StateHasChanged(); } protected override void OnInitialized() { if (NoteId != null) _noteChangedHandler = MessageService.Register(NoteId, OnNoteChanged, MessageService.Type.Updated); _locationChangingHandlerDisposable = Navigation.RegisterLocationChangingHandler(LocationChangeHandler); MessageService.AnyNoteDeleted += OnNoteDeleted; } private void OnNoteChanged(object? _, NoteResponse note) { var __ = Refresh(); } private void OnNoteDeleted(object? _, NoteResponse note) { if (NoteId == note.Id) { _componentState = State.Empty; } else { Ascendants?.Remove(note); Descendants?.Remove(note); } StateHasChanged(); } private async Task Refresh() { if (NoteId == null) throw new InvalidOperationException("RefreshNote called under impossible circumstances"); Descendants = await ApiService.Notes.GetNoteDescendants(NoteId, default); Ascendants = await ApiService.Notes.GetNoteAscendants(NoteId, default); StateHasChanged(); } private ValueTask LocationChangeHandler(LocationChangingContext arg) { SaveState(); return ValueTask.CompletedTask; } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { Module = (IJSInProcessObjectReference)await Js.InvokeAsync("import", "/Pages/SingleNote.razor.js"); } if (_componentState == State.Loaded) { var state = StateSvc.SingleNote.GetState(NoteId!); if (Module is null) { Logger.LogError("JS Interop used before initialization"); return; } if (state != null) { Module.InvokeVoid("SetScrollY", state.ScrollTop); } else { Module.InvokeVoid("ScrollIntoView", RootNoteRef); } } } private void SaveState() { if (NoteId == null || _componentState != State.Loaded) return; var scrollTop = (Module ?? throw new Exception("JS Interop used before init")) .Invoke("GetScrollY"); var state = new SingleNoteState { ScrollTop = scrollTop }; StateSvc.SingleNote.SetState(NoteId, state); } public void Dispose() { _noteChangedHandler?.Dispose(); SaveState(); _locationChangingHandlerDisposable?.Dispose(); MessageService.AnyNoteDeleted -= OnNoteDeleted; } }