layout: update navigation and add icons

Used different CSS-only tab technique. The implementation before
messed up the whole height of the page and used unnecessary
absolute positioning.

Added comment icon from MFG Labs iconset (SIL)
- https://github.com/MfgLabs/mfglabs-iconset

Added outlined and filled heart from Elusiv icons (SIL)
- https://github.com/dovy/elusive-iconfont

Time ago seconds should be rounded.
pull/2/head
OFF0 2 years ago
parent a71de21302
commit 13b3db4302
Signed by: offbyn
GPG Key ID: 94A2F643C51F37FA

@ -9,7 +9,9 @@ export const options = {
'src/main.js', 'src/main.js',
'src/main.css', 'src/main.css',
'src/index.html', 'src/index.html',
'src/bubble.svg' 'src/assets/bubble.svg',
'src/assets/comment.svg',
'src/assets/heart-fill.svg',
], ],
outdir: 'dist', outdir: 'dist',
//entryNames: '[name]-[hash]', TODO: replace urls in index.html with hashed paths //entryNames: '[name]-[hash]', TODO: replace urls in index.html with hashed paths

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="21" viewBox="0 0 80.035 70.031"><path fill="currentColor" d="M0 31.347q0-5.113 2.02-9.875 2.019-4.761 5.724-8.633 3.705-3.872 8.615-6.762 4.91-2.89 11.023-4.484Q33.496 0 40.018 0q6.52 0 12.635 1.593 6.113 1.594 11.023 4.484 4.91 2.89 8.615 6.762 3.705 3.872 5.725 8.633 2.019 4.762 2.019 9.875 0 6.373-3.168 12.153t-8.522 9.968q-5.354 4.187-12.765 6.67-7.41 2.482-15.562 2.482-7.967 0-15.303-2.371l-9.116 6.447q-5.113 3.223-7.188 1.871-2.075-1.352-.926-7.614l1.89-9.486q-4.484-4.15-6.93-9.3Q0 37.017 0 31.347Z" style="stroke-width:.0370534"/></svg>

After

Width:  |  Height:  |  Size: 607 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M14 20.408c-.492.308-.903.546-1.192.709-.153.086-.308.17-.463.252h-.002a.75.75 0 01-.686 0 16.709 16.709 0 01-.465-.252 31.147 31.147 0 01-4.803-3.34C3.8 15.572 1 12.331 1 8.513 1 5.052 3.829 2.5 6.736 2.5 9.03 2.5 10.881 3.726 12 5.605 13.12 3.726 14.97 2.5 17.264 2.5 20.17 2.5 23 5.052 23 8.514c0 3.818-2.801 7.06-5.389 9.262A31.146 31.146 0 0114 20.408z"></path></svg>

After

Width:  |  Height:  |  Size: 464 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill-rule="evenodd" d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.565 20.565 0 008 13.393a20.561 20.561 0 003.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.75.75 0 01-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5zM8 14.25l-.345.666-.002-.001-.006-.003-.018-.01a7.643 7.643 0 01-.31-.17 22.075 22.075 0 01-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.08 22.08 0 01-3.744 2.584l-.018.01-.006.003h-.002L8 14.25zm0 0l.345.666a.752.752 0 01-.69 0L8 14.25z"></path></svg>

After

Width:  |  Height:  |  Size: 734 B

@ -8,13 +8,14 @@
} }
.mbox-img { .mbox-img {
--size: 4ch; --size: 5rem;
align-self: start; align-self: start;
flex-basis: var(--size); flex-basis: var(--size);
height: var(--size); height: var(--size);
margin-right: 1rem; margin-right: 1rem;
margin-top: .5ch; margin-top: .5ch;
max-width: var(--size); max-width: var(--size);
max-width: var(--size);
} }
.mbox-recommend-server .mbox-img { .mbox-recommend-server .mbox-img {
--size: 2.5ch; --size: 2.5ch;
@ -38,7 +39,6 @@
} }
.mbox-header time, .mbox-header time,
.mbox-username { .mbox-username {
color: var(--color-accent);
} }
.mbox-recommend-server .mbox-body { .mbox-recommend-server .mbox-body {

@ -13,7 +13,7 @@
export function elem(name = 'div', {data, ...props} = {}, children = []) { export function elem(name = 'div', {data, ...props} = {}, children = []) {
const el = document.createElement(name); const el = document.createElement(name);
Object.assign(el, props); Object.assign(el, props);
if (typeof children === 'string') { if (['number', 'string'].includes(typeof children)) {
el.append(children); el.append(children);
} else { } else {
el.append(...children); el.append(...children);

@ -4,7 +4,6 @@ form {
input, input,
textarea { textarea {
color: var(--color); color: var(--color);
font-family: monospace;
font-size: 1.6rem; font-size: 1.6rem;
margin-bottom: 1.2rem; margin-bottom: 1.2rem;
padding: 1.3rem 1.8rem; padding: 1.3rem 1.8rem;
@ -12,8 +11,8 @@ textarea {
button, button,
label { label {
cursor: pointer;
display: block; display: block;
font-family: monospace;
font-size: 1.6rem; font-size: 1.6rem;
margin-bottom: 0; margin-bottom: 0;
padding: 1.3rem 1.8rem; padding: 1.3rem 1.8rem;
@ -34,8 +33,8 @@ input[type="text"] {
} }
input[type="password"]:focus, input[type="password"]:focus,
input[type="text"]:focus { input[type="text"]:focus {
border-color: #d4d4d4; border-color: var(--focus-border-color);
outline-offset: 1px; outline-offset: 2px;
} }
.buttons { .buttons {
@ -58,11 +57,18 @@ button {
button:focus { button:focus {
} }
.button-inline { .btn-inline {
align-items: center;
background: transparent; background: transparent;
color: var(--color); color: var(--color);
display: inline; display: inline-flex;
padding: .3rem; gap: .5ch;
line-height: 1;
padding: .6rem;
}
.btn-inline img {
max-height: 18px;
max-width: 18px;
} }
button:disabled { button:disabled {

@ -6,14 +6,22 @@
<link rel="stylesheet" href="main.css" type="text/css"> <link rel="stylesheet" href="main.css" type="text/css">
</head> </head>
<body> <body>
<div class="tabs"> <main class="tabbed">
<input type="radio" name="maintabs" id="feed" class="tab" checked>
<label for="feed">feed</label>
<input type="radio" name="maintabs" id="trending" class="tab">
<label for="trending">trending</label>
<input type="radio" name="maintabs" id="direct" class="tab">
<label for="direct">direct</label>
<input type="radio" name="maintabs" id="chat" class="tab">
<label for="chat">chat</label>
<input type="radio" name="maintabs" id="settings" class="tab">
<label for="settings">settings</label>
<div class="tab"> <div class="tabs">
<input type="radio" name="maintabs" id="homefeed" checked> <div class="tab-content">
<label for="homefeed">feed</label>
<div class="content">
<article class="mbox"> <article class="mbox">
<img class="mbox-img" src="bubble.svg"> <img class="mbox-img" src="assets/comment.svg" alt="">
<div class="mbox-body" id="newMessage"> <div class="mbox-body" id="newMessage">
<form class="form-inline" id="writeForm"> <form class="form-inline" id="writeForm">
<input type="text" name="message"> <input type="text" name="message">
@ -22,43 +30,28 @@
<small id="sendstatus" class="form-status"></small> <small id="sendstatus" class="form-status"></small>
</div> </div>
</article> </article>
<div class="cards" id="feed"></div> <div class="cards" id="homefeed"></div>
</div>
</div> </div>
<div class="tab"> <div class="tab-content">
<input type="radio" name="maintabs" id="trending">
<label for="trending">trending</label>
<div class="content">
<p><a href="https://github.com/nostr-protocol/nips/blob/master/12.md">NIP-12 (generic queries)</a></p> <p><a href="https://github.com/nostr-protocol/nips/blob/master/12.md">NIP-12 (generic queries)</a></p>
</div> </div>
</div>
<div class="tab"> <div class="tab-content">
<input type="radio" name="maintabs" id="direct">
<label for="direct">direct</label>
<div class="content">
<p><a href="https://github.com/nostr-protocol/nips/blob/master/04.md">NIP-04 (direct msg)</a></p> <p><a href="https://github.com/nostr-protocol/nips/blob/master/04.md">NIP-04 (direct msg)</a></p>
</div> </div>
</div>
<div class="tab"> <div class="tab-content">
<input type="radio" name="maintabs" id="chat">
<label for="chat">chat</label>
<div class="content">
<p><a href="https://github.com/nostr-protocol/nips/blob/master/28.md">NIP-28 (public chat)</a></p> <p><a href="https://github.com/nostr-protocol/nips/blob/master/28.md">NIP-28 (public chat)</a></p>
</div> </div>
</div>
<div class="tab"> <div class="tab-content">
<input type="radio" name="maintabs" id="settings"> <form action="#" name="settings">
<label for="settings">settings</label>
<form action="#" name="settings" class="content">
<label for="pubkey">public-key</label> <label for="pubkey">public-key</label>
<input type="text" id="pubkey"> <input type="text" id="pubkey">
<label for="privatekey"> <label for="privatekey">
private-key private-key
<button type="button" name="privatekey-toggle" class="button-inline" > <button type="button" name="privatekey-toggle" class="btn-inline" >
<small>show</small> <small>show</small>
</button> </button>
</label> </label>
@ -71,8 +64,9 @@
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</main>
</body> </body>
<script src="main.js"></script> <script src="main.js"></script>
</html> </html>

@ -3,6 +3,13 @@
@import "form.css"; @import "form.css";
:root { :root {
/* 5px auto Highlight */
--focus-border-color: rgb(0, 122, 255);
--focus-border-radius: 2px;
--focus-outline-color: rgb(127, 189, 247);
--focus-outline-style: solid;
--focus-outline-width: 2px;
--focus-outline: var(--focus-outline-width) var(--focus-outline-style) var(--focus-outline-color);
--font-small: 1.2rem; --font-small: 1.2rem;
} }
@ -53,20 +60,35 @@ html {
body { body {
background-color: var(--bgcolor); background-color: var(--bgcolor);
color: var(--color); color: var(--color);
font-family: monospace;
font-size: 1.6rem; font-size: 1.6rem;
line-height: 1.5; line-height: 1.5;
} }
body,
button,
input,
select,
textarea {
font-family: monospace;
}
small, small,
time { time {
font-size: var(--font-small); font-size: var(--font-small);
} }
*, ::after, ::before {
box-sizing: border-box;
}
.danger { .danger {
background-color: var(--bgcolor-danger); background-color: var(--bgcolor-danger);
} }
a:focus {
border-radius: var(--focus-border-radius);
outline: var(--focus-outline);
outline-offset: 0;
}
img[alt] {
font-size: .9rem;
text-align: center;
word-break: break-all;
}

@ -75,7 +75,7 @@ function handleTextNote(evt, relay) {
} }
// feed // feed
const feedContainer = document.querySelector('#feed'); const feedContainer = document.querySelector('#homefeed');
const feedDomMap = {}; const feedDomMap = {};
const sortByCreatedAt = (evt1, evt2) => { const sortByCreatedAt = (evt1, evt2) => {
if (evt1.created_at === evt2.created_at) { if (evt1.created_at === evt2.created_at) {
@ -115,28 +115,31 @@ setInterval(() => {
function createTextNote(evt, relay) { function createTextNote(evt, relay) {
const {host, img, isReply, replies, time, userName} = getMetadata(evt, relay); const {host, img, isReply, replies, time, userName} = getMetadata(evt, relay);
const name = elem('strong', {className: 'mbox-username', title: evt.pubkey}, userName); const isLongContent = evt.content.length > 280;
const timeElem = elem('time', { dateTime: time.toISOString()}, formatTime(time)); const content = isLongContent ? `${evt.content.slice(0, 280)}` : evt.content;
const hasLongContent = evt.content.length > 280;
const headerInfo = isReply
? [name, ' ', timeElem]
: [name, ` on ${host} `, timeElem];
const content = hasLongContent ? `${evt.content.slice(0, 280)}` : evt.content;
const body = elem('div', {className: 'mbox-body'}, [ const body = elem('div', {className: 'mbox-body'}, [
elem('header', { elem('header', {
className: 'mbox-header', className: 'mbox-header',
title: `Event ${evt.id}\non ${host} ${time} title: `User: ${userName}\n${time}\n\nUser pubkey: ${evt.pubkey}\n\nRelay: ${host}\n\nEvent-id: ${evt.id}
${isReply ? `\nReply ${evt.tags[0][1]}\n` : ''}` ${isReply ? `\nReply to ${evt.tags[0][1]}\n` : ''}`
}, [ }, [
elem('small', {}, headerInfo), elem('small', {}, [
elem('strong', {className: 'mbox-username'}, userName),
' ',
elem('time', {dateTime: time.toISOString()}, formatTime(time))
]),
]), ]),
elem('div', {data: hasLongContent ? {append: evt.content.slice(280)} : null}, content), elem('div', {data: isLongContent ? {append: evt.content.slice(280)} : null}, content),
elem('button', {
className: 'btn-inline', name: 'reply', type: 'button',
data: {'eventId': evt.id, relay},
}, [elem('img', {height: 24, width: 24, src: 'assets/comment.svg'})]),
elem('button', { elem('button', {
className: 'button-inline', className: 'btn-inline', name: 'star', type: 'button',
name: 'reply', type: 'button', data: {'eventId': evt.id, relay},
data: {'eventId': evt.id, relay}
}, [ }, [
elem('small', {}, 'reply') elem('img', {alt: '♥', height: 24, width: 24, src: 'assets/heart-fill.svg'}),
elem('small', {}, 2),
]), ]),
replies[0] ? elem('div', {className: 'mobx-replies'}, replies.map(e => createTextNote(e, relay))) : '', replies[0] ? elem('div', {className: 'mobx-replies'}, replies.map(e => createTextNote(e, relay))) : '',
]); ]);
@ -242,15 +245,14 @@ const getHost = (url) => {
function getMetadata(evt, relay) { function getMetadata(evt, relay) {
const host = getHost(relay); const host = getHost(relay);
const user = userList.find(user => user.pubkey === evt.pubkey); const user = userList.find(user => user.pubkey === evt.pubkey);
const userImg = /*user?.metadata[relay]?.picture || */'bubble.svg'; // TODO: enable pic once we have proxy const userImg = /*user?.metadata[relay]?.picture || */'assets/bubble.svg'; // TODO: enable pic once we have proxy
const userName = user?.metadata[relay]?.name || evt.pubkey.slice(0, 8); const userName = user?.metadata[relay]?.name || evt.pubkey.slice(0, 8);
const userAbout = user?.metadata[relay]?.about || ''; const userAbout = user?.metadata[relay]?.about || '';
const title = `${userName} on ${host} ${userAbout}`;
const img = elem('img', { const img = elem('img', {
className: 'mbox-img', className: 'mbox-img',
src: userImg, src: userImg,
alt: title, alt: `${userName} ${host}`,
title, title: `${userName} on ${host} ${userAbout}`,
}, ''); }, '');
const isReply = evt.tags.some(hasEventTag); const isReply = evt.tags.some(hasEventTag);
const replies = replyList.filter((reply) => reply.tags[0][1] === evt.id); const replies = replyList.filter((reply) => reply.tags[0][1] === evt.id);

@ -1,19 +1,12 @@
.tabs {
position: relative;
max-width: 96ch;
min-height: 200px;
}
.tab {
float: left;
}
.tab > label { .tabs .tab-content { display: none; }
cursor: pointer; #feed:checked ~ .tabs .tab-content:nth-child(1),
padding: 1rem 1.5em; #trending:checked ~ .tabs .tab-content:nth-child(2),
} #direct:checked ~ .tabs .tab-content:nth-child(3),
#chat:checked ~ .tabs .tab-content:nth-child(4),
#settings:checked ~ .tabs .tab-content:nth-child(5) { display: block; }
.tab [type=radio] { input[type="radio"].tab {
clip: rect(0, 0, 0, 0); clip: rect(0, 0, 0, 0);
height: 0; height: 0;
overflow: hidden; overflow: hidden;
@ -21,33 +14,45 @@
width: 0; width: 0;
} }
.tab [type=radio] + label { .tab + label {
border: none;
color: var(--color);
display: inline-block;
outline: 2px solid var(--bgcolor-accent); outline: 2px solid var(--bgcolor-accent);
outline-offset: -1px; padding: 1rem 1.5em;
position: relative;
top: 1px;
}
input[type="radio"]:checked + label {
background: var(--bgcolor-accent);
} }
/* .tab:focus + label,
.tab [type=radio]:focus + label { .tab:active + label {
outline: 2px dotted black; border-color: var(--focus-border-color);
border-radius: var(--focus-border-radius);
outline: var(--focus-outline);
outline-offset: var(--focus-outline-offset);
} }
*/
.tab [type=radio]:checked ~ label { .tab-content {
background-color: var(--bgcolor-accent); max-width: 96ch;
color: var(--color); min-height: 200px;
z-index: 2;
} }
.tab [type=radio]:checked ~ label ~ .content { /*
opacity: 1;
z-index: 1;
.tabs {
position: relative;
} }
.tab .content { .tab {
bottom: 0; float: left;
left: 0; }
opacity: 0;
position: absolute; .tab > label {
right: 0;
top: 10rem;
} }
*/

@ -45,7 +45,7 @@ const timeAgo = (time, locale = 'en') => {
} else if (minutes > 0) { } else if (minutes > 0) {
return relativeTime.format(0 - minutes, 'minute'); return relativeTime.format(0 - minutes, 'minute');
} else { } else {
return relativeTime.format(0 - timeSince, 'second'); return relativeTime.format(Math.round(0 - timeSince), 'second');
} }
}; };

Loading…
Cancel
Save