diff --git a/src/db/User.ts b/src/db/User.ts new file mode 100644 index 00000000..a8fc7de4 --- /dev/null +++ b/src/db/User.ts @@ -0,0 +1,32 @@ +import { HexKey, TaggedRawEvent, UserMetadata } from "../nostr"; + +export interface MetadataCache extends UserMetadata { + /** + * When the object was saved in cache + */ + loaded: number, + + /** + * When the source metadata event was created + */ + created: number, + + /** + * The pubkey of the owner of this metadata + */ + pubkey: HexKey +}; + +export function mapEventToProfile(ev: TaggedRawEvent) { + try { + let data: UserMetadata = JSON.parse(ev.content); + return { + pubkey: ev.pubkey, + created: ev.created_at, + loaded: new Date().getTime(), + ...data + } as MetadataCache; + } catch (e) { + console.error("Failed to parse JSON", ev, e); + } +} \ No newline at end of file diff --git a/src/db.ts b/src/db/index.ts similarity index 87% rename from src/db.ts rename to src/db/index.ts index 630ddb73..d5734ccb 100644 --- a/src/db.ts +++ b/src/db/index.ts @@ -1,5 +1,6 @@ import Dexie, { Table } from 'dexie'; -import { MetadataCache } from './state/Users'; +import { MetadataCache } from './User'; + export class SnortDB extends Dexie { users!: Table; diff --git a/src/element/Nip5Service.tsx b/src/element/Nip5Service.tsx index 1612346b..9a64eef3 100644 --- a/src/element/Nip5Service.tsx +++ b/src/element/Nip5Service.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from "react"; -import { useDispatch, useSelector } from "react-redux"; +import { useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { ServiceProvider, @@ -15,14 +15,10 @@ import AsyncButton from "./AsyncButton"; import LNURLTip from "./LNURLTip"; // @ts-ignore import Copy from "./Copy"; -// @ts-ignore import useProfile from "../feed/ProfileFeed"; -// @ts-ignore import useEventPublisher from "../feed/EventPublisher"; -// @ts-ignore -import { resetProfile } from "../state/Users"; -// @ts-ignore import { hexToBech32 } from "../Util"; +import { UserMetadata } from "../nostr"; type Nip05ServiceProps = { name: string, @@ -35,10 +31,9 @@ type Nip05ServiceProps = { type ReduxStore = any; export default function Nip5Service(props: Nip05ServiceProps) { - const dispatch = useDispatch(); const navigate = useNavigate(); const pubkey = useSelector(s => s.login.publicKey); - const user: any = useProfile(pubkey); + const user = useProfile(pubkey); const publisher = useEventPublisher(); const svc = new ServiceProvider(props.service); const [serviceConfig, setServiceConfig] = useState(); @@ -71,11 +66,11 @@ export default function Nip5Service(props: Nip05ServiceProps) { setError(undefined); setAvailabilityResponse(undefined); if (handle && domain) { - if(handle.length < (domainConfig?.length[0] ?? 2)) { + if (handle.length < (domainConfig?.length[0] ?? 2)) { setAvailabilityResponse({ available: false, why: "TOO_SHORT" }); return; } - if(handle.length > (domainConfig?.length[1] ?? 20)) { + if (handle.length > (domainConfig?.length[1] ?? 20)) { setAvailabilityResponse({ available: false, why: "TOO_LONG" }); return; } @@ -149,17 +144,15 @@ export default function Nip5Service(props: Nip05ServiceProps) { } async function updateProfile(handle: string, domain: string) { - let newProfile = { - ...user, - nip05: `${handle}@${domain}` - }; - delete newProfile["loaded"]; - delete newProfile["fromEvent"]; - delete newProfile["pubkey"]; - let ev = await publisher.metadata(newProfile); - dispatch(resetProfile(pubkey)); - publisher.broadcast(ev); - navigate("/settings"); + if (user) { + let newProfile = { + ...user, + nip05: `${handle}@${domain}` + } as UserMetadata; + let ev = await publisher.metadata(newProfile); + publisher.broadcast(ev); + navigate("/settings"); + } } return ( diff --git a/src/element/Note.js b/src/element/Note.js index 080e88f7..c9f5425c 100644 --- a/src/element/Note.js +++ b/src/element/Note.js @@ -1,6 +1,5 @@ import "./Note.css"; -import { useCallback } from "react"; -import { useSelector } from "react-redux"; +import { useCallback, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import Event from "../nostr/Event"; @@ -10,15 +9,15 @@ import { eventLink, hexToBech32 } from "../Util"; import NoteFooter from "./NoteFooter"; import NoteTime from "./NoteTime"; import EventKind from "../nostr/EventKind"; +import useProfile from "../feed/ProfileFeed"; export default function Note(props) { const navigate = useNavigate(); - const opt = props.options; - const dataEvent = props["data-ev"]; - const { data, isThread, reactions, deletion, hightlight } = props + const { data, isThread, reactions, deletion, hightlight, options: opt, ["data-ev"]: parsedEvent } = props + const ev = useMemo(() => parsedEvent ?? new Event(data), [data]); + const pubKeys = useMemo(() => ev.Thread?.PubKeys || [], [ev]); - const users = useSelector(s => s.users?.users); - const ev = dataEvent ?? new Event(data); + const users = useProfile(pubKeys); const options = { showHeader: true, @@ -32,8 +31,8 @@ export default function Note(props) { if (deletion?.length > 0) { return (Deleted); } - return ; - }, [data, dataEvent, reactions, deletion]); + return ; + }, [props]); function goToEvent(e, id) { if (!window.location.pathname.startsWith("/e/")) { @@ -49,7 +48,7 @@ export default function Note(props) { const maxMentions = 2; let replyId = ev.Thread?.ReplyTo?.Event ?? ev.Thread?.Root?.Event; - let mentions = ev.Thread?.PubKeys?.map(a => [a, users[a]])?.map(a => (a[1]?.name?.length ?? 0) > 0 ? a[1].name : hexToBech32("npub", a[0]).substring(0, 12)) + let mentions = ev.Thread?.PubKeys?.map(a => [a, users ? users[a] : null])?.map(a => (a[1]?.name?.length ?? 0) > 0 ? a[1].name : hexToBech32("npub", a[0]).substring(0, 12)) .sort((a, b) => a.startsWith("npub") ? 1 : -1); let othersLength = mentions.length - maxMentions let pubMentions = mentions.length > maxMentions ? `${mentions?.slice(0, maxMentions).join(", ")} & ${othersLength} other${othersLength > 1 ? 's' : ''}` : mentions?.join(", "); diff --git a/src/element/NoteCreator.js b/src/element/NoteCreator.js index 9569b881..01a6d60b 100644 --- a/src/element/NoteCreator.js +++ b/src/element/NoteCreator.js @@ -1,5 +1,4 @@ -import { useState, Component } from "react"; -import { useSelector } from "react-redux"; +import { useState } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faPaperclip } from "@fortawesome/free-solid-svg-icons"; @@ -20,7 +19,6 @@ export function NoteCreator(props) { const [note, setNote] = useState(""); const [error, setError] = useState(""); const [active, setActive] = useState(false); - const users = useSelector((state) => state.users.users) async function sendNote() { let ev = replyTo ? await publisher.reply(replyTo, note) : await publisher.note(note); @@ -71,7 +69,6 @@ export function NoteCreator(props) {