diff --git a/client/src/components/Forms/PostFormCard.tsx b/client/src/components/Forms/PostFormCard.tsx index e978d83..73a847a 100644 --- a/client/src/components/Forms/PostFormCard.tsx +++ b/client/src/components/Forms/PostFormCard.tsx @@ -45,10 +45,13 @@ const NewNoteCard = ({ useEffect(() => { if (refEvent && tagType && unsigned.tags.length === 0) { - const tags = tagMapping[tagType]; - if (tags) { - tags.forEach(tag => unsigned.tags.push([tag, refEvent[tag === 'p' ? 'pubkey' : 'id']])); - // Ref event should be the latest event they're replying to, and their event should include prev replies + if (tagType === 'Reply' && unsigned.tags.length === 0) { + unsigned.tags.push(['p', refEvent.pubkey]); + unsigned.tags.push(['e', refEvent.id, 'root']); + } else { + unsigned.tags = refEvent.tags + unsigned.tags.push(['p', refEvent.pubkey]); + unsigned.tags.push(['e', refEvent.id]); } if (tagType === 'Quote') { setComment(comment + '\nnostr:' + nip19.noteEncode(refEvent.id)); diff --git a/client/src/components/Home.tsx b/client/src/components/Home.tsx index 188bc04..2e19df0 100644 --- a/client/src/components/Home.tsx +++ b/client/src/components/Home.tsx @@ -6,6 +6,7 @@ import { verifyPow } from "../utils/mine"; import { Event } from "nostr-tools"; import NewNoteCard from "./Forms/PostFormCard"; import RepostCard from "./Modals/RepostCard"; +import OptionsBar from "./Modals/OptionsBar"; const DEFAULT_DIFFICULTY = 20; @@ -21,7 +22,7 @@ const useUniqEvents = () => { const uniqEvents = uniqBy(events, "id"); - const noteEvents = uniqEvents.filter(event => event.kind === 1); + const noteEvents = uniqEvents.filter(event => event.kind === 1 || event.kind === 6); const metadataEvents = uniqEvents.filter(event => event.kind === 0); return { noteEvents, metadataEvents }; @@ -74,40 +75,7 @@ const Home = () => {
-
- - -
+
{sortedEvents.map((event) => ( event.kind === 1 ? diff --git a/client/src/components/Modals/OptionsBar.tsx b/client/src/components/Modals/OptionsBar.tsx new file mode 100644 index 0000000..3c9b936 --- /dev/null +++ b/client/src/components/Modals/OptionsBar.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { useState } from 'react'; + +interface OptionsBarProps { + sortByTime?: boolean; + setAnon?: boolean; + toggleSort?: () => void; + toggleAnon?: () => void; +} + +const OptionsBar: React.FC = ({ sortByTime, setAnon, toggleSort, toggleAnon }) => { + const [showAdvancedSettings, setShowAdvancedSettings] = useState(false); + + return ( +
+ setShowAdvancedSettings(!showAdvancedSettings)} className="text-xs text-neutral-600"> + {">"} Alter Feed + +
+ {toggleSort && } + {toggleAnon && } +
+
+ ); +}; + +export default OptionsBar; \ No newline at end of file diff --git a/client/src/components/Notifications.tsx b/client/src/components/Notifications.tsx new file mode 100644 index 0000000..7fdc1c5 --- /dev/null +++ b/client/src/components/Notifications.tsx @@ -0,0 +1,93 @@ +import { useEffect, useState, useCallback } from "react"; +import PostCard from "./Modals/NoteCard"; +import { uniqBy } from "../utils/otherUtils"; // Assume getPow is a correct import now +import { subGlobalFeed } from "../utils/subscriptions"; +import { verifyPow } from "../utils/mine"; +import { Event } from "nostr-tools"; +import NewNoteCard from "./Forms/PostFormCard"; +import RepostCard from "./Modals/RepostCard"; +import OptionsBar from "./Modals/OptionsBar"; +import { subNotifications } from "../utils/subscriptions"; + +const useUniqEvents = () => { + const [events, setEvents] = useState([]); + let storedKeys = JSON.parse(localStorage.getItem('usedKeys') || '[]'); + let storedPubkeys = storedKeys.map((key: any[]) => key[1]); + + useEffect(() => { + const onEvent = (event: Event) => setEvents((prevEvents) => [...prevEvents, event]); + const unsubscribe = subNotifications(storedPubkeys, onEvent); + + return unsubscribe; + }, []); + + const uniqEvents = uniqBy(events, "id"); + + const noteEvents = uniqEvents.filter(event => event.kind === 1 || event.kind === 6); + const metadataEvents = uniqEvents.filter(event => event.kind === 0); + + return { noteEvents, metadataEvents }; +}; + +const Notifications = () => { + const [sortByTime, setSortByTime] = useState(localStorage.getItem('sortBy') !== 'false'); + const [setAnon, setSetAnon] = useState(localStorage.getItem('anonMode') !== 'false'); + const { noteEvents, metadataEvents } = useUniqEvents(); + + const postEvents = noteEvents + .filter((event) => + event.kind !== 0 && + (event.kind !== 1) + ) + + const sortedEvents = [...postEvents] + .sort((a, b) => + sortByTime ? b.created_at - a.created_at : verifyPow(b) - verifyPow(a) + ) + .filter( + !setAnon ? (e) => !metadataEvents.some((metadataEvent) => metadataEvent.pubkey === e.pubkey) : () => true + ); + + const toggleSort = useCallback(() => { + setSortByTime(prev => { + const newValue = !prev; + localStorage.setItem('sortBy', String(newValue)); + return newValue; + }); + }, []); + + const toggleAnon = useCallback(() => { + setSetAnon(prev => { + const newValue = !prev; + localStorage.setItem('anonMode', String(newValue)); + return newValue; + }); + }, []); + + const countReplies = (event: Event) => { + return noteEvents.filter((e) => e.tags.some((tag) => tag[0] === "e" && tag[1] === event.id)).length; + }; + + // Render the component + return ( +
+ +
+ {sortedEvents.map((event) => ( + event.kind === 1 ? + e.pubkey === event.pubkey && e.kind === 0) || null} + replyCount={countReplies(event)} + /> + : + + ))} +
+
+ ); +}; + +export default Notifications; diff --git a/client/src/components/Thread.tsx b/client/src/components/Thread.tsx index d4d17fa..7a4b3ac 100644 --- a/client/src/components/Thread.tsx +++ b/client/src/components/Thread.tsx @@ -10,6 +10,7 @@ import PostCard from './Modals/NoteCard'; import Placeholder from './Modals/Placeholder'; import NewNoteCard from './Forms/PostFormCard'; import RepostNote from './Forms/RepostNote'; +import OptionsBar from './Modals/OptionsBar'; type PostType = "" | "Reply" | "Quote" | undefined; @@ -49,9 +50,10 @@ const Thread = () => { useEffect(() => { if (!hasRun && events.length > 0) { - let OPEvent = uniqEvents[0]; + let OPEvent = uniqEvents.find(event => event.id === hexID); setOPEvent(OPEvent); + console.log(OPEvent) if (OPEvent && OPEvent.id !== hexID) { OPEvent = events.find(e => e.id === hexID) as Event; } @@ -126,7 +128,7 @@ const Thread = () => { ))} {OPEvent && }
-
+
{ @@ -153,36 +155,20 @@ const Thread = () => {
{(showForm && postType) &&
- {postType}-post - +
+ {postType}-post +
+
} - {showRepost &&
- Repost note - + {showRepost && OPEvent &&
+
+ Repost note +
+
} -
- -
+
{/* This is the white line separator */} +
-
{/* This is the white line separator */} {displayedEvents.map((event, index) => ( ))} diff --git a/client/src/utils/subscriptions.ts b/client/src/utils/subscriptions.ts index a8477b4..437098b 100644 --- a/client/src/utils/subscriptions.ts +++ b/client/src/utils/subscriptions.ts @@ -29,15 +29,6 @@ export const subGlobalFeed = (onEvent: SubCallback) => { unsub: true }); - // // New Callback to only add events that pass the PoW requirement - // const powFilteredCallback = (evt: Event, relay: string) => { - // if (getPow(evt.id) > 2) { // Replace '5' with your actual PoW requirement - // pubkeys.add(evt.pubkey); - // notes.add(evt.id); - // onEvent(evt, relay); - // } - // }; - setTimeout(() => { // get profile info sub({ @@ -91,20 +82,6 @@ export const subGlobalFeed = (onEvent: SubCallback) => { }); }; -/** subscribe to global feed */ -export const simpleSub24hFeed = (onEvent: SubCallback) => { - unsubAll(); - sub({ - cb: onEvent, - filter: { - kinds: [1], - //until: Math.floor(Date.now() * 0.001), - since: Math.floor((Date.now() * 0.001) - (24 * 60 * 60)), - limit: 1, - } - }); -}; - /** subscribe to a note id (nip-19) */ export const subNote = ( eventId: string, @@ -236,6 +213,40 @@ export const subNotesOnce = ( }, 2000); }; +// /** quick subscribe to a note id (nip-19) */ +// export const subNotifications = ( +// pubkeys: string[], +// onEvent: SubCallback, +// ) => { +// const replyPubkeys = new Set(); +// sub({ +// cb: (evt, relay) => { +// replyPubkeys.add(evt.pubkey); +// onEvent(evt, relay); +// }, +// filter: { +// "#p": pubkeys, +// kinds: [1], +// limit: 50, +// }, +// unsub: true, +// }); + +// setTimeout(() => { +// // get profile info +// sub({ +// cb: onEvent, +// filter: { +// authors: Array.from(replyPubkeys), +// kinds: [0], +// limit: replyPubkeys.size, +// }, +// unsub: true, +// }); +// replyPubkeys.clear(); +// }, 2000); +// }; + /** quick subscribe to a note id (nip-19) */ export const subNotifications = ( pubkeys: string[], @@ -243,12 +254,9 @@ export const subNotifications = ( ) => { const replyPubkeys = new Set(); sub({ - cb: (evt, relay) => { - replyPubkeys.add(evt.pubkey); - onEvent(evt, relay); - }, + cb: onEvent, filter: { - "#p": pubkeys, + authors: Array.from(pubkeys), kinds: [1], limit: 50, }, @@ -270,3 +278,68 @@ export const subNotifications = ( }, 2000); }; +// const hasEventTag = (tag: string[]) => tag[0] === 'e'; +// const isReply = ([tag, , , marker]: string[]) => tag === 'e' && marker !== 'mention'; + +// export const getReplyTo = (evt: Event): string | null => { +// const eventTags = evt.tags.filter(isReply); +// const withReplyMarker = eventTags.filter(([, , , marker]) => marker === 'reply'); +// if (withReplyMarker.length === 1) { +// return withReplyMarker[0][1]; +// } +// const withRootMarker = eventTags.filter(([, , , marker]) => marker === 'root'); +// if (withReplyMarker.length === 0 && withRootMarker.length === 1) { +// return withRootMarker[0][1]; +// } +// // fallback to deprecated positional 'e' tags (nip-10) +// const lastTag = eventTags.at(-1); +// return lastTag ? lastTag[1] : null; +// }; + +// export const subNotifications = ( +// pubkeys: string[], +// onEvent: SubCallback, +// ) => { +// const authorsPrefixes = pubkeys.map(pubkey => pubkey.slice(0, 32)); +// console.info(`subscribe to homefeed ${authorsPrefixes}`); +// unsubAll(); + +// const repliesTo = new Set(); +// sub({ +// cb: (evt, relay) => { +// if ( +// evt.tags.some(hasEventTag) +// ) { +// const note = getReplyTo(evt); // get all reply to events instead? +// if (note && !repliesTo.has(note)) { +// repliesTo.add(note); +// subOnce({ +// cb: onEvent, +// filter: { +// ids: [note], +// kinds: [1], +// limit: 1, +// }, +// relay, +// }); +// } +// } +// onEvent(evt, relay); +// }, +// filter: { +// authors: authorsPrefixes, +// kinds: [1], +// limit: 20, +// }, +// }); +// // get metadata +// sub({ +// cb: onEvent, +// filter: { +// authors: pubkeys, +// kinds: [0], +// limit: pubkeys.length, +// }, +// unsub: true, +// }); +// };