fix file upload and UI work

This commit is contained in:
smolgrrr 2023-11-25 15:28:21 +11:00
parent 2526540403
commit 933009d2c4
11 changed files with 78 additions and 116 deletions

View File

@ -5,4 +5,6 @@ npm-debug.log
README.md README.md
.next .next
.git .git
build build
.env
bun.lockb

View File

@ -12,14 +12,6 @@ interface EmojiPickerProps {
ref: RefObject<HTMLDivElement>; ref: RefObject<HTMLDivElement>;
} }
const customCategoryIcons = {
categoryIcons: {
poast: {
src: "https://poa.st/emoji/custom/poast_hat.png",
},
},
};
export function EmojiPicker({ export function EmojiPicker({
topOffset, topOffset,
leftOffset, leftOffset,

View File

@ -6,8 +6,7 @@ import {
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
import { XCircleIcon } from "@heroicons/react/24/solid"; import { XCircleIcon } from "@heroicons/react/24/solid";
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import { generatePrivateKey, getPublicKey, finishEvent, UnsignedEvent, Event as NostrEvent, nip19 } from "nostr-tools"; import { UnsignedEvent, Event as NostrEvent, nip19 } from "nostr-tools";
import { publish } from "../../utils/relays";
import { renderMedia, attachFile } from "../../utils/FileUpload"; import { renderMedia, attachFile } from "../../utils/FileUpload";
import { EmojiPicker } from "./Emojis/emoji-picker"; import { EmojiPicker } from "./Emojis/emoji-picker";
import customEmojis from './custom_emojis.json'; import customEmojis from './custom_emojis.json';

View File

@ -52,6 +52,7 @@ export const useSubmitForm = (unsigned: UnsignedEvent, difficulty: string) => {
setDoingWorkProp(false); setDoingWorkProp(false);
const signedEvent = finishEvent(unsignedPoWEvent, sk); const signedEvent = finishEvent(unsignedPoWEvent, sk);
publish(signedEvent); publish(signedEvent);
setSk(generatePrivateKey())
} }
}, [unsignedPoWEvent]); }, [unsignedPoWEvent]);

View File

@ -1,6 +1,5 @@
import { import {
Cog6ToothIcon, Cog6ToothIcon
QuestionMarkCircleIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
export default function Header() { export default function Header() {

View File

@ -5,31 +5,11 @@ 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 } from "../../utils/deterministicProfileIcon"; 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";
import { useNavigate } from 'react-router-dom'; 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 { interface CardProps {
key?: string | number; key?: string | number;
event: Event; event: Event;
@ -47,7 +27,7 @@ const PostCard = ({
repliedTo, repliedTo,
type type
}: CardProps) => { }: CardProps) => {
const { comment, file } = parseContent(event); const { file } = parseContent(event);
const icon = getIconFromHash(event.pubkey); const icon = getIconFromHash(event.pubkey);
const metadataParsed = metadata ? getMetadata(metadata) : null; const metadataParsed = metadata ? getMetadata(metadata) : null;
const navigate = useNavigate(); const navigate = useNavigate();
@ -78,7 +58,6 @@ const PostCard = ({
))} ))}
</div>} </div>}
<div className={`flex justify-between items-center ${type !== "OP" ? 'hover:cursor-pointer' : ''}`} onClick={handleClick}> <div className={`flex justify-between items-center ${type !== "OP" ? 'hover:cursor-pointer' : ''}`} onClick={handleClick}>
<div className="flex items-center gap-2.5">
{metadataParsed ? {metadataParsed ?
<img <img
key = {key} key = {key}
@ -90,7 +69,6 @@ const PostCard = ({
: :
<div className={`h-4 w-4 ${icon} rounded-full`} /> <div className={`h-4 w-4 ${icon} rounded-full`} />
} }
</div>
<div className="flex items-center ml-auto gap-2.5"> <div className="flex items-center ml-auto gap-2.5">
<div className="inline-flex text-xs text-neutral-600 gap-0.5"> <div className="inline-flex text-xs text-neutral-600 gap-0.5">
<CpuChipIcon className="h-4 w-4" /> {verifyPow(event)} <CpuChipIcon className="h-4 w-4" /> {verifyPow(event)}

View File

@ -1,66 +1,23 @@
import { parseContent } from "../../../utils/content"; import { parseContent } from "../../../utils/content";
import { Event } from "nostr-tools"; import { Event } from "nostr-tools";
import { getMetadata, uniqBy } from "../../../utils/otherUtils"; import { getMetadata } from "../../../utils/otherUtils";
import ContentPreview from "./TextModal"; import ContentPreview from "./TextModal";
import { renderMedia } from "../../../utils/FileUpload"; import { renderMedia } from "../../../utils/FileUpload";
import { getIconFromHash, timeAgo } from "../../../utils/cardUtils";
const colorCombos = [ import { CpuChipIcon } from "@heroicons/react/24/outline";
"from-red-400 to-yellow-500", import { verifyPow } from "../../../utils/mine";
"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`;
};
const QuoteEmbed = ({ const QuoteEmbed = ({
key,
event, event,
metadata, metadata,
}: { }: {
key?: string | number;
event: Event; event: Event;
metadata: Event | null; metadata: Event | null;
}) => { }) => {
const { comment, file } = parseContent(event); const { file } = parseContent(event);
const colorCombo = getColorFromHash(event.pubkey, colorCombos); const icon = getIconFromHash(event.pubkey);
let metadataParsed = null; let metadataParsed = null;
if (metadata !== null) { if (metadata !== null) {
@ -68,31 +25,33 @@ const QuoteEmbed = ({
} }
return ( return (
<div className="p-3 rounded-lg border border-neutral-700 bg-neutral-800"> <div className="p-3 rounded-lg border border-neutral-700">
<div className="flex flex-col"> <div className="flex flex-col gap-2">
<div className="flex flex-col break-words"> <div className="flex flex-col break-words">
<ContentPreview key={event.id} eventdata={event} /> <ContentPreview key={event.id} eventdata={event} />
</div> </div>
{renderMedia(file)} {renderMedia(file)}
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="flex items-center gap-1.5"> {metadataParsed ?
{metadataParsed ? ( <img
<> key={key}
<img className={`h-5 w-5 rounded-full`}
className={`h-5 w-5 rounded-full`} src={metadataParsed?.picture ?? icon}
src={metadataParsed.picture} alt=""
alt="" loading="lazy"
/> decoding="async" />
<div className="text-sm font-medium">{metadataParsed.name}</div> :
</> <div className={`h-4 w-4 ${icon} rounded-full`} />
) : ( }
<> <div className="flex items-center ml-auto gap-2.5">
<div <div className="inline-flex text-xs text-neutral-600 gap-0.5">
className={`h-4 w-4 bg-gradient-to-r ${colorCombo} rounded-full`} <CpuChipIcon className="h-4 w-4" /> {verifyPow(event)}
/> </div>
<div className="text-sm font-medium">Anonymous</div> <span className="text-neutral-700">·</span>
</> <div className="text-xs font-semibold text-neutral-600">
)} {timeAgo(event.created_at)}
</div>
<span className="text-neutral-700">·</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -26,7 +26,7 @@ const RichText = ({ text, isExpanded, emojiMap }: { text: string; isExpanded: bo
}; };
const ContentPreview = ({ key, eventdata }: { key: string; eventdata: Event }) => { const ContentPreview = ({ key, eventdata }: { key: string; eventdata: Event }) => {
const { comment, file } = parseContent(eventdata); const { comment } = parseContent(eventdata);
const [finalComment, setFinalComment] = useState(comment); const [finalComment, setFinalComment] = useState(comment);
const [quoteEvents, setQuoteEvents] = useState<Event[]>([]); // Initialize state const [quoteEvents, setQuoteEvents] = useState<Event[]>([]); // Initialize state
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);

View File

@ -1,3 +1,6 @@
import { generatePrivateKey, getPublicKey, finishEvent } from "nostr-tools";
import { base64 } from "@scure/base";
export interface UploadResult { export interface UploadResult {
url?: string; url?: string;
error?: string; error?: string;
@ -10,7 +13,21 @@ export interface UploadResult {
export default async function FileUpload(file: File): Promise<UploadResult> { export default async function FileUpload(file: File): Promise<UploadResult> {
const buf = await file.arrayBuffer(); 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", { const req = await fetch("https://void.cat/upload", {
body: buf, body: buf,
method: "POST", method: "POST",
@ -20,6 +37,7 @@ export default async function FileUpload(file: File): Promise<UploadResult> {
"V-Filename": file.name, // Extracting the filename "V-Filename": file.name, // Extracting the filename
"V-Description": "Upload from https://tao-green.vercel.app/", "V-Description": "Upload from https://tao-green.vercel.app/",
"V-Strip-Metadata": "true", // Here's the new header "V-Strip-Metadata": "true", // Here's the new header
"authorization": await auth()
}, },
}); });
if (req.ok) { if (req.ok) {

View File

@ -37,4 +37,24 @@ export const getIconFromHash = (id: string): string => {
const directionIndex = Math.abs(Math.floor(hash / colorCombos.length)) % gradientDirections.length; const directionIndex = Math.abs(Math.floor(hash / colorCombos.length)) % gradientDirections.length;
return `${gradientDirections[directionIndex]} ${colorCombos[colorIndex]}`; 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;
}
}; };

View File

@ -80,12 +80,6 @@ export const unsubAll = () => {
currentSubList.length = 0; currentSubList.length = 0;
}; };
type PublishCallback = (
relay: string,
errorMessage?: string,
) => void;
export const publish = (event: Event) => { export const publish = (event: Event) => {
relayMap.forEach(async (relay, url) => { relayMap.forEach(async (relay, url) => {
try { try {