-
diff --git a/web/js/core.js b/web/js/core.js
index 1bc58c3..5cefa03 100644
--- a/web/js/core.js
+++ b/web/js/core.js
@@ -1,3 +1,8 @@
+const KIND_METADATA = 0;
+const KIND_NOTE = 1;
+const KIND_SERVER = 2;
+const KIND_REACTION = 7;
+
function get_local_state(key) {
if (DAMUS[key] != null)
return DAMUS[key]
@@ -197,17 +202,13 @@ function gather_reply_tags(pubkey, from) {
return tags
}
-function get_tag_event(tag)
-{
+function get_tag_event(tag) {
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 null
}
diff --git a/web/js/damus.js b/web/js/damus.js
index c7ce0f3..80fdc74 100644
--- a/web/js/damus.js
+++ b/web/js/damus.js
@@ -3,7 +3,8 @@ let DAMUS
const STANDARD_KINDS = [1, 42, 5, 6, 7];
const BOOTSTRAP_RELAYS = [
- "wss://relay.damus.io",
+ "wss://nostr.rdfriedl.com",
+ //"wss://relay.damus.io",
//"wss://nostr-relay.wlvs.space",
//"wss://nostr-pub.wellorder.net"
]
@@ -57,10 +58,11 @@ async function damus_web_init_ready() {
model.view_el = document.querySelector("#view")
//load_cache(model)
- switch_view('home')
+ view_timeline_apply_mode(model, VM_FRIENDS);
document.addEventListener('visibilitychange', () => {
update_title(model)
- })
+ });
+ update_timestamps();
pool.on("open", on_pool_open);
pool.on("event", on_pool_event);
pool.on("notice", on_pool_notice);
@@ -69,8 +71,15 @@ async function damus_web_init_ready() {
return pool
}
+function update_timestamps() {
+ setTimeout(() => {
+ update_timestamps();
+ view_timeline_update_timestamps();
+ }, 60 * 1000);
+}
+
function on_pool_open(relay) {
- console.info("opened relay", relay);
+ log_info("opened relay", relay);
const model = DAMUS;
// We check the cache if we have init anything, if not we do our inital
// otherwise we do a get since last
@@ -84,32 +93,30 @@ function on_pool_open(relay) {
}
function on_pool_notice(relay, notice) {
- console.info("notice", relay.url, notice);
+ log_info("notice", relay.url, notice);
// DO NOTHING
}
// 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);
+ //console.info("eose", relay.url, sub_id);
const model = DAMUS;
const { ids, pool } = model;
switch (sub_id) {
case ids.home:
- //const events = model.views.home.events
- //handle_comments_loaded(ids, model, events, relay)
+ const events = model_events_arr(model);
+ // TODO filter out events to friends of friends
+ on_eose_comments(ids, model, events, relay)
+ pool.unsubscribe(ids.home, relay);
break;
case ids.profiles:
- //const view = get_current_view()
- //handle_profiles_loaded(ids, model, view, relay)
- pool.unsubscribe(ids.profiles, relay);
+ model.pool.unsubscribe(ids.profiles, relay);
+ on_eose_profiles(ids, model, relay)
break;
case ids.unknown:
- // TODO document why we unsub from unknowns
pool.unsubscribe(ids.unknowns, relay);
break;
- case ids.account:
- break;
}
}
@@ -120,13 +127,15 @@ function on_pool_ok(relay) {
function on_pool_event(relay, sub_id, ev) {
const model = DAMUS;
const { ids, pool } = model;
-
+
+ // Process event and apply side effects
if (!model.all_events[ev.id]) {
model.all_events[ev.id] = ev;
model_process_event(model, ev);
// schedule_save_events(model);
}
+ // Update subscriptions
switch (sub_id) {
case ids.account:
model.done_init[relay] = true;
@@ -134,22 +143,44 @@ function on_pool_event(relay, sub_id, ev) {
model_subscribe_defaults(model, relay);
break;
}
-
- // 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,
-};
+function on_eose_profiles(ids, model, relay) {
+ const prefix = difficulty_to_prefix(model.pow);
+ const fofs = Array.from(model.contacts.friend_of_friends);
+ const standard_kinds = [1,42,5,6,7];
+ let pow_filter = {kinds: standard_kinds, limit: 50};
+ if (model.pow > 0)
+ pow_filter.ids = [ prefix ];
+ let explore_filters = [ pow_filter ];
+ if (fofs.length > 0)
+ explore_filters.push({kinds: standard_kinds, authors: fofs, limit: 50});
+ model.pool.subscribe(ids.explore, explore_filters, relay);
+}
+
+function on_eose_comments(ids, model, events, relay) {
+ const pubkeys = events.reduce((s, ev) => {
+ s.add(ev.pubkey);
+ for (const tag of ev.tags) {
+ if (tag.length >= 2 && tag[0] === "p") {
+ if (!model.profile_events[tag[1]])
+ s.add(tag[1]);
+ }
+ }
+ return s;
+ }, new Set());
+ const authors = Array.from(pubkeys)
+ // load profiles and noticed chatrooms
+ const profile_filter = {kinds: [0,3], authors: authors};
+ let filters = [];
+ if (authors.length > 0)
+ filters.push(profile_filter);
+ if (filters.length === 0) {
+ //log_debug("No profiles filters to request...")
+ return
+ }
+ //console.log("subscribe", profiles_id, filter, relay)
+ //log_debug("subscribing to profiles on %s", relay.url)
+ model.pool.subscribe(ids.profiles, filters, relay)
+}
diff --git a/web/js/event.js b/web/js/event.js
index c490864..7634bca 100644
--- a/web/js/event.js
+++ b/web/js/event.js
@@ -51,7 +51,6 @@ function event_get_tag_refs(tags) {
let ids = []
let pubkeys = []
let root, reply
- let i = 0
for (const tag of tags) {
if (tag.length >= 4 && tag[0] == "e") {
ids.push(tag[1])
@@ -65,7 +64,6 @@ function event_get_tag_refs(tags) {
} else if (tag.length >= 2 && tag[0] == "p") {
pubkeys.push(tag[1])
}
- i++
}
if (!(root && reply) && ids.length > 0) {
if (ids.length === 1)
@@ -77,11 +75,6 @@ function event_get_tag_refs(tags) {
return {root, reply, pubkeys}
}
-function passes_spam_filter(contacts, ev, pow) {
- log_warn("passes_spam_filter deprecated, use event_is_spam");
- return !event_is_spam(ev, contacts, pow);
-}
-
function event_is_spam(ev, contacts, pow) {
if (contacts.friend_of_friends.has(ev.pubkey))
return true
@@ -96,3 +89,34 @@ function event_cmp_created(a, b) {
return 0;
}
+/* event_refs_event checks if event A references event B in its tags.
+ */
+function event_refs_event(a, b) {
+ for (const tag of a.tags) {
+ if (tag.length >= 2 && tag[0] === "e" && tag[1] == b.id)
+ return true;
+ }
+ return false;
+}
+
+function event_get_last_tags(ev) {
+ let o = {};
+ for (const tag of ev.tags) {
+ if (tag.length >= 2 && (tag[0] === "e" || tag[0] === "p"))
+ o[tag[0]] = tag[1];
+ }
+ return o;
+}
+
+/* event_get_reacted_to returns the reacted to event & pubkey (e, p). Returns
+ * undefined if invalid or incomplete.
+ */
+function event_parse_reaction(ev) {
+ if (!is_valid_reaction_content(ev.content) || ev.kind != 7)
+ return;
+ const o = event_get_last_tags(ev);
+ if (o["e"] && o["p"]) {
+ return o;
+ }
+}
+
diff --git a/web/js/lib.js b/web/js/lib.js
index 079b183..6f0586d 100644
--- a/web/js/lib.js
+++ b/web/js/lib.js
@@ -57,116 +57,6 @@ function handle_redraw_logic(model, view_name)
view.redraw_timer = setTimeout(redraw_events.bind(null, model, view), 600)
}
-/*
-function handle_home_event(model, relay, sub_id, ev) {
- const ids = model.ids
-
- // ignore duplicates
- if (!has_event(model, ev.id)) {
- model.all_events[ev.id] = ev
- process_event(model, ev)
- schedule_save_events(model)
- }
-
- ev = model.all_events[ev.id]
-
- let is_new = true
- switch (sub_id) {
- case model.ids.explore:
- const view = model.views.explore
-
- // show more things in explore timeline
- if (should_add_to_explore_timeline(model.contacts, view, ev, model.pow)) {
- view.seen.add(ev.pubkey)
- is_new = insert_event_sorted(view.events, ev)
- }
-
- if (is_new)
- handle_redraw_logic(model, 'explore')
- break;
-
- case model.ids.notifications:
- if (should_add_to_notification_timeline(model.pubkey, model.contacts, ev, model.pow))
- is_new = insert_event_sorted(model.views.notifications.events, ev)
-
- if (is_new)
- handle_redraw_logic(model, 'notifications')
- break;
-
- case model.ids.home:
- if (should_add_to_timeline(ev))
- is_new = insert_event_sorted(model.views.home.events, ev)
-
- if (is_new)
- handle_redraw_logic(model, 'home')
- break;
- case model.ids.account:
- switch (ev.kind) {
- case 3:
- model.done_init[relay] = true
- model.pool.unsubscribe(model.ids.account, relay)
- send_home_filters(model, relay)
- break
- }
- break
- case model.ids.profiles:
- break
- }
-}*/
-
-function handle_profiles_loaded(ids, model, view, relay) {
- // stop asking for profiles
- model.pool.unsubscribe(ids.profiles, relay)
-
- //redraw_events(model, view)
- redraw_my_pfp(model)
-
- const prefix = difficulty_to_prefix(model.pow)
- const fofs = Array.from(model.contacts.friend_of_friends)
- const standard_kinds = [1,42,5,6,7]
- let pow_filter = {kinds: standard_kinds, limit: 50}
- if (model.pow > 0)
- pow_filter.ids = [ prefix ]
-
- let explore_filters = [ pow_filter ]
-
- if (fofs.length > 0) {
- explore_filters.push({kinds: standard_kinds, authors: fofs, limit: 50})
- }
-
- model.pool.subscribe(ids.explore, explore_filters, relay)
-}
-
-// load profiles after comment notes are loaded
-function handle_comments_loaded(ids, model, events, relay) {
- const pubkeys = events.reduce((s, ev) => {
- s.add(ev.pubkey)
- for (const tag of ev.tags) {
- if (tag.length >= 2 && tag[0] === "p") {
- if (!model.profile_events[tag[1]])
- s.add(tag[1])
- }
- }
- return s
- }, new Set())
- const authors = Array.from(pubkeys)
-
- // load profiles and noticed chatrooms
- const profile_filter = {kinds: [0,3], authors: authors}
-
- let filters = []
- if (authors.length > 0)
- filters.push(profile_filter)
- if (filters.length === 0) {
- log_debug("No profiles filters to request...")
- return
- }
-
- //console.log("subscribe", profiles_id, filter, relay)
- log_debug("subscribing to profiles on %s", relay.url)
- model.pool.subscribe(ids.profiles, filters, relay)
-}
-
/* DEPRECATED */
function is_deleted(model, evid) {
@@ -194,4 +84,8 @@ function should_add_to_timeline(ev) {
log_warn("should_add_to_timeline is deprecated, use event_is_timeline");
return event_is_timeline(ev);
}
+function passes_spam_filter(contacts, ev, pow) {
+ log_warn("passes_spam_filter deprecated, use event_is_spam");
+ return !event_is_spam(ev, contacts, pow);
+}
diff --git a/web/js/model.js b/web/js/model.js
index e36fa24..e7b9ddc 100644
--- a/web/js/model.js
+++ b/web/js/model.js
@@ -3,8 +3,10 @@
* and fetching of unknown pubkey profiles.
*/
function model_process_event(model, ev) {
- ev.refs = event_get_tag_refs(ev.tags)
- ev.pow = event_calculate_pow(ev)
+ ev.refs = event_get_tag_refs(ev.tags);
+ ev.pow = event_calculate_pow(ev);
+
+ // TODO this doesn't actually work because of async nature
ev.is_spam = !event_is_spam(ev, model.contacts, model.pow);
// Process specific event needs based on it's kind. Not using a map because
@@ -47,7 +49,15 @@ function model_process_event(model, ev) {
// If we find some unknown ids lets schedule their subscription for info
if (model_event_has_unknown_ids(model, ev))
schedule_unknown_refetch(model);
+
+ // Refresh timeline
+ model.invalidated.push(ev.id);
+ clearTimeout(inv_timer);
+ inv_timer = setTimeout(() => {
+ view_timeline_update(model);
+ }, 1000);
}
+let inv_timer;
//function process_chatroom_event(model, ev) {
// model.chatrooms[ev.id] = safe_parse_json(ev.content,
@@ -63,22 +73,19 @@ function model_process_event_profile(model, ev) {
return
model.profile_events[ev.pubkey] = ev.id
model.profiles[ev.pubkey] = safe_parse_json(ev.content, "profile contents")
+ view_timeline_update_profiles(model, ev);
}
/* model_process_event_reaction updates the reactions dictionary
*/
function model_process_event_reaction(model, ev) {
- if (!is_valid_reaction_content(ev.content))
- return
- let last = {}
- for (const tag of ev.tags) {
- if (tag.length >= 2 && (tag[0] === "e" || tag[0] === "p"))
- last[tag[0]] = tag[1]
- }
- if (last.e) {
- model.reactions_to[last.e] = model.reactions_to[last.e] || new Set()
- model.reactions_to[last.e].add(ev.id)
- }
+ let reaction = event_parse_reaction(ev);
+ if (!reaction)
+ return;
+ if (!model.reactions_to[reaction.e])
+ model.reactions_to[reaction.e] = new Set();
+ model.reactions_to[reaction.e].add(ev.id);
+ view_timeline_update_reaction(model, ev);
}
function model_process_event_contact(model, ev) {
@@ -244,20 +251,6 @@ function new_model() {
unknown_pks: {},
deletions: {},
but_wait_theres_more: 0,
- cw_open: {},
- views: {
- home: new_timeline('home'),
- explore: {
- ...new_timeline('explore'),
- seen: new Set(),
- },
- notifications: {
- ...new_timeline('notifications'),
- max_depth: 1,
- },
- profile: new_timeline('profile'),
- thread: new_timeline('thread'),
- },
pow: 0, // pow difficulty target
deleted: {},
profiles: {},
@@ -268,15 +261,6 @@ function new_model() {
friends: new Set(),
friend_of_friends: new Set(),
},
- }
-}
-
-function new_timeline(name) {
- return {
- name,
- events: [],
- rendered: new Set(),
- depths: {},
- expanded: new Set(),
+ invalidated: [],
}
}
diff --git a/web/js/ui/fmt.js b/web/js/ui/fmt.js
index 14101a6..3a23733 100644
--- a/web/js/ui/fmt.js
+++ b/web/js/ui/fmt.js
@@ -31,10 +31,8 @@ function format_content(ev, show_media) {
return "❤️"
return sanitize(ev.content.trim())
}
-
const content = ev.content.trim()
const body = convert_quote_blocks(content, show_media)
-
let cw = get_content_warning(ev.tags)
if (cw !== null) {
let cwHTML = "Content Warning"
@@ -43,15 +41,13 @@ function format_content(ev, show_media) {
} else {
cwHTML += `: "
${cw}".`
}
- const open = !!DAMUS.cw_open[ev.id]? "open" : ""
return `
-
+
${cwHTML}
${body}
`
}
-
return body
}
diff --git a/web/js/ui/render.js b/web/js/ui/render.js
index 4e71b1c..ac2beba 100644
--- a/web/js/ui/render.js
+++ b/web/js/ui/render.js
@@ -2,7 +2,7 @@
// is done by simple string manipulations & templates. If you need to write
// loops simply write it in code and return strings.
-function render_timeline_event(damus, view, ev)
+/*function render_timeline_event(damus, view, ev)
{
const root_id = get_thread_root_id(damus, ev.id)
const max_depth = root_id ? get_thread_max_depth(damus, view, root_id) : get_default_max_depth(damus, view)
@@ -17,7 +17,7 @@ function render_events(damus, view) {
return view.events
.filter((ev, i) => i < 140)
.map((ev) => render_timeline_event(damus, view, ev)).join("\n")
-}
+}*/
function render_reply_line_top(has_top_line) {
const classes = has_top_line ? "" : "invisible"
@@ -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 `
@@ -157,7 +157,7 @@ function render_deleted_comment_body(ev, deleted) {
`
}
-function render_event(damus, view, ev, opts={}) {
+/*function render_event(damus, view, ev, opts={}) {
if (ev.kind === 6)
return render_share(damus, view, ev, opts)
if (shouldnt_render_event(damus.pubkey, view, ev, opts))
@@ -189,42 +189,61 @@ function render_event(damus, view, ev, opts={}) {
has_bot_line,
reply_line_bot,
});
-}
+}*/
-function render_event2(model, ev, opts={}) {
+function render_event(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)
+ const thread_root = (ev.refs && ev.refs.root) || ev.id;
+ const profile = model.profiles[ev.pubkey];
+ const delta = fmt_since_str(new Date().getTime(), ev.created_at*1000)
+ const border_bottom = opts.is_composing || has_bot_line ? "" : "bottom-border";
+ let thread_btn = "";
if (!reply_line_bot) reply_line_bot = '';
return `
${render_reply_line_top(has_top_line)}
- ${deleted ? render_deleted_pfp() : render_pfp(ev.pubkey, profile)}
+ ${render_pfp(ev.pubkey, profile)}
${reply_line_bot}
${render_name(ev.pubkey, profile)}
-
${delta}
-
`
}
+function render_event_nointeract(model, ev, opts={}) {
+ const profile = model.profiles[ev.pubkey];
+ const delta = fmt_since_str(new Date().getTime(), ev.created_at*1000)
+ return `
+
+ ${render_pfp(ev.pubkey, profile)}
+
+
+
+ ${render_name(ev.pubkey, profile)}
+ ${delta}
+
+
+
+
`
+}
+
function render_react_onclick(our_pubkey, reacting_to, emoji, reactions) {
const reaction = reactions[our_pubkey]
if (!reaction) {
@@ -337,7 +356,7 @@ function render_name(pk, profile, prefix="") {
${prefix}
+ onclick="open_profile('${pk}')">
${render_name_plain(profile)}
`
@@ -349,9 +368,13 @@ function render_deleted_name() {
function render_pfp(pk, profile) {
const name = render_name_plain(profile)
- return `
`
+ src="${get_picture(pk, profile)}"/>`
}
function render_deleted_pfp() {
@@ -361,8 +384,7 @@ function render_deleted_pfp() {
`
}
-function render_loading_spinner()
-{
+function render_loading_spinner() {
return `
diff --git a/web/js/ui/state.js b/web/js/ui/state.js
index 5a6006f..50fc75f 100644
--- a/web/js/ui/state.js
+++ b/web/js/ui/state.js
@@ -13,25 +13,7 @@ function get_thread_max_depth(damus, view, root_id) {
return view.depths[root_id]
}
-function shouldnt_render_event(our_pk, view, ev, opts) {
- return !opts.is_composing &&
- !view.expanded.has(ev.id) &&
- view.rendered.has(ev.id)
-}
-
-function toggle_content_warning(el) {
- const id = el.id.split("_")[1]
- const ev = DAMUS.all_events[id]
-
- if (!ev) {
- log_debug("could not find content-warning event", id)
- return
- }
-
- DAMUS.cw_open[id] = el.open
-}
-
-function expand_thread(id, reply_id) {
+/*function expand_thread(id, reply_id) {
const view = get_current_view()
const root_id = get_thread_root_id(DAMUS, id)
if (!root_id) {
@@ -41,7 +23,7 @@ function expand_thread(id, reply_id) {
view.expanded.add(reply_id)
view.depths[root_id] = get_thread_max_depth(DAMUS, view, root_id) + 1
redraw_events(DAMUS, view)
-}
+}*/
function get_thread_root_id(damus, id) {
const ev = damus.all_events[id]
@@ -52,71 +34,18 @@ function get_thread_root_id(damus, id) {
return ev.refs && ev.refs.root
}
-function redraw_events(damus, view) {
- //log_debug("redrawing events for", view)
- view.rendered = new Set()
- const events_el = damus.view_el.querySelector(`#${view.name}-view > .events`)
- events_el.innerHTML = render_events(damus, view)
+function switch_view(mode, opts) {
+ log_warn("switch_view deprecated, use view_timeline_apply_mode");
+ view_timeline_apply_mode(DAMUS, mode, opts);
}
-function redraw_timeline_events(damus, name) {
- const view = DAMUS.views[name]
- const events_el = damus.view_el.querySelector(`#${name}-view > .events`)
- if (view.events.length > 0) {
- redraw_events(damus, view)
- } else {
- events_el.innerHTML = render_loading_spinner()
- }
+function view_get_timeline_el() {
+ return find_node("#timeline");
}
-function switch_view(name, opts={})
-{
- if (name === DAMUS.current_view) {
- log_debug("Not switching to '%s', we are already there", name)
- return
- }
-
- const last = get_current_view()
- if (!last) {
- // render initial
- DAMUS.current_view = name
- redraw_timeline_events(DAMUS, name)
- return
- }
-
- log_debug("switching to '%s' by hiding '%s'", name, DAMUS.current_view)
-
- DAMUS.current_view = name
- const current = get_current_view()
- const last_el = get_view_el(last.name)
- const current_el = get_view_el(current.name)
-
- if (last_el)
- last_el.classList.add("hide");
-
- // TODO accomodate views that do not render events
- // TODO find out if having multiple event divs is slow
- //redraw_timeline_events(DAMUS, name)
-
- find_node("#nav > div[data-active]").dataset.active = name;
-
- if (current_el)
- current_el.classList.remove("hide");
-}
-
-function get_current_view()
-{
- // TODO resolve memory & html descriptencies
- // Currently there is tracking of which divs are visible in HTML/CSS and
- // which is active in memory, simply resolve this by finding the visible
- // element instead of tracking it in memory (or remove dom elements). This
- // would simplify state tracking IMO - Thomas
- return DAMUS.views[DAMUS.current_view]
-}
-
-function view_timeline_update_profiles(model, state, ev) {
+function view_timeline_update_profiles(model, ev) {
let xs, html;
- const el = find_node("#view #home-view .events");
+ const el = view_get_timeline_el();
const pk = ev.pubkey;
const p = model.profiles[pk];
@@ -140,8 +69,98 @@ function view_timeline_update_profiles(model, state, ev) {
}
}
-function view_timeline_update(model, state) {
- const el = find_node("#view #home-view .events");
+function view_timeline_update_timestamps(model) {
+ const el = view_get_timeline_el();
+ let xs = el.querySelectorAll(".timestamp");
+ let now = new Date().getTime();
+ for (const x of xs) {
+ let t = parseInt(x.dataset.timestamp)
+ x.innerText = fmt_since_str(now, t*1000);
+ }
+}
+
+function view_timeline_update_reaction(model, ev) {
+ // TODO loop through elements with ev reactions to and update them
+}
+
+const VM_FRIENDS = "friends";
+const VM_EXPLORE = "explore";
+const VM_NOTIFICATIONS = "notifications";
+const VM_THREAD = "thread";
+const VM_USER = "user";
+// friends: mine + only events that are from my contacts
+// explore: all events
+// notifications: reactions & replys
+// thread: all events in response to target event
+// user: all events by pubkey
+
+function view_mode_contains_event(model, ev, mode, opts={}) {
+ switch(mode) {
+ case VM_EXPLORE:
+ return ev.kind != KIND_REACTION;
+ case VM_USER:
+ return opts.pubkey && ev.pubkey == opts.pubkey;
+ case VM_FRIENDS:
+ return ev.pubkey == model.pubkey || contact_is_friend(model.contacts, ev.pubkey);
+ case VM_THREAD:
+ return ev.id == opts.thread_id || (ev.refs && (
+ ev.refs.root == opts.thread_id ||
+ ev.refs.reply == opts.thread_id));
+ //event_refs_event(ev, opts.thread_id);
+ case VM_NOTIFICATIONS:
+ return event_refs_pubkey(ev, model.pubkey);
+ }
+ return false;
+}
+
+function view_timeline_apply_mode(model, mode, opts={}) {
+ let xs;
+ const { pubkey, thread_id } = opts;
+ const el = view_get_timeline_el();
+
+ el.dataset.mode = mode;
+ switch(mode) {
+ case VM_THREAD:
+ el.dataset.threadId = thread_id;
+ case VM_USER:
+ el.dataset.pubkey = pubkey;
+ break;
+ default:
+ delete el.dataset.threadId;
+ delete el.dataset.pubkey;
+ break;
+ }
+
+ const names = {};
+ names[VM_FRIENDS] = "Home";
+ names[VM_EXPLORE] = "Explore";
+ names[VM_NOTIFICATIONS] = "Notifications";
+ names[VM_USER] = "Profile";
+ names[VM_THREAD] = "Thread";
+ find_node("#view header > label").innerText = mode == VM_USER ? render_name_plain(DAMUS.profiles[opts.pubkey]) : names[mode];
+ find_node("#nav > div[data-active]").dataset.active = names[mode].toLowerCase();
+ find_node("#view [role='profile-info']").classList.toggle("hide", mode != VM_USER);
+ find_node("#newpost").classList.toggle("hide", mode != VM_FRIENDS);
+
+ xs = el.querySelectorAll(".event");
+ for (const x of xs) {
+ let evid = x.id.substr(2);
+ let ev = model.all_events[evid];
+ x.classList.toggle("hide",
+ !view_mode_contains_event(model, ev, mode, opts));
+ }
+}
+
+/* view_timeline_update iterates through invalidated event ids and either adds
+ * or removes them from the timeline.
+ */
+function view_timeline_update(model) {
+ const el = view_get_timeline_el();
+ const mode = el.dataset.mode;
+ const opts = {
+ thread_id: el.dataset.threadId,
+ pubkey: el.dataset.pubkey,
+ };
// 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
@@ -151,27 +170,31 @@ function view_timeline_update(model, state) {
// const cache = {};
// Dumb function to insert needed events
+ let visible_count = 0;
const all = model_events_arr(model);
- while (state.invalidated.length > 0) {
- var evid = state.invalidated.pop();
+ while (model.invalidated.length > 0) {
+ var evid = model.invalidated.pop();
var ev = model.all_events[evid];
- if (!event_is_renderable(ev)) {
+ if (!event_is_renderable(ev) || model_is_event_deleted(model, evid)) {
// 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, {});
+ div.innerHTML = render_event(model, ev, {});
ev_el = div.firstChild;
+ if (!view_mode_contains_event(model, ev, mode, opts)) {
+ ev_el.classList.add("hide");
+ } else {
+ visible_count++;
+ }
}
// find prior event element and insert it before that
@@ -187,6 +210,9 @@ function view_timeline_update(model, state) {
el.insertBefore(ev_el, prior_el);
}
}
+
+ if (visible_count > 0)
+ find_node("#view .loading-events").classList.add("hide");
}
function event_is_renderable(ev={}) {
diff --git a/web/js/ui/util.js b/web/js/ui/util.js
index 1502c15..bb13200 100644
--- a/web/js/ui/util.js
+++ b/web/js/ui/util.js
@@ -172,17 +172,16 @@ async function do_send_reply() {
}
function reply_to(evid) {
+ const ev = DAMUS.all_events[evid]
const modal = document.querySelector("#reply-modal")
const replybox = modal.querySelector("#reply-content")
- modal.classList.remove("closed")
const replying_to = modal.querySelector("#replying-to")
-
replying_to.dataset.evid = evid
-
- const ev = DAMUS.all_events[evid]
- const view = get_current_view()
- replying_to.innerHTML = render_event(DAMUS, view, ev, {is_composing: true, nobar: true, max_depth: 1})
-
+ replying_to.innerHTML = render_event_nointeract(DAMUS, ev, {
+ is_composing: true,
+ nobar: true
+ });
+ modal.classList.remove("closed")
replybox.focus()
}
@@ -190,7 +189,7 @@ function redraw_my_pfp(model, force = false) {
const p = model.profiles[model.pubkey]
if (!p) return;
const html = render_pfp(model.pubkey, p);
- const el = document.querySelector(".my-userpic")
+ const el = document.querySelector(".my-userpic");
if (!force && el.dataset.loaded) return;
el.dataset.loaded = true;
el.innerHTML = html;
@@ -261,4 +260,35 @@ function get_privkey() {
return privkey
}
+function open_thread(thread_id) {
+ view_timeline_apply_mode(DAMUS, VM_THREAD, { thread_id });
+}
+function open_profile(pubkey) {
+ view_timeline_apply_mode(DAMUS, VM_USER, { pubkey });
+
+ const profile = DAMUS.profiles[pubkey];
+ const el = find_node("[role='profile-info']");
+ // TODO show loading indicator then render
+
+ find_node("[role='profile-image']", el).src = get_picture(pubkey, profile);
+ find_nodes("[role='profile-name']", el).forEach(el => {
+ el.innerText = render_name_plain(profile);
+ });
+
+ const el_nip5 = find_node("[role='profile-nip5']", el)
+ el_nip5.innerText = profile.nip05;
+ el_nip5.classList.toggle("hide", !profile.nip05);
+
+ const el_desc = find_node("[role='profile-desc']", el)
+ el_desc.innerHTML = newlines_to_br(profile.about);
+ el_desc.classList.toggle("hide", !profile.about);
+
+ find_node("button[role='copy-pk']", el).dataset.pk = 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);
+}
diff --git a/web/js/util.js b/web/js/util.js
index e382381..0cb6665 100644
--- a/web/js/util.js
+++ b/web/js/util.js
@@ -22,7 +22,7 @@ function safe_parse_json(data, message) {
let value = undefined;
try {
value = JSON.parse(data);
- } catch (e) {
+ } catch (err) {
log_error(`${message} : unable to parse JSON`, err, data);
}
return value;
@@ -151,6 +151,11 @@ function difficulty_to_prefix(d) {
/* time_delta returns a string of the time of current since previous.
*/
function time_delta(current, previous) {
+ log_warn("time_delta deprecated, use fmt_since_str");
+ fmt_since_str(current, previous);
+}
+
+function fmt_since_str(current, previous) {
var msPerMinute = 60 * 1000;
var msPerHour = msPerMinute * 60;
var msPerDay = msPerHour * 24;