diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea7..9b77ea7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,10 +1,9 @@ --- name: Bug report about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- **Describe the bug** @@ -12,6 +11,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -24,15 +24,17 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] + +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] +- Version [e.g. 22] **Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] + +- Device: [e.g. iPhone6] +- OS: [e.g. iOS8.1] +- Browser [e.g. stock browser, safari] +- Version [e.g. 22] **Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7..2bc5d5f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,10 +1,9 @@ --- name: Feature request about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- **Is your feature request related to a problem? Please describe.** diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1 @@ +{} diff --git a/d.ts b/d.ts index 6e95ec0..125a621 100644 --- a/d.ts +++ b/d.ts @@ -1,14 +1,14 @@ declare module "*.jpg" { - const value: any - export default value + const value: any; + export default value; } declare module "*.svg" { - const value: any - export default value + const value: any; + export default value; } declare module "*.webp" { - const value: any - export default value -} \ No newline at end of file + const value: any; + export default value; +} diff --git a/package.json b/package.json index 0becb86..a982dcc 100644 --- a/package.json +++ b/package.json @@ -72,5 +72,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "prettier": "2.8.3" } } diff --git a/public/index.html b/public/index.html index d91b444..e8779eb 100644 --- a/public/index.html +++ b/public/index.html @@ -1,23 +1,26 @@ + + + + + + + - - - - - - - - - - - snort.social - Nostr interface - - - - -
- + + + snort.social - Nostr interface + + + +
+ diff --git a/src/Const.ts b/src/Const.ts index 7a79d31..012f6e2 100644 --- a/src/Const.ts +++ b/src/Const.ts @@ -18,12 +18,14 @@ 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 @@ -33,59 +35,61 @@ export const DefaultConnectTimeout = 2000; /** * How long profile cache should be considered valid for */ -export const ProfileCacheExpire = (1_000 * 60 * 5); +export const ProfileCacheExpire = 1_000 * 60 * 5; /** * Default bootstrap relays */ export const DefaultRelays = new Map([ - ["wss://relay.snort.social", { read: true, write: true }], - ["wss://eden.nostr.land", { read: true, write: true }], - ["wss://atlas.nostr.land", { read: true, write: true }] + ["wss://relay.snort.social", { read: true, write: true }], + ["wss://eden.nostr.land", { read: true, write: true }], + ["wss://atlas.nostr.land", { read: true, write: true }], ]); /** * Default search relays */ export const SearchRelays = new Map([ - ["wss://relay.nostr.band", { read: true, write: false }], + ["wss://relay.nostr.band", { read: true, write: false }], ]); /** * List of recommended follows for new users */ export const RecommendedFollows = [ - "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2", // jack - "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", // fiatjaf - "020f2d21ae09bf35fcdfb65decf1478b846f5f728ab30c5eaabcd6d081a81c3e", // adam3us - "6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93", // gigi - "63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed", // Kieran - "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", // jb55 - "e33fe65f1fde44c6dc17eeb38fdad0fceaf1cae8722084332ed1e32496291d42", // wiz - "00000000827ffaa94bfea288c3dfce4422c794fbb96625b6b31e9049f729d700", // cameri - "A341F45FF9758F570A21B000C17D4E53A3A497C8397F26C0E6D61E5ACFFC7A98", // Saylor - "E88A691E98D9987C964521DFF60025F60700378A4879180DCBBB4A5027850411", // NVK - "C4EABAE1BE3CF657BC1855EE05E69DE9F059CB7A059227168B80B89761CBC4E0", // jackmallers - "85080D3BAD70CCDCD7F74C29A44F55BB85CBCD3DD0CBB957DA1D215BDB931204", // preston - "C49D52A573366792B9A6E4851587C28042FB24FA5625C6D67B8C95C8751ACA15", // holdonaut - "83E818DFBECCEA56B0F551576B3FD39A7A50E1D8159343500368FA085CCD964B", // jeffbooth - "3F770D65D3A764A9C5CB503AE123E62EC7598AD035D836E2A810F3877A745B24", // DerekRoss - "472F440F29EF996E92A186B8D320FF180C855903882E59D50DE1B8BD5669301E", // MartyBent - "1577e4599dd10c863498fe3c20bd82aafaf829a595ce83c5cf8ac3463531b09b", // yegorpetrov - "04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9", // ODELL - "7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194", // verbiricha - "52b4a076bcbbbdc3a1aefa3735816cf74993b1b8db202b01c883c58be7fad8bd", // semisol + "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2", // jack + "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", // fiatjaf + "020f2d21ae09bf35fcdfb65decf1478b846f5f728ab30c5eaabcd6d081a81c3e", // adam3us + "6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93", // gigi + "63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed", // Kieran + "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", // jb55 + "e33fe65f1fde44c6dc17eeb38fdad0fceaf1cae8722084332ed1e32496291d42", // wiz + "00000000827ffaa94bfea288c3dfce4422c794fbb96625b6b31e9049f729d700", // cameri + "A341F45FF9758F570A21B000C17D4E53A3A497C8397F26C0E6D61E5ACFFC7A98", // Saylor + "E88A691E98D9987C964521DFF60025F60700378A4879180DCBBB4A5027850411", // NVK + "C4EABAE1BE3CF657BC1855EE05E69DE9F059CB7A059227168B80B89761CBC4E0", // jackmallers + "85080D3BAD70CCDCD7F74C29A44F55BB85CBCD3DD0CBB957DA1D215BDB931204", // preston + "C49D52A573366792B9A6E4851587C28042FB24FA5625C6D67B8C95C8751ACA15", // holdonaut + "83E818DFBECCEA56B0F551576B3FD39A7A50E1D8159343500368FA085CCD964B", // jeffbooth + "3F770D65D3A764A9C5CB503AE123E62EC7598AD035D836E2A810F3877A745B24", // DerekRoss + "472F440F29EF996E92A186B8D320FF180C855903882E59D50DE1B8BD5669301E", // MartyBent + "1577e4599dd10c863498fe3c20bd82aafaf829a595ce83c5cf8ac3463531b09b", // yegorpetrov + "04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9", // ODELL + "7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194", // verbiricha + "52b4a076bcbbbdc3a1aefa3735816cf74993b1b8db202b01c883c58be7fad8bd", // semisol ]; /** * Regex to match email address */ -export const EmailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; +export const EmailRegex = + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; /** * Generic URL regex */ -export const UrlRegex = /((?:http|ftp|https):\/\/(?:[\w+?\.\w+])+(?:[a-zA-Z0-9\~\!\@\#\$\%\^\&\*\(\)_\-\=\+\\\/\?\.\:\;\'\,]*)?)/i; +export const UrlRegex = + /((?:http|ftp|https):\/\/(?:[\w+?\.\w+])+(?:[a-zA-Z0-9\~\!\@\#\$\%\^\&\*\(\)_\-\=\+\\\/\?\.\:\;\'\,]*)?)/i; /** * Extract file extensions regex @@ -105,12 +109,14 @@ export const InvoiceRegex = /(lnbc\w+)/i; /** * YouTube URL regex */ -export const YoutubeUrlRegex = /(?:https?:\/\/)?(?:www|m\.)?(?:youtu\.be\/|youtube\.com\/(?:shorts\/|embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})/; +export const YoutubeUrlRegex = + /(?:https?:\/\/)?(?:www|m\.)?(?:youtu\.be\/|youtube\.com\/(?:shorts\/|embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})/; /** * Tweet Regex */ -export const TweetUrlRegex = /https?:\/\/twitter\.com\/(?:#!\/)?(\w+)\/status(?:es)?\/(\d+)/ +export const TweetUrlRegex = + /https?:\/\/twitter\.com\/(?:#!\/)?(\w+)\/status(?:es)?\/(\d+)/; /** * Hashtag regex @@ -125,12 +131,15 @@ 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 46ec27c..59cf5fd 100644 --- a/src/Db/index.ts +++ b/src/Db/index.ts @@ -3,21 +3,21 @@ import { TaggedRawEvent, u256 } from "Nostr"; import { MetadataCache } from "State/Users"; import { hexToBech32 } from "Util"; -export const NAME = 'snortDB' -export const VERSION = 3 +export const NAME = "snortDB"; +export const VERSION = 3; export interface SubCache { - id: string, - ids: u256[], - until?: number, - since?: number, + id: string; + ids: u256[]; + until?: number; + since?: number; } const STORES = { - users: '++pubkey, name, display_name, picture, nip05, npub', - events: '++id, pubkey, created_at', - feeds: '++id' -} + users: "++pubkey, name, display_name, picture, nip05, npub", + events: "++id, pubkey, created_at", + feeds: "++id", +}; export class SnortDB extends Dexie { users!: Table; @@ -26,11 +26,16 @@ export class SnortDB extends Dexie { constructor() { super(NAME); - this.version(VERSION).stores(STORES).upgrade(async tx => { - await tx.table("users").toCollection().modify(user => { - user.npub = hexToBech32("npub", user.pubkey) + this.version(VERSION) + .stores(STORES) + .upgrade(async (tx) => { + await tx + .table("users") + .toCollection() + .modify((user) => { + user.npub = hexToBech32("npub", user.pubkey); + }); }); - }); } } diff --git a/src/Element/AsyncButton.tsx b/src/Element/AsyncButton.tsx index 784d9ea..d420aa3 100644 --- a/src/Element/AsyncButton.tsx +++ b/src/Element/AsyncButton.tsx @@ -1,27 +1,31 @@ -import { useState } from "react" +import { useState } from "react"; export default function AsyncButton(props: any) { - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(false); - async function handle(e : any) { - if(loading) return; - setLoading(true); - try { - if (typeof props.onClick === "function") { - let f = props.onClick(e); - if (f instanceof Promise) { - await f; - } - } - } - finally { - setLoading(false); + async function handle(e: any) { + if (loading) return; + setLoading(true); + try { + if (typeof props.onClick === "function") { + let f = props.onClick(e); + if (f instanceof Promise) { + await f; } + } + } finally { + setLoading(false); } + } - return ( - - ) -} \ No newline at end of file + return ( + + ); +} diff --git a/src/Element/Avatar.css b/src/Element/Avatar.css index c1e9320..18305b1 100644 --- a/src/Element/Avatar.css +++ b/src/Element/Avatar.css @@ -1,19 +1,19 @@ .avatar { - border-radius: 50%; - height: 210px; - width: 210px; - background-image: var(--img-url); - border: 1px solid transparent; - background-origin: border-box; - background-clip: content-box, border-box; - background-size: cover; - box-sizing: border-box; + border-radius: 50%; + height: 210px; + width: 210px; + background-image: var(--img-url); + border: 1px solid transparent; + background-origin: border-box; + background-clip: content-box, border-box; + background-size: cover; + box-sizing: border-box; } .avatar[data-domain="snort.social"] { - background-image: var(--img-url), var(--snort-gradient); + background-image: var(--img-url), var(--snort-gradient); } -.avatar[data-domain="strike.army"] { - background-image: var(--img-url), var(--strike-army-gradient); +.avatar[data-domain="strike.army"] { + background-image: var(--img-url), var(--strike-army-gradient); } diff --git a/src/Element/Avatar.tsx b/src/Element/Avatar.tsx index 3fd73b8..a4f7b90 100644 --- a/src/Element/Avatar.tsx +++ b/src/Element/Avatar.tsx @@ -4,30 +4,35 @@ 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]); - const backgroundImage = `url(${url})` - const style = { '--img-url': backgroundImage } as CSSProperties - const domain = user?.nip05 && user.nip05.split('@')[1] + const backgroundImage = `url(${url})`; + const style = { "--img-url": backgroundImage } as CSSProperties; + const domain = user?.nip05 && user.nip05.split("@")[1]; return (
-
- ) -} + > + ); +}; -export default Avatar +export default Avatar; diff --git a/src/Element/BackButton.css b/src/Element/BackButton.css index 0ebb52b..e94ae1b 100644 --- a/src/Element/BackButton.css +++ b/src/Element/BackButton.css @@ -7,7 +7,7 @@ } .back-button svg { - margin-right: .5em; + margin-right: 0.5em; } .back-button:hover { diff --git a/src/Element/BackButton.tsx b/src/Element/BackButton.tsx index 85b772e..61d060f 100644 --- a/src/Element/BackButton.tsx +++ b/src/Element/BackButton.tsx @@ -1,24 +1,25 @@ -import "./BackButton.css" +import "./BackButton.css"; import ArrowBack from "Icons/ArrowBack"; interface BackButtonProps { - text?: string - onClick?(): void + text?: string; + onClick?(): void; } const BackButton = ({ text = "Back", onClick }: BackButtonProps) => { const onClickHandler = () => { if (onClick) { - onClick() + onClick(); } - } + }; return ( - ) -} + ); +}; -export default BackButton +export default BackButton; diff --git a/src/Element/BlockButton.tsx b/src/Element/BlockButton.tsx index f2cd542..5f89ef5 100644 --- a/src/Element/BlockButton.tsx +++ b/src/Element/BlockButton.tsx @@ -2,20 +2,20 @@ import { HexKey } from "Nostr"; import useModeration from "Hooks/useModeration"; interface BlockButtonProps { - pubkey: HexKey + pubkey: HexKey; } const BlockButton = ({ pubkey }: BlockButtonProps) => { - const { block, unblock, isBlocked } = useModeration() + const { block, unblock, isBlocked } = useModeration(); return isBlocked(pubkey) ? ( ) : ( - ) -} + ); +}; -export default BlockButton +export default BlockButton; diff --git a/src/Element/BlockList.tsx b/src/Element/BlockList.tsx index bcf91d5..7640d23 100644 --- a/src/Element/BlockList.tsx +++ b/src/Element/BlockList.tsx @@ -1,7 +1,8 @@ import { useMemo } from "react"; import { useSelector } from "react-redux"; -import { HexKey } from "Nostr"; import type { RootState } from "State/Store"; +import { HexKey } from "Nostr"; +import type { RootState } from "State/Store"; import MuteButton from "Element/MuteButton"; import BlockButton from "Element/BlockButton"; import ProfilePreview from "Element/ProfilePreview"; @@ -9,31 +10,45 @@ import useMutedFeed, { getMuted } from "Feed/MuteList"; import useModeration from "Hooks/useModeration"; interface BlockListProps { - variant: "muted" | "blocked" + variant: "muted" | "blocked"; } export default function BlockList({ variant }: BlockListProps) { - const { publicKey } = useSelector((s: RootState) => s.login) - const { blocked, muted } = useModeration(); + const { publicKey } = useSelector((s: RootState) => s.login); + const { blocked, muted } = useModeration(); - return ( -
- {variant === "muted" && ( - <> -

{muted.length} muted

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

{blocked.length} blocked

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

{muted.length} muted

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

{blocked.length} blocked

+ {blocked.map((a) => { + return ( + } + pubkey={a} + options={{ about: false }} + key={a} + /> + ); + })} + + )} +
+ ); } diff --git a/src/Element/Collapsed.tsx b/src/Element/Collapsed.tsx index 2649217..569fb32 100644 --- a/src/Element/Collapsed.tsx +++ b/src/Element/Collapsed.tsx @@ -3,22 +3,25 @@ import { useState, ReactNode } from "react"; import ShowMore from "Element/ShowMore"; interface CollapsedProps { - text?: string - children: ReactNode - collapsed: boolean - setCollapsed(b: boolean): void + text?: string; + children: ReactNode; + collapsed: boolean; + setCollapsed(b: boolean): void; } -const Collapsed = ({ text, children, collapsed, setCollapsed }: CollapsedProps) => { +const Collapsed = ({ + text, + children, + collapsed, + setCollapsed, +}: CollapsedProps) => { return collapsed ? (
setCollapsed(false)} />
) : ( -
- {children} -
- ) -} +
{children}
+ ); +}; -export default Collapsed +export default Collapsed; diff --git a/src/Element/Copy.css b/src/Element/Copy.css index f85044a..22da429 100644 --- a/src/Element/Copy.css +++ b/src/Element/Copy.css @@ -4,9 +4,9 @@ } .copy .body { - font-size: var(--font-size-small); - color: var(--font-color); - margin-right: 6px; + font-size: var(--font-size-small); + color: var(--font-color); + margin-right: 6px; } .copy .icon { diff --git a/src/Element/Copy.tsx b/src/Element/Copy.tsx index 80ef926..df6848f 100644 --- a/src/Element/Copy.tsx +++ b/src/Element/Copy.tsx @@ -4,22 +4,30 @@ import CopyIcon from "Icons/Copy"; import { useCopy } from "useCopy"; export interface CopyProps { - text: string, - maxSize?: number + text: string; + maxSize?: number; } export default function Copy({ text, maxSize = 32 }: CopyProps) { - const { copy, copied, error } = useCopy(); - const sliceLength = maxSize / 2 - const trimmed = text.length > maxSize ? `${text.slice(0, sliceLength)}...${text.slice(-sliceLength)}` : text + const { copy, copied, error } = useCopy(); + const sliceLength = maxSize / 2; + const trimmed = + text.length > maxSize + ? `${text.slice(0, sliceLength)}...${text.slice(-sliceLength)}` + : text; - return ( -
copy(text)}> - - {trimmed} - - - {copied ? : } - -
- ) + return ( +
copy(text)}> + {trimmed} + + {copied ? ( + + ) : ( + + )} + +
+ ); } diff --git a/src/Element/DM.css b/src/Element/DM.css index a43943c..dbec697 100644 --- a/src/Element/DM.css +++ b/src/Element/DM.css @@ -1,23 +1,23 @@ .dm { - padding: 8px; - background-color: var(--gray); - margin-bottom: 5px; - border-radius: 5px; - width: fit-content; - min-width: 100px; - max-width: 90%; - overflow: hidden; - min-height: 40px; - white-space: pre-wrap; + padding: 8px; + background-color: var(--gray); + margin-bottom: 5px; + border-radius: 5px; + width: fit-content; + min-width: 100px; + max-width: 90%; + overflow: hidden; + min-height: 40px; + white-space: pre-wrap; } .dm > div:first-child { - color: var(--gray-light); - font-size: small; - margin-bottom: 3px; + color: var(--gray-light); + font-size: small; + margin-bottom: 3px; } .dm.me { - align-self: flex-end; - background-color: var(--gray-secondary); + align-self: flex-end; + background-color: var(--gray-secondary); } diff --git a/src/Element/DM.tsx b/src/Element/DM.tsx index 3131512..e4400ab 100644 --- a/src/Element/DM.tsx +++ b/src/Element/DM.tsx @@ -1,7 +1,7 @@ import "./DM.css"; import { useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { useInView } from 'react-intersection-observer'; +import { useInView } from "react-intersection-observer"; import useEventPublisher from "Feed/EventPublisher"; import Event from "Nostr/Event"; @@ -13,42 +13,53 @@ import { HexKey, TaggedRawEvent } from "Nostr"; import { incDmInteraction } from "State/Login"; export type DMProps = { - data: TaggedRawEvent -} + data: TaggedRawEvent; +}; export default function DM(props: DMProps) { - const dispatch = useDispatch(); - 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 isMe = props.data.pubkey === pubKey; - const otherPubkey = isMe ? pubKey : props.data.tags.find(a => a[0] === "p")![1]; + const dispatch = useDispatch(); + 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 isMe = props.data.pubkey === pubKey; + const otherPubkey = isMe + ? pubKey + : props.data.tags.find((a) => a[0] === "p")![1]; - async function decrypt() { - let e = new Event(props.data); - let decrypted = await publisher.decryptDm(e); - setContent(decrypted || ""); - if (!isMe) { - setLastReadDm(e.PubKey); - dispatch(incDmInteraction()); - } + async function decrypt() { + let e = new Event(props.data); + let decrypted = await publisher.decryptDm(e); + setContent(decrypted || ""); + if (!isMe) { + setLastReadDm(e.PubKey); + dispatch(incDmInteraction()); } + } - useEffect(() => { - if (!decrypted && inView) { - setDecrypted(true); - decrypt().catch(console.error); - } - }, [inView, props.data]); + useEffect(() => { + if (!decrypted && inView) { + setDecrypted(true); + decrypt().catch(console.error); + } + }, [inView, props.data]); - return ( -
-
-
- -
-
- ) + return ( +
+
+ +
+
+ +
+
+ ); } diff --git a/src/Element/FollowButton.tsx b/src/Element/FollowButton.tsx index da19400..98f1e2b 100644 --- a/src/Element/FollowButton.tsx +++ b/src/Element/FollowButton.tsx @@ -8,32 +8,34 @@ import { RootState } from "State/Store"; import { parseId } from "Util"; export interface FollowButtonProps { - pubkey: HexKey, - className?: string + pubkey: HexKey; + className?: string; } 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 baseClassname = `${props.className} follow-button` + const pubkey = parseId(props.pubkey); + const publiser = useEventPublisher(); + const isFollowing = useSelector( + (s) => s.login.follows?.includes(pubkey) ?? false + ); + const baseClassname = `${props.className} follow-button`; - async function follow(pubkey: HexKey) { - let ev = await publiser.addFollow(pubkey); - publiser.broadcast(ev); - } + async function follow(pubkey: HexKey) { + let ev = await publiser.addFollow(pubkey); + publiser.broadcast(ev); + } - async function unfollow(pubkey: HexKey) { - let ev = await publiser.removeFollow(pubkey); - publiser.broadcast(ev); - } + async function unfollow(pubkey: HexKey) { + let ev = await publiser.removeFollow(pubkey); + publiser.broadcast(ev); + } - return ( - - ) + return ( + + ); } diff --git a/src/Element/FollowListBase.tsx b/src/Element/FollowListBase.tsx index 678c223..57d9493 100644 --- a/src/Element/FollowListBase.tsx +++ b/src/Element/FollowListBase.tsx @@ -3,24 +3,35 @@ import { HexKey } from "Nostr"; import ProfilePreview from "Element/ProfilePreview"; export interface FollowListBaseProps { - pubkeys: HexKey[], - title?: string + pubkeys: HexKey[]; + title?: string; } -export default function FollowListBase({ pubkeys, title }: FollowListBaseProps) { - const publisher = useEventPublisher(); +export default function FollowListBase({ + pubkeys, + title, +}: FollowListBaseProps) { + const publisher = useEventPublisher(); - async function followAll() { - let ev = await publisher.addFollow(pubkeys); - publisher.broadcast(ev); - } + async function followAll() { + let ev = await publisher.addFollow(pubkeys); + publisher.broadcast(ev); + } - return ( -
-
-
{title}
- -
- {pubkeys?.map(a => )} -
- ) + return ( +
+
+
{title}
+ +
+ {pubkeys?.map((a) => ( + + ))} +
+ ); } diff --git a/src/Element/FollowersList.tsx b/src/Element/FollowersList.tsx index 84d0605..1d614e5 100644 --- a/src/Element/FollowersList.tsx +++ b/src/Element/FollowersList.tsx @@ -5,16 +5,22 @@ import EventKind from "Nostr/EventKind"; import FollowListBase from "Element/FollowListBase"; export interface FollowersListProps { - pubkey: HexKey + pubkey: HexKey; } export default function FollowersList({ pubkey }: FollowersListProps) { - const feed = useFollowersFeed(pubkey); + const feed = useFollowersFeed(pubkey); - const pubkeys = useMemo(() => { - let contactLists = feed?.store.notes.filter(a => a.kind === EventKind.ContactList && a.tags.some(b => b[0] === "p" && b[1] === pubkey)); - return [...new Set(contactLists?.map(a => a.pubkey))]; - }, [feed]); + const pubkeys = useMemo(() => { + let contactLists = feed?.store.notes.filter( + (a) => + a.kind === EventKind.ContactList && + a.tags.some((b) => b[0] === "p" && b[1] === pubkey) + ); + return [...new Set(contactLists?.map((a) => a.pubkey))]; + }, [feed]); - return -} \ No newline at end of file + return ( + + ); +} diff --git a/src/Element/FollowsList.tsx b/src/Element/FollowsList.tsx index 69dfa98..e279488 100644 --- a/src/Element/FollowsList.tsx +++ b/src/Element/FollowsList.tsx @@ -2,18 +2,20 @@ import { useMemo } from "react"; import useFollowsFeed from "Feed/FollowsFeed"; import { HexKey } from "Nostr"; import FollowListBase from "Element/FollowListBase"; -import { getFollowers} from "Feed/FollowsFeed"; +import { getFollowers } from "Feed/FollowsFeed"; export interface FollowsListProps { - pubkey: HexKey + pubkey: HexKey; } export default function FollowsList({ pubkey }: FollowsListProps) { - const feed = useFollowsFeed(pubkey); + const feed = useFollowsFeed(pubkey); - const pubkeys = useMemo(() => { - return getFollowers(feed.store, pubkey); - }, [feed]); + const pubkeys = useMemo(() => { + return getFollowers(feed.store, pubkey); + }, [feed]); - return -} \ No newline at end of file + return ( + + ); +} diff --git a/src/Element/FollowsYou.css b/src/Element/FollowsYou.css index 2b1cf5e..5b91d30 100644 --- a/src/Element/FollowsYou.css +++ b/src/Element/FollowsYou.css @@ -1,6 +1,6 @@ .follows-you { color: var(--font-secondary-color); font-size: var(--font-size-tiny); - margin-left: .2em; - font-weight: normal + margin-left: 0.2em; + font-weight: normal; } diff --git a/src/Element/FollowsYou.tsx b/src/Element/FollowsYou.tsx index efaad37..503110f 100644 --- a/src/Element/FollowsYou.tsx +++ b/src/Element/FollowsYou.tsx @@ -3,26 +3,26 @@ import { useMemo } from "react"; import { useSelector } from "react-redux"; import { HexKey } from "Nostr"; import { RootState } from "State/Store"; -import useFollowsFeed from "Feed/FollowsFeed"; +import useFollowsFeed from "Feed/FollowsFeed"; import { getFollowers } from "Feed/FollowsFeed"; export interface FollowsYouProps { - pubkey: HexKey + pubkey: HexKey; } -export default function FollowsYou({ pubkey }: FollowsYouProps ) { - const feed = useFollowsFeed(pubkey); - const loginPubKey = useSelector(s => s.login.publicKey); +export default function FollowsYou({ pubkey }: FollowsYouProps) { + const feed = useFollowsFeed(pubkey); + const loginPubKey = useSelector( + (s) => s.login.publicKey + ); - const pubkeys = useMemo(() => { - return getFollowers(feed.store, pubkey); - }, [feed]); + const pubkeys = useMemo(() => { + return getFollowers(feed.store, pubkey); + }, [feed]); - const followsMe = pubkeys.includes(loginPubKey!) ?? false ; + const followsMe = pubkeys.includes(loginPubKey!) ?? false; - return ( - <> - { followsMe ? follows you : null } - - ) + return ( + <>{followsMe ? follows you : null} + ); } diff --git a/src/Element/Hashtag.css b/src/Element/Hashtag.css index 630973a..52232cc 100644 --- a/src/Element/Hashtag.css +++ b/src/Element/Hashtag.css @@ -1,3 +1,3 @@ .hashtag { - color: var(--highlight); + color: var(--highlight); } diff --git a/src/Element/Hashtag.tsx b/src/Element/Hashtag.tsx index 78a5e5c..f604642 100644 --- a/src/Element/Hashtag.tsx +++ b/src/Element/Hashtag.tsx @@ -1,12 +1,14 @@ -import { Link } from 'react-router-dom' -import './Hashtag.css' +import { Link } from "react-router-dom"; +import "./Hashtag.css"; const Hashtag = ({ tag }: { tag: string }) => { return ( - e.stopPropagation()}>#{tag} + e.stopPropagation()}> + #{tag} + - ) -} + ); +}; -export default Hashtag +export default Hashtag; diff --git a/src/Element/HyperText.tsx b/src/Element/HyperText.tsx index 0bb472e..72683fe 100644 --- a/src/Element/HyperText.tsx +++ b/src/Element/HyperText.tsx @@ -1,106 +1,153 @@ -import { useCallback } from 'react'; -import { useSelector } from 'react-redux'; +import { useCallback } from "react"; +import { useSelector } from "react-redux"; import { TwitterTweetEmbed } from "react-twitter-embed"; import { - FileExtensionRegex, - YoutubeUrlRegex, - TweetUrlRegex, - TidalRegex, - SoundCloudRegex, - MixCloudRegex, - SpotifyRegex + FileExtensionRegex, + YoutubeUrlRegex, + TweetUrlRegex, + TidalRegex, + SoundCloudRegex, + MixCloudRegex, + SpotifyRegex, } from "Const"; -import { RootState } from 'State/Store'; -import SoundCloudEmbed from 'Element/SoundCloudEmded' -import MixCloudEmbed from 'Element/MixCloudEmbed'; +import { RootState } from "State/Store"; +import SoundCloudEmbed from "Element/SoundCloudEmded"; +import MixCloudEmbed from "Element/MixCloudEmbed"; import SpotifyEmbed from "Element/SpotifyEmbed"; import TidalEmbed from "Element/TidalEmbed"; -import { ProxyImg } from 'Element/ProxyImg'; -import { HexKey } from 'Nostr'; +import { ProxyImg } from "Element/ProxyImg"; +import { HexKey } from "Nostr"; -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); +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); - if (pref.autoLoadMedia === "none" || hideNonFollows) { - return e.stopPropagation()} target="_blank" rel="noreferrer" className="ext">{a} - } - const url = new URL(a); - const youtubeId = YoutubeUrlRegex.test(a) && RegExp.$1; - const tweetId = TweetUrlRegex.test(a) && RegExp.$2; - const tidalId = TidalRegex.test(a) && RegExp.$1; - 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; - if (extension) { - switch (extension) { - case "gif": - case "jpg": - case "jpeg": - case "png": - case "bmp": - case "webp": { - return ; - } - case "wav": - case "mp3": - case "ogg": { - return