From 26ddcbfa89048d95647c74e33b755f72826fa39a Mon Sep 17 00:00:00 2001 From: saji Date: Fri, 2 May 2025 21:31:28 -0500 Subject: [PATCH] wip: add markdown-it-figcaption --- markdown-it-figcaption.js | 83 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 markdown-it-figcaption.js diff --git a/markdown-it-figcaption.js b/markdown-it-figcaption.js new file mode 100644 index 0000000..88d8b13 --- /dev/null +++ b/markdown-it-figcaption.js @@ -0,0 +1,83 @@ +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'; }; +} + +