import React, { useState } from "react"; import { useEntity, useReplicache } from "src/replicache"; import { localImages } from "src/utils/addImage"; import { Modal } from "components/Modal"; import { MobileSheet } from "components/MobileSheet"; import { ButtonPrimary, ButtonTertiary } from "components/Buttons"; import { useIsMobile } from "src/hooks/isMobile"; // Alt-text editor shown as a modal (desktop) or mobile sheet. Does not // autosave — the value is committed on Save or Enter. Shared by ImageBlock, // the gallery's per-image button, and the gallery lightbox. export function ImageAltModal(props: { entityID: string; trigger: React.ReactNode; title: string; }) { let [altEditorOpen, setAltEditorOpen] = useState(false); let isMobile = useIsMobile(); // Mount fresh on open so the textarea seeds from the current alt text. if (isMobile) return ( setAltEditorOpen(false)} /> ); return ( setAltEditorOpen(false)} /> ); } function ImageAltModalContent(props: { entityID: string; onClose: () => void; }) { let { rep } = useReplicache(); let image = useEntity(props.entityID, "block/image"); let altText = useEntity(props.entityID, "image/alt")?.data.value ?? ""; // `draft` stays null until the user edits, so the textarea reflects the live // alt text once Replicache's subscription resolves — the first render returns // a stale page-load snapshot that would otherwise get frozen into state. let [draft, setDraft] = useState(null); let value = draft ?? altText; let src = image ? localImages.get(image.data.src) ?? image.data.src : null; let save = async (next: string = value) => { await rep?.mutate.assertFact({ entity: props.entityID, attribute: "image/alt", data: { type: "string", value: next }, }); props.onClose(); }; return (
{src && ( {altText} )}