notedeck

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

commit 26ece3bc053be5b46bdbb82661dee5f58f00c862
parent a64ff3b6304ad52201f2467d36d69e6b5db0b986
Author: Fernando López Guevara <fernando.lguevara@gmail.com>
Date:   Thu, 31 Jul 2025 18:09:55 -0300

feat(note): show full created date format on selected notes

Diffstat:
Mcrates/notedeck/src/time.rs | 4++--
Mcrates/notedeck_columns/src/ui/thread.rs | 1+
Mcrates/notedeck_ui/src/note/contents.rs | 43+++++++++++++++++++++++++++++++++----------
Mcrates/notedeck_ui/src/note/mod.rs | 14+++++++++++---
Mcrates/notedeck_ui/src/note/options.rs | 5++++-
5 files changed, 51 insertions(+), 16 deletions(-)

diff --git a/crates/notedeck/src/time.rs b/crates/notedeck/src/time.rs @@ -84,9 +84,9 @@ fn time_ago_between(i18n: &mut Localization, timestamp: u64, now: u64) -> String } } -pub fn time_format(_i18n: &mut Localization, timestamp: i64) -> String { +pub fn time_format(_i18n: &mut Localization, timestamp: u64) -> String { // TODO: format this using the selected locale - DateTime::from_timestamp(timestamp, 0) + DateTime::from_timestamp(timestamp as i64, 0) .unwrap() .format("%l:%M %p %b %d, %Y") .to_string() diff --git a/crates/notedeck_columns/src/ui/thread.rs b/crates/notedeck_columns/src/ui/thread.rs @@ -292,6 +292,7 @@ struct ThreadNote<'a> { impl<'a> ThreadNote<'a> { fn options(&self, mut cur_options: NoteOptions) -> NoteOptions { + cur_options.set(NoteOptions::ShowCreatedAtBottom, true); match self.note_type { ThreadNoteType::Chain { root: _ } => cur_options, ThreadNoteType::Selected { root: _ } => { diff --git a/crates/notedeck_ui/src/note/contents.rs b/crates/notedeck_ui/src/note/contents.rs @@ -1,16 +1,16 @@ +use super::media::image_carousel; use crate::{ note::{NoteAction, NoteOptions, NoteResponse, NoteView}, secondary_label, }; -use notedeck::{JobsCache, RenderableMedia}; - use egui::{Color32, Hyperlink, Label, RichText}; use nostrdb::{BlockType, Mention, Note, NoteKey, Transaction}; +use notedeck::{ + time_format, update_imeta_blurhashes, IsFollowing, NoteCache, NoteContext, NotedeckTextStyle, +}; +use notedeck::{JobsCache, RenderableMedia}; use tracing::warn; -use super::media::image_carousel; -use notedeck::{update_imeta_blurhashes, IsFollowing, NoteCache, NoteContext, NotedeckTextStyle}; - pub struct NoteContents<'a, 'd> { note_context: &'a mut NoteContext<'d>, txn: &'a Transaction, @@ -42,8 +42,13 @@ impl<'a, 'd> NoteContents<'a, 'd> { impl egui::Widget for &mut NoteContents<'_, '_> { fn ui(self, ui: &mut egui::Ui) -> egui::Response { + let create_at_bottom = self.options.contains(NoteOptions::ShowCreatedAtBottom); if self.options.contains(NoteOptions::ShowNoteClientTop) { - render_client(ui, self.note_context.note_cache, self.note); + render_client(ui, self.note_context.note_cache, self.note, false); + } + // bottom created at only on selected note + if create_at_bottom { + self.options.set(NoteOptions::ShowCreatedAtBottom, false); } let result = render_note_contents( ui, @@ -53,21 +58,39 @@ impl egui::Widget for &mut NoteContents<'_, '_> { self.options, self.jobs, ); - if self.options.contains(NoteOptions::ShowNoteClientBottom) { - render_client(ui, self.note_context.note_cache, self.note); - } + ui.horizontal(|ui| { + if create_at_bottom { + secondary_label( + ui, + time_format(self.note_context.i18n, self.note.created_at()), + ); + } + + if self.options.contains(NoteOptions::ShowNoteClientBottom) { + render_client( + ui, + self.note_context.note_cache, + self.note, + create_at_bottom, + ); + } + }); + self.action = result.action; result.response } } #[profiling::function] -fn render_client(ui: &mut egui::Ui, note_cache: &mut NoteCache, note: &Note) { +fn render_client(ui: &mut egui::Ui, note_cache: &mut NoteCache, note: &Note, before: bool) { let cached_note = note_cache.cached_note_or_insert_mut(note.key().unwrap(), note); match cached_note.client.as_deref() { Some(client) if !client.is_empty() => { ui.horizontal(|ui| { + if before { + secondary_label(ui, "⋅"); + } secondary_label(ui, format!("via {client}")); }); } diff --git a/crates/notedeck_ui/src/note/mod.rs b/crates/notedeck_ui/src/note/mod.rs @@ -363,13 +363,17 @@ impl<'a, 'd> NoteView<'a, 'd> { note: &Note, profile: &Result<nostrdb::ProfileRecord<'_>, nostrdb::Error>, show_unread_indicator: bool, + flags: NoteOptions, ) { let horiz_resp = ui .horizontal(|ui| { ui.spacing_mut().item_spacing.x = if is_narrow(ui.ctx()) { 1.0 } else { 2.0 }; - ui.add(Username::new(i18n, profile.as_ref().ok(), note.pubkey()).abbreviated(20)); - - render_notetime(ui, i18n, note.created_at(), true) + let response = ui + .add(Username::new(i18n, profile.as_ref().ok(), note.pubkey()).abbreviated(20)); + if !flags.contains(NoteOptions::ShowCreatedAtBottom) { + return render_notetime(ui, i18n, note.created_at(), true).response; + } + response }) .response; @@ -417,6 +421,7 @@ impl<'a, 'd> NoteView<'a, 'd> { self.note, profile, self.show_unread_indicator, + self.flags, ); }) .response @@ -503,6 +508,8 @@ impl<'a, 'd> NoteView<'a, 'd> { let pfp_rect = pfp_resp.bounding_rect; let mut note_action: Option<NoteAction> = pfp_resp.into_action(self.note.pubkey()); + self.flags.set(NoteOptions::ShowCreatedAtBottom, false); + ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| { NoteView::note_header( ui, @@ -510,6 +517,7 @@ impl<'a, 'd> NoteView<'a, 'd> { self.note, profile, self.show_unread_indicator, + self.flags, ); ui.horizontal_wrapped(|ui| 's: { diff --git a/crates/notedeck_ui/src/note/options.rs b/crates/notedeck_ui/src/note/options.rs @@ -22,11 +22,14 @@ bitflags! { /// Is the content truncated? If the length is over a certain size it /// will end with a ... and a "Show more" button. const Truncate = 1 << 11; - /// Show note's client in the note header + /// Show note's client in the note content const ShowNoteClientTop = 1 << 12; const ShowNoteClientBottom = 1 << 13; const RepliesNewestFirst = 1 << 14; + + // Show note's created at note bottom + const ShowCreatedAtBottom = 1 << 15; } }