diff --git a/src/Const.ts b/src/Const.ts index cd9878c..6f9e080 100644 --- a/src/Const.ts +++ b/src/Const.ts @@ -24,7 +24,9 @@ export const ProfileCacheExpire = (1_000 * 60 * 5); * Default bootstrap relays */ export const DefaultRelays = new Map([ - ["wss://relay.snort.social", { read: true, write: true }] + ["wss://relay.snort.social", { read: true, write: true }], + ["wss://eden.nostr.land", { read: true, write: true }], + ["wss://nostr-pub.semisol.dev", { read: true, write: true }] ]); /** @@ -114,4 +116,4 @@ export const SoundCloudRegex = /soundcloud\.com\/(?!live)([a-zA-Z0-9]+)\/([a-zA- * Mixcloud regex */ -export const MixCloudRegex = /mixcloud\.com\/(?!live)([a-zA-Z0-9]+)\/([a-zA-Z0-9-]+)/ \ No newline at end of file +export const MixCloudRegex = /mixcloud\.com\/(?!live)([a-zA-Z0-9]+)\/([a-zA-Z0-9-]+)/ diff --git a/src/Element/NoteCreator.tsx b/src/Element/NoteCreator.tsx index fb25634..43e8a8b 100644 --- a/src/Element/NoteCreator.tsx +++ b/src/Element/NoteCreator.tsx @@ -7,11 +7,10 @@ import "./NoteCreator.css"; import Plus from "Icons/Plus"; import useEventPublisher from "Feed/EventPublisher"; import { openFile } from "Util"; -import VoidUpload from "Feed/VoidUpload"; -import { FileExtensionRegex, VoidCatHost } from "Const"; import Textarea from "Element/Textarea"; import Modal from "Element/Modal"; import { default as NEvent } from "Nostr/Event"; +import useFileUpload from "Feed/FileUpload"; export interface NoteCreatorProps { show: boolean @@ -27,6 +26,7 @@ export function NoteCreator(props: NoteCreatorProps) { const [note, setNote] = useState(); const [error, setError] = useState(); const [active, setActive] = useState(false); + const uploader = useFileUpload(); async function sendNote() { if (note) { @@ -46,16 +46,11 @@ export function NoteCreator(props: NoteCreatorProps) { try { let file = await openFile(); if (file) { - let rx = await VoidUpload(file, file.name); - if (rx?.ok && rx?.file) { - let ext = file.name.match(FileExtensionRegex); - - // extension tricks note parser to embed the content - let url = rx.file.meta?.url ?? `${VoidCatHost}/d/${rx.file.id}${ext ? `.${ext[1]}` : ""}`; - - setNote(n => `${n}\n${url}`); - } else if (rx?.errorMessage) { - setError(rx.errorMessage); + let rx = await uploader.upload(file, file.name); + if (rx.url) { + setNote(n => `${n}\n${rx.url}`); + } else if (rx?.error) { + setError(rx.error); } } } catch (error: any) { diff --git a/src/Element/NoteTime.tsx b/src/Element/NoteTime.tsx index 5e27f90..11ce062 100644 --- a/src/Element/NoteTime.tsx +++ b/src/Element/NoteTime.tsx @@ -12,6 +12,8 @@ export interface NoteTimeProps { export default function NoteTime(props: NoteTimeProps) { const [time, setTime] = useState(); const { from, fallback } = props; + const absoluteTime = new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'long'}).format(from); + const isoDate = new Date(from).toISOString(); function calcTime() { let fromDate = new Date(from); @@ -46,5 +48,5 @@ export default function NoteTime(props: NoteTimeProps) { return () => clearInterval(t); }, [from]); - return <>{time} + return } diff --git a/src/Feed/FileUpload.ts b/src/Feed/FileUpload.ts new file mode 100644 index 0000000..1610c85 --- /dev/null +++ b/src/Feed/FileUpload.ts @@ -0,0 +1,30 @@ +import { useSelector } from "react-redux"; +import { RootState } from "State/Store"; +import NostrBuildUpload from "./NostrBuildUpload"; +import VoidUpload from "./VoidUpload"; + +export interface UploadResult { + url?: string, + error?: string +} + +export interface Uploader { + upload: (f: File | Blob, filename: string) => Promise +} + +export default function useFileUpload(): Uploader { + const fileUploader = useSelector((s: RootState) => s.login.preferences.fileUploader); + + switch (fileUploader) { + case "nostr.build": { + return { + upload: NostrBuildUpload + } as Uploader; + } + default: { + return { + upload: VoidUpload + } as Uploader; + } + } +} \ No newline at end of file diff --git a/src/Feed/NostrBuildUpload.ts b/src/Feed/NostrBuildUpload.ts new file mode 100644 index 0000000..9c934ef --- /dev/null +++ b/src/Feed/NostrBuildUpload.ts @@ -0,0 +1,24 @@ +import { UploadResult } from "./FileUpload"; + +export default async function NostrBuildUpload(file: File | Blob): Promise { + let fd = new FormData(); + fd.append("fileToUpload", file); + fd.append("submit", "Upload Image"); + + let rsp = await fetch("https://nostr.build/api/upload/snort.php", { + body: fd, + method: "POST", + headers: { + "accept": "application/json" + } + }); + if(rsp.ok) { + let data = await rsp.json(); + return { + url: new URL(data).toString() + } + } + return { + error: "Upload failed" + } +} \ No newline at end of file diff --git a/src/Feed/VoidUpload.ts b/src/Feed/VoidUpload.ts index 53c4aeb..794d2bd 100644 --- a/src/Feed/VoidUpload.ts +++ b/src/Feed/VoidUpload.ts @@ -1,11 +1,12 @@ import * as secp from "@noble/secp256k1"; -import { VoidCatHost } from "Const"; +import { FileExtensionRegex, VoidCatHost } from "Const"; +import { UploadResult } from "./FileUpload"; /** * Upload file to void.cat * https://void.cat/swagger/index.html */ -export default async function VoidUpload(file: File | Blob, filename: string) { +export default async function VoidUpload(file: File | Blob, filename: string): Promise { const buf = await file.arrayBuffer(); const digest = await crypto.subtle.digest("SHA-256", buf); @@ -25,9 +26,20 @@ export default async function VoidUpload(file: File | Blob, filename: string) { if (req.ok) { let rsp: VoidUploadResponse = await req.json(); - return rsp; + if (rsp.ok) { + let ext = filename.match(FileExtensionRegex); + return { + url: rsp.file?.meta?.url ?? `${VoidCatHost}/d/${rsp.file?.id}${ext ? `.${ext[1]}` : ""}` + } + } else { + return { + error: rsp.errorMessage + } + } } - return null; + return { + error: "Upload failed" + }; } export type VoidUploadResponse = { diff --git a/src/Pages/settings/Preferences.tsx b/src/Pages/settings/Preferences.tsx index 3304868..ee1f50f 100644 --- a/src/Pages/settings/Preferences.tsx +++ b/src/Pages/settings/Preferences.tsx @@ -59,6 +59,18 @@ const PreferencesPage = () => { dispatch(setPreferences({ ...perf, autoShowLatest: e.target.checked }))} /> +
+
+
File upload service
+ Pick which upload service you want to upload attachments to +
+
+ +
+
Debug Menus
diff --git a/src/Pages/settings/Profile.tsx b/src/Pages/settings/Profile.tsx index 90ef5bf..a88f399 100644 --- a/src/Pages/settings/Profile.tsx +++ b/src/Pages/settings/Profile.tsx @@ -15,7 +15,7 @@ import { hexToBech32, openFile } from "Util"; import Copy from "Element/Copy"; import { RootState } from "State/Store"; import { HexKey } from "Nostr"; -import { VoidCatHost } from "Const"; +import useFileUpload from "Feed/FileUpload"; export default function ProfileSettings() { const navigate = useNavigate(); @@ -23,6 +23,7 @@ export default function ProfileSettings() { const privKey = useSelector(s => s.login.privateKey); const user = useUserProfile(id!); const publisher = useEventPublisher(); + const uploader = useFileUpload(); const [name, setName] = useState(); const [displayName, setDisplayName] = useState(); @@ -76,25 +77,25 @@ export default function ProfileSettings() { let file = await openFile(); if (file) { console.log(file); - let rsp = await VoidUpload(file, file.name); - if (!rsp?.ok) { - throw "Upload failed, please try again later"; + let rsp = await uploader.upload(file, file.name); + if (!rsp?.error) { + throw new Error("Upload failed, please try again later"); } - return rsp.file; + return rsp.url; } } async function setNewAvatar() { const rsp = await uploadFile(); if (rsp) { - setPicture(rsp.meta?.url ?? `${VoidCatHost}/d/${rsp.id}`); + setPicture(rsp); } } async function setNewBanner() { const rsp = await uploadFile(); if (rsp) { - setBanner(rsp.meta?.url ?? `${VoidCatHost}/d/${rsp.id}`); + setBanner(rsp); } } diff --git a/src/State/Login.ts b/src/State/Login.ts index 9387882..d7c756d 100644 --- a/src/State/Login.ts +++ b/src/State/Login.ts @@ -48,7 +48,12 @@ export interface UserPreferences { /** * Show debugging menus to help diagnose issues */ - showDebugMenus: boolean + showDebugMenus: boolean, + + /** + * File uploading service to upload attachments to + */ + fileUploader: "void.cat" | "nostr.build" } export interface LoginStore { @@ -149,7 +154,8 @@ const InitState = { theme: "system", confirmReposts: false, showDebugMenus: false, - autoShowLatest: false + autoShowLatest: false, + fileUploader: "void.cat" } } as LoginStore;