mirror of
https://github.com/mikedilger/gossip.git
synced 2024-09-19 11:43:43 +00:00
Merge pull request #154 from fiatjaf/tag-stuff
insert and parse npub keys into and from text content.
This commit is contained in:
commit
40fee33863
@ -19,6 +19,7 @@ mod process;
|
||||
mod relationship;
|
||||
mod settings;
|
||||
mod signer;
|
||||
mod tags;
|
||||
mod ui;
|
||||
|
||||
use crate::comms::ToOverlordMessage;
|
||||
|
@ -6,6 +6,7 @@ use crate::db::{DbEvent, DbEventSeen, DbPersonRelay, DbRelay};
|
||||
use crate::error::Error;
|
||||
use crate::globals::GLOBALS;
|
||||
use crate::people::People;
|
||||
use crate::tags::{add_pubkey_to_tags, add_tag, keys_from_text};
|
||||
use minion::Minion;
|
||||
use nostr_types::{
|
||||
Event, EventKind, Id, IdHex, PreEvent, PrivateKey, PublicKey, PublicKeyHex, Tag, Unixtime, Url,
|
||||
@ -602,7 +603,11 @@ impl Overlord {
|
||||
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 public_key = match GLOBALS.signer.read().await.public_key() {
|
||||
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 {
|
||||
pubkey: public_key,
|
||||
created_at: Unixtime::now().unwrap(),
|
||||
@ -669,7 +681,7 @@ impl Overlord {
|
||||
|
||||
async fn post_reply(
|
||||
&mut self,
|
||||
content: String,
|
||||
mut content: String,
|
||||
mut tags: Vec<Tag>,
|
||||
reply_to: Id,
|
||||
) -> Result<(), Error> {
|
||||
@ -692,35 +704,26 @@ impl Overlord {
|
||||
}
|
||||
};
|
||||
|
||||
// Add all the 'p' tags from the note we are replying to
|
||||
for parent_p_tag in event.tags.iter() {
|
||||
if let Tag::Pubkey {
|
||||
pubkey: parent_p_tag_pubkey,
|
||||
..
|
||||
} = parent_p_tag
|
||||
{
|
||||
if parent_p_tag_pubkey.0 == public_key.as_hex_string() {
|
||||
// do not tag ourselves
|
||||
continue;
|
||||
}
|
||||
|
||||
if tags
|
||||
.iter()
|
||||
.any(|existing_tag| {
|
||||
matches!(
|
||||
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)
|
||||
tags.push(parent_p_tag.to_owned())
|
||||
// Add a 'p' tag for the author we are replying to (except if it is our own key)
|
||||
if let Some(pubkey) = GLOBALS.signer.read().await.public_key() {
|
||||
if pubkey != event.pubkey {
|
||||
add_pubkey_to_tags(&mut tags, pubkey.into());
|
||||
}
|
||||
}
|
||||
|
||||
// Add all the 'p' tags from the note we are replying to (except our own)
|
||||
for tag in &event.tags {
|
||||
if tag.tagname() == "p" {
|
||||
add_tag(&mut tags, tag);
|
||||
}
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
if let Some((root, _maybeurl)) = event.replies_to_root() {
|
||||
// Add an 'e' tag for the root
|
||||
tags.push(Tag::Event {
|
||||
|
@ -6,7 +6,9 @@ use dashmap::{DashMap, DashSet};
|
||||
use eframe::egui::ColorImage;
|
||||
use egui_extras::image::FitTo;
|
||||
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::time::Duration;
|
||||
use tokio::task;
|
||||
@ -351,7 +353,7 @@ 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 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:
|
||||
if text.starts_with('@') {
|
||||
text = &text[1..]
|
||||
@ -419,7 +421,12 @@ impl People {
|
||||
};
|
||||
results[0..max]
|
||||
.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()
|
||||
}
|
||||
|
||||
|
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::feed::FeedKind;
|
||||
use crate::globals::{Globals, GLOBALS};
|
||||
use crate::tags::keys_from_text;
|
||||
use crate::ui::widgets::CopyButton;
|
||||
use crate::AVATAR_SIZE_F32;
|
||||
use eframe::egui;
|
||||
@ -141,25 +142,22 @@ fn real_posting_area(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram
|
||||
Some(replying_to_id) => {
|
||||
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PostReply(
|
||||
app.draft.clone(),
|
||||
app.draft_tags.clone(),
|
||||
vec![],
|
||||
replying_to_id,
|
||||
));
|
||||
}
|
||||
None => {
|
||||
let _ = GLOBALS.to_overlord.send(ToOverlordMessage::PostTextNote(
|
||||
app.draft.clone(),
|
||||
app.draft_tags.clone(),
|
||||
));
|
||||
let _ = GLOBALS
|
||||
.to_overlord
|
||||
.send(ToOverlordMessage::PostTextNote(app.draft.clone(), vec![]));
|
||||
}
|
||||
}
|
||||
app.draft = "".to_owned();
|
||||
app.draft_tags = vec![];
|
||||
app.replying_to = None;
|
||||
}
|
||||
|
||||
if ui.button("Cancel").clicked() {
|
||||
app.draft = "".to_owned();
|
||||
app.draft_tags = vec![];
|
||||
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| {
|
||||
for pair in pairs {
|
||||
if ui.button(pair.0).clicked() {
|
||||
let idx = app
|
||||
.draft_tags
|
||||
.iter()
|
||||
.position(|tag| {
|
||||
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));
|
||||
if !app.draft.ends_with(" ") {
|
||||
app.draft.push_str(" ");
|
||||
}
|
||||
app.draft.push_str(&pair.1.try_as_bech32_string().unwrap());
|
||||
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
|
||||
for (i, tag) in app.draft_tags.iter().enumerate() {
|
||||
let rendered = match tag {
|
||||
Tag::Pubkey { pubkey, .. } => {
|
||||
if let Some(person) = GLOBALS.people.get(pubkey) {
|
||||
match person.name {
|
||||
Some(name) => name,
|
||||
None => pubkey.0.clone(),
|
||||
}
|
||||
} else {
|
||||
pubkey.0.clone()
|
||||
}
|
||||
// List tags that will be applied (FIXME: list tags from parent event too in case of reply)
|
||||
for (i, (npub, pubkey)) in keys_from_text(&app.draft).iter().enumerate() {
|
||||
let rendered = if let Some(person) = GLOBALS.people.get(&pubkey.as_hex_string().into()) {
|
||||
match person.name {
|
||||
Some(name) => name,
|
||||
None => npub.to_string(),
|
||||
}
|
||||
_ => serde_json::to_string(tag).unwrap(),
|
||||
} else {
|
||||
npub.to_string()
|
||||
};
|
||||
|
||||
ui.label(format!("{}: {}", i, rendered));
|
||||
}
|
||||
}
|
||||
@ -538,19 +518,6 @@ fn render_post_actual(
|
||||
.clicked()
|
||||
{
|
||||
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);
|
||||
|
@ -19,7 +19,7 @@ use egui::{
|
||||
ColorImage, Context, ImageData, Label, RichText, SelectableLabel, Sense, TextStyle,
|
||||
TextureHandle, TextureOptions, Ui,
|
||||
};
|
||||
use nostr_types::{Id, IdHex, PublicKey, PublicKeyHex, Tag};
|
||||
use nostr_types::{Id, IdHex, PublicKey, PublicKeyHex};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::{Duration, Instant};
|
||||
@ -76,7 +76,6 @@ struct GossipUi {
|
||||
icon: TextureHandle,
|
||||
placeholder_avatar: TextureHandle,
|
||||
draft: String,
|
||||
draft_tags: Vec<Tag>,
|
||||
tag_someone: String,
|
||||
settings: Settings,
|
||||
nip05follow: String,
|
||||
@ -174,7 +173,6 @@ impl GossipUi {
|
||||
icon: icon_texture_handle,
|
||||
placeholder_avatar: placeholder_avatar_texture_handle,
|
||||
draft: "".to_owned(),
|
||||
draft_tags: Vec::new(),
|
||||
tag_someone: "".to_owned(),
|
||||
settings,
|
||||
nip05follow: "".to_owned(),
|
||||
|
Loading…
Reference in New Issue
Block a user