development
dependabot/npm_and_yarn/astro-7.4.0
dependabot/npm_and_yarn/astrojs/mdx-7.0.8
dependabot/npm_and_yarn/astrojs/sitemap-3.7.4
dependabot/npm_and_yarn/katex-0.19.0
dependabot/npm_and_yarn/prettier-3.9.10
dependabot/npm_and_yarn/sharp-0.35.5
dependabot/npm_and_yarn/tsx-4.23.15
dependabot/npm_and_yarn/typescript-eslint-8.71.2
dependabot/npm_and_yarn/typescript-eslint/parser-8.71.2
development
main
| 1 | import { visit } from 'unist-util-visit' |
| 2 | import { SITE } from '../config.ts' |
| 3 | |
| 4 | /** |
| 5 | * Rehype plugin that processes images in markdown content: |
| 6 | * - Wraps images with alt text in figure/figcaption elements |
| 7 | * - Adds data-preview attribute for image viewer functionality |
| 8 | * - Handles multiple images in a single paragraph |
| 9 | */ |
| 10 | export default function rehypeImageProcessor() { |
| 11 | return (tree) => { |
| 12 | visit(tree, 'element', (node, index, parent) => { |
| 13 | if (node.tagName !== 'p') { |
| 14 | return |
| 15 | } |
| 16 | if (!parent || typeof index !== 'number') { |
| 17 | return |
| 18 | } |
| 19 | |
| 20 | const imgNodes = [] |
| 21 | let hasNonImageContent = false |
| 22 | |
| 23 | for (const child of node.children) { |
| 24 | if (child.type === 'element' && child.tagName === 'img') { |
| 25 | imgNodes.push(child) |
| 26 | } else if (child.type !== 'text' || child.value.trim() !== '') { |
| 27 | hasNonImageContent = true |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | if (hasNonImageContent || imgNodes.length === 0) { |
| 32 | return |
| 33 | } |
| 34 | |
| 35 | const newNodes = [] |
| 36 | |
| 37 | for (const imgNode of imgNodes) { |
| 38 | const alt = imgNode.properties?.alt?.trim() |
| 39 | |
| 40 | imgNode.properties = { |
| 41 | ...imgNode.properties, |
| 42 | 'data-preview': SITE.imageViewer ? 'true' : 'false' |
| 43 | } |
| 44 | |
| 45 | if (!alt || alt.includes('_')) { |
| 46 | newNodes.push(imgNode) |
| 47 | continue |
| 48 | } |
| 49 | |
| 50 | const figure = { |
| 51 | type: 'element', |
| 52 | tagName: 'figure', |
| 53 | properties: { |
| 54 | className: ['image-caption-wrapper'] |
| 55 | }, |
| 56 | children: [ |
| 57 | imgNode, |
| 58 | { |
| 59 | type: 'element', |
| 60 | tagName: 'figcaption', |
| 61 | properties: { |
| 62 | className: ['img-caption'] |
| 63 | }, |
| 64 | children: [ |
| 65 | { |
| 66 | type: 'text', |
| 67 | value: alt |
| 68 | } |
| 69 | ] |
| 70 | } |
| 71 | ] |
| 72 | } |
| 73 | |
| 74 | newNodes.push(figure) |
| 75 | } |
| 76 | |
| 77 | if (newNodes.length > 0) { |
| 78 | parent.children.splice(index, 1, ...newNodes) |
| 79 | return index + newNodes.length - 1 |
| 80 | } |
| 81 | }) |
| 82 | } |
| 83 | } |