Got a unified timeline working
This commit is contained in:
parent
00edee1cb3
commit
f417732942
8 changed files with 224 additions and 51 deletions
|
@ -65,6 +65,7 @@ async function damus_web_init_ready() {
|
|||
pool.on("event", on_pool_event);
|
||||
pool.on("notice", on_pool_notice);
|
||||
pool.on("eose", on_pool_eose);
|
||||
pool.on("ok", on_pool_ok);
|
||||
return pool
|
||||
}
|
||||
|
||||
|
@ -83,11 +84,12 @@ function on_pool_open(relay) {
|
|||
}
|
||||
|
||||
function on_pool_notice(relay, notice) {
|
||||
console.info("notice", notice);
|
||||
console.info("notice", relay.url, notice);
|
||||
// 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) {
|
||||
console.info("eose", relay.url, sub_id);
|
||||
const model = DAMUS;
|
||||
|
@ -100,16 +102,22 @@ async function on_pool_eose(relay, sub_id) {
|
|||
case ids.profiles:
|
||||
//const view = get_current_view()
|
||||
//handle_profiles_loaded(ids, model, view, relay)
|
||||
pool.unsubscribe(ids.profiles, relay);
|
||||
break;
|
||||
case ids.unknown:
|
||||
// TODO document why we unsub from unknowns
|
||||
pool.unsubscribe(ids.unknowns, relay);
|
||||
break;
|
||||
case ids.account:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function on_pool_ok(relay) {
|
||||
console.log("OK", arguments);
|
||||
}
|
||||
|
||||
function on_pool_event(relay, sub_id, ev) {
|
||||
console.info("event", relay.url, sub_id, ev);
|
||||
const model = DAMUS;
|
||||
const { ids, pool } = model;
|
||||
|
||||
|
@ -127,6 +135,21 @@ function on_pool_event(relay, sub_id, ev) {
|
|||
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,
|
||||
};
|
||||
|
||||
|
|
|
@ -77,21 +77,6 @@ function event_get_tag_refs(tags) {
|
|||
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) {
|
||||
log_warn("passes_spam_filter deprecated, use event_is_spam");
|
||||
return !event_is_spam(ev, contacts, pow);
|
||||
|
@ -103,5 +88,11 @@ function event_is_spam(ev, contacts, 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -185,3 +185,13 @@ function insert_event_sorted(evs, new_ev) {
|
|||
log_warn("insert_event_sorted deprecated, use events_insert_sorted");
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -198,6 +198,40 @@ function model_subscribe_defaults(model, 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() {
|
||||
return {
|
||||
done_init: {},
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
function linkify(text, show_media) {
|
||||
return text.replace(URL_REGEX, function(match, p1, p2, p3) {
|
||||
const url = p2+p3
|
||||
const parsed = new URL(url)
|
||||
const url = p2+p3;
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(url)
|
||||
} catch (err) {
|
||||
return match;
|
||||
}
|
||||
let html;
|
||||
if (show_media && is_img_url(parsed.pathname)) {
|
||||
html = `
|
||||
|
|
|
@ -97,7 +97,7 @@ function render_unknown_event(damus, ev) {
|
|||
}
|
||||
|
||||
function render_share(damus, view, ev, opts) {
|
||||
//todo validate content
|
||||
// TODO validate content
|
||||
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
|
||||
// the event
|
||||
|
@ -113,7 +113,7 @@ function render_share(damus, view, ev, opts) {
|
|||
|
||||
function render_comment_body(damus, ev, opts) {
|
||||
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
|
||||
|
||||
return `
|
||||
|
@ -165,11 +165,9 @@ function render_event(damus, view, ev, opts={}) {
|
|||
|
||||
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 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)
|
||||
if (deleted && !opts.is_reply)
|
||||
|
@ -183,10 +181,30 @@ function render_event(damus, view, ev, opts={}) {
|
|||
}
|
||||
|
||||
const has_top_line = replied_events !== ""
|
||||
const border_bottom = opts.is_composing || has_bot_line ? "" : "bottom-border";
|
||||
return `
|
||||
${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">
|
||||
${render_reply_line_top(has_top_line)}
|
||||
${deleted ? render_deleted_pfp() : render_pfp(ev.pubkey, profile)}
|
||||
|
@ -201,11 +219,10 @@ function render_event(damus, view, ev, opts={}) {
|
|||
</button>
|
||||
</div>
|
||||
<div class="comment">
|
||||
${deleted ? render_deleted_comment_body(ev, deleted) : render_comment_body(damus, ev, opts)}
|
||||
${body}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
</div>`
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
function render_action_bar(damus, ev, can_delete) {
|
||||
function render_action_bar(model, ev, opts={}) {
|
||||
let { can_delete } = opts;
|
||||
let delete_html = ""
|
||||
if (can_delete)
|
||||
delete_html = `
|
||||
|
@ -249,10 +267,9 @@ function render_action_bar(damus, ev, can_delete) {
|
|||
<img class="icon svg small" src="icon/event-delete.svg"/>
|
||||
</button>`
|
||||
|
||||
const groups = get_reactions(damus, ev.id)
|
||||
const like = "❤️"
|
||||
const likes = groups[like] || {}
|
||||
const react_onclick = render_react_onclick(damus.pubkey, ev.id, like, likes)
|
||||
const groups = get_reactions(model, ev.id)
|
||||
const react_onclick = render_react_onclick(model.pubkey, ev.id,
|
||||
"❤️", groups["❤️"] || {})
|
||||
return `
|
||||
<div class="action-bar">
|
||||
<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="") {
|
||||
return `
|
||||
<span class="username clickable" onclick="show_profile('${pk}')"
|
||||
data-pk="${pk}">${prefix}${render_name_plain(profile)}
|
||||
<span>
|
||||
${prefix}
|
||||
<span class="username clickable" data-pubkey="${pk}"
|
||||
onclick="show_profile('${pk}')">
|
||||
${render_name_plain(profile)}
|
||||
</span>
|
||||
</span>`
|
||||
}
|
||||
|
||||
|
@ -328,7 +349,7 @@ function render_deleted_name() {
|
|||
|
||||
function render_pfp(pk, 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)}';"
|
||||
src="${get_picture(pk, profile)}">`
|
||||
}
|
||||
|
|
|
@ -114,3 +114,84 @@ function get_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;
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,25 @@ function shuffle(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) {
|
||||
// don't count events far in the future
|
||||
if (created_at - now_sec >= 120) {
|
||||
|
@ -129,17 +148,6 @@ function difficulty_to_prefix(d) {
|
|||
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.
|
||||
*/
|
||||
function time_delta(current, previous) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue