1
0
Fork 0
blog/markdown-it-figcaption.js

108 lines
3.8 KiB
JavaScript
Raw Permalink Normal View History

2025-05-03 02:31:28 +00:00
export default function figcaptionPlugin(md) {
// Rule to identify images followed by italicized text for figcaption
function figcaptionRule(state) {
const tokens = state.tokens;
let figcaptionStartIndex = -1;
for (let i = 0; i < tokens.length; i++) {
// Check for paragraph containing only an image
if (
2025-05-03 03:37:23 +00:00
tokens[i].type === "paragraph_open" &&
2025-05-03 02:31:28 +00:00
i + 2 < tokens.length &&
2025-05-03 03:37:23 +00:00
tokens[i + 1].type === "inline" &&
2025-05-03 02:31:28 +00:00
tokens[i + 1].children &&
tokens[i + 1].children.length === 1 &&
2025-05-03 03:37:23 +00:00
tokens[i + 1].children[0].type === "image" &&
tokens[i + 2].type === "paragraph_close"
2025-05-03 02:31:28 +00:00
) {
// Check if the next token is a paragraph starting with emphasis
if (
i + 5 < tokens.length &&
2025-05-03 03:37:23 +00:00
tokens[i + 3].type === "paragraph_open" &&
tokens[i + 4].type === "inline" &&
2025-05-03 02:31:28 +00:00
tokens[i + 4].children &&
tokens[i + 4].children.length > 0 &&
2025-05-03 03:37:23 +00:00
tokens[i + 4].children[0].type === "em_open" &&
tokens[i + 5].type === "paragraph_close"
2025-05-03 02:31:28 +00:00
) {
figcaptionStartIndex = i + 3; // Start index of the caption paragraph
// --- Replace tokens ---
// 1. Change paragraph_open to figure_open
2025-05-03 03:37:23 +00:00
const figureOpen = new state.Token("figure_open", "figure", 1);
2025-05-03 02:31:28 +00:00
tokens[i] = figureOpen; // Replace paragraph_open
// 2. Keep the inline token with the image as is (tokens[i+1])
// 3. Change paragraph_close to figcaption_open
2025-05-03 03:37:23 +00:00
const figcaptionOpen = new state.Token(
"figcaption_open",
"figcaption",
1,
);
2025-05-03 02:31:28 +00:00
tokens[i + 2] = figcaptionOpen; // Replace paragraph_close
// 4. Remove the caption's paragraph_open
tokens.splice(figcaptionStartIndex, 1); // Remove paragraph_open at i+3
// 5. Modify the caption's inline content: remove outer <em> tags
const captionInlineToken = tokens[figcaptionStartIndex]; // Now at index i+3 after splice
if (
2025-05-03 03:37:23 +00:00
captionInlineToken.children[0].type === "em_open" &&
captionInlineToken.children[captionInlineToken.children.length - 1]
.type === "em_close"
2025-05-03 02:31:28 +00:00
) {
captionInlineToken.children.shift(); // Remove em_open
captionInlineToken.children.pop(); // Remove em_close
}
// Adjust level for caption content
captionInlineToken.level += 1;
2025-05-03 03:37:23 +00:00
captionInlineToken.children.forEach((child) => {
child.level += 1;
});
2025-05-03 02:31:28 +00:00
// 6. Change the caption's paragraph_close to figcaption_close
2025-05-03 03:37:23 +00:00
const figcaptionClose = new state.Token(
"figcaption_close",
"figcaption",
-1,
);
2025-05-03 02:31:28 +00:00
tokens[figcaptionStartIndex + 1] = figcaptionClose; // Replace paragraph_close (now at i+4)
// 7. Add figure_close after figcaption_close
2025-05-03 03:37:23 +00:00
const figureClose = new state.Token("figure_close", "figure", -1);
2025-05-03 02:31:28 +00:00
tokens.splice(figcaptionStartIndex + 2, 0, figureClose); // Insert figure_close (at i+5)
// Adjust token index to skip the newly inserted/modified tokens
i += 6; // Move past the figure structure
}
}
}
}
2025-05-03 03:37:23 +00:00
md.core.ruler.after("inline", "figcaption", figcaptionRule);
2025-05-03 02:31:28 +00:00
// Add renderer rules if they don't exist
2025-05-03 03:37:23 +00:00
md.renderer.rules.figure_open =
md.renderer.rules.figure_open ||
function () {
return "<figure>\n";
};
md.renderer.rules.figure_close =
md.renderer.rules.figure_close ||
function () {
return "</figure>\n";
};
md.renderer.rules.figcaption_open =
md.renderer.rules.figcaption_open ||
function () {
return "<figcaption>";
};
md.renderer.rules.figcaption_close =
md.renderer.rules.figcaption_close ||
function () {
return "</figcaption>\n";
};
2025-05-03 02:31:28 +00:00
}