@using Iceshrimp.Assets.PhosphorIcons @using Iceshrimp.Frontend.Components.Note @using Iceshrimp.Frontend.Core.Miscellaneous @using Iceshrimp.Frontend.Core.Services @using Iceshrimp.Frontend.Core.Services.NoteStore @using Iceshrimp.Frontend.Localization @using Iceshrimp.MfmSharp @using Iceshrimp.Shared.Schemas.Web @using Microsoft.Extensions.Localization @inject IJSRuntime Js @inject ApiService ApiService @inject ComposeService ComposeService @inject EmojiService EmojiService @inject SessionService SessionService @inject MetadataService MetadataService @inject IStringLocalizer Loc; @inject GlobalComponentSvc GlobalComponentSvc @inject SettingsService Settings; @inject NoteActions NoteActions;
@Loc["ComposeNote"] @Loc["Sending"] @Loc["Done"] @Loc["Retry"]
@if (ReplyOrQuote != null && !Preview) {
} @if (NoteDraft.Cw != null) {
} @if (Preview) {
@if (string.IsNullOrWhiteSpace(NoteDraft.Text)) { @Loc["Nothing to preview"] } else { }
}
@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 bool Preview { get; set; } private List EmojiList { get; set; } = []; private int NoteLength { get; set; } private bool SendLock { get; set; } = false; 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!" } }; private async Task HandleKeyDown(KeyboardEventArgs e) { if (e is { Code: "Enter", CtrlKey: true } or { Code: "Enter", MetaKey: true }) { await SendNote(); } } 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) { await 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) { var settings = await Settings.GetUserSettingsAsync(); if (replyTo != null) { var mentions = await EnumerateMentions(replyTo); await ResetState(); ReplyOrQuote = replyTo; NoteDraft.ReplyId = replyTo.Id; NoteDraft.Visibility = settings.DefaultNoteVisibility > replyTo.Visibility ? settings.DefaultNoteVisibility : replyTo.Visibility; NoteDraft.Cw = replyTo.Cw; TextPlaceholder = AvailablePlaceholders["reply"]; foreach (var el in mentions) { NoteDraft.Text += $"@{el} "; } } else if (quote != null) { await ResetState(); ReplyOrQuote = quote; NoteDraft.RenoteId = quote.Id; NoteDraft.Visibility = settings.DefaultNoteVisibility > quote.Visibility ? settings.DefaultNoteVisibility : quote.Visibility; TextPlaceholder = AvailablePlaceholders["quote"]; } else { await ResetState(); } StateHasChanged(); await _module.InvokeVoidAsync("openDialog", Dialog); } private async Task> 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 instance = await MetadataService.Instance.Value; var mfmNodes = noteBase.Text != null ? MfmParser.Parse(noteBase.Text) : []; foreach (var node in mfmNodes) { if (node is MfmMentionNode mentionNode) { mentions.Add(mentionNode.Acct.Replace($"@{instance.AccountDomain}", "")); } } mentions = mentions.Distinct().ToList(); mentions.Remove(SessionService.Current.Username); return mentions; } private async Task ResetState() { var settings = await Settings.GetUserSettingsAsync(); ReplyOrQuote = null; Attachments = new List(); NoteDraft = new NoteCreateRequest { Text = "", Visibility = settings.DefaultNoteVisibility, Cw = null }; TextPlaceholder = AvailablePlaceholders["default"]; } private async Task CloseDialog() { await _module.InvokeVoidAsync("closeDialog", Dialog); } private async Task SendNote() { if (SendLock) return; SendLock = true; SendButton.State = StateButton.StateEnum.Loading; if (Attachments.Count > 0) { NoteDraft.MediaIds = Attachments.Select(x => x.Id).ToList(); } try { await ApiService.Notes.CreateNoteAsync(NoteDraft); } catch (ApiException) { SendButton.State = StateButton.StateEnum.Failed; return; } if (ReplyOrQuote != null) { await NoteActions.RefetchNoteAsync(ReplyOrQuote); } SendButton.State = StateButton.StateEnum.Success; await CloseDialog(); SendLock = false; 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.UploadFileAsync(e.File); Attachments.Add(res); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _module = await Js.InvokeAsync("import", "./Components/Compose.razor.js"); EmojiList = await EmojiService.GetEmojiAsync(); var instance = await MetadataService.Instance.Value; NoteLength = instance.Limits.NoteLength; 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", Textarea); var text = NoteDraft.Text; var emojiString = $":{emoji.Name}: "; NoteDraft.Text = text.Insert(pos, emojiString); StateHasChanged(); } protected override void OnInitialized() { TextPlaceholder = AvailablePlaceholders["default"]; } }