Updates
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:
parent
1850ed6aa1
commit
f00f327a3d
11 changed files with 145 additions and 122 deletions
41
js/core.js
41
js/core.js
|
@ -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
|
||||
}
|
||||
|
||||
|
|
19
js/event.js
19
js/event.js
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
52
js/model.js
52
js/model.js
|
@ -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(),
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue