diff --git a/css/styles.css b/css/styles.css index 25172ee..5aeb1f1 100644 --- a/css/styles.css +++ b/css/styles.css @@ -20,6 +20,27 @@ a { a:visited { color: var(--clrLinkVisited); } +table { + width: 100%; + border-collapse: collapse; +} +thead { + font-weight: bold; +} +th, td { + padding: 5px 0; + font-size: var(--fsNormal); +} + +.row { + margin: 15px 0; +} +.mr-some { + margin-right: 15px; +} +.align-right { + text-align: right; +} #gsticker { position: absolute; @@ -163,14 +184,14 @@ button.nav > img.icon { width: 750px; min-height: 100vh; } -#view header { +#view > div > header { position: sticky; top: 0; z-index: var(--zHeader); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); } -#view header > label { +#view > div > header > label { padding: 15px; font-size: 22px; font-weight: 800; @@ -484,6 +505,16 @@ label[role="profile-nip5"] { margin: 15px 0; } +/* Settings */ + +#settings section { + margin: 15px; +} +#settings header > label { + font-weight: bold; + font-size: var(--fsEnlarged); +} + /* Inputs */ .block { @@ -493,6 +524,11 @@ label[role="profile-nip5"] { width: 100%; } +/* Prevent events from inside button sub elements */ +button > * { + pointer-events: none; +} + input[type="text"] { background: transparent; border: none; diff --git a/icon/add-relay.svg b/icon/add-relay.svg new file mode 100644 index 0000000..6a90197 --- /dev/null +++ b/icon/add-relay.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icon/settings-active.svg b/icon/settings-active.svg new file mode 100644 index 0000000..14a662e --- /dev/null +++ b/icon/settings-active.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icon/settings.svg b/icon/settings.svg new file mode 100644 index 0000000..d774f6f --- /dev/null +++ b/icon/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.html b/index.html index bb58f38..ea675e1 100644 --- a/index.html +++ b/index.html @@ -17,6 +17,7 @@ + @@ -69,11 +70,10 @@
- -
+
@@ -161,6 +164,42 @@ Show New (0)
+
+
+
+ + +
+ + + + + + + + + +
AddressOption
+
+
+
+
+
+
+ + + +
+
diff --git a/js/main.js b/js/main.js index f0383e1..dcded44 100644 --- a/js/main.js +++ b/js/main.js @@ -1,11 +1,5 @@ let DAMUS = new_model(); -const BOOTSTRAP_RELAYS = [ - "wss://relay.damus.io", - "wss://nostr-relay.wlvs.space", - "wss://nostr-pub.wellorder.net", -] - // TODO autogenerate these constants with a bash script const IMG_EVENT_LIKED = "icon/event-liked.svg"; const IMG_EVENT_LIKE = "icon/event-like.svg"; @@ -87,8 +81,10 @@ async function webapp_init() { window.alert("Unable to load contacts."); } + init_settings(model); + // Create our pool so that event processing functions can work - const pool = nostrjs.RelayPool(BOOTSTRAP_RELAYS); + const pool = nostrjs.RelayPool(model.relays); model.pool = pool pool.on("open", on_pool_open); pool.on("event", on_pool_event); diff --git a/js/model.js b/js/model.js index 877f43e..fc24088 100644 --- a/js/model.js +++ b/js/model.js @@ -290,6 +290,8 @@ async function model_save_settings(model) { store.put({ pubkey: model.pubkey, notifications_last_viewed: model.notifications.last_viewed, + relays: Array.from(model.relays), + embeds: model.embeds, }); }; } @@ -306,6 +308,9 @@ async function model_load_settings(model) { const settings = ev.target.result; if (settings) { model.notifications.last_viewed = settings.notifications_last_viewed; + if (settings.length) + model.relays = new Set(settings.relays); + model.embeds = settings.embeds ? settings.embeds : "friends"; } db.close(); resolve(); @@ -377,16 +382,6 @@ function new_model() { last_viewed: 0, // time since last looking at notifications count: 0, // the number not seen since last looking }, - max_depth: 2, - reactions_to: {}, - - unknown_ids: {}, - unknown_pks: {}, - but_wait_theres_more: 0, - - deletions: {}, - deleted: {}, - pow: 0, // pow difficulty target profiles: new Map(), // pubkey => profile data contacts: { event: null, @@ -396,5 +391,17 @@ function new_model() { invalidated: [], // event ids which should be added/removed elements: {}, // map of evid > rendered element relay_que: new Map(), + relays: new Set([ + "wss://relay.damus.io", + "wss://nostr-relay.wlvs.space", + "wss://nostr-pub.wellorder.net", + ]), + embeds: "friends", // friends, everyone + + max_depth: 2, + reactions_to: {}, + deletions: {}, + deleted: {}, + pow: 0, // pow difficulty target }; } diff --git a/js/nostr.js b/js/nostr.js index 2668eaa..aa31ee3 100644 --- a/js/nostr.js +++ b/js/nostr.js @@ -55,7 +55,7 @@ RelayPool.prototype.remove = function relayPoolRemove(url) { for (const relay of this.relays) { if (relay.url === url) { relay.ws && relay.ws.close() - this.relays = this.replays.splice(i, 1) + this.relays.splice(i, 1) return true } diff --git a/js/ui/render.js b/js/ui/render.js index 4a80e8c..11c4f4c 100644 --- a/js/ui/render.js +++ b/js/ui/render.js @@ -102,8 +102,12 @@ function render_event_body(model, ev, opts) { const can_delete = model.pubkey === ev.pubkey || (opts.shared && model.pubkey == opts.shared.pubkey); // Only show media for content that is by friends. - const show_media = !opts.is_composing && - model.contacts.friends.has(ev.pubkey); + let show_media = true; + if (opts.is_composing) { + show_media = false; + } else if (model.embeds == "friends") { + show_media = model.contacts.friends.has(ev.pubkey); + } let str = "
"; str += shared ? render_shared_by(ev, opts) : render_replying_to(model, ev); str += `

diff --git a/js/ui/settings.js b/js/ui/settings.js new file mode 100644 index 0000000..7546685 --- /dev/null +++ b/js/ui/settings.js @@ -0,0 +1,71 @@ +function init_settings(model) { + const el = find_node("#settings"); + find_node("#add-relay", el).addEventListener("click", on_click_add_relay); + const embeds_el = find_node("input[name='show_embeds']", el); + embeds_el.addEventListener("click", on_click_toggle_embeds); + embeds_el.checked = model.embeds != "friends"; + find_node("[role='sign-out']", el).addEventListener("click", on_click_sign_out); + find_node("[role='about-nostr']", el).addEventListener("click", open_faqs); + const rlist = find_node("#relay-list tbody", el); + model.relays.forEach((str) => { + rlist.appendChild(new_relay_item(str)); + }); +} + +function new_relay_item(str) { + const tr = document.createElement('tr'); + tr.innerHTML = `${str} + + + `; + find_node("button", tr).addEventListener("click", on_click_remove_relay); + return tr; +} + +function on_click_add_relay(ev) { + const model = DAMUS; + const address = prompt("Please provide a websocket address:", "wss://"); + log_debug("got address", address); + // TODO add relay validation + if (!model.pool.add(address)) + return; + model.relays.add(address); + find_node("#relay-list tbody").appendChild(new_relay_item(address)); + model_save_settings(model); +} + +function on_click_toggle_embeds(ev) { + const model = DAMUS; + model.embeds = ev.target.checked ? "everyone" : "friends"; + window.alert("You will need to refresh to see changes."); + model_save_settings(model); +} + +async function on_click_sign_out(ev) { + if (confirm("Are you sure you want to sign out?")) { + localStorage.clear(); + await dbclear(); + window.location.reload(); + } +} + +function on_click_remove_relay(ev) { + const model = DAMUS; + const address = ev.target.dataset.address; + if (!model.pool.remove(address)) + return; + model.relays.delete(address); + let parent = ev.target; + while (parent) { + if (parent.matches("tr")) { + parent.parentElement.removeChild(parent); + break; + } + parent = parent.parentElement; + } + model_save_settings(model); +} diff --git a/js/ui/state.js b/js/ui/state.js index fca710b..d7d263a 100644 --- a/js/ui/state.js +++ b/js/ui/state.js @@ -1,8 +1,9 @@ -const VM_FRIENDS = "friends"; // mine + only events that are from my contacts -const VM_EXPLORE = "explore"; // all events +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_THREAD = "thread"; // all events in response to target event -const VM_USER = "user"; // all events by pubkey +const VM_THREAD = "thread"; // all events in response to target event +const VM_USER = "user"; // all events by pubkey +const VM_SETTINGS = "settings"; function view_get_timeline_el() { return find_node("#timeline"); @@ -51,15 +52,24 @@ function view_timeline_apply_mode(model, mode, opts={}) { names[VM_NOTIFICATIONS] = "Notifications"; names[VM_USER] = "Profile"; names[VM_THREAD] = "Thread"; + names[VM_SETTINGS] = "Settings"; // Do some visual updates find_node("#view header > label").innerText = 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); - find_node("#timeline").classList.toggle("reverse", mode == VM_THREAD); + const timeline_el = find_node("#timeline"); + timeline_el.classList.toggle("reverse", mode == VM_THREAD); + timeline_el.classList.toggle("hide", mode == VM_SETTINGS); + find_node("#settings").classList.toggle("hide", mode != VM_SETTINGS); view_timeline_refresh(model, mode, opts); + + if (mode == VM_SETTINGS) { + view_show_spinner(false); + view_set_show_count(0, true, true); + } return mode; } diff --git a/js/ui/util.js b/js/ui/util.js index 2b0bc23..c425284 100644 --- a/js/ui/util.js +++ b/js/ui/util.js @@ -163,14 +163,6 @@ function close_reply() { modal.classList.add("closed"); } -async function press_logout() { - if (confirm("Are you sure you want to sign out?")) { - localStorage.clear(); - await dbclear(); - window.location.reload(); - } -} - function delete_post_confirm(evid) { if (!confirm("Are you sure you want to delete this post?")) return;