mirror of
https://github.com/smolgrrr/TAO.git
synced 2024-09-20 09:21:25 +00:00
start clean up
This commit is contained in:
parent
11628443cf
commit
9e28d9dba6
@ -1,14 +1,13 @@
|
|||||||
import "./App.css";
|
import "./styles/App.css";
|
||||||
import Home from "./components/Home";
|
import Home from "./components/routes/Home";
|
||||||
import Settings from "./components/Settings";
|
import Settings from "./components/routes/Settings";
|
||||||
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
|
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
|
||||||
import Thread from "./components/Thread";
|
import Thread from "./components/routes/Thread";
|
||||||
import Header from "./components/Header/Header";
|
import Header from "./components/modals/Header";
|
||||||
import AddToHomeScreenPrompt from "./components/Modals/CheckMobile/CheckMobile";
|
import AddToHomeScreenPrompt from "./components/modals/CheckMobile/CheckMobile";
|
||||||
import Notifications from "./components/Notifications";
|
import Notifications from "./components/routes/Notifications";
|
||||||
// import TestUI from "./components/TestUI";
|
import Hashtags from "./components/routes/Hashtags";
|
||||||
import Hashtags from "./components/Hashtags";
|
import HashtagPage from "./components/routes/HashtagPage";
|
||||||
import HashtagPage from "./components/HashtagPage";
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@ -21,7 +20,6 @@ function App() {
|
|||||||
<Route path="/notifications" element={<Notifications />} />
|
<Route path="/notifications" element={<Notifications />} />
|
||||||
<Route path="/hashtags" element={<Hashtags />} />
|
<Route path="/hashtags" element={<Hashtags />} />
|
||||||
<Route path="/hashtag/:id" element={<HashtagPage />} />
|
<Route path="/hashtag/:id" element={<HashtagPage />} />
|
||||||
{/* <Route path="/test" element={<TestUI />} /> */}
|
|
||||||
</Routes>
|
</Routes>
|
||||||
<AddToHomeScreenPrompt/>
|
<AddToHomeScreenPrompt/>
|
||||||
</Router>
|
</Router>
|
||||||
|
@ -8,10 +8,10 @@ import { XCircleIcon } from "@heroicons/react/24/solid";
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { UnsignedEvent, Event as NostrEvent, nip19 } from "nostr-tools";
|
import { UnsignedEvent, Event as NostrEvent, nip19 } from "nostr-tools";
|
||||||
import { renderMedia, attachFile } from "../../utils/FileUpload";
|
import { renderMedia, attachFile } from "../../utils/FileUpload";
|
||||||
import { EmojiPicker } from "./Emojis/emoji-picker";
|
import EmojiPicker from "@emoji-mart/react";
|
||||||
import customEmojis from './custom_emojis.json';
|
import customEmojis from './custom_emojis.json';
|
||||||
import { useSubmitForm } from "./handleSubmit";
|
import { useSubmitForm } from "./handleSubmit";
|
||||||
import "./Form.css";
|
import "../../styles/Form.css";
|
||||||
|
|
||||||
interface FormProps {
|
interface FormProps {
|
||||||
refEvent?: NostrEvent;
|
refEvent?: NostrEvent;
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { UnsignedEvent, Event as NostrEvent, nip19 } from "nostr-tools";
|
import { UnsignedEvent, Event as NostrEvent, nip19 } from "nostr-tools";
|
||||||
import { useSubmitForm } from "./handleSubmit";
|
import { useSubmitForm } from "./handleSubmit";
|
||||||
import "./Form.css";
|
import "../../styles/Form.css";
|
||||||
|
|
||||||
interface FormProps {
|
interface FormProps {
|
||||||
refEvent: NostrEvent;
|
refEvent: NostrEvent;
|
||||||
|
@ -1,114 +0,0 @@
|
|||||||
import { useEffect, useState, useCallback } from "react";
|
|
||||||
import PostCard from "./Modals/NoteCard";
|
|
||||||
import { uniqBy } from "../utils/otherUtils"; // Assume getPow is a correct import now
|
|
||||||
import { subHashtagFeed, subProfile} from "../utils/subscriptions";
|
|
||||||
import { verifyPow } from "../utils/mine";
|
|
||||||
import { Event, nip19 } from "nostr-tools";
|
|
||||||
import NewNoteCard from "./Forms/PostFormCard";
|
|
||||||
import RepostCard from "./Modals/RepostCard";
|
|
||||||
import OptionsBar from "./Modals/OptionsBar";
|
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
|
|
||||||
const DEFAULT_DIFFICULTY = 0;
|
|
||||||
|
|
||||||
const useUniqEvents = (hashtag: string) => {
|
|
||||||
const [events, setEvents] = useState<Event[]>([]);
|
|
||||||
const age = Number(localStorage.getItem("age")) || 24;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const onEvent = (event: Event) => setEvents((prevEvents) => [...prevEvents, event]);
|
|
||||||
console.log(events)
|
|
||||||
const unsubscribe = subHashtagFeed(hashtag, onEvent, age);
|
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
}, [hashtag]);
|
|
||||||
|
|
||||||
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 HashtagPage = () => {
|
|
||||||
const { id } = useParams();
|
|
||||||
const filterDifficulty = localStorage.getItem("filterHashtagDifficulty") || DEFAULT_DIFFICULTY;
|
|
||||||
const [sortByTime, setSortByTime] = useState<boolean>(localStorage.getItem('sortBy') !== 'true');
|
|
||||||
const [setAnon, setSetAnon] = useState<boolean>(localStorage.getItem('anonMode') !== 'false');
|
|
||||||
|
|
||||||
const {noteEvents, metadataEvents } = useUniqEvents(id as string);
|
|
||||||
|
|
||||||
const [delayedSort, setDelayedSort] = useState(false)
|
|
||||||
|
|
||||||
const postEvents: Event[] = noteEvents
|
|
||||||
.filter((event) =>
|
|
||||||
verifyPow(event) >= Number(filterDifficulty) &&
|
|
||||||
event.kind !== 0 &&
|
|
||||||
(event.kind !== 1 || !event.tags.some((tag) => tag[0] === "e"))
|
|
||||||
)
|
|
||||||
|
|
||||||
let sortedEvents = [...postEvents]
|
|
||||||
.sort((a, b) => {
|
|
||||||
// Sort by PoW in descending order
|
|
||||||
const powDiff = verifyPow(b) - verifyPow(a);
|
|
||||||
if (powDiff !== 0) return powDiff;
|
|
||||||
|
|
||||||
// If PoW is the same, sort by created_at in descending order
|
|
||||||
return b.created_at - a.created_at;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (delayedSort) {
|
|
||||||
sortedEvents = sortedEvents.filter(
|
|
||||||
!setAnon ? (e) => !metadataEvents.some((metadataEvent) => metadataEvent.pubkey === e.pubkey) : () => true
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
sortedEvents = sortedEvents.filter((e) => setAnon || e.tags.some((tag) => tag[0] === "client" && tag[1] === 'getwired.app'));
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<main className="text-white mb-20">
|
|
||||||
<div className="w-full px-4 sm:px-0 sm:max-w-xl mx-auto my-2">
|
|
||||||
<NewNoteCard hashtag={id as string}/>
|
|
||||||
</div>
|
|
||||||
<OptionsBar sortByTime={sortByTime} setAnon={setAnon} toggleSort={toggleSort} toggleAnon={toggleAnon} />
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4 p-4">
|
|
||||||
{sortedEvents.map((event) => (
|
|
||||||
event.kind === 1 ?
|
|
||||||
<PostCard
|
|
||||||
event={event}
|
|
||||||
metadata={metadataEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null}
|
|
||||||
replyCount={countReplies(event)}
|
|
||||||
/>
|
|
||||||
:
|
|
||||||
<RepostCard
|
|
||||||
event={event}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default HashtagPage;
|
|
@ -1,145 +0,0 @@
|
|||||||
import { useEffect, useState, useCallback, useMemo } 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";
|
|
||||||
|
|
||||||
const DEFAULT_DIFFICULTY = 20;
|
|
||||||
|
|
||||||
const useUniqEvents = () => {
|
|
||||||
const [events, setEvents] = useState<Event[]>([]);
|
|
||||||
const age = Number(localStorage.getItem("age")) || 24;
|
|
||||||
|
|
||||||
// Load cached metadataEvents from localStorage
|
|
||||||
const [cachedMetadataEvents, setCachedMetadataEvents] = useState<Event[]>(
|
|
||||||
JSON.parse(localStorage.getItem("cachedMetadataEvents") || "[]")
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const onEvent = (event: Event) => {
|
|
||||||
setEvents((prevEvents) => [...prevEvents, event]);
|
|
||||||
|
|
||||||
// If the new event is a metadata event, add it to the cached metadata events
|
|
||||||
if (event.kind === 0) {
|
|
||||||
setCachedMetadataEvents((prevMetadataEvents) => {
|
|
||||||
// Check if the event already exists in the cached metadata events
|
|
||||||
const existingEvent = prevMetadataEvents.find((e) => e.id === event.id || e.pubkey === event.pubkey)
|
|
||||||
if (!existingEvent) {
|
|
||||||
// If the event doesn't exist, add it to the cached metadata events
|
|
||||||
return [...prevMetadataEvents, event];
|
|
||||||
} else if (existingEvent && existingEvent.created_at < event.created_at) {
|
|
||||||
// Remove any existing metadata event with the same pubkey and id
|
|
||||||
const updatedMetadataEvents = prevMetadataEvents.filter(
|
|
||||||
(e) => e.id !== existingEvent.id
|
|
||||||
);
|
|
||||||
// Add the new metadata event
|
|
||||||
return [...updatedMetadataEvents, event];
|
|
||||||
}
|
|
||||||
// If the event already exists, return the previous cached metadata events
|
|
||||||
return prevMetadataEvents;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const unsubscribe = subGlobalFeed(onEvent, age);
|
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const uniqEvents = uniqBy(events, "id");
|
|
||||||
|
|
||||||
const noteEvents = uniqEvents.filter(event => event.kind === 1 || event.kind === 6);
|
|
||||||
const metadataEvents = [...cachedMetadataEvents, ...uniqEvents.filter(event => event.kind === 0)];
|
|
||||||
|
|
||||||
// Save the cached metadataEvents to localStorage
|
|
||||||
useEffect(() => {
|
|
||||||
localStorage.setItem("cachedMetadataEvents", JSON.stringify(cachedMetadataEvents));
|
|
||||||
}, [cachedMetadataEvents]);
|
|
||||||
return { noteEvents, metadataEvents };
|
|
||||||
};
|
|
||||||
|
|
||||||
const Home = () => {
|
|
||||||
const filterDifficulty = localStorage.getItem("filterDifficulty") || DEFAULT_DIFFICULTY;
|
|
||||||
const [sortByTime, setSortByTime] = useState<boolean>(localStorage.getItem('sortBy') !== 'true');
|
|
||||||
const [setAnon, setSetAnon] = useState<boolean>(localStorage.getItem('anonMode') !== 'true');
|
|
||||||
const {noteEvents, metadataEvents } = useUniqEvents();
|
|
||||||
const [delayedSort, setDelayedSort] = useState(false)
|
|
||||||
|
|
||||||
const postEvents: Event[] = noteEvents
|
|
||||||
.filter((event) =>
|
|
||||||
verifyPow(event) >= Number(filterDifficulty) &&
|
|
||||||
event.kind !== 0 &&
|
|
||||||
(event.kind !== 1 || !event.tags.some((tag) => tag[0] === "e" || tag[0] === "a"))
|
|
||||||
)
|
|
||||||
|
|
||||||
// Delayed filtering
|
|
||||||
useEffect(() => {
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
setDelayedSort(true);
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
let sortedEvents = [...postEvents]
|
|
||||||
.sort((a, b) =>
|
|
||||||
sortByTime ? verifyPow(b) - verifyPow(a) : b.created_at - a.created_at
|
|
||||||
)
|
|
||||||
|
|
||||||
if (delayedSort) {
|
|
||||||
sortedEvents = sortedEvents.filter(
|
|
||||||
!setAnon ? (e) => !metadataEvents.some((metadataEvent) => metadataEvent.pubkey === e.pubkey) : () => true
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
sortedEvents = sortedEvents.filter((e) => setAnon || e.tags.some((tag) => tag[0] === "client" && tag[1] === 'getwired.app'));
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<main className="text-white mb-20">
|
|
||||||
<div className="w-full px-4 sm:px-0 sm:max-w-xl mx-auto my-2">
|
|
||||||
<NewNoteCard />
|
|
||||||
</div>
|
|
||||||
<OptionsBar sortByTime={sortByTime} setAnon={setAnon} toggleSort={toggleSort} toggleAnon={toggleAnon} />
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4 p-4">
|
|
||||||
{sortedEvents.map((event) => (
|
|
||||||
event.kind === 1 ?
|
|
||||||
<PostCard
|
|
||||||
event={event}
|
|
||||||
metadata={metadataEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null}
|
|
||||||
replyCount={countReplies(event)}
|
|
||||||
/>
|
|
||||||
:
|
|
||||||
<RepostCard
|
|
||||||
event={event}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Home;
|
|
@ -1,6 +1,6 @@
|
|||||||
import { parseContent } from "../../../utils/content";
|
import { parseContent } from "../../../utils/content";
|
||||||
import { Event } from "nostr-tools";
|
import { Event } from "nostr-tools";
|
||||||
import { getMetadata } from "../../../utils/otherUtils";
|
import { getMetadata } from "../../../utils/getMetadata";
|
||||||
import ContentPreview from "./TextModal";
|
import ContentPreview from "./TextModal";
|
||||||
import { renderMedia } from "../../../utils/FileUpload";
|
import { renderMedia } from "../../../utils/FileUpload";
|
||||||
import { getIconFromHash, timeAgo } from "../../../utils/cardUtils";
|
import { getIconFromHash, timeAgo } from "../../../utils/cardUtils";
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import QuoteEmbed from "./QuoteEmbed";
|
|
||||||
import { Event } from "nostr-tools";
|
import { Event } from "nostr-tools";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { subNoteOnce } from "../../../utils/subscriptions";
|
import { subNoteOnce } from "../../../utils/subscriptions";
|
||||||
import { nip19 } from "nostr-tools";
|
import { nip19 } from "nostr-tools";
|
||||||
import LinkModal from "./LinkPreview";
|
|
||||||
import { parseContent } from "../../../utils/content";
|
import { parseContent } from "../../../utils/content";
|
||||||
|
import QuoteEmbed from "./QuoteEmbed";
|
||||||
|
import LinkModal from "./LinkPreview";
|
||||||
|
|
||||||
const RichText = ({ text, isExpanded, emojiMap }: { text: string; isExpanded: boolean; emojiMap: Record<string, any> }) => {
|
const RichText = ({ text, isExpanded, emojiMap }: { text: string; isExpanded: boolean; emojiMap: Record<string, any> }) => {
|
||||||
const content = isExpanded ? text.split('\n') : text.slice(0, 350).split('\n');
|
const content = isExpanded ? text.split('\n') : text.slice(0, 350).split('\n');
|
||||||
|
@ -2,7 +2,7 @@ import CardContainer from "./CardContainer";
|
|||||||
import { FolderIcon, CpuChipIcon } from "@heroicons/react/24/outline";
|
import { FolderIcon, CpuChipIcon } from "@heroicons/react/24/outline";
|
||||||
// import { parseContent } from "../../utils/content";
|
// import { parseContent } from "../../utils/content";
|
||||||
import { Event, nip19 } from "nostr-tools";
|
import { Event, nip19 } from "nostr-tools";
|
||||||
import { getMetadata } from "../../utils/otherUtils";
|
import { getMetadata } from "../../utils/getMetadata";
|
||||||
import ContentPreview from "./CardModals/TextModal";
|
import ContentPreview from "./CardModals/TextModal";
|
||||||
// import { renderMedia } from "../../utils/FileUpload";
|
// import { renderMedia } from "../../utils/FileUpload";
|
||||||
import { getIconFromHash, timeAgo } from "../../utils/cardUtils";
|
import { getIconFromHash, timeAgo } from "../../utils/cardUtils";
|
||||||
@ -13,7 +13,7 @@ interface CardProps {
|
|||||||
key?: string | number;
|
key?: string | number;
|
||||||
event: Event;
|
event: Event;
|
||||||
metadata: Event | null;
|
metadata: Event | null;
|
||||||
replyCount: number;
|
replies: Event[];
|
||||||
repliedTo?: Event[]
|
repliedTo?: Event[]
|
||||||
type?: 'OP' | 'Reply' | 'Post';
|
type?: 'OP' | 'Reply' | 'Post';
|
||||||
}
|
}
|
||||||
@ -22,7 +22,7 @@ const PostCard = ({
|
|||||||
key,
|
key,
|
||||||
event,
|
event,
|
||||||
metadata,
|
metadata,
|
||||||
replyCount,
|
replies,
|
||||||
repliedTo,
|
repliedTo,
|
||||||
type
|
type
|
||||||
}: CardProps) => {
|
}: CardProps) => {
|
||||||
@ -77,7 +77,7 @@ const PostCard = ({
|
|||||||
<span className="text-neutral-700">·</span>
|
<span className="text-neutral-700">·</span>
|
||||||
<div className="inline-flex items-center gap-1">
|
<div className="inline-flex items-center gap-1">
|
||||||
<FolderIcon className="h-4 w-4 text-neutral-600" />
|
<FolderIcon className="h-4 w-4 text-neutral-600" />
|
||||||
<span className="text-xs text-neutral-600">{replyCount}</span>
|
<span className="text-xs text-neutral-600">{replies.length}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
interface OptionsBarProps {
|
|
||||||
sortByTime?: boolean;
|
|
||||||
setAnon?: boolean;
|
|
||||||
toggleSort?: () => void;
|
|
||||||
toggleAnon?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const OptionsBar: React.FC<OptionsBarProps> = ({ sortByTime, setAnon, toggleSort, toggleAnon }) => {
|
|
||||||
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="px-8">
|
|
||||||
<span onClick={() => setShowAdvancedSettings(!showAdvancedSettings)} className="text-xs text-neutral-600">
|
|
||||||
{">"} Alter Feed
|
|
||||||
</span>
|
|
||||||
<div className={`transition-height duration-200 ease-in-out overflow-hidden ${showAdvancedSettings ? 'h-auto' : 'h-0'} flex w-full z-2`}>
|
|
||||||
{toggleSort && <label htmlFor="toggleA" className="flex items-center cursor-pointer">
|
|
||||||
<div className="relative">
|
|
||||||
<input
|
|
||||||
id="toggleA"
|
|
||||||
type="checkbox"
|
|
||||||
className="sr-only"
|
|
||||||
checked={sortByTime}
|
|
||||||
onChange={toggleSort}
|
|
||||||
/>
|
|
||||||
<div className="block bg-gray-600 w-8 h-4 rounded-full"></div>
|
|
||||||
<div className={`dot absolute left-1 top-0.5 bg-white w-3 h-3 rounded-full transition ${sortByTime ? 'transform translate-x-full bg-blue-400' : ''}`} ></div>
|
|
||||||
</div>
|
|
||||||
<div className={`ml-2 text-neutral-500 text-sm ${sortByTime ? 'text-neutral-500' : ''}`}>
|
|
||||||
{sortByTime ? 'Sort by Work' : 'Sort by Time'}
|
|
||||||
</div>
|
|
||||||
</label>}
|
|
||||||
{toggleAnon && <label htmlFor="toggleB" className="flex items-center cursor-pointer ml-4"> {/* Add margin-left here */}
|
|
||||||
<div className="relative">
|
|
||||||
<input
|
|
||||||
id="toggleB"
|
|
||||||
type="checkbox"
|
|
||||||
className="sr-only"
|
|
||||||
checked={setAnon}
|
|
||||||
onChange={toggleAnon}
|
|
||||||
/>
|
|
||||||
<div className="block bg-gray-600 w-8 h-4 rounded-full"></div>
|
|
||||||
<div className={`dot absolute left-1 top-0.5 bg-white w-3 h-3 rounded-full transition ${setAnon ? 'transform translate-x-full bg-blue-400' : ''}`} ></div>
|
|
||||||
</div>
|
|
||||||
<div className={`ml-2 text-neutral-500 text-sm ${setAnon ? 'text-neutral-500' : ''}`}>
|
|
||||||
{setAnon ? 'Namefags' : 'Anon'}
|
|
||||||
</div>
|
|
||||||
</label>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default OptionsBar;
|
|
@ -2,7 +2,7 @@
|
|||||||
import { CpuChipIcon } from "@heroicons/react/24/outline";
|
import { CpuChipIcon } from "@heroicons/react/24/outline";
|
||||||
// import { parseContent } from "../../utils/content";
|
// import { parseContent } from "../../utils/content";
|
||||||
import { Event, nip19 } from "nostr-tools";
|
import { Event, nip19 } from "nostr-tools";
|
||||||
import { getMetadata, Metadata } from "../../utils/otherUtils";
|
import { getMetadata, Metadata } from "../../utils/getMetadata";
|
||||||
import ContentPreview from "./CardModals/TextModal";
|
import ContentPreview from "./CardModals/TextModal";
|
||||||
// import { renderMedia } from "../../utils/FileUpload";
|
// import { renderMedia } from "../../utils/FileUpload";
|
||||||
import { getIconFromHash, timeAgo } from "../../utils/cardUtils";
|
import { getIconFromHash, timeAgo } from "../../utils/cardUtils";
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import Placeholder from "./Modals/Placeholder";
|
|
||||||
import OptionsBar from "./Modals/OptionsBar";
|
|
||||||
|
|
||||||
const TestUI = () => {
|
|
||||||
const [sortByTime, setSortByTime] = useState(true);
|
|
||||||
|
|
||||||
const toggleSort = () => {
|
|
||||||
setSortByTime(prev => !prev);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Placeholder />
|
|
||||||
<div className="col-span-full h-0.5 bg-neutral-900"/> {/* This is the white line separator */}
|
|
||||||
<OptionsBar sortByTime={sortByTime} toggleSort={toggleSort} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TestUI;
|
|
57
client/src/components/routes/HashtagPage.tsx
Normal file
57
client/src/components/routes/HashtagPage.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import PostCard from "../modals/NoteCard";
|
||||||
|
import { verifyPow } from "../../utils/mine";
|
||||||
|
import { Event } from "nostr-tools";
|
||||||
|
import NewNoteCard from "../forms/PostFormCard";
|
||||||
|
import RepostCard from "../modals/RepostCard";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { useUniqEvents } from "../../hooks/useUniqEvents";
|
||||||
|
|
||||||
|
const DEFAULT_DIFFICULTY = 0;
|
||||||
|
|
||||||
|
const HashtagPage = () => {
|
||||||
|
const { id } = useParams();
|
||||||
|
const filterDifficulty = localStorage.getItem("filterHashtagDifficulty") || DEFAULT_DIFFICULTY;
|
||||||
|
const { noteEvents, metadataEvents } = useUniqEvents(id as string, false);
|
||||||
|
|
||||||
|
const postEvents: Event[] = noteEvents
|
||||||
|
.filter((event) =>
|
||||||
|
verifyPow(event) >= Number(filterDifficulty) &&
|
||||||
|
event.kind !== 0 &&
|
||||||
|
(event.kind !== 1 || !event.tags.some((tag) => tag[0] === "e"))
|
||||||
|
)
|
||||||
|
|
||||||
|
let sortedEvents = [...postEvents]
|
||||||
|
.sort((a, b) => {
|
||||||
|
// Sort by PoW in descending order
|
||||||
|
const powDiff = verifyPow(b) - verifyPow(a);
|
||||||
|
if (powDiff !== 0) return powDiff;
|
||||||
|
|
||||||
|
// If PoW is the same, sort by created_at in descending order
|
||||||
|
return b.created_at - a.created_at;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render the component
|
||||||
|
return (
|
||||||
|
<main className="text-white mb-20">
|
||||||
|
<div className="w-full px-4 sm:px-0 sm:max-w-xl mx-auto my-2">
|
||||||
|
<NewNoteCard hashtag={id as string} />
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4 p-4">
|
||||||
|
{sortedEvents.map((event) => (
|
||||||
|
event.kind === 1 ?
|
||||||
|
<PostCard
|
||||||
|
event={event}
|
||||||
|
metadata={metadataEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null}
|
||||||
|
replies={sortedEvents.filter((e) => e.tags.some((tag) => tag[0] === "e" && tag[1] === event.id))}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
<RepostCard
|
||||||
|
event={event}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HashtagPage;
|
54
client/src/components/routes/Home.tsx
Normal file
54
client/src/components/routes/Home.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import PostCard from "../modals/NoteCard";
|
||||||
|
import { verifyPow } from "../../utils/mine";
|
||||||
|
import { Event } from "nostr-tools";
|
||||||
|
import NewNoteCard from "../forms/PostFormCard";
|
||||||
|
import RepostCard from "../modals/RepostCard";
|
||||||
|
import { DEFAULT_DIFFICULTY } from "../../config";
|
||||||
|
import { useUniqEvents } from "../../hooks/useUniqEvents";
|
||||||
|
|
||||||
|
const Home = () => {
|
||||||
|
const filterDifficulty = localStorage.getItem("filterDifficulty") || DEFAULT_DIFFICULTY;
|
||||||
|
const { noteEvents, metadataEvents } = useUniqEvents();
|
||||||
|
|
||||||
|
const postEvents: Event[] = noteEvents
|
||||||
|
.filter((event) =>
|
||||||
|
verifyPow(event) >= Number(filterDifficulty) &&
|
||||||
|
event.kind !== 0 &&
|
||||||
|
(event.kind !== 1 || !event.tags.some((tag) => tag[0] === "e" || tag[0] === "a"))
|
||||||
|
)
|
||||||
|
|
||||||
|
let sortedEvents = [...postEvents]
|
||||||
|
.sort((a, b) => {
|
||||||
|
// Sort by PoW in descending order
|
||||||
|
const powDiff = verifyPow(b) - verifyPow(a);
|
||||||
|
if (powDiff !== 0) return powDiff;
|
||||||
|
|
||||||
|
// If PoW is the same, sort by created_at in descending order
|
||||||
|
return b.created_at - a.created_at;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render the component
|
||||||
|
return (
|
||||||
|
<main className="text-white mb-20">
|
||||||
|
<div className="w-full px-4 sm:px-0 sm:max-w-xl mx-auto my-2">
|
||||||
|
<NewNoteCard />
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4 p-4">
|
||||||
|
{sortedEvents.map((event) => (
|
||||||
|
event.kind === 1 ?
|
||||||
|
<PostCard
|
||||||
|
event={event}
|
||||||
|
metadata={metadataEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null}
|
||||||
|
replies={sortedEvents.filter((e) => e.tags.some((tag) => tag[0] === "e" && tag[1] === event.id))}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
<RepostCard
|
||||||
|
event={event}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Home;
|
@ -1,39 +1,12 @@
|
|||||||
import { useEffect, useState, useCallback } from "react";
|
import { useState, useCallback } from "react";
|
||||||
import PostCard from "./Modals/NoteCard";
|
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 { Event } from "nostr-tools";
|
||||||
import NewNoteCard from "./Forms/PostFormCard";
|
import RepostCard from "../modals/RepostCard";
|
||||||
import RepostCard from "./Modals/RepostCard";
|
import { useUniqEvents } from "../../hooks/useUniqEvents";
|
||||||
import OptionsBar from "./Modals/OptionsBar";
|
|
||||||
import { subNotifications } from "../utils/subscriptions";
|
|
||||||
|
|
||||||
const useUniqEvents = () => {
|
|
||||||
const [events, setEvents] = useState<Event[]>([]);
|
|
||||||
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 Notifications = () => {
|
||||||
const [sortByTime, setSortByTime] = useState<boolean>(localStorage.getItem('sortBy') !== 'false');
|
|
||||||
const [setAnon, setSetAnon] = useState<boolean>(localStorage.getItem('anonMode') !== 'false');
|
|
||||||
const [notifsView, setNotifsView] = useState(false);
|
const [notifsView, setNotifsView] = useState(false);
|
||||||
const { noteEvents, metadataEvents } = useUniqEvents();
|
const { noteEvents, metadataEvents } = useUniqEvents(undefined,true);
|
||||||
const storedKeys = JSON.parse(localStorage.getItem('usedKeys') || '[]');
|
const storedKeys = JSON.parse(localStorage.getItem('usedKeys') || '[]');
|
||||||
const storedPubkeys = storedKeys.map((key: any[]) => key[1]);
|
const storedPubkeys = storedKeys.map((key: any[]) => key[1]);
|
||||||
|
|
||||||
@ -45,11 +18,8 @@ const Notifications = () => {
|
|||||||
|
|
||||||
const sortedEvents = [...postEvents]
|
const sortedEvents = [...postEvents]
|
||||||
.sort((a, b) =>
|
.sort((a, b) =>
|
||||||
sortByTime ? b.created_at - a.created_at : verifyPow(b) - verifyPow(a)
|
b.created_at - a.created_at
|
||||||
)
|
)
|
||||||
.filter(
|
|
||||||
!setAnon ? (e) => !metadataEvents.some((metadataEvent) => metadataEvent.pubkey === e.pubkey) : () => true
|
|
||||||
);
|
|
||||||
|
|
||||||
const mentions = noteEvents
|
const mentions = noteEvents
|
||||||
.filter((event) =>
|
.filter((event) =>
|
||||||
@ -59,40 +29,20 @@ const Notifications = () => {
|
|||||||
|
|
||||||
const sortedMentions = [...mentions]
|
const sortedMentions = [...mentions]
|
||||||
.sort((a, b) =>
|
.sort((a, b) =>
|
||||||
sortByTime ? b.created_at - a.created_at : verifyPow(b) - verifyPow(a)
|
b.created_at - a.created_at
|
||||||
)
|
)
|
||||||
.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 toggleNotifs = useCallback(() => {
|
const toggleNotifs = useCallback(() => {
|
||||||
setNotifsView(prev => !prev);
|
setNotifsView(prev => !prev);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const countReplies = (event: Event) => {
|
const countReplies = (event: Event) => {
|
||||||
return noteEvents.filter((e) => e.tags.some((tag) => tag[0] === "e" && tag[1] === event.id)).length;
|
return noteEvents.filter((e) => e.tags.some((tag) => tag[0] === "e" && tag[1] === event.id));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Render the component
|
// Render the component
|
||||||
return (
|
return (
|
||||||
<main className="text-white mb-20">
|
<main className="text-white mb-20">
|
||||||
<OptionsBar sortByTime={sortByTime} setAnon={setAnon} toggleSort={toggleSort} toggleAnon={toggleAnon} />
|
|
||||||
<div className="block sm:hidden">
|
<div className="block sm:hidden">
|
||||||
<label htmlFor="toggleC" className="p-4 flex items-center cursor-pointer">
|
<label htmlFor="toggleC" className="p-4 flex items-center cursor-pointer">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
@ -119,7 +69,7 @@ const Notifications = () => {
|
|||||||
<PostCard
|
<PostCard
|
||||||
event={event}
|
event={event}
|
||||||
metadata={metadataEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null}
|
metadata={metadataEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null}
|
||||||
replyCount={countReplies(event)}
|
replies={countReplies(event)}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
<RepostCard
|
<RepostCard
|
||||||
@ -134,7 +84,7 @@ const Notifications = () => {
|
|||||||
<PostCard
|
<PostCard
|
||||||
event={event}
|
event={event}
|
||||||
metadata={metadataEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null}
|
metadata={metadataEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null}
|
||||||
replyCount={countReplies(event)}
|
replies={countReplies(event)}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
<RepostCard
|
<RepostCard
|
@ -1,16 +1,14 @@
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Event, nip19 } from "nostr-tools"
|
import { Event, nip19 } from "nostr-tools"
|
||||||
import { subNote, subNotesOnce } from '../utils/subscriptions';
|
import { subNote, subNotesOnce } from '../../utils/subscriptions';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { uniqBy } from '../utils/otherUtils';
|
import { uniqBy } from '../../utils/otherUtils';
|
||||||
import { DocumentTextIcon, FolderPlusIcon, DocumentDuplicateIcon, ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline';
|
import { DocumentTextIcon, FolderPlusIcon, DocumentDuplicateIcon, ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline';
|
||||||
import { getPow } from '../utils/mine';
|
import PostCard from '../modals/NoteCard';
|
||||||
import PostCard from './Modals/NoteCard';
|
import Placeholder from '../modals/Placeholder';
|
||||||
import Placeholder from './Modals/Placeholder';
|
import NewNoteCard from '../forms/PostFormCard';
|
||||||
import NewNoteCard from './Forms/PostFormCard';
|
import RepostNote from '../forms/RepostNote';
|
||||||
import RepostNote from './Forms/RepostNote';
|
|
||||||
import OptionsBar from './Modals/OptionsBar';
|
|
||||||
|
|
||||||
type PostType = "" | "Reply" | "Quote" | undefined;
|
type PostType = "" | "Reply" | "Quote" | undefined;
|
||||||
|
|
||||||
@ -23,9 +21,8 @@ const Thread = () => {
|
|||||||
const [postType, setPostType] = useState<PostType>("");
|
const [postType, setPostType] = useState<PostType>("");
|
||||||
const [hasRun, setHasRun] = useState(false);
|
const [hasRun, setHasRun] = useState(false);
|
||||||
const [preOPEvents, setPreOPEvents] = useState(['']);
|
const [preOPEvents, setPreOPEvents] = useState(['']);
|
||||||
const [sortByTime, setSortByTime] = useState(true);
|
// const filterDifficulty = useState(localStorage.getItem("filterDifficulty") || "20");
|
||||||
const filterDifficulty = useState(localStorage.getItem("filterDifficulty") || "20");
|
// Load cached metadataEvents from localStorage
|
||||||
// Load cached metadataEvents from localStorage
|
|
||||||
const [cachedMetadataEvents, setCachedMetadataEvents] = useState<Event[]>(
|
const [cachedMetadataEvents, setCachedMetadataEvents] = useState<Event[]>(
|
||||||
JSON.parse(localStorage.getItem("cachedMetadataEvents") || "[]")
|
JSON.parse(localStorage.getItem("cachedMetadataEvents") || "[]")
|
||||||
);
|
);
|
||||||
@ -96,7 +93,7 @@ const Thread = () => {
|
|||||||
}, [uniqEvents, hasRun]);
|
}, [uniqEvents, hasRun]);
|
||||||
|
|
||||||
const countReplies = (event: Event) => {
|
const countReplies = (event: Event) => {
|
||||||
return uniqEvents.filter(e => e.tags.some(tag => tag[0] === 'e' && tag[1] === event.id)).length;
|
return uniqEvents.filter(e => e.tags.some(tag => tag[0] === 'e' && tag[1] === event.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
const repliedList = (event: Event): Event[] => {
|
const repliedList = (event: Event): Event[] => {
|
||||||
@ -109,34 +106,18 @@ const Thread = () => {
|
|||||||
preOPEvents.includes(event.id)
|
preOPEvents.includes(event.id)
|
||||||
).sort((a, b) => (b.created_at as any) - (a.created_at as any));
|
).sort((a, b) => (b.created_at as any) - (a.created_at as any));
|
||||||
|
|
||||||
const toggleSort = () => {
|
const displayedEvents = [...uniqEvents].slice(1)
|
||||||
setSortByTime(prev => !prev);
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventsSortedByTime = [...uniqEvents].slice(1)
|
|
||||||
.filter(event =>
|
.filter(event =>
|
||||||
event.kind === 1 &&
|
event.kind === 1 &&
|
||||||
!earlierEvents.map(e => e.id).includes(event.id) &&
|
!earlierEvents.map(e => e.id).includes(event.id) &&
|
||||||
(OPEvent ? OPEvent.id !== event.id : true)
|
(OPEvent ? OPEvent.id !== event.id : true)
|
||||||
).sort((a, b) => a.created_at - b.created_at);
|
).sort((a, b) => a.created_at - b.created_at);
|
||||||
|
|
||||||
// Events sorted by PoW (assuming `getPow` returns a numerical representation of the PoW)
|
|
||||||
const eventsSortedByPow = [...uniqEvents].slice(1)
|
|
||||||
.filter((event) =>
|
|
||||||
getPow(event.id) > Number(filterDifficulty) &&
|
|
||||||
event.kind === 1 &&
|
|
||||||
!earlierEvents.map(e => e.id).includes(event.id) &&
|
|
||||||
(OPEvent ? OPEvent.id !== event.id : true)
|
|
||||||
).sort((a, b) => getPow(b.id) - getPow(a.id));
|
|
||||||
|
|
||||||
const displayedEvents = sortByTime ? eventsSortedByTime : eventsSortedByPow;
|
|
||||||
|
|
||||||
if (uniqEvents.length === 0) {
|
if (uniqEvents.length === 0) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Placeholder />
|
<Placeholder />
|
||||||
<div className="col-span-full h-0.5 bg-neutral-900"/> {/* This is the white line separator */}
|
<div className="col-span-full h-0.5 bg-neutral-900"/> {/* This is the white line separator */}
|
||||||
<OptionsBar sortByTime={sortByTime} toggleSort={toggleSort} />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -147,9 +128,9 @@ const Thread = () => {
|
|||||||
{earlierEvents
|
{earlierEvents
|
||||||
.filter(event => event.kind === 1)
|
.filter(event => event.kind === 1)
|
||||||
.sort((a, b) => a.created_at - b.created_at).map((event, index) => (
|
.sort((a, b) => a.created_at - b.created_at).map((event, index) => (
|
||||||
<PostCard event={event} metadata={metadataEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null} replyCount={countReplies(event)} />
|
<PostCard event={event} metadata={metadataEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null} replies={countReplies(event)} />
|
||||||
))}
|
))}
|
||||||
{OPEvent && <PostCard event={OPEvent} metadata={metadataEvents.find((e) => e.pubkey === OPEvent.pubkey && e.kind === 0) || null} replyCount={countReplies(OPEvent)} type={'OP'}/>}
|
{OPEvent && <PostCard event={OPEvent} metadata={metadataEvents.find((e) => e.pubkey === OPEvent.pubkey && e.kind === 0) || null} replies={countReplies(OPEvent)} type={'OP'}/>}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-full flex justify-center space-x-16 pb-4">
|
<div className="col-span-full flex justify-center space-x-16 pb-4">
|
||||||
<DocumentTextIcon
|
<DocumentTextIcon
|
||||||
@ -195,10 +176,15 @@ const Thread = () => {
|
|||||||
<RepostNote refEvent={OPEvent}/>
|
<RepostNote refEvent={OPEvent}/>
|
||||||
</div>}
|
</div>}
|
||||||
<div className="col-span-full h-0.5 bg-neutral-900"/> {/* This is the white line separator */}
|
<div className="col-span-full h-0.5 bg-neutral-900"/> {/* This is the white line separator */}
|
||||||
<OptionsBar sortByTime={sortByTime} toggleSort={toggleSort} />
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
|
||||||
{displayedEvents.map((event, index) => (
|
{displayedEvents.map((event, index) => (
|
||||||
<PostCard key={index} event={event} metadata={metadataEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null} replyCount={countReplies(event)} repliedTo={repliedList(event)} />
|
<PostCard
|
||||||
|
key={index}
|
||||||
|
event={event}
|
||||||
|
metadata={metadataEvents.find((e) => e.pubkey === event.pubkey && e.kind === 0) || null}
|
||||||
|
replies={displayedEvents.filter((e) => e.tags.some((tag) => tag[0] === "e" && tag[1] === event.id))}
|
||||||
|
repliedTo={repliedList(event)}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
1
client/src/config.ts
Normal file
1
client/src/config.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const DEFAULT_DIFFICULTY = 20;
|
72
client/src/hooks/useUniqEvents.ts
Normal file
72
client/src/hooks/useUniqEvents.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { subGlobalFeed, subHashtagFeed, subNotifications } from "../utils/subscriptions";
|
||||||
|
import { uniqBy } from "../utils/otherUtils";
|
||||||
|
import { Event } from "nostr-tools";
|
||||||
|
import { } from "../utils/subscriptions";
|
||||||
|
|
||||||
|
export const useUniqEvents = (hashtag?: string, notifications?: boolean) => {
|
||||||
|
const [events, setEvents] = useState<Event[]>([]);
|
||||||
|
const age = Number(localStorage.getItem("age")) || 24;
|
||||||
|
|
||||||
|
// Load cached metadataEvents from localStorage
|
||||||
|
const [cachedMetadataEvents, setCachedMetadataEvents] = useState<Event[]>(
|
||||||
|
JSON.parse(localStorage.getItem("cachedMetadataEvents") || "[]")
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onEvent = (event: Event) => {
|
||||||
|
setEvents((prevEvents) => [...prevEvents, event]);
|
||||||
|
|
||||||
|
// If the new event is a metadata event, add it to the cached metadata events
|
||||||
|
if (event.kind === 0) {
|
||||||
|
setCachedMetadataEvents((prevMetadataEvents) => {
|
||||||
|
// Check if the event already exists in the cached metadata events
|
||||||
|
const existingEvent = prevMetadataEvents.find(
|
||||||
|
(e) => e.id === event.id || e.pubkey === event.pubkey
|
||||||
|
);
|
||||||
|
if (!existingEvent) {
|
||||||
|
// If the event doesn't exist, add it to the cached metadata events
|
||||||
|
return [...prevMetadataEvents, event];
|
||||||
|
} else if (existingEvent && existingEvent.created_at < event.created_at) {
|
||||||
|
// Remove any existing metadata event with the same pubkey and id
|
||||||
|
const updatedMetadataEvents = prevMetadataEvents.filter(
|
||||||
|
(e) => e.id !== existingEvent.id
|
||||||
|
);
|
||||||
|
// Add the new metadata event
|
||||||
|
return [...updatedMetadataEvents, event];
|
||||||
|
}
|
||||||
|
// If the event already exists, return the previous cached metadata events
|
||||||
|
return prevMetadataEvents;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let unsubscribe;
|
||||||
|
if (hashtag) {
|
||||||
|
// Code from the second function
|
||||||
|
unsubscribe = subHashtagFeed(hashtag, onEvent, age);
|
||||||
|
} else if (notifications) {
|
||||||
|
// Code from the third function
|
||||||
|
let storedKeys = JSON.parse(localStorage.getItem("usedKeys") || "[]");
|
||||||
|
let storedPubkeys = storedKeys.map((key: any[]) => key[1]);
|
||||||
|
unsubscribe = subNotifications(storedPubkeys, onEvent);
|
||||||
|
} else {
|
||||||
|
// Code from the first function
|
||||||
|
unsubscribe = subGlobalFeed(onEvent, age);
|
||||||
|
}
|
||||||
|
|
||||||
|
return unsubscribe;
|
||||||
|
}, [hashtag]);
|
||||||
|
|
||||||
|
const uniqEvents = uniqBy(events, "id");
|
||||||
|
|
||||||
|
const noteEvents = uniqEvents.filter((event) => event.kind === 1 || event.kind === 6);
|
||||||
|
const metadataEvents = [...cachedMetadataEvents, ...uniqEvents.filter((event) => event.kind === 0)];
|
||||||
|
|
||||||
|
// Save the cached metadataEvents to localStorage
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem("cachedMetadataEvents", JSON.stringify(cachedMetadataEvents));
|
||||||
|
}, [cachedMetadataEvents]);
|
||||||
|
|
||||||
|
return { noteEvents, metadataEvents };
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import './index.css';
|
import './styles/index.css';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
|
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
|
||||||
|
|
||||||
|
25
client/src/utils/getMetadata.ts
Normal file
25
client/src/utils/getMetadata.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Event } from "nostr-tools"
|
||||||
|
|
||||||
|
export interface Metadata {
|
||||||
|
name?: string
|
||||||
|
username?: string
|
||||||
|
display_name?: string
|
||||||
|
picture?: string
|
||||||
|
banner?: string
|
||||||
|
about?: string
|
||||||
|
website?: string
|
||||||
|
lud06?: string
|
||||||
|
lud16?: string
|
||||||
|
nip05?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getMetadata = (event: Event) => {
|
||||||
|
try {
|
||||||
|
const content = event.content.replace(/[\n\r\t]/g, '')
|
||||||
|
const metadata: Metadata = JSON.parse(content)
|
||||||
|
return metadata
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error parsing metadata for event: ${event.id}`, error)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,3 @@
|
|||||||
import { Event } from "nostr-tools"
|
|
||||||
|
|
||||||
export const uniqBy = <T>(arr: T[], key: keyof T): T[] => {
|
export const uniqBy = <T>(arr: T[], key: keyof T): T[] => {
|
||||||
return Object.values(
|
return Object.values(
|
||||||
arr.reduce(
|
arr.reduce(
|
||||||
@ -21,27 +19,3 @@ export const dateToUnix = (_date?: Date) => {
|
|||||||
|
|
||||||
return Math.floor(date.getTime() / 1000)
|
return Math.floor(date.getTime() / 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Metadata {
|
|
||||||
name?: string
|
|
||||||
username?: string
|
|
||||||
display_name?: string
|
|
||||||
picture?: string
|
|
||||||
banner?: string
|
|
||||||
about?: string
|
|
||||||
website?: string
|
|
||||||
lud06?: string
|
|
||||||
lud16?: string
|
|
||||||
nip05?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getMetadata = (event: Event) => {
|
|
||||||
try {
|
|
||||||
const content = event.content.replace(/[\n\r\t]/g, '')
|
|
||||||
const metadata: Metadata = JSON.parse(content)
|
|
||||||
return metadata
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error parsing metadata for event: ${event.id}`, error)
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user