Profile editor improvements
This commit is contained in:
parent
08112a0b3e
commit
f62c471848
@ -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
|
||||
*/
|
||||
|
@ -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]);
|
||||
}
|
@ -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]);
|
||||
|
@ -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) {
|
||||
@ -50,3 +40,13 @@ export default function useUsersCache() {
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
export function mapEventToProfile(ev) {
|
||||
let data = JSON.parse(ev.content);
|
||||
return {
|
||||
pubkey: ev.pubkey,
|
||||
fromEvent: ev,
|
||||
loaded: new Date().getTime(),
|
||||
...data
|
||||
};
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 (
|
||||
<>
|
||||
<div className="editor">
|
||||
<div className="form-group">
|
||||
<div>Name:</div>
|
||||
<div>
|
||||
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<div className="form-group f-col">
|
||||
<div>About:</div>
|
||||
<div>
|
||||
<input type="text" value={about} onChange={(e) => setAbout(e.target.value)} />
|
||||
<div className="w-max">
|
||||
<textarea onChange={(e) => setAbout(e.target.value)} value={about}></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
@ -167,7 +175,7 @@ export default function ProfilePage() {
|
||||
<div className="btn" onClick={() => saveProfile()}>Save</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,10 @@ const UsersSlice = createSlice({
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user