@ -8,11 +8,12 @@ import NoteToSelf from "Element/NoteToSelf";
|
|||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import WriteMessage from "Element/WriteMessage";
|
import WriteMessage from "Element/WriteMessage";
|
||||||
import { Chat, ChatType, useChatSystem } from "chat";
|
import { Chat, ChatType, useChatSystem } from "chat";
|
||||||
|
import { Nip4ChatSystem } from "chat/nip4";
|
||||||
|
|
||||||
export default function DmWindow({ id }: { id: string }) {
|
export default function DmWindow({ id }: { id: string }) {
|
||||||
const pubKey = useLogin().publicKey;
|
const pubKey = useLogin().publicKey;
|
||||||
const dms = useChatSystem();
|
const dms = useChatSystem();
|
||||||
const chat = dms.find(a => a.id === id);
|
const chat = dms.find(a => a.id === id) ?? Nip4ChatSystem.createChatObj(id, []);
|
||||||
|
|
||||||
function sender() {
|
function sender() {
|
||||||
if (id === pubKey) {
|
if (id === pubKey) {
|
||||||
@ -24,7 +25,7 @@ export default function DmWindow({ id }: { id: string }) {
|
|||||||
if (chat?.profile) {
|
if (chat?.profile) {
|
||||||
return <ProfileImage pubkey={id} className="f-grow mb10" profile={chat.profile} />;
|
return <ProfileImage pubkey={id} className="f-grow mb10" profile={chat.profile} />;
|
||||||
}
|
}
|
||||||
return <ProfileImage pubkey={""} className="f-grow mb10" overrideUsername={chat?.id} />;
|
return <ProfileImage pubkey={id ?? ""} className="f-grow mb10" overrideUsername={chat?.id} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -34,7 +35,7 @@ export default function DmWindow({ id }: { id: string }) {
|
|||||||
<div className="flex f-col">{chat && <DmChatSelected chat={chat} />}</div>
|
<div className="flex f-col">{chat && <DmChatSelected chat={chat} />}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<WriteMessage chatId={id} />
|
<WriteMessage chat={chat} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -7,9 +7,9 @@ import useFileUpload from "Upload";
|
|||||||
import { openFile } from "SnortUtils";
|
import { openFile } from "SnortUtils";
|
||||||
import Textarea from "./Textarea";
|
import Textarea from "./Textarea";
|
||||||
import { System } from "index";
|
import { System } from "index";
|
||||||
import { useChatSystem } from "chat";
|
import { Chat } from "chat";
|
||||||
|
|
||||||
export default function WriteMessage({ chatId }: { chatId: string }) {
|
export default function WriteMessage({ chat }: { chat: Chat }) {
|
||||||
const [msg, setMsg] = useState("");
|
const [msg, setMsg] = useState("");
|
||||||
const [sending, setSending] = useState(false);
|
const [sending, setSending] = useState(false);
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
@ -17,7 +17,6 @@ export default function WriteMessage({ chatId }: { chatId: string }) {
|
|||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
const uploader = useFileUpload();
|
const uploader = useFileUpload();
|
||||||
const chat = useChatSystem().find(a => a.id === chatId);
|
|
||||||
|
|
||||||
async function attachFile() {
|
async function attachFile() {
|
||||||
try {
|
try {
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
.chat-page {
|
|
||||||
height: calc(100vh - 57px);
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
import DmWindow from "Element/DmWindow";
|
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { bech32ToHex } from "SnortUtils";
|
|
||||||
|
|
||||||
import "./ChatPage.css";
|
|
||||||
|
|
||||||
export default function ChatPage() {
|
|
||||||
const { id } = useParams();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="chat-page">
|
|
||||||
<DmWindow id={bech32ToHex(id ?? "")} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,12 +1,12 @@
|
|||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { NostrPrefix } from "@snort/system";
|
import { NostrPrefix } from "@snort/system";
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
|
|
||||||
import UnreadCount from "Element/UnreadCount";
|
import UnreadCount from "Element/UnreadCount";
|
||||||
import ProfileImage, { getDisplayName } from "Element/ProfileImage";
|
import ProfileImage, { getDisplayName } from "Element/ProfileImage";
|
||||||
import { hexToBech32 } from "SnortUtils";
|
import { hexToBech32, parseId } from "SnortUtils";
|
||||||
import NoteToSelf from "Element/NoteToSelf";
|
import NoteToSelf from "Element/NoteToSelf";
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
@ -29,7 +29,9 @@ export default function MessagesPage() {
|
|||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [chat, setChat] = useState<string>();
|
const { id } = useParams();
|
||||||
|
const parsedId = parseId(id ?? "");
|
||||||
|
const [chat, setChat] = useState(id ? parsedId : undefined);
|
||||||
const pageWidth = usePageWidth();
|
const pageWidth = usePageWidth();
|
||||||
|
|
||||||
const chats = useChatSystem();
|
const chats = useChatSystem();
|
||||||
|
@ -107,7 +107,7 @@ export function profileLink(hex: HexKey, relays?: Array<string> | string) {
|
|||||||
* Convert hex to bech32
|
* Convert hex to bech32
|
||||||
*/
|
*/
|
||||||
export function hexToBech32(hrp: string, hex?: string) {
|
export function hexToBech32(hrp: string, hex?: string) {
|
||||||
if (typeof hex !== "string" || hex.length === 0 || hex.length % 2 !== 0) {
|
if (typeof hex !== "string" || hex.length === 0 || hex.length % 2 !== 0 || !isHex(hex)) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,6 +173,14 @@ export function deepClone<T>(obj: T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isHex(s: string) {
|
||||||
|
// 48-57 = 0-9
|
||||||
|
// 65-90 = A-Z
|
||||||
|
// 97-122 = a-z
|
||||||
|
return [...s]
|
||||||
|
.map(v => v.charCodeAt(0))
|
||||||
|
.every(v => (v >= 48 && v <= 57) || (v >= 65 && v <= 90) || v >= 97 || v <= 122);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Simple debounce
|
* Simple debounce
|
||||||
*/
|
*/
|
||||||
|
@ -40,25 +40,29 @@ export class Nip4ChatSystem extends ExternalStore<Array<Chat>> implements ChatSy
|
|||||||
|
|
||||||
listChats(): Chat[] {
|
listChats(): Chat[] {
|
||||||
const myDms = this.#nip4Events();
|
const myDms = this.#nip4Events();
|
||||||
return dedupe(myDms.map(a => a.pubkey)).map(a => {
|
return dedupe(myDms.map(a => chatTo(a))).map(a => {
|
||||||
const messages = myDms.filter(b => chatTo(b) === a || b.pubkey === a);
|
const messages = myDms.filter(b => chatTo(b) === a || b.pubkey === a);
|
||||||
const last = lastReadInChat(a);
|
return Nip4ChatSystem.createChatObj(a, messages);
|
||||||
return {
|
|
||||||
type: ChatType.DirectMessage,
|
|
||||||
id: a,
|
|
||||||
unread: messages.reduce((acc, v) => (v.created_at > last ? acc++ : acc), 0),
|
|
||||||
lastMessage: messages.reduce((acc, v) => (v.created_at > acc ? v.created_at : acc), 0),
|
|
||||||
messages,
|
|
||||||
createMessage: (msg, pub) => {
|
|
||||||
return pub.sendDm(msg, a);
|
|
||||||
},
|
|
||||||
sendMessage: (ev: NostrEvent, system: SystemInterface) => {
|
|
||||||
system.BroadcastEvent(ev);
|
|
||||||
},
|
|
||||||
} as Chat;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static createChatObj(id: string, messages: Array<NostrEvent>) {
|
||||||
|
const last = lastReadInChat(id);
|
||||||
|
return {
|
||||||
|
type: ChatType.DirectMessage,
|
||||||
|
id,
|
||||||
|
unread: messages.reduce((acc, v) => (v.created_at > last ? acc++ : acc), 0),
|
||||||
|
lastMessage: messages.reduce((acc, v) => (v.created_at > acc ? v.created_at : acc), 0),
|
||||||
|
messages,
|
||||||
|
createMessage: (msg, pub) => {
|
||||||
|
return pub.sendDm(msg, id);
|
||||||
|
},
|
||||||
|
sendMessage: (ev: NostrEvent, system: SystemInterface) => {
|
||||||
|
system.BroadcastEvent(ev);
|
||||||
|
},
|
||||||
|
} as Chat;
|
||||||
|
}
|
||||||
|
|
||||||
#nip4Events() {
|
#nip4Events() {
|
||||||
return this.#cache.snapshot().filter(a => a.kind === EventKind.DirectMessage);
|
return this.#cache.snapshot().filter(a => a.kind === EventKind.DirectMessage);
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ import SettingsPage, { SettingsRoutes } from "Pages/SettingsPage";
|
|||||||
import ErrorPage from "Pages/ErrorPage";
|
import ErrorPage from "Pages/ErrorPage";
|
||||||
import VerificationPage from "Pages/Verification";
|
import VerificationPage from "Pages/Verification";
|
||||||
import MessagesPage from "Pages/MessagesPage";
|
import MessagesPage from "Pages/MessagesPage";
|
||||||
import ChatPage from "Pages/ChatPage";
|
|
||||||
import DonatePage from "Pages/DonatePage";
|
import DonatePage from "Pages/DonatePage";
|
||||||
import HashTagsPage from "Pages/HashTagsPage";
|
import HashTagsPage from "Pages/HashTagsPage";
|
||||||
import SearchPage from "Pages/SearchPage";
|
import SearchPage from "Pages/SearchPage";
|
||||||
@ -123,13 +122,9 @@ export const router = createBrowserRouter([
|
|||||||
element: <VerificationPage />,
|
element: <VerificationPage />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/messages",
|
path: "/messages/:id?",
|
||||||
element: <MessagesPage />,
|
element: <MessagesPage />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/messages/:id",
|
|
||||||
element: <ChatPage />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/donate",
|
path: "/donate",
|
||||||
element: <DonatePage />,
|
element: <DonatePage />,
|
||||||
|
@ -244,7 +244,9 @@ export class NostrSystem extends ExternalStore<SystemSnapshot> implements System
|
|||||||
*/
|
*/
|
||||||
BroadcastEvent(ev: NostrEvent) {
|
BroadcastEvent(ev: NostrEvent) {
|
||||||
for (const [, s] of this.#sockets) {
|
for (const [, s] of this.#sockets) {
|
||||||
s.SendEvent(ev);
|
if (!s.Ephemeral) {
|
||||||
|
s.SendEvent(ev);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user