add PostView mentions UI

Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
kernelkind
2025-02-02 17:51:40 -05:00
parent e7ada80876
commit c0662798a2
3 changed files with 129 additions and 16 deletions

View File

@@ -1,16 +1,23 @@
use poll_promise::Promise;
use crate::{media_upload::Nip94Event, ui::note::PostType, Error};
use crate::{media_upload::Nip94Event, post::PostBuffer, ui::note::PostType, Error};
use std::collections::HashMap;
#[derive(Default)]
pub struct Draft {
pub buffer: String,
pub buffer: PostBuffer,
pub cur_mention_hint: Option<MentionHint>,
pub uploaded_media: Vec<Nip94Event>, // media uploads to include
pub uploading_media: Vec<Promise<Result<Nip94Event, Error>>>, // promises that aren't ready yet
pub upload_errors: Vec<String>, // media upload errors to show the user
}
pub struct MentionHint {
pub index: usize,
pub pos: egui::Pos2,
pub text: String,
}
#[derive(Default)]
pub struct Drafts {
replies: HashMap<[u8; 32], Draft>,
@@ -46,7 +53,7 @@ impl Draft {
}
pub fn clear(&mut self) {
self.buffer = "".to_string();
self.buffer = PostBuffer::default();
self.upload_errors = Vec::new();
self.uploaded_media = Vec::new();
self.uploading_media = Vec::new();

View File

@@ -13,6 +13,7 @@ pub struct NewPost {
pub content: String,
pub account: FullKeypair,
pub media: Vec<Nip94Event>,
pub mentions: Vec<Pubkey>,
}
fn add_client_tag(builder: NoteBuilder<'_>) -> NoteBuilder<'_> {
@@ -23,11 +24,17 @@ fn add_client_tag(builder: NoteBuilder<'_>) -> NoteBuilder<'_> {
}
impl NewPost {
pub fn new(content: String, account: FullKeypair, media: Vec<Nip94Event>) -> Self {
pub fn new(
content: String,
account: enostr::FullKeypair,
media: Vec<Nip94Event>,
mentions: Vec<Pubkey>,
) -> Self {
NewPost {
content,
account,
media,
mentions,
}
}

View File

@@ -1,12 +1,16 @@
use crate::draft::{Draft, Drafts};
use crate::draft::{Draft, Drafts, MentionHint};
use crate::images::fetch_img;
use crate::media_upload::{nostrbuild_nip96_upload, MediaPath};
use crate::post::NewPost;
use crate::post::{MentionType, NewPost};
use crate::profile::get_display_name;
use crate::ui::search_results::SearchResultsView;
use crate::ui::{self, Preview, PreviewConfig};
use crate::Result;
use egui::text::CCursorRange;
use egui::text_edit::TextEditOutput;
use egui::widgets::text_edit::TextEdit;
use egui::{vec2, Frame, Layout, Margin, Pos2, ScrollArea, Sense};
use enostr::{FilledKeypair, FullKeypair, NoteId, RelayPool};
use enostr::{FilledKeypair, FullKeypair, NoteId, Pubkey, RelayPool};
use nostrdb::{Ndb, Transaction};
use notedeck::{ImageCache, NoteCache};
@@ -126,18 +130,85 @@ impl<'a> PostView<'a> {
);
}
let response = ui.add_sized(
ui.available_size(),
TextEdit::multiline(&mut self.draft.buffer)
.hint_text(egui::RichText::new("Write a banger note here...").weak())
.frame(false),
);
let textedit = TextEdit::multiline(&mut self.draft.buffer)
.hint_text(egui::RichText::new("Write a banger note here...").weak())
.frame(false)
.desired_width(ui.available_width());
let focused = response.has_focus();
let out = textedit.show(ui);
if let Some(cursor_index) = get_cursor_index(&out.state.cursor.char_range()) {
self.show_mention_hints(txn, ui, cursor_index, &out);
}
let focused = out.response.has_focus();
ui.ctx().data_mut(|d| d.insert_temp(self.id(), focused));
response
out.response
}
fn show_mention_hints(
&mut self,
txn: &nostrdb::Transaction,
ui: &mut egui::Ui,
cursor_index: usize,
textedit_output: &TextEditOutput,
) {
if let Some(mention) = &self.draft.buffer.get_mention(cursor_index) {
if mention.info.mention_type == MentionType::Pending {
let mention_str = self.draft.buffer.get_mention_string(mention);
if !mention_str.is_empty() {
if let Some(mention_hint) = &mut self.draft.cur_mention_hint {
if mention_hint.index != mention.index {
mention_hint.index = mention.index;
mention_hint.pos = calculate_mention_hints_pos(
textedit_output,
mention.info.start_index,
);
}
mention_hint.text = mention_str.to_owned();
} else {
self.draft.cur_mention_hint = Some(MentionHint {
index: mention.index,
text: mention_str.to_owned(),
pos: calculate_mention_hints_pos(
textedit_output,
mention.info.start_index,
),
});
}
}
if let Some(hint) = &self.draft.cur_mention_hint {
let hint_rect = {
let mut hint_rect = self.inner_rect;
hint_rect.set_top(hint.pos.y);
hint_rect
};
if let Ok(res) = self.ndb.search_profile(txn, mention_str, 10) {
let hint_selection =
SearchResultsView::new(self.img_cache, self.ndb, txn, &res)
.show_in_rect(hint_rect, ui);
if let Some(hint_index) = hint_selection {
if let Some(pk) = res.get(hint_index) {
let record = self.ndb.get_profile_by_pubkey(txn, pk);
self.draft.buffer.select_mention_and_replace_name(
mention.index,
get_display_name(record.ok().as_ref()).name(),
Pubkey::new(**pk),
);
self.draft.cur_mention_hint = None;
}
}
}
}
}
}
}
fn focused(&self, ui: &egui::Ui) -> bool {
@@ -237,10 +308,12 @@ impl<'a> PostView<'a> {
)
.clicked()
{
let output = self.draft.buffer.output();
let new_post = NewPost::new(
self.draft.buffer.clone(),
output.text,
self.poster.to_full(),
self.draft.uploaded_media.clone(),
output.mentions,
);
Some(PostAction::new(self.post_type.clone(), new_post))
} else {
@@ -485,6 +558,32 @@ fn show_remove_upload_button(ui: &mut egui::Ui, desired_rect: egui::Rect) -> egu
resp
}
fn get_cursor_index(cursor: &Option<CCursorRange>) -> Option<usize> {
let range = cursor.as_ref()?;
if range.primary.index == range.secondary.index {
Some(range.primary.index)
} else {
None
}
}
fn calculate_mention_hints_pos(out: &TextEditOutput, char_pos: usize) -> egui::Pos2 {
let mut cur_pos = 0;
for row in &out.galley.rows {
if cur_pos + row.glyphs.len() <= char_pos {
cur_pos += row.glyphs.len();
} else if let Some(glyph) = row.glyphs.get(char_pos - cur_pos) {
let mut pos = glyph.pos + out.galley_pos.to_vec2();
pos.y += row.rect.height();
return pos;
}
}
out.text_clip_rect.left_bottom()
}
mod preview {
use crate::media_upload::Nip94Event;