commit c0662798a2667cd887d0c022221dcd6e23837430
parent e7ada80876d4e69b5659e1865d6018e690236741
Author: kernelkind <kernelkind@gmail.com>
Date: Sun, 2 Feb 2025 17:51:40 -0500
add PostView mentions UI
Signed-off-by: kernelkind <kernelkind@gmail.com>
Diffstat:
3 files changed, 129 insertions(+), 16 deletions(-)
diff --git a/crates/notedeck_columns/src/draft.rs b/crates/notedeck_columns/src/draft.rs
@@ -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();
diff --git a/crates/notedeck_columns/src/post.rs b/crates/notedeck_columns/src/post.rs
@@ -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,
}
}
diff --git a/crates/notedeck_columns/src/ui/note/post.rs b/crates/notedeck_columns/src/ui/note/post.rs
@@ -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;