-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
127 changed files
with
2,117 additions
and
1,278 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"nostrudel": minor | ||
--- | ||
|
||
Add "DM Feed" tool |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"nostrudel": minor | ||
--- | ||
|
||
Thread view improvements |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"nostrudel": minor | ||
--- | ||
|
||
Add option to search communities in search view |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"nostrudel": minor | ||
--- | ||
|
||
Add "create $prism" link to lists |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"nostrudel": minor | ||
--- | ||
|
||
Add people list to search and hashtag views |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"nostrudel": patch | ||
--- | ||
|
||
Fix link cards breaking lines |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
18 | ||
20 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
# syntax=docker/dockerfile:1 | ||
FROM node:18 | ||
FROM node:20 | ||
WORKDIR /app | ||
COPY . /app/ | ||
ENV VITE_COMMIT_HASH="" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { useState } from "react"; | ||
import { Button, Card, CardBody, CardHeader, CloseButton, Flex, Heading, IconButton, useToast } from "@chakra-ui/react"; | ||
import { Kind } from "nostr-tools"; | ||
import dayjs from "dayjs"; | ||
import { useForm } from "react-hook-form"; | ||
|
||
import { ChevronDownIcon, ChevronUpIcon } from "../icons"; | ||
import UserName from "../user-name"; | ||
import MagicTextArea from "../magic-textarea"; | ||
import useTimelineLoader from "../../hooks/use-timeline-loader"; | ||
import useCurrentAccount from "../../hooks/use-current-account"; | ||
import { useReadRelayUrls, useWriteRelayUrls } from "../../hooks/use-client-relays"; | ||
import { useUserRelays } from "../../hooks/use-user-relays"; | ||
import { RelayMode } from "../../classes/relay"; | ||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback"; | ||
import IntersectionObserverProvider from "../../providers/intersection-observer"; | ||
import useSubject from "../../hooks/use-subject"; | ||
import Message from "../../views/messages/message"; | ||
import { LightboxProvider } from "../lightbox-provider"; | ||
import { useSigningContext } from "../../providers/signing-provider"; | ||
import { DraftNostrEvent } from "../../types/nostr-event"; | ||
import NostrPublishAction from "../../classes/nostr-publish-action"; | ||
import { correctContentMentions, createEmojiTags } from "../../helpers/nostr/post"; | ||
import { useContextEmojis } from "../../providers/emoji-provider"; | ||
|
||
export default function ChatWindow({ pubkey, onClose }: { pubkey: string; onClose: () => void }) { | ||
const toast = useToast(); | ||
const account = useCurrentAccount()!; | ||
const emojis = useContextEmojis(); | ||
const [expanded, setExpanded] = useState(true); | ||
|
||
const usersRelays = useUserRelays(pubkey); | ||
const readRelays = useReadRelayUrls(usersRelays.filter((c) => c.mode & RelayMode.WRITE).map((c) => c.url)); | ||
const writeRelays = useWriteRelayUrls(usersRelays.filter((c) => c.mode & RelayMode.WRITE).map((c) => c.url)); | ||
|
||
const timeline = useTimelineLoader(`${pubkey}-${account.pubkey}-messages`, readRelays, [ | ||
{ authors: [account.pubkey], kinds: [Kind.EncryptedDirectMessage], "#p": [pubkey] }, | ||
{ authors: [pubkey], kinds: [Kind.EncryptedDirectMessage], "#p": [account.pubkey] }, | ||
]); | ||
|
||
const { handleSubmit, getValues, setValue, formState, watch, reset } = useForm({ defaultValues: { content: "" } }); | ||
watch("content"); | ||
const { requestSignature, requestEncrypt } = useSigningContext(); | ||
const submit = handleSubmit(async (values) => { | ||
try { | ||
if (!values.content) return; | ||
let draft: DraftNostrEvent = { | ||
kind: Kind.EncryptedDirectMessage, | ||
content: values.content, | ||
tags: [["p", pubkey]], | ||
created_at: dayjs().unix(), | ||
}; | ||
|
||
draft = createEmojiTags(draft, emojis); | ||
draft.content = correctContentMentions(draft.content); | ||
|
||
// encrypt content | ||
draft.content = await requestEncrypt(draft.content, pubkey); | ||
|
||
const signed = await requestSignature(draft); | ||
const pub = new NostrPublishAction("Send DM", writeRelays, signed); | ||
|
||
reset(); | ||
} catch (e) { | ||
if (e instanceof Error) toast({ status: "error", description: e.message }); | ||
} | ||
}); | ||
|
||
const messages = useSubject(timeline.timeline); | ||
const callback = useTimelineCurserIntersectionCallback(timeline); | ||
|
||
return ( | ||
<Card size="sm" borderRadius="md" w={expanded ? "md" : "xs"} variant="outline"> | ||
<CardHeader display="flex" gap="2" alignItems="center"> | ||
<Heading size="md" mr="8"> | ||
<UserName pubkey={pubkey} /> | ||
</Heading> | ||
<IconButton | ||
aria-label="Toggle Window" | ||
onClick={() => setExpanded((v) => !v)} | ||
variant="ghost" | ||
icon={expanded ? <ChevronDownIcon /> : <ChevronUpIcon />} | ||
ml="auto" | ||
size="sm" | ||
/> | ||
<CloseButton onClick={onClose} /> | ||
</CardHeader> | ||
{expanded && ( | ||
<> | ||
<CardBody | ||
maxH="lg" | ||
overflowX="hidden" | ||
overflowY="auto" | ||
pt="0" | ||
display="flex" | ||
flexDirection="column-reverse" | ||
gap="2" | ||
> | ||
<LightboxProvider> | ||
<IntersectionObserverProvider callback={callback}> | ||
{messages.map((event) => ( | ||
<Message key={event.id} event={event} /> | ||
))} | ||
</IntersectionObserverProvider> | ||
</LightboxProvider> | ||
</CardBody> | ||
<Flex as="form" onSubmit={submit} gap="2"> | ||
<MagicTextArea | ||
isRequired | ||
value={getValues().content} | ||
onChange={(e) => setValue("content", e.target.value, { shouldDirty: true })} | ||
/> | ||
<Button type="submit" isLoading={formState.isSubmitting}> | ||
Send | ||
</Button> | ||
</Flex> | ||
</> | ||
)} | ||
</Card> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import { useEffect, useMemo, useState } from "react"; | ||
import { | ||
Alert, | ||
AlertIcon, | ||
Button, | ||
Card, | ||
CardBody, | ||
CardHeader, | ||
CloseButton, | ||
Heading, | ||
IconButton, | ||
Input, | ||
InputGroup, | ||
InputLeftElement, | ||
} from "@chakra-ui/react"; | ||
import dayjs from "dayjs"; | ||
|
||
import { ChevronDownIcon, ChevronUpIcon, SearchIcon } from "../icons"; | ||
import useSubject from "../../hooks/use-subject"; | ||
import directMessagesService from "../../services/direct-messages"; | ||
import UserAvatar from "../user-avatar"; | ||
import UserName from "../user-name"; | ||
|
||
export default function ContactsWindow({ | ||
onClose, | ||
onSelectPubkey, | ||
}: { | ||
onClose: () => void; | ||
onSelectPubkey: (pubkey: string) => void; | ||
}) { | ||
const [expanded, setExpanded] = useState(true); | ||
|
||
// TODO: find a better way to load recent contacts | ||
const [from, setFrom] = useState(() => dayjs().subtract(2, "days")); | ||
const conversations = useSubject(directMessagesService.conversations); | ||
useEffect(() => directMessagesService.loadDateRange(from), [from]); | ||
const sortedConversations = useMemo(() => { | ||
return Array.from(conversations).sort((a, b) => { | ||
const latestA = directMessagesService.getUserMessages(a).value[0]?.created_at ?? 0; | ||
const latestB = directMessagesService.getUserMessages(b).value[0]?.created_at ?? 0; | ||
|
||
return latestB - latestA; | ||
}); | ||
}, [conversations]); | ||
|
||
return ( | ||
<Card size="sm" borderRadius="md" minW={expanded ? "sm" : 0}> | ||
<CardHeader display="flex" gap="2" alignItems="center"> | ||
<Heading size="md" mr="8"> | ||
Contacts | ||
</Heading> | ||
<IconButton | ||
aria-label="Toggle Window" | ||
onClick={() => setExpanded((v) => !v)} | ||
variant="ghost" | ||
icon={expanded ? <ChevronDownIcon /> : <ChevronUpIcon />} | ||
ml="auto" | ||
size="sm" | ||
/> | ||
<CloseButton onClick={onClose} /> | ||
</CardHeader> | ||
{expanded && ( | ||
<CardBody maxH="lg" overflowX="hidden" overflowY="auto" pt="0" display="flex" flexDirection="column" gap="2"> | ||
<Alert status="warning"> | ||
<AlertIcon /> | ||
Work in progress! | ||
</Alert> | ||
{/* <InputGroup> | ||
<InputLeftElement pointerEvents="none"> | ||
<SearchIcon /> | ||
</InputLeftElement> | ||
<Input autoFocus /> | ||
</InputGroup> */} | ||
{sortedConversations.map((pubkey) => ( | ||
<Button | ||
key={pubkey} | ||
leftIcon={<UserAvatar pubkey={pubkey} size="sm" />} | ||
justifyContent="flex-start" | ||
p="2" | ||
variant="ghost" | ||
onClick={() => onSelectPubkey(pubkey)} | ||
> | ||
<UserName pubkey={pubkey} /> | ||
</Button> | ||
))} | ||
</CardBody> | ||
)} | ||
</Card> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { Flex, IconButton } from "@chakra-ui/react"; | ||
import { useCallback, useState } from "react"; | ||
import { useLocalStorage } from "react-use"; | ||
|
||
import ContactsWindow from "./contacts-window"; | ||
import { DirectMessagesIcon } from "../icons"; | ||
import ChatWindow from "./chat-window"; | ||
import useCurrentAccount from "../../hooks/use-current-account"; | ||
|
||
export default function ChatWindows() { | ||
const account = useCurrentAccount(); | ||
const [pubkeys, setPubkeys] = useState<string[]>([]); | ||
const [show, setShow] = useLocalStorage("show-chat-windows", false); | ||
|
||
const openPubkey = useCallback( | ||
(pubkey: string) => { | ||
setPubkeys((keys) => (keys.includes(pubkey) ? keys : keys.concat(pubkey))); | ||
}, | ||
[setPubkeys], | ||
); | ||
|
||
const closePubkey = useCallback( | ||
(pubkey: string) => { | ||
setPubkeys((keys) => keys.filter((key) => key !== pubkey)); | ||
}, | ||
[setPubkeys], | ||
); | ||
|
||
if (!account) { | ||
return null; | ||
} | ||
|
||
if (!show) { | ||
return ( | ||
<IconButton | ||
icon={<DirectMessagesIcon boxSize={6} />} | ||
aria-label="Show Contacts" | ||
onClick={() => setShow(true)} | ||
position="fixed" | ||
bottom="0" | ||
right="0" | ||
size="lg" | ||
zIndex={1} | ||
/> | ||
); | ||
} | ||
|
||
return ( | ||
<Flex direction="row-reverse" position="fixed" bottom="0" right="0" gap="4" alignItems="flex-end" zIndex={1}> | ||
<ContactsWindow onClose={() => setShow(false)} onSelectPubkey={openPubkey} /> | ||
{pubkeys.map((pubkey) => ( | ||
<ChatWindow key={pubkey} pubkey={pubkey} onClose={() => closePubkey(pubkey)} /> | ||
))} | ||
</Flex> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { Card, CardBody, CardHeader, CardProps, LinkBox, Spacer, Text } from "@chakra-ui/react"; | ||
|
||
import { NostrEvent } from "../../../types/nostr-event"; | ||
import { TrustProvider } from "../../../providers/trust"; | ||
import UserAvatarLink from "../../user-avatar-link"; | ||
import { UserLink } from "../../user-link"; | ||
import Timestamp from "../../timestamp"; | ||
import DecryptPlaceholder from "../../../views/messages/decrypt-placeholder"; | ||
import { MessageContent } from "../../../views/messages/message"; | ||
import { getMessageRecipient } from "../../../services/direct-messages"; | ||
import useCurrentAccount from "../../../hooks/use-current-account"; | ||
|
||
export default function EmbeddedDM({ dm, ...props }: Omit<CardProps, "children"> & { dm: NostrEvent }) { | ||
const account = useCurrentAccount(); | ||
const isOwnMessage = account?.pubkey === dm.pubkey; | ||
|
||
const sender = dm.pubkey; | ||
const receiver = getMessageRecipient(dm); | ||
|
||
if (!receiver) return "Broken DM"; | ||
|
||
return ( | ||
<TrustProvider event={dm}> | ||
<Card as={LinkBox} variant="outline" {...props}> | ||
<CardHeader display="flex" gap="2" p="2" alignItems="center"> | ||
<UserAvatarLink pubkey={sender} size="xs" /> | ||
<UserLink pubkey={sender} fontWeight="bold" isTruncated fontSize="lg" /> | ||
<Text mx="2">Messaged</Text> | ||
<UserAvatarLink pubkey={receiver} size="xs" /> | ||
<UserLink pubkey={receiver} fontWeight="bold" isTruncated fontSize="lg" /> | ||
<Timestamp timestamp={dm.created_at} /> | ||
</CardHeader> | ||
<CardBody px="2" pt="0" pb="2"> | ||
<DecryptPlaceholder data={dm.content} pubkey={isOwnMessage ? getMessageRecipient(dm) ?? "" : dm.pubkey}> | ||
{(text) => <MessageContent event={dm} text={text} />} | ||
</DecryptPlaceholder> | ||
</CardBody> | ||
</Card> | ||
</TrustProvider> | ||
); | ||
} |
Oops, something went wrong.