From db361ade813e1f9259369fc953d4eb79f61446cd Mon Sep 17 00:00:00 2001 From: Cyril Date: Mon, 11 Aug 2025 12:22:04 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8(frontend)=20set=20empty=20alt=20f?= =?UTF-8?q?or=20decorative=20images=20in=20blocknote=20editor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ensure decorative images have empty alt to comply with RGAA 1.2 accessibility Signed-off-by: Cyril --- CHANGELOG.md | 2 +- .../e2e/__tests__/app-impress/config.spec.ts | 4 ++- .../__tests__/app-impress/doc-editor.spec.ts | 4 ++- .../__tests__/app-impress/doc-export.spec.ts | 8 +++-- .../doc-editor/components/BlockNoteEditor.tsx | 34 +++++++++++++++++++ 5 files changed, 47 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a21793b07c..f218cf4c65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,13 +17,13 @@ and this project adheres to - #1262 - #1244 - #1270 + - #1282 ### Fixed - 🐛(makefile) Windows compatibility fix for Docker volume mounting #1264 - 🐛(minio) fix user permission error with Minio and Windows #1264 - ## [3.5.0] - 2025-07-31 ### Added diff --git a/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts index 250f2d6f84..c718613935 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts @@ -43,7 +43,9 @@ test.describe('Config', () => { 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(); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts index 41098f2526..b1c07f644a 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts @@ -272,7 +272,9 @@ test.describe('Doc Editor', () => { 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(); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts index 1e14c42ee2..610fdbf374 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts @@ -122,7 +122,9 @@ test.describe('Doc Export', () => { const fileChooser = await fileChooserPromise; 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(); @@ -182,7 +184,9 @@ test.describe('Doc Export', () => { const fileChooser = await fileChooserPromise; 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(); diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx index 6b7b77a483..c38d52ed72 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx @@ -169,6 +169,40 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { }; }, [setEditor, editor]); + // Accessibility: make all images decorative by default + useEffect(() => { + if (!editor) { + return; + } + + const applyDecorativeAlt = () => { + const imgs = document.querySelectorAll( + '.--docs--editor-container img.bn-visual-media', + ); + imgs.forEach((img) => { + img.setAttribute('alt', ''); + img.setAttribute('role', 'presentation'); + img.setAttribute('aria-hidden', 'true'); + }); + }; + + applyDecorativeAlt(); + + const unsubscribe = editor.onChange(() => { + applyDecorativeAlt(); + }); + + return () => { + try { + if (typeof unsubscribe === 'function') { + unsubscribe(); + } + } catch { + /* no-op */ + } + }; + }, [editor]); + return ( Date: Tue, 12 Aug 2025 15:44:48 +0200 Subject: [PATCH 2/3] =?UTF-8?q?fixup!=20=E2=9C=A8(frontend)=20set=20empty?= =?UTF-8?q?=20alt=20for=20decorative=20images=20in=20blocknote=20editor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/app-impress/doc-editor.spec.ts | 5 +++ .../doc-editor/components/BlockNoteEditor.tsx | 41 +++---------------- .../custom-blocks/AccessibleImageBlock.tsx | 35 ++++++++++++++++ .../components/custom-blocks/index.ts | 1 + 4 files changed, 47 insertions(+), 35 deletions(-) create mode 100644 src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/AccessibleImageBlock.tsx diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts index b1c07f644a..29cb59b78a 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts @@ -286,6 +286,11 @@ test.describe('Doc Editor', () => { expect(await image.getAttribute('src')).toMatch( /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 }) => { diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx index c38d52ed72..04238f48a4 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx @@ -33,7 +33,11 @@ import { randomColor } from '../utils'; import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu'; import { BlockNoteToolbar } from './BlockNoteToolBar/BlockNoteToolbar'; -import { CalloutBlock, DividerBlock } from './custom-blocks'; +import { + AccessibleImageBlock, + CalloutBlock, + DividerBlock, +} from './custom-blocks'; import { InterlinkingLinkInlineContent, InterlinkingSearchInlineContent, @@ -50,6 +54,7 @@ const baseBlockNoteSchema = withPageBreak( ...defaultBlockSpecs, callout: CalloutBlock, divider: DividerBlock, + image: AccessibleImageBlock, }, inlineContentSpecs: { ...defaultInlineContentSpecs, @@ -169,40 +174,6 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => { }; }, [setEditor, editor]); - // Accessibility: make all images decorative by default - useEffect(() => { - if (!editor) { - return; - } - - const applyDecorativeAlt = () => { - const imgs = document.querySelectorAll( - '.--docs--editor-container img.bn-visual-media', - ); - imgs.forEach((img) => { - img.setAttribute('alt', ''); - img.setAttribute('role', 'presentation'); - img.setAttribute('aria-hidden', 'true'); - }); - }; - - applyDecorativeAlt(); - - const unsubscribe = editor.onChange(() => { - applyDecorativeAlt(); - }); - - return () => { - try { - if (typeof unsubscribe === 'function') { - unsubscribe(); - } - } catch { - /* no-op */ - } - }; - }, [editor]); - return ( , + editor: BlockNoteEditor, +) => { + 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, +}); diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/index.ts b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/index.ts index 34a8c459cb..99c1ee271e 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/index.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/index.ts @@ -1,2 +1,3 @@ +export * from './AccessibleImageBlock'; export * from './CalloutBlock'; export * from './DividerBlock'; From c37591f3995ec7549a33bcae1788ff3539e2a1fe Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Wed, 13 Aug 2025 09:32:55 +0200 Subject: [PATCH 3/3] =?UTF-8?q?fixup!=20fixup!=20=E2=9C=A8(frontend)=20set?= =?UTF-8?q?=20empty=20alt=20for=20decorative=20images=20in=20blocknote=20e?= =?UTF-8?q?ditor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom-blocks/AccessibleImageBlock.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/AccessibleImageBlock.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/AccessibleImageBlock.tsx index b847acd68d..59b2f349be 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/AccessibleImageBlock.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/AccessibleImageBlock.tsx @@ -1,7 +1,9 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { BlockFromConfig, BlockNoteEditor, + BlockSchemaWithBlock, + InlineContentSchema, + StyleSchema, createBlockSpec, imageBlockConfig, imageParse, @@ -9,9 +11,15 @@ import { imageToExternalHTML, } from '@blocknote/core'; +type ImageBlockConfig = typeof imageBlockConfig; + export const accessibleImageRender = ( - block: BlockFromConfig, - editor: BlockNoteEditor, + block: BlockFromConfig, + editor: BlockNoteEditor< + BlockSchemaWithBlock, + InlineContentSchema, + StyleSchema + >, ) => { const imageRenderComputed = imageRender(block, editor); const dom = imageRenderComputed.dom;