diff --git a/package.json b/package.json index 241c4c0d..cb8bf8ed 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "snort", + "name": "nostr_ui", "version": "0.1.0", "private": true, "dependencies": { @@ -33,19 +33,7 @@ "react-textarea-autosize": "^8.4.0", "react-twitter-embed": "^4.0.4", "typescript": "^4.9.4", - "uuid": "^9.0.0", - "workbox-background-sync": "^6.4.2", - "workbox-broadcast-update": "^6.4.2", - "workbox-cacheable-response": "^6.4.2", - "workbox-core": "^6.4.2", - "workbox-expiration": "^6.4.2", - "workbox-google-analytics": "^6.4.2", - "workbox-navigation-preload": "^6.4.2", - "workbox-precaching": "^6.4.2", - "workbox-range-requests": "^6.4.2", - "workbox-routing": "^6.4.2", - "workbox-strategies": "^6.4.2", - "workbox-streams": "^6.4.2" + "uuid": "^9.0.0" }, "scripts": { "start": "react-scripts start", diff --git a/public/index.html b/public/index.html index 7ecdff29..57dd0e50 100644 --- a/public/index.html +++ b/public/index.html @@ -8,7 +8,7 @@ + content="default-src 'self'; child-src 'none'; frame-src youtube.com www.youtube.com https://platform.twitter.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; connect-src wss://* 'self' https://*; img-src * data:; font-src https://fonts.gstatic.com; media-src *; script-src 'self' https://static.cloudflareinsights.com https://platform.twitter.com;" /> diff --git a/src/Number.ts b/src/Number.ts deleted file mode 100644 index 64460dbd..00000000 --- a/src/Number.ts +++ /dev/null @@ -1,14 +0,0 @@ -const intl = new Intl.NumberFormat("en", { - minimumFractionDigits: 0, - maximumFractionDigits: 2, -}); - -export function formatShort(n: number) { - if (n < 999) { - return n - } else if (n < 1e8) { - return `${intl.format(Math.floor(n / 1e3))}K` - } else { - return `${intl.format(Math.floor(n / 1e6))}M` - } -} diff --git a/src/element/FollowsYou.css b/src/element/FollowsYou.css deleted file mode 100644 index 89d1b40f..00000000 --- a/src/element/FollowsYou.css +++ /dev/null @@ -1,6 +0,0 @@ -.follows-you { - color: var(--font-tertiary-color); - font-size: var(--font-size-tiny); - margin-left: .2em; - font-weight: normal -} diff --git a/src/element/FollowsYou.tsx b/src/element/FollowsYou.tsx index c9cf63af..88280e27 100644 --- a/src/element/FollowsYou.tsx +++ b/src/element/FollowsYou.tsx @@ -1,4 +1,3 @@ -import "./FollowsYou.css"; import { useMemo } from "react"; import { useSelector } from "react-redux"; import { HexKey } from "../nostr"; @@ -22,7 +21,7 @@ export default function FollowsYou({ pubkey }: FollowsYouProps ) { return ( <> - { followsMe ? follows you : null } + { followsMe ? follows you : null } ) } diff --git a/src/element/Invoice.css b/src/element/Invoice.css index 824ea481..04dbf0b1 100644 --- a/src/element/Invoice.css +++ b/src/element/Invoice.css @@ -1,6 +1,7 @@ .note-invoice { - border: 1px solid var(--font-secondary-color); - border-radius: 16px; + background: var(--bg-color); + border-radius: 10px; + border: 1px solid var(--gray-tertiary); padding: 12px; margin: 10px auto; } @@ -10,5 +11,5 @@ } .note-invoice small { - color: var(--font-secondary-color); + color: var(--gray-medium); } diff --git a/src/element/LNURLTip.css b/src/element/LNURLTip.css index f6d3d68a..3c0d1b73 100644 --- a/src/element/LNURLTip.css +++ b/src/element/LNURLTip.css @@ -17,24 +17,9 @@ background-color: var(--gray); } -.sat-amount { - display: inline-block; - background-color: var(--gray-secondary); - color: var(--font-color); - padding: 2px 10px; - border-radius: 10px; - user-select: none; - margin: 2px 5px; -} - -.sat-amount:hover { - cursor: pointer; -} - -.sat-amount.active { - font-weight: bold; - color: var(--note-bg); - background-color: var(--font-color); +.lnurl-tip .pill.active { + color: var(--bg-color); + background-color: var(--font-color); } .lnurl-tip .invoice { diff --git a/src/element/LNURLTip.tsx b/src/element/LNURLTip.tsx index bf5652ba..0d947ded 100644 --- a/src/element/LNURLTip.tsx +++ b/src/element/LNURLTip.tsx @@ -188,11 +188,11 @@ export default function LNURLTip(props: LNURLTipProps) { setComment(e.target.value)} /> : null}
- {serviceAmounts.map(a => selectAmount(a)}> + {serviceAmounts.map(a => selectAmount(a)}> {a.toLocaleString()} )} {payService ? - selectAmount(-1)}> + selectAmount(-1)}> Custom : null}
diff --git a/src/element/Note.css b/src/element/Note.css index 3c1e14b4..85c87c43 100644 --- a/src/element/Note.css +++ b/src/element/Note.css @@ -1,40 +1,35 @@ .note { - margin-bottom: 12px; - border-radius: 16px; + margin-bottom: 10px; + border-radius: 10px; background-color: var(--note-bg); - padding: 12px; + padding: 10px 10px 8px 10px; min-height: 110px; } -@media (min-width: 720px) { - .note { margin-bottom: 24px; padding: 24px; } -} .note.thread { border-bottom: none; } -.note .header { - display: flex; - flex-direction: row; - justify-content: space-between; +.note > .header > .pfp { + flex-grow: 1; } .note > .header .reply { - font-size: var(--font-size-tiny); - color: var(--font-secondary-color); + font-size: 12px; + color: var(--gray-light); } .note > .header > .info { - font-size: var(--font-size); + font-size: 10px; white-space: nowrap; - color: var(--font-secondary-color); + color: var(--gray-light); + align-self: flex-start; } .note > .body { - margin-top: 12px; - overflow: hidden; - text-overflow: ellipsis; + padding: 10px 5px; white-space: pre-wrap; + overflow: hidden; word-break: normal; } @@ -43,32 +38,7 @@ } .note > .footer { - display: flex; - flex-direction: row-reverse; - margin-top: 12px; -} - -.note > .note-creator { - margin-top: 12px; -} - -@media (min-width: 720px) { - .note > .footer { margin-top: 24px; } - .note > .note-creator { margin-top: 24px; } -} - - -.thread.note { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - -.thread.note, .indented .note { - margin-bottom: 0; -} - -.indented .note { - border-radius: 0; + text-align: right; } .indented { @@ -76,73 +46,8 @@ padding-left: 2px; } -.note:last-child { - border-bottom-right-radius: 16px; - margin-bottom: 24px; -} - -.indented .note.active:last-child { - border-bottom-right-radius: 16px; - margin-bottom: 24px; -} - -.indented > .indented .note:last-child { - border-bottom-right-radius: 0px; - margin-bottom: 0; -} - .indented .active { background-color: var(--gray-tertiary); margin-left: -5px; border-left: 3px solid var(--highlight); - border-radius: 0; -} - -.reaction-pill { - display: flex; - flex-direction: row; - padding: 0px 10px; - user-select: none; - color: var(--font-secondary-color); -} - -.reaction-pill .reaction-pill-number { - margin-left: 8px; -} - -.reaction-pill.reacted { - color: var(--highlight); -} - -.reaction-pill:hover { - cursor: pointer; -} - -.note.active > .header .reply { - color: var(--font-tertiary-color); -} - -.note.active > .header > .info { - color: var(--font-tertiary-color); -} - -.note.active > .footer > .reaction-pill { - color: var(--font-tertiary-color); -} - -@media (prefers-color-scheme: light) { - .indented .active { - background-color: var(--gray-secondary); - } - .note.active > .header .reply { - color: var(--font-secondary-color); - } - - .note.active > .header > .info { - color: var(--font-secondary-color); - } - - .note.active > .footer > .reaction-pill { - color: var(--font-seco666ndary-color); - } -} +} \ No newline at end of file diff --git a/src/element/Note.tsx b/src/element/Note.tsx index f2e8e07d..3e85bea7 100644 --- a/src/element/Note.tsx +++ b/src/element/Note.tsx @@ -78,7 +78,7 @@ export default function Note(props: NoteProps) { let pubMentions = mentions.length > maxMentions ? `${mentions?.slice(0, maxMentions).join(", ")} & ${othersLength} other${othersLength > 1 ? 's' : ''}` : mentions?.join(", "); return (
- {(pubMentions?.length ?? 0) > 0 ? pubMentions : replyId ? hexToBech32("note", replyId)?.substring(0, 12) : ""} + ➡️ {(pubMentions?.length ?? 0) > 0 ? pubMentions : replyId ? hexToBech32("note", replyId)?.substring(0, 12) : ""}
) } diff --git a/src/element/NoteCreator.tsx b/src/element/NoteCreator.tsx index 442f7671..f3a3f1de 100644 --- a/src/element/NoteCreator.tsx +++ b/src/element/NoteCreator.tsx @@ -100,4 +100,4 @@ export function NoteCreator(props: NoteCreatorProps) { ); -} +} \ No newline at end of file diff --git a/src/element/NoteFooter.tsx b/src/element/NoteFooter.tsx index fef70fc5..22a234fa 100644 --- a/src/element/NoteFooter.tsx +++ b/src/element/NoteFooter.tsx @@ -3,7 +3,6 @@ import { useSelector } from "react-redux"; import { faHeart, faReply, faThumbsDown, faTrash, faBolt, faRepeat } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { formatShort } from "../Number"; import useEventPublisher from "../feed/EventPublisher"; import { getReactions, normalizeReaction, Reaction } from "../Util"; import { NoteCreator } from "./NoteCreator"; @@ -30,6 +29,7 @@ export default function NoteFooter(props: NoteFooterProps) { const isMine = ev.RootPubKey === login; const reactions = useMemo(() => getReactions(related, ev.Id, EventKind.Reaction), [related]); const reposts = useMemo(() => getReactions(related, ev.Id, EventKind.Repost), [related]); + const groupReactions = useMemo(() => { return reactions?.reduce((acc, { content }) => { let r = normalizeReaction(content); @@ -50,10 +50,8 @@ export default function NoteFooter(props: NoteFooterProps) { } async function react(content: string) { - if (!hasReacted(content)) { let evLike = await publisher.react(ev, content); publisher.broadcast(evLike); - } } async function deleteEvent() { @@ -75,11 +73,9 @@ export default function NoteFooter(props: NoteFooterProps) { if (service) { return ( <> -
setTip(true)}> -
- -
-
+ setTip(true)}> + + ) } @@ -89,10 +85,10 @@ export default function NoteFooter(props: NoteFooterProps) { function reactionIcon(content: string, reacted: boolean) { switch (content) { case Reaction.Positive: { - return ; + return ; } case Reaction.Negative: { - return ; + return ; } } return content; @@ -101,45 +97,30 @@ export default function NoteFooter(props: NoteFooterProps) { return ( <>
- {isMine && ( -
-
- deleteEvent()} /> -
-
- )} -
setReply(s => !s)}> -
- -
-
-
repost()}> -
- -
- {reposts.length > 0 && ( -
- {formatShort(reposts.length)} -
- )} -
-
react("+")}> -
- -
-
- {formatShort(groupReactions[Reaction.Positive])} -
-
-
react("-")}> -
- -
-
- {formatShort(groupReactions[Reaction.Negative])} -
-
+ {isMine ? + deleteEvent()} /> + : null} {tipButton()} + repost()}> + + {reposts.length > 0 ? <> {reposts.length} : null} + + setReply(s => !s)}> + + + {Object.keys(groupReactions || {}).map((emoji) => { + let didReact = hasReacted(emoji); + return ( + { + if (!didReact) { + react(emoji); + } + }} key={emoji}> + {reactionIcon(emoji, didReact)} + {groupReactions[emoji] ? <> {groupReactions[emoji]} : null} + + ) + })}
.note { margin: 10px 20px; + border: 1px solid var(--gray); + border-radius: 10px; + padding: 10px; } -.reaction > .header { - display: flex; - flex-direction: row; - justify-content: space-between; +.reaction > .header > .pfp { + flex-grow: 1; } .reaction > .header .reply { - font-size: var(--font-size-small); + font-size: small; } .reaction > .header > .info { - font-size: var(--font-size); - white-space: nowrap; - color: var(--font-secondary-color); - margin-right: 24px; + color: var(--gray-light); + font-size: 10px; + align-self: flex-start; +} + +.reaction .reaction-text { + margin-left: .2em; + color: var(--gray-light); } diff --git a/src/element/NoteReaction.tsx b/src/element/NoteReaction.tsx index c199df59..bd43b3a0 100644 --- a/src/element/NoteReaction.tsx +++ b/src/element/NoteReaction.tsx @@ -44,6 +44,13 @@ export default function NoteReaction(props: NoteReactionProps) { } } + function tagLine() { + switch (ev.Kind) { + case EventKind.Reaction: return reacted with {mapReaction(ev.Content)}; + case EventKind.Repost: return reposted + } + } + /** * Some clients embed the reposted note in the content */ diff --git a/src/element/NoteTime.tsx b/src/element/NoteTime.tsx index 5e27f906..ae17dd4c 100644 --- a/src/element/NoteTime.tsx +++ b/src/element/NoteTime.tsx @@ -25,10 +25,11 @@ export default function NoteTime(props: NoteTimeProps) { return fallback } else { let mins = Math.floor(absAgo / MinuteInMs); + let minutes = mins === 1 ? 'min' : 'mins' if(ago < 0) { - return `in ${mins}m`; + return `in ${mins} ${minutes}`; } - return `${mins}m`; + return `${mins} ${minutes} ago`; } } @@ -47,4 +48,4 @@ export default function NoteTime(props: NoteTimeProps) { }, [from]); return <>{time} -} +} \ No newline at end of file diff --git a/src/element/NoteToSelf.css b/src/element/NoteToSelf.css new file mode 100644 index 00000000..b33de862 --- /dev/null +++ b/src/element/NoteToSelf.css @@ -0,0 +1,43 @@ +.nts { + display: flex; + align-items: center; +} + +.note-to-self { + margin-left: 5px; + margin-top: 3px; +} + +.nts .avatar-wrapper { + margin-right: 8px; +} + +.nts .avatar { + border-width: 1px; + width: 40px; + height: 40px; +} +.nts .avatar.clickable { + cursor: pointer; +} + +.nts a { + text-decoration: none; +} + +.nts a:hover { + text-decoration: underline; + text-decoration-color: var(--gray-superlight); +} + +.nts .name { + margin-top: -.2em; + display: flex; + flex-direction: column; + font-weight: bold; +} + +.nts .nip05 { + margin: 0; + margin-top: -.2em; +} diff --git a/src/element/NoteToSelf.tsx b/src/element/NoteToSelf.tsx new file mode 100644 index 00000000..79d963ee --- /dev/null +++ b/src/element/NoteToSelf.tsx @@ -0,0 +1,56 @@ +import "./NoteToSelf.css"; + +import { Link, useNavigate } from "react-router-dom"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faBook, faCertificate } from "@fortawesome/free-solid-svg-icons" +import useProfile from "../feed/ProfileFeed"; +import Nip05 from "./Nip05"; +import { profileLink } from "../Util"; + +export interface NoteToSelfProps { + pubkey: string, + clickable?: boolean + className?: string, + link?: string +}; + +function NoteLabel({pubkey, link}:NoteToSelfProps) { + const user = useProfile(pubkey)?.get(pubkey); + return ( +
+ Note to Self + {user?.nip05 && } +
+ ) +} + +export default function NoteToSelf({ pubkey, clickable, className, link }: NoteToSelfProps) { + const navigate = useNavigate(); + + const clickLink = () => { + if(clickable) { + navigate(link ?? profileLink(pubkey)) + } + } + + return ( +
+
+
+ +
+
+
+
+ {clickable && ( + + + + ) || ( + + )} +
+
+
+ ) +} diff --git a/src/element/ProfileImage.tsx b/src/element/ProfileImage.tsx index 9734b51a..39c824e1 100644 --- a/src/element/ProfileImage.tsx +++ b/src/element/ProfileImage.tsx @@ -7,7 +7,6 @@ import { hexToBech32, profileLink } from "../Util"; import Avatar from "./Avatar" import Nip05 from "./Nip05"; import { HexKey } from "../nostr"; -import { MetadataCache } from "../db/User"; export interface ProfileImageProps { pubkey: HexKey, @@ -22,7 +21,13 @@ export default function ProfileImage({ pubkey, subHeader, showUsername = true, c const user = useProfile(pubkey)?.get(pubkey); const name = useMemo(() => { - return getDisplayName(user, pubkey); + let name = hexToBech32("npub", pubkey).substring(0, 12); + if ((user?.display_name?.length ?? 0) > 0) { + name = user!.display_name!; + } else if ((user?.name?.length ?? 0) > 0) { + name = user!.name!; + } + return name; }, [user, pubkey]); return ( @@ -43,13 +48,3 @@ export default function ProfileImage({ pubkey, subHeader, showUsername = true, c ) } - -export function getDisplayName(user: MetadataCache | undefined, pubkey: HexKey) { - let name = hexToBech32("npub", pubkey).substring(0, 12); - if ((user?.display_name?.length ?? 0) > 0) { - name = user!.display_name!; - } else if ((user?.name?.length ?? 0) > 0) { - name = user!.name!; - } - return name; -} diff --git a/src/element/Relay.css b/src/element/Relay.css index e9c22206..76bf2a6b 100644 --- a/src/element/Relay.css +++ b/src/element/Relay.css @@ -6,7 +6,6 @@ display: grid; grid-template-columns: min-content auto; overflow: hidden; - font-size: var(--font-size-small); } .relay > div { @@ -19,24 +18,4 @@ background-color: var(--gray-tertiary); border-radius: 0 0 5px 5px; white-space: nowrap; - font-size: var(--font-size-small); -} - -.icon-btn { - padding: 2px 10px; - border-radius: 10px; - background-color: var(--gray); - user-select: none; - color: var(--font-color); -} - -.icon-btn:hover { - cursor: pointer; -} - -.checkmark { - margin-left: .5em; - padding: 2px 10px; - background-color: var(--gray); - border-radius: 10px; -} +} \ No newline at end of file diff --git a/src/element/Relay.tsx b/src/element/Relay.tsx index d165d01b..47c6f732 100644 --- a/src/element/Relay.tsx +++ b/src/element/Relay.tsx @@ -44,13 +44,13 @@ export default function Relay(props: RelayProps) { {name}
Write - configure({ write: !relaySettings.write, read: relaySettings.read })}> + configure({ write: !relaySettings.write, read: relaySettings.read })}>
Read - configure({ write: relaySettings.write, read: !relaySettings.read })}> + configure({ write: relaySettings.write, read: !relaySettings.read })}>
@@ -62,7 +62,7 @@ export default function Relay(props: RelayProps) { {state?.disconnects}
- setShowExtra(s => !s)}> + setShowExtra(s => !s)}>
@@ -79,7 +79,7 @@ export default function Relay(props: RelayProps) {
Delete - dispatch(removeRelay(props.addr))}> + dispatch(removeRelay(props.addr))}>
diff --git a/src/element/Text.css b/src/element/Text.css index 4ec9e40c..6d3c7b8f 100644 --- a/src/element/Text.css +++ b/src/element/Text.css @@ -23,7 +23,6 @@ .text p { margin: 0; - margin-bottom: 4px; } .text pre { @@ -53,44 +52,9 @@ max-height: 500px; margin: 10px auto; display: block; - border-radius: 12px; } .text iframe, .text video { width: -webkit-fill-available; aspect-ratio: 16 / 9; } - -.text .truncate { - display: inline-block; - margin: 0; - max-width: 260px; - white-space: nowrap; - text-overflow: ellipsis; - color: var(--highlight); - height: var(--font-size); -} - -@media (min-width: 420px) { - .text .truncate { - max-width: 300px; - } -} - -@media (min-width: 520px) { - .text .truncate { - max-width: 360px; - } -} - -@media (min-width: 720px) { - .text .truncate { - max-width: 420px; - } -} - -@media (min-width: 1024px) { - .text .truncate { - max-width: 600px; - } -} diff --git a/src/element/Text.tsx b/src/element/Text.tsx index 3019f4d1..ee28313e 100644 --- a/src/element/Text.tsx +++ b/src/element/Text.tsx @@ -37,11 +37,7 @@ function transformHttpLink(a: string) { return