The composer completes mentions and hashtags
A mention or hashtag being written at the end of the words is offered, once typing pauses, the accounts or tags PrivaPub already knows (/api/v2/search, never resolving); a click writes it out. e2e: a town persona's handle, begun, is offered and completed. CLAUDE.md also says that FEP-5624's moderated comments (PeerTube) show as pending on a card, as GoToSocial's policies do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
658683f816
commit
511edf3591
4 files changed
+102
-4
No files matched your search
@@ -1,3 +1,4 @@
|
||||
@using System.Text.RegularExpressions
|
||||
@inherits LocalizableComponentBase
|
||||
|
||||
@* A new post or a reply, as the current persona. Through the Mastodon API it is plain text with a content warning, a
|
||||
@@ -17,11 +18,28 @@
|
||||
</DInput>
|
||||
}
|
||||
<div class="relative">
|
||||
<DTextarea TValue="string" @bind-Value="form.Content" Immediate="true"
|
||||
<DTextarea TValue="string" @bind-Value="form.Content" @bind-Value:after="Typed" Immediate="true"
|
||||
class="@(form.HasContentWarning ? "w-full rounded-b-[1.4rem] rounded-t-lg" : "w-full rounded-[1.4rem]")" maxlength="@ComposeForm.MaxContentLength" autocomplete="off"
|
||||
placeholder="@Placeholder" rows="3" data-compose />
|
||||
<span class="absolute text-xs opacity-50 right-3 bottom-2">@ContentLength/@ComposeForm.MaxContentLength</span>
|
||||
</div>
|
||||
@if (suggestedAccounts.Count > 0 || suggestedTags.Count > 0)
|
||||
{
|
||||
<div class="flex flex-wrap gap-2" data-suggestions>
|
||||
@foreach (var account in suggestedAccounts)
|
||||
{
|
||||
<DButton Size="DSize.Xs" class="max-w-full" OnClick="@(() => Complete("@" + account.Acct))" data-suggestion="@("@" + account.Acct)">
|
||||
@* alt="": the handle is next to it. *@
|
||||
<img alt="" class="h-4 w-4 flex-none rounded-full object-cover" src="@(account.Avatar ?? "imgs/icon-192.png")" />
|
||||
<span class="truncate">@("@" + account.Acct)</span>
|
||||
</DButton>
|
||||
}
|
||||
@foreach (var tag in suggestedTags)
|
||||
{
|
||||
<DButton Size="DSize.Xs" OnClick="@(() => Complete("#" + tag.Name))" data-suggestion="@("#" + tag.Name)">#@tag.Name</DButton>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Quoting is not null)
|
||||
{
|
||||
@@ -215,6 +233,7 @@
|
||||
|
||||
@code {
|
||||
[Inject] StatusService StatusService { get; set; }
|
||||
[Inject] AccountService AccountService { get; set; }
|
||||
[Inject] ILoggingService Logger { get; set; }
|
||||
[Inject] IJSRuntime JS { get; set; }
|
||||
|
||||
@@ -231,11 +250,51 @@
|
||||
|
||||
ComposeForm form = new();
|
||||
List<string> fileErrors = [];
|
||||
// what the words end with while a mention or a hashtag is being written: its sign and what follows it
|
||||
static readonly Regex Typing = new(@"(?:^|\s)([@#])([\p{L}\p{N}_][\p{L}\p{N}_.\-]*(?:@[\p{L}\p{N}.\-]*)?)$", RegexOptions.Compiled);
|
||||
List<Account> suggestedAccounts = [];
|
||||
List<StatusTag> suggestedTags = [];
|
||||
// the newest lookup: an older one's answer comes too late to show
|
||||
int suggesting;
|
||||
string error;
|
||||
bool isPublishing;
|
||||
|
||||
int ContentLength => form.Content?.Length ?? 0;
|
||||
|
||||
// what is being written, once typing pauses, among the accounts and hashtags this server knows
|
||||
async Task Typed()
|
||||
{
|
||||
var typed = Typing.Match(form.Content ?? "");
|
||||
var lookup = ++suggesting;
|
||||
if (!typed.Success || typed.Groups[2].Length < 2)
|
||||
{
|
||||
suggestedAccounts = [];
|
||||
suggestedTags = [];
|
||||
return;
|
||||
}
|
||||
await Task.Delay(300);
|
||||
if (lookup != suggesting)
|
||||
return;
|
||||
var hashtags = typed.Groups[1].Value == "#";
|
||||
var found = await AccountService.Suggest(typed.Groups[2].Value, hashtags);
|
||||
if (lookup != suggesting)
|
||||
return;
|
||||
suggestedAccounts = hashtags ? [] : [.. found.Accounts.Take(5)];
|
||||
suggestedTags = hashtags ? [.. found.Hashtags.Take(5)] : [];
|
||||
}
|
||||
|
||||
// the mention or hashtag being written, written out
|
||||
void Complete(string written)
|
||||
{
|
||||
var typed = Typing.Match(form.Content ?? "");
|
||||
suggesting++;
|
||||
suggestedAccounts = [];
|
||||
suggestedTags = [];
|
||||
if (!typed.Success)
|
||||
return;
|
||||
form.Content = form.Content[..typed.Groups[1].Index] + written + " ";
|
||||
}
|
||||
|
||||
string Placeholder => Editing is not null ? Localizer["Edit your post"]
|
||||
: Quoting is not null ? Localizer["Say something about it"]
|
||||
: InReplyTo is null ? Localizer["What's happening?"]
|
||||
|
||||
Reference in new issue
Block a user