diff --git a/css/styles.css b/css/styles.css index b9adaec..4a2d25e 100644 --- a/css/styles.css +++ b/css/styles.css @@ -115,6 +115,11 @@ th, td { #nav > div[data-active="messages"] [data-view="dm"] img.active { display: block; } +#new-note { + background: white; + height: 56px; + border-radius: 38px; +} #app-icon-logo > img { width: 36px; diff --git a/icon/new-note.svg b/icon/new-note.svg new file mode 100644 index 0000000..400aec8 --- /dev/null +++ b/icon/new-note.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.html b/index.html index 7605feb..62f574f 100644 --- a/index.html +++ b/index.html @@ -63,9 +63,6 @@ - - + @@ -119,10 +115,6 @@
-
-
- -
- - - -
-
@@ -199,7 +176,7 @@
-
+
@@ -266,7 +243,7 @@
- + @@ -282,7 +259,10 @@
-
+
+ +
+
diff --git a/js/core.js b/js/core.js index 0934183..47bcef8 100644 --- a/js/core.js +++ b/js/core.js @@ -21,6 +21,12 @@ const STANDARD_KINDS = [ KIND_REACTION, KIND_SHARE, ]; +const PUBLIC_KINDS = [ + KIND_NOTE, + KIND_DELETE, + KIND_REACTION, + KIND_SHARE, +]; function get_local_state(key) { if (DAMUS[key] != null) diff --git a/js/main.js b/js/main.js index 3005d1f..1ac48c8 100644 --- a/js/main.js +++ b/js/main.js @@ -6,14 +6,13 @@ const IMG_EVENT_LIKE = "/icon/event-like.svg"; const IMG_NO_USER = "/icon/no-user.svg"; const SID_META = "meta"; -const SID_HISTORY = "history"; -const SID_NOTIFICATIONS = "notifications"; -const SID_DMS_OUT = "dms_out"; -const SID_DMS_IN = "dms_in"; -const SID_EXPLORE = "explore"; -const SID_PROFILES = "profiles"; -const SID_THREAD = "thread"; -const SID_FRIENDS = "friends"; +const SID_HISTORY = "hist"; +const SID_NOTIFICATIONS = "noti"; +const SID_DMS_OUT = "dout"; +const SID_DMS_IN = "din"; +const SID_PROFILES = "prof"; +const SID_THREAD = "thrd"; +const SID_FRIENDS = "frds"; // This is our main entry. // https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event @@ -69,6 +68,7 @@ async function signin() { } async function webapp_init() { + let err; const model = DAMUS; // WARNING Order Matters! @@ -81,11 +81,10 @@ async function webapp_init() { // Load data from storage await model_load_settings(model); - let err; - err = await contacts_load(model); + /*err = await contacts_load(model); if (err) { window.alert("Unable to load contacts."); - } + }*/ init_settings(model); // Create our pool so that event processing functions can work @@ -132,7 +131,7 @@ function parse_url_mode() { } switch (mode) { case VM_FRIENDS: - opts.hide_replys = true; + //opts.hide_replys = true; break; case VM_THREAD: opts.thread_id = parts[1]; @@ -201,7 +200,7 @@ function on_pool_open(relay) { // Get our notifications relay.subscribe(SID_NOTIFICATIONS, [{ - kinds: STANDARD_KINDS, + kinds: PUBLIC_KINDS, "#p": [pubkey], limit: 5000, }]); @@ -215,19 +214,6 @@ function on_pool_open(relay) { kinds: [KIND_DM], authors: [pubkey], }]); - - // Subscribe to the world as it will serve our friends, notifications, and - // explore views - relay.subscribe(SID_EXPLORE, [{ - kinds: STANDARD_KINDS, - limit: 5000, - }]); - - // Grab our friends history so our default timeline looks loaded - if (model.contacts.friends.size > 0) { - model_get_relay_que(model, relay).contacts_init = true; - fetch_friends_history(Array.from(model.contacts.friends), model.pool, relay); - } } function on_pool_notice(relay, notice) { @@ -246,15 +232,19 @@ async function on_pool_eose(relay, sub_id) { switch (sid) { case SID_HISTORY: case SID_THREAD: - case SID_FRIENDS: view_timeline_refresh(model); pool.unsubscribe(sub_id, relay); break + case SID_FRIENDS: + view_timeline_refresh(model); + //pool.unsubscribe(sub_id, relay); + break case SID_META: // if sid is ours and we did not init properly (must be login) then // we will fetch our friends history now - if (model.pubkey == identifier && - !model_get_relay_que(model, relay).contacts_init) { + //if (model.pubkey == identifier && + // !model_get_relay_que(model, relay).contacts_init) { + if (model.pubkey == identifier) { fetch_friends_history(Array.from(model.contacts.friends), pool, relay); log_debug("Got our friends after no init & fetching our friends"); @@ -296,7 +286,6 @@ function fetch_profile_info(pubkey, pool, relay) { pool.subscribe(sid, [{ kinds: [KIND_METADATA, KIND_CONTACT, KIND_RELAY], authors: [pubkey], - limit: 1, }], relay); return sid; } @@ -304,7 +293,7 @@ function fetch_profile_info(pubkey, pool, relay) { function fetch_profile(pubkey, pool, relay) { fetch_profile_info(pubkey, pool, relay); pool.subscribe(`${SID_HISTORY}:${pubkey}`, [{ - kinds: STANDARD_KINDS, + kinds: PUBLIC_KINDS, authors: [pubkey], limit: 1000, }], relay); @@ -313,17 +302,19 @@ function fetch_profile(pubkey, pool, relay) { function fetch_thread_history(evid, pool) { const sid = `${SID_THREAD}:${evid}` pool.subscribe(sid, [{ - kinds: STANDARD_KINDS, - limit: 1000, + kinds: PUBLIC_KINDS, "#e": [evid], }]); log_debug(`fetching thread ${sid}`); } function fetch_friends_history(friends, pool, relay) { + // TODO only fetch friends history from their desired relay instead of + // pinging all of the relays pool.subscribe(SID_FRIENDS, [{ - kinds: STANDARD_KINDS, + kinds: PUBLIC_KINDS, authors: friends, - limit: 500, + limit: 5000, }], relay); + log_debug(`fetching friends history`); } diff --git a/js/model.js b/js/model.js index abf1c5b..11e198a 100644 --- a/js/model.js +++ b/js/model.js @@ -7,6 +7,7 @@ function model_process_event(model, relay, ev) { return; } + let fetch_profile = false; model.all_events[ev.id] = ev; ev.refs = event_get_tag_refs(ev.tags); ev.pow = event_calculate_pow(ev); @@ -15,6 +16,10 @@ function model_process_event(model, relay, ev) { // integers can't be used. let fn; switch(ev.kind) { + case KIND_NOTE: + case KIND_SHARE: + fetch_profile = true; + break; case KIND_METADATA: fn = model_process_event_metadata; break; @@ -43,11 +48,13 @@ function model_process_event(model, relay, ev) { return; // 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); - } - }); + if (fetch_profile) { + event_get_pubkeys(ev).forEach((pubkey) => { + if (!model_has_profile(model, pubkey)) { + model_que_profile(model, relay, pubkey); + } + }); + } } function model_get_relay_que(model, relay) { diff --git a/js/ui/fmt.js b/js/ui/fmt.js index 160acd6..f36e2a8 100644 --- a/js/ui/fmt.js +++ b/js/ui/fmt.js @@ -1,4 +1,4 @@ -function linkify(text="", show_media=false) { +function linkify(text="") { return text.replace(URL_REGEX, function(match, p1, p2, p3) { const url = p2+p3; let parsed; @@ -7,24 +7,17 @@ function linkify(text="", show_media=false) { } catch (err) { return match; } - let markup; - if (show_media && is_img_url(parsed.pathname)) { - markup = html` - `; - } else if (show_media && is_video_url(parsed.pathname)) { - markup = html` - `; - } else { - markup = html`${url}`; + let markup = html`${url}`; + if (is_img_url(parsed.pathname)) { + //markup += ``; + } else if (is_video_url(parsed.pathname)) { + //markup += ``; } return p1+markup; }) } -function format_content(model, ev, show_media) { +function format_content(model, ev) { if (ev.kind === KIND_REACTION) { if (ev.content === "" || ev.content === "+") return "❤️" @@ -32,7 +25,7 @@ function format_content(model, ev, show_media) { } const content = (ev.kind == KIND_DM ? ev.decrypted || ev.content : ev.content) .trim(); - const body = fmt_mentions(model, fmt_body(content, show_media), ev.tags); + const body = fmt_mentions(model, fmt_body(content), ev.tags); let cw = get_content_warning(ev.tags) if (cw !== null) { let cwHTML = "Content Warning" @@ -97,25 +90,8 @@ function fmt_mentions(model, str, tags) { /* fmt_body will parse images, blockquotes, and sanitize the content. */ -function fmt_body(content, show_media) { - const split = content.split("\n") - let blockin = false - return split.reduce((str, line) => { - if (line !== "" && line[0] === '>') { - if (!blockin) { - str += "" - blockin = true - } - str += linkify(html`${line.slice(1)}`, show_media) - } else { - if (blockin) { - blockin = false - str += "" - } - str += linkify(html`${line}`, show_media) - } - return str + "
" - }, "") +function fmt_body(content) { + return newlines_to_br(linkify(content)) } /* DEPRECATED: use fmt_name diff --git a/js/ui/render.js b/js/ui/render.js index 90b9f39..4c34542 100644 --- a/js/ui/render.js +++ b/js/ui/render.js @@ -169,17 +169,9 @@ function render_react_onclick(our_pubkey, reacting_to, emoji, reactions) { function render_reaction_group(model, emoji, reactions, reacting_to) { let count = 0; - let str = ""; for (const k in reactions) { count++; - if (count > 5) - continue; - const pubkey = reactions[k].pubkey; - str += render_profile_img(model_get_profile(model, pubkey), - {noclick:true}); } - if (count > 5) - str = `${count}`; let onclick = render_react_onclick(model.pubkey, reacting_to.id, emoji, reactions); return html` @@ -187,7 +179,7 @@ function render_reaction_group(model, emoji, reactions, reacting_to) { ${emoji} - $${str} + ${count} `; } diff --git a/js/ui/state.js b/js/ui/state.js index 375f13f..f85300d 100644 --- a/js/ui/state.js +++ b/js/ui/state.js @@ -1,5 +1,4 @@ const VM_FRIENDS = "friends"; // mine + only events that are from my contacts -const VM_EXPLORE = "explore"; // all events const VM_NOTIFICATIONS = "notifications"; // reactions & replys const VM_DM = "dm"; // all events of KIND_DM aimmed at user const VM_DM_THREAD = "dmthread"; // all events from a user of KIND_DM @@ -9,7 +8,6 @@ const VM_SETTINGS = "settings"; const VIEW_NAMES= {}; VIEW_NAMES[VM_FRIENDS] = "Home"; -VIEW_NAMES[VM_EXPLORE] = "Explore"; VIEW_NAMES[VM_NOTIFICATIONS] = "Notifications"; VIEW_NAMES[VM_DM] = "Messages"; VIEW_NAMES[VM_DM_THREAD] = "DM"; @@ -120,7 +118,6 @@ function view_timeline_apply_mode(model, mode, opts={}, push_state=true) { find_node("#view header > label").innerText = name; 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 && mode != VM_DM_THREAD); const timeline_el = find_node("#timeline"); timeline_el.classList.toggle("reverse", mode == VM_THREAD); timeline_el.classList.toggle("hide", mode == VM_SETTINGS || mode == VM_DM); @@ -131,8 +128,6 @@ function view_timeline_apply_mode(model, mode, opts={}, push_state=true) { dms_available() : true); find_node("#header-tools button[action='mark-all-read']") .classList.toggle("hide", mode != VM_DM); - find_node("#header-tools button[action='toggle-hide-replys']") - .classList.toggle("hide", mode != VM_FRIENDS); // Show/hide different profile image in header const show_mypfp = mode != VM_DM_THREAD && mode != VM_USER; @@ -219,8 +214,7 @@ function view_timeline_refresh(model, mode, opts={}) { show_more = false; // If we reached the limit there is "probably" more to show so show // the more button - const is_more_mode = mode == VM_FRIENDS || mode == VM_NOTIFICATIONS || - mode == VM_EXPLORE; + const is_more_mode = mode == VM_FRIENDS || mode == VM_NOTIFICATIONS; if (is_more_mode && show_more) { find_node("#show-more").classList.remove("hide"); } @@ -426,10 +420,8 @@ function view_timeline_update_profiles(model, pubkey) { const name = fmt_name(p); const pic = get_profile_pic(p); for (const evid in model.elements) { - // Omitting this because we want to update profiles and names on all - // reactions - //if (!event_contains_pubkey(model.all_events[evid], pubkey)) - // continue; + // XXX if possible update profile pics in a smarter way + // this may be perhaps a micro optimization tho update_el_profile(model.elements[evid], pubkey, name, pic); } // Update the profile view if it's active @@ -446,7 +438,6 @@ function view_timeline_update_profiles(model, pubkey) { // be caught by the process above. update_el_profile(find_node("#dms"), pubkey, name, pic); update_el_profile(find_node("#view header"), pubkey, name, pic); - update_el_profile(find_node("#newpost"), pubkey, name, pic); } function update_el_profile(el, pubkey, name, pic) { @@ -507,8 +498,6 @@ function view_mode_contains_event(model, ev, mode, opts={}) { return false; } switch(mode) { - case VM_EXPLORE: - return ev.kind != KIND_REACTION; case VM_USER: return opts.pubkey && ev.pubkey == opts.pubkey; case VM_FRIENDS: @@ -603,35 +592,39 @@ function init_my_pfp(model) { } function init_postbox(model) { - const el = find_node("#newpost"); - find_node("textarea", el).addEventListener("input", oninput_post); - find_node("button[role='send']").addEventListener("click", onclick_send); - find_node("button[role='toggle-cw']") - .addEventListener("click", onclick_toggle_cw); - // Do reply box - // TODO refactor & cleanup reply modal init find_node("#reply-content").addEventListener("input", oninput_post); find_node("button[name='reply']") .addEventListener("click", onclick_reply); find_node("button[name='reply-all']") .addEventListener("click", onclick_reply); + find_node("button[name='send']") + .addEventListener("click", onclick_send); } async function onclick_reply(ev) { do_send_reply(ev.target.dataset.all == "1"); } async function onclick_send(ev) { - const el = view_get_timeline_el(); - const mode = el.dataset.mode; + const el = find_node("#reply-modal"); const pubkey = await get_pubkey(); - const el_input = document.querySelector("#post-input"); - const el_cw = document.querySelector("#content-warning-input"); + const el_input = el.querySelector("#reply-content"); let post = { pubkey, kind: KIND_NOTE, created_at: new_creation_time(), content: el_input.value, - tags: el_cw.value ? [["content-warning", el_cw.value]] : [], - } + tags: [], + }; + post.id = await nostrjs.calculate_id(post); + post = await sign_event(post); + broadcast_event(post); + + // Reset UI + el_input.value = ""; + trigger_postbox_assess(el_input); + + /* + const el_cw = document.querySelector("#content-warning-input"); + //tags: el_cw.value ? [["content-warning", el_cw.value]] : [], // Handle DM type post if (mode == VM_DM_THREAD) { @@ -645,15 +638,7 @@ async function onclick_send(ev) { post.content = await window.nostr.nip04.encrypt(target, post.content); } - // Send it - post.id = await nostrjs.calculate_id(post) - post = await sign_event(post) - broadcast_event(post); - - // Reset UI - el_input.value = ""; - el_cw.value = ""; - trigger_postbox_assess(el_input); + el_cw.value = "";*/ } /* oninput_post checks the content of the textarea and updates the size * of it's element. Additionally I will toggle the enabled state of the sending @@ -752,6 +737,8 @@ function onclick_any(ev) { case "toggle-hide-replys": toggle_hide_replys(el); break; + case "new-note": + new_note(); } } diff --git a/js/ui/util.js b/js/ui/util.js index 2453862..1b2ba8c 100644 --- a/js/ui/util.js +++ b/js/ui/util.js @@ -97,18 +97,39 @@ async function do_send_reply(all=false) { close_modal(modal); } +function update_reply_box(state="new") { + const isnew = state == "new"; + const modal = document.querySelector("#reply-modal"); + modal.querySelector("#replying-to").classList.toggle("hide", isnew); + modal.querySelector("header label").textContent = isnew ? "New Note" : "Replying To"; + modal.querySelector(".post-tools.new").classList.toggle("hide", !isnew); + modal.querySelector(".post-tools.reply").classList.toggle("hide", isnew); +} + +function new_note() { + const modal = document.querySelector("#reply-modal"); + const inputbox = modal.querySelector("#reply-content"); + update_reply_box("new"); + inputbox.placeholder = "What's up?"; + modal.showModal(); + inputbox.focus(); +} + function reply(evid) { const ev = DAMUS.all_events[evid] const modal = document.querySelector("#reply-modal") const replybox = modal.querySelector("#reply-content") const replying_to = modal.querySelector("#replying-to") + update_reply_box("reply"); replying_to.dataset.evid = evid + replying_to.classList.remove("hide"); replying_to.innerHTML = render_event_nointeract(DAMUS, ev, { is_composing: true, nobar: true }); + replybox.placeholder = "Reply..."; modal.showModal(); - replybox.focus() + replybox.focus(); } function update_favicon(path) {