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)} /> +
+
About: