ui: move note and profile rendering to notedeck_ui
We want to render notes in other apps like dave, so lets move our note rendering to notedeck_ui. We rework NoteAction so it doesn't have anything specific to notedeck_columns Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
20
crates/notedeck/src/abbrev.rs
Normal file
20
crates/notedeck/src/abbrev.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
#[inline]
|
||||
pub fn floor_char_boundary(s: &str, index: usize) -> usize {
|
||||
if index >= s.len() {
|
||||
s.len()
|
||||
} else {
|
||||
let lower_bound = index.saturating_sub(3);
|
||||
let new_index = s.as_bytes()[lower_bound..=index]
|
||||
.iter()
|
||||
.rposition(|b| is_utf8_char_boundary(*b));
|
||||
|
||||
// SAFETY: we know that the character boundary will be within four bytes
|
||||
unsafe { lower_bound + new_index.unwrap_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_utf8_char_boundary(c: u8) -> bool {
|
||||
// This is bit magic equivalent to: b < 128 || b >= 192
|
||||
(c as i8) >= -0x40
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod abbrev;
|
||||
mod accounts;
|
||||
mod app;
|
||||
mod args;
|
||||
@@ -9,10 +10,12 @@ pub mod fonts;
|
||||
mod frame_history;
|
||||
mod imgcache;
|
||||
mod muted;
|
||||
pub mod name;
|
||||
pub mod note;
|
||||
mod notecache;
|
||||
mod persist;
|
||||
pub mod platform;
|
||||
pub mod profile;
|
||||
pub mod relay_debug;
|
||||
pub mod relayspec;
|
||||
mod result;
|
||||
@@ -41,9 +44,14 @@ pub use imgcache::{
|
||||
MediaCacheValue, TextureFrame, TexturedImage,
|
||||
};
|
||||
pub use muted::{MuteFun, Muted};
|
||||
pub use note::{NoteRef, RootIdError, RootNoteId, RootNoteIdBuf};
|
||||
pub use name::NostrName;
|
||||
pub use note::{
|
||||
BroadcastContext, ContextSelection, NoteAction, NoteContext, NoteContextSelection, NoteRef,
|
||||
RootIdError, RootNoteId, RootNoteIdBuf, ZapAction,
|
||||
};
|
||||
pub use notecache::{CachedNote, NoteCache};
|
||||
pub use persist::*;
|
||||
pub use profile::get_profile_url;
|
||||
pub use relay_debug::RelayDebugView;
|
||||
pub use relayspec::RelaySpec;
|
||||
pub use result::Result;
|
||||
|
||||
64
crates/notedeck/src/name.rs
Normal file
64
crates/notedeck/src/name.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use nostrdb::ProfileRecord;
|
||||
|
||||
pub struct NostrName<'a> {
|
||||
pub username: Option<&'a str>,
|
||||
pub display_name: Option<&'a str>,
|
||||
pub nip05: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> NostrName<'a> {
|
||||
pub fn name(&self) -> &'a str {
|
||||
if let Some(name) = self.username {
|
||||
name
|
||||
} else if let Some(name) = self.display_name {
|
||||
name
|
||||
} else {
|
||||
self.nip05.unwrap_or("??")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unknown() -> Self {
|
||||
Self {
|
||||
username: None,
|
||||
display_name: None,
|
||||
nip05: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty(s: &str) -> bool {
|
||||
s.chars().all(|c| c.is_whitespace())
|
||||
}
|
||||
|
||||
pub fn get_display_name<'a>(record: Option<&ProfileRecord<'a>>) -> NostrName<'a> {
|
||||
let Some(record) = record else {
|
||||
return NostrName::unknown();
|
||||
};
|
||||
|
||||
let Some(profile) = record.record().profile() else {
|
||||
return NostrName::unknown();
|
||||
};
|
||||
|
||||
let display_name = profile.display_name().filter(|n| !is_empty(n));
|
||||
let username = profile.name().filter(|n| !is_empty(n));
|
||||
|
||||
let nip05 = if let Some(raw_nip05) = profile.nip05() {
|
||||
if let Some(at_pos) = raw_nip05.find('@') {
|
||||
if raw_nip05.starts_with('_') {
|
||||
raw_nip05.get(at_pos + 1..)
|
||||
} else {
|
||||
Some(raw_nip05)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
NostrName {
|
||||
username,
|
||||
display_name,
|
||||
nip05,
|
||||
}
|
||||
}
|
||||
33
crates/notedeck/src/note/action.rs
Normal file
33
crates/notedeck/src/note/action.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use super::context::ContextSelection;
|
||||
use crate::zaps::NoteZapTargetOwned;
|
||||
use enostr::{NoteId, Pubkey};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub enum NoteAction {
|
||||
/// User has clicked the quote reply action
|
||||
Reply(NoteId),
|
||||
|
||||
/// User has clicked the quote repost action
|
||||
Quote(NoteId),
|
||||
|
||||
/// User has clicked a hashtag
|
||||
Hashtag(String),
|
||||
|
||||
/// User has clicked a profile
|
||||
Profile(Pubkey),
|
||||
|
||||
/// User has clicked a note link
|
||||
Note(NoteId),
|
||||
|
||||
/// User has selected some context option
|
||||
Context(ContextSelection),
|
||||
|
||||
/// User has clicked the zap action
|
||||
Zap(ZapAction),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub enum ZapAction {
|
||||
Send(NoteZapTargetOwned),
|
||||
ClearError(NoteZapTargetOwned),
|
||||
}
|
||||
63
crates/notedeck/src/note/context.rs
Normal file
63
crates/notedeck/src/note/context.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use enostr::{ClientMessage, NoteId, Pubkey, RelayPool};
|
||||
use nostrdb::{Note, NoteKey};
|
||||
use tracing::error;
|
||||
|
||||
/// When broadcasting notes, this determines whether to broadcast
|
||||
/// over the local network via multicast, or globally
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum BroadcastContext {
|
||||
LocalNetwork,
|
||||
Everywhere,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum NoteContextSelection {
|
||||
CopyText,
|
||||
CopyPubkey,
|
||||
CopyNoteId,
|
||||
CopyNoteJSON,
|
||||
Broadcast(BroadcastContext),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub struct ContextSelection {
|
||||
pub note_key: NoteKey,
|
||||
pub action: NoteContextSelection,
|
||||
}
|
||||
|
||||
impl NoteContextSelection {
|
||||
pub fn process(&self, ui: &mut egui::Ui, note: &Note<'_>, pool: &mut RelayPool) {
|
||||
match self {
|
||||
NoteContextSelection::Broadcast(context) => {
|
||||
tracing::info!("Broadcasting note {}", hex::encode(note.id()));
|
||||
match context {
|
||||
BroadcastContext::LocalNetwork => {
|
||||
pool.send_to(&ClientMessage::event(note).unwrap(), "multicast");
|
||||
}
|
||||
|
||||
BroadcastContext::Everywhere => {
|
||||
pool.send(&ClientMessage::event(note).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
NoteContextSelection::CopyText => {
|
||||
ui.ctx().copy_text(note.content().to_string());
|
||||
}
|
||||
NoteContextSelection::CopyPubkey => {
|
||||
if let Some(bech) = Pubkey::new(*note.pubkey()).to_bech() {
|
||||
ui.ctx().copy_text(bech);
|
||||
}
|
||||
}
|
||||
NoteContextSelection::CopyNoteId => {
|
||||
if let Some(bech) = NoteId::new(*note.id()).to_bech() {
|
||||
ui.ctx().copy_text(bech);
|
||||
}
|
||||
}
|
||||
NoteContextSelection::CopyNoteJSON => match note.json() {
|
||||
Ok(json) => ui.ctx().copy_text(json),
|
||||
Err(err) => error!("error copying note json: {err}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,26 @@
|
||||
use crate::notecache::NoteCache;
|
||||
use enostr::NoteId;
|
||||
mod action;
|
||||
mod context;
|
||||
|
||||
pub use action::{NoteAction, ZapAction};
|
||||
pub use context::{BroadcastContext, ContextSelection, NoteContextSelection};
|
||||
|
||||
use crate::{notecache::NoteCache, zaps::Zaps, Images};
|
||||
use enostr::{NoteId, RelayPool};
|
||||
use nostrdb::{Ndb, Note, NoteKey, QueryResult, Transaction};
|
||||
use std::borrow::Borrow;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
|
||||
/// Aggregates dependencies to reduce the number of parameters
|
||||
/// passed to inner UI elements, minimizing prop drilling.
|
||||
pub struct NoteContext<'d> {
|
||||
pub ndb: &'d Ndb,
|
||||
pub img_cache: &'d mut Images,
|
||||
pub note_cache: &'d mut NoteCache,
|
||||
pub zaps: &'d mut Zaps,
|
||||
pub pool: &'d mut RelayPool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
|
||||
pub struct NoteRef {
|
||||
pub key: NoteKey,
|
||||
18
crates/notedeck/src/profile.rs
Normal file
18
crates/notedeck/src/profile.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use nostrdb::ProfileRecord;
|
||||
|
||||
pub fn get_profile_url<'a>(profile: Option<&ProfileRecord<'a>>) -> &'a str {
|
||||
unwrap_profile_url(profile.and_then(|pr| pr.record().profile().and_then(|p| p.picture())))
|
||||
}
|
||||
|
||||
pub fn unwrap_profile_url(maybe_url: Option<&str>) -> &str {
|
||||
if let Some(url) = maybe_url {
|
||||
url
|
||||
} else {
|
||||
no_pfp_url()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn no_pfp_url() -> &'static str {
|
||||
"https://damus.io/img/no-profile.svg"
|
||||
}
|
||||
Reference in New Issue
Block a user