add PostView mentions UI
Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
@@ -1,16 +1,23 @@
|
|||||||
use poll_promise::Promise;
|
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;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Draft {
|
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 uploaded_media: Vec<Nip94Event>, // media uploads to include
|
||||||
pub uploading_media: Vec<Promise<Result<Nip94Event, Error>>>, // promises that aren't ready yet
|
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 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)]
|
#[derive(Default)]
|
||||||
pub struct Drafts {
|
pub struct Drafts {
|
||||||
replies: HashMap<[u8; 32], Draft>,
|
replies: HashMap<[u8; 32], Draft>,
|
||||||
@@ -46,7 +53,7 @@ impl Draft {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.buffer = "".to_string();
|
self.buffer = PostBuffer::default();
|
||||||
self.upload_errors = Vec::new();
|
self.upload_errors = Vec::new();
|
||||||
self.uploaded_media = Vec::new();
|
self.uploaded_media = Vec::new();
|
||||||
self.uploading_media = Vec::new();
|
self.uploading_media = Vec::new();
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub struct NewPost {
|
|||||||
pub content: String,
|
pub content: String,
|
||||||
pub account: FullKeypair,
|
pub account: FullKeypair,
|
||||||
pub media: Vec<Nip94Event>,
|
pub media: Vec<Nip94Event>,
|
||||||
|
pub mentions: Vec<Pubkey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_client_tag(builder: NoteBuilder<'_>) -> NoteBuilder<'_> {
|
fn add_client_tag(builder: NoteBuilder<'_>) -> NoteBuilder<'_> {
|
||||||
@@ -23,11 +24,17 @@ fn add_client_tag(builder: NoteBuilder<'_>) -> NoteBuilder<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl NewPost {
|
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 {
|
NewPost {
|
||||||
content,
|
content,
|
||||||
account,
|
account,
|
||||||
media,
|
media,
|
||||||
|
mentions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
use crate::draft::{Draft, Drafts};
|
use crate::draft::{Draft, Drafts, MentionHint};
|
||||||
use crate::images::fetch_img;
|
use crate::images::fetch_img;
|
||||||
use crate::media_upload::{nostrbuild_nip96_upload, MediaPath};
|
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::ui::{self, Preview, PreviewConfig};
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
use egui::text::CCursorRange;
|
||||||
|
use egui::text_edit::TextEditOutput;
|
||||||
use egui::widgets::text_edit::TextEdit;
|
use egui::widgets::text_edit::TextEdit;
|
||||||
use egui::{vec2, Frame, Layout, Margin, Pos2, ScrollArea, Sense};
|
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 nostrdb::{Ndb, Transaction};
|
||||||
|
|
||||||
use notedeck::{ImageCache, NoteCache};
|
use notedeck::{ImageCache, NoteCache};
|
||||||
@@ -126,18 +130,85 @@ impl<'a> PostView<'a> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = ui.add_sized(
|
let textedit = TextEdit::multiline(&mut self.draft.buffer)
|
||||||
ui.available_size(),
|
|
||||||
TextEdit::multiline(&mut self.draft.buffer)
|
|
||||||
.hint_text(egui::RichText::new("Write a banger note here...").weak())
|
.hint_text(egui::RichText::new("Write a banger note here...").weak())
|
||||||
.frame(false),
|
.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));
|
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 {
|
fn focused(&self, ui: &egui::Ui) -> bool {
|
||||||
@@ -237,10 +308,12 @@ impl<'a> PostView<'a> {
|
|||||||
)
|
)
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
|
let output = self.draft.buffer.output();
|
||||||
let new_post = NewPost::new(
|
let new_post = NewPost::new(
|
||||||
self.draft.buffer.clone(),
|
output.text,
|
||||||
self.poster.to_full(),
|
self.poster.to_full(),
|
||||||
self.draft.uploaded_media.clone(),
|
self.draft.uploaded_media.clone(),
|
||||||
|
output.mentions,
|
||||||
);
|
);
|
||||||
Some(PostAction::new(self.post_type.clone(), new_post))
|
Some(PostAction::new(self.post_type.clone(), new_post))
|
||||||
} else {
|
} else {
|
||||||
@@ -485,6 +558,32 @@ fn show_remove_upload_button(ui: &mut egui::Ui, desired_rect: egui::Rect) -> egu
|
|||||||
resp
|
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 {
|
mod preview {
|
||||||
|
|
||||||
use crate::media_upload::Nip94Event;
|
use crate::media_upload::Nip94Event;
|
||||||
|
|||||||
Reference in New Issue
Block a user