From 0920abfcba1707ff6c0bcde2660926038037db87 Mon Sep 17 00:00:00 2001 From: smolgrrr Date: Sat, 6 Jan 2024 00:44:39 +1100 Subject: [PATCH] add boards --- client/src/App.tsx | 5 +- client/src/components/Board.tsx | 114 +++++++++++++++++++ client/src/components/Boards.tsx | 69 +++++++++++ client/src/components/Forms/PostFormCard.tsx | 12 +- client/src/components/Header/Header.tsx | 13 ++- client/src/utils/subscriptions.ts | 47 ++++++++ 6 files changed, 254 insertions(+), 6 deletions(-) create mode 100644 client/src/components/Board.tsx create mode 100644 client/src/components/Boards.tsx diff --git a/client/src/App.tsx b/client/src/App.tsx index 586d1ae..eaa17e4 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -6,9 +6,10 @@ import Thread from "./components/Thread"; import Header from "./components/Header/Header"; import AddToHomeScreenPrompt from "./components/Modals/CheckMobile/CheckMobile"; import Notifications from "./components/Notifications"; +import Board from "./components/Board"; +import Boards from "./components/Boards"; function App() { - return (
@@ -17,6 +18,8 @@ function App() { } /> } /> } /> + } /> + } /> diff --git a/client/src/components/Board.tsx b/client/src/components/Board.tsx new file mode 100644 index 0000000..e0fc799 --- /dev/null +++ b/client/src/components/Board.tsx @@ -0,0 +1,114 @@ +import { useEffect, useState, useCallback } from "react"; +import PostCard from "./Modals/NoteCard"; +import { uniqBy } from "../utils/otherUtils"; // Assume getPow is a correct import now +import { subBoardFeed } 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 = 20; + + +const Board = () => { + const { id } = useParams(); + const filterDifficulty = localStorage.getItem("filterDifficulty") || DEFAULT_DIFFICULTY; + const [sortByTime, setSortByTime] = useState(localStorage.getItem('sortBy') !== 'false'); + const [setAnon, setSetAnon] = useState(localStorage.getItem('anonMode') !== 'false'); + + let decodeResult = nip19.decode(id as string); + let pubkey = decodeResult.data as string; + + const [delayedSort, setDelayedSort] = useState(false) + const [events, setEvents] = useState([]); + + useEffect(() => { + const onEvent = (event: Event) => setEvents((prevEvents) => [...prevEvents, event]); + console.log(events[events.length]) + const unsubscribe = subBoardFeed(pubkey, onEvent); + + return unsubscribe; + }, [pubkey]); + + const uniqEvents = uniqBy(events, "id"); + + const noteEvents = uniqEvents.filter(event => event.kind === 1 || event.kind === 6); + const metadataEvents = uniqEvents.filter(event => event.kind === 0); + + const postEvents: Event[] = noteEvents + .filter((event) => + verifyPow(event) >= Number(filterDifficulty) && + event.kind !== 0 && + (event.kind !== 1 || !event.tags.some((tag) => tag[0] === "e")) + ) + + // Delayed filtering + useEffect(() => { + const timer = setTimeout(() => { + setDelayedSort(true); + }, 3000); + + return () => clearTimeout(timer); + }, []); + + let sortedEvents = [...postEvents] + .sort((a, b) => + sortByTime ? b.created_at - a.created_at : verifyPow(b) - verifyPow(a) + ) + + 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 ( +
+
+ +
+ +
+ {sortedEvents.map((event) => ( + event.kind === 1 ? + e.pubkey === event.pubkey && e.kind === 0) || null} + replyCount={countReplies(event)} + /> + : + + ))} +
+
+ ); +}; + +export default Board; diff --git a/client/src/components/Boards.tsx b/client/src/components/Boards.tsx new file mode 100644 index 0000000..ed4548f --- /dev/null +++ b/client/src/components/Boards.tsx @@ -0,0 +1,69 @@ +import React, { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +const Boards = () => { + const navigate = useNavigate(); + const addedBoards = JSON.parse(localStorage.getItem('addedBoards') as string) || []; + const [boardName, setBoardName] = useState(''); + const [boardPubkey, setboardPubkey] = useState('') + + const DefaultBoards = [['bitcoin', 'npub19nrn4l0s39kpwww7pgk9jddj8lzekqxmtrll8r2a57chtq3zx6sq00vetn']]; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + addedBoards.push([boardName, boardPubkey]) + localStorage.setItem('addedBoards', String(addedBoards)); + }; + + return ( +
+

Boards

+
+ {/* Map over DefaultBoards and addedBoards and display them */} +
    + {DefaultBoards.map((board, index) => ( +
  • /{board[0]}/
  • + ))} + {addedBoards.map((board: string, index: number) => ( +
  • /{board[0]}/
  • + ))} +
+ +
+
+
+ +
+ setBoardName(e.target.value)} + className="w-full px-3 py-2 border rounded-md bg-black" + /> + setboardPubkey(e.target.value)} + className="w-full px-3 py-2 border rounded-md bg-black" + /> +
+
+
+ +
+
+
+ ); +}; + +export default Boards; diff --git a/client/src/components/Forms/PostFormCard.tsx b/client/src/components/Forms/PostFormCard.tsx index 7f8585b..8f7c717 100644 --- a/client/src/components/Forms/PostFormCard.tsx +++ b/client/src/components/Forms/PostFormCard.tsx @@ -16,6 +16,7 @@ import "./Form.css"; interface FormProps { refEvent?: NostrEvent; tagType?: 'Reply' | 'Quote' | ''; + board?: string; } const tagMapping = { @@ -25,7 +26,8 @@ const tagMapping = { const NewNoteCard = ({ refEvent, - tagType + tagType, + board }: FormProps) => { const ref = useRef(null); const [comment, setComment] = useState(""); @@ -49,8 +51,8 @@ const NewNoteCard = ({ const [uploadingFile, setUploadingFile] = useState(false); useEffect(() => { - if (refEvent && tagType && unsigned.tags.length === 0) { - if (tagType === 'Reply' && unsigned.tags.length === 0) { + if (refEvent && tagType && unsigned.tags.length === 1) { + if (tagType === 'Reply') { unsigned.tags.push(['p', refEvent.pubkey]); unsigned.tags.push(['e', refEvent.id, 'root']); } else { @@ -66,6 +68,10 @@ const NewNoteCard = ({ } } + if (board) { + unsigned.tags.push(['d', nip19.decode(board).data as string]); + } + const handleDifficultyChange = (event: Event) => { const customEvent = event as CustomEvent; const { difficulty } = customEvent.detail; diff --git a/client/src/components/Header/Header.tsx b/client/src/components/Header/Header.tsx index d145ad5..8db69fc 100644 --- a/client/src/components/Header/Header.tsx +++ b/client/src/components/Header/Header.tsx @@ -1,6 +1,7 @@ import { Cog6ToothIcon, - BellIcon + BellIcon, + ArchiveBoxIcon } from "@heroicons/react/24/outline"; export default function Header() { @@ -17,8 +18,16 @@ export default function Header() {
+ + +