11/**
2- * @import {Element, Root} from 'hast'
2+ * @import {Root} from 'hast'
33 */
44
5- /**
6- * This is a private fork of https://github.com/rehypejs/rehype-unwrap-images to work around a Nextra bug.
7- * See https://github.com/rehypejs/rehype-unwrap-images/pull/1 for why this won't be merged upstream.
8- */
9-
10- import { interactive } from 'hast-util-interactive'
115import { whitespace } from 'hast-util-whitespace'
126import { SKIP , visit } from 'unist-util-visit'
137
14- const unknown = 1
15- const containsImage = 2
16- const containsOther = 3
8+ const isImage = ( node ) =>
9+ ( node . type === 'element' && node . tagName === 'img' ) || ( node . type === 'mdxJsxFlowElement' && node . name === 'img' )
1710
1811/**
19- * Remove the wrapping paragraph for images.
12+ * This plugin does two things:
13+ * 1. Removes the `p` tag when it only contains images (and whitespace)
14+ * 2. Adds data attributes to help with image rendering:
15+ * - `data-wrapping-image` to links that contain an image
16+ * - `data-inline-image` to images that are in a paragraph with other content (text, links, etc.)
2017 *
2118 * @returns
2219 * Transform.
@@ -32,54 +29,38 @@ export default function rehypeUnwrapImages() {
3229 */
3330 return function ( tree ) {
3431 visit ( tree , 'element' , function ( node , index , parent ) {
35- if ( node . tagName === 'p' && parent && typeof index === 'number' && applicable ( node , false ) === containsImage ) {
36- parent . children . splice ( index , 1 , ...node . children )
37- return [ SKIP , index ]
38- }
39- } )
40- }
41- }
42-
43- /**
44- * Check if a node can be unraveled.
45- *
46- * @param {Element } node
47- * Node.
48- * @param {boolean } inLink
49- * Whether the node is in a link.
50- * @returns {1 | 2 | 3 }
51- * Info.
52- */
53- function applicable ( node , inLink ) {
54- /** @type {1 | 2 | 3 } */
55- let image = unknown
56- let index = - 1
57-
58- while ( ++ index < node . children . length ) {
59- const child = node . children [ index ]
32+ if ( node . tagName === 'p' && parent && typeof index === 'number' ) {
33+ // First pass: check if the paragraph contains any non-image content
34+ const hasNonImageContent = node . children . some ( ( child ) => {
35+ if ( child . type === 'text' ) {
36+ return ! whitespace ( child . value )
37+ } else {
38+ return ! isImage ( child )
39+ }
40+ } )
6041
61- if ( child . type === 'text' && whitespace ( child . value ) ) {
62- // Whitespace is fine.
63- } else if (
64- ( child . type === 'element' && child . tagName === 'img' ) ||
65- ( child . type === 'mdxJsxFlowElement' && child . name === 'img' )
66- ) {
67- image = containsImage
68- } else if ( ! inLink && interactive ( child ) ) {
69- // Cast as `interactive` is always `Element`.
70- const linkResult = applicable ( /** @type {Element } */ ( child ) , true )
42+ // Second pass: add data attributes
43+ node . children . forEach ( ( child ) => {
44+ if ( child . type === 'element' && child . tagName === 'a' && child . children . some ( isImage ) ) {
45+ child . properties = child . properties || { }
46+ child . properties [ 'data-wrapping-image' ] = true
47+ } else if ( isImage ( child ) && hasNonImageContent ) {
48+ if ( child . type === 'mdxJsxFlowElement' ) {
49+ child . attributes = child . attributes || [ ]
50+ child . attributes . push ( { type : 'mdxJsxAttribute' , name : 'data-inline-image' , value : true } )
51+ } else {
52+ child . properties = child . properties || { }
53+ child . properties [ 'data-inline-image' ] = true
54+ }
55+ }
56+ } )
7157
72- if ( linkResult === containsOther ) {
73- return containsOther
58+ // If the paragraph only contains images (and whitespace), remove it
59+ if ( ! hasNonImageContent ) {
60+ parent . children . splice ( index , 1 , ...node . children )
61+ return [ SKIP , index ]
62+ }
7463 }
75-
76- if ( linkResult === containsImage ) {
77- image = containsImage
78- }
79- } else {
80- return containsOther
81- }
64+ } )
8265 }
83-
84- return image
8566}
0 commit comments