mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-09 13:51:57 -04:00
refactor(media): store filenames, use pre-signed s3/azure URLs, UUIDs
Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
parent
4132833b5d
commit
157a0fe278
47 changed files with 869 additions and 389 deletions
|
@ -3,8 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
const imageId = 'non-existing.png'
|
||||
const fakeUuid = '77fdcf1c-35fa-4a65-bdcf-1c35fa8a65d5'
|
||||
|
||||
describe('File upload', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -22,7 +21,8 @@ describe('File upload', () => {
|
|||
{
|
||||
statusCode: 201,
|
||||
body: {
|
||||
id: imageId
|
||||
uuid: fakeUuid,
|
||||
fileName: 'demo.png'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -38,7 +38,7 @@ describe('File upload', () => {
|
|||
},
|
||||
{ force: true }
|
||||
)
|
||||
cy.get('.cm-line').contains(``)
|
||||
cy.get('.cm-line').contains(``)
|
||||
})
|
||||
|
||||
it('via paste', () => {
|
||||
|
@ -51,7 +51,7 @@ describe('File upload', () => {
|
|||
}
|
||||
}
|
||||
cy.get('.cm-content').trigger('paste', pasteEvent)
|
||||
cy.get('.cm-line').contains(``)
|
||||
cy.get('.cm-line').contains(``)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -65,7 +65,7 @@ describe('File upload', () => {
|
|||
},
|
||||
{ action: 'drag-drop', force: true }
|
||||
)
|
||||
cy.get('.cm-line').contains(``)
|
||||
cy.get('.cm-line').contains(``)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -31,7 +31,7 @@ export const getProxiedUrl = async (imageUrl: string): Promise<ImageProxyRespons
|
|||
* @return The URL of the uploaded media object.
|
||||
* @throws {Error} when the api request wasn't successful.
|
||||
*/
|
||||
export const uploadFile = async (noteIdOrAlias: string, media: Blob): Promise<MediaUpload> => {
|
||||
export const uploadFile = async (noteIdOrAlias: string, media: File): Promise<MediaUpload> => {
|
||||
const postData = new FormData()
|
||||
postData.append('file', media)
|
||||
const response = await new PostApiRequestBuilder<MediaUpload, void>('media')
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
export interface MediaUpload {
|
||||
id: string
|
||||
uuid: string
|
||||
fileName: string
|
||||
noteId: string | null
|
||||
createdAt: string
|
||||
username: string
|
||||
username: string | null
|
||||
}
|
||||
|
||||
export interface ImageProxyResponse {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -60,8 +60,8 @@ export const useHandleUpload = (): handleUploadSignature => {
|
|||
return replaceSelection(cursorSelection ?? currentSelection, uploadPlaceholder, false)
|
||||
})
|
||||
uploadFile(noteId, file)
|
||||
.then(({ id }) => {
|
||||
const fullUrl = `${baseUrl}api/private/media/${id}`
|
||||
.then(({ uuid }) => {
|
||||
const fullUrl = `${baseUrl}media/${uuid}`
|
||||
const replacement = ``
|
||||
changeContent(({ markdownContent }) => [
|
||||
replaceInContent(markdownContent, uploadPlaceholder, replacement),
|
||||
|
|
|
@ -49,7 +49,7 @@ export const MediaBrowserSidebarMenu: React.FC<SpecificSidebarMenuProps> = ({
|
|||
if (loading || error || !value) {
|
||||
return []
|
||||
}
|
||||
return value.map((entry) => <MediaEntry entry={entry} key={entry.id} onDelete={setMediaEntryForDeletion} />)
|
||||
return value.map((entry) => <MediaEntry entry={entry} key={entry.uuid} onDelete={setMediaEntryForDeletion} />)
|
||||
}, [value, loading, error, setMediaEntryForDeletion])
|
||||
|
||||
const cancelDeletion = useCallback(() => {
|
||||
|
|
|
@ -25,7 +25,7 @@ export const MediaEntryDeletionModal: React.FC<MediaEntryDeletionModalProps> = (
|
|||
const { showErrorNotification, dispatchUiNotification } = useUiNotifications()
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
deleteUploadedMedia(entry.id)
|
||||
deleteUploadedMedia(entry.uuid)
|
||||
.then(() => {
|
||||
dispatchUiNotification('common.success', 'editor.mediaBrowser.mediaDeleted', {})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
.preview {
|
||||
max-width: 100%;
|
||||
max-height: 150px;
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
|
@ -11,13 +11,15 @@ import {
|
|||
Trash as IconTrash,
|
||||
FileRichtextFill as IconFileRichtextFill,
|
||||
Person as IconPerson,
|
||||
Clock as IconClock
|
||||
Clock as IconClock,
|
||||
FileText as IconFileText
|
||||
} from 'react-bootstrap-icons'
|
||||
import { useIsOwner } from '../../../../../hooks/common/use-is-owner'
|
||||
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
|
||||
import { UserAvatarForUsername } from '../../../../common/user-avatar/user-avatar-for-username'
|
||||
import { useChangeEditorContentCallback } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { replaceSelection } from '../../../editor-pane/tool-bar/formatters/replace-selection'
|
||||
import styles from './media-entry.module.css'
|
||||
|
||||
export interface MediaEntryProps {
|
||||
entry: MediaUpload
|
||||
|
@ -37,7 +39,7 @@ export const MediaEntry: React.FC<MediaEntryProps> = ({ entry, onDelete }) => {
|
|||
const isOwner = useIsOwner()
|
||||
|
||||
const imageUrl = useMemo(() => {
|
||||
return `${baseUrl}api/private/media/${entry.id}`
|
||||
return `${baseUrl}media/${entry.uuid}`
|
||||
}, [entry, baseUrl])
|
||||
const textCreatedTime = useMemo(() => {
|
||||
return new Date(entry.createdAt).toLocaleString()
|
||||
|
@ -47,7 +49,7 @@ export const MediaEntry: React.FC<MediaEntryProps> = ({ entry, onDelete }) => {
|
|||
changeEditorContent?.(({ currentSelection }) => {
|
||||
return replaceSelection(
|
||||
{ from: currentSelection.to ?? currentSelection.from },
|
||||
``,
|
||||
``,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
@ -61,10 +63,15 @@ export const MediaEntry: React.FC<MediaEntryProps> = ({ entry, onDelete }) => {
|
|||
<div className={'p-2 border-bottom border-opacity-50'}>
|
||||
<a href={imageUrl} target={'_blank'} rel={'noreferrer'} className={'text-center d-block mb-2'}>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src={imageUrl} alt={`Upload ${entry.id}`} height={100} className={'mw-100'} />
|
||||
<img src={imageUrl} alt={`Upload ${entry.fileName}`} className={styles.preview} />
|
||||
</a>
|
||||
<div className={'w-100 d-flex flex-row align-items-center justify-content-between'}>
|
||||
<div>
|
||||
<small>
|
||||
<IconFileText className={'me-1'} />
|
||||
{entry.fileName}
|
||||
</small>
|
||||
<br />
|
||||
<small className={'d-inline-flex flex-row align-items-center'}>
|
||||
<IconPerson className={'me-1'} />
|
||||
<UserAvatarForUsername username={entry.username} size={'sm'} />
|
||||
|
|
|
@ -12,13 +12,15 @@ const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
|||
{
|
||||
username: 'tilman',
|
||||
createdAt: '2022-03-20T20:36:32Z',
|
||||
id: 'dummy.png',
|
||||
uuid: '5355ed83-7e12-4db0-95ed-837e124db08c',
|
||||
fileName: 'dummy.png',
|
||||
noteId: 'features'
|
||||
},
|
||||
{
|
||||
username: 'tilman',
|
||||
createdAt: '2022-03-20T20:36:57+0000',
|
||||
id: 'dummy.png',
|
||||
uuid: '656745ab-fbf9-47f1-a745-abfbf9a7f10c',
|
||||
fileName: 'dummy2.png',
|
||||
noteId: null
|
||||
}
|
||||
])
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -20,7 +20,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse): Promise<void>
|
|||
req,
|
||||
res,
|
||||
{
|
||||
id: '/public/img/avatar.png',
|
||||
uuid: 'e81f57cd-5866-4253-9f57-cd5866a253ca',
|
||||
fileName: 'avatar.png',
|
||||
noteId: null,
|
||||
username: 'test',
|
||||
createdAt: '2022-02-27T21:54:23.856Z'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue