239 lines
8.7 KiB
Text
239 lines
8.7 KiB
Text
@using System.Diagnostics.CodeAnalysis
|
|
@using Iceshrimp.Assets.PhosphorIcons
|
|
@* ReSharper disable once RedundantUsingDirective *@
|
|
@using Iceshrimp.Frontend.Components.Note
|
|
@using Iceshrimp.Frontend.Core.Miscellaneous
|
|
@using Iceshrimp.Frontend.Core.Services
|
|
@using Iceshrimp.Frontend.Localization
|
|
@using Iceshrimp.Shared.Schemas.Web
|
|
@using Microsoft.Extensions.Localization
|
|
@using Iceshrimp.Frontend.Core.Services.StateServicePatterns
|
|
@inject IStringLocalizer<Localization> Loc;
|
|
@inject ApiService Api;
|
|
@inject NavigationManager Navigation;
|
|
@inject StateService StateService;
|
|
@inject IJSRuntime Js;
|
|
|
|
<form class="search" @onsubmit="Search" role="search">
|
|
<input @bind="SearchString" class="input" aria-label=""/>
|
|
<InputSelect @bind-Value="@SearchType">
|
|
@foreach (var type in Enum.GetValues(typeof(SearchTypeEnum)))
|
|
{
|
|
<option value="@type">@type</option>
|
|
}
|
|
</InputSelect>
|
|
<button class="button"><Icon Name="Icons.MagnifyingGlass"/>@Loc["Search"]</button>
|
|
</form>
|
|
|
|
@if (_resultType == ResultType.Notes)
|
|
{
|
|
<div class="note-results" @ref="Scroller">
|
|
@if (NoteSearchInstance?.NoteResponses != null)
|
|
{
|
|
foreach (var note in NoteSearchInstance?.NoteResponses!)
|
|
{
|
|
<div class="wrapper">
|
|
<div @onclick="() => OpenNote(note.Id)" class="note-container">
|
|
<Note NoteResponse="note" OpenNote="false"/>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="end">
|
|
<ScrollEnd ManualLoad="NoteSearchInstance.FetchOlder" IntersectionChange="async () => { if (!NoteSearchInstance.SearchComplete) { await NoteSearchInstance.FetchOlder(); } }"/>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
@if (_resultType == ResultType.Users)
|
|
{
|
|
<div class="user-results">
|
|
@if (UserSearchInstance?.UserResponses != null)
|
|
{
|
|
foreach (var user in UserSearchInstance?.UserResponses!)
|
|
{
|
|
<div @onclick="() => OpenProfile(user)" class="wrapper">
|
|
<UserProfileCard User="user"/>
|
|
</div>
|
|
}
|
|
|
|
<div class="end">
|
|
<ScrollEnd ManualLoad="UserSearchInstance.FetchOlder" IntersectionChange="async () => { if (!UserSearchInstance.SearchComplete) { await UserSearchInstance.FetchOlder(); } }"/>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
@code {
|
|
private string SearchString { get; set; } = "";
|
|
private SearchTypeEnum SearchType { get; set; } = SearchTypeEnum.Note;
|
|
private NoteSearch? NoteSearchInstance { get; set; }
|
|
private UserSearch? UserSearchInstance { get; set; }
|
|
private IJSInProcessObjectReference Module { get; set; } = null!;
|
|
private ElementReference Scroller { get; set; }
|
|
private ResultType _resultType;
|
|
private bool _setScroll;
|
|
private float _scrollTop;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
Module = (IJSInProcessObjectReference)await Js.InvokeAsync<IJSObjectReference>("import", "/Components/SearchComponent.razor.js");
|
|
var state = StateService.Search.GetState();
|
|
if (state != null)
|
|
{
|
|
NoteSearchInstance = new NoteSearch(state.SearchString, Api, state.SearchResults);
|
|
_resultType = ResultType.Notes;
|
|
_scrollTop = state.ScrollTop;
|
|
_setScroll = true;
|
|
}
|
|
}
|
|
|
|
protected override void OnAfterRender(bool firstRender)
|
|
{
|
|
if (_setScroll)
|
|
{
|
|
SetScrollY(_scrollTop);
|
|
_setScroll = false;
|
|
}
|
|
}
|
|
|
|
private async Task Search()
|
|
{
|
|
NoteSearchInstance = null;
|
|
_resultType = ResultType.None;
|
|
StateHasChanged();
|
|
try
|
|
{
|
|
var lookup = await Api.Search.LookupAsync(SearchString);
|
|
if (lookup is not null && lookup.TargetUrl.Contains("user"))
|
|
{
|
|
var user = await Api.Users.GetUserAsync(lookup.TargetUrl.Split("users/")[1]);
|
|
if (user is not null)
|
|
{
|
|
var username = $"@{user.Username}";
|
|
if (user.Host != null) username += $"@{user.Host}";
|
|
Navigation.NavigateTo($"/{username}");
|
|
}
|
|
}
|
|
else if (lookup is not null && lookup.TargetUrl.Contains("notes"))
|
|
{
|
|
Navigation.NavigateTo(lookup.TargetUrl);
|
|
}
|
|
}
|
|
catch (ApiException)
|
|
{
|
|
if (SearchType == SearchTypeEnum.Note)
|
|
{
|
|
var searchRes = await Api.Search.SearchNotesAsync(SearchString, new PaginationQuery { Limit = 20 });
|
|
if (searchRes.Count > 0)
|
|
{
|
|
NoteSearchInstance = new NoteSearch(searchString: SearchString, api: Api, noteResponses: searchRes);
|
|
_resultType = ResultType.Notes;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
if (SearchType == SearchTypeEnum.User)
|
|
{
|
|
var searchRes = await Api.Search.SearchUsersAsync(SearchString, new PaginationQuery() { Limit = 20 });
|
|
if (searchRes.Count > 0)
|
|
{
|
|
UserSearchInstance = new UserSearch(searchString: SearchString, api: Api, userResponses: searchRes);
|
|
_resultType = ResultType.Users;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OpenNote(string id)
|
|
{
|
|
StateService.Search.SetState(new SearchState { SearchResults = NoteSearchInstance!.NoteResponses, ScrollTop = GetScrollY(), SearchString = NoteSearchInstance!.SearchString });
|
|
Navigation.NavigateTo($"/notes/{id}");
|
|
}
|
|
|
|
private void OpenProfile(UserResponse user)
|
|
{
|
|
var username = $"@{user.Username}";
|
|
if (user.Host != null) username += $"@{user.Host}";
|
|
Navigation.NavigateTo($"/{username}");
|
|
}
|
|
|
|
private float GetScrollY()
|
|
{
|
|
return Module.Invoke<float>("GetScrollY");
|
|
}
|
|
|
|
private void SetScrollY(float scrollTop)
|
|
{
|
|
Module.InvokeVoid("SetScrollY", scrollTop);
|
|
}
|
|
|
|
private enum ResultType
|
|
{
|
|
Default,
|
|
Notes,
|
|
Users,
|
|
None
|
|
}
|
|
|
|
private enum SearchTypeEnum
|
|
{
|
|
Note,
|
|
User
|
|
}
|
|
|
|
[method: SetsRequiredMembers]
|
|
private class UserSearch(string searchString, ApiService api, List<UserResponse> userResponses)
|
|
{
|
|
internal required string MinId = userResponses.Last().Id;
|
|
internal required string MaxId = userResponses.First().Id;
|
|
internal required string SearchString = searchString;
|
|
internal required ApiService Api = api;
|
|
public required List<UserResponse> UserResponses { get; init; } = userResponses;
|
|
public bool SearchComplete = false;
|
|
|
|
public async Task FetchOlder()
|
|
{
|
|
var pq = new PaginationQuery { Limit = 15, MaxId = MinId };
|
|
var res = await Api.Search.SearchUsersAsync(SearchString, pq);
|
|
switch (res.Count)
|
|
{
|
|
case > 0:
|
|
MinId = res.Last().Id;
|
|
UserResponses.AddRange(res);
|
|
break;
|
|
case 0:
|
|
SearchComplete = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
[method: SetsRequiredMembers]
|
|
private class NoteSearch(string searchString, ApiService api, List<NoteResponse> noteResponses)
|
|
{
|
|
internal required string MinId = noteResponses.Last().Id;
|
|
internal required string MaxId = noteResponses.First().Id;
|
|
public required string SearchString = searchString;
|
|
internal required ApiService Api = api;
|
|
public required List<NoteResponse> NoteResponses { get; init; } = noteResponses;
|
|
public bool SearchComplete = false;
|
|
|
|
public async Task FetchOlder()
|
|
{
|
|
var pq = new PaginationQuery { Limit = 15, MaxId = MinId };
|
|
var res = await Api.Search.SearchNotesAsync(SearchString, pq);
|
|
switch (res.Count)
|
|
{
|
|
case > 0:
|
|
MinId = res.Last().Id;
|
|
NoteResponses.AddRange(res);
|
|
break;
|
|
case 0:
|
|
SearchComplete = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|