mirror of
https://github.com/lumina-rocks/lumina.git
synced 2026-04-07 22:16:46 +02:00
WIP (unfollow visual not working)
This commit is contained in:
78
02.md
Normal file
78
02.md
Normal file
@@ -0,0 +1,78 @@
|
||||
NIP-02
|
||||
======
|
||||
|
||||
Follow List
|
||||
-----------
|
||||
|
||||
`final` `optional`
|
||||
|
||||
A special event with kind `3`, meaning "follow list" is defined as having a list of `p` tags, one for each of the followed/known profiles one is following.
|
||||
|
||||
Each tag entry should contain the key for the profile, a relay URL where events from that key can be found (can be set to an empty string if not needed), and a local name (or "petname") for that profile (can also be set to an empty string or not provided), i.e., `["p", <32-bytes hex key>, <main relay URL>, <petname>]`.
|
||||
|
||||
The `.content` is not used.
|
||||
|
||||
For example:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"kind": 3,
|
||||
"tags": [
|
||||
["p", "91cf9..4e5ca", "wss://alicerelay.com/", "alice"],
|
||||
["p", "14aeb..8dad4", "wss://bobrelay.com/nostr", "bob"],
|
||||
["p", "612ae..e610f", "ws://carolrelay.com/ws", "carol"]
|
||||
],
|
||||
"content": "",
|
||||
// other fields...
|
||||
}
|
||||
```
|
||||
|
||||
Every new following list that gets published overwrites the past ones, so it should contain all entries. Relays and clients SHOULD delete past following lists as soon as they receive a new one.
|
||||
|
||||
Whenever new follows are added to an existing list, clients SHOULD append them to the end of the list, so they are stored in chronological order.
|
||||
|
||||
## Uses
|
||||
|
||||
### Follow list backup
|
||||
|
||||
If one believes a relay will store their events for sufficient time, they can use this kind-3 event to backup their following list and recover on a different device.
|
||||
|
||||
### Profile discovery and context augmentation
|
||||
|
||||
A client may rely on the kind-3 event to display a list of followed people by profiles one is browsing; make lists of suggestions on who to follow based on the follow lists of other people one might be following or browsing; or show the data in other contexts.
|
||||
|
||||
### Relay sharing
|
||||
|
||||
A client may publish a follow list with good relays for each of their follows so other clients may use these to update their internal relay lists if needed, increasing censorship-resistance.
|
||||
|
||||
### Petname scheme
|
||||
|
||||
The data from these follow lists can be used by clients to construct local ["petname"](http://www.skyhunter.com/marcs/petnames/IntroPetNames.html) tables derived from other people's follow lists. This alleviates the need for global human-readable names. For example:
|
||||
|
||||
A user has an internal follow list that says
|
||||
|
||||
```json
|
||||
[
|
||||
["p", "21df6d143fb96c2ec9d63726bf9edc71", "", "erin"]
|
||||
]
|
||||
```
|
||||
|
||||
And receives two follow lists, one from `21df6d143fb96c2ec9d63726bf9edc71` that says
|
||||
|
||||
```json
|
||||
[
|
||||
["p", "a8bb3d884d5d90b413d9891fe4c4e46d", "", "david"]
|
||||
]
|
||||
```
|
||||
|
||||
and another from `a8bb3d884d5d90b413d9891fe4c4e46d` that says
|
||||
|
||||
```json
|
||||
[
|
||||
["p", "f57f54057d2a7af0efecc8b0b66f5708", "", "frank"]
|
||||
]
|
||||
```
|
||||
|
||||
When the user sees `21df6d143fb96c2ec9d63726bf9edc71` the client can show _erin_ instead;
|
||||
When the user sees `a8bb3d884d5d90b413d9891fe4c4e46d` the client can show _david.erin_ instead;
|
||||
When the user sees `f57f54057d2a7af0efecc8b0b66f5708` the client can show _frank.david.erin_ instead.
|
||||
@@ -1,10 +1,11 @@
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button } from './ui/button';
|
||||
import { useNostr, useNostrEvents } from 'nostr-react';
|
||||
import { finalizeEvent } from 'nostr-tools';
|
||||
import { finalizeEvent, NostrEvent } from 'nostr-tools';
|
||||
import { sign } from 'crypto';
|
||||
import { SignalMedium } from 'lucide-react';
|
||||
import { ReloadIcon } from '@radix-ui/react-icons';
|
||||
import { signEvent } from '@/utils/utils';
|
||||
|
||||
interface FollowButtonProps {
|
||||
pubkey: string;
|
||||
@@ -12,17 +13,10 @@ interface FollowButtonProps {
|
||||
}
|
||||
|
||||
const FollowButton: React.FC<FollowButtonProps> = ({ pubkey, userPubkey }) => {
|
||||
// const { publish } = useNostr();
|
||||
const [isFollowing, setIsFollowing] = useState(false);
|
||||
const { publish } = useNostr()
|
||||
|
||||
let storedPubkey: string | null = null;
|
||||
let storedNsec: string | null = null;
|
||||
let isLoggedIn = false;
|
||||
if (typeof window !== 'undefined') {
|
||||
storedPubkey = window.localStorage.getItem('pubkey');
|
||||
storedNsec = window.localStorage.getItem('nsec');
|
||||
isLoggedIn = storedPubkey !== null;
|
||||
}
|
||||
const [isFollowing, setIsFollowing] = useState(false);
|
||||
const loginType = typeof window !== "undefined" ? window.localStorage.getItem("loginType") : null
|
||||
|
||||
const { events, isLoading } = useNostrEvents({
|
||||
filter: {
|
||||
@@ -38,58 +32,60 @@ const FollowButton: React.FC<FollowButtonProps> = ({ pubkey, userPubkey }) => {
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (followingPubkeys.includes(pubkey)) {
|
||||
setIsFollowing(true);
|
||||
}
|
||||
}, [followingPubkeys, isFollowing, setIsFollowing]);
|
||||
// Reset the following state when receiving new data
|
||||
setIsFollowing(followingPubkeys.includes(pubkey));
|
||||
}, [followingPubkeys, pubkey]);
|
||||
|
||||
const handleFollow = async () => {
|
||||
// if (isLoggedIn) {
|
||||
if (userPubkey) {
|
||||
// Get a unique set of pubkeys to follow
|
||||
let uniqueFollows = new Set(followingPubkeys);
|
||||
|
||||
// Add or remove the target pubkey
|
||||
if (isFollowing) {
|
||||
uniqueFollows.delete(pubkey);
|
||||
} else {
|
||||
uniqueFollows.add(pubkey);
|
||||
}
|
||||
|
||||
// Convert to array and create properly formatted p tags
|
||||
const formattedTags = Array.from(uniqueFollows).map(pk => ["p", pk]);
|
||||
|
||||
// let eventTemplate = {
|
||||
// kind: 3,
|
||||
// created_at: Math.floor(Date.now() / 1000),
|
||||
// tags: [followingPubkeys],
|
||||
// content: '',
|
||||
// }
|
||||
let eventTemplate = {
|
||||
kind: 3,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: formattedTags,
|
||||
content: '',
|
||||
pubkey: '', // Placeholder
|
||||
id: '', // Placeholder
|
||||
sig: '', // Placeholder
|
||||
}
|
||||
|
||||
// console.log(eventTemplate);
|
||||
console.log('Sending event template:', eventTemplate);
|
||||
|
||||
// if (isFollowing) {
|
||||
// eventTemplate.tags = eventTemplate.tags.filter(tag => tag[1] !== pubkey);
|
||||
// } else {
|
||||
// eventTemplate.tags[0].push(pubkey);
|
||||
// }
|
||||
let signedEvent = null;
|
||||
signedEvent = await signEvent(loginType, eventTemplate) as NostrEvent;
|
||||
|
||||
// console.log(eventTemplate);
|
||||
|
||||
// let signedEvent = null;
|
||||
// if (storedNsec != null) {
|
||||
// // TODO: Sign Nostr Event with nsec
|
||||
// const nsecArray = storedNsec ? new TextEncoder().encode(storedNsec) : new Uint8Array();
|
||||
// signedEvent = finalizeEvent(eventTemplate, nsecArray);
|
||||
// console.log(signedEvent);
|
||||
// } else if (storedPubkey != null) {
|
||||
// // TODO: Request Extension to sign Nostr Event
|
||||
// console.log('Requesting Extension to sign Nostr Event..');
|
||||
// try {
|
||||
// signedEvent = await window.nostr.signEvent(eventTemplate);
|
||||
// } catch (error) {
|
||||
// console.error('Nostr Extension not found or aborted.');
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (signedEvent !== null) {
|
||||
// console.log(signedEvent);
|
||||
// publish(signedEvent);
|
||||
// setIsFollowing(!isFollowing);
|
||||
// }
|
||||
// }
|
||||
if (signedEvent !== null) {
|
||||
console.log('Publishing signed event:', signedEvent);
|
||||
publish(signedEvent);
|
||||
|
||||
// Update UI state immediately
|
||||
setIsFollowing(!isFollowing);
|
||||
|
||||
// Optionally, store this change in a local state if needed
|
||||
// This could help with rapid UI interactions before the relay responds
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button className='w-full' onClick={handleFollow} disabled={isLoading || !isLoggedIn}>
|
||||
{isFollowing ? 'Unfollow' : 'Follow'}
|
||||
<Button className='w-full' onClick={handleFollow} disabled={isLoading || !userPubkey}>
|
||||
{isLoading ? (
|
||||
<ReloadIcon className='animate-spin' />
|
||||
) : (
|
||||
isFollowing ? 'Unfollow' : 'Follow'
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user