pfp cache

This commit is contained in:
smolgrrr 2024-08-12 18:36:07 +10:00
parent 7346368a2b
commit 6bea0bf8f1
8 changed files with 147 additions and 80 deletions

View File

@ -1,4 +1,4 @@
import { useEffect, useState, useCallback } from "react"; import { useEffect, useState, useCallback, useMemo } 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 { uniqBy } from "../utils/otherUtils"; // Assume getPow is a correct import now
import { subGlobalFeed } from "../utils/subscriptions"; import { subGlobalFeed } from "../utils/subscriptions";
@ -14,8 +14,36 @@ const useUniqEvents = () => {
const [events, setEvents] = useState<Event[]>([]); const [events, setEvents] = useState<Event[]>([]);
const age = Number(localStorage.getItem("age")) || 24; const age = Number(localStorage.getItem("age")) || 24;
// Load cached metadataEvents from localStorage
const [cachedMetadataEvents, setCachedMetadataEvents] = useState<Event[]>(
JSON.parse(localStorage.getItem("cachedMetadataEvents") || "[]")
);
useEffect(() => { useEffect(() => {
const onEvent = (event: Event) => setEvents((prevEvents) => [...prevEvents, event]); 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); const unsubscribe = subGlobalFeed(onEvent, age);
return unsubscribe; return unsubscribe;
@ -24,14 +52,18 @@ const useUniqEvents = () => {
const uniqEvents = uniqBy(events, "id"); const uniqEvents = uniqBy(events, "id");
const noteEvents = uniqEvents.filter(event => event.kind === 1 || event.kind === 6); const noteEvents = uniqEvents.filter(event => event.kind === 1 || event.kind === 6);
const metadataEvents = uniqEvents.filter(event => event.kind === 0); 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 }; return { noteEvents, metadataEvents };
}; };
const Home = () => { const Home = () => {
const filterDifficulty = localStorage.getItem("filterDifficulty") || DEFAULT_DIFFICULTY; const filterDifficulty = localStorage.getItem("filterDifficulty") || DEFAULT_DIFFICULTY;
const [sortByTime, setSortByTime] = useState<boolean>(localStorage.getItem('sortBy') !== 'false'); const [sortByTime, setSortByTime] = useState<boolean>(localStorage.getItem('sortBy') !== 'true');
const [setAnon, setSetAnon] = useState<boolean>(localStorage.getItem('anonMode') !== 'true'); const [setAnon, setSetAnon] = useState<boolean>(localStorage.getItem('anonMode') !== 'true');
const {noteEvents, metadataEvents } = useUniqEvents(); const {noteEvents, metadataEvents } = useUniqEvents();
const [delayedSort, setDelayedSort] = useState(false) const [delayedSort, setDelayedSort] = useState(false)
@ -54,7 +86,7 @@ const Home = () => {
let sortedEvents = [...postEvents] let sortedEvents = [...postEvents]
.sort((a, b) => .sort((a, b) =>
sortByTime ? b.created_at - a.created_at : verifyPow(b) - verifyPow(a) sortByTime ? verifyPow(b) - verifyPow(a) : b.created_at - a.created_at
) )
if (delayedSort) { if (delayedSort) {

View File

@ -1,68 +1,68 @@
import { getLinkPreview } from 'link-preview-js'; // import { getLinkPreview } from 'link-preview-js';
import { useState, useEffect } from 'react'; // import { useState, useEffect } from 'react';
const LinkModal = ({ url }: { url: string }) => { const LinkModal = ({ url }: { url: string }) => {
const [linkPreview, setLinkPreview] = useState<LinkPreview | null>(null); // const [linkPreview, setLinkPreview] = useState<LinkPreview | null>(null);
const [error, setError] = useState<string | null>(null); // const [error, setError] = useState<string | null>(null);
const fetchWithProxy = (url: string) => { // const fetchWithProxy = (url: string) => {
const proxyUrl = 'https://api.allorigins.win/raw?url='; // const proxyUrl = 'https://api.allorigins.win/raw?url=';
return getLinkPreview(proxyUrl + url) // return getLinkPreview(proxyUrl + url)
.then((preview) => setLinkPreview(preview as LinkPreview)) // .then((preview) => setLinkPreview(preview as LinkPreview))
.catch((error) => { // .catch((error) => {
console.error("Error fetching URL with proxy:", error); // console.error("Error fetching URL with proxy:", error);
setError('Unable to fetch URL with proxy.'); // setError('Unable to fetch URL with proxy.');
}); // });
}; // };
useEffect(() => { // useEffect(() => {
getLinkPreview(url) // getLinkPreview(url)
.then((preview) => setLinkPreview(preview as LinkPreview)) // .then((preview) => setLinkPreview(preview as LinkPreview))
.catch(error => { // .catch(error => {
console.error("Error fetching original URL, trying with proxy:", error); // console.error("Error fetching original URL, trying with proxy:", error);
setError('Error fetching original URL. Trying with proxy...'); // setError('Error fetching original URL. Trying with proxy...');
return fetchWithProxy(url); // return fetchWithProxy(url);
}); // });
}, [url]); // }, [url]);
if (error) { // if (error) {
// return <a className='hover:underline text-xs text-neutral-500' href={url}>{url}</a>; // or some loading state
// }
// if (!linkPreview) {
return <a className='hover:underline text-xs text-neutral-500' href={url}>{url}</a>; // or some loading state return <a className='hover:underline text-xs text-neutral-500' href={url}>{url}</a>; // or some loading state
} // }
if (!linkPreview) { // return (
return <a className='hover:underline text-xs text-neutral-500' href={url}>{url}</a>; // or some loading state // <div className="link-preview p-1 bg-neutral-800 rounded-lg border border-neutral-800">
} // <a href={linkPreview.url} target="_blank" rel="noopener noreferrer" className="">
// <img src={linkPreview.images[0]} alt={linkPreview.title} className="rounded-lg" />
return ( // <div className="font-semibold text-xs text-gray-300">
<div className="link-preview p-1 bg-neutral-800 rounded-lg border border-neutral-800"> // {linkPreview.title}
<a href={linkPreview.url} target="_blank" rel="noopener noreferrer" className=""> // </div>
<img src={linkPreview.images[0]} alt={linkPreview.title} className="rounded-lg" /> // </a>
<div className="font-semibold text-xs text-gray-300"> // </div>
{linkPreview.title} // );
</div>
</a>
</div>
);
}; };
interface LinkPreview { // interface LinkPreview {
url: string; // url: string;
title: string; // title: string;
siteName?: string; // siteName?: string;
description?: string; // description?: string;
mediaType: string; // mediaType: string;
contentType?: string; // contentType?: string;
images: string[]; // images: string[];
videos: { // videos: {
url?: string; // url?: string;
secureUrl?: string; // secureUrl?: string;
type?: string; // type?: string;
width?: string; // width?: string;
height?: string; // height?: string;
[key: string]: any; // [key: string]: any;
}[]; // }[];
[key: string]: any; // [key: string]: any;
} // }
export default LinkModal; export default LinkModal;

View File

@ -85,7 +85,7 @@ const ContentPreview = ({ key, eventdata }: { key: string; eventdata: Event }) =
{isExpanded ? "...Read less" : "...Read more"} {isExpanded ? "...Read less" : "...Read more"}
</button> </button>
)} )}
{/* {url !== "" && <LinkModal key={key} url={url} />} */} {url !== "" && <LinkModal key={key} url={url} />}
{quoteEvents[0] && quoteEvents.length > 0 && ( {quoteEvents[0] && quoteEvents.length > 0 && (
<a href={`/thread/${nip19.noteEncode(quoteEvents[0].id)}`}> <a href={`/thread/${nip19.noteEncode(quoteEvents[0].id)}`}>
<QuoteEmbed <QuoteEmbed

View File

@ -1,10 +1,10 @@
import CardContainer from "./CardContainer"; 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/otherUtils";
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";
import { verifyPow } from "../../utils/mine"; import { verifyPow } from "../../utils/mine";
import { uniqBy } from "../../utils/otherUtils"; import { uniqBy } from "../../utils/otherUtils";
@ -26,7 +26,7 @@ const PostCard = ({
repliedTo, repliedTo,
type type
}: CardProps) => { }: CardProps) => {
const { files } = parseContent(event); // const { files } = parseContent(event);
const icon = getIconFromHash(event.pubkey); const icon = getIconFromHash(event.pubkey);
const metadataParsed = metadata ? getMetadata(metadata) : null; const metadataParsed = metadata ? getMetadata(metadata) : null;

View File

@ -30,7 +30,7 @@ const OptionsBar: React.FC<OptionsBarProps> = ({ sortByTime, setAnon, toggleSort
<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 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>
<div className={`ml-2 text-neutral-500 text-sm ${sortByTime ? 'text-neutral-500' : ''}`}> <div className={`ml-2 text-neutral-500 text-sm ${sortByTime ? 'text-neutral-500' : ''}`}>
{sortByTime ? 'Time' : 'PoW'} {sortByTime ? 'PoW' : 'Time'}
</div> </div>
</label>} </label>}
{toggleAnon && <label htmlFor="toggleB" className="flex items-center cursor-pointer ml-4"> {/* Add margin-left here */} {toggleAnon && <label htmlFor="toggleB" className="flex items-center cursor-pointer ml-4"> {/* Add margin-left here */}

View File

@ -1,10 +1,10 @@
import CardContainer from "./CardContainer"; // import CardContainer from "./CardContainer";
import { CpuChipIcon, ArrowPathRoundedSquareIcon } from "@heroicons/react/24/outline"; import { CpuChipIcon } from "@heroicons/react/24/outline";
import { parseContent } from "../../utils/content"; // import { parseContent } from "../../utils/content";
import { Event as NostrEvent, nip19 } from "nostr-tools"; import { Event, nip19 } from "nostr-tools";
import { getMetadata, Metadata } from "../../utils/otherUtils"; import { getMetadata, Metadata } from "../../utils/otherUtils";
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";
import { verifyPow } from "../../utils/mine"; import { verifyPow } from "../../utils/mine";
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@ -13,7 +13,7 @@ import { useEffect, useState } from "react";
interface RepostProps { interface RepostProps {
key?: string | number; key?: string | number;
event: NostrEvent; event: Event;
} }
const RepostCard = ({ const RepostCard = ({
@ -21,15 +21,40 @@ const RepostCard = ({
event event
}: RepostProps) => { }: RepostProps) => {
const repostedEvent = JSON.parse(event.content); const repostedEvent = JSON.parse(event.content);
const { files } = parseContent(repostedEvent); // const { files } = parseContent(repostedEvent);
const icon = getIconFromHash(event.pubkey); const icon = getIconFromHash(event.pubkey);
const navigate = useNavigate(); const navigate = useNavigate();
const [cachedMetadataEvents, setCachedMetadataEvents] = useState<Event[]>(
JSON.parse(localStorage.getItem("cachedMetadataEvents") || "[]")
);
const [metadata, setMetadata] = useState<Metadata>() const [metadata, setMetadata] = useState<Metadata>()
// Define your callback function for subGlobalFeed // Define your callback function for subGlobalFeed
const onEvent = (event: NostrEvent, relay: string) => { const onEvent = (event: Event, relay: string) => {
if (event.kind === 0 && event.pubkey === repostedEvent.pubkey && metadata == null) { const existingEvent = cachedMetadataEvents.find((e) => e.pubkey === event.pubkey)
if (existingEvent) {
setMetadata(getMetadata(existingEvent))
}
else if (!existingEvent && event.kind === 0 && event.pubkey === repostedEvent.pubkey && metadata == null) {
setMetadata(getMetadata(event)) setMetadata(getMetadata(event))
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;
});
} }
}; };

View File

@ -4,7 +4,7 @@ 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 } from '@heroicons/react/24/outline'; import { DocumentTextIcon, FolderPlusIcon, DocumentDuplicateIcon, ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline';
import { getPow } from '../utils/mine'; 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';
@ -124,7 +124,7 @@ const Thread = () => {
))} ))}
{OPEvent && <PostCard event={OPEvent} metadata={getMetadataEvent(OPEvent)} replyCount={countReplies(OPEvent)} type={'OP'}/>} {OPEvent && <PostCard event={OPEvent} metadata={getMetadataEvent(OPEvent)} replyCount={countReplies(OPEvent)} type={'OP'}/>}
</div> </div>
<div className="col-span-full flex justify-center space-x-36 pb-4"> <div className="col-span-full flex justify-center space-x-16 pb-4">
<DocumentTextIcon <DocumentTextIcon
className="h-5 w-5 text-gray-200 cursor-pointer" className="h-5 w-5 text-gray-200 cursor-pointer"
onClick={() => { onClick={() => {
@ -148,6 +148,11 @@ const Thread = () => {
setShowRepost(false) setShowRepost(false)
}} }}
/> />
<a href={`nostr:${id}`} target="_blank" rel="noopener noreferrer">
<ArrowTopRightOnSquareIcon
className="h-5 w-5 text-gray-200 cursor-pointer"
/>
</a>
</div> </div>
{(showForm && postType) && {(showForm && postType) &&
<div className="w-full px-4 sm:px-0 sm:max-w-xl mx-auto my-2"> <div className="w-full px-4 sm:px-0 sm:max-w-xl mx-auto my-2">

View File

@ -36,7 +36,12 @@ export interface Metadata {
} }
export const getMetadata = (event: Event) => { export const getMetadata = (event: Event) => {
const content = event.content.replace(/[\n\r\t]/g, '') try {
const metadata: Metadata = JSON.parse(content) const content = event.content.replace(/[\n\r\t]/g, '')
return metadata const metadata: Metadata = JSON.parse(content)
return metadata
} catch (error) {
console.error(`Error parsing metadata for event: ${event.id}`, error)
return {}
}
} }