mirror of
https://github.com/suitenumerique/docs.git
synced 2026-10-01 05:55:16 +02:00
🐛(frontend) keep caption and alignment when replacing an image
The antivirus loader rebuilt the image block with an empty caption and left alignment, so both were lost after a replace. Signed-off-by: Ovgodd <c.gromoff@gmail.com>
This commit is contained in:
@@ -77,6 +77,7 @@ and this project adheres to
|
||||
### Fixed
|
||||
|
||||
- 🐛(frontend) open search results in a new tab with ctrl/cmd+click #2719
|
||||
- 🐛(frontend) keep caption and alignment when replacing an image #2700
|
||||
- 🐛(frontend) clear callout background on Backspace #2052
|
||||
|
||||
- 🐛(export) keep image aspect ratio in PDF columns #2670
|
||||
|
||||
+12
-2
@@ -29,6 +29,11 @@ type UploadLoaderPropSchema = {
|
||||
readonly default: '';
|
||||
};
|
||||
readonly blockUploadUrl: { readonly default: '' };
|
||||
readonly blockUploadCaption: { readonly default: '' };
|
||||
readonly blockUploadTextAlignment: {
|
||||
readonly default: 'left';
|
||||
readonly values: readonly ['left', 'center', 'right', 'justify'];
|
||||
};
|
||||
};
|
||||
|
||||
type UploadLoaderBlockConfig = BlockConfig<
|
||||
@@ -90,9 +95,9 @@ const UploadLoaderBlockComponent = ({
|
||||
url: `${mediaUrl}${response.file}`,
|
||||
showPreview: block.props.blockUploadShowPreview,
|
||||
name: block.props.blockUploadName,
|
||||
caption: '',
|
||||
caption: block.props.blockUploadCaption,
|
||||
backgroundColor: 'default',
|
||||
textAlignment: 'left',
|
||||
textAlignment: block.props.blockUploadTextAlignment,
|
||||
},
|
||||
} as never,
|
||||
],
|
||||
@@ -160,6 +165,11 @@ export const UploadLoaderBlock = createReactBlockSpec(
|
||||
default: '',
|
||||
},
|
||||
blockUploadUrl: { default: '' },
|
||||
blockUploadCaption: { default: '' },
|
||||
blockUploadTextAlignment: {
|
||||
default: 'left',
|
||||
values: ['left', 'center', 'right', 'justify'],
|
||||
},
|
||||
},
|
||||
content: 'none',
|
||||
},
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
import { createReactBlockSpec } from '@blocknote/react';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { ComponentType } from 'react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { AppWrapper } from '@/tests/utils';
|
||||
|
||||
import { UploadLoaderBlock } from '../UploadLoaderBlock';
|
||||
|
||||
const { loopCheckDocMediaStatus, capturedRender } = vi.hoisted(() => ({
|
||||
loopCheckDocMediaStatus: vi.fn(),
|
||||
capturedRender: {
|
||||
current: undefined as ComponentType<Record<string, unknown>> | undefined,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../../api', () => ({
|
||||
loopCheckDocMediaStatus,
|
||||
}));
|
||||
|
||||
vi.mock('@blocknote/react', async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import('@blocknote/react')>(
|
||||
'@blocknote/react',
|
||||
);
|
||||
|
||||
return {
|
||||
...actual,
|
||||
createReactBlockSpec: (
|
||||
config: Parameters<typeof createReactBlockSpec>[0],
|
||||
implementation: Parameters<typeof createReactBlockSpec>[1],
|
||||
) => {
|
||||
capturedRender.current = implementation.render as ComponentType<
|
||||
Record<string, unknown>
|
||||
>;
|
||||
return actual.createReactBlockSpec(config, implementation);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
void UploadLoaderBlock;
|
||||
|
||||
const analyzeUrl = 'https://docs.example/media-check/abc';
|
||||
|
||||
describe('UploadLoaderBlock', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.spyOn(Math, 'random').mockReturnValue(0);
|
||||
loopCheckDocMediaStatus.mockResolvedValue({
|
||||
status: 'ready',
|
||||
file: '/media/doc/photo.png',
|
||||
});
|
||||
});
|
||||
|
||||
it('restores the caption and alignment once the analysis is done', async () => {
|
||||
const replaceBlocks = vi.fn();
|
||||
const Render = capturedRender.current!;
|
||||
|
||||
render(
|
||||
<Render
|
||||
block={{
|
||||
id: 'loader',
|
||||
type: 'uploadLoader',
|
||||
props: {
|
||||
information: 'Analyzing file...',
|
||||
type: 'loading',
|
||||
blockUploadName: 'photo.png',
|
||||
blockUploadShowPreview: true,
|
||||
blockUploadType: 'image',
|
||||
blockUploadUrl: analyzeUrl,
|
||||
blockUploadCaption: 'Pont Neuf',
|
||||
blockUploadTextAlignment: 'center',
|
||||
},
|
||||
content: undefined,
|
||||
children: [],
|
||||
}}
|
||||
editor={{
|
||||
isEditable: true,
|
||||
replaceBlocks,
|
||||
}}
|
||||
/>,
|
||||
{ wrapper: AppWrapper },
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(replaceBlocks).toHaveBeenCalledWith(
|
||||
['loader'],
|
||||
[
|
||||
expect.objectContaining({
|
||||
type: 'image',
|
||||
props: expect.objectContaining({
|
||||
caption: 'Pont Neuf',
|
||||
textAlignment: 'center',
|
||||
}),
|
||||
}),
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
+40
-1
@@ -4,7 +4,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { AppWrapper } from '@/tests/utils';
|
||||
|
||||
import { useUploadFile } from '../useUploadFile';
|
||||
import { DocsBlockNoteEditor } from '../../types';
|
||||
import { useUploadFile, useUploadStatus } from '../useUploadFile';
|
||||
|
||||
const { mockToast } = vi.hoisted(() => ({ mockToast: vi.fn() }));
|
||||
|
||||
@@ -71,3 +72,41 @@ describe('useUploadFile', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useUploadStatus', () => {
|
||||
it('keeps the caption and alignment while the new file is analyzed', () => {
|
||||
const replaceBlocks = vi.fn();
|
||||
const editor = {
|
||||
document: [
|
||||
{
|
||||
id: 'img',
|
||||
type: 'image',
|
||||
props: {
|
||||
url: 'https://docs.example/media-check/abc',
|
||||
name: 'photo.png',
|
||||
showPreview: true,
|
||||
caption: 'Pont Neuf',
|
||||
textAlignment: 'center',
|
||||
},
|
||||
},
|
||||
],
|
||||
replaceBlocks,
|
||||
onUploadEnd: vi.fn(),
|
||||
} as unknown as DocsBlockNoteEditor;
|
||||
|
||||
renderHook(() => useUploadStatus(editor), { wrapper: AppWrapper });
|
||||
|
||||
expect(replaceBlocks).toHaveBeenCalledWith(
|
||||
['img'],
|
||||
[
|
||||
expect.objectContaining({
|
||||
type: 'uploadLoader',
|
||||
props: expect.objectContaining({
|
||||
blockUploadCaption: 'Pont Neuf',
|
||||
blockUploadTextAlignment: 'center',
|
||||
}),
|
||||
}),
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,6 +16,30 @@ import { DocsBlockNoteEditor } from '../types';
|
||||
|
||||
const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024; // Default to 10MB
|
||||
|
||||
const TEXT_ALIGNMENTS = ['left', 'center', 'right', 'justify'] as const;
|
||||
|
||||
type TextAlignment = (typeof TEXT_ALIGNMENTS)[number];
|
||||
|
||||
const fileCaption = (props: object) => {
|
||||
if ('caption' in props && typeof props.caption === 'string') {
|
||||
return props.caption;
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
const fileTextAlignment = (props: object): TextAlignment => {
|
||||
if (
|
||||
'textAlignment' in props &&
|
||||
typeof props.textAlignment === 'string' &&
|
||||
TEXT_ALIGNMENTS.includes(props.textAlignment as TextAlignment)
|
||||
) {
|
||||
return props.textAlignment as TextAlignment;
|
||||
}
|
||||
|
||||
return 'left';
|
||||
};
|
||||
|
||||
export const useUploadFile = (docId: string) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
@@ -98,6 +122,8 @@ export const useUploadStatus = (editor: DocsBlockNoteEditor) => {
|
||||
const blockUploadName = block.props.name;
|
||||
const blockUploadShowPreview =
|
||||
('showPreview' in block.props && block.props.showPreview) || false;
|
||||
const blockUploadCaption = fileCaption(block.props);
|
||||
const blockUploadTextAlignment = fileTextAlignment(block.props);
|
||||
|
||||
try {
|
||||
editor.replaceBlocks(
|
||||
@@ -112,6 +138,8 @@ export const useUploadStatus = (editor: DocsBlockNoteEditor) => {
|
||||
blockUploadType,
|
||||
blockUploadUrl,
|
||||
blockUploadShowPreview,
|
||||
blockUploadCaption,
|
||||
blockUploadTextAlignment,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user