diff --git a/.changeset/purple-carrots-worry.md b/.changeset/purple-carrots-worry.md new file mode 100644 index 000000000..e7152b34b --- /dev/null +++ b/.changeset/purple-carrots-worry.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Remove NIP-72 communities diff --git a/src/app.tsx b/src/app.tsx index 2a211b7e7..6f3721ef3 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -70,12 +70,6 @@ const BadgesView = lazy(() => import("./views/badges")); const BadgesBrowseView = lazy(() => import("./views/badges/browse")); const BadgeDetailsView = lazy(() => import("./views/badges/badge-details")); -const CommunitiesHomeView = lazy(() => import("./views/communities")); -const CommunityFindByNameView = lazy(() => import("./views/community/find-by-name")); -const CommunityView = lazy(() => import("./views/community/index")); -const CommunityPendingView = lazy(() => import("./views/community/views/pending")); -const CommunityNewestView = lazy(() => import("./views/community/views/newest")); - import RelaysView from "./views/relays"; import RelayView from "./views/relays/relay"; import BrowseRelaySetsView from "./views/relays/browse-sets"; @@ -458,10 +452,6 @@ const router = createHashRouter([ { path: "", element: }, ], }, - { - path: "communities", - children: [{ path: "", element: }], - }, { path: "articles", children: [ @@ -469,21 +459,6 @@ const router = createHashRouter([ { path: ":naddr", element: }, ], }, - { - path: "c/:community", - children: [ - { path: "", element: }, - { - path: ":pubkey", - element: , - children: [ - { path: "", element: }, - { path: "newest", element: }, - { path: "pending", element: }, - ], - }, - ], - }, { path: "torrents", children: [ diff --git a/src/components/common-event/event-verification-icon.tsx b/src/components/common-event/event-verification-icon.tsx index 8de85a6df..ce8433ea5 100644 --- a/src/components/common-event/event-verification-icon.tsx +++ b/src/components/common-event/event-verification-icon.tsx @@ -3,7 +3,7 @@ import { verifyEvent } from "nostr-tools"; import { NostrEvent } from "../../types/nostr-event"; import { CheckIcon, VerificationFailed } from "../icons"; -import useAppSettings from "../../hooks/use-app-settings"; +import useAppSettings from "../../hooks/use-user-app-settings"; function EventVerificationIcon({ event }: { event: NostrEvent }) { const { showSignatureVerification } = useAppSettings(); diff --git a/src/components/content/components/expandable-embed.tsx b/src/components/content/components/expandable-embed.tsx index 227b6062f..5ba71f3bf 100644 --- a/src/components/content/components/expandable-embed.tsx +++ b/src/components/content/components/expandable-embed.tsx @@ -2,7 +2,7 @@ import { PropsWithChildren, ReactNode } from "react"; import EmbedActions from "./embed-actions"; import { Link, useDisclosure } from "@chakra-ui/react"; -import useAppSettings from "../../../hooks/use-app-settings"; +import useAppSettings from "../../../hooks/use-user-app-settings"; export default function ExpandableEmbed({ children, diff --git a/src/components/content/links/image.tsx b/src/components/content/links/image.tsx index 1126c7d0d..a7a5e9f74 100644 --- a/src/components/content/links/image.tsx +++ b/src/components/content/links/image.tsx @@ -28,7 +28,7 @@ import { import { useRegisterSlide } from "../../lightbox-provider"; import { isImageURL } from "../../../helpers/url"; import { NostrEvent } from "../../../types/nostr-event"; -import useAppSettings from "../../../hooks/use-app-settings"; +import useAppSettings from "../../../hooks/use-user-app-settings"; import useElementTrustBlur from "../../../hooks/use-element-trust-blur"; import { buildImageProxyURL } from "../../../helpers/image"; import ExpandableEmbed from "../components/expandable-embed"; diff --git a/src/components/content/links/music.tsx b/src/components/content/links/music.tsx index 75e102677..9051af464 100644 --- a/src/components/content/links/music.tsx +++ b/src/components/content/links/music.tsx @@ -3,7 +3,7 @@ import { Box, useColorMode } from "@chakra-ui/react"; import { EmbedEventPointer } from "../../embed-event"; import { STEMSTR_RELAY } from "../../../helpers/nostr/stemstr"; import ExpandableEmbed from "../components/expandable-embed"; -import useAppSettings from "../../../hooks/use-app-settings"; +import useAppSettings from "../../../hooks/use-user-app-settings"; const setZIndex: CSSProperties = { zIndex: 1, position: "relative" }; diff --git a/src/components/content/links/reddit.tsx b/src/components/content/links/reddit.tsx index 4d542fe2f..3d0627ad8 100644 --- a/src/components/content/links/reddit.tsx +++ b/src/components/content/links/reddit.tsx @@ -1,5 +1,5 @@ import { replaceDomain } from "../../../helpers/url"; -import useAppSettings from "../../../hooks/use-app-settings"; +import useAppSettings from "../../../hooks/use-user-app-settings"; import { renderGenericUrl } from "./common"; // copied from https://github.com/SimonBrazell/privacy-redirect/blob/master/src/assets/javascripts/helpers/reddit.js diff --git a/src/components/content/links/twitter.tsx b/src/components/content/links/twitter.tsx index d2caeae8f..d2946da7d 100644 --- a/src/components/content/links/twitter.tsx +++ b/src/components/content/links/twitter.tsx @@ -2,7 +2,7 @@ import { Link } from "applesauce-content/nast"; import { renderOpenGraphUrl } from "./common"; import { replaceDomain } from "../../../helpers/url"; -import useAppSettings from "../../../hooks/use-app-settings"; +import useAppSettings from "../../../hooks/use-user-app-settings"; // copied from https://github.com/SimonBrazell/privacy-redirect/blob/master/src/assets/javascripts/helpers/twitter.js export const TWITTER_DOMAINS = ["x.com", "twitter.com", "www.twitter.com", "mobile.twitter.com", "pbs.twimg.com"]; diff --git a/src/components/content/links/video.tsx b/src/components/content/links/video.tsx index 4451f8b0f..931964edb 100644 --- a/src/components/content/links/video.tsx +++ b/src/components/content/links/video.tsx @@ -2,7 +2,7 @@ import { lazy, VideoHTMLAttributes } from "react"; import styled from "@emotion/styled"; import { isStreamURL, isVideoURL } from "../../../helpers/url"; -import useAppSettings from "../../../hooks/use-app-settings"; +import useAppSettings from "../../../hooks/use-user-app-settings"; import useElementTrustBlur from "../../../hooks/use-element-trust-blur"; import ExpandableEmbed from "../components/expandable-embed"; const LiveVideoPlayer = lazy(() => import("../../live-video-player")); diff --git a/src/components/content/links/youtube.tsx b/src/components/content/links/youtube.tsx index 0133c57cb..dfdf635b3 100644 --- a/src/components/content/links/youtube.tsx +++ b/src/components/content/links/youtube.tsx @@ -1,6 +1,6 @@ import { AspectRatio } from "@chakra-ui/react"; import ExpandableEmbed from "../components/expandable-embed"; -import useAppSettings from "../../../hooks/use-app-settings"; +import useAppSettings from "../../../hooks/use-user-app-settings"; // copied from https://github.com/SimonBrazell/privacy-redirect/blob/master/src/assets/javascripts/helpers/youtube.js export const YOUTUBE_DOMAINS = [ diff --git a/src/components/embed-event/event-types/embedded-community.tsx b/src/components/embed-event/event-types/embedded-community.tsx index 45cdc25e2..dc7b35ec1 100644 --- a/src/components/embed-event/event-types/embedded-community.tsx +++ b/src/components/embed-event/event-types/embedded-community.tsx @@ -1,17 +1,21 @@ -import { Link as RouterLink } from "react-router-dom"; +import { useContext } from "react"; import { Card, CardFooter, CardHeader, CardProps, Heading, LinkBox, LinkOverlay, Text } from "@chakra-ui/react"; -import { nip19 } from "nostr-tools"; +import { getTagValue } from "applesauce-core/helpers"; import UserAvatarLink from "../../user/user-avatar-link"; import UserLink from "../../user/user-link"; import { NostrEvent } from "../../../types/nostr-event"; -import { getCommunityImage, getCommunityName } from "../../../helpers/nostr/communities"; +import { AppHandlerContext } from "../../../providers/route/app-handler-provider"; +import useShareableEventAddress from "../../../hooks/use-shareable-event-address"; export default function EmbeddedCommunity({ community, ...props }: Omit & { community: NostrEvent }) { - const name = getCommunityName(community); + const name = getTagValue(community, "name") || getTagValue(community, "d"); + const image = getTagValue(community, "image"); + const naddr = useShareableEventAddress(community); + const { openAddress } = useContext(AppHandlerContext); return ( - - {name} - + naddr && openAddress(naddr)}>{name} diff --git a/src/components/embed-event/event-types/embedded-note.tsx b/src/components/embed-event/event-types/embedded-note.tsx index 738fbab2c..63ea2a82e 100644 --- a/src/components/embed-event/event-types/embedded-note.tsx +++ b/src/components/embed-event/event-types/embedded-note.tsx @@ -16,7 +16,7 @@ import HoverLinkOverlay from "../../hover-link-overlay"; import singleEventService from "../../../services/single-event"; import { getSharableEventAddress } from "../../../services/relay-hints"; import localSettings from "../../../services/local-settings"; -import useAppSettings from "../../../hooks/use-app-settings"; +import useAppSettings from "../../../hooks/use-user-app-settings"; export default function EmbeddedNote({ event, ...props }: Omit & { event: NostrEvent }) { const { showSignatureVerification } = useAppSettings(); diff --git a/src/components/embed-event/event-types/embedded-torrent-comment.tsx b/src/components/embed-event/event-types/embedded-torrent-comment.tsx index 4efc0d243..40f7a24db 100644 --- a/src/components/embed-event/event-types/embedded-torrent-comment.tsx +++ b/src/components/embed-event/event-types/embedded-torrent-comment.tsx @@ -15,7 +15,7 @@ import { getTorrentTitle } from "../../../helpers/nostr/torrents"; import { useNavigateInDrawer } from "../../../providers/drawer-sub-view-provider"; import { MouseEventHandler, useCallback } from "react"; import { nip19 } from "nostr-tools"; -import useAppSettings from "../../../hooks/use-app-settings"; +import useAppSettings from "../../../hooks/use-user-app-settings"; export default function EmbeddedTorrentComment({ comment, diff --git a/src/components/event-zap-modal/input-step.tsx b/src/components/event-zap-modal/input-step.tsx index db6ed1ed7..16ed1ee55 100644 --- a/src/components/event-zap-modal/input-step.tsx +++ b/src/components/event-zap-modal/input-step.tsx @@ -7,7 +7,7 @@ import { humanReadableSats } from "../../helpers/lightning"; import { LightningIcon } from "../icons"; import useUserLNURLMetadata from "../../hooks/use-user-lnurl-metadata"; import { EmbedEvent, EmbedProps } from "../embed-event"; -import useAppSettings from "../../hooks/use-app-settings"; +import useAppSettings from "../../hooks/use-user-app-settings"; import CustomZapAmountOptions from "./zap-options"; import UserAvatar from "../user/user-avatar"; import UserLink from "../user/user-link"; diff --git a/src/components/event-zap-modal/pay-step.tsx b/src/components/event-zap-modal/pay-step.tsx index 096cbf494..eae6a5b44 100644 --- a/src/components/event-zap-modal/pay-step.tsx +++ b/src/components/event-zap-modal/pay-step.tsx @@ -17,7 +17,7 @@ import UserLink from "../user/user-link"; import { ChevronDownIcon, ChevronUpIcon, CheckIcon, ErrorIcon, LightningIcon } from "../icons"; import { InvoiceModalContent } from "../invoice-modal"; import { PropsWithChildren, useEffect, useState } from "react"; -import useAppSettings from "../../hooks/use-app-settings"; +import useAppSettings from "../../hooks/use-user-app-settings"; function UserCard({ children, pubkey }: PropsWithChildren & { pubkey: string }) { return ( diff --git a/src/components/event-zap-modal/zap-options.tsx b/src/components/event-zap-modal/zap-options.tsx index 38c9cb5c9..1973158fb 100644 --- a/src/components/event-zap-modal/zap-options.tsx +++ b/src/components/event-zap-modal/zap-options.tsx @@ -1,6 +1,6 @@ import { Button, Flex } from "@chakra-ui/react"; -import useAppSettings from "../../hooks/use-app-settings"; +import useAppSettings from "../../hooks/use-user-app-settings"; import { LightningIcon } from "../icons"; export default function CustomZapAmountOptions({ onSelect }: { onSelect: (value: number) => void }) { diff --git a/src/components/icons.tsx b/src/components/icons.tsx index 762565cb8..d32018bd7 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -223,12 +223,6 @@ export const AppearanceIcon = Colors; export const DatabaseIcon = Database01; export const PerformanceIcon = Speedometer03; -export const CommunityIcon = createIcon({ - displayName: "CommunityIcon", - d: "M9.55 11.5C8.30736 11.5 7.3 10.4926 7.3 9.25C7.3 8.00736 8.30736 7 9.55 7C10.7926 7 11.8 8.00736 11.8 9.25C11.8 10.4926 10.7926 11.5 9.55 11.5ZM10 19.748V16.4C10 15.9116 10.1442 15.4627 10.4041 15.0624C10.1087 15.0213 9.80681 15 9.5 15C7.93201 15 6.49369 15.5552 5.37091 16.4797C6.44909 18.0721 8.08593 19.2553 10 19.748ZM4.45286 14.66C5.86432 13.6168 7.61013 13 9.5 13C10.5435 13 11.5431 13.188 12.4667 13.5321C13.3447 13.1888 14.3924 13 15.5 13C17.1597 13 18.6849 13.4239 19.706 14.1563C19.8976 13.4703 20 12.7471 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 12.9325 4.15956 13.8278 4.45286 14.66ZM18.8794 16.0859C18.4862 15.5526 17.1708 15 15.5 15C13.4939 15 12 15.7967 12 16.4V20C14.9255 20 17.4843 18.4296 18.8794 16.0859ZM12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM15.5 12.5C14.3954 12.5 13.5 11.6046 13.5 10.5C13.5 9.39543 14.3954 8.5 15.5 8.5C16.6046 8.5 17.5 9.39543 17.5 10.5C17.5 11.6046 16.6046 12.5 15.5 12.5Z", - defaultProps, -}); - /** @deprecated */ export const GhostIcon = createIcon({ displayName: "GhostIcon", @@ -255,4 +249,4 @@ export const WikiIcon = BookOpen01; export const ArticleIcon = Edit04; export const VideoIcon = Film02; -export const MediaIcon = Camera01 +export const MediaIcon = Camera01; diff --git a/src/components/layout/nav-items.tsx b/src/components/layout/nav-items.tsx index c5410f53c..2f07ba3aa 100644 --- a/src/components/layout/nav-items.tsx +++ b/src/components/layout/nav-items.tsx @@ -49,9 +49,7 @@ export default function NavItems() { else if (location.pathname.startsWith("/relays")) active = "relays"; else if (location.pathname.startsWith("/r/")) active = "relays"; else if (location.pathname.startsWith("/lists")) active = "lists"; - else if (location.pathname.startsWith("/communities")) active = "communities"; else if (location.pathname.startsWith("/channels")) active = "channels"; - else if (location.pathname.startsWith("/c/")) active = "communities"; else if (location.pathname.startsWith("/goals")) active = "goals"; else if (location.pathname.startsWith("/badges")) active = "badges"; else if (location.pathname.startsWith("/emojis")) active = "emojis"; @@ -83,8 +81,8 @@ export default function NavItems() { if (apps.length < 3 && !apps.some((a) => a.id === "streams")) { apps.push(internal.find((app) => app.id === "streams")!); } - if (apps.length < 3 && !apps.some((a) => a.id === "communities")) { - apps.push(internal.find((app) => app.id === "communities")!); + if (apps.length < 3 && !apps.some((a) => a.id === "articles")) { + apps.push(internal.find((app) => app.id === "articles")!); } if (apps.length < 3 && !apps.some((a) => a.id === "channels")) { apps.push(internal.find((app) => app.id === "channels")!); diff --git a/src/components/media-post/media-post-card.tsx b/src/components/media-post/media-post-card.tsx index 8fe7cb0d5..1360d2973 100644 --- a/src/components/media-post/media-post-card.tsx +++ b/src/components/media-post/media-post-card.tsx @@ -9,7 +9,7 @@ import DebugEventButton from "../debug-modal/debug-event-button"; import { TrustProvider } from "../../providers/local/trust-provider"; import EventReactionButtons from "../event-reactions/event-reactions"; import AddReactionButton from "../note/timeline-note/components/add-reaction-button"; -import RepostButton from "../note/timeline-note/components/repost-button"; +import ShareButton from "../note/timeline-note/components/share-button"; import QuoteEventButton from "../note/quote-event-button"; import MediaPostSlides from "./media-slides"; import MediaPostContents from "./media-post-content"; @@ -55,7 +55,7 @@ export default function MediaPost({ post }: { post: NostrEvent }) { - + diff --git a/src/components/note/timeline-note/components/repost-modal.tsx b/src/components/note/timeline-note/components/repost-modal.tsx deleted file mode 100644 index d34a0b862..000000000 --- a/src/components/note/timeline-note/components/repost-modal.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { useState } from "react"; -import { - Button, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, - ModalOverlay, - ModalProps, - SimpleGrid, - useDisclosure, -} from "@chakra-ui/react"; -import { EventTemplate, NostrEvent, kinds } from "nostr-tools"; -import dayjs from "dayjs"; -import type { AddressPointer } from "nostr-tools/nip19"; - -import { ChevronDownIcon, ChevronUpIcon, ExternalLinkIcon } from "../../../icons"; -import { getAddressPointerRelayHints, getEventRelayHint } from "../../../../services/relay-hints"; -import { usePublishEvent } from "../../../../providers/global/publish-provider"; -import useCurrentAccount from "../../../../hooks/use-current-account"; -import useUserCommunitiesList from "../../../../hooks/use-user-communities-list"; -import { createCoordinate } from "../../../../classes/batch-kind-pubkey-loader"; -import { EmbedEvent } from "../../../embed-event"; - -function buildRepost(event: NostrEvent): EventTemplate { - const hint = getEventRelayHint(event); - const tags: NostrEvent["tags"] = []; - tags.push(["e", event.id, hint ?? ""]); - tags.push(["k", String(event.kind)]); - - return { - kind: event.kind === kinds.ShortTextNote ? kinds.Repost : kinds.GenericRepost, - tags, - content: JSON.stringify(event), - created_at: dayjs().unix(), - }; -} - -export default function RepostModal({ - event, - isOpen, - onClose, - ...props -}: Omit & { event: NostrEvent }) { - const account = useCurrentAccount(); - const publish = usePublishEvent(); - const showCommunities = useDisclosure(); - const { pointers } = useUserCommunitiesList(account?.pubkey); - - const [loading, setLoading] = useState(false); - const repost = async (communityPointer?: AddressPointer) => { - setLoading(true); - const draft = buildRepost(event); - if (communityPointer) { - draft.tags.push([ - "a", - createCoordinate(communityPointer.kind, communityPointer.pubkey, communityPointer.identifier), - getAddressPointerRelayHints(communityPointer)[0], - ]); - } - await publish("Repost", draft); - onClose(); - setLoading(false); - }; - - return ( - - - - - Repost Note - - - - - {showCommunities.isOpen && ( - - {pointers.map((pointer) => ( - - ))} - - )} - - - - - - {!showCommunities.isOpen && ( - - )} - - - - ); -} diff --git a/src/components/note/timeline-note/components/repost-button.tsx b/src/components/note/timeline-note/components/share-button.tsx similarity index 62% rename from src/components/note/timeline-note/components/repost-button.tsx rename to src/components/note/timeline-note/components/share-button.tsx index 090ce08cb..f5ec738dd 100644 --- a/src/components/note/timeline-note/components/repost-button.tsx +++ b/src/components/note/timeline-note/components/share-button.tsx @@ -5,27 +5,27 @@ import { NostrEvent } from "../../../../types/nostr-event"; import { RepostIcon } from "../../../icons"; import useEventCount from "../../../../hooks/use-event-count"; import useCurrentAccount from "../../../../hooks/use-current-account"; -import RepostModal from "./repost-modal"; +import ShareModal from "./share-modal"; -export default function RepostButton({ event }: { event: NostrEvent }) { +export default function ShareButton({ event }: { event: NostrEvent }) { const { isOpen, onClose, onOpen } = useDisclosure(); const account = useCurrentAccount(); - const hasReposted = useEventCount( + const hasShared = useEventCount( account ? { "#e": [event.id], kinds: [kinds.Repost, kinds.GenericRepost], authors: [account.pubkey] } : undefined, ); - const repostCount = useEventCount({ "#e": [event.id], kinds: [kinds.Repost, kinds.GenericRepost] }); + const ShareCount = useEventCount({ "#e": [event.id], kinds: [kinds.Repost, kinds.GenericRepost] }); return ( <> - {repostCount !== undefined && repostCount > 0 ? ( + {ShareCount !== undefined && ShareCount > 0 ? ( ) : ( )} - {isOpen && } + {isOpen && } ); } diff --git a/src/components/note/timeline-note/components/share-modal.tsx b/src/components/note/timeline-note/components/share-modal.tsx new file mode 100644 index 000000000..08cbd7eac --- /dev/null +++ b/src/components/note/timeline-note/components/share-modal.tsx @@ -0,0 +1,68 @@ +import { useState } from "react"; +import { + Button, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + ModalProps, +} from "@chakra-ui/react"; +import { NostrEvent } from "nostr-tools"; +import { useEventFactory } from "applesauce-react/hooks"; + +import { usePublishEvent } from "../../../../providers/global/publish-provider"; +import { EmbedEvent } from "../../../embed-event"; + +export default function ShareModal({ + event, + isOpen, + onClose, + ...props +}: Omit & { event: NostrEvent }) { + const publish = usePublishEvent(); + const factory = useEventFactory(); + + const [loading, setLoading] = useState(false); + const share = async () => { + setLoading(true); + const draft = await factory.share(event); + + await publish("Share", draft); + onClose(); + setLoading(false); + }; + + return ( + + + + + Share Note + + + + + + + + + + + + + ); +} diff --git a/src/components/note/timeline-note/index.tsx b/src/components/note/timeline-note/index.tsx index 3abd7680e..142f69582 100644 --- a/src/components/note/timeline-note/index.tsx +++ b/src/components/note/timeline-note/index.tsx @@ -23,7 +23,7 @@ import UserLink from "../../user/user-link"; import NoteZapButton from "../note-zap-button"; import { ExpandProvider } from "../../../providers/local/expanded"; import EventVerificationIcon from "../../common-event/event-verification-icon"; -import RepostButton from "./components/repost-button"; +import ShareButton from "./components/share-button"; import QuoteEventButton from "../quote-event-button"; import { ReplyIcon } from "../../icons"; import NoteContentWithWarning from "./note-content-with-warning"; @@ -47,7 +47,7 @@ import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref"; import { getSharableEventAddress } from "../../../services/relay-hints"; import localSettings from "../../../services/local-settings"; import NotePublishedUsing from "../note-published-using"; -import useAppSettings from "../../../hooks/use-app-settings"; +import useAppSettings from "../../../hooks/use-user-app-settings"; export type TimelineNoteProps = Omit & { event: NostrEvent; @@ -130,7 +130,7 @@ export function TimelineNote({ {showReplyButton && ( } aria-label="Reply" title="Reply" onClick={replyForm.onOpen} /> )} - + diff --git a/src/components/note/timeline-note/note-community-metadata.tsx b/src/components/note/timeline-note/note-community-metadata.tsx index 64a845bca..eba1edfcd 100644 --- a/src/components/note/timeline-note/note-community-metadata.tsx +++ b/src/components/note/timeline-note/note-community-metadata.tsx @@ -1,22 +1,24 @@ -import { useMemo } from "react"; -import { Link as RouterLink } from "react-router-dom"; +import { useContext, useMemo } from "react"; import { Link, Text, TextProps } from "@chakra-ui/react"; -import { NostrEvent } from "nostr-tools"; +import { nip19, NostrEvent } from "nostr-tools"; import { getEventCommunityPointer } from "../../../helpers/nostr/communities"; +import { AppHandlerContext } from "../../../providers/route/app-handler-provider"; +/** @deprecated remove when communities are no longer supported */ export default function NoteCommunityMetadata({ event, ...props }: Omit & { event: NostrEvent }) { const communityPointer = useMemo(() => getEventCommunityPointer(event), [event]); + const { openAddress } = useContext(AppHandlerContext); if (!communityPointer) return null; return ( Posted in{" "} - + openAddress(nip19.naddrEncode(communityPointer))} color="blue.500"> {communityPointer.identifier} {" "} community diff --git a/src/components/note/timeline-note/note-content-with-warning.tsx b/src/components/note/timeline-note/note-content-with-warning.tsx index eea83b7c8..6612b5037 100644 --- a/src/components/note/timeline-note/note-content-with-warning.tsx +++ b/src/components/note/timeline-note/note-content-with-warning.tsx @@ -4,7 +4,7 @@ import { getContentWarning } from "applesauce-core/helpers"; import { TextNoteContents } from "./text-note-contents"; import { useExpand } from "../../../providers/local/expanded"; import ContentWarning from "../../content-warning"; -import useAppSettings from "../../../hooks/use-app-settings"; +import useAppSettings from "../../../hooks/use-user-app-settings"; export default function NoteContentWithWarning({ event }: { event: NostrEvent }) { const expand = useExpand(); diff --git a/src/components/post-modal/community-select.tsx b/src/components/post-modal/community-select.tsx deleted file mode 100644 index 6019643ec..000000000 --- a/src/components/post-modal/community-select.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { forwardRef } from "react"; -import { Select, SelectProps } from "@chakra-ui/react"; - -import useUserCommunitiesList from "../../hooks/use-user-communities-list"; -import useCurrentAccount from "../../hooks/use-current-account"; -import { getCommunityName } from "../../helpers/nostr/communities"; -import { AddressPointer } from "nostr-tools/nip19"; -import useReplaceableEvent from "../../hooks/use-replaceable-event"; -import { getEventCoordinate } from "../../helpers/nostr/event"; - -function CommunityOption({ pointer }: { pointer: AddressPointer }) { - const community = useReplaceableEvent(pointer); - if (!community) return; - - return ; -} - -const CommunitySelect = forwardRef>(({ ...props }, ref) => { - const account = useCurrentAccount(); - const { pointers } = useUserCommunitiesList(account?.pubkey); - - return ( - - ); -}); -export default CommunitySelect; diff --git a/src/components/post-modal/index.tsx b/src/components/post-modal/index.tsx index 6e1b2f71f..b16a6adcf 100644 --- a/src/components/post-modal/index.tsx +++ b/src/components/post-modal/index.tsx @@ -38,13 +38,12 @@ import { PublishDetails } from "../../views/task-manager/publish-log/publish-det import { TrustProvider } from "../../providers/local/trust-provider"; import MagicTextArea, { RefType } from "../magic-textarea"; import { useContextEmojis } from "../../providers/global/emoji-provider"; -import CommunitySelect from "./community-select"; import ZapSplitCreator from "../../views/new/note/zap-split-creator"; import useCurrentAccount from "../../hooks/use-current-account"; import useCacheForm from "../../hooks/use-cache-form"; import useTextAreaUploadFile, { useTextAreaInsertTextWithForm } from "../../hooks/use-textarea-upload-file"; import MinePOW from "../pow/mine-pow"; -import useAppSettings from "../../hooks/use-app-settings"; +import useAppSettings from "../../hooks/use-user-app-settings"; import { ErrorBoundary } from "../error-boundary"; import { useFinalizeDraft, usePublishEvent } from "../../providers/global/publish-provider"; import { TextNoteContents } from "../note/timeline-note/text-note-contents"; @@ -54,11 +53,9 @@ import InsertGifButton from "../gif/insert-gif-button"; import InsertImageButton from "../../views/new/note/insert-image-button"; type FormValues = { - subject: string; content: string; nsfw: boolean; nsfwReason: string; - community: string; split: Omit[]; difficulty: number; }; @@ -66,8 +63,6 @@ type FormValues = { export type PostModalProps = { cacheFormKey?: string | null; initContent?: string; - initCommunity?: string; - requireSubject?: boolean; }; export default function PostModal({ @@ -75,8 +70,6 @@ export default function PostModal({ onClose, cacheFormKey = "new-note", initContent = "", - initCommunity = "", - requireSubject, }: Omit & PostModalProps) { const publish = usePublishEvent(); const finalizeDraft = useFinalizeDraft(); @@ -93,11 +86,9 @@ export default function PostModal({ const [draft, setDraft] = useState(); const { getValues, setValue, watch, register, handleSubmit, formState, reset } = useForm({ defaultValues: { - subject: "", content: initContent, nsfw: false, nsfwReason: "", - community: initCommunity, split: [] as Omit[], difficulty: noteDifficulty || 0, }, @@ -123,10 +114,6 @@ export default function PostModal({ splits: values.split, }); - // TODO: remove when NIP-72 communities are removed - if (values.community) draft.tags.push(["a", values.community]); - if (values.subject) draft.tags.push(["subject", values.subject]); - const unsigned = await finalizeDraft(draft); setDraft(unsigned); @@ -188,7 +175,6 @@ export default function PostModal({ return ( <> - {requireSubject && } - - Post to community - - NSFW {getValues().nsfw && ( diff --git a/src/components/timeline-page/generic-note-timeline/repost-event.tsx b/src/components/timeline-page/generic-note-timeline/share-event.tsx similarity index 74% rename from src/components/timeline-page/generic-note-timeline/repost-event.tsx rename to src/components/timeline-page/generic-note-timeline/share-event.tsx index e38edb0d8..0064d9a82 100644 --- a/src/components/timeline-page/generic-note-timeline/repost-event.tsx +++ b/src/components/timeline-page/generic-note-timeline/share-event.tsx @@ -1,7 +1,6 @@ import { memo } from "react"; -import { Flex, Heading, Link, Text } from "@chakra-ui/react"; +import { Flex, Heading, Text } from "@chakra-ui/react"; import { kinds, nip18 } from "nostr-tools"; -import { Link as RouterLink } from "react-router-dom"; import { NostrEvent } from "../../../types/nostr-event"; import TimelineNote from "../../note/timeline-note"; @@ -13,12 +12,11 @@ import useSingleEvent from "../../../hooks/use-single-event"; import { EmbedEvent } from "../../embed-event"; import useUserMuteFilter from "../../../hooks/use-user-mute-filter"; import { parseHardcodedNoteContent } from "../../../helpers/nostr/event"; -import { getEventCommunityPointer } from "../../../helpers/nostr/communities"; import LoadingNostrLink from "../../loading-nostr-link"; import NoteMenu from "../../note/note-menu"; import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref"; -function RepostEvent({ event }: { event: NostrEvent }) { +function ShareEvent({ event }: { event: NostrEvent }) { const muteFilter = useUserMuteFilter(); const hardCodedNote = parseHardcodedNoteContent(event); @@ -26,8 +24,6 @@ function RepostEvent({ event }: { event: NostrEvent }) { const loadedNote = useSingleEvent(pointer?.id, pointer?.relays); const note = hardCodedNote || loadedNote; - const communityCoordinate = getEventCommunityPointer(event); - const ref = useEventIntersectionRef(event); if ((note && muteFilter(note)) || !pointer) return null; @@ -41,18 +37,7 @@ function RepostEvent({ event }: { event: NostrEvent }) { - - {communityCoordinate ? `Shared to` : `Shared`} - - {communityCoordinate && ( - - {communityCoordinate.identifier} - - )} + Shared {!note ? ( @@ -68,4 +53,4 @@ function RepostEvent({ event }: { event: NostrEvent }) { ); } -export default memo(RepostEvent); +export default memo(ShareEvent); diff --git a/src/components/timeline-page/generic-note-timeline/timeline-item.tsx b/src/components/timeline-page/generic-note-timeline/timeline-item.tsx index 126c67c96..94a3f839b 100644 --- a/src/components/timeline-page/generic-note-timeline/timeline-item.tsx +++ b/src/components/timeline-page/generic-note-timeline/timeline-item.tsx @@ -4,7 +4,7 @@ import { Box } from "@chakra-ui/react"; import { ErrorBoundary } from "../../error-boundary"; import ReplyNote from "./reply-note"; -import RepostEvent from "./repost-event"; +import ShareEvent from "./share-event"; import StreamNote from "./stream-note"; import RelayRecommendation from "./relay-recommendation"; import BadgeAwardCard from "../../../views/badges/components/badge-award-card"; @@ -29,7 +29,7 @@ function TimelineItem({ event, visible, minHeight }: { event: NostrEvent; visibl break; case kinds.Repost: case kinds.GenericRepost: - content = ; + content = ; break; case kinds.LiveEvent: content = ; diff --git a/src/components/user/user-avatar.tsx b/src/components/user/user-avatar.tsx index 45b2df52a..d1eac5cb5 100644 --- a/src/components/user/user-avatar.tsx +++ b/src/components/user/user-avatar.tsx @@ -7,7 +7,7 @@ import { ProfileContent } from "applesauce-core/helpers"; import { getIdenticon } from "../../helpers/identicon"; import { safeUrl } from "../../helpers/parse"; import { getDisplayName } from "../../helpers/nostr/profile"; -import useAppSettings from "../../hooks/use-app-settings"; +import useAppSettings from "../../hooks/use-user-app-settings"; import useCurrentAccount from "../../hooks/use-current-account"; import { buildImageProxyURL } from "../../helpers/image"; import UserDnsIdentityIcon from "./user-dns-identity-icon"; diff --git a/src/components/user/user-link.tsx b/src/components/user/user-link.tsx index a9577790b..fd00fab49 100644 --- a/src/components/user/user-link.tsx +++ b/src/components/user/user-link.tsx @@ -4,7 +4,7 @@ import { nip19 } from "nostr-tools"; import { getDisplayName } from "../../helpers/nostr/profile"; import useUserProfile from "../../hooks/use-user-profile"; -import useAppSettings from "../../hooks/use-app-settings"; +import useAppSettings from "../../hooks/use-user-app-settings"; import useCurrentAccount from "../../hooks/use-current-account"; export type UserLinkProps = LinkProps & { diff --git a/src/components/user/user-name.tsx b/src/components/user/user-name.tsx index 0f9f216ae..3d998b3e3 100644 --- a/src/components/user/user-name.tsx +++ b/src/components/user/user-name.tsx @@ -3,7 +3,7 @@ import { Text, TextProps } from "@chakra-ui/react"; import { getDisplayName } from "../../helpers/nostr/profile"; import useUserProfile from "../../hooks/use-user-profile"; -import useAppSettings from "../../hooks/use-app-settings"; +import useAppSettings from "../../hooks/use-user-app-settings"; function UserName({ pubkey, ...props }: Omit & { pubkey: string }) { const metadata = useUserProfile(pubkey); diff --git a/src/helpers/app-settings.ts b/src/helpers/app-settings.ts index 709bac206..d07f2d916 100644 --- a/src/helpers/app-settings.ts +++ b/src/helpers/app-settings.ts @@ -1,6 +1,7 @@ import { ColorModeWithSystem } from "@chakra-ui/react"; +import { kinds } from "nostr-tools"; -export const APP_SETTINGS_KIND = 30078; +export const APP_SETTINGS_KIND = kinds.Application; export const APP_SETTING_IDENTIFIER = "nostrudel-settings"; export type AppSettingsV0 = { diff --git a/src/helpers/nostr/communities.ts b/src/helpers/nostr/communities.ts index 8f8e4cbf0..1a315cc72 100644 --- a/src/helpers/nostr/communities.ts +++ b/src/helpers/nostr/communities.ts @@ -39,49 +39,6 @@ export function getCommunityRanking(community: NostrEvent) { return community.tags.find((t) => t[0] === "rank_mode")?.[1]; } -export function getPostSubject(event: NostrEvent) { - const subject = event.tags.find((t) => t[0] === "subject")?.[1]; - if (subject) return subject; - const firstLine = event.content.match(/^[^\n\t]+/)?.[0]; - if (!firstLine) return; - if (!getMatchNostrLink().test(firstLine) && !getMatchLink().test(firstLine)) return firstLine; -} - -export function getApprovedEmbeddedNote(approval: NostrEvent) { - if (!approval.content) return null; - try { - const json = JSON.parse(approval.content); - validateEvent(json); - return (json as NostrEvent) ?? null; - } catch (e) {} - return null; -} - -export function validateCommunity(community: NostrEvent) { - try { - getCommunityName(community); - return true; - } catch (e) { - return false; - } -} - -export function buildApprovalMap(events: Iterable, mods: string[]) { - const approvals = new Map(); - for (const event of events) { - if (event.kind === kinds.CommunityPostApproval && mods.includes(event.pubkey)) { - for (const tag of event.tags) { - if (isETag(tag)) { - const arr = approvals.get(tag[1]); - if (!arr) approvals.set(tag[1], [event]); - else arr.push(event); - } - } - } - } - return approvals; -} - export function getEventCommunityPointer(event: NostrEvent) { const communityTag = event.tags.filter(isATag).find((t) => t[1].startsWith(kinds.CommunityDefinition + ":")); return communityTag ? parseCoordinate(communityTag[1], true) : null; diff --git a/src/helpers/nostr/lists.ts b/src/helpers/nostr/lists.ts index c187954cb..f06bcedcf 100644 --- a/src/helpers/nostr/lists.ts +++ b/src/helpers/nostr/lists.ts @@ -1,8 +1,8 @@ import dayjs from "dayjs"; -import { EventTemplate, NostrEvent, kinds, nip19 } from "nostr-tools"; +import { EventTemplate, NostrEvent, kinds } from "nostr-tools"; import { getPointerFromTag } from "applesauce-core/helpers"; -import { PTag, isATag, isDTag, isPTag, isRTag } from "../../types/nostr-event"; +import { PTag, isDTag, isPTag, isRTag } from "../../types/nostr-event"; import { getEventCoordinate, replaceOrAddSimpleTag } from "./event"; import { getRelayVariations, safeRelayUrls } from "../relay"; import { isAddressPointerInList, isEventPointerInList, isProfilePointerInList } from "applesauce-lists/helpers"; diff --git a/src/hooks/use-count-community-members.ts b/src/hooks/use-count-community-members.ts deleted file mode 100644 index 20dc14144..000000000 --- a/src/hooks/use-count-community-members.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { kinds } from "nostr-tools"; - -import { getEventCoordinate } from "../helpers/nostr/event"; -import { NostrEvent } from "../types/nostr-event"; -import useEventCount from "./use-event-count"; - -export default function useCountCommunityMembers(community: NostrEvent) { - return useEventCount({ "#a": [getEventCoordinate(community)], kinds: [kinds.CommunitiesList] }); -} diff --git a/src/hooks/use-mute-word-filter.ts b/src/hooks/use-mute-word-filter.ts index b283973ea..fc01e2066 100644 --- a/src/hooks/use-mute-word-filter.ts +++ b/src/hooks/use-mute-word-filter.ts @@ -1,7 +1,7 @@ import { useCallback, useMemo } from "react"; import { NostrEvent } from "../types/nostr-event"; -import useAppSettings from "./use-app-settings"; +import useAppSettings from "./use-user-app-settings"; export default function useWordMuteFilter() { const { mutedWords } = useAppSettings(); diff --git a/src/hooks/use-open-graph-data.ts b/src/hooks/use-open-graph-data.ts index 7e85b74ef..acc0ebdf1 100644 --- a/src/hooks/use-open-graph-data.ts +++ b/src/hooks/use-open-graph-data.ts @@ -1,7 +1,7 @@ import { useAsync } from "react-use"; import { fetchWithProxy } from "../helpers/request"; import type { OgObjectInteral } from "../lib/open-graph-scraper/types"; -import useAppSettings from "./use-app-settings"; +import useAppSettings from "./use-user-app-settings"; const pageExtensions = [".html", ".php", "htm"]; diff --git a/src/hooks/use-set-color-mode.ts b/src/hooks/use-set-color-mode.ts index 4499b516f..be68df72c 100644 --- a/src/hooks/use-set-color-mode.ts +++ b/src/hooks/use-set-color-mode.ts @@ -1,7 +1,7 @@ import { useColorMode } from "@chakra-ui/react"; import { useSearchParams } from "react-router-dom"; import { useEffect } from "react"; -import useAppSettings from "./use-app-settings"; +import useAppSettings from "./use-user-app-settings"; export default function useSetColorMode() { const { setColorMode } = useColorMode(); diff --git a/src/hooks/use-textarea-upload-file.ts b/src/hooks/use-textarea-upload-file.ts index 52ff53306..ed3d9aeb4 100644 --- a/src/hooks/use-textarea-upload-file.ts +++ b/src/hooks/use-textarea-upload-file.ts @@ -5,7 +5,7 @@ import { nostrBuildUploadImage } from "../helpers/media-upload/nostr-build"; import { RefType } from "../components/magic-textarea"; import { useSigningContext } from "../providers/global/signing-provider"; import { UseFormGetValues, UseFormSetValue } from "react-hook-form"; -import useAppSettings from "./use-app-settings"; +import useAppSettings from "./use-user-app-settings"; import useUsersMediaServers from "./use-user-media-servers"; import { simpleMultiServerUpload } from "../helpers/media-upload/blossom"; import useCurrentAccount from "./use-current-account"; diff --git a/src/hooks/use-app-settings.ts b/src/hooks/use-user-app-settings.ts similarity index 83% rename from src/hooks/use-app-settings.ts rename to src/hooks/use-user-app-settings.ts index 6e34a129c..9848c7eaf 100644 --- a/src/hooks/use-app-settings.ts +++ b/src/hooks/use-user-app-settings.ts @@ -19,12 +19,19 @@ function buildAppSettingsEvent(settings: Partial): EventTemplate { }; } +export function useUserAppSettings(pubkey: string) { + useReplaceableEvent({ kind: APP_SETTINGS_KIND, pubkey, identifier: APP_SETTING_IDENTIFIER }); + return useStoreQuery(AppSettingsQuery, [pubkey]); +} + export default function useAppSettings() { const account = useCurrentAccount(); const publish = usePublishEvent(); // load synced settings - useReplaceableEvent(account?.pubkey && { kind: APP_SETTINGS_KIND, pubkey: account.pubkey }); + useReplaceableEvent( + account?.pubkey && { kind: APP_SETTINGS_KIND, pubkey: account.pubkey, identifier: APP_SETTING_IDENTIFIER }, + ); const localSettings = account?.localSettings; const syncedSettings = useStoreQuery(AppSettingsQuery, account && [account.pubkey]); diff --git a/src/hooks/use-user-communities-list.ts b/src/hooks/use-user-communities-list.ts deleted file mode 100644 index 1314332de..000000000 --- a/src/hooks/use-user-communities-list.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { kinds } from "nostr-tools"; -import { getAddressPointersFromList } from "applesauce-lists/helpers"; - -import { SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER } from "../helpers/nostr/communities"; -import { RequestOptions } from "../services/replaceable-events"; -import useCurrentAccount from "./use-current-account"; -import useReplaceableEvent from "./use-replaceable-event"; - -export default function useUserCommunitiesList(pubkey?: string, relays?: Iterable, opts?: RequestOptions) { - const account = useCurrentAccount(); - const key = pubkey ?? account?.pubkey; - - // TODO: remove at some future date when apps have transitioned to using k:10004 for communities - // https://github.com/nostr-protocol/nips/pull/880 - /** @deprecated */ - const oldList = useReplaceableEvent( - key - ? { - kind: kinds.Genericlists, - identifier: SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER, - pubkey: key, - } - : undefined, - [], - opts, - ); - const list = useReplaceableEvent(key ? { kind: kinds.CommunitiesList, pubkey: key } : undefined, relays, opts); - - let useList = list || oldList; - - // if both exist, use the newest one - if (list && oldList) { - useList = list.created_at > oldList.created_at ? list : oldList; - } - - const pointers = useList - ? getAddressPointersFromList(useList).filter((cord) => cord.kind === kinds.CommunityDefinition) - : []; - - return { list: useList, pointers }; -} diff --git a/src/providers/global/index.tsx b/src/providers/global/index.tsx index bc6431033..638e2fabf 100644 --- a/src/providers/global/index.tsx +++ b/src/providers/global/index.tsx @@ -4,7 +4,7 @@ import { QueryStoreProvider } from "applesauce-react/providers"; import { SigningProvider } from "./signing-provider"; import buildTheme from "../../theme"; -import useAppSettings from "../../hooks/use-app-settings"; +import useAppSettings from "../../hooks/use-user-app-settings"; import NotificationsProvider from "./notifications-provider"; import { UserEmojiProvider } from "./emoji-provider"; import BreakpointProvider from "./breakpoint-provider"; diff --git a/src/providers/route/invoice-modal-provider.tsx b/src/providers/route/invoice-modal-provider.tsx index ca913c179..cebb110f2 100644 --- a/src/providers/route/invoice-modal-provider.tsx +++ b/src/providers/route/invoice-modal-provider.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useContext, useState } from "react"; import InvoiceModal from "../../components/invoice-modal"; import createDefer, { Deferred } from "../../classes/deferred"; -import useAppSettings from "../../hooks/use-app-settings"; +import useAppSettings from "../../hooks/use-user-app-settings"; export type InvoiceModalContext = { requestPay: (invoice: string) => Promise; diff --git a/src/views/communities/components/community-card.tsx b/src/views/communities/components/community-card.tsx deleted file mode 100644 index fcdd1447d..000000000 --- a/src/views/communities/components/community-card.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { memo } from "react"; -import { nip19 } from "nostr-tools"; -import { Link as RouterLink } from "react-router-dom"; -import { - Card, - CardFooter, - CardHeader, - CardProps, - Heading, - LinkBox, - LinkOverlay, - Tag, - TagLabel, - TagLeftIcon, - Text, -} from "@chakra-ui/react"; - -import { NostrEvent } from "../../../types/nostr-event"; -import { getCommunityImage, getCommunityName } from "../../../helpers/nostr/communities"; -import UserAvatarLink from "../../../components/user/user-avatar-link"; -import UserLink from "../../../components/user/user-link"; -import useCountCommunityMembers from "../../../hooks/use-count-community-members"; -import { humanReadableSats } from "../../../helpers/lightning"; -import User01 from "../../../components/icons/user-01"; -import useReplaceableEvent from "../../../hooks/use-replaceable-event"; -import { AddressPointer } from "nostr-tools/nip19"; -import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref"; - -function CommunityCard({ community, ...props }: Omit & { community: NostrEvent }) { - const ref = useEventIntersectionRef(community); - - const name = getCommunityName(community); - const countMembers = useCountCommunityMembers(community); - - // NOTE: disabled because nostr.band has a rate limit - // const notesInLastMonth = useCountCommunityPosts(community); - - return ( - - - - - {name} - - - - {/* - - */} - - - by - - {countMembers !== undefined && countMembers > 0 && ( - - - {humanReadableSats(countMembers)} - - )} - - {/* {notesInLastMonth !== undefined && {notesInLastMonth} Posts in the past month} */} - - - ); -} - -export function PointerCommunityCard({ pointer, ...props }: Omit & { pointer: AddressPointer }) { - const community = useReplaceableEvent(pointer); - if (!community) return Loading {pointer.identifier}; - return ; -} - -export default memo(CommunityCard); diff --git a/src/views/communities/components/community-create-modal.tsx b/src/views/communities/components/community-create-modal.tsx deleted file mode 100644 index 4706e0d76..000000000 --- a/src/views/communities/components/community-create-modal.tsx +++ /dev/null @@ -1,350 +0,0 @@ -import { useCallback, useState } from "react"; -import { - Box, - Button, - Flex, - FormControl, - FormErrorMessage, - FormHelperText, - FormLabel, - IconButton, - IconButtonProps, - Input, - Link, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, - ModalOverlay, - ModalProps, - Text, - Textarea, - useToast, -} from "@chakra-ui/react"; -import { SubmitHandler, useForm } from "react-hook-form"; - -import useCurrentAccount from "../../../hooks/use-current-account"; -import UserAvatar from "../../../components/user/user-avatar"; -import UserLink from "../../../components/user/user-link"; -import { TrashIcon } from "../../../components/icons"; -import Upload01 from "../../../components/icons/upload-01"; -import { nostrBuildUploadImage } from "../../../helpers/media-upload/nostr-build"; -import { useSigningContext } from "../../../providers/global/signing-provider"; -import { RelayUrlInput } from "../../../components/relay-url-input"; -import { RelayFavicon } from "../../../components/relay-favicon"; -import UserAutocomplete from "../../../components/user-autocomplete"; -import { normalizeToHexPubkey } from "../../../helpers/nip19"; -import { safeUrl } from "../../../helpers/parse"; -import { safeRelayUrl } from "../../../helpers/relay"; - -function RemoveButton({ ...props }: IconButtonProps) { - return } size="sm" colorScheme="red" variant="ghost" ml="auto" {...props} />; -} - -export type FormValues = { - name: string; - banner: string; - description: string; - rules: string; - mods: string[]; - relays: string[]; - links: ([string] | [string, string])[]; - // ranking: string; -}; - -export default function CommunityCreateModal({ - isOpen, - onClose, - onSubmit, - defaultValues, - isUpdate, - ...props -}: Omit & { - onSubmit: SubmitHandler; - defaultValues?: FormValues; - isUpdate?: boolean; -}) { - const toast = useToast(); - const account = useCurrentAccount(); - const { requestSignature } = useSigningContext(); - - const { - register, - formState: { errors, isSubmitting }, - handleSubmit, - watch, - getValues, - setValue, - } = useForm({ - mode: "all", - defaultValues: defaultValues || { - name: "", - banner: "", - description: "", - rules: "", - mods: account ? [account.pubkey] : [], - relays: [], - links: [], - // ranking: "votes", - }, - }); - - watch("mods"); - // watch("ranking"); - watch("banner"); - watch("links"); - watch("relays"); - - const [uploading, setUploading] = useState(false); - const uploadFile = useCallback( - async (file: File) => { - try { - if (!(file.type.includes("image") || file.type.includes("video") || file.type.includes("audio"))) - throw new Error("Unsupported file type"); - - setUploading(true); - - const response = await nostrBuildUploadImage(file, requestSignature); - const imageUrl = response.url; - setValue("banner", imageUrl, { shouldDirty: true, shouldValidate: true }); - } catch (e) { - if (e instanceof Error) toast({ description: e.message, status: "error" }); - } - setUploading(false); - }, - [setValue, getValues, requestSignature, toast], - ); - - const [modInput, setModInput] = useState(""); - const addMod = () => { - if (!modInput) return; - const pubkey = normalizeToHexPubkey(modInput); - if (pubkey) { - setValue("mods", getValues("mods").concat(pubkey)); - } - setModInput(""); - }; - const removeMod = (pubkey: string) => { - setValue( - "mods", - getValues("mods").filter((p) => p !== pubkey), - ); - }; - - const [relayInput, setRelayInput] = useState(""); - const addRelay = () => { - if (!relayInput) return; - const url = safeRelayUrl(relayInput); - if (url) { - setValue("relays", getValues("relays").concat(url)); - } - setRelayInput(""); - }; - const removeRelay = (url: string) => { - setValue( - "relays", - getValues("relays").filter((r) => r !== url), - ); - }; - - const [linkInput, setLinkInput] = useState(""); - const [linkName, setLinkName] = useState(""); - const addLink = () => { - if (!linkInput) return; - const url = safeUrl(linkInput); - if (url) { - setValue("links", [...getValues("links"), linkName ? [url, linkName] : [url]]); - } - setLinkInput(""); - setLinkName(""); - }; - const removeLink = (url: string) => { - setValue( - "links", - getValues("links").filter(([r]) => r !== url), - ); - }; - - return ( - - - - {isUpdate ? "Update Community" : "Create Community"} - - - {!isUpdate && ( - - Community Name - { - if (/\p{Z}/iu.test(v)) return "Must not have spaces"; - return true; - }, - })} - isReadOnly={isUpdate} - autoComplete="off" - placeholder="more-cat-pictures" - /> - The name of your community (no-spaces) - {errors.name?.message && {errors.name?.message}} - - )} - - - Description -