Improved recognization of replying to thread and being able to open it.

Rewrote profile storing on the model. Additionally fixed issues where
the profile was not getting loaded for referenced pubkeys on an event.
This commit is contained in:
Thomas Mathews 2022-12-29 18:01:05 -08:00
parent 1850ed6aa1
commit f00f327a3d
11 changed files with 145 additions and 122 deletions

View file

@ -38,23 +38,22 @@ async function sign_id(privkey, id) {
}
async function broadcast_related_events(ev) {
ev.tags
.reduce((evs, tag) => {
// cap it at something sane
if (evs.length >= 5)
return evs
const ev = get_tag_event(tag)
if (!ev)
return evs
ev.tags.reduce((evs, tag) => {
// cap it at something sane
if (evs.length >= 5)
return evs
}, [])
.forEach((ev, i) => {
// so we don't get rate limited
setTimeout(() => {
log_debug("broadcasting related event", ev)
broadcast_event(ev)
}, (i+1)*1200)
})
const ev = get_tag_event(tag)
if (!ev)
return evs
return evs
}, [])
.forEach((ev, i) => {
// so we don't get rate limited
setTimeout(() => {
log_debug("broadcasting related event", ev)
broadcast_event(ev)
}, (i+1)*1200)
});
}
function broadcast_event(ev) {
@ -278,12 +277,16 @@ function gather_reply_tags(pubkey, from) {
}
function get_tag_event(tag) {
const model = DAMUS;
if (tag.length < 2)
return null
if (tag[0] === "e")
return DAMUS.all_events[tag[1]]
if (tag[0] === "p")
return DAMUS.all_events[DAMUS.profile_events[tag[1]]]
return model.all_events[tag[1]]
if (tag[0] === "p") {
let profile = model_get_profile(model, tag[1]);
if (profile.evid)
return model.all_events[profile.evid];
}
return null
}

View file

@ -15,6 +15,25 @@ function event_refs_pubkey(ev, pubkey) {
return false
}
function event_contains_pubkey(ev, pubkey) {
if (ev.pubkey == pubkey)
return true;
for (const tag of ev.tags) {
if (tag.length >= 2 && tag[0] == "p" && tag[1] == pubkey)
return true;
}
return false;
}
function event_get_pubkeys(ev) {
const keys = [ev.pubkey];
for (const tag of ev.tags) {
if (tag.length >= 2 && tag[0] == "p")
keys.push(tag[1]);
}
return keys;
}
function event_calculate_pow(ev) {
const id_bits = leading_zero_bits(ev.id)
for (const tag of ev.tags) {

View file

@ -242,7 +242,7 @@ function fetch_profiles(pool, relay, pubkeys) {
function fetch_profile_info(pubkey, pool, relay) {
const sid = `${SID_META}:${pubkey}`;
pool.subscribe(sid, [{
kinds: [KIND_METADATA, KIND_CONTACT],
kinds: [KIND_METADATA, KIND_CONTACT, KIND_RELAY],
authors: [pubkey],
limit: 1,
}], relay);

View file

@ -39,9 +39,12 @@ function model_process_event(model, relay, ev) {
if (!relay)
return;
// Request the profile if we have never seen it
if (!model.profile_events[ev.pubkey])
model_que_profile(model, relay, ev.pubkey);
// Request new profiles for unseen pubkeys of the event
event_get_pubkeys(ev).forEach((pubkey) => {
if (!model_has_profile(model, pubkey)) {
model_que_profile(model, relay, pubkey);
}
});
// TODO fetch unknown referenced events & pubkeys from this event
// TODO notify user of new events aimed at them!
@ -108,22 +111,40 @@ function model_fetch_next_profile(model, relay) {
* in the event.
*/
function model_process_event_metadata(model, ev, update_view) {
const prev_ev = model.all_events[model.profile_events[ev.pubkey]]
if (prev_ev && prev_ev.created_at > ev.created_at)
return
model.profile_events[ev.pubkey] = ev.id
model.profiles[ev.pubkey] = safe_parse_json(ev.content, "profile contents")
if (update_view)
view_timeline_update_profiles(model, ev);
const profile = model_get_profile(model, ev.pubkey);
const evs = model.all_events;
if (profile.evid &&
evs[ev.id].created_at < evs[profile.evid].created_at)
return;
profile.evid = ev.id;
profile.data = safe_parse_json(ev.content, "profile contents");
if (update_view)
view_timeline_update_profiles(model, profile.pubkey);
// If it's my pubkey let's redraw my pfp that is not located in the view
// This has to happen regardless of update_view because of the it's not
// related to events
if (ev.pubkey == model.pubkey) {
if (profile.pubkey == model.pubkey) {
redraw_my_pfp(model);
}
}
function model_has_profile(model, pk) {
return !!model_get_profile(model, pk).evid;
}
function model_get_profile(model, pubkey) {
if (model.profiles.has(pubkey)) {
return model.profiles.get(pubkey);
}
model.profiles.set(pubkey, {
pubkey: pubkey,
evid: "",
relays: [],
data: {},
});
return model.profiles.get(pubkey);
}
function model_process_event_following(model, ev, update_view) {
contacts_process_event(model.contacts, model.pubkey, ev)
// TODO support loading relays that are stored on the initial relay
@ -241,10 +262,6 @@ function model_is_event_deleted(model, evid) {
return false
}
function model_has_profile(model, pk) {
return pk in model.profiles
}
function model_has_event(model, evid) {
return evid in model.all_events
}
@ -399,8 +416,7 @@ function new_model() {
deletions: {},
deleted: {},
pow: 0, // pow difficulty target
profiles: {}, // pubkey => profile data
profile_events: {}, // pubkey => event id - use with all_events
profiles: new Map(), // pubkey => profile data
contacts: {
event: null,
friends: new Set(),

View file

@ -5,17 +5,23 @@
function render_replying_to(model, ev) {
if (!(ev.refs && ev.refs.reply))
return "";
if (ev.kind === KIND_CHATROOM)
return render_replying_to_chat(model, ev);
let pubkeys = ev.refs.pubkeys || []
if (pubkeys.length === 0 && ev.refs.reply) {
const replying_to = model.all_events[ev.refs.reply]
if (!replying_to)
return html`<div class="replying-to small-txt">reply to ${ev.refs.reply}</div>`;
pubkeys = [replying_to.pubkey]
// If there is no profile being replied to, it is simply a reply to an
// event itself, thus render it differently.
if (!replying_to) {
return html`<span class="replying-to small-txt">
replying in thread
<span class="thread-id clickable"
onclick="open_thread('${ev.refs.reply}')">
${fmt_pubkey(ev.refs.reply)}</span></span>`;
} else {
pubkeys = [replying_to.pubkey];
}
}
const names = pubkeys.map((pk) => {
return render_mentioned_name(pk, model.profiles[pk]);
return render_name(pk, model_get_profile(model, pk).data);
}).join(", ")
return `
<span class="replying-to small-txt">
@ -24,19 +30,19 @@ function render_replying_to(model, ev) {
`
}
function render_share(damus, ev, opts) {
const shared_ev = damus.all_events[ev.refs && ev.refs.root]
function render_share(model, ev, opts) {
const shared_ev = model.all_events[ev.refs && ev.refs.root]
// If the shared event hasn't been resolved or leads to a circular event
// kind we will skip out on it.
if (!shared_ev || shared_ev.kind == KIND_SHARE)
return "";
opts.shared = {
pubkey: ev.pubkey,
profile: damus.profiles[ev.pubkey],
profile: model_get_profile(model, ev.pubkey),
share_time: ev.created_at,
share_evid: ev.id,
}
return render_event(damus, shared_ev, opts)
return render_event(model, shared_ev, opts)
}
function render_shared_by(ev, opts) {
@ -52,22 +58,18 @@ function render_event(model, ev, opts={}) {
return render_share(model, ev, opts);
}
const thread_root = (ev.refs && ev.refs.root) || ev.id;
const profile = model.profiles[ev.pubkey];
const profile = model_get_profile(model, ev.pubkey);
const delta = fmt_since_str(new Date().getTime(), ev.created_at*1000)
const border_bottom = opts.is_composing ? "" : "bottom-border";
let thread_btn = "";
return html`<div id="ev${ev.id}" class="event ${border_bottom}">
<div class="userpic">
$${render_pfp(ev.pubkey, profile)}
$${render_pfp(ev.pubkey, profile.data)}
</div>
<div class="event-content">
<div class="info">
$${render_name(ev.pubkey, profile)}
$${render_name(ev.pubkey, profile.data)}
<span class="timestamp" data-timestamp="${ev.created_at}">${delta}</span>
<button class="icon" title="View Thread" role="view-event" onclick="open_thread('${thread_root}')">
<img class="icon svg small" src="icon/open-thread.svg"/>
</button>
</div>
<div class="comment">
$${render_event_body(model, ev, opts)}
@ -77,15 +79,15 @@ function render_event(model, ev, opts={}) {
}
function render_event_nointeract(model, ev, opts={}) {
const profile = model.profiles[ev.pubkey];
const profile = model_get_profiles(model, ev.pubkey);
const delta = fmt_since_str(new Date().getTime(), ev.created_at*1000)
return html`<div class="event border-bottom">
<div class="userpic">
$${render_pfp(ev.pubkey, profile)}
$${render_pfp(ev.pubkey, profile.data)}
</div>
<div class="event-content">
<div class="info">
$${render_name(ev.pubkey, profile)}
$${render_name(ev.pubkey, profile.data)}
<span class="timestamp" data-timestamp="${ev.created_at}">${delta}</span>
</div>
<div class="comment">
@ -132,7 +134,7 @@ function render_reaction_group(model, emoji, reactions, reacting_to) {
break;
}
const pubkey = reactions[k].pubkey;
str += render_pfp(pubkey, model.profiles[pubkey], {noclick:true});
str += render_pfp(pubkey, model_get_profile(model, pubkey).data, {noclick:true});
}
let onclick = render_react_onclick(model.pubkey,
reacting_to.id, emoji, reactions);
@ -149,6 +151,7 @@ function render_action_bar(model, ev, opts={}) {
const { pubkey } = model;
let { can_delete, shared } = opts;
// TODO rewrite all of the toggle heart code. It's mine & I hate it.
const thread_root = (ev.refs && ev.refs.root) || ev.id;
const reaction = model_get_reacts_to(model, pubkey, ev.id, R_HEART);
const liked = !!reaction;
const reaction_id = reaction ? reaction.id : "";
@ -181,7 +184,15 @@ function render_action_bar(model, ev, opts={}) {
<img class="icon svg small" src="icon/event-delete.svg"/>
</button>`
}
return str + "</div>";
return str + `
<button class="icon" title="View Thread" role="view-event"
onclick="open_thread('${thread_root}')">
<img class="icon svg small" src="icon/open-thread.svg"/>
</button>
<button class="icon" title="View Replies" role="view-event"
onclick="open_thread('${ev.id}')">
<img class="icon svg small" src="icon/open-thread-here.svg"/>
</button></div>`;
}
function render_reactions_inner(model, ev) {

View file

@ -234,26 +234,29 @@ function view_render_event(model, ev, force=false) {
return el;
}
function view_timeline_update_profiles(model, ev) {
function view_timeline_update_profiles(model, pubkey) {
let xs, html;
const el = view_get_timeline_el();
const pk = ev.pubkey;
const p = model.profiles[pk];
const name = fmt_profile_name(p, fmt_pubkey(pk));
const pic = get_picture(pk, p)
const p = model_get_profile(model, pubkey);
const name = fmt_profile_name(p.data, fmt_pubkey(pubkey));
const pic = get_picture(pubkey, p.data)
for (const evid in model.elements) {
if (model.all_events[evid].pubkey != pk)
if (!event_contains_pubkey(model.all_events[evid], pubkey))
continue;
const el = model.elements[evid];
find_node(`.username[data-pubkey='${pk}']`, el).innerText = name;
// TODO Sometimes this fails and I don't know why
let img = find_node(`img.pfp[data-pubkey='${pk}']`, el);
if (img)
img.src = pic;
let xs;
xs = find_nodes(`.username[data-pubkey='${pubkey}']`, el)
xs.forEach((el)=> {
el.innerText = name;
});
xs = find_nodes(`img[data-pubkey='${pubkey}']`, el)
xs.forEach((el)=> {
el.src = pic;
});
}
// Update the profile view if it's active
if (el.dataset.mode == VM_USER && el.dataset.pubkey == pk) {
view_update_profile(model, pk);
if (el.dataset.mode == VM_USER && el.dataset.pubkey == pubkey) {
view_update_profile(model, pubkey);
}
}

View file

@ -62,7 +62,8 @@ function update_notification_markers(active) {
*/
function show_profile(pk) {
switch_view("profile");
const profile = DAMUS.profiles[pk];
const model = DAMUS;
const profile = model_get_profile(model, pk).data;
const el = find_node("#profile-view");
// TODO show loading indicator then render
@ -85,7 +86,7 @@ function show_profile(pk) {
btn_follow.dataset.pk = pk;
// TODO check follow status
btn_follow.innerText = 1 == 1 ? "Follow" : "Unfollow";
btn_follow.classList.toggle("hide", pk == DAMUS.pubkey);
btn_follow.classList.toggle("hide", pk == model.pubkey);
}
/* newlines_to_br takes a string and converts all newlines to HTML 'br' tags.
@ -215,7 +216,7 @@ function reply_all(evid) {
}
function redraw_my_pfp(model) {
const p = model.profiles[model.pubkey]
const p = model_get_profile(model, model.pubkey).data;
const html = render_pfp(model.pubkey, p || {});
const el = document.querySelector(".my-userpic");
el.innerHTML = html;
@ -306,23 +307,23 @@ function close_modal(el) {
}
function view_update_profile(model, pubkey) {
const profile = model.profiles[pubkey] || {};
const profile = model_get_profile(pubkey);
const el = find_node("[role='profile-info']");
const name = fmt_profile_name(profile, fmt_pubkey(pubkey));
const name = fmt_profile_name(profile.data, fmt_pubkey(pubkey));
find_node("#view header > label").innerText = name;
find_node("[role='profile-image']", el).src = get_picture(pubkey, profile);
find_node("[role='profile-image']", el).src = get_picture(pubkey, profile.data);
find_nodes("[role='profile-name']", el).forEach(el => {
el.innerText = name;
});
const el_nip5 = find_node("[role='profile-nip5']", el)
el_nip5.innerText = profile.nip05;
el_nip5.classList.toggle("hide", !profile.nip05);
el_nip5.innerText = profile.data.nip05;
el_nip5.classList.toggle("hide", !profile.data.nip05);
const el_desc = find_node("[role='profile-desc']", el)
el_desc.innerHTML = newlines_to_br(linkify(profile.about));
el_desc.classList.toggle("hide", !profile.about);
el_desc.innerHTML = newlines_to_br(linkify(profile.data.about));
el_desc.classList.toggle("hide", !profile.data.about);
find_node("button[role='copy-pk']", el).dataset.pk = pubkey;
find_node("button[role='edit-profile']", el)
@ -331,18 +332,18 @@ function view_update_profile(model, pubkey) {
const btn_follow = find_node("button[role='follow-user']", el)
btn_follow.dataset.pk = pubkey;
// TODO check follow status
btn_follow.innerText = contact_is_friend(DAMUS.contacts, pubkey) ? "Unfollow" : "Follow";
btn_follow.classList.toggle("hide", pubkey == DAMUS.pubkey);
btn_follow.innerText = contact_is_friend(model.contacts, pubkey) ? "Unfollow" : "Follow";
btn_follow.classList.toggle("hide", pubkey == model.pubkey);
}
const PROFILE_FIELDS = ['name', 'picture', 'nip05', 'about'];
function show_profile_editor() {
const p = DAMUS.profiles[DAMUS.pubkey];
const p = model_get_profile(DAMUS, DAMUS.pubkey);
const el = find_node("#profile-editor");
el.classList.remove("closed");
for (const key of PROFILE_FIELDS) {
find_node(`[name='${key}']`, el).value = p[key];
find_node(`[name='${key}']`, el).value = p.data[key];
}
}

View file

@ -103,16 +103,14 @@ function get_since_time(created_at) {
* parent element to search on.
*/
function find_node(selector, parentEl) {
const el = parentEl ? parentEl : document;
return el.querySelector(selector)
return (parentEl || document).querySelector(selector)
}
/* find_nodes is a short name for document.querySelectorAll, it also takes in a
* parent element to search on.
*/
function find_nodes(selector, parentEl) {
const el = parentEl ? parentEl : document;
return el.querySelectorAll(selector)
return (parentEl || document).querySelectorAll(selector);
}
/* uuidv4 returns a new uuid v4