Skip to content

Commit b9e5019

Browse files
Merge pull request #35 from cesardeazevedo/file-upload2
Simplify file upload interface
2 parents 439e11c + f735541 commit b9e5019

25 files changed

+259
-571
lines changed

README.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,23 @@ To handle image uploads with nostr-editor, you can configure the extension as fo
165165

166166
```ts
167167
NostrExtension.configure({
168-
image: {
169-
defaultUploadUrl: 'https://nostr.build',
170-
defaultUploadType: 'nip96', // or blossom
171-
},
172-
video: {
173-
defaultUploadUrl: 'https://nostr.build',
174-
defaultUploadType: 'nip96', // or blossom
175-
},
176168
fileUpload: {
169+
uploadFile: (attrs: FileAttributes) => {
170+
// If something went wrong, return an error string
171+
if (error) {
172+
return {error}
173+
}
174+
175+
// Upload attrs.file and return an UploadTask
176+
return {
177+
result: {
178+
url, // The file url
179+
sha256, // The file hash
180+
tags, // Additional nostr tags to be added to imeta. Refer to NIP 94 for recommended tags.
181+
// url, x, ox, m, and size are automatically inferred but can be overridden
182+
},
183+
}
184+
},
177185
immediateUpload: true, // It will automatically upload when a file is added to the editor, if false, call `editor.commands.uploadFiles()` manually
178186
sign: async (event) => {
179187
if ('nostr' in window) {

examples/react/src/App.tsx

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ export type NostrExtension = {
3939
signEvent(event: EventTemplate): Promise<NostrEvent>
4040
}
4141

42+
export function bufferToHex(buffer: ArrayBuffer) {
43+
return Array.from(new Uint8Array(buffer))
44+
.map((b) => b.toString(16).padStart(2, '0'))
45+
.join('')
46+
}
47+
4248
function App() {
4349
const [raw, setRaw] = useState('')
4450
const [type, setType] = useState<EditorType>('text')
@@ -181,23 +187,18 @@ function App() {
181187
},
182188
},
183189
link: { autolink: type === 'markdown' },
184-
video: {
185-
defaultUploadUrl: 'https://nostr.build',
186-
defaultUploadType: 'nip96',
187-
},
188-
image: {
189-
defaultUploadUrl: 'https://nostr.build',
190-
defaultUploadType: 'nip96',
191-
},
192190
fileUpload: settings.fileUpload !== false && {
193191
immediateUpload: false,
194-
sign: async (event) => {
195-
if ('nostr' in window) {
196-
const nostr = window.nostr as NostrExtension
197-
return await nostr.signEvent(event)
192+
upload: async (attrs) => {
193+
const hash = bufferToHex(await crypto.subtle.digest('SHA-256', await attrs.file.arrayBuffer()))
194+
195+
return {
196+
result: {
197+
url: URL.createObjectURL(attrs.file),
198+
sha256: hash,
199+
tags: [],
200+
},
198201
}
199-
console.error('No nostr extension found')
200-
return Promise.reject('No signer found, install a nostr browser extension')
201202
},
202203
onDrop() {
203204
setPending(true)

examples/react/src/components/Image/ImageEditor.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ import type { ImageAttributes } from 'nostr-editor'
55
import { AltButton } from '../AltChip'
66
import { DeleteButton } from '../DeleteButton'
77
import { MediaFooter } from '../MediaFooter'
8-
import { UploadChip } from '../UploadChip'
98
import { UploadingProgress } from '../UploadingProgress'
109
import { Image } from './Image'
1110

1211
export function ImageEditor(props: NodeViewProps) {
13-
const { src, alt, uploadUrl, uploading, uploadError } = props.node.attrs as ImageAttributes
14-
const isUploaded = !src.startsWith('blob:http')
12+
const { src, alt, sha256, uploading, error } = props.node.attrs as ImageAttributes
13+
const isUploaded = Boolean(sha256)
1514
return (
1615
<NodeViewWrapper
1716
data-drag-handle=''
@@ -22,14 +21,6 @@ export function ImageEditor(props: NodeViewProps) {
2221
<Image src={src} />
2322
<MediaFooter>
2423
{!isUploaded && <AltButton value={alt} onChange={(alt) => props.updateAttributes({ alt })} />}
25-
{!isUploaded && (
26-
<UploadChip
27-
uploadUrl={uploadUrl}
28-
onChange={(uploadType, uploadUrl) => {
29-
props.updateAttributes({ uploadType, uploadUrl })
30-
}}
31-
/>
32-
)}
3324
{isUploaded && (
3425
<span data-tooltip={src}>
3526
<IconCheck
@@ -39,8 +30,8 @@ export function ImageEditor(props: NodeViewProps) {
3930
/>
4031
</span>
4132
)}
42-
{uploadError && (
43-
<span data-tooltip={uploadError} className=''>
33+
{error && (
34+
<span data-tooltip={error} className=''>
4435
<IconFileX
4536
size={28}
4637
strokeWidth='1.5'

examples/react/src/components/UploadChip.tsx

Lines changed: 0 additions & 76 deletions
This file was deleted.

examples/react/src/components/Video/VideoEditor.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ import type { VideoAttributes } from 'nostr-editor'
55
import { AltButton } from '../AltChip'
66
import { DeleteButton } from '../DeleteButton'
77
import { MediaFooter } from '../MediaFooter'
8-
import { UploadChip } from '../UploadChip'
98
import { UploadingProgress } from '../UploadingProgress'
109
import { Video } from './Video'
1110

1211
export function VideoEditor(props: NodeViewProps) {
13-
const { src, alt, uploadError, uploadUrl, uploading } = props.node.attrs as VideoAttributes
14-
const isUploaded = !src.startsWith('blob:http')
12+
const { src, alt, sha256, error, uploading } = props.node.attrs as VideoAttributes
13+
const isUploaded = Boolean(sha256)
1514
return (
1615
<NodeViewWrapper
1716
data-drag-handle=''
@@ -22,12 +21,6 @@ export function VideoEditor(props: NodeViewProps) {
2221
<Video controls={false} src={src} />
2322
<MediaFooter>
2423
{!isUploaded ? <AltButton value={alt} onChange={(alt) => props.updateAttributes({ alt })} /> : <div />}
25-
{!isUploaded && (
26-
<UploadChip
27-
uploadUrl={uploadUrl}
28-
onChange={(uploadType, uploadUrl) => props.updateAttributes({ uploadType, uploadUrl })}
29-
/>
30-
)}
3124
{isUploaded && (
3225
<span data-tooltip={src}>
3326
<IconCheck
@@ -37,8 +30,8 @@ export function VideoEditor(props: NodeViewProps) {
3730
/>
3831
</span>
3932
)}
40-
{uploadError && (
41-
<span data-tooltip={uploadError} className=''>
33+
{error && (
34+
<span data-tooltip={error} className=''>
4235
<IconFileX
4336
size={28}
4437
strokeWidth='1.5'

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
"eslint-plugin-prettier": "^5.2.1",
4747
"globals": "^15.9.0",
4848
"happy-dom": "^15.0.0",
49-
"msw": "2.5.2",
5049
"prettier": "^3.3.3",
5150
"prosemirror-view": "^1.39.3",
5251
"rollup-plugin-peer-deps-external": "^2.2.4",
@@ -66,7 +65,7 @@
6665
"@tiptap/extension-link": "^2.6.6",
6766
"@tiptap/pm": "^2.6.6",
6867
"linkifyjs": "^4.1.3",
69-
"nostr-tools": "~2.12.0",
68+
"nostr-tools": "^2.14.2",
7069
"prosemirror-markdown": "^1.13.0",
7170
"prosemirror-model": "^1.22.3",
7271
"prosemirror-state": "^1.4.3",

0 commit comments

Comments
 (0)