Added basic settings support.
You can edit relays, embed options, and sign out. This moves the signout button from the nav to the settings area.
This commit is contained in:
parent
9a3361750e
commit
e3b31af127
12 changed files with 198 additions and 40 deletions
|
@ -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;
|
||||
|
|
1
icon/add-relay.svg
Normal file
1
icon/add-relay.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256zM256 368C269.3 368 280 357.3 280 344V280H344C357.3 280 368 269.3 368 256C368 242.7 357.3 232 344 232H280V168C280 154.7 269.3 144 256 144C242.7 144 232 154.7 232 168V232H168C154.7 232 144 242.7 144 256C144 269.3 154.7 280 168 280H232V344C232 357.3 242.7 368 256 368z"/></svg>
|
After Width: | Height: | Size: 620 B |
1
icon/settings-active.svg
Normal file
1
icon/settings-active.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M502.6 182.6l-45.25-45.25C451.4 131.4 443.3 128 434.8 128H384V80C384 53.5 362.5 32 336 32h-160C149.5 32 128 53.5 128 80V128H77.25c-8.5 0-16.62 3.375-22.62 9.375L9.375 182.6C3.375 188.6 0 196.8 0 205.3V304h128v-32C128 263.1 135.1 256 144 256h32C184.9 256 192 263.1 192 272v32h128v-32C320 263.1 327.1 256 336 256h32C376.9 256 384 263.1 384 272v32h128V205.3C512 196.8 508.6 188.6 502.6 182.6zM336 128h-160V80h160V128zM384 368c0 8.875-7.125 16-16 16h-32c-8.875 0-16-7.125-16-16v-32H192v32C192 376.9 184.9 384 176 384h-32C135.1 384 128 376.9 128 368v-32H0V448c0 17.62 14.38 32 32 32h448c17.62 0 32-14.38 32-32v-112h-128V368z"/></svg>
|
After Width: | Height: | Size: 867 B |
1
icon/settings.svg
Normal file
1
icon/settings.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M502.6 214.6l-77.25-77.25C419.4 131.4 411.2 128 402.7 128H384V88C384 57.13 358.9 32 328 32h-144C153.1 32 128 57.13 128 88V128H109.3C100.8 128 92.63 131.4 86.63 137.4L9.373 214.6C3.371 220.6 0 228.8 0 237.3V416c0 35.35 28.65 64 64 64h384c35.35 0 64-28.65 64-64V237.3C512 228.8 508.6 220.6 502.6 214.6zM176 88c0-4.406 3.594-8 8-8h144c4.406 0 8 3.594 8 8V128h-160V88zM115.9 176h280.2L464 243.9V296h-88V279.1C376 266.7 365.3 256 352 256s-24 10.75-24 23.1V296h-144V279.1C184 266.7 173.3 256 160 256C146.7 256 136 266.7 136 279.1V296H48V243.9L115.9 176zM448 432H64c-8.837 0-16-7.163-16-16v-72h88v16C136 373.3 146.7 384 159.1 384C173.3 384 184 373.3 184 360V344h144v16C328 373.3 338.7 384 352 384s24-10.75 24-23.1V344h88V416C464 424.8 456.8 432 448 432z"/></svg>
|
After Width: | Height: | Size: 994 B |
49
index.html
49
index.html
|
@ -17,6 +17,7 @@
|
|||
<script defer src="js/ui/render.js?v=15"></script>
|
||||
<script defer src="js/ui/state.js?v=1"></script>
|
||||
<script defer src="js/ui/fmt.js?v=1"></script>
|
||||
<script defer src="js/ui/settings.js?v=1"></script>
|
||||
<script defer src="js/noble-secp256k1.js?v=1"></script>
|
||||
<script defer src="js/bech32.js?v=1"></script>
|
||||
<script defer src="js/nostr.js?v=7"></script>
|
||||
|
@ -69,11 +70,10 @@
|
|||
<img class="icon svg invert" src="icon/notifications.svg"/>
|
||||
<div class="new-notifications hide"></div>
|
||||
</button>
|
||||
<button class="icon" role="sign-out" title="Sign Out" onclick="press_logout()">
|
||||
<img class="icon svg invert" src="icon/sign-out.svg"/>
|
||||
<button class="icon" role="settings" title="Settings" onclick="switch_view('settings')">
|
||||
<img class="icon svg invert" src="icon/settings.svg"/>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div id="container">
|
||||
<div class="flex-fill vertical-hide"></div>
|
||||
<div id="nav" class="flex-noshrink vertical-hide">
|
||||
|
@ -97,11 +97,14 @@
|
|||
<img class="icon svg active" src="icon/notifications-active.svg"/>
|
||||
<div class="new-notifications hide"></div>
|
||||
</button>
|
||||
<button title="Sign Out" class="nav icon" onclick="press_logout()">
|
||||
<img class="icon svg" src="icon/sign-out.svg"/>
|
||||
<button role="settings" title="Sign Out" class="nav icon"
|
||||
onclick="switch_view('settings')">
|
||||
<img class="icon svg inactive" src="icon/settings.svg"/>
|
||||
<img class="icon svg active" src="icon/settings-active.svg"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="view">
|
||||
<div>
|
||||
<header>
|
||||
|
@ -161,6 +164,42 @@
|
|||
Show New (<span role="count">0</span>)</button>
|
||||
</div>
|
||||
<div id="timeline" class="events"></div>
|
||||
<div id="settings" class="hide">
|
||||
<section>
|
||||
<header>
|
||||
<label>Relays</label>
|
||||
<button id="add-relay" class="btn-text">
|
||||
<img class="svg icon small" src="icon/add-relay.svg"/>
|
||||
</button>
|
||||
</header>
|
||||
<table id="relay-list" class="row">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Address</td>
|
||||
<td>Option</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<section>
|
||||
<header><label>Miscellanious</label></header>
|
||||
<div class="row">
|
||||
<label>
|
||||
Show Embeds From Everyone
|
||||
<input type="checkbox" name="show_embeds"/>
|
||||
<label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<button class="action mr-some" role='about-nostr'>Read About Nostr</button>
|
||||
<button class="action" role="sign-out">
|
||||
Sign Out
|
||||
<img class="icon svg small invert" src="icon/sign-out.svg"/>
|
||||
</button>
|
||||
</row>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-fill vertical-hide"></div>
|
||||
|
|
10
js/main.js
10
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);
|
||||
|
|
27
js/model.js
27
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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = "<div>";
|
||||
str += shared ? render_shared_by(ev, opts) : render_replying_to(model, ev);
|
||||
str += `</div><p>
|
||||
|
|
71
js/ui/settings.js
Normal file
71
js/ui/settings.js
Normal file
|
@ -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 = `<td>${str}</td>
|
||||
<td>
|
||||
<button class="remove-relay btn-text"
|
||||
data-address="${str}"
|
||||
role="remove-relay">
|
||||
<img class="icon svg small" src="icon/event-delete.svg"/>
|
||||
</button>
|
||||
</td>`;
|
||||
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);
|
||||
}
|
|
@ -3,6 +3,7 @@ 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_SETTINGS = "settings";
|
||||
|
||||
function view_get_timeline_el() {
|
||||
return find_node("#timeline");
|
||||
|
@ -51,16 +52,25 @@ 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue