Crude 'tagging' ability, will improve over time

This commit is contained in:
Mike Dilger 2023-01-08 16:31:52 +13:00
parent aea9c1f1a2
commit 5c1fc6a510
5 changed files with 96 additions and 33 deletions

View File

@ -1,4 +1,4 @@
use nostr_types::{Event, Id, IdHex, PublicKey, PublicKeyHex}; use nostr_types::{Event, Id, IdHex, PublicKey, PublicKeyHex, Tag};
/// This is a message sent to the Overlord /// This is a message sent to the Overlord
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -15,8 +15,8 @@ pub enum ToOverlordMessage {
Like(Id, PublicKey), Like(Id, PublicKey),
MinionIsReady, MinionIsReady,
ProcessIncomingEvents, ProcessIncomingEvents,
PostReply(String, Id), PostReply(String, Vec<Tag>, Id),
PostTextNote(String), PostTextNote(String, Vec<Tag>),
SaveRelays, SaveRelays,
SaveSettings, SaveSettings,
Shutdown, Shutdown,

View File

@ -436,11 +436,11 @@ impl Overlord {
} }
}); });
} }
ToOverlordMessage::PostReply(content, reply_to) => { ToOverlordMessage::PostReply(content, tags, reply_to) => {
self.post_reply(content, reply_to).await?; self.post_reply(content, tags, reply_to).await?;
} }
ToOverlordMessage::PostTextNote(content) => { ToOverlordMessage::PostTextNote(content, tags) => {
self.post_textnote(content).await?; self.post_textnote(content, tags).await?;
} }
ToOverlordMessage::SaveRelays => { ToOverlordMessage::SaveRelays => {
let dirty_relays: Vec<DbRelay> = GLOBALS let dirty_relays: Vec<DbRelay> = GLOBALS
@ -619,7 +619,7 @@ impl Overlord {
Ok(()) Ok(())
} }
async fn post_textnote(&mut self, content: String) -> Result<(), Error> { async fn post_textnote(&mut self, content: String, tags: Vec<Tag>) -> Result<(), Error> {
let event = { let event = {
let public_key = match GLOBALS.signer.read().await.public_key() { let public_key = match GLOBALS.signer.read().await.public_key() {
Some(pk) => pk, Some(pk) => pk,
@ -633,7 +633,7 @@ impl Overlord {
pubkey: public_key, pubkey: public_key,
created_at: Unixtime::now().unwrap(), created_at: Unixtime::now().unwrap(),
kind: EventKind::TextNote, kind: EventKind::TextNote,
tags: vec![], tags,
content, content,
ots: None, ots: None,
}; };
@ -672,9 +672,12 @@ impl Overlord {
Ok(()) Ok(())
} }
async fn post_reply(&mut self, content: String, reply_to: Id) -> Result<(), Error> { async fn post_reply(
let mut tags: Vec<Tag> = Vec::new(); &mut self,
content: String,
mut tags: Vec<Tag>,
reply_to: Id,
) -> Result<(), Error> {
let event = { let event = {
let public_key = match GLOBALS.signer.read().await.public_key() { let public_key = match GLOBALS.signer.read().await.public_key() {
Some(pk) => pk, Some(pk) => pk,
@ -720,6 +723,8 @@ impl Overlord {
.collect(); .collect();
tags.extend(parent_p_tags); tags.extend(parent_p_tags);
// FIXME deduplicate 'p' tags
let pre_event = PreEvent { let pre_event = PreEvent {
pubkey: public_key, pubkey: public_key,
created_at: Unixtime::now().unwrap(), created_at: Unixtime::now().unwrap(),

View File

@ -2,7 +2,7 @@ use crate::db::DbPerson;
use crate::error::Error; use crate::error::Error;
use crate::globals::GLOBALS; use crate::globals::GLOBALS;
use image::RgbaImage; use image::RgbaImage;
use nostr_types::{Metadata, PublicKeyHex, Unixtime, Url}; use nostr_types::{Metadata, PublicKey, PublicKeyHex, Unixtime, Url};
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::time::Duration; use std::time::Duration;
@ -333,6 +333,28 @@ impl People {
} }
} }
/// This lets you start typing a name, and autocomplete the results for tagging
/// someone in a post. It returns maximum 10 results.
pub fn get_ids_from_prefix(&self, mut prefix: &str) -> Vec<(String, PublicKey)> {
// work with or without the @ symbol:
if prefix.starts_with('@') {
prefix = &prefix[1..]
}
self.people
.iter()
.filter_map(|(_, person)| {
if let Some(name) = &person.name {
if name.starts_with(prefix) {
let pubkey = PublicKey::try_from_hex_string(&person.pubkey).unwrap(); // FIXME
return Some((name.clone(), pubkey));
}
}
None
})
.take(10)
.collect()
}
/// This is a 'just in case' the main code isn't keeping them in sync. /// This is a 'just in case' the main code isn't keeping them in sync.
pub async fn populate_new_people() -> Result<(), Error> { pub async fn populate_new_people() -> Result<(), Error> {
let sql = "INSERT or IGNORE INTO person (pubkey) SELECT DISTINCT pubkey FROM EVENT"; let sql = "INSERT or IGNORE INTO person (pubkey) SELECT DISTINCT pubkey FROM EVENT";

View File

@ -145,28 +145,60 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram
} }
ui.with_layout(Layout::right_to_left(Align::TOP), |ui| { ui.with_layout(Layout::right_to_left(Align::TOP), |ui| {
if ui.button("Send").clicked() && !app.draft.is_empty() { ui.with_layout(Layout::top_down(Align::RIGHT), |ui| {
match app.replying_to { if ui.button("Send").clicked() && !app.draft.is_empty() {
Some(replying_to_id) => { match app.replying_to {
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PostReply( Some(replying_to_id) => {
app.draft.clone(), let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PostReply(
replying_to_id, app.draft.clone(),
)); app.draft_tags.clone(),
replying_to_id,
));
}
None => {
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PostTextNote(
app.draft.clone(),
app.draft_tags.clone(),
));
}
} }
None => { app.draft = "".to_owned();
let _ = GLOBALS app.replying_to = None;
.to_overlord }
.send(ToOverlordMessage::PostTextNote(app.draft.clone()));
if ui.button("Cancel").clicked() {
app.draft = "".to_owned();
app.replying_to = None;
}
ui.add(
TextEdit::singleline(&mut app.tag_someone)
.desired_width(100.0)
.hint_text("@username"),
);
if !app.tag_someone.is_empty() {
let pairs = GLOBALS
.people
.blocking_read()
.get_ids_from_prefix(&app.tag_someone);
if !pairs.is_empty() {
ui.menu_button("@", |ui| {
for pair in pairs {
if ui.button(pair.0).clicked() {
app.draft_tags.push(Tag::Pubkey {
pubkey: pair.1,
recommended_relay_url: None, // FIXME
petname: None,
});
app.draft
.push_str(&format!("#[{}]", app.draft_tags.len() - 1));
app.tag_someone = "".to_owned();
}
}
});
} }
} }
app.draft = "".to_owned(); });
app.replying_to = None;
}
if ui.button("Cancel").clicked() {
app.draft = "".to_owned();
app.replying_to = None;
}
ui.add( ui.add(
TextEdit::multiline(&mut app.draft) TextEdit::multiline(&mut app.draft)
.hint_text("Type your message here") .hint_text("Type your message here")

View File

@ -18,7 +18,7 @@ use egui::{
ColorImage, Context, ImageData, Label, RichText, SelectableLabel, Sense, TextStyle, ColorImage, Context, ImageData, Label, RichText, SelectableLabel, Sense, TextStyle,
TextureHandle, TextureOptions, Ui, TextureHandle, TextureOptions, Ui,
}; };
use nostr_types::{Id, IdHex, PublicKey, PublicKeyHex}; use nostr_types::{Id, IdHex, PublicKey, PublicKeyHex, Tag};
use std::collections::HashMap; use std::collections::HashMap;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use zeroize::Zeroize; use zeroize::Zeroize;
@ -76,6 +76,8 @@ struct GossipUi {
icon: TextureHandle, icon: TextureHandle,
placeholder_avatar: TextureHandle, placeholder_avatar: TextureHandle,
draft: String, draft: String,
draft_tags: Vec<Tag>,
tag_someone: String,
settings: Settings, settings: Settings,
nip05follow: String, nip05follow: String,
follow_bech32_pubkey: String, follow_bech32_pubkey: String,
@ -148,6 +150,8 @@ impl GossipUi {
icon: icon_texture_handle, icon: icon_texture_handle,
placeholder_avatar: placeholder_avatar_texture_handle, placeholder_avatar: placeholder_avatar_texture_handle,
draft: "".to_owned(), draft: "".to_owned(),
draft_tags: Vec::new(),
tag_someone: "".to_owned(),
settings, settings,
nip05follow: "".to_owned(), nip05follow: "".to_owned(),
follow_bech32_pubkey: "".to_owned(), follow_bech32_pubkey: "".to_owned(),