diff --git a/Cargo.lock b/Cargo.lock index 9060f12da..9e9896371 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4545,6 +4545,7 @@ name = "tokenator" version = "0.1.0" dependencies = [ "hex", + "nostrdb", ] [[package]] diff --git a/crates/notedeck_columns/src/decks.rs b/crates/notedeck_columns/src/decks.rs index 903b8f3d4..64489d97f 100644 --- a/crates/notedeck_columns/src/decks.rs +++ b/crates/notedeck_columns/src/decks.rs @@ -1,8 +1,9 @@ use std::collections::{hash_map::ValuesMut, HashMap}; use enostr::Pubkey; -use nostrdb::Transaction; +use nostrdb::{Note, NoteBuilder, Transaction}; use notedeck::AppContext; +use tokenator::{NoteTokenWriter, TokenBuffer, TokenWriter}; use tracing::{error, info}; use crate::{ @@ -301,6 +302,37 @@ impl Deck { self.name = changes.name; self.icon = changes.icon; } + + pub fn to_note(&self, seckey: &[u8; 32]) -> Note { + let mut builder = NoteBuilder::new(); + builder = builder + .start_tag() + .tag_str("name") + .tag_str(&self.name) + .start_tag() + .tag_str("icon") + .tag_str(self.icon.to_string().as_str()); + + let mut column_writers = Vec::new(); + for column in self.columns.columns() { + if let Some(route) = column.router().first().filter(|r| r.exportable_to_note()) { + let mut writer = TokenWriter::new(TokenBuffer::ToNote(NoteTokenWriter::default())); + route.serialize_tokens(&mut writer); + column_writers.push(writer); + } + } + + for writer in &column_writers { + if let TokenBuffer::ToNote(buf) = &writer.buf { + builder = buf.to_builder(builder); + } + } + + builder + .sign(seckey) + .build() + .expect("failed to build Deck note") + } } pub fn demo_decks( diff --git a/crates/notedeck_columns/src/route.rs b/crates/notedeck_columns/src/route.rs index 996406a1a..a122cf310 100644 --- a/crates/notedeck_columns/src/route.rs +++ b/crates/notedeck_columns/src/route.rs @@ -225,6 +225,37 @@ impl Route { Route::EditProfile(_) => ColumnTitle::simple("Edit Profile"), } } + + pub fn exportable_to_note(&self) -> bool { + match &self { + Route::Timeline(timeline_kind) => match timeline_kind { + TimelineKind::List(list_kind) => match list_kind { + ListKind::Contact(_) => true, + }, + TimelineKind::Algo(algo) => match algo { + AlgoTimeline::LastPerPubkey(list_kind) => match list_kind { + ListKind::Contact(_) => true, + }, + }, + TimelineKind::Notifications(_) => true, + TimelineKind::Profile(_) => true, + TimelineKind::Thread(_) => false, + TimelineKind::Universe => true, + TimelineKind::Generic(_) => false, + TimelineKind::Hashtag(_) => true, + }, + Route::Accounts(_) => false, + Route::Reply(_) => false, + Route::Quote(_) => false, + Route::Relays => false, + Route::ComposeNote => false, + Route::AddColumn(_) => false, + Route::EditProfile(_) => false, + Route::Support => false, + Route::NewDeck => false, + Route::EditDeck(_) => false, + } + } } // TODO: add this to egui-nav so we don't have to deal with returning @@ -309,6 +340,10 @@ impl Router { pub fn routes(&self) -> &Vec { &self.routes } + + pub fn first(&self) -> Option<&R> { + self.routes.first() + } } impl fmt::Display for Route { diff --git a/crates/tokenator/Cargo.toml b/crates/tokenator/Cargo.toml index a1ca14afa..c82493b8f 100644 --- a/crates/tokenator/Cargo.toml +++ b/crates/tokenator/Cargo.toml @@ -6,3 +6,4 @@ description = "A simple library for parsing a serializing string tokens" [dependencies] hex = { workspace = true } +nostrdb = { workspace = true } diff --git a/crates/tokenator/src/lib.rs b/crates/tokenator/src/lib.rs index 103314aa7..a5141d35e 100644 --- a/crates/tokenator/src/lib.rs +++ b/crates/tokenator/src/lib.rs @@ -25,18 +25,21 @@ pub enum ParseError<'a> { } pub struct TokenWriter { - delim: &'static str, - tokens_written: usize, - buf: Vec, + pub buf: TokenBuffer, } -impl Default for TokenWriter { - fn default() -> Self { - Self::new(":") - } +pub enum TokenBuffer { + ToString(StrTokenWriter), + ToNote(NoteTokenWriter), } -impl TokenWriter { +pub struct StrTokenWriter { + pub delim: &'static str, + pub tokens_written: usize, + pub buf: Vec, +} + +impl StrTokenWriter { pub fn new(delim: &'static str) -> Self { let buf = vec![]; let tokens_written = 0; @@ -66,6 +69,81 @@ impl TokenWriter { } } +const DELIM: &str = ":"; + +pub struct NoteTokenWriter { + pub delim: &'static str, + pub buf: Vec, +} + +impl Default for NoteTokenWriter { + fn default() -> Self { + Self { + delim: DELIM, + buf: Default::default(), + } + } +} + +impl Default for StrTokenWriter { + fn default() -> Self { + Self::new(DELIM) + } +} + +impl Default for TokenWriter { + fn default() -> Self { + Self::new(TokenBuffer::ToString(StrTokenWriter::default())) + } +} + +impl TokenWriter { + pub fn new(buf: TokenBuffer) -> Self { + Self { buf } + } + + pub fn write_token(&mut self, token: &str) { + match &mut self.buf { + TokenBuffer::ToString(string_token_writer) => string_token_writer.write_token(token), + TokenBuffer::ToNote(note_token_writer) => note_token_writer.write_token(token), + } + } + + pub fn str(&self) -> String { + match &self.buf { + TokenBuffer::ToString(string_token_writer) => string_token_writer.str().to_owned(), + TokenBuffer::ToNote(note_token_writer) => note_token_writer.to_string(), + } + } +} + +impl NoteTokenWriter { + pub fn write_token(&mut self, token: &str) { + self.buf.push(token.to_owned()); + } + + pub fn to_builder<'a>( + &self, + mut builder: nostrdb::NoteBuilder<'a>, + ) -> nostrdb::NoteBuilder<'a> { + if !self.buf.is_empty() { + builder = builder.start_tag().tag_str("col"); + + for token in &self.buf { + builder = builder.tag_str(token); + } + } + + builder + } +} + +impl std::fmt::Display for NoteTokenWriter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.buf.join(self.delim)) + } +} + #[derive(Clone)] pub struct TokenParser<'a> { tokens: &'a [&'a str],