diff --git a/CHANGELOG.md b/CHANGELOG.md index 832615d6..eac75aef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **Editable captions for images.** Images gain an optional caption shown + below them, edited inline from the image bubble menu and stored as a `caption` attribute. Captions round-trip + losslessly through markdown as a `data-caption` attribute on the image, so + they survive export/import unchanged. (#221) + - **Quick-create regular and temporary notes from the Home and Space screens.** The Home screen now shows a second action next to "New note" that creates a *temporary* note (one that auto-moves to Trash after the workspace lifetime), diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index 45234831..e96e7651 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -286,6 +286,9 @@ "Alt text": "Alt text", "Describe this for accessibility.": "Describe this for accessibility.", "Add a description": "Add a description", + "Caption": "Caption", + "Add a caption": "Add a caption", + "Shown below the image.": "Shown below the image.", "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 index 1a43f9d7..2f1e8eb5 100644 --- 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 @@ -1,16 +1,7 @@ -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"; +import { useImageTextFieldControl } from "@/features/editor/components/common/use-image-text-field-control.tsx"; const ALT_MAX_LENGTH = 300; @@ -27,113 +18,25 @@ type UseAltTextControlArgs = { currentAlt: string; }; +// Thin wrapper over the shared image text-field popover; see +// useImageTextFieldControl. The t("...") literals stay here so they remain +// statically extractable for i18n. 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.")} - -