Merge remote-tracking branch 'github/pr/1025'
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -3485,6 +3485,7 @@ dependencies = [
|
|||||||
"bincode",
|
"bincode",
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"blurhash",
|
"blurhash",
|
||||||
|
"chrono",
|
||||||
"dirs",
|
"dirs",
|
||||||
"eframe",
|
"eframe",
|
||||||
"egui",
|
"egui",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ members = [
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
opener = "0.8.2"
|
opener = "0.8.2"
|
||||||
|
chrono = "0.4.40"
|
||||||
base32 = "0.4.0"
|
base32 = "0.4.0"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
rmpv = "1.3.0"
|
rmpv = "1.3.0"
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ once_cell = { workspace = true }
|
|||||||
md5 = { workspace = true }
|
md5 = { workspace = true }
|
||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
chrono = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ pub use storage::{AccountStorage, DataPath, DataPathType, Directory};
|
|||||||
pub use style::NotedeckTextStyle;
|
pub use style::NotedeckTextStyle;
|
||||||
pub use theme::ColorTheme;
|
pub use theme::ColorTheme;
|
||||||
pub use time::time_ago_since;
|
pub use time::time_ago_since;
|
||||||
|
pub use time::time_format;
|
||||||
pub use timecache::TimeCached;
|
pub use timecache::TimeCached;
|
||||||
pub use unknowns::{get_unknown_note_ids, NoteRefsUnkIdAction, SingleUnkIdAction, UnknownIds};
|
pub use unknowns::{get_unknown_note_ids, NoteRefsUnkIdAction, SingleUnkIdAction, UnknownIds};
|
||||||
pub use urls::{supported_mime_hosted_at_url, SupportedMimeType, UrlMimes};
|
pub use urls::{supported_mime_hosted_at_url, SupportedMimeType, UrlMimes};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::{tr, Localization};
|
use crate::{tr, Localization};
|
||||||
|
use chrono::DateTime;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
// Time duration constants in seconds
|
// Time duration constants in seconds
|
||||||
@@ -83,6 +84,14 @@ fn time_ago_between(i18n: &mut Localization, timestamp: u64, now: u64) -> String
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn time_format(_i18n: &mut Localization, timestamp: u64) -> String {
|
||||||
|
// TODO: format this using the selected locale
|
||||||
|
DateTime::from_timestamp(timestamp as i64, 0)
|
||||||
|
.unwrap()
|
||||||
|
.format("%l:%M %p %b %d, %Y")
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn time_ago_since(i18n: &mut Localization, timestamp: u64) -> String {
|
pub fn time_ago_since(i18n: &mut Localization, timestamp: u64) -> String {
|
||||||
let now = SystemTime::now()
|
let now = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
|
|||||||
@@ -591,6 +591,7 @@ fn render_nav_body(
|
|||||||
)
|
)
|
||||||
.ui(ui)
|
.ui(ui)
|
||||||
.map(RenderNavAction::SettingsAction),
|
.map(RenderNavAction::SettingsAction),
|
||||||
|
|
||||||
Route::Reply(id) => {
|
Route::Reply(id) => {
|
||||||
let txn = if let Ok(txn) = Transaction::new(ctx.ndb) {
|
let txn = if let Ok(txn) = Transaction::new(ctx.ndb) {
|
||||||
txn
|
txn
|
||||||
|
|||||||
@@ -270,6 +270,7 @@ impl<'a> SettingsView<'a> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let txn = Transaction::new(self.note_context.ndb).unwrap();
|
let txn = Transaction::new(self.note_context.ndb).unwrap();
|
||||||
|
|
||||||
if let Some(note_id) = NoteId::from_bech(PREVIEW_NOTE_ID) {
|
if let Some(note_id) = NoteId::from_bech(PREVIEW_NOTE_ID) {
|
||||||
if let Ok(preview_note) =
|
if let Ok(preview_note) =
|
||||||
self.note_context.ndb.get_note_by_id(&txn, note_id.bytes())
|
self.note_context.ndb.get_note_by_id(&txn, note_id.bytes())
|
||||||
@@ -277,17 +278,17 @@ impl<'a> SettingsView<'a> {
|
|||||||
notedeck_ui::padding(8.0, ui, |ui| {
|
notedeck_ui::padding(8.0, ui, |ui| {
|
||||||
if is_narrow(ui.ctx()) {
|
if is_narrow(ui.ctx()) {
|
||||||
ui.set_max_width(ui.available_width());
|
ui.set_max_width(ui.available_width());
|
||||||
}
|
|
||||||
|
|
||||||
NoteView::new(
|
NoteView::new(
|
||||||
self.note_context,
|
self.note_context,
|
||||||
&preview_note,
|
&preview_note,
|
||||||
*self.note_options,
|
*self.note_options,
|
||||||
self.jobs,
|
self.jobs,
|
||||||
)
|
)
|
||||||
.actionbar(false)
|
.actionbar(false)
|
||||||
.options_button(false)
|
.options_button(false)
|
||||||
.show(ui);
|
.show(ui);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
ui.separator();
|
ui.separator();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -292,6 +292,7 @@ struct ThreadNote<'a> {
|
|||||||
|
|
||||||
impl<'a> ThreadNote<'a> {
|
impl<'a> ThreadNote<'a> {
|
||||||
fn options(&self, mut cur_options: NoteOptions) -> NoteOptions {
|
fn options(&self, mut cur_options: NoteOptions) -> NoteOptions {
|
||||||
|
cur_options.set(NoteOptions::ShowCreatedAtBottom, true);
|
||||||
match self.note_type {
|
match self.note_type {
|
||||||
ThreadNoteType::Chain { root: _ } => cur_options,
|
ThreadNoteType::Chain { root: _ } => cur_options,
|
||||||
ThreadNoteType::Selected { root: _ } => {
|
ThreadNoteType::Selected { root: _ } => {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ serde_json = { workspace = true }
|
|||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
nostrdb = { workspace = true }
|
nostrdb = { workspace = true }
|
||||||
hex = { workspace = true }
|
hex = { workspace = true }
|
||||||
chrono = "0.4.40"
|
chrono = { workspace = true }
|
||||||
rand = "0.9.0"
|
rand = "0.9.0"
|
||||||
bytemuck = "1.22.0"
|
bytemuck = "1.22.0"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
|
use super::media::image_carousel;
|
||||||
use crate::{
|
use crate::{
|
||||||
note::{NoteAction, NoteOptions, NoteResponse, NoteView},
|
note::{NoteAction, NoteOptions, NoteResponse, NoteView},
|
||||||
secondary_label,
|
secondary_label,
|
||||||
};
|
};
|
||||||
use notedeck::{JobsCache, RenderableMedia};
|
|
||||||
|
|
||||||
use egui::{Color32, Hyperlink, Label, RichText};
|
use egui::{Color32, Hyperlink, Label, RichText};
|
||||||
use nostrdb::{BlockType, Mention, Note, NoteKey, Transaction};
|
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 tracing::warn;
|
||||||
|
|
||||||
use super::media::image_carousel;
|
|
||||||
use notedeck::{update_imeta_blurhashes, IsFollowing, NoteCache, NoteContext, NotedeckTextStyle};
|
|
||||||
|
|
||||||
pub struct NoteContents<'a, 'd> {
|
pub struct NoteContents<'a, 'd> {
|
||||||
note_context: &'a mut NoteContext<'d>,
|
note_context: &'a mut NoteContext<'d>,
|
||||||
txn: &'a Transaction,
|
txn: &'a Transaction,
|
||||||
@@ -42,8 +42,13 @@ impl<'a, 'd> NoteContents<'a, 'd> {
|
|||||||
|
|
||||||
impl egui::Widget for &mut NoteContents<'_, '_> {
|
impl egui::Widget for &mut NoteContents<'_, '_> {
|
||||||
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
|
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
|
||||||
|
let create_at_bottom = self.options.contains(NoteOptions::ShowCreatedAtBottom);
|
||||||
if self.options.contains(NoteOptions::ShowNoteClientTop) {
|
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(
|
let result = render_note_contents(
|
||||||
ui,
|
ui,
|
||||||
@@ -53,21 +58,39 @@ impl egui::Widget for &mut NoteContents<'_, '_> {
|
|||||||
self.options,
|
self.options,
|
||||||
self.jobs,
|
self.jobs,
|
||||||
);
|
);
|
||||||
if self.options.contains(NoteOptions::ShowNoteClientBottom) {
|
ui.horizontal(|ui| {
|
||||||
render_client(ui, self.note_context.note_cache, self.note);
|
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;
|
self.action = result.action;
|
||||||
result.response
|
result.response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[profiling::function]
|
#[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);
|
let cached_note = note_cache.cached_note_or_insert_mut(note.key().unwrap(), note);
|
||||||
|
|
||||||
match cached_note.client.as_deref() {
|
match cached_note.client.as_deref() {
|
||||||
Some(client) if !client.is_empty() => {
|
Some(client) if !client.is_empty() => {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
if before {
|
||||||
|
secondary_label(ui, "⋅");
|
||||||
|
}
|
||||||
secondary_label(ui, format!("via {client}"));
|
secondary_label(ui, format!("via {client}"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ impl<'a, 'd> NoteView<'a, 'd> {
|
|||||||
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, self.note_context.i18n, self.note.created_at(), false).response
|
render_notetime(ui, self.note_context.i18n, self.note.created_at(), false).response
|
||||||
});
|
});
|
||||||
let (_id, rect) = ui.allocate_space(egui::vec2(150.0, 20.0));
|
let (_id, rect) = ui.allocate_space(egui::vec2(150.0, 20.0));
|
||||||
ui.allocate_rect(rect, Sense::hover());
|
ui.allocate_rect(rect, Sense::hover());
|
||||||
@@ -363,13 +363,17 @@ impl<'a, 'd> NoteView<'a, 'd> {
|
|||||||
note: &Note,
|
note: &Note,
|
||||||
profile: &Result<nostrdb::ProfileRecord<'_>, nostrdb::Error>,
|
profile: &Result<nostrdb::ProfileRecord<'_>, nostrdb::Error>,
|
||||||
show_unread_indicator: bool,
|
show_unread_indicator: bool,
|
||||||
|
flags: NoteOptions,
|
||||||
) {
|
) {
|
||||||
let horiz_resp = ui
|
let horiz_resp = ui
|
||||||
.horizontal(|ui| {
|
.horizontal(|ui| {
|
||||||
ui.spacing_mut().item_spacing.x = if is_narrow(ui.ctx()) { 1.0 } else { 2.0 };
|
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));
|
let response = ui
|
||||||
|
.add(Username::new(i18n, profile.as_ref().ok(), note.pubkey()).abbreviated(20));
|
||||||
render_reltime(ui, i18n, note.created_at(), true);
|
if !flags.contains(NoteOptions::ShowCreatedAtBottom) {
|
||||||
|
return render_notetime(ui, i18n, note.created_at(), true).response;
|
||||||
|
}
|
||||||
|
response
|
||||||
})
|
})
|
||||||
.response;
|
.response;
|
||||||
|
|
||||||
@@ -417,6 +421,7 @@ impl<'a, 'd> NoteView<'a, 'd> {
|
|||||||
self.note,
|
self.note,
|
||||||
profile,
|
profile,
|
||||||
self.show_unread_indicator,
|
self.show_unread_indicator,
|
||||||
|
self.flags,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.response
|
.response
|
||||||
@@ -503,6 +508,8 @@ impl<'a, 'd> NoteView<'a, 'd> {
|
|||||||
let pfp_rect = pfp_resp.bounding_rect;
|
let pfp_rect = pfp_resp.bounding_rect;
|
||||||
let mut note_action: Option<NoteAction> = pfp_resp.into_action(self.note.pubkey());
|
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| {
|
ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| {
|
||||||
NoteView::note_header(
|
NoteView::note_header(
|
||||||
ui,
|
ui,
|
||||||
@@ -510,6 +517,7 @@ impl<'a, 'd> NoteView<'a, 'd> {
|
|||||||
self.note,
|
self.note,
|
||||||
profile,
|
profile,
|
||||||
self.show_unread_indicator,
|
self.show_unread_indicator,
|
||||||
|
self.flags,
|
||||||
);
|
);
|
||||||
|
|
||||||
ui.horizontal_wrapped(|ui| 's: {
|
ui.horizontal_wrapped(|ui| 's: {
|
||||||
@@ -862,7 +870,7 @@ fn render_note_actionbar(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[profiling::function]
|
#[profiling::function]
|
||||||
fn render_reltime(
|
fn render_notetime(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
i18n: &mut Localization,
|
i18n: &mut Localization,
|
||||||
created_at: u64,
|
created_at: u64,
|
||||||
|
|||||||
@@ -22,11 +22,14 @@ bitflags! {
|
|||||||
/// Is the content truncated? If the length is over a certain size it
|
/// Is the content truncated? If the length is over a certain size it
|
||||||
/// will end with a ... and a "Show more" button.
|
/// will end with a ... and a "Show more" button.
|
||||||
const Truncate = 1 << 11;
|
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 ShowNoteClientTop = 1 << 12;
|
||||||
const ShowNoteClientBottom = 1 << 13;
|
const ShowNoteClientBottom = 1 << 13;
|
||||||
|
|
||||||
const RepliesNewestFirst = 1 << 14;
|
const RepliesNewestFirst = 1 << 14;
|
||||||
|
|
||||||
|
// Show note's created at note bottom
|
||||||
|
const ShowCreatedAtBottom = 1 << 15;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user