diff --git a/src/Const.js b/src/Const.js index ac90743b..cc7c7ed5 100644 --- a/src/Const.js +++ b/src/Const.js @@ -4,6 +4,11 @@ */ export const DefaultConnectTimeout = 1000; +/** + * How long profile cache should be considered valid for + */ +export const ProfileCacheExpire = (1_000 * 60 * 5); + /** * List of recommended follows for new users */ diff --git a/src/feed/LoginFeed.js b/src/feed/LoginFeed.js index 916d72d5..efe10c44 100644 --- a/src/feed/LoginFeed.js +++ b/src/feed/LoginFeed.js @@ -3,7 +3,9 @@ import { useDispatch, useSelector } from "react-redux"; import EventKind from "../nostr/EventKind"; import { Subscriptions } from "../nostr/Subscriptions"; import { addNotifications, setFollows, setRelays } from "../state/Login"; +import { setUserData } from "../state/Users"; import useSubscription from "./Subscription"; +import { mapEventToProfile } from "./UsersFeed"; /** * Managed loading data for the current logged in user @@ -21,6 +23,7 @@ export default function useLoginFeed() { sub.Id = `login:${sub.Id}`; sub.Authors.add(pubKey); sub.Kinds.add(EventKind.ContactList); + sub.Kinds.add(EventKind.SetMetadata); let notifications = new Subscriptions(); notifications.Kinds.add(EventKind.TextNote); @@ -34,18 +37,20 @@ export default function useLoginFeed() { const { notes } = useSubscription(sub, { leaveOpen: true }); useEffect(() => { - let metadatas = notes.filter(a => a.kind === EventKind.ContactList); - let others = notes.filter(a => a.kind !== EventKind.ContactList); + let contactList = notes.filter(a => a.kind === EventKind.ContactList); + let notifications = notes.filter(a => a.kind === EventKind.TextNote); + let metadata = notes.filter(a => a.kind === EventKind.SetMetadata).map(a => mapEventToProfile(a)); - for(let md of metadatas) { - if (md.content !== "") { - let relays = JSON.parse(md.content); + for(let cl of contactList) { + if (cl.content !== "") { + let relays = JSON.parse(cl.content); dispatch(setRelays(relays)); } - let pTags = md.tags.filter(a => a[0] === "p").map(a => a[1]); + let pTags = cl.tags.filter(a => a[0] === "p").map(a => a[1]); dispatch(setFollows(pTags)); } - dispatch(addNotifications(others)); + dispatch(addNotifications(notifications)); + dispatch(setUserData(metadata)); }, [notes]); } \ No newline at end of file diff --git a/src/feed/ProfileFeed.js b/src/feed/ProfileFeed.js index 237b85b0..61922be0 100644 --- a/src/feed/ProfileFeed.js +++ b/src/feed/ProfileFeed.js @@ -5,10 +5,9 @@ import { addPubKey } from "../state/Users"; export default function useProfile(pubKey) { const dispatch = useDispatch(); const user = useSelector(s => s.users.users[pubKey]); - const pubKeys = useSelector(s => s.users.pubKeys); useEffect(() => { - if (pubKey !== "" && !pubKeys.includes(pubKey)) { + if (pubKey !== "") { dispatch(addPubKey(pubKey)); } }, [pubKey]); diff --git a/src/feed/UsersFeed.js b/src/feed/UsersFeed.js index a928609c..1e8349da 100644 --- a/src/feed/UsersFeed.js +++ b/src/feed/UsersFeed.js @@ -1,5 +1,6 @@ import { useEffect, useMemo } from "react"; import { useDispatch, useSelector } from "react-redux"; +import { ProfileCacheExpire } from "../Const"; import Event from "../nostr/Event"; import EventKind from "../nostr/EventKind"; import { Subscriptions } from "../nostr/Subscriptions"; @@ -12,22 +13,11 @@ export default function useUsersCache() { const users = useSelector(s => s.users.users); function isUserCached(id) { - let expire = new Date().getTime() - (1_000 * 60 * 5); // 5min expire + let expire = new Date().getTime() - ProfileCacheExpire; let u = users[id]; return u && u.loaded > expire; } - function mapEventToProfile(ev) { - let metaEvent = Event.FromObject(ev); - let data = JSON.parse(metaEvent.Content); - return { - pubkey: metaEvent.PubKey, - fromEvent: ev, - loaded: new Date().getTime(), - ...data - }; - } - const sub = useMemo(() => { let needProfiles = pKeys.filter(a => !isUserCached(a)); if (needProfiles.length === 0) { @@ -49,4 +39,14 @@ export default function useUsersCache() { }, [results]); return results; +} + +export function mapEventToProfile(ev) { + let data = JSON.parse(ev.content); + return { + pubkey: ev.pubkey, + fromEvent: ev, + loaded: new Date().getTime(), + ...data + }; } \ No newline at end of file diff --git a/src/index.css b/src/index.css index 62039705..b7210321 100644 --- a/src/index.css +++ b/src/index.css @@ -65,7 +65,7 @@ code { border-radius: 25px; } -input[type="text"], input[type="password"] { +input[type="text"], input[type="password"], textarea { padding: 10px; border-radius: 5px; border: 0; @@ -91,6 +91,15 @@ input[type="text"], input[type="password"] { padding: 0 15px; } +.f-col { + flex-direction: column; + align-items: flex-start !important; +} + +.w-max { + width: 100%; +} + a { color: inherit; line-height: 1.3em; diff --git a/src/pages/ProfilePage.css b/src/pages/ProfilePage.css index f4f429c2..e8014593 100644 --- a/src/pages/ProfilePage.css +++ b/src/pages/ProfilePage.css @@ -29,6 +29,13 @@ opacity: 0.5; } +.profile .editor textarea { + resize: vertical; + width: calc(100% - 30px); + max-height: 300px; + min-height: 60px; +} + @media(max-width: 720px) { .profile { flex-direction: column; diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index 1bed6e0e..1bcb5fa8 100644 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -89,6 +89,15 @@ export default function ProfilePage() { }; delete userCopy["loaded"]; delete userCopy["fromEvent"]; + // event top level props should not be copied into metadata (bug) + delete userCopy["pubkey"]; + delete userCopy["sig"]; + delete userCopy["pubkey"]; + delete userCopy["tags"]; + delete userCopy["content"]; + delete userCopy["created_at"]; + delete userCopy["id"]; + delete userCopy["kind"] // trim empty string fields Object.keys(userCopy).forEach(k => { @@ -101,7 +110,6 @@ export default function ProfilePage() { let ev = await publisher.metadata(userCopy); console.debug(ev); publisher.broadcast(ev); - dispatch(resetProfile(id)); } async function openFile() { @@ -128,17 +136,17 @@ export default function ProfilePage() { function editor() { return ( - <> +
Name:
setName(e.target.value)} />
-
+
About:
-
- setAbout(e.target.value)} /> +
+
@@ -167,7 +175,7 @@ export default function ProfilePage() {
saveProfile()}>Save
- +
) } diff --git a/src/state/Users.js b/src/state/Users.js index 93b7d074..a6a11e18 100644 --- a/src/state/Users.js +++ b/src/state/Users.js @@ -40,10 +40,14 @@ const UsersSlice = createSlice({ if (!Array.isArray(ud)) { ud = [ud]; } - + for (let x of ud) { let existing = state.users[x.pubkey]; if (existing) { + if(existing.fromEvent.created_at > x.fromEvent.created_at) { + // prevent patching with older metadata + continue; + } x = { ...existing, ...x @@ -58,7 +62,7 @@ const UsersSlice = createSlice({ } }, resetProfile: (state, action) => { - if(state.users[action.payload]) { + if (state.users[action.payload]) { delete state.users[action.payload]; state.users = { ...state.users