mirror of
https://github.com/aljazceru/notedeck.git
synced 2025-12-17 08:44:20 +01:00
Merge Note Metadata + stats #1188
William Casarin (13):
net: switch ping/pong messages to trace
update nostrdb
clippy fixes
add is_root_note helper
ui: note metadata stats
ui: rename actionbar function
ui: move debug slider to ui crate
ui: add rolling number function
ui/note: use rolling numbers for note stats
windows: fix time overflow crash
nostrdb: update for windows fix
clndash: clippy fix
Changelog-Added: Add realtime note stats
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -3520,7 +3520,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostrdb"
|
name = "nostrdb"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
source = "git+https://github.com/damus-io/nostrdb-rs?rev=2b2e5e43c019b80b98f1db6a03a1b88ca699bfa3#2b2e5e43c019b80b98f1db6a03a1b88ca699bfa3"
|
source = "git+https://github.com/damus-io/nostrdb-rs?rev=035bb156dbedd7b058c7ccc176b7141b15436a41#035bb156dbedd7b058c7ccc176b7141b15436a41"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"cc",
|
"cc",
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ md5 = "0.7.0"
|
|||||||
nostr = { version = "0.37.0", default-features = false, features = ["std", "nip49"] }
|
nostr = { version = "0.37.0", default-features = false, features = ["std", "nip49"] }
|
||||||
nwc = "0.39.0"
|
nwc = "0.39.0"
|
||||||
mio = { version = "1.0.3", features = ["os-poll", "net"] }
|
mio = { version = "1.0.3", features = ["os-poll", "net"] }
|
||||||
nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "2b2e5e43c019b80b98f1db6a03a1b88ca699bfa3" }
|
nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "035bb156dbedd7b058c7ccc176b7141b15436a41" }
|
||||||
#nostrdb = "0.6.1"
|
#nostrdb = "0.6.1"
|
||||||
notedeck = { path = "crates/notedeck" }
|
notedeck = { path = "crates/notedeck" }
|
||||||
notedeck_chrome = { path = "crates/notedeck_chrome" }
|
notedeck_chrome = { path = "crates/notedeck_chrome" }
|
||||||
|
|||||||
@@ -7,11 +7,8 @@ use std::time::{Duration, Instant};
|
|||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
use ewebsock::{WsEvent, WsMessage};
|
use ewebsock::{WsEvent, WsMessage};
|
||||||
|
use tracing::{debug, error, trace};
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
use tracing::{debug, error};
|
|
||||||
|
|
||||||
use super::subs_debug::SubsDebug;
|
use super::subs_debug::SubsDebug;
|
||||||
|
|
||||||
@@ -257,7 +254,7 @@ impl RelayPool {
|
|||||||
|
|
||||||
let should_ping = now - relay.last_ping > self.ping_rate;
|
let should_ping = now - relay.last_ping > self.ping_rate;
|
||||||
if should_ping {
|
if should_ping {
|
||||||
debug!("pinging {}", relay.relay.url);
|
trace!("pinging {}", relay.relay.url);
|
||||||
relay.relay.ping();
|
relay.relay.ping();
|
||||||
relay.last_ping = Instant::now();
|
relay.last_ping = Instant::now();
|
||||||
}
|
}
|
||||||
@@ -382,7 +379,7 @@ impl RelayPool {
|
|||||||
// We only need to do this natively.
|
// We only need to do this natively.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
if let WsMessage::Ping(ref bs) = ev {
|
if let WsMessage::Ping(ref bs) = ev {
|
||||||
debug!("pong {}", relay.url());
|
trace!("pong {}", relay.url());
|
||||||
match relay {
|
match relay {
|
||||||
PoolRelay::Websocket(wsr) => {
|
PoolRelay::Websocket(wsr) => {
|
||||||
wsr.relay.sender.send(WsMessage::Pong(bs.to_owned()));
|
wsr.relay.sender.send(WsMessage::Pong(bs.to_owned()));
|
||||||
|
|||||||
@@ -407,7 +407,8 @@ impl UrlMimes {
|
|||||||
url.to_owned(),
|
url.to_owned(),
|
||||||
CachedMime {
|
CachedMime {
|
||||||
mime: None,
|
mime: None,
|
||||||
expires_at: SystemTime::UNIX_EPOCH + Duration::from_secs(u64::MAX / 2), // never expire...
|
expires_at: SystemTime::UNIX_EPOCH
|
||||||
|
+ Duration::from_secs(253_402_300_799 / 2), // never expire...
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,18 +8,15 @@ pub enum ConnectionState {
|
|||||||
Connecting,
|
Connecting,
|
||||||
Active,
|
Active,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub enum LoadingState<T, E> {
|
pub enum LoadingState<T, E> {
|
||||||
|
#[default]
|
||||||
Loading,
|
Loading,
|
||||||
Failed(E),
|
Failed(E),
|
||||||
Loaded(T),
|
Loaded(T),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, E> Default for LoadingState<T, E> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Loading
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, E> LoadingState<T, E> {
|
impl<T, E> LoadingState<T, E> {
|
||||||
fn _as_ref(&self) -> LoadingState<&T, &E> {
|
fn _as_ref(&self) -> LoadingState<&T, &E> {
|
||||||
match self {
|
match self {
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ pub fn summary_cards_ui(ui: &mut egui::Ui, s: &Summary, prev: Option<&Summary>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the last row wasn't full, close it anyway
|
// If the last row wasn't full, close it anyway
|
||||||
if items_len % cols != 0 {
|
if !items_len.is_multiple_of(cols) {
|
||||||
ui.end_row();
|
ui.end_row();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -145,11 +145,6 @@ pub enum ToolCalls {
|
|||||||
Invalid(InvalidToolCall),
|
Invalid(InvalidToolCall),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
struct ErrorCall {
|
|
||||||
error: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToolCalls {
|
impl ToolCalls {
|
||||||
pub fn to_api(&self) -> FunctionCall {
|
pub fn to_api(&self) -> FunctionCall {
|
||||||
FunctionCall {
|
FunctionCall {
|
||||||
|
|||||||
@@ -212,3 +212,120 @@ impl<'a> PulseAlpha<'a> {
|
|||||||
(cur_val + alpha_min_f32).clamp(self.alpha_min as f32, self.alpha_max as f32) as u8
|
(cur_val + alpha_min_f32).clamp(self.alpha_min as f32, self.alpha_max as f32) as u8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stateless rolling number using egui's internal animation memory.
|
||||||
|
/// Each digit has a different "speed" / easing.
|
||||||
|
pub fn rolling_number(ui: &mut egui::Ui, id_source: impl std::hash::Hash, value: u32) -> Response {
|
||||||
|
let ctx = ui.ctx();
|
||||||
|
let id = ui.make_persistent_id(id_source);
|
||||||
|
|
||||||
|
// Global animated value (one float in egui's memory):
|
||||||
|
let anim = ctx.animate_value_with_time(id, value as f32, 0.35);
|
||||||
|
|
||||||
|
let anim_floor = anim.floor().max(0.0);
|
||||||
|
let base = anim_floor as u32;
|
||||||
|
let t_global = anim - anim_floor; // base step phase: 0..1
|
||||||
|
let next = if t_global == 0.0 {
|
||||||
|
base
|
||||||
|
} else {
|
||||||
|
base.saturating_add(1)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Choose how many digits we want to show.
|
||||||
|
let max_show = value.max(next);
|
||||||
|
let num_digits = max_show.to_string().len().max(1);
|
||||||
|
|
||||||
|
let font_size = 12.0;
|
||||||
|
let font_id = egui::FontId::proportional(font_size);
|
||||||
|
let color = ui.visuals().text_color();
|
||||||
|
|
||||||
|
let response = ui.allocate_response(egui::Vec2::ZERO, Sense::hover());
|
||||||
|
|
||||||
|
let prev_spacing = ui.spacing().item_spacing.x;
|
||||||
|
ui.spacing_mut().item_spacing.x = 0.0;
|
||||||
|
//let pos = ui.available_rect_before_wrap().min;
|
||||||
|
let digit_size = egui::vec2(7.0, font_size);
|
||||||
|
|
||||||
|
for i in 0..num_digits {
|
||||||
|
// Leftmost digit = index 0, rightmost = num_digits - 1
|
||||||
|
let place = 10_u32.pow((num_digits - 1 - i) as u32);
|
||||||
|
let from = (base / place) % 10;
|
||||||
|
let to = (next / place) % 10;
|
||||||
|
|
||||||
|
// Per-digit "speed": rightmost digits move more / earlier.
|
||||||
|
let idx_from_right = (num_digits - 1 - i) as f32;
|
||||||
|
// tweak these constants to taste:
|
||||||
|
let speed_factor = 0.8 + 0.25 * idx_from_right; // higher place → slightly faster
|
||||||
|
|
||||||
|
// Local phase for this digit:
|
||||||
|
let mut t_digit = (t_global * speed_factor).clamp(0.0, 1.0);
|
||||||
|
|
||||||
|
// Add a nice easing curve so some digits ease in/out:
|
||||||
|
t_digit = ease_in_out_cubic(t_digit);
|
||||||
|
|
||||||
|
draw_rolling_digit(
|
||||||
|
ui, from as u8, to as u8, t_digit, &font_id, color, digit_size,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.spacing_mut().item_spacing.x = prev_spacing;
|
||||||
|
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic cubic ease-in-out
|
||||||
|
fn ease_in_out_cubic(t: f32) -> f32 {
|
||||||
|
if t < 0.5 {
|
||||||
|
4.0 * t * t * t
|
||||||
|
} else {
|
||||||
|
let t = 2.0 * t - 2.0;
|
||||||
|
0.5 * t * t * t + 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_rolling_digit(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
from: u8,
|
||||||
|
to: u8,
|
||||||
|
t: f32, // 0..1, already "warped" per digit
|
||||||
|
font_id: &egui::FontId,
|
||||||
|
color: egui::Color32,
|
||||||
|
desired_size: egui::Vec2,
|
||||||
|
) -> egui::Response {
|
||||||
|
let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::hover());
|
||||||
|
|
||||||
|
let painter = ui.painter().with_clip_rect(rect);
|
||||||
|
|
||||||
|
let current_str = format!("{from}");
|
||||||
|
let next_str = format!("{to}");
|
||||||
|
|
||||||
|
let current_galley = painter.layout_no_wrap(current_str, font_id.clone(), color);
|
||||||
|
let next_galley = painter.layout_no_wrap(next_str, font_id.clone(), color);
|
||||||
|
|
||||||
|
let h = current_galley.rect.height().max(next_galley.rect.height());
|
||||||
|
let center_x = rect.center().x;
|
||||||
|
let center_y = rect.center().y;
|
||||||
|
|
||||||
|
let current_y = egui::lerp(center_y..=center_y - h, t);
|
||||||
|
let next_y = egui::lerp(center_y + h..=center_y, t);
|
||||||
|
|
||||||
|
painter.galley(
|
||||||
|
egui::pos2(
|
||||||
|
center_x - current_galley.rect.width() * 0.5,
|
||||||
|
current_y - current_galley.rect.height() * 0.5,
|
||||||
|
),
|
||||||
|
current_galley,
|
||||||
|
egui::Color32::RED,
|
||||||
|
);
|
||||||
|
|
||||||
|
painter.galley(
|
||||||
|
egui::pos2(
|
||||||
|
center_x - next_galley.rect.width() * 0.5,
|
||||||
|
next_y - next_galley.rect.height() * 0.5,
|
||||||
|
),
|
||||||
|
next_galley,
|
||||||
|
egui::Color32::RED,
|
||||||
|
);
|
||||||
|
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
use egui::{vec2, Pos2, Rect};
|
||||||
|
|
||||||
/*
|
pub fn debug_slider(
|
||||||
fn debug_slider(
|
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
id: egui::Id,
|
id: egui::Id,
|
||||||
point: Pos2,
|
point: Pos2,
|
||||||
@@ -22,5 +22,3 @@ fn debug_slider(
|
|||||||
|
|
||||||
val
|
val
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@ pub mod app_images;
|
|||||||
pub mod colors;
|
pub mod colors;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod context_menu;
|
pub mod context_menu;
|
||||||
|
pub mod debug;
|
||||||
pub mod icons;
|
pub mod icons;
|
||||||
pub mod images;
|
pub mod images;
|
||||||
pub mod media;
|
pub mod media;
|
||||||
@@ -13,7 +14,8 @@ pub mod profile;
|
|||||||
mod username;
|
mod username;
|
||||||
pub mod widgets;
|
pub mod widgets;
|
||||||
|
|
||||||
pub use anim::{AnimationHelper, PulseAlpha};
|
pub use anim::{rolling_number, AnimationHelper, PulseAlpha};
|
||||||
|
pub use debug::debug_slider;
|
||||||
pub use icons::{expanding_button, ICON_EXPANSION_MULTIPLE, ICON_WIDTH};
|
pub use icons::{expanding_button, ICON_EXPANSION_MULTIPLE, ICON_WIDTH};
|
||||||
pub use mention::Mention;
|
pub use mention::Mention;
|
||||||
pub use note::{NoteContents, NoteOptions, NoteView};
|
pub use note::{NoteContents, NoteOptions, NoteView};
|
||||||
|
|||||||
@@ -452,15 +452,30 @@ impl<'a, 'd> NoteView<'a, 'd> {
|
|||||||
// question: WTF? question 2: WHY?
|
// question: WTF? question 2: WHY?
|
||||||
ui.allocate_space(egui::vec2(0.0, 0.0));
|
ui.allocate_space(egui::vec2(0.0, 0.0));
|
||||||
|
|
||||||
render_note_actionbar(
|
let counts = self
|
||||||
|
.note_context
|
||||||
|
.ndb
|
||||||
|
.get_note_metadata(txn, self.note.id())
|
||||||
|
.ok()
|
||||||
|
.and_then(|md| {
|
||||||
|
md.into_iter().find_map(|e| {
|
||||||
|
if let nostrdb::NoteMetadataEntryVariant::Counts(ce) = e {
|
||||||
|
Some(ce)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
actionbar_ui(
|
||||||
ui,
|
ui,
|
||||||
|
counts,
|
||||||
get_zapper(
|
get_zapper(
|
||||||
self.note_context.accounts,
|
self.note_context.accounts,
|
||||||
self.note_context.global_wallet,
|
self.note_context.global_wallet,
|
||||||
self.note_context.zaps,
|
self.note_context.zaps,
|
||||||
),
|
),
|
||||||
self.note.id(),
|
self.note,
|
||||||
self.note.pubkey(),
|
|
||||||
self.note_context.accounts.selected_account_pubkey(),
|
self.note_context.accounts.selected_account_pubkey(),
|
||||||
note_key,
|
note_key,
|
||||||
self.note_context.i18n,
|
self.note_context.i18n,
|
||||||
@@ -539,17 +554,32 @@ impl<'a, 'd> NoteView<'a, 'd> {
|
|||||||
note_action = contents.action.or(note_action);
|
note_action = contents.action.or(note_action);
|
||||||
|
|
||||||
if self.options().contains(NoteOptions::ActionBar) {
|
if self.options().contains(NoteOptions::ActionBar) {
|
||||||
|
let counts = self
|
||||||
|
.note_context
|
||||||
|
.ndb
|
||||||
|
.get_note_metadata(txn, self.note.id())
|
||||||
|
.ok()
|
||||||
|
.and_then(|md| {
|
||||||
|
md.into_iter().find_map(|e| {
|
||||||
|
if let nostrdb::NoteMetadataEntryVariant::Counts(ce) = e {
|
||||||
|
Some(ce)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
note_action = ui
|
note_action = ui
|
||||||
.horizontal_wrapped(|ui| {
|
.horizontal_wrapped(|ui| {
|
||||||
render_note_actionbar(
|
actionbar_ui(
|
||||||
ui,
|
ui,
|
||||||
|
counts,
|
||||||
get_zapper(
|
get_zapper(
|
||||||
self.note_context.accounts,
|
self.note_context.accounts,
|
||||||
self.note_context.global_wallet,
|
self.note_context.global_wallet,
|
||||||
self.note_context.zaps,
|
self.note_context.zaps,
|
||||||
),
|
),
|
||||||
self.note.id(),
|
self.note,
|
||||||
self.note.pubkey(),
|
|
||||||
self.note_context.accounts.selected_account_pubkey(),
|
self.note_context.accounts.selected_account_pubkey(),
|
||||||
note_key,
|
note_key,
|
||||||
self.note_context.i18n,
|
self.note_context.i18n,
|
||||||
@@ -844,51 +874,100 @@ fn zap_actionbar_button(
|
|||||||
action
|
action
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_root_note(note: &Note) -> bool {
|
||||||
|
for tag in note.tags() {
|
||||||
|
if tag.count() < 2 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// any reference to an e tag is a non-root note
|
||||||
|
if tag.get_str(0) == Some("e") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
#[profiling::function]
|
#[profiling::function]
|
||||||
fn render_note_actionbar(
|
fn actionbar_ui(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
|
counts: Option<nostrdb::CountsEntry<'_>>,
|
||||||
zapper: Option<Zapper<'_>>,
|
zapper: Option<Zapper<'_>>,
|
||||||
note_id: &[u8; 32],
|
note: &Note,
|
||||||
note_pubkey: &[u8; 32],
|
|
||||||
current_user_pubkey: &Pubkey,
|
current_user_pubkey: &Pubkey,
|
||||||
note_key: NoteKey,
|
note_key: NoteKey,
|
||||||
i18n: &mut Localization,
|
i18n: &mut Localization,
|
||||||
) -> Option<NoteAction> {
|
) -> Option<NoteAction> {
|
||||||
let mut action = None;
|
let mut action = None;
|
||||||
|
let spacing = 24.0;
|
||||||
|
|
||||||
|
ui.spacing_mut().item_spacing.x = 2.0;
|
||||||
ui.set_min_height(26.0);
|
ui.set_min_height(26.0);
|
||||||
ui.spacing_mut().item_spacing.x = 24.0;
|
|
||||||
|
|
||||||
let reply_resp =
|
let reply_resp =
|
||||||
reply_button(ui, i18n, note_key).on_hover_cursor(egui::CursorIcon::PointingHand);
|
reply_button(ui, i18n, note_key).on_hover_cursor(egui::CursorIcon::PointingHand);
|
||||||
|
|
||||||
|
if let Some(c) = &counts {
|
||||||
|
let count = if is_root_note(note) {
|
||||||
|
c.thread_replies()
|
||||||
|
} else {
|
||||||
|
c.direct_replies() as u32
|
||||||
|
};
|
||||||
|
|
||||||
|
if count > 0 {
|
||||||
|
//ui.weak(format!("{}", count));
|
||||||
|
crate::anim::rolling_number(ui, egui::Id::new((note_key, "replies")), count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.add_space(spacing);
|
||||||
|
|
||||||
let filled = ui
|
let filled = ui
|
||||||
.ctx()
|
.ctx()
|
||||||
.data(|d| d.get_temp(reaction_sent_id(current_user_pubkey, note_id)))
|
.data(|d| d.get_temp(reaction_sent_id(current_user_pubkey, note.id())))
|
||||||
== Some(true);
|
== Some(true);
|
||||||
|
|
||||||
let like_resp =
|
let like_resp =
|
||||||
like_button(ui, i18n, note_key, filled).on_hover_cursor(egui::CursorIcon::PointingHand);
|
like_button(ui, i18n, note_key, filled).on_hover_cursor(egui::CursorIcon::PointingHand);
|
||||||
|
|
||||||
|
if let Some(c) = &counts {
|
||||||
|
let count = c.reactions();
|
||||||
|
if count > 0 {
|
||||||
|
crate::anim::rolling_number(ui, egui::Id::new((note_key, "likes")), count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.add_space(spacing);
|
||||||
|
|
||||||
let quote_resp =
|
let quote_resp =
|
||||||
quote_repost_button(ui, i18n, note_key).on_hover_cursor(egui::CursorIcon::PointingHand);
|
quote_repost_button(ui, i18n, note_key).on_hover_cursor(egui::CursorIcon::PointingHand);
|
||||||
|
|
||||||
|
if let Some(c) = &counts {
|
||||||
|
let count = c.quotes() + c.reposts();
|
||||||
|
if count > 0 {
|
||||||
|
crate::anim::rolling_number(ui, egui::Id::new((note_key, "quotes")), count as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.add_space(spacing);
|
||||||
|
|
||||||
if reply_resp.clicked() {
|
if reply_resp.clicked() {
|
||||||
action = Some(NoteAction::Reply(NoteId::new(*note_id)));
|
action = Some(NoteAction::Reply(NoteId::new(*note.id())));
|
||||||
}
|
}
|
||||||
|
|
||||||
if like_resp.clicked() {
|
if like_resp.clicked() {
|
||||||
action = Some(NoteAction::React(ReactAction::new(
|
action = Some(NoteAction::React(ReactAction::new(
|
||||||
NoteId::new(*note_id),
|
NoteId::new(*note.id()),
|
||||||
"🤙🏻",
|
"🤙🏻",
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if quote_resp.clicked() {
|
if quote_resp.clicked() {
|
||||||
action = Some(NoteAction::Repost(NoteId::new(*note_id)));
|
action = Some(NoteAction::Repost(NoteId::new(*note.id())));
|
||||||
}
|
}
|
||||||
|
|
||||||
action = zap_actionbar_button(ui, note_id, note_pubkey, zapper, i18n).or(action);
|
action = zap_actionbar_button(ui, note.id(), note.pubkey(), zapper, i18n).or(action);
|
||||||
|
|
||||||
action
|
action
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user