various tings alright

This commit is contained in:
smolgrrr 2023-10-25 18:53:34 +11:00
parent 23da7698c8
commit 80a2b3fc29
9 changed files with 313 additions and 153 deletions

View File

@ -15,8 +15,10 @@
"nostr-tools": "latest", "nostr-tools": "latest",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-pull-to-refresh": "^2.0.1",
"react-router-dom": "^6.15.0", "react-router-dom": "^6.15.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-swipe": "^6.0.4",
"react-swipeable-views": "^0.14.0", "react-swipeable-views": "^0.14.0",
"web-vitals": "^2.1.4", "web-vitals": "^2.1.4",
"workbox-background-sync": "^6.6.0", "workbox-background-sync": "^6.6.0",

View File

@ -2,7 +2,7 @@ import { PropsWithChildren } from 'react';
export default function CardContainer({ children }: PropsWithChildren) { export default function CardContainer({ children }: PropsWithChildren) {
return ( return (
<div className="card bg-gradient-to-r from-black to-neutral-900 shadow-lg shadow-black"> <div className="card bg-gradient-to-r from-black to-neutral-950 shadow-lg shadow-black">
<div className="card-body p-4">{children}</div> <div className="card-body p-4">{children}</div>
</div> </div>
); );

View File

@ -1,14 +1,16 @@
import CardContainer from './CardContainer'; import CardContainer from './CardContainer';
import { ArrowUpTrayIcon, CpuChipIcon } from '@heroicons/react/24/outline'; import { ArrowUpTrayIcon, CpuChipIcon } from '@heroicons/react/24/outline';
import { useState } from 'react'; import { useState } from 'react';
import { Event, generatePrivateKey, getPublicKey, finishEvent, relayInit} from 'nostr-tools'; import { Event, generatePrivateKey, getPublicKey, finishEvent, relayInit } from 'nostr-tools';
import { minePow } from '../../utils/mine'; import { minePow } from '../../utils/mine';
import { publish } from '../../utils/relays'; import { publish } from '../../utils/relays';
import NostrImg from '../../utils/ImgUpload';
const difficulty = 20 const difficulty = 20
const NewThreadCard: React.FC = () => { const NewThreadCard: React.FC = () => {
const [comment, setComment] = useState(""); const [comment, setComment] = useState("");
const [file, setFile] = useState("");
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
@ -18,36 +20,37 @@ const NewThreadCard: React.FC = () => {
const event = minePow({ const event = minePow({
kind: 1, kind: 1,
tags: [], tags: [],
content: comment, content: comment + " " + file,
created_at: Math.floor(Date.now() / 1000), created_at: Math.floor(Date.now() / 1000),
pubkey: getPublicKey(sk), pubkey: getPublicKey(sk),
}, difficulty); }, difficulty);
const signedEvent = finishEvent(event, sk); const signedEvent = finishEvent(event, sk);
await publish(signedEvent); await publish(signedEvent);
console.log(signedEvent.id);
setComment("")
setFile("")
} catch (error) { } catch (error) {
setComment(comment + " " + error); setComment(comment + " " + error);
} }
}; };
// async function attachFile(file_input: File | null) { async function attachFile(file_input: File | null) {
// try { try {
// if (file_input) { if (file_input) {
// const rx = await NostrImg(file_input); const rx = await NostrImg(file_input);
// if (rx.url) { if (rx.url) {
// setComment(comment + " " + rx.url); setFile(rx.url);
// } else if (rx?.error) { } else if (rx?.error) {
// setComment(comment + " " + rx.error); setFile(rx.error);
// } }
// } }
// } catch (error: unknown) { } catch (error: unknown) {
// if (error instanceof Error) { if (error instanceof Error) {
// setComment(comment + " " + error?.message); setFile(error?.message);
// } }
// } }
// } }
return ( return (
<> <>
@ -73,10 +76,34 @@ const NewThreadCard: React.FC = () => {
onChange={(e) => setComment(e.target.value)} onChange={(e) => setComment(e.target.value)}
/> />
</div> </div>
<div>
{file !== "" && (
<div className="file m-0.5">
<img
src={file}
loading="lazy"
/>
</div>
)}
</div>
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="flex items-center"> <div className="flex items-center">
<ArrowUpTrayIcon className="h-6 w-6 text-white" /> <ArrowUpTrayIcon
<input type="file" className="hidden" /> className="h-6 w-6 text-white cursor-pointer"
onClick={() => document.getElementById('file_input')?.click()}
/>
<input
type="file"
name="file_input"
id="file_input"
style={{ display: 'none' }}
onChange={(e) => {
const file_input = e.target.files?.[0];
if (file_input) {
attachFile(file_input);
}
}}
/>
</div> </div>
<span className="flex items-center"><CpuChipIcon className="h-6 w-6 text-white" />: {difficulty}</span> <span className="flex items-center"><CpuChipIcon className="h-6 w-6 text-white" />: {difficulty}</span>
<button type="submit" className="px-4 py-2 bg-gradient-to-r from-cyan-900 to-blue-500 rounded text-white font-semibold"> <button type="submit" className="px-4 py-2 bg-gradient-to-r from-cyan-900 to-blue-500 rounded text-white font-semibold">

View File

@ -94,14 +94,14 @@ const PostCard = ({ event, metadata, replyCount }: { event: Event, metadata: Eve
<div className="mr-2 flex flex-col break-words"> <div className="mr-2 flex flex-col break-words">
{comment} {comment}
</div> </div>
{/* {file !== "" && ( {file !== "" && (
<div className="file"> <div className="file">
<img <img
src={file} src={file}
loading="lazy" loading="lazy"
/> />
</div> </div>
)} */} )}
</div> </div>
</a> </a>
</CardContainer> </CardContainer>

View File

@ -1,56 +1,55 @@
import React, { useEffect, useState } from 'react'; import React, { useState, useEffect } from 'react';
import {generatePrivateKey, getPublicKey} from 'nostr-tools';
// import {powEvent} from './system';
// import {publish} from './relays';
const Settings = () => { const Settings = () => {
// State variables to hold the settings const [filterDifficulty, setFilterDifficulty] = useState(localStorage.getItem('filterDifficulty') || 20);
const [isDarkMode, setIsDarkMode] = useState(false); const [difficulty, setDifficulty] = useState(localStorage.getItem('difficulty') || 20);
const [username, setUsername] = useState('');
// Mimic fetching existing settings from an API or local storage const handleSubmit = (e: React.FormEvent) => {
useEffect(() => { e.preventDefault();
// Simulate fetching existing settings localStorage.setItem('filterDifficulty', String(filterDifficulty));
const existingSettings = { localStorage.setItem('difficulty', String(difficulty));
isDarkMode: false, // replace with actual value
username: '' // replace with actual value
};
setIsDarkMode(existingSettings.isDarkMode);
setUsername(existingSettings.username);
}, []);
// Function to save changes (simulate API call or local storage update)
const saveChanges = () => {
// Replace this with an actual API call or local storage update
console.log('Dark Mode:', isDarkMode);
console.log('Username:', username);
}; };
return ( return (
<div className="settings-page"> <div className="settings-page bg-black text-white min-h-screen p-8">
<h1>Settings</h1> <h1 className="text-lg font-semibold mb-4">Settings</h1>
<form onSubmit={handleSubmit}>
<div className="setting-item"> <div className="flex flex-wrap -mx-2 mb-4">
<label> <div className="w-full md:w-1/3 px-2 mb-4 md:mb-0">
Dark Mode <label className="block mb-2" htmlFor="filterDifficulty">
<input Filter Difficulty:
type="checkbox"
checked={isDarkMode}
onChange={(e) => setIsDarkMode(e.target.checked)}
/>
</label> </label>
</div>
<div className="setting-item">
<label>
Username
<input <input
type="text" id="filterDifficulty"
value={username} type="number"
onChange={(e) => setUsername(e.target.value)} value={filterDifficulty}
onChange={e => setFilterDifficulty(e.target.value)}
className="w-full px-3 py-2 border rounded-md text-black"
/> />
</label>
</div> </div>
<button onClick={saveChanges}>Save Changes</button> <div className="w-full md:w-1/3 px-2 mb-4 md:mb-0">
<label className="block mb-2" htmlFor="difficulty">
Post Difficulty:
</label>
<input
id="difficulty"
type="number"
value={difficulty}
onChange={e => setDifficulty(e.target.value)}
className="w-full px-3 py-2 border rounded-md text-black"
/>
</div> </div>
</div>
<button className="bg-gradient-to-r from-blue-900 to-cyan-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Save Settings
</button>
</form>
</div>
); );
}; };

View File

@ -10,6 +10,7 @@ import { ArrowUpTrayIcon, CpuChipIcon } from '@heroicons/react/24/outline';
import { generatePrivateKey, getPublicKey, finishEvent, relayInit } from 'nostr-tools'; import { generatePrivateKey, getPublicKey, finishEvent, relayInit } from 'nostr-tools';
import { minePow } from '../../utils/mine'; import { minePow } from '../../utils/mine';
import { publish } from '../../utils/relays'; import { publish } from '../../utils/relays';
import ThreadPost from './ThreadPost';
const difficulty = 20 const difficulty = 20
@ -20,6 +21,7 @@ const Thread = () => {
let decodeResult = nip19.decode(id as string); let decodeResult = nip19.decode(id as string);
const [comment, setComment] = useState(""); const [comment, setComment] = useState("");
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
const [postType, setPostType] = useState("");
@ -80,7 +82,7 @@ const Thread = () => {
if (!uniqEvents[0]) { if (!uniqEvents[0]) {
return ( return (
<main className="bg-black text-white min-h-screen"> <>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
<div className="border border-blue-300 shadow rounded-md p-4 max-w-sm w-full mx-auto"> <div className="border border-blue-300 shadow rounded-md p-4 max-w-sm w-full mx-auto">
<div className="animate-pulse flex space-x-4"> <div className="animate-pulse flex space-x-4">
@ -98,7 +100,7 @@ const Thread = () => {
</div> </div>
</div> </div>
</div> </div>
</main> </>
); );
} }
return ( return (
@ -107,44 +109,24 @@ const Thread = () => {
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
<PostCard event={uniqEvents[0]} metadata={getMetadataEvent(uniqEvents[0])} replyCount={countReplies(uniqEvents[0])} /> <PostCard event={uniqEvents[0]} metadata={getMetadataEvent(uniqEvents[0])} replyCount={countReplies(uniqEvents[0])} />
<div className="col-span-full flex justify-center space-x-36 "> <div className="col-span-full flex justify-center space-x-36 ">
<DocumentTextIcon className="h-5 w-5 text-gray-200" onClick={() => setShowForm(prevShowForm => !prevShowForm)} /> <DocumentTextIcon
<FolderPlusIcon className="h-5 w-5 text-gray-200" onClick={() => setShowForm(prevShowForm => !prevShowForm)} /> className="h-5 w-5 text-gray-200"
</div> onClick={() => {
<div> setShowForm(prevShowForm => !prevShowForm);
{showForm && ( setPostType('r');
<form }}
name="post" />
method="post"
encType="multipart/form-data" <FolderPlusIcon
className="" className="h-5 w-5 text-gray-200"
onSubmit={handleSubmit} onClick={() => {
> setShowForm(prevShowForm => !prevShowForm);
<input type="hidden" name="MAX_FILE_SIZE" defaultValue={4194304} /> setPostType('q');
<div id="togglePostFormLink" className="text-lg font-semibold"> }}
Reply to note
</div>
<div>
<textarea
name="com"
wrap="soft"
className="w-full p-2 rounded bg-gradient-to-r from-blue-900 to-cyan-500 text-white border-none placeholder-blue-300"
placeholder='Shitpost here...'
value={comment}
onChange={(e) => setComment(e.target.value)}
/> />
</div> </div>
<div className="flex justify-between items-center"> <div>
<div className="flex items-center"> <ThreadPost state={showForm} type={postType} />
<ArrowUpTrayIcon className="h-6 w-6 text-white" />
<input type="file" className="hidden" />
</div>
<span className="flex items-center"><CpuChipIcon className="h-6 w-6 text-white" />: {difficulty}</span>
<button type="submit" className="px-4 py-2 bg-gradient-to-r from-cyan-900 to-blue-500 rounded text-white font-semibold">
Submit
</button>
</div>
<div id="postFormError" className="text-red-500" />
</form>)}
</div> </div>
<div className="col-span-full h-0.5 bg-neutral-900"></div> {/* This is the white line separator */} <div className="col-span-full h-0.5 bg-neutral-900"></div> {/* This is the white line separator */}
{uniqEvents {uniqEvents

View File

@ -0,0 +1,133 @@
import { useParams } from 'react-router-dom';
import { useState } from "react";
import { ArrowUpTrayIcon, CpuChipIcon } from '@heroicons/react/24/outline';
import { generatePrivateKey, getPublicKey, finishEvent, relayInit } from 'nostr-tools';
import { minePow } from '../../utils/mine';
import { publish } from '../../utils/relays';
import NostrImg from '../../utils/ImgUpload';
import { nip19 } from 'nostr-tools';
const difficulty = 5
const ThreadPost = ({ state, type }: { state: Boolean, type: String }) => {
const { id} = useParams();
const [comment, setComment] = useState("");
const [file, setFile] = useState("");
const [tags, setTags] = useState([['']]);
let decodeResult = nip19.decode(id as string);
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
let sk = generatePrivateKey();
let id = decodeResult.data as string
if (type === 'r') {
tags.push(["e", id as string])
} else if (type === 'q') {
tags.push(["q", id as string])
setComment(comment + ' nostr:' + id)
}
try {
const event = minePow({
kind: 1,
tags,
content: comment + " " + file,
created_at: Math.floor(Date.now() / 1000),
pubkey: getPublicKey(sk),
}, difficulty);
const signedEvent = finishEvent(event, sk);
await publish(signedEvent);
console.log(signedEvent.id);
} catch (error) {
setComment(comment + " " + error);
}
};
async function attachFile(file_input: File | null) {
try {
if (file_input) {
const rx = await NostrImg(file_input);
if (rx.url) {
setFile(rx.url);
} else if (rx?.error) {
setFile(rx.error);
}
}
} catch (error: unknown) {
if (error instanceof Error) {
setFile(error?.message);
}
}
}
return (
<>
{state && (
<form
name="post"
method="post"
encType="multipart/form-data"
className=""
onSubmit={handleSubmit}
>
<input type="hidden" name="MAX_FILE_SIZE" defaultValue={4194304} />
<div id="togglePostFormLink" className="text-lg font-semibold">
{type === 'r' ? <span>Reply To Post</span> : <span>Quote Post</span>}
</div>
<div>
<textarea
name="com"
wrap="soft"
className="w-full p-2 rounded bg-gradient-to-r from-blue-900 to-cyan-500 text-white border-none placeholder-blue-300"
placeholder='Shitpost here...'
value={comment}
onChange={(e) => setComment(e.target.value)}
/>
</div>
<div>
{file !== "" && (
<div className="file m-0.5">
<img
src={file}
loading="lazy"
/>
</div>
)}
</div>
<div className="flex justify-between items-center">
<div className="flex items-center">
<ArrowUpTrayIcon
className="h-6 w-6 text-white cursor-pointer"
onClick={() => document.getElementById('file_input')?.click()}
/>
<input
type="file"
name="file_input"
id="file_input"
style={{ display: 'none' }}
onChange={(e) => {
const file_input = e.target.files?.[0];
if (file_input) {
attachFile(file_input);
}
}}
/>
</div>
<span className="flex items-center"><CpuChipIcon className="h-6 w-6 text-white" />: {difficulty}</span>
<button type="submit" className="px-4 py-2 bg-gradient-to-r from-cyan-900 to-blue-500 rounded text-white font-semibold">
Submit
</button>
</div>
<div id="postFormError" className="text-red-500" />
</form>)}
</>
);
};
export default ThreadPost;

View File

@ -9,6 +9,7 @@ body {
sans-serif; sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
background-color: black;
} }
code { code {

View File

@ -9,23 +9,23 @@ export interface UploadResult {
*/ */
export default async function NostrImg(file: File ): Promise<UploadResult> { export default async function NostrImg(file: File ): Promise<UploadResult> {
const buf = await file.arrayBuffer(); const fd = new FormData();
fd.append("image", file);
const req = await fetch("https://void.cat/upload", { const req = await fetch("https://nostrimg.com/api/upload", {
body: buf, body: fd,
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/octet-stream", accept: "application/json",
"V-Content-Type": file.type, // Extracting the mime type
"V-Filename": file.name, // Extracting the filename
"V-Description": "Upload from https://tao-green.vercel.app/",
}, },
}); });
if (req.ok) { if (req.ok) {
let rsp: VoidUploadResponse = await req.json(); const data: UploadResponse = await req.json();
const fileExtension = file.name.split('.').pop(); // Extracting the file extension if (typeof data?.imageUrl === "string" && data.success) {
const resultUrl = `https://void.cat/d/${rsp.file?.id}.${fileExtension}`; return {
return {url: resultUrl}; url: new URL(data.imageUrl).toString(),
};
}
} }
return { return {
error: "Upload failed", error: "Upload failed",
@ -43,6 +43,22 @@ export type VoidUploadResponse = {
errorMessage?: string, errorMessage?: string,
} }
interface UploadResponse {
fileID?: string;
fileName?: string;
imageUrl?: string;
lightningDestination?: string;
lightningPaymentLink?: string;
message?: string;
route?: string;
status: number;
success: boolean;
url?: string;
data?: {
url?: string;
};
}
export type VoidFile = { export type VoidFile = {
id: string, id: string,
meta?: VoidFileMeta meta?: VoidFileMeta