BlockNote
BlockNote copied to clipboard
Feature Request: more secure way to get image URL and deleteFile callback
I love the image block feature and the uploadFile
method in the editor that allows you to define a custom function for uploading images to your server. To improve on this, I wish there would be an equivalent deleteFile
callback invoked when the image block is deleted so we can delete the image from our database to save resources. I also think that defining the image as just a public URL is not the most secure, and we could also define a getFile
method that will allow developers to define a custom function to retrieve a signed URL that expires after a certain duration.
I recently used the library and felt the same blocker. Requirement of deleteFile
is ➕ ➕ .
@matthewlipski I'm interested in contributing to it. Let me know if I can take this.
Thanks. Both deleteFile
and getFile
are good suggestions.
What's tricky with deleteFile
this is that a user could delete a block and then re-add it using cmd/ctrl+z. How would you go about this? Happy to accept contributions ofc!
Hey, any updates on deleteFile
functionality?
Not at the moment unfortunately:/ the updates to file blocks were done as part of a client request so most likely deleteFile
will be added also from client feedback or when we have time to implement it after some higher prio issues.
I think the most direct approach to the undo problem is to provide a message to the user if a file is not successfully loaded indicating that the file is not available, and that the block should be removed and a new one added.
A more sophisticated approach would be to provide a reload option, which would replace an existing file or supply a missing file. This would require a fileDelete trigger for any replaced file.
How do you handle duplicate file names?
In the meantime, is there a way to extend the editor specs for useCreateBlockNote to include a onDeleteFile
property?
Here's what I'm doing in the meantime (using firebase): My editor is modal ('display', 'edit').
When uploading a file, I record the filename in an array, saved to the same firestore document as the editor content
async function uploadFile(file:File) {
if (editorUploadedFiles.includes(file.name)) { // avoid invalidation of the first
alert(file.name + ' has already been uploaded') // placeholder
return null
}
const fileRef = ref(storage, workboxHandler.editRecord.profile.workbox.id + '/document/' + file.name)
try {
await uploadBytes(fileRef, file)
} catch (error) {
console.log('An error occured uploading file.name', file.name)
alert (error.message) // placeholder
return null
}
editorUploadedFiles.push(file.name) // <<== save uploaded file name
const url = await getDownloadURL(fileRef)
return url
}
Deletions in edit mode before save make no change to the file list, or the storage files (which takes care of the Ctrl-Z problem).
On save, I do this (function tucked away in a class)
// deletes storage files not in editor files, before firestore save of edited document
reconcileUploadedFiles = (uploadedFiles, editorFiles) => {
// avoid duplicates
const uploadedFileSet = new Set(uploadedFiles)
const editorFileSet = new Set(editorFiles)
this.removeOrphans(uploadedFileSet, editorFileSet, this)
// refreshes firestore document files list to match editor files, in place
uploadedFiles.length = 0
editorFileSet.forEach((filename) => {
uploadedFiles.push(filename)
})
}
On cancel I do something similar, but with the cancelled uploadedFiles list compared to the previous media editor blocks
// takes edited file list and compares with pre-edit editor.content
revertUploadedFiles = (uploadedFiles, editorFiles) => {
// avoid duplicates
const uploadedFileSet = new Set(uploadedFiles)
const editorFileSet = new Set(editorFiles)
this.removeOrphans(uploadedFileSet, editorFileSet, this)
}
Here is the common removeOrphans
private removeOrphans = (uploadedFileSet, editorFileSet, self) => {
// delete storage files not referenced in editor files
uploadedFileSet.forEach(async function (filename) {
if (!editorFileSet.has(filename)) {
const fileRef = ref(self.internal.storage,self.workboxRecord.profile.workbox.id + '/document/' + filename )
try {
await deleteObject(fileRef)
} catch (error) {
console.log('error deleting file for revert', filename, error.message)
}
}
})
}
Still needs a recovery routine in case an internet break scrambles things, but I think this will do.
Getting the editor media file names is of course trivial
getEditorFiles = (editorDocument) => {
const editorFiles = []
editorDocument.forEach((block) => {
if (['image','video','audio','file'].includes(block.type)) {
editorFiles.push(block.props.name)
}
})
return editorFiles
}