timeline: auto-add yourself to your home timeline

This is the most intuitive, and damus iOS does the same thing. You
have to follow yourself, sorry. Otherwise you won't see your posts
when you post which is confusing.

Fixes: https://github.com/damus-io/notedeck/issues/509
This commit is contained in:
William Casarin
2024-12-18 23:16:02 -08:00
parent 09d6568ef9
commit 6fa6a5733e
5 changed files with 52 additions and 7 deletions

View File

@@ -192,13 +192,14 @@ impl FilteredTags {
/// Create a filter from tags. This can be used to create a filter /// Create a filter from tags. This can be used to create a filter
/// from a contact list /// from a contact list
pub fn filter_from_tags(note: &Note) -> Result<FilteredTags> { pub fn filter_from_tags(note: &Note, add_pubkey: Option<&[u8; 32]>) -> Result<FilteredTags> {
let mut author_filter = Filter::new(); let mut author_filter = Filter::new();
let mut hashtag_filter = Filter::new(); let mut hashtag_filter = Filter::new();
let mut author_res: Option<FilterBuilder> = None; let mut author_res: Option<FilterBuilder> = None;
let mut hashtag_res: Option<FilterBuilder> = None; let mut hashtag_res: Option<FilterBuilder> = None;
let mut author_count = 0i32; let mut author_count = 0i32;
let mut hashtag_count = 0i32; let mut hashtag_count = 0i32;
let mut has_added_pubkey = false;
let tags = note.tags(); let tags = note.tags();
@@ -223,6 +224,13 @@ pub fn filter_from_tags(note: &Note) -> Result<FilteredTags> {
continue; continue;
}; };
if let Some(pk) = add_pubkey {
if author == pk {
// we don't need to add it afterwards
has_added_pubkey = true;
}
}
author_filter.add_id_element(author)?; author_filter.add_id_element(author)?;
author_count += 1; author_count += 1;
} else if t == "t" { } else if t == "t" {
@@ -237,6 +245,14 @@ pub fn filter_from_tags(note: &Note) -> Result<FilteredTags> {
} }
} }
// some additional ad-hoc logic for adding a pubkey
if let Some(pk) = add_pubkey {
if !has_added_pubkey {
author_filter.add_id_element(pk)?;
author_count += 1;
}
}
author_filter.end_field(); author_filter.end_field();
hashtag_filter.end_field(); hashtag_filter.end_field();

View File

@@ -141,6 +141,11 @@ fn try_process_event(
app_ctx.note_cache, app_ctx.note_cache,
timeline, timeline,
&app_ctx.accounts.mutefun(), &app_ctx.accounts.mutefun(),
app_ctx
.accounts
.get_selected_account()
.as_ref()
.map(|sa| &sa.pubkey),
) )
}; };

View File

@@ -25,6 +25,13 @@ impl PubkeySource {
PubkeySource::DeckAuthor => deck_author, PubkeySource::DeckAuthor => deck_author,
} }
} }
pub fn to_pubkey_bytes<'a>(&'a self, deck_author: &'a [u8; 32]) -> &'a [u8; 32] {
match self {
PubkeySource::Explicit(pk) => pk.bytes(),
PubkeySource::DeckAuthor => deck_author,
}
}
} }
impl ListKind { impl ListKind {
@@ -177,7 +184,7 @@ impl TimelineKind {
)); ));
} }
match Timeline::contact_list(&results[0].note, pk_src.clone()) { match Timeline::contact_list(&results[0].note, pk_src.clone(), default_user) {
Err(Error::App(notedeck::Error::Filter(FilterError::EmptyContactList))) => { Err(Error::App(notedeck::Error::Filter(FilterError::EmptyContactList))) => {
Some(Timeline::new( Some(Timeline::new(
TimelineKind::contact_list(pk_src), TimelineKind::contact_list(pk_src),

View File

@@ -15,7 +15,7 @@ use std::fmt;
use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::atomic::{AtomicU32, Ordering};
use egui_virtual_list::VirtualList; use egui_virtual_list::VirtualList;
use enostr::{Relay, RelayPool}; use enostr::{Pubkey, Relay, RelayPool};
use nostrdb::{Filter, Ndb, Note, Subscription, Transaction}; use nostrdb::{Filter, Ndb, Note, Subscription, Transaction};
use std::cell::RefCell; use std::cell::RefCell;
use std::hash::Hash; use std::hash::Hash;
@@ -187,8 +187,13 @@ pub struct Timeline {
impl Timeline { impl Timeline {
/// Create a timeline from a contact list /// Create a timeline from a contact list
pub fn contact_list(contact_list: &Note, pk_src: PubkeySource) -> Result<Self> { pub fn contact_list(
let filter = filter::filter_from_tags(contact_list)?.into_follow_filter(); contact_list: &Note,
pk_src: PubkeySource,
deck_author: Option<&[u8; 32]>,
) -> Result<Self> {
let our_pubkey = deck_author.map(|da| pk_src.to_pubkey_bytes(da));
let filter = filter::filter_from_tags(contact_list, our_pubkey)?.into_follow_filter();
Ok(Timeline::new( Ok(Timeline::new(
TimelineKind::contact_list(pk_src), TimelineKind::contact_list(pk_src),
@@ -388,6 +393,7 @@ pub fn merge_sorted_vecs<T: Ord + Copy>(vec1: &[T], vec2: &[T]) -> (Vec<T>, Merg
/// ///
/// We do this by maintaining this sub_id in the filter state, even when /// We do this by maintaining this sub_id in the filter state, even when
/// in the ready state. See: [`FilterReady`] /// in the ready state. See: [`FilterReady`]
#[allow(clippy::too_many_arguments)]
pub fn setup_new_timeline( pub fn setup_new_timeline(
timeline: &mut Timeline, timeline: &mut Timeline,
ndb: &Ndb, ndb: &Ndb,
@@ -396,9 +402,10 @@ pub fn setup_new_timeline(
note_cache: &mut NoteCache, note_cache: &mut NoteCache,
since_optimize: bool, since_optimize: bool,
is_muted: &MuteFun, is_muted: &MuteFun,
our_pk: Option<&Pubkey>,
) { ) {
// if we're ready, setup local subs // if we're ready, setup local subs
if is_timeline_ready(ndb, pool, note_cache, timeline, is_muted) { if is_timeline_ready(ndb, pool, note_cache, timeline, is_muted, our_pk) {
if let Err(err) = setup_timeline_nostrdb_sub(ndb, note_cache, timeline, is_muted) { if let Err(err) = setup_timeline_nostrdb_sub(ndb, note_cache, timeline, is_muted) {
error!("setup_new_timeline: {err}"); error!("setup_new_timeline: {err}");
} }
@@ -627,6 +634,7 @@ pub fn is_timeline_ready(
note_cache: &mut NoteCache, note_cache: &mut NoteCache,
timeline: &mut Timeline, timeline: &mut Timeline,
is_muted: &MuteFun, is_muted: &MuteFun,
our_pk: Option<&Pubkey>,
) -> bool { ) -> bool {
// TODO: we should debounce the filter states a bit to make sure we have // TODO: we should debounce the filter states a bit to make sure we have
// seen all of the different contact lists from each relay // seen all of the different contact lists from each relay
@@ -658,7 +666,12 @@ pub fn is_timeline_ready(
let filter = { let filter = {
let txn = Transaction::new(ndb).expect("txn"); let txn = Transaction::new(ndb).expect("txn");
let note = ndb.get_note_by_key(&txn, note_key).expect("note"); let note = ndb.get_note_by_key(&txn, note_key).expect("note");
filter::filter_from_tags(&note).map(|f| f.into_follow_filter()) let add_pk = timeline
.kind
.pubkey_source()
.as_ref()
.and_then(|pk_src| our_pk.map(|pk| pk_src.to_pubkey_bytes(pk)));
filter::filter_from_tags(&note, add_pk).map(|f| f.into_follow_filter())
}; };
// TODO: into_follow_filter is hardcoded to contact lists, let's generalize // TODO: into_follow_filter is hardcoded to contact lists, let's generalize

View File

@@ -504,6 +504,10 @@ pub fn render_add_column_routes(
ctx.note_cache, ctx.note_cache,
app.since_optimize, app.since_optimize,
&ctx.accounts.mutefun(), &ctx.accounts.mutefun(),
ctx.accounts
.get_selected_account()
.as_ref()
.map(|sa| &sa.pubkey),
); );
app.columns_mut(ctx.accounts) app.columns_mut(ctx.accounts)
.add_timeline_to_column(col, timeline); .add_timeline_to_column(col, timeline);