1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
import type { Element, Root } from "hast";
import { h } from "hastscript";
import type { Plugin } from "unified";
import { visit } from "unist-util-visit";
/**
* Rehype plugin that wraps images with figure/figcaption to display alt text on hover.
* Skips images without alt text or with generic alt text like "Image".
*/
export const rehypeImageCaptions: Plugin<[], Root> = () => {
return (tree: Root) => {
visit(tree, "element", (node: Element, index, parent) => {
// Only process img elements that have a parent and an index
if (node.tagName !== "img" || !parent || index === undefined) {
return;
}
// Get the alt text
const alt = node.properties?.alt;
if (!alt || typeof alt !== "string") {
return;
}
// Skip generic or empty alt text
const trimmedAlt = alt.trim();
if (!trimmedAlt || trimmedAlt.toLowerCase() === "image") {
return;
}
// Preserve all existing properties including data-zoomable for medium-zoom
const imgProperties = { ...node.properties };
// Create the figcaption element with tailwind classes for hover overlay
const figcaption = h(
"figcaption",
{
class: [
"pointer-events-none",
"absolute",
"bottom-0",
"left-0",
"right-0",
"rounded-b-lg",
"bg-black/70",
"px-3",
"py-2",
"text-sm",
"text-white",
"opacity-0",
"transition-opacity",
"group-hover:opacity-100",
].join(" "),
},
[{ type: "text", value: trimmedAlt }],
);
// Create the figure wrapper with group class for hover trigger
const figure = h(
"figure",
{
class: "image-with-alt group relative",
},
[
{
type: "element",
tagName: "img",
properties: imgProperties,
children: [],
},
figcaption,
],
);
// Replace the img with the figure in the parent
parent.children[index] = figure;
});
};
};
|