From 933009d2c4bf2158ae9b5d7e3b60c576f2f34320 Mon Sep 17 00:00:00 2001 From: smolgrrr Date: Sat, 25 Nov 2023 15:28:21 +1100 Subject: [PATCH] fix file upload and UI work --- client/.dockerignore | 4 +- .../components/Forms/Emojis/emoji-picker.tsx | 8 -- client/src/components/Forms/PostFormCard.tsx | 3 +- client/src/components/Forms/handleSubmit.ts | 1 + client/src/components/Header/Header.tsx | 3 +- client/src/components/Modals/Card.tsx | 26 +---- .../Modals/CardModals/QuoteEmbed.tsx | 101 ++++++------------ .../Modals/CardModals/TextModal.tsx | 2 +- client/src/utils/FileUpload.tsx | 20 +++- ...terministicProfileIcon.ts => cardUtils.ts} | 20 ++++ client/src/utils/relays.ts | 6 -- 11 files changed, 78 insertions(+), 116 deletions(-) rename client/src/utils/{deterministicProfileIcon.ts => cardUtils.ts} (69%) diff --git a/client/.dockerignore b/client/.dockerignore index e3d77d6..aae7517 100644 --- a/client/.dockerignore +++ b/client/.dockerignore @@ -5,4 +5,6 @@ npm-debug.log README.md .next .git -build \ No newline at end of file +build +.env +bun.lockb \ No newline at end of file diff --git a/client/src/components/Forms/Emojis/emoji-picker.tsx b/client/src/components/Forms/Emojis/emoji-picker.tsx index 41aeb77..cd5f8fb 100644 --- a/client/src/components/Forms/Emojis/emoji-picker.tsx +++ b/client/src/components/Forms/Emojis/emoji-picker.tsx @@ -12,14 +12,6 @@ interface EmojiPickerProps { ref: RefObject; } -const customCategoryIcons = { - categoryIcons: { - poast: { - src: "https://poa.st/emoji/custom/poast_hat.png", - }, - }, -}; - export function EmojiPicker({ topOffset, leftOffset, diff --git a/client/src/components/Forms/PostFormCard.tsx b/client/src/components/Forms/PostFormCard.tsx index dd504be..e22a397 100644 --- a/client/src/components/Forms/PostFormCard.tsx +++ b/client/src/components/Forms/PostFormCard.tsx @@ -6,8 +6,7 @@ import { } from "@heroicons/react/24/outline"; import { XCircleIcon } from "@heroicons/react/24/solid"; import { useState, useEffect, useRef } from "react"; -import { generatePrivateKey, getPublicKey, finishEvent, UnsignedEvent, Event as NostrEvent, nip19 } from "nostr-tools"; -import { publish } from "../../utils/relays"; +import { UnsignedEvent, Event as NostrEvent, nip19 } from "nostr-tools"; import { renderMedia, attachFile } from "../../utils/FileUpload"; import { EmojiPicker } from "./Emojis/emoji-picker"; import customEmojis from './custom_emojis.json'; diff --git a/client/src/components/Forms/handleSubmit.ts b/client/src/components/Forms/handleSubmit.ts index 7019741..d44b600 100644 --- a/client/src/components/Forms/handleSubmit.ts +++ b/client/src/components/Forms/handleSubmit.ts @@ -52,6 +52,7 @@ export const useSubmitForm = (unsigned: UnsignedEvent, difficulty: string) => { setDoingWorkProp(false); const signedEvent = finishEvent(unsignedPoWEvent, sk); publish(signedEvent); + setSk(generatePrivateKey()) } }, [unsignedPoWEvent]); diff --git a/client/src/components/Header/Header.tsx b/client/src/components/Header/Header.tsx index 3a717dc..bd4b1c7 100644 --- a/client/src/components/Header/Header.tsx +++ b/client/src/components/Header/Header.tsx @@ -1,6 +1,5 @@ import { - Cog6ToothIcon, - QuestionMarkCircleIcon, + Cog6ToothIcon } from "@heroicons/react/24/outline"; export default function Header() { diff --git a/client/src/components/Modals/Card.tsx b/client/src/components/Modals/Card.tsx index ddae7f9..d5bbbeb 100644 --- a/client/src/components/Modals/Card.tsx +++ b/client/src/components/Modals/Card.tsx @@ -5,31 +5,11 @@ import { Event, nip19 } from "nostr-tools"; import { getMetadata } from "../../utils/otherUtils"; import ContentPreview from "./CardModals/TextModal"; import { renderMedia } from "../../utils/FileUpload"; -import { getIconFromHash } from "../../utils/deterministicProfileIcon"; +import { getIconFromHash, timeAgo } from "../../utils/cardUtils"; import { verifyPow } from "../../utils/mine"; import { uniqBy } from "../../utils/otherUtils"; import { useNavigate } from 'react-router-dom'; -const timeUnits = [ - { unit: 'w', value: 60 * 60 * 24 * 7 }, - { unit: 'd', value: 60 * 60 * 24 }, - { unit: 'h', value: 60 * 60 }, - { unit: 'm', value: 60 }, -]; - -const timeAgo = (unixTime: number) => { - let seconds = Math.floor(new Date().getTime() / 1000 - unixTime); - - if (seconds < 60) return `now`; - - for (let unit of timeUnits) { - if (seconds >= unit.value) { - return `${Math.floor(seconds / unit.value)}${unit.unit}`; - } - seconds %= unit.value; - } -}; - interface CardProps { key?: string | number; event: Event; @@ -47,7 +27,7 @@ const PostCard = ({ repliedTo, type }: CardProps) => { - const { comment, file } = parseContent(event); + const { file } = parseContent(event); const icon = getIconFromHash(event.pubkey); const metadataParsed = metadata ? getMetadata(metadata) : null; const navigate = useNavigate(); @@ -78,7 +58,6 @@ const PostCard = ({ ))} }
-
{metadataParsed ? } -
{verifyPow(event)} diff --git a/client/src/components/Modals/CardModals/QuoteEmbed.tsx b/client/src/components/Modals/CardModals/QuoteEmbed.tsx index 76cb1ab..3307d73 100644 --- a/client/src/components/Modals/CardModals/QuoteEmbed.tsx +++ b/client/src/components/Modals/CardModals/QuoteEmbed.tsx @@ -1,66 +1,23 @@ import { parseContent } from "../../../utils/content"; import { Event } from "nostr-tools"; -import { getMetadata, uniqBy } from "../../../utils/otherUtils"; +import { getMetadata } from "../../../utils/otherUtils"; import ContentPreview from "./TextModal"; import { renderMedia } from "../../../utils/FileUpload"; - -const colorCombos = [ - "from-red-400 to-yellow-500", - "from-green-400 to-blue-500", - "from-purple-400 to-pink-500", - "from-yellow-400 to-orange-500", - "from-indigo-400 to-purple-500", - "from-pink-400 to-red-500", - "from-blue-400 to-indigo-500", - "from-orange-400 to-red-500", - "from-teal-400 to-green-500", - "from-cyan-400 to-teal-500", - "from-lime-400 to-green-500", - "from-amber-400 to-orange-500", - "from-rose-400 to-pink-500", - "from-violet-400 to-purple-500", - "from-sky-400 to-cyan-500", -]; - -const getColorFromHash = (id: string, colors: string[]): string => { - // Create a simple hash from the event.id - let hash = 0; - for (let i = 0; i < id.length; i++) { - hash = (hash << 5) - hash + id.charCodeAt(i); - } - - // Use the hash to pick a color from the colors array - const index = Math.abs(hash) % colors.length; - return colors[index]; -}; - -const timeAgo = (unixTime: number) => { - const seconds = Math.floor(new Date().getTime() / 1000 - unixTime); - - if (seconds < 60) return `now`; - - const minutes = Math.floor(seconds / 60); - if (minutes < 60) return `${minutes}m`; - - const hours = Math.floor(minutes / 60); - if (hours < 24) return `${hours}h`; - - const days = Math.floor(hours / 24); - if (days < 7) return `${days}d`; - - const weeks = Math.floor(days / 7); - return `${weeks}w`; -}; +import { getIconFromHash, timeAgo } from "../../../utils/cardUtils"; +import { CpuChipIcon } from "@heroicons/react/24/outline"; +import { verifyPow } from "../../../utils/mine"; const QuoteEmbed = ({ + key, event, metadata, }: { + key?: string | number; event: Event; metadata: Event | null; }) => { - const { comment, file } = parseContent(event); - const colorCombo = getColorFromHash(event.pubkey, colorCombos); + const { file } = parseContent(event); + const icon = getIconFromHash(event.pubkey); let metadataParsed = null; if (metadata !== null) { @@ -68,31 +25,33 @@ const QuoteEmbed = ({ } return ( -
-
+
+
{renderMedia(file)}
-
- {metadataParsed ? ( - <> - -
{metadataParsed.name}
- - ) : ( - <> -
-
Anonymous
- - )} + {metadataParsed ? + + : +
+ } +
+
+ {verifyPow(event)} +
+ · +
+ {timeAgo(event.created_at)} +
+ ·
diff --git a/client/src/components/Modals/CardModals/TextModal.tsx b/client/src/components/Modals/CardModals/TextModal.tsx index bd8e2ff..7da26dc 100644 --- a/client/src/components/Modals/CardModals/TextModal.tsx +++ b/client/src/components/Modals/CardModals/TextModal.tsx @@ -26,7 +26,7 @@ const RichText = ({ text, isExpanded, emojiMap }: { text: string; isExpanded: bo }; const ContentPreview = ({ key, eventdata }: { key: string; eventdata: Event }) => { - const { comment, file } = parseContent(eventdata); + const { comment } = parseContent(eventdata); const [finalComment, setFinalComment] = useState(comment); const [quoteEvents, setQuoteEvents] = useState([]); // Initialize state const [isExpanded, setIsExpanded] = useState(false); diff --git a/client/src/utils/FileUpload.tsx b/client/src/utils/FileUpload.tsx index 2cc7b89..7d2b3e3 100644 --- a/client/src/utils/FileUpload.tsx +++ b/client/src/utils/FileUpload.tsx @@ -1,3 +1,6 @@ +import { generatePrivateKey, getPublicKey, finishEvent } from "nostr-tools"; +import { base64 } from "@scure/base"; + export interface UploadResult { url?: string; error?: string; @@ -10,7 +13,21 @@ export interface UploadResult { export default async function FileUpload(file: File): Promise { const buf = await file.arrayBuffer(); - + const sk = generatePrivateKey(); + const auth = async () => { + const authEvent = { + kind: 27235, + tags: [ + ["u", "https://void.cat/upload"], + ["method", "POST"] + ], + content: "", + created_at: Math.floor(Date.now() / 1000), + pubkey: getPublicKey(sk), + } + return `Nostr ${base64.encode(new TextEncoder().encode(JSON.stringify(finishEvent(authEvent, sk))))}`; + }; + const req = await fetch("https://void.cat/upload", { body: buf, method: "POST", @@ -20,6 +37,7 @@ export default async function FileUpload(file: File): Promise { "V-Filename": file.name, // Extracting the filename "V-Description": "Upload from https://tao-green.vercel.app/", "V-Strip-Metadata": "true", // Here's the new header + "authorization": await auth() }, }); if (req.ok) { diff --git a/client/src/utils/deterministicProfileIcon.ts b/client/src/utils/cardUtils.ts similarity index 69% rename from client/src/utils/deterministicProfileIcon.ts rename to client/src/utils/cardUtils.ts index e54eb5b..128a264 100644 --- a/client/src/utils/deterministicProfileIcon.ts +++ b/client/src/utils/cardUtils.ts @@ -37,4 +37,24 @@ export const getIconFromHash = (id: string): string => { const directionIndex = Math.abs(Math.floor(hash / colorCombos.length)) % gradientDirections.length; return `${gradientDirections[directionIndex]} ${colorCombos[colorIndex]}`; +}; + +const timeUnits = [ + { unit: 'w', value: 60 * 60 * 24 * 7 }, + { unit: 'd', value: 60 * 60 * 24 }, + { unit: 'h', value: 60 * 60 }, + { unit: 'm', value: 60 }, +]; + +export const timeAgo = (unixTime: number) => { + let seconds = Math.floor(new Date().getTime() / 1000 - unixTime); + + if (seconds < 60) return `now`; + + for (let unit of timeUnits) { + if (seconds >= unit.value) { + return `${Math.floor(seconds / unit.value)}${unit.unit}`; + } + seconds %= unit.value; + } }; \ No newline at end of file diff --git a/client/src/utils/relays.ts b/client/src/utils/relays.ts index 5e2eca3..480dc51 100644 --- a/client/src/utils/relays.ts +++ b/client/src/utils/relays.ts @@ -80,12 +80,6 @@ export const unsubAll = () => { currentSubList.length = 0; }; -type PublishCallback = ( - relay: string, - errorMessage?: string, -) => void; - - export const publish = (event: Event) => { relayMap.forEach(async (relay, url) => { try {