Profile editor improvements
This commit is contained in:
parent
08112a0b3e
commit
f62c471848
@ -4,6 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
export const DefaultConnectTimeout = 1000;
|
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
|
* List of recommended follows for new users
|
||||||
*/
|
*/
|
||||||
|
@ -3,7 +3,9 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import EventKind from "../nostr/EventKind";
|
import EventKind from "../nostr/EventKind";
|
||||||
import { Subscriptions } from "../nostr/Subscriptions";
|
import { Subscriptions } from "../nostr/Subscriptions";
|
||||||
import { addNotifications, setFollows, setRelays } from "../state/Login";
|
import { addNotifications, setFollows, setRelays } from "../state/Login";
|
||||||
|
import { setUserData } from "../state/Users";
|
||||||
import useSubscription from "./Subscription";
|
import useSubscription from "./Subscription";
|
||||||
|
import { mapEventToProfile } from "./UsersFeed";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Managed loading data for the current logged in user
|
* Managed loading data for the current logged in user
|
||||||
@ -21,6 +23,7 @@ export default function useLoginFeed() {
|
|||||||
sub.Id = `login:${sub.Id}`;
|
sub.Id = `login:${sub.Id}`;
|
||||||
sub.Authors.add(pubKey);
|
sub.Authors.add(pubKey);
|
||||||
sub.Kinds.add(EventKind.ContactList);
|
sub.Kinds.add(EventKind.ContactList);
|
||||||
|
sub.Kinds.add(EventKind.SetMetadata);
|
||||||
|
|
||||||
let notifications = new Subscriptions();
|
let notifications = new Subscriptions();
|
||||||
notifications.Kinds.add(EventKind.TextNote);
|
notifications.Kinds.add(EventKind.TextNote);
|
||||||
@ -34,18 +37,20 @@ export default function useLoginFeed() {
|
|||||||
const { notes } = useSubscription(sub, { leaveOpen: true });
|
const { notes } = useSubscription(sub, { leaveOpen: true });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let metadatas = notes.filter(a => a.kind === EventKind.ContactList);
|
let contactList = notes.filter(a => a.kind === EventKind.ContactList);
|
||||||
let others = 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) {
|
for(let cl of contactList) {
|
||||||
if (md.content !== "") {
|
if (cl.content !== "") {
|
||||||
let relays = JSON.parse(md.content);
|
let relays = JSON.parse(cl.content);
|
||||||
dispatch(setRelays(relays));
|
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(setFollows(pTags));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(addNotifications(others));
|
dispatch(addNotifications(notifications));
|
||||||
|
dispatch(setUserData(metadata));
|
||||||
}, [notes]);
|
}, [notes]);
|
||||||
}
|
}
|
@ -5,10 +5,9 @@ import { addPubKey } from "../state/Users";
|
|||||||
export default function useProfile(pubKey) {
|
export default function useProfile(pubKey) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const user = useSelector(s => s.users.users[pubKey]);
|
const user = useSelector(s => s.users.users[pubKey]);
|
||||||
const pubKeys = useSelector(s => s.users.pubKeys);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pubKey !== "" && !pubKeys.includes(pubKey)) {
|
if (pubKey !== "") {
|
||||||
dispatch(addPubKey(pubKey));
|
dispatch(addPubKey(pubKey));
|
||||||
}
|
}
|
||||||
}, [pubKey]);
|
}, [pubKey]);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { ProfileCacheExpire } from "../Const";
|
||||||
import Event from "../nostr/Event";
|
import Event from "../nostr/Event";
|
||||||
import EventKind from "../nostr/EventKind";
|
import EventKind from "../nostr/EventKind";
|
||||||
import { Subscriptions } from "../nostr/Subscriptions";
|
import { Subscriptions } from "../nostr/Subscriptions";
|
||||||
@ -12,22 +13,11 @@ export default function useUsersCache() {
|
|||||||
const users = useSelector(s => s.users.users);
|
const users = useSelector(s => s.users.users);
|
||||||
|
|
||||||
function isUserCached(id) {
|
function isUserCached(id) {
|
||||||
let expire = new Date().getTime() - (1_000 * 60 * 5); // 5min expire
|
let expire = new Date().getTime() - ProfileCacheExpire;
|
||||||
let u = users[id];
|
let u = users[id];
|
||||||
return u && u.loaded > expire;
|
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(() => {
|
const sub = useMemo(() => {
|
||||||
let needProfiles = pKeys.filter(a => !isUserCached(a));
|
let needProfiles = pKeys.filter(a => !isUserCached(a));
|
||||||
if (needProfiles.length === 0) {
|
if (needProfiles.length === 0) {
|
||||||
@ -50,3 +40,13 @@ export default function useUsersCache() {
|
|||||||
|
|
||||||
return results;
|
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;
|
border-radius: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"], input[type="password"] {
|
input[type="text"], input[type="password"], textarea {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: 0;
|
border: 0;
|
||||||
@ -91,6 +91,15 @@ input[type="text"], input[type="password"] {
|
|||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.f-col {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-max {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
line-height: 1.3em;
|
line-height: 1.3em;
|
||||||
|
@ -29,6 +29,13 @@
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile .editor textarea {
|
||||||
|
resize: vertical;
|
||||||
|
width: calc(100% - 30px);
|
||||||
|
max-height: 300px;
|
||||||
|
min-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
@media(max-width: 720px) {
|
@media(max-width: 720px) {
|
||||||
.profile {
|
.profile {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -89,6 +89,15 @@ export default function ProfilePage() {
|
|||||||
};
|
};
|
||||||
delete userCopy["loaded"];
|
delete userCopy["loaded"];
|
||||||
delete userCopy["fromEvent"];
|
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
|
// trim empty string fields
|
||||||
Object.keys(userCopy).forEach(k => {
|
Object.keys(userCopy).forEach(k => {
|
||||||
@ -101,7 +110,6 @@ export default function ProfilePage() {
|
|||||||
let ev = await publisher.metadata(userCopy);
|
let ev = await publisher.metadata(userCopy);
|
||||||
console.debug(ev);
|
console.debug(ev);
|
||||||
publisher.broadcast(ev);
|
publisher.broadcast(ev);
|
||||||
dispatch(resetProfile(id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openFile() {
|
async function openFile() {
|
||||||
@ -128,17 +136,17 @@ export default function ProfilePage() {
|
|||||||
|
|
||||||
function editor() {
|
function editor() {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="editor">
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<div>Name:</div>
|
<div>Name:</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
|
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group f-col">
|
||||||
<div>About:</div>
|
<div>About:</div>
|
||||||
<div>
|
<div className="w-max">
|
||||||
<input type="text" value={about} onChange={(e) => setAbout(e.target.value)} />
|
<textarea onChange={(e) => setAbout(e.target.value)} value={about}></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
@ -167,7 +175,7 @@ export default function ProfilePage() {
|
|||||||
<div className="btn" onClick={() => saveProfile()}>Save</div>
|
<div className="btn" onClick={() => saveProfile()}>Save</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,10 @@ const UsersSlice = createSlice({
|
|||||||
for (let x of ud) {
|
for (let x of ud) {
|
||||||
let existing = state.users[x.pubkey];
|
let existing = state.users[x.pubkey];
|
||||||
if (existing) {
|
if (existing) {
|
||||||
|
if(existing.fromEvent.created_at > x.fromEvent.created_at) {
|
||||||
|
// prevent patching with older metadata
|
||||||
|
continue;
|
||||||
|
}
|
||||||
x = {
|
x = {
|
||||||
...existing,
|
...existing,
|
||||||
...x
|
...x
|
||||||
@ -58,7 +62,7 @@ const UsersSlice = createSlice({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetProfile: (state, action) => {
|
resetProfile: (state, action) => {
|
||||||
if(state.users[action.payload]) {
|
if (state.users[action.payload]) {
|
||||||
delete state.users[action.payload];
|
delete state.users[action.payload];
|
||||||
state.users = {
|
state.users = {
|
||||||
...state.users
|
...state.users
|
||||||
|
Loading…
Reference in New Issue
Block a user