Updated modals to be better for everyone.

This commit is contained in:
Thomas Mathews 2023-01-20 13:42:58 -08:00
parent 3970bb5acd
commit 8ddeca2227
7 changed files with 78 additions and 211 deletions

View file

@ -33,4 +33,15 @@
width: 44px; width: 44px;
height: 44px; height: 44px;
} }
dialog:modal {
width: initial;
padding: 10px;
}
.modal-content {
margin-top: 0;
border-radius: 0;
height: 100%;
}
} }

View file

@ -370,6 +370,11 @@ button.nav > img.icon {
.action-bar button.icon { .action-bar button.icon {
transition: opacity 0.3s linear; transition: opacity 0.3s linear;
padding: 5px;
}
.action-bar button.icon img.icon {
width: 16px;
height: 16px;
} }
.action-bar button.icon:hover { .action-bar button.icon:hover {
opacity: 1; opacity: 1;
@ -385,51 +390,45 @@ details.cw summary {
} }
/* Modal */ /* Modal */
.modal { dialog:modal {
position: fixed; width: 80%;
z-index: var(--zModal); max-width: 700px;
left: 0; padding: 20px;
top: 0; border: none;
width: 100%; background: transparent;
height: 100%; color: var(--clrText);
background: rgba(255,255,255,0.4); }
opacity: 1; dialog::backdrop {
transition: opacity 0.2s linear;
backdrop-filter: blur(20px); backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
} }
.modal.scrollable { dialog header {
overflow-y: auto;
}
.modal.closed {
opacity: 0;
pointer-events: none;
}
.modal-content {
padding: 20px;
overflow: auto;
border-radius: 15px;
background: var(--clrPanel);
max-width: 700px;
margin: 0 auto;
margin-top: 35px;
}
.modal header {
display: flex; display: flex;
} }
.modal header label { dialog header label {
flex: 1;
font-weight: 800; font-weight: 800;
font-size: var(--fsEnlarged); font-size: var(--fsEnlarged);
flex: 1;
word-break: break-word; word-break: break-word;
} }
.modal header button { dialog header button {
font-size: 24px; font-size: 24px;
} }
.modal .modal-floating-close-btn { dialog > img {
position: sticky; /* fix for media preview */
top: 20px; max-width: 100%;
left: 20px; max-height: 100%;
}
dialog > .container {
display: flex;
flex-direction: column;
background: var(--clrPanel);
border-radius: 15px;
padding: 20px;
}
dialog > .container .max-content {
max-height: min(100vh/2, 500px);
overflow: auto;
} }
/* Post & Reply */ /* Post & Reply */
@ -440,6 +439,7 @@ details.cw summary {
textarea.post-input { textarea.post-input {
display: block; display: block;
width: 100%; width: 100%;
min-height: 25px;
} }
.post-tools { .post-tools {
@ -452,6 +452,9 @@ textarea.post-input {
.post-tools > button.icon.cw.active { .post-tools > button.icon.cw.active {
opacity: 1.0; opacity: 1.0;
} }
.post-tools > button[name='reply-all'] {
margin-right: 5px;
}
input[type="text"].cw { input[type="text"].cw {
border: none; border: none;
border-bottom: solid 2px var(--clrWarn); border-bottom: solid 2px var(--clrWarn);
@ -490,21 +493,6 @@ label[role="profile-nip5"] {
display: block; display: block;
} }
/* Media Preview */
.modal .media-container {
text-align: center;
position: absolute;
width: 100%;
height: 100%;
}
.modal .media-container > img {
object-fit: scale-down;
object-position: center;
width: 100%;
height: 100%;
}
/* Profile Editor */ /* Profile Editor */
#profile-editor header { #profile-editor header {

View file

@ -51,11 +51,6 @@
Sign In with Key Sign In with Key
<img src="./icon/key.svg" class="icon svg small invert"/> <img src="./icon/key.svg" class="icon svg small invert"/>
</button> </button>
<br/>
<br/>
<button class="btn-text" action="open-faqs">
What's Nostr?
</button>
</div> </div>
</div> </div>
</div> </div>
@ -211,7 +206,6 @@
<label> <label>
</div> </div>
<div class="row"> <div class="row">
<button class="action mr-some" role='about-nostr'>Read About Nostr</button>
<button class="action" role="sign-out"> <button class="action" role="sign-out">
Sign Out Sign Out
<img class="icon svg small invert" src="icon/sign-out.svg"/> <img class="icon svg small invert" src="icon/sign-out.svg"/>
@ -246,15 +240,12 @@
</div> </div>
</div> </div>
<div class="modal closed" id="media-preview"> <dialog id="media-preview">
<div class="media-container"> <img action="close-media" src=""/>
<img action="close-media" src=""/>
</div>
<!-- TODO add loader to media preview --> <!-- TODO add loader to media preview -->
</div> </dialog>
<dialog id="reply-modal">
<div class="modal closed" id="reply-modal"> <div class="container">
<div id="reply-modal-content" class="modal-content">
<header> <header>
<label>Reply To</label> <label>Reply To</label>
<button class="icon" action="close-modal"> <button class="icon" action="close-modal">
@ -266,14 +257,14 @@
<textarea id="reply-content" class="post-input" <textarea id="reply-content" class="post-input"
placeholder="Reply..."></textarea> placeholder="Reply..."></textarea>
<div class="post-tools"> <div class="post-tools">
<button id="reply-button" class="action">Reply</button> <button class="action" name="reply-all" data-all="1">Reply All</button>
<button class="action" name="reply">Reply</button>
</div> </div>
</div> </div>
</div> </div>
</div> </dialog>
<dialog id="profile-editor">
<div id="profile-editor" class="modal closed"> <div class="container">
<div class="modal-content">
<header> <header>
<label>Update Profile</label> <label>Update Profile</label>
<button class="icon" action="close-modal"> <button class="icon" action="close-modal">
@ -290,116 +281,21 @@
</button> </button>
</div> </div>
</div> </div>
</div> </dialog>
<div id="event-details" class="modal closed"> <dialog id="event-details">
<div class="modal-content"> <div class="container">
<header> <header>
<label>Event Details</label> <label>Event Details</label>
<button class="icon modal-floating-close-btn" action="close-modal"> <button class="icon modal-floating-close-btn" action="close-modal">
<img class="icon svg" src="icon/close-modal.svg"/> <img class="icon svg" src="icon/close-modal.svg"/>
</button> </button>
</header> </header>
<div> <div class="max-content">
<pre><code></code></pre> <pre><code></code></pre>
</div> </div>
</div> </div>
</div> </dialog>
<div id="faqs" class="modal scrollable closed">
<button class="icon modal-floating-close-btn" action="close-modal">
<img class="icon svg" src="icon/close-modal.svg"/>
</button>
<div class="page-content">
<h1>Welcome to Nostr</h1>
<p>The open social network for "literally" everyone.</p>
<h2>What is Nostr?</h2>
<p>
Nostr is a protocol, not a platform or an app. This means that
users can pick and choose which clients (apps) to use and which
relays (servers) they wish to connect to.
</p>
<p>Nostr uses encryption to validate content authors. This means
that there is no centralized account system. We use
<a href="https://en.wikipedia.org/wiki/Public-key_cryptography"
target="_blank">public &amp; private</a> keys to sign our
content when we post. Do not confuse this with blockchain
technology.
</p>
<p>
Nostr stands for "Notes and Other Stuff Transmitted by Relays".
</p>
<p>
Read more about the protocol <a
href="https://github.com/nostr-protocol/nostr"
target="_blank">here</a>.
</p>
<h2>Apps</h2>
<p>
You are using one right now! You just haven't signed in yet to see
the actual application. Close this modal to sign in with your key.
</p>
<p>
A rich app ecosystem for Nostr. Anyone can build an app for the
protocol that fits their needs. This is the best option for users
as everyone has their own style. Another benifit is that you can
use multiple apps with the same account.
</p>
<p>
Since there are many apps for Nostr it allows developers to focus
on building certain experiences. Some apps may offer features that
others don't, such as direct messages, content viewing methods,
etc. You are allowed to choose how you want to interact with
Nostr.
</p>
<h3>Pick One</h3>
<p>
Here are a list of (trusted) apps that work with Nostr. Pick one
that fits you.
</p>
<ul>
<li><a href="https://damus.io" target="_blank">Damus</a> (iOS)</li>
<li><a href="https://github.com/fiatjaf/noscl" target="_blank">noscl</a> (Linux, Windows, MacOS)</li>
</ul>
<h3>Web Apps Warning</h3>
<p>
While the web is great for accessibility and is cross platform, it
is riddled with security implications you should be aware of. The
browser is susceptible to cross-site scripting (<a
href="https://en.wikipedia.org/wiki/Cross-site_scripting"
target="_blank">XSS</a>) attacks and extension malware. Therefore
you should be sure any web app you use is audited and trusted.
Additionally be aware of what apps you are using.
</p>
<p>
Secondly it is recommended you use a browser extension to handle
your Nostr key(s). This will delegate the signing process to an
extension that a XSS attack can't access. This is where native apps
have a stronger use case, but you should equally trust those as
well.
</p>
<h2>Relays</h2>
<p>
Relays are points of connection, a server. They allow you to read
events (content) and write to it depending on it's configuration.
Some may be read only, others may require some sort of payment to
use. Some may simply clone other relays.
</p>
<p>
Relays allow for specific needs and niches. You can host your
own relay for your club or community (such as gaming, art, sciences,
etc.) Or you can build your own relay with your own logic that
dictates who can access what and how. This is great for all kinds
of use cases for a range of users from individuals to businesses.
</p>
<h2>Account Creation</h2>
<p>You need to use a CLI tool. TODO fill this out.</p>
</div>
</div> </div>
</body> </body>
</html> </html>

View file

@ -197,12 +197,9 @@ function render_action_bar(model, ev, opts={}) {
let str = html`<div class="action-bar">`; let str = html`<div class="action-bar">`;
if (!shared && event_can_reply(ev)) { if (!shared && event_can_reply(ev)) {
str += html` str += html`
<button class="icon" title="Reply" action="reply-author" data-evid="${ev.id}"> <button class="icon" title="Reply" action="reply-to" data-evid="${ev.id}">
<img class="icon svg small" src="icon/event-reply.svg"/> <img class="icon svg small" src="icon/event-reply.svg"/>
</button> </button>
<button class="icon" title="Reply All" action="reply-all" data-evid="${ev.id}">
<img class="icon svg small" src="icon/event-reply-all.svg"/>
</button>
<button class="icon react heart ${ab(liked, 'liked', '')}" <button class="icon react heart ${ab(liked, 'liked', '')}"
action="react-like" action="react-like"
data-reaction-id="${reaction_id}" data-reaction-id="${reaction_id}"

View file

@ -5,7 +5,6 @@ function init_settings(model) {
embeds_el.addEventListener("click", on_click_toggle_embeds); embeds_el.addEventListener("click", on_click_toggle_embeds);
embeds_el.checked = model.embeds != "friends"; embeds_el.checked = model.embeds != "friends";
find_node("[role='sign-out']", el).addEventListener("click", on_click_sign_out); 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); const rlist = find_node("#relay-list tbody", el);
model.relays.forEach((str) => { model.relays.forEach((str) => {
rlist.appendChild(new_relay_item(str)); rlist.appendChild(new_relay_item(str));

View file

@ -501,11 +501,13 @@ function init_postbox(model) {
// Do reply box // Do reply box
// TODO refactor & cleanup reply modal init // TODO refactor & cleanup reply modal init
find_node("#reply-content").addEventListener("input", oninput_post); find_node("#reply-content").addEventListener("input", oninput_post);
find_node("#reply-button").addEventListener("click", onclick_reply); find_node("button[name='reply']")
.addEventListener("click", onclick_reply);
find_node("button[name='reply-all']")
.addEventListener("click", onclick_reply);
} }
async function onclick_reply(ev) { async function onclick_reply(ev) {
// Temp method do_send_reply(ev.target.dataset.all == "1");
do_send_reply();
} }
async function onclick_send(ev) { async function onclick_send(ev) {
const el = view_get_timeline_el(); const el = view_get_timeline_el();
@ -571,9 +573,6 @@ function onclick_any(ev) {
const el = ev.target; const el = ev.target;
const action = el.getAttribute("action"); const action = el.getAttribute("action");
switch (action) { switch (action) {
case "open-faqs":
open_faqs();
break;
case "toggle-gnav": case "toggle-gnav":
toggle_gnav(el); toggle_gnav(el);
break; break;
@ -607,11 +606,8 @@ function onclick_any(ev) {
case "delete": case "delete":
delete_post(el.dataset.evid); delete_post(el.dataset.evid);
break; break;
case "reply-author": case "reply-to":
reply_author(el.dataset.evid); reply(el.dataset.evid);
break;
case "reply-all":
reply_all(el.dataset.evid);
break; break;
case "react-like": case "react-like":
click_toggle_like(el); click_toggle_like(el);

View file

@ -65,7 +65,7 @@ function click_toggle_like(el) {
*/ */
function open_media_preview(url, type) { function open_media_preview(url, type) {
const el = find_node("#media-preview"); const el = find_node("#media-preview");
el.classList.remove("closed"); el.showModal();
find_node("img", el).src = url; find_node("img", el).src = url;
// TODO handle different medias such as audio and video // TODO handle different medias such as audio and video
// TODO add loading state & error checking // TODO add loading state & error checking
@ -74,7 +74,7 @@ function open_media_preview(url, type) {
/* close_media_preview closes any present media modal. /* close_media_preview closes any present media modal.
*/ */
function close_media_preview() { function close_media_preview() {
find_node("#media-preview").classList.add("closed"); find_node("#media-preview").close();
} }
function delete_post_confirm(evid) { function delete_post_confirm(evid) {
@ -86,11 +86,10 @@ function delete_post_confirm(evid) {
delete_post(evid, reason) delete_post(evid, reason)
} }
async function do_send_reply() { async function do_send_reply(all=false) {
const modal = document.querySelector("#reply-modal"); const modal = document.querySelector("#reply-modal");
const replying_to = modal.querySelector("#replying-to"); const replying_to = modal.querySelector("#replying-to");
const evid = replying_to.dataset.evid; const evid = replying_to.dataset.evid;
const all = replying_to.dataset.toAll != "";
const reply_content_el = document.querySelector("#reply-content"); const reply_content_el = document.querySelector("#reply-content");
const content = reply_content_el.value; const content = reply_content_el.value;
await send_reply(content, evid, all); await send_reply(content, evid, all);
@ -98,29 +97,20 @@ async function do_send_reply() {
close_modal(modal); close_modal(modal);
} }
function reply(evid, all=false) { function reply(evid) {
const ev = DAMUS.all_events[evid] const ev = DAMUS.all_events[evid]
const modal = document.querySelector("#reply-modal") const modal = document.querySelector("#reply-modal")
const replybox = modal.querySelector("#reply-content") const replybox = modal.querySelector("#reply-content")
const replying_to = modal.querySelector("#replying-to") const replying_to = modal.querySelector("#replying-to")
replying_to.dataset.evid = evid replying_to.dataset.evid = evid
replying_to.dataset.toAll = all ? "all" : "";
replying_to.innerHTML = render_event_nointeract(DAMUS, ev, { replying_to.innerHTML = render_event_nointeract(DAMUS, ev, {
is_composing: true, is_composing: true,
nobar: true nobar: true
}); });
modal.classList.remove("closed") modal.showModal();
replybox.focus() replybox.focus()
} }
function reply_author(evid) {
reply(evid);
}
function reply_all(evid) {
reply(evid, true);
}
function update_favicon(path) { function update_favicon(path) {
let link = document.querySelector("link[rel~='icon']"); let link = document.querySelector("link[rel~='icon']");
const head = document.getElementsByTagName('head')[0] const head = document.getElementsByTagName('head')[0]
@ -194,18 +184,8 @@ function open_thread(thread_id) {
view_timeline_apply_mode(DAMUS, VM_THREAD, { thread_id }); view_timeline_apply_mode(DAMUS, VM_THREAD, { thread_id });
} }
function open_faqs() {
find_node("#faqs").classList.remove("closed");
}
function close_modal(el) { function close_modal(el) {
while (el) { find_parent(el, "dialog").close();
if (el.classList.contains("modal")) {
el.classList.add("closed");
break;
}
el = el.parentElement;
}
} }
function on_click_show_event_details(evid) { function on_click_show_event_details(evid) {
@ -214,7 +194,7 @@ function on_click_show_event_details(evid) {
if (!ev) if (!ev)
return; return;
const el = find_node("#event-details"); const el = find_node("#event-details");
el.classList.remove("closed"); el.showModal();
find_node("code", el).innerText = JSON.stringify(ev, null, "\t"); find_node("code", el).innerText = JSON.stringify(ev, null, "\t");
} }