diff --git a/lang/en/back.php b/lang/en/back.php index 1cc37ff1..7aad3ed0 100644 --- a/lang/en/back.php +++ b/lang/en/back.php @@ -257,7 +257,8 @@ return [ "position" => "Image position", "format" => "Image format", "image" => "Image", - "alt" => "ALT attribute of the image", + "attribute_alt" => "Alt attribute of the image", + "attribute_title" => "Title associated to the image", "caption" => "Caption of the image", "has_caption" => "Presence of a legend", "landscape" => "Landscape", diff --git a/lang/fr/back.php b/lang/fr/back.php index 92ae3a1d..e47ad3a0 100644 --- a/lang/fr/back.php +++ b/lang/fr/back.php @@ -258,7 +258,8 @@ return [ "position" => "Position de l'image", "format" => "Format de l'image", "image" => "Image", - "alt" => "Texte alternatif de l'image", + "attribute_alt" => "Texte alternatif de l'image", + "attribute_title" => "Titre associé à l'image", "caption" => "Légende de l'image", "has_caption" => "Présence d'une légende", "landscape" => "Paysage", diff --git a/resources/ts/back/Components/Inputs/ModelSelect.tsx b/resources/ts/back/Components/Inputs/ModelSelect.tsx index 09c852a4..9daa460f 100644 --- a/resources/ts/back/Components/Inputs/ModelSelect.tsx +++ b/resources/ts/back/Components/Inputs/ModelSelect.tsx @@ -1667,7 +1667,7 @@ export const ModelSelect = forwardRef( {/* Pagination boutons */} {options.length > 0 && lastPage !== 1 && ( -
+
diff --git a/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/Image.tsx b/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/Image.tsx index 29bf0bf9..f9729860 100644 --- a/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/Image.tsx +++ b/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/Image.tsx @@ -23,6 +23,7 @@ const Image = TiptapImage.extend({ width: null, height: null, alt: null, + title: null, caption: null, style: null, }; @@ -44,6 +45,7 @@ const Image = TiptapImage.extend({ return { src: img.src, alt: img.getAttribute("alt"), + title: img.getAttribute("title"), width: parseInt( img.getAttribute("width") || imageDetails.width, 10, diff --git a/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/ImageButton.tsx b/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/ImageButton.tsx index 81e21280..32df55e7 100644 --- a/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/ImageButton.tsx +++ b/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/ImageButton.tsx @@ -57,10 +57,12 @@ export default function ImageButton({ editor, disabled }: ImageProps) { // Values of inputs text. const [inputText, setInputText] = useState<{ alt: string; + title: string; caption: string | null; hasCaption: boolean; }>({ alt: "", + title: "", caption: null, hasCaption: false, }); @@ -78,7 +80,8 @@ export default function ImageButton({ editor, disabled }: ImageProps) { .ucFirst() .value(); const transImage = useTrans("back.wysiwyg.image.image"); - const transAlt = useTrans("back.wysiwyg.image.alt"); + const transAlt = useTrans("back.wysiwyg.image.attribute_alt"); + const transTitle = useTrans("back.wysiwyg.image.attribute_title"); const transCaption = useTrans("back.wysiwyg.image.caption"); const transValidation = (attribute: string) => { return { @@ -94,6 +97,7 @@ export default function ImageButton({ editor, disabled }: ImageProps) { }; }; const transValidationAlt = transValidation(transAlt); + const transValidationTitle = transValidation(transTitle); const transValidationCaption = transValidation(transCaption); // List of image types. @@ -169,6 +173,7 @@ export default function ImageButton({ editor, disabled }: ImageProps) { upload: "image", inputFile: "image", alt: "alt", + title: "title", caption: "caption", }; // Manage zod errors. @@ -216,6 +221,7 @@ export default function ImageButton({ editor, disabled }: ImageProps) { setFile(null); setInputText({ alt: "", + title: "", caption: null, hasCaption: false, }); @@ -275,6 +281,7 @@ export default function ImageButton({ editor, disabled }: ImageProps) { const inputTextSchema = imageTextSchema({ hasCaption: inputText.hasCaption, transValidationAlt, + transValidationTitle, transValidationCaption, }); @@ -285,6 +292,7 @@ export default function ImageButton({ editor, disabled }: ImageProps) { .parse({ inputFile, alt: inputText.alt, + title: inputText.title, caption: inputText.caption, }); @@ -295,6 +303,7 @@ export default function ImageButton({ editor, disabled }: ImageProps) { editor.commands.setImage({ src: imageSrc, alt: parsedZod.alt, + title: parsedZod.title, caption: parsedZod.caption, width: String(inputType.width), height: String(inputType.height), @@ -336,6 +345,7 @@ export default function ImageButton({ editor, disabled }: ImageProps) { useEffect(() => { setInputText({ alt: inputText.alt, + title: inputText.title, caption: inputText.hasCaption ? inputText.caption : null, hasCaption: inputText.hasCaption, }); diff --git a/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/ImageInputs.tsx b/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/ImageInputs.tsx index 53214bce..6cf446fa 100644 --- a/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/ImageInputs.tsx +++ b/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/ImageInputs.tsx @@ -7,6 +7,7 @@ import str from "@/hooks/use-string"; type ImageInputsProps = { value: { alt: string; + title: string; caption: string | null; hasCaption: boolean; }; @@ -31,13 +32,30 @@ export default function ImageInputs({ {/* Input alt attribute of the image. */} onChange({ ...value, alt: v })} + setData={(_, currentValue) => + onChange({ ...value, alt: currentValue }) + } + disabled={disabled || loading} + parentErrors={errors} + /> + {/* Input title attribute of the image. */} + + onChange({ ...value, title: currentValue }) + } disabled={disabled || loading} parentErrors={errors} /> @@ -52,11 +70,11 @@ export default function ImageInputs({ name="hasCaption" checked={value.hasCaption} className="justify-center" - setData={(_, v) => + setData={(_, currentValue) => onChange({ ...value, - hasCaption: v, - caption: v ? value.caption : null, + hasCaption: currentValue, + caption: currentValue ? value.caption : null, }) } /> @@ -68,7 +86,9 @@ export default function ImageInputs({ placeholder="ex: John Doe" required={false} value={value.caption ?? ""} - setData={(_, v) => onChange({ ...value, caption: v })} + setData={(_, currentValue) => + onChange({ ...value, caption: currentValue }) + } disabled={disabled || loading} parentErrors={errors} /> diff --git a/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/ImageNodeView.tsx b/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/ImageNodeView.tsx index 13ea6d7a..e890f1c2 100644 --- a/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/ImageNodeView.tsx +++ b/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/ImageNodeView.tsx @@ -84,6 +84,7 @@ const PLACEMENT_STYLES: Record = { type ImageAttrs = { src: string; alt: string; + title: string; width: number; height: number; caption: string | null; @@ -112,10 +113,12 @@ export default function ImageNodeView(props: NodeViewProps) { // Values of inputs text. const [inputText, setInputText] = useState<{ alt: string; + title: string; caption: string | null; hasCaption: boolean; }>({ alt: attrs.alt, + title: attrs.title, caption: attrs.caption, hasCaption: !!attrs.caption, }); @@ -123,7 +126,8 @@ export default function ImageNodeView(props: NodeViewProps) { const [topSticky, setTopSticky] = useState(0); // Translations. - const transAlt = useTrans("back.wysiwyg.image.alt"); + const transAlt = useTrans("back.wysiwyg.image.attribute_alt"); + const transTitle = useTrans("back.wysiwyg.image.attribute_title"); const transCaption = useTrans("back.wysiwyg.image.caption"); const transValidation = (attribute: string) => { return { @@ -139,6 +143,7 @@ export default function ImageNodeView(props: NodeViewProps) { }; }; const transValidationAlt = transValidation(transAlt); + const transValidationTitle = transValidation(transTitle); const transValidationCaption = transValidation(transCaption); /** @@ -260,6 +265,7 @@ export default function ImageNodeView(props: NodeViewProps) { const fieldErrors: typeof errors = {}; const fieldMapping: Record = { alt: "alt", + title: "title", caption: "caption", }; if (err instanceof z.ZodError) { @@ -284,6 +290,7 @@ export default function ImageNodeView(props: NodeViewProps) { const inputTextSchema = imageTextSchema({ hasCaption: inputText.hasCaption, transValidationAlt, + transValidationTitle, transValidationCaption, }); @@ -291,12 +298,14 @@ export default function ImageNodeView(props: NodeViewProps) { // Validate data. const parsedZod = inputTextSchema.parse({ alt: inputText.alt, + title: inputText.title, caption: inputText.caption, }); // Update data of the image in @tiptap editor. updateAttributes({ alt: parsedZod.alt, + title: parsedZod.title, caption: parsedZod.caption, }); setOpen(false); @@ -374,6 +383,7 @@ export default function ImageNodeView(props: NodeViewProps) { useEffect(() => { setInputText({ alt: inputText.alt, + title: inputText.title, caption: inputText.hasCaption ? inputText.caption : null, hasCaption: inputText.hasCaption, }); @@ -423,6 +433,7 @@ export default function ImageNodeView(props: NodeViewProps) { ref={imgRef} src={attrs.src.replace(url, "")} alt={attrs.alt} + title={attrs.title} width={attrs.width} height={attrs.height} draggable={false} diff --git a/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/ImageUtils.tsx b/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/ImageUtils.tsx index c2bcf26e..20da02e0 100644 --- a/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/ImageUtils.tsx +++ b/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/ImageUtils.tsx @@ -17,6 +17,7 @@ export const imageDetails = { export const imageTextSchema = ({ hasCaption, transValidationAlt, + transValidationTitle, transValidationCaption, }: { hasCaption: boolean; @@ -25,6 +26,11 @@ export const imageTextSchema = ({ max: string; string: string; }; + transValidationTitle: { + min: string; + max: string; + string: string; + }; transValidationCaption: { min: string; max: string; @@ -36,15 +42,21 @@ export const imageTextSchema = ({ .string({ message: transValidationAlt.string }) .min(3, transValidationAlt.min) .max(255, transValidationAlt.max); + // Attribute title zod rules. + const titleRules = z + .string({ message: transValidationTitle.string }) + .min(3, transValidationTitle.min) + .max(255, transValidationTitle.max); // Caption zod rules. const captionRules = z - .string({ message: transValidationAlt.string }) + .string({ message: transValidationCaption.string }) .min(3, transValidationCaption.min) .max(255, transValidationCaption.max); // Return zod object. return z.object({ alt: altRules, + title: titleRules, caption: hasCaption ? captionRules : z.null(), }); }; diff --git a/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/image.d.ts b/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/image.d.ts index 509eccbf..77f3987f 100644 --- a/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/image.d.ts +++ b/resources/ts/back/Components/Inputs/Wysiwyg/Extensions/Image/image.d.ts @@ -9,6 +9,7 @@ declare module "@tiptap/core" { setImage: (attrs: { src: string; alt: string; + title: string; caption: string | null; width: string; height: string;