fix: Move file upload logic into hook

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-08-21 12:51:04 +02:00
parent ccbbaeb843
commit b797f07aa5
4 changed files with 58 additions and 61 deletions

View file

@ -7,7 +7,7 @@
import { useMemo } from 'react' import { useMemo } from 'react'
import { EditorView } from '@codemirror/view' import { EditorView } from '@codemirror/view'
import type { Extension } from '@codemirror/state' import type { Extension } from '@codemirror/state'
import { handleUpload } from '../use-handle-upload' import { useHandleUpload } from '../use-handle-upload'
import { Optional } from '@mrdrogdrog/optional' import { Optional } from '@mrdrogdrog/optional'
const calculateCursorPositionInEditor = (view: EditorView, event: MouseEvent): number => { const calculateCursorPositionInEditor = (view: EditorView, event: MouseEvent): number => {
@ -32,6 +32,8 @@ const extractFirstFile = (fileList?: FileList): Optional<File> => {
* @return the code mirror callback * @return the code mirror callback
*/ */
export const useCodeMirrorFileInsertExtension = (): Extension => { export const useCodeMirrorFileInsertExtension = (): Extension => {
const handleUpload = useHandleUpload()
return useMemo(() => { return useMemo(() => {
return EditorView.domEventHandlers({ return EditorView.domEventHandlers({
drop: (event, view) => { drop: (event, view) => {
@ -47,5 +49,5 @@ export const useCodeMirrorFileInsertExtension = (): Extension => {
}) })
} }
}) })
}, []) }, [handleUpload])
} }

View file

@ -14,6 +14,7 @@ import { findRegexMatchInText } from './find-regex-match-in-text'
import { Optional } from '@mrdrogdrog/optional' import { Optional } from '@mrdrogdrog/optional'
import { useHandleUpload } from '../use-handle-upload' import { useHandleUpload } from '../use-handle-upload'
import type { CursorSelection } from '../../tool-bar/formatters/types/cursor-selection' import type { CursorSelection } from '../../tool-bar/formatters/types/cursor-selection'
import { useCodeMirrorReference } from '../../../change-content-context/change-content-context'
const log = new Logger('useOnImageUpload') const log = new Logger('useOnImageUpload')
const imageWithPlaceholderLinkRegex = /!\[([^\]]*)]\(https:\/\/([^)]*)\)/g const imageWithPlaceholderLinkRegex = /!\[([^\]]*)]\(https:\/\/([^)]*)\)/g
@ -22,12 +23,17 @@ const imageWithPlaceholderLinkRegex = /!\[([^\]]*)]\(https:\/\/([^)]*)\)/g
* Receives {@link CommunicationMessageType.IMAGE_UPLOAD image upload events} via iframe communication and processes the attached uploads. * Receives {@link CommunicationMessageType.IMAGE_UPLOAD image upload events} via iframe communication and processes the attached uploads.
*/ */
export const useOnImageUploadFromRenderer = (): void => { export const useOnImageUploadFromRenderer = (): void => {
const codeMirrorReference = useCodeMirrorReference()
const handleUpload = useHandleUpload() const handleUpload = useHandleUpload()
useEditorReceiveHandler( useEditorReceiveHandler(
CommunicationMessageType.IMAGE_UPLOAD, CommunicationMessageType.IMAGE_UPLOAD,
useCallback( useCallback(
(values: ImageUploadMessage) => { (values: ImageUploadMessage) => {
if (codeMirrorReference === undefined) {
log.error("Can't upload image without codemirror reference")
return
}
const { dataUri, fileName, lineIndex, placeholderIndexInLine } = values const { dataUri, fileName, lineIndex, placeholderIndexInLine } = values
if (!dataUri.startsWith('data:image/')) { if (!dataUri.startsWith('data:image/')) {
log.error('Received uri is no data uri and image!') log.error('Received uri is no data uri and image!')
@ -44,11 +50,11 @@ export const useOnImageUploadFromRenderer = (): void => {
return findPlaceholderInMarkdownContent(actualLineIndex + lineOffset, placeholderIndexInLine) return findPlaceholderInMarkdownContent(actualLineIndex + lineOffset, placeholderIndexInLine)
}) })
.orElse({} as ExtractResult) .orElse({} as ExtractResult)
handleUpload(file, cursorSelection, alt, title) handleUpload(codeMirrorReference, file, cursorSelection, alt, title)
}) })
.catch((error) => log.error(error)) .catch((error) => log.error(error))
}, },
[handleUpload] [codeMirrorReference, handleUpload]
) )
) )
} }

View file

@ -8,7 +8,6 @@ import { uploadFile } from '../../../../api/media'
import { getGlobalState } from '../../../../redux' import { getGlobalState } from '../../../../redux'
import { supportedMimeTypes } from '../../../common/upload-image-mimetypes' import { supportedMimeTypes } from '../../../common/upload-image-mimetypes'
import { t } from 'i18next' import { t } from 'i18next'
import { showErrorNotification } from '../../../../redux/ui-notifications/methods'
import { useCallback } from 'react' import { useCallback } from 'react'
import { changeEditorContent } from '../../change-content-context/use-change-editor-content-callback' import { changeEditorContent } from '../../change-content-context/use-change-editor-content-callback'
import { replaceSelection } from '../tool-bar/formatters/replace-selection' import { replaceSelection } from '../tool-bar/formatters/replace-selection'
@ -16,74 +15,57 @@ import { replaceInContent } from '../tool-bar/formatters/replace-in-content'
import type { CursorSelection } from '../tool-bar/formatters/types/cursor-selection' import type { CursorSelection } from '../tool-bar/formatters/types/cursor-selection'
import type { EditorView } from '@codemirror/view' import type { EditorView } from '@codemirror/view'
import type { ContentFormatter } from '../../change-content-context/change-content-context' import type { ContentFormatter } from '../../change-content-context/change-content-context'
import { useCodeMirrorReference } from '../../change-content-context/change-content-context' import { showErrorNotification } from '../../../../redux/ui-notifications/methods'
/** /**
* Processes the upload of the given file and inserts the correct Markdown code.
*
* @param view the codemirror instance that is used to insert the Markdown code * @param view the codemirror instance that is used to insert the Markdown code
* @param file The file to upload * @param file The file to upload
* @param cursorSelection The position where the progress message should be placed * @param cursorSelection The position where the progress message should be placed
* @param description The text that should be used in the description part of the resulting image tag * @param description The text that should be used in the description part of the resulting image tag
* @param additionalUrlText Additional text that should be inserted behind the link but within the tag * @param additionalUrlText Additional text that should be inserted behind the link but within the tag
*/ */
export const handleUpload = ( type handleUploadSignature = (
view: EditorView, view: EditorView,
file: File, file: File,
cursorSelection?: CursorSelection, cursorSelection?: CursorSelection,
description?: string, description?: string,
additionalUrlText?: string additionalUrlText?: string
): void => { ) => void
const changeContent = (callback: ContentFormatter) => changeEditorContent(view, callback)
if (!file || !supportedMimeTypes.includes(file.type) || !changeContent) {
return
}
const randomId = Math.random().toString(36).slice(7)
const uploadFileInfo = description
? t('editor.upload.uploadFile.withDescription', { fileName: file.name, description: description })
: t('editor.upload.uploadFile.withoutDescription', { fileName: file.name })
const uploadPlaceholder = `![${uploadFileInfo}](upload-${randomId}${additionalUrlText ?? ''})`
const noteId = getGlobalState().noteDetails.id
changeContent(({ currentSelection }) => {
return replaceSelection(cursorSelection ?? currentSelection, uploadPlaceholder, false)
})
uploadFile(noteId, file)
.then(({ url }) => {
const replacement = `![${description ?? file.name ?? ''}](${url}${additionalUrlText ?? ''})`
changeContent(({ markdownContent }) => [
replaceInContent(markdownContent, uploadPlaceholder, replacement),
undefined
])
})
.catch((error: Error) => {
showErrorNotification('editor.upload.failed', { fileName: file.name })(error)
const replacement = `![upload of ${file.name} failed]()`
changeContent(({ markdownContent }) => [
replaceInContent(markdownContent, uploadPlaceholder, replacement),
undefined
])
})
}
/** /**
* Provides a callback that uploads the given file and writes the progress into the given editor at the given cursor positions. * Provides a callback that uploads a given file and inserts the correct Markdown code into the current editor.
*
* @return The generated callback
*/ */
export const useHandleUpload = (): (( export const useHandleUpload = (): handleUploadSignature => {
file: File, return useCallback((view, file, cursorSelection, description, additionalUrlText) => {
cursorSelection?: CursorSelection, const changeContent = (callback: ContentFormatter) => changeEditorContent(view, callback)
description?: string, if (!file || !supportedMimeTypes.includes(file.type) || !changeContent) {
additionalUrlText?: string return
) => void) => { }
const codeMirrorReference = useCodeMirrorReference() const randomId = Math.random().toString(36).slice(7)
return useCallback( const uploadFileInfo = description
(file: File, cursorSelection?: CursorSelection, description?: string, additionalUrlText?: string): void => { ? t('editor.upload.uploadFile.withDescription', { fileName: file.name, description: description })
if (codeMirrorReference) { : t('editor.upload.uploadFile.withoutDescription', { fileName: file.name })
handleUpload(codeMirrorReference, file, cursorSelection, description, additionalUrlText)
} const uploadPlaceholder = `![${uploadFileInfo}](upload-${randomId}${additionalUrlText ?? ''})`
}, const noteId = getGlobalState().noteDetails.id
[codeMirrorReference] changeContent(({ currentSelection }) => {
) return replaceSelection(cursorSelection ?? currentSelection, uploadPlaceholder, false)
})
uploadFile(noteId, file)
.then(({ url }) => {
const replacement = `![${description ?? file.name ?? ''}](${url}${additionalUrlText ?? ''})`
changeContent(({ markdownContent }) => [
replaceInContent(markdownContent, uploadPlaceholder, replacement),
undefined
])
})
.catch((error: Error) => {
showErrorNotification('editor.upload.failed', { fileName: file.name })(error)
const replacement = `![upload of ${file.name} failed]()`
changeContent(({ markdownContent }) => [
replaceInContent(markdownContent, uploadPlaceholder, replacement),
undefined
])
})
}, [])
} }

View file

@ -16,6 +16,9 @@ import { ShowIf } from '../../../../common/show-if/show-if'
import { useCodeMirrorReference } from '../../../change-content-context/change-content-context' import { useCodeMirrorReference } from '../../../change-content-context/change-content-context'
import { extractSelectedText } from './extract-selected-text' import { extractSelectedText } from './extract-selected-text'
import { Optional } from '@mrdrogdrog/optional' import { Optional } from '@mrdrogdrog/optional'
import { Logger } from '../../../../../utils/logger'
const logger = new Logger('Upload image button')
/** /**
* Shows a button that uploads a chosen file to the backend and adds the link to the note. * Shows a button that uploads a chosen file to the backend and adds the link to the note.
@ -27,15 +30,19 @@ export const UploadImageButton: React.FC = () => {
clickRef.current?.() clickRef.current?.()
}, []) }, [])
const handleUpload = useHandleUpload()
const codeMirror = useCodeMirrorReference() const codeMirror = useCodeMirrorReference()
const handleUpload = useHandleUpload()
const onUploadImage = useCallback( const onUploadImage = useCallback(
(file: File) => { (file: File) => {
if (codeMirror === undefined) {
logger.error("can't upload image without codemirror reference")
return
}
const description = Optional.ofNullable(codeMirror?.state) const description = Optional.ofNullable(codeMirror?.state)
.map((state) => extractSelectedText(state)) .map((state) => extractSelectedText(state))
.orElse(undefined) .orElse(undefined)
handleUpload(file, undefined, description) handleUpload(codeMirror, file, undefined, description)
}, },
[codeMirror, handleUpload] [codeMirror, handleUpload]
) )