mirror of
https://github.com/mikedilger/gossip.git
synced 2024-09-19 19:46:50 +00:00
insert and parse npub keys into and from text content.
instead of inserting a "#[index]" string. includes a new file `tags.rs` with useful functions.
This commit is contained in:
parent
876e9e1516
commit
0264ee8bf6
@ -19,6 +19,7 @@ mod process;
|
|||||||
mod relationship;
|
mod relationship;
|
||||||
mod settings;
|
mod settings;
|
||||||
mod signer;
|
mod signer;
|
||||||
|
mod tags;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
use crate::comms::ToOverlordMessage;
|
use crate::comms::ToOverlordMessage;
|
||||||
|
@ -6,6 +6,7 @@ use crate::db::{DbEvent, DbEventSeen, DbPersonRelay, DbRelay};
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::globals::GLOBALS;
|
use crate::globals::GLOBALS;
|
||||||
use crate::people::People;
|
use crate::people::People;
|
||||||
|
use crate::tags::{add_pubkey_to_tags, add_tag, keys_from_text};
|
||||||
use minion::Minion;
|
use minion::Minion;
|
||||||
use nostr_types::{
|
use nostr_types::{
|
||||||
Event, EventKind, Id, IdHex, PreEvent, PrivateKey, PublicKey, PublicKeyHex, Tag, Unixtime, Url,
|
Event, EventKind, Id, IdHex, PreEvent, PrivateKey, PublicKey, PublicKeyHex, Tag, Unixtime, Url,
|
||||||
@ -602,7 +603,11 @@ impl Overlord {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn post_textnote(&mut self, content: String, mut tags: Vec<Tag>) -> Result<(), Error> {
|
async fn post_textnote(
|
||||||
|
&mut self,
|
||||||
|
mut content: String,
|
||||||
|
mut 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,
|
||||||
@ -619,6 +624,13 @@ impl Overlord {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add tags for keys that are in the post body as npub1...
|
||||||
|
for (npub, pubkey) in keys_from_text(&content) {
|
||||||
|
let idx = add_pubkey_to_tags(&mut tags, pubkey);
|
||||||
|
content = content.replace(&npub, &format!("#[{}]", idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally build the event
|
||||||
let pre_event = PreEvent {
|
let pre_event = PreEvent {
|
||||||
pubkey: public_key,
|
pubkey: public_key,
|
||||||
created_at: Unixtime::now().unwrap(),
|
created_at: Unixtime::now().unwrap(),
|
||||||
@ -669,7 +681,7 @@ impl Overlord {
|
|||||||
|
|
||||||
async fn post_reply(
|
async fn post_reply(
|
||||||
&mut self,
|
&mut self,
|
||||||
content: String,
|
mut content: String,
|
||||||
mut tags: Vec<Tag>,
|
mut tags: Vec<Tag>,
|
||||||
reply_to: Id,
|
reply_to: Id,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
@ -692,33 +704,24 @@ impl Overlord {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add all the 'p' tags from the note we are replying to
|
// Add a 'p' tag for the author we are replying to (except if it is our own key)
|
||||||
for parent_p_tag in event.tags.iter() {
|
if let Some(pubkey) = GLOBALS.signer.read().await.public_key() {
|
||||||
if let Tag::Pubkey {
|
if pubkey != event.pubkey {
|
||||||
pubkey: parent_p_tag_pubkey,
|
add_pubkey_to_tags(&mut tags, pubkey.into());
|
||||||
..
|
}
|
||||||
} = parent_p_tag
|
|
||||||
{
|
|
||||||
if parent_p_tag_pubkey.0 == public_key.as_hex_string() {
|
|
||||||
// do not tag ourselves
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if tags
|
// Add all the 'p' tags from the note we are replying to (except our own)
|
||||||
.iter()
|
for tag in &event.tags {
|
||||||
.any(|existing_tag| {
|
if tag.tagname() == "p" {
|
||||||
matches!(
|
add_tag(&mut tags, tag);
|
||||||
existing_tag,
|
}
|
||||||
Tag::Pubkey { pubkey: existing_pubkey, .. } if existing_pubkey.0 == parent_p_tag_pubkey.0
|
|
||||||
)
|
|
||||||
}) {
|
|
||||||
// we already have this `p` tag, do not add again
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add (FIXME: include relay hint it not exists)
|
// Add tags for keys that are in the post body as npub1...
|
||||||
tags.push(parent_p_tag.to_owned())
|
for (npub, pubkey) in keys_from_text(&content) {
|
||||||
}
|
let idx = add_pubkey_to_tags(&mut tags, pubkey);
|
||||||
|
content = content.replace(&npub, &format!("#[{}]", idx));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((root, _maybeurl)) = event.replies_to_root() {
|
if let Some((root, _maybeurl)) = event.replies_to_root() {
|
||||||
|
@ -6,7 +6,9 @@ use dashmap::{DashMap, DashSet};
|
|||||||
use eframe::egui::ColorImage;
|
use eframe::egui::ColorImage;
|
||||||
use egui_extras::image::FitTo;
|
use egui_extras::image::FitTo;
|
||||||
use image::imageops::FilterType;
|
use image::imageops::FilterType;
|
||||||
use nostr_types::{Event, EventKind, Metadata, PreEvent, PublicKeyHex, Tag, Unixtime, Url};
|
use nostr_types::{
|
||||||
|
Event, EventKind, Metadata, PreEvent, PublicKey, PublicKeyHex, Tag, Unixtime, Url,
|
||||||
|
};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
@ -351,7 +353,7 @@ impl People {
|
|||||||
|
|
||||||
/// This lets you start typing a name, and autocomplete the results for tagging
|
/// This lets you start typing a name, and autocomplete the results for tagging
|
||||||
/// someone in a post. It returns maximum 10 results.
|
/// someone in a post. It returns maximum 10 results.
|
||||||
pub fn search_people_to_tag(&self, mut text: &str) -> Vec<(String, PublicKeyHex)> {
|
pub fn search_people_to_tag(&self, mut text: &str) -> Vec<(String, PublicKey)> {
|
||||||
// work with or without the @ symbol:
|
// work with or without the @ symbol:
|
||||||
if text.starts_with('@') {
|
if text.starts_with('@') {
|
||||||
text = &text[1..]
|
text = &text[1..]
|
||||||
@ -419,7 +421,12 @@ impl People {
|
|||||||
};
|
};
|
||||||
results[0..max]
|
results[0..max]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|r| (r.1.to_owned(), r.2.clone()))
|
.map(|r| {
|
||||||
|
(
|
||||||
|
r.1.to_owned(),
|
||||||
|
PublicKey::try_from_hex_string(&r.2).unwrap(),
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
84
src/tags.rs
Normal file
84
src/tags.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
use nostr_types::{PublicKey, Tag};
|
||||||
|
|
||||||
|
pub fn keys_from_text(text: &str) -> Vec<(String, PublicKey)> {
|
||||||
|
let mut pubkeys: Vec<(String, PublicKey)> = text
|
||||||
|
.split(&[' ', ',', '.', ':', ';', '?', '!', '/'][..])
|
||||||
|
.filter_map(|npub| {
|
||||||
|
if !npub.starts_with("npub1") {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
PublicKey::try_from_bech32_string(&npub)
|
||||||
|
.ok()
|
||||||
|
.map(|pubkey| (npub.to_string(), pubkey))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
pubkeys.sort_unstable_by_key(|nk| nk.1.as_bytes());
|
||||||
|
pubkeys.dedup();
|
||||||
|
pubkeys
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_pubkey_to_tags(existing_tags: &mut Vec<Tag>, added: PublicKey) -> usize {
|
||||||
|
add_tag(
|
||||||
|
existing_tags,
|
||||||
|
&Tag::Pubkey {
|
||||||
|
pubkey: added.as_hex_string().into(),
|
||||||
|
recommended_relay_url: None,
|
||||||
|
petname: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_tag(existing_tags: &mut Vec<Tag>, added: &Tag) -> usize {
|
||||||
|
match added {
|
||||||
|
Tag::Pubkey { pubkey, .. } => {
|
||||||
|
match existing_tags.iter().position(|existing_tag| {
|
||||||
|
matches!(
|
||||||
|
existing_tag,
|
||||||
|
Tag::Pubkey { pubkey: existing_p, .. } if existing_p.0 == pubkey.0
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
None => {
|
||||||
|
// add (FIXME: include relay hint it not exists)
|
||||||
|
existing_tags.push(added.to_owned());
|
||||||
|
existing_tags.len() - 1
|
||||||
|
}
|
||||||
|
Some(idx) => idx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tag::Event { id, .. } => {
|
||||||
|
match existing_tags.iter().position(|existing_tag| {
|
||||||
|
matches!(
|
||||||
|
existing_tag,
|
||||||
|
Tag::Event { id: existing_e, .. } if existing_e.0 == id.0
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
None => {
|
||||||
|
// add (FIXME: include relay hint it not exists)
|
||||||
|
existing_tags.push(added.to_owned());
|
||||||
|
existing_tags.len() - 1
|
||||||
|
}
|
||||||
|
Some(idx) => idx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tag::Hashtag(hashtag) => {
|
||||||
|
match existing_tags.iter().position(|existing_tag| {
|
||||||
|
matches!(
|
||||||
|
existing_tag,
|
||||||
|
Tag::Hashtag(existing_hashtag) if existing_hashtag == hashtag
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
None => {
|
||||||
|
// add (FIXME: include relay hint it not exists)
|
||||||
|
existing_tags.push(added.to_owned());
|
||||||
|
existing_tags.len() - 1
|
||||||
|
}
|
||||||
|
Some(idx) => idx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
existing_tags.push(added.to_owned());
|
||||||
|
existing_tags.len() - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ use super::{GossipUi, Page};
|
|||||||
use crate::comms::ToOverlordMessage;
|
use crate::comms::ToOverlordMessage;
|
||||||
use crate::feed::FeedKind;
|
use crate::feed::FeedKind;
|
||||||
use crate::globals::{Globals, GLOBALS};
|
use crate::globals::{Globals, GLOBALS};
|
||||||
|
use crate::tags::keys_from_text;
|
||||||
use crate::ui::widgets::CopyButton;
|
use crate::ui::widgets::CopyButton;
|
||||||
use crate::AVATAR_SIZE_F32;
|
use crate::AVATAR_SIZE_F32;
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
@ -141,25 +142,22 @@ fn real_posting_area(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram
|
|||||||
Some(replying_to_id) => {
|
Some(replying_to_id) => {
|
||||||
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PostReply(
|
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PostReply(
|
||||||
app.draft.clone(),
|
app.draft.clone(),
|
||||||
app.draft_tags.clone(),
|
vec![],
|
||||||
replying_to_id,
|
replying_to_id,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PostTextNote(
|
let _ = GLOBALS
|
||||||
app.draft.clone(),
|
.to_overlord
|
||||||
app.draft_tags.clone(),
|
.send(ToOverlordMessage::PostTextNote(app.draft.clone(), vec![]));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
app.draft = "".to_owned();
|
app.draft = "".to_owned();
|
||||||
app.draft_tags = vec![];
|
|
||||||
app.replying_to = None;
|
app.replying_to = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui.button("Cancel").clicked() {
|
if ui.button("Cancel").clicked() {
|
||||||
app.draft = "".to_owned();
|
app.draft = "".to_owned();
|
||||||
app.draft_tags = vec![];
|
|
||||||
app.replying_to = None;
|
app.replying_to = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,24 +172,10 @@ fn real_posting_area(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram
|
|||||||
ui.menu_button("@", |ui| {
|
ui.menu_button("@", |ui| {
|
||||||
for pair in pairs {
|
for pair in pairs {
|
||||||
if ui.button(pair.0).clicked() {
|
if ui.button(pair.0).clicked() {
|
||||||
let idx = app
|
if !app.draft.ends_with(" ") {
|
||||||
.draft_tags
|
app.draft.push_str(" ");
|
||||||
.iter()
|
}
|
||||||
.position(|tag| {
|
app.draft.push_str(&pair.1.try_as_bech32_string().unwrap());
|
||||||
matches!(
|
|
||||||
tag,
|
|
||||||
Tag::Pubkey { pubkey, .. } if pubkey.0 == *pair.1
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
app.draft_tags.push(Tag::Pubkey {
|
|
||||||
pubkey: pair.1,
|
|
||||||
recommended_relay_url: None, // FIXME
|
|
||||||
petname: None,
|
|
||||||
});
|
|
||||||
app.draft_tags.len() - 1
|
|
||||||
});
|
|
||||||
app.draft.push_str(&format!("#[{}]", idx));
|
|
||||||
app.tag_someone = "".to_owned();
|
app.tag_someone = "".to_owned();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -209,21 +193,17 @@ fn real_posting_area(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// List of tags to be applied
|
// List tags that will be applied (FIXME: list tags from parent event too in case of reply)
|
||||||
for (i, tag) in app.draft_tags.iter().enumerate() {
|
for (i, (npub, pubkey)) in keys_from_text(&app.draft).iter().enumerate() {
|
||||||
let rendered = match tag {
|
let rendered = if let Some(person) = GLOBALS.people.get(&pubkey.as_hex_string().into()) {
|
||||||
Tag::Pubkey { pubkey, .. } => {
|
|
||||||
if let Some(person) = GLOBALS.people.get(pubkey) {
|
|
||||||
match person.name {
|
match person.name {
|
||||||
Some(name) => name,
|
Some(name) => name,
|
||||||
None => pubkey.0.clone(),
|
None => npub.to_string(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pubkey.0.clone()
|
npub.to_string()
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => serde_json::to_string(tag).unwrap(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ui.label(format!("{}: {}", i, rendered));
|
ui.label(format!("{}: {}", i, rendered));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -538,19 +518,6 @@ fn render_post_actual(
|
|||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
app.replying_to = Some(event.id);
|
app.replying_to = Some(event.id);
|
||||||
|
|
||||||
// Cleanup tags
|
|
||||||
app.draft_tags = vec![];
|
|
||||||
// Add a 'p' tag for the author we are replying to (except if it is our own key)
|
|
||||||
if let Some(pubkey) = GLOBALS.signer.blocking_read().public_key() {
|
|
||||||
if pubkey != event.pubkey {
|
|
||||||
app.draft_tags.push(Tag::Pubkey {
|
|
||||||
pubkey: event.pubkey.into(),
|
|
||||||
recommended_relay_url: None, // FIXME
|
|
||||||
petname: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add_space(24.0);
|
ui.add_space(24.0);
|
||||||
|
@ -19,7 +19,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, Tag};
|
use nostr_types::{Id, IdHex, PublicKey, PublicKeyHex};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@ -76,7 +76,6 @@ struct GossipUi {
|
|||||||
icon: TextureHandle,
|
icon: TextureHandle,
|
||||||
placeholder_avatar: TextureHandle,
|
placeholder_avatar: TextureHandle,
|
||||||
draft: String,
|
draft: String,
|
||||||
draft_tags: Vec<Tag>,
|
|
||||||
tag_someone: String,
|
tag_someone: String,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
nip05follow: String,
|
nip05follow: String,
|
||||||
@ -174,7 +173,6 @@ 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(),
|
tag_someone: "".to_owned(),
|
||||||
settings,
|
settings,
|
||||||
nip05follow: "".to_owned(),
|
nip05follow: "".to_owned(),
|
||||||
|
Loading…
Reference in New Issue
Block a user