a fork of iceshrimp.net but a tweaked frontend to my personal liking. waow
fediverse social-media social iceshrimp fedi
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

[frontend/pages] Improve profile view button layout

pancakes 6235de62 8c4a1be5

+375 -357
-3
Iceshrimp.Frontend/Components/FollowButton.razor.css
··· 8 8 border: var(--notice-color) solid 0.1rem; 9 9 padding-inline: 1rem; 10 10 padding: 1rem; 11 - margin-bottom: 0.5rem; 12 - margin-inline: 1rem; 13 11 > .icon-wrapper { 14 12 padding-left: 0.5rem; 15 13 } ··· 29 27 .follow-button { 30 28 display: flex; 31 29 align-items: center; 32 - margin-left: auto; 33 30 } 34 31 ::deep {.icon { 35 32 vertical-align: text-bottom;
+347 -34
Iceshrimp.Frontend/Components/ProfileInfo.razor
··· 3 3 @using Microsoft.Extensions.Localization 4 4 @using Iceshrimp.Assets.PhosphorIcons 5 5 @using Iceshrimp.Frontend.Core.Miscellaneous 6 + @using Iceshrimp.Frontend.Core.Services 7 + @inject ApiService Api 8 + @inject GlobalComponentSvc GlobalComponentSvc; 9 + @inject IJSRuntime Js; 6 10 @inject IStringLocalizer<Localization> Loc; 11 + @inject MetadataService MetadataService 12 + @inject NavigationManager Nav; 13 + 7 14 <div class="profile-info"> 8 15 @if (User.MovedTo != null) 9 16 { ··· 12 19 <a href="@User.MovedTo" target="_blank">@Loc["Go to account"]</a> 13 20 </WarningBanner> 14 21 } 15 - <div class="badges"> 16 - @if (UserProfile.Lang != null && (UserProfile.Pronouns?.TryGetValue(UserProfile.Lang, out var pronouns) ?? false)) 17 - { 18 - <span class="badge">@pronouns</span> 19 - } 20 - @switch (UserProfile.Role) 21 - { 22 - case Role.Moderator: 22 + <div class="top-row"> 23 + <div class="badges"> 24 + @if (UserProfile.Lang != null && (UserProfile.Pronouns?.TryGetValue(UserProfile.Lang, out var pronouns) ?? false)) 25 + { 26 + <span class="badge">@pronouns</span> 27 + } 28 + @switch (UserProfile.Role) 29 + { 30 + case Role.Moderator: 31 + <span class="badge"> 32 + <Icon Name="Icons.Shield" Size="1.3em"/> 33 + @Loc["Moderator"] 34 + </span> 35 + break; 36 + case Role.Admin: 37 + <span class="badge"> 38 + <Icon Name="Icons.ShieldStar" Size="1.3em"/> 39 + @Loc["Admin"] 40 + </span> 41 + break; 42 + case Role.None: 43 + break; 44 + default: 45 + throw new ArgumentOutOfRangeException(); 46 + } 47 + @if (User.IsBot) 48 + { 23 49 <span class="badge"> 24 - <Icon Name="Icons.Shield" Size="1.3em"/> 25 - @Loc["Moderator"] 50 + <Icon Name="Icons.Robot" Size="1.3em"/> 51 + @Loc["Automated"] 26 52 </span> 27 - break; 28 - case Role.Admin: 53 + } 54 + @if (UserProfile.IsLocked) 55 + { 29 56 <span class="badge"> 30 - <Icon Name="Icons.ShieldStar" Size="1.3em"/> 31 - @Loc["Admin"] 57 + <Icon Name="Icons.Lock" Size="1.3em"/> 58 + @Loc["Private"] 32 59 </span> 33 - break; 34 - case Role.None: 35 - break; 36 - default: 37 - throw new ArgumentOutOfRangeException(); 38 - } 39 - @if (User.IsBot) 40 - { 41 - <span class="badge"> 42 - <Icon Name="Icons.Robot" Size="1.3em"/> 43 - @Loc["Automated"] 44 - </span> 45 - } 46 - @if (UserProfile.IsLocked) 47 - { 48 - <span class="badge"> 49 - <Icon Name="Icons.Lock" Size="1.3em"/> 50 - @Loc["Private"] 51 - </span> 52 - } 60 + } 61 + </div> 62 + <div class="profile-buttons"> 63 + <FollowButton User="User" UserProfile="UserProfile"/> 64 + <button @ref="MenuButton" class="button context-button" @onclick="ToggleMenu" @onclick:stopPropagation="true" aria-label="more"> 65 + <Icon Name="Icons.DotsThreeOutline" Size="1.3em"/> 66 + <Menu @ref="ContextMenu"> 67 + @if (UserProfile.Relations.HasFlag(Relations.Self)) 68 + { 69 + <MenuElement Icon="Icons.Pencil" OnSelect="EditProfile"> 70 + <Text>@Loc["Edit profile"]</Text> 71 + </MenuElement> 72 + } 73 + else 74 + { 75 + <MenuElement Icon="Icons.Tooth" OnSelect="Bite"> 76 + <Text>@Loc["Bite"]</Text> 77 + </MenuElement> 78 + } 79 + @if (User.Host != null) 80 + { 81 + <MenuElement Icon="Icons.ArrowsClockwise" OnSelect="RefetchUser"> 82 + <Text>@Loc["Refetch"]</Text> 83 + </MenuElement> 84 + } 85 + <hr class="rule"/> 86 + <MenuElement Icon="Icons.Info" OnSelect="OpenAbout"> 87 + <Text>@Loc["About"]</Text> 88 + </MenuElement> 89 + @if (User.Host != null && UserProfile?.Url != null) 90 + { 91 + <MenuElement Icon="Icons.ArrowSquareOut" OnSelect="OpenOriginal"> 92 + <Text>@Loc["Open original page"]</Text> 93 + </MenuElement> 94 + } 95 + <MenuElement Icon="Icons.Share" OnSelect="CopyLink"> 96 + <Text>@Loc["Copy link"]</Text> 97 + </MenuElement> 98 + @if (User.Host != null && UserProfile?.Url != null) 99 + { 100 + <MenuElement Icon="Icons.ShareNetwork" OnSelect="CopyLinkRemote"> 101 + <Text>@Loc["Copy link (remote)"]</Text> 102 + </MenuElement> 103 + } 104 + <MenuElement Icon="Icons.At" OnSelect="CopyUsername"> 105 + <Text>@Loc["Copy username"]</Text> 106 + </MenuElement> 107 + @if (User.Host == null) 108 + { 109 + <MenuElement Icon="Icons.Atom" OnSelect="CopyAtomFeed"> 110 + <Text>@Loc["Copy Atom feed"]</Text> 111 + </MenuElement> 112 + <MenuElement Icon="Icons.BracketsCurly" OnSelect="CopyJsonFeed"> 113 + <Text>@Loc["Copy JSON feed"]</Text> 114 + </MenuElement> 115 + <MenuElement Icon="Icons.Rss" OnSelect="CopyRssFeed"> 116 + <Text>@Loc["Copy RSS feed"]</Text> 117 + </MenuElement> 118 + } 119 + @if (UserProfile != null && !UserProfile.Relations.HasFlag(Relations.Self)) 120 + { 121 + <hr class="rule"/> 122 + @if (UserProfile.Relations.HasFlag(Relations.Muting)) 123 + { 124 + <MenuElement Icon="Icons.Eye" OnSelect="Unmute"> 125 + <Text>@Loc["Unmute"]</Text> 126 + </MenuElement> 127 + } 128 + else 129 + { 130 + <MenuElement Icon="Icons.EyeSlash" OnSelect="Mute"> 131 + <Text>@Loc["Mute"]</Text> 132 + </MenuElement> 133 + <MenuElement Icon="Icons.Timer" OnSelect="TemporaryMute"> 134 + <Text>@Loc["Temporary mute"]</Text> 135 + </MenuElement> 136 + } 137 + 138 + @if (UserProfile.Relations.HasFlag(Relations.FollowedBy)) 139 + { 140 + <MenuElement Icon="Icons.LinkBreak" OnSelect="RemoveFromFollowers" Danger> 141 + <Text>@Loc["Remove follower"]</Text> 142 + </MenuElement> 143 + } 144 + 145 + @if (UserProfile.Relations.HasFlag(Relations.Blocking)) 146 + { 147 + <MenuElement Icon="Icons.Prohibit" OnSelect="Unblock" Danger> 148 + <Text>@Loc["Unblock"]</Text> 149 + </MenuElement> 150 + } 151 + else 152 + { 153 + <MenuElement Icon="Icons.Prohibit" OnSelect="Block" Danger> 154 + <Text>@Loc["Block"]</Text> 155 + </MenuElement> 156 + } 157 + 158 + <MenuElement Icon="Icons.WarningCircle" OnSelect="Report" Danger> 159 + <Text>@Loc["Report"]</Text> 160 + </MenuElement> 161 + } 162 + <ClosingBackdrop OnClose="ContextMenu.Close"></ClosingBackdrop> 163 + </Menu> 164 + </button> 165 + </div> 53 166 </div> 54 167 @if (UserProfile.Bio != null) 55 168 { ··· 123 236 [Parameter] [EditorRequired] public required UserProfileResponse UserProfile { get; set; } 124 237 [Parameter] [EditorRequired] public required List<EmojiResponse> Emojis { get; set; } 125 238 239 + private ElementReference MenuButton { get; set; } 240 + private Menu ContextMenu { get; set; } = null!; 241 + private InstanceResponse? _instance; 242 + 126 243 private Dictionary<string, string> _languages = LanguageHelper.Bcp47Languages; 244 + 245 + private void ToggleMenu() => ContextMenu.Toggle(MenuButton); 246 + 247 + private void EditProfile() => Nav.NavigateTo("/settings/profile"); 248 + 249 + private void Bite() => Api.Users.BiteUserAsync(User.Id); 250 + 251 + private async Task RemoveFromFollowersAction(bool confirm) 252 + { 253 + if (!confirm) return; 254 + 255 + var removed = await Api.Users.RemoveUserFromFollowersAsync(User.Id); 256 + if (!removed) return; 257 + 258 + await GlobalComponentSvc.NoticeDialog?.Display(Loc["Removed {0} from followers", User.DisplayName ?? User.Username])!; 259 + 260 + UserProfile.Relations &= ~Relations.FollowedBy; 261 + StateHasChanged(); 262 + } 263 + 264 + private void RemoveFromFollowers() => 265 + GlobalComponentSvc.ConfirmDialog?.Confirm(new EventCallback<bool>(this, RemoveFromFollowersAction), Loc["Remove {0} from followers?", User.DisplayName ?? User.Username], buttonText: Loc["Remove follower"]); 266 + 267 + private void Mute() => 268 + GlobalComponentSvc.ConfirmDialog?.Confirm(new EventCallback<bool>(this, MuteCallback), Loc["Mute {0}?", User.DisplayName ?? User.Username], buttonText: Loc["Mute"]); 269 + 270 + private async Task MuteCallback(bool confirm) 271 + { 272 + if (!confirm) return; 273 + 274 + try 275 + { 276 + await Api.Users.MuteUserAsync(User.Id, null); 277 + 278 + await GlobalComponentSvc.NoticeDialog?.Display(Loc["Muted {0}", User.DisplayName ?? User.Username])!; 279 + 280 + UserProfile.Relations += (int)Relations.Muting; 281 + StateHasChanged(); 282 + } 283 + catch (ApiException e) 284 + { 285 + await GlobalComponentSvc.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!; 286 + } 287 + } 288 + 289 + private void TemporaryMute() => 290 + GlobalComponentSvc.SelectDialog?.Select(new EventCallback<object?>(this, TemporaryMuteCallback), Loc["Mute {0}?", User.DisplayName ?? User.Username], [(Loc["For 1 hour"], (object)DateTime.Now.AddHours(1)), (Loc["For {0} hours", 3], (object)DateTime.Now.AddHours(3)), (Loc["For {0} hours", 8], (object)DateTime.Now.AddHours(8)), (Loc["For 1 day"], (object)DateTime.Now.AddDays(1)), (Loc["For 1 week"], (object)DateTime.Now.AddDays(7))], Loc["Mute"]); 291 + 292 + private async Task TemporaryMuteCallback(object? value) 293 + { 294 + if (value is not DateTime expires) return; 295 + 296 + try 297 + { 298 + await Api.Users.MuteUserAsync(User.Id, expires); 299 + 300 + await GlobalComponentSvc.NoticeDialog?.Display(Loc["Muted {0}", User.DisplayName ?? User.Username])!; 301 + 302 + UserProfile.Relations += (int)Relations.Muting; 303 + StateHasChanged(); 304 + } 305 + catch (ApiException e) 306 + { 307 + await GlobalComponentSvc.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!; 308 + } 309 + } 310 + 311 + private void Unmute() => 312 + GlobalComponentSvc.ConfirmDialog?.Confirm(new EventCallback<bool>(this, UnmuteCallback), Loc["Unmute {0}?", User.DisplayName ?? User.Username], buttonText: Loc["Unmute"]); 313 + 314 + private async Task UnmuteCallback(bool confirm) 315 + { 316 + if (!confirm) return; 317 + 318 + try 319 + { 320 + await Api.Users.UnmuteUserAsync(User.Id); 321 + 322 + await GlobalComponentSvc.NoticeDialog?.Display(Loc["Unmuted {0}", User.DisplayName ?? User.Username])!; 323 + 324 + UserProfile.Relations -= (int)Relations.Muting; 325 + StateHasChanged(); 326 + } 327 + catch (ApiException e) 328 + { 329 + await GlobalComponentSvc.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!; 330 + } 331 + } 332 + 333 + private void Block() => 334 + GlobalComponentSvc.ConfirmDialog?.Confirm(new EventCallback<bool>(this, BlockCallback), Loc["Block {0}?", User.DisplayName ?? User.Username], buttonText: Loc["Block"]); 335 + 336 + private void Report() => 337 + GlobalComponentSvc.ReportDialog?.ReportUser(User); 338 + 339 + private async Task BlockCallback(bool confirm) 340 + { 341 + if (!confirm) return; 342 + 343 + try 344 + { 345 + await Api.Users.BlockUserAsync(User.Id); 346 + 347 + await GlobalComponentSvc.NoticeDialog?.Display(Loc["Blocked {0}", User.DisplayName ?? User.Username])!; 348 + 349 + UserProfile.Relations += (int)Relations.Blocking; 350 + StateHasChanged(); 351 + } 352 + catch (ApiException e) 353 + { 354 + await GlobalComponentSvc.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!; 355 + } 356 + } 357 + 358 + private void Unblock() => 359 + GlobalComponentSvc.ConfirmDialog?.Confirm(new EventCallback<bool>(this, UnblockCallback), Loc["Unblock {0}?", User.DisplayName ?? User.Username], buttonText: Loc["Unblock"]); 360 + 361 + private async Task UnblockCallback(bool confirm) 362 + { 363 + if (!confirm) return; 364 + 365 + try 366 + { 367 + await Api.Users.UnblockUserAsync(User.Id); 368 + 369 + await GlobalComponentSvc.NoticeDialog?.Display(Loc["Unblocked {0}", User.DisplayName ?? User.Username])!; 370 + 371 + UserProfile.Relations -= (int)Relations.Blocking; 372 + StateHasChanged(); 373 + } 374 + catch (ApiException e) 375 + { 376 + await GlobalComponentSvc.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!; 377 + } 378 + } 379 + 380 + private void OpenAbout() 381 + { 382 + Nav.NavigateTo($"{Nav.Uri}/about"); 383 + } 384 + 385 + private void OpenOriginal() 386 + { 387 + if (UserProfile.Url != null) 388 + Js.InvokeVoidAsync("open", UserProfile.Url, "_blank"); 389 + } 390 + 391 + private async Task CopyLink() 392 + { 393 + await Js.InvokeVoidAsync("navigator.clipboard.writeText", $"https://{_instance?.WebDomain}/@{User.Username}" + (User.Host != null ? $"@{User.Host}" : "")); 394 + } 395 + 396 + private void CopyLinkRemote() 397 + { 398 + if (UserProfile.Url != null) 399 + Js.InvokeVoidAsync("navigator.clipboard.writeText", UserProfile.Url); 400 + } 401 + 402 + private void CopyUsername() => Js.InvokeVoidAsync("navigator.clipboard.writeText", $"@{User.Username}" + (User.Host != null ? $"@{User.Host}" : "")); 403 + 404 + private void CopyAtomFeed() => Js.InvokeVoidAsync("navigator.clipboard.writeText", $"https://{_instance?.WebDomain}/users/{User.Id}/feed.atom"); 405 + 406 + private void CopyJsonFeed() => Js.InvokeVoidAsync("navigator.clipboard.writeText", $"https://{_instance?.WebDomain}/users/{User.Id}/feed.json"); 407 + 408 + private void CopyRssFeed() => Js.InvokeVoidAsync("navigator.clipboard.writeText", $"https://{_instance?.WebDomain}/users/{User.Id}/feed.rss"); 409 + 410 + private async Task RefetchUser() => 411 + await GlobalComponentSvc.ConfirmDialog?.Confirm(new EventCallback<bool>(this, RefetchUserCallback), Loc["Refetch {0}'s profile?", User.DisplayName ?? User.Username])!; 412 + 413 + private async Task RefetchUserCallback(bool refetch) 414 + { 415 + if (!refetch) return; 416 + 417 + try 418 + { 419 + var res = await Api.Users.RefetchUserAsync(User.Id); 420 + if (res != null) 421 + { 422 + User = res; 423 + var newProfile = await Api.Users.GetUserProfileAsync(User.Id); 424 + if (newProfile != null) 425 + UserProfile = newProfile; 426 + StateHasChanged(); 427 + await GlobalComponentSvc.NoticeDialog?.Display(Loc["Successfully refetched {0}'s profile", User.DisplayName ?? User.Username])!; 428 + } 429 + } 430 + catch (ApiException e) 431 + { 432 + await GlobalComponentSvc.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!; 433 + } 434 + } 435 + 436 + protected override async Task OnParametersSetAsync() 437 + { 438 + _instance = await MetadataService.Instance.Value; 439 + } 127 440 }
+28 -3
Iceshrimp.Frontend/Components/ProfileInfo.razor.css
··· 51 51 } 52 52 } 53 53 54 - .badges { 54 + .top-row { 55 + display: flex; 56 + gap: 1rem; 57 + align-items: center; 58 + justify-content: space-between; 59 + margin: 1rem 0; 60 + } 61 + 62 + .badges, .profile-buttons { 55 63 display: flex; 56 64 flex-wrap: wrap; 57 65 gap: 0.5rem; 58 - margin: 1rem 0; 66 + align-items: start; 59 67 } 60 68 61 69 .badge { ··· 74 82 } 75 83 } 76 84 77 - @media (max-width: 640px) { 85 + .profile-buttons { 86 + align-items: center; 87 + } 88 + 89 + .context-button { 90 + margin: 0; 91 + } 92 + 93 + @media (max-width: 1000px) { 94 + .top-row { 95 + flex-direction: column; 96 + align-items: start; 97 + } 98 + 99 + .profile-buttons { 100 + align-self: end; 101 + } 102 + 78 103 ::deep .field { 79 104 flex-direction: column; 80 105
-299
Iceshrimp.Frontend/Pages/ProfileView.razor
··· 12 12 @using Microsoft.Extensions.Localization 13 13 @attribute [Authorize] 14 14 @inject ApiService Api 15 - @inject GlobalComponentSvc GlobalComponentSvc; 16 15 @inject IJSRuntime Js; 17 16 @inject IStringLocalizer<Localization> Loc; 18 - @inject MetadataService MetadataService 19 - @inject NavigationManager Nav; 20 17 21 18 <SectionContent SectionName="top-bar"> 22 19 <Icon Name="Icons.User"/> ··· 72 69 </div> 73 70 </div> 74 71 </div> 75 - <FollowButton User="UserResponse" UserProfile="Profile"/> 76 - <button @ref="MenuButton" class="context-button" @onclick="ToggleMenu" @onclick:stopPropagation="true" aria-label="more"> 77 - <Icon Name="Icons.DotsThreeOutline" Size="1.3em"/> 78 - <Menu @ref="ContextMenu"> 79 - @if (Profile != null && Profile.Relations.HasFlag(Relations.Self)) 80 - { 81 - <MenuElement Icon="Icons.Pencil" OnSelect="EditProfile"> 82 - <Text>@Loc["Edit profile"]</Text> 83 - </MenuElement> 84 - } 85 - else 86 - { 87 - <MenuElement Icon="Icons.Tooth" OnSelect="Bite"> 88 - <Text>@Loc["Bite"]</Text> 89 - </MenuElement> 90 - } 91 - @if (UserResponse.Host != null) 92 - { 93 - <MenuElement Icon="Icons.ArrowsClockwise" OnSelect="RefetchUser"> 94 - <Text>@Loc["Refetch"]</Text> 95 - </MenuElement> 96 - } 97 - <hr class="rule"/> 98 - <MenuElement Icon="Icons.Info" OnSelect="OpenAbout"> 99 - <Text>@Loc["About"]</Text> 100 - </MenuElement> 101 - @if (UserResponse.Host != null && Profile?.Url != null) 102 - { 103 - <MenuElement Icon="Icons.ArrowSquareOut" OnSelect="OpenOriginal"> 104 - <Text>@Loc["Open original page"]</Text> 105 - </MenuElement> 106 - } 107 - <MenuElement Icon="Icons.Share" OnSelect="CopyLink"> 108 - <Text>@Loc["Copy link"]</Text> 109 - </MenuElement> 110 - @if (UserResponse.Host != null && Profile?.Url != null) 111 - { 112 - <MenuElement Icon="Icons.ShareNetwork" OnSelect="CopyLinkRemote"> 113 - <Text>@Loc["Copy link (remote)"]</Text> 114 - </MenuElement> 115 - } 116 - <MenuElement Icon="Icons.At" OnSelect="CopyUsername"> 117 - <Text>@Loc["Copy username"]</Text> 118 - </MenuElement> 119 - @if (UserResponse.Host == null) 120 - { 121 - <MenuElement Icon="Icons.Atom" OnSelect="CopyAtomFeed"> 122 - <Text>@Loc["Copy Atom feed"]</Text> 123 - </MenuElement> 124 - <MenuElement Icon="Icons.BracketsCurly" OnSelect="CopyJsonFeed"> 125 - <Text>@Loc["Copy JSON feed"]</Text> 126 - </MenuElement> 127 - <MenuElement Icon="Icons.Rss" OnSelect="CopyRssFeed"> 128 - <Text>@Loc["Copy RSS feed"]</Text> 129 - </MenuElement> 130 - } 131 - @if (Profile != null && !Profile.Relations.HasFlag(Relations.Self)) 132 - { 133 - <hr class="rule"/> 134 - @if (Profile != null && Profile.Relations.HasFlag(Relations.Muting)) 135 - { 136 - <MenuElement Icon="Icons.Eye" OnSelect="Unmute"> 137 - <Text>@Loc["Unmute"]</Text> 138 - </MenuElement> 139 - } 140 - else 141 - { 142 - <MenuElement Icon="Icons.EyeSlash" OnSelect="Mute"> 143 - <Text>@Loc["Mute"]</Text> 144 - </MenuElement> 145 - <MenuElement Icon="Icons.Timer" OnSelect="TemporaryMute"> 146 - <Text>@Loc["Temporary mute"]</Text> 147 - </MenuElement> 148 - } 149 - @if (Profile != null && Profile.Relations.HasFlag(Relations.FollowedBy)) 150 - { 151 - <MenuElement Icon="Icons.LinkBreak" OnSelect="RemoveFromFollowers" Danger> 152 - <Text>@Loc["Remove follower"]</Text> 153 - </MenuElement> 154 - } 155 - @if (Profile != null && Profile.Relations.HasFlag(Relations.Blocking)) 156 - { 157 - <MenuElement Icon="Icons.Prohibit" OnSelect="Unblock" Danger> 158 - <Text>@Loc["Unblock"]</Text> 159 - </MenuElement> 160 - } 161 - else 162 - { 163 - <MenuElement Icon="Icons.Prohibit" OnSelect="Block" Danger> 164 - <Text>@Loc["Block"]</Text> 165 - </MenuElement> 166 - } 167 - <MenuElement Icon="Icons.WarningCircle" OnSelect="Report" Danger> 168 - <Text>@Loc["Report"]</Text> 169 - </MenuElement> 170 - } 171 - <ClosingBackdrop OnClose="ContextMenu.Close"></ClosingBackdrop> 172 - </Menu> 173 - </button> 174 72 </div> 175 73 <ProfileInfo Emojis="@UserResponse.Emojis" User="UserResponse" UserProfile="Profile"/> 176 74 </div> ··· 242 140 private string? MinId { get; set; } 243 141 private List<NoteResponse> UserPinned { get; set; } = []; 244 142 private List<NoteResponse> UserNotes { get; set; } = []; 245 - 246 - private ElementReference MenuButton { get; set; } 247 - private Menu ContextMenu { get; set; } = null!; 248 143 249 144 private bool _loading = true; 250 145 private bool _init; 251 146 private bool _notFound; 252 147 private bool _error; 253 148 private bool _fetchLock; 254 - 255 - private InstanceResponse? _instance; 256 149 257 150 private async Task GetNotes(string? minId) 258 151 { ··· 349 242 Profile = null; 350 243 StateHasChanged(); 351 244 await LoadProfile(); 352 - _instance = await MetadataService.Instance.Value; 353 - } 354 - 355 - private void ToggleMenu() => ContextMenu.Toggle(MenuButton); 356 - 357 - private void EditProfile() => Nav.NavigateTo("/settings/profile"); 358 - 359 - private void Bite() => Api.Users.BiteUserAsync(UserResponse.Id); 360 - 361 - private async Task RemoveFromFollowersAction(bool confirm) 362 - { 363 - if (!confirm) return; 364 - 365 - var removed = await Api.Users.RemoveUserFromFollowersAsync(UserResponse.Id); 366 - if (!removed) return; 367 - 368 - await GlobalComponentSvc.NoticeDialog?.Display(Loc["Removed {0} from followers", UserResponse.DisplayName ?? UserResponse.Username])!; 369 - 370 - Profile!.Relations &= ~Relations.FollowedBy; 371 - StateHasChanged(); 372 - } 373 - 374 - private void RemoveFromFollowers() => 375 - GlobalComponentSvc.ConfirmDialog?.Confirm(new EventCallback<bool>(this, RemoveFromFollowersAction), Loc["Remove {0} from followers?", UserResponse.DisplayName ?? UserResponse.Username], buttonText: Loc["Remove follower"]); 376 - 377 - private void Mute() => 378 - GlobalComponentSvc.ConfirmDialog?.Confirm(new EventCallback<bool>(this, MuteCallback), Loc["Mute {0}?", UserResponse.DisplayName ?? UserResponse.Username], buttonText: Loc["Mute"]); 379 - 380 - private async Task MuteCallback(bool confirm) 381 - { 382 - if (!confirm) return; 383 - 384 - try 385 - { 386 - await Api.Users.MuteUserAsync(UserResponse.Id, null); 387 - 388 - await GlobalComponentSvc.NoticeDialog?.Display(Loc["Muted {0}", UserResponse.DisplayName ?? UserResponse.Username])!; 389 - 390 - Profile!.Relations += (int)Relations.Muting; 391 - StateHasChanged(); 392 - } 393 - catch (ApiException e) 394 - { 395 - await GlobalComponentSvc.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!; 396 - } 397 - } 398 - 399 - private void TemporaryMute() => 400 - GlobalComponentSvc.SelectDialog?.Select(new EventCallback<object?>(this, TemporaryMuteCallback), Loc["Mute {0}?", UserResponse.DisplayName ?? UserResponse.Username], [(Loc["For 1 hour"], (object)DateTime.Now.AddHours(1)), (Loc["For {0} hours", 3], (object)DateTime.Now.AddHours(3)), (Loc["For {0} hours", 8], (object)DateTime.Now.AddHours(8)), (Loc["For 1 day"], (object)DateTime.Now.AddDays(1)), (Loc["For 1 week"], (object)DateTime.Now.AddDays(7))], Loc["Mute"]); 401 - 402 - private async Task TemporaryMuteCallback(object? value) 403 - { 404 - if (value is not DateTime expires) return; 405 - 406 - try 407 - { 408 - await Api.Users.MuteUserAsync(UserResponse.Id, expires); 409 - 410 - await GlobalComponentSvc.NoticeDialog?.Display(Loc["Muted {0}", UserResponse.DisplayName ?? UserResponse.Username])!; 411 - 412 - Profile!.Relations += (int)Relations.Muting; 413 - StateHasChanged(); 414 - } 415 - catch (ApiException e) 416 - { 417 - await GlobalComponentSvc.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!; 418 - } 419 - } 420 - 421 - private void Unmute() => 422 - GlobalComponentSvc.ConfirmDialog?.Confirm(new EventCallback<bool>(this, UnmuteCallback), Loc["Unmute {0}?", UserResponse.DisplayName ?? UserResponse.Username], buttonText: Loc["Unmute"]); 423 - 424 - private async Task UnmuteCallback(bool confirm) 425 - { 426 - if (!confirm) return; 427 - 428 - try 429 - { 430 - await Api.Users.UnmuteUserAsync(UserResponse.Id); 431 - 432 - await GlobalComponentSvc.NoticeDialog?.Display(Loc["Unmuted {0}", UserResponse.DisplayName ?? UserResponse.Username])!; 433 - 434 - Profile!.Relations -= (int)Relations.Muting; 435 - StateHasChanged(); 436 - } 437 - catch (ApiException e) 438 - { 439 - await GlobalComponentSvc.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!; 440 - } 441 - } 442 - 443 - private void Block() => 444 - GlobalComponentSvc.ConfirmDialog?.Confirm(new EventCallback<bool>(this, BlockCallback), Loc["Block {0}?", UserResponse.DisplayName ?? UserResponse.Username], buttonText: Loc["Block"]); 445 - 446 - private void Report() => 447 - GlobalComponentSvc.ReportDialog?.ReportUser(UserResponse); 448 - 449 - private async Task BlockCallback(bool confirm) 450 - { 451 - if (!confirm) return; 452 - 453 - try 454 - { 455 - await Api.Users.BlockUserAsync(UserResponse.Id); 456 - 457 - await GlobalComponentSvc.NoticeDialog?.Display(Loc["Blocked {0}", UserResponse.DisplayName ?? UserResponse.Username])!; 458 - 459 - Profile!.Relations += (int)Relations.Blocking; 460 - StateHasChanged(); 461 - } 462 - catch (ApiException e) 463 - { 464 - await GlobalComponentSvc.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!; 465 - } 466 - } 467 - 468 - private void Unblock() => 469 - GlobalComponentSvc.ConfirmDialog?.Confirm(new EventCallback<bool>(this, UnblockCallback), Loc["Unblock {0}?", UserResponse.DisplayName ?? UserResponse.Username], buttonText: Loc["Unblock"]); 470 - 471 - private async Task UnblockCallback(bool confirm) 472 - { 473 - if (!confirm) return; 474 - 475 - try 476 - { 477 - await Api.Users.UnblockUserAsync(UserResponse.Id); 478 - 479 - await GlobalComponentSvc.NoticeDialog?.Display(Loc["Unblocked {0}", UserResponse.DisplayName ?? UserResponse.Username])!; 480 - 481 - Profile!.Relations -= (int)Relations.Blocking; 482 - StateHasChanged(); 483 - } 484 - catch (ApiException e) 485 - { 486 - await GlobalComponentSvc.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!; 487 - } 488 - } 489 - 490 - private void OpenAbout() 491 - { 492 - Nav.NavigateTo($"{Nav.Uri}/about"); 493 - } 494 - 495 - private void OpenOriginal() 496 - { 497 - if (Profile?.Url != null) 498 - Js.InvokeVoidAsync("open", Profile.Url, "_blank"); 499 - } 500 - 501 - private async Task CopyLink() 502 - { 503 - await Js.InvokeVoidAsync("navigator.clipboard.writeText", $"https://{_instance?.WebDomain}/@{UserResponse.Username}" + (UserResponse.Host != null ? $"@{UserResponse.Host}" : "")); 504 - } 505 - 506 - private void CopyLinkRemote() 507 - { 508 - if (Profile?.Url != null) 509 - Js.InvokeVoidAsync("navigator.clipboard.writeText", Profile.Url); 510 - } 511 - 512 - private void CopyUsername() => Js.InvokeVoidAsync("navigator.clipboard.writeText", $"@{UserResponse.Username}" + (UserResponse.Host != null ? $"@{UserResponse.Host}" : "")); 513 - 514 - private void CopyAtomFeed() => Js.InvokeVoidAsync("navigator.clipboard.writeText", $"https://{_instance?.WebDomain}/users/{UserResponse.Id}/feed.atom"); 515 - 516 - private void CopyJsonFeed() => Js.InvokeVoidAsync("navigator.clipboard.writeText", $"https://{_instance?.WebDomain}/users/{UserResponse.Id}/feed.json"); 517 - 518 - private void CopyRssFeed() => Js.InvokeVoidAsync("navigator.clipboard.writeText", $"https://{_instance?.WebDomain}/users/{UserResponse.Id}/feed.rss"); 519 - 520 - private async Task RefetchUser() => 521 - await GlobalComponentSvc.ConfirmDialog?.Confirm(new EventCallback<bool>(this, RefetchUserCallback), Loc["Refetch {0}'s profile?", UserResponse.DisplayName ?? UserResponse.Username])!; 522 - 523 - private async Task RefetchUserCallback(bool refetch) 524 - { 525 - if (!refetch) return; 526 - 527 - try 528 - { 529 - var res = await Api.Users.RefetchUserAsync(UserResponse.Id); 530 - if (res != null) 531 - { 532 - UserResponse = res; 533 - var newProfile = await Api.Users.GetUserProfileAsync(UserResponse.Id); 534 - if (newProfile != null) 535 - Profile = newProfile; 536 - StateHasChanged(); 537 - await GlobalComponentSvc.NoticeDialog?.Display(Loc["Successfully refetched {0}'s profile", UserResponse.DisplayName ?? UserResponse.Username])!; 538 - } 539 - } 540 - catch (ApiException e) 541 - { 542 - await GlobalComponentSvc.NoticeDialog?.Display(e.Response.Message ?? Loc["An unknown error occurred"], NoticeDialog.NoticeType.Error)!; 543 - } 544 245 } 545 246 546 247 protected override async Task OnAfterRenderAsync(bool firstRender)
-18
Iceshrimp.Frontend/Pages/ProfileView.razor.css
··· 38 38 margin-left: 0.5rem; 39 39 } 40 40 41 - .context-button { 42 - margin-left: auto; 43 - padding: 0.75em; 44 - line-height: 1; 45 - } 46 - 47 - ::deep { 48 - .context-button { 49 - .ph { 50 - vertical-align: middle; 51 - } 52 - } 53 - } 54 - 55 - .follow-button + .context-button { 56 - margin-left: 0.5rem; 57 - } 58 - 59 41 .profile-card { 60 42 margin: auto; 61 43 margin-top: 1rem;