diff --git a/package.json b/package.json
index d6aa0445..1bfbab73 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,7 @@
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-hover-card": "^1.0.7",
"@radix-ui/react-popover": "^1.0.7",
+ "@radix-ui/react-toolbar": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-query": "^4.36.1",
"@tauri-apps/api": "2.0.0-alpha.8",
@@ -48,6 +49,7 @@
"@tauri-apps/plugin-updater": "2.0.0-alpha.1",
"@tauri-apps/plugin-upload": "2.0.0-alpha.1",
"@tauri-apps/plugin-window": "2.0.0-alpha.1",
+ "@tiptap/extension-character-count": "^2.1.12",
"@tiptap/extension-image": "^2.1.12",
"@tiptap/extension-mention": "^2.1.12",
"@tiptap/extension-placeholder": "^2.1.12",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 13e6c655..052cff96 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -44,6 +44,9 @@ dependencies:
'@radix-ui/react-popover':
specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-toolbar':
+ specifier: ^1.0.4
+ version: 1.0.4(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-tooltip':
specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0)
@@ -95,6 +98,9 @@ dependencies:
'@tauri-apps/plugin-window':
specifier: 2.0.0-alpha.1
version: 2.0.0-alpha.1
+ '@tiptap/extension-character-count':
+ specifier: ^2.1.12
+ version: 2.1.12(@tiptap/core@2.1.12)(@tiptap/pm@2.1.12)
'@tiptap/extension-image':
specifier: ^2.1.12
version: 2.1.12(@tiptap/core@2.1.12)
@@ -1551,6 +1557,27 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
+ /@radix-ui/react-separator@1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.2
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0)
+ '@types/react': 18.2.29
+ '@types/react-dom': 18.2.14
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-slot@1.0.2(@types/react@18.2.29)(react@18.2.0):
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
peerDependencies:
@@ -1566,6 +1593,83 @@ packages:
react: 18.2.0
dev: false
+ /@radix-ui/react-toggle-group@1.0.4(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.2
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.29)(react@18.2.0)
+ '@radix-ui/react-direction': 1.0.1(@types/react@18.2.29)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-toggle': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.29)(react@18.2.0)
+ '@types/react': 18.2.29
+ '@types/react-dom': 18.2.14
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
+ /@radix-ui/react-toggle@1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.2
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.29)(react@18.2.0)
+ '@types/react': 18.2.29
+ '@types/react-dom': 18.2.14
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
+ /@radix-ui/react-toolbar@1.0.4(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-tBgmM/O7a07xbaEkYJWYTXkIdU/1pW4/KZORR43toC/4XWyBCURK0ei9kMUdp+gTPPKBgYLxXmRSH1EVcIDp8Q==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.2
+ '@radix-ui/primitive': 1.0.1
+ '@radix-ui/react-context': 1.0.1(@types/react@18.2.29)(react@18.2.0)
+ '@radix-ui/react-direction': 1.0.1(@types/react@18.2.29)(react@18.2.0)
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-separator': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-toggle-group': 1.0.4(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0)
+ '@types/react': 18.2.29
+ '@types/react-dom': 18.2.14
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-tooltip@1.0.7(@types/react-dom@18.2.14)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==}
peerDependencies:
@@ -2279,6 +2383,16 @@ packages:
'@tiptap/core': 2.1.12(@tiptap/pm@2.1.12)
dev: false
+ /@tiptap/extension-character-count@2.1.12(@tiptap/core@2.1.12)(@tiptap/pm@2.1.12):
+ resolution: {integrity: sha512-+GFbBG13nvF8mFIeisSERG/Q3CuRsTNwVZIRbJTLgGdbHXFqPhJh4Xfm7cv7OaOYevUlVyO+z5pGD7wIl1bLqQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.0.0
+ '@tiptap/pm': ^2.0.0
+ dependencies:
+ '@tiptap/core': 2.1.12(@tiptap/pm@2.1.12)
+ '@tiptap/pm': 2.1.12
+ dev: false
+
/@tiptap/extension-code-block@2.1.12(@tiptap/core@2.1.12)(@tiptap/pm@2.1.12):
resolution: {integrity: sha512-RXtSYCVsnk8D+K80uNZShClfZjvv1EgO42JlXLVGWQdIgaNyuOv/6I/Jdf+ZzhnpsBnHufW+6TJjwP5vJPSPHA==}
peerDependencies:
diff --git a/src/app/notes/components/editor/article.tsx b/src/app/notes/components/editor/article.tsx
new file mode 100644
index 00000000..dc566182
--- /dev/null
+++ b/src/app/notes/components/editor/article.tsx
@@ -0,0 +1,124 @@
+import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
+import CharacterCount from '@tiptap/extension-character-count';
+import Image from '@tiptap/extension-image';
+import Placeholder from '@tiptap/extension-placeholder';
+import { EditorContent, useEditor } from '@tiptap/react';
+import StarterKit from '@tiptap/starter-kit';
+import { convert } from 'html-to-text';
+import { useState } from 'react';
+import { toast } from 'sonner';
+
+import { useNDK } from '@libs/ndk/provider';
+
+import { MediaUploader, MentionPopup } from '@shared/composer';
+import { LoaderIcon } from '@shared/icons';
+
+export function ArticleEditor() {
+ const { ndk } = useNDK();
+ const [loading, setLoading] = useState(false);
+
+ const editor = useEditor({
+ extensions: [
+ StarterKit.configure(),
+ Placeholder.configure({ placeholder: 'Type something...' }),
+ Image.configure({
+ HTMLAttributes: {
+ class:
+ 'rounded-lg w-full object-cover h-auto max-h-[400px] border border-neutral-200 dark:border-neutral-800 outline outline-1 outline-offset-0 outline-neutral-300 dark:outline-neutral-700',
+ },
+ }),
+ CharacterCount.configure(),
+ ],
+ content: JSON.parse(localStorage.getItem('editor-post') || '{}'),
+ editorProps: {
+ attributes: {
+ class:
+ 'outline-none prose prose-lg prose-neutral max-w-none select-text whitespace-pre-line break-words dark:prose-invert hover:prose-a:text-blue-500',
+ },
+ },
+ onUpdate: ({ editor }) => {
+ const jsonContent = JSON.stringify(editor.getJSON());
+ localStorage.setItem('editor-post', jsonContent);
+ },
+ });
+
+ const submit = async () => {
+ try {
+ setLoading(true);
+
+ // get plaintext content
+ const html = editor.getHTML();
+ const serializedContent = convert(html, {
+ selectors: [
+ { selector: 'a', options: { linkBrackets: false } },
+ { selector: 'img', options: { linkBrackets: false } },
+ ],
+ });
+
+ // define tags
+ const tags: string[][] = [];
+
+ // add hashtag to tags if present
+ const hashtags = serializedContent
+ .split(/\s/gm)
+ .filter((s: string) => s.startsWith('#'));
+ hashtags?.forEach((tag: string) => {
+ tags.push(['t', tag.replace('#', '')]);
+ });
+
+ // publish message
+ const event = new NDKEvent(ndk);
+ event.content = serializedContent;
+ event.kind = NDKKind.Article;
+ event.tags = tags;
+
+ const publishedRelays = await event.publish();
+ if (publishedRelays) {
+ toast.success(`Broadcasted to ${publishedRelays.size} relays successfully.`);
+ // update state
+ setLoading(false);
+ // reset editor
+ editor.commands.clearContent();
+ localStorage.setItem('editor-post', '{}');
+ }
+ } catch (e) {
+ setLoading(false);
+ toast.error(e);
+ }
+ };
+
+ return (
+
+
+
+
+ {editor?.storage?.characterCount.characters()}
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/notes/components/editor/file.tsx b/src/app/notes/components/editor/file.tsx
new file mode 100644
index 00000000..817d7d13
--- /dev/null
+++ b/src/app/notes/components/editor/file.tsx
@@ -0,0 +1,162 @@
+import { NDKEvent } from '@nostr-dev-kit/ndk';
+import { message, open } from '@tauri-apps/plugin-dialog';
+import { readBinaryFile } from '@tauri-apps/plugin-fs';
+import { useState } from 'react';
+import { toast } from 'sonner';
+
+import { useNDK } from '@libs/ndk/provider';
+
+import { LoaderIcon } from '@shared/icons';
+
+export function FileEditor() {
+ const { ndk } = useNDK();
+
+ const [loading, setLoading] = useState(false);
+ const [isPublish, setIsPublish] = useState(false);
+ const [metadata, setMetadata] = useState(null);
+ const [caption, setCaption] = useState('');
+
+ const uploadFile = async () => {
+ try {
+ setLoading(true);
+
+ const selected = await open({
+ multiple: false,
+ filters: [
+ {
+ name: 'Media',
+ extensions: [
+ 'png',
+ 'jpeg',
+ 'jpg',
+ 'gif',
+ 'mp4',
+ 'mp3',
+ 'webm',
+ 'mkv',
+ 'avi',
+ 'mov',
+ ],
+ },
+ ],
+ });
+
+ if (!selected) {
+ setLoading(false);
+ return;
+ }
+
+ const file = await readBinaryFile(selected.path);
+ const blob = new Blob([file]);
+
+ const data = new FormData();
+ data.append('fileToUpload', blob);
+ data.append('submit', 'Upload Image');
+
+ const res = await fetch('https://nostr.build/api/v2/upload/files', {
+ method: 'POST',
+ body: data,
+ });
+
+ if (res.ok) {
+ const json = await res.json();
+ const data = json.data[0];
+
+ setMetadata([
+ ['url', data.url],
+ ['m', data.mime ?? 'application/octet-stream'],
+ ['x', data.sha256 ?? ''],
+ ['size', data.size.toString() ?? '0'],
+ ['dim', `${data.dimensions.width}x${data.dimensions.height}` ?? '0'],
+ ['blurhash', data.blurhash ?? ''],
+ ['thumb', data.thumbnail ?? ''],
+ ]);
+
+ // stop loading
+ setLoading(false);
+ }
+ } catch (e) {
+ // stop loading
+ setLoading(false);
+ await message(`Upload failed, error: ${e}`, { title: 'Lume', type: 'error' });
+ }
+ };
+
+ const submit = async () => {
+ try {
+ setIsPublish(true);
+
+ const event = new NDKEvent(ndk);
+ event.content = caption;
+ event.kind = 1063;
+ event.tags = metadata;
+
+ const publishedRelays = await event.publish();
+ if (publishedRelays) {
+ setMetadata(null);
+ setIsPublish(false);
+ toast.success(`Broadcasted to ${publishedRelays.size} relays successfully.`);
+ }
+ } catch (e) {
+ setIsPublish(false);
+ toast.error(e);
+ }
+ };
+
+ return (
+
+
+
+
+
+ setCaption(e.target.value)}
+ spellCheck={false}
+ autoComplete="off"
+ autoCorrect="off"
+ autoCapitalize="off"
+ placeholder="Caption (Optional)..."
+ className="h-11 flex-1 rounded-lg bg-neutral-100 px-3 placeholder:text-neutral-500 dark:bg-neutral-900 dark:placeholder:text-neutral-400"
+ />
+
+
+
+
+
+ );
+}
diff --git a/src/app/notes/components/editor/post.tsx b/src/app/notes/components/editor/post.tsx
index de650e81..60a90a88 100644
--- a/src/app/notes/components/editor/post.tsx
+++ b/src/app/notes/components/editor/post.tsx
@@ -1,25 +1,39 @@
+import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
+import CharacterCount from '@tiptap/extension-character-count';
import Image from '@tiptap/extension-image';
import Placeholder from '@tiptap/extension-placeholder';
import { EditorContent, useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
+import { convert } from 'html-to-text';
+import { useState } from 'react';
+import { toast } from 'sonner';
+
+import { useNDK } from '@libs/ndk/provider';
+
+import { MediaUploader, MentionPopup } from '@shared/composer';
+import { LoaderIcon } from '@shared/icons';
export function PostEditor() {
+ const { ndk } = useNDK();
+ const [loading, setLoading] = useState(false);
+
const editor = useEditor({
extensions: [
StarterKit.configure(),
- Placeholder.configure({ placeholder: 'Type something...' }),
+ Placeholder.configure({ placeholder: 'Sharing some thoughts...' }),
Image.configure({
HTMLAttributes: {
class:
- 'rounded-lg w-full h-auto border border-white/10 outline outline-2 outline-offset-0 outline-white/20 ml-1',
+ 'rounded-lg w-full object-cover h-auto max-h-[400px] border border-neutral-200 dark:border-neutral-800 outline outline-1 outline-offset-0 outline-neutral-300 dark:outline-neutral-700',
},
}),
+ CharacterCount.configure(),
],
content: JSON.parse(localStorage.getItem('editor-post') || '{}'),
editorProps: {
attributes: {
class:
- 'h-full outline-none prose prose-lg prose-neutral max-w-none select-text whitespace-pre-line break-words dark:prose-invert hover:prose-a:text-blue-500',
+ 'outline-none prose prose-lg prose-neutral max-w-none select-text whitespace-pre-line break-words dark:prose-invert hover:prose-a:text-blue-500',
},
},
onUpdate: ({ editor }) => {
@@ -28,13 +42,79 @@ export function PostEditor() {
},
});
+ const submit = async () => {
+ try {
+ setLoading(true);
+
+ // get plaintext content
+ const html = editor.getHTML();
+ const serializedContent = convert(html, {
+ selectors: [
+ { selector: 'a', options: { linkBrackets: false } },
+ { selector: 'img', options: { linkBrackets: false } },
+ ],
+ });
+
+ // define tags
+ const tags: string[][] = [];
+
+ // add hashtag to tags if present
+ const hashtags = serializedContent
+ .split(/\s/gm)
+ .filter((s: string) => s.startsWith('#'));
+ hashtags?.forEach((tag: string) => {
+ tags.push(['t', tag.replace('#', '')]);
+ });
+
+ // publish message
+ const event = new NDKEvent(ndk);
+ event.content = serializedContent;
+ event.kind = NDKKind.Text;
+ event.tags = tags;
+
+ const publishedRelays = await event.publish();
+ if (publishedRelays) {
+ toast.success(`Broadcasted to ${publishedRelays.size} relays successfully.`);
+ // update state
+ setLoading(false);
+ // reset editor
+ editor.commands.clearContent();
+ localStorage.setItem('editor-post', '{}');
+ }
+ } catch (e) {
+ setLoading(false);
+ toast.error(e);
+ }
+ };
+
return (
-
+
+
+
+
+ {editor?.storage?.characterCount.characters()}
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/src/app/notes/components/index.ts b/src/app/notes/components/index.ts
index bfd163e0..098a1ce3 100644
--- a/src/app/notes/components/index.ts
+++ b/src/app/notes/components/index.ts
@@ -1 +1,3 @@
export * from './editor/post';
+export * from './editor/article';
+export * from './editor/file';
diff --git a/src/app/notes/new.tsx b/src/app/notes/new.tsx
index b7c95886..ea5034d1 100644
--- a/src/app/notes/new.tsx
+++ b/src/app/notes/new.tsx
@@ -2,73 +2,84 @@ import { useState } from 'react';
import { Link } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';
-import { PostEditor } from '@app/notes/components';
+import { ArticleEditor, FileEditor, PostEditor } from '@app/notes/components';
import { ArrowLeftIcon } from '@shared/icons';
export function NewNoteScreen() {
const [type, setType] = useState<'post' | 'article' | 'file' | 'raw'>('post');
+ const renderEditor = () => {
+ switch (type) {
+ case 'post':
+ return ;
+ case 'article':
+ return ;
+ case 'file':
+ return ;
+ default:
+ return null;
+ }
+ };
+
return (
-
-
-
-
-
-
-
-
-
-
-
-
- {type === 'post' ?
: null}
-
-
+
+
+
+
+
+
+
+
+
+
+
+
{renderEditor()}
+
+
);
}
diff --git a/src/libs/storage/instance.ts b/src/libs/storage/instance.ts
index 5ac8499d..a876b033 100644
--- a/src/libs/storage/instance.ts
+++ b/src/libs/storage/instance.ts
@@ -52,10 +52,10 @@ export class LumeStorage {
const account = results[0];
if (typeof account.follows === 'string')
- account.follows = JSON.parse(account.follows);
+ account.follows = JSON.parse(account.follows) ?? [];
if (typeof account.circles === 'string')
- account.circles = JSON.parse(account.circles);
+ account.circles = JSON.parse(account.circles) ?? [];
if (typeof account.last_login_at === 'string')
account.last_login_at = parseInt(account.last_login_at);
diff --git a/src/main.jsx b/src/main.jsx
index 6048b434..1e152d8e 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -16,7 +16,7 @@ root.render(
-
+
diff --git a/src/shared/composer/mediaUploader.tsx b/src/shared/composer/mediaUploader.tsx
index 4c23a859..947b64ee 100644
--- a/src/shared/composer/mediaUploader.tsx
+++ b/src/shared/composer/mediaUploader.tsx
@@ -1,24 +1,61 @@
-import { UnlistenFn, listen } from '@tauri-apps/api/event';
-import { message } from '@tauri-apps/plugin-dialog';
+import { message, open } from '@tauri-apps/plugin-dialog';
+import { readBinaryFile } from '@tauri-apps/plugin-fs';
import { Editor } from '@tiptap/react';
-import { useEffect, useState } from 'react';
+import { useState } from 'react';
import { MediaIcon } from '@shared/icons';
-import { useNostr } from '@utils/hooks/useNostr';
-
export function MediaUploader({ editor }: { editor: Editor }) {
- const { upload } = useNostr();
const [loading, setLoading] = useState(false);
- const uploadToNostrBuild = async (file?: string) => {
+ const uploadToNostrBuild = async () => {
try {
// start loading
setLoading(true);
- const image = await upload(file, true);
- if (image.url) {
- editor.commands.setImage({ src: image.url });
+ const selected = await open({
+ multiple: false,
+ filters: [
+ {
+ name: 'Media',
+ extensions: [
+ 'png',
+ 'jpeg',
+ 'jpg',
+ 'gif',
+ 'mp4',
+ 'mp3',
+ 'webm',
+ 'mkv',
+ 'avi',
+ 'mov',
+ ],
+ },
+ ],
+ });
+
+ if (!selected) {
+ setLoading(false);
+ return;
+ }
+
+ const file = await readBinaryFile(selected.path);
+ const blob = new Blob([file]);
+
+ const data = new FormData();
+ data.append('fileToUpload', blob);
+ data.append('submit', 'Upload Image');
+
+ const res = await fetch('https://nostr.build/api/v2/upload/files', {
+ method: 'POST',
+ body: data,
+ });
+
+ if (res.ok) {
+ const json = await res.json();
+ const content = json.data[0];
+
+ editor.commands.setImage({ src: content.url });
editor.commands.createParagraphNear();
// stop loading
@@ -31,29 +68,11 @@ export function MediaUploader({ editor }: { editor: Editor }) {
}
};
- useEffect(() => {
- let unlisten: UnlistenFn;
-
- async function listenDnD() {
- unlisten = await listen('tauri://file-drop', (event) => {
- uploadToNostrBuild(event.payload[0]);
- });
- }
-
- // start listen drag and drop event
- listenDnD();
-
- // clean up
- return () => {
- unlisten();
- };
- }, []);
-
return (