@using Iceshrimp.Assets.PhosphorIcons @using Iceshrimp.Frontend.Components.Note @using Iceshrimp.Frontend.Core.Miscellaneous @using Iceshrimp.Frontend.Core.Services @using Iceshrimp.Frontend.Localization @using Iceshrimp.Parsing @using Iceshrimp.Shared.Schemas.Web @using Microsoft.Extensions.Localization @inject IJSRuntime Js @inject ApiService ApiService @inject ComposeService ComposeService @inject SessionService SessionService @inject IStringLocalizer Loc; @inject GlobalComponentSvc GlobalComponentSvc @inject MessageService MessageService
@Loc["ComposePost"] @Loc["Retry"]
@if (ReplyOrQuote != null) {
} @if (NoteDraft.Cw != null) {
}
@code { private ElementReference Dialog { get; set; } private IJSObjectReference _module = null!; private IList Attachments { get; set; } = []; private InputFile UploadInput { get; set; } = null!; private NoteBase? ReplyOrQuote { get; set; } private string? TextPlaceholder { get; set; } private ElementReference Textarea { get; set; } private ElementReference EmojiButton { get; set; } private StateButton SendButton { get; set; } = null!; private NoteCreateRequest NoteDraft { get; set; } = new() { Text = "", Visibility = NoteVisibility.Followers, // FIXME: Default to visibilty in settings Cw = null }; private Dictionary AvailablePlaceholders { get; set; } = new() { { "default", "What's on your mind?" }, { "reply", "Reply goes here!" }, { "quote", "Quote this post!" } }; RenderFragment DropdownIcon(NoteVisibility vis) { return vis switch { NoteVisibility.Public => (@), NoteVisibility.Home => (@), NoteVisibility.Followers => (@), NoteVisibility.Specified => (@), _ => throw new ArgumentOutOfRangeException() }; } RenderFragment DropdownContent(NoteVisibility vis) { return vis switch { NoteVisibility.Public => (@Public), NoteVisibility.Home => (@Unlisted), NoteVisibility.Followers => (@Followers), NoteVisibility.Specified => (@Direct), _ => throw new ArgumentOutOfRangeException() }; } private IList> DropDownCreate() { return Enum.GetValues() .Select(vis => new DropdownElement { #pragma warning disable BL0005 // Setting this outside the component is fine until this is reworked Icon = DropdownIcon(vis), Content = DropdownContent(vis), Selection = vis #pragma warning restore BL0005 }) .ToList(); } // The Component is hidden, and triggered by a sepperate button. // That way we get it's functionality, without the styling limitations of the InputFile component private async Task OpenUpload() { await _module.InvokeVoidAsync("openUpload", UploadInput.Element); } public async Task OpenDialogRedraft(NoteResponse note) { ResetState(); NoteDraft.Text = note.Text ?? ""; NoteDraft.Cw = note.Cw; NoteDraft.Visibility = note.Visibility; NoteDraft.MediaIds = note.Attachments.Select(p => p.Id).ToList(); NoteDraft.RenoteId = note.RenoteId; NoteDraft.ReplyId = note.ReplyId; StateHasChanged(); await _module.InvokeVoidAsync("openDialog", Dialog); } public async Task OpenDialog(NoteBase? replyTo = null, NoteBase? quote = null) { if (replyTo != null) { var mentions = EnumerateMentions(replyTo); ResetState(); ReplyOrQuote = replyTo; NoteDraft.ReplyId = replyTo.Id; NoteDraft.Visibility = replyTo.Visibility; NoteDraft.Cw = replyTo.Cw; TextPlaceholder = AvailablePlaceholders["reply"]; foreach (var el in mentions) { NoteDraft.Text += $"@{el} "; } StateHasChanged(); } else if (quote != null) { ResetState(); ReplyOrQuote = quote; NoteDraft.RenoteId = quote.Id; NoteDraft.Visibility = quote.Visibility; TextPlaceholder = AvailablePlaceholders["quote"]; StateHasChanged(); } else { ResetState(); StateHasChanged(); } await _module.InvokeVoidAsync("openDialog", Dialog); } private List EnumerateMentions(NoteBase noteBase) { List mentions = []; if (noteBase.User.Id != SessionService.Current!.Id) { var userMention = noteBase.User.Username; if (noteBase.User.Host != null) { userMention += $"@{noteBase.User.Host}"; } mentions.Add(userMention); } var current = $"@{SessionService.Current.Username}@{SessionService.Current.Host}"; var mfmNodes = Mfm.parse(noteBase.Text); foreach (var node in mfmNodes) { if (node is MfmNodeTypes.MfmMentionNode mentionNode) { mentions.Add(mentionNode.Acct); } } mentions = mentions.Distinct().ToList(); mentions.Remove(current); return mentions; } private void ResetState() { ReplyOrQuote = null; Attachments = new List(); NoteDraft = new NoteCreateRequest { Text = "", Visibility = NoteVisibility.Followers, Cw = null }; TextPlaceholder = AvailablePlaceholders["default"]; } private async Task CloseDialog() { await _module.InvokeVoidAsync("closeDialog", Dialog); } private async Task SendNote() { SendButton.State = StateButton.StateEnum.Loading; if (Attachments.Count > 0) { NoteDraft.MediaIds = Attachments.Select(x => x.Id).ToList(); } try { await ApiService.Notes.CreateNote(NoteDraft); } catch (ApiException) { SendButton.State = StateButton.StateEnum.Failed; return; } if (ReplyOrQuote != null) { var res = await ApiService.Notes.GetNote(ReplyOrQuote.Id); if (res != null) _ = MessageService.UpdateNote(res); } SendButton.State = StateButton.StateEnum.Success; await CloseDialog(); SendButton.State = StateButton.StateEnum.Success; // FIXME: Implement timeline refresh and call it here. } private void ToggleCw() { NoteDraft.Cw = NoteDraft.Cw == null ? "" : null; } private async Task Upload(InputFileChangeEventArgs e) { var res = await ApiService.Drive.UploadFile(e.File); Attachments.Add(res); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _module = await Js.InvokeAsync("import", "./Components/Compose.razor.js"); ComposeService.ComposeDialog = this; } } private void ToggleEmojiPicker() { GlobalComponentSvc.EmojiPicker?.Open(EmojiButton, new EventCallback(this, AddEmoji)); } private async Task AddEmoji(EmojiResponse emoji) { var pos = await _module.InvokeAsync("getSelectionStart"); var text = NoteDraft.Text; var emojiString = $":{emoji.Name}"; NoteDraft.Text = text.Insert(pos, emojiString); StateHasChanged(); } protected override void OnInitialized() { TextPlaceholder = AvailablePlaceholders["default"]; } }