299 lines
No EOL
11 KiB
Text
299 lines
No EOL
11 KiB
Text
@page "/{User}"
|
|
@using System.Text.RegularExpressions
|
|
@using Iceshrimp.Frontend.Components
|
|
@using Iceshrimp.Frontend.Components.Note
|
|
@using Iceshrimp.Frontend.Core.Miscellaneous
|
|
@using Iceshrimp.Frontend.Core.Services
|
|
@using Iceshrimp.Shared.Schemas.Web
|
|
@using Microsoft.AspNetCore.Authorization
|
|
@using Iceshrimp.Assets.PhosphorIcons
|
|
@using Iceshrimp.Frontend.Localization
|
|
@using Microsoft.Extensions.Localization
|
|
@attribute [Authorize]
|
|
@inject ApiService Api
|
|
@inject GlobalComponentSvc GlobalComponentSvc;
|
|
@inject IJSRuntime Js;
|
|
@inject IStringLocalizer<Localization> Loc;
|
|
@inject MetadataService MetadataService
|
|
@inject NavigationManager Nav;
|
|
@inject SessionService Session
|
|
|
|
@if (_init)
|
|
{
|
|
<div class="scroller">
|
|
@if (UserResponse.BannerUrl != null)
|
|
{
|
|
<img class="banner" src="@UserResponse.BannerUrl"
|
|
alt="@(UserResponse.BannerAlt ?? Loc["{0}'s banner", UserResponse.DisplayName ?? UserResponse.Username])"
|
|
title="@(UserResponse.BannerAlt ?? Loc["{0}'s banner", UserResponse.DisplayName ?? UserResponse.Username])"/>
|
|
}
|
|
|
|
<div class="profile-card">
|
|
<div class="header">
|
|
<div class="subheader">
|
|
<div>
|
|
<UserAvatar User="@UserResponse" Size="5em" Title="@true"/>
|
|
</div>
|
|
<div class="name-section">
|
|
<div class="name">
|
|
<UserDisplayName User="@UserResponse"/>
|
|
</div>
|
|
<div class="identifier">
|
|
@@@UserResponse.Username
|
|
@if (UserResponse.Host != null)
|
|
{
|
|
var host = $"@{UserResponse.Host}";
|
|
@host
|
|
}
|
|
@if (Profile != null && Profile.Relations.HasFlag(Relations.FollowedBy))
|
|
{
|
|
<span class="follows-badge">Follows you</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<FollowButton User="UserResponse" UserProfile="Profile"/>
|
|
<button @ref="MenuButton" class="context-button" @onclick="ToggleMenu" @onclick:stopPropagation="true" aria-label="more">
|
|
<Icon Name="Icons.DotsThreeOutline" Size="1.3em"/>
|
|
<Menu @ref="ContextMenu">
|
|
@if (UserResponse.Id == Session.Current?.Id)
|
|
{
|
|
<MenuElement Icon="Icons.Pencil" OnSelect="EditProfile">
|
|
<Text>@Loc["Edit profile"]</Text>
|
|
</MenuElement>
|
|
}
|
|
else
|
|
{
|
|
<MenuElement Icon="Icons.Tooth" OnSelect="Bite">
|
|
<Text>@Loc["Bite"]</Text>
|
|
</MenuElement>
|
|
@if (Profile != null && Profile.Relations.HasFlag(Relations.FollowedBy))
|
|
{
|
|
<MenuElement Icon="Icons.LinkBreak" OnSelect="RemoveFromFollowers">
|
|
<Text>@Loc["Remove follower"]</Text>
|
|
</MenuElement>
|
|
}
|
|
}
|
|
@if (UserResponse.Host != null && Profile?.Url != null)
|
|
{
|
|
<MenuElement Icon="Icons.ArrowSquareOut" OnSelect="OpenOriginal">
|
|
<Text>@Loc["Open original page"]</Text>
|
|
</MenuElement>
|
|
}
|
|
<MenuElement Icon="Icons.Share" OnSelect="CopyLink">
|
|
<Text>@Loc["Copy link"]</Text>
|
|
</MenuElement>
|
|
@if (UserResponse.Host != null && Profile?.Url != null)
|
|
{
|
|
<MenuElement Icon="Icons.ShareNetwork" OnSelect="CopyLinkRemote">
|
|
<Text>@Loc["Copy link (remote)"]</Text>
|
|
</MenuElement>
|
|
}
|
|
<MenuElement Icon="Icons.At" OnSelect="CopyUsername">
|
|
<Text>@Loc["Copy username"]</Text>
|
|
</MenuElement>
|
|
@if (UserResponse.Host != null)
|
|
{
|
|
<MenuElement Icon="Icons.ArrowsClockwise" OnSelect="RefetchUser">
|
|
<Text>@Loc["Refetch"]</Text>
|
|
</MenuElement>
|
|
}
|
|
<ClosingBackdrop OnClose="ContextMenu.Close"></ClosingBackdrop>
|
|
</Menu>
|
|
</button>
|
|
</div>
|
|
<ProfileInfo Emojis="@UserResponse.Emojis" User="UserResponse" UserProfile="Profile"/>
|
|
</div>
|
|
@if (UserNotes.Count > 0)
|
|
{
|
|
<div class="notes">
|
|
@foreach (var note in UserNotes)
|
|
{
|
|
<div class="note-container" @onclick="() => OpenNote(note.RenoteId ?? note.Id)">
|
|
<Note NoteResponse="note"/>
|
|
</div>
|
|
}
|
|
</div>
|
|
<ScrollEnd IntersectionChange="AddNotes" ManualLoad="AddNotes" Class="end"/>
|
|
}
|
|
</div>
|
|
}
|
|
@if (_loading)
|
|
{
|
|
<div>loading</div>
|
|
}
|
|
@if (_notFound)
|
|
{
|
|
<div>User does not exist!</div>
|
|
}
|
|
@if (_error)
|
|
{
|
|
<div>Failure</div>
|
|
}
|
|
|
|
@code {
|
|
[Parameter] public string? User { get; set; }
|
|
[Parameter] public string? Host { get; set; }
|
|
private UserResponse UserResponse { get; set; } = null!;
|
|
private UserProfileResponse? Profile { get; set; }
|
|
private string? MinId { get; set; }
|
|
private List<NoteResponse> UserNotes { get; set; } = [];
|
|
|
|
private ElementReference MenuButton { get; set; }
|
|
private Menu ContextMenu { get; set; } = null!;
|
|
|
|
private bool _loading = true;
|
|
private bool _init;
|
|
private bool _notFound;
|
|
private bool _error;
|
|
private bool _fetchLock;
|
|
|
|
private async Task GetNotes(string? minId)
|
|
{
|
|
var pq = new PaginationQuery { Limit = 10, MaxId = minId };
|
|
var notes = await Api.Users.GetUserNotesAsync(UserResponse.Id, pq);
|
|
if (notes is not null && notes.Count > 0)
|
|
{
|
|
MinId = notes.Last().Id;
|
|
UserNotes.AddRange(notes);
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private async Task AddNotes()
|
|
{
|
|
if (_fetchLock == false)
|
|
{
|
|
_fetchLock = true;
|
|
await GetNotes(MinId);
|
|
_fetchLock = false;
|
|
}
|
|
}
|
|
|
|
private async Task LoadProfile()
|
|
{
|
|
try
|
|
{
|
|
if (User is null)
|
|
{
|
|
_notFound = true;
|
|
}
|
|
else
|
|
{
|
|
var pattern = "^@([^@]+)@?(.+)?$";
|
|
var matches = Regex.Match(User, pattern);
|
|
var userResponse = await Api.Users.LookupUserAsync(matches.Groups[1].Value, matches.Groups[2].Value);
|
|
if (userResponse is null)
|
|
{
|
|
_notFound = true;
|
|
}
|
|
else
|
|
{
|
|
UserResponse = userResponse;
|
|
Profile = await Api.Users.GetUserProfileAsync(UserResponse.Id);
|
|
await GetNotes(null);
|
|
_init = true;
|
|
_loading = false;
|
|
}
|
|
}
|
|
}
|
|
catch (ApiException)
|
|
{
|
|
_loading = false;
|
|
_error = true;
|
|
}
|
|
}
|
|
|
|
private void OpenNote(string id)
|
|
{
|
|
Nav.NavigateTo($"/notes/{id}");
|
|
}
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await LoadProfile();
|
|
}
|
|
|
|
protected override async Task OnParametersSetAsync()
|
|
{
|
|
_init = false;
|
|
_loading = true;
|
|
UserNotes = [];
|
|
MinId = null;
|
|
Profile = null;
|
|
StateHasChanged();
|
|
await LoadProfile();
|
|
}
|
|
|
|
private void ToggleMenu() => ContextMenu.Toggle(MenuButton);
|
|
|
|
private void EditProfile() => Nav.NavigateTo("/settings/profile");
|
|
|
|
private async Task Bite()
|
|
{
|
|
await Api.Users.BiteUserAsync(UserResponse.Id);
|
|
await GlobalComponentSvc.NoticeDialog?.Display(Loc["Bit {0}", UserResponse.DisplayName ?? UserResponse.Username])!;
|
|
}
|
|
|
|
private async Task RemoveFromFollowersAction(bool confirm)
|
|
{
|
|
if (!confirm) return;
|
|
|
|
var removed = await Api.Users.RemoveUserFromFollowersAsync(UserResponse.Id);
|
|
if (!removed) return;
|
|
|
|
await GlobalComponentSvc.NoticeDialog?.Display(Loc["Removed {0} from followers", UserResponse.DisplayName ?? UserResponse.Username])!;
|
|
|
|
Profile!.Relations &= ~Relations.FollowedBy;
|
|
StateHasChanged();
|
|
}
|
|
|
|
private void RemoveFromFollowers() =>
|
|
GlobalComponentSvc.ConfirmDialog?.Confirm(new EventCallback<bool>(this, RemoveFromFollowersAction), Loc["Remove {0} from followers?", UserResponse.DisplayName ?? UserResponse.Username], buttonText: Loc["Remove follower"]);
|
|
|
|
private void OpenOriginal()
|
|
{
|
|
if (Profile?.Url != null)
|
|
Js.InvokeVoidAsync("open", Profile.Url, "_blank");
|
|
}
|
|
|
|
private async Task CopyLink()
|
|
{
|
|
var instance = await MetadataService.Instance.Value;
|
|
await Js.InvokeVoidAsync("navigator.clipboard.writeText", $"https://{instance.WebDomain}/@{UserResponse.Username}" + (UserResponse.Host != null ? $"@{UserResponse.Host}" : ""));
|
|
}
|
|
|
|
private void CopyLinkRemote()
|
|
{
|
|
if (Profile?.Url != null)
|
|
Js.InvokeVoidAsync("navigator.clipboard.writeText", Profile.Url);
|
|
}
|
|
|
|
private void CopyUsername() => Js.InvokeVoidAsync("navigator.clipboard.writeText", $"@{UserResponse.Username}" + (UserResponse.Host != null ? $"@{UserResponse.Host}" : ""));
|
|
|
|
private async Task RefetchUser() =>
|
|
await GlobalComponentSvc.ConfirmDialog?.Confirm(new EventCallback<bool>(this, RefetchUserCallback), Loc["Refetch {0}'s profile?", UserResponse.DisplayName ?? UserResponse.Username])!;
|
|
|
|
private async Task RefetchUserCallback(bool refetch)
|
|
{
|
|
if (!refetch) return;
|
|
|
|
try
|
|
{
|
|
var res = await Api.Users.RefetchUserAsync(UserResponse.Id);
|
|
if (res != null)
|
|
{
|
|
UserResponse = res;
|
|
var newProfile = await Api.Users.GetUserProfileAsync(UserResponse.Id);
|
|
if (newProfile != null)
|
|
Profile = newProfile;
|
|
StateHasChanged();
|
|
await GlobalComponentSvc.NoticeDialog?.Display(Loc["Successfully refetched {0}'s profile", UserResponse.DisplayName ?? UserResponse.Username])!;
|
|
}
|
|
}
|
|
catch (ApiException e)
|
|
{
|
|
await GlobalComponentSvc.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!;
|
|
}
|
|
}
|
|
} |