(frontend) set empty alt for decorative images in blocknote editor

ensure decorative images have empty alt to comply with RGAA 1.2 accessibility

Signed-off-by: Cyril <c.gromoff@gmail.com>
This commit is contained in:
Cyril
2025-08-11 12:22:04 +02:00
parent 81f3997628
commit d0c9de9d96
7 changed files with 68 additions and 6 deletions

View File

@@ -18,13 +18,13 @@ and this project adheres to
- #1262 - #1262
- #1244 - #1244
- #1270 - #1270
- #1282
### Fixed ### Fixed
- 🐛(makefile) Windows compatibility fix for Docker volume mounting #1264 - 🐛(makefile) Windows compatibility fix for Docker volume mounting #1264
- 🐛(minio) fix user permission error with Minio and Windows #1264 - 🐛(minio) fix user permission error with Minio and Windows #1264
## [3.5.0] - 2025-07-31 ## [3.5.0] - 2025-07-31
### Added ### Added

View File

@@ -43,7 +43,9 @@ test.describe('Config', () => {
path.join(__dirname, 'assets/logo-suite-numerique.png'), path.join(__dirname, 'assets/logo-suite-numerique.png'),
); );
const image = page.getByRole('img', { name: 'logo-suite-numerique.png' }); const image = page
.locator('.--docs--editor-container img.bn-visual-media')
.first();
await expect(image).toBeVisible(); await expect(image).toBeVisible();

View File

@@ -272,7 +272,9 @@ test.describe('Doc Editor', () => {
path.join(__dirname, 'assets/logo-suite-numerique.png'), path.join(__dirname, 'assets/logo-suite-numerique.png'),
); );
const image = page.getByRole('img', { name: 'logo-suite-numerique.png' }); const image = page
.locator('.--docs--editor-container img.bn-visual-media')
.first();
await expect(image).toBeVisible(); await expect(image).toBeVisible();
@@ -284,6 +286,11 @@ test.describe('Doc Editor', () => {
expect(await image.getAttribute('src')).toMatch( expect(await image.getAttribute('src')).toMatch(
/http:\/\/localhost:8083\/media\/.*\/attachments\/.*.png/, /http:\/\/localhost:8083\/media\/.*\/attachments\/.*.png/,
); );
await expect(image).toHaveAttribute('role', 'presentation');
await expect(image).toHaveAttribute('alt', '');
await expect(image).toHaveAttribute('tabindex', '-1');
await expect(image).toHaveAttribute('aria-hidden', 'true');
}); });
test('it checks the AI buttons', async ({ page, browserName }) => { test('it checks the AI buttons', async ({ page, browserName }) => {

View File

@@ -122,7 +122,9 @@ test.describe('Doc Export', () => {
const fileChooser = await fileChooserPromise; const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(path.join(__dirname, 'assets/test.svg')); await fileChooser.setFiles(path.join(__dirname, 'assets/test.svg'));
const image = page.getByRole('img', { name: 'test.svg' }); const image = page
.locator('.--docs--editor-container img.bn-visual-media')
.first();
await expect(image).toBeVisible(); await expect(image).toBeVisible();
@@ -182,7 +184,9 @@ test.describe('Doc Export', () => {
const fileChooser = await fileChooserPromise; const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(path.join(__dirname, 'assets/test.svg')); await fileChooser.setFiles(path.join(__dirname, 'assets/test.svg'));
const image = page.getByRole('img', { name: 'test.svg' }); const image = page
.locator('.--docs--editor-container img.bn-visual-media')
.first();
await expect(image).toBeVisible(); await expect(image).toBeVisible();

View File

@@ -33,7 +33,11 @@ import { randomColor } from '../utils';
import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu'; import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu';
import { BlockNoteToolbar } from './BlockNoteToolBar/BlockNoteToolbar'; import { BlockNoteToolbar } from './BlockNoteToolBar/BlockNoteToolbar';
import { CalloutBlock, DividerBlock } from './custom-blocks'; import {
AccessibleImageBlock,
CalloutBlock,
DividerBlock,
} from './custom-blocks';
import { import {
InterlinkingLinkInlineContent, InterlinkingLinkInlineContent,
InterlinkingSearchInlineContent, InterlinkingSearchInlineContent,
@@ -50,6 +54,7 @@ const baseBlockNoteSchema = withPageBreak(
...defaultBlockSpecs, ...defaultBlockSpecs,
callout: CalloutBlock, callout: CalloutBlock,
divider: DividerBlock, divider: DividerBlock,
image: AccessibleImageBlock,
}, },
inlineContentSpecs: { inlineContentSpecs: {
...defaultInlineContentSpecs, ...defaultInlineContentSpecs,

View File

@@ -0,0 +1,43 @@
import {
BlockFromConfig,
BlockNoteEditor,
BlockSchemaWithBlock,
InlineContentSchema,
StyleSchema,
createBlockSpec,
imageBlockConfig,
imageParse,
imageRender,
imageToExternalHTML,
} from '@blocknote/core';
type ImageBlockConfig = typeof imageBlockConfig;
export const accessibleImageRender = (
block: BlockFromConfig<ImageBlockConfig, InlineContentSchema, StyleSchema>,
editor: BlockNoteEditor<
BlockSchemaWithBlock<ImageBlockConfig['type'], ImageBlockConfig>,
InlineContentSchema,
StyleSchema
>,
) => {
const imageRenderComputed = imageRender(block, editor);
const dom = imageRenderComputed.dom;
const imgSelector = dom.querySelector('img');
imgSelector?.setAttribute('alt', '');
imgSelector?.setAttribute('role', 'presentation');
imgSelector?.setAttribute('aria-hidden', 'true');
imgSelector?.setAttribute('tabindex', '-1');
return {
...imageRenderComputed,
dom,
};
};
export const AccessibleImageBlock = createBlockSpec(imageBlockConfig, {
render: accessibleImageRender,
parse: imageParse,
toExternalHTML: imageToExternalHTML,
});

View File

@@ -1,2 +1,3 @@
export * from './AccessibleImageBlock';
export * from './CalloutBlock'; export * from './CalloutBlock';
export * from './DividerBlock'; export * from './DividerBlock';