From 076d5d8cd58b6a05be92375bbe0d41d6397a55c4 Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 24 Jul 2023 15:30:21 +0100 Subject: [PATCH] Profile / Thread styles --- .prettierignore | 6 + .vscode/settings.json | 3 +- .yarnrc.yml | 2 +- package.json | 3 +- packages/app/.prettierignore | 3 - packages/app/public/icons.svg | 53 ++- packages/app/public/index.html | 2 +- packages/app/src/Element/Copy.tsx | 2 +- packages/app/src/Element/LiveEvent.tsx | 2 +- packages/app/src/Element/Nip05.tsx | 2 +- packages/app/src/Element/Note.css | 3 +- packages/app/src/Element/NoteFooter.tsx | 9 +- packages/app/src/Element/NoteReaction.css | 23 +- packages/app/src/Element/NoteReaction.tsx | 25 +- packages/app/src/Element/ProfileImage.tsx | 3 +- packages/app/src/Element/Tabs.css | 25 +- packages/app/src/Element/Tabs.tsx | 3 +- packages/app/src/Element/Thread.css | 87 +---- packages/app/src/Element/Thread.tsx | 10 +- packages/app/src/Pages/Layout.tsx | 6 +- packages/app/src/Pages/ProfilePage.css | 120 +++---- packages/app/src/Pages/ProfilePage.tsx | 159 +++++++-- packages/app/src/Pages/Root.tsx | 27 +- packages/app/src/index.css | 17 +- packages/app/src/lang.json | 9 + packages/app/src/translations/en.json | 9 +- packages/nostr/src/client/conn.ts | 16 +- packages/nostr/src/client/emitter.ts | 22 +- packages/nostr/src/client/index.ts | 30 +- packages/nostr/src/client/relay.ts | 32 +- packages/nostr/src/crypto.ts | 16 +- packages/nostr/src/event/contact-list.ts | 10 +- packages/nostr/src/event/deletion.ts | 6 +- packages/nostr/src/event/direct-message.ts | 20 +- packages/nostr/src/event/index.ts | 8 +- packages/nostr/src/event/set-metadata.ts | 16 +- packages/nostr/src/event/text.ts | 4 +- packages/nostr/test/browser/index.html | 2 +- packages/nostr/test/browser/server.ts | 2 +- packages/nostr/test/setup.ts | 6 +- packages/nostr/test/test.deletion.ts | 4 +- packages/nostr/test/test.direct-message.ts | 20 +- .../nostr/test/test.internet-identifier.ts | 4 +- packages/nostr/test/test.relay-info.ts | 2 +- packages/nostr/test/test.set-metadata.ts | 4 +- packages/nostr/test/test.text-note.ts | 4 +- packages/shared/src/feed-cache.ts | 4 +- packages/shared/src/utils.ts | 2 +- .../system-react/src/useRequestBuilder.tsx | 4 +- packages/system-react/src/useSystemState.tsx | 2 +- packages/system-react/src/useUserProfile.ts | 2 +- packages/system/src/cache/user-metadata.ts | 6 +- packages/system/src/connection.ts | 4 +- packages/system/src/const.ts | 2 +- packages/system/src/event-builder.ts | 4 +- packages/system/src/event-publisher.ts | 2 +- packages/system/src/gossip-model.ts | 2 +- packages/system/src/impl/nip4.ts | 4 +- packages/system/src/impl/nip46.ts | 4 +- packages/system/src/impl/nip7.ts | 4 +- packages/system/src/profile-cache.ts | 2 +- packages/system/src/query.ts | 4 +- packages/system/src/text.ts | 335 +++++++++--------- packages/system/src/utils.ts | 38 +- packages/system/tests/request-builder.test.ts | 2 +- packages/system/tests/utils.test.ts | 8 +- yarn.lock | 10 + 67 files changed, 684 insertions(+), 602 deletions(-) create mode 100644 .prettierignore delete mode 100644 packages/app/.prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..bf970ce0 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +.yarn/ +build/ +.vscode/ +.github/ +transifex.yml +dist/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 08e81730..7d9b0afe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,5 +14,6 @@ }, "typescript.tsdk": ".yarn/sdks/typescript/lib", "typescript.enablePromptUseWorkspaceTsdk": true, - "eslint.nodePath": ".yarn/sdks" + "eslint.nodePath": ".yarn/sdks", + "prettier.prettierPath": ".yarn/sdks/prettier/index.js" } diff --git a/.yarnrc.yml b/.yarnrc.yml index c2d990ec..fda49efb 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -1,4 +1,4 @@ yarnPath: .yarn/releases/yarn-3.6.1.cjs npmScopes: void-cat: - npmRegistryServer: https://git.v0l.io/api/packages/Kieran/npm/ \ No newline at end of file + npmRegistryServer: https://git.v0l.io/api/packages/Kieran/npm/ diff --git a/package.json b/package.json index 276d6b77..b0f4902f 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,9 @@ "test": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/app test && yarn workspace @snort/system test" }, "devDependencies": { + "@cloudflare/workers-types": "^4.20230307.0", "@tauri-apps/cli": "^1.2.3", - "@cloudflare/workers-types": "^4.20230307.0" + "prettier": "^3.0.0" }, "prettier": { "printWidth": 120, diff --git a/packages/app/.prettierignore b/packages/app/.prettierignore deleted file mode 100644 index 2ce50019..00000000 --- a/packages/app/.prettierignore +++ /dev/null @@ -1,3 +0,0 @@ -build/ -.github/ -transifex.yml \ No newline at end of file diff --git a/packages/app/public/icons.svg b/packages/app/public/icons.svg index 4a96e53c..2e204b46 100644 --- a/packages/app/public/icons.svg +++ b/packages/app/public/icons.svg @@ -40,7 +40,7 @@ - + @@ -231,6 +231,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/app/public/index.html b/packages/app/public/index.html index 9e4652aa..32dbd7f2 100644 --- a/packages/app/public/index.html +++ b/packages/app/public/index.html @@ -1,4 +1,4 @@ - + diff --git a/packages/app/src/Element/Copy.tsx b/packages/app/src/Element/Copy.tsx index 837705a4..e2520eb1 100644 --- a/packages/app/src/Element/Copy.tsx +++ b/packages/app/src/Element/Copy.tsx @@ -16,7 +16,7 @@ export default function Copy({ text, maxSize = 32, className }: CopyProps) {
copy(text)}> {trimmed} - {copied ? : } + {copied ? : }
); diff --git a/packages/app/src/Element/LiveEvent.tsx b/packages/app/src/Element/LiveEvent.tsx index 5c134e52..d64ed907 100644 --- a/packages/app/src/Element/LiveEvent.tsx +++ b/packages/app/src/Element/LiveEvent.tsx @@ -13,7 +13,7 @@ export function LiveEvent({ ev }: { ev: NostrEvent }) {

{title}

- + diff --git a/packages/app/src/Element/Nip05.tsx b/packages/app/src/Element/Nip05.tsx index 27df2988..546d6c65 100644 --- a/packages/app/src/Element/Nip05.tsx +++ b/packages/app/src/Element/Nip05.tsx @@ -29,7 +29,7 @@ const Nip05 = ({ nip05, pubkey, verifyNip = true }: Nip05Params) => { {domain} - + )}
diff --git a/packages/app/src/Element/Note.css b/packages/app/src/Element/Note.css index f8f7edc7..256eb6da 100644 --- a/packages/app/src/Element/Note.css +++ b/packages/app/src/Element/Note.css @@ -2,7 +2,7 @@ min-height: 110px; display: flex; flex-direction: column; - gap: 12px; + gap: 8px; } .note:hover { @@ -185,7 +185,6 @@ .note.active { border-left: 1px solid var(--highlight); - border-bottom-left-radius: 0; margin-left: -1px; } diff --git a/packages/app/src/Element/NoteFooter.tsx b/packages/app/src/Element/NoteFooter.tsx index fc0bca2d..4a92c86b 100644 --- a/packages/app/src/Element/NoteFooter.tsx +++ b/packages/app/src/Element/NoteFooter.tsx @@ -191,7 +191,7 @@ export default function NoteFooter(props: NoteFooterProps) { function repostIcon() { return (
repost()}> - + {reposts.length > 0 &&
{formatShort(reposts.length)}
}
); @@ -201,12 +201,11 @@ export default function NoteFooter(props: NoteFooterProps) { if (!prefs.enableReactions) { return null; } + const reacted = hasReacted("+"); return ( <> -
react(prefs.reactionEmoji)}> - +
react(prefs.reactionEmoji)}> +
{formatShort(positive.length)}
diff --git a/packages/app/src/Element/NoteReaction.css b/packages/app/src/Element/NoteReaction.css index 5e049e99..59258ae4 100644 --- a/packages/app/src/Element/NoteReaction.css +++ b/packages/app/src/Element/NoteReaction.css @@ -1,23 +1,14 @@ .reaction { -} - -.reaction > .note { - margin: 10px 0; -} - -.reaction > .header { display: flex; - flex-direction: row; - justify-content: space-between; + flex-direction: column; + gap: 8px; } -.reaction > .header .reply { - font-size: var(--font-size-small); +.reaction > div:nth-child(1) { + font-size: 16px; + font-weight: 600; } -.reaction > .header > .info { - font-size: var(--font-size); - white-space: nowrap; - color: var(--font-secondary-color); - margin-right: 24px; +.reaction > div:nth-child(1) svg { + opacity: 0.5; } diff --git a/packages/app/src/Element/NoteReaction.tsx b/packages/app/src/Element/NoteReaction.tsx index aa7cf1c2..808dfca2 100644 --- a/packages/app/src/Element/NoteReaction.tsx +++ b/packages/app/src/Element/NoteReaction.tsx @@ -1,13 +1,16 @@ import "./NoteReaction.css"; import { Link } from "react-router-dom"; import { useMemo } from "react"; -import { EventKind, NostrEvent, TaggedNostrEvent, NostrPrefix, EventExt } from "@snort/system"; +import { EventKind, NostrEvent, TaggedNostrEvent, NostrPrefix } from "@snort/system"; import Note from "Element/Note"; -import ProfileImage from "Element/ProfileImage"; +import { getDisplayName } from "Element/ProfileImage"; import { eventLink, hexToBech32 } from "SnortUtils"; -import NoteTime from "Element/NoteTime"; import useModeration from "Hooks/useModeration"; +import { FormattedMessage } from "react-intl"; +import Icon from "Icons/Icon"; +import { useUserProfile } from "@snort/system-react"; +import { System } from "index"; export interface NoteReactionProps { data: TaggedNostrEvent; @@ -16,6 +19,7 @@ export interface NoteReactionProps { export default function NoteReaction(props: NoteReactionProps) { const { data: ev } = props; const { isMuted } = useModeration(); + const profile = useUserProfile(System, ev.pubkey); const refEvent = useMemo(() => { if (ev) { @@ -60,12 +64,15 @@ export default function NoteReaction(props: NoteReactionProps) { }; return shouldNotBeRendered ? null : ( -
-
- -
- -
+
+
+ +
{root ? : null} {!root && refEvent ? ( diff --git a/packages/app/src/Element/ProfileImage.tsx b/packages/app/src/Element/ProfileImage.tsx index 203dd9e3..9bed17c8 100644 --- a/packages/app/src/Element/ProfileImage.tsx +++ b/packages/app/src/Element/ProfileImage.tsx @@ -50,8 +50,7 @@ export default function ProfileImage({ + onClick={handleClick}>
diff --git a/packages/app/src/Element/Tabs.css b/packages/app/src/Element/Tabs.css index 54cd1afc..fc0ef6d4 100644 --- a/packages/app/src/Element/Tabs.css +++ b/packages/app/src/Element/Tabs.css @@ -5,8 +5,9 @@ overflow-x: scroll; -ms-overflow-style: none; /* for Internet Explorer, Edge */ scrollbar-width: none; /* Firefox */ - margin-bottom: 18px; white-space: nowrap; + gap: 8px; + padding: 16px 12px; } .tabs::-webkit-scrollbar { @@ -14,23 +15,21 @@ } .tab { + background: var(--gray-ultradark); color: var(--font-tertiary-color); - border: 1px solid var(--border-color); - border-radius: 16px; + border-radius: 100px; font-weight: 600; - font-size: 14px; - padding: 6px 12px; - text-align: center; - font-feature-settings: "tnum"; -} - -.tab:not(:last-of-type) { - margin-right: 8px; + font-size: 16px; + padding: 10px 16px; + display: flex; + align-items: center; + justify-items: center; + gap: 6px; } .tab.active { - border-color: var(--font-color); - color: var(--font-color); + color: black; + background: white; } .tabs > div { diff --git a/packages/app/src/Element/Tabs.tsx b/packages/app/src/Element/Tabs.tsx index baca3e18..318afefc 100644 --- a/packages/app/src/Element/Tabs.tsx +++ b/packages/app/src/Element/Tabs.tsx @@ -1,8 +1,9 @@ +import { ReactNode } from "react"; import "./Tabs.css"; import useHorizontalScroll from "Hooks/useHorizontalScroll"; export interface Tab { - text: string; + text: ReactNode; value: number; disabled?: boolean; } diff --git a/packages/app/src/Element/Thread.css b/packages/app/src/Element/Thread.css index 3049886e..59a057fd 100644 --- a/packages/app/src/Element/Thread.css +++ b/packages/app/src/Element/Thread.css @@ -1,7 +1,3 @@ -.thread-container { - margin: 12px 0 150px 0; -} - .thread-container .hidden-note { margin: 0; border-radius: 0; @@ -11,11 +7,6 @@ box-shadow: none; } -.thread-root.note > .body { - margin-top: 8px; - padding-left: 8px; -} - .thread-root.note > .body .text { font-size: 19px; } @@ -31,12 +22,13 @@ } .thread-note.note { - border-radius: 0; - margin-bottom: 0; + border: 0; } -.light .thread-note.note.card { - box-shadow: none; +.thread-note.note .zaps-summary, +.thread-note.note .footer, +.thread-note.note .body { + margin-left: 61px; } .thread-container .hidden-note { @@ -58,83 +50,47 @@ position: relative; } -.line-container { - background: var(--note-bg); -} - .subthread-container.subthread-multi .line-container:before { content: ""; position: absolute; - left: 36px; + left: calc(48px / 2 + 16px); top: 48px; border-left: 1px solid var(--gray-superdark); height: 100%; + z-index: -1; } -@media (min-width: 720px) { - .subthread-container.subthread-multi .line-container:before { - left: 48px; - } -} - -.subthread-container.subthread-mid:not(.subthread-last) .line-container:after { - content: ""; - position: absolute; - left: 36px; - top: 48px; - border-left: 1px solid var(--gray-superdark); - height: 100%; -} - -@media (min-width: 720px) { - .subthread-container.subthread-mid:not(.subthread-last) .line-container:after { - left: 48px; - } -} - -.subthread-container.subthread-mid:not(.subthread-last) .line-container:after { +.subthread-container.subthread-mid:not(.subthread-last) .line-container:before { content: ""; position: absolute; border-left: 1px solid var(--gray-superdark); - left: 36px; + left: calc(48px / 2 + 16px); top: 0; height: 48px; -} - -@media (min-width: 720px) { - .subthread-container.subthread-mid:not(.subthread-last) .line-container:after { - left: 48px; - } + z-index: -1; } .subthread-container.subthread-last .line-container:before { content: ""; position: absolute; border-left: 1px solid var(--gray-superdark); - left: 36px; + left: calc(48px / 2 + 16px); top: 0; height: 48px; -} - -@media (min-width: 720px) { - .subthread-container.subthread-last .line-container:before { - left: 48px; - } + z-index: -1; } .divider-container { - background: var(--note-bg); + margin-right: 16px; } .divider { height: 1px; background: var(--gray-superdark); - margin-left: 28px; - margin-right: 22px; } .divider.divider-small { - margin-left: 80px; + margin-left: calc(16px + 61px); } .thread-container .collapsed, @@ -143,11 +99,6 @@ min-height: 48px; } -.thread-note.is-last-note { - border-bottom-left-radius: 16px; - border-bottom-right-radius: 16px; -} - .thread-container .collapsed { background-color: var(--note-bg); } @@ -155,13 +106,3 @@ .thread-container .hidden-note { padding-left: 48px; } - -.thread-root.thread-root-single.note { - border-bottom-left-radius: 16px; - border-bottom-right-radius: 16px; -} - -.thread-root.ghost-root { - border-top-left-radius: 16px; - border-top-right-radius: 16px; -} diff --git a/packages/app/src/Element/Thread.tsx b/packages/app/src/Element/Thread.tsx index 77e1f265..3e32e40e 100644 --- a/packages/app/src/Element/Thread.tsx +++ b/packages/app/src/Element/Thread.tsx @@ -374,9 +374,11 @@ export default function Thread() { description: "Navigate back button on threads view", }); return ( -
- -
+ <> +
+ +
+
{root && renderRoot(root)} {root && renderChain(root.id)} @@ -392,7 +394,7 @@ export default function Thread() { ); })}
-
+ ); } diff --git a/packages/app/src/Pages/Layout.tsx b/packages/app/src/Pages/Layout.tsx index 8cb59459..f06711ee 100644 --- a/packages/app/src/Pages/Layout.tsx +++ b/packages/app/src/Pages/Layout.tsx @@ -40,7 +40,7 @@ export default function Layout() { }; const shouldHideNoteCreator = useMemo(() => { - const hideOn = ["/settings", "/messages", "/new", "/login", "/donate", "/p/", "/e", "/subscribe", "/live"]; + const hideOn = ["/settings", "/messages", "/new", "/login", "/donate", "/p/", "/e", "/subscribe"]; return isReplyNoteCreatorShowing || hideOn.some(a => location.pathname.startsWith(a)); }, [location, isReplyNoteCreatorShowing]); @@ -50,8 +50,8 @@ export default function Layout() { }, [location]); useEffect(() => { - const widePage = ["/login", "/messages", "/live"]; - const noScroll = ["/messages", "/live"]; + const widePage = ["/login", "/messages"]; + const noScroll = ["/messages"]; if (widePage.some(a => location.pathname.startsWith(a))) { setPageClass(noScroll.some(a => location.pathname.startsWith(a)) ? "scroll-lock" : ""); } else { diff --git a/packages/app/src/Pages/ProfilePage.css b/packages/app/src/Pages/ProfilePage.css index e81b203e..e25d57a5 100644 --- a/packages/app/src/Pages/ProfilePage.css +++ b/packages/app/src/Pages/ProfilePage.css @@ -2,28 +2,21 @@ display: flex; flex-direction: column; align-items: flex-start; + border: 1px solid var(--gray-superdark); } .profile .banner { width: 100%; height: 160px; object-fit: cover; - margin-bottom: -60px; + margin-bottom: -37px; z-index: 0; } -@media (min-width: 720px) { - .profile .banner { - border-radius: 12px; - } -} .profile .profile-actions { - position: absolute; - top: 72px; - right: 0; display: flex; - flex-direction: row; align-items: center; + align-self: flex-end; } .profile .icon-actions { @@ -52,13 +45,13 @@ } .profile .profile-wrapper { - margin: 0 16px; - width: calc(100% - 32px); + margin: 0 16px 12px 16px; display: flex; flex-direction: column; align-items: flex-start; position: relative; overflow: hidden; + gap: 16px; } .profile p { @@ -66,21 +59,26 @@ } .details-wrapper > .name > h2 { - margin: 12px 0 0 0; + margin: 0 0 4px 0; font-weight: 600; - font-size: 19px; - line-height: 23px; + font-size: 21px; +} + +.details-wrapper > .name > .nip05 { + font-size: 15px; } .profile-wrapper > .avatar-wrapper { z-index: 1; + display: flex; + justify-content: space-between; } .profile-wrapper > .avatar-wrapper .avatar { - width: 120px; - height: 120px; + width: 100px; + height: 100px; background-image: var(--img-url); - border: 3px solid var(--bg-color); + border: 3px solid #fff; } .profile .name { @@ -88,28 +86,30 @@ flex-direction: column; } -.profile .details { - width: 100%; +.profile .about { color: var(--font-secondary-color); - margin-bottom: 12px; - font-weight: 400; - font-size: 14px; - line-height: 22px; + font-size: 16px; + line-height: 26px; } -.profile .details p { +.profile .about p { word-break: break-word; } -.profile .details a { +.profile .about a { color: var(--highlight); text-decoration: none; } -.profile .details a:hover { +.profile .about a:hover { text-decoration: underline; } +.profile .about .text { + font-size: inherit; + line-height: inherit; +} + .profile .btn-icon { color: var(--font-color); padding: 6px; @@ -118,54 +118,36 @@ .profile .details-wrapper { display: flex; flex-direction: column; - justify-content: space-between; - width: calc(100% - 32px); + gap: 16px; } -.profile .details .text { - font-size: 14px; -} - -.profile .links { - font-size: 14px; - margin-top: 4px; - margin-left: 2px; - margin-bottom: 12px; -} - -.profile .website { - margin: 4px 0; +.profile .link-section { + display: flex; + flex-direction: column; + gap: 4px; + font-size: 15px; + line-height: 24px; +} + +.profile .link { display: flex; - flex-direction: row; align-items: center; -} - -@media (max-width: 720px) { - .profile .lnurl { - display: none; - } -} - -.profile .website a { - color: var(--font-color); + gap: 8px; } .profile .website a { text-decoration: none; } - .profile .website a:hover { text-decoration: underline; } -.profile .lnurl { - cursor: pointer; +.profile .link svg { + color: var(--highlight); } -.profile .ln-address { - display: flex; - flex-direction: row; - align-items: center; +.profile .lnurl { + cursor: pointer; } .profile .lnurl:hover { @@ -177,21 +159,9 @@ text-overflow: ellipsis; } -.profile .links svg { - color: var(--highlight); - margin-right: 8px; - width: 12px; - height: 12px; -} - -.profile .npub { - display: flex; - flex-direction: row; - align-items: center; -} - -.profile .copy { - margin-top: 12px; +.profile .copy .body { + font-size: inherit; + line-height: inherit; } .qr-modal .pfp { diff --git a/packages/app/src/Pages/ProfilePage.tsx b/packages/app/src/Pages/ProfilePage.tsx index 69115c35..b78d0df0 100644 --- a/packages/app/src/Pages/ProfilePage.tsx +++ b/packages/app/src/Pages/ProfilePage.tsx @@ -1,6 +1,6 @@ import "./ProfilePage.css"; import { useEffect, useState } from "react"; -import { useIntl, FormattedMessage } from "react-intl"; +import { FormattedMessage } from "react-intl"; import { useNavigate, useParams } from "react-router-dom"; import { encodeTLV, @@ -109,7 +109,6 @@ function BookMarksTab({ id }: { id: HexKey }) { } export default function ProfilePage() { - const { formatMessage } = useIntl(); const params = useParams(); const navigate = useNavigate(); const [id, setId] = useState(); @@ -144,16 +143,88 @@ export default function ProfilePage() { const follows = useFollowsFeed(id); // tabs const ProfileTab = { - Notes: { text: formatMessage({ defaultMessage: "Notes" }), value: NOTES }, - Reactions: { text: formatMessage(messages.Reactions), value: REACTIONS }, - Followers: { text: formatMessage(messages.Followers), value: FOLLOWERS }, - Follows: { text: formatMessage(messages.Follows), value: FOLLOWS }, - Zaps: { text: formatMessage(messages.Zaps), value: ZAPS }, - Muted: { text: formatMessage(messages.Muted), value: MUTED }, - Blocked: { text: formatMessage(messages.BlockedCount, { n: blocked.length }), value: BLOCKED }, - Relays: { text: formatMessage(messages.Relays), value: RELAYS }, - Bookmarks: { text: formatMessage(messages.Bookmarks), value: BOOKMARKS }, - }; + Notes: { + text: ( + <> + + + + ), + value: NOTES, + }, + Reactions: { + text: ( + <> + + + + ), + value: REACTIONS, + }, + Followers: { + text: ( + <> + + + + ), + value: FOLLOWERS, + }, + Follows: { + text: ( + <> + + + + ), + value: FOLLOWS, + }, + Zaps: { + text: ( + <> + + + + ), + value: ZAPS, + }, + Muted: { + text: ( + <> + + + + ), + value: MUTED, + }, + Blocked: { + text: ( + <> + + + + ), + value: BLOCKED, + }, + Relays: { + text: ( + <> + + + + ), + value: RELAYS, + }, + Bookmarks: { + text: ( + <> + + + + ), + value: BOOKMARKS, + }, + } as { [key: string]: Tab }; const [tab, setTab] = useState(ProfileTab.Notes); const optionalTabs = [ProfileTab.Zaps, ProfileTab.Relays, ProfileTab.Bookmarks, ProfileTab.Muted].filter(a => unwrap(a) @@ -179,34 +250,48 @@ export default function ProfilePage() { function username() { return ( -
-

- {user?.display_name || user?.name || "Nostrich"} - -

- {user?.nip05 && } + <> +
+

+ {user?.display_name || user?.name || "Nostrich"} + +

+ {user?.nip05 && } +
- - {links()} -
+
+ + {links()} +
+ ); } + function tryFormatWebsite(url: string) { + try { + const u = new URL(url); + return `${u.hostname}${u.pathname !== "/" ? u.pathname : ""}`; + } catch { + // ignore + } + return url; + } + function links() { return ( -
+ <> {user?.website && ( -
- + )} {lnurl && ( -
setShowLnQr(true)}> - +
setShowLnQr(true)}> + {lnurl.name}
)} @@ -218,14 +303,14 @@ export default function ProfilePage() { author={id} target={user?.display_name || user?.name} /> -
+ ); } function bio() { return ( aboutText.length > 0 && ( -
+
{about}
) @@ -296,8 +381,12 @@ export default function ProfilePage() { function avatar() { return ( -
+
+
+ {renderIcons()} + {!isMe && id && } +
); } @@ -356,12 +445,8 @@ export default function ProfilePage() { function userDetails() { if (!id) return; return ( -
+
{username()} -
- {renderIcons()} - {!isMe && } -
{bio()}
); @@ -374,9 +459,9 @@ export default function ProfilePage() { const w = window.document.querySelector(".page")?.clientWidth; return ( <> -
+
{user?.banner && } -
+
{avatar()} {userDetails()}
diff --git a/packages/app/src/Pages/Root.tsx b/packages/app/src/Pages/Root.tsx index 351d58d4..b2b0f3f7 100644 --- a/packages/app/src/Pages/Root.tsx +++ b/packages/app/src/Pages/Root.tsx @@ -31,15 +31,6 @@ export default function RootPage() { const { publicKey: pubKey, tags, preferences } = useLogin(); const [rootType, setRootType] = useState("following"); - useEffect(() => { - if (location.pathname === "/") { - const t = pubKey ? preferences.defaultRootTab ?? "/notes" : "/global"; - navigate(t, { - replace: true, - }); - } - }, [location]); - const menuItems = [ { tab: "following", @@ -107,6 +98,18 @@ export default function RootPage() { element: ReactNode; }>; + useEffect(() => { + if (location.pathname === "/") { + const t = pubKey ? preferences.defaultRootTab ?? "/notes" : "/global"; + navigate(t); + } else { + const currentTab = menuItems.find(a => a.path === location.pathname)?.tab; + if (currentTab) { + setRootType(currentTab); + } + } + }, [location]); + function currentMenuItem() { if (location.pathname.startsWith("/t/")) { return ( @@ -139,8 +142,7 @@ export default function RootPage() { {menuItems.map(a => ( { - setRootType(a.tab); - navigate(a.path, { replace: true }); + navigate(a.path); }}> {a.element} @@ -148,8 +150,7 @@ export default function RootPage() { {tags.item.map(v => ( { - setRootType("tags"); - navigate(`/t/${v}`, { replace: true }); + navigate(`/t/${v}`); }}> {v} diff --git a/packages/app/src/index.css b/packages/app/src/index.css index 9c7f8acd..edcb2a1d 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -9,7 +9,7 @@ --font-size-tiny: 11px; --modal-bg-color: rgba(0, 0, 0, 0.8); --note-bg: #0c0c0c; - --highlight: #8b5cf6; + --highlight: #ac88ff; --error: #ff6053; --success: #2ad544; --warning: #ff8800; @@ -23,6 +23,7 @@ --gray-tertiary: #444; --gray-dark: #2b2b2b; --gray-superdark: #1a1a1a; + --gray-ultradark: #111; --gray-gradient: linear-gradient(to bottom right, var(--gray-superlight), var(--gray), var(--gray-light)); --snort-gradient: linear-gradient(90deg, #a178ff 0%, #ff6baf 100%); --dm-gradient: linear-gradient(90deg, #5722d2 0%, #db1771 100%); @@ -123,10 +124,17 @@ body #root > div:not(.page) header { } .card { - padding: 16px; + padding: 12px 16px; border-bottom: 1px solid var(--gray-superdark); } +/* Card inside card */ +.card .card { + border: 1px solid var(--gray-superdark); + border-radius: 16px; + min-height: 0; +} + .card .header { display: flex; flex-direction: row; @@ -407,6 +415,11 @@ input:disabled { a { color: inherit; line-height: 1.3em; + text-decoration: none; +} + +a:hover { + text-decoration: underline; } a.ext { diff --git a/packages/app/src/lang.json b/packages/app/src/lang.json index 0c10ed17..df2b1a70 100644 --- a/packages/app/src/lang.json +++ b/packages/app/src/lang.json @@ -17,6 +17,9 @@ "+vVZ/G": { "defaultMessage": "Connect" }, + "+xliwN": { + "defaultMessage": "{name} reposted" + }, "/4tOwT": { "defaultMessage": "Skip" }, @@ -36,6 +39,9 @@ "/n5KSF": { "defaultMessage": "{n} ms" }, + "00LcfG": { + "defaultMessage": "Load more" + }, "08zn6O": { "defaultMessage": "Export Keys" }, @@ -438,6 +444,9 @@ "IEwZvs": { "defaultMessage": "Are you sure you want to unpin this note?" }, + "IKKHqV": { + "defaultMessage": "Follows" + }, "INSqIz": { "defaultMessage": "Twitter username..." }, diff --git a/packages/app/src/translations/en.json b/packages/app/src/translations/en.json index 744496ad..136df205 100644 --- a/packages/app/src/translations/en.json +++ b/packages/app/src/translations/en.json @@ -5,12 +5,14 @@ "+vA//S": "Logins", "+vIQlC": "Please make sure to save the following password in order to manage your handle in the future", "+vVZ/G": "Connect", + "+xliwN": "{name} reposted", "/4tOwT": "Skip", "/JE/X+": "Account Support", "/PCavi": "Public", "/RD0e2": "Nostr uses digital signature technology to provide tamper proof notes which can safely be replicated to many relays to provide redundant storage of your content.", "/d6vEc": "Make your profile easier to find and share", "/n5KSF": "{n} ms", + "00LcfG": "Load more", "08zn6O": "Export Keys", "0Azlrb": "Manage", "0BUTMv": "Search...", @@ -87,7 +89,6 @@ "B6+XJy": "zapped", "B6H7eJ": "nsec, npub, nip-05, hex", "BGCM48": "Write access to Snort relay, with 1 year of event retention", - "BGxpTN": "Stream Chat", "BOUMjw": "No nostr users found for {twitterUsername}", "BOr9z/": "Snort is an open source project built by passionate people in their free time", "BWpuKl": "Update", @@ -144,6 +145,7 @@ "HbefNb": "Open Wallet", "IDjHJ6": "Thanks for using Snort, please consider donating if you can.", "IEwZvs": "Are you sure you want to unpin this note?", + "IKKHqV": "Follows", "INSqIz": "Twitter username...", "IUZC+0": "This means that nobody can modify notes which you have created and everybody can easily verify that the notes they are reading are created by you.", "Ig9/a1": "Sent {n} sats to {name}", @@ -268,7 +270,6 @@ "c+oiJe": "Install Extension", "c35bj2": "If you have an enquiry about your NIP-05 order please DM {link}", "c3g2hL": "Broadcast Again", - "cE4Hfw": "Discover", "cFbU1B": "Using Alby? Go to {link} to get your NWC config!", "cPIKU2": "Following", "cQfLWb": "URL..", @@ -285,7 +286,6 @@ "eHAneD": "Reaction emoji", "eJj8HD": "Get Verified", "eSzf2G": "A single zap of {nIn} sats will allocate {nOut} sats to the zap pool.", - "fBI91o": "Zap", "fOksnD": "Can't vote because LNURL service does not support zaps", "fWZYP5": "Pinned", "filwqD": "Read", @@ -366,7 +366,6 @@ "pzTOmv": "Followers", "qD9EUF": "Email <> DM bridge for your Snort nostr address", "qDwvZ4": "Unknown error", - "qInqHy": "Message...", "qMx1sA": "Default Zap amount", "qUJTsT": "Blocked", "qdGuQo": "Your Private Key Is (do not share this with anyone)", @@ -426,4 +425,4 @@ "zjJZBd": "You're ready!", "zonsdq": "Failed to load LNURL service", "zvCDao": "Automatically show latest notes" -} \ No newline at end of file +} diff --git a/packages/nostr/src/client/conn.ts b/packages/nostr/src/client/conn.ts index 1abeb803..7f3f557d 100644 --- a/packages/nostr/src/client/conn.ts +++ b/packages/nostr/src/client/conn.ts @@ -226,12 +226,12 @@ function parseIncomingMessage(data: string): IncomingMessage { if (json[0] === "EVENT") { if (typeof json[1] !== "string") { throw new NostrError( - `second element of "EVENT" should be a string, but wasn't: ${data}` + `second element of "EVENT" should be a string, but wasn't: ${data}`, ) } if (typeof json[2] !== "object") { throw new NostrError( - `second element of "EVENT" should be an object, but wasn't: ${data}` + `second element of "EVENT" should be an object, but wasn't: ${data}`, ) } const event = parseEventData(json[2]) @@ -246,7 +246,7 @@ function parseIncomingMessage(data: string): IncomingMessage { if (json[0] === "NOTICE") { if (typeof json[1] !== "string") { throw new NostrError( - `second element of "NOTICE" should be a string, but wasn't: ${data}` + `second element of "NOTICE" should be a string, but wasn't: ${data}`, ) } return { @@ -259,17 +259,17 @@ function parseIncomingMessage(data: string): IncomingMessage { if (json[0] === "OK") { if (typeof json[1] !== "string") { throw new NostrError( - `second element of "OK" should be a string, but wasn't: ${data}` + `second element of "OK" should be a string, but wasn't: ${data}`, ) } if (typeof json[2] !== "boolean") { throw new NostrError( - `third element of "OK" should be a boolean, but wasn't: ${data}` + `third element of "OK" should be a boolean, but wasn't: ${data}`, ) } if (typeof json[3] !== "string") { throw new NostrError( - `fourth element of "OK" should be a string, but wasn't: ${data}` + `fourth element of "OK" should be a string, but wasn't: ${data}`, ) } return { @@ -284,7 +284,7 @@ function parseIncomingMessage(data: string): IncomingMessage { if (json[0] === "EOSE") { if (typeof json[1] !== "string") { throw new NostrError( - `second element of "EOSE" should be a string, but wasn't: ${data}` + `second element of "EOSE" should be a string, but wasn't: ${data}`, ) } return { @@ -312,7 +312,7 @@ function parseEventData(json: { [key: string]: unknown }): RawEvent { typeof json["kind"] !== "number" || !(json["tags"] instanceof Array) || !json["tags"].every( - (x) => x instanceof Array && x.every((y) => typeof y === "string") + (x) => x instanceof Array && x.every((y) => typeof y === "string"), ) || typeof json["content"] !== "string" || typeof json["sig"] !== "string" diff --git a/packages/nostr/src/client/emitter.ts b/packages/nostr/src/client/emitter.ts index 59663f4d..e9f229ef 100644 --- a/packages/nostr/src/client/emitter.ts +++ b/packages/nostr/src/client/emitter.ts @@ -13,7 +13,7 @@ export class EventEmitter extends Base { override addListener(eventName: "newListener", listener: NewListener): this override addListener( eventName: "removeListener", - listener: RemoveListener + listener: RemoveListener, ): this override addListener(eventName: "open", listener: OpenListener): this override addListener(eventName: "close", listener: CloseListener): this @@ -36,7 +36,7 @@ export class EventEmitter extends Base { override emit( eventName: "eose", subscriptionId: SubscriptionId, - nostr: Nostr + nostr: Nostr, ): boolean override emit(eventName: "error", err: unknown, nostr: Nostr): boolean override emit(eventName: EventName, ...args: unknown[]): boolean { @@ -101,11 +101,11 @@ export class EventEmitter extends Base { override prependListener( eventName: "newListener", - listener: NewListener + listener: NewListener, ): this override prependListener( eventName: "removeListener", - listener: RemoveListener + listener: RemoveListener, ): this override prependListener(eventName: "open", listener: OpenListener): this override prependListener(eventName: "close", listener: CloseListener): this @@ -120,30 +120,30 @@ export class EventEmitter extends Base { override prependOnceListener( eventName: "newListener", - listener: NewListener + listener: NewListener, ): this override prependOnceListener( eventName: "removeListener", - listener: RemoveListener + listener: RemoveListener, ): this override prependOnceListener(eventName: "open", listener: OpenListener): this override prependOnceListener( eventName: "close", - listener: CloseListener + listener: CloseListener, ): this override prependOnceListener( eventName: "event", - listener: EventListener + listener: EventListener, ): this override prependOnceListener( eventName: "notice", - listener: NoticeListener + listener: NoticeListener, ): this override prependOnceListener(eventName: "ok", listener: OkListener): this override prependOnceListener(eventName: "eose", listener: EoseListener): this override prependOnceListener( eventName: "error", - listener: ErrorListener + listener: ErrorListener, ): this override prependOnceListener(eventName: EventName, listener: Listener): this { return super.prependOnceListener(eventName, listener) @@ -156,7 +156,7 @@ export class EventEmitter extends Base { override removeListener(eventName: "newListener", listener: NewListener): this override removeListener( eventName: "removeListener", - listener: RemoveListener + listener: RemoveListener, ): this override removeListener(eventName: "open", listener: OpenListener): this override removeListener(eventName: "close", listener: CloseListener): this diff --git a/packages/nostr/src/client/index.ts b/packages/nostr/src/client/index.ts index 4799ac17..3ffc3865 100644 --- a/packages/nostr/src/client/index.ts +++ b/packages/nostr/src/client/index.ts @@ -43,18 +43,18 @@ export class Nostr extends EventEmitter { */ open( url: URL | string, - opts?: { read?: boolean; write?: boolean; fetchInfo?: boolean } + opts?: { read?: boolean; write?: boolean; fetchInfo?: boolean }, ): void { const relayUrl = new URL(url) // If the connection already exists, update the options. const existingConn = this.#conns.find( - (c) => c.relay.url.toString() === relayUrl.toString() + (c) => c.relay.url.toString() === relayUrl.toString(), ) if (existingConn !== undefined) { if (opts === undefined) { throw new NostrError( - `called connect with existing connection ${url}, but options were not specified` + `called connect with existing connection ${url}, but options were not specified`, ) } if (opts.read !== undefined) { @@ -88,7 +88,7 @@ export class Nostr extends EventEmitter { event: parseEvent(msg.event), subscriptionId: msg.subscriptionId, }, - this + this, ) } else if (msg.kind === "notice") { this.emit("notice", msg.notice, this) @@ -101,7 +101,7 @@ export class Nostr extends EventEmitter { ok: msg.ok, message: msg.message, }, - this + this, ) } else if (msg.kind === "eose") { this.emit("eose", msg.subscriptionId, this) @@ -116,13 +116,13 @@ export class Nostr extends EventEmitter { onOpen: async () => { // Update the connection readyState. const conn = this.#conns.find( - (c) => c.relay.url.toString() === relayUrl.toString() + (c) => c.relay.url.toString() === relayUrl.toString(), ) if (conn === undefined) { this.#error( new NostrError( - `bug: expected connection to ${relayUrl.toString()} to be in the map` - ) + `bug: expected connection to ${relayUrl.toString()} to be in the map`, + ), ) } else { if (conn.relay.readyState !== ReadyState.CONNECTING) { @@ -130,8 +130,8 @@ export class Nostr extends EventEmitter { new NostrError( `bug: expected connection to ${relayUrl.toString()} to have readyState CONNECTING, got ${ conn.relay.readyState - }` - ) + }`, + ), ) } conn.relay = { @@ -148,13 +148,13 @@ export class Nostr extends EventEmitter { onClose: () => { // Update the connection readyState. const conn = this.#conns.find( - (c) => c.relay.url.toString() === relayUrl.toString() + (c) => c.relay.url.toString() === relayUrl.toString(), ) if (conn === undefined) { this.#error( new NostrError( - `bug: expected connection to ${relayUrl.toString()} to be in the map` - ) + `bug: expected connection to ${relayUrl.toString()} to be in the map`, + ), ) } else { conn.relay.readyState = ReadyState.CLOSED @@ -207,7 +207,7 @@ export class Nostr extends EventEmitter { } const relayUrl = new URL(url) const c = this.#conns.find( - (c) => c.relay.url.toString() === relayUrl.toString() + (c) => c.relay.url.toString() === relayUrl.toString(), ) if (c === undefined) { throw new NostrError(`connection to ${url} doesn't exist`) @@ -231,7 +231,7 @@ export class Nostr extends EventEmitter { */ subscribe( filters: Filters[], - subscriptionId: SubscriptionId = randomSubscriptionId() + subscriptionId: SubscriptionId = randomSubscriptionId(), ): SubscriptionId { this.#subscriptions.set(subscriptionId, filters) for (const { conn, read } of this.#conns.values()) { diff --git a/packages/nostr/src/client/relay.ts b/packages/nostr/src/client/relay.ts index ac8f9137..fc02d6d5 100644 --- a/packages/nostr/src/client/relay.ts +++ b/packages/nostr/src/client/relay.ts @@ -75,32 +75,32 @@ export async function fetchRelayInfo(url: URL | string): Promise { info.name = undefined throw new NostrError( `invalid relay info, expected "name" to be a string: ${JSON.stringify( - info - )}` + info, + )}`, ) } if (info.description !== undefined && typeof info.description !== "string") { info.description = undefined throw new NostrError( `invalid relay info, expected "description" to be a string: ${JSON.stringify( - info - )}` + info, + )}`, ) } if (info.pubkey !== undefined && typeof info.pubkey !== "string") { info.pubkey = undefined throw new NostrError( `invalid relay info, expected "pubkey" to be a string: ${JSON.stringify( - info - )}` + info, + )}`, ) } if (info.contact !== undefined && typeof info.contact !== "string") { info.contact = undefined throw new NostrError( `invalid relay info, expected "contact" to be a string: ${JSON.stringify( - info - )}` + info, + )}`, ) } if (info.supported_nips !== undefined) { @@ -109,16 +109,16 @@ export async function fetchRelayInfo(url: URL | string): Promise { info.supported_nips = undefined throw new NostrError( `invalid relay info, expected "supported_nips" elements to be numbers: ${JSON.stringify( - info - )}` + info, + )}`, ) } } else { info.supported_nips = undefined throw new NostrError( `invalid relay info, expected "supported_nips" to be an array: ${JSON.stringify( - info - )}` + info, + )}`, ) } } @@ -126,16 +126,16 @@ export async function fetchRelayInfo(url: URL | string): Promise { info.software = undefined throw new NostrError( `invalid relay info, expected "software" to be a string: ${JSON.stringify( - info - )}` + info, + )}`, ) } if (info.version !== undefined && typeof info.version !== "string") { info.version = undefined throw new NostrError( `invalid relay info, expected "version" to be a string: ${JSON.stringify( - info - )}` + info, + )}`, ) } return info diff --git a/packages/nostr/src/crypto.ts b/packages/nostr/src/crypto.ts index 0a1edfae..4b382d6c 100644 --- a/packages/nostr/src/crypto.ts +++ b/packages/nostr/src/crypto.ts @@ -99,7 +99,7 @@ export function schnorrVerify(sig: Hex, data: Hex, key: PublicKey): boolean { export async function aesEncryptBase64( sender: PrivateKey, recipient: PublicKey, - plaintext: string + plaintext: string, ): Promise { const sharedPoint = secp.secp256k1.getSharedSecret(sender, "02" + recipient) const sharedKey = sharedPoint.slice(1, 33) @@ -109,7 +109,7 @@ export async function aesEncryptBase64( sharedKey, { name: "AES-CBC" }, false, - ["encrypt"] + ["encrypt"], ) const iv = window.crypto.getRandomValues(new Uint8Array(16)) const data = new TextEncoder().encode(plaintext) @@ -119,7 +119,7 @@ export async function aesEncryptBase64( iv, }, key, - data + data, ) return { data: base64.fromByteArray(new Uint8Array(encrypted)), @@ -131,7 +131,7 @@ export async function aesEncryptBase64( const cipher = crypto.createCipheriv( "aes-256-cbc", Buffer.from(sharedKey), - iv + iv, ) let encrypted = cipher.update(plaintext, "utf8", "base64") encrypted += cipher.final("base64") @@ -145,7 +145,7 @@ export async function aesEncryptBase64( export async function aesDecryptBase64( sender: PublicKey, recipient: PrivateKey, - { data, iv }: AesEncryptedBase64 + { data, iv }: AesEncryptedBase64, ): Promise { const sharedPoint = secp.secp256k1.getSharedSecret(recipient, "02" + sender) const sharedKey = sharedPoint.slice(1, 33) @@ -157,7 +157,7 @@ export async function aesDecryptBase64( sharedKey, { name: "AES-CBC" }, false, - ["decrypt"] + ["decrypt"], ) const plaintext = await window.crypto.subtle.decrypt( { @@ -165,7 +165,7 @@ export async function aesDecryptBase64( iv: decodedIv, }, importedKey, - decodedData + decodedData, ) return new TextDecoder().decode(plaintext) } else { @@ -173,7 +173,7 @@ export async function aesDecryptBase64( const decipher = crypto.createDecipheriv( "aes-256-cbc", Buffer.from(sharedKey), - base64.toByteArray(iv) + base64.toByteArray(iv), ) const plaintext = decipher.update(data, "base64", "utf8") return plaintext + decipher.final() diff --git a/packages/nostr/src/event/contact-list.ts b/packages/nostr/src/event/contact-list.ts index f3b31998..eb1d7db0 100644 --- a/packages/nostr/src/event/contact-list.ts +++ b/packages/nostr/src/event/contact-list.ts @@ -30,7 +30,7 @@ export interface Contact { */ export function createContactList( contacts: Contact[], - priv?: HexOrBechPrivateKey + priv?: HexOrBechPrivateKey, ): Promise { return signEvent( { @@ -44,7 +44,7 @@ export function createContactList( content: "", getContacts, }, - priv + priv, ) } @@ -57,8 +57,8 @@ export function getContacts(this: ContactList): Contact[] { if (pubkey === undefined) { throw new NostrError( `missing contact pubkey for contact list event: ${JSON.stringify( - this - )}` + this, + )}`, ) } @@ -70,7 +70,7 @@ export function getContacts(this: ContactList): Contact[] { } } catch (e) { throw new NostrError( - `invalid relay URL for contact list event: ${JSON.stringify(this)}` + `invalid relay URL for contact list event: ${JSON.stringify(this)}`, ) } diff --git a/packages/nostr/src/event/deletion.ts b/packages/nostr/src/event/deletion.ts index 1fa8cca7..f65462a9 100644 --- a/packages/nostr/src/event/deletion.ts +++ b/packages/nostr/src/event/deletion.ts @@ -21,7 +21,7 @@ export interface Deletion extends RawEvent { */ export function createDeletion( { events, content }: { events: EventId[]; content?: string }, - priv?: HexOrBechPrivateKey + priv?: HexOrBechPrivateKey, ): Promise { return signEvent( { @@ -30,7 +30,7 @@ export function createDeletion( content: content ?? "", getEvents, }, - priv + priv, ) } @@ -40,7 +40,7 @@ export function getEvents(this: Deletion): EventId[] { .map((tag) => { if (tag[1] === undefined) { throw new NostrError( - `invalid deletion event tag: ${JSON.stringify(tag)}` + `invalid deletion event tag: ${JSON.stringify(tag)}`, ) } return tag[1] diff --git a/packages/nostr/src/event/direct-message.ts b/packages/nostr/src/event/direct-message.ts index e08df8c6..391a3091 100644 --- a/packages/nostr/src/event/direct-message.ts +++ b/packages/nostr/src/event/direct-message.ts @@ -46,7 +46,7 @@ export async function createDirectMessage( message: string recipient: PublicKey }, - priv?: PrivateKey + priv?: PrivateKey, ): Promise { recipient = parsePublicKey(recipient) if (priv === undefined) { @@ -66,7 +66,7 @@ export async function createDirectMessage( getRecipient, getPrevious, }, - priv + priv, ) } else { priv = parsePrivateKey(priv) @@ -80,14 +80,14 @@ export async function createDirectMessage( getRecipient, getPrevious, }, - priv + priv, ) } } export async function getMessage( this: DirectMessage, - priv?: HexOrBechPrivateKey + priv?: HexOrBechPrivateKey, ): Promise { if (priv !== undefined) { priv = parsePrivateKey(priv) @@ -114,9 +114,9 @@ export function getRecipient(this: DirectMessage): PublicKey { const recipientTag = this.tags.find((tag) => tag[0] === "p") if (typeof recipientTag?.[1] !== "string") { throw new NostrError( - `expected "p" tag to be of type string, but got ${ - recipientTag?.[1] - } in ${JSON.stringify(this)}` + `expected "p" tag to be of type string, but got ${recipientTag?.[1]} in ${JSON.stringify( + this, + )}`, ) } return recipientTag[1] @@ -129,9 +129,9 @@ export function getPrevious(this: DirectMessage): EventId | undefined { } if (typeof previousTag[1] !== "string") { throw new NostrError( - `expected "e" tag to be of type string, but got ${ - previousTag?.[1] - } in ${JSON.stringify(this)}` + `expected "e" tag to be of type string, but got ${previousTag?.[1]} in ${JSON.stringify( + this, + )}`, ) } return previousTag[1] diff --git a/packages/nostr/src/event/index.ts b/packages/nostr/src/event/index.ts index 6bfc3240..62710ca7 100644 --- a/packages/nostr/src/event/index.ts +++ b/packages/nostr/src/event/index.ts @@ -122,7 +122,7 @@ type UnsignedWithPubkey = { */ export async function signEvent( event: Unsigned, - priv?: HexOrBechPrivateKey + priv?: HexOrBechPrivateKey, ): Promise { event.created_at ??= unixTimestamp() if (priv !== undefined) { @@ -130,7 +130,7 @@ export async function signEvent( event.pubkey = getPublicKey(priv) const id = serializeEventId( // This conversion is safe because the pubkey field is set above. - event as unknown as UnsignedWithPubkey + event as unknown as UnsignedWithPubkey, ) event.id = id event.sig = schnorrSign(id, priv) @@ -162,8 +162,8 @@ export function parseEvent(event: RawEvent): Event { if (event.id !== serializeEventId(event)) { throw new NostrError( `invalid id ${event.id} for event ${JSON.stringify( - event - )}, expected ${serializeEventId(event)}` + event, + )}, expected ${serializeEventId(event)}`, ) } if (!schnorrVerify(event.sig, event.id, event.pubkey)) { diff --git a/packages/nostr/src/event/set-metadata.ts b/packages/nostr/src/event/set-metadata.ts index 25fb257d..849a4b00 100644 --- a/packages/nostr/src/event/set-metadata.ts +++ b/packages/nostr/src/event/set-metadata.ts @@ -22,7 +22,7 @@ export interface SetMetadata extends RawEvent { * @return The internet identifier. `undefined` if there is no internet identifier. */ verifyInternetIdentifier( - opts?: VerificationOptions + opts?: VerificationOptions, ): Promise } @@ -38,7 +38,7 @@ export interface UserMetadata { */ export function createSetMetadata( content: UserMetadata, - priv?: HexOrBechPrivateKey + priv?: HexOrBechPrivateKey, ): Promise { return signEvent( { @@ -48,7 +48,7 @@ export function createSetMetadata( getUserMetadata, verifyInternetIdentifier, }, - priv + priv, ) } @@ -60,7 +60,7 @@ export function getUserMetadata(this: SetMetadata): UserMetadata { typeof userMetadata.picture !== "string" ) { throw new NostrError( - `invalid user metadata ${userMetadata} in ${JSON.stringify(this)}` + `invalid user metadata ${userMetadata} in ${JSON.stringify(this)}`, ) } return userMetadata @@ -68,7 +68,7 @@ export function getUserMetadata(this: SetMetadata): UserMetadata { export async function verifyInternetIdentifier( this: SetMetadata, - opts?: VerificationOptions + opts?: VerificationOptions, ): Promise { const metadata = this.getUserMetadata() if (metadata.nip05 === undefined) { @@ -81,14 +81,14 @@ export async function verifyInternetIdentifier( !/^[a-zA-Z0-9-_]+$/.test(name) ) { throw new NostrError( - `invalid NIP-05 internet identifier: ${metadata.nip05}` + `invalid NIP-05 internet identifier: ${metadata.nip05}`, ) } const res = await fetch( `${ opts?.https === false ? "http" : "https" }://${domain}/.well-known/nostr.json?name=${name}`, - { redirect: "error" } + { redirect: "error" }, ) const wellKnown = await res.json() const pubkey = wellKnown.names?.[name] @@ -96,7 +96,7 @@ export async function verifyInternetIdentifier( throw new NostrError( `invalid NIP-05 internet identifier: ${ metadata.nip05 - } pubkey does not match, ${JSON.stringify(wellKnown)}` + } pubkey does not match, ${JSON.stringify(wellKnown)}`, ) } const relays = wellKnown.relays?.[pubkey] diff --git a/packages/nostr/src/event/text.ts b/packages/nostr/src/event/text.ts index bbe2175a..0f3b109d 100644 --- a/packages/nostr/src/event/text.ts +++ b/packages/nostr/src/event/text.ts @@ -12,7 +12,7 @@ export interface TextNote extends RawEvent { export function createTextNote( content: string, - priv?: HexOrBechPrivateKey + priv?: HexOrBechPrivateKey, ): Promise { return signEvent( { @@ -20,6 +20,6 @@ export function createTextNote( tags: [], content, }, - priv + priv, ) } diff --git a/packages/nostr/test/browser/index.html b/packages/nostr/test/browser/index.html index 2a2700c1..713ece01 100644 --- a/packages/nostr/test/browser/index.html +++ b/packages/nostr/test/browser/index.html @@ -1,4 +1,4 @@ - + diff --git a/packages/nostr/test/browser/server.ts b/packages/nostr/test/browser/server.ts index a0bfdab6..e0bda810 100644 --- a/packages/nostr/test/browser/server.ts +++ b/packages/nostr/test/browser/server.ts @@ -18,7 +18,7 @@ app.use("/", (req: express.Request, res: express.Response) => { .readdirSync(path.join(__dirname, "..", "..", "dist", "test")) .filter( (f) => - f.startsWith("test.") && !f.endsWith(".map") && !f.endsWith(".d.ts") + f.startsWith("test.") && !f.endsWith(".map") && !f.endsWith(".d.ts"), ) .map((src) => ``) .join("\n") diff --git a/packages/nostr/test/setup.ts b/packages/nostr/test/setup.ts index 2a079c77..4301badb 100644 --- a/packages/nostr/test/setup.ts +++ b/packages/nostr/test/setup.ts @@ -30,7 +30,7 @@ export interface Setup { export async function setup( done: (e?: unknown) => void, - test: (setup: Setup) => void | Promise + test: (setup: Setup) => void | Promise, ) { try { await restartRelay() @@ -55,7 +55,7 @@ export async function setup( const { data, iv } = await aesEncryptBase64( parsePrivateKey(publisherSecret), pubkey, - plaintext + plaintext, ) return `${data}?iv=${iv}` }, @@ -67,7 +67,7 @@ export async function setup( { data, iv, - } + }, ) }, }, diff --git a/packages/nostr/test/test.deletion.ts b/packages/nostr/test/test.deletion.ts index 490accb7..a7459581 100644 --- a/packages/nostr/test/test.deletion.ts +++ b/packages/nostr/test/test.deletion.ts @@ -52,7 +52,7 @@ describe("deletion", () => { // After the text note has been published, delete it. const deletion = await createDeletion( { events: [textNoteId] }, - publisherSecret + publisherSecret, ) deletionId = deletion.id publisher.publish({ @@ -66,7 +66,7 @@ describe("deletion", () => { subscriber.subscribe([]) } }) - } + }, ) }) }) diff --git a/packages/nostr/test/test.direct-message.ts b/packages/nostr/test/test.direct-message.ts index de1c85ad..bee6038d 100644 --- a/packages/nostr/test/test.direct-message.ts +++ b/packages/nostr/test/test.direct-message.ts @@ -34,16 +34,16 @@ describe("direct-message", () => { if (event.kind === EventKind.DirectMessage) { assert.strictEqual( event.getRecipient(), - parsePublicKey(subscriberPubkey) + parsePublicKey(subscriberPubkey), ) assert.strictEqual( await event.getMessage(subscriberSecret), - message + message, ) } done() - } + }, ) const subscriptionId = subscriber.subscribe([]) @@ -54,11 +54,11 @@ describe("direct-message", () => { message, recipient: subscriberPubkey, }, - publisherSecret + publisherSecret, ) publisher.publish(event) }) - } + }, ) }) @@ -92,11 +92,11 @@ describe("direct-message", () => { if (event.kind === EventKind.DirectMessage) { assert.strictEqual( event.getRecipient(), - parsePublicKey(recipientPubkey) + parsePublicKey(recipientPubkey), ) assert.strictEqual( await event.getMessage(subscriberSecret), - undefined + undefined, ) } @@ -104,7 +104,7 @@ describe("direct-message", () => { } catch (e) { done(e) } - } + }, ) const subscriptionId = subscriber.subscribe([]) @@ -116,11 +116,11 @@ describe("direct-message", () => { message, recipient: recipientPubkey, }, - publisherSecret + publisherSecret, ) publisher.publish(event) }) - } + }, ) }) }) diff --git a/packages/nostr/test/test.internet-identifier.ts b/packages/nostr/test/test.internet-identifier.ts index 39c718f9..b880ae66 100644 --- a/packages/nostr/test/test.internet-identifier.ts +++ b/packages/nostr/test/test.internet-identifier.ts @@ -34,7 +34,7 @@ describe("internet-identifier", () => { picture: "", nip05: "bob@localhost:12647", }, - publisherSecret + publisherSecret, )), }) }) @@ -66,7 +66,7 @@ describe("internet-identifier", () => { name: "", picture: "", }, - publisherSecret + publisherSecret, )), }) }) diff --git a/packages/nostr/test/test.relay-info.ts b/packages/nostr/test/test.relay-info.ts index dd37e5eb..20fb0d5d 100644 --- a/packages/nostr/test/test.relay-info.ts +++ b/packages/nostr/test/test.relay-info.ts @@ -16,7 +16,7 @@ describe("relay info", () => { assert.ok((relay.info.supported_nips?.length ?? 0) > 0) assert.strictEqual( relay.info.software, - "https://git.sr.ht/~gheartsfield/nostr-rs-relay" + "https://git.sr.ht/~gheartsfield/nostr-rs-relay", ) assert.strictEqual(relay.info.version, "0.8.8") } diff --git a/packages/nostr/test/test.set-metadata.ts b/packages/nostr/test/test.set-metadata.ts index a4ba8e4f..a8b7a767 100644 --- a/packages/nostr/test/test.set-metadata.ts +++ b/packages/nostr/test/test.set-metadata.ts @@ -43,12 +43,12 @@ describe("set metadata", () => { publisher.publish({ ...(await createSetMetadata( { name, about, picture }, - publisherSecret + publisherSecret, )), created_at: timestamp, }) }) - } + }, ) }) }) diff --git a/packages/nostr/test/test.text-note.ts b/packages/nostr/test/test.text-note.ts index c1a72e56..e3a4b526 100644 --- a/packages/nostr/test/test.text-note.ts +++ b/packages/nostr/test/test.text-note.ts @@ -30,7 +30,7 @@ describe("text note", () => { assert.strictEqual(event.content, note) assert.strictEqual(actualSubscriptionId, subscriptionId) done() - } + }, ) const subscriptionId = subscriber.subscribe([]) @@ -45,7 +45,7 @@ describe("text note", () => { created_at: timestamp, }) }) - } + }, ) }) diff --git a/packages/shared/src/feed-cache.ts b/packages/shared/src/feed-cache.ts index bfa4a033..c4be82e7 100644 --- a/packages/shared/src/feed-cache.ts +++ b/packages/shared/src/feed-cache.ts @@ -32,7 +32,7 @@ export abstract class FeedCache { this.cache.size, this.onTable.size, this.#hooks.length, - ((this.#hits / (this.#hits + this.#miss)) * 100).toFixed(1) + ((this.#hits / (this.#hits + this.#miss)) * 100).toFixed(1), ); }, 30_000); } @@ -174,7 +174,7 @@ export abstract class FeedCache { `Loaded %d/%d in %d ms`, fromCacheFiltered.length, keys.length, - (unixNowMs() - start).toLocaleString() + (unixNowMs() - start).toLocaleString(), ); return mapped.filter(a => !a.has).map(a => a.key); } diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index d32a683a..099a0fd4 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -73,7 +73,7 @@ export function countMembers(a: any) { export function equalProp( a: string | number | Array | undefined, - b: string | number | Array | undefined + b: string | number | Array | undefined, ) { if ((a !== undefined && b === undefined) || (a === undefined && b !== undefined)) { return false; diff --git a/packages/system-react/src/useRequestBuilder.tsx b/packages/system-react/src/useRequestBuilder.tsx index e63995bd..e5c09385 100644 --- a/packages/system-react/src/useRequestBuilder.tsx +++ b/packages/system-react/src/useRequestBuilder.tsx @@ -8,7 +8,7 @@ import { unwrap } from "@snort/shared"; const useRequestBuilder = >( system: SystemInterface, type: { new (): TStore }, - rb: RequestBuilder | null + rb: RequestBuilder | null, ) => { const subscribe = (onChanged: () => void) => { if (rb) { @@ -33,7 +33,7 @@ const useRequestBuilder = >( v => subscribe(v), - () => getState() + () => getState(), ); }; diff --git a/packages/system-react/src/useSystemState.tsx b/packages/system-react/src/useSystemState.tsx index ceece800..95cb9108 100644 --- a/packages/system-react/src/useSystemState.tsx +++ b/packages/system-react/src/useSystemState.tsx @@ -5,6 +5,6 @@ import { ExternalStore } from "@snort/shared"; export function useSystemState(system: ExternalStore) { return useSyncExternalStore( cb => system.hook(cb), - () => system.snapshot() + () => system.snapshot(), ); } diff --git a/packages/system-react/src/useUserProfile.ts b/packages/system-react/src/useUserProfile.ts index 95a2667a..5e748639 100644 --- a/packages/system-react/src/useUserProfile.ts +++ b/packages/system-react/src/useUserProfile.ts @@ -18,6 +18,6 @@ export function useUserProfile(system: NostrSystem, pubKey?: HexKey): MetadataCa } }; }, - () => system.ProfileLoader.Cache.getFromCache(pubKey) + () => system.ProfileLoader.Cache.getFromCache(pubKey), ); } diff --git a/packages/system/src/cache/user-metadata.ts b/packages/system/src/cache/user-metadata.ts index 51fcbd78..5693a54b 100644 --- a/packages/system/src/cache/user-metadata.ts +++ b/packages/system/src/cache/user-metadata.ts @@ -96,7 +96,7 @@ export class UserProfileCache extends FeedCache { }); } }, - 5 + 5, ); setTimeout(() => this.#processZapperQueue(), 1_000); @@ -116,7 +116,7 @@ export class UserProfileCache extends FeedCache { }); } }, - 5 + 5, ); setTimeout(() => this.#processNip5Queue(), 1_000); @@ -135,7 +135,7 @@ export class UserProfileCache extends FeedCache { console.warn("Failed to process item", i); } batch.pop(); // pop any - })() + })(), ); if (batch.length === batchSize) { await Promise.all(batch); diff --git a/packages/system/src/connection.ts b/packages/system/src/connection.ts index cd614f19..1650a46b 100644 --- a/packages/system/src/connection.ts +++ b/packages/system/src/connection.ts @@ -153,7 +153,7 @@ export class Connection extends ExternalStore { this.#log( `[${this.Address}] Closed (code=${e.code}), trying again in ${(this.ConnectTimeout / 1000) .toFixed(0) - .toLocaleString()} sec` + .toLocaleString()} sec`, ); this.ReconnectTimer = setTimeout(() => { this.Connect(); @@ -425,7 +425,7 @@ export class Connection extends ExternalStore { "%s Inactive connection has %d active requests! %O", this.Address, this.ActiveRequests.size, - this.ActiveRequests + this.ActiveRequests, ); } else { this.Close(); diff --git a/packages/system/src/const.ts b/packages/system/src/const.ts index 37a93a0f..af06cced 100644 --- a/packages/system/src/const.ts +++ b/packages/system/src/const.ts @@ -33,4 +33,4 @@ export const CashuRegex = /(cashuA[A-Za-z0-9_-]{0,10000}={0,3})/i; /** * Regex to match any npub/nevent/naddr/nprofile/note */ -export const MentionNostrEntityRegex = /@n(pub|profile|event|ote|addr|)1[acdefghjklmnpqrstuvwxyz023456789]+/g; \ No newline at end of file +export const MentionNostrEntityRegex = /@n(pub|profile|event|ote|addr|)1[acdefghjklmnpqrstuvwxyz023456789]+/g; diff --git a/packages/system/src/event-builder.ts b/packages/system/src/event-builder.ts index f7f9300a..c9beb2b3 100644 --- a/packages/system/src/event-builder.ts +++ b/packages/system/src/event-builder.ts @@ -43,9 +43,7 @@ export class EventBuilder { */ processContent() { if (this.#content) { - this.#content = this.#content.replace(MentionNostrEntityRegex, m => - this.#replaceMention(m) - ); + this.#content = this.#content.replace(MentionNostrEntityRegex, m => this.#replaceMention(m)); const hashTags = [...this.#content.matchAll(HashtagRegex)]; hashTags.map(hashTag => { diff --git a/packages/system/src/event-publisher.ts b/packages/system/src/event-publisher.ts index 60186736..0a652e10 100644 --- a/packages/system/src/event-publisher.ts +++ b/packages/system/src/event-publisher.ts @@ -146,7 +146,7 @@ export class EventPublisher { relays: Array, note?: HexKey, msg?: string, - fnExtra?: EventBuilderHook + fnExtra?: EventBuilderHook, ) { const eb = this.#eb(EventKind.ZapRequest); eb.content(msg ?? ""); diff --git a/packages/system/src/gossip-model.ts b/packages/system/src/gossip-model.ts index ea8243e6..d931d6b6 100644 --- a/packages/system/src/gossip-model.ts +++ b/packages/system/src/gossip-model.ts @@ -175,6 +175,6 @@ function pickTopRelays(cache: RelayCache, authors: Array, n: number) { key: a.key, relays: [], }; - }) + }), ); } diff --git a/packages/system/src/impl/nip4.ts b/packages/system/src/impl/nip4.ts index 016de9e0..c775dca5 100644 --- a/packages/system/src/impl/nip4.ts +++ b/packages/system/src/impl/nip4.ts @@ -1,6 +1,4 @@ import { MessageEncryptor, MessageEncryptorPayload, MessageEncryptorVersion } from "index"; - -import { base64 } from "@scure/base"; import { secp256k1 } from "@noble/curves/secp256k1"; export class Nip4WebCryptoEncryptor implements MessageEncryptor { @@ -20,7 +18,7 @@ export class Nip4WebCryptoEncryptor implements MessageEncryptor { iv: iv, }, key, - data + data, ); return { ciphertext: new Uint8Array(result), diff --git a/packages/system/src/impl/nip46.ts b/packages/system/src/impl/nip46.ts index c2b3e276..df531089 100644 --- a/packages/system/src/impl/nip46.ts +++ b/packages/system/src/impl/nip46.ts @@ -94,7 +94,7 @@ export class Nip46Signer implements EventSigner { "#p": [this.#localPubkey], }, ], - () => {} + () => {}, ); if (isBunker) { @@ -181,7 +181,7 @@ export class Nip46Signer implements EventSigner { result: "ack", error: "", }, - unwrap(this.#remotePubkey) + unwrap(this.#remotePubkey), ); id = "connect"; } diff --git a/packages/system/src/impl/nip7.ts b/packages/system/src/impl/nip7.ts index 74ec83ae..c472edbf 100644 --- a/packages/system/src/impl/nip7.ts +++ b/packages/system/src/impl/nip7.ts @@ -37,7 +37,7 @@ export class Nip7Signer implements EventSigner { throw new Error("Cannot use NIP-07 signer, not found!"); } return await barrierQueue(Nip7Queue, () => - unwrap(window.nostr?.nip04?.encrypt).call(window.nostr?.nip04, key, content) + unwrap(window.nostr?.nip04?.encrypt).call(window.nostr?.nip04, key, content), ); } @@ -46,7 +46,7 @@ export class Nip7Signer implements EventSigner { throw new Error("Cannot use NIP-07 signer, not found!"); } return await barrierQueue(Nip7Queue, () => - unwrap(window.nostr?.nip04?.decrypt).call(window.nostr?.nip04, otherKey, content) + unwrap(window.nostr?.nip04?.decrypt).call(window.nostr?.nip04, otherKey, content), ); } diff --git a/packages/system/src/profile-cache.ts b/packages/system/src/profile-cache.ts index e453d866..899bd73c 100644 --- a/packages/system/src/profile-cache.ts +++ b/packages/system/src/profile-cache.ts @@ -128,7 +128,7 @@ export class ProfileLoaderService { pubkey: a, loaded: unixNowMs() - ProfileCacheExpire + 30_000, // expire in 30s created: 69, - } as MetadataCache) + } as MetadataCache), ); await Promise.all(empty); } diff --git a/packages/system/src/query.ts b/packages/system/src/query.ts index d7661a12..8fc51634 100644 --- a/packages/system/src/query.ts +++ b/packages/system/src/query.ts @@ -27,7 +27,7 @@ class QueryTrace { readonly filters: Array, readonly connId: string, fnClose: (id: string) => void, - fnProgress: () => void + fnProgress: () => void, ) { this.id = uuid(); this.start = unixNowMs(); @@ -293,7 +293,7 @@ export class Query implements QueryBase { q.filters, c.Id, x => c.CloseReq(x), - () => this.#onProgress() + () => this.#onProgress(), ); this.#tracing.push(qt); c.QueueReq(["REQ", qt.id, ...qt.filters], () => qt.sentToRelay()); diff --git a/packages/system/src/text.ts b/packages/system/src/text.ts index b20a2e7a..ea273285 100644 --- a/packages/system/src/text.ts +++ b/packages/system/src/text.ts @@ -5,195 +5,198 @@ import { validateNostrLink } from "./nostr-link"; import { splitByUrl } from "./utils"; export interface ParsedFragment { - type: "text" | "link" | "mention" | "invoice" | "media" | "cashu" | "hashtag" | "custom_emoji" - content: string - mimeType?: string + type: "text" | "link" | "mention" | "invoice" | "media" | "cashu" | "hashtag" | "custom_emoji"; + content: string; + mimeType?: string; } export type Fragment = string | ParsedFragment; function extractLinks(fragments: Fragment[]) { - return fragments - .map(f => { - if (typeof f === "string") { - return splitByUrl(f).map(a => { - const validateLink = () => { - const normalizedStr = a.toLowerCase(); + return fragments + .map(f => { + if (typeof f === "string") { + return splitByUrl(f).map(a => { + const validateLink = () => { + const normalizedStr = a.toLowerCase(); - if (normalizedStr.startsWith("web+nostr:") || normalizedStr.startsWith("nostr:")) { - return validateNostrLink(normalizedStr); - } - - return ( - normalizedStr.startsWith("http:") || - normalizedStr.startsWith("https:") || - normalizedStr.startsWith("magnet:") - ); - }; - - if (validateLink()) { - const url = new URL(a); - const extension = url.pathname.match(FileExtensionRegex); - - if (extension && extension.length > 1) { - const mediaType = (() => { - switch (extension[1]) { - case "gif": - case "jpg": - case "jpeg": - case "jfif": - case "png": - case "bmp": - case "webp": - return "image"; - case "wav": - case "mp3": - case "ogg": - return "audio"; - case "mp4": - case "mov": - case "mkv": - case "avi": - case "m4v": - case "webm": - case "m3u8": - return "video"; - default: - return "unknown"; - } - })(); - return { - type: "media", - content: a, - mimeType: `${mediaType}/${extension[1]}` - } as ParsedFragment; - } else { - return { - type: "link", - content: a - } as ParsedFragment; - } - } - return a; - }); + if (normalizedStr.startsWith("web+nostr:") || normalizedStr.startsWith("nostr:")) { + return validateNostrLink(normalizedStr); } - return f; - }) - .flat(); + + return ( + normalizedStr.startsWith("http:") || + normalizedStr.startsWith("https:") || + normalizedStr.startsWith("magnet:") + ); + }; + + if (validateLink()) { + const url = new URL(a); + const extension = url.pathname.match(FileExtensionRegex); + + if (extension && extension.length > 1) { + const mediaType = (() => { + switch (extension[1]) { + case "gif": + case "jpg": + case "jpeg": + case "jfif": + case "png": + case "bmp": + case "webp": + return "image"; + case "wav": + case "mp3": + case "ogg": + return "audio"; + case "mp4": + case "mov": + case "mkv": + case "avi": + case "m4v": + case "webm": + case "m3u8": + return "video"; + default: + return "unknown"; + } + })(); + return { + type: "media", + content: a, + mimeType: `${mediaType}/${extension[1]}`, + } as ParsedFragment; + } else { + return { + type: "link", + content: a, + } as ParsedFragment; + } + } + return a; + }); + } + return f; + }) + .flat(); } function extractMentions(fragments: Fragment[]) { - return fragments - .map(f => { - if (typeof f === "string") { - return f.split(MentionNostrEntityRegex).map(i => { - if (MentionNostrEntityRegex.test(i)) { - return { - type: "mention", - content: i - } as ParsedFragment; - } else { - return i; - } - }); - } - return f; - }) - .flat(); + return fragments + .map(f => { + if (typeof f === "string") { + return f.split(MentionNostrEntityRegex).map(i => { + if (MentionNostrEntityRegex.test(i)) { + return { + type: "mention", + content: i, + } as ParsedFragment; + } else { + return i; + } + }); + } + return f; + }) + .flat(); } function extractCashuTokens(fragments: Fragment[]) { - return fragments - .map(f => { - if (typeof f === "string" && f.includes("cashuA")) { - return f.split(CashuRegex).map(a => { - return { - type: "cashu", - content: a - } as ParsedFragment - }); - } - return f; - }) - .flat(); + return fragments + .map(f => { + if (typeof f === "string" && f.includes("cashuA")) { + return f.split(CashuRegex).map(a => { + return { + type: "cashu", + content: a, + } as ParsedFragment; + }); + } + return f; + }) + .flat(); } function extractInvoices(fragments: Fragment[]) { - return fragments - .map(f => { - if (typeof f === "string") { - return f.split(InvoiceRegex).map(i => { - if (i.toLowerCase().startsWith("lnbc")) { - return { - type: "invoice", - content: i - } as ParsedFragment - } else { - return i; - } - }); - } - return f; - }) - .flat(); + return fragments + .map(f => { + if (typeof f === "string") { + return f.split(InvoiceRegex).map(i => { + if (i.toLowerCase().startsWith("lnbc")) { + return { + type: "invoice", + content: i, + } as ParsedFragment; + } else { + return i; + } + }); + } + return f; + }) + .flat(); } function extractHashtags(fragments: Fragment[]) { - return fragments - .map(f => { - if (typeof f === "string") { - return f.split(HashtagRegex).map(i => { - if (i.toLowerCase().startsWith("#")) { - return { - type: "hashtag", - content: i.substring(1) - } as ParsedFragment; - } else { - return i; - } - }); - } - return f; - }) - .flat(); + return fragments + .map(f => { + if (typeof f === "string") { + return f.split(HashtagRegex).map(i => { + if (i.toLowerCase().startsWith("#")) { + return { + type: "hashtag", + content: i.substring(1), + } as ParsedFragment; + } else { + return i; + } + }); + } + return f; + }) + .flat(); } function extractCustomEmoji(fragments: Fragment[], tags: Array>) { - return fragments - .map(f => { - if (typeof f === "string") { - return f.split(/:(\w+):/g).map(i => { - const t = tags.find(a => a[0] === "emoji" && a[1] === i); - if (t) { - return { - type: "custom_emoji", - content: t[2] - } as ParsedFragment - } else { - return i; - } - }); - } - return f; - }) - .flat(); + return fragments + .map(f => { + if (typeof f === "string") { + return f.split(/:(\w+):/g).map(i => { + const t = tags.find(a => a[0] === "emoji" && a[1] === i); + if (t) { + return { + type: "custom_emoji", + content: t[2], + } as ParsedFragment; + } else { + return i; + } + }); + } + return f; + }) + .flat(); } export function transformText(body: string, tags: Array>) { - let fragments = extractLinks([body]); - fragments = extractMentions(fragments); - fragments = extractHashtags(fragments); - fragments = extractInvoices(fragments); - fragments = extractCashuTokens(fragments); - fragments = extractCustomEmoji(fragments, tags); - fragments = fragments.map(a => { - if (typeof a === "string") { - if (a.length > 0) { - return { type: "text", content: a } as ParsedFragment; - } - } else { - return a; + let fragments = extractLinks([body]); + fragments = extractMentions(fragments); + fragments = extractHashtags(fragments); + fragments = extractInvoices(fragments); + fragments = extractCashuTokens(fragments); + fragments = extractCustomEmoji(fragments, tags); + fragments = fragments + .map(a => { + if (typeof a === "string") { + if (a.length > 0) { + return { type: "text", content: a } as ParsedFragment; } - }).filter(a => a).map(a => unwrap(a)); - return fragments as Array; -} \ No newline at end of file + } else { + return a; + } + }) + .filter(a => a) + .map(a => unwrap(a)); + return fragments as Array; +} diff --git a/packages/system/src/utils.ts b/packages/system/src/utils.ts index 165cb6d3..3553ece2 100644 --- a/packages/system/src/utils.ts +++ b/packages/system/src/utils.ts @@ -27,24 +27,26 @@ export function reqFilterEq(a: FlatReqFilter | ReqFilter, b: FlatReqFilter | Req } export function flatFilterEq(a: FlatReqFilter, b: FlatReqFilter): boolean { - return a.keys === b.keys - && a.since === b.since - && a.until === b.until - && a.limit === b.limit - && a.search === b.search - && a.ids === b.ids - && a.kinds === b.kinds - && a.authors === b.authors - && a["#e"] === b["#e"] - && a["#p"] === b["#p"] - && a["#t"] === b["#t"] - && a["#d"] === b["#d"] - && a["#r"] === b["#r"]; + return ( + a.keys === b.keys && + a.since === b.since && + a.until === b.until && + a.limit === b.limit && + a.search === b.search && + a.ids === b.ids && + a.kinds === b.kinds && + a.authors === b.authors && + a["#e"] === b["#e"] && + a["#p"] === b["#p"] && + a["#t"] === b["#t"] && + a["#d"] === b["#d"] && + a["#r"] === b["#r"] + ); } export function splitByUrl(str: string) { - const urlRegex = - /((?:http|ftp|https|nostr|web\+nostr|magnet):\/?\/?(?:[\w+?.\w+])+(?:[a-zA-Z0-9~!@#$%^&*()_\-=+\\/?.:;',]*)?(?:[-A-Za-z0-9+&@#/%=~()_|]))/i; - - return str.split(urlRegex); - } + const urlRegex = + /((?:http|ftp|https|nostr|web\+nostr|magnet):\/?\/?(?:[\w+?.\w+])+(?:[a-zA-Z0-9~!@#$%^&*()_\-=+\\/?.:;',]*)?(?:[-A-Za-z0-9+&@#/%=~()_|]))/i; + + return str.split(urlRegex); +} diff --git a/packages/system/tests/request-builder.test.ts b/packages/system/tests/request-builder.test.ts index adc93561..c57a403f 100644 --- a/packages/system/tests/request-builder.test.ts +++ b/packages/system/tests/request-builder.test.ts @@ -193,7 +193,7 @@ describe("build diff, large follow list", () => { }, ], }; - }) + }), ); expect(unixNowMs() - start).toBeLessThan(500); diff --git a/packages/system/tests/utils.test.ts b/packages/system/tests/utils.test.ts index b7248ed4..dc4aac99 100644 --- a/packages/system/tests/utils.test.ts +++ b/packages/system/tests/utils.test.ts @@ -17,8 +17,8 @@ describe("tryParseNostrLink", () => { }); expect( parseNostrLink( - "nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p" - ) + "nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p", + ), ).toMatchObject({ id: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", type: NostrPrefix.Profile, @@ -30,8 +30,8 @@ describe("tryParseNostrLink", () => { }); expect( parseNostrLink( - "nostr:naddr1qqzkjurnw4ksz9thwden5te0wfjkccte9ehx7um5wghx7un8qgs2d90kkcq3nk2jry62dyf50k0h36rhpdtd594my40w9pkal876jxgrqsqqqa28pccpzu" - ) + "nostr:naddr1qqzkjurnw4ksz9thwden5te0wfjkccte9ehx7um5wghx7un8qgs2d90kkcq3nk2jry62dyf50k0h36rhpdtd594my40w9pkal876jxgrqsqqqa28pccpzu", + ), ).toMatchObject({ id: "ipsum", type: NostrPrefix.Address, diff --git a/yarn.lock b/yarn.lock index 8b8eca2c..9e247888 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12617,6 +12617,15 @@ __metadata: languageName: node linkType: hard +"prettier@npm:^3.0.0": + version: 3.0.0 + resolution: "prettier@npm:3.0.0" + bin: + prettier: bin/prettier.cjs + checksum: 6a832876a1552dc58330d2467874e5a0b46b9ccbfc5d3531eb69d15684743e7f83dc9fbd202db6270446deba9c82b79d24383d09924c462b457136a759425e33 + languageName: node + linkType: hard + "pretty-bytes@npm:^5.3.0, pretty-bytes@npm:^5.4.1": version: 5.6.0 resolution: "pretty-bytes@npm:5.6.0" @@ -13411,6 +13420,7 @@ __metadata: "@cloudflare/workers-types": ^4.20230307.0 "@tauri-apps/cli": ^1.2.3 eslint: ^8.44.0 + prettier: ^3.0.0 typescript: ^5.1.6 languageName: unknown linkType: soft