✨(frontend) add upload to the doc editor
We can now upload images to the doc editor. The image is uploaded to the server and the URL is inserted into the editor.
This commit is contained in:
@@ -6,11 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0),
|
|||||||
and this project adheres to
|
and this project adheres to
|
||||||
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
# Added
|
# Added
|
||||||
|
|
||||||
- ✨Add image attachments with access control
|
- ✨Add image attachments with access control
|
||||||
|
- ✨(frontend) Upload image to a document
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
|
|
||||||
## [1.2.1] - 2024-08-23
|
## [1.2.1] - 2024-08-23
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -1,3 +1,5 @@
|
|||||||
|
import path from 'path';
|
||||||
|
|
||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
import { createDoc, goToGridDoc, mockedDocument } from './common';
|
import { createDoc, goToGridDoc, mockedDocument } from './common';
|
||||||
@@ -159,4 +161,31 @@ test.describe('Doc Editor', () => {
|
|||||||
page.getByText('Read only, you cannot edit this document.'),
|
page.getByText('Read only, you cannot edit this document.'),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it adds an image to the doc editor', async ({ page }) => {
|
||||||
|
await goToGridDoc(page);
|
||||||
|
|
||||||
|
const fileChooserPromise = page.waitForEvent('filechooser');
|
||||||
|
|
||||||
|
await page.locator('.bn-block-outer').last().fill('Hello World');
|
||||||
|
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await page.locator('.bn-block-outer').last().fill('/');
|
||||||
|
await page.getByText('Resizable image with caption').click();
|
||||||
|
await page.getByText('Upload image').click();
|
||||||
|
|
||||||
|
const fileChooser = await fileChooserPromise;
|
||||||
|
await fileChooser.setFiles(
|
||||||
|
path.join(__dirname, 'assets/logo-suite-numerique.png'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const image = page.getByRole('img', { name: 'logo-suite-numerique.png' });
|
||||||
|
|
||||||
|
await expect(image).toBeVisible();
|
||||||
|
|
||||||
|
// Check src of image
|
||||||
|
expect(await image.getAttribute('src')).toMatch(
|
||||||
|
/http:\/\/localhost:8083\/media\/.*\/attachments\/.*.png/,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -175,6 +175,11 @@ test.describe('Doc Export', () => {
|
|||||||
name: 'Image',
|
name: 'Image',
|
||||||
})
|
})
|
||||||
.click();
|
.click();
|
||||||
|
await page
|
||||||
|
.getByRole('tab', {
|
||||||
|
name: 'Embed',
|
||||||
|
})
|
||||||
|
.click();
|
||||||
await page
|
await page
|
||||||
.getByPlaceholder('Enter URL')
|
.getByPlaceholder('Enter URL')
|
||||||
.fill('https://example.com/image.jpg');
|
.fill('https://example.com/image.jpg');
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
NEXT_PUBLIC_THEME=dsfr
|
|
||||||
NEXT_PUBLIC_SIGNALING_URL=
|
|
||||||
NEXT_PUBLIC_API_ORIGIN=
|
NEXT_PUBLIC_API_ORIGIN=
|
||||||
|
NEXT_PUBLIC_MEDIA_URL=
|
||||||
|
NEXT_PUBLIC_SIGNALING_URL=
|
||||||
|
NEXT_PUBLIC_THEME=dsfr
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
export const baseApiUrl = (apiVersion: string = '1.0') => {
|
export const mediaUrl = () =>
|
||||||
const origin =
|
process.env.NEXT_PUBLIC_MEDIA_URL ||
|
||||||
process.env.NEXT_PUBLIC_API_ORIGIN ||
|
(typeof window !== 'undefined' ? window.location.origin : '');
|
||||||
(typeof window !== 'undefined' ? window.location.origin : '');
|
|
||||||
|
|
||||||
return `${origin}/api/v${apiVersion}/`;
|
export const backendUrl = () =>
|
||||||
};
|
process.env.NEXT_PUBLIC_API_ORIGIN ||
|
||||||
|
(typeof window !== 'undefined' ? window.location.origin : '');
|
||||||
|
|
||||||
|
export const baseApiUrl = (apiVersion: string = '1.0') =>
|
||||||
|
`${backendUrl()}/api/v${apiVersion}/`;
|
||||||
|
|
||||||
export const signalingUrl = (docId: string) => {
|
export const signalingUrl = (docId: string) => {
|
||||||
const base =
|
const base =
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ declare module '*.svg?url' {
|
|||||||
namespace NodeJS {
|
namespace NodeJS {
|
||||||
interface ProcessEnv {
|
interface ProcessEnv {
|
||||||
NEXT_PUBLIC_API_ORIGIN?: string;
|
NEXT_PUBLIC_API_ORIGIN?: string;
|
||||||
|
NEXT_PUBLIC_MEDIA_URL?: string;
|
||||||
NEXT_PUBLIC_SIGNALING_URL?: string;
|
NEXT_PUBLIC_SIGNALING_URL?: string;
|
||||||
NEXT_PUBLIC_SW_DEACTIVATED?: string;
|
NEXT_PUBLIC_SW_DEACTIVATED?: string;
|
||||||
NEXT_PUBLIC_THEME?: string;
|
NEXT_PUBLIC_THEME?: string;
|
||||||
|
|||||||
@@ -2,14 +2,16 @@ import { BlockNoteEditor as BlockNoteEditorCore } from '@blocknote/core';
|
|||||||
import '@blocknote/core/fonts/inter.css';
|
import '@blocknote/core/fonts/inter.css';
|
||||||
import { BlockNoteView } from '@blocknote/mantine';
|
import { BlockNoteView } from '@blocknote/mantine';
|
||||||
import '@blocknote/mantine/style.css';
|
import '@blocknote/mantine/style.css';
|
||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { WebrtcProvider } from 'y-webrtc';
|
import { WebrtcProvider } from 'y-webrtc';
|
||||||
|
|
||||||
import { Box } from '@/components';
|
import { Box, TextErrors } from '@/components';
|
||||||
|
import { mediaUrl } from '@/core';
|
||||||
import { useAuthStore } from '@/core/auth';
|
import { useAuthStore } from '@/core/auth';
|
||||||
import { Doc } from '@/features/docs/doc-management';
|
import { Doc } from '@/features/docs/doc-management';
|
||||||
import { Version } from '@/features/docs/doc-versioning/';
|
import { Version } from '@/features/docs/doc-versioning/';
|
||||||
|
|
||||||
|
import { useCreateDocAttachment } from '../api/useCreateDocUpload';
|
||||||
import useSaveDoc from '../hook/useSaveDoc';
|
import useSaveDoc from '../hook/useSaveDoc';
|
||||||
import { useDocStore } from '../stores';
|
import { useDocStore } from '../stores';
|
||||||
import { randomColor } from '../utils';
|
import { randomColor } from '../utils';
|
||||||
@@ -56,8 +58,28 @@ export const BlockNoteContent = ({
|
|||||||
const { setStore, docsStore } = useDocStore();
|
const { setStore, docsStore } = useDocStore();
|
||||||
const canSave = doc.abilities.partial_update && !isVersion;
|
const canSave = doc.abilities.partial_update && !isVersion;
|
||||||
useSaveDoc(doc.id, provider.doc, canSave);
|
useSaveDoc(doc.id, provider.doc, canSave);
|
||||||
|
|
||||||
const storedEditor = docsStore?.[storeId]?.editor;
|
const storedEditor = docsStore?.[storeId]?.editor;
|
||||||
|
const {
|
||||||
|
mutateAsync: createDocAttachment,
|
||||||
|
isError: isErrorAttachment,
|
||||||
|
error: errorAttachment,
|
||||||
|
} = useCreateDocAttachment();
|
||||||
|
|
||||||
|
const uploadFile = useCallback(
|
||||||
|
async (file: File) => {
|
||||||
|
const body = new FormData();
|
||||||
|
body.append('file', file);
|
||||||
|
|
||||||
|
const ret = await createDocAttachment({
|
||||||
|
docId: doc.id,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
return `${mediaUrl()}${ret.file}`;
|
||||||
|
},
|
||||||
|
[createDocAttachment, doc.id],
|
||||||
|
);
|
||||||
|
|
||||||
const editor = useMemo(() => {
|
const editor = useMemo(() => {
|
||||||
if (storedEditor) {
|
if (storedEditor) {
|
||||||
return storedEditor;
|
return storedEditor;
|
||||||
@@ -72,8 +94,9 @@ export const BlockNoteContent = ({
|
|||||||
color: randomColor(),
|
color: randomColor(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
uploadFile,
|
||||||
});
|
});
|
||||||
}, [provider, storedEditor, userData?.email]);
|
}, [provider, storedEditor, uploadFile, userData?.email]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setStore(storeId, { editor });
|
setStore(storeId, { editor });
|
||||||
@@ -90,6 +113,12 @@ export const BlockNoteContent = ({
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
|
{isErrorAttachment && (
|
||||||
|
<Box $margin={{ bottom: 'big' }}>
|
||||||
|
<TextErrors causes={errorAttachment.cause} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
<BlockNoteView
|
<BlockNoteView
|
||||||
editor={editor}
|
editor={editor}
|
||||||
formattingToolbar={false}
|
formattingToolbar={false}
|
||||||
|
|||||||
Reference in New Issue
Block a user