notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

mod.rs (6557B)


      1 mod action;
      2 mod context;
      3 pub mod publish;
      4 
      5 pub use action::{NoteAction, ReactAction, ScrollInfo, ZapAction, ZapTargetAmount};
      6 pub use context::{BroadcastContext, ContextSelection, NoteContextSelection};
      7 pub use publish::{
      8     builder_from_note, send_mute_event, send_people_list_event, send_report_event,
      9     send_unmute_event, ReportTarget, ReportType,
     10 };
     11 
     12 use crate::jobs::MediaJobSender;
     13 use crate::nip05::Nip05Cache;
     14 use crate::Accounts;
     15 use crate::GlobalWallet;
     16 use crate::Localization;
     17 use crate::UnknownIds;
     18 use crate::{notecache::NoteCache, zaps::Zaps, Images};
     19 use enostr::NoteId;
     20 use nostrdb::{Ndb, Note, NoteKey, QueryResult, Transaction};
     21 use std::borrow::Borrow;
     22 use std::cmp::Ordering;
     23 use std::fmt;
     24 
     25 /// Aggregates dependencies to reduce the number of parameters
     26 /// passed to inner UI elements, minimizing prop drilling.
     27 pub struct NoteContext<'d> {
     28     pub ndb: &'d Ndb,
     29     pub accounts: &'d Accounts,
     30     pub global_wallet: &'d GlobalWallet,
     31     pub i18n: &'d mut Localization,
     32     pub img_cache: &'d mut Images,
     33     pub note_cache: &'d mut NoteCache,
     34     pub zaps: &'d mut Zaps,
     35     pub jobs: &'d MediaJobSender,
     36     pub unknown_ids: &'d mut UnknownIds,
     37     pub nip05_cache: &'d mut Nip05Cache,
     38     pub clipboard: &'d mut egui_winit::clipboard::Clipboard,
     39 }
     40 
     41 #[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
     42 pub struct NoteRef {
     43     pub key: NoteKey,
     44     pub created_at: u64,
     45 }
     46 
     47 #[derive(Clone, Copy, Eq, PartialEq, Hash)]
     48 pub struct RootNoteIdBuf([u8; 32]);
     49 
     50 impl fmt::Debug for RootNoteIdBuf {
     51     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
     52         write!(f, "RootNoteIdBuf({})", self.hex())
     53     }
     54 }
     55 
     56 #[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
     57 pub struct RootNoteId<'a>(&'a [u8; 32]);
     58 
     59 impl RootNoteIdBuf {
     60     pub fn to_note_id(self) -> NoteId {
     61         NoteId::new(self.0)
     62     }
     63 
     64     pub fn bytes(&self) -> &[u8; 32] {
     65         &self.0
     66     }
     67 
     68     pub fn new(
     69         ndb: &Ndb,
     70         note_cache: &mut NoteCache,
     71         txn: &Transaction,
     72         id: &[u8; 32],
     73     ) -> Result<RootNoteIdBuf, RootIdError> {
     74         root_note_id_from_selected_id(ndb, note_cache, txn, id).map(|rnid| Self(*rnid.bytes()))
     75     }
     76 
     77     pub fn hex(&self) -> String {
     78         hex::encode(self.bytes())
     79     }
     80 
     81     pub fn new_unsafe(id: [u8; 32]) -> Self {
     82         Self(id)
     83     }
     84 
     85     pub fn borrow(&self) -> RootNoteId<'_> {
     86         RootNoteId(self.bytes())
     87     }
     88 }
     89 
     90 impl<'a> RootNoteId<'a> {
     91     pub fn to_note_id(self) -> NoteId {
     92         NoteId::new(*self.0)
     93     }
     94 
     95     pub fn bytes(&self) -> &[u8; 32] {
     96         self.0
     97     }
     98 
     99     pub fn hex(&self) -> String {
    100         hex::encode(self.bytes())
    101     }
    102 
    103     pub fn to_owned(&self) -> RootNoteIdBuf {
    104         RootNoteIdBuf::new_unsafe(*self.bytes())
    105     }
    106 
    107     pub fn new(
    108         ndb: &Ndb,
    109         note_cache: &mut NoteCache,
    110         txn: &'a Transaction,
    111         id: &'a [u8; 32],
    112     ) -> Result<RootNoteId<'a>, RootIdError> {
    113         root_note_id_from_selected_id(ndb, note_cache, txn, id)
    114     }
    115 
    116     pub fn new_unsafe(id: &'a [u8; 32]) -> Self {
    117         Self(id)
    118     }
    119 }
    120 
    121 impl Borrow<[u8; 32]> for RootNoteIdBuf {
    122     fn borrow(&self) -> &[u8; 32] {
    123         &self.0
    124     }
    125 }
    126 
    127 impl Borrow<[u8; 32]> for RootNoteId<'_> {
    128     fn borrow(&self) -> &[u8; 32] {
    129         self.0
    130     }
    131 }
    132 
    133 impl NoteRef {
    134     pub fn new(key: NoteKey, created_at: u64) -> Self {
    135         NoteRef { key, created_at }
    136     }
    137 
    138     pub fn from_note(note: &Note<'_>) -> Self {
    139         let created_at = note.created_at();
    140         let key = note.key().expect("todo: implement NoteBuf");
    141         NoteRef::new(key, created_at)
    142     }
    143 
    144     pub fn from_query_result(qr: QueryResult<'_>) -> Self {
    145         NoteRef {
    146             key: qr.note_key,
    147             created_at: qr.note.created_at(),
    148         }
    149     }
    150 }
    151 
    152 impl Ord for NoteRef {
    153     fn cmp(&self, other: &Self) -> Ordering {
    154         match self.created_at.cmp(&other.created_at) {
    155             Ordering::Equal => self.key.cmp(&other.key),
    156             Ordering::Less => Ordering::Greater,
    157             Ordering::Greater => Ordering::Less,
    158         }
    159     }
    160 }
    161 
    162 impl PartialOrd for NoteRef {
    163     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
    164         Some(self.cmp(other))
    165     }
    166 }
    167 
    168 #[derive(Debug, Copy, Clone)]
    169 pub enum RootIdError {
    170     NoteNotFound,
    171     NoRootId,
    172 }
    173 
    174 pub fn root_note_id_from_selected_id<'txn, 'a>(
    175     ndb: &Ndb,
    176     note_cache: &mut NoteCache,
    177     txn: &'txn Transaction,
    178     selected_note_id: &'a [u8; 32],
    179 ) -> Result<RootNoteId<'txn>, RootIdError>
    180 where
    181     'a: 'txn,
    182 {
    183     let selected_note_key = if let Ok(key) = ndb.get_notekey_by_id(txn, selected_note_id) {
    184         key
    185     } else {
    186         return Err(RootIdError::NoteNotFound);
    187     };
    188 
    189     let note = if let Ok(note) = ndb.get_note_by_key(txn, selected_note_key) {
    190         note
    191     } else {
    192         return Err(RootIdError::NoteNotFound);
    193     };
    194 
    195     note_cache
    196         .cached_note_or_insert(selected_note_key, &note)
    197         .reply
    198         .borrow(note.tags())
    199         .root()
    200         .map_or_else(
    201             || Ok(RootNoteId::new_unsafe(selected_note_id)),
    202             |rnid| Ok(RootNoteId::new_unsafe(rnid.id)),
    203         )
    204 }
    205 
    206 pub fn event_tag<'a>(ev: &nostrdb::Note<'a>, name: &str) -> Option<&'a str> {
    207     ev.tags().iter().find_map(|tag| {
    208         if tag.count() < 2 {
    209             return None;
    210         }
    211 
    212         let cur_name = tag.get_str(0)?;
    213 
    214         if cur_name != name {
    215             return None;
    216         }
    217 
    218         tag.get_str(1)
    219     })
    220 }
    221 
    222 /// Temporary way of checking whether a user has sent a reaction.
    223 /// Should be replaced with nostrdb metadata
    224 pub fn reaction_sent_id(sender_pk: &enostr::Pubkey, note_reacted_to: &[u8; 32]) -> egui::Id {
    225     egui::Id::new(("sent-reaction-id", note_reacted_to, sender_pk))
    226 }
    227 
    228 /// Count the number of hashtags in a note by examining its tags
    229 pub fn count_hashtags(note: &Note) -> usize {
    230     let mut count = 0;
    231 
    232     for tag in note.tags() {
    233         // Early continue if not enough elements
    234         if tag.count() < 2 {
    235             continue;
    236         }
    237 
    238         // Check if this is a hashtag tag (type "t")
    239         let Some("t") = tag.get_unchecked(0).variant().str() else {
    240             continue;
    241         };
    242 
    243         count += 1;
    244     }
    245 
    246     count
    247 }
    248 
    249 pub fn get_p_tags<'a>(note: &Note<'a>) -> Vec<&'a [u8; 32]> {
    250     let mut items = Vec::new();
    251     for tag in note.tags() {
    252         if tag.count() < 2 {
    253             continue;
    254         }
    255 
    256         if tag.get_str(0) != Some("p") {
    257             continue;
    258         }
    259 
    260         let Some(item) = tag.get_id(1) else {
    261             continue;
    262         };
    263 
    264         items.push(item);
    265     }
    266 
    267     items
    268 }