Got a unified timeline working

This commit is contained in:
Thomas Mathews 2022-12-16 15:15:33 -08:00
parent 00edee1cb3
commit f417732942
8 changed files with 224 additions and 51 deletions

View file

@ -65,6 +65,7 @@ async function damus_web_init_ready() {
pool.on("event", on_pool_event); pool.on("event", on_pool_event);
pool.on("notice", on_pool_notice); pool.on("notice", on_pool_notice);
pool.on("eose", on_pool_eose); pool.on("eose", on_pool_eose);
pool.on("ok", on_pool_ok);
return pool return pool
} }
@ -83,11 +84,12 @@ function on_pool_open(relay) {
} }
function on_pool_notice(relay, notice) { function on_pool_notice(relay, notice) {
console.info("notice", notice); console.info("notice", relay.url, notice);
// DO NOTHING // DO NOTHING
} }
// TODO document what EOSE is // on_pool_eose occurs when all storage from a relay has been sent to the
// client.
async function on_pool_eose(relay, sub_id) { async function on_pool_eose(relay, sub_id) {
console.info("eose", relay.url, sub_id); console.info("eose", relay.url, sub_id);
const model = DAMUS; const model = DAMUS;
@ -100,16 +102,22 @@ async function on_pool_eose(relay, sub_id) {
case ids.profiles: case ids.profiles:
//const view = get_current_view() //const view = get_current_view()
//handle_profiles_loaded(ids, model, view, relay) //handle_profiles_loaded(ids, model, view, relay)
pool.unsubscribe(ids.profiles, relay);
break; break;
case ids.unknown: case ids.unknown:
// TODO document why we unsub from unknowns // TODO document why we unsub from unknowns
pool.unsubscribe(ids.unknowns, relay); pool.unsubscribe(ids.unknowns, relay);
break; break;
case ids.account:
break;
} }
} }
function on_pool_ok(relay) {
console.log("OK", arguments);
}
function on_pool_event(relay, sub_id, ev) { function on_pool_event(relay, sub_id, ev) {
console.info("event", relay.url, sub_id, ev);
const model = DAMUS; const model = DAMUS;
const { ids, pool } = model; const { ids, pool } = model;
@ -127,6 +135,21 @@ function on_pool_event(relay, sub_id, ev) {
break; break;
} }
// TODO do smart view update logic here // Refresh view
state.invalidated.push(ev.id);
clearTimeout(state.timer);
state.timer = setTimeout(() => {
view_timeline_update(model, state);
}, 1000);
// If it was metadata let's refresh the usernames and pics
if (ev.kind == 0) {
view_timeline_update_profiles(model, state, ev);
}
} }
const state = {
invalidated: [],
timer: -1,
};

View file

@ -77,21 +77,6 @@ function event_get_tag_refs(tags) {
return {root, reply, pubkeys} return {root, reply, pubkeys}
} }
function events_insert_sorted(evs, new_ev) {
for (let i = 0; i < evs.length; i++) {
const ev = evs[i]
if (new_ev.id === ev.id) {
return false
}
if (new_ev.created_at > ev.created_at) {
evs.splice(i, 0, new_ev)
return true
}
}
evs.push(new_ev)
return true
}
function passes_spam_filter(contacts, ev, pow) { function passes_spam_filter(contacts, ev, pow) {
log_warn("passes_spam_filter deprecated, use event_is_spam"); log_warn("passes_spam_filter deprecated, use event_is_spam");
return !event_is_spam(ev, contacts, pow); return !event_is_spam(ev, contacts, pow);
@ -103,5 +88,11 @@ function event_is_spam(ev, contacts, pow) {
return ev.pow >= pow return ev.pow >= pow
} }
function event_cmp_created(a, b) {
if (a.created_at > b.created_at)
return 1;
if (a.created_at < b.created_at)
return -1;
return 0;
}

View file

@ -185,3 +185,13 @@ function insert_event_sorted(evs, new_ev) {
log_warn("insert_event_sorted deprecated, use events_insert_sorted"); log_warn("insert_event_sorted deprecated, use events_insert_sorted");
events_insert_sorted(evs, new_ev); events_insert_sorted(evs, new_ev);
} }
function can_reply(ev) {
log_warn("can_reply is deprecated, use event_can_reply");
return event_can_reply(ev);
}
function should_add_to_timeline(ev) {
// TODO rename should_add_to_timeline to is_timeline_event
log_warn("should_add_to_timeline is deprecated, use event_is_timeline");
return event_is_timeline(ev);
}

View file

@ -198,6 +198,40 @@ function model_subscribe_defaults(model, relay) {
filters_subscribe(filters, model.pool, [relay]); filters_subscribe(filters, model.pool, [relay]);
} }
function test_model_events_arr() {
const arr = model_events_arr({all_events: {
"c": {name: "c", created_at: 2},
"a": {name: "a", created_at: 0},
"b": {name: "b", created_at: 1},
"e": {name: "e", created_at: 4},
"d": {name: "d", created_at: 3},
}});
let last;
while(arr.length > 0) {
let ev = arr.pop();
log_debug("test:", ev.name, ev.created_at);
if (!last) {
last = ev;
continue;
}
if (ev.created_at > last.created_at) {
log_error(`ev ${ev.name} should be before ${last.name}`);
}
last = ev;
}
}
function model_events_arr(model) {
const events = model.all_events;
let arr = [];
for (const evid in events) {
const ev = events[evid];
const i = arr_bsearch_insert(arr, ev, event_cmp_created);
arr.splice(i, 0, ev);
}
return arr;
}
function new_model() { function new_model() {
return { return {
done_init: {}, done_init: {},

View file

@ -1,7 +1,12 @@
function linkify(text, show_media) { function linkify(text, show_media) {
return text.replace(URL_REGEX, function(match, p1, p2, p3) { return text.replace(URL_REGEX, function(match, p1, p2, p3) {
const url = p2+p3 const url = p2+p3;
const parsed = new URL(url) let parsed;
try {
parsed = new URL(url)
} catch (err) {
return match;
}
let html; let html;
if (show_media && is_img_url(parsed.pathname)) { if (show_media && is_img_url(parsed.pathname)) {
html = ` html = `

View file

@ -97,7 +97,7 @@ function render_unknown_event(damus, ev) {
} }
function render_share(damus, view, ev, opts) { function render_share(damus, view, ev, opts) {
//todo validate content // TODO validate content
const shared_ev = damus.all_events[ev.refs && ev.refs.root] const shared_ev = damus.all_events[ev.refs && ev.refs.root]
// share isn't resolved yet. that's ok, we can render this when we have // share isn't resolved yet. that's ok, we can render this when we have
// the event // the event
@ -113,7 +113,7 @@ function render_share(damus, view, ev, opts) {
function render_comment_body(damus, ev, opts) { function render_comment_body(damus, ev, opts) {
const can_delete = damus.pubkey === ev.pubkey; const can_delete = damus.pubkey === ev.pubkey;
const bar = !can_reply(ev) || opts.nobar? "" : render_action_bar(damus, ev, can_delete) const bar = !can_reply(ev) || opts.nobar? "" : render_action_bar(damus, ev, {can_delete})
const show_media = !opts.is_composing const show_media = !opts.is_composing
return ` return `
@ -165,11 +165,9 @@ function render_event(damus, view, ev, opts={}) {
view.rendered.add(ev.id) view.rendered.add(ev.id)
const profile = damus.profiles[ev.pubkey]
const delta = time_delta(new Date().getTime(), ev.created_at*1000)
const has_bot_line = opts.is_reply const has_bot_line = opts.is_reply
const reply_line_bot = (has_bot_line && render_reply_line_bot()) || "" const reply_line_bot = (has_bot_line && render_reply_line_bot()) || ""
if (opts.is_composing) has_bot_line = true;
const deleted = is_deleted(damus, ev.id) const deleted = is_deleted(damus, ev.id)
if (deleted && !opts.is_reply) if (deleted && !opts.is_reply)
@ -183,10 +181,30 @@ function render_event(damus, view, ev, opts={}) {
} }
const has_top_line = replied_events !== "" const has_top_line = replied_events !== ""
const border_bottom = opts.is_composing || has_bot_line ? "" : "bottom-border";
return ` return `
${replied_events} ${replied_events}
<div id="ev${ev.id}" class="event ${border_bottom}"> ` + render_event2(damus, ev, {
deleted,
has_top_line,
has_bot_line,
reply_line_bot,
});
}
function render_event2(model, ev, opts={}) {
let {
deleted,
has_bot_line,
has_top_line,
reply_line_bot,
} = opts
const profile = model.profiles[ev.pubkey]
const delta = time_delta(new Date().getTime(), ev.created_at*1000)
const border_bottom = has_bot_line ? "" : "bottom-border";
const body = deleted ? render_deleted_comment_body(ev, deleted) : render_comment_body(model, ev, opts)
if (!reply_line_bot) reply_line_bot = '';
return `<div id="ev${ev.id}" class="event ${border_bottom}">
<div class="userpic"> <div class="userpic">
${render_reply_line_top(has_top_line)} ${render_reply_line_top(has_top_line)}
${deleted ? render_deleted_pfp() : render_pfp(ev.pubkey, profile)} ${deleted ? render_deleted_pfp() : render_pfp(ev.pubkey, profile)}
@ -201,11 +219,10 @@ function render_event(damus, view, ev, opts={}) {
</button> </button>
</div> </div>
<div class="comment"> <div class="comment">
${deleted ? render_deleted_comment_body(ev, deleted) : render_comment_body(damus, ev, opts)} ${body}
</div> </div>
</div> </div>
</div> </div>`
`
} }
function render_react_onclick(our_pubkey, reacting_to, emoji, reactions) { function render_react_onclick(our_pubkey, reacting_to, emoji, reactions) {
@ -241,7 +258,8 @@ function render_reaction(model, reaction) {
return render_pfp(reaction.pubkey, profile) return render_pfp(reaction.pubkey, profile)
} }
function render_action_bar(damus, ev, can_delete) { function render_action_bar(model, ev, opts={}) {
let { can_delete } = opts;
let delete_html = "" let delete_html = ""
if (can_delete) if (can_delete)
delete_html = ` delete_html = `
@ -249,10 +267,9 @@ function render_action_bar(damus, ev, can_delete) {
<img class="icon svg small" src="icon/event-delete.svg"/> <img class="icon svg small" src="icon/event-delete.svg"/>
</button>` </button>`
const groups = get_reactions(damus, ev.id) const groups = get_reactions(model, ev.id)
const like = "❤️" const react_onclick = render_react_onclick(model.pubkey, ev.id,
const likes = groups[like] || {} "❤️", groups["❤️"] || {})
const react_onclick = render_react_onclick(damus.pubkey, ev.id, like, likes)
return ` return `
<div class="action-bar"> <div class="action-bar">
<button class="icon" title="Reply" onclick="reply_to('${ev.id}')"> <button class="icon" title="Reply" onclick="reply_to('${ev.id}')">
@ -317,8 +334,12 @@ function render_mentioned_name(pk, profile) {
function render_name(pk, profile, prefix="") { function render_name(pk, profile, prefix="") {
return ` return `
<span class="username clickable" onclick="show_profile('${pk}')" <span>
data-pk="${pk}">${prefix}${render_name_plain(profile)} ${prefix}
<span class="username clickable" data-pubkey="${pk}"
onclick="show_profile('${pk}')">
${render_name_plain(profile)}
</span>
</span>` </span>`
} }
@ -328,7 +349,7 @@ function render_deleted_name() {
function render_pfp(pk, profile) { function render_pfp(pk, profile) {
const name = render_name_plain(profile) const name = render_name_plain(profile)
return `<img class="pfp" title="${name}" return `<img class="pfp" data-pubkey="${pk}" title="${name}"
onerror="this.onerror=null;this.src='${robohash(pk)}';" onerror="this.onerror=null;this.src='${robohash(pk)}';"
src="${get_picture(pk, profile)}">` src="${get_picture(pk, profile)}">`
} }

View file

@ -114,3 +114,84 @@ function get_current_view()
return DAMUS.views[DAMUS.current_view] return DAMUS.views[DAMUS.current_view]
} }
function view_timeline_update_profiles(model, state, ev) {
let xs, html;
const el = find_node("#view #home-view .events");
const pk = ev.pubkey;
const p = model.profiles[pk];
// If it's my pubkey let's redraw my pfp that is not located in the view
if (pk == model.pubkey) {
redraw_my_pfp(model);
}
// Update displayed names
xs = el.querySelectorAll(`.username[data-pubkey='${pk}']`)
html = render_name_plain(p);
for (const x of xs) {
x.innerText = html;
}
// Update profile pictures
xs = el.querySelectorAll(`img.pfp[data-pubkey='${pk}']`);
html = get_picture(pk, p)
for (const x of xs) {
x.src = html;
}
}
function view_timeline_update(model, state) {
const el = find_node("#view #home-view .events");
// for each event not rendered, go through the list and render it marking
// it as rendered and adding it to the appropriate fragment. fragments are
// created based on slices in the existing timeline. fragments are started
// at the previous event
// const fragments = {};
// const cache = {};
// Dumb function to insert needed events
const all = model_events_arr(model);
while (state.invalidated.length > 0) {
var evid = state.invalidated.pop();
var ev = model.all_events[evid];
if (!event_is_renderable(ev)) {
// TODO check deleted
let x = find_node("#ev"+evid, el);
if (x) el.removeChild(x);
continue;
}
// TODO if event is not viewable for page, simply hide it
// if event is in el already, do nothing or update?
let ev_el = find_node("#ev"+evid, el);
if (ev_el) {
continue;
} else {
let div = document.createElement("div");
div.innerHTML = render_event2(model, ev, {});
ev_el = div.firstChild;
}
// find prior event element and insert it before that
let prior_el;
let prior_idx = arr_bsearch_insert(all, ev, event_cmp_created);
while (prior_idx > 0 && !prior_el) {
prior_el = find_node("#ev"+all[prior_idx].id, el);
prior_idx--;
}
if (!prior_el) {
el.appendChild(ev_el);
} else {
el.insertBefore(ev_el, prior_el);
}
}
}
function event_is_renderable(ev={}) {
if (ev.is_spam) return false;
if (ev.kind != 1) return false;
return true;
}

View file

@ -50,6 +50,25 @@ function shuffle(arr) {
return arr; return arr;
} }
/* arr_bsearch_insert finds the point in the array that an item should be
* inserted at based on the 'cmp' function used.
*/
function arr_bsearch_insert(arr, item, cmp) {
let start = 0;
let end = arr.length - 1;
while (start <= end) {
let middle = parseInt((start + end) / 2);
let x = cmp(item, arr[middle])
if (x > 0)
start = middle + 1;
else if (x < 0)
end = middle - 1;
else
return middle;
}
return start;
}
function is_valid_time(now_sec, created_at) { function is_valid_time(now_sec, created_at) {
// don't count events far in the future // don't count events far in the future
if (created_at - now_sec >= 120) { if (created_at - now_sec >= 120) {
@ -129,17 +148,6 @@ function difficulty_to_prefix(d) {
return s return s
} }
function can_reply(ev) {
log_debug("can_reply is deprecated, use event_can_reply");
return event_can_reply(ev);
}
function should_add_to_timeline(ev) {
// TODO rename should_add_to_timeline to is_timeline_event
log_debug("should_add_to_timeline is deprecated, use event_is_timeline");
return event_is_timeline(ev);
}
/* time_delta returns a string of the time of current since previous. /* time_delta returns a string of the time of current since previous.
*/ */
function time_delta(current, previous) { function time_delta(current, previous) {