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
@ -6,3 +6,5 @@ README.md
|
|||||||
.next
|
.next
|
||||||
.git
|
.git
|
||||||
build
|
build
|
||||||
|
.env
|
||||||
|
bun.lockb
|
@ -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,
|
||||||
|
@ -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';
|
||||||
|
@ -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]);
|
||||||
|
|
||||||
|
@ -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() {
|
||||||
|
@ -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)}
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
@ -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,6 +13,20 @@ 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,
|
||||||
@ -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) {
|
||||||
|
@ -38,3 +38,23 @@ export const getIconFromHash = (id: string): string => {
|
|||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
};
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user