diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..5cda1d84 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +node_modules/ +.github/ +.vscode/ +build/ +yarn-error.log \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json index 0967ef42..e1280035 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1 +1,5 @@ -{} +{ + "printWidth": 120, + "bracketSameLine": true, + "arrowParens": "avoid" +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..e69de29b diff --git a/d.ts b/d.ts index 2bf20721..8efeccf6 100644 --- a/d.ts +++ b/d.ts @@ -12,3 +12,17 @@ declare module "*.webp" { const value: string; export default value; } + +declare module "light-bolt11-decoder" { + export function decode(pr?: string): ParsedInvoice; + + export interface ParsedInvoice { + paymentRequest: string; + sections: Section[]; + } + + export interface Section { + name: string; + value: string | Uint8Array | number | undefined; + } +} diff --git a/public/index.html b/public/index.html index e8779ebf..1cbe83b2 100644 --- a/public/index.html +++ b/public/index.html @@ -3,16 +3,12 @@ - + + content="default-src 'self'; child-src 'none'; worker-src 'self'; frame-src youtube.com www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com https://www.mixcloud.com https://open.spotify.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 https://embed.tidal.com;" /> diff --git a/src/Const.ts b/src/Const.ts index ac6c8290..a037de0d 100644 --- a/src/Const.ts +++ b/src/Const.ts @@ -18,14 +18,12 @@ export const VoidCatHost = "https://void.cat"; /** * Kierans pubkey */ -export const KieranPubKey = - "npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49"; +export const KieranPubKey = "npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49"; /** * Official snort account */ -export const SnortPubKey = - "npub1sn0rtcjcf543gj4wsg7fa59s700d5ztys5ctj0g69g2x6802npjqhjjtws"; +export const SnortPubKey = "npub1sn0rtcjcf543gj4wsg7fa59s700d5ztys5ctj0g69g2x6802npjqhjjtws"; /** * Websocket re-connect timeout @@ -49,9 +47,7 @@ export const DefaultRelays = new Map([ /** * Default search relays */ -export const SearchRelays = new Map([ - ["wss://relay.nostr.band", { read: true, write: false }], -]); +export const SearchRelays = new Map([["wss://relay.nostr.band", { read: true, write: false }]]); /** * List of recommended follows for new users @@ -118,8 +114,7 @@ export const YoutubeUrlRegex = /** * Tweet Regex */ -export const TweetUrlRegex = - /https?:\/\/twitter\.com\/(?:#!\/)?(\w+)\/status(?:es)?\/(\d+)/; +export const TweetUrlRegex = /https?:\/\/twitter\.com\/(?:#!\/)?(\w+)\/status(?:es)?\/(\d+)/; /** * Hashtag regex @@ -135,15 +130,12 @@ export const TidalRegex = /tidal\.com\/(?:browse\/)?(\w+)\/([a-z0-9-]+)/i; /** * SoundCloud regex */ -export const SoundCloudRegex = - /soundcloud\.com\/(?!live)([a-zA-Z0-9]+)\/([a-zA-Z0-9-]+)/; +export const SoundCloudRegex = /soundcloud\.com\/(?!live)([a-zA-Z0-9]+)\/([a-zA-Z0-9-]+)/; /** * Mixcloud regex */ -export const MixCloudRegex = - /mixcloud\.com\/(?!live)([a-zA-Z0-9]+)\/([a-zA-Z0-9-]+)/; +export const MixCloudRegex = /mixcloud\.com\/(?!live)([a-zA-Z0-9]+)\/([a-zA-Z0-9-]+)/; -export const SpotifyRegex = - /open\.spotify\.com\/(track|album|playlist|episode)\/([a-zA-Z0-9]+)/; +export const SpotifyRegex = /open\.spotify\.com\/(track|album|playlist|episode)\/([a-zA-Z0-9]+)/; diff --git a/src/Db/index.ts b/src/Db/index.ts index 59cf5fdf..89276478 100644 --- a/src/Db/index.ts +++ b/src/Db/index.ts @@ -28,11 +28,11 @@ export class SnortDB extends Dexie { super(NAME); this.version(VERSION) .stores(STORES) - .upgrade(async (tx) => { + .upgrade(async tx => { await tx .table("users") .toCollection() - .modify((user) => { + .modify(user => { user.npub = hexToBech32("npub", user.pubkey); }); }); diff --git a/src/Element/AsyncButton.tsx b/src/Element/AsyncButton.tsx index 068c40c3..59bdaf86 100644 --- a/src/Element/AsyncButton.tsx +++ b/src/Element/AsyncButton.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; -interface AsyncButtonProps - extends React.ButtonHTMLAttributes { +interface AsyncButtonProps extends React.ButtonHTMLAttributes { onClick(e: React.MouseEvent): Promise | void; children?: React.ReactNode; } diff --git a/src/Element/Avatar.tsx b/src/Element/Avatar.tsx index a4f7b905..97f5a9ea 100644 --- a/src/Element/Avatar.tsx +++ b/src/Element/Avatar.tsx @@ -4,20 +4,14 @@ import { CSSProperties, useEffect, useState } from "react"; import type { UserMetadata } from "Nostr"; import useImgProxy from "Feed/ImgProxy"; -const Avatar = ({ - user, - ...rest -}: { - user?: UserMetadata; - onClick?: () => void; -}) => { +const Avatar = ({ user, ...rest }: { user?: UserMetadata; onClick?: () => void }) => { const [url, setUrl] = useState(Nostrich); const { proxy } = useImgProxy(); useEffect(() => { if (user?.picture) { proxy(user.picture, 120) - .then((a) => setUrl(a)) + .then(a => setUrl(a)) .catch(console.warn); } }, [user]); @@ -25,14 +19,7 @@ const Avatar = ({ const backgroundImage = `url(${url})`; const style = { "--img-url": backgroundImage } as CSSProperties; const domain = user?.nip05 && user.nip05.split("@")[1]; - return ( -
- ); + return
; }; export default Avatar; diff --git a/src/Element/BlockList.tsx b/src/Element/BlockList.tsx index e6ba6c83..33e07e61 100644 --- a/src/Element/BlockList.tsx +++ b/src/Element/BlockList.tsx @@ -18,39 +18,21 @@ export default function BlockList({ variant }: BlockListProps) { {variant === "muted" && ( <>

- +

- {muted.map((a) => { - return ( - } - pubkey={a} - options={{ about: false }} - key={a} - /> - ); + {muted.map(a => { + return } pubkey={a} options={{ about: false }} key={a} />; })} )} {variant === "blocked" && ( <>

- +

- {blocked.map((a) => { + {blocked.map(a => { return ( - } - pubkey={a} - options={{ about: false }} - key={a} - /> + } pubkey={a} options={{ about: false }} key={a} /> ); })} diff --git a/src/Element/Collapsed.tsx b/src/Element/Collapsed.tsx index b1f282e6..8a69e989 100644 --- a/src/Element/Collapsed.tsx +++ b/src/Element/Collapsed.tsx @@ -9,12 +9,7 @@ interface CollapsedProps { setCollapsed(b: boolean): void; } -const Collapsed = ({ - text, - children, - collapsed, - setCollapsed, -}: CollapsedProps) => { +const Collapsed = ({ text, children, collapsed, setCollapsed }: CollapsedProps) => { return collapsed ? (
setCollapsed(false)} /> diff --git a/src/Element/Copy.tsx b/src/Element/Copy.tsx index 10ea2311..cc3f6765 100644 --- a/src/Element/Copy.tsx +++ b/src/Element/Copy.tsx @@ -10,23 +10,13 @@ export interface CopyProps { export default function Copy({ text, maxSize = 32 }: CopyProps) { const { copy, copied } = useCopy(); const sliceLength = maxSize / 2; - const trimmed = - text.length > maxSize - ? `${text.slice(0, sliceLength)}...${text.slice(-sliceLength)}` - : text; + const trimmed = text.length > maxSize ? `${text.slice(0, sliceLength)}...${text.slice(-sliceLength)}` : text; return (
copy(text)}> {trimmed} - - {copied ? ( - - ) : ( - - )} + + {copied ? : }
); diff --git a/src/Element/DM.tsx b/src/Element/DM.tsx index 0c0d871c..fdf59a4c 100644 --- a/src/Element/DM.tsx +++ b/src/Element/DM.tsx @@ -22,18 +22,14 @@ export type DMProps = { export default function DM(props: DMProps) { const dispatch = useDispatch(); - const pubKey = useSelector( - (s) => s.login.publicKey - ); + const pubKey = useSelector(s => s.login.publicKey); const publisher = useEventPublisher(); const [content, setContent] = useState("Loading..."); const [decrypted, setDecrypted] = useState(false); const { ref, inView } = useInView(); const { formatMessage } = useIntl(); const isMe = props.data.pubkey === pubKey; - const otherPubkey = isMe - ? pubKey - : unwrap(props.data.tags.find((a) => a[0] === "p")?.[1]); + const otherPubkey = isMe ? pubKey : unwrap(props.data.tags.find(a => a[0] === "p")?.[1]); async function decrypt() { const e = new Event(props.data); @@ -55,18 +51,10 @@ export default function DM(props: DMProps) { return (
- +
- +
); diff --git a/src/Element/FollowButton.tsx b/src/Element/FollowButton.tsx index da7b03a5..050234ad 100644 --- a/src/Element/FollowButton.tsx +++ b/src/Element/FollowButton.tsx @@ -15,9 +15,7 @@ export interface FollowButtonProps { export default function FollowButton(props: FollowButtonProps) { const pubkey = parseId(props.pubkey); const publiser = useEventPublisher(); - const isFollowing = useSelector( - (s) => s.login.follows?.includes(pubkey) ?? false - ); + const isFollowing = useSelector(s => s.login.follows?.includes(pubkey) ?? false); const baseClassname = `${props.className} follow-button`; async function follow(pubkey: HexKey) { @@ -34,13 +32,8 @@ export default function FollowButton(props: FollowButtonProps) { ); } diff --git a/src/Element/FollowListBase.tsx b/src/Element/FollowListBase.tsx index d421d1d6..50931357 100644 --- a/src/Element/FollowListBase.tsx +++ b/src/Element/FollowListBase.tsx @@ -10,10 +10,7 @@ export interface FollowListBaseProps { pubkeys: HexKey[]; title?: string; } -export default function FollowListBase({ - pubkeys, - title, -}: FollowListBaseProps) { +export default function FollowListBase({ pubkeys, title }: FollowListBaseProps) { const publisher = useEventPublisher(); async function followAll() { @@ -25,15 +22,11 @@ export default function FollowListBase({
{title}
-
- {pubkeys?.map((a) => ( + {pubkeys?.map(a => ( ))}
diff --git a/src/Element/FollowersList.tsx b/src/Element/FollowersList.tsx index 7fb169eb..9e1eaaa7 100644 --- a/src/Element/FollowersList.tsx +++ b/src/Element/FollowersList.tsx @@ -18,17 +18,10 @@ export default function FollowersList({ pubkey }: FollowersListProps) { const pubkeys = useMemo(() => { const contactLists = feed?.store.notes.filter( - (a) => - a.kind === EventKind.ContactList && - a.tags.some((b) => b[0] === "p" && b[1] === pubkey) + a => a.kind === EventKind.ContactList && a.tags.some(b => b[0] === "p" && b[1] === pubkey) ); - return [...new Set(contactLists?.map((a) => a.pubkey))]; + return [...new Set(contactLists?.map(a => a.pubkey))]; }, [feed, pubkey]); - return ( - - ); + return ; } diff --git a/src/Element/FollowsList.tsx b/src/Element/FollowsList.tsx index a6557d97..42931845 100644 --- a/src/Element/FollowsList.tsx +++ b/src/Element/FollowsList.tsx @@ -20,10 +20,5 @@ export default function FollowsList({ pubkey }: FollowsListProps) { return getFollowers(feed.store, pubkey); }, [feed, pubkey]); - return ( - - ); + return ; } diff --git a/src/Element/FollowsYou.tsx b/src/Element/FollowsYou.tsx index cc49dd48..581dbda5 100644 --- a/src/Element/FollowsYou.tsx +++ b/src/Element/FollowsYou.tsx @@ -17,9 +17,7 @@ export interface FollowsYouProps { export default function FollowsYou({ pubkey }: FollowsYouProps) { const { formatMessage } = useIntl(); const feed = useFollowsFeed(pubkey); - const loginPubKey = useSelector( - (s) => s.login.publicKey - ); + const loginPubKey = useSelector(s => s.login.publicKey); const pubkeys = useMemo(() => { return getFollowers(feed.store, pubkey); @@ -27,7 +25,5 @@ export default function FollowsYou({ pubkey }: FollowsYouProps) { const followsMe = loginPubKey ? pubkeys.includes(loginPubKey) : false; - return followsMe ? ( - {formatMessage(messages.FollowsYou)} - ) : null; + return followsMe ? {formatMessage(messages.FollowsYou)} : null; } diff --git a/src/Element/Hashtag.tsx b/src/Element/Hashtag.tsx index f6046425..0bc5a2ad 100644 --- a/src/Element/Hashtag.tsx +++ b/src/Element/Hashtag.tsx @@ -4,7 +4,7 @@ import "./Hashtag.css"; const Hashtag = ({ tag }: { tag: string }) => { return ( - e.stopPropagation()}> + e.stopPropagation()}> #{tag} diff --git a/src/Element/HyperText.tsx b/src/Element/HyperText.tsx index 2d4f1b05..1f096e44 100644 --- a/src/Element/HyperText.tsx +++ b/src/Element/HyperText.tsx @@ -19,30 +19,17 @@ import TidalEmbed from "Element/TidalEmbed"; import { ProxyImg } from "Element/ProxyImg"; import { HexKey } from "Nostr"; -export default function HyperText({ - link, - creator, -}: { - link: string; - creator: HexKey; -}) { +export default function HyperText({ link, creator }: { link: string; creator: HexKey }) { const pref = useSelector((s: RootState) => s.login.preferences); const follows = useSelector((s: RootState) => s.login.follows); const render = useCallback(() => { const a = link; try { - const hideNonFollows = - pref.autoLoadMedia === "follows-only" && !follows.includes(creator); + const hideNonFollows = pref.autoLoadMedia === "follows-only" && !follows.includes(creator); if (pref.autoLoadMedia === "none" || hideNonFollows) { return ( - e.stopPropagation()} - target="_blank" - rel="noreferrer" - className="ext" - > + e.stopPropagation()} target="_blank" rel="noreferrer" className="ext"> {a} ); @@ -54,8 +41,7 @@ export default function HyperText({ const soundcloundId = SoundCloudRegex.test(a) && RegExp.$1; const mixcloudId = MixCloudRegex.test(a) && RegExp.$1; const spotifyId = SpotifyRegex.test(a); - const extension = - FileExtensionRegex.test(url.pathname.toLowerCase()) && RegExp.$1; + const extension = FileExtensionRegex.test(url.pathname.toLowerCase()) && RegExp.$1; if (extension) { switch (extension) { case "gif": @@ -83,11 +69,10 @@ export default function HyperText({ e.stopPropagation()} + onClick={e => e.stopPropagation()} target="_blank" rel="noreferrer" - className="ext" - > + className="ext"> {url.toString()} ); @@ -124,13 +109,7 @@ export default function HyperText({ return ; } else { return ( - e.stopPropagation()} - target="_blank" - rel="noreferrer" - className="ext" - > + e.stopPropagation()} target="_blank" rel="noreferrer" className="ext"> {a} ); @@ -139,13 +118,7 @@ export default function HyperText({ // Ignore the error. } return ( - e.stopPropagation()} - target="_blank" - rel="noreferrer" - className="ext" - > + e.stopPropagation()} target="_blank" rel="noreferrer" className="ext"> {a} ); diff --git a/src/Element/Invoice.tsx b/src/Element/Invoice.tsx index 56958597..85539bf1 100644 --- a/src/Element/Invoice.tsx +++ b/src/Element/Invoice.tsx @@ -1,7 +1,6 @@ import "./Invoice.css"; import { useState } from "react"; import { useIntl, FormattedMessage } from "react-intl"; -// @ts-expect-error No types available import { decode as invoiceDecode } from "light-bolt11-decoder"; import { useMemo } from "react"; import SendSats from "Element/SendSats"; @@ -14,10 +13,6 @@ export interface InvoiceProps { invoice: string; } -interface Section { - name: string; -} - export default function Invoice(props: InvoiceProps) { const invoice = props.invoice; const webln = useWebln(); @@ -28,22 +23,19 @@ export default function Invoice(props: InvoiceProps) { try { const parsed = invoiceDecode(invoice); - const amount = parseInt( - parsed.sections.find((a: Section) => a.name === "amount")?.value - ); - const timestamp = parseInt( - parsed.sections.find((a: Section) => a.name === "timestamp")?.value - ); - const expire = parseInt( - parsed.sections.find((a: Section) => a.name === "expiry")?.value - ); - const description = parsed.sections.find( - (a: Section) => a.name === "description" - )?.value; + const amountSection = parsed.sections.find(a => a.name === "amount"); + const amount = amountSection ? (amountSection.value as number) : NaN; + + const timestampSection = parsed.sections.find(a => a.name === "timestamp"); + const timestamp = timestampSection ? (timestampSection.value as number) : NaN; + + const expirySection = parsed.sections.find(a => a.name === "expiry"); + const expire = expirySection ? (expirySection.value as number) : NaN; + const descriptionSection = parsed.sections.find(a => a.name === "description")?.value; const ret = { amount: !isNaN(amount) ? amount / 1000 : 0, expire: !isNaN(timestamp) && !isNaN(expire) ? timestamp + expire : null, - description, + description: descriptionSection as string | undefined, expired: false, }; if (ret.expire) { @@ -93,18 +85,13 @@ export default function Invoice(props: InvoiceProps) { return ( <> -
+
{header()}

{amount > 0 && ( <> - {amount.toLocaleString()}{" "} - sat{amount === 1 ? "" : "s"} + {amount.toLocaleString()} sat{amount === 1 ? "" : "s"} )}

@@ -117,11 +104,7 @@ export default function Invoice(props: InvoiceProps) {
) : ( )}
diff --git a/src/Element/LNURLTip.css b/src/Element/LNURLTip.css deleted file mode 100644 index 917f4c2c..00000000 --- a/src/Element/LNURLTip.css +++ /dev/null @@ -1,59 +0,0 @@ -.lnurl-tip { - text-align: center; -} - -.lnurl-tip .btn { - background-color: inherit; - width: 210px; - margin: 0 0 10px 0; -} - -.lnurl-tip .btn:hover { - 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 .invoice { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} - -.lnurl-tip .invoice .actions { - display: flex; - flex-direction: column; - align-items: flex-start; - text-align: center; -} - -.lnurl-tip .invoice .actions .copy-action { - margin: 10px auto; -} - -.lnurl-tip .invoice .actions .pay-actions { - margin: 10px auto; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} diff --git a/src/Element/LNURLTip.tsx b/src/Element/LNURLTip.tsx deleted file mode 100644 index 2ff53b01..00000000 --- a/src/Element/LNURLTip.tsx +++ /dev/null @@ -1,301 +0,0 @@ -import "./LNURLTip.css"; -import { useEffect, useMemo, useState } from "react"; -import { bech32ToText, unwrap } from "Util"; -import { HexKey } from "Nostr"; -import useEventPublisher from "Feed/EventPublisher"; -import Modal from "Element/Modal"; -import QrCode from "Element/QrCode"; -import Copy from "Element/Copy"; -import useWebln from "Hooks/useWebln"; - -interface LNURLService { - nostrPubkey?: HexKey; - minSendable?: number; - maxSendable?: number; - metadata: string; - callback: string; - commentAllowed?: number; -} - -interface LNURLInvoice { - pr: string; - successAction?: LNURLSuccessAction; -} - -interface LNURLSuccessAction { - description?: string; - url?: string; -} - -export interface LNURLTipProps { - onClose?: () => void; - svc?: string; - show?: boolean; - invoice?: string; // shortcut to invoice qr tab - title?: string; - notice?: string; - note?: HexKey; - author?: HexKey; -} - -export default function LNURLTip(props: LNURLTipProps) { - const onClose = props.onClose || (() => undefined); - const service = props.svc; - const show = props.show || false; - const { note, author } = props; - const amounts = [50, 100, 500, 1_000, 5_000, 10_000, 50_000]; - const [payService, setPayService] = useState(); - const [amount, setAmount] = useState(); - const [customAmount, setCustomAmount] = useState(0); - const [invoice, setInvoice] = useState(); - const [comment, setComment] = useState(); - const [error, setError] = useState(); - const [success, setSuccess] = useState(); - const webln = useWebln(show); - const publisher = useEventPublisher(); - - useEffect(() => { - if (show && !props.invoice) { - loadService() - .then((a) => setPayService(unwrap(a))) - .catch(() => setError("Failed to load LNURL service")); - } else { - setPayService(undefined); - setError(undefined); - setInvoice(props.invoice ? { pr: props.invoice } : undefined); - setAmount(undefined); - setComment(undefined); - setSuccess(undefined); - } - }, [show, service]); - - const serviceAmounts = useMemo(() => { - if (payService) { - const min = (payService.minSendable ?? 0) / 1000; - const max = (payService.maxSendable ?? 0) / 1000; - return amounts.filter((a) => a >= min && a <= max); - } - return []; - }, [payService]); - - const metadata = useMemo(() => { - if (payService) { - const meta: string[][] = JSON.parse(payService.metadata); - const desc = meta.find((a) => a[0] === "text/plain"); - const image = meta.find((a) => a[0] === "image/png;base64"); - return { - description: desc ? desc[1] : null, - image: image ? image[1] : null, - }; - } - return null; - }, [payService]); - - const selectAmount = (a: number) => { - setError(undefined); - setInvoice(undefined); - setAmount(a); - }; - - async function fetchJson(url: string) { - const rsp = await fetch(url); - if (rsp.ok) { - const data: T = await rsp.json(); - console.log(data); - setError(undefined); - return data; - } - return null; - } - - async function loadService(): Promise { - if (service) { - const isServiceUrl = service.toLowerCase().startsWith("lnurl"); - if (isServiceUrl) { - const serviceUrl = bech32ToText(service); - return await fetchJson(serviceUrl); - } else { - const ns = service.split("@"); - return await fetchJson(`https://${ns[1]}/.well-known/lnurlp/${ns[0]}`); - } - } - return null; - } - - async function loadInvoice() { - if (!amount || !payService) return null; - let url = ""; - const amountParam = `amount=${Math.floor(amount * 1000)}`; - const commentParam = comment - ? `&comment=${encodeURIComponent(comment)}` - : ""; - if (payService.nostrPubkey && author) { - const ev = await publisher.zap(author, note, comment); - const nostrParam = - ev && `&nostr=${encodeURIComponent(JSON.stringify(ev.ToObject()))}`; - url = `${payService.callback}?${amountParam}${commentParam}${nostrParam}`; - } else { - url = `${payService.callback}?${amountParam}${commentParam}`; - } - try { - const rsp = await fetch(url); - if (rsp.ok) { - const data = await rsp.json(); - console.log(data); - if (data.status === "ERROR") { - setError(data.reason); - } else { - setInvoice(data); - setError(""); - payWebLNIfEnabled(data); - } - } else { - setError("Failed to load invoice"); - } - } catch (e) { - setError("Failed to load invoice"); - } - } - - function custom() { - const min = (payService?.minSendable ?? 0) / 1000; - const max = (payService?.maxSendable ?? 21_000_000_000) / 1000; - return ( -
- setCustomAmount(parseInt(e.target.value))} - /> -
selectAmount(customAmount)}> - Confirm -
-
- ); - } - - async function payWebLNIfEnabled(invoice: LNURLInvoice) { - try { - if (webln?.enabled) { - const res = await webln.sendPayment(invoice.pr); - console.log(res); - setSuccess(invoice.successAction || {}); - } - } catch (e: unknown) { - console.warn(e); - if (e instanceof Error) { - setError(e.toString()); - } - } - } - - function invoiceForm() { - if (invoice) return null; - return ( - <> -
- {metadata?.description ?? service} -
-
- {(payService?.commentAllowed ?? 0) > 0 ? ( - setComment(e.target.value)} - /> - ) : null} -
-
- {serviceAmounts.map((a) => ( - selectAmount(a)} - > - {a.toLocaleString()} - - ))} - {payService ? ( - selectAmount(-1)} - > - Custom - - ) : null} -
- {amount === -1 ? custom() : null} - {(amount ?? 0) > 0 && ( - - )} - - ); - } - - function payInvoice() { - if (success) return null; - const pr = invoice?.pr; - return ( - <> -
- {props.notice && {props.notice}} - -
- {pr && ( - <> -
- -
-
- -
- - )} -
-
- - ); - } - - function successAction() { - if (!success) return null; - return ( - <> -

{success?.description ?? "Paid!"}

- {success.url ? ( - - {success.url} - - ) : null} - - ); - } - - const defaultTitle = payService?.nostrPubkey - ? "⚡️ Send Zap!" - : "⚡️ Send sats"; - if (!show) return null; - return ( - -
e.stopPropagation()}> -

{props.title || defaultTitle}

- {invoiceForm()} - {error ?

{error}

: null} - {payInvoice()} - {successAction()} -
-
- ); -} diff --git a/src/Element/LoadMore.tsx b/src/Element/LoadMore.tsx index 4bbfad79..8982c375 100644 --- a/src/Element/LoadMore.tsx +++ b/src/Element/LoadMore.tsx @@ -24,7 +24,7 @@ export default function LoadMore({ useEffect(() => { const t = setInterval(() => { - setTick((x) => (x += 1)); + setTick(x => (x += 1)); }, 500); return () => clearInterval(t); }, []); diff --git a/src/Element/LogoutButton.tsx b/src/Element/LogoutButton.tsx index f3947f7c..53659aad 100644 --- a/src/Element/LogoutButton.tsx +++ b/src/Element/LogoutButton.tsx @@ -16,8 +16,7 @@ export default function LogoutButton() { onClick={() => { dispatch(logout()); navigate("/"); - }} - > + }}> ); diff --git a/src/Element/Mention.tsx b/src/Element/Mention.tsx index c38140c8..1c03115a 100644 --- a/src/Element/Mention.tsx +++ b/src/Element/Mention.tsx @@ -18,7 +18,7 @@ export default function Mention({ pubkey }: { pubkey: HexKey }) { }, [user, pubkey]); return ( - e.stopPropagation()}> + e.stopPropagation()}> @{name} ); diff --git a/src/Element/MixCloudEmbed.tsx b/src/Element/MixCloudEmbed.tsx index 2c8a4369..c24629a0 100644 --- a/src/Element/MixCloudEmbed.tsx +++ b/src/Element/MixCloudEmbed.tsx @@ -3,14 +3,9 @@ import { useSelector } from "react-redux"; import { RootState } from "State/Store"; const MixCloudEmbed = ({ link }: { link: string }) => { - const feedPath = - (MixCloudRegex.test(link) && RegExp.$1) + - "%2F" + - (MixCloudRegex.test(link) && RegExp.$2); + const feedPath = (MixCloudRegex.test(link) && RegExp.$1) + "%2F" + (MixCloudRegex.test(link) && RegExp.$2); - const lightTheme = useSelector( - (s) => s.login.preferences.theme === "light" - ); + const lightTheme = useSelector(s => s.login.preferences.theme === "light"); const lightParams = lightTheme ? "light=1" : "light=0"; diff --git a/src/Element/Modal.tsx b/src/Element/Modal.tsx index d136c076..15c6b9fd 100644 --- a/src/Element/Modal.tsx +++ b/src/Element/Modal.tsx @@ -8,10 +8,7 @@ export interface ModalProps { children: React.ReactNode; } -function useOnClickOutside( - ref: React.MutableRefObject, - onClickOutside: () => void -) { +function useOnClickOutside(ref: React.MutableRefObject, onClickOutside: () => void) { useEffect(() => { function handleClickOutside(ev: MouseEvent) { if (ref && ref.current && !ref.current.contains(ev.target as Node)) { diff --git a/src/Element/MutedList.tsx b/src/Element/MutedList.tsx index ee59ac05..8a700548 100644 --- a/src/Element/MutedList.tsx +++ b/src/Element/MutedList.tsx @@ -24,29 +24,18 @@ export default function MutedList({ pubkey }: MutedListProps) {
- +
- {pubkeys?.map((a) => { - return ( - } - pubkey={a} - options={{ about: false }} - key={a} - /> - ); + {pubkeys?.map(a => { + return } pubkey={a} options={{ about: false }} key={a} />; })}
); diff --git a/src/Element/Nip05.tsx b/src/Element/Nip05.tsx index fadd5c6f..0feaaf12 100644 --- a/src/Element/Nip05.tsx +++ b/src/Element/Nip05.tsx @@ -1,11 +1,7 @@ import { useQuery } from "react-query"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faCircleCheck, - faSpinner, - faTriangleExclamation, -} from "@fortawesome/free-solid-svg-icons"; +import { faCircleCheck, faSpinner, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; import "./Nip05.css"; import { HexKey } from "Nostr"; @@ -19,13 +15,9 @@ async function fetchNip05Pubkey(name: string, domain: string) { return undefined; } try { - const res = await fetch( - `https://${domain}/.well-known/nostr.json?name=${encodeURIComponent( - name - )}` - ); + const res = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(name)}`); const data: NostrJson = await res.json(); - const match = Object.keys(data.names).find((n) => { + const match = Object.keys(data.names).find(n => { return n.toLowerCase() === name.toLowerCase(); }); return match ? data.names[match] : undefined; @@ -39,16 +31,12 @@ const VERIFICATION_STALE_TIMEOUT = 10 * 60 * 1000; export function useIsVerified(pubkey: HexKey, nip05?: string) { const [name, domain] = nip05 ? nip05.split("@") : []; - const { isError, isSuccess, data } = useQuery( - ["nip05", nip05], - () => fetchNip05Pubkey(name, domain), - { - retry: false, - retryOnMount: false, - cacheTime: VERIFICATION_CACHE_TIME, - staleTime: VERIFICATION_STALE_TIMEOUT, - } - ); + const { isError, isSuccess, data } = useQuery(["nip05", nip05], () => fetchNip05Pubkey(name, domain), { + retry: false, + retryOnMount: false, + cacheTime: VERIFICATION_CACHE_TIME, + staleTime: VERIFICATION_STALE_TIMEOUT, + }); const isVerified = isSuccess && data === pubkey; const cantVerify = isSuccess && data !== pubkey; return { isVerified, couldNotVerify: isError || cantVerify }; @@ -62,42 +50,18 @@ export interface Nip05Params { const Nip05 = (props: Nip05Params) => { const [name, domain] = props.nip05 ? props.nip05.split("@") : []; const isDefaultUser = name === "_"; - const { isVerified, couldNotVerify } = useIsVerified( - props.pubkey, - props.nip05 - ); + const { isVerified, couldNotVerify } = useIsVerified(props.pubkey, props.nip05); return ( -
ev.stopPropagation()} - > +
ev.stopPropagation()}> {!isDefaultUser &&
{`${name}@`}
} {domain} - {isVerified && ( - - )} - {!isVerified && !couldNotVerify && ( - - )} - {couldNotVerify && ( - - )} + {isVerified && } + {!isVerified && !couldNotVerify && } + {couldNotVerify && }
); diff --git a/src/Element/Nip5Service.tsx b/src/Element/Nip5Service.tsx index a41711c6..f5289760 100644 --- a/src/Element/Nip5Service.tsx +++ b/src/Element/Nip5Service.tsx @@ -20,6 +20,7 @@ import { debounce, hexToBech32 } from "Util"; import { UserMetadata } from "Nostr"; import messages from "./messages"; +import { RootState } from "State/Store"; type Nip05ServiceProps = { name: string; @@ -29,47 +30,34 @@ type Nip05ServiceProps = { supportLink: string; }; -interface ReduxStore { - login: { publicKey: string }; -} - export default function Nip5Service(props: Nip05ServiceProps) { const navigate = useNavigate(); const { formatMessage } = useIntl(); - const pubkey = useSelector((s) => s.login.publicKey); + const pubkey = useSelector((s: RootState) => s.login.publicKey); const user = useUserProfile(pubkey); const publisher = useEventPublisher(); - const svc = useMemo( - () => new ServiceProvider(props.service), - [props.service] - ); + const svc = useMemo(() => new ServiceProvider(props.service), [props.service]); const [serviceConfig, setServiceConfig] = useState(); const [error, setError] = useState(); const [handle, setHandle] = useState(""); const [domain, setDomain] = useState(""); - const [availabilityResponse, setAvailabilityResponse] = - useState(); - const [registerResponse, setRegisterResponse] = - useState(); + const [availabilityResponse, setAvailabilityResponse] = useState(); + const [registerResponse, setRegisterResponse] = useState(); const [showInvoice, setShowInvoice] = useState(false); const [registerStatus, setRegisterStatus] = useState(); - const domainConfig = useMemo( - () => serviceConfig?.domains.find((a) => a.name === domain), - [domain, serviceConfig] - ); + const domainConfig = useMemo(() => serviceConfig?.domains.find(a => a.name === domain), [domain, serviceConfig]); useEffect(() => { svc .GetConfig() - .then((a) => { + .then(a => { if ("error" in a) { setError(a as ServiceError); } else { const svc = a as ServiceConfig; setServiceConfig(svc); - const defaultDomain = - svc.domains.find((a) => a.default)?.name || svc.domains[0].name; + const defaultDomain = svc.domains.find(a => a.default)?.name || svc.domains[0].name; setDomain(defaultDomain); } }) @@ -88,10 +76,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { setAvailabilityResponse({ available: false, why: "TOO_LONG" }); return; } - const rx = new RegExp( - domainConfig?.regex[0] ?? "", - domainConfig?.regex[1] ?? "" - ); + const rx = new RegExp(domainConfig?.regex[0] ?? "", domainConfig?.regex[1] ?? ""); if (!rx.test(handle)) { setAvailabilityResponse({ available: false, why: "REGEX" }); return; @@ -99,7 +84,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { return debounce(500, () => { svc .CheckAvailable(handle, domain) - .then((a) => { + .then(a => { if ("error" in a) { setError(a as ServiceError); } else { @@ -133,10 +118,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { } }, [registerResponse, showInvoice, svc]); - function mapError( - e: ServiceErrorCode | undefined, - t: string | null - ): string | undefined { + function mapError(e: ServiceErrorCode | undefined, t: string | null): string | undefined { if (e === undefined) { return undefined; } @@ -152,8 +134,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { } async function startBuy(handle: string, domain: string) { - if (registerResponse) { - setShowInvoice(true); + if (!pubkey) { return; } @@ -202,11 +183,11 @@ export default function Nip5Service(props: Nip05ServiceProps) { type="text" placeholder="Handle" value={handle} - onChange={(e) => setHandle(e.target.value.toLowerCase())} + onChange={e => setHandle(e.target.value.toLowerCase())} />  @  - setDomain(e.target.value)}> + {serviceConfig?.domains.map(a => ( ))} @@ -215,10 +196,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { {availabilityResponse?.available && !registerStatus && (
- +
{availabilityResponse.quote?.data.type}
@@ -238,10 +216,7 @@ export default function Nip5Service(props: Nip05ServiceProps) {
{" "} - {mapError( - availabilityResponse.why, - availabilityResponse.reasonTag || null - )} + {mapError(availabilityResponse.why, availabilityResponse.reasonTag || null)}
)} diff --git a/src/Element/Note.tsx b/src/Element/Note.tsx index 9818dcff..7a02a892 100644 --- a/src/Element/Note.tsx +++ b/src/Element/Note.tsx @@ -1,11 +1,5 @@ import "./Note.css"; -import { - useCallback, - useMemo, - useState, - useLayoutEffect, - ReactNode, -} from "react"; +import { useCallback, useMemo, useState, useLayoutEffect, ReactNode } from "react"; import { useNavigate, Link } from "react-router-dom"; import { useInView } from "react-intersection-observer"; import { useIntl, FormattedMessage } from "react-intl"; @@ -58,21 +52,11 @@ const HiddenNote = ({ children }: { children: React.ReactNode }) => { export default function Note(props: NoteProps) { const navigate = useNavigate(); - const { - data, - related, - highlight, - options: opt, - ["data-ev"]: parsedEvent, - ignoreModeration = false, - } = props; + const { data, related, highlight, options: opt, ["data-ev"]: parsedEvent, ignoreModeration = false } = props; const ev = useMemo(() => parsedEvent ?? new NEvent(data), [data]); const pubKeys = useMemo(() => ev.Thread?.PubKeys || [], [ev]); const users = useUserProfiles(pubKeys); - const deletions = useMemo( - () => getReactions(related, ev.Id, EventKind.Deletion), - [related] - ); + const deletions = useMemo(() => getReactions(related, ev.Id, EventKind.Deletion), [related]); const { isMuted } = useModeration(); const isOpMuted = isMuted(ev.PubKey); const { ref, inView, entry } = useInView({ triggerOnce: true }); @@ -99,14 +83,7 @@ export default function Note(props: NoteProps) { ); } - return ( - - ); + return ; }, [ev]); useLayoutEffect(() => { @@ -139,9 +116,7 @@ export default function Note(props: NoteProps) { mentions.push({ pk, name: u.name ?? shortNpub, - link: ( - {u.name ? `@${u.name}` : shortNpub} - ), + link: {u.name ? `@${u.name}` : shortNpub}, }); } else { mentions.push({ @@ -151,7 +126,7 @@ export default function Note(props: NoteProps) { }); } } - mentions.sort((a) => (a.name.startsWith("npub") ? 1 : -1)); + mentions.sort(a => (a.name.startsWith("npub") ? 1 : -1)); const othersLength = mentions.length - maxMentions; const renderMention = (m: { link: React.ReactNode }, idx: number) => { return ( @@ -162,13 +137,8 @@ export default function Note(props: NoteProps) { ); }; const pubMentions = - mentions.length > maxMentions - ? mentions?.slice(0, maxMentions).map(renderMention) - : mentions?.map(renderMention); - const others = - mentions.length > maxMentions - ? formatMessage(messages.Others, { n: othersLength }) - : ""; + mentions.length > maxMentions ? mentions?.slice(0, maxMentions).map(renderMention) : mentions?.map(renderMention); + const others = mentions.length > maxMentions ? formatMessage(messages.Others, { n: othersLength }) : ""; return (
re:  @@ -178,11 +148,7 @@ export default function Note(props: NoteProps) { {others} ) : ( - replyId && ( - - {hexToBech32("note", replyId)?.substring(0, 12)} - - ) + replyId && {hexToBech32("note", replyId)?.substring(0, 12)} )}
); @@ -192,10 +158,7 @@ export default function Note(props: NoteProps) { return ( <>

- +

{JSON.stringify(ev.ToObject(), undefined, "  ")}
@@ -207,10 +170,7 @@ export default function Note(props: NoteProps) { return ( <>

- +

{translated.text} @@ -230,10 +190,7 @@ export default function Note(props: NoteProps) { <> {options.showHeader && (
- + {options.showTime && (
@@ -241,43 +198,27 @@ export default function Note(props: NoteProps) { )}
)} -
goToEvent(e, ev.Id)}> +
goToEvent(e, ev.Id)}> {transformBody()} {translation()}
{extendable && !showMore && ( - setShowMore(true)} - > + setShowMore(true)}> )} - {options.showFooter && ( - setTranslated(t)} - /> - )} + {options.showFooter && setTranslated(t)} />} ); } const note = (
+ className={`${baseClassName}${highlight ? " active " : " "}${extendable && !showMore ? " note-expand" : ""}`} + ref={ref}> {content()}
); - return !ignoreModeration && isOpMuted ? ( - {note} - ) : ( - note - ); + return !ignoreModeration && isOpMuted ? {note} : note; } diff --git a/src/Element/NoteCreator.tsx b/src/Element/NoteCreator.tsx index b8ee1e80..b28cbdbe 100644 --- a/src/Element/NoteCreator.tsx +++ b/src/Element/NoteCreator.tsx @@ -48,9 +48,7 @@ export function NoteCreator(props: NoteCreatorProps) { async function sendNote() { if (note) { - const ev = replyTo - ? await publisher.reply(replyTo, note) - : await publisher.note(note); + const ev = replyTo ? await publisher.reply(replyTo, note) : await publisher.note(note); console.debug("Sending note: ", ev); publisher.broadcast(ev); setNote(""); @@ -68,7 +66,7 @@ export function NoteCreator(props: NoteCreatorProps) { if (file) { const rx = await uploader.upload(file, file.name); if (rx.url) { - setNote((n) => `${n ? `${n}\n` : ""}${rx.url}`); + setNote(n => `${n ? `${n}\n` : ""}${rx.url}`); } else if (rx?.error) { setError(rx.error); } @@ -125,11 +123,7 @@ export function NoteCreator(props: NoteCreatorProps) {
diff --git a/src/Element/NoteFooter.tsx b/src/Element/NoteFooter.tsx index 519d82f3..610835ac 100644 --- a/src/Element/NoteFooter.tsx +++ b/src/Element/NoteFooter.tsx @@ -21,13 +21,7 @@ import Zap from "Icons/Zap"; import Reply from "Icons/Reply"; import { formatShort } from "Number"; import useEventPublisher from "Feed/EventPublisher"; -import { - getReactions, - dedupeByPubkey, - hexToBech32, - normalizeReaction, - Reaction, -} from "Util"; +import { getReactions, dedupeByPubkey, hexToBech32, normalizeReaction, Reaction } from "Util"; import { NoteCreator } from "Element/NoteCreator"; import Reactions from "Element/Reactions"; import SendSats from "Element/SendSats"; @@ -58,13 +52,9 @@ export interface NoteFooterProps { export default function NoteFooter(props: NoteFooterProps) { const { related, ev } = props; const { formatMessage } = useIntl(); - const login = useSelector( - (s) => s.login.publicKey - ); + const login = useSelector(s => s.login.publicKey); const { mute, block } = useModeration(); - const prefs = useSelector( - (s) => s.login.preferences - ); + const prefs = useSelector(s => s.login.preferences); const author = useUserProfile(ev.RootPubKey); const publisher = useEventPublisher(); const [reply, setReply] = useState(false); @@ -75,29 +65,23 @@ export default function NoteFooter(props: NoteFooterProps) { const langNames = new Intl.DisplayNames([...window.navigator.languages], { type: "language", }); - const reactions = useMemo( - () => getReactions(related, ev.Id, EventKind.Reaction), - [related, ev] - ); - const reposts = useMemo( - () => dedupeByPubkey(getReactions(related, ev.Id, EventKind.Repost)), - [related, ev] - ); + const reactions = useMemo(() => getReactions(related, ev.Id, EventKind.Reaction), [related, ev]); + const reposts = useMemo(() => dedupeByPubkey(getReactions(related, ev.Id, EventKind.Repost)), [related, ev]); const zaps = useMemo(() => { const sortedZaps = getReactions(related, ev.Id, EventKind.ZapReceipt) .map(parseZap) - .filter((z) => z.valid && z.zapper !== ev.PubKey); + .filter(z => z.valid && z.zapper !== ev.PubKey); sortedZaps.sort((a, b) => b.amount - a.amount); return sortedZaps; }, [related]); const zapTotal = zaps.reduce((acc, z) => acc + z.amount, 0); - const didZap = zaps.some((a) => a.zapper === login); + const didZap = zaps.some(a => a.zapper === login); const groupReactions = useMemo(() => { const result = reactions?.reduce( (acc, reaction) => { const kind = normalizeReaction(reaction.content); const rs = acc[kind] || []; - if (rs.map((e) => e.pubkey).includes(reaction.pubkey)) { + if (rs.map(e => e.pubkey).includes(reaction.pubkey)) { return acc; } return { ...acc, [kind]: [...rs, reaction] }; @@ -116,14 +100,11 @@ export default function NoteFooter(props: NoteFooterProps) { const negative = groupReactions[Reaction.Negative]; function hasReacted(emoji: string) { - return reactions?.some( - ({ pubkey, content }) => - normalizeReaction(content) === emoji && pubkey === login - ); + return reactions?.some(({ pubkey, content }) => normalizeReaction(content) === emoji && pubkey === login); } function hasReposted() { - return reposts.some((a) => a.pubkey === login); + return reposts.some(a => a.pubkey === login); } async function react(content: string) { @@ -134,11 +115,7 @@ export default function NoteFooter(props: NoteFooterProps) { } async function deleteEvent() { - if ( - window.confirm( - formatMessage(messages.ConfirmDeletion, { id: ev.Id.substring(0, 8) }) - ) - ) { + if (window.confirm(formatMessage(messages.ConfirmDeletion, { id: ev.Id.substring(0, 8) }))) { const evDelete = await publisher.delete(ev.Id); publisher.broadcast(evDelete); } @@ -146,10 +123,7 @@ export default function NoteFooter(props: NoteFooterProps) { async function repost() { if (!hasReposted()) { - if ( - !prefs.confirmReposts || - window.confirm(formatMessage(messages.ConfirmRepost, { id: ev.Id })) - ) { + if (!prefs.confirmReposts || window.confirm(formatMessage(messages.ConfirmRepost, { id: ev.Id }))) { const evRepost = await publisher.repost(ev); publisher.broadcast(evRepost); } @@ -161,18 +135,11 @@ export default function NoteFooter(props: NoteFooterProps) { if (service) { return ( <> -
setTip(true)} - > +
setTip(true)}>
- {zapTotal > 0 && ( -
- {formatShort(zapTotal)} -
- )} + {zapTotal > 0 &&
{formatShort(zapTotal)}
}
); @@ -182,18 +149,11 @@ export default function NoteFooter(props: NoteFooterProps) { function repostIcon() { return ( -
repost()} - > +
repost()}>
- {reposts.length > 0 && ( -
- {formatShort(reposts.length)} -
- )} + {reposts.length > 0 &&
{formatShort(reposts.length)}
}
); } @@ -204,16 +164,11 @@ export default function NoteFooter(props: NoteFooterProps) { } return ( <> -
react("+")} - > +
react("+")}>
-
- {formatShort(positive.length)} -
+
{formatShort(positive.length)}
{repostIcon()} @@ -221,9 +176,7 @@ export default function NoteFooter(props: NoteFooterProps) { } async function share() { - const url = `${window.location.protocol}//${ - window.location.host - }/e/${hexToBech32("note", ev.Id)}`; + const url = `${window.location.protocol}//${window.location.host}/e/${hexToBech32("note", ev.Id)}`; if ("share" in window.navigator) { await window.navigator.share({ title: "Snort", @@ -262,9 +215,7 @@ export default function NoteFooter(props: NoteFooterProps) { } async function copyEvent() { - await navigator.clipboard.writeText( - JSON.stringify(ev.Original, undefined, " ") - ); + await navigator.clipboard.writeText(JSON.stringify(ev.Original, undefined, " ")); } function menuItems() { @@ -291,10 +242,7 @@ export default function NoteFooter(props: NoteFooterProps) { {prefs.enableReactions && ( react("-")}> - + )} block(ev.PubKey)}> @@ -303,10 +251,7 @@ export default function NoteFooter(props: NoteFooterProps) { translate()}> - + {prefs.showDebugMenus && ( copyEvent()}> @@ -330,10 +275,7 @@ export default function NoteFooter(props: NoteFooterProps) {
{tipButton()} {reactionIcons()} -
setReply((s) => !s)} - > +
setReply(s => !s)}>
@@ -346,18 +288,11 @@ export default function NoteFooter(props: NoteFooterProps) {
} - menuClassName="ctx-menu" - > + menuClassName="ctx-menu"> {menuItems()}
- setReply(false)} - show={reply} - setShow={setReply} - /> + setReply(false)} show={reply} setShow={setReply} /> { if (ev) { - const eTags = ev.Tags.filter((a) => a.Key === "e"); + const eTags = ev.Tags.filter(a => a.Key === "e"); if (eTags.length > 0) { return eTags[0].Event; } @@ -39,11 +39,7 @@ export default function NoteReaction(props: NoteReactionProps) { * Some clients embed the reposted note in the content */ function extractRoot() { - if ( - ev?.Kind === EventKind.Repost && - ev.Content.length > 0 && - ev.Content !== "#[0]" - ) { + if (ev?.Kind === EventKind.Repost && ev.Content.length > 0 && ev.Content !== "#[0]") { try { const r: RawEvent = JSON.parse(ev.Content); return r as TaggedRawEvent; @@ -73,9 +69,7 @@ export default function NoteReaction(props: NoteReactionProps) { {root ? : null} {!root && refEvent ? (

- - #{hexToBech32("note", refEvent).substring(0, 12)} - + #{hexToBech32("note", refEvent).substring(0, 12)}

) : null}
diff --git a/src/Element/NoteTime.tsx b/src/Element/NoteTime.tsx index 42ce88a7..26d1bcba 100644 --- a/src/Element/NoteTime.tsx +++ b/src/Element/NoteTime.tsx @@ -31,10 +31,7 @@ export default function NoteTime(props: NoteTimeProps) { weekday: "short", }); } else if (absAgo > HourInMs) { - return `${fromDate.getHours().toString().padStart(2, "0")}:${fromDate - .getMinutes() - .toString() - .padStart(2, "0")}`; + return `${fromDate.getHours().toString().padStart(2, "0")}:${fromDate.getMinutes().toString().padStart(2, "0")}`; } else if (absAgo < MinuteInMs) { return fallback; } else { @@ -49,7 +46,7 @@ export default function NoteTime(props: NoteTimeProps) { useEffect(() => { setTime(calcTime()); const t = setInterval(() => { - setTime((s) => { + setTime(s => { const newTime = calcTime(); if (newTime !== s) { return newTime; diff --git a/src/Element/NoteToSelf.tsx b/src/Element/NoteToSelf.tsx index a701238c..d4ba54d1 100644 --- a/src/Element/NoteToSelf.tsx +++ b/src/Element/NoteToSelf.tsx @@ -20,19 +20,13 @@ function NoteLabel({ pubkey }: NoteToSelfProps) { const user = useUserProfile(pubkey); return (
- {" "} - + {user?.nip05 && }
); } -export default function NoteToSelf({ - pubkey, - clickable, - className, - link, -}: NoteToSelfProps) { +export default function NoteToSelf({ pubkey, clickable, className, link }: NoteToSelfProps) { const navigate = useNavigate(); const clickLink = () => { @@ -45,12 +39,7 @@ export default function NoteToSelf({
- +
diff --git a/src/Element/ProfileImage.tsx b/src/Element/ProfileImage.tsx index db123b09..4c529e63 100644 --- a/src/Element/ProfileImage.tsx +++ b/src/Element/ProfileImage.tsx @@ -17,13 +17,7 @@ export interface ProfileImageProps { link?: string; } -export default function ProfileImage({ - pubkey, - subHeader, - showUsername = true, - className, - link, -}: ProfileImageProps) { +export default function ProfileImage({ pubkey, subHeader, showUsername = true, className, link }: ProfileImageProps) { const navigate = useNavigate(); const user = useUserProfile(pubkey); @@ -34,19 +28,12 @@ export default function ProfileImage({ return (
- navigate(link ?? profileLink(pubkey))} - /> + navigate(link ?? profileLink(pubkey))} />
{showUsername && (
- + {name} {user?.nip05 && } @@ -58,10 +45,7 @@ export default function ProfileImage({ ); } -export function getDisplayName( - user: MetadataCache | undefined, - pubkey: HexKey -) { +export function getDisplayName(user: MetadataCache | undefined, pubkey: HexKey) { let name = hexToBech32("npub", pubkey).substring(0, 12); if (user?.display_name !== undefined && user.display_name.length > 0) { name = user.display_name; diff --git a/src/Element/ProfilePreview.tsx b/src/Element/ProfilePreview.tsx index 415b02ce..69aba61d 100644 --- a/src/Element/ProfilePreview.tsx +++ b/src/Element/ProfilePreview.tsx @@ -25,21 +25,12 @@ export default function ProfilePreview(props: ProfilePreviewProps) { }; return ( -
+
{inView && ( <> {user?.about}
- ) : undefined - } + subHeader={options.about ?
{user?.about}
: undefined} /> {props.actions ?? (
diff --git a/src/Element/ProxyImg.tsx b/src/Element/ProxyImg.tsx index 7f7d8193..37893c13 100644 --- a/src/Element/ProxyImg.tsx +++ b/src/Element/ProxyImg.tsx @@ -1,11 +1,7 @@ import useImgProxy from "Feed/ImgProxy"; import { useEffect, useState } from "react"; -interface ProxyImgProps - extends React.DetailedHTMLProps< - React.ImgHTMLAttributes, - HTMLImageElement - > { +interface ProxyImgProps extends React.DetailedHTMLProps, HTMLImageElement> { size?: number; } @@ -17,7 +13,7 @@ export const ProxyImg = (props: ProxyImgProps) => { useEffect(() => { if (src) { proxy(src, size) - .then((a) => setUrl(a)) + .then(a => setUrl(a)) .catch(console.warn); } }, [src]); diff --git a/src/Element/Reactions.tsx b/src/Element/Reactions.tsx index cee3721d..dbeabf85 100644 --- a/src/Element/Reactions.tsx +++ b/src/Element/Reactions.tsx @@ -28,14 +28,7 @@ interface ReactionsProps { zaps: ParsedZap[]; } -const Reactions = ({ - show, - setShow, - positive, - negative, - reposts, - zaps, -}: ReactionsProps) => { +const Reactions = ({ show, setShow, positive, negative, reposts, zaps }: ReactionsProps) => { const { formatMessage } = useIntl(); const onClose = () => setShow(false); const likes = useMemo(() => { @@ -48,8 +41,7 @@ const Reactions = ({ sorted.sort((a, b) => b.created_at - a.created_at); return sorted; }, [negative]); - const total = - positive.length + negative.length + zaps.length + reposts.length; + const total = positive.length + negative.length + zaps.length + reposts.length; const defaultTabs: Tab[] = [ { text: formatMessage(messages.Likes, { n: likes.length }), @@ -93,24 +85,17 @@ const Reactions = ({

- +

{tab.value === 0 && - likes.map((ev) => { + likes.map(ev => { return (
- {ev.content === "+" ? ( - - ) : ( - ev.content - )} + {ev.content === "+" ? : ev.content}
@@ -118,23 +103,22 @@ const Reactions = ({ ); })} {tab.value === 1 && - zaps.map((z) => { + zaps.map(z => { return ( -
-
- - {formatShort(z.amount)} + z.zapper && ( +
+
+ + {formatShort(z.amount)} +
+ {z.content}} /> +
- {z.content}} - /> - -
+ ) ); })} {tab.value === 2 && - reposts.map((ev) => { + reposts.map(ev => { return (
@@ -146,7 +130,7 @@ const Reactions = ({ ); })} {tab.value === 3 && - dislikes.map((ev) => { + dislikes.map(ev => { return (
diff --git a/src/Element/Relay.tsx b/src/Element/Relay.tsx index ed486a60..3fd138f8 100644 --- a/src/Element/Relay.tsx +++ b/src/Element/Relay.tsx @@ -27,10 +27,7 @@ export default function Relay(props: RelayProps) { const dispatch = useDispatch(); const { formatMessage } = useIntl(); const navigate = useNavigate(); - const allRelaySettings = useSelector< - RootState, - Record - >((s) => s.login.relays); + const allRelaySettings = useSelector>(s => s.login.relays); const relaySettings = allRelaySettings[props.addr]; const state = useRelayState(props.addr); const name = useMemo(() => new URL(props.addr).host, [props.addr]); @@ -66,11 +63,8 @@ export default function Relay(props: RelayProps) { write: !relaySettings.write, read: relaySettings.read, }) - } - > - + }> +
@@ -82,11 +76,8 @@ export default function Relay(props: RelayProps) { write: relaySettings.write, read: !relaySettings.read, }) - } - > - + }> +
@@ -104,10 +95,7 @@ export default function Relay(props: RelayProps) { {state?.disconnects}
- navigate(state?.id ?? "")} - > + navigate(state?.id ?? "")}>
diff --git a/src/Element/SendSats.tsx b/src/Element/SendSats.tsx index 5d2a69bc..8c4545a9 100644 --- a/src/Element/SendSats.tsx +++ b/src/Element/SendSats.tsx @@ -54,9 +54,7 @@ export default function LNURLTip(props: LNURLTipProps) { const service = props.svc; const show = props.show || false; const { note, author, target } = props; - const amounts = [ - 500, 1_000, 5_000, 10_000, 20_000, 50_000, 100_000, 1_000_000, - ]; + const amounts = [500, 1_000, 5_000, 10_000, 20_000, 50_000, 100_000, 1_000_000]; const emojis: Record = { 1_000: "👍", 5_000: "💜", @@ -77,13 +75,12 @@ export default function LNURLTip(props: LNURLTipProps) { const { formatMessage } = useIntl(); const publisher = useEventPublisher(); const horizontalScroll = useHorizontalScroll(); - const canComment = - (payService?.commentAllowed ?? 0) > 0 || payService?.nostrPubkey; + const canComment = (payService?.commentAllowed ?? 0) > 0 || payService?.nostrPubkey; useEffect(() => { if (show && !props.invoice) { loadService() - .then((a) => setPayService(a ?? undefined)) + .then(a => setPayService(a ?? undefined)) .catch(() => setError(formatMessage(messages.LNURLFail))); } else { setPayService(undefined); @@ -99,13 +96,11 @@ export default function LNURLTip(props: LNURLTipProps) { if (payService) { const min = (payService.minSendable ?? 0) / 1000; const max = (payService.maxSendable ?? 0) / 1000; - return amounts.filter((a) => a >= min && a <= max); + return amounts.filter(a => a >= min && a <= max); } return []; }, [payService]); - // TODO Why was this never used? I think this might be a bug, or was it just an oversight? - const selectAmount = (a: number) => { setError(undefined); setInvoice(undefined); @@ -141,14 +136,10 @@ export default function LNURLTip(props: LNURLTipProps) { if (!amount || !payService) return null; let url = ""; const amountParam = `amount=${Math.floor(amount * 1000)}`; - const commentParam = - comment && payService?.commentAllowed - ? `&comment=${encodeURIComponent(comment)}` - : ""; + const commentParam = comment && payService?.commentAllowed ? `&comment=${encodeURIComponent(comment)}` : ""; if (payService.nostrPubkey && author) { const ev = await publisher.zap(author, note, comment); - const nostrParam = - ev && `&nostr=${encodeURIComponent(JSON.stringify(ev.ToObject()))}`; + const nostrParam = ev && `&nostr=${encodeURIComponent(JSON.stringify(ev.ToObject()))}`; url = `${payService.callback}?${amountParam}${commentParam}${nostrParam}`; } else { url = `${payService.callback}?${amountParam}${commentParam}`; @@ -185,14 +176,13 @@ export default function LNURLTip(props: LNURLTipProps) { className="f-grow mr10" placeholder={formatMessage(messages.Custom)} value={customAmount} - onChange={(e) => setCustomAmount(parseInt(e.target.value))} + onChange={e => setCustomAmount(parseInt(e.target.value))} />
@@ -222,12 +212,8 @@ export default function LNURLTip(props: LNURLTipProps) {
- {serviceAmounts.map((a) => ( - selectAmount(a)} - > + {serviceAmounts.map(a => ( + selectAmount(a)}> {emojis[a] && <>{emojis[a]} } {formatShort(a)} @@ -241,28 +227,18 @@ export default function LNURLTip(props: LNURLTipProps) { placeholder={formatMessage(messages.Comment)} className="f-grow" maxLength={payService?.commentAllowed || 120} - onChange={(e) => setComment(e.target.value)} + onChange={e => setComment(e.target.value)} /> )}
{(amount ?? 0) > 0 && ( - @@ -285,11 +261,7 @@ export default function LNURLTip(props: LNURLTipProps) {
- @@ -319,9 +291,7 @@ export default function LNURLTip(props: LNURLTipProps) { ); } - const defaultTitle = payService?.nostrPubkey - ? formatMessage(messages.SendZap) - : formatMessage(messages.SendSats); + const defaultTitle = payService?.nostrPubkey ? formatMessage(messages.SendZap) : formatMessage(messages.SendSats); const title = target ? formatMessage(messages.ToTarget, { action: defaultTitle, @@ -331,7 +301,7 @@ export default function LNURLTip(props: LNURLTipProps) { if (!show) return null; return ( -
e.stopPropagation()}> +
e.stopPropagation()}>
diff --git a/src/Element/Skeleton.css b/src/Element/Skeleton.css index 157162c0..09213ac4 100644 --- a/src/Element/Skeleton.css +++ b/src/Element/Skeleton.css @@ -37,12 +37,6 @@ } .skeleton::after { - background-image: linear-gradient( - 90deg, - #50535a 0%, - #656871 20%, - #50535a 40%, - #50535a 100% - ); + background-image: linear-gradient(90deg, #50535a 0%, #656871 20%, #50535a 40%, #50535a 100%); } } diff --git a/src/Element/Skeleton.tsx b/src/Element/Skeleton.tsx index c74e3ef7..1fa311fe 100644 --- a/src/Element/Skeleton.tsx +++ b/src/Element/Skeleton.tsx @@ -8,22 +8,13 @@ interface ISkepetonProps { margin?: string; } -export default function Skeleton({ - children, - width, - height, - margin, - loading = true, -}: ISkepetonProps) { +export default function Skeleton({ children, width, height, margin, loading = true }: ISkepetonProps) { if (!loading) { return <>{children}; } return ( -
+
{children}
); diff --git a/src/Element/SoundCloudEmded.tsx b/src/Element/SoundCloudEmded.tsx index a3c6f11a..424ce49b 100644 --- a/src/Element/SoundCloudEmded.tsx +++ b/src/Element/SoundCloudEmded.tsx @@ -5,8 +5,7 @@ const SoundCloudEmbed = ({ link }: { link: string }) => { height="166" scrolling="no" allow="autoplay" - src={`https://w.soundcloud.com/player/?url=${link}`} - > + src={`https://w.soundcloud.com/player/?url=${link}`}> ); }; diff --git a/src/Element/SpotifyEmbed.tsx b/src/Element/SpotifyEmbed.tsx index e15a3fa1..93fc14d2 100644 --- a/src/Element/SpotifyEmbed.tsx +++ b/src/Element/SpotifyEmbed.tsx @@ -1,8 +1,5 @@ const SpotifyEmbed = ({ link }: { link: string }) => { - const convertedUrl = link.replace( - /\/(track|album|playlist|episode)\/([a-zA-Z0-9]+)/, - "/embed/$1/$2" - ); + const convertedUrl = link.replace(/\/(track|album|playlist|episode)\/([a-zA-Z0-9]+)/, "/embed/$1/$2"); return ( + loading="lazy"> ); }; diff --git a/src/Element/Tabs.tsx b/src/Element/Tabs.tsx index 4fff9f39..3da948e4 100644 --- a/src/Element/Tabs.tsx +++ b/src/Element/Tabs.tsx @@ -20,11 +20,8 @@ interface TabElementProps extends Omit { export const TabElement = ({ t, tab, setTab }: TabElementProps) => { return (
!t.disabled && setTab(t)} - > + className={`tab ${tab.value === t.value ? "active" : ""} ${t.disabled ? "disabled" : ""}`} + onClick={() => !t.disabled && setTab(t)}> {t.text}
); @@ -33,7 +30,7 @@ export const TabElement = ({ t, tab, setTab }: TabElementProps) => { const Tabs = ({ tabs, tab, setTab }: TabsProps) => { return (
- {tabs.map((t) => ( + {tabs.map(t => ( ))}
diff --git a/src/Element/Text.tsx b/src/Element/Text.tsx index 2e249af3..fa87e2c4 100644 --- a/src/Element/Text.tsx +++ b/src/Element/Text.tsx @@ -34,9 +34,9 @@ export interface TextProps { export default function Text({ content, tags, creator, users }: TextProps) { function extractLinks(fragments: Fragment[]) { return fragments - .map((f) => { + .map(f => { if (typeof f === "string") { - return f.split(UrlRegex).map((a) => { + return f.split(UrlRegex).map(a => { if (a.startsWith("http")) { return ; } @@ -50,29 +50,22 @@ export default function Text({ content, tags, creator, users }: TextProps) { function extractMentions(frag: TextFragment) { return frag.body - .map((f) => { + .map(f => { if (typeof f === "string") { - return f.split(MentionRegex).map((match) => { + return f.split(MentionRegex).map(match => { const matchTag = match.match(/#\[(\d+)\]/); if (matchTag && matchTag.length === 2) { const idx = parseInt(matchTag[1]); - const ref = frag.tags?.find((a) => a.Index === idx); + const ref = frag.tags?.find(a => a.Index === idx); if (ref) { switch (ref.Key) { case "p": { return ; } case "e": { - const eText = hexToBech32( - "note", - ref.Event ?? "" - ).substring(0, 12); + const eText = hexToBech32("note", ref.Event).substring(0, 12); return ( - e.stopPropagation()} - > + e.stopPropagation()}> #{eText} ); @@ -95,9 +88,9 @@ export default function Text({ content, tags, creator, users }: TextProps) { function extractInvoices(fragments: Fragment[]) { return fragments - .map((f) => { + .map(f => { if (typeof f === "string") { - return f.split(InvoiceRegex).map((i) => { + return f.split(InvoiceRegex).map(i => { if (i.toLowerCase().startsWith("lnbc")) { return ; } else { @@ -112,9 +105,9 @@ export default function Text({ content, tags, creator, users }: TextProps) { function extractHashtags(fragments: Fragment[]) { return fragments - .map((f) => { + .map(f => { if (typeof f === "string") { - return f.split(HashtagRegex).map((i) => { + return f.split(HashtagRegex).map(i => { if (i.toLowerCase().startsWith("#")) { return ; } else { @@ -134,7 +127,7 @@ export default function Text({ content, tags, creator, users }: TextProps) { function transformParagraph(frag: TextFragment) { const fragments = transformText(frag); - if (fragments.every((f) => typeof f === "string")) { + if (fragments.every(f => typeof f === "string")) { return

{fragments}

; } return <>{fragments}; @@ -150,13 +143,9 @@ export default function Text({ content, tags, creator, users }: TextProps) { const components = useMemo(() => { return { - p: (x: { children?: React.ReactNode[] }) => - transformParagraph({ body: x.children ?? [], tags, users }), - a: (x: { href?: string }) => ( - - ), - li: (x: { children?: Fragment[] }) => - transformLi({ body: x.children ?? [], tags, users }), + p: (x: { children?: React.ReactNode[] }) => transformParagraph({ body: x.children ?? [], tags, users }), + a: (x: { href?: string }) => , + li: (x: { children?: Fragment[] }) => transformLi({ body: x.children ?? [], tags, users }), }; }, [content]); @@ -178,9 +167,7 @@ export default function Text({ content, tags, creator, users }: TextProps) { ) { node.type = "text"; const position = unwrap(node.position); - node.value = content - .slice(position.start.offset, position.end.offset) - .replace(/\)$/, " )"); + node.value = content.slice(position.start.offset, position.end.offset).replace(/\)$/, " )"); return SKIP; } }); @@ -188,11 +175,7 @@ export default function Text({ content, tags, creator, users }: TextProps) { [content] ); return ( - + {content} ); diff --git a/src/Element/Textarea.tsx b/src/Element/Textarea.tsx index 65021808..7d5923e2 100644 --- a/src/Element/Textarea.tsx +++ b/src/Element/Textarea.tsx @@ -85,11 +85,8 @@ const Textarea = (props: TextareaProps) => { "@": { afterWhitespace: true, dataProvider: userDataProvider, - component: (props: { entity: MetadataCache }) => ( - - ), - output: (item: { pubkey: string }) => - `@${hexToBech32("npub", item.pubkey)}`, + component: (props: { entity: MetadataCache }) => , + output: (item: { pubkey: string }) => `@${hexToBech32("npub", item.pubkey)}`, }, }} /> diff --git a/src/Element/Thread.css b/src/Element/Thread.css index 11363041..3049886e 100644 --- a/src/Element/Thread.css +++ b/src/Element/Thread.css @@ -87,8 +87,7 @@ } @media (min-width: 720px) { - .subthread-container.subthread-mid:not(.subthread-last) - .line-container:after { + .subthread-container.subthread-mid:not(.subthread-last) .line-container:after { left: 48px; } } @@ -103,8 +102,7 @@ } @media (min-width: 720px) { - .subthread-container.subthread-mid:not(.subthread-last) - .line-container:after { + .subthread-container.subthread-mid:not(.subthread-last) .line-container:after { left: 48px; } } diff --git a/src/Element/Thread.tsx b/src/Element/Thread.tsx index 2645a845..cc82312b 100644 --- a/src/Element/Thread.tsx +++ b/src/Element/Thread.tsx @@ -13,12 +13,9 @@ import NoteGhost from "Element/NoteGhost"; import Collapsed from "Element/Collapsed"; import messages from "./messages"; -function getParent( - ev: HexKey, - chains: Map -): HexKey | undefined { +function getParent(ev: HexKey, chains: Map): HexKey | undefined { for (const [k, vs] of chains.entries()) { - const fs = vs.map((a) => a.Id); + const fs = vs.map(a => a.Id); if (fs.includes(ev)) { return k; } @@ -49,30 +46,17 @@ interface SubthreadProps { onNavigate: (e: u256) => void; } -const Subthread = ({ - active, - path, - notes, - related, - chains, - onNavigate, -}: SubthreadProps) => { +const Subthread = ({ active, path, notes, related, chains, onNavigate }: SubthreadProps) => { const renderSubthread = (a: NEvent, idx: number) => { const isLastSubthread = idx === notes.length - 1; const replies = getReplies(a.Id, chains); return ( <> -
0 ? "subthread-multi" : "" - }`} - > +
0 ? "subthread-multi" : ""}`}> { const { formatMessage } = useIntl(); const replies = getReplies(note.Id, chains); - const activeInReplies = replies.map((r) => r.Id).includes(active); + const activeInReplies = replies.map(r => r.Id).includes(active); const [collapsed, setCollapsed] = useState(!activeInReplies); const hasMultipleNotes = replies.length > 0; const isLastVisibleNote = isLastSubthread && isLast && !hasMultipleNotes; - const className = `subthread-container ${ - isLast && collapsed ? "subthread-last" : "subthread-multi subthread-mid" - }`; + const className = `subthread-container ${isLast && collapsed ? "subthread-last" : "subthread-multi subthread-mid"}`; return ( <>
@@ -149,11 +131,7 @@ const ThreadNote = ({ onNavigate={onNavigate} /> ) : ( - + { +const TierTwo = ({ active, isLastSubthread, path, from, notes, related, chains, onNavigate }: SubthreadProps) => { const [first, ...rest] = notes; return ( @@ -216,36 +185,22 @@ const TierTwo = ({ ); }; -const TierThree = ({ - active, - path, - isLastSubthread, - from, - notes, - related, - chains, - onNavigate, -}: SubthreadProps) => { +const TierThree = ({ active, path, isLastSubthread, from, notes, related, chains, onNavigate }: SubthreadProps) => { const [first, ...rest] = notes; const replies = getReplies(first.Id, chains); - const activeInReplies = - notes.map((r) => r.Id).includes(active) || - replies.map((r) => r.Id).includes(active); + const activeInReplies = notes.map(r => r.Id).includes(active) || replies.map(r => r.Id).includes(active); const hasMultipleNotes = rest.length > 0 || replies.length > 0; const isLast = replies.length === 0 && rest.length === 0; return ( <>
+ className={`subthread-container ${hasMultipleNotes ? "subthread-multi" : ""} ${ + isLast ? "subthread-last" : "subthread-mid" + }`}> 0 && (
-
@@ -284,10 +235,9 @@ const TierThree = ({ return (
+ className={`subthread-container ${lastReply ? "" : "subthread-multi"} ${ + lastReply ? "subthread-last" : "subthread-mid" + }`}> new NEvent(a)); + const parsedNotes = notes.map(a => new NEvent(a)); // root note has no thread info - const root = useMemo( - () => parsedNotes.find((a) => a.Thread === null), - [notes] - ); + const root = useMemo(() => parsedNotes.find(a => a.Thread === null), [notes]); const [path, setPath] = useState([]); const currentId = path.length > 0 && path[path.length - 1]; - const currentRoot = useMemo( - () => parsedNotes.find((a) => a.Id === currentId), - [notes, currentId] - ); + const currentRoot = useMemo(() => parsedNotes.find(a => a.Id === currentId), [notes, currentId]); const [navigated, setNavigated] = useState(false); const navigate = useNavigate(); - const isSingleNote = - parsedNotes.filter((a) => a.Kind === EventKind.TextNote).length === 1; + const isSingleNote = parsedNotes.filter(a => a.Kind === EventKind.TextNote).length === 1; const location = useLocation(); const urlNoteId = location?.pathname.slice(3); const urlNoteHex = urlNoteId && bech32ToHex(urlNoteId); @@ -334,9 +277,9 @@ export default function Thread(props: ThreadProps) { const chains = useMemo(() => { const chains = new Map(); parsedNotes - ?.filter((a) => a.Kind === EventKind.TextNote) + ?.filter(a => a.Kind === EventKind.TextNote) .sort((a, b) => b.CreatedAt - a.CreatedAt) - .forEach((v) => { + .forEach(v => { const replyTo = v.Thread?.ReplyTo?.Event ?? v.Thread?.Root?.Event; if (replyTo) { if (!chains.has(replyTo)) { @@ -378,28 +321,15 @@ export default function Thread(props: ThreadProps) { }, [root, navigated, urlNoteHex, chains]); const brokenChains = useMemo(() => { - return Array.from(chains?.keys()).filter( - (a) => !parsedNotes?.some((b) => b.Id === a) - ); + return Array.from(chains?.keys()).filter(a => !parsedNotes?.some(b => b.Id === a)); }, [chains]); function renderRoot(note: NEvent) { const className = `thread-root ${isSingleNote ? "thread-root-single" : ""}`; if (note) { - return ( - - ); + return ; } else { - return ( - - Loading thread root.. ({notes?.length} notes loaded) - - ); + return Loading thread root.. ({notes?.length} notes loaded); } } @@ -438,25 +368,18 @@ export default function Thread(props: ThreadProps) { return (
- 1 ? "Parent" : "Back"} - /> + 1 ? "Parent" : "Back"} />
{currentRoot && renderRoot(currentRoot)} {currentRoot && renderChain(currentRoot.Id)} {currentRoot === root && ( <> {brokenChains.length > 0 &&

Other replies

} - {brokenChains.map((a) => { + {brokenChains.map(a => { return (
- - Missing event{" "} - {a.substring(0, 8)} + + Missing event {a.substring(0, 8)} {renderChain(a)}
diff --git a/src/Element/TidalEmbed.tsx b/src/Element/TidalEmbed.tsx index f838f0a7..dc0676ee 100644 --- a/src/Element/TidalEmbed.tsx +++ b/src/Element/TidalEmbed.tsx @@ -34,13 +34,11 @@ async function oembedLookup(link: string) { const TidalEmbed = ({ link }: { link: string }) => { const [source, setSource] = useState(); const [height, setHeight] = useState(); - const extraStyles = link.includes("video") - ? { aspectRatio: "16 / 9" } - : { height }; + const extraStyles = link.includes("video") ? { aspectRatio: "16 / 9" } : { height }; useEffect(() => { oembedLookup(link) - .then((data) => { + .then(data => { setSource(data.source || undefined); setHeight(data.height); }) @@ -49,25 +47,11 @@ const TidalEmbed = ({ link }: { link: string }) => { if (!source) return ( - e.stopPropagation()} - className="ext" - > + e.stopPropagation()} className="ext"> {link} ); - return ( -