Files
decePubClient/Pages/Scheduled.razor
T

147 lines
4.9 KiB
Plaintext
Raw Normal View History

@page "/scheduled"
@attribute [Authorize]
@inherits LocalizableComponentBase
<Title>@Localizer["Scheduled posts"]</Title>
@* The current persona's posts waiting for their time (/api/v1/scheduled_statuses): moved to another time, or dropped so
they are never published. The composer schedules them. *@
<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">
<div class="flex items-center gap-2">
<i class="ion-md-time text-lg"></i>
<h1 class="font-bold truncate">@Localizer["Scheduled posts"]</h1>
</div>
@if (IsLoading)
{
<p class="flex w-full items-center justify-center gap-2">
<DLoading Variant="DLoadingVariant.Dots" LabelText="@Localizer["loading"]" />
@Localizer["loading"]
</p>
}
else if (scheduled.Count == 0)
{
<p class="w-full text-center" data-feed-empty><i class="ion-ios-remove-circle-outline"></i> @Localizer["Nothing is waiting to be published."]</p>
}
<div class="flex flex-col gap-3">
@foreach (var item in scheduled)
{
<article class="flex flex-col gap-2 p-3 md:p-4 rounded-xl neo-raised neo-xs" data-scheduled-id="@item.Id">
<div class="flex flex-wrap items-center gap-2 text-sm">
<i class="ion-md-time"></i>
<b>@item.ScheduledAt.ToLocalTime().ToString(SUtility.dateTimeFormatExt)</b>
<span class="opacity-70">@VisibilityName(item.Params.Visibility)</span>
@if (item.MediaAttachments.Count > 0)
{
<span class="opacity-70"><i class="ion-md-images"></i> @item.MediaAttachments.Count</span>
}
</div>
@if (item.Params.SpoilerText is { Length: > 0 } warning)
{
<p class="text-sm"><i class="ion-md-warning text-warning"></i> @warning</p>
}
<p class="text-sm whitespace-pre-wrap break-words">@item.Params.Text</p>
<div class="flex flex-wrap items-center gap-2">
<input type="datetime-local" class="input input-sm w-auto" autocomplete="off" aria-label="@Localizer["Publish it on"]"
value="@(moves.GetValueOrDefault(item.Id) ?? item.ScheduledAt.ToLocalTime().ToString("yyyy-MM-ddTHH:mm"))"
@onchange="@((ChangeEventArgs e) => moves[item.Id] = e.Value?.ToString())" />
<DButton Size="DSize.Sm" Disabled="@(IsDefaultDisabled || isSaving || !moves.ContainsKey(item.Id))" OnClick="@(() => Move(item))">
<i class="ion-md-checkmark"></i><span>@Localizer["Move"]</span>
</DButton>
<DButton Size="DSize.Sm" Disabled="@(IsDefaultDisabled || isSaving)" OnClick="@(() => Drop(item))">
<i class="ion-md-trash text-error"></i><span>@Localizer["Never publish it"]</span>
</DButton>
</div>
</article>
}
</div>
</div>
</section>
@code {
[Inject] StatusService StatusService { get; set; }
2026-10-06 23:28:22 +02:00
[Inject] ConfirmService Confirm { get; set; }
List<ScheduledStatus> scheduled = [];
readonly Dictionary<string, string> moves = [];
string loadedPersonaId;
bool isSaving;
string VisibilityName(string visibility) => visibility switch
{
"unlisted" => Localizer["Unlisted"],
"private" => Localizer["Followers only"],
"direct" => Localizer["Direct"],
_ => Localizer["Public"]
};
// The persona changed (NavMenu's switcher): its scheduled posts 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;
moves.Clear();
scheduled = await StatusService.Scheduled();
IsLoading = false;
}
async Task Move(ScheduledStatus item)
{
if (!DateTime.TryParse(moves[item.Id], System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.AssumeLocal, out var local))
return;
isSaving = true;
var result = await StatusService.Reschedule(item.Id, local.ToUniversalTime());
isSaving = false;
if (result.IsInvalid)
{
Toast.AddResponseError(result);
return;
}
var moved = (ScheduledStatus)result.Data;
moves.Remove(item.Id);
item.ScheduledAt = moved.ScheduledAt;
scheduled = [.. scheduled.OrderBy(s => s.ScheduledAt)];
Toast.AddResponseSuccess(Localizer["Scheduled for {0}.", moved.ScheduledAt.ToLocalTime().ToString(SUtility.dateTimeFormatExt)]);
}
async Task Drop(ScheduledStatus item)
{
2026-10-06 23:28:22 +02:00
if (!await Confirm.Ask(Localizer["Never publish this post?"], null, Localizer["Never publish it"], true))
return;
isSaving = true;
var result = await StatusService.Unschedule(item.Id);
isSaving = false;
if (result.IsInvalid)
{
Toast.AddResponseError(result);
return;
}
scheduled.Remove(item);
Toast.AddResponseSuccess(Localizer["It will not be published."]);
}
}