mirror of
https://github.com/smolgrrr/TAO.git
synced 2024-09-20 01:11:25 +00:00
fix file upload and UI work
This commit is contained in:
parent
2526540403
commit
933009d2c4
@ -5,4 +5,6 @@ npm-debug.log
|
||||
README.md
|
||||
.next
|
||||
.git
|
||||
build
|
||||
build
|
||||
.env
|
||||
bun.lockb
|
@ -12,14 +12,6 @@ interface EmojiPickerProps {
|
||||
ref: RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const customCategoryIcons = {
|
||||
categoryIcons: {
|
||||
poast: {
|
||||
src: "https://poa.st/emoji/custom/poast_hat.png",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function EmojiPicker({
|
||||
topOffset,
|
||||
leftOffset,
|
||||
|
@ -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';
|
||||
|
@ -52,6 +52,7 @@ export const useSubmitForm = (unsigned: UnsignedEvent, difficulty: string) => {
|
||||
setDoingWorkProp(false);
|
||||
const signedEvent = finishEvent(unsignedPoWEvent, sk);
|
||||
publish(signedEvent);
|
||||
setSk(generatePrivateKey())
|
||||
}
|
||||
}, [unsignedPoWEvent]);
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {
|
||||
Cog6ToothIcon,
|
||||
QuestionMarkCircleIcon,
|
||||
Cog6ToothIcon
|
||||
} from "@heroicons/react/24/outline";
|
||||
|
||||
export default function Header() {
|
||||
|
@ -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 = ({
|
||||
))}
|
||||
</div>}
|
||||
<div className={`flex justify-between items-center ${type !== "OP" ? 'hover:cursor-pointer' : ''}`} onClick={handleClick}>
|
||||
<div className="flex items-center gap-2.5">
|
||||
{metadataParsed ?
|
||||
<img
|
||||
key = {key}
|
||||
@ -90,7 +69,6 @@ const PostCard = ({
|
||||
:
|
||||
<div className={`h-4 w-4 ${icon} rounded-full`} />
|
||||
}
|
||||
</div>
|
||||
<div className="flex items-center ml-auto gap-2.5">
|
||||
<div className="inline-flex text-xs text-neutral-600 gap-0.5">
|
||||
<CpuChipIcon className="h-4 w-4" /> {verifyPow(event)}
|
||||
|
@ -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 (
|
||||
<div className="p-3 rounded-lg border border-neutral-700 bg-neutral-800">
|
||||
<div className="flex flex-col">
|
||||
<div className="p-3 rounded-lg border border-neutral-700">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col break-words">
|
||||
<ContentPreview key={event.id} eventdata={event} />
|
||||
</div>
|
||||
{renderMedia(file)}
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-1.5">
|
||||
{metadataParsed ? (
|
||||
<>
|
||||
<img
|
||||
className={`h-5 w-5 rounded-full`}
|
||||
src={metadataParsed.picture}
|
||||
alt=""
|
||||
/>
|
||||
<div className="text-sm font-medium">{metadataParsed.name}</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className={`h-4 w-4 bg-gradient-to-r ${colorCombo} rounded-full`}
|
||||
/>
|
||||
<div className="text-sm font-medium">Anonymous</div>
|
||||
</>
|
||||
)}
|
||||
{metadataParsed ?
|
||||
<img
|
||||
key={key}
|
||||
className={`h-5 w-5 rounded-full`}
|
||||
src={metadataParsed?.picture ?? icon}
|
||||
alt=""
|
||||
loading="lazy"
|
||||
decoding="async" />
|
||||
:
|
||||
<div className={`h-4 w-4 ${icon} rounded-full`} />
|
||||
}
|
||||
<div className="flex items-center ml-auto gap-2.5">
|
||||
<div className="inline-flex text-xs text-neutral-600 gap-0.5">
|
||||
<CpuChipIcon className="h-4 w-4" /> {verifyPow(event)}
|
||||
</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>
|
||||
|
@ -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<Event[]>([]); // Initialize state
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
@ -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<UploadResult> {
|
||||
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<UploadResult> {
|
||||
"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) {
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user