nip10: show initial reply information on notes
Using the newly merged nip10 code, we can show replies on notes! This is not final, and it's actually different than how we do it in Damus iOS. Not sure if I like it yet. We will likely have to put pubkey information back in somewhere soon. Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -2556,8 +2556,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostrdb"
|
name = "nostrdb"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
source = "git+https://github.com/damus-io/nostrdb-rs?rev=1489a5aee49996d8a6a54b4c3c9c8397e3e8d40f#1489a5aee49996d8a6a54b4c3c9c8397e3e8d40f"
|
source = "git+https://github.com/damus-io/nostrdb-rs?rev=99d8296fcba5957245ed883e2f3b1c0d1cb16397#99d8296fcba5957245ed883e2f3b1c0d1cb16397"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"cc",
|
"cc",
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ serde_json = "1.0.89"
|
|||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
puffin_egui = { version = "0.27.0", optional = true }
|
puffin_egui = { version = "0.27.0", optional = true }
|
||||||
puffin = { version = "0.19.0", optional = true }
|
puffin = { version = "0.19.0", optional = true }
|
||||||
nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "1489a5aee49996d8a6a54b4c3c9c8397e3e8d40f" }
|
nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "99d8296fcba5957245ed883e2f3b1c0d1cb16397" }
|
||||||
#nostrdb = "0.3.2"
|
#nostrdb = "0.3.3"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
base32 = "0.4.0"
|
base32 = "0.4.0"
|
||||||
nostr-sdk = "0.29.0"
|
nostr-sdk = "0.29.0"
|
||||||
|
|||||||
@@ -499,10 +499,10 @@ impl Damus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_note_cache_mut(&mut self, note_key: NoteKey, created_at: u64) -> &mut NoteCache {
|
pub fn get_note_cache_mut(&mut self, note_key: NoteKey, note: &Note<'_>) -> &mut NoteCache {
|
||||||
self.note_cache
|
self.note_cache
|
||||||
.entry(note_key)
|
.entry(note_key)
|
||||||
.or_insert_with(|| NoteCache::new(created_at))
|
.or_insert_with(|| NoteCache::new(note))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,28 @@
|
|||||||
use crate::time::time_ago_since;
|
use crate::time::time_ago_since;
|
||||||
use crate::timecache::TimeCached;
|
use crate::timecache::TimeCached;
|
||||||
|
use nostrdb::{Note, NoteReply, NoteReplyBuf};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub struct NoteCache {
|
pub struct NoteCache {
|
||||||
reltime: TimeCached<String>,
|
reltime: TimeCached<String>,
|
||||||
|
pub reply: NoteReplyBuf,
|
||||||
pub bar_open: bool,
|
pub bar_open: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NoteCache {
|
impl NoteCache {
|
||||||
pub fn new(created_at: u64) -> Self {
|
pub fn new(note: &Note<'_>) -> Self {
|
||||||
|
let created_at = note.created_at();
|
||||||
let reltime = TimeCached::new(
|
let reltime = TimeCached::new(
|
||||||
Duration::from_secs(1),
|
Duration::from_secs(1),
|
||||||
Box::new(move || time_ago_since(created_at)),
|
Box::new(move || time_ago_since(created_at)),
|
||||||
);
|
);
|
||||||
|
let reply = NoteReply::new(note.tags()).to_owned();
|
||||||
let bar_open = false;
|
let bar_open = false;
|
||||||
NoteCache { reltime, bar_open }
|
NoteCache {
|
||||||
|
reltime,
|
||||||
|
reply,
|
||||||
|
bar_open,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reltime_str(&mut self) -> &str {
|
pub fn reltime_str(&mut self) -> &str {
|
||||||
|
|||||||
61
src/ui/mention.rs
Normal file
61
src/ui/mention.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use crate::{colors, ui, Damus};
|
||||||
|
use nostrdb::Transaction;
|
||||||
|
|
||||||
|
pub struct Mention<'a> {
|
||||||
|
app: &'a mut Damus,
|
||||||
|
txn: &'a Transaction,
|
||||||
|
pk: &'a [u8; 32],
|
||||||
|
size: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Mention<'a> {
|
||||||
|
pub fn new(app: &'a mut Damus, txn: &'a Transaction, pk: &'a [u8; 32]) -> Self {
|
||||||
|
let size = 16.0;
|
||||||
|
Mention { app, txn, pk, size }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(mut self, size: f32) -> Self {
|
||||||
|
self.size = size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> egui::Widget for Mention<'a> {
|
||||||
|
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
|
||||||
|
mention_ui(self.app, self.txn, self.pk, ui, self.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mention_ui(
|
||||||
|
app: &mut Damus,
|
||||||
|
txn: &Transaction,
|
||||||
|
pk: &[u8; 32],
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
size: f32,
|
||||||
|
) -> egui::Response {
|
||||||
|
#[cfg(feature = "profiling")]
|
||||||
|
puffin::profile_function!();
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let profile = app.ndb.get_profile_by_pubkey(txn, pk).ok();
|
||||||
|
|
||||||
|
let name: String =
|
||||||
|
if let Some(name) = profile.as_ref().and_then(crate::profile::get_profile_name) {
|
||||||
|
format!("@{}", name.username())
|
||||||
|
} else {
|
||||||
|
"??".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let resp = ui.add(egui::Label::new(
|
||||||
|
egui::RichText::new(name).color(colors::PURPLE).size(size),
|
||||||
|
));
|
||||||
|
|
||||||
|
if let Some(rec) = profile.as_ref() {
|
||||||
|
resp.on_hover_ui_at_pointer(|ui| {
|
||||||
|
ui.set_max_width(300.0);
|
||||||
|
ui.add(ui::ProfilePreview::new(rec, &mut app.img_cache));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.response
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
pub mod anim;
|
pub mod anim;
|
||||||
|
pub mod mention;
|
||||||
pub mod note;
|
pub mod note;
|
||||||
pub mod preview;
|
pub mod preview;
|
||||||
pub mod profile;
|
pub mod profile;
|
||||||
pub mod relay;
|
pub mod relay;
|
||||||
pub mod username;
|
pub mod username;
|
||||||
|
|
||||||
|
pub use mention::Mention;
|
||||||
pub use note::Note;
|
pub use note::Note;
|
||||||
pub use preview::{Preview, PreviewApp};
|
pub use preview::{Preview, PreviewApp};
|
||||||
pub use profile::{ProfilePic, ProfilePreview};
|
pub use profile::{ProfilePic, ProfilePreview};
|
||||||
|
|||||||
@@ -97,29 +97,6 @@ fn render_note_preview(
|
|||||||
.response
|
.response
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mention_ui(app: &mut Damus, txn: &Transaction, pk: &[u8; 32], ui: &mut egui::Ui) {
|
|
||||||
#[cfg(feature = "profiling")]
|
|
||||||
puffin::profile_function!();
|
|
||||||
|
|
||||||
let profile = app.ndb.get_profile_by_pubkey(txn, pk).ok();
|
|
||||||
|
|
||||||
let name: String =
|
|
||||||
if let Some(name) = profile.as_ref().and_then(crate::profile::get_profile_name) {
|
|
||||||
format!("@{}", name.username())
|
|
||||||
} else {
|
|
||||||
"??".to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
let resp = ui.colored_label(colors::PURPLE, &name);
|
|
||||||
|
|
||||||
if let Some(rec) = profile.as_ref() {
|
|
||||||
resp.on_hover_ui_at_pointer(|ui| {
|
|
||||||
ui.set_max_width(300.0);
|
|
||||||
ui.add(ui::ProfilePreview::new(rec, &mut app.img_cache));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_note_contents(
|
fn render_note_contents(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
damus: &mut Damus,
|
damus: &mut Damus,
|
||||||
@@ -149,11 +126,11 @@ fn render_note_contents(
|
|||||||
match block.blocktype() {
|
match block.blocktype() {
|
||||||
BlockType::MentionBech32 => match block.as_mention().unwrap() {
|
BlockType::MentionBech32 => match block.as_mention().unwrap() {
|
||||||
Mention::Profile(profile) => {
|
Mention::Profile(profile) => {
|
||||||
mention_ui(damus, txn, profile.pubkey(), ui);
|
ui.add(ui::Mention::new(damus, txn, profile.pubkey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Mention::Pubkey(npub) => {
|
Mention::Pubkey(npub) => {
|
||||||
mention_ui(damus, txn, npub.pubkey(), ui);
|
ui.add(ui::Mention::new(damus, txn, npub.pubkey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Mention::Note(note) if options.has_note_previews() => {
|
Mention::Note(note) if options.has_note_previews() => {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ pub use options::NoteOptions;
|
|||||||
|
|
||||||
use crate::{colors, ui, Damus};
|
use crate::{colors, ui, Damus};
|
||||||
use egui::{Label, RichText, Sense};
|
use egui::{Label, RichText, Sense};
|
||||||
|
use nostrdb::{NoteKey, Transaction};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
pub struct Note<'a> {
|
pub struct Note<'a> {
|
||||||
@@ -38,6 +39,90 @@ impl Hash for ProfileAnimId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reply_desc(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
txn: &Transaction,
|
||||||
|
app: &mut Damus,
|
||||||
|
note_key: NoteKey,
|
||||||
|
note: &nostrdb::Note<'_>,
|
||||||
|
) {
|
||||||
|
#[cfg(feature = "profiling")]
|
||||||
|
puffin::profile_function!();
|
||||||
|
|
||||||
|
let note_reply = app
|
||||||
|
.get_note_cache_mut(note_key, note)
|
||||||
|
.reply
|
||||||
|
.borrow(note.tags());
|
||||||
|
|
||||||
|
let reply = if let Some(reply) = note_reply.reply() {
|
||||||
|
reply
|
||||||
|
} else {
|
||||||
|
// not a reply, nothing to do here
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.add(Label::new(
|
||||||
|
RichText::new("replying to")
|
||||||
|
.size(10.0)
|
||||||
|
.color(colors::GRAY_SECONDARY),
|
||||||
|
));
|
||||||
|
|
||||||
|
let reply_note = if let Ok(reply_note) = app.ndb.get_note_by_id(txn, reply.id) {
|
||||||
|
reply_note
|
||||||
|
} else {
|
||||||
|
ui.add(Label::new(
|
||||||
|
RichText::new("a note")
|
||||||
|
.size(10.0)
|
||||||
|
.color(colors::GRAY_SECONDARY),
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if note_reply.is_reply_to_root() {
|
||||||
|
// We're replying to the root, let's show this
|
||||||
|
ui.add(ui::Mention::new(app, txn, reply_note.pubkey()).size(10.0));
|
||||||
|
ui.add(Label::new(
|
||||||
|
RichText::new("'s note")
|
||||||
|
.size(10.0)
|
||||||
|
.color(colors::GRAY_SECONDARY),
|
||||||
|
));
|
||||||
|
} else if let Some(root) = note_reply.root() {
|
||||||
|
// replying to another post in a thread, not the root
|
||||||
|
|
||||||
|
if let Ok(root_note) = app.ndb.get_note_by_id(txn, root.id) {
|
||||||
|
if root_note.pubkey() == reply_note.pubkey() {
|
||||||
|
// simply "replying to bob's note" when replying to bob in his thread
|
||||||
|
ui.add(ui::Mention::new(app, txn, reply_note.pubkey()).size(10.0));
|
||||||
|
ui.add(Label::new(
|
||||||
|
RichText::new("'s note")
|
||||||
|
.size(10.0)
|
||||||
|
.color(colors::GRAY_SECONDARY),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
// replying to bob in alice's thread
|
||||||
|
|
||||||
|
ui.add(ui::Mention::new(app, txn, reply_note.pubkey()).size(10.0));
|
||||||
|
ui.add(Label::new(
|
||||||
|
RichText::new("in").size(10.0).color(colors::GRAY_SECONDARY),
|
||||||
|
));
|
||||||
|
ui.add(ui::Mention::new(app, txn, root_note.pubkey()).size(10.0));
|
||||||
|
ui.add(Label::new(
|
||||||
|
RichText::new("'s thread")
|
||||||
|
.size(10.0)
|
||||||
|
.color(colors::GRAY_SECONDARY),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ui.add(ui::Mention::new(app, txn, reply_note.pubkey()).size(10.0));
|
||||||
|
ui.add(Label::new(
|
||||||
|
RichText::new("in someone's thread")
|
||||||
|
.size(10.0)
|
||||||
|
.color(colors::GRAY_SECONDARY),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Note<'a> {
|
impl<'a> Note<'a> {
|
||||||
pub fn new(app: &'a mut Damus, note: &'a nostrdb::Note<'a>) -> Self {
|
pub fn new(app: &'a mut Damus, note: &'a nostrdb::Note<'a>) -> Self {
|
||||||
let flags = NoteOptions::actionbar | NoteOptions::note_previews;
|
let flags = NoteOptions::actionbar | NoteOptions::note_previews;
|
||||||
@@ -69,32 +154,30 @@ impl<'a> Note<'a> {
|
|||||||
ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| {
|
ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| {
|
||||||
let profile = self.app.ndb.get_profile_by_pubkey(txn, self.note.pubkey());
|
let profile = self.app.ndb.get_profile_by_pubkey(txn, self.note.pubkey());
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
//ui.horizontal(|ui| {
|
||||||
ui.spacing_mut().item_spacing.x = 2.0;
|
ui.spacing_mut().item_spacing.x = 2.0;
|
||||||
|
|
||||||
let note_cache = self
|
let note_cache = self.app.get_note_cache_mut(note_key, self.note);
|
||||||
.app
|
|
||||||
.get_note_cache_mut(note_key, self.note.created_at());
|
|
||||||
|
|
||||||
let (_id, rect) = ui.allocate_space(egui::vec2(50.0, 20.0));
|
let (_id, rect) = ui.allocate_space(egui::vec2(50.0, 20.0));
|
||||||
ui.allocate_rect(rect, Sense::hover());
|
ui.allocate_rect(rect, Sense::hover());
|
||||||
ui.put(rect, |ui: &mut egui::Ui| {
|
ui.put(rect, |ui: &mut egui::Ui| {
|
||||||
render_reltime(ui, note_cache, false).response
|
render_reltime(ui, note_cache, false).response
|
||||||
});
|
|
||||||
let (_id, rect) = ui.allocate_space(egui::vec2(150.0, 20.0));
|
|
||||||
ui.allocate_rect(rect, Sense::hover());
|
|
||||||
ui.put(rect, |ui: &mut egui::Ui| {
|
|
||||||
ui.add(
|
|
||||||
ui::Username::new(profile.as_ref().ok(), self.note.pubkey())
|
|
||||||
.abbreviated(8)
|
|
||||||
.pk_colored(true),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.add(NoteContents::new(
|
|
||||||
self.app, txn, self.note, note_key, self.flags,
|
|
||||||
));
|
|
||||||
});
|
});
|
||||||
|
let (_id, rect) = ui.allocate_space(egui::vec2(150.0, 20.0));
|
||||||
|
ui.allocate_rect(rect, Sense::hover());
|
||||||
|
ui.put(rect, |ui: &mut egui::Ui| {
|
||||||
|
ui.add(
|
||||||
|
ui::Username::new(profile.as_ref().ok(), self.note.pubkey())
|
||||||
|
.abbreviated(8)
|
||||||
|
.pk_colored(true),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add(NoteContents::new(
|
||||||
|
self.app, txn, self.note, note_key, self.flags,
|
||||||
|
));
|
||||||
|
//});
|
||||||
})
|
})
|
||||||
.response
|
.response
|
||||||
}
|
}
|
||||||
@@ -163,12 +246,15 @@ impl<'a> Note<'a> {
|
|||||||
.abbreviated(20),
|
.abbreviated(20),
|
||||||
);
|
);
|
||||||
|
|
||||||
let note_cache = self
|
let note_cache = self.app.get_note_cache_mut(note_key, self.note);
|
||||||
.app
|
|
||||||
.get_note_cache_mut(note_key, self.note.created_at());
|
|
||||||
render_reltime(ui, note_cache, true);
|
render_reltime(ui, note_cache, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.spacing_mut().item_spacing.x = 2.0;
|
||||||
|
reply_desc(ui, txn, self.app, note_key, self.note);
|
||||||
|
});
|
||||||
|
|
||||||
ui.add(NoteContents::new(
|
ui.add(NoteContents::new(
|
||||||
self.app,
|
self.app,
|
||||||
txn,
|
txn,
|
||||||
@@ -210,6 +296,12 @@ fn render_note_actionbar(ui: &mut egui::Ui) -> egui::InnerResponse<()> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn secondary_label(ui: &mut egui::Ui, s: impl Into<String>) {
|
||||||
|
ui.add(Label::new(
|
||||||
|
RichText::new(s).size(10.0).color(colors::GRAY_SECONDARY),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
fn render_reltime(
|
fn render_reltime(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
note_cache: &mut crate::notecache::NoteCache,
|
note_cache: &mut crate::notecache::NoteCache,
|
||||||
@@ -220,19 +312,13 @@ fn render_reltime(
|
|||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if before {
|
if before {
|
||||||
ui.add(Label::new(
|
secondary_label(ui, "⋅");
|
||||||
RichText::new("⋅").size(10.0).color(colors::GRAY_SECONDARY),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
ui.add(Label::new(
|
|
||||||
RichText::new(note_cache.reltime_str())
|
secondary_label(ui, note_cache.reltime_str());
|
||||||
.size(10.0)
|
|
||||||
.color(colors::GRAY_SECONDARY),
|
|
||||||
));
|
|
||||||
if !before {
|
if !before {
|
||||||
ui.add(Label::new(
|
secondary_label(ui, "⋅");
|
||||||
RichText::new("⋅").size(10.0).color(colors::GRAY_SECONDARY),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,13 +115,16 @@ fn add_relay_button() -> egui::Button<'static> {
|
|||||||
Button::new("+ Add relay").min_size(Vec2::new(0.0, 32.0))
|
Button::new("+ Add relay").min_size(Vec2::new(0.0, 32.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_button(dark_mode: bool) -> egui::Button<'static> {
|
fn delete_button(_dark_mode: bool) -> egui::Button<'static> {
|
||||||
|
/*
|
||||||
let img_data = if dark_mode {
|
let img_data = if dark_mode {
|
||||||
egui::include_image!("../../assets/icons/delete_icon_4x.png")
|
egui::include_image!("../../assets/icons/delete_icon_4x.png")
|
||||||
} else {
|
} else {
|
||||||
// TODO: use light delete icon
|
// TODO: use light delete icon
|
||||||
egui::include_image!("../../assets/icons/delete_icon_4x.png")
|
egui::include_image!("../../assets/icons/delete_icon_4x.png")
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
let img_data = egui::include_image!("../../assets/icons/delete_icon_4x.png");
|
||||||
|
|
||||||
egui::Button::image(egui::Image::new(img_data).max_width(10.0)).frame(false)
|
egui::Button::image(egui::Image::new(img_data).max_width(10.0)).frame(false)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user