-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrehype.mjs
More file actions
145 lines (133 loc) · 4.44 KB
/
Copy pathrehype.mjs
File metadata and controls
145 lines (133 loc) · 4.44 KB
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
const isElement = (node) =>
node && node.type === "element" && typeof node.tagName === "string";
const isTableCell = (node) =>
isElement(node) && (node.tagName === "th" || node.tagName === "td");
const getClasses = (node) => {
const c = node.properties?.class;
if (!c) return [];
return Array.isArray(c) ? c : [c];
};
const childText = (node) => {
let out = "";
const stack = node.children ? [...node.children] : [];
while (stack.length) {
const n = stack.pop();
if (n.type === "text" || n.type === "raw") out = n.value + out;
if (n.children) for (const c of n.children) stack.push(c);
}
return out;
};
/**
* Makes tables responsive on mobile by converting each row into a mini table
* with the header on the left and the row's content on the right.
*/
export const rehypeResponsiveTables = {
name: "rehypeResponsiveTables",
element: [
{
filter: ["table"],
visit(table, ctx) {
const headerRows = [];
const bodyRows = [];
for (const child of table.children || []) {
if (!isElement(child)) continue;
if (child.tagName === "thead") {
for (const c of child.children || [])
if (isElement(c) && c.tagName === "tr") headerRows.push(c);
} else if (child.tagName === "tbody") {
for (const c of child.children || [])
if (isElement(c) && c.tagName === "tr") bodyRows.push(c);
} else if (child.tagName === "tr") {
bodyRows.push(child);
}
}
if (headerRows[0] && getClasses(headerRows[0]).includes("md:table-row"))
return;
if (headerRows.length === 0 && bodyRows.length === 0) return;
const headerTexts = headerRows[0]
? (headerRows[0].children || []).filter(isTableCell).map(childText)
: [];
if (headerRows[0]) {
ctx.setProperty(headerRows[0], "class", ["hidden", "md:table-row"]);
}
for (const row of bodyRows) {
const cells = (row.children || []).filter(isTableCell);
if (cells.length === 0) continue;
if (cells[0]) ctx.setProperty(cells[0], "class", ["md:pl-0"]);
cells.forEach((_cell, i) => {
if (headerTexts[i] === undefined) return;
ctx.insertChildAt(row, i * 2, {
type: "element",
tagName: "td",
properties: {
class: ["table-cell", "pl-0", "font-bold", "md:hidden"],
"aria-hidden": "true",
},
children: [{ type: "text", value: headerTexts[i] }],
});
});
ctx.setProperty(row, "class", [
"row",
"grid",
"grid-cols-2",
"md:table-row",
]);
}
},
},
],
};
/**
* Wraps each image in the rendered Markdown with a PhotoSwipe item
* (`<a>` with `data-pswp-*`).
*/
export const rehypeGalleryImages = {
name: "rehypeGalleryImages",
element: [
{
filter: ["img"],
visit(img, ctx) {
const parent = ctx.parent(img);
if (isElement(parent) && parent.tagName === "a") return;
// ctx.data is scoped to the document being rendered, so this is
// true only for the first transformed image of each post.
const priority = !ctx.data.__gallerySawImage;
ctx.data.__gallerySawImage = true;
return transformImage(img, ctx, priority);
},
},
],
};
function transformImage(node, ctx, priority) {
const props = node.properties || {};
const src = props.src;
if (typeof src !== "string") return;
// The image is emitted in multiple build-time variants (image.layout:
// "constrained"). The sizes hint matches the prose column width so
// browsers pick a small variant inline; the gallery script points the
// lightbox at the largest variant for full-size viewing.
ctx.replaceNode(node, {
type: "element",
tagName: "a",
properties: {
"data-pswp": "",
},
children: [
{
type: "element",
tagName: "img",
properties: {
src,
alt: props.alt ?? "Image",
sizes: "(min-width: 768px) 720px, 100vw",
// Mirror what the `priority` prop does on Astro's <Image>:
loading: priority ? "eager" : "lazy",
decoding: priority ? "sync" : "async",
fetchpriority: priority ? "high" : undefined,
class: "cursor-pointer rounded-md",
},
children: [],
},
],
});
}