Files
decePubClient/Pages/Search.razor
T

132 lines
4.7 KiB
Plaintext
Raw Permalink Normal View History

@page "/search"
@attribute [Authorize]
@inherits LocalizableComponentBase
<Title>@Localizer["Search"]</Title>
@* Accounts, posts and hashtags (/api/v2/search). A handle (name@host) or a post's address is resolved by PrivaPub,
which fetches it from its server if it has not met it yet; the browser never contacts another server. *@
<section class="block relative w-full h-full neo-inset neo-xs rounded-xl">
<div class="flex flex-col gap-4 p-4 md:p-5 w-full h-full absolute overflow-y-auto">
<EditForm Model="form" OnValidSubmit="Run" class="flex flex-wrap gap-2 items-center">
<DInput TValue="string" class="flex-1 min-w-48" @bind-Value="form.Query" autocomplete="off" type="search"
placeholder="@Localizer["Search accounts, posts and tags, or paste an address"]">
<Prefix><i class="ion-md-search"></i></Prefix>
</DInput>
<DButton Type="submit" Size="DSize.Sm" Color="DColor.Primary" Disabled="@(IsDefaultDisabled || isSearching)">
@if (isSearching)
{
<DLoading Size="DSize.Xs" LabelText="@Localizer["loading"]" />
}
<span>@Localizer["Search"]</span>
</DButton>
</EditForm>
@if (searched)
{
@if (results.Accounts.Count == 0 && results.Statuses.Count == 0 && results.Hashtags.Count == 0)
{
<p class="w-full text-center"><i class="ion-ios-remove-circle-outline"></i> @Localizer["Nothing found."]</p>
}
@if (results.Accounts.Count > 0)
{
<h2 class="font-bold">@Localizer["Accounts"]</h2>
<div class="flex flex-col gap-2">
@foreach (var account in results.Accounts)
{
<a class="flex items-center gap-3 p-3 rounded-xl neo-raised neo-2xs min-w-0" href="@($"account/{Uri.EscapeDataString(account.Id)}")">
@* alt="": the name is next to it. *@
<img alt="" class="h-10 w-10 flex-none object-cover rounded-full" src="@(account.Avatar ?? "imgs/icon-192.png")" />
<span class="flex flex-col min-w-0">
<b class="truncate">@account.Name</b>
<span class="text-xs opacity-70 truncate">@("@" + account.Acct)</span>
</span>
</a>
}
</div>
}
@if (results.Hashtags.Count > 0)
{
<h2 class="font-bold">@Localizer["Hashtags"]</h2>
<div class="flex flex-wrap gap-2">
@foreach (var tag in results.Hashtags)
{
<DButton Size="DSize.Sm" OnClick="@(() => OpenTag(tag.Name))">#@tag.Name</DButton>
}
</div>
}
@if (results.Statuses.Count > 0)
{
<h2 class="font-bold">@Localizer["Posts and replies"]</h2>
<div class="flex flex-col gap-4">
@foreach (var post in statuses)
{
<Content @key="post.Key" Post="post" OnTag="OpenTag" />
}
</div>
}
}
</div>
</section>
@code {
[Inject] AccountService AccountService { get; set; }
[Inject] InstanceService InstanceService { get; set; }
[Inject] ILocalStorageService LocalStorage { get; set; }
[SupplyParameterFromQuery(Name = "q")] public string Q { get; set; }
sealed class SearchForm
{
public string Query { get; set; }
}
SearchForm form = new();
SearchResults results = new();
List<FeedPost> statuses = [];
bool searched;
bool isSearching;
protected override async Task OnInitializedAsync()
{
IsLoading = false;
if (string.IsNullOrWhiteSpace(Q))
return;
form.Query = Q;
await Run();
}
async Task Run()
{
if (string.IsNullOrWhiteSpace(form.Query))
return;
isSearching = true;
// a handle or an address is something to resolve; plain words only search what PrivaPub already holds
var resolve = form.Query.Contains('@') || form.Query.StartsWith("http", StringComparison.OrdinalIgnoreCase);
results = await AccountService.Search(form.Query, resolve);
statuses = [.. results.Statuses.Select(FeedPost.Of)];
searched = true;
isSearching = false;
await InstanceService.Resolve(statuses.Select(p => p.Host).Append(InstanceService.SelfHost));
InstanceService.Locate(statuses);
// a handle opens its account's profile, whatever else matched
var handle = form.Query.Trim().TrimStart('@');
if (form.Query.Contains('@') && results.Accounts.FirstOrDefault(a => string.Equals(a.Acct, handle, StringComparison.OrdinalIgnoreCase)
|| string.Equals($"{a.Acct}@{InstanceService.SelfHost}", handle, StringComparison.OrdinalIgnoreCase)) is { } exact)
Navigation.NavigateTo($"account/{Uri.EscapeDataString(exact.Id)}");
else if (results.Accounts.Count == 1 && results.Statuses.Count == 0 && form.Query.Contains('@'))
Navigation.NavigateTo($"account/{Uri.EscapeDataString(results.Accounts[0].Id)}");
}
async Task OpenTag(string tagName)
{
var filter = await LocalStorage.GetItemAsync<FeedFilter>(nameof(FeedFilter)) ?? new();
filter.Tag = tagName;
filter.TimelineType = TimelineType.Tag;
await LocalStorage.SetItemAsync(nameof(FeedFilter), filter);
Navigation.NavigateTo("");
}
}