diff --git a/client/src/App.tsx b/client/src/App.tsx index 3f2fef7..0a7a4e6 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -5,7 +5,6 @@ import Settings from './components/Settings'; import SwipeableViews from 'react-swipeable-views'; import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; import Thread from './components/Thread/Thread'; -import { useState, useEffect } from 'react'; function App() { const [index, setIndex] = React.useState(1); diff --git a/client/src/components/Home.tsx b/client/src/components/Home.tsx index 89547d2..0f444d1 100644 --- a/client/src/components/Home.tsx +++ b/client/src/components/Home.tsx @@ -8,6 +8,7 @@ import { uniqBy } from '../utils/utils'; const Home = () => { const [events, setEvents] = useState([]); + const [filterDifficulty, setFilterDifficulty] = useState(localStorage.getItem('filterDifficulty') || '12'); const onEvent = (event: Event) => { setEvents((prevEvents) => [...prevEvents, event]); @@ -16,15 +17,27 @@ const Home = () => { useEffect(() => { subGlobalFeed(onEvent); // If you eventually need a cleanup function, put it here + + const handleDifficultyChange = (event: any) => { + const customEvent = event as CustomEvent; + const { difficulty, filterDifficulty } = customEvent.detail; + setFilterDifficulty(filterDifficulty); + }; + + window.addEventListener('difficultyChanged', handleDifficultyChange); + + return () => { + window.removeEventListener('difficultyChanged', handleDifficultyChange); + }; }, []); const uniqEvents = events.length > 0 ? uniqBy(events, "id") : []; const filteredAndSortedEvents = uniqEvents .filter(event => - getPow(event.id) > 5 && + getPow(event.id) > Math.ceil(Number(filterDifficulty)/4) && event.kind === 1 && - !event.tags.some(tag => tag[0] === 'p') + !event.tags.some(tag => tag[0] === 'e') ) .sort((a, b) => (b.created_at as any) - (a.created_at as any)); diff --git a/client/src/components/Modals/TextModal.tsx b/client/src/components/Modals/TextModal.tsx index 6110058..3fe93dd 100644 --- a/client/src/components/Modals/TextModal.tsx +++ b/client/src/components/Modals/TextModal.tsx @@ -17,11 +17,11 @@ const ContentPreview = ({ key, comment }: { key: string, comment: string }) => { }; useEffect(() => { - // const findUrl = comment.match(/\bhttps?:\/\/\S+/gi); - // if (findUrl && findUrl.length > 0) { - // setUrl(findUrl[0]) - // setFinalComment(finalComment.replace(findUrl[0], '').trim()) - // } + const findUrl = comment.match(/\bhttps?:\/\/\S+/gi); + if (findUrl && findUrl.length > 0) { + setUrl(findUrl[0]) + setFinalComment(finalComment.replace(findUrl[0], '').trim()) + } const match = comment.match(/\bnostr:([a-z0-9]+)/i); const nostrQuoteID = match && match[1]; diff --git a/client/src/components/PostCard/NewThreadCard.tsx b/client/src/components/PostCard/NewThreadCard.tsx index 32d334e..ecc9ac1 100644 --- a/client/src/components/PostCard/NewThreadCard.tsx +++ b/client/src/components/PostCard/NewThreadCard.tsx @@ -2,59 +2,73 @@ import CardContainer from './CardContainer'; import { ArrowUpTrayIcon, CpuChipIcon } from '@heroicons/react/24/outline'; import { useState, useEffect, useMemo } from 'react'; import { generatePrivateKey, getPublicKey, finishEvent } from 'nostr-tools'; -import { minePow } from '../../utils/mine'; import { publish } from '../../utils/relays'; import NostrImg from '../../utils/ImgUpload'; -const difficulty = 20 - const NewThreadCard: React.FC = () => { const [comment, setComment] = useState(""); const [file, setFile] = useState(""); - const [sk, setSk] = useState(generatePrivateKey()); - + const [sk, setSk] = useState(generatePrivateKey()); + const [difficulty, setDifficulty] = useState(localStorage.getItem('difficulty') || '21'); + + const [messageFromWorker, setMessageFromWorker] = useState(null); - // Initialize the worker outside of any effects + const [doingWorkProp, setDoingWorkProp] = useState(false); + // Initialize the worker outside of any effects const worker = useMemo(() => new Worker(new URL('../../powWorker', import.meta.url)), []); useEffect(() => { worker.onmessage = (event) => { setMessageFromWorker(event.data); }; + + const handleDifficultyChange = (event: Event) => { + const customEvent = event as CustomEvent; + const { difficulty, filterDifficulty } = customEvent.detail; + setDifficulty(difficulty); + }; + + window.addEventListener('difficultyChanged', handleDifficultyChange); + + return () => { + window.removeEventListener('difficultyChanged', handleDifficultyChange); + }; }, []); const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); worker.postMessage({ - unsigned: { - kind: 1, - tags: [], - content: comment + " " + file, - created_at: Math.floor(Date.now() / 1000), - pubkey: getPublicKey(sk), - }, - difficulty + unsigned: { + kind: 1, + tags: [], + content: comment + " " + file, + created_at: Math.floor(Date.now() / 1000), + pubkey: getPublicKey(sk), + }, + difficulty }); }; useEffect(() => { + setDoingWorkProp(false) if (messageFromWorker) { - try { - const signedEvent = finishEvent(messageFromWorker, sk); - publish(signedEvent); + try { + const signedEvent = finishEvent(messageFromWorker, sk); + publish(signedEvent); - setComment(""); - setFile(""); - setSk(generatePrivateKey()) + setComment(""); + setFile(""); + setSk(generatePrivateKey()); + setMessageFromWorker(null); - return () => { - worker.terminate(); - }; - } catch (error) { - setComment(error + ' ' + comment); - } + return () => { + worker.terminate(); + }; + } catch (error) { + setComment(error + ' ' + comment); + } } -}, [messageFromWorker]); + }, [messageFromWorker]); async function attachFile(file_input: File | null) { try { @@ -76,13 +90,15 @@ const NewThreadCard: React.FC = () => { return ( <> - {/*

Message from worker: {messageFromWorker}

*/}
{ + handleSubmit(event); + setDoingWorkProp(true); + }} >
- {file !== "" && ( -
+ {file !== "" && ( +
-
- )} + /> +
+ )}
@@ -132,6 +148,12 @@ const NewThreadCard: React.FC = () => { Submit
+ {doingWorkProp ? ( +
+ + Working... +
+ ) : null}
diff --git a/client/src/components/Settings.tsx b/client/src/components/Settings.tsx index 68f967f..e033f2b 100644 --- a/client/src/components/Settings.tsx +++ b/client/src/components/Settings.tsx @@ -10,6 +10,13 @@ const Settings = () => { e.preventDefault(); localStorage.setItem('filterDifficulty', String(filterDifficulty)); localStorage.setItem('difficulty', String(difficulty)); + + const eventData = { + difficulty: String(difficulty), + filterDifficulty: String(filterDifficulty), + }; + const event = new CustomEvent('settingsChanged', { detail: eventData }); + window.dispatchEvent(event); }; return ( diff --git a/client/src/components/Thread/ThreadPost.tsx b/client/src/components/Thread/ThreadPost.tsx index 27f3dcc..1e893c0 100644 --- a/client/src/components/Thread/ThreadPost.tsx +++ b/client/src/components/Thread/ThreadPost.tsx @@ -1,53 +1,94 @@ import { useParams } from 'react-router-dom'; -import { useState } from "react"; +import { useState, useMemo, useEffect } from "react"; import { ArrowUpTrayIcon, CpuChipIcon } from '@heroicons/react/24/outline'; import { generatePrivateKey, getPublicKey, finishEvent } from 'nostr-tools'; -import { minePow } from '../../utils/mine'; import { publish } from '../../utils/relays'; import NostrImg from '../../utils/ImgUpload'; import { nip19 } from 'nostr-tools'; -const difficulty = 25 - const ThreadPost = ({ state, type }: { state: Boolean, type: String }) => { - const { id} = useParams(); + const { id } = useParams(); const [comment, setComment] = useState(""); const [file, setFile] = useState(""); + const [difficulty, setDifficulty] = useState(localStorage.getItem('difficulty') || '21'); let decodeResult = nip19.decode(id as string); + const [sk, setSk] = useState(generatePrivateKey()); + + const [messageFromWorker, setMessageFromWorker] = useState(null); + const [doingWorkProp, setDoingWorkProp] = useState(false); + // Initialize the worker outside of any effects + const worker = useMemo(() => new Worker(new URL('../../powWorker', import.meta.url)), []); + + useEffect(() => { + worker.onmessage = (event) => { + setMessageFromWorker(event.data); + }; + + const handleDifficultyChange = (event: Event) => { + const customEvent = event as CustomEvent; + setDifficulty(customEvent.detail); + }; + + window.addEventListener('difficultyChanged', handleDifficultyChange); + + return () => { + window.removeEventListener('difficultyChanged', handleDifficultyChange); + }; + }, []); + const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); - let sk = generatePrivateKey(); let id = decodeResult.data as string - let tags = []; + let tags = []; + let modifiedComment = comment + " " + file; if (type === 'r') { tags.push(["e", id as string]) } else if (type === 'q') { tags.push(["q", id as string]) - setComment(comment + ' nostr:' + id) + modifiedComment += ' nostr:' + nip19.noteEncode(id); } try { - const event = minePow({ - kind: 1, - tags, - content: comment + " " + file, - created_at: Math.floor(Date.now() / 1000), - pubkey: getPublicKey(sk), - }, difficulty); - - const signedEvent = finishEvent(event, sk); - await publish(signedEvent); - console.log(signedEvent.id); + worker.postMessage({ + unsigned: { + kind: 1, + tags, + content: modifiedComment, + created_at: Math.floor(Date.now() / 1000), + pubkey: getPublicKey(sk), + }, difficulty + }); } catch (error) { setComment(comment + " " + error); } }; + useEffect(() => { + setDoingWorkProp(false) + if (messageFromWorker) { + try { + const signedEvent = finishEvent(messageFromWorker, sk); + publish(signedEvent); + + setComment(""); + setFile(""); + setSk(generatePrivateKey()) + setMessageFromWorker(null); + + return () => { + worker.terminate(); + }; + } catch (error) { + setComment(error + ' ' + comment); + } + } + }, [messageFromWorker]); + async function attachFile(file_input: File | null) { try { if (file_input) { @@ -74,7 +115,10 @@ const ThreadPost = ({ state, type }: { state: Boolean, type: String }) => { method="post" encType="multipart/form-data" className="" - onSubmit={handleSubmit} + onSubmit={(event) => { + handleSubmit(event); + setDoingWorkProp(true); + }} > + {doingWorkProp ? ( +
+ + Working... +
+ ) : null}
)} diff --git a/client/src/utils/postNote.ts b/client/src/utils/postNote.ts new file mode 100644 index 0000000..d2a68bb --- /dev/null +++ b/client/src/utils/postNote.ts @@ -0,0 +1,32 @@ +import { + type Event as NostrEvent, + generatePrivateKey, + getEventHash, + getPublicKey, + signEvent, +} from "nostr-tools"; + +export const handleThreadSubmit = async (comment: string, tags: []) => { + if (!comment) { + alert("no message provided"); + return; + } + + const newEvent: NostrEvent = { + id: 'null', + content: comment, + kind: 1, + tags, + created_at: Math.floor(Date.now() / 1000), + pubkey: 'null', + sig: 'null', + }; + + let sk = generatePrivateKey(); + + newEvent.pubkey = getPublicKey(sk); + newEvent.id = getEventHash(newEvent); + newEvent.sig = signEvent(newEvent, sk); + + return newEvent +}; \ No newline at end of file