From 1c166c4736fa6fb981f7d60f65e434fed15b26a4 Mon Sep 17 00:00:00 2001 From: Olivier Lambert Date: Wed, 20 May 2026 17:45:59 +0200 Subject: [PATCH] feat(editor): add alt text support for images (#2097) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(editor): add alt text support for images * feat:  extend alt text support to videos and diagrams --------- Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com> --- .../public/locales/en-US/translation.json | 3 + .../common/use-alt-text-control.tsx | 139 ++++++++++++++++++ .../editor/components/drawio/drawio-menu.tsx | 24 ++- .../components/excalidraw/excalidraw-menu.tsx | 24 ++- .../editor/components/image/image-menu.tsx | 24 ++- .../editor/components/image/image-view.tsx | 4 +- .../editor/components/video/video-menu.tsx | 24 ++- .../editor/components/video/video-view.tsx | 4 +- packages/editor-ext/src/lib/drawio.ts | 20 ++- packages/editor-ext/src/lib/excalidraw.ts | 20 ++- .../src/lib/markdown/utils/turndown.utils.ts | 27 +++- packages/editor-ext/src/lib/video/video.ts | 19 +++ 12 files changed, 315 insertions(+), 17 deletions(-) create mode 100644 apps/client/src/features/editor/components/common/use-alt-text-control.tsx diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index 62927f66a..c0b67e9d1 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -277,6 +277,9 @@ "Align left": "Align left", "Align right": "Align right", "Align center": "Align center", + "Alt text": "Alt text", + "Describe this for accessibility.": "Describe this for accessibility.", + "Add a description": "Add a description", "Justify": "Justify", "Merge cells": "Merge cells", "Split cell": "Split cell", diff --git a/apps/client/src/features/editor/components/common/use-alt-text-control.tsx b/apps/client/src/features/editor/components/common/use-alt-text-control.tsx new file mode 100644 index 000000000..1a43f9d79 --- /dev/null +++ b/apps/client/src/features/editor/components/common/use-alt-text-control.tsx @@ -0,0 +1,139 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { Editor } from "@tiptap/react"; +import { + ActionIcon, + Button, + Group, + Paper, + Text, + Textarea, + Tooltip, +} from "@mantine/core"; +import { IconAlt } from "@tabler/icons-react"; +import { useTranslation } from "react-i18next"; + +const ALT_MAX_LENGTH = 300; + +function sanitizeAlt(value: string): string { + return value + .replace(/[\\\[\]!]/g, "") + .replace(/\s+/g, " ") + .trim(); +} + +type UseAltTextControlArgs = { + editor: Editor; + nodeName: string; + currentAlt: string; +}; + +export function useAltTextControl({ + editor, + nodeName, + currentAlt, +}: UseAltTextControlArgs) { + const { t } = useTranslation(); + const [showInput, setShowInput] = useState(false); + const [draft, setDraft] = useState(""); + + const open = useCallback(() => { + setDraft(currentAlt || ""); + setShowInput(true); + }, [currentAlt]); + + useEffect(() => { + const handler = () => { + if (!editor.isActive(nodeName)) { + setShowInput(false); + } + }; + editor.on("selectionUpdate", handler); + return () => { + editor.off("selectionUpdate", handler); + }; + }, [editor, nodeName]); + + const cancel = useCallback(() => { + setShowInput(false); + }, []); + + const save = useCallback(() => { + editor + .chain() + .focus(undefined, { scrollIntoView: false }) + .updateAttributes(nodeName, { alt: sanitizeAlt(draft) || undefined }) + .run(); + setShowInput(false); + }, [editor, nodeName, draft]); + + const onKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + save(); + } else if (e.key === "Escape") { + e.preventDefault(); + cancel(); + } + }, + [save, cancel], + ); + + const button = ( + + + + + + ); + + const panel = showInput ? ( + + + {t("Alt text")} + + + {t("Describe this for accessibility.")} + +