diff --git a/src/Text.js b/src/Text.js
index 58a3c151..e2a56c06 100644
--- a/src/Text.js
+++ b/src/Text.js
@@ -38,7 +38,7 @@ function transformHttpLink(a) {
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
- allowfullscreen=""
+ allowFullScreen=""
/>
>
@@ -85,7 +85,7 @@ export function extractMentions(fragments, tags, users) {
}
}
}
- return {matchTag[0]}?;
+ return {matchTag[0]}?;
} else {
return match;
}
diff --git a/src/element/Copy.css b/src/element/Copy.css
index 3a949667..2ed09ebc 100644
--- a/src/element/Copy.css
+++ b/src/element/Copy.css
@@ -1,14 +1,15 @@
.copy {
user-select: none;
cursor: pointer;
+ -webkit-tap-highlight-color: transparent;
}
.copy .body {
font-family: monospace;
font-size: 14px;
- margin: 0;
- width: 18em;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
-}
\ No newline at end of file
+ background: var(--gray-secondary);
+ color: var(--font-color);
+ padding: 2px 4px;
+ border-radius: 10px;
+ margin: 0 4px 0 0;
+}
diff --git a/src/element/Copy.js b/src/element/Copy.js
index f70d8803..4bc922b4 100644
--- a/src/element/Copy.js
+++ b/src/element/Copy.js
@@ -3,19 +3,21 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCopy, faCheck } from "@fortawesome/free-solid-svg-icons";
import { useCopy } from "../useCopy";
-export default function Copy(props) {
-
+export default function Copy({ text, maxSize = 32 }) {
const { copy, copied, error } = useCopy();
+ const sliceLength = maxSize / 2
+ const trimmed = text.length > maxSize ? `${text.slice(0, sliceLength)}:${text.slice(-sliceLength)}` : text
+
return (
-
copy(props.text)}>
+
copy(text)}>
+
+ {trimmed}
+
-
- {props.text}
-
)
-}
\ No newline at end of file
+}
diff --git a/src/element/FollowButton.js b/src/element/FollowButton.js
index 7205ffa3..1de67e5e 100644
--- a/src/element/FollowButton.js
+++ b/src/element/FollowButton.js
@@ -1,11 +1,15 @@
import { useSelector } from "react-redux";
import useEventPublisher from "../feed/EventPublisher";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faUserMinus, faUserPlus } from "@fortawesome/free-solid-svg-icons";
export default function FollowButton(props) {
const pubkey = props.pubkey;
- const className = props.className ? `btn ${props.className}` : "btn";
const publiser = useEventPublisher();
const follows = useSelector(s => s.login.follows);
+ let isFollowing = follows?.includes(pubkey) ?? false;
+ const baseClassName = isFollowing ? `btn btn-warn` : `btn btn-success`
+ const className = props.className ? `${baseClassName} ${props.className}` : `${baseClassName}`;
async function follow(pubkey) {
let ev = await publiser.addFollow(pubkey);
@@ -17,10 +21,9 @@ export default function FollowButton(props) {
publiser.broadcast(ev);
}
- let isFollowing = follows?.includes(pubkey) ?? false;
return (
isFollowing ? unfollow(pubkey) : follow(pubkey)}>
- {isFollowing ? "Unfollow" : "Follow"}
+
)
}
\ No newline at end of file
diff --git a/src/element/Invoice.css b/src/element/Invoice.css
index 3f2021fe..9651fa5b 100644
--- a/src/element/Invoice.css
+++ b/src/element/Invoice.css
@@ -1,6 +1,7 @@
.note-invoice {
+ background: var(--bg-color);
border-radius: 10px;
- border: 1px solid #444;
+ border: 1px solid var(--gray-tertiary);
padding: 10px;
}
@@ -9,5 +10,5 @@
}
.note-invoice small {
- color: #666;
-}
\ No newline at end of file
+ color: var(--gray-medium);
+}
diff --git a/src/element/LNURLTip.css b/src/element/LNURLTip.css
index 785f3195..3fea13cb 100644
--- a/src/element/LNURLTip.css
+++ b/src/element/LNURLTip.css
@@ -1,5 +1,5 @@
.lnurl-tip {
- background-color: #222;
+ background-color: var(--gray-secondary);
padding: 10px;
border-radius: 10px;
width: 500px;
@@ -12,7 +12,7 @@
}
.lnurl-tip .btn:hover {
- background-color: #333;
+ background-color: var(--gray);
}
.lnurl-tip .invoice {
@@ -39,4 +39,4 @@
.lnurl-tip .invoice .actions {
text-align: center;
}
-}
\ No newline at end of file
+}
diff --git a/src/element/Modal.css b/src/element/Modal.css
index 55be6fec..d690343b 100644
--- a/src/element/Modal.css
+++ b/src/element/Modal.css
@@ -4,8 +4,8 @@
position: fixed;
top: 0;
left: 0;
- background-color: rgba(0,0,0, 0.8);
+ background-color: var(--modal-bg-color);
display: flex;
justify-content: center;
align-items: center;
-}
\ No newline at end of file
+}
diff --git a/src/element/Nip05.css b/src/element/Nip05.css
index ead258dc..f80f464b 100644
--- a/src/element/Nip05.css
+++ b/src/element/Nip05.css
@@ -2,22 +2,18 @@
justify-content: flex-start;
align-items: center;
font-size: 14px;
- margin: .2em 0;
+ margin: .2em;
}
.nip05 .nick {
- color: #999;
+ color: var(--gray-light);
}
.nip05 .domain {
- color: #DDD;
+ color: var(--gray-superlight);
}
.nip05 .badge {
margin-left: .2em;
-}
-
-.nip05 .error {
- margin-top: .2em;
- margin-left: .2em;
+ margin-top: .1em;
}
diff --git a/src/element/Nip05.js b/src/element/Nip05.js
index 8097609b..84347031 100644
--- a/src/element/Nip05.js
+++ b/src/element/Nip05.js
@@ -1,7 +1,7 @@
import { useState, useEffect } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faCheck, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
+import { faCheck, faSpinner, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
import './Nip05.css'
@@ -28,30 +28,35 @@ const Nip05 = ({ nip05, pubkey }) => {
}, [nip05, name, domain])
return (
-
+
ev.stopPropagation()}>
{!isDefaultUser &&
{name}
}
{!isDefaultUser && '@'}
{domain}
- {isVerified && (
-
+
+ {!isVerified && !couldNotVerify && (
+
+ )}
+ {isVerified && (
+
+ )}
+ {couldNotVerify && (
-
- )}
- {couldNotVerify && (
-
-
-
- )}
+ )}
+
)
}
diff --git a/src/element/Note.css b/src/element/Note.css
index 888ff670..3b5566ff 100644
--- a/src/element/Note.css
+++ b/src/element/Note.css
@@ -1,10 +1,10 @@
.note {
margin-bottom: 10px;
- border-bottom: 1px solid #333;
+ border-bottom: 1px solid var(--gray);
}
-.note.active {
- background-color: #222;
+.note.thread {
+ border-bottom: none;
}
.note > .header > .pfp {
@@ -13,13 +13,13 @@
.note > .header .reply {
font-size: small;
- color: #999;
+ color: var(--gray-light);
}
.note > .header > .info {
font-size: small;
white-space: nowrap;
- color: #999;
+ color: var(--gray-light);
}
.note > .body {
@@ -32,10 +32,10 @@
.note > .body > img, .note > .body > video, .note > .body > iframe {
max-width: 100%;
max-height: 500px;
-}
-
-.note > .body > iframe {
- margin: 10px 0;
+ margin: 10px;
+ margin-left: auto;
+ margin-right: auto;
+ display: block;
}
.note > .header > img:hover, .note > .header > .name > .reply:hover, .note > .body:hover {
@@ -48,6 +48,22 @@
}
.indented {
- border-left: 3px solid #444;
+ border-left: 3px solid var(--gray-tertiary);
padding-left: 2px;
-}
\ No newline at end of file
+}
+
+.indented .active {
+ background-color: var(--gray-tertiary);
+ margin-left: -5px;
+ border-left: 3px solid var(--highlight);
+}
+
+
+.indented .note {
+ border-bottom: none;
+ padding: 4px;
+}
+
+.note .body a {
+ color: var(--highlight);
+}
diff --git a/src/element/Note.js b/src/element/Note.js
index 085fe74b..1d34d1fc 100644
--- a/src/element/Note.js
+++ b/src/element/Note.js
@@ -12,12 +12,9 @@ import NoteTime from "./NoteTime";
export default function Note(props) {
const navigate = useNavigate();
- const data = props.data;
const opt = props.options;
const dataEvent = props["data-ev"];
- const reactions = props.reactions;
- const deletion = props.deletion;
- const hightlight = props.hightlight;
+ const { data, isThread, reactions, deletion, hightlight } = props
const users = useSelector(s => s.users?.users);
const ev = dataEvent ?? Event.FromObject(data);
@@ -81,7 +78,7 @@ export default function Note(props) {
}
return (
-
+
{options.showHeader ?
diff --git a/src/element/NoteCreator.css b/src/element/NoteCreator.css
index 866700ab..95aacdff 100644
--- a/src/element/NoteCreator.css
+++ b/src/element/NoteCreator.css
@@ -1,6 +1,6 @@
.note-creator {
margin-bottom: 10px;
- background-color: #333;
+ background-color: var(--gray);
border-radius: 10px;
overflow: hidden;
}
diff --git a/src/element/NoteReaction.css b/src/element/NoteReaction.css
index 13984c9b..ecc03962 100644
--- a/src/element/NoteReaction.css
+++ b/src/element/NoteReaction.css
@@ -1,11 +1,10 @@
.reaction {
margin-bottom: 10px;
- border-bottom: 1px solid #333;
}
.reaction > .note {
margin: 5px;
- border: 1px solid #333;
+ border: 1px solid var(--gray);
border-radius: 10px;
padding: 5px;
}
@@ -19,5 +18,6 @@
}
.reaction > .header > .info {
+ color: #999;
font-size: small;
-}
\ No newline at end of file
+}
diff --git a/src/element/NoteTime.js b/src/element/NoteTime.js
index b260f387..9b2ebc77 100644
--- a/src/element/NoteTime.js
+++ b/src/element/NoteTime.js
@@ -16,9 +16,12 @@ export default function NoteTime(props) {
return fromDate.toLocaleDateString(undefined, { year: "2-digit", month: "short", day: "2-digit", weekday: "short" });
} else if (absAgo > HourInMs) {
return `${fromDate.getHours().toString().padStart(2, '0')}:${fromDate.getMinutes().toString().padStart(2, '0')}`;
+ } else if (absAgo < MinuteInMs) {
+ return 'Just now'
} else {
let mins = parseInt(absAgo / MinuteInMs);
- return `${mins} mins ago`;
+ let minutes = mins === 1 ? 'min' : 'mins'
+ return `${mins} ${minutes} ago`;
}
}
diff --git a/src/element/ProfileImage.css b/src/element/ProfileImage.css
index 4d5f385a..d179d356 100644
--- a/src/element/ProfileImage.css
+++ b/src/element/ProfileImage.css
@@ -7,7 +7,7 @@
width: 40px;
height: 40px;
margin-right: 10px;
- border-radius: 10px;
+ border-radius: 100%;
cursor: pointer;
}
diff --git a/src/element/ProfileImage.js b/src/element/ProfileImage.js
index 977d574b..b75c8427 100644
--- a/src/element/ProfileImage.js
+++ b/src/element/ProfileImage.js
@@ -6,9 +6,7 @@ import { Link, useNavigate } from "react-router-dom";
import useProfile from "../feed/ProfileFeed";
import { profileLink } from "../Util";
-export default function ProfileImage(props) {
- const pubkey = props.pubkey;
- const subHeader = props.subHeader;
+export default function ProfileImage({ pubkey, subHeader, showUsername = true }) {
const navigate = useNavigate();
const user = useProfile(pubkey);
@@ -25,10 +23,12 @@ export default function ProfileImage(props) {
return (
navigate(profileLink(pubkey))} />
-
+ {showUsername && (
+
{name}
{subHeader ?
{subHeader}
: null}
-
+
+ )}
)
}
\ No newline at end of file
diff --git a/src/element/Relay.css b/src/element/Relay.css
index 93e1f796..7ff20731 100644
--- a/src/element/Relay.css
+++ b/src/element/Relay.css
@@ -1,10 +1,10 @@
.relay {
margin-bottom: 10px;
- background-color: #222;
+ background-color: var(--gray-secondary);
border-radius: 5px;
text-align: start;
}
.relay > div {
padding: 5px;
-}
\ No newline at end of file
+}
diff --git a/src/element/Relay.js b/src/element/Relay.js
index 2d3ac597..7ab13828 100644
--- a/src/element/Relay.js
+++ b/src/element/Relay.js
@@ -24,7 +24,7 @@ export default function Relay(props) {
<>
-
+
{name}
@@ -47,4 +47,4 @@ export default function Relay(props) {
>
)
-}
\ No newline at end of file
+}
diff --git a/src/element/Thread.js b/src/element/Thread.js
index 0a8a9380..313a710b 100644
--- a/src/element/Thread.js
+++ b/src/element/Thread.js
@@ -47,7 +47,7 @@ export default function Thread(props) {
function renderRoot() {
if (root) {
- return
+ return
} else {
return
Loading thread root.. ({notes.length} notes loaded)
diff --git a/src/index.css b/src/index.css
index 6635e421..3023b5b6 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,12 +1,41 @@
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap');
+:root {
+ --font-color: #FFF;
+ --bg-color: #000;
+ --modal-bg-color: rgba(0,0,0, 0.8);
+ --gray-superlight: #EEE;
+ --gray-light: #999;
+ --gray-medium: #666;
+ --gray: #333;
+ --gray-secondary: #222;
+ --gray-tertiary: #444;
+ --highlight: #A9E000;
+ --error: #FF6053;
+ --success: #2AD544;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ --font-color: #000;
+ --bg-color: #FFF;
+ --highlight: #FF9B00;
+ --modal-bg-color: rgba(240, 240, 240, 0.8);
+ --gray: #CCC;
+ --gray-secondary: #DDD;
+ --gray-tertiary: #EEE;
+ --gray-superlight: #333;
+ --gray-light: #555;
+ }
+}
+
body {
margin: 0;
font-family: 'Montserrat', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
- background-color: #000;
- color: #fff;
+ background-color: var(--bg-color);
+ color: var(--font-color);
}
code {
@@ -42,19 +71,28 @@ code {
border-radius: 5px;
cursor: pointer;
user-select: none;
- background-color: #000;
+ background-color: var(--bg-color);
border: 1px solid;
display: inline-block;
}
+.btn-warn {
+ border-color: var(--error);
+}
+
+.btn-success {
+ border-color: var(--success);
+}
+
.btn.active {
border: 2px solid;
- background-color: #222;
+ background-color: var(--gray-secondary);
+ color: var(--font-color);
font-weight: bold;
}
.btn:hover {
- background-color: #333;
+ background-color: var(--gray);
}
.btn-sm {
@@ -73,8 +111,12 @@ input[type="text"], input[type="password"], input[type="number"], textarea {
padding: 10px;
border-radius: 5px;
border: 0;
- background-color: #333;
- color: #eee;
+ background-color: var(--gray);
+ color: var(--font-color);
+}
+
+textarea:placeholder {
+ color: var(--gray-superlight);
}
.flex {
@@ -122,7 +164,7 @@ a {
span.pill {
display: inline-block;
- background-color: #333;
+ background-color: var(--gray);
padding: 2px 10px;
border-radius: 10px;
user-select: none;
@@ -130,7 +172,8 @@ span.pill {
}
span.pill.active {
- background-color: #444;
+ background-color: var(--gray-tertiary);
+ color: var(--font-color);
font-weight: bold;
}
@@ -186,7 +229,7 @@ div.form-group > div:nth-child(2) input {
.modal .modal-content > div {
padding: 10px;
border-radius: 10px;
- background-color: #333;
+ background-color: var(--gray);
margin-top: 5vh;
}
@@ -224,11 +267,19 @@ body.scroll-lock {
margin: 0;
}
-.tabs > div.active {
- background-color: #222;
- font-weight: bold;
+.error {
+ color: var(--error);
}
-.error {
- color: red;
-}
\ No newline at end of file
+.root-tabs {
+ padding: 0 2px;
+ align-items: center;
+ justify-content: flex-start;
+}
+
+.root-tab {
+ border-bottom: 3px solid var(--gray-secondary);
+}
+.root-tab.active {
+ border-bottom: 3px solid var(--highlight);
+}
diff --git a/src/pages/Layout.css b/src/pages/Layout.css
index 20ef3ef7..e4f7bfa9 100644
--- a/src/pages/Layout.css
+++ b/src/pages/Layout.css
@@ -1,3 +1,7 @@
.notifications {
margin-right: 10px;
-}
\ No newline at end of file
+}
+
+.unread-count {
+ margin-left: .2em;
+}
diff --git a/src/pages/Layout.js b/src/pages/Layout.js
index 21eb8222..c7d5fee8 100644
--- a/src/pages/Layout.js
+++ b/src/pages/Layout.js
@@ -54,14 +54,18 @@ export default function Layout(props) {
}
function accountHeader() {
- const unreadNotifications = notifications?.filter(a => (a.created_at * 1000) > readNotifications).length ?? 0;
+ const unreadNotifications = notifications?.filter(a => (a.created_at * 1000) > readNotifications).length;
return (
<>
goToNotifications(e)}>
- {unreadNotifications}
+ {unreadNotifications !== 0 && (
+
+ {unreadNotifications}
+
+ )}
-
+
>
)
}
diff --git a/src/pages/ProfilePage.css b/src/pages/ProfilePage.css
index 933b272f..547eb136 100644
--- a/src/pages/ProfilePage.css
+++ b/src/pages/ProfilePage.css
@@ -18,11 +18,46 @@
margin: 0;
}
+.profile .avatar-wrapper {
+ margin: auto 10px;
+}
+
.profile .avatar {
width: 256px;
height: 256px;
background-size: cover;
- border-radius: 10px;
+ border-radius: 100%;
+}
+
+.profile .details {
+ margin-top: auto;
+ margin-bottom: auto;
+ overflow: hidden;
+}
+
+.profile .website {
+ padding-left: 0;
+ color: var(--highlight);
+ margin-bottom: 2px;
+}
+
+.profile .lnurl {
+ padding-left: 0;
+}
+
+.profile .btn-icon {
+ padding: 6px;
+ margin-left: 4px;
+}
+
+.profile .website::before {
+ content: '🔗 ';
+ font-size: 10px;
+}
+
+.profile .lnurl::before {
+ content: '⚡️ ';
+ font-size: 10px;
}
@media(max-width: 720px) {
@@ -31,7 +66,34 @@
align-items: center;
}
.profile > div:last-child {
- margin: 0;
+ margin: 5px 0;
width: 100%;
}
}
+
+@media(max-width: 360px) {
+ .profile .name { flex-direction: column; }
+ .profile .name .btn {
+ margin-top: 5px;
+ }
+}
+
+.tabs {
+ display: flex;
+ justify-content: flex-start;
+ width: 100%;
+ margin: 10px 0;
+}
+
+.tabs > div {
+ margin-right: 0;
+}
+
+.tab {
+ margin: 0;
+ padding: 4px;
+ border-bottom: 3px solid var(--gray-secondary);
+}
+.tab.active {
+ border-bottom: 3px solid var(--highlight);
+}
diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js
index c27fae3b..16753dcc 100644
--- a/src/pages/ProfilePage.js
+++ b/src/pages/ProfilePage.js
@@ -4,7 +4,7 @@ import Nostrich from "../nostrich.jpg";
import { useState } from "react";
import { useSelector } from "react-redux";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faQrcode } from "@fortawesome/free-solid-svg-icons";
+import { faQrcode, faGear } from "@fortawesome/free-solid-svg-icons";
import { useNavigate, useParams } from "react-router-dom";
import useProfile from "../feed/ProfileFeed";
@@ -33,20 +33,30 @@ export default function ProfilePage() {
{user?.display_name || user?.name}
+ {user?.nip05 && }
- {isMe ?
navigate("/settings")}>Settings
:
}
+ {isMe ? (
+
navigate("/settings")}>
+
+
+ ) :
+ }
- {user?.nip05 &&
}
{extractLinks([user?.about])}
- {user?.website ?
{user?.website} : null}
- {lnurl ?
-
setShowLnQr(true)}>
-
+ {user?.website && (
+
+ )}
+
+ {lnurl ?
+ {lnurl}
+
setShowLnQr(true)}>
+
-
⚡️ {lnurl}
: null}
setShowLnQr(false)} />
>
@@ -56,21 +66,21 @@ export default function ProfilePage() {
return (
<>
-
+
-
- {details()}
+
+ {details()}
-
Notes
-
Reactions
-
Followers
-
Follows
+
Notes
+
Reactions
+
Followers
+
Follows
>
)
-}
\ No newline at end of file
+}
diff --git a/src/pages/Root.css b/src/pages/Root.css
index 1a1b7674..6363b308 100644
--- a/src/pages/Root.css
+++ b/src/pages/Root.css
@@ -1,4 +1,4 @@
.root-tabs > div {
padding: 5px 0;
- background-color: #333;
-}
\ No newline at end of file
+ margin-right: 0;
+}
diff --git a/src/pages/Root.js b/src/pages/Root.js
index 858c0f50..5d41e58c 100644
--- a/src/pages/Root.js
+++ b/src/pages/Root.js
@@ -29,10 +29,10 @@ export default function RootPage() {
{pubKey ? <>
-
setTab(RootTab.Follows)}>
+
setTab(RootTab.Follows)}>
Follows
-
setTab(RootTab.Global)}>
+
setTab(RootTab.Global)}>
Global
> : null}
diff --git a/src/pages/SettingsPage.css b/src/pages/SettingsPage.css
index a78f5c57..cf2c2efa 100644
--- a/src/pages/SettingsPage.css
+++ b/src/pages/SettingsPage.css
@@ -1,10 +1,10 @@
-
.settings .avatar {
width: 256px;
height: 256px;
background-size: cover;
- border-radius: 10px;
+ border-radius: 100%;
cursor: pointer;
+ margin-bottom: 20px;
}
.settings .avatar .edit {
@@ -14,7 +14,7 @@
width: 100%;
height: 100%;
opacity: 0;
- background-color: black;
+ background-color: var(--bg-color);
}
.settings .avatar .edit:hover {
diff --git a/src/pages/SettingsPage.js b/src/pages/SettingsPage.js
index 2e9630fa..9fea2319 100644
--- a/src/pages/SettingsPage.js
+++ b/src/pages/SettingsPage.js
@@ -20,6 +20,7 @@ export default function SettingsPage(props) {
const publisher = useEventPublisher();
const [name, setName] = useState("");
+ const [displayName, setDisplayName] = useState("");
const [picture, setPicture] = useState("");
const [about, setAbout] = useState("");
const [website, setWebsite] = useState("");
@@ -31,6 +32,7 @@ export default function SettingsPage(props) {
useEffect(() => {
if (user) {
setName(user.name ?? "");
+ setDisplayName(user.display_name ?? "")
setPicture(user.picture ?? "");
setAbout(user.about ?? "");
setWebsite(user.website ?? "");
@@ -45,6 +47,7 @@ export default function SettingsPage(props) {
let userCopy = {
...user,
name,
+ display_name: displayName,
about,
picture,
website,
@@ -102,6 +105,12 @@ export default function SettingsPage(props) {
setName(e.target.value)} />
+
+
Display name:
+
+ setDisplayName(e.target.value)} />
+
+