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 ( tokens[i].type === "paragraph_open" && i + 2 < tokens.length && tokens[i + 1].type === "inline" && tokens[i + 1].children && tokens[i + 1].children.length === 1 && tokens[i + 1].children[0].type === "image" && tokens[i + 2].type === "paragraph_close" ) { // Check if the next token is a paragraph starting with emphasis if ( i + 5 < tokens.length && tokens[i + 3].type === "paragraph_open" && tokens[i + 4].type === "inline" && tokens[i + 4].children && tokens[i + 4].children.length > 0 && tokens[i + 4].children[0].type === "em_open" && tokens[i + 5].type === "paragraph_close" ) { figcaptionStartIndex = i + 3; // Start index of the caption paragraph // --- Replace tokens --- // 1. Change paragraph_open to figure_open const figureOpen = new state.Token("figure_open", "figure", 1); 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 const figcaptionOpen = new state.Token( "figcaption_open", "figcaption", 1, ); 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 tags const captionInlineToken = tokens[figcaptionStartIndex]; // Now at index i+3 after splice if ( captionInlineToken.children[0].type === "em_open" && captionInlineToken.children[captionInlineToken.children.length - 1] .type === "em_close" ) { captionInlineToken.children.shift(); // Remove em_open captionInlineToken.children.pop(); // Remove em_close } // Adjust level for caption content captionInlineToken.level += 1; captionInlineToken.children.forEach((child) => { child.level += 1; }); // 6. Change the caption's paragraph_close to figcaption_close const figcaptionClose = new state.Token( "figcaption_close", "figcaption", -1, ); tokens[figcaptionStartIndex + 1] = figcaptionClose; // Replace paragraph_close (now at i+4) // 7. Add figure_close after figcaption_close const figureClose = new state.Token("figure_close", "figure", -1); 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 } } } } md.core.ruler.after("inline", "figcaption", figcaptionRule); // Add renderer rules if they don't exist md.renderer.rules.figure_open = md.renderer.rules.figure_open || function () { return "
\n"; }; md.renderer.rules.figure_close = md.renderer.rules.figure_close || function () { return "
\n"; }; md.renderer.rules.figcaption_open = md.renderer.rules.figcaption_open || function () { return "
"; }; md.renderer.rules.figcaption_close = md.renderer.rules.figcaption_close || function () { return "
\n"; }; }