@page "/notes/{NoteId}" @attribute [Authorize] @inject NoteStore NoteStore; @inject RelatedStore RelatedStore; @inject IJSRuntime Js @inject StateService StateSvc @inject NavigationManager Navigation @inject IStringLocalizer Loc @inject ILogger Logger; @inject StateSynchronizer StateSynchronizer; @inherits ChildNoteController @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.NoteStore @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 SortedList? Descendants { get; set; } private SortedList? Ascendants { get; set; } private IJSInProcessObjectReference? Module { get; set; } private ElementReference RootNoteRef { get; set; } private int _depth = 20; private IDisposable? _locationChangingHandlerDisposable; private State _componentState; private bool _firstLoad = true; private bool _cwState; private async Task Load() { Logger.LogTrace($"Opening NoteID: {NoteId}"); _componentState = State.Loading; if (NoteId == null) { _componentState = State.NotFound; return; } try { var rootNoteTask = NoteStore.GetNoteAsync(NoteId); var descendantsTask = RelatedStore.GetDescendantsAsync(NoteId, _depth); var ascendantsTask = RelatedStore.GetAscendantsAsync(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() { _locationChangingHandlerDisposable = Navigation.RegisterLocationChangingHandler(LocationChangeHandler); StateSynchronizer.NoteDeleted += OnNoteDeleted; NoteStore.AnyNoteChanged += OnNoteChanged; } private void OnNoteDeleted(object? _, NoteBase note) { if (NoteId == note.Id) { _componentState = State.Empty; } else { Ascendants?.Remove(note.Id); Descendants?.Remove(note.Id); } StateHasChanged(); } private void OnNoteChanged(object? _, NoteResponse note) { if (note.Id == NoteId) { var __ = Refresh(); } } private async Task Refresh() { if (NoteId != null) Descendants = await RelatedStore.GetDescendantsAsync(NoteId, _depth, true); StateHasChanged(); } // ReSharper disable once InconsistentNaming 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); } } if (_cwState) { foreach (var note in NoteChildren) { note.OpenCw(); } } else { foreach (var note in NoteChildren) { note.CloseCw(); } } } private void ToggleCw() { if (_cwState) { foreach (var note in NoteChildren) { note.CloseCw(); } _cwState = false; } else { foreach (var note in NoteChildren) { note.OpenCw(); } _cwState = true; } } 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() { SaveState(); _locationChangingHandlerDisposable?.Dispose(); StateSynchronizer.NoteDeleted -= OnNoteDeleted; NoteStore.AnyNoteChanged -= OnNoteChanged; } }