@page "/lists" @attribute [Authorize] @inherits LocalizableComponentBase @Localizer["Lists"] @* The current persona's lists (/api/v1/lists): made, renamed, set to show which replies and whether their members stay out of the home feed, and deleted; their members added from the accounts the persona follows (PrivaPub accepts no others, and drops a member when the follow ends) and removed. A list is read as a tab of the feed page. Lists are the persona's own and never leave PrivaPub. *@

@Localizer["Lists"]

@Localizer["Make a list"] @if (IsLoading) {

@Localizer["loading"]

} else if (lists.Count == 0) {

@Localizer["This persona has no list yet."]

}
@foreach (var list in lists) {
@list.Title @if (list.Exclusive) { @Localizer["Exclusive"] }
@if (open?.Id == list.Id) {
@foreach (var policy in Policies) { }
@Localizer["Save"] @if (isConfirmingDelete) { @Localizer["Delete it for good"] @Localizer["Cancel"] } else { @Localizer["Delete"] }

@Localizer["Members"] @members.Count

@if (members.Count == 0) {

@Localizer["Nobody is in this list yet."]

} @foreach (var account in members) {
@* alt="": the name is next to it. *@ @account.Name @("@" + account.Acct)
}
@if (isFinding) { } @Localizer["Search"] @if (found is not null) {
@if (Candidates.Count == 0) {

@Localizer["Only accounts you follow can join a list, and none of them matches."]

} @foreach (var account in Candidates) {
@* alt="": the name is next to it. *@ @account.Name @("@" + account.Acct) @Localizer["Add"]
}
} }
}
@code { static readonly ListRepliesPolicy[] Policies = [ListRepliesPolicy.List, ListRepliesPolicy.Followed, ListRepliesPolicy.None]; [Inject] ListService ListService { get; set; } [Inject] ILocalStorageService LocalStorage { get; set; } List lists = []; MastodonList draft = new(); MastodonList edit = new(); MastodonList open; List members = []; List found; SearchForm find = new(); string loadedPersonaId; bool isSaving; bool isFinding; bool isConfirmingDelete; List Candidates => found.Where(a => members.All(m => m.Id != a.Id)).ToList(); string PolicyName(ListRepliesPolicy policy) => policy switch { ListRepliesPolicy.Followed => Localizer["Replies to anyone you follow"], ListRepliesPolicy.None => Localizer["No replies"], _ => Localizer["Replies to the list's members"] }; // The persona changed (NavMenu's switcher): its lists are another persona's. protected override async Task OnParametersSetAsync() { if (CascadingState.PersonaId is null) { IsLoading = false; return; } if (CascadingState.PersonaId == loadedPersonaId) return; loadedPersonaId = CascadingState.PersonaId; if (CascadingState.IsOffline) { WhenOnline(LoadData); StartOnlinePolling(); IsLoading = false; return; } await LoadData(); } async Task LoadData() { IsLoading = true; open = null; members = []; found = null; lists = await ListService.All(); IsLoading = false; } async Task Create() { isSaving = true; var result = await ListService.Save(new MastodonList { Title = draft.Title }); isSaving = false; if (result.IsInvalid) { Toast.AddResponseError(result); return; } var list = (MastodonList)result.Data; lists.Add(list); lists = [.. lists.OrderBy(l => l.Title, StringComparer.CurrentCultureIgnoreCase)]; draft = new(); Toast.AddResponseSuccess(Localizer["List {0} made.", list.Title]); await Toggle(list); } async Task Toggle(MastodonList list) { isConfirmingDelete = false; found = null; find = new(); if (open?.Id == list.Id) { open = null; members = []; return; } open = list; edit = new MastodonList { Id = list.Id, Title = list.Title, RepliesPolicy = list.RepliesPolicy, Exclusive = list.Exclusive }; members = await ListService.Members(list.Id); } async Task Save() { isSaving = true; var result = await ListService.Save(edit); isSaving = false; if (result.IsInvalid) { Toast.AddResponseError(result); return; } var saved = (MastodonList)result.Data; var index = lists.FindIndex(l => l.Id == saved.Id); if (index >= 0) lists[index] = saved; open = saved; Toast.AddResponseSuccess(Localizer["List {0} saved.", saved.Title]); } async Task Delete() { isSaving = true; var result = await ListService.Delete(open.Id); isSaving = false; isConfirmingDelete = false; if (result.IsInvalid) { Toast.AddResponseError(result); return; } lists.RemoveAll(l => l.Id == open.Id); Toast.AddResponseSuccess(Localizer["List {0} deleted.", open.Title]); open = null; members = []; } async Task Find() { isFinding = true; found = await ListService.FindFollowed(find.Query); isFinding = false; } async Task SetMember(Account account, bool member) { isSaving = true; var result = await ListService.SetMember(open.Id, account.Id, member); isSaving = false; if (result.IsInvalid) { Toast.AddResponseError(result); return; } if (member) members.Add(account); else members.RemoveAll(m => m.Id == account.Id); Toast.AddResponseSuccess(member ? Localizer["{0} added to {1}.", account.Name, open.Title] : Localizer["{0} removed from {1}.", account.Name, open.Title]); } async Task Read(MastodonList list) { var filter = await LocalStorage.GetItemAsync(nameof(FeedFilter)) ?? new(); filter.TimelineType = TimelineType.Lists; filter.ListId = list.Id; await LocalStorage.SetItemAsync(nameof(FeedFilter), filter); Navigation.NavigateTo(""); } sealed class SearchForm { public string Query { get; set; } } }